diff --git a/code/__DEFINES/martial_arts.dm b/code/__DEFINES/martial_arts.dm
new file mode 100644
index 00000000000..4eb139cc44a
--- /dev/null
+++ b/code/__DEFINES/martial_arts.dm
@@ -0,0 +1,16 @@
+#define MARTIAL_COMBO_FAIL 0 // If the combo failed
+#define MARTIAL_COMBO_CONTINUE 1 // If the combo should continue
+#define MARTIAL_COMBO_DONE 2 // If the combo is successful and done
+#define MARTIAL_COMBO_DONE_NO_CLEAR 3 // If the combo is successful and done but the others should have a chance to finish
+#define MARTIAL_COMBO_DONE_BASIC_HIT 4 // If the combo should do a basic hit after it's done
+#define MARTIAL_COMBO_DONE_CLEAR_COMBOS 5 // If the combo should do a basic hit after it's done
+
+#define MARTIAL_ARTS_CANNOT_USE -1
+
+#define MARTIAL_COMBO_STEP_HARM "Harm"
+#define MARTIAL_COMBO_STEP_DISARM "Disarm"
+#define MARTIAL_COMBO_STEP_GRAB "Grab"
+#define MARTIAL_COMBO_STEP_HELP "Help"
+
+// A check used for all act types. Such as disarm_act
+#define MARTIAL_ARTS_ACT_CHECK if((. = ..()) != FALSE) return .
diff --git a/code/datums/cache/crew.dm b/code/datums/cache/crew.dm
index f92bab9cf7a..84308dd4262 100644
--- a/code/datums/cache/crew.dm
+++ b/code/datums/cache/crew.dm
@@ -1,5 +1,8 @@
GLOBAL_DATUM_INIT(crew_repository, /datum/repository/crew, new())
+/datum/repository/crew
+ var/static/list/bold_jobs
+
/datum/repository/crew/New()
cache_data = list()
..()
@@ -18,6 +21,13 @@ GLOBAL_DATUM_INIT(crew_repository, /datum/repository/crew, new())
if(world.time < cache_entry.timestamp)
return cache_entry.data
+ // Initialize the jobs here because in New(), GLOB.command_positions may not be inited yet
+ if(!bold_jobs)
+ bold_jobs = list()
+ bold_jobs += GLOB.command_positions
+ bold_jobs += get_all_centcom_jobs()
+ bold_jobs += list("Nanotrasen Representative", "Blueshield", "Magistrate")
+
for(var/thing in GLOB.human_list)
var/mob/living/carbon/human/H = thing
var/obj/item/clothing/under/C = H.w_uniform
@@ -32,11 +42,13 @@ GLOBAL_DATUM_INIT(crew_repository, /datum/repository/crew, new())
crewmemberData["name"] = H.get_authentification_name(if_no_id="Unknown")
crewmemberData["rank"] = H.get_authentification_rank(if_no_id="Unknown", if_no_job="No Job")
crewmemberData["assignment"] = H.get_assignment(if_no_id="Unknown", if_no_job="No Job")
+ crewmemberData["is_command"] = (crewmemberData["assignment"] in bold_jobs)
if(C.sensor_mode >= SUIT_SENSOR_BINARY)
- crewmemberData["dead"] = H.stat > UNCONSCIOUS
+ crewmemberData["dead"] = H.stat == DEAD
if(C.sensor_mode >= SUIT_SENSOR_VITAL)
+ crewmemberData["stat"] = H.stat
crewmemberData["oxy"] = round(H.getOxyLoss(), 1)
crewmemberData["tox"] = round(H.getToxLoss(), 1)
crewmemberData["fire"] = round(H.getFireLoss(), 1)
diff --git a/code/datums/mind.dm b/code/datums/mind.dm
index 8fa5b909cda..53230d1f83d 100644
--- a/code/datums/mind.dm
+++ b/code/datums/mind.dm
@@ -35,6 +35,7 @@
var/list/restricted_roles = list()
var/list/spell_list = list() // Wizard mode & "Give Spell" badmin button.
+ var/datum/martial_art/martial_art
var/role_alt_title
@@ -1102,8 +1103,8 @@
special_role = null
to_chat(current,"Your infernal link has been severed! You are no longer a devil!")
RemoveSpell(/obj/effect/proc_holder/spell/targeted/infernal_jaunt)
- RemoveSpell(/obj/effect/proc_holder/spell/fireball/hellish)
- RemoveSpell(/obj/effect/proc_holder/spell/targeted/summon_contract)
+ RemoveSpell(/obj/effect/proc_holder/spell/targeted/click/fireball/hellish)
+ RemoveSpell(/obj/effect/proc_holder/spell/targeted/click/summon_contract)
RemoveSpell(/obj/effect/proc_holder/spell/targeted/conjure_item/pitchfork)
RemoveSpell(/obj/effect/proc_holder/spell/targeted/conjure_item/pitchfork/greater)
RemoveSpell(/obj/effect/proc_holder/spell/targeted/conjure_item/pitchfork/ascended)
diff --git a/code/datums/outfits/outfit_admin.dm b/code/datums/outfits/outfit_admin.dm
index 2b48d5da5f7..d5be5f50f35 100644
--- a/code/datums/outfits/outfit_admin.dm
+++ b/code/datums/outfits/outfit_admin.dm
@@ -242,7 +242,7 @@
/obj/item/organ/internal/cyberimp/eyes/shield,
/obj/item/organ/internal/cyberimp/eyes/hud/security,
/obj/item/organ/internal/cyberimp/eyes/xray,
- /obj/item/organ/internal/cyberimp/brain/anti_stun,
+ /obj/item/organ/internal/cyberimp/brain/anti_stun/hardened,
/obj/item/organ/internal/cyberimp/chest/nutriment/plus,
/obj/item/organ/internal/cyberimp/arm/combat/centcom
)
diff --git a/code/datums/spell.dm b/code/datums/spell.dm
index 94779ed67fb..abb9fdc37c9 100644
--- a/code/datums/spell.dm
+++ b/code/datums/spell.dm
@@ -24,6 +24,20 @@ GLOBAL_LIST_INIT(spells, typesof(/obj/effect/proc_holder/spell))
user.face_atom(A)
return FALSE
+/datum/click_intercept/proc_holder
+ var/obj/effect/proc_holder/spell
+
+/datum/click_intercept/proc_holder/New(client/C, obj/effect/proc_holder/spell_to_cast)
+ . = ..()
+ spell = spell_to_cast
+
+/datum/click_intercept/proc_holder/InterceptClickOn(user, params, atom/object)
+ spell.InterceptClickOn(user, params, object)
+
+/datum/click_intercept/proc_holder/quit()
+ spell.remove_ranged_ability(spell.ranged_ability_user)
+ return ..()
+
/obj/effect/proc_holder/proc/add_ranged_ability(mob/living/user, var/msg)
if(!user || !user.client)
return
@@ -32,7 +46,7 @@ GLOBAL_LIST_INIT(spells, typesof(/obj/effect/proc_holder/spell))
user.ranged_ability.remove_ranged_ability(user)
user.ranged_ability = src
ranged_ability_user = user
- user.client.click_intercept = user.ranged_ability
+ user.client.click_intercept = new /datum/click_intercept/proc_holder(user.client, user.ranged_ability)
add_mousepointer(user.client)
active = TRUE
if(msg)
@@ -48,15 +62,17 @@ GLOBAL_LIST_INIT(spells, typesof(/obj/effect/proc_holder/spell))
C.mouse_pointer_icon = initial(C.mouse_pointer_icon)
/obj/effect/proc_holder/proc/remove_ranged_ability(mob/living/user, var/msg)
- if(!user || !user.client || (user.ranged_ability && user.ranged_ability != src)) //To avoid removing the wrong ability
+ if(!user || (user.ranged_ability && user.ranged_ability != src)) //To avoid removing the wrong ability
return
user.ranged_ability = null
ranged_ability_user = null
- user.client.click_intercept = null
- remove_mousepointer(user.client)
active = FALSE
- if(msg)
- to_chat(user, msg)
+ if(user.client)
+ qdel(user.client.click_intercept)
+ user.client.click_intercept = null
+ remove_mousepointer(user.client)
+ if(msg)
+ to_chat(user, msg)
update_icon()
/obj/effect/proc_holder/spell
@@ -114,10 +130,14 @@ GLOBAL_LIST_INIT(spells, typesof(/obj/effect/proc_holder/spell))
var/sound = null //The sound the spell makes when it is cast
-/obj/effect/proc_holder/spell/proc/cast_check(skipcharge = 0, mob/living/user = usr) //checks if the spell can be cast based on its settings; skipcharge is used when an additional cast_check is called inside the spell
- if(((!user.mind) || !(src in user.mind.spell_list)) && !(src in user.mob_spell_list))
- to_chat(user, "You shouldn't have this spell! Something's wrong.")
- return 0
+/* Checks if the user can cast the spell
+ * @param charge_check If the proc should do the cooldown check
+ * @param start_recharge If the proc should set the cooldown
+ * @param user The caster of the spell
+*/
+/obj/effect/proc_holder/spell/proc/cast_check(charge_check = TRUE, start_recharge = TRUE, mob/living/user = usr) //checks if the spell can be cast based on its settings; skipcharge is used when an additional cast_check is called inside the spell
+ if(!can_cast(user, charge_check, TRUE))
+ return FALSE
if(ishuman(user))
var/mob/living/carbon/human/caster = user
@@ -126,49 +146,7 @@ GLOBAL_LIST_INIT(spells, typesof(/obj/effect/proc_holder/spell))
caster.reset_perspective(0)
return 0
- if(is_admin_level(user.z) && !centcom_cancast) //Certain spells are not allowed on the centcom zlevel
- return 0
-
- if(!skipcharge)
- switch(charge_type)
- if("recharge")
- if(charge_counter < charge_max)
- to_chat(user, still_recharging_msg)
- return 0
- if("charges")
- if(!charge_counter)
- to_chat(user, "[name] has no charges left.")
- return 0
-
- if(!ghost)
- if(user.stat && !stat_allowed)
- to_chat(user, "You can't cast this spell while incapacitated.")
- return 0
- if(ishuman(user) && (invocation_type == "whisper" || invocation_type == "shout") && user.is_muzzled())
- to_chat(user, "Mmmf mrrfff!")
- return 0
-
- var/obj/effect/proc_holder/spell/noclothes/clothes_spell = locate() in (user.mob_spell_list | (user.mind ? user.mind.spell_list : list()))
- if((ishuman(user) && clothes_req) && !istype(clothes_spell))//clothes check
- var/mob/living/carbon/human/H = user
- var/obj/item/clothing/robe = H.wear_suit
- var/obj/item/clothing/hat = H.head
- var/obj/item/clothing/shoes = H.shoes
- if(!robe || !hat || !shoes)
- to_chat(user, "Your outfit isn't complete, you should put on your robe and wizard hat, as well as sandals.")
- return 0
- if(!robe.magical || !hat.magical || !shoes.magical)
- to_chat(user, "Your outfit isn't magical enough, you should put on your robe and wizard hat, as well as your sandals.")
- return 0
- else if(!ishuman(user))
- if(clothes_req || human_req)
- to_chat(user, "This spell can only be cast by humans!")
- return 0
- if(nonabstract_req && (isbrain(user) || ispAI(user)))
- to_chat(user, "This spell can only be cast by physical beings!")
- return 0
-
- if(!skipcharge)
+ if(start_recharge)
switch(charge_type)
if("recharge")
charge_counter = 0 //doesn't start recharging until the targets selecting ends
@@ -442,6 +420,100 @@ GLOBAL_LIST_INIT(spells, typesof(/obj/effect/proc_holder/spell))
return
+/obj/effect/proc_holder/spell/targeted/click
+ var/click_radius = 1 // How big the radius around the clicked atom is to find a suitable target. -1 is only the selected atom is considered
+ var/selection_activated_message = "Click on a target to cast the spell."
+ var/selection_deactivated_message = "You choose to not cast this spell."
+ var/allowed_type = /mob/living // Which type the targets have to be
+ var/auto_target_single = TRUE // If the spell should auto select a target if only one is found
+
+/obj/effect/proc_holder/spell/targeted/click/Click()
+ var/mob/living/user = usr
+ if(!istype(user))
+ return
+
+ if(active)
+ remove_ranged_ability(user, selection_deactivated_message)
+ else
+ if(cast_check(TRUE, FALSE, user))
+ if(auto_target_single && attempt_auto_target(user))
+ return
+
+ add_ranged_ability(user, selection_activated_message)
+ else
+ to_chat(user, "[src] is not ready to be used yet.")
+
+/obj/effect/proc_holder/spell/targeted/click/proc/attempt_auto_target(mob/user)
+ var/atom/target
+ for(var/atom/A in view_or_range(range, user, selection_type))
+ if(valid_target(A, user))
+ if(target)
+ return FALSE // Two targets found. ABORT
+ target = A
+
+ if(target && cast_check(TRUE, TRUE, user)) // Singular target found. Cast it instantly
+ to_chat(user, "Only one target found. Casting [src] on [target]!")
+ perform(list(target), user = user)
+ return TRUE
+ return FALSE
+
+/obj/effect/proc_holder/spell/targeted/click/InterceptClickOn(mob/living/user, params, atom/A)
+ if(..() || !cast_check(TRUE, TRUE, user))
+ remove_ranged_ability(user)
+ revert_cast(user)
+ return TRUE
+
+ var/list/targets = list()
+ if(valid_target(A, user))
+ targets.Add(A)
+
+ if((!max_targets || max_targets > targets.len) && click_radius >= 0)
+ var/list/found_others = list()
+ for(var/atom/target in range(click_radius, A))
+ if(valid_target(target, user))
+ found_others |= target
+ if(!max_targets)
+ targets.Add(found_others)
+ else
+ if(max_targets <= found_others.len + targets.len)
+ targets.Add(found_others)
+ else
+ switch(random_target_priority) //Add in the rest
+ if(TARGET_RANDOM)
+ while(targets.len < max_targets && found_others.len) // Add the others
+ targets.Add(pick_n_take(found_others))
+ if(TARGET_CLOSEST)
+ var/list/distances = list()
+ for(var/target in found_others)
+ distances[target] = get_dist(user, target)
+ sortTim(distances, /proc/cmp_numeric_asc, TRUE) // Sort on distance
+ for(var/target in distances)
+ targets.Add(target)
+ if(targets.len >= max_targets)
+ break
+
+
+ if(!targets.len)
+ to_chat(user, "No suitable target found.")
+ revert_cast(user)
+ return FALSE
+
+ perform(targets, user = user)
+ remove_ranged_ability(user)
+ return TRUE
+
+/* Checks if a target is valid
+ * Should not include to_chats or other types of messages since this is used often on tons of targets.
+ * @param target The target to check
+ * @param user The user of the spell
+*/
+/obj/effect/proc_holder/spell/targeted/click/proc/valid_target(target, user)
+ return istype(target, allowed_type) && (include_user || target != user) && \
+ (target in view_or_range(range, user, selection_type))
+
+/obj/effect/proc_holder/spell/targeted/click/choose_targets(mob/living/user, atom/A) // Not used
+ return
+
/obj/effect/proc_holder/spell/aoe_turf/choose_targets(mob/user = usr)
var/list/targets = list()
@@ -475,30 +547,39 @@ GLOBAL_LIST_INIT(spells, typesof(/obj/effect/proc_holder/spell))
qdel(dummy)
return 1
-/obj/effect/proc_holder/spell/proc/can_cast(mob/user = usr)
+/obj/effect/proc_holder/spell/proc/can_cast(mob/user = usr, charge_check = TRUE, show_message = FALSE)
if(((!user.mind) || !(src in user.mind.spell_list)) && !(src in user.mob_spell_list))
+ if(show_message)
+ to_chat(user, "You shouldn't have this spell! Something's wrong.")
return 0
if(is_admin_level(user.z) && !centcom_cancast) //Certain spells are not allowed on the centcom zlevel
return 0
- switch(charge_type)
- if("recharge")
- if(charge_counter < charge_max)
- return 0
- if("charges")
- if(!charge_counter)
- return 0
-
- if(user.stat && !stat_allowed)
- return 0
+ if(charge_check)
+ switch(charge_type)
+ if("recharge")
+ if(charge_counter < charge_max)
+ if(show_message)
+ to_chat(user, still_recharging_msg)
+ return 0
+ if("charges")
+ if(!charge_counter)
+ if(show_message)
+ to_chat(user, "[name] has no charges left.")
+ return 0
+ if(!ghost)
+ if(user.stat && !stat_allowed)
+ if(show_message)
+ to_chat(user, "You can't cast this spell while incapacitated.")
+ return 0
+ if(ishuman(user) && (invocation_type == "whisper" || invocation_type == "shout") && user.is_muzzled())
+ if(show_message)
+ to_chat(user, "Mmmf mrrfff!")
+ return 0
if(ishuman(user))
var/mob/living/carbon/human/H = user
-
- if((invocation_type == "whisper" || invocation_type == "shout") && H.is_muzzled())
- return 0
-
var/clothcheck = locate(/obj/effect/proc_holder/spell/noclothes) in user.mob_spell_list
var/clothcheck2 = user.mind && (locate(/obj/effect/proc_holder/spell/noclothes) in user.mind.spell_list)
if(clothes_req && !clothcheck && !clothcheck2) //clothes check
@@ -506,12 +587,20 @@ GLOBAL_LIST_INIT(spells, typesof(/obj/effect/proc_holder/spell))
var/obj/item/clothing/hat = H.head
var/obj/item/clothing/shoes = H.shoes
if(!robe || !hat || !shoes)
+ if(show_message)
+ to_chat(user, "Your outfit isn't complete, you should put on your robe and wizard hat, as well as sandals.")
return 0
if(!robe.magical || !hat.magical || !shoes.magical)
+ if(show_message)
+ to_chat(user, "Your outfit isn't magical enough, you should put on your robe and wizard hat, as well as your sandals.")
return 0
else
if(clothes_req || human_req)
+ if(show_message)
+ to_chat(user, "This spell can only be cast by humans!")
return 0
if(nonabstract_req && (isbrain(user) || ispAI(user)))
+ if(show_message)
+ to_chat(user, "This spell can only be cast by physical beings!")
return 0
return 1
diff --git a/code/datums/spells/area_teleport.dm b/code/datums/spells/area_teleport.dm
index 0d24a984014..5986da3a412 100644
--- a/code/datums/spells/area_teleport.dm
+++ b/code/datums/spells/area_teleport.dm
@@ -11,7 +11,7 @@
/obj/effect/proc_holder/spell/targeted/area_teleport/perform(list/targets, recharge = 1, mob/living/user = usr)
var/thearea = before_cast(targets)
- if(!thearea || !cast_check(1))
+ if(!thearea || !cast_check(TRUE, FALSE, user))
revert_cast()
return
invocation(thearea)
diff --git a/code/datums/spells/chaplain.dm b/code/datums/spells/chaplain.dm
index bdb7bb3f551..f16a2a3fbac 100644
--- a/code/datums/spells/chaplain.dm
+++ b/code/datums/spells/chaplain.dm
@@ -1,25 +1,31 @@
-/obj/effect/proc_holder/spell/targeted/chaplain_bless
+/obj/effect/proc_holder/spell/targeted/click/chaplain_bless
name = "Bless"
desc = "Blesses a single person."
school = "transmutation"
charge_max = 60
- clothes_req = 0
+ clothes_req = FALSE
invocation = "none"
invocation_type = "none"
max_targets = 1
- include_user = 0
- humans_only = 1
-
+ include_user = FALSE
+ allowed_type = /mob/living/carbon/human
+ selection_activated_message = "You prepare a blessing. Click on a target to start blessing."
+ selection_deactivated_message = "The crew will be blessed another time."
range = 1
+ click_radius = -1 // Only precision clicking
cooldown_min = 20
action_icon_state = "shield"
+/obj/effect/proc_holder/spell/targeted/click/chaplain_bless/valid_target(mob/living/carbon/human/target, user)
+ if(!..())
+ return FALSE
-/obj/effect/proc_holder/spell/targeted/chaplain_bless/cast(list/targets, mob/living/user = usr, distanceoverride)
+ return target.mind && target.ckey && !target.stat
+/obj/effect/proc_holder/spell/targeted/click/chaplain_bless/cast(list/targets, mob/living/user = usr)
if(!istype(user))
to_chat(user, "Somehow, you are not a living mob. This should never happen. Report this bug.")
revert_cast()
@@ -35,32 +41,7 @@
revert_cast()
return
- var/mob/living/carbon/human/target = targets[range]
-
- if(!istype(target))
- to_chat(user, "No target.")
- revert_cast()
- return
-
- if(!(target in oview(range)) && !distanceoverride)//If they are not in overview after selection. Do note that !() is necessary for in to work because ! takes precedence over it.
- to_chat(user, "[target] is too far away!")
- revert_cast()
- return
-
- if(!target.mind)
- to_chat(user, "[target] appears to be catatonic. Your blessing would have no effect.")
- revert_cast()
- return
-
- if(!target.ckey)
- to_chat(user, "[target] appears to be too out of it to benefit from this.")
- revert_cast()
- return
-
- if(target.stat == DEAD)
- to_chat(user, "[target] is already dead. There is no point.")
- revert_cast()
- return
+ var/mob/living/carbon/human/target = targets[1]
spawn(0) // allows cast to complete even if recipient ignores the prompt
if(alert(target, "[user] wants to bless you, in the name of [user.p_their()] religion. Accept?", "Accept Blessing?", "Yes", "No") == "Yes") // prevents forced conversions
diff --git a/code/datums/spells/devil.dm b/code/datums/spells/devil.dm
index 1a1069481cf..f9e075bdf81 100644
--- a/code/datums/spells/devil.dm
+++ b/code/datums/spells/devil.dm
@@ -21,13 +21,18 @@
action_background_icon_state = "bg_demon"
-/obj/effect/proc_holder/spell/targeted/summon_contract
+/obj/effect/proc_holder/spell/targeted/click/summon_contract
name = "Summon infernal contract"
desc = "Skip making a contract by hand, just do it by magic."
invocation_type = "whisper"
invocation = "Just sign on the dotted line."
- include_user = 0
+ selection_activated_message = "You prepare a detailed contract. Click on a target to summon the contract in his hands."
+ selection_deactivated_message = "You archive the contract for later use."
+ include_user = FALSE
range = 5
+ auto_target_single = FALSE // Prevent an accidental contract from summoning
+ click_radius = -1 // Precision clicking required
+ allowed_type = /mob/living/carbon
clothes_req = FALSE
school = "conjuration"
charge_max = 150
@@ -35,8 +40,9 @@
action_icon_state = "spell_default"
action_background_icon_state = "bg_demon"
-/obj/effect/proc_holder/spell/targeted/summon_contract/cast(list/targets, mob/user = usr)
- for(var/mob/living/carbon/C in targets)
+/obj/effect/proc_holder/spell/targeted/click/summon_contract/cast(list/targets, mob/user = usr)
+ for(var/target in targets)
+ var/mob/living/carbon/C = target
if(C.mind && user.mind)
if(C.stat == DEAD)
if(user.drop_item())
@@ -63,7 +69,7 @@
to_chat(user,"[C] seems to not be sentient. You are unable to summon a contract for them.")
-/obj/effect/proc_holder/spell/fireball/hellish
+/obj/effect/proc_holder/spell/targeted/click/fireball/hellish
name = "Hellfire"
desc = "This spell launches hellfire at the target."
school = "evocation"
@@ -74,7 +80,7 @@
fireball_type = /obj/item/projectile/magic/fireball/infernal
action_background_icon_state = "bg_demon"
-/obj/effect/proc_holder/spell/fireball/hellish/cast(list/targets, mob/living/user = usr)
+/obj/effect/proc_holder/spell/targeted/click/fireball/hellish/cast(list/targets, mob/living/user = usr)
add_attack_logs(user, targets, "has fired a Hellfire ball", ATKLOG_FEW)
.=..()
diff --git a/code/datums/spells/horsemask.dm b/code/datums/spells/horsemask.dm
index 0140fbdf951..d5f686fb763 100644
--- a/code/datums/spells/horsemask.dm
+++ b/code/datums/spells/horsemask.dm
@@ -1,39 +1,31 @@
-/obj/effect/proc_holder/spell/targeted/horsemask
+/obj/effect/proc_holder/spell/targeted/click/horsemask
name = "Curse of the Horseman"
desc = "This spell triggers a curse on a target, causing them to wield an unremovable horse head mask. They will speak like a horse! Any masks they are wearing will be disintegrated. This spell does not require robes."
school = "transmutation"
charge_type = "recharge"
charge_max = 150
charge_counter = 0
- clothes_req = 0
- stat_allowed = 0
+ clothes_req = FALSE
+ stat_allowed = FALSE
invocation = "KN'A FTAGHU, PUCK 'BTHNK!"
invocation_type = "shout"
range = 7
cooldown_min = 30 //30 deciseconds reduction per rank
selection_type = "range"
+ selection_activated_message = "You start to quietly neigh an incantation. Click on or near a target to cast the spell."
+ selection_deactivated_message = "You stop neighing to yourself."
+ allowed_type = /mob/living/carbon/human
+
action_icon_state = "barn"
sound = 'sound/magic/HorseHead_curse.ogg'
-/obj/effect/proc_holder/spell/targeted/horsemask/cast(list/targets, mob/user = usr)
+/obj/effect/proc_holder/spell/targeted/click/horsemask/cast(list/targets, mob/user = usr)
if(!targets.len)
to_chat(user, "No target found in range.")
return
- var/mob/living/carbon/target = targets[1]
-
- if(!target)
- return
-
-
- if(!ishuman(target))
- to_chat(user, "It'd be stupid to curse [target] with a horse's head!")
- return
-
- if(!(target in oview(range)))//If they are not in overview after selection.
- to_chat(user, "They are too far away!")
- return
+ var/mob/living/carbon/human/target = targets[1]
var/obj/item/clothing/mask/horsehead/magichead = new /obj/item/clothing/mask/horsehead
magichead.flags |= NODROP | DROPDEL //curses!
diff --git a/code/datums/spells/lightning.dm b/code/datums/spells/lightning.dm
index ed6b7cd543e..20cd8dccdf5 100644
--- a/code/datums/spells/lightning.dm
+++ b/code/datums/spells/lightning.dm
@@ -25,10 +25,10 @@
/obj/effect/proc_holder/spell/targeted/lightning/Click()
if(!ready && start_time == 0)
- if(cast_check())
+ if(cast_check(TRUE, FALSE, usr))
StartChargeup()
else
- if(ready && cast_check(skipcharge=1))
+ if(ready && cast_check(TRUE, TRUE, usr))
choose_targets()
return 1
diff --git a/code/datums/spells/magnet.dm b/code/datums/spells/magnet.dm
index dfdac6f1b9c..155a3cb0b45 100644
--- a/code/datums/spells/magnet.dm
+++ b/code/datums/spells/magnet.dm
@@ -20,10 +20,10 @@
/obj/effect/proc_holder/spell/targeted/magnet/Click()
if(!ready && start_time == 0)
- if(cast_check())
+ if(cast_check(TRUE, FALSE, usr))
StartChargeup()
else
- if(ready && cast_check(skipcharge=1))
+ if(ready && cast_check(TRUE, TRUE, usr))
choose_targets()
return 1
diff --git a/code/datums/spells/mind_transfer.dm b/code/datums/spells/mind_transfer.dm
index 3d9031a8e03..e4924ee1c2f 100644
--- a/code/datums/spells/mind_transfer.dm
+++ b/code/datums/spells/mind_transfer.dm
@@ -1,4 +1,4 @@
-/obj/effect/proc_holder/spell/targeted/mind_transfer
+/obj/effect/proc_holder/spell/targeted/click/mind_transfer
name = "Mind Transfer"
desc = "This spell allows the user to switch bodies with a target."
@@ -8,33 +8,29 @@
invocation = "GIN'YU CAPAN"
invocation_type = "whisper"
range = 1
+ click_radius = 0 // Still gotta be pretty accurate
+ selection_activated_message = "You prepare to transfer your mind. Click on a target to cast the spell."
+ selection_deactivated_message = "You decide that your current form is good enough."
cooldown_min = 200 //100 deciseconds reduction per rank
var/list/protected_roles = list("Wizard","Changeling","Cultist") //which roles are immune to the spell
var/paralysis_amount_caster = 20 //how much the caster is paralysed for after the spell
var/paralysis_amount_victim = 20 //how much the victim is paralysed for after the spell
action_icon_state = "mindswap"
+/obj/effect/proc_holder/spell/targeted/click/mind_transfer/valid_target(mob/living/target, user)
+ if(!..())
+ return FALSE
+ return target.stat != DEAD && target.key && target.mind
+
/*
Urist: I don't feel like figuring out how you store object spells so I'm leaving this for you to do.
Make sure spells that are removed from spell_list are actually removed and deleted when mind transfering.
Also, you never added distance checking after target is selected. I've went ahead and did that.
*/
-/obj/effect/proc_holder/spell/targeted/mind_transfer/cast(list/targets, mob/user = usr, distanceoverride)
+/obj/effect/proc_holder/spell/targeted/click/mind_transfer/cast(list/targets, mob/user = usr)
var/mob/living/target = targets[range]
- if(!(target in oview(range)) && !distanceoverride)//If they are not in overview after selection. Do note that !() is necessary for in to work because ! takes precedence over it.
- to_chat(user, "They are too far away!")
- return
-
- if(target.stat == DEAD)
- to_chat(user, "You don't particularly want to be dead.")
- return
-
- if(!target.key || !target.mind)
- to_chat(user, "[target.p_they(TRUE)] appear[target.p_s()] to be catatonic. Not even magic can affect [target.p_their()] vacant mind.")
- return
-
if(user.suiciding)
to_chat(user, "You're killing yourself! You can't concentrate enough to do this!")
return
diff --git a/code/datums/spells/wizard.dm b/code/datums/spells/wizard.dm
index 94383f1b1e2..a99655222a4 100644
--- a/code/datums/spells/wizard.dm
+++ b/code/datums/spells/wizard.dm
@@ -308,76 +308,50 @@
duration = 300
sound = 'sound/magic/blind.ogg'
-/obj/effect/proc_holder/spell/fireball
+/obj/effect/proc_holder/spell/targeted/click/fireball
name = "Fireball"
desc = "This spell fires a fireball at a target and does not require wizard garb."
school = "evocation"
charge_max = 60
- clothes_req = 0
+ clothes_req = FALSE
invocation = "ONI SOMA"
invocation_type = "shout"
+ auto_target_single = FALSE // Having this true won't ever find a single target and is just lost processing power
range = 20
cooldown_min = 20 //10 deciseconds reduction per rank
+
+ click_radius = -1
+ selection_activated_message = "Your prepare to cast your fireball spell! Left-click to cast at a target!"
+ selection_deactivated_message = "You extinguish your fireball...for now."
+ allowed_type = /atom // FIRE AT EVERYTHING
+
var/fireball_type = /obj/item/projectile/magic/fireball
action_icon_state = "fireball0"
sound = 'sound/magic/fireball.ogg'
active = FALSE
-/obj/effect/proc_holder/spell/fireball/Click()
- var/mob/living/user = usr
- if(!istype(user))
- return
-
- var/msg
-
- if(!can_cast(user))
- msg = "You can no longer cast Fireball."
- remove_ranged_ability(user, msg)
- return
-
- if(active)
- msg = "You extinguish your fireball...for now."
- remove_ranged_ability(user, msg)
- else
- msg = "Your prepare to cast your fireball spell! Left-click to cast at a target!"
- add_ranged_ability(user, msg)
-
-/obj/effect/proc_holder/spell/fireball/update_icon()
+/obj/effect/proc_holder/spell/targeted/click/fireball/update_icon()
if(!action)
return
action.button_icon_state = "fireball[active]"
action.UpdateButtonIcon()
-/obj/effect/proc_holder/spell/fireball/InterceptClickOn(mob/living/user, params, atom/target)
- if(..())
- return FALSE
-
- if(!cast_check(0, user))
- remove_ranged_ability(user)
- return FALSE
-
- var/list/targets = list(target)
- perform(targets, user = user)
-
- return TRUE
-
-/obj/effect/proc_holder/spell/fireball/cast(list/targets, mob/living/user = usr)
+/obj/effect/proc_holder/spell/targeted/click/fireball/cast(list/targets, mob/living/user = usr)
var/target = targets[1] //There is only ever one target for fireball
var/turf/T = user.loc
var/turf/U = get_step(user, user.dir) // Get the tile infront of the move, based on their direction
if(!isturf(U) || !isturf(T))
- return 0
+ return FALSE
var/obj/item/projectile/magic/fireball/FB = new fireball_type(user.loc)
FB.current = get_turf(user)
FB.preparePixelProjectile(target, get_turf(target), user)
FB.fire()
user.newtonian_move(get_dir(U, T))
- remove_ranged_ability(user)
- return 1
+ return TRUE
/obj/effect/proc_holder/spell/aoe_turf/repulse
name = "Repulse"
diff --git a/code/datums/uplink_item.dm b/code/datums/uplink_item.dm
index 8a29d2cb0d0..42f74e234a0 100644
--- a/code/datums/uplink_item.dm
+++ b/code/datums/uplink_item.dm
@@ -1563,11 +1563,11 @@ GLOBAL_LIST_INIT(uplink_items, subtypesof(/datum/uplink_item))
cost = 10
/datum/uplink_item/cyber_implants/antistun
- name = "CNS Rebooter Implant"
- desc = "This implant will help you get back up on your feet faster after being stunned. \
+ name = "Hardened CNS Rebooter Implant"
+ desc = "This implant will help you get back up on your feet faster after being stunned. It is invulnerable to EMPs. \
Comes with an automated implanting tool."
reference = "CIAS"
- item = /obj/item/organ/internal/cyberimp/brain/anti_stun
+ item = /obj/item/organ/internal/cyberimp/brain/anti_stun/hardened
cost = 12
/datum/uplink_item/cyber_implants/reviver
diff --git a/code/game/dna/genes/goon_powers.dm b/code/game/dna/genes/goon_powers.dm
index 87bbdf97c5c..77eb58e84b7 100644
--- a/code/game/dna/genes/goon_powers.dm
+++ b/code/game/dna/genes/goon_powers.dm
@@ -123,13 +123,13 @@
instability = GENE_INSTABILITY_MODERATE
mutation = CRYO
- spelltype = /obj/effect/proc_holder/spell/targeted/cryokinesis
+ spelltype = /obj/effect/proc_holder/spell/targeted/click/cryokinesis
/datum/dna/gene/basic/grant_spell/cryo/New()
..()
block = GLOB.cryoblock
-/obj/effect/proc_holder/spell/targeted/cryokinesis
+/obj/effect/proc_holder/spell/targeted/click/cryokinesis
name = "Cryokinesis"
desc = "Drops the bodytemperature of another person."
panel = "Abilities"
@@ -137,45 +137,44 @@
charge_type = "recharge"
charge_max = 1200
- clothes_req = 0
- stat_allowed = 0
+ clothes_req = FALSE
+ stat_allowed = FALSE
+
+ click_radius = 0
+ auto_target_single = FALSE // Give the clueless geneticists a way out and to have them not target themselves
+ selection_activated_message = "Your mind grow cold. Click on a target to cast the spell."
+ selection_deactivated_message = "Your mind returns to normal."
+ allowed_type = /mob/living/carbon
invocation_type = "none"
range = 7
selection_type = "range"
- include_user = 1
+ include_user = TRUE
var/list/compatible_mobs = list(/mob/living/carbon/human)
action_icon_state = "genetic_cryo"
-/obj/effect/proc_holder/spell/targeted/cryokinesis/cast(list/targets, mob/user = usr)
- if(!targets.len)
- to_chat(user, "No target found in range.")
- return
+/obj/effect/proc_holder/spell/targeted/click/cryokinesis/cast(list/targets, mob/user = usr)
var/mob/living/carbon/C = targets[1]
- if(!iscarbon(C))
- to_chat(user, "This will only work on normal organic beings.")
- return
-
if(COLDRES in C.mutations)
C.visible_message("A cloud of fine ice crystals engulfs [C.name], but disappears almost instantly!")
return
- var/handle_suit = 0
+ var/handle_suit = FALSE
if(ishuman(C))
var/mob/living/carbon/human/H = C
if(istype(H.head, /obj/item/clothing/head/helmet/space))
if(istype(H.wear_suit, /obj/item/clothing/suit/space))
- handle_suit = 1
+ handle_suit = TRUE
if(H.internal)
H.visible_message("[user] sprays a cloud of fine ice crystals, engulfing [H]!",
"[user] sprays a cloud of fine ice crystals over your [H.head]'s visor.")
- add_attack_logs(user, C, "Cryokinesis")
else
H.visible_message("[user] sprays a cloud of fine ice crystals engulfing, [H]!",
"[user] sprays a cloud of fine ice crystals cover your [H.head]'s visor and make it into your air vents!.")
- add_attack_logs(user, C, "Cryokinesis")
+
H.bodytemperature = max(0, H.bodytemperature - 100)
+ add_attack_logs(user, C, "Cryokinesis")
if(!handle_suit)
C.bodytemperature = max(0, C.bodytemperature - 200)
C.ExtinguishMob()
@@ -454,7 +453,7 @@
name = "Polymorphism"
desc = "Enables the subject to reconfigure their appearance to mimic that of others."
- spelltype =/obj/effect/proc_holder/spell/targeted/polymorph
+ spelltype =/obj/effect/proc_holder/spell/targeted/click/polymorph
//cooldown = 1800
activation_messages = list("You don't feel entirely like yourself somehow.")
deactivation_messages = list("You feel secure in your identity.")
@@ -465,34 +464,36 @@
..()
block = GLOB.polymorphblock
-/obj/effect/proc_holder/spell/targeted/polymorph
+/obj/effect/proc_holder/spell/targeted/click/polymorph
name = "Polymorph"
desc = "Mimic the appearance of others!"
panel = "Abilities"
charge_max = 1800
- clothes_req = 0
- human_req = 1
- stat_allowed = 0
+ clothes_req = FALSE
+ stat_allowed = FALSE
+
+ click_radius = -1 // Precision required
+ auto_target_single = FALSE // Safety to not turn into monkey (420)
+ selection_activated_message = "You body becomes unstable. Click on a target to cast transform into them."
+ selection_deactivated_message = "Your body calms down again."
+ allowed_type = /mob/living/carbon/human
+
invocation_type = "none"
range = 1
selection_type = "range"
action_icon_state = "genetic_poly"
-/obj/effect/proc_holder/spell/targeted/polymorph/cast(list/targets, mob/user = usr)
- var/mob/living/M = targets[1]
- if(!ishuman(M))
- to_chat(usr, "You can only change your appearance to that of another human.")
- return
+/obj/effect/proc_holder/spell/targeted/click/polymorph/cast(list/targets, mob/user = usr)
+ var/mob/living/carbon/human/target = targets[1]
user.visible_message("[user]'s body shifts and contorts.")
spawn(10)
- if(M && user)
+ if(target && user)
playsound(user.loc, 'sound/goonstation/effects/gib.ogg', 50, 1)
var/mob/living/carbon/human/H = user
- var/mob/living/carbon/human/target = M
H.UpdateAppearance(target.dna.UI)
H.real_name = target.real_name
H.name = target.name
diff --git a/code/game/gamemodes/devil/devilinfo.dm b/code/game/gamemodes/devil/devilinfo.dm
index ecbeb54f7f7..22a5f4a6cde 100644
--- a/code/game/gamemodes/devil/devilinfo.dm
+++ b/code/game/gamemodes/devil/devilinfo.dm
@@ -92,7 +92,7 @@ GLOBAL_LIST_INIT(lawlorify, list (
var/form = BASIC_DEVIL
var/exists = 0
var/static/list/dont_remove_spells = list(
- /obj/effect/proc_holder/spell/targeted/summon_contract,
+ /obj/effect/proc_holder/spell/targeted/click/summon_contract,
/obj/effect/proc_holder/spell/targeted/conjure_item/violin,
/obj/effect/proc_holder/spell/targeted/summon_dancefloor)
var/ascendable = FALSE
@@ -326,12 +326,12 @@ GLOBAL_LIST_INIT(lawlorify, list (
owner.RemoveSpell(S)
/datum/devilinfo/proc/give_summon_contract()
- owner.AddSpell(new /obj/effect/proc_holder/spell/targeted/summon_contract(null))
+ owner.AddSpell(new /obj/effect/proc_holder/spell/targeted/click/summon_contract(null))
/datum/devilinfo/proc/give_base_spells(give_summon_contract = 0)
remove_spells()
- owner.AddSpell(new /obj/effect/proc_holder/spell/fireball/hellish(null))
+ owner.AddSpell(new /obj/effect/proc_holder/spell/targeted/click/fireball/hellish(null))
owner.AddSpell(new /obj/effect/proc_holder/spell/targeted/conjure_item/pitchfork(null))
if(give_summon_contract)
give_summon_contract()
@@ -343,13 +343,13 @@ GLOBAL_LIST_INIT(lawlorify, list (
/datum/devilinfo/proc/give_lizard_spells()
remove_spells()
owner.AddSpell(new /obj/effect/proc_holder/spell/targeted/conjure_item/pitchfork(null))
- owner.AddSpell(new /obj/effect/proc_holder/spell/fireball/hellish(null))
+ owner.AddSpell(new /obj/effect/proc_holder/spell/targeted/click/fireball/hellish(null))
owner.AddSpell(new /obj/effect/proc_holder/spell/targeted/infernal_jaunt(null))
/datum/devilinfo/proc/give_true_spells()
remove_spells()
owner.AddSpell(new /obj/effect/proc_holder/spell/targeted/conjure_item/pitchfork/greater(null))
- owner.AddSpell(new /obj/effect/proc_holder/spell/fireball/hellish(null))
+ owner.AddSpell(new /obj/effect/proc_holder/spell/targeted/click/fireball/hellish(null))
owner.AddSpell(new /obj/effect/proc_holder/spell/targeted/infernal_jaunt(null))
owner.AddSpell(new /obj/effect/proc_holder/spell/targeted/sintouch(null))
diff --git a/code/game/gamemodes/miniantags/revenant/revenant_abilities.dm b/code/game/gamemodes/miniantags/revenant/revenant_abilities.dm
index 91ed583efc7..47e5e759c77 100644
--- a/code/game/gamemodes/miniantags/revenant/revenant_abilities.dm
+++ b/code/game/gamemodes/miniantags/revenant/revenant_abilities.dm
@@ -153,7 +153,7 @@
else
name = "[initial(name)] ([cast_amount]E)"
-/obj/effect/proc_holder/spell/aoe_turf/revenant/can_cast(mob/living/simple_animal/revenant/user = usr)
+/obj/effect/proc_holder/spell/aoe_turf/revenant/can_cast(mob/living/simple_animal/revenant/user = usr, charge_check = TRUE, show_message = FALSE)
if(user.inhibited)
return 0
if(charge_counter < charge_max)
diff --git a/code/game/gamemodes/shadowling/shadowling_abilities.dm b/code/game/gamemodes/shadowling/shadowling_abilities.dm
index 7692aa202b2..4be50a6cd59 100644
--- a/code/game/gamemodes/shadowling/shadowling_abilities.dm
+++ b/code/game/gamemodes/shadowling/shadowling_abilities.dm
@@ -18,80 +18,56 @@
return 0
-/obj/effect/proc_holder/spell/targeted/glare //Stuns and mutes a human target, depending on the distance relative to the shadowling
+/obj/effect/proc_holder/spell/targeted/click/glare //Stuns and mutes a human target, depending on the distance relative to the shadowling
name = "Glare"
desc = "Stuns and mutes a target for a decent duration. Duration depends on the proximity to the target."
panel = "Shadowling Abilities"
charge_max = 300
- clothes_req = 0
+ clothes_req = FALSE
range = 10 //has no effect beyond this range, so setting this makes invalid/useless targets not show up in popup
action_icon_state = "glare"
- humans_only = 1 //useless since we override chose_targets, but might be used for other code later??? Might remove, idk
-/obj/effect/proc_holder/spell/targeted/glare/choose_targets(mob/user)
- var/list/possible_targets = list()
- for(var/mob/living/carbon/human/target in view_or_range(range, user, "view"))
- if(target.stat)
- continue
- if(is_shadow_or_thrall(target))
- continue
- possible_targets += target
- var/mob/living/carbon/human/M
- var/list/targets = list()
- if(possible_targets.len == 1)//no choice involved
- targets = possible_targets
- else
- M = input("Choose the target for the spell.", "Targeting") as mob in possible_targets
- if(M in view_or_range(range, user, "view"))
- targets += M
+ selection_activated_message = "Your prepare to your eyes for a stunning glare! Left-click to cast at a target!"
+ selection_deactivated_message = "Your eyes relax... for now."
+ allowed_type = /mob/living/carbon/human
+/obj/effect/proc_holder/spell/targeted/click/glare/cast_check(charge_check = TRUE, start_recharge = TRUE, mob/living/user = usr)
+ if(!shadowling_check(user))
+ return FALSE
+ return ..()
- if(!targets.len) //doesn't waste the spell
- revert_cast(user)
+/obj/effect/proc_holder/spell/targeted/click/glare/valid_target(mob/living/carbon/human/target, user)
+ if(!..())
+ return FALSE
+ return !target.stat && !is_shadow_or_thrall(target)
+
+/obj/effect/proc_holder/spell/targeted/click/glare/cast(list/targets, mob/user = usr)
+ var/mob/living/carbon/human/H = targets[1]
+
+ user.visible_message("[user]'s eyes flash a blinding red!")
+ var/distance = get_dist(H, user)
+ if (distance <= 1) //Melee glare
+ H.visible_message("[H] freezes in place, [H.p_their()] eyes glazing over...", \
+ "Your gaze is forcibly drawn into [user]'s eyes, and you are mesmerized by [user.p_their()] heavenly beauty...")
+ H.Stun(10)
+ H.AdjustSilence(10)
+ else //Distant glare
+ var/loss = 10 - distance
+ var/duration = 10 - loss
+ if(loss <= 0)
+ to_chat(user, "Your glare had no effect over a such long distance!")
+ return
+ H.slowed = duration
+ H.AdjustSilence(10)
+ to_chat(H, "A red light flashes across your vision, and your mind tries to resist them.. you are exhausted.. you are not able to speak..")
+ addtimer(CALLBACK(src, .proc/do_stun, H, user, loss), duration SECONDS)
+
+/obj/effect/proc_holder/spell/targeted/click/glare/proc/do_stun(mob/living/carbon/human/target, user, stun_time)
+ if(!istype(target) || target.stat)
return
-
- perform(targets, user = user)
- return
-
-
-/obj/effect/proc_holder/spell/targeted/glare/cast(list/targets, mob/user = usr)
- for(var/mob/living/carbon/human/target in targets)
- if(!ishuman(target))
- to_chat(user, "You may only glare at humans!")
- charge_counter = charge_max
- return
- if(!shadowling_check(user))
- charge_counter = charge_max
- return
- if(target.stat)
- to_chat(user, "[target] must be conscious!")
- charge_counter = charge_max
- return
- if(is_shadow_or_thrall(target))
- to_chat(user, "You don't see why you would want to paralyze an ally.")
- charge_counter = charge_max
- return
- var/mob/living/carbon/human/M = target
- user.visible_message("[user]'s eyes flash a blinding red!")
- var/distance = get_dist(target, user)
- if (distance <= 1) //Melee glare
- target.visible_message("[target] freezes in place, [target.p_their()] eyes glazing over...")
- to_chat(target, "Your gaze is forcibly drawn into [user]'s eyes, and you are mesmerized by [user.p_their()] heavenly beauty...")
- target.Stun(10)
- M.AdjustSilence(10)
- else //Distant glare
- var/loss = 10 - distance
- var/duration = 10 - loss
- if(loss <= 0)
- to_chat(user, "Your glare had no effect over a such long distance!")
- return
- target.slowed = duration
- M.AdjustSilence(10)
- to_chat(target, "A red light flashes across your vision, and your mind tries to resist them.. you are exhausted.. you are not able to speak..")
- sleep(duration*10)
- target.Stun(loss)
- target.visible_message("[target] freezes in place, [target.p_their()] eyes glazing over...")
- to_chat(target, "Red lights suddenly dance in your vision, and you are mesmerized by the heavenly lights...")
+ target.Stun(stun_time)
+ target.visible_message("[target] freezes in place, [target.p_their()] eyes glazing over...",\
+ "Red lights suddenly dance in your vision, and you are mesmerized by the heavenly lights...")
/obj/effect/proc_holder/spell/aoe_turf/veil
name = "Veil"
@@ -228,96 +204,80 @@
M.reagents.add_reagent("frostoil", 15) //Half of a cryosting
-/obj/effect/proc_holder/spell/targeted/enthrall //Turns a target into the shadowling's slave. This overrides all previous loyalties
+/obj/effect/proc_holder/spell/targeted/click/enthrall //Turns a target into the shadowling's slave. This overrides all previous loyalties
name = "Enthrall"
desc = "Allows you to enslave a conscious, non-braindead, non-catatonic human to your will. This takes some time to cast."
panel = "Shadowling Abilities"
charge_max = 0
- clothes_req = 0
+ clothes_req = FALSE
range = 1 //Adjacent to user
- var/enthralling = 0
+ var/enthralling = FALSE
action_icon_state = "enthrall"
- humans_only = 1
-/obj/effect/proc_holder/spell/targeted/enthrall/cast(list/targets, mob/user = usr)
+ click_radius = -1 // Precision baby
+ selection_activated_message = "Your prepare your mind to entrall a mortal. Left-click to cast at a target!"
+ selection_deactivated_message = "Your mind relaxes."
+ allowed_type = /mob/living/carbon/human
+
+/obj/effect/proc_holder/spell/targeted/click/enthrall/can_cast(mob/user = usr, charge_check = TRUE, show_message = FALSE)
+ if(enthralling || !shadowling_check(user))
+ return FALSE
+ return ..()
+
+/obj/effect/proc_holder/spell/targeted/click/enthrall/valid_target(mob/living/carbon/human/target, user)
+ if(!..())
+ return FALSE
+ return target.key && target.mind && !target.stat && !is_shadow_or_thrall(target) && target.client
+
+/obj/effect/proc_holder/spell/targeted/click/enthrall/cast(list/targets, mob/user = usr)
var/mob/living/carbon/human/ling = user
listclearnulls(SSticker.mode.shadowling_thralls)
if(!(ling.mind in SSticker.mode.shadows))
return
- if(!isshadowling(ling))
- if(SSticker.mode.shadowling_thralls.len >= 5)
- charge_counter = charge_max
- return
- for(var/mob/living/carbon/human/target in targets)
- if(!in_range(user, target))
- to_chat(user, "You need to be closer to enthrall [target].")
- charge_counter = charge_max
- return
- if(!target.key || !target.mind)
- to_chat(user, "The target has no mind.")
- charge_counter = charge_max
- return
- if(target.stat)
- to_chat(user, "The target must be conscious.")
- charge_counter = charge_max
- return
- if(is_shadow_or_thrall(target))
- to_chat(user, "You can not enthrall allies.")
- charge_counter = charge_max
- return
- if(!ishuman(target))
- to_chat(user, "You can only enthrall humans.")
- charge_counter = charge_max
- return
- if(enthralling)
- to_chat(user, "You are already enthralling!")
- charge_counter = charge_max
- return
- if(!target.client)
- to_chat(user, "[target]'s mind is vacant of activity.")
- enthralling = 1
- to_chat(user, "This target is valid. You begin the enthralling.")
- to_chat(target, "[user] stares at you. You feel your head begin to pulse.")
+ var/mob/living/carbon/human/target = targets[1]
+ enthralling = TRUE
+ to_chat(user, "This target is valid. You begin the enthralling.")
+ to_chat(target, "[user] stares at you. You feel your head begin to pulse.")
- for(var/progress = 0, progress <= 3, progress++)
- switch(progress)
- if(1)
- to_chat(user, "You place your hands to [target]'s head...")
- user.visible_message("[user] places [user.p_their()] hands onto the sides of [target]'s head!")
- if(2)
- to_chat(user, "You begin preparing [target]'s mind as a blank slate...")
- user.visible_message("[user]'s palms flare a bright red against [target]'s temples!")
- to_chat(target, "A terrible red light floods your mind. You collapse as conscious thought is wiped away.")
- target.Weaken(12)
- sleep(20)
- if(ismindshielded(target))
- to_chat(user, "They have a mindshield implant. You begin to deactivate it - this will take some time.")
- user.visible_message("[user] pauses, then dips [user.p_their()] head in concentration!")
- to_chat(target, "Your mindshield implant becomes hot as it comes under attack!")
- sleep(100) //10 seconds - not spawn() so the enthralling takes longer
- to_chat(user, "The nanobots composing the mindshield implant have been rendered inert. Now to continue.")
- user.visible_message("[user] relaxes again.")
- for(var/obj/item/implant/mindshield/L in target)
- if(L && L.implanted)
- qdel(L)
- to_chat(target, "Your mental protection implant unexpectedly falters, dims, dies.")
- if(3)
- to_chat(user, "You begin planting the tumor that will control the new thrall...")
- user.visible_message("A strange energy passes from [user]'s hands into [target]'s head!")
- to_chat(target, "You feel your memories twisting, morphing. A sense of horror dominates your mind.")
- if(!do_mob(user, target, 70)) //around 21 seconds total for enthralling, 31 for someone with a mindshield implant
- to_chat(user, "The enthralling has been interrupted - your target's mind returns to its previous state.")
- to_chat(target, "You wrest yourself away from [user]'s hands and compose yourself")
- enthralling = 0
- return
+ for(var/progress = 0, progress <= 3, progress++)
+ switch(progress)
+ if(1)
+ to_chat(user, "You place your hands to [target]'s head...")
+ user.visible_message("[user] places [user.p_their()] hands onto the sides of [target]'s head!")
+ if(2)
+ to_chat(user, "You begin preparing [target]'s mind as a blank slate...")
+ user.visible_message("[user]'s palms flare a bright red against [target]'s temples!")
+ to_chat(target, "A terrible red light floods your mind. You collapse as conscious thought is wiped away.")
+ target.Weaken(12)
+ sleep(20)
+ if(ismindshielded(target))
+ to_chat(user, "They have a mindshield implant. You begin to deactivate it - this will take some time.")
+ user.visible_message("[user] pauses, then dips [user.p_their()] head in concentration!")
+ to_chat(target, "Your mindshield implant becomes hot as it comes under attack!")
+ sleep(100) //10 seconds - not spawn() so the enthralling takes longer
+ to_chat(user, "The nanobots composing the mindshield implant have been rendered inert. Now to continue.")
+ user.visible_message("[user] relaxes again.")
+ for(var/obj/item/implant/mindshield/L in target)
+ if(L && L.implanted)
+ qdel(L)
+ to_chat(target, "Your mental protection implant unexpectedly falters, dims, dies.")
+ if(3)
+ to_chat(user, "You begin planting the tumor that will control the new thrall...")
+ user.visible_message("A strange energy passes from [user]'s hands into [target]'s head!")
+ to_chat(target, "You feel your memories twisting, morphing. A sense of horror dominates your mind.")
+ if(!do_mob(user, target, 70)) //around 21 seconds total for enthralling, 31 for someone with a mindshield implant
+ to_chat(user, "The enthralling has been interrupted - your target's mind returns to its previous state.")
+ to_chat(target, "You wrest yourself away from [user]'s hands and compose yourself")
+ enthralling = FALSE
+ return
- enthralling = 0
- to_chat(user, "You have enthralled [target]!")
- target.visible_message("[target] looks to have experienced a revelation!", \
- "False faces all dark not real not real not--")
- target.setOxyLoss(0) //In case the shadowling was choking them out
- SSticker.mode.add_thrall(target.mind)
- target.mind.special_role = SPECIAL_ROLE_SHADOWLING_THRALL
+ enthralling = FALSE
+ to_chat(user, "You have enthralled [target]!")
+ target.visible_message("[target] looks to have experienced a revelation!", \
+ "False faces all dark not real not real not--")
+ target.setOxyLoss(0) //In case the shadowling was choking them out
+ SSticker.mode.add_thrall(target.mind)
+ target.mind.special_role = SPECIAL_ROLE_SHADOWLING_THRALL
/obj/effect/proc_holder/spell/targeted/shadowling_regenarmor //Resets a shadowling's species to normal, removes genetic defects, and re-equips their armor
name = "Rapid Re-Hatch"
@@ -405,7 +365,7 @@
reviveThrallAcquired = 1
to_chat(target, "The power of your thralls has granted you the Black Recuperation ability. \
This will, after a short time, bring a dead thrall completely back to life with no bodily defects.")
- target.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/reviveThrall(null))
+ target.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/click/reviveThrall(null))
if(thralls < victory_threshold)
to_chat(target, "You do not have the power to ascend. You require [victory_threshold] thralls, but only [thralls] living thralls are present.")
@@ -571,169 +531,170 @@
-/obj/effect/proc_holder/spell/targeted/reviveThrall
+/obj/effect/proc_holder/spell/targeted/click/reviveThrall
name = "Black Recuperation"
desc = "Revives or empowers a thrall."
panel = "Shadowling Abilities"
range = 1
charge_max = 600
- clothes_req = 0
- include_user = 0
+ clothes_req = FALSE
+ include_user = FALSE
action_icon_state = "revive_thrall"
- humans_only = 1
+ click_radius = -1 // Precision baby
+ selection_activated_message = "You start focusing your powers on mending wounds of allies. Left-click to cast at a target!"
+ selection_deactivated_message = "Your mind relaxes."
+ allowed_type = /mob/living/carbon/human
-/obj/effect/proc_holder/spell/targeted/reviveThrall/cast(list/targets, mob/user = usr)
+/obj/effect/proc_holder/spell/targeted/click/reviveThrall/can_cast(mob/user = usr)
if(!shadowling_check(user))
- charge_counter = charge_max
- return
- for(var/mob/living/carbon/human/thrallToRevive in targets)
- var/choice = alert(user,"Empower a living thrall or revive a dead one?",,"Empower","Revive","Cancel")
- switch(choice)
- if("Empower")
- if(!is_thrall(thrallToRevive))
- to_chat(user, "[thrallToRevive] is not a thrall.")
- charge_counter = charge_max
- return
- if(thrallToRevive.stat != CONSCIOUS)
- to_chat(user, "[thrallToRevive] must be conscious to become empowered.")
- charge_counter = charge_max
- return
- if(isshadowlinglesser(thrallToRevive))
- to_chat(user, "[thrallToRevive] is already empowered.")
- charge_counter = charge_max
- return
- var/empowered_thralls = 0
- for(var/datum/mind/M in SSticker.mode.shadowling_thralls)
- if(!ishuman(M.current))
- return
- var/mob/living/carbon/human/H = M.current
- if(isshadowlinglesser(H))
- empowered_thralls++
- if(empowered_thralls >= EMPOWERED_THRALL_LIMIT)
- to_chat(user, "You cannot spare this much energy. There are too many empowered thralls.")
- charge_counter = charge_max
- return
- user.visible_message("[user] places [user.p_their()] hands over [thrallToRevive]'s face, red light shining from beneath.", \
- "You place your hands on [thrallToRevive]'s face and begin gathering energy...")
- to_chat(thrallToRevive, "[user] places [user.p_their()] hands over your face. You feel energy gathering. Stand still...")
- if(!do_mob(user, thrallToRevive, 80))
- to_chat(user, "Your concentration snaps. The flow of energy ebbs.")
- charge_counter = charge_max
- return
- to_chat(user, "You release a massive surge of power into [thrallToRevive]!")
- user.visible_message("Red lightning surges into [thrallToRevive]'s face!")
- playsound(thrallToRevive, 'sound/weapons/egloves.ogg', 50, 1)
- playsound(thrallToRevive, 'sound/machines/defib_zap.ogg', 50, 1)
- user.Beam(thrallToRevive,icon_state="red_lightning",icon='icons/effects/effects.dmi',time=1)
- thrallToRevive.Weaken(5)
- thrallToRevive.visible_message("[thrallToRevive] collapses, [thrallToRevive.p_their()] skin and face distorting!", \
- "AAAAAAAAAAAAAAAAAAAGH-")
- sleep(20)
- thrallToRevive.visible_message("[thrallToRevive] slowly rises, no longer recognizable as human.", \
- "You feel new power flow into you. You have been gifted by your masters. You now closely resemble them. You are empowered in \
- darkness but wither slowly in light. In addition, you now have glare and true shadow walk.")
- thrallToRevive.set_species(/datum/species/shadow/ling/lesser)
- thrallToRevive.mind.RemoveSpell(/obj/effect/proc_holder/spell/targeted/lesser_shadow_walk)
- thrallToRevive.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/glare(null))
- thrallToRevive.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/shadow_walk(null))
- if("Revive")
- if(!is_thrall(thrallToRevive))
- to_chat(user, "[thrallToRevive] is not a thrall.")
- charge_counter = charge_max
- return
- if(thrallToRevive.stat != DEAD)
- to_chat(user, "[thrallToRevive] is not dead.")
- charge_counter = charge_max
- return
- user.visible_message("[user] kneels over [thrallToRevive], placing [user.p_their()] hands on [thrallToRevive.p_their()] chest.", \
- "You crouch over the body of your thrall and begin gathering energy...")
- thrallToRevive.notify_ghost_cloning("Your masters are resuscitating you! Re-enter your corpse if you wish to be brought to life.", source = thrallToRevive)
- if(!do_mob(user, thrallToRevive, 30))
- to_chat(user, "Your concentration snaps. The flow of energy ebbs.")
- charge_counter = charge_max
- return
- to_chat(user, "You release a massive surge of power into [thrallToRevive]!")
- user.visible_message("Red lightning surges from [user]'s hands into [thrallToRevive]'s chest!")
- playsound(thrallToRevive, 'sound/weapons/egloves.ogg', 50, 1)
- playsound(thrallToRevive, 'sound/machines/defib_zap.ogg', 50, 1)
- user.Beam(thrallToRevive,icon_state="red_lightning",icon='icons/effects/effects.dmi',time=1)
- sleep(10)
- if(thrallToRevive.revive())
- thrallToRevive.visible_message("[thrallToRevive] heaves in breath, dim red light shining in [thrallToRevive.p_their()] eyes.", \
- "You have returned. One of your masters has brought you from the darkness beyond.")
- thrallToRevive.Weaken(4)
- thrallToRevive.emote("gasp")
- playsound(thrallToRevive, "bodyfall", 50, 1)
- else
- charge_counter = charge_max
- return
+ return FALSE
+ return ..()
-/obj/effect/proc_holder/spell/targeted/shadowling_extend_shuttle
+/obj/effect/proc_holder/spell/targeted/click/reviveThrall/valid_target(mob/living/carbon/human/target, user)
+ if(!..())
+ return FALSE
+
+ return is_thrall(target)
+
+/obj/effect/proc_holder/spell/targeted/click/reviveThrall/cast(list/targets, mob/user = usr)
+ var/mob/living/carbon/human/thrallToRevive = targets[1]
+ if(thrallToRevive.stat == CONSCIOUS)
+ if(isshadowlinglesser(thrallToRevive))
+ to_chat(user, "[thrallToRevive] is already empowered.")
+ revert_cast(user)
+ return
+ var/empowered_thralls = 0
+ for(var/datum/mind/M in SSticker.mode.shadowling_thralls)
+ if(!ishuman(M.current))
+ return
+ var/mob/living/carbon/human/H = M.current
+ if(isshadowlinglesser(H))
+ empowered_thralls++
+ if(empowered_thralls >= EMPOWERED_THRALL_LIMIT)
+ to_chat(user, "You cannot spare this much energy. There are too many empowered thralls.")
+ revert_cast(user)
+ return
+ user.visible_message("[user] places [user.p_their()] hands over [thrallToRevive]'s face, red light shining from beneath.", \
+ "You place your hands on [thrallToRevive]'s face and begin gathering energy...")
+ to_chat(thrallToRevive, "[user] places [user.p_their()] hands over your face. You feel energy gathering. Stand still...")
+ if(!do_mob(user, thrallToRevive, 80))
+ to_chat(user, "Your concentration snaps. The flow of energy ebbs.")
+ revert_cast(user)
+ return
+ to_chat(user, "You release a massive surge of power into [thrallToRevive]!")
+ user.visible_message("Red lightning surges into [thrallToRevive]'s face!")
+ playsound(thrallToRevive, 'sound/weapons/egloves.ogg', 50, 1)
+ playsound(thrallToRevive, 'sound/machines/defib_zap.ogg', 50, 1)
+ user.Beam(thrallToRevive,icon_state="red_lightning",icon='icons/effects/effects.dmi',time=1)
+ thrallToRevive.Weaken(5)
+ thrallToRevive.visible_message("[thrallToRevive] collapses, [thrallToRevive.p_their()] skin and face distorting!", \
+ "AAAAAAAAAAAAAAAAAAAGH-")
+ sleep(20)
+ thrallToRevive.visible_message("[thrallToRevive] slowly rises, no longer recognizable as human.", \
+ "You feel new power flow into you. You have been gifted by your masters. You now closely resemble them. You are empowered in \
+ darkness but wither slowly in light. In addition, you now have glare and true shadow walk.")
+ thrallToRevive.set_species(/datum/species/shadow/ling/lesser)
+ thrallToRevive.mind.RemoveSpell(/obj/effect/proc_holder/spell/targeted/lesser_shadow_walk)
+ thrallToRevive.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/click/glare(null))
+ thrallToRevive.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/shadow_walk(null))
+ else if(thrallToRevive.stat == DEAD)
+ user.visible_message("[user] kneels over [thrallToRevive], placing [user.p_their()] hands on [thrallToRevive.p_their()] chest.", \
+ "You crouch over the body of your thrall and begin gathering energy...")
+ thrallToRevive.notify_ghost_cloning("Your masters are resuscitating you! Re-enter your corpse if you wish to be brought to life.", source = thrallToRevive)
+ if(!do_mob(user, thrallToRevive, 30))
+ to_chat(user, "Your concentration snaps. The flow of energy ebbs.")
+ revert_cast(user)
+ return
+ to_chat(user, "You release a massive surge of power into [thrallToRevive]!")
+ user.visible_message("Red lightning surges from [user]'s hands into [thrallToRevive]'s chest!")
+ playsound(thrallToRevive, 'sound/weapons/egloves.ogg', 50, 1)
+ playsound(thrallToRevive, 'sound/machines/defib_zap.ogg', 50, 1)
+ user.Beam(thrallToRevive,icon_state="red_lightning",icon='icons/effects/effects.dmi',time=1)
+ sleep(10)
+ if(thrallToRevive.revive())
+ thrallToRevive.visible_message("[thrallToRevive] heaves in breath, dim red light shining in [thrallToRevive.p_their()] eyes.", \
+ "You have returned. One of your masters has brought you from the darkness beyond.")
+ thrallToRevive.Weaken(4)
+ thrallToRevive.emote("gasp")
+ playsound(thrallToRevive, "bodyfall", 50, 1)
+ else
+ to_chat(user, "The target must be awake to empower or dead to revive.")
+ revert_cast(user)
+
+/obj/effect/proc_holder/spell/targeted/click/shadowling_extend_shuttle
name = "Destroy Engines"
desc = "Extends the time of the emergency shuttle's arrival by ten minutes using a life force of our enemy. Shuttle will be unable to be recalled. This can only be used once."
panel = "Shadowling Abilities"
range = 1
- clothes_req = 0
+ clothes_req = FALSE
charge_max = 600
+ click_radius = -1 // Precision baby
+ selection_activated_message = "You start gathering destructive powers to delay the shuttle. Left-click to cast at a target!"
+ selection_deactivated_message = "Your mind relaxes."
+ allowed_type = /mob/living/carbon/human
action_icon_state = "extend_shuttle"
var/global/extendlimit = 0
-/obj/effect/proc_holder/spell/targeted/shadowling_extend_shuttle/cast(list/targets, mob/user = usr)
+/obj/effect/proc_holder/spell/targeted/click/shadowling_extend_shuttle/can_cast(mob/user = usr, charge_check = TRUE, show_message = FALSE)
if(!shadowling_check(user))
- charge_counter = charge_max
- return
+ return FALSE
if(extendlimit == 1)
- to_chat(user, "Shuttle was already delayed.")
- charge_counter = charge_max
- return
- for(var/mob/living/carbon/human/target in targets)
- if(target.stat)
- charge_counter = charge_max
- return
- if(is_shadow_or_thrall(target))
- to_chat(user, "[target] must not be an ally.")
- charge_counter = charge_max
- return
- if(SSshuttle.emergency.mode != SHUTTLE_CALL)
+ if(show_message)
+ to_chat(user, "Shuttle was already delayed.")
+ return FALSE
+ if(SSshuttle.emergency.mode != SHUTTLE_CALL)
+ if(show_message)
to_chat(user, "The shuttle must be inbound only to the station.")
- charge_counter = charge_max
- return
- var/mob/living/carbon/human/M = target
- user.visible_message("[user]'s eyes flash a bright red!", \
- "You begin to draw [M]'s life force.")
- M.visible_message("[M]'s face falls slack, [M.p_their()] jaw slightly distending.", \
- "You are suddenly transported... far, far away...")
- extendlimit = 1
- if(!do_after(user, 150, target = M))
- extendlimit = 0
- to_chat(M, "You are snapped back to reality, your haze dissipating!")
- to_chat(user, "You have been interrupted. The draw has failed.")
- return
- to_chat(user, "You project [M]'s life force toward the approaching shuttle, extending its arrival duration!")
- M.visible_message("[M]'s eyes suddenly flare red. They proceed to collapse on the floor, not breathing.", \
- "...speeding by... ...pretty blue glow... ...touch it... ...no glow now... ...no light... ...nothing at all...")
- M.death()
- if(SSshuttle.emergency.mode == SHUTTLE_CALL)
- var/more_minutes = 6000
- var/timer = SSshuttle.emergency.timeLeft(1) + more_minutes
- GLOB.event_announcement.Announce("Major system failure aboard the emergency shuttle. This will extend its arrival time by approximately 10 minutes and the shuttle is unable to be recalled.", "System Failure", 'sound/misc/notice1.ogg')
- SSshuttle.emergency.setTimer(timer)
- SSshuttle.emergency.canRecall = FALSE
- user.mind.spell_list.Remove(src) //Can only be used once!
- qdel(src)
+ return FALSE
+ return ..()
+
+/obj/effect/proc_holder/spell/targeted/click/shadowling_extend_shuttle/valid_target(mob/living/carbon/human/target, user)
+ if(!..())
+ return FALSE
+ return !target.stat && !is_shadow_or_thrall(target)
+
+
+/obj/effect/proc_holder/spell/targeted/click/shadowling_extend_shuttle/cast(list/targets, mob/user = usr)
+ var/mob/living/carbon/human/target = targets[1]
+
+ user.visible_message("[user]'s eyes flash a bright red!", \
+ "You begin to draw [target]'s life force.")
+ target.visible_message("[target]'s face falls slack, [target.p_their()] jaw slightly distending.", \
+ "You are suddenly transported... far, far away...")
+ extendlimit = 1
+ if(!do_after(user, 150, target = target))
+ extendlimit = 0
+ to_chat(target, "You are snapped back to reality, your haze dissipating!")
+ to_chat(user, "You have been interrupted. The draw has failed.")
+ return
+ to_chat(user, "You project [target]'s life force toward the approaching shuttle, extending its arrival duration!")
+ target.visible_message("[target]'s eyes suddenly flare red. They proceed to collapse on the floor, not breathing.", \
+ "...speeding by... ...pretty blue glow... ...touch it... ...no glow now... ...no light... ...nothing at all...")
+ target.death()
+ if(SSshuttle.emergency.mode == SHUTTLE_CALL)
+ var/more_minutes = 6000
+ var/timer = SSshuttle.emergency.timeLeft(1) + more_minutes
+ GLOB.event_announcement.Announce("Major system failure aboard the emergency shuttle. This will extend its arrival time by approximately 10 minutes and the shuttle is unable to be recalled.", "System Failure", 'sound/misc/notice1.ogg')
+ SSshuttle.emergency.setTimer(timer)
+ SSshuttle.emergency.canRecall = FALSE
+ user.mind.spell_list.Remove(src) //Can only be used once!
+ qdel(src)
// ASCENDANT ABILITIES BEYOND THIS POINT //
-/obj/effect/proc_holder/spell/targeted/annihilate
+/obj/effect/proc_holder/spell/targeted/click/annihilate
name = "Annihilate"
desc = "Gibs someone instantly."
panel = "Ascendant"
range = 7
- charge_max = 0
- clothes_req = 0
+ charge_max = FALSE
+ clothes_req = FALSE
action_icon_state = "annihilate"
+ selection_activated_message = "You start thinking about gibs. Left-click to cast at a target!"
+ selection_deactivated_message = "Your mind relaxes."
+ allowed_type = /mob/living/carbon/human
-/obj/effect/proc_holder/spell/targeted/annihilate/cast(list/targets, mob/user = usr)
+/obj/effect/proc_holder/spell/targeted/click/annihilate/cast(list/targets, mob/user = usr)
var/mob/living/simple_animal/ascendant_shadowling/SHA = user
if(SHA.phasing)
to_chat(user, "You are not in the same plane of existence. Unphase first.")
@@ -756,45 +717,42 @@
-/obj/effect/proc_holder/spell/targeted/hypnosis
+/obj/effect/proc_holder/spell/targeted/click/hypnosis
name = "Hypnosis"
desc = "Instantly enthralls a human."
panel = "Ascendant"
range = 7
- charge_max = 0
- clothes_req = 0
+ charge_max = FALSE
+ clothes_req = FALSE
action_icon_state = "enthrall"
-/obj/effect/proc_holder/spell/targeted/hypnosis/cast(list/targets, mob/user = usr)
- var/mob/living/simple_animal/ascendant_shadowling/SHA = user
- if(SHA.phasing)
- charge_counter = charge_max
- to_chat(user, "You are not in the same plane of existence. Unphase first.")
- return
+ click_radius = -1
+ selection_activated_message = "You start preparing to mindwash over a mortal mind. Left-click to cast at a target!"
+ selection_deactivated_message = "Your mind relaxes."
+ allowed_type = /mob/living/carbon/human
- for(var/mob/living/carbon/human/target in targets)
- if(is_shadow_or_thrall(target))
- to_chat(user, "You cannot enthrall an ally.")
- charge_counter = charge_max
- return
- if(!target.ckey || !target.mind)
- to_chat(user, "The target has no mind.")
- charge_counter = charge_max
- return
- if(target.stat)
- to_chat(user, "The target must be conscious.")
- charge_counter = charge_max
- return
- if(!ishuman(target))
- to_chat(user, "You can only enthrall humans.")
- charge_counter = charge_max
- return
+/obj/effect/proc_holder/spell/targeted/click/hypnosis/can_cast(mob/living/simple_animal/ascendant_shadowling/user = usr, charge_check = TRUE, show_message = FALSE)
+ if(!istype(user))
+ return FALSE
+ if(user.phasing)
+ if(show_message)
+ to_chat(user, "You are not in the same plane of existence. Unphase first.")
+ return FALSE
+ return ..()
- to_chat(user, "You instantly rearrange [target]'s memories, hyptonitizing [target.p_them()] into a thrall.")
- to_chat(target, "An agonizing spike of pain drives into your mind, and--")
- SSticker.mode.add_thrall(target.mind)
- target.mind.special_role = SPECIAL_ROLE_SHADOWLING_THRALL
- target.add_language("Shadowling Hivemind")
+/obj/effect/proc_holder/spell/targeted/click/hypnosis/valid_target(mob/living/carbon/human/target, user)
+ if(!..())
+ return FALSE
+ return !is_shadow_or_thrall(target) && target.ckey && target.mind && !target.stat
+
+/obj/effect/proc_holder/spell/targeted/click/hypnosis/cast(list/targets, mob/user = usr)
+ var/mob/living/carbon/human/target = targets[1]
+
+ to_chat(user, "You instantly rearrange [target]'s memories, hyptonitizing [target.p_them()] into a thrall.")
+ to_chat(target, "An agonizing spike of pain drives into your mind, and--")
+ SSticker.mode.add_thrall(target.mind)
+ target.mind.special_role = SPECIAL_ROLE_SHADOWLING_THRALL
+ target.add_language("Shadowling Hivemind")
diff --git a/code/game/gamemodes/shadowling/special_shadowling_abilities.dm b/code/game/gamemodes/shadowling/special_shadowling_abilities.dm
index 83ca06fe4b7..28c934f7d38 100644
--- a/code/game/gamemodes/shadowling/special_shadowling_abilities.dm
+++ b/code/game/gamemodes/shadowling/special_shadowling_abilities.dm
@@ -99,14 +99,14 @@ GLOBAL_LIST_INIT(possibleShadowlingNames, list("U'ruan", "Y`shej", "Nex", "Hel-u
to_chat(H, "Your powers are awoken. You may now live to your fullest extent. Remember your goal. Cooperate with your thralls and allies.")
H.ExtinguishMob()
H.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/shadow_vision(null))
- H.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/enthrall(null))
- H.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/glare(null))
+ H.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/click/enthrall(null))
+ H.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/click/glare(null))
H.mind.AddSpell(new /obj/effect/proc_holder/spell/aoe_turf/veil(null))
H.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/shadow_walk(null))
H.mind.AddSpell(new /obj/effect/proc_holder/spell/aoe_turf/flashfreeze(null))
H.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/collective_mind(null))
H.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/shadowling_regenarmor(null))
- H.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/shadowling_extend_shuttle(null))
+ H.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/click/shadowling_extend_shuttle(null))
QDEL_NULL(H.hud_used)
H.hud_used = new /datum/hud/human(H, ui_style2icon(H.client.prefs.UI_style), H.client.prefs.UI_style_color, H.client.prefs.UI_style_alpha)
@@ -172,8 +172,8 @@ GLOBAL_LIST_INIT(possibleShadowlingNames, list("U'ruan", "Y`shej", "Nex", "Hel-u
H.mind.transfer_to(A)
A.name = H.real_name
A.languages = H.languages
- A.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/annihilate(null))
- A.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/hypnosis(null))
+ A.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/click/annihilate(null))
+ A.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/click/hypnosis(null))
A.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/shadowling_phase_shift(null))
A.mind.AddSpell(new /obj/effect/proc_holder/spell/aoe_turf/ascendant_storm(null))
A.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/shadowlingAscendantTransmit(null))
diff --git a/code/game/gamemodes/vampire/vampire_powers.dm b/code/game/gamemodes/vampire/vampire_powers.dm
index 7906deeb718..406ab50b70d 100644
--- a/code/game/gamemodes/vampire/vampire_powers.dm
+++ b/code/game/gamemodes/vampire/vampire_powers.dm
@@ -15,7 +15,7 @@
if(!gain_desc)
gain_desc = "You have gained \the [src] ability."
-/obj/effect/proc_holder/spell/vampire/cast_check(skipcharge = 0, mob/living/user = usr)
+/obj/effect/proc_holder/spell/vampire/cast_check(charge_check = TRUE, start_recharge = TRUE, mob/living/user = usr)
if(!user.mind)
return 0
if(!ishuman(user))
@@ -45,7 +45,7 @@
return 0
return ..()
-/obj/effect/proc_holder/spell/vampire/can_cast(mob/user = usr)
+/obj/effect/proc_holder/spell/vampire/can_cast(mob/user = usr, charge_check = TRUE, show_message = FALSE)
if(!user.mind)
return 0
if(!ishuman(user))
diff --git a/code/game/gamemodes/wizard/artefact.dm b/code/game/gamemodes/wizard/artefact.dm
index 442e9958fc7..cb0c277b1e6 100644
--- a/code/game/gamemodes/wizard/artefact.dm
+++ b/code/game/gamemodes/wizard/artefact.dm
@@ -61,7 +61,7 @@
switch(href_list["school"])
if("destruction")
M.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/projectile/magic_missile(null))
- M.mind.AddSpell(new /obj/effect/proc_holder/spell/fireball(null))
+ M.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/click/fireball(null))
to_chat(M, "Your service has not gone unrewarded, however. Studying under [H.real_name], you have learned powerful, destructive spells. You are able to cast magic missile and fireball.")
if("bluespace")
M.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/area_teleport/teleport(null))
@@ -74,7 +74,7 @@
to_chat(M, "Your service has not gone unrewarded, however. Studying under [H.real_name], you have learned livesaving survival spells. You are able to cast charge and forcewall.")
if("robeless")
M.mind.AddSpell(new /obj/effect/proc_holder/spell/aoe_turf/knock(null))
- M.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/mind_transfer(null))
+ M.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/click/mind_transfer(null))
to_chat(M, "Your service has not gone unrewarded, however. Studying under [H.real_name], you have learned stealthy, robeless spells. You are able to cast knock and mindswap.")
M.equip_to_slot_or_del(new /obj/item/radio/headset(M), slot_l_ear)
diff --git a/code/game/gamemodes/wizard/spellbook.dm b/code/game/gamemodes/wizard/spellbook.dm
index d0773947c21..efa3303c26b 100644
--- a/code/game/gamemodes/wizard/spellbook.dm
+++ b/code/game/gamemodes/wizard/spellbook.dm
@@ -132,7 +132,7 @@
/datum/spellbook_entry/horseman
name = "Curse of the Horseman"
- spell_type = /obj/effect/proc_holder/spell/targeted/horsemask
+ spell_type = /obj/effect/proc_holder/spell/targeted/click/horsemask
log_name = "HH"
category = "Offensive"
@@ -144,7 +144,7 @@
/datum/spellbook_entry/fireball
name = "Fireball"
- spell_type = /obj/effect/proc_holder/spell/fireball
+ spell_type = /obj/effect/proc_holder/spell/targeted/click/fireball
log_name = "FB"
category = "Offensive"
@@ -257,7 +257,7 @@
/datum/spellbook_entry/mindswap
name = "Mindswap"
- spell_type = /obj/effect/proc_holder/spell/targeted/mind_transfer
+ spell_type = /obj/effect/proc_holder/spell/targeted/click/mind_transfer
log_name = "MT"
category = "Mobility"
@@ -877,7 +877,7 @@
return
/obj/item/spellbook/oneuse/fireball
- spell = /obj/effect/proc_holder/spell/fireball
+ spell = /obj/effect/proc_holder/spell/targeted/click/fireball
spellname = "fireball"
icon_state = "bookfireball"
desc = "This book feels warm to the touch."
@@ -910,7 +910,7 @@
user.EyeBlind(10)
/obj/item/spellbook/oneuse/mindswap
- spell = /obj/effect/proc_holder/spell/targeted/mind_transfer
+ spell = /obj/effect/proc_holder/spell/targeted/click/mind_transfer
spellname = "mindswap"
icon_state = "bookmindswap"
desc = "This book's cover is pristine, though its pages look ragged and torn."
@@ -934,8 +934,8 @@
to_chat(user, "You stare at the book some more, but there doesn't seem to be anything else to learn...")
return
- var/obj/effect/proc_holder/spell/targeted/mind_transfer/swapper = new
- swapper.cast(user, stored_swap, 1)
+ var/obj/effect/proc_holder/spell/targeted/click/mind_transfer/swapper = new
+ swapper.cast(user, stored_swap)
to_chat(stored_swap, "You're suddenly somewhere else... and someone else?!")
to_chat(user, "Suddenly you're staring at [src] again... where are you, who are you?!")
@@ -966,7 +966,7 @@
user.Weaken(20)
/obj/item/spellbook/oneuse/horsemask
- spell = /obj/effect/proc_holder/spell/targeted/horsemask
+ spell = /obj/effect/proc_holder/spell/targeted/click/horsemask
spellname = "horses"
icon_state = "bookhorses"
desc = "This book is more horse than your mind has room for."
diff --git a/code/game/gamemodes/wizard/wizloadouts.dm b/code/game/gamemodes/wizard/wizloadouts.dm
index c050b27f273..518b9a68ca8 100644
--- a/code/game/gamemodes/wizard/wizloadouts.dm
+++ b/code/game/gamemodes/wizard/wizloadouts.dm
@@ -18,7 +18,7 @@
Care should be taken in hiding the item you choose as your phylactery after using Bind Soul, as you cannot revive if it destroyed or too far from your body!
\
Provides Bind Soul, Ethereal Jaunt, Fireball, Rod Form, Disable Tech, and Greater Forcewall."
log_name = "DL"
- spells_path = list(/obj/effect/proc_holder/spell/targeted/lichdom, /obj/effect/proc_holder/spell/targeted/ethereal_jaunt, /obj/effect/proc_holder/spell/fireball, \
+ spells_path = list(/obj/effect/proc_holder/spell/targeted/lichdom, /obj/effect/proc_holder/spell/targeted/ethereal_jaunt, /obj/effect/proc_holder/spell/targeted/click/fireball, \
/obj/effect/proc_holder/spell/targeted/rod_form, /obj/effect/proc_holder/spell/targeted/emplosion/disable_tech, /obj/effect/proc_holder/spell/targeted/forcewall/greater)
is_ragin_restricted = TRUE
diff --git a/code/game/jobs/job/central.dm b/code/game/jobs/job/central.dm
index f91460ba234..8684ca7d1b0 100644
--- a/code/game/jobs/job/central.dm
+++ b/code/game/jobs/job/central.dm
@@ -94,7 +94,7 @@
)
cybernetic_implants = list(
/obj/item/organ/internal/cyberimp/eyes/xray,
- /obj/item/organ/internal/cyberimp/brain/anti_stun,
+ /obj/item/organ/internal/cyberimp/brain/anti_stun/hardened,
/obj/item/organ/internal/cyberimp/chest/nutriment/plus,
/obj/item/organ/internal/cyberimp/arm/combat/centcom
)
diff --git a/code/game/jobs/job/support_chaplain.dm b/code/game/jobs/job/support_chaplain.dm
index b39eed344ae..bfd9c4520f5 100644
--- a/code/game/jobs/job/support_chaplain.dm
+++ b/code/game/jobs/job/support_chaplain.dm
@@ -26,7 +26,7 @@
/obj/item/camera/spooky = 1,
/obj/item/nullrod = 1
)
-
+
/datum/outfit/job/chaplain/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
. = ..()
@@ -77,7 +77,7 @@
new_deity = deity_name
B.deity_name = new_deity
- H.AddSpell(new /obj/effect/proc_holder/spell/targeted/chaplain_bless(null))
+ H.AddSpell(new /obj/effect/proc_holder/spell/targeted/click/chaplain_bless(null))
var/accepted = 0
var/outoftime = 0
diff --git a/code/game/machinery/atmoalter/pump.dm b/code/game/machinery/atmoalter/pump.dm
index 4d54b162466..8d974f97a70 100644
--- a/code/game/machinery/atmoalter/pump.dm
+++ b/code/game/machinery/atmoalter/pump.dm
@@ -1,21 +1,26 @@
+
+/// The maximum `target_pressure` you can set on the pump. Equates to about 1013.25 kPa.
+#define MAX_TARGET_PRESSURE 10 * ONE_ATMOSPHERE
+/// The pump will be siphoning gas.
+#define DIRECTION_IN 0
+/// The pump will be pumping gas out.
+#define DIRECTION_OUT 1
+
/obj/machinery/portable_atmospherics/pump
name = "Portable Air Pump"
-
icon = 'icons/obj/atmos.dmi'
icon_state = "psiphon:0"
- density = 1
-
- var/on = 0
- var/direction_out = 0 //0 = siphoning, 1 = releasing
- var/target_pressure = 100
-
- var/pressuremin = 0
- var/pressuremax = 10 * ONE_ATMOSPHERE
-
+ density = TRUE
volume = 1000
+ /// If the pump is turned on or off.
+ var/on = FALSE
+ /// The direction the pump is operating in. This should be either `DIRECTION_IN` or `DIRECTION_OUT`.
+ var/direction = DIRECTION_IN
+ /// The desired pressure the pump should be outputting, either into the atmosphere, or into a holding tank.
+ var/target_pressure = 101.325
/obj/machinery/portable_atmospherics/pump/update_icon()
- src.overlays = 0
+ overlays = 0
if(on)
icon_state = "psiphon:1"
@@ -39,7 +44,7 @@
on = !on
if(prob(100/severity))
- direction_out = !direction_out
+ direction = !direction
target_pressure = rand(0,1300)
update_icon()
@@ -54,7 +59,7 @@
environment = holding.air_contents
else
environment = loc.return_air()
- if(direction_out)
+ if(direction == DIRECTION_OUT)
var/pressure_delta = target_pressure - environment.return_pressure()
//Can not have a pressure delta that would cause environment pressure > tank pressure
@@ -87,9 +92,7 @@
air_update_turf()
air_contents.merge(removed)
- //src.update_icon()
- src.updateDialog()
return
/obj/machinery/portable_atmospherics/pump/return_air()
@@ -102,72 +105,74 @@
if(on)
on = FALSE
update_icon()
- else if(on && holding && direction_out)
+ else if(on && holding && direction == DIRECTION_OUT)
investigate_log("[key_name(user)] started a transfer into [holding].
", "atmos")
-/obj/machinery/portable_atmospherics/pump/attack_ai(var/mob/user as mob)
- src.add_hiddenprint(user)
- return src.attack_hand(user)
+/obj/machinery/portable_atmospherics/pump/attack_ai(mob/user)
+ add_hiddenprint(user)
+ return attack_hand(user)
-/obj/machinery/portable_atmospherics/pump/attack_ghost(var/mob/user as mob)
- return src.attack_hand(user)
+/obj/machinery/portable_atmospherics/pump/attack_ghost(mob/user)
+ return attack_hand(user)
-/obj/machinery/portable_atmospherics/pump/attack_hand(var/mob/user as mob)
- ui_interact(user)
+/obj/machinery/portable_atmospherics/pump/attack_hand(mob/user)
+ tgui_interact(user)
-/obj/machinery/portable_atmospherics/pump/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1, var/datum/topic_state/state = GLOB.physical_state)
- // update the ui if it exists, returns null if no ui is passed/found
- ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open)
+/obj/machinery/portable_atmospherics/pump/tgui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/tgui_state/state = GLOB.tgui_default_state)
+ ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
if(!ui)
- // the ui does not exist, so we'll create a new() one
- // for a list of parameters and their descriptions see the code docs in \code\modules\nano\nanoui.dm
- ui = new(user, src, ui_key, "portpump.tmpl", "Portable Pump", 480, 400, state = state)
- // open the new ui window
+ ui = new(user, src, ui_key, "PortablePump", "Portable Pump", 434, 377, master_ui, state)
ui.open()
- // auto update every Master Controller tick
- ui.set_auto_update(1)
+ ui.set_autoupdate(TRUE)
-/obj/machinery/portable_atmospherics/pump/ui_data(mob/user, ui_key = "main", datum/topic_state/state = GLOB.physical_state)
- var/data[0]
- data["portConnected"] = connected_port ? 1 : 0
- data["tankPressure"] = round(air_contents.return_pressure() > 0 ? air_contents.return_pressure() : 0)
- data["targetpressure"] = round(target_pressure)
- data["pump_dir"] = direction_out
- data["minpressure"] = round(pressuremin)
- data["maxpressure"] = round(pressuremax)
- data["on"] = on ? 1 : 0
-
- data["hasHoldingTank"] = holding ? 1 : 0
+/obj/machinery/portable_atmospherics/pump/tgui_data(mob/user)
+ var/list/data = list(
+ "on" = on,
+ "direction" = direction,
+ "port_connected" = connected_port ? TRUE : FALSE,
+ "max_target_pressure" = MAX_TARGET_PRESSURE,
+ "target_pressure" = round(target_pressure, 0.001),
+ "tank_pressure" = air_contents.return_pressure() > 0 ? round(air_contents.return_pressure(), 0.001) : 0
+ )
if(holding)
- data["holdingTank"] = list("name" = holding.name, "tankPressure" = round(holding.air_contents.return_pressure() > 0 ? holding.air_contents.return_pressure() : 0))
+ data["has_holding_tank"] = TRUE
+ data["holding_tank"] = list("name" = holding.name, "tank_pressure" = holding.air_contents.return_pressure() > 0 ? round(holding.air_contents.return_pressure(), 0.001) : 0)
+ else
+ data["has_holding_tank"] = FALSE
return data
-/obj/machinery/portable_atmospherics/pump/Topic(href, href_list)
+/obj/machinery/portable_atmospherics/pump/tgui_act(action, list/params)
if(..())
- return 1
+ return
- if(href_list["power"])
- on = !on
- if(on && direction_out)
- investigate_log("[key_name(usr)] started a transfer into [holding].
", "atmos")
- update_icon()
+ switch(action)
+ if("power")
+ on = !on
+ if(on && direction == DIRECTION_OUT)
+ investigate_log("[key_name(usr)] started a transfer into [holding].
", "atmos")
+ update_icon()
+ return TRUE
- if(href_list["direction"])
- direction_out = !direction_out
- if(on && holding)
- investigate_log("[key_name(usr)] started a transfer into [holding].
", "atmos")
+ if("set_direction")
+ if(text2num(params["direction"]) == DIRECTION_IN)
+ direction = DIRECTION_IN
+ else
+ direction = DIRECTION_OUT
+ if(on && holding)
+ investigate_log("[key_name(usr)] started a transfer into [holding].
", "atmos")
+ return TRUE
- if(href_list["remove_tank"])
- if(holding)
- on = FALSE
- holding.loc = loc
- holding = null
- update_icon()
+ if("remove_tank")
+ if(holding)
+ on = FALSE
+ holding.forceMove(get_turf(src))
+ holding = null
+ update_icon()
+ return TRUE
- if(href_list["pressure_adj"])
- var/diff = text2num(href_list["pressure_adj"])
- target_pressure = clamp(target_pressure+diff, pressuremin, pressuremax)
- update_icon()
+ if("set_pressure")
+ target_pressure = clamp(text2num(params["pressure"]), 0, MAX_TARGET_PRESSURE)
+ return TRUE
- src.add_fingerprint(usr)
+ add_fingerprint(usr)
diff --git a/code/game/machinery/computer/camera.dm b/code/game/machinery/computer/camera.dm
index 3f8fc080bf8..cfe82db29e8 100644
--- a/code/game/machinery/computer/camera.dm
+++ b/code/game/machinery/computer/camera.dm
@@ -130,6 +130,10 @@
// adding a template with the key "mapHeader" replaces the map header content
ui.add_template("mapHeader", "sec_camera_map_header.tmpl")
+ // Send nanomaps
+ var/datum/asset/nanomaps = get_asset_datum(/datum/asset/simple/nanomaps)
+ nanomaps.send(user)
+
ui.open()
/obj/machinery/computer/security/ui_data(mob/user, ui_key = "main", datum/topic_state/state = GLOB.default_state)
diff --git a/code/game/machinery/vending.dm b/code/game/machinery/vending.dm
index 1ac341db260..8e87506803b 100644
--- a/code/game/machinery/vending.dm
+++ b/code/game/machinery/vending.dm
@@ -447,7 +447,7 @@
/obj/machinery/vending/tgui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = TRUE, datum/tgui/master_ui = null, datum/tgui_state/state = GLOB.tgui_default_state)
ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
if(!ui)
- var/estimated_height = 100 + (length(product_records) * 34)
+ var/estimated_height = 100 + min(length(product_records) * 34, 500)
if(length(prices) > 0)
estimated_height += 100 // to account for the "current user" interface
ui = new(user, src, ui_key, "Vending", name, 470, estimated_height, master_ui, state)
diff --git a/code/game/objects/items/devices/radio/radio.dm b/code/game/objects/items/devices/radio/radio.dm
index 22e333ed7b9..ed6b0190502 100644
--- a/code/game/objects/items/devices/radio/radio.dm
+++ b/code/game/objects/items/devices/radio/radio.dm
@@ -230,6 +230,9 @@ GLOBAL_LIST_INIT(default_medbay_channels, list(
if(!(freq in internal_channels))
return FALSE
+ if(isrobot(user))
+ return FALSE // cyborgs and drones are not allowed to remotely re-tune intercomms, etc
+
return user.has_internal_radio_channel_access(user, internal_channels[freq])
/mob/proc/has_internal_radio_channel_access(var/mob/user, var/list/req_one_accesses)
@@ -606,13 +609,14 @@ GLOBAL_LIST_INIT(default_medbay_channels, list(
/obj/item/radio/borg
name = "Cyborg Radio"
var/mob/living/silicon/robot/myborg = null // Cyborg which owns this radio. Used for power checks
- var/obj/item/encryptionkey/keyslot = new /obj/item/encryptionkey/heads/ai_integrated //Borg radios can handle a single encryption key
+ var/obj/item/encryptionkey/keyslot // Borg radios can handle a single encryption key
icon = 'icons/obj/robot_component.dmi' // Cyborgs radio icons should look like the component.
icon_state = "radio"
has_loudspeaker = TRUE
loudspeaker = FALSE
canhear_range = 0
dog_fashion = null
+ freqlock = TRUE // don't let cyborgs change the default channel of their internal radio away from common
/obj/item/radio/borg/syndicate
keyslot = new /obj/item/encryptionkey/syndicate/nukeops
diff --git a/code/game/objects/items/weapons/highlander_swords.dm b/code/game/objects/items/weapons/highlander_swords.dm
index 330da6beba0..568b023d051 100644
--- a/code/game/objects/items/weapons/highlander_swords.dm
+++ b/code/game/objects/items/weapons/highlander_swords.dm
@@ -25,14 +25,14 @@
return ..()
/obj/item/claymore/highlander/equipped(mob/user, slot)
- if(!ishuman(user))
+ if(!ishuman(user) || !user.mind)
return
var/mob/living/carbon/human/H = user
if(slot == slot_r_hand || slot == slot_l_hand)
- if(H.martial_art && H.martial_art != style)
- style.teach(H, 1)
+ if(H.mind.martial_art && H.mind.martial_art != style)
+ style.teach(H, TRUE)
to_chat(H, "THERE CAN ONLY BE ONE!")
- else if(H.martial_art && H.martial_art == style)
+ else if(H.mind.martial_art && H.mind.martial_art == style)
style.remove(H)
var/obj/item/claymore/highlander/sword = H.is_in_hands(/obj/item/claymore/highlander)
if(sword)
diff --git a/code/game/objects/items/weapons/implants/implant_krav_maga.dm b/code/game/objects/items/weapons/implants/implant_krav_maga.dm
index 3c2666f3d66..9c33f43950a 100644
--- a/code/game/objects/items/weapons/implants/implant_krav_maga.dm
+++ b/code/game/objects/items/weapons/implants/implant_krav_maga.dm
@@ -17,12 +17,12 @@
/obj/item/implant/krav_maga/activate()
var/mob/living/carbon/human/H = imp_in
- if(!ishuman(H))
+ if(!ishuman(H) || !H.mind)
return
- if(istype(H.martial_art, /datum/martial_art/krav_maga))
+ if(istype(H.mind.martial_art, /datum/martial_art/krav_maga))
style.remove(H)
else
- style.teach(H,1)
+ style.teach(H, TRUE)
/obj/item/implanter/krav_maga
name = "implanter (krav maga)"
diff --git a/code/game/objects/structures/tables_racks.dm b/code/game/objects/structures/tables_racks.dm
index bdab5ca2466..e9abfca2dac 100644
--- a/code/game/objects/structures/tables_racks.dm
+++ b/code/game/objects/structures/tables_racks.dm
@@ -90,7 +90,7 @@
..()
if(climber)
climber.Weaken(2)
- climber.visible_message("[climber.name] has been knocked off the table", "You've been knocked off the table", "You see [climber.name] get knocked off the table")
+ climber.visible_message("[climber.name] has been knocked off the table", "You've been knocked off the table", "You hear [climber.name] get knocked off the table")
else if(Adjacent(user) && user.pulling && user.pulling.pass_flags & PASSTABLE)
user.Move_Pulled(src)
if(user.pulling.loc == loc)
diff --git a/code/modules/client/asset_cache.dm b/code/modules/client/asset_cache.dm
index 8d8d83b4ae2..e39c62e5cd7 100644
--- a/code/modules/client/asset_cache.dm
+++ b/code/modules/client/asset_cache.dm
@@ -262,7 +262,6 @@ GLOBAL_LIST_EMPTY(asset_datums)
var/list/common_dirs = list(
"nano/assets/",
"nano/codemirror/",
- "nano/images/",
"nano/layouts/"
)
var/list/uncommon_dirs = list(
@@ -376,3 +375,14 @@ GLOBAL_LIST_EMPTY(asset_datums)
"font-awesome.css" = 'html/font-awesome/css/all.min.css',
"v4shim.css" = 'html/font-awesome/css/v4-shims.min.css'
)
+
+// Nanomaps
+/datum/asset/simple/nanomaps
+ // It REALLY doesnt matter too much if these arent up to date
+ // They are relatively big
+ verify = FALSE
+ assets = list(
+ "Cyberiad_nanomap_z1.png" = 'icons/_nanomaps/Cyberiad_nanomap_z1.png',
+ "Delta_nanomap_z1.png" = 'icons/_nanomaps/Delta_nanomap_z1.png',
+ "MetaStation_nanomap_z1.png" = 'icons/_nanomaps/MetaStation_nanomap_z1.png',
+ )
diff --git a/code/modules/client/client defines.dm b/code/modules/client/client defines.dm
index 72537f8ccc3..46ec605e71d 100644
--- a/code/modules/client/client defines.dm
+++ b/code/modules/client/client defines.dm
@@ -36,7 +36,10 @@
////////////
//SECURITY//
////////////
- var/next_allowed_topic_time = 10
+
+ ///Used for limiting the rate of topic sends by the client to avoid abuse
+ var/list/topiclimiter
+
// comment out the line below when debugging locally to enable the options & messages menu
//control_freak = 1
diff --git a/code/modules/client/client procs.dm b/code/modules/client/client procs.dm
index ae503be111b..ea099ea53b3 100644
--- a/code/modules/client/client procs.dm
+++ b/code/modules/client/client procs.dm
@@ -11,6 +11,13 @@
#define SUGGESTED_CLIENT_VERSION 511 // only integers (e.g: 510, 511) useful here. Does not properly handle minor versions (e.g: 510.58, 511.848)
#define SSD_WARNING_TIMER 30 // cycles, not seconds, so 30=60s
+#define LIMITER_SIZE 5
+#define CURRENT_SECOND 1
+#define SECOND_COUNT 2
+#define CURRENT_MINUTE 3
+#define MINUTE_COUNT 4
+#define ADMINSWARNED_AT 5
+
/*
When somebody clicks a link in game, this Topic is called first.
It does the stuff in this proc and then is redirected to the Topic() proc for the src=[0xWhatever]
@@ -59,10 +66,38 @@
if(href_list["_src_"] == "chat")
return chatOutput.Topic(href, href_list)
- //Reduces spamming of links by dropping calls that happen during the delay period
- if(next_allowed_topic_time > world.time)
- return
- next_allowed_topic_time = world.time + TOPIC_SPAM_DELAY
+ // Rate limiting
+ var/mtl = 100 // 100 topics per minute
+ if (!holder) // Admins are allowed to spam click, deal with it.
+ var/minute = round(world.time, 600)
+ if (!topiclimiter)
+ topiclimiter = new(LIMITER_SIZE)
+ if (minute != topiclimiter[CURRENT_MINUTE])
+ topiclimiter[CURRENT_MINUTE] = minute
+ topiclimiter[MINUTE_COUNT] = 0
+ topiclimiter[MINUTE_COUNT] += 1
+ if (topiclimiter[MINUTE_COUNT] > mtl)
+ var/msg = "Your previous action was ignored because you've done too many in a minute."
+ if (minute != topiclimiter[ADMINSWARNED_AT]) //only one admin message per-minute. (if they spam the admins can just boot/ban them)
+ topiclimiter[ADMINSWARNED_AT] = minute
+ msg += " Administrators have been informed."
+ log_game("[key_name(src)] Has hit the per-minute topic limit of [mtl] topic calls in a given game minute")
+ message_admins("[ADMIN_LOOKUPFLW(usr)] Has hit the per-minute topic limit of [mtl] topic calls in a given game minute")
+ to_chat(src, "[msg]")
+ return
+
+ var/stl = 10 // 10 topics a second
+ if (!holder) // Admins are allowed to spam click, deal with it.
+ var/second = round(world.time, 10)
+ if (!topiclimiter)
+ topiclimiter = new(LIMITER_SIZE)
+ if (second != topiclimiter[CURRENT_SECOND])
+ topiclimiter[CURRENT_SECOND] = second
+ topiclimiter[SECOND_COUNT] = 0
+ topiclimiter[SECOND_COUNT] += 1
+ if (topiclimiter[SECOND_COUNT] > stl)
+ to_chat(src, "Your previous action was ignored because you've done too many in a second")
+ return
//search the href for script injection
if( findtext(href,"