Merge branch 'master' of https://github.com/tgstation/tgstation into upstream-sync

This commit is contained in:
xPokee
2025-10-03 07:05:54 -04:00
517 changed files with 24339 additions and 21253 deletions
+6 -4
View File
@@ -21,18 +21,20 @@
src.climb_stun = climb_stun
RegisterSignal(target, COMSIG_ATOM_ATTACK_HAND, PROC_REF(attack_hand))
RegisterSignal(target, COMSIG_ATOM_EXAMINE, PROC_REF(on_examine))
RegisterSignal(target, COMSIG_ATOM_EXAMINE_TAGS, PROC_REF(get_examine_tags))
RegisterSignal(target, COMSIG_MOUSEDROPPED_ONTO, PROC_REF(mousedrop_receive))
ADD_TRAIT(target, TRAIT_CLIMBABLE, ELEMENT_TRAIT(type))
/datum/element/climbable/Detach(datum/target)
UnregisterSignal(target, list(COMSIG_ATOM_ATTACK_HAND, COMSIG_ATOM_EXAMINE, COMSIG_MOUSEDROPPED_ONTO, COMSIG_ATOM_BUMPED))
UnregisterSignal(target, list(COMSIG_ATOM_ATTACK_HAND, COMSIG_ATOM_EXAMINE_TAGS, COMSIG_MOUSEDROPPED_ONTO, COMSIG_ATOM_BUMPED))
REMOVE_TRAIT(target, TRAIT_CLIMBABLE, ELEMENT_TRAIT(type))
return ..()
/datum/element/climbable/proc/on_examine(atom/source, mob/user, list/examine_texts)
///Someone inspected our embeddable item
/datum/element/climbable/proc/get_examine_tags(atom/source, mob/user, list/examine_list)
SIGNAL_HANDLER
examine_texts += span_notice("[source] looks climbable.")
examine_list["climbable"] = "It looks like it can be climbed on."
/datum/element/climbable/proc/can_climb(atom/source, mob/user)
if (!user.CanReach(source))
+1 -1
View File
@@ -60,7 +60,7 @@
/obj/effect/temp_visual/curse_blast/Initialize(mapload)
. = ..()
animate(src, transform = matrix() * 0.2, time = 0, flags = ANIMATION_PARALLEL)
animate(transform = matrix() * 2, time = duration, easing = EASE_IN)
animate(transform = matrix() * 2, time = duration, easing = QUAD_EASING|EASE_IN)
animate(src, alpha = 255, time = 0, flags = ANIMATION_PARALLEL)
animate(alpha = 255, time = 0.2 SECONDS)
+46 -15
View File
@@ -8,16 +8,19 @@
argument_hash_start_idx = 2
/// Path of the trophy dropped (or list of trophies)
var/trophy_type
/// chance to drop the trophy, lowered by the mob only taking partial crusher damage instead of full
/// for example, 25% would mean ~4 mobs need to die before you find one.
/// but it would be more if you didn't deal full crusher damage to them.
/// Chance to drop the trophy, lowered by the mob only taking partial crusher damage instead of full
/// For example, 25% would mean ~4 mobs need to die before you find one.
/// But it would be more if you didn't deal full crusher damage to them.
var/drop_mod
/// If true, will immediately spawn the item instead of putting it in butcher loot.
var/drop_immediately
/// Crusher damage percentage at which the drop is guaranteed, if any
var/guaranteed_drop
/// Should crusher drops replace *all* spawned loot?
var/replace_all
/datum/element/crusher_loot/Attach(datum/target, trophy_type, drop_mod = 25, drop_immediately = FALSE)
/datum/element/crusher_loot/Attach(datum/target, trophy_type, drop_mod = 25, drop_immediately = FALSE, guaranteed_drop = null, replace_all = FALSE)
. = ..()
if(!isliving(target))
return ELEMENT_INCOMPATIBLE
@@ -25,27 +28,54 @@
src.trophy_type = trophy_type
src.drop_mod = drop_mod
src.guaranteed_drop = guaranteed_drop
src.drop_immediately = drop_immediately
src.replace_all = replace_all
if (replace_all)
RegisterSignal(target, COMSIG_LIVING_DROP_LOOT, PROC_REF(on_loot_drop))
/datum/element/crusher_loot/Detach(datum/target)
UnregisterSignal(target, COMSIG_LIVING_DEATH)
UnregisterSignal(target, list(COMSIG_LIVING_DEATH, COMSIG_LIVING_DROP_LOOT))
return ..()
/datum/element/crusher_loot/proc/on_loot_drop(mob/living/target, list/spawn_loot, gibbed)
SIGNAL_HANDLER
// Prevent normal loot from being dropped if we have replace_all enabled
return COMPONENT_NO_LOOT_DROP
/datum/element/crusher_loot/proc/on_death(mob/living/target, gibbed)
SIGNAL_HANDLER
var/datum/status_effect/crusher_damage/damage = target.has_status_effect(/datum/status_effect/crusher_damage)
// SKYRAT EDIT START - ASHWALKER TROPHIES
var/datum/status_effect/ashwalker_damage/ashie_damage = target.has_status_effect(/datum/status_effect/ashwalker_damage) // SKYRAT EDIT ADDITION
var/damage_total = damage?.total_damage + ashie_damage?.total_damage // SKYRAT EDIT ADDITION
if(damage && prob((damage_total/target.maxHealth) * drop_mod)) //on average, you'll need to kill 4 creatures before getting the item. by default.
// SKYRAT EDIT END - ASHWALKER TROPHIES
if(!islist(trophy_type))
make_path(target, trophy_type)
if (!damage)
return
if (guaranteed_drop)
if (damage.total_damage / target.maxHealth < guaranteed_drop)
return
else if (!prob((damage.total_damage / target.maxHealth) * drop_mod)) // On average, you'll need to kill 4 creatures before getting the item. by default.
return
if (replace_all && isanimal(target))
var/mob/living/simple_animal/expiring_code = target
if (!islist(trophy_type))
expiring_code.loot = list(trophy_type)
return
for(var/trophypath in trophy_type)
make_path(target, trophypath)
var/list/trophies = trophy_type
expiring_code.loot = trophies.Copy()
return
if(!islist(trophy_type))
make_path(target, trophy_type)
return
if (replace_all)
target.butcher_results?.Cut()
target.guaranteed_butcher_results?.Cut()
for(var/trophypath in trophy_type)
make_path(target, trophypath)
/datum/element/crusher_loot/proc/make_path(mob/living/target, path)
if(drop_immediately)
@@ -54,4 +84,5 @@
if (!target.guaranteed_butcher_results)
target.guaranteed_butcher_results = list()
target.guaranteed_butcher_results[path] = 1
+20 -5
View File
@@ -6,16 +6,19 @@
/datum/element/death_drops
element_flags = ELEMENT_BESPOKE
argument_hash_start_idx = 2
///what items the target drops when killed
/// What items the target drops when killed
var/list/loot
/// Should the items not be dropped when gibbed?
var/no_gib_drops
/datum/element/death_drops/Attach(datum/target, list/loot)
/datum/element/death_drops/Attach(datum/target, list/loot, no_gib_drops = FALSE)
. = ..()
if(!isliving(target))
return ELEMENT_INCOMPATIBLE
if(!loot)
stack_trace("[type] added to [target] with NO LOOT.")
src.loot = loot
src.no_gib_drops = no_gib_drops
RegisterSignal(target, COMSIG_LIVING_DEATH, PROC_REF(on_death))
/datum/element/death_drops/Detach(datum/target)
@@ -25,10 +28,22 @@
///signal called by the stat of the target changing
/datum/element/death_drops/proc/on_death(mob/living/target, gibbed)
SIGNAL_HANDLER
if (no_gib_drops && gibbed)
return
var/list/spawn_loot = null
if (islist(loot))
spawn_loot = loot.Copy()
else
spawn_loot = list(loot)
var/atom/loot_loc = target.drop_location()
for(var/thing_to_spawn in loot)
for(var/i in 1 to (loot[thing_to_spawn] || 1))
create_loot(thing_to_spawn, loot_loc, target, gibbed, spread_px = loot.len * 3)
if (SEND_SIGNAL(target, COMSIG_LIVING_DROP_LOOT, spawn_loot, gibbed) & COMPONENT_NO_LOOT_DROP)
return
for(var/thing_to_spawn in spawn_loot)
for(var/i in 1 to (spawn_loot[thing_to_spawn] || 1))
create_loot(thing_to_spawn, loot_loc, target, gibbed, spread_px = spawn_loot.len * 3)
/// Handles creating the loots
/datum/element/death_drops/proc/create_loot(typepath, atom/loot_loc, mob/living/dead, gibbed, spread_px = 4)
+4 -4
View File
@@ -27,14 +27,14 @@
/datum/element/digitalcamo/proc/HideFromAIHuds(mob/living/target)
for(var/mob/living/silicon/ai/AI in GLOB.ai_list)
for (var/hud_type in AI.silicon_huds)
var/datum/atom_hud/silicon_hud = GLOB.huds[hud_type]
for (var/hud_trait in AI.silicon_huds)
var/datum/atom_hud/silicon_hud = GLOB.huds[GLOB.trait_to_hud[hud_trait]]
silicon_hud.hide_single_atomhud_from(AI,target)
/datum/element/digitalcamo/proc/UnhideFromAIHuds(mob/living/target)
for(var/mob/living/silicon/ai/AI in GLOB.ai_list)
for (var/hud_type in AI.silicon_huds)
var/datum/atom_hud/silicon_hud = GLOB.huds[hud_type]
for (var/hud_trait in AI.silicon_huds)
var/datum/atom_hud/silicon_hud = GLOB.huds[GLOB.trait_to_hud[hud_trait]]
silicon_hud.unhide_single_atomhud_from(AI,target)
/datum/element/digitalcamo/proc/on_examine(datum/source, mob/M, list/examine_list)
+25 -2
View File
@@ -9,12 +9,35 @@
return ELEMENT_INCOMPATIBLE
flags = _flags
RegisterSignal(target, COMSIG_ATOM_PRE_EMP_ACT, PROC_REF(getEmpFlags))
RegisterSignal(target, COMSIG_ATOM_EXAMINE_TAGS, PROC_REF(get_examine_tags))
/datum/element/empprotection/Detach(atom/target)
UnregisterSignal(target, COMSIG_ATOM_PRE_EMP_ACT)
UnregisterSignal(target, list(COMSIG_ATOM_PRE_EMP_ACT, COMSIG_ATOM_EXAMINE_TAGS))
return ..()
/datum/element/empprotection/proc/getEmpFlags(datum/source, severity)
SIGNAL_HANDLER
return flags
return (flags & EMP_PROTECT_ALL)
/datum/element/empprotection/proc/get_examine_tags(atom/source, mob/user, list/examine_list)
SIGNAL_HANDLER
if(flags & EMP_NO_EXAMINE)
return
if(flags & EMP_PROTECT_ALL == EMP_PROTECT_ALL)
examine_list["EMP proof"] = "[source.p_They()] [source.p_are()] unaffected by electromagnetic pulses, and shields [source.p_their()] contents and wiring from them."
return
if(flags & EMP_PROTECT_SELF)
examine_list["EMP resilient"] = "[source.p_They()] [source.p_are()] unaffected by electromagnetic pulses."
if(flags & (EMP_PROTECT_CONTENTS|EMP_PROTECT_WIRES) == (EMP_PROTECT_CONTENTS|EMP_PROTECT_WIRES))
examine_list["EMP blocking"] = "[source.p_They()] protects [source.p_their()] wiring and contents from electromagnetic pulses."
else if(flags & EMP_PROTECT_CONTENTS)
examine_list["EMP blocking"] = "[source.p_They()] protects [source.p_their()] contents from electromagnetic pulses."
else if(flags & EMP_PROTECT_WIRES)
examine_list["EMP blocking"] = "[source.p_They()] protects [source.p_their()] wiring from electromagnetic pulses."
+5 -7
View File
@@ -25,23 +25,21 @@
///signal called on the parent attacking an item
/datum/element/envenomable_casing/proc/handle_interaction(obj/item/ammo_casing/casing, mob/user, atom/target, list/modifiers)
SIGNAL_HANDLER
if(!is_reagent_container(target))
if(!target.is_open_container())
return NONE
var/obj/item/reagent_containers/venom_container = target
if(!casing.loaded_projectile)
user.balloon_alert(user, "casing is already spent!")
return ITEM_INTERACT_BLOCKING
if(!(venom_container.reagent_flags & OPENCONTAINER))
user.balloon_alert(user, "open the container!")
return ITEM_INTERACT_BLOCKING
var/datum/reagent/venom_applied = venom_container.reagents.get_master_reagent()
var/datum/reagent/venom_applied = target.reagents.get_master_reagent()
if(!venom_applied)
return ITEM_INTERACT_BLOCKING
var/amount_applied = min(venom_applied.volume, amount_allowed)
casing.loaded_projectile.AddElement(/datum/element/venomous, venom_applied.type, amount_applied)
to_chat(user, span_notice("You coat [casing] in [venom_applied]."))
venom_container.reagents.remove_reagent(venom_applied.type, amount_applied)
target.reagents.remove_reagent(venom_applied.type, amount_applied)
///stops further poison application
UnregisterSignal(casing, COMSIG_ITEM_INTERACTING_WITH_ATOM)
RegisterSignal(casing, COMSIG_ATOM_EXAMINE, PROC_REF(on_examine_after_dip), override = TRUE)
+9 -11
View File
@@ -16,21 +16,19 @@
. = ..()
UnregisterSignal(target, COMSIG_ITEM_INTERACTING_WITH_ATOM)
/datum/element/dunkable/proc/get_dunked(datum/source, mob/user, atom/target, params)
/datum/element/dunkable/proc/get_dunked(obj/item/source, mob/user, atom/target, params)
SIGNAL_HANDLER
var/obj/item/reagent_containers/container = target // the container we're trying to dunk into
if(istype(container) && (container.reagent_flags & DUNKABLE)) // container should be a valid target for dunking
if(!container.is_drainable())
to_chat(user, span_warning("[container] is unable to be dunked in!"))
if(target.reagents?.flags & DUNKABLE) // container should be a valid target for dunking
if(!target.is_drainable())
to_chat(user, span_warning("[target] is unable to be dunked in!"))
return ITEM_INTERACT_BLOCKING
var/obj/item/I = source // the item that has the dunkable element
if(container.reagents.trans_to(I, dunk_amount, transferred_by = user)) //if reagents were transferred, show the message
to_chat(user, span_notice("You dunk \the [I] into \the [container]."))
if(target.reagents.trans_to(source, dunk_amount, transferred_by = user)) //if reagents were transferred, show the message
to_chat(user, span_notice("You dunk \the [target] into \the [target]."))
return ITEM_INTERACT_SUCCESS
if(!container.reagents.total_volume)
to_chat(user, span_warning("[container] is empty!"))
if(!target.reagents.total_volume)
to_chat(user, span_warning("[target] is empty!"))
else
to_chat(user, span_warning("[I] is full!"))
to_chat(user, span_warning("[source] is full!"))
return ITEM_INTERACT_BLOCKING
return NONE
+7 -2
View File
@@ -269,10 +269,12 @@ GLOBAL_LIST_INIT(immerse_ignored_movable, typecacheof(list(
/// A band-aid to keep the (unique) visual overlay from scaling and rotating along with its owner. I'm sorry.
/datum/element/immerse/proc/on_update_transform(mob/living/source, resize, new_lying_angle, is_opposite_angle)
SIGNAL_HANDLER
var/atom/movable/immerse_mask/effect_relay = generated_visual_overlays[source]
if (!effect_relay)
return
var/matrix/new_transform = matrix()
new_transform.Scale(1 / source.current_size)
new_transform.Turn(-new_lying_angle)
var/atom/movable/immerse_mask/effect_relay = generated_visual_overlays[source]
var/mutable_appearance/relay_appearance = new(effect_relay.appearance)
relay_appearance.transform = new_transform
effect_relay.appearance = relay_appearance
@@ -281,10 +283,13 @@ GLOBAL_LIST_INIT(immerse_ignored_movable, typecacheof(list(
/datum/element/immerse/proc/on_spin_animation(atom/source, speed, loops, segments, segment)
SIGNAL_HANDLER
var/atom/movable/immerse_mask/immerse_mask = generated_visual_overlays[source]
immerse_mask.do_spin_animation(speed, loops, segments, -segment)
if (immerse_mask)
immerse_mask.do_spin_animation(speed, loops, segments, -segment)
/datum/element/immerse/proc/on_update_offsets(mob/living/source, new_x, new_y, new_w, new_z, animate)
SIGNAL_HANDLER
if (!generated_visual_overlays[source])
return
var/old_height = ceil(max(ICON_SIZE_Y - source.pixel_z, ICON_SIZE_Y) / ICON_SIZE_Y)
var/new_height = ceil(max(ICON_SIZE_Y - new_z, ICON_SIZE_Y) / ICON_SIZE_Y)
if (old_height != new_height)
+62
View File
@@ -0,0 +1,62 @@
/// Element that grants an achievement to all mobs who killed the owner when it dies
/datum/element/kill_achievement
element_flags = ELEMENT_BESPOKE
argument_hash_start_idx = 2
/// Achievements to grant when killed
var/list/achievement_types = null
/// Achievement to grant when killed with a crusher
var/crusher_achievement_type = null
/// A memory to grant to killers, if any
var/kill_memory_type = null
/// Range in which to grant the achievement
var/achievement_range = 7
/// Threshold for damage dealt with a crusher to count it as a crusher kill
/// If null, then no kill counts as a crusher kill
var/crusher_kill_threshold = 0.6
/// Blackbox tally string to record kills into
var/tally_string = "megafauna_kills"
/datum/element/kill_achievement/Attach(datum/target, list/achievement_types, crusher_achievement_type, kill_memory_type, achievement_range = 7, crusher_kill_threshold = 0.6, tally_string = "megafauna_kills")
. = ..()
if (!isliving(target) || !length(achievement_types))
return ELEMENT_INCOMPATIBLE
src.achievement_types = achievement_types
src.crusher_achievement_type = crusher_achievement_type
src.kill_memory_type = kill_memory_type
src.achievement_range = achievement_range
src.crusher_kill_threshold = crusher_kill_threshold
src.tally_string = tally_string
RegisterSignal(target, COMSIG_LIVING_DEATH, PROC_REF(on_death))
/datum/element/kill_achievement/Detach(datum/target)
. = ..()
UnregisterSignal(target, COMSIG_LIVING_DEATH)
/datum/element/kill_achievement/proc/on_death(mob/living/source, gibbed)
SIGNAL_HANDLER
if ((source.flags_1 & ADMIN_SPAWNED_1) || !SSachievements.achievements_enabled)
return
// Check whether we killed the megafauna with primarily crusher damage or not
var/datum/status_effect/crusher_damage/crusher_dmg = source.has_status_effect(/datum/status_effect/crusher_damage)
var/crusher_kill = (!isnull(crusher_kill_threshold) && crusher_dmg && (crusher_dmg.total_damage >= floor(source.maxHealth * 0.6)))
var/turf/our_loc = get_turf(source)
if (!our_loc)
return
for (var/mob/living/player in SSmobs.clients_by_zlevel[our_loc.z])
var/turf/player_turf = get_turf(player)
if (player.stat || get_dist(player_turf, source) > achievement_range)
continue
for (var/achievement_type in achievement_types)
player.client.give_award(achievement_type, player)
if (kill_memory_type)
player.add_mob_memory(kill_memory_type, antagonist = source)
if(crusher_kill && crusher_achievement_type && istype(player.get_active_held_item(), /obj/item/kinetic_crusher))
player.client.give_award(crusher_achievement_type, player)
SSblackbox.record_feedback("tally", "[tally_string][crusher_kill ? "_crusher" : ""]", 1, "[initial(source.name)]")
-28
View File
@@ -1,28 +0,0 @@
///element given to mobs that can mine when moving
/datum/element/proficient_miner
/datum/element/proficient_miner/Attach(datum/target)
. = ..()
if(!ismovable(target))
return
RegisterSignal(target, COMSIG_MOVABLE_BUMP, PROC_REF(on_bump))
/datum/element/proficient_miner/proc/on_bump(mob/living/source, atom/target)
SIGNAL_HANDLER
if(!ismineralturf(target) || (istype(source) && source.stat != CONSCIOUS))
return
var/turf/closed/mineral/mineral_wall = target
if(!istype(mineral_wall, /turf/closed/mineral/gibtonite))
mineral_wall.gets_drilled(source)
return
var/turf/closed/mineral/gibtonite/gibtonite_wall = mineral_wall
if(gibtonite_wall.stage == GIBTONITE_UNSTRUCK)
mineral_wall.gets_drilled(source)
/datum/element/proficient_miner/Detach(datum/source, ...)
UnregisterSignal(source, COMSIG_MOVABLE_BUMP)
return ..()
+2 -2
View File
@@ -384,8 +384,8 @@
result["name"] = item.name
result["alternate"] = item_data.get_alternate_actions(owner, user)
var/static/list/already_cried = list()
if(length(result["alternate"]) > 2 && !(type in already_cried))
stack_trace("Too many alternate actions for [type]! Only two are supported at the moment! This will look bad!")
if(length(result["alternate"]) > 3 && !(type in already_cried))
stack_trace("Too many alternate actions for [type]! Only three are supported at the moment! This will look bad!")
already_cried += type
items[strippable_key] = result