"
if(!players)
players = GLOB.player_list
+ // SKYRAT EDIT CHANGE BEGIN - ANNOUNCEMENTS
+ /* Original
+ if(target.client.prefs.read_preference(/datum/preference/toggle/sound_announcements))
+ SEND_SOUND(target, sound_to_play)
+ */
+ if(!sound)
+ sound = SSstation.announcer.get_rand_alert_sound()
+ else if(SSstation.announcer.event_sounds[sound])
+ var/list/announcer_key = SSstation.announcer.event_sounds[sound]
+ sound = pick(announcer_key)
var/sound_to_play = sound(sound)
for(var/mob/target in players)
if(!isnewplayer(target) && target.can_hear())
to_chat(target, announcement)
- if(target.client.prefs.read_preference(/datum/preference/toggle/sound_announcements))
- SEND_SOUND(target, sound_to_play)
+
+ alert_sound_to_playing(sound_to_play, players = players)
+ // SKYRAT EDIT CHANGE END - ANNOUNCEMENTS
/proc/print_command_report(text = "", title = null, announce=TRUE)
if(!title)
@@ -86,11 +99,11 @@
if(announce)
priority_announce("A report has been downloaded and printed out at all communications consoles.", "Incoming Classified Message", SSstation.announcer.get_rand_report_sound(), has_important_message = TRUE)
- var/datum/comm_message/M = new
- M.title = title
- M.content = text
+ var/datum/comm_message/message = new
+ message.title = title
+ message.content = text
- SScommunications.send_message(M)
+ SScommunications.send_message(message)
/**
* Sends a minor annoucement to players.
@@ -122,8 +135,20 @@
if(!target.can_hear())
continue
- to_chat(target, "[span_minorannounce("[title] [message]")] ")
+ to_chat(target, " [span_minorannounce(title)] ")
+ to_chat(target, "[span_minoralert(message)]
")
+ // SKYRAT EDIT CHANGE START - ANNOUNCEMENTS
+ /* Original
if(should_play_sound && target.client?.prefs.read_preference(/datum/preference/toggle/sound_announcements))
var/sound_to_play = sound_override || (alert ? 'sound/misc/notice1.ogg' : 'sound/misc/notice2.ogg')
SEND_SOUND(target, sound(sound_to_play))
-*/ // SKYRAT EDIT REMOVAL END
+ */
+ if(sound_override)
+ if(SSstation.announcer.event_sounds[sound_override])
+ var/list/announcement_key = SSstation.announcer.event_sounds[sound_override]
+ sound_override = pick(announcement_key)
+
+ var/sound_to_play = sound_override || (alert ? 'modular_skyrat/modules/alerts/sound/alerts/alert1.ogg' : 'sound/misc/notice2.ogg')
+ alert_sound_to_playing(sound_to_play, players = players)
+ // SKYRAT EDIT CHANGE END - ANNOUNCEMENTS
+
diff --git a/code/_globalvars/lists/mobs.dm b/code/_globalvars/lists/mobs.dm
index 5ee1f4e6439..135fb00dea1 100644
--- a/code/_globalvars/lists/mobs.dm
+++ b/code/_globalvars/lists/mobs.dm
@@ -12,6 +12,37 @@ GLOBAL_LIST_INIT(dangerous_turfs, typecacheof(list(
/turf/open/space,
/turf/open/openspace)))
+/// List of types of abstract mob which shouldn't usually exist in the world on its own if we're spawning random mobs
+GLOBAL_LIST_INIT(abstract_mob_types, list(
+ /mob/living/basic/blob_minion,
+ /mob/living/basic/construct,
+ /mob/living/basic/heretic_summon,
+ /mob/living/basic/mining,
+ /mob/living/basic/pet,
+ /mob/living/basic,
+ /mob/living/basic/spider,
+ /mob/living/carbon/alien/adult,
+ /mob/living/carbon/alien,
+ /mob/living/carbon/human/consistent,
+ /mob/living/carbon/human/dummy/consistent,
+ /mob/living/carbon/human/dummy,
+ /mob/living/carbon/human/species,
+ /mob/living/carbon,
+ /mob/living/silicon,
+ /mob/living/simple_animal/bot,
+ /mob/living/simple_animal/hostile/asteroid/elite,
+ /mob/living/simple_animal/hostile/asteroid,
+ /mob/living/simple_animal/hostile/construct,
+ /mob/living/simple_animal/hostile/guardian,
+ /mob/living/simple_animal/hostile/megafauna,
+ /mob/living/simple_animal/hostile/mimic, // Cannot exist if spawned without being passed an item reference
+ /mob/living/simple_animal/hostile/retaliate,
+ /mob/living/simple_animal/hostile,
+ /mob/living/simple_animal/pet,
+ /mob/living/simple_animal/soulscythe, // As mimic, can't exist if spawned outside an item
+ /mob/living/simple_animal,
+))
+
//Since it didn't really belong in any other category, I'm putting this here
//This is for procs to replace all the goddamn 'in world's that are chilling around the code
diff --git a/code/_globalvars/traits.dm b/code/_globalvars/traits.dm
index da03288a0ca..8fd711279a5 100644
--- a/code/_globalvars/traits.dm
+++ b/code/_globalvars/traits.dm
@@ -216,6 +216,7 @@ GLOBAL_LIST_INIT(traits_by_type, list(
"TRAIT_XRAY_HEARING" = TRAIT_XRAY_HEARING,
"TRAIT_TENTACLE_IMMUNE" = TRAIT_TENTACLE_IMMUNE,
"TRAIT_OVERWATCH_IMMUNE" = TRAIT_OVERWATCH_IMMUNE,
+ "TRAIT_NO_TRANSFORM" = TRAIT_NO_TRANSFORM,
),
/obj/item/bodypart = list(
"TRAIT_PARALYSIS" = TRAIT_PARALYSIS,
diff --git a/code/_onclick/click.dm b/code/_onclick/click.dm
index 4b24b20c846..667611f1075 100644
--- a/code/_onclick/click.dm
+++ b/code/_onclick/click.dm
@@ -277,7 +277,10 @@
/**
- * Translates into [atom/proc/attack_hand], etc.
+ * UnarmedAttack: The higest level of mob click chain discounting click itself.
+ *
+ * This handles, just "clicking on something" without an item. It translates
+ * into [atom/proc/attack_hand], [atom/proc/attack_animal] etc.
*
* Note: proximity_flag here is used to distinguish between normal usage (flag=1),
* and usage when clicking on things telekinetically (flag=0). This proc will
diff --git a/code/_onclick/other_mobs.dm b/code/_onclick/other_mobs.dm
index aeb42361c05..ce281bb4ecf 100644
--- a/code/_onclick/other_mobs.dm
+++ b/code/_onclick/other_mobs.dm
@@ -9,33 +9,66 @@
else if (secondary_result != SECONDARY_ATTACK_CALL_NORMAL)
CRASH("resolve_right_click_attack (probably attack_hand_secondary) did not return a SECONDARY_ATTACK_* define.")
-/*
- Humans:
- Adds an exception for gloves, to allow special glove types like the ninja ones.
+/**
+ * Checks if this mob is in a valid state to punch someone.
+ */
+/mob/living/proc/can_unarmed_attack()
+ return !HAS_TRAIT(src, TRAIT_HANDS_BLOCKED)
+
+/mob/living/carbon/can_unarmed_attack()
+ . = ..()
+ if(!.)
+ return FALSE
- Otherwise pretty standard.
-*/
-/mob/living/carbon/human/UnarmedAttack(atom/A, proximity_flag, list/modifiers)
- if(HAS_TRAIT(src, TRAIT_HANDS_BLOCKED))
- if(src == A)
- check_self_for_injuries()
- return
if(!has_active_hand()) //can't attack without a hand.
var/obj/item/bodypart/check_arm = get_active_hand()
if(check_arm?.bodypart_disabled)
to_chat(src, span_warning("Your [check_arm.name] is in no condition to be used."))
- return
+ return FALSE
to_chat(src, span_notice("You look at your arm and sigh."))
- return
+ return FALSE
- //This signal is needed to prevent gloves of the north star + hulk.
- if(SEND_SIGNAL(src, COMSIG_HUMAN_EARLY_UNARMED_ATTACK, A, proximity_flag, modifiers) & COMPONENT_CANCEL_ATTACK_CHAIN)
- return
- SEND_SIGNAL(src, COMSIG_HUMAN_MELEE_UNARMED_ATTACK, A, proximity_flag, modifiers)
+ return TRUE
+
+/mob/living/UnarmedAttack(atom/attack_target, proximity_flag, list/modifiers)
+ // The sole reason for this signal needing to exist is making FotNS incompatible with Hulk.
+ // Note that it is send before [proc/can_unarmed_attack] is called, keep this in mind.
+ var/sigreturn = SEND_SIGNAL(src, COMSIG_LIVING_EARLY_UNARMED_ATTACK, attack_target, modifiers)
+ if(sigreturn & COMPONENT_CANCEL_ATTACK_CHAIN)
+ return TRUE
+ if(sigreturn & COMPONENT_SKIP_ATTACK)
+ return FALSE
- if(!right_click_attack_chain(A, modifiers) && !dna?.species?.spec_unarmedattack(src, A, modifiers)) //Because species like monkeys dont use attack hand
- A.attack_hand(src, modifiers)
+ if(!can_unarmed_attack(attack_target))
+ return FALSE
+
+ sigreturn = SEND_SIGNAL(src, COMSIG_LIVING_UNARMED_ATTACK, attack_target, proximity_flag, modifiers)
+ if(sigreturn & COMPONENT_CANCEL_ATTACK_CHAIN)
+ return TRUE
+ if(sigreturn & COMPONENT_SKIP_ATTACK)
+ return FALSE
+
+ if(!right_click_attack_chain(attack_target, modifiers))
+ resolve_unarmed_attack(attack_target, modifiers)
+ return TRUE
+
+/mob/living/carbon/human/UnarmedAttack(atom/attack_target, proximity_flag, list/modifiers)
+ // Humans can always check themself regardless of having their hands blocked or w/e
+ if(src == attack_target)
+ check_self_for_injuries()
+ return TRUE
+
+ return ..()
+
+/mob/living/carbon/resolve_unarmed_attack(atom/attack_target, list/modifiers)
+ return attack_target.attack_paw(src, modifiers)
+
+/mob/living/carbon/human/resolve_unarmed_attack(atom/attack_target, list/modifiers)
+ if(!ISADVANCEDTOOLUSER(src))
+ return ..()
+
+ return attack_target.attack_hand(src, modifiers)
/mob/living/carbon/human/resolve_right_click_attack(atom/target, list/modifiers)
return target.attack_hand_secondary(src, modifiers)
@@ -119,17 +152,6 @@
Animals & All Unspecified
*/
-// If the UnarmedAttack chain is blocked
-#define LIVING_UNARMED_ATTACK_BLOCKED(target_atom) (HAS_TRAIT(src, TRAIT_HANDS_BLOCKED) \
- || SEND_SIGNAL(src, COMSIG_LIVING_UNARMED_ATTACK, target_atom, proximity_flag, modifiers) & COMPONENT_CANCEL_ATTACK_CHAIN)
-
-/mob/living/UnarmedAttack(atom/attack_target, proximity_flag, list/modifiers)
- if(LIVING_UNARMED_ATTACK_BLOCKED(attack_target))
- return FALSE
- if(!right_click_attack_chain(attack_target, modifiers))
- resolve_unarmed_attack(attack_target, modifiers)
- return TRUE
-
/**
* Called when the unarmed attack hasn't been stopped by the LIVING_UNARMED_ATTACK_BLOCKED macro or the right_click_attack_chain proc.
* This will call an attack proc that can vary from mob type to mob type on the target.
@@ -295,8 +317,6 @@
GiveTarget(attack_target)
INVOKE_ASYNC(src, PROC_REF(AttackingTarget), attack_target)
-#undef LIVING_UNARMED_ATTACK_BLOCKED
-
/*
New Players:
Have no reason to click on anything at all.
diff --git a/code/controllers/subsystem/events.dm b/code/controllers/subsystem/events.dm
index c5223ca6963..98f847e4be6 100644
--- a/code/controllers/subsystem/events.dm
+++ b/code/controllers/subsystem/events.dm
@@ -2,15 +2,19 @@ SUBSYSTEM_DEF(events)
name = "Events"
init_order = INIT_ORDER_EVENTS
runlevels = RUNLEVEL_GAME
-
- var/list/control = list() //list of all datum/round_event_control. Used for selecting events based on weight and occurrences.
- var/list/running = list() //list of all existing /datum/round_event
+ ///list of all datum/round_event_control. Used for selecting events based on weight and occurrences.
+ var/list/control = list()
+ ///list of all existing /datum/round_event currently being run.
+ var/list/running = list()
+ ///cache of currently running events, for lag checking.
var/list/currentrun = list()
-
- var/scheduled = 0 //The next world.time that a naturally occuring random event can be selected.
- var/frequency_lower = 1800 //3 minutes lower bound.
- var/frequency_upper = 6000 //10 minutes upper bound. Basically an event will happen every 3 to 10 minutes.
-
+ ///The next world.time that a naturally occuring random event can be selected.
+ var/scheduled = 0
+ ///The lower bound for how soon another random event can be scheduled.
+ var/frequency_lower = 2.5 MINUTES
+ ///The upper bound for how soon another random event can be scheduled.
+ var/frequency_upper = 7 MINUTES
+ ///Will wizard events be included in the event pool?
var/wizardmode = FALSE
var/list/previously_run = list() //SKYRAT EDIT ADDITION
@@ -61,50 +65,45 @@ SUBSYSTEM_DEF(events)
if(!CONFIG_GET(flag/allow_random_events))
return
- var/players_amt = get_active_player_count(alive_check = 1, afk_check = 1, human_check = 1)
+ var/players_amt = get_active_player_count(alive_check = TRUE, afk_check = TRUE, human_check = TRUE)
// Only alive, non-AFK human players count towards this.
- var/sum_of_weights = 0
- for(var/datum/round_event_control/E in control)
- if(!E.can_spawn_event(players_amt))
+ var/list/event_roster = list()
+
+ for(var/datum/round_event_control/event_to_check in control)
+ if(!event_to_check.can_spawn_event(players_amt))
continue
//SKYRAT EDIT ADDITION
- if(threat_override && !E.alert_observers)
+ if(threat_override && !event_to_check.alert_observers)
continue
//SKYRAT EDIT END
- if(E.weight < 0) //for round-start events etc.
- var/res = TriggerEvent(E)
+ if(event_to_check.weight < 0) //for round-start events etc.
+ var/res = TriggerEvent(event_to_check)
if(res == EVENT_INTERRUPTED)
continue //like it never happened
if(res == EVENT_CANT_RUN)
return
- sum_of_weights += E.weight
-
- sum_of_weights = rand(0,sum_of_weights) //reusing this variable. It now represents the 'weight' we want to select
-
- for(var/datum/round_event_control/E in control)
- if(!E.can_spawn_event(players_amt))
- continue
- sum_of_weights -= E.weight
+ else
+ event_roster[event_to_check] = event_to_check.weight
- if(sum_of_weights <= 0) //we've hit our goal
- if(TriggerEvent(E))
- return
+ var/datum/round_event_control/event_to_run = pick_weight(event_roster)
+ TriggerEvent(event_to_run)
-/datum/controller/subsystem/events/proc/TriggerEvent(datum/round_event_control/E)
- . = E.preRunEvent()
+///Does the last pre-flight checks for the passed event, and runs it if the event is ready.
+/datum/controller/subsystem/events/proc/TriggerEvent(datum/round_event_control/event_to_trigger)
+ . = event_to_trigger.preRunEvent()
if(. == EVENT_CANT_RUN)//we couldn't run this event for some reason, set its max_occurrences to 0
- E.max_occurrences = 0
+ event_to_trigger.max_occurrences = 0
else if(. == EVENT_READY)
- E.run_event(random = TRUE)
-
+ event_to_trigger.run_event(random = TRUE)
+///Toggles whether or not wizard events will be in the event pool, and sends a notification to the admins.
/datum/controller/subsystem/events/proc/toggleWizardmode()
wizardmode = !wizardmode
message_admins("Summon Events has been [wizardmode ? "enabled, events will occur every [SSevents.frequency_lower / 600] to [SSevents.frequency_upper / 600] minutes" : "disabled"]!")
log_game("Summon Events was [wizardmode ? "enabled" : "disabled"]!")
-
+///Sets the event frequency bounds back to their initial value.
/datum/controller/subsystem/events/proc/resetFrequency()
frequency_lower = initial(frequency_lower)
frequency_upper = initial(frequency_upper)
diff --git a/code/controllers/subsystem/ticker.dm b/code/controllers/subsystem/ticker.dm
index 1aaf01ee85f..97b2c85b2aa 100644
--- a/code/controllers/subsystem/ticker.dm
+++ b/code/controllers/subsystem/ticker.dm
@@ -300,7 +300,7 @@ SUBSYSTEM_DEF(ticker)
to_chat(world, span_notice("and..."))
for(var/holidayname in GLOB.holidays)
var/datum/holiday/holiday = GLOB.holidays[holidayname]
- to_chat(world, "
[holiday.greet()]
")
+ to_chat(world, span_info(holiday.greet()))
PostSetup()
diff --git a/code/datums/actions/mobs/fire_breath.dm b/code/datums/actions/mobs/fire_breath.dm
index 254a6081425..45b6538c018 100644
--- a/code/datums/actions/mobs/fire_breath.dm
+++ b/code/datums/actions/mobs/fire_breath.dm
@@ -2,63 +2,121 @@
name = "Fire Breath"
button_icon = 'icons/effects/magic.dmi'
button_icon_state = "fireball"
- desc = "Allows you to shoot fire towards a target."
+ desc = "Breathe a line of flames towards the target."
cooldown_time = 3 SECONDS
/// The range of the fire
var/fire_range = 15
/// The sound played when you use this ability
var/fire_sound = 'sound/magic/fireball.ogg'
- /// If the fire should be icey fire
- var/ice_breath = FALSE
+ /// Time to wait between spawning each fire turf
+ var/fire_delay = 1.5 DECISECONDS
+ /// How hot is our fire
+ var/fire_temperature = DRAKE_FIRE_TEMP
+ /// 'How much' fire do we expose the turf to?
+ var/fire_volume = DRAKE_FIRE_EXPOSURE
+ /// How much damage do you take when engulfed?
+ var/fire_damage = 20
+ /// How much damage to mechs take when engulfed?
+ var/mech_damage = 45
/datum/action/cooldown/mob_cooldown/fire_breath/Activate(atom/target_atom)
- StartCooldown(360 SECONDS, 360 SECONDS)
attack_sequence(target_atom)
StartCooldown()
return TRUE
+/// Apply our specific fire breathing shape, in proc form so we can override it in subtypes
/datum/action/cooldown/mob_cooldown/fire_breath/proc/attack_sequence(atom/target)
playsound(owner.loc, fire_sound, 200, TRUE)
- fire_line(target, 0)
+ fire_line(target)
+/// Breathe fire in a line towards the target, optionally rotated at an offset from the target
/datum/action/cooldown/mob_cooldown/fire_breath/proc/fire_line(atom/target, offset)
- SLEEP_CHECK_DEATH(0, owner)
- var/list/turfs = line_target(offset, fire_range, target)
- // This proc sleeps
- INVOKE_ASYNC(GLOBAL_PROC, GLOBAL_PROC_REF(dragon_fire_line), owner, /* burn_turfs = */ turfs, /* frozen = */ ice_breath)
+ if (isnull(target))
+ return
+ var/turf/target_turf = get_ranged_target_turf_direct(owner, target, fire_range, offset)
+ var/list/turfs = get_line(owner, target_turf) - get_turf(owner)
+ INVOKE_ASYNC(src, PROC_REF(progressive_fire_line), turfs)
-/datum/action/cooldown/mob_cooldown/fire_breath/proc/line_target(offset, range, atom/target)
- if(!target)
+/// Creates fire with a delay on the list of targetted turfs
+/datum/action/cooldown/mob_cooldown/fire_breath/proc/progressive_fire_line(list/burn_turfs)
+ if (QDELETED(owner) || owner.stat == DEAD)
return
- var/turf/T = get_ranged_target_turf_direct(owner, target, range, offset)
- return (get_line(owner, T) - get_turf(owner))
+ // Guys we have already hit, no double dipping
+ var/list/hit_list = list(owner) // also don't burn ourselves
+ for(var/turf/target_turf in burn_turfs)
+ if (target_turf.is_blocked_turf(exclude_mobs = TRUE))
+ return
+ burn_turf(target_turf, hit_list, owner)
+ sleep(fire_delay)
+
+/// Finally spawn the actual fire, spawns the fire hotspot in case you want to recolour it or something
+/datum/action/cooldown/mob_cooldown/fire_breath/proc/burn_turf(turf/fire_turf, list/hit_list, mob/living/source)
+ var/obj/effect/hotspot/fire_hotspot = new /obj/effect/hotspot(fire_turf)
+ fire_turf.hotspot_expose(fire_temperature, fire_volume, TRUE)
+
+ for(var/mob/living/barbecued in fire_turf.contents)
+ if(barbecued in hit_list)
+ continue
+ hit_list |= barbecued
+ on_burn_mob(barbecued, source)
+
+ for(var/obj/vehicle/sealed/mecha/robotron in fire_turf.contents)
+ if(robotron in hit_list)
+ continue
+ hit_list |= robotron
+ robotron.take_damage(mech_damage, BURN, FIRE)
+ return fire_hotspot
+
+/// Do something unpleasant to someone we set on fire
+/datum/action/cooldown/mob_cooldown/fire_breath/proc/on_burn_mob(mob/living/barbecued, mob/living/source)
+ to_chat(barbecued, span_userdanger("You are burned by [source]'s fire breath!"))
+ barbecued.adjustFireLoss(fire_damage)
+
+/// Shoot three lines of fire in a sort of fork pattern approximating a cone
/datum/action/cooldown/mob_cooldown/fire_breath/cone
name = "Fire Cone"
- desc = "Allows you to shoot fire towards a target with surrounding lines of fire."
+ desc = "Breathe several lines of fire directed at a target."
/// The angles relative to the target that shoot lines of fire
var/list/angles = list(-40, 0, 40)
/datum/action/cooldown/mob_cooldown/fire_breath/cone/attack_sequence(atom/target)
playsound(owner.loc, fire_sound, 200, TRUE)
for(var/offset in angles)
- INVOKE_ASYNC(src, PROC_REF(fire_line), target, offset)
+ fire_line(target, offset)
+/// Shoot fire in a whole bunch of directions
/datum/action/cooldown/mob_cooldown/fire_breath/mass_fire
name = "Mass Fire"
button_icon = 'icons/effects/fire.dmi'
button_icon_state = "1"
- desc = "Allows you to shoot fire in all directions."
+ desc = "Breathe flames in all directions."
cooldown_time = 3 SECONDS
+ click_to_activate = FALSE
+ /// How many fire lines do we produce to turn a full circle?
+ var/sectors = 12
+ /// How long do we wait between each spin?
+ var/breath_delay = 2.5 SECONDS
+ /// How many full circles do we perform?
+ var/total_spins = 3
+
+/datum/action/cooldown/mob_cooldown/fire_breath/mass_fire/Activate(atom/target_atom)
+ target_atom = get_step(owner, owner.dir) // Just shoot it forwards, we don't need to click on someone for this one
+ return ..()
/datum/action/cooldown/mob_cooldown/fire_breath/mass_fire/attack_sequence(atom/target)
- shoot_mass_fire(target, 12, 2.5 SECONDS, 3)
+ var/queued_spins = 0
+ for (var/i in 1 to total_spins)
+ var/delay = queued_spins * breath_delay
+ queued_spins++
+ addtimer(CALLBACK(src, PROC_REF(fire_spin), target, queued_spins), delay)
-/datum/action/cooldown/mob_cooldown/fire_breath/mass_fire/proc/shoot_mass_fire(atom/target, spiral_count, delay_time, times)
- SLEEP_CHECK_DEATH(0, owner)
- for(var/i = 1 to times)
- playsound(owner.loc, fire_sound, 200, TRUE)
- var/increment = 360 / spiral_count
- for(var/j = 1 to spiral_count)
- INVOKE_ASYNC(src, PROC_REF(fire_line), target, j * increment + i * increment / 2)
- SLEEP_CHECK_DEATH(delay_time, owner)
+/// Breathe fire in a circle, with a slight angle offset based on which of our several circles it is
+/datum/action/cooldown/mob_cooldown/fire_breath/mass_fire/proc/fire_spin(target, spin_count)
+ if (QDELETED(owner) || owner.stat == DEAD)
+ return // Too dead to spin
+ playsound(owner.loc, fire_sound, 200, TRUE)
+ var/angle_increment = 360 / sectors
+ var/additional_offset = spin_count * angle_increment / 2
+ for (var/i in 1 to sectors)
+ fire_line(target, (angle_increment * i) + (additional_offset))
diff --git a/code/datums/actions/mobs/mobcooldown.dm b/code/datums/actions/mobs/mobcooldown.dm
index 17561f2f45a..a495859494f 100644
--- a/code/datums/actions/mobs/mobcooldown.dm
+++ b/code/datums/actions/mobs/mobcooldown.dm
@@ -3,7 +3,7 @@
button_icon = 'icons/mob/actions/actions_items.dmi'
button_icon_state = "sniper_zoom"
desc = "Click this ability to attack."
- check_flags = AB_CHECK_CONSCIOUS
+ check_flags = AB_CHECK_CONSCIOUS | AB_CHECK_INCAPACITATED
cooldown_time = 5 SECONDS
text_cooldown = TRUE
click_to_activate = TRUE
diff --git a/code/datums/actions/mobs/sneak.dm b/code/datums/actions/mobs/sneak.dm
index 3fed4f4d500..738bb7b70cf 100644
--- a/code/datums/actions/mobs/sneak.dm
+++ b/code/datums/actions/mobs/sneak.dm
@@ -5,7 +5,6 @@
button_icon_state = "sniper_zoom"
background_icon_state = "bg_alien"
overlay_icon_state = "bg_alien_border"
- check_flags = AB_CHECK_CONSCIOUS | AB_CHECK_INCAPACITATED | AB_CHECK_INCAPACITATED
cooldown_time = 0.5 SECONDS
melee_cooldown_time = 0 SECONDS
click_to_activate = FALSE
diff --git a/code/datums/ai/babies/babies_behaviors.dm b/code/datums/ai/babies/babies_behaviors.dm
index 716922cc7b2..553b192a180 100644
--- a/code/datums/ai/babies/babies_behaviors.dm
+++ b/code/datums/ai/babies/babies_behaviors.dm
@@ -32,7 +32,7 @@
partner = other
//shyness check. we're not shy in front of things that share a faction with us.
- else if(isliving(other) && !pawn_mob.faction_check_mob(other))
+ else if(isliving(other) && !pawn_mob.faction_check_atom(other))
finish_action(controller, FALSE)
return
diff --git a/code/datums/ai/basic_mobs/targetting_datums/basic_targetting_datum.dm b/code/datums/ai/basic_mobs/targetting_datums/basic_targetting_datum.dm
index bc4a554bc02..1d6fde335cb 100644
--- a/code/datums/ai/basic_mobs/targetting_datums/basic_targetting_datum.dm
+++ b/code/datums/ai/basic_mobs/targetting_datums/basic_targetting_datum.dm
@@ -81,7 +81,7 @@
/datum/targetting_datum/basic/proc/faction_check(datum/ai_controller/controller, mob/living/living_mob, mob/living/the_target)
if (controller.blackboard[BB_ALWAYS_IGNORE_FACTION] || controller.blackboard[BB_TEMPORARILY_IGNORE_FACTION])
return FALSE
- return living_mob.faction_check_mob(the_target, exact_match = check_factions_exactly)
+ return living_mob.faction_check_atom(the_target, exact_match = check_factions_exactly)
/// Subtype more forgiving for items.
/// Careful, this can go wrong and keep a mob hyper-focused on an item it can't lose aggro on
diff --git a/code/datums/ai/generic/find_and_set.dm b/code/datums/ai/generic/find_and_set.dm
index dc941ec33fa..43337674969 100644
--- a/code/datums/ai/generic/find_and_set.dm
+++ b/code/datums/ai/generic/find_and_set.dm
@@ -129,7 +129,7 @@
continue
if (living_pawn.see_invisible < dead_pal.invisibility)
continue
- if (!living_pawn.faction_check_mob(dead_pal))
+ if (!living_pawn.faction_check_atom(dead_pal))
continue
nearby_bodies += dead_pal
diff --git a/code/datums/components/butchering.dm b/code/datums/components/butchering.dm
index 689de30db37..183203ed709 100644
--- a/code/datums/components/butchering.dm
+++ b/code/datums/components/butchering.dm
@@ -257,16 +257,16 @@
if(!(slot & source.slot_flags))
return
butchering_enabled = TRUE
- RegisterSignal(user, COMSIG_HUMAN_EARLY_UNARMED_ATTACK, PROC_REF(butcher_target))
+ RegisterSignal(user, COMSIG_LIVING_UNARMED_ATTACK, PROC_REF(butcher_target))
///Same as disable_butchering but for worn items
/datum/component/butchering/wearable/proc/worn_disable_butchering(obj/item/source, mob/user)
SIGNAL_HANDLER
butchering_enabled = FALSE
- UnregisterSignal(user, COMSIG_HUMAN_EARLY_UNARMED_ATTACK)
+ UnregisterSignal(user, COMSIG_LIVING_UNARMED_ATTACK)
/datum/component/butchering/wearable/proc/butcher_target(mob/user, atom/target, proximity)
SIGNAL_HANDLER
if(!isliving(target))
- return
- onItemAttack(parent, target, user)
+ return NONE
+ return onItemAttack(parent, target, user)
diff --git a/code/datums/components/fantasy/suffixes.dm b/code/datums/components/fantasy/suffixes.dm
index c8809efae49..69d9d0a7ebb 100644
--- a/code/datums/components/fantasy/suffixes.dm
+++ b/code/datums/components/fantasy/suffixes.dm
@@ -103,30 +103,35 @@
. = ..()
// This is set up to be easy to add to these lists as I expect it will need modifications
var/static/list/possible_mobtypes
- if(!possible_mobtypes)
- // The base list of allowed mob/species types
- possible_mobtypes = zebra_typecacheof(list(
- /mob/living/simple_animal = TRUE,
- /mob/living/carbon = TRUE,
- /datum/species = TRUE,
- // Some types to remove them and their subtypes
- /mob/living/carbon/human/species = FALSE,
- /mob/living/simple_animal/hostile/asteroid/elite = FALSE,
- /mob/living/simple_animal/hostile/megafauna = FALSE,
- ))
- // Some particular types to disallow if they're too broad/abstract
- // Not in the above typecache generator because it includes subtypes and this doesn't.
- possible_mobtypes -= list(
- /mob/living/simple_animal/hostile,
+ if(isnull(possible_mobtypes))
+ possible_mobtypes = list()
+ var/list/mob_subtype_whitelist = list(
+ /mob/living/basic,
+ /mob/living/carbon,
+ /mob/living/simple_animal,
)
+ for(var/type in mob_subtype_whitelist)
+ possible_mobtypes += subtypesof(type)
- var/mob/picked_mobtype = pick(possible_mobtypes)
- // This works even with the species picks since we're only accessing the name
+ var/list/mob_subtype_blacklist = list(
+ /mob/living/simple_animal/hostile/asteroid/elite,
+ /mob/living/simple_animal/hostile/megafauna,
+ )
+ for(var/type in mob_subtype_blacklist)
+ possible_mobtypes -= subtypesof(type)
+
+ possible_mobtypes -= GLOB.abstract_mob_types
+ var/mob/picked_mobtype = pick(possible_mobtypes)
var/obj/item/master = comp.parent
- var/max_mobs = max(CEILING(comp.quality/2, 1), 1)
- var/spawn_delay = 300 - 30 * comp.quality
- comp.appliedComponents += master.AddComponent(/datum/component/summoning, list(picked_mobtype), 100, max_mobs, spawn_delay)
+ var/max_mobs = max(CEILING(comp.quality / 2, 1), 1)
+ var/spawn_delay = 30 SECONDS - (3 SECONDS * comp.quality)
+ comp.appliedComponents += master.AddComponent(\
+ /datum/component/summoning,\
+ mob_types = list(picked_mobtype),\
+ max_mobs = max_mobs,\
+ spawn_delay = spawn_delay,\
+ )
return "[newName] of [initial(picked_mobtype.name)] summoning"
/datum/fantasy_affix/shrapnel
diff --git a/code/datums/components/focused_attacker.dm b/code/datums/components/focused_attacker.dm
index eda6bd17979..8635973f263 100644
--- a/code/datums/components/focused_attacker.dm
+++ b/code/datums/components/focused_attacker.dm
@@ -24,12 +24,12 @@
/datum/component/focused_attacker/RegisterWithParent()
if (isliving(parent))
- RegisterSignals(parent, list(COMSIG_LIVING_UNARMED_ATTACK, COMSIG_HUMAN_MELEE_UNARMED_ATTACK), PROC_REF(pre_mob_attack))
+ RegisterSignal(parent, COMSIG_LIVING_UNARMED_ATTACK, PROC_REF(pre_mob_attack))
else
RegisterSignal(parent, COMSIG_ITEM_PRE_ATTACK, PROC_REF(pre_item_attack))
/datum/component/focused_attacker/UnregisterFromParent()
- UnregisterSignal(parent, list(COMSIG_LIVING_UNARMED_ATTACK, COMSIG_HUMAN_MELEE_UNARMED_ATTACK, COMSIG_ITEM_PRE_ATTACK))
+ UnregisterSignal(parent, list(COMSIG_LIVING_UNARMED_ATTACK, COMSIG_ITEM_PRE_ATTACK))
/// Before a mob attacks, try increasing its attack power
/datum/component/focused_attacker/proc/pre_mob_attack(mob/living/attacker, atom/target)
diff --git a/code/datums/components/mob_chain.dm b/code/datums/components/mob_chain.dm
index 8312d9d5504..2ff7c4f1967 100644
--- a/code/datums/components/mob_chain.dm
+++ b/code/datums/components/mob_chain.dm
@@ -43,7 +43,7 @@
RegisterSignal(parent, COMSIG_QDELETING, PROC_REF(on_deletion))
RegisterSignal(parent, COMSIG_MOVABLE_MOVED, PROC_REF(on_moved))
RegisterSignal(parent, COMSIG_ATOM_CAN_BE_PULLED, PROC_REF(on_pulled))
- RegisterSignals(parent, list(COMSIG_LIVING_UNARMED_ATTACK, COMSIG_HUMAN_EARLY_UNARMED_ATTACK, COMSIG_MOB_ATTACK_RANGED), PROC_REF(on_attack))
+ RegisterSignals(parent, list(COMSIG_LIVING_UNARMED_ATTACK, COMSIG_MOB_ATTACK_RANGED), PROC_REF(on_attack))
RegisterSignal(parent, COMSIG_MOVABLE_UPDATE_GLIDE_SIZE, PROC_REF(on_glide_size_changed))
if (vary_icon_state)
RegisterSignal(parent, COMSIG_ATOM_UPDATE_ICON_STATE, PROC_REF(on_update_icon_state))
@@ -61,7 +61,6 @@
COMSIG_ATOM_CAN_BE_PULLED,
COMSIG_ATOM_UPDATE_ICON_STATE,
COMSIG_CARBON_LIMB_DAMAGED,
- COMSIG_HUMAN_EARLY_UNARMED_ATTACK,
COMSIG_LIVING_ADJUST_BRUTE_DAMAGE,
COMSIG_LIVING_ADJUST_BURN_DAMAGE,
COMSIG_LIVING_ADJUST_CLONE_DAMAGE,
diff --git a/code/datums/components/plumbing/_plumbing.dm b/code/datums/components/plumbing/_plumbing.dm
index af27b4bbb46..15c9f24833b 100644
--- a/code/datums/components/plumbing/_plumbing.dm
+++ b/code/datums/components/plumbing/_plumbing.dm
@@ -114,8 +114,9 @@
//take an equal amount from each supplier
var/currentRequest
+ var/target_volume = reagents.total_volume + amount
for(var/datum/component/plumbing/give as anything in valid_suppliers)
- currentRequest = amount / suppliersLeft
+ currentRequest = (target_volume - reagents.total_volume) / suppliersLeft
give.transfer_to(src, currentRequest, reagent, net)
suppliersLeft--
@@ -131,6 +132,8 @@
else if(reagents.total_volume) //take whatever
return TRUE
+ return FALSE
+
///this is where the reagent is actually transferred and is thus the finish point of our process()
/datum/component/plumbing/proc/transfer_to(datum/component/plumbing/target, amount, reagent, datum/ductnet/net)
if(!reagents || !target || !target.reagents)
diff --git a/code/datums/components/plumbing/reaction_chamber.dm b/code/datums/components/plumbing/reaction_chamber.dm
index f728ce315e6..42b7abeb2e8 100644
--- a/code/datums/components/plumbing/reaction_chamber.dm
+++ b/code/datums/components/plumbing/reaction_chamber.dm
@@ -10,7 +10,7 @@
/datum/component/plumbing/reaction_chamber/can_give(amount, reagent, datum/ductnet/net)
. = ..()
var/obj/machinery/plumbing/reaction_chamber/reaction_chamber = parent
- if(!. || !reaction_chamber.emptying || reagents.is_reacting == TRUE)
+ if(!. || !reaction_chamber.emptying || reagents.is_reacting)
return FALSE
/datum/component/plumbing/reaction_chamber/send_request(dir)
@@ -30,7 +30,7 @@
//compute how much more is needed and round it
diff = chamber.required_reagents[required_reagent] - present_amount
- if(diff > CHEMICAL_QUANTISATION_LEVEL)
+ if(diff >= CHEMICAL_QUANTISATION_LEVEL * 10) //should be safe even after rounding
process_request(min(diff, MACHINE_REAGENT_TRANSFER), required_reagent, dir)
return
diff --git a/code/datums/components/pry_open_door.dm b/code/datums/components/pry_open_door.dm
deleted file mode 100644
index 17e445d25ca..00000000000
--- a/code/datums/components/pry_open_door.dm
+++ /dev/null
@@ -1,52 +0,0 @@
-/**
- * Attached to a basic mob.
- * Causes attacks on doors to attempt to open them.
- */
-/datum/component/pry_open_door
- /// Odds the attack opens the door
- var/open_chance
- /// Time it takes to open a door with force
- var/force_wait
-
-/datum/component/pry_open_door/Initialize(open_chance = 100, force_wait = 10 SECONDS)
- . = ..()
-
- if(!isbasicmob(parent))
- return COMPONENT_INCOMPATIBLE
- src.open_chance = open_chance
- src.force_wait = force_wait
-
-/datum/component/pry_open_door/RegisterWithParent()
- RegisterSignal(parent, COMSIG_HOSTILE_POST_ATTACKINGTARGET, PROC_REF(hostile_attackingtarget))
-
-/datum/component/pry_open_door/UnregisterFromParent()
- UnregisterSignal(parent, COMSIG_HOSTILE_POST_ATTACKINGTARGET)
-
-/datum/component/pry_open_door/proc/hostile_attackingtarget(mob/living/basic/attacker, atom/target, success)
- SIGNAL_HANDLER
-
- if(!success)
- return
- if(istype(target, /obj/machinery/door/airlock) && prob(open_chance))
- var/obj/machinery/door/airlock/airlock_target = target
- INVOKE_ASYNC(src, PROC_REF(open_door), attacker, airlock_target)
-
-/datum/component/pry_open_door/proc/open_door(mob/living/basic/attacker, obj/machinery/door/airlock/airlock_target)
- if(airlock_target.locked)
- to_chat(attacker, span_warning("The airlock's bolts prevent it from being forced!"))
- return
- else if(!airlock_target.allowed(attacker) && airlock_target.hasPower())
- attacker.visible_message(span_warning("We start forcing the [airlock_target] open."), \
- span_hear("You hear a metal screeching sound."))
- playsound(airlock_target, 'sound/machines/airlock_alien_prying.ogg', 100, TRUE)
- if(!do_after(attacker, force_wait, airlock_target))
- return
- if(airlock_target.locked)
- return
- attacker.visible_message(span_warning("We force the [airlock_target] to open."))
- airlock_target.open(BYPASS_DOOR_CHECKS)
- else if(!airlock_target.hasPower())
- attacker.visible_message(span_warning("We force the [airlock_target] to open."))
- airlock_target.open(FORCING_DOOR_CHECKS)
- else
- airlock_target.open(DEFAULT_DOOR_CHECKS)
diff --git a/code/datums/components/punchcooldown.dm b/code/datums/components/punchcooldown.dm
index 7c2ae1047fe..80a72784c09 100644
--- a/code/datums/components/punchcooldown.dm
+++ b/code/datums/components/punchcooldown.dm
@@ -1,6 +1,6 @@
///Your favourite Jojoke. Used for the gloves of the north star.
/datum/component/wearertargeting/punchcooldown
- signals = list(COMSIG_HUMAN_MELEE_UNARMED_ATTACK, COMSIG_LIVING_SLAP_MOB)
+ signals = list(COMSIG_LIVING_UNARMED_ATTACK, COMSIG_LIVING_SLAP_MOB)
mobtype = /mob/living/carbon
proctype = PROC_REF(reducecooldown)
valid_slots = list(ITEM_SLOT_GLOVES)
@@ -13,7 +13,7 @@
return
RegisterSignal(parent, COMSIG_ITEM_ATTACK_SELF, PROC_REF(changewarcry))
-///Called on COMSIG_HUMAN_MELEE_UNARMED_ATTACK. Yells the warcry and and reduces punch cooldown.
+///Called on COMSIG_LIVING_UNARMED_ATTACK. Yells the warcry and and reduces punch cooldown.
/datum/component/wearertargeting/punchcooldown/proc/reducecooldown(mob/living/carbon/M, atom/target)
if((M.combat_mode && isliving(target)) || istype(M.get_active_held_item(), /obj/item/hand_item/slapper))
M.changeNext_move(CLICK_CD_RAPID)
diff --git a/code/datums/components/riding/riding_mob.dm b/code/datums/components/riding/riding_mob.dm
index f7b48ecb01b..85aaee4ab7c 100644
--- a/code/datums/components/riding/riding_mob.dm
+++ b/code/datums/components/riding/riding_mob.dm
@@ -218,7 +218,7 @@
/datum/component/riding/creature/human/RegisterWithParent()
. = ..()
- RegisterSignal(parent, COMSIG_HUMAN_EARLY_UNARMED_ATTACK, PROC_REF(on_host_unarmed_melee))
+ RegisterSignal(parent, COMSIG_LIVING_UNARMED_ATTACK, PROC_REF(on_host_unarmed_melee))
RegisterSignal(parent, COMSIG_LIVING_SET_BODY_POSITION, PROC_REF(check_carrier_fall_over))
/datum/component/riding/creature/human/log_riding(mob/living/living_parent, mob/living/rider)
@@ -240,12 +240,13 @@
return ..()
/// If the carrier shoves the person they're carrying, force the carried mob off
-/datum/component/riding/creature/human/proc/on_host_unarmed_melee(mob/living/carbon/human/human_parent, atom/target, proximity, modifiers)
+/datum/component/riding/creature/human/proc/on_host_unarmed_melee(mob/living/source, atom/target, proximity, modifiers)
SIGNAL_HANDLER
- if(LAZYACCESS(modifiers, RIGHT_CLICK) && (target in human_parent.buckled_mobs))
+ if(LAZYACCESS(modifiers, RIGHT_CLICK) && (target in source.buckled_mobs))
force_dismount(target)
return COMPONENT_CANCEL_ATTACK_CHAIN
+ return NONE
/// If the carrier gets knocked over, force the rider(s) off and see if someone got hurt
/datum/component/riding/creature/human/proc/check_carrier_fall_over(mob/living/carbon/human/human_parent)
diff --git a/code/datums/components/shovel_hands.dm b/code/datums/components/shovel_hands.dm
index e4ee2d644d3..34eaf8a98b6 100644
--- a/code/datums/components/shovel_hands.dm
+++ b/code/datums/components/shovel_hands.dm
@@ -14,10 +14,10 @@
/datum/component/shovel_hands/RegisterWithParent()
. = ..()
- RegisterSignals(parent, list(COMSIG_LIVING_UNARMED_ATTACK, COMSIG_HUMAN_MELEE_UNARMED_ATTACK, COMSIG_HOSTILE_PRE_ATTACKINGTARGET), PROC_REF(dig))
+ RegisterSignals(parent, list(COMSIG_LIVING_UNARMED_ATTACK, COMSIG_HOSTILE_PRE_ATTACKINGTARGET), PROC_REF(dig))
/datum/component/shovel_hands/UnregisterFromParent()
- UnregisterSignal(parent, list(COMSIG_LIVING_UNARMED_ATTACK, COMSIG_HUMAN_MELEE_UNARMED_ATTACK, COMSIG_HOSTILE_PRE_ATTACKINGTARGET))
+ UnregisterSignal(parent, list(COMSIG_LIVING_UNARMED_ATTACK, COMSIG_HOSTILE_PRE_ATTACKINGTARGET))
return ..()
/datum/component/shovel_hands/Destroy(force, silent)
diff --git a/code/datums/components/shy.dm b/code/datums/components/shy.dm
index 845da7e0f7d..5743322dea1 100644
--- a/code/datums/components/shy.dm
+++ b/code/datums/components/shy.dm
@@ -46,7 +46,7 @@
/datum/component/shy/RegisterWithParent()
RegisterSignal(parent, COMSIG_MOB_CLICKON, PROC_REF(on_clickon))
RegisterSignal(parent, COMSIG_LIVING_TRY_PULL, PROC_REF(on_try_pull))
- RegisterSignals(parent, list(COMSIG_LIVING_UNARMED_ATTACK, COMSIG_HUMAN_EARLY_UNARMED_ATTACK), PROC_REF(on_unarmed_attack))
+ RegisterSignal(parent, COMSIG_LIVING_UNARMED_ATTACK, PROC_REF(on_unarmed_attack))
RegisterSignal(parent, COMSIG_TRY_STRIP, PROC_REF(on_try_strip))
RegisterSignal(parent, COMSIG_TRY_ALT_ACTION, PROC_REF(on_try_alt_action))
@@ -54,7 +54,7 @@
UnregisterSignal(parent, list(
COMSIG_MOB_CLICKON,
COMSIG_LIVING_TRY_PULL,
- COMSIG_LIVING_UNARMED_ATTACK, COMSIG_HUMAN_EARLY_UNARMED_ATTACK,
+ COMSIG_LIVING_UNARMED_ATTACK,
COMSIG_TRY_STRIP,
COMSIG_TRY_ALT_ACTION,
))
@@ -137,4 +137,3 @@
return is_shy(target) && COMPONENT_CANT_ALT_ACTION
#undef SHY_COMPONENT_CACHE_TIME
-
diff --git a/code/datums/components/shy_in_room.dm b/code/datums/components/shy_in_room.dm
index 2413c9abafd..023dbaff719 100644
--- a/code/datums/components/shy_in_room.dm
+++ b/code/datums/components/shy_in_room.dm
@@ -17,7 +17,7 @@
/datum/component/shy_in_room/RegisterWithParent()
RegisterSignal(parent, COMSIG_MOB_CLICKON, PROC_REF(on_clickon))
RegisterSignal(parent, COMSIG_LIVING_TRY_PULL, PROC_REF(on_try_pull))
- RegisterSignals(parent, list(COMSIG_LIVING_UNARMED_ATTACK, COMSIG_HUMAN_EARLY_UNARMED_ATTACK), PROC_REF(on_unarmed_attack))
+ RegisterSignal(parent, COMSIG_LIVING_UNARMED_ATTACK, PROC_REF(on_unarmed_attack))
RegisterSignal(parent, COMSIG_TRY_STRIP, PROC_REF(on_try_strip))
RegisterSignal(parent, COMSIG_TRY_ALT_ACTION, PROC_REF(on_try_alt_action))
@@ -27,7 +27,6 @@
COMSIG_MOB_CLICKON,
COMSIG_LIVING_TRY_PULL,
COMSIG_LIVING_UNARMED_ATTACK,
- COMSIG_HUMAN_EARLY_UNARMED_ATTACK,
COMSIG_TRY_STRIP,
COMSIG_TRY_ALT_ACTION,
))
@@ -73,4 +72,3 @@
/datum/component/shy_in_room/proc/on_try_alt_action(datum/source, atom/target)
SIGNAL_HANDLER
return is_shy(target) && COMPONENT_CANT_ALT_ACTION
-
diff --git a/code/datums/components/style/style.dm b/code/datums/components/style/style.dm
index 42f5de156e0..b6b9770ec48 100644
--- a/code/datums/components/style/style.dm
+++ b/code/datums/components/style/style.dm
@@ -107,7 +107,7 @@
RegisterSignal(parent, COMSIG_MOB_EMOTED("flip"), PROC_REF(on_flip))
RegisterSignal(parent, COMSIG_MOB_EMOTED("spin"), PROC_REF(on_spin))
RegisterSignal(parent, COMSIG_MOB_ITEM_ATTACK, PROC_REF(on_attack))
- RegisterSignal(parent, COMSIG_HUMAN_MELEE_UNARMED_ATTACK, PROC_REF(on_punch))
+ RegisterSignal(parent, COMSIG_LIVING_UNARMED_ATTACK, PROC_REF(on_punch))
RegisterSignal(SSdcs, COMSIG_GLOB_MOB_DEATH, PROC_REF(on_death))
RegisterSignal(parent, COMSIG_LIVING_RESONATOR_BURST, PROC_REF(on_resonator_burst))
RegisterSignal(parent, COMSIG_LIVING_PROJECTILE_PARRIED, PROC_REF(on_projectile_parry))
@@ -134,7 +134,7 @@
UnregisterSignal(parent, COMSIG_MOB_MINED)
UnregisterSignal(parent, COMSIG_MOB_APPLY_DAMAGE)
UnregisterSignal(parent, list(COMSIG_MOB_EMOTED("flip"), COMSIG_MOB_EMOTED("spin")))
- UnregisterSignal(parent, list(COMSIG_MOB_ITEM_ATTACK, COMSIG_HUMAN_MELEE_UNARMED_ATTACK))
+ UnregisterSignal(parent, list(COMSIG_MOB_ITEM_ATTACK, COMSIG_LIVING_UNARMED_ATTACK))
UnregisterSignal(SSdcs, COMSIG_GLOB_MOB_DEATH)
UnregisterSignal(parent, COMSIG_LIVING_RESONATOR_BURST)
UnregisterSignal(parent, COMSIG_LIVING_PROJECTILE_PARRIED)
diff --git a/code/datums/components/summoning.dm b/code/datums/components/summoning.dm
index 54eea882e8a..220a4baca5f 100644
--- a/code/datums/components/summoning.dm
+++ b/code/datums/components/summoning.dm
@@ -1,16 +1,32 @@
/datum/component/summoning
+ /// Types of mob we can create
var/list/mob_types = list()
- var/spawn_chance // chance for the mob to spawn on hit in percent
+ /// Percentage chance to spawn a mob
+ var/spawn_chance
+ /// Maximum mobs we can have active at once
var/max_mobs
- var/spawn_delay // delay in spawning between mobs (deciseconds)
+ /// Cooldown between spawning mobs
+ var/spawn_delay
+ /// Text to display when spawning a mob
var/spawn_text
+ /// Sound to play when spawning a mob
var/spawn_sound
+ /// Factions to assign to a summoned mob
var/list/faction
-
- var/last_spawned_time = 0
+ /// Cooldown tracker for when we can summon another mob
+ COOLDOWN_DECLARE(summon_cooldown)
+ /// List containing all of our mobs
var/list/spawned_mobs = list()
-/datum/component/summoning/Initialize(mob_types, spawn_chance=100, max_mobs=3, spawn_delay=100, spawn_text="appears out of nowhere", spawn_sound='sound/magic/summon_magic.ogg', faction)
+/datum/component/summoning/Initialize(
+ mob_types,
+ spawn_chance = 100,
+ max_mobs = 3,
+ spawn_delay = 10 SECONDS,
+ spawn_text = "appears out of nowhere",
+ spawn_sound = 'sound/magic/summon_magic.ogg',
+ list/faction,
+)
if(!isitem(parent) && !ishostile(parent) && !isgun(parent) && !ismachinery(parent) && !isstructure(parent) && !isprojectilespell(parent))
return COMPONENT_INCOMPATIBLE
@@ -54,26 +70,22 @@
do_spawn_mob(get_turf(target), firer)
/datum/component/summoning/proc/do_spawn_mob(atom/spawn_location, summoner)
- if(spawned_mobs.len >= max_mobs)
- return
- if(last_spawned_time > world.time)
+ if(length(spawned_mobs) >= max_mobs || !COOLDOWN_FINISHED(src, summon_cooldown) || !prob(spawn_chance))
return
- if(!prob(spawn_chance))
- return
- last_spawned_time = world.time + spawn_delay
+ COOLDOWN_START(src, summon_cooldown, spawn_delay)
var/chosen_mob_type = pick(mob_types)
- var/mob/living/simple_animal/L = new chosen_mob_type(spawn_location)
- if(ishostile(L))
- var/mob/living/simple_animal/hostile/H = L
- H.friends += summoner // do not attack our summon boy
- spawned_mobs += L
+ var/mob/living/summoned = new chosen_mob_type(spawn_location)
+ if(ishostile(summoned))
+ var/mob/living/simple_animal/hostile/angry_boy = summoned
+ angry_boy.friends |= summoner // do not attack our summon boy
+ spawned_mobs |= summoned
if(faction != null)
- L.faction = faction
- RegisterSignal(L, COMSIG_LIVING_DEATH, PROC_REF(on_spawned_death)) // so we can remove them from the list, etc (for mobs with corpses)
- playsound(spawn_location,spawn_sound, 50, TRUE)
- spawn_location.visible_message(span_danger("[L] [spawn_text]."))
+ summoned.faction = faction.Copy()
+ RegisterSignals(summoned, list(COMSIG_LIVING_DEATH, COMSIG_QDELETING), PROC_REF(on_spawned_death))
+ spawn_location.visible_message(span_danger("[summoned] [spawn_text]!"))
+/// When a spawned thing dies, remove it from our list
/datum/component/summoning/proc/on_spawned_death(mob/killed, gibbed)
SIGNAL_HANDLER
-
+ UnregisterSignal(killed, list(COMSIG_LIVING_DEATH, COMSIG_QDELETING))
spawned_mobs -= killed
diff --git a/code/datums/components/torn_wall.dm b/code/datums/components/torn_wall.dm
new file mode 100644
index 00000000000..ebe0fe9a0a6
--- /dev/null
+++ b/code/datums/components/torn_wall.dm
@@ -0,0 +1,99 @@
+
+#define TORN_WALL_RUINED 2
+#define TORN_WALL_DAMAGED 1
+#define TORN_WALL_INITIAL 0
+
+/**
+ * Component applied to a wall to progressively destroy it.
+ * If component is applied to something which already has it, stage increases.
+ * Wall is destroyed on third application.
+ * Can be fixed using a welder
+ */
+/datum/component/torn_wall
+ dupe_mode = COMPONENT_DUPE_UNIQUE_PASSARGS
+ var/current_stage = TORN_WALL_INITIAL
+
+/datum/component/torn_wall/Initialize()
+ . = ..()
+ if (!iswallturf(parent) || isindestructiblewall(parent))
+ return COMPONENT_INCOMPATIBLE
+
+/datum/component/torn_wall/RegisterWithParent()
+ RegisterSignal(parent, COMSIG_ATOM_EXAMINE, PROC_REF(on_examined))
+ RegisterSignal(parent, COMSIG_ATOM_TOOL_ACT(TOOL_WELDER), PROC_REF(on_welded))
+ RegisterSignal(parent, COMSIG_ATOM_UPDATE_OVERLAYS, PROC_REF(on_update_overlays))
+ RegisterSignal(parent, COMSIG_TURF_CHANGE, PROC_REF(on_turf_changed))
+ apply_visuals()
+
+/datum/component/torn_wall/UnregisterFromParent()
+ var/atom/atom_parent = parent
+ UnregisterSignal(parent, list(
+ COMSIG_ATOM_EXAMINE,
+ COMSIG_ATOM_TOOL_ACT(TOOL_WELDER),
+ COMSIG_ATOM_UPDATE_OVERLAYS,
+ COMSIG_TURF_CHANGE,
+ ))
+ atom_parent.update_appearance(UPDATE_ICON)
+
+/datum/component/torn_wall/InheritComponent(datum/component/C, i_am_original)
+ increase_stage()
+
+/// Play a fun animation and make our wall look damaged
+/datum/component/torn_wall/proc/apply_visuals()
+ var/atom/atom_parent = parent
+ playsound(atom_parent, 'sound/effects/bang.ogg', 50, vary = TRUE)
+ atom_parent.update_appearance(UPDATE_ICON)
+ atom_parent.Shake(shake_interval = 0.1 SECONDS, duration = 0.5 SECONDS)
+
+/// Make the effect more dramatic
+/datum/component/torn_wall/proc/increase_stage()
+ current_stage++
+ if (current_stage != TORN_WALL_RUINED)
+ apply_visuals()
+ return
+ var/turf/closed/wall/attached_wall = parent
+ playsound(attached_wall, 'sound/effects/meteorimpact.ogg', 100, vary = TRUE)
+ attached_wall.dismantle_wall(devastated = TRUE)
+
+/// Fix it up on weld
+/datum/component/torn_wall/proc/on_welded(atom/source, mob/user, obj/item/tool)
+ SIGNAL_HANDLER
+ INVOKE_ASYNC(src, PROC_REF(try_repair), source, user, tool)
+ return COMPONENT_BLOCK_TOOL_ATTACK
+
+/// Fix us up
+/datum/component/torn_wall/proc/try_repair(atom/source, mob/user, obj/item/tool)
+ source.balloon_alert(user, "repairing...")
+ if(!tool.use_tool(source, user, 5 SECONDS, amount = 2, volume = 50))
+ source.balloon_alert(user, "interrupted!")
+ return
+ current_stage--
+ if (current_stage < TORN_WALL_INITIAL)
+ qdel(src)
+ return
+ source.update_appearance(UPDATE_ICON)
+ source.tool_act(user, tool, TOOL_WELDER, is_right_clicking = FALSE) // Keep going
+
+/// Give them a hint
+/datum/component/torn_wall/proc/on_examined(atom/source, mob/user, list/examine_list)
+ SIGNAL_HANDLER
+ var/intensity = (current_stage == TORN_WALL_INITIAL) ? "slightly" : "badly"
+ examine_list += span_notice("It looks [intensity] damaged.")
+ examine_list += span_info("You may be able to repair it using a welding tool.")
+
+/// Show a little crack on here
+/datum/component/torn_wall/proc/on_update_overlays(turf/source, list/overlays)
+ SIGNAL_HANDLER
+ var/mutable_appearance/crack = mutable_appearance('icons/turf/overlays.dmi', "explodable", source.layer + 0.1)
+ if (current_stage == TORN_WALL_INITIAL)
+ crack.alpha *= 0.5
+ overlays += crack
+
+/// If the wall becomes any other turf, delete us. Transforming into a different works fine as a fix.
+/datum/component/torn_wall/proc/on_turf_changed()
+ SIGNAL_HANDLER
+ qdel(src)
+
+#undef TORN_WALL_RUINED
+#undef TORN_WALL_DAMAGED
+#undef TORN_WALL_INITIAL
diff --git a/code/datums/diseases/_MobProcs.dm b/code/datums/diseases/_MobProcs.dm
index e64e91c5533..1aa8b6654cd 100644
--- a/code/datums/diseases/_MobProcs.dm
+++ b/code/datums/diseases/_MobProcs.dm
@@ -98,7 +98,7 @@
if(HAS_TRAIT(src, TRAIT_VIRUS_RESISTANCE) && prob(75))
return
- if(((disease.spread_flags & DISEASE_SPREAD_AIRBORNE) || force_spread) && prob((50*disease.spreading_modifier) - 1))
+ if(((disease.spread_flags & DISEASE_SPREAD_AIRBORNE) || force_spread) && prob(min((50*disease.spreading_modifier - 1), 50)))
ForceContractDisease(disease)
/mob/living/carbon/AirborneContractDisease(datum/disease/disease, force_spread)
diff --git a/code/datums/elements/amputating_limbs.dm b/code/datums/elements/amputating_limbs.dm
index 16a99e96f6c..8684a76f47f 100644
--- a/code/datums/elements/amputating_limbs.dm
+++ b/code/datums/elements/amputating_limbs.dm
@@ -32,16 +32,16 @@
src.minimum_stat = minimum_stat
src.snip_chance = snip_chance
src.target_zones = target_zones
- RegisterSignals(target, list(COMSIG_LIVING_UNARMED_ATTACK, COMSIG_HUMAN_MELEE_UNARMED_ATTACK, COMSIG_HOSTILE_PRE_ATTACKINGTARGET), PROC_REF(try_amputate))
+ RegisterSignals(target, list(COMSIG_LIVING_UNARMED_ATTACK, COMSIG_HOSTILE_PRE_ATTACKINGTARGET), PROC_REF(try_amputate))
/datum/element/amputating_limbs/Detach(datum/source)
- UnregisterSignal(source, list(COMSIG_LIVING_UNARMED_ATTACK, COMSIG_HUMAN_MELEE_UNARMED_ATTACK, COMSIG_HOSTILE_PRE_ATTACKINGTARGET))
+ UnregisterSignal(source, list(COMSIG_LIVING_UNARMED_ATTACK, COMSIG_HOSTILE_PRE_ATTACKINGTARGET))
return ..()
/// Called when you click on literally anything with your hands, see if it is an injured carbon and then try to cut it up
-/datum/element/amputating_limbs/proc/try_amputate(mob/living/surgeon, atom/victim)
+/datum/element/amputating_limbs/proc/try_amputate(mob/living/surgeon, atom/victim, proximity, modifiers)
SIGNAL_HANDLER
- if (!iscarbon(victim) || HAS_TRAIT(victim, TRAIT_NODISMEMBER) || !prob(snip_chance))
+ if (!proximity || !iscarbon(victim) || HAS_TRAIT(victim, TRAIT_NODISMEMBER) || !prob(snip_chance))
return
var/mob/living/carbon/limbed_victim = victim
diff --git a/code/datums/elements/basic_eating.dm b/code/datums/elements/basic_eating.dm
index 86f4be63cac..08c2ed0c192 100644
--- a/code/datums/elements/basic_eating.dm
+++ b/code/datums/elements/basic_eating.dm
@@ -37,7 +37,12 @@
/datum/element/basic_eating/proc/on_unarm_attack(mob/living/eater, atom/target, proximity, modifiers)
SIGNAL_HANDLER
- try_eating(eater, target)
+ if(!proximity)
+ return NONE
+
+ if(try_eating(eater, target))
+ return COMPONENT_CANCEL_ATTACK_CHAIN
+ return NONE
/datum/element/basic_eating/proc/on_pre_attackingtarget(mob/living/eater, atom/target)
SIGNAL_HANDLER
@@ -45,7 +50,7 @@
/datum/element/basic_eating/proc/try_eating(mob/living/eater, atom/target)
if(!is_type_in_list(target, food_types))
- return
+ return FALSE
var/eat_verb = pick("bite","chew","nibble","gnaw","gobble","chomp")
if (heal_amt > 0)
@@ -54,16 +59,17 @@
eater.heal_overall_damage(heal_amt)
eater.visible_message(span_notice("[eater] [eat_verb]s [target]."), span_notice("You [eat_verb] [target][healed ? ", restoring some health" : ""]."))
finish_eating(eater, target)
- return
+ return TRUE
if (damage_amount > 0 && damage_type)
eater.apply_damage(damage_amount, damage_type)
eater.visible_message(span_notice("[eater] [eat_verb]s [target], and seems to hurt itself."), span_notice("You [eat_verb] [target], hurting yourself in the process."))
finish_eating(eater, target)
- return
+ return TRUE
eater.visible_message(span_notice("[eater] [eat_verb]s [target]."), span_notice("You [eat_verb] [target]."))
finish_eating(eater, target)
+ return TRUE
/datum/element/basic_eating/proc/finish_eating(mob/living/eater, atom/target)
playsound(eater.loc,'sound/items/eatfood.ogg', rand(10,50), TRUE)
diff --git a/code/datums/elements/dextrous.dm b/code/datums/elements/dextrous.dm
index 335c7c196d1..526dfa974af 100644
--- a/code/datums/elements/dextrous.dm
+++ b/code/datums/elements/dextrous.dm
@@ -50,8 +50,10 @@
/// Try picking up items
/datum/element/dextrous/proc/on_hand_clicked(mob/living/hand_haver, atom/target, proximity, modifiers)
SIGNAL_HANDLER
+ if(!proximity)
+ return NONE
if (!isitem(target) && hand_haver.combat_mode)
- return
+ return NONE
if (LAZYACCESS(modifiers, RIGHT_CLICK))
INVOKE_ASYNC(target, TYPE_PROC_REF(/atom, attack_hand_secondary), hand_haver, modifiers)
else
diff --git a/code/datums/elements/door_pryer.dm b/code/datums/elements/door_pryer.dm
new file mode 100644
index 00000000000..a17687407e3
--- /dev/null
+++ b/code/datums/elements/door_pryer.dm
@@ -0,0 +1,70 @@
+/**
+ * Attached to a basic mob.
+ * Causes attacks on doors to attempt to open them.
+ */
+/datum/element/door_pryer
+ element_flags = ELEMENT_BESPOKE
+ argument_hash_start_idx = 2
+ /// Time it takes to open a door with force
+ var/pry_time
+ /// Interaction key for if we force a door open
+ var/interaction_key
+
+/datum/element/door_pryer/Attach(datum/target, pry_time = 10 SECONDS, interaction_key = null)
+ . = ..()
+ if (!isliving(target))
+ return ELEMENT_INCOMPATIBLE
+ src.pry_time = pry_time
+ src.interaction_key = interaction_key
+ RegisterSignal(target, COMSIG_LIVING_UNARMED_ATTACK, PROC_REF(on_attack))
+
+/datum/element/door_pryer/Detach(datum/source)
+ . = ..()
+ UnregisterSignal(source, COMSIG_LIVING_UNARMED_ATTACK)
+
+/// If we're targetting an airlock, open it
+/datum/element/door_pryer/proc/on_attack(mob/living/basic/attacker, atom/target, proximity_flag)
+ SIGNAL_HANDLER
+ if(!proximity_flag || !istype(target, /obj/machinery/door/airlock))
+ return NONE
+ var/obj/machinery/door/airlock/airlock_target = target
+ if (!airlock_target.density)
+ return NONE // It's already open numbnuts
+
+ if(DOING_INTERACTION_WITH_TARGET(attacker, target) || (!isnull(interaction_key) && DOING_INTERACTION(attacker, interaction_key)))
+ attacker.balloon_alert(attacker, "busy!")
+ return COMPONENT_CANCEL_ATTACK_CHAIN
+
+ if (airlock_target.locked || airlock_target.welded || airlock_target.seal)
+ if (!attacker.combat_mode)
+ airlock_target.balloon_alert(attacker, "it's sealed!")
+ return COMPONENT_CANCEL_ATTACK_CHAIN
+ return // Attack the door
+
+ INVOKE_ASYNC(src, PROC_REF(open_door), attacker, airlock_target)
+ return COMPONENT_CANCEL_ATTACK_CHAIN
+
+/// Try opening the door, and if we can't then try forcing it
+/datum/element/door_pryer/proc/open_door(mob/living/basic/attacker, obj/machinery/door/airlock/airlock_target)
+ if (!airlock_target.hasPower())
+ attacker.visible_message(span_warning("[attacker] forces the [airlock_target] to open."))
+ airlock_target.open(FORCING_DOOR_CHECKS)
+ return
+
+ if (airlock_target.allowed(attacker))
+ airlock_target.open(DEFAULT_DOOR_CHECKS)
+ return
+
+ attacker.visible_message(\
+ message = span_warning("[attacker] starts forcing the [airlock_target] open!"),
+ blind_message = span_hear("You hear a metal screeching sound."),
+ )
+ playsound(airlock_target, 'sound/machines/airlock_alien_prying.ogg', 100, TRUE)
+ airlock_target.balloon_alert(attacker, "prying...")
+ if(!do_after(attacker, pry_time, airlock_target))
+ airlock_target.balloon_alert(attacker, "interrupted!")
+ return
+ if(airlock_target.locked)
+ return
+ attacker.visible_message(span_warning("[attacker] forces the [airlock_target] to open."))
+ airlock_target.open(BYPASS_DOOR_CHECKS)
diff --git a/code/datums/elements/human_biter.dm b/code/datums/elements/human_biter.dm
new file mode 100644
index 00000000000..852dea12320
--- /dev/null
+++ b/code/datums/elements/human_biter.dm
@@ -0,0 +1,28 @@
+/// Allows carbons with heads to attempt to bite mobs if attacking with cuffed hands / missing arms
+/datum/element/human_biter
+
+/datum/element/human_biter/Attach(datum/target)
+ . = ..()
+ if(!iscarbon(target))
+ return ELEMENT_INCOMPATIBLE
+
+ RegisterSignal(target, COMSIG_LIVING_EARLY_UNARMED_ATTACK, PROC_REF(try_bite))
+
+/datum/element/human_biter/Detach(datum/source, ...)
+ . = ..()
+ UnregisterSignal(source, COMSIG_LIVING_EARLY_UNARMED_ATTACK)
+
+/datum/element/human_biter/proc/try_bite(mob/living/carbon/human/source, atom/target, proximity_flag, modifiers)
+ SIGNAL_HANDLER
+
+ if(!proximity_flag || !source.combat_mode || LAZYACCESS(modifiers, RIGHT_CLICK) || !isliving(target))
+ return NONE
+
+ // If we can attack like normal, just go ahead and do that
+ if(source.can_unarmed_attack())
+ return NONE
+
+ if(target.attack_paw(source, modifiers))
+ return COMPONENT_CANCEL_ATTACK_CHAIN // bite successful!
+
+ return COMPONENT_SKIP_ATTACK // we will fail anyways if we try to attack normally, so skip the rest
diff --git a/code/datums/elements/mob_grabber.dm b/code/datums/elements/mob_grabber.dm
index a85c5dc48b2..cc766f24887 100644
--- a/code/datums/elements/mob_grabber.dm
+++ b/code/datums/elements/mob_grabber.dm
@@ -13,18 +13,19 @@
return ELEMENT_INCOMPATIBLE
src.minimum_stat = minimum_stat
src.steal_from_others = steal_from_others
- RegisterSignals(target, list(COMSIG_LIVING_UNARMED_ATTACK, COMSIG_HUMAN_MELEE_UNARMED_ATTACK, COMSIG_HOSTILE_PRE_ATTACKINGTARGET), PROC_REF(grab_mob))
+ RegisterSignals(target, list(COMSIG_LIVING_UNARMED_ATTACK, COMSIG_HOSTILE_PRE_ATTACKINGTARGET), PROC_REF(grab_mob))
/datum/element/mob_grabber/Detach(datum/source)
- UnregisterSignal(source, list(COMSIG_LIVING_UNARMED_ATTACK, COMSIG_HUMAN_MELEE_UNARMED_ATTACK, COMSIG_HOSTILE_PRE_ATTACKINGTARGET))
+ UnregisterSignal(source, list(COMSIG_LIVING_UNARMED_ATTACK, COMSIG_HOSTILE_PRE_ATTACKINGTARGET))
. = ..()
/// Try and grab something we attacked
-/datum/element/mob_grabber/proc/grab_mob(mob/living/source, mob/living/target)
+/datum/element/mob_grabber/proc/grab_mob(mob/living/source, mob/living/target, proximity, modifiers)
SIGNAL_HANDLER
- if (!isliving(target) || !source.Adjacent(target) || target.stat < minimum_stat)
- return
+ if (!isliving(target) || !proximity || target.stat < minimum_stat)
+ return NONE
var/atom/currently_pulled = target.pulledby
if (!isnull(currently_pulled) && (!steal_from_others || currently_pulled == source))
- return
+ return NONE
INVOKE_ASYNC(target, TYPE_PROC_REF(/mob/living, grabbedby), source)
+ return COMPONENT_CANCEL_ATTACK_CHAIN
diff --git a/code/datums/elements/structure_repair.dm b/code/datums/elements/structure_repair.dm
index d3b26eed815..1f57a3d1730 100644
--- a/code/datums/elements/structure_repair.dm
+++ b/code/datums/elements/structure_repair.dm
@@ -25,11 +25,11 @@
return ..()
/// If the target is of a valid type, interrupt the attack chain to repair it instead
-/datum/element/structure_repair/proc/try_repair(mob/living/fixer, atom/target)
+/datum/element/structure_repair/proc/try_repair(mob/living/fixer, atom/target, proximity)
SIGNAL_HANDLER
- if (!is_type_in_typecache(target, structure_types_typecache))
- return
+ if (!proximity || !is_type_in_typecache(target, structure_types_typecache))
+ return NONE
if (target.get_integrity() >= target.max_integrity)
target.balloon_alert(fixer, "not damaged!")
diff --git a/code/datums/elements/wall_smasher.dm b/code/datums/elements/wall_smasher.dm
index 3c6c1d92da7..ba8a689253c 100644
--- a/code/datums/elements/wall_smasher.dm
+++ b/code/datums/elements/wall_smasher.dm
@@ -17,7 +17,7 @@
return ELEMENT_INCOMPATIBLE
src.strength_flag = strength_flag
- RegisterSignals(target, list(COMSIG_LIVING_UNARMED_ATTACK, COMSIG_HUMAN_EARLY_UNARMED_ATTACK), PROC_REF(on_unarm_attack)) // Players
+ RegisterSignal(target, COMSIG_LIVING_UNARMED_ATTACK, PROC_REF(on_unarm_attack)) // Players
RegisterSignal(target, COMSIG_HOSTILE_PRE_ATTACKINGTARGET, PROC_REF(on_pre_attackingtarget)) // AI
if (isanimal_or_basicmob(target))
@@ -25,7 +25,7 @@
animal_target.environment_smash = strength_flag
/datum/element/wall_smasher/Detach(datum/target)
- UnregisterSignal(target, list(COMSIG_LIVING_UNARMED_ATTACK, COMSIG_HUMAN_EARLY_UNARMED_ATTACK, COMSIG_HOSTILE_PRE_ATTACKINGTARGET))
+ UnregisterSignal(target, list(COMSIG_LIVING_UNARMED_ATTACK, COMSIG_HOSTILE_PRE_ATTACKINGTARGET))
if (isanimal_or_basicmob(target))
var/mob/living/simple_animal/animal_target = target
animal_target.environment_smash = initial(animal_target.environment_smash)
@@ -34,19 +34,19 @@
/datum/element/wall_smasher/proc/on_unarm_attack(mob/living/puncher, atom/target, proximity, modifiers)
SIGNAL_HANDLER
- try_smashing(puncher, target)
+ return try_smashing(puncher, target)
/datum/element/wall_smasher/proc/on_pre_attackingtarget(mob/living/puncher, atom/target)
SIGNAL_HANDLER
- try_smashing(puncher, target)
+ return try_smashing(puncher, target)
/datum/element/wall_smasher/proc/try_smashing(mob/living/puncher, atom/target)
if (!isturf(target))
- return
+ return NONE
if (isfloorturf(target))
- return
+ return NONE
if (isindestructiblewall(target))
- return
+ return NONE
puncher.changeNext_move(CLICK_CD_MELEE)
puncher.do_attack_animation(target)
diff --git a/code/datums/elements/wall_tearer.dm b/code/datums/elements/wall_tearer.dm
new file mode 100644
index 00000000000..e7ca4b3be96
--- /dev/null
+++ b/code/datums/elements/wall_tearer.dm
@@ -0,0 +1,83 @@
+/// Returned if we can rip up this target
+#define WALL_TEAR_ALLOWED TRUE
+/// Returned if we can't rip up this target
+#define WALL_TEAR_INVALID FALSE
+/// Returned if we can't rip up the target but still don't want to attack it
+#define WALL_TEAR_FAIL_CANCEL_CHAIN -1
+
+/**
+ * Allows attached mobs to destroy walls over time, a little less unreasonable than the instant wall deletion of wall_smasher
+ */
+/datum/element/wall_tearer
+ element_flags = ELEMENT_BESPOKE
+ argument_hash_start_idx = 2
+ /// Whether we can break reinforced walls
+ var/allow_reinforced
+ /// How long it takes for us to destroy a wall completely (its a 3 step process so this will be divided by three)
+ var/tear_time
+ /// How much longer it takes to break reinforced walls
+ var/reinforced_multiplier
+ /// What interaction key do we use for our interaction
+ var/do_after_key
+
+/datum/element/wall_tearer/Attach(datum/target, allow_reinforced = TRUE, tear_time = 4 SECONDS, reinforced_multiplier = 3, do_after_key = null)
+ . = ..()
+ if (!isliving(target))
+ return ELEMENT_INCOMPATIBLE
+ src.allow_reinforced = allow_reinforced
+ src.tear_time = tear_time
+ src.reinforced_multiplier = reinforced_multiplier
+ src.do_after_key = do_after_key
+ RegisterSignal(target, COMSIG_LIVING_UNARMED_ATTACK, PROC_REF(on_attacked_wall))
+
+/datum/element/wall_tearer/Detach(datum/source)
+ . = ..()
+ UnregisterSignal(source, COMSIG_LIVING_UNARMED_ATTACK)
+
+/// Try to tear up a wall
+/datum/element/wall_tearer/proc/on_attacked_wall(mob/living/tearer, atom/target, proximity_flag)
+ SIGNAL_HANDLER
+ if (!proximity_flag)
+ return NONE
+ if (DOING_INTERACTION_WITH_TARGET(tearer, target) || (!isnull(do_after_key) && DOING_INTERACTION(tearer, do_after_key)))
+ tearer.balloon_alert(tearer, "busy!")
+ return COMPONENT_HOSTILE_NO_ATTACK
+ var/is_valid = validate_target(target, tearer)
+ if (is_valid != WALL_TEAR_ALLOWED)
+ return is_valid == WALL_TEAR_FAIL_CANCEL_CHAIN ? COMPONENT_HOSTILE_NO_ATTACK : NONE
+ INVOKE_ASYNC(src, PROC_REF(rip_and_tear), tearer, target)
+ return COMPONENT_HOSTILE_NO_ATTACK
+
+/datum/element/wall_tearer/proc/rip_and_tear(mob/living/tearer, atom/target)
+ // We need to do this three times to actually destroy it
+ var/rip_time = (istype(target, /turf/closed/wall/r_wall) ? tear_time * reinforced_multiplier : tear_time) / 3
+ if (rip_time > 0)
+ tearer.visible_message(span_warning("[tearer] begins tearing through [target]!"))
+ playsound(tearer, 'sound/machines/airlock_alien_prying.ogg', vol = 100, vary = TRUE)
+ target.balloon_alert(tearer, "tearing...")
+ if (!do_after(tearer, delay = rip_time, target = target, interaction_key = do_after_key))
+ tearer.balloon_alert(tearer, "interrupted!")
+ return
+ // Might have been replaced, removed, or reinforced during our do_after
+ var/is_valid = validate_target(target, tearer)
+ if (is_valid != WALL_TEAR_ALLOWED)
+ return
+ target.AddComponent(/datum/component/torn_wall)
+ is_valid = validate_target(target, tearer) // And now we might have just destroyed it
+ if (is_valid == WALL_TEAR_ALLOWED)
+ tearer.UnarmedAttack(target, proximity_flag = TRUE)
+
+/// Check if the target atom is a wall we can actually rip up
+/datum/element/wall_tearer/proc/validate_target(atom/target, mob/living/tearer)
+ if (!isclosedturf(target) || isindestructiblewall(target))
+ return WALL_TEAR_INVALID
+
+ var/reinforced = istype(target, /turf/closed/wall/r_wall)
+ if (!allow_reinforced && reinforced)
+ target.balloon_alert(tearer, "it's too strong!")
+ return WALL_TEAR_FAIL_CANCEL_CHAIN
+ return WALL_TEAR_ALLOWED
+
+#undef WALL_TEAR_ALLOWED
+#undef WALL_TEAR_INVALID
+#undef WALL_TEAR_FAIL_CANCEL_CHAIN
diff --git a/code/datums/looping_sounds/vents.dm b/code/datums/looping_sounds/vents.dm
new file mode 100644
index 00000000000..2d0a3443631
--- /dev/null
+++ b/code/datums/looping_sounds/vents.dm
@@ -0,0 +1,7 @@
+/datum/looping_sound/vent_pump_overclock
+ start_sound = 'sound/machines/fan_start.ogg'
+ start_length = 1.5 SECONDS
+ end_sound = 'sound/machines/fan_stop.ogg'
+ end_sound = 1.5 SECONDS
+ mid_sounds = 'sound/machines/fan_loop.ogg'
+ mid_length = 2 SECONDS
diff --git a/code/datums/mutations/chameleon.dm b/code/datums/mutations/chameleon.dm
index 0ae69e71f66..4ec73f92a4b 100644
--- a/code/datums/mutations/chameleon.dm
+++ b/code/datums/mutations/chameleon.dm
@@ -19,7 +19,7 @@
/// SKYRAT EDIT END
owner.alpha = CHAMELEON_MUTATION_DEFAULT_TRANSPARENCY
RegisterSignal(owner, COMSIG_MOVABLE_MOVED, PROC_REF(on_move))
- RegisterSignal(owner, COMSIG_HUMAN_EARLY_UNARMED_ATTACK, PROC_REF(on_attack_hand))
+ RegisterSignal(owner, COMSIG_LIVING_UNARMED_ATTACK, PROC_REF(on_attack_hand))
/datum/mutation/human/chameleon/on_life(seconds_per_tick, times_fired)
/// SKYRAT EDIT BEGIN
@@ -73,7 +73,7 @@
if(..())
return
owner.alpha = 255
- UnregisterSignal(owner, list(COMSIG_MOVABLE_MOVED, COMSIG_HUMAN_EARLY_UNARMED_ATTACK))
+ UnregisterSignal(owner, list(COMSIG_MOVABLE_MOVED, COMSIG_LIVING_UNARMED_ATTACK))
/// SKYRAT EDIT BEGIN
REMOVE_TRAIT(owner, TRAIT_CHAMELEON_SKIN, GENETIC_MUTATION)
/// SKYRAT EDIT END
diff --git a/code/datums/mutations/hulk.dm b/code/datums/mutations/hulk.dm
index 9ab5d4b86ef..970d850eafa 100644
--- a/code/datums/mutations/hulk.dm
+++ b/code/datums/mutations/hulk.dm
@@ -29,26 +29,27 @@
part.variable_color = "#00aa00"
owner.update_body_parts()
owner.add_mood_event("hulk", /datum/mood_event/hulk)
- RegisterSignal(owner, COMSIG_HUMAN_EARLY_UNARMED_ATTACK, PROC_REF(on_attack_hand))
+ RegisterSignal(owner, COMSIG_LIVING_EARLY_UNARMED_ATTACK, PROC_REF(on_attack_hand))
RegisterSignal(owner, COMSIG_MOB_SAY, PROC_REF(handle_speech))
RegisterSignal(owner, COMSIG_MOB_CLICKON, PROC_REF(check_swing))
/datum/mutation/human/hulk/proc/on_attack_hand(mob/living/carbon/human/source, atom/target, proximity, modifiers)
SIGNAL_HANDLER
- if(!proximity)
- return
- if(!source.combat_mode || LAZYACCESS(modifiers, RIGHT_CLICK))
- return
- if(target.attack_hulk(owner))
- if(world.time > (last_scream + scream_delay))
- last_scream = world.time
- INVOKE_ASYNC(src, PROC_REF(scream_attack), source)
- log_combat(source, target, "punched", "hulk powers")
- source.do_attack_animation(target, ATTACK_EFFECT_SMASH)
- source.changeNext_move(CLICK_CD_MELEE)
-
+ if(!source.combat_mode || !proximity || LAZYACCESS(modifiers, RIGHT_CLICK))
+ return NONE
+ if(!source.can_unarmed_attack())
return COMPONENT_CANCEL_ATTACK_CHAIN
+ if(!target.attack_hulk(owner))
+ return NONE
+
+ if(world.time > (last_scream + scream_delay))
+ last_scream = world.time
+ INVOKE_ASYNC(src, PROC_REF(scream_attack), source)
+ log_combat(source, target, "punched", "hulk powers")
+ source.do_attack_animation(target, ATTACK_EFFECT_SMASH)
+ source.changeNext_move(CLICK_CD_MELEE)
+ return COMPONENT_CANCEL_ATTACK_CHAIN
/datum/mutation/human/hulk/proc/scream_attack(mob/living/carbon/human/source)
source.say("WAAAAAAAAAAAAAAGH!", forced="hulk")
@@ -90,7 +91,7 @@
part.variable_color = null
owner.update_body_parts()
owner.clear_mood_event("hulk")
- UnregisterSignal(owner, COMSIG_HUMAN_EARLY_UNARMED_ATTACK)
+ UnregisterSignal(owner, COMSIG_LIVING_EARLY_UNARMED_ATTACK)
UnregisterSignal(owner, COMSIG_MOB_SAY)
UnregisterSignal(owner, COMSIG_MOB_CLICKON)
diff --git a/code/datums/recipe.dm b/code/datums/recipe.dm
index de3d3d9b96d..2149c5452ea 100644
--- a/code/datums/recipe.dm
+++ b/code/datums/recipe.dm
@@ -89,7 +89,6 @@
for (var/obj/O in (container.contents-result_obj))
if (O.reagents)
O.reagents.del_reagent(/datum/reagent/consumable/nutriment)
- O.reagents.update_total()
O.reagents.trans_to(result_obj, O.reagents.total_volume)
qdel(O)
container.reagents.clear_reagents()
diff --git a/code/datums/sprite_accessories.dm b/code/datums/sprite_accessories.dm
index 16ce537b840..e0e84baa190 100644
--- a/code/datums/sprite_accessories.dm
+++ b/code/datums/sprite_accessories.dm
@@ -85,6 +85,7 @@
//////////////////////
/datum/sprite_accessory/hair
icon = 'icons/mob/human/human_face.dmi' // default icon for all hairs
+ var/y_offset = 0 // Y offset to apply so we can have hair that reaches above the player sprite's visual bounding box
// please make sure they're sorted alphabetically and, where needed, categorized
// try to capitalize the names please~
@@ -103,6 +104,11 @@
name = "Afro (Large)"
icon_state = "hair_bigafro"
+/datum/sprite_accessory/hair/afro_huge
+ name = "Afro (Huge)"
+ icon_state = "hair_hugeafro"
+ y_offset = 6
+
/datum/sprite_accessory/hair/allthefuzz
name = "All The Fuzz"
icon_state = "hair_allthefuzz"
diff --git a/code/datums/weather/weather_types/ash_storm.dm b/code/datums/weather/weather_types/ash_storm.dm
index 92a1ba1eed6..bb4e5af63f3 100644
--- a/code/datums/weather/weather_types/ash_storm.dm
+++ b/code/datums/weather/weather_types/ash_storm.dm
@@ -66,7 +66,7 @@
return FALSE
/datum/weather/ash_storm/weather_act(mob/living/victim)
- victim.adjustFireLoss(4)
+ victim.adjustFireLoss(4, required_bodytype = BODYTYPE_ORGANIC)
/datum/weather/ash_storm/end()
GLOB.ash_storm_sounds -= weak_sounds
diff --git a/code/datums/wounds/bones.dm b/code/datums/wounds/bones.dm
index 270d2a23f42..d4624acbb92 100644
--- a/code/datums/wounds/bones.dm
+++ b/code/datums/wounds/bones.dm
@@ -60,9 +60,9 @@
/datum/wound/blunt/bone/set_victim(new_victim)
if (victim)
- UnregisterSignal(victim, COMSIG_HUMAN_EARLY_UNARMED_ATTACK)
+ UnregisterSignal(victim, COMSIG_LIVING_UNARMED_ATTACK)
if (new_victim)
- RegisterSignal(new_victim, COMSIG_HUMAN_EARLY_UNARMED_ATTACK, PROC_REF(attack_with_hurt_hand))
+ RegisterSignal(new_victim, COMSIG_LIVING_UNARMED_ATTACK, PROC_REF(attack_with_hurt_hand))
return ..()
@@ -113,8 +113,8 @@
/datum/wound/blunt/bone/proc/attack_with_hurt_hand(mob/M, atom/target, proximity)
SIGNAL_HANDLER
- if(victim.get_active_hand() != limb || !victim.combat_mode || !ismob(target) || severity <= WOUND_SEVERITY_MODERATE)
- return
+ if(victim.get_active_hand() != limb || !proximity || !victim.combat_mode || !ismob(target) || severity <= WOUND_SEVERITY_MODERATE)
+ return NONE
// With a severe or critical wound, you have a 15% or 30% chance to proc pain on hit
if(prob((severity - 1) * 15))
@@ -130,6 +130,7 @@
limb.receive_damage(brute=rand(3,7))
return COMPONENT_CANCEL_ATTACK_CHAIN
+ return NONE
/datum/wound/blunt/bone/receive_damage(wounding_type, wounding_dmg, wound_bonus)
if(!victim || wounding_dmg < WOUND_MINIMUM_DAMAGE)
diff --git a/code/game/atom_defense.dm b/code/game/atom_defense.dm
index 1ed6dc4cc5a..90533d05f62 100644
--- a/code/game/atom_defense.dm
+++ b/code/game/atom_defense.dm
@@ -51,6 +51,13 @@
if(atom_integrity == new_value)
return
atom_integrity = new_value
+ on_update_integrity(old_value, new_value)
+ return new_value
+
+/// Handle updates to your atom's integrity
+/atom/proc/on_update_integrity(old_value, new_value)
+ SHOULD_NOT_SLEEP(TRUE)
+ SHOULD_CALL_PARENT(TRUE)
SEND_SIGNAL(src, COMSIG_ATOM_INTEGRITY_CHANGED, old_value, new_value)
/// This mostly exists to keep atom_integrity private. Might be useful in the future.
@@ -58,6 +65,11 @@
SHOULD_BE_PURE(TRUE)
return atom_integrity
+/// Similar to get_integrity, but returns the percentage as [0-1] instead.
+/atom/proc/get_integrity_percentage()
+ SHOULD_BE_PURE(TRUE)
+ return round(atom_integrity / max_integrity, 0.01)
+
///returns the damage value of the attack after processing the atom's various armor protections
/atom/proc/run_atom_armor(damage_amount, damage_type, damage_flag = 0, attack_dir, armour_penetration = 0)
if(!uses_integrity)
diff --git a/code/game/atoms.dm b/code/game/atoms.dm
index d8ce2fb9751..aafeb699ea7 100644
--- a/code/game/atoms.dm
+++ b/code/game/atoms.dm
@@ -683,7 +683,7 @@
. += "It contains [round(reagents.total_volume, 0.01)] units of various reagents[user_sees_reagents ? ":" : "."]"
if(user_sees_reagents) //Show each individual reagent for detailed examination
for(var/datum/reagent/current_reagent as anything in reagents.reagent_list)
- . += "• [FLOOR(current_reagent.volume, CHEMICAL_QUANTISATION_LEVEL)] units of [current_reagent.name]"
+ . += "• [round(current_reagent.volume, 0.01)] units of [current_reagent.name]"
if(reagents.is_reacting)
. += span_warning("It is currently reacting!")
. += span_notice("The solution's pH is [round(reagents.ph, 0.01)] and has a temperature of [reagents.chem_temp]K.")
diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm
index 41172ff552d..53b248fde38 100644
--- a/code/game/atoms_movable.dm
+++ b/code/game/atoms_movable.dm
@@ -18,6 +18,8 @@
var/initial_language_holder = /datum/language_holder
/// Holds all languages this mob can speak and understand
VAR_PRIVATE/datum/language_holder/language_holder
+ /// The list of factions this atom belongs to
+ var/list/faction
var/verb_say = "says"
var/verb_ask = "asks"
@@ -1656,3 +1658,20 @@
*/
/atom/movable/proc/keybind_face_direction(direction)
setDir(direction)
+
+/**
+ * Check if the other atom/movable has any factions the same as us. Defined at the atom/movable level so it can be defined for just about anything.
+ *
+ * If exact match is set, then all our factions must match exactly
+ */
+/atom/movable/proc/faction_check_atom(atom/movable/target, exact_match)
+ if(!exact_match)
+ return faction_check(faction, target.faction, FALSE)
+
+ var/list/faction_src = LAZYCOPY(faction)
+ var/list/faction_target = LAZYCOPY(target.faction)
+ if(!("[REF(src)]" in faction_target)) //if they don't have our ref faction, remove it from our factions list.
+ faction_src -= "[REF(src)]" //if we don't do this, we'll never have an exact match.
+ if(!("[REF(target)]" in faction_src))
+ faction_target -= "[REF(target)]" //same thing here.
+ return faction_check(faction_src, faction_target, TRUE)
diff --git a/code/game/communications.dm b/code/game/communications.dm
index 7b2da607828..41867376684 100644
--- a/code/game/communications.dm
+++ b/code/game/communications.dm
@@ -141,6 +141,7 @@ GLOBAL_LIST_INIT(reverseradiochannels, list(
))
/datum/radio_frequency
+ /// The frequency of this radio frequency. Of course.
var/frequency
/// List of filters -> list of devices
var/list/list/datum/weakref/devices = list()
@@ -189,6 +190,7 @@ GLOBAL_LIST_INIT(reverseradiochannels, list(
device.receive_signal(signal)
CHECK_TICK
+/// Handles adding a listener to the radio frequency.
/datum/radio_frequency/proc/add_listener(obj/device, filter as text|null)
if (!filter)
filter = "_default"
@@ -201,6 +203,7 @@ GLOBAL_LIST_INIT(reverseradiochannels, list(
devices[filter] = devices_line = list()
devices_line += new_listener
+/// Handles removing a listener from this radio frequency.
/datum/radio_frequency/proc/remove_listener(obj/device)
for(var/devices_filter in devices)
var/list/devices_line = devices[devices_filter]
@@ -210,15 +213,27 @@ GLOBAL_LIST_INIT(reverseradiochannels, list(
if(!devices_line.len)
devices -= devices_filter
+/**
+ * Proc for reacting to a received `/datum/signal`. To be implemented as needed,
+ * does nothing by default.
+ */
/obj/proc/receive_signal(datum/signal/signal)
set waitfor = FALSE
return
/datum/signal
+ /// The source of this signal.
var/obj/source
+ /// The frequency on which this signal was emitted.
var/frequency = 0
+ /// The method through which this signal was transmitted.
+ /// See all of the `TRANSMISSION_X` in `code/__DEFINES/radio.dm` for
+ /// all of the possible options.
var/transmission_method
+ /// The data carried through this signal. Defaults to `null`, otherwise it's
+ /// an associative list of (string, any).
var/list/data
+ /// Logging data, used for logging purposes. Makes sense, right?
var/logging_data
/datum/signal/New(data, transmission_method = TRANSMISSION_RADIO, logging_data = null)
diff --git a/code/game/gamemodes/dynamic/dynamic.dm b/code/game/gamemodes/dynamic/dynamic.dm
index ce64f200458..96674292430 100644
--- a/code/game/gamemodes/dynamic/dynamic.dm
+++ b/code/game/gamemodes/dynamic/dynamic.dm
@@ -426,6 +426,24 @@ GLOBAL_LIST_EMPTY(dynamic_forced_rulesets)
generate_budgets()
set_cooldowns()
log_dynamic("Dynamic Mode initialized with a Threat Level of... [threat_level]! ([round_start_budget] round start budget)")
+ SSblackbox.record_feedback(
+ "associative",
+ "dynamic_threat",
+ 1,
+ list(
+ "server_name" = CONFIG_GET(string/serversqlname),
+ "forced_threat_level" = GLOB.dynamic_forced_threat_level,
+ "threat_level" = threat_level,
+ "round_start_budget" = round_start_budget,
+ "parameters" = list(
+ "threat_curve_centre" = threat_curve_centre,
+ "threat_curve_width" = threat_curve_width,
+ "forced_extended" = GLOB.dynamic_forced_extended,
+ "no_stacking" = GLOB.dynamic_no_stacking,
+ "stacking_limit" = GLOB.dynamic_stacking_limit,
+ ),
+ ),
+ )
return TRUE
/datum/game_mode/dynamic/proc/setup_shown_threat()
diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm b/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm
index 80fcbc54db8..d30f3ffef76 100644
--- a/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm
+++ b/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm
@@ -595,7 +595,7 @@
var/datum/mind/player_mind = new /datum/mind(applicant.key)
player_mind.active = TRUE
- var/mob/living/simple_animal/hostile/space_dragon/S = new (pick(spawn_locs))
+ var/mob/living/basic/space_dragon/S = new (pick(spawn_locs))
player_mind.transfer_to(S)
player_mind.add_antag_datum(/datum/antagonist/space_dragon)
diff --git a/code/game/machinery/_machinery.dm b/code/game/machinery/_machinery.dm
index 494f1cca77e..710b5c7ec09 100644
--- a/code/game/machinery/_machinery.dm
+++ b/code/game/machinery/_machinery.dm
@@ -681,10 +681,25 @@
/obj/machinery/attack_paw(mob/living/user, list/modifiers)
if(!user.combat_mode)
return attack_hand(user)
+
user.changeNext_move(CLICK_CD_MELEE)
user.do_attack_animation(src, ATTACK_EFFECT_PUNCH)
- var/damage = take_damage(4, BRUTE, MELEE, 1)
- user.visible_message(span_danger("[user] smashes [src] with [user.p_their()] paws[damage ? "." : ", without leaving a mark!"]"), null, null, COMBAT_MESSAGE_RANGE)
+ var/damage = take_damage(damage_amount = 4, damage_type = BRUTE, damage_flag = MELEE, sound_effect = TRUE, attack_dir = get_dir(user, src))
+
+ var/hit_with_what_noun = "paws"
+ var/obj/item/bodypart/arm/arm = user.get_active_hand()
+ if(!isnull(arm))
+ hit_with_what_noun = arm.appendage_noun // hit with "their hand"
+ if(user.usable_hands > 1)
+ hit_with_what_noun += plural_s(hit_with_what_noun) // hit with "their hands"
+
+ user.visible_message(
+ span_danger("[user] smashes [src] with [user.p_their()] [hit_with_what_noun][damage ? "." : ", without leaving a mark!"]"),
+ span_danger("You smash [src] with your [hit_with_what_noun][damage ? "." : ", without leaving a mark!"]"),
+ span_hear("You hear a [damage ? "smash" : "thud"]."),
+ COMBAT_MESSAGE_RANGE,
+ )
+ return TRUE
/obj/machinery/attack_hulk(mob/living/carbon/user)
. = ..()
@@ -739,7 +754,7 @@
return
update_last_used(user)
-/obj/machinery/tool_act(mob/living/user, obj/item/tool, tool_type)
+/obj/machinery/tool_act(mob/living/user, obj/item/tool, tool_type, is_right_clicking)
if(SEND_SIGNAL(user, COMSIG_TRY_USE_MACHINE, src) & COMPONENT_CANT_USE_MACHINE_TOOLS)
return TOOL_ACT_MELEE_CHAIN_BLOCKING
. = ..()
diff --git a/code/game/machinery/porta_turret/portable_turret.dm b/code/game/machinery/porta_turret/portable_turret.dm
index 701d6222369..cacecb6a684 100644
--- a/code/game/machinery/porta_turret/portable_turret.dm
+++ b/code/game/machinery/porta_turret/portable_turret.dm
@@ -40,6 +40,8 @@ DEFINE_BITFIELD(turret_flags, list(
armor_type = /datum/armor/machinery_porta_turret
base_icon_state = "standard"
blocks_emissive = EMISSIVE_BLOCK_UNIQUE
+ // Same faction mobs will never be shot at, no matter the other settings
+ faction = list(FACTION_TURRET)
///if TRUE this will cause the turret to stop working if the stored_gun var is null in process()
var/uses_stored = TRUE
@@ -89,8 +91,6 @@ DEFINE_BITFIELD(turret_flags, list(
var/on = TRUE
/// Determines if our projectiles hit our faction
var/ignore_faction = FALSE
- /// Same faction mobs will never be shot at, no matter the other settings
- var/list/faction = list(FACTION_TURRET)
/// The spark system, used for generating... sparks?
var/datum/effect_system/spark_spread/spark_system
/// The turret will try to shoot from a turf in that direction when in a wall
diff --git a/code/game/machinery/telecomms/broadcasting.dm b/code/game/machinery/telecomms/broadcasting.dm
index 2ba6856ce1f..97ecd19e470 100644
--- a/code/game/machinery/telecomms/broadcasting.dm
+++ b/code/game/machinery/telecomms/broadcasting.dm
@@ -1,67 +1,26 @@
-/*
-
- Here is the big, bad function that broadcasts a message given the appropriate
- parameters.
-
- @param M:
- Reference to the mob/speaker, stored in signal.data["mob"]
-
- @param vmask:
- Boolean value if the mob is "hiding" its identity via voice mask, stored in
- signal.data["vmask"]
-
- @param vmessage:
- If specified, will display this as the message; such as "chimpering"
- for monkeys if the mob is not understood. Stored in signal.data["vmessage"].
-
- @param radio:
- Reference to the radio broadcasting the message, stored in signal.data["radio"]
-
- @param message:
- The actual string message to display to mobs who understood mob M. Stored in
- signal.data["message"]
-
- @param name:
- The name to display when a mob receives the message. signal.data["name"]
-
- @param job:
- The name job to display for the AI when it receives the message. signal.data["job"]
-
- @param realname:
- The "real" name associated with the mob. signal.data["realname"]
-
- @param vname:
- If specified, will use this name when mob M is not understood. signal.data["vname"]
-
- @param data:
- If specified:
- 1 -- Will only broadcast to intercoms
- 2 -- Will only broadcast to intercoms and station-bounced radios
- 3 -- Broadcast to syndicate frequency
- 4 -- AI can't track down this person. Useful for imitation broadcasts where you can't find the actual mob
-
- @param compression:
- If 0, the signal is audible
- If nonzero, the signal may be partially inaudible or just complete gibberish.
-
- @param level:
- The list of Z levels that the sending radio is broadcasting to. Having 0 in the list broadcasts on all levels
-
- @param freq
- The frequency of the signal
-
-**/
-
// Subtype of /datum/signal with additional processing information.
/datum/signal/subspace
transmission_method = TRANSMISSION_SUBSPACE
+ /// The type of server this signal is meant to be relayed to.
+ /// Not exclusive, the bus will usually try to send it through
+ /// more signals, but for that look for
+ /// `/obj/machinery/telecomms/bus/receive_information()`
var/server_type = /obj/machinery/telecomms/server
+ /// The signal that was the origin of this one, in case it was a copy.
var/datum/signal/subspace/original
+ /// The levels on which this signal can be received. Generally set by
+ /// a broadcaster, a relay or a message server.
+ /// If this list contains `0`, then it will be receivable on every single
+ /// z-level.
var/list/levels
/datum/signal/subspace/New(data)
src.data = data || list()
+/**
+ * Handles creating a new subspace signal that's a hard copy of this one, linked
+ * to this current signal via the `original` value, so that it can be traced back.
+ */
/datum/signal/subspace/proc/copy()
var/datum/signal/subspace/copy = new
copy.original = src
@@ -73,18 +32,26 @@
copy.data = data.Copy()
return copy
+/**
+ * Handles marking the current signal, as well as its original signal,
+ * and their original signals (recursively) as done, in their `data["done"]`.
+ */
/datum/signal/subspace/proc/mark_done()
var/datum/signal/subspace/current = src
while (current)
current.data["done"] = TRUE
current = current.original
+/**
+ * Handles sending this signal to every available receiver and mainframe.
+ */
/datum/signal/subspace/proc/send_to_receivers()
- for(var/obj/machinery/telecomms/receiver/R in GLOB.telecomms_list)
- R.receive_signal(src)
- for(var/obj/machinery/telecomms/allinone/R in GLOB.telecomms_list)
- R.receive_signal(src)
+ for(var/obj/machinery/telecomms/receiver/receiver in GLOB.telecomms_list)
+ receiver.receive_signal(src)
+ for(var/obj/machinery/telecomms/allinone/all_in_one_receiver in GLOB.telecomms_list)
+ all_in_one_receiver.receive_signal(src)
+/// Handles broadcasting this signal out, to be implemented by subtypes.
/datum/signal/subspace/proc/broadcast()
set waitfor = FALSE
@@ -92,9 +59,14 @@
// Despite "subspace" in the name, these transmissions can also be RADIO
// (intercoms and SBRs) or SUPERSPACE (CentCom).
/datum/signal/subspace/vocal
+ /// The virtualspeaker associated with this vocal transmission.
var/atom/movable/virtualspeaker/virt
+ /// The language this vocal transmission was sent in.
var/datum/language/language
+#define COMPRESSION_VOCAL_SIGNAL_MIN 35
+#define COMPRESSION_VOCAL_SIGNAL_MAX 65
+
/datum/signal/subspace/vocal/New(
obj/source, // the originating radio
frequency, // the frequency the signal is taking place on
@@ -113,13 +85,16 @@
"name" = speaker.name,
"job" = speaker.job,
"message" = message,
- "compression" = rand(35, 65),
+ "compression" = rand(COMPRESSION_VOCAL_SIGNAL_MIN, COMPRESSION_VOCAL_SIGNAL_MAX),
"language" = lang_instance.name,
"spans" = spans,
"mods" = message_mods
)
levels = SSmapping.get_connected_levels(get_turf(source))
+#undef COMPRESSION_VOCAL_SIGNAL_MIN
+#undef COMPRESSION_VOCAL_SIGNAL_MAX
+
/datum/signal/subspace/vocal/copy()
var/datum/signal/subspace/vocal/copy = new(source, frequency, virt, language)
copy.original = src
@@ -127,6 +102,10 @@
copy.levels = levels
return copy
+/// Past this amount of compression, the resulting gibberish will actually
+/// replace characters, making it even harder to understand.
+#define COMPRESSION_REPLACE_CHARACTER_THRESHOLD 30
+
/// This is the meat function for making radios hear vocal transmissions.
/datum/signal/subspace/vocal/broadcast()
set waitfor = FALSE
@@ -137,7 +116,7 @@
return
var/compression = data["compression"]
if(compression > 0)
- message = Gibberish(message, compression >= 30)
+ message = Gibberish(message, compression >= COMPRESSION_REPLACE_CHARACTER_THRESHOLD)
var/list/signal_reaches_every_z_level = levels
@@ -206,8 +185,8 @@
var/spans_part = ""
if(length(spans))
spans_part = "(spans:"
- for(var/S in spans)
- spans_part = "[spans_part] [S]"
+ for(var/span in spans)
+ spans_part = "[spans_part] [span]"
spans_part = "[spans_part] ) "
var/lang_name = data["language"]
@@ -220,4 +199,6 @@
else
log_telecomms("[virt.source] [log_text] [loc_name(get_turf(virt.source))]")
- QDEL_IN(virt, 50) // Make extra sure the virtualspeaker gets qdeleted
+ QDEL_IN(virt, 5 SECONDS) // Make extra sure the virtualspeaker gets qdeleted
+
+#undef COMPRESSION_REPLACE_CHARACTER_THRESHOLD
diff --git a/code/game/machinery/telecomms/computers/logbrowser.dm b/code/game/machinery/telecomms/computers/logbrowser.dm
index b258a8d6f6e..e202a508ecf 100644
--- a/code/game/machinery/telecomms/computers/logbrowser.dm
+++ b/code/game/machinery/telecomms/computers/logbrowser.dm
@@ -41,7 +41,7 @@
// Send selected server data
var/list/server_out = list()
server_out["name"] = SelectedServer.name
- server_out["traffic"] = SelectedServer.totaltraffic
+ server_out["traffic"] = SelectedServer.total_traffic
// Get the messages on this server
var/list/packets = list()
for(var/datum/comm_log_entry/packet in SelectedServer.log_entries)
diff --git a/code/game/machinery/telecomms/computers/message.dm b/code/game/machinery/telecomms/computers/message.dm
index e2a669804a1..ab2a3f8cefc 100644
--- a/code/game/machinery/telecomms/computers/message.dm
+++ b/code/game/machinery/telecomms/computers/message.dm
@@ -20,13 +20,16 @@
var/obj/machinery/telecomms/message_server/linkedServer = null
/// Sparks effect - For emag
var/datum/effect_system/spark_spread/spark_system
- /// Computer properties
- var/screen = MSG_MON_SCREEN_MAIN // 0 = Main menu, 1 = Message Logs, 2 = Hacked screen, 3 = Custom Message
- var/message = "System bootup complete. Please select an option." // The message that shows on the main menu.
- var/auth = FALSE // Are they authenticated?
- /// Error, Success & Notice messages
+ /// Computer properties.
+ /// 0 = Main menu, 1 = Message Logs, 2 = Hacked screen, 3 = Custom Message
+ var/screen = MSG_MON_SCREEN_MAIN
+ /// The message that shows on the main menu.
+ var/message = "System bootup complete. Please select an option."
+ /// Error message to display in the interface.
var/error_message = ""
+ /// Notice message to display in the interface.
var/notice_message = ""
+ /// Success message to display in the interface.
var/success_message = ""
/// Decrypt password
var/password = ""
@@ -89,7 +92,7 @@
"error_message" = error_message,
"notice_message" = notice_message,
"success_message" = success_message,
- "auth" = auth,
+ "auth" = authenticated,
"server_status" = !LINKED_SERVER_NONRESPONSIVE,
)
@@ -109,7 +112,7 @@
if(MSG_MON_SCREEN_REQUEST_LOGS)
var/list/request_list = list()
for(var/datum/data_rc_msg/rc in linkedServer.rc_msgs)
- request_list += list(list("ref" = REF(rc), "message" = rc.message, "stamp" = rc.stamp, "id_auth" = rc.id_auth, "departament" = rc.send_dpt))
+ request_list += list(list("ref" = REF(rc), "message" = rc.message, "stamp" = rc.stamp, "id_auth" = rc.id_auth, "departament" = rc.sender_department))
data["requests"] = request_list
return data
@@ -126,15 +129,15 @@
if("auth")
var/authPass = params["auth_password"]
- if(auth)
- auth = FALSE
+ if(authenticated)
+ authenticated = FALSE
return TRUE
if(linkedServer.decryptkey != authPass)
error_message = "ALERT: Incorrect decryption key!"
return TRUE
- auth = TRUE
+ authenticated = TRUE
success_message = "YOU SUCCESFULLY LOGGED IN!"
return TRUE
@@ -246,14 +249,14 @@
linkedServer.receive_information(signal, null)
usr.log_message("(Tablet: [name] | [usr.real_name]) sent \"[message]\" to [signal.format_target()]", LOG_PDA)
return TRUE
- // Malfunction AI and cyborgs can hack console. This will auth console, but you need to wait password selection
+ // Malfunction AI and cyborgs can hack console. This will authenticate the console, but you need to wait password selection
if("hack")
var/time = 10 SECONDS * length(linkedServer.decryptkey)
addtimer(CALLBACK(src, PROC_REF(unemag_console)), time)
screen = MSG_MON_SCREEN_HACKED
error_message = "%$&(£: Critical %$$@ Error // !RestArting! - ?pLeaSe wAit!"
linkedServer.toggled = FALSE
- auth = TRUE
+ authenticated = TRUE
return TRUE
return TRUE
@@ -284,6 +287,9 @@
else
return INITIALIZE_HINT_LATELOAD
+/**
+ * Handles printing the monitor key for a given server onto this piece of paper.
+ */
/obj/item/paper/monitorkey/proc/print(obj/machinery/telecomms/message_server/server)
add_raw_text("
Daily Key Reset
The new message monitor key is [server.decryptkey]. Please keep this a secret and away from the clown. If necessary, change the password to a more secure one.")
add_overlay("paper_words")
diff --git a/code/game/machinery/telecomms/machine_interactions.dm b/code/game/machinery/telecomms/machine_interactions.dm
index f3d5f8d8352..f6fa498c3dd 100644
--- a/code/game/machinery/telecomms/machine_interactions.dm
+++ b/code/game/machinery/telecomms/machine_interactions.dm
@@ -1,15 +1,13 @@
-
-/*
-
- All telecommunications interactions:
-
-*/
+// This file is separate from telecommunications.dm to isolate the implementation
+// of basic interactions with the machines.
/obj/machinery/telecomms
- var/temp = "" // output message
+ /// The current temporary frequency used to add new filtered frequencies
+ /// options.
var/tempfreq = FREQ_COMMON
+ /// The current mob operating the machine.
var/mob/living/operator
- ///Illegal frequencies that can't be listened to by telecommunication servers.
+ /// Illegal frequencies that can't be listened to by telecommunication servers.
var/list/banned_frequencies = list(
FREQ_SYNDICATE,
FREQ_CENTCOM,
@@ -19,7 +17,7 @@
FREQ_CTF_BLUE,
)
-/obj/machinery/telecomms/attackby(obj/item/P, mob/user, params)
+/obj/machinery/telecomms/attackby(obj/item/attacking_item, mob/user, params)
var/icon_closed = initial(icon_state)
var/icon_open = "[initial(icon_state)]_o"
@@ -27,13 +25,13 @@
icon_closed = "[initial(icon_state)]_off"
icon_open = "[initial(icon_state)]_o_off"
- if(default_deconstruction_screwdriver(user, icon_open, icon_closed, P))
+ if(default_deconstruction_screwdriver(user, icon_open, icon_closed, attacking_item))
return
// Using a multitool lets you access the receiver's interface
- else if(P.tool_behaviour == TOOL_MULTITOOL)
+ else if(attacking_item.tool_behaviour == TOOL_MULTITOOL)
attack_hand(user)
- else if(default_deconstruction_crowbar(P))
+ else if(default_deconstruction_crowbar(attacking_item))
return
else
return ..()
@@ -122,8 +120,8 @@
playsound(src, 'sound/machines/buzz-sigh.ogg', 50, TRUE)
return
else
- for(var/obj/machinery/telecomms/T in links)
- remove_link(T)
+ for(var/obj/machinery/telecomms/linked_machine in links)
+ remove_link(linked_machine)
network = params["value"]
links = list()
operator.log_message("has changed the network for [src] to [network].", LOG_GAME)
@@ -145,13 +143,13 @@
operator.log_message("removed frequency [params["value"]] for [src].", LOG_GAME)
. = TRUE
if("unlink")
- var/obj/machinery/telecomms/T = links[text2num(params["value"])]
- if(T)
- . = remove_link(T, operator)
+ var/obj/machinery/telecomms/machine_to_unlink = links[text2num(params["value"])]
+ if(machine_to_unlink)
+ . = remove_link(machine_to_unlink, operator)
if("link")
if(heldmultitool)
- var/obj/machinery/telecomms/T = heldmultitool.buffer
- . = add_new_link(T, operator)
+ var/obj/machinery/telecomms/machine_to_link = heldmultitool.buffer
+ . = add_new_link(machine_to_link, operator)
if("buffer")
heldmultitool.set_buffer(src)
. = TRUE
@@ -162,7 +160,7 @@
add_act(action, params)
. = TRUE
-///adds new_connection to src's links list AND vice versa. also updates links_by_telecomms_type
+/// Adds new_connection to src's links list AND vice versa. Also updates `links_by_telecomms_type`.
/obj/machinery/telecomms/proc/add_new_link(obj/machinery/telecomms/new_connection, mob/user)
if(!istype(new_connection) || new_connection == src)
return FALSE
@@ -180,7 +178,7 @@
user.log_message("linked [src] for [new_connection].", LOG_GAME)
return TRUE
-///removes old_connection from src's links list AND vice versa. also updates links_by_telecomms_type
+/// Removes old_connection from src's links list AND vice versa. Also updates `links_by_telecomms_type`.
/obj/machinery/telecomms/proc/remove_link(obj/machinery/telecomms/old_connection, mob/user)
if(!istype(old_connection) || old_connection == src)
return FALSE
@@ -198,6 +196,11 @@
return TRUE
+/**
+ * Wrapper for adding additional options to a machine's interface.
+ *
+ * Returns a list, or `null` if it wasn't implemented by the machine.
+ */
/obj/machinery/telecomms/proc/add_option()
return
@@ -214,7 +217,14 @@
data["receiving"] = receiving
return data
+/**
+ * Wrapper for adding another time of action for `ui_act()`, rather than
+ * having you override `ui_act` yourself.
+ *
+ * Returns `TRUE` if the action was handled, nothing if not.
+ */
/obj/machinery/telecomms/proc/add_act(action, params)
+ return
/obj/machinery/telecomms/relay/add_act(action, params)
switch(action)
@@ -236,20 +246,19 @@
else
change_frequency = 0
-// Returns a multitool from a user depending on their mobtype.
-
+/// Returns a multitool from a user depending on their mobtype.
/obj/machinery/telecomms/proc/get_multitool(mob/user)
- var/obj/item/multitool/P = null
+ var/obj/item/multitool/multitool = null
// Let's double check
if(!issilicon(user) && istype(user.get_active_held_item(), /obj/item/multitool))
- P = user.get_active_held_item()
+ multitool = user.get_active_held_item()
else if(isAI(user))
var/mob/living/silicon/ai/U = user
- P = U.aiMulti
+ multitool = U.aiMulti
else if(iscyborg(user) && in_range(user, src))
if(istype(user.get_active_held_item(), /obj/item/multitool))
- P = user.get_active_held_item()
- return P
+ multitool = user.get_active_held_item()
+ return multitool
/obj/machinery/telecomms/proc/canAccess(mob/user)
if(issilicon(user) || in_range(user, src))
diff --git a/code/game/machinery/telecomms/machines/allinone.dm b/code/game/machinery/telecomms/machines/allinone.dm
index 8296e0d0bd9..22612118033 100644
--- a/code/game/machinery/telecomms/machines/allinone.dm
+++ b/code/game/machinery/telecomms/machines/allinone.dm
@@ -1,8 +1,7 @@
-/*
- Basically just an empty shell for receiving and broadcasting radio messages. Not
- very flexible, but it gets the job done.
-*/
-
+/**
+ * Basically just an empty shell for receiving and broadcasting radio messages. Not
+ * very flexible, but it gets the job done.
+ */
/obj/machinery/telecomms/allinone
name = "telecommunications mainframe"
icon_state = "comm_server"
@@ -41,6 +40,6 @@
sleep(signal.data["slow"]) // simulate the network lag if necessary
signal.broadcast()
-/obj/machinery/telecomms/allinone/attackby(obj/item/P, mob/user, params)
- if(P.tool_behaviour == TOOL_MULTITOOL)
+/obj/machinery/telecomms/allinone/attackby(obj/item/attacking_item, mob/user, params)
+ if(attacking_item.tool_behaviour == TOOL_MULTITOOL)
return attack_hand(user)
diff --git a/code/game/machinery/telecomms/machines/broadcaster.dm b/code/game/machinery/telecomms/machines/broadcaster.dm
index 56b53437e63..a3b4caecf39 100644
--- a/code/game/machinery/telecomms/machines/broadcaster.dm
+++ b/code/game/machinery/telecomms/machines/broadcaster.dm
@@ -1,13 +1,14 @@
-/*
- The broadcaster sends processed messages to all radio devices in the game. They
- do not have to be headsets; intercoms and station-bounced radios suffice.
-
- They receive their message from a server after the message has been logged.
-*/
-
-GLOBAL_LIST_EMPTY(recentmessages) // global list of recent messages broadcasted : used to circumvent massive radio spam
-GLOBAL_VAR_INIT(message_delay, 0) // To make sure restarting the recentmessages list is kept in sync
-
+/// Global list of recent messages broadcasted : used to circumvent massive radio spam
+GLOBAL_LIST_EMPTY(recent_messages)
+/// Used to make sure restarting the recent_messages list is kept in sync.
+GLOBAL_VAR_INIT(message_delay, FALSE)
+
+/**
+ * The broadcaster sends processed messages to all radio devices in the game. They
+ * do not have to be headsets; intercoms and station-bounced radios suffice.
+ *
+ * They receive their message from a server after the message has been logged.
+ */
/obj/machinery/telecomms/broadcaster
name = "subspace broadcaster"
icon_state = "broadcaster"
@@ -18,15 +19,17 @@ GLOBAL_VAR_INIT(message_delay, 0) // To make sure restarting the recentmessages
circuit = /obj/item/circuitboard/machine/telecomms/broadcaster
/obj/machinery/telecomms/broadcaster/receive_information(datum/signal/subspace/signal, obj/machinery/telecomms/machine_from)
- // Don't broadcast rejected signals
if(!istype(signal))
return
+
+ // Don't broadcast rejected signals
if(signal.data["reject"])
return
if(!signal.data["message"])
return
+
var/signal_message = "[signal.frequency]:[signal.data["message"]]:[signal.data["name"]]"
- if(signal_message in GLOB.recentmessages)
+ if(signal_message in GLOB.recent_messages)
return
// Prevents massive radio spam
@@ -35,11 +38,11 @@ GLOBAL_VAR_INIT(message_delay, 0) // To make sure restarting the recentmessages
if(original && ("compression" in signal.data))
original.data["compression"] = signal.data["compression"]
- var/turf/T = get_turf(src)
- if (T)
- signal.levels |= SSmapping.get_connected_levels(T)
+ var/turf/current_turf = get_turf(src)
+ if (current_turf)
+ signal.levels |= SSmapping.get_connected_levels(current_turf)
- GLOB.recentmessages.Add(signal_message)
+ GLOB.recent_messages.Add(signal_message)
if(signal.data["slow"] > 0)
sleep(signal.data["slow"]) // simulate the network lag if necessary
@@ -55,29 +58,31 @@ GLOBAL_VAR_INIT(message_delay, 0) // To make sure restarting the recentmessages
use_power(idle_power_usage)
+/**
+ * Simply resets the message delay and the recent messages list, to ensure that
+ * recent messages can be sent again. Is called on a one second timer after a
+ * delay is set, from `/obj/machinery/telecomms/broadcaster/receive_information()`
+ */
/proc/end_message_delay()
GLOB.message_delay = FALSE
- GLOB.recentmessages = list()
+ GLOB.recent_messages = list()
/obj/machinery/telecomms/broadcaster/Destroy()
// In case message_delay is left on 1, otherwise it won't reset the list and people can't say the same thing twice anymore.
if(GLOB.message_delay)
- GLOB.message_delay = 0
+ GLOB.message_delay = FALSE
return ..()
-
-//Preset Broadcasters
+// Preset Broadcasters
//--PRESET LEFT--//
-
/obj/machinery/telecomms/broadcaster/preset_left
id = "Broadcaster A"
network = "tcommsat"
autolinkers = list("broadcasterA")
//--PRESET RIGHT--//
-
/obj/machinery/telecomms/broadcaster/preset_right
id = "Broadcaster B"
network = "tcommsat"
diff --git a/code/game/machinery/telecomms/machines/bus.dm b/code/game/machinery/telecomms/machines/bus.dm
index 64a13ccbd71..8ac8297b612 100644
--- a/code/game/machinery/telecomms/machines/bus.dm
+++ b/code/game/machinery/telecomms/machines/bus.dm
@@ -1,13 +1,13 @@
-/*
- The bus mainframe idles and waits for hubs to relay them signals. They act
- as junctions for the network.
-
- They transfer uncompressed subspace packets to processor units, and then take
- the processed packet to a server for logging.
-
- Link to a subspace hub if it can't send to a server.
-*/
-
+/**
+ * The bus mainframe idles and waits for hubs to relay them signals. They act
+ * as junctions for the network.
+ *
+ * They transfer uncompressed subspace packets to processor units, and then take
+ * the processed packet to a server for logging.
+ *
+ * Can be linked to a telecommunications hub or a broadcaster in the absence
+ * of a server, at the cost of some added latency.
+ */
/obj/machinery/telecomms/bus
name = "bus mainframe"
icon_state = "bus"
@@ -17,7 +17,9 @@
idle_power_usage = BASE_MACHINE_IDLE_CONSUMPTION * 0.01
netspeed = 40
circuit = /obj/item/circuitboard/machine/telecomms/bus
- var/change_frequency = 0
+ /// The frequency this bus will use to override the received signal's frequency,
+ /// if not `NONE`.
+ var/change_frequency = NONE
/obj/machinery/telecomms/bus/receive_information(datum/signal/subspace/signal, obj/machinery/telecomms/machine_from)
if(!istype(signal) || !is_freq_listening(signal))
@@ -47,7 +49,7 @@
use_power(idle_power_usage)
-//Preset Buses
+// Preset Buses
/obj/machinery/telecomms/bus/preset_one
id = "Bus 1"
@@ -75,6 +77,8 @@
/obj/machinery/telecomms/bus/preset_four/Initialize(mapload)
. = ..()
+ // We want to include every freely-available frequency on this one, so they
+ // get processed quickly when used on-station.
for(var/i = MIN_FREQ, i <= MAX_FREQ, i += 2)
freq_listening |= i
diff --git a/code/game/machinery/telecomms/machines/hub.dm b/code/game/machinery/telecomms/machines/hub.dm
index e84f1571736..094180a6e70 100644
--- a/code/game/machinery/telecomms/machines/hub.dm
+++ b/code/game/machinery/telecomms/machines/hub.dm
@@ -1,13 +1,12 @@
-/*
- The HUB idles until it receives information. It then passes on that information
- depending on where it came from.
-
- This is the heart of the Telecommunications Network, sending information where it
- is needed. It mainly receives information from long-distance Relays and then sends
- that information to be processed. Afterwards it gets the uncompressed information
- from Servers/Buses and sends that back to the relay, to then be broadcasted.
-*/
-
+/**
+ * The HUB idles until it receives information. It then passes on that information
+ * depending on where it came from.
+ *
+ * This is the heart of the Telecommunications Network, sending information where it
+ * is needed. It mainly receives information from long-distance Relays and then sends
+ * that information to be processed. Afterwards it gets the uncompressed information
+ * from Servers/Buses and sends that back to the relay, to then be broadcasted.
+ */
/obj/machinery/telecomms/hub
name = "telecommunication hub"
icon_state = "hub"
@@ -53,12 +52,31 @@
QDEL_NULL(soundloop)
return ..()
-//Preset HUB
+// Preset HUB
/obj/machinery/telecomms/hub/preset
id = "Hub"
network = "tcommsat"
- autolinkers = list("hub", "relay", "s_relay", "m_relay", "r_relay", "h_relay", "science", "medical",
- "supply", "service", "common", "command", "engineering", "security",
- "receiverA", "receiverB", "broadcasterA", "broadcasterB", "autorelay", "messaging")
+ autolinkers = list(
+ "hub",
+ "relay",
+ "s_relay",
+ "m_relay",
+ "r_relay",
+ "h_relay",
+ "science",
+ "medical",
+ "supply",
+ "service",
+ "common",
+ "command",
+ "engineering",
+ "security",
+ "receiverA",
+ "receiverB",
+ "broadcasterA",
+ "broadcasterB",
+ "autorelay",
+ "messaging",
+ )
diff --git a/code/game/machinery/telecomms/machines/message_server.dm b/code/game/machinery/telecomms/machines/message_server.dm
index 02a146fac31..6faad6f5eab 100644
--- a/code/game/machinery/telecomms/machines/message_server.dm
+++ b/code/game/machinery/telecomms/machines/message_server.dm
@@ -1,17 +1,12 @@
-/*
- The equivalent of the server, for PDA and request console messages.
- Without it, PDA and request console messages cannot be transmitted.
- PDAs require the rest of the telecomms setup, but request consoles only
- require the message server.
-*/
-
// A decorational representation of SSblackbox, usually placed alongside the message server. Also contains a traitor theft item.
/obj/machinery/blackbox_recorder
+ name = "Blackbox Recorder"
icon = 'icons/obj/machines/telecomms.dmi'
icon_state = "blackbox"
- name = "Blackbox Recorder"
density = TRUE
armor_type = /datum/armor/machinery_blackbox_recorder
+ /// The object that's stored in the machine, which is to say, the blackbox itself.
+ /// When it hasn't already been stolen, of course.
var/obj/item/stored
/datum/armor/machinery_blackbox_recorder
@@ -39,15 +34,15 @@
to_chat(user, span_warning("It seems that the blackbox is missing..."))
return
-/obj/machinery/blackbox_recorder/attackby(obj/item/I, mob/living/user, params)
- if(istype(I, /obj/item/blackbox))
- if(HAS_TRAIT(I, TRAIT_NODROP) || !user.transferItemToLoc(I, src))
- to_chat(user, span_warning("[I] is stuck to your hand!"))
+/obj/machinery/blackbox_recorder/attackby(obj/item/attacking_item, mob/living/user, params)
+ if(istype(attacking_item, /obj/item/blackbox))
+ if(HAS_TRAIT(attacking_item, TRAIT_NODROP) || !user.transferItemToLoc(attacking_item, src))
+ to_chat(user, span_warning("[attacking_item] is stuck to your hand!"))
return
- user.visible_message(span_notice("[user] clicks [I] into [src]!"), \
+ user.visible_message(span_notice("[user] clicks [attacking_item] into [src]!"), \
span_notice("You press the device into [src], and it clicks into place. The tapes begin spinning again."))
playsound(src, 'sound/machines/click.ogg', 50, TRUE)
- stored = I
+ stored = attacking_item
update_appearance()
return
return ..()
@@ -73,26 +68,41 @@
w_class = WEIGHT_CLASS_BULKY
resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ACID_PROOF
-#define MESSAGE_SERVER_FUNCTIONING_MESSAGE "This is an automated message. The messaging system is functioning correctly."
-// The message server itself.
+/**
+ * The equivalent of the server, for PDA and request console messages.
+ * Without it, PDA and request console messages cannot be transmitted.
+ * PDAs require the rest of the telecomms setup, but request consoles only
+ * require the message server.
+ */
/obj/machinery/telecomms/message_server
- icon_state = "message_server"
name = "Messaging Server"
desc = "A machine that processes and routes PDA and request console messages."
+ icon_state = "message_server"
telecomms_type = /obj/machinery/telecomms/message_server
density = TRUE
circuit = /obj/item/circuitboard/machine/telecomms/message_server
+ /// A list of all the PDA messages that were intercepted and processed by
+ /// this messaging server.
var/list/datum/data_tablet_msg/pda_msgs = list()
+ /// A list of all the Request Console messages that were intercepted and
+ /// processed by this messaging server.
var/list/datum/data_rc_msg/rc_msgs = list()
+ /// The password of this messaging server.
var/decryptkey = "password"
- var/calibrating = 15 MINUTES //Init reads this and adds world.time, then becomes 0 when that time has passed and the machine works
+ /// Init reads this and adds world.time, then becomes 0 when that time has
+ /// passed and the machine works.
+ /// Basically, if it's not 0, it's calibrating and therefore non-functional.
+ var/calibrating = 15 MINUTES
+
+
+#define MESSAGE_SERVER_FUNCTIONING_MESSAGE "This is an automated message. The messaging system is functioning correctly."
/obj/machinery/telecomms/message_server/Initialize(mapload)
. = ..()
if (!decryptkey)
- decryptkey = GenerateKey()
+ decryptkey = generate_key()
if (calibrating)
calibrating += world.time
@@ -112,12 +122,16 @@
if(calibrating)
. += span_warning("It's still calibrating.")
-/obj/machinery/telecomms/message_server/proc/GenerateKey()
- var/newKey
- newKey += pick("the", "if", "of", "as", "in", "a", "you", "from", "to", "an", "too", "little", "snow", "dead", "drunk", "rosebud", "duck", "al", "le")
- newKey += pick("diamond", "beer", "mushroom", "assistant", "clown", "captain", "twinkie", "security", "nuke", "small", "big", "escape", "yellow", "gloves", "monkey", "engine", "nuclear", "ai")
- newKey += pick("1", "2", "3", "4", "5", "6", "7", "8", "9", "0")
- return newKey
+/**
+ * Handles generating a key for the message server, returning it. Doesn't assign
+ * it in this proc, you have to do so yourself.
+ */
+/obj/machinery/telecomms/message_server/proc/generate_key()
+ var/generated_key
+ generated_key += pick("the", "if", "of", "as", "in", "a", "you", "from", "to", "an", "too", "little", "snow", "dead", "drunk", "rosebud", "duck", "al", "le")
+ generated_key += pick("diamond", "beer", "mushroom", "assistant", "clown", "captain", "twinkie", "security", "nuke", "small", "big", "escape", "yellow", "gloves", "monkey", "engine", "nuclear", "ai")
+ generated_key += pick("1", "2", "3", "4", "5", "6", "7", "8", "9", "0")
+ return generated_key
/obj/machinery/telecomms/message_server/process()
. = ..()
@@ -125,6 +139,8 @@
calibrating = 0
pda_msgs += new /datum/data_tablet_msg("System Administrator", "system", MESSAGE_SERVER_FUNCTIONING_MESSAGE)
+#undef MESSAGE_SERVER_FUNCTIONING_MESSAGE
+
/obj/machinery/telecomms/message_server/receive_information(datum/signal/subspace/messaging/signal, obj/machinery/telecomms/machine_from)
// can't log non-message signals
if(!istype(signal) || !signal.data["message"] || !on || calibrating)
@@ -136,8 +152,8 @@
var/datum/data_tablet_msg/log_message = new(PDAsignal.format_target(), PDAsignal.format_sender(), PDAsignal.format_message(), PDAsignal.format_photo_path())
pda_msgs += log_message
else if(istype(signal, /datum/signal/subspace/messaging/rc))
- var/datum/data_rc_msg/msg = new(signal.data["rec_dpt"], signal.data["send_dpt"], signal.data["message"], signal.data["stamped"], signal.data["verified"], signal.data["priority"])
- if(signal.data["send_dpt"]) // don't log messages not from a department but allow them to work
+ var/datum/data_rc_msg/msg = new(signal.data["receiving_department"], signal.data["sender_department"], signal.data["message"], signal.data["stamped"], signal.data["verified"], signal.data["priority"])
+ if(signal.data["sender_department"]) // don't log messages not from a department but allow them to work
rc_msgs += msg
signal.data["reject"] = FALSE
@@ -151,6 +167,14 @@
if(calibrating)
. += "message_server_calibrate"
+// Preset messaging server
+/obj/machinery/telecomms/message_server/preset
+ id = "Messaging Server"
+ network = "tcommsat"
+ autolinkers = list("messaging")
+ decryptkey = null //random
+ calibrating = 0
+
// Root messaging signal datum
/datum/signal/subspace/messaging
@@ -160,8 +184,8 @@
/datum/signal/subspace/messaging/New(init_source, init_data)
source = init_source
data = init_data
- var/turf/T = get_turf(source)
- levels = SSmapping.get_connected_levels(T)
+ var/turf/origin_turf = get_turf(source)
+ levels = SSmapping.get_connected_levels(origin_turf)
if(!("reject" in data))
data["reject"] = TRUE
@@ -172,20 +196,26 @@
return copy
// Tablet message signal datum
+/// Returns a string representing the target of this message, formatted properly.
/datum/signal/subspace/messaging/tablet_message/proc/format_target()
if (data["everyone"])
return "Everyone"
+
var/datum/computer_file/program/messenger/target_app = data["targets"][1]
var/obj/item/modular_computer/target = target_app.computer
- return "[target.saved_identification] ([target.saved_job])"
+ return STRINGIFY_PDA_TARGET(target.saved_identification, target.saved_job)
+/// Returns a string representing the sender of this message, formatted properly.
/datum/signal/subspace/messaging/tablet_message/proc/format_sender()
var/display_name = get_messenger_name(locate(data["ref"]))
return display_name ? display_name : STRINGIFY_PDA_TARGET(data["fakename"], data["fakejob"])
+/// Returns the formatted message contained in this message. Use this to apply
+/// any processing to it if it needs to be formatted in a specific way.
/datum/signal/subspace/messaging/tablet_message/proc/format_message()
return data["message"]
+/// Returns the formatted photo path contained in this message, if there's one.
/datum/signal/subspace/messaging/tablet_message/proc/format_photo_path()
return data["photo"]
@@ -201,13 +231,19 @@
if(ckey(console.department) == recipient_department || (data["ore_update"] && console.receive_ore_updates))
console.create_message(data)
-// Log datums stored by the message server.
+/// Log datums stored by the message server.
/datum/data_tablet_msg
+ /// Who sent the message.
var/sender = "Unspecified"
+ /// Who was targeted by the message.
var/recipient = "Unspecified"
- var/message = "Blank" // transferred message
- var/picture_asset_key // attached photo path
- var/automated = FALSE // automated message
+ /// The transfered message.
+ var/message = "Blank"
+ /// The attached photo path, if any.
+ var/picture_asset_key
+ /// Whether or not it's an automated message. Defaults to `FALSE`.
+ var/automated = FALSE
+
/datum/data_tablet_msg/New(param_rec, param_sender, param_message, param_photo)
if(param_rec)
@@ -219,19 +255,32 @@
if(param_photo)
picture_asset_key = param_photo
+
+#define REQUEST_PRIORITY_NORMAL "Normal"
+#define REQUEST_PRIORITY_HIGH "High"
+#define REQUEST_PRIORITY_EXTREME "Extreme"
+#define REQUEST_PRIORITY_UNDETERMINED "Undetermined"
+
+
/datum/data_rc_msg
- var/rec_dpt = "Unspecified" // receiving department
- var/send_dpt = "Unspecified" // sending department
+ /// The department that sent the request.
+ var/sender_department = "Unspecified"
+ /// The department that was targeted by the request.
+ var/receiving_department = "Unspecified"
+ /// The message of the request.
var/message = "Blank"
+ /// The stamp that authenticated this message, if any.
var/stamp = "Unstamped"
+ /// The ID that authenticated this message, if any.
var/id_auth = "Unauthenticated"
- var/priority = "Normal"
+ /// The priority of this request.
+ var/priority = REQUEST_PRIORITY_NORMAL
/datum/data_rc_msg/New(param_rec, param_sender, param_message, param_stamp, param_id_auth, param_priority)
if(param_rec)
- rec_dpt = param_rec
+ receiving_department = param_rec
if(param_sender)
- send_dpt = param_sender
+ sender_department = param_sender
if(param_message)
message = param_message
if(param_stamp)
@@ -241,19 +290,15 @@
if(param_priority)
switch(param_priority)
if(REQ_NORMAL_MESSAGE_PRIORITY)
- priority = "Normal"
+ priority = REQUEST_PRIORITY_NORMAL
if(REQ_HIGH_MESSAGE_PRIORITY)
- priority = "High"
+ priority = REQUEST_PRIORITY_HIGH
if(REQ_EXTREME_MESSAGE_PRIORITY)
- priority = "Extreme"
+ priority = REQUEST_PRIORITY_EXTREME
else
- priority = "Undetermined"
+ priority = REQUEST_PRIORITY_UNDETERMINED
-#undef MESSAGE_SERVER_FUNCTIONING_MESSAGE
-
-/obj/machinery/telecomms/message_server/preset
- id = "Messaging Server"
- network = "tcommsat"
- autolinkers = list("messaging")
- decryptkey = null //random
- calibrating = 0
+#undef REQUEST_PRIORITY_NORMAL
+#undef REQUEST_PRIORITY_HIGH
+#undef REQUEST_PRIORITY_EXTREME
+#undef REQUEST_PRIORITY_UNDETERMINED
diff --git a/code/game/machinery/telecomms/machines/processor.dm b/code/game/machinery/telecomms/machines/processor.dm
index 906635d67ab..00529450746 100644
--- a/code/game/machinery/telecomms/machines/processor.dm
+++ b/code/game/machinery/telecomms/machines/processor.dm
@@ -1,11 +1,10 @@
-/*
- The processor is a very simple machine that decompresses subspace signals and
- transfers them back to the original bus. It is essential in producing audible
- data.
-
- Link to servers if bus is not present
-*/
-
+/**
+ * The processor is a very simple machine that decompresses subspace signals and
+ * transfers them back to the original bus. It is essential in producing audible
+ * data.
+ *
+ * They'll link to servers if bus is not present, with some delay added to it.
+ */
/obj/machinery/telecomms/processor
name = "processor unit"
icon_state = "processor"
@@ -14,16 +13,22 @@
density = TRUE
idle_power_usage = BASE_MACHINE_IDLE_CONSUMPTION * 0.01
circuit = /obj/item/circuitboard/machine/telecomms/processor
- var/process_mode = 1 // 1 = Uncompress Signals, 0 = Compress Signals
+ /// Whether this processor is currently compressing the data,
+ /// or actually decompressing it. Defaults to `FALSE`.
+ var/compressing = FALSE
+
+#define COMPRESSION_AMOUNT_COMPRESSING 100
+#define COMPRESSION_AMOUNT_DECOMPRESSING 0
/obj/machinery/telecomms/processor/receive_information(datum/signal/subspace/signal, obj/machinery/telecomms/machine_from)
if(!is_freq_listening(signal))
return
- if (!process_mode)
- signal.data["compression"] = 100 // even more compressed signal
- else if (signal.data["compression"])
- signal.data["compression"] = 0 // uncompress subspace signal
+ if(compressing)
+ signal.data["compression"] = COMPRESSION_AMOUNT_COMPRESSING // We compress the signal even further.
+ // Otherwise we just fully decompress it if it was compressed to begin with.
+ else if(signal.data["compression"])
+ signal.data["compression"] = COMPRESSION_AMOUNT_DECOMPRESSING
if(istype(machine_from, /obj/machinery/telecomms/bus))
relay_direct_information(signal, machine_from) // send the signal back to the machine
@@ -31,7 +36,10 @@
signal.data["slow"] += rand(5, 10) // slow the signal down
relay_information(signal, signal.server_type)
-//Preset Processors
+#undef COMPRESSION_AMOUNT_COMPRESSING
+#undef COMPRESSION_AMOUNT_DECOMPRESSING
+
+// Preset Processors
/obj/machinery/telecomms/processor/preset_one
id = "Processor 1"
diff --git a/code/game/machinery/telecomms/machines/receiver.dm b/code/game/machinery/telecomms/machines/receiver.dm
index c9c183c35ff..0c4b6d2a02d 100644
--- a/code/game/machinery/telecomms/machines/receiver.dm
+++ b/code/game/machinery/telecomms/machines/receiver.dm
@@ -1,11 +1,10 @@
-/*
- The receiver idles and receives messages from subspace-compatible radio equipment;
- primarily headsets. Then they just relay this information to all linked devices,
- which would probably be network hubs.
-
- Link to Processor Units in case receiver can't send to bus units.
-*/
-
+/**
+ * The receiver idles and receives messages from subspace-compatible radio equipment,
+ * primarily headsets. Then they just relay this information to all linked devices,
+ * which would usually be through the telecommunications hub.
+ *
+ * Link to Processor Units in case receiver can't send to a telecommunication hub.
+ */
/obj/machinery/telecomms/receiver
name = "subspace receiver"
icon_state = "broadcast receiver"
@@ -27,21 +26,27 @@
use_power(idle_power_usage)
+/**
+ * Checks whether the signal can be received by this receiver or not, based on
+ * if it's in the signal's `levels`, or if there's a liked hub with a linked
+ * relay that can receive the signal for it.
+ *
+ * Returns `TRUE` if it can receive the signal, `FALSE` if not.
+ */
/obj/machinery/telecomms/receiver/proc/check_receive_level(datum/signal/subspace/signal)
if (z in signal.levels)
return TRUE
- for(var/obj/machinery/telecomms/hub/H in links)
- for(var/obj/machinery/telecomms/relay/R in H.links)
- if(R.can_receive(signal) && (R.z in signal.levels))
+ for(var/obj/machinery/telecomms/hub/linked_hub in links)
+ for(var/obj/machinery/telecomms/relay/linked_relay in linked_hub.links)
+ if(linked_relay.can_receive(signal) && (linked_relay.z in signal.levels))
return TRUE
return FALSE
-//Preset Receivers
+// Preset Receivers
//--PRESET LEFT--//
-
/obj/machinery/telecomms/receiver/preset_left
id = "Receiver A"
network = "tcommsat"
@@ -50,16 +55,16 @@
//--PRESET RIGHT--//
-
/obj/machinery/telecomms/receiver/preset_right
id = "Receiver B"
network = "tcommsat"
autolinkers = list("receiverB") // link to relay
freq_listening = list(FREQ_COMMAND, FREQ_ENGINEERING, FREQ_SECURITY)
- //Common and other radio frequencies for people to freely use
/obj/machinery/telecomms/receiver/preset_right/Initialize(mapload)
. = ..()
+ // Also add common and other freely-available radio frequencies for people
+ // to have access to.
for(var/i = MIN_FREQ, i <= MAX_FREQ, i += 2)
freq_listening |= i
diff --git a/code/game/machinery/telecomms/machines/relay.dm b/code/game/machinery/telecomms/machines/relay.dm
index d3a6ac9ee90..2173a519be4 100644
--- a/code/game/machinery/telecomms/machines/relay.dm
+++ b/code/game/machinery/telecomms/machines/relay.dm
@@ -1,11 +1,11 @@
-/*
- The relay idles until it receives information. It then passes on that information
- depending on where it came from.
-
- The relay is needed in order to send information pass Z levels. It must be linked
- with a HUB, the only other machine that can send/receive pass Z levels.
-*/
-
+/**
+ * The relay idles until it receives information. It then passes on that information
+ * depending on where it came from.
+ *
+ * The relay is needed in order to send information to different Z levels. It
+ * must be linked with a hub, the only other machine that can send to/receive
+ * from other Z levels.
+ */
/obj/machinery/telecomms/relay
name = "telecommunication relay"
icon_state = "relay"
@@ -14,10 +14,12 @@
density = TRUE
idle_power_usage = BASE_MACHINE_IDLE_CONSUMPTION * 0.01
netspeed = 5
- long_range_link = 1
+ long_range_link = TRUE
circuit = /obj/item/circuitboard/machine/telecomms/relay
- var/broadcasting = 1
- var/receiving = 1
+ /// Can this relay broadcast signals to other Z levels?
+ var/broadcasting = TRUE
+ /// Can this relay receive signals from other Z levels?
+ var/receiving = TRUE
/obj/machinery/telecomms/relay/receive_information(datum/signal/subspace/signal, obj/machinery/telecomms/machine_from)
// Add our level and send it back
@@ -25,33 +27,49 @@
if(can_send(signal) && relay_turf)
// Relays send signals to all ZTRAIT_STATION z-levels
if(SSmapping.level_trait(relay_turf.z, ZTRAIT_STATION))
- for(var/z in SSmapping.levels_by_trait(ZTRAIT_STATION))
- signal.levels |= SSmapping.get_connected_levels(z)
+ for(var/z_level in SSmapping.levels_by_trait(ZTRAIT_STATION))
+ signal.levels |= SSmapping.get_connected_levels(z_level)
else
signal.levels |= SSmapping.get_connected_levels(relay_turf)
use_power(idle_power_usage)
-/// Checks to see if it can send/receive.
-/obj/machinery/telecomms/relay/proc/can(datum/signal/signal)
+/**
+ * Checks to see if the relay can send/receive the signal, by checking if it's
+ * on, and if it's listening to the frequency of the signal.
+ *
+ * Returns `TRUE` if it can listen to the signal, `FALSE` if not.
+ */
+/obj/machinery/telecomms/relay/proc/can_listen_to_signal(datum/signal/signal)
if(!on)
return FALSE
if(!is_freq_listening(signal))
return FALSE
return TRUE
+/**
+ * Checks to see if the relay can send this signal, which requires it to have
+ * `broadcasting` set to `TRUE`.
+ *
+ * Returns `TRUE` if it can send the signal, `FALSE` if not.
+ */
/obj/machinery/telecomms/relay/proc/can_send(datum/signal/signal)
- if(!can(signal))
+ if(!can_listen_to_signal(signal))
return FALSE
return broadcasting
+/**
+ * Checks to see if the relay can receive this signal, which requires it to have
+ * `receiving` set to `TRUE`.
+ *
+ * Returns `TRUE` if it can receive the signal, `FALSE` if not.
+ */
/obj/machinery/telecomms/relay/proc/can_receive(datum/signal/signal)
- if(!can(signal))
+ if(!can_listen_to_signal(signal))
return FALSE
return receiving
-//Preset Relay
-
+// Preset Relays
/obj/machinery/telecomms/relay/preset
network = "tcommsat"
@@ -78,7 +96,7 @@
toggled = FALSE
autolinkers = list("r_relay")
-//Generic preset relay
+// Generic preset relay
/obj/machinery/telecomms/relay/preset/auto
hide = TRUE
autolinkers = list("autorelay")
diff --git a/code/game/machinery/telecomms/machines/server.dm b/code/game/machinery/telecomms/machines/server.dm
index 4bcc2c70a73..fedcf519ff2 100644
--- a/code/game/machinery/telecomms/machines/server.dm
+++ b/code/game/machinery/telecomms/machines/server.dm
@@ -1,10 +1,11 @@
-/*
- The server logs all traffic and signal data. Once it records the signal, it sends
- it to the subspace broadcaster.
-
- Store a maximum of 100 logs and then deletes them.
-*/
-
+#define MAX_LOG_ENTRIES 400
+
+/**
+ * The server logs all traffic and signal data. Once it records the signal, it
+ * sends it to the subspace broadcaster.
+ *
+ * Store a maximum of `MAX_LOG_ENTRIES` (400) log entries and then deletes them.
+ */
/obj/machinery/telecomms/server
name = "telecommunication server"
icon_state = "comm_server"
@@ -13,8 +14,13 @@
density = TRUE
idle_power_usage = BASE_MACHINE_IDLE_CONSUMPTION * 0.01
circuit = /obj/item/circuitboard/machine/telecomms/server
+ /// A list of previous entries on the network. It will not exceed
+ /// `MAX_LOG_ENTRIES` entries in length, flushing the oldest entries
+ /// automatically.
var/list/log_entries = list()
- var/totaltraffic = 0 // gigabytes (if > 1024, divide by 1024 -> terrabytes)
+ /// Total trafic, which is increased every time a signal is increased and
+ /// the current traffic is higher than 0. See `traffic` for more info.
+ var/total_traffic = 0
/obj/machinery/telecomms/server/receive_information(datum/signal/subspace/vocal/signal, obj/machinery/telecomms/machine_from)
// can't log non-vocal signals
@@ -22,10 +28,10 @@
return
if(traffic > 0)
- totaltraffic += traffic // add current traffic to total traffic
+ total_traffic += traffic // add current traffic to total traffic
// Delete particularly old logs
- if (log_entries.len >= 400)
+ if (log_entries.len >= MAX_LOG_ENTRIES)
log_entries.Cut(1, 2)
// Don't create a log if the frequency is banned from being logged
@@ -39,7 +45,7 @@
// If the signal is still compressed, make the log entry gibberish
var/compression = signal.data["compression"]
- if(compression > 0)
+ if(compression > NONE)
log.input_type = "Corrupt File"
var/replace_characters = compression >= 20 ? TRUE : FALSE
log.parameters["name"] = Gibberish(signal.data["name"], replace_characters)
@@ -47,7 +53,7 @@
log.parameters["message"] = Gibberish(signal.data["message"], replace_characters)
// Give the log a name and store it
- var/identifier = num2text( rand(-1000,1000) + world.time )
+ var/identifier = num2text(rand(-1000, 1000) + world.time)
log.name = "data packet ([md5(identifier)])"
log_entries.Add(log)
@@ -57,11 +63,16 @@
use_power(idle_power_usage)
-// Simple log entry datum
+#undef MAX_LOG_ENTRIES
+
+/// Simple log entry datum for the telecommunication server
/datum/comm_log_entry
+ /// Type of entry.
var/input_type = "Speech File"
+ /// Name of the entry.
var/name = "data packet (#)"
- var/parameters = list() // copied from signal.data above
+ /// Parameters extracted from the signal.
+ var/parameters = list()
// Preset Servers
@@ -98,9 +109,9 @@
freq_listening = list()
autolinkers = list("common")
-//Common and other radio frequencies for people to freely use
/obj/machinery/telecomms/server/presets/common/Initialize(mapload)
. = ..()
+ // Common and other radio frequencies for people to freely use
for(var/i = MIN_FREQ, i <= MAX_FREQ, i += 2)
freq_listening |= i
diff --git a/code/game/machinery/telecomms/telecomunications.dm b/code/game/machinery/telecomms/telecomunications.dm
index 95d5ca581ca..b8d0414ad28 100644
--- a/code/game/machinery/telecomms/telecomunications.dm
+++ b/code/game/machinery/telecomms/telecomunications.dm
@@ -1,19 +1,11 @@
-
-/*
- Hello, friends, this is Doohl from sexylands. You may be wondering what this
- monstrous code file is. Sit down, boys and girls, while I tell you the tale.
-
-
- The telecom machines were designed to be compatible with any radio
- signals, provided they use subspace transmission. Currently they are only used for
- headsets, but they can eventually be outfitted for real COMPUTER networks. This
- is just a skeleton, ladies and gentlemen.
-
- Look at radio.dm for the prequel to this code.
-*/
-
+/// A list of all of the `/obj/machinery/telecomms` (and subtypes) machines
+/// that exist in the world currently.
GLOBAL_LIST_EMPTY(telecomms_list)
+/**
+ * The basic telecomms machinery type, implementing all of the logic that's
+ * shared between all of the telecomms machinery.
+ */
/obj/machinery/telecomms
icon = 'icons/obj/machines/telecomms.dmi'
critical_machine = TRUE
@@ -40,15 +32,16 @@ GLOBAL_LIST_EMPTY(telecomms_list)
// list of frequencies to tune into: if none, will listen to all
var/list/freq_listening = list()
+ /// Is it actually active or not?
var/on = TRUE
- /// Is it toggled on
+ /// Is it toggled on, so is it /meant/ to be active?
var/toggled = TRUE
/// Can you link it across Z levels or on the otherside of the map? (Relay & Hub)
var/long_range_link = FALSE
/// Is it a hidden machine?
var/hide = FALSE
- ///Looping sounds for any servers
+ /// Looping sounds for any servers
var/datum/looping_sound/server/soundloop
/// relay signal to all linked machinery that are of type [filter]. If signal has been sent [amount] times, stop sending
@@ -90,16 +83,20 @@ GLOBAL_LIST_EMPTY(telecomms_list)
return send_count
+/// Sends a signal directly to a machine.
/obj/machinery/telecomms/proc/relay_direct_information(datum/signal/signal, obj/machinery/telecomms/machine)
- // send signal directly to a machine
machine.receive_information(signal, src)
-///receive information from linked machinery
+/// Receive information from linked machinery
/obj/machinery/telecomms/proc/receive_information(datum/signal/signal, obj/machinery/telecomms/machine_from)
return
+/**
+ * Checks whether the machinery is listening to that signal.
+ *
+ * Returns `TRUE` if found, `FALSE` if not.
+ */
/obj/machinery/telecomms/proc/is_freq_listening(datum/signal/signal)
- // return TRUE if found, FALSE if not found
return signal && (!length(freq_listening) || (signal.frequency in freq_listening))
/obj/machinery/telecomms/Initialize(mapload)
@@ -122,17 +119,17 @@ GLOBAL_LIST_EMPTY(telecomms_list)
links = list()
return ..()
-/// Used in auto linking
-/obj/machinery/telecomms/proc/add_automatic_link(obj/machinery/telecomms/T)
+/// Handles the automatic linking of another machine to this one.
+/obj/machinery/telecomms/proc/add_automatic_link(obj/machinery/telecomms/machine_to_link)
var/turf/position = get_turf(src)
- var/turf/T_position = get_turf(T)
- if((position.z != T_position.z) && !(long_range_link && T.long_range_link))
+ var/turf/T_position = get_turf(machine_to_link)
+ if((position.z != T_position.z) && !(long_range_link && machine_to_link.long_range_link))
return
- if(src == T)
+ if(src == machine_to_link)
return
for(var/autolinker_id in autolinkers)
- if(autolinker_id in T.autolinkers)
- add_new_link(T)
+ if(autolinker_id in machine_to_link.autolinkers)
+ add_new_link(machine_to_link)
return
/obj/machinery/telecomms/update_icon_state()
@@ -143,6 +140,11 @@ GLOBAL_LIST_EMPTY(telecomms_list)
update_appearance()
return ..()
+/**
+ * Handles updating the power state of the machine, modifying its `on`
+ * variable based on if it's `toggled` and if it's either broken, has no power
+ * or it's EMP'd. Handles updating appearance based on that power change.
+ */
/obj/machinery/telecomms/proc/update_power()
var/old_on = on
if(toggled)
@@ -167,8 +169,9 @@ GLOBAL_LIST_EMPTY(telecomms_list)
return
if(prob(100/severity) && !(machine_stat & EMPED))
set_machine_stat(machine_stat | EMPED)
- var/duration = (300 * 10)/severity
- addtimer(CALLBACK(src, PROC_REF(de_emp)), rand(duration - 20, duration + 20))
+ var/duration = (300 SECONDS)/severity
+ addtimer(CALLBACK(src, PROC_REF(de_emp)), rand(duration - 2 SECONDS, duration + 2 SECONDS))
+/// Handles the machine stopping being affected by an EMP.
/obj/machinery/telecomms/proc/de_emp()
set_machine_stat(machine_stat & ~EMPED)
diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm
index c10c04a9fa1..a8aeab47614 100644
--- a/code/game/objects/items.dm
+++ b/code/game/objects/items.dm
@@ -520,7 +520,6 @@
return
var/datum/fantasy_affix/affix = affixes[picked_affix_name]
affixes.Remove(affix)
- QDEL_LIST_ASSOC(affixes) //remove the rest, we didn't use them
var/fantasy_quality = 0
if(affix.alignment & AFFIX_GOOD)
fantasy_quality++
diff --git a/code/game/objects/items/devices/laserpointer.dm b/code/game/objects/items/devices/laserpointer.dm
index f595d376c3b..fae8d3950fd 100644
--- a/code/game/objects/items/devices/laserpointer.dm
+++ b/code/game/objects/items/devices/laserpointer.dm
@@ -71,7 +71,7 @@
diode = null
return TRUE
-/obj/item/laser_pointer/tool_act(mob/living/user, obj/item/tool)
+/obj/item/laser_pointer/tool_act(mob/living/user, obj/item/tool, tool_type, is_right_clicking)
. = ..()
if(isnull(crystal_lens) || !(tool.tool_behaviour == TOOL_WIRECUTTER || tool.tool_behaviour == TOOL_HEMOSTAT))
return
diff --git a/code/game/objects/items/dna_probe.dm b/code/game/objects/items/dna_probe.dm
index 4ea89d0a95e..6c9651a3142 100644
--- a/code/game/objects/items/dna_probe.dm
+++ b/code/game/objects/items/dna_probe.dm
@@ -135,7 +135,7 @@
to_chat(user, span_notice("You pull out the needle from [src] and flip the switch, and start injecting yourself with it."))
if(!do_after(user, CARP_MIX_DNA_TIMER))
return
- var/mob/living/simple_animal/hostile/space_dragon/new_dragon = user.change_mob_type(/mob/living/simple_animal/hostile/space_dragon, location = loc, delete_old_mob = TRUE)
+ var/mob/living/basic/space_dragon/new_dragon = user.change_mob_type(/mob/living/basic/space_dragon, location = loc, delete_old_mob = TRUE)
new_dragon.add_filter("anger_glow", 3, list("type" = "outline", "color" = "#ff330030", "size" = 5))
new_dragon.add_movespeed_modifier(/datum/movespeed_modifier/dragon_rage)
priority_announce("A large organic energy flux has been recorded near of [station_name()], please stand-by.", "Lifesign Alert")
diff --git a/code/game/objects/items/food/monkeycube.dm b/code/game/objects/items/food/monkeycube.dm
index ffc9b63c62f..d61b05477de 100644
--- a/code/game/objects/items/food/monkeycube.dm
+++ b/code/game/objects/items/food/monkeycube.dm
@@ -8,7 +8,6 @@
foodtypes = MEAT | SUGAR
food_flags = FOOD_FINGER_FOOD
w_class = WEIGHT_CLASS_TINY
- var/faction
var/spawned_mob = /mob/living/carbon/human/species/monkey
/obj/item/food/monkeycube/proc/Expand()
diff --git a/code/game/objects/items/stacks/golem_food/golem_status_effects.dm b/code/game/objects/items/stacks/golem_food/golem_status_effects.dm
index 5ed7fcd4682..e52c5f8034c 100644
--- a/code/game/objects/items/stacks/golem_food/golem_status_effects.dm
+++ b/code/game/objects/items/stacks/golem_food/golem_status_effects.dm
@@ -343,7 +343,7 @@
if (!.)
return FALSE
var/mob/living/carbon/human/human_owner = owner
- RegisterSignal(human_owner, COMSIG_HUMAN_MELEE_UNARMED_ATTACK, PROC_REF(on_punched))
+ RegisterSignal(human_owner, COMSIG_LIVING_UNARMED_ATTACK, PROC_REF(on_punched))
human_owner.physiology.brute_mod *= brute_modifier
for (var/obj/item/bodypart/arm/arm in human_owner.bodyparts)
if (arm.limb_id != SPECIES_GOLEM)
@@ -354,10 +354,10 @@
/datum/status_effect/golem/titanium/proc/on_punched(mob/living/puncher, atom/punchee, proximity)
SIGNAL_HANDLER
if (!proximity || !isliving(punchee))
- return
+ return NONE
var/mob/living/victim = punchee
if (victim.body_position == LYING_DOWN || (!(FACTION_MINING in victim.faction) && !(FACTION_BOSS in victim.faction)))
- return
+ return NONE
victim.apply_damage(mining_bonus, BRUTE)
/// Make the targeted arm big and strong
@@ -370,7 +370,7 @@
/datum/status_effect/golem/titanium/on_remove()
var/mob/living/carbon/human/human_owner = owner
- UnregisterSignal(human_owner, COMSIG_HUMAN_MELEE_UNARMED_ATTACK)
+ UnregisterSignal(human_owner, COMSIG_LIVING_UNARMED_ATTACK)
human_owner.physiology.brute_mod /= brute_modifier
for (var/obj/item/bodypart/arm/arm as anything in modified_arms)
debuff_arm(arm)
diff --git a/code/game/objects/items/storage/secure.dm b/code/game/objects/items/storage/secure.dm
index ad4be8d6e40..0d845b5254e 100644
--- a/code/game/objects/items/storage/secure.dm
+++ b/code/game/objects/items/storage/secure.dm
@@ -40,7 +40,7 @@
. = ..()
icon_state = "[initial(icon_state)][atom_storage?.locked ? "_locked" : null]"
-/obj/item/storage/secure/tool_act(mob/living/user, obj/item/tool)
+/obj/item/storage/secure/tool_act(mob/living/user, obj/item/tool, tool_type, is_right_clicking)
if(can_hack_open && atom_storage.locked)
return ..()
else
diff --git a/code/game/objects/items/tcg/tcg_machines.dm b/code/game/objects/items/tcg/tcg_machines.dm
index a38bb8a9599..767592535f7 100644
--- a/code/game/objects/items/tcg/tcg_machines.dm
+++ b/code/game/objects/items/tcg/tcg_machines.dm
@@ -293,9 +293,9 @@ GLOBAL_LIST_EMPTY(tcgcard_machine_radial_choices)
use_power = NO_POWER_USE
///Reference to the display panel generated by this button.
- var/obj/effect/decal/trading_card_panel/display_panel_ref
+ var/obj/effect/trading_card_panel/display_panel_ref
///Typepath of the display panel generated.
- var/display_panel_type = /obj/effect/decal/trading_card_panel
+ var/display_panel_type = /obj/effect/trading_card_panel
///Where the panel will be spawned in relation to the button on the X axis.
var/panel_offset_x = 1
///Where the panel will be spawned in relation to the button on the Y axis.
@@ -306,10 +306,7 @@ GLOBAL_LIST_EMPTY(tcgcard_mana_bar_radial_choices)
/obj/machinery/trading_card_button/Initialize(mapload)
. = ..()
- var/obj/effect/decal/trading_card_panel/new_panel = new display_panel_type(get_turf(src))
- new_panel.pixel_x = panel_offset_x
- new_panel.pixel_y = panel_offset_y
- display_panel_ref = new_panel
+ display_panel_ref = new display_panel_type(locate(x + panel_offset_x, y + panel_offset_y, z))
/obj/machinery/trading_card_button/Destroy()
QDEL_NULL(display_panel_ref)
@@ -367,8 +364,8 @@ GLOBAL_LIST_EMPTY(tcgcard_mana_bar_radial_choices)
name = "life control panel"
desc = "A set of buttons that lets you keep track of your life shards when playing Tactical Game Cards."
icon_state = "health_buttons"
- display_panel_type = /obj/effect/decal/trading_card_panel/health
- panel_offset_x = -24
+ display_panel_type = /obj/effect/trading_card_panel/health
+ panel_offset_x = -1
///Global list containing all options used for the TGC health button.
GLOBAL_LIST_EMPTY(tcgcard_health_bar_radial_choices)
@@ -395,11 +392,12 @@ GLOBAL_LIST_EMPTY(tcgcard_health_bar_radial_choices)
display_panel_ref.gems -= tgui_input_number(user, "Please input total damage", "Inflict damage", 1, display_panel_ref.gem_slots, 0)
///A display panel that renders a set of icons (in this case mana crystals), this is generated by /obj/machinery/trading_card_button and can be manipulated by the button which generates it.
-/obj/effect/decal/trading_card_panel
+/obj/effect/trading_card_panel
name = "mana panel"
icon = 'icons/obj/toys/tcgmisc_large.dmi'
icon_state = "display_panel"
pixel_x = -10
+ anchored = TRUE
///How much "active" gems will appear
var/gems = 1
@@ -428,11 +426,11 @@ GLOBAL_LIST_EMPTY(tcgcard_health_bar_radial_choices)
///The name of what this panel tracks, used in the description
var/gem_title = "mana"
-/obj/effect/decal/trading_card_panel/Initialize(mapload)
+/obj/effect/trading_card_panel/Initialize(mapload)
. = ..()
update_icon(UPDATE_OVERLAYS)
-/obj/effect/decal/trading_card_panel/update_overlays()
+/obj/effect/trading_card_panel/update_overlays()
. = ..()
if(!gem_slots)
return
@@ -447,12 +445,12 @@ GLOBAL_LIST_EMPTY(tcgcard_health_bar_radial_choices)
gem_overlay.pixel_w = (gem - 1) * individual_gem_offset_x + gem_bar_offset_w
. += gem_overlay
-/obj/effect/decal/trading_card_panel/examine(mob/user)
+/obj/effect/trading_card_panel/examine(mob/user)
. = ..()
. += span_notice("It is currently showing [gems] out of [gem_slots] [gem_title].")
///A variant of the display panel for life shards, this one is set up to display two columns.
-/obj/effect/decal/trading_card_panel/health
+/obj/effect/trading_card_panel/health
name = "life shard panel"
pixel_x = 9
diff --git a/code/game/objects/structures/false_walls.dm b/code/game/objects/structures/false_walls.dm
index 6ac17e1ae02..8cb4b487c67 100644
--- a/code/game/objects/structures/false_walls.dm
+++ b/code/game/objects/structures/false_walls.dm
@@ -84,7 +84,7 @@
qdel(src)
return T
-/obj/structure/falsewall/tool_act(mob/living/user, obj/item/tool)
+/obj/structure/falsewall/tool_act(mob/living/user, obj/item/tool, tool_type, is_right_clicking)
if(!opening)
return ..()
to_chat(user, span_warning("You must wait until the door has stopped moving!"))
diff --git a/code/game/objects/structures/fluff.dm b/code/game/objects/structures/fluff.dm
index 78085b37d55..15298367bc0 100644
--- a/code/game/objects/structures/fluff.dm
+++ b/code/game/objects/structures/fluff.dm
@@ -287,6 +287,7 @@
/obj/structure/fluff/broken_canister_frame
name = "broken canister frame"
+ desc = "A torn apart canister. It looks like some metal can be salvaged with a wrench."
icon_state = "broken_canister"
anchored = FALSE
density = TRUE
diff --git a/code/game/objects/structures/spawner.dm b/code/game/objects/structures/spawner.dm
index 2ad70bdbc84..b654e0d43f4 100644
--- a/code/game/objects/structures/spawner.dm
+++ b/code/game/objects/structures/spawner.dm
@@ -8,11 +8,12 @@
anchored = TRUE
density = TRUE
+ faction = list(FACTION_HOSTILE)
+
var/max_mobs = 5
var/spawn_time = 30 SECONDS
var/mob_types = list(/mob/living/basic/carp)
var/spawn_text = "emerges from"
- var/faction = list(FACTION_HOSTILE)
var/spawner_type = /datum/component/spawner
/// Is this spawner taggable with something?
var/scanner_taggable = FALSE
diff --git a/code/game/turfs/closed/walls.dm b/code/game/turfs/closed/walls.dm
index f28c6b41f80..f8d411acee4 100644
--- a/code/game/turfs/closed/walls.dm
+++ b/code/game/turfs/closed/walls.dm
@@ -34,48 +34,63 @@
var/list/dent_decals
-/turf/closed/wall/MouseDrop_T(mob/living/carbon/carbon_mob, mob/user)
+/turf/closed/wall/MouseDrop_T(atom/dropping, mob/user, params)
..()
- if(carbon_mob != user)
+ if(dropping != user)
return
- if(carbon_mob.is_leaning == TRUE)
+ if(!iscarbon(dropping) && !iscyborg(dropping))
return
- if(carbon_mob.pulledby)
+ var/mob/living/leaner = dropping
+ if(leaner.incapacitated(IGNORE_RESTRAINTS) || leaner.stat != CONSCIOUS || HAS_TRAIT(leaner, TRAIT_NO_TRANSFORM))
return
- if(!carbon_mob.density)
+ if(!leaner.density || leaner.pulledby || leaner.buckled || !(leaner.mobility_flags & MOBILITY_STAND))
return
- var/turf/checked_turf = get_step(carbon_mob, REVERSE_DIR(carbon_mob.dir))
- if(checked_turf == src)
- carbon_mob.start_leaning(src)
-
-/mob/living/carbon/proc/start_leaning(obj/wall)
+ if(HAS_TRAIT_FROM(leaner, TRAIT_UNDENSE, LEANING_TRAIT))
+ return
+ var/turf/checked_turf = get_step(leaner, REVERSE_DIR(leaner.dir))
+ if(checked_turf != src)
+ return
+ leaner.start_leaning(src)
+/mob/living/proc/start_leaning(turf/closed/wall/wall)
+ var/new_y = base_pixel_y + pixel_y
+ var/new_x = base_pixel_x + pixel_x
switch(dir)
if(SOUTH)
- pixel_y += LEANING_OFFSET
+ new_y += LEANING_OFFSET
if(NORTH)
- pixel_y += -LEANING_OFFSET
+ new_y -= LEANING_OFFSET
if(WEST)
- pixel_x += LEANING_OFFSET
+ new_x += LEANING_OFFSET
if(EAST)
- pixel_x += -LEANING_OFFSET
-
- ADD_TRAIT(src, TRAIT_UNDENSE, LEANING_TRAIT)
- ADD_TRAIT(src, TRAIT_EXPANDED_FOV, LEANING_TRAIT)
- visible_message(span_notice("[src] leans against \the [wall]!"), \
- span_notice("You lean against \the [wall]!"))
- RegisterSignals(src, list(COMSIG_MOB_CLIENT_PRE_MOVE, COMSIG_HUMAN_DISARM_HIT, COMSIG_LIVING_GET_PULLED, COMSIG_MOVABLE_TELEPORTING, COMSIG_ATOM_DIR_CHANGE), PROC_REF(stop_leaning))
+ new_x -= LEANING_OFFSET
+
+ animate(src, 0.2 SECONDS, pixel_x = new_x, pixel_y = new_y)
+ add_traits(list(TRAIT_UNDENSE, TRAIT_EXPANDED_FOV), LEANING_TRAIT)
+ visible_message(
+ span_notice("[src] leans against [wall]."),
+ span_notice("You lean against [wall]."),
+ )
+ RegisterSignals(src, list(
+ COMSIG_MOB_CLIENT_PRE_MOVE,
+ COMSIG_HUMAN_DISARM_HIT,
+ COMSIG_LIVING_GET_PULLED,
+ COMSIG_MOVABLE_TELEPORTING,
+ COMSIG_ATOM_DIR_CHANGE,
+ ), PROC_REF(stop_leaning))
update_fov()
- is_leaning = TRUE
-/mob/living/carbon/proc/stop_leaning()
+/mob/living/proc/stop_leaning()
SIGNAL_HANDLER
- UnregisterSignal(src, list(COMSIG_MOB_CLIENT_PRE_MOVE, COMSIG_HUMAN_DISARM_HIT, COMSIG_LIVING_GET_PULLED, COMSIG_MOVABLE_TELEPORTING, COMSIG_ATOM_DIR_CHANGE))
- is_leaning = FALSE
- pixel_y = base_pixel_y + body_position_pixel_x_offset
- pixel_x = base_pixel_y + body_position_pixel_y_offset
- REMOVE_TRAIT(src, TRAIT_UNDENSE, LEANING_TRAIT)
- REMOVE_TRAIT(src, TRAIT_EXPANDED_FOV, LEANING_TRAIT)
+ UnregisterSignal(src, list(
+ COMSIG_MOB_CLIENT_PRE_MOVE,
+ COMSIG_HUMAN_DISARM_HIT,
+ COMSIG_LIVING_GET_PULLED,
+ COMSIG_MOVABLE_TELEPORTING,
+ COMSIG_ATOM_DIR_CHANGE,
+ ))
+ animate(src, 0.2 SECONDS, pixel_x = base_pixel_x, pixel_y = base_pixel_y)
+ remove_traits(list(TRAIT_UNDENSE, TRAIT_EXPANDED_FOV), LEANING_TRAIT)
update_fov()
/turf/closed/wall/Initialize(mapload)
diff --git a/code/game/turfs/open/floor/plating.dm b/code/game/turfs/open/floor/plating.dm
index eee4797887b..a38338010d4 100644
--- a/code/game/turfs/open/floor/plating.dm
+++ b/code/game/turfs/open/floor/plating.dm
@@ -177,7 +177,7 @@
ScrapeAway(flags = CHANGETURF_INHERIT_AIR)
return TRUE
-/turf/open/floor/plating/foam/tool_act(mob/living/user, obj/item/I, tool_type)
+/turf/open/floor/plating/foam/tool_act(mob/living/user, obj/item/tool, tool_type, is_right_clicking)
return
//reinforced plating deconstruction states
diff --git a/code/modules/antagonists/heretic/heretic_knowledge.dm b/code/modules/antagonists/heretic/heretic_knowledge.dm
index 1f408698f3c..a885c24a6a4 100644
--- a/code/modules/antagonists/heretic/heretic_knowledge.dm
+++ b/code/modules/antagonists/heretic/heretic_knowledge.dm
@@ -177,9 +177,11 @@
var/obj/item/stack/sac_stack = sacrificed
var/how_much_to_use = 0
for(var/requirement in required_atoms)
- if(islist(requirement) && !is_type_in_list(sacrificed, requirement))
+ // If it's not requirement type and type is not a list, skip over this check
+ if(!istype(sacrificed, requirement) && !islist(requirement))
continue
- if(!istype(sacrificed, requirement))
+ // If requirement *is* a list and the stack *is* in the list, skip over this check
+ if(islist(requirement) && !is_type_in_list(sacrificed, requirement))
continue
how_much_to_use = min(required_atoms[requirement], sac_stack.amount)
break
diff --git a/code/modules/antagonists/heretic/knowledge/blade_lore.dm b/code/modules/antagonists/heretic/knowledge/blade_lore.dm
index f2f3b156a2f..55102c12c79 100644
--- a/code/modules/antagonists/heretic/knowledge/blade_lore.dm
+++ b/code/modules/antagonists/heretic/knowledge/blade_lore.dm
@@ -32,13 +32,13 @@
/datum/heretic_knowledge/limited_amount/starting/base_blade
name = "The Cutting Edge"
desc = "Opens up the Path of Blades to you. \
- Allows you to transmute a knife with two bars of silver to create a Sundered Blade. \
+ Allows you to transmute a knife with two bars of silver or titanium to create a Sundered Blade. \
You can create up to five at a time."
gain_text = "Our great ancestors forged swords and practiced sparring on the eve of great battles."
next_knowledge = list(/datum/heretic_knowledge/blade_grasp)
required_atoms = list(
/obj/item/knife = 1,
- /obj/item/stack/sheet/mineral/silver = 2,
+ list(/obj/item/stack/sheet/mineral/silver, /obj/item/stack/sheet/mineral/titanium) = 2,
)
result_atoms = list(/obj/item/melee/sickly_blade/dark)
limit = 5 // It's the blade path, it's a given
diff --git a/code/modules/antagonists/space_dragon/space_dragon.dm b/code/modules/antagonists/space_dragon/space_dragon.dm
index 7c49bed6b95..574e458faca 100644
--- a/code/modules/antagonists/space_dragon/space_dragon.dm
+++ b/code/modules/antagonists/space_dragon/space_dragon.dm
@@ -157,6 +157,7 @@
if(objective_complete)
return
rifts_charged = 0
+ ADD_TRAIT(owner.current, TRAIT_RIFT_FAILURE, REF(src))
owner.current.add_movespeed_modifier(/datum/movespeed_modifier/dragon_depression)
riftTimer = -1
SEND_SOUND(owner.current, sound('sound/vehicles/rocketlaunch.ogg'))
diff --git a/code/modules/antagonists/traitor/objectives/hack_comm_console.dm b/code/modules/antagonists/traitor/objectives/hack_comm_console.dm
index 52e0397cc60..93323e4e15f 100644
--- a/code/modules/antagonists/traitor/objectives/hack_comm_console.dm
+++ b/code/modules/antagonists/traitor/objectives/hack_comm_console.dm
@@ -23,7 +23,7 @@
/datum/traitor_objective/hack_comm_console/generate_objective(datum/mind/generating_for, list/possible_duplicates)
AddComponent(/datum/component/traitor_objective_mind_tracker, generating_for, \
- signals = list(COMSIG_HUMAN_EARLY_UNARMED_ATTACK = PROC_REF(on_unarmed_attack)))
+ signals = list(COMSIG_LIVING_UNARMED_ATTACK = PROC_REF(on_unarmed_attack)))
RegisterSignal(SSdcs, COMSIG_GLOB_TRAITOR_OBJECTIVE_COMPLETED, PROC_REF(on_global_obj_completed))
return TRUE
diff --git a/code/modules/atmospherics/machinery/air_alarm/_air_alarm.dm b/code/modules/atmospherics/machinery/air_alarm/_air_alarm.dm
index e629c14e0fe..24d1e63e42a 100644
--- a/code/modules/atmospherics/machinery/air_alarm/_air_alarm.dm
+++ b/code/modules/atmospherics/machinery/air_alarm/_air_alarm.dm
@@ -287,6 +287,8 @@ GLOBAL_LIST_EMPTY_TYPED(air_alarms, /obj/machinery/airalarm)
"refID" = REF(vent),
"long_name" = sanitize(vent.name),
"power" = vent.on,
+ "overclock" = vent.fan_overclocked,
+ "integrity" = vent.get_integrity_percentage(),
"checks" = vent.pressure_checks,
"excheck" = vent.pressure_checks & ATMOS_EXTERNAL_BOUND,
"incheck" = vent.pressure_checks & ATMOS_INTERNAL_BOUND,
@@ -356,6 +358,14 @@ GLOBAL_LIST_EMPTY_TYPED(air_alarms, /obj/machinery/airalarm)
powering.on = !!params["val"]
powering.atmos_conditions_changed()
powering.update_appearance(UPDATE_ICON)
+
+ if("overclock")
+ if(isnull(vent))
+ return TRUE
+ vent.toggle_overclock()
+ vent.update_appearance(UPDATE_ICON)
+ return TRUE
+
if ("direction")
if (isnull(vent))
return TRUE
diff --git a/code/modules/atmospherics/machinery/components/unary_devices/vent_pump.dm b/code/modules/atmospherics/machinery/components/unary_devices/vent_pump.dm
index 3422f3e3adf..25713e55e37 100644
--- a/code/modules/atmospherics/machinery/components/unary_devices/vent_pump.dm
+++ b/code/modules/atmospherics/machinery/components/unary_devices/vent_pump.dm
@@ -15,6 +15,8 @@
shift_underlay_only = FALSE
pipe_state = "uvent"
vent_movement = VENTCRAWL_ALLOWED | VENTCRAWL_CAN_SEE | VENTCRAWL_ENTRANCE_ALLOWED
+ // vents are more complex machinery and so are less resistant to damage
+ max_integrity = 100
///Direction of pumping the gas (ATMOS_DIRECTION_RELEASING or ATMOS_DIRECTION_SIPHONING)
var/pump_direction = ATMOS_DIRECTION_RELEASING
@@ -34,6 +36,18 @@
///area this vent is assigned to
var/area/assigned_area
+ /// Is this vent currently overclocked, removing pressure limits but damaging the fan?
+ var/fan_overclocked = FALSE
+
+ /// Rate of damage per atmos process to the fan when overclocked. Set to 0 to disable damage.
+ var/fan_damage_rate = 0.5
+
+ /// The cached string we show for examine that lets you know how fucked up the fan is.
+ var/examine_condition
+
+ /// Datum for managing the overclock sound loop
+ var/datum/looping_sound/vent_pump_overclock/sound_loop
+
/obj/machinery/atmospherics/components/unary/vent_pump/Initialize(mapload)
if(!id_tag)
id_tag = assign_random_name()
@@ -42,16 +56,44 @@
tool_screentips = string_assoc_nested_list(list(
TOOL_MULTITOOL = list(
SCREENTIP_CONTEXT_LMB = "Log to link later with air sensor",
- )
+ ),
+ TOOL_SCREWDRIVER = list(
+ SCREENTIP_CONTEXT_LMB = "Repair",
+ ),
))
AddElement(/datum/element/contextual_screentip_tools, tool_screentips)
. = ..()
+ sound_loop = new(src)
assign_to_area()
+/obj/machinery/atmospherics/components/unary/vent_pump/on_update_integrity(old_value, new_value)
+ . = ..()
+ var/condition_string
+ switch(get_integrity_percentage())
+ if(1)
+ condition_string = "perfect"
+ if(0.75 to 0.99)
+ condition_string = "good"
+ if(0.50 to 0.74)
+ condition_string = "okay"
+ if(0.25 to 0.49)
+ condition_string = "bad"
+ else
+ condition_string = "terrible"
+ examine_condition = "The fan is in [condition_string] condition."
+
/obj/machinery/atmospherics/components/unary/vent_pump/examine(mob/user)
. = ..()
. += span_notice("You can link it with an air sensor using a multitool.")
+ if(fan_overclocked)
+ . += span_warning("It is currently overclocked causing it to take damage over time.")
+
+ if(get_integrity() > 0)
+ . += span_notice(examine_condition)
+ else
+ . += span_warning("The fan is broken.")
+
/obj/machinery/atmospherics/components/unary/vent_pump/multitool_act(mob/living/user, obj/item/multitool/multi_tool)
if(istype(multi_tool.buffer, /obj/machinery/air_sensor))
var/obj/machinery/air_sensor/sensor = multi_tool.buffer
@@ -63,8 +105,33 @@
multi_tool.set_buffer(src)
return TOOL_ACT_TOOLTYPE_SUCCESS
+/obj/machinery/atmospherics/components/unary/vent_pump/screwdriver_act(mob/living/user, obj/item/tool)
+ var/time_to_repair = (10 SECONDS) * (1 - get_integrity_percentage())
+ if(!time_to_repair)
+ return FALSE
+
+ balloon_alert(user, "repairing vent...")
+ if(do_after(user, time_to_repair, src))
+ balloon_alert(user, "vent repaired")
+ repair_damage(max_integrity)
+
+ else
+ balloon_alert(user, "interrupted!")
+ return TOOL_ACT_TOOLTYPE_SUCCESS
+
+/obj/machinery/atmospherics/components/unary/vent_pump/atom_fix()
+ set_is_operational(TRUE)
+ update_appearance()
+ return ..()
+
+/obj/machinery/atmospherics/components/unary/vent_pump/atom_break(damage_flag)
+ set_is_operational(FALSE)
+ update_appearance()
+ return ..()
+
/obj/machinery/atmospherics/components/unary/vent_pump/Destroy()
disconnect_from_area()
+ QDEL_NULL(sound_loop)
var/area/vent_area = get_area(src)
if(vent_area)
@@ -107,6 +174,17 @@
. = ..()
disconnect_from_area(area_to_unregister)
+/obj/machinery/atmospherics/components/unary/vent_pump/update_overlays()
+ . = ..()
+ if(!powered())
+ return
+
+ if(get_integrity() <= 0)
+ . += mutable_appearance(icon, "broken")
+
+ else if(fan_overclocked)
+ . += mutable_appearance(icon, "overclocked")
+
/obj/machinery/atmospherics/components/unary/vent_pump/update_icon_nopipes()
cut_overlays()
if(showpipe)
@@ -144,6 +222,20 @@
else // pump_direction == SIPHONING
icon_state = "vent_in"
+/obj/machinery/atmospherics/components/unary/vent_pump/proc/toggle_overclock(from_break = FALSE)
+ fan_overclocked = !fan_overclocked
+
+ if(from_break)
+ playsound(src, 'sound/machines/fan_break.ogg', 100)
+ fan_overclocked = FALSE
+
+ if(fan_overclocked)
+ sound_loop.start()
+ else
+ sound_loop.stop()
+
+ update_appearance()
+
/obj/machinery/atmospherics/components/unary/vent_pump/process_atmos()
if(!is_operational)
return
@@ -154,6 +246,17 @@
var/turf/open/us = loc
if(!istype(us))
return
+
+ var/current_integrity = get_integrity()
+ if(fan_overclocked)
+ take_damage(fan_damage_rate, sound_effect=FALSE)
+ if(current_integrity == 0)
+ on = FALSE
+ set_is_operational(FALSE)
+ toggle_overclock(from_break = TRUE)
+ return
+
+ var/percent_integrity = get_integrity_percentage()
var/datum/gas_mixture/air_contents = airs[1]
var/datum/gas_mixture/environment = us.return_air()
var/environment_pressure = environment.return_pressure()
@@ -168,9 +271,13 @@
if(pressure_delta > 0)
if(air_contents.temperature > 0)
- if(environment_pressure >= 50 * ONE_ATMOSPHERE)
+ if(!fan_overclocked && (environment_pressure >= 50 * ONE_ATMOSPHERE))
return FALSE
+
var/transfer_moles = (pressure_delta * environment.volume) / (air_contents.temperature * R_IDEAL_GAS_EQUATION)
+ if(!fan_overclocked && (percent_integrity < 1))
+ transfer_moles *= percent_integrity
+
var/datum/gas_mixture/removed = air_contents.remove(transfer_moles)
if(!removed || !removed.total_moles())
@@ -187,9 +294,12 @@
pressure_delta = min(pressure_delta, (internal_pressure_bound - air_contents.return_pressure()))
if(pressure_delta > 0 && environment.temperature > 0)
- if(air_contents.return_pressure() >= 50 * ONE_ATMOSPHERE)
+ if(!fan_overclocked && (air_contents.return_pressure() >= 50 * ONE_ATMOSPHERE))
return FALSE
+
var/transfer_moles = (pressure_delta * air_contents.volume) / (environment.temperature * R_IDEAL_GAS_EQUATION)
+ if(!fan_overclocked && (percent_integrity < 1))
+ transfer_moles *= percent_integrity
var/datum/gas_mixture/removed = loc.remove_air(transfer_moles)
diff --git a/code/modules/capture_the_flag/ctf_equipment.dm b/code/modules/capture_the_flag/ctf_equipment.dm
index 5ec412f9017..e822ae2dbb1 100644
--- a/code/modules/capture_the_flag/ctf_equipment.dm
+++ b/code/modules/capture_the_flag/ctf_equipment.dm
@@ -165,7 +165,7 @@
/obj/item/ammo_casing/energy/instakill
projectile_type = /obj/projectile/beam/instakill
- e_cost = 0
+ e_cost = 0 // Not possible to use the macro
select_name = "DESTROY"
/obj/projectile/beam/instakill
diff --git a/code/modules/capture_the_flag/ctf_game.dm b/code/modules/capture_the_flag/ctf_game.dm
index 1f6c44c293a..bfebcc706ca 100644
--- a/code/modules/capture_the_flag/ctf_game.dm
+++ b/code/modules/capture_the_flag/ctf_game.dm
@@ -152,11 +152,13 @@
if(player_mob.dna.species.outfit_important_for_life)
player_mob.set_species(/datum/species/human)
- player_mob.AddComponent( \
- /datum/component/temporary_body, \
- old_mind = new_team_member.mob.mind, \
- old_body = new_team_member.mob.mind.current, \
- )
+ var/datum/mind/new_member_mind = new_team_member.mob.mind
+ if(new_member_mind?.current)
+ player_mob.AddComponent( \
+ /datum/component/temporary_body, \
+ old_mind = new_member_mind, \
+ old_body = new_member_mind.current, \
+ )
player_mob.ckey = new_team_member.ckey
if(isnull(ctf_player_component))
diff --git a/code/modules/cards/cardhand.dm b/code/modules/cards/cardhand.dm
index 9dab4e65b58..ac14e17fea6 100644
--- a/code/modules/cards/cardhand.dm
+++ b/code/modules/cards/cardhand.dm
@@ -110,7 +110,7 @@
icon_state = null // we want an error icon to appear if this doesn't get qdel
return
- var/starting_card_pos = max(1, cards.len - CARDS_MAX_DISPLAY_LIMIT) // only display the top cards in the cardhand
+ var/starting_card_pos = max(0, cards.len - CARDS_MAX_DISPLAY_LIMIT) + 1 // only display the top cards in the cardhand, +1 because list indexes start at 1
var/cards_to_display = min(CARDS_MAX_DISPLAY_LIMIT, cards.len)
// 90 degrees from the 1st card to the last, so split the divider by total cards displayed
var/angle_divider = round(90/(cards_to_display - 1))
diff --git a/code/modules/cargo/universal_scanner.dm b/code/modules/cargo/universal_scanner.dm
index 880a75783d8..b9a2d01e14e 100644
--- a/code/modules/cargo/universal_scanner.dm
+++ b/code/modules/cargo/universal_scanner.dm
@@ -175,6 +175,7 @@
price += report.total_value[exported_datum]
if(price)
to_chat(user, span_notice("Scanned [target], value: [price] credits[target.contents.len ? " (contents included)" : ""]."))
+ playsound(src, 'sound/machines/terminal_select.ogg', 50, vary = TRUE)
else
to_chat(user, span_warning("Scanned [target], no export value."))
diff --git a/code/modules/client/client_procs.dm b/code/modules/client/client_procs.dm
index 56406139379..44591686aef 100644
--- a/code/modules/client/client_procs.dm
+++ b/code/modules/client/client_procs.dm
@@ -275,6 +275,7 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
prefs = GLOB.preferences_datums[ckey]
if(prefs)
prefs.parent = src
+ prefs.load_savefile() // just to make sure we have the latest data
prefs.apply_all_client_preferences()
else
prefs = new /datum/preferences(src)
diff --git a/code/modules/client/preferences/species_features/basic.dm b/code/modules/client/preferences/species_features/basic.dm
index 6e34a234e7d..abf4ea0e44e 100644
--- a/code/modules/client/preferences/species_features/basic.dm
+++ b/code/modules/client/preferences/species_features/basic.dm
@@ -1,4 +1,4 @@
-/proc/generate_icon_with_head_accessory(datum/sprite_accessory/sprite_accessory)
+/proc/generate_icon_with_head_accessory(datum/sprite_accessory/sprite_accessory, y_offset = 0)
var/static/icon/head_icon
if (isnull(head_icon))
head_icon = icon('icons/mob/human/bodyparts_greyscale.dmi', "human_head_m")
@@ -7,8 +7,10 @@
var/icon/final_icon = new(head_icon)
if (!isnull(sprite_accessory))
ASSERT(istype(sprite_accessory))
-
+
var/icon/head_accessory_icon = icon(sprite_accessory.icon, sprite_accessory.icon_state)
+ if(y_offset)
+ head_accessory_icon.Shift(NORTH, y_offset)
head_accessory_icon.Blend(COLOR_DARK_BROWN, ICON_MULTIPLY)
final_icon.Blend(head_accessory_icon, ICON_OVERLAY)
@@ -138,7 +140,8 @@
return assoc_to_keys_features(GLOB.hairstyles_list)
/datum/preference/choiced/hairstyle/icon_for(value)
- return generate_icon_with_head_accessory(GLOB.hairstyles_list[value])
+ var/datum/sprite_accessory/hair/hairstyle = GLOB.hairstyles_list[value]
+ return generate_icon_with_head_accessory(hairstyle, hairstyle?.y_offset)
/datum/preference/choiced/hairstyle/apply_to_human(mob/living/carbon/human/target, value)
target.set_hairstyle(value, update = FALSE)
diff --git a/code/modules/clothing/head/helmet.dm b/code/modules/clothing/head/helmet.dm
index f8cd88923ec..2960260255f 100644
--- a/code/modules/clothing/head/helmet.dm
+++ b/code/modules/clothing/head/helmet.dm
@@ -178,7 +178,7 @@
toggle_message = "You pull the visor down on"
alt_toggle_message = "You push the visor up on"
armor_type = /datum/armor/toggleable_riot
- flags_inv = HIDEEARS|HIDEFACE|HIDESNOUT
+ flags_inv = HIDEHAIR|HIDEEARS|HIDEFACE|HIDESNOUT
strip_delay = 80
actions_types = list(/datum/action/item_action/toggle)
visor_flags_inv = HIDEFACE|HIDESNOUT
diff --git a/code/modules/clothing/head/wig.dm b/code/modules/clothing/head/wig.dm
index b692c1383c5..4d3165f827c 100644
--- a/code/modules/clothing/head/wig.dm
+++ b/code/modules/clothing/head/wig.dm
@@ -25,7 +25,7 @@
item_flags &= ~EXAMINE_SKIP
/obj/item/clothing/head/wig/update_icon_state()
- var/datum/sprite_accessory/hair_style = GLOB.hairstyles_list[hairstyle]
+ var/datum/sprite_accessory/hair/hair_style = GLOB.hairstyles_list[hairstyle]
if(hair_style)
icon = hair_style.icon
icon_state = hair_style.icon_state
@@ -37,12 +37,13 @@
if(isinhands)
return
- var/datum/sprite_accessory/hair = GLOB.hairstyles_list[hairstyle]
+ var/datum/sprite_accessory/hair/hair = GLOB.hairstyles_list[hairstyle]
if(!hair)
return
var/mutable_appearance/hair_overlay = mutable_appearance(hair.icon, hair.icon_state, layer = -HAIR_LAYER, appearance_flags = RESET_COLOR)
hair_overlay.color = color
+ hair_overlay.pixel_y = hair.y_offset
. += hair_overlay
// So that the wig actually blocks emissives.
diff --git a/code/modules/clothing/shoes/gunboots.dm b/code/modules/clothing/shoes/gunboots.dm
index db24b2338cf..d8335b5fcb0 100644
--- a/code/modules/clothing/shoes/gunboots.dm
+++ b/code/modules/clothing/shoes/gunboots.dm
@@ -18,13 +18,13 @@
/obj/item/clothing/shoes/gunboots/equipped(mob/user, slot)
. = ..()
if(slot & ITEM_SLOT_FEET)
- RegisterSignal(user, COMSIG_HUMAN_MELEE_UNARMED_ATTACK, PROC_REF(check_kick))
+ RegisterSignal(user, COMSIG_LIVING_UNARMED_ATTACK, PROC_REF(check_kick))
else
- UnregisterSignal(user, COMSIG_HUMAN_MELEE_UNARMED_ATTACK)
+ UnregisterSignal(user, COMSIG_LIVING_UNARMED_ATTACK)
/obj/item/clothing/shoes/gunboots/dropped(mob/user)
if(user)
- UnregisterSignal(user, COMSIG_HUMAN_MELEE_UNARMED_ATTACK)
+ UnregisterSignal(user, COMSIG_LIVING_UNARMED_ATTACK)
return ..()
/// After each step, check if we randomly fire a shot
@@ -38,8 +38,8 @@
/// Stomping on someone while wearing gunboots shoots them point blank
/obj/item/clothing/shoes/gunboots/proc/check_kick(mob/living/carbon/human/kicking_person, atom/attacked_atom, proximity)
SIGNAL_HANDLER
- if(!isliving(attacked_atom))
- return
+ if(!proximity || !isliving(attacked_atom))
+ return NONE
var/mob/living/attacked_living = attacked_atom
if(attacked_living.body_position == LYING_DOWN)
INVOKE_ASYNC(src, PROC_REF(fire_shot), attacked_living)
diff --git a/code/modules/clothing/suits/ghostsheet.dm b/code/modules/clothing/suits/ghostsheet.dm
index e99d838dc50..268cca46199 100644
--- a/code/modules/clothing/suits/ghostsheet.dm
+++ b/code/modules/clothing/suits/ghostsheet.dm
@@ -10,6 +10,7 @@
flags_inv = HIDEGLOVES|HIDEEARS|HIDEFACE|HIDEHAIR|HIDEFACIALHAIR|HIDESNOUT
alternate_worn_layer = UNDER_HEAD_LAYER
species_exception = list(/datum/species/golem)
+ supports_variations_flags = CLOTHING_DIGITIGRADE_VARIATION_NO_NEW_ICON
/obj/item/clothing/suit/costume/ghost_sheet/Initialize(mapload)
. = ..()
diff --git a/code/modules/events/ghost_role/space_dragon.dm b/code/modules/events/ghost_role/space_dragon.dm
index 379b5d7e07b..dd75a97a869 100644
--- a/code/modules/events/ghost_role/space_dragon.dm
+++ b/code/modules/events/ghost_role/space_dragon.dm
@@ -31,7 +31,7 @@
if(isnull(spawn_location))
return MAP_ERROR
- var/mob/living/simple_animal/hostile/space_dragon/dragon = new (spawn_location)
+ var/mob/living/basic/space_dragon/dragon = new (spawn_location)
dragon.key = key
dragon.mind.add_antag_datum(/datum/antagonist/space_dragon)
playsound(dragon, 'sound/magic/ethereal_exit.ogg', 50, TRUE, -1)
diff --git a/code/modules/food_and_drinks/machinery/microwave.dm b/code/modules/food_and_drinks/machinery/microwave.dm
index d52e2213a5b..8488025149a 100644
--- a/code/modules/food_and_drinks/machinery/microwave.dm
+++ b/code/modules/food_and_drinks/machinery/microwave.dm
@@ -35,6 +35,8 @@
var/wire_disabled = FALSE
/// Wire cut to run mode backwards
var/wire_mode_swap = FALSE
+ /// Fail due to inserted PDA
+ var/pda_failure = FALSE
var/operating = FALSE
/// How dirty is it?
var/dirty = 0
@@ -123,8 +125,8 @@
else if(held_item && istype(held_item, /obj/item/stock_parts/cell))
context[SCREENTIP_CONTEXT_CTRL_LMB] = "Insert cell"
- if(!anchored && held_item?.tool_behaviour == TOOL_WRENCH)
- context[SCREENTIP_CONTEXT_LMB] = "Install/Secure"
+ if(held_item?.tool_behaviour == TOOL_WRENCH)
+ context[SCREENTIP_CONTEXT_LMB] = "[anchored ? "Unsecure" : "Install/Secure"]"
return CONTEXTUAL_SCREENTIP_SET
if(broken > NOT_BROKEN)
@@ -469,6 +471,9 @@
vampire_charging_enabled = !vampire_charging_enabled
balloon_alert(user, "set to [vampire_charging_enabled ? "charge" : "cook"]")
+ playsound(src, 'sound/machines/twobeep_high.ogg', 50, FALSE)
+ if(issilicon(user))
+ visible_message(span_notice("[user] sets \the [src] to [vampire_charging_enabled ? "charge" : "cook"]."), blind_message = span_notice("You hear \the [src] make an informative beep!"))
/obj/machinery/microwave/CtrlClick(mob/user)
. = ..()
@@ -571,6 +576,9 @@
for(var/atom/movable/potential_fooditem as anything in ingredients)
if(IS_EDIBLE(potential_fooditem))
non_food_ingedients--
+ if(istype(potential_fooditem, /obj/item/modular_computer/pda) && prob(75))
+ pda_failure = TRUE
+ notify_ghosts("[cooker] has overheated their PDA!", source = src, action = NOTIFY_JUMP, flashwindow = FALSE, header = "Hunger Games: Catching Fire")
// If we're cooking non-food items we can fail randomly
if(length(non_food_ingedients) && prob(min(dirty * 5, 100)))
@@ -662,15 +670,15 @@
*/
/obj/machinery/microwave/proc/loop_finish(mob/cooker)
operating = FALSE
+ if(pda_failure)
+ spark()
+ pda_failure = FALSE // in case they repair it after this, reset
+ broken = REALLY_BROKEN
+ explosion(src, heavy_impact_range = 1, light_impact_range = 2, flame_range = 1)
var/cursed_chef = cooker && HAS_TRAIT(cooker, TRAIT_CURSED)
var/metal_amount = 0
for(var/obj/item/cooked_item in ingredients)
- if(istype(cooked_item, /obj/item/modular_computer/pda) && prob(75))
- spark()
- broken = REALLY_BROKEN
- explosion(src, heavy_impact_range = 1, light_impact_range = 2, flame_range = 1)
-
var/sigreturn = cooked_item.microwave_act(src, cooker, randomize_pixel_offset = ingredients.len)
if(sigreturn & COMPONENT_MICROWAVE_SUCCESS)
if(isstack(cooked_item))
@@ -734,7 +742,6 @@
* * cooker - The mob that initiated the cook cycle, can be null if no apparent mob triggered it (such as via emp)
*/
/obj/machinery/microwave/proc/vampire(mob/cooker)
- wzhzhzh()
var/obj/item/modular_computer/pda/vampire_pda = LAZYACCESS(ingredients, 1)
if(isnull(vampire_pda))
playsound(src, 'sound/machines/buzz-sigh.ogg', 50, FALSE)
@@ -742,11 +749,12 @@
return
vampire_cell = vampire_pda.internal_cell
- if(isnull(vampire_pda))
+ if(isnull(vampire_cell))
playsound(src, 'sound/machines/buzz-sigh.ogg', 50, FALSE)
after_finish_loop()
return
+ wzhzhzh()
var/vampire_charge_amount = vampire_cell.maxcharge - vampire_cell.charge
charge_loop(vampire_charge_amount, cooker = cooker)
diff --git a/code/modules/food_and_drinks/recipes/soup_mixtures.dm b/code/modules/food_and_drinks/recipes/soup_mixtures.dm
index ad2caa84ca6..b35bc128d32 100644
--- a/code/modules/food_and_drinks/recipes/soup_mixtures.dm
+++ b/code/modules/food_and_drinks/recipes/soup_mixtures.dm
@@ -85,7 +85,7 @@
if(!length(required_ingredients))
return
- // If a food item is supposed to be made, remove relevant ingredients from the pot, then make the item
+ // If a food item is supposed to be made, remove relevant ingredients from the pot, then make the item
if(!isnull(resulting_food_path))
var/list/tracked_ingredients
LAZYINITLIST(tracked_ingredients)
@@ -112,7 +112,7 @@
for(var/reagent_path as anything in required_reagents)
holder.add_reagent(reagent_path,(required_reagents[reagent_path])*(created_volume-ingredient_max_multiplier))
-
+
// This only happens if we're being instant reacted so let's just skip to what we really want
if(isnull(reaction))
testing("Soup reaction of type [type] instant reacted, cleaning up.")
@@ -851,9 +851,9 @@
/datum/chemical_reaction/food/soup/monkey
required_reagents = list(
- /datum/reagent/water = 25,
+ /datum/reagent/water = 20,
/datum/reagent/consumable/flour = 5,
- /datum/reagent/consumable/salt = 5,
+ /datum/reagent/water/salt = 10,
/datum/reagent/consumable/blackpepper = 5,
)
required_ingredients = list(
@@ -2103,7 +2103,7 @@
name = "\improper Hong Kong macaroni soup"
icon = 'icons/obj/food/martian.dmi'
icon_state = "hong_kong_macaroni"
- drink_type = MEAT | VEGETABLES | GRAIN
+ drink_type = MEAT | VEGETABLES | GRAIN
/datum/chemical_reaction/food/soup/hong_kong_macaroni
required_reagents = list(
diff --git a/code/modules/hallucination/mother.dm b/code/modules/hallucination/mother.dm
new file mode 100644
index 00000000000..4e2a389260c
--- /dev/null
+++ b/code/modules/hallucination/mother.dm
@@ -0,0 +1,81 @@
+/// Your mother appears to scold you.
+/datum/hallucination/your_mother
+ random_hallucination_weight = 2
+ var/obj/effect/client_image_holder/hallucination/your_mother/mother
+
+/datum/hallucination/your_mother/start()
+ var/list/spawn_locs = list()
+ for(var/turf/open/floor in view(hallucinator, 4))
+ if(floor.is_blocked_turf(exclude_mobs = TRUE))
+ continue
+ spawn_locs += floor
+
+ if(!length(spawn_locs))
+ return FALSE
+ var/turf/spawn_loc = pick(spawn_locs)
+ mother = new(spawn_loc, hallucinator, src)
+ mother.AddComponent(/datum/component/leash, owner = hallucinator, distance = get_dist(hallucinator, mother)) //basically makes mother follow them
+ point_at(hallucinator)
+ talk("[hallucinator]!!!!")
+ var/list/scold_lines = list(
+ pick(list("CLEAN YOUR ROOM THIS INSTANT!", "IT'S TIME TO WAKE UP FOR SCHOOL!!")),
+ pick(list("YOU INSULT YOUR GRANDPARENTS!", "USELESS!")),
+ pick(list("I BROUGHT YOU INTO THIS WORLD, I CAN TAKE YOU OUT!!!", "YOU'RE GROUNDED!!")),
+ )
+ var/delay = 2 SECONDS
+ for(var/line in scold_lines)
+ addtimer(CALLBACK(src, PROC_REF(talk), line), delay)
+ delay += 2 SECONDS
+ addtimer(CALLBACK(src, PROC_REF(exit)), delay + 4 SECONDS)
+ return TRUE
+
+/datum/hallucination/your_mother/proc/point_at(atom/target)
+ var/turf/tile = get_turf(target)
+ if(!tile)
+ return
+
+ var/obj/visual = image('icons/hud/screen_gen.dmi', mother.loc, "arrow", FLY_LAYER)
+
+ INVOKE_ASYNC(GLOBAL_PROC, GLOBAL_PROC_REF(flick_overlay_global), visual, list(hallucinator.client), 2.5 SECONDS)
+ animate(visual, pixel_x = (tile.x - mother.x) * world.icon_size, pixel_y = (tile.y - mother.y) * world.icon_size, time = 1.7, easing = EASE_OUT)
+
+/datum/hallucination/your_mother/proc/talk(text)
+ var/plus_runechat = hallucinator.client?.prefs.read_preference(/datum/preference/toggle/enable_runechat)
+ var/datum/language/understood_language = hallucinator.get_random_understood_language()
+ var/spans = list(mother.speech_span)
+
+ if(!plus_runechat)
+ var/image/speech_overlay = image('icons/mob/effects/talk.dmi', mother, "default0", layer = ABOVE_MOB_LAYER)
+ INVOKE_ASYNC(GLOBAL_PROC, GLOBAL_PROC_REF(flick_overlay_global), speech_overlay, list(hallucinator.client), 30)
+ else
+ hallucinator.create_chat_message(mother, understood_language, text, spans)
+
+ var/message = hallucinator.compose_message(mother, understood_language, text, null, spans, visible_name = TRUE)
+ to_chat(hallucinator, message)
+
+/datum/hallucination/your_mother/proc/exit()
+ qdel(src)
+
+/datum/outfit/yourmother
+ name = "Your Mother"
+
+ uniform = /obj/item/clothing/under/color/jumpskirt/red
+ neck = /obj/item/clothing/neck/beads
+ shoes = /obj/item/clothing/shoes/sandal
+
+/datum/outfit/yourmother/post_equip(mob/living/carbon/human/user, visualsOnly = FALSE)
+ . = ..()
+ user.set_hairstyle("Braided", update = TRUE) //get_dynamic_human_appearance uses bald dummies
+
+/obj/effect/client_image_holder/hallucination/your_mother
+ gender = FEMALE
+ image_icon = 'icons/mob/simple/simple_human.dmi'
+ name = "Your mother"
+ desc = "She is not happy."
+ image_state = ""
+
+/obj/effect/client_image_holder/hallucination/your_mother/Initialize(mapload, list/mobs_which_see_us, datum/hallucination/parent)
+ . = ..()
+ var/mob/living/carbon/hallucinator = parent.hallucinator
+ image_icon = getFlatIcon(get_dynamic_human_appearance(/datum/outfit/yourmother, hallucinator.dna.species))
+ regenerate_image()
diff --git a/code/modules/hallucination/station_message.dm b/code/modules/hallucination/station_message.dm
index fa51e103cca..02488d2016e 100644
--- a/code/modules/hallucination/station_message.dm
+++ b/code/modules/hallucination/station_message.dm
@@ -1,6 +1,3 @@
-#define ALERT_TITLE(text) ("
" + text + "
")
-#define ALERT_BODY(text) ("
" + span_alert(text) + "
")
-
/datum/hallucination/station_message
abstract_hallucination_parent = /datum/hallucination/station_message
random_hallucination_weight = 1
@@ -12,16 +9,16 @@
/datum/hallucination/station_message/blob_alert
/datum/hallucination/station_message/blob_alert/start()
- to_chat(hallucinator, ALERT_TITLE("Biohazard Alert"))
- to_chat(hallucinator, ALERT_BODY("Confirmed outbreak of level 5 biohazard aboard [station_name()]. All personnel must contain the outbreak."))
+ to_chat(hallucinator, span_priorityannounce("Biohazard Alert"))
+ to_chat(hallucinator, span_priorityalert("Confirmed outbreak of level 5 biohazard aboard [station_name()]. All personnel must contain the outbreak."))
SEND_SOUND(hallucinator, sound(SSstation.announcer.event_sounds[ANNOUNCER_OUTBREAK5]))
return ..()
/datum/hallucination/station_message/shuttle_dock
/datum/hallucination/station_message/shuttle_dock/start()
- to_chat(hallucinator, ALERT_TITLE("Priority Announcement"))
- to_chat(hallucinator, ALERT_BODY("[SSshuttle.emergency || "The Emergency Shuttle"] has docked with the station. You have 3 minutes to board the Emergency Shuttle."))
+ to_chat(hallucinator, span_priorityannounce("Priority Announcement"))
+ to_chat(hallucinator, span_priorityalert("[SSshuttle.emergency || "The Emergency Shuttle"] has docked with the station. You have 3 minutes to board the Emergency Shuttle."))
SEND_SOUND(hallucinator, sound(SSstation.announcer.event_sounds[ANNOUNCER_SHUTTLEDOCK]))
return ..()
@@ -31,8 +28,8 @@
if(!(locate(/mob/living/silicon/ai) in GLOB.silicon_mobs))
return FALSE
- to_chat(hallucinator, ALERT_TITLE("Anomaly Alert"))
- to_chat(hallucinator, ALERT_BODY("Hostile runtimes detected in all station systems, please deactivate your AI to prevent possible damage to its morality core."))
+ to_chat(hallucinator, span_priorityannounce("Anomaly Alert"))
+ to_chat(hallucinator, span_priorityalert("Hostile runtimes detected in all station systems, please deactivate your AI to prevent possible damage to its morality core."))
SEND_SOUND(hallucinator, sound(SSstation.announcer.event_sounds[ANNOUNCER_AIMALF]))
return ..()
@@ -56,8 +53,8 @@
var/message_with_name = pick(ascension_bodies)
message_with_name = replacetext(message_with_name, "%FAKENAME%", totally_real_heretic.real_name)
- to_chat(hallucinator, ALERT_TITLE(generate_heretic_text()))
- to_chat(hallucinator, ALERT_BODY("[generate_heretic_text()] [message_with_name] [generate_heretic_text()]"))
+ to_chat(hallucinator, span_priorityannounce("[generate_heretic_text()]"))
+ to_chat(hallucinator, span_priorityalert("[generate_heretic_text()] [message_with_name] [generate_heretic_text()]"))
SEND_SOUND(hallucinator, sound(SSstation.announcer.event_sounds[ANNOUNCER_SPANOMALIES]))
return ..()
@@ -74,8 +71,8 @@
var/area/fake_summon_area_type = pick(GLOB.the_station_areas - hallucinator_area.type)
var/area/fake_summon_area = GLOB.areas_by_type[fake_summon_area_type]
- to_chat(hallucinator, ALERT_TITLE("Central Command Higher Dimensional Affairs"))
- to_chat(hallucinator, ALERT_BODY("Figments from an eldritch god are being summoned by [totally_real_cult_leader.real_name] \
+ to_chat(hallucinator, span_priorityannounce("Central Command Higher Dimensional Affairs"))
+ to_chat(hallucinator, span_priorityalert("Figments from an eldritch god are being summoned by [totally_real_cult_leader.real_name] \
into [fake_summon_area] from an unknown dimension. Disrupt the ritual at all costs!"))
SEND_SOUND(hallucinator, 'sound/ambience/antag/bloodcult/bloodcult_scribe.ogg')
@@ -85,8 +82,8 @@
random_hallucination_weight = 2
/datum/hallucination/station_message/meteors/start()
- to_chat(hallucinator, ALERT_TITLE("Meteor Alert"))
- to_chat(hallucinator, ALERT_BODY("Meteors have been detected on collision course with the station."))
+ to_chat(hallucinator, span_priorityannounce("Meteor Alert"))
+ to_chat(hallucinator, span_priorityalert("Meteors have been detected on collision course with the station."))
SEND_SOUND(hallucinator, sound(SSstation.announcer.event_sounds[ANNOUNCER_METEORS]))
return ..()
@@ -113,6 +110,3 @@
hallucinator.playsound_local(get_turf(hallucinator), 'sound/effects/explosion_distant.ogg', 50, FALSE, pressure_affected = FALSE)
qdel(src)
-
-#undef ALERT_TITLE
-#undef ALERT_BODY
diff --git a/code/modules/holodeck/turfs.dm b/code/modules/holodeck/turfs.dm
index 28482873aeb..1e89052af59 100644
--- a/code/modules/holodeck/turfs.dm
+++ b/code/modules/holodeck/turfs.dm
@@ -8,7 +8,7 @@
/turf/open/floor/holofloor/attackby(obj/item/I, mob/living/user)
return // HOLOFLOOR DOES NOT GIVE A FUCK
-/turf/open/floor/holofloor/tool_act(mob/living/user, obj/item/I, tool_type)
+/turf/open/floor/holofloor/tool_act(mob/living/user, obj/item/tool, tool_type, is_right_clicking)
return
/turf/open/floor/holofloor/burn_tile()
diff --git a/code/modules/mapfluff/ruins/objects_and_mobs/ash_walker_den.dm b/code/modules/mapfluff/ruins/objects_and_mobs/ash_walker_den.dm
index 5775f0a20f2..6f08be00e5d 100644
--- a/code/modules/mapfluff/ruins/objects_and_mobs/ash_walker_den.dm
+++ b/code/modules/mapfluff/ruins/objects_and_mobs/ash_walker_den.dm
@@ -13,8 +13,8 @@
resistance_flags = FIRE_PROOF | LAVA_PROOF
max_integrity = 200
+ faction = list(FACTION_ASHWALKER)
- var/faction = list(FACTION_ASHWALKER)
var/meat_counter = 6
var/datum/team/ashwalkers/ashies
var/datum/linked_objective
diff --git a/code/modules/mining/lavaland/megafauna_loot.dm b/code/modules/mining/lavaland/megafauna_loot.dm
index 65d3a1ad3b3..20cbec1444b 100644
--- a/code/modules/mining/lavaland/megafauna_loot.dm
+++ b/code/modules/mining/lavaland/megafauna_loot.dm
@@ -401,7 +401,7 @@
give_blood(10)
/obj/item/soulscythe/attack_hand(mob/user, list/modifiers)
- if(soul.ckey && !soul.faction_check_mob(user))
+ if(soul.ckey && !soul.faction_check_atom(user))
to_chat(user, span_warning("You can't pick up [src]!"))
return
return ..()
diff --git a/code/modules/mining/lavaland/tendril_loot.dm b/code/modules/mining/lavaland/tendril_loot.dm
index 96e330825f1..a6684a221ae 100644
--- a/code/modules/mining/lavaland/tendril_loot.dm
+++ b/code/modules/mining/lavaland/tendril_loot.dm
@@ -632,7 +632,7 @@
. = ..()
if(slot & ITEM_SLOT_GLOVES)
tool_behaviour = TOOL_MINING
- RegisterSignal(user, COMSIG_HUMAN_EARLY_UNARMED_ATTACK, PROC_REF(rocksmash))
+ RegisterSignal(user, COMSIG_LIVING_UNARMED_ATTACK, PROC_REF(rocksmash))
RegisterSignal(user, COMSIG_MOVABLE_BUMP, PROC_REF(rocksmash))
else
stopmining(user)
@@ -643,13 +643,15 @@
/obj/item/clothing/gloves/gauntlets/proc/stopmining(mob/user)
tool_behaviour = initial(tool_behaviour)
- UnregisterSignal(user, COMSIG_HUMAN_EARLY_UNARMED_ATTACK)
+ UnregisterSignal(user, COMSIG_LIVING_UNARMED_ATTACK)
UnregisterSignal(user, COMSIG_MOVABLE_BUMP)
/obj/item/clothing/gloves/gauntlets/proc/rocksmash(mob/living/carbon/human/user, atom/rocks, proximity)
SIGNAL_HANDLER
+ if(!proximity)
+ return NONE
if(!ismineralturf(rocks) && !isasteroidturf(rocks))
- return
+ return NONE
rocks.attackby(src, user)
return COMPONENT_CANCEL_ATTACK_CHAIN
diff --git a/code/modules/mob/dead/observer/observer.dm b/code/modules/mob/dead/observer/observer.dm
index a28153a2196..8033d465a90 100644
--- a/code/modules/mob/dead/observer/observer.dm
+++ b/code/modules/mob/dead/observer/observer.dm
@@ -219,9 +219,8 @@ GLOBAL_VAR_INIT(observer_default_invisibility, INVISIBILITY_OBSERVER)
setDir(2 )//reset the dir to its default so the sprites all properly align up
if(ghost_accs == GHOST_ACCS_FULL && (icon_state in GLOB.ghost_forms_with_accessories_list)) //check if this form supports accessories and if the client wants to show them
- var/datum/sprite_accessory/S
if(facial_hairstyle)
- S = GLOB.facial_hairstyles_list[facial_hairstyle]
+ var/datum/sprite_accessory/S = GLOB.facial_hairstyles_list[facial_hairstyle]
if(S)
facial_hair_overlay = mutable_appearance(S.icon, "[S.icon_state]", -HAIR_LAYER)
if(facial_hair_color)
@@ -229,12 +228,13 @@ GLOBAL_VAR_INIT(observer_default_invisibility, INVISIBILITY_OBSERVER)
facial_hair_overlay.alpha = 200
add_overlay(facial_hair_overlay)
if(hairstyle)
- S = GLOB.hairstyles_list[hairstyle]
+ var/datum/sprite_accessory/hair/S = GLOB.hairstyles_list[hairstyle]
if(S)
hair_overlay = mutable_appearance(S.icon, "[S.icon_state]", -HAIR_LAYER)
if(hair_color)
hair_overlay.color = hair_color
hair_overlay.alpha = 200
+ hair_overlay.pixel_y = S.y_offset
add_overlay(hair_overlay)
/*
diff --git a/code/modules/mob/living/basic/icemoon/ice_whelp/ice_whelp.dm b/code/modules/mob/living/basic/icemoon/ice_whelp/ice_whelp.dm
index f89009cc2d9..8fb02ec83df 100644
--- a/code/modules/mob/living/basic/icemoon/ice_whelp/ice_whelp.dm
+++ b/code/modules/mob/living/basic/icemoon/ice_whelp/ice_whelp.dm
@@ -43,12 +43,12 @@
AddElement(/datum/element/footstep, FOOTSTEP_MOB_HEAVY)
AddComponent(/datum/component/basic_mob_ability_telegraph)
AddComponent(/datum/component/basic_mob_attack_telegraph, telegraph_duration = 0.6 SECONDS)
- var/datum/action/cooldown/mob_cooldown/ice_breath/flamethrower = new(src)
- var/datum/action/cooldown/mob_cooldown/ice_breathe_all_directions/wide_flames = new(src)
+ var/datum/action/cooldown/mob_cooldown/fire_breath/ice/flamethrower = new(src)
flamethrower.Grant(src)
+ ai_controller.set_blackboard_key(BB_WHELP_STRAIGHTLINE_FIRE, flamethrower)
+ var/datum/action/cooldown/mob_cooldown/fire_breath/ice/cross/wide_flames = new(src)
wide_flames.Grant(src)
ai_controller.set_blackboard_key(BB_WHELP_WIDESPREAD_FIRE, wide_flames)
- ai_controller.set_blackboard_key(BB_WHELP_STRAIGHTLINE_FIRE, flamethrower)
RegisterSignal(src, COMSIG_HOSTILE_PRE_ATTACKINGTARGET, PROC_REF(pre_attack))
diff --git a/code/modules/mob/living/basic/icemoon/ice_whelp/ice_whelp_abilities.dm b/code/modules/mob/living/basic/icemoon/ice_whelp/ice_whelp_abilities.dm
index d5dc50d0a69..026106516fb 100644
--- a/code/modules/mob/living/basic/icemoon/ice_whelp/ice_whelp_abilities.dm
+++ b/code/modules/mob/living/basic/icemoon/ice_whelp/ice_whelp_abilities.dm
@@ -1,23 +1,26 @@
-/datum/action/cooldown/mob_cooldown/ice_breath
+/// Breathe "fire" in a line (it's freezing cold)
+/datum/action/cooldown/mob_cooldown/fire_breath/ice
name = "Ice Breath"
desc = "Fire a cold line of fire towards the enemy!"
button_icon = 'icons/effects/magic.dmi'
button_icon_state = "fireball"
cooldown_time = 3 SECONDS
melee_cooldown_time = 0 SECONDS
- click_to_activate = TRUE
- ///the range of fire
- var/fire_range = 4
+ fire_range = 4
+ fire_damage = 10
-/datum/action/cooldown/mob_cooldown/ice_breath/Activate(atom/target_atom)
- var/turf/target_fire_turf = get_ranged_target_turf_direct(owner, target_atom, fire_range)
- var/list/burn_turfs = get_line(owner, target_fire_turf) - get_turf(owner)
- // This proc sleeps
- INVOKE_ASYNC(GLOBAL_PROC, GLOBAL_PROC_REF(dragon_fire_line), owner, /* burn_turfs = */ burn_turfs, /* frozen = */ TRUE)
- StartCooldown()
- return TRUE
+/datum/action/cooldown/mob_cooldown/fire_breath/ice/burn_turf(turf/fire_turf, list/hit_list, atom/source)
+ var/obj/effect/hotspot/fire_hotspot = ..()
+ fire_hotspot.add_atom_colour(COLOR_BLUE_LIGHT, FIXED_COLOUR_PRIORITY) // You're blue now, that's my attack
+ return fire_hotspot
-/datum/action/cooldown/mob_cooldown/ice_breathe_all_directions
+/datum/action/cooldown/mob_cooldown/fire_breath/ice/on_burn_mob(mob/living/barbecued, mob/living/source)
+ barbecued.apply_status_effect(/datum/status_effect/ice_block_talisman, 2 SECONDS)
+ to_chat(barbecued, span_userdanger("You're frozen solid by [source]'s icy breath!"))
+ barbecued.adjustFireLoss(fire_damage)
+
+/// Breathe really cold fire in a plus shape, like bomberman
+/datum/action/cooldown/mob_cooldown/fire_breath/ice/cross
name = "Fire all directions"
desc = "Unleash lines of cold fire in all directions"
button_icon = 'icons/effects/fire.dmi'
@@ -25,13 +28,10 @@
cooldown_time = 4 SECONDS
melee_cooldown_time = 0 SECONDS
click_to_activate = FALSE
- ///the range of fire
- var/fire_range = 6
+ fire_range = 6
-/datum/action/cooldown/mob_cooldown/ice_breathe_all_directions/Activate(atom/target_atom)
+/datum/action/cooldown/mob_cooldown/fire_breath/ice/cross/attack_sequence(atom/target)
+ playsound(owner.loc, fire_sound, 200, TRUE)
for(var/direction in GLOB.cardinals)
var/turf/target_fire_turf = get_ranged_target_turf(owner, direction, fire_range)
- var/list/burn_turfs = get_line(owner, target_fire_turf) - get_turf(owner)
- INVOKE_ASYNC(GLOBAL_PROC, GLOBAL_PROC_REF(dragon_fire_line), owner, burn_turfs, frozen = TRUE)
- StartCooldown()
- return TRUE
+ fire_line(target_fire_turf)
diff --git a/code/modules/mob/living/basic/lavaland/brimdemon/brimbeam.dm b/code/modules/mob/living/basic/lavaland/brimdemon/brimbeam.dm
index 29a3de60029..b8e2bb68c69 100644
--- a/code/modules/mob/living/basic/lavaland/brimdemon/brimbeam.dm
+++ b/code/modules/mob/living/basic/lavaland/brimdemon/brimbeam.dm
@@ -9,7 +9,6 @@
click_to_activate = TRUE
cooldown_time = 5 SECONDS
melee_cooldown_time = 0
- check_flags = AB_CHECK_CONSCIOUS | AB_CHECK_INCAPACITATED
/// How far does our beam go?
var/beam_range = 10
/// How long does our beam last?
diff --git a/code/modules/mob/living/basic/lavaland/goliath/goliath_actions.dm b/code/modules/mob/living/basic/lavaland/goliath/goliath_actions.dm
index bb8adaf61c3..b484afa0cea 100644
--- a/code/modules/mob/living/basic/lavaland/goliath/goliath_actions.dm
+++ b/code/modules/mob/living/basic/lavaland/goliath/goliath_actions.dm
@@ -9,7 +9,6 @@
click_to_activate = TRUE
cooldown_time = 12 SECONDS
melee_cooldown_time = 0
- check_flags = AB_CHECK_CONSCIOUS | AB_CHECK_INCAPACITATED
shared_cooldown = NONE
/// Furthest range we can activate ability at
var/max_range = 7
@@ -44,7 +43,6 @@
overlay_icon_state = "bg_demon_border"
cooldown_time = 24 SECONDS
melee_cooldown_time = 0
- check_flags = AB_CHECK_CONSCIOUS | AB_CHECK_INCAPACITATED
shared_cooldown = NONE
click_to_activate = FALSE
@@ -69,7 +67,6 @@
click_to_activate = TRUE
cooldown_time = 12 SECONDS
melee_cooldown_time = 0
- check_flags = AB_CHECK_CONSCIOUS | AB_CHECK_INCAPACITATED
shared_cooldown = NONE
/datum/action/cooldown/mob_cooldown/tentacle_grasp/Activate(atom/target)
diff --git a/code/modules/mob/living/basic/lavaland/hivelord/spawn_hivelord_brood.dm b/code/modules/mob/living/basic/lavaland/hivelord/spawn_hivelord_brood.dm
index 3fee2a003f3..63a66d4c4e3 100644
--- a/code/modules/mob/living/basic/lavaland/hivelord/spawn_hivelord_brood.dm
+++ b/code/modules/mob/living/basic/lavaland/hivelord/spawn_hivelord_brood.dm
@@ -9,7 +9,6 @@
click_to_activate = TRUE
cooldown_time = 2 SECONDS
melee_cooldown_time = 0
- check_flags = AB_CHECK_CONSCIOUS | AB_CHECK_INCAPACITATED
shared_cooldown = NONE
/// If a mob is not clicked directly, inherit targetting data from this blackboard key and setting it upon this target key
var/ai_target_key = BB_BASIC_MOB_CURRENT_TARGET
diff --git a/code/modules/mob/living/basic/lavaland/legion/legion_ai.dm b/code/modules/mob/living/basic/lavaland/legion/legion_ai.dm
index 310bbeb708e..ff29ea3c240 100644
--- a/code/modules/mob/living/basic/lavaland/legion/legion_ai.dm
+++ b/code/modules/mob/living/basic/lavaland/legion/legion_ai.dm
@@ -32,7 +32,7 @@
/datum/targetting_datum/basic/attack_until_dead/legion
/datum/targetting_datum/basic/attack_until_dead/legion/faction_check(datum/ai_controller/controller, mob/living/living_mob, mob/living/the_target)
- if (!living_mob.faction_check_mob(the_target, exact_match = check_factions_exactly))
+ if (!living_mob.faction_check_atom(the_target, exact_match = check_factions_exactly))
return FALSE
if (istype(the_target, living_mob.type))
return TRUE
@@ -46,7 +46,7 @@
/datum/ai_planning_subtree/flee_target/legion/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick)
var/mob/living/target = controller.blackboard[target_key]
- if (QDELETED(target) || target.faction_check_mob(controller.pawn))
+ if (QDELETED(target) || target.faction_check_atom(controller.pawn))
return // Only flee if we have a hostile target
return ..()
diff --git a/code/modules/mob/living/basic/lavaland/legion/legion_brood.dm b/code/modules/mob/living/basic/lavaland/legion/legion_brood.dm
index 962d232c5ef..7ce568d5760 100644
--- a/code/modules/mob/living/basic/lavaland/legion/legion_brood.dm
+++ b/code/modules/mob/living/basic/lavaland/legion/legion_brood.dm
@@ -49,7 +49,7 @@
if (ishuman(target) && target.stat > SOFT_CRIT)
infest(target)
return
- if (isliving(target) && faction_check_mob(target) && !istype(target, created_by?.type))
+ if (isliving(target) && faction_check_atom(target) && !istype(target, created_by?.type))
visible_message(span_warning("[src] melds with [target]'s flesh!"))
target.apply_status_effect(/datum/status_effect/regenerative_core)
new /obj/effect/temp_visual/heal(get_turf(target), COLOR_HEALING_CYAN)
diff --git a/code/modules/mob/living/basic/lavaland/legion/spawn_legions.dm b/code/modules/mob/living/basic/lavaland/legion/spawn_legions.dm
index 1ffcafecd56..f68c5f7fafe 100644
--- a/code/modules/mob/living/basic/lavaland/legion/spawn_legions.dm
+++ b/code/modules/mob/living/basic/lavaland/legion/spawn_legions.dm
@@ -9,7 +9,6 @@
click_to_activate = TRUE
cooldown_time = 2 SECONDS
melee_cooldown_time = 0
- check_flags = AB_CHECK_CONSCIOUS | AB_CHECK_INCAPACITATED
shared_cooldown = NONE
/// If a mob is not clicked directly, inherit targetting data from this blackboard key and setting it upon this target key
var/ai_target_key = BB_BASIC_MOB_CURRENT_TARGET
diff --git a/code/modules/mob/living/basic/lavaland/watcher/watcher_gaze.dm b/code/modules/mob/living/basic/lavaland/watcher/watcher_gaze.dm
index 4b322c220ed..06a221db2dc 100644
--- a/code/modules/mob/living/basic/lavaland/watcher/watcher_gaze.dm
+++ b/code/modules/mob/living/basic/lavaland/watcher/watcher_gaze.dm
@@ -9,7 +9,6 @@
background_icon_state = "bg_demon"
overlay_icon_state = "bg_demon_border"
cooldown_time = 20 SECONDS
- check_flags = AB_CHECK_CONSCIOUS | AB_CHECK_INCAPACITATED
click_to_activate = FALSE
shared_cooldown = NONE
melee_cooldown_time = 0 SECONDS
diff --git a/code/modules/mob/living/basic/lavaland/watcher/watcher_overwatch.dm b/code/modules/mob/living/basic/lavaland/watcher/watcher_overwatch.dm
index 0c8194c524a..541a4ed9e1c 100644
--- a/code/modules/mob/living/basic/lavaland/watcher/watcher_overwatch.dm
+++ b/code/modules/mob/living/basic/lavaland/watcher/watcher_overwatch.dm
@@ -10,7 +10,6 @@
background_icon_state = "bg_demon"
overlay_icon_state = "bg_demon_border"
cooldown_time = 20 SECONDS
- check_flags = AB_CHECK_CONSCIOUS | AB_CHECK_INCAPACITATED
click_to_activate = TRUE
shared_cooldown = NONE
/// Furthest range we can activate ability at
diff --git a/code/modules/mob/living/basic/space_fauna/demon/demon_subtypes.dm b/code/modules/mob/living/basic/space_fauna/demon/demon_subtypes.dm
index e52eb09ac0b..ed3c131949d 100644
--- a/code/modules/mob/living/basic/space_fauna/demon/demon_subtypes.dm
+++ b/code/modules/mob/living/basic/space_fauna/demon/demon_subtypes.dm
@@ -99,16 +99,19 @@
/mob/living/basic/demon/slaughter/proc/on_attack(mob/living/source, atom/attack_target, proximity_flag, list/modifiers)
SIGNAL_HANDLER
+ if(!proximity_flag)
+ return NONE
+
if(LAZYACCESS(modifiers, RIGHT_CLICK))
bodyslam(attack_target)
return COMPONENT_CANCEL_ATTACK_CHAIN
if(!iscarbon(attack_target))
- return
+ return NONE
var/mob/living/carbon/target = attack_target
if(target.stat == DEAD || isnull(target.mind) || (current_hitstreak > wound_bonus_hitstreak_max))
- return
+ return NONE
current_hitstreak++
wound_bonus += wound_bonus_per_hit
diff --git a/code/modules/mob/living/basic/space_fauna/faithless.dm b/code/modules/mob/living/basic/space_fauna/faithless.dm
index c1dc297ea46..88696b3a3c7 100644
--- a/code/modules/mob/living/basic/space_fauna/faithless.dm
+++ b/code/modules/mob/living/basic/space_fauna/faithless.dm
@@ -38,9 +38,9 @@
/mob/living/basic/faithless/Initialize(mapload)
. = ..()
ADD_TRAIT(src, TRAIT_SPACEWALK, INNATE_TRAIT)
+ AddElement(/datum/element/door_pryer)
AddElement(/datum/element/footstep, FOOTSTEP_MOB_SHOE)
AddElement(/datum/element/mob_grabber, steal_from_others = FALSE)
- AddComponent(/datum/component/pry_open_door)
/mob/living/basic/faithless/melee_attack(atom/target, list/modifiers, ignore_cooldown)
. = ..()
diff --git a/code/modules/mob/living/basic/space_fauna/ghost.dm b/code/modules/mob/living/basic/space_fauna/ghost.dm
index 35afaade990..ad79fefc3e6 100644
--- a/code/modules/mob/living/basic/space_fauna/ghost.dm
+++ b/code/modules/mob/living/basic/space_fauna/ghost.dm
@@ -71,10 +71,11 @@
ghost_facial_hair_color = ghost_hair_color
if(!isnull(ghost_hairstyle) && ghost_hairstyle != "Bald") //Bald hairstyle and the Shaved facial hairstyle lack an associated sprite and will not properly generate hair, and just cause runtimes.
- var/datum/sprite_accessory/hair_style = GLOB.hairstyles_list[ghost_hairstyle] //We use the hairstyle name to get the sprite accessory, which we copy the icon_state from.
+ var/datum/sprite_accessory/hair/hair_style = GLOB.hairstyles_list[ghost_hairstyle] //We use the hairstyle name to get the sprite accessory, which we copy the icon_state from.
ghost_hair = mutable_appearance('icons/mob/human/human_face.dmi', "[hair_style.icon_state]", -HAIR_LAYER)
ghost_hair.alpha = 200
ghost_hair.color = ghost_hair_color
+ ghost_hair.pixel_y = hair_style.y_offset
add_overlay(ghost_hair)
if(!isnull(ghost_facial_hairstyle) && ghost_facial_hairstyle != "Shaved")
diff --git a/code/modules/mob/living/basic/space_fauna/mushroom.dm b/code/modules/mob/living/basic/space_fauna/mushroom.dm
index 96280db2923..6f900b08abf 100644
--- a/code/modules/mob/living/basic/space_fauna/mushroom.dm
+++ b/code/modules/mob/living/basic/space_fauna/mushroom.dm
@@ -74,7 +74,7 @@
///we only attacked another mushrooms
/datum/targetting_datum/basic/mushroom/faction_check(datum/ai_controller/controller, mob/living/living_mob, mob/living/the_target)
- return !living_mob.faction_check_mob(the_target, exact_match = check_factions_exactly)
+ return !living_mob.faction_check_atom(the_target, exact_match = check_factions_exactly)
/datum/ai_planning_subtree/find_and_hunt_target/mushroom_food
target_key = BB_LOW_PRIORITY_HUNTING_TARGET
diff --git a/code/modules/mob/living/basic/space_fauna/regal_rat/regal_rat.dm b/code/modules/mob/living/basic/space_fauna/regal_rat/regal_rat.dm
index 164c25fb896..24e53b3bf44 100644
--- a/code/modules/mob/living/basic/space_fauna/regal_rat/regal_rat.dm
+++ b/code/modules/mob/living/basic/space_fauna/regal_rat/regal_rat.dm
@@ -54,6 +54,7 @@
AddElement(/datum/element/waddling)
AddElement(/datum/element/ai_retaliate)
+ AddElement(/datum/element/door_pryer, pry_time = 5 SECONDS, interaction_key = REGALRAT_INTERACTION)
AddComponent(\
/datum/component/ghost_direct_control,\
poll_candidates = poll_ghosts,\
@@ -81,7 +82,7 @@
return
if(ismouse(user))
- if(user.faction_check_mob(src, exact_match = TRUE))
+ if(user.faction_check_atom(src, exact_match = TRUE))
. += span_notice("This is your king. Long live [p_their()] majesty!")
else
. += span_warning("This is a false king! Strike [p_them()] down!")
@@ -174,10 +175,6 @@
if(isnull(mind))
return
- if(istype(target, /obj/machinery/door/airlock))
- INVOKE_ASYNC(src, PROC_REF(pry_door), target)
- return COMPONENT_HOSTILE_NO_ATTACK
-
if(!combat_mode)
INVOKE_ASYNC(src, PROC_REF(poison_target), target)
return COMPONENT_HOSTILE_NO_ATTACK
@@ -195,7 +192,7 @@
balloon_alert(src, "already dead!")
return FALSE
- if(living_target.faction_check_mob(src, exact_match = TRUE))
+ if(living_target.faction_check_atom(src, exact_match = TRUE))
balloon_alert(src, "one of your soldiers!")
return FALSE
@@ -235,43 +232,7 @@
heal_bodypart_damage(amount)
qdel(target)
-/**
- * Allows rat king to pry open an airlock if it isn't locked.
- *
- * A proc used for letting the rat king pry open airlocks instead of just attacking them.
- * This allows the rat king to traverse the station when there is a lack of vents or
- * accessible doors, something which is common in certain rat king spawn points.
- *
- * Returns TRUE if the door opens, FALSE otherwise.
- */
-/mob/living/basic/regal_rat/proc/pry_door(target)
- if(DOING_INTERACTION(src, REGALRAT_INTERACTION))
- return FALSE
-
- var/obj/machinery/door/airlock/prying_door = target
- if(!prying_door.density || prying_door.locked || prying_door.welded || prying_door.seal)
- return FALSE
-
- visible_message(
- span_warning("[src] begins prying open the airlock..."),
- span_notice("You begin digging your claws into the airlock..."),
- span_warning("You hear groaning metal..."),
- )
- var/time_to_open = 0.5 SECONDS
-
- if(prying_door.hasPower())
- time_to_open = 5 SECONDS
- playsound(src, 'sound/machines/airlock_alien_prying.ogg', 100, vary = TRUE)
-
- if(!do_after(src, time_to_open, prying_door, interaction_key = REGALRAT_INTERACTION))
- return FALSE
-
- if(!prying_door.open(BYPASS_DOOR_CHECKS))
- balloon_alert(src, "failed to open!")
- return FALSE
-
- return TRUE
-
+/// Regal rat subtype which can be possessed by ghosts
/mob/living/basic/regal_rat/controlled
poll_ghosts = TRUE
diff --git a/code/modules/mob/living/basic/space_fauna/space_dragon/dragon_breath.dm b/code/modules/mob/living/basic/space_fauna/space_dragon/dragon_breath.dm
new file mode 100644
index 00000000000..5be5038b3a4
--- /dev/null
+++ b/code/modules/mob/living/basic/space_fauna/space_dragon/dragon_breath.dm
@@ -0,0 +1,32 @@
+/// A space dragon's fire breath, toasts lunch AND buffs your friends
+/datum/action/cooldown/mob_cooldown/fire_breath/carp
+ desc = "A Space Dragon's burning breath not only chars its foes, but invigorates Space Carp as well."
+ fire_damage = 30
+ mech_damage = 50
+ fire_range = 20
+ fire_temperature = 700 // Even hotter than a megafauna for some reason
+ shared_cooldown = NONE
+ melee_cooldown_time = 0 SECONDS
+
+/datum/action/cooldown/mob_cooldown/fire_breath/carp/on_burn_mob(mob/living/barbecued, mob/living/source)
+ if (!source.faction_check_atom(barbecued))
+ return ..()
+ to_chat(barbecued, span_notice("[source]'s fiery breath fills you with energy!"))
+ barbecued.apply_status_effect(/datum/status_effect/carp_invigoration)
+
+/// Makes you run faster for the duration
+/datum/status_effect/carp_invigoration
+ id = "carp_invigorated"
+ alert_type = null
+ duration = 8 SECONDS
+
+/datum/status_effect/carp_invigoration/on_apply()
+ . = ..()
+ if (!.)
+ return
+ owner.add_filter("anger_glow", 3, list("type" = "outline", "color" = COLOR_CARP_RIFT_RED, "size" = 2))
+ owner.add_movespeed_modifier(/datum/movespeed_modifier/dragon_rage)
+
+/datum/status_effect/carp_invigoration/on_remove()
+ owner.remove_filter("anger_glow")
+ owner.remove_movespeed_modifier(/datum/movespeed_modifier/dragon_rage)
diff --git a/code/modules/mob/living/basic/space_fauna/space_dragon/dragon_gust.dm b/code/modules/mob/living/basic/space_fauna/space_dragon/dragon_gust.dm
new file mode 100644
index 00000000000..074804de86c
--- /dev/null
+++ b/code/modules/mob/living/basic/space_fauna/space_dragon/dragon_gust.dm
@@ -0,0 +1,94 @@
+/// Default additional time to spend stunned per usage of ability
+#define DEFAULT_ACTIVATED_ENDLAG 3 DECISECONDS
+
+/// Rise into the air and slam down, knocking people away. No real cooldown but has escalating endlag if used in quick succession.
+/datum/action/cooldown/mob_cooldown/wing_buffet
+ name = "Wing Buffet"
+ desc = "Rise into the air and release a powerful gust from your wings, blowing attackers away. Becomes more tiring if used in quick succession."
+ button_icon = 'icons/effects/magic.dmi'
+ button_icon_state = "tornado"
+ cooldown_time = 1 SECONDS
+ melee_cooldown_time = 0
+ click_to_activate = FALSE
+ shared_cooldown = NONE
+ /// Timer we use to track our current action
+ var/active_timer
+ /// How far away can we reach people?
+ var/gust_distance = 4
+ /// How long to animate for before we start?
+ var/windup_time = 1.2 SECONDS
+ /// Minimum amount of stun time following use of wing buffet
+ var/minimum_endlag = 4 DECISECONDS
+ /// Amount of extra time to stay stunned after the end of the ability
+ var/additional_endlag = 0 DECISECONDS
+ /// Amount of time to add to endlag after each successful use of the ability
+ var/endlag_per_activation = DEFAULT_ACTIVATED_ENDLAG
+ /// How much accumulated stun time do we subtract every second? Takes a full minute to regen off a single use :(
+ var/endlag_decay_per_second = DEFAULT_ACTIVATED_ENDLAG / 60
+ /// Increase the effect of our accumulated additional stun time by this much if space dragon has lost some rifts
+ var/exhaustion_multiplier = 5
+ /// List of traits we apply while the ability is ongoing, stops us from moving around and such
+ var/static/list/applied_traits = list(
+ TRAIT_IMMOBILIZED,
+ TRAIT_INCAPACITATED,
+ TRAIT_NO_FLOATING_ANIM,
+ TRAIT_WING_BUFFET,
+ )
+
+/datum/action/cooldown/mob_cooldown/wing_buffet/Grant(mob/granted_to)
+ . = ..()
+ RegisterSignal(granted_to, COMSIG_LIVING_LIFE, PROC_REF(on_life))
+
+/datum/action/cooldown/mob_cooldown/wing_buffet/Remove(mob/removed_from)
+ . = ..()
+ deltimer(active_timer)
+ UnregisterSignal(removed_from, COMSIG_LIVING_LIFE)
+ removed_from.remove_traits(applied_traits + TRAIT_WING_BUFFET_TIRED, REF(src))
+
+/// Decay our accumulated additional tiredness
+/datum/action/cooldown/mob_cooldown/wing_buffet/proc/on_life(mob/living/liver, seconds_per_tick, times_fired)
+ SIGNAL_HANDLER
+ if (liver.stat == DEAD)
+ return // not so life now buddy
+ additional_endlag = max(0, additional_endlag - (endlag_decay_per_second * seconds_per_tick))
+
+/datum/action/cooldown/mob_cooldown/wing_buffet/Activate(atom/target)
+ begin_sequence()
+ StartCooldown()
+ return TRUE
+
+/// Rise up into the air
+/datum/action/cooldown/mob_cooldown/wing_buffet/proc/begin_sequence()
+ owner.add_traits(applied_traits, REF(src)) // No moving till we're done
+ owner.update_appearance(UPDATE_ICON)
+ animate(owner, pixel_y = 20, time = windup_time)
+ active_timer = addtimer(CALLBACK(src, PROC_REF(ground_pound)), windup_time, TIMER_DELETE_ME | TIMER_STOPPABLE)
+
+/// Slam into the ground
+/datum/action/cooldown/mob_cooldown/wing_buffet/proc/ground_pound()
+ if (QDELETED(owner))
+ return
+ owner.pixel_y = 0
+ playsound(owner, 'sound/effects/gravhit.ogg', 100, TRUE)
+ for (var/mob/living/candidate in view(gust_distance, owner))
+ if(candidate == owner || candidate.faction_check_atom(owner))
+ continue
+ owner.visible_message(span_boldwarning("[candidate] is knocked back by the gust!"))
+ to_chat(candidate, span_userdanger("You're knocked back by the gust!"))
+ var/dir_to_target = get_dir(get_turf(owner), get_turf(candidate))
+ var/throwtarget = get_edge_target_turf(target, dir_to_target)
+ candidate.safe_throw_at(throwtarget, range = 10, speed = 1, thrower = owner)
+ candidate.Paralyze(5 SECONDS)
+
+ var/endlag_multiplier = HAS_TRAIT(owner, TRAIT_RIFT_FAILURE) ? exhaustion_multiplier : 1
+ var/stun_time = minimum_endlag + (additional_endlag * endlag_multiplier)
+ additional_endlag += endlag_per_activation * endlag_multiplier // double dips, rough
+ ADD_TRAIT(owner, TRAIT_WING_BUFFET_TIRED, REF(src))
+ owner.update_appearance(UPDATE_ICON)
+ active_timer = addtimer(CALLBACK(src, PROC_REF(complete_ability)), stun_time, TIMER_DELETE_ME | TIMER_STOPPABLE)
+
+/datum/action/cooldown/mob_cooldown/wing_buffet/proc/complete_ability()
+ owner.remove_traits(applied_traits + TRAIT_WING_BUFFET_TIRED, REF(src))
+ owner.update_appearance(UPDATE_ICON)
+
+#undef DEFAULT_ACTIVATED_ENDLAG
diff --git a/code/modules/mob/living/basic/space_fauna/space_dragon/space_dragon.dm b/code/modules/mob/living/basic/space_fauna/space_dragon/space_dragon.dm
new file mode 100644
index 00000000000..50c54da6c9b
--- /dev/null
+++ b/code/modules/mob/living/basic/space_fauna/space_dragon/space_dragon.dm
@@ -0,0 +1,230 @@
+/// You can't make a dragon darker than this, it'd be hard to see
+#define REJECT_DARK_COLOUR_THRESHOLD 50
+/// Any interactions executed by the space dragon
+#define DOAFTER_SOURCE_SPACE_DRAGON_INTERACTION "space dragon interaction"
+
+/**
+ * Advanced stage of the space carp life cycle, spawned as a midround antagonist or via traitor transformation.
+ * Can eat corpses to heal, blow people back with its wings, and obviously as a dragon it breathes fire. It can even tear through walls.
+ * The midround even version also creates rifts which summon carp, and heals when near them.
+ */
+/mob/living/basic/space_dragon
+ name = "Space Dragon"
+ desc = "A serpentine leviathan whose flight defies all modern understanding of physics. Said to be the ultimate stage in the life cycle of the Space Carp."
+ icon = 'icons/mob/nonhuman-player/spacedragon.dmi'
+ icon_state = "spacedragon"
+ icon_living = "spacedragon"
+ icon_dead = "spacedragon_dead"
+ health_doll_icon = "spacedragon"
+ faction = list(FACTION_CARP)
+ flags_1 = PREVENT_CONTENTS_EXPLOSION_1
+ gender = NEUTER
+ maxHealth = 400
+ health = 400
+ unsuitable_cold_damage = 0
+ unsuitable_heat_damage = 0
+ unsuitable_atmos_damage = 0
+ damage_coeff = list(BRUTE = 1, BURN = 1, TOX = 1, CLONE = 1, STAMINA = 0.5, OXY = 1)
+ combat_mode = TRUE
+ speed = 0
+ movement_type = FLYING
+ attack_verb_continuous = "chomps"
+ attack_verb_simple = "chomp"
+ attack_sound = 'sound/magic/demon_attack1.ogg'
+ attack_vis_effect = ATTACK_EFFECT_BITE
+ obj_damage = 50
+ melee_damage_upper = 35
+ melee_damage_lower = 35
+ melee_attack_cooldown = CLICK_CD_MELEE
+ mob_size = MOB_SIZE_LARGE
+ armour_penetration = 30
+ pixel_x = -16
+ base_pixel_x = -16
+ maptext_height = 64
+ maptext_width = 64
+ mouse_opacity = MOUSE_OPACITY_ICON
+ death_sound = 'sound/creatures/space_dragon_roar.ogg'
+ death_message = "screeches in agony as it collapses to the floor, its life extinguished."
+ butcher_results = list(/obj/item/stack/ore/diamond = 5, /obj/item/stack/sheet/sinew = 5, /obj/item/stack/sheet/bone = 30)
+
+ /// The colour of the space dragon
+ var/chosen_colour
+ /// Minimum devastation damage dealt coefficient based on max health
+ var/devastation_damage_min_percentage = 0.4
+ /// Maximum devastation damage dealt coefficient based on max health
+ var/devastation_damage_max_percentage = 0.75
+ /// Our fire breath action
+ var/datum/action/cooldown/mob_cooldown/fire_breath/carp/fire_breath
+ /// Our wing flap action
+ var/datum/action/cooldown/mob_cooldown/wing_buffet/buffet
+
+/mob/living/basic/space_dragon/Initialize(mapload)
+ . = ..()
+ add_traits(list(TRAIT_SPACEWALK, TRAIT_FREE_HYPERSPACE_MOVEMENT, TRAIT_NO_FLOATING_ANIM, TRAIT_HEALS_FROM_CARP_RIFTS), INNATE_TRAIT)
+ AddElement(/datum/element/simple_flying)
+ AddElement(/datum/element/content_barfer)
+ AddElement(/datum/element/wall_tearer, do_after_key = DOAFTER_SOURCE_SPACE_DRAGON_INTERACTION)
+ AddElement(/datum/element/door_pryer, pry_time = 4 SECONDS, interaction_key = DOAFTER_SOURCE_SPACE_DRAGON_INTERACTION)
+ AddComponent(/datum/component/seethrough_mob)
+ RegisterSignal(src, COMSIG_HOSTILE_PRE_ATTACKINGTARGET, PROC_REF(pre_attack))
+ RegisterSignal(src, COMSIG_MOB_STATCHANGE, PROC_REF(on_stat_changed))
+ RegisterSignal(src, COMSIG_ATOM_PRE_EX_ACT, PROC_REF(on_exploded))
+
+ fire_breath = new(src)
+ fire_breath.Grant(src)
+
+ buffet = new(src)
+ buffet.Grant(src)
+
+/mob/living/basic/space_dragon/Destroy()
+ QDEL_NULL(fire_breath)
+ QDEL_NULL(buffet)
+ return ..()
+
+/mob/living/basic/space_dragon/Login()
+ . = ..()
+ if(!isnull(chosen_colour))
+ return
+ rename_dragon()
+ select_colour()
+
+/// Allows the space dragon to pick a funny name
+/mob/living/basic/space_dragon/proc/rename_dragon()
+ var/chosen_name = sanitize_name(reject_bad_text(tgui_input_text(src, "What would you like your name to be?", "Choose Your Name", real_name, MAX_NAME_LEN)))
+ if(!chosen_name) // Null or empty or rejected
+ to_chat(src, span_warning("Not a valid name, please try again."))
+ rename_dragon()
+ return
+ to_chat(src, span_notice("Your name is now [span_name("[chosen_name]")], the feared Space Dragon."))
+ fully_replace_character_name(null, chosen_name)
+
+/// Select scale colour with the colour picker
+/mob/living/basic/space_dragon/proc/select_colour()
+ chosen_colour = input(src, "What colour would you like to be?" ,"Colour Selection", COLOR_WHITE) as color|null
+ if(!chosen_colour) // Redo proc until we get a color
+ to_chat(src, span_warning("Not a valid colour, please try again."))
+ select_colour()
+ return
+ var/temp_hsv = RGBtoHSV(chosen_colour)
+ if(ReadHSV(temp_hsv)[3] < REJECT_DARK_COLOUR_THRESHOLD)
+ to_chat(src, span_danger("Invalid colour. Your colour is not bright enough."))
+ select_colour()
+ return
+ add_atom_colour(chosen_colour, FIXED_COLOUR_PRIORITY)
+ update_appearance(UPDATE_OVERLAYS)
+
+/mob/living/basic/space_dragon/update_icon_state()
+ . = ..()
+ if (stat == DEAD)
+ return
+ if (!HAS_TRAIT(src, TRAIT_WING_BUFFET))
+ icon_state = icon_living
+ return
+ if (HAS_TRAIT(src, TRAIT_WING_BUFFET_TIRED))
+ icon_state = "spacedragon_gust_2"
+ return
+ icon_state = "spacedragon_gust"
+
+/mob/living/basic/space_dragon/update_overlays()
+ . = ..()
+ var/overlay_state = "overlay_base"
+ if (stat == DEAD)
+ overlay_state = "overlay_dead"
+ else if (HAS_TRAIT(src, TRAIT_WING_BUFFET_TIRED))
+ overlay_state = "overlay_gust_2"
+ else if (HAS_TRAIT(src, TRAIT_WING_BUFFET))
+ overlay_state = "overlay_gust"
+
+ var/mutable_appearance/overlay = mutable_appearance(icon, overlay_state)
+ overlay.appearance_flags = RESET_COLOR
+ . += overlay
+
+/mob/living/basic/space_dragon/melee_attack(obj/vehicle/sealed/mecha/target, list/modifiers, ignore_cooldown)
+ . = ..()
+ if (!. || !ismecha(target))
+ return
+ target.take_damage(obj_damage, BRUTE, MELEE)
+
+/// Before we attack something, check if we want to do something else instead
+/mob/living/basic/space_dragon/proc/pre_attack(mob/living/source, atom/target)
+ SIGNAL_HANDLER
+ if (target == src)
+ return COMPONENT_HOSTILE_NO_ATTACK // Easy to misclick yourself, let's not
+ if (DOING_INTERACTION(source, DOAFTER_SOURCE_SPACE_DRAGON_INTERACTION))
+ balloon_alert(source, "busy!")
+ return COMPONENT_HOSTILE_NO_ATTACK
+ if (!isliving(target))
+ return
+ var/mob/living/living_target = target
+ if (living_target.stat != DEAD)
+ return
+ INVOKE_ASYNC(src, PROC_REF(try_eat), living_target)
+ return COMPONENT_HOSTILE_NO_ATTACK
+
+/// Try putting something inside us
+/mob/living/basic/space_dragon/proc/try_eat(mob/living/food)
+ balloon_alert(src, "swallowing...")
+ if (do_after(src, 3 SECONDS, target = food))
+ eat(food)
+
+/// Succeed in putting something inside us
+/mob/living/basic/space_dragon/proc/eat(mob/living/food)
+ adjust_health(-food.maxHealth * 0.25)
+ if (QDELETED(food) || food.loc == src)
+ return FALSE
+ playsound(src, 'sound/magic/demon_attack1.ogg', 60, TRUE)
+ visible_message(span_boldwarning("[src] swallows [food] whole!"))
+ food.extinguish_mob() // It's wet in there, and our food is likely to be on fire. Let's be decent and not husk them.
+ food.forceMove(src)
+ return TRUE
+
+/mob/living/basic/space_dragon/Entered(atom/movable/arrived, atom/old_loc, list/atom/old_locs)
+ . = ..()
+ if (isliving(arrived))
+ RegisterSignal(arrived, COMSIG_MOB_STATCHANGE, PROC_REF(eaten_stat_changed))
+
+/mob/living/basic/space_dragon/Exited(atom/movable/gone, direction)
+ . = ..()
+ if (isliving(gone))
+ UnregisterSignal(gone, COMSIG_MOB_STATCHANGE)
+
+/// Release consumed mobs if they transition from dead to alive
+/mob/living/basic/space_dragon/proc/eaten_stat_changed(mob/living/eaten)
+ SIGNAL_HANDLER
+ if (eaten.stat == DEAD)
+ return
+ new /obj/effect/decal/cleanable/vomit(loc)
+ playsound(src, 'sound/effects/splat.ogg', vol = 50, vary = TRUE)
+ visible_message(span_danger("[src] vomits up [eaten]!"))
+ eaten.forceMove(loc)
+ eaten.Paralyze(5 SECONDS)
+
+/mob/living/basic/space_dragon/RangedAttack(atom/target, modifiers)
+ fire_breath.Trigger(target = target)
+
+/mob/living/basic/space_dragon/ranged_secondary_attack(atom/target, modifiers)
+ buffet.Trigger()
+
+/// When our stat changes, make sure we are using the correct overlay
+/mob/living/basic/space_dragon/proc/on_stat_changed()
+ SIGNAL_HANDLER
+ update_appearance(UPDATE_OVERLAYS)
+
+/// We take devastating bomb damage as a random percentage of our maximum health instead of being gibbed
+/mob/living/basic/space_dragon/proc/on_exploded(mob/living/source, severity, target, origin)
+ SIGNAL_HANDLER
+ if (severity != EXPLODE_DEVASTATE)
+ return
+ var/damage_coefficient = rand(devastation_damage_min_percentage, devastation_damage_max_percentage)
+ adjustBruteLoss(initial(maxHealth)*damage_coefficient)
+ return COMPONENT_CANCEL_EX_ACT // we handled it
+
+/// Subtype used by the midround/event
+/mob/living/basic/space_dragon/spawn_with_antag
+
+/mob/living/basic/space_dragon/spawn_with_antag/mind_initialize()
+ . = ..()
+ mind.add_antag_datum(/datum/antagonist/space_dragon)
+
+#undef REJECT_DARK_COLOUR_THRESHOLD
+#undef DOAFTER_SOURCE_SPACE_DRAGON_INTERACTION
diff --git a/code/modules/mob/living/basic/space_fauna/spider/spider_abilities/lay_eggs.dm b/code/modules/mob/living/basic/space_fauna/spider/spider_abilities/lay_eggs.dm
index 28a543be3ac..01477cd8435 100644
--- a/code/modules/mob/living/basic/space_fauna/spider/spider_abilities/lay_eggs.dm
+++ b/code/modules/mob/living/basic/space_fauna/spider/spider_abilities/lay_eggs.dm
@@ -5,7 +5,6 @@
button_icon_state = "lay_eggs"
background_icon_state = "bg_alien"
overlay_icon_state = "bg_alien_border"
- check_flags = AB_CHECK_CONSCIOUS | AB_CHECK_INCAPACITATED
cooldown_time = 0
melee_cooldown_time = 0
shared_cooldown = NONE
diff --git a/code/modules/mob/living/basic/space_fauna/spider/spider_abilities/web.dm b/code/modules/mob/living/basic/space_fauna/spider/spider_abilities/web.dm
index b7062a2dc17..d4f587c9386 100644
--- a/code/modules/mob/living/basic/space_fauna/spider/spider_abilities/web.dm
+++ b/code/modules/mob/living/basic/space_fauna/spider/spider_abilities/web.dm
@@ -6,7 +6,6 @@
button_icon_state = "lay_web"
background_icon_state = "bg_alien"
overlay_icon_state = "bg_alien_border"
- check_flags = AB_CHECK_CONSCIOUS | AB_CHECK_INCAPACITATED
cooldown_time = 0 SECONDS
melee_cooldown_time = 0
shared_cooldown = NONE
diff --git a/code/modules/mob/living/basic/space_fauna/spider/spider_abilities/wrap.dm b/code/modules/mob/living/basic/space_fauna/spider/spider_abilities/wrap.dm
index 536f09cef8d..e7771f075a8 100644
--- a/code/modules/mob/living/basic/space_fauna/spider/spider_abilities/wrap.dm
+++ b/code/modules/mob/living/basic/space_fauna/spider/spider_abilities/wrap.dm
@@ -8,7 +8,6 @@
overlay_icon_state = "bg_alien_border"
button_icon = 'icons/mob/actions/actions_animal.dmi'
button_icon_state = "wrap_0"
- check_flags = AB_CHECK_CONSCIOUS | AB_CHECK_INCAPACITATED
click_to_activate = TRUE
ranged_mousepointer = 'icons/effects/mouse_pointers/wrap_target.dmi'
shared_cooldown = NONE
diff --git a/code/modules/mob/living/basic/vermin/mouse.dm b/code/modules/mob/living/basic/vermin/mouse.dm
index 87d549ac523..6043cceb4b1 100644
--- a/code/modules/mob/living/basic/vermin/mouse.dm
+++ b/code/modules/mob/living/basic/vermin/mouse.dm
@@ -72,7 +72,7 @@
/mob/living/basic/mouse/examine(mob/user)
. = ..()
- var/sameside = user.faction_check_mob(src, exact_match = TRUE)
+ var/sameside = user.faction_check_atom(src, exact_match = TRUE)
if(isregalrat(user))
if(sameside)
. += span_notice("This rat serves under you.")
diff --git a/code/modules/mob/living/carbon/carbon_defines.dm b/code/modules/mob/living/carbon/carbon_defines.dm
index c467cd2f001..0cfb9d08c83 100644
--- a/code/modules/mob/living/carbon/carbon_defines.dm
+++ b/code/modules/mob/living/carbon/carbon_defines.dm
@@ -122,8 +122,6 @@
/// A bitfield of "bodytypes", updated by /obj/item/bodypart/proc/synchronize_bodytypes()
var/bodytype = BODYTYPE_HUMANOID | BODYTYPE_ORGANIC
- var/is_leaning = FALSE
-
COOLDOWN_DECLARE(bleeding_message_cd)
diff --git a/code/modules/mob/living/carbon/human/_species.dm b/code/modules/mob/living/carbon/human/_species.dm
index 22589f09440..6bd93d364d3 100644
--- a/code/modules/mob/living/carbon/human/_species.dm
+++ b/code/modules/mob/living/carbon/human/_species.dm
@@ -1218,7 +1218,6 @@ GLOBAL_LIST_EMPTY(features_by_species)
target.lastattacker = user.real_name
target.lastattackerckey = user.ckey
- user.dna.species.spec_unarmedattacked(user, target)
if(user.limb_destroyer)
target.dismembering_strike(user, affecting.body_zone)
@@ -1250,9 +1249,6 @@ GLOBAL_LIST_EMPTY(features_by_species)
target.StaminaKnockdown(20) //SKYRAT EDIT ADDITION
log_combat(user, target, "got a stun punch with their previous punch")
-/datum/species/proc/spec_unarmedattacked(mob/living/carbon/human/user, mob/living/carbon/human/target)
- return
-
/datum/species/proc/disarm(mob/living/carbon/human/user, mob/living/carbon/human/target, datum/martial_art/attacker_style)
if(target.check_block())
target.visible_message(span_warning("[user]'s shove is blocked by [target]!"), \
@@ -1829,11 +1825,8 @@ GLOBAL_LIST_EMPTY(features_by_species)
former_tail_owner.clear_mood_event("tail_balance_lost")
former_tail_owner.clear_mood_event("wrong_tail_regained")
-///Species override for unarmed attacks because the attack_hand proc was made by a mouth-breathing troglodyte on a tricycle. Also to whoever thought it would be a good idea to make it so the original spec_unarmedattack was not actually linked to unarmed attack needs to be checked by a doctor because they clearly have a vast empty space in their head.
-/datum/species/proc/spec_unarmedattack(mob/living/carbon/human/user, atom/target, modifiers)
- return FALSE
-
/* SKYRAT EDIT REMOVAL - MOVED TO MODULAR
+
/// Returns a list of strings representing features this species has.
/// Used by the preferences UI to know what buttons to show.
/datum/species/proc/get_features()
diff --git a/code/modules/mob/living/carbon/human/species_types/android.dm b/code/modules/mob/living/carbon/human/species_types/android.dm
index ed8163a1538..a6f6a484afc 100644
--- a/code/modules/mob/living/carbon/human/species_types/android.dm
+++ b/code/modules/mob/living/carbon/human/species_types/android.dm
@@ -22,6 +22,7 @@
TRAIT_NOBLOOD,
TRAIT_NO_DNA_COPY,
TRAIT_NO_TRANSFORMATION_STING,
+ TRAIT_NOCRITDAMAGE,
)
inherent_biotypes = MOB_ROBOTIC|MOB_HUMANOID
diff --git a/code/modules/mob/living/carbon/human/species_types/monkeys.dm b/code/modules/mob/living/carbon/human/species_types/monkeys.dm
index 2d054c67c71..bcb0a831182 100644
--- a/code/modules/mob/living/carbon/human/species_types/monkeys.dm
+++ b/code/modules/mob/living/carbon/human/species_types/monkeys.dm
@@ -52,70 +52,13 @@
passtable_on(H, SPECIES_TRAIT)
H.dna.add_mutation(/datum/mutation/human/race, MUT_NORMAL)
H.dna.activate_mutation(/datum/mutation/human/race)
+ H.AddElement(/datum/element/human_biter)
/datum/species/monkey/on_species_loss(mob/living/carbon/C)
. = ..()
passtable_off(C, SPECIES_TRAIT)
C.dna.remove_mutation(/datum/mutation/human/race)
-
-/datum/species/monkey/spec_unarmedattack(mob/living/carbon/human/user, atom/target, modifiers)
- // If our hands are not blocked, dont try to bite them
- if(!HAS_TRAIT(user, TRAIT_HANDS_BLOCKED))
- // if we aren't an advanced tool user, we call attack_paw and cancel the preceeding attack chain
- if(!ISADVANCEDTOOLUSER(user))
- target.attack_paw(user, modifiers)
- return TRUE
- return ..()
-
- // this shouldn't even be possible, but I'm sure the check was here for a reason
- if(!iscarbon(target))
- stack_trace("HEY LISTEN! We are performing a species spec_unarmed attack with a non-carbon user. How did you fuck this up?")
- return TRUE
- var/mob/living/carbon/victim = target
- if(user.is_muzzled())
- return TRUE // cannot bite them if we're muzzled
-
- var/obj/item/bodypart/affecting
- if(ishuman(victim))
- var/mob/living/carbon/human/human_victim = victim
- affecting = human_victim.get_bodypart(human_victim.get_random_valid_zone(even_weights = TRUE))
- var/armor = victim.run_armor_check(affecting, MELEE)
-
- if(prob(MONKEY_SPEC_ATTACK_BITE_MISS_CHANCE))
- victim.visible_message(
- span_danger("[user]'s bite misses [victim]!"),
- span_danger("You avoid [user]'s bite!"),
- span_hear("You hear jaws snapping shut!"),
- COMBAT_MESSAGE_RANGE,
- user,
- )
- to_chat(user, span_danger("Your bite misses [victim]!"))
- return TRUE
-
- var/obj/item/bodypart/head/mouth = user.get_bodypart(BODY_ZONE_HEAD)
- if(!mouth) // check for them having a head, ala HARS
- return TRUE
-
- var/damage_roll = rand(mouth.unarmed_damage_low, mouth.unarmed_damage_high)
- victim.apply_damage(damage_roll, BRUTE, affecting, armor)
-
- victim.visible_message(
- span_danger("[name] bites [victim]!"),
- span_userdanger("[name] bites you!"),
- span_hear("You hear a chomp!"),
- COMBAT_MESSAGE_RANGE,
- name,
- )
- to_chat(user, span_danger("You bite [victim]!"))
-
- if(armor >= 2) // if they have basic armor on the limb we bit, don't spread diseases
- return TRUE
- for(var/datum/disease/bite_infection as anything in user.diseases)
- if(bite_infection.spread_flags & (DISEASE_SPREAD_SPECIAL | DISEASE_SPREAD_NON_CONTAGIOUS))
- continue // ignore diseases that have special spread logic, or are not contagious
- victim.ForceContractDisease(bite_infection)
-
- return TRUE
+ C.RemoveElement(/datum/element/human_biter)
/datum/species/monkey/check_roundstart_eligible()
if(check_holidays(MONKEYDAY))
diff --git a/code/modules/mob/living/living_defense.dm b/code/modules/mob/living/living_defense.dm
index 01362dfb1ba..454742c3313 100644
--- a/code/modules/mob/living/living_defense.dm
+++ b/code/modules/mob/living/living_defense.dm
@@ -321,10 +321,6 @@
return martial_result
/mob/living/attack_paw(mob/living/carbon/human/user, list/modifiers)
- if(isturf(loc) && istype(loc.loc, /area/misc/start))
- to_chat(user, "No attacking people at spawn, you jackass.")
- return FALSE
-
var/martial_result = user.apply_martial_art(src, modifiers)
if (martial_result != MARTIAL_ATTACK_INVALID)
return martial_result
@@ -339,6 +335,8 @@
to_chat(user, span_warning("You don't want to hurt anyone!"))
return FALSE
+ if(!user.get_bodypart(BODY_ZONE_HEAD))
+ return FALSE
if(user.is_muzzled() || user.is_mouth_covered(ITEM_SLOT_MASK))
to_chat(user, span_warning("You can't bite with your mouth covered!"))
return FALSE
diff --git a/code/modules/mob/living/simple_animal/friendly/drone/drone_say.dm b/code/modules/mob/living/simple_animal/friendly/drone/drone_say.dm
index 88fc3b5cd85..0a89b0f956b 100644
--- a/code/modules/mob/living/simple_animal/friendly/drone/drone_say.dm
+++ b/code/modules/mob/living/simple_animal/friendly/drone/drone_say.dm
@@ -6,7 +6,7 @@
* * dead_can_hear - Boolean that determines if ghosts can hear the message (`FALSE` by default)
* * source - [/atom] source that created the message
* * faction_checked_mob - [/mob/living] to determine faction matches from
- * * exact_faction_match - Passed to [/mob/proc/faction_check_mob]
+ * * exact_faction_match - Passed to [/mob/proc/faction_check_atom]
*/
/proc/_alert_drones(msg, dead_can_hear = FALSE, atom/source, mob/living/faction_checked_mob, exact_faction_match)
if (dead_can_hear && source)
@@ -17,7 +17,7 @@
var/mob/living/simple_animal/drone/D = i
if(istype(D) && D.stat != DEAD)
if(faction_checked_mob)
- if(D.faction_check_mob(faction_checked_mob, exact_faction_match))
+ if(D.faction_check_atom(faction_checked_mob, exact_faction_match))
to_chat(D, msg)
else
to_chat(D, msg)
@@ -28,7 +28,7 @@
* Wraps [/proc/_alert_drones] with defaults
*
* * source - `src`
- * * faction_check_mob - `src`
+ * * faction_check_atom - `src`
* * dead_can_hear - `TRUE`
*/
/mob/living/simple_animal/drone/proc/alert_drones(msg, dead_can_hear = FALSE)
diff --git a/code/modules/mob/living/simple_animal/hostile/hostile.dm b/code/modules/mob/living/simple_animal/hostile/hostile.dm
index 305853adb96..96ae60c3e07 100644
--- a/code/modules/mob/living/simple_animal/hostile/hostile.dm
+++ b/code/modules/mob/living/simple_animal/hostile/hostile.dm
@@ -263,7 +263,7 @@
if(search_objects < 2)
if(isliving(the_target))
var/mob/living/L = the_target
- var/faction_check = faction_check_mob(L)
+ var/faction_check = faction_check_atom(L)
if(robust_searching)
if(faction_check && !attack_same)
return FALSE
@@ -433,7 +433,7 @@
playsound(loc, 'sound/machines/chime.ogg', 50, TRUE, -1)
var/atom/target_from = GET_TARGETS_FROM(src)
for(var/mob/living/simple_animal/hostile/M in oview(distance, target_from))
- if(faction_check_mob(M, TRUE))
+ if(faction_check_atom(M, TRUE))
if(M.AIStatus == AI_OFF)
return
else
@@ -445,7 +445,7 @@
for(var/mob/living/L in T)
if(L == src || L == A)
continue
- if(faction_check_mob(L) && !attack_same)
+ if(faction_check_atom(L) && !attack_same)
return TRUE
/mob/living/simple_animal/hostile/proc/OpenFire(atom/A)
diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/bubblegum.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/bubblegum.dm
index 02d9e582b1a..ec90fa9fa7b 100644
--- a/code/modules/mob/living/simple_animal/hostile/megafauna/bubblegum.dm
+++ b/code/modules/mob/living/simple_animal/hostile/megafauna/bubblegum.dm
@@ -137,7 +137,7 @@ Difficulty: Hard
. = list()
for(var/mob/living/L in targets)
var/list/bloodpool = get_bloodcrawlable_pools(get_turf(L), 0)
- if(bloodpool.len && (!faction_check_mob(L) || L.stat == DEAD))
+ if(bloodpool.len && (!faction_check_atom(L) || L.stat == DEAD))
. += L
/**
@@ -201,7 +201,7 @@ Difficulty: Hard
new /obj/effect/temp_visual/bubblegum_hands/leftsmack(T)
SLEEP_CHECK_DEATH(4, src)
for(var/mob/living/L in T)
- if(!faction_check_mob(L))
+ if(!faction_check_atom(L))
to_chat(L, span_userdanger("[src] rends you!"))
playsound(T, attack_sound, 100, TRUE, -1)
var/limb_to_hit = L.get_bodypart(L.get_random_valid_zone(even_weights = TRUE))
@@ -217,7 +217,7 @@ Difficulty: Hard
new /obj/effect/temp_visual/bubblegum_hands/leftthumb(T)
SLEEP_CHECK_DEATH(6, src)
for(var/mob/living/L in T)
- if(!faction_check_mob(L))
+ if(!faction_check_atom(L))
if(L.stat != CONSCIOUS)
to_chat(L, span_userdanger("[src] drags you through the blood!"))
playsound(T, 'sound/magic/enter_blood.ogg', 100, TRUE, -1)
diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/drake.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/drake.dm
index 84e2d9c049b..5aef5c9029b 100644
--- a/code/modules/mob/living/simple_animal/hostile/megafauna/drake.dm
+++ b/code/modules/mob/living/simple_animal/hostile/megafauna/drake.dm
@@ -1,7 +1,3 @@
-///used whenever the drake generates a hotspot
-#define DRAKE_FIRE_TEMP 500
-///used whenever the drake generates a hotspot
-#define DRAKE_FIRE_EXPOSURE 50
///used to see if the drake is enraged or not
#define DRAKE_ENRAGED (health < maxHealth*0.5)
@@ -175,36 +171,6 @@
remove_atom_colour(TEMPORARY_COLOUR_PRIORITY)
set_light_range(initial(light_range))
-//fire line keeps going even if dragon is deleted
-/proc/dragon_fire_line(atom/source, list/turfs, frozen = FALSE)
- var/list/hit_list = list()
- for(var/turf/T in turfs)
- if(isclosedturf(T))
- break
- var/obj/effect/hotspot/drake_fire_hotspot = new /obj/effect/hotspot(T)
- if(frozen)
- drake_fire_hotspot.add_atom_colour(COLOR_BLUE_LIGHT, FIXED_COLOUR_PRIORITY)
- T.hotspot_expose(DRAKE_FIRE_TEMP,DRAKE_FIRE_EXPOSURE,1)
- for(var/mob/living/L in T.contents)
- if(L in hit_list || istype(L, source.type))
- continue
- hit_list += L
- if(!frozen)
- L.adjustFireLoss(20)
- to_chat(L, span_userdanger("You're hit by [source]'s fire breath!"))
- continue
- L.adjustFireLoss(10)
- L.apply_status_effect(/datum/status_effect/ice_block_talisman, 20)
- to_chat(L, span_userdanger("You're hit by [source]'s freezing breath!"))
-
- // deals damage to mechs
- for(var/obj/vehicle/sealed/mecha/M in T.contents)
- if(M in hit_list)
- continue
- hit_list += M
- M.take_damage(45, BRUTE, MELEE, 1)
- sleep(0.15 SECONDS)
-
/mob/living/simple_animal/hostile/megafauna/dragon/ex_act(severity, target)
if(severity <= EXPLODE_LIGHT)
return FALSE
@@ -371,8 +337,5 @@
return
#undef DRAKE_ENRAGED
-#undef DRAKE_FIRE_EXPOSURE
-#undef DRAKE_FIRE_TEMP
-
#undef SWOOP_DAMAGEABLE
#undef SWOOP_INVULNERABLE
diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/hierophant.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/hierophant.dm
index 828a78ccfb1..e20724227f1 100644
--- a/code/modules/mob/living/simple_animal/hostile/megafauna/hierophant.dm
+++ b/code/modules/mob/living/simple_animal/hostile/megafauna/hierophant.dm
@@ -694,7 +694,7 @@ Difficulty: Hard
return
for(var/mob/living/L in T.contents - hit_things) //find and damage mobs...
hit_things += L
- if((friendly_fire_check && caster?.faction_check_mob(L)) || L.stat == DEAD)
+ if((friendly_fire_check && caster?.faction_check_atom(L)) || L.stat == DEAD)
continue
if(L.client)
flash_color(L.client, "#660099", 1)
@@ -719,7 +719,7 @@ Difficulty: Hard
hit_things += M
for(var/O in M.occupants)
var/mob/living/occupant = O
- if(friendly_fire_check && caster?.faction_check_mob(occupant))
+ if(friendly_fire_check && caster?.faction_check_atom(occupant))
continue
to_chat(occupant, span_userdanger("Your [M.name] is struck by a [name]!"))
playsound(M,'sound/weapons/sear.ogg', 50, TRUE, -4)
diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/legion.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/legion.dm
index 777cb3b878f..de693bde9ea 100644
--- a/code/modules/mob/living/simple_animal/hostile/megafauna/legion.dm
+++ b/code/modules/mob/living/simple_animal/hostile/megafauna/legion.dm
@@ -269,14 +269,14 @@
density = TRUE
layer = ABOVE_OBJ_LAYER
armor_type = /datum/armor/structure_legionturret
+ //Compared with the targeted mobs. If they have the faction, turret won't shoot.
+ faction = list(FACTION_MINING)
///What kind of projectile the actual damaging part should be.
var/projectile_type = /obj/projectile/beam/legion
///Time until the tracer gets shot
var/initial_firing_time = 18
///How long it takes between shooting the tracer and the projectile.
var/shot_delay = 8
- ///Compared with the targeted mobs. If they have the faction, turret won't shoot.
- var/faction = list(FACTION_MINING)
/datum/armor/structure_legionturret
laser = 100
diff --git a/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/elite.dm b/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/elite.dm
index 6fe1fa1ecfc..fcfa4560611 100644
--- a/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/elite.dm
+++ b/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/elite.dm
@@ -37,7 +37,7 @@
/mob/living/simple_animal/hostile/asteroid/elite/AttackingTarget()
if(ishostile(target))
var/mob/living/simple_animal/hostile/M = target
- if(faction_check_mob(M))
+ if(faction_check_atom(M))
return FALSE
if(istype(target, /obj/structure/elite_tumor))
var/obj/structure/elite_tumor/T = target
diff --git a/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/herald.dm b/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/herald.dm
index bf4d33a10ed..aa61c128e3f 100644
--- a/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/herald.dm
+++ b/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/herald.dm
@@ -234,7 +234,7 @@
/obj/projectile/herald/on_hit(atom/target, blocked = FALSE)
if(ismob(target) && ismob(firer))
var/mob/living/mob_target = target
- if(mob_target.faction_check_mob(firer))
+ if(mob_target.faction_check_atom(firer))
damage = 0
. = ..()
diff --git a/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/legionnaire.dm b/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/legionnaire.dm
index ec6c843080c..926f4e96baa 100644
--- a/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/legionnaire.dm
+++ b/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/legionnaire.dm
@@ -149,7 +149,7 @@
var/throwtarget = get_edge_target_turf(src, move_dir)
for(var/mob/living/trample_target in T.contents - hit_things - src)
hit_things += trample_target
- if(faction_check_mob(trample_target))
+ if(faction_check_atom(trample_target))
continue
visible_message(span_boldwarning("[src] tramples and kicks [trample_target]!"))
to_chat(trample_target, span_userdanger("[src] tramples you and kicks you away!"))
diff --git a/code/modules/mob/living/simple_animal/hostile/mining_mobs/gutlunch.dm b/code/modules/mob/living/simple_animal/hostile/mining_mobs/gutlunch.dm
index 92ad5f35927..7a3451e4ef8 100644
--- a/code/modules/mob/living/simple_animal/hostile/mining_mobs/gutlunch.dm
+++ b/code/modules/mob/living/simple_animal/hostile/mining_mobs/gutlunch.dm
@@ -60,7 +60,7 @@
if(isliving(the_target))
var/mob/living/L = the_target
- if(faction_check_mob(L) && !attack_same)
+ if(faction_check_atom(L) && !attack_same)
return FALSE
if(L.stat > stat_attack || L.stat != stat_attack && stat_exclusive)
return FALSE
diff --git a/code/modules/mob/living/simple_animal/hostile/retaliate/retaliate.dm b/code/modules/mob/living/simple_animal/hostile/retaliate/retaliate.dm
index 237360468f8..bf1c12d5da1 100644
--- a/code/modules/mob/living/simple_animal/hostile/retaliate/retaliate.dm
+++ b/code/modules/mob/living/simple_animal/hostile/retaliate/retaliate.dm
@@ -37,7 +37,7 @@
continue
if(isliving(A))
var/mob/living/M = A
- if(faction_check_mob(M) && attack_same || !faction_check_mob(M))
+ if(faction_check_atom(M) && attack_same || !faction_check_atom(M))
enemies |= WEAKREF(M)
else if(ismecha(A))
var/obj/vehicle/sealed/mecha/M = A
@@ -46,7 +46,7 @@
add_enemies(M.occupants)
for(var/mob/living/simple_animal/hostile/retaliate/H in around)
- if(faction_check_mob(H) && !attack_same && !H.attack_same)
+ if(faction_check_atom(H) && !attack_same && !H.attack_same)
H.enemies |= enemies
/mob/living/simple_animal/hostile/retaliate/adjustHealth(amount, updating_health = TRUE, forced = FALSE)
diff --git a/code/modules/mob/living/simple_animal/hostile/space_dragon.dm b/code/modules/mob/living/simple_animal/hostile/space_dragon.dm
deleted file mode 100644
index c536da22cd1..00000000000
--- a/code/modules/mob/living/simple_animal/hostile/space_dragon.dm
+++ /dev/null
@@ -1,444 +0,0 @@
-/// The darkness threshold for space dragon when choosing a color
-#define DARKNESS_THRESHOLD 50
-/// Any interactions executed by the space dragon
-#define DOAFTER_SOURCE_SPACE_DRAGON_INTERACTION "space dragon interaction"
-
-/**
- * # Space Dragon
- *
- * A space-faring leviathan-esque monster which breathes fire and summons carp. Spawned during its respective midround antagonist event.
- *
- * A space-faring monstrosity who has the ability to breathe dangerous fire breath and uses its powerful wings to knock foes away.
- * Normally spawned as an antagonist during the Space Dragon event, Space Dragon's main goal is to open three rifts from which to pull a great tide of carp onto the station.
- * Space Dragon can summon only one rift at a time, and can do so anywhere a blob is allowed to spawn. In order to trigger his victory condition, Space Dragon must summon and defend three rifts while they charge.
- * Space Dragon, when spawned, has five minutes to summon the first rift. Failing to do so will cause Space Dragon to return from whence he came.
- * When the rift spawns, ghosts can interact with it to spawn in as space carp to help complete the mission. One carp is granted when the rift is first summoned, with an extra one every 30 seconds.
- * Once the victory condition is met, all current rifts become invulnerable to damage, are allowed to spawn infinite sentient space carp, and Space Dragon gets unlimited rage.
- * Alternatively, if the shuttle arrives while Space Dragon is still active, their victory condition will automatically be met and all the rifts will immediately become fully charged.
- * If a charging rift is destroyed, Space Dragon will be incredibly slowed, and the endlag on his gust attack is greatly increased on each use.
- * Space Dragon has the following abilities to assist him with his objective:
- * - Can shoot fire in straight line, dealing 30 burn damage and setting those suseptible on fire.
- * - Can use his wings to temporarily stun and knock back any nearby mobs. This attack has no cooldown, but instead has endlag after the attack where Space Dragon cannot act. This endlag's time decreases over time, but is added to every time he uses the move.
- * - Can swallow mob corpses to heal for half their max health. Any corpses swallowed are stored within him, and will be regurgitated on death.
- * - Can tear through any type of wall. This takes 4 seconds for most walls, and 12 seconds for reinforced walls.
- */
-/mob/living/simple_animal/hostile/space_dragon
- name = "Space Dragon"
- desc = "A vile, leviathan-esque creature that flies in the most unnatural way. Looks slightly similar to a space carp."
- gender = NEUTER
- maxHealth = 400
- health = 400
- damage_coeff = list(BRUTE = 1, BURN = 1, TOX = 1, CLONE = 1, STAMINA = 0.5, OXY = 1)
- combat_mode = TRUE
- speed = 0
- movement_type = FLYING
- attack_verb_continuous = "chomps"
- attack_verb_simple = "chomp"
- attack_sound = 'sound/magic/demon_attack1.ogg'
- attack_vis_effect = ATTACK_EFFECT_BITE
- death_sound = 'sound/creatures/space_dragon_roar.ogg'
- icon = 'icons/mob/nonhuman-player/spacedragon.dmi'
- icon_state = "spacedragon"
- icon_living = "spacedragon"
- icon_dead = "spacedragon_dead"
- health_doll_icon = "spacedragon"
- obj_damage = 50
- environment_smash = ENVIRONMENT_SMASH_NONE
- flags_1 = PREVENT_CONTENTS_EXPLOSION_1
- melee_damage_upper = 35
- melee_damage_lower = 35
- mob_size = MOB_SIZE_LARGE
- armour_penetration = 30
- pixel_x = -16
- base_pixel_x = -16
- maptext_height = 64
- maptext_width = 64
- turns_per_move = 5
- ranged = TRUE
- mouse_opacity = MOUSE_OPACITY_ICON
- butcher_results = list(/obj/item/stack/ore/diamond = 5, /obj/item/stack/sheet/sinew = 5, /obj/item/stack/sheet/bone = 30)
- death_message = "screeches as its wings turn to dust and it collapses on the floor, its life extinguished."
- atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_plas" = 0, "max_plas" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0)
- minbodytemp = 0
- maxbodytemp = 1500
- faction = list(FACTION_CARP)
- pressure_resistance = 200
- /// How much endlag using Wing Gust should apply. Each use of wing gust increments this, and it decreases over time.
- var/tiredness = 0
- /// A multiplier to how much each use of wing gust should add to the tiredness variable. Set to 5 if the current rift is destroyed.
- var/tiredness_mult = 1
- /// The distance Space Dragon's gust reaches
- var/gust_distance = 4
- /// The amount of tiredness to add to Space Dragon per use of gust
- var/gust_tiredness = 30
- /// Determines whether or not Space Dragon is in the middle of using wing gust. If set to true, prevents him from moving and doing certain actions.
- var/using_special = FALSE
- /// The color of the space dragon.
- var/chosen_color
- /// Minimum devastation damage dealt coefficient based on max health
- var/devastation_damage_min_percentage = 0.4
- /// Maximum devastation damage dealt coefficient based on max health
- var/devastation_damage_max_percentage = 0.75
-
-/mob/living/simple_animal/hostile/space_dragon/Initialize(mapload)
- . = ..()
- AddComponent(/datum/component/seethrough_mob)
- AddElement(/datum/element/simple_flying)
- add_traits(list(TRAIT_SPACEWALK, TRAIT_FREE_HYPERSPACE_MOVEMENT, TRAIT_NO_FLOATING_ANIM, TRAIT_HEALS_FROM_CARP_RIFTS), INNATE_TRAIT)
- AddElement(/datum/element/content_barfer)
- RegisterSignal(src, COMSIG_LIVING_UNARMED_ATTACK, PROC_REF(before_attack))
-
-/mob/living/simple_animal/hostile/space_dragon/Login()
- . = ..()
- if(!chosen_color)
- dragon_name()
- color_selection()
-
-/mob/living/simple_animal/hostile/space_dragon/ex_act_devastate()
- var/damage_coefficient = rand(devastation_damage_min_percentage, devastation_damage_max_percentage)
- adjustBruteLoss(initial(maxHealth)*damage_coefficient)
-
-/mob/living/simple_animal/hostile/space_dragon/Life(seconds_per_tick = SSMOBS_DT, times_fired)
- . = ..()
- tiredness = max(tiredness - (0.5 * seconds_per_tick), 0)
-
-/mob/living/simple_animal/hostile/space_dragon/Entered(atom/movable/arrived, atom/old_loc, list/atom/old_locs)
- . = ..()
- if (isliving(arrived))
- RegisterSignal(arrived, COMSIG_MOB_STATCHANGE, PROC_REF(eaten_stat_changed))
-
-/mob/living/simple_animal/hostile/space_dragon/Exited(atom/movable/gone, direction)
- . = ..()
- if (isliving(gone))
- UnregisterSignal(gone, COMSIG_MOB_STATCHANGE)
-
-/// Release consumed mobs if they transition from dead to alive
-/mob/living/simple_animal/hostile/space_dragon/proc/eaten_stat_changed(mob/living/eaten)
- SIGNAL_HANDLER
- if (eaten.stat == DEAD)
- return
- playsound(src, 'sound/effects/splat.ogg', vol = 50, vary = TRUE)
- visible_message(span_danger("[src] vomits up [eaten]!"))
- eaten.forceMove(loc)
- eaten.Paralyze(5 SECONDS)
-
-/mob/living/simple_animal/hostile/space_dragon/proc/before_attack(datum/source, atom/target)
- SIGNAL_HANDLER
- if(using_special)
- return COMPONENT_CANCEL_ATTACK_CHAIN
-
- if(target == src)
- to_chat(src, span_warning("You almost bite yourself, but then decide against it."))
- return COMPONENT_CANCEL_ATTACK_CHAIN
-
- if(DOING_INTERACTION(src, DOAFTER_SOURCE_SPACE_DRAGON_INTERACTION)) // patience grasshopper
- target.balloon_alert(src, "finish current action first!")
- return COMPONENT_CANCEL_ATTACK_CHAIN
-
- if(ismecha(target))
- target.take_damage(50, BRUTE, MELEE, 1)
- return // don't block the rest of the attack chain
-
- if(iswallturf(target))
- INVOKE_ASYNC(src, PROC_REF(tear_down_wall), target)
- return COMPONENT_CANCEL_ATTACK_CHAIN
-
- if(isliving(target)) //Swallows corpses like a snake to regain health.
- var/mob/living/living_target = target
- if(living_target.stat != DEAD)
- return // go ham on slapping the shit out of them buddy
-
- INVOKE_ASYNC(src, PROC_REF(eat_this_corpse), living_target)
- return COMPONENT_CANCEL_ATTACK_CHAIN
-
-/// Handles tearing down a wall. Returns TRUE if successful, FALSE otherwise.
-/mob/living/simple_animal/hostile/space_dragon/proc/tear_down_wall(turf/closed/wall/wall_target)
- to_chat(src, span_warning("You begin tearing through the wall..."))
- playsound(src, 'sound/machines/airlock_alien_prying.ogg', 100, TRUE)
-
- var/time_to_tear = 4 SECONDS
- if(istype(wall_target, /turf/closed/wall/r_wall))
- time_to_tear = 12 SECONDS
-
- if(!do_after(src, time_to_tear, target = wall_target, interaction_key = DOAFTER_SOURCE_SPACE_DRAGON_INTERACTION))
- return FALSE
-
- if(isopenturf(wall_target))
- return FALSE // well the thing was destroyed while we were sleeping so that's nice, but we didn't successfully tear it down. whatever
-
- wall_target.dismantle_wall(devastated = TRUE)
- playsound(src, 'sound/effects/meteorimpact.ogg', 100, TRUE)
- return TRUE
-
-/// Handles eating a corpse, giving us a bit of health back. Returns TRUE if we were sucessful in eating, FALSE otherwise.
-/mob/living/simple_animal/hostile/space_dragon/proc/eat_this_corpse(mob/living/corpse)
- to_chat(src, span_warning("You begin to swallow the body of [corpse] whole..."))
-
- if(!do_after(src, 3 SECONDS, target = corpse, interaction_key = DOAFTER_SOURCE_SPACE_DRAGON_INTERACTION))
- return FALSE
- if(!eat(corpse))
- return FALSE
-
- adjustHealth(-(corpse.maxHealth * 0.25))
- return TRUE
-
-/mob/living/simple_animal/hostile/space_dragon/ranged_secondary_attack(atom/target, modifiers)
- if(using_special)
- return
- using_special = TRUE
- icon_state = "spacedragon_gust"
- add_dragon_overlay()
- useGust(0)
-
-/mob/living/simple_animal/hostile/space_dragon/Move()
- if(!using_special)
- ..()
-
-/mob/living/simple_animal/hostile/space_dragon/OpenFire()
- if(using_special)
- return
- ranged_cooldown = world.time + ranged_cooldown_time
- fire_stream()
-
-/mob/living/simple_animal/hostile/space_dragon/death(gibbed)
- . = ..()
- add_dragon_overlay()
-
-/mob/living/simple_animal/hostile/space_dragon/revive(full_heal_flags = NONE, excess_healing = 0, force_grab_ghost = FALSE)
- . = ..()
- add_dragon_overlay()
-
-/**
- * Allows space dragon to choose its own name.
- *
- * Prompts the space dragon to choose a name, which it will then apply to itself.
- * If the name is invalid, will re-prompt the dragon until a proper name is chosen.
- */
-/mob/living/simple_animal/hostile/space_dragon/proc/dragon_name()
- var/chosen_name = sanitize_name(reject_bad_text(tgui_input_text(src, "What would you like your name to be?", "Choose Your Name", real_name, MAX_NAME_LEN)))
- if(!chosen_name)
- to_chat(src, span_warning("Not a valid name, please try again."))
- dragon_name()
- return
- to_chat(src, span_notice("Your name is now [span_name("[chosen_name]")], the feared Space Dragon."))
- fully_replace_character_name(null, chosen_name)
-
-/**
- * Allows space dragon to choose a color for itself.
- *
- * Prompts the space dragon to choose a color, from which it will then apply to itself.
- * If an invalid color is given, will re-prompt the dragon until a proper color is chosen.
- */
-/mob/living/simple_animal/hostile/space_dragon/proc/color_selection()
- chosen_color = input(src,"What would you like your color to be?","Choose Your Color", COLOR_WHITE) as color|null
- if(!chosen_color) //redo proc until we get a color
- to_chat(src, span_warning("Not a valid color, please try again."))
- color_selection()
- return
- var/temp_hsv = RGBtoHSV(chosen_color)
- if(ReadHSV(temp_hsv)[3] < DARKNESS_THRESHOLD)
- to_chat(src, span_danger("Invalid color. Your color is not bright enough."))
- color_selection()
- return
- add_atom_colour(chosen_color, FIXED_COLOUR_PRIORITY)
- add_dragon_overlay()
-
-/**
- * Adds the proper overlay to the space dragon.
- *
- * Clears the current overlay on space dragon and adds a proper one for whatever animation he's in.
- */
-/mob/living/simple_animal/hostile/space_dragon/proc/add_dragon_overlay()
- cut_overlays()
- if(stat == DEAD)
- var/mutable_appearance/overlay = mutable_appearance(icon, "overlay_dead")
- overlay.appearance_flags = RESET_COLOR
- add_overlay(overlay)
- return
- if(!using_special)
- var/mutable_appearance/overlay = mutable_appearance(icon, "overlay_base")
- overlay.appearance_flags = RESET_COLOR
- add_overlay(overlay)
- return
- if(using_special)
- var/mutable_appearance/overlay = mutable_appearance(icon, "overlay_gust")
- overlay.appearance_flags = RESET_COLOR
- add_overlay(overlay)
-
-/**
- * Determines a line of turfs from sources's position to the target with length range.
- *
- * Determines a line of turfs from the source's position to the target with length range.
- * The line will extend on past the target if the range is large enough, and not reach the target if range is small enough.
- * Arguments:
- * * offset - whether or not to aim slightly to the left or right of the target
- * * range - how many turfs should we go out for
- * * atom/at - The target
- */
-/mob/living/simple_animal/hostile/space_dragon/proc/line_target(offset, range, atom/at = target)
- if(!at)
- return
- var/angle = ATAN2(at.x - src.x, at.y - src.y) + offset
- var/turf/T = get_turf(src)
- for(var/i in 1 to range)
- var/turf/check = locate(src.x + cos(angle) * i, src.y + sin(angle) * i, src.z)
- if(!check)
- break
- T = check
- return (get_line(src, T) - get_turf(src))
-
-/**
- * Spawns fire at each position in a line from the source to the target.
- *
- * Spawns fire at each position in a line from the source to the target.
- * Stops if it comes into contact with a solid wall, a window, or a door.
- * Delays the spawning of each fire by 1.5 deciseconds.
- * Arguments:
- * * atom/at - The target
- */
-/mob/living/simple_animal/hostile/space_dragon/proc/fire_stream(atom/at = target)
- playsound(get_turf(src),'sound/magic/fireball.ogg', 200, TRUE)
- var/range = 20
- var/list/turfs = list()
- var/list/hit_list_parameter = list(src)
- turfs = line_target(0, range, at)
- var/delayFire = -1.0
- for(var/turf/T in turfs)
- if(isclosedturf(T))
- return
- for(var/obj/structure/window/W in T.contents)
- return
- for(var/obj/machinery/door/D in T.contents)
- if(D.density)
- return
- delayFire += 1.0
- addtimer(CALLBACK(src, PROC_REF(dragon_fire_line), T, hit_list_parameter), delayFire)
-
-/**
- * What occurs on each tile to actually create the fire.
- *
- * Creates a fire on the given turf.
- * It creates a hotspot on the given turf, damages any living mob with 30 burn damage, and damages mechs by 50.
- * It can only hit any given target once.
- * Arguments:
- * * turf/T - The turf to trigger the effects on.
- * * list/hit_list - The list of targets that have already been hit in the fire_stream.
- */
-/mob/living/simple_animal/hostile/space_dragon/proc/dragon_fire_line(turf/fire_turf, list/hit_list)
- new /obj/effect/hotspot(fire_turf)
- fire_turf.hotspot_expose(700,50,1)
- for(var/mob/living/living_target in fire_turf.contents)
- if(living_target.faction_check_mob(src) && living_target != src)
- hit_list += living_target
- start_carp_speedboost(living_target)
- if(living_target in hit_list)
- continue
- if(living_target.mind?.has_antag_datum(/datum/antagonist/space_carp))
- continue
- hit_list += living_target
- living_target.adjustFireLoss(30)
- to_chat(living_target, span_userdanger("You're hit by [src]'s fire breath!"))
- // deals damage to mechs
- for(var/obj/vehicle/sealed/mecha/mech_target in fire_turf.contents)
- if(mech_target in hit_list)
- continue
- hit_list += mech_target
- mech_target.take_damage(50, BRUTE, MELEE, 1)
-
-/**
- * Applies the speed boost to carps when hit by space dragon's flame breath
- *
- * Applies the dragon rage effect to carps temporarily, giving them a glow and a speed boost.
- * This lasts for 8 seconds.
- * Arguments:
- * * mob/living/target - The carp being affected.
- */
-/mob/living/simple_animal/hostile/space_dragon/proc/start_carp_speedboost(mob/living/target)
- target.add_filter("anger_glow", 3, list("type" = "outline", "color" = "#ff330030", "size" = 2))
- target.add_movespeed_modifier(/datum/movespeed_modifier/dragon_rage)
- addtimer(CALLBACK(src, PROC_REF(end_carp_speedboost), target), 8 SECONDS)
-
-/**
- * Remove the speed boost from carps when hit by space dragon's flame breath
- *
- * Removes the dragon rage effect from carps, removing their glow and speed boost.
- * Arguments:
- * * mob/living/target - The carp being affected.
- */
-/mob/living/simple_animal/hostile/space_dragon/proc/end_carp_speedboost(mob/living/target)
- target.remove_filter("anger_glow")
- target.remove_movespeed_modifier(/datum/movespeed_modifier/dragon_rage)
-
-/**
- * Handles consuming and storing consumed things inside Space Dragon
- *
- * Plays a sound and then stores the consumed thing inside Space Dragon.
- * Used in AttackingTarget(), paired with a heal should it succeed.
- * Arguments:
- * * atom/movable/A - The thing being consumed
- */
-/mob/living/simple_animal/hostile/space_dragon/proc/eat(atom/movable/A)
- if(A && A.loc != src)
- playsound(src, 'sound/magic/demon_attack1.ogg', 60, TRUE)
- visible_message(span_warning("[src] swallows [A] whole!"))
- to_chat(src, span_notice("Your acids cleanse the flames off [A] on the way down. Delicious!"))
- A.extinguish()
- A.forceMove(src)
- return TRUE
- return FALSE
-
-/**
- * Resets Space Dragon's status after using wing gust.
- *
- * Resets Space Dragon's status after using wing gust.
- * If it isn't dead by the time it calls this method, reset the sprite back to the normal living sprite.
- * Also sets the using_special variable to FALSE, allowing Space Dragon to move and attack freely again.
- */
-/mob/living/simple_animal/hostile/space_dragon/proc/reset_status()
- if(stat != DEAD)
- icon_state = "spacedragon"
- using_special = FALSE
- add_dragon_overlay()
-
-/**
- * Handles wing gust from the windup all the way to the endlag at the end.
- *
- * Handles the wing gust attack from start to finish, based on the timer.
- * When intially triggered, starts at 0. Until the timer reaches 10, increase Space Dragon's y position by 2 and call back to the function in 1.5 deciseconds.
- * When the timer is at 10, trigger the attack. Change Space Dragon's sprite. reset his y position, and push all living creatures back in a 3 tile radius and stun them for 5 seconds.
- * Stay in the ending state for how much our tiredness dictates and add to our tiredness.
- * Arguments:
- * * timer - The timer used for the windup.
- */
-/mob/living/simple_animal/hostile/space_dragon/proc/useGust(timer)
- if(timer != 10)
- pixel_y = pixel_y + 2;
- addtimer(CALLBACK(src, PROC_REF(useGust), timer + 1), 1.2)
- return
- pixel_y = 0
- icon_state = "spacedragon_gust_2"
- cut_overlays()
- var/mutable_appearance/overlay = mutable_appearance(icon, "overlay_gust_2")
- overlay.appearance_flags = RESET_COLOR
- add_overlay(overlay)
- playsound(src, 'sound/effects/gravhit.ogg', 100, TRUE)
- for (var/mob/living/candidate in view(gust_distance, src))
- if(candidate == src || candidate.faction_check_mob(src))
- continue
- visible_message(span_boldwarning("[candidate] is knocked back by the gust!"))
- to_chat(candidate, span_userdanger("You're knocked back by the gust!"))
- var/dir_to_target = get_dir(get_turf(src), get_turf(candidate))
- var/throwtarget = get_edge_target_turf(target, dir_to_target)
- candidate.safe_throw_at(throwtarget, 10, 1, src)
- candidate.Paralyze(50)
- addtimer(CALLBACK(src, PROC_REF(reset_status)), 4 + ((tiredness * tiredness_mult) / 10))
- tiredness = tiredness + (gust_tiredness * tiredness_mult)
-
-/mob/living/simple_animal/hostile/space_dragon/spawn_with_antag
-
-/mob/living/simple_animal/hostile/space_dragon/spawn_with_antag/mind_initialize()
- . = ..()
- mind.add_antag_datum(/datum/antagonist/space_dragon)
-
-#undef DARKNESS_THRESHOLD
-#undef DOAFTER_SOURCE_SPACE_DRAGON_INTERACTION
diff --git a/code/modules/mob/living/simple_animal/simple_animal.dm b/code/modules/mob/living/simple_animal/simple_animal.dm
index 85c146b29c8..4c382a59b2f 100644
--- a/code/modules/mob/living/simple_animal/simple_animal.dm
+++ b/code/modules/mob/living/simple_animal/simple_animal.dm
@@ -509,7 +509,7 @@
else if(!is_child && M.gender == MALE && !(M.flags_1 & HOLOGRAM_1)) //Better safe than sorry ;_;
partner = M
- else if(isliving(M) && !faction_check_mob(M)) //shyness check. we're not shy in front of things that share a faction with us.
+ else if(isliving(M) && !faction_check_atom(M)) //shyness check. we're not shy in front of things that share a faction with us.
return //we never mate when not alone, so just abort early
if(alone && partner && children < 3)
diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm
index 9002033a13e..e5ddcf1f0ae 100644
--- a/code/modules/mob/mob.dm
+++ b/code/modules/mob/mob.dm
@@ -1202,21 +1202,7 @@
///Can this mob use storage
/mob/proc/canUseStorage()
return FALSE
-/**
- * Check if the other mob has any factions the same as us
- *
- * If exact match is set, then all our factions must match exactly
- */
-/mob/proc/faction_check_mob(mob/target, exact_match)
- if(exact_match) //if we need an exact match, we need to do some bullfuckery.
- var/list/faction_src = faction.Copy()
- var/list/faction_target = target.faction.Copy()
- if(!("[REF(src)]" in faction_target)) //if they don't have our ref faction, remove it from our factions list.
- faction_src -= "[REF(src)]" //if we don't do this, we'll never have an exact match.
- if(!("[REF(target)]" in faction_src))
- faction_target -= "[REF(target)]" //same thing here.
- return faction_check(faction_src, faction_target, TRUE)
- return faction_check(faction, target.faction, FALSE)
+
/*
* Compare two lists of factions, returning true if any match
*
diff --git a/code/modules/mob/mob_defines.dm b/code/modules/mob/mob_defines.dm
index 12905700318..c5aac5f526e 100644
--- a/code/modules/mob/mob_defines.dm
+++ b/code/modules/mob/mob_defines.dm
@@ -20,6 +20,8 @@
// we never want to hide a turf because it's not lit
// We can rely on the lighting plane to handle that for us
see_in_dark = 1e6
+ // A list of factions that this mob is currently in, for hostile mob targetting, amongst other things
+ faction = list(FACTION_NEUTRAL)
/// The current client inhabiting this mob. Managed by login/logout
/// This exists so we can do cleanup in logout for occasions where a client was transfere rather then destroyed
/// We need to do this because the mob on logout never actually has a reference to client
@@ -144,9 +146,6 @@
/// What job does this mob have
var/job = null//Living
- /// A list of factions that this mob is currently in, for hostile mob targetting, amongst other things
- var/list/faction = list(FACTION_NEUTRAL)
-
/// Can this mob enter shuttles
var/move_on_shuttle = 1
diff --git a/code/modules/mob_spawn/mob_spawn.dm b/code/modules/mob_spawn/mob_spawn.dm
index 02b3ea1866f..3f5dd81c184 100644
--- a/code/modules/mob_spawn/mob_spawn.dm
+++ b/code/modules/mob_spawn/mob_spawn.dm
@@ -11,8 +11,6 @@
var/mob_name
///the type of the mob, you best inherit this
var/mob_type = /mob/living/basic/cockroach
- ///Lazy string list of factions that the spawned mob will be in upon spawn
- var/list/faction
////Human specific stuff. Don't set these if you aren't using a human, the unit tests will put a stop to your sinful hand.
diff --git a/code/modules/mod/mod_core.dm b/code/modules/mod/mod_core.dm
index 45fa61677b2..d5c43497982 100644
--- a/code/modules/mod/mod_core.dm
+++ b/code/modules/mod/mod_core.dm
@@ -399,7 +399,7 @@
SIGNAL_HANDLER
if(mod.active)
particle_effect = new(mod.wearer, /particles/pollen, PARTICLE_ATTACH_MOB)
- mob_spawner = mod.wearer.AddComponent(/datum/component/spawner, spawn_types=list(spawned_mob_type), spawn_time=5 SECONDS, max_spawned=3)
+ mob_spawner = mod.wearer.AddComponent(/datum/component/spawner, spawn_types=list(spawned_mob_type), spawn_time=5 SECONDS, max_spawned=3, faction=mod.wearer.faction)
RegisterSignal(mob_spawner, COMSIG_SPAWNER_SPAWNED, PROC_REF(new_mob))
RegisterSignal(mod.wearer, COMSIG_MOVABLE_MOVED, PROC_REF(spread_flowers))
diff --git a/code/modules/mod/modules/modules_ninja.dm b/code/modules/mod/modules/modules_ninja.dm
index 7a38238594d..31db6017609 100644
--- a/code/modules/mod/modules/modules_ninja.dm
+++ b/code/modules/mod/modules/modules_ninja.dm
@@ -24,7 +24,7 @@
return
if(bumpoff)
RegisterSignal(mod.wearer, COMSIG_LIVING_MOB_BUMP, PROC_REF(unstealth))
- RegisterSignal(mod.wearer, COMSIG_HUMAN_MELEE_UNARMED_ATTACK, PROC_REF(on_unarmed_attack))
+ RegisterSignal(mod.wearer, COMSIG_LIVING_UNARMED_ATTACK, PROC_REF(on_unarmed_attack))
RegisterSignal(mod.wearer, COMSIG_ATOM_BULLET_ACT, PROC_REF(on_bullet_act))
RegisterSignals(mod.wearer, list(COMSIG_MOB_ITEM_ATTACK, COMSIG_ATOM_ATTACKBY, COMSIG_ATOM_ATTACK_HAND, COMSIG_ATOM_HITBY, COMSIG_ATOM_HULK_ATTACK, COMSIG_ATOM_ATTACK_PAW, COMSIG_CARBON_CUFF_ATTEMPTED), PROC_REF(unstealth))
animate(mod.wearer, alpha = stealth_alpha, time = 1.5 SECONDS)
@@ -36,7 +36,7 @@
return
if(bumpoff)
UnregisterSignal(mod.wearer, COMSIG_LIVING_MOB_BUMP)
- UnregisterSignal(mod.wearer, list(COMSIG_HUMAN_MELEE_UNARMED_ATTACK, COMSIG_MOB_ITEM_ATTACK, COMSIG_ATOM_ATTACKBY, COMSIG_ATOM_ATTACK_HAND, COMSIG_ATOM_BULLET_ACT, COMSIG_ATOM_HITBY, COMSIG_ATOM_HULK_ATTACK, COMSIG_ATOM_ATTACK_PAW, COMSIG_CARBON_CUFF_ATTEMPTED))
+ UnregisterSignal(mod.wearer, list(COMSIG_LIVING_UNARMED_ATTACK, COMSIG_MOB_ITEM_ATTACK, COMSIG_ATOM_ATTACKBY, COMSIG_ATOM_ATTACK_HAND, COMSIG_ATOM_BULLET_ACT, COMSIG_ATOM_HITBY, COMSIG_ATOM_HULK_ATTACK, COMSIG_ATOM_ATTACK_PAW, COMSIG_CARBON_CUFF_ATTEMPTED))
animate(mod.wearer, alpha = 255, time = 1.5 SECONDS)
/obj/item/mod/module/stealth/proc/unstealth(datum/source)
@@ -143,10 +143,10 @@
var/door_hack_counter = 0
/obj/item/mod/module/hacker/on_suit_activation()
- RegisterSignal(mod.wearer, COMSIG_HUMAN_EARLY_UNARMED_ATTACK, PROC_REF(hack))
+ RegisterSignal(mod.wearer, COMSIG_LIVING_UNARMED_ATTACK, PROC_REF(hack))
/obj/item/mod/module/hacker/on_suit_deactivation(deleting = FALSE)
- UnregisterSignal(mod.wearer, COMSIG_HUMAN_EARLY_UNARMED_ATTACK)
+ UnregisterSignal(mod.wearer, COMSIG_LIVING_UNARMED_ATTACK)
/obj/item/mod/module/hacker/proc/hack(mob/living/carbon/human/source, atom/target, proximity, modifiers)
SIGNAL_HANDLER
diff --git a/code/modules/mod/modules/modules_security.dm b/code/modules/mod/modules/modules_security.dm
index 7e331433b4f..3ac431034b5 100644
--- a/code/modules/mod/modules/modules_security.dm
+++ b/code/modules/mod/modules/modules_security.dm
@@ -422,8 +422,10 @@
UnregisterSignal(creature, COMSIG_MOVABLE_MOVED)
return
- if(oldgroup != newgroup)
- sorted_creatures[oldgroup] -= creature
+ if(oldgroup == newgroup)
+ return
+
+ sorted_creatures[oldgroup] -= creature
sorted_creatures[newgroup] += creature
keyed_creatures[creature] = newgroup
diff --git a/code/modules/plumbing/plumbers/acclimator.dm b/code/modules/plumbing/plumbers/acclimator.dm
index 6b7a8caba4a..850ebaaa3ed 100644
--- a/code/modules/plumbing/plumbers/acclimator.dm
+++ b/code/modules/plumbing/plumbers/acclimator.dm
@@ -3,6 +3,9 @@
#define HEATING "Heating"
#define NEUTRAL "Neutral"
+///cool/heat power. converts temperature into joules
+#define HEATER_COEFFICIENT 0.05
+
///this the plumbing version of a heater/freezer.
/obj/machinery/plumbing/acclimator
name = "chemical acclimator"
@@ -17,8 +20,6 @@
var/target_temperature = 300
///I cant find a good name for this. Basically if target is 300, and this is 10, it will still target 300 but will start emptying itself at 290 and 310.
var/allowed_temperature_difference = 1
- ///cool/heat power
- var/heater_coefficient = 0.05
///Are we turned on or off? this is from the on and off button
var/enabled = TRUE
///COOLING, HEATING or NEUTRAL. We track this for change, so we dont needlessly update our icon
@@ -52,7 +53,7 @@
emptying = TRUE
if(!emptying) //suspend heating/cooling during emptying phase
- reagents.adjust_thermal_energy((target_temperature - reagents.chem_temp) * heater_coefficient * seconds_per_tick * SPECIFIC_HEAT_DEFAULT * reagents.total_volume) //keep constant with chem heater
+ reagents.adjust_thermal_energy((target_temperature - reagents.chem_temp) * HEATER_COEFFICIENT * seconds_per_tick * SPECIFIC_HEAT_DEFAULT * reagents.total_volume) //keep constant with chem heater
reagents.handle_reactions()
use_power(active_power_usage * seconds_per_tick)
else if(acclimate_state != NEUTRAL)
@@ -109,3 +110,4 @@
#undef COOLING
#undef HEATING
#undef NEUTRAL
+#undef HEATER_COEFFICIENT
diff --git a/code/modules/plumbing/plumbers/reaction_chamber.dm b/code/modules/plumbing/plumbers/reaction_chamber.dm
index 36320f18184..d41c502ecf9 100644
--- a/code/modules/plumbing/plumbers/reaction_chamber.dm
+++ b/code/modules/plumbing/plumbers/reaction_chamber.dm
@@ -53,9 +53,6 @@
var/power_usage = active_power_usage * 0.5
if(!emptying || reagents.is_reacting)
- //do reactions and stuff
- reagents.handle_reactions()
-
//adjust temperature of final solution
var/temp_diff = target_temperature - reagents.chem_temp
if(abs(temp_diff) > 0.01) //if we are not close enough keep going
@@ -174,9 +171,15 @@
/obj/machinery/plumbing/reaction_chamber/chem/handle_reagents(seconds_per_tick)
while(reagents.ph < acidic_limit || reagents.ph > alkaline_limit)
+ //no power
if(machine_stat & NOPOWER)
return
+ //nothing to react with
+ var/num_of_reagents = length(reagents.reagent_list)
+ if(!num_of_reagents)
+ return
+
/**
* figure out which buffer to transfer to restore balance
* if solution is getting too basic(high ph) add some acid to lower it's value
@@ -186,13 +189,14 @@
if(!buffer.total_volume)
return
- //transfer buffer and handle reactions, not a proven math but looks logical
- var/transfer_amount = FLOOR((reagents.ph > alkaline_limit ? (reagents.ph - alkaline_limit) : (acidic_limit - reagents.ph)) * seconds_per_tick, CHEMICAL_QUANTISATION_LEVEL)
- if(transfer_amount <= CHEMICAL_QUANTISATION_LEVEL || !buffer.trans_to(reagents, transfer_amount))
+ //transfer buffer and handle reactions
+ var/ph_change = (reagents.ph > alkaline_limit ? (reagents.ph - alkaline_limit) : (acidic_limit - reagents.ph))
+ var/buffer_amount = ((ph_change * reagents.total_volume) / (BUFFER_IONIZING_STRENGTH * num_of_reagents))
+ if(!buffer.trans_to(reagents, buffer_amount * seconds_per_tick))
return
//some power for accurate ph balancing
- use_power(active_power_usage * 0.2 * seconds_per_tick)
+ use_power(active_power_usage * 0.03 * buffer_amount * seconds_per_tick)
/obj/machinery/plumbing/reaction_chamber/chem/ui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
diff --git a/code/modules/plumbing/plumbers/synthesizer.dm b/code/modules/plumbing/plumbers/synthesizer.dm
index a4521dd9b2e..3dddd648e61 100644
--- a/code/modules/plumbing/plumbers/synthesizer.dm
+++ b/code/modules/plumbing/plumbers/synthesizer.dm
@@ -10,11 +10,11 @@
///Amount we produce for every process. Ideally keep under 5 since thats currently the standard duct capacity
var/amount = 1
///I track them here because I have no idea how I'd make tgui loop like that
- var/static/list/possible_amounts = list(0,1,2,3,4,5)
+ var/static/list/possible_amounts = list(0, 1, 2, 3, 4, 5)
///The reagent we are producing. We are a typepath, but are also typecast because there's several occations where we need to use initial.
var/datum/reagent/reagent_id = null
///straight up copied from chem dispenser. Being a subtype would be extremely tedious and making it global would restrict potential subtypes using different dispensable_reagents
- var/list/dispensable_reagents = list(
+ var/static/list/default_reagents = list(
/datum/reagent/aluminium,
/datum/reagent/bromine,
/datum/reagent/carbon,
@@ -41,10 +41,13 @@
/datum/reagent/water,
/datum/reagent/fuel,
)
+ //reagents this synthesizer can dispense
+ var/list/dispensable_reagents
/obj/machinery/plumbing/synthesizer/Initialize(mapload, bolt, layer)
. = ..()
AddComponent(/datum/component/plumbing/simple_supply, bolt, layer)
+ dispensable_reagents = default_reagents
/obj/machinery/plumbing/synthesizer/process(seconds_per_tick)
if(machine_stat & NOPOWER || !reagent_id || !amount)
@@ -114,7 +117,7 @@
icon_state = "synthesizer_soda"
//Copied from soda dispenser
- dispensable_reagents = list(
+ var/static/list/soda_reagents = list(
/datum/reagent/consumable/coffee,
/datum/reagent/consumable/space_cola,
/datum/reagent/consumable/cream,
@@ -140,6 +143,11 @@
/datum/reagent/water,
)
+/obj/machinery/plumbing/synthesizer/soda/Initialize(mapload, bolt, layer)
+ . = ..()
+
+ dispensable_reagents = soda_reagents
+
/obj/machinery/plumbing/synthesizer/beer
name = "beer synthesizer"
desc = "Produces a single chemical at a given volume. Must be plumbed."
@@ -147,7 +155,7 @@
icon_state = "synthesizer_booze"
//Copied from beer dispenser
- dispensable_reagents = list(
+ var/static/list/beer_reagents = list(
/datum/reagent/consumable/ethanol/absinthe,
/datum/reagent/consumable/ethanol/ale,
/datum/reagent/consumable/ethanol/applejack,
@@ -172,3 +180,7 @@
/datum/reagent/consumable/ethanol/wine,
)
+/obj/machinery/plumbing/synthesizer/beer/Initialize(mapload, bolt, layer)
+ . = ..()
+
+ dispensable_reagents = beer_reagents
diff --git a/code/modules/power/cell.dm b/code/modules/power/cell.dm
index dcc3369766f..eb2a5b4d1a2 100644
--- a/code/modules/power/cell.dm
+++ b/code/modules/power/cell.dm
@@ -23,7 +23,7 @@
///Current charge in cell units
var/charge = 0
///Maximum charge in cell units
- var/maxcharge = 1000
+ var/maxcharge = STANDARD_CELL_CHARGE
custom_materials = list(/datum/material/iron=SMALL_MATERIAL_AMOUNT*7, /datum/material/glass=SMALL_MATERIAL_AMOUNT*0.5)
grind_results = list(/datum/reagent/lithium = 15, /datum/reagent/iron = 5, /datum/reagent/silicon = 5)
///If the cell has been booby-trapped by injecting it with plasma. Chance on use() to explode.
@@ -31,7 +31,7 @@
///If the power cell was damaged by an explosion, chance for it to become corrupted and function the same as rigged.
var/corrupted = FALSE
///how much power is given every tick in a recharger
- var/chargerate = 100
+ var/chargerate = STANDARD_CELL_CHARGE * 0.1
///If true, the cell will state it's maximum charge in it's description
var/ratingdesc = TRUE
///If it's a grown that acts as a battery, add a wire overlay to it.
@@ -51,7 +51,7 @@
create_reagents(5, INJECTABLE | DRAINABLE)
if (override_maxcharge)
maxcharge = override_maxcharge
- rating = max(round(maxcharge / 10000, 1), 1)
+ rating = max(round(maxcharge / (STANDARD_CELL_CHARGE * 10), 1), 1)
if(!charge)
charge = maxcharge
if(empty)
@@ -82,7 +82,7 @@
. = COMPONENT_ITEM_CHARGED
if(prob(80))
- maxcharge -= 200
+ maxcharge -= STANDARD_CELL_CHARGE * 0.2
if(maxcharge <= 1) // Div by 0 protection
maxcharge = 1
@@ -289,7 +289,7 @@
/obj/item/stock_parts/cell/crap
name = "\improper Nanotrasen brand rechargeable AA battery"
desc = "You can't top the plasma top." //TOTALLY TRADEMARK INFRINGEMENT
- maxcharge = 500
+ maxcharge = STANDARD_CELL_CHARGE * 0.5
custom_materials = list(/datum/material/glass=SMALL_MATERIAL_AMOUNT*0.4)
/obj/item/stock_parts/cell/crap/empty
@@ -298,18 +298,18 @@
/obj/item/stock_parts/cell/upgraded
name = "upgraded power cell"
desc = "A power cell with a slightly higher capacity than normal!"
- maxcharge = 2500
+ maxcharge = STANDARD_CELL_CHARGE * 2.5
custom_materials = list(/datum/material/glass=SMALL_MATERIAL_AMOUNT*0.5)
- chargerate = 1000
+ chargerate = STANDARD_CELL_CHARGE
/obj/item/stock_parts/cell/upgraded/plus
name = "upgraded power cell+"
desc = "A power cell with an even higher capacity than the base model!"
- maxcharge = 5000
+ maxcharge = STANDARD_CELL_CHARGE * 5
/obj/item/stock_parts/cell/secborg
name = "security borg rechargeable D battery"
- maxcharge = 600 //600 max charge / 100 charge per shot = six shots
+ maxcharge = STANDARD_CELL_CHARGE * 0.6
custom_materials = list(/datum/material/glass=SMALL_MATERIAL_AMOUNT*0.4)
/obj/item/stock_parts/cell/secborg/empty
@@ -317,38 +317,38 @@
/obj/item/stock_parts/cell/mini_egun
name = "miniature energy gun power cell"
- maxcharge = 600
+ maxcharge = STANDARD_CELL_CHARGE * 0.6
/obj/item/stock_parts/cell/hos_gun
name = "X-01 multiphase energy gun power cell"
- maxcharge = 1200
+ maxcharge = STANDARD_CELL_CHARGE * 1.2
/obj/item/stock_parts/cell/pulse //200 pulse shots
name = "pulse rifle power cell"
- maxcharge = 40000
- chargerate = 1500
+ maxcharge = STANDARD_CELL_CHARGE * 40
+ chargerate = STANDARD_CELL_CHARGE * 1.5
/obj/item/stock_parts/cell/pulse/carbine //25 pulse shots
name = "pulse carbine power cell"
- maxcharge = 5000
+ maxcharge = STANDARD_CELL_CHARGE * 5
/obj/item/stock_parts/cell/pulse/pistol //10 pulse shots
name = "pulse pistol power cell"
- maxcharge = 2000
+ maxcharge = STANDARD_CELL_CHARGE * 2
/obj/item/stock_parts/cell/ninja
name = "black power cell"
icon_state = "bscell"
- maxcharge = 10000
+ maxcharge = STANDARD_CELL_CHARGE * 10
custom_materials = list(/datum/material/glass=SMALL_MATERIAL_AMOUNT*0.6)
- chargerate = 2000
+ chargerate = STANDARD_CELL_CHARGE * 2
/obj/item/stock_parts/cell/high
name = "high-capacity power cell"
icon_state = "hcell"
- maxcharge = 10000
+ maxcharge = STANDARD_CELL_CHARGE * 10
custom_materials = list(/datum/material/glass=SMALL_MATERIAL_AMOUNT*0.6)
- chargerate = 1500
+ chargerate = STANDARD_CELL_CHARGE * 1.5
/obj/item/stock_parts/cell/high/empty
empty = TRUE
@@ -356,9 +356,9 @@
/obj/item/stock_parts/cell/super
name = "super-capacity power cell"
icon_state = "scell"
- maxcharge = 20000
+ maxcharge = STANDARD_CELL_CHARGE * 20
custom_materials = list(/datum/material/glass=SMALL_MATERIAL_AMOUNT * 3)
- chargerate = 2000
+ chargerate = STANDARD_CELL_CHARGE * 2
/obj/item/stock_parts/cell/super/empty
empty = TRUE
@@ -366,9 +366,9 @@
/obj/item/stock_parts/cell/hyper
name = "hyper-capacity power cell"
icon_state = "hpcell"
- maxcharge = 30000
+ maxcharge = STANDARD_CELL_CHARGE * 30
custom_materials = list(/datum/material/glass=SMALL_MATERIAL_AMOUNT * 4)
- chargerate = 3000
+ chargerate = STANDARD_CELL_CHARGE * 3
/obj/item/stock_parts/cell/hyper/empty
empty = TRUE
@@ -377,9 +377,9 @@
name = "bluespace power cell"
desc = "A rechargeable transdimensional power cell."
icon_state = "bscell"
- maxcharge = 40000
+ maxcharge = STANDARD_CELL_CHARGE * 40
custom_materials = list(/datum/material/glass=SMALL_MATERIAL_AMOUNT*6)
- chargerate = 4000
+ chargerate = STANDARD_CELL_CHARGE * 4
/obj/item/stock_parts/cell/bluespace/empty
empty = TRUE
@@ -400,7 +400,7 @@
desc = "An alien power cell that produces energy seemingly out of nowhere."
icon = 'icons/obj/antags/abductor.dmi'
icon_state = "cell"
- maxcharge = 50000
+ maxcharge = STANDARD_CELL_CHARGE * 50
ratingdesc = FALSE
/obj/item/stock_parts/cell/infinite/abductor/Initialize(mapload)
@@ -413,7 +413,7 @@
icon = 'icons/obj/service/hydroponics/harvest.dmi'
icon_state = "potato"
charge = 100
- maxcharge = 300
+ maxcharge = STANDARD_CELL_CHARGE * 0.3
charge_light_type = null
connector_type = null
custom_materials = null
@@ -423,7 +423,7 @@
/obj/item/stock_parts/cell/emproof
name = "\improper EMP-proof cell"
desc = "An EMP-proof cell."
- maxcharge = 500
+ maxcharge = STANDARD_CELL_CHARGE * 0.5
/obj/item/stock_parts/cell/emproof/Initialize(mapload)
AddElement(/datum/element/empprotection, EMP_PROTECT_SELF)
@@ -441,15 +441,15 @@
icon = 'icons/mob/simple/slimes.dmi'
icon_state = "yellow slime extract"
custom_materials = null
- maxcharge = 5000
+ maxcharge = STANDARD_CELL_CHARGE * 5
charge_light_type = null
connector_type = "slimecore"
/obj/item/stock_parts/cell/beam_rifle
name = "beam rifle capacitor"
desc = "A high powered capacitor that can provide huge amounts of energy in an instant."
- maxcharge = 50000
- chargerate = 5000 //Extremely energy intensive
+ maxcharge = STANDARD_CELL_CHARGE * 50
+ chargerate = STANDARD_CELL_CHARGE * 5 //Extremely energy intensive
/obj/item/stock_parts/cell/beam_rifle/corrupt()
return
@@ -463,7 +463,7 @@
/obj/item/stock_parts/cell/emergency_light
name = "miniature power cell"
desc = "A tiny power cell with a very low power capacity. Used in light fixtures to power them in the event of an outage."
- maxcharge = 120 //Emergency lights use 0.2 W per tick, meaning ~10 minutes of emergency power from a cell
+ maxcharge = STANDARD_CELL_CHARGE * 0.12 //Emergency lights use 0.2 W per tick, meaning ~10 minutes of emergency power from a cell
custom_materials = list(/datum/material/glass = SMALL_MATERIAL_AMOUNT*0.2)
w_class = WEIGHT_CLASS_TINY
@@ -478,7 +478,7 @@
name = "crystal power cell"
desc = "A very high power cell made from crystallized plasma"
icon_state = "crystal_cell"
- maxcharge = 50000
+ maxcharge = STANDARD_CELL_CHARGE * 50
chargerate = 0
charge_light_type = null
connector_type = "crystal"
@@ -486,7 +486,7 @@
grind_results = null
/obj/item/stock_parts/cell/inducer_supply
- maxcharge = 5000
+ maxcharge = STANDARD_CELL_CHARGE * 5
#undef CELL_DRAIN_TIME
#undef CELL_POWER_GAIN
diff --git a/code/modules/projectiles/ammunition/energy/_energy.dm b/code/modules/projectiles/ammunition/energy/_energy.dm
index 808cbdfbfe7..11c8707a5e7 100644
--- a/code/modules/projectiles/ammunition/energy/_energy.dm
+++ b/code/modules/projectiles/ammunition/energy/_energy.dm
@@ -4,7 +4,7 @@
caliber = ENERGY
projectile_type = /obj/projectile/energy
slot_flags = null
- var/e_cost = 100 //The amount of energy a cell needs to expend to create this shot.
+ var/e_cost = LASER_SHOTS(10, STANDARD_CELL_CHARGE) //The amount of energy a cell needs to expend to create this shot.
var/select_name = CALIBER_ENERGY
fire_sound = 'sound/weapons/laser.ogg'
firing_effect_type = /obj/effect/temp_visual/dir_setting/firing_effect/energy
diff --git a/code/modules/projectiles/ammunition/energy/ebow.dm b/code/modules/projectiles/ammunition/energy/ebow.dm
index 0eee10a58e5..535ea126576 100644
--- a/code/modules/projectiles/ammunition/energy/ebow.dm
+++ b/code/modules/projectiles/ammunition/energy/ebow.dm
@@ -1,7 +1,7 @@
/obj/item/ammo_casing/energy/bolt
projectile_type = /obj/projectile/energy/bolt
select_name = "bolt"
- e_cost = 500
+ e_cost = LASER_SHOTS(1, STANDARD_CELL_CHARGE * 0.5)
fire_sound = 'sound/weapons/gun/general/heavy_shot_suppressed.ogg' // Even for non-suppressed crossbows, this is the most appropriate sound
/obj/item/ammo_casing/energy/bolt/halloween
diff --git a/code/modules/projectiles/ammunition/energy/gravity.dm b/code/modules/projectiles/ammunition/energy/gravity.dm
index 5b781189c52..fe73ad26883 100644
--- a/code/modules/projectiles/ammunition/energy/gravity.dm
+++ b/code/modules/projectiles/ammunition/energy/gravity.dm
@@ -1,5 +1,5 @@
/obj/item/ammo_casing/energy/gravity
- e_cost = 0
+ e_cost = 0 // Not possible to use the macro
fire_sound = 'sound/weapons/wave.ogg'
select_name = "gravity"
delay = 50
diff --git a/code/modules/projectiles/ammunition/energy/laser.dm b/code/modules/projectiles/ammunition/energy/laser.dm
index e766f42be0f..c47181688a4 100644
--- a/code/modules/projectiles/ammunition/energy/laser.dm
+++ b/code/modules/projectiles/ammunition/energy/laser.dm
@@ -1,24 +1,21 @@
/obj/item/ammo_casing/energy/laser
projectile_type = /obj/projectile/beam/laser
- e_cost = 83
+ e_cost = LASER_SHOTS(12, STANDARD_CELL_CHARGE)
select_name = "kill"
/obj/item/ammo_casing/energy/laser/hellfire
projectile_type = /obj/projectile/beam/laser/hellfire
- e_cost = 100
+ e_cost = LASER_SHOTS(10, STANDARD_CELL_CHARGE)
select_name = "maim"
-/obj/item/ammo_casing/energy/laser/hellfire/antique
- e_cost = 100
-
/obj/item/ammo_casing/energy/lasergun
projectile_type = /obj/projectile/beam/laser
- e_cost = 62.5
+ e_cost = LASER_SHOTS(16, STANDARD_CELL_CHARGE)
select_name = "kill"
/obj/item/ammo_casing/energy/lasergun/carbine
projectile_type = /obj/projectile/beam/laser/carbine
- e_cost = 25 // 40 shots
+ e_cost = LASER_SHOTS(40, STANDARD_CELL_CHARGE)
select_name = "kill"
fire_sound = 'sound/weapons/laser2.ogg'
@@ -29,15 +26,15 @@
/obj/item/ammo_casing/energy/lasergun/old
projectile_type = /obj/projectile/beam/laser
- e_cost = 200
+ e_cost = LASER_SHOTS(5, STANDARD_CELL_CHARGE)
select_name = "kill"
/obj/item/ammo_casing/energy/laser/hos
- e_cost = 120
+ e_cost = LASER_SHOTS(10, STANDARD_CELL_CHARGE * 1.2)
/obj/item/ammo_casing/energy/laser/musket
projectile_type = /obj/projectile/beam/laser/musket
- e_cost = 1000
+ e_cost = LASER_SHOTS(1, STANDARD_CELL_CHARGE)
/obj/item/ammo_casing/energy/laser/musket/prime
projectile_type = /obj/projectile/beam/laser/musket/prime
@@ -49,7 +46,7 @@
/obj/item/ammo_casing/energy/chameleon
projectile_type = /obj/projectile/energy/chameleon
- e_cost = 0
+ e_cost = 0 // Can't really use the macro here, unfortunately
var/projectile_vars = list()
/obj/item/ammo_casing/energy/chameleon/ready_proj()
@@ -89,7 +86,7 @@
/obj/item/ammo_casing/energy/laser/pulse
projectile_type = /obj/projectile/beam/pulse
- e_cost = 200
+ e_cost = LASER_SHOTS(200, STANDARD_CELL_CHARGE * 40)
select_name = "DESTROY"
fire_sound = 'sound/weapons/pulse.ogg'
@@ -111,7 +108,7 @@
/obj/item/ammo_casing/energy/xray
projectile_type = /obj/projectile/beam/xray
- e_cost = 50
+ e_cost = LASER_SHOTS(20, STANDARD_CELL_CHARGE)
fire_sound = 'sound/weapons/laser3.ogg'
/obj/item/ammo_casing/energy/mindflayer
@@ -127,7 +124,7 @@
/obj/item/ammo_casing/energy/nanite
projectile_type = /obj/projectile/bullet/c10mm //henk
select_name = "bullet"
- e_cost = 120
+ e_cost = LASER_SHOTS(8, STANDARD_CELL_CHARGE)
fire_sound = 'sound/weapons/thermalpistol.ogg'
/obj/item/ammo_casing/energy/nanite/inferno
diff --git a/code/modules/projectiles/ammunition/energy/lmg.dm b/code/modules/projectiles/ammunition/energy/lmg.dm
index fbd5916613e..632044f0652 100644
--- a/code/modules/projectiles/ammunition/energy/lmg.dm
+++ b/code/modules/projectiles/ammunition/energy/lmg.dm
@@ -2,5 +2,5 @@
projectile_type = /obj/projectile/bullet/c3d
select_name = "spraydown"
fire_sound = 'sound/weapons/gun/smg/shot.ogg'
- e_cost = 20
+ e_cost = LASER_SHOTS(30, STANDARD_CELL_CHARGE * 0.6)
firing_effect_type = /obj/effect/temp_visual/dir_setting/firing_effect
diff --git a/code/modules/projectiles/ammunition/energy/plasma.dm b/code/modules/projectiles/ammunition/energy/plasma.dm
index 00de4a90ffe..e660903bdc9 100644
--- a/code/modules/projectiles/ammunition/energy/plasma.dm
+++ b/code/modules/projectiles/ammunition/energy/plasma.dm
@@ -3,9 +3,9 @@
select_name = "plasma burst"
fire_sound = 'sound/weapons/plasma_cutter.ogg'
delay = 15
- e_cost = 25
+ e_cost = LASER_SHOTS(40, STANDARD_CELL_CHARGE)
/obj/item/ammo_casing/energy/plasma/adv
projectile_type = /obj/projectile/plasma/adv
delay = 10
- e_cost = 10
+ e_cost = LASER_SHOTS(100, STANDARD_CELL_CHARGE)
diff --git a/code/modules/projectiles/ammunition/energy/portal.dm b/code/modules/projectiles/ammunition/energy/portal.dm
index 8bdd697f1bf..0ef63491f11 100644
--- a/code/modules/projectiles/ammunition/energy/portal.dm
+++ b/code/modules/projectiles/ammunition/energy/portal.dm
@@ -1,6 +1,6 @@
/obj/item/ammo_casing/energy/wormhole
projectile_type = /obj/projectile/beam/wormhole
- e_cost = 0
+ e_cost = 0 // Can't use the macro
harmful = FALSE
fire_sound = 'sound/weapons/pulse3.ogg'
select_name = "blue"
diff --git a/code/modules/projectiles/ammunition/energy/special.dm b/code/modules/projectiles/ammunition/energy/special.dm
index 24fba4b9ba4..f2fc274ee8a 100644
--- a/code/modules/projectiles/ammunition/energy/special.dm
+++ b/code/modules/projectiles/ammunition/energy/special.dm
@@ -5,7 +5,7 @@
/obj/item/ammo_casing/energy/ion/hos
projectile_type = /obj/projectile/ion/weak
- e_cost = 300
+ e_cost = LASER_SHOTS(4, STANDARD_CELL_CHARGE * 1.2)
/obj/item/ammo_casing/energy/declone
projectile_type = /obj/projectile/energy/declone
@@ -30,12 +30,12 @@
/obj/item/ammo_casing/energy/flora/revolution
projectile_type = /obj/projectile/energy/florarevolution
select_name = "revolution"
- e_cost = 250
+ e_cost = LASER_SHOTS(4, STANDARD_CELL_CHARGE)
/obj/item/ammo_casing/energy/temp
projectile_type = /obj/projectile/temp
select_name = "freeze"
- e_cost = 250
+ e_cost = LASER_SHOTS(40, STANDARD_CELL_CHARGE * 10)
fire_sound = 'sound/weapons/pulse3.ogg'
/obj/item/ammo_casing/energy/temp/hot
@@ -60,23 +60,23 @@
/obj/item/ammo_casing/energy/tesla_cannon
fire_sound = 'sound/magic/lightningshock.ogg'
- e_cost = 30
+ e_cost = LASER_SHOTS(33, STANDARD_CELL_CHARGE)
select_name = "shock"
projectile_type = /obj/projectile/energy/tesla_cannon
/obj/item/ammo_casing/energy/shrink
projectile_type = /obj/projectile/beam/shrink
select_name = "shrink ray"
- e_cost = 200
+ e_cost = LASER_SHOTS(5, STANDARD_CELL_CHARGE)
/obj/item/ammo_casing/energy/marksman
projectile_type = /obj/projectile/bullet/marksman
select_name = "marksman nanoshot"
- e_cost = 0
+ e_cost = 0 // Can't use the macro
fire_sound = 'sound/weapons/gun/revolver/shot_alt.ogg'
/obj/item/ammo_casing/energy/fisher
projectile_type = /obj/projectile/energy/fisher
select_name = "light-buster"
- e_cost = 250
+ e_cost = LASER_SHOTS(2, STANDARD_CELL_CHARGE * 0.5)
fire_sound = 'sound/weapons/gun/general/heavy_shot_suppressed.ogg' // fwip fwip fwip fwip
diff --git a/code/modules/projectiles/ammunition/energy/stun.dm b/code/modules/projectiles/ammunition/energy/stun.dm
index 630233e5d73..ee2f9fa17ee 100644
--- a/code/modules/projectiles/ammunition/energy/stun.dm
+++ b/code/modules/projectiles/ammunition/energy/stun.dm
@@ -2,38 +2,33 @@
projectile_type = /obj/projectile/energy/electrode
select_name = "stun"
fire_sound = 'sound/weapons/taser.ogg'
- e_cost = 200
+ e_cost = LASER_SHOTS(5, STANDARD_CELL_CHARGE)
harmful = FALSE
/obj/item/ammo_casing/energy/electrode/spec
- e_cost = 100
+ e_cost = LASER_SHOTS(10, STANDARD_CELL_CHARGE)
/obj/item/ammo_casing/energy/electrode/gun
fire_sound = 'sound/weapons/gun/pistol/shot.ogg'
- e_cost = 100
+ e_cost = LASER_SHOTS(10, STANDARD_CELL_CHARGE)
/obj/item/ammo_casing/energy/electrode/old
- e_cost = 1000
+ e_cost = LASER_SHOTS(1, STANDARD_CELL_CHARGE)
/obj/item/ammo_casing/energy/disabler
projectile_type = /obj/projectile/beam/disabler
select_name = "disable"
- e_cost = 50
+ e_cost = LASER_SHOTS(20, STANDARD_CELL_CHARGE)
fire_sound = 'sound/weapons/taser2.ogg'
harmful = FALSE
/obj/item/ammo_casing/energy/disabler/hos
- e_cost = 60
+ e_cost = LASER_SHOTS(20, STANDARD_CELL_CHARGE * 1.2)
/obj/item/ammo_casing/energy/disabler/smoothbore
projectile_type = /obj/projectile/beam/disabler/smoothbore
- e_cost = 1000
+ e_cost = LASER_SHOTS(1, STANDARD_CELL_CHARGE)
/obj/item/ammo_casing/energy/disabler/smoothbore/prime
projectile_type = /obj/projectile/beam/disabler/smoothbore/prime
- e_cost = 500
-
-// SKYRAT ADDITION START
-/obj/item/ammo_casing/energy/disabler/skyrat
- e_cost = 1000
-// SKYRAT ADDITION END
+ e_cost = LASER_SHOTS(2, STANDARD_CELL_CHARGE)
diff --git a/code/modules/projectiles/guns/energy/beam_rifle.dm b/code/modules/projectiles/guns/energy/beam_rifle.dm
index 8869da14a59..a451b14de9a 100644
--- a/code/modules/projectiles/guns/energy/beam_rifle.dm
+++ b/code/modules/projectiles/guns/energy/beam_rifle.dm
@@ -433,7 +433,7 @@
/obj/item/ammo_casing/energy/beam_rifle/hitscan
projectile_type = /obj/projectile/beam/beam_rifle/hitscan
select_name = "beam"
- e_cost = 10000
+ e_cost = LASER_SHOTS(5, 50000) // Beam rifle has a custom cell
fire_sound = 'sound/weapons/beam_sniper.ogg'
/obj/projectile/beam/beam_rifle
diff --git a/code/modules/projectiles/guns/energy/dueling.dm b/code/modules/projectiles/guns/energy/dueling.dm
index a766ba5667f..7b3929d7574 100644
--- a/code/modules/projectiles/guns/energy/dueling.dm
+++ b/code/modules/projectiles/guns/energy/dueling.dm
@@ -292,7 +292,7 @@
//Casing
/obj/item/ammo_casing/energy/duel
- e_cost = 0
+ e_cost = 0 // Can't use the macro
projectile_type = /obj/projectile/energy/duel
var/setting
diff --git a/code/modules/projectiles/guns/energy/kinetic_accelerator.dm b/code/modules/projectiles/guns/energy/kinetic_accelerator.dm
index 06e82a37aae..444b18ad274 100644
--- a/code/modules/projectiles/guns/energy/kinetic_accelerator.dm
+++ b/code/modules/projectiles/guns/energy/kinetic_accelerator.dm
@@ -165,7 +165,7 @@
/obj/item/ammo_casing/energy/kinetic
projectile_type = /obj/projectile/kinetic
select_name = "kinetic"
- e_cost = 500
+ e_cost = LASER_SHOTS(1, STANDARD_CELL_CHARGE * 0.5)
fire_sound = 'sound/weapons/kenetic_accel.ogg' // fine spelling there chap
/obj/item/ammo_casing/energy/kinetic/ready_proj(atom/target, mob/living/user, quiet, zone_override = "")
diff --git a/code/modules/projectiles/guns/energy/laser.dm b/code/modules/projectiles/guns/energy/laser.dm
index 94d58bb5e32..7e07a440de7 100644
--- a/code/modules/projectiles/guns/energy/laser.dm
+++ b/code/modules/projectiles/guns/energy/laser.dm
@@ -82,7 +82,7 @@
selfcharge = 1
resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ACID_PROOF
flags_1 = PREVENT_CONTENTS_EXPLOSION_1
- ammo_type = list(/obj/item/ammo_casing/energy/laser/hellfire/antique)
+ ammo_type = list(/obj/item/ammo_casing/energy/laser/hellfire)
/obj/item/gun/energy/laser/captain/scattershot
name = "scatter shot laser rifle"
diff --git a/code/modules/reagents/chemistry/holder.dm b/code/modules/reagents/chemistry/holder.dm
index c72bfbb6c3e..b210bde63a2 100644
--- a/code/modules/reagents/chemistry/holder.dm
+++ b/code/modules/reagents/chemistry/holder.dm
@@ -1361,7 +1361,7 @@
if(!.)
ph = CHEMICAL_NORMAL_PH
else
- ph = clamp(total_ph / total_volume, 0, 14)
+ ph = clamp(total_ph / total_volume, CHEMICAL_MIN_PH, CHEMICAL_MAX_PH)
//now send the signals after the volume & ph has been computed
for(var/datum/reagent/deleted_reagent as anything in deleted_reagents)
@@ -1626,9 +1626,9 @@
* Arguments:
* * value - How much to adjust the base pH by
*/
-/datum/reagents/proc/adjust_all_reagents_ph(value, lower_limit = 0, upper_limit = 14)
+/datum/reagents/proc/adjust_all_reagents_ph(value)
for(var/datum/reagent/reagent as anything in reagent_list)
- reagent.ph = clamp(reagent.ph + value, lower_limit, upper_limit)
+ reagent.ph = clamp(reagent.ph + value, CHEMICAL_MIN_PH, CHEMICAL_MAX_PH)
/*
* Adjusts the base pH of a specific type
@@ -1638,14 +1638,12 @@
* Arguments:
* * input_reagent - type path of the reagent
* * value - How much to adjust the base pH by
-* * lower_limit - how low the pH can go
-* * upper_limit - how high the pH can go
*/
-/datum/reagents/proc/adjust_specific_reagent_ph(input_reagent, value, lower_limit = 0, upper_limit = 14)
+/datum/reagents/proc/adjust_specific_reagent_ph(input_reagent, value)
var/datum/reagent/reagent = get_reagent(input_reagent)
if(!reagent) //We can call this with missing reagents.
return FALSE
- reagent.ph = clamp(reagent.ph + value, lower_limit, upper_limit)
+ reagent.ph = clamp(reagent.ph + value, CHEMICAL_MIN_PH, CHEMICAL_MAX_PH)
/**
* Outputs a log-friendly list of reagents based on an external reagent list.
diff --git a/code/modules/reagents/chemistry/reagents/reaction_agents_reagents.dm b/code/modules/reagents/chemistry/reagents/reaction_agents_reagents.dm
index c1da8b7424f..d6c4f0009b4 100644
--- a/code/modules/reagents/chemistry/reagents/reaction_agents_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/reaction_agents_reagents.dm
@@ -24,22 +24,23 @@
inverse_chem = null
fallback_icon = 'icons/obj/drinks/drink_effects.dmi'
fallback_icon_state = "acid_buffer_fallback"
- ///The strength of the buffer where (volume/holder.total_volume)*strength. So for 1u added to 50u the ph will decrease by 0.4
- var/strength = 30
//Consumes self on addition and shifts ph
/datum/reagent/reaction_agent/acidic_buffer/intercept_reagents_transfer(datum/reagents/target, amount)
. = ..()
if(!.)
return
+
+ //do the ph change
+ var/message
if(target.ph <= ph)
- target.my_atom.audible_message(span_warning("The beaker froths as the buffer is added, to no effect."))
- playsound(target.my_atom, 'sound/chemistry/bufferadd.ogg', 50, TRUE)
- holder.remove_reagent(type, amount)//Remove from holder because it's not transferred
- return
- var/ph_change = -((amount/target.total_volume)*strength)
- target.adjust_all_reagents_ph(ph_change, ph, 14)
- target.my_atom.audible_message(span_warning("The beaker fizzes as the ph changes!"))
+ message = "The beaker froths as the buffer is added, to no effect."
+ else
+ message = "The beaker froths as the pH changes!"
+ target.adjust_all_reagents_ph((-(amount / target.total_volume) * BUFFER_IONIZING_STRENGTH))
+
+ //give feedback & remove from holder because it's not transferred
+ target.my_atom.audible_message(span_warning(message))
playsound(target.my_atom, 'sound/chemistry/bufferadd.ogg', 50, TRUE)
holder.remove_reagent(type, amount)
@@ -51,21 +52,22 @@
inverse_chem = null
fallback_icon = 'icons/obj/drinks/drink_effects.dmi'
fallback_icon_state = "base_buffer_fallback"
- ///The strength of the buffer where (volume/holder.total_volume)*strength. So for 1u added to 50u the ph will increase by 0.4
- var/strength = 30
/datum/reagent/reaction_agent/basic_buffer/intercept_reagents_transfer(datum/reagents/target, amount)
. = ..()
if(!.)
return
+
+ //do the ph change
+ var/message
if(target.ph >= ph)
- target.my_atom.audible_message(span_warning("The beaker froths as the buffer is added, to no effect."))
- playsound(target.my_atom, 'sound/chemistry/bufferadd.ogg', 50, TRUE)
- holder.remove_reagent(type, amount)//Remove from holder because it's not transferred
- return
- var/ph_change = (amount/target.total_volume)*strength
- target.adjust_all_reagents_ph(ph_change, 0, ph)
- target.my_atom.audible_message(span_warning("The beaker froths as the ph changes!"))
+ message = "The beaker froths as the buffer is added, to no effect."
+ else
+ message = "The beaker froths as the pH changes!"
+ target.adjust_all_reagents_ph(((amount / target.total_volume) * BUFFER_IONIZING_STRENGTH))
+
+ //give feedback & remove from holder because it's not transferred
+ target.my_atom.audible_message(span_warning(message))
playsound(target.my_atom, 'sound/chemistry/bufferadd.ogg', 50, TRUE)
holder.remove_reagent(type, amount)
@@ -103,14 +105,14 @@
target.my_atom.audible_message(span_warning("The added reagent doesn't seem to do much."))
holder.remove_reagent(type, amount)
+///How much the reaction speed is sped up by - for 5u added to 100u, an additional step of 1 will be done up to a max of 2x
+#define SPEED_REAGENT_STRENGTH 20
+
/datum/reagent/reaction_agent/speed_agent
name = "Tempomyocin"
description = "This reagent will consume itself and speed up an ongoing reaction, modifying the current reaction's purity by it's own."
ph = 10
color = "#e61f82"
- ///How much the reaction speed is sped up by - for 5u added to 100u, an additional step of 1 will be done up to a max of 2x
- var/strength = 20
-
/datum/reagent/reaction_agent/speed_agent/intercept_reagents_transfer(datum/reagents/target, amount)
. = ..()
@@ -123,8 +125,10 @@
var/datum/equilibrium/reaction = _reaction
if(!reaction)
CRASH("[_reaction] is in the reaction list, but is not an equilibrium")
- var/power = (amount/reaction.target_vol)*strength
+ var/power = (amount / reaction.target_vol) * SPEED_REAGENT_STRENGTH
power *= creation_purity
power = clamp(power, 0, 2)
reaction.react_timestep(power, creation_purity)
holder.remove_reagent(type, amount)
+
+#undef SPEED_REAGENT_STRENGTH
diff --git a/code/modules/reagents/reagent_dispenser.dm b/code/modules/reagents/reagent_dispenser.dm
index ef48a50af21..8241331d7b0 100644
--- a/code/modules/reagents/reagent_dispenser.dm
+++ b/code/modules/reagents/reagent_dispenser.dm
@@ -181,7 +181,7 @@
// It did not account for how much fuel was actually in the tank at all, just the size of the tank.
// I encourage others to better scale these numbers in the future.
// As it stands this is a minor nerf in exchange for an easy bombing technique working that has been broken for a while.
- switch(volatiles.volume)
+ switch(fuel_amt)
if(25 to 150)
explosion(src, light_impact_range = 1, flame_range = 2)
if(150 to 300)
diff --git a/code/modules/research/server.dm b/code/modules/research/server.dm
index 45a0a520fa0..56be6864b54 100644
--- a/code/modules/research/server.dm
+++ b/code/modules/research/server.dm
@@ -154,7 +154,7 @@
if(HDD_OVERLOADED)
. += "The front panel is dangling open. The hdd inside is destroyed and the wires are all burned."
-/obj/machinery/rnd/server/master/tool_act(mob/living/user, obj/item/tool, tool_type)
+/obj/machinery/rnd/server/master/tool_act(mob/living/user, obj/item/tool, tool_type, is_right_clicking)
// Only antags are given the training and knowledge to disassemble this thing.
if(is_special_character(user))
return ..()
diff --git a/code/modules/surgery/bodyparts/_bodyparts.dm b/code/modules/surgery/bodyparts/_bodyparts.dm
index f033c081ead..407ac67a43a 100644
--- a/code/modules/surgery/bodyparts/_bodyparts.dm
+++ b/code/modules/surgery/bodyparts/_bodyparts.dm
@@ -969,8 +969,8 @@
icon_state = initial(icon_state)//no overlays found, we default back to initial icon.
return
for(var/image/img as anything in standing)
- img.pixel_x = px_x
- img.pixel_y = px_y
+ img.pixel_x += px_x
+ img.pixel_y += px_y
add_overlay(standing)
///Generates an /image for the limb to be used as an overlay
diff --git a/code/modules/surgery/bodyparts/head_hair_and_lips.dm b/code/modules/surgery/bodyparts/head_hair_and_lips.dm
index 607c8ad73c9..2370d3dc184 100644
--- a/code/modules/surgery/bodyparts/head_hair_and_lips.dm
+++ b/code/modules/surgery/bodyparts/head_hair_and_lips.dm
@@ -119,11 +119,12 @@
var/image/hair_overlay
if(!(show_debrained && (head_flags & HEAD_DEBRAIN)) && !hair_hidden && hairstyle && (head_flags & HEAD_HAIR))
- sprite_accessory = GLOB.hairstyles_list[hairstyle]
- if(sprite_accessory)
+ var/datum/sprite_accessory/hair/hair_sprite_accessory = GLOB.hairstyles_list[hairstyle]
+ if(hair_sprite_accessory)
//Overlay
- hair_overlay = image(sprite_accessory.icon, sprite_accessory.icon_state, -HAIR_LAYER, image_dir)
+ hair_overlay = image(hair_sprite_accessory.icon, hair_sprite_accessory.icon_state, -HAIR_LAYER, image_dir)
hair_overlay.alpha = hair_alpha
+ hair_overlay.pixel_y = hair_sprite_accessory.y_offset
//Emissive blocker
if(blocks_emissive != EMISSIVE_BLOCK_NONE)
hair_overlay.overlays += emissive_blocker(hair_overlay.icon, hair_overlay.icon_state, location, alpha = hair_alpha)
@@ -139,12 +140,13 @@
var/hair_gradient_style = LAZYACCESS(gradient_styles, GRADIENT_HAIR_KEY)
if(hair_gradient_style)
var/hair_gradient_color = LAZYACCESS(gradient_colors, GRADIENT_HAIR_KEY)
- var/image/hair_gradient_overlay = get_gradient_overlay(sprite_accessory.icon, sprite_accessory.icon_state, -HAIR_LAYER, GLOB.hair_gradients_list[hair_gradient_style], hair_gradient_color)
+ var/image/hair_gradient_overlay = get_gradient_overlay(hair_sprite_accessory.icon, hair_sprite_accessory.icon_state, -HAIR_LAYER, GLOB.hair_gradients_list[hair_gradient_style], hair_gradient_color)
// SKYRAT ADD - Hair offset
if(LAZYFIND(owner?.dna?.species?.offset_features, OFFSET_HAIR))
hair_gradient_overlay.pixel_x = owner.dna.species.offset_features[OFFSET_HAIR][INDEX_X]
hair_gradient_overlay.pixel_y = owner.dna.species.offset_features[OFFSET_HAIR][INDEX_Y]
// SKYRAT ADD END
+
. += hair_gradient_overlay
if(show_debrained && (head_flags & HEAD_DEBRAIN))
diff --git a/code/modules/surgery/bodyparts/parts.dm b/code/modules/surgery/bodyparts/parts.dm
index 4207f3541f6..7eee48f9a79 100644
--- a/code/modules/surgery/bodyparts/parts.dm
+++ b/code/modules/surgery/bodyparts/parts.dm
@@ -113,6 +113,8 @@
var/datum/worn_feature_offset/worn_glove_offset
/// Datum describing how to offset things held in the hands of this arm, the x offset IS functional here
var/datum/worn_feature_offset/held_hand_offset
+ /// The noun to use when referring to this arm's appendage, e.g. "hand" or "paw"
+ var/appendage_noun = "hand"
biological_state = BIO_STANDARD_JOINTED
@@ -211,6 +213,7 @@
unarmed_damage_low = 1 /// monkey punches must be really weak, considering they bite people instead and their bites are weak as hell.
unarmed_damage_high = 2
unarmed_stun_threshold = 3
+ appendage_noun = "paw"
/obj/item/bodypart/arm/left/alien
icon = 'icons/mob/human/species/alien/bodyparts.dmi'
@@ -224,6 +227,7 @@
can_be_disabled = FALSE
max_damage = 100
should_draw_greyscale = FALSE
+ appendage_noun = "scythe-like hand"
/obj/item/bodypart/arm/right
@@ -314,6 +318,7 @@
unarmed_damage_low = 1
unarmed_damage_high = 2
unarmed_stun_threshold = 3
+ appendage_noun = "paw"
/obj/item/bodypart/arm/right/alien
icon = 'icons/mob/human/species/alien/bodyparts.dmi'
@@ -327,6 +332,7 @@
can_be_disabled = FALSE
max_damage = 100
should_draw_greyscale = FALSE
+ appendage_noun = "scythe-like hand"
/// Parent Type for legs, should not appear in game.
/obj/item/bodypart/leg
diff --git a/code/modules/surgery/bodyparts/robot_bodyparts.dm b/code/modules/surgery/bodyparts/robot_bodyparts.dm
index 99591daaa4b..d967f2da9ec 100644
--- a/code/modules/surgery/bodyparts/robot_bodyparts.dm
+++ b/code/modules/surgery/bodyparts/robot_bodyparts.dm
@@ -235,8 +235,50 @@
/obj/item/bodypart/chest/robot/Destroy()
QDEL_NULL(cell)
+ UnregisterSignal(src, COMSIG_BODYPART_ATTACHED)
return ..()
+/obj/item/bodypart/chest/robot/Initialize(mapload)
+ . = ..()
+ RegisterSignal(src, COMSIG_BODYPART_ATTACHED, PROC_REF(on_attached))
+ RegisterSignal(src, COMSIG_BODYPART_REMOVED, PROC_REF(on_detached))
+
+/obj/item/bodypart/chest/robot/proc/on_attached(obj/item/bodypart/chest/robot/this_bodypart, mob/living/carbon/human/new_owner)
+ SIGNAL_HANDLER
+
+ RegisterSignals(new_owner, list(COMSIG_CARBON_POST_ATTACH_LIMB, COMSIG_CARBON_POST_REMOVE_LIMB), PROC_REF(check_limbs))
+
+/obj/item/bodypart/chest/robot/proc/on_detached(obj/item/bodypart/chest/robot/this_bodypart, mob/living/carbon/human/old_owner)
+ SIGNAL_HANDLER
+
+ UnregisterSignal(old_owner, list(COMSIG_CARBON_POST_ATTACH_LIMB, COMSIG_CARBON_POST_REMOVE_LIMB))
+
+/obj/item/bodypart/chest/robot/proc/check_limbs()
+ SIGNAL_HANDLER
+
+ var/all_robotic = TRUE
+ for(var/obj/item/bodypart/part in owner.bodyparts)
+ all_robotic = all_robotic && IS_ROBOTIC_LIMB(part)
+
+ if(all_robotic)
+ owner.add_traits(list(
+ /* SKYRAT EDIT REMOVAL BEGIN - Synths are not immune to temperature
+ TRAIT_RESISTCOLD,
+ TRAIT_RESISTHEAT,
+ SKYRAT EDIT REMOVAL END */
+ TRAIT_RESISTLOWPRESSURE,
+ TRAIT_RESISTHIGHPRESSURE,
+ ), AUGMENTATION_TRAIT)
+ else
+ owner.remove_traits(list(
+ /* SKYRAT EDIT REMOVAL BEGIN - Synths are not immune to temperature
+ TRAIT_RESISTCOLD,
+ TRAIT_RESISTHEAT,
+ SKYRAT EDIT REMOVAL END */
+ TRAIT_RESISTLOWPRESSURE,
+ TRAIT_RESISTHIGHPRESSURE,
+ ), AUGMENTATION_TRAIT)
+
/obj/item/bodypart/chest/robot/attackby(obj/item/weapon, mob/user, params)
if(istype(weapon, /obj/item/stock_parts/cell))
if(cell)
diff --git a/code/modules/surgery/organ_manipulation.dm b/code/modules/surgery/organ_manipulation.dm
index 17a8e147fb3..25004d20d20 100644
--- a/code/modules/surgery/organ_manipulation.dm
+++ b/code/modules/surgery/organ_manipulation.dm
@@ -87,7 +87,7 @@
var/obj/item/tool = user.get_active_held_item()
if(step.try_op(user, target, user.zone_selected, tool, src, try_to_fail))
return TRUE
- if(tool && tool.item_flags) //Mechanic organ manipulation isn't done with just surgery tools
+ if(tool && tool.tool_behaviour) //Mechanic organ manipulation isn't done with just surgery tools
to_chat(user, span_warning("This step requires a different tool!"))
return TRUE
diff --git a/code/modules/surgery/organs/internal/cyberimp/augments_arms.dm b/code/modules/surgery/organs/internal/cyberimp/augments_arms.dm
index 86cdf855134..142cf8d5143 100644
--- a/code/modules/surgery/organs/internal/cyberimp/augments_arms.dm
+++ b/code/modules/surgery/organs/internal/cyberimp/augments_arms.dm
@@ -381,11 +381,11 @@
/obj/item/organ/internal/cyberimp/arm/muscle/Insert(mob/living/carbon/reciever, special = FALSE, drop_if_replaced = TRUE)
. = ..()
if(ishuman(reciever)) //Sorry, only humans
- RegisterSignal(reciever, COMSIG_HUMAN_EARLY_UNARMED_ATTACK, PROC_REF(on_attack_hand))
+ RegisterSignal(reciever, COMSIG_LIVING_EARLY_UNARMED_ATTACK, PROC_REF(on_attack_hand))
/obj/item/organ/internal/cyberimp/arm/muscle/Remove(mob/living/carbon/implant_owner, special = 0)
. = ..()
- UnregisterSignal(implant_owner, COMSIG_HUMAN_EARLY_UNARMED_ATTACK)
+ UnregisterSignal(implant_owner, COMSIG_LIVING_EARLY_UNARMED_ATTACK)
/obj/item/organ/internal/cyberimp/arm/muscle/emp_act(severity)
. = ..()
@@ -402,15 +402,17 @@
/obj/item/organ/internal/cyberimp/arm/muscle/proc/on_attack_hand(mob/living/carbon/human/source, atom/target, proximity, modifiers)
SIGNAL_HANDLER
- if(source.get_active_hand() != source.get_bodypart(check_zone(zone)) || !proximity)
- return
+ if(source.get_active_hand() != hand || !proximity)
+ return NONE
if(!source.combat_mode || LAZYACCESS(modifiers, RIGHT_CLICK))
- return
+ return NONE
if(!isliving(target))
- return
+ return NONE
var/datum/dna/dna = source.has_dna()
if(dna?.check_mutation(/datum/mutation/human/hulk)) //NO HULK
- return
+ return NONE
+ if(!source.can_unarmed_attack())
+ return COMPONENT_CANCEL_ATTACK_CHAIN
var/mob/living/living_target = target
source.changeNext_move(CLICK_CD_MELEE)
diff --git a/code/modules/surgery/organs/internal/lungs/_lungs.dm b/code/modules/surgery/organs/internal/lungs/_lungs.dm
index 71dc305ac5f..faf67a4596f 100644
--- a/code/modules/surgery/organs/internal/lungs/_lungs.dm
+++ b/code/modules/surgery/organs/internal/lungs/_lungs.dm
@@ -514,6 +514,7 @@
if(prob(20))
n2o_euphoria = EUPHORIA_ACTIVE
breather.emote(pick("giggle", "laugh"))
+ breather.set_drugginess(30 SECONDS)
else
n2o_euphoria = EUPHORIA_INACTIVE
return
diff --git a/code/modules/tgui_input/say_modal/modal.dm b/code/modules/tgui_input/say_modal/modal.dm
index 3bfff57bd68..1185aaa12af 100644
--- a/code/modules/tgui_input/say_modal/modal.dm
+++ b/code/modules/tgui_input/say_modal/modal.dm
@@ -36,6 +36,7 @@
/datum/tgui_say/New(client/client, id)
src.client = client
window = new(client, id)
+ winset(client, "tgui_say", "size=1,1;is-visible=0;")
window.subscribe(src, PROC_REF(on_message))
window.is_browser = TRUE
@@ -62,11 +63,14 @@
*/
/datum/tgui_say/proc/load()
window_open = FALSE
- winshow(client, "tgui_say", FALSE)
+
+ winset(client, "tgui_say", "pos=848,500;size=231,30;is-visible=0;")
+
window.send_message("props", list(
lightMode = client.prefs?.read_preference(/datum/preference/toggle/tgui_say_light_mode),
maxLength = max_length,
))
+
stop_thinking()
return TRUE
@@ -84,9 +88,7 @@
window_open = TRUE
if(payload["channel"] != OOC_CHANNEL && payload["channel"] != ADMIN_CHANNEL && payload["channel"] != LOOC_CHANNEL) // SKYRAT EDIT CHANGE (Add LOOC_CHANNEL)
start_thinking()
- if(client.typing_indicators)
- log_speech_indicators("[key_name(client)] started typing at [loc_name(client.mob)], indicators enabled.")
- else
+ if(!client.typing_indicators)
log_speech_indicators("[key_name(client)] started typing at [loc_name(client.mob)], indicators DISABLED.")
return TRUE
@@ -97,9 +99,7 @@
/datum/tgui_say/proc/close()
window_open = FALSE
stop_thinking()
- if(client.typing_indicators)
- log_speech_indicators("[key_name(client)] stopped typing at [loc_name(client.mob)], indicators enabled.")
- else
+ if(!client.typing_indicators)
log_speech_indicators("[key_name(client)] stopped typing at [loc_name(client.mob)], indicators DISABLED.")
/**
diff --git a/code/modules/unit_tests/_unit_tests.dm b/code/modules/unit_tests/_unit_tests.dm
index dd3a7ec9eff..4b6c7a737a9 100644
--- a/code/modules/unit_tests/_unit_tests.dm
+++ b/code/modules/unit_tests/_unit_tests.dm
@@ -148,6 +148,7 @@
#include "heretic_rituals.dm"
#include "high_five.dm"
#include "holidays.dm"
+#include "hulk.dm"
#include "human_through_recycler.dm"
#include "hunger_curse.dm"
#include "hydroponics_extractor_storage.dm"
diff --git a/code/modules/unit_tests/dcs_check_list_arguments.dm b/code/modules/unit_tests/dcs_check_list_arguments.dm
index 8089896ba3d..67d7417062b 100644
--- a/code/modules/unit_tests/dcs_check_list_arguments.dm
+++ b/code/modules/unit_tests/dcs_check_list_arguments.dm
@@ -1,18 +1,36 @@
/**
- * list arguments for bespoke elements are treated just like any other datum: as a text ref in the ID.
- * Using un-cached lists in AddElement() and RemoveElement() calls will just create new elements over
- * and over. That's what this unit test is for. It's not a catch-all, but it does a decent job at it.
+ * list arguments for bespoke elements are treated as a text ref in the ID, like any other datum.
+ * Which means that, unless cached, using lists as arguments will lead to multiple instance of the same element
+ * being created over and over.
+ *
+ * Because of how it works, this unit test checks that these list datum args
+ * do not share similar contents (when rearranged in descending alpha-numerical order), to ensure that
+ * the least necessary amount of elements is created. So, using static lists may not be enough,
+ * for example, in the case of two different critters using the death_drops element to drop ectoplasm on death, since,
+ * despite being static lists, the two are different instances assigned to different mob types.
+ *
+ * Most of the time, you won't encounter two different static lists with similar contents used as element args,
+ * meaning using static lists is accepted. However, should that happen, it's advised to replace the instances
+ * with various string_x procs: lists, assoc_lists, assoc_nested_lists or numbers_list, depending on the type.
+ *
+ * In the case of an element where the position of the contents of each datum list argument is important,
+ * ELEMENT_DONT_SORT_LIST_ARGS should be added to its flags, to prevent such issues where the contents are similar
+ * when sorted, but the element instances are not.
+ *
+ * In the off-chance the element is not compatible with this unit test (such as for connect_loc et simila),
+ * you can also use ELEMENT_NO_LIST_UNIT_TEST so that they won't be processed by this unit test at all.
*/
/datum/unit_test/dcs_check_list_arguments
/**
- * This unit test requires every (tangible) atom to have been created at least once
- * so its search is more accurate. That's why it's run after create_and_destroy.
+ * This unit test requires every (unless ignored) atom to have been created at least once
+ * for a more accurate search, which is why it's run after create_and_destroy is done running.
*/
priority = TEST_AFTER_CREATE_AND_DESTROY
/datum/unit_test/dcs_check_list_arguments/Run()
+ var/we_failed = FALSE
for(var/element_type in SSdcs.arguments_that_are_lists_by_element)
- // Keeps tracks of the lists that shouldn't be compared with again.
+ // Keeps track of the lists that shouldn't be compared with again.
var/list/to_ignore = list()
var/list/superlist = SSdcs.arguments_that_are_lists_by_element[element_type]
for(var/list/current as anything in superlist)
@@ -27,7 +45,11 @@
bad_lists += list(compare)
to_ignore[compare] = TRUE
if(bad_lists)
+ we_failed = TRUE
//Include the original, unsorted list in the report. It should be easier to find by the contributor.
var/list/unsorted_list = superlist[current]
- TEST_FAIL("found [length(bad_lists)] identical lists used as argument for element [element_type]. List: [json_encode(unsorted_list)].\n\
- Make sure it's a cached list, or use one of the string_list proc. Also, use the ELEMENT_DONT_SORT_LIST_ARGS flag if the key position of your lists matters.")
+ TEST_FAIL("Found [length(bad_lists)] datum list arguments with similar contents for [element_type]. Contents: [json_encode(unsorted_list)].")
+ ///Let's avoid sending the same instructions over and over, as it's just going to clutter the CI and confuse someone.
+ if(we_failed)
+ TEST_FAIL("Ensure that each list is static or cached. string_lists() (as well as similar procs) is your friend here.\n\
+ Check the documentation from dcs_check_list_arguments.dm for more information!")
diff --git a/code/modules/unit_tests/dragon_expiration.dm b/code/modules/unit_tests/dragon_expiration.dm
index 7b36b576291..45262dc9d60 100644
--- a/code/modules/unit_tests/dragon_expiration.dm
+++ b/code/modules/unit_tests/dragon_expiration.dm
@@ -2,9 +2,12 @@
/datum/unit_test/contents_barfer
/datum/unit_test/contents_barfer/Run()
- var/mob/living/simple_animal/hostile/space_dragon/dragon_time = allocate(/mob/living/simple_animal/hostile/space_dragon)
+ var/mob/living/basic/space_dragon/dragon_time = allocate(/mob/living/basic/space_dragon)
var/mob/living/carbon/human/to_be_consumed = allocate(/mob/living/carbon/human/consistent)
+ to_be_consumed.adjust_fire_stacks(5)
+ to_be_consumed.ignite_mob()
TEST_ASSERT(dragon_time.eat(to_be_consumed), "The space dragon failed to consume the dummy!")
+ TEST_ASSERT(!to_be_consumed.has_status_effect(/datum/status_effect/fire_handler/fire_stacks), "The space dragon failed to extinguish the dummy!")
TEST_ASSERT_EQUAL(to_be_consumed.loc, dragon_time, "The dummy's location, after being successfuly consumed, was not within the space dragon's contents!")
dragon_time.death()
TEST_ASSERT(isturf(to_be_consumed.loc), "After dying, the space dragon did not eject the consumed dummy content barfer element.")
@@ -13,7 +16,7 @@
/datum/unit_test/space_dragon_expiration
/datum/unit_test/space_dragon_expiration/Run()
- var/mob/living/simple_animal/hostile/space_dragon/dragon_time = allocate(/mob/living/simple_animal/hostile/space_dragon)
+ var/mob/living/basic/space_dragon/dragon_time = allocate(/mob/living/basic/space_dragon)
var/mob/living/carbon/human/to_be_consumed = allocate(/mob/living/carbon/human/consistent)
dragon_time.mind_initialize()
diff --git a/code/modules/unit_tests/hulk.dm b/code/modules/unit_tests/hulk.dm
new file mode 100644
index 00000000000..52706e9ac73
--- /dev/null
+++ b/code/modules/unit_tests/hulk.dm
@@ -0,0 +1,44 @@
+/// Tests hulk attacking over normal attacking
+/datum/unit_test/hulk_attack
+ var/hulk_hits = 0
+ var/hand_hits = 0
+
+/datum/unit_test/hulk_attack/Run()
+ var/mob/living/carbon/human/hulk = allocate(/mob/living/carbon/human/consistent)
+ var/mob/living/carbon/human/dummy = allocate(/mob/living/carbon/human/consistent)
+
+
+ RegisterSignal(dummy, COMSIG_ATOM_HULK_ATTACK, PROC_REF(hulk_sig_fire))
+ RegisterSignal(dummy, COMSIG_ATOM_ATTACK_HAND, PROC_REF(hand_sig_fire))
+
+ hulk.dna.add_mutation(/datum/mutation/human/hulk)
+ hulk.set_combat_mode(TRUE)
+ hulk.ClickOn(dummy)
+
+ TEST_ASSERT_EQUAL(hulk_hits, 1, "Hulk should have hit the dummy once.")
+ TEST_ASSERT_EQUAL(hand_hits, 0, "Hulk should not have hit the dummy with attack_hand.")
+ TEST_ASSERT(dummy.getBruteLoss(), "Dummy should have taken brute damage from being hulk punched.")
+
+/datum/unit_test/hulk_attack/proc/hulk_sig_fire()
+ SIGNAL_HANDLER
+ hulk_hits += 1
+
+/datum/unit_test/hulk_attack/proc/hand_sig_fire()
+ SIGNAL_HANDLER
+ hand_hits += 1
+
+/// Tests that hulks aren't given rapid attacks from rapid attack gloves
+/datum/unit_test/hulk_north_star
+
+/datum/unit_test/hulk_north_star/Run()
+ var/mob/living/carbon/human/hulk = allocate(/mob/living/carbon/human/consistent)
+ var/mob/living/carbon/human/dummy = allocate(/mob/living/carbon/human/consistent)
+ var/obj/item/clothing/gloves/rapid/fotns = allocate(/obj/item/clothing/gloves/rapid)
+
+ hulk.equip_to_appropriate_slot(fotns)
+ hulk.dna.add_mutation(/datum/mutation/human/hulk)
+ hulk.set_combat_mode(TRUE)
+ hulk.ClickOn(dummy)
+
+ TEST_ASSERT_NOTEQUAL(hulk.next_move, world.time + CLICK_CD_RAPID, "Hulk should not gain the effects of the Fists of the North Star.")
+ TEST_ASSERT_EQUAL(hulk.next_move, world.time + CLICK_CD_MELEE, "Hulk click cooldown was a value not expected.")
diff --git a/code/modules/unit_tests/simple_animal_freeze.dm b/code/modules/unit_tests/simple_animal_freeze.dm
index fc258596d4a..9771862ad6b 100644
--- a/code/modules/unit_tests/simple_animal_freeze.dm
+++ b/code/modules/unit_tests/simple_animal_freeze.dm
@@ -163,8 +163,6 @@
/mob/living/simple_animal/hostile/skeleton/plasmaminer,
/mob/living/simple_animal/hostile/skeleton/plasmaminer/jackhammer,
/mob/living/simple_animal/hostile/skeleton/templar,
- /mob/living/simple_animal/hostile/space_dragon,
- /mob/living/simple_animal/hostile/space_dragon/spawn_with_antag,
/mob/living/simple_animal/hostile/vatbeast,
/mob/living/simple_animal/hostile/wizard,
/mob/living/simple_animal/hostile/zombie,
diff --git a/config/lavaruinblacklist.txt b/config/lavaruinblacklist.txt
index b2574fd7fd6..2c0642bffac 100644
--- a/config/lavaruinblacklist.txt
+++ b/config/lavaruinblacklist.txt
@@ -41,17 +41,4 @@ _maps/RandomRuins/LavaRuins/lavaland_surface_ash_walker1.dmm
#_maps/RandomRuins/LavaRuins/lavaland_surface_watcher_grave.dmm
#_maps/RandomRuins/LavaRuins/lavaland_surface_ww_vault.dmm
#_maps/RandomRuins/LavaRuins/lavaland_surface_wwiioutpost.dmm
-<<<<<<< HEAD
-#_maps/RandomRuins/LavaRuins/lavaland_surface_tomb.dmm
-#_maps/RandomRuins/LavaRuins/lavaland_surface_hierophant.dmm
-#_maps/RandomRuins/LavaRuins/lavaland_surface_pizzaparty.dmm
-#_maps/RandomRuins/LavaRuins/lavaland_surface_cultaltar.dmm
-#_maps/RandomRuins/LavaRuins/lavaland_surface_hermit.dmm
-#_maps/RandomRuins/LavaRuins/lavaland_surface_gaia.dmm
-#_maps/RandomRuins/LavaRuins/lavaland_surface_elephant_graveyard.dmm
-#_maps/RandomRuins/LavaRuins/lavaland_surface_library.dmm
-#_maps/RandomRuins/AnywhereRuins/fountain_hall.dmm
-#_maps/RandomRuins/LavaRuins/lavaland_surface_phonebooth.dmm
-=======
#_maps/RandomRuins/LavaRuins/lavaland_surface_xeno_nest.dmm
->>>>>>> 0f5d14e68b1 (Mook village and basic mook refactor (#78789))
diff --git a/html/changelogs/archive/2023-08.yml b/html/changelogs/archive/2023-08.yml
index b2d87c8f66c..cd0ea66cfbb 100644
--- a/html/changelogs/archive/2023-08.yml
+++ b/html/changelogs/archive/2023-08.yml
@@ -377,6 +377,10 @@
vinylspiders:
- bugfix: greyscale color selection menu is now working again in the loadout prefs
menu
+ AnywayFarus:
+ - bugfix: Fixed auto-accent on 515
+ Iajret:
+ - bugfix: several minor journey and kilo fixes
2023-08-09:
Fazzie:
- qol: 'Many changes in Birdshot''s Library.
@@ -1261,6 +1265,18 @@
- bugfix: The decloning virus is curable with rezadone again.
tf-4:
- spellcheck: Fixed examine text for gemital.
+ Iajret:
+ - bugfix: Some more fixes to journey and kilo
+ - bugfix: fixed several redsec reskins. Namely satchel, wintercoat, new bluesec
+ skirt and eyepatch reskins
+ Lazar:
+ - rscadd: New weak body quirk
+ - qol: New bluck to hand quirk
+ - balance: rebalanced teshari pounch damage and incoming damage
+ - sound: added/ teshari sounds and more GAS sounds
+ - image: added teshari MOD suits and modules iconds
+ - code_imp: changed human ride and human base code
+ - config: Added teshari new teshari to raundstart races, remove old
2023-08-27:
GoldenAlpharex:
- bugfix: Fixes the recycler being able to grind the server to a halt by trying
diff --git a/html/changelogs/archive/2023-09.yml b/html/changelogs/archive/2023-09.yml
index a45513671ce..e617c03b805 100644
--- a/html/changelogs/archive/2023-09.yml
+++ b/html/changelogs/archive/2023-09.yml
@@ -76,6 +76,10 @@
- bugfix: anti-fauna arrows now do their bonus damage against mining mobs
- bugfix: bows will now display their ammo count correctly (1 when loaded instead
of 2)
+ HWSensum:
+ - bugfix: fixed CQD holster sprite hiding.
+ - bugfix: holster now can be attached to any uniform.
+ - rscadd: Added new CQD holster.
2023-09-03:
Guillaume Prata:
- rscadd: 'New funny internals for the clowns to spawn with. They come with O2 and
diff --git a/html/changelogs/archive/2023-10.yml b/html/changelogs/archive/2023-10.yml
index 45cb9cd9c76..3e5becdb3ed 100644
--- a/html/changelogs/archive/2023-10.yml
+++ b/html/changelogs/archive/2023-10.yml
@@ -252,6 +252,9 @@
- bugfix: fixes gender shaping and height offsets on underwear
- bugfix: female gender shaping now works with digi jumpsuits
- bugfix: fixed rainbow jumpsuit digi sprite
+ HWSensum:
+ - rscadd: Added sanguirite recipe. It's made from salglu, phenol, carbon and water
+ (check in-game wiki!)
2023-10-07:
GoldenAlpharex:
- bugfix: The round end report will no longer expose people's ckeys for the achievements
@@ -613,6 +616,8 @@
- qol: Table flipping feedback is now in the form of balloon alerts
- rscadd: Robo can now print standard and alien surgical tools
- bugfix: Defibs now dont screw over organics if the user was robotic
+ delingar:
+ - rscadd: Added new Halloween-themed craftable decorations
2023-10-13:
Hatterhat:
- bugfix: The CIN replicator medipen pouch and pocket first-aid kit no longer have
@@ -669,27 +674,167 @@
as see a blurb in examine about it
- rscadd: You can now buckle yourself to washing machines! This serves no practical
purpose except for FUN TIMES.
+2023-10-14:
+ LT3:
+ - image: Nitrogen canisters are now yellow, antinob are grey/yellow, empty are grey,
+ hydrogen are red/white
+ Melbert:
+ - bugfix: Fixes Mauna Loa, Monover, Silibinin, Granibitaluri not exiting your system
+ on metabolism
+ - bugfix: Fixes holy water exiting your system at double the rate on metabolism
+ - bugfix: Holy Water no longer spams cultists with big text every time, it's much
+ more tame now
+ Rhials:
+ - rscadd: Two new psyker-oriented virtual domains -- Crate Chaos and Infected Domain.
+ - rscadd: Map helper for cyber-police corpse spawn.
+ - rscadd: Map helper for swapping the encrypted crate in an area with a random crate
+ from that same area.
+ - qol: You can now return to your old body after being summoned by a manifest rune.
+ - qol: You can now return to your old body after dying in CTF.
+ - qol: You can now return to your old body after dying in the Medisim Shuttle battle
+ area.
+ - qol: You can no longer suicide in CTF areas, for integrity purposes.
+ SkyratBot:
+ - bugfix: Space Dragons can now, once again, tear down walls and eat corpses. They
+ also have regained their special damage modifier when attacking mechs.
+ - bugfix: Pete's anger management training has worn off, and he will once again
+ sometimes pick a fight with you for absolutely no reason.
+ - qol: Attacking a goat will not spam messages so frequently.
+ - admin: VV can now display the contents of special byond lists like filters, or
+ client.images
+ - admin: VV on images now displays the image in the header
+ - admin: VV can now display filters and includes their type
+ - qol: apples can now be sliced
+ - bugfix: sqdl2 query readout displays location of turfs properly
+ - sound: laser2.ogg sound has been changed. Now laser carbine uses it.
+ - image: Laser carbine and orange laser sprite have been improved.
+ - bugfix: '"Mirror Walk" is once more the domain of the Maid in the Mirror rather
+ than "every heretic summon"'
+ - bugfix: Heretic mobs can once again survive space
+ - rscadd: targetting someone's arm with *slap now has a unique message
+ - bugfix: The crew is instructed to place fax machines properly in the center of
+ a table without hanging.
+ - bugfix: Modsuits can no longer be deepfried
+ - bugfix: Space Dragon can break walls, eat corpses and destroy mechs more efficiently
+ again
+ - bugfix: Player-controlled lavaland elites can once again return to their tumor
+ after winning their fight
+ TheSS13Melon:
+ - rscadd: Adds the Security Patrol Cap and Sol Police Helmet to the security vendor
+ - rscadd: Adds the Sol Police Helmet to /datum/supply_pack/security/helmets_skyrat
+ - rscdel: Removes redsec helmets from /datum/supply_pack/security/helmets_skyrat
+ nikothedude:
+ - bugfix: Experimental robotic tend wounds now actually shows up
+2023-10-15:
+ LT3:
+ - bugfix: Fixed moldies event default weight overriding the configured event weight
+ Melbert:
+ - qol: Leaning now has a small animation associated.
+ - qol: Cyborgs can now lean against walls.
+ - bugfix: Fixed some runtimes associated with leaning.
+ - bugfix: Fixed being able to lean while dead or in otherwise invalid states.
+ SkyratBot:
+ - bugfix: Fixes Monkey's Delight recipe
+ - refactor: Space Dragons are now basic mobs, please report any unexpected behaviour.
+ - balance: You can now see that a space dragon is destroying a wall with a visual
+ indicator of the wall being damaged.
+ - balance: Space Dragons can pry open airlocks.
+ - bugfix: makes the riot helmet hide hair like other sec helmets
+ - rscadd: Vent Pumps can now be overclocked, do some light reading in the air alarm
+ to figure out what this means.
+ - balance: Vent Pumps now have fan integrity, the damaging of which reduces their
+ ability to move air.
+ - sound: Overclocking sounds, including spool, stop, and loop
+ - balance: Allows spacemen to use age-appropriate drugs by making it so you can
+ now huff N2O to get high.
+ honkpocket:
+ - bugfix: fixed ghost bedsheet wearable being an error sprite on digitigrade legged
+ characters
+ vinylspiders:
+ - bugfix: fixed an issue that could cause the hair the layering option to reset
+ itself upon unequipping a paper mask
+ - qol: paper masks have an action button for adjusting the mask drawing + hair layering,
+ as well as the ability to hide the strap with ctrl click. changing the drawings
+ on the mask now require a pen.
+2023-10-16:
+ SkyratBot:
+ - bugfix: plumbing setups should(hopefully) no longer grind to a halt nor will overflow
+ with excess volume of reagents.
+ - code_imp: created defines for min & max ph. Improved some reaction_reagent code.
+ Made synthesizer dispensable reagent list values static to save memory.
+ - refactor: ph balancing mechanism for reaction chamber is significantly improved.
+ Optimized it's code overall
+ - refactor: examining each individual reagent will display their results back to
+ 2 decimal places again and not 4 for easy readability.
+ - qol: Bladists can now use silver *or* titanium while creating their blades
+ - spellcheck: Broken canisters now have a description
+ - rscadd: Added Afro (Huge) hairstyle
+ Wallem:
+ - bugfix: The active sonar module won't attempt to create 6000000 new pings per
+ process cycle anymore
+ nikothedude:
+ - rscadd: The nobility dresscoat, donator reward for NikoTheGuyDude
2023-10-17:
- AnywayFarus:
- - bugfix: Fixed auto-accent on 515
- HWSensum:
- - bugfix: fixed CQD holster sprite hiding.
- - bugfix: holster now can be attached to any uniform.
- - rscadd: Added new CQD holster.
- - rscadd: Added sanguirite recipe. It's made from salglu, phenol, carbon and water
- (check in-game wiki!)
- Iajret:
- - bugfix: several minor journey and kilo fixes
- - bugfix: Some more fixes to journey and kilo
- - bugfix: fixed several redsec reskins. Namely satchel, wintercoat, new bluesec
- skirt and eyepatch reskins
- Lazar:
- - rscadd: New weak body quirk
- - qol: New bluck to hand quirk
- - balance: rebalanced teshari pounch damage and incoming damage
- - sound: added/ teshari sounds and more GAS sounds
- - image: added teshari MOD suits and modules iconds
- - code_imp: changed human ride and human base code
- - config: Added teshari new teshari to raundstart races, remove old
- delingar:
- - rscadd: Added new Halloween-themed craftable decorations
+ Fazzie:
+ - bugfix: Fixed the doors on the Beach away mission
+ - bugfix: Fixed the railings on the Beach away mission
+ - rscadd: The free golem ship has been swapped for a much better one, with fishing
+ equipment.
+ GoldenAlpharex:
+ - code_imp: Documented a huge part of telecommunications machinery and signal code,
+ and did some minor code improvements to said code.
+ - code_imp: Got rid of a few more single-letter variables. Only over six thousand
+ left to go, woo!
+ - bugfix: Hands of cards will now properly display the last card added to the hand
+ all the time, even when there's more than five cards in that hand.
+ LT3:
+ - bugfix: Fixed font scaling for announcements
+ - bugfix: The remaining survival pod bed on Icebox is now a medical bed
+ - bugfix: Announcement text now uses the intended CSS
+ - bugfix: Microwave will no longer get stuck turned on if a PDA has no cell
+ - bugfix: Silicons can no longer silently change the microwave between cook and
+ charge
+ Melbert:
+ - refactor: Refactored unarmed attacking mechanisms, this means dis-coordinated
+ humans will now bite people like monkeys (like how coordinated monkeys punch
+ people like humans?)
+ - refactor: Dis-coordinated humans smashing up machines now use their hands, rather
+ than their paws
+ Motho:
+ - sound: Plasmamen all across the sector have begun to announce their fright or
+ pain in a new way.
+ Paxilmaniac:
+ - image: A large number of security's clothing has been unified in color palette
+ and updated in style.
+ - rscdel: Some really old or unfitting security outfits, like the correction's officer's
+ weird hat and the unusued tactical peacekeeper jumpsuit have been removed.
+ RatFromTheJungle:
+ - qol: moves the self authentication device's bulky desc to an examine more, and
+ puts a shorter on in it's place.
+ SkyratBot:
+ - bugfix: Fixes a bug with the plasma flower core MODsuit that would cause a butterfly
+ murder scene wherever there were turrets
+ - sound: added sounds for scanning valued items with an export scanner
+ - bugfix: Delta's cargo bay has been connected to the atmos pipe networks
+ - bugfix: TGC Mana and Health bars are correctly offset on the holodeck.
+ - qol: Icebox Visitation now has a door connected to brig
+ - bugfix: Having all augmented limbs will make you properly spaceproof once again.
+ - bugfix: Androids are immune to crit damage again.
+ - bugfix: Surgery on robotic limbs can be canceled.
+ - code_imp: removed unnecessary calls to `update_total()`
+ - bugfix: Megafauna, lavaland elites, and abstract mobs now correctly cannot be
+ spawned by a toolbox of ash drake summoning
+ - bugfix: Fuel tanks are explosive again
+ - bugfix: TGUI Say should no longer flash during initialization
+ - bugfix: Fixes respiration-transmission advanced viruses to no longer have an always-guaranteed
+ infection chance per tick.
+ honkpocket:
+ - balance: The Bolt Pepperball AHG is now small sized instead of normal sized
+ jjpark-kb:
+ - rscadd: added the worm/ant farm
+ - balance: ash farming can be worm fertilized (same as regen core), but now only
+ does one harvest
+ - image: added the worm/ant farm, worm fertilizer
+ vinylspiders:
+ - bugfix: air alarm and mass driver controller in Voidraptor disposals room will
+ no longer overlap
diff --git a/icons/mob/human/human_face.dmi b/icons/mob/human/human_face.dmi
index 6530b300aa6..886f0bf793b 100644
Binary files a/icons/mob/human/human_face.dmi and b/icons/mob/human/human_face.dmi differ
diff --git a/icons/obj/machines/atmospherics/unary_devices.dmi b/icons/obj/machines/atmospherics/unary_devices.dmi
index 3350500fde9..6a929f211b8 100644
Binary files a/icons/obj/machines/atmospherics/unary_devices.dmi and b/icons/obj/machines/atmospherics/unary_devices.dmi differ
diff --git a/modular_skyrat/master_files/code/game/objects/items/stacks/sheets/sheet_types.dm b/modular_skyrat/master_files/code/game/objects/items/stacks/sheets/sheet_types.dm
index b74721c63ae..3c1cbc2cb0e 100644
--- a/modular_skyrat/master_files/code/game/objects/items/stacks/sheets/sheet_types.dm
+++ b/modular_skyrat/master_files/code/game/objects/items/stacks/sheets/sheet_types.dm
@@ -73,6 +73,7 @@ GLOBAL_LIST_INIT(skyrat_wood_recipes, list(
new/datum/stack_recipe("sauna oven", /obj/structure/sauna_oven, 30, time = 1.5 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_ENTERTAINMENT),
new/datum/stack_recipe("large wooden mortar", /obj/structure/large_mortar, 10, time = 3 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_TOOLS),
new/datum/stack_recipe("wooden cutting board", /obj/item/cutting_board, 5, time = 2 SECONDS, check_density = FALSE, category = CAT_TOOLS),
+ new/datum/stack_recipe("worm barrel", /obj/structure/wormfarm, 5, time = 2 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_TOOLS),
))
/obj/item/stack/sheet/mineral/wood/get_main_recipes()
@@ -129,3 +130,13 @@ GLOBAL_LIST_INIT(skyrat_snow_recipes, list(
/obj/item/stack/sheet/mineral/snow/get_main_recipes()
. = ..()
. += GLOB.skyrat_snow_recipes
+
+// Sand
+
+GLOBAL_LIST_INIT(skyrat_sand_recipes, list(
+ new/datum/stack_recipe("ant farm", /obj/structure/antfarm, 20, time = 2 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_TOOLS),
+))
+
+/obj/item/stack/ore/glass/get_main_recipes()
+ . = ..()
+ . += GLOB.skyrat_sand_recipes
diff --git a/modular_skyrat/master_files/code/modules/clothing/under/costume.dm b/modular_skyrat/master_files/code/modules/clothing/under/costume.dm
index 8fd875d2289..9e5567e4496 100644
--- a/modular_skyrat/master_files/code/modules/clothing/under/costume.dm
+++ b/modular_skyrat/master_files/code/modules/clothing/under/costume.dm
@@ -9,7 +9,7 @@
worn_icon = 'modular_skyrat/master_files/icons/mob/clothing/under/costume.dmi'
can_adjust = FALSE
-//My least favorite file. Just... try to keep it sorted. And nothing over the top (The victorian dresses were way too much)
+//My least favorite file. Just... try to keep it sorted. And nothing over the top
/*
* UNSORTED
diff --git a/modular_skyrat/master_files/code/modules/projectiles/ammunition/energy/stun.dm b/modular_skyrat/master_files/code/modules/projectiles/ammunition/energy/stun.dm
new file mode 100644
index 00000000000..8d05d57e551
--- /dev/null
+++ b/modular_skyrat/master_files/code/modules/projectiles/ammunition/energy/stun.dm
@@ -0,0 +1,2 @@
+/obj/item/ammo_casing/energy/disabler/skyrat
+ e_cost = LASER_SHOTS(1, STANDARD_CELL_CHARGE)
diff --git a/modular_skyrat/master_files/icons/donator/mob/clothing/suit.dmi b/modular_skyrat/master_files/icons/donator/mob/clothing/suit.dmi
index da2fa01153b..ec84bdf6e76 100644
Binary files a/modular_skyrat/master_files/icons/donator/mob/clothing/suit.dmi and b/modular_skyrat/master_files/icons/donator/mob/clothing/suit.dmi differ
diff --git a/modular_skyrat/master_files/icons/donator/obj/clothing/suits.dmi b/modular_skyrat/master_files/icons/donator/obj/clothing/suits.dmi
index cfb3b90e070..140bd772f44 100644
Binary files a/modular_skyrat/master_files/icons/donator/obj/clothing/suits.dmi and b/modular_skyrat/master_files/icons/donator/obj/clothing/suits.dmi differ
diff --git a/modular_skyrat/master_files/icons/mob/clothing/head.dmi b/modular_skyrat/master_files/icons/mob/clothing/head.dmi
index a67c748a66d..dcb4e45d41b 100644
Binary files a/modular_skyrat/master_files/icons/mob/clothing/head.dmi and b/modular_skyrat/master_files/icons/mob/clothing/head.dmi differ
diff --git a/modular_skyrat/master_files/icons/mob/clothing/head/helmet.dmi b/modular_skyrat/master_files/icons/mob/clothing/head/helmet.dmi
index 63e6d14124d..6043c7fa83e 100644
Binary files a/modular_skyrat/master_files/icons/mob/clothing/head/helmet.dmi and b/modular_skyrat/master_files/icons/mob/clothing/head/helmet.dmi differ
diff --git a/modular_skyrat/master_files/icons/mob/clothing/head/winterhood.dmi b/modular_skyrat/master_files/icons/mob/clothing/head/winterhood.dmi
index f4d68afba7b..f07efd1c011 100644
Binary files a/modular_skyrat/master_files/icons/mob/clothing/head/winterhood.dmi and b/modular_skyrat/master_files/icons/mob/clothing/head/winterhood.dmi differ
diff --git a/modular_skyrat/master_files/icons/mob/clothing/mask.dmi b/modular_skyrat/master_files/icons/mob/clothing/mask.dmi
index 07bef00e9c4..8b52d28c7cb 100644
Binary files a/modular_skyrat/master_files/icons/mob/clothing/mask.dmi and b/modular_skyrat/master_files/icons/mob/clothing/mask.dmi differ
diff --git a/modular_skyrat/master_files/icons/mob/clothing/neck.dmi b/modular_skyrat/master_files/icons/mob/clothing/neck.dmi
index 3e29bf945b9..93c9d34c648 100644
Binary files a/modular_skyrat/master_files/icons/mob/clothing/neck.dmi and b/modular_skyrat/master_files/icons/mob/clothing/neck.dmi differ
diff --git a/modular_skyrat/master_files/icons/mob/clothing/suit.dmi b/modular_skyrat/master_files/icons/mob/clothing/suit.dmi
index c00b68f50cf..dd76a562f7b 100644
Binary files a/modular_skyrat/master_files/icons/mob/clothing/suit.dmi and b/modular_skyrat/master_files/icons/mob/clothing/suit.dmi differ
diff --git a/modular_skyrat/master_files/icons/mob/clothing/suits/armor.dmi b/modular_skyrat/master_files/icons/mob/clothing/suits/armor.dmi
index 24004a9b260..9d6de8b7cfc 100644
Binary files a/modular_skyrat/master_files/icons/mob/clothing/suits/armor.dmi and b/modular_skyrat/master_files/icons/mob/clothing/suits/armor.dmi differ
diff --git a/modular_skyrat/master_files/icons/mob/clothing/suits/armor_digi.dmi b/modular_skyrat/master_files/icons/mob/clothing/suits/armor_digi.dmi
index f6f7defe702..5ec98ebafa8 100644
Binary files a/modular_skyrat/master_files/icons/mob/clothing/suits/armor_digi.dmi and b/modular_skyrat/master_files/icons/mob/clothing/suits/armor_digi.dmi differ
diff --git a/modular_skyrat/master_files/icons/mob/clothing/suits/wintercoat.dmi b/modular_skyrat/master_files/icons/mob/clothing/suits/wintercoat.dmi
index 11e61ac81d7..a92a34ccc90 100644
Binary files a/modular_skyrat/master_files/icons/mob/clothing/suits/wintercoat.dmi and b/modular_skyrat/master_files/icons/mob/clothing/suits/wintercoat.dmi differ
diff --git a/modular_skyrat/master_files/icons/mob/clothing/under/security.dmi b/modular_skyrat/master_files/icons/mob/clothing/under/security.dmi
index 2b69e02fd83..0aeace1f82e 100644
Binary files a/modular_skyrat/master_files/icons/mob/clothing/under/security.dmi and b/modular_skyrat/master_files/icons/mob/clothing/under/security.dmi differ
diff --git a/modular_skyrat/master_files/icons/mob/clothing/under/security_digi.dmi b/modular_skyrat/master_files/icons/mob/clothing/under/security_digi.dmi
index ea98435846f..5b5a709889c 100644
Binary files a/modular_skyrat/master_files/icons/mob/clothing/under/security_digi.dmi and b/modular_skyrat/master_files/icons/mob/clothing/under/security_digi.dmi differ
diff --git a/modular_skyrat/master_files/icons/obj/clothing/glasses.dmi b/modular_skyrat/master_files/icons/obj/clothing/glasses.dmi
index 7a86c3a396e..d42c3f245b1 100644
Binary files a/modular_skyrat/master_files/icons/obj/clothing/glasses.dmi and b/modular_skyrat/master_files/icons/obj/clothing/glasses.dmi differ
diff --git a/modular_skyrat/master_files/icons/obj/clothing/neck.dmi b/modular_skyrat/master_files/icons/obj/clothing/neck.dmi
index 792b56aba8d..a3a71e6a73c 100644
Binary files a/modular_skyrat/master_files/icons/obj/clothing/neck.dmi and b/modular_skyrat/master_files/icons/obj/clothing/neck.dmi differ
diff --git a/modular_skyrat/master_files/icons/obj/clothing/suits.dmi b/modular_skyrat/master_files/icons/obj/clothing/suits.dmi
index 45b23619f89..bd5fb3aebba 100644
Binary files a/modular_skyrat/master_files/icons/obj/clothing/suits.dmi and b/modular_skyrat/master_files/icons/obj/clothing/suits.dmi differ
diff --git a/modular_skyrat/master_files/icons/obj/clothing/suits/armor.dmi b/modular_skyrat/master_files/icons/obj/clothing/suits/armor.dmi
index e0a71694d5b..30fd1d4cb22 100644
Binary files a/modular_skyrat/master_files/icons/obj/clothing/suits/armor.dmi and b/modular_skyrat/master_files/icons/obj/clothing/suits/armor.dmi differ
diff --git a/modular_skyrat/master_files/icons/obj/clothing/under/security.dmi b/modular_skyrat/master_files/icons/obj/clothing/under/security.dmi
index c9b5e383fea..ac3891c27c1 100644
Binary files a/modular_skyrat/master_files/icons/obj/clothing/under/security.dmi and b/modular_skyrat/master_files/icons/obj/clothing/under/security.dmi differ
diff --git a/modular_skyrat/modules/alerts/code/priority_announce.dm b/modular_skyrat/modules/alerts/code/priority_announce.dm
deleted file mode 100644
index 196806f6130..00000000000
--- a/modular_skyrat/modules/alerts/code/priority_announce.dm
+++ /dev/null
@@ -1,139 +0,0 @@
-/**
- * Make a big red text announcement to
- *
- * Formatted like:
- *
- * " Message from sender "
- *
- * " Title "
- *
- * " Text "
- *
- * Arguments
- * * text - required, the text to announce
- * * title - optional, the title of the announcement.
- * * sound - optional, the sound played accompanying the announcement
- * * type - optional, the type of the announcement, for some "preset" announcement templates. "Priority", "Captain", "Syndicate Captain"
- * * sender_override - optional, modifies the sender of the announcement
- * * has_important_message - is this message critical to the game (and should not be overridden by station traits), or not
- * * players - a list of all players to send the message to. defaults to all players (not including new players)
- * * encode_title - if TRUE, the title will be HTML encoded
- * * encode_text - if TRUE, the text will be HTML encoded
- */
-/proc/priority_announce(text, title = "", sound, type, sender_override, has_important_message = FALSE, list/mob/players, encode_title = TRUE, encode_text = TRUE)
- if(!text)
- return
-
- if(encode_title && title && length(title) > 0)
- title = html_encode(title)
- if(encode_text)
- text = html_encode(text)
- if(!length(text))
- return
-
- var/announcement
-
- if(!sound)
- sound = SSstation.announcer.get_rand_alert_sound()
- else if(SSstation.announcer.event_sounds[sound])
- var/list/picked = SSstation.announcer.event_sounds[sound]
- sound = pick(picked)
-
- if(type == "Priority")
- announcement += "