diff --git a/code/__DEFINES/dcs/signals/atom/mob/living/signals_xeno.dm b/code/__DEFINES/dcs/signals/atom/mob/living/signals_xeno.dm
index ab233e9cf82c..e76768a82b46 100644
--- a/code/__DEFINES/dcs/signals/atom/mob/living/signals_xeno.dm
+++ b/code/__DEFINES/dcs/signals/atom/mob/living/signals_xeno.dm
@@ -50,6 +50,10 @@
/// From /datum/action/xeno_action/proc/use_ability_wrapper(): (mob/owner)
#define COMSIG_XENO_ACTION_USED "xeno_action_used"
+/// From /datum/action/xeno_action/proc/use_ability_wrapper(): (mob/owner)
+#define COMSIG_XENO_PRE_ACTION_USED "xeno_pre_action_used"
+/// From /datum/action/xeno_action/proc/use_ability_wrapper(): (mob/owner)
+#define COMSIG_XENO_FAILED_ACTION_USED "xeno_failed_action_used"
/// From /mob/living/carbon/xenomorph/proc/check_blood_splash()
#define COMSIG_XENO_DEAL_ACID_DAMAGE "xeno_deal_acid_damage"
/// From /mob/living/carbon/xenomorph/proc/recalculate_speed()
diff --git a/code/__DEFINES/dcs/signals/atom/signals_item.dm b/code/__DEFINES/dcs/signals/atom/signals_item.dm
index 5ba79960657b..88f99bbff578 100644
--- a/code/__DEFINES/dcs/signals/atom/signals_item.dm
+++ b/code/__DEFINES/dcs/signals/atom/signals_item.dm
@@ -83,3 +83,5 @@
#define COMSIG_CAMERA_SET_AREA "camera_manager_set_area"
#define COMSIG_CAMERA_CLEAR "camera_manager_clear_target"
#define COMSIG_CAMERA_REFRESH "camera_manager_refresh"
+
+#define COMSIG_PRED_BRACER_DECLOAKED "pred_bracer_decloaked"
diff --git a/code/__DEFINES/dcs/signals/atom/signals_movable.dm b/code/__DEFINES/dcs/signals/atom/signals_movable.dm
index ba889d0b5212..ad4be2b1dc9f 100644
--- a/code/__DEFINES/dcs/signals/atom/signals_movable.dm
+++ b/code/__DEFINES/dcs/signals/atom/signals_movable.dm
@@ -11,6 +11,9 @@
#define COMPONENT_CANCEL_MOVE (1<<0)
/// From /turf/open/gm/river/Entered(): (turf/open/gm/river/river, covered)
#define COMSIG_MOVABLE_ENTERED_RIVER "movable_entered_river"
+/// From /atom/movable/proc/doMove: I think it only works with forceMove so watch out
+#define COMSIG_MOVABLE_FORCEMOVE_PRE_CROSSED "movable_forcemove_pre_crossed"
+ #define COMPONENT_IGNORE_CROSS (1<<0)
///from /mob/living/carbon/xenomorph/start_pulling(): (mob/living/carbon/xenomorph/X)
#define COMSIG_MOVABLE_XENO_START_PULLING "movable_xeno_start_pulling"
diff --git a/code/__DEFINES/dcs/signals/signals_datum.dm b/code/__DEFINES/dcs/signals/signals_datum.dm
index b798d510763e..c35038fcf3e9 100644
--- a/code/__DEFINES/dcs/signals/signals_datum.dm
+++ b/code/__DEFINES/dcs/signals/signals_datum.dm
@@ -34,6 +34,8 @@
#define COMSIG_ACTION_HIDDEN "action_hidden"
/// From base of /datum/action/proc/unhide_from(): (mob/owner)
#define COMSIG_ACTION_UNHIDDEN "action_unhidden"
+/// From base of /datum/action/proc/action_activate() : ()
+#define COMSIG_ACTION_ACTIVATED "action_activated"
///from /datum/component/bonus_damage_stack
#define COMSIG_BONUS_DAMAGE "bonus_damage"
diff --git a/code/__DEFINES/mobs.dm b/code/__DEFINES/mobs.dm
index 77f2393e6542..d8f820ab382b 100644
--- a/code/__DEFINES/mobs.dm
+++ b/code/__DEFINES/mobs.dm
@@ -113,7 +113,7 @@
#define CANROOT (1<<6)
#define GODMODE (1<<12)
#define FAKEDEATH (1<<13) //Replaces stuff like changeling.changeling_fakedeath
-#define DISFIGURED (1<<14) //I'll probably move this elsewhere if I ever get wround to writing a bitflag mob-damage system
+//#define DISFIGURED (1<<14) //unused
#define XENO_HOST (1<<15) //Tracks whether we're gonna be a baby alien's mummy.
#define IMMOBILE_ACTION (1<<16) // If you are performing an action that prevents you from being pushed by your own people.
#define PERMANENTLY_DEAD (1<<17)
diff --git a/code/__DEFINES/subsystems.dm b/code/__DEFINES/subsystems.dm
index 47aa0e732c76..88496c79d630 100644
--- a/code/__DEFINES/subsystems.dm
+++ b/code/__DEFINES/subsystems.dm
@@ -123,6 +123,7 @@
#define SS_INIT_NIGHTMARE 21.5
#define SS_INIT_TIMETRACK 21.1
#define SS_INIT_HUMANS 21
+#define SS_INIT_WHO 20
#define SS_INIT_POWER 19
#define SS_INIT_INFLUXMCSTATS 12
#define SS_INIT_INFLUXSTATS 11
diff --git a/code/__DEFINES/traits.dm b/code/__DEFINES/traits.dm
index 964e77402655..e6b9c4c4b9ee 100644
--- a/code/__DEFINES/traits.dm
+++ b/code/__DEFINES/traits.dm
@@ -161,6 +161,8 @@
#define TRAIT_MERGED_WITH_WEEDS "merged_with_weeds"
/// Apply this to identify a mob as temporarily muted
#define TRAIT_TEMPORARILY_MUTED "temporarily_muted"
+/// Mob wont get hit by stray projectiles
+#define TRAIT_NO_STRAY "trait_no_stray"
// SPECIES TRAITS
/// Knowledge of Yautja technology
diff --git a/code/__HELPERS/_lists.dm b/code/__HELPERS/_lists.dm
index e46c92df543a..aa73d6008e02 100644
--- a/code/__HELPERS/_lists.dm
+++ b/code/__HELPERS/_lists.dm
@@ -128,17 +128,23 @@
* You should only pass integers in.
*/
/proc/pick_weight(list/list_to_pick)
+ if(length(list_to_pick) == 0)
+ return null
+
var/total = 0
- var/item
- for(item in list_to_pick)
+ for(var/item in list_to_pick)
if(!list_to_pick[item])
list_to_pick[item] = 0
total += list_to_pick[item]
- total = rand(0, total)
- for(item in list_to_pick)
- total -= list_to_pick[item]
- if(total <= 0 && list_to_pick[item])
+ total = rand(1, total)
+ for(var/item in list_to_pick)
+ var/item_weight = list_to_pick[item]
+ if(item_weight == 0)
+ continue
+
+ total -= item_weight
+ if(total <= 0)
return item
return null
diff --git a/code/__HELPERS/game.dm b/code/__HELPERS/game.dm
index 5bf36f785746..094f8205c80e 100644
--- a/code/__HELPERS/game.dm
+++ b/code/__HELPERS/game.dm
@@ -235,7 +235,7 @@
* * hive - The hive we're filling a slot for to check if the player is banished
* * sorted - Whether to sort by larva_queue_time (default TRUE) or leave unsorted
*/
-/proc/get_alien_candidates(datum/hive_status/hive = null, sorted = TRUE)
+/proc/get_alien_candidates(datum/hive_status/hive = null, sorted = TRUE, abomination = FALSE)
var/list/candidates = list()
for(var/mob/dead/observer/cur_obs as anything in GLOB.observer_list)
@@ -275,6 +275,11 @@
if(banished)
continue
+ if(abomination)
+ if(!(/datum/tutorial/xenomorph/abomination::tutorial_id in cur_obs.client.prefs.completed_tutorials))
+ to_chat(cur_obs, SPAN_BOLDNOTICE("You were passed over for playing as an Abomination because you have not completed its tutorial."))
+ continue
+
candidates += cur_obs
// Optionally sort by larva_queue_time
diff --git a/code/__HELPERS/lists.dm b/code/__HELPERS/lists.dm
index d5212611a04b..32ea0f5ec32e 100644
--- a/code/__HELPERS/lists.dm
+++ b/code/__HELPERS/lists.dm
@@ -87,22 +87,6 @@
result = first ^ second
return result
-//Pretends to pick an element based on its weight but really just seems to pick a random element.
-/proc/pickweight(list/L)
- var/total = 0
- var/item
- for (item in L)
- if (!L[item])
- L[item] = 1
- total += L[item]
-
- total = rand(1, total)
- for (item in L)
- total -=L [item]
- if (total <= 0)
- return item
- return null
-
/// Pick a random element from the list and remove it from the list.
/proc/pick_n_take(list/L)
RETURN_TYPE(L[_].type)
diff --git a/code/_globalvars/bitfields.dm b/code/_globalvars/bitfields.dm
index facc2b951ad3..d3fc9994414a 100644
--- a/code/_globalvars/bitfields.dm
+++ b/code/_globalvars/bitfields.dm
@@ -356,7 +356,6 @@ DEFINE_BITFIELD(status_flags, list(
"PASSEMOTES" = PASSEMOTES,
"GODMODE" = GODMODE,
"FAKEDEATH" = FAKEDEATH,
- "DISFIGURED" = DISFIGURED,
"XENO_HOST" = XENO_HOST,
"IMMOBILE_ACTION" = IMMOBILE_ACTION,
"PERMANENTLY_DEAD" = PERMANENTLY_DEAD,
diff --git a/code/_globalvars/global_lists.dm b/code/_globalvars/global_lists.dm
index c054b9a4bf1f..c6957eefd22c 100644
--- a/code/_globalvars/global_lists.dm
+++ b/code/_globalvars/global_lists.dm
@@ -119,6 +119,34 @@ GLOBAL_LIST(chemical_reactions_filtered_list) //List of all /datum/chemical_reac
GLOBAL_LIST(chemical_reactions_list) //List of all /datum/chemical_reaction datums indexed by reaction id. Used to search for the result instead of the components.
GLOBAL_LIST(chemical_reagents_list) //List of all /datum/reagent datums indexed by reagent id. Used by chemistry stuff
GLOBAL_LIST(chemical_properties_list) //List of all /datum/chem_property datums indexed by property name
+//list of all properties that conflict with each other.
+GLOBAL_LIST_INIT_TYPED(conflicting_properties, /list, list( PROPERTY_NUTRITIOUS = PROPERTY_HEMORRAGING, PROPERTY_NUTRITIOUS = PROPERTY_HEMOLYTIC, PROPERTY_TOXIC = PROPERTY_ANTITOXIC,\
+ PROPERTY_CORROSIVE = PROPERTY_ANTICORROSIVE, PROPERTY_BIOCIDIC = PROPERTY_NEOGENETIC, PROPERTY_HYPERTHERMIC = PROPERTY_HYPOTHERMIC,\
+ PROPERTY_NUTRITIOUS = PROPERTY_KETOGENIC, PROPERTY_PAINING = PROPERTY_PAINKILLING, PROPERTY_HALLUCINOGENIC = PROPERTY_ANTIHALLUCINOGENIC,\
+ PROPERTY_HEPATOTOXIC = PROPERTY_HEPATOPEUTIC, PROPERTY_NEPHROTOXIC = PROPERTY_NEPHROPEUTIC, PROPERTY_PNEUMOTOXIC = PROPERTY_PNEUMOPEUTIC,\
+ PROPERTY_OCULOTOXIC = PROPERTY_OCULOPEUTIC, PROPERTY_CARDIOTOXIC = PROPERTY_CARDIOPEUTIC, PROPERTY_NEUROTOXIC = PROPERTY_NEUROPEUTIC,\
+ PROPERTY_FLUXING = PROPERTY_REPAIRING, PROPERTY_RELAXING = PROPERTY_MUSCLESTIMULATING, PROPERTY_HEMOGENIC = PROPERTY_HEMOLYTIC,\
+ PROPERTY_HEMOGENIC = PROPERTY_HEMORRAGING, PROPERTY_NUTRITIOUS = PROPERTY_EMETIC,\
+ PROPERTY_HYPERGENETIC = PROPERTY_NEOGENETIC, PROPERTY_HYPERGENETIC = PROPERTY_HEPATOPEUTIC, PROPERTY_HYPERGENETIC = PROPERTY_NEPHROPEUTIC,\
+ PROPERTY_HYPERGENETIC = PROPERTY_PNEUMOPEUTIC, PROPERTY_HYPERGENETIC = PROPERTY_OCULOPEUTIC, PROPERTY_HYPERGENETIC = PROPERTY_CARDIOPEUTIC,\
+ PROPERTY_HYPERGENETIC = PROPERTY_NEUROPEUTIC, PROPERTY_ADDICTIVE = PROPERTY_ANTIADDICTIVE, PROPERTY_NEUROSHIELDING = PROPERTY_NEUROTOXIC,\
+ PROPERTY_HYPOMETABOLIC = PROPERTY_HYPERMETABOLIC, PROPERTY_HYPERTHROTTLING = PROPERTY_NEUROINHIBITING,
+ PROPERTY_FOCUSING = PROPERTY_NERVESTIMULATING, PROPERTY_THERMOSTABILIZING = PROPERTY_HYPERTHERMIC, PROPERTY_THERMOSTABILIZING = PROPERTY_HYPOTHERMIC,
+ PROPERTY_AIDING = PROPERTY_NEUROINHIBITING, PROPERTY_OXYGENATING = PROPERTY_HYPOXEMIC, PROPERTY_ANTICARCINOGENIC = PROPERTY_CARCINOGENIC, \
+ PROPERTY_CIPHERING = PROPERTY_CIPHERING_PREDATOR, PROPERTY_TRANSFORMATIVE = PROPERTY_ANTITOXIC, PROPERTY_MUSCLESTIMULATING = PROPERTY_NERVESTIMULATING))
+//list of all properties that combine into something else, now featured in global list
+GLOBAL_LIST_INIT_TYPED(combining_properties, /list, list( PROPERTY_DEFIBRILLATING = list(PROPERTY_MUSCLESTIMULATING, PROPERTY_CARDIOPEUTIC),\
+ PROPERTY_THANATOMETABOL = list(PROPERTY_HYPOXEMIC, PROPERTY_CRYOMETABOLIZING, PROPERTY_NEUROCRYOGENIC),\
+ PROPERTY_HYPERDENSIFICATING = list(PROPERTY_MUSCLESTIMULATING, PROPERTY_BONEMENDING, PROPERTY_CARCINOGENIC),\
+ PROPERTY_HYPERTHROTTLING = list(PROPERTY_PSYCHOSTIMULATING, PROPERTY_HALLUCINOGENIC),\
+ PROPERTY_NEUROSHIELDING = list(PROPERTY_ALCOHOLIC, PROPERTY_BALDING),\
+ PROPERTY_ANTIADDICTIVE = list(PROPERTY_PSYCHOSTIMULATING, PROPERTY_ANTIHALLUCINOGENIC),\
+ PROPERTY_ADDICTIVE = list(PROPERTY_PSYCHOSTIMULATING, PROPERTY_NEUROTOXIC),\
+ PROPERTY_CIPHERING_PREDATOR = list(PROPERTY_CIPHERING, PROPERTY_CROSSMETABOLIZING),\
+ PROPERTY_FIRE_PENETRATING = list(PROPERTY_OXYGENATING, PROPERTY_VISCOUS),\
+ PROPERTY_BONEMENDING = list(PROPERTY_HYPERDENSIFICATING, PROPERTY_NUTRITIOUS),\
+ PROPERTY_BONEMENDING = list(PROPERTY_HYPERDENSIFICATING, PROPERTY_NUTRITIOUS),\
+ PROPERTY_ENCEPHALOPHRASIVE = list(PROPERTY_NERVESTIMULATING, PROPERTY_PSYCHOSTIMULATING)))
//List of all id's from classed /datum/reagent datums indexed by class or tier. Used by chemistry generator and chem spawners.
GLOBAL_LIST_INIT_TYPED(chemical_gen_classes_list, /list, list("C" = list(),"C1" = list(),"C2" = list(),"C3" = list(),"C4" = list(),"C5" = list(),"C6" = list(),"T1" = list(),"T2" = list(),"T3" = list(),"T4" = list(),"tau", list()))
//properties generated in chemicals, helps to make sure the same property doesn't show up 10 times
diff --git a/code/_macros.dm b/code/_macros.dm
index abfa83df7d36..9b92dc8730c3 100644
--- a/code/_macros.dm
+++ b/code/_macros.dm
@@ -95,6 +95,9 @@
#define GENERATE_DEBUG_ID "[rand(0, 9)][rand(0, 9)][rand(0, 9)][rand(0, 9)][pick(alphabet_lowercase)][pick(alphabet_lowercase)][pick(alphabet_lowercase)][pick(alphabet_lowercase)]"
#define RECT new /datum/shape/rectangle
+#define SQUARE new /datum/shape/rectangle/square
+#define ELLIPSE new /datum/shape/ellipse
+#define CIRCLE new /datum/shape/ellipse/circle
#define QTREE new /datum/quadtree
#define SEARCH_QTREE(qtree, shape_range, flags) qtree.query_range(shape_range, null, flags)
diff --git a/code/_onclick/xeno.dm b/code/_onclick/xeno.dm
index 3bb69fe05419..15dc1c39f495 100644
--- a/code/_onclick/xeno.dm
+++ b/code/_onclick/xeno.dm
@@ -3,7 +3,7 @@
*/
/mob/living/carbon/xenomorph/UnarmedAttack(atom/target, proximity, click_parameters, tile_attack = FALSE, ignores_resin = FALSE)
- if(body_position == LYING_DOWN || HAS_TRAIT(src, TRAIT_ABILITY_BURROWED)) //No attacks while laying down
+ if(body_position == LYING_DOWN || HAS_TRAIT(src, TRAIT_ABILITY_BURROWED) || cannot_slash) //No attacks while laying down
return FALSE
var/mob/alt
diff --git a/code/controllers/subsystem/sound.dm b/code/controllers/subsystem/sound.dm
index 024df7cc45ad..3cc3f0ef8090 100644
--- a/code/controllers/subsystem/sound.dm
+++ b/code/controllers/subsystem/sound.dm
@@ -19,8 +19,7 @@ SUBSYSTEM_DEF(sound)
if(!run_hearers) // Initialize for handling next template
run_hearers = run_queue[run_template] // get base hearers
if(run_template.range) // ranging
- var/datum/shape/rectangle/zone = RECT(run_template.x, run_template.y, run_template.range * 2, run_template.range * 2)
- run_hearers |= SSquadtree.players_in_range(zone, run_template.z)
+ run_hearers |= SSquadtree.players_in_range(SQUARE(run_template.x, run_template.y, run_template.range * 2), run_template.z)
if(MC_TICK_CHECK)
return
while(length(run_hearers)) // Output sound to hearers
diff --git a/code/controllers/subsystem/vote.dm b/code/controllers/subsystem/vote.dm
index 85e2a57cc6d6..25f522753543 100644
--- a/code/controllers/subsystem/vote.dm
+++ b/code/controllers/subsystem/vote.dm
@@ -414,6 +414,7 @@ SUBSYSTEM_DEF(vote)
qdel(src)
/datum/action/innate/vote/action_activate()
+ . = ..()
owner.vote()
/datum/action/innate/vote/proc/remove_from_client()
diff --git a/code/controllers/subsystem/who.dm b/code/controllers/subsystem/who.dm
new file mode 100644
index 000000000000..43ecbb435587
--- /dev/null
+++ b/code/controllers/subsystem/who.dm
@@ -0,0 +1,312 @@
+SUBSYSTEM_DEF(who)
+ name = "Who"
+ flags = SS_BACKGROUND
+ runlevels = RUNLEVELS_DEFAULT|RUNLEVEL_LOBBY
+ init_order = SS_INIT_WHO
+ wait = 5 SECONDS
+
+ var/datum/player_list/who = new
+ var/datum/player_list/staff/staff_who = new
+
+/datum/controller/subsystem/who/Initialize()
+ who.update_data()
+ staff_who.update_data()
+ return SS_INIT_SUCCESS
+
+/datum/controller/subsystem/who/fire(resumed = TRUE)
+ who.update_data()
+ staff_who.update_data()
+
+//datum
+/datum/player_list
+ var/tgui_name = "Who"
+ var/tgui_interface_name = "Who"
+ var/list/mobs_ckey = list()
+ var/list/list_data = list()
+
+/datum/player_list/proc/update_data()
+ var/list/new_list_data = list()
+ var/list/new_mobs_ckey = list()
+ var/list/additional_data = list(
+ "lobby" = 0,
+ "admin_observers" = 0,
+ "observers" = 0,
+ "yautja" = 0,
+ "infected_preds" = 0,
+ "humans" = 0,
+ "infected_humans" = 0,
+ "uscm" = 0,
+ "uscm_marines" = 0,
+ )
+ new_list_data["additional_info"] = list()
+ var/list/counted_factions = list()
+ for(var/client/client as anything in sortTim(GLOB.clients, GLOBAL_PROC_REF(cmp_ckey_asc)))
+ CHECK_TICK
+ new_list_data["all_clients"]++
+ var/list/client_payload = list()
+ client_payload["ckey"] = "[client.key]"
+ client_payload["text"] = "[client.key]"
+ client_payload["ckey_color"] = "white"
+ var/mob/client_mob = client.mob
+ new_mobs_ckey[client.key] = client_mob
+ if(client_mob)
+ if(istype(client_mob, /mob/new_player))
+ client_payload["text"] += " - in Lobby"
+ additional_data["lobby"]++
+
+ else if(isobserver(client_mob))
+ client_payload["text"] += " - Playing as [client_mob.real_name]"
+ if(CLIENT_IS_STAFF(client))
+ additional_data["admin_observers"]++
+ else
+ additional_data["observers"]++
+
+ var/mob/dead/observer/observer = client_mob
+ if(observer.started_as_observer)
+ client_payload["color"] += "#ce89cd"
+ client_payload["text"] += " - Spectating"
+ else
+ client_payload["color"] += "#A000D0"
+ client_payload["text"] += " - DEAD"
+
+ else
+ client_payload["text"] += " - Playing as [client_mob.real_name]"
+
+ switch(client_mob.stat)
+ if(UNCONSCIOUS)
+ client_payload["color"] += "#B0B0B0"
+ client_payload["text"] += " - Unconscious"
+ if(DEAD)
+ client_payload["color"] += "#A000D0"
+ client_payload["text"] += " - DEAD"
+
+ if(client_mob.stat != DEAD)
+ if(isxeno(client_mob))
+ client_payload["color"] += "#ec3535"
+ client_payload["text"] += " - Xenomorph"
+
+ else if(ishuman(client_mob))
+ if(client_mob.faction == FACTION_ZOMBIE)
+ counted_factions[FACTION_ZOMBIE]++
+ client_payload["color"] += "#2DACB1"
+ client_payload["text"] += " - Zombie"
+ else if(client_mob.faction == FACTION_YAUTJA)
+ client_payload["color"] += "#7ABA19"
+ client_payload["text"] += " - Yautja"
+ additional_data["yautja"]++
+ if(client_mob.status_flags & XENO_HOST)
+ additional_data["infected_preds"]++
+ else
+ additional_data["humans"]++
+ if(client_mob.status_flags & XENO_HOST)
+ additional_data["infected_humans"]++
+ if(client_mob.faction == FACTION_MARINE)
+ additional_data["uscm"]++
+ if(client_mob.job in (GLOB.ROLES_MARINES))
+ additional_data["uscm_marines"]++
+ else
+ counted_factions[client_mob.faction]++
+
+ new_list_data["total_players"] += list(client_payload)
+
+ new_list_data["additional_info"] += list(list(
+ "content" = "In Lobby: [additional_data["lobby"]]",
+ "color" = "#777",
+ "text" = "Player in lobby",
+ ))
+
+ new_list_data["additional_info"] += list(list(
+ "content" = "Spectating Players: [additional_data["observers"]]",
+ "color" = "#777",
+ "text" = "Spectating players",
+ ))
+
+ new_list_data["additional_info"] += list(list(
+ "content" = "Spectating Admins: [additional_data["admin_observers"]]",
+ "color" = "#777",
+ "text" = "Spectating administrators",
+ ))
+
+ new_list_data["additional_info"] += list(list(
+ "content" = "Humans: [additional_data["humans"]]",
+ "color" = "#2C7EFF",
+ "text" = "Players playing as Human",
+ ))
+
+ new_list_data["additional_info"] += list(list(
+ "content" = "Infected Humans: [additional_data["infected_humans"]]",
+ "color" = "#ec3535",
+ "text" = "Players playing as Infected Human",
+ ))
+
+ new_list_data["additional_info"] += list(list(
+ "content" = "[MAIN_SHIP_NAME] Personnel: [additional_data["uscm"]]",
+ "color" = "#5442bd",
+ "text" = "Players playing as [MAIN_SHIP_NAME] Personnel",
+ ))
+
+ new_list_data["additional_info"] += list(list(
+ "content" = "Marines: [additional_data["uscm_marines"]]",
+ "color" = "#5442bd",
+ "text" = "Players playing as Marines",
+ ))
+
+ new_list_data["additional_info"] += list(list(
+ "content" = "Yautjas: [additional_data["yautja"]]",
+ "color" = "#7ABA19",
+ "text" = "Players playing as Yautja",
+ ))
+
+ new_list_data["additional_info"] += list(list(
+ "content" = "Infected Predators: [additional_data["infected_preds"]]",
+ "color" = "#7ABA19",
+ "text" = "Players playing as Infected Yautja",
+ ))
+
+ for(var/i in 1 to length(counted_factions))
+ if(counted_factions[counted_factions[i]])
+ new_list_data["factions"] += list(list(
+ "content" = "[counted_factions[i]]: [counted_factions[counted_factions[i]]]",
+ "color" = "#2C7EFF",
+ "text" = "Other",
+ ))
+ if(counted_factions[FACTION_NEUTRAL])
+ new_list_data["factions"] += list(list(
+ "content" = "[FACTION_NEUTRAL] Humans: [counted_factions[FACTION_NEUTRAL]]",
+ "color" = "#688944",
+ "text" = "Neutrals",
+ ))
+
+ for(var/faction_to_get in ALL_XENO_HIVES)
+ var/datum/hive_status/hive = GLOB.hive_datum[faction_to_get]
+ if(hive && length(hive.totalXenos))
+ new_list_data["xenomorphs"] += list(list(
+ "content" = "[hive.name]: [length(hive.totalXenos)]",
+ "color" = hive.color ? hive.color : "#8200FF",
+ "text" = "Queen: [hive.living_xeno_queen ? "Alive" : "Dead"]",
+ ))
+
+ list_data = new_list_data
+ mobs_ckey = new_mobs_ckey
+
+/datum/player_list/tgui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, tgui_name, tgui_interface_name)
+ ui.open()
+ ui.set_autoupdate(TRUE)
+
+/datum/player_list/ui_data(mob/user)
+ . = list_data
+
+/datum/player_list/ui_static_data(mob/user)
+ . = list()
+
+ .["admin"] = CLIENT_IS_STAFF(user.client)
+
+/datum/player_list/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
+ . = ..()
+ if(.)
+ return
+
+ switch(action)
+ if("get_player_panel")
+ if(mobs_ckey[params["ckey"]])
+ GLOB.admin_datums[usr.client.ckey].show_player_panel(mobs_ckey[params["ckey"]])
+
+/datum/player_list/ui_status(mob/user, datum/ui_state/state)
+ return UI_INTERACTIVE
+
+
+/datum/player_list/staff
+ tgui_name = "StaffWho"
+ tgui_interface_name = "Staff Who"
+
+ var/list/category_colors = list(
+ "Management" = "purple",
+ "Maintainers" = "blue",
+ "Administrators" = "red",
+ "Moderators" = "orange",
+ "Mentors" = "green"
+ )
+
+/datum/player_list/staff/update_data()
+ var/list/new_list_data = list()
+ mobs_ckey = list()
+
+ var/list/listings
+ var/list/mappings
+ if(CONFIG_GET(flag/show_manager))
+ LAZYSET(mappings, "Management", R_PERMISSIONS)
+ if(CONFIG_GET(flag/show_devs))
+ LAZYSET(mappings, "Maintainers", R_PROFILER)
+ LAZYSET(mappings, "Administrators", R_ADMIN)
+ if(CONFIG_GET(flag/show_mods))
+ LAZYSET(mappings, "Moderators", R_MOD && R_BAN)
+ if(CONFIG_GET(flag/show_mentors))
+ LAZYSET(mappings, "Mentors", R_MENTOR)
+
+ for(var/category in mappings)
+ LAZYSET(listings, category, list())
+
+ for(var/client/client as anything in GLOB.admins)
+ if(client.admin_holder?.fakekey && !CLIENT_IS_STAFF(client))
+ continue
+
+ for(var/category in mappings)
+ if(CLIENT_HAS_RIGHTS(client, mappings[category]))
+ LAZYADD(listings[category], client)
+ break
+
+ for(var/category in listings)
+ var/list/admins = list()
+ for(var/client/entry as anything in listings[category])
+ var/list/admin = list()
+ var/rank = entry.admin_holder.rank
+ if(entry.admin_holder.extra_titles?.len)
+ for(var/srank in entry.admin_holder.extra_titles)
+ rank += " & [srank]"
+
+ admin["content"] = "[entry.key] ([rank])"
+ admin["text"] = ""
+
+ if(entry.admin_holder?.fakekey)
+ admin["text"] += " (HIDDEN)"
+
+ if(istype(entry.mob, /mob/dead/observer))
+ admin["color"] = "#808080"
+ admin["text"] += " Spectating"
+
+ else if(istype(entry.mob, /mob/new_player))
+ admin["color"] = "#688944"
+ admin["text"] += " in Lobby"
+ else
+ admin["color"] = "#688944"
+ admin["text"] += " Playing"
+
+ if(entry.is_afk())
+ admin["color"] = "#A040D0"
+ admin["text"] += " (AFK)"
+
+ admins += list(admin)
+
+ new_list_data["administrators"] += list(list(
+ "category" = category,
+ "category_color" = category_colors[category],
+ "category_administrators" = length(listings[category]),
+ "admins" = admins,
+ ))
+
+ list_data = new_list_data
+
+/mob/verb/who()
+ set category = "OOC"
+ set name = "Who"
+
+ SSwho.who.tgui_interact(src)
+
+/mob/verb/staffwho()
+ set category = "Admin"
+ set name = "StaffWho"
+
+ SSwho.staff_who.tgui_interact(src)
diff --git a/code/datums/action.dm b/code/datums/action.dm
index d1768655a2da..e6c87eca6a0d 100644
--- a/code/datums/action.dm
+++ b/code/datums/action.dm
@@ -43,7 +43,9 @@
return
/datum/action/proc/action_activate()
- return
+ SHOULD_CALL_PARENT(TRUE)
+
+ SEND_SIGNAL(src, COMSIG_ACTION_ACTIVATED)
/// handler for when a keybind signal is received by the action, calls the action_activate proc asynchronous
/datum/action/proc/keybind_activation()
@@ -158,6 +160,10 @@
hidden = FALSE
L.update_action_buttons()
+/proc/get_action(mob/action_mob, action_path)
+ for(var/datum/action/action in action_mob.actions)
+ if(istype(action, action_path))
+ return action
/datum/action/item_action
name = "Use item"
@@ -181,11 +187,6 @@
holder_item = null
return ..()
-/datum/action/item_action/action_activate()
- if(target)
- var/obj/item/I = target
- I.ui_action_click(owner, holder_item)
-
/datum/action/item_action/can_use_action()
if(ishuman(owner) && !owner.is_mob_incapacitated())
var/mob/living/carbon/human/human = owner
@@ -206,6 +207,17 @@
name = "Toggle [target]"
button.name = name
+/datum/action/item_action/toggle/action_activate()
+ . = ..()
+ if(target)
+ var/obj/item/I = target
+ I.ui_action_click(owner, holder_item)
+
+/datum/action/item_action/toggle/use/New(target)
+ . = ..()
+ name = "Use [target]"
+ button.name = name
+
//This is the proc used to update all the action buttons.
/mob/proc/update_action_buttons(reload_screen)
if(!client)
diff --git a/code/datums/ammo/bullet/lever_action.dm b/code/datums/ammo/bullet/lever_action.dm
index 2770231b6811..e1475146b21f 100644
--- a/code/datums/ammo/bullet/lever_action.dm
+++ b/code/datums/ammo/bullet/lever_action.dm
@@ -52,7 +52,7 @@
/datum/ammo/bullet/lever_action/xm88
name = ".458 SOCOM round"
- damage = 80
+ damage = 104
penetration = ARMOR_PENETRATION_TIER_2
accuracy = HIT_ACCURACY_TIER_1
shell_speed = AMMO_SPEED_TIER_6
diff --git a/code/datums/ammo/misc.dm b/code/datums/ammo/misc.dm
index 9a9ed2fb505b..28610f283df2 100644
--- a/code/datums/ammo/misc.dm
+++ b/code/datums/ammo/misc.dm
@@ -49,10 +49,21 @@
drop_flame(get_turf(P), P.weapon_cause_data)
/datum/ammo/flamethrower/tank_flamer
- flamer_reagent_id = "napalmx"
-
+ flamer_reagent_id = "highdamagenapalm"
max_range = 8
+/datum/ammo/flamethrower/tank_flamer/drop_flame(turf/turf, datum/cause_data/cause_data)
+ if(!istype(turf))
+ return
+
+ var/datum/reagent/napalm/high_damage/reagent = new()
+ new /obj/flamer_fire(turf, cause_data, reagent, 1)
+
+ var/datum/effect_system/smoke_spread/landingsmoke = new /datum/effect_system/smoke_spread
+ landingsmoke.set_up(1, 0, turf, null, 4, cause_data)
+ landingsmoke.start()
+ landingsmoke = null
+
/datum/ammo/flamethrower/sentry_flamer
flags_ammo_behavior = AMMO_IGNORE_ARMOR|AMMO_IGNORE_COVER|AMMO_FLAME
flamer_reagent_id = "napalmx"
diff --git a/code/datums/autocells/explosion.dm b/code/datums/autocells/explosion.dm
index ecc6f9925800..ec310e80367c 100644
--- a/code/datums/autocells/explosion.dm
+++ b/code/datums/autocells/explosion.dm
@@ -264,7 +264,9 @@ as having entered the turf.
falloff = max(falloff, power/100)
- msg_admin_attack("Explosion with Power: [power], Falloff: [falloff], Shape: [falloff_shape] in [epicenter.loc.name] ([epicenter.x],[epicenter.y],[epicenter.z]).", epicenter.x, epicenter.y, epicenter.z)
+ var/obj/causing_obj = explosion_cause_data?.resolve_cause()
+ var/mob/causing_mob = explosion_cause_data?.resolve_mob()
+ msg_admin_attack("Explosion with Power: [power], Falloff: [falloff], Shape: [falloff_shape],[causing_obj ? " from [causing_obj]" : ""][causing_mob ? " by [key_name(causing_mob)]" : ""] in [epicenter.loc.name] ([epicenter.x],[epicenter.y],[epicenter.z]).", epicenter.x, epicenter.y, epicenter.z)
playsound(epicenter, 'sound/effects/explosionfar.ogg', 100, 1, round(power^2,1))
diff --git a/code/datums/components/bad_leg.dm b/code/datums/components/bad_leg.dm
index 4a8678c4da76..8793271803dc 100644
--- a/code/datums/components/bad_leg.dm
+++ b/code/datums/components/bad_leg.dm
@@ -149,6 +149,7 @@
CRASH("No bound wound to link action")
/datum/action/human_action/rest_legs/action_activate()
+ . = ..()
var/mob/living/carbon/human/homan = owner
if(in_use)
to_chat(homan, SPAN_WARNING("You're already doing that!"))
diff --git a/code/datums/components/bonus_damage_stack.dm b/code/datums/components/bonus_damage_stack.dm
index 78da5e036ce4..7a9bf5aa9560 100644
--- a/code/datums/components/bonus_damage_stack.dm
+++ b/code/datums/components/bonus_damage_stack.dm
@@ -60,7 +60,7 @@
var/color = COLOR_BONUS_DAMAGE
var/intensity = bonus_damage_stacks / (initial(bonus_damage_cap) * 2)
// if intensity is too high of a value, the hex code will become invalid
- color += num2text(BONUS_DAMAGE_MAX_ALPHA * clamp(intensity, 0, 0.5), 1, 16)
+ color += num2text(BONUS_DAMAGE_MAX_ALPHA * clamp(intensity, 0, 0.5), 2, 16)
if(parent)
var/atom/A = parent
A.add_filter("bonus_damage_stacks", 2, list("type" = "outline", "color" = color, "size" = 1 + clamp(intensity, 0, 1)))
diff --git a/code/datums/quadtree.dm b/code/datums/quadtree.dm
index 5e5b27d57330..9056dfd6bd59 100644
--- a/code/datums/quadtree.dm
+++ b/code/datums/quadtree.dm
@@ -49,43 +49,124 @@
..()
return QDEL_HINT_IWILLGC
-/datum/shape //Leaving rectangles as a subtype if anyone decides to add circles later
+/// A simple geometric shape for testing collisions and intersections. This one is a single point.
+/datum/shape
+ /// Horizontal position of the shape's center point.
var/center_x = 0
+ /// Vertical position of the shape's center point.
var/center_y = 0
+ /// Distance from the shape's leftmost to rightmost extent.
+ var/bounds_x = 0
+ /// Distance from the shape's topmost to bottommost extent.
+ var/bounds_y = 0
-/datum/shape/proc/intersects()
- return
-/datum/shape/proc/contains()
- return
+/datum/shape/New(center_x, center_y)
+ set_shape(center_x, center_y)
+/// Assign shape variables.
+/datum/shape/proc/set_shape(center_x, center_y)
+ src.center_x = center_x
+ src.center_y = center_y
+
+/// Returns TRUE if the coordinates x, y are in or on the shape, otherwise FALSE.
+/datum/shape/proc/contains_xy(x, y)
+ return center_x == x && center_y == y
+
+/// Returns TRUE if the coord datum is in or on the shape, otherwise FALSE.
+/datum/shape/proc/contains_coords(datum/coords/coords)
+ return contains_xy(coords.x_pos, coords.y_pos)
+
+/// Returns TRUE if the atom is in or on the shape, otherwise FALSE.
+/datum/shape/proc/contains_atom(atom/atom)
+ return contains_xy(atom.x, atom.y)
+
+/// Returns TRUE if this shape's bounding box intersects the provided shape's bounding box, otherwise FALSE. Generally faster than a full intersection test.
+/datum/shape/proc/intersects_aabb(datum/shape/aabb)
+ return (abs(src.center_x - aabb.center_x) <= (src.bounds_x + aabb.bounds_x) * 0.5) && (abs(src.center_y - aabb.center_y) <= (src.bounds_y + aabb.bounds_y) * 0.5)
+
+/// Returns TRUE if this shape intersects the provided rectangle shape, otherwise FALSE.
+/datum/shape/proc/intersects_rect(datum/shape/rectangle/rect)
+ return rect.contains_xy(src.center_x, src.center_y)
+
+/// A simple geometric shape for testing collisions and intersections. This one is an axis-aligned rectangle.
/datum/shape/rectangle
+ /// Distance from the shape's leftmost to rightmost extent.
+ var/width = 0
+ /// Distance from the shape's topmost to bottommost extent.
+ var/height = 0
+
+/datum/shape/rectangle/New(center_x, center_y, width, height)
+ set_shape(center_x, center_y, width, height)
+
+/datum/shape/rectangle/set_shape(center_x, center_y, width, height)
+ ..()
+ src.bounds_x = width
+ src.bounds_y = height
+ src.width = width
+ src.height = height
+
+/datum/shape/rectangle/contains_xy(x, y)
+ return (abs(center_x - x) <= width * 0.5) && (abs(center_y - y) <= height * 0.5)
+
+/datum/shape/rectangle/intersects_rect(datum/shape/rectangle/rect)
+ return intersects_aabb(rect)
+
+/// A simple geometric shape for testing collisions and intersections. This one is an axis-aligned square.
+/datum/shape/rectangle/square
+ /// Distance between the shape's opposing extents.
+ var/length = 0
+
+/datum/shape/rectangle/square/New(center_x, center_y, length)
+ set_shape(center_x, center_y, length)
+
+/datum/shape/rectangle/square/set_shape(center_x, center_y, length)
+ ..(center_x, center_y, length, length)
+ src.length = length
+
+/// A simple geometric shape for testing collisions and intersections. This one is an axis-aligned ellipse.
+/datum/shape/ellipse
+ /// Distance from the shape's leftmost to rightmost extent.
var/width = 0
+ /// Distance from the shape's topmost to bottommost extent.
var/height = 0
+ VAR_PROTECTED/_axis_x_sq = 0
+ VAR_PROTECTED/_axis_y_sq = 0
+
+/datum/shape/ellipse/New(center_x, center_y, width, height)
+ set_shape(center_x, center_y, width, height)
-/datum/shape/rectangle/New(x, y, w, h)
+/datum/shape/ellipse/set_shape(center_x, center_y, width, height)
..()
- center_x = x
- center_y = y
- width = w
- height = h
-
-/datum/shape/rectangle/intersects(datum/shape/rectangle/range)
- return !(range.center_x + range.width/2 < center_x - width / 2|| \
- range.center_x - range.width/2 > center_x + width / 2|| \
- range.center_y + range.height/2 < center_y - height / 2|| \
- range.center_y - range.height/2 > center_y + height / 2)
-
-/datum/shape/rectangle/contains(datum/coords/coords)
- return (coords.x_pos >= center_x - width / 2 \
- && coords.x_pos <= center_x + width / 2 \
- && coords.y_pos >= center_y - height /2 \
- && coords.y_pos <= center_y + height / 2)
-
-/datum/shape/rectangle/proc/contains_atom(atom/A)
- return (A.x >= center_x - width / 2 \
- && A.x <= center_x + width / 2 \
- && A.y >= center_y - height /2 \
- && A.y <= center_y + height / 2)
+ src.bounds_x = width
+ src.bounds_y = height
+ src.width = width
+ src.height = height
+ src._axis_x_sq = (width * 0.5)**2
+ src._axis_y_sq = (height * 0.5)**2
+
+/datum/shape/ellipse/contains_xy(x, y)
+ return ((center_x - x)**2 / _axis_x_sq + (center_y - y)**2 / _axis_y_sq <= 1)
+
+/datum/shape/ellipse/intersects_rect(datum/shape/rectangle/rect)
+ if(..())
+ return TRUE
+
+ var/nearest_x = clamp(src.center_x, rect.center_x - rect.width * 0.5, rect.center_x + rect.width * 0.5)
+ var/nearest_y = clamp(src.center_y, rect.center_y - rect.height * 0.5, rect.center_y + rect.height * 0.5)
+
+ return src.contains_xy(nearest_x, nearest_y)
+
+/// A simple geometric shape for testing collisions and intersections. This one is a circle.
+/datum/shape/ellipse/circle
+ /// Distance from the shape's center to edge.
+ var/radius = 0
+
+/datum/shape/ellipse/circle/New(center_x, center_y, radius)
+ set_shape(center_x, center_y, radius)
+
+/datum/shape/ellipse/circle/set_shape(center_x, center_y, radius)
+ ..(center_x, center_y, radius * 2, radius * 2)
+ src.radius = radius
/datum/quadtree/proc/subdivide()
//Warning: this might give you eye cancer
@@ -96,7 +177,7 @@
is_divided = TRUE
/datum/quadtree/proc/insert_player(datum/coords/qtplayer/p_coords)
- if(!boundary.contains(p_coords))
+ if(!boundary.contains_coords(p_coords))
return FALSE
if(!player_coords)
@@ -118,11 +199,11 @@
player_coords.Add(p_coords)
return TRUE
-/datum/quadtree/proc/query_range(datum/shape/rectangle/range, list/found_players, flags = 0)
+/datum/quadtree/proc/query_range(datum/shape/range, list/found_players, flags = 0)
if(!found_players)
found_players = list()
. = found_players
- if(!range?.intersects(boundary))
+ if(!range?.intersects_rect(boundary))
return
if(is_divided)
nw_branch.query_range(range, found_players, flags)
@@ -136,7 +217,7 @@
continue
if((flags & QTREE_EXCLUDE_OBSERVER) && P.is_observer)
continue
- if(range.contains(P))
+ if(range.contains_coords(P))
if(flags & QTREE_SCAN_MOBS)
found_players.Add(P.player.mob)
else
diff --git a/code/datums/statistics/entities/round_stats.dm b/code/datums/statistics/entities/round_stats.dm
index 10ec04c6da0e..79493ca87ef0 100644
--- a/code/datums/statistics/entities/round_stats.dm
+++ b/code/datums/statistics/entities/round_stats.dm
@@ -393,6 +393,7 @@
return TRUE
/datum/action/show_round_statistics/action_activate()
+ . = ..()
if(!can_use_action())
return
diff --git a/code/datums/supply_packs/explosives.dm b/code/datums/supply_packs/explosives.dm
index 032ef047c78a..78f0f3e9251a 100644
--- a/code/datums/supply_packs/explosives.dm
+++ b/code/datums/supply_packs/explosives.dm
@@ -89,6 +89,18 @@
containername = "\improper explosive M40 HEDP grenades crate (WARNING)"
group = "Explosives"
+/datum/supply_packs/explosives_sebb
+ name = "G2 electroshock grenades crate (x6)"
+ contains = list(
+ /obj/item/storage/box/packet/sebb,
+ /obj/item/storage/box/packet/sebb,
+ )
+ cost = 30
+ containertype = /obj/structure/closet/crate/explosives
+ containername = "\improper G2 electroshock grenades crate (WARNING)"
+ group = "Explosives"
+
+
/datum/supply_packs/explosives_hedp
name = "M40 HEDP blast grenade box crate (x25)"
contains = list(
diff --git a/code/datums/tutorial/_tutorial.dm b/code/datums/tutorial/_tutorial.dm
index ddeddddd0407..b7403da3c0a9 100644
--- a/code/datums/tutorial/_tutorial.dm
+++ b/code/datums/tutorial/_tutorial.dm
@@ -4,7 +4,7 @@ GLOBAL_LIST_EMPTY_TYPED(ongoing_tutorials, /datum/tutorial)
/datum/tutorial
/// What the tutorial is called, is player facing
var/name = "Base"
- /// Internal ID of the tutorial, kept for save files
+ /// Internal ID of the tutorial, kept for save files. Format is "tutorialtype_specifictutorial_number". So, the first basic xeno tutorial would be "xeno_basic_1", and the 2nd marine medical tutorial would be "marine_medical_2"
var/tutorial_id = "base"
/// A short 1-2 sentence description of the tutorial itself
var/desc = ""
@@ -144,6 +144,8 @@ GLOBAL_LIST_EMPTY_TYPED(ongoing_tutorials, /datum/tutorial)
/// Ends the tutorial after a certain amount of time.
/datum/tutorial/proc/tutorial_end_in(time = 5 SECONDS, completed = TRUE)
+ if(completed)
+ mark_completed() // This is done because if you're calling this proc with completed == TRUE, then the tutorial's a done deal. We shouldn't penalize the player if they exit a few seconds before it actually completes.
tutorial_ending = TRUE
addtimer(CALLBACK(src, PROC_REF(end_tutorial), completed), time)
@@ -221,6 +223,7 @@ GLOBAL_LIST_EMPTY_TYPED(ongoing_tutorials, /datum/tutorial)
tutorial = WEAKREF(selected_tutorial)
/datum/action/tutorial_end/action_activate()
+ . = ..()
if(!tutorial)
return
diff --git a/code/datums/tutorial/xenomorph/abomination.dm b/code/datums/tutorial/xenomorph/abomination.dm
new file mode 100644
index 000000000000..83ac86b8f09e
--- /dev/null
+++ b/code/datums/tutorial/xenomorph/abomination.dm
@@ -0,0 +1,256 @@
+/datum/tutorial/xenomorph/abomination
+ name = "Xenomorph - Predalien"
+ desc = "A tutorial to teach you how to play the \"Predalien\", also known as Abomination, xenomorph caste. Completing this is required to be able to play an Abomination."
+ icon_state = "predalien"
+ tutorial_id = "xeno_abom_1"
+ tutorial_template = /datum/map_template/tutorial/s7x7
+ starting_xenomorph_type = /mob/living/carbon/xenomorph/predalien/tutorial
+ /// How many marines in the kill_marines stage have been killed
+ var/ending_marines_killed = 0
+
+// START OF SCRITPING
+
+/datum/tutorial/xenomorph/abomination/start_tutorial(mob/starting_mob)
+ . = ..()
+ if(!.)
+ return
+
+ init_mob()
+ xeno.lock_evolve = TRUE
+
+ message_to_player("Welcome to the tutorial for the Abomination xenomorph. As an Abomination, you are a frontline powerhouse whose damage scales with your kill count.")
+ message_to_player("Your kill count scales when you kill humans with your slash attack, up to 10 kills. Ability kills do not count towards this.")
+
+ addtimer(CALLBACK(src, PROC_REF(how_to_be_abom)), 12 SECONDS)
+
+/datum/tutorial/xenomorph/abomination/proc/how_to_be_abom()
+ message_to_player("Be aware that you are kill-on-sight to all Predators forever, and will very likely need to defend yourself against multiple.")
+ message_to_player("Be sure to stick close to other xenomorphs or over-extend. While you may be stronger than many, you don't have enough health or armor to go out on your own.")
+ addtimer(CALLBACK(src, PROC_REF(feral_rush_tutorial)), 10.5 SECONDS)
+
+/datum/tutorial/xenomorph/abomination/proc/feral_rush_tutorial()
+ var/datum/action/rush = give_action(xeno, /datum/action/xeno_action/onclick/feralrush)
+ message_to_player("Your first unique ability is Feral Rush, an ability that temporarily increases your speed and your armor. Use Feral Rush to continue.")
+ update_objective("Use your Feral Rush ability.")
+ add_highlight(rush.button)
+ RegisterSignal(rush, COMSIG_XENO_ACTION_USED, PROC_REF(on_rush_used))
+
+/datum/tutorial/xenomorph/abomination/proc/on_rush_used(datum/action/source, mob/owner)
+ SIGNAL_HANDLER
+
+ UnregisterSignal(source, COMSIG_XENO_ACTION_USED)
+ remove_highlight(source.button)
+ addtimer(CALLBACK(src, PROC_REF(predalien_roar_tutorial_1)), 5 SECONDS)
+
+/datum/tutorial/xenomorph/abomination/proc/predalien_roar_tutorial_1()
+ hide_action(xeno, /datum/action/xeno_action/onclick/feralrush)
+ xeno.cannot_slash = TRUE
+ message_to_player("Your next ability is Roar, a versatile ability that disables any motion detectors or cloaks in a medium radius around you.")
+ message_to_player("Additionally, it gives a slash and speed bonus to any friendly xenomorphs in range.")
+ addtimer(CALLBACK(src, PROC_REF(predalien_roar_tutorial_2)), 8 SECONDS)
+
+/datum/tutorial/xenomorph/abomination/proc/predalien_roar_tutorial_2()
+ var/datum/action/roar = give_action(xeno, /datum/action/xeno_action/onclick/predalien_roar)
+ message_to_player("One of Roar's most useful abilities is uncloaking nearby Predators. Use Roar to uncloak the newly spawned Predator.")
+ update_objective("Use your Roar ability to uncloak the nearby predator.")
+ add_highlight(roar.button)
+ var/mob/living/carbon/human/pred = new(loc_from_corner(3, 3))
+ add_to_tracking_atoms(pred)
+ pred.create_hud()
+ arm_equipment(pred, /datum/equipment_preset/yautja/blooded)
+ var/obj/item/clothing/gloves/yautja/hunter/bracers = locate() in pred
+ if(!bracers)
+ message_to_player("Something has gone wrong. Please make a bug report.")
+ CRASH("predator spawned without bracers in tutorial")
+
+ bracers.cloaker_internal(pred, TRUE, TRUE, TRUE)
+ RegisterSignal(bracers, COMSIG_PRED_BRACER_DECLOAKED, PROC_REF(smash_tutorial_1))
+
+/datum/tutorial/xenomorph/abomination/proc/smash_tutorial_1(datum/source)
+ SIGNAL_HANDLER
+
+ var/datum/action/roar = get_action(xeno, /datum/action/xeno_action/onclick/predalien_roar)
+ remove_highlight(roar.button)
+ update_objective("")
+
+ UnregisterSignal(source, COMSIG_PRED_BRACER_DECLOAKED)
+ addtimer(CALLBACK(src, PROC_REF(smash_tutorial_2)), 2.5 SECONDS)
+
+/datum/tutorial/xenomorph/abomination/proc/smash_tutorial_2()
+ hide_action(xeno, /datum/action/xeno_action/onclick/predalien_roar)
+ message_to_player("Good. Roar will be one of your primary tools for defending against Predators. Your next ability is Feral Smash.")
+ xeno.cannot_slash = FALSE
+
+ TUTORIAL_ATOM_FROM_TRACKING(/mob/living/carbon/human, pred)
+ remove_from_tracking_atoms(pred)
+ qdel(pred)
+
+ addtimer(CALLBACK(src, PROC_REF(smash_tutorial_3)), 5 SECONDS)
+
+/datum/tutorial/xenomorph/abomination/proc/smash_tutorial_3()
+ var/datum/action/smash = give_action(xeno, /datum/action/xeno_action/activable/feral_smash)
+ RegisterSignal(smash, COMSIG_XENO_PRE_ACTION_USED, PROC_REF(frenzy_tutorial_1))
+ add_highlight(smash.button)
+
+ message_to_player("Feral Smash is a strong lunge with a range of five tiles. It deals decent damage that scales with your kill count.")
+ message_to_player("Use Feral Smash on the marine to continue.")
+ update_objective("Use your Feral Smash ability on the marine.")
+
+ xeno.forceMove(loc_from_corner(0, 2))
+ xeno.anchored = TRUE
+ ADD_TRAIT(xeno, TRAIT_IMMOBILIZED, TRAIT_SOURCE_TUTORIAL)
+
+ var/mob/living/carbon/human/marine = new(loc_from_corner(4, 2))
+ add_to_tracking_atoms(marine)
+ arm_equipment(marine, /datum/equipment_preset/uscm/private_equipped)
+
+/datum/tutorial/xenomorph/abomination/proc/frenzy_tutorial_1(datum/action/source, mob/owner)
+ SIGNAL_HANDLER
+
+ xeno.anchored = FALSE
+ REMOVE_TRAIT(xeno, TRAIT_IMMOBILIZED, TRAIT_SOURCE_TUTORIAL)
+ RegisterSignal(source, COMSIG_XENO_ACTION_USED, PROC_REF(frenzy_tutorial_2))
+ RegisterSignal(source, COMSIG_XENO_FAILED_ACTION_USED, PROC_REF(frenzy_tutorial_1_fail))
+
+/datum/tutorial/xenomorph/abomination/proc/frenzy_tutorial_1_fail(datum/action/source, mob/owner)
+ SIGNAL_HANDLER
+
+ xeno.anchored = TRUE
+ ADD_TRAIT(xeno, TRAIT_IMMOBILIZED, TRAIT_SOURCE_TUTORIAL)
+ UnregisterSignal(source, list(COMSIG_XENO_FAILED_ACTION_USED, COMSIG_XENO_ACTION_USED))
+
+/datum/tutorial/xenomorph/abomination/proc/frenzy_tutorial_2(datum/action/source, mob/owner)
+ SIGNAL_HANDLER
+
+ if(get_turf(xeno) == loc_from_corner(0, 2)) // xeno didn't lunge at the mob
+ xeno.anchored = TRUE
+ UnregisterSignal(source, COMSIG_XENO_ACTION_USED)
+ ADD_TRAIT(xeno, TRAIT_IMMOBILIZED, TRAIT_SOURCE_TUTORIAL)
+ return
+
+ update_objective("")
+ var/datum/action/smash = get_action(xeno, /datum/action/xeno_action/activable/feral_smash)
+ remove_highlight(smash.button)
+ UnregisterSignal(source, list(COMSIG_XENO_ACTION_USED, COMSIG_XENO_PRE_ACTION_USED))
+ addtimer(CALLBACK(src, PROC_REF(frenzy_tutorial_3)), 2 SECONDS)
+
+/datum/tutorial/xenomorph/abomination/proc/frenzy_tutorial_3()
+ remove_action(xeno, /datum/action/xeno_action/activable/feral_smash)
+ message_to_player("Good. Your final ability is Feral Frenzy, a strong ability that can alternate between hitting a single target or all within a large radius. However, it locks you in place while it winds up.")
+
+ TUTORIAL_ATOM_FROM_TRACKING(/mob/living/carbon/human, marine)
+ remove_from_tracking_atoms(marine)
+ qdel(marine)
+
+ addtimer(CALLBACK(src, PROC_REF(frenzy_tutorial_4)), 6 SECONDS)
+
+/datum/tutorial/xenomorph/abomination/proc/frenzy_tutorial_4()
+ var/mob/living/carbon/human/marine = new(loc_from_corner(4, 2))
+ add_to_tracking_atoms(marine)
+ RegisterSignal(marine, COMSIG_MOB_DEATH, PROC_REF(on_marine_early_death))
+ arm_equipment(marine, /datum/equipment_preset/uscm/private_equipped)
+
+ var/datum/action/frenzy = give_action(xeno, /datum/action/xeno_action/activable/feralfrenzy)
+ add_highlight(frenzy.button)
+ message_to_player("By default, Feral Frenzy is on single-target mode. Use Feral Frenzy on the newly spawned marine.")
+ update_objective("Use Feral Frenzy on the marine.")
+
+ RegisterSignal(frenzy, COMSIG_XENO_ACTION_USED, PROC_REF(frenzy_tutorial_5))
+
+/datum/tutorial/xenomorph/abomination/proc/frenzy_tutorial_5(datum/action/xeno_action/source, mob/owner)
+ SIGNAL_HANDLER
+
+ TUTORIAL_ATOM_FROM_TRACKING(/mob/living/carbon/human, marine)
+ if(get_dist(marine, xeno) > 1)
+ return
+
+ UnregisterSignal(source, COMSIG_XENO_ACTION_USED)
+ var/datum/action/frenzy = get_action(xeno, /datum/action/xeno_action/activable/feralfrenzy)
+ remove_highlight(frenzy.button)
+ var/datum/action/frenzy_toggle = give_action(xeno, /datum/action/xeno_action/onclick/toggle_gut_targeting)
+ add_highlight(frenzy_toggle.button)
+ message_to_player("Good, now toggle Feral Frenzy's AOE mode with the newly available Toggle Gutting Type ability.")
+ update_objective("Use the Toggle Gutting Type ability to change your frenzy mode.")
+
+ RegisterSignal(frenzy_toggle, COMSIG_XENO_ACTION_USED, PROC_REF(frenzy_tutorial_6))
+
+/datum/tutorial/xenomorph/abomination/proc/frenzy_tutorial_6(datum/action/xeno_action/source, mob/owner)
+ SIGNAL_HANDLER
+
+ UnregisterSignal(source, COMSIG_XENO_ACTION_USED)
+ remove_highlight(source.button)
+ source.plasma_cost = INFINITY // slightly scuffed way of disabling the switch button
+ source.update_button_icon()
+
+ message_to_player("Feral Frenzy has now been changed into AOE mode. Use Feral Frenzy again anywhere within 2 tiles of the marine.")
+ update_objective("Use Feral Frenzy within 2 tiles of the marine.")
+ TUTORIAL_ATOM_FROM_TRACKING(/mob/living/carbon/human, marine)
+ marine.rejuvenate()
+ var/datum/action/xeno_action/activable/feralfrenzy/frenzy = get_action(xeno, /datum/action/xeno_action/activable/feralfrenzy)
+ frenzy.targeting = AOETARGETGUT
+ frenzy.reduce_cooldown(frenzy.xeno_cooldown)
+ add_highlight(frenzy.button)
+
+ RegisterSignal(frenzy, COMSIG_XENO_ACTION_USED, PROC_REF(frenzy_tutorial_7))
+
+/datum/tutorial/xenomorph/abomination/proc/frenzy_tutorial_7(datum/action/source)
+ SIGNAL_HANDLER
+
+ TUTORIAL_ATOM_FROM_TRACKING(/mob/living/carbon/human, marine)
+ var/datum/action/xeno_action/activable/feralfrenzy/frenzy = get_action(xeno, /datum/action/xeno_action/activable/feralfrenzy)
+ if(get_dist(xeno, marine) > frenzy.range)
+ // Not close enough to actually hit the marine
+ return
+
+ UnregisterSignal(frenzy, COMSIG_XENO_ACTION_USED)
+ UnregisterSignal(marine, COMSIG_MOB_DEATH)
+ remove_highlight(frenzy.button)
+ message_to_player("Good. As you may have noticed, the AOE version of Feral Frenzy takes longer to wind up, in addition to doing less overall damage.")
+ addtimer(CALLBACK(src, PROC_REF(kill_marines)), 6 SECONDS)
+
+/datum/tutorial/xenomorph/abomination/proc/kill_marines()
+ message_to_player("To finish the tutorial, kill the three newly-spawned marines using any of your attacks or abilities.")
+
+ // Spawn/rejuv the dummies
+ TUTORIAL_ATOM_FROM_TRACKING(/mob/living/carbon/human, marine) // we can reuse this one though
+ marine.rejuvenate()
+ marine.forceMove(loc_from_corner(4, 2))
+ RegisterSignal(marine, COMSIG_MOB_DEATH, PROC_REF(kill_marines_2))
+
+ var/mob/living/carbon/human/marine_2 = new(loc_from_corner(2, 2))
+ arm_equipment(marine_2, /datum/equipment_preset/uscm/private_equipped)
+ RegisterSignal(marine_2, COMSIG_MOB_DEATH, PROC_REF(kill_marines_2))
+
+ var/mob/living/carbon/human/marine_3 = new(loc_from_corner(0, 2))
+ arm_equipment(marine_3, /datum/equipment_preset/uscm/private_equipped)
+ RegisterSignal(marine_3, COMSIG_MOB_DEATH, PROC_REF(kill_marines_2))
+
+ // Arrange the actions about how they'd be in an actual game
+ remove_action(xeno, /datum/action/xeno_action/activable/feralfrenzy)
+ remove_action(xeno, /datum/action/xeno_action/onclick/toggle_gut_targeting)
+
+ give_action(xeno, /datum/action/xeno_action/activable/tail_stab)
+ give_action(xeno, /datum/action/xeno_action/onclick/feralrush)
+ give_action(xeno, /datum/action/xeno_action/onclick/predalien_roar)
+ give_action(xeno, /datum/action/xeno_action/activable/feral_smash)
+ give_action(xeno, /datum/action/xeno_action/activable/feralfrenzy)
+ give_action(xeno, /datum/action/xeno_action/onclick/toggle_gut_targeting)
+
+/datum/tutorial/xenomorph/abomination/proc/kill_marines_2(datum/source)
+ SIGNAL_HANDLER
+
+ if(ending_marines_killed < 2)
+ ending_marines_killed++
+ return
+
+ message_to_player("Good work. The tutorial will end shortly.")
+ tutorial_end_in(7 SECONDS, TRUE)
+
+// END OF SCRIPTING
+
+/// In case a marine dies early to prevent softlocks
+/datum/tutorial/xenomorph/abomination/proc/on_marine_early_death(datum/source)
+ SIGNAL_HANDLER
+
+ TUTORIAL_ATOM_FROM_TRACKING(/mob/living/carbon/human, marine)
+ marine.rejuvenate()
diff --git a/code/datums/xeno_shields/shield_types/vanguard_shield.dm b/code/datums/xeno_shields/shield_types/vanguard_shield.dm
index 21d9fb12cfd7..cd9e4534e778 100644
--- a/code/datums/xeno_shields/shield_types/vanguard_shield.dm
+++ b/code/datums/xeno_shields/shield_types/vanguard_shield.dm
@@ -42,7 +42,7 @@
return
linked_xeno.overlay_shields()
- var/datum/action/xeno_action/activable/cleave/cAction = get_xeno_action_by_type(linked_xeno, /datum/action/xeno_action/activable/cleave)
+ var/datum/action/xeno_action/activable/cleave/cAction = get_action(linked_xeno, /datum/action/xeno_action/activable/cleave)
if (istype(cAction))
addtimer(CALLBACK(cAction, TYPE_PROC_REF(/datum/action/xeno_action/activable/cleave, remove_buff)), 7, TIMER_UNIQUE)
diff --git a/code/game/camera_manager/camera_manager.dm b/code/game/camera_manager/camera_manager.dm
index 9f111b0f8ec6..90e80ec7037e 100644
--- a/code/game/camera_manager/camera_manager.dm
+++ b/code/game/camera_manager/camera_manager.dm
@@ -6,7 +6,7 @@
/datum/component/camera_manager
var/map_name
var/obj/structure/machinery/camera/current
- var/datum/shape/rectangle/current_area
+ var/datum/shape/current_area
var/atom/movable/screen/map_view/cam_screen
var/atom/movable/screen/background/cam_background
var/list/range_turfs = list()
@@ -86,7 +86,7 @@
RegisterSignal(parent, COMSIG_CAMERA_UNREGISTER_UI, PROC_REF(unregister))
RegisterSignal(parent, COMSIG_CAMERA_SET_NVG, PROC_REF(enable_nvg))
RegisterSignal(parent, COMSIG_CAMERA_CLEAR_NVG, PROC_REF(disable_nvg))
- RegisterSignal(parent, COMSIG_CAMERA_SET_AREA, PROC_REF(set_camera_rect))
+ RegisterSignal(parent, COMSIG_CAMERA_SET_AREA, PROC_REF(set_camera_area))
RegisterSignal(parent, COMSIG_CAMERA_SET_TARGET, PROC_REF(set_camera))
RegisterSignal(parent, COMSIG_CAMERA_CLEAR, PROC_REF(clear_camera))
RegisterSignal(parent, COMSIG_CAMERA_REFRESH, PROC_REF(refresh_camera))
@@ -133,18 +133,18 @@
RegisterSignal(current, COMSIG_PARENT_QDELETING, PROC_REF(show_camera_static))
update_target_camera()
-/datum/component/camera_manager/proc/set_camera_rect(source, x, y, z, w, h)
+/datum/component/camera_manager/proc/set_camera_area(source, datum/shape/new_area, z)
SIGNAL_HANDLER
render_mode = RENDER_MODE_AREA
if(current)
UnregisterSignal(current, COMSIG_PARENT_QDELETING)
current = null
- current_area = RECT(x, y, w, h)
- target_x = x
- target_y = y
+ current_area = new_area
+ target_x = current_area.center_x
+ target_y = current_area.center_y
target_z = z
- target_width = w
- target_height = h
+ target_width = current_area.bounds_x
+ target_height = current_area.bounds_y
update_area_camera()
/datum/component/camera_manager/proc/enable_nvg(source, power, matrixcol)
@@ -221,8 +221,8 @@
// Cameras that get here are moving, and are likely attached to some moving atom such as cyborgs.
last_camera_turf = new_location
- var/x_size = current_area.width
- var/y_size = current_area.height
+ var/x_size = current_area.bounds_x
+ var/y_size = current_area.bounds_y
var/turf/target = locate(current_area.center_x, current_area.center_y, target_z)
var/list/visible_things = isXRay ? range("[x_size]x[y_size]", target) : view("[x_size]x[y_size]", target)
diff --git a/code/game/machinery/autolathe_datums.dm b/code/game/machinery/autolathe_datums.dm
index 78a8e46b64aa..9c8ee271845d 100644
--- a/code/game/machinery/autolathe_datums.dm
+++ b/code/game/machinery/autolathe_datums.dm
@@ -136,11 +136,6 @@
path = /obj/item/circuitboard/apc
category = AUTOLATHE_CATEGORY_ENGINEERING
-/datum/autolathe/recipe/rcd_ammo
- name = "matter cartridge"
- path = /obj/item/ammo_rcd
- category = AUTOLATHE_CATEGORY_ENGINEERING
-
/datum/autolathe/recipe/table_parts
name = "table parts"
path = /obj/item/frame/table
diff --git a/code/game/machinery/computer/arcade.dm b/code/game/machinery/computer/arcade.dm
index ff8f3959d64e..4f6f4df4ef08 100644
--- a/code/game/machinery/computer/arcade.dm
+++ b/code/game/machinery/computer/arcade.dm
@@ -123,7 +123,7 @@
src.temp = "[src.enemy_name] has fallen! Rejoice!"
if(!length(contents))
- var/prizeselect = pickweight(prizes)
+ var/prizeselect = pick_weight(prizes)
new prizeselect(src.loc)
if(istype(prizeselect, /obj/item/toy/gun)) //Ammo comes with the gun
@@ -176,5 +176,5 @@
if(2)
num_of_prizes = rand(0,2)
for(num_of_prizes; num_of_prizes > 0; num_of_prizes--)
- empprize = pickweight(prizes)
+ empprize = pick_weight(prizes)
new empprize(src.loc)
diff --git a/code/game/machinery/computer/dropship_weapons.dm b/code/game/machinery/computer/dropship_weapons.dm
index e07b415ed233..7f8f4f2b3850 100644
--- a/code/game/machinery/computer/dropship_weapons.dm
+++ b/code/game/machinery/computer/dropship_weapons.dm
@@ -313,9 +313,8 @@
var/obj/structure/machinery/defenses/sentry/defense = sentry.deployed_turret
if(defense.has_camera)
defense.set_range()
- var/datum/shape/rectangle/current_bb = defense.range_bounds
camera_area_equipment = sentry
- SEND_SIGNAL(src, COMSIG_CAMERA_SET_AREA, current_bb.center_x, current_bb.center_y, defense.loc.z, current_bb.width, current_bb.height)
+ SEND_SIGNAL(src, COMSIG_CAMERA_SET_AREA, defense.range_bounds, defense.loc.z)
return TRUE
if("clear-camera")
diff --git a/code/game/machinery/medical_pod/autodoc.dm b/code/game/machinery/medical_pod/autodoc.dm
index ef335c6841e6..b5fd43b35651 100644
--- a/code/game/machinery/medical_pod/autodoc.dm
+++ b/code/game/machinery/medical_pod/autodoc.dm
@@ -238,11 +238,6 @@
surgery_list += create_autodoc_surgery(L,ORGAN_SURGERY,"damage",0,I)
organdamagesurgery++
- if(istype(L,/obj/limb/head))
- var/obj/limb/head/H = L
- if(H.disfigured)
- surgery_list += create_autodoc_surgery(L,LIMB_SURGERY,"facial")
-
if(L.status & LIMB_BROKEN)
surgery_list += create_autodoc_surgery(L,LIMB_SURGERY,"broken")
if(L.status & LIMB_DESTROYED)
@@ -521,20 +516,6 @@
if(!surgery) break
close_incision(H,S.limb_ref)
- if("facial")
- if(prob(30)) visible_message("[icon2html(src, viewers(src))] \The [src] speaks: Beginning Facial Reconstruction Surgery.");
- if(S.unneeded)
- sleep(UNNEEDED_DELAY)
- visible_message("[icon2html(src, viewers(src))] \The [src] speaks: Procedure has been deemed unnecessary.");
- surgery_todo_list -= S
- continue
- if(istype(S.limb_ref, /obj/limb/head))
- var/obj/limb/head/F = S.limb_ref
- sleep(SCALPEL_MAX_DURATION + HEMOSTAT_MAX_DURATION + RETRACTOR_MAX_DURATION + CAUTERY_MAX_DURATION)
- F.remove_all_bleeding(TRUE)
- F.disfigured = 0
- F.owner.name = F.owner.get_visible_name()
-
if("open")
if(prob(30)) visible_message("[icon2html(src, viewers(src))] \The [src]croaks: Closing surgical incision.");
close_encased(H,S.limb_ref)
@@ -753,9 +734,6 @@
if("shrapnel")
surgeryqueue["shrapnel"] = 1
dat += "Shrapnel Removal Surgery"
- if("facial")
- surgeryqueue["facial"] = 1
- dat += "Facial Reconstruction Surgery"
if("open")
surgeryqueue["open"] = 1
dat += "Close Open Incisions"
@@ -902,18 +880,6 @@
N.fields["autodoc_manual"] += create_autodoc_surgery(null,LIMB_SURGERY,"shrapnel",1)
updateUsrDialog()
- if(href_list["facial"])
- for(var/obj/limb/L in connected.occupant.limbs)
- if(L)
- if(istype(L,/obj/limb/head))
- var/obj/limb/head/J = L
- if(J.disfigured)
- N.fields["autodoc_manual"] += create_autodoc_surgery(L,LIMB_SURGERY,"facial")
- else
- N.fields["autodoc_manual"] += create_autodoc_surgery(L,LIMB_SURGERY,"facial",1)
- updateUsrDialog()
- break
-
if(href_list["open"])
for(var/obj/limb/L in connected.occupant.limbs)
if(L)
diff --git a/code/game/machinery/vending/vendor_types/crew/combat_correspondent.dm b/code/game/machinery/vending/vendor_types/crew/combat_correspondent.dm
index b0894ca2a5a2..81bee126dbc8 100644
--- a/code/game/machinery/vending/vendor_types/crew/combat_correspondent.dm
+++ b/code/game/machinery/vending/vendor_types/crew/combat_correspondent.dm
@@ -3,9 +3,11 @@
GLOBAL_LIST_INIT(cm_vending_clothing_combat_correspondent, list(
list("STANDARD EQUIPMENT (TAKE ALL)", 0, null, null, null),
list("Essential Reporter's Set", 0, /obj/effect/essentials_set/cc, MARINE_CAN_BUY_ESSENTIALS, VENDOR_ITEM_MANDATORY),
+ list("Leather Satchel", 0, /obj/item/storage/backpack/satchel, MARINE_CAN_BUY_BACKPACK, VENDOR_ITEM_REGULAR),
+
+ list("CIVILIAN EQUIPMENT (TAKE ALL)", 0, null, null, null),
list("Portable Press Fax Machine", 0, /obj/item/device/fax_backpack, CIVILIAN_CAN_BUY_BACKPACK, VENDOR_ITEM_RECOMMENDED),
list("Press Broadcasting Camera", 0, /obj/item/device/camera/broadcasting, CIVILIAN_CAN_BUY_UTILITY, VENDOR_ITEM_RECOMMENDED),
- list("Leather Satchel", 0, /obj/item/storage/backpack/satchel, MARINE_CAN_BUY_BACKPACK, VENDOR_ITEM_REGULAR),
list("UNIFORM (CHOOSE 1)", 0, null, null, null),
list("Black Uniform", 0, /obj/item/clothing/under/marine/reporter/black, MARINE_CAN_BUY_UNIFORM, VENDOR_ITEM_REGULAR),
diff --git a/code/game/machinery/vending/vendor_types/crew/commanding_officer.dm b/code/game/machinery/vending/vendor_types/crew/commanding_officer.dm
index d7d49a8ae044..dd2fc9c4a5b7 100644
--- a/code/game/machinery/vending/vendor_types/crew/commanding_officer.dm
+++ b/code/game/machinery/vending/vendor_types/crew/commanding_officer.dm
@@ -32,6 +32,7 @@ GLOBAL_LIST_INIT(cm_vending_gear_commanding_officer, list(
list("HEDP Grenade Pack", 15, /obj/item/storage/box/packet/high_explosive, null, VENDOR_ITEM_REGULAR),
list("HEFA Grenade Pack", 15, /obj/item/storage/box/packet/hefa, null, VENDOR_ITEM_REGULAR),
list("WP Grenade Pack", 15, /obj/item/storage/box/packet/phosphorus, null, VENDOR_ITEM_REGULAR),
+ list("G2 Electroshock Grenade Packet (x3 grenades)", 15, /obj/item/storage/box/packet/sebb, null, VENDOR_ITEM_REGULAR),
list("RAIL ATTACHMENTS", 0, null, null, null),
list("Red-Dot Sight", 15, /obj/item/attachable/reddot, null, VENDOR_ITEM_REGULAR),
diff --git a/code/game/machinery/vending/vendor_types/crew/staff_officer_armory.dm b/code/game/machinery/vending/vendor_types/crew/staff_officer_armory.dm
index 1e21f2f7256d..46de6ed028d0 100644
--- a/code/game/machinery/vending/vendor_types/crew/staff_officer_armory.dm
+++ b/code/game/machinery/vending/vendor_types/crew/staff_officer_armory.dm
@@ -18,7 +18,7 @@ GLOBAL_LIST_INIT(cm_vending_clothing_staff_officer_armory, list(
list("Marine Combat Gloves", 0, /obj/item/clothing/gloves/marine, MARINE_CAN_BUY_GLOVES, VENDOR_ITEM_MANDATORY),
list("MRE", 0, /obj/item/storage/box/MRE, MARINE_CAN_BUY_MRE, VENDOR_ITEM_MANDATORY),
list("Aviator Shades", 0, /obj/item/clothing/glasses/sunglasses/aviator, MARINE_CAN_BUY_GLASSES, VENDOR_ITEM_REGULAR),
- list("Bayonet", 0, /obj/item/attachable/bayonet, null, VENDOR_ITEM_REGULAR),
+ list("Bayonet", 0, /obj/item/attachable/bayonet, MARINE_CAN_BUY_ATTACHMENT, VENDOR_ITEM_REGULAR),
list("SPECIALISATION KIT (CHOOSE 1)", 0, null, null, null),
list("Essential Engineer Set", 0, /obj/effect/essentials_set/engi, MARINE_CAN_BUY_ESSENTIALS, VENDOR_ITEM_RECOMMENDED),
diff --git a/code/game/machinery/vending/vendor_types/crew/synthetic.dm b/code/game/machinery/vending/vendor_types/crew/synthetic.dm
index 7fbe39480999..9ce15535e2da 100644
--- a/code/game/machinery/vending/vendor_types/crew/synthetic.dm
+++ b/code/game/machinery/vending/vendor_types/crew/synthetic.dm
@@ -188,7 +188,7 @@ GLOBAL_LIST_INIT(cm_vending_clothing_synth, list(
GLOBAL_LIST_INIT(cm_vending_clothing_synth_snowflake, list(
list("USCM UNIFORMS", 0, null, null, null),
list("Medical Scrubs, Blue", 12, /obj/item/clothing/under/rank/medical/blue, null, VENDOR_ITEM_REGULAR),
- list("Medical Scrubs, Light Blue", 0, /obj/item/clothing/under/rank/medical/lightblue, null, VENDOR_ITEM_REGULAR),
+ list("Medical Scrubs, Light Blue", 12, /obj/item/clothing/under/rank/medical/lightblue, null, VENDOR_ITEM_REGULAR),
list("Medical Scrubs, Green", 12, /obj/item/clothing/under/rank/medical/green, null, VENDOR_ITEM_REGULAR),
list("Medical Scrubs, Purple", 12, /obj/item/clothing/under/rank/medical/purple, null, VENDOR_ITEM_REGULAR),
list("Medical Scrubs, Olive", 12, /obj/item/clothing/under/rank/medical/olive, null, VENDOR_ITEM_REGULAR),
@@ -349,6 +349,13 @@ GLOBAL_LIST_INIT(cm_vending_clothing_synth_snowflake, list(
list("Purple Armband", 6, /obj/item/clothing/accessory/armband/science, null, VENDOR_ITEM_REGULAR),
list("Yellow Armband", 6, /obj/item/clothing/accessory/armband/engine, null, VENDOR_ITEM_REGULAR),
list("Green Armband", 6, /obj/item/clothing/accessory/armband/medgreen, null, VENDOR_ITEM_REGULAR),
+ list("Blue Tie", 6, /obj/item/clothing/accessory/blue, null, VENDOR_ITEM_REGULAR),
+ list("Green Tie", 6, /obj/item/clothing/accessory/green, null, VENDOR_ITEM_REGULAR),
+ list("Black Tie", 6, /obj/item/clothing/accessory/black, null, VENDOR_ITEM_REGULAR),
+ list("Gold Tie", 6, /obj/item/clothing/accessory/gold, null, VENDOR_ITEM_REGULAR),
+ list("Red Tie", 6, /obj/item/clothing/accessory/red, null, VENDOR_ITEM_REGULAR),
+ list("Purple Tie", 6, /obj/item/clothing/accessory/purple, null, VENDOR_ITEM_REGULAR),
+ list("Stethoscope", 6, /obj/item/clothing/accessory/stethoscope, null, VENDOR_ITEM_REGULAR),
list("Dress Gloves", 6, /obj/item/clothing/gloves/marine/dress, null, VENDOR_ITEM_REGULAR),
))
diff --git a/code/game/machinery/vending/vendor_types/requisitions.dm b/code/game/machinery/vending/vendor_types/requisitions.dm
index a8d44a8b5012..5c5362257da2 100644
--- a/code/game/machinery/vending/vendor_types/requisitions.dm
+++ b/code/game/machinery/vending/vendor_types/requisitions.dm
@@ -55,6 +55,7 @@
list("M74 AGM-Smoke Airburst Grenade", floor(scale * 4), /obj/item/explosive/grenade/smokebomb/airburst, VENDOR_ITEM_REGULAR),
list("M74 AGM-Star Shell", floor(scale * 2), /obj/item/explosive/grenade/high_explosive/airburst/starshell, VENDOR_ITEM_REGULAR),
list("M74 AGM-Hornet Shell", floor(scale * 4), /obj/item/explosive/grenade/high_explosive/airburst/hornet_shell, VENDOR_ITEM_REGULAR),
+ list("G2 Electroshock Grenade", round(scale * 5), /obj/item/explosive/grenade/sebb, VENDOR_ITEM_REGULAR),
list("M40 HIRR Baton Slug", floor(scale * 8), /obj/item/explosive/grenade/slug/baton, VENDOR_ITEM_REGULAR),
list("M40 MFHS Metal Foam Grenade", floor(scale * 6), /obj/item/explosive/grenade/metal_foam, VENDOR_ITEM_REGULAR),
list("Plastic Explosives", floor(scale * 3), /obj/item/explosive/plastic, VENDOR_ITEM_REGULAR),
@@ -154,6 +155,7 @@
list("M74 AGM-Airburst Smoke Grenade Packet", 0, /obj/item/storage/box/packet/airburst_smoke, VENDOR_ITEM_REGULAR),
list("M74 AGM-S Star Shell Packet", 0, /obj/item/storage/box/packet/flare, VENDOR_ITEM_REGULAR),
list("M74 AGM-H Hornet Shell Packet", 0, /obj/item/storage/box/packet/hornet, VENDOR_ITEM_REGULAR),
+ list("G2 Electroshock grenade packet", 0, /obj/item/storage/box/packet/sebb, VENDOR_ITEM_REGULAR),
list("M20 mine box", 0, /obj/item/storage/box/explosive_mines, VENDOR_ITEM_REGULAR),
list("OTHER BOXES", -1, null, null),
diff --git a/code/game/machinery/vending/vendor_types/squad_prep/squad_engineer.dm b/code/game/machinery/vending/vendor_types/squad_prep/squad_engineer.dm
index aaf134c4a459..9c19a5b172c6 100644
--- a/code/game/machinery/vending/vendor_types/squad_prep/squad_engineer.dm
+++ b/code/game/machinery/vending/vendor_types/squad_prep/squad_engineer.dm
@@ -35,6 +35,7 @@ GLOBAL_LIST_INIT(cm_vending_gear_engi, list(
list("M74 AGM-Hornet Airburst Packet (x3 airburst grenades", 20, /obj/item/storage/box/packet/hornet, null, VENDOR_ITEM_REGULAR),
list("M20 Mine Box (x4 mines)", 18, /obj/item/storage/box/explosive_mines, null, VENDOR_ITEM_REGULAR),
list("M40 MFHS Metal Foam Grenade", 5, /obj/item/explosive/grenade/metal_foam, null, VENDOR_ITEM_REGULAR),
+ list("G2 Electroshock Grenade Packet (x3 grenades)", 16, /obj/item/storage/box/packet/sebb, null, VENDOR_ITEM_REGULAR),
list("PRIMARY AMMUNITION", 0, null, null, null),
list("M4RA AP Magazine (10x24mm)", 6, /obj/item/ammo_magazine/rifle/m4ra/ap, null, VENDOR_ITEM_REGULAR),
diff --git a/code/game/machinery/vending/vendor_types/squad_prep/squad_leader.dm b/code/game/machinery/vending/vendor_types/squad_prep/squad_leader.dm
index 17d3419ac2f8..7f8d70db4328 100644
--- a/code/game/machinery/vending/vendor_types/squad_prep/squad_leader.dm
+++ b/code/game/machinery/vending/vendor_types/squad_prep/squad_leader.dm
@@ -61,6 +61,7 @@ GLOBAL_LIST_INIT(cm_vending_gear_leader, list(
list("M74 AGM-Hornet Airburst Packet (x3 airburst grenades", 20, /obj/item/storage/box/packet/hornet, null, VENDOR_ITEM_REGULAR),
list("M20 Mine Box (x4 mines)", 20, /obj/item/storage/box/explosive_mines, null, VENDOR_ITEM_REGULAR),
list("M40 MFHS Metal Foam Grenade", 5, /obj/item/explosive/grenade/metal_foam, null, VENDOR_ITEM_REGULAR),
+ list("G2 Electroshock Grenade Packet (x3 grenades)", 16, /obj/item/storage/box/packet/sebb, null, VENDOR_ITEM_REGULAR),
list("MEDICAL SUPPLIES", 0, null, null, null),
list("Burn Kit", 2, /obj/item/stack/medical/advanced/ointment, null, VENDOR_ITEM_REGULAR),
diff --git a/code/game/machinery/vending/vendor_types/squad_prep/squad_medic.dm b/code/game/machinery/vending/vendor_types/squad_prep/squad_medic.dm
index a73df202d4ef..e9d69ad396de 100644
--- a/code/game/machinery/vending/vendor_types/squad_prep/squad_medic.dm
+++ b/code/game/machinery/vending/vendor_types/squad_prep/squad_medic.dm
@@ -57,6 +57,7 @@ GLOBAL_LIST_INIT(cm_vending_gear_medic, list(
list("M74 AGM-Smoke Airburst Packet (x3 airburst grenades)", 10, /obj/item/storage/box/packet/airburst_smoke, null, VENDOR_ITEM_REGULAR),
list("M74 AGM-Hornet Airburst Packet (x3 airburst grenades", 20, /obj/item/storage/box/packet/hornet, null, VENDOR_ITEM_REGULAR),
list("M20 Mine Box (x4 mines)", 20, /obj/item/storage/box/explosive_mines, null, VENDOR_ITEM_REGULAR),
+ list("G2 Electroshock Grenade Packet (x3 grenades)", 16, /obj/item/storage/box/packet/sebb, null, VENDOR_ITEM_REGULAR),
list("PRIMARY AMMUNITION", 0, null, null, null),
list("M4RA AP Magazine (10x24mm)", 6, /obj/item/ammo_magazine/rifle/m4ra/ap, null, VENDOR_ITEM_REGULAR),
diff --git a/code/game/machinery/vending/vendor_types/squad_prep/squad_specialist.dm b/code/game/machinery/vending/vendor_types/squad_prep/squad_specialist.dm
index 4d14b7b89ccd..8fbf574d36d7 100644
--- a/code/game/machinery/vending/vendor_types/squad_prep/squad_specialist.dm
+++ b/code/game/machinery/vending/vendor_types/squad_prep/squad_specialist.dm
@@ -32,6 +32,7 @@ GLOBAL_LIST_INIT(cm_vending_gear_spec, list(
list("M74 AGM-F Fragmentation Grenades x6", 40, /obj/effect/essentials_set/agmf_6_pack, null, VENDOR_ITEM_REGULAR),
list("M74 AGM-I Incendiary Grenades x6", 40, /obj/effect/essentials_set/agmi_6_pack, null, VENDOR_ITEM_REGULAR),
list("M74 AGM-S Smoke Grenades x6", 20, /obj/effect/essentials_set/agms_6_pack, null, VENDOR_ITEM_REGULAR),
+ list("G2 Electroshock Grenade Pack x6", 40, /obj/effect/essentials_set/sebb_6_pack, null, VENDOR_ITEM_REGULAR),
list("EXTRA FLAMETHROWER TANKS", 0, null, null, null),
list("Large Incinerator Tank", 40, /obj/item/ammo_magazine/flamer_tank/large, null, VENDOR_ITEM_REGULAR),
@@ -249,3 +250,13 @@ GLOBAL_LIST_INIT(cm_vending_clothing_specialist, list(
/obj/item/explosive/grenade/smokebomb/airburst,
/obj/item/explosive/grenade/smokebomb/airburst,
)
+
+/obj/effect/essentials_set/sebb_6_pack
+ spawned_gear_list = list(
+ /obj/item/explosive/grenade/sebb,
+ /obj/item/explosive/grenade/sebb,
+ /obj/item/explosive/grenade/sebb,
+ /obj/item/explosive/grenade/sebb,
+ /obj/item/explosive/grenade/sebb,
+ /obj/item/explosive/grenade/sebb,
+ )
diff --git a/code/game/machinery/vending/vendor_types/squad_prep/squad_tl.dm b/code/game/machinery/vending/vendor_types/squad_prep/squad_tl.dm
index c37dd98ed263..73efbe1148e5 100644
--- a/code/game/machinery/vending/vendor_types/squad_prep/squad_tl.dm
+++ b/code/game/machinery/vending/vendor_types/squad_prep/squad_tl.dm
@@ -12,6 +12,7 @@ GLOBAL_LIST_INIT(cm_vending_gear_tl, list(
list("M74 AGM-Hornet Airburst Packet (x3 airburst grenades", 20, /obj/item/storage/box/packet/hornet, null, VENDOR_ITEM_REGULAR),
list("M20 Mine Box (x4 mines)", 20, /obj/item/storage/box/explosive_mines, null, VENDOR_ITEM_REGULAR),
list("M40 MFHS Metal Foam Grenade", 5, /obj/item/explosive/grenade/metal_foam, null, VENDOR_ITEM_REGULAR),
+ list("G2 Electroshock Grenade Packet (x3 grenades)", 16, /obj/item/storage/box/packet/sebb, null, VENDOR_ITEM_REGULAR),
list("PRIMARY AMMUNITION", 0, null, null, null),
list("M4RA AP Magazine (10x24mm)", 10, /obj/item/ammo_magazine/rifle/m4ra/ap, null, VENDOR_ITEM_REGULAR),
diff --git a/code/game/objects/effects/aliens.dm b/code/game/objects/effects/aliens.dm
index 10d4e8d098fb..34da5e3d2623 100644
--- a/code/game/objects/effects/aliens.dm
+++ b/code/game/objects/effects/aliens.dm
@@ -597,7 +597,7 @@
total_hits++
- var/datum/action/xeno_action/activable/boiler_trap/trap = get_xeno_action_by_type(linked_xeno, /datum/action/xeno_action/activable/boiler_trap)
+ var/datum/action/xeno_action/activable/boiler_trap/trap = get_action(linked_xeno, /datum/action/xeno_action/activable/boiler_trap)
trap.reduce_cooldown(total_hits*4 SECONDS)
diff --git a/code/game/objects/effects/effect_system/particle_effects.dm b/code/game/objects/effects/effect_system/particle_effects.dm
index 972d242bf359..9440c16f2d4e 100644
--- a/code/game/objects/effects/effect_system/particle_effects.dm
+++ b/code/game/objects/effects/effect_system/particle_effects.dm
@@ -8,10 +8,10 @@
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
unacidable = TRUE // So effect are not targeted by alien acid.
-/obj/effect/particle_effect/initialize_pass_flags(datum/pass_flags_container/PF)
+/obj/effect/particle_effect/initialize_pass_flags(datum/pass_flags_container/pass_flags)
..()
- if (PF)
- PF.flags_pass = PASS_OVER|PASS_AROUND|PASS_UNDER|PASS_THROUGH|PASS_MOB_THRU
+ if (pass_flags)
+ pass_flags.flags_pass = PASS_OVER|PASS_AROUND|PASS_UNDER|PASS_THROUGH|PASS_MOB_THRU
//Water
@@ -22,17 +22,14 @@
var/life = 15
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
-/obj/effect/particle_effect/water/initialize_pass_flags(datum/pass_flags_container/PF)
+/obj/effect/particle_effect/water/initialize_pass_flags(datum/pass_flags_container/pass_flags)
..()
- if (PF)
- PF.flags_pass = PASS_THROUGH|PASS_OVER|PASS_MOB_THRU|PASS_UNDER
+ if (pass_flags)
+ pass_flags.flags_pass = PASS_THROUGH|PASS_OVER|PASS_MOB_THRU|PASS_UNDER
/obj/effect/particle_effect/water/Move(turf/newloc)
- //var/turf/T = src.loc
- //if (istype(T, /turf))
- // T.firelevel = 0 //TODO: FIX
- if (--src.life < 1)
- //SN src = null
+ life -= 1
+ if (life < 1)
qdel(src)
if(newloc.density)
return 0
diff --git a/code/game/objects/effects/effect_system/smoke.dm b/code/game/objects/effects/effect_system/smoke.dm
index d4152bdee37e..d0ea5d2ed5ef 100644
--- a/code/game/objects/effects/effect_system/smoke.dm
+++ b/code/game/objects/effects/effect_system/smoke.dm
@@ -731,7 +731,7 @@
location = get_turf(loca)
if(direct)
direction = direct
- if(lifetime)
+ if(smoke_time)
lifetime = smoke_time
radius = min(radius, 10)
amount = radius
diff --git a/code/game/objects/effects/overlays.dm b/code/game/objects/effects/overlays.dm
index ce0fd5506cd7..d559137f79b1 100644
--- a/code/game/objects/effects/overlays.dm
+++ b/code/game/objects/effects/overlays.dm
@@ -223,6 +223,7 @@
icon_state = "empdisable"
name = "emp sparks"
effect_duration = 10
+ mouse_opacity = MOUSE_OPACITY_TRANSPARENT
/obj/effect/overlay/temp/emp_sparks/New(loc)
setDir(pick(GLOB.cardinals))
@@ -234,8 +235,12 @@
icon_state = "emppulse"
effect_duration = 20
-
-
+/obj/effect/overlay/temp/elec_arc
+ icon = 'icons/effects/effects.dmi'
+ icon_state = "electricity"
+ name = "electric arc"
+ effect_duration = 3 SECONDS
+ mouse_opacity = MOUSE_OPACITY_TRANSPARENT
//gib animation
diff --git a/code/game/objects/explosion_recursive.dm b/code/game/objects/explosion_recursive.dm
index 2ec61b0cc1f1..855f6c2f43c7 100644
--- a/code/game/objects/explosion_recursive.dm
+++ b/code/game/objects/explosion_recursive.dm
@@ -71,7 +71,9 @@ explosion resistance exactly as much as their health
falloff = max(falloff0, power/100) //prevent explosions with a range larger than 100 tiles
minimum_spread_power = -power * reflection_amplification_limit
- msg_admin_attack("Explosion with Power: [power], Falloff: [falloff] in area [epicenter.loc.name] ([epicenter.x],[epicenter.y],[epicenter.z]).", src.loc.x, src.loc.y, src.loc.z)
+ var/obj/causing_obj = explosion_cause_data?.resolve_cause()
+ var/mob/causing_mob = explosion_cause_data?.resolve_mob()
+ msg_admin_attack("Explosion with Power: [power], Falloff: [falloff],[causing_obj ? " from [causing_obj]" : ""][causing_mob ? " by [key_name(causing_mob)]" : ""] in area [epicenter.loc.name] ([epicenter.x],[epicenter.y],[epicenter.z]).", loc.x, loc.y, loc.z)
playsound(epicenter, 'sound/effects/explosionfar.ogg', 100, 1, round(power^2,1))
playsound(epicenter, "explosion", 90, 1, max(round(power,1),7) )
diff --git a/code/game/objects/items/devices/RCD.dm b/code/game/objects/items/devices/RCD.dm
deleted file mode 100644
index 00e569800314..000000000000
--- a/code/game/objects/items/devices/RCD.dm
+++ /dev/null
@@ -1,194 +0,0 @@
-//This file was auto-corrected by findeclaration.exe on 25.5.2012 20:42:32
-
-/*
-CONTAINS:
-RCD
-*/
-/obj/item/device/rcd
- name = "rapid-construction-device (RCD)"
- desc = "A device used to rapidly build walls/floor."
- icon = 'icons/obj/items/devices.dmi'
- icon_state = "rcd"
- opacity = FALSE
- density = FALSE
- anchored = FALSE
- flags_atom = FPRINT|CONDUCT
- force = 10
- throwforce = 10
- throw_speed = SPEED_FAST
- throw_range = 5
- w_class = SIZE_MEDIUM
- matter = list("metal" = 50000)
-
- var/datum/effect_system/spark_spread/spark_system
- var/stored_matter = 0
- var/working = 0
- var/mode = 1
- var/canRwall = 0
- var/disabled = 0
-
-
-/obj/item/device/rcd/New()
- desc = "A RCD. It currently holds [stored_matter]/30 matter-units."
- src.spark_system = new /datum/effect_system/spark_spread
- spark_system.set_up(5, 0, src)
- spark_system.attach(src)
- return
-
-/obj/item/device/rcd/Destroy()
- QDEL_NULL(spark_system)
- return ..()
-
-
-/obj/item/device/rcd/attackby(obj/item/W, mob/user)
- ..()
- if(istype(W, /obj/item/ammo_rcd))
- if((stored_matter + 10) > 30)
- to_chat(user, SPAN_NOTICE("The RCD cant hold any more matter-units."))
- return
- user.drop_held_item()
- qdel(W)
- stored_matter += 10
- playsound(src.loc, 'sound/machines/click.ogg', 15, 1)
- to_chat(user, SPAN_NOTICE("The RCD now holds [stored_matter]/30 matter-units."))
- desc = "A RCD. It currently holds [stored_matter]/30 matter-units."
- return
-
-
-/obj/item/device/rcd/attack_self(mob/user)
- ..()
-
- //Change the mode
- playsound(src.loc, 'sound/effects/pop.ogg', 15, 0)
- switch(mode)
- if(1)
- mode = 2
- to_chat(user, SPAN_NOTICE("Changed mode to 'Airlock'"))
- if(prob(20))
- src.spark_system.start()
- return
- if(2)
- mode = 3
- to_chat(user, SPAN_NOTICE("Changed mode to 'Deconstruct'"))
- if(prob(20))
- src.spark_system.start()
- return
- if(3)
- mode = 1
- to_chat(user, SPAN_NOTICE("Changed mode to 'Floor & Walls'"))
- if(prob(20))
- src.spark_system.start()
- return
-
-/obj/item/device/rcd/proc/activate()
- playsound(src.loc, 'sound/items/Deconstruct.ogg', 25, 1)
-
-
-/obj/item/device/rcd/afterattack(atom/A, mob/user, proximity)
- if(!proximity) return
- if(disabled)
- return 0
- if(istype(A,/area/shuttle) || istype(A,/turf/open/space/transit))
- return 0
- if(!(istype(A, /turf) || istype(A, /obj/structure/machinery/door/airlock)))
- return 0
-
- switch(mode)
- if(1)
- if(istype(A, /turf/open/space))
- if(useResource(1, user))
- to_chat(user, "Building Floor...")
- activate()
- A:ChangeTurf(/turf/open/floor/plating/airless)
- return 1
- return 0
-
- if(istype(A, /turf/open/floor))
- if(checkResource(3, user))
- to_chat(user, "Building Wall ...")
- playsound(src.loc, 'sound/machines/click.ogg', 15, 1)
- if(do_after(user, 20, INTERRUPT_ALL|BEHAVIOR_IMMOBILE, BUSY_ICON_BUILD))
- if(!useResource(3, user)) return 0
- activate()
- A:ChangeTurf(/turf/closed/wall)
- return 1
- return 0
-
- if(2)
- if(istype(A, /turf/open/floor))
- if(checkResource(10, user))
- to_chat(user, "Building Airlock...")
- playsound(src.loc, 'sound/machines/click.ogg', 15, 1)
- if(do_after(user, 50, INTERRUPT_ALL|BEHAVIOR_IMMOBILE, BUSY_ICON_BUILD))
- if(!useResource(10, user)) return 0
- activate()
- var/obj/structure/machinery/door/airlock/T = new /obj/structure/machinery/door/airlock( A )
- T.autoclose = 1
- return 1
- return 0
- return 0
-
- if(3)
- if(istype(A, /turf/closed/wall))
- var/turf/closed/wall/WL = A
- if(WL.hull)
- return 0
- if(istype(A, /turf/closed/wall/r_wall) && !canRwall)
- return 0
- if(checkResource(5, user))
- to_chat(user, "Deconstructing Wall...")
- playsound(src.loc, 'sound/machines/click.ogg', 15, 1)
- if(do_after(user, 40, INTERRUPT_ALL|BEHAVIOR_IMMOBILE, BUSY_ICON_BUILD))
- if(!useResource(5, user)) return 0
- activate()
- A:ChangeTurf(/turf/open/floor/plating/airless)
- return 1
- return 0
-
- if(istype(A, /turf/open/floor) && !istype(A, /turf/open/floor/plating))
- if(checkResource(5, user))
- to_chat(user, "Deconstructing Floor...")
- playsound(src.loc, 'sound/machines/click.ogg', 15, 1)
- if(do_after(user, 50, INTERRUPT_ALL|BEHAVIOR_IMMOBILE, BUSY_ICON_BUILD))
- if(!useResource(5, user)) return 0
- activate()
- A:ChangeTurf(/turf/open/floor/plating/airless)
- return 1
- return 0
-
- if(istype(A, /obj/structure/machinery/door/airlock))
- if(checkResource(10, user))
- to_chat(user, "Deconstructing Airlock...")
- playsound(src.loc, 'sound/machines/click.ogg', 15, 1)
- if(do_after(user, 50, INTERRUPT_ALL|BEHAVIOR_IMMOBILE, BUSY_ICON_BUILD))
- if(!useResource(10, user)) return 0
- activate()
- qdel(A)
- return 1
- return 0
- return 0
- else
- to_chat(user, "ERROR: RCD in MODE: [mode] attempted use by [user]. Send this text #coderbus or an admin.")
- return 0
-
-/obj/item/device/rcd/proc/useResource(amount, mob/user)
- if(stored_matter < amount)
- return 0
- stored_matter -= amount
- desc = "A RCD. It currently holds [stored_matter]/30 matter-units."
- return 1
-
-/obj/item/device/rcd/proc/checkResource(amount, mob/user)
- return stored_matter >= amount
-
-/obj/item/ammo_rcd
- name = "compressed matter cartridge"
- desc = "Highly compressed matter for the RCD."
- icon = 'icons/obj/items/weapons/guns/legacy/old_bayguns.dmi'
- icon_state = "rcd"
- item_state = "rcdammo"
- opacity = FALSE
- density = FALSE
- anchored = FALSE
-
- matter = list("metal" = 30000,"glass" = 15000)
diff --git a/code/game/objects/items/devices/RSF.dm b/code/game/objects/items/devices/RSF.dm
deleted file mode 100644
index 29f84c7c6d0c..000000000000
--- a/code/game/objects/items/devices/RSF.dm
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
-CONTAINS:
-RSF
-
-*/
-
-/obj/item/device/rsf
- name = "\improper Rapid-Service-Fabricator"
- desc = "A device used to rapidly deploy service items."
- icon = 'icons/obj/items/devices.dmi'
- icon_state = "rcd"
- opacity = FALSE
- density = FALSE
- anchored = FALSE
- var/stored_matter = 30
- var/mode = 1
- w_class = SIZE_MEDIUM
-
-/obj/item/device/rsf/get_examine_text(mob/user)
- . = ..()
- . += "It currently holds [stored_matter]/30 fabrication-units."
-
-/obj/item/device/rsf/attackby(obj/item/W, mob/user)
- ..()
- if (istype(W, /obj/item/ammo_rcd))
-
- if ((stored_matter + 10) > 30)
- to_chat(user, "The RSF can't hold any more matter.")
- return
-
- qdel(W)
-
- stored_matter += 10
- playsound(src.loc, 'sound/machines/click.ogg', 15, 1)
- to_chat(user, "The RSF now holds [stored_matter]/30 fabrication-units.")
- return
-
-/obj/item/device/rsf/attack_self(mob/user)
- ..()
- playsound(src.loc, 'sound/effects/pop.ogg', 15, 0)
- if (mode == 1)
- mode = 2
- to_chat(user, "Changed dispensing mode to 'Drinking Glass'")
- return
- if (mode == 2)
- mode = 3
- to_chat(user, "Changed dispensing mode to 'Paper'")
- return
- if (mode == 3)
- mode = 4
- to_chat(user, "Changed dispensing mode to 'Pen'")
- return
- if (mode == 4)
- mode = 5
- to_chat(user, "Changed dispensing mode to 'Dice Pack'")
- return
- if (mode == 5)
- mode = 6
- to_chat(user, "Changed dispensing mode to 'Cigarette'")
- return
- if (mode == 6)
- mode = 1
- to_chat(user, "Changed dispensing mode to 'Dosh'")
- return
- // Change mode
-
-/obj/item/device/rsf/afterattack(atom/A, mob/user, proximity)
-
- if(!proximity) return
-
- if(stored_matter <= 0)
- return
-
- if(!istype(A, /obj/structure/surface/table) && !istype(A, /turf/open/floor))
- return
-
- playsound(src.loc, 'sound/machines/click.ogg', 25, 1)
- var/obj/product
-
- switch(mode)
- if(1)
- product = new /obj/item/spacecash/c10()
- if(2)
- product = new /obj/item/reagent_container/food/drinks/drinkingglass()
- if(3)
- product = new /obj/item/paper()
- if(4)
- product = new /obj/item/tool/pen()
- if(5)
- product = new /obj/item/storage/pill_bottle/dice()
- if(6)
- product = new /obj/item/clothing/mask/cigarette()
-
- to_chat(user, "Dispensing [product ? product : "product"]...")
- product.forceMove(get_turf(A))
-
- stored_matter--
- to_chat(user, "The RSF now holds [stored_matter]/30 fabrication-units.")
diff --git a/code/game/objects/items/devices/RSP.dm b/code/game/objects/items/devices/RSP.dm
deleted file mode 100644
index cb61de1a77cc..000000000000
--- a/code/game/objects/items/devices/RSP.dm
+++ /dev/null
@@ -1,11 +0,0 @@
-/obj/item/device/rsp
- name = "\improper Rapid-Seed-Producer (RSP)"
- desc = "A device used to rapidly deploy seeds."
- icon = 'icons/obj/items/devices.dmi'
- icon_state = "rsp"
- opacity = FALSE
- density = FALSE
- anchored = FALSE
- var/stored_matter = 0
- var/mode = 1
- w_class = SIZE_MEDIUM
diff --git a/code/game/objects/items/devices/binoculars.dm b/code/game/objects/items/devices/binoculars.dm
index 5da4704e0e78..3248115adfa8 100644
--- a/code/game/objects/items/devices/binoculars.dm
+++ b/code/game/objects/items/devices/binoculars.dm
@@ -403,6 +403,7 @@
COOLDOWN_START(designator, spotting_cooldown, 0)
/datum/action/item_action/specialist/spotter_target/action_activate()
+ . = ..()
if(!ishuman(owner))
return
var/mob/living/carbon/human/human = owner
diff --git a/code/game/objects/items/devices/flashlight.dm b/code/game/objects/items/devices/flashlight.dm
index 8d5e3cc752ff..e506f51ce21c 100644
--- a/code/game/objects/items/devices/flashlight.dm
+++ b/code/game/objects/items/devices/flashlight.dm
@@ -15,7 +15,7 @@
ground_offset_x = 2
ground_offset_y = 6
- actions_types = list(/datum/action/item_action)
+ actions_types = list(/datum/action/item_action/toggle)
var/on = FALSE
var/raillight_compatible = TRUE //Can this be turned into a rail light ?
var/toggleable = TRUE
diff --git a/code/game/objects/items/devices/helmet_visors.dm b/code/game/objects/items/devices/helmet_visors.dm
index 8f921a62f3f5..e2005a841bc3 100644
--- a/code/game/objects/items/devices/helmet_visors.dm
+++ b/code/game/objects/items/devices/helmet_visors.dm
@@ -146,6 +146,7 @@
return
/datum/action/item_action/view_publications/helmet_visor/action_activate()
+ . = ..()
var/obj/item/device/helmet_visor/medical/advanced/medical_visor = locate() in holder_item
if(!medical_visor)
diff --git a/code/game/objects/items/devices/motion_detector.dm b/code/game/objects/items/devices/motion_detector.dm
index 3551e3a02bef..f68295001f92 100644
--- a/code/game/objects/items/devices/motion_detector.dm
+++ b/code/game/objects/items/devices/motion_detector.dm
@@ -33,9 +33,9 @@
var/long_range_cooldown = 2
var/blip_type = "detector"
var/iff_signal = FACTION_MARINE
- actions_types = list(/datum/action/item_action)
+ actions_types = list(/datum/action/item_action/toggle)
var/scanning = FALSE // controls if MD is in process of scan
- var/datum/shape/rectangle/range_bounds
+ var/datum/shape/rectangle/square/range_bounds
var/long_range_locked = FALSE //only long-range MD
var/ping_overlay
@@ -48,7 +48,7 @@
/obj/item/device/motiondetector/Initialize()
. = ..()
- range_bounds = new //Just creating a rectangle datum
+ range_bounds = new //Just creating a square datum
update_icon()
/obj/item/device/motiondetector/Destroy()
@@ -215,12 +215,7 @@
if(!istype(cur_turf))
return
- if(!range_bounds)
- range_bounds = new/datum/shape/rectangle
- range_bounds.center_x = cur_turf.x
- range_bounds.center_y = cur_turf.y
- range_bounds.width = detector_range * 2
- range_bounds.height = detector_range * 2
+ range_bounds.set_shape(cur_turf.x, cur_turf.y, detector_range * 2)
var/list/ping_candidates = SSquadtree.players_in_range(range_bounds, cur_turf.z, QTREE_EXCLUDE_OBSERVER | QTREE_SCAN_MOBS)
diff --git a/code/game/objects/items/devices/suit_cooling.dm b/code/game/objects/items/devices/suit_cooling.dm
deleted file mode 100644
index 564b3e41f591..000000000000
--- a/code/game/objects/items/devices/suit_cooling.dm
+++ /dev/null
@@ -1,179 +0,0 @@
-/obj/item/device/suit_cooling_unit
- name = "portable suit cooling unit"
- desc = "A portable heat sink and liquid cooled radiator that can be hooked up to a space suit's existing temperature controls to provide industrial levels of cooling."
- w_class = SIZE_LARGE
- icon_state = "suitcooler0"
- flags_equip_slot = SLOT_BACK //you can carry it on your back if you want, but it won't do anything unless attached to suit storage
-
- //copied from tank.dm
- flags_atom = FPRINT|CONDUCT
- force = 5
- throwforce = 10
- throw_speed = SPEED_FAST
- throw_range = 4
-
-
-
- var/on = 0 //is it turned on?
- var/cover_open = 0 //is the cover open?
- var/obj/item/cell/cell
- var/max_cooling = 12 //in degrees per second - probably don't need to mess with heat capacity here
- var/charge_consumption = 16.6 //charge per second at max_cooling
- var/thermostat = T20C
-
- //TODO: make it heat up the surroundings when not in space
-
-/obj/item/device/suit_cooling_unit/Initialize(mapload, ...)
- . = ..()
-
- START_PROCESSING(SSobj, src)
-
- cell = new/obj/item/cell(src) //comes with the crappy default power cell - high-capacity ones shouldn't be hard to find
-
-/obj/item/device/suit_cooling_unit/Destroy()
- STOP_PROCESSING(SSobj, src)
- return ..()
-
-/obj/item/device/suit_cooling_unit/process()
- if (!on || !cell)
- return
-
- if (!ismob(loc))
- return
-
- if (!attached_to_suit(loc)) //make sure they have a suit and we are attached to it
- return
-
- var/mob/living/carbon/human/H = loc
-
- var/efficiency = 1 - H.get_pressure_weakness() //you need to have a good seal for effective cooling
- var/env_temp = get_environment_temperature() //wont save you from a fire
- var/temp_adj = min(H.bodytemperature - max(thermostat, env_temp), max_cooling)
-
- if (temp_adj < 0.5) //only cools, doesn't heat, also we don't need extreme precision
- return
-
- var/charge_usage = (temp_adj/max_cooling)*charge_consumption
-
- H.bodytemperature -= temp_adj*efficiency
- H.recalculate_move_delay = TRUE
-
- cell.use(charge_usage)
-
- if(cell.charge <= 0)
- turn_off()
-
-/obj/item/device/suit_cooling_unit/proc/get_environment_temperature()
- if (ishuman(loc))
- var/mob/living/carbon/human/H = loc
- return H.return_temperature()
-
- var/turf/T = get_turf(src)
- return T.return_temperature()
-
-/obj/item/device/suit_cooling_unit/proc/attached_to_suit(mob/M)
- if (!ishuman(M))
- return 0
-
- var/mob/living/carbon/human/H = M
-
- if (!H.wear_suit || H.s_store != src)
- return 0
-
- return 1
-
-/obj/item/device/suit_cooling_unit/proc/turn_on()
- if(!cell)
- return
- if(cell.charge <= 0)
- return
-
- on = 1
- updateicon()
-
-/obj/item/device/suit_cooling_unit/proc/turn_off()
- if (ismob(src.loc))
- var/mob/M = src.loc
- M.show_message("\The [src] clicks and whines as it powers down.", SHOW_MESSAGE_AUDIBLE) //let them know in case it's run out of power.
- on = 0
- updateicon()
-
-/obj/item/device/suit_cooling_unit/attack_self(mob/user)
- ..()
-
- if(cover_open && cell)
- if(ishuman(user))
- user.put_in_hands(cell)
- else
- cell.forceMove(get_turf(loc))
-
- cell.add_fingerprint(user)
- cell.update_icon()
-
- to_chat(user, "You remove [cell].")
- src.cell = null
- updateicon()
- return
-
- //TODO use a UI like the air tanks
- if(on)
- turn_off()
- else
- turn_on()
- if (on)
- to_chat(user, "You switch on [src].")
-
-/obj/item/device/suit_cooling_unit/attackby(obj/item/W as obj, mob/user as mob)
- if (HAS_TRAIT(W, TRAIT_TOOL_SCREWDRIVER))
- if(cover_open)
- cover_open = 0
- to_chat(user, "You screw the panel into place.")
- else
- cover_open = 1
- to_chat(user, "You unscrew the panel.")
- updateicon()
- return
-
- if (istype(W, /obj/item/cell))
- if(cover_open)
- if(cell)
- to_chat(user, "There is \a [cell] already installed here.")
- else
- if(user.drop_held_item())
- W.forceMove(src)
- cell = W
- to_chat(user, "You insert [cell].")
- updateicon()
- return
-
- return ..()
-
-/obj/item/device/suit_cooling_unit/proc/updateicon()
- if (cover_open)
- if (cell)
- icon_state = "suitcooler1"
- else
- icon_state = "suitcooler2"
- else
- icon_state = "suitcooler0"
-
-/obj/item/device/suit_cooling_unit/get_examine_text(mob/user)
- . = ..()
- if (on)
- if (attached_to_suit(src.loc))
- . += "It's switched on and running."
- else
- . += "It's switched on, but not attached to anything."
- else
- . += "It is switched off."
-
- if (cover_open)
- if(cell)
- . += "The panel is open, exposing [cell]."
- else
- . += "The panel is open."
-
- if (cell)
- . += "The charge meter reads [floor(cell.percent())]%."
- else
- . += "It doesn't have a power cell installed."
diff --git a/code/game/objects/items/devices/walkman.dm b/code/game/objects/items/devices/walkman.dm
index 42c03d757dbd..bef8e8f5ff79 100644
--- a/code/game/objects/items/devices/walkman.dm
+++ b/code/game/objects/items/devices/walkman.dm
@@ -269,6 +269,7 @@
button.name = name
/datum/action/item_action/walkman/play_pause/action_activate()
+ . = ..()
if(target)
var/obj/item/device/walkman/WM = target
WM.attack_self(owner)
@@ -282,6 +283,7 @@
button.name = name
/datum/action/item_action/walkman/next_song/action_activate()
+ . = ..()
if(target)
var/obj/item/device/walkman/WM = target
WM.next_song(owner)
@@ -295,6 +297,7 @@
button.name = name
/datum/action/item_action/walkman/restart_song/action_activate()
+ . = ..()
if(target)
var/obj/item/device/walkman/WM = target
WM.restart_song(owner)
diff --git a/code/game/objects/items/devices/whistle.dm b/code/game/objects/items/devices/whistle.dm
index 331df3ffa006..07196a3e1bb9 100644
--- a/code/game/objects/items/devices/whistle.dm
+++ b/code/game/objects/items/devices/whistle.dm
@@ -5,7 +5,7 @@
w_class = SIZE_TINY
flags_atom = FPRINT|CONDUCT
flags_equip_slot = SLOT_FACE
- actions_types = list(/datum/action/item_action)
+ actions_types = list(/datum/action/item_action/toggle/use)
var/volume = 60
var/spam_cooldown_time = 10 SECONDS
@@ -51,7 +51,6 @@
usr.put_in_l_hand(src)
add_fingerprint(usr)
-
/obj/item/device/hailer
name = "hailer"
desc = "Used by obese officers to save their breath for running."
diff --git a/code/game/objects/items/explosives/grenades/marines.dm b/code/game/objects/items/explosives/grenades/marines.dm
index 1cd3e1577c57..09c0197cda7f 100644
--- a/code/game/objects/items/explosives/grenades/marines.dm
+++ b/code/game/objects/items/explosives/grenades/marines.dm
@@ -465,6 +465,187 @@
icon_state = "grenade_phos_clf"
item_state = "grenade_phos_clf"
+/obj/item/explosive/grenade/sebb
+ name = "\improper G2 Electroshock grenade"
+ desc = "This is a G2 Electroshock Grenade. Produced by Armat Battlefield Systems, it's sometimes referred to as the Sonic Electric Ball Breaker, \
+ after a rash of incidents where the intense 1.2 gV sonic payload caused... rupturing. \
+ A bounding landmine mode is available for this weapon which activates a small drill to self-bury itself when planted. Simply plant it at your feet and walk away."
+ icon_state = "grenade_sebb"
+ item_state = "grenade_sebb"
+ det_time = 3 SECONDS
+ underslug_launchable = TRUE
+ /// Maximum range of effect
+ var/range = 5
+ /// Maximum possible damage before falloff.
+ var/damage = 110
+ /// Factor to mutiply the effect range has on damage.
+ var/falloff_dam_reduction_mult = 20
+ /// Post falloff calc damage is divided by this to get xeno slowdown
+ var/xeno_slowdown_numerator = 12
+ /// Post falloff calc damage is multipled by this to get human stamina damage
+ var/human_stam_dam_factor = 0.9
+
+/obj/item/explosive/grenade/sebb/get_examine_text(mob/user)
+ . = ..()
+ . += SPAN_NOTICE("To put into mine mode, plant at feet.")
+
+/obj/item/explosive/grenade/sebb/afterattack(atom/target, mob/user, proximity)
+ var/turf/user_turf = get_turf(user)
+ if(active)
+ return
+
+ if(!isturf(target))
+ return
+
+ if(user.action_busy)
+ return
+
+ if(target != get_turf(user))
+ return
+
+ if(locate(/obj/item/explosive/mine) in get_turf(src))
+ to_chat(user, SPAN_WARNING("There already is a mine at this position!"))
+ return
+
+ if(antigrief_protection && user.faction == FACTION_MARINE && explosive_antigrief_check(src, user))
+ to_chat(user, SPAN_WARNING("\The [name]'s safe-area accident inhibitor prevents you from planting!"))
+ msg_admin_niche("[key_name(user)] attempted to plant \a [name] in [get_area(src)] [ADMIN_JMP(src.loc)]")
+ return
+
+ if(ishuman(user))
+ var/mob/living/carbon/human/human = user
+ if(!human.allow_gun_usage)
+ to_chat(user, SPAN_WARNING("Your programming prevents you from using this!"))
+ return
+
+ if(user_turf && (user_turf.density || locate(/obj/structure/fence) in user_turf))
+ to_chat(user, SPAN_WARNING("You can't plant a mine here."))
+ return
+
+ if(Adjacent(/obj/item/explosive/mine)) // bit more strict on this than normal mines
+ to_chat(user, SPAN_WARNING("Too close to another mine! Plant it somewhere less obvious."))
+ return
+
+ user.visible_message(SPAN_NOTICE("[user] starts deploying [src]."),
+ SPAN_NOTICE("You switch [src] into landmine mode and start placing it..."))
+ playsound(user.loc, 'sound/effects/thud.ogg', 40)
+ if(!do_after(user, 5 SECONDS * user.get_skill_duration_multiplier(SKILL_CONSTRUCTION), INTERRUPT_ALL|BEHAVIOR_IMMOBILE, BUSY_ICON_BUILD))
+ to_chat(user, SPAN_NOTICE("You stop planting."))
+ return
+
+ user.visible_message(SPAN_NOTICE("[user] finishes deploying [src]."),
+ SPAN_NOTICE("You finish deploying [src]."))
+ var/obj/item/explosive/mine/sebb/planted = new /obj/item/explosive/mine/sebb(get_turf(user))
+ planted.activate_sensors()
+ planted.iff_signal = user.faction // assuring IFF is set
+ planted.pixel_x += rand(-5, 5)
+ planted.pixel_y += rand(-5, 5)
+ qdel(src)
+
+/obj/item/explosive/grenade/sebb/activate()
+ ..()
+ var/beeplen = 6 // Actual length of the sound rounded up to nearest decisecond
+ var/soundtime = det_time - beeplen
+ if(det_time < beeplen) // just play sound if detonation shorter than the sound
+ playsound(loc, 'sound/effects/sebb_explode.ogg', 90, 0, 10)
+ else
+ addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(playsound), loc, 'sound/effects/sebb_beep.ogg', 60, 0, 10), soundtime)
+
+
+
+/obj/item/explosive/grenade/sebb/prime()
+ var/datum/effect_system/spark_spread/sparka = new
+ var/turf/sebb_turf = get_turf(src)
+ var/list/full_range = oview(range, src) // Fill a list of stuff in the range so we won't have to spam oview
+ new /obj/effect/overlay/temp/sebb(sebb_turf)
+
+ playsound(src.loc, 'sound/effects/sebb_explode.ogg', 90, 0, 10)
+
+ for(var/obj/structure/machinery/defenses/sentry/sentry_stun in full_range)
+ sentry_stun.sentry_range = 0 // Temporarily "disable" the sentry by killing its range then setting it back.
+ new /obj/effect/overlay/temp/elec_arc(get_turf(sentry_stun)) // sprites are meh but we need visual indication that the sentry was messed up
+ addtimer(VARSET_CALLBACK(sentry_stun, sentry_range, initial(sentry_stun.sentry_range)), 5 SECONDS) // assure to set it back
+ sentry_stun.visible_message(SPAN_DANGER("[src]'s screen flickes violently as it's shocked!"))
+ sentry_stun.visible_message(SPAN_DANGER("[src] says \"ERROR: Fire control system resetting due to critical voltage flucuation!\""))
+ sparka.set_up(1, 1, sentry_stun)
+ sparka.start()
+
+ for(var/turf/turf in full_range)
+ if(prob(8))
+ var/datum/effect_system/spark_spread/sparkTurf = new //using a different spike system because the spark system doesn't like when you reuse it for differant things
+ sparkTurf.set_up(1, 1, turf)
+ sparkTurf.start()
+ if(prob(10))
+ new /obj/effect/overlay/temp/emp_sparks(turf)
+
+ for(var/mob/living/carbon/mob in full_range) // no legacy mob support
+
+ var/mob_dist = get_dist(src, mob) // Distance from mob
+
+ /**
+ * Damage equation: damage - (mob distance * falloff_dam_reduction_mult)
+ * Example: A marine is 3 tiles out, the distance (3) is multiplied by falloff_dam_reduction_mult to get falloff.
+ * The raw damage is minused by falloff to get actual damage
+ */
+
+ var/falloff = mob_dist * falloff_dam_reduction_mult
+ var/damage_applied = damage - falloff // Final damage applied after falloff calc
+ sparka.set_up(1, 1, mob)
+ sparka.start()
+ shake_camera(mob, 1, 1)
+ if(ishuman(mob))
+ var/mob/living/carbon/human/shocked_human = mob
+ if(isspeciessynth(shocked_human)) // Massive overvoltage to ungrounded robots is pretty bad
+ shocked_human.Stun(1 + (damage_applied/40))
+ damage_applied *= 1.5
+ new /obj/effect/overlay/temp/elec_arc(get_turf(shocked_human))
+ to_chat(mob, SPAN_HIGHDANGER("All of your systems jam up as your main bus is overvolted by [damage_applied*2] volts."))
+ mob.visible_message(SPAN_WARNING("[mob] seizes up from the elctric shock"))
+ shocked_human.take_overall_armored_damage(damage_applied, ARMOR_ENERGY, BURN, 90) // 90% chance to be on additional limbs
+ shocked_human.make_dizzy(damage_applied)
+ mob.apply_stamina_damage(damage_applied*human_stam_dam_factor) // Stamina damage
+ shocked_human.emote("pain")
+ else //nonhuman damage + slow
+ mob.apply_damage(damage_applied, BURN)
+ if((mob_dist < (range-3))) // 2 tiles around small superslow
+ mob.Superslow(2)
+ mob.Slow(damage_applied/11)
+
+ if(mob_dist < 1) // Range based stuff, standing ontop of the equivalent of a canned lighting bolt should mess you up.
+ mob.Superslow(3) // Note that humans will likely be in stamcrit so it's always worse for them when ontop of it and we can just balancing it on xenos.
+ mob.eye_blurry = damage_applied/4
+ mob.Daze(1)
+ else if((mob_dist < (range-1)) && (mob.mob_size < MOB_SIZE_XENO_VERY_SMALL)) // Flicker stun humans that are closer to the grenade and larvas too.
+ mob.apply_effect(1 + (damage_applied/100),WEAKEN) // 1 + damage/40
+ mob.eye_blurry = damage_applied/8
+
+ else
+ to_chat(mob, SPAN_HIGHDANGER("Your entire body seizes up as a powerful shock courses through it!"))
+
+
+ new /obj/effect/overlay/temp/emp_sparks(mob)
+ mob.make_jittery(damage_applied*2)
+ empulse(src, 1, 2) // mini EMP
+ qdel(src)
+
+
+/obj/item/explosive/grenade/sebb/primed
+ desc = "A G2 Electroshock Grenade, looks like it's quite angry! Oh shit!"
+ det_time = 7 // 0.7 seconds to blow up. We want them to get caught if they go through.
+
+/obj/item/explosive/grenade/sebb/primed/Initialize()
+ . = ..()
+ src.visible_message(SPAN_HIGHDANGER("[src] pops out of the ground!"))
+ activate()
+
+/obj/effect/overlay/temp/sebb
+ icon = 'icons/effects/sebb.dmi'
+ icon_state = "sebb_explode"
+ layer = ABOVE_LIGHTING_PLANE
+ pixel_x = -175 // We need these offsets to force center the sprite because BYOND is dumb
+ pixel_y = -175
+ appearance_flags = RESET_COLOR
+
/*
//================================================
Nerve Gas Grenades
diff --git a/code/game/objects/items/explosives/mine.dm b/code/game/objects/items/explosives/mine.dm
index 45065a2de1de..6e7aa2bdccc3 100644
--- a/code/game/objects/items/explosives/mine.dm
+++ b/code/game/objects/items/explosives/mine.dm
@@ -14,6 +14,7 @@
throw_speed = SPEED_VERY_FAST
unacidable = TRUE
flags_atom = FPRINT|CONDUCT
+ antigrief_protection = TRUE
allowed_sensors = list(/obj/item/device/assembly/prox_sensor)
max_container_volume = 120
reaction_limits = list( "max_ex_power" = 105, "base_ex_falloff" = 60, "max_ex_shards" = 32,
@@ -71,7 +72,12 @@
if(active || user.action_busy)
return
- user.visible_message(SPAN_NOTICE("[user] starts deploying [src]."), \
+ if(antigrief_protection && user.faction == FACTION_MARINE && explosive_antigrief_check(src, user))
+ to_chat(user, SPAN_WARNING("\The [name]'s safe-area accident inhibitor prevents you from planting!"))
+ msg_admin_niche("[key_name(user)] attempted to plant \a [name] in [get_area(src)] [ADMIN_JMP(src.loc)]")
+ return
+
+ user.visible_message(SPAN_NOTICE("[user] starts deploying [src]."),
SPAN_NOTICE("You start deploying [src]."))
if(!do_after(user, 40, INTERRUPT_NO_NEEDHAND, BUSY_ICON_HOSTILE))
user.visible_message(SPAN_NOTICE("[user] stops deploying [src]."), \
@@ -317,3 +323,19 @@
customizable = TRUE
matter = list("metal" = 3750)
has_blast_wave_dampener = TRUE
+
+/obj/item/explosive/mine/sebb
+ name = "\improper G2 Electroshock grenade"
+ icon_state = "grenade_sebb_planted"
+ desc = "A G2 electroshock grenade planted as a landmine."
+ pixel_y = -5
+ anchored = TRUE // this is supposed to be planeted already when spawned
+
+/obj/item/explosive/mine/sebb/disarm()
+ . = ..()
+ new /obj/item/explosive/grenade/sebb(get_turf(src))
+ qdel(src)
+
+/obj/item/explosive/mine/sebb/prime()
+ new /obj/item/explosive/grenade/sebb/primed(get_turf(src))
+ qdel(src)
diff --git a/code/game/objects/items/hoverpack.dm b/code/game/objects/items/hoverpack.dm
index 02a2d4be779a..65406eb15dc6 100644
--- a/code/game/objects/items/hoverpack.dm
+++ b/code/game/objects/items/hoverpack.dm
@@ -208,6 +208,7 @@
return TRUE
/datum/action/item_action/hover/action_activate()
+ . = ..()
var/mob/living/carbon/human/H = owner
if(H.selected_ability == src)
to_chat(H, "You will no longer use [name] with \
diff --git a/code/game/objects/items/reagent_containers/reagent_container.dm b/code/game/objects/items/reagent_containers/reagent_container.dm
index 5207df4ca7bb..37029ff247d6 100644
--- a/code/game/objects/items/reagent_containers/reagent_container.dm
+++ b/code/game/objects/items/reagent_containers/reagent_container.dm
@@ -109,5 +109,6 @@
button.overlays += IMG
/datum/action/item_action/reagent_container/set_transfer_amount/action_activate()
+ . = ..()
var/obj/item/reagent_container/cont = holder_item
cont.set_APTFT()
diff --git a/code/game/objects/items/stacks/flags.dm b/code/game/objects/items/stacks/flags.dm
index 484d2779f5f8..14833812b06c 100644
--- a/code/game/objects/items/stacks/flags.dm
+++ b/code/game/objects/items/stacks/flags.dm
@@ -76,3 +76,208 @@
newflag.icon_state = "[newflag.base_state]_open"
newflag.visible_message("[user] plants [newflag] firmly in the ground.")
src.use(1)
+
+
+/// PLANTABLE FLAG
+
+/obj/structure/flag/plantable
+ name = "flag"
+ desc = "A flag of something. This one looks like you could dismantle it."
+ icon = 'icons/obj/structures/plantable_flag.dmi'
+ pixel_x = 9 // All flags need to be offset to the right by 9 to be centered.
+ layer = ABOVE_XENO_LAYER
+ health = 150
+ unacidable = TRUE
+
+ /// The typepath for the flag item that gets spawned when the flag is taken down.
+ var/flag_type = /obj/item/flag/plantable
+ /// Used to limit the spam of the warcry_extra_sound
+ COOLDOWN_DECLARE(warcry_cooldown_struc)
+
+/obj/structure/flag/plantable/attack_hand(mob/user)
+ ..()
+ disassemble(user, flag_type)
+
+/// Proc for dismantling the flag into an item that can be picked up.
+/obj/structure/flag/plantable/proc/disassemble(mob/user, flag_type)
+ if(user.action_busy)
+ return
+
+ user.visible_message(SPAN_NOTICE("[user] starts taking [src] down..."), SPAN_NOTICE("You start taking [src] down..."))
+
+ playsound(loc, 'sound/effects/flag_raising.ogg', 30)
+ if(!do_after(user, 6 SECONDS, INTERRUPT_ALL, BUSY_ICON_GENERIC) || QDELETED(src))
+ return
+
+ playsound(loc, 'sound/effects/flag_raised.ogg', 30)
+ user.visible_message(SPAN_NOTICE("[user] starts takes [src] down!"), SPAN_NOTICE("You take [src] down!"))
+ var/obj/item/flag/plantable/flag_item = new flag_type(loc)
+ user.put_in_hands(flag_item)
+ COOLDOWN_START(flag_item, warcry_cooldown_item, COOLDOWN_TIMELEFT(src, warcry_cooldown_struc))
+ qdel(src)
+
+/// Proc for when the flag gets forcefully dismantled (due to general damage, explosions, etc.)
+/obj/structure/flag/plantable/proc/demolish(flag_type)
+ playsound(loc, 'sound/effects/flag_raised.ogg', 30)
+ visible_message(SPAN_WARNING("[src] crumples to the ground!"))
+ var/obj/item/flag/plantable/flag_item = new flag_type(loc)
+ COOLDOWN_START(flag_item, warcry_cooldown_item, COOLDOWN_TIMELEFT(src, warcry_cooldown_struc))
+ qdel(src)
+
+// Procs for handling damage.
+/obj/structure/flag/plantable/update_health(damage)
+ if(damage)
+ health -= damage
+ if(health <= 0)
+ demolish(flag_type)
+
+/obj/structure/flag/plantable/ex_act(severity)
+ if(health <= 0)
+ return
+ update_health(severity)
+
+/obj/structure/flag/plantable/attack_alien(mob/living/carbon/xenomorph/xeno)
+ if(xeno.a_intent == INTENT_HARM)
+ if(unslashable)
+ return
+ xeno.animation_attack_on(src)
+ playsound(loc, 'sound/effects/metalhit.ogg', 25, 1)
+ xeno.visible_message(SPAN_DANGER("[xeno] slashes [src]!"), SPAN_DANGER("We slash [src]!"), null, 5, CHAT_TYPE_XENO_COMBAT)
+ update_health(rand(xeno.melee_damage_lower, xeno.melee_damage_upper))
+ return XENO_ATTACK_ACTION
+ else
+ to_chat(xeno, SPAN_WARNING("We stare at [src] cluelessly."))
+ return XENO_NONCOMBAT_ACTION
+
+/obj/structure/flag/plantable/bullet_act(obj/projectile/bullet)
+ bullet_ping(bullet)
+ visible_message(SPAN_DANGER("[src] is hit by [bullet]!"), null, 4, CHAT_TYPE_TAKING_HIT)
+ update_health(bullet.damage)
+ return TRUE
+
+/obj/structure/flag/plantable/attackby(obj/item/weapon, mob/living/user)
+ if(!indestructible)
+ visible_message(SPAN_DANGER("[src] has been hit by [user] with [weapon]!"), null, 5, CHAT_TYPE_MELEE_HIT)
+ user.animation_attack_on(src)
+ playsound(loc, 'sound/effects/metalhit.ogg', 25, 1)
+ update_health(weapon.force * weapon.demolition_mod)
+
+/obj/item/flag/plantable
+ name = "plantable flag"
+ desc = "A flag of something. This one looks ready to be planted into the ground."
+ w_class = SIZE_LARGE
+ throw_range = 2
+ icon = 'icons/obj/structures/plantable_flag.dmi'
+ inhand_x_dimension = 64
+ inhand_y_dimension = 64
+ force = 15
+ throwforce = 5
+ hitsound = "swing_hit"
+ unacidable = TRUE
+ indestructible = TRUE
+ item_icons = list(
+ WEAR_L_HAND = 'icons/mob/humans/onmob/items_lefthand_64.dmi',
+ WEAR_R_HAND = 'icons/mob/humans/onmob/items_righthand_64.dmi'
+ )
+
+ /// The typepath of the flag structure that gets spawned when the flag is planted.
+ var/flag_type = /obj/structure/flag/plantable
+ /// Used to check if nearby mobs belong to a faction when calculating for the stronger warcry.
+ var/faction
+ /// Does the flag play a unique warcry when planted? (Only while on harm intent.)
+ var/play_warcry = FALSE
+ /// The warcry's sound path.
+ var/warcry_sound
+ /// When there are more than 14 allies nearby, play this stronger warcry.
+ var/warcry_extra_sound
+ /// How many nearby allies do we need for the stronger warcry to be played?
+ var/allies_required = 14
+ /// Used to limit the spam of the warcry_extra_sound
+ COOLDOWN_DECLARE(warcry_cooldown_item)
+
+/obj/item/flag/plantable/get_examine_text(mob/user)
+ . = ..()
+ if(play_warcry && user.faction == faction)
+ . += SPAN_NOTICE("Planting the flag while in HARM intent will cause you to bellow out a rallying warcry!")
+
+/// Proc for turning the flag item into a structure.
+/obj/item/flag/plantable/proc/plant_flag(mob/living/user, play_warcry = FALSE, warcry_sound, warcry_extra_sound, faction)
+ if(user.action_busy)
+ return
+
+ if(SSinterior.in_interior(user))
+ to_chat(usr, SPAN_WARNING("There's no way to plant [src] in here!"))
+ return
+
+ var/turf/turf_to_plant = get_step(user, user.dir)
+ if(istype(turf_to_plant, /turf/open))
+ var/turf/open/floor = turf_to_plant
+ if(!floor.allow_construction || istype(floor, /turf/open/space))
+ to_chat(user, SPAN_WARNING("You cannot deploy [src] here, find a more secure surface!"))
+ return
+ else
+ to_chat(user, SPAN_WARNING("[turf_to_plant] is blocking you from deploying [src]!"))
+ return
+
+ for(var/obj/object in turf_to_plant)
+ if(object.density)
+ to_chat(usr, SPAN_WARNING("You need a clear, open area to plant [src], something is blocking the way in front of you!"))
+ return
+
+ user.visible_message(SPAN_NOTICE("[user] starts planting [src] into the ground..."), SPAN_NOTICE("You start planting [src] into the ground..."))
+ playsound(user, 'sound/effects/flag_raising.ogg', 30)
+ if(!do_after(user, 6 SECONDS, INTERRUPT_ALL, BUSY_ICON_GENERIC))
+ return
+
+ user.visible_message(SPAN_NOTICE("[user] plants [src] into the ground!"), SPAN_NOTICE("You plant [src] into the ground!"))
+ var/obj/structure/flag/plantable/planted_flag = new flag_type(turf_to_plant)
+
+ // If there are more than 14 allies nearby, play a stronger rallying cry.
+ // Otherwise, play the default warcry sound if there is one. If not, play a generic flag raising sfx.
+ if(play_warcry && user.faction == faction && user.a_intent == INTENT_HARM)
+ var/allies_nearby = 0
+ if(COOLDOWN_FINISHED(src, warcry_cooldown_item))
+ for(var/mob/living/carbon/human in orange(planted_flag, 7))
+ if(human.is_dead() || human.faction != faction)
+ continue
+ allies_nearby++
+
+ user.show_speech_bubble("warcry")
+ if(allies_nearby >= allies_required)
+ playsound(user, warcry_extra_sound, 40)
+ // Start a cooldown on the flag structure. This way we can keep track of the cooldown when the flag is hoisted and taken down.
+ COOLDOWN_START(planted_flag, warcry_cooldown_struc, 90 SECONDS)
+ user.manual_emote("shouts an invigorating rallying cry!")
+ else
+ playsound(user, warcry_sound, 30)
+ user.manual_emote("shouts an inspiring cry!")
+ // Ditto. If the cooldown isn't finished we have to transfer the leftover time to the structure.
+ COOLDOWN_START(planted_flag, warcry_cooldown_struc, COOLDOWN_TIMELEFT(src, warcry_cooldown_item))
+ else
+ playsound(loc, 'sound/effects/flag_raised.ogg', 30)
+
+ qdel(src)
+
+/obj/item/flag/plantable/attack_self(mob/user)
+ ..()
+ plant_flag(user, play_warcry, warcry_sound, warcry_extra_sound, faction)
+
+// UNITED AMERICAS FLAG //
+//////////////////////////
+
+/obj/item/flag/plantable/ua
+ name = "\improper United Americas flag"
+ desc = "The flag of the United Americas. This one looks ready to be planted into the ground."
+ icon = 'icons/obj/structures/plantable_flag.dmi'
+ icon_state = "flag_ua"
+ flag_type = /obj/structure/flag/plantable/ua
+ faction = FACTION_MARINE
+ play_warcry = TRUE
+ warcry_sound = 'sound/effects/flag_warcry_ua.ogg'
+ warcry_extra_sound = 'sound/effects/flag_warcry_ua_extra.ogg'
+
+/obj/structure/flag/plantable/ua
+ name = "\improper United Americas flag"
+ desc = "The flag of the United Americas. Semper fi."
+ icon_state = "flag_ua_planted"
+ flag_type = /obj/item/flag/plantable/ua
diff --git a/code/game/objects/items/storage/backpack.dm b/code/game/objects/items/storage/backpack.dm
index baa91db19396..06636d2c3f76 100644
--- a/code/game/objects/items/storage/backpack.dm
+++ b/code/game/objects/items/storage/backpack.dm
@@ -268,6 +268,7 @@
return TRUE
/datum/action/item_action/specialist/santabag/action_activate()
+ . = ..()
var/obj/item/storage/backpack/santabag/santa_bag = holder_item
santa_bag.refill_santa_bag(owner)
update_button_icon()
@@ -537,6 +538,7 @@ GLOBAL_LIST_EMPTY_TYPED(radio_packs, /obj/item/storage/backpack/marine/satchel/r
button.overlays += IMG
/datum/action/item_action/rto_pack/use_phone/action_activate()
+ . = ..()
for(var/obj/item/storage/backpack/marine/satchel/rto/radio_backpack in owner)
radio_backpack.use_phone(owner)
return
@@ -870,6 +872,7 @@ GLOBAL_LIST_EMPTY_TYPED(radio_packs, /obj/item/storage/backpack/marine/satchel/r
return TRUE
/datum/action/item_action/specialist/toggle_cloak/action_activate()
+ . = ..()
var/obj/item/storage/backpack/marine/satchel/scout_cloak/SC = holder_item
SC.camouflage()
diff --git a/code/game/objects/items/storage/large_holster.dm b/code/game/objects/items/storage/large_holster.dm
index 02983e1552ed..d8e1ee51d0d8 100644
--- a/code/game/objects/items/storage/large_holster.dm
+++ b/code/game/objects/items/storage/large_holster.dm
@@ -375,6 +375,7 @@
return TRUE
/datum/action/item_action/specialist/toggle_fuel/action_activate()
+ . = ..()
var/obj/item/storage/large_holster/fuelpack/FP = holder_item
if (!istype(FP))
return
diff --git a/code/game/objects/items/tanks/jetpack.dm b/code/game/objects/items/tanks/jetpack.dm
deleted file mode 100644
index 3a5afef6cf1e..000000000000
--- a/code/game/objects/items/tanks/jetpack.dm
+++ /dev/null
@@ -1,85 +0,0 @@
-//This file was auto-corrected by findeclaration.exe on 25.5.2012 20:42:32
-
-/obj/item/tank/jetpack
- name = "Jetpack (Empty)"
- desc = "A tank of compressed gas for use as propulsion in zero-gravity areas. Use with caution."
- icon_state = "jetpack"
- w_class = SIZE_LARGE
- item_state = "jetpack"
- distribute_pressure = ONE_ATMOSPHERE*O2STANDARD
- var/datum/effect_system/ion_trail_follow/ion_trail
- var/on = 0
- var/stabilization_on = 0
- var/volume_rate = 500 //Needed for borg jetpack transfer
- actions_types = list(/datum/action/item_action)
-
-/obj/item/tank/jetpack/Initialize()
- . = ..()
- src.ion_trail = new /datum/effect_system/ion_trail_follow()
- src.ion_trail.set_up(src)
-
-/obj/item/tank/jetpack/Destroy()
- QDEL_NULL(ion_trail)
- return ..()
-
-
-/obj/item/tank/jetpack/verb/toggle_rockets()
- set name = "Toggle Jetpack Stabilization"
- set category = "Object"
- set src in usr
- src.stabilization_on = !( src.stabilization_on )
- to_chat(usr, "You toggle the stabilization [stabilization_on? "on":"off"].")
-
-/obj/item/tank/jetpack/verb/toggle()
- set name = "Toggle Jetpack"
- set category = "Object"
- set src in usr
- on = !on
- if(on)
- icon_state = "[icon_state]-on"
- ion_trail.start()
- else
- icon_state = initial(icon_state)
- ion_trail.stop()
-
- if (ismob(usr))
- var/mob/M = usr
- M.update_inv_back()
-
- for(var/X in actions)
- var/datum/action/A = X
- A.update_button_icon()
-
-/obj/item/tank/jetpack/proc/allow_thrust(num, mob/living/user)
- if(!(src.on))
- return 0
-
- if(pressure > 5)
- return 1
- else
- ion_trail.stop()
- return 0
-
-
-/obj/item/tank/jetpack/ui_action_click()
- toggle()
-
-
-/obj/item/tank/jetpack/void
- name = "Void Jetpack (Oxygen)"
- desc = "It works well in a void."
- icon_state = "jetpack-void"
- item_state = "jetpack-void"
-
-/obj/item/tank/jetpack/oxygen
- name = "Jetpack (Oxygen)"
- desc = "A tank of compressed oxygen for use as propulsion in zero-gravity areas. Use with caution."
- icon_state = "jetpack"
- item_state = "jetpack"
-
-/obj/item/tank/jetpack/carbondioxide
- name = "Jetpack (Carbon Dioxide)"
- desc = "A tank of compressed carbon dioxide for use as propulsion in zero-gravity areas. Painted black to indicate that it should not be used as a source for internals."
- distribute_pressure = 0
- icon_state = "jetpack-black"
- item_state = "jetpack-black"
diff --git a/code/game/objects/items/tools/kitchen_tools.dm b/code/game/objects/items/tools/kitchen_tools.dm
index d6473b156a67..a4c4925fba2c 100644
--- a/code/game/objects/items/tools/kitchen_tools.dm
+++ b/code/game/objects/items/tools/kitchen_tools.dm
@@ -48,7 +48,7 @@
var/fullness = M.nutrition + (M.reagents.get_reagent_amount("nutriment") * 25)
if(fullness > NUTRITION_HIGH)
to_chat(user, SPAN_WARNING("[user == M ? "You" : "They"] don't feel like eating more right now."))
- return ..()
+ return
reagents.set_source_mob(user)
reagents.trans_to_ingest(M, reagents.total_volume)
if(M == user)
diff --git a/code/game/objects/objs.dm b/code/game/objects/objs.dm
index f93f2dab0984..2dc064aa86d4 100644
--- a/code/game/objects/objs.dm
+++ b/code/game/objects/objs.dm
@@ -416,7 +416,7 @@
var/offset_x = worn_x_dimension
var/offset_y = worn_y_dimension
- if(inhands)
+ if(inhands == 1 || inhands == 0)
offset_x = inhand_x_dimension
offset_y = inhand_y_dimension
diff --git a/code/game/objects/structures/crates_lockers/closets/malfunction.dm b/code/game/objects/structures/crates_lockers/closets/malfunction.dm
deleted file mode 100644
index 704e2c79157a..000000000000
--- a/code/game/objects/structures/crates_lockers/closets/malfunction.dm
+++ /dev/null
@@ -1,16 +0,0 @@
-
-/obj/structure/closet/malf/suits
- desc = "It's a storage unit for operational gear."
- icon_state = "syndicate"
- icon_closed = "syndicate"
- icon_opened = "syndicate_open"
-
-/obj/structure/closet/malf/suits/Initialize()
- . = ..()
- new /obj/item/tank/jetpack/void(src)
- new /obj/item/clothing/mask/breath(src)
- new /obj/item/clothing/head/helmet/space/uscm(src)
- new /obj/item/clothing/suit/space/uscm(src)
- new /obj/item/tool/crowbar(src)
- new /obj/item/cell(src)
- new /obj/item/device/multitool(src)
diff --git a/code/game/objects/structures/crates_lockers/closets/utility_closets.dm b/code/game/objects/structures/crates_lockers/closets/utility_closets.dm
index b000fd5733a2..0bf39322d107 100644
--- a/code/game/objects/structures/crates_lockers/closets/utility_closets.dm
+++ b/code/game/objects/structures/crates_lockers/closets/utility_closets.dm
@@ -23,7 +23,7 @@
. = ..()
#ifndef UNIT_TESTS
- switch (pickweight(list("small" = 55, "aid" = 25, "tank" = 10, "both" = 10, "nothing" = 0, "delete" = 0)))
+ switch (pick_weight(list("small" = 55, "aid" = 25, "tank" = 10, "both" = 10, "nothing" = 1, "delete" = 1)))
#else
var/test = "both"
switch (test) // We don't want randomness in tests
diff --git a/code/game/objects/structures/crates_lockers/crates.dm b/code/game/objects/structures/crates_lockers/crates.dm
index d891119a8404..8a10cd4d93ea 100644
--- a/code/game/objects/structures/crates_lockers/crates.dm
+++ b/code/game/objects/structures/crates_lockers/crates.dm
@@ -277,13 +277,6 @@
name = "RCD crate"
desc = "A crate for the storage of the RCD."
-/obj/structure/closet/crate/rcd/Initialize()
- . = ..()
- new /obj/item/ammo_rcd(src)
- new /obj/item/ammo_rcd(src)
- new /obj/item/ammo_rcd(src)
- new /obj/item/device/rcd(src)
-
/obj/structure/closet/crate/freezer/rations //Fpr use in the escape shuttle
desc = "A crate of emergency rations."
name = "Emergency Rations"
diff --git a/code/game/objects/structures/roof.dm b/code/game/objects/structures/roof.dm
new file mode 100644
index 000000000000..e302133f0c9a
--- /dev/null
+++ b/code/game/objects/structures/roof.dm
@@ -0,0 +1,165 @@
+/obj/structure/roof
+ name = "roof"
+ desc = "A roof"
+ icon = 'icons/turf/almayer.dmi'
+ icon_state = "plating_catwalk"
+ density = FALSE
+ layer = ABOVE_XENO_LAYER
+ health = 6000
+ var/image/under_image //immage that is used when there is mob on connected node, displayed only to mobs under it not others
+ var/image/normal_image
+ var/datum/roof_master_node/linked_master
+ var/lazy_nodes = TRUE //if roof should create nodes that watch around it on spawn
+
+
+/obj/structure/roof/Initialize()
+ . = ..()
+ under_image = image(icon, src, icon_state, layer = layer)
+ under_image.alpha = 127
+
+ normal_image = image(icon, src, icon_state, layer = layer)
+
+ icon_state = null
+
+ RegisterSignal(SSdcs, COMSIG_GLOB_MOB_LOGGED_IN, PROC_REF(add_default_image))
+
+ for(var/icon in GLOB.player_list)
+ add_default_image(SSdcs, icon)
+ if(lazy_nodes) //creates new node on each surounding tile if there is not one already
+ var/obj/effect/roof_node/neighbor = locate() in loc
+ if(!neighbor)
+ neighbor = new(loc)
+ for(var/direction in CARDINAL_ALL_DIRS)
+ var/adjacent_loc = get_step(src, direction)
+ neighbor = locate() in adjacent_loc
+ if(!neighbor)
+ neighbor = new(adjacent_loc)
+ return INITIALIZE_HINT_LATELOAD
+
+/obj/structure/roof/LateInitialize() //we use late init to allow for lazy nodes to spawn first on mapload
+ . = ..()
+ if(linked_master)
+ return
+ for(var/direction in CARDINAL_ALL_DIRS) //this searches if there is lattice with master already, to work with runtime creation
+ for(var/obj/structure/roof/roof in get_step(src,direction))
+ if(roof.linked_master)
+ roof.linked_master.connect(loc)
+ return
+ var/datum/roof_master_node/roof_master_node = new(loc) //no master and no lattice to connect to, create new master
+ roof_master_node.connect(loc)
+
+/obj/structure/roof/Destroy(force, ...)
+ if(linked_master)
+ linked_master.remove_roof(src)
+ for(var/icon in GLOB.player_list)
+ var/mob/mob = icon
+ mob.client.images -= normal_image
+ return ..()
+
+/obj/structure/roof/proc/add_default_image(subsystem, mob/mob)
+ SIGNAL_HANDLER
+ mob.client.images += normal_image
+
+/obj/structure/roof/proc/link_master(datum/roof_master_node/master) //performs bfs and connects to master
+ if(linked_master != null)
+ return
+ master.connected_roof += src
+ linked_master = master
+ for(var/direction in CARDINAL_ALL_DIRS)
+ for(var/obj/structure/roof/roof in get_step(src,direction))
+ roof.link_master(master)
+
+
+/obj/effect/roof_node //used for observing if mob is near the roof
+ name = "roof_node"
+ anchored = TRUE
+ mouse_opacity = MOUSE_OPACITY_TRANSPARENT
+ invisibility = 101
+ unacidable = TRUE
+ var/datum/roof_master_node/linked_master
+
+/obj/effect/roof_node/Crossed(atom/movable/mover, target_dir)
+ if(!linked_master)
+ return
+ if(isliving(mover))
+ var/mob/living/mob = mover
+ linked_master.add_under_roof(mob)
+
+/obj/effect/roof_node/Destroy(force, ...)
+ if(linked_master)
+ if(linked_master.connected_nodes)
+ linked_master.connected_nodes -= src
+ return ..()
+
+/obj/effect/roof_node/proc/link_master(datum/roof_master_node/master) //performs bfs and connects to master
+ if(linked_master)
+ return
+ master.connected_nodes += src
+ linked_master = master
+ for(var/direction in CARDINAL_ALL_DIRS)
+ for(var/obj/effect/roof_node/node in get_step(src,direction))
+ node.link_master(master)
+
+
+/datum/roof_master_node //maintains one block of roof
+ var/list/connected_nodes = list()
+ var/list/connected_roof = list()
+ var/list/mobs_under = list()
+ var/location
+
+/datum/roof_master_node/Destroy(force, ...)
+ if(connected_nodes)
+ for(var/obj/effect/roof_node/roof_node in connected_nodes)
+ qdel(roof_node)
+ if(connected_nodes)
+ for(var/obj/structure/roof/roof in connected_roof)
+ qdel(roof)
+ return ..()
+
+/datum/roof_master_node/proc/add_under_roof(mob/living/living) //mob crossed connected node
+ if(living in mobs_under)
+ return
+ mobs_under += living
+ RegisterSignal(living, COMSIG_PARENT_QDELETING, PROC_REF(remove_under_roof))
+ RegisterSignal(living, COMSIG_MOB_LOGGED_IN, PROC_REF(add_client))
+ RegisterSignal(living, COMSIG_MOVABLE_MOVED, PROC_REF(check_under_roof))
+
+ if(living.client)
+ add_client(living)
+
+/datum/roof_master_node/proc/add_client(mob/living/mob)
+ SIGNAL_HANDLER
+ for(var/obj/structure/roof/roof in connected_roof)
+ mob.client.images -= roof.normal_image
+ mob.client.images += roof.under_image
+
+/datum/roof_master_node/proc/remove_under_roof(mob/living/living) //mob is no longer under roof
+ SIGNAL_HANDLER
+ if(living.client)
+ for(var/obj/structure/roof/roof in connected_roof)
+ living.client.images -= roof.under_image
+ roof.add_default_image(SSdcs, living)
+ mobs_under -= living
+ UnregisterSignal(living, list(
+ COMSIG_PARENT_QDELETING,
+ COMSIG_MOB_LOGGED_IN,
+ COMSIG_MOVABLE_MOVED,
+ ))
+
+/datum/roof_master_node/proc/check_under_roof(mob/living/living) //check if the mob is under connected roof
+ SIGNAL_HANDLER
+ for(var/obj/effect/roof_node/roof in connected_nodes)
+ if(living.loc == roof.loc)
+ return
+ remove_under_roof(living)
+
+/datum/roof_master_node/proc/connect(location)
+ for(var/obj/effect/roof_node/node in location)
+ node.link_master(src)
+ for(var/obj/structure/roof/roof in location)
+ roof.link_master(src)
+
+/datum/roof_master_node/proc/remove_roof(obj/structure/roof/roof) //roof tile got removed
+ connected_roof -= roof
+ if(!length(connected_roof))
+ qdel(src)
diff --git a/code/game/objects/structures/stool_bed_chair_nest/wheelchair.dm b/code/game/objects/structures/stool_bed_chair_nest/wheelchair.dm
index 986ae99739aa..f71882374518 100644
--- a/code/game/objects/structures/stool_bed_chair_nest/wheelchair.dm
+++ b/code/game/objects/structures/stool_bed_chair_nest/wheelchair.dm
@@ -36,12 +36,12 @@
move_delay += 4 //harder to move a wheelchair with a single hand
working_hands--
else if((left_hand.status & LIMB_BROKEN) && !(left_hand.status & LIMB_SPLINTED))
- move_delay++
+ move_delay ++
if(!right_hand || (right_hand.status & LIMB_DESTROYED))
move_delay += 4
working_hands--
else if((right_hand.status & LIMB_BROKEN) && !(right_hand.status & LIMB_SPLINTED))
- move_delay += 2
+ move_delay++
if(!working_hands)
return // No hands to drive your chair? Tough luck!
if(driver.pulling && driver.pulling.drag_delay && driver.get_pull_miltiplier()) //Dragging stuff can slow you down a bit.
diff --git a/code/game/objects/structures/stool_bed_chair_nest/xeno_nest.dm b/code/game/objects/structures/stool_bed_chair_nest/xeno_nest.dm
index c3f0b97e509a..68b899f78f15 100644
--- a/code/game/objects/structures/stool_bed_chair_nest/xeno_nest.dm
+++ b/code/game/objects/structures/stool_bed_chair_nest/xeno_nest.dm
@@ -245,6 +245,7 @@
do_buckle(mob, user)
ADD_TRAIT(mob, TRAIT_NESTED, TRAIT_SOURCE_BUCKLE)
+ ADD_TRAIT(mob, TRAIT_NO_STRAY, TRAIT_SOURCE_BUCKLE)
SEND_SIGNAL(mob, COMSIG_MOB_NESTED, user)
if(!human)
@@ -275,6 +276,7 @@
buckled_mob.pixel_y = 0
buckled_mob.old_y = 0
REMOVE_TRAIT(buckled_mob, TRAIT_NESTED, TRAIT_SOURCE_BUCKLE)
+ REMOVE_TRAIT(buckled_mob, TRAIT_NO_STRAY, TRAIT_SOURCE_BUCKLE)
var/mob/living/carbon/human/buckled_human = buckled_mob
var/mob/dead/observer/G = ghost_of_buckled_mob
diff --git a/code/game/objects/structures/vulture_spotter.dm b/code/game/objects/structures/vulture_spotter.dm
index dc341edf0446..dcbfd88c9c08 100644
--- a/code/game/objects/structures/vulture_spotter.dm
+++ b/code/game/objects/structures/vulture_spotter.dm
@@ -313,6 +313,7 @@
tripod = WEAKREF(spotting_tripod)
/datum/action/vulture_tripod_unscope/action_activate()
+ . = ..()
if(!tripod)
return
diff --git a/code/game/supplyshuttle.dm b/code/game/supplyshuttle.dm
index c5a675b531d2..8974eb36187f 100644
--- a/code/game/supplyshuttle.dm
+++ b/code/game/supplyshuttle.dm
@@ -532,7 +532,7 @@ GLOBAL_DATUM_INIT(supply_controller, /datum/controller/supply, new())
for(var/datum/supply_packs_asrs/crate in cratelist)
var/weight = (floor(10000/crate.cost))
weighted_crate_list[crate] = weight
- return pickweight(weighted_crate_list)
+ return pick_weight(weighted_crate_list)
//To stop things being sent to centcomm which should not be sent to centcomm. Recursively checks for these types.
/datum/controller/supply/proc/forbidden_atoms_check(atom/A)
@@ -1115,7 +1115,7 @@ GLOBAL_DATUM_INIT(supply_controller, /datum/controller/supply, new())
else if (href_list["rreq"])
var/ordernum = text2num(href_list["rreq"])
temp = "Invalid Request.
"
- for(var/i=1, length(i<=GLOB.supply_controller.requestlist), i++)
+ for(var/i=1, i<=length(GLOB.supply_controller.requestlist), i++)
var/datum/supply_order/SO = GLOB.supply_controller.requestlist[i]
if(SO.ordernum == ordernum)
GLOB.supply_controller.requestlist.Cut(i,i+1)
diff --git a/code/game/turfs/transit.dm b/code/game/turfs/transit.dm
index 00175ac5e365..5b4645805d3b 100644
--- a/code/game/turfs/transit.dm
+++ b/code/game/turfs/transit.dm
@@ -138,6 +138,12 @@
clear_active_explosives()
ADD_TRAIT(src, TRAIT_IMMOBILIZED, TRAIT_SOURCE_DROPSHIP_INTERACTION)
ADD_TRAIT(src, TRAIT_UNDENSE, TRAIT_SOURCE_DROPSHIP_INTERACTION)
+ ADD_TRAIT(src, TRAIT_NO_STRAY, TRAIT_SOURCE_DROPSHIP_INTERACTION)
+ RegisterSignal(src, COMSIG_MOVABLE_FORCEMOVE_PRE_CROSSED, PROC_REF(cancel_cross))
+ RegisterSignal(src, list(
+ COMSIG_LIVING_FLAMER_FLAMED,
+ COMSIG_LIVING_PREIGNITION
+ ), PROC_REF(cancel_fire))
var/image/cables = image('icons/obj/structures/droppod_32x64.dmi', src, "chute_cables_static")
overlays += cables
var/image/chute = image('icons/obj/structures/droppod_64x64.dmi', src, "chute_static")
@@ -163,8 +169,18 @@
return
REMOVE_TRAIT(src, TRAIT_IMMOBILIZED, TRAIT_SOURCE_DROPSHIP_INTERACTION)
REMOVE_TRAIT(src, TRAIT_UNDENSE, TRAIT_SOURCE_DROPSHIP_INTERACTION)
+ REMOVE_TRAIT(src, TRAIT_NO_STRAY, TRAIT_SOURCE_DROPSHIP_INTERACTION)
+ UnregisterSignal(src, list(
+ COMSIG_MOVABLE_FORCEMOVE_PRE_CROSSED,
+ COMSIG_LIVING_FLAMER_FLAMED,
+ COMSIG_LIVING_PREIGNITION
+ ))
overlays -= cables
overlays -= chute
+ for(var/atom/movable/atom in loc)
+ if(atom == src)
+ continue
+ atom.Cross(src)
/atom/movable/proc/clear_active_explosives()
for(var/obj/item/explosive/explosive in contents)
@@ -232,6 +248,13 @@
death(last_damage_data)
status_flags |= PERMANENTLY_DEAD
+/atom/movable/proc/cancel_cross()
+ SIGNAL_HANDLER
+ return COMPONENT_IGNORE_CROSS
+
+/atom/movable/proc/cancel_fire()
+ SIGNAL_HANDLER
+ return COMPONENT_NO_BURN
/turf/open/space/transit/dropship/alamo
shuttle_tag = DROPSHIP_ALAMO
diff --git a/code/game/verbs/who.dm b/code/game/verbs/who.dm
deleted file mode 100644
index 3a9274dbec62..000000000000
--- a/code/game/verbs/who.dm
+++ /dev/null
@@ -1,209 +0,0 @@
-/client/verb/who()//likely don't touch any... this is easy can die. (:troll_fale:)
- set name = "Who"
- set category = "OOC"
-
- var/list/counted_humanoids = list(
- "Observers" = 0,
- "Admin observers" = 0,
- "Humans" = 0,
- "Infected humans" = 0,
- FACTION_MARINE = 0,
- "USCM Marines" = 0,
- "Lobby" = 0,
-
- FACTION_YAUTJA = 0,
- "Infected preds" = 0,
-
- FACTION_PMC = 0,
- FACTION_CLF = 0,
- FACTION_UPP = 0,
- FACTION_TWE = 0,
- FACTION_FREELANCER = 0,
- FACTION_SURVIVOR = 0,
- FACTION_WY_DEATHSQUAD = 0,
- FACTION_COLONIST = 0,
- FACTION_MERCENARY = 0,
- FACTION_DUTCH = 0,
- FACTION_HEFA = 0,
- FACTION_GLADIATOR = 0,
- FACTION_PIRATE = 0,
- FACTION_PIZZA = 0,
- FACTION_SOUTO = 0,
-
- FACTION_NEUTRAL = 0,
-
- FACTION_ZOMBIE = 0
- )
-
- var/list/counted_xenos = list()
-
- var/players = length(GLOB.clients)
-
- var/dat = "