diff --git a/code/__DEFINES/components.dm b/code/__DEFINES/components.dm
index d155f3f2..c16c95e0 100644
--- a/code/__DEFINES/components.dm
+++ b/code/__DEFINES/components.dm
@@ -25,24 +25,18 @@
//////////////////////////////////////////////////////////////////
// /datum signals
-#define COMSIG_PARENT_QDELETED "parent_qdeleted" //after a datum's Destroy() is called: (force, qdel_hint), at this point none of the other components chose to interrupt qdel and Destroy has been called
-
// /atom signals
//Positions for overrides list
//End positions
#define COMSIG_ATOM_RATVAR_ACT "atom_ratvar_act" //from base of atom/ratvar_act(): ()
/////////////////
-
-
// /area signals
// /turf signals
// /atom/movable signals
-// /mob signals
-
// /mob/living signals
// /mob/living/carbon signals
@@ -96,3 +90,14 @@
//Ouch my toes!
#define CALTROP_BYPASS_SHOES 1
#define CALTROP_IGNORE_WALKERS 2
+
+#define ELEMENT_INCOMPATIBLE 1 // Return value to cancel attaching
+
+// /datum/element flags
+/// Causes the detach proc to be called when the host object is being deleted
+#define ELEMENT_DETACH (1 << 0)
+/**
+ * Only elements created with the same arguments given after `id_arg_index` share an element instance
+ * The arguments are the same when the text and number values are the same and all other values have the same ref
+ */
+#define ELEMENT_BESPOKE (1 << 1)
diff --git a/code/__DEFINES/dcs/signals.dm b/code/__DEFINES/dcs/signals.dm
index 9609f19c..4036bab9 100644
--- a/code/__DEFINES/dcs/signals.dm
+++ b/code/__DEFINES/dcs/signals.dm
@@ -180,21 +180,17 @@
///from base of area/Exited(): (/area)
#define COMSIG_EXIT_AREA "exit_area"
///from base of atom/Click(): (location, control, params, mob/user)
-#define COMSIG_CLICK "atom_click"
-///from base of atom/ShiftClick(): (/mob)
-#define COMSIG_CLICK_SHIFT "shift_click"
- #define COMPONENT_ALLOW_EXAMINATE (1<<0) //Allows the user to examinate regardless of client.eye.
-///from base of atom/CtrlClickOn(): (/mob)
-#define COMSIG_CLICK_CTRL "ctrl_click"
-///from base of atom/AltClick(): (/mob)
-#define COMSIG_CLICK_ALT "alt_click"
-///from base of atom/CtrlShiftClick(/mob)
-#define COMSIG_CLICK_CTRL_SHIFT "ctrl_shift_click"
-///from base of atom/MouseDrop(): (/atom/over, /mob/user)
-#define COMSIG_MOUSEDROP_ONTO "mousedrop_onto"
- #define COMPONENT_NO_MOUSEDROP (1<<0)
-///from base of atom/MouseDrop_T: (/atom/from, /mob/user)
-#define COMSIG_MOUSEDROPPED_ONTO "mousedropped_onto"
+#define COMSIG_CLICK "atom_click" //from base of atom/Click(): (location, control, params, mob/user)
+#define COMSIG_CLICK_SHIFT "shift_click" //from base of atom/ShiftClick(): (/mob)
+ #define COMPONENT_ALLOW_EXAMINATE 1
+ #define COMPONENT_DENY_EXAMINATE 2 //Higher priority compared to the above one
+
+#define COMSIG_CLICK_CTRL "ctrl_click" //from base of atom/CtrlClickOn(): (/mob)
+#define COMSIG_CLICK_ALT "alt_click" //from base of atom/AltClick(): (/mob)
+#define COMSIG_CLICK_CTRL_SHIFT "ctrl_shift_click" //from base of atom/CtrlShiftClick(/mob)
+#define COMSIG_MOUSEDROP_ONTO "mousedrop_onto" //from base of atom/MouseDrop(): (/atom/over, /mob/user)
+ #define COMPONENT_NO_MOUSEDROP 1
+#define COMSIG_MOUSEDROPPED_ONTO "mousedropped_onto"
// /area signals
@@ -261,15 +257,45 @@
///called when the movable is added to a disposal holder object for disposal movement: (obj/structure/disposalholder/holder, obj/machinery/disposal/source)
#define COMSIG_MOVABLE_DISPOSING "movable_disposing"
+#define COMSIG_MOVABLE_TELEPORTED "movable_teleported" //from base of do_teleport(): (channel, turf/origin, turf/destination)
// /mob signals
+// /mob signals
+#define COMSIG_MOB_EXAMINATE "mob_examinate" //from base of /mob/verb/examinate(): (atom/A)
+#define COMSIG_MOB_DEATH "mob_death" //from base of mob/death(): (gibbed)
+ #define COMPONENT_BLOCK_DEATH_BROADCAST 1 //stops the death from being broadcasted in deadchat.
+#define COMSIG_MOB_GHOSTIZE "mob_ghostize" //from base of mob/Ghostize(): (can_reenter_corpse, special, penalize)
+ #define COMPONENT_BLOCK_GHOSTING 1
+#define COMSIG_MOB_ALLOWED "mob_allowed" //from base of obj/allowed(mob/M): (/obj) returns bool, if TRUE the mob has id access to the obj
+#define COMSIG_MOB_RECEIVE_MAGIC "mob_receive_magic" //from base of mob/anti_magic_check(): (mob/user, magic, holy, tinfoil, chargecost, self, protection_sources)
+ #define COMPONENT_BLOCK_MAGIC 1
+#define COMSIG_MOB_HUD_CREATED "mob_hud_created" //from base of mob/create_mob_hud(): ()
+#define COMSIG_MOB_ATTACK_HAND "mob_attack_hand" //from base of
+#define COMSIG_MOB_ITEM_ATTACK "mob_item_attack" //from base of /obj/item/attack(): (mob/M, mob/user)
+ #define COMPONENT_ITEM_NO_ATTACK 1
+#define COMSIG_MOB_ITEM_AFTERATTACK "mob_item_afterattack" //from base of obj/item/afterattack(): (atom/target, mob/user, proximity_flag, click_parameters)
+#define COMSIG_MOB_ATTACK_RANGED "mob_attack_ranged" //from base of mob/RangedAttack(): (atom/A, params)
+#define COMSIG_MOB_THROW "mob_throw" //from base of /mob/throw_item(): (atom/target)
+#define COMSIG_MOB_KEY_CHANGE "mob_key_change" //from base of /mob/transfer_ckey(): (new_character, old_character)
+#define COMSIG_MOB_PRE_PLAYER_CHANGE "mob_pre_player_change" //sent to the target mob from base of /mob/transfer_ckey() and /mind/transfer_to(): (our_character, their_character)
+// #define COMPONENT_STOP_MIND_TRANSFER 1
+#define COMSIG_MOB_UPDATE_SIGHT "mob_update_sight" //from base of /mob/update_sight(): ()
+#define COMSIG_MOB_ON_NEW_MIND "mob_on_new_mind" //called when a new mind is assigned to a mob: ()
+#define COMSIG_MOB_SAY "mob_say" // from /mob/living/say(): (proc args list)
+ #define COMPONENT_UPPERCASE_SPEECH 1
+ // used to access COMSIG_MOB_SAY argslist
+ #define SPEECH_MESSAGE 1
+ // #define SPEECH_BUBBLE_TYPE 2
+ #define SPEECH_SPANS 3
+ /* #define SPEECH_SANITIZE 4
+ #define SPEECH_LANGUAGE 5
+ #define SPEECH_IGNORE_SPAM 6
+ #define SPEECH_FORCED 7 */
///from base of /mob/Login(): ()
#define COMSIG_MOB_LOGIN "mob_login"
///from base of /mob/Logout(): ()
#define COMSIG_MOB_LOGOUT "mob_logout"
-///from base of mob/death(): (gibbed)
-#define COMSIG_MOB_DEATH "mob_death"
///from base of mob/set_stat(): (new_stat)
#define COMSIG_MOB_STATCHANGE "mob_statchange"
///from base of mob/clickon(): (atom/A, params)
@@ -279,44 +305,12 @@
///from base of mob/AltClickOn(): (atom/A)
#define COMSIG_MOB_ALTCLICKON "mob_altclickon"
#define COMSIG_MOB_CANCEL_CLICKON (1<<0)
-
-///from base of obj/allowed(mob/M): (/obj) returns bool, if TRUE the mob has id access to the obj
-#define COMSIG_MOB_ALLOWED "mob_allowed"
-///from base of mob/anti_magic_check(): (mob/user, magic, holy, tinfoil, chargecost, self, protection_sources)
-#define COMSIG_MOB_RECEIVE_MAGIC "mob_receive_magic"
- #define COMPONENT_BLOCK_MAGIC (1<<0)
-///from base of mob/create_mob_hud(): ()
-#define COMSIG_MOB_HUD_CREATED "mob_hud_created"
-///from base of atom/attack_hand(): (mob/user)
-#define COMSIG_MOB_ATTACK_HAND "mob_attack_hand"
-///from base of /obj/item/attack(): (mob/M, mob/user)
-#define COMSIG_MOB_ITEM_ATTACK "mob_item_attack"
- #define COMPONENT_ITEM_NO_ATTACK (1<<0)
///from base of /mob/living/proc/apply_damage(): (damage, damagetype, def_zone)
#define COMSIG_MOB_APPLY_DAMGE "mob_apply_damage"
-///from base of obj/item/afterattack(): (atom/target, mob/user, proximity_flag, click_parameters)
-#define COMSIG_MOB_ITEM_AFTERATTACK "mob_item_afterattack"
///from base of obj/item/attack_qdeleted(): (atom/target, mob/user, proxiumity_flag, click_parameters)
-#define COMSIG_MOB_ITEM_ATTACK_QDELETED "mob_item_attack_qdeleted"
-///from base of mob/RangedAttack(): (atom/A, params)
-#define COMSIG_MOB_ATTACK_RANGED "mob_attack_ranged"
-///from base of /mob/throw_item(): (atom/target)
-#define COMSIG_MOB_THROW "mob_throw"
-///from base of /mob/verb/examinate(): (atom/target)
-#define COMSIG_MOB_EXAMINATE "mob_examinate"
-///from base of /mob/update_sight(): ()
-#define COMSIG_MOB_UPDATE_SIGHT "mob_update_sight"
-////from /mob/living/say(): ()
-#define COMSIG_MOB_SAY "mob_say"
- #define COMPONENT_UPPERCASE_SPEECH (1<<0)
- // used to access COMSIG_MOB_SAY argslist
- #define SPEECH_MESSAGE 1
- // #define SPEECH_BUBBLE_TYPE 2
- #define SPEECH_SPANS 3
- /* #define SPEECH_SANITIZE 4
- #define SPEECH_LANGUAGE 5
- #define SPEECH_IGNORE_SPAM 6
- #define SPEECH_FORCED 7 */
+#define COMSIG_MOB_ITEM_ATTACK_QDELETED "mob_item_attack_qdeleted" //from base of /mob/transfer_ckey(): (new_character, old_character)
+ #define COMPONENT_DO_NOT_PENALIZE_GHOSTING (1<<1)
+ #define COMPONENT_FREE_GHOSTING (1<<2)
///from /mob/say_dead(): (mob/speaker, message)
#define COMSIG_MOB_DEADSAY "mob_deadsay"
@@ -343,6 +337,9 @@
#define COMSIG_LIVING_MINOR_SHOCK "living_minor_shock"
///from base of mob/living/revive() (full_heal, admin_revive)
#define COMSIG_LIVING_REVIVE "living_revive"
+
+#define COMSIG_LIVING_GUN_PROCESS_FIRE "living_gun_process_fire" //from base of /obj/item/gun/proc/process_fire(): (atom/target, params, zone_override)
+
///from base of /mob/living/regenerate_limbs(): (noheal, excluded_limbs)
#define COMSIG_LIVING_REGENERATE_LIMBS "living_regen_limbs"
///from base of /obj/item/bodypart/proc/attach_limb(): (new_limb, special) allows you to fail limb attachment
diff --git a/code/__DEFINES/misc.dm b/code/__DEFINES/misc.dm
index a1869b99..35f0482a 100644
--- a/code/__DEFINES/misc.dm
+++ b/code/__DEFINES/misc.dm
@@ -503,4 +503,6 @@ GLOBAL_LIST_INIT(pda_reskins, list(PDA_SKIN_CLASSIC = 'icons/obj/pda.dmi', PDA_S
#define FALL_STOP_INTERCEPTING (1<<2) //Used in situations where halting the whole "intercept" loop would be better, like supermatter dusting (and thus deleting) the atom.
//Misc text define. Does 4 spaces. Used as a makeshift tabulator.
-#define FOURSPACES " "
\ No newline at end of file
+#define FOURSPACES " "
+
+#define CANT_REENTER_ROUND -1
diff --git a/code/__DEFINES/traits.dm b/code/__DEFINES/traits.dm
index e86200ce..bc5ff183 100644
--- a/code/__DEFINES/traits.dm
+++ b/code/__DEFINES/traits.dm
@@ -168,6 +168,10 @@
#define TRAIT_COLDBLOODED "coldblooded" // Your body is literal room temperature. Does not make you immune to the temp.
#define TRAIT_FLIMSY "flimsy" //you have 20% less maxhealth
#define TRAIT_TOUGH "tough" //you have 10% more maxhealth
+#define TRAIT_AUTO_CATCH_ITEM "auto_catch_item"
+#define TRAIT_CLOWN_MENTALITY "clown_mentality" // The future is now, clownman.
+#define TRAIT_FREESPRINT "free_sprinting"
+
// common trait sources
#define TRAIT_GENERIC "generic"
diff --git a/code/__HELPERS/game.dm b/code/__HELPERS/game.dm
index 23ecbf0c..cc7f5d5d 100644
--- a/code/__HELPERS/game.dm
+++ b/code/__HELPERS/game.dm
@@ -442,12 +442,8 @@
candidates -= M
/proc/pollGhostCandidates(Question, jobbanType, datum/game_mode/gametypeCheck, be_special_flag = 0, poll_time = 300, ignore_category = null, flashwindow = TRUE)
- var/list/candidates = list()
-
- for(var/mob/dead/observer/G in GLOB.player_list)
- if(G.can_reenter_round)
- candidates += G
-
+ var/datum/element/ghost_role_eligibility/eligibility = SSdcs.GetElement(/datum/element/ghost_role_eligibility)
+ var/list/candidates = eligibility.get_all_ghost_role_eligible()
return pollCandidates(Question, jobbanType, gametypeCheck, be_special_flag, poll_time, ignore_category, flashwindow, candidates)
/proc/pollCandidates(Question, jobbanType, datum/game_mode/gametypeCheck, be_special_flag = 0, poll_time = 300, ignore_category = null, flashwindow = TRUE, list/group = null)
@@ -510,7 +506,7 @@
G_found.client.prefs.copy_to(new_character)
new_character.dna.update_dna_identity()
- new_character.key = G_found.key
+ G_found.transfer_ckey(new_character, FALSE)
return new_character
diff --git a/code/__HELPERS/unsorted.dm b/code/__HELPERS/unsorted.dm
index 7173aa73..2df393fb 100644
--- a/code/__HELPERS/unsorted.dm
+++ b/code/__HELPERS/unsorted.dm
@@ -768,16 +768,6 @@ GLOBAL_LIST_INIT(can_embed_types, typecacheof(list(
/obj/item/stack/rods,
/obj/item/pipe)))
-/proc/can_embed(obj/item/W)
- if(W.is_sharp())
- return 1
- if(is_pointed(W))
- return 1
-
- if(is_type_in_typecache(W, GLOB.can_embed_types))
- return 1
-
-
/*
Checks if that loc and dir has an item on the wall
*/
diff --git a/code/_globalvars/misc.dm b/code/_globalvars/misc.dm
index 899ccbe7..38147262 100644
--- a/code/_globalvars/misc.dm
+++ b/code/_globalvars/misc.dm
@@ -17,6 +17,8 @@ GLOBAL_VAR_INIT(bsa_unlock, FALSE) //BSA unlocked by head ID swipes
GLOBAL_LIST_EMPTY(player_details) // ckey -> /datum/player_details
+GLOBAL_LIST_EMPTY(clientless_round_timeouts) // ckey -> time that ckey can rejoin round
+
// All religion stuff
GLOBAL_VAR(religion)
GLOBAL_VAR(deity)
@@ -24,4 +26,4 @@ GLOBAL_VAR(bible_name)
GLOBAL_VAR(bible_icon_state)
GLOBAL_VAR(bible_item_state)
GLOBAL_VAR(holy_weapon_type)
-GLOBAL_VAR(holy_armor_type)
\ No newline at end of file
+GLOBAL_VAR(holy_armor_type)
diff --git a/code/_onclick/click.dm b/code/_onclick/click.dm
index e59aafda..8b5a754d 100644
--- a/code/_onclick/click.dm
+++ b/code/_onclick/click.dm
@@ -315,9 +315,10 @@
/mob/proc/ShiftClickOn(atom/A)
A.ShiftClick(src)
return
+
/atom/proc/ShiftClick(mob/user)
- SEND_SIGNAL(src, COMSIG_CLICK_SHIFT, user)
- if(user.client && user.client.eye == user || user.client.eye == user.loc)
+ var/flags = SEND_SIGNAL(src, COMSIG_CLICK_SHIFT, user)
+ if(!(flags & COMPONENT_DENY_EXAMINATE) && user.client && (user.client.eye == user || user.client.eye == user.loc || flags & COMPONENT_ALLOW_EXAMINATE))
user.examinate(src)
return
diff --git a/code/_onclick/item_attack.dm b/code/_onclick/item_attack.dm
index c080777b..6fb20a93 100644
--- a/code/_onclick/item_attack.dm
+++ b/code/_onclick/item_attack.dm
@@ -109,17 +109,18 @@
take_damage(I.force, I.damtype, "melee", 1)
/mob/living/attacked_by(obj/item/I, mob/living/user)
+ var/totitemdamage = I.force
+ if(iscarbon(user))
+ var/mob/living/carbon/tempcarb = user
+ if(!tempcarb.combatmode)
+ totitemdamage *= 0.5
+ if(user.resting)
+ totitemdamage *= 0.5
+ //CIT CHANGES END HERE
+ if(user != src && check_shields(I, totitemdamage, "the [I.name]", MELEE_ATTACK, I.armour_penetration))
+ return FALSE
send_item_attack_message(I, user)
if(I.force)
- //CIT CHANGES START HERE - combatmode and resting checks
- var/totitemdamage = I.force
- if(iscarbon(user))
- var/mob/living/carbon/tempcarb = user
- if(!tempcarb.combatmode)
- totitemdamage *= 0.5
- if(user.resting)
- totitemdamage *= 0.5
- //CIT CHANGES END HERE
apply_damage(totitemdamage, I.damtype) //CIT CHANGE - replaces I.force with totitemdamage
if(I.damtype == BRUTE)
if(prob(33))
diff --git a/code/controllers/configuration/entries/game_options.dm b/code/controllers/configuration/entries/game_options.dm
index a75d17fa..b487dc3d 100644
--- a/code/controllers/configuration/entries/game_options.dm
+++ b/code/controllers/configuration/entries/game_options.dm
@@ -132,6 +132,14 @@
min_val = 0
max_val = 1
+/datum/config_entry/number/suicide_reenter_round_timer
+ config_entry_value = 30
+ min_val = 0
+
+/datum/config_entry/number/roundstart_suicide_time_limit
+ config_entry_value = 30
+ min_val = 0
+
/datum/config_entry/number/shuttle_refuel_delay
config_entry_value = 12000
min_val = 0
diff --git a/code/controllers/subsystem/dcs.dm b/code/controllers/subsystem/dcs.dm
index c1e101a0..faf95fd3 100644
--- a/code/controllers/subsystem/dcs.dm
+++ b/code/controllers/subsystem/dcs.dm
@@ -1,6 +1,27 @@
-SUBSYSTEM_DEF(dcs)
+PROCESSING_SUBSYSTEM_DEF(dcs)
name = "Datum Component System"
- flags = SS_NO_INIT | SS_NO_FIRE
+ flags = SS_NO_INIT
+ var/list/elements_by_type = list()
-/datum/controller/subsystem/dcs/Recover()
+/datum/controller/subsystem/processing/dcs/Recover()
comp_lookup = SSdcs.comp_lookup
+
+/datum/controller/subsystem/processing/dcs/proc/GetElement(datum/element/eletype, ...)
+ var/element_id = eletype
+
+ if(initial(eletype.element_flags) & ELEMENT_BESPOKE)
+ var/list/fullid = list("[eletype]")
+ for(var/i in initial(eletype.id_arg_index) to length(args))
+ var/argument = args[i]
+ if(istext(argument) || isnum(argument))
+ fullid += "[argument]"
+ else
+ fullid += "[REF(argument)]"
+ element_id = fullid.Join("&")
+
+ . = elements_by_type[element_id]
+ if(.)
+ return
+ if(!ispath(eletype, /datum/element))
+ CRASH("Attempted to instantiate [eletype] as a /datum/element")
+ . = elements_by_type[element_id] = new eletype
\ No newline at end of file
diff --git a/code/controllers/subsystem/garbage.dm b/code/controllers/subsystem/garbage.dm
index eec9f63a..f8ca1e7e 100644
--- a/code/controllers/subsystem/garbage.dm
+++ b/code/controllers/subsystem/garbage.dm
@@ -98,7 +98,7 @@ SUBSYSTEM_DEF(garbage)
state = SS_RUNNING
break
-
+
/datum/controller/subsystem/garbage/proc/HandleQueue(level = GC_QUEUE_CHECK)
@@ -266,8 +266,8 @@ SUBSYSTEM_DEF(garbage)
D.gc_destroyed = GC_CURRENTLY_BEING_QDELETED
var/start_time = world.time
var/start_tick = world.tick_usage
+ SEND_SIGNAL(D, COMSIG_PARENT_QDELETING, force) // Let the (remaining) components know about the result of Destroy
var/hint = D.Destroy(arglist(args.Copy(2))) // Let our friend know they're about to get fucked up.
- SEND_SIGNAL(D, COMSIG_PARENT_QDELETED, force, hint) // Let the (remaining) components know about the result of Destroy
if(world.time != start_time)
I.slept_destroy++
else
diff --git a/code/datums/action.dm b/code/datums/action.dm
index cd0d31e0..66e837c7 100644
--- a/code/datums/action.dm
+++ b/code/datums/action.dm
@@ -141,6 +141,17 @@
current_button.add_overlay(mutable_appearance(icon_icon, button_icon_state))
current_button.button_icon_state = button_icon_state
+/datum/action/ghost
+ icon_icon = 'icons/mob/mob.dmi'
+ button_icon_state = "ghost"
+ name = "Ghostize"
+ desc = "Turn into a ghost and freely come back to your body."
+
+/datum/action/ghost/Trigger()
+ if(!..())
+ return 0
+ var/mob/M = target
+ M.ghostize(1)
//Presets for item actions
/datum/action/item_action
diff --git a/code/datums/components/earhealing.dm b/code/datums/components/earhealing.dm
deleted file mode 100644
index bd3d5748..00000000
--- a/code/datums/components/earhealing.dm
+++ /dev/null
@@ -1,30 +0,0 @@
-// An item worn in the ear slot with this component will heal your ears each
-// Life() tick, even if normally your ears would be too damaged to heal.
-
-/datum/component/earhealing
- var/mob/living/carbon/wearer
-
-/datum/component/earhealing/Initialize()
- if(!isitem(parent))
- return COMPONENT_INCOMPATIBLE
- RegisterSignal(parent, list(COMSIG_ITEM_EQUIPPED, COMSIG_ITEM_DROPPED), .proc/equippedChanged)
-
-/datum/component/earhealing/proc/equippedChanged(datum/source, mob/living/carbon/user, slot)
- if (slot == SLOT_EARS && istype(user))
- if (!wearer)
- START_PROCESSING(SSobj, src)
- wearer = user
- else
- if (wearer)
- STOP_PROCESSING(SSobj, src)
- wearer = null
-
-/datum/component/earhealing/process()
- if (!wearer)
- STOP_PROCESSING(SSobj, src)
- return
- if(!HAS_TRAIT(wearer, TRAIT_DEAF))
- var/obj/item/organ/ears/ears = wearer.getorganslot(ORGAN_SLOT_EARS)
- if (ears)
- ears.deaf = max(ears.deaf - 1, (ears.damage < ears.maxHealth ? 0 : 1)) // Do not clear deafness if our ears are too damaged
- ears.damage = max(ears.damage - 0.1, 0)
diff --git a/code/datums/components/mood.dm b/code/datums/components/mood.dm
index eb381af5..883dc656 100644
--- a/code/datums/components/mood.dm
+++ b/code/datums/components/mood.dm
@@ -280,7 +280,7 @@
var/datum/hud/hud = owner.hud_used
screen_obj = new
hud.infodisplay += screen_obj
- RegisterSignal(hud, COMSIG_PARENT_QDELETED, .proc/unmodify_hud)
+ RegisterSignal(hud, COMSIG_PARENT_QDELETING, .proc/unmodify_hud)
RegisterSignal(screen_obj, COMSIG_CLICK, .proc/hud_click)
/datum/component/mood/proc/unmodify_hud(datum/source)
diff --git a/code/datums/elements/_element.dm b/code/datums/elements/_element.dm
new file mode 100644
index 00000000..17e2b122
--- /dev/null
+++ b/code/datums/elements/_element.dm
@@ -0,0 +1,39 @@
+/datum/element
+ var/element_flags = NONE
+ /**
+ * The index of the first attach argument to consider for duplicate elements
+ * Is only used when flags contains ELEMENT_BESPOKE
+ * This is infinity so you must explicitly set this
+ */
+ var/id_arg_index = INFINITY
+
+/datum/element/proc/Attach(datum/target)
+ if(type == /datum/element)
+ return ELEMENT_INCOMPATIBLE
+ if(element_flags & ELEMENT_DETACH)
+ RegisterSignal(target, COMSIG_PARENT_QDELETING, .proc/Detach, override = TRUE)
+
+/datum/element/proc/Detach(datum/source, force)
+ UnregisterSignal(source, COMSIG_PARENT_QDELETING)
+
+/datum/element/Destroy(force)
+ if(!force)
+ return QDEL_HINT_LETMELIVE
+ SSdcs.elements_by_type -= type
+ return ..()
+
+//DATUM PROCS
+
+/datum/proc/AddElement(eletype, ...)
+ var/datum/element/ele = SSdcs.GetElement(arglist(args))
+ args[1] = src
+ if(ele.Attach(arglist(args)) == ELEMENT_INCOMPATIBLE)
+ CRASH("Incompatible [eletype] assigned to a [type]! args: [json_encode(args)]")
+
+/**
+ * Finds the singleton for the element type given and detaches it from src
+ * You only need additional arguments beyond the type if you're using ELEMENT_BESPOKE
+ */
+/datum/proc/RemoveElement(eletype, ...)
+ var/datum/element/ele = SSdcs.GetElement(arglist(args))
+ ele.Detach(src)
diff --git a/code/datums/components/cleaning.dm b/code/datums/elements/cleaning.dm
similarity index 64%
rename from code/datums/components/cleaning.dm
rename to code/datums/elements/cleaning.dm
index 05c26efc..02084a88 100644
--- a/code/datums/components/cleaning.dm
+++ b/code/datums/elements/cleaning.dm
@@ -1,19 +1,18 @@
-/datum/component/cleaning
- dupe_mode = COMPONENT_DUPE_UNIQUE_PASSARGS
+/datum/element/cleaning/Attach(datum/target)
+ . = ..()
+ if(!ismovableatom(target))
+ return ELEMENT_INCOMPATIBLE
+ RegisterSignal(target, COMSIG_MOVABLE_MOVED, .proc/Clean)
-/datum/component/cleaning/Initialize()
- if(!ismovableatom(parent))
- return COMPONENT_INCOMPATIBLE
- RegisterSignal(parent, list(COMSIG_MOVABLE_MOVED), .proc/Clean)
+/datum/element/cleaning/Detach(datum/target)
+ . = ..()
+ UnregisterSignal(target, COMSIG_MOVABLE_MOVED)
-/datum/component/cleaning/proc/Clean()
- var/atom/movable/AM = parent
- var/turf/tile = AM.loc
- if(!isturf(tile))
- return
-
- SEND_SIGNAL(tile, COMSIG_COMPONENT_CLEAN_ACT, CLEAN_STRENGTH_BLOOD)
- for(var/A in tile)
+/datum/element/cleaning/proc/Clean(datum/source)
+ var/atom/movable/AM = source
+ var/turf/T = AM.loc
+ SEND_SIGNAL(T, COMSIG_COMPONENT_CLEAN_ACT, CLEAN_WEAK)
+ for(var/A in T)
if(is_cleanable(A))
qdel(A)
else if(istype(A, /obj/item))
@@ -36,4 +35,4 @@
SEND_SIGNAL(cleaned_human, COMSIG_COMPONENT_CLEAN_ACT, CLEAN_STRENGTH_BLOOD)
cleaned_human.wash_cream()
cleaned_human.regenerate_icons()
- to_chat(cleaned_human, "[AM] cleans your face!")
+ to_chat(cleaned_human, "[src] cleans your face!")
diff --git a/code/datums/elements/earhealing.dm b/code/datums/elements/earhealing.dm
new file mode 100644
index 00000000..74777426
--- /dev/null
+++ b/code/datums/elements/earhealing.dm
@@ -0,0 +1,37 @@
+
+/datum/element/earhealing
+ element_flags = ELEMENT_DETACH
+ var/list/user_by_item = list()
+
+/datum/element/earhealing/New()
+ START_PROCESSING(SSdcs, src)
+
+/datum/element/earhealing/Attach(datum/target)
+ . = ..()
+ if(!isitem(target))
+ return ELEMENT_INCOMPATIBLE
+
+ RegisterSignal(target, list(COMSIG_ITEM_EQUIPPED, COMSIG_ITEM_DROPPED), .proc/equippedChanged)
+
+/datum/element/earhealing/Detach(datum/target)
+ . = ..()
+ UnregisterSignal(target, list(COMSIG_ITEM_EQUIPPED, COMSIG_ITEM_DROPPED))
+ user_by_item -= target
+
+/datum/element/earhealing/proc/equippedChanged(datum/source, mob/living/carbon/user, slot)
+ if(slot == SLOT_EARS && istype(user))
+ user_by_item[source] = user
+ else
+ user_by_item -= source
+
+/datum/element/earhealing/process()
+ for(var/i in user_by_item)
+ var/mob/living/carbon/user = user_by_item[i]
+ if(HAS_TRAIT(user, TRAIT_DEAF))
+ continue
+ var/obj/item/organ/ears/ears = user.getorganslot(ORGAN_SLOT_EARS)
+ if(!ears)
+ continue
+ ears.deaf = max(ears.deaf - 0.25, (ears.damage < ears.maxHealth ? 0 : 1)) // Do not clear deafness if our ears are too damaged
+ ears.damage = max(ears.damage - 0.025, 0)
+ CHECK_TICK
diff --git a/code/datums/elements/ghost_role_eligibility.dm b/code/datums/elements/ghost_role_eligibility.dm
new file mode 100644
index 00000000..e57aaddd
--- /dev/null
+++ b/code/datums/elements/ghost_role_eligibility.dm
@@ -0,0 +1,56 @@
+/datum/element/ghost_role_eligibility
+ element_flags = ELEMENT_DETACH
+ var/list/timeouts = list()
+ var/list/mob/eligible_mobs = list()
+
+/datum/element/ghost_role_eligibility/Attach(datum/target,penalize = FALSE)
+ . = ..()
+ if(!ismob(target))
+ return ELEMENT_INCOMPATIBLE
+ var/mob/M = target
+ if(!(M in eligible_mobs))
+ eligible_mobs += M
+ if(penalize) //penalizing them from making a ghost role / midround antag comeback right away.
+ var/penalty = CONFIG_GET(number/suicide_reenter_round_timer) MINUTES
+ var/roundstart_quit_limit = CONFIG_GET(number/roundstart_suicide_time_limit) MINUTES
+ if(world.time < roundstart_quit_limit) //add up the time difference to their antag rolling penalty if they quit before half a (ingame) hour even passed.
+ penalty += roundstart_quit_limit - world.time
+ if(penalty)
+ penalty += world.realtime
+ if(penalty - SSshuttle.realtimeofstart > SSshuttle.auto_call + SSshuttle.emergencyCallTime + SSshuttle.emergencyDockTime + SSshuttle.emergencyEscapeTime)
+ penalty = CANT_REENTER_ROUND
+ if(!(M.ckey in timeouts))
+ timeouts += M.ckey
+ timeouts[M.ckey] = 0
+ else if(timeouts[M.ckey] == CANT_REENTER_ROUND)
+ return
+ timeouts[M.ckey] = max(timeouts[M.ckey],penalty)
+
+/datum/element/ghost_role_eligibility/Detach(mob/M)
+ . = ..()
+ if(M in eligible_mobs)
+ eligible_mobs -= M
+
+/datum/element/ghost_role_eligibility/proc/get_all_ghost_role_eligible(silent = FALSE)
+ var/list/candidates = list()
+ for(var/m in eligible_mobs)
+ var/mob/M = m
+ if(M.can_reenter_round(TRUE))
+ candidates += M
+ return candidates
+
+/mob/proc/can_reenter_round(silent = FALSE)
+ var/datum/element/ghost_role_eligibility/eli = SSdcs.GetElement(/datum/element/ghost_role_eligibility)
+ return eli.can_reenter_round(src,silent)
+
+/datum/element/ghost_role_eligibility/proc/can_reenter_round(var/mob/M,silent = FALSE)
+ if(!(M in eligible_mobs))
+ return FALSE
+ if(!(M.ckey in timeouts))
+ return TRUE
+ var/timeout = timeouts[M.ckey]
+ if(timeout != CANT_REENTER_ROUND && timeout <= world.realtime)
+ return TRUE
+ if(!silent && M.client)
+ to_chat(M, "You are unable to reenter the round[timeout != CANT_REENTER_ROUND ? " yet. Your ghost role blacklist will expire in [DisplayTimeText(timeout - world.realtime)]" : ""].")
+ return FALSE
diff --git a/code/datums/elements/mob_holder.dm b/code/datums/elements/mob_holder.dm
new file mode 100644
index 00000000..9b9bc6f9
--- /dev/null
+++ b/code/datums/elements/mob_holder.dm
@@ -0,0 +1,187 @@
+/datum/element/mob_holder
+ element_flags = ELEMENT_BESPOKE|ELEMENT_DETACH
+ id_arg_index = 2
+ var/worn_state
+ var/alt_worn
+ var/right_hand
+ var/left_hand
+ var/inv_slots
+ var/proctype //if present, will be invoked on headwear generation.
+
+/datum/element/mob_holder/Attach(datum/target, _worn_state, _alt_worn, _right_hand, _left_hand, _inv_slots = NONE, _proctype)
+ . = ..()
+
+ if(!isliving(target))
+ return ELEMENT_INCOMPATIBLE
+
+ worn_state = _worn_state
+ alt_worn = _alt_worn
+ right_hand = _right_hand
+ left_hand = _left_hand
+ inv_slots = _inv_slots
+ proctype = _proctype
+
+ RegisterSignal(target, COMSIG_CLICK_ALT, .proc/mob_try_pickup)
+ RegisterSignal(target, COMSIG_PARENT_EXAMINE, .proc/on_examine)
+
+/datum/element/mob_holder/Detach(datum/source, force)
+ . = ..()
+ UnregisterSignal(source, COMSIG_CLICK_ALT)
+ UnregisterSignal(source, COMSIG_PARENT_EXAMINE)
+
+/datum/element/mob_holder/proc/on_examine(mob/living/source, mob/user, list/examine_list)
+ if(ishuman(user) && !istype(source.loc, /obj/item/clothing/head/mob_holder))
+ examine_list += "Looks like [source.p_they(TRUE)] can be picked up with Alt+Click!"
+
+/datum/element/mob_holder/proc/mob_try_pickup(mob/living/source, mob/user)
+ if(!ishuman(user) || !user.Adjacent(source) || user.incapacitated())
+ return FALSE
+ if(user.get_active_held_item())
+ to_chat(user, "Your hands are full!")
+ return FALSE
+ if(source.buckled)
+ to_chat(user, "[source] is buckled to something!")
+ return FALSE
+ if(source == user)
+ to_chat(user, "You can't pick yourself up.")
+ return FALSE
+ source.visible_message("[user] starts picking up [source].", \
+ "[user] starts picking you up!")
+ if(!do_after(user, 20, target = source) || source.buckled)
+ return FALSE
+
+ source.visible_message("[user] picks up [source]!", \
+ "[user] picks you up!")
+ to_chat(user, "You pick [source] up.")
+ source.drop_all_held_items()
+ var/obj/item/clothing/head/mob_holder/holder = new(get_turf(source), source, worn_state, alt_worn, right_hand, left_hand, inv_slots)
+ if(proctype)
+ INVOKE_ASYNC(src, proctype, source, holder, user)
+ user.put_in_hands(holder)
+ return TRUE
+
+/datum/element/mob_holder/proc/drone_worn_icon(mob/living/simple_animal/drone/D, obj/item/clothing/head/mob_holder/holder, mob/user)
+ var/new_state = "[D.visualAppearence]_hat"
+ holder.item_state = new_state
+ holder.icon_state = new_state
+
+
+//The item itself,
+/obj/item/clothing/head/mob_holder
+ name = "bugged mob"
+ desc = "Yell at coderbrush."
+ icon = null
+ alternate_worn_icon = 'icons/mob/animals_held.dmi'
+ righthand_file = 'icons/mob/animals_held_rh.dmi'
+ lefthand_file = 'icons/mob/animals_held_lh.dmi'
+ icon_state = ""
+ w_class = WEIGHT_CLASS_BULKY
+ var/mob/living/held_mob
+ var/can_head = FALSE
+
+/obj/item/clothing/head/mob_holder/Initialize(mapload, mob/living/target, worn_state, alt_worn, right_hand, left_hand, slots = NONE)
+ . = ..()
+
+ if(target)
+ assimilate(target)
+
+ if(alt_worn)
+ alternate_worn_icon = alt_worn
+ if(worn_state)
+ item_state = worn_state
+ icon_state = worn_state
+ if(left_hand)
+ lefthand_file = left_hand
+ if(right_hand)
+ righthand_file = right_hand
+ slot_flags = slots
+
+/obj/item/clothing/head/mob_holder/proc/assimilate(mob/living/target)
+ target.setDir(SOUTH)
+ held_mob = target
+ target.forceMove(src)
+ var/image/I = new //work around to retain the same appearance to the mob idependently from inhands/worn states.
+ I.appearance = target.appearance
+ I.override = TRUE
+ add_overlay(I)
+ name = target.name
+ desc = target.desc
+ switch(target.mob_size)
+ if(MOB_SIZE_TINY)
+ w_class = WEIGHT_CLASS_TINY
+ if(MOB_SIZE_SMALL)
+ w_class = WEIGHT_CLASS_NORMAL
+ if(MOB_SIZE_HUMAN)
+ w_class = WEIGHT_CLASS_BULKY
+ if(MOB_SIZE_LARGE)
+ w_class = WEIGHT_CLASS_HUGE
+ RegisterSignal(src, COMSIG_CLICK_SHIFT, .proc/examine_held_mob, override = TRUE)
+
+/obj/item/clothing/head/mob_holder/Destroy()
+ if(held_mob)
+ release()
+ return ..()
+
+/obj/item/clothing/head/mob_holder/proc/examine_held_mob(datum/source, mob/user)
+ held_mob.ShiftClick(user)
+ return COMPONENT_DENY_EXAMINATE
+
+/obj/item/clothing/head/mob_holder/Exited(atom/movable/AM, atom/newloc)
+ . = ..()
+ if(AM == held_mob)
+ held_mob.reset_perspective()
+ held_mob = null
+ qdel(src)
+
+/obj/item/clothing/head/mob_holder/Entered(atom/movable/AM, atom/newloc)
+ . = ..()
+ if(AM != held_mob)
+ var/destination = loc
+ if(isliving(loc)) //the mob is held or worn, drop things on the floor
+ destination = get_turf(loc)
+ AM.forceMove(destination)
+
+/obj/item/clothing/head/mob_holder/dropped()
+ . = ..()
+ if(held_mob && isturf(loc))//don't release on soft-drops
+ release()
+
+/obj/item/clothing/head/mob_holder/proc/release()
+ if(held_mob)
+ var/mob/living/L = held_mob
+ held_mob = null
+ L.forceMove(get_turf(L))
+ L.reset_perspective()
+ L.setDir(SOUTH)
+ qdel(src)
+
+/obj/item/clothing/head/mob_holder/relaymove(mob/user)
+ return
+
+/obj/item/clothing/head/mob_holder/container_resist()
+ if(isliving(loc))
+ var/mob/living/L = loc
+ L.visible_message("[src] escapes from [L]!", "[src] escapes your grip!")
+ release()
+
+/obj/item/clothing/head/mob_holder/assume_air(datum/gas_mixture/env)
+ var/atom/location = loc
+ if(!loc)
+ return //null
+ var/turf/T = get_turf(loc)
+ while(location != T)
+ location = location.loc
+ if(ismob(location))
+ return location.loc.assume_air(env)
+ return loc.assume_air(env)
+
+/obj/item/clothing/head/mob_holder/remove_air(amount)
+ var/atom/location = loc
+ if(!loc)
+ return //null
+ var/turf/T = get_turf(loc)
+ while(location != T)
+ location = location.loc
+ if(ismob(location))
+ return location.loc.remove_air(amount)
+ return loc.remove_air(amount)
diff --git a/code/datums/elements/wuv.dm b/code/datums/elements/wuv.dm
new file mode 100644
index 00000000..55bc9baf
--- /dev/null
+++ b/code/datums/elements/wuv.dm
@@ -0,0 +1,64 @@
+
+/datum/element/wuv //D'awwwww
+ element_flags = ELEMENT_BESPOKE
+ id_arg_index = 2
+ //the for the me emote proc call when petted.
+ var/pet_emote
+ //whether the emote is visible or audible
+ var/pet_type
+ //same as above, except when harmed. "You are going into orbit, you stupid mutt!"
+ var/punt_emote
+ //same as pet_type
+ var/punt_type
+ //mood typepath for the moodlet signal when petted.
+ var/pet_moodlet
+ //same as above but for harm
+ var/punt_moodlet
+
+/datum/element/wuv/Attach(datum/target, pet, pet_t, pet_mood, punt, punt_t, punt_mood)
+ . = ..()
+
+ if(!isliving(target))
+ return ELEMENT_INCOMPATIBLE
+
+ pet_emote = pet
+ pet_type = pet_t
+ punt_emote = punt
+ punt_type = punt_t
+ pet_moodlet = pet_mood
+ punt_moodlet = punt_mood
+
+ RegisterSignal(target, COMSIG_MOB_ATTACK_HAND, .proc/on_attack_hand)
+
+/datum/element/wuv/Detach(datum/source, force)
+ . = ..()
+ UnregisterSignal(source, COMSIG_MOB_ATTACK_HAND)
+
+/datum/element/wuv/proc/on_attack_hand(datum/source, mob/user)
+ var/mob/living/L = source
+
+ if(L.stat == DEAD)
+ return
+ //we want to delay the effect to be displayed after the mob is petted, not before.
+ switch(user.a_intent)
+ if(INTENT_HARM, INTENT_DISARM)
+ addtimer(CALLBACK(src, .proc/kick_the_dog, source, user), 1)
+ if(INTENT_HELP)
+ addtimer(CALLBACK(src, .proc/pet_the_dog, source, user), 1)
+
+/datum/element/wuv/proc/pet_the_dog(mob/target, mob/user)
+ if(QDELETED(target) || QDELETED(user) || target.stat != CONSCIOUS)
+ return
+ new /obj/effect/temp_visual/heart(target.loc)
+ if(pet_emote)
+ target.emote("me", pet_type, pet_emote)
+ if(pet_moodlet && !CHECK_BITFIELD(target.flags_1, HOLOGRAM_1)) //prevents unlimited happiness petting park exploit.
+ SEND_SIGNAL(user, COMSIG_ADD_MOOD_EVENT, target, pet_moodlet, target)
+
+/datum/element/wuv/proc/kick_the_dog(mob/target, mob/user)
+ if(QDELETED(target) || QDELETED(user) || target.stat != CONSCIOUS)
+ return
+ if(punt_emote)
+ target.emote("me", punt_type, punt_emote)
+ if(punt_moodlet && !CHECK_BITFIELD(target.flags_1, HOLOGRAM_1))
+ SEND_SIGNAL(user, COMSIG_ADD_MOOD_EVENT, target, punt_moodlet, target)
diff --git a/code/datums/helper_datums/teleport.dm b/code/datums/helper_datums/teleport.dm
index d60f8127..1c2ce851 100644
--- a/code/datums/helper_datums/teleport.dm
+++ b/code/datums/helper_datums/teleport.dm
@@ -79,6 +79,7 @@
tele_play_specials(teleatom, destturf, effectout, asoundout)
if(ismegafauna(teleatom))
message_admins("[teleatom] [ADMIN_FLW(teleatom)] has teleported from [ADMIN_VERBOSEJMP(curturf)] to [ADMIN_VERBOSEJMP(destturf)].")
+ SEND_SIGNAL(teleatom, COMSIG_MOVABLE_TELEPORTED, channel, curturf, destturf)
if(ismob(teleatom))
var/mob/M = teleatom
diff --git a/code/datums/martial.dm b/code/datums/martial.dm
index d119759e..39070c5e 100644
--- a/code/datums/martial.dm
+++ b/code/datums/martial.dm
@@ -10,6 +10,7 @@
var/block_chance = 0 //Chance to block melee attacks using items while on throw mode.
var/restraining = 0 //used in cqc's disarm_act to check if the disarmed is being restrained and so whether they should be put in a chokehold or not
var/help_verb
+ var/pacifism_check = TRUE //are the martial arts combos/attacks unable to be used by pacifist.
var/no_guns = FALSE
var/allow_temp_override = TRUE //if this martial art can be overridden by temporary martial arts
diff --git a/code/datums/martial/boxing.dm b/code/datums/martial/boxing.dm
index 7399528e..502d3ada 100644
--- a/code/datums/martial/boxing.dm
+++ b/code/datums/martial/boxing.dm
@@ -1,6 +1,8 @@
/datum/martial_art/boxing
name = "Boxing"
id = MARTIALART_BOXING
+ pacifism_check = FALSE //Let's pretend pacifists can boxe the heck out of other people, it only deals stamina damage right now.
+
/datum/martial_art/boxing/disarm_act(mob/living/carbon/human/A, mob/living/carbon/human/D)
to_chat(A, "Can't disarm while boxing!")
diff --git a/code/datums/martial/psychotic_brawl.dm b/code/datums/martial/psychotic_brawl.dm
index 34301516..00fb0524 100644
--- a/code/datums/martial/psychotic_brawl.dm
+++ b/code/datums/martial/psychotic_brawl.dm
@@ -1,6 +1,7 @@
/datum/martial_art/psychotic_brawling
name = "Psychotic Brawling"
id = MARTIALART_PSYCHOBRAWL
+ pacifism_check = FALSE //Quite uncontrollable and unpredictable, people will still end up harming others with it.
/datum/martial_art/psychotic_brawling/disarm_act(mob/living/carbon/human/A, mob/living/carbon/human/D)
return psycho_attack(A,D)
@@ -64,4 +65,4 @@
if(atk_verb)
log_combat(A, D, "[atk_verb] (Psychotic Brawling)")
- return 1
\ No newline at end of file
+ return 1
diff --git a/code/datums/mood_events/generic_positive_events.dm b/code/datums/mood_events/generic_positive_events.dm
index 922c5a6e..8504f88b 100644
--- a/code/datums/mood_events/generic_positive_events.dm
+++ b/code/datums/mood_events/generic_positive_events.dm
@@ -23,10 +23,13 @@
mood_change = 3
timeout = 3000
-/datum/mood_event/pet_corgi
- description = "Corgis are adorable! I can't stop petting them!\n"
- mood_change = 3
- timeout = 3000
+/datum/mood_event/pet_animal
+ description = "Animals are adorable! I can't stop petting them!\n"
+ mood_change = 2
+ timeout = 5 MINUTES
+
+/datum/mood_event/pet_animal/add_effects(mob/animal)
+ description = "\The [animal.name] is adorable! I can't stop petting [animal.p_them()]!\n"
/datum/mood_event/honk
description = "Maybe clowns aren't so bad after all. Honk!\n"
diff --git a/code/game/atoms.dm b/code/game/atoms.dm
index 761553b9..23124b94 100644
--- a/code/game/atoms.dm
+++ b/code/game/atoms.dm
@@ -165,7 +165,7 @@
return FALSE
-/atom/proc/attack_hulk(mob/living/carbon/human/user, does_attack_animation = 0)
+/atom/proc/attack_hulk(mob/living/carbon/human/user, does_attack_animation = FALSE)
SEND_SIGNAL(src, COMSIG_ATOM_HULK_ATTACK, user)
if(does_attack_animation)
user.changeNext_move(CLICK_CD_MELEE)
diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm b/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm
index 25e26856..7bcd6c8a 100644
--- a/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm
+++ b/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm
@@ -17,6 +17,7 @@
var/list/living_antags = list()
var/list/dead_players = list()
var/list/list_observers = list()
+ var/list/ghost_eligible = list()
/datum/dynamic_ruleset/midround/from_ghosts
weight = 0
@@ -32,10 +33,11 @@
// So for example you can get the list of all current dead players with var/list/dead_players = candidates[CURRENT_DEAD_PLAYERS]
// Make sure to properly typecheck the mobs in those lists, as the dead_players list could contain ghosts, or dead players still in their bodies.
// We're still gonna trim the obvious (mobs without clients, jobbanned players, etc)
- living_players = trim_list(mode.current_players[CURRENT_LIVING_PLAYERS])
- living_antags = trim_list(mode.current_players[CURRENT_LIVING_ANTAGS])
- dead_players = trim_list(mode.current_players[CURRENT_DEAD_PLAYERS])
- list_observers = trim_list(mode.current_players[CURRENT_OBSERVERS])
+ living_players = trim_list(mode.current_players[CURRENT_LIVING_PLAYERS])
+ living_antags = trim_list(mode.current_players[CURRENT_LIVING_ANTAGS])
+ list_observers = trim_list(mode.current_players[CURRENT_OBSERVERS])
+ var/datum/element/ghost_role_eligibility/eligibility = SSdcs.GetElement(/datum/element/ghost_role_eligibility)
+ ghost_eligible = trim_list(eligibility.get_all_ghost_role_eligible())
/datum/dynamic_ruleset/midround/proc/trim_list(list/L = list())
var/list/trimmed_list = L.Copy()
@@ -65,6 +67,25 @@
continue
return trimmed_list
+/datum/dynamic_ruleset/midround/from_ghosts/trim_list(list/L = list())
+ var/list/trimmed_list = L.Copy()
+ for(var/mob/M in trimmed_list)
+ if (!M.client) // Are they connected?
+ trimmed_list.Remove(M)
+ continue
+ if(!mode.check_age(M.client, minimum_required_age))
+ trimmed_list.Remove(M)
+ continue
+ if(antag_flag_override)
+ if(!(antag_flag_override in M.client.prefs.be_special) || jobban_isbanned(M.ckey, antag_flag_override))
+ trimmed_list.Remove(M)
+ continue
+ else
+ if(!(antag_flag in M.client.prefs.be_special) || jobban_isbanned(M.ckey, antag_flag))
+ trimmed_list.Remove(M)
+ continue
+ return trimmed_list
+
// You can then for example prompt dead players in execute() to join as strike teams or whatever
// Or autotator someone
@@ -85,15 +106,16 @@
return FALSE
return TRUE
-/datum/dynamic_ruleset/midround/from_ghosts/execute()
- var/list/possible_candidates = list()
- possible_candidates.Add(dead_players)
- possible_candidates.Add(list_observers)
- send_applications(possible_candidates)
- if(assigned.len > 0)
- return TRUE
- else
+/datum/dynamic_ruleset/midround/from_ghosts/ready(forced = FALSE)
+ if (required_candidates > ghost_eligible.len)
+ SSblackbox.record_feedback("tally","dynamic",1,"Times rulesets rejected due to not enough ghosts")
return FALSE
+ return ..()
+
+
+/datum/dynamic_ruleset/midround/from_ghosts/execute()
+ var/application_successful = send_applications(ghost_eligible)
+ return assigned.len > 0 && application_successful
/// This sends a poll to ghosts if they want to be a ghost spawn from a ruleset.
/datum/dynamic_ruleset/midround/from_ghosts/proc/send_applications(list/possible_volunteers = list())
@@ -596,4 +618,4 @@
#undef ABDUCTOR_MAX_TEAMS
-#undef REVENANT_SPAWN_THRESHOLD
\ No newline at end of file
+#undef REVENANT_SPAWN_THRESHOLD
diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm
index eec9ca8b..e8935cf5 100644
--- a/code/game/objects/items.dm
+++ b/code/game/objects/items.dm
@@ -619,6 +619,12 @@ GLOBAL_VAR_INIT(rpg_loot_items, FALSE)
/obj/item/proc/is_sharp()
return sharpness
+/obj/item/proc/get_temperature()
+ return heat
+
+/obj/item/proc/get_sharpness()
+ return sharpness
+
/obj/item/proc/get_dismemberment_chance(obj/item/bodypart/affecting)
if(affecting.can_dismember(src))
if((sharpness || damtype == BURN) && w_class >= WEIGHT_CLASS_NORMAL && force >= 10)
diff --git a/code/game/objects/items/holy_weapons.dm b/code/game/objects/items/holy_weapons.dm
index 040cd53c..01b20d42 100644
--- a/code/game/objects/items/holy_weapons.dm
+++ b/code/game/objects/items/holy_weapons.dm
@@ -490,10 +490,10 @@
possessed = TRUE
- var/list/mob/dead/observer/candidates = pollGhostCandidates("Do you want to play as the spirit of [user.real_name]'s blade?", ROLE_PAI, null, FALSE, 100, POLL_IGNORE_POSSESSED_BLADE)
+ var/list/mob/candidates = pollGhostCandidates("Do you want to play as the spirit of [user.real_name]'s blade?", ROLE_PAI, null, FALSE, 100, POLL_IGNORE_POSSESSED_BLADE)
if(LAZYLEN(candidates))
- var/mob/dead/observer/C = pick(candidates)
+ var/mob/C = pick(candidates)
var/mob/living/simple_animal/shade/S = new(src)
S.real_name = name
S.name = name
diff --git a/code/game/objects/items/melee/misc.dm b/code/game/objects/items/melee/misc.dm
index cdb9c146..160c4fa6 100644
--- a/code/game/objects/items/melee/misc.dm
+++ b/code/game/objects/items/melee/misc.dm
@@ -212,11 +212,12 @@
if(!iscyborg(target))
return
else
- if(cooldown <= world.time)
+ if(cooldown < world.time)
+ if(target.check_shields(src, 0, "[user]'s [name]", MELEE_ATTACK))
+ playsound(target, 'sound/weapons/genhit.ogg', 50, 1)
+ return
if(ishuman(target))
var/mob/living/carbon/human/H = target
- if (H.check_shields(src, 0, "[user]'s [name]", MELEE_ATTACK))
- return
if(check_martial_counter(H, user))
return
playsound(get_turf(src), 'sound/effects/woodhit.ogg', 75, 1, -1)
diff --git a/code/game/objects/items/robot/robot_items.dm b/code/game/objects/items/robot/robot_items.dm
index 2a8f0806..5d8ccc02 100644
--- a/code/game/objects/items/robot/robot_items.dm
+++ b/code/game/objects/items/robot/robot_items.dm
@@ -11,11 +11,9 @@
var/charge_cost = 30
/obj/item/borg/stun/attack(mob/living/M, mob/living/user)
- if(ishuman(M))
- var/mob/living/carbon/human/H = M
- if(H.check_shields(src, 0, "[M]'s [name]", MELEE_ATTACK))
- playsound(M, 'sound/weapons/genhit.ogg', 50, 1)
- return FALSE
+ if(M.check_shields(src, 0, "[M]'s [name]", MELEE_ATTACK))
+ playsound(M, 'sound/weapons/genhit.ogg', 50, 1)
+ return FALSE
if(iscyborg(user))
var/mob/living/silicon/robot/R = user
if(!R.cell.use(charge_cost))
@@ -944,4 +942,4 @@
/obj/item/borg/apparatus/circuit/pre_attack(atom/A, mob/living/user, params)
. = ..()
if(istype(A, /obj/item/aiModule) && !stored) //If an admin wants a borg to upload laws, who am I to stop them? Otherwise, we can hint that it fails
- to_chat(user, "This circuit board doesn't seem to have standard robot apparatus pin holes. You're unable to pick it up.")
\ No newline at end of file
+ to_chat(user, "This circuit board doesn't seem to have standard robot apparatus pin holes. You're unable to pick it up.")
diff --git a/code/game/objects/items/stunbaton.dm b/code/game/objects/items/stunbaton.dm
index 4a6c15f7..5d86947d 100644
--- a/code/game/objects/items/stunbaton.dm
+++ b/code/game/objects/items/stunbaton.dm
@@ -167,11 +167,9 @@
/obj/item/melee/baton/proc/baton_stun(mob/living/L, mob/user)
- if(ishuman(L))
- var/mob/living/carbon/human/H = L
- if(H.check_shields(src, 0, "[user]'s [name]", MELEE_ATTACK)) //No message; check_shields() handles that
- playsound(L, 'sound/weapons/genhit.ogg', 50, 1)
- return FALSE
+ if(L.check_shields(src, 0, "[user]'s [name]", MELEE_ATTACK)) //No message; check_shields() handles that
+ playsound(L, 'sound/weapons/genhit.ogg', 50, 1)
+ return FALSE
var/stunpwr = stunforce
var/obj/item/stock_parts/cell/our_cell = get_cell()
if(!our_cell)
@@ -250,4 +248,4 @@
sparkler?.activate()
. = ..()
-#undef STUNBATON_CHARGE_LENIENCY
\ No newline at end of file
+#undef STUNBATON_CHARGE_LENIENCY
diff --git a/code/modules/admin/admin.dm b/code/modules/admin/admin.dm
index 3d9c9bcd..8c684ecc 100644
--- a/code/modules/admin/admin.dm
+++ b/code/modules/admin/admin.dm
@@ -190,7 +190,6 @@
body += "Shade"
body += "
"
- if (M.client)
body += "
"
body += "Other actions:"
body += "
"
@@ -199,9 +198,9 @@
body += "Thunderdome 2 | "
body += "Thunderdome Admin | "
body += "Thunderdome Observer | "
-
- body += usr.client.citaPPoptions(M) // CITADEL
-
+ body += "Make mentor | "
+ body += "Remove mentor"
+ body += "Allow reentering round"
body += "
"
body += "