"
-#undef VV_HTML_ENCODE
+#undef VV_HTML_ENCODE
\ No newline at end of file
diff --git a/code/modules/antagonists/blob/blob/blobs/shield.dm b/code/modules/antagonists/blob/blob/blobs/shield.dm
index bc4e517ced..384df935f5 100644
--- a/code/modules/antagonists/blob/blob/blobs/shield.dm
+++ b/code/modules/antagonists/blob/blob/blobs/shield.dm
@@ -45,13 +45,15 @@
desc = "A solid wall of slightly twitching tendrils with a reflective glow."
damaged_desc = "A wall of twitching tendrils with a reflective glow."
icon_state = "blob_glow"
- flags_1 = CHECK_RICOCHET_1
point_return = 8
max_integrity = 100
brute_resist = 1
explosion_block = 2
-/obj/structure/blob/shield/reflective/handle_ricochet(obj/item/projectile/P)
+/obj/structure/blob/shield/reflective/check_projectile_ricochet(obj/item/projectile/P)
+ return PROJECTILE_RICOCHET_FORCE
+
+/obj/structure/blob/shield/reflective/handle_projectile_ricochet(obj/item/projectile/P)
var/turf/p_turf = get_turf(P)
var/face_direction = get_dir(src, p_turf)
var/face_angle = dir2angle(face_direction)
@@ -61,4 +63,4 @@
var/new_angle_s = SIMPLIFY_DEGREES(face_angle + incidence_s)
P.setAngle(new_angle_s)
visible_message("[P] reflects off [src]!")
- return TRUE
\ No newline at end of file
+ return TRUE
diff --git a/code/modules/antagonists/bloodsucker/items/bloodsucker_stake.dm b/code/modules/antagonists/bloodsucker/items/bloodsucker_stake.dm
index 4c377f4b82..b13238f199 100644
--- a/code/modules/antagonists/bloodsucker/items/bloodsucker_stake.dm
+++ b/code/modules/antagonists/bloodsucker/items/bloodsucker_stake.dm
@@ -167,4 +167,4 @@
///obj/item/pipe = 2)
time = 80
category = CAT_WEAPONRY
- subcategory = CAT_WEAPON
+ subcategory = CAT_MELEE
diff --git a/code/modules/antagonists/changeling/changeling.dm b/code/modules/antagonists/changeling/changeling.dm
index 106ae16031..35639bfd97 100644
--- a/code/modules/antagonists/changeling/changeling.dm
+++ b/code/modules/antagonists/changeling/changeling.dm
@@ -91,6 +91,7 @@
B.organ_flags |= ORGAN_VITAL
B.decoy_override = FALSE
remove_changeling_powers()
+ owner.special_role = null
. = ..()
/datum/antagonist/changeling/proc/remove_clownmut()
diff --git a/code/modules/antagonists/changeling/powers/mutations.dm b/code/modules/antagonists/changeling/powers/mutations.dm
index 5378ea2276..e4417a6d64 100644
--- a/code/modules/antagonists/changeling/powers/mutations.dm
+++ b/code/modules/antagonists/changeling/powers/mutations.dm
@@ -432,15 +432,18 @@
/obj/item/shield/changeling
name = "shield-like mass"
desc = "A mass of tough, boney tissue. You can still see the fingers as a twisted pattern in the shield."
- item_flags = ABSTRACT | DROPDEL
+ item_flags = ABSTRACT | DROPDEL | ITEM_CAN_BLOCK
icon = 'icons/obj/items_and_weapons.dmi'
icon_state = "ling_shield"
lefthand_file = 'icons/mob/inhands/antag/changeling_lefthand.dmi'
righthand_file = 'icons/mob/inhands/antag/changeling_righthand.dmi'
- block_chance = 50
+ block_parry_data = /datum/block_parry_data/shield/changeling
var/remaining_uses //Set by the changeling ability.
+/datum/block_parry_data/shield/changeling
+ block_slowdown = 0
+
/obj/item/shield/changeling/Initialize(mapload)
. = ..()
ADD_TRAIT(src, TRAIT_NODROP, CHANGELING_TRAIT)
@@ -451,7 +454,7 @@
block_return[BLOCK_RETURN_BLOCK_CAPACITY] = (block_return[BLOCK_RETURN_BLOCK_CAPACITY] || 0) + remaining_uses
return ..()
-/obj/item/shield/changeling/run_block(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return)
+/obj/item/shield/changeling/active_block(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return)
. = ..()
if(--remaining_uses < 1)
if(ishuman(loc))
diff --git a/code/modules/antagonists/clockcult/clock_helpers/clock_powerdrain.dm b/code/modules/antagonists/clockcult/clock_helpers/clock_powerdrain.dm
index 7658f7e715..1d65cf9209 100644
--- a/code/modules/antagonists/clockcult/clock_helpers/clock_powerdrain.dm
+++ b/code/modules/antagonists/clockcult/clock_helpers/clock_powerdrain.dm
@@ -1,21 +1,31 @@
//horrifying power drain proc made for clockcult's power drain in lieu of six istypes or six for(x in view) loops
-/atom/movable/proc/power_drain(clockcult_user)
+/atom/movable/proc/power_drain(clockcult_user, drain_weapons = FALSE) //This proc as of now is only in use for void volt
var/obj/item/stock_parts/cell/cell = get_cell()
if(cell)
return cell.power_drain(clockcult_user)
- return 0
+ return 0 //Returns 0 instead of FALSE to symbolise it returning the power amount in other cases, not TRUE aka 1
-/obj/item/melee/baton/power_drain(clockcult_user) //balance memes
- return 0
+/obj/item/melee/baton/power_drain(clockcult_user, drain_weapons = FALSE) //balance memes
+ if(!drain_weapons)
+ return 0
+ return ..()
-/obj/item/gun/power_drain(clockcult_user) //balance memes
- return 0
+/obj/item/gun/power_drain(clockcult_user, drain_weapons = FALSE) //balance memes
+ if(!drain_weapons)
+ return 0
+ var/obj/item/stock_parts/cell/cell = get_cell()
+ if(!cell)
+ return 0
+ if(cell.charge)
+ . = min(cell.charge, MIN_CLOCKCULT_POWER*4) //Done snowflakey because guns have far smaller cells than batons / other equipment
+ cell.use(.)
+ update_icon()
-/obj/machinery/power/apc/power_drain(clockcult_user)
+/obj/machinery/power/apc/power_drain(clockcult_user, drain_weapons = FALSE)
if(cell && cell.charge)
playsound(src, "sparks", 50, 1)
flick("apc-spark", src)
- . = min(cell.charge, MIN_CLOCKCULT_POWER*3)
+ . = min(cell.charge, MIN_CLOCKCULT_POWER*4)
cell.use(.) //Better than a power sink!
if(!cell.charge && !shorted)
shorted = 1
@@ -23,9 +33,9 @@
update()
update_icon()
-/obj/machinery/power/smes/power_drain(clockcult_user)
+/obj/machinery/power/smes/power_drain(clockcult_user, drain_weapons = FALSE)
if(charge)
- . = min(charge, MIN_CLOCKCULT_POWER*3)
+ . = min(charge, MIN_CLOCKCULT_POWER*4)
charge -= . * 50
if(!charge && !panel_open)
panel_open = TRUE
@@ -34,19 +44,19 @@
visible_message("[src]'s panel flies open with a flurry of sparks!")
update_icon()
-/obj/item/stock_parts/cell/power_drain(clockcult_user)
+/obj/item/stock_parts/cell/power_drain(clockcult_user, drain_weapons = FALSE)
if(charge)
- . = min(charge, MIN_CLOCKCULT_POWER*3)
- charge = use(.)
+ . = min(charge, MIN_CLOCKCULT_POWER * 4) //Done like this because normal cells are usually quite a bit bigger than the ones used in guns / APCs
+ use(min(charge, . * 10)) //Usually cell-powered equipment that is not a gun has at least ten times the capacity of a gun / 5 times the amount of an APC. This adjusts the drain to account for that.
update_icon()
-/mob/living/silicon/robot/power_drain(clockcult_user)
+/mob/living/silicon/robot/power_drain(clockcult_user, drain_weapons = FALSE)
if((!clockcult_user || !is_servant_of_ratvar(src)) && cell && cell.charge)
. = min(cell.charge, MIN_CLOCKCULT_POWER*4)
cell.use(.)
spark_system.start()
-/obj/mecha/power_drain(clockcult_user)
+/obj/mecha/power_drain(clockcult_user, drain_weapons = FALSE)
if((!clockcult_user || (occupant && !is_servant_of_ratvar(occupant))) && cell && cell.charge)
. = min(cell.charge, MIN_CLOCKCULT_POWER*4)
cell.use(.)
diff --git a/code/modules/antagonists/clockcult/clock_helpers/slab_abilities.dm b/code/modules/antagonists/clockcult/clock_helpers/slab_abilities.dm
index de2e85a501..81ad7ddc26 100644
--- a/code/modules/antagonists/clockcult/clock_helpers/slab_abilities.dm
+++ b/code/modules/antagonists/clockcult/clock_helpers/slab_abilities.dm
@@ -135,6 +135,29 @@
return TRUE
+//For the Volt Void scripture, fires a ray of energy at a target location
+/obj/effect/proc_holder/slab/volt
+ ranged_mousepointer = 'icons/effects/volt_target.dmi'
+
+/obj/effect/proc_holder/slab/volt/InterceptClickOn(mob/living/caller, params, atom/target)
+ if(target == slab || ..()) //we can't cancel
+ return TRUE
+
+ var/turf/T = ranged_ability_user.loc
+ if(!isturf(T))
+ return TRUE
+
+ if(target in view(7, get_turf(ranged_ability_user)))
+ successful = TRUE
+ ranged_ability_user.visible_message("[ranged_ability_user] fires a ray of energy at [target]!", "You fire a volt ray at [target].")
+ playsound(ranged_ability_user, 'sound/effects/light_flicker.ogg', 50, 1)
+ T = get_turf(target)
+ new/obj/effect/temp_visual/ratvar/volt_hit(T, ranged_ability_user)
+ log_combat(ranged_ability_user, T, "fired a volt ray")
+ remove_ranged_ability()
+
+ return TRUE
+
//For the Kindle scripture; stuns and mutes a target non-servant.
/obj/effect/proc_holder/slab/kindle
ranged_mousepointer = 'icons/effects/volt_target.dmi'
diff --git a/code/modules/antagonists/clockcult/clock_scripture.dm b/code/modules/antagonists/clockcult/clock_scripture.dm
index 16d251fe92..1ebefe4d05 100644
--- a/code/modules/antagonists/clockcult/clock_scripture.dm
+++ b/code/modules/antagonists/clockcult/clock_scripture.dm
@@ -203,6 +203,10 @@ Applications: 8 servants, 3 caches, and 100 CV
if(!do_after(invoker, chant_interval, target = invoker, extra_checks = CALLBACK(src, .proc/can_recite)))
break
clockwork_say(invoker, text2ratvar(pick(chant_invocations)), whispered)
+ if(multiple_invokers_used)
+ for(var/mob/living/L in range(1, get_turf(invoker)))
+ if(can_recite_scripture(L) && L != invoker)
+ clockwork_say(L, text2ratvar(pick(chant_invocations)), whispered)
if(!chant_effects(i))
break
if(invoker && slab)
diff --git a/code/modules/antagonists/clockcult/clock_scriptures/scripture_scripts.dm b/code/modules/antagonists/clockcult/clock_scriptures/scripture_scripts.dm
index c40168a986..d22a2f69b7 100644
--- a/code/modules/antagonists/clockcult/clock_scriptures/scripture_scripts.dm
+++ b/code/modules/antagonists/clockcult/clock_scriptures/scripture_scripts.dm
@@ -115,7 +115,7 @@
tier = SCRIPTURE_SCRIPT
space_allowed = TRUE
primary_component = VANGUARD_COGWHEEL
- sort_priority = 5
+ sort_priority = 6
quickbind = TRUE
quickbind_desc = "Creates a Ratvarian shield, which can absorb energy from attacks for use in powerful bashes."
@@ -131,7 +131,7 @@
usage_tip = "Throwing the spear at a mob will do massive damage and knock them down, but break the spear. You will need to wait for 30 seconds before resummoning it."
tier = SCRIPTURE_SCRIPT
primary_component = VANGUARD_COGWHEEL
- sort_priority = 6
+ sort_priority = 7
important = TRUE
quickbind = TRUE
quickbind_desc = "Permanently binds clockwork armor and a Ratvarian spear to you."
@@ -231,7 +231,7 @@
usage_tip = "This gateway is strictly one-way and will only allow things through the invoker's portal."
tier = SCRIPTURE_SCRIPT
primary_component = GEIS_CAPACITOR
- sort_priority = 7
+ sort_priority = 9
quickbind = TRUE
quickbind_desc = "Allows you to create a one-way Spatial Gateway to a living Servant or Clockwork Obelisk."
@@ -263,3 +263,227 @@
duration = max(duration, 100)
return slab.procure_gateway(invoker, duration, portal_uses)
+
+//Mending Mantra: Channeled for up to ten times over twenty seconds to repair structures and heal allies
+/datum/clockwork_scripture/channeled/mending_mantra
+ descname = "Channeled, Area Healing and Repair"
+ name = "Mending Mantra"
+ desc = "Repairs nearby structures and constructs. Servants wearing clockwork armor will also be healed. Channeled every two seconds for a maximum of twenty seconds."
+ chant_invocations = list("Mend our dents!", "Heal our scratches!", "Repair our gears!")
+ chant_amount = 10
+ chant_interval = 20
+ power_cost = 400
+ usage_tip = "This is a very effective way to rapidly reinforce a base after an attack."
+ tier = SCRIPTURE_SCRIPT
+ primary_component = VANGUARD_COGWHEEL
+ sort_priority = 8
+ quickbind = TRUE
+ quickbind_desc = "Repairs nearby structures and constructs. Servants wearing clockwork armor will also be healed. Maximum 10 chants."
+ var/heal_attempts = 4
+ var/heal_amount = 2.5
+ var/static/list/damage_heal_order = list(BRUTE, BURN, OXY)
+ var/static/list/heal_finish_messages = list("There, all mended!", "Try not to get too damaged.", "No more dents and scratches for you!", "Champions never die.", "All patched up.", \
+ "Ah, child, it's okay now.", "Pain is temporary.", "What you do for the Justiciar is eternal.", "Bear this for me.", "Be strong, child.", "Please, be careful!", \
+ "If you die, you will be remembered.")
+ var/static/list/heal_target_typecache = typecacheof(list(
+ /obj/structure/destructible/clockwork,
+ /obj/machinery/door/airlock/clockwork,
+ /obj/machinery/door/window/clockwork,
+ /obj/structure/window/reinforced/clockwork,
+ /obj/structure/table/reinforced/brass))
+ var/static/list/ratvarian_armor_typecache = typecacheof(list(
+ /obj/item/clothing/suit/armor/clockwork,
+ /obj/item/clothing/head/helmet/clockwork,
+ /obj/item/clothing/gloves/clockwork,
+ /obj/item/clothing/shoes/clockwork))
+
+/datum/clockwork_scripture/channeled/mending_mantra/chant_effects(chant_number)
+ var/turf/T
+ for(var/atom/movable/M in range(7, invoker))
+ if(isliving(M))
+ if(isclockmob(M) || istype(M, /mob/living/simple_animal/drone/cogscarab))
+ var/mob/living/simple_animal/S = M
+ if(S.health == S.maxHealth || S.stat == DEAD)
+ continue
+ T = get_turf(M)
+ for(var/i in 1 to heal_attempts)
+ if(S.health < S.maxHealth)
+ S.adjustHealth(-heal_amount)
+ new /obj/effect/temp_visual/heal(T, "#1E8CE1")
+ if(i == heal_attempts && S.health >= S.maxHealth) //we finished healing on the last tick, give them the message
+ to_chat(S, "\"[text2ratvar(pick(heal_finish_messages))]\"")
+ break
+ else
+ to_chat(S, "\"[text2ratvar(pick(heal_finish_messages))]\"")
+ break
+ else if(issilicon(M))
+ var/mob/living/silicon/S = M
+ if(S.health == S.maxHealth || S.stat == DEAD || !is_servant_of_ratvar(S))
+ continue
+ T = get_turf(M)
+ for(var/i in 1 to heal_attempts)
+ if(S.health < S.maxHealth)
+ S.heal_ordered_damage(heal_amount, damage_heal_order)
+ new /obj/effect/temp_visual/heal(T, "#1E8CE1")
+ if(i == heal_attempts && S.health >= S.maxHealth)
+ to_chat(S, "\"[text2ratvar(pick(heal_finish_messages))]\"")
+ break
+ else
+ to_chat(S, "\"[text2ratvar(pick(heal_finish_messages))]\"")
+ break
+ else if(ishuman(M))
+ var/mob/living/carbon/human/H = M
+ if(H.health == H.maxHealth || H.stat == DEAD || !is_servant_of_ratvar(H))
+ continue
+ T = get_turf(M)
+ var/heal_ticks = 0 //one heal tick for each piece of ratvarian armor worn
+ var/obj/item/I = H.get_item_by_slot(SLOT_WEAR_SUIT)
+ if(is_type_in_typecache(I, ratvarian_armor_typecache))
+ heal_ticks++
+ I = H.get_item_by_slot(SLOT_HEAD)
+ if(is_type_in_typecache(I, ratvarian_armor_typecache))
+ heal_ticks++
+ I = H.get_item_by_slot(SLOT_GLOVES)
+ if(is_type_in_typecache(I, ratvarian_armor_typecache))
+ heal_ticks++
+ I = H.get_item_by_slot(SLOT_SHOES)
+ if(is_type_in_typecache(I, ratvarian_armor_typecache))
+ heal_ticks++
+ if(heal_ticks)
+ for(var/i in 1 to heal_ticks)
+ if(H.health < H.maxHealth)
+ H.heal_ordered_damage(heal_amount, damage_heal_order)
+ new /obj/effect/temp_visual/heal(T, "#1E8CE1")
+ if(i == heal_ticks && H.health >= H.maxHealth)
+ to_chat(H, "\"[text2ratvar(pick(heal_finish_messages))]\"")
+ break
+ else
+ to_chat(H, "\"[text2ratvar(pick(heal_finish_messages))]\"")
+ break
+ else if(is_type_in_typecache(M, heal_target_typecache))
+ var/obj/structure/destructible/clockwork/C = M
+ if(C.obj_integrity == C.max_integrity || (istype(C) && !C.can_be_repaired))
+ continue
+ T = get_turf(M)
+ for(var/i in 1 to heal_attempts)
+ if(C.obj_integrity < C.max_integrity)
+ C.obj_integrity = min(C.obj_integrity + 5, C.max_integrity)
+ C.update_icon()
+ new /obj/effect/temp_visual/heal(T, "#1E8CE1")
+ else
+ break
+ new /obj/effect/temp_visual/ratvar/mending_mantra(get_turf(invoker))
+ return TRUE
+
+//Volt Blaster: Channeled for up to five times over ten seconds to fire up to five rays of energy at target locations.
+/datum/clockwork_scripture/channeled/volt_blaster
+ descname = "Channeled, Targeted Energy Blasts"
+ name = "Volt Blaster"
+ desc = "Allows you to fire five energy rays at target locations. Channeled every fourth of a second for a maximum of ten seconds."
+ channel_time = 30
+ invocations = list("Amperage...", "...grant me your power!")
+ chant_invocations = list("Use charge to kill!", "Slay with power!", "Hunt with energy!")
+ chant_amount = 5
+ chant_interval = 4
+ power_cost = 500
+ usage_tip = "Though it requires you to stand still, this scripture can do massive damage."
+ tier = SCRIPTURE_SCRIPT
+ primary_component = BELLIGERENT_EYE
+ sort_priority = 5
+ quickbind = TRUE
+ quickbind_desc = "Allows you to fire energy rays at target locations. Maximum 5 chants."
+ var/static/list/nzcrentr_insults = list("You're not very good at aiming.", "You hunt badly.", "What a waste of energy.", "Almost funny to watch.",
+ "Boss says \"Click something, you idiot!\".", "Stop wasting power if you can't aim.")
+
+/datum/clockwork_scripture/channeled/volt_blaster/chant_effects(chant_number)
+ slab.busy = null
+ var/datum/clockwork_scripture/ranged_ability/volt_ray/ray = new
+ ray.slab = slab
+ ray.invoker = invoker
+ var/turf/T = get_turf(invoker)
+ if(!ray.run_scripture() && slab && invoker)
+ if(can_recite() && T == get_turf(invoker))
+ to_chat(invoker, "\"[text2ratvar(pick(nzcrentr_insults))]\"")
+ else
+ return FALSE
+ return TRUE
+
+/obj/effect/ebeam/volt_ray
+ name = "volt_ray"
+ layer = LYING_MOB_LAYER
+
+/datum/clockwork_scripture/ranged_ability/volt_ray
+ name = "Volt Ray"
+ slab_overlay = "volt"
+ allow_mobility = FALSE
+ ranged_type = /obj/effect/proc_holder/slab/volt
+ ranged_message = "You charge the clockwork slab with shocking might.\n\
+ Left-click a target to fire, quickly!"
+ timeout_time = 20
+
+/datum/clockwork_scripture/channeled/void_volt
+ descname = "Channeled, Power Drain"
+ name = "Void Volt"
+ desc = "A channeled spell that quickly drains any powercells in a radius of eight tiles, but burns the invoker. \
+ Can be channeled with more cultists to increase range and split the caused damage evenly over all invokers. \
+ Also charges clockwork power by a small percentage of the drained power amount, which can help offset this scriptures powercost."
+ invocations = list("Channel their energy through my body... ", "... so it may fuel Engine!")
+ chant_invocations = list("Make their lights fall dark!", "They shall be powerless!", "Rob them of their power!")
+ chant_amount = 20
+ chant_interval = 10 //100KW drain per pulse for guns / APCs / 1MW for other cells = 10 chants / 100ds / 10s to drain a charged weapon or a baton with a nonupgraded cell
+ channel_time = 50
+ power_cost = 300
+ multiple_invokers_used = TRUE
+ multiple_invokers_optional = TRUE
+ usage_tip = "It may be useful to end channelling early if the burning becomes too much to handle.."
+ tier = SCRIPTURE_SCRIPT
+ primary_component = GEIS_CAPACITOR
+ sort_priority = 10
+ quickbind = TRUE
+ quickbind_desc = "Quickly drains power in an area around the invoker, causing burns proportional to the amount of energy drained. Maximum of 20 chants."
+
+/datum/clockwork_scripture/channeled/void_volt/scripture_effects()
+ invoker.visible_message("[invoker] glows in a brilliant golden light!")
+ invoker.add_atom_colour("#FFD700", ADMIN_COLOUR_PRIORITY)
+ invoker.light_power = 2
+ invoker.light_range = 4
+ invoker.light_color = LIGHT_COLOR_FIRE
+ invoker.update_light()
+ return ..()
+
+
+/datum/clockwork_scripture/channeled/void_volt/chant_effects(chant_number)
+ var/power_drained = 0
+ var/power_mod = 0.005 //Amount of power drained (generally) is multiplied with this, and subsequently dealt in damage to the invoker, then 15 times that is added to the clockwork cult's power reserves.
+ var/drain_range = 8
+ var/additional_chanters = 0
+ var/list/chanters = list()
+ chanters += invoker
+ for(var/mob/living/L in range(1, invoker))
+ if(!L.stat && is_servant_of_ratvar(L))
+ additional_chanters++
+ chanters += L
+ drain_range = min(drain_range + 2 * additional_chanters, drain_range * 2) //s u c c
+ for(var/t in spiral_range_turfs(drain_range, invoker))
+ var/turf/T = t
+ for(var/M in T)
+ var/atom/movable/A = M
+ power_drained += A.power_drain(TRUE, TRUE) //Yes, this absolutely does drain weaponry. 10 pulses to drain guns / batons, though of course they can just be recharged.
+ new /obj/effect/temp_visual/ratvar/sigil/transgression(invoker.loc, 1 + (power_drained * power_mod))
+ var/datum/effect_system/spark_spread/S = new
+ S.set_up(round(1 + (power_drained * power_mod), 1), 0, get_turf(invoker))
+ S.start()
+ adjust_clockwork_power(power_drained * power_mod * 15)
+ for(var/mob/living/L in chanters)
+ L.adjustFireLoss(round(clamp(power_drained * power_mod / (1 + additional_chanters), 0, 20), 0.1)) //No you won't just immediately melt if you do this in a very power-rich area
+
+
+ return TRUE
+
+/datum/clockwork_scripture/channeled/void_volt/chant_end_effects()
+ invoker.visible_message("[invoker] stops glowing...")
+ invoker.remove_atom_colour(ADMIN_COLOUR_PRIORITY)
+ invoker.light_power = 0
+ invoker.light_range = 0
+ invoker.update_light()
+ return ..()
diff --git a/code/modules/antagonists/clockcult/clock_structure.dm b/code/modules/antagonists/clockcult/clock_structure.dm
index 13da9c5a42..2464015b6b 100644
--- a/code/modules/antagonists/clockcult/clock_structure.dm
+++ b/code/modules/antagonists/clockcult/clock_structure.dm
@@ -101,7 +101,7 @@
return 1
return ..()
-/obj/structure/destructible/clockwork/attacked_by(obj/item/I, mob/living/user)
+/obj/structure/destructible/clockwork/attacked_by(obj/item/I, mob/living/user, attackchain_flags = NONE, damage_multiplier = 1)
if(is_servant_of_ratvar(user) && immune_to_servant_attacks)
return FALSE
return ..()
diff --git a/code/modules/antagonists/cult/blood_magic.dm b/code/modules/antagonists/cult/blood_magic.dm
index 28d1a66e00..52ead9a1e7 100644
--- a/code/modules/antagonists/cult/blood_magic.dm
+++ b/code/modules/antagonists/cult/blood_magic.dm
@@ -343,7 +343,7 @@
icon = 'icons/obj/items_and_weapons.dmi'
icon_state = "disintegrate"
item_state = null
- item_flags = NEEDS_PERMIT | ABSTRACT | DROPDEL | NO_ATTACK_CHAIN_SOFT_STAMCRIT
+ item_flags = NEEDS_PERMIT | ABSTRACT | DROPDEL
w_class = WEIGHT_CLASS_HUGE
throwforce = 0
diff --git a/code/modules/antagonists/cult/cult_items.dm b/code/modules/antagonists/cult/cult_items.dm
index ffab8174e5..8f0f9a658c 100644
--- a/code/modules/antagonists/cult/cult_items.dm
+++ b/code/modules/antagonists/cult/cult_items.dm
@@ -273,6 +273,7 @@
knockdown = 20
/obj/item/restraints/legcuffs/bola/cult/pickup(mob/living/user)
+ . = ..()
if(!iscultist(user))
to_chat(user, "The bola seems to take on a life of its own!")
ensnare(user)
diff --git a/code/modules/antagonists/devil/true_devil/_true_devil.dm b/code/modules/antagonists/devil/true_devil/_true_devil.dm
index 1b245b5a01..7d7031dad4 100644
--- a/code/modules/antagonists/devil/true_devil/_true_devil.dm
+++ b/code/modules/antagonists/devil/true_devil/_true_devil.dm
@@ -116,24 +116,11 @@
/mob/living/carbon/true_devil/get_ear_protection()
return 2
-
-/mob/living/carbon/true_devil/attacked_by(obj/item/I, mob/living/user, def_zone)
- var/weakness = check_weakness(I, user)
- apply_damage(I.force * weakness, I.damtype, def_zone)
- var/message_verb = ""
- if(I.attack_verb && I.attack_verb.len)
- message_verb = "[pick(I.attack_verb)]"
- else if(I.force)
- message_verb = "attacked"
-
- var/attack_message = "[src] has been [message_verb] with [I]."
- if(user)
- user.do_attack_animation(src)
- if(user in viewers(src, null))
- attack_message = "[user] has [message_verb] [src] with [I]!"
- if(message_verb)
- visible_message("[attack_message]",
- "[attack_message]", null, COMBAT_MESSAGE_RANGE)
+/mob/living/carbon/true_devil/attacked_by(obj/item/I, mob/living/user, def_zone, attackchain_flags = NONE, damage_multiplier = 1)
+ var/totitemdamage = pre_attacked_by(I, user)
+ totitemdamage *= check_weakness(I, user)
+ apply_damage(totitemdamage, I.damtype, def_zone)
+ send_item_attack_message(I, user, null, totitemdamage)
return TRUE
/mob/living/carbon/true_devil/singularity_act()
diff --git a/code/modules/antagonists/traitor/classes/hijack.dm b/code/modules/antagonists/traitor/classes/hijack.dm
index dbfa78eb96..bbfb0cb11d 100644
--- a/code/modules/antagonists/traitor/classes/hijack.dm
+++ b/code/modules/antagonists/traitor/classes/hijack.dm
@@ -4,6 +4,7 @@
weight = 3
chaos = 5
threat = 3
+ min_players = 25
uplink_filters = list(/datum/uplink_item/stealthy_weapons/romerol_kit)
/datum/traitor_class/human/hijack/forge_objectives(datum/antagonist/traitor/T)
diff --git a/code/modules/antagonists/traitor/classes/martyr.dm b/code/modules/antagonists/traitor/classes/martyr.dm
index 72f9ac86fc..5c407b70fd 100644
--- a/code/modules/antagonists/traitor/classes/martyr.dm
+++ b/code/modules/antagonists/traitor/classes/martyr.dm
@@ -4,6 +4,7 @@
weight = 2
chaos = 5
threat = 5
+ min_players = 20
uplink_filters = list(/datum/uplink_item/stealthy_weapons/romerol_kit,/datum/uplink_item/bundles_TC/contract_kit)
/datum/traitor_class/human/martyr/forge_objectives(datum/antagonist/traitor/T)
diff --git a/code/modules/antagonists/traitor/classes/traitor_class.dm b/code/modules/antagonists/traitor/classes/traitor_class.dm
index 260e978bec..568619f59d 100644
--- a/code/modules/antagonists/traitor/classes/traitor_class.dm
+++ b/code/modules/antagonists/traitor/classes/traitor_class.dm
@@ -7,6 +7,8 @@ GLOBAL_LIST_EMPTY(traitor_classes)
var/chaos = 0
var/threat = 0
var/TC = 20
+ /// Minimum players for this to randomly roll via get_random_traitor_class().
+ var/min_players = 0
var/list/uplink_filters
/datum/traitor_class/New()
@@ -41,4 +43,4 @@ GLOBAL_LIST_EMPTY(traitor_classes)
/datum/traitor_class/proc/clean_up_traitor(datum/antagonist/traitor/T)
// Any effects that need to be cleaned up if traitor class is being swapped.
-
\ No newline at end of file
+
diff --git a/code/modules/antagonists/traitor/datum_traitor.dm b/code/modules/antagonists/traitor/datum_traitor.dm
index 4422f2f174..0309b7e826 100644
--- a/code/modules/antagonists/traitor/datum_traitor.dm
+++ b/code/modules/antagonists/traitor/datum_traitor.dm
@@ -47,6 +47,8 @@
for(var/C in GLOB.traitor_classes)
if(!(C in blacklist))
var/datum/traitor_class/class = GLOB.traitor_classes[C]
+ if(class.min_players > length(GLOB.joined_player_list))
+ continue
var/weight = LOGISTIC_FUNCTION(1.5*class.weight,chaos_weight,class.chaos,0)
weights[C] = weight * 1000
var/choice = pickweight(weights, 0)
diff --git a/code/modules/atmospherics/machinery/airalarm.dm b/code/modules/atmospherics/machinery/airalarm.dm
index 733c760855..b47f45b42d 100644
--- a/code/modules/atmospherics/machinery/airalarm.dm
+++ b/code/modules/atmospherics/machinery/airalarm.dm
@@ -36,7 +36,7 @@
/obj/item/electronics/airalarm
name = "air alarm electronics"
icon_state = "airalarm_electronics"
- custom_price = 50
+ custom_price = PRICE_CHEAP
/obj/item/wallframe/airalarm
name = "air alarm frame"
diff --git a/code/modules/atmospherics/machinery/components/components_base.dm b/code/modules/atmospherics/machinery/components/components_base.dm
index c229a4ba27..33fd160b1a 100644
--- a/code/modules/atmospherics/machinery/components/components_base.dm
+++ b/code/modules/atmospherics/machinery/components/components_base.dm
@@ -162,6 +162,9 @@
to_chat(user, "Access denied.")
return UI_CLOSE
+/obj/machinery/atmospherics/components/attack_ghost(mob/dead/observer/O)
+ . = ..()
+ atmosanalyzer_scan(airs, O, src, FALSE)
// Tool acts
diff --git a/code/modules/atmospherics/machinery/pipes/pipes.dm b/code/modules/atmospherics/machinery/pipes/pipes.dm
index c466a422b2..4a6170c251 100644
--- a/code/modules/atmospherics/machinery/pipes/pipes.dm
+++ b/code/modules/atmospherics/machinery/pipes/pipes.dm
@@ -111,3 +111,10 @@
pipe_color = paint_color
update_node_icon()
return TRUE
+
+/obj/machinery/atmospherics/pipe/attack_ghost(mob/dead/observer/O)
+ . = ..()
+ if(parent)
+ atmosanalyzer_scan(parent.air, O, src, FALSE)
+ else
+ to_chat(O, "[src] doesn't have a pipenet, which is probably a bug.")
diff --git a/code/modules/atmospherics/machinery/portable/portable_atmospherics.dm b/code/modules/atmospherics/machinery/portable/portable_atmospherics.dm
index 8bf0554070..952db8315a 100644
--- a/code/modules/atmospherics/machinery/portable/portable_atmospherics.dm
+++ b/code/modules/atmospherics/machinery/portable/portable_atmospherics.dm
@@ -147,10 +147,14 @@
/obj/machinery/portable_atmospherics/analyzer_act(mob/living/user, obj/item/I)
atmosanalyzer_scan(air_contents, user, src)
-/obj/machinery/portable_atmospherics/attacked_by(obj/item/I, mob/user)
+/obj/machinery/portable_atmospherics/attacked_by(obj/item/I, mob/user, attackchain_flags = NONE, damage_multiplier = 1)
if(I.force < 10 && !(stat & BROKEN))
take_damage(0)
else
investigate_log("was smacked with \a [I] by [key_name(user)].", INVESTIGATE_ATMOS)
add_fingerprint(user)
..()
+
+/obj/machinery/portable_atmospherics/attack_ghost(mob/dead/observer/O)
+ . = ..()
+ atmosanalyzer_scan(air_contents, O, src, FALSE)
diff --git a/code/modules/cargo/exports/large_objects.dm b/code/modules/cargo/exports/large_objects.dm
index c7a5e59046..2b93a25a61 100644
--- a/code/modules/cargo/exports/large_objects.dm
+++ b/code/modules/cargo/exports/large_objects.dm
@@ -171,10 +171,10 @@
var/worth = 10
var/gases = C.air_contents.gases
- worth += gases[/datum/gas/bz]*4
+ worth += gases[/datum/gas/bz]*3
worth += gases[/datum/gas/stimulum]*25
worth += gases[/datum/gas/hypernoblium]*1000
- worth += gases[/datum/gas/miasma]*4
+ worth += gases[/datum/gas/miasma]*2
worth += gases[/datum/gas/tritium]*7
worth += gases[/datum/gas/pluoxium]*6
worth += gases[/datum/gas/nitryl]*30
diff --git a/code/modules/cargo/packs/costumes_toys.dm b/code/modules/cargo/packs/costumes_toys.dm
index dab8cad5b6..c181d6fb74 100644
--- a/code/modules/cargo/packs/costumes_toys.dm
+++ b/code/modules/cargo/packs/costumes_toys.dm
@@ -71,7 +71,8 @@
/obj/item/clothing/mask/gas/syndicate,
/obj/item/clothing/neck/necklace/dope,
/obj/item/vending_refill/donksoft,
- /obj/item/circuitboard/computer/arcade/amputation)
+ /obj/item/circuitboard/computer/arcade/amputation,
+ /obj/item/storage/bag/ammo)
crate_name = "crate"
/datum/supply_pack/costumes_toys/foamforce
diff --git a/code/modules/client/client_defines.dm b/code/modules/client/client_defines.dm
index 7d87a82afb..0402dc683f 100644
--- a/code/modules/client/client_defines.dm
+++ b/code/modules/client/client_defines.dm
@@ -133,3 +133,6 @@
var/parallax_movedir = 0
var/parallax_layers_max = 3
var/parallax_animate_timer
+
+ //world.time of when the crew manifest can be accessed
+ var/crew_manifest_delay
diff --git a/code/modules/client/preferences.dm b/code/modules/client/preferences.dm
index 040b56f124..347a77059a 100644
--- a/code/modules/client/preferences.dm
+++ b/code/modules/client/preferences.dm
@@ -50,6 +50,8 @@ GLOBAL_LIST_EMPTY(preferences_datums)
/// Custom Keybindings
var/list/key_bindings = list()
+ /// List with a key string associated to a list of keybindings. Unlike key_bindings, this one operates on raw key, allowing for binding a key that triggers regardless of if a modifier is depressed as long as the raw key is sent.
+ var/list/modless_key_bindings = list()
var/tgui_fancy = TRUE
@@ -154,6 +156,7 @@ GLOBAL_LIST_EMPTY(preferences_datums)
"ipc_screen" = "Sunburst",
"ipc_antenna" = "None",
"flavor_text" = "",
+ "silicon_flavor_text" = "",
"ooc_notes" = "",
"meat_type" = "Mammalian",
"body_model" = MALE,
@@ -367,6 +370,15 @@ GLOBAL_LIST_EMPTY(preferences_datums)
dat += "[features["flavor_text"]]"
else
dat += "[TextPreview(features["flavor_text"])]... "
+ dat += "
Silicon Flavor Text
"
+ dat += "Set Silicon Examine Text "
+ if(length(features["silicon_flavor_text"]) <= 40)
+ if(!length(features["silicon_flavor_text"]))
+ dat += "\[...\]"
+ else
+ dat += "[features["silicon_flavor_text"]]"
+ else
+ dat += "[TextPreview(features["silicon_flavor_text"])]... "
dat += "
OOC notes
"
dat += "Set OOC notes "
var/ooc_notes_len = length(features["ooc_notes"])
@@ -1088,11 +1100,16 @@ GLOBAL_LIST_EMPTY(preferences_datums)
Input mode is the closest thing to the old input system. \
IMPORTANT: While in input mode's non hotkey setting (tab toggled), Ctrl + KEY will send KEY to the keybind system as the key itself, not as Ctrl + KEY. This means Ctrl + T/W/A/S/D/all your familiar stuff still works, but you \
won't be able to access any regular Ctrl binds. "
+ dat += " Modifier-Independent binding - This is a singular bind that works regardless of if Ctrl/Shift/Alt are held down. For example, if combat mode is bound to C in modifier-independent binds, it'll trigger regardless of if you are \
+ holding down shift for sprint. Each keybind can only have one independent binding, and each key can only have one keybind independently bound to it."
// Create an inverted list of keybindings -> key
var/list/user_binds = list()
+ var/list/user_modless_binds = list()
for (var/key in key_bindings)
for(var/kb_name in key_bindings[key])
user_binds[kb_name] += list(key)
+ for (var/key in modless_key_bindings)
+ user_modless_binds[modless_key_bindings[key]] = key
var/list/kb_categories = list()
// Group keybinds by category
@@ -1100,21 +1117,29 @@ GLOBAL_LIST_EMPTY(preferences_datums)
var/datum/keybinding/kb = GLOB.keybindings_by_name[name]
kb_categories[kb.category] += list(kb)
- dat += ""
+ dat += {"
+
+ "}
for (var/category in kb_categories)
dat += "
[category]
"
for (var/i in kb_categories[category])
var/datum/keybinding/kb = i
+ var/current_independent_binding = user_modless_binds[kb.name] || "Unbound"
if(!length(user_binds[kb.name]))
- dat += " Unbound"
+ dat += "[kb.full_name]Unbound"
var/list/default_keys = hotkeys ? kb.hotkey_keys : kb.classic_keys
if(LAZYLEN(default_keys))
dat += "| Default: [default_keys.Join(", ")]"
+ dat += "Independent Binding: [current_independent_binding]"
dat += " "
else
var/bound_key = user_binds[kb.name][1]
- dat += " [bound_key]"
+ dat += "[kb.full_name][bound_key]"
for(var/bound_key_index in 2 to length(user_binds[kb.name]))
bound_key = user_binds[kb.name][bound_key_index]
dat += " | [bound_key]"
@@ -1123,6 +1148,7 @@ GLOBAL_LIST_EMPTY(preferences_datums)
var/list/default_keys = hotkeys ? kb.classic_keys : kb.hotkey_keys
if(LAZYLEN(default_keys))
dat += "| Default: [default_keys.Join(", ")]"
+ dat += "Independent Binding: [current_independent_binding]"
dat += " "
dat += "