diff --git a/.github/alternate_byond_versions.txt b/.github/alternate_byond_versions.txt
index 1407b98d98a7..830244379f6f 100644
--- a/.github/alternate_byond_versions.txt
+++ b/.github/alternate_byond_versions.txt
@@ -2,7 +2,12 @@
# This is useful for making sure we maintain compatibility with both older and newer versions,
# while still having our main tests run on a guaranteed pinned version.
-# Format is version: map
+# Format is "version: map" or "version: map;max_required_client_version"
# Example:
# 500.1337: runtimestation
-515.1636: runtimestation
+# 516.1638: runtimestation;516
+# Lowest supported version
+# NOVA EDIT - Original: 515.1627: runtimestation
+515.1637: runtimestation
+# Beta version
+516.1648: runtimestation;516
diff --git a/.github/guides/STANDARDS.md b/.github/guides/STANDARDS.md
index c27c8ae7417e..00fc1efc3849 100644
--- a/.github/guides/STANDARDS.md
+++ b/.github/guides/STANDARDS.md
@@ -68,8 +68,6 @@ var/path_type = "/obj/item/baseball_bat"
* You are expected to help maintain the code that you add, meaning that if there is a problem then you are likely to be approached in order to fix any issues, runtimes, or bugs.
-* Do not divide when you can easily convert it to multiplication. (ie `4/2` should be done as `4*0.5`)
-
* Separating single lines into more readable blocks is not banned, however you should use it only where it makes new information more accessible, or aids maintainability. We do not have a column limit, and mass conversions will not be received well.
* If you used regex to replace code during development of your code, post the regex in your PR for the benefit of future developers and downstream users.
diff --git a/.github/workflows/ci_suite.yml b/.github/workflows/ci_suite.yml
index fab8f6799219..1d93bf93fc47 100644
--- a/.github/workflows/ci_suite.yml
+++ b/.github/workflows/ci_suite.yml
@@ -190,7 +190,7 @@ jobs:
- name: Find Alternate Tests
id: alternate_test_finder
run: |
- ALTERNATE_TESTS_JSON=$(jq -nRc '[inputs | capture("^(?[0-9]+)\\.(?[0-9]+): (?
"
- C << browse(html.Join(), "window=[tag];size=600x400")
+ var/datum/browser/browser = new(C.mob, "[tag]", "Report for map file [original_path]", 600, 400)
+ browser.set_content(html.Join())
+ browser.open()
/datum/map_report/Topic(href, href_list)
. = ..()
diff --git a/code/modules/meteors/meteor_types.dm b/code/modules/meteors/meteor_types.dm
index 287a93821aeb..8d3fcc67f53b 100644
--- a/code/modules/meteors/meteor_types.dm
+++ b/code/modules/meteors/meteor_types.dm
@@ -45,6 +45,9 @@
/obj/effect/meteor/Destroy()
GLOB.meteor_list -= src
+ var/datum/move_loop/moveloop = GLOB.move_manager.processing_on(src, SSmovement)
+ if (!isnull(moveloop))
+ UnregisterSignal(moveloop, COMSIG_MOVELOOP_STOP)
return ..()
/obj/effect/meteor/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change = TRUE)
diff --git a/code/modules/mining/equipment/kheiral_cuffs.dm b/code/modules/mining/equipment/kheiral_cuffs.dm
index 27b3a1c42a70..0f1e4f12fd84 100644
--- a/code/modules/mining/equipment/kheiral_cuffs.dm
+++ b/code/modules/mining/equipment/kheiral_cuffs.dm
@@ -93,7 +93,6 @@
if(isliving(loc))
connect_kheiral_network(loc)
-// LEMON AND HERE
/obj/item/kheiral_cuffs/worn_overlays(mutable_appearance/standing, isinhands, icon_file)
. = ..()
if(!isinhands)
diff --git a/code/modules/mining/equipment/kinetic_crusher.dm b/code/modules/mining/equipment/kinetic_crusher.dm
deleted file mode 100644
index e02f8836b91a..000000000000
--- a/code/modules/mining/equipment/kinetic_crusher.dm
+++ /dev/null
@@ -1,579 +0,0 @@
-/**
- * Kinetic Crusher
- *
- * Lavaland's "Hard Mode" option for players, requiring melee attacks (backstabs even better),
- * but allowing you to upgrade it with trophies gained from fighting lavaland monsters, making it
- * a good tradeoff and a decent playstyle.
- */
-/obj/item/kinetic_crusher
- icon = 'icons/obj/mining.dmi'
- icon_state = "crusher"
- inhand_icon_state = "crusher0"
- lefthand_file = 'icons/mob/inhands/weapons/hammers_lefthand.dmi'
- righthand_file = 'icons/mob/inhands/weapons/hammers_righthand.dmi'
- name = "proto-kinetic crusher"
- desc = "An early design of the proto-kinetic accelerator, it is little more than a combination of various mining tools cobbled together, \
- forming a high-tech club. While it is an effective mining tool, it did little to aid any but the most skilled and/or \
- suicidal miners against local fauna."
- resistance_flags = FIRE_PROOF
- force = 0 //You can't hit stuff unless wielded
- w_class = WEIGHT_CLASS_BULKY
- slot_flags = ITEM_SLOT_BACK
- throwforce = 5
- throw_speed = 4
- armour_penetration = 10
- custom_materials = list(/datum/material/iron=HALF_SHEET_MATERIAL_AMOUNT*1.15, /datum/material/glass=HALF_SHEET_MATERIAL_AMOUNT*2.075)
- hitsound = 'sound/items/weapons/bladeslice.ogg'
- attack_verb_continuous = list("smashes", "crushes", "cleaves", "chops", "pulps")
- attack_verb_simple = list("smash", "crush", "cleave", "chop", "pulp")
- sharpness = SHARP_EDGED
- actions_types = list(/datum/action/item_action/toggle_light)
- obj_flags = UNIQUE_RENAME
- light_system = OVERLAY_LIGHT
- light_range = 5
- light_power = 1.2
- light_color = "#ffff66"
- light_on = FALSE
- ///List of all crusher trophies attached to this.
- var/list/obj/item/crusher_trophy/trophies = list()
- var/charged = TRUE
- var/charge_time = 1.5 SECONDS
- var/detonation_damage = 50
- var/backstab_bonus = 30
- var/current_inhand_icon_state = "crusher" //variable used by retool kits when changing the crusher's appearance
- var/projectile_icon = "pulse1" //variable used by retool kits when changing the crusher's projectile sprite
-
-/obj/item/kinetic_crusher/Initialize(mapload)
- . = ..()
- AddComponent(/datum/component/butchering, \
- speed = 6 SECONDS, \
- effectiveness = 110, \
- )
- //technically it's huge and bulky, but this provides an incentive to use it
- AddComponent(/datum/component/two_handed, force_unwielded=0, force_wielded=20)
-
-/obj/item/kinetic_crusher/Destroy()
- QDEL_LIST(trophies)
- return ..()
-
-/obj/item/kinetic_crusher/Exited(atom/movable/gone, direction)
- . = ..()
- trophies -= gone
-
-/obj/item/kinetic_crusher/examine(mob/living/user)
- . = ..()
- . += span_notice("Mark a large creature with a destabilizing force with right-click, then hit them in melee to do [force + detonation_damage] damage.")
- . += span_notice("Does [force + detonation_damage + backstab_bonus] damage if the target is backstabbed, instead of [force + detonation_damage].")
- for(var/obj/item/crusher_trophy/crusher_trophy as anything in trophies)
- . += span_notice("It has \a [crusher_trophy] attached, which causes [crusher_trophy.effect_desc()].")
-
-/obj/item/kinetic_crusher/attackby(obj/item/attacking_item, mob/user, params)
- if(istype(attacking_item, /obj/item/crusher_trophy))
- var/obj/item/crusher_trophy/crusher_trophy = attacking_item
- crusher_trophy.add_to(src, user)
- return
- return ..()
-
-/obj/item/kinetic_crusher/crowbar_act(mob/living/user, obj/item/tool)
- . = ..()
- if(!LAZYLEN(trophies))
- user.balloon_alert(user, "no trophies!")
- return ITEM_INTERACT_BLOCKING
- user.balloon_alert(user, "trophies removed")
- tool.play_tool_sound(src)
- for(var/obj/item/crusher_trophy/crusher_trophy as anything in trophies)
- crusher_trophy.remove_from(src, user)
- return ITEM_INTERACT_SUCCESS
-
-/obj/item/kinetic_crusher/pre_attack(atom/A, mob/living/user, params)
- . = ..()
- if(.)
- return TRUE
- if(!HAS_TRAIT(src, TRAIT_WIELDED) && !acts_as_if_wielded) // NOVA EDIT CHANGE - Original: if(!HAS_TRAIT(src, TRAIT_WIELDED))
- user.balloon_alert(user, "must be wielded!")
- return TRUE
- return .
-
-/obj/item/kinetic_crusher/attack(mob/living/target, mob/living/carbon/user)
- target.apply_status_effect(/datum/status_effect/crusher_damage)
- return ..()
-
-/obj/item/kinetic_crusher/afterattack(mob/living/target, mob/living/user, clickparams)
- if(!isliving(target))
- return
- // Melee effect
- for(var/obj/item/crusher_trophy/crusher_trophy as anything in trophies)
- crusher_trophy.on_melee_hit(target, user)
- if(QDELETED(target))
- return
- var/datum/status_effect/crusher_mark/mark = target.has_status_effect(/datum/status_effect/crusher_mark)
- var/boosted_mark = mark?.boosted
- if(!target.remove_status_effect(mark))
- return
- // Detonation effect
- var/datum/status_effect/crusher_damage/crusher_damage_effect = target.has_status_effect(/datum/status_effect/crusher_damage) || target.apply_status_effect(/datum/status_effect/crusher_damage)
- var/target_health = target.health
- for(var/obj/item/crusher_trophy/crusher_trophy as anything in trophies)
- crusher_trophy.on_mark_detonation(target, user)
- if(QDELETED(target))
- return
- if(!QDELETED(crusher_damage_effect))
- crusher_damage_effect.total_damage += target_health - target.health //we did some damage, but let's not assume how much we did
- new /obj/effect/temp_visual/kinetic_blast(get_turf(target))
- var/backstabbed = FALSE
- var/combined_damage = detonation_damage
- var/def_check = target.getarmor(type = BOMB)
- // Backstab bonus
- if(check_behind(user, target) || boosted_mark)
- backstabbed = TRUE
- combined_damage += backstab_bonus
- playsound(user, 'sound/items/weapons/kinetic_accel.ogg', 100, TRUE) //Seriously who spelled it wrong
- if(!QDELETED(crusher_damage_effect))
- crusher_damage_effect.total_damage += combined_damage
- SEND_SIGNAL(user, COMSIG_LIVING_CRUSHER_DETONATE, target, src, backstabbed)
- target.apply_damage(combined_damage, BRUTE, blocked = def_check)
-
-/obj/item/kinetic_crusher/interact_with_atom_secondary(atom/interacting_with, mob/living/user, list/modifiers)
- if(!HAS_TRAIT(src, TRAIT_WIELDED) && !acts_as_if_wielded) // NOVA EDIT CHANGE - Original: if(!HAS_TRAIT(src, TRAIT_WIELDED))
- balloon_alert(user, "wield it first!")
- return ITEM_INTERACT_BLOCKING
- if(interacting_with == user)
- balloon_alert(user, "can't aim at yourself!")
- return ITEM_INTERACT_BLOCKING
- fire_kinetic_blast(interacting_with, user, modifiers)
- user.changeNext_move(CLICK_CD_MELEE)
- return ITEM_INTERACT_SUCCESS
-
-/obj/item/kinetic_crusher/ranged_interact_with_atom_secondary(atom/interacting_with, mob/living/user, list/modifiers)
- return interact_with_atom_secondary(interacting_with, user, modifiers)
-
-/obj/item/kinetic_crusher/proc/fire_kinetic_blast(atom/target, mob/living/user, list/modifiers)
- if(!charged)
- return
- var/turf/proj_turf = user.loc
- if(!isturf(proj_turf))
- return
- var/obj/projectile/destabilizer/destabilizer = new(proj_turf)
- destabilizer.icon_state = "[projectile_icon]"
- for(var/obj/item/crusher_trophy/attached_trophy as anything in trophies)
- attached_trophy.on_projectile_fire(destabilizer, user)
- destabilizer.aim_projectile(target, user, modifiers)
- destabilizer.firer = user
- playsound(user, 'sound/items/weapons/plasma_cutter.ogg', 100, TRUE)
- destabilizer.fire()
- charged = FALSE
- update_appearance()
- addtimer(CALLBACK(src, PROC_REF(recharge_projectile)), charge_time)
-
-/obj/item/kinetic_crusher/proc/recharge_projectile()
- if(!charged)
- charged = TRUE
- update_appearance()
- playsound(src.loc, 'sound/items/weapons/kinetic_reload.ogg', 60, TRUE)
-
-/obj/item/kinetic_crusher/ui_action_click(mob/user, actiontype)
- set_light_on(!light_on)
- playsound(user, 'sound/items/weapons/empty.ogg', 100, TRUE)
- update_appearance()
-
-/obj/item/kinetic_crusher/on_saboteur(datum/source, disrupt_duration)
- . = ..()
- set_light_on(FALSE)
- playsound(src, 'sound/items/weapons/empty.ogg', 100, TRUE)
- return TRUE
-
-/obj/item/kinetic_crusher/update_icon_state()
- inhand_icon_state = "[current_inhand_icon_state][HAS_TRAIT(src, TRAIT_WIELDED)]" // this is not icon_state and not supported by 2hcomponent
- return ..()
-
-/obj/item/kinetic_crusher/update_overlays()
- . = ..()
- if(!charged)
- . += "[icon_state]_uncharged"
- if(light_on)
- . += "[icon_state]_lit"
-
-/obj/item/kinetic_crusher/compact //for admins
- name = "compact kinetic crusher"
- w_class = WEIGHT_CLASS_NORMAL
-
-//destablizing force
-/obj/projectile/destabilizer
- name = "destabilizing force"
- damage = 0 //We're just here to mark people. This is still a melee weapon.
- damage_type = BRUTE
- armor_flag = BOMB
- range = 6
- log_override = TRUE
- /// Has this projectile been boosted
- var/boosted = FALSE
-
-/obj/projectile/destabilizer/Initialize(mapload)
- . = ..()
- AddComponent(/datum/component/parriable_projectile, parry_callback = CALLBACK(src, PROC_REF(on_parry)))
-
-/obj/projectile/destabilizer/proc/on_parry(mob/user)
- SIGNAL_HANDLER
- boosted = TRUE
- // Get a bit of a damage/range boost after being parried
- damage = 10
- range = 9
-
-/obj/projectile/destabilizer/on_hit(atom/target, blocked = 0, pierce_hit)
- if(isliving(target))
- var/mob/living/living_target = target
- living_target.apply_status_effect(/datum/status_effect/crusher_mark, boosted)
- var/target_turf = get_turf(target)
- if(ismineralturf(target_turf))
- var/turf/closed/mineral/hit_mineral = target_turf
- new /obj/effect/temp_visual/kinetic_blast(hit_mineral)
- hit_mineral.gets_drilled(firer)
- return ..()
-
-//trophies
-/obj/item/crusher_trophy
- name = "tail spike"
- desc = "A strange spike with no usage."
- icon = 'icons/obj/mining_zones/artefacts.dmi'
- icon_state = "tail_spike"
- var/bonus_value = 10 //if it has a bonus effect, this is how much that effect is
- var/denied_type = /obj/item/crusher_trophy
-
-/obj/item/crusher_trophy/examine(mob/living/user)
- . = ..()
- . += span_notice("Causes [effect_desc()] when attached to a kinetic crusher.")
-
-/obj/item/crusher_trophy/proc/effect_desc()
- return "errors"
-
-/obj/item/crusher_trophy/attackby(obj/item/A, mob/living/user)
- if(istype(A, /obj/item/kinetic_crusher))
- add_to(A, user)
- else
- ..()
-
-/obj/item/crusher_trophy/proc/add_to(obj/item/kinetic_crusher/crusher, mob/living/user)
- for(var/obj/item/crusher_trophy/trophy as anything in crusher.trophies)
- if(istype(trophy, denied_type) || istype(src, trophy.denied_type))
- to_chat(user, span_warning("You can't seem to attach [src] to [crusher]. Maybe remove a few trophies?"))
- return FALSE
- if(!user.transferItemToLoc(src, crusher))
- return
- crusher.trophies += src
- to_chat(user, span_notice("You attach [src] to [crusher]."))
- return TRUE
-
-/obj/item/crusher_trophy/proc/remove_from(obj/item/kinetic_crusher/crusher, mob/living/user)
- forceMove(get_turf(crusher))
- return TRUE
-
-/obj/item/crusher_trophy/proc/on_melee_hit(mob/living/target, mob/living/user) //the target and the user
-/obj/item/crusher_trophy/proc/on_projectile_fire(obj/projectile/destabilizer/marker, mob/living/user) //the projectile fired and the user
-/obj/item/crusher_trophy/proc/on_mark_detonation(mob/living/target, mob/living/user) //the target and the user
-
-//watcher
-/obj/item/crusher_trophy/watcher_wing
- name = "watcher wing"
- desc = "A wing ripped from a watcher. Suitable as a trophy for a kinetic crusher."
- icon_state = "watcher_wing"
- denied_type = /obj/item/crusher_trophy/watcher_wing
- bonus_value = 5
-
-/obj/item/crusher_trophy/watcher_wing/effect_desc()
- return "mark detonation to prevent certain creatures from using certain attacks for [bonus_value*0.1] second\s"
-
-/obj/item/crusher_trophy/watcher_wing/on_mark_detonation(mob/living/target, mob/living/user)
- if(ishostile(target))
- var/mob/living/simple_animal/hostile/H = target
- if(H.ranged) //briefly delay ranged attacks
- if(H.ranged_cooldown >= world.time)
- H.ranged_cooldown += bonus_value
- else
- H.ranged_cooldown = bonus_value + world.time
-
-//magmawing watcher
-/obj/item/crusher_trophy/blaster_tubes/magma_wing
- name = "magmawing watcher wing"
- desc = "A still-searing wing from a magmawing watcher. Suitable as a trophy for a kinetic crusher."
- icon_state = "magma_wing"
- gender = NEUTER
- bonus_value = 5
-
-/obj/item/crusher_trophy/blaster_tubes/magma_wing/effect_desc()
- return "mark detonation to make the next destabilizer shot deal [bonus_value] damage"
-
-/obj/item/crusher_trophy/blaster_tubes/magma_wing/on_projectile_fire(obj/projectile/destabilizer/marker, mob/living/user)
- if(deadly_shot)
- marker.name = "heated [marker.name]"
- marker.icon_state = "lava"
- marker.damage = bonus_value
- deadly_shot = FALSE
-
-//icewing watcher
-/obj/item/crusher_trophy/watcher_wing/ice_wing
- name = "icewing watcher wing"
- desc = "A carefully preserved frozen wing from an icewing watcher. Suitable as a trophy for a kinetic crusher."
- icon_state = "ice_wing"
- bonus_value = 8
-
-//legion
-/obj/item/crusher_trophy/legion_skull
- name = "legion skull"
- desc = "A dead and lifeless legion skull. Suitable as a trophy for a kinetic crusher."
- icon_state = "legion_skull"
- denied_type = /obj/item/crusher_trophy/legion_skull
- bonus_value = 3
-
-/obj/item/crusher_trophy/legion_skull/effect_desc()
- return "a kinetic crusher to recharge [bonus_value*0.1] second\s faster"
-
-/obj/item/crusher_trophy/legion_skull/add_to(obj/item/kinetic_crusher/pkc, mob/living/user)
- . = ..()
- if(.)
- pkc.charge_time -= bonus_value
-
-/obj/item/crusher_trophy/legion_skull/remove_from(obj/item/kinetic_crusher/pkc, mob/living/user)
- . = ..()
- if(.)
- pkc.charge_time += bonus_value
-
-//blood-drunk hunter
-/obj/item/crusher_trophy/miner_eye
- name = "eye of a blood-drunk hunter"
- desc = "Its pupil is collapsed and turned to mush. Suitable as a trophy for a kinetic crusher."
- icon_state = "hunter_eye"
- denied_type = /obj/item/crusher_trophy/miner_eye
-
-/obj/item/crusher_trophy/miner_eye/effect_desc()
- return "mark detonation to grant stun immunity and 90% damage reduction for 1 second"
-
-/obj/item/crusher_trophy/miner_eye/on_mark_detonation(mob/living/target, mob/living/user)
- user.apply_status_effect(/datum/status_effect/blooddrunk)
-
-//ash drake
-/obj/item/crusher_trophy/tail_spike
- desc = "A spike taken from an ash drake's tail. Suitable as a trophy for a kinetic crusher."
- denied_type = /obj/item/crusher_trophy/tail_spike
- bonus_value = 5
-
-/obj/item/crusher_trophy/tail_spike/effect_desc()
- return "mark detonation to do [bonus_value] damage to nearby creatures and push them back"
-
-/obj/item/crusher_trophy/tail_spike/on_mark_detonation(mob/living/target, mob/living/user)
- for(var/mob/living/living_target in oview(2, user))
- if(user.faction_check_atom(living_target) || living_target.stat == DEAD)
- continue
- playsound(living_target, 'sound/effects/magic/fireball.ogg', 20, TRUE)
- new /obj/effect/temp_visual/fire(living_target.loc)
- addtimer(CALLBACK(src, PROC_REF(pushback), living_target, user), 1) //no free backstabs, we push AFTER module stuff is done
- living_target.adjustFireLoss(bonus_value, forced = TRUE)
-
-/obj/item/crusher_trophy/tail_spike/proc/pushback(mob/living/target, mob/living/user)
- if(!QDELETED(target) && !QDELETED(user) && (!target.anchored || ismegafauna(target))) //megafauna will always be pushed
- step(target, get_dir(user, target))
-
-//bubblegum
-/obj/item/crusher_trophy/demon_claws
- name = "demon claws"
- desc = "A set of blood-drenched claws from a massive demon's hand. Suitable as a trophy for a kinetic crusher."
- icon_state = "demon_claws"
- gender = PLURAL
- denied_type = /obj/item/crusher_trophy/demon_claws
- bonus_value = 10
- var/static/list/damage_heal_order = list(BRUTE, BURN, OXY)
-
-/obj/item/crusher_trophy/demon_claws/effect_desc()
- return "melee hits to do [bonus_value * 0.2] more damage and heal you for [bonus_value * 0.1], with 5X effect on mark detonation"
-
-/obj/item/crusher_trophy/demon_claws/add_to(obj/item/kinetic_crusher/pkc, mob/living/user)
- . = ..()
- if(.)
- pkc.force += bonus_value * 0.2
- pkc.detonation_damage += bonus_value * 0.8
- AddComponent(/datum/component/two_handed, force_wielded=(20 + bonus_value * 0.2))
-
-/obj/item/crusher_trophy/demon_claws/remove_from(obj/item/kinetic_crusher/pkc, mob/living/user)
- . = ..()
- if(.)
- pkc.force -= bonus_value * 0.2
- pkc.detonation_damage -= bonus_value * 0.8
- AddComponent(/datum/component/two_handed, force_wielded=20)
-
-/obj/item/crusher_trophy/demon_claws/on_melee_hit(mob/living/target, mob/living/user)
- user.heal_ordered_damage(bonus_value * 0.1, damage_heal_order)
-
-/obj/item/crusher_trophy/demon_claws/on_mark_detonation(mob/living/target, mob/living/user)
- user.heal_ordered_damage(bonus_value * 0.4, damage_heal_order)
-
-//colossus
-/obj/item/crusher_trophy/blaster_tubes
- name = "blaster tubes"
- desc = "The blaster tubes from a colossus's arm. Suitable as a trophy for a kinetic crusher."
- icon_state = "blaster_tubes"
- gender = PLURAL
- denied_type = /obj/item/crusher_trophy/blaster_tubes
- bonus_value = 15
- var/deadly_shot = FALSE
-
-/obj/item/crusher_trophy/blaster_tubes/effect_desc()
- return "mark detonation to make the next destabilizer shot deal [bonus_value] damage but move slower"
-
-/obj/item/crusher_trophy/blaster_tubes/on_projectile_fire(obj/projectile/destabilizer/marker, mob/living/user)
- if(deadly_shot)
- marker.name = "deadly [marker.name]"
- marker.icon_state = "chronobolt"
- marker.damage = bonus_value
- marker.speed = 0.5
- deadly_shot = FALSE
-
-/obj/item/crusher_trophy/blaster_tubes/on_mark_detonation(mob/living/target, mob/living/user)
- deadly_shot = TRUE
- addtimer(CALLBACK(src, PROC_REF(reset_deadly_shot)), 300, TIMER_UNIQUE|TIMER_OVERRIDE)
-
-/obj/item/crusher_trophy/blaster_tubes/proc/reset_deadly_shot()
- deadly_shot = FALSE
-
-//hierophant
-/obj/item/crusher_trophy/vortex_talisman
- name = "vortex talisman"
- desc = "A glowing trinket that was originally the Hierophant's beacon. Suitable as a trophy for a kinetic crusher."
- icon_state = "vortex_talisman"
- denied_type = /obj/item/crusher_trophy/vortex_talisman
-
-/obj/item/crusher_trophy/vortex_talisman/effect_desc()
- return "mark detonation to create a homing hierophant chaser"
-
-/obj/item/crusher_trophy/vortex_talisman/on_mark_detonation(mob/living/target, mob/living/user)
- if(isliving(target))
- var/obj/effect/temp_visual/hierophant/chaser/chaser = new(get_turf(user), user, target, 3, TRUE)
- chaser.monster_damage_boost = FALSE // Weaker cuz no cooldown
- chaser.damage = 20
- log_combat(user, target, "fired a chaser at", src)
-
-/obj/item/crusher_trophy/ice_demon_cube
- name = "demonic cube"
- desc = "A stone cold cube dropped from an ice demon."
- icon_state = "ice_demon_cube"
- denied_type = /obj/item/crusher_trophy/ice_demon_cube
- ///how many will we summon?
- var/summon_amount = 2
- ///cooldown to summon demons upon the target
- COOLDOWN_DECLARE(summon_cooldown)
-
-/obj/item/crusher_trophy/ice_demon_cube/effect_desc()
- return "mark detonation to unleash demonic ice clones upon the target"
-
-/obj/item/crusher_trophy/ice_demon_cube/on_mark_detonation(mob/living/target, mob/living/user)
- if(isnull(target) || !COOLDOWN_FINISHED(src, summon_cooldown))
- return
- for(var/i in 1 to summon_amount)
- var/turf/drop_off = find_dropoff_turf(target, user)
- var/mob/living/basic/mining/demon_afterimage/crusher/friend = new(drop_off)
- friend.faction = list(FACTION_NEUTRAL)
- friend.befriend(user)
- friend.ai_controller?.set_blackboard_key(BB_BASIC_MOB_CURRENT_TARGET, target)
- COOLDOWN_START(src, summon_cooldown, 30 SECONDS)
-
-///try to make them spawn all around the target to surround him
-/obj/item/crusher_trophy/ice_demon_cube/proc/find_dropoff_turf(mob/living/target, mob/living/user)
- var/list/turfs_list = get_adjacent_open_turfs(target)
- for(var/turf/possible_turf in turfs_list)
- if(possible_turf.is_blocked_turf())
- continue
- return possible_turf
- return get_turf(user)
-
-//wolf trophy
-
-/obj/item/crusher_trophy/wolf_ear
- name = "wolf ear"
- desc = "It's a wolf ear."
- icon_state = "wolf_ear"
- denied_type = /obj/item/crusher_trophy/wolf_ear
-
-/obj/item/crusher_trophy/wolf_ear/effect_desc()
- return "mark detonation to gain a slight speed boost temporarily"
-
-/obj/item/crusher_trophy/wolf_ear/on_mark_detonation(mob/living/target, mob/living/user)
- user.apply_status_effect(/datum/status_effect/speed_boost, 1 SECONDS)
-
-//cosmetic items for changing the crusher's look
-
-/obj/item/crusher_trophy/retool_kit
- name = "crusher sword retool kit"
- desc = "A toolkit for changing the crusher's appearance without affecting the device's function. This one will make it look like a sword."
- icon = 'icons/obj/mining.dmi'
- icon_state = "retool_kit"
- denied_type = /obj/item/crusher_trophy/retool_kit
- ///Specifies the sprite/icon state which the crusher is changed to as an item. Should appear in the icons/obj/mining.dmi file with accompanying "lit" and "recharging" sprites
- var/retool_icon = "crusher_sword"
- ///Specifies the icon state for the crusher's appearance in hand. Should appear in both icons/mob/inhands/weapons/hammers_lefthand.dmi and icons/mob/inhands/weapons/hammers_righthand.dmi
- var/retool_inhand_icon = "crusher_sword"
- ///For if the retool kit changes the projectile's appearance. The sprite should be in icons/obj/weapons/guns/projectiles.dmi
- var/retool_projectile_icon = "pulse1"
-
-/obj/item/crusher_trophy/retool_kit/effect_desc()
- return "the crusher to have the appearance of a sword"
-
-/obj/item/crusher_trophy/retool_kit/add_to(obj/item/kinetic_crusher/pkc, mob/user)
- . = ..()
- if(.)
- pkc.icon_state = retool_icon
- pkc.current_inhand_icon_state = retool_inhand_icon
- pkc.projectile_icon = retool_projectile_icon
- if(iscarbon(pkc.loc))
- var/mob/living/carbon/holder = pkc.loc
- holder.update_worn_back()
- holder.update_suit_storage()
- holder.update_held_items()
- pkc.update_appearance()
-
-/obj/item/crusher_trophy/retool_kit/remove_from(obj/item/kinetic_crusher/pkc)
- pkc.icon_state = initial(pkc.icon_state)
- pkc.current_inhand_icon_state = initial(pkc.current_inhand_icon_state)
- pkc.projectile_icon = initial(pkc.projectile_icon)
- if(iscarbon(pkc.loc))
- var/mob/living/carbon/holder = pkc.loc
- holder.update_worn_back()
- holder.update_suit_storage()
- holder.update_held_items()
- pkc.update_appearance()
- ..()
-
-/obj/item/crusher_trophy/retool_kit/harpoon
- name = "crusher harpoon retool kit"
- desc = "A toolkit for changing the crusher's appearance without affecting the device's function. This one will make it look like a harpoon."
- icon = 'icons/obj/mining.dmi'
- icon_state = "retool_kit"
- denied_type = /obj/item/crusher_trophy/retool_kit
- retool_icon = "crusher_harpoon"
- retool_inhand_icon = "crusher_harpoon"
- retool_projectile_icon = "pulse_harpoon"
-
-/obj/item/crusher_trophy/retool_kit/harpoon/effect_desc()
- return "the crusher to have the appearance of a harpoon"
-
-/obj/item/crusher_trophy/retool_kit/dagger
- name = "crusher dagger retool kit"
- desc = "A toolkit for changing the crusher's appearance without affecting the device's function. This one will make it look like a dual dagger and mini-blaster on a chain."
- icon = 'icons/obj/mining.dmi'
- icon_state = "retool_kit"
- denied_type = /obj/item/crusher_trophy/retool_kit
- retool_icon = "crusher_dagger"
- retool_inhand_icon = "crusher_dagger"
-
-/obj/item/crusher_trophy/retool_kit/dagger/effect_desc()
- return "the crusher to have the appearance of a dual dagger and blaster"
-
-/obj/item/crusher_trophy/retool_kit/ashenskull
- name = "ashen skull"
- desc = "It burns with the flame of the necropolis, whispering in your ear. It demands to be bound to a suitable weapon."
- icon = 'icons/obj/mining.dmi'
- icon_state = "retool_kit_skull"
- denied_type = /obj/item/crusher_trophy/retool_kit
- retool_icon = "crusher_skull"
- retool_inhand_icon = "crusher_skull"
- retool_projectile_icon = "pulse_skull"
-
-/obj/item/crusher_trophy/retool_kit/ashenskull/effect_desc()
- return "the crusher to appear corrupted by infernal powers"
diff --git a/code/modules/mining/equipment/kinetic_crusher/kinetic_crusher.dm b/code/modules/mining/equipment/kinetic_crusher/kinetic_crusher.dm
new file mode 100644
index 000000000000..656e72fc5f2f
--- /dev/null
+++ b/code/modules/mining/equipment/kinetic_crusher/kinetic_crusher.dm
@@ -0,0 +1,335 @@
+/**
+ * Kinetic Crusher
+ *
+ * Lavaland's "Hard Mode" option for players, requiring melee attacks (backstabs even better),
+ * but allowing you to upgrade it with trophies gained from fighting lavaland monsters, making it
+ * a good tradeoff and a decent playstyle.
+ */
+/obj/item/kinetic_crusher
+ name = "proto-kinetic crusher"
+ desc = "An early design of the proto-kinetic accelerator, it is little more than a combination of various mining tools cobbled together, \
+ forming a high-tech club. While it is an effective mining tool, it did little to aid any but the most skilled and/or \
+ suicidal miners against local fauna."
+ icon = 'icons/obj/mining.dmi'
+ icon_state = "crusher"
+ inhand_icon_state = "crusher0"
+ icon_angle = -45
+ lefthand_file = 'icons/mob/inhands/weapons/hammers_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/weapons/hammers_righthand.dmi'
+ resistance_flags = FIRE_PROOF
+ force = 0 //You can't hit stuff unless wielded
+ w_class = WEIGHT_CLASS_BULKY
+ slot_flags = ITEM_SLOT_BACK
+ throwforce = 5
+ throw_speed = 4
+ armour_penetration = 10
+ custom_materials = list(/datum/material/iron=HALF_SHEET_MATERIAL_AMOUNT*1.15, /datum/material/glass=HALF_SHEET_MATERIAL_AMOUNT*2.075)
+ hitsound = 'sound/items/weapons/bladeslice.ogg'
+ attack_verb_continuous = list("smashes", "crushes", "cleaves", "chops", "pulps")
+ attack_verb_simple = list("smash", "crush", "cleave", "chop", "pulp")
+ sharpness = SHARP_EDGED
+ actions_types = list(/datum/action/item_action/toggle_light)
+ obj_flags = UNIQUE_RENAME
+ light_system = OVERLAY_LIGHT
+ light_range = 5
+ light_power = 1.2
+ light_color = "#ffff66"
+ light_on = FALSE
+ /// List of all crusher trophies attached to this.
+ var/list/obj/item/crusher_trophy/trophies = list()
+ /// If our crusher is ready to fire a projectile (FALSE means it's on cooldown)
+ var/charged = TRUE
+ /// How long before our crusher will recharge by default
+ var/charge_time = 1.5 SECONDS
+ /// Timer before our crusher recharges
+ var/charge_timer
+ /// Damage that the mark does when hit by the crusher
+ var/detonation_damage = 50
+ /// Damage that the mark additionally does when hit by the crusher via backstab
+ var/backstab_bonus = 30
+ /// Used by retool kits when changing the crusher's appearance
+ var/current_inhand_icon_state = "crusher"
+ /// Used by retool kits when changing the crusher's projectile sprite
+ var/projectile_icon = "pulse1"
+
+/obj/item/kinetic_crusher/Initialize(mapload)
+ . = ..()
+ AddComponent(/datum/component/butchering, \
+ speed = 6 SECONDS, \
+ effectiveness = 110, \
+ )
+ //technically it's huge and bulky, but this provides an incentive to use it
+ AddComponent(/datum/component/two_handed, force_unwielded=0, force_wielded=20)
+ // NOVA EDIT ADDITION START
+ if (flags_1 & HAS_CONTEXTUAL_SCREENTIPS_1)
+ return
+ // NOVA EDIT ADDITION END
+ register_context()
+
+/obj/item/kinetic_crusher/add_context(atom/source, list/context, obj/item/held_item, mob/user)
+ . = ..()
+ if(!held_item)
+ context[SCREENTIP_CONTEXT_RMB] = "Detach trophy"
+ return CONTEXTUAL_SCREENTIP_SET
+
+ if(istype(held_item) && held_item.tool_behaviour == TOOL_CROWBAR)
+ context[SCREENTIP_CONTEXT_LMB] = "Detach all trophies"
+ return CONTEXTUAL_SCREENTIP_SET
+
+/obj/item/kinetic_crusher/Destroy()
+ QDEL_LIST(trophies)
+ return ..()
+
+/obj/item/kinetic_crusher/Exited(atom/movable/gone, direction)
+ . = ..()
+ trophies -= gone
+
+/obj/item/kinetic_crusher/examine(mob/living/user)
+ . = ..()
+ . += span_notice("Mark a large creature with a destabilizing force with right-click, then hit them in melee to do [force + detonation_damage] damage.")
+ . += span_notice("Does [force + detonation_damage + backstab_bonus] damage if the target is backstabbed, instead of [force + detonation_damage].")
+ for(var/obj/item/crusher_trophy/crusher_trophy as anything in trophies)
+ . += span_notice("It has \a [crusher_trophy] attached, which causes [crusher_trophy.effect_desc()].")
+
+/obj/item/kinetic_crusher/attackby(obj/item/attacking_item, mob/user, params)
+ if(istype(attacking_item, /obj/item/crusher_trophy))
+ var/obj/item/crusher_trophy/crusher_trophy = attacking_item
+ crusher_trophy.add_to(src, user)
+ return
+ return ..()
+
+/obj/item/kinetic_crusher/crowbar_act(mob/living/user, obj/item/tool)
+ . = ..()
+ if(!LAZYLEN(trophies))
+ user.balloon_alert(user, "no trophies!")
+ return ITEM_INTERACT_BLOCKING
+ user.balloon_alert(user, "trophies removed")
+ tool.play_tool_sound(src)
+ for(var/obj/item/crusher_trophy/crusher_trophy as anything in trophies)
+ crusher_trophy.remove_from(src, user)
+ return ITEM_INTERACT_SUCCESS
+
+// adapted from kinetic accelerator attack_hand_secodary
+/obj/item/kinetic_crusher/attack_hand_secondary(mob/user, list/modifiers)
+ . = ..()
+ if(. == SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN)
+ return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
+
+ if(!LAZYLEN(trophies))
+ return SECONDARY_ATTACK_CONTINUE_CHAIN
+
+ var/list/display_names = list()
+ var/list/items = list()
+ for(var/trophies_length in 1 to length(trophies))
+ var/obj/item/crusher_trophy/trophy = trophies[trophies_length]
+ display_names[trophy.name] = REF(trophy)
+ var/image/item_image = image(icon = trophy.icon, icon_state = trophy.icon_state)
+ if(length(trophy.overlays))
+ item_image.copy_overlays(trophy)
+ items["[trophy.name]"] = item_image
+
+ var/pick = show_radial_menu(user, src, items, custom_check = CALLBACK(src, PROC_REF(check_menu), user), radius = 36, require_near = TRUE, tooltips = TRUE)
+ if(!pick)
+ return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
+
+ var/trophy_reference = display_names[pick]
+ var/obj/item/crusher_trophy/trophy_to_remove = locate(trophy_reference) in trophies
+ if(!istype(trophy_to_remove))
+ return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
+ trophy_to_remove.remove_from(src, user)
+ if(!user.put_in_hands(trophy_to_remove))
+ trophy_to_remove.forceMove(drop_location())
+
+ return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
+
+/obj/item/kinetic_crusher/proc/check_menu(mob/living/carbon/human/user)
+ if(!istype(user))
+ return FALSE
+ if(user.incapacitated)
+ return FALSE
+ return TRUE
+
+/obj/item/kinetic_crusher/pre_attack(atom/A, mob/living/user, params)
+ . = ..()
+ if(.)
+ return TRUE
+ if(!HAS_TRAIT(src, TRAIT_WIELDED) && !acts_as_if_wielded) // NOVA EDIT CHANGE - Original: if(!HAS_TRAIT(src, TRAIT_WIELDED))
+ user.balloon_alert(user, "must be wielded!")
+ return TRUE
+ return .
+
+/obj/item/kinetic_crusher/attack(mob/living/target, mob/living/carbon/user)
+ target.apply_status_effect(/datum/status_effect/crusher_damage)
+ return ..()
+
+/obj/item/kinetic_crusher/afterattack(mob/living/target, mob/living/user, clickparams)
+ if(!isliving(target))
+ return
+ // Melee effect
+ for(var/obj/item/crusher_trophy/crusher_trophy as anything in trophies)
+ crusher_trophy.on_melee_hit(target, user)
+ if(QDELETED(target))
+ return
+ var/datum/status_effect/crusher_mark/mark = target.has_status_effect(/datum/status_effect/crusher_mark)
+ if(!mark)
+ return
+ var/boosted_mark = mark.boosted
+ if(world.time < mark.mark_applied + mark.ready_delay) // Simple way to prevent right+left click at the same time to detonate the mark for free
+ return
+ if(!target.remove_status_effect(mark))
+ return
+ // Detonation effect
+ var/datum/status_effect/crusher_damage/crusher_damage_effect = target.has_status_effect(/datum/status_effect/crusher_damage) || target.apply_status_effect(/datum/status_effect/crusher_damage)
+ var/target_health = target.health
+ for(var/obj/item/crusher_trophy/crusher_trophy as anything in trophies)
+ crusher_trophy.on_mark_detonation(target, user)
+ if(QDELETED(target))
+ return
+ if(!QDELETED(crusher_damage_effect))
+ crusher_damage_effect.total_damage += target_health - target.health //we did some damage, but let's not assume how much we did
+ new /obj/effect/temp_visual/kinetic_blast(get_turf(target))
+ var/backstabbed = FALSE
+ var/combined_damage = detonation_damage
+ var/def_check = target.getarmor(type = BOMB)
+ // Backstab bonus
+ if(check_behind(user, target) || boosted_mark)
+ backstabbed = TRUE
+ combined_damage += backstab_bonus
+ playsound(user, 'sound/items/weapons/kinetic_accel.ogg', 100, TRUE) //Seriously who spelled it wrong
+ if(!QDELETED(crusher_damage_effect))
+ crusher_damage_effect.total_damage += combined_damage
+ SEND_SIGNAL(user, COMSIG_LIVING_CRUSHER_DETONATE, target, src, backstabbed)
+ target.apply_damage(combined_damage, BRUTE, blocked = def_check)
+
+/obj/item/kinetic_crusher/interact_with_atom_secondary(atom/interacting_with, mob/living/user, list/modifiers)
+ if(!HAS_TRAIT(src, TRAIT_WIELDED) && !acts_as_if_wielded) // NOVA EDIT CHANGE - Original: if(!HAS_TRAIT(src, TRAIT_WIELDED))
+ balloon_alert(user, "wield it first!")
+ return ITEM_INTERACT_BLOCKING
+ if(interacting_with == user)
+ balloon_alert(user, "can't aim at yourself!")
+ return ITEM_INTERACT_BLOCKING
+ fire_kinetic_blast(interacting_with, user, modifiers)
+ return ITEM_INTERACT_SUCCESS
+
+/obj/item/kinetic_crusher/ranged_interact_with_atom_secondary(atom/interacting_with, mob/living/user, list/modifiers)
+ return interact_with_atom_secondary(interacting_with, user, modifiers)
+
+/obj/item/kinetic_crusher/proc/fire_kinetic_blast(atom/target, mob/living/user, list/modifiers)
+ if(!charged)
+ return
+ var/turf/proj_turf = user.loc
+ if(!isturf(proj_turf))
+ return
+ var/obj/projectile/destabilizer/destabilizer = new(proj_turf)
+ destabilizer.icon_state = "[projectile_icon]"
+ for(var/obj/item/crusher_trophy/attached_trophy as anything in trophies)
+ attached_trophy.on_projectile_fire(destabilizer, user)
+ destabilizer.aim_projectile(target, user, modifiers)
+ destabilizer.firer = user
+ destabilizer.fired_from = src
+ playsound(user, 'sound/items/weapons/plasma_cutter.ogg', 100, TRUE)
+ destabilizer.fire()
+ charged = FALSE
+ update_appearance()
+ attempt_recharge_projectile()
+
+/// Handles the timer for reloading the projectile
+/obj/item/kinetic_crusher/proc/attempt_recharge_projectile(set_recharge_time)
+ if(!set_recharge_time)
+ set_recharge_time = charge_time
+ deltimer(charge_timer)
+ charge_timer = addtimer(CALLBACK(src, PROC_REF(recharge_projectile)), set_recharge_time, TIMER_STOPPABLE)
+
+/// Recharges the projectile
+/obj/item/kinetic_crusher/proc/recharge_projectile()
+ if(!charged)
+ charged = TRUE
+ update_appearance()
+ playsound(src.loc, 'sound/items/weapons/kinetic_reload.ogg', 60, TRUE)
+
+/obj/item/kinetic_crusher/ui_action_click(mob/user, actiontype)
+ set_light_on(!light_on)
+ playsound(user, 'sound/items/weapons/empty.ogg', 100, TRUE)
+ update_appearance()
+
+/obj/item/kinetic_crusher/on_saboteur(datum/source, disrupt_duration)
+ . = ..()
+ set_light_on(FALSE)
+ playsound(src, 'sound/items/weapons/empty.ogg', 100, TRUE)
+ return TRUE
+
+/obj/item/kinetic_crusher/update_icon_state()
+ inhand_icon_state = "[current_inhand_icon_state][HAS_TRAIT(src, TRAIT_WIELDED)]" // this is not icon_state and not supported by 2hcomponent
+ return ..()
+
+/obj/item/kinetic_crusher/update_overlays()
+ . = ..()
+ if(!charged)
+ . += "[icon_state]_uncharged"
+ if(light_on)
+ . += "[icon_state]_lit"
+
+/obj/item/kinetic_crusher/compact //for admins
+ name = "compact kinetic crusher"
+ w_class = WEIGHT_CLASS_NORMAL
+
+//destablizing force
+/obj/projectile/destabilizer
+ name = "destabilizing force"
+ damage = 0 //We're just here to mark people. This is still a melee weapon.
+ damage_type = BRUTE
+ armor_flag = BOMB
+ range = 6
+ log_override = TRUE
+ /// Has this projectile been boosted
+ var/boosted = FALSE
+
+/obj/projectile/destabilizer/Initialize(mapload)
+ . = ..()
+ AddComponent(/datum/component/parriable_projectile, parry_callback = CALLBACK(src, PROC_REF(on_parry)))
+
+/obj/projectile/destabilizer/Destroy()
+ fired_from = null
+ return ..()
+
+/obj/projectile/destabilizer/proc/on_parry(mob/user)
+ SIGNAL_HANDLER
+ boosted = TRUE
+ // Get a bit of a damage/range boost after being parried
+ damage = 10
+ range = 9
+
+/obj/projectile/destabilizer/on_hit(atom/target, blocked = 0, pierce_hit)
+ var/obj/item/kinetic_crusher/used_crusher
+ if(istype(fired_from, /obj/item/kinetic_crusher))
+ used_crusher = fired_from
+
+ if(isliving(target))
+ for(var/obj/item/crusher_trophy/crusher_trophy as anything in used_crusher?.trophies)
+ crusher_trophy.on_projectile_hit_mob(target, firer)
+ if(QDELETED(target))
+ return ..()
+ var/mob/living/living_target = target
+ living_target.apply_status_effect(/datum/status_effect/crusher_mark, boosted)
+ return ..()
+
+ var/target_turf = get_turf(target)
+ if(ismineralturf(target_turf))
+ var/turf/closed/mineral/hit_mineral = target_turf
+ for(var/obj/item/crusher_trophy/crusher_trophy as anything in used_crusher?.trophies)
+ crusher_trophy.on_projectile_hit_mineral(hit_mineral, firer)
+ if(QDELETED(hit_mineral))
+ return ..()
+ new /obj/effect/temp_visual/kinetic_blast(hit_mineral)
+ hit_mineral.gets_drilled(firer, TRUE)
+ if(!iscarbon(firer))
+ return ..()
+ var/mob/living/carbon/carbon_firer = firer
+ var/skill_modifier = 1
+ // If there is a mind, check for skill modifier to allow them to reload faster.
+ if(carbon_firer.mind && used_crusher)
+ skill_modifier = carbon_firer.mind.get_skill_modifier(/datum/skill/mining, SKILL_SPEED_MODIFIER)
+ used_crusher.attempt_recharge_projectile(used_crusher.charge_time * skill_modifier) //If you hit a mineral, you might get a quicker reload. epic gamer style.
+
+ return ..()
+
diff --git a/code/modules/mining/equipment/kinetic_crusher/kinetic_crusher_trophies.dm b/code/modules/mining/equipment/kinetic_crusher/kinetic_crusher_trophies.dm
new file mode 100644
index 000000000000..6a00423206e0
--- /dev/null
+++ b/code/modules/mining/equipment/kinetic_crusher/kinetic_crusher_trophies.dm
@@ -0,0 +1,63 @@
+/*!
+ * Contains the baseline of kinetic crusher trophies.
+ */
+
+/obj/item/crusher_trophy
+ name = "tail spike"
+ desc = "A strange spike with no usage."
+ icon = 'icons/obj/mining_zones/artefacts.dmi'
+ icon_state = "tail_spike"
+ /// if it has a bonus effect, this is how much that effect is
+ var/bonus_value = 10
+ /// what type of trophies will block this trophy from being added, must be overriden
+ var/denied_type = /obj/item/crusher_trophy
+
+/obj/item/crusher_trophy/examine(mob/living/user)
+ . = ..()
+ . += span_notice("Causes [effect_desc()] when attached to a kinetic crusher.")
+
+/// Returns a string to get added to the examine
+/obj/item/crusher_trophy/proc/effect_desc()
+ SHOULD_CALL_PARENT(FALSE)
+ return "errors"
+
+/obj/item/crusher_trophy/attackby(obj/item/attacking_item, mob/living/user)
+ if(!istype(attacking_item, /obj/item/kinetic_crusher))
+ return ..()
+ add_to(attacking_item, user)
+
+/// Tries to add the trophy to our crusher
+/obj/item/crusher_trophy/proc/add_to(obj/item/kinetic_crusher/crusher, mob/living/user)
+ for(var/obj/item/crusher_trophy/trophy as anything in crusher.trophies)
+ if(istype(trophy, denied_type) || istype(src, trophy.denied_type))
+ to_chat(user, span_warning("You can't seem to attach [src] to [crusher]. Maybe remove a few trophies?"))
+ return FALSE
+ if(!user.transferItemToLoc(src, crusher))
+ return
+ crusher.trophies += src
+ to_chat(user, span_notice("You attach [src] to [crusher]."))
+ return TRUE
+
+/// Removes the trophy from our crusher
+/obj/item/crusher_trophy/proc/remove_from(obj/item/kinetic_crusher/crusher, mob/living/user)
+ forceMove(get_turf(crusher))
+ return TRUE
+
+/// Does an effect when you hit a mob with a crusher
+/obj/item/crusher_trophy/proc/on_melee_hit(mob/living/target, mob/living/user) //the target and the user
+ return
+
+/obj/item/crusher_trophy/proc/on_projectile_fire(obj/projectile/destabilizer/marker, mob/living/user) //the projectile fired and the user
+ return
+
+/// Does an effect when you hit a mob with the projectile
+/obj/item/crusher_trophy/proc/on_projectile_hit_mob(mob/living/target, mob/living/user) //the target and the user
+ return
+
+/// Does an effect when you hit a mineral turf with the projectile
+/obj/item/crusher_trophy/proc/on_projectile_hit_mineral(turf/closed/mineral, mob/living/user) //the target and the user
+ return
+
+/// Does an effect when you hit a mob that is marked via the projectile
+/obj/item/crusher_trophy/proc/on_mark_detonation(mob/living/target, mob/living/user) //the target and the user
+ return
diff --git a/code/modules/mining/equipment/kinetic_crusher/trophies_fauna.dm b/code/modules/mining/equipment/kinetic_crusher/trophies_fauna.dm
new file mode 100644
index 000000000000..2327980a9833
--- /dev/null
+++ b/code/modules/mining/equipment/kinetic_crusher/trophies_fauna.dm
@@ -0,0 +1,241 @@
+/*!
+ * Contains crusher trophies you can obtain from regular fauna
+ */
+
+//watcher
+/obj/item/crusher_trophy/watcher_wing
+ name = "watcher wing"
+ desc = "A wing ripped from a watcher. Suitable as a trophy for a kinetic crusher."
+ icon_state = "watcher_wing"
+ denied_type = /obj/item/crusher_trophy/watcher_wing
+ bonus_value = 5
+
+/obj/item/crusher_trophy/watcher_wing/effect_desc()
+ return "mark detonation to prevent certain creatures from using certain attacks for [bonus_value*0.1] second\s"
+
+/obj/item/crusher_trophy/watcher_wing/on_mark_detonation(mob/living/target, mob/living/user)
+ if(ishostile(target))
+ var/mob/living/simple_animal/hostile/H = target
+ if(H.ranged) //briefly delay ranged attacks
+ if(H.ranged_cooldown >= world.time)
+ H.ranged_cooldown += bonus_value
+ else
+ H.ranged_cooldown = bonus_value + world.time
+
+//magmawing watcher
+/obj/item/crusher_trophy/blaster_tubes/magma_wing
+ name = "magmawing watcher wing"
+ desc = "A still-searing wing from a magmawing watcher. Suitable as a trophy for a kinetic crusher."
+ icon_state = "magma_wing"
+ gender = NEUTER
+ bonus_value = 5
+
+/obj/item/crusher_trophy/blaster_tubes/magma_wing/effect_desc()
+ return "mark detonation to make the next destabilizer shot deal [bonus_value] damage"
+
+/obj/item/crusher_trophy/blaster_tubes/magma_wing/on_projectile_fire(obj/projectile/destabilizer/marker, mob/living/user)
+ if(deadly_shot)
+ marker.name = "heated [marker.name]"
+ marker.icon_state = "lava"
+ marker.damage = bonus_value
+ deadly_shot = FALSE
+
+//icewing watcher
+/obj/item/crusher_trophy/watcher_wing/ice_wing
+ name = "icewing watcher wing"
+ desc = "A carefully preserved frozen wing from an icewing watcher. Suitable as a trophy for a kinetic crusher."
+ icon_state = "ice_wing"
+ bonus_value = 8
+
+//legion
+/obj/item/crusher_trophy/legion_skull
+ name = "legion skull"
+ desc = "A dead and lifeless legion skull. Suitable as a trophy for a kinetic crusher."
+ icon_state = "legion_skull"
+ denied_type = /obj/item/crusher_trophy/legion_skull
+ bonus_value = 3
+
+/obj/item/crusher_trophy/legion_skull/effect_desc()
+ return "a kinetic crusher to recharge [bonus_value*0.1] second\s faster"
+
+/obj/item/crusher_trophy/legion_skull/add_to(obj/item/kinetic_crusher/pkc, mob/living/user)
+ . = ..()
+ if(.)
+ pkc.charge_time -= bonus_value
+
+/obj/item/crusher_trophy/legion_skull/remove_from(obj/item/kinetic_crusher/pkc, mob/living/user)
+ . = ..()
+ if(.)
+ pkc.charge_time += bonus_value
+
+// Goliath - Increases damage as your health decreases.
+/obj/item/crusher_trophy/goliath_tentacle
+ name = "goliath tentacle"
+ desc = "A sliced-off goliath tentacle. Suitable as a trophy for a kinetic crusher."
+ icon_state = "goliath_tentacle"
+ denied_type = /obj/item/crusher_trophy/goliath_tentacle
+ bonus_value = 2
+ /// Your missing health is multiplied by this value to find the bonus damage
+ var/missing_health_ratio = 0.1
+ /// Amount of health you must lose to gain damage, according to the examine text. Cached so we don't recalculate it every examine.
+ var/missing_health_desc
+
+/obj/item/crusher_trophy/goliath_tentacle/Initialize(mapload)
+ . = ..()
+ missing_health_desc = 1 / missing_health_ratio / bonus_value
+
+/obj/item/crusher_trophy/goliath_tentacle/effect_desc()
+ return "mark detonation to do [bonus_value] more damage for every [missing_health_desc] health you are missing"
+
+/obj/item/crusher_trophy/goliath_tentacle/on_mark_detonation(mob/living/target, mob/living/user)
+ var/missing_health = user.maxHealth - user.health
+ missing_health *= missing_health_ratio //bonus is active at all times, even if you're above 90 health
+ missing_health *= bonus_value //multiply the remaining amount by bonus_value
+ if(missing_health > 0)
+ target.adjustBruteLoss(missing_health) //and do that much damage
+
+// Lobstrosity - Rebukes targets, increasing their click cooldown.
+/obj/item/crusher_trophy/lobster_claw
+ name = "lobster claw"
+ icon_state = "lobster_claw"
+ desc = "A lobster claw."
+ denied_type = /obj/item/crusher_trophy/lobster_claw
+ bonus_value = 1
+
+/obj/item/crusher_trophy/lobster_claw/effect_desc()
+ return "mark detonation to briefly rebuke the target for [bonus_value] seconds"
+
+/obj/item/crusher_trophy/lobster_claw/on_mark_detonation(mob/living/target, mob/living/user)
+ target.apply_status_effect(/datum/status_effect/rebuked, bonus_value SECONDS)
+
+// Brimdemon - makes a funny sound, the most essential trophy out of all
+/obj/item/crusher_trophy/brimdemon_fang
+ name = "brimdemon's fang"
+ icon_state = "brimdemon_fang"
+ desc = "A fang from a brimdemon's corpse."
+ denied_type = /obj/item/crusher_trophy/brimdemon_fang
+ /// Cartoon punching vfx
+ var/static/list/comic_phrases = list("BOOM", "BANG", "KABLOW", "KAPOW", "OUCH", "BAM", "KAPOW", "WHAM", "POW", "KABOOM")
+
+/obj/item/crusher_trophy/brimdemon_fang/effect_desc()
+ return "mark detonation to create visual and audiosensory effects at the target"
+
+/obj/item/crusher_trophy/brimdemon_fang/on_mark_detonation(mob/living/target, mob/living/user)
+ target.balloon_alert_to_viewers("[pick(comic_phrases)]!")
+ playsound(target, 'sound/mobs/non-humanoids/brimdemon/brimdemon_crush.ogg', 100)
+
+// Bileworm
+/obj/item/crusher_trophy/bileworm_spewlet
+ name = "bileworm spewlet"
+ icon = 'icons/mob/simple/lavaland/bileworm.dmi'
+ icon_state = "bileworm_spewlet"
+ desc = "A baby bileworm. Suitable as a trophy for a kinetic crusher."
+ denied_type = /obj/item/crusher_trophy/bileworm_spewlet
+ ///item ability that handles the effect
+ var/datum/action/cooldown/mob_cooldown/projectile_attack/dir_shots/spewlet/ability
+
+/obj/item/crusher_trophy/bileworm_spewlet/Initialize(mapload)
+ . = ..()
+ ability = new()
+
+/obj/item/crusher_trophy/bileworm_spewlet/Destroy(force)
+ . = ..()
+ QDEL_NULL(ability)
+
+/obj/item/crusher_trophy/bileworm_spewlet/add_to(obj/item/kinetic_crusher/crusher, mob/living/user)
+ . = ..()
+ if(.)
+ crusher.add_item_action(ability)
+
+/obj/item/crusher_trophy/bileworm_spewlet/remove_from(obj/item/kinetic_crusher/crusher, mob/living/user)
+ . = ..()
+ crusher.remove_item_action(ability)
+
+/obj/item/crusher_trophy/bileworm_spewlet/effect_desc()
+ return "mark detonation launches projectiles in cardinal directions on a 10 second cooldown. Also gives you an AOE when mining minerals"
+
+/obj/item/crusher_trophy/bileworm_spewlet/on_mark_detonation(mob/living/target, mob/living/user)
+ //ability itself handles cooldowns.
+ ability.InterceptClickOn(user, null, target)
+
+/obj/item/crusher_trophy/bileworm_spewlet/on_projectile_hit_mineral(turf/closed/mineral, mob/living/user)
+ for(var/turf/closed/mineral/mineral_turf in RANGE_TURFS(1, mineral) - mineral)
+ mineral_turf.gets_drilled(user, TRUE)
+
+//yes this is a /mob_cooldown subtype being added to an item. I can't recommend you do what I'm doing
+/datum/action/cooldown/mob_cooldown/projectile_attack/dir_shots/spewlet
+ check_flags = NONE
+ owner_has_control = FALSE
+ cooldown_time = 10 SECONDS
+ projectile_type = /obj/projectile/bileworm_acid
+ projectile_sound = 'sound/mobs/non-humanoids/bileworm/bileworm_spit.ogg'
+
+/datum/action/cooldown/mob_cooldown/projectile_attack/dir_shots/spewlet/New(Target)
+ firing_directions = GLOB.cardinals.Copy()
+ return ..()
+
+// demonic watcher
+/obj/item/crusher_trophy/ice_demon_cube
+ name = "demonic cube"
+ desc = "A stone cold cube dropped from an ice demon."
+ icon_state = "ice_demon_cube"
+ denied_type = /obj/item/crusher_trophy/ice_demon_cube
+ ///how many will we summon?
+ var/summon_amount = 2
+ ///cooldown to summon demons upon the target
+ COOLDOWN_DECLARE(summon_cooldown)
+
+/obj/item/crusher_trophy/ice_demon_cube/effect_desc()
+ return "mark detonation to unleash demonic ice clones upon the target"
+
+/obj/item/crusher_trophy/ice_demon_cube/on_mark_detonation(mob/living/target, mob/living/user)
+ if(isnull(target) || !COOLDOWN_FINISHED(src, summon_cooldown))
+ return
+ for(var/i in 1 to summon_amount)
+ var/turf/drop_off = find_dropoff_turf(target, user)
+ var/mob/living/basic/mining/demon_afterimage/crusher/friend = new(drop_off)
+ friend.faction = list(FACTION_NEUTRAL)
+ friend.befriend(user)
+ friend.ai_controller?.set_blackboard_key(BB_BASIC_MOB_CURRENT_TARGET, target)
+ COOLDOWN_START(src, summon_cooldown, 30 SECONDS)
+
+///try to make them spawn all around the target to surround him
+/obj/item/crusher_trophy/ice_demon_cube/proc/find_dropoff_turf(mob/living/target, mob/living/user)
+ var/list/turfs_list = get_adjacent_open_turfs(target)
+ for(var/turf/possible_turf in turfs_list)
+ if(possible_turf.is_blocked_turf())
+ continue
+ return possible_turf
+ return get_turf(user)
+
+// Wolf
+
+/obj/item/crusher_trophy/wolf_ear
+ name = "wolf ear"
+ desc = "It's a wolf ear."
+ icon_state = "wolf_ear"
+ denied_type = /obj/item/crusher_trophy/wolf_ear
+
+/obj/item/crusher_trophy/wolf_ear/effect_desc()
+ return "mark detonation to gain a slight speed boost temporarily"
+
+/obj/item/crusher_trophy/wolf_ear/on_mark_detonation(mob/living/target, mob/living/user)
+ user.apply_status_effect(/datum/status_effect/speed_boost, 1 SECONDS)
+
+// Polar bear
+/obj/item/crusher_trophy/bear_paw
+ name = "polar bear paw"
+ desc = "It's a polar bear paw."
+ icon_state = "bear_paw"
+ denied_type = /obj/item/crusher_trophy/bear_paw
+
+/obj/item/crusher_trophy/bear_paw/effect_desc()
+ return "mark detonation to attack twice if you are below half your life"
+
+/obj/item/crusher_trophy/bear_paw/on_mark_detonation(mob/living/target, mob/living/user)
+ if(user.health / user.maxHealth > 0.5)
+ return
+ var/obj/item/I = user.get_active_held_item()
+ if(!I)
+ return
+ I.melee_attack_chain(user, target, null)
diff --git a/code/modules/mining/equipment/kinetic_crusher/trophies_megafauna.dm b/code/modules/mining/equipment/kinetic_crusher/trophies_megafauna.dm
new file mode 100644
index 000000000000..ba302999010c
--- /dev/null
+++ b/code/modules/mining/equipment/kinetic_crusher/trophies_megafauna.dm
@@ -0,0 +1,217 @@
+/*!
+ * Contains crusher trophies you can obtain from megafauna
+ */
+
+//blood-drunk hunter
+/obj/item/crusher_trophy/miner_eye
+ name = "eye of a blood-drunk hunter"
+ desc = "Its pupil is collapsed and turned to mush. Suitable as a trophy for a kinetic crusher."
+ icon_state = "hunter_eye"
+ denied_type = /obj/item/crusher_trophy/miner_eye
+
+/obj/item/crusher_trophy/miner_eye/effect_desc()
+ return "mark detonation to grant stun immunity and 90% damage reduction for 1 second"
+
+/obj/item/crusher_trophy/miner_eye/on_mark_detonation(mob/living/target, mob/living/user)
+ user.apply_status_effect(/datum/status_effect/blooddrunk)
+
+//ash drake
+/obj/item/crusher_trophy/tail_spike
+ desc = "A spike taken from an ash drake's tail. Suitable as a trophy for a kinetic crusher."
+ denied_type = /obj/item/crusher_trophy/tail_spike
+ bonus_value = 5
+
+/obj/item/crusher_trophy/tail_spike/effect_desc()
+ return "mark detonation to do [bonus_value] damage to nearby creatures and push them back"
+
+/obj/item/crusher_trophy/tail_spike/on_mark_detonation(mob/living/target, mob/living/user)
+ for(var/mob/living/living_target in oview(2, user))
+ if(user.faction_check_atom(living_target) || living_target.stat == DEAD)
+ continue
+ playsound(living_target, 'sound/effects/magic/fireball.ogg', 20, TRUE)
+ new /obj/effect/temp_visual/fire(living_target.loc)
+ addtimer(CALLBACK(src, PROC_REF(pushback), living_target, user), 1) //no free backstabs, we push AFTER module stuff is done
+ living_target.adjustFireLoss(bonus_value, forced = TRUE)
+
+/obj/item/crusher_trophy/tail_spike/proc/pushback(mob/living/target, mob/living/user)
+ if(!QDELETED(target) && !QDELETED(user) && (!target.anchored || ismegafauna(target))) //megafauna will always be pushed
+ step(target, get_dir(user, target))
+
+//bubblegum
+/obj/item/crusher_trophy/demon_claws
+ name = "demon claws"
+ desc = "A set of blood-drenched claws from a massive demon's hand. Suitable as a trophy for a kinetic crusher."
+ icon_state = "demon_claws"
+ gender = PLURAL
+ denied_type = /obj/item/crusher_trophy/demon_claws
+ bonus_value = 10
+ var/static/list/damage_heal_order = list(BRUTE, BURN, OXY)
+
+/obj/item/crusher_trophy/demon_claws/effect_desc()
+ return "melee hits to do [bonus_value * 0.2] more damage and heal you for [bonus_value * 0.1], with 5X effect on mark detonation"
+
+/obj/item/crusher_trophy/demon_claws/add_to(obj/item/kinetic_crusher/pkc, mob/living/user)
+ . = ..()
+ if(.)
+ pkc.force += bonus_value * 0.2
+ pkc.detonation_damage += bonus_value * 0.8
+ AddComponent(/datum/component/two_handed, force_wielded=(20 + bonus_value * 0.2))
+
+/obj/item/crusher_trophy/demon_claws/remove_from(obj/item/kinetic_crusher/pkc, mob/living/user)
+ . = ..()
+ if(.)
+ pkc.force -= bonus_value * 0.2
+ pkc.detonation_damage -= bonus_value * 0.8
+ AddComponent(/datum/component/two_handed, force_wielded=20)
+
+/obj/item/crusher_trophy/demon_claws/on_melee_hit(mob/living/target, mob/living/user)
+ user.heal_ordered_damage(bonus_value * 0.1, damage_heal_order)
+
+/obj/item/crusher_trophy/demon_claws/on_mark_detonation(mob/living/target, mob/living/user)
+ user.heal_ordered_damage(bonus_value * 0.4, damage_heal_order)
+
+//colossus
+/obj/item/crusher_trophy/blaster_tubes
+ name = "blaster tubes"
+ desc = "The blaster tubes from a colossus's arm. Suitable as a trophy for a kinetic crusher."
+ icon_state = "blaster_tubes"
+ gender = PLURAL
+ denied_type = /obj/item/crusher_trophy/blaster_tubes
+ bonus_value = 15
+ var/deadly_shot = FALSE
+
+/obj/item/crusher_trophy/blaster_tubes/effect_desc()
+ return "mark detonation to make the next destabilizer shot deal [bonus_value] damage but move slower"
+
+/obj/item/crusher_trophy/blaster_tubes/on_projectile_fire(obj/projectile/destabilizer/marker, mob/living/user)
+ if(deadly_shot)
+ marker.name = "deadly [marker.name]"
+ marker.icon_state = "chronobolt"
+ marker.damage = bonus_value
+ marker.speed = 2
+ deadly_shot = FALSE
+
+/obj/item/crusher_trophy/blaster_tubes/on_mark_detonation(mob/living/target, mob/living/user)
+ deadly_shot = TRUE
+ addtimer(CALLBACK(src, PROC_REF(reset_deadly_shot)), 300, TIMER_UNIQUE|TIMER_OVERRIDE)
+
+/obj/item/crusher_trophy/blaster_tubes/proc/reset_deadly_shot()
+ deadly_shot = FALSE
+
+//hierophant
+/obj/item/crusher_trophy/vortex_talisman
+ name = "vortex talisman"
+ desc = "A glowing trinket that was originally the Hierophant's beacon. Suitable as a trophy for a kinetic crusher."
+ icon_state = "vortex_talisman"
+ denied_type = /obj/item/crusher_trophy/vortex_talisman
+
+/obj/item/crusher_trophy/vortex_talisman/effect_desc()
+ return "mark detonation to create a homing hierophant chaser"
+
+/obj/item/crusher_trophy/vortex_talisman/on_mark_detonation(mob/living/target, mob/living/user)
+ if(isliving(target))
+ var/obj/effect/temp_visual/hierophant/chaser/chaser = new(get_turf(user), user, target, 3, TRUE)
+ chaser.monster_damage_boost = FALSE // Weaker cuz no cooldown
+ chaser.damage = 20
+ log_combat(user, target, "fired a chaser at", src)
+
+// Demonic frost miner
+/obj/item/crusher_trophy/ice_block_talisman
+ name = "ice block talisman"
+ desc = "A glowing trinket that a demonic miner had on him, it seems he couldn't utilize it for whatever reason."
+ icon_state = "ice_trap_talisman"
+ denied_type = /obj/item/crusher_trophy/ice_block_talisman
+
+/obj/item/crusher_trophy/ice_block_talisman/effect_desc()
+ return "mark detonation to freeze a creature in a block of ice for a period, preventing them from moving"
+
+/obj/item/crusher_trophy/ice_block_talisman/on_mark_detonation(mob/living/target, mob/living/user)
+ target.apply_status_effect(/datum/status_effect/ice_block_talisman)
+
+// Wendigo
+/obj/item/crusher_trophy/wendigo_horn
+ name = "wendigo horn"
+ desc = "A gnarled horn ripped from the skull of a wendigo. Suitable as a trophy for a kinetic crusher."
+ icon_state = "wendigo_horn"
+ denied_type = /obj/item/crusher_trophy/wendigo_horn
+
+/obj/item/crusher_trophy/wendigo_horn/effect_desc()
+ return "melee hits inflict twice as much damage"
+
+/obj/item/crusher_trophy/wendigo_horn/add_to(obj/item/kinetic_crusher/crusher, mob/living/user)
+ . = ..()
+ if(.)
+ crusher.AddComponent(/datum/component/two_handed, force_wielded=40)
+
+/obj/item/crusher_trophy/wendigo_horn/remove_from(obj/item/kinetic_crusher/crusher, mob/living/user)
+ . = ..()
+ if(.)
+ crusher.AddComponent(/datum/component/two_handed, force_wielded=20)
+
+// Goliath Broodmother
+/obj/item/crusher_trophy/broodmother_tongue
+ name = "broodmother tongue"
+ desc = "The tongue of a broodmother. If attached a certain way, makes for a suitable crusher trophy. It also feels very spongey, I wonder what would happen if you squeezed it?..."
+ icon = 'icons/obj/mining_zones/elite_trophies.dmi'
+ icon_state = "broodmother_tongue"
+ denied_type = /obj/item/crusher_trophy/broodmother_tongue
+ bonus_value = 10
+ /// Time at which the item becomes usable again
+ var/use_time
+
+/obj/item/crusher_trophy/broodmother_tongue/effect_desc()
+ return "mark detonation to have a [bonus_value]% chance to summon a patch of goliath tentacles at the target's location"
+
+/obj/item/crusher_trophy/broodmother_tongue/on_mark_detonation(mob/living/target, mob/living/user)
+ if(prob(bonus_value) && target.stat != DEAD)
+ new /obj/effect/goliath_tentacle/broodmother/patch(get_turf(target), user)
+
+/obj/item/crusher_trophy/broodmother_tongue/attack_self(mob/user)
+ if(!isliving(user))
+ return
+ var/mob/living/living_user = user
+ if(use_time > world.time)
+ to_chat(living_user, "The tongue looks dried out. You'll need to wait longer to use it again.")
+ return
+ else if(HAS_TRAIT(living_user, TRAIT_LAVA_IMMUNE))
+ to_chat(living_user, "You stare at the tongue. You don't think this is any use to you.")
+ return
+ ADD_TRAIT(living_user, TRAIT_LAVA_IMMUNE, type)
+ to_chat(living_user, "You squeeze the tongue, and some transluscent liquid shoots out all over you.")
+ addtimer(TRAIT_CALLBACK_REMOVE(user, TRAIT_LAVA_IMMUNE, type), 10 SECONDS)
+ use_time = world.time + 60 SECONDS
+
+// Legionnaire
+/obj/item/crusher_trophy/legionnaire_spine
+ name = "legionnaire spine"
+ desc = "The spine of a legionnaire. With some creativity, you could use it as a crusher trophy. Alternatively, shaking it might do something as well."
+ icon = 'icons/obj/mining_zones/elite_trophies.dmi'
+ icon_state = "legionnaire_spine"
+ denied_type = /obj/item/crusher_trophy/legionnaire_spine
+ bonus_value = 20
+ /// Time at which the item becomes usable again
+ var/next_use_time
+
+/obj/item/crusher_trophy/legionnaire_spine/effect_desc()
+ return "mark detonation to have a [bonus_value]% chance to summon a loyal legion skull"
+
+/obj/item/crusher_trophy/legionnaire_spine/on_mark_detonation(mob/living/target, mob/living/user)
+ if(!prob(bonus_value) || target.stat == DEAD)
+ return
+ var/mob/living/basic/legion_brood/minion = new (user.loc)
+ minion.assign_creator(user)
+ minion.ai_controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET] = target
+
+/obj/item/crusher_trophy/legionnaire_spine/attack_self(mob/user)
+ if(!isliving(user))
+ return
+ var/mob/living/LivingUser = user
+ if(next_use_time > world.time)
+ LivingUser.visible_message(span_warning("[LivingUser] shakes the [src], but nothing happens..."))
+ to_chat(LivingUser, "You need to wait longer to use this again.")
+ return
+ LivingUser.visible_message(span_boldwarning("[LivingUser] shakes the [src] and summons a legion skull!"))
+
+ var/mob/living/basic/legion_brood/minion = new (LivingUser.loc)
+ minion.assign_creator(LivingUser)
+ next_use_time = world.time + 4 SECONDS
diff --git a/code/modules/mining/equipment/kinetic_crusher/trophies_misc.dm b/code/modules/mining/equipment/kinetic_crusher/trophies_misc.dm
new file mode 100644
index 000000000000..4d50d0aa3f79
--- /dev/null
+++ b/code/modules/mining/equipment/kinetic_crusher/trophies_misc.dm
@@ -0,0 +1,84 @@
+/*!
+ * Contains crusher trophies that are not obtained from fauna
+ */
+
+//cosmetic items for changing the crusher's look
+
+/obj/item/crusher_trophy/retool_kit
+ name = "crusher sword retool kit"
+ desc = "A toolkit for changing the crusher's appearance without affecting the device's function. This one will make it look like a sword."
+ icon = 'icons/obj/mining.dmi'
+ icon_state = "retool_kit"
+ denied_type = /obj/item/crusher_trophy/retool_kit
+ ///Specifies the sprite/icon state which the crusher is changed to as an item. Should appear in the icons/obj/mining.dmi file with accompanying "lit" and "recharging" sprites
+ var/retool_icon = "crusher_sword"
+ ///Specifies the icon state for the crusher's appearance in hand. Should appear in both icons/mob/inhands/weapons/hammers_lefthand.dmi and icons/mob/inhands/weapons/hammers_righthand.dmi
+ var/retool_inhand_icon = "crusher_sword"
+ ///For if the retool kit changes the projectile's appearance. The sprite should be in icons/obj/weapons/guns/projectiles.dmi
+ var/retool_projectile_icon = "pulse1"
+
+/obj/item/crusher_trophy/retool_kit/effect_desc()
+ return "the crusher to have the appearance of a sword"
+
+/obj/item/crusher_trophy/retool_kit/add_to(obj/item/kinetic_crusher/pkc, mob/user)
+ . = ..()
+ if(.)
+ pkc.icon_state = retool_icon
+ pkc.current_inhand_icon_state = retool_inhand_icon
+ pkc.projectile_icon = retool_projectile_icon
+ if(iscarbon(pkc.loc))
+ var/mob/living/carbon/holder = pkc.loc
+ holder.update_worn_back()
+ holder.update_suit_storage()
+ holder.update_held_items()
+ pkc.update_appearance()
+
+/obj/item/crusher_trophy/retool_kit/remove_from(obj/item/kinetic_crusher/pkc)
+ pkc.icon_state = initial(pkc.icon_state)
+ pkc.current_inhand_icon_state = initial(pkc.current_inhand_icon_state)
+ pkc.projectile_icon = initial(pkc.projectile_icon)
+ if(iscarbon(pkc.loc))
+ var/mob/living/carbon/holder = pkc.loc
+ holder.update_worn_back()
+ holder.update_suit_storage()
+ holder.update_held_items()
+ pkc.update_appearance()
+ ..()
+
+/obj/item/crusher_trophy/retool_kit/harpoon
+ name = "crusher harpoon retool kit"
+ desc = "A toolkit for changing the crusher's appearance without affecting the device's function. This one will make it look like a harpoon."
+ icon = 'icons/obj/mining.dmi'
+ icon_state = "retool_kit"
+ denied_type = /obj/item/crusher_trophy/retool_kit
+ retool_icon = "crusher_harpoon"
+ retool_inhand_icon = "crusher_harpoon"
+ retool_projectile_icon = "pulse_harpoon"
+
+/obj/item/crusher_trophy/retool_kit/harpoon/effect_desc()
+ return "the crusher to have the appearance of a harpoon"
+
+/obj/item/crusher_trophy/retool_kit/dagger
+ name = "crusher dagger retool kit"
+ desc = "A toolkit for changing the crusher's appearance without affecting the device's function. This one will make it look like a dual dagger and mini-blaster on a chain."
+ icon = 'icons/obj/mining.dmi'
+ icon_state = "retool_kit"
+ denied_type = /obj/item/crusher_trophy/retool_kit
+ retool_icon = "crusher_dagger"
+ retool_inhand_icon = "crusher_dagger"
+
+/obj/item/crusher_trophy/retool_kit/dagger/effect_desc()
+ return "the crusher to have the appearance of a dual dagger and blaster"
+
+/obj/item/crusher_trophy/retool_kit/ashenskull
+ name = "ashen skull"
+ desc = "It burns with the flame of the necropolis, whispering in your ear. It demands to be bound to a suitable weapon."
+ icon = 'icons/obj/mining.dmi'
+ icon_state = "retool_kit_skull"
+ denied_type = /obj/item/crusher_trophy/retool_kit
+ retool_icon = "crusher_skull"
+ retool_inhand_icon = "crusher_skull"
+ retool_projectile_icon = "pulse_skull"
+
+/obj/item/crusher_trophy/retool_kit/ashenskull/effect_desc()
+ return "the crusher to appear corrupted by infernal powers"
diff --git a/code/modules/mining/equipment/mining_tools.dm b/code/modules/mining/equipment/mining_tools.dm
index 8e14f4d2ae9f..207c99c1d1ee 100644
--- a/code/modules/mining/equipment/mining_tools.dm
+++ b/code/modules/mining/equipment/mining_tools.dm
@@ -4,6 +4,7 @@
icon = 'icons/obj/mining.dmi'
icon_state = "pickaxe"
inhand_icon_state = "pickaxe"
+ icon_angle = -45
obj_flags = CONDUCTS_ELECTRICITY
slot_flags = ITEM_SLOT_BELT | ITEM_SLOT_BACK
force = 15
@@ -65,6 +66,7 @@
name = "mining drill"
icon_state = "handdrill"
inhand_icon_state = "handdrill"
+ icon_angle = 0
slot_flags = ITEM_SLOT_BELT
toolspeed = 0.6 //available from roundstart, faster than a pickaxe.
usesound = 'sound/items/weapons/drill.ogg'
@@ -121,6 +123,7 @@
icon = 'icons/obj/mining.dmi'
icon_state = "shovel"
inhand_icon_state = "shovel"
+ icon_angle = 135
lefthand_file = 'icons/mob/inhands/equipment/mining_lefthand.dmi'
righthand_file = 'icons/mob/inhands/equipment/mining_righthand.dmi'
obj_flags = CONDUCTS_ELECTRICITY
@@ -158,6 +161,7 @@
desc = "A small tool for digging and moving dirt."
icon_state = "spade"
inhand_icon_state = "spade"
+ icon_angle = -135
lefthand_file = 'icons/mob/inhands/equipment/hydroponics_lefthand.dmi'
righthand_file = 'icons/mob/inhands/equipment/hydroponics_righthand.dmi'
force = 5
@@ -168,6 +172,7 @@
name = "cyborg spade"
icon = 'icons/obj/items_cyborg.dmi'
icon_state = "sili_shovel"
+ icon_angle = 0
toolspeed = 0.6
worn_icon_state = null
@@ -213,6 +218,7 @@
icon = 'icons/obj/mining.dmi'
icon_state = "trench_tool"
inhand_icon_state = "trench_tool"
+ icon_angle = -45
lefthand_file = 'icons/mob/inhands/equipment/mining_lefthand.dmi'
righthand_file = 'icons/mob/inhands/equipment/mining_righthand.dmi'
obj_flags = CONDUCTS_ELECTRICITY
@@ -302,6 +308,7 @@
desc = "A gigantic wrench made illegal because of its many incidents involving this tool."
icon_state = "giant_wrench"
icon = 'icons/obj/weapons/giant_wrench.dmi'
+ icon_angle = 0
inhand_icon_state = null
lefthand_file = 'icons/mob/inhands/64x64_lefthand.dmi'
righthand_file = 'icons/mob/inhands/64x64_righthand.dmi'
diff --git a/code/modules/mining/equipment/monster_organs/regenerative_core.dm b/code/modules/mining/equipment/monster_organs/regenerative_core.dm
index e601ac89f8c5..bcb7bc0455f2 100644
--- a/code/modules/mining/equipment/monster_organs/regenerative_core.dm
+++ b/code/modules/mining/equipment/monster_organs/regenerative_core.dm
@@ -31,7 +31,7 @@
trigger_organ_action(TRIGGER_FORCE_AVAILABLE)
/obj/item/organ/monster_core/regenerative_core/on_triggered_internal()
- owner.revive(HEAL_ALL)
+ owner.revive(HEAL_ALL & ~HEAL_REFRESH_ORGANS)
qdel(src)
/// Log applications and apply moodlet.
diff --git a/code/modules/mining/equipment/survival_pod.dm b/code/modules/mining/equipment/survival_pod.dm
index ce0c2d923a45..a770d7f4bbf0 100644
--- a/code/modules/mining/equipment/survival_pod.dm
+++ b/code/modules/mining/equipment/survival_pod.dm
@@ -4,7 +4,7 @@
icon_state = "away"
static_lighting = TRUE
requires_power = FALSE
- has_gravity = STANDARD_GRAVITY
+ default_gravity = STANDARD_GRAVITY
area_flags = BLOBS_ALLOWED | UNIQUE_AREA
flags_1 = CAN_BE_DIRTY_1
diff --git a/code/modules/mining/lavaland/megafauna_loot.dm b/code/modules/mining/lavaland/megafauna_loot.dm
index e411c0de2512..7a7f6f9805e5 100644
--- a/code/modules/mining/lavaland/megafauna_loot.dm
+++ b/code/modules/mining/lavaland/megafauna_loot.dm
@@ -46,6 +46,7 @@
desc = "The strange technology of this large club allows various nigh-magical teleportation feats. It used to beat you, but now you can set the beat."
icon_state = "hierophant_club_ready_beacon"
inhand_icon_state = "hierophant_club_ready_beacon"
+ icon_angle = -135
icon = 'icons/obj/mining_zones/artefacts.dmi'
lefthand_file = 'icons/mob/inhands/64x64_lefthand.dmi'
righthand_file = 'icons/mob/inhands/64x64_righthand.dmi'
@@ -651,6 +652,7 @@
icon = 'icons/obj/weapons/sword.dmi'
icon_state = "spectral"
inhand_icon_state = "spectral"
+ icon_angle = -45
lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi'
righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi'
obj_flags = CONDUCTS_ELECTRICITY
@@ -660,17 +662,22 @@
throwforce = 1
hitsound = 'sound/effects/ghost2.ogg'
block_sound = 'sound/items/weapons/parry.ogg'
- attack_verb_continuous = list("attacks", "slashes", "stabs", "slices", "tears", "lacerates", "rips", "dices", "rends")
- attack_verb_simple = list("attack", "slash", "stab", "slice", "tear", "lacerate", "rip", "dice", "rend")
+ attack_verb_continuous = list("attacks", "slashes", "slices", "tears", "lacerates", "rips", "dices", "rends")
+ attack_verb_simple = list("attack", "slash", "slice", "tear", "lacerate", "rip", "dice", "rend")
resistance_flags = LAVA_PROOF | FIRE_PROOF | ACID_PROOF
var/summon_cooldown = 0
var/list/mob/dead/observer/spirits
+ var/list/alt_continuous = list("stabs", "pierces", "impales")
+ var/list/alt_simple = list("stab", "pierce", "impale")
/obj/item/melee/ghost_sword/Initialize(mapload)
. = ..()
spirits = list()
START_PROCESSING(SSobj, src)
SSpoints_of_interest.make_point_of_interest(src)
+ alt_continuous = string_list(alt_continuous)
+ alt_simple = string_list(alt_simple)
+ AddComponent(/datum/component/alternative_sharpness, SHARP_POINTY, alt_continuous, alt_simple)
AddComponent(\
/datum/component/butchering, \
speed = 11.5 SECONDS, \
@@ -787,6 +794,7 @@
desc = "The ability to fill the emergency shuttle with lava. What more could you want out of life?"
icon_state = "lavastaff"
inhand_icon_state = "lavastaff"
+ icon_angle = -45
lefthand_file = 'icons/mob/inhands/weapons/staves_lefthand.dmi'
righthand_file = 'icons/mob/inhands/weapons/staves_righthand.dmi'
icon = 'icons/obj/weapons/guns/magic.dmi'
@@ -988,6 +996,7 @@
desc = "An ancient staff retrieved from the remains of Legion. The wind stirs as you move it."
icon_state = "staffofstorms"
inhand_icon_state = "staffofstorms"
+ icon_angle = -45
icon = 'icons/obj/weapons/guns/magic.dmi'
lefthand_file = 'icons/mob/inhands/weapons/staves_lefthand.dmi'
righthand_file = 'icons/mob/inhands/weapons/staves_righthand.dmi'
diff --git a/code/modules/mining/lavaland/tendril_loot.dm b/code/modules/mining/lavaland/tendril_loot.dm
index 133aef377726..557ec8b938c8 100644
--- a/code/modules/mining/lavaland/tendril_loot.dm
+++ b/code/modules/mining/lavaland/tendril_loot.dm
@@ -73,6 +73,7 @@
righthand_file = 'icons/mob/inhands/weapons/staves_righthand.dmi'
icon_state = "asclepius_dormant"
inhand_icon_state = "asclepius_dormant"
+ icon_angle = -45
var/activated = FALSE
/obj/item/rod_of_asclepius/Initialize(mapload)
@@ -533,7 +534,6 @@
/datum/reagent/flightpotion
name = "Flight Potion"
description = "Strange mutagenic compound of unknown origins."
- reagent_state = LIQUID
color = "#976230"
/datum/reagent/flightpotion/expose_mob(mob/living/exposed_mob, methods=TOUCH, reac_volume, show_message = TRUE)
@@ -973,9 +973,7 @@
if(!katana.drew_blood)
to_chat(owner, span_userdanger("[katana] lashes out at you in hunger!"))
playsound(owner, 'sound/effects/magic/demon_attack1.ogg', 50, TRUE)
- var/obj/item/bodypart/part = owner.get_holding_bodypart_of_item(katana)
- if(part)
- part.receive_damage(brute = 25, wound_bonus = 10, sharpness = SHARP_EDGED)
+ owner.apply_damage(25, BRUTE, hand, wound_bonus = 10, sharpness = SHARP_EDGED)
katana.drew_blood = FALSE
katana.wash(CLEAN_TYPE_BLOOD)
return ..()
@@ -993,6 +991,7 @@
Even with the weapon destroyed, all the pieces containing the creature have coagulated back together to find a new host."
icon = 'icons/obj/mining_zones/artefacts.dmi'
icon_state = "cursed_katana"
+ icon_angle = -45
lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi'
righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi'
force = 15
@@ -1001,8 +1000,8 @@
block_sound = 'sound/items/weapons/parry.ogg'
sharpness = SHARP_EDGED
w_class = WEIGHT_CLASS_HUGE
- attack_verb_continuous = list("attacks", "slashes", "stabs", "slices", "tears", "lacerates", "rips", "dices", "cuts")
- attack_verb_simple = list("attack", "slash", "stab", "slice", "tear", "lacerate", "rip", "dice", "cut")
+ attack_verb_continuous = list("attacks", "slashes", "slices", "tears", "lacerates", "rips", "dices", "cuts")
+ attack_verb_simple = list("attack", "slash", "slice", "tear", "lacerate", "rip", "dice", "cut")
hitsound = 'sound/items/weapons/bladeslice.ogg'
resistance_flags = LAVA_PROOF | FIRE_PROOF | UNACIDABLE | FREEZE_PROOF
var/shattered = FALSE
@@ -1015,9 +1014,14 @@
ATTACK_CLOAK = list(COMBO_STEPS = list(LEFT_ATTACK, RIGHT_ATTACK, LEFT_ATTACK, RIGHT_ATTACK), COMBO_PROC = PROC_REF(cloak)),
ATTACK_SHATTER = list(COMBO_STEPS = list(RIGHT_ATTACK, LEFT_ATTACK, RIGHT_ATTACK, LEFT_ATTACK), COMBO_PROC = PROC_REF(shatter)),
)
+ var/list/alt_continuous = list("stabs", "pierces", "impales")
+ var/list/alt_simple = list("stab", "pierce", "impale")
/obj/item/cursed_katana/Initialize(mapload)
. = ..()
+ alt_continuous = string_list(alt_continuous)
+ alt_simple = string_list(alt_simple)
+ AddComponent(/datum/component/alternative_sharpness, SHARP_POINTY, alt_continuous, alt_simple)
AddComponent( \
/datum/component/combo_attacks, \
combos = combo_list, \
@@ -1081,6 +1085,7 @@
user.visible_message(span_warning("[user] does a wide slice!"),
span_notice("You do a wide slice!"))
playsound(src, 'sound/items/weapons/bladeslice.ogg', 50, TRUE)
+ user.do_item_attack_animation(target, used_item = src, animation_type = ATTACK_ANIMATION_SLASH)
var/turf/user_turf = get_turf(user)
var/dir_to_target = get_dir(user_turf, get_turf(target))
var/static/list/cursed_katana_slice_angles = list(0, -45, 45, -90, 90) //so that the animation animates towards the target clicked and not towards a side target
@@ -1120,6 +1125,7 @@
user.visible_message(span_warning("[user] cuts [target]'s tendons!"),
span_notice("You tendon cut [target]!"))
to_chat(target, span_userdanger("Your tendons have been cut by [user]!"))
+ user.do_item_attack_animation(target, used_item = src, animation_type = ATTACK_ANIMATION_SLASH)
target.apply_damage(damage = 15, sharpness = SHARP_EDGED, wound_bonus = 15)
user.do_attack_animation(target, ATTACK_EFFECT_DISARM)
playsound(src, 'sound/items/weapons/rapierhit.ogg', 50, TRUE)
diff --git a/code/modules/mob/dead/new_player/poll.dm b/code/modules/mob/dead/new_player/poll.dm
index 9be971204c1e..6ecd6f12a272 100644
--- a/code/modules/mob/dead/new_player/poll.dm
+++ b/code/modules/mob/dead/new_player/poll.dm
@@ -18,7 +18,7 @@ GLOBAL_PROTECT(poll_options)
var/datum/poll_question/poll = p
if((poll.admin_only && !client.holder) || poll.future_poll)
continue
- output += "
[span_notice("NOTICE - New cyborg connection detected: [name]")] ")
if(AI_NOTIFICATION_NEW_MODEL) //New Model
to_chat(connected_ai, "
[span_notice("NOTICE - Cyborg model change detected: [name] has loaded the [designation] model.")] ")
if(AI_NOTIFICATION_CYBORG_RENAMED) //New Name
to_chat(connected_ai, "
[span_notice("NOTICE - Cyborg reclassification detected: [oldname] is now designated as [newname].")] ")
if(AI_NOTIFICATION_AI_SHELL) //New Shell
- to_chat(connected_ai, "
[span_notice("NOTICE - New cyborg shell detected: [name]")] ")
if(AI_NOTIFICATION_CYBORG_DISCONNECTED) //Tampering with the wires
to_chat(connected_ai, "
[span_notice("NOTICE - Remote telemetry lost with [name].")] ")
diff --git a/code/modules/mob/living/silicon/robot/robot_defines.dm b/code/modules/mob/living/silicon/robot/robot_defines.dm
index d9a33fd017e8..64bfc43c02cc 100644
--- a/code/modules/mob/living/silicon/robot/robot_defines.dm
+++ b/code/modules/mob/living/silicon/robot/robot_defines.dm
@@ -16,6 +16,7 @@
has_limbs = TRUE
hud_type = /datum/hud/robot
unique_name = TRUE
+ mouse_drop_zone = TRUE
///Represents the cyborg's model (engineering, medical, etc.)
var/obj/item/robot_model/model = null
diff --git a/code/modules/mob/living/silicon/silicon.dm b/code/modules/mob/living/silicon/silicon.dm
index f0b1b7b3a8e4..305cc5a1cc67 100644
--- a/code/modules/mob/living/silicon/silicon.dm
+++ b/code/modules/mob/living/silicon/silicon.dm
@@ -77,6 +77,7 @@
TRAIT_SILICON_ACCESS,
TRAIT_REAGENT_SCANNER,
TRAIT_UNOBSERVANT,
+ TRAIT_NO_SLIP_ALL,
)
add_traits(traits_to_apply, ROUNDSTART_TRAIT)
@@ -153,7 +154,7 @@
for(var/alarm_type in alarm_types_show)
msg += "[uppertext(alarm_type)]: [alarm_types_show[alarm_type]] alarms detected. - "
- msg += "\[Show Alerts\]"
+ msg += "\[Show Alerts\]"
to_chat(src, msg)
if(length(alarms_to_clear) < 3)
@@ -166,7 +167,7 @@
for(var/alarm_type in alarm_types_clear)
msg += "[uppertext(alarm_type)]: [alarm_types_clear[alarm_type]] alarms cleared. - "
- msg += "\[Show Alerts\]"
+ msg += "\[Show Alerts\]"
to_chat(src, msg)
diff --git a/code/modules/mob/living/silicon/silicon_defense.dm b/code/modules/mob/living/silicon/silicon_defense.dm
index baa2e9565a17..9c66f377fb02 100644
--- a/code/modules/mob/living/silicon/silicon_defense.dm
+++ b/code/modules/mob/living/silicon/silicon_defense.dm
@@ -87,13 +87,13 @@
/mob/living/silicon/check_block(atom/hitby, damage, attack_text, attack_type, armour_penetration, damage_type, attack_flag)
. = ..()
- if(.)
- return TRUE
+ if(. == SUCCESSFUL_BLOCK)
+ return SUCCESSFUL_BLOCK
if(damage_type == BRUTE && attack_type == UNARMED_ATTACK && attack_flag == MELEE && damage <= 10)
playsound(src, 'sound/effects/bang.ogg', 10, TRUE)
visible_message(span_danger("[attack_text] doesn't leave a dent on [src]!"), vision_distance = COMBAT_MESSAGE_RANGE)
- return TRUE
- return FALSE
+ return SUCCESSFUL_BLOCK
+ return FAILED_BLOCK
/mob/living/silicon/attack_drone(mob/living/basic/drone/user)
if(user.combat_mode)
diff --git a/code/modules/mob/living/silicon/silicon_say.dm b/code/modules/mob/living/silicon/silicon_say.dm
index 824bba98dc07..3111eb07c652 100644
--- a/code/modules/mob/living/silicon/silicon_say.dm
+++ b/code/modules/mob/living/silicon/silicon_say.dm
@@ -32,7 +32,7 @@
M,
span_binarysay("\
Robotic Talk, \
- [span_name("[namepart] ([designation])")] \
+ [span_name("[namepart] ([designation])")] \
[quoted_message]\
"),
avoid_highlighting = src == M
diff --git a/code/modules/mob/living/simple_animal/bot/bot.dm b/code/modules/mob/living/simple_animal/bot/bot.dm
index 38599c204653..6753a34b5a3b 100644
--- a/code/modules/mob/living/simple_animal/bot/bot.dm
+++ b/code/modules/mob/living/simple_animal/bot/bot.dm
@@ -686,8 +686,8 @@ Pass a positive integer as an argument to override a bot's default speed.
if(mode != BOT_SUMMON && mode != BOT_RESPONDING)
access_card.set_access(prev_access)
-/mob/living/simple_animal/bot/proc/call_bot(caller, turf/waypoint, message = TRUE)
- if(isAI(caller) && calling_ai && calling_ai != src) //Prevents an override if another AI is controlling this bot.
+/mob/living/simple_animal/bot/proc/call_bot(summoner, turf/waypoint, message = TRUE)
+ if(isAI(summoner) && calling_ai && calling_ai != src) //Prevents an override if another AI is controlling this bot.
return FALSE
bot_reset() //Reset a bot before setting it to call mode.
@@ -696,7 +696,7 @@ Pass a positive integer as an argument to override a bot's default speed.
//Easier then building the list ourselves. I'm sorry.
var/static/obj/item/card/id/all_access = new /obj/item/card/id/advanced/gold/captains_spare()
set_path(get_path_to(src, waypoint, max_distance=200, access = all_access.GetAccess()))
- calling_ai = caller //Link the AI to the bot!
+ calling_ai = summoner //Link the AI to the bot!
ai_waypoint = waypoint
if(path?.len) //Ensures that a valid path is calculated!
@@ -706,7 +706,7 @@ Pass a positive integer as an argument to override a bot's default speed.
access_card.set_access(REGION_ACCESS_ALL_STATION) //Give the bot all-access while under the AI's command.
if(client)
reset_access_timer_id = addtimer(CALLBACK (src, PROC_REF(bot_reset)), 60 SECONDS, TIMER_UNIQUE|TIMER_OVERRIDE|TIMER_STOPPABLE) //if the bot is player controlled, they get the extra access for a limited time
- to_chat(src, span_notice("[span_big("Priority waypoint set by [icon2html(calling_ai, src)] [caller]. Proceed to [end_area].")] [path.len-1] meters to destination. You have been granted additional door access for 60 seconds."))
+ to_chat(src, span_notice("[span_big("Priority waypoint set by [icon2html(calling_ai, src)] [summoner]. Proceed to [end_area].")] [path.len-1] meters to destination. You have been granted additional door access for 60 seconds."))
if(message)
to_chat(calling_ai, span_notice("[icon2html(src, calling_ai)] [name] called to [end_area]. [path.len-1] meters to destination."))
pathset = TRUE
diff --git a/code/modules/mob/living/simple_animal/bot/construction.dm b/code/modules/mob/living/simple_animal/bot/construction.dm
index e0f847e20b7a..234c06ed3522 100644
--- a/code/modules/mob/living/simple_animal/bot/construction.dm
+++ b/code/modules/mob/living/simple_animal/bot/construction.dm
@@ -197,8 +197,10 @@
icon_state = "repairbot_base"
throwforce = 10
created_name = "Repairbot"
+ ///the toolbox our repairbot is made of
var/toolbox = /obj/item/storage/toolbox/mechanical
- var/toolbox_color = "" //Blank for blue, r for red, y for yellow, etc.
+ ///the color of our toolbox
+ var/toolbox_color = ""
/obj/item/bot_assembly/repairbot/Initialize(mapload)
. = ..()
@@ -245,7 +247,9 @@
repair.toolbox = toolbox
repair.set_color(toolbox_color)
to_chat(user, span_notice("You add [item] to [src]. Boop beep!"))
- qdel(item)
+ var/obj/item/stack/crafting_stack = item
+ var/atom/used_belt = crafting_stack.split_stack(user, 1)
+ qdel(used_belt)
qdel(src)
diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/demonic_frost_miner.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/demonic_frost_miner.dm
index 8cdf9141ab84..a5897c01695d 100644
--- a/code/modules/mob/living/simple_animal/hostile/megafauna/demonic_frost_miner.dm
+++ b/code/modules/mob/living/simple_animal/hostile/megafauna/demonic_frost_miner.dm
@@ -321,18 +321,6 @@ Difficulty: Extremely Hard
mineral_scan_pulse(T, world.view + 1, src)
. = ..()
-/obj/item/crusher_trophy/ice_block_talisman
- name = "ice block talisman"
- desc = "A glowing trinket that a demonic miner had on him, it seems he couldn't utilize it for whatever reason."
- icon_state = "ice_trap_talisman"
- denied_type = /obj/item/crusher_trophy/ice_block_talisman
-
-/obj/item/crusher_trophy/ice_block_talisman/effect_desc()
- return "mark detonation to freeze a creature in a block of ice for a period, preventing them from moving"
-
-/obj/item/crusher_trophy/ice_block_talisman/on_mark_detonation(mob/living/target, mob/living/user)
- target.apply_status_effect(/datum/status_effect/ice_block_talisman)
-
/datum/status_effect/ice_block_talisman
id = "ice_block_talisman"
duration = 4 SECONDS
diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/wendigo.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/wendigo.dm
index aad198801adf..b6ba30f523f6 100644
--- a/code/modules/mob/living/simple_animal/hostile/megafauna/wendigo.dm
+++ b/code/modules/mob/living/simple_animal/hostile/megafauna/wendigo.dm
@@ -199,6 +199,7 @@ Difficulty: Hard
/obj/projectile/colossus/wendigo_shockwave
name = "wendigo shockwave"
speed = 0.5
+
/// Amount the angle changes every pixel move
var/wave_speed = 0.5
/// Amount of movements this projectile has made
@@ -209,14 +210,16 @@ Difficulty: Hard
/obj/projectile/colossus/wendigo_shockwave/wave
speed = 0.125
- homing = TRUE
wave_speed = 0.3
/obj/projectile/colossus/wendigo_shockwave/wave/alternate
wave_speed = -0.3
-/obj/projectile/colossus/wendigo_shockwave/process_homing()
- pixel_moves++
+/obj/projectile/colossus/wendigo_shockwave/process_movement(pixels_to_move, hitscan, tile_limit)
+ . = ..()
+ if (QDELETED(src))
+ return
+ pixel_moves += .
set_angle(original_angle + pixel_moves * wave_speed)
/obj/item/wendigo_blood
@@ -237,25 +240,6 @@ Difficulty: Hard
playsound(human_user.loc, 'sound/items/drink.ogg', rand(10,50), TRUE)
qdel(src)
-/obj/item/crusher_trophy/wendigo_horn
- name = "wendigo horn"
- desc = "A gnarled horn ripped from the skull of a wendigo. Suitable as a trophy for a kinetic crusher."
- icon_state = "wendigo_horn"
- denied_type = /obj/item/crusher_trophy/wendigo_horn
-
-/obj/item/crusher_trophy/wendigo_horn/effect_desc()
- return "melee hits inflict twice as much damage"
-
-/obj/item/crusher_trophy/wendigo_horn/add_to(obj/item/kinetic_crusher/crusher, mob/living/user)
- . = ..()
- if(.)
- crusher.AddComponent(/datum/component/two_handed, force_wielded=40)
-
-/obj/item/crusher_trophy/wendigo_horn/remove_from(obj/item/kinetic_crusher/crusher, mob/living/user)
- . = ..()
- if(.)
- crusher.AddComponent(/datum/component/two_handed, force_wielded=20)
-
/obj/item/wendigo_skull
name = "wendigo skull"
desc = "A bloody skull torn from a murderous beast, the soulless eye sockets seem to constantly track your movement."
diff --git a/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/goliath_broodmother.dm b/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/goliath_broodmother.dm
index 6e673aa9be5e..55d7a1d5c3d9 100644
--- a/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/goliath_broodmother.dm
+++ b/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/goliath_broodmother.dm
@@ -244,39 +244,6 @@
T = get_step(T, i)
new /obj/effect/goliath_tentacle/broodmother(T)
-// Broodmother's loot: Broodmother Tongue
-/obj/item/crusher_trophy/broodmother_tongue
- name = "broodmother tongue"
- desc = "The tongue of a broodmother. If attached a certain way, makes for a suitable crusher trophy. It also feels very spongey, I wonder what would happen if you squeezed it?..."
- icon = 'icons/obj/mining_zones/elite_trophies.dmi'
- icon_state = "broodmother_tongue"
- denied_type = /obj/item/crusher_trophy/broodmother_tongue
- bonus_value = 10
- /// Time at which the item becomes usable again
- var/use_time
-
-/obj/item/crusher_trophy/broodmother_tongue/effect_desc()
- return "mark detonation to have a [bonus_value]% chance to summon a patch of goliath tentacles at the target's location"
-
-/obj/item/crusher_trophy/broodmother_tongue/on_mark_detonation(mob/living/target, mob/living/user)
- if(prob(bonus_value) && target.stat != DEAD)
- new /obj/effect/goliath_tentacle/broodmother/patch(get_turf(target), user)
-
-/obj/item/crusher_trophy/broodmother_tongue/attack_self(mob/user)
- if(!isliving(user))
- return
- var/mob/living/living_user = user
- if(use_time > world.time)
- to_chat(living_user, "The tongue looks dried out. You'll need to wait longer to use it again.")
- return
- else if(HAS_TRAIT(living_user, TRAIT_LAVA_IMMUNE))
- to_chat(living_user, "You stare at the tongue. You don't think this is any use to you.")
- return
- ADD_TRAIT(living_user, TRAIT_LAVA_IMMUNE, type)
- to_chat(living_user, "You squeeze the tongue, and some transluscent liquid shoots out all over you.")
- addtimer(TRAIT_CALLBACK_REMOVE(user, TRAIT_LAVA_IMMUNE, type), 10 SECONDS)
- use_time = world.time + 60 SECONDS
-
#undef CALL_CHILDREN
#undef RAGE
#undef SPAWN_CHILDREN
diff --git a/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/legionnaire.dm b/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/legionnaire.dm
index 051733211ed3..1c729df07d36 100644
--- a/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/legionnaire.dm
+++ b/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/legionnaire.dm
@@ -310,42 +310,6 @@
. = ..()
transform *= 0.33
-// Legionnaire's loot: Legionnaire Spine
-
-/obj/item/crusher_trophy/legionnaire_spine
- name = "legionnaire spine"
- desc = "The spine of a legionnaire. With some creativity, you could use it as a crusher trophy. Alternatively, shaking it might do something as well."
- icon = 'icons/obj/mining_zones/elite_trophies.dmi'
- icon_state = "legionnaire_spine"
- denied_type = /obj/item/crusher_trophy/legionnaire_spine
- bonus_value = 20
- /// Time at which the item becomes usable again
- var/next_use_time
-
-/obj/item/crusher_trophy/legionnaire_spine/effect_desc()
- return "mark detonation to have a [bonus_value]% chance to summon a loyal legion skull"
-
-/obj/item/crusher_trophy/legionnaire_spine/on_mark_detonation(mob/living/target, mob/living/user)
- if(!prob(bonus_value) || target.stat == DEAD)
- return
- var/mob/living/basic/legion_brood/minion = new (user.loc)
- minion.assign_creator(user)
- minion.ai_controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET] = target
-
-/obj/item/crusher_trophy/legionnaire_spine/attack_self(mob/user)
- if(!isliving(user))
- return
- var/mob/living/LivingUser = user
- if(next_use_time > world.time)
- LivingUser.visible_message(span_warning("[LivingUser] shakes the [src], but nothing happens..."))
- to_chat(LivingUser, "You need to wait longer to use this again.")
- return
- LivingUser.visible_message(span_boldwarning("[LivingUser] shakes the [src] and summons a legion skull!"))
-
- var/mob/living/basic/legion_brood/minion = new (LivingUser.loc)
- minion.assign_creator(LivingUser)
- next_use_time = world.time + 4 SECONDS
-
#undef LEGIONNAIRE_CHARGE
#undef HEAD_DETACH
#undef BONFIRE_TELEPORT
diff --git a/code/modules/mob/living/simple_animal/hostile/mining_mobs/polarbear.dm b/code/modules/mob/living/simple_animal/hostile/mining_mobs/polarbear.dm
index 5dba83e25320..ef8e596ac2ce 100644
--- a/code/modules/mob/living/simple_animal/hostile/mining_mobs/polarbear.dm
+++ b/code/modules/mob/living/simple_animal/hostile/mining_mobs/polarbear.dm
@@ -65,20 +65,3 @@
name = "magic polar bear"
desc = "It seems sentient somehow."
faction = list(FACTION_NEUTRAL)
-
-/obj/item/crusher_trophy/bear_paw
- name = "polar bear paw"
- desc = "It's a polar bear paw."
- icon_state = "bear_paw"
- denied_type = /obj/item/crusher_trophy/bear_paw
-
-/obj/item/crusher_trophy/bear_paw/effect_desc()
- return "mark detonation to attack twice if you are below half your life"
-
-/obj/item/crusher_trophy/bear_paw/on_mark_detonation(mob/living/target, mob/living/user)
- if(user.health / user.maxHealth > 0.5)
- return
- var/obj/item/I = user.get_active_held_item()
- if(!I)
- return
- I.melee_attack_chain(user, target, null)
diff --git a/code/modules/mob/living/simple_animal/hostile/ooze.dm b/code/modules/mob/living/simple_animal/hostile/ooze.dm
index 123515dc8609..663539db18ca 100644
--- a/code/modules/mob/living/simple_animal/hostile/ooze.dm
+++ b/code/modules/mob/living/simple_animal/hostile/ooze.dm
@@ -353,7 +353,7 @@
return TRUE
-/datum/action/cooldown/globules/InterceptClickOn(mob/living/caller, params, atom/target)
+/datum/action/cooldown/globules/InterceptClickOn(mob/living/clicker, params, atom/target)
. = ..()
if(!.)
return FALSE
@@ -362,19 +362,19 @@
// Well, we need to use the params of the click intercept
// for passing into aim_projectile, so we'll handle it here instead.
// We just need to make sure Pre-activate and Activate return TRUE so we make it this far
- caller.visible_message(
- span_nicegreen("[caller] launches a mending globule!"),
+ clicker.visible_message(
+ span_nicegreen("[clicker] launches a mending globule!"),
span_notice("You launch a mending globule."),
)
- var/mob/living/simple_animal/hostile/ooze/oozy = caller
+ var/mob/living/simple_animal/hostile/ooze/oozy = clicker
if(istype(oozy))
oozy.adjust_ooze_nutrition(-5)
var/modifiers = params2list(params)
- var/obj/projectile/globule/globule = new(caller.loc)
- globule.aim_projectile(target, caller, modifiers)
- globule.def_zone = caller.zone_selected
+ var/obj/projectile/globule/globule = new(clicker.loc)
+ globule.aim_projectile(target, clicker, modifiers)
+ globule.def_zone = clicker.zone_selected
globule.fire()
StartCooldown()
diff --git a/code/modules/mob/living/simple_animal/hostile/vatbeast.dm b/code/modules/mob/living/simple_animal/hostile/vatbeast.dm
index 8eab28a52e6a..c4850fae783a 100644
--- a/code/modules/mob/living/simple_animal/hostile/vatbeast.dm
+++ b/code/modules/mob/living/simple_animal/hostile/vatbeast.dm
@@ -31,7 +31,13 @@
GRANT_ACTION(/datum/action/cooldown/tentacle_slap)
add_cell_sample()
- AddComponent(/datum/component/tameable, list(/obj/item/food/fries, /obj/item/food/cheesyfries, /obj/item/food/cornchips, /obj/item/food/carrotfries), tame_chance = 30, bonus_tame_chance = 0)
+ var/static/list/food_types = list(
+ /obj/item/food/fries,
+ /obj/item/food/cheesyfries,
+ /obj/item/food/cornchips,
+ /obj/item/food/carrotfries,
+ )
+ AddComponent(/datum/component/tameable, food_types = food_types, tame_chance = 30, bonus_tame_chance = 0)
/mob/living/simple_animal/hostile/vatbeast/tamed(mob/living/tamer, obj/item/food)
buckle_lying = 0
@@ -79,13 +85,13 @@
if(refund_cooldown)
to_chat(on_who, span_notice("You stop preparing your [on_who == owner ? "":"steed's "]pimp-tentacle."))
-/datum/action/cooldown/tentacle_slap/InterceptClickOn(mob/living/caller, params, atom/target)
+/datum/action/cooldown/tentacle_slap/InterceptClickOn(mob/living/clicker, params, atom/target)
// Check if we can slap
if(!isliving(target) || target == owner)
return FALSE
if(!owner.Adjacent(target))
- owner.balloon_alert(caller, "too far!")
+ owner.balloon_alert(clicker, "too far!")
return FALSE
// Do the slap
@@ -95,8 +101,8 @@
// Give feedback from the slap.
// Additional feedback for if a rider did it
- if(caller != owner)
- to_chat(caller, span_notice("You command [owner] to slap [target] with its tentacles."))
+ if(clicker != owner)
+ to_chat(clicker, span_notice("You command [owner] to slap [target] with its tentacles."))
return TRUE
diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm
index 9de3ea7bd4e0..5bacc9937e86 100644
--- a/code/modules/mob/mob.dm
+++ b/code/modules/mob/mob.dm
@@ -179,7 +179,7 @@
else
var/image/I = image('modular_nova/master_files/icons/mob/huds/hud.dmi', src, "") //NOVA EDIT: original filepath 'icons/mob/huds/hud.dmi'
- I.appearance_flags = RESET_COLOR|RESET_TRANSFORM
+ I.appearance_flags = RESET_COLOR|PIXEL_SCALE|KEEP_APART
hud_list[hud] = I
set_hud_image_active(hud, update_huds = FALSE) //by default everything is active. but dont add it to huds to keep control.
@@ -563,7 +563,7 @@
var/list/result = examinify.examine_more(src)
if(!length(result))
result += span_notice("You examine [examinify] closer, but find nothing of interest...")
- result_combined = examine_block(jointext(result, " "))
+ result_combined = boxed_message(jointext(result, " "))
result_combined = replacetext(result_combined, " ", "") // NOVA EDIT ADDITION - bit of a hack here to make sure we don't get linebreaks coming after headers
else
@@ -575,8 +575,8 @@
var/list/result = examinify.examine(src)
var/atom_title = examinify.examine_title(src, thats = TRUE)
SEND_SIGNAL(src, COMSIG_MOB_EXAMINING, examinify, result)
- result_combined = (atom_title ? "[span_slightly_larger("[atom_title][ismob(examinify) ? "!" :"."][EXAMINE_SECTION_BREAK]")]" : "") + jointext(result, " ") // NOVA EDIT CHANGE - No centered title + exclamation point for mobs - ORIGINAL: result_combined = (atom_title ? fieldset_block("[span_slightly_larger(atom_title)].", jointext(result, " "), "examine_block") : examine_block(jointext(result, " ")))
- result_combined = examine_block(replacetext(result_combined, " ", "")) // NOVA EDIT ADDITION - bit of a hack here to make sure we don't get linebreaks coming after headers, as well as properly adding the examine_block
+ result_combined = (atom_title ? fieldset_block("[atom_title][ismob(examinify) ? "!" :"."]", jointext(result, " "), "boxed_message") : boxed_message(jointext(result, " "))) // NOVA EDIT CHANGE - ORIGINAL: result_combined = (atom_title ? fieldset_block("[atom_title]", jointext(result, " "), "boxed_message") : boxed_message(jointext(result, " ")))
+ result_combined = replacetext(result_combined, " ", "") // NOVA EDIT ADDITION - bit of a hack here to make sure we don't get linebreaks coming after headers
to_chat(src, span_infoplain(result_combined))
SEND_SIGNAL(src, COMSIG_MOB_EXAMINATE, examinify)
diff --git a/code/modules/mob/mob_helpers.dm b/code/modules/mob/mob_helpers.dm
index 0d232ef3b669..006b2684c975 100644
--- a/code/modules/mob/mob_helpers.dm
+++ b/code/modules/mob/mob_helpers.dm
@@ -324,8 +324,8 @@
to_chat(ghost, span_ghostalert(message))
continue
- var/interact_link = click_interact ? " (Play)" : ""
- var/view_link = " (View)"
+ var/interact_link = click_interact ? " (Play)" : ""
+ var/view_link = " (View)"
to_chat(ghost, span_ghostalert("[message][custom_link][interact_link][view_link]"))
diff --git a/code/modules/mob/mob_movement.dm b/code/modules/mob/mob_movement.dm
index 2b60b5682acb..e5b7557c99f6 100644
--- a/code/modules/mob/mob_movement.dm
+++ b/code/modules/mob/mob_movement.dm
@@ -281,6 +281,9 @@
if (SEND_SIGNAL(src, COMSIG_MOB_ATTEMPT_HALT_SPACEMOVE, movement_dir, continuous_move, backup) & COMPONENT_PREVENT_SPACEMOVE_HALT)
return FALSE
+ if (drift_handler?.attempt_halt(movement_dir, continuous_move, backup))
+ return FALSE
+
if(continuous_move || !istype(backup) || !movement_dir || backup.anchored)
return TRUE
@@ -300,8 +303,9 @@
/**
* Finds a target near a mob that is viable for pushing off when moving.
* Takes the intended movement direction as input, alongside if the context is checking if we're allowed to continue drifting
+ * If include_floors is TRUE, includes floors *with gravity*
*/
-/mob/get_spacemove_backup(moving_direction, continuous_move)
+/mob/get_spacemove_backup(moving_direction, continuous_move, include_floors = FALSE)
var/atom/secondary_backup
var/list/priority_dirs = (moving_direction in GLOB.cardinals) ? GLOB.cardinals : GLOB.diagonals
for(var/atom/pushover as anything in range(1, get_turf(src)))
@@ -309,13 +313,15 @@
continue
if(isarea(pushover))
continue
+ var/is_priority = pushover.loc == loc || (get_dir(src, pushover) in priority_dirs)
if(isturf(pushover))
var/turf/turf = pushover
if(isspaceturf(turf))
continue
if(!turf.density && !mob_negates_gravity())
- continue
- if (get_dir(src, pushover) in priority_dirs)
+ if (!include_floors || !turf.has_gravity())
+ continue
+ if (is_priority)
return pushover
secondary_backup = pushover
continue
@@ -335,21 +341,21 @@
if(rebound.last_pushoff == world.time)
continue
if(continuous_move && !pass_allowed)
- var/datum/move_loop/move/rebound_engine = GLOB.move_manager.processing_on(rebound, SSnewtonian_movement)
+ var/datum/move_loop/smooth_move/rebound_engine = GLOB.move_manager.processing_on(rebound, SSnewtonian_movement)
// If you're moving toward it and you're both going the same direction, stop
- if(moving_direction == get_dir(src, pushover) && rebound_engine && moving_direction == rebound_engine.direction)
+ if(moving_direction == get_dir(src, pushover) && rebound_engine && moving_direction == angle2dir(rebound_engine.angle))
continue
else if(!pass_allowed)
if(moving_direction == get_dir(src, pushover)) // Can't push "off" of something that you're walking into
continue
if(rebound.anchored)
- if (get_dir(src, rebound) in priority_dirs)
+ if (is_priority)
return rebound
secondary_backup = rebound
continue
if(pulling == rebound)
continue
- if (get_dir(src, rebound) in priority_dirs)
+ if (is_priority)
return rebound
secondary_backup = rebound
return secondary_backup
diff --git a/code/modules/mod/mod_control.dm b/code/modules/mod/mod_control.dm
index 066abfd9425d..377a8e3f768b 100644
--- a/code/modules/mod/mod_control.dm
+++ b/code/modules/mod/mod_control.dm
@@ -526,7 +526,7 @@
unset_wearer()
old_wearer.temporarilyRemoveItemFromInventory(src)
-/obj/item/mod/control/proc/on_species_gain(datum/source, datum/species/new_species, datum/species/old_species)
+/obj/item/mod/control/proc/on_species_gain(datum/source, datum/species/new_species, datum/species/old_species, pref_load, regenerate_icons)
SIGNAL_HANDLER
for(var/obj/item/part in get_parts(all = TRUE))
@@ -758,7 +758,7 @@
to_chat(user, span_warning("It's too dangerous to smear [speed_potion] on [src] while it's active!"))
return SPEED_POTION_STOP
to_chat(user, span_notice("You slather the red gunk over [src], making it faster."))
- set_mod_color(COLOR_RED)
+ set_mod_color(color_transition_filter(COLOR_RED))
slowdown_inactive = 0
slowdown_active = 0
update_speed()
diff --git a/code/modules/mod/mod_link.dm b/code/modules/mod/mod_link.dm
index aff84d640b7b..c9d77a406f59 100644
--- a/code/modules/mod/mod_link.dm
+++ b/code/modules/mod/mod_link.dm
@@ -401,7 +401,7 @@
RETURN_TYPE(/datum/mod_link)
if(!link_call)
return
- return link_call.caller == src ? link_call.receiver : link_call.caller
+ return link_call.link_caller == src ? link_call.link_receiver : link_call.link_caller
/datum/mod_link/proc/call_link(datum/mod_link/called, mob/user)
if(!frequency)
@@ -428,8 +428,8 @@
link_target.playsound_local(get_turf(called.holder), 'sound/items/weapons/ring.ogg', 15, vary = TRUE)
var/atom/movable/screen/alert/modlink_call/alert = link_target.throw_alert("[REF(src)]_modlink", /atom/movable/screen/alert/modlink_call)
alert.desc = "[holder] ([id]) is calling you! Left-click this to accept the call. Right-click to deny it."
- alert.caller_ref = WEAKREF(src)
- alert.receiver_ref = WEAKREF(called)
+ alert.link_caller_ref = WEAKREF(src)
+ alert.link_receiver_ref = WEAKREF(called)
alert.user_ref = WEAKREF(user)
/datum/mod_link/proc/end_call()
@@ -442,33 +442,33 @@
/// A MODlink call datum, used to handle the call between two MODlinks.
/datum/mod_link_call
/// The MODlink that is calling.
- var/datum/mod_link/caller
+ var/datum/mod_link/link_caller
/// The MODlink that is being called.
- var/datum/mod_link/receiver
-
-/datum/mod_link_call/New(datum/mod_link/caller, datum/mod_link/receiver)
- caller.link_call = src
- receiver.link_call = src
- src.caller = caller
- src.receiver = receiver
- var/mob/living/caller_mob = caller.get_user_callback.Invoke()
+ var/datum/mod_link/link_receiver
+
+/datum/mod_link_call/New(datum/mod_link/link_caller, datum/mod_link/link_receiver)
+ link_caller.link_call = src
+ link_receiver.link_call = src
+ src.link_caller = link_caller
+ src.link_receiver = link_receiver
+ var/mob/living/caller_mob = link_caller.get_user_callback.Invoke()
ADD_TRAIT(caller_mob, TRAIT_IN_CALL, REF(src))
- var/mob/living/receiver_mob = receiver.get_user_callback.Invoke()
+ var/mob/living/receiver_mob = link_receiver.get_user_callback.Invoke()
ADD_TRAIT(receiver_mob, TRAIT_IN_CALL, REF(src))
make_visuals()
START_PROCESSING(SSprocessing, src)
/datum/mod_link_call/Destroy()
- var/mob/living/caller_mob = caller.get_user_callback.Invoke()
+ var/mob/living/caller_mob = link_caller.get_user_callback.Invoke()
if(!QDELETED(caller_mob))
REMOVE_TRAIT(caller_mob, TRAIT_IN_CALL, REF(src))
- var/mob/living/receiver_mob = receiver.get_user_callback.Invoke()
+ var/mob/living/receiver_mob = link_receiver.get_user_callback.Invoke()
if(!QDELETED(receiver_mob))
REMOVE_TRAIT(receiver_mob, TRAIT_IN_CALL, REF(src))
STOP_PROCESSING(SSprocessing, src)
clear_visuals()
- caller.link_call = null
- receiver.link_call = null
+ link_caller.link_call = null
+ link_receiver.link_call = null
return ..()
/datum/mod_link_call/process(seconds_per_tick)
@@ -477,17 +477,17 @@
qdel(src)
/datum/mod_link_call/proc/can_continue_call()
- return caller.frequency == receiver.frequency && caller.can_call_callback.Invoke() && receiver.can_call_callback.Invoke()
+ return link_caller.frequency == link_receiver.frequency && link_caller.can_call_callback.Invoke() && link_receiver.can_call_callback.Invoke()
/datum/mod_link_call/proc/make_visuals()
- var/caller_visual = caller.make_visual_callback.Invoke()
- var/receiver_visual = receiver.make_visual_callback.Invoke()
- caller.get_visual_callback.Invoke(receiver_visual)
- receiver.get_visual_callback.Invoke(caller_visual)
+ var/caller_visual = link_caller.make_visual_callback.Invoke()
+ var/receiver_visual = link_receiver.make_visual_callback.Invoke()
+ link_caller.get_visual_callback.Invoke(receiver_visual)
+ link_receiver.get_visual_callback.Invoke(caller_visual)
/datum/mod_link_call/proc/clear_visuals()
- caller.delete_visual_callback.Invoke()
- receiver.delete_visual_callback.Invoke()
+ link_caller.delete_visual_callback.Invoke()
+ link_receiver.delete_visual_callback.Invoke()
/proc/call_link(mob/user, datum/mod_link/calling_link)
if(!calling_link.frequency)
@@ -518,9 +518,9 @@
clickable_glow = TRUE
var/end_message = "call timed out!"
/// A weak reference to the MODlink that is calling.
- var/datum/weakref/caller_ref
+ var/datum/weakref/link_caller_ref
/// A weak reference to the MODlink that is being called.
- var/datum/weakref/receiver_ref
+ var/datum/weakref/link_receiver_ref
/// A weak reference to the mob that is calling.
var/datum/weakref/user_ref
@@ -528,25 +528,25 @@
. = ..()
if(usr != owner)
return
- var/datum/mod_link/caller = caller_ref.resolve()
- var/datum/mod_link/receiver = receiver_ref.resolve()
- if(!caller || !receiver)
+ var/datum/mod_link/link_caller = link_caller_ref.resolve()
+ var/datum/mod_link/link_receiver = link_receiver_ref.resolve()
+ if(!link_caller || !link_receiver)
return
- if(caller.link_call || receiver.link_call)
+ if(link_caller.link_call || link_receiver.link_call)
return
var/list/modifiers = params2list(params)
if(LAZYACCESS(modifiers, RIGHT_CLICK))
end_message = "call denied!"
- owner.clear_alert("[REF(caller)]_modlink")
+ owner.clear_alert("[REF(link_caller)]_modlink")
return
end_message = "call accepted"
- new /datum/mod_link_call(caller, receiver)
- owner.clear_alert("[REF(caller)]_modlink")
+ new /datum/mod_link_call(link_caller, link_receiver)
+ owner.clear_alert("[REF(link_caller)]_modlink")
/atom/movable/screen/alert/modlink_call/Destroy()
var/mob/living/user = user_ref?.resolve()
- var/datum/mod_link/caller = caller_ref?.resolve()
- if(!user || !caller)
+ var/datum/mod_link/link_caller = link_caller_ref?.resolve()
+ if(!user || !link_caller)
return ..()
- caller.holder.balloon_alert(user, end_message)
+ link_caller.holder.balloon_alert(user, end_message)
return ..()
diff --git a/code/modules/mod/mod_types.dm b/code/modules/mod/mod_types.dm
index 626144bf3e11..91fb00c633b0 100644
--- a/code/modules/mod/mod_types.dm
+++ b/code/modules/mod/mod_types.dm
@@ -467,43 +467,43 @@
)
/// The insignia type, insignias show what sort of member of the ERT you're dealing with.
var/insignia_type = /obj/item/mod/module/insignia
- /// Additional module we add, as a treat.
- var/additional_module
+ /// Additional module (or modules if list) we add, as a treat.
+ var/additional_modules
/obj/item/mod/control/pre_equipped/responsory/Initialize(mapload, new_theme, new_skin, new_core)
applied_modules.Insert(1, insignia_type)
- if(additional_module)
- applied_modules += additional_module
- default_pins += additional_module
+ if(additional_modules)
+ applied_modules += additional_modules
+ default_pins += additional_modules
return ..()
/obj/item/mod/control/pre_equipped/responsory/commander
insignia_type = /obj/item/mod/module/insignia/commander
- additional_module = /obj/item/mod/module/power_kick
+ additional_modules = /obj/item/mod/module/power_kick
/obj/item/mod/control/pre_equipped/responsory/security
insignia_type = /obj/item/mod/module/insignia/security
- additional_module = /obj/item/mod/module/pepper_shoulders
+ additional_modules = /obj/item/mod/module/pepper_shoulders
/obj/item/mod/control/pre_equipped/responsory/engineer
insignia_type = /obj/item/mod/module/insignia/engineer
- additional_module = /obj/item/mod/module/rad_protection
+ additional_modules = /obj/item/mod/module/rad_protection
/obj/item/mod/control/pre_equipped/responsory/medic
insignia_type = /obj/item/mod/module/insignia/medic
- additional_module = /obj/item/mod/module/quick_carry
+ additional_modules = /obj/item/mod/module/quick_carry
/obj/item/mod/control/pre_equipped/responsory/janitor
insignia_type = /obj/item/mod/module/insignia/janitor
- additional_module = /obj/item/mod/module/noslip
+ additional_modules = list(/obj/item/mod/module/noslip, /obj/item/mod/module/mister/cleaner)
/obj/item/mod/control/pre_equipped/responsory/clown
insignia_type = /obj/item/mod/module/insignia/clown
- additional_module = /obj/item/mod/module/bikehorn
+ additional_modules = /obj/item/mod/module/bikehorn
/obj/item/mod/control/pre_equipped/responsory/chaplain
insignia_type = /obj/item/mod/module/insignia/chaplain
- additional_module = /obj/item/mod/module/injector
+ additional_modules = /obj/item/mod/module/injector
/obj/item/mod/control/pre_equipped/responsory/inquisitory
applied_skin = "inquisitory"
@@ -538,19 +538,19 @@
/obj/item/mod/control/pre_equipped/responsory/inquisitory/commander
insignia_type = /obj/item/mod/module/insignia/commander
- additional_module = /obj/item/mod/module/power_kick
+ additional_modules = /obj/item/mod/module/power_kick
/obj/item/mod/control/pre_equipped/responsory/inquisitory/security
insignia_type = /obj/item/mod/module/insignia/security
- additional_module = /obj/item/mod/module/pepper_shoulders
+ additional_modules = /obj/item/mod/module/pepper_shoulders
/obj/item/mod/control/pre_equipped/responsory/inquisitory/medic
insignia_type = /obj/item/mod/module/insignia/medic
- additional_module = /obj/item/mod/module/quick_carry
+ additional_modules = /obj/item/mod/module/quick_carry
/obj/item/mod/control/pre_equipped/responsory/inquisitory/chaplain
insignia_type = /obj/item/mod/module/insignia/chaplain
- additional_module = /obj/item/mod/module/injector
+ additional_modules = /obj/item/mod/module/injector
/obj/item/mod/control/pre_equipped/apocryphal
theme = /datum/mod_theme/apocryphal
diff --git a/code/modules/mod/modules/modules_general.dm b/code/modules/mod/modules/modules_general.dm
index 316a6c6f9260..a158297e9356 100644
--- a/code/modules/mod/modules/modules_general.dm
+++ b/code/modules/mod/modules/modules_general.dm
@@ -143,6 +143,7 @@
COMSIG_MODULE_DEACTIVATED, \
MOD_ABORT_USE, \
thrust_callback, \
+ thrust_callback, \
/datum/effect_system/trail_follow/ion/grav_allowed, \
)
diff --git a/code/modules/mod/modules/modules_maint.dm b/code/modules/mod/modules/modules_maint.dm
index 1b398f3387a8..7344993802cb 100644
--- a/code/modules/mod/modules/modules_maint.dm
+++ b/code/modules/mod/modules/modules_maint.dm
@@ -327,16 +327,20 @@
if(you_fucked_up || mod.wearer.has_gravity() > NEGATIVE_GRAVITY)
return
- if (forced || SHOULD_DISABLE_FOOTSTEPS(mod.wearer))
- return
-
var/turf/open/current_turf = get_turf(mod.wearer)
var/turf/open/openspace/turf_above = get_step_multiz(mod.wearer, UP)
if(current_turf && istype(turf_above))
current_turf.zFall(mod.wearer)
+ return
+
else if(!turf_above && istype(current_turf) && current_turf.planetary_atmos) //nothing holding you down
INVOKE_ASYNC(src, PROC_REF(fly_away))
- else if(!(step_count % 2))
+ return
+
+ if (forced || (SSlag_switch.measures[DISABLE_FOOTSTEPS] && !(HAS_TRAIT(source, TRAIT_BYPASS_MEASURES))))
+ return
+
+ if(!(step_count % 2))
playsound(current_turf, 'sound/items/modsuit/atrocinator_step.ogg', 50)
step_count++
diff --git a/code/modules/mod/modules/modules_service.dm b/code/modules/mod/modules/modules_service.dm
index 70c11f069f09..9cf7c4702f70 100644
--- a/code/modules/mod/modules/modules_service.dm
+++ b/code/modules/mod/modules/modules_service.dm
@@ -91,3 +91,21 @@
REMOVE_TRAIT(mod.wearer, TRAIT_WADDLING, REF(src))
if(is_clown_job(mod.wearer.mind?.assigned_role))
mod.wearer.clear_mood_event("clownshoes")
+
+// recharging cleaner spray module
+/obj/item/mod/module/mister/cleaner
+ name = "MOD janitorial mister module"
+ desc = "An space cleaner mister, able to clean up messes quickly. Synthesizes its own supply over time (if active)."
+ device = /obj/item/reagent_containers/spray/mister/janitor
+ volume = 100
+ active_power_cost = DEFAULT_CHARGE_DRAIN
+
+/obj/item/mod/module/mister/cleaner/Initialize(mapload)
+ . = ..()
+ reagents.flags = AMOUNT_VISIBLE
+ reagents.add_reagent(/datum/reagent/space_cleaner, volume)
+
+/obj/item/mod/module/mister/cleaner/on_active_process(seconds_per_tick)
+ var/refill_add = min(volume - reagents.total_volume, 2 * seconds_per_tick)
+ if(refill_add > 0)
+ reagents.add_reagent(/datum/reagent/space_cleaner, refill_add)
diff --git a/code/modules/modular_computers/computers/item/computer.dm b/code/modules/modular_computers/computers/item/computer.dm
index 206d9501ab5e..be2f72375e3b 100644
--- a/code/modules/modular_computers/computers/item/computer.dm
+++ b/code/modules/modular_computers/computers/item/computer.dm
@@ -549,11 +549,11 @@
* The program calling this proc.
* The message that the program wishes to display.
*/
-/obj/item/modular_computer/proc/alert_call(datum/computer_file/program/caller, alerttext, sound = 'sound/machines/beep/twobeep_high.ogg')
- if(!caller || !caller.alert_able || caller.alert_silenced || !alerttext) //Yeah, we're checking alert_able. No, you don't get to make alerts that the user can't silence.
+/obj/item/modular_computer/proc/alert_call(datum/computer_file/program/call_source, alerttext, sound = 'sound/machines/beep/twobeep_high.ogg')
+ if(!call_source || !call_source.alert_able || call_source.alert_silenced || !alerttext) //Yeah, we're checking alert_able. No, you don't get to make alerts that the user can't silence.
return FALSE
playsound(src, sound, 50, TRUE)
- physical.loc.visible_message(span_notice("[icon2html(physical, viewers(physical.loc))] \The [src] displays a [caller.filedesc] notification: [alerttext]"))
+ physical.loc.visible_message(span_notice("[icon2html(physical, viewers(physical.loc))] \The [src] displays a [call_source.filedesc] notification: [alerttext]"))
/obj/item/modular_computer/proc/ring(ringtone, list/balloon_alertees) // bring bring
if(!use_energy())
diff --git a/code/modules/modular_computers/computers/item/laptop.dm b/code/modules/modular_computers/computers/item/laptop.dm
index 5053b6c6b2cb..343a09a980d3 100644
--- a/code/modules/modular_computers/computers/item/laptop.dm
+++ b/code/modules/modular_computers/computers/item/laptop.dm
@@ -29,6 +29,16 @@
if(screen_on)
. += span_notice("Alt-click to close it.")
+/obj/item/modular_computer/laptop/add_context(atom/source, list/context, obj/item/held_item, mob/living/user)
+ . = ..()
+ if(screen_on)
+ context[SCREENTIP_CONTEXT_ALT_LMB] = "Close"
+ context[SCREENTIP_CONTEXT_RMB] = "Interact"
+ else
+ context[SCREENTIP_CONTEXT_RMB] = "Open"
+
+ return CONTEXTUAL_SCREENTIP_SET
+
/obj/item/modular_computer/laptop/Initialize(mapload)
. = ..()
@@ -70,13 +80,6 @@
return
user.put_in_hand(src, H.held_index)
-/obj/item/modular_computer/laptop/attack_hand(mob/user, list/modifiers)
- . = ..()
- if(.)
- return
- if(screen_on && isturf(loc))
- return attack_self(user)
-
/obj/item/modular_computer/laptop/proc/try_toggle_open(mob/living/user)
if(issilicon(user))
return
@@ -94,6 +97,14 @@
try_toggle_open(user) // Close it.
return CLICK_ACTION_SUCCESS
+/obj/item/modular_computer/laptop/attack_hand_secondary(mob/user, list/modifiers)
+ . = ..()
+ if(. == SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN)
+ return
+
+ attack_self(user)
+ return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
+
/obj/item/modular_computer/laptop/proc/toggle_open(mob/living/user=null)
if(screen_on)
to_chat(user, span_notice("You close \the [src]."))
diff --git a/code/modules/modular_computers/file_system/programs/messenger/messenger_program.dm b/code/modules/modular_computers/file_system/programs/messenger/messenger_program.dm
index 83d2aeeb665d..0d6392bb51bb 100644
--- a/code/modules/modular_computers/file_system/programs/messenger/messenger_program.dm
+++ b/code/modules/modular_computers/file_system/programs/messenger/messenger_program.dm
@@ -717,7 +717,7 @@
reply = "(Reply)"
if (isAI(messaged_mob))
- sender_title = "[sender_title]"
+ sender_title = "[sender_title]"
var/inbound_message = "[signal.format_message()]"
diff --git a/code/modules/pai/hud.dm b/code/modules/pai/hud.dm
index 77bcafefc82d..cb7a5c9df390 100644
--- a/code/modules/pai/hud.dm
+++ b/code/modules/pai/hud.dm
@@ -2,6 +2,7 @@
/atom/movable/screen/pai
icon = 'icons/hud/screen_pai.dmi'
+ mouse_over_pointer = MOUSE_HAND_POINTER
var/required_software
/atom/movable/screen/pai/Click()
diff --git a/code/modules/paperwork/clipboard.dm b/code/modules/paperwork/clipboard.dm
index 8a5e9ea20040..7e29f9f831dd 100644
--- a/code/modules/paperwork/clipboard.dm
+++ b/code/modules/paperwork/clipboard.dm
@@ -92,14 +92,23 @@
/obj/item/clipboard/update_overlays()
. = ..()
- var/obj/item/paper/toppaper = toppaper_ref?.resolve()
- if(toppaper)
- . += toppaper.icon_state
- . += toppaper.overlays
+ var/paper_to_add = get_paper_overlay()
+ if(paper_to_add)
+ . += paper_to_add
if(!integrated_pen && pen) // NOVA EDIT - CARGO BORGS - ORIGINAL: if(pen)
. += "clipboard_pen"
. += "clipboard_over"
+/obj/item/clipboard/proc/get_paper_overlay()
+ var/obj/item/paper/toppaper = toppaper_ref?.resolve()
+ if(isnull(toppaper))
+ return
+
+ var/mutable_appearance/paper_overlay = mutable_appearance(icon, toppaper.icon_state, offset_spokesman = src, appearance_flags = KEEP_APART)
+ paper_overlay = toppaper.color_atom_overlay(paper_overlay)
+ paper_overlay.overlays += toppaper.overlays
+ return paper_overlay
+
/obj/item/clipboard/attack_hand(mob/user, list/modifiers)
if(LAZYACCESS(modifiers, RIGHT_CLICK))
var/obj/item/paper/toppaper = toppaper_ref?.resolve()
diff --git a/code/modules/paperwork/fax.dm b/code/modules/paperwork/fax.dm
index 6ad571896974..62a269d477e4 100644
--- a/code/modules/paperwork/fax.dm
+++ b/code/modules/paperwork/fax.dm
@@ -1,4 +1,5 @@
GLOBAL_VAR_INIT(nt_fax_department, pick("NT HR Department", "NT Legal Department", "NT Complaint Department", "NT Customer Relations", "Nanotrasen Tech Support", "NT Internal Affairs Dept"))
+GLOBAL_VAR_INIT(fax_autoprinting, FALSE)
/obj/machinery/fax
name = "Fax Machine"
@@ -325,11 +326,22 @@ GLOBAL_VAR_INIT(nt_fax_department, pick("NT HR Department", "NT Legal Department
history_add("Send", params["name"])
- GLOB.requests.fax_request(usr.client, "sent a fax message from [fax_name]/[fax_id] to [params["name"]]", fax_paper)
- to_chat(GLOB.admins, span_adminnotice("[icon2html(src.icon, GLOB.admins)]FAX REQUEST: [ADMIN_FULLMONTY(usr)]: [span_linkify("sent a fax message from [fax_name]/[fax_id][ADMIN_FLW(src)] to [html_encode(params["name"])]")] [ADMIN_SHOW_PAPER(fax_paper)] [ADMIN_PRINT_FAX(fax_paper, fax_name, params["id"])]"), confidential = TRUE)
+ GLOB.requests.fax_request(usr.client, "sent a fax message from [fax_name]/[fax_id] to [params["name"]]", list("paper" = fax_paper, "destination_id" = params["id"], "sender_name" = fax_name))
+ to_chat(GLOB.admins,
+ span_adminnotice("[icon2html(src.icon, GLOB.admins)]FAX REQUEST: [ADMIN_FULLMONTY(usr)]: [span_linkify("sent a fax message from [fax_name]/[fax_id][ADMIN_FLW(src)] to [html_encode(params["name"])]")] [ADMIN_SHOW_PAPER(fax_paper)] [ADMIN_PRINT_FAX(fax_paper, fax_name, params["id"])]"),
+ type = MESSAGE_TYPE_PRAYER,
+ confidential = TRUE)
for(var/client/staff as anything in GLOB.admins)
if(staff?.prefs.read_preference(/datum/preference/toggle/comms_notification))
SEND_SOUND(staff, sound('sound/misc/server-ready.ogg'))
+
+ if(GLOB.fax_autoprinting)
+ for(var/obj/machinery/fax/admin/FAX as anything in SSmachines.get_machines_by_type_and_subtypes(/obj/machinery/fax/admin))
+ if(FAX.fax_id != params["id"])
+ continue
+ FAX.receive(fax_paper, fax_name)
+ break
+
log_fax(fax_paper, params["id"], params["name"])
loaded_item_ref = null
update_appearance()
diff --git a/code/modules/paperwork/paper.dm b/code/modules/paperwork/paper.dm
index 4ef8ee396f78..7a027eb8c759 100644
--- a/code/modules/paperwork/paper.dm
+++ b/code/modules/paperwork/paper.dm
@@ -162,7 +162,7 @@
new_paper.raw_stamp_data = copy_raw_stamps()
new_paper.stamp_cache = stamp_cache?.Copy()
new_paper.update_icon_state()
- copy_overlays(new_paper, TRUE)
+ new_paper.copy_overlays(src)
return new_paper
/**
@@ -278,9 +278,9 @@
if(LAZYLEN(stamp_cache) > MAX_PAPER_STAMPS_OVERLAYS)
return
- var/mutable_appearance/stamp_overlay = mutable_appearance('icons/obj/service/bureaucracy.dmi', "paper_[stamp_icon_state]")
- stamp_overlay.pixel_x = rand(-2, 2)
- stamp_overlay.pixel_y = rand(-3, 2)
+ var/mutable_appearance/stamp_overlay = mutable_appearance('icons/obj/service/bureaucracy.dmi', "paper_[stamp_icon_state]", appearance_flags = KEEP_APART | RESET_COLOR)
+ stamp_overlay.pixel_w = rand(-2, 2)
+ stamp_overlay.pixel_z = rand(-3, 2)
add_overlay(stamp_overlay)
LAZYADD(stamp_cache, stamp_icon_state)
@@ -306,6 +306,8 @@
/obj/item/paper/update_icon_state()
if(LAZYLEN(raw_text_inputs) && show_written_words)
icon_state = "[initial(icon_state)]_words"
+ else
+ icon_state = initial(icon_state)
return ..()
/obj/item/paper/verb/rename()
@@ -394,7 +396,7 @@
* * plane_type - what it will be folded into (path)
*/
/obj/item/paper/proc/make_plane(mob/living/user, plane_type = /obj/item/paperplane)
- balloon_alert(user, "folded into a plane")
+ loc.balloon_alert(user, "folded into a plane")
user.temporarilyRemoveItemFromInventory(src)
var/obj/item/paperplane/new_plane = new plane_type(loc, src)
if(user.Adjacent(new_plane))
diff --git a/code/modules/paperwork/pen.dm b/code/modules/paperwork/pen.dm
index 60c6aeb4dfef..ff113e2b2cd5 100644
--- a/code/modules/paperwork/pen.dm
+++ b/code/modules/paperwork/pen.dm
@@ -11,12 +11,13 @@
* Pens
*/
/obj/item/pen
- desc = "It's a normal black ink pen."
name = "pen"
+ desc = "It's a normal black ink pen."
icon = 'icons/obj/service/bureaucracy.dmi'
icon_state = "pen"
inhand_icon_state = "pen"
worn_icon_state = "pen"
+ icon_angle = -135
slot_flags = ITEM_SLOT_BELT | ITEM_SLOT_EARS
throwforce = 0
w_class = WEIGHT_CLASS_TINY
@@ -304,8 +305,8 @@
* (Alan) Edaggers
*/
/obj/item/pen/edagger
- attack_verb_continuous = list("slashes", "stabs", "slices", "tears", "lacerates", "rips", "dices", "cuts") //these won't show up if the pen is off
- attack_verb_simple = list("slash", "stab", "slice", "tear", "lacerate", "rip", "dice", "cut")
+ attack_verb_continuous = list("slashes", "slices", "tears", "lacerates", "rips", "dices", "cuts") //these won't show up if the pen is off
+ attack_verb_simple = list("slash", "slice", "tear", "lacerate", "rip", "dice", "cut")
sharpness = SHARP_POINTY
armour_penetration = 20
bare_wound_bonus = 10
@@ -322,9 +323,14 @@
var/hidden_desc = "It's a normal black ink pe- Wait. That's a thing used to stab people!"
/// The real icons used when extended.
var/hidden_icon = "edagger"
+ var/list/alt_continuous = list("stabs", "pierces", "shanks")
+ var/list/alt_simple = list("stab", "pierce", "shank")
/obj/item/pen/edagger/Initialize(mapload)
. = ..()
+ alt_continuous = string_list(alt_continuous)
+ alt_simple = string_list(alt_simple)
+ AddComponent(/datum/component/alternative_sharpness, SHARP_POINTY, alt_continuous, alt_simple, -5, TRAIT_TRANSFORM_ACTIVE)
AddComponent(/datum/component/butchering, \
speed = 6 SECONDS, \
butcher_sound = 'sound/items/weapons/blade1.ogg', \
diff --git a/code/modules/plumbing/plumbers/_plumb_machinery.dm b/code/modules/plumbing/plumbers/_plumb_machinery.dm
index 33c063bbfed2..f16091cc21b2 100644
--- a/code/modules/plumbing/plumbers/_plumb_machinery.dm
+++ b/code/modules/plumbing/plumbers/_plumb_machinery.dm
@@ -102,117 +102,3 @@
user.balloon_alert_to_viewers("finished plunging")
reagents.expose(get_turf(src), TOUCH) //splash on the floor
reagents.clear_reagents()
-
-/**
- * Specialized reagent container for plumbing. Uses the round robin approach of transferring reagents
- * so transfer 5 from 15 water, 15 sugar and 15 plasma becomes 10, 15, 15 instead of 13.3333, 13.3333 13.3333. Good if you hate floating point errors
- */
-/datum/reagents/plumbing
-
-/**
- * Same as the parent trans_to except only a few arguments have impact here & the rest of the arguments are discarded.
- * Arguments
- *
- * * atom/target - the target we are transfering to
- * * amount - amount to transfer
- * * datum/reagent/target_id - the reagent id we want to transfer. if null everything gets transfered
- * * methods - this is key for deciding between round-robin or proportional transfer. It does not mean the same as the
- * parent proc. LINEAR for round robin(in this technique reagents are missing/lost/not preserved when there isn't enough space to hold them)
- * NONE means everything is transfered regardless of how much space is available in the receiver in proportions
- */
-/datum/reagents/plumbing/trans_to(
- atom/target,
- amount = 1,
- multiplier = 1, //unused for plumbing
- datum/reagent/target_id,
- preserve_data = TRUE, //unused for plumbing
- no_react = FALSE, //unused for plumbing we always want reactions
- mob/transferred_by, //unused for plumbing logging is not important inside plumbing machines
- remove_blacklisted = FALSE, //unused for plumbing, we don't care what reagents are inside us
- methods = LINEAR, //default round robin technique for transferring reagents
- show_message = TRUE, //unused for plumbing, used for logging only
- ignore_stomach = FALSE //unused for plumbing, reagents flow only between machines & is not injected to mobs at any point in time
-)
- if(QDELETED(target) || !total_volume)
- return FALSE
-
- if(!IS_FINITE(amount))
- stack_trace("non finite amount passed to trans_to [amount] amount of reagents")
- return FALSE
-
- if(!isnull(target_id) && !ispath(target_id))
- stack_trace("invalid target reagent id [target_id] passed to trans_to")
- return FALSE
-
- var/datum/reagents/target_holder
- if(istype(target, /datum/reagents))
- target_holder = target
- else
- target_holder = target.reagents
-
- // Prevents small amount problems, as well as zero and below zero amounts.
- amount = round(min(amount, total_volume, target_holder.maximum_volume - target_holder.total_volume), CHEMICAL_QUANTISATION_LEVEL)
- if(amount <= 0)
- return FALSE
-
- //Set up new reagents to inherit the old ongoing reactions
- transfer_reactions(target_holder)
-
- var/list/cached_reagents = reagent_list
- var/list/reagents_to_remove = list()
- var/transfer_amount
- var/transfered_amount
- var/total_transfered_amount = 0
-
- var/round_robin = methods & LINEAR
- var/part
- var/to_transfer
- if(round_robin)
- to_transfer = amount
- else
- part = amount / total_volume
-
- //first add reagents to target
- for(var/datum/reagent/reagent as anything in cached_reagents)
- if(round_robin && !to_transfer)
- break
-
- if(!isnull(target_id))
- if(reagent.type == target_id)
- force_stop_reagent_reacting(reagent)
- transfer_amount = min(amount, reagent.volume)
- else
- continue
- else
- if(round_robin)
- transfer_amount = min(to_transfer, reagent.volume)
- else
- transfer_amount = reagent.volume * part
-
- if(reagent.intercept_reagents_transfer(target_holder, amount))
- update_total()
- target_holder.update_total()
- continue
-
- transfered_amount = target_holder.add_reagent(reagent.type, transfer_amount, copy_data(reagent), chem_temp, reagent.purity, reagent.ph, no_react = TRUE, ignore_splitting = reagent.chemical_flags & REAGENT_DONOTSPLIT) //we only handle reaction after every reagent has been transferred.
- if(!transfered_amount)
- continue
- reagents_to_remove += list(list("R" = reagent, "T" = transfer_amount))
- total_transfered_amount += transfered_amount
- if(round_robin)
- to_transfer -= transfered_amount
-
- if(!isnull(target_id))
- break
-
- //remove chemicals that were added above
- for(var/list/data as anything in reagents_to_remove)
- var/datum/reagent/reagent = data["R"]
- transfer_amount = data["T"]
- remove_reagent(reagent.type, transfer_amount)
-
- //handle reactions
- target_holder.handle_reactions()
- src.handle_reactions()
-
- return round(total_transfered_amount, CHEMICAL_VOLUME_ROUNDING)
diff --git a/code/modules/plumbing/plumbers/_plumb_reagents.dm b/code/modules/plumbing/plumbers/_plumb_reagents.dm
new file mode 100644
index 000000000000..90954ca4985a
--- /dev/null
+++ b/code/modules/plumbing/plumbers/_plumb_reagents.dm
@@ -0,0 +1,268 @@
+
+/**
+ * Specialized reagent container for plumbing. Uses the round robin approach of transferring reagents
+ * so transfer 5 from 15 water, 15 sugar and 15 plasma becomes 10, 15, 15 instead of 13.3333, 13.3333 13.3333. Good if you hate floating point errors
+ */
+/datum/reagents/plumbing
+
+/**
+ * Same as the parent trans_to except only a few arguments have impact here & the rest of the arguments are discarded.
+ * Arguments
+ *
+ * * atom/target - the target we are transfering to
+ * * amount - amount to transfer
+ * * datum/reagent/target_id - the reagent id we want to transfer. if null everything gets transfered
+ * * methods - this is key for deciding between round-robin or proportional transfer. It does not mean the same as the
+ * parent proc. LINEAR for round robin(in this technique reagents are missing/lost/not preserved when there isn't enough space to hold them)
+ * NONE means everything is transfered regardless of how much space is available in the receiver in proportions
+ */
+/datum/reagents/plumbing/trans_to(
+ atom/target,
+ amount = 1,
+ multiplier = 1, //unused for plumbing
+ datum/reagent/target_id,
+ preserve_data = TRUE, //unused for plumbing
+ no_react = FALSE, //unused for plumbing we always want reactions
+ mob/transferred_by, //unused for plumbing logging is not important inside plumbing machines
+ remove_blacklisted = FALSE, //unused for plumbing, we don't care what reagents are inside us
+ methods = LINEAR, //default round robin technique for transferring reagents
+ show_message = TRUE, //unused for plumbing, used for logging only
+ ignore_stomach = FALSE //unused for plumbing, reagents flow only between machines & is not injected to mobs at any point in time
+)
+ if(QDELETED(target) || !total_volume)
+ return FALSE
+
+ if(!IS_FINITE(amount))
+ stack_trace("non finite amount passed to trans_to [amount] amount of reagents")
+ return FALSE
+
+ if(!isnull(target_id) && !ispath(target_id))
+ stack_trace("invalid target reagent id [target_id] passed to trans_to")
+ return FALSE
+
+ var/datum/reagents/target_holder
+ if(istype(target, /datum/reagents))
+ target_holder = target
+ else
+ target_holder = target.reagents
+
+ // Prevents small amount problems, as well as zero and below zero amounts.
+ amount = round(min(amount, total_volume, target_holder.maximum_volume - target_holder.total_volume), CHEMICAL_QUANTISATION_LEVEL)
+ if(amount <= 0)
+ return FALSE
+
+ //Set up new reagents to inherit the old ongoing reactions
+ transfer_reactions(target_holder)
+
+ var/list/cached_reagents = reagent_list
+ var/list/reagents_to_remove = list()
+ var/transfer_amount
+ var/transfered_amount
+ var/total_transfered_amount = 0
+
+ var/round_robin = methods & LINEAR
+ var/part
+ var/to_transfer
+ if(round_robin)
+ to_transfer = amount
+ else
+ part = amount / total_volume
+
+ //first add reagents to target
+ for(var/datum/reagent/reagent as anything in cached_reagents)
+ if(round_robin && !to_transfer)
+ break
+
+ if(!isnull(target_id))
+ if(reagent.type == target_id)
+ force_stop_reagent_reacting(reagent)
+ transfer_amount = min(amount, reagent.volume)
+ else
+ continue
+ else
+ if(round_robin)
+ transfer_amount = min(to_transfer, reagent.volume)
+ else
+ transfer_amount = reagent.volume * part
+
+ if(reagent.intercept_reagents_transfer(target_holder, amount))
+ update_total()
+ target_holder.update_total()
+ continue
+
+ transfered_amount = target_holder.add_reagent(reagent.type, transfer_amount, copy_data(reagent), chem_temp, reagent.purity, reagent.ph, no_react = TRUE, ignore_splitting = reagent.chemical_flags & REAGENT_DONOTSPLIT) //we only handle reaction after every reagent has been transferred.
+ if(!transfered_amount)
+ continue
+ reagents_to_remove += list(list("R" = reagent, "T" = transfer_amount))
+ total_transfered_amount += transfered_amount
+ if(round_robin)
+ to_transfer -= transfered_amount
+
+ if(!isnull(target_id))
+ break
+
+ //remove chemicals that were added above
+ for(var/list/data as anything in reagents_to_remove)
+ var/datum/reagent/reagent = data["R"]
+ transfer_amount = data["T"]
+ remove_reagent(reagent.type, transfer_amount)
+
+ //handle reactions
+ target_holder.handle_reactions()
+ src.handle_reactions()
+
+ return round(total_transfered_amount, CHEMICAL_VOLUME_ROUNDING)
+
+///Excludes catalysts during the emptying process
+/datum/reagents/plumbing/reaction_chamber
+
+///Returns the total volume of reagents without the catalysts
+/datum/reagents/plumbing/reaction_chamber/proc/get_catalyst_excluded_volume()
+ SHOULD_NOT_OVERRIDE(TRUE)
+
+ . = 0
+
+ //no reagents
+ if(!total_volume)
+ return
+
+ var/obj/machinery/plumbing/reaction_chamber/reactor = my_atom
+ var/list/datum/reagent/catalysts = reactor.catalysts
+
+ //no catalysts
+ if(!catalysts.len)
+ return total_volume
+
+ //filter out catalysts except when we have excess of them
+ var/working_volume
+ var/catalyst_volume
+ var/list/cached_reagents = reagent_list
+ for(var/datum/reagent/reagent as anything in cached_reagents)
+ catalyst_volume = catalysts[reagent.type]
+ working_volume = reagent.volume
+
+ //regular reagent add to total as normal
+ if(!catalyst_volume)
+ . += working_volume
+ continue
+
+ //only add the excess to total as that's what will get transferred
+ if(working_volume > catalyst_volume)
+ . += working_volume - catalyst_volume
+ . = min(round(., CHEMICAL_VOLUME_ROUNDING), maximum_volume)
+
+/datum/reagents/plumbing/reaction_chamber/trans_to(
+ atom/target,
+ amount = 1,
+ multiplier = 1,
+ datum/reagent/target_id,
+ preserve_data = TRUE,
+ no_react = FALSE,
+ mob/transferred_by,
+ remove_blacklisted = FALSE,
+ methods = LINEAR,
+ show_message = TRUE,
+ ignore_stomach = FALSE
+)
+ var/obj/machinery/plumbing/reaction_chamber/reactor = my_atom
+ var/list/datum/reagent/catalysts = reactor.catalysts
+
+ //usual stuff
+ if(!catalysts.len)
+ return ..()
+
+ if(QDELETED(target))
+ return FALSE
+
+ if(!IS_FINITE(amount))
+ stack_trace("non finite amount passed to trans_to [amount] amount of reagents")
+ return FALSE
+
+ if(!isnull(target_id) && !ispath(target_id))
+ stack_trace("invalid target reagent id [target_id] passed to trans_to")
+ return FALSE
+
+ var/datum/reagents/target_holder
+ if(istype(target, /datum/reagents))
+ target_holder = target
+ else
+ target_holder = target.reagents
+ var/list/cached_reagents = reagent_list
+
+ var/actual_volume = get_catalyst_excluded_volume()
+
+ // Prevents small amount problems, as well as zero and below zero amounts.
+ amount = round(min(amount, actual_volume, target_holder.maximum_volume - target_holder.total_volume), CHEMICAL_QUANTISATION_LEVEL)
+ if(amount <= 0)
+ return FALSE
+
+ //Set up new reagents to inherit the old ongoing reactions
+ transfer_reactions(target_holder)
+
+ var/list/reagents_to_remove = list()
+ var/working_volume
+ var/catalyst_volume
+ var/transfer_amount
+ var/transfered_amount
+ var/total_transfered_amount = 0
+
+ var/round_robin = methods & LINEAR
+ var/part
+ var/to_transfer
+ if(round_robin)
+ to_transfer = amount
+ else
+ part = amount / actual_volume
+
+ //first add reagents to target
+ for(var/datum/reagent/reagent as anything in cached_reagents)
+ if(round_robin && !to_transfer)
+ break
+ working_volume = reagent.volume
+
+ catalyst_volume = catalysts[reagent.type]
+ if(catalyst_volume) //we have a working catalyst
+ if(reagent.volume <= catalyst_volume) //dont transfer since we have the required volume
+ continue
+ else
+ working_volume -= catalyst_volume //dump out the excess
+
+ if(!isnull(target_id))
+ if(reagent.type == target_id)
+ force_stop_reagent_reacting(reagent)
+ transfer_amount = min(amount, working_volume)
+ else
+ continue
+ else
+ if(round_robin)
+ transfer_amount = min(to_transfer, working_volume)
+ else
+ transfer_amount = working_volume * part
+
+ if(reagent.intercept_reagents_transfer(target_holder, amount))
+ update_total()
+ target_holder.update_total()
+ continue
+
+ transfered_amount = target_holder.add_reagent(reagent.type, transfer_amount, copy_data(reagent), chem_temp, reagent.purity, reagent.ph, no_react = TRUE, ignore_splitting = reagent.chemical_flags & REAGENT_DONOTSPLIT) //we only handle reaction after every reagent has been transferred.
+ if(!transfered_amount)
+ continue
+ reagents_to_remove += list(list("R" = reagent, "T" = transfer_amount))
+ total_transfered_amount += transfered_amount
+ if(round_robin)
+ to_transfer -= transfered_amount
+
+ if(!isnull(target_id))
+ break
+
+ //remove chemicals that were added above
+ for(var/list/data as anything in reagents_to_remove)
+ var/datum/reagent/reagent = data["R"]
+ transfer_amount = data["T"]
+ remove_reagent(reagent.type, transfer_amount)
+
+ //handle reactions
+ target_holder.handle_reactions()
+ src.handle_reactions()
+
+ return round(total_transfered_amount, CHEMICAL_VOLUME_ROUNDING)
diff --git a/code/modules/plumbing/plumbers/reaction_chamber.dm b/code/modules/plumbing/plumbers/reaction_chamber.dm
index 9828c9e697f8..551b95fc0b7d 100644
--- a/code/modules/plumbing/plumbers/reaction_chamber.dm
+++ b/code/modules/plumbing/plumbers/reaction_chamber.dm
@@ -9,19 +9,23 @@
icon_state = "reaction_chamber"
buffer = 200
reagent_flags = TRANSPARENT | NO_REACT
+ reagents = /datum/reagents/plumbing/reaction_chamber
/**
* list of set reagents that the reaction_chamber allows in, and must all be present before mixing is enabled.
* example: list(/datum/reagent/water = 20, /datum/reagent/fuel/oil = 50)
*/
- var/list/required_reagents = list()
-
+ var/list/datum/reagent/required_reagents = list()
+ ///list of catalyst reagents to take
+ var/list/datum/reagent/catalysts = list()
///our reagent goal has been reached, so now we lock our inputs and start emptying
var/emptying = FALSE
-
///towards which temperature do we build (except during draining)?
var/target_temperature = 300
+/obj/machinery/plumbing/reaction_chamber/Destroy()
+ return ..()
+
/obj/machinery/plumbing/reaction_chamber/Initialize(mapload, bolt, layer)
. = ..()
AddComponent(/datum/component/plumbing/reaction_chamber, bolt, layer)
@@ -36,15 +40,17 @@
SIGNAL_HANDLER
UnregisterSignal(reagents, list(COMSIG_REAGENTS_REM_REAGENT, COMSIG_REAGENTS_DEL_REAGENT, COMSIG_REAGENTS_CLEAR_REAGENTS, COMSIG_REAGENTS_REACTED, COMSIG_QDELETING))
+
return NONE
/// Handles stopping the emptying process when the chamber empties.
-/obj/machinery/plumbing/reaction_chamber/proc/on_reagent_change(datum/reagents/holder, ...)
+/obj/machinery/plumbing/reaction_chamber/proc/on_reagent_change(datum/reagents/plumbing/reaction_chamber/holder, ...)
SIGNAL_HANDLER
- if(!holder.total_volume && emptying) //we were emptying, but now we aren't
+ if(!holder.get_catalyst_excluded_volume() && emptying) //we were emptying, but now we aren't
emptying = FALSE
holder.flags |= NO_REACT
+
return NONE
/obj/machinery/plumbing/reaction_chamber/process(seconds_per_tick)
@@ -60,8 +66,15 @@
//do other stuff with final solution
handle_reagents(seconds_per_tick)
-///For subtypes that want to do additional reagent handling
+/**
+ * For subtypes that want to do additional reagent handling
+ * Arguments
+ *
+ * * seconds_per_tick - passed down from process()
+ */
/obj/machinery/plumbing/reaction_chamber/proc/handle_reagents(seconds_per_tick)
+ PROTECTED_PROC(TRUE)
+
return
/obj/machinery/plumbing/reaction_chamber/power_change()
@@ -81,11 +94,21 @@
var/list/reagents_data = list()
for(var/datum/reagent/required_reagent as anything in required_reagents) //make a list where the key is text, because that looks alot better in the ui than a typepath
var/list/reagent_data = list()
+ if(catalysts[required_reagent])
+ continue
reagent_data["name"] = initial(required_reagent.name)
reagent_data["volume"] = required_reagents[required_reagent]
reagents_data += list(reagent_data)
+ var/list/catalyst_data = list()
+ for(var/datum/reagent/required_catalyst as anything in catalysts)
+ var/list/reagent_data = list()
+ reagent_data["name"] = initial(required_catalyst.name)
+ reagent_data["volume"] = catalysts[required_catalyst]
+ catalyst_data += list(reagent_data)
+
.["reagents"] = reagents_data
+ .["catalysts"] = catalyst_data
.["emptying"] = emptying
.["temperature"] = round(reagents.chem_temp, 0.1)
.["targetTemp"] = target_temperature
@@ -108,9 +131,9 @@
if(!input_reagent)
return FALSE
- if(!required_reagents.Find(input_reagent))
+ if(!required_reagents[input_reagent])
var/input_amount = text2num(params["amount"])
- if(!isnull(input_amount))
+ if(input_amount)
required_reagents[input_reagent] = input_amount
return TRUE
return FALSE
@@ -129,6 +152,25 @@
return TRUE
return FALSE
+ if("catalyst")
+ var/reagent = get_chem_id(params["chem"])
+
+ if(!reagent)
+ return FALSE
+
+ if(reagent && !catalysts[reagent])
+ catalysts[reagent] = required_reagents[reagent]
+ return TRUE
+ else
+ return FALSE
+
+ if("catremove")
+ var/reagent = get_chem_id(params["chem"])
+ if(reagent)
+ catalysts -= reagent
+ return TRUE
+ return FALSE
+
var/result = handle_ui_act(action, params, ui, state)
if(isnull(result))
result = FALSE
@@ -136,6 +178,8 @@
/// For custom handling of ui actions from inside a subtype
/obj/machinery/plumbing/reaction_chamber/proc/handle_ui_act(action, params, datum/tgui/ui, datum/ui_state/state)
+ PROTECTED_PROC(TRUE)
+
return null
///Chemistry version of reaction chamber that allows for acid and base buffers to be used while reacting
diff --git a/code/modules/point/point.dm b/code/modules/point/point.dm
index 683710bf128e..98574373a816 100644
--- a/code/modules/point/point.dm
+++ b/code/modules/point/point.dm
@@ -122,7 +122,15 @@
if(!(pointing_at in view(client.view, src)))
return FALSE
-
+ if(iscarbon(src)) // special interactions for carbons
+ var/mob/living/carbon/our_carbon = src
+ if(our_carbon.usable_hands <= 0 || src.incapacitated & INCAPABLE_RESTRAINTS || HAS_TRAIT(src, TRAIT_HANDS_BLOCKED))
+ if(TIMER_COOLDOWN_FINISHED(src, "point_verb_emote_cooldown"))
+ //cooldown handled in the emote.
+ our_carbon.emote("point [pointing_at]")
+ else
+ to_chat(src, span_warning("You need to wait before pointing again!"))
+ return FALSE
point_at(pointing_at, TRUE)
return TRUE
diff --git a/code/modules/power/floodlight.dm b/code/modules/power/floodlight.dm
index 5b9d983cf1dd..7db89987d2d4 100644
--- a/code/modules/power/floodlight.dm
+++ b/code/modules/power/floodlight.dm
@@ -173,6 +173,8 @@
var/light_color = NONSENSICAL_VALUE
if(!isnull(color))
light_color = color
+ if (cached_color_filter)
+ light_color = apply_matrix_to_color(COLOR_WHITE, cached_color_filter["color"], cached_color_filter["space"] || COLORSPACE_RGB)
set_light(light_setting_list[setting], light_power, light_color)
/obj/machinery/power/floodlight/add_context(
diff --git a/code/modules/power/lighting/light.dm b/code/modules/power/lighting/light.dm
index 6a704ad6b2e2..e6594bd93781 100644
--- a/code/modules/power/lighting/light.dm
+++ b/code/modules/power/lighting/light.dm
@@ -99,7 +99,7 @@
continue
if(on_turf.dir != dir)
continue
- stack_trace("Conflicting double stacked light [on_turf.type] found at ([our_location.x],[our_location.y],[our_location.z])")
+ stack_trace("Conflicting double stacked light [on_turf.type] found at [get_area(our_location)] ([our_location.x],[our_location.y],[our_location.z])")
qdel(on_turf)
if(!mapload) //sync up nightshift lighting for player made lights
@@ -231,6 +231,8 @@
var/color_set = bulb_colour
if(color)
color_set = color
+ if (cached_color_filter)
+ color_set = apply_matrix_to_color(color_set, cached_color_filter["color"], cached_color_filter["space"] || COLORSPACE_RGB)
if(reagents)
START_PROCESSING(SSmachines, src)
var/area/local_area = get_room_area()
@@ -628,15 +630,13 @@
else if(istype(user) && user.dna.check_mutation(/datum/mutation/human/telekinesis))
to_chat(user, span_notice("You telekinetically remove the light [fitting]."))
else
- var/obj/item/bodypart/affecting = user.get_bodypart("[(user.active_hand_index % 2 == 0) ? "r" : "l" ]_arm")
- if(affecting?.receive_damage( 0, 5 )) // 5 burn damage
- user.update_damage_overlays()
+ var/obj/item/bodypart/affecting = user.get_active_hand()
+ user.apply_damage(5, BURN, affecting, wound_bonus = CANT_WOUND)
if(HAS_TRAIT(user, TRAIT_LIGHTBULB_REMOVER))
- to_chat(user, span_notice("You feel your [affecting] burning, and the light beginning to budge."))
+ to_chat(user, span_notice("You feel your [affecting.plaintext_zone] burning, but the light begins to budge..."))
if(!do_after(user, 5 SECONDS, target = src))
return
- if(affecting?.receive_damage( 0, 10 )) // 10 more burn damage
- user.update_damage_overlays()
+ user.apply_damage(10, BURN, user.get_active_hand(), wound_bonus = CANT_WOUND)
to_chat(user, span_notice("You manage to remove the light [fitting], shattering it in process."))
break_light_tube()
else
diff --git a/code/modules/power/lighting/light_items.dm b/code/modules/power/lighting/light_items.dm
index 357507d0aa4f..a5b40e0534ff 100644
--- a/code/modules/power/lighting/light_items.dm
+++ b/code/modules/power/lighting/light_items.dm
@@ -55,6 +55,7 @@
icon_state = "ltube"
base_state = "ltube"
inhand_icon_state = "ltube"
+ icon_angle = -45
brightness = 8
custom_price = PAYCHECK_CREW * 0.5
@@ -75,6 +76,7 @@
desc = "A replacement light bulb."
icon_state = "lbulb"
base_state = "lbulb"
+ icon_angle = -90
inhand_icon_state = "contvapour"
lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi'
righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi'
diff --git a/code/modules/power/power_store.dm b/code/modules/power/power_store.dm
index 0ca9ba226f44..3c2ce20be0ff 100644
--- a/code/modules/power/power_store.dm
+++ b/code/modules/power/power_store.dm
@@ -201,7 +201,7 @@
. = ..()
if(rigged)
. += span_danger("This [name] seems to be faulty!")
- else
+ else if(!isnull(charge_light_type))
. += "The charge meter reads [CEILING(percent(), 0.1)]%." //so it doesn't say 0% charge when the overlay indicates it still has charge
/obj/item/stock_parts/power_store/proc/on_reagent_change(datum/reagents/holder, ...)
diff --git a/code/modules/power/solar.dm b/code/modules/power/solar.dm
index 9eaa0fe2fedd..d6ec7a25cecc 100644
--- a/code/modules/power/solar.dm
+++ b/code/modules/power/solar.dm
@@ -170,7 +170,7 @@
// actually flip to other direction?
if(abs(angle - azimuth_current) > 180)
- mid_azimuth = reverse_angle(mid_azimuth)
+ mid_azimuth = REVERSE_ANGLE(mid_azimuth)
// Split into 2 parts so it doesn't distort on large changes
animate(part,
diff --git a/code/modules/power/tracker.dm b/code/modules/power/tracker.dm
index 0855e63ed4bf..4b103080b16e 100644
--- a/code/modules/power/tracker.dm
+++ b/code/modules/power/tracker.dm
@@ -90,7 +90,7 @@
// actually flip to other direction?
if(abs(angle - azimuth_current) > 180)
- mid_azimuth = reverse_angle(mid_azimuth)
+ mid_azimuth = REVERSE_ANGLE(mid_azimuth)
// Split into 2 parts so it doesn't distort on large changes
animate(part,
diff --git a/code/modules/power/turbine/turbine.dm b/code/modules/power/turbine/turbine.dm
index e839800158f6..bdb91cd8d2f4 100644
--- a/code/modules/power/turbine/turbine.dm
+++ b/code/modules/power/turbine/turbine.dm
@@ -1,16 +1,17 @@
+///Minimum pressure of gases pumped through the turbine
#define MINIMUM_TURBINE_PRESSURE 0.01
+///Returns the minimum pressure if it falls below the value
#define PRESSURE_MAX(value)(max((value), MINIMUM_TURBINE_PRESSURE))
/obj/machinery/power/turbine
+ icon = 'icons/obj/machines/engine/turbine.dmi'
density = TRUE
resistance_flags = FIRE_PROOF
can_atmos_pass = ATMOS_PASS_DENSITY
- processing_flags = NONE
+ processing_flags = START_PROCESSING_MANUALLY
- ///Checks if the machine is processing or not
- var/active = FALSE
- ///The parts can be registered on the main one only when their panel is closed
- var/can_connect = TRUE
+ ///The cached efficiency of this turbines installed part
+ var/efficiency = 0
///Reference to our turbine part
var/obj/item/turbine_parts/installed_part
///Path of the turbine part we can install
@@ -18,15 +19,6 @@
///The gas mixture this turbine part is storing
var/datum/gas_mixture/machine_gasmix
- ///Our overlay when active
- var/active_overlay = ""
- ///Our overlay when off
- var/off_overlay = ""
- ///Our overlay when open
- var/open_overlay = ""
- ///Should we use emissive appearance?
- var/emissive = FALSE
-
/obj/machinery/power/turbine/Initialize(mapload, gas_theoretical_volume)
. = ..()
@@ -35,16 +27,17 @@
if(mapload)
installed_part = new part_path(src)
+ efficiency = installed_part.get_tier_value(TURBINE_MAX_EFFICIENCY)
air_update_turf(TRUE)
- update_appearance()
+ update_appearance(UPDATE_OVERLAYS)
register_context()
-
/obj/machinery/power/turbine/post_machine_initialize()
. = ..()
+
activate_parts()
/obj/machinery/power/turbine/Destroy()
@@ -59,40 +52,9 @@
deactivate_parts()
return ..()
-/**
- * Handles all the calculations needed for the gases, work done, temperature increase/decrease
- *
- * Arguments
- * * datum/gas_mixture/input_mix - the gas from the environment or from another part of the turbine
- * * datum/gas_mixture/output_mix - the gas that got pumped into this part from the input mix.
- * ideally should be same as input mix but varying texmperatur & pressures can cause varying results
- * * work_amount_to_remove - the amount of work to subtract from the actual work done to pump in the input mixture.
- * For e.g. if gas was transfered from the inlet compressor to the rotor we want to subtract the work done
- * by the inlet from the rotor to get the true work done
- * * intake_size - the percentage of gas to be fed into an turbine part, controlled by turbine computer for inlet compressor only
- */
-/obj/machinery/power/turbine/proc/transfer_gases(datum/gas_mixture/input_mix, datum/gas_mixture/output_mix, work_amount_to_remove, intake_size = 1)
- //pump gases. if no gases were transferred then no work was done
- var/output_pressure = PRESSURE_MAX(output_mix.return_pressure())
- var/datum/gas_mixture/transferred_gases = input_mix.pump_gas_to(output_mix, input_mix.return_pressure() * intake_size)
- if(!transferred_gases)
- return 0
-
- //compute work done
- var/work_done = QUANTIZE(transferred_gases.total_moles()) * R_IDEAL_GAS_EQUATION * transferred_gases.temperature * log((transferred_gases.volume * PRESSURE_MAX(transferred_gases.return_pressure())) / (output_mix.volume * output_pressure)) * TURBINE_WORK_CONVERSION_MULTIPLIER
- if(work_amount_to_remove)
- work_done = work_done - work_amount_to_remove
-
- //compute temperature & work from temperature if that is a lower value
- var/output_mix_heat_capacity = output_mix.heat_capacity()
- if(!output_mix_heat_capacity)
- return 0
- work_done = min(work_done, (output_mix_heat_capacity * output_mix.temperature - output_mix_heat_capacity * TCMB) / TURBINE_HEAT_CONVERSION_MULTIPLIER)
- output_mix.temperature = max((output_mix.temperature * output_mix_heat_capacity + work_done * TURBINE_HEAT_CONVERSION_MULTIPLIER) / output_mix_heat_capacity, TCMB)
- return work_done
-
-/obj/machinery/power/turbine/block_superconductivity()
- return TRUE
+/obj/machinery/power/turbine/on_deconstruction(disassembled)
+ installed_part?.forceMove(loc)
+ return ..()
/obj/machinery/power/turbine/add_context(atom/source, list/context, obj/item/held_item, mob/user)
if(isnull(held_item))
@@ -128,7 +90,7 @@
. = ..()
if(installed_part)
. += span_notice("Currently at tier [installed_part.current_tier].")
- if(installed_part.current_tier + 1 < installed_part.max_tier)
+ if(installed_part.current_tier + 1 < TURBINE_PART_TIER_FOUR)
. += span_notice("Can be upgraded by using a tier [installed_part.current_tier + 1] part.")
. += span_notice("The [installed_part.name] can be [EXAMINE_HINT("pried")] out.")
else
@@ -138,21 +100,72 @@
. += span_notice("It can rotated with a [EXAMINE_HINT("wrench")]")
. += span_notice("The full machine can be [EXAMINE_HINT("pried")] apart")
+///Is this machine currently running
+/obj/machinery/power/turbine/proc/is_active()
+ SHOULD_BE_PURE(TRUE)
+ PROTECTED_PROC(TRUE)
+
+ return FALSE
+
+/**
+ * Adds overlays to this turbines appearance
+ * Arguments
+ *
+ * * list/overlays - the list of overlays to add to
+ */
+/obj/machinery/power/turbine/proc/set_overlays(list/overlays)
+ PROTECTED_PROC(TRUE)
+
+ overlays += "[base_icon_state]_[is_active() ? "on" : "off"]"
+
/obj/machinery/power/turbine/update_overlays()
. = ..()
+
if(panel_open)
- . += open_overlay
+ . += "[base_icon_state]_open"
- if(active)
- . += active_overlay
- if(emissive)
- . += emissive_appearance(icon, active_overlay, src)
- else
- . += off_overlay
+ set_overlays(.)
+
+/**
+ * Handles all the calculations needed for the gases, work done, temperature increase/decrease
+ *
+ * Arguments
+ * * datum/gas_mixture/input_mix - the gas from the environment or from another part of the turbine
+ * * datum/gas_mixture/output_mix - the gas that got pumped into this part from the input mix.
+ * ideally should be same as input mix but varying texmperatur & pressures can cause varying results
+ * * work_amount_to_remove - the amount of work to subtract from the actual work done to pump in the input mixture.
+ * For e.g. if gas was transfered from the inlet compressor to the rotor we want to subtract the work done
+ * by the inlet from the rotor to get the true work done
+ * * intake_size - the percentage of gas to be fed into an turbine part, controlled by turbine computer for inlet compressor only
+ */
+/obj/machinery/power/turbine/proc/transfer_gases(datum/gas_mixture/input_mix, datum/gas_mixture/output_mix, work_amount_to_remove, intake_size = 1)
+ PROTECTED_PROC(TRUE)
+
+ //pump gases. if no gases were transferred then no work was done
+ var/output_pressure = PRESSURE_MAX(output_mix.return_pressure())
+ var/datum/gas_mixture/transferred_gases = input_mix.pump_gas_to(output_mix, input_mix.return_pressure() * intake_size)
+ if(!transferred_gases)
+ return 0
+
+ //compute work done
+ var/work_done = QUANTIZE(transferred_gases.total_moles()) * R_IDEAL_GAS_EQUATION * transferred_gases.temperature * log((transferred_gases.volume * PRESSURE_MAX(transferred_gases.return_pressure())) / (output_mix.volume * output_pressure)) * TURBINE_WORK_CONVERSION_MULTIPLIER
+ if(work_amount_to_remove)
+ work_done = work_done - work_amount_to_remove
+
+ //compute temperature & work from temperature if that is a lower value
+ var/output_mix_heat_capacity = output_mix.heat_capacity()
+ if(!output_mix_heat_capacity)
+ return 0
+ work_done = min(work_done, (output_mix_heat_capacity * output_mix.temperature - output_mix_heat_capacity * TCMB) / TURBINE_HEAT_CONVERSION_MULTIPLIER)
+ output_mix.temperature = max((output_mix.temperature * output_mix_heat_capacity + work_done * TURBINE_HEAT_CONVERSION_MULTIPLIER) / output_mix_heat_capacity, TCMB)
+ return work_done
+
+/obj/machinery/power/turbine/block_superconductivity()
+ return TRUE
/obj/machinery/power/turbine/screwdriver_act(mob/living/user, obj/item/tool)
. = ITEM_INTERACT_BLOCKING
- if(active)
+ if(is_active())
balloon_alert(user, "turn it off!")
return
if(!anchored)
@@ -165,7 +178,7 @@
deactivate_parts(user)
else
activate_parts(user)
- update_appearance()
+ update_appearance(UPDATE_OVERLAYS)
return ITEM_INTERACT_SUCCESS
@@ -179,10 +192,6 @@
if(default_deconstruction_crowbar(tool))
return ITEM_INTERACT_SUCCESS
-/obj/machinery/power/turbine/on_deconstruction(disassembled)
- installed_part?.forceMove(loc)
- return ..()
-
/obj/machinery/power/turbine/crowbar_act_secondary(mob/living/user, obj/item/tool)
. = ITEM_INTERACT_BLOCKING
if(!panel_open)
@@ -191,7 +200,7 @@
if(!installed_part)
balloon_alert(user, "no rotor installed!")
return
- if(active)
+ if(is_active())
balloon_alert(user, "[src] is on!")
return
@@ -206,7 +215,11 @@
* * check_only - if TRUE it will not activate the machine but will only check if it can be activated
*/
/obj/machinery/power/turbine/proc/activate_parts(mob/user, check_only = FALSE)
- can_connect = TRUE
+ SHOULD_CALL_PARENT(TRUE)
+
+ set_machine_stat(machine_stat & ~MAINT)
+
+ return TRUE
/**
* Allow easy disabling of each machine from the main controller
@@ -215,13 +228,18 @@
* * mob/user - the player who deactivated the parts
*/
/obj/machinery/power/turbine/proc/deactivate_parts(mob/user)
- can_connect = FALSE
+ SHOULD_CALL_PARENT(TRUE)
+
+ set_machine_stat(machine_stat | MAINT)
+
+ return TRUE
/obj/machinery/power/turbine/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change = TRUE)
. = ..()
set_panel_open(TRUE)
- update_appearance()
+ update_appearance(UPDATE_OVERLAYS)
deactivate_parts()
+ old_loc.air_update_turf(TRUE)
air_update_turf(TRUE)
/obj/machinery/power/turbine/Exited(atom/movable/gone, direction)
@@ -229,22 +247,22 @@
if(gone == installed_part)
installed_part = null
-/obj/machinery/power/turbine/attackby(obj/item/turbine_parts/object, mob/user, params)
- //not the correct part
+/obj/machinery/power/turbine/item_interaction(mob/living/user, obj/item/turbine_parts/object, list/modifiers)
+ . = NONE
if(!istype(object, part_path))
- return ..()
+ return
- //not in a state to accep the part. return TRUE so we don't bash the machine and damage it
- if(active)
+ //not in a state to accept the part. block so we don't bash the machine and damage it
+ if(is_active())
balloon_alert(user, "turn off the machine first!")
- return TRUE
+ return ITEM_INTERACT_BLOCKING
if(!panel_open)
balloon_alert(user, "open the maintenance hatch first!")
- return TRUE
+ return ITEM_INTERACT_BLOCKING
//install the part
if(!do_after(user, 2 SECONDS, src))
- return TRUE
+ return ITEM_INTERACT_BLOCKING
if(installed_part)
user.put_in_hands(installed_part)
balloon_alert(user, "replaced part with the one in hand")
@@ -252,22 +270,17 @@
balloon_alert(user, "installed new part")
user.transferItemToLoc(object, src)
installed_part = object
- return TRUE
+ efficiency = installed_part.get_tier_value(TURBINE_MAX_EFFICIENCY)
-/// Gets the efficiency of the installed part, returns 0 if no part is installed
-/obj/machinery/power/turbine/proc/get_efficiency()
- return installed_part?.part_efficiency || 0
+ return ITEM_INTERACT_SUCCESS
/obj/machinery/power/turbine/inlet_compressor
name = "inlet compressor"
desc = "The input side of a turbine generator, contains the compressor."
- icon = 'icons/obj/machines/engine/turbine.dmi'
icon_state = "inlet_compressor"
+ base_icon_state = "inlet"
circuit = /obj/item/circuitboard/machine/turbine_compressor
part_path = /obj/item/turbine_parts/compressor
- active_overlay = "inlet_animation"
- off_overlay = "inlet_off"
- open_overlay = "inlet_open"
/// The rotor this inlet is linked to
var/obj/machinery/power/turbine/core_rotor/rotor
@@ -284,6 +297,9 @@
//Volume of gas mixture is 1000
return ..(mapload, gas_theoretical_volume = 1000)
+/obj/machinery/power/turbine/inlet_compressor/is_active()
+ return QDELETED(rotor) ? FALSE : rotor.is_active()
+
/obj/machinery/power/turbine/inlet_compressor/deactivate_parts(mob/user)
. = ..()
if(!QDELETED(rotor))
@@ -296,6 +312,8 @@
* Returns temperature of the gas mix absorbed only if some work was done
*/
/obj/machinery/power/turbine/inlet_compressor/proc/compress_gases()
+ SHOULD_NOT_OVERRIDE(TRUE)
+
compressor_work = 0
compressor_pressure = MINIMUM_TURBINE_PRESSURE
if(QDELETED(input_turf))
@@ -314,16 +332,14 @@
return input_turf_mixture.temperature
+//===========================OUTLET==============================================
/obj/machinery/power/turbine/turbine_outlet
name = "turbine outlet"
desc = "The output side of a turbine generator, contains the turbine and the stator."
- icon = 'icons/obj/machines/engine/turbine.dmi'
icon_state = "turbine_outlet"
+ base_icon_state = "outlet"
circuit = /obj/item/circuitboard/machine/turbine_stator
part_path = /obj/item/turbine_parts/stator
- active_overlay = "outlet_animation"
- off_overlay = "outlet_off"
- open_overlay = "outlet_open"
/// The rotor this outlet is linked to
var/obj/machinery/power/turbine/core_rotor/rotor
@@ -334,6 +350,9 @@
//Volume of gas mixture is 6000
return ..(mapload, gas_theoretical_volume = 6000)
+/obj/machinery/power/turbine/turbine_outlet/is_active()
+ return QDELETED(rotor) ? FALSE : rotor.is_active()
+
/obj/machinery/power/turbine/turbine_outlet/deactivate_parts(mob/user)
. = ..()
if(!QDELETED(rotor))
@@ -343,13 +362,15 @@
/// push gases from its gas mix to output turf
/obj/machinery/power/turbine/turbine_outlet/proc/expel_gases()
+ SHOULD_NOT_OVERRIDE(TRUE)
+
if(QDELETED(output_turf))
output_turf = get_step(loc, dir)
//turf is blocked don't eject gases
if(!TURF_SHARES(output_turf))
return FALSE
- //eject gases and update turf is any was ejected
+ //eject gases and update turf if any was ejected
var/datum/gas_mixture/ejected_gases = machine_gasmix.pump_gas_to(output_turf.air, machine_gasmix.return_pressure())
if(ejected_gases)
output_turf.air_update_turf(TRUE)
@@ -358,29 +379,28 @@
//return ejected gases
return ejected_gases
+//===========================================CORE ROTOR=========================================
/obj/machinery/power/turbine/core_rotor
name = "core rotor"
desc = "The middle part of a turbine generator, contains the rotor and the main computer."
- icon = 'icons/obj/machines/engine/turbine.dmi'
icon_state = "core_rotor"
- active_overlay = "core_light"
- open_overlay = "core_open"
- active_power_usage = BASE_MACHINE_ACTIVE_CONSUMPTION
- emissive = TRUE
+ base_icon_state = "core"
can_change_cable_layer = TRUE
circuit = /obj/item/circuitboard/machine/turbine_rotor
part_path = /obj/item/turbine_parts/rotor
///ID to easily connect the main part of the turbine to the computer
var/mapping_id
+ ///Checks if the machine is processing or not
+ var/active = FALSE
///Reference to the compressor
var/obj/machinery/power/turbine/inlet_compressor/compressor
///Reference to the turbine
var/obj/machinery/power/turbine/turbine_outlet/turbine
///Rotation per minute the machine is doing
- var/rpm
+ var/rpm = 0
///Amount of power the machine is producing
- var/produced_energy
+ var/produced_energy = 0
///Check to see if all parts are connected to the core
var/all_parts_connected = FALSE
///Max rmp that the installed parts can handle, limits the rpms
@@ -430,6 +450,15 @@
if(!all_parts_connected)
. += span_warning("The parts need to be linked via a [EXAMINE_HINT("multitool")]")
+///Adds overlays to this turbines appearance
+/obj/machinery/power/turbine/core_rotor/set_overlays(list/overlays)
+ if(active)
+ overlays += "[base_icon_state]_on"
+ overlays += emissive_appearance(icon, "[base_icon_state]_on", src)
+
+/obj/machinery/power/turbine/core_rotor/is_active()
+ return active
+
/obj/machinery/power/turbine/core_rotor/cable_layer_act(mob/living/user, obj/item/tool)
if(!panel_open)
balloon_alert(user, "open panel first!")
@@ -462,30 +491,45 @@
//works same as regular left click
return multitool_act(user, tool)
-/// convinience proc for balloon alert which returns if viewer is null
+/**
+ * convinience proc for balloon alert which returns if viewer is null
+ * Arguments
+ *
+ * * mob/viewer - the player receiving the message
+ * * text - the message
+ */
/obj/machinery/power/turbine/core_rotor/proc/feedback(mob/viewer, text)
+ PRIVATE_PROC(TRUE)
+
if(isnull(viewer))
return
balloon_alert(viewer, text)
-/**
- * Called to activate the complete machine, checks for part presence, correct orientation and installed parts
- * Registers the input/output turfs
- */
+///Called to activate the complete machine, checks for part presence, correct orientation and installed parts
/obj/machinery/power/turbine/core_rotor/activate_parts(mob/user, check_only = FALSE)
//if this is not a checkup and all parts are connected then we have nothing to do
if(!check_only && all_parts_connected)
return TRUE
+ //are we broken
+ if(machine_stat & BROKEN)
+ feedback(user, "rotor is broken!")
+ return (all_parts_connected = FALSE)
+
//locate compressor & turbine, when checking we simply check to see if they are still there
if(!check_only)
- compressor = locate(/obj/machinery/power/turbine/inlet_compressor) in get_step(src, REVERSE_DIR(dir))
- turbine = locate(/obj/machinery/power/turbine/turbine_outlet) in get_step(src, dir)
+ compressor = locate() in get_step(src, REVERSE_DIR(dir))
+ turbine = locate() in get_step(src, dir)
+
+ //maybe look for them the other way around. this means the rotor is facing the wrong way
+ if(QDELETED(compressor) && QDELETED(turbine))
+ compressor = locate() in get_step(src, dir)
+ turbine = locate() in get_step(src, REVERSE_DIR(dir))
- //maybe look for them the other way around. we want the rotor to allign with them either way for player convinience
- if(!compressor && !turbine)
- compressor = locate(/obj/machinery/power/turbine/inlet_compressor) in get_step(src, dir)
- turbine = locate(/obj/machinery/power/turbine/turbine_outlet) in get_step(src, REVERSE_DIR(dir))
+ //show corrective actions
+ if(!QDELETED(compressor) || !QDELETED(turbine))
+ feedback(user, "rotor is facing the wrong way!")
+ return (all_parts_connected = FALSE)
//sanity checks for compressor
if(QDELETED(compressor))
@@ -494,9 +538,12 @@
if(compressor.dir != dir && compressor.dir != REVERSE_DIR(dir)) //make sure it's not perpendicular to the rotor
feedback(user, "compressor not aligned with rotor!")
return (all_parts_connected = FALSE)
- if(!compressor.can_connect)
+ if(compressor.machine_stat & MAINT)
feedback(user, "close compressor panel!")
return (all_parts_connected = FALSE)
+ if(compressor.machine_stat & BROKEN)
+ feedback(user, "compressor is broken!")
+ return (all_parts_connected = FALSE)
if(!compressor.installed_part)
feedback(user, "compressor has a missing part!")
return (all_parts_connected = FALSE)
@@ -505,17 +552,20 @@
if(QDELETED(turbine))
feedback(user, "missing turbine!")
return (all_parts_connected = FALSE)
- if(turbine.dir != dir && turbine.dir != REVERSE_DIR(dir))
+ if(turbine.dir != dir && turbine.dir != REVERSE_DIR(dir)) //make sure it's not perpendicular to the rotor
feedback(user, "turbine not aligned with rotor!")
return (all_parts_connected = FALSE)
- if(!turbine.can_connect)
- feedback(user, "turbine panel is either open or is misplaced!") //we say misplaced because can_connect becomes FALSE when this turbine is moved
+ if(turbine.machine_stat & MAINT)
+ feedback(user, "close turbine panel!")
+ return (all_parts_connected = FALSE)
+ if(turbine.machine_stat & BROKEN)
+ feedback(user, "turbine is broken!")
return (all_parts_connected = FALSE)
if(!turbine.installed_part)
feedback(user, "turbine is missing stator part!")
return (all_parts_connected = FALSE)
- //final sanity check to make sure turbine & compressor are facing the same direction. From an visual perspective they will appear facing away from each other actually. I know blame spriter's
+ //sanity check to make sure turbine & compressor are facing the same direction. From an visual perspective they will appear facing away from each other actually. I know blame spriter's
if(compressor.dir != turbine.dir)
feedback(user, "turbine & compressor are not facing away from each other!")
return (all_parts_connected = FALSE)
@@ -527,94 +577,67 @@
compressor.rotor = src
turbine.rotor = src
- max_allowed_rpm = (compressor.installed_part.max_rpm + turbine.installed_part.max_rpm + installed_part.max_rpm) / 3
- max_allowed_temperature = (compressor.installed_part.max_temperature + turbine.installed_part.max_temperature + installed_part.max_temperature) / 3
+ max_allowed_rpm = (compressor.installed_part.get_tier_value(TURBINE_MAX_RPM) + turbine.installed_part.get_tier_value(TURBINE_MAX_RPM) + installed_part.get_tier_value(TURBINE_MAX_RPM)) / 3
+ max_allowed_temperature = (compressor.installed_part.get_tier_value(TURBINE_MAX_TEMP) + turbine.installed_part.get_tier_value(TURBINE_MAX_TEMP) + installed_part.get_tier_value(TURBINE_MAX_TEMP)) / 3
connect_to_network()
- return TRUE
+ return ..()
-/**
- * Allows to null the various machines and references from the main core
- */
+///Allows to null the various machines and references from the main core
/obj/machinery/power/turbine/core_rotor/deactivate_parts()
- if(all_parts_connected)
- power_off()
+ toggle_power(force_off = TRUE)
compressor?.rotor = null
compressor = null
turbine?.rotor = null
turbine = null
all_parts_connected = FALSE
+ rpm = 0
+ produced_energy = 0
disconnect_from_network()
- SSair.stop_processing_machine(src)
+
+ return ..()
/obj/machinery/power/turbine/core_rotor/on_deconstruction(disassembled)
deactivate_parts()
return ..()
/// Toggle power on and off, not safe
-/obj/machinery/power/turbine/core_rotor/proc/toggle_power()
- if(active)
- power_off()
- return
- power_on()
-
-/**
- * Activate all three parts, not safe, it assumes the machine already connected and properly working
- * It does a minimun check to ensure the parts still exist
- */
-/obj/machinery/power/turbine/core_rotor/proc/power_on()
- if(active || QDELETED(compressor) || QDELETED(turbine))
- return
- active = TRUE
- compressor.active = TRUE
- turbine.active = TRUE
- call_parts_update_appearance()
- SSair.start_processing_machine(src)
-
-/// Calls all parts update appearance proc.
-/obj/machinery/power/turbine/core_rotor/proc/call_parts_update_appearance()
- update_appearance()
- if(!QDELETED(compressor))
- compressor.update_appearance()
- if(!QDELETED(turbine))
- turbine.update_appearance()
+/obj/machinery/power/turbine/core_rotor/proc/toggle_power(force_off)
+ SHOULD_NOT_OVERRIDE(TRUE)
+
+ //toggle status
+ if(force_off)
+ if(!active) //was already off
+ return
+ active = FALSE
+ else
+ active = !active
-/**
- * Deactivate all three parts, not safe, it assumes the machine already connected and properly working
- * will try to turn off whatever components are left of this machine
- */
-/obj/machinery/power/turbine/core_rotor/proc/power_off()
- if(!active)
- return
- active = FALSE
+ //update operation status of parts
+ update_appearance(UPDATE_OVERLAYS)
if(!QDELETED(compressor))
- compressor.active = FALSE
+ compressor.update_appearance(UPDATE_OVERLAYS)
if(!QDELETED(turbine))
- turbine.active = FALSE
- call_parts_update_appearance()
- SSair.stop_processing_machine(src)
+ turbine.update_appearance(UPDATE_OVERLAYS)
-/// Returns true if all parts have their panel closed
-/obj/machinery/power/turbine/core_rotor/proc/all_parts_ready()
- if(QDELETED(compressor))
- return FALSE
- if(QDELETED(turbine))
- return FALSE
- return !panel_open && !compressor.panel_open && !turbine.panel_open
+ //start or stop processing
+ if(active)
+ update_mode_power_usage(ACTIVE_POWER_USE, active_power_usage)
+ begin_processing()
+ else
+ unset_static_power()
+ end_processing()
/// Getter for turbine integrity, return the amount in %
/obj/machinery/power/turbine/core_rotor/proc/get_turbine_integrity()
- var/integrity = damage / 500
- integrity = max(round(100 - integrity * 100, 0.01), 0)
- return integrity
+ SHOULD_NOT_OVERRIDE(TRUE)
-/obj/machinery/power/turbine/core_rotor/process_atmos()
- if(!active || !activate_parts(check_only = TRUE) || (machine_stat & BROKEN) || !powered(ignore_use_power = TRUE))
- power_off()
- return PROCESS_KILL
+ return max(round(100 - (damage / 500) * 100, 0.01), 0)
- //use power to operate internal electronics & stuff
- update_mode_power_usage(ACTIVE_POWER_USE, active_power_usage)
+/obj/machinery/power/turbine/core_rotor/process(seconds_per_tick)
+ if(!active || !activate_parts(check_only = TRUE) || !powered(ignore_use_power = TRUE))
+ deactivate_parts()
+ return PROCESS_KILL
//===============COMPRESSOR WORKING========//
//Transfer gases from turf to compressor
@@ -634,16 +657,16 @@
var/integrity = get_turbine_integrity()
if(integrity <= 0)
deactivate_parts()
- if(rpm < 35000)
+ if(rpm < TURBINE_MAX_BASE_RPM)
explosion(src, 0, 1, 4)
return PROCESS_KILL
- if(rpm < 87500)
+ if(rpm < TURBINE_MAX_BASE_RPM * 2.5)
explosion(src, 0, 2, 6)
return PROCESS_KILL
- if(rpm < 220000)
+ if(rpm < TURBINE_MAX_BASE_RPM * 2.5 * 2.5)
explosion(src, 1, 3, 7)
return PROCESS_KILL
- if(rpm < 550000)
+ if(rpm < TURBINE_MAX_BASE_RPM * 2.5 * 2.5 * 2.5)
explosion(src, 2, 5, 7)
return PROCESS_KILL
@@ -667,10 +690,10 @@
//removing the work needed to move the compressor but adding back the turbine work that is the one generating most of the power.
work_done = max(work_done - compressor.compressor_work * TURBINE_COMPRESSOR_STATOR_INTERACTION_MULTIPLIER - turbine_work, 0)
//calculate final acheived rpm
- rpm = ((work_done * compressor.get_efficiency()) ** turbine.get_efficiency()) * get_efficiency() / TURBINE_RPM_CONVERSION
- rpm = FLOOR(min(rpm, max_allowed_rpm), 1)
+ rpm = ((work_done * compressor.efficiency) ** turbine.efficiency) * efficiency / TURBINE_RPM_CONVERSION
+ rpm = min(ROUND_UP(rpm), max_allowed_rpm)
//add energy into the grid, also use part of it for turbine operation
- produced_energy = rpm * TURBINE_ENERGY_RECTIFICATION_MULTIPLIER * TURBINE_RPM_CONVERSION
+ produced_energy = rpm * TURBINE_ENERGY_RECTIFICATION_MULTIPLIER * TURBINE_RPM_CONVERSION * seconds_per_tick
add_avail(produced_energy)
/obj/item/paper/guides/jobs/atmos/turbine
diff --git a/code/modules/power/turbine/turbine_computer.dm b/code/modules/power/turbine/turbine_computer.dm
index 2ad777edd622..7771bda03f8d 100644
--- a/code/modules/power/turbine/turbine_computer.dm
+++ b/code/modules/power/turbine/turbine_computer.dm
@@ -11,19 +11,17 @@
/obj/machinery/computer/turbine_computer/post_machine_initialize()
. = ..()
- locate_machinery()
-/obj/machinery/computer/turbine_computer/locate_machinery(multitool_connection)
if(!mapping_id)
return
for(var/obj/machinery/power/turbine/core_rotor/main as anything in SSmachines.get_machines_by_type_and_subtypes(/obj/machinery/power/turbine/core_rotor))
if(main.mapping_id != mapping_id)
continue
register_machine(main)
- return
+ break
-/obj/machinery/computer/turbine_computer/multitool_act(mob/living/user, obj/item/tool)
- var/obj/item/multitool/multitool = tool
+/obj/machinery/computer/turbine_computer/multitool_act(mob/living/user, obj/item/multitool/multitool)
+ . = ITEM_INTERACT_FAILURE
if(!istype(multitool.buffer, /obj/machinery/power/turbine/core_rotor))
to_chat(user, span_notice("Wrong machine type in [multitool] buffer..."))
return
@@ -31,12 +29,21 @@
to_chat(user, span_notice("Changing [src] bluespace network..."))
if(!do_after(user, 0.2 SECONDS, src))
return
+
playsound(get_turf(user), 'sound/machines/click.ogg', 10, TRUE)
register_machine(multitool.buffer)
to_chat(user, span_notice("You link [src] to the console in [multitool]'s buffer."))
- return TRUE
+ return ITEM_INTERACT_SUCCESS
+
+/**
+ * Links the rotor with this computer
+ * Arguments
+ *
+ * * obj/machinery/power/turbine/core_rotor/machine - the machine to link
+ */
+/obj/machinery/computer/turbine_computer/proc/register_machine(obj/machinery/power/turbine/core_rotor/machine)
+ PRIVATE_PROC(TRUE)
-/obj/machinery/computer/turbine_computer/proc/register_machine(machine)
turbine_core = WEAKREF(machine)
/obj/machinery/computer/turbine_computer/ui_interact(mob/user, datum/tgui/ui)
@@ -47,26 +54,27 @@
ui.open()
/obj/machinery/computer/turbine_computer/ui_data(mob/user)
- var/list/data = list()
+ . = list()
+ //do we have the main rotor with all parts connected
var/obj/machinery/power/turbine/core_rotor/main_control = turbine_core?.resolve()
- data["connected"] = !!QDELETED(main_control)
- if(!main_control)
+ if(QDELETED(main_control) || !main_control.all_parts_connected)
+ .["connected"] = FALSE
return
+ else
+ .["connected"] = TRUE
- data["active"] = main_control.active
- data["rpm"] = main_control.rpm ? main_control.rpm : 0
- data["power"] = main_control.produced_energy ? main_control.produced_energy : 0
- data["integrity"] = main_control.get_turbine_integrity()
- data["parts_linked"] = main_control.all_parts_connected
- data["parts_ready"] = main_control.all_parts_ready()
+ //operation status
+ .["active"] = main_control.active
+ .["rpm"] = main_control.rpm
+ .["power"] = energy_to_power(main_control.produced_energy)
+ .["integrity"] = main_control.get_turbine_integrity()
- data["max_rpm"] = main_control.max_allowed_rpm
- data["max_temperature"] = main_control.max_allowed_temperature
- data["temp"] = main_control.compressor?.input_turf?.air.temperature || 0
- data["regulator"] = QDELETED(main_control.compressor) ? 0 : main_control.compressor.intake_regulator
-
- return data
+ //running parameters
+ .["max_rpm"] = main_control.max_allowed_rpm
+ .["max_temperature"] = main_control.max_allowed_temperature
+ .["temp"] = main_control.compressor.input_turf?.air.temperature || 0
+ .["regulator"] = main_control.compressor.intake_regulator
/obj/machinery/computer/turbine_computer/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
. = ..()
@@ -76,19 +84,35 @@
switch(action)
if("toggle_power")
var/obj/machinery/power/turbine/core_rotor/main_control = turbine_core?.resolve()
- if(!main_control || !main_control.all_parts_connected || main_control.rpm > 1000)
- return TRUE
- if(!main_control.activate_parts(usr, check_only = TRUE))
- return TRUE
+ if(!main_control)
+ return FALSE
+
+ if(!main_control.active) //turning on the machine requires all part to be linked
+ if(!main_control.activate_parts(ui.user, check_only = TRUE))
+ return FALSE
+ else if(main_control.rpm > 1000) //turning off requires rpm to be less than 1000
+ return FALSE
+
main_control.toggle_power()
main_control.rpm = 0
main_control.produced_energy = 0
- . = TRUE
+ return TRUE
+
if("regulate")
- var/intake_size = text2num(params["regulate"])
+ var/intake_size = params["regulate"]
+ if(isnull(intake_size))
+ return FALSE
+
+ intake_size = text2num(intake_size)
+ if(isnull(intake_size))
+ return FALSE
+
var/obj/machinery/power/turbine/core_rotor/main_control = turbine_core?.resolve()
- if(intake_size == null || !main_control)
- return
- if(!QDELETED(main_control.compressor))
- main_control.compressor.intake_regulator = clamp(intake_size, 0.01, 1)
- . = TRUE
+ if(!main_control)
+ return FALSE
+
+ if(QDELETED(main_control.compressor))
+ return FALSE
+
+ main_control.compressor.intake_regulator = clamp(intake_size, 0.01, 1)
+ return TRUE
diff --git a/code/modules/power/turbine/turbine_parts.dm b/code/modules/power/turbine/turbine_parts.dm
index 4215fccf39fe..2da60eff1316 100644
--- a/code/modules/power/turbine/turbine_parts.dm
+++ b/code/modules/power/turbine/turbine_parts.dm
@@ -1,121 +1,128 @@
+///String to access turbine part typepath to upgrade
+#define TURBINE_UPGRADE_PART "part"
+///String to access turbine part required amount to upgrade
+#define TURBINE_UPGRADE_AMOUNT "amount"
+
/obj/item/turbine_parts
name = "turbine parts"
desc = "you really should call an admin"
icon = 'icons/obj/machines/engine/turbine.dmi'
icon_state = "inlet_compressor"
- ///Efficiency of the part to the turbine machine
- var/part_efficiency = 0
- ///Efficiency increase amount for each tier
- var/part_efficiency_increase_amount = 0
-
///Current part tier
var/current_tier = TURBINE_PART_TIER_ONE
- ///Max part tier
- var/max_tier = TURBINE_PART_TIER_FOUR
-
- ///Stores the path of the material for the second tier upgrade
- var/obj/item/stack/sheet/second_tier_material = /obj/item/stack/sheet/plasteel
- ///Amount of second tier material for the upgrade
- var/second_tier_material_amount = 10
-
- ///Stores the path of the material for the third tier upgrade
- var/obj/item/stack/sheet/third_tier_material = /obj/item/stack/sheet/mineral/titanium
- ///Amount of third tier material for the upgrade
- var/third_tier_material_amount = 10
-
- ///Stores the path of the material for the fourth tier upgrade
- var/obj/item/stack/sheet/fourth_tier_material = /obj/item/stack/sheet/mineral/metal_hydrogen
- ///Amount of fourth tier material for the upgrade
- var/fourth_tier_material_amount = 5
-
- ///Max rpm reachable by the part
- var/max_rpm = 35000
- ///Multiplier to increase the max rpm per tier, max should be around 500000 rpm
- var/max_rpm_tier_multiplier = 2.5
-
- ///Max temperature achievable by the part before the turbine starts to take damage
- var/max_temperature = 50000
- ///Max temperature exponential value per tier
- var/max_temperature_tier_exponential = 1.2
/obj/item/turbine_parts/examine(mob/user)
. = ..()
- . += "This is a tier [current_tier] turbine part, rated for [max_rpm] rpm and [max_temperature] K."
- var/upgrade_material_name_amount
- switch(current_tier)
- if(TURBINE_PART_TIER_ONE)
- upgrade_material_name_amount = "[second_tier_material_amount] [initial(second_tier_material.name)] sheets"
- if(TURBINE_PART_TIER_TWO)
- upgrade_material_name_amount = "[third_tier_material_amount] [initial(third_tier_material.name)] sheets"
- if(TURBINE_PART_TIER_THREE)
- upgrade_material_name_amount = "[fourth_tier_material_amount] [initial(fourth_tier_material.name)] sheets"
+ . += span_notice("This is a tier [current_tier] turbine part, rated for [get_tier_value(TURBINE_MAX_RPM)] rpm and [get_tier_value(TURBINE_MAX_TEMP)] K.")
- if(upgrade_material_name_amount)
- . += "Can be upgraded with [upgrade_material_name_amount]."
+ var/list/required_parts = get_tier_upgrades()
+ if(length(required_parts))
+ var/obj/item/stack/material = required_parts[TURBINE_UPGRADE_PART]
+ . += span_notice("Can be upgraded with [required_parts[TURBINE_UPGRADE_AMOUNT]] [initial(material.name)] sheets.")
else
- . += "Is already at max tier."
+ . += span_notice("Is already at max tier.")
+
+/**
+ * Returns the max values of various attributes of this turbine based on its tier
+ * param - see code/__DEFINES/turbine_defines/.dm
+ */
+/obj/item/turbine_parts/proc/get_tier_value(param)
+ SHOULD_BE_PURE(TRUE)
+
+ var/max_value = 0
+ for(var/_ in 1 to current_tier)
+ switch(param)
+ if(TURBINE_MAX_RPM)
+ if(!max_value)
+ max_value = TURBINE_MAX_BASE_RPM
+ else
+ max_value *= 2.5
+
+ if(TURBINE_MAX_TEMP)
+ if(!max_value)
+ max_value = 50000
+ else
+ max_value = max_value ** 1.2
+
+ if(TURBINE_MAX_EFFICIENCY)
+ if(!max_value)
+ max_value = 0.25
+ else
+ max_value += 0.2
+ return max_value
+
+///Returns a list containing the typepath & amount of it required to upgrade to the next tier
+/obj/item/turbine_parts/proc/get_tier_upgrades()
+ PROTECTED_PROC(TRUE)
+ SHOULD_BE_PURE(TRUE)
+ RETURN_TYPE(/list)
-/obj/item/turbine_parts/attackby(obj/item/attacking_item, mob/user, params)
- if(current_tier >= max_tier)
- return FALSE
switch(current_tier)
if(TURBINE_PART_TIER_ONE)
- if(!istype(attacking_item, second_tier_material))
- return
- var/obj/item/stack/sheet/second_tier = attacking_item
- if(do_after(user, 1 SECONDS, src) && second_tier.use(second_tier_material_amount))
- current_tier = 2
- part_efficiency += part_efficiency_increase_amount
- max_rpm *= max_rpm_tier_multiplier
- max_temperature = max_temperature ** max_temperature_tier_exponential
- return TRUE
+ return list(TURBINE_UPGRADE_PART = /obj/item/stack/sheet/plasteel, TURBINE_UPGRADE_AMOUNT = 10)
if(TURBINE_PART_TIER_TWO)
- if(!istype(attacking_item, third_tier_material))
- return
- var/obj/item/stack/sheet/third_tier = attacking_item
- if(do_after(user, 2 SECONDS, src) && third_tier.use(third_tier_material_amount))
- current_tier = 3
- part_efficiency += part_efficiency_increase_amount
- max_rpm *= max_rpm_tier_multiplier
- max_temperature = max_temperature ** max_temperature_tier_exponential
- return TRUE
+ return list(TURBINE_UPGRADE_PART = /obj/item/stack/sheet/mineral/titanium, TURBINE_UPGRADE_AMOUNT = 10)
if(TURBINE_PART_TIER_THREE)
- if(!istype(attacking_item, fourth_tier_material))
- return
- var/obj/item/stack/sheet/fourth_tier = attacking_item
- if(do_after(user, 3 SECONDS, src) && fourth_tier.use(fourth_tier_material_amount))
- current_tier = 4
- part_efficiency += part_efficiency_increase_amount
- max_rpm *= max_rpm_tier_multiplier
- max_temperature = max_temperature ** max_temperature_tier_exponential
- return TRUE
+ return list(TURBINE_UPGRADE_PART = /obj/item/stack/sheet/mineral/metal_hydrogen, TURBINE_UPGRADE_AMOUNT = 5)
- return ..()
+/obj/item/turbine_parts/item_interaction(mob/living/user, obj/item/attacking_item, list/modifiers)
+ . = NONE
+
+ var/list/required_parts = get_tier_upgrades()
+ if(!length(required_parts))
+ balloon_alert(user, "already at max tier!")
+ return ITEM_INTERACT_FAILURE
+
+ var/obj/item/stack/sheet/material = attacking_item
+ if(!istype(material, required_parts[TURBINE_UPGRADE_PART]))
+ balloon_alert(user, "incorrect part!")
+ return ITEM_INTERACT_FAILURE
+
+ var/amount = required_parts[TURBINE_UPGRADE_AMOUNT]
+ if(material.amount < amount)
+ balloon_alert(user, "requires [amount] sheets!")
+ return ITEM_INTERACT_FAILURE
+
+ if(do_after(user, current_tier SECONDS, src) && material.use(amount))
+ current_tier += 1
+ return ITEM_INTERACT_SUCCESS
/obj/item/turbine_parts/compressor
name = "compressor part"
desc = "Install in a turbine engine compressor to increase its performance"
icon_state = "compressor_part"
- part_efficiency = 0.25
- part_efficiency_increase_amount = 0.2
/obj/item/turbine_parts/rotor
name = "rotor part"
desc = "Install in a turbine engine rotor to increase its performance"
icon_state = "rotor_part"
- part_efficiency = 0.25
- part_efficiency_increase_amount = 0.2
/obj/item/turbine_parts/stator
name = "stator part"
desc = "Install in a turbine engine turbine to increase its performance"
icon_state = "stator_part"
- part_efficiency = 0.85
- part_efficiency_increase_amount = 0.015
- second_tier_material = /obj/item/stack/sheet/mineral/titanium
- third_tier_material = /obj/item/stack/sheet/mineral/metal_hydrogen
- fourth_tier_material = /obj/item/stack/sheet/mineral/zaukerite
- second_tier_material_amount = 15
- third_tier_material_amount = 15
- fourth_tier_material_amount = 10
+
+/obj/item/turbine_parts/stator/get_tier_value(param)
+ if(param == TURBINE_MAX_EFFICIENCY)
+ var/max_value = 0
+ for(var/_ in 1 to current_tier)
+ if(!max_value)
+ max_value = 0.85
+ else
+ max_value += 0.015
+ return max_value
+
+ return ..()
+
+/obj/item/turbine_parts/stator/get_tier_upgrades()
+ switch(current_tier)
+ if(TURBINE_PART_TIER_ONE)
+ return list(TURBINE_UPGRADE_PART = /obj/item/stack/sheet/mineral/titanium, TURBINE_UPGRADE_AMOUNT = 15)
+ if(TURBINE_PART_TIER_TWO)
+ return list(TURBINE_UPGRADE_PART = /obj/item/stack/sheet/mineral/metal_hydrogen, TURBINE_UPGRADE_AMOUNT = 15)
+ if(TURBINE_PART_TIER_THREE)
+ return list(TURBINE_UPGRADE_PART = /obj/item/stack/sheet/mineral/zaukerite, TURBINE_UPGRADE_AMOUNT = 10)
+
+#undef TURBINE_UPGRADE_PART
+#undef TURBINE_UPGRADE_AMOUNT
diff --git a/code/modules/projectiles/ammunition/_firing.dm b/code/modules/projectiles/ammunition/_firing.dm
index cb2a553d7979..74c8460113a9 100644
--- a/code/modules/projectiles/ammunition/_firing.dm
+++ b/code/modules/projectiles/ammunition/_firing.dm
@@ -62,6 +62,8 @@
loaded_projectile.damage *= gun.projectile_damage_multiplier * integrity_mult
loaded_projectile.stamina *= gun.projectile_damage_multiplier * integrity_mult
+ loaded_projectile.speed *= gun.projectile_speed_multiplier * integrity_mult
+
loaded_projectile.wound_bonus += gun.projectile_wound_bonus
loaded_projectile.wound_bonus *= loaded_projectile.wound_bonus >= 0 ? 1 : 2 - integrity_mult
loaded_projectile.bare_wound_bonus += gun.projectile_wound_bonus
diff --git a/code/modules/projectiles/ammunition/ballistic/revolver.dm b/code/modules/projectiles/ammunition/ballistic/revolver.dm
index 47dca04b6db9..23daaa13f612 100644
--- a/code/modules/projectiles/ammunition/ballistic/revolver.dm
+++ b/code/modules/projectiles/ammunition/ballistic/revolver.dm
@@ -1,26 +1,31 @@
// .357 (Syndie Revolver)
-/obj/item/ammo_casing/a357
+/obj/item/ammo_casing/c357
name = ".357 bullet casing"
desc = "A .357 bullet casing."
caliber = CALIBER_357
- projectile_type = /obj/projectile/bullet/a357
+ projectile_type = /obj/projectile/bullet/c357
-/obj/item/ammo_casing/a357/spent
+/obj/item/ammo_casing/c357/spent
projectile_type = null
-/obj/item/ammo_casing/a357/match
+/obj/item/ammo_casing/c357/match
name = ".357 match bullet casing"
desc = "A .357 bullet casing, manufactured to exceedingly high standards."
- projectile_type = /obj/projectile/bullet/a357/match
+ projectile_type = /obj/projectile/bullet/c357/match
-/obj/item/ammo_casing/a357/phasic
+/obj/item/ammo_casing/c357/phasic
name = ".357 phasic bullet casing"
- projectile_type = /obj/projectile/bullet/a357/phasic
+ projectile_type = /obj/projectile/bullet/c357/phasic
-/obj/item/ammo_casing/a357/heartseeker
+/obj/item/ammo_casing/c357/heartseeker
name = ".357 heartseeker bullet casing"
- projectile_type = /obj/projectile/bullet/a357/heartseeker
+ projectile_type = /obj/projectile/bullet/c357/heartseeker
+
+/obj/item/ammo_casing/c357/heartseeker/ready_proj(atom/target, mob/living/user, quiet, zone_override, atom/fired_from)
+ . = ..()
+ if(!isturf(target))
+ loaded_projectile.set_homing_target(target)
// 7.62x38mmR (Nagant Revolver)
@@ -54,6 +59,11 @@
projectile_type = /obj/projectile/bullet/c38/match/bouncy
harmful = FALSE //NOVA EDIT ADDITION
+/obj/item/ammo_casing/c38/match/true
+ name = ".38 True Strike bullet casing"
+ desc = "A .38 True Strike bullet casing."
+ projectile_type = /obj/projectile/bullet/c38/match/true
+
/obj/item/ammo_casing/c38/dumdum
name = ".38 DumDum bullet casing"
desc = "A .38 DumDum bullet casing."
@@ -68,3 +78,35 @@
name = ".38 Iceblox bullet casing"
desc = "A .38 Iceblox bullet casing."
projectile_type = /obj/projectile/bullet/c38/iceblox
+
+//gatfruit
+/obj/item/ammo_casing/pea
+ name = "pea bullet casing"
+ desc = "A bizarre pea bullet."
+ caliber = CALIBER_PEA
+ icon_state = "pea"
+ projectile_type = /obj/projectile/bullet/pea
+ /// Damage we achieve at 100 potency
+ var/max_damage = 15
+ /// Damage set by the plant
+ var/damage = 15 //max potency, is set
+
+/obj/item/ammo_casing/pea/Initialize(mapload)
+ . = ..()
+ create_reagents(60, SEALED_CONTAINER)
+
+/obj/item/ammo_casing/pea/ready_proj(atom/target, mob/living/user, quiet, zone_override, atom/fired_from)
+ . = ..()
+ if(isnull(loaded_projectile))
+ return
+ loaded_projectile.damage = damage
+
+/obj/item/ammo_casing/pea/attack_self(mob/user)
+ . = ..()
+ if(isnull(loaded_projectile))
+ return
+ var/obj/item/food/grown/peas/peas = new(user.drop_location())
+ user.put_in_hands(peas)
+ to_chat(user, span_notice("You separate [peas] from [src]."))
+ loaded_projectile = null
+ update_appearance()
diff --git a/code/modules/projectiles/ammunition/energy/stun.dm b/code/modules/projectiles/ammunition/energy/stun.dm
index 7fb22e42ef5a..6fa8ee80c6de 100644
--- a/code/modules/projectiles/ammunition/energy/stun.dm
+++ b/code/modules/projectiles/ammunition/energy/stun.dm
@@ -16,6 +16,9 @@
/obj/item/ammo_casing/energy/electrode/old
e_cost = LASER_SHOTS(1, STANDARD_CELL_CHARGE)
+/obj/item/ammo_casing/energy/electrode/ai_turrets
+ projectile_type = /obj/projectile/energy/electrode/ai_turrets
+
/obj/item/ammo_casing/energy/disabler
projectile_type = /obj/projectile/beam/disabler
select_name = "disable"
diff --git a/code/modules/projectiles/boxes_magazines/ammo_boxes.dm b/code/modules/projectiles/boxes_magazines/ammo_boxes.dm
index 1fb6cbbf94dc..dbeb6860e013 100644
--- a/code/modules/projectiles/boxes_magazines/ammo_boxes.dm
+++ b/code/modules/projectiles/boxes_magazines/ammo_boxes.dm
@@ -2,7 +2,7 @@
name = "speed loader (.357)"
desc = "Designed to quickly reload revolvers."
icon_state = "357"
- ammo_type = /obj/item/ammo_casing/a357
+ ammo_type = /obj/item/ammo_casing/c357
max_ammo = 7
multiple_sprites = AMMO_BOX_PER_BULLET
item_flags = NO_MAT_REDEMPTION
@@ -12,13 +12,13 @@
/obj/item/ammo_box/a357/match
name = "speed loader (.357 Match)"
desc = "Designed to quickly reload revolvers. These rounds are manufactured within extremely tight tolerances, making them easy to show off trickshots with."
- ammo_type = /obj/item/ammo_casing/a357/match
+ ammo_type = /obj/item/ammo_casing/c357/match
ammo_band_color = "#77828a"
/obj/item/ammo_box/a357/phasic
name = "speed loader (.357 Phasic)"
desc = "Designed to quickly reload revolvers. Holds phasic ammo, also known as 'Ghost Lead', allowing it to pass through non-organic material."
- ammo_type = /obj/item/ammo_casing/a357/phasic
+ ammo_type = /obj/item/ammo_casing/c357/phasic
ammo_band_color = "#693a6a"
/obj/item/ammo_box/a357/heartseeker
@@ -26,7 +26,7 @@
desc = "Designed to quickly reload revolvers. Holds heartseeker ammo, which veers into targets with exceptional precision using \
an unknown method. It apparently predicts movement using neural pulses in the brain, but that's less marketable. \
As seen in the hit NTFlik horror-space western film, Forget-Me-Not! Brought to you by Roseus Galactic!"
- ammo_type = /obj/item/ammo_casing/a357/heartseeker
+ ammo_type = /obj/item/ammo_casing/c357/heartseeker
ammo_band_color = "#a91e1e"
/obj/item/ammo_box/c38
@@ -58,6 +58,12 @@
ammo_type = /obj/item/ammo_casing/c38/match/bouncy
ammo_band_color = "#556696"
+/obj/item/ammo_box/c38/true
+ name = "speed loader (.38 True Strike)"
+ desc = "Designed to quickly reload revolvers. Bullets bounce towards new targets with surprising accuracy."
+ ammo_type = /obj/item/ammo_casing/c38/match/true
+ ammo_band_color = "#d647b0"
+
/obj/item/ammo_box/c38/dumdum
name = "speed loader (.38 DumDum)"
desc = "Designed to quickly reload revolvers. These rounds expand on impact, allowing them to shred the target and cause massive bleeding. Very weak against armor and distant targets."
diff --git a/code/modules/projectiles/boxes_magazines/external/pistol.dm b/code/modules/projectiles/boxes_magazines/external/pistol.dm
index 8b0bc1da7e5b..05d4c4bf4c99 100644
--- a/code/modules/projectiles/boxes_magazines/external/pistol.dm
+++ b/code/modules/projectiles/boxes_magazines/external/pistol.dm
@@ -1,44 +1,4 @@
-/obj/item/ammo_box/magazine/m10mm
- name = "pistol magazine (10mm)"
- desc = "A gun magazine."
- icon_state = "9x19p"
- base_icon_state = "9x19p"
- ammo_type = /obj/item/ammo_casing/c10mm
- caliber = CALIBER_10MM
- max_ammo = 8
- multiple_sprites = AMMO_BOX_FULL_EMPTY
- multiple_sprite_use_base = TRUE
-
-/obj/item/ammo_box/magazine/m10mm/fire
- name = "pistol magazine (10mm incendiary)"
- icon_state = "9x19pI"
- base_icon_state = "9x19pI"
- desc = "A 10mm pistol magazine. Loaded with rounds which ignite the target."
- ammo_type = /obj/item/ammo_casing/c10mm/fire
-
-/obj/item/ammo_box/magazine/m10mm/hp
- name = "pistol magazine (10mm HP)"
- icon_state = "9x19pH"
- base_icon_state = "9x19pH"
- desc= "A 10mm pistol magazine. Loaded with hollow-point rounds, extremely effective against unarmored targets, but nearly useless against protective clothing."
- ammo_type = /obj/item/ammo_casing/c10mm/hp
-
-/obj/item/ammo_box/magazine/m10mm/ap
- name = "pistol magazine (10mm AP)"
- icon_state = "9x19pA"
- base_icon_state = "9x19pA"
- desc= "A 10mm pistol magazine. Loaded with rounds which penetrate armour, but are less effective against normal targets."
- ammo_type = /obj/item/ammo_casing/c10mm/ap
-
-/obj/item/ammo_box/magazine/m45
- name = "handgun magazine (.45)"
- icon_state = "45-8"
- base_icon_state = "45"
- ammo_type = /obj/item/ammo_casing/c45
- caliber = CALIBER_45
- max_ammo = 8
- multiple_sprites = AMMO_BOX_PER_BULLET
- multiple_sprite_use_base = TRUE
+// Makarov (9mm) //
/obj/item/ammo_box/magazine/m9mm
name = "pistol magazine (9mm)"
@@ -46,7 +6,7 @@
base_icon_state = "9x19p"
ammo_type = /obj/item/ammo_casing/c9mm
caliber = CALIBER_9MM
- max_ammo = 8
+ max_ammo = 12
multiple_sprites = AMMO_BOX_FULL_EMPTY
multiple_sprite_use_base = TRUE
@@ -71,6 +31,8 @@
desc= "A gun magazine. Loaded with rounds which penetrate armour, but are less effective against normal targets."
ammo_type = /obj/item/ammo_casing/c9mm/ap
+// Stechkin APS (9mm) //
+
/obj/item/ammo_box/magazine/m9mm_aps
name = "stechkin pistol magazine (9mm)"
icon_state = "9mmaps-15"
@@ -86,25 +48,50 @@
/obj/item/ammo_box/magazine/m9mm_aps/fire
name = "stechkin pistol magazine (9mm incendiary)"
ammo_type = /obj/item/ammo_casing/c9mm/fire
- max_ammo = 15
/obj/item/ammo_box/magazine/m9mm_aps/hp
name = "stechkin pistol magazine (9mm HP)"
ammo_type = /obj/item/ammo_casing/c9mm/hp
- max_ammo = 15
/obj/item/ammo_box/magazine/m9mm_aps/ap
name = "stechkin pistol magazine (9mm AP)"
ammo_type = /obj/item/ammo_casing/c9mm/ap
- max_ammo = 15
-/obj/item/ammo_box/magazine/m50
- name = "handgun magazine (.50ae)"
- icon_state = "50ae"
- ammo_type = /obj/item/ammo_casing/a50ae
- caliber = CALIBER_50AE
- max_ammo = 7
- multiple_sprites = AMMO_BOX_PER_BULLET
+// Ansem (10mm) //
+
+/obj/item/ammo_box/magazine/m10mm
+ name = "pistol magazine (10mm)"
+ desc = "A gun magazine."
+ icon_state = "9x19p"
+ base_icon_state = "9x19p"
+ ammo_type = /obj/item/ammo_casing/c10mm
+ caliber = CALIBER_10MM
+ max_ammo = 8
+ multiple_sprites = AMMO_BOX_FULL_EMPTY
+ multiple_sprite_use_base = TRUE
+
+/obj/item/ammo_box/magazine/m10mm/fire
+ name = "pistol magazine (10mm incendiary)"
+ icon_state = "9x19pI"
+ base_icon_state = "9x19pI"
+ desc = "A 10mm pistol magazine. Loaded with rounds which ignite the target."
+ ammo_type = /obj/item/ammo_casing/c10mm/fire
+
+/obj/item/ammo_box/magazine/m10mm/hp
+ name = "pistol magazine (10mm HP)"
+ icon_state = "9x19pH"
+ base_icon_state = "9x19pH"
+ desc= "A 10mm pistol magazine. Loaded with hollow-point rounds, extremely effective against unarmored targets, but nearly useless against protective clothing."
+ ammo_type = /obj/item/ammo_casing/c10mm/hp
+
+/obj/item/ammo_box/magazine/m10mm/ap
+ name = "pistol magazine (10mm AP)"
+ icon_state = "9x19pA"
+ base_icon_state = "9x19pA"
+ desc= "A 10mm pistol magazine. Loaded with rounds which penetrate armour, but are less effective against normal targets."
+ ammo_type = /obj/item/ammo_casing/c10mm/ap
+
+// Regal Condor (10mm) //
/obj/item/ammo_box/magazine/r10mm
name = "regal condor magazine (10mm Reaper)"
@@ -115,3 +102,25 @@
max_ammo = 8
multiple_sprites = AMMO_BOX_PER_BULLET
multiple_sprite_use_base = TRUE
+
+// M1911 (.45) //
+
+/obj/item/ammo_box/magazine/m45
+ name = "handgun magazine (.45)"
+ icon_state = "45-8"
+ base_icon_state = "45"
+ ammo_type = /obj/item/ammo_casing/c45
+ caliber = CALIBER_45
+ max_ammo = 8
+ multiple_sprites = AMMO_BOX_PER_BULLET
+ multiple_sprite_use_base = TRUE
+
+// Desert Eagle (.50 AE) //
+
+/obj/item/ammo_box/magazine/m50
+ name = "handgun magazine (.50ae)"
+ icon_state = "50ae"
+ ammo_type = /obj/item/ammo_casing/a50ae
+ caliber = CALIBER_50AE
+ max_ammo = 7
+ multiple_sprites = AMMO_BOX_PER_BULLET
diff --git a/code/modules/projectiles/boxes_magazines/external/rifle.dm b/code/modules/projectiles/boxes_magazines/external/rifle.dm
index 882fefedec11..96916fe9bb5a 100644
--- a/code/modules/projectiles/boxes_magazines/external/rifle.dm
+++ b/code/modules/projectiles/boxes_magazines/external/rifle.dm
@@ -21,3 +21,66 @@
/obj/item/ammo_box/magazine/m223/phasic
name = "toploader magazine (.223 Phasic)"
ammo_type = /obj/item/ammo_casing/a223/phasic
+
+// .38 (Battle Rifle) //
+
+/obj/item/ammo_box/magazine/m38
+ name = "battle rifle magazine (.38)"
+ desc = "A magazine for a BR-38 battle rifle."
+ icon_state = "38mag"
+ base_icon_state = "38mag"
+ w_class = WEIGHT_CLASS_NORMAL
+ ammo_type = /obj/item/ammo_casing/c38
+ caliber = CALIBER_38
+ max_ammo = 15
+ ammo_band_icon = "+38mag_ammo_band"
+ ammo_band_color = null
+
+/obj/item/ammo_box/magazine/m38/update_icon_state()
+ . = ..()
+ icon_state = "[base_icon_state][ammo_count() ? "-ammo" : ""]"
+
+/obj/item/ammo_box/magazine/m38/empty
+ start_empty = TRUE
+
+/obj/item/ammo_box/magazine/m38/trac
+ name = "battle rifle magazine (.38 TRAC)"
+ desc = "A magazine for a BR-38 battle rifle. TRAC bullets embed a tracking implant within the target's body and are entirely nonlethal."
+ ammo_type = /obj/item/ammo_casing/c38/trac
+ ammo_band_color = "#7b6383"
+
+/obj/item/ammo_box/magazine/m38/match
+ name = "battle rifle magazine (.38 Match)"
+ desc = "A magazine for a BR-38 battle rifle. These rounds are manufactured within extremely tight tolerances, making them easy to show off trickshots with."
+ ammo_type = /obj/item/ammo_casing/c38/match
+ ammo_band_color = "#7b6383"
+
+/obj/item/ammo_box/magazine/m38/match/bouncy
+ name = "battle rifle magazine (.38 Rubber)"
+ desc = "A magazine for a BR-38 battle rifle. These rounds are incredibly bouncy and MOSTLY nonlethal, making them great to show off trickshots with."
+ ammo_type = /obj/item/ammo_casing/c38/match/bouncy
+ ammo_band_color = "#556696"
+
+/obj/item/ammo_box/magazine/m38/true
+ name = "battle rifle magazine (.38 True Strike)"
+ desc = "A magazine for a BR-38 battle rifle. Bullets bounce towards new targets with surprising accuracy."
+ ammo_type = /obj/item/ammo_casing/c38/match/true
+ ammo_band_color = "#d647b0"
+
+/obj/item/ammo_box/magazine/m38/dumdum
+ name = "battle rifle magazine (.38 DumDum)"
+ desc = "A magazine for a BR-38 battle rifle. These rounds expand on impact, allowing them to shred the target and cause massive bleeding. Very weak against armor and distant targets."
+ ammo_type = /obj/item/ammo_casing/c38/dumdum
+ ammo_band_color = "#969578"
+
+/obj/item/ammo_box/magazine/m38/hotshot
+ name = "battle rifle magazine (.38 Hot Shot)"
+ desc = "A magazine for a BR-38 battle rifle. Hot Shot bullets contain an incendiary payload."
+ ammo_type = /obj/item/ammo_casing/c38/hotshot
+ ammo_band_color = "#805a57"
+
+/obj/item/ammo_box/magazine/m38/iceblox
+ name = "battle rifle magazine (.38 Iceblox)"
+ desc = "A magazine for a BR-38 battle rifle. Iceblox bullets contain a cryogenic payload."
+ ammo_type = /obj/item/ammo_casing/c38/iceblox
+ ammo_band_color = "#658e94"
diff --git a/code/modules/projectiles/boxes_magazines/external/toy.dm b/code/modules/projectiles/boxes_magazines/external/toy.dm
index 4f666e119b84..695388280ebc 100644
--- a/code/modules/projectiles/boxes_magazines/external/toy.dm
+++ b/code/modules/projectiles/boxes_magazines/external/toy.dm
@@ -20,7 +20,7 @@
/obj/item/ammo_box/magazine/toy/pistol
name = "foam force pistol magazine"
icon_state = "9x19p"
- max_ammo = 8
+ max_ammo = 12
multiple_sprites = AMMO_BOX_FULL_EMPTY
/obj/item/ammo_box/magazine/toy/pistol/riot
diff --git a/code/modules/projectiles/boxes_magazines/internal/_cylinder.dm b/code/modules/projectiles/boxes_magazines/internal/_cylinder.dm
index 28df0262352d..7f467881a9d6 100644
--- a/code/modules/projectiles/boxes_magazines/internal/_cylinder.dm
+++ b/code/modules/projectiles/boxes_magazines/internal/_cylinder.dm
@@ -1,6 +1,6 @@
/obj/item/ammo_box/magazine/internal/cylinder
name = "revolver cylinder"
- ammo_type = /obj/item/ammo_casing/a357
+ ammo_type = /obj/item/ammo_casing/c357
caliber = CALIBER_357
max_ammo = 7
diff --git a/code/modules/projectiles/boxes_magazines/internal/revolver.dm b/code/modules/projectiles/boxes_magazines/internal/revolver.dm
index e74a192d6900..1e891abeef28 100644
--- a/code/modules/projectiles/boxes_magazines/internal/revolver.dm
+++ b/code/modules/projectiles/boxes_magazines/internal/revolver.dm
@@ -12,7 +12,7 @@
/obj/item/ammo_box/magazine/internal/cylinder/rus357
name = "\improper Russian revolver cylinder"
- ammo_type = /obj/item/ammo_casing/a357
+ ammo_type = /obj/item/ammo_casing/c357
caliber = CALIBER_357
max_ammo = 6
multiload = FALSE
@@ -21,5 +21,11 @@
/obj/item/ammo_box/magazine/internal/cylinder/rus357/Initialize(mapload)
. = ..()
for (var/i in 1 to max_ammo - 1)
- stored_ammo += new /obj/item/ammo_casing/a357/spent(src)
- stored_ammo += new /obj/item/ammo_casing/a357(src)
+ stored_ammo += new /obj/item/ammo_casing/c357/spent(src)
+ stored_ammo += new /obj/item/ammo_casing/c357(src)
+
+/obj/item/ammo_box/magazine/internal/cylinder/peashooter
+ name = "peashooter cylinder"
+ ammo_type = /obj/item/ammo_casing/pea
+ caliber = CALIBER_PEA
+ max_ammo = 7
diff --git a/code/modules/projectiles/gun.dm b/code/modules/projectiles/gun.dm
index 4c751f5a8995..53cc3439bf7f 100644
--- a/code/modules/projectiles/gun.dm
+++ b/code/modules/projectiles/gun.dm
@@ -53,6 +53,10 @@
/// Even snowflakier way to modify projectile wounding bonus/potential for projectiles fired from this gun.
var/projectile_wound_bonus = 0
+ /// The most reasonable way to modify projectile speed values for projectile fired from this gun. Honest.
+ /// Lower values are better, higher values are worse.
+ var/projectile_speed_multiplier = 1
+
var/spread = 0 //Spread induced by the gun itself.
var/randomspread = 1 //Set to 0 for shotguns. This is used for weapons that don't fire all their bullets at once.
@@ -73,8 +77,9 @@
/obj/item/gun/Initialize(mapload)
. = ..()
- if(pin)
- pin = new pin(src)
+ if(ispath(pin))
+ pin = new pin
+ pin.gun_insert(new_gun = src)
add_seclight_point()
// NOVA EDIT ADDITION BEGIN - GUN SAFETIES AND MANUFACTURER EXAMINE
@@ -600,7 +605,8 @@
/obj/item/gun/proc/unlock() //used in summon guns and as a convience for admins
if(pin)
qdel(pin)
- pin = new /obj/item/firing_pin
+ var/obj/item/firing_pin/new_pin = new
+ new_pin.gun_insert(new_gun = src)
//Happens before the actual projectile creation
/obj/item/gun/proc/before_firing(atom/target,mob/user)
diff --git a/code/modules/projectiles/guns/ballistic.dm b/code/modules/projectiles/guns/ballistic.dm
index de641df6dbfa..e6ec32b8d53f 100644
--- a/code/modules/projectiles/guns/ballistic.dm
+++ b/code/modules/projectiles/guns/ballistic.dm
@@ -479,7 +479,7 @@
/obj/item/gun/ballistic/shoot_live_shot(mob/living/user, pointblank = 0, atom/pbtarget = null, message = 1)
if(isnull(chambered))
return ..()
- if(can_misfire && chambered.can_misfire != FALSE)
+ if(can_misfire)
misfire_probability += misfire_percentage_increment
misfire_probability = clamp(misfire_probability, 0, misfire_probability_cap)
if(chambered.can_misfire)
diff --git a/code/modules/projectiles/guns/ballistic/automatic.dm b/code/modules/projectiles/guns/ballistic/automatic.dm
index 1c158cf4a87d..89c6deaa4d2d 100644
--- a/code/modules/projectiles/guns/ballistic/automatic.dm
+++ b/code/modules/projectiles/guns/ballistic/automatic.dm
@@ -352,3 +352,196 @@
actions_types = list()
fire_sound = 'sound/items/weapons/laser.ogg'
casing_ejector = FALSE
+
+// NT Battle Rifle //
+
+/obj/item/gun/ballistic/automatic/battle_rifle
+ name = "\improper NT BR-38 battle rifle"
+ desc = "Nanotrasen's prototype security weapon, found exclusively in the hands of their private security teams. Chambered in .38 pistol rounds. \
+ Ignore that this makes it technically a carbine. And that it functions as a designated marksman rifle. Marketing weren't being very co-operative \
+ when it came time to name the gun. That, and the endless arguments in board rooms about exactly what designation the gun is meant to be."
+ icon = 'icons/obj/weapons/guns/wide_guns.dmi'
+ icon_state = "battle_rifle"
+ inhand_icon_state = "battle_rifle"
+ base_icon_state = "battle_rifle"
+ worn_icon = 'icons/mob/clothing/back.dmi'
+ worn_icon_state = "battle_rifle"
+ slot_flags = ITEM_SLOT_BACK
+
+ weapon_weight = WEAPON_HEAVY
+ accepted_magazine_type = /obj/item/ammo_box/magazine/m38
+ w_class = WEIGHT_CLASS_BULKY
+ force = 15 //this thing is kind of oversized, okay?
+ mag_display = TRUE
+ projectile_damage_multiplier = 1.2
+ projectile_speed_multiplier = 1.2
+ fire_delay = 2
+ burst_size = 1
+ actions_types = list()
+ spread = 10 //slightly inaccurate in burst fire mode, mostly important for long range shooting
+ fire_sound = 'sound/items/weapons/thermalpistol.ogg'
+ suppressor_x_offset = 8
+
+ /// Determines how many shots we can make before the weapon needs to be maintained.
+ var/shots_before_degradation = 10
+ /// The max number of allowed shots this gun can have before degradation.
+ var/max_shots_before_degradation = 10
+ /// Determines the degradation stage. The higher the value, the more poorly the weapon performs.
+ var/degradation_stage = 0
+ /// Maximum degradation stage.
+ var/degradation_stage_max = 5
+ /// The probability of degradation increasing per shot.
+ var/degradation_probability = 10
+ /// The maximum speed malus for projectile flight speed. Projectiles probably shouldn't move too slowly or else they will start to cause problems.
+ var/maximum_speed_malus = 0.7
+ /// What is our damage multiplier if the gun is emagged?
+ var/emagged_projectile_damage_multiplier = 1.6
+
+ /// Whether or not our gun is suffering an EMP related malfunction.
+ var/emp_malfunction = FALSE
+
+ /// Our timer for when our gun is suffering an extreme malfunction. AKA it is going to explode
+ var/explosion_timer
+
+ SET_BASE_PIXEL(-8, 0)
+
+/obj/item/gun/ballistic/automatic/battle_rifle/Initialize(mapload)
+ . = ..()
+ AddComponent(/datum/component/scope, range_modifier = 2)
+ register_context()
+
+/obj/item/gun/ballistic/automatic/battle_rifle/add_context(atom/source, list/context, obj/item/held_item, mob/user)
+ . = ..()
+
+ if(held_item?.tool_behaviour == TOOL_MULTITOOL && shots_before_degradation < max_shots_before_degradation)
+ context[SCREENTIP_CONTEXT_LMB] = "Reset System"
+ return CONTEXTUAL_SCREENTIP_SET
+
+/obj/item/gun/ballistic/automatic/battle_rifle/examine_more(mob/user)
+ . = ..()
+ . += span_notice("Looking down at the [name], you recall something you read in a promotional pamphlet... ")
+
+ . += span_info("The BR-38 possesses an acceleration rail that launches bullets at higher than typical velocity.\
+ This allows even less powerful cartridges to put out significant amounts of stopping power.")
+
+ . += span_notice("However, you also remember some of the rumors... ")
+
+ . += span_notice("In a sour twist of irony for Nanotrasen's historical issues with ballistics-based security weapons, the BR-38 has one significant flaw. \
+ It is possible for the weapon to suffer from unintended discombulations due to closed heat distribution systems should the weapon be tampered with. \
+ R&D are working on this issue before the weapon sees commercial sales. That, and trying to work out why the weapon's onboard computation systems suffer \
+ from so many calculation errors.")
+
+/obj/item/gun/ballistic/automatic/battle_rifle/examine(mob/user)
+ . = ..()
+ if(shots_before_degradation)
+ . += span_notice("[src] can fire [shots_before_degradation] more times before risking system degradation.")
+ else
+ . += span_notice("[src] is in the process of system degradation. It is currently at stage [degradation_stage] of [degradation_stage_max]. Use a multitool on [src] to recalibrate. Alternatively, insert it into a weapon recharger.")
+
+/obj/item/gun/ballistic/automatic/battle_rifle/update_icon_state()
+ . = ..()
+ if(!shots_before_degradation)
+ inhand_icon_state = "[base_icon_state]-empty"
+ else
+ inhand_icon_state = "[base_icon_state]"
+
+/obj/item/gun/ballistic/automatic/battle_rifle/update_overlays()
+ . = ..()
+ if(degradation_stage)
+ . += "[base_icon_state]_empty"
+ else if(shots_before_degradation)
+ var/ratio_for_overlay = CEILING(clamp(shots_before_degradation / max_shots_before_degradation, 0, 1) * 3, 1)
+ . += "[icon_state]_stage_[ratio_for_overlay]"
+
+/obj/item/gun/ballistic/automatic/battle_rifle/emp_act(severity)
+ . = ..()
+ if (!(. & EMP_PROTECT_SELF) && prob(50 / severity))
+ shots_before_degradation = 0
+ emp_malfunction = TRUE
+ attempt_degradation(TRUE)
+
+/obj/item/gun/ballistic/automatic/battle_rifle/emag_act(mob/user, obj/item/card/emag/emag_card)
+ . = ..()
+ if(obj_flags & EMAGGED)
+ return FALSE
+ obj_flags |= EMAGGED
+ projectile_damage_multiplier = emagged_projectile_damage_multiplier
+ balloon_alert(user, "heat distribution systems deactivated")
+ return TRUE
+
+/obj/item/gun/ballistic/automatic/battle_rifle/multitool_act(mob/living/user, obj/item/tool)
+ if(!tool.use_tool(src, user, 20 SECONDS, volume = 50))
+ balloon_alert(user, "interrupted!")
+ return ITEM_INTERACT_BLOCKING
+
+ emp_malfunction = FALSE
+ shots_before_degradation = initial(shots_before_degradation)
+ degradation_stage = initial(degradation_stage)
+ projectile_speed_multiplier = initial(projectile_speed_multiplier)
+ fire_delay = initial(fire_delay)
+ update_appearance()
+ balloon_alert(user, "system reset")
+ return ITEM_INTERACT_SUCCESS
+
+/obj/item/gun/ballistic/automatic/battle_rifle/try_fire_gun(atom/target, mob/living/user, params)
+ . = ..()
+ if(!chambered || (chambered && !chambered.loaded_projectile))
+ return
+
+ if(shots_before_degradation)
+ shots_before_degradation --
+ return
+
+ else if ((obj_flags & EMAGGED) && degradation_stage == degradation_stage_max && !explosion_timer)
+ perform_extreme_malfunction(user)
+
+ else
+ attempt_degradation(FALSE)
+
+
+/obj/item/gun/ballistic/automatic/battle_rifle/process_fire(atom/target, mob/living/user, message = TRUE, params = null, zone_override = "", bonus_spread = 0)
+ if(chambered.loaded_projectile && prob(75) && (emp_malfunction || degradation_stage == degradation_stage_max))
+ balloon_alert_to_viewers("*click*")
+ playsound(src, dry_fire_sound, dry_fire_sound_volume, TRUE)
+ return
+
+ return ..()
+
+/// Proc to handle weapon degradation. Called when attempting to fire or immediately after an EMP takes place.
+/obj/item/gun/ballistic/automatic/battle_rifle/proc/attempt_degradation(force_increment = FALSE)
+ if(!prob(degradation_probability) && !force_increment || degradation_stage == degradation_stage_max)
+ return //Only update if we actually increment our degradation stage
+
+ degradation_stage = clamp(degradation_stage + (obj_flags & EMAGGED ? 2 : 1), 0, degradation_stage_max)
+ projectile_speed_multiplier = clamp(initial(projectile_speed_multiplier) + degradation_stage * 0.1, initial(projectile_speed_multiplier), maximum_speed_malus)
+ fire_delay = initial(fire_delay) + (degradation_stage * 0.5)
+ do_sparks(1, TRUE, src)
+ update_appearance()
+
+/// Called by /obj/machinery/recharger while inserted: attempts to recalibrate our gun but reducing degradation.
+/obj/item/gun/ballistic/automatic/battle_rifle/proc/attempt_recalibration(restoring_shots_before_degradation = FALSE, recharge_rate = 1)
+ emp_malfunction = FALSE
+
+ if(restoring_shots_before_degradation)
+ shots_before_degradation = clamp(round(shots_before_degradation + recharge_rate, 1), 0, max_shots_before_degradation)
+
+ else
+ degradation_stage = clamp(degradation_stage - 1, 0, degradation_stage_max)
+ if(degradation_stage)
+ projectile_speed_multiplier = clamp(initial(projectile_speed_multiplier) - degradation_stage * 0.1, maximum_speed_malus, initial(projectile_speed_multiplier))
+ fire_delay = initial(fire_delay) + (degradation_stage * 0.5)
+ else
+ projectile_speed_multiplier = initial(projectile_speed_multiplier)
+ fire_delay = initial(fire_delay)
+
+ update_appearance()
+
+/// Proc to handle the countdown for our detonation
+/obj/item/gun/ballistic/automatic/battle_rifle/proc/perform_extreme_malfunction(mob/living/user)
+ balloon_alert(user, "gun is exploding, throw it!")
+ explosion_timer = addtimer(CALLBACK(src, PROC_REF(fucking_explodes_you)), 5 SECONDS, (TIMER_UNIQUE|TIMER_OVERRIDE))
+ playsound(src, 'sound/items/weapons/gun/general/empty_alarm.ogg', 50, FALSE)
+
+/// proc to handle our detonation
+/obj/item/gun/ballistic/automatic/battle_rifle/proc/fucking_explodes_you()
+ explosion(src, devastation_range = 1, heavy_impact_range = 3, light_impact_range = 6, explosion_cause = src)
diff --git a/code/modules/projectiles/guns/ballistic/bows/bow_quivers.dm b/code/modules/projectiles/guns/ballistic/bows/bow_quivers.dm
index 2492f0d4276f..a3de2f4b2fe2 100644
--- a/code/modules/projectiles/guns/ballistic/bows/bow_quivers.dm
+++ b/code/modules/projectiles/guns/ballistic/bows/bow_quivers.dm
@@ -14,7 +14,7 @@
. = ..()
atom_storage.numerical_stacking = TRUE
atom_storage.max_specific_storage = WEIGHT_CLASS_TINY
- atom_storage.max_slots = 40
+ atom_storage.max_slots = max_slots
atom_storage.max_total_storage = 100
atom_storage.set_holdable(/obj/item/ammo_casing/arrow)
diff --git a/code/modules/projectiles/guns/ballistic/revolver.dm b/code/modules/projectiles/guns/ballistic/revolver.dm
index bb589bc011a1..6fe97628fb07 100644
--- a/code/modules/projectiles/guns/ballistic/revolver.dm
+++ b/code/modules/projectiles/guns/ballistic/revolver.dm
@@ -318,3 +318,9 @@
clumsy_check = FALSE
icon_state = "mateba"
+/obj/item/gun/ballistic/revolver/peashooter
+ name = "peashooter"
+ icon_state = "peashooter"
+ desc = "A wild plantlife mutation that shoots hardened peas. Incredible."
+ fire_sound = 'sound/items/weapons/peashoot.ogg'
+ accepted_magazine_type = /obj/item/ammo_box/magazine/internal/cylinder/peashooter
diff --git a/code/modules/projectiles/guns/ballistic/toy.dm b/code/modules/projectiles/guns/ballistic/toy.dm
index bd84e5f79418..dae77b093683 100644
--- a/code/modules/projectiles/guns/ballistic/toy.dm
+++ b/code/modules/projectiles/guns/ballistic/toy.dm
@@ -17,6 +17,7 @@
/obj/item/gun/ballistic/automatic/toy/riot
spawn_magazine_type = /obj/item/ammo_box/magazine/toy/smg/riot
+
/obj/item/gun/ballistic/automatic/pistol/toy
name = "foam force pistol"
desc = "A small, easily concealable toy handgun. Ages 8 and up."
@@ -31,6 +32,9 @@
magazine = new /obj/item/ammo_box/magazine/toy/pistol/riot(src)
return ..()
+/obj/item/gun/ballistic/automatic/pistol/toy/riot/clandestine
+ projectile_damage_multiplier = 1.4
+
/obj/item/gun/ballistic/shotgun/toy
name = "foam force shotgun"
desc = "A toy shotgun with wood furniture and a four-shell capacity underneath. Ages 8 and up."
diff --git a/code/modules/projectiles/guns/energy/dueling.dm b/code/modules/projectiles/guns/energy/dueling.dm
index 9a7fa9aa78be..95707874e729 100644
--- a/code/modules/projectiles/guns/energy/dueling.dm
+++ b/code/modules/projectiles/guns/energy/dueling.dm
@@ -298,15 +298,17 @@
/obj/item/ammo_casing/energy/duel/ready_proj(atom/target, mob/living/user, quiet, zone_override)
. = ..()
- var/obj/projectile/energy/duel/D = loaded_projectile
- D.setting = setting
- D.update_appearance()
+ var/obj/projectile/energy/duel/dueling_projectile = loaded_projectile
+ dueling_projectile.setting = setting
+ dueling_projectile.update_appearance()
+ if(!isturf(target))
+ dueling_projectile.set_homing_target(target)
/obj/item/ammo_casing/energy/duel/fire_casing(atom/target, mob/living/user, params, distro, quiet, zone_override, spread, atom/fired_from)
. = ..()
- var/obj/effect/temp_visual/dueling_chaff/C = new(get_turf(user))
- C.setting = setting
- C.update_appearance()
+ var/obj/effect/temp_visual/dueling_chaff/chaff = new(get_turf(user))
+ chaff.setting = setting
+ chaff.update_appearance()
//Projectile
@@ -314,7 +316,6 @@
name = "dueling beam"
icon_state = "declone"
reflectable = FALSE
- homing = TRUE
var/setting
/obj/projectile/energy/duel/update_icon()
diff --git a/code/modules/projectiles/guns/energy/energy_gun.dm b/code/modules/projectiles/guns/energy/energy_gun.dm
index e826e1392bf2..f6421ab7e1b8 100644
--- a/code/modules/projectiles/guns/energy/energy_gun.dm
+++ b/code/modules/projectiles/guns/energy/energy_gun.dm
@@ -136,7 +136,7 @@
inhand_icon_state = "turretlaser"
slot_flags = null
w_class = WEIGHT_CLASS_HUGE
- ammo_type = list(/obj/item/ammo_casing/energy/electrode, /obj/item/ammo_casing/energy/laser)
+ ammo_type = list(/obj/item/ammo_casing/energy/electrode/ai_turrets, /obj/item/ammo_casing/energy/laser)
weapon_weight = WEAPON_HEAVY
trigger_guard = TRIGGER_GUARD_NONE
ammo_x_offset = 2
diff --git a/code/modules/projectiles/guns/energy/kinetic_accelerator.dm b/code/modules/projectiles/guns/energy/kinetic_accelerator.dm
index d542f2299148..ba63e41af4c0 100644
--- a/code/modules/projectiles/guns/energy/kinetic_accelerator.dm
+++ b/code/modules/projectiles/guns/energy/kinetic_accelerator.dm
@@ -225,6 +225,11 @@
update_appearance()
/obj/projectile/kinetic/on_range()
+ if(!pressure_decrease_active && !lavaland_equipment_pressure_check(get_turf(src)))
+ name = "weakened [name]"
+ damage = damage * pressure_decrease
+ pressure_decrease_active = TRUE
+
strike_thing(loc)
..()
diff --git a/code/modules/projectiles/guns/magic/arcane_barrage.dm b/code/modules/projectiles/guns/magic/arcane_barrage.dm
index 74be54a6323e..3534b99675df 100644
--- a/code/modules/projectiles/guns/magic/arcane_barrage.dm
+++ b/code/modules/projectiles/guns/magic/arcane_barrage.dm
@@ -6,6 +6,7 @@
icon_state = "arcane_barrage"
inhand_icon_state = "arcane_barrage"
base_icon_state = "arcane_barrage"
+ icon_angle = 90
lefthand_file = 'icons/mob/inhands/weapons/guns_lefthand.dmi'
righthand_file = 'icons/mob/inhands/weapons/guns_righthand.dmi'
slot_flags = null
diff --git a/code/modules/projectiles/guns/magic/staff.dm b/code/modules/projectiles/guns/magic/staff.dm
index b3f72a6c9ca2..7fc184e32a13 100644
--- a/code/modules/projectiles/guns/magic/staff.dm
+++ b/code/modules/projectiles/guns/magic/staff.dm
@@ -3,6 +3,7 @@
ammo_type = /obj/item/ammo_casing/magic/nothing
worn_icon_state = null
icon_state = "staff"
+ icon_angle = -45
lefthand_file = 'icons/mob/inhands/weapons/staves_lefthand.dmi'
righthand_file = 'icons/mob/inhands/weapons/staves_righthand.dmi'
item_flags = NEEDS_PERMIT | NO_MAT_REDEMPTION
@@ -242,6 +243,7 @@
ammo_type = /obj/item/ammo_casing/magic/spellblade
icon_state = "spellblade"
inhand_icon_state = "spellblade"
+ icon_angle = -45
lefthand_file = 'icons/mob/inhands/weapons/swords_lefthand.dmi'
righthand_file = 'icons/mob/inhands/weapons/swords_righthand.dmi'
hitsound = 'sound/items/weapons/rapierhit.ogg'
diff --git a/code/modules/projectiles/guns/magic/wand.dm b/code/modules/projectiles/guns/magic/wand.dm
index 0a82f44318e7..6f59998ba61d 100644
--- a/code/modules/projectiles/guns/magic/wand.dm
+++ b/code/modules/projectiles/guns/magic/wand.dm
@@ -4,6 +4,7 @@
ammo_type = /obj/item/ammo_casing/magic
icon_state = "nothingwand"
inhand_icon_state = "wand"
+ icon_angle = -45
lefthand_file = 'icons/mob/inhands/items_lefthand.dmi'
righthand_file = 'icons/mob/inhands/items_righthand.dmi'
base_icon_state = "nothingwand"
diff --git a/code/modules/projectiles/guns/special/blastcannon.dm b/code/modules/projectiles/guns/special/blastcannon.dm
index befb622251a4..80bf245aa330 100644
--- a/code/modules/projectiles/guns/special/blastcannon.dm
+++ b/code/modules/projectiles/guns/special/blastcannon.dm
@@ -49,8 +49,6 @@
/obj/item/gun/blastcannon/Initialize(mapload)
. = ..()
- if(!pin)
- pin = new
RegisterSignal(src, COMSIG_ATOM_INTERNAL_EXPLOSION, PROC_REF(channel_blastwave))
AddElement(/datum/element/update_icon_updates_onmob)
diff --git a/code/modules/projectiles/guns/special/meat_hook.dm b/code/modules/projectiles/guns/special/meat_hook.dm
index 8e79e23b1a90..6419c1c5df2e 100644
--- a/code/modules/projectiles/guns/special/meat_hook.dm
+++ b/code/modules/projectiles/guns/special/meat_hook.dm
@@ -8,6 +8,7 @@
ammo_type = /obj/item/ammo_casing/magic/hook
icon_state = "hook"
inhand_icon_state = "hook"
+ icon_angle = 45
lefthand_file = 'icons/mob/inhands/weapons/melee_lefthand.dmi'
righthand_file = 'icons/mob/inhands/weapons/melee_righthand.dmi'
fire_sound = 'sound/items/weapons/batonextend.ogg'
diff --git a/code/modules/projectiles/pins.dm b/code/modules/projectiles/pins.dm
index 7ee44a10e7d8..46345e769e4b 100644
--- a/code/modules/projectiles/pins.dm
+++ b/code/modules/projectiles/pins.dm
@@ -20,11 +20,6 @@
var/pin_removable = TRUE
var/obj/item/gun/gun
-/obj/item/firing_pin/New(newloc)
- ..()
- if(isgun(newloc))
- gun = newloc
-
/obj/item/firing_pin/interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers)
if(!isgun(interacting_with))
return NONE
@@ -58,8 +53,8 @@
balloon_alert(user, "authentication checks overridden")
return TRUE
-/obj/item/firing_pin/proc/gun_insert(mob/living/user, obj/item/gun/G)
- gun = G
+/obj/item/firing_pin/proc/gun_insert(mob/living/user, obj/item/gun/new_gun)
+ gun = new_gun
forceMove(gun)
gun.pin = src
return TRUE
@@ -165,9 +160,9 @@
return TRUE //The clown op leader antag datum isn't a subtype of the normal clown op antag datum.
return FALSE
-/obj/item/firing_pin/clown/ultra/gun_insert(mob/living/user, obj/item/gun/G)
+/obj/item/firing_pin/clown/ultra/gun_insert(mob/living/user, obj/item/gun/new_gun)
..()
- G.clumsy_check = FALSE
+ new_gun.clumsy_check = FALSE
/obj/item/firing_pin/clown/ultra/gun_remove(mob/living/user)
gun.clumsy_check = initial(gun.clumsy_check)
@@ -244,14 +239,15 @@
if(pin_owner)
. += span_notice("This firing pin is currently authorized to pay into the account of [pin_owner.account_holder].")
-/obj/item/firing_pin/paywall/gun_insert(mob/living/user, obj/item/gun/G)
+/obj/item/firing_pin/paywall/gun_insert(mob/living/user, obj/item/gun/new_gun)
if(!pin_owner)
- to_chat(user, span_warning("ERROR: Please swipe valid identification card before installing firing pin!"))
- user.put_in_hands(src)
+ if(isnull(user))
+ forceMove(new_gun.drop_location())
+ else
+ to_chat(user, span_warning("ERROR: Please swipe valid identification card before installing firing pin!"))
+ user.put_in_hands(src)
return FALSE
- gun = G
- forceMove(gun)
- gun.pin = src
+ ..()
if(multi_payment)
gun.desc += span_notice(" This [gun.name] has a per-shot cost of [payment_amount] credit[( payment_amount > 1 ) ? "s" : ""].")
return TRUE
@@ -260,7 +256,7 @@
/obj/item/firing_pin/paywall/gun_remove(mob/living/user)
- gun.desc = initial(desc)
+ gun.desc = gun::desc
..()
/obj/item/firing_pin/paywall/attackby(obj/item/M, mob/living/user, params)
diff --git a/code/modules/projectiles/projectile.dm b/code/modules/projectiles/projectile.dm
index 6ae8dc85f661..09d60b979856 100644
--- a/code/modules/projectiles/projectile.dm
+++ b/code/modules/projectiles/projectile.dm
@@ -169,8 +169,9 @@
var/impact_light_color_override
// Homing
- /// If the projectile is homing. Warning - this changes projectile's processing logic, reverting it to segmented processing instead of new raymarching logic
- var/homing = FALSE
+ /// If the projectile is currently homing. Warning - this changes projectile's processing logic, reverting it to segmented processing instead of new raymarching logic
+ /// This does not actually set up the projectile to home in on a target - you need to set that up with set_homing_target() on the projectile!
+ VAR_FINAL/homing = FALSE
/// Target the projectile is homing on
var/atom/homing_target
/// Angles per move segment, distance is based on SSprojectiles.pixels_per_decisecond
@@ -428,12 +429,18 @@
// Shooting yourself point-blank
if (firer == original)
original = null
+ if (firer == fired_from)
+ fired_from = null
firer = null
/obj/projectile/proc/original_deleted(datum/source)
SIGNAL_HANDLER
original = null
+/obj/projectile/proc/fired_from_deleted(datum/source)
+ SIGNAL_HANDLER
+ fired_from = null
+
/obj/projectile/proc/on_ricochet(atom/target)
ricochets++
if(!ricochet_auto_aim_angle || !ricochet_auto_aim_range)
@@ -760,11 +767,13 @@
/obj/projectile/proc/fire(fire_angle, atom/direct_target)
LAZYINITLIST(impacted)
- if (fired_from)
- SEND_SIGNAL(fired_from, COMSIG_PROJECTILE_BEFORE_FIRE, src, original)
if (firer)
RegisterSignal(firer, COMSIG_QDELETING, PROC_REF(firer_deleted))
SEND_SIGNAL(firer, COMSIG_PROJECTILE_FIRER_BEFORE_FIRE, src, fired_from, original)
+ if (fired_from)
+ if (firer != fired_from)
+ RegisterSignal(fired_from, COMSIG_QDELETING, PROC_REF(fired_from_deleted))
+ SEND_SIGNAL(fired_from, COMSIG_PROJECTILE_BEFORE_FIRE, src, original)
if (original)
if (firer != original)
RegisterSignal(original, COMSIG_QDELETING, PROC_REF(original_deleted))
@@ -914,6 +923,7 @@
* Normal behavior moves projectiles in a straight line through tiles, but it gets trickier with homing.
* Every pixels_per_decisecond we will stop and call process_homing(), which while a bit rough, does not have a significant performance impact
* This proc needs to be very performant, so do not add overridable logic that can be handled in homing or animations here.
+ * Return is how many tiles we've actually passed (or attempted to pass, if we ended up on a half-move)
*
* pixels_to_move determines how many pixels the projectile should move
* hitscan prevents animation logic from running
@@ -921,8 +931,9 @@
*/
/obj/projectile/proc/process_movement(pixels_to_move, hitscan = FALSE, tile_limit = FALSE)
if (!isturf(loc) || !movement_vector)
- return
+ return 0
var/total_move_distance = pixels_to_move
+ var/movements_done = 0
last_projectile_move = world.time
while (pixels_to_move > 0 && isturf(loc) && !QDELETED(src) && !deletion_queued)
// Because pixel_x/y represents offset and not actual visual position of the projectile, we add 16 pixels to each and cut the excess because projectiles are not meant to be highly offset by default
@@ -957,7 +968,7 @@
if (distance_to_border == INFINITY)
stack_trace("WARNING: Projectile had an empty movement vector and tried to process")
qdel(src)
- return
+ return movements_done
var/distance_to_move = min(distance_to_border, pixels_to_move)
// For homing we cap the maximum distance to move every loop
@@ -979,14 +990,14 @@
// We've hit an invalid turf, end of a z level or smth went wrong
if (!istype(new_turf))
qdel(src)
- return
+ return movements_done
// Move to the next tile
step_towards(src, new_turf)
SEND_SIGNAL(src, COMSIG_PROJECTILE_MOVE_PROCESS_STEP)
// We hit something and got deleted, stop the loop
if (QDELETED(src))
- return
+ return movements_done
if (loc != new_turf)
moving_turfs = FALSE
// If we've impacted something, we need to animate our movement until the actual hit
@@ -996,12 +1007,13 @@
// to move in the next turf to get from entry to impact position
delete_distance = distance_to_move + sqrt((impact_x - entry_x) ** 2 + (impact_y - entry_y) ** 2)
+ movements_done += 1
// We cannot move more than one turf worth of distance per loop, so this is a safe solution
pixels_moved_last_tile += distance_to_move
if (!deletion_queued && pixels_moved_last_tile >= ICON_SIZE_ALL)
reduce_range()
if (QDELETED(src))
- return
+ return movements_done
// Similarly with range out deletion, need to calculate how many pixels we can actually move before deleting
if (deletion_queued)
delete_distance = distance_to_move - (ICON_SIZE_ALL - pixels_moved_last_tile)
@@ -1027,7 +1039,7 @@
if (!move_animate(delete_x, delete_y, animate_time, deleting = TRUE))
animate(src, pixel_x = delete_x, pixel_y = delete_y, time = animate_time, flags = ANIMATION_PARALLEL | ANIMATION_CONTINUE)
animate(alpha = 0, time = 0, flags = ANIMATION_CONTINUE)
- return
+ return movements_done
pixels_to_move -= distance_to_move
// animate() instantly changes pixel_x/y values and just interpolates them client-side so next loop processes properly
@@ -1047,16 +1059,18 @@
// We've hit a timestop field, abort any remaining movement
if (paused)
- return
+ return movements_done
// Prevents long-range high-speed projectiles from ruining the server performance by moving 100 tiles per tick when subsystem is set to a high cap
if (TICK_CHECK)
// If we ran out of time, add whatever distance we're yet to pass to overrun debt to be processed next tick and break the loop
overrun += pixels_to_move
- return
+ return movements_done
if (tile_limit && moving_turfs)
- return
+ return movements_done
+
+ return movements_done
/// Called every time projectile animates its movement, in case child wants to have custom animations.
/// Returning TRUE cancels normal animation
@@ -1068,8 +1082,8 @@
if(!homing_target)
return
var/datum/point/new_point = RETURN_PRECISE_POINT(homing_target)
- new_point.x += clamp(homing_offset_x, 1, world.maxx)
- new_point.y += clamp(homing_offset_y, 1, world.maxy)
+ new_point.pixel_x += homing_offset_x
+ new_point.pixel_y += homing_offset_y
var/new_angle = closer_angle_difference(angle, angle_between_points(RETURN_PRECISE_POINT(src), new_point))
set_angle(angle + clamp(new_angle, -homing_turn_speed, homing_turn_speed))
diff --git a/code/modules/projectiles/projectile/bullets/revolver.dm b/code/modules/projectiles/projectile/bullets/revolver.dm
index e9d6389c89d0..273a0109c569 100644
--- a/code/modules/projectiles/projectile/bullets/revolver.dm
+++ b/code/modules/projectiles/projectile/bullets/revolver.dm
@@ -48,7 +48,6 @@
name = ".38 Rubber bullet"
damage = 10
stamina = 30
- weak_against_armour = TRUE
ricochets_max = 6
ricochet_incidence_leeway = 0
ricochet_chance = 130
@@ -57,6 +56,16 @@
sharpness = NONE
embed_type = null
+/obj/projectile/bullet/c38/match/true
+ name = ".38 True Strike bullet"
+ damage = 15
+ ricochet_auto_aim_range = 3
+ ricochet_auto_aim_angle = 100
+ ricochet_incidence_leeway = 0
+ ricochet_shoots_firer = FALSE
+ shrapnel_type = null
+ embed_type = null
+
// premium .38 ammo from cargo, weak against armor, lower base damage, but excellent at embedding and causing slice wounds at close range
/obj/projectile/bullet/c38/dumdum
name = ".38 DumDum bullet"
@@ -106,9 +115,9 @@
/obj/projectile/bullet/c38/hotshot/on_hit(atom/target, blocked = 0, pierce_hit)
. = ..()
if(iscarbon(target))
- var/mob/living/carbon/M = target
- M.adjust_fire_stacks(6)
- M.ignite_mob()
+ var/mob/living/carbon/criminal_scum = target
+ criminal_scum.adjust_fire_stacks(6)
+ criminal_scum.ignite_mob()
/obj/projectile/bullet/c38/iceblox //see /obj/projectile/temp for the original code
name = ".38 Iceblox bullet"
@@ -119,32 +128,31 @@
/obj/projectile/bullet/c38/iceblox/on_hit(atom/target, blocked = 0, pierce_hit)
. = ..()
if(isliving(target))
- var/mob/living/M = target
- M.adjust_bodytemperature(((100-blocked)/100)*(temperature - M.bodytemperature))
+ var/mob/living/criminal_scum = target
+ criminal_scum.adjust_bodytemperature(((100-blocked)/100)*(temperature - criminal_scum.bodytemperature))
// .357 (Syndie Revolver)
-/obj/projectile/bullet/a357
+/obj/projectile/bullet/c357
name = ".357 bullet"
damage = 60
wound_bonus = -30
-/obj/projectile/bullet/a357/phasic
+/obj/projectile/bullet/c357/phasic
name = ".357 phasic bullet"
icon_state = "gaussphase"
damage = 35
armour_penetration = 100
projectile_phasing = PASSTABLE | PASSGLASS | PASSGRILLE | PASSCLOSEDTURF | PASSMACHINE | PASSSTRUCTURE | PASSDOORS
-/obj/projectile/bullet/a357/heartseeker
+/obj/projectile/bullet/c357/heartseeker
name = ".357 heartseeker bullet"
icon_state = "gauss"
damage = 50
- homing = TRUE
homing_turn_speed = 120
// admin only really, for ocelot memes
-/obj/projectile/bullet/a357/match
+/obj/projectile/bullet/c357/match
name = ".357 match bullet"
ricochets_max = 5
ricochet_chance = 140
@@ -152,3 +160,32 @@
ricochet_auto_aim_range = 6
ricochet_incidence_leeway = 80
ricochet_decay_chance = 1
+
+//gatfruit
+/obj/projectile/bullet/pea
+ name = "pea bullet"
+ damage = 15
+ weak_against_armour = TRUE
+ ricochets_max = 3
+ ricochet_chance = 100
+ icon_state = "pea"
+
+/obj/projectile/bullet/pea/Initialize(mapload)
+ . = ..()
+ create_reagents(100, NO_REACT) //same as the fruit itself, wont ever hit that much though i believe
+
+/obj/projectile/bullet/pea/on_hit(mob/living/carbon/target, blocked = 0, pierce_hit)
+ if(istype(target) && blocked != 100)
+ if(iszombie(target)) // https://www.youtube.com/watch?v=ssZoq1eUK-s
+ target.adjustBruteLoss(15)
+ if(target.can_inject(target_zone = def_zone)) // Pass the hit zone to see if it can inject by whether it hit the head or the body.
+ ..()
+ reagents.trans_to(target, reagents.total_volume, methods = INJECT)
+ return BULLET_ACT_HIT
+ blocked = 100
+ target.visible_message(span_danger("\The [src] is deflected!"), span_userdanger("You are protected against \the [src]!"))
+ . = ..()
+ if(reagents & NO_REACT) //first impact on a noncarbon
+ reagents.flags &= ~(NO_REACT)
+ reagents.handle_reactions()
+
diff --git a/code/modules/projectiles/projectile/energy/stun.dm b/code/modules/projectiles/projectile/energy/stun.dm
index 559d1cf80386..a8db175561b6 100644
--- a/code/modules/projectiles/projectile/energy/stun.dm
+++ b/code/modules/projectiles/projectile/energy/stun.dm
@@ -2,29 +2,398 @@
name = "electrode"
icon_state = "spark"
color = COLOR_YELLOW
- paralyze = 10 SECONDS
- stutter = 10 SECONDS
- jitter = 40 SECONDS
hitsound = 'sound/items/weapons/taserhit.ogg'
- range = 7
+ range = 5
+ reflectable = FALSE
tracer_type = /obj/effect/projectile/tracer/stun
muzzle_type = /obj/effect/projectile/muzzle/stun
impact_type = /obj/effect/projectile/impact/stun
+ /// How much stamina damage will the tase deal in 1 second
+ VAR_PROTECTED/tase_stamina = 60
+ /// Electrodes that follow the projectile
+ VAR_PRIVATE/datum/weakref/beam_weakref
+ /// We need to track who was the ORIGINAL firer of the projectile specifically to ensure deflects work correctly
+ VAR_PRIVATE/datum/weakref/initial_firer_weakref
-/obj/projectile/energy/electrode/on_hit(atom/target, blocked = 0, pierce_hit)
+/obj/projectile/energy/electrode/is_hostile_projectile()
+ return TRUE
+
+/obj/projectile/energy/electrode/Destroy()
+ QDEL_NULL(beam_weakref)
+ return ..()
+
+/obj/projectile/energy/electrode/fire(fire_angle, atom/direct_target)
+ if(firer)
+ beam_weakref = WEAKREF(firer.Beam(
+ BeamTarget = src,
+ icon = 'icons/effects/beam.dmi',
+ icon_state = "electrodes_nozap",
+ maxdistance = maximum_range + 1,
+ beam_type = /obj/effect/ebeam/electrodes_nozap,
+ ))
+ initial_firer_weakref = WEAKREF(firer)
+ return ..()
+
+/obj/projectile/energy/electrode/on_hit(mob/living/target, blocked = 0, pierce_hit)
. = ..()
- if(!ismob(target) || blocked >= 100) //Fully blocked by mob or collided with dense object - burst into sparks!
- do_sparks(1, TRUE, src)
- else if(iscarbon(target))
- var/mob/living/carbon/C = target
- C.adjust_confusion_up_to(15 SECONDS, 30 SECONDS) // NOVA EDIT ADDITION - Electrode jitteriness
- C.add_mood_event("tased", /datum/mood_event/tased)
- SEND_SIGNAL(C, COMSIG_LIVING_MINOR_SHOCK)
- if(HAS_TRAIT(C, TRAIT_HULK))
- C.say(pick(";RAAAAAAAARGH!", ";HNNNNNNNNNGGGGGGH!", ";GWAAAAAAAARRRHHH!", "NNNNNNNNGGGGGGGGHH!", ";AAAAAAARRRGH!" ), forced = "hulk")
- else if(!C.check_stun_immunity(CANKNOCKDOWN))
- addtimer(CALLBACK(C, TYPE_PROC_REF(/mob/living/carbon, do_jitter_animation), 20), 0.5 SECONDS)
+ if(pierce_hit)
+ return
+ if(. == BULLET_ACT_BLOCK || blocked >= 100 || !isliving(target))
+ return
+ // we need a "from", otherwise, where does the electricity come from?
+ if(isnull(fired_from))
+ target.visible_message(
+ span_warning("[src]\s collide with [target] harmlessly[isfloorturf(target.loc) ? ", before falling to [target.loc]" : ""]."),
+ span_notice("[src] collide with you harmlessly[isfloorturf(target.loc) ? ", before falling to [target.loc]" : ""]."),
+ )
+ return
+
+ do_sparks(1, TRUE, src)
+ do_sparks(1, TRUE, fired_from)
+ target.adjust_confusion_up_to(15 SECONDS, 30 SECONDS) // NOVA EDIT ADDITION - Electrode jitteriness
+ target.apply_status_effect(
+ /*type = *//datum/status_effect/tased,
+ /*taser = */fired_from,
+ /*firer = */initial_firer_weakref?.resolve() || firer,
+ /*tase_stamina = */tase_stamina,
+ /*energy_drain = */STANDARD_CELL_CHARGE * 0.05,
+ /*electrode_name = */"\the [src]\s",
+ /*tase_range = */maximum_range + 1,
+ )
/obj/projectile/energy/electrode/on_range() //to ensure the bolt sparks when it reaches the end of its range if it didn't hit a target yet
do_sparks(1, TRUE, src)
- ..()
+ return ..()
+
+/obj/projectile/energy/electrode/ai_turrets
+ tase_stamina = 120
+
+/// Status effect tracking being tased by someone!
+/datum/status_effect/tased
+ id = "being_tased"
+ status_type = STATUS_EFFECT_MULTIPLE
+ alert_type = null
+ tick_interval = 0.25 SECONDS
+ on_remove_on_mob_delete = TRUE
+ /// What atom is tasing us?
+ VAR_PRIVATE/datum/taser
+ /// What atom is using the atom tasing us? Sometimes the same as the taser, such as with turrets.
+ VAR_PRIVATE/atom/movable/firer
+ /// The beam datum representing the taser electrodes
+ VAR_PRIVATE/datum/beam/tase_line
+ /// How much stamina damage does it aim to cause in a second?
+ VAR_FINAL/stamina_per_second = 80
+ /// How much energy does the taser use per tick?
+ VAR_FINAL/energy_drain = STANDARD_CELL_CHARGE * 0.05
+ /// What do we name the electrodes?
+ VAR_FINAL/electrode_name
+ /// How far can the taser reach?
+ VAR_FINAL/tase_range = 6
+
+/datum/status_effect/tased/on_creation(
+ mob/living/new_owner,
+ datum/fired_from,
+ atom/movable/firer,
+ tase_stamina = 80,
+ energy_drain = STANDARD_CELL_CHARGE * 0.05,
+ electrode_name = "the electrodes",
+ tase_range = 6,
+)
+ if(isnull(fired_from) || isnull(firer) || !can_tase_with(fired_from))
+ qdel(src)
+ return
+
+ src.stamina_per_second = tase_stamina
+ src.energy_drain = energy_drain
+ src.electrode_name = electrode_name
+ src.tase_range = tase_range
+
+ . = ..()
+ if(!.)
+ return
+
+ set_taser(fired_from)
+ set_firer(firer)
+
+/// Checks if the passed atom is captable of being used to tase someone
+/datum/status_effect/tased/proc/can_tase_with(datum/with_what)
+ if(istype(with_what, /obj/item/gun/energy))
+ var/obj/item/gun/energy/taser_gun = with_what
+ if(isnull(taser_gun.cell))
+ return FALSE
+
+ else if(istype(with_what, /obj/machinery))
+ var/obj/machinery/taser_machine = with_what
+ if(!taser_machine.is_operational)
+ return FALSE
+
+ return TRUE
+
+/// Actually does the tasing with the passed atom
+/// Returns TRUE if the tasing was successful, FALSE if it failed
+/datum/status_effect/tased/proc/do_tase_with(atom/with_what, seconds_between_ticks)
+ if(!can_see(taser, owner, 5))
+ return FALSE
+ if(istype(with_what, /obj/item/gun/energy))
+ var/obj/item/gun/energy/taser_gun = with_what
+ if(!taser_gun.cell?.use(energy_drain * seconds_between_ticks))
+ return FALSE
+ taser_gun.update_appearance()
+ return TRUE
+
+ if(istype(taser, /obj/machinery))
+ var/obj/machinery/taser_machine = taser
+ if(!taser_machine.is_operational)
+ return FALSE
+ if(!taser_machine.use_energy(energy_drain * seconds_between_ticks, force = FALSE))
+ return FALSE
+ return TRUE
+
+ if(istype(taser, /obj/item/mecha_parts/mecha_equipment))
+ var/obj/item/mecha_parts/mecha_equipment/taser_equipment = taser
+ if(!taser_equipment.chassis \
+ || !taser_equipment.active \
+ || taser_equipment.get_integrity() <= 1 \
+ || taser_equipment.chassis.is_currently_ejecting \
+ || taser_equipment.chassis.equipment_disabled \
+ || !taser_equipment.chassis.use_energy(energy_drain * seconds_between_ticks))
+ return FALSE
+ return TRUE
+
+ return TRUE
+
+/datum/status_effect/tased/on_apply()
+ if(issilicon(owner) || isbot(owner) || isdrone(owner) || HAS_TRAIT(owner, TRAIT_PIERCEIMMUNE))
+ owner.visible_message(span_warning("[capitalize(electrode_name)] fail to catch [owner][isfloorturf(owner.loc) ? ", falling to [owner.loc]" : ""]!"))
+ return FALSE
+
+ RegisterSignal(owner, COMSIG_LIVING_RESIST, PROC_REF(try_remove_taser))
+ RegisterSignal(owner, COMSIG_CARBON_PRE_MISC_HELP, PROC_REF(someome_removing_taser))
+ SEND_SIGNAL(owner, COMSIG_LIVING_MINOR_SHOCK)
+ if(!owner.has_status_effect(type))
+ // does not use the status effect api because we snowflake it a bit
+ owner.throw_alert(type, /atom/movable/screen/alert/tazed)
+ owner.add_mood_event("tased", /datum/mood_event/tased)
+ owner.add_movespeed_modifier(/datum/movespeed_modifier/being_tased)
+ if(!HAS_TRAIT(owner, TRAIT_ANALGESIA))
+ owner.emote("scream")
+ if(HAS_TRAIT(owner, TRAIT_HULK))
+ owner.say(pick(
+ ";RAAAAAAAARGH!",
+ ";HNNNNNNNNNGGGGGGH!",
+ ";GWAAAAAAAARRRHHH!",
+ "NNNNNNNNGGGGGGGGHH!",
+ ";AAAAAAARRRGH!",
+ ), forced = "hulk")
+ if(ishuman(owner))
+ var/mob/living/carbon/human/human_owner = owner
+ human_owner.force_say()
+ return TRUE
+
+/datum/status_effect/tased/on_remove()
+ if(istype(taser, /obj/machinery/porta_turret))
+ var/obj/machinery/porta_turret/taser_turret = taser
+ taser_turret.manual_control = initial(taser_turret.manual_control)
+ taser_turret.always_up = initial(taser_turret.always_up)
+ taser_turret.check_should_process()
+ else if(istype(taser, /obj/machinery/power/emitter))
+ var/obj/machinery/power/emitter/taser_emitter = taser
+ taser_emitter.manual = initial(taser_emitter.manual)
+
+ var/mob/living/mob_firer = firer
+ if(istype(mob_firer))
+ mob_firer.remove_movespeed_modifier(/datum/movespeed_modifier/tasing_someone)
+
+ if(!QDELING(owner) && !owner.has_status_effect(type))
+ owner.adjust_jitter_up_to(10 SECONDS, 1 MINUTES)
+ owner.remove_movespeed_modifier(/datum/movespeed_modifier/being_tased)
+ owner.clear_alert(type)
+
+ taser = null
+ firer = null
+ QDEL_NULL(tase_line)
+
+/datum/status_effect/tased/tick(seconds_between_ticks)
+ if(!do_tase_with(taser, seconds_between_ticks))
+ end_tase()
+ return
+
+ owner.adjust_stutter_up_to(10 SECONDS, 20 SECONDS)
+ owner.adjust_jitter_up_to(20 SECONDS, 30 SECONDS)
+ if(owner.stat <= SOFT_CRIT)
+ owner.do_jitter_animation(INFINITY) // maximum POWER
+
+ // You are damp, that's bad when you're being tased
+ if(owner.fire_stacks < 0)
+ owner.apply_damage(max(1, owner.fire_stacks * -0.5 * seconds_between_ticks), FIRE, spread_damage = TRUE)
+ if(SPT_PROB(25, seconds_between_ticks))
+ do_sparks(1, FALSE, owner)
+
+ // clumsy people might hit their head while being tased
+ if(HAS_TRAIT(owner, TRAIT_CLUMSY) && owner.body_position == LYING_DOWN && SPT_PROB(20, seconds_between_ticks))
+ owner.apply_damage(10, BRUTE, BODY_ZONE_HEAD)
+ playsound(owner, 'sound/effects/tableheadsmash.ogg', 75, TRUE)
+
+ // the actual stunning is here
+ if(!owner.check_stun_immunity(CANSTUN|CANKNOCKDOWN))
+ owner.apply_damage(stamina_per_second * seconds_between_ticks, STAMINA)
+
+/// Sets the passed atom as the "taser"
+/datum/status_effect/tased/proc/set_taser(datum/new_taser)
+ taser = new_taser
+ RegisterSignals(taser, list(COMSIG_QDELETING, COMSIG_ITEM_DROPPED, COMSIG_ITEM_EQUIPPED), PROC_REF(end_tase))
+ RegisterSignal(taser, COMSIG_GUN_TRY_FIRE, PROC_REF(block_firing))
+ // snowflake cases! yay!
+ if(istype(taser, /obj/machinery/porta_turret))
+ var/obj/machinery/porta_turret/taser_turret = taser
+ taser_turret.manual_control = TRUE
+ taser_turret.always_up = TRUE
+ else if(istype(taser, /obj/machinery/power/emitter))
+ var/obj/machinery/power/emitter/taser_emitter = taser
+ taser_emitter.manual = TRUE
+
+/// Sets the passed atom as the person operating the taser, the "firer"
+/datum/status_effect/tased/proc/set_firer(atom/new_firer)
+ firer = new_firer
+ if(taser != firer) // Turrets, notably, are both
+ RegisterSignal(firer, COMSIG_QDELETING, PROC_REF(end_tase))
+
+ RegisterSignal(firer, COMSIG_MOB_CLICKON, PROC_REF(user_cancel_tase))
+
+ // Ensures AI mobs or turrets don't tase players until they run out of power
+ var/mob/living/mob_firer = new_firer
+ if(!istype(mob_firer) || isnull(mob_firer.client))
+ // If multiple things are tasing the same mob, give up sooner, so they can select a new target potentially
+ addtimer(CALLBACK(src, PROC_REF(end_tase)), (owner.has_status_effect(type) != src) ? 2 SECONDS : 8 SECONDS)
+ if(istype(mob_firer))
+ mob_firer.add_movespeed_modifier(/datum/movespeed_modifier/tasing_someone)
+
+ if(firer == owner)
+ return
+
+ tase_line = firer.Beam(
+ BeamTarget = owner,
+ icon = 'icons/effects/beam.dmi',
+ icon_state = "electrodes",
+ maxdistance = tase_range,
+ beam_type = /obj/effect/ebeam/reacting/electrodes,
+ )
+ RegisterSignal(tase_line, COMSIG_BEAM_ENTERED, PROC_REF(disrupt_tase))
+ // RegisterSignal(tase_line, COMSIG_QDELETING, PROC_REF(end_tase)) // NOVA EDIT REMOVAL - redraw deletes the beam, ending the tase, making the redraw useless and causing flaky CI
+ // moves the tase beam up or down if the target moves up or down
+ tase_line.RegisterSignal(owner, COMSIG_LIVING_SET_BODY_POSITION, TYPE_PROC_REF(/datum/beam, redrawing))
+
+/datum/status_effect/tased/proc/block_firing(...)
+ SIGNAL_HANDLER
+ return COMPONENT_CANCEL_GUN_FIRE
+
+/datum/status_effect/tased/proc/user_cancel_tase(mob/living/source, atom/clicked_on, modifiers)
+ SIGNAL_HANDLER
+ if(clicked_on != owner)
+ return NONE
+ if(LAZYACCESS(modifiers, SHIFT_CLICK))
+ return NONE
+ end_tase()
+ source.changeNext_move(CLICK_CD_GRABBING)
+ return COMSIG_MOB_CANCEL_CLICKON
+
+/datum/status_effect/tased/proc/end_tase(...)
+ SIGNAL_HANDLER
+ if(QDELING(src))
+ return
+ owner.visible_message(
+ span_warning("[capitalize(electrode_name)] stop shocking [owner][isfloorturf(owner.loc) ? ", falling to [owner.loc]" : ""]."),
+ span_notice("[capitalize(electrode_name)] stop shocking you[isfloorturf(owner.loc) ? ", falling to [owner.loc]" : ""]."),
+ )
+ qdel(src)
+
+/datum/status_effect/tased/proc/try_remove_taser(datum/source)
+ SIGNAL_HANDLER
+ INVOKE_ASYNC(src, PROC_REF(try_remove_taser_async), owner)
+
+/datum/status_effect/tased/proc/someome_removing_taser(datum/source, mob/living/helper)
+ SIGNAL_HANDLER
+ INVOKE_ASYNC(src, PROC_REF(try_remove_taser_async), helper)
+ return COMPONENT_BLOCK_MISC_HELP
+
+/datum/status_effect/tased/proc/try_remove_taser_async(mob/living/remover)
+ if(DOING_INTERACTION(remover, id))
+ return
+ owner.shake_up_animation()
+ playsound(owner, 'sound/items/weapons/thudswoosh.ogg', 50, TRUE, -1)
+ remover.visible_message(
+ span_warning("[owner] tries to remove [electrode_name][remover == owner ? "" : " from [owner]"]!"),
+ span_notice("You try to remove [electrode_name][remover == owner ? "" : " from [owner]"]!"),
+ )
+ // If embedding was less... difficult to work with, I would make tasers rely on an embedded object to handle this
+ if(!do_after(remover, 5 SECONDS, owner, extra_checks = CALLBACK(src, PROC_REF(try_remove_taser_checks)), interaction_key = id))
+ return
+ remover.visible_message(
+ span_warning("[owner] removes [electrode_name] from [remover == owner ? "[owner.p_their()]" : "[owner]'s"] body!"),
+ span_notice("You remove [electrode_name][remover == owner ? "" : " from [owner]'s body"]!"),
+ )
+ end_tase()
+
+/datum/status_effect/tased/proc/try_remove_taser_checks()
+ return !QDELETED(src)
+
+/datum/status_effect/tased/proc/disrupt_tase(datum/beam/source, obj/effect/ebeam/beam_effect, atom/movable/entering)
+ SIGNAL_HANDLER
+
+ if(!isliving(entering) || entering == taser || entering == firer || entering == owner)
+ return
+ if(entering.pass_flags & (PASSMOB|PASSGRILLE|PASSTABLE))
+ return
+ var/mob/living/disruptor = entering
+ if(!HAS_TRAIT(entering, TRAIT_CLUMSY) || prob(50))
+ if(isliving(firer))
+ // taser firer can lie down so people can cross over it!
+ var/mob/living/firer_living = firer
+ if(firer_living.body_position != disruptor.body_position)
+ return
+ else
+ // otherwise you can limbo under it
+ if(disruptor.body_position == LYING_DOWN)
+ return
+ disruptor.visible_message(
+ span_warning("[disruptor] gets tangled in [electrode_name]!"),
+ span_warning("You get tangled in [electrode_name]!"),
+ )
+ if(!disruptor.check_stun_immunity(CANSTUN|CANKNOCKDOWN))
+ disruptor.apply_damage(90, STAMINA)
+ disruptor.Knockdown(5 SECONDS)
+ disruptor.adjust_jitter_up_to(10 SECONDS, 30 SECONDS)
+ qdel(src)
+
+/// Screen alert for being tased, clicking does a resist
+/atom/movable/screen/alert/tazed
+ name = "Tased!"
+ desc = "You're being tased! You can click this or resist to attempt to stop it, assuming you've not already collapsed."
+ icon_state = "stun"
+ clickable_glow = TRUE
+
+/atom/movable/screen/alert/tazed/Click(location, control, params)
+ . = ..()
+ if(!.)
+ return
+ var/mob/living/clicker = usr
+ clicker.resist()
+
+/obj/effect/ebeam/electrodes_nozap
+ name = "electrodes"
+ alpha = 192
+
+/obj/effect/ebeam/reacting/electrodes
+ name = "electrodes"
+ light_system = OVERLAY_LIGHT
+ light_on = TRUE
+ light_color = COLOR_YELLOW
+ light_power = 1
+ light_range = 1.5
+
+// movespeed mods
+/datum/movespeed_modifier/tasing_someone
+ multiplicative_slowdown = 2
+
+/datum/movespeed_modifier/being_tased
+ multiplicative_slowdown = 4
diff --git a/code/modules/projectiles/projectile/magic.dm b/code/modules/projectiles/projectile/magic.dm
index 1755d43de1f6..c7f457ee427a 100644
--- a/code/modules/projectiles/projectile/magic.dm
+++ b/code/modules/projectiles/projectile/magic.dm
@@ -37,7 +37,7 @@
if(isliving(target))
var/mob/living/victim = target
if(victim.mob_biotypes & MOB_UNDEAD) //negative energy heals the undead
- if(victim.revive(ADMIN_HEAL_ALL, force_grab_ghost = TRUE)) // This heals suicides
+ if(victim.revive(ADMIN_HEAL_ALL & ~HEAL_REFRESH_ORGANS , force_grab_ghost = TRUE)) // This heals suicides
victim.grab_ghost(force = TRUE)
to_chat(victim, span_notice("You rise with a start, you're undead!!!"))
else if(victim.stat != DEAD)
@@ -68,7 +68,7 @@
victim.death()
return
- if(victim.revive(ADMIN_HEAL_ALL, force_grab_ghost = TRUE)) // This heals suicides
+ if(victim.revive(ADMIN_HEAL_ALL & ~HEAL_REFRESH_ORGANS , force_grab_ghost = TRUE)) // This heals suicides
to_chat(victim, span_notice("You rise with a start, you're alive!!!"))
else if(victim.stat != DEAD)
to_chat(victim, span_notice("You feel great!"))
diff --git a/code/modules/reagents/chemistry/items.dm b/code/modules/reagents/chemistry/items.dm
index 1e712db9c23e..f7501f36fc47 100644
--- a/code/modules/reagents/chemistry/items.dm
+++ b/code/modules/reagents/chemistry/items.dm
@@ -132,7 +132,7 @@
out_message += "[round(reagent.volume, 0.01)]u of [reagent.name], Purity: [round(reagent.purity, 0.000001)*100]%, [(scanmode?"[(reagent.overdose_threshold?"Overdose: [reagent.overdose_threshold]u, ":"")]Base pH: [initial(reagent.ph)], Current pH: [reagent.ph].":"Current pH: [reagent.ph].")]\n"
if(scanmode)
out_message += "Analysis: [reagent.description]\n"
- to_chat(user, examine_block(span_notice("[out_message.Join()]")))
+ to_chat(user, boxed_message(span_notice("[out_message.Join()]")))
desc = "An electrode attached to a small circuit box that will display details of a solution. Can be toggled to provide a description of each of the reagents. The screen currently displays detected vol: [round(cont.volume, 0.01)] detected pH:[round(cont.reagents.ph, 0.1)]."
return ITEM_INTERACT_SUCCESS
diff --git a/code/modules/reagents/chemistry/machinery/chem_master.dm b/code/modules/reagents/chemistry/machinery/chem_master.dm
index 1fdced9f41dc..b249dfb2fc8a 100644
--- a/code/modules/reagents/chemistry/machinery/chem_master.dm
+++ b/code/modules/reagents/chemistry/machinery/chem_master.dm
@@ -417,7 +417,7 @@
if(amount == -1) // Set custom amount
var/mob/user = ui.user //Hold a reference of the user if the UI is closed
- amount = tgui_input_number(user, "Enter amount to transfer", "Transfer amount")
+ amount = FLOOR(tgui_input_number(user, "Enter amount to transfer", "Transfer amount", round_value = FALSE), CHEMICAL_VOLUME_ROUNDING)
if(!amount || !user.can_perform_action(src))
return FALSE
diff --git a/code/modules/reagents/chemistry/machinery/reagentgrinder.dm b/code/modules/reagents/chemistry/machinery/reagentgrinder.dm
index 7bf1c06541f3..a6938f5dcf5c 100644
--- a/code/modules/reagents/chemistry/machinery/reagentgrinder.dm
+++ b/code/modules/reagents/chemistry/machinery/reagentgrinder.dm
@@ -9,7 +9,6 @@
circuit = /obj/item/circuitboard/machine/reagentgrinder
pass_flags = PASSTABLE
resistance_flags = ACID_PROOF
- interaction_flags_machine = parent_type::interaction_flags_machine | INTERACT_MACHINE_OFFLINE
anchored_tabletop_offset = 8
/// The maximum weight of items this grinder can hold
@@ -273,45 +272,36 @@
return NONE
/obj/machinery/reagentgrinder/wrench_act(mob/living/user, obj/item/tool)
- if(user.combat_mode)
- return NONE
+ . = NONE
- var/tool_result = ITEM_INTERACT_BLOCKING
if(operating)
balloon_alert(user, "still operating!")
- return tool_result
+ return ITEM_INTERACT_BLOCKING
if(default_unfasten_wrench(user, tool) == SUCCESSFUL_UNFASTEN)
update_appearance(UPDATE_OVERLAYS)
- tool_result = ITEM_INTERACT_SUCCESS
- return tool_result
+ return ITEM_INTERACT_SUCCESS
/obj/machinery/reagentgrinder/screwdriver_act(mob/living/user, obj/item/tool)
- if(user.combat_mode)
- return NONE
+ . = NONE
- var/tool_result = ITEM_INTERACT_BLOCKING
if(operating)
balloon_alert(user, "still operating!")
- return tool_result
+ return ITEM_INTERACT_BLOCKING
if(default_deconstruction_screwdriver(user, icon_state, icon_state, tool))
update_appearance(UPDATE_OVERLAYS)
- tool_result = ITEM_INTERACT_SUCCESS
- return tool_result
+ return ITEM_INTERACT_SUCCESS
/obj/machinery/reagentgrinder/crowbar_act(mob/living/user, obj/item/tool)
- if(user.combat_mode)
- return NONE
+ . = NONE
- var/tool_result = ITEM_INTERACT_BLOCKING
if(operating)
balloon_alert(user, "still operating!")
- return tool_result
+ return ITEM_INTERACT_BLOCKING
if(default_deconstruction_crowbar(tool))
- tool_result = ITEM_INTERACT_SUCCESS
- return tool_result
+ return ITEM_INTERACT_SUCCESS
/obj/machinery/reagentgrinder/proc/on_storage_dump(datum/source, datum/storage/storage, mob/user)
SIGNAL_HANDLER
@@ -328,9 +318,7 @@
/obj/machinery/reagentgrinder/attack_hand_secondary(mob/user, list/modifiers)
. = ..()
- if(. == SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN)
- return
- if(operating || !can_interact(user) || !user.can_perform_action(src, ALLOW_SILICON_REACH | FORBID_TELEKINESIS_REACH))
+ if(. == SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN || !check_interactable(user))
return
replace_beaker(user)
return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
@@ -342,13 +330,11 @@
return attack_hand_secondary(user, modifiers)
/obj/machinery/reagentgrinder/ui_interact(mob/user)
- . = ..()
-
- //some interaction sanity checks
- if(!anchored || operating || !can_interact(user) || !user.can_perform_action(src, ALLOW_SILICON_REACH | FORBID_TELEKINESIS_REACH))
+ //sanity check
+ if(!user.can_perform_action(src, ALLOW_SILICON_REACH | FORBID_TELEKINESIS_REACH))
return
+
var/static/radial_eject = image(icon = 'icons/hud/radial.dmi', icon_state = "radial_eject")
- var/static/radial_mix = image(icon = 'icons/hud/radial.dmi', icon_state = "radial_mix")
//create list of options available
var/list/options = list()
@@ -357,7 +343,7 @@
if((to_process in component_parts) || to_process == beaker)
continue
- if(!QDELETED(beaker) && !beaker.reagents.holder_full() && is_operational && anchored)
+ if(is_operational && anchored && !QDELETED(beaker) && !beaker.reagents.holder_full())
var/static/radial_grind = image(icon = 'icons/hud/radial.dmi', icon_state = "radial_grind")
options["grind"] = radial_grind
@@ -366,16 +352,17 @@
options["eject"] = radial_eject
break
+
//eject action if we have a beaker
if(!QDELETED(beaker))
options["eject"] = radial_eject
//mix reagents present inside
- if(beaker?.reagents.total_volume && is_operational && anchored)
+ if(is_operational && anchored && beaker.reagents.total_volume)
+ var/static/radial_mix = image(icon = 'icons/hud/radial.dmi', icon_state = "radial_mix")
options["mix"] = radial_mix
+
//examine action if Ai is trying to see whats up
if(HAS_AI_ACCESS(user))
- if(machine_stat & NOPOWER)
- return
var/static/radial_examine = image(icon = 'icons/hud/radial.dmi', icon_state = "radial_examine")
options["examine"] = radial_examine
@@ -389,16 +376,18 @@
)
if(!choice)
return
+
+ //act on choice
switch(choice)
if("eject")
replace_beaker(user)
dump_inventory_contents()
if("grind", "juice")
- operate_for(60 DECISECONDS, choice == "juice", user)
+ operate_for(6 SECONDS, choice == "juice", user)
if("mix")
- mix(50 DECISECONDS, user)
+ mix(5 SECONDS, user)
if("examine")
- to_chat(user, examine_block(span_infoplain("[examine(user)]")))
+ to_chat(user, boxed_message(jointext(examine(user), "\n")))
/**
* Checks if the radial menu can interact with this machine
@@ -409,13 +398,7 @@
/obj/machinery/reagentgrinder/proc/check_interactable(mob/user)
PRIVATE_PROC(TRUE)
- if(!can_interact(user))
- return FALSE
-
- if(!anchored || operating || !user.can_perform_action(src, ALLOW_SILICON_REACH))
- return FALSE
-
- return TRUE
+ return !operating && user.can_perform_action(src, ALLOW_SILICON_REACH | FORBID_TELEKINESIS_REACH)
/**
* Grinds/Juices all contents inside the grinder
@@ -428,10 +411,14 @@
/obj/machinery/reagentgrinder/proc/operate_for(time, juicing = FALSE, mob/user)
PRIVATE_PROC(TRUE)
+ if(!anchored || !is_operational || QDELETED(beaker) || beaker.reagents.holder_full())
+ operating = FALSE
+ return
+ operating = TRUE
+
var/duration = time / speed
Shake(pixelshiftx = 1, pixelshifty = 0, duration = duration)
- operating = TRUE
if(!juicing)
playsound(src, 'sound/machines/blender.ogg', 50, TRUE)
else
@@ -486,10 +473,15 @@
/obj/machinery/reagentgrinder/proc/mix(time, mob/user)
PRIVATE_PROC(TRUE)
+ if(!anchored || !is_operational || QDELETED(beaker) || !beaker.reagents.total_volume)
+ operating = FALSE
+ return
+ operating = TRUE
+
var/duration = time / speed
Shake(pixelshiftx = 1, pixelshifty = 0, duration = duration)
- operating = TRUE
+
playsound(src, 'sound/machines/juicer.ogg', 20, TRUE)
addtimer(CALLBACK(src, PROC_REF(mix_complete), duration), duration)
@@ -503,8 +495,7 @@
/obj/machinery/reagentgrinder/proc/mix_complete(duration)
PRIVATE_PROC(TRUE)
- if(QDELETED(beaker) || beaker.reagents.total_volume <= 0)
- operating = FALSE
+ if(QDELETED(src) || !is_operational)
return
//Recipe to make Butter
@@ -516,13 +507,12 @@
tasty_butter.reagents.set_all_reagents_purity(purity)
//Recipe to make Mayonnaise
- if (beaker.reagents.has_reagent(/datum/reagent/consumable/eggyolk))
- beaker.reagents.convert_reagent(/datum/reagent/consumable/eggyolk, /datum/reagent/consumable/mayonnaise)
+ beaker.reagents.convert_reagent(/datum/reagent/consumable/eggyolk, /datum/reagent/consumable/mayonnaise)
//Recipe to make whipped cream
- if (beaker.reagents.has_reagent(/datum/reagent/consumable/cream))
- beaker.reagents.convert_reagent(/datum/reagent/consumable/cream, /datum/reagent/consumable/whipped_cream)
+ beaker.reagents.convert_reagent(/datum/reagent/consumable/cream, /datum/reagent/consumable/whipped_cream)
//power consumed based on the ratio of total reagents mixed
use_energy((active_power_usage * (duration / 1 SECONDS)) * (beaker.reagents.total_volume / beaker.reagents.maximum_volume))
+
operating = FALSE
diff --git a/code/modules/reagents/chemistry/reagents.dm b/code/modules/reagents/chemistry/reagents.dm
index 7176b4c13370..e238ebdd3740 100644
--- a/code/modules/reagents/chemistry/reagents.dm
+++ b/code/modules/reagents/chemistry/reagents.dm
@@ -12,8 +12,6 @@
var/taste_mult = 1
/// reagent holder this belongs to
var/datum/reagents/holder = null
- /// LIQUID, SOLID, GAS
- var/reagent_state = LIQUID
/// Special data associated with the reagent that will be passed on upon transfer to a new holder.
var/list/data
/// increments everytime on_mob_life is called
diff --git a/code/modules/reagents/chemistry/reagents/atmos_gas_reagents.dm b/code/modules/reagents/chemistry/reagents/atmos_gas_reagents.dm
index 5728f6cb24db..58ed84f53dcd 100644
--- a/code/modules/reagents/chemistry/reagents/atmos_gas_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/atmos_gas_reagents.dm
@@ -1,7 +1,6 @@
/datum/reagent/freon
name = "Freon"
description = "A powerful heat absorbent."
- reagent_state = GAS
metabolization_rate = REAGENTS_METABOLISM * 0.5 // Because nitrium/freon/hypernoblium are handled through gas breathing, metabolism must be lower for breathcode to keep up
color = "90560B"
taste_description = "burning"
@@ -18,7 +17,6 @@
/datum/reagent/halon
name = "Halon"
description = "A fire suppression gas that removes oxygen and cools down the area"
- reagent_state = GAS
metabolization_rate = REAGENTS_METABOLISM * 0.5
color = "90560B"
taste_description = "minty"
@@ -36,7 +34,6 @@
/datum/reagent/healium
name = "Healium"
description = "A powerful sleeping agent with healing properties"
- reagent_state = GAS
metabolization_rate = REAGENTS_METABOLISM * 0.5
color = "90560B"
taste_description = "rubbery"
@@ -59,7 +56,6 @@
/datum/reagent/hypernoblium
name = "Hyper-Noblium"
description = "A suppressive gas that stops gas reactions on those who inhale it."
- reagent_state = GAS
metabolization_rate = REAGENTS_METABOLISM * 0.5 // Because nitrium/freon/hyper-nob are handled through gas breathing, metabolism must be lower for breathcode to keep up
color = "90560B"
taste_description = "searingly cold"
@@ -73,7 +69,6 @@
/datum/reagent/nitrium_high_metabolization
name = "Nitrosyl plasmide"
description = "A highly reactive byproduct that stops you from sleeping, while dealing increasing toxin damage over time."
- reagent_state = GAS
metabolization_rate = REAGENTS_METABOLISM * 0.5 // Because nitrium/freon/hypernoblium are handled through gas breathing, metabolism must be lower for breathcode to keep up
color = "E1A116"
taste_description = "sourness"
@@ -93,7 +88,6 @@
/datum/reagent/nitrium_low_metabolization
name = "Nitrium"
description = "A highly reactive gas that makes you feel faster."
- reagent_state = GAS
metabolization_rate = REAGENTS_METABOLISM * 0.5 // Because nitrium/freon/hypernoblium are handled through gas breathing, metabolism must be lower for breathcode to keep up
color = "90560B"
taste_description = "burning"
@@ -111,7 +105,6 @@
/datum/reagent/pluoxium
name = "Pluoxium"
description = "A gas that is eight times more efficient than O2 at lung diffusion with organ healing properties on sleeping patients."
- reagent_state = GAS
metabolization_rate = REAGENTS_METABOLISM * 0.5
color = COLOR_GRAY
taste_description = "irradiated air"
@@ -132,7 +125,6 @@
/datum/reagent/zauker
name = "Zauker"
description = "An unstable gas that is toxic to all living beings."
- reagent_state = GAS
metabolization_rate = REAGENTS_METABOLISM * 0.5
color = "90560B"
taste_description = "bitter"
diff --git a/code/modules/reagents/chemistry/reagents/cat2_medicine_reagents.dm b/code/modules/reagents/chemistry/reagents/cat2_medicine_reagents.dm
index e1a83156421a..a705d7f87bf2 100644
--- a/code/modules/reagents/chemistry/reagents/cat2_medicine_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/cat2_medicine_reagents.dm
@@ -18,7 +18,6 @@
taste_description = "cold and lifeless"
ph = 8
overdose_threshold = 35
- reagent_state = SOLID
inverse_chem_val = 0.3
inverse_chem = /datum/reagent/inverse/helgrasp
var/helbent = FALSE
@@ -77,7 +76,7 @@
if(3) //VICTORY ROYALE
to_chat(affected_mob, span_hierophant("You win, and the malevolent spirits fade away as well as your wounds."))
affected_mob.client.give_award(/datum/award/achievement/jobs/helbitaljanken, affected_mob)
- affected_mob.revive(HEAL_ALL)
+ affected_mob.revive(HEAL_ALL & ~HEAL_REFRESH_ORGANS)
holder.del_reagent(type)
return
@@ -104,7 +103,6 @@
color = "#ECEC8D" // rgb: 236 236 141
ph = 8.2
taste_description = "bitter with a hint of alcohol"
- reagent_state = SOLID
inverse_chem_val = 0.3
inverse_chem = /datum/reagent/inverse/libitoil
chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
@@ -120,7 +118,6 @@
/datum/reagent/medicine/c2/probital
name = "Probital"
description = "Originally developed as a prototype-gym supliment for those looking for quick workout turnover, this oral medication quickly repairs broken muscle tissue but causes lactic acid buildup, tiring the patient. Overdosing can cause extreme drowsiness. An Influx of nutrients promotes the muscle repair even further."
- reagent_state = SOLID
color = "#FFFF6B"
ph = 5.5
overdose_threshold = 20
@@ -171,7 +168,6 @@
/datum/reagent/medicine/c2/lenturi
name = "Lenturi"
description = "Used to treat burns. Applies stomach damage when it leaves your system."
- reagent_state = LIQUID
color = "#6171FF"
ph = 4.7
var/resetting_probability = 0 //What are these for?? Can I remove them?
@@ -180,7 +176,6 @@
inverse_chem_val = 0.4
inverse_chem = /datum/reagent/inverse/lentslurri
-
/datum/reagent/medicine/c2/lenturi/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired)
. = ..()
var/need_mob_update
@@ -192,7 +187,6 @@
/datum/reagent/medicine/c2/aiuri
name = "Aiuri"
description = "Used to treat burns. Does minor eye damage."
- reagent_state = LIQUID
color = "#8C93FF"
ph = 4
var/resetting_probability = 0 //same with this? Old legacy vars that should be removed?
@@ -212,7 +206,6 @@
/datum/reagent/medicine/c2/hercuri
name = "Hercuri"
description = "Not to be confused with element Mercury, this medicine excels in reverting effects of dangerous high-temperature environments. Prolonged exposure can cause hypothermia."
- reagent_state = LIQUID
color = "#F7FFA5"
overdose_threshold = 25
reagent_weight = 0.6
@@ -267,7 +260,6 @@
/datum/reagent/medicine/c2/convermol
name = "Convermol"
description = "Restores oxygen deprivation while producing a lesser amount of toxic byproducts. Both scale with exposure to the drug and current amount of oxygen deprivation. Overdose causes toxic byproducts regardless of oxygen deprivation."
- reagent_state = LIQUID
color = "#FF6464"
overdose_threshold = 35 // at least 2 full syringes +some, this stuff is nasty if left in for long
ph = 5.6
@@ -412,7 +404,6 @@
/datum/reagent/medicine/c2/syriniver //Inject >> SYRINge
name = "Syriniver"
description = "A potent antidote for intravenous use with a narrow therapeutic index, it is considered an active prodrug of musiver."
- reagent_state = LIQUID
color = "#8CDF24" // heavy saturation to make the color blend better
metabolization_rate = 0.75 * REAGENTS_METABOLISM
overdose_threshold = 6
@@ -457,7 +448,6 @@
/datum/reagent/medicine/c2/musiver //MUScles
name = "Musiver"
description = "The active metabolite of syriniver. Causes muscle weakness on overdose"
- reagent_state = LIQUID
color = "#DFD54E"
metabolization_rate = 0.25 * REAGENTS_METABOLISM
overdose_threshold = 25
@@ -499,7 +489,6 @@
/datum/reagent/medicine/c2/synthflesh
name = "Synthflesh"
description = "Heals brute and burn damage at the cost of toxicity (66% of damage healed). 100u or more can restore corpses husked by burns. Touch application only."
- reagent_state = LIQUID
color = "#FFEBEB"
ph = 7.2
chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
@@ -529,7 +518,25 @@
to_chat(carbies, span_danger("You feel your burns and bruises healing! It stings like hell!"))
carbies.add_mood_event("painful_medicine", /datum/mood_event/painful_medicine)
- if(HAS_TRAIT_FROM(exposed_mob, TRAIT_HUSK, BURN) && carbies.getFireLoss() < UNHUSK_DAMAGE_THRESHOLD && (carbies.reagents.get_reagent_amount(/datum/reagent/medicine/c2/synthflesh) + reac_volume >= SYNTHFLESH_UNHUSK_AMOUNT))
+
+ //don't unhusked non husked mobs
+ if (!HAS_TRAIT_FROM(exposed_mob, TRAIT_HUSK, BURN))
+ return
+
+ //don't try to unhusk mobs above burn damage threshold
+ if (carbies.getFireLoss() > UNHUSK_DAMAGE_THRESHOLD)
+ return
+
+ var/datum/reagent/synthflesh = carbies.reagents.has_reagent(/datum/reagent/medicine/c2/synthflesh)
+ var/current_volume = synthflesh ? synthflesh.volume : 0
+ var/current_purity = synthflesh ? synthflesh.purity : 0
+
+ if (methods & TOUCH) //touch does not apply chems to blood, we want to combine the two volumes before attempting to unhusk
+ current_purity = current_volume > 0 ? (current_volume * current_purity + reac_volume * creation_purity) / (current_volume + reac_volume) : creation_purity
+ current_volume += reac_volume
+
+ //when purity = 100%, 60u to unhusk, when purity = 60%, 100u to unhusk.
+ if(current_volume >= SYNTHFLESH_UNHUSK_MAX || current_volume * current_purity >= SYNTHFLESH_UNHUSK_AMOUNT)
carbies.cure_husk(BURN)
carbies.visible_message(span_nicegreen("A rubbery liquid coats [carbies]'s burns. [carbies] looks a lot healthier!")) //we're avoiding using the phrases "burnt flesh" and "burnt skin" here because carbies could be a skeleton or a golem or something
// NOVA EDIT ADDITION BEGIN - non-modular changeling balancing
diff --git a/code/modules/reagents/chemistry/reagents/drinks/drink_reagents.dm b/code/modules/reagents/chemistry/reagents/drinks/drink_reagents.dm
index 21bb32e0d655..529d67197637 100644
--- a/code/modules/reagents/chemistry/reagents/drinks/drink_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/drinks/drink_reagents.dm
@@ -700,7 +700,6 @@
/datum/reagent/consumable/ice
name = "Ice"
description = "Frozen water, your dentist wouldn't like you chewing this."
- reagent_state = SOLID
color = "#619494" // rgb: 97, 148, 148
taste_description = "ice"
chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
diff --git a/code/modules/reagents/chemistry/reagents/drug_reagents.dm b/code/modules/reagents/chemistry/reagents/drug_reagents.dm
index b150f7ef43d7..e32d7be926ea 100644
--- a/code/modules/reagents/chemistry/reagents/drug_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/drug_reagents.dm
@@ -65,7 +65,6 @@
/datum/reagent/drug/nicotine
name = "Nicotine"
description = "Slightly reduces stun times. If overdosed it will deal toxin and oxygen damage."
- reagent_state = LIQUID
color = "#60A584" // rgb: 96, 165, 132
taste_description = "smoke"
trippy = FALSE
@@ -102,14 +101,12 @@
/datum/reagent/drug/krokodil
name = "Krokodil"
description = "Cools and calms you down. If overdosed it will deal significant Brain and Toxin damage."
- reagent_state = LIQUID
color = "#0064B4"
overdose_threshold = 20
ph = 9
chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
addiction_types = list(/datum/addiction/opioids = 18) //7.2 per 2 seconds
-
/datum/reagent/drug/krokodil/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired)
. = ..()
var/high_message = pick("You feel calm.", "You feel collected.", "You feel like you need to relax.")
@@ -137,7 +134,6 @@
/datum/reagent/drug/methamphetamine
name = "Methamphetamine"
description = "Reduces stun times by about 300%, speeds the user up, and allows the user to quickly recover stamina while dealing a small amount of Brain damage. If overdosed the subject will move randomly, laugh randomly, drop items and suffer from Toxin and Brain damage. If addicted the subject will constantly jitter and drool, before becoming dizzy and losing motor control and eventually suffer heavy toxin damage."
- reagent_state = LIQUID
color = "#78C8FA" //best case scenario is the "default", gets muddled depending on purity
overdose_threshold = 20
metabolization_rate = 0.75 * REAGENTS_METABOLISM
@@ -204,7 +200,6 @@
/datum/reagent/drug/bath_salts
name = "Bath Salts"
description = "Makes you impervious to stuns and grants a stamina regeneration buff, but you will be a nearly uncontrollable tramp-bearded raving lunatic."
- reagent_state = LIQUID
color = "#FAFAFA"
overdose_threshold = 20
taste_description = "salt" // because they're bathsalts?
@@ -256,7 +251,6 @@
/datum/reagent/drug/aranesp
name = "Aranesp"
description = "Amps you up, gets you going, and rapidly restores stamina damage. Side effects include breathlessness and toxicity."
- reagent_state = LIQUID
color = "#78FFF0"
chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
addiction_types = list(/datum/addiction/stimulants = 8)
@@ -279,7 +273,6 @@
/datum/reagent/drug/happiness
name = "Happiness"
description = "Fills you with ecstasic numbness and causes minor brain damage. Highly addictive. If overdosed causes sudden mood swings."
- reagent_state = LIQUID
color = "#EE35FF"
overdose_threshold = 20
chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
@@ -323,7 +316,6 @@
/datum/reagent/drug/pumpup
name = "Pump-Up"
description = "Take on the world! A fast acting, hard hitting drug that pushes the limit on what you can handle."
- reagent_state = LIQUID
color = "#e38e44"
metabolization_rate = 2 * REAGENTS_METABOLISM
overdose_threshold = 30
@@ -388,7 +380,6 @@
/datum/reagent/drug/maint/powder
name = "Maintenance Powder"
description = "An unknown powder that you most likely gotten from an assistant, a bored chemist... or cooked yourself. It is a refined form of tar that enhances your mental ability, making you learn stuff a lot faster."
- reagent_state = SOLID
color = "#ffffff"
metabolization_rate = 0.5 * REAGENTS_METABOLISM
overdose_threshold = 15
@@ -416,7 +407,6 @@
/datum/reagent/drug/maint/sludge
name = "Maintenance Sludge"
description = "An unknown sludge that you most likely gotten from an assistant, a bored chemist... or cooked yourself. Half refined, it fills your body with itself, making it more resistant to wounds, but causes toxins to accumulate."
- reagent_state = LIQUID
color = "#203d2c"
metabolization_rate = 2 * REAGENTS_METABOLISM
overdose_threshold = 25
@@ -446,7 +436,6 @@
/datum/reagent/drug/maint/tar
name = "Maintenance Tar"
description = "An unknown tar that you most likely gotten from an assistant, a bored chemist... or cooked yourself. Raw tar, straight from the floor. It can help you with escaping bad situations at the cost of liver damage."
- reagent_state = LIQUID
color = COLOR_BLACK
overdose_threshold = 30
chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
@@ -552,7 +541,6 @@
/datum/reagent/drug/blastoff
name = "bLaStOoF"
description = "A drug for the hardcore party crowd said to enhance ones abilities on the dance floor.\nMost old heads refuse to touch this stuff, perhaps because memories of the luna discoteque incident are seared into their brains."
- reagent_state = LIQUID
color = "#9015a9"
taste_description = "holodisk cleaner"
ph = 5
@@ -678,7 +666,6 @@
/datum/reagent/drug/saturnx
name = "Saturn-X"
description = "This compound was first discovered during the infancy of cloaking technology and at the time thought to be a promising candidate agent. It was withdrawn for consideration after the researchers discovered a slew of associated safety issues including thought disorders and hepatoxicity."
- reagent_state = SOLID
taste_description = "metallic bitterness"
color = "#638b9b"
overdose_threshold = 25
@@ -779,7 +766,6 @@
/datum/reagent/drug/kronkaine
name = "Kronkaine"
description = "A highly illegal stimulant from the edge of the galaxy.\nIt is said the average kronkaine addict causes as much criminal damage as five stick up men, two rascals and one proferssional cambringo hustler combined."
- reagent_state = SOLID
color = "#FAFAFA"
taste_description = "numbing bitterness"
ph = 8
@@ -865,3 +851,54 @@
)
new /obj/structure/bouncy_castle(gored.loc, gored)
gored.gib()
+
+/datum/reagent/drug/syndol
+ name = "Syndol"
+ description = "A potent and addictive hallucinogen used by syndicate agents disorient certain targets. \
+ It is said that the hallucinations it causes are tailored to the user's fears, but tests have been inconclusive, \
+ with subjects in security and assistants reporting wildly different experiences."
+ color = "#c90000"
+ taste_description = "metallic"
+ ph = 7
+ overdose_threshold = 10
+ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
+ addiction_types = list(/datum/addiction/hallucinogens = 20)
+ /// Track the active hallucination we're giving out so we don't replace it by accident
+ VAR_PRIVATE/datum/weakref/active_hallucination_weakref
+
+/datum/reagent/drug/syndol/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired)
+ . = ..()
+ var/obj/item/organ/liver = affected_mob.get_organ_slot(ORGAN_SLOT_LIVER)
+ if(isnull(liver) || !(liver.organ_flags & affected_organ_flags))
+ return
+ // non-trivial but not immediately dangerous liver damage
+ liver.apply_organ_damage(0.5 * REM * seconds_per_tick)
+ // anti-hallucinogens can counteract the effects
+ if(HAS_TRAIT(affected_mob, TRAIT_HALLUCINATION_IMMUNE) || affected_mob.reagents.has_reagent(/datum/reagent/medicine/haloperidol, amount = 3, needs_metabolizing = TRUE))
+ QDEL_NULL(active_hallucination_weakref)
+ return
+
+ // and the main event, funny hallucinations
+ if(active_hallucination_weakref?.resolve())
+ return
+ var/greatest_fear
+ if(HAS_TRAIT(liver, TRAIT_LAW_ENFORCEMENT_METABOLISM))
+ greatest_fear = /datum/hallucination/delusion/preset/syndies
+ else if(HAS_TRAIT(liver, TRAIT_MAINTENANCE_METABOLISM) || HAS_TRAIT(liver, TRAIT_COMEDY_METABOLISM))
+ greatest_fear = /datum/hallucination/delusion/preset/seccies
+
+ if(greatest_fear)
+ // 5 minutes = 15 units, roughly. we cancel the hallucination early when we exit the mob, anyway
+ active_hallucination_weakref = WEAKREF(affected_mob.cause_hallucination(greatest_fear, name, duration = 5 MINUTES, skip_nearby = !overdosed))
+ else
+ // if they're just some random schmuck, give them random hallucinations
+ affected_mob.adjust_hallucinations_up_to(4 SECONDS * REM * seconds_per_tick, 20 SECONDS)
+
+/datum/reagent/drug/syndol/on_mob_end_metabolize(mob/living/affected_mob)
+ . = ..()
+ affected_mob.adjust_hallucinations(-16 SECONDS)
+ QDEL_NULL(active_hallucination_weakref)
+
+/datum/reagent/drug/syndol/overdose_start(mob/living/affected_mob)
+ // no message, just refresh the hallucination
+ QDEL_NULL(active_hallucination_weakref)
diff --git a/code/modules/reagents/chemistry/reagents/food_reagents.dm b/code/modules/reagents/chemistry/reagents/food_reagents.dm
index fcb056251b31..6246eca04802 100644
--- a/code/modules/reagents/chemistry/reagents/food_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/food_reagents.dm
@@ -65,7 +65,6 @@
/datum/reagent/consumable/nutriment
name = "Nutriment"
description = "All the vitamins, minerals, and carbohydrates the body needs in pure form."
- reagent_state = SOLID
nutriment_factor = 15
color = "#664330" // rgb: 102, 67, 48
chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
@@ -133,7 +132,6 @@
description = "All the best vitamins, minerals, and carbohydrates the body needs in pure form."
taste_description = "bitterness"
chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
-
brute_heal = 1
burn_heal = 1
@@ -296,7 +294,6 @@
/datum/reagent/consumable/sugar
name = "Sugar"
description = "The organic compound commonly known as table sugar and sometimes called saccharose. This white, odorless, crystalline powder has a pleasing, sweet taste."
- reagent_state = SOLID
color = COLOR_WHITE // rgb: 255, 255, 255
taste_mult = 1.5 // stop sugar drowning out other flavours
nutriment_factor = 2
@@ -476,7 +473,6 @@
/datum/reagent/consumable/salt
name = "Table Salt"
description = "A salt made of sodium chloride. Commonly used to season food."
- reagent_state = SOLID
color = COLOR_WHITE // rgb: 255,255,255
taste_description = "salt"
penetrates_skin = NONE
@@ -527,7 +523,6 @@
/datum/reagent/consumable/blackpepper
name = "Black Pepper"
description = "A powder ground from peppercorns. *AAAACHOOO*"
- reagent_state = SOLID
// no color (ie, black)
taste_description = "pepper"
chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
@@ -536,7 +531,6 @@
/datum/reagent/consumable/coco
name = "Coco Powder"
description = "A fatty, bitter paste made from coco beans."
- reagent_state = SOLID
nutriment_factor = 5
color = "#302000" // rgb: 48, 32, 0
taste_description = "bitterness"
@@ -613,7 +607,6 @@
/datum/reagent/consumable/dry_ramen
name = "Dry Ramen"
description = "Space age food, since August 25, 1958. Contains dried noodles, vegetables, and chemicals that boil in contact with water."
- reagent_state = SOLID
color = "#302000" // rgb: 48, 32, 0
taste_description = "dry and cheap noodles"
chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
@@ -655,7 +648,6 @@
/datum/reagent/consumable/flour
name = "Flour"
description = "This is what you rub all over yourself to pretend to be a ghost."
- reagent_state = SOLID
color = COLOR_WHITE // rgb: 0, 0, 0
taste_description = "chalky wheat"
chemical_flags = REAGENT_CAN_BE_SYNTHESIZED|REAGENT_AFFECTS_WOUNDS
@@ -719,7 +711,6 @@
/datum/reagent/consumable/rice
name = "Rice"
description = "tiny nutritious grains"
- reagent_state = SOLID
nutriment_factor = 3
color = COLOR_WHITE // rgb: 0, 0, 0
taste_description = "rice"
@@ -729,7 +720,6 @@
/datum/reagent/consumable/rice_flour
name = "Rice Flour"
description = "Flour mixed with Rice"
- reagent_state = SOLID
color = COLOR_WHITE // rgb: 0, 0, 0
taste_description = "chalky wheat with rice"
chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
@@ -737,7 +727,7 @@
/datum/reagent/consumable/vanilla
name = "Vanilla Powder"
description = "A fatty, bitter paste made from vanilla pods."
- reagent_state = SOLID
+
nutriment_factor = 5
color = "#FFFACD"
taste_description = "vanilla"
@@ -896,7 +886,6 @@
/datum/reagent/consumable/nutriment/stabilized
name = "Stabilized Nutriment"
description = "A bioengineered protien-nutrient structure designed to decompose in high saturation. In layman's terms, it won't get you fat."
- reagent_state = SOLID
nutriment_factor = 15
color = "#664330" // rgb: 102, 67, 48
chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
@@ -1009,7 +998,6 @@
description = "A space age artifical sweetener."
nutriment_factor = 0
metabolization_rate = 2 * REAGENTS_METABOLISM
- reagent_state = SOLID
color = COLOR_WHITE // rgb: 255, 255, 255
taste_mult = 8
taste_description = "sweetness"
@@ -1050,7 +1038,6 @@
color = "#D98736"
taste_mult = 2
taste_description = "caramel"
- reagent_state = SOLID
chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
/datum/reagent/consumable/caramel/expose_mob(mob/living/exposed_mob, methods=TOUCH, reac_volume)
@@ -1061,7 +1048,6 @@
/datum/reagent/consumable/char
name = "Char"
description = "Essence of the grill. Has strange properties when overdosed."
- reagent_state = LIQUID
nutriment_factor = 5
color = "#C8C8C8"
taste_mult = 6
@@ -1176,7 +1162,6 @@
name = "Peanut Butter"
description = "A rich, creamy spread produced by grinding peanuts."
taste_description = "peanuts"
- reagent_state = SOLID
color = "#D9A066"
nutriment_factor = 15
chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
diff --git a/code/modules/reagents/chemistry/reagents/impure_reagents.dm b/code/modules/reagents/chemistry/reagents/impure_reagents.dm
index b9c0ee0522df..0f4642509771 100644
--- a/code/modules/reagents/chemistry/reagents/impure_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/impure_reagents.dm
@@ -92,7 +92,6 @@
/datum/reagent/inverse/cryostylane
name = "Cryogelidia"
description = "Freezes the live or dead patient in a cryostasis ice block."
- reagent_state = LIQUID
color = "#03dbfc"
taste_description = "your tongue freezing, shortly followed by your thoughts. Brr!"
ph = 14
diff --git a/code/modules/reagents/chemistry/reagents/impure_reagents/impure_medicine_reagents.dm b/code/modules/reagents/chemistry/reagents/impure_reagents/impure_medicine_reagents.dm
index ebd392f53689..0b5d297669db 100644
--- a/code/modules/reagents/chemistry/reagents/impure_reagents/impure_medicine_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/impure_reagents/impure_medicine_reagents.dm
@@ -174,7 +174,6 @@ Basically, we fill the time between now and 2s from now with hands based off the
name = "Metabolic Inhibition Factor"
description = "This enzyme catalyzes crashes the conversion of nutricious food into healing peptides."
metabolization_rate = 0.0625 * REAGENTS_METABOLISM //slow metabolism rate so the patient can self heal with food even after the troph has metabolized away for amazing reagent efficency.
- reagent_state = SOLID
color = "#b3ff00"
overdose_threshold = 10
ph = 1
@@ -694,7 +693,6 @@ Basically, we fill the time between now and 2s from now with hands based off the
/datum/reagent/inverse/oculine
name = "Oculater"
description = "Temporarily blinds the patient."
- reagent_state = LIQUID
color = "#DDDDDD"
metabolization_rate = 0.1 * REM
addiction_types = list(/datum/addiction/medicine = 3)
@@ -723,7 +721,6 @@ Basically, we fill the time between now and 2s from now with hands based off the
/datum/reagent/impurity/inacusiate
name = "Tinacusiate"
description = "Makes the patient's hearing temporarily funky."
- reagent_state = LIQUID
addiction_types = list(/datum/addiction/medicine = 5.6)
color = "#DDDDFF"
taste_description = "the heat evaporating from your mouth."
@@ -764,7 +761,6 @@ Basically, we fill the time between now and 2s from now with hands based off the
name = "Benzoic Acid"
description = "Robust fertilizer that provides a decent range of benefits for plant life."
taste_description = "flowers"
- reagent_state = LIQUID
color = "#e6c843"
ph = 3.4
tox_damage = 0
@@ -778,7 +774,6 @@ Basically, we fill the time between now and 2s from now with hands based off the
/datum/reagent/inverse/oxandrolone
name = "Oxymetholone"
description = "Anabolic steroid that promotes the growth of muscle during and after exercise."
- reagent_state = LIQUID
color = "#520c23"
taste_description = "sweat"
metabolization_rate = 0.4 * REM
@@ -805,7 +800,6 @@ Basically, we fill the time between now and 2s from now with hands based off the
/datum/reagent/inverse/salbutamol
name = "Bamethan"
description = "Blood thinner that drastically increases the chance of receiving bleeding wounds."
- reagent_state = LIQUID
color = "#ecd4d6"
taste_description = "paint thinner"
ph = 4.5
@@ -816,7 +810,6 @@ Basically, we fill the time between now and 2s from now with hands based off the
/datum/reagent/inverse/pen_acid
name = "Pendetide"
description = "Purges basic toxin healing medications and increases the severity of radiation poisoning."
- reagent_state = LIQUID
color = "#09ff00"
ph = 3.7
taste_description = "venom"
@@ -839,7 +832,6 @@ Basically, we fill the time between now and 2s from now with hands based off the
/datum/reagent/inverse/atropine
name = "Hyoscyamine"
description = "Slowly regenerates all damaged organs, but cannot restore non-functional organs."
- reagent_state = LIQUID
color = "#273333"
ph = 13.6
metabolization_rate = 0.2 * REM
@@ -875,7 +867,6 @@ Basically, we fill the time between now and 2s from now with hands based off the
/datum/reagent/inverse/ammoniated_mercury
name = "Ammoniated Sludge"
description = "A ghastly looking mess of mercury by-product. Causes bursts of manic hysteria."
- reagent_state = LIQUID
color = "#353535"
ph = 10.2
metabolization_rate = 0.4 * REM
@@ -890,7 +881,6 @@ Basically, we fill the time between now and 2s from now with hands based off the
/datum/reagent/inverse/rezadone
name = "Inreziniver"
description = "Makes the user horribly afraid of all things related to carps."
- reagent_state = LIQUID
color = "#c92eb4"
ph = 13.9
metabolization_rate = 0.05 * REM
diff --git a/code/modules/reagents/chemistry/reagents/impure_reagents/impure_toxin_reagents.dm b/code/modules/reagents/chemistry/reagents/impure_reagents/impure_toxin_reagents.dm
index 4e904dcf684d..4a3d837f1db0 100644
--- a/code/modules/reagents/chemistry/reagents/impure_reagents/impure_toxin_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/impure_reagents/impure_toxin_reagents.dm
@@ -22,7 +22,6 @@
/datum/reagent/impurity/methanol
name = "Methanol"
description = "A light, colourless liquid with a distinct smell. Ingestion can lead to blindness. It is a byproduct of organisms processing impure Formaldehyde."
- reagent_state = LIQUID
color = "#aae7e4"
ph = 7
liver_damage = 0
@@ -37,7 +36,6 @@
/datum/reagent/impurity/chloralax
name = "Chloralax"
description = "An oily, colorless and slightly toxic liquid. It is produced when impure choral hydrate is broken down inside an organism."
- reagent_state = LIQUID
color = "#387774"
ph = 7
liver_damage = 0
@@ -51,7 +49,6 @@
/datum/reagent/impurity/rosenol
name = "Rosenol"
description = "A strange, blue liquid that is produced during impure mindbreaker toxin reactions. Historically it has been abused to write poetry."
- reagent_state = LIQUID
color = "#0963ad"
ph = 7
liver_damage = 0
diff --git a/code/modules/reagents/chemistry/reagents/medicine_reagents.dm b/code/modules/reagents/chemistry/reagents/medicine_reagents.dm
index 04bd1d883d31..9efd3eaecb26 100644
--- a/code/modules/reagents/chemistry/reagents/medicine_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/medicine_reagents.dm
@@ -43,7 +43,7 @@
chemical_flags = REAGENT_DEAD_PROCESS
metabolized_traits = list(TRAIT_ANALGESIA)
/// Flags to fullheal every metabolism tick
- var/full_heal_flags = ~(HEAL_BRUTE|HEAL_BURN|HEAL_TOX|HEAL_RESTRAINTS|HEAL_REFRESH_ORGANS)
+ var/full_heal_flags = ~(HEAL_BRUTE|HEAL_BURN|HEAL_TOX|HEAL_RESTRAINTS|HEAL_ORGANS)
// The best stuff there is. For testing/debugging.
/datum/reagent/medicine/adminordrazine/on_hydroponics_apply(obj/machinery/hydroponics/mytray, mob/user)
@@ -76,7 +76,7 @@
name = "Quantum Medicine"
description = "Rare and experimental particles, that apparently swap the user's body with one from an alternate dimension where it's completely healthy."
taste_description = "science"
- full_heal_flags = ~(HEAL_ADMIN|HEAL_BRUTE|HEAL_BURN|HEAL_TOX|HEAL_RESTRAINTS|HEAL_ALL_REAGENTS|HEAL_REFRESH_ORGANS)
+ full_heal_flags = ~(HEAL_ADMIN|HEAL_BRUTE|HEAL_BURN|HEAL_TOX|HEAL_RESTRAINTS|HEAL_ALL_REAGENTS|HEAL_ORGANS)
/datum/reagent/medicine/synaptizine
name = "Synaptizine"
@@ -205,7 +205,6 @@
/datum/reagent/medicine/rezadone
name = "Rezadone"
description = "A powder derived from fish toxin, Rezadone can effectively restore corpses husked by burns as well as treat minor wounds. Overdose will cause intense nausea and minor toxin damage."
- reagent_state = SOLID
color = "#669900" // rgb: 102, 153, 0
overdose_threshold = 30
ph = 12.2
@@ -257,7 +256,6 @@
/datum/reagent/medicine/oxandrolone
name = "Oxandrolone"
description = "Stimulates the healing of severe burns. Extremely rapidly heals severe burns and slowly heals minor ones. Overdose will worsen existing burns."
- reagent_state = LIQUID
color = "#1E8BFF"
metabolization_rate = 0.5 * REAGENTS_METABOLISM
overdose_threshold = 25
@@ -285,7 +283,6 @@
/datum/reagent/medicine/salglu_solution
name = "Saline-Glucose Solution"
description = "Has a 33% chance per metabolism cycle to heal brute and burn damage. Can be used as a temporary blood substitute, as well as slowly speeding blood regeneration."
- reagent_state = LIQUID
color = "#DCDCDC"
metabolization_rate = 0.5 * REAGENTS_METABOLISM
overdose_threshold = 60
@@ -335,7 +332,6 @@
/datum/reagent/medicine/mine_salve
name = "Miner's Salve"
description = "A powerful painkiller. Restores bruising and burns in addition to making the patient believe they are fully healed. Also great for treating severe burn wounds in a pinch."
- reagent_state = LIQUID
color = "#6D6374"
metabolization_rate = 0.4 * REAGENTS_METABOLISM
ph = 2.6
@@ -383,7 +379,6 @@
/datum/reagent/medicine/omnizine
name = "Omnizine"
description = "Slowly heals all damage types. Overdose will cause damage in all types instead."
- reagent_state = LIQUID
color = "#DCDCDC"
metabolization_rate = 0.25 * REAGENTS_METABOLISM
overdose_threshold = 30
@@ -422,7 +417,6 @@
name = "Calomel"
description = "Quickly purges the body of all chemicals except itself. The more health a person has, \
the more toxin damage it will deal. It can heal toxin damage when people have low enough health."
- reagent_state = LIQUID
color = "#c85319"
metabolization_rate = 1 * REAGENTS_METABOLISM
taste_description = "acid"
@@ -451,7 +445,6 @@
description = "Quickly purges the body of toxic chemicals. Heals toxin damage when in a good condition someone has \
no brute and fire damage. When hurt with brute or fire damage, it can deal a great amount of toxin damage. \
When there are no toxins present, it starts slowly purging itself."
- reagent_state = LIQUID
color = "#f3f1f0"
metabolization_rate = 0.1 * REAGENTS_METABOLISM
taste_description = "metallic"
@@ -482,7 +475,6 @@
/datum/reagent/medicine/potass_iodide
name = "Potassium Iodide"
description = "Heals low toxin damage while the patient is irradiated, and will halt the damaging effects of radiation."
- reagent_state = LIQUID
color = "#BAA15D"
metabolization_rate = 2 * REAGENTS_METABOLISM
ph = 12 //It's a reducing agent
@@ -498,7 +490,6 @@
/datum/reagent/medicine/pen_acid
name = "Pentetic Acid"
description = "Reduces massive amounts of toxin damage while purging other chemicals from the body."
- reagent_state = LIQUID
color = "#E6FFF0"
metabolization_rate = 0.5 * REAGENTS_METABOLISM
ph = 1 //One of the best buffers, NEVERMIND!
@@ -518,7 +509,6 @@
/datum/reagent/medicine/sal_acid
name = "Salicylic Acid"
description = "Stimulates the healing of severe bruises. Extremely rapidly heals severe bruising and slowly heals minor ones. Overdose will worsen existing bruising."
- reagent_state = LIQUID
color = "#D2D2D2"
metabolization_rate = 0.5 * REAGENTS_METABOLISM
overdose_threshold = 25
@@ -546,7 +536,6 @@
/datum/reagent/medicine/salbutamol
name = "Salbutamol"
description = "Rapidly restores oxygen deprivation as well as preventing more of it to an extent."
- reagent_state = LIQUID
color = COLOR_CYAN
metabolization_rate = 0.25 * REAGENTS_METABOLISM
ph = 2
@@ -570,7 +559,6 @@
/datum/reagent/medicine/ephedrine
name = "Ephedrine"
description = "Increases resistance to batons and movement speed, giving you hand cramps. Overdose deals toxin damage and inhibits breathing."
- reagent_state = LIQUID
color = "#D2FFFA"
metabolization_rate = 0.5 * REAGENTS_METABOLISM
overdose_threshold = 30
@@ -624,7 +612,6 @@
/datum/reagent/medicine/diphenhydramine
name = "Diphenhydramine"
description = "Rapidly purges the body of Histamine and reduces jitteriness. Slight chance of causing drowsiness."
- reagent_state = LIQUID
color = "#64FFE6"
metabolization_rate = 0.5 * REAGENTS_METABOLISM
ph = 11.5
@@ -640,7 +627,6 @@
/datum/reagent/medicine/morphine
name = "Morphine"
description = "A painkiller that allows the patient to move at full speed even when injured. Causes drowsiness and eventually unconsciousness in high doses. Overdose will cause a variety of effects, ranging from minor to lethal."
- reagent_state = LIQUID
color = "#A9FBFB"
metabolization_rate = 0.5 * REAGENTS_METABOLISM
overdose_threshold = 30
@@ -695,7 +681,6 @@
/datum/reagent/medicine/oculine
name = "Oculine"
description = "Quickly restores eye damage, cures nearsightedness, and has a chance to restore vision to the blind."
- reagent_state = LIQUID
color = "#404040" //oculine is dark grey, inacusiate is light grey
metabolization_rate = 1 * REAGENTS_METABOLISM
overdose_threshold = 30
@@ -774,6 +759,63 @@
if(affected_mob.adjustOrganLoss(ORGAN_SLOT_EYES, 1.5 * REM * seconds_per_tick, required_organ_flag = affected_organ_flags))
. = UPDATE_MOB_HEALTH
+/datum/reagent/medicine/oculine/flumpuline
+ name = "Flumpuline"
+ description = "Often confused for, or sold as, Oculine or a variation thereof. Slowly transmogrifies the eyes of the patient into grotesque stalks - but you'll never need glasses again."
+ color = "#6c596d"
+ metabolization_rate = 0.1 * REAGENTS_METABOLISM
+ overdose_threshold = 5
+ taste_description = "fungus"
+ purity = 1
+ ph = 0.01
+ chemical_flags = REAGENT_DEAD_PROCESS|REAGENT_IGNORE_STASIS|REAGENT_NO_RANDOM_RECIPE|REAGENT_CAN_BE_SYNTHESIZED
+ inverse_chem = /datum/reagent/inverse
+ inverse_chem_val = 0
+ var/static/list/eye_types = list(/obj/item/organ/eyes/snail, /obj/item/organ/eyes/night_vision/mushroom)
+
+/datum/reagent/medicine/oculine/flumpuline/improve_eyesight(mob/living/carbon/affected_mob, obj/item/organ/eyes/eyes)
+ delta_light = 200 //2x better than pure oculine
+ eyes.lighting_cutoff += delta_light
+ affected_mob.update_sight()
+
+/datum/reagent/medicine/oculine/flumpuline/restore_eyesight(mob/living/carbon/affected_mob, obj/item/organ/eyes/eyes)
+ eyes.lighting_cutoff -= delta_light
+ affected_mob.update_sight()
+
+/datum/reagent/medicine/oculine/flumpuline/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired)
+ . = ..()
+ var/obj/item/organ/eyes/eyes = affected_mob.get_organ_slot(ORGAN_SLOT_EYES)
+ // if no eyes or inorganic do nothing. we let already changed eyes go because funny
+ if(!eyes || !IS_ORGANIC_ORGAN(eyes))
+ return .
+
+ if(!prob(2))
+ return .
+
+ flump_eyes(affected_mob, eyes)
+
+// Overdose causes constant eye popping
+/datum/reagent/medicine/oculine/flumpuline/overdose_process(mob/living/affected_mob, seconds_per_tick, times_fired)
+ . = ..()
+ if(!prob(25))
+ return
+ var/obj/item/organ/eyes/eyes = affected_mob.get_organ_slot(ORGAN_SLOT_EYES)
+
+ flump_eyes(affected_mob, eyes)
+
+/datum/reagent/medicine/oculine/flumpuline/proc/flump_eyes(mob/affected_mob, obj/eyes)
+ var/obj/item/organ/eyes/new_eyes = pick(eye_types)
+ new_eyes = new new_eyes(affected_mob)
+ new_eyes.Insert(affected_mob)
+ playsound(affected_mob, 'sound/effects/cartoon_sfx/cartoon_pop.ogg', 50, TRUE)
+ affected_mob.visible_message("[affected_mob]'s [eyes ? eyes : "eye holes"] suddenly sprout stalks and turn into [new_eyes]!")
+ ASYNC
+ affected_mob.emote("scream")
+ sleep(5 SECONDS)
+ if(!QDELETED(eyes))
+ eyes.visible_message(span_danger("[eyes] rapidly turn to dust."))
+ eyes.dust()
+
/datum/reagent/medicine/inacusiate
name = "Inacusiate"
description = "Rapidly repairs damage to the patient's ears to cure deafness, assuming the source of said deafness isn't from genetic mutations, chronic deafness, or a total defecit of ears." //by "chronic" deafness, we mean people with the "deaf" quirk
@@ -808,7 +850,6 @@
/datum/reagent/medicine/atropine
name = "Atropine"
description = "If a patient is in critical condition, rapidly heals all damage types as well as regulating oxygen in the body. Excellent for stabilizing wounded patients, and said to neutralize blood-activated internal explosives found amongst clandestine black op agents."
- reagent_state = LIQUID
color = "#1D3535" //slightly more blue, like epinephrine
metabolization_rate = 0.25 * REAGENTS_METABOLISM
overdose_threshold = 35
@@ -846,7 +887,6 @@
/datum/reagent/medicine/epinephrine
name = "Epinephrine"
description = "Very minor boost to stun resistance. Slowly heals damage if a patient is in critical condition, as well as regulating oxygen loss. Overdose causes weakness and toxin damage."
- reagent_state = LIQUID
color = "#D2FFFA"
metabolization_rate = 0.25 * REAGENTS_METABOLISM
overdose_threshold = 30
@@ -906,7 +946,6 @@
/datum/reagent/medicine/strange_reagent
name = "Strange Reagent"
description = "A miracle drug capable of bringing the dead back to life. Works topically unless anotamically complex, in which case works orally. Cannot revive targets under -%MAXHEALTHRATIO% health."
- reagent_state = LIQUID
color = "#A0E85E"
metabolization_rate = 1.25 * REAGENTS_METABOLISM
taste_description = "magnets"
@@ -1022,7 +1061,6 @@
/datum/reagent/medicine/strange_reagent/fishy_reagent
name = "Fishy Reagent"
description = "This reagent has a chemical composition very similar to that of Strange Reagent, however, it seems to work purely and only on... fish. Or at least, aquatic creatures."
- reagent_state = LIQUID
color = "#5ee8b3"
metabolization_rate = 1.25 * REAGENTS_METABOLISM
taste_description = "magnetic scales"
@@ -1221,7 +1259,6 @@
/datum/reagent/medicine/insulin
name = "Insulin"
description = "Increases sugar depletion rates."
- reagent_state = LIQUID
color = "#FFFFF0"
metabolization_rate = 0.5 * REAGENTS_METABOLISM
ph = 6.7
@@ -1237,7 +1274,6 @@
/datum/reagent/medicine/inaprovaline //is this used anywhere?
name = "Inaprovaline"
description = "Stabilizes the breathing of patients. Good for those in critical condition."
- reagent_state = LIQUID
color = "#A4D8D8"
ph = 8.5
chemical_flags = REAGENT_CAN_BE_SYNTHESIZED|REAGENT_NO_RANDOM_RECIPE
@@ -1251,7 +1287,6 @@
/datum/reagent/medicine/regen_jelly
name = "Regenerative Jelly"
description = "Gradually regenerates all types of damage, without harming slime anatomy."
- reagent_state = LIQUID
color = "#CC23FF"
taste_description = "jelly"
chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
@@ -1280,7 +1315,6 @@
/datum/reagent/medicine/syndicate_nanites //Used exclusively by Syndicate medical cyborgs
name = "Restorative Nanites"
description = "Miniature medical robots that swiftly restore bodily damage."
- reagent_state = SOLID
color = "#555555"
overdose_threshold = 30
ph = 11
@@ -1369,7 +1403,6 @@
/datum/reagent/medicine/haloperidol
name = "Haloperidol"
description = "Increases depletion rates for most stimulating/hallucinogenic drugs. Reduces druggy effects and jitteriness. Severe stamina regeneration penalty, causes drowsiness. Small chance of brain damage."
- reagent_state = LIQUID
color = "#27870a"
metabolization_rate = 0.4 * REAGENTS_METABOLISM
ph = 4.3
@@ -1499,7 +1532,6 @@
/datum/reagent/medicine/modafinil
name = "Modafinil"
description = "Long-lasting sleep suppressant that very slightly reduces stun and knockdown times. Overdosing has horrendous side effects and deals lethal oxygen damage, will knock you unconscious if not dealt with."
- reagent_state = LIQUID
color = "#BEF7D8" // palish blue white
metabolization_rate = 0.1 * REAGENTS_METABOLISM
overdose_threshold = 20 // with the random effects this might be awesome or might kill you at less than 10u (extensively tested)
@@ -1565,7 +1597,6 @@
/datum/reagent/medicine/psicodine
name = "Psicodine"
description = "Suppresses anxiety and other various forms of mental distress. Overdose causes hallucinations and minor toxin damage."
- reagent_state = LIQUID
color = "#07E79E"
metabolization_rate = 0.25 * REAGENTS_METABOLISM
overdose_threshold = 30
@@ -1592,7 +1623,6 @@
name = "Mitogen Metabolism Factor"
description = "This enzyme catalyzes the conversion of nutricious food into healing peptides."
metabolization_rate = 0.0625 * REAGENTS_METABOLISM //slow metabolism rate so the patient can self heal with food even after the troph has metabolized away for amazing reagent efficency.
- reagent_state = SOLID
color = "#FFBE00"
overdose_threshold = 10
inverse_chem_val = 0.1 //Shouldn't happen - but this is so looking up the chem will point to the failed type
@@ -1611,7 +1641,6 @@
/datum/reagent/medicine/silibinin
name = "Silibinin"
description = "A thistle derrived hepatoprotective flavolignan mixture that help reverse damage to the liver."
- reagent_state = SOLID
color = "#FFFFD0"
metabolization_rate = 1.5 * REAGENTS_METABOLISM
chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
@@ -1624,7 +1653,6 @@
/datum/reagent/medicine/polypyr //This is intended to be an ingredient in advanced chems.
name = "Polypyrylium Oligomers"
description = "A purple mixture of short polyelectrolyte chains not easily synthesized in the laboratory. It is valued as an intermediate in the synthesis of the cutting edge pharmaceuticals."
- reagent_state = SOLID
color = "#9423FF"
metabolization_rate = 0.25 * REAGENTS_METABOLISM
overdose_threshold = 50
@@ -1656,7 +1684,6 @@
name = "Granibitaluri" //achieve "GRANular" amounts of C2
description = "A mild painkiller useful as an additive alongside more potent medicines. Speeds up the healing of small wounds and burns, but is ineffective at treating severe injuries. Extremely large doses are toxic, and may eventually cause liver failure."
color = "#E0E0E0"
- reagent_state = LIQUID
overdose_threshold = 50
metabolization_rate = 0.5 * REAGENTS_METABOLISM //same as C2s
chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
@@ -1682,7 +1709,6 @@
/datum/reagent/medicine/coagulant
name = "Sanguirite"
description = "A proprietary coagulant used to help bleeding wounds clot faster. It is purged by heparin."
- reagent_state = LIQUID
color = "#bb2424"
metabolization_rate = 0.25 * REAGENTS_METABOLISM
overdose_threshold = 20
@@ -1796,7 +1822,6 @@
/datum/reagent/medicine/ondansetron
name = "Ondansetron"
description = "Prevents nausea and vomiting. May cause drowsiness and wear."
- reagent_state = LIQUID
color = "#74d3ff"
metabolization_rate = 0.5 * REAGENTS_METABOLISM
ph = 10.6
diff --git a/code/modules/reagents/chemistry/reagents/other_reagents.dm b/code/modules/reagents/chemistry/reagents/other_reagents.dm
index 3fbde56078c7..bb0b40df1de3 100644
--- a/code/modules/reagents/chemistry/reagents/other_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/other_reagents.dm
@@ -1010,7 +1010,6 @@
/datum/reagent/oxygen
name = "Oxygen"
description = "A colorless, odorless gas. Grows on trees but is still pretty valuable."
- reagent_state = GAS
color = COLOR_GRAY
taste_mult = 0 // oderless and tasteless
ph = 9.2//It's acutally a huge range and very dependant on the chemistry but ph is basically a made up var in its implementation anyways
@@ -1026,7 +1025,6 @@
/datum/reagent/copper
name = "Copper"
description = "A highly ductile metal. Things made out of copper aren't very durable, but it makes a decent material for electrical wiring."
- reagent_state = SOLID
color = "#6E3B08" // rgb: 110, 59, 8
taste_description = "metal"
ph = 5.5
@@ -1045,7 +1043,6 @@
/datum/reagent/nitrogen
name = "Nitrogen"
description = "A colorless, odorless, tasteless gas. A simple asphyxiant that can silently displace vital oxygen."
- reagent_state = GAS
color = COLOR_GRAY
taste_mult = 0
chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
@@ -1058,7 +1055,6 @@
/datum/reagent/hydrogen
name = "Hydrogen"
description = "A colorless, odorless, nonmetallic, tasteless, highly combustible diatomic gas."
- reagent_state = GAS
color = COLOR_GRAY
taste_mult = 0
ph = 0.1//Now I'm stuck in a trap of my own design. Maybe I should make -ve phes? (not 0 so I don't get div/0 errors)
@@ -1067,7 +1063,6 @@
/datum/reagent/potassium
name = "Potassium"
description = "A soft, low-melting solid that can easily be cut with a knife. Reacts violently with water."
- reagent_state = SOLID
color = "#A0A0A0" // rgb: 160, 160, 160
taste_description = "sweetness"
chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
@@ -1091,7 +1086,6 @@
/datum/reagent/sulfur
name = "Sulfur"
description = "A sickly yellow solid mostly known for its nasty smell. It's actually much more helpful than it looks in biochemisty."
- reagent_state = SOLID
color = "#BF8C00" // rgb: 191, 140, 0
taste_description = "rotten eggs"
ph = 4.5
@@ -1100,7 +1094,6 @@
/datum/reagent/carbon
name = "Carbon"
description = "A crumbly black solid that, while unexciting on a physical level, forms the base of all known life. Kind of a big deal."
- reagent_state = SOLID
color = "#1C1300" // rgb: 30, 20, 0
taste_description = "sour chalk"
ph = 5
@@ -1116,7 +1109,6 @@
/datum/reagent/chlorine
name = "Chlorine"
description = "A pale yellow gas that's well known as an oxidizer. While it forms many harmless molecules in its elemental form it is far from harmless."
- reagent_state = GAS
color = "#FFFB89" //pale yellow? let's make it light gray
taste_description = "chlorine"
ph = 7.4
@@ -1140,7 +1132,6 @@
/datum/reagent/fluorine
name = "Fluorine"
description = "A comically-reactive chemical element. The universe does not want this stuff to exist in this form in the slightest."
- reagent_state = GAS
color = COLOR_GRAY
taste_description = "acid"
ph = 2
@@ -1161,7 +1152,6 @@
/datum/reagent/sodium
name = "Sodium"
description = "A soft silver metal that can easily be cut with a knife. It's not salt just yet, so refrain from putting it on your chips."
- reagent_state = SOLID
color = COLOR_GRAY
taste_description = "salty metal"
ph = 11.6
@@ -1170,7 +1160,6 @@
/datum/reagent/phosphorus
name = "Phosphorus"
description = "A ruddy red powder that burns readily. Though it comes in many colors, the general theme is always the same."
- reagent_state = SOLID
color = "#832828" // rgb: 131, 40, 40
taste_description = "vinegar"
ph = 6.5
@@ -1185,7 +1174,6 @@
/datum/reagent/lithium
name = "Lithium"
description = "A silver metal, its claim to fame is its remarkably low density. Using it is a bit too effective in calming oneself down."
- reagent_state = SOLID
color = COLOR_GRAY
taste_description = "metal"
ph = 11.3
@@ -1228,7 +1216,6 @@
/datum/reagent/iron
name = "Iron"
description = "Pure iron is a metal."
- reagent_state = SOLID
taste_description = "iron"
material = /datum/material/iron
chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
@@ -1243,7 +1230,6 @@
/datum/reagent/gold
name = "Gold"
description = "Gold is a dense, soft, shiny metal and the most malleable and ductile metal known."
- reagent_state = SOLID
color = "#F7C430" // rgb: 247, 196, 48
taste_description = "expensive metal"
material = /datum/material/gold
@@ -1252,7 +1238,6 @@
/datum/reagent/silver
name = "Silver"
description = "A soft, white, lustrous transition metal, it has the highest electrical conductivity of any element and the highest thermal conductivity of any metal."
- reagent_state = SOLID
color = "#D0D0D0" // rgb: 208, 208, 208
taste_description = "expensive yet reasonable metal"
material = /datum/material/silver
@@ -1261,7 +1246,6 @@
/datum/reagent/uranium
name = "Uranium"
description = "A jade-green metallic chemical element in the actinide series, weakly radioactive."
- reagent_state = SOLID
color = "#5E9964" //this used to be silver, but liquid uranium can still be green and it's more easily noticeable as uranium like this so why bother?
taste_description = "the inside of a reactor"
ph = 4
@@ -1294,7 +1278,6 @@
/datum/reagent/uranium/radium
name = "Radium"
description = "Radium is an alkaline earth metal. It is extremely radioactive."
- reagent_state = SOLID
color = "#00CC00" // ditto
taste_description = "the colour blue and regret"
tox_damage = 1
@@ -1305,7 +1288,6 @@
/datum/reagent/bluespace
name = "Bluespace Dust"
description = "A dust composed of microscopic bluespace crystals, with minor space-warping properties."
- reagent_state = SOLID
color = "#0000CC"
taste_description = "fizzling blue"
material = /datum/material/bluespace
@@ -1331,7 +1313,6 @@
/datum/reagent/aluminium
name = "Aluminium"
description = "A silvery white and ductile member of the boron group of chemical elements."
- reagent_state = SOLID
color = "#A8A8A8" // rgb: 168, 168, 168
taste_description = "metal"
chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
@@ -1339,7 +1320,6 @@
/datum/reagent/silicon
name = "Silicon"
description = "A tetravalent metalloid, silicon is less reactive than its chemical analog carbon."
- reagent_state = SOLID
color = "#A8A8A8" // rgb: 168, 168, 168
taste_mult = 0
material = /datum/material/glass
@@ -1410,12 +1390,7 @@
if(reac_volume < 1)
return
- exposed_turf.wash(clean_types)
- for(var/am in exposed_turf)
- var/atom/movable/movable_content = am
- if(ismopable(movable_content)) // Mopables will be cleaned anyways by the turf wash
- continue
- movable_content.wash(clean_types)
+ exposed_turf.wash(clean_types, TRUE)
for(var/mob/living/basic/slime/exposed_slime in exposed_turf)
exposed_slime.adjustToxLoss(rand(5,10))
@@ -1428,9 +1403,9 @@
/datum/reagent/space_cleaner/on_burn_wound_processing(datum/wound/burn/flesh/burn_wound)
burn_wound.sanitization += 0.3
if(prob(5))
- to_chat(burn_wound.victim, span_notice("Your [burn_wound] stings and burns from the [src] covering it! It does look pretty clean though."))
- burn_wound.victim.adjustToxLoss(0.5)
- burn_wound.limb.receive_damage(burn = 0.5, wound_bonus = CANT_WOUND)
+ to_chat(burn_wound.victim, span_notice("Your [burn_wound] stings and burns from [src] covering it! It does look pretty clean though."))
+ burn_wound.victim.apply_damage(0.5, TOX)
+ burn_wound.victim.apply_damage(0.5, BURN, burn_wound.limb, wound_bonus = CANT_WOUND)
/datum/reagent/space_cleaner/ez_clean
name = "EZ Clean"
@@ -1562,7 +1537,6 @@
/datum/reagent/foaming_agent// Metal foaming agent. This is lithium hydride. Add other recipes (e.g. LiH + H2O -> LiOH + H2) eventually.
name = "Foaming Agent"
description = "An agent that yields metallic foam when mixed with light metal and a strong acid."
- reagent_state = SOLID
color = "#664B63" // rgb: 102, 75, 99
taste_description = "metal"
ph = 11.5
@@ -1571,7 +1545,6 @@
/datum/reagent/smart_foaming_agent //Smart foaming agent. Functions similarly to metal foam, but conforms to walls.
name = "Smart Foaming Agent"
description = "An agent that yields metallic foam which conforms to area boundaries when mixed with light metal and a strong acid."
- reagent_state = SOLID
color = "#664B63" // rgb: 102, 75, 99
taste_description = "metal"
ph = 11.8
@@ -1580,7 +1553,6 @@
/datum/reagent/ammonia
name = "Ammonia"
description = "A caustic substance commonly used in fertilizer or household cleaners."
- reagent_state = GAS
color = "#404030" // rgb: 64, 64, 48
taste_description = "mordant"
ph = 11.6
@@ -1614,7 +1586,6 @@
/datum/reagent/carbondioxide
name = "Carbon Dioxide"
- reagent_state = GAS
description = "A gas commonly produced by burning carbon fuels. You're constantly producing this in your lungs."
color = "#B0B0B0" // rgb : 192, 192, 192
taste_description = "something unknowable"
@@ -1630,7 +1601,6 @@
name = "Nitrous Oxide"
description = "A potent oxidizer used as fuel in rockets and as an anaesthetic during surgery. As it is an anticoagulant, nitrous oxide is best \
used alongside sanguirite to allow blood clotting to continue."
- reagent_state = LIQUID
metabolization_rate = 1.5 * REAGENTS_METABOLISM
color = COLOR_GRAY
taste_description = "sweetness"
@@ -1680,11 +1650,11 @@
/datum/reagent/colorful_reagent/powder
name = "Mundane Powder" //the name's a bit similar to the name of colorful reagent, but hey, they're practically the same chem anyway
- var/colorname = "none"
description = "A powder that is used for coloring things."
- reagent_state = SOLID
color = COLOR_WHITE
taste_description = "the back of class"
+ can_color_organs = TRUE
+ var/colorname = "none"
/datum/reagent/colorful_reagent/powder/New()
if(colorname == "none")
@@ -1759,51 +1729,51 @@
name = "White Powder"
colorname = "white"
color = COLOR_WHITE
- random_color_list = list(COLOR_WHITE) //doesn't actually change appearance at all
+ random_color_list = list(COLOR_WHITE)
chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
/* used by crayons, can't color living things but still used for stuff like food recipes */
/datum/reagent/colorful_reagent/powder/red/crayon
name = "Red Crayon Powder"
- can_colour_mobs = FALSE
+ can_color_mobs = FALSE
chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
/datum/reagent/colorful_reagent/powder/orange/crayon
name = "Orange Crayon Powder"
- can_colour_mobs = FALSE
+ can_color_mobs = FALSE
chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
/datum/reagent/colorful_reagent/powder/yellow/crayon
name = "Yellow Crayon Powder"
- can_colour_mobs = FALSE
+ can_color_mobs = FALSE
chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
/datum/reagent/colorful_reagent/powder/green/crayon
name = "Green Crayon Powder"
- can_colour_mobs = FALSE
+ can_color_mobs = FALSE
chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
/datum/reagent/colorful_reagent/powder/blue/crayon
name = "Blue Crayon Powder"
- can_colour_mobs = FALSE
+ can_color_mobs = FALSE
chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
/datum/reagent/colorful_reagent/powder/purple/crayon
name = "Purple Crayon Powder"
- can_colour_mobs = FALSE
+ can_color_mobs = FALSE
chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
//datum/reagent/colorful_reagent/powder/invisible/crayon
/datum/reagent/colorful_reagent/powder/black/crayon
name = "Black Crayon Powder"
- can_colour_mobs = FALSE
+ can_color_mobs = FALSE
chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
/datum/reagent/colorful_reagent/powder/white/crayon
name = "White Crayon Powder"
- can_colour_mobs = FALSE
+ can_color_mobs = FALSE
chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
//////////////////////////////////Hydroponics stuff///////////////////////////////
@@ -1898,7 +1868,6 @@
/datum/reagent/fuel/oil
name = "Oil"
description = "Burns in a small smoky fire, can be used to get Ash."
- reagent_state = LIQUID
color = "#2D2D2D"
taste_description = "oil"
burning_temperature = 1200//Oil is crude
@@ -1910,7 +1879,6 @@
/datum/reagent/stable_plasma
name = "Stable Plasma"
description = "Non-flammable plasma locked into a liquid form that cannot ignite or become gaseous/solid."
- reagent_state = LIQUID
color = "#2D2D2D"
taste_description = "bitterness"
taste_mult = 1.5
@@ -1924,7 +1892,6 @@
/datum/reagent/iodine
name = "Iodine"
description = "Commonly added to table salt as a nutrient. On its own it tastes far less pleasing."
- reagent_state = LIQUID
color = "#BC8A00"
taste_description = "metal"
ph = 4.5
@@ -1933,7 +1900,6 @@
/datum/reagent/carpet
name = "Carpet"
description = "For those that need a more creative way to roll out a red carpet."
- reagent_state = LIQUID
color = "#771100"
taste_description = "carpet" // Your tounge feels furry.
var/carpet_type = /turf/open/floor/carpet
@@ -2152,7 +2118,6 @@
/datum/reagent/bromine
name = "Bromine"
description = "A brownish liquid that's highly reactive. Useful for stopping free radicals, but not intended for human consumption."
- reagent_state = LIQUID
color = "#D35415"
taste_description = "chemicals"
ph = 7.8
@@ -2161,7 +2126,6 @@
/datum/reagent/pentaerythritol
name = "Pentaerythritol"
description = "Slow down, it ain't no spelling bee!"
- reagent_state = SOLID
color = "#E66FFF"
taste_description = "acid"
chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
@@ -2169,7 +2133,6 @@
/datum/reagent/acetaldehyde
name = "Acetaldehyde"
description = "Similar to plastic. Tastes like dead people."
- reagent_state = SOLID
color = "#EEEEEF"
taste_description = "dead people" //made from formaldehyde, ya get da joke ?
chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
@@ -2177,7 +2140,6 @@
/datum/reagent/acetone_oxide
name = "Acetone Oxide"
description = "Enslaved oxygen"
- reagent_state = LIQUID
color = "#966199cb"
taste_description = "acid"
chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
@@ -2191,7 +2153,6 @@
/datum/reagent/phenol
name = "Phenol"
description = "An aromatic ring of carbon with a hydroxyl group. A useful precursor to some medicines, but has no healing properties on its own."
- reagent_state = LIQUID
color = "#E7EA91"
taste_description = "acid"
ph = 5.5
@@ -2200,7 +2161,6 @@
/datum/reagent/ash
name = "Ash"
description = "Supposedly phoenixes rise from these, but you've never seen it."
- reagent_state = LIQUID
color = "#515151"
taste_description = "ash"
ph = 6.5
@@ -2215,7 +2175,6 @@
/datum/reagent/acetone
name = "Acetone"
description = "A slick, slightly carcinogenic liquid. Has a multitude of mundane uses in everyday life."
- reagent_state = LIQUID
color = "#AF14B7"
taste_description = "acid"
chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
@@ -2223,12 +2182,16 @@
/datum/reagent/colorful_reagent
name = "Colorful Reagent"
description = "Thoroughly sample the rainbow."
- reagent_state = LIQUID
var/list/random_color_list = list("#00aedb","#a200ff","#f47835","#d41243","#d11141","#00b159","#00aedb","#f37735","#ffc425","#008744","#0057e7","#d62d20","#ffa700")
color = COLOR_GRAY
taste_description = "rainbows"
- var/can_colour_mobs = TRUE
chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
+ /// Whenever this reagent can color mob limbs and organs upon exposure
+ var/can_color_mobs = TRUE
+ /// Whenever this reagent can color mob equipment when they're exposed to it externally
+ var/can_color_clothing = TRUE
+ /// Whenever this reagent can color mob organs when taken internally
+ var/can_color_organs = FALSE // False by default as this would cause chaotic flickering of victim's eyes
var/datum/callback/color_callback
/datum/reagent/colorful_reagent/New()
@@ -2245,21 +2208,68 @@
color_callback = null
color = pick(random_color_list)
+/datum/reagent/colorful_reagent/expose_mob(mob/living/exposed_mob, methods, reac_volume, show_message, touch_protection)
+ . = ..()
+ var/picked_color = pick(random_color_list)
+ var/color_filter = color_transition_filter(picked_color, SATURATION_OVERRIDE)
+ if (can_color_clothing && (methods & TOUCH|VAPOR|INHALE))
+ var/include_flags = INCLUDE_HELD|INCLUDE_ACCESSORIES
+ if (methods & VAPOR|INHALE)
+ include_flags |= INCLUDE_POCKETS
+ // Not as anyting because this can produce nulls with the flags we passed
+ for (var/obj/item/to_color in exposed_mob.get_equipped_items(include_flags))
+ to_color.add_atom_colour(color_filter, WASHABLE_COLOUR_PRIORITY)
+
+ if (ishuman(exposed_mob))
+ var/mob/living/carbon/human/exposed_human = exposed_mob
+ exposed_human.set_facial_haircolor(picked_color, update = FALSE)
+ exposed_human.set_haircolor(picked_color)
+
+ if (!can_color_mobs)
+ return
+
+ if (!iscarbon(exposed_mob))
+ exposed_mob.add_atom_colour(color_filter, WASHABLE_COLOUR_PRIORITY)
+ return
+
+ if (!(methods & TOUCH|VAPOR|INHALE))
+ return
+
+ var/mob/living/carbon/exposed_carbon = exposed_mob
+ for (var/obj/item/bodypart/part as anything in exposed_carbon.bodyparts)
+ part.add_atom_colour(color_filter, WASHABLE_COLOUR_PRIORITY)
+
+ for (var/obj/item/organ/organ as anything in exposed_carbon.organs)
+ organ.add_atom_colour(color_filter, WASHABLE_COLOUR_PRIORITY)
+
/datum/reagent/colorful_reagent/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired)
. = ..()
- if(can_colour_mobs)
- affected_mob.add_atom_colour(pick(random_color_list), WASHABLE_COLOUR_PRIORITY)
+
+ if (!iscarbon(affected_mob))
+ if (can_color_mobs)
+ affected_mob.add_atom_colour(color_transition_filter(pick(random_color_list), SATURATION_OVERRIDE), WASHABLE_COLOUR_PRIORITY)
+ return
+
+ if(!can_color_organs)
+ return
+
+ var/mob/living/carbon/carbon_mob = affected_mob
+ var/color_priority = WASHABLE_COLOUR_PRIORITY
+ if (current_cycle >= 30) // Seeps deep into your tissues
+ color_priority = FIXED_COLOUR_PRIORITY
+
+ for (var/obj/item/organ/organ as anything in carbon_mob.organs)
+ organ.add_atom_colour(color_transition_filter(pick(random_color_list), SATURATION_OVERRIDE), color_priority)
/// Colors anything it touches a random color.
/datum/reagent/colorful_reagent/expose_atom(atom/exposed_atom, reac_volume)
. = ..()
- if(!isliving(exposed_atom) || can_colour_mobs)
- exposed_atom.add_atom_colour(pick(random_color_list), WASHABLE_COLOUR_PRIORITY)
+ if(!isliving(exposed_atom))
+ exposed_atom.add_atom_colour(color_transition_filter(pick(random_color_list), SATURATION_OVERRIDE), WASHABLE_COLOUR_PRIORITY)
/datum/reagent/hair_dye
name = "Quantum Hair Dye"
description = "Has a high chance of making you look like a mad scientist."
- reagent_state = LIQUID
var/list/potential_colors = list("#00aadd","#aa00ff","#ff7733","#dd1144","#dd1144","#00bb55","#00aadd","#ff7733","#ffcc22","#008844","#0055ee","#dd2222","#ffaa00") // fucking hair code
color = COLOR_GRAY
taste_description = "sourness"
@@ -2275,7 +2285,7 @@
/datum/reagent/hair_dye/expose_mob(mob/living/exposed_mob, methods=TOUCH, reac_volume, show_message=TRUE, touch_protection=FALSE)
. = ..()
- if(!(methods & (TOUCH|VAPOR)) || !ishuman(exposed_mob))
+ if(!(methods & (TOUCH|VAPOR|INHALE)) || !ishuman(exposed_mob))
return
var/mob/living/carbon/human/exposed_human = exposed_mob
@@ -2286,7 +2296,6 @@
/datum/reagent/barbers_aid
name = "Barber's Aid"
description = "A solution to hair loss across the world."
- reagent_state = LIQUID
color = "#A86B45" //hair is brown
taste_description = "sourness"
penetrates_skin = NONE
@@ -2309,7 +2318,6 @@
/datum/reagent/concentrated_barbers_aid
name = "Concentrated Barber's Aid"
description = "A concentrated solution to hair loss across the world."
- reagent_state = LIQUID
color = "#7A4E33" //hair is dark browmn
taste_description = "sourness"
penetrates_skin = NONE
@@ -2350,7 +2358,6 @@
/datum/reagent/baldium
name = "Baldium"
description = "A major cause of hair loss across the world."
- reagent_state = LIQUID
color = "#ecb2cf"
taste_description = "bitterness"
penetrates_skin = NONE
@@ -2369,7 +2376,6 @@
/datum/reagent/saltpetre
name = "Saltpetre"
description = "Volatile. Controversial. Third Thing."
- reagent_state = LIQUID
color = "#60A584" // rgb: 96, 165, 132
taste_description = "cool salt"
ph = 11.2
@@ -2384,7 +2390,6 @@
/datum/reagent/lye
name = "Lye"
description = "Also known as sodium hydroxide. As a profession making this is somewhat underwhelming."
- reagent_state = LIQUID
color = "#FFFFD6" // very very light yellow
taste_description = "acid"
ph = 11.9
@@ -2393,7 +2398,6 @@
/datum/reagent/drying_agent
name = "Drying Agent"
description = "A desiccant. Can be used to dry things."
- reagent_state = LIQUID
color = "#A70FFF"
taste_description = "dryness"
ph = 10.7
@@ -2506,7 +2510,6 @@
/datum/reagent/magillitis
name = "Magillitis"
description = "An experimental serum which causes rapid muscular growth in Hominidae. Side-affects may include hypertrichosis, violent outbursts, and an unending affinity for bananas."
- reagent_state = LIQUID
color = "#00f041"
chemical_flags = REAGENT_CAN_BE_SYNTHESIZED|REAGENT_NO_RANDOM_RECIPE
@@ -2565,9 +2568,8 @@
description = "if you can see this description, contact a coder."
color = COLOR_WHITE //pure white
taste_description = "plastic"
- reagent_state = SOLID
- var/glitter_type = /obj/effect/decal/cleanable/glitter
chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
+ var/glitter_type = /obj/effect/decal/cleanable/glitter
/datum/reagent/glitter/expose_turf(turf/exposed_turf, reac_volume)
. = ..()
@@ -2842,7 +2844,6 @@
/datum/reagent/cellulose
name = "Cellulose Fibers"
description = "A crystaline polydextrose polymer, plants swear by this stuff."
- reagent_state = SOLID
color = "#E6E6DA"
taste_mult = 0
chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
@@ -2851,7 +2852,6 @@
/datum/reagent/determination
name = "Determination"
description = "For when you need to push on a little more. Do NOT allow near plants."
- reagent_state = LIQUID
color = "#D2FFFA"
metabolization_rate = 0.75 * REAGENTS_METABOLISM // 5u (WOUND_DETERMINATION_CRITICAL) will last for ~34 seconds
chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
@@ -2942,7 +2942,6 @@
/datum/reagent/ants
name = "Ants"
description = "A genetic crossbreed between ants and termites, their bites land at a 3 on the Schmidt Pain Scale."
- reagent_state = SOLID
color = "#993333"
taste_mult = 1.3
taste_description = "tiny legs scuttling down the back of your throat"
@@ -3047,7 +3046,6 @@
name = "Lead"
description = "A dull metallic element with a low melting point."
taste_description = "metal"
- reagent_state = SOLID
color = "#80919d"
metabolization_rate = 0.4 * REAGENTS_METABOLISM
@@ -3076,7 +3074,6 @@
/datum/reagent/brimdust
name = "Brimdust"
description = "A brimdemon's dust. Consumption is not recommended, although plants like it."
- reagent_state = SOLID
color = "#522546"
taste_description = "burning"
chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
diff --git a/code/modules/reagents/chemistry/reagents/pyrotechnic_reagents.dm b/code/modules/reagents/chemistry/reagents/pyrotechnic_reagents.dm
index c401d39f020e..a4cb1fdee759 100644
--- a/code/modules/reagents/chemistry/reagents/pyrotechnic_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/pyrotechnic_reagents.dm
@@ -2,7 +2,6 @@
/datum/reagent/thermite
name = "Thermite"
description = "Thermite produces an aluminothermic reaction known as a thermite reaction. Can be used to melt walls."
- reagent_state = SOLID
chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
color = "#550000"
taste_description = "sweet tasting metal"
@@ -19,15 +18,20 @@
/datum/reagent/nitroglycerin
name = "Nitroglycerin"
- description = "Nitroglycerin is a heavy, colorless, oily, explosive liquid obtained by nitrating glycerol."
+ description = "Nitroglycerin is a heavy, colorless, oily liquid obtained by nitrating glycerol. \
+ It is commonly used to treat heart conditions, but also in the creation of explosives."
color = COLOR_GRAY
taste_description = "oil"
chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
+/datum/reagent/nitroglycerin/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired)
+ . = ..()
+ if(affected_mob.adjustOrganLoss(ORGAN_SLOT_HEART, -1 * REM * seconds_per_tick * normalise_creation_purity(), required_organ_flag = affected_organ_flags))
+ return UPDATE_MOB_HEALTH
+
/datum/reagent/stabilizing_agent
name = "Stabilizing Agent"
description = "Keeps unstable chemicals stable. This does not work on everything."
- reagent_state = LIQUID
color = COLOR_YELLOW
taste_description = "metal"
chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
@@ -39,7 +43,6 @@
/datum/reagent/clf3
name = "Chlorine Trifluoride"
description = "Makes a temporary 3x3 fireball when it comes into existence, so be careful when mixing. ClF3 applied to a surface burns things that wouldn't otherwise burn, sometimes through the very floors of the station and exposing it to the vacuum of space."
- reagent_state = LIQUID
color = "#FFC8C8"
metabolization_rate = 10 * REAGENTS_METABOLISM
taste_description = "burning"
@@ -78,7 +81,6 @@
/datum/reagent/sorium
name = "Sorium"
description = "Sends everything flying from the detonation point."
- reagent_state = LIQUID
color = "#5A64C8"
taste_description = "air and bitterness"
chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
@@ -86,7 +88,6 @@
/datum/reagent/liquid_dark_matter
name = "Liquid Dark Matter"
description = "Sucks everything into the detonation point."
- reagent_state = LIQUID
color = "#210021"
taste_description = "compressed bitterness"
chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
@@ -94,7 +95,6 @@
/datum/reagent/gunpowder
name = "Gunpowder"
description = "Explodes. Violently."
- reagent_state = LIQUID
color = COLOR_BLACK
metabolization_rate = 0.125 * REAGENTS_METABOLISM
taste_description = "salt"
@@ -123,7 +123,6 @@
/datum/reagent/rdx
name = "RDX"
description = "Military grade explosive"
- reagent_state = SOLID
color = COLOR_WHITE
taste_description = "salt"
chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
@@ -131,7 +130,6 @@
/datum/reagent/tatp
name = "TaTP"
description = "Suicide grade explosive"
- reagent_state = SOLID
color = COLOR_WHITE
taste_description = "death"
chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
@@ -139,7 +137,6 @@
/datum/reagent/flash_powder
name = "Flash Powder"
description = "Makes a very bright flash."
- reagent_state = LIQUID
color = "#C8C8C8"
taste_description = "salt"
chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
@@ -147,7 +144,6 @@
/datum/reagent/smoke_powder
name = "Smoke Powder"
description = "Makes a large cloud of smoke that can carry reagents."
- reagent_state = LIQUID
color = "#C8C8C8"
taste_description = "smoke"
chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
@@ -155,7 +151,6 @@
/datum/reagent/sonic_powder
name = "Sonic Powder"
description = "Makes a deafening noise."
- reagent_state = LIQUID
color = "#C8C8C8"
taste_description = "loud noises"
chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
@@ -163,7 +158,6 @@
/datum/reagent/phlogiston
name = "Phlogiston"
description = "Catches you on fire and makes you ignite."
- reagent_state = LIQUID
color = "#FA00AF"
taste_description = "burning"
self_consuming = TRUE
@@ -185,7 +179,6 @@
/datum/reagent/napalm
name = "Napalm"
description = "Very flammable."
- reagent_state = LIQUID
color = "#FA00AF"
taste_description = "burning"
self_consuming = TRUE
@@ -296,7 +289,6 @@
/datum/reagent/teslium //Teslium. Causes periodic shocks, and makes shocks against the target much more effective.
name = "Teslium"
description = "An unstable, electrically-charged metallic slurry. Periodically electrocutes its victim, and makes electrocutions against them more deadly. Excessively heating teslium results in dangerous destabilization. Do not allow to come into contact with water."
- reagent_state = LIQUID
color = "#20324D" //RGB: 32, 50, 77
metabolization_rate = 0.5 * REAGENTS_METABOLISM
taste_description = "charged metal"
@@ -335,7 +327,6 @@
/datum/reagent/teslium/energized_jelly
name = "Energized Jelly"
description = "Electrically-charged jelly. Boosts jellypeople's nervous system, but only shocks other lifeforms."
- reagent_state = LIQUID
color = "#CAFF43"
taste_description = "jelly"
chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
@@ -354,7 +345,6 @@
/datum/reagent/firefighting_foam
name = "Firefighting Foam"
description = "A historical fire suppressant. Originally believed to simply displace oxygen to starve fires, it actually interferes with the combustion reaction itself. Vastly superior to the cheap water-based extinguishers found on NT vessels."
- reagent_state = LIQUID
color = "#A6FAFF55"
taste_description = "the inside of a fire extinguisher"
chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
diff --git a/code/modules/reagents/chemistry/reagents/toxin_reagents.dm b/code/modules/reagents/chemistry/reagents/toxin_reagents.dm
index 6e6b625dae1a..9dcf7955a31c 100644
--- a/code/modules/reagents/chemistry/reagents/toxin_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/toxin_reagents.dm
@@ -152,7 +152,6 @@
/datum/reagent/toxin/hot_ice
name = "Hot Ice Slush"
description = "Frozen plasma, worth its weight in gold, to the right people."
- reagent_state = SOLID
color = "#724cb8" // rgb: 114, 76, 184
taste_description = "thick and smokey"
specific_heat = SPECIFIC_HEAT_PLASMA
@@ -245,7 +244,6 @@
name = "Zombie Powder"
description = "A strong neurotoxin that puts the subject into a death-like state."
silent_toxin = TRUE
- reagent_state = SOLID
creation_purity = REAGENT_STANDARD_PURITY
purity = REAGENT_STANDARD_PURITY
color = "#669900" // rgb: 102, 153, 0
@@ -294,7 +292,6 @@
/datum/reagent/toxin/ghoulpowder
name = "Ghoul Powder"
description = "A strong neurotoxin that slows metabolism to a death-like state, while keeping the patient fully active. Causes toxin buildup if used too long."
- reagent_state = SOLID
color = "#664700" // rgb: 102, 71, 0
creation_purity = REAGENT_STANDARD_PURITY
purity = REAGENT_STANDARD_PURITY
@@ -469,7 +466,6 @@
name = "Chloral Hydrate"
description = "A powerful sedative that induces confusion and drowsiness before putting its target to sleep."
silent_toxin = TRUE
- reagent_state = SOLID
creation_purity = REAGENT_STANDARD_PURITY
purity = REAGENT_STANDARD_PURITY
color = "#000067" // rgb: 0, 0, 103
@@ -526,7 +522,6 @@
/datum/reagent/toxin/coffeepowder
name = "Coffee Grounds"
description = "Finely ground coffee beans, used to make coffee."
- reagent_state = SOLID
color = "#5B2E0D" // rgb: 91, 46, 13
toxpwr = 0.5
ph = 4.2
@@ -536,7 +531,6 @@
/datum/reagent/toxin/teapowder
name = "Ground Tea Leaves"
description = "Finely shredded tea leaves, used for making tea."
- reagent_state = SOLID
color = "#7F8400" // rgb: 127, 132, 0
toxpwr = 0.1
taste_description = "green tea"
@@ -547,7 +541,6 @@
/datum/reagent/toxin/mushroom_powder
name = "Mushroom Powder"
description = "Finely ground polypore mushrooms, ready to be steeped in water to make mushroom tea."
- reagent_state = SOLID
color = "#67423A" // rgb: 127, 132, 0
toxpwr = 0.1
taste_description = "mushrooms"
@@ -589,7 +582,6 @@
/datum/reagent/toxin/polonium
name = "Polonium"
description = "An extremely radioactive material in liquid form. Ingestion results in fatal irradiation."
- reagent_state = LIQUID
color = "#787878"
metabolization_rate = 0.125 * REAGENTS_METABOLISM
toxpwr = 0
@@ -607,7 +599,6 @@
name = "Histamine"
description = "Histamine's effects become more dangerous depending on the dosage amount. They range from mildly annoying to incredibly lethal."
silent_toxin = TRUE
- reagent_state = LIQUID
color = "#FA6464"
metabolization_rate = 0.25 * REAGENTS_METABOLISM
overdose_threshold = 30
@@ -644,7 +635,6 @@
name = "Formaldehyde"
description = "Formaldehyde, on its own, is a fairly weak toxin. It contains trace amounts of Histamine, very rarely making it decay into Histamine. When used in a dead body, will prevent organ decay."
silent_toxin = TRUE
- reagent_state = LIQUID
color = "#B4004B"
metabolization_rate = 0.5 * REAGENTS_METABOLISM
creation_purity = REAGENT_STANDARD_PURITY
@@ -668,7 +658,6 @@
/datum/reagent/toxin/venom
name = "Venom"
description = "An exotic poison extracted from highly toxic fauna. Causes scaling amounts of toxin damage and bruising depending and dosage. Often decays into Histamine."
- reagent_state = LIQUID
color = "#F0FFF0"
metabolization_rate = 0.25 * REAGENTS_METABOLISM
toxpwr = 0
@@ -700,7 +689,6 @@
/datum/reagent/toxin/fentanyl
name = "Fentanyl"
description = "Fentanyl will inhibit brain function and cause toxin damage before eventually knocking out its victim."
- reagent_state = LIQUID
color = "#64916E"
metabolization_rate = 0.5 * REAGENTS_METABOLISM
creation_purity = REAGENT_STANDARD_PURITY
@@ -726,7 +714,6 @@
/datum/reagent/toxin/cyanide
name = "Cyanide"
description = "An infamous poison known for its use in assassination. Causes small amounts of toxin damage with a small chance of oxygen damage or a stun."
- reagent_state = LIQUID
color = "#00B4FF"
creation_purity = REAGENT_STANDARD_PURITY
purity = REAGENT_STANDARD_PURITY
@@ -751,7 +738,6 @@
/datum/reagent/toxin/bad_food
name = "Bad Food"
description = "The result of some abomination of cookery, food so bad it's toxic."
- reagent_state = LIQUID
color = "#d6d6d8"
metabolization_rate = 0.25 * REAGENTS_METABOLISM
toxpwr = 0.5
@@ -762,7 +748,6 @@
name = "Itching Powder"
description = "A powder that induces itching upon contact with the skin. Causes the victim to scratch at their itches and has a very low chance to decay into Histamine."
silent_toxin = TRUE
- reagent_state = LIQUID
creation_purity = REAGENT_STANDARD_PURITY
purity = REAGENT_STANDARD_PURITY
color = "#C8C8C8"
@@ -799,7 +784,6 @@
name = "Initropidril"
description = "A powerful poison with insidious effects. It can cause stuns, lethal breathing failure, and cardiac arrest."
silent_toxin = TRUE
- reagent_state = LIQUID
color = "#7F10C0"
metabolization_rate = 0.5 * REAGENTS_METABOLISM
toxpwr = 2.5
@@ -833,7 +817,6 @@
name = "Pancuronium"
description = "An undetectable toxin that swiftly incapacitates its victim. May also cause breathing failure."
silent_toxin = TRUE
- reagent_state = LIQUID
color = "#195096"
metabolization_rate = 0.25 * REAGENTS_METABOLISM
toxpwr = 0
@@ -852,7 +835,6 @@
name = "Sodium Thiopental"
description = "Sodium Thiopental induces heavy weakness in its target as well as unconsciousness."
silent_toxin = TRUE
- reagent_state = LIQUID
color = LIGHT_COLOR_BLUE
metabolization_rate = 0.75 * REAGENTS_METABOLISM
toxpwr = 0
@@ -870,7 +852,6 @@
name = "Sulfonal"
description = "A stealthy poison that deals minor toxin damage and eventually puts the target to sleep."
silent_toxin = TRUE
- reagent_state = LIQUID
creation_purity = REAGENT_STANDARD_PURITY
purity = REAGENT_STANDARD_PURITY
color = "#7DC3A0"
@@ -888,7 +869,6 @@
name = "Amanitin"
description = "A very powerful delayed toxin. Upon full metabolization, a massive amount of toxin damage will be dealt depending on how long it has been in the victim's bloodstream."
silent_toxin = TRUE
- reagent_state = LIQUID
color = COLOR_WHITE
toxpwr = 0
metabolization_rate = 0.5 * REAGENTS_METABOLISM
@@ -909,7 +889,6 @@
description = "A powerful toxin that will destroy fat cells, massively reducing body weight in a short time. Deadly to those without nutriment in their body."
silent_toxin = TRUE
taste_description = "mothballs"
- reagent_state = LIQUID
creation_purity = REAGENT_STANDARD_PURITY
purity = REAGENT_STANDARD_PURITY
color = "#F0FFF0"
@@ -930,7 +909,6 @@
/datum/reagent/toxin/coniine
name = "Coniine"
description = "Coniine metabolizes extremely slowly, but deals high amounts of toxin damage and stops breathing."
- reagent_state = LIQUID
color = "#7DC3A0"
metabolization_rate = 0.06 * REAGENTS_METABOLISM
toxpwr = 1.75
@@ -945,7 +923,6 @@
/datum/reagent/toxin/spewium
name = "Spewium"
description = "A powerful emetic, causes uncontrollable vomiting. May result in vomiting organs at high doses."
- reagent_state = LIQUID
color = "#2f6617" //A sickly green color
metabolization_rate = REAGENTS_METABOLISM
overdose_threshold = 29
@@ -977,7 +954,6 @@
/datum/reagent/toxin/curare
name = "Curare"
description = "Causes slight toxin damage followed by chain-stunning and oxygen damage."
- reagent_state = LIQUID
color = "#191919"
metabolization_rate = 0.125 * REAGENTS_METABOLISM
toxpwr = 1
@@ -994,7 +970,6 @@
name = "Heparin"
description = "A powerful anticoagulant. All open cut wounds on the victim will open up and bleed much faster. It directly purges sanguirite, a coagulant."
silent_toxin = TRUE
- reagent_state = LIQUID
creation_purity = REAGENT_STANDARD_PURITY
purity = REAGENT_STANDARD_PURITY
color = "#C8C8C8" //RGB: 200, 200, 200
@@ -1013,7 +988,6 @@
name = "Rotatium"
description = "A constantly swirling, oddly colourful fluid. Causes the consumer's sense of direction and hand-eye coordination to become wild."
silent_toxin = TRUE
- reagent_state = LIQUID
creation_purity = REAGENT_STANDARD_PURITY
purity = REAGENT_STANDARD_PURITY
color = "#AC88CA" //RGB: 172, 136, 202
@@ -1044,7 +1018,6 @@
/datum/reagent/toxin/anacea
name = "Anacea"
description = "A toxin that quickly purges medicines and metabolizes very slowly."
- reagent_state = LIQUID
color = "#3C5133"
metabolization_rate = 0.08 * REAGENTS_METABOLISM
creation_purity = REAGENT_STANDARD_PURITY
@@ -1152,7 +1125,6 @@
/datum/reagent/toxin/delayed
name = "Toxin Microcapsules"
description = "Causes heavy toxin damage after a brief time of inactivity."
- reagent_state = LIQUID
metabolization_rate = 0 //stays in the system until active.
var/actual_metaboliztion_rate = REAGENTS_METABOLISM
toxpwr = 0
@@ -1223,12 +1195,12 @@
if(BP)
playsound(affected_mob, SFX_DESECRATION, 50, TRUE, -1)
affected_mob.visible_message(span_warning("[affected_mob]'s bones hurt too much!!"), span_danger("Your bones hurt too much!!"))
- affected_mob.say("OOF!!", forced = /datum/reagent/toxin/bonehurtingjuice)
- if(BP.receive_damage(brute = 20 * REM * seconds_per_tick, burn = 0, blocked = 200, updating_health = FALSE, wound_bonus = rand(30, 130)))
- . = UPDATE_MOB_HEALTH
+ affected_mob.say("OOF!!", forced = type)
+ affected_mob.apply_damage(20, BRUTE, BP, wound_bonus = rand(30, 130))
+
else //SUCH A LUST FOR REVENGE!!!
to_chat(affected_mob, span_warning("A phantom limb hurts!"))
- affected_mob.say("Why are we still here, just to suffer?", forced = /datum/reagent/toxin/bonehurtingjuice)
+ affected_mob.say("Why are we still here, just to suffer?", forced = type)
/datum/reagent/toxin/bonehurtingjuice/used_on_fish(obj/item/fish/fish)
if(HAS_TRAIT(fish, TRAIT_FISH_MADE_OF_BONE))
@@ -1263,7 +1235,6 @@
/datum/reagent/toxin/leadacetate
name = "Lead Acetate"
description = "Used hundreds of years ago as a sweetener, before it was realized that it's incredibly poisonous."
- reagent_state = SOLID
color = "#2b2b2b" // rgb: 127, 132, 0
toxpwr = 0.5
taste_mult = 1.3
@@ -1302,7 +1273,6 @@
name = "Tetrodotoxin"
description = "A colorless, odorless, tasteless neurotoxin usually carried by livers of animals of the Tetraodontiformes order."
silent_toxin = TRUE
- reagent_state = SOLID
color = COLOR_VERY_LIGHT_GRAY
metabolization_rate = 0.1 * REAGENTS_METABOLISM
liver_tolerance_multiplier = 0.1
@@ -1435,3 +1405,9 @@
SIGNAL_HANDLER
if(current_cycle > 28 && !HAS_TRAIT(source, TRAIT_TETRODOTOXIN_HEALING))
return COMSIG_CARBON_BLOCK_BREATH
+
+/datum/reagent/toxin/gatfruit
+ name = "Phytotoxin"
+ description = "A poison produced by the rare and elusive gatfruit plant."
+ liver_damage_multiplier = 0
+ toxpwr = 1
diff --git a/code/modules/reagents/chemistry/recipes.dm b/code/modules/reagents/chemistry/recipes.dm
index acde1703668e..105ce859ffd7 100644
--- a/code/modules/reagents/chemistry/recipes.dm
+++ b/code/modules/reagents/chemistry/recipes.dm
@@ -207,7 +207,7 @@
var/atom/A = holder.my_atom
var/turf/T = get_turf(A)
var/message = "Mobs have been spawned in [ADMIN_VERBOSEJMP(T)] by a [reaction_name] reaction."
- message += " (VV)"
+ message += " (VV)"
var/mob/M = get(A, /mob)
if(M)
diff --git a/code/modules/reagents/reagent_containers/cups/_cup.dm b/code/modules/reagents/reagent_containers/cups/_cup.dm
index 6c3c9c1b1187..43fc32875795 100644
--- a/code/modules/reagents/reagent_containers/cups/_cup.dm
+++ b/code/modules/reagents/reagent_containers/cups/_cup.dm
@@ -28,7 +28,7 @@
. = ..()
if(drink_type)
var/list/types = bitfield_to_list(drink_type, FOOD_FLAGS)
- . += span_notice("It is [LOWER_TEXT(english_list(types))].")
+ . += span_notice("The label says it contains [LOWER_TEXT(english_list(types))] ingredients.")
/**
* Checks if the mob actually liked drinking this cup.
@@ -587,7 +587,7 @@
desc = "The most advanced coffeepot the eggheads could cook up: sleek design; graduated lines; connection to a pocket dimension for coffee containment; yep, it's got it all. Contains 8 standard cups."
volume = 240
icon_state = "coffeepot_bluespace"
- fill_icon_thresholds = list(0)
+ fill_icon_thresholds = null
///Test tubes created by chem master and pandemic and placed in racks
/obj/item/reagent_containers/cup/tube
diff --git a/code/modules/reagents/reagent_containers/cups/bottle.dm b/code/modules/reagents/reagent_containers/cups/bottle.dm
index 4c757e0e1469..2a81c39886be 100644
--- a/code/modules/reagents/reagent_containers/cups/bottle.dm
+++ b/code/modules/reagents/reagent_containers/cups/bottle.dm
@@ -37,6 +37,11 @@
desc = "A small bottle of spewium."
list_reagents = list(/datum/reagent/toxin/spewium = 30)
+/obj/item/reagent_containers/cup/bottle/syndol
+ name = "syndol bottle"
+ desc = "A small bottle of syndol."
+ list_reagents = list(/datum/reagent/drug/syndol = 30)
+
/obj/item/reagent_containers/cup/bottle/morphine
name = "morphine bottle"
desc = "A small bottle of morphine."
diff --git a/code/modules/reagents/reagent_containers/cups/glassbottle.dm b/code/modules/reagents/reagent_containers/cups/glassbottle.dm
index 6ca309958ed0..2ec1c9bdc3d0 100644
--- a/code/modules/reagents/reagent_containers/cups/glassbottle.dm
+++ b/code/modules/reagents/reagent_containers/cups/glassbottle.dm
@@ -10,6 +10,7 @@
icon = 'icons/obj/drinks/bottles.dmi'
icon_state = "glassbottle"
worn_icon_state = "bottle"
+ icon_angle = 90
fill_icon_thresholds = list(0, 10, 20, 30, 40, 50, 60, 70, 80, 90)
custom_price = PAYCHECK_CREW * 1.1
amount_per_transfer_from_this = 10
@@ -21,7 +22,6 @@
var/broken_inhand_icon_state = "broken_beer"
lefthand_file = 'icons/mob/inhands/items/drinks_lefthand.dmi'
righthand_file = 'icons/mob/inhands/items/drinks_righthand.dmi'
- drink_type = ALCOHOL
age_restricted = TRUE // wrryy can't set an init value to see if drink_type contains ALCOHOL so here we go
///Directly relates to the 'knockdown' duration. Lowered by armor (i.e. helmets)
var/bottle_knockdown_duration = BOTTLE_KNOCKDOWN_DEFAULT_DURATION
@@ -309,6 +309,7 @@
desc = "Brewed with \"Pure Ice Asteroid Spring Water\"."
icon_state = "litebeer"
list_reagents = list(/datum/reagent/consumable/ethanol/beer/light = 30)
+ drink_type = ALCOHOL
/obj/item/reagent_containers/cup/glass/bottle/rootbeer
name = "Two-Time root beer"
@@ -334,47 +335,53 @@
desc = "A bottle of high quality gin, produced in the New London Space Station."
icon_state = "ginbottle"
list_reagents = list(/datum/reagent/consumable/ethanol/gin = 100)
+ drink_type = ALCOHOL
/obj/item/reagent_containers/cup/glass/bottle/whiskey
name = "Uncle Git's special reserve"
desc = "A premium single-malt whiskey, gently matured inside the tunnels of a nuclear shelter. TUNNEL WHISKEY RULES."
icon_state = "whiskeybottle"
list_reagents = list(/datum/reagent/consumable/ethanol/whiskey = 100)
+ drink_type = ALCOHOL
/obj/item/reagent_containers/cup/glass/bottle/kong
name = "Kong"
desc = "Makes You Go Ape!®"
list_reagents = list(/datum/reagent/consumable/ethanol/whiskey/kong = 100)
+ drink_type = ALCOHOL
/obj/item/reagent_containers/cup/glass/bottle/candycornliquor
name = "candy corn liquor"
desc = "Like they drank in 2D speakeasies."
list_reagents = list(/datum/reagent/consumable/ethanol/whiskey/candycorn = 100)
+ drink_type = ALCOHOL
/obj/item/reagent_containers/cup/glass/bottle/vodka
name = "Tunguska triple distilled"
desc = "Aah, vodka. Prime choice of drink AND fuel by Russians worldwide."
icon_state = "vodkabottle"
list_reagents = list(/datum/reagent/consumable/ethanol/vodka = 100)
+ drink_type = ALCOHOL
/obj/item/reagent_containers/cup/glass/bottle/vodka/badminka
name = "Badminka vodka"
desc = "The label's written in Cyrillic. All you can make out is the name and a word that looks vaguely like 'Vodka'."
icon_state = "badminka"
list_reagents = list(/datum/reagent/consumable/ethanol/vodka = 100)
+ drink_type = ALCOHOL
/obj/item/reagent_containers/cup/glass/bottle/tequila
name = "Caccavo guaranteed quality tequila"
desc = "Made from premium petroleum distillates, pure thalidomide and other fine quality ingredients!"
icon_state = "tequilabottle"
list_reagents = list(/datum/reagent/consumable/ethanol/tequila = 100)
+ drink_type = ALCOHOL
/obj/item/reagent_containers/cup/glass/bottle/bottleofnothing
name = "bottle of nothing"
desc = "A bottle filled with nothing."
icon_state = "bottleofnothing"
list_reagents = list(/datum/reagent/consumable/nothing = 100)
- drink_type = NONE
age_restricted = FALSE
/obj/item/reagent_containers/cup/glass/bottle/patron
@@ -382,18 +389,21 @@
desc = "Silver laced tequila, served in space night clubs across the galaxy."
icon_state = "patronbottle"
list_reagents = list(/datum/reagent/consumable/ethanol/patron = 100)
+ drink_type = ALCOHOL
/obj/item/reagent_containers/cup/glass/bottle/rum
name = "Captain Pete's Cuban spiced rum"
desc = "This isn't just rum, oh no. It's practically GRIFF in a bottle."
icon_state = "rumbottle"
list_reagents = list(/datum/reagent/consumable/ethanol/rum = 100)
+ drink_type = ALCOHOL
/obj/item/reagent_containers/cup/glass/bottle/rum/aged
name = "Captain Pete's Vintage spiced rum"
desc = "Shiver me timbers, a vintage edition of Captain Pete's rum. It's pratically GRIFF in a bottle from over 50 years ago."
icon_state = "rumbottle_gold"
list_reagents = list(/datum/reagent/consumable/ethanol/rum/aged = 100)
+ drink_type = ALCOHOL
/obj/item/reagent_containers/cup/glass/bottle/maltliquor
name = "\improper Rabid Bear malt liquor"
@@ -401,6 +411,7 @@
icon_state = "maltliquorbottle"
list_reagents = list(/datum/reagent/consumable/ethanol/beer/maltliquor = 100)
custom_price = PAYCHECK_CREW
+ drink_type = ALCOHOL
/obj/item/reagent_containers/cup/glass/bottle/holywater
name = "flask of holy water"
@@ -410,7 +421,6 @@
inhand_icon_state = "holyflask"
broken_inhand_icon_state = "broken_holyflask"
list_reagents = list(/datum/reagent/water/holywater = 100)
- drink_type = NONE
/obj/item/reagent_containers/cup/glass/bottle/holywater/add_message_overlay()
return //looks too weird...
@@ -425,6 +435,7 @@
desc = "Sweet, sweet dryness~"
icon_state = "vermouthbottle"
list_reagents = list(/datum/reagent/consumable/ethanol/vermouth = 100)
+ drink_type = ALCOHOL
/obj/item/reagent_containers/cup/glass/bottle/kahlua
name = "Robert Robust's coffee liqueur"
@@ -438,12 +449,14 @@
desc = "Because they are the only ones who will drink 100 proof cinnamon schnapps."
icon_state = "goldschlagerbottle"
list_reagents = list(/datum/reagent/consumable/ethanol/goldschlager = 100)
+ drink_type = ALCOHOL
/obj/item/reagent_containers/cup/glass/bottle/cognac
name = "Chateau de Baton premium cognac"
desc = "A sweet and strongly alchoholic drink, made after numerous distillations and years of maturing. You might as well not scream 'SHITCURITY' this time."
icon_state = "cognacbottle"
list_reagents = list(/datum/reagent/consumable/ethanol/cognac = 100)
+ drink_type = ALCOHOL
/obj/item/reagent_containers/cup/glass/bottle/wine
name = "Doublebeard's bearded special wine"
@@ -491,6 +504,7 @@
desc = "A strong alcoholic drink brewed and distributed by"
icon_state = "absinthebottle"
list_reagents = list(/datum/reagent/consumable/ethanol/absinthe = 100)
+ drink_type = ALCOHOL
/obj/item/reagent_containers/cup/glass/bottle/absinthe/Initialize(mapload)
. = ..()
@@ -540,6 +554,7 @@
name = "Gwyn's premium absinthe"
desc = "A potent alcoholic beverage, almost makes you forget the ash in your lungs."
icon_state = "absinthepremium"
+ drink_type = ALCOHOL
/obj/item/reagent_containers/cup/glass/bottle/absinthe/premium/redact()
return
@@ -557,24 +572,28 @@
icon_state = "hcider"
volume = 50
list_reagents = list(/datum/reagent/consumable/ethanol/hcider = 50)
+ drink_type = ALCOHOL
/obj/item/reagent_containers/cup/glass/bottle/amaretto
name = "Luini Amaretto"
desc = "A gentle, syrupy drink that tastes of almonds and apricots."
icon_state = "disaronno"
list_reagents = list(/datum/reagent/consumable/ethanol/amaretto = 100)
+ drink_type = ALCOHOL
/obj/item/reagent_containers/cup/glass/bottle/grappa
name = "Phillipes well-aged Grappa"
desc = "Bottle of Grappa."
icon_state = "grappabottle"
list_reagents = list(/datum/reagent/consumable/ethanol/grappa = 100)
+ drink_type = ALCOHOL
/obj/item/reagent_containers/cup/glass/bottle/sake
name = "Ryo's traditional sake"
desc = "Sweet as can be, and burns like fire going down."
icon_state = "sakebottle"
list_reagents = list(/datum/reagent/consumable/ethanol/sake = 100)
+ drink_type = ALCOHOL
/obj/item/reagent_containers/cup/glass/bottle/sake/Initialize(mapload)
if(prob(10))
@@ -597,6 +616,7 @@
desc = "A bottle of pure Fernet Bronca, produced in Cordoba Space Station"
icon_state = "fernetbottle"
list_reagents = list(/datum/reagent/consumable/ethanol/fernet = 100)
+ drink_type = ALCOHOL
/obj/item/reagent_containers/cup/glass/bottle/bitters
name = "Andromeda Bitters"
@@ -604,12 +624,14 @@
icon_state = "bitters_bottle"
volume = 30
list_reagents = list(/datum/reagent/consumable/ethanol/bitters = 30)
+ drink_type = ALCOHOL
/obj/item/reagent_containers/cup/glass/bottle/curacao
name = "Beekhof Blauw Curaçao"
desc = "Still produced on the island of Curaçao, after all these years."
icon_state = "curacao_bottle"
list_reagents = list(/datum/reagent/consumable/ethanol/curacao = 100)
+ drink_type = ALCOHOL
/obj/item/reagent_containers/cup/glass/bottle/curacao/add_message_overlay()
return //doesn't fit the sprite
@@ -619,6 +641,7 @@
desc = "Ironically named, given it's made in Bermuda."
icon_state = "navy_rum_bottle"
list_reagents = list(/datum/reagent/consumable/ethanol/navy_rum = 100)
+ drink_type = ALCOHOL
/obj/item/reagent_containers/cup/glass/bottle/grenadine
name = "Jester Grenadine"
@@ -653,6 +676,7 @@
reagent_flags = TRANSPARENT
spillable = FALSE
list_reagents = list(/datum/reagent/consumable/ethanol/champagne = 100)
+ drink_type = ALCOHOL
///Used for sabrage; increases the chance of success per 1 force of the attacking sharp item
var/sabrage_success_percentile = 5
///Whether this bottle was a victim of a successful sabrage attempt
@@ -677,7 +701,7 @@
if(spillable)
return
- if(attacking_item.sharpness != SHARP_EDGED)
+ if(attacking_item.get_sharpness() != SHARP_EDGED)
return
if(attacking_item != user.get_active_held_item()) //no TK allowed
@@ -806,6 +830,7 @@
desc = "You feel like you should give the bottle a good rub before opening."
icon_state = "blazaambottle"
list_reagents = list(/datum/reagent/consumable/ethanol/blazaam = 100)
+ drink_type = ALCOHOL
/obj/item/reagent_containers/cup/glass/bottle/trappist
name = "Mont de Requin Trappistes Bleu"
@@ -813,12 +838,14 @@
icon_state = "trappistbottle"
volume = 50
list_reagents = list(/datum/reagent/consumable/ethanol/trappist = 50)
+ drink_type = ALCOHOL
/obj/item/reagent_containers/cup/glass/bottle/hooch
name = "hooch bottle"
desc = "A bottle of rotgut. Its owner has applied some street wisdom to cleverly disguise it as a brown paper bag."
icon_state = "hoochbottle"
list_reagents = list(/datum/reagent/consumable/ethanol/hooch = 100)
+ drink_type = ALCOHOL
/obj/item/reagent_containers/cup/glass/bottle/hooch/add_message_overlay()
return //doesn't fit the sprite
@@ -828,6 +855,7 @@
desc = "It is said that the ancient Applalacians used these stoneware jugs to capture lightning in a bottle."
icon_state = "moonshinebottle"
list_reagents = list(/datum/reagent/consumable/ethanol/moonshine = 100)
+ drink_type = ALCOHOL
/obj/item/reagent_containers/cup/glass/bottle/moonshine/add_message_overlay()
return //doesn't fit the sprite
@@ -839,6 +867,7 @@
volume = 30
list_reagents = list(/datum/reagent/consumable/ethanol/mushi_kombucha = 30)
isGlass = FALSE
+ drink_type = ALCOHOL
/obj/item/reagent_containers/cup/glass/bottle/hakka_mate
name = "Hakka-Mate"
@@ -851,18 +880,21 @@
desc = "A boozier form of shochu designed for mixing. Comes straight from Mars' Dusty City itself, Shu-Kouba."
icon_state = "shochu_bottle"
list_reagents = list(/datum/reagent/consumable/ethanol/shochu = 100)
+ drink_type = ALCOHOL
/obj/item/reagent_containers/cup/glass/bottle/yuyake
name = "Moonlabor YÅ«yake"
desc = "The distilled essence of disco and flared pants, captured like lightning in a bottle."
icon_state = "yuyake_bottle"
list_reagents = list(/datum/reagent/consumable/ethanol/yuyake = 100)
+ drink_type = ALCOHOL
/obj/item/reagent_containers/cup/glass/bottle/coconut_rum
name = "Breezy Shoals Coconut Rum"
desc = "Live the breezy life with Breezy Shoals, made with only the *finest Caribbean rum."
icon_state = "coconut_rum_bottle"
list_reagents = list(/datum/reagent/consumable/ethanol/coconut_rum = 100)
+ drink_type = ALCOHOL
////////////////////////// MOLOTOV ///////////////////////
/obj/item/reagent_containers/cup/glass/bottle/molotov
diff --git a/code/modules/recycling/conveyor.dm b/code/modules/recycling/conveyor.dm
index 44d9631a6095..9e03dc98aeac 100644
--- a/code/modules/recycling/conveyor.dm
+++ b/code/modules/recycling/conveyor.dm
@@ -248,8 +248,10 @@ GLOBAL_LIST_EMPTY(conveyors_by_id)
start_conveying(movable)
return TRUE
-/obj/machinery/conveyor/proc/conveyable_enter(datum/source, atom/convayable)
+/obj/machinery/conveyor/proc/conveyable_enter(datum/source, atom/movable/convayable)
SIGNAL_HANDLER
+ if(convayable.loc != loc) // If we are not on the same turf (order of operations memes) go to hell
+ return
if(operating == CONVEYOR_OFF)
GLOB.move_manager.stop_looping(convayable, SSconveyors)
return
diff --git a/code/modules/religion/burdened/psyker.dm b/code/modules/religion/burdened/psyker.dm
index 0fc677389132..87261ea683ee 100644
--- a/code/modules/religion/burdened/psyker.dm
+++ b/code/modules/religion/burdened/psyker.dm
@@ -63,8 +63,7 @@
if(!psykerize())
to_chat(src, span_warning("The transformation subsides..."))
return
- var/obj/item/bodypart/head/psyker_head = get_bodypart(BODY_ZONE_HEAD)
- psyker_head.receive_damage(brute = 50)
+ apply_damage(50, BRUTE, BODY_ZONE_HEAD)
to_chat(src, span_userdanger("Your head splits open! Your brain mutates!"))
new /obj/effect/gibspawner/generic(drop_location(), src)
emote("scream")
diff --git a/code/modules/religion/sparring/ceremonial_gear.dm b/code/modules/religion/sparring/ceremonial_gear.dm
index 2c7e73b5a754..d755704a6d34 100644
--- a/code/modules/religion/sparring/ceremonial_gear.dm
+++ b/code/modules/religion/sparring/ceremonial_gear.dm
@@ -5,6 +5,7 @@
icon_state = "default"
inhand_icon_state = "default"
icon = 'icons/obj/weapons/ritual_weapon.dmi'
+ icon_angle = -45
//does the exact thing we want so heck why not
greyscale_config = /datum/greyscale_config/ceremonial_blade
@@ -18,8 +19,8 @@
throwforce = 1 //10
wound_bonus = CANT_WOUND // bad for sparring
w_class = WEIGHT_CLASS_NORMAL
- attack_verb_continuous = list("attacks", "slashes", "stabs", "slices", "tears", "lacerates", "rips", "dices", "cuts")
- attack_verb_simple = list("attack", "slash", "stab", "slice", "tear", "lacerate", "rip", "dice", "cut")
+ attack_verb_continuous = list("attacks", "slashes", "slices", "tears", "lacerates", "rips", "dices", "cuts")
+ attack_verb_simple = list("attack", "slash", "slice", "tear", "lacerate", "rip", "dice", "cut")
block_chance = 3 //30
block_sound = 'sound/items/weapons/parry.ogg'
sharpness = SHARP_EDGED
@@ -27,6 +28,8 @@
material_flags = MATERIAL_EFFECTS | MATERIAL_ADD_PREFIX | MATERIAL_GREYSCALE //doesn't affect stats of the weapon as to avoid gamering your opponent with a dope weapon
armor_type = /datum/armor/item_ceremonial_blade
resistance_flags = FIRE_PROOF
+ var/list/alt_continuous = list("stabs", "pierces", "impales")
+ var/list/alt_simple = list("stab", "pierce", "impale")
/datum/armor/item_ceremonial_blade
fire = 100
@@ -34,6 +37,9 @@
/obj/item/ceremonial_blade/Initialize(mapload)
. = ..()
+ alt_continuous = string_list(alt_continuous)
+ alt_simple = string_list(alt_simple)
+ AddComponent(/datum/component/alternative_sharpness, SHARP_POINTY, alt_continuous, alt_simple)
AddComponent(/datum/component/butchering, \
speed = 4 SECONDS, \
effectiveness = 105, \
diff --git a/code/modules/requests/request_manager.dm b/code/modules/requests/request_manager.dm
index 99a9bba1cc84..3106a925acd2 100644
--- a/code/modules/requests/request_manager.dm
+++ b/code/modules/requests/request_manager.dm
@@ -154,6 +154,10 @@ GLOBAL_DATUM_INIT(requests, /datum/request_manager, new)
to_chat(usr, "You do not have permission to do this, you require +ADMIN", confidential = TRUE)
return
+ if (action == "toggleprint")
+ GLOB.fax_autoprinting = !GLOB.fax_autoprinting
+ return TRUE
+
// Get the request this relates to
var/id = params["id"] != null ? text2num(params["id"]) : null
if (!id)
@@ -228,9 +232,20 @@ GLOBAL_DATUM_INIT(requests, /datum/request_manager, new)
if(request.req_type != REQUEST_FAX)
to_chat(usr, "Request doesn't have a paper to read.", confidential = TRUE)
return TRUE
- var/obj/item/paper/request_message = request.additional_information
+ var/obj/item/paper/request_message = request.additional_information["paper"]
request_message.ui_interact(usr)
return TRUE
+ if ("print")
+ if (request.req_type != REQUEST_FAX)
+ to_chat(usr, "Request doesn't have a paper to print.", confidential = TRUE)
+ return TRUE
+ for(var/obj/machinery/fax/admin/FAX as anything in SSmachines.get_machines_by_type_and_subtypes(/obj/machinery/fax/admin))
+ if(FAX.fax_id != request.additional_information["destination_id"])
+ continue
+ var/obj/item/paper/request_message = request.additional_information["paper"]
+ var/sender_name = request.additional_information["sender_name"]
+ FAX.receive(request_message, sender_name)
+ return TRUE
if ("play")
if(request.req_type != REQUEST_INTERNET_SOUND)
to_chat(usr, "Request doesn't have a sound to play.", confidential = TRUE)
@@ -257,6 +272,7 @@ GLOBAL_DATUM_INIT(requests, /datum/request_manager, new)
"timestamp" = request.timestamp,
"timestamp_str" = gameTimestamp(wtime = request.timestamp)
))
+ data["fax_autoprinting"] = GLOB.fax_autoprinting
return data
#undef REQUEST_PRAYER
diff --git a/code/modules/research/designs/autolathe/security_designs.dm b/code/modules/research/designs/autolathe/security_designs.dm
index 7b2fa09bc0fe..a881f655d778 100644
--- a/code/modules/research/designs/autolathe/security_designs.dm
+++ b/code/modules/research/designs/autolathe/security_designs.dm
@@ -179,7 +179,7 @@
id = "a357"
build_type = AUTOLATHE
materials = list(/datum/material/iron = SHEET_MATERIAL_AMOUNT*2)
- build_path = /obj/item/ammo_casing/a357
+ build_path = /obj/item/ammo_casing/c357
category = list(
RND_CATEGORY_HACKED,
RND_CATEGORY_WEAPONS + RND_SUBCATEGORY_WEAPONS_AMMO,
diff --git a/code/modules/research/designs/machine_designs.dm b/code/modules/research/designs/machine_designs.dm
index 15a5fed08eb0..74e00231035c 100644
--- a/code/modules/research/designs/machine_designs.dm
+++ b/code/modules/research/designs/machine_designs.dm
@@ -1347,3 +1347,13 @@
RND_CATEGORY_MACHINE + RND_SUBCATEGORY_MACHINE_ENGINEERING
)
departmental_flags = DEPARTMENT_BITFLAG_ENGINEERING | DEPARTMENT_BITFLAG_CARGO
+
+/datum/design/board/mailsorter
+ name = "Mail Sorter"
+ desc = "The circuit board for a mail sorting unit."
+ id = "mailsorter"
+ build_path = /obj/item/circuitboard/machine/mailsorter
+ category = list(
+ RND_CATEGORY_MACHINE + RND_SUBCATEGORY_MACHINE_CARGO
+ )
+ departmental_flags = DEPARTMENT_BITFLAG_CARGO | DEPARTMENT_BITFLAG_ENGINEERING
diff --git a/code/modules/research/designs/mechfabricator_designs.dm b/code/modules/research/designs/mechfabricator_designs.dm
index 787453c1987d..717110d9ad6b 100644
--- a/code/modules/research/designs/mechfabricator_designs.dm
+++ b/code/modules/research/designs/mechfabricator_designs.dm
@@ -2801,3 +2801,14 @@
)
departmental_flags = DEPARTMENT_BITFLAG_SCIENCE
+/datum/design/module/mister_janitor
+ name = "Cleaning Mister Module"
+ id = "mod_mister_janitor"
+ materials = list(
+ /datum/material/glass =HALF_SHEET_MATERIAL_AMOUNT,
+ /datum/material/titanium =HALF_SHEET_MATERIAL_AMOUNT * 1,
+ )
+ build_path = /obj/item/mod/module/mister/cleaner
+ category = list(
+ RND_CATEGORY_MODSUIT_MODULES + RND_SUBCATEGORY_MODSUIT_MODULES_SERVICE
+ )
diff --git a/code/modules/research/designs/weapon_designs.dm b/code/modules/research/designs/weapon_designs.dm
index ca7dacab03ae..4ed7adfaf2b1 100644
--- a/code/modules/research/designs/weapon_designs.dm
+++ b/code/modules/research/designs/weapon_designs.dm
@@ -3,7 +3,7 @@
/////////////////////////////////////////
/datum/design/c38/sec
- id = "sec_38"
+ id = "c38_sec"
build_type = PROTOLATHE | AWAY_LATHE
category = list(
RND_CATEGORY_WEAPONS + RND_SUBCATEGORY_WEAPONS_AMMO
@@ -16,7 +16,11 @@
desc = "Designed to quickly reload revolvers. TRAC bullets embed a tracking implant within the target's body. The implant's signal is incompatible with teleporters."
id = "c38_trac"
build_type = PROTOLATHE | AWAY_LATHE
- materials = list(/datum/material/iron = SHEET_MATERIAL_AMOUNT * 10, /datum/material/silver =SHEET_MATERIAL_AMOUNT * 2.5, /datum/material/gold =HALF_SHEET_MATERIAL_AMOUNT)
+ materials = list(
+ /datum/material/iron = HALF_SHEET_MATERIAL_AMOUNT * 10,
+ /datum/material/silver = HALF_SHEET_MATERIAL_AMOUNT * 2.5,
+ /datum/material/gold = HALF_SHEET_MATERIAL_AMOUNT,
+ )
build_path = /obj/item/ammo_box/c38/trac
category = list(
RND_CATEGORY_WEAPONS + RND_SUBCATEGORY_WEAPONS_AMMO
@@ -28,7 +32,10 @@
desc = "Designed to quickly reload revolvers. Hot Shot bullets contain an incendiary payload."
id = "c38_hotshot"
build_type = PROTOLATHE | AWAY_LATHE
- materials = list(/datum/material/iron = SHEET_MATERIAL_AMOUNT * 10, /datum/material/plasma = SHEET_MATERIAL_AMOUNT * 2.5)
+ materials = list(
+ /datum/material/iron = HALF_SHEET_MATERIAL_AMOUNT * 10,
+ /datum/material/plasma = HALF_SHEET_MATERIAL_AMOUNT * 2.5,
+ )
build_path = /obj/item/ammo_box/c38/hotshot
category = list(
RND_CATEGORY_WEAPONS + RND_SUBCATEGORY_WEAPONS_AMMO
@@ -40,7 +47,10 @@
desc = "Designed to quickly reload revolvers. Iceblox bullets contain a cryogenic payload."
id = "c38_iceblox"
build_type = PROTOLATHE | AWAY_LATHE
- materials = list(/datum/material/iron = SHEET_MATERIAL_AMOUNT * 10, /datum/material/plasma = SHEET_MATERIAL_AMOUNT * 2.5)
+ materials = list(
+ /datum/material/iron = HALF_SHEET_MATERIAL_AMOUNT * 10,
+ /datum/material/plasma = HALF_SHEET_MATERIAL_AMOUNT * 2.5,
+ )
build_path = /obj/item/ammo_box/c38/iceblox
category = list(
RND_CATEGORY_WEAPONS + RND_SUBCATEGORY_WEAPONS_AMMO
@@ -52,13 +62,125 @@
desc = "Designed to quickly reload revolvers. Rubber bullets are bouncy and less-than-lethal."
id = "c38_rubber"
build_type = PROTOLATHE | AWAY_LATHE
- materials = list(/datum/material/iron = SHEET_MATERIAL_AMOUNT * 10)
+ materials = list(/datum/material/iron = HALF_SHEET_MATERIAL_AMOUNT * 10)
build_path = /obj/item/ammo_box/c38/match/bouncy
category = list(
RND_CATEGORY_WEAPONS + RND_SUBCATEGORY_WEAPONS_AMMO
)
departmental_flags = DEPARTMENT_BITFLAG_SECURITY
+/datum/design/c38_true
+ name = "Speedloader (.38 True Strike) (Lethal)"
+ desc = "Designed to quickly reload revolvers. Bullets bounce towards new targets with surprising accuracy."
+ id = "c38_true_strike"
+ build_type = PROTOLATHE | AWAY_LATHE
+ materials = list(
+ /datum/material/iron = HALF_SHEET_MATERIAL_AMOUNT * 10,
+ /datum/material/bluespace = HALF_SHEET_MATERIAL_AMOUNT,
+ )
+ build_path = /obj/item/ammo_box/magazine/m38/true
+ category = list(
+ RND_CATEGORY_WEAPONS + RND_SUBCATEGORY_WEAPONS_AMMO
+ )
+ departmental_flags = DEPARTMENT_BITFLAG_SECURITY
+
+/datum/design/c38_mag
+ name = "Magazine (.38) (Lethal)"
+ desc = "Designed to tactically reload a NT BR-38 Battle Rifle. Less powerful by design, guns chambered in .38 caliber rounds are still quite popular for use by police forces, \
+ private security firms and organizations unable to access energy-based nonlethal weaponry. The lower (relative) penetrative power is useful for preventing potential hull damage \
+ aboard space stations and shuttles."
+ id = "c38_mag"
+ build_type = PROTOLATHE | AWAY_LATHE
+ materials = list(
+ /datum/material/iron = HALF_SHEET_MATERIAL_AMOUNT * 30,
+ /datum/material/plastic = HALF_SHEET_MATERIAL_AMOUNT * 3,
+ )
+ build_path = /obj/item/ammo_box/magazine/m38
+ category = list(
+ RND_CATEGORY_WEAPONS + RND_SUBCATEGORY_WEAPONS_AMMO
+ )
+ departmental_flags = DEPARTMENT_BITFLAG_SECURITY
+
+/datum/design/c38_trac_mag
+ name = "Magazine (.38 TRAC) (Less Lethal)"
+ desc = "Designed to tactically reload a NT BR-38 Battle Rifle. TRAC bullets embed a tracking implant within the target's body. The implant's signal is incompatible with teleporters."
+ id = "c38_trac_mag"
+ build_type = PROTOLATHE | AWAY_LATHE
+ materials = list(
+ /datum/material/iron = HALF_SHEET_MATERIAL_AMOUNT * 30,
+ /datum/material/silver = HALF_SHEET_MATERIAL_AMOUNT * 2.5,
+ /datum/material/gold = HALF_SHEET_MATERIAL_AMOUNT,
+ /datum/material/plastic = HALF_SHEET_MATERIAL_AMOUNT * 3,
+ )
+ build_path = /obj/item/ammo_box/magazine/m38/trac
+ category = list(
+ RND_CATEGORY_WEAPONS + RND_SUBCATEGORY_WEAPONS_AMMO
+ )
+ departmental_flags = DEPARTMENT_BITFLAG_SECURITY
+
+/datum/design/c38_hotshot_mag
+ name = "Magazine (.38 Hot Shot) (Very Lethal)"
+ desc = "Designed to tactically reload a NT BR-38 Battle Rifle. Hot Shot bullets contain an incendiary payload."
+ id = "c38_hotshot_mag"
+ build_type = PROTOLATHE | AWAY_LATHE
+ materials = list(
+ /datum/material/iron = HALF_SHEET_MATERIAL_AMOUNT * 30,
+ /datum/material/plasma = HALF_SHEET_MATERIAL_AMOUNT * 2.5,
+ /datum/material/plastic = HALF_SHEET_MATERIAL_AMOUNT * 3,
+ )
+ build_path = /obj/item/ammo_box/magazine/m38/hotshot
+ category = list(
+ RND_CATEGORY_WEAPONS + RND_SUBCATEGORY_WEAPONS_AMMO
+ )
+ departmental_flags = DEPARTMENT_BITFLAG_SECURITY
+
+/datum/design/c38_iceblox_mag
+ name = "Magazine (.38 Iceblox) (Lethal/Very Lethal (Lizardpeople))"
+ desc = "Designed to tactically reload a NT BR-38 Battle Rifle. Iceblox bullets contain a cryogenic payload."
+ id = "c38_iceblox_mag"
+ build_type = PROTOLATHE | AWAY_LATHE
+ materials = list(
+ /datum/material/iron = HALF_SHEET_MATERIAL_AMOUNT * 30,
+ /datum/material/plasma = HALF_SHEET_MATERIAL_AMOUNT * 2.5,
+ /datum/material/plastic = HALF_SHEET_MATERIAL_AMOUNT * 3,
+ )
+ build_path = /obj/item/ammo_box/magazine/m38/iceblox
+ category = list(
+ RND_CATEGORY_WEAPONS + RND_SUBCATEGORY_WEAPONS_AMMO
+ )
+ departmental_flags = DEPARTMENT_BITFLAG_SECURITY
+
+/datum/design/c38_rubber_mag
+ name = "Magazine (.38 Rubber) (Less Lethal)"
+ desc = "Designed to tactically reload a NT BR-38 Battle Rifle. Rubber bullets are bouncy and less-than-lethal."
+ id = "c38_rubber_mag"
+ build_type = PROTOLATHE | AWAY_LATHE
+ materials = list(
+ /datum/material/iron = HALF_SHEET_MATERIAL_AMOUNT * 30,
+ /datum/material/plastic = HALF_SHEET_MATERIAL_AMOUNT * 3,
+ )
+ build_path = /obj/item/ammo_box/magazine/m38/match/bouncy
+ category = list(
+ RND_CATEGORY_WEAPONS + RND_SUBCATEGORY_WEAPONS_AMMO
+ )
+ departmental_flags = DEPARTMENT_BITFLAG_SECURITY
+
+/datum/design/c38_true_mag
+ name = "Magazine (.38 Truee Strike) (Lethal)"
+ desc = "Designed to tactically reload a NT BR-38 Battle Rifle. Bullets bounce towards new targets with surprising accuracy."
+ id = "c38_true_strike_mag"
+ build_type = PROTOLATHE | AWAY_LATHE
+ materials = list(
+ /datum/material/iron = HALF_SHEET_MATERIAL_AMOUNT * 30,
+ /datum/material/plastic = HALF_SHEET_MATERIAL_AMOUNT * 3,
+ /datum/material/bluespace = HALF_SHEET_MATERIAL_AMOUNT,
+ )
+ build_path = /obj/item/ammo_box/magazine/m38/true
+ category = list(
+ RND_CATEGORY_WEAPONS + RND_SUBCATEGORY_WEAPONS_AMMO
+ )
+ departmental_flags = DEPARTMENT_BITFLAG_SECURITY
+
/datum/design/rubbershot/sec
id = "sec_rshot"
desc = "Rubbershot shotgun shells. Fires a cloud of pellets. Rubber bullets are bouncy and less-than-lethal."
diff --git a/code/modules/research/experimentor.dm b/code/modules/research/experimentor.dm
index c78ca6601d41..75ad0146fd4b 100644
--- a/code/modules/research/experimentor.dm
+++ b/code/modules/research/experimentor.dm
@@ -828,7 +828,6 @@
var/datum/dimension_theme/shifter = SSmaterials.dimensional_themes[new_theme_path]
for(var/turf/shiftee in range(1, user))
shifter.apply_theme(shiftee, show_effect = TRUE)
- qdel(shifter)
// prevent *total* spam conversion
min_cooldown += 2 SECONDS
max_cooldown += 2 SECONDS
diff --git a/code/modules/research/part_replacer.dm b/code/modules/research/part_replacer.dm
new file mode 100644
index 000000000000..916fbdd01407
--- /dev/null
+++ b/code/modules/research/part_replacer.dm
@@ -0,0 +1,186 @@
+///RPED. Allows installing & exchaging parts on machines
+/obj/item/storage/part_replacer
+ name = "rapid part exchange device"
+ desc = "Special mechanical module made to store, sort, and apply standard machine parts."
+ icon_state = "RPED"
+ inhand_icon_state = "RPED"
+ worn_icon_state = "RPED"
+ lefthand_file = 'icons/mob/inhands/items/devices_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/items/devices_righthand.dmi'
+ w_class = WEIGHT_CLASS_HUGE
+ storage_type = /datum/storage/rped
+
+/obj/item/storage/part_replacer/interact_with_atom(obj/attacked_object, mob/living/user, list/modifiers)
+ if(user.combat_mode)
+ return ITEM_INTERACT_SKIP_TO_ATTACK
+
+ //its very important to NOT block so frames can still interact with it
+ if(!ismachinery(attacked_object) || istype(attacked_object, /obj/machinery/computer))
+ return NONE
+
+ var/obj/machinery/attacked_machinery = attacked_object
+ if(!LAZYLEN(attacked_machinery.component_parts))
+ return ITEM_INTERACT_FAILURE
+
+ return attacked_machinery.exchange_parts(user, src) ? ITEM_INTERACT_SUCCESS : ITEM_INTERACT_FAILURE
+
+///Plays the sound for RPED exhanging or installing parts.
+/obj/item/storage/part_replacer/proc/play_rped_sound()
+ playsound(src, 'sound/items/tools/rped.ogg', 40, TRUE)
+
+/**
+ * Gets parts sorted in order of their tier
+ * Arguments
+ *
+ * * ignore_stacks - should the final list contain stacks
+ */
+/obj/item/storage/part_replacer/proc/get_sorted_parts(ignore_stacks = FALSE)
+ RETURN_TYPE(/list/obj/item)
+
+ var/list/obj/item/part_list = list()
+ //Assemble a list of current parts, then sort them by their rating!
+ for(var/obj/item/component_part in contents)
+ //No need to put circuit boards in this list or stacks when exchanging parts
+ if(istype(component_part, /obj/item/circuitboard) || (ignore_stacks && istype(component_part, /obj/item/stack)))
+ continue
+ part_list += component_part
+ //Sort the parts. This ensures that higher tier items are applied first.
+ sortTim(part_list, GLOBAL_PROC_REF(cmp_rped_sort))
+
+ return part_list
+
+///Bluespace RPED. Allows exchanging parts from a distance & through cameras
+/obj/item/storage/part_replacer/bluespace
+ name = "bluespace rapid part exchange device"
+ desc = "A version of the RPED that allows for replacement of parts and scanning from a distance, along with higher capacity for parts."
+ icon_state = "BS_RPED"
+ inhand_icon_state = "BS_RPED"
+ w_class = WEIGHT_CLASS_NORMAL
+ storage_type = /datum/storage/rped/bluespace
+
+/obj/item/storage/part_replacer/bluespace/Initialize(mapload)
+ . = ..()
+
+ RegisterSignal(src, COMSIG_ATOM_ENTERED, PROC_REF(on_part_entered))
+ RegisterSignal(src, COMSIG_ATOM_EXITED, PROC_REF(on_part_exited))
+
+/obj/item/storage/part_replacer/bluespace/interact_with_atom(obj/attacked_object, mob/living/user, list/modifiers)
+ . = ..()
+ if(. & ITEM_INTERACT_ANY_BLOCKER)
+ user.Beam(attacked_object, icon_state = "rped_upgrade", time = 0.5 SECONDS)
+
+/obj/item/storage/part_replacer/bluespace/ranged_interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers)
+ return interact_with_atom(interacting_with, user, modifiers)
+
+/obj/item/storage/part_replacer/bluespace/play_rped_sound()
+ if(prob(1))
+ playsound(src, 'sound/items/pshoom/pshoom_2.ogg', 40, TRUE)
+ return
+ playsound(src, 'sound/items/pshoom/pshoom.ogg', 40, TRUE)
+
+/**
+ * Signal handler for when a part has been inserted into the BRPED.
+ *
+ * If the inserted item is a rigged or corrupted cell, does some logging.
+ *
+ * If it has a reagent holder, clears the reagents and registers signals to prevent new
+ * reagents being added and registers clean up signals on inserted item's removal from
+ * the BRPED.
+ */
+/obj/item/storage/part_replacer/bluespace/proc/on_part_entered(datum/source, obj/item/inserted_component)
+ SIGNAL_HANDLER
+
+ if(istype(inserted_component, /obj/item/stock_parts/power_store))
+ var/obj/item/stock_parts/power_store/inserted_cell = inserted_component
+ if(inserted_cell.rigged || inserted_cell.corrupted)
+ message_admins("[ADMIN_LOOKUPFLW(usr)] has inserted rigged/corrupted [inserted_cell] into [src].")
+ usr.log_message("has inserted rigged/corrupted [inserted_cell] into [src].", LOG_GAME)
+ usr.log_message("inserted rigged/corrupted [inserted_cell] into [src]", LOG_ATTACK)
+ return
+
+ if(inserted_component.reagents)
+ if(length(inserted_component.reagents.reagent_list))
+ inserted_component.reagents.clear_reagents()
+ to_chat(usr, span_notice("[src] churns as [inserted_component] has its reagents emptied into bluespace."))
+ RegisterSignal(inserted_component.reagents, COMSIG_REAGENTS_PRE_ADD_REAGENT, PROC_REF(on_insered_component_reagent_pre_add))
+
+/**
+ * Signal handler for when the reagents datum of an inserted part has reagents added to it.
+ *
+ * Registers the PRE_ADD variant which allows the signal handler to stop reagents being
+ * added.
+ *
+ * Simply returns COMPONENT_CANCEL_REAGENT_ADD. We never want to allow people to add
+ * reagents to beakers in BRPEDs as they can then be used for spammable remote bombing.
+ */
+/obj/item/storage/part_replacer/bluespace/proc/on_insered_component_reagent_pre_add(datum/source, reagent, amount, reagtemp, data, no_react)
+ SIGNAL_HANDLER
+
+ return COMPONENT_CANCEL_REAGENT_ADD
+
+/**
+ * Signal handler for a part is removed from the BRPED.
+ *
+ * Does signal registration cleanup on its reagents, if it has any.
+ */
+/obj/item/storage/part_replacer/bluespace/proc/on_part_exited(datum/source, obj/item/removed_component)
+ SIGNAL_HANDLER
+
+ if(removed_component.reagents)
+ UnregisterSignal(removed_component.reagents, COMSIG_REAGENTS_PRE_ADD_REAGENT)
+
+//RPED with tiered contents
+/obj/item/storage/part_replacer/bluespace/tier1/PopulateContents()
+ for(var/i in 1 to 10)
+ new /obj/item/stock_parts/capacitor(src)
+ new /obj/item/stock_parts/scanning_module(src)
+ new /obj/item/stock_parts/servo(src)
+ new /obj/item/stock_parts/micro_laser(src)
+ new /obj/item/stock_parts/matter_bin(src)
+ new /obj/item/stock_parts/power_store/cell/high(src)
+
+/obj/item/storage/part_replacer/bluespace/tier2/PopulateContents()
+ for(var/i in 1 to 10)
+ new /obj/item/stock_parts/capacitor/adv(src)
+ new /obj/item/stock_parts/scanning_module/adv(src)
+ new /obj/item/stock_parts/servo/nano(src)
+ new /obj/item/stock_parts/micro_laser/high(src)
+ new /obj/item/stock_parts/matter_bin/adv(src)
+ new /obj/item/stock_parts/power_store/cell/super(src)
+
+/obj/item/storage/part_replacer/bluespace/tier3/PopulateContents()
+ for(var/i in 1 to 10)
+ new /obj/item/stock_parts/capacitor/super(src)
+ new /obj/item/stock_parts/scanning_module/phasic(src)
+ new /obj/item/stock_parts/servo/pico(src)
+ new /obj/item/stock_parts/micro_laser/ultra(src)
+ new /obj/item/stock_parts/matter_bin/super(src)
+ new /obj/item/stock_parts/power_store/cell/hyper(src)
+
+/obj/item/storage/part_replacer/bluespace/tier4/PopulateContents()
+ for(var/i in 1 to 10)
+ new /obj/item/stock_parts/capacitor/quadratic(src)
+ new /obj/item/stock_parts/scanning_module/triphasic(src)
+ new /obj/item/stock_parts/servo/femto(src)
+ new /obj/item/stock_parts/micro_laser/quadultra(src)
+ new /obj/item/stock_parts/matter_bin/bluespace(src)
+ new /obj/item/stock_parts/power_store/cell/bluespace(src)
+
+//used in a cargo crate
+/obj/item/storage/part_replacer/cargo/PopulateContents()
+ for(var/i in 1 to 10)
+ new /obj/item/stock_parts/capacitor(src)
+ new /obj/item/stock_parts/scanning_module(src)
+ new /obj/item/stock_parts/servo(src)
+ new /obj/item/stock_parts/micro_laser(src)
+ new /obj/item/stock_parts/matter_bin(src)
+
+///Cyborg variant
+/obj/item/storage/part_replacer/cyborg
+ name = "rapid part exchange device"
+ desc = "Special mechanical module made to store, sort, and apply standard machine parts. This one has an extra large compartment for more parts."
+ icon_state = "borgrped"
+ inhand_icon_state = "RPED"
+ lefthand_file = 'icons/mob/inhands/items/devices_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/items/devices_righthand.dmi'
+ storage_type = /datum/storage/rped/bluespace
diff --git a/code/modules/research/stock_parts.dm b/code/modules/research/stock_parts.dm
index 3ed1a8f5b376..beafc7ba260c 100644
--- a/code/modules/research/stock_parts.dm
+++ b/code/modules/research/stock_parts.dm
@@ -1,218 +1,6 @@
/*Power cells are in code\modules\power\cell.dm
If you create T5+ please take a pass at mech_fabricator.dm. The parts being good enough allows it to go into minus values and create materials out of thin air when printing stuff.*/
-/obj/item/storage/part_replacer //NOVA EDIT - ICON OVERRIDDEN BY AESTHETICS - SEE MODULE
- name = "rapid part exchange device"
- desc = "Special mechanical module made to store, sort, and apply standard machine parts."
- icon_state = "RPED"
- inhand_icon_state = "RPED"
- worn_icon_state = "RPED"
- lefthand_file = 'icons/mob/inhands/items/devices_lefthand.dmi'
- righthand_file = 'icons/mob/inhands/items/devices_righthand.dmi'
- w_class = WEIGHT_CLASS_HUGE
- var/works_from_distance = FALSE
- var/pshoom_or_beepboopblorpzingshadashwoosh = 'sound/items/tools/rped.ogg'
- var/alt_sound = null
-
-/obj/item/storage/part_replacer/Initialize(mapload)
- . = ..()
- create_storage(storage_type = /datum/storage/rped)
-
-/obj/item/storage/part_replacer/proc/part_replace_action(obj/attacked_object, mob/living/user)
- if(!ismachinery(attacked_object) || istype(attacked_object, /obj/machinery/computer))
- return FALSE
-
- var/obj/machinery/attacked_machinery = attacked_object
- if(!LAZYLEN(attacked_machinery.component_parts))
- return FALSE
-
- if(attacked_machinery.exchange_parts(user, src) && works_from_distance)
- user.Beam(attacked_machinery, icon_state = "rped_upgrade", time = 0.5 SECONDS)
- return TRUE
-
-/obj/item/storage/part_replacer/interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers)
- if(part_replace_action(interacting_with, user))
- return ITEM_INTERACT_SUCCESS
- return NONE
-
-/obj/item/storage/part_replacer/ranged_interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers)
- if(!works_from_distance)
- return NONE
- if(part_replace_action(interacting_with, user))
- user.Beam(interacting_with, icon_state = "rped_upgrade", time = 0.5 SECONDS)
- return ITEM_INTERACT_SUCCESS
- if(istype(interacting_with, /obj/structure/frame))
- // Cursed snowflake but we need to handle frame ranged interaction here
- // Likely no longer necessary with the new framework, revisit later
- interacting_with.item_interaction(user, src)
- user.Beam(interacting_with, icon_state = "rped_upgrade", time = 0.5 SECONDS)
- return ITEM_INTERACT_SUCCESS
- return NONE
-
-/obj/item/storage/part_replacer/proc/play_rped_sound()
- //Plays the sound for RPED exhanging or installing parts.
- if(alt_sound && prob(1))
- playsound(src, alt_sound, 40, TRUE)
- else
- playsound(src, pshoom_or_beepboopblorpzingshadashwoosh, 40, TRUE)
-
-/obj/item/storage/part_replacer/bluespace //NOVA EDIT - ICON OVERRIDDEN BY AESTHETICS - SEE MODULE
- name = "bluespace rapid part exchange device"
- desc = "A version of the RPED that allows for replacement of parts and scanning from a distance, along with higher capacity for parts."
- icon_state = "BS_RPED"
- inhand_icon_state = "BS_RPED"
- w_class = WEIGHT_CLASS_NORMAL
- works_from_distance = TRUE
- pshoom_or_beepboopblorpzingshadashwoosh = 'sound/items/pshoom/pshoom.ogg'
- alt_sound = 'sound/items/pshoom/pshoom_2.ogg'
-
-/obj/item/storage/part_replacer/bluespace/Initialize(mapload)
- . = ..()
-
- atom_storage.max_slots = 400
- atom_storage.max_total_storage = 800
- atom_storage.max_specific_storage = WEIGHT_CLASS_GIGANTIC
-
- RegisterSignal(src, COMSIG_ATOM_ENTERED, PROC_REF(on_part_entered))
- RegisterSignal(src, COMSIG_ATOM_EXITED, PROC_REF(on_part_exited))
-
-/**
- * Signal handler for when a part has been inserted into the BRPED.
- *
- * If the inserted item is a rigged or corrupted cell, does some logging.
- *
- * If it has a reagent holder, clears the reagents and registers signals to prevent new
- * reagents being added and registers clean up signals on inserted item's removal from
- * the BRPED.
- */
-/obj/item/storage/part_replacer/bluespace/proc/on_part_entered(datum/source, obj/item/inserted_component)
- SIGNAL_HANDLER
-
- if(istype(inserted_component, /obj/item/stock_parts/power_store))
- var/obj/item/stock_parts/power_store/inserted_cell = inserted_component
- if(inserted_cell.rigged || inserted_cell.corrupted)
- message_admins("[ADMIN_LOOKUPFLW(usr)] has inserted rigged/corrupted [inserted_cell] into [src].")
- usr.log_message("has inserted rigged/corrupted [inserted_cell] into [src].", LOG_GAME)
- usr.log_message("inserted rigged/corrupted [inserted_cell] into [src]", LOG_ATTACK)
- return
-
- if(inserted_component.reagents)
- if(length(inserted_component.reagents.reagent_list))
- inserted_component.reagents.clear_reagents()
- to_chat(usr, span_notice("[src] churns as [inserted_component] has its reagents emptied into bluespace."))
- RegisterSignal(inserted_component.reagents, COMSIG_REAGENTS_PRE_ADD_REAGENT, PROC_REF(on_insered_component_reagent_pre_add))
-
-/**
- * Signal handler for when the reagents datum of an inserted part has reagents added to it.
- *
- * Registers the PRE_ADD variant which allows the signal handler to stop reagents being
- * added.
- *
- * Simply returns COMPONENT_CANCEL_REAGENT_ADD. We never want to allow people to add
- * reagents to beakers in BRPEDs as they can then be used for spammable remote bombing.
- */
-/obj/item/storage/part_replacer/bluespace/proc/on_insered_component_reagent_pre_add(datum/source, reagent, amount, reagtemp, data, no_react)
- SIGNAL_HANDLER
-
- return COMPONENT_CANCEL_REAGENT_ADD
-
-/**
- * Signal handler for a part is removed from the BRPED.
- *
- * Does signal registration cleanup on its reagents, if it has any.
- */
-/obj/item/storage/part_replacer/bluespace/proc/on_part_exited(datum/source, obj/item/removed_component)
- SIGNAL_HANDLER
-
- if(removed_component.reagents)
- UnregisterSignal(removed_component.reagents, COMSIG_REAGENTS_PRE_ADD_REAGENT)
-
-
-/obj/item/storage/part_replacer/bluespace/tier1
-
-/obj/item/storage/part_replacer/bluespace/tier1/PopulateContents()
- for(var/i in 1 to 10)
- new /obj/item/stock_parts/capacitor(src)
- new /obj/item/stock_parts/scanning_module(src)
- new /obj/item/stock_parts/servo(src)
- new /obj/item/stock_parts/micro_laser(src)
- new /obj/item/stock_parts/matter_bin(src)
- new /obj/item/stock_parts/power_store/cell/high(src)
-
-/obj/item/storage/part_replacer/bluespace/tier2
-
-/obj/item/storage/part_replacer/bluespace/tier2/PopulateContents()
- for(var/i in 1 to 10)
- new /obj/item/stock_parts/capacitor/adv(src)
- new /obj/item/stock_parts/scanning_module/adv(src)
- new /obj/item/stock_parts/servo/nano(src)
- new /obj/item/stock_parts/micro_laser/high(src)
- new /obj/item/stock_parts/matter_bin/adv(src)
- new /obj/item/stock_parts/power_store/cell/super(src)
-
-/obj/item/storage/part_replacer/bluespace/tier3
-
-/obj/item/storage/part_replacer/bluespace/tier3/PopulateContents()
- for(var/i in 1 to 10)
- new /obj/item/stock_parts/capacitor/super(src)
- new /obj/item/stock_parts/scanning_module/phasic(src)
- new /obj/item/stock_parts/servo/pico(src)
- new /obj/item/stock_parts/micro_laser/ultra(src)
- new /obj/item/stock_parts/matter_bin/super(src)
- new /obj/item/stock_parts/power_store/cell/hyper(src)
-
-/obj/item/storage/part_replacer/bluespace/tier4
-
-/obj/item/storage/part_replacer/bluespace/tier4/PopulateContents()
- for(var/i in 1 to 10)
- new /obj/item/stock_parts/capacitor/quadratic(src)
- new /obj/item/stock_parts/scanning_module/triphasic(src)
- new /obj/item/stock_parts/servo/femto(src)
- new /obj/item/stock_parts/micro_laser/quadultra(src)
- new /obj/item/stock_parts/matter_bin/bluespace(src)
- new /obj/item/stock_parts/power_store/cell/bluespace(src)
-
-/obj/item/storage/part_replacer/cargo //used in a cargo crate
-
-/obj/item/storage/part_replacer/cargo/PopulateContents()
- for(var/i in 1 to 10)
- new /obj/item/stock_parts/capacitor(src)
- new /obj/item/stock_parts/scanning_module(src)
- new /obj/item/stock_parts/servo(src)
- new /obj/item/stock_parts/micro_laser(src)
- new /obj/item/stock_parts/matter_bin(src)
-
-/obj/item/storage/part_replacer/cyborg //NOVA EDIT - ICON OVERRIDDEN BY AESTHETICS - SEE MODULE
- name = "rapid part exchange device"
- desc = "Special mechanical module made to store, sort, and apply standard machine parts. This one has an extra large compartment for more parts."
- icon_state = "borgrped"
- inhand_icon_state = "RPED"
- lefthand_file = 'icons/mob/inhands/items/devices_lefthand.dmi'
- righthand_file = 'icons/mob/inhands/items/devices_righthand.dmi'
-
-/obj/item/storage/part_replacer/cyborg/Initialize(mapload)
- . = ..()
- atom_storage.max_slots = 400
- atom_storage.max_total_storage = 800
- atom_storage.max_specific_storage = WEIGHT_CLASS_GIGANTIC
-
-/obj/item/storage/part_replacer/proc/get_sorted_parts(ignore_stacks = FALSE)
- var/list/part_list = list()
- //Assemble a list of current parts, then sort them by their rating!
- for(var/obj/item/component_part in contents)
- //No need to put circuit boards in this list or stacks when exchanging parts
- if(istype(component_part, /obj/item/circuitboard) || (ignore_stacks && istype(component_part, /obj/item/stack)))
- continue
- part_list += component_part
- //Sort the parts. This ensures that higher tier items are applied first.
- sortTim(part_list, GLOBAL_PROC_REF(cmp_rped_sort))
- return part_list
-
-/proc/cmp_rped_sort(obj/item/first_item, obj/item/second_item)
- /**
- * even though stacks aren't stock parts, get_part_rating() is defined on the item level (see /obj/item/proc/get_part_rating()) and defaults to returning 0.
- */
- return second_item.get_part_rating() - first_item.get_part_rating()
/obj/item/stock_parts
name = "stock part"
diff --git a/code/modules/research/techweb/nodes/cyborg_nodes.dm b/code/modules/research/techweb/nodes/cyborg_nodes.dm
index fad15b6f019a..87300c7e70c8 100644
--- a/code/modules/research/techweb/nodes/cyborg_nodes.dm
+++ b/code/modules/research/techweb/nodes/cyborg_nodes.dm
@@ -148,7 +148,6 @@
"implantcase",
"implanter",
"locator",
- "c38_trac",
)
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_1_POINTS)
announce_channels = list(RADIO_CHANNEL_SECURITY, RADIO_CHANNEL_MEDICAL)
diff --git a/code/modules/research/techweb/nodes/engi_nodes.dm b/code/modules/research/techweb/nodes/engi_nodes.dm
index 4ef55e21bc97..75c9459771c2 100644
--- a/code/modules/research/techweb/nodes/engi_nodes.dm
+++ b/code/modules/research/techweb/nodes/engi_nodes.dm
@@ -155,6 +155,7 @@
"manulathe",
"manusorter",
"manurouter",
+ "mailsorter",
)
/datum/techweb_node/energy_manipulation
diff --git a/code/modules/research/techweb/nodes/modsuit_nodes.dm b/code/modules/research/techweb/nodes/modsuit_nodes.dm
index cc31a1fc1ef7..0ce6eb4b229a 100644
--- a/code/modules/research/techweb/nodes/modsuit_nodes.dm
+++ b/code/modules/research/techweb/nodes/modsuit_nodes.dm
@@ -34,6 +34,7 @@
"mod_longfall",
"mod_thermal_regulator",
"mod_sign_radio",
+ "mod_mister_janitor",
)
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_1_POINTS)
announce_channels = list(RADIO_CHANNEL_SCIENCE)
diff --git a/code/modules/research/techweb/nodes/security_nodes.dm b/code/modules/research/techweb/nodes/security_nodes.dm
index 97d2036207c5..02679b4280ee 100644
--- a/code/modules/research/techweb/nodes/security_nodes.dm
+++ b/code/modules/research/techweb/nodes/security_nodes.dm
@@ -7,7 +7,9 @@
"toy_armblade",
"toygun",
"c38_rubber",
- "sec_38",
+ "c38_rubber_mag",
+ "c38_sec",
+ "c38_mag",
"capbox",
"foam_dart",
"sec_beanbag_slug",
@@ -80,7 +82,13 @@
prereq_ids = list(TECHWEB_NODE_EXPLOSIVES)
design_ids = list(
"c38_hotshot",
+ "c38_hotshot_mag",
"c38_iceblox",
+ "c38_iceblox_mag",
+ "c38_trac",
+ "c38_trac_mag",
+ "c38_true_strike",
+ "c38_true_strike_mag",
"techshotshell",
)
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_4_POINTS)
diff --git a/code/modules/research/xenobiology/crossbreeding/_potions.dm b/code/modules/research/xenobiology/crossbreeding/_potions.dm
index c33868e07971..2f82396168e7 100644
--- a/code/modules/research/xenobiology/crossbreeding/_potions.dm
+++ b/code/modules/research/xenobiology/crossbreeding/_potions.dm
@@ -9,7 +9,7 @@ Slimecrossing Potions
name = "extract cloning potion"
desc = "A more powerful version of the extract enhancer potion, capable of cloning regular slime extracts."
icon = 'icons/obj/medical/chemical.dmi'
- icon_state = "potpurple"
+ icon_state = "potgold"
/obj/item/slimepotion/extract_cloner/interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers)
. = ..()
@@ -102,7 +102,7 @@ Slimecrossing Potions
name = "slime pressurization potion"
desc = "A potent chemical sealant that will render any article of clothing airtight. Has two uses."
icon = 'icons/obj/medical/chemical.dmi'
- icon_state = "potblue"
+ icon_state = "potblack"
var/uses = 2
/obj/item/slimepotion/spaceproof/interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers)
@@ -125,7 +125,7 @@ Slimecrossing Potions
to_chat(user, span_notice("You slather the blue gunk over the [clothing], making it airtight."))
clothing.name = "pressure-resistant [clothing.name]"
clothing.remove_atom_colour(WASHABLE_COLOUR_PRIORITY)
- clothing.add_atom_colour(COLOR_NAVY, FIXED_COLOUR_PRIORITY)
+ clothing.add_atom_colour(color_transition_filter(COLOR_NAVY, SATURATION_OVERRIDE), FIXED_COLOUR_PRIORITY)
clothing.min_cold_protection_temperature = SPACE_SUIT_MIN_TEMP_PROTECT
clothing.cold_protection = clothing.body_parts_covered
clothing.clothing_flags |= STOPSPRESSUREDAMAGE
@@ -139,14 +139,14 @@ Slimecrossing Potions
name = "extract maximizer"
desc = "An extremely potent chemical mix that will maximize a slime extract's uses."
icon = 'icons/obj/medical/chemical.dmi'
- icon_state = "potpurple"
+ icon_state = "potcerulean"
//Lavaproofing potion - Charged Red
/obj/item/slimepotion/lavaproof
name = "slime lavaproofing potion"
desc = "A strange, reddish goo said to repel lava as if it were water, without reducing flammability. Has two uses."
icon = 'icons/obj/medical/chemical.dmi'
- icon_state = "potred"
+ icon_state = "potyellow"
resistance_flags = LAVA_PROOF | FIRE_PROOF
var/uses = 2
@@ -165,7 +165,7 @@ Slimecrossing Potions
to_chat(user, span_notice("You slather the red gunk over the [clothing], making it lavaproof."))
clothing.name = "lavaproof [clothing.name]"
clothing.remove_atom_colour(WASHABLE_COLOUR_PRIORITY)
- clothing.add_atom_colour(COLOR_MAROON, FIXED_COLOUR_PRIORITY)
+ clothing.add_atom_colour(color_transition_filter(COLOR_MAROON, SATURATION_OVERRIDE), FIXED_COLOUR_PRIORITY)
clothing.resistance_flags |= LAVA_PROOF
if (isclothing(clothing))
var/obj/item/clothing/clothing_real = clothing
@@ -181,7 +181,7 @@ Slimecrossing Potions
name = "slime revival potion"
desc = "Infused with plasma and compressed gel, this brings dead slimes back to life."
icon = 'icons/obj/medical/chemical.dmi'
- icon_state = "potsilver"
+ icon_state = "potgrey"
/obj/item/slimepotion/slime_reviver/interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers)
. = ..()
diff --git a/code/modules/research/xenobiology/crossbreeding/_weapons.dm b/code/modules/research/xenobiology/crossbreeding/_weapons.dm
index 3cc46b9be32e..c73ef832c548 100644
--- a/code/modules/research/xenobiology/crossbreeding/_weapons.dm
+++ b/code/modules/research/xenobiology/crossbreeding/_weapons.dm
@@ -87,6 +87,7 @@ Slimecrossing Weapons
icon = 'icons/obj/science/slimecrossing.dmi'
icon_state = "bloodgun"
inhand_icon_state = "bloodgun"
+ icon_angle = 180
lefthand_file = 'icons/mob/inhands/weapons/guns_lefthand.dmi'
righthand_file = 'icons/mob/inhands/weapons/guns_righthand.dmi'
item_flags = ABSTRACT | DROPDEL
diff --git a/code/modules/research/xenobiology/crossbreeding/burning.dm b/code/modules/research/xenobiology/crossbreeding/burning.dm
index c3eb811fa427..f114d3ad55da 100644
--- a/code/modules/research/xenobiology/crossbreeding/burning.dm
+++ b/code/modules/research/xenobiology/crossbreeding/burning.dm
@@ -211,9 +211,6 @@ Burning extracts:
effect_desc = "The user gets a dull arm blade in the hand it is used in."
/obj/item/slimecross/burning/green/do_effect(mob/user)
- var/which_hand = "l_hand"
- if(!(user.active_hand_index % 2))
- which_hand = "r_hand"
var/mob/living/L = user
if(!istype(user))
return
@@ -226,7 +223,7 @@ Burning extracts:
else
user.visible_message(span_danger("[src] sublimates the flesh around [user]'s arm, transforming the bone into a gruesome blade!"))
user.emote("scream")
- L.apply_damage(30,BURN,which_hand)
+ L.apply_damage(30, BURN, L.get_active_hand())
..()
/obj/item/slimecross/burning/pink
diff --git a/code/modules/research/xenobiology/crossbreeding/chilling.dm b/code/modules/research/xenobiology/crossbreeding/chilling.dm
index 0a22cd0380f5..c1c599da345c 100644
--- a/code/modules/research/xenobiology/crossbreeding/chilling.dm
+++ b/code/modules/research/xenobiology/crossbreeding/chilling.dm
@@ -252,9 +252,6 @@ Chilling extracts:
effect_desc = "Creates a bone gun in the hand it is used in, which uses blood as ammo."
/obj/item/slimecross/chilling/green/do_effect(mob/user)
- var/which_hand = "l_hand"
- if(!(user.active_hand_index % 2))
- which_hand = "r_hand"
var/mob/living/L = user
if(!istype(user))
return
@@ -267,7 +264,7 @@ Chilling extracts:
else
user.visible_message(span_danger("[src] chills and snaps off the front of the bone on [user]'s arm, leaving behind a strange, gun-like structure!"))
user.emote("scream")
- L.apply_damage(30,BURN,which_hand)
+ L.apply_damage(30, BURN, L.get_active_hand())
..()
/obj/item/slimecross/chilling/pink
diff --git a/code/modules/research/xenobiology/crossbreeding/prismatic.dm b/code/modules/research/xenobiology/crossbreeding/prismatic.dm
index 947323b0e47f..647711192afe 100644
--- a/code/modules/research/xenobiology/crossbreeding/prismatic.dm
+++ b/code/modules/research/xenobiology/crossbreeding/prismatic.dm
@@ -14,7 +14,7 @@ Prismatic extracts:
if(!isturf(interacting_with) || isspaceturf(interacting_with))
return NONE
user.do_attack_animation(interacting_with)
- interacting_with.add_atom_colour(paintcolor, WASHABLE_COLOUR_PRIORITY)
+ interacting_with.add_atom_colour(color_transition_filter(paintcolor, SATURATION_OVERRIDE), WASHABLE_COLOUR_PRIORITY)
playsound(interacting_with, 'sound/effects/slosh.ogg', 20, TRUE)
return ITEM_INTERACT_SUCCESS
diff --git a/code/modules/research/xenobiology/crossbreeding/regenerative.dm b/code/modules/research/xenobiology/crossbreeding/regenerative.dm
index a2c5698fbb19..a0d9a1e89eff 100644
--- a/code/modules/research/xenobiology/crossbreeding/regenerative.dm
+++ b/code/modules/research/xenobiology/crossbreeding/regenerative.dm
@@ -30,7 +30,7 @@ Regenerative extracts:
span_notice("You squeeze [src], and it bursts in your hand, splashing you with milky goo which quickly regenerates your injuries!"))
core_effect_before(H, user)
user.do_attack_animation(interacting_with)
- H.revive(HEAL_ALL)
+ H.revive(HEAL_ALL & ~HEAL_REFRESH_ORGANS)
core_effect(H, user)
playsound(H, 'sound/effects/splat.ogg', 40, TRUE)
qdel(src)
@@ -128,13 +128,13 @@ Regenerative extracts:
if(fireproofed)
target.visible_message(span_notice("Some of [target]'s clothing gets coated in the goo, and turns blue!"))
-/obj/item/slimecross/regenerative/darkblue/proc/fireproof(obj/item/clothing/C)
- C.name = "fireproofed [C.name]"
- C.remove_atom_colour(WASHABLE_COLOUR_PRIORITY)
- C.add_atom_colour(COLOR_NAVY, FIXED_COLOUR_PRIORITY)
- C.max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT
- C.heat_protection = C.body_parts_covered
- C.resistance_flags |= FIRE_PROOF
+/obj/item/slimecross/regenerative/darkblue/proc/fireproof(obj/item/clothing/clothing_piece)
+ clothing_piece.name = "fireproofed [clothing_piece.name]"
+ clothing_piece.remove_atom_colour(WASHABLE_COLOUR_PRIORITY)
+ clothing_piece.add_atom_colour(color_transition_filter(COLOR_NAVY, SATURATION_OVERRIDE), FIXED_COLOUR_PRIORITY)
+ clothing_piece.max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT
+ clothing_piece.heat_protection = clothing_piece.body_parts_covered
+ clothing_piece.resistance_flags |= FIRE_PROOF
/obj/item/slimecross/regenerative/silver
colour = SLIME_TYPE_SILVER
@@ -188,7 +188,7 @@ Regenerative extracts:
/obj/item/slimecross/regenerative/pyrite/core_effect(mob/living/target, mob/user)
target.visible_message(span_warning("The milky goo coating [target] leaves [target.p_them()] a different color!"))
- target.add_atom_colour(rgb(rand(0,255),rand(0,255),rand(0,255)),WASHABLE_COLOUR_PRIORITY)
+ target.add_atom_colour(color_transition_filter(rgb(rand(0,255), rand(0,255), rand(0,255)), SATURATION_OVERRIDE), WASHABLE_COLOUR_PRIORITY)
/obj/item/slimecross/regenerative/red
colour = SLIME_TYPE_RED
@@ -270,7 +270,7 @@ Regenerative extracts:
if(target == user)
return
var/mob/living/U = user
- U.revive(HEAL_ALL)
+ U.revive(HEAL_ALL & ~HEAL_REFRESH_ORGANS)
to_chat(U, span_notice("Some of the milky goo sprays onto you, as well!"))
/obj/item/slimecross/regenerative/adamantine
diff --git a/code/modules/research/xenobiology/xenobio_camera.dm b/code/modules/research/xenobiology/xenobio_camera.dm
index 66cbd4b93de5..d98f62596ea4 100644
--- a/code/modules/research/xenobiology/xenobio_camera.dm
+++ b/code/modules/research/xenobiology/xenobio_camera.dm
@@ -350,7 +350,7 @@ Due to keyboard shortcuts, the second one is not necessarily the remote eye's lo
render_list += "• Alt-click a slime to feed it a potion."
render_list += "• Ctrl-click or a dead monkey to recycle it, or the floor to place a new monkey."
- to_chat(owner, examine_block(jointext(render_list, "\n")))
+ to_chat(owner, boxed_message(jointext(render_list, "\n")))
//
// Alternate clicks for slime, monkey and open turf if using a xenobio console
diff --git a/code/modules/research/xenobiology/xenobiology.dm b/code/modules/research/xenobiology/xenobiology.dm
index 594163d153bc..f63ee0f08807 100644
--- a/code/modules/research/xenobiology/xenobiology.dm
+++ b/code/modules/research/xenobiology/xenobiology.dm
@@ -896,7 +896,7 @@
name = "slime speed potion"
desc = "A potent chemical mix that will remove the slowdown from any item."
icon = 'icons/obj/medical/chemical.dmi'
- icon_state = "potyellow"
+ icon_state = "potred"
/obj/item/slimepotion/speed/interact_with_atom(obj/interacting_with, mob/living/user, list/modifiers)
. = ..()
@@ -921,7 +921,7 @@
to_chat(user, span_notice("You slather the red gunk over the [interacting_with], making it faster."))
interacting_with.remove_atom_colour(WASHABLE_COLOUR_PRIORITY)
- interacting_with.add_atom_colour(COLOR_RED, FIXED_COLOUR_PRIORITY)
+ interacting_with.add_atom_colour(color_transition_filter(COLOR_RED, SATURATION_OVERRIDE), FIXED_COLOUR_PRIORITY)
interacting_with.drag_slowdown = 0
ADD_TRAIT(interacting_with, TRAIT_SPEED_POTIONED, SLIME_POTION_TRAIT)
qdel(src)
@@ -952,7 +952,7 @@
to_chat(user, span_notice("You slather the blue gunk over the [clothing], fireproofing it."))
clothing.name = "fireproofed [clothing.name]"
clothing.remove_atom_colour(WASHABLE_COLOUR_PRIORITY)
- clothing.add_atom_colour(COLOR_NAVY, FIXED_COLOUR_PRIORITY)
+ clothing.add_atom_colour(color_transition_filter(COLOR_NAVY, SATURATION_OVERRIDE), FIXED_COLOUR_PRIORITY)
clothing.max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT
clothing.heat_protection = clothing.body_parts_covered
clothing.resistance_flags |= FIRE_PROOF
@@ -965,7 +965,7 @@
name = "gender change potion"
desc = "An interesting chemical mix that changes the biological gender of what its applied to. Cannot be used on things that lack gender entirely."
icon = 'icons/obj/medical/chemical.dmi'
- icon_state = "potlightpink"
+ icon_state = "potrainbow"
/obj/item/slimepotion/genderchange/attack(mob/living/L, mob/user)
if(!istype(L) || L.stat == DEAD)
@@ -989,7 +989,7 @@
name = "renaming potion"
desc = "A potion that allows a self-aware being to change what name it subconciously presents to the world."
icon = 'icons/obj/medical/chemical.dmi'
- icon_state = "potgreen"
+ icon_state = "potbrown"
var/being_used = FALSE
@@ -1023,7 +1023,7 @@
name = "bluespace radio potion"
desc = "A strange chemical that grants those who ingest it the ability to broadcast and receive subscape radio waves."
icon = 'icons/obj/medical/chemical.dmi'
- icon_state = "potgrey"
+ icon_state = "potbluespace"
/obj/item/slimepotion/slime/slimeradio/attack(mob/living/radio_head, mob/user)
if(!isanimal_or_basicmob(radio_head))
diff --git a/code/modules/shuttle/emergency.dm b/code/modules/shuttle/emergency.dm
deleted file mode 100644
index 18e8de721017..000000000000
--- a/code/modules/shuttle/emergency.dm
+++ /dev/null
@@ -1,896 +0,0 @@
-#define TIME_LEFT (SSshuttle.emergency.timeLeft())
-#define ENGINES_START_TIME 100
-#define ENGINES_STARTED (SSshuttle.emergency.mode == SHUTTLE_IGNITING)
-#define IS_DOCKED (SSshuttle.emergency.mode == SHUTTLE_DOCKED || (ENGINES_STARTED))
-#define SHUTTLE_CONSOLE_ACTION_DELAY (5 SECONDS)
-
-#define NOT_BEGUN 0
-#define STAGE_1 1
-#define STAGE_2 2
-#define STAGE_3 3
-#define STAGE_4 4
-#define HIJACKED 5
-
-/obj/machinery/computer/emergency_shuttle
- name = "emergency shuttle console"
- desc = "For shuttle control."
- icon_screen = "shuttle"
- icon_keyboard = "tech_key"
- resistance_flags = INDESTRUCTIBLE
- var/auth_need = 3
- var/list/authorized = list()
- var/list/acted_recently = list()
- var/hijack_last_stage_increase = 0 SECONDS
- var/hijack_stage_time = 5 SECONDS
- var/hijack_stage_cooldown = 5 SECONDS
- var/hijack_flight_time_increase = 30 SECONDS
- var/hijack_completion_flight_time_set = 10 SECONDS //How long in deciseconds to set shuttle's timer after hijack is done.
- var/hijack_hacking = FALSE
- var/hijack_announce = TRUE
-
-/obj/machinery/computer/emergency_shuttle/examine(mob/user)
- . = ..()
- if(hijack_announce)
- . += span_danger("Security systems present on console. Any unauthorized tampering will result in an emergency announcement.")
- if(user?.mind?.get_hijack_speed())
- . += span_danger("Alt click on this to attempt to hijack the shuttle. This will take multiple tries (current: stage [SSshuttle.emergency.hijack_status]/[HIJACKED]).")
- . += span_notice("It will take you [(hijack_stage_time * user.mind.get_hijack_speed()) / 10] seconds to reprogram a stage of the shuttle's navigational firmware, and the console will undergo automated timed lockout for [hijack_stage_cooldown/10] seconds after each stage.")
- if(hijack_announce)
- . += span_warning("It is probably best to fortify your position as to be uninterrupted during the attempt, given the automatic announcements..")
-
-/obj/machinery/computer/emergency_shuttle/attackby(obj/item/I, mob/user,params)
- if(isidcard(I))
- say("Please equip your ID card into your ID slot to authenticate.")
- . = ..()
-
-/obj/machinery/computer/emergency_shuttle/ui_state(mob/user)
- return GLOB.human_adjacent_state
-
-/obj/machinery/computer/emergency_shuttle/ui_interact(mob/user, datum/tgui/ui)
- . = ..()
- ui = SStgui.try_update_ui(user, src, ui)
- if(!ui)
- ui = new(user, src, "EmergencyShuttleConsole", name)
- ui.open()
-
-/obj/machinery/computer/emergency_shuttle/ui_data(user)
- var/list/data = list()
-
- data["timer_str"] = SSshuttle.emergency.getTimerStr()
- data["engines_started"] = ENGINES_STARTED
- data["authorizations_remaining"] = max((auth_need - authorized.len), 0)
- var/list/A = list()
- for(var/i in authorized)
- var/obj/item/card/id/ID = i
- var/name = ID.registered_name
- var/job = ID.assignment
-
- if(obj_flags & EMAGGED)
- name = Gibberish(name)
- job = Gibberish(job)
- A += list(list("name" = name, "job" = job))
- data["authorizations"] = A
-
- data["enabled"] = (IS_DOCKED && !ENGINES_STARTED) && !(user in acted_recently)
- data["emagged"] = obj_flags & EMAGGED ? 1 : 0
- return data
-
-/obj/machinery/computer/emergency_shuttle/ui_act(action, params, datum/tgui/ui)
- . = ..()
- if(.)
- return
- if(ENGINES_STARTED) // past the point of no return
- return
- if(!IS_DOCKED) // shuttle computer only has uses when onstation
- return
- if(SSshuttle.emergency.mode == SHUTTLE_DISABLED) // admins have disabled the shuttle.
- return
- if(!isliving(usr))
- return
-
- var/area/my_area = get_area(src)
- if(!istype(my_area, /area/shuttle/escape))
- say("Error - Network connectivity: Console has lost connection to the shuttle.")
- return
-
- var/mob/living/user = usr
- . = FALSE
-
- var/obj/item/card/id/ID = user.get_idcard(TRUE)
-
- if(!ID)
- to_chat(user, span_warning("You don't have an ID."))
- return
-
- if(!(ACCESS_COMMAND in ID.access))
- to_chat(user, span_warning("The access level of your card is not high enough."))
- return
-
- if (user in acted_recently)
- return
-
- var/old_len = authorized.len
- addtimer(CALLBACK(src, PROC_REF(clear_recent_action), user), SHUTTLE_CONSOLE_ACTION_DELAY)
-
- switch(action)
- if("authorize")
- . = authorize(user)
-
- if("repeal")
- authorized -= ID
-
- if("abort")
- if(authorized.len)
- // Abort. The action for when heads are fighting over whether
- // to launch early.
- authorized.Cut()
- . = TRUE
-
- if((old_len != authorized.len) && !ENGINES_STARTED)
- var/alert = (authorized.len > old_len)
- var/repeal = (authorized.len < old_len)
- var/remaining = max(0, auth_need - authorized.len)
- if(authorized.len && remaining)
- minor_announce("[remaining] authorizations needed until shuttle is launched early", null, alert)
- if(repeal)
- minor_announce("Early launch authorization revoked, [remaining] authorizations needed")
-
- acted_recently += user
- SStgui.update_user_uis(user, src)
-
-/obj/machinery/computer/emergency_shuttle/proc/authorize(mob/living/user, source)
- var/obj/item/card/id/ID = user.get_idcard(TRUE)
-
- if(ID in authorized)
- return FALSE
- for(var/i in authorized)
- var/obj/item/card/id/other = i
- if(other.registered_name == ID.registered_name)
- return FALSE // No using IDs with the same name
-
- authorized += ID
-
- message_admins("[ADMIN_LOOKUPFLW(user)] has authorized early shuttle launch")
- log_shuttle("[key_name(user)] has authorized early shuttle launch in [COORD(src)]")
- // Now check if we're on our way
- . = TRUE
- process(SSMACHINES_DT)
-
-/obj/machinery/computer/emergency_shuttle/proc/clear_recent_action(mob/user)
- acted_recently -= user
- if (!QDELETED(user))
- SStgui.update_user_uis(user, src)
-
-/obj/machinery/computer/emergency_shuttle/process()
- // Launch check is in process in case auth_need changes for some reason
- // probably external.
- . = FALSE
- if(!SSshuttle.emergency)
- return
-
- if(SSshuttle.emergency.mode == SHUTTLE_STRANDED)
- authorized.Cut()
- obj_flags &= ~(EMAGGED)
-
- if(ENGINES_STARTED || (!IS_DOCKED))
- return .
-
- // Check to see if we've reached criteria for early launch
- if((authorized.len >= auth_need) || (obj_flags & EMAGGED))
- // shuttle timers use 1/10th seconds internally
- SSshuttle.emergency.setTimer(ENGINES_START_TIME)
- var/system_error = obj_flags & EMAGGED ? "SYSTEM ERROR:" : null
- minor_announce("The emergency shuttle will launch in \
- [TIME_LEFT] seconds", system_error, alert=TRUE)
- . = TRUE
-
-/obj/machinery/computer/emergency_shuttle/proc/increase_hijack_stage()
- var/obj/docking_port/mobile/emergency/shuttle = SSshuttle.emergency
- // Begin loading this early, prevents a delay when the shuttle goes to land
- INVOKE_ASYNC(SSmapping, TYPE_PROC_REF(/datum/controller/subsystem/mapping, lazy_load_template), LAZY_TEMPLATE_KEY_NUKIEBASE)
-
- shuttle.hijack_status++
- if(hijack_announce)
- announce_hijack_stage()
- hijack_last_stage_increase = world.time
- say("Navigational protocol error! Rebooting systems.")
- if(shuttle.mode == SHUTTLE_ESCAPE)
- if(shuttle.hijack_status == HIJACKED)
- shuttle.setTimer(hijack_completion_flight_time_set)
- else
- shuttle.setTimer(shuttle.timeLeft(1) + hijack_flight_time_increase) //give the guy more time to hijack if it's already in flight.
- return shuttle.hijack_status
-
-/obj/machinery/computer/emergency_shuttle/click_alt(mob/living/user)
- if(!isliving(user))
- return NONE
- attempt_hijack_stage(user)
- return CLICK_ACTION_SUCCESS
-
-/obj/machinery/computer/emergency_shuttle/proc/attempt_hijack_stage(mob/living/user)
- if(!user.CanReach(src))
- return
- if(HAS_TRAIT(user, TRAIT_HANDS_BLOCKED))
- to_chat(user, span_warning("You need your hands free before you can manipulate [src]."))
- return
- var/area/my_area = get_area(src)
- if(!istype(my_area, /area/shuttle/escape))
- say("Error - Network connectivity: Console has lost connection to the shuttle.")
- return
- if(!user?.mind?.get_hijack_speed())
- to_chat(user, span_warning("You manage to open a user-mode shell on [src], and hundreds of lines of debugging output fly through your vision. It is probably best to leave this alone."))
- return
- if(!EMERGENCY_AT_LEAST_DOCKED) // prevent advancing hijack stages on BYOS shuttles until the shuttle has "docked"
- to_chat(user, span_warning("The flight plans for the shuttle haven't been loaded yet, you can't hack this right now."))
- return
- if(hijack_hacking == TRUE)
- return
- if(SSshuttle.emergency.hijack_status >= HIJACKED)
- to_chat(user, span_warning("The emergency shuttle is already loaded with a corrupt navigational payload. What more do you want from it?"))
- return
- if(hijack_last_stage_increase >= world.time - hijack_stage_cooldown)
- say("Error - Catastrophic software error detected. Input is currently on timeout.")
- return
- hijack_hacking = TRUE
- to_chat(user, span_boldwarning("You [SSshuttle.emergency.hijack_status == NOT_BEGUN? "begin" : "continue"] to override [src]'s navigational protocols."))
- say("Software override initiated.")
- var/turf/console_hijack_turf = get_turf(src)
- message_admins("[src] is being overriden for hijack by [ADMIN_LOOKUPFLW(user)] in [ADMIN_VERBOSEJMP(console_hijack_turf)]")
- user.log_message("is hijacking [src].", LOG_GAME)
- . = FALSE
- if(do_after(user, hijack_stage_time * (1 / user.mind.get_hijack_speed()), target = src))
- increase_hijack_stage()
- console_hijack_turf = get_turf(src)
- message_admins("[ADMIN_LOOKUPFLW(user)] has hijacked [src] in [ADMIN_VERBOSEJMP(console_hijack_turf)]. Hijack stage increased to stage [SSshuttle.emergency.hijack_status] out of [HIJACKED].")
- user.log_message("has hijacked [src]. Hijack stage increased to stage [SSshuttle.emergency.hijack_status] out of [HIJACKED].", LOG_GAME)
- . = TRUE
- to_chat(user, span_notice("You reprogram some of [src]'s programming, putting it on timeout for [hijack_stage_cooldown/10] seconds."))
- visible_message(
- span_warning("[user.name] appears to be tampering with [src]."),
- blind_message = span_hear("You hear someone tapping computer keys."),
- vision_distance = COMBAT_MESSAGE_RANGE,
- ignored_mobs = user
- )
- hijack_hacking = FALSE
-
-/obj/machinery/computer/emergency_shuttle/proc/announce_hijack_stage()
- var/msg
- switch(SSshuttle.emergency.hijack_status)
- if(NOT_BEGUN)
- return
- if(STAGE_1)
- msg = "AUTHENTICATING - FAIL. AUTHENTICATING - FAIL. AUTHENTICATING - FAI###### Welcome, technician JOHN DOE."
- if(STAGE_2)
- msg = "Warning: Navigational route fails \"IS_AUTHORIZED\". Please try againNN[scramble_message_replace_chars("againagainagainagainagain", 70)]."
- if(STAGE_3)
- msg = "CRC mismatch at ~h~ in calculated route buffer. Full reset initiated of FTL_NAVIGATION_SERVICES. Memory decrypted for automatic repair."
- if(STAGE_4)
- msg = "~ACS_directive module_load(cyberdyne.exploit.nanotrasen.shuttlenav)... NT key mismatch. Confirm load? Y...###Reboot complete. $SET transponder_state = 0; System link initiated with connected engines..."
- if(HIJACKED)
- msg = "SYSTEM OVERRIDE - Resetting course to \[[scramble_message_replace_chars("###########", 100)]\] \
- ([scramble_message_replace_chars("#######", 100)]/[scramble_message_replace_chars("#######", 100)]/[scramble_message_replace_chars("#######", 100)]) \
- {AUTH - ROOT (uid: 0)}.\
- [SSshuttle.emergency.mode == SHUTTLE_ESCAPE ? "Diverting from existing route - Bluespace exit in \
- [hijack_completion_flight_time_set >= INFINITY ? "[scramble_message_replace_chars("\[ERROR\]")]" : hijack_completion_flight_time_set/10] seconds." : ""]"
- minor_announce(scramble_message_replace_chars(msg, replaceprob = 10), "Emergency Shuttle", TRUE)
-
-/obj/machinery/computer/emergency_shuttle/emag_act(mob/user, obj/item/card/emag/emag_card)
- // How did you even get on the shuttle before it go to the station?
- if(!IS_DOCKED)
- return FALSE
-
- if((obj_flags & EMAGGED) || ENGINES_STARTED) //SYSTEM ERROR: THE SHUTTLE WILL LA-SYSTEM ERROR: THE SHUTTLE WILL LA-SYSTEM ERROR: THE SHUTTLE WILL LAUNCH IN 10 SECONDS
- balloon_alert(user, "shuttle already about to launch!")
- return FALSE
-
- var/time = TIME_LEFT
- if (user)
- message_admins("[ADMIN_LOOKUPFLW(user)] has emagged the emergency shuttle [time] seconds before launch.")
- log_shuttle("[key_name(user)] has emagged the emergency shuttle in [COORD(src)] [time] seconds before launch.")
- else
- message_admins("The emergency shuttle was emagged [time] seconds before launch, with no emagger.")
- log_shuttle("The emergency shuttle was emagged in [COORD(src)] [time] seconds before launch, with no emagger.")
-
- obj_flags |= EMAGGED
- SSshuttle.emergency.movement_force = list("KNOCKDOWN" = 60, "THROW" = 20)//YOUR PUNY SEATBELTS can SAVE YOU NOW, MORTAL
- for(var/i in 1 to 10)
- // the shuttle system doesn't know who these people are, but they
- // must be important, surely
- var/obj/item/card/id/ID = new(src)
- var/datum/job/J = pick(SSjob.joinable_occupations)
- ID.registered_name = generate_random_name_species_based(species_type = /datum/species/human)
- ID.assignment = J.title
-
- authorized += ID
-
- process(SSMACHINES_DT)
- return TRUE
-
-/obj/machinery/computer/emergency_shuttle/Destroy()
- // Our fake IDs that the emag generated are just there for colour
- // They're not supposed to be accessible
-
- for(var/obj/item/card/id/ID in src)
- qdel(ID)
- if(authorized?.len)
- authorized.Cut()
- authorized = null
-
- . = ..()
-
-/obj/docking_port/mobile/emergency
- name = "emergency shuttle"
- shuttle_id = "emergency"
- dir = EAST
- port_direction = WEST
- var/sound_played = 0 //If the launch sound has been sent to all players on the shuttle itself
- var/hijack_status = NOT_BEGUN
-
-/obj/docking_port/mobile/emergency/Initialize(mapload)
- . = ..()
-
- setup_shuttle_events()
-
-/obj/docking_port/mobile/emergency/canDock(obj/docking_port/stationary/S)
- return SHUTTLE_CAN_DOCK //If the emergency shuttle can't move, the whole game breaks, so it will force itself to land even if it has to crush a few departments in the process
-
-/obj/docking_port/mobile/emergency/register()
- . = ..()
- SSshuttle.emergency = src
-
-/obj/docking_port/mobile/emergency/Destroy(force)
- if(force)
- // This'll make the shuttle subsystem use the backup shuttle.
- if(src == SSshuttle.emergency)
- // If we're the selected emergency shuttle
- SSshuttle.emergencyDeregister()
-
- . = ..()
-
-/obj/docking_port/mobile/emergency/request(obj/docking_port/stationary/S, area/signal_origin, reason, red_alert, set_coefficient=null, silent=FALSE) // NOVA EDIT CHANGE - AUTOTRANSFER - adds silent arg
- if(!isnum(set_coefficient))
- set_coefficient = SSsecurity_level.current_security_level.shuttle_call_time_mod
- alert_coeff = set_coefficient
- var/call_time = SSshuttle.emergency_call_time * alert_coeff * engine_coeff
- switch(mode)
- // The shuttle can not normally be called while "recalling", so
- // if this proc is called, it's via admin fiat
- if(SHUTTLE_RECALL, SHUTTLE_IDLE, SHUTTLE_CALL)
- mode = SHUTTLE_CALL
- setTimer(call_time)
- else
- return
-
- SSshuttle.emergencyCallAmount++
-
- if(prob(70))
- SSshuttle.emergency_last_call_loc = signal_origin
- else
- SSshuttle.emergency_last_call_loc = null
-
- // NOVA EDIT ADDITION START
- if(silent)
- return
- // NOVA EDIT ADDITION END
- priority_announce(
- text = "The emergency shuttle has been called. [red_alert ? "Red Alert state confirmed: Dispatching priority shuttle. " : "" ]It will arrive in [(timeLeft(60 SECONDS))] minutes.[reason][SSshuttle.emergency_last_call_loc ? "\n\nCall signal traced. Results can be viewed on any communications console." : "" ][SSshuttle.admin_emergency_no_recall ? "\n\nWarning: Shuttle recall subroutines disabled; Recall not possible." : ""]",
- title = "Emergency Shuttle Dispatched",
- sound = ANNOUNCER_SHUTTLECALLED,
- sender_override = "Emergency Shuttle Uplink Alert",
- color_override = "orange",
- )
-
-/obj/docking_port/mobile/emergency/cancel(area/signalOrigin)
- if(mode != SHUTTLE_CALL)
- return
- if(SSshuttle.emergency_no_recall)
- return
-
- invertTimer()
- mode = SHUTTLE_RECALL
-
- if(prob(70))
- SSshuttle.emergency_last_call_loc = signalOrigin
- else
- SSshuttle.emergency_last_call_loc = null
- priority_announce(
- text = "The emergency shuttle has been recalled.[SSshuttle.emergency_last_call_loc ? " Recall signal traced. Results can be viewed on any communications console." : "" ]",
- title = "Emergency Shuttle Recalled",
- sound = ANNOUNCER_SHUTTLERECALLED,
- sender_override = "Emergency Shuttle Uplink Alert",
- color_override = "orange",
- )
-
- SSticker.emergency_reason = null
-
-/**
- * Proc that handles checking if the emergency shuttle was successfully hijacked via being the only people present on the shuttle for the elimination hijack or highlander objective
- *
- * Checks for all mobs on the shuttle, checks their status, and checks if they're
- * borgs or simple animals. Depending on the args, certain mobs may be ignored,
- * and the presence of other antags may or may not invalidate a hijack.
- * Args:
- * filter_by_human, default TRUE, tells the proc that only humans should block a hijack. Borgs and animals are ignored and will not block if this is TRUE.
- * solo_hijack, default FALSE, tells the proc to fail with multiple hijackers, such as for Highlander mode.
- */
-/obj/docking_port/mobile/emergency/proc/elimination_hijack(filter_by_human = TRUE, solo_hijack = FALSE)
- var/has_people = FALSE
- var/hijacker_count = 0
- for(var/mob/living/player in GLOB.player_list)
- if(player.mind)
- if(player.stat != DEAD)
- if(issilicon(player) && filter_by_human) //Borgs are technically dead anyways
- continue
- if(isanimal_or_basicmob(player) && filter_by_human) //animals don't count
- continue
- if(isbrain(player)) //also technically dead
- continue
- if(shuttle_areas[get_area(player)])
- has_people = TRUE
- var/location = get_area(player.mind.current)
- //Non-antag present. Can't hijack.
- if(!(player.mind.has_antag_datum(/datum/antagonist)) && !istype(location, /area/shuttle/escape/brig))
- return FALSE
- //Antag present, doesn't stop but let's see if we actually want to hijack
- var/prevent = FALSE
- for(var/datum/antagonist/A in player.mind.antag_datums)
- if(A.can_elimination_hijack == ELIMINATION_ENABLED)
- hijacker_count += 1
- prevent = FALSE
- break //If we have both prevent and hijacker antags assume we want to hijack.
- else if(A.can_elimination_hijack == ELIMINATION_PREVENT)
- prevent = TRUE
- if(prevent)
- return FALSE
-
- //has people AND either there's only one hijacker or there's any but solo_hijack is disabled
- return has_people && ((hijacker_count == 1) || (hijacker_count && !solo_hijack))
-
-/obj/docking_port/mobile/emergency/proc/is_hijacked()
- return hijack_status == HIJACKED
-
-/obj/docking_port/mobile/emergency/proc/ShuttleDBStuff()
- set waitfor = FALSE
- if(!SSdbcore.Connect())
- return
- var/datum/db_query/query_round_shuttle_name = SSdbcore.NewQuery({"
- UPDATE [format_table_name("round")] SET shuttle_name = :name WHERE id = :round_id
- "}, list("name" = name, "round_id" = GLOB.round_id))
- query_round_shuttle_name.Execute()
- qdel(query_round_shuttle_name)
-
-/obj/docking_port/mobile/emergency/check()
- if(!timer)
- return
- var/time_left = timeLeft(1)
-
- // The emergency shuttle doesn't work like others so this
- // ripple check is slightly different
- if(!ripples.len && (time_left <= SHUTTLE_RIPPLE_TIME) && ((mode == SHUTTLE_CALL) || (mode == SHUTTLE_ESCAPE)))
- var/destination
- if(mode == SHUTTLE_CALL)
- destination = SSshuttle.getDock("emergency_home")
- else if(mode == SHUTTLE_ESCAPE)
- destination = SSshuttle.getDock("emergency_away")
- create_ripples(destination)
-
- switch(mode)
- if(SHUTTLE_RECALL)
- if(time_left <= 0)
- mode = SHUTTLE_IDLE
- timer = 0
- if(SHUTTLE_CALL)
- if(time_left <= 0)
- //move emergency shuttle to station
- if(initiate_docking(SSshuttle.getDock("emergency_home")) != DOCKING_SUCCESS)
- setTimer(20)
- return
- mode = SHUTTLE_DOCKED
- setTimer(SSshuttle.emergency_dock_time)
- send2adminchat("Server", "The Emergency Shuttle has docked with the station.")
- priority_announce(
- text = "[SSshuttle.emergency] has docked with the station. You have [DisplayTimeText(SSshuttle.emergency_dock_time)] to board the emergency shuttle.",
- title = "Emergency Shuttle Arrival",
- sound = ANNOUNCER_SHUTTLEDOCK,
- sender_override = "Emergency Shuttle Uplink Alert",
- color_override = "orange",
- )
- ShuttleDBStuff()
- addtimer(CALLBACK(src, PROC_REF(announce_shuttle_events)), 20 SECONDS)
-
-
- if(SHUTTLE_DOCKED)
- if(time_left <= ENGINES_START_TIME)
- mode = SHUTTLE_IGNITING
- SSshuttle.checkHostileEnvironment()
- if(mode == SHUTTLE_STRANDED)
- return
- for(var/A in SSshuttle.mobile_docking_ports)
- var/obj/docking_port/mobile/M = A
- if(M.launch_status == UNLAUNCHED) //Pods will not launch from the mine/planet, and other ships won't launch unless we tell them to.
- M.check_transit_zone()
-
-
- if(SHUTTLE_IGNITING)
- var/success = TRUE
- SSshuttle.checkHostileEnvironment()
- if(mode == SHUTTLE_STRANDED)
- return
-
- success &= (check_transit_zone() == TRANSIT_READY)
- for(var/A in SSshuttle.mobile_docking_ports)
- var/obj/docking_port/mobile/M = A
- if(M.launch_status == UNLAUNCHED)
- success &= (M.check_transit_zone() == TRANSIT_READY)
- if(!success)
- setTimer(ENGINES_START_TIME)
-
- if(time_left <= 50 && !sound_played) //4 seconds left:REV UP THOSE ENGINES BOYS. - should sync up with the launch
- sound_played = 1 //Only rev them up once.
- var/list/areas = list()
- for(var/area/shuttle/escape/E in GLOB.areas)
- areas += E
- hyperspace_sound(HYPERSPACE_WARMUP, areas)
-
- if(time_left <= 0 && !SSshuttle.emergency_no_escape)
- //move each escape pod (or applicable spaceship) to its corresponding transit dock
- for(var/A in SSshuttle.mobile_docking_ports)
- var/obj/docking_port/mobile/M = A
- M.on_emergency_launch()
-
- //now move the actual emergency shuttle to its transit dock
- var/list/areas = list()
- for(var/area/shuttle/escape/E in GLOB.areas)
- areas += E
- hyperspace_sound(HYPERSPACE_LAUNCH, areas)
- enterTransit()
-
- //Tell the events we're starting, so they can time their spawns or do some other stuff
- for(var/datum/shuttle_event/event as anything in event_list)
- event.start_up_event(SSshuttle.emergency_escape_time * engine_coeff)
-
- mode = SHUTTLE_ESCAPE
- launch_status = ENDGAME_LAUNCHED
- bolt_all_doors() // NOVA EDIT ADDITION
- setTimer(SSshuttle.emergency_escape_time * engine_coeff)
- priority_announce(
- text = "The emergency shuttle has left the station. Estimate [timeLeft(60 SECONDS)] minutes until the shuttle docks at [command_name()].",
- title = "Emergency Shuttle Departure",
- sender_override = "Emergency Shuttle Uplink Alert",
- color_override = "orange",
- )
- INVOKE_ASYNC(SSticker, TYPE_PROC_REF(/datum/controller/subsystem/ticker, poll_hearts))
- INVOKE_ASYNC(SSvote, TYPE_PROC_REF(/datum/controller/subsystem/vote, initiate_vote), /datum/vote/map_vote, vote_initiator_name = "Map Rotation", forced = TRUE)
-
- if(!is_reserved_level(z))
- CRASH("Emergency shuttle did not move to transit z-level!")
-
- if(SHUTTLE_STRANDED, SHUTTLE_DISABLED)
- SSshuttle.checkHostileEnvironment()
-
-
- if(SHUTTLE_ESCAPE)
- if(sound_played && time_left <= HYPERSPACE_END_TIME)
- var/list/areas = list()
- for(var/area/shuttle/escape/E in GLOB.areas)
- areas += E
- hyperspace_sound(HYPERSPACE_END, areas)
- if(time_left <= PARALLAX_LOOP_TIME)
- var/area_parallax = FALSE
- for(var/place in shuttle_areas)
- var/area/shuttle/shuttle_area = place
- if(shuttle_area.parallax_movedir)
- area_parallax = TRUE
- break
- if(area_parallax)
- parallax_slowdown()
- for(var/A in SSshuttle.mobile_docking_ports)
- var/obj/docking_port/mobile/M = A
- if(M.launch_status == ENDGAME_LAUNCHED)
- if(istype(M, /obj/docking_port/mobile/pod))
- M.parallax_slowdown()
-
- process_events()
-
- if(time_left <= 0)
- //move each escape pod to its corresponding escape dock
- for(var/obj/docking_port/mobile/port as anything in SSshuttle.mobile_docking_ports)
- port.on_emergency_dock()
-
- // now move the actual emergency shuttle to centcom
- // unless the shuttle is "hijacked"
- var/destination_dock = "emergency_away"
- if(is_hijacked() || elimination_hijack())
- // just double check
- SSmapping.lazy_load_template(LAZY_TEMPLATE_KEY_NUKIEBASE)
- destination_dock = "emergency_syndicate"
- minor_announce("Corruption detected in \
- shuttle navigation protocols. Please contact your \
- supervisor.", "SYSTEM ERROR:", sound_override = 'sound/announcer/announcement/announce_syndi.ogg')
-
- dock_id(destination_dock)
- unbolt_all_doors() //NOVA EDIT ADDITION
- INVOKE_ASYNC(GLOBAL_PROC, GLOBAL_PROC_REF(process_eorg_bans)) //NOVA EDIT ADDITION
- mode = SHUTTLE_ENDGAME
- timer = 0
-
-/obj/docking_port/mobile/emergency/transit_failure()
- ..()
- message_admins("Moving emergency shuttle directly to centcom dock to prevent deadlock.")
-
- mode = SHUTTLE_ESCAPE
- launch_status = ENDGAME_LAUNCHED
- setTimer(SSshuttle.emergency_escape_time)
- priority_announce(
- text = "The emergency shuttle is preparing for direct jump. Estimate [timeLeft(60 SECONDS)] minutes until the shuttle docks at [command_name()].",
- title = "Emergency Shuttle Transit Failure",
- sender_override = "Emergency Shuttle Uplink Alert",
- color_override = "orange",
- )
-
-///Generate a list of events to run during the departure
-/obj/docking_port/mobile/emergency/proc/setup_shuttle_events()
- var/list/names = list()
- for(var/datum/shuttle_event/event as anything in subtypesof(/datum/shuttle_event))
- if(prob(initial(event.event_probability)))
- add_shuttle_event(event)
- names += initial(event.name)
- if(LAZYLEN(names))
- log_game("[capitalize(name)] has selected the following shuttle events: [english_list(names)].")
-
-/obj/docking_port/mobile/monastery
- name = "monastery pod"
- shuttle_id = "mining_common" //set so mining can call it down
- launch_status = UNLAUNCHED //required for it to launch as a pod.
-
-/obj/docking_port/mobile/monastery/on_emergency_dock()
- if(launch_status == ENDGAME_LAUNCHED)
- initiate_docking(SSshuttle.getDock("pod_away")) //docks our shuttle as any pod would
- mode = SHUTTLE_ENDGAME
-
-/obj/docking_port/mobile/pod
- name = "escape pod"
- shuttle_id = "pod"
- launch_status = UNLAUNCHED
-
-/obj/docking_port/mobile/pod/request(obj/docking_port/stationary/S)
- var/obj/machinery/computer/shuttle/connected_computer = get_control_console()
- if(!istype(connected_computer, /obj/machinery/computer/shuttle/pod))
- return FALSE
- if(!(SSsecurity_level.get_current_level_as_number() >= SEC_LEVEL_RED) && !(connected_computer.obj_flags & EMAGGED))
- to_chat(usr, span_warning("Escape pods will only launch during \"Code Red\" security alert."))
- return FALSE
- if(launch_status == UNLAUNCHED)
- launch_status = EARLY_LAUNCHED
- return ..()
-
-/obj/docking_port/mobile/pod/cancel()
- return
-
-/obj/machinery/computer/shuttle/pod
- name = "pod control computer"
- locked = TRUE
- possible_destinations = "pod_asteroid"
- icon = 'icons/obj/machines/wallmounts.dmi'
- icon_state = "pod_off"
- circuit = /obj/item/circuitboard/computer/emergency_pod
- light_color = LIGHT_COLOR_BLUE
- density = FALSE
- icon_keyboard = null
- icon_screen = "pod_on"
-
-/obj/machinery/computer/shuttle/pod/Initialize(mapload)
- . = ..()
- RegisterSignal(SSsecurity_level, COMSIG_SECURITY_LEVEL_CHANGED, PROC_REF(check_lock))
-
-/obj/machinery/computer/shuttle/pod/emag_act(mob/user, obj/item/card/emag/emag_card)
- if(obj_flags & EMAGGED)
- return FALSE
- obj_flags |= EMAGGED
- locked = FALSE
- balloon_alert(user, "alert level checking disabled")
- icon_screen = "emagged_general"
- update_appearance()
- return TRUE
-
-/obj/machinery/computer/shuttle/pod/connect_to_shuttle(mapload, obj/docking_port/mobile/port, obj/docking_port/stationary/dock)
- . = ..()
- if(port)
- //Checks if the computer has already added the shuttle destination with the initial id
- //This has to be done because connect_to_shuttle is called again after its ID is updated
- //due to conflicting id names
- var/base_shuttle_destination = ";[initial(port.shuttle_id)]_lavaland"
- var/shuttle_destination = ";[port.shuttle_id]_lavaland"
-
- var/position = findtext(possible_destinations, base_shuttle_destination)
- if(position)
- if(base_shuttle_destination == shuttle_destination)
- return
- possible_destinations = splicetext(possible_destinations, position, position + length(base_shuttle_destination), shuttle_destination)
- return
-
- possible_destinations += shuttle_destination
-
-/**
- * Signal handler for checking if we should lock or unlock escape pods accordingly to a newly set security level
- *
- * Arguments:
- * * source The datum source of the signal
- * * new_level The new security level that is in effect
- */
-/obj/machinery/computer/shuttle/pod/proc/check_lock(datum/source, new_level)
- SIGNAL_HANDLER
-
- if(obj_flags & EMAGGED)
- return
- locked = (new_level < SEC_LEVEL_RED)
-
-/obj/docking_port/stationary/random
- name = "escape pod"
- shuttle_id = "pod"
- hidden = TRUE
- override_can_dock_checks = TRUE
- /// The area the pod tries to land at
- var/target_area = /area/lavaland/surface/outdoors
- /// Minimal distance from the map edge, setting this too low can result in shuttle landing on the edge and getting "sliced"
- var/edge_distance = 16
-
-/obj/docking_port/stationary/random/Initialize(mapload)
- . = ..()
- if(!mapload)
- return
-
- var/list/turfs = get_area_turfs(target_area)
- var/original_len = turfs.len
- while(turfs.len)
- var/turf/picked_turf = pick(turfs)
- if(picked_turf.x stationary_dock.dwidth)
+ return SHUTTLE_DWIDTH_TOO_LARGE
+
+ if(width-dwidth > stationary_dock.width-stationary_dock.dwidth)
+ return SHUTTLE_WIDTH_TOO_LARGE
+
+ if(dheight > stationary_dock.dheight)
+ return SHUTTLE_DHEIGHT_TOO_LARGE
+
+ if(height-dheight > stationary_dock.height-stationary_dock.dheight)
+ return SHUTTLE_HEIGHT_TOO_LARGE
+
+ //check the dock isn't occupied
+ var/currently_docked = stationary_dock.get_docked()
+ if(currently_docked)
+ // by someone other than us
+ if(currently_docked != src)
+ return SHUTTLE_SOMEONE_ELSE_DOCKED
+ else
+ // This isn't an error, per se, but we can't let the shuttle code
+ // attempt to move us where we currently are, it will get weird.
+ return SHUTTLE_ALREADY_DOCKED
+
+ return SHUTTLE_CAN_DOCK
+
+/obj/docking_port/mobile/proc/check_dock(obj/docking_port/stationary/S, silent = FALSE)
+ var/status = canDock(S)
+ if(status == SHUTTLE_CAN_DOCK)
+ return TRUE
+ else
+ if(status != SHUTTLE_ALREADY_DOCKED && !silent) // SHUTTLE_ALREADY_DOCKED is no cause for error
+ message_admins("Shuttle [src] cannot dock at [S], error: [status]")
+ // We're already docked there, don't need to do anything.
+ // Triggering shuttle movement code in place is weird
+ return FALSE
+
+/obj/docking_port/mobile/proc/transit_failure()
+ message_admins("Shuttle [src] repeatedly failed to create transit zone.")
+
+/**
+ * Calls the shuttle to the destination port, respecting its ignition and call timers
+ *
+ * Arguments:
+ * * destination_port - Stationary docking port to move the shuttle to
+ */
+/obj/docking_port/mobile/proc/request(obj/docking_port/stationary/destination_port, forced = FALSE) // NOVA EDIT ADDITION - Forced check
+ if(!check_dock(destination_port) && !forced) // NOVA EDIT CHANGE - ORIGINAL: if(!check_dock(destination_port))
+ testing("check_dock failed on request for [src]")
+ return
+ // NOVA EDIT START - Forced check
+ if(forced)
+ admin_forced = TRUE
+ // NOVA EDIT END
+
+ if(mode == SHUTTLE_IGNITING && destination == destination_port)
+ return
+
+ switch(mode)
+ if(SHUTTLE_CALL)
+ if(destination_port == destination)
+ if(timeLeft(1) < callTime * engine_coeff)
+ setTimer(callTime * engine_coeff)
+ else
+ destination = destination_port
+ setTimer(callTime * engine_coeff)
+ if(SHUTTLE_RECALL)
+ if(destination_port == destination)
+ setTimer(callTime * engine_coeff - timeLeft(1))
+ else
+ destination = destination_port
+ setTimer(callTime * engine_coeff)
+ mode = SHUTTLE_CALL
+ if(SHUTTLE_IDLE, SHUTTLE_IGNITING)
+ destination = destination_port
+ mode = SHUTTLE_IGNITING
+ // NOVA EDIT ADD START
+ bolt_all_doors()
+ play_engine_sound(src, TRUE)
+ // NOVA EDIT ADD END
+ setTimer(ignitionTime)
+
+//recall the shuttle to where it was previously
+/obj/docking_port/mobile/proc/cancel()
+ if(mode != SHUTTLE_CALL)
+ return
+
+ remove_ripples()
+
+ invertTimer()
+ mode = SHUTTLE_RECALL
+
+/obj/docking_port/mobile/proc/enterTransit()
+ if((SSshuttle.lockdown && is_station_level(z)) || !canMove()) //emp went off, no escape
+ mode = SHUTTLE_IDLE
+ return
+ previous = null
+ if(!destination)
+ // sent to transit with no destination -> unlimited timer
+ timer = INFINITY
+ var/obj/docking_port/stationary/S0 = get_docked()
+ var/obj/docking_port/stationary/S1 = assigned_transit
+ if(S1)
+ if(initiate_docking(S1) != DOCKING_SUCCESS)
+ WARNING("shuttle \"[shuttle_id]\" could not enter transit space. Docked at [S0 ? S0.shuttle_id : "null"]. Transit dock [S1 ? S1.shuttle_id : "null"].")
+ else if(S0)
+ if(S0.delete_after)
+ qdel(S0, TRUE)
+ else
+ previous = S0
+ else
+ WARNING("shuttle \"[shuttle_id]\" could not enter transit space. S0=[S0 ? S0.shuttle_id : "null"] S1=[S1 ? S1.shuttle_id : "null"]")
+
+
+/obj/docking_port/mobile/proc/jumpToNullSpace()
+ // Destroys the docking port and the shuttle contents.
+ // Not in a fancy way, it just ceases.
+ var/obj/docking_port/stationary/current_dock = get_docked()
+
+ var/underlying_area_type = SHUTTLE_DEFAULT_UNDERLYING_AREA
+ // If the shuttle is docked to a stationary port, restore its normal
+ // "empty" area and turf
+ if(current_dock?.area_type)
+ underlying_area_type = current_dock.area_type
+
+ var/list/old_turfs = return_ordered_turfs(x, y, z, dir)
+
+ var/area/underlying_area = GLOB.areas_by_type[underlying_area_type]
+ if(!underlying_area)
+ underlying_area = new underlying_area_type(null)
+
+ for(var/i in 1 to old_turfs.len)
+ var/turf/oldT = old_turfs[i]
+ if(!oldT || !istype(oldT.loc, area_type))
+ continue
+ oldT.change_area(oldT.loc, underlying_area)
+ oldT.empty(FALSE)
+
+ // Here we locate the bottommost shuttle boundary and remove all turfs above it
+ var/shuttle_tile_depth = oldT.depth_to_find_baseturf(/turf/baseturf_skipover/shuttle)
+ if (!isnull(shuttle_tile_depth))
+ oldT.ScrapeAway(shuttle_tile_depth)
+
+ qdel(src, force=TRUE)
+
+/**
+ * Ghosts and marks as escaped (for greentext purposes) all mobs, then deletes the shuttle.
+ * Used by the Shuttle Manipulator
+ */
+/obj/docking_port/mobile/proc/intoTheSunset()
+ // Loop over mobs
+ for(var/turf/turfs as anything in return_turfs())
+ for(var/mob/living/sunset_mobs in turfs.get_all_contents())
+ // If they have a mind and they're not in the brig, they escaped
+ if(sunset_mobs.mind && !istype(get_area(sunset_mobs), /area/shuttle/escape/brig))
+ sunset_mobs.mind.force_escaped = TRUE
+ // Ghostize them and put them in nullspace stasis (for stat & possession checks)
+ ADD_TRAIT(sunset_mobs, TRAIT_NO_TRANSFORM, REF(src))
+ sunset_mobs.ghostize(FALSE)
+ sunset_mobs.moveToNullspace()
+
+ // Now that mobs are stowed, delete the shuttle
+ jumpToNullSpace()
+
+/obj/docking_port/mobile/proc/create_ripples(obj/docking_port/stationary/S1, animate_time)
+ var/list/turfs = ripple_area(S1)
+ for(var/t in turfs)
+ ripples += new /obj/effect/abstract/ripple(t, animate_time)
+
+/obj/docking_port/mobile/proc/remove_ripples()
+ QDEL_LIST(ripples)
+
+/obj/docking_port/mobile/proc/ripple_area(obj/docking_port/stationary/S1)
+ var/list/L0 = return_ordered_turfs(x, y, z, dir)
+ var/list/L1 = return_ordered_turfs(S1.x, S1.y, S1.z, S1.dir)
+
+ var/list/ripple_turfs = list()
+ var/stop = min(L0.len, L1.len)
+ for(var/i in 1 to stop)
+ var/turf/T0 = L0[i]
+ var/turf/T1 = L1[i]
+ if(!istype(T0.loc, area_type) || istype(T0.loc, /area/shuttle/transit))
+ continue // not part of the shuttle
+ ripple_turfs += T1
+
+ return ripple_turfs
+
+/obj/docking_port/mobile/proc/check_poddoors()
+ for(var/obj/machinery/door/poddoor/shuttledock/pod as anything in SSmachines.get_machines_by_type_and_subtypes(/obj/machinery/door/poddoor/shuttledock))
+ pod.check()
+
+/obj/docking_port/mobile/proc/dock_id(id)
+ var/port = SSshuttle.getDock(id)
+ if(port)
+ . = initiate_docking(port)
+ else
+ . = null
+
+//used by shuttle subsystem to check timers
+/obj/docking_port/mobile/proc/check()
+ check_effects()
+ //process_events() if you were to add events to non-escape shuttles, uncomment this
+
+ if(mode == SHUTTLE_IGNITING)
+ check_transit_zone()
+
+ if(timeLeft(1) > 0)
+ return
+ // If we can't dock or we don't have a transit slot, wait for 20 ds,
+ // then try again
+ switch(mode)
+ if(SHUTTLE_CALL, SHUTTLE_PREARRIVAL)
+ if(prearrivalTime && mode != SHUTTLE_PREARRIVAL)
+ mode = SHUTTLE_PREARRIVAL
+ setTimer(prearrivalTime)
+ return
+ var/error = initiate_docking(destination, preferred_direction)
+ if(error && error & (DOCKING_NULL_DESTINATION | DOCKING_NULL_SOURCE))
+ var/msg = "A mobile dock in transit exited initiate_docking() with an error. This is most likely a mapping problem: Error: [error], ([src]) ([previous][ADMIN_JMP(previous)] -> [destination][ADMIN_JMP(destination)])"
+ WARNING(msg)
+ message_admins(msg)
+ mode = SHUTTLE_IDLE
+ return
+ else if(error)
+ setTimer(20)
+ return
+ if(rechargeTime)
+ mode = SHUTTLE_RECHARGING
+ unbolt_all_doors() // NOVA EDIT ADDITION
+ setTimer(rechargeTime)
+ return
+ if(SHUTTLE_RECALL)
+ if(initiate_docking(previous) != DOCKING_SUCCESS)
+ setTimer(20)
+ return
+ if(SHUTTLE_IGNITING)
+ if(check_transit_zone() != TRANSIT_READY)
+ setTimer(20)
+ return
+ else
+ mode = SHUTTLE_CALL
+ setTimer(callTime * engine_coeff)
+ enterTransit()
+ return
+
+ admin_forced = FALSE // NOVA EDIT ADDITION
+ unbolt_all_doors() // NOVA EDIT ADDITION
+ mode = SHUTTLE_IDLE
+ timer = 0
+ destination = null
+
+/obj/docking_port/mobile/proc/check_effects()
+ if(!ripples.len)
+ if((mode == SHUTTLE_CALL) || (mode == SHUTTLE_RECALL))
+ var/tl = timeLeft(1)
+ if(tl <= SHUTTLE_RIPPLE_TIME)
+ create_ripples(destination, tl)
+ play_engine_sound(src, FALSE) // NOVA EDIT ADDITION
+ play_engine_sound(destination, FALSE) // NOVA EDIT ADDITION
+
+ var/obj/docking_port/stationary/S0 = get_docked()
+ if(istype(S0, /obj/docking_port/stationary/transit) && timeLeft(1) <= PARALLAX_LOOP_TIME)
+ for(var/place in shuttle_areas)
+ var/area/shuttle/shuttle_area = place
+ if(shuttle_area.parallax_movedir)
+ parallax_slowdown()
+
+/obj/docking_port/mobile/proc/parallax_slowdown()
+ for(var/place in shuttle_areas)
+ var/area/shuttle/shuttle_area = place
+ shuttle_area.parallax_movedir = FALSE
+ if(assigned_transit?.assigned_area)
+ assigned_transit.assigned_area.parallax_movedir = FALSE
+ var/list/L0 = return_ordered_turfs(x, y, z, dir)
+ for (var/thing in L0)
+ var/turf/T = thing
+ if(!T || !istype(T.loc, area_type))
+ continue
+ for (var/atom/movable/movable as anything in T)
+ if (movable.client_mobs_in_contents)
+ movable.update_parallax_contents()
+
+/obj/docking_port/mobile/proc/check_transit_zone()
+ if(assigned_transit)
+ return TRANSIT_READY
+ else
+ SSshuttle.request_transit_dock(src)
+
+/obj/docking_port/mobile/proc/setTimer(wait)
+ timer = world.time + wait
+ last_timer_length = wait
+
+/obj/docking_port/mobile/proc/modTimer(multiple)
+ var/time_remaining = timer - world.time
+ if(time_remaining < 0 || !last_timer_length)
+ return
+ time_remaining *= multiple
+ last_timer_length *= multiple
+ setTimer(time_remaining)
+
+/obj/docking_port/mobile/proc/alert_coeff_change(new_coeff)
+ if(isnull(new_coeff))
+ return
+
+ var/time_multiplier = new_coeff / alert_coeff
+ var/time_remaining = timer - world.time
+ if(time_remaining < 0 || !last_timer_length)
+ return
+
+ time_remaining *= time_multiplier
+ last_timer_length *= time_multiplier
+ alert_coeff = new_coeff
+ setTimer(time_remaining)
+
+/obj/docking_port/mobile/proc/invertTimer()
+ if(!last_timer_length)
+ return
+ var/time_remaining = timer - world.time
+ if(time_remaining > 0)
+ var/time_passed = last_timer_length - time_remaining
+ setTimer(time_passed)
+
+//returns timeLeft
+/obj/docking_port/mobile/proc/timeLeft(divisor)
+ if(divisor <= 0)
+ divisor = 10
+
+ var/ds_remaining
+ if(!timer)
+ ds_remaining = callTime * engine_coeff
+ else
+ ds_remaining = max(0, timer - world.time)
+
+ . = round(ds_remaining / divisor, 1)
+
+// returns 3-letter mode string, used by status screens and mob status panel
+/obj/docking_port/mobile/proc/getModeStr()
+ switch(mode)
+ if(SHUTTLE_IGNITING)
+ return "IGN"
+ if(SHUTTLE_RECALL)
+ return "RCL"
+ if(SHUTTLE_CALL)
+ return "ETA"
+ if(SHUTTLE_DOCKED)
+ return "ETD"
+ if(SHUTTLE_ESCAPE)
+ return "ESC"
+ if(SHUTTLE_STRANDED)
+ return "ERR"
+ if(SHUTTLE_RECHARGING)
+ return "RCH"
+ if(SHUTTLE_PREARRIVAL)
+ return "LDN"
+ if(SHUTTLE_DISABLED)
+ return "DIS"
+ return ""
+
+// returns 5-letter timer string, used by status screens and mob status panel
+/obj/docking_port/mobile/proc/getTimerStr()
+ if(mode == SHUTTLE_STRANDED || mode == SHUTTLE_DISABLED)
+ return "--:--"
+
+ var/timeleft = timeLeft()
+ if(timeleft > 1 HOURS)
+ return "--:--"
+ else if(timeleft > 0)
+ return "[add_leading(num2text((timeleft / 60) % 60), 2, "0")]:[add_leading(num2text(timeleft % 60), 2, "0")]"
+ else
+ return "00:00"
+
+/**
+ * Gets shuttle location status in a form of string for tgui interfaces
+ */
+/obj/docking_port/mobile/proc/get_status_text_tgui()
+ var/obj/docking_port/stationary/dockedAt = get_docked()
+ var/docked_at = dockedAt?.name || "Unknown"
+ if(!istype(dockedAt, /obj/docking_port/stationary/transit))
+ return docked_at
+ if(timeLeft() > 1 HOURS)
+ return "Hyperspace"
+ else
+ var/obj/docking_port/stationary/dst = (mode == SHUTTLE_RECALL) ? previous : destination
+ return "In transit to [dst?.name || "unknown location"]"
+
+/obj/docking_port/mobile/proc/getStatusText()
+ var/obj/docking_port/stationary/dockedAt = get_docked()
+ var/docked_at = dockedAt?.name || "unknown"
+ if(istype(dockedAt, /obj/docking_port/stationary/transit))
+ if (timeLeft() > 1 HOURS)
+ return "hyperspace"
+ else
+ var/obj/docking_port/stationary/dst
+ if(mode == SHUTTLE_RECALL)
+ dst = previous
+ else
+ dst = destination
+ . = "transit towards [dst?.name || "unknown location"] ([getTimerStr()])"
+ else if(mode == SHUTTLE_RECHARGING)
+ return "[docked_at], recharging [getTimerStr()]"
+ else
+ return docked_at
+
+/obj/docking_port/mobile/proc/getDbgStatusText()
+ var/obj/docking_port/stationary/dockedAt = get_docked()
+ . = (dockedAt?.name) ? dockedAt.name : "unknown"
+ if(istype(dockedAt, /obj/docking_port/stationary/transit))
+ var/obj/docking_port/stationary/dst
+ if(mode == SHUTTLE_RECALL)
+ dst = previous
+ else
+ dst = destination
+ if(dst)
+ . = "(transit to) [dst.name || dst.shuttle_id]"
+ else
+ . = "(transit to) nowhere"
+ else if(dockedAt)
+ . = dockedAt.name || dockedAt.shuttle_id
+ else
+ . = "unknown"
+
+
+// attempts to locate /obj/machinery/computer/shuttle with matching ID inside the shuttle
+/obj/docking_port/mobile/proc/get_control_console()
+ for(var/area/shuttle/shuttle_area as anything in shuttle_areas)
+ var/obj/machinery/computer/shuttle/shuttle_computer = locate(/obj/machinery/computer/shuttle) in shuttle_area
+ if(!shuttle_computer)
+ continue
+ if(shuttle_computer.shuttleId == shuttle_id)
+ return shuttle_computer
+ return null
+
+/obj/docking_port/mobile/proc/hyperspace_sound(phase, list/areas)
+ var/selected_sound
+ switch(phase)
+ if(HYPERSPACE_WARMUP)
+ selected_sound = "hyperspace_begin"
+ if(HYPERSPACE_LAUNCH)
+ selected_sound = "hyperspace_progress"
+ if(HYPERSPACE_END)
+ selected_sound = "hyperspace_end"
+ else
+ CRASH("Invalid hyperspace sound phase: [phase]")
+ // This previously was played from each door at max volume, and was one of the worst things I had ever seen.
+ // Now it's instead played from the nearest engine if close, or the first engine in the list if far since it doesn't really matter.
+ // Or a door if for some reason the shuttle has no engine, fuck oh hi daniel fuck it
+ var/range = (engine_coeff * max(width, height))
+ var/long_range = range * 2.5
+ var/atom/distant_source
+
+ if(engine_list.len)
+ distant_source = engine_list[1]
+ else
+ for(var/our_area in areas)
+ distant_source = locate(/obj/machinery/door) in our_area
+ if(distant_source)
+ break
+
+ if(!distant_source)
+ return
+ for(var/mob/zlevel_mobs as anything in SSmobs.clients_by_zlevel[z])
+ var/dist_far = get_dist(zlevel_mobs, distant_source)
+ if(dist_far <= long_range && dist_far > range)
+ zlevel_mobs.playsound_local(distant_source, "sound/runtime/hyperspace/[selected_sound]_distance.ogg", 100)
+ else if(dist_far <= range)
+ var/source
+ if(!engine_list.len)
+ source = distant_source
+ else
+ var/closest_dist = 10000
+ for(var/obj/machinery/power/shuttle_engine/engines as anything in engine_list)
+ var/dist_near = get_dist(zlevel_mobs, engines)
+ if(dist_near < closest_dist)
+ source = engines
+ closest_dist = dist_near
+ zlevel_mobs.playsound_local(source, "sound/runtime/hyperspace/[selected_sound].ogg", 100)
+
+// Losing all initial engines should get you 2
+// Adding another set of engines at 0.5 time
+/obj/docking_port/mobile/proc/alter_engines(mod)
+ if(!mod)
+ return
+ var/old_coeff = engine_coeff
+ engine_coeff = get_engine_coeff(mod)
+ current_engine_power = max(0, current_engine_power + mod)
+ if(in_flight())
+ var/delta_coeff = engine_coeff / old_coeff
+ modTimer(delta_coeff)
+
+// Double initial engines to get to 0.5 minimum
+// Lose all initial engines to get to 2
+//For 0 engine shuttles like BYOS 5 engines to get to doublespeed
+/obj/docking_port/mobile/proc/get_engine_coeff(engine_mod)
+ var/new_value = max(0, current_engine_power + engine_mod)
+ if(new_value == initial_engine_power)
+ return 1
+ if(new_value > initial_engine_power)
+ var/delta = new_value - initial_engine_power
+ var/change_per_engine = (1 - ENGINE_COEFF_MIN) / ENGINE_DEFAULT_MAXSPEED_ENGINES // 5 by default
+ if(initial_engine_power > 0)
+ change_per_engine = (1 - ENGINE_COEFF_MIN) / initial_engine_power // or however many it had
+ return clamp(1 - delta * change_per_engine,ENGINE_COEFF_MIN, ENGINE_COEFF_MAX)
+ if(new_value < initial_engine_power)
+ var/delta = initial_engine_power - new_value
+ var/change_per_engine = 1 //doesn't really matter should not be happening for 0 engine shuttles
+ if(initial_engine_power > 0)
+ change_per_engine = (ENGINE_COEFF_MAX - 1) / initial_engine_power //just linear drop to max delay
+ return clamp(1 + delta * change_per_engine, ENGINE_COEFF_MIN, ENGINE_COEFF_MAX)
+
+
+/obj/docking_port/mobile/proc/in_flight()
+ switch(mode)
+ if(SHUTTLE_CALL,SHUTTLE_RECALL,SHUTTLE_PREARRIVAL)
+ return TRUE
+ if(SHUTTLE_IDLE,SHUTTLE_IGNITING)
+ return FALSE
+ return FALSE // hmm
+
+/obj/docking_port/mobile/emergency/in_flight()
+ switch(mode)
+ if(SHUTTLE_ESCAPE)
+ return TRUE
+ if(SHUTTLE_STRANDED,SHUTTLE_ENDGAME)
+ return FALSE
+ return ..()
+
+//Called when emergency shuttle leaves the station
+/obj/docking_port/mobile/proc/on_emergency_launch()
+ if(launch_status == UNLAUNCHED) //Pods will not launch from the mine/planet, and other ships won't launch unless we tell them to.
+ launch_status = ENDGAME_LAUNCHED
+ enterTransit()
+
+///Let people know shits about to go down
+/obj/docking_port/mobile/proc/announce_shuttle_events()
+ for(var/datum/shuttle_event/event as anything in event_list)
+ notify_ghosts("The [name] has selected: [event.name]")
+
+/obj/docking_port/mobile/emergency/on_emergency_launch()
+ return
+
+//Called when emergency shuttle docks at centcom
+/obj/docking_port/mobile/proc/on_emergency_dock()
+ // Mapping a new docking point for each ship mappers could potentially want docking with centcom would take up lots of space,
+ // just let them keep flying off "into the sunset" for their greentext.
+ if(launch_status == ENDGAME_LAUNCHED)
+ launch_status = ENDGAME_TRANSIT
+
+/obj/docking_port/mobile/pod/on_emergency_dock()
+ if(launch_status == ENDGAME_LAUNCHED)
+ initiate_docking(SSshuttle.getDock("[shuttle_id]_away")) //Escape pods dock at centcom
+ mode = SHUTTLE_ENDGAME
+
+/obj/docking_port/mobile/emergency/on_emergency_dock()
+ return
+
+///Process all the shuttle events for every shuttle tick we get
+/obj/docking_port/mobile/proc/process_events()
+ var/list/removees
+ for(var/datum/shuttle_event/event as anything in event_list)
+ if(event.event_process() == SHUTTLE_EVENT_CLEAR) //if we return SHUTTLE_EVENT_CLEAR, we clean them up
+ LAZYADD(removees, event)
+ for(var/item in removees)
+ event_list.Remove(item)
+
+/// Give a typepath of a shuttle event to add to the shuttle. If added during endgame transit, will insta start the event
+/obj/docking_port/mobile/proc/add_shuttle_event(typepath)
+ var/datum/shuttle_event/event = new typepath (src)
+ event_list.Add(event)
+ if(launch_status == ENDGAME_LAUNCHED)
+ event.start_up_event(0)
+ return event
diff --git a/code/modules/shuttle/docking.dm b/code/modules/shuttle/mobile_port/shuttle_move.dm
similarity index 98%
rename from code/modules/shuttle/docking.dm
rename to code/modules/shuttle/mobile_port/shuttle_move.dm
index 1100fb0584d2..cb8ed3b1f796 100644
--- a/code/modules/shuttle/docking.dm
+++ b/code/modules/shuttle/mobile_port/shuttle_move.dm
@@ -1,4 +1,5 @@
-/// This is the main proc. It instantly moves our mobile port to stationary port `new_dock`.
+/// This is the main proc. Despite what the name suggests,
+/// it instantly moves our mobile port to stationary port `new_dock`.
/obj/docking_port/mobile/proc/initiate_docking(obj/docking_port/stationary/new_dock, movement_direction, force=FALSE)
// Crashing this ship with NO SURVIVORS
diff --git a/code/modules/shuttle/on_move.dm b/code/modules/shuttle/mobile_port/shuttle_move_callbacks.dm
similarity index 99%
rename from code/modules/shuttle/on_move.dm
rename to code/modules/shuttle/mobile_port/shuttle_move_callbacks.dm
index 23bfa86868e8..aafab24c1986 100644
--- a/code/modules/shuttle/on_move.dm
+++ b/code/modules/shuttle/mobile_port/shuttle_move_callbacks.dm
@@ -247,11 +247,6 @@ All ShuttleMove procs go here
. = ..()
recharging_turf = get_step(loc, dir)
-/obj/machinery/atmospherics/afterShuttleMove(turf/oldT, list/movement_force, shuttle_dir, shuttle_preferred_direction, move_dir, rotation)
- . = ..()
- if(pipe_vision_img)
- pipe_vision_img.loc = loc
-
/obj/machinery/computer/auxiliary_base/afterShuttleMove(turf/oldT, list/movement_force, shuttle_dir, shuttle_preferred_direction, move_dir, rotation)
. = ..()
if(is_mining_level(z)) //Avoids double logging and landing on other Z-levels due to badminnery
@@ -259,6 +254,9 @@ All ShuttleMove procs go here
/obj/machinery/atmospherics/afterShuttleMove(turf/oldT, list/movement_force, shuttle_dir, shuttle_preferred_direction, move_dir, rotation)
. = ..()
+ if(pipe_vision_img)
+ pipe_vision_img.loc = loc
+
var/missing_nodes = FALSE
for(var/i in 1 to device_type)
if(nodes[i])
diff --git a/code/modules/shuttle/shuttle_rotate.dm b/code/modules/shuttle/mobile_port/shuttle_rotate_callbacks.dm
similarity index 94%
rename from code/modules/shuttle/shuttle_rotate.dm
rename to code/modules/shuttle/mobile_port/shuttle_rotate_callbacks.dm
index 15af6db6a4f6..7afd43a0d354 100644
--- a/code/modules/shuttle/shuttle_rotate.dm
+++ b/code/modules/shuttle/mobile_port/shuttle_rotate_callbacks.dm
@@ -82,7 +82,11 @@ If ever any of these procs are useful for non-shuttles, rename it to proc/rotate
/obj/machinery/atmospherics/shuttleRotate(rotation, params)
var/list/real_node_connect = get_node_connects()
for(var/i in 1 to device_type)
- real_node_connect[i] = angle2dir(rotation+dir2angle(real_node_connect[i]))
+ var/node_dir = real_node_connect[i]
+ if(isnull(node_dir))
+ continue
+
+ real_node_connect[i] = turn(node_dir, -rotation)
. = ..()
set_init_directions()
@@ -90,7 +94,11 @@ If ever any of these procs are useful for non-shuttles, rename it to proc/rotate
var/list/nodes_copy = nodes.Copy()
for(var/i in 1 to device_type)
- var/new_pos = supposed_node_connect.Find(real_node_connect[i])
+ var/node_dir = real_node_connect[i]
+ if(isnull(node_dir))
+ continue
+
+ var/new_pos = supposed_node_connect.Find(node_dir)
nodes[new_pos] = nodes_copy[i]
//prevents shuttles attempting to rotate this since it messes up sprites
diff --git a/code/modules/shuttle/arrivals.dm b/code/modules/shuttle/mobile_port/variants/arrivals.dm
similarity index 100%
rename from code/modules/shuttle/arrivals.dm
rename to code/modules/shuttle/mobile_port/variants/arrivals.dm
diff --git a/code/modules/shuttle/assault_pod.dm b/code/modules/shuttle/mobile_port/variants/assault_pod.dm
similarity index 100%
rename from code/modules/shuttle/assault_pod.dm
rename to code/modules/shuttle/mobile_port/variants/assault_pod.dm
diff --git a/code/modules/shuttle/battlecruiser_starfury.dm b/code/modules/shuttle/mobile_port/variants/battlecruiser_starfury.dm
similarity index 100%
rename from code/modules/shuttle/battlecruiser_starfury.dm
rename to code/modules/shuttle/mobile_port/variants/battlecruiser_starfury.dm
diff --git a/code/modules/shuttle/elevator.dm b/code/modules/shuttle/mobile_port/variants/elevator.dm
similarity index 100%
rename from code/modules/shuttle/elevator.dm
rename to code/modules/shuttle/mobile_port/variants/elevator.dm
diff --git a/code/modules/shuttle/mobile_port/variants/emergency/emergency.dm b/code/modules/shuttle/mobile_port/variants/emergency/emergency.dm
new file mode 100644
index 000000000000..8cfb12d0a6ad
--- /dev/null
+++ b/code/modules/shuttle/mobile_port/variants/emergency/emergency.dm
@@ -0,0 +1,318 @@
+/obj/docking_port/mobile/emergency
+ name = "emergency shuttle"
+ shuttle_id = "emergency"
+ dir = EAST
+ port_direction = WEST
+ var/sound_played = 0 //If the launch sound has been sent to all players on the shuttle itself
+ var/hijack_status = HIJACK_NOT_BEGUN
+
+/obj/docking_port/mobile/emergency/Initialize(mapload)
+ . = ..()
+
+ setup_shuttle_events()
+
+/obj/docking_port/mobile/emergency/canDock(obj/docking_port/stationary/S)
+ return SHUTTLE_CAN_DOCK //If the emergency shuttle can't move, the whole game breaks, so it will force itself to land even if it has to crush a few departments in the process
+
+/obj/docking_port/mobile/emergency/register()
+ . = ..()
+ SSshuttle.emergency = src
+
+/obj/docking_port/mobile/emergency/Destroy(force)
+ if(force)
+ // This'll make the shuttle subsystem use the backup shuttle.
+ if(src == SSshuttle.emergency)
+ // If we're the selected emergency shuttle
+ SSshuttle.emergencyDeregister()
+
+ . = ..()
+
+/obj/docking_port/mobile/emergency/request(obj/docking_port/stationary/S, area/signal_origin, reason, red_alert, set_coefficient=null, silent = FALSE) // NOVA EDIT CHANGE - AUTOTRANSFER - adds silent arg
+ if(!isnum(set_coefficient))
+ set_coefficient = SSsecurity_level.current_security_level.shuttle_call_time_mod
+ alert_coeff = set_coefficient
+ var/call_time = SSshuttle.emergency_call_time * alert_coeff * engine_coeff
+ switch(mode)
+ // The shuttle can not normally be called while "recalling", so
+ // if this proc is called, it's via admin fiat
+ if(SHUTTLE_RECALL, SHUTTLE_IDLE, SHUTTLE_CALL)
+ mode = SHUTTLE_CALL
+ setTimer(call_time)
+ else
+ return
+
+ SSshuttle.emergencyCallAmount++
+
+ if(prob(70))
+ SSshuttle.emergency_last_call_loc = signal_origin
+ else
+ SSshuttle.emergency_last_call_loc = null
+
+ // NOVA EDIT ADDITION START
+ if(silent)
+ return
+ // NOVA EDIT ADDITION END
+ priority_announce(
+ text = "The emergency shuttle has been called. [red_alert ? "Red Alert state confirmed: Dispatching priority shuttle. " : "" ]It will arrive in [(timeLeft(60 SECONDS))] minutes.[reason][SSshuttle.emergency_last_call_loc ? "\n\nCall signal traced. Results can be viewed on any communications console." : "" ][SSshuttle.admin_emergency_no_recall ? "\n\nWarning: Shuttle recall subroutines disabled; Recall not possible." : ""]",
+ title = "Emergency Shuttle Dispatched",
+ sound = ANNOUNCER_SHUTTLECALLED,
+ sender_override = "Emergency Shuttle Uplink Alert",
+ color_override = "orange",
+ )
+
+/obj/docking_port/mobile/emergency/cancel(area/signalOrigin)
+ if(mode != SHUTTLE_CALL)
+ return
+ if(SSshuttle.emergency_no_recall)
+ return
+
+ invertTimer()
+ mode = SHUTTLE_RECALL
+
+ if(prob(70))
+ SSshuttle.emergency_last_call_loc = signalOrigin
+ else
+ SSshuttle.emergency_last_call_loc = null
+ priority_announce(
+ text = "The emergency shuttle has been recalled.[SSshuttle.emergency_last_call_loc ? " Recall signal traced. Results can be viewed on any communications console." : "" ]",
+ title = "Emergency Shuttle Recalled",
+ sound = ANNOUNCER_SHUTTLERECALLED,
+ sender_override = "Emergency Shuttle Uplink Alert",
+ color_override = "orange",
+ )
+
+ SSticker.emergency_reason = null
+
+/**
+ * Proc that handles checking if the emergency shuttle was successfully hijacked via being the only people present on the shuttle for the elimination hijack or highlander objective
+ *
+ * Checks for all mobs on the shuttle, checks their status, and checks if they're
+ * borgs or simple animals. Depending on the args, certain mobs may be ignored,
+ * and the presence of other antags may or may not invalidate a hijack.
+ * Args:
+ * filter_by_human, default TRUE, tells the proc that only humans should block a hijack. Borgs and animals are ignored and will not block if this is TRUE.
+ * solo_hijack, default FALSE, tells the proc to fail with multiple hijackers, such as for Highlander mode.
+ */
+/obj/docking_port/mobile/emergency/proc/elimination_hijack(filter_by_human = TRUE, solo_hijack = FALSE)
+ var/has_people = FALSE
+ var/hijacker_count = 0
+ for(var/mob/living/player in GLOB.player_list)
+ if(player.mind)
+ if(player.stat != DEAD)
+ if(issilicon(player) && filter_by_human) //Borgs are technically dead anyways
+ continue
+ if(isanimal_or_basicmob(player) && filter_by_human) //animals don't count
+ continue
+ if(isbrain(player)) //also technically dead
+ continue
+ if(shuttle_areas[get_area(player)])
+ has_people = TRUE
+ var/location = get_area(player.mind.current)
+ //Non-antag present. Can't hijack.
+ if(!(player.mind.has_antag_datum(/datum/antagonist)) && !istype(location, /area/shuttle/escape/brig))
+ return FALSE
+ //Antag present, doesn't stop but let's see if we actually want to hijack
+ var/prevent = FALSE
+ for(var/datum/antagonist/A in player.mind.antag_datums)
+ if(A.can_elimination_hijack == ELIMINATION_ENABLED)
+ hijacker_count += 1
+ prevent = FALSE
+ break //If we have both prevent and hijacker antags assume we want to hijack.
+ else if(A.can_elimination_hijack == ELIMINATION_PREVENT)
+ prevent = TRUE
+ if(prevent)
+ return FALSE
+
+ //has people AND either there's only one hijacker or there's any but solo_hijack is disabled
+ return has_people && ((hijacker_count == 1) || (hijacker_count && !solo_hijack))
+
+/obj/docking_port/mobile/emergency/proc/is_hijacked()
+ return hijack_status == HIJACK_COMPLETED
+
+/obj/docking_port/mobile/emergency/proc/ShuttleDBStuff()
+ set waitfor = FALSE
+ if(!SSdbcore.Connect())
+ return
+ var/datum/db_query/query_round_shuttle_name = SSdbcore.NewQuery({"
+ UPDATE [format_table_name("round")] SET shuttle_name = :name WHERE id = :round_id
+ "}, list("name" = name, "round_id" = GLOB.round_id))
+ query_round_shuttle_name.Execute()
+ qdel(query_round_shuttle_name)
+
+/obj/docking_port/mobile/emergency/check()
+ if(!timer)
+ return
+ var/time_left = timeLeft(1)
+
+ // The emergency shuttle doesn't work like others so this
+ // ripple check is slightly different
+ if(!ripples.len && (time_left <= SHUTTLE_RIPPLE_TIME) && ((mode == SHUTTLE_CALL) || (mode == SHUTTLE_ESCAPE)))
+ var/destination
+ if(mode == SHUTTLE_CALL)
+ destination = SSshuttle.getDock("emergency_home")
+ else if(mode == SHUTTLE_ESCAPE)
+ destination = SSshuttle.getDock("emergency_away")
+ create_ripples(destination)
+
+ switch(mode)
+ if(SHUTTLE_RECALL)
+ if(time_left <= 0)
+ mode = SHUTTLE_IDLE
+ timer = 0
+ if(SHUTTLE_CALL)
+ if(time_left <= 0)
+ //move emergency shuttle to station
+ if(initiate_docking(SSshuttle.getDock("emergency_home")) != DOCKING_SUCCESS)
+ setTimer(20)
+ return
+ mode = SHUTTLE_DOCKED
+ setTimer(SSshuttle.emergency_dock_time)
+ send2adminchat("Server", "The Emergency Shuttle has docked with the station.")
+ priority_announce(
+ text = "[SSshuttle.emergency] has docked with the station. You have [DisplayTimeText(SSshuttle.emergency_dock_time)] to board the emergency shuttle.",
+ title = "Emergency Shuttle Arrival",
+ sound = ANNOUNCER_SHUTTLEDOCK,
+ sender_override = "Emergency Shuttle Uplink Alert",
+ color_override = "orange",
+ )
+ ShuttleDBStuff()
+ addtimer(CALLBACK(src, PROC_REF(announce_shuttle_events)), 20 SECONDS)
+
+
+ if(SHUTTLE_DOCKED)
+ if(time_left <= ENGINE_START_TIME)
+ mode = SHUTTLE_IGNITING
+ SSshuttle.checkHostileEnvironment()
+ if(mode == SHUTTLE_STRANDED)
+ return
+ for(var/A in SSshuttle.mobile_docking_ports)
+ var/obj/docking_port/mobile/M = A
+ if(M.launch_status == UNLAUNCHED) //Pods will not launch from the mine/planet, and other ships won't launch unless we tell them to.
+ M.check_transit_zone()
+
+ if(SHUTTLE_IGNITING)
+ var/success = TRUE
+ SSshuttle.checkHostileEnvironment()
+ if(mode == SHUTTLE_STRANDED)
+ return
+
+ success &= (check_transit_zone() == TRANSIT_READY)
+ for(var/A in SSshuttle.mobile_docking_ports)
+ var/obj/docking_port/mobile/M = A
+ if(M.launch_status == UNLAUNCHED)
+ success &= (M.check_transit_zone() == TRANSIT_READY)
+ if(!success)
+ setTimer(ENGINE_START_TIME)
+
+ if(time_left <= 50 && !sound_played) //4 seconds left:REV UP THOSE ENGINES BOYS. - should sync up with the launch
+ sound_played = 1 //Only rev them up once.
+ var/list/areas = list()
+ for(var/area/shuttle/escape/E in GLOB.areas)
+ areas += E
+ hyperspace_sound(HYPERSPACE_WARMUP, areas)
+
+ if(time_left <= 0 && !SSshuttle.emergency_no_escape)
+ //move each escape pod (or applicable spaceship) to its corresponding transit dock
+ for(var/A in SSshuttle.mobile_docking_ports)
+ var/obj/docking_port/mobile/M = A
+ M.on_emergency_launch()
+
+ //now move the actual emergency shuttle to its transit dock
+ var/list/areas = list()
+ for(var/area/shuttle/escape/E in GLOB.areas)
+ areas += E
+ hyperspace_sound(HYPERSPACE_LAUNCH, areas)
+ enterTransit()
+
+ //Tell the events we're starting, so they can time their spawns or do some other stuff
+ for(var/datum/shuttle_event/event as anything in event_list)
+ event.start_up_event(SSshuttle.emergency_escape_time * engine_coeff)
+
+ mode = SHUTTLE_ESCAPE
+ launch_status = ENDGAME_LAUNCHED
+ bolt_all_doors() // NOVA EDIT ADDITION
+ setTimer(SSshuttle.emergency_escape_time * engine_coeff)
+ priority_announce(
+ text = "The emergency shuttle has left the station. Estimate [timeLeft(60 SECONDS)] minutes until the shuttle docks at [command_name()].",
+ title = "Emergency Shuttle Departure",
+ sender_override = "Emergency Shuttle Uplink Alert",
+ color_override = "orange",
+ )
+ INVOKE_ASYNC(SSticker, TYPE_PROC_REF(/datum/controller/subsystem/ticker, poll_hearts))
+ INVOKE_ASYNC(SSvote, TYPE_PROC_REF(/datum/controller/subsystem/vote, initiate_vote), /datum/vote/map_vote, vote_initiator_name = "Map Rotation", forced = TRUE)
+
+ if(!is_reserved_level(z))
+ CRASH("Emergency shuttle did not move to transit z-level!")
+
+ if(SHUTTLE_STRANDED, SHUTTLE_DISABLED)
+ SSshuttle.checkHostileEnvironment()
+
+
+ if(SHUTTLE_ESCAPE)
+ if(sound_played && time_left <= HYPERSPACE_END_TIME)
+ var/list/areas = list()
+ for(var/area/shuttle/escape/E in GLOB.areas)
+ areas += E
+ hyperspace_sound(HYPERSPACE_END, areas)
+ if(time_left <= PARALLAX_LOOP_TIME)
+ var/area_parallax = FALSE
+ for(var/place in shuttle_areas)
+ var/area/shuttle/shuttle_area = place
+ if(shuttle_area.parallax_movedir)
+ area_parallax = TRUE
+ break
+ if(area_parallax)
+ parallax_slowdown()
+ for(var/A in SSshuttle.mobile_docking_ports)
+ var/obj/docking_port/mobile/M = A
+ if(M.launch_status == ENDGAME_LAUNCHED)
+ if(istype(M, /obj/docking_port/mobile/pod))
+ M.parallax_slowdown()
+
+ process_events()
+
+ if(time_left <= 0)
+ //move each escape pod to its corresponding escape dock
+ for(var/obj/docking_port/mobile/port as anything in SSshuttle.mobile_docking_ports)
+ port.on_emergency_dock()
+
+ // now move the actual emergency shuttle to centcom
+ // unless the shuttle is "hijacked"
+ var/destination_dock = "emergency_away"
+ if(is_hijacked() || elimination_hijack())
+ // just double check
+ SSmapping.lazy_load_template(LAZY_TEMPLATE_KEY_NUKIEBASE)
+ destination_dock = "emergency_syndicate"
+ minor_announce("Corruption detected in \
+ shuttle navigation protocols. Please contact your \
+ supervisor.", "SYSTEM ERROR:", sound_override = 'sound/announcer/announcement/announce_syndi.ogg')
+
+ dock_id(destination_dock)
+ unbolt_all_doors() // NOVA EDIT ADDITION
+ INVOKE_ASYNC(GLOBAL_PROC, GLOBAL_PROC_REF(process_eorg_bans)) // NOVA EDIT ADDITION
+ mode = SHUTTLE_ENDGAME
+ timer = 0
+
+/obj/docking_port/mobile/emergency/transit_failure()
+ ..()
+ message_admins("Moving emergency shuttle directly to centcom dock to prevent deadlock.")
+
+ mode = SHUTTLE_ESCAPE
+ launch_status = ENDGAME_LAUNCHED
+ setTimer(SSshuttle.emergency_escape_time)
+ priority_announce(
+ text = "The emergency shuttle is preparing for direct jump. Estimate [timeLeft(60 SECONDS)] minutes until the shuttle docks at [command_name()].",
+ title = "Emergency Shuttle Transit Failure",
+ sender_override = "Emergency Shuttle Uplink Alert",
+ color_override = "orange",
+ )
+
+///Generate a list of events to run during the departure
+/obj/docking_port/mobile/emergency/proc/setup_shuttle_events()
+ var/list/names = list()
+ for(var/datum/shuttle_event/event as anything in subtypesof(/datum/shuttle_event))
+ if(prob(initial(event.event_probability)))
+ add_shuttle_event(event)
+ names += initial(event.name)
+ if(LAZYLEN(names))
+ log_game("[capitalize(name)] has selected the following shuttle events: [english_list(names)].")
diff --git a/code/modules/shuttle/mobile_port/variants/emergency/emergency_console.dm b/code/modules/shuttle/mobile_port/variants/emergency/emergency_console.dm
new file mode 100644
index 000000000000..b46bfff27430
--- /dev/null
+++ b/code/modules/shuttle/mobile_port/variants/emergency/emergency_console.dm
@@ -0,0 +1,316 @@
+#define ENGINES_STARTED (SSshuttle.emergency.mode == SHUTTLE_IGNITING)
+#define IS_DOCKED (SSshuttle.emergency.mode == SHUTTLE_DOCKED || (ENGINES_STARTED))
+#define SHUTTLE_CONSOLE_ACTION_DELAY (5 SECONDS)
+#define TIME_LEFT (SSshuttle.emergency.timeLeft())
+
+/obj/machinery/computer/emergency_shuttle
+ name = "emergency shuttle console"
+ desc = "For shuttle control."
+ icon_screen = "shuttle"
+ icon_keyboard = "tech_key"
+ resistance_flags = INDESTRUCTIBLE
+ var/auth_need = 3
+ var/list/authorized = list()
+ var/list/acted_recently = list()
+ var/hijack_last_stage_increase = 0 SECONDS
+ var/hijack_stage_time = 5 SECONDS
+ var/hijack_stage_cooldown = 5 SECONDS
+ var/hijack_flight_time_increase = 30 SECONDS
+ var/hijack_completion_flight_time_set = 10 SECONDS //How long in deciseconds to set shuttle's timer after hijack is done.
+ var/hijack_hacking = FALSE
+ var/hijack_announce = TRUE
+
+/obj/machinery/computer/emergency_shuttle/Destroy()
+ // Our fake IDs that the emag generated are just there for colour
+ // They're not supposed to be accessible
+
+ for(var/obj/item/card/id/ID in src)
+ qdel(ID)
+ if(authorized?.len)
+ authorized.Cut()
+ authorized = null
+
+ . = ..()
+
+/obj/machinery/computer/emergency_shuttle/examine(mob/user)
+ . = ..()
+ if(hijack_announce)
+ . += span_danger("Security systems present on console. Any unauthorized tampering will result in an emergency announcement.")
+ if(user?.mind?.get_hijack_speed())
+ . += span_danger("Alt click on this to attempt to hijack the shuttle. This will take multiple tries (current: stage [SSshuttle.emergency.hijack_status]/[HIJACK_COMPLETED]).")
+ . += span_notice("It will take you [(hijack_stage_time * user.mind.get_hijack_speed()) / 10] seconds to reprogram a stage of the shuttle's navigational firmware, and the console will undergo automated timed lockout for [hijack_stage_cooldown/10] seconds after each stage.")
+ if(hijack_announce)
+ . += span_warning("It is probably best to fortify your position as to be uninterrupted during the attempt, given the automatic announcements..")
+
+/obj/machinery/computer/emergency_shuttle/attackby(obj/item/I, mob/user,params)
+ if(isidcard(I))
+ say("Please equip your ID card into your ID slot to authenticate.")
+ . = ..()
+
+/obj/machinery/computer/emergency_shuttle/ui_state(mob/user)
+ return GLOB.human_adjacent_state
+
+/obj/machinery/computer/emergency_shuttle/ui_interact(mob/user, datum/tgui/ui)
+ . = ..()
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "EmergencyShuttleConsole", name)
+ ui.open()
+
+/obj/machinery/computer/emergency_shuttle/ui_data(user)
+ var/list/data = list()
+
+ data["timer_str"] = SSshuttle.emergency.getTimerStr()
+ data["engines_started"] = ENGINES_STARTED
+ data["authorizations_remaining"] = max((auth_need - authorized.len), 0)
+ var/list/A = list()
+ for(var/i in authorized)
+ var/obj/item/card/id/ID = i
+ var/name = ID.registered_name
+ var/job = ID.assignment
+
+ if(obj_flags & EMAGGED)
+ name = Gibberish(name)
+ job = Gibberish(job)
+ A += list(list("name" = name, "job" = job))
+ data["authorizations"] = A
+
+ data["enabled"] = (IS_DOCKED && !ENGINES_STARTED) && !(user in acted_recently)
+ data["emagged"] = obj_flags & EMAGGED ? 1 : 0
+ return data
+
+/obj/machinery/computer/emergency_shuttle/ui_act(action, params, datum/tgui/ui)
+ . = ..()
+ if(.)
+ return
+ if(ENGINES_STARTED) // past the point of no return
+ return
+ if(!IS_DOCKED) // shuttle computer only has uses when onstation
+ return
+ if(SSshuttle.emergency.mode == SHUTTLE_DISABLED) // admins have disabled the shuttle.
+ return
+ if(!isliving(usr))
+ return
+
+ var/area/my_area = get_area(src)
+ if(!istype(my_area, /area/shuttle/escape))
+ say("Error - Network connectivity: Console has lost connection to the shuttle.")
+ return
+
+ var/mob/living/user = usr
+ . = FALSE
+
+ var/obj/item/card/id/ID = user.get_idcard(TRUE)
+
+ if(!ID)
+ to_chat(user, span_warning("You don't have an ID."))
+ return
+
+ if(!(ACCESS_COMMAND in ID.access))
+ to_chat(user, span_warning("The access level of your card is not high enough."))
+ return
+
+ if (user in acted_recently)
+ return
+
+ var/old_len = authorized.len
+ addtimer(CALLBACK(src, PROC_REF(clear_recent_action), user), SHUTTLE_CONSOLE_ACTION_DELAY)
+
+ switch(action)
+ if("authorize")
+ . = authorize(user)
+
+ if("repeal")
+ authorized -= ID
+
+ if("abort")
+ if(authorized.len)
+ // Abort. The action for when heads are fighting over whether
+ // to launch early.
+ authorized.Cut()
+ . = TRUE
+
+ if((old_len != authorized.len) && !ENGINES_STARTED)
+ var/alert = (authorized.len > old_len)
+ var/repeal = (authorized.len < old_len)
+ var/remaining = max(0, auth_need - authorized.len)
+ if(authorized.len && remaining)
+ minor_announce("[remaining] authorizations needed until shuttle is launched early", null, alert)
+ if(repeal)
+ minor_announce("Early launch authorization revoked, [remaining] authorizations needed")
+
+ acted_recently += user
+ SStgui.update_user_uis(user, src)
+
+/obj/machinery/computer/emergency_shuttle/proc/authorize(mob/living/user, source)
+ var/obj/item/card/id/ID = user.get_idcard(TRUE)
+
+ if(ID in authorized)
+ return FALSE
+ for(var/i in authorized)
+ var/obj/item/card/id/other = i
+ if(other.registered_name == ID.registered_name)
+ return FALSE // No using IDs with the same name
+
+ authorized += ID
+
+ message_admins("[ADMIN_LOOKUPFLW(user)] has authorized early shuttle launch")
+ log_shuttle("[key_name(user)] has authorized early shuttle launch in [COORD(src)]")
+ // Now check if we're on our way
+ . = TRUE
+ process(SSMACHINES_DT)
+
+/obj/machinery/computer/emergency_shuttle/proc/clear_recent_action(mob/user)
+ acted_recently -= user
+ if (!QDELETED(user))
+ SStgui.update_user_uis(user, src)
+
+/obj/machinery/computer/emergency_shuttle/process()
+ // Launch check is in process in case auth_need changes for some reason
+ // probably external.
+ . = FALSE
+ if(!SSshuttle.emergency)
+ return
+
+ if(SSshuttle.emergency.mode == SHUTTLE_STRANDED)
+ authorized.Cut()
+ obj_flags &= ~(EMAGGED)
+
+ if(ENGINES_STARTED || (!IS_DOCKED))
+ return .
+
+ // Check to see if we've reached criteria for early launch
+ if((authorized.len >= auth_need) || (obj_flags & EMAGGED))
+ // shuttle timers use 1/10th seconds internally
+ SSshuttle.emergency.setTimer(ENGINE_START_TIME)
+ var/system_error = obj_flags & EMAGGED ? "SYSTEM ERROR:" : null
+ minor_announce("The emergency shuttle will launch in \
+ [TIME_LEFT] seconds", system_error, alert=TRUE)
+ . = TRUE
+
+/obj/machinery/computer/emergency_shuttle/proc/increase_hijack_stage()
+ var/obj/docking_port/mobile/emergency/shuttle = SSshuttle.emergency
+ // Begin loading this early, prevents a delay when the shuttle goes to land
+ INVOKE_ASYNC(SSmapping, TYPE_PROC_REF(/datum/controller/subsystem/mapping, lazy_load_template), LAZY_TEMPLATE_KEY_NUKIEBASE)
+
+ shuttle.hijack_status++
+ if(hijack_announce)
+ announce_hijack_stage()
+ hijack_last_stage_increase = world.time
+ say("Navigational protocol error! Rebooting systems.")
+ if(shuttle.mode == SHUTTLE_ESCAPE)
+ if(shuttle.hijack_status == HIJACK_COMPLETED)
+ shuttle.setTimer(hijack_completion_flight_time_set)
+ else
+ shuttle.setTimer(shuttle.timeLeft(1) + hijack_flight_time_increase) //give the guy more time to hijack if it's already in flight.
+ return shuttle.hijack_status
+
+/obj/machinery/computer/emergency_shuttle/click_alt(mob/living/user)
+ if(!isliving(user))
+ return NONE
+ attempt_hijack_stage(user)
+ return CLICK_ACTION_SUCCESS
+
+/obj/machinery/computer/emergency_shuttle/proc/attempt_hijack_stage(mob/living/user)
+ if(!user.CanReach(src))
+ return
+ if(HAS_TRAIT(user, TRAIT_HANDS_BLOCKED))
+ to_chat(user, span_warning("You need your hands free before you can manipulate [src]."))
+ return
+ var/area/my_area = get_area(src)
+ if(!istype(my_area, /area/shuttle/escape))
+ say("Error - Network connectivity: Console has lost connection to the shuttle.")
+ return
+ if(!user?.mind?.get_hijack_speed())
+ to_chat(user, span_warning("You manage to open a user-mode shell on [src], and hundreds of lines of debugging output fly through your vision. It is probably best to leave this alone."))
+ return
+ if(!EMERGENCY_AT_LEAST_DOCKED) // prevent advancing hijack stages on BYOS shuttles until the shuttle has "docked"
+ to_chat(user, span_warning("The flight plans for the shuttle haven't been loaded yet, you can't hack this right now."))
+ return
+ if(hijack_hacking == TRUE)
+ return
+ if(SSshuttle.emergency.hijack_status >= HIJACK_COMPLETED)
+ to_chat(user, span_warning("The emergency shuttle is already loaded with a corrupt navigational payload. What more do you want from it?"))
+ return
+ if(hijack_last_stage_increase >= world.time - hijack_stage_cooldown)
+ say("Error - Catastrophic software error detected. Input is currently on timeout.")
+ return
+ hijack_hacking = TRUE
+ to_chat(user, span_boldwarning("You [SSshuttle.emergency.hijack_status == HIJACK_NOT_BEGUN? "begin" : "continue"] to override [src]'s navigational protocols."))
+ say("Software override initiated.")
+ var/turf/console_hijack_turf = get_turf(src)
+ message_admins("[src] is being overriden for hijack by [ADMIN_LOOKUPFLW(user)] in [ADMIN_VERBOSEJMP(console_hijack_turf)]")
+ user.log_message("is hijacking [src].", LOG_GAME)
+ . = FALSE
+ if(do_after(user, hijack_stage_time * (1 / user.mind.get_hijack_speed()), target = src))
+ increase_hijack_stage()
+ console_hijack_turf = get_turf(src)
+ message_admins("[ADMIN_LOOKUPFLW(user)] has hijacked [src] in [ADMIN_VERBOSEJMP(console_hijack_turf)]. Hijack stage increased to stage [SSshuttle.emergency.hijack_status] out of [HIJACK_COMPLETED].")
+ user.log_message("has hijacked [src]. Hijack stage increased to stage [SSshuttle.emergency.hijack_status] out of [HIJACK_COMPLETED].", LOG_GAME)
+ . = TRUE
+ to_chat(user, span_notice("You reprogram some of [src]'s programming, putting it on timeout for [hijack_stage_cooldown/10] seconds."))
+ visible_message(
+ span_warning("[user.name] appears to be tampering with [src]."),
+ blind_message = span_hear("You hear someone tapping computer keys."),
+ vision_distance = COMBAT_MESSAGE_RANGE,
+ ignored_mobs = user
+ )
+ hijack_hacking = FALSE
+
+/obj/machinery/computer/emergency_shuttle/proc/announce_hijack_stage()
+ var/msg
+ switch(SSshuttle.emergency.hijack_status)
+ if(HIJACK_NOT_BEGUN)
+ return
+ if(HIJACK_STAGE_1)
+ msg = "AUTHENTICATING - FAIL. AUTHENTICATING - FAIL. AUTHENTICATING - FAI###### Welcome, technician JOHN DOE."
+ if(HIJACK_STAGE_2)
+ msg = "Warning: Navigational route fails \"IS_AUTHORIZED\". Please try againNN[scramble_message_replace_chars("againagainagainagainagain", 70)]."
+ if(HIJACK_STAGE_3)
+ msg = "CRC mismatch at ~h~ in calculated route buffer. Full reset initiated of FTL_NAVIGATION_SERVICES. Memory decrypted for automatic repair."
+ if(HIJACK_STAGE_4)
+ msg = "~ACS_directive module_load(cyberdyne.exploit.nanotrasen.shuttlenav)... NT key mismatch. Confirm load? Y...###Reboot complete. $SET transponder_state = 0; System link initiated with connected engines..."
+ if(HIJACK_COMPLETED)
+ msg = "SYSTEM OVERRIDE - Resetting course to \[[scramble_message_replace_chars("###########", 100)]\] \
+ ([scramble_message_replace_chars("#######", 100)]/[scramble_message_replace_chars("#######", 100)]/[scramble_message_replace_chars("#######", 100)]) \
+ {AUTH - ROOT (uid: 0)}.\
+ [SSshuttle.emergency.mode == SHUTTLE_ESCAPE ? "Diverting from existing route - Bluespace exit in \
+ [hijack_completion_flight_time_set >= INFINITY ? "[scramble_message_replace_chars("\[ERROR\]")]" : hijack_completion_flight_time_set/10] seconds." : ""]"
+ minor_announce(scramble_message_replace_chars(msg, replaceprob = 10), "Emergency Shuttle", TRUE)
+
+/obj/machinery/computer/emergency_shuttle/emag_act(mob/user, obj/item/card/emag/emag_card)
+ // How did you even get on the shuttle before it go to the station?
+ if(!IS_DOCKED)
+ return FALSE
+
+ if((obj_flags & EMAGGED) || ENGINES_STARTED) //SYSTEM ERROR: THE SHUTTLE WILL LA-SYSTEM ERROR: THE SHUTTLE WILL LA-SYSTEM ERROR: THE SHUTTLE WILL LAUNCH IN 10 SECONDS
+ balloon_alert(user, "shuttle already about to launch!")
+ return FALSE
+
+ var/time = TIME_LEFT
+ if (user)
+ message_admins("[ADMIN_LOOKUPFLW(user)] has emagged the emergency shuttle [time] seconds before launch.")
+ log_shuttle("[key_name(user)] has emagged the emergency shuttle in [COORD(src)] [time] seconds before launch.")
+ else
+ message_admins("The emergency shuttle was emagged [time] seconds before launch, with no emagger.")
+ log_shuttle("The emergency shuttle was emagged in [COORD(src)] [time] seconds before launch, with no emagger.")
+
+ obj_flags |= EMAGGED
+ SSshuttle.emergency.movement_force = list("KNOCKDOWN" = 60, "THROW" = 20)//YOUR PUNY SEATBELTS can SAVE YOU NOW, MORTAL
+ for(var/i in 1 to 10)
+ // the shuttle system doesn't know who these people are, but they
+ // must be important, surely
+ var/obj/item/card/id/ID = new(src)
+ var/datum/job/J = pick(SSjob.joinable_occupations)
+ ID.registered_name = generate_random_name_species_based(species_type = /datum/species/human)
+ ID.assignment = J.title
+
+ authorized += ID
+
+ process(SSMACHINES_DT)
+ return TRUE
+
+#undef TIME_LEFT
+#undef ENGINES_STARTED
+#undef IS_DOCKED
+#undef SHUTTLE_CONSOLE_ACTION_DELAY
diff --git a/code/modules/shuttle/mobile_port/variants/emergency/emergency_types.dm b/code/modules/shuttle/mobile_port/variants/emergency/emergency_types.dm
new file mode 100644
index 000000000000..6030999698b0
--- /dev/null
+++ b/code/modules/shuttle/mobile_port/variants/emergency/emergency_types.dm
@@ -0,0 +1,39 @@
+/// Fallback shuttle
+/obj/docking_port/mobile/emergency/backup
+ name = "backup shuttle"
+ shuttle_id = "backup"
+ dir = EAST
+
+/obj/docking_port/mobile/emergency/backup/Initialize(mapload)
+ // We want to be a valid emergency shuttle
+ // but not be the main one, keep whatever's set
+ // valid.
+ // backup shuttle ignores `timid` because THERE SHOULD BE NO TOUCHING IT
+ var/current_emergency = SSshuttle.emergency
+ . = ..()
+ SSshuttle.emergency = current_emergency
+ SSshuttle.backup_shuttle = src
+
+/obj/docking_port/mobile/emergency/backup/Destroy(force)
+ if(SSshuttle.backup_shuttle == src)
+ SSshuttle.backup_shuttle = null
+ return ..()
+
+/// Monastery shuttle
+/obj/docking_port/mobile/monastery
+ name = "monastery pod"
+ shuttle_id = "mining_common" //set so mining can call it down
+ launch_status = UNLAUNCHED //required for it to launch as a pod.
+
+/obj/docking_port/mobile/monastery/on_emergency_dock()
+ if(launch_status == ENDGAME_LAUNCHED)
+ initiate_docking(SSshuttle.getDock("pod_away")) //docks our shuttle as any pod would
+ mode = SHUTTLE_ENDGAME
+
+/// Build Your Own Shuttle (BYOS) kit
+/obj/docking_port/mobile/emergency/shuttle_build
+
+/obj/docking_port/mobile/emergency/shuttle_build/postregister()
+ . = ..()
+ initiate_docking(SSshuttle.getDock("emergency_home"))
+
diff --git a/code/modules/shuttle/mobile_port/variants/emergency/pods.dm b/code/modules/shuttle/mobile_port/variants/emergency/pods.dm
new file mode 100644
index 000000000000..1d8e1bae6bc0
--- /dev/null
+++ b/code/modules/shuttle/mobile_port/variants/emergency/pods.dm
@@ -0,0 +1,211 @@
+// THIS FILE CONTAINS: Pod mobile/stationary docking port, pod control console, pod storage and pod items
+
+/obj/docking_port/mobile/pod
+ name = "escape pod"
+ shuttle_id = "pod"
+ launch_status = UNLAUNCHED
+
+/obj/docking_port/mobile/pod/request(obj/docking_port/stationary/S)
+ var/obj/machinery/computer/shuttle/connected_computer = get_control_console()
+ if(!istype(connected_computer, /obj/machinery/computer/shuttle/pod))
+ return FALSE
+ if(!(SSsecurity_level.get_current_level_as_number() >= SEC_LEVEL_RED) && !(connected_computer.obj_flags & EMAGGED))
+ to_chat(usr, span_warning("Escape pods will only launch during \"Code Red\" security alert."))
+ return FALSE
+ if(launch_status == UNLAUNCHED)
+ launch_status = EARLY_LAUNCHED
+ return ..()
+
+/obj/docking_port/mobile/pod/cancel()
+ return
+
+/obj/machinery/computer/shuttle/pod
+ name = "pod control computer"
+ locked = TRUE
+ possible_destinations = "pod_asteroid"
+ icon = 'icons/obj/machines/wallmounts.dmi'
+ icon_state = "pod_off"
+ circuit = /obj/item/circuitboard/computer/emergency_pod
+ light_color = LIGHT_COLOR_BLUE
+ density = FALSE
+ icon_keyboard = null
+ icon_screen = "pod_on"
+
+/obj/machinery/computer/shuttle/pod/Initialize(mapload)
+ . = ..()
+ RegisterSignal(SSsecurity_level, COMSIG_SECURITY_LEVEL_CHANGED, PROC_REF(check_lock))
+
+/obj/machinery/computer/shuttle/pod/emag_act(mob/user, obj/item/card/emag/emag_card)
+ if(obj_flags & EMAGGED)
+ return FALSE
+ obj_flags |= EMAGGED
+ locked = FALSE
+ balloon_alert(user, "alert level checking disabled")
+ icon_screen = "emagged_general"
+ update_appearance()
+ return TRUE
+
+/obj/machinery/computer/shuttle/pod/connect_to_shuttle(mapload, obj/docking_port/mobile/port, obj/docking_port/stationary/dock)
+ . = ..()
+ if(port)
+ //Checks if the computer has already added the shuttle destination with the initial id
+ //This has to be done because connect_to_shuttle is called again after its ID is updated
+ //due to conflicting id names
+ var/base_shuttle_destination = ";[initial(port.shuttle_id)]_lavaland"
+ var/shuttle_destination = ";[port.shuttle_id]_lavaland"
+
+ var/position = findtext(possible_destinations, base_shuttle_destination)
+ if(position)
+ if(base_shuttle_destination == shuttle_destination)
+ return
+ possible_destinations = splicetext(possible_destinations, position, position + length(base_shuttle_destination), shuttle_destination)
+ return
+
+ possible_destinations += shuttle_destination
+
+/**
+ * Signal handler for checking if we should lock or unlock escape pods accordingly to a newly set security level
+ *
+ * Arguments:
+ * * source The datum source of the signal
+ * * new_level The new security level that is in effect
+ */
+/obj/machinery/computer/shuttle/pod/proc/check_lock(datum/source, new_level)
+ SIGNAL_HANDLER
+
+ if(obj_flags & EMAGGED)
+ return
+ locked = (new_level < SEC_LEVEL_RED)
+
+/obj/docking_port/stationary/random
+ name = "escape pod"
+ shuttle_id = "pod"
+ hidden = TRUE
+ override_can_dock_checks = TRUE
+ /// The area the pod tries to land at
+ var/target_area = /area/lavaland/surface/outdoors
+ /// Minimal distance from the map edge, setting this too low can result in shuttle landing on the edge and getting "sliced"
+ var/edge_distance = 16
+
+/obj/docking_port/stationary/random/Initialize(mapload)
+ . = ..()
+ if(!mapload)
+ return
+
+ var/list/turfs = get_area_turfs(target_area)
+ var/original_len = turfs.len
+ while(turfs.len)
+ var/turf/picked_turf = pick(turfs)
+ if(picked_turf.x stationary_dock.dwidth)
- return SHUTTLE_DWIDTH_TOO_LARGE
-
- if(width-dwidth > stationary_dock.width-stationary_dock.dwidth)
- return SHUTTLE_WIDTH_TOO_LARGE
-
- if(dheight > stationary_dock.dheight)
- return SHUTTLE_DHEIGHT_TOO_LARGE
-
- if(height-dheight > stationary_dock.height-stationary_dock.dheight)
- return SHUTTLE_HEIGHT_TOO_LARGE
-
- //check the dock isn't occupied
- var/currently_docked = stationary_dock.get_docked()
- if(currently_docked)
- // by someone other than us
- if(currently_docked != src)
- return SHUTTLE_SOMEONE_ELSE_DOCKED
- else
- // This isn't an error, per se, but we can't let the shuttle code
- // attempt to move us where we currently are, it will get weird.
- return SHUTTLE_ALREADY_DOCKED
-
- return SHUTTLE_CAN_DOCK
-
-/obj/docking_port/mobile/proc/check_dock(obj/docking_port/stationary/S, silent = FALSE)
- var/status = canDock(S)
- if(status == SHUTTLE_CAN_DOCK)
- return TRUE
- else
- if(status != SHUTTLE_ALREADY_DOCKED && !silent) // SHUTTLE_ALREADY_DOCKED is no cause for error
- message_admins("Shuttle [src] cannot dock at [S], error: [status]")
- // We're already docked there, don't need to do anything.
- // Triggering shuttle movement code in place is weird
- return FALSE
-
-/obj/docking_port/mobile/proc/transit_failure()
- message_admins("Shuttle [src] repeatedly failed to create transit zone.")
-
-/**
- * Calls the shuttle to the destination port, respecting its ignition and call timers
- *
- * Arguments:
- * * destination_port - Stationary docking port to move the shuttle to
- */
-/obj/docking_port/mobile/proc/request(obj/docking_port/stationary/destination_port, forced = FALSE) // NOVA EDIT ADDITION - Forced check
- if(!check_dock(destination_port) && !forced) // NOVA EDIT ADDITION - Forced check
- testing("check_dock failed on request for [src]")
- return
-
- // NOVA EDIT START - Forced check
- if(forced)
- admin_forced = TRUE
- // NOVA EDIT END
-
- if(mode == SHUTTLE_IGNITING && destination == destination_port)
- return
-
- switch(mode)
- if(SHUTTLE_CALL)
- if(destination_port == destination)
- if(timeLeft(1) < callTime * engine_coeff)
- setTimer(callTime * engine_coeff)
- else
- destination = destination_port
- setTimer(callTime * engine_coeff)
- if(SHUTTLE_RECALL)
- if(destination_port == destination)
- setTimer(callTime * engine_coeff - timeLeft(1))
- else
- destination = destination_port
- setTimer(callTime * engine_coeff)
- mode = SHUTTLE_CALL
- if(SHUTTLE_IDLE, SHUTTLE_IGNITING)
- destination = destination_port
- mode = SHUTTLE_IGNITING
- // NOVA EDIT ADD START
- bolt_all_doors()
- play_engine_sound(src, TRUE)
- // NOVA EDIT ADD END
- setTimer(ignitionTime)
-
-//recall the shuttle to where it was previously
-/obj/docking_port/mobile/proc/cancel()
- if(mode != SHUTTLE_CALL)
- return
-
- remove_ripples()
-
- invertTimer()
- mode = SHUTTLE_RECALL
-
-/obj/docking_port/mobile/proc/enterTransit()
- if((SSshuttle.lockdown && is_station_level(z)) || !canMove()) //emp went off, no escape
- mode = SHUTTLE_IDLE
- return
- previous = null
- if(!destination)
- // sent to transit with no destination -> unlimited timer
- timer = INFINITY
- var/obj/docking_port/stationary/S0 = get_docked()
- var/obj/docking_port/stationary/S1 = assigned_transit
- if(S1)
- if(initiate_docking(S1) != DOCKING_SUCCESS)
- WARNING("shuttle \"[shuttle_id]\" could not enter transit space. Docked at [S0 ? S0.shuttle_id : "null"]. Transit dock [S1 ? S1.shuttle_id : "null"].")
- else if(S0)
- if(S0.delete_after)
- qdel(S0, TRUE)
- else
- previous = S0
- else
- WARNING("shuttle \"[shuttle_id]\" could not enter transit space. S0=[S0 ? S0.shuttle_id : "null"] S1=[S1 ? S1.shuttle_id : "null"]")
-
-
-/obj/docking_port/mobile/proc/jumpToNullSpace()
- // Destroys the docking port and the shuttle contents.
- // Not in a fancy way, it just ceases.
- var/obj/docking_port/stationary/current_dock = get_docked()
-
- var/underlying_area_type = SHUTTLE_DEFAULT_UNDERLYING_AREA
- // If the shuttle is docked to a stationary port, restore its normal
- // "empty" area and turf
- if(current_dock?.area_type)
- underlying_area_type = current_dock.area_type
-
- var/list/old_turfs = return_ordered_turfs(x, y, z, dir)
-
- var/area/underlying_area = GLOB.areas_by_type[underlying_area_type]
- if(!underlying_area)
- underlying_area = new underlying_area_type(null)
-
- for(var/i in 1 to old_turfs.len)
- var/turf/oldT = old_turfs[i]
- if(!oldT || !istype(oldT.loc, area_type))
- continue
- oldT.change_area(oldT.loc, underlying_area)
- oldT.empty(FALSE)
-
- // Here we locate the bottommost shuttle boundary and remove all turfs above it
- var/shuttle_tile_depth = oldT.depth_to_find_baseturf(/turf/baseturf_skipover/shuttle)
- if (!isnull(shuttle_tile_depth))
- oldT.ScrapeAway(shuttle_tile_depth)
-
- qdel(src, force=TRUE)
-
-/**
- * Ghosts and marks as escaped (for greentext purposes) all mobs, then deletes the shuttle.
- * Used by the Shuttle Manipulator
- */
-/obj/docking_port/mobile/proc/intoTheSunset()
- // Loop over mobs
- for(var/turf/turfs as anything in return_turfs())
- for(var/mob/living/sunset_mobs in turfs.get_all_contents())
- // If they have a mind and they're not in the brig, they escaped
- if(sunset_mobs.mind && !istype(get_area(sunset_mobs), /area/shuttle/escape/brig))
- sunset_mobs.mind.force_escaped = TRUE
- // Ghostize them and put them in nullspace stasis (for stat & possession checks)
- ADD_TRAIT(sunset_mobs, TRAIT_NO_TRANSFORM, REF(src))
- sunset_mobs.ghostize(FALSE)
- sunset_mobs.moveToNullspace()
-
- // Now that mobs are stowed, delete the shuttle
- jumpToNullSpace()
-
-/obj/docking_port/mobile/proc/create_ripples(obj/docking_port/stationary/S1, animate_time)
- var/list/turfs = ripple_area(S1)
- for(var/t in turfs)
- ripples += new /obj/effect/abstract/ripple(t, animate_time)
-
-/obj/docking_port/mobile/proc/remove_ripples()
- QDEL_LIST(ripples)
-
-/obj/docking_port/mobile/proc/ripple_area(obj/docking_port/stationary/S1)
- var/list/L0 = return_ordered_turfs(x, y, z, dir)
- var/list/L1 = return_ordered_turfs(S1.x, S1.y, S1.z, S1.dir)
-
- var/list/ripple_turfs = list()
- var/stop = min(L0.len, L1.len)
- for(var/i in 1 to stop)
- var/turf/T0 = L0[i]
- var/turf/T1 = L1[i]
- if(!istype(T0.loc, area_type) || istype(T0.loc, /area/shuttle/transit))
- continue // not part of the shuttle
- ripple_turfs += T1
-
- return ripple_turfs
-
-/obj/docking_port/mobile/proc/check_poddoors()
- for(var/obj/machinery/door/poddoor/shuttledock/pod as anything in SSmachines.get_machines_by_type_and_subtypes(/obj/machinery/door/poddoor/shuttledock))
- pod.check()
-
-/obj/docking_port/mobile/proc/dock_id(id)
- var/port = SSshuttle.getDock(id)
- if(port)
- . = initiate_docking(port)
- else
- . = null
-
-//used by shuttle subsystem to check timers
-/obj/docking_port/mobile/proc/check()
- check_effects()
- //process_events() if you were to add events to non-escape shuttles, uncomment this
-
- if(mode == SHUTTLE_IGNITING)
- check_transit_zone()
-
- if(timeLeft(1) > 0)
- return
- // If we can't dock or we don't have a transit slot, wait for 20 ds,
- // then try again
- switch(mode)
- if(SHUTTLE_CALL, SHUTTLE_PREARRIVAL)
- if(prearrivalTime && mode != SHUTTLE_PREARRIVAL)
- mode = SHUTTLE_PREARRIVAL
- setTimer(prearrivalTime)
- return
- var/error = initiate_docking(destination, preferred_direction)
- if(error && error & (DOCKING_NULL_DESTINATION | DOCKING_NULL_SOURCE))
- var/msg = "A mobile dock in transit exited initiate_docking() with an error. This is most likely a mapping problem: Error: [error], ([src]) ([previous][ADMIN_JMP(previous)] -> [destination][ADMIN_JMP(destination)])"
- WARNING(msg)
- message_admins(msg)
- mode = SHUTTLE_IDLE
- return
- else if(error)
- setTimer(20)
- return
- if(rechargeTime)
- mode = SHUTTLE_RECHARGING
- unbolt_all_doors() //NOVA EDIT ADDITION
- setTimer(rechargeTime)
- return
- if(SHUTTLE_RECALL)
- if(initiate_docking(previous) != DOCKING_SUCCESS)
- setTimer(20)
- return
- if(SHUTTLE_IGNITING)
- if(check_transit_zone() != TRANSIT_READY)
- setTimer(20)
- return
- else
- mode = SHUTTLE_CALL
- setTimer(callTime * engine_coeff)
- enterTransit()
- return
-
- admin_forced = FALSE //NOVA EDIT ADDITION
- unbolt_all_doors() //NOVA EDIT ADDITION
- mode = SHUTTLE_IDLE
- timer = 0
- destination = null
-
-/obj/docking_port/mobile/proc/check_effects()
- if(!ripples.len)
- if((mode == SHUTTLE_CALL) || (mode == SHUTTLE_RECALL))
- var/tl = timeLeft(1)
- if(tl <= SHUTTLE_RIPPLE_TIME)
- create_ripples(destination, tl)
- play_engine_sound(src, FALSE) //NOVA EDIT ADDITION
- play_engine_sound(destination, FALSE) //NOVA EDIT ADDITION
-
- var/obj/docking_port/stationary/S0 = get_docked()
- if(istype(S0, /obj/docking_port/stationary/transit) && timeLeft(1) <= PARALLAX_LOOP_TIME)
- for(var/place in shuttle_areas)
- var/area/shuttle/shuttle_area = place
- if(shuttle_area.parallax_movedir)
- parallax_slowdown()
-
-/obj/docking_port/mobile/proc/parallax_slowdown()
- for(var/place in shuttle_areas)
- var/area/shuttle/shuttle_area = place
- shuttle_area.parallax_movedir = FALSE
- if(assigned_transit?.assigned_area)
- assigned_transit.assigned_area.parallax_movedir = FALSE
- var/list/L0 = return_ordered_turfs(x, y, z, dir)
- for (var/thing in L0)
- var/turf/T = thing
- if(!T || !istype(T.loc, area_type))
- continue
- for (var/atom/movable/movable as anything in T)
- if (movable.client_mobs_in_contents)
- movable.update_parallax_contents()
-
-/obj/docking_port/mobile/proc/check_transit_zone()
- if(assigned_transit)
- return TRANSIT_READY
- else
- SSshuttle.request_transit_dock(src)
-
-/obj/docking_port/mobile/proc/setTimer(wait)
- timer = world.time + wait
- last_timer_length = wait
-
-/obj/docking_port/mobile/proc/modTimer(multiple)
- var/time_remaining = timer - world.time
- if(time_remaining < 0 || !last_timer_length)
- return
- time_remaining *= multiple
- last_timer_length *= multiple
- setTimer(time_remaining)
-
-/obj/docking_port/mobile/proc/alert_coeff_change(new_coeff)
- if(isnull(new_coeff))
- return
-
- var/time_multiplier = new_coeff / alert_coeff
- var/time_remaining = timer - world.time
- if(time_remaining < 0 || !last_timer_length)
- return
-
- time_remaining *= time_multiplier
- last_timer_length *= time_multiplier
- alert_coeff = new_coeff
- setTimer(time_remaining)
-
-/obj/docking_port/mobile/proc/invertTimer()
- if(!last_timer_length)
- return
- var/time_remaining = timer - world.time
- if(time_remaining > 0)
- var/time_passed = last_timer_length - time_remaining
- setTimer(time_passed)
-
-//returns timeLeft
-/obj/docking_port/mobile/proc/timeLeft(divisor)
- if(divisor <= 0)
- divisor = 10
-
- var/ds_remaining
- if(!timer)
- ds_remaining = callTime * engine_coeff
- else
- ds_remaining = max(0, timer - world.time)
-
- . = round(ds_remaining / divisor, 1)
-
-// returns 3-letter mode string, used by status screens and mob status panel
-/obj/docking_port/mobile/proc/getModeStr()
- switch(mode)
- if(SHUTTLE_IGNITING)
- return "IGN"
- if(SHUTTLE_RECALL)
- return "RCL"
- if(SHUTTLE_CALL)
- return "ETA"
- if(SHUTTLE_DOCKED)
- return "ETD"
- if(SHUTTLE_ESCAPE)
- return "ESC"
- if(SHUTTLE_STRANDED)
- return "ERR"
- if(SHUTTLE_RECHARGING)
- return "RCH"
- if(SHUTTLE_PREARRIVAL)
- return "LDN"
- if(SHUTTLE_DISABLED)
- return "DIS"
- return ""
-
-// returns 5-letter timer string, used by status screens and mob status panel
-/obj/docking_port/mobile/proc/getTimerStr()
- if(mode == SHUTTLE_STRANDED || mode == SHUTTLE_DISABLED)
- return "--:--"
-
- var/timeleft = timeLeft()
- if(timeleft > 1 HOURS)
- return "--:--"
- else if(timeleft > 0)
- return "[add_leading(num2text((timeleft / 60) % 60), 2, "0")]:[add_leading(num2text(timeleft % 60), 2, "0")]"
- else
- return "00:00"
-
-/**
- * Gets shuttle location status in a form of string for tgui interfaces
- */
-/obj/docking_port/mobile/proc/get_status_text_tgui()
- var/obj/docking_port/stationary/dockedAt = get_docked()
- var/docked_at = dockedAt?.name || "Unknown"
- if(!istype(dockedAt, /obj/docking_port/stationary/transit))
- return docked_at
- if(timeLeft() > 1 HOURS)
- return "Hyperspace"
- else
- var/obj/docking_port/stationary/dst = (mode == SHUTTLE_RECALL) ? previous : destination
- return "In transit to [dst?.name || "unknown location"]"
-
-/obj/docking_port/mobile/proc/getStatusText()
- var/obj/docking_port/stationary/dockedAt = get_docked()
- var/docked_at = dockedAt?.name || "unknown"
- if(istype(dockedAt, /obj/docking_port/stationary/transit))
- if (timeLeft() > 1 HOURS)
- return "hyperspace"
- else
- var/obj/docking_port/stationary/dst
- if(mode == SHUTTLE_RECALL)
- dst = previous
- else
- dst = destination
- . = "transit towards [dst?.name || "unknown location"] ([getTimerStr()])"
- else if(mode == SHUTTLE_RECHARGING)
- return "[docked_at], recharging [getTimerStr()]"
- else
- return docked_at
-
-/obj/docking_port/mobile/proc/getDbgStatusText()
- var/obj/docking_port/stationary/dockedAt = get_docked()
- . = (dockedAt?.name) ? dockedAt.name : "unknown"
- if(istype(dockedAt, /obj/docking_port/stationary/transit))
- var/obj/docking_port/stationary/dst
- if(mode == SHUTTLE_RECALL)
- dst = previous
- else
- dst = destination
- if(dst)
- . = "(transit to) [dst.name || dst.shuttle_id]"
- else
- . = "(transit to) nowhere"
- else if(dockedAt)
- . = dockedAt.name || dockedAt.shuttle_id
- else
- . = "unknown"
-
-
-// attempts to locate /obj/machinery/computer/shuttle with matching ID inside the shuttle
-/obj/docking_port/mobile/proc/get_control_console()
- for(var/area/shuttle/shuttle_area as anything in shuttle_areas)
- var/obj/machinery/computer/shuttle/shuttle_computer = locate(/obj/machinery/computer/shuttle) in shuttle_area
- if(!shuttle_computer)
- continue
- if(shuttle_computer.shuttleId == shuttle_id)
- return shuttle_computer
- return null
-
-/obj/docking_port/mobile/proc/hyperspace_sound(phase, list/areas)
- var/selected_sound
- switch(phase)
- if(HYPERSPACE_WARMUP)
- selected_sound = "hyperspace_begin"
- if(HYPERSPACE_LAUNCH)
- selected_sound = "hyperspace_progress"
- if(HYPERSPACE_END)
- selected_sound = "hyperspace_end"
- else
- CRASH("Invalid hyperspace sound phase: [phase]")
- // This previously was played from each door at max volume, and was one of the worst things I had ever seen.
- // Now it's instead played from the nearest engine if close, or the first engine in the list if far since it doesn't really matter.
- // Or a door if for some reason the shuttle has no engine, fuck oh hi daniel fuck it
- var/range = (engine_coeff * max(width, height))
- var/long_range = range * 2.5
- var/atom/distant_source
-
- if(engine_list.len)
- distant_source = engine_list[1]
- else
- for(var/our_area in areas)
- distant_source = locate(/obj/machinery/door) in our_area
- if(distant_source)
- break
-
- if(!distant_source)
- return
- for(var/mob/zlevel_mobs as anything in SSmobs.clients_by_zlevel[z])
- var/dist_far = get_dist(zlevel_mobs, distant_source)
- if(dist_far <= long_range && dist_far > range)
- zlevel_mobs.playsound_local(distant_source, "sound/runtime/hyperspace/[selected_sound]_distance.ogg", 100)
- else if(dist_far <= range)
- var/source
- if(!engine_list.len)
- source = distant_source
- else
- var/closest_dist = 10000
- for(var/obj/machinery/power/shuttle_engine/engines as anything in engine_list)
- var/dist_near = get_dist(zlevel_mobs, engines)
- if(dist_near < closest_dist)
- source = engines
- closest_dist = dist_near
- zlevel_mobs.playsound_local(source, "sound/runtime/hyperspace/[selected_sound].ogg", 100)
-
-// Losing all initial engines should get you 2
-// Adding another set of engines at 0.5 time
-/obj/docking_port/mobile/proc/alter_engines(mod)
- if(!mod)
- return
- var/old_coeff = engine_coeff
- engine_coeff = get_engine_coeff(mod)
- current_engine_power = max(0, current_engine_power + mod)
- if(in_flight())
- var/delta_coeff = engine_coeff / old_coeff
- modTimer(delta_coeff)
-
-// Double initial engines to get to 0.5 minimum
-// Lose all initial engines to get to 2
-//For 0 engine shuttles like BYOS 5 engines to get to doublespeed
-/obj/docking_port/mobile/proc/get_engine_coeff(engine_mod)
- var/new_value = max(0, current_engine_power + engine_mod)
- if(new_value == initial_engine_power)
- return 1
- if(new_value > initial_engine_power)
- var/delta = new_value - initial_engine_power
- var/change_per_engine = (1 - ENGINE_COEFF_MIN) / ENGINE_DEFAULT_MAXSPEED_ENGINES // 5 by default
- if(initial_engine_power > 0)
- change_per_engine = (1 - ENGINE_COEFF_MIN) / initial_engine_power // or however many it had
- return clamp(1 - delta * change_per_engine,ENGINE_COEFF_MIN, ENGINE_COEFF_MAX)
- if(new_value < initial_engine_power)
- var/delta = initial_engine_power - new_value
- var/change_per_engine = 1 //doesn't really matter should not be happening for 0 engine shuttles
- if(initial_engine_power > 0)
- change_per_engine = (ENGINE_COEFF_MAX - 1) / initial_engine_power //just linear drop to max delay
- return clamp(1 + delta * change_per_engine, ENGINE_COEFF_MIN, ENGINE_COEFF_MAX)
-
-
-/obj/docking_port/mobile/proc/in_flight()
- switch(mode)
- if(SHUTTLE_CALL,SHUTTLE_RECALL,SHUTTLE_PREARRIVAL)
- return TRUE
- if(SHUTTLE_IDLE,SHUTTLE_IGNITING)
- return FALSE
- return FALSE // hmm
-
-/obj/docking_port/mobile/emergency/in_flight()
- switch(mode)
- if(SHUTTLE_ESCAPE)
- return TRUE
- if(SHUTTLE_STRANDED,SHUTTLE_ENDGAME)
- return FALSE
- return ..()
-
-//Called when emergency shuttle leaves the station
-/obj/docking_port/mobile/proc/on_emergency_launch()
- if(launch_status == UNLAUNCHED) //Pods will not launch from the mine/planet, and other ships won't launch unless we tell them to.
- launch_status = ENDGAME_LAUNCHED
- enterTransit()
-
-///Let people know shits about to go down
-/obj/docking_port/mobile/proc/announce_shuttle_events()
- for(var/datum/shuttle_event/event as anything in event_list)
- notify_ghosts("The [name] has selected: [event.name]")
-
-/obj/docking_port/mobile/emergency/on_emergency_launch()
- return
-
-//Called when emergency shuttle docks at centcom
-/obj/docking_port/mobile/proc/on_emergency_dock()
- // Mapping a new docking point for each ship mappers could potentially want docking with centcom would take up lots of space,
- // just let them keep flying off "into the sunset" for their greentext.
- if(launch_status == ENDGAME_LAUNCHED)
- launch_status = ENDGAME_TRANSIT
-
-/obj/docking_port/mobile/pod/on_emergency_dock()
- if(launch_status == ENDGAME_LAUNCHED)
- initiate_docking(SSshuttle.getDock("[shuttle_id]_away")) //Escape pods dock at centcom
- mode = SHUTTLE_ENDGAME
-
-/obj/docking_port/mobile/emergency/on_emergency_dock()
- return
-
-///Process all the shuttle events for every shuttle tick we get
-/obj/docking_port/mobile/proc/process_events()
- var/list/removees
- for(var/datum/shuttle_event/event as anything in event_list)
- if(event.event_process() == SHUTTLE_EVENT_CLEAR) //if we return SHUTTLE_EVENT_CLEAR, we clean them up
- LAZYADD(removees, event)
- for(var/item in removees)
- event_list.Remove(item)
-
-/// Give a typepath of a shuttle event to add to the shuttle. If added during endgame transit, will insta start the event
-/obj/docking_port/mobile/proc/add_shuttle_event(typepath)
- var/datum/shuttle_event/event = new typepath (src)
- event_list.Add(event)
- if(launch_status == ENDGAME_LAUNCHED)
- event.start_up_event(0)
- return event
-
-#ifdef TESTING
-#undef DOCKING_PORT_HIGHLIGHT
-#endif
diff --git a/code/modules/shuttle/monastery.dm b/code/modules/shuttle/shuttle_consoles/monastery.dm
similarity index 100%
rename from code/modules/shuttle/monastery.dm
rename to code/modules/shuttle/shuttle_consoles/monastery.dm
diff --git a/code/modules/shuttle/navigation_computer.dm b/code/modules/shuttle/shuttle_consoles/navigation_computer.dm
similarity index 85%
rename from code/modules/shuttle/navigation_computer.dm
rename to code/modules/shuttle/shuttle_consoles/navigation_computer.dm
index 7c588e06dc00..3e9bf0cbe3be 100644
--- a/code/modules/shuttle/navigation_computer.dm
+++ b/code/modules/shuttle/shuttle_consoles/navigation_computer.dm
@@ -34,7 +34,7 @@
. = ..()
actions += new /datum/action/innate/shuttledocker_rotate(src)
actions += new /datum/action/innate/shuttledocker_place(src)
-
+ AddElement(/datum/element/nav_computer_icon, 'icons/effects/nav_computer_indicators.dmi', "computer", FALSE)
set_init_ports()
if(connect_to_shuttle(mapload, SSshuttle.get_containing_shuttle(src)))
@@ -124,6 +124,7 @@
SET_PLANE(I, ABOVE_GAME_PLANE, shuttle_turf)
I.mouse_opacity = MOUSE_OPACITY_TRANSPARENT
the_eye.placement_images[I] = list(x_off, y_off)
+ gatherNavComputerIcons()
return TRUE
@@ -134,6 +135,7 @@
var/list/to_add = list()
to_add += the_eye.placement_images
to_add += the_eye.placed_images
+ to_add += the_eye.extra_images
if(!see_hidden)
to_add += SSshuttle.hidden_shuttle_turf_images
@@ -147,12 +149,55 @@
var/list/to_remove = list()
to_remove += the_eye.placement_images
to_remove += the_eye.placed_images
+ to_remove += the_eye.extra_images
if(!see_hidden)
to_remove += SSshuttle.hidden_shuttle_turf_images
user.client.images -= to_remove
user.client.view_size.resetToDefault()
+/obj/machinery/computer/camera_advanced/shuttle_docker/proc/shuttle_turf_from_coords(list/coords)
+ var/mob/eye/camera/remote/shuttle_docker/the_eye = eyeobj
+ var/shuttleDir = shuttle_port.dir
+ var/curDir = the_eye.dir
+ var/list/adjustedCoords = coords.Copy()
+
+ // Rotate coords so they match the current shuttle docking port's dir
+ if(turn(curDir, -90) == shuttleDir)
+ adjustedCoords[1] = coords[2] + y_offset
+ adjustedCoords[2] = -(coords[1] + x_offset)
+ else if(turn(curDir, 90) == shuttleDir)
+ adjustedCoords[1] = -(coords[2] + y_offset)
+ adjustedCoords[2] = coords[1] + x_offset
+ else if(turn(curDir, 180) == shuttleDir)
+ adjustedCoords[1] = -(coords[1] + x_offset)
+ adjustedCoords[2] = -(coords[2] + y_offset)
+ else
+ adjustedCoords[1] = coords[1] + x_offset
+ adjustedCoords[2] = coords[2] + y_offset
+
+ return locate(shuttle_port.x + adjustedCoords[1], shuttle_port.y + adjustedCoords[2], shuttle_port.z)
+
+/obj/machinery/computer/camera_advanced/shuttle_docker/proc/gatherNavComputerIcons()
+ var/mob/eye/camera/remote/shuttle_docker/the_eye = eyeobj
+ var/list/placement_image_cache = the_eye.placement_images
+ var/list/extra_image_cache = the_eye.extra_images
+ for(var/i in 1 to placement_image_cache.len)
+ var/image/placement_image = placement_image_cache[i]
+ var/list/coords = placement_image_cache[placement_image]
+ var/turf/shuttle_turf = shuttle_turf_from_coords(coords)
+ var/list/images_to_add = list()
+ for(var/atom/atom as anything in shuttle_turf)
+ SEND_SIGNAL(atom, COMSIG_SHUTTLE_NAV_COMPUTER_IMAGE_REQUESTED, images_to_add)
+ for(var/i2 in 1 to images_to_add.len)
+ var/image/extra_image = images_to_add[i2]
+ extra_image.dir = turn(extra_image.dir, dir2angle(the_eye.dir) - dir2angle(shuttle_port.dir))
+ extra_image.loc = locate(the_eye.x + coords[1], the_eye.y + coords[2], the_eye.z)
+ extra_image.layer = ABOVE_NORMAL_TURF_LAYER
+ SET_PLANE(extra_image, ABOVE_GAME_PLANE, the_eye)
+ extra_image.mouse_opacity = MOUSE_OPACITY_TRANSPARENT
+ extra_image_cache[extra_image] = coords.Copy()
+
/obj/machinery/computer/camera_advanced/shuttle_docker/proc/placeLandingSpot()
if(designating_target_loc || !current_user)
return
@@ -226,7 +271,7 @@
/obj/machinery/computer/camera_advanced/shuttle_docker/proc/rotateLandingSpot()
var/mob/eye/camera/remote/shuttle_docker/the_eye = eyeobj
- var/list/image_cache = the_eye.placement_images
+ var/list/image_cache = the_eye.placement_images + the_eye.extra_images
the_eye.setDir(turn(the_eye.dir, -90))
for(var/i in 1 to image_cache.len)
var/image/pic = image_cache[i]
@@ -235,6 +280,7 @@
coords[1] = coords[2]
coords[2] = -Tmp
pic.loc = locate(the_eye.x + coords[1], the_eye.y + coords[2], the_eye.z)
+ pic.dir = turn(pic.dir, -90)
var/Tmp = x_offset
x_offset = y_offset
y_offset = -Tmp
@@ -267,6 +313,12 @@
else
I.icon_state = "red"
. = SHUTTLE_DOCKER_BLOCKED
+ var/list/extra_image_cache = the_eye.extra_images
+ for(var/i in 1 to extra_image_cache.len)
+ var/image/image = extra_image_cache[i]
+ var/list/coords = extra_image_cache[image]
+ var/turf/turf = locate(eyeturf.x + coords[1], eyeturf.y + coords[2], eyeturf.z)
+ image.loc = turf
/obj/machinery/computer/camera_advanced/shuttle_docker/proc/checkLandingTurf(turf/T, list/overlappers)
// Too close to the map edge is never allowed
@@ -322,6 +374,7 @@
use_visibility = FALSE
var/list/image/placement_images = list()
var/list/image/placed_images = list()
+ var/list/image/extra_images = list()
/mob/eye/camera/remote/shuttle_docker/setLoc(turf/destination, force_update = FALSE)
. = ..()
diff --git a/code/modules/shuttle/computer.dm b/code/modules/shuttle/shuttle_consoles/shuttle_console.dm
similarity index 96%
rename from code/modules/shuttle/computer.dm
rename to code/modules/shuttle/shuttle_consoles/shuttle_console.dm
index 0de3396aeaab..d06874f3e669 100644
--- a/code/modules/shuttle/computer.dm
+++ b/code/modules/shuttle/shuttle_consoles/shuttle_console.dm
@@ -33,6 +33,7 @@
/obj/machinery/computer/shuttle/Initialize(mapload)
. = ..()
+ AddElement(/datum/element/nav_computer_icon, 'icons/effects/nav_computer_indicators.dmi', "computer", FALSE)
connect_to_shuttle(mapload, SSshuttle.get_containing_shuttle(src))
/obj/machinery/computer/shuttle/ui_interact(mob/user, datum/tgui/ui)
@@ -226,7 +227,7 @@
return
COOLDOWN_START(src, request_cooldown, 1 MINUTES)
to_chat(usr, span_notice("Your request has been received by CentCom."))
- to_chat(GLOB.admins, "SHUTTLE: [ADMIN_LOOKUPFLW(usr)] (Move Shuttle)(Lock/Unlock Shuttle) is requesting to move or unlock the shuttle.")
+ to_chat(GLOB.admins, "SHUTTLE: [ADMIN_LOOKUPFLW(usr)] (Move Shuttle)(Lock/Unlock Shuttle) is requesting to move or unlock the shuttle.")
return TRUE
/obj/machinery/computer/shuttle/emag_act(mob/user, obj/item/card/emag/emag_card)
diff --git a/code/modules/shuttle/syndicate.dm b/code/modules/shuttle/shuttle_consoles/syndicate.dm
similarity index 100%
rename from code/modules/shuttle/syndicate.dm
rename to code/modules/shuttle/shuttle_consoles/syndicate.dm
diff --git a/code/modules/shuttle/white_ship.dm b/code/modules/shuttle/shuttle_consoles/white_ship.dm
similarity index 100%
rename from code/modules/shuttle/white_ship.dm
rename to code/modules/shuttle/shuttle_consoles/white_ship.dm
diff --git a/code/modules/shuttle/stationary_port/port_types.dm b/code/modules/shuttle/stationary_port/port_types.dm
new file mode 100644
index 000000000000..047856566c2d
--- /dev/null
+++ b/code/modules/shuttle/stationary_port/port_types.dm
@@ -0,0 +1,100 @@
+/// Subtype for escape pod ports so that we can give them trait behaviour
+/obj/docking_port/stationary/escape_pod
+ name = "escape pod loader"
+ height = 5
+ width = 3
+ dwidth = 1
+ roundstart_template = /datum/map_template/shuttle/escape_pod/default
+ /// Set to true if you have a snowflake escape pod dock which needs to always have the normal pod or some other one
+ var/enforce_specific_pod = FALSE
+
+/obj/docking_port/stationary/escape_pod/Initialize(mapload)
+ . = ..()
+ if (enforce_specific_pod)
+ return
+
+ if (HAS_TRAIT(SSstation, STATION_TRAIT_SMALLER_PODS))
+ roundstart_template = /datum/map_template/shuttle/escape_pod/cramped
+ return
+ if (HAS_TRAIT(SSstation, STATION_TRAIT_BIGGER_PODS))
+ roundstart_template = /datum/map_template/shuttle/escape_pod/luxury
+
+// should fit the syndicate infiltrator, and smaller ships like the battlecruiser corvettes and fighters
+/obj/docking_port/stationary/syndicate
+ name = "near the station"
+ dheight = 1
+ dwidth = 12
+ height = 17
+ width = 23
+ shuttle_id = "syndicate_nearby"
+
+/obj/docking_port/stationary/syndicate/northwest
+ name = "northwest of station"
+ shuttle_id = "syndicate_nw"
+
+/obj/docking_port/stationary/syndicate/northeast
+ name = "northeast of station"
+ shuttle_id = "syndicate_ne"
+
+/obj/docking_port/stationary/transit
+ name = "In Transit"
+ override_can_dock_checks = TRUE
+ /// The turf reservation returned by the transit area request
+ var/datum/turf_reservation/reserved_area
+ /// The area created during the transit area reservation
+ var/area/shuttle/transit/assigned_area
+ /// The mobile port that owns this transit port
+ var/obj/docking_port/mobile/owner
+
+/obj/docking_port/stationary/transit/Initialize(mapload)
+ . = ..()
+ SSshuttle.transit_docking_ports += src
+
+/obj/docking_port/stationary/transit/Destroy(force=FALSE)
+ if(force)
+ if(get_docked())
+ log_world("A transit dock was destroyed while something was docked to it.")
+ SSshuttle.transit_docking_ports -= src
+ if(owner)
+ if(owner.assigned_transit == src)
+ owner.assigned_transit = null
+ owner = null
+ if(!QDELETED(reserved_area))
+ qdel(reserved_area)
+ reserved_area = null
+ return ..()
+
+/obj/docking_port/stationary/picked
+ ///Holds a list of map name strings for the port to pick from
+ var/list/shuttlekeys
+
+/obj/docking_port/stationary/picked/Initialize(mapload)
+ . = ..()
+ if(!LAZYLEN(shuttlekeys))
+ WARNING("Random docking port [shuttle_id] loaded with no shuttle keys")
+ return
+ var/selectedid = pick(shuttlekeys)
+ roundstart_template = SSmapping.shuttle_templates[selectedid]
+
+/obj/docking_port/stationary/picked/whiteship
+ name = "Deep Space"
+ shuttle_id = "whiteship_away"
+ height = 45 //Width and height need to remain in sync with the size of whiteshipdock.dmm, otherwise we'll get overflow
+ width = 44
+ dheight = 18
+ dwidth = 18
+ dir = 2
+ shuttlekeys = list(
+ "whiteship_meta",
+ "whiteship_pubby",
+ "whiteship_box",
+ "whiteship_cere",
+ "whiteship_kilo",
+ "whiteship_donut",
+ "whiteship_delta",
+ "whiteship_tram",
+ "whiteship_personalshuttle",
+ "whiteship_obelisk",
+ "whiteship_birdshot",
+ )
+
diff --git a/code/modules/shuttle/stationary_port/stationary_port.dm b/code/modules/shuttle/stationary_port/stationary_port.dm
new file mode 100644
index 000000000000..49437730cb07
--- /dev/null
+++ b/code/modules/shuttle/stationary_port/stationary_port.dm
@@ -0,0 +1,91 @@
+
+/obj/docking_port/stationary
+ name = "dock"
+
+ var/last_dock_time
+
+ /// Map template to load when the dock is loaded
+ var/datum/map_template/shuttle/roundstart_template
+ /// Used to check if the shuttle template is enabled in the config file
+ var/json_key
+ ///If true, the shuttle can always dock at this docking port, despite its area checks, or if something is already docked
+ var/override_can_dock_checks = FALSE
+
+/obj/docking_port/stationary/Initialize(mapload)
+ . = ..()
+ register()
+ if(!area_type)
+ var/area/place = get_area(src)
+ area_type = place?.type // We might be created in nullspace
+
+ if(mapload)
+ for(var/turf/T in return_turfs())
+ T.turf_flags |= NO_RUINS
+
+ if(SSshuttle.initialized)
+ INVOKE_ASYNC(SSshuttle, TYPE_PROC_REF(/datum/controller/subsystem/shuttle, setup_shuttles), list(src))
+
+#ifdef TESTING
+ highlight("#f00")
+#endif
+
+/obj/docking_port/stationary/Destroy(force)
+ if(force)
+ unregister()
+ return ..()
+
+/obj/docking_port/stationary/register(replace = FALSE)
+ . = ..()
+ if(!shuttle_id)
+ shuttle_id = "dock"
+ else
+ port_destinations = shuttle_id
+
+ if(!name)
+ name = "dock"
+
+ var/counter = SSshuttle.assoc_stationary[shuttle_id]
+ if(!replace || !counter)
+ if(counter)
+ counter++
+ SSshuttle.assoc_stationary[shuttle_id] = counter
+ shuttle_id = "[shuttle_id]_[counter]"
+ name = "[name] [counter]"
+ else
+ SSshuttle.assoc_stationary[shuttle_id] = 1
+
+ if(!port_destinations)
+ port_destinations = shuttle_id
+
+ SSshuttle.stationary_docking_ports += src
+
+/obj/docking_port/stationary/unregister()
+ . = ..()
+ SSshuttle.stationary_docking_ports -= src
+
+/obj/docking_port/stationary/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change = TRUE)
+ . = ..()
+ if(area_type) // We already have one
+ return
+ var/area/newarea = get_area(src)
+ area_type = newarea?.type
+
+/obj/docking_port/stationary/proc/load_roundstart()
+ if(json_key)
+ var/sid = SSmapping.current_map.shuttles[json_key]
+ roundstart_template = SSmapping.shuttle_templates[sid]
+ if(!roundstart_template)
+ CRASH("json_key:[json_key] value \[[sid]\] resulted in a null shuttle template for [src]")
+ else if(roundstart_template) // passed a PATH
+ var/sid = "[initial(roundstart_template.port_id)]_[initial(roundstart_template.suffix)]"
+
+ roundstart_template = SSmapping.shuttle_templates[sid]
+ if(!roundstart_template)
+ CRASH("Invalid path ([sid]/[roundstart_template]) passed to docking port.")
+
+ if(roundstart_template)
+ SSshuttle.action_load(roundstart_template, src)
+
+//returns first-found touching shuttleport
+/obj/docking_port/stationary/get_docked()
+ . = locate(/obj/docking_port/mobile) in loc
diff --git a/code/modules/spells/spell_types/pointed/_pointed.dm b/code/modules/spells/spell_types/pointed/_pointed.dm
index 39d6fb9d6736..6d65d70734d6 100644
--- a/code/modules/spells/spell_types/pointed/_pointed.dm
+++ b/code/modules/spells/spell_types/pointed/_pointed.dm
@@ -64,13 +64,13 @@
build_all_button_icons()
return TRUE
-/datum/action/cooldown/spell/pointed/InterceptClickOn(mob/living/caller, params, atom/target)
+/datum/action/cooldown/spell/pointed/InterceptClickOn(mob/living/clicker, params, atom/target)
var/atom/aim_assist_target
if(aim_assist)
- aim_assist_target = aim_assist(caller, target)
- return ..(caller, params, aim_assist_target || target)
+ aim_assist_target = aim_assist(clicker, target)
+ return ..(clicker, params, aim_assist_target || target)
-/datum/action/cooldown/spell/pointed/proc/aim_assist(mob/living/caller, atom/target)
+/datum/action/cooldown/spell/pointed/proc/aim_assist(mob/living/clicker, atom/target)
if(!isturf(target))
return
diff --git a/code/modules/spells/spell_types/pointed/swap.dm b/code/modules/spells/spell_types/pointed/swap.dm
index 884504efc8ed..57b74fba0562 100644
--- a/code/modules/spells/spell_types/pointed/swap.dm
+++ b/code/modules/spells/spell_types/pointed/swap.dm
@@ -35,29 +35,30 @@
return FALSE
return TRUE
-/datum/action/cooldown/spell/pointed/swap/InterceptClickOn(mob/living/caller, params, atom/target)
- if(LAZYACCESS(params2list(params), RIGHT_CLICK))
- if(!IsAvailable(feedback = TRUE))
- return FALSE
+/datum/action/cooldown/spell/pointed/swap/InterceptClickOn(mob/living/clicker, params, atom/target)
+ if(!LAZYACCESS(params2list(params), RIGHT_CLICK))
+ return ..()
+
+ if(!IsAvailable(feedback = TRUE))
+ return FALSE
+ if(!target)
+ return FALSE
+ if(!isliving(target) || isturf(target))
+ // Find any living being in the list. We aren't picky, it's aim assist after all
+ target = locate(/mob/living) in target
if(!target)
+ to_chat(owner, span_warning("You can only select living beings as secondary target!"))
return FALSE
- if(!isliving(target) || isturf(target))
- // Find any living being in the list. We aren't picky, it's aim assist after all
- target = locate(/mob/living) in target
- if(!target)
- to_chat(owner, span_warning("You can only select living beings as secondary target!"))
- return FALSE
- if(target == owner)
- if(!isnull(second_target))
- to_chat(owner, span_notice("You cancel your secondary swap target!"))
- second_target = null
- else
- to_chat(owner, span_warning("You have no secondary swap target!"))
- return FALSE
- second_target = target
- to_chat(owner, span_notice("You select [target.name] as a secondary swap target!"))
+ if(target == owner)
+ if(!isnull(second_target))
+ to_chat(owner, span_notice("You cancel your secondary swap target!"))
+ second_target = null
+ else
+ to_chat(owner, span_warning("You have no secondary swap target!"))
return FALSE
- return ..()
+ second_target = target
+ to_chat(owner, span_notice("You select [target.name] as a secondary swap target!"))
+ return FALSE
/datum/action/cooldown/spell/pointed/swap/cast(mob/living/carbon/cast_on)
. = ..()
diff --git a/code/modules/spells/spell_types/touch/_touch.dm b/code/modules/spells/spell_types/touch/_touch.dm
index 49d1f24e39c6..a783874c14cf 100644
--- a/code/modules/spells/spell_types/touch/_touch.dm
+++ b/code/modules/spells/spell_types/touch/_touch.dm
@@ -153,9 +153,9 @@
return ..() | SPELL_NO_FEEDBACK | SPELL_NO_IMMEDIATE_COOLDOWN
/datum/action/cooldown/spell/touch/cast(mob/living/carbon/cast_on)
- if(SEND_SIGNAL(cast_on, COMSIG_TOUCH_HANDLESS_CAST) & COMPONENT_CAST_HANDLESS)
+ if(SEND_SIGNAL(cast_on, COMSIG_TOUCH_HANDLESS_CAST, src) & COMPONENT_CAST_HANDLESS)
StartCooldown()
- return ..()
+ return
if(!QDELETED(attached_hand) && (attached_hand in cast_on.held_items))
remove_hand(cast_on, reset_cooldown_after = TRUE)
diff --git a/code/modules/station_goals/meteor_shield.dm b/code/modules/station_goals/meteor_shield.dm
index 84a61395a4b9..e4b76f600ca8 100644
--- a/code/modules/station_goals/meteor_shield.dm
+++ b/code/modules/station_goals/meteor_shield.dm
@@ -61,13 +61,14 @@
name = "\improper Meteor Shield Satellite"
desc = "A meteor point-defense satellite."
mode = "M-SHIELD"
- processing_flags = START_PROCESSING_MANUALLY
- subsystem_type = /datum/controller/subsystem/processing/fastprocess
/// the range a meteor shield sat can destroy meteors
var/kill_range = 14
//emag behavior dark matt-eor stuff
+ /// Proximity monitor associated with this atom, needed for it to work.
+ var/datum/proximity_monitor/proximity_monitor
+
/// amount of emagged active meteor shields
var/static/emagged_active_meteor_shields = 0
/// the highest amount of shields you've ever emagged
@@ -94,34 +95,43 @@
return FALSE
return TRUE
-/obj/machinery/satellite/meteor_shield/process()
- if(obj_flags & EMAGGED)
- //kills the processing because emagged meteor shields no longer stop meteors in any way
- return PROCESS_KILL
- if(!active)
+/obj/machinery/satellite/meteor_shield/Initialize(mapload)
+ . = ..()
+ proximity_monitor = new(src, /* range = */ 0)
+
+/obj/machinery/satellite/meteor_shield/HasProximity(atom/movable/proximity_check_mob)
+ . = ..()
+ if(!istype(proximity_check_mob, /obj/effect/meteor))
return
- for(var/obj/effect/meteor/meteor_to_destroy in GLOB.meteor_list)
- if(meteor_to_destroy.z != z)
- continue
- if(get_dist(meteor_to_destroy, src) > kill_range)
- continue
- if(space_los(meteor_to_destroy))
- var/turf/beam_from = get_turf(src)
- beam_from.Beam(get_turf(meteor_to_destroy), icon_state="sat_beam", time = 5)
- if(meteor_to_destroy.shield_defense(src))
- qdel(meteor_to_destroy)
+ var/obj/effect/meteor/meteor_to_destroy = proximity_check_mob
+ if(space_los(meteor_to_destroy))
+ var/turf/beam_from = get_turf(src)
+ beam_from.Beam(get_turf(meteor_to_destroy), icon_state="sat_beam", time = 5)
+ if(meteor_to_destroy.shield_defense(src))
+ qdel(meteor_to_destroy)
/obj/machinery/satellite/meteor_shield/toggle(user)
+ if(user)
+ balloon_alert(user, "looking for [active ? "off" : "on"] button")
+ if(user && !do_after(user, 2 SECONDS, src, IGNORE_HELD_ITEM))
+ return FALSE
if(!..(user))
return FALSE
if(obj_flags & EMAGGED)
update_emagged_meteor_sat(user)
+ if(active)
+ proximity_monitor.set_range(kill_range)
+ else
+ proximity_monitor.set_range(0)
+
+
var/datum/station_goal/station_shield/goal = SSstation.get_station_goal(/datum/station_goal/station_shield)
goal?.update_coverage()
/obj/machinery/satellite/meteor_shield/Destroy()
. = ..()
+ QDEL_NULL(proximity_monitor)
if(obj_flags & EMAGGED)
//satellites that are destroying are not active, this will count down the number of emagged sats
update_emagged_meteor_sat()
@@ -181,6 +191,7 @@
for(var/datum/round_event_control/stray_meteor/stray_meteor in SSevents.control)
stray_meteor.weight *= mod
+
#undef EMAGGED_METEOR_SHIELD_THRESHOLD_ONE
#undef EMAGGED_METEOR_SHIELD_THRESHOLD_TWO
#undef EMAGGED_METEOR_SHIELD_THRESHOLD_THREE
diff --git a/code/modules/surgery/bodyparts/_bodyparts.dm b/code/modules/surgery/bodyparts/_bodyparts.dm
index 820108119fe5..8795d722871d 100644
--- a/code/modules/surgery/bodyparts/_bodyparts.dm
+++ b/code/modules/surgery/bodyparts/_bodyparts.dm
@@ -366,7 +366,7 @@
for(var/obj/item/embedded_thing in embedded_objects)
var/stuck_word = embedded_thing.is_embed_harmless() ? "stuck" : "embedded"
- check_list += "\t There is \a [embedded_thing] [stuck_word] in your [name]!"
+ check_list += "\t There is \a [embedded_thing] [stuck_word] in your [name]!"
/obj/item/bodypart/blob_act()
receive_damage(max_damage, wound_bonus = CANT_WOUND)
@@ -814,7 +814,7 @@
SIGNAL_ADDTRAIT(TRAIT_NOBLOOD),
))
- UnregisterSignal(old_owner, COMSIG_ATOM_RESTYLE)
+ UnregisterSignal(old_owner, list(COMSIG_ATOM_RESTYLE, COMSIG_COMPONENT_CLEAN_ACT))
/// Apply ownership of a limb to someone, giving the appropriate traits, updates and signals
/obj/item/bodypart/proc/apply_ownership(mob/living/carbon/new_owner)
@@ -843,6 +843,7 @@
update_disabled()
RegisterSignal(owner, COMSIG_ATOM_RESTYLE, PROC_REF(on_attempt_feature_restyle_mob))
+ RegisterSignal(owner, COMSIG_COMPONENT_CLEAN_ACT, PROC_REF(on_owner_clean))
forceMove(owner)
RegisterSignal(src, COMSIG_MOVABLE_MOVED, PROC_REF(on_forced_removal)) //this must be set after we moved, or we insta gib
@@ -1012,7 +1013,12 @@
/obj/item/bodypart/proc/remove_color_override(color_priority)
LAZYREMOVE(color_overrides, "[color_priority]")
-//to update the bodypart's icon when not attached to a mob
+/// Called when limb's current owner gets washed
+/obj/item/bodypart/proc/on_owner_clean(mob/living/carbon/source, clean_types)
+ SIGNAL_HANDLER
+ wash(clean_types)
+
+/// To update the bodypart's icon when not attached to a mob
/obj/item/bodypart/proc/update_icon_dropped()
SHOULD_CALL_PARENT(TRUE)
@@ -1026,6 +1032,24 @@
img.pixel_y += px_y
add_overlay(standing)
+/obj/item/bodypart/update_atom_colour()
+ . = ..()
+ for(var/i in 1 to COLOUR_PRIORITY_AMOUNT)
+ var/list/checked_color = atom_colours[i]
+ if (!checked_color)
+ remove_color_override(LIMB_COLOR_ATOM_COLOR + i)
+ continue
+ var/actual_color = checked_color[ATOM_COLOR_VALUE_INDEX]
+ if (checked_color[ATOM_COLOR_TYPE_INDEX] == ATOM_COLOR_TYPE_FILTER)
+ var/color_filter = checked_color[ATOM_COLOR_VALUE_INDEX]
+ actual_color = apply_matrix_to_color(COLOR_WHITE, color_filter["color"], color_filter["space"] || COLORSPACE_RGB)
+ add_color_override(actual_color, LIMB_COLOR_ATOM_COLOR + i)
+ update_limb()
+ if (owner)
+ owner.update_body_parts()
+ else
+ update_icon_dropped()
+
///Generates an /image for the limb to be used as an overlay
/obj/item/bodypart/proc/get_limb_icon(dropped)
SHOULD_CALL_PARENT(TRUE)
@@ -1351,14 +1375,11 @@
* * gauze- Just the gauze stack we're taking a sheet from to apply here
*/
/obj/item/bodypart/proc/apply_gauze(obj/item/stack/medical/gauze/new_gauze)
- if(!istype(new_gauze) || !new_gauze.absorption_capacity)
+ if(!istype(new_gauze) || !new_gauze.absorption_capacity || !new_gauze.use(1))
return
- var/newly_gauzed = FALSE
- if(!current_gauze)
- newly_gauzed = TRUE
+ var/newly_gauzed = !current_gauze
QDEL_NULL(current_gauze)
current_gauze = new new_gauze.type(src, 1)
- new_gauze.use(1)
current_gauze.gauzed_bodypart = src
if(newly_gauzed)
SEND_SIGNAL(src, COMSIG_BODYPART_GAUZED, current_gauze, new_gauze)
diff --git a/code/modules/surgery/bodyparts/helpers.dm b/code/modules/surgery/bodyparts/helpers.dm
index dec8efb154ea..2f9a42e0d1f9 100644
--- a/code/modules/surgery/bodyparts/helpers.dm
+++ b/code/modules/surgery/bodyparts/helpers.dm
@@ -40,11 +40,12 @@
///Get the bodypart for whatever hand we have active, Only relevant for carbons
/mob/proc/get_active_hand()
+ RETURN_TYPE(/obj/item/bodypart)
return FALSE
/mob/living/carbon/get_active_hand()
var/which_hand = BODY_ZONE_PRECISE_L_HAND
- if(!(active_hand_index % RIGHT_HANDS))
+ if(IS_RIGHT_INDEX(active_hand_index))
which_hand = BODY_ZONE_PRECISE_R_HAND
return get_bodypart(check_zone(which_hand))
@@ -54,7 +55,7 @@
/mob/living/carbon/get_inactive_hand()
var/which_hand = BODY_ZONE_PRECISE_R_HAND
- if(!(active_hand_index % RIGHT_HANDS))
+ if(IS_RIGHT_INDEX(active_hand_index))
which_hand = BODY_ZONE_PRECISE_L_HAND
return get_bodypart(check_zone(which_hand))
@@ -64,7 +65,7 @@
/mob/living/carbon/has_left_hand(check_disabled = TRUE)
for(var/obj/item/bodypart/hand_instance in hand_bodyparts)
- if(!(hand_instance.held_index % RIGHT_HANDS) || (check_disabled && hand_instance.bodypart_disabled))
+ if(IS_RIGHT_INDEX(hand_instance.held_index) || (check_disabled && hand_instance.bodypart_disabled))
continue
return TRUE
return FALSE
@@ -80,7 +81,7 @@
/mob/living/carbon/has_right_hand(check_disabled = TRUE)
for(var/obj/item/bodypart/hand_instance in hand_bodyparts)
- if(hand_instance.held_index % RIGHT_HANDS || (check_disabled && hand_instance.bodypart_disabled))
+ if(IS_LEFT_INDEX(hand_instance.held_index) || (check_disabled && hand_instance.bodypart_disabled))
continue
return TRUE
return FALSE
diff --git a/code/modules/surgery/burn_dressing.dm b/code/modules/surgery/burn_dressing.dm
index 9ffeaef08955..54aa82f43b43 100644
--- a/code/modules/surgery/burn_dressing.dm
+++ b/code/modules/surgery/burn_dressing.dm
@@ -100,7 +100,7 @@
span_notice("[user] successfully excises some of the infected flesh from [target]'s [target.parse_zone_with_bodypart(target_zone)]!"),
)
log_combat(user, target, "excised infected flesh in", addition="COMBAT MODE: [uppertext(user.combat_mode)]")
- surgery.operated_bodypart.receive_damage(brute=3, wound_bonus=CANT_WOUND)
+ target.apply_damage(3, BRUTE, surgery.operated_bodypart, wound_bonus = CANT_WOUND, sharpness = SHARP_EDGED, attacking_item = tool)
burn_wound.infestation -= infestation_removed
burn_wound.sanitization += sanitization_added
if(burn_wound.infestation <= 0)
@@ -118,7 +118,7 @@
span_notice("[user] carves away some of the healthy flesh from [target]'s [target.parse_zone_with_bodypart(target_zone)] with [tool]!"),
span_notice("[user] carves away some of the healthy flesh from [target]'s [target.parse_zone_with_bodypart(target_zone)]!"),
)
- surgery.operated_bodypart.receive_damage(brute=rand(4,8), sharpness=TRUE)
+ target.apply_damage(rand(4, 8), BRUTE, surgery.operated_bodypart, sharpness = SHARP_EDGED, attacking_item = tool)
/datum/surgery_step/debride/initiate(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery, try_to_fail = FALSE)
if(!..())
diff --git a/code/modules/surgery/organs/external/_visual_organs.dm b/code/modules/surgery/organs/external/_visual_organs.dm
index ec88bad9295e..201ebaf87ce3 100644
--- a/code/modules/surgery/organs/external/_visual_organs.dm
+++ b/code/modules/surgery/organs/external/_visual_organs.dm
@@ -87,11 +87,16 @@ Unlike normal organs, we're actually inside a persons limbs at all times
bodypart_overlay.set_appearance(typed_accessory)
- if(owner) //are we in a person?
+ // if(owner && !(owner.living_flags & STOP_OVERLAY_UPDATE_BODY_PARTS)) //are we a person? // NOVA EDIT REMOVAL
+ // NOVA EDIT ADDITION
+ if(owner && !(owner.living_flags)) //are we a person?
+ #ifdef STOP_OVERLAY_UPDATE_BODY_PARTS // Once #4504 is merged, remove this edit
+ #error A prior conflict has been resolved, please remove this edit
+ #endif
+ // NOVA EDIT ADDITION END
owner.update_body_parts()
- else if(bodypart_owner) //are we in a limb?
- bodypart_owner.update_icon_dropped()
- //else if(use_mob_sprite_as_obj_sprite) //are we out in the world, unprotected by flesh?
+ else
+ bodypart_owner?.update_icon_dropped() //are we in a limb?
/obj/item/organ/update_overlays()
. = ..()
diff --git a/code/modules/surgery/organs/external/wings/functional_wings.dm b/code/modules/surgery/organs/external/wings/functional_wings.dm
index 05271edceb61..01d49ba05037 100644
--- a/code/modules/surgery/organs/external/wings/functional_wings.dm
+++ b/code/modules/surgery/organs/external/wings/functional_wings.dm
@@ -11,7 +11,7 @@
/datum/action/innate/flight/Activate()
var/mob/living/carbon/human/human = owner
var/obj/item/organ/wings/functional/wings = human.get_organ_slot(ORGAN_SLOT_EXTERNAL_WINGS)
- if(wings?.can_fly(human))
+ if(wings?.can_fly())
wings.toggle_flight(human)
///The true wings that you can use to fly and shit (you cant actually shit with them)
@@ -29,6 +29,23 @@
// grind_results = list(/datum/reagent/flightpotion = 5)
food_reagents = list(/datum/reagent/flightpotion = 5)
+ var/drift_force = FUNCTIONAL_WING_FORCE
+ var/stabilizer_force = FUNCTIONAL_WING_STABILIZATION
+
+/obj/item/organ/wings/functional/Initialize(mapload)
+ . = ..()
+ AddComponent( \
+ /datum/component/jetpack, \
+ TRUE, \
+ drift_force, \
+ stabilizer_force, \
+ COMSIG_WINGS_OPENED, \
+ COMSIG_WINGS_CLOSED, \
+ null, \
+ CALLBACK(src, PROC_REF(can_fly)), \
+ CALLBACK(src, PROC_REF(can_fly)), \
+ )
+
/obj/item/organ/wings/functional/Destroy()
QDEL_NULL(fly)
return ..()
@@ -54,14 +71,14 @@
/obj/item/organ/wings/functional/proc/handle_flight(mob/living/carbon/human/human)
if(!HAS_TRAIT_FROM(human, TRAIT_MOVE_FLOATING, SPECIES_FLIGHT_TRAIT))
return FALSE
- if(!can_fly(human))
+ if(!can_fly())
toggle_flight(human)
return FALSE
return TRUE
-
///Check if we're still eligible for flight (wings covered, atmosphere too thin, etc)
-/obj/item/organ/wings/functional/proc/can_fly(mob/living/carbon/human/human)
+/obj/item/organ/wings/functional/proc/can_fly()
+ var/mob/living/carbon/human/human = owner
if(human.stat || human.body_position == LYING_DOWN || isnull(human.client))
return FALSE
//Jumpsuits have tail holes, so it makes sense they have wing holes too
@@ -105,13 +122,10 @@
/obj/item/organ/wings/functional/proc/toggle_flight(mob/living/carbon/human/human)
if(!HAS_TRAIT_FROM(human, TRAIT_MOVE_FLOATING, SPECIES_FLIGHT_TRAIT))
human.physiology.stun_mod *= 2
- human.add_traits(list(TRAIT_NO_FLOATING_ANIM, TRAIT_MOVE_FLOATING, TRAIT_IGNORING_GRAVITY, TRAIT_NOGRAV_ALWAYS_DRIFT), SPECIES_FLIGHT_TRAIT)
+ human.add_traits(list(TRAIT_MOVE_FLOATING, TRAIT_IGNORING_GRAVITY, TRAIT_NOGRAV_ALWAYS_DRIFT), SPECIES_FLIGHT_TRAIT)
human.add_movespeed_modifier(/datum/movespeed_modifier/jetpack/wings)
human.AddElement(/datum/element/forced_gravity, 0)
passtable_on(human, SPECIES_FLIGHT_TRAIT)
- RegisterSignal(human, COMSIG_MOB_CLIENT_MOVE_NOGRAV, PROC_REF(on_client_move))
- RegisterSignal(human, COMSIG_MOB_ATTEMPT_HALT_SPACEMOVE, PROC_REF(on_pushoff))
- START_PROCESSING(SSnewtonian_movement, src)
open_wings()
to_chat(human, span_notice("You beat your wings and begin to hover gently above the ground..."))
human.set_resting(FALSE, TRUE)
@@ -119,50 +133,21 @@
return
human.physiology.stun_mod *= 0.5
- human.remove_traits(list(TRAIT_NO_FLOATING_ANIM, TRAIT_MOVE_FLOATING, TRAIT_IGNORING_GRAVITY, TRAIT_NOGRAV_ALWAYS_DRIFT), SPECIES_FLIGHT_TRAIT)
+ human.remove_traits(list(TRAIT_MOVE_FLOATING, TRAIT_IGNORING_GRAVITY, TRAIT_NOGRAV_ALWAYS_DRIFT), SPECIES_FLIGHT_TRAIT)
human.remove_movespeed_modifier(/datum/movespeed_modifier/jetpack/wings)
human.RemoveElement(/datum/element/forced_gravity, 0)
passtable_off(human, SPECIES_FLIGHT_TRAIT)
- UnregisterSignal(human, list(COMSIG_MOB_CLIENT_MOVE_NOGRAV, COMSIG_MOB_ATTEMPT_HALT_SPACEMOVE))
- STOP_PROCESSING(SSnewtonian_movement, src)
to_chat(human, span_notice("You settle gently back onto the ground..."))
close_wings()
human.refresh_gravity()
-/obj/item/organ/wings/functional/proc/on_client_move(mob/source, list/move_args)
- SIGNAL_HANDLER
-
- if (!can_fly(source))
- return
-
- var/max_drift_force = (DEFAULT_INERTIA_SPEED / source.cached_multiplicative_slowdown - 1) / INERTIA_SPEED_COEF + 1
- source.newtonian_move(dir2angle(source.client.intended_direction), instant = TRUE, drift_force = FUNCTIONAL_WING_FORCE, controlled_cap = max_drift_force)
- source.setDir(source.client.intended_direction)
-
-/obj/item/organ/wings/functional/proc/on_pushoff(mob/source, movement_dir, continuous_move, atom/backup)
- SIGNAL_HANDLER
-
- if (get_dir(source, backup) == movement_dir || source.loc == backup.loc)
- return
-
- if (!can_fly(source) || !source.client.intended_direction || (source.client.intended_direction & get_dir(source, backup)))
- return
-
- return COMPONENT_PREVENT_SPACEMOVE_HALT
-
-/obj/item/organ/wings/functional/process(seconds_per_tick)
- if (!owner || !can_fly(owner) || isnull(owner.drift_handler))
- return
-
- var/max_drift_force = (DEFAULT_INERTIA_SPEED / owner.cached_multiplicative_slowdown - 1) / INERTIA_SPEED_COEF + 1
- owner.drift_handler.stabilize_drift(owner.client.intended_direction ? dir2angle(owner.client.intended_direction) : null, owner.client.intended_direction ? max_drift_force : 0, FUNCTIONAL_WING_STABILIZATION * (seconds_per_tick * 1 SECONDS))
-
///SPREAD OUR WINGS AND FLLLLLYYYYYY
/obj/item/organ/wings/functional/proc/open_wings()
var/datum/bodypart_overlay/mutant/wings/functional/overlay = bodypart_overlay
overlay.open_wings()
wings_open = TRUE
owner.update_body_parts()
+ SEND_SIGNAL(src, COMSIG_WINGS_OPENED, owner)
///close our wings
/obj/item/organ/wings/functional/proc/close_wings()
@@ -175,6 +160,8 @@
var/turf/location = loc
location.Entered(src, NONE)
+ SEND_SIGNAL(src, COMSIG_WINGS_CLOSED, owner)
+
///Bodypart overlay of function wings, including open and close functionality!
/datum/bodypart_overlay/mutant/wings/functional
///Are our wings currently open? Change through open_wings or close_wings()
diff --git a/code/modules/surgery/organs/external/wings/moth_wings.dm b/code/modules/surgery/organs/external/wings/moth_wings.dm
index f6e20c49699a..8c8cc8f0e386 100644
--- a/code/modules/surgery/organs/external/wings/moth_wings.dm
+++ b/code/modules/surgery/organs/external/wings/moth_wings.dm
@@ -16,18 +16,31 @@
///Store our old datum here for if our burned wings are healed
var/original_sprite_datum
+ var/drift_force = MOTH_WING_FORCE
+ var/stabilizer_force = MOTH_WING_FORCE
+
+/obj/item/organ/wings/moth/Initialize(mapload)
+ . = ..()
+ AddComponent( \
+ /datum/component/jetpack, \
+ TRUE, \
+ drift_force, \
+ stabilizer_force, \
+ COMSIG_ORGAN_IMPLANTED, \
+ COMSIG_ORGAN_REMOVED, \
+ null, \
+ CALLBACK(src, PROC_REF(allow_flight)), \
+ null, \
+ )
+
/obj/item/organ/wings/moth/on_mob_insert(mob/living/carbon/receiver)
. = ..()
RegisterSignal(receiver, COMSIG_HUMAN_BURNING, PROC_REF(try_burn_wings))
RegisterSignal(receiver, COMSIG_LIVING_POST_FULLY_HEAL, PROC_REF(heal_wings))
- RegisterSignal(receiver, COMSIG_MOB_CLIENT_MOVE_NOGRAV, PROC_REF(on_client_move))
- RegisterSignal(receiver, COMSIG_MOB_ATTEMPT_HALT_SPACEMOVE, PROC_REF(on_pushoff))
- START_PROCESSING(SSnewtonian_movement, src)
/obj/item/organ/wings/moth/on_mob_remove(mob/living/carbon/organ_owner)
. = ..()
- UnregisterSignal(organ_owner, list(COMSIG_HUMAN_BURNING, COMSIG_LIVING_POST_FULLY_HEAL, COMSIG_MOB_CLIENT_MOVE_NOGRAV, COMSIG_MOB_ATTEMPT_HALT_SPACEMOVE))
- STOP_PROCESSING(SSnewtonian_movement, src)
+ UnregisterSignal(organ_owner, list(COMSIG_HUMAN_BURNING, COMSIG_LIVING_POST_FULLY_HEAL))
/obj/item/organ/wings/moth/make_flap_sound(mob/living/carbon/wing_owner)
playsound(wing_owner, 'sound/mobs/humanoids/moth/moth_flutter.ogg', 50, TRUE)
@@ -38,14 +51,6 @@
/obj/item/organ/wings/moth/proc/allow_flight()
if(!owner || !owner.client)
return FALSE
- if(!isturf(owner.loc))
- return FALSE
- if(!(owner.movement_type & FLOATING) || owner.buckled)
- return FALSE
- if(owner.pulledby)
- return FALSE
- if(owner.throwing)
- return FALSE
if(owner.has_gravity())
return FALSE
if(ishuman(owner))
@@ -59,34 +64,6 @@
return TRUE
return FALSE
-/obj/item/organ/wings/moth/process(seconds_per_tick)
- if (!owner || !allow_flight() || isnull(owner.drift_handler))
- return
-
- var/max_drift_force = (DEFAULT_INERTIA_SPEED / owner.cached_multiplicative_slowdown - 1) / INERTIA_SPEED_COEF + 1
- owner.drift_handler.stabilize_drift(owner.client.intended_direction ? dir2angle(owner.client.intended_direction) : null, owner.client.intended_direction ? max_drift_force : 0, MOTH_WING_FORCE * (seconds_per_tick * 1 SECONDS))
-
-/obj/item/organ/wings/moth/proc/on_client_move(mob/source, list/move_args)
- SIGNAL_HANDLER
-
- if (!allow_flight())
- return
-
- var/max_drift_force = (DEFAULT_INERTIA_SPEED / source.cached_multiplicative_slowdown - 1) / INERTIA_SPEED_COEF + 1
- source.newtonian_move(dir2angle(source.client.intended_direction), instant = TRUE, drift_force = MOTH_WING_FORCE, controlled_cap = max_drift_force)
- source.setDir(source.client.intended_direction)
-
-/obj/item/organ/wings/moth/proc/on_pushoff(mob/source, movement_dir, continuous_move, atom/backup)
- SIGNAL_HANDLER
-
- if (get_dir(source, backup) == movement_dir || source.loc == backup.loc)
- return
-
- if (!allow_flight() || !source.client.intended_direction || (source.client.intended_direction & get_dir(source, backup)))
- return
-
- return COMPONENT_PREVENT_SPACEMOVE_HALT
-
///check if our wings can burn off ;_;
/obj/item/organ/wings/moth/proc/try_burn_wings(mob/living/carbon/human/human)
SIGNAL_HANDLER
diff --git a/code/modules/surgery/organs/internal/cyberimp/augments_chest.dm b/code/modules/surgery/organs/internal/cyberimp/augments_chest.dm
index 8b77ef5ec78b..603acc0eae9d 100644
--- a/code/modules/surgery/organs/internal/cyberimp/augments_chest.dm
+++ b/code/modules/surgery/organs/internal/cyberimp/augments_chest.dm
@@ -175,6 +175,7 @@
COMSIG_THRUSTER_DEACTIVATED, \
THRUSTER_ACTIVATION_FAILED, \
CALLBACK(src, PROC_REF(allow_thrust), 0.01), \
+ CALLBACK(src, PROC_REF(allow_thrust), 0.01), \
/datum/effect_system/trail_follow/ion, \
)
diff --git a/code/modules/surgery/organs/internal/cyberimp/augments_eyes.dm b/code/modules/surgery/organs/internal/cyberimp/augments_eyes.dm
index 204247e4de82..cdb881f02dde 100644
--- a/code/modules/surgery/organs/internal/cyberimp/augments_eyes.dm
+++ b/code/modules/surgery/organs/internal/cyberimp/augments_eyes.dm
@@ -20,11 +20,11 @@
/obj/item/organ/cyberimp/eyes/hud/proc/toggle_hud(mob/living/carbon/eye_owner)
if(toggled_on)
toggled_on = FALSE
- eye_owner.add_traits(HUD_traits, ORGAN_TRAIT)
+ eye_owner.remove_traits(HUD_traits, ORGAN_TRAIT)
balloon_alert(eye_owner, "hud disabled")
return
toggled_on = TRUE
- eye_owner.remove_traits(HUD_traits, ORGAN_TRAIT)
+ eye_owner.add_traits(HUD_traits, ORGAN_TRAIT)
balloon_alert(eye_owner, "hud enabled")
/obj/item/organ/cyberimp/eyes/hud/on_mob_insert(mob/living/carbon/eye_owner, special = FALSE, movement_flags)
diff --git a/code/modules/surgery/organs/internal/cyberimp/augments_internal.dm b/code/modules/surgery/organs/internal/cyberimp/augments_internal.dm
index 42f0e5ac7b23..ffd3f022e7e2 100644
--- a/code/modules/surgery/organs/internal/cyberimp/augments_internal.dm
+++ b/code/modules/surgery/organs/internal/cyberimp/augments_internal.dm
@@ -6,16 +6,6 @@
organ_flags = ORGAN_ROBOTIC
failing_desc = "seems to be broken."
var/implant_color = COLOR_WHITE
- var/implant_overlay
-
-/obj/item/organ/cyberimp/New(mob/implanted_mob = null)
- if(iscarbon(implanted_mob))
- src.Insert(implanted_mob)
- if(implant_overlay)
- var/mutable_appearance/overlay = mutable_appearance(icon, implant_overlay)
- overlay.color = implant_color
- add_overlay(overlay)
- return ..()
//[[[[BRAIN]]]]
diff --git a/code/modules/surgery/organs/internal/eyes/_eyes.dm b/code/modules/surgery/organs/internal/eyes/_eyes.dm
index 7e7410061d51..6a09aaa47f25 100644
--- a/code/modules/surgery/organs/internal/eyes/_eyes.dm
+++ b/code/modules/surgery/organs/internal/eyes/_eyes.dm
@@ -54,8 +54,9 @@
. = ..()
receiver.cure_blind(NO_EYES)
apply_damaged_eye_effects()
- refresh(receiver, call_update = TRUE)
+ refresh(receiver, call_update = !special)
RegisterSignal(receiver, COMSIG_ATOM_BULLET_ACT, PROC_REF(on_bullet_act))
+ RegisterSignal(receiver, COMSIG_COMPONENT_CLEAN_FACE_ACT, PROC_REF(on_face_wash))
if (scarring)
apply_scarring_effects()
@@ -69,10 +70,11 @@
return
var/mob/living/carbon/human/affected_human = eye_owner
- if(eye_color_left)
+ if(length(eye_color_left))
affected_human.add_eye_color_left(eye_color_left, EYE_COLOR_ORGAN_PRIORITY, update_body = FALSE)
- if(eye_color_right)
+ if(length(eye_color_right))
affected_human.add_eye_color_right(eye_color_right, EYE_COLOR_ORGAN_PRIORITY, update_body = FALSE)
+ refresh_atom_color_overrides()
if(HAS_TRAIT(affected_human, TRAIT_NIGHT_VISION)) // NOVA EDIT CHANGE - ORIGINAL: if(HAS_TRAIT(affected_human, TRAIT_NIGHT_VISION) && !lighting_cutoff)
//lighting_cutoff = LIGHTING_CUTOFF_REAL_LOW // NOVA EDIT REMOVAL
@@ -98,6 +100,8 @@
if(ishuman(organ_owner))
var/mob/living/carbon/human/human_owner = organ_owner
human_owner.remove_eye_color(EYE_COLOR_ORGAN_PRIORITY, update_body = FALSE)
+ for(var/i in 1 to COLOUR_PRIORITY_AMOUNT)
+ human_owner.remove_eye_color(EYE_COLOR_ATOM_COLOR_PRIORITY + i, update_body = FALSE)
if(native_fov)
organ_owner.remove_fov_trait(type)
if(!special)
@@ -116,7 +120,45 @@
organ_owner.update_tint()
organ_owner.update_sight()
is_emissive = FALSE // NOVA EDIT ADDITION
- UnregisterSignal(organ_owner, COMSIG_ATOM_BULLET_ACT)
+ UnregisterSignal(organ_owner, list(COMSIG_ATOM_BULLET_ACT, COMSIG_COMPONENT_CLEAN_FACE_ACT))
+
+/obj/item/organ/eyes/update_atom_colour()
+ . = ..()
+ if (ishuman(owner))
+ refresh_atom_color_overrides()
+ owner.update_body()
+
+/// Adds eye color overrides to our owner from our atom color
+/obj/item/organ/eyes/proc/refresh_atom_color_overrides()
+ if (!atom_colours)
+ return
+
+ var/mob/living/carbon/human/human_owner = owner
+ for(var/i in 1 to COLOUR_PRIORITY_AMOUNT)
+ var/list/checked_color = atom_colours[i]
+ if (!checked_color)
+ human_owner.remove_eye_color(EYE_COLOR_ATOM_COLOR_PRIORITY + i, update_body = FALSE)
+ continue
+
+ var/left_color = COLOR_WHITE
+ var/right_color = COLOR_WHITE
+
+ if (length(eye_color_left))
+ left_color = eye_color_left
+ if (length(eye_color_right))
+ right_color = eye_color_right
+
+ if (checked_color[ATOM_COLOR_TYPE_INDEX] == ATOM_COLOR_TYPE_FILTER)
+ var/color_filter = checked_color[ATOM_COLOR_VALUE_INDEX]
+ left_color = apply_matrix_to_color(left_color, color_filter["color"], color_filter["space"] || COLORSPACE_RGB)
+ right_color = apply_matrix_to_color(right_color, color_filter["color"], color_filter["space"] || COLORSPACE_RGB)
+ else
+ var/list/target_color = color_transition_filter(checked_color[ATOM_COLOR_VALUE_INDEX], SATURATION_OVERRIDE)
+ left_color = apply_matrix_to_color(left_color, target_color["color"], COLORSPACE_HSL)
+ right_color = apply_matrix_to_color(right_color, target_color["color"], COLORSPACE_HSL)
+
+ human_owner.add_eye_color_left(left_color, EYE_COLOR_ATOM_COLOR_PRIORITY + i, update_body = FALSE)
+ human_owner.add_eye_color_right(right_color, EYE_COLOR_ATOM_COLOR_PRIORITY + i, update_body = FALSE)
/obj/item/organ/eyes/proc/on_bullet_act(mob/living/carbon/source, obj/projectile/proj, def_zone, piercing_hit, blocked)
SIGNAL_HANDLER
@@ -145,6 +187,11 @@
eye_puncture.apply_wound(bodypart_owner, wound_source = "bullet impact", right_side = picked_side)
apply_scar(picked_side)
+/// When our owner washes their face. The idea that spessmen wash their eyeballs is highly disturbing but this is the easiest way to get rid of cursed crayon eye coloring
+/obj/item/organ/eyes/proc/on_face_wash()
+ SIGNAL_HANDLER
+ wash(CLEAN_WASH)
+
#define OFFSET_X 1
#define OFFSET_Y 2
diff --git a/code/modules/surgery/organs/internal/liver/liver_skeleton.dm b/code/modules/surgery/organs/internal/liver/liver_skeleton.dm
index 8ff1f0c3cf32..6ed49b8f1137 100644
--- a/code/modules/surgery/organs/internal/liver/liver_skeleton.dm
+++ b/code/modules/surgery/organs/internal/liver/liver_skeleton.dm
@@ -37,7 +37,7 @@
playsound(organ_owner, SFX_DESECRATION, 50, vary = TRUE) //You just want to socialize
organ_owner.visible_message(span_warning("[organ_owner] rattles loudly and flails around!!"), span_danger("Your bones hurt so much that your missing muscles spasm!!"))
INVOKE_ASYNC(organ_owner, TYPE_PROC_REF(/atom/movable, say), "OOF!!", forced = chem.type)
- bodypart.receive_damage(brute = 200) //But I don't think we should
+ organ_owner.apply_damage(200, BRUTE, bodypart)
else
to_chat(organ_owner, span_warning("Your missing [parse_zone(selected_part)] aches from wherever you left it."))
INVOKE_ASYNC(organ_owner, TYPE_PROC_REF(/mob, emote), "sigh")
diff --git a/code/modules/surgery/organs/internal/tongue/_tongue.dm b/code/modules/surgery/organs/internal/tongue/_tongue.dm
index 3b2a4888ccb5..a10ae21b90ba 100644
--- a/code/modules/surgery/organs/internal/tongue/_tongue.dm
+++ b/code/modules/surgery/organs/internal/tongue/_tongue.dm
@@ -212,7 +212,7 @@
)
// NOVA EDIT ADDITION END
-/obj/item/organ/tongue/lizard/New(class, timer, datum/mutation/human/copymut)
+/obj/item/organ/tongue/lizard/Initialize(mapload)
. = ..()
AddComponent(/datum/component/speechmod, replacements = CONFIG_GET(flag/russian_text_formation) ? russian_speech_replacements : speech_replacements, should_modify_speech = CALLBACK(src, PROC_REF(should_modify_speech))) // NOVA EDIT CHANGE - ORIGINAL: AddComponent(/datum/component/speechmod, replacements = speech_replacements, should_modify_speech = CALLBACK(src, PROC_REF(should_modify_speech)))
diff --git a/code/modules/surgery/repair_puncture.dm b/code/modules/surgery/repair_puncture.dm
index b916668433f0..77b36d264cdf 100644
--- a/code/modules/surgery/repair_puncture.dm
+++ b/code/modules/surgery/repair_puncture.dm
@@ -81,8 +81,8 @@
span_notice("[user] successfully realigns some of the blood vessels in [target]'s [target.parse_zone_with_bodypart(target_zone)] with [tool]!"),
span_notice("[user] successfully realigns some of the blood vessels in [target]'s [target.parse_zone_with_bodypart(target_zone)]!"),
)
- log_combat(user, target, "excised infected flesh in", addition="COMBAT MODE: [uppertext(user.combat_mode)]")
- surgery.operated_bodypart.receive_damage(brute=3, wound_bonus=CANT_WOUND)
+ log_combat(user, target, "realigned blood vessels in", addition="COMBAT MODE: [uppertext(user.combat_mode)]")
+ target.apply_damage(3, BRUTE, surgery.operated_bodypart, wound_bonus = CANT_WOUND, sharpness = SHARP_EDGED, attacking_item = tool)
pierce_wound.adjust_blood_flow(-0.25)
return ..()
@@ -95,7 +95,7 @@
span_notice("[user] jerks apart some of the blood vessels in [target]'s [target.parse_zone_with_bodypart(target_zone)] with [tool]!"),
span_notice("[user] jerk apart some of the blood vessels in [target]'s [target.parse_zone_with_bodypart(target_zone)]!"),
)
- surgery.operated_bodypart.receive_damage(brute=rand(4,8), sharpness=SHARP_EDGED, wound_bonus = 10)
+ target.apply_damage(rand(4, 8), BRUTE, surgery.operated_bodypart, wound_bonus = 10, sharpness = SHARP_EDGED, attacking_item = tool)
///// Sealing the vessels back together
/datum/surgery_step/seal_veins
@@ -144,7 +144,7 @@
)
log_combat(user, target, "dressed burns in", addition="COMBAT MODE: [uppertext(user.combat_mode)]")
pierce_wound.adjust_blood_flow(-0.5)
- if(pierce_wound.blood_flow > 0)
+ if(!QDELETED(pierce_wound) && pierce_wound.blood_flow > 0)
surgery.status = REALIGN_INNARDS
to_chat(user, span_notice("There still seems to be misaligned blood vessels to finish..."))
else
diff --git a/code/modules/surgery/tools.dm b/code/modules/surgery/tools.dm
index 185564ffb20a..0a370b59b2f6 100644
--- a/code/modules/surgery/tools.dm
+++ b/code/modules/surgery/tools.dm
@@ -4,6 +4,7 @@
icon = 'icons/obj/medical/surgery_tools.dmi'
icon_state = "retractor"
inhand_icon_state = "retractor"
+ icon_angle = 45
lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi'
righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi'
custom_materials = list(/datum/material/iron = SHEET_MATERIAL_AMOUNT*3, /datum/material/glass =SHEET_MATERIAL_AMOUNT * 1.5)
@@ -25,6 +26,7 @@
/obj/item/retractor/cyborg
icon = 'icons/mob/silicon/robot_items.dmi'
icon_state = "toolkit_medborg_retractor"
+ icon_angle = 45
/obj/item/hemostat
name = "hemostat"
@@ -32,6 +34,7 @@
icon = 'icons/obj/medical/surgery_tools.dmi'
icon_state = "hemostat"
inhand_icon_state = "hemostat"
+ icon_angle = 135
lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi'
righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi'
custom_materials = list(/datum/material/iron =SHEET_MATERIAL_AMOUNT * 2.5, /datum/material/glass = SHEET_MATERIAL_AMOUNT*1.25)
@@ -55,6 +58,7 @@
/obj/item/hemostat/cyborg
icon = 'icons/mob/silicon/robot_items.dmi'
icon_state = "toolkit_medborg_hemostat"
+ icon_angle = 45
/obj/item/cautery
name = "cautery"
@@ -62,6 +66,7 @@
icon = 'icons/obj/medical/surgery_tools.dmi'
icon_state = "cautery"
inhand_icon_state = "cautery"
+ icon_angle = 135
lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi'
righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi'
custom_materials = list(/datum/material/iron = SHEET_MATERIAL_AMOUNT*1.25, /datum/material/glass = SMALL_MATERIAL_AMOUNT*7.5)
@@ -89,6 +94,7 @@
/obj/item/cautery/cyborg
icon = 'icons/mob/silicon/robot_items.dmi'
icon_state = "toolkit_medborg_cautery"
+ icon_angle = 45
/obj/item/cautery/advanced
name = "searing tool"
@@ -201,6 +207,7 @@
icon = 'icons/obj/medical/surgery_tools.dmi'
icon_state = "scalpel"
inhand_icon_state = "scalpel"
+ icon_angle = 180
lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi'
righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi'
obj_flags = CONDUCTS_ELECTRICITY
@@ -212,8 +219,8 @@
throw_speed = 3
throw_range = 5
custom_materials = list(/datum/material/iron = SHEET_MATERIAL_AMOUNT*2, /datum/material/glass =HALF_SHEET_MATERIAL_AMOUNT)
- attack_verb_continuous = list("attacks", "slashes", "stabs", "slices", "tears", "lacerates", "rips", "dices", "cuts")
- attack_verb_simple = list("attack", "slash", "stab", "slice", "tear", "lacerate", "rip", "dice", "cut")
+ attack_verb_continuous = list("attacks", "slashes", "slices", "tears", "lacerates", "rips", "dices", "cuts")
+ attack_verb_simple = list("attack", "slash", "slice", "tear", "lacerate", "rip", "dice", "cut")
hitsound = 'sound/items/weapons/bladeslice.ogg'
sharpness = SHARP_EDGED
tool_behaviour = TOOL_SCALPEL
@@ -222,6 +229,8 @@
bare_wound_bonus = 15
/// How this looks when placed in a surgical tray
var/surgical_tray_overlay = "scalpel_normal"
+ var/list/alt_continuous = list("stabs", "pierces", "impales")
+ var/list/alt_simple = list("stab", "pierce", "impale")
/obj/item/scalpel/Initialize(mapload)
. = ..()
@@ -232,6 +241,9 @@
)
// IRIS EDIT: Originally 8 SECONDS
AddElement(/datum/element/eyestab)
+ alt_continuous = string_list(alt_continuous)
+ alt_simple = string_list(alt_simple)
+ AddComponent(/datum/component/alternative_sharpness, SHARP_POINTY, alt_continuous, alt_simple)
/obj/item/scalpel/get_surgery_tool_overlay(tray_extended)
return surgical_tray_overlay
@@ -243,6 +255,7 @@
/obj/item/scalpel/cyborg
icon = 'icons/mob/silicon/robot_items.dmi'
icon_state = "toolkit_medborg_scalpel"
+ icon_angle = 0
/obj/item/scalpel/augment
desc = "Ultra-sharp blade attached directly to your bone for extra-accuracy."
@@ -254,6 +267,7 @@
icon = 'icons/obj/medical/surgery_tools.dmi'
icon_state = "saw"
inhand_icon_state = "saw"
+ icon_angle = 180
lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi'
righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi'
hitsound = 'sound/items/weapons/circsawhit.ogg'
@@ -298,6 +312,7 @@
/obj/item/circular_saw/cyborg
icon = 'icons/mob/silicon/robot_items.dmi'
icon_state = "toolkit_medborg_saw"
+ icon_angle = 0
/obj/item/circular_saw/augment
desc = "A small but very fast spinning saw. It rips and tears until it is done."
@@ -462,12 +477,13 @@
name = "mechanical pinches"
desc = "An agglomerate of rods and gears."
icon = 'icons/obj/medical/surgery_tools.dmi'
- custom_materials = list(/datum/material/iron = SHEET_MATERIAL_AMOUNT*6, /datum/material/glass = SHEET_MATERIAL_AMOUNT*2, /datum/material/silver = SHEET_MATERIAL_AMOUNT*2, /datum/material/titanium =SHEET_MATERIAL_AMOUNT * 2.5)
icon_state = "adv_retractor"
inhand_icon_state = "adv_retractor"
surgical_tray_overlay = "retractor_advanced"
+ icon_angle = 0
lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi'
righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi'
+ custom_materials = list(/datum/material/iron = SHEET_MATERIAL_AMOUNT*6, /datum/material/glass = SHEET_MATERIAL_AMOUNT*2, /datum/material/silver = SHEET_MATERIAL_AMOUNT*2, /datum/material/titanium =SHEET_MATERIAL_AMOUNT * 2.5)
w_class = WEIGHT_CLASS_NORMAL
toolspeed = 0.7
@@ -508,6 +524,7 @@
desc = "A type of heavy duty surgical shears used for achieving a clean separation between limb and patient. Keeping the patient still is imperative to be able to secure and align the shears."
icon = 'icons/obj/medical/surgery_tools.dmi'
icon_state = "shears"
+ icon_angle = 90
obj_flags = CONDUCTS_ELECTRICITY
item_flags = SURGICAL_TOOL
toolspeed = 1
@@ -593,6 +610,7 @@
desc = "For setting things right."
icon = 'icons/obj/medical/surgery_tools.dmi'
icon_state = "bonesetter"
+ icon_angle = 135
lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi'
righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi'
custom_materials = list(/datum/material/iron =SHEET_MATERIAL_AMOUNT * 2.5, /datum/material/glass = SHEET_MATERIAL_AMOUNT*1.25, /datum/material/silver = SHEET_MATERIAL_AMOUNT*1.25)
@@ -610,6 +628,7 @@
/obj/item/bonesetter/cyborg
icon = 'icons/mob/silicon/robot_items.dmi'
icon_state = "toolkit_medborg_bonesetter"
+ icon_angle = 45
/obj/item/blood_filter
name = "blood filter"
diff --git a/code/modules/tgs/v5/undefs.dm b/code/modules/tgs/v5/undefs.dm
index acd19dfa6411..ca49e46cdffa 100644
--- a/code/modules/tgs/v5/undefs.dm
+++ b/code/modules/tgs/v5/undefs.dm
@@ -18,6 +18,7 @@
#undef DMAPI5_PARAMETER_ACCESS_IDENTIFIER
#undef DMAPI5_PARAMETER_CUSTOM_COMMANDS
+#undef DMAPI5_PARAMETER_TOPIC_PORT
#undef DMAPI5_CHUNK
#undef DMAPI5_CHUNK_PAYLOAD
diff --git a/code/modules/tgui_panel/external.dm b/code/modules/tgui_panel/external.dm
index 3e7dbc3178f2..e48ddeb22390 100644
--- a/code/modules/tgui_panel/external.dm
+++ b/code/modules/tgui_panel/external.dm
@@ -19,22 +19,19 @@
// Failed to fix, using tgalert as fallback
action = tgalert(src, "Did that work?", "", "Yes", "No, switch to old ui")
if (action == "No, switch to old ui")
- winset(src, "output", "on-show=&is-disabled=0&is-visible=1")
- winset(src, "browseroutput", "is-disabled=1;is-visible=0")
+ winset(src, "legacy_output_selector", "left=output_legacy")
log_tgui(src, "Failed to fix.", context = "verb/fix_tgui_panel")
/client/proc/nuke_chat()
// Catch all solution (kick the whole thing in the pants)
- winset(src, "output", "on-show=&is-disabled=0&is-visible=1")
- winset(src, "browseroutput", "is-disabled=1;is-visible=0")
+ winset(src, "legacy_output_selector", "left=output_legacy")
if(!tgui_panel || !istype(tgui_panel))
log_tgui(src, "tgui_panel datum is missing",
context = "verb/fix_tgui_panel")
tgui_panel = new(src)
tgui_panel.initialize(force = TRUE)
// Force show the panel to see if there are any errors
- winset(src, "output", "is-disabled=1&is-visible=0")
- winset(src, "browseroutput", "is-disabled=0;is-visible=1")
+ winset(src, "legacy_output_selector", "left=output_browser")
/client/verb/refresh_tgui()
set name = "Refresh TGUI"
diff --git a/code/modules/tgui_panel/tgui_panel.dm b/code/modules/tgui_panel/tgui_panel.dm
index a680056b0e5e..a54f56ead0c0 100644
--- a/code/modules/tgui_panel/tgui_panel.dm
+++ b/code/modules/tgui_panel/tgui_panel.dm
@@ -61,7 +61,7 @@
*/
/datum/tgui_panel/proc/on_initialize_timed_out()
// Currently does nothing but sending a message to old chat.
- SEND_TEXT(client, span_userdanger("Failed to load fancy chat, click HERE to attempt to reload it."))
+ SEND_TEXT(client, span_userdanger("Failed to load fancy chat, click HERE to attempt to reload it."))
/**
* private
diff --git a/code/modules/transport/elevator/elev_panel.dm b/code/modules/transport/elevator/elev_panel.dm
index 659049a7448c..162c70f390ce 100644
--- a/code/modules/transport/elevator/elev_panel.dm
+++ b/code/modules/transport/elevator/elev_panel.dm
@@ -19,6 +19,7 @@
icon_state = "elevpanel0"
base_icon_state = "elevpanel"
+ mouse_over_pointer = MOUSE_HAND_POINTER
power_channel = AREA_USAGE_ENVIRON
// Indestructible until someone wants to make these constructible, with all the chaos that implies
resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF
diff --git a/code/modules/transport/tram/tram_controller.dm b/code/modules/transport/tram/tram_controller.dm
index 37220034b664..1eeed96375f0 100644
--- a/code/modules/transport/tram/tram_controller.dm
+++ b/code/modules/transport/tram/tram_controller.dm
@@ -54,7 +54,7 @@
var/recovery_clear_count = 0
///if the tram's next stop will be the tram malfunction event sequence
- var/malf_active = FALSE
+ var/malf_active = TRANSPORT_SYSTEM_NORMAL
///fluff information of the tram, such as ongoing kill count and age
var/datum/tram_mfg_info/tram_registration
@@ -259,14 +259,16 @@
playsound(paired_cabinet, 'sound/machines/synth/synth_yes.ogg', 40, vary = FALSE, extrarange = SHORT_RANGE_SOUND_EXTRARANGE)
paired_cabinet.say("Controller reset.")
- if(malf_active)
- addtimer(CALLBACK(src, PROC_REF(announce_malf_event)), 1 SECONDS)
-
SEND_SIGNAL(src, COMSIG_TRAM_TRAVEL, idle_platform, destination_platform)
for(var/obj/structure/transport/linear/tram/transport_module as anything in transport_modules) //only thing everyone needs to know is the new location.
if(transport_module.travelling) //wee woo wee woo there was a double action queued. damn multi tile structs
return //we don't care to undo cover_locked controls, though, as that will resolve itself
+ if(malf_active == TRANSPORT_LOCAL_WARNING)
+ if(transport_module.check_for_humans())
+ throw_chance *= 1.75
+ malf_active = TRANSPORT_LOCAL_FAULT
+ addtimer(CALLBACK(src, PROC_REF(announce_malf_event)), 1 SECONDS)
transport_module.verify_transport_contents()
transport_module.glide_size_override = DELAY_TO_GLIDE_SIZE(speed_limiter)
transport_module.set_travelling(TRUE)
@@ -296,7 +298,7 @@
return PROCESS_KILL
if(!travel_remaining)
- if(!controller_operational || malf_active)
+ if(!controller_operational || malf_active == TRANSPORT_LOCAL_FAULT)
degraded_stop()
else
normal_stop()
@@ -370,10 +372,10 @@
paired_cabinet.say("Controller reset.")
log_transport("TC: [specific_transport_id] position data successfully reset. ")
speed_limiter = initial(speed_limiter)
- if(malf_active)
+ if(malf_active == TRANSPORT_LOCAL_FAULT)
set_status_code(SYSTEM_FAULT, TRUE)
addtimer(CALLBACK(src, PROC_REF(cycle_doors), CYCLE_OPEN), 2 SECONDS)
- malf_active = FALSE
+ malf_active = TRANSPORT_SYSTEM_NORMAL
throw_chance = initial(throw_chance)
playsound(paired_cabinet, 'sound/machines/buzz/buzz-sigh.ogg', 60, vary = FALSE, extrarange = SHORT_RANGE_SOUND_EXTRARANGE)
paired_cabinet.say("Controller error. Please contact your engineering department.")
@@ -417,6 +419,7 @@
* Performs a reset of the tram's position data by finding a predetermined reference landmark, then driving to it.
*/
/datum/transport_controller/linear/tram/proc/reset_position()
+ malf_active = TRANSPORT_SYSTEM_NORMAL
if(idle_platform)
if(get_turf(idle_platform) == get_turf(nav_beacon))
set_status_code(SYSTEM_FAULT, FALSE)
@@ -602,7 +605,8 @@
* Tram malfunction random event. Set comm error, requiring engineering or AI intervention.
*/
/datum/transport_controller/linear/tram/proc/start_malf_event()
- malf_active = TRUE
+ malf_active = TRANSPORT_LOCAL_WARNING
+ paired_cabinet.update_appearance()
throw_chance *= 1.25
log_transport("TC: [specific_transport_id] starting Tram Malfunction event.")
@@ -615,7 +619,8 @@
/datum/transport_controller/linear/tram/proc/end_malf_event()
if(!(malf_active))
return
- malf_active = FALSE
+ malf_active = TRANSPORT_SYSTEM_NORMAL
+ paired_cabinet.update_appearance()
throw_chance = initial(throw_chance)
log_transport("TC: [specific_transport_id] ending Tram Malfunction event.")
@@ -978,7 +983,7 @@
. += emissive_appearance(icon, "[base_icon_state]-estop", src, alpha = src.alpha)
return
- if(controller_datum.controller_status & SYSTEM_FAULT || controller_datum.malf_active)
+ if(controller_datum.controller_status & SYSTEM_FAULT || controller_datum.malf_active != TRANSPORT_SYSTEM_NORMAL)
. += mutable_appearance(icon, "[base_icon_state]-fault")
. += emissive_appearance(icon, "[base_icon_state]-fault", src, alpha = src.alpha)
return
@@ -1079,7 +1084,7 @@
"recoveryMode" = controller_datum.recovery_mode,
"currentSpeed" = controller_datum.current_speed,
"currentLoad" = controller_datum.current_load,
- "statusSF" = controller_datum.controller_status & SYSTEM_FAULT,
+ "statusSF" = controller_datum.controller_status & SYSTEM_FAULT || controller_datum.malf_active != TRANSPORT_SYSTEM_NORMAL,
"statusCE" = controller_datum.controller_status & COMM_ERROR,
"statusES" = controller_datum.controller_status & EMERGENCY_STOP,
"statusPD" = controller_datum.controller_status & PRE_DEPARTURE,
diff --git a/code/modules/transport/tram/tram_doors.dm b/code/modules/transport/tram/tram_doors.dm
index 6e1680bcb4c1..5eb3be234e3d 100644
--- a/code/modules/transport/tram/tram_doors.dm
+++ b/code/modules/transport/tram/tram_doors.dm
@@ -211,7 +211,7 @@
if(!hasPower() && density)
balloon_alert(user, "pulling emergency exit...")
if(do_after(user, 4 SECONDS, target = src))
- try_to_crowbar(src, user, TRUE)
+ try_to_crowbar(null, user, TRUE)
return TRUE
/**
diff --git a/code/modules/transport/tram/tram_structures.dm b/code/modules/transport/tram/tram_structures.dm
index 346cb5e68028..9b04bba6ce7e 100644
--- a/code/modules/transport/tram/tram_structures.dm
+++ b/code/modules/transport/tram/tram_structures.dm
@@ -474,8 +474,8 @@
canSmoothWith = null
/// Position of the spoiler
var/deployed = FALSE
- /// Malfunctioning due to tampering or emag
- var/malfunctioning = FALSE
+ /// Locked in position
+ var/locked = FALSE
/// Weakref to the tram piece we control
var/datum/weakref/tram_ref
/// The tram we're attached to
@@ -494,7 +494,7 @@
context[SCREENTIP_CONTEXT_LMB] = "repair"
if(held_item?.tool_behaviour == TOOL_WELDER && atom_integrity >= max_integrity)
- context[SCREENTIP_CONTEXT_LMB] = "[malfunctioning ? "repair" : "lock"]"
+ context[SCREENTIP_CONTEXT_LMB] = "[locked ? "repair" : "sabotage"]"
return CONTEXTUAL_SCREENTIP_SET
@@ -503,22 +503,19 @@
if(obj_flags & EMAGGED)
. += span_warning("The electronics panel is sparking occasionally. It can be reset with a [EXAMINE_HINT("multitool.")]")
- if(malfunctioning)
+ if(locked)
. += span_warning("The spoiler is [EXAMINE_HINT("welded")] in place!")
else
- . += span_notice("The spoiler can be locked in to place with a [EXAMINE_HINT("welder.")]")
+ . += span_notice("The spoiler can be locked in place with a [EXAMINE_HINT("welder.")]")
/obj/structure/tram/spoiler/proc/set_spoiler(source, controller, controller_active, controller_status, travel_direction)
SIGNAL_HANDLER
var/spoiler_direction = travel_direction
- if(obj_flags & EMAGGED && !malfunctioning)
- malfunctioning = TRUE
-
- if(malfunctioning || controller_status & COMM_ERROR)
+ if(locked || controller_status & COMM_ERROR || obj_flags & EMAGGED)
if(!deployed)
// Bring out the blades
- if(malfunctioning)
+ if(locked)
visible_message(span_danger("\the [src] locks up due to its servo overheating!"))
do_sparks(3, cardinal_only = FALSE, source = src)
deploy_spoiler()
@@ -583,14 +580,14 @@
return FALSE
if(atom_integrity >= max_integrity)
- to_chat(user, span_warning("You begin to weld \the [src], [malfunctioning ? "repairing damage" : "preventing retraction"]."))
+ to_chat(user, span_warning("You begin to weld \the [src], [locked ? "repairing damage" : "preventing retraction"]."))
if(!tool.use_tool(src, user, 4 SECONDS, volume = 50))
return
- malfunctioning = !malfunctioning
- user.visible_message(span_warning("[user] [malfunctioning ? "welds \the [src] in place" : "repairs \the [src]"] with [tool]."), \
- span_warning("You finish welding \the [src], [malfunctioning ? "locking it in place." : "it can move freely again!"]"), null, COMBAT_MESSAGE_RANGE)
+ locked = !locked
+ user.visible_message(span_warning("[user] [locked ? "welds \the [src] in place" : "repairs \the [src]"] with [tool]."), \
+ span_warning("You finish welding \the [src], [locked ? "locking it in place." : "it can move freely again!"]"), null, COMBAT_MESSAGE_RANGE)
- if(malfunctioning)
+ if(locked)
deploy_spoiler()
update_appearance()
@@ -606,7 +603,7 @@
/obj/structure/tram/spoiler/update_overlays()
. = ..()
- if(deployed && malfunctioning)
+ if(deployed && locked)
. += mutable_appearance(icon, "tram-spoiler-welded")
/obj/structure/chair/sofa/bench/tram
diff --git a/code/modules/transport/transport_module.dm b/code/modules/transport/transport_module.dm
index b0497ed3b2e9..d1384067a0d1 100644
--- a/code/modules/transport/transport_module.dm
+++ b/code/modules/transport/transport_module.dm
@@ -171,6 +171,13 @@
if(!(movable_contents.loc in locs))
remove_item_from_transport(movable_contents)
+/obj/structure/transport/linear/proc/check_for_humans()
+ for(var/atom/movable/movable_contents as anything in transport_contents)
+ if(ishuman(movable_contents))
+ return TRUE
+
+ return FALSE
+
///signal handler for COMSIG_MOVABLE_UPDATE_GLIDE_SIZE: when a movable in transport_contents changes its glide_size independently.
///adds that movable to a lazy list, movables in that list have their glide_size updated when the tram next moves
/obj/structure/transport/linear/proc/on_changed_glide_size(atom/movable/moving_contents, new_glide_size)
diff --git a/code/modules/tutorials/tutorial_skip.dm b/code/modules/tutorials/tutorial_skip.dm
index 803aebc86505..48c4a9550dbc 100644
--- a/code/modules/tutorials/tutorial_skip.dm
+++ b/code/modules/tutorials/tutorial_skip.dm
@@ -6,6 +6,7 @@
alpha = 0
mouse_opacity = MOUSE_OPACITY_OPAQUE
layer = TUTORIAL_INSTRUCTIONS_LAYER
+ mouse_over_pointer = MOUSE_HAND_POINTER
var/atom/movable/screen/tutorial_skip_text/skip_text
/atom/movable/screen/tutorial_skip/Initialize(mapload, datum/hud/hud_owner)
diff --git a/code/modules/tutorials/tutorials/switch_hands.dm b/code/modules/tutorials/tutorials/switch_hands.dm
index f1bcbbb3b711..1d8fbd72f3c9 100644
--- a/code/modules/tutorials/tutorials/switch_hands.dm
+++ b/code/modules/tutorials/tutorials/switch_hands.dm
@@ -38,7 +38,7 @@
/datum/tutorial/switch_hands/proc/create_hand_preview(initial_screen_loc)
hand_preview = animate_ui_element(
- "hand_[hand_to_watch % 2 == 0 ? "r" : "l"]",
+ "hand_[user.held_index_to_dir(hand_to_watch)]",
initial_screen_loc,
ui_hand_position(hand_to_watch),
TIME_TO_START_MOVING_HAND_ICON,
@@ -50,7 +50,7 @@
switch (stage)
if (STAGE_SHOULD_SWAP_HAND)
- var/hand_name = hand_to_watch % 2 == 0 ? "right" : "left"
+ var/hand_name = IS_RIGHT_INDEX(hand_to_watch) ? "right" : "left"
show_instruction(keybinding_message(
/datum/keybinding/mob/swap_hands,
"Press '%KEY%' to use your [hand_name] hand",
diff --git a/code/modules/unit_tests/_unit_tests.dm b/code/modules/unit_tests/_unit_tests.dm
index 5010e0cd8eb0..97298ee7287e 100644
--- a/code/modules/unit_tests/_unit_tests.dm
+++ b/code/modules/unit_tests/_unit_tests.dm
@@ -310,6 +310,7 @@
#include "unit_test.dm"
#include "verify_config_tags.dm"
#include "verify_emoji_names.dm"
+#include "washing.dm"
#include "weird_food.dm"
#include "wizard_loadout.dm"
#include "worn_icons.dm"
diff --git a/code/modules/unit_tests/fish_unit_tests.dm b/code/modules/unit_tests/fish_unit_tests.dm
index 767c1ceeaebf..7ed7851b86af 100644
--- a/code/modules/unit_tests/fish_unit_tests.dm
+++ b/code/modules/unit_tests/fish_unit_tests.dm
@@ -23,8 +23,7 @@
/datum/unit_test/fish_size_weight/Run()
var/obj/structure/table/table = allocate(/obj/structure/table)
- var/obj/item/fish/testdummy/fish = new /obj/item/fish/testdummy (table.loc)
- allocated += fish
+ var/obj/item/fish/testdummy/fish = allocate(__IMPLIED_TYPE__, table.loc)
var/datum/reagent/reagent = fish.reagents?.has_reagent(/datum/reagent/fishdummy)
TEST_ASSERT(reagent, "the test fish doesn't have the test reagent.[fish.reagents ? "" : " It doesn't even have a reagent holder."]")
var/expected_units = FISH_REAGENT_AMOUNT * fish.weight / FISH_WEIGHT_BITE_DIVISOR
@@ -42,14 +41,32 @@
allocated += content
TEST_ASSERT_EQUAL(counted_fillets, expected_num_fillets, "the test fish yielded [counted_fillets] fillets when it should have been [expected_num_fillets]")
+/// Make sure fish don't stay hungry after being fed
+/datum/unit_test/fish_feeding
+
+/datum/unit_test/fish_feeding/Run()
+ var/obj/item/fish/testdummy/hungry = allocate(__IMPLIED_TYPE__)
+ hungry.last_feeding = 0 //the fish should be hungry.
+ TEST_ASSERT(hungry.get_hunger(), "the fish doesn't seem to be hungry in the slightest")
+ var/obj/item/reagent_containers/cup/fish_feed/yummy = allocate(__IMPLIED_TYPE__)
+ hungry.feed(yummy.reagents)
+ TEST_ASSERT(!hungry.get_hunger(), "the fish is still hungry despite having been just fed")
+
+ ///Try feeding it again, but this time with the right hunger so they actually grow
+ hungry.last_feeding = world.time - (hungry.feeding_frequency * FISH_GROWTH_PEAK)
+ var/old_size = hungry.size
+ var/old_weight = hungry.weight
+ hungry.feed(yummy.reagents)
+ TEST_ASSERT(hungry.size > old_size, "the fish size didn't increase after being properly fed")
+ TEST_ASSERT(hungry.weight > old_weight, "the fish weight didn't increase after being properly fed")
+
///Checks that fish breeding works correctly.
/datum/unit_test/fish_breeding
/datum/unit_test/fish_breeding/Run()
- var/obj/item/fish/fish = allocate(/obj/item/fish/testdummy)
+ var/obj/item/fish_tank/reproduction/fish_tank = allocate(__IMPLIED_TYPE__)
///Check if the fishes can generate offsprings at all.
- var/obj/item/fish/fish_two = allocate(/obj/item/fish/testdummy/two)
- var/obj/item/fish/new_fish = fish.create_offspring(fish_two.type, fish_two)
+ var/obj/item/fish/new_fish = fish_tank.fish.try_to_reproduce()
TEST_ASSERT(new_fish, "the two test fishes couldn't generate an offspring")
var/traits_len = length(new_fish.fish_traits)
TEST_ASSERT_NOTEQUAL(traits_len, 2, "the offspring of the test fishes has both parents' traits, which are incompatible with each other")
@@ -66,6 +83,20 @@
TEST_ASSERT(cloner_jr, "The test aquarium's cloner fish didn't manage to reproduce when it should have")
TEST_ASSERT_NOTEQUAL(cloner_jr.type, aquarium.sterile.type, "The test aquarium's cloner fish mated with the sterile fish")
+/obj/item/fish_tank/reproduction
+ var/obj/item/fish/testdummy/small/fish
+ var/obj/item/fish/testdummy/small/partner
+
+/obj/item/fish_tank/reproduction/Initialize(mapload)
+ . = ..()
+ fish = new(src)
+ partner = new(src)
+
+/obj/item/fish_tank/reproduction/Destroy()
+ fish = null
+ partner = null
+ return ..()
+
///Checks that fish evolutions work correctly.
/datum/unit_test/fish_evolution
@@ -104,6 +135,10 @@
fish_id_redirect_path = /obj/item/fish/goldfish //Stops SSfishing from complaining
var/expected_num_fillets = 0 //used to know how many fillets should be gotten out of this fish
+/obj/item/fish/testdummy/small
+ // The parent type is too big to reproduce inside the more compact fish tank
+ average_size = /obj/item/fish_tank::max_total_size * 0.2
+
/obj/item/fish/testdummy/add_fillet_type()
expected_num_fillets = ..()
return expected_num_fillets
diff --git a/code/modules/unit_tests/organs.dm b/code/modules/unit_tests/organs.dm
index cf01436796db..6162bd43e5b0 100644
--- a/code/modules/unit_tests/organs.dm
+++ b/code/modules/unit_tests/organs.dm
@@ -98,3 +98,23 @@
TEST_ASSERT_EQUAL(dummy.get_organ_loss(slot_to_use), test_organ.maxHealth, \
"Mob level \"apply organ damage\" can exceed the [slot_to_use] organ's damage cap with a large maximum supplied.")
dummy.fully_heal(HEAL_ORGANS)
+
+///Allocate a human mob, give 'em a skillchip and a generic trauma, then see if it throws any error when the brain is removed.
+/datum/unit_test/chipped_traumatized_brain_removal
+
+/datum/unit_test/chipped_traumatized_brain_removal/Run()
+ var/mob/living/carbon/human/dummy/dummy = allocate(__IMPLIED_TYPE__)
+
+ //add the chip and activate it
+ var/obj/item/skillchip/basketweaving/chip = new(dummy.loc)
+ dummy.implant_skillchip(chip, force = TRUE)
+ TEST_ASSERT(chip.holding_brain, "Skillchip couldn't be implanted successfully, 'holding_brain' is null")
+ chip.try_activate_skillchip(force = TRUE)
+ TEST_ASSERT(chip.active, "Skillchip couldn't be activated")
+
+ //add a trauma
+ dummy.gain_trauma_type(BRAIN_TRAUMA_MILD)
+
+ var/obj/item/organ/brain = locate() in dummy.organs
+ brain.forceMove(dummy.loc)
+ allocated += brain
diff --git a/code/modules/unit_tests/unit_test.dm b/code/modules/unit_tests/unit_test.dm
index 749fca1f90f6..20003c25ddf1 100644
--- a/code/modules/unit_tests/unit_test.dm
+++ b/code/modules/unit_tests/unit_test.dm
@@ -254,6 +254,8 @@ GLOBAL_VAR_INIT(focused_tests, focused_tests())
//Yet more templates
/obj/machinery/restaurant_portal,
//Template type
+ /obj/machinery/power/turbine,
+ //Template type
/obj/effect/mob_spawn,
//Template type
/obj/structure/holosign/robot_seat,
diff --git a/code/modules/unit_tests/washing.dm b/code/modules/unit_tests/washing.dm
new file mode 100644
index 000000000000..c0239e9244fc
--- /dev/null
+++ b/code/modules/unit_tests/washing.dm
@@ -0,0 +1,43 @@
+/datum/unit_test/washing
+ /// Stuff we want to test that isn't cleanables, just to make sure they are getting cleaned when they should
+ var/list/cleanable_bonus_list = list(
+ /obj/effect/rune,
+ /obj/item/clothing/gloves/color/black,
+ /mob/living/carbon/human/dummy/consistent,
+ )
+
+ /// Tracks if we caught the clean signal, to know we washed successfully
+ VAR_PRIVATE/clean_sig_caught = 0
+
+/datum/unit_test/washing/Run()
+ for(var/i in subtypesof(/obj/effect/decal/cleanable) + cleanable_bonus_list)
+ var/atom/movable/to_clean = allocate(i)
+ var/mopable = HAS_TRAIT(to_clean, TRAIT_MOPABLE)
+
+ clean_sig_caught = 0
+ RegisterSignal(to_clean, COMSIG_COMPONENT_CLEAN_ACT, PROC_REF(clean_caught))
+ run_loc_floor_bottom_left.wash(CLEAN_ALL)
+ // mopables are cleaned when their turf is cleaned
+ if(mopable)
+ if(clean_sig_caught == 0)
+ TEST_FAIL("[i] was not cleaned when its turf was cleaned (cleaning only mopables)!")
+ if(clean_sig_caught > 1)
+ TEST_FAIL("[i] was cleaned more than once when its turf was cleaned (cleaning only mopables)!")
+ // non-mopables require the all_contents = TRUE flag to be cleaned
+ else
+ if(clean_sig_caught != 0)
+ TEST_FAIL("[i] was cleaned when its turf was cleaned (cleaning only mopables)!")
+ run_loc_floor_bottom_left.wash(CLEAN_ALL, TRUE)
+ if(clean_sig_caught == 0)
+ TEST_FAIL("[i] was not cleaned when its turf was cleaned (cleaning all contents)!")
+ if(clean_sig_caught > 1)
+ TEST_FAIL("[i] was cleaned more than once when its turf was cleaned (cleaning all contents)!")
+
+ if(!QDELETED(to_clean))
+ if(istype(to_clean, /obj/effect/decal/cleanable))
+ TEST_FAIL("[i] was not deleted when its turf was cleaned!")
+ qdel(to_clean)
+
+/datum/unit_test/washing/proc/clean_caught(...)
+ SIGNAL_HANDLER
+ clean_sig_caught += 1
diff --git a/code/modules/uplink/uplink_items.dm b/code/modules/uplink/uplink_items.dm
index dc8b776131e5..1782f836bea7 100644
--- a/code/modules/uplink/uplink_items.dm
+++ b/code/modules/uplink/uplink_items.dm
@@ -194,7 +194,8 @@
return
QDEL_NULL(gun_reward.pin)
- gun_reward.pin = new /obj/item/firing_pin(gun_reward)
+ var/obj/item/firing_pin/pin = new
+ pin.gun_insert(new_gun = gun_reward)
///For special overrides if an item can be bought or not.
/datum/uplink_item/proc/can_be_bought(datum/uplink_handler/source)
diff --git a/code/modules/uplink/uplink_items/ammunition.dm b/code/modules/uplink/uplink_items/ammunition.dm
index 2276485a2b7b..7000fb7e9a76 100644
--- a/code/modules/uplink/uplink_items/ammunition.dm
+++ b/code/modules/uplink/uplink_items/ammunition.dm
@@ -7,19 +7,19 @@
surplus = 40
/datum/uplink_item/ammo/toydarts
- name = "Box of Riot Darts"
- desc = "A box of 40 Donksoft riot darts, for reloading any compatible foam dart magazine. Don't forget to share!"
- item = /obj/item/ammo_box/foambox/riot
+ name = "Donksoft Riot Pistol Ammunition Case"
+ desc = "A case containing three spare magazines for the Donksoft riot pistol, along with a box of loose riot darts."
+ item = /obj/item/storage/toolbox/guncase/traitor/ammunition/donksoft
cost = 2
- surplus = 0
uplink_item_flags = SYNDIE_TRIPS_CONTRABAND
purchasable_from = ~UPLINK_SERIOUS_OPS
/datum/uplink_item/ammo/pistol
- name = "9mm Handgun Magazine"
- desc = "An additional 8-round 9mm magazine, compatible with the Makarov pistol."
- item = /obj/item/ammo_box/magazine/m9mm
- cost = 1
+ name = "9mm Magazine Case"
+ desc = "A case containing three additional 8-round 9mm magazines, compatible with the Makarov pistol, as well as \
+ a box of loose 9mm ammunition."
+ item = /obj/item/storage/toolbox/guncase/traitor/ammunition
+ cost = 2
purchasable_from = ~UPLINK_ALL_SYNDIE_OPS
uplink_item_flags = SYNDIE_TRIPS_CONTRABAND
diff --git a/code/modules/uplink/uplink_items/badass.dm b/code/modules/uplink/uplink_items/badass.dm
index 5c5e0390b504..9a346da2d535 100644
--- a/code/modules/uplink/uplink_items/badass.dm
+++ b/code/modules/uplink/uplink_items/badass.dm
@@ -106,3 +106,9 @@
Contains enough special solution to spray a single super-size seditious symbol, subjecting station staff to slippery suffering."
item = /obj/item/traitor_spraycan
cost = 1
+
+/datum/uplink_item/badass/pinpointer
+ name = "Surplus Pinpointer"
+ desc = "Provides a surplus pinpointer, left over from the previous models that were abandoned in favor of a SAAS cloud-based PDA app."
+ item = /obj/item/pinpointer/nuke/syndicate
+ cost = 2
diff --git a/code/modules/uplink/uplink_items/dangerous.dm b/code/modules/uplink/uplink_items/dangerous.dm
index 0c86d8731e00..092ec4c38478 100644
--- a/code/modules/uplink/uplink_items/dangerous.dm
+++ b/code/modules/uplink/uplink_items/dangerous.dm
@@ -7,19 +7,22 @@
category = /datum/uplink_category/dangerous
/datum/uplink_item/dangerous/foampistol
- name = "Toy Pistol with Riot Darts"
- desc = "An innocent-looking toy pistol designed to fire foam darts. Comes loaded with riot-grade \
- darts effective at incapacitating a target."
- item = /obj/item/gun/ballistic/automatic/pistol/toy/riot
- cost = 2
+ name = "Donksoft Riot Pistol Case"
+ desc = "A case containing an innocent-looking toy pistol designed to fire foam darts at higher than normal velocity. \
+ Comes loaded with riot-grade darts effective at incapacitating a target, two spare magazines and a box of loose \
+ riot darts. Perfect for nonlethal takedowns at range, as well as deniability. While not included in the kit, the \
+ pistol is compatible with suppressors, which can be purchased separately."
+ item = /obj/item/storage/toolbox/guncase/traitor/donksoft
+ cost = 6
surplus = 10
purchasable_from = ~UPLINK_SERIOUS_OPS
/datum/uplink_item/dangerous/pistol
- name = "Makarov Pistol"
- desc = "A small, easily concealable handgun that uses 9mm auto rounds in 8-round magazines and is compatible \
- with suppressors."
- item = /obj/item/gun/ballistic/automatic/pistol
+ name = "Makarov Pistol Case"
+ desc = "A weapon case containing an unknown variant of the Makarov pistol, along with two spare magazines and a box of loose 9mm ammunition. \
+ Chambered in 9mm. Perfect for frequent skirmishes with security, as well as ensuring you have enough firepower to outlast the competition. \
+ While not included in the kit, the pistol is compatible with suppressors, which can be purchased seperately."
+ item = /obj/item/storage/toolbox/guncase/traitor
cost = 7
purchasable_from = ~UPLINK_ALL_SYNDIE_OPS
diff --git a/code/modules/uplink/uplink_items/job.dm b/code/modules/uplink/uplink_items/job.dm
index a5070f838676..32855f296cb9 100644
--- a/code/modules/uplink/uplink_items/job.dm
+++ b/code/modules/uplink/uplink_items/job.dm
@@ -258,7 +258,7 @@
progression_minimum = 15 MINUTES
item = /obj/item/gun/chem
cost = 12
- restricted_roles = list(JOB_CHEMIST, JOB_CHIEF_MEDICAL_OFFICER, JOB_BOTANIST)
+ restricted_roles = list(JOB_CHEMIST, JOB_MEDICAL_DOCTOR, JOB_CHIEF_MEDICAL_OFFICER, JOB_BOTANIST)
/datum/uplink_item/role_restricted/pie_cannon
name = "Banana Cream Pie Cannon"
diff --git a/code/modules/uplink/uplink_items/stealthy_tools.dm b/code/modules/uplink/uplink_items/stealthy_tools.dm
index 000364f27be4..7268ef5efe35 100644
--- a/code/modules/uplink/uplink_items/stealthy_tools.dm
+++ b/code/modules/uplink/uplink_items/stealthy_tools.dm
@@ -104,6 +104,13 @@
cost = 1
surplus = 30
+/datum/uplink_item/stealthy_tools/forensics_spofer
+ name = "Forensics Spoofing Kit"
+ desc = "A box that contains the forensics spoofer (and instructions) which can scan and replicate fingerprints and fibers \
+ and apply them to a target object. Helpful for framing crew. Recommend buying soap with your purchase."
+ item = /obj/item/storage/box/syndie_kit/forensics_spoofer
+ cost = 5
+
/datum/uplink_item/stealthy_tools/telecomm_blackout
name = "Disable Telecomms"
desc = "When purchased, a virus will be uploaded to the telecommunication processing servers to temporarily disable themselves."
diff --git a/code/modules/vehicles/cars/speedwagon.dm b/code/modules/vehicles/cars/speedwagon.dm
index 1fa9e2dcc6c9..ee3eac8a5952 100644
--- a/code/modules/vehicles/cars/speedwagon.dm
+++ b/code/modules/vehicles/cars/speedwagon.dm
@@ -14,9 +14,11 @@
///Determines whether we throw all things away when ramming them or just mobs, varedit only
var/crash_all = FALSE
-/obj/vehicle/sealed/car/speedwagon/Initialize(mapload)
+/obj/vehicle/sealed/car/speedwagon/update_overlays()
. = ..()
- add_overlay(image(icon, "speedwagon_cover", ABOVE_MOB_LAYER))
+ var/mutable_appearance/cover_overlay = mutable_appearance(icon, "speedwagon_cover", ABOVE_MOB_LAYER, src, appearance_flags = KEEP_APART)
+ cover_overlay = color_atom_overlay(cover_overlay)
+ . += cover_overlay
/obj/vehicle/sealed/car/speedwagon/Bump(atom/bumped)
. = ..()
diff --git a/code/modules/vehicles/lavaboat.dm b/code/modules/vehicles/lavaboat.dm
index fbe130d96970..307c47ab9e24 100644
--- a/code/modules/vehicles/lavaboat.dm
+++ b/code/modules/vehicles/lavaboat.dm
@@ -23,6 +23,7 @@
icon = 'icons/mob/rideables/vehicles.dmi'
icon_state = "oar"
inhand_icon_state = "oar"
+ icon_angle = 45
lefthand_file = 'icons/mob/inhands/items/lavaland_lefthand.dmi'
righthand_file = 'icons/mob/inhands/items/lavaland_righthand.dmi'
force = 12
diff --git a/code/modules/vehicles/mecha/combat/durand.dm b/code/modules/vehicles/mecha/combat/durand.dm
index 1497c4c615de..699520694293 100644
--- a/code/modules/vehicles/mecha/combat/durand.dm
+++ b/code/modules/vehicles/mecha/combat/durand.dm
@@ -274,7 +274,7 @@ own integrity back to max. Shield is automatically dropped if we run out of powe
flick("shield_impact", src)
if(!.)
return
- if(!chassis.use_energy(. * (STANDARD_CELL_CHARGE / 15)))
+ if(!chassis.use_energy(. * (STANDARD_CELL_CHARGE / 150)))
chassis.cell?.charge = 0
for(var/O in chassis.occupants)
var/mob/living/occupant = O
diff --git a/code/modules/vehicles/mecha/combat/savannah_ivanov.dm b/code/modules/vehicles/mecha/combat/savannah_ivanov.dm
index dfcf2896b5b7..6395b39393ef 100644
--- a/code/modules/vehicles/mecha/combat/savannah_ivanov.dm
+++ b/code/modules/vehicles/mecha/combat/savannah_ivanov.dm
@@ -145,7 +145,7 @@
chassis.mecha_flags |= QUIET_STEPS|QUIET_TURNS|CANNOT_INTERACT
chassis.phasing = "flying"
chassis.movedelay = 1
- chassis.density = FALSE
+ chassis.set_density(FALSE)
chassis.layer = ABOVE_ALL_MOB_LAYER
animate(chassis, alpha = 0, time = 8, easing = QUAD_EASING|EASE_IN, flags = ANIMATION_PARALLEL)
animate(chassis, pixel_z = 400, time = 10, easing = QUAD_EASING|EASE_IN, flags = ANIMATION_PARALLEL) //Animate our rising mech (just like pods hehe)
@@ -176,7 +176,7 @@
chassis.mecha_flags &= ~(QUIET_STEPS|QUIET_TURNS|CANNOT_INTERACT)
chassis.phasing = initial(chassis.phasing)
chassis.movedelay = initial(chassis.movedelay)
- chassis.density = TRUE
+ chassis.set_density(TRUE)
chassis.layer = initial(chassis.layer)
SET_PLANE(chassis, initial(chassis.plane), landed_on)
skyfall_charge_level = 0
diff --git a/code/modules/vehicles/mecha/equipment/tools/air_tank.dm b/code/modules/vehicles/mecha/equipment/tools/air_tank.dm
index f00444ae598b..6d9765e6b058 100644
--- a/code/modules/vehicles/mecha/equipment/tools/air_tank.dm
+++ b/code/modules/vehicles/mecha/equipment/tools/air_tank.dm
@@ -79,11 +79,10 @@
var/datum/gas_mixture/tank_air = internal_tank.return_air()
var/datum/gas_mixture/cabin_air = chassis.cabin_air
var/release_pressure = internal_tank.release_pressure
- var/cabin_pressure = cabin_air.return_pressure()
- if(cabin_pressure < release_pressure)
+ if(cabin_air.return_pressure() < release_pressure)
tank_air.release_gas_to(cabin_air, release_pressure)
- if(cabin_pressure)
- cabin_air.pump_gas_to(external_air, PUMP_MAX_PRESSURE, GAS_CO2)
+ if(cabin_air.has_gas(/datum/gas/carbon_dioxide))
+ cabin_air.pump_gas_to(external_air, PUMP_MAX_PRESSURE, /datum/gas/carbon_dioxide)
/obj/item/mecha_parts/mecha_equipment/air_tank/proc/process_pump(seconds_per_tick)
if(!tank_pump_active)
diff --git a/code/modules/vehicles/mecha/equipment/tools/other_tools.dm b/code/modules/vehicles/mecha/equipment/tools/other_tools.dm
index 4d3b682b4277..15cc3e6b8cdb 100644
--- a/code/modules/vehicles/mecha/equipment/tools/other_tools.dm
+++ b/code/modules/vehicles/mecha/equipment/tools/other_tools.dm
@@ -285,7 +285,7 @@
///Maximum fuel capacity of the generator, in units
var/max_fuel = 75 * SHEET_MATERIAL_AMOUNT
///Energy recharged per second
- var/rechargerate = 0.005 * STANDARD_CELL_RATE
+ var/rechargerate = 0.05 * STANDARD_CELL_RATE
/obj/item/mecha_parts/mecha_equipment/generator/Initialize(mapload)
. = ..()
diff --git a/code/modules/vehicles/mecha/equipment/tools/work_tools.dm b/code/modules/vehicles/mecha/equipment/tools/work_tools.dm
index 75f6580d401d..f8f5a7ab9388 100644
--- a/code/modules/vehicles/mecha/equipment/tools/work_tools.dm
+++ b/code/modules/vehicles/mecha/equipment/tools/work_tools.dm
@@ -219,31 +219,32 @@
attempt_refill(usr)
return TRUE
+///Maximum range the RCD can construct at.
+#define RCD_RANGE 3
+
/obj/item/mecha_parts/mecha_equipment/rcd
name = "mounted RCD"
desc = "An exosuit-mounted Rapid Construction Device."
icon_state = "mecha_rcd"
equip_cooldown = 0 // internal RCD already handles it
energy_drain = 0 // internal RCD handles power consumption based on matter use
- range = MECHA_MELEE|MECHA_RANGED
+ range = MECHA_MELEE | MECHA_RANGED
item_flags = NO_MAT_REDEMPTION
- ///Maximum range the RCD can construct at.
- var/rcd_range = 3
+
+ ///The location the mech was when it began using the rcd
+ var/atom/initial_location = FALSE
///Whether or not to deconstruct instead.
var/deconstruct_active = FALSE
- ///The type of internal RCD this equipment uses.
- var/rcd_type = /obj/item/construction/rcd/exosuit
///The internal RCD item used by this equipment.
- var/obj/item/construction/rcd/internal_rcd
+ var/obj/item/construction/rcd/exosuit/internal_rcd
/obj/item/mecha_parts/mecha_equipment/rcd/Initialize(mapload)
. = ..()
- internal_rcd = new rcd_type(src)
- GLOB.rcd_list += src
+ internal_rcd = new(src)
/obj/item/mecha_parts/mecha_equipment/rcd/Destroy()
- GLOB.rcd_list -= src
- qdel(internal_rcd)
+ initial_location = null
+ QDEL_NULL(internal_rcd)
return ..()
/obj/item/mecha_parts/mecha_equipment/rcd/get_snowflake_data()
@@ -279,16 +280,31 @@
internal_rcd.ui_interact(driver)
return TRUE
+
+/obj/item/mecha_parts/mecha_equipment/rcd/do_after_checks(atom/target)
+ // Checks if mech moved during operation
+ if(chassis.loc != initial_location)
+ return FALSE
+
+ // Cancel build if design changes
+ if(!deconstruct_active && internal_rcd.blueprint_changed)
+ return FALSE
+
+ return ..()
+
/obj/item/mecha_parts/mecha_equipment/rcd/action(mob/source, atom/target, list/modifiers)
if(!action_checks(target))
return
- if(get_dist(chassis, target) > rcd_range)
+ // No meson action!
+ if (!(target in view(RCD_RANGE, get_turf(chassis))))
+ return
+ if(get_dist(chassis, target) > RCD_RANGE)
balloon_alert(source, "out of range!")
return
- if(!internal_rcd) // if it somehow went missing
- internal_rcd = new rcd_type(src)
- stack_trace("Exosuit-mounted RCD had no internal RCD!")
+ initial_location = chassis.loc
+
..() // do this now because the do_after can take a while
+
var/construction_mode = internal_rcd.mode
if(deconstruct_active) // deconstruct isn't in the RCD menu so switch it to deconstruct mode and set it back when it's done
internal_rcd.mode = RCD_DECONSTRUCT
@@ -296,11 +312,13 @@
internal_rcd.mode = construction_mode
return TRUE
-/obj/item/mecha_parts/mecha_equipment/rcd/attackby(obj/item/attacking_item, mob/user, params)
+/obj/item/mecha_parts/mecha_equipment/rcd/interact_with_atom(obj/item/attacking_item, mob/living/user, list/modifiers)
+ . = NONE
if(istype(attacking_item, /obj/item/rcd_upgrade))
internal_rcd.install_upgrade(attacking_item, user)
- return
- return ..()
+ return ITEM_INTERACT_SUCCESS
+
+#undef RCD_RANGE
//Dunno where else to put this so shrug
/obj/item/mecha_parts/mecha_equipment/ripleyupgrade
diff --git a/code/modules/vehicles/mecha/equipment/weapons/weapons.dm b/code/modules/vehicles/mecha/equipment/weapons/weapons.dm
index c62b2a0d9ce4..c811e94c3094 100644
--- a/code/modules/vehicles/mecha/equipment/weapons/weapons.dm
+++ b/code/modules/vehicles/mecha/equipment/weapons/weapons.dm
@@ -68,6 +68,7 @@
var/obj/projectile/projectile_obj = new projectile(get_turf(src))
projectile_obj.log_override = TRUE //we log being fired ourselves a little further down.
projectile_obj.firer = chassis
+ projectile_obj.fired_from = src // mech = firer, equipment = fired from
projectile_obj.aim_projectile(target, source, modifiers, spread)
if(isliving(source) && source.client) //dont want it to happen from syndie mecha npc mobs, they do direct fire anyways
var/mob/living/shooter = source
@@ -193,6 +194,7 @@
icon_state = "mecha_honker"
energy_drain = 200
equip_cooldown = 150
+ projectiles_per_shot = 0
range = MECHA_MELEE|MECHA_RANGED
kickback = FALSE
mech_flags = EXOSUIT_MODULE_HONK
diff --git a/code/modules/vehicles/mecha/mecha_ai_interaction.dm b/code/modules/vehicles/mecha/mecha_ai_interaction.dm
index 168b2e0ea029..f43a11903a95 100644
--- a/code/modules/vehicles/mecha/mecha_ai_interaction.dm
+++ b/code/modules/vehicles/mecha/mecha_ai_interaction.dm
@@ -9,7 +9,7 @@
to_chat(user, "[B.get_mecha_info()]")
break
//Nothing like a big, red link to make the player feel powerful!
- to_chat(user, "[span_userdanger("ASSUME DIRECT CONTROL?")] ")
+ to_chat(user, "[span_userdanger("ASSUME DIRECT CONTROL?")] ")
return
examine(user)
if(length(return_occupants()) >= max_occupants)
@@ -23,7 +23,7 @@
if(!can_control_mech)
to_chat(user, span_warning("You cannot control exosuits without AI control beacons installed."))
return
- to_chat(user, "[span_boldnotice("Take control of exosuit?")] ")
+ to_chat(user, "[span_boldnotice("Take control of exosuit?")] ")
/obj/vehicle/sealed/mecha/transfer_ai(interaction, mob/user, mob/living/silicon/ai/AI, obj/item/aicard/card)
. = ..()
@@ -66,7 +66,9 @@
return
if(AI_MECH_HACK) //Called by AIs on the mech
- AI.linked_core = new /obj/structure/ai_core/deactivated(AI.loc)
+ var/obj/structure/ai_core/deactivated/deactivated_core = new(AI.loc, FALSE, FALSE, AI)
+ AI.linked_core = deactivated_core
+ AI.linked_core.RegisterSignal(deactivated_core, COMSIG_ATOM_DESTRUCTION, TYPE_PROC_REF(/obj/structure/ai_core/deactivated, disable_doomsday)) //Protect that core! The structure goes bye-bye when we re-shunt back in so no need for cleanup.
AI.linked_core.remote_ai = AI
if(AI.can_dominate_mechs && LAZYLEN(occupants)) //Oh, I am sorry, were you using that?
to_chat(AI, span_warning("Occupants detected! Forced ejection initiated!"))
diff --git a/code/modules/vehicles/motorized_wheelchair.dm b/code/modules/vehicles/motorized_wheelchair.dm
index 6a38f65e3bf0..cc11e713e614 100644
--- a/code/modules/vehicles/motorized_wheelchair.dm
+++ b/code/modules/vehicles/motorized_wheelchair.dm
@@ -66,7 +66,7 @@
speed += servo.tier
var/chair_icon = "motorized_wheelchair[speed > delay_multiplier ? "_fast" : ""]"
if(icon_state != chair_icon)
- wheels_overlay = image(icon, chair_icon + "_overlay", ABOVE_MOB_LAYER)
+ overlay_icon = chair_icon + "_overlay"
icon_state = chair_icon
diff --git a/code/modules/vehicles/speedbike.dm b/code/modules/vehicles/speedbike.dm
index 3e375b59c2e0..c7cf18240ae4 100644
--- a/code/modules/vehicles/speedbike.dm
+++ b/code/modules/vehicles/speedbike.dm
@@ -7,7 +7,6 @@
/obj/vehicle/ridden/speedbike/Initialize(mapload)
. = ..()
- add_overlay(image(icon, cover_iconstate, ABOVE_MOB_LAYER))
AddElement(/datum/element/ridable, /datum/component/riding/vehicle/speedbike)
/obj/vehicle/ridden/speedbike/Move(newloc,move_dir)
@@ -15,6 +14,12 @@
new /obj/effect/temp_visual/dir_setting/speedbike_trail(loc,move_dir)
return ..()
+/obj/vehicle/ridden/speedbike/update_overlays()
+ . = ..()
+ var/mutable_appearance/cover_overlay = mutable_appearance(icon, cover_iconstate, ABOVE_MOB_LAYER, src, appearance_flags = KEEP_APART)
+ cover_overlay = color_atom_overlay(cover_overlay)
+ . += cover_overlay
+
/obj/vehicle/ridden/speedbike/red
icon_state = "speedbike_red"
cover_iconstate = "cover_red"
diff --git a/code/modules/vehicles/vehicle_key.dm b/code/modules/vehicles/vehicle_key.dm
index 2bcc17115b06..60b578d0962e 100644
--- a/code/modules/vehicles/vehicle_key.dm
+++ b/code/modules/vehicles/vehicle_key.dm
@@ -26,6 +26,7 @@
/obj/item/key/janitor
desc = "A keyring with a small steel key, and a pink fob reading \"Pussy Wagon\"."
icon_state = "keyjanitor"
+ icon_angle = 90
force = 2
w_class = WEIGHT_CLASS_SMALL
throwforce = 9
diff --git a/code/modules/vehicles/wheelchair.dm b/code/modules/vehicles/wheelchair.dm
index b94257bb45f2..cc6af6a2f1be 100644
--- a/code/modules/vehicles/wheelchair.dm
+++ b/code/modules/vehicles/wheelchair.dm
@@ -13,7 +13,6 @@
var/delay_multiplier = 6.7
/// This variable is used to specify which overlay icon is used for the wheelchair, ensures wheelchair can cover your legs
var/overlay_icon = "wheelchair_overlay"
- var/image/wheels_overlay
///Determines the typepath of what the object folds into
var/foldabletype = /obj/item/wheelchair
///Bell attached to the wheelchair, if we have one.
@@ -36,7 +35,6 @@
/obj/vehicle/ridden/wheelchair/Initialize(mapload)
. = ..()
make_ridable()
- wheels_overlay = image(icon, overlay_icon, ABOVE_MOB_LAYER)
ADD_TRAIT(src, TRAIT_NO_IMMOBILIZE, INNATE_TRAIT)
AddComponent(/datum/component/simple_rotation) //Since it's technically a chair I want it to have chair properties
AddElement(/datum/element/noisy_movement, volume = 75)
@@ -69,7 +67,9 @@
/obj/vehicle/ridden/wheelchair/update_overlays()
. = ..()
if(has_buckled_mobs())
- . += wheels_overlay
+ var/mutable_appearance/wheel_overlay = mutable_appearance(icon, overlay_icon, ABOVE_MOB_LAYER, src, appearance_flags = KEEP_APART)
+ wheel_overlay = color_atom_overlay(wheel_overlay)
+ . += wheel_overlay
if(bell_attached)
. += "wheelchair_bell"
diff --git a/code/modules/vending/_vending.dm b/code/modules/vending/_vending.dm
index 3bdb747879ba..093a58d711ff 100644
--- a/code/modules/vending/_vending.dm
+++ b/code/modules/vending/_vending.dm
@@ -1161,15 +1161,15 @@ GLOBAL_LIST_EMPTY(vending_machines_to_restock)
return FALSE
/obj/machinery/vending/exchange_parts(mob/user, obj/item/storage/part_replacer/replacer)
- if(!istype(replacer))
- return FALSE
- if(!component_parts || !refill_canister)
+ if(!istype(replacer) || !component_parts || !refill_canister)
return FALSE
- if(!panel_open || replacer.works_from_distance)
+ var/works_from_distance = istype(replacer, /obj/item/storage/part_replacer/bluespace)
+
+ if(!panel_open || works_from_distance)
to_chat(user, display_parts(user))
- if(!panel_open && !replacer.works_from_distance)
+ if(!panel_open && !works_from_distance)
return FALSE
var/restocked = 0
diff --git a/code/modules/vending/mail.dm b/code/modules/vending/mail.dm
new file mode 100644
index 000000000000..6bc3648b056d
--- /dev/null
+++ b/code/modules/vending/mail.dm
@@ -0,0 +1,314 @@
+#define STATE_SORTING "sorting"
+#define STATE_IDLE "idle"
+#define STATE_YES "yes"
+#define STATE_NO "no"
+#define MAIL_CAPACITY 100
+
+/obj/machinery/mailsorter
+ name = "mail sorter"
+ desc = "A large mail sorting unit. Sorting mail since 1987!"
+ icon = 'icons/obj/machines/mailsorter.dmi'
+ icon_state = "mailsorter"
+ base_icon_state = "mailsorter"
+ layer = BELOW_OBJ_LAYER
+ density = TRUE
+ max_integrity = 300
+ integrity_failure = 0.33
+ req_access = list(ACCESS_CARGO)
+ circuit = /obj/item/circuitboard/machine/mailsorter
+
+ var/light_mask = "mailsorter-light-mask"
+ var/panel_type = "panel"
+
+ /// What the machine is currently doing. Can be "sorting", "idle", "yes", "no".
+ var/currentstate = STATE_IDLE
+ /// List of all mail that's inside the mailbox.
+ var/list/mail_list = list()
+ /// The direction in which the mail will be unloaded.
+ var/output_dir = SOUTH
+ /// List of the departments to sort the mail for.
+ var/static/list/sorting_departments = list(
+ DEPARTMENT_ENGINEERING,
+ DEPARTMENT_SECURITY,
+ DEPARTMENT_MEDICAL,
+ DEPARTMENT_SCIENCE,
+ DEPARTMENT_CARGO,
+ DEPARTMENT_SERVICE,
+ DEPARTMENT_COMMAND,
+ )
+ var/static/list/choices = list(
+ "Eject" = icon('icons/hud/radial.dmi', "radial_eject"),
+ "Dump" = icon('icons/hud/radial.dmi', "mail_dump"),
+ "Sort" = icon('icons/hud/radial.dmi', "mail_sort"),
+ )
+
+/// Steps one tile in the `output_dir`. Returns `turf`.
+/obj/machinery/mailsorter/proc/get_unload_turf()
+ return get_step(src, output_dir)
+
+/// Opening the maintenance panel.
+/obj/machinery/mailsorter/screwdriver_act(mob/living/user, obj/item/tool)
+ default_deconstruction_screwdriver(user, "[base_icon_state]-off", base_icon_state, tool)
+ update_appearance(UPDATE_OVERLAYS)
+ return ITEM_INTERACT_SUCCESS
+
+/// Deconstructing the mail sorter.
+/obj/machinery/mailsorter/crowbar_act(mob/living/user, obj/item/tool)
+ default_deconstruction_crowbar(tool)
+ return ITEM_INTERACT_SUCCESS
+
+/obj/machinery/mailsorter/examine(mob/user)
+ . = ..()
+ . += span_notice("There is[length(mail_list) < 100 ? " " : " no more "]space for [length(mail_list) < 100 ? "[100 - length(mail_list)] " : ""]envelope\s inside.")
+ . += span_notice("There [length(mail_list) >= 2 ? "are" : "is"] [length(mail_list) ? length(mail_list) : "no"] envelope\s inside.")
+ if(panel_open)
+ . += span_notice("Alt-click to rotate the output direction.")
+
+/obj/machinery/mailsorter/Destroy()
+ QDEL_LIST(mail_list)
+ . = ..()
+
+/obj/machinery/mailsorter/on_deconstruction(disassembled)
+ drop_all_mail()
+ . = ..()
+
+/// Drops all enevlopes on the machine turf.
+/obj/machinery/mailsorter/proc/drop_all_mail()
+ if(!isturf(get_turf(src)))
+ QDEL_LIST(mail_list)
+ return
+ for(var/obj/item/mail in mail_list)
+ mail.forceMove(src)
+ mail_list -= mail
+
+/// Dumps all envelopes on the `unload_turf`.
+/obj/machinery/mailsorter/proc/dump_all_mail()
+ if(!isturf(get_turf(src)))
+ QDEL_LIST(mail_list)
+ return
+ var/turf/unload_turf = get_unload_turf()
+ for(var/obj/item/mail in mail_list)
+ mail.forceMove(unload_turf)
+ mail.throw_at(unload_turf, 2, 3)
+ mail_list -= mail
+
+/// Validates whether the inserted item is acceptable.
+/obj/machinery/mailsorter/proc/accept_check(obj/item/weapon)
+ var/static/list/accepted_items = list(
+ /obj/item/mail,
+ /obj/item/paper,
+ )
+ return is_type_in_list(weapon, accepted_items)
+
+/obj/machinery/mailsorter/interact(mob/user)
+ if (!allowed(user))
+ to_chat(user, span_warning("Access denied."))
+ return
+ if (currentstate != STATE_IDLE)
+ return
+ if (length(mail_list) == 0)
+ to_chat(user, span_warning("There's no mail inside!"))
+ return
+ var/choice = show_radial_menu(
+ user,
+ src,
+ choices,
+ require_near = !HAS_SILICON_ACCESS(user),
+ autopick_single_option = FALSE,
+ )
+ if (!choice)
+ return
+ switch (choice)
+ if ("Eject")
+ pick_mail(user)
+ if ("Dump")
+ playsound(src, 'sound/machines/buzz/buzz-sigh.ogg', 20, TRUE)
+ to_chat(user, span_notice("[src] dumps [length(mail_list)] envelope\s on the floor."))
+ dump_all_mail()
+ if ("Sort")
+ sort_mail(user)
+
+/// Prompts the player to select a department to sort the mail for. Returns if `null`.
+/obj/machinery/mailsorter/proc/sort_mail(mob/user)
+ var/sorting_dept = tgui_input_list(user, "Choose the department to sort mail for","Mail Sorting", sorting_departments)
+ if (!sorting_dept)
+ return
+ currentstate = STATE_SORTING
+ update_appearance(UPDATE_OVERLAYS)
+ playsound(src, 'sound/machines/mail_sort.ogg', 20, TRUE)
+ addtimer(CALLBACK(src, PROC_REF(continue_sort), user, sorting_dept), 5 SECONDS)
+
+/// Sorts the mail based on the picked department. Ejects the sorted envelopes onto the `unload_turf`.
+/obj/machinery/mailsorter/proc/continue_sort(mob/user, sorting_dept)
+ var/list/sorted_mail = list()
+ var/total_to_sort = length(mail_list)
+ var/sorted = 0
+ var/unable_to_sort = 0
+
+ for (var/obj/item/mail/some_mail in mail_list)
+ if (!some_mail.recipient_ref)
+ unable_to_sort ++
+ continue
+ var/datum/mind/some_recipient = some_mail.recipient_ref.resolve()
+ if (some_recipient)
+ var/datum/job/recipient_job = some_recipient.assigned_role
+ var/datum/job_department/primary_department = recipient_job.departments_list?[1]
+ if (primary_department == null) // permabrig is temporary, tide is forever
+ unable_to_sort ++
+ else
+ var/datum/job_department/main_department = primary_department.department_name
+ if (main_department == sorting_dept)
+ sorted_mail.Add(some_mail)
+ sorted ++
+ else
+ unable_to_sort ++
+ if (length(sorted_mail) == 0)
+ currentstate = STATE_NO
+ update_appearance(UPDATE_OVERLAYS)
+ playsound(src, 'sound/machines/buzz/buzz-sigh.ogg', 20, TRUE)
+ say("No mail for the following department: [sorting_dept].")
+ else
+ currentstate = STATE_YES
+ update_appearance(UPDATE_OVERLAYS)
+ say("[sorted] envelope\s sorted successfully.")
+ playsound(src, 'sound/machines/ping.ogg', 20, TRUE)
+ to_chat(user, span_notice("[src] ejects [length(sorted_mail)] envelope\s."))
+ var/turf/unload_turf = get_unload_turf()
+ for (var/obj/item/mail/mail_in_list in sorted_mail)
+ mail_in_list.forceMove(unload_turf)
+ sorted_mail -= mail_in_list
+ mail_list -= mail_in_list
+ addtimer(CALLBACK(src, PROC_REF(check_sorted), unable_to_sort, total_to_sort), 1 SECONDS)
+
+/// Informs the player of the amount of processed envelopes.
+/obj/machinery/mailsorter/proc/check_sorted(mob/user, unable_to_sort, total_to_sort)
+ if (unable_to_sort > 0)
+ playsound(src, 'sound/machines/buzz/buzz-sigh.ogg', 20, TRUE)
+ say("Couldn't sort [unable_to_sort] envelope\s.")
+ else
+ playsound(src, 'sound/machines/ping.ogg', 20, TRUE)
+ say("[total_to_sort] envelope\s processed.")
+ addtimer(CALLBACK(src, PROC_REF(update_state_after_sorting)), 1 SECONDS)
+
+/obj/machinery/mailsorter/proc/update_state_after_sorting()
+ currentstate = STATE_IDLE
+ update_appearance(UPDATE_OVERLAYS)
+
+/obj/machinery/mailsorter/item_interaction(mob/user, obj/item/thingy, params)
+ if (istype(thingy, /obj/item/storage/bag/mail))
+ if (length(thingy.contents) < 1)
+ to_chat(user, span_warning("The [thingy] is empty!"))
+ return
+ var/loaded = 0
+ for (var/obj/item/mail in thingy.contents)
+ if (!(mail.item_flags & ABSTRACT) && \
+ !(mail.flags_1 & HOLOGRAM_1) && \
+ accept_check(mail) \
+ )
+ if (length(mail_list) + 1 > MAIL_CAPACITY )
+ to_chat(user, span_warning("There is no space for more mail in [src]!"))
+ return FALSE
+ else if (load(mail, user))
+ loaded++
+ mail_list += mail
+ if(loaded)
+ user.visible_message(span_notice("[user] loads \the [src] with \the [thingy]."), \
+ span_notice("You load \the [src] with \the [thingy]."))
+ if(length(thingy.contents))
+ to_chat(user, span_warning("Some items are refused."))
+ return TRUE
+ else
+ to_chat(user, span_warning("There is nothing in \the [thingy] to put in the [src]!"))
+ return FALSE
+ else if (istype(thingy, /obj/item/mail))
+ if (length(mail_list) + 1 > MAIL_CAPACITY )
+ to_chat(user, span_warning("There is no space for more mail in [src]!"))
+ else
+ thingy.forceMove(src)
+ mail_list += thingy
+ to_chat(user, span_notice("The [src] whizzles as it accepts the [thingy]."))
+
+/// Prompts the user to select an anvelope from the list of all the envelopes inside.
+/obj/machinery/mailsorter/proc/pick_mail(mob/user)
+ if(!length(mail_list))
+ return
+ var/obj/item/mail/mail_throw = tgui_input_list(user, "Choose the envelope to eject","Mail Sorting", mail_list)
+ if(!mail_throw)
+ return
+ currentstate = STATE_SORTING
+ update_appearance(UPDATE_OVERLAYS)
+ playsound(src, 'sound/machines/mail_sort.ogg', 20, TRUE)
+ addtimer(CALLBACK(src, PROC_REF(pick_envelope), user, mail_throw), 50)
+
+/// Ejects a single envelope the player has picked onto the `unload_turf`.
+/obj/machinery/mailsorter/proc/pick_envelope(mob/user, obj/item/mail/mail_throw)
+ to_chat(user, span_notice("[src] reluctantly spits out [mail_throw]."))
+ var/turf/unload_turf = get_unload_turf()
+ mail_throw.forceMove(unload_turf)
+ mail_throw.throw_at(unload_turf, 2, 3)
+ mail_list -= mail_throw
+ currentstate = STATE_IDLE
+ update_appearance(UPDATE_OVERLAYS)
+
+/// Tries to load something into the machine.
+/obj/machinery/mailsorter/proc/load(obj/item/thingy, mob/user)
+ if(ismob(thingy.loc))
+ var/mob/owner = thingy.loc
+ if(!owner.transferItemToLoc(thingy, src))
+ to_chat(owner, span_warning("\the [thingy] is stuck to your hand, you cannot put it in \the [src]!"))
+ return FALSE
+ return TRUE
+ else
+ if(thingy.loc.atom_storage)
+ return thingy.loc.atom_storage.attempt_remove(thingy, src, silent = TRUE)
+ else
+ thingy.forceMove(src)
+ return TRUE
+
+/obj/machinery/mailsorter/click_alt(mob/living/user)
+ if(!panel_open)
+ return CLICK_ACTION_BLOCKING
+ output_dir = turn(output_dir, -90)
+ to_chat(user, span_notice("You change [src]'s I/O settings, setting the output to [dir2text(output_dir)]."))
+ update_appearance(UPDATE_OVERLAYS)
+ return CLICK_ACTION_SUCCESS
+
+
+/obj/machinery/mailsorter/update_overlays()
+ . = ..()
+ if(!powered())
+ return
+ if(!(machine_stat & BROKEN))
+ var/image/mail_output = image(icon='icons/obj/doors/airlocks/station/overlays.dmi', icon_state="unres_[output_dir]")
+ switch(output_dir)
+ if(NORTH)
+ mail_output.pixel_y = 32
+ if(SOUTH)
+ mail_output.pixel_y = -32
+ if(EAST)
+ mail_output.pixel_x = 32
+ if(WEST)
+ mail_output.pixel_x = -32
+ mail_output.color = COLOR_CRAYON_ORANGE
+ var/mutable_appearance/light_out = emissive_appearance(mail_output.icon, mail_output.icon_state, offset_spokesman = src, alpha = mail_output.alpha)
+ light_out.pixel_y = mail_output.pixel_y
+ light_out.pixel_x = mail_output.pixel_x
+ . += mail_output
+ . += light_out
+ . += mutable_appearance(base_icon_state, currentstate)
+ if(panel_open)
+ . += panel_type
+ if(light_mask && !(machine_stat & BROKEN))
+ . += emissive_appearance(icon, light_mask, src)
+
+/obj/machinery/mailsorter/update_icon_state()
+ icon_state = "[base_icon_state][powered() ? null : "-off"]"
+ if(machine_stat & BROKEN)
+ icon_state = "[base_icon_state]-broken"
+ return ..()
+
+#undef STATE_SORTING
+#undef STATE_IDLE
+#undef STATE_YES
+#undef STATE_NO
+#undef MAIL_CAPACITY
diff --git a/code/modules/vending/wardrobes.dm b/code/modules/vending/wardrobes.dm
index 72eccce44fdc..b494e0124ca3 100644
--- a/code/modules/vending/wardrobes.dm
+++ b/code/modules/vending/wardrobes.dm
@@ -201,13 +201,14 @@ GLOBAL_VAR_INIT(roaches_deployed, FALSE)
/obj/item/storage/backpack/satchel/leather = 3,
/obj/item/storage/backpack/duffelbag = 3,
/obj/item/storage/backpack/messenger = 3,
- /obj/item/storage/bag/mail = 3,
/obj/item/radio/headset/headset_cargo = 3,
/obj/item/clothing/accessory/pocketprotector = 3,
)
premium = list(
+ /obj/item/storage/bag/mail = 3,
/obj/item/clothing/head/costume/mailman = 1,
/obj/item/clothing/under/misc/mailman = 1,
+ /obj/item/flatpack/mailsorter = 1,
/obj/item/clothing/under/rank/cargo/miner = 3,
/obj/item/clothing/under/rank/cargo/miner/lavaland = 3,
/obj/item/clothing/under/rank/cargo/bitrunner = 3,
diff --git a/code/modules/wiremod/shell/brain_computer_interface.dm b/code/modules/wiremod/shell/brain_computer_interface.dm
index b31f3ce151bc..1ade714552f7 100644
--- a/code/modules/wiremod/shell/brain_computer_interface.dm
+++ b/code/modules/wiremod/shell/brain_computer_interface.dm
@@ -208,7 +208,7 @@
SIGNAL_HANDLER
if (isobserver(mob))
- examine_text += span_notice("[source.p_They()] [source.p_have()] \a [parent] implanted in [source.p_them()].")
+ examine_text += span_notice("[source.p_They()] [source.p_have()] \a [parent] implanted in [source.p_them()].")
/obj/item/circuit_component/bci_core/Topic(href, list/href_list)
..()
diff --git a/dependencies.sh b/dependencies.sh
index d9b286e61aed..1046b72c12d7 100644
--- a/dependencies.sh
+++ b/dependencies.sh
@@ -8,7 +8,7 @@ export BYOND_MAJOR=515
export BYOND_MINOR=1637
#rust_g git tag
-export RUST_G_VERSION=3.3.0
+export RUST_G_VERSION=3.5.1
#node version
export NODE_VERSION_LTS=22.11.0
diff --git a/html/admin/search.js b/html/admin/search.js
index ded0b9284467..639a3729fe3a 100644
--- a/html/admin/search.js
+++ b/html/admin/search.js
@@ -23,11 +23,11 @@ function updateSearch(){
}
if(found == 0) row.style.display='none';
else{
- row.style.display='block';
+ row.style.display='table-row'; /* DON'T make tables with block property */
row.className = alt_style;
if(alt_style == 'alt') alt_style = 'norm';
else alt_style = 'alt';
}
}catch(err) { }
}
-}
\ No newline at end of file
+}
diff --git a/html/changelogs/AutoChangeLog-pr-4732.yml b/html/changelogs/AutoChangeLog-pr-4732.yml
new file mode 100644
index 000000000000..8b94f5b5f1ef
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-4732.yml
@@ -0,0 +1,4 @@
+author: "NeonNik2245"
+delete-after: True
+changes:
+ - qol: "Chemistry bag now can carry hypovials"
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-4734.yml b/html/changelogs/AutoChangeLog-pr-4734.yml
new file mode 100644
index 000000000000..169e2a670887
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-4734.yml
@@ -0,0 +1,4 @@
+author: "LordHookie"
+delete-after: True
+changes:
+ - bugfix: "Ammo manufacturers have corrected their labelling habits, and many objects which referred to ammo types that have not existed for several years no longer do so."
\ No newline at end of file
diff --git a/html/changelogs/archive/2025-01.yml b/html/changelogs/archive/2025-01.yml
index 67a08af7b0b9..9253f05e1be4 100644
--- a/html/changelogs/archive/2025-01.yml
+++ b/html/changelogs/archive/2025-01.yml
@@ -42,3 +42,622 @@
2025-01-10:
OrbisAnima:
- code_imp: Hopefully fixes opendream issues
+2025-01-15:
+ 00-Steven:
+ - bugfix: Photocopying no longer removes the stamp overlays from the original paper,
+ and actually copies them to the copy.
+ - spellcheck: Give alert 'examiante' > 'examine'.
+ - bugfix: Custom emotes done via the custom emote keybind default to both audible
+ and visible again.
+ - bugfix: Stamps no longer render below the paper sometimes.
+ - bugfix: Stamps no longer inherit the color of the paper they're on.
+ - bugfix: Produce, bitrunning, and mining order consoles no longer have broken icons
+ when swapping tabs.
+ - bugfix: Items in produce, bitrunning, and mining order consoles with especially
+ long names no longer make the information tooltip and item order buttons wonky.
+ - qol: The information tooltip button for items in produce, bitrunning, and mining
+ order consoles no longer moves with the item name, and instead sits next to
+ the item order buttons.
+ - bugfix: Clearing paper, like by splashing it with ethanol, actually resets its
+ icon state to the cleared version.
+ - bugfix: Lesser quiver actually has less slots as intended.
+ 00-Steven, SmArtKar:
+ - bugfix: Paper on clipboards uses its own colour rather than that of the clipboard.
+ 13spacemen:
+ - rscdel: Time Dilation no longer shows in the hub text
+ Absolucy:
+ - bugfix: Fixed graphics getting screwed up when reconnecting with BYOND 515 after
+ playing on BYOND 516.
+ - bugfix: Made the broadcast camera's sprite more consistent, it hopefully shouldn't
+ reset to the inactive sprite while recording in some weird scenarios now.
+ - rscadd: Added a notice for clients on BYOND 516 that 516 is still in beta, and
+ UI elements may not be fully compatible yet.
+ - qol: Rust heretic healing (leeching walk, rust ascension) now, so server lag shouldn't
+ fuck you over nearly as much if you're relying on the healing.
+ - code_imp: Very slightly improved the performance of code related to adding and
+ removing traits.
+ - bugfix: Fixed a runtime error related to the balloon alert from folding a paper
+ plane.
+ Absolucy, Flleeppyy:
+ - sound: Added a new, unique sound for polling!
+ Absolucy, S34NW:
+ - bugfix: Chat settings properly save on BYOND 516 now. Settings still won't carry
+ over from 515 tho, 515 and 516 settings will be separate.
+ Absolucy, ShadowLarkens, S34N:
+ - bugfix: Fixed chat rapidly flickering in BYOND 516.
+ Arturlang:
+ - bugfix: The unique AI station trait will no longer be able to choose lawsets set
+ as default in the config.
+ Autisem:
+ - refactor: Nanotrasen has introducted new upgrades into the aging station shield
+ statalites, they require a but longer to toggle on however
+ AyIong:
+ - bugfix: Admin/Debug UI's (Especially the Game Panel) now work properly on Byond
+ 516, instead of showing raw HTML
+ - qol: Now you can hide `Initialized some shit within 0s` messages, by unchecking
+ `Debug Log` checkbox into the chat tabs settings
+ - qol: AdminPMs, admin tickets, vote results and started vote notification are now
+ much more visible in the chat.
+ - qol: Boxed messages in chat (like examine), has been restyled.
+ - admin: Ticket actions buttons in chat got a little bigger
+ - bugfix: Highlighted PMs, vote results and examine will not have weird black title
+ - bugfix: Fixed scrollbar colors and background position in TGUI on Byond 516
+ Ben10Omintrix:
+ - bugfix: radial pet commanding emotes will now not appear if the command is impossible
+ to execute
+ - rscadd: adds flora-turtles. obtainable through cargo or by fishing from the hydroponics
+ tray
+ - qol: u can now directly feed animals from ur hands, like raptors or cats, by clicking
+ on them with their preferred food.
+ - balance: u can now heal ur raptors mid or post battles by hand feeding them ores
+ - bugfix: repairbots now gain their destructive abilities when hacked by an AI
+ - bugfix: repairbot crafting recipes have been updated
+ - qol: holding shift and hovering over your pet will display a list of commands
+ you can click from
+ - bugfix: fixes the fishing pet command not working
+ - bugfix: corgi's population will no longer exponentially grow
+ Cruix:
+ - rscadd: Shuttle navigation computers now show the location of airlocks, turrets,
+ and the shuttle control consoles on the ship outline while placing a custom
+ landing location.
+ DATA-xPUNGED:
+ - map: removed the sec record computer from the Icemoon Listening Post.
+ Darkened-Earth:
+ - bugfix: Alarm ranchers have wrangled up a rogue fire alarm in Meta class station
+ custom offices and relocated it to a safe habitat
+ - bugfix: NanoTrasen has shown a xenobiology containment engineer for Delta class
+ stations a very important lesson between inlets and outlets
+ Deadgebert:
+ - balance: Synthflesh required for unhusking now between 60u and 100u depending
+ on purity.
+ EnterTheJake:
+ - bugfix: temporary blocks such as blade heretic orbiting knives properly stop body
+ throws.
+ FlufflesTheDog:
+ - bugfix: welding sparks no longer break on transitioning to a different floor
+ - spellcheck: paywall firing pins no longer set the gun description to the pin's
+ description on removal
+ - qol: vending machines deconstructed during a vendor uprising are no bolted to
+ the ground
+ - bugfix: Dimensional shifter relics work more reliably.
+ Gaxeer:
+ - bugfix: modular computer laptops can now be interacted with RMB, instead of picked
+ up
+ - qol: modular computer laptops can now be interacted with RMB when open, or opened
+ with RMB when closed. Also screentips for this added
+ Ghommie:
+ - bugfix: Aquarium safe mode is now safe.
+ - bugfix: Fish are no longer still hungry after being fed.
+ - bugfix: Actually fixed alt-clicking aquariums.
+ - bugfix: Fixed feeding fish only increasing their size but not the weight.
+ - bugfix: Fixing some errors when removing brains with skillchips in them.
+ - bugfix: Fixing remote/ghost alt-click functioning on aquariums
+ - bugfix: Added missing plasma tetra to freshwater fishing spots.
+ - balance: plasma tetra is now smaller and qualifies as baitfish.
+ - bugfix: You should be once again able to fish moonfish and other fish used in
+ lizard cuisine from tiziran water turfs.
+ - bugfix: Fixed the displayed stats when examining fishing rods twice.
+ - bugfix: Fixed aquariums auto-feeding.
+ - rscadd: Added fish mounts to the game. They can be crafted with 3 sheets of wood.
+ Otherwise there's one at the bar that stores the fish between rounds.
+ Hatterhat:
+ - bugfix: .357 Heartseeker homes in on people if you click on them, instead of not
+ homing in at all.
+ - qol: You can now pull singular trophies off kinetic crushers with RMB.
+ - qol: Kinetic crushers now have screentips informing people that they can remove
+ trophies via empty hand (RMB) or crowbar (LMB).
+ - bugfix: Proto-kinetic crushers no longer cause a runtime every time you hit a
+ mob that doesn't have a mark.
+ - bugfix: Equipment pieces with upgradable armor (e.g. explorer suits) now properly
+ list what they use to upgrade.
+ - bugfix: Mobs without targetable limbs no longer have extraneous spaces appear
+ in their hit-by-projectile messages.
+ JohnFulpWillard:
+ - bugfix: Smartfridges on grid mode (like botany's) will no longer instantly spit
+ out all the items it has when you try to set how many it should spit out.
+ JoshAdamPowell:
+ - map: In the new year's budget the syndicate have decided that chemists need beakers
+ to do their job properly.
+ KazooBard:
+ - qol: Selecting which spells to cast with hotkeys, and using them in general is
+ faster.
+ Kocma-san:
+ - admin: moves all admin requests in admin stuff section. Now they have type Prayers
+ - rscadd: added a "print" button to the request manager
+ - rscadd: admin fax can now send exotic items
+ - rscadd: added "Auto-print Faxes" button to the request manager, which allows you
+ to enable automatic printing of requests on the admin fax
+ - bugfix: fixed a bug where pressing the print button would cause the receive animation
+ to appear on all admin faxes.
+ - bugfix: fix radio sound output when receiving a message
+ - sound: the sound of receiving your own messages over the radio is no longer played
+ Kylerace:
+ - bugfix: holodeck is slightly less likely to explode the server
+ LT3:
+ - bugfix: Tram spoilers correctly provide welder or multitool hints depending on
+ their damage
+ - bugfix: Malfunctioning tram controller flashes orange and can be preemptively
+ fixed before it crashes
+ - bugfix: Fixed lead acid cell having extremely high max charge
+ - bugfix: Map vote will no longer sometimes ignore the winning choice and pick a
+ cached, ineligible map
+ - bugfix: You can no longer hug yourself to overcome fear of the dark
+ - bugfix: Fixed unconstructed solar panels on Nebulastation port aft solars
+ - qol: Entertainment and newscaster broadcasts can be toggled in chat tabs
+ - bugfix: Premade/vending machine flatpacks now display what board is inside
+ LemonInTheDark:
+ - bugfix: Boulders will no longer randomly run free from smelting pipelines! We
+ have enslaved them once more.
+ LordHookie:
+ - bugfix: The Syndicate Medical Space Suit is no longer labelled a 'green space
+ suit' despite being a noticeably non-green color.
+ - bugfix: Syndicate Medical and Engineering space helmets now display themselves
+ as such.
+ - balance: The .460 Ceres AP round has had its armor piercing and damage reduced.
+ Majkl-J:
+ - bugfix: Aloe cream no longer catches fire seconds after finishing baking
+ - bugfix: Overcharged SMESes no longer spam runtime when timers chug up
+ - bugfix: Ruins will now correctly spawn their tied ruins in
+ - bugfix: The map_logging test now runs proper
+ - code_imp: The stacked_lights test now screams with area names too.
+ - bugfix: Fauna can no longer push necropolis gates
+ Melbert:
+ - rscdel: Some crowbars on Wawa, Nebula, and Birdboat are significantly less heavy
+ - balance: Lava no longer burns mobs thrown over it
+ - rscadd: Adds Syndol to the chemical kit, an addictive hallucinogen that applies
+ bonus effects when security officers, assistants, or clowns are exposed.
+ - balance: Carpenter hammer force 20 -> 17
+ - balance: Carpenter hammer throwforce 20 -> 14
+ - balance: Carpenter hammer demo mod 1.25 -> 1.15
+ - bugfix: Words in other languages will be randomized far less often (depending
+ on how commonly they are used). This bug was 10 years old.
+ - balance: Having a broken arm affects your accuracy with ranged weapons fired with
+ that arm. Utilizing a painkiller will nullify this effect, however.
+ - balance: Painkillers will prevent your punches from being cancelled due to having
+ a broken arm. You'll still take damage, though.
+ - balance: Being drunk now affects your accuracy with ranged weapon. The bartender
+ is immune to this effect via their skillchip.
+ - code_imp: A lot of code involving left and right hand handling has been cleaned
+ up, easier to read. Report any oddities, like left and rights being flipped
+ - qol: Treatment message now better reflects what you're doing ("suturing", "applying",
+ etc)
+ - bugfix: Gauze is now stickier (and will actually apply to bodyparts)
+ - bugfix: Metalgen works as a lockpick; igniting a crate metalgen'd into plasma
+ will properly drop its contents.
+ - code_imp: Hiding stuff in food should generally work more consistently now.
+ - bugfix: Fixes players not doing the "searching for item" do-after for items hidden
+ in food.
+ - balance: Tasers are now more realistic
+ - rscdel: Electrodes are no longer in the hallucination projectile pool
+ - qol: Navigate verb better indicates areas on a separate level. Selecting an area
+ on a different level will direct you to the nearest staircase (as with "Nearest
+ Way Up/Down")
+ - qol: Navigating to a staircase or ladder will shorten the cooldown of navigate
+ and clear your existing path for you upon finding it
+ - bugfix: Meshes should work better
+ - bugfix: Medical item usage is correctly blackbox logged
+ - bugfix: Double balloon alerts from medical item usage
+ - bugfix: Poultice works on dead people
+ - qol: Hovering over clickable screen elements will now update your mouse cursor
+ to indicate they're clickable
+ - qol: Hovering over small wall mounts (light switches, buttons, fire alarms) will
+ now update to mouse cursor indicating you're hovering them
+ - rscadd: Nitroglycerin now heals heart damage.
+ - qol: Cauterizing bleeding wounds now plays the cautery sfx.
+ - qol: Bleeding wounds will now go away the moment they're fully healed, rather
+ than a second or two later.
+ - qol: Suture / Mesh treatment is now uniform! meaning healing bruises with a suture
+ is now the same thing as healing cuts with a suture. This has very little difference
+ in practice, but it should generally result in a lot smoother experience.
+ - qol: 'Suture / Mesh usage has been reworked slightly, and now offers two modes
+ of use:'
+ - qol: LEFT CLICKING will heal in AUTO MODE, which will AUTOMATICALLY switch between
+ damaged bodyparts, prioritizing your targeted limb. You cannot change target
+ mid-heal; changing target simply changes your priority for your NEXT heal.
+ - qol: RIGHT CLICKING will heal in MANUAL MODE, which functions like it does currently
+ - allowing you to change your target before you finish your heal and giving
+ you a 1 second "assessment" step to change your target when you're done healing
+ a limb. Manual mode is 10% faster than Auto mode.
+ - qol: When dragging an item (like, with your mouse cursor. not physically), your
+ cursor updates when hovering humans or cyborgs to indicate you're hovering over
+ a human or cyborg.
+ - bugfix: Gibs get bulk cleaned if you clean the turf again
+ - refactor: Changed how things determine "I can be bulk cleaned if I clean the turf
+ underneath me", let me know if you notice anything not getting bulk cleaned
+ or weird things getting bulk cleaned
+ - refactor: A ton of things now use the more correct method of applying damage to
+ you. Which means they will correctly factor in damage modifiers and are less
+ likely to break your sprite. Some examples include embedded objects jostling
+ around, chiropractice, and tackling a wall. Report any oddities, such as extreme
+ damage or bodyparts being wrongly affected.
+ - bugfix: Having acid splashed on your face may now disfigure you and make you bald,
+ as it once did three years ago.
+ - bugfix: Itchy heretic trauma now better checks if the bodypart is covered or not
+ before determining if you should itch.
+ - bugfix: '"Repair Puncture" logs no longer mistakenly report you are "Incising
+ burned flesh"'
+ - bugfix: Lobby button sprites
+ MelokGleb:
+ - qol: changes Chronic Illness quirk name, description and icon to match it's dangerousness
+ Namelessfairy and SmArtKar:
+ - bugfix: The Extradimensional Blade no longer infinitely scales damage
+ - bugfix: The nullblade correctly does increased damage when sharpened
+ NecromancerAnne (code), SmArtKar (sprites):
+ - balance: Makarovs and Toy Pistols come in weapon cases. Complete with spare ammo.
+ - balance: Basic ammo for either weapon comes in weapon cases of three extra magazines
+ at an affordable price.
+ - balance: Donksoft Toy Pistols from the uplink are much stronger than their standard
+ counterparts, but now priced at 6 TC.
+ - balance: Makarovs and Toy pistols have a magazine capacity of 12 rounds.
+ - balance: Gun/Ammo cases from the traitor uplink can be destroyed by activating
+ the disposal bomb. Press Alt-Right-Click on the case to start the timer.
+ NecromancerAnne (code), orcacora (sprites):
+ - rscadd: Adds NT BR-38 Battle Rifles. A hybrid weapon. Find it in your local armory
+ and cargo catalogue today. (Keep away from EMPs)
+ OrionTheFox:
+ - image: resprited the Blood Cult Archives/Altar (the two summoning tables)
+ - image: updated the cartridge coffeemaker + coffeepot sprites, and added bluespace
+ pot overlays to coffeemakers
+ - qol: the coffeemakers can now be interacted with while unpowered! No longer will
+ your coffeepot be stuck inside the machine because it's unplugged! (Still need
+ power to brew)
+ - bugfix: fixed the Icebox Phonebooth air alarm being on the outside, thus triggering
+ because the planet is, indeed, cold. It is now inside and all-access so that
+ callers can turn it off when they decide the phone's more important than their
+ health and safety.
+ - bugfix: fixed the outdated N-Spect description falsely claiming it can scan people.
+ It can't. Nanotrasen denies all claims it ever broke sapient right to privacy
+ by giving crew full-body scanners in a handheld format.
+ Paxilmaniac:
+ - bugfix: fixes crafting menu-made rice dough being unusable for bread
+ - bugfix: Fixes resin sprayers not working if the target is more than one tile away
+ from you
+ Rhials:
+ - balance: When dominating a mech as a Malfunctioning AI, the core you shunted from
+ will disable your Doomsday Counterdown when destroyed. Make sure to protect
+ that core!
+ - rscadd: Nukie uplinks now offer an OG-style nuke pinpointer in their Badassery
+ shop section.
+ Runi-c:
+ - balance: medical doctors can buy Reagent Dartgun from traitor uplink
+ SmArtKar:
+ - qol: AI laws and tape recorders no longer cause radio blips
+ - bugfix: Fixed floodlights not being affected by spraycan painting
+ - balance: Mech-mounted RCD no longer has wallhacks
+ - balance: Reinforced walls now take double the time to get deconstructed when using
+ an RCD
+ - balance: Removed organ "refreshing" from legion cores, magic wands and regenerative
+ crossbreeds so they no longer get rid of your implants
+ - admin: Admins can now return the shuttle back to the station without ending the
+ round
+ - bugfix: Fixed a Honkerblast 5000 runtime
+ - map: Added a Condi-Master to Birdshot's bar
+ - bugfix: Fixed forcing open airlocks only working once and then never again
+ - rscadd: 'Changed how colorful reagent and crayon powder work: douse your victims
+ to color their clothing, bodyparts and even internal organs!'
+ - rscadd: You can wash your eyes when washing your face at a sink
+ - bugfix: You can color robotic limbs with left click (again)
+ - bugfix: Fixed a zero g pushoff runtime
+ - image: Scarves have received a minor update to their sprites
+ - image: Added unique sprites for Endotherm wintercoats
+ - bugfix: Fixed rolling table being able to carry an infinite amount of dwarves
+ - bugfix: Fixed an edge case with meteor moveloop code
+ - balance: You can no longer check if budget insuls work via examine tags
+ - bugfix: Fixed mech KA AOE dealing full damage in pressurized environments
+ - bugfix: Fixed c38 not misfiring when chambered in c357
+ - bugfix: Fixed moth wing stabilization working in zero-g as long as you keep moving
+ - bugfix: Mice no longer can spawn in unsafe atmos from garbage spawners
+ - bugfix: Fixes primed stingbangs being invisible
+ - bugfix: Removed rogue sand decals from Island Brawl domain walls
+ - bugfix: Fixed Hilbert's rigged analyzer not being able to scan the hotel orb
+ - bugfix: Fixed moths only being able to fly if they spawn in zero gravity
+ - refactor: Rewrote some of HUD code so they're no longer colored in their owner's
+ color
+ - bugfix: Space dragons no longer turn invisible when toggling seethrough mode
+ - qol: Iron material tiles can now be used to tile lattice to make plating
+ - rscadd: Rave and plasma stabilizer MODules now utilize theme-specific visors
+ - qol: Resist button now has visible feedback.
+ - qol: Readjusted UI layout.
+ - image: Completely redrawn Midnight and Midnight-derived UIs!
+ - qol: Jetpacks should ACTUALLY feel better now
+ - bugfix: Fixed all parried projectiles only going up or down
+ - rscadd: Added new (purely visual) animations to sharp and pointy items.
+ - rscadd: Certain items, like knives and swords, now have a secondary stabbing attack.
+ - balance: Spears are now pointy and no longer act as oversized knives.
+ - balance: Structure damage is now affected by attacking item's AP.
+ - bugfix: You will now see the same attack verb in chat as everyone else.
+ - bugfix: Fixed showers not passively washing objects
+ - bugfix: Fixed HUD implant message being reversed
+ - bugfix: Fixed shoes slot being semi-transparent when you have digitigrade legs
+ - bugfix: Fixed an airlock unlocking runtime
+ - spellcheck: Bioscrambler no longer informs you about "your armor softening the
+ blow!"
+ - image: Returned plasmafire and clockwork UI's transparency, slightly adjusted
+ active hand slot overlays
+ - bugfix: Fixed a board GPS imprintion runtime
+ - bugfix: Fixed podperson hair not updating
+ - bugfix: Chemmaster no longer rounds reagent amounts when trying to transfer a
+ custom amount
+ - bugfix: Made wendigo's bullet hell lag less, at cost of its visuals.
+ - bugfix: Fixed "Leave Body" escape menu tab runtiming
+ - bugfix: Fixed projectile homing
+ - image: Wintercoat hoods now show a bit of your hair!
+ - bugfix: Fixed chair, echair, wheelchair and vehicle overlays on painted objects
+ - bugfix: Fixed atrocinator not yeeting you up
+ - bugfix: Blood no longer gets colored with the item its attached to
+ - rscadd: Toolboxes can be used on any object to pull out and use a tool from it
+ as long as your offhand is free.
+ - qol: Jetpacks are significantly smoother and nicer to use now - and not affected
+ by lag anymore!
+ - code_imp: Cleaned up spacemove/jetpack code a bit and moved some common code to
+ helpers.
+ - refactor: Wings are now... jetpacks. They behave exactly the same and this should
+ reduce the amount of copypaste code in spacemove significantly.
+ - qol: Shifted the escape menu stat panel down a bit
+ - bugfix: Fixed smoker addictions not refreshing
+ - bugfix: Toolboxes can now be placed onto tables/into crates
+ - bugfix: Fixed toolboxes automatically using the first item in them
+ - bugfix: Fixed a qdel loop in hypnosis brain trauma
+ - bugfix: Fixed a runtime in detomatix logging
+ SmArtKar, AnturK:
+ - admin: Fixes admin observe showing all antags as revs upon enabling Real Name
+ Display
+ SmArtKar, Kapu:
+ - code_imp: Implemented caching for icon sizes which should significantly improve
+ mob health performance due to HUDs constantly fetching icons
+ SmArtKar, LemonInTheDark:
+ - rscadd: Changed how spraycans color items - "old" mode is still availible via
+ right click.
+ - refactor: Refactored how some items and effects color things so that they look
+ prettier.
+ Sparex:
+ - map: Added lifting benches/workout benches to the fitness room for IceboxStation.dmm
+ SyncIt21:
+ - bugfix: Changing gather mode on storage items won't drop it's stored items
+ - code_imp: improved code for machinery
+ - bugfix: rolling tables can be rolled up again
+ - bugfix: skull cookies are visible again
+ - code_imp: slightly improved code for borg inducer
+ - spellcheck: fixes examines & screentips for borg inducer
+ - bugfix: crowbars can be recycled in lathes only when the panel is closed
+ - bugfix: fixed runtime when sealing mecha cabin with air tank installed
+ - code_imp: condensed code for reagent grinder
+ - bugfix: reagent grinder won't break when 2 or more people are simultaneously interacting
+ with it
+ - bugfix: ejecting contents & examining the reagent grinder as an AI via the radial
+ menu does not require it to be powered or anchored
+ - bugfix: examine block for reagent grinder as an AI is properly formatted
+ - bugfix: Big manipulator respects storage limits of storages when dropping stuff
+ into them
+ - code_imp: improved code for RPED
+ - refactor: RPED attack chain has been refactored. Reports bugs on github
+ - code_imp: improved code for leaning
+ - bugfix: Fixes shattering element dropping stuff on blocked turfs
+ - refactor: improved attack chain code for rapid pipe dispenser
+ - code_imp: organized lists & global vars for rapid pipe dispenser into their own
+ respective files & improved a bunch of code
+ - bugfix: personal ordered crates can be unlocked & relocked as many times again
+ after the 1st attempt
+ - bugfix: breaking out of a closet won't spam chat or shake like it's having a seizure
+ - balance: turbine now has higher max rpm & increased power output
+ - code_imp: further improved code for turbine
+ - bugfix: Turbine converts energy to power correctly & shows correct reading with
+ multitool
+ - refactor: turbine code has been overall improved. report bugs on github
+ - code_imp: removes unused var from reagent code
+ - code_imp: plumbing reaction chamber won't waste extra ticks for the pipeline when
+ sending out catalysts
+ - bugfix: greyscale modify menu has better validation for player entered colours
+ - code_imp: improved code for mecha rcd
+ - bugfix: mecha rcd will cancel its action when the mech is rotated or moves during
+ the action
+ TealSeer:
+ - qol: Atmos devices like valves and pumps can now be renamed with a pen.
+ - qol: The time until the server reboots is now visible in the status tab.
+ - admin: Added a cancel reboot verb to the server tab.
+ TheRealSpriteMan1337:
+ - spellcheck: Fixes some usages of its, clarifies wording on the syndie medbot,
+ changes span_warning to notice.
+ Time-Green:
+ - balance: Bioscramblers are no longer immortal
+ - balance: Anomalies give 20 extra seconds to defuse! Or 20 extra seconds for them
+ to reach havoc...
+ - balance: Material anomalies only teleport 1-4 times before detonating
+ UnokiAs:
+ - rscadd: Make spears able to break open lockers in melee.
+ Wallem:
+ - image: Updates slime potion sprites, adds some new colors and rearranges some
+ others.
+ - rscadd: Rare collectors radios have been reported around Nanotrasen stations!
+ WebcomicArtist:
+ - bugfix: Durand shield now uses proper amount of power upon taking damage
+ - bugfix: Mech plasma generator now produces the correct amount of charge, previously
+ bugged to be 10% of intended.
+ Xander3359:
+ - rscadd: Bileworm crusher trophy now gives you AOE mining
+ - balance: The pk crusher no longer has click delay after shooting the projectile
+ - balance: The pk crusher gives mining XP when it mines rocks
+ - balance: the pk crusher charges faster when you mine rocks based on your skill
+ as a miner
+ - code_imp: cleaned up some of the kinetic crusher code
+ Y0SH1M4S73R:
+ - bugfix: The "Move Up" and "Move Down" verbs properly respect multi-z connectivity
+ when operating a remote camera.
+ Zenitheevee:
+ - bugfix: turrets will turn off instead of yelling endlessly when empty (stoping
+ it from infilooping)
+ araeotu:
+ - rscadd: slime processor now have usb port and circuit component
+ - code_imp: the process code of processor now separate from interact
+ carlarctg:
+ - rscadd: Adds rift fishing to the game. Includes new wacky fish!
+ - rscadd: Drained and undrained influences can be fished in, the latter only by
+ heretics. Fishing in an undrained influence shows a bobber floating over nothing
+ to other people, so don't be stupid!
+ - rscadd: influences cannot be bombed for fish.
+ - rscadd: Heretics can now infuse their fishing rod, and fish for knowledge.
+ - rscadd: Kissing while you have the ink infusion ability off cooldown gives you
+ an ink kiss
+ - rscadd: Adds suicides to fish. Like, a lot of suicides. Almost all of them very
+ unique. I'm too lazy to make a video, but they've been thoroughly tested.
+ - bugfix: Recovered crew no longer show up on roundend report
+ - rscadd: Surgery trays now have a small chance to become medical toolboxes. Autopsy
+ trays can become coroner toolboxes.
+ - rscadd: Added a 1 in 1.000.000 chance for a toolbox to have four latches.
+ distributivgesetz:
+ - code_imp: Fixed rare cases where moving an object somewhere could silently fail,
+ but still run unintended code. Report any weird issues on Github
+ grungussuss:
+ - bugfix: fixed regal rat attack logic
+ - code_imp: removed an extra proc override in digitigrade legs preference logic
+ code
+ - bugfix: fixed *me emote being called when using the *help emote
+ - image: added hat masking for berets
+ - bugfix: you will no longer slip off your mount when traversing slippery surfaces
+ - rscadd: pointing now has interactions with the amount of limbs/organs you have
+ - balance: you can now point while restrained
+ - sound: pointing with your head makes a sound
+ - bugfix: fixed being unable to remove bar seating holograms
+ - bugfix: fixed being unable to place rpeds on tables
+ - sound: added sounds for locking/unlocking closets
+ - bugfix: '*me emote works again'
+ - bugfix: fixed an error with slipping
+ - bugfix: picking stuff up actually respects the vary settings of an item
+ - bugfix: fixed items not falling from a lattice after being deconstructed/destroyed
+ - bugfix: fixed access on birdshot engi mulebot delivery window
+ - rscadd: computers and airlocks are now leanable
+ - refactor: changed how density/collision of some objects is changed, report any
+ oddities!
+ - balance: computers can now be used as cover, firing a projectile over them is
+ now possible, while they may block projectiles if you are not adjacent to them
+ when firing.
+ - bugfix: computer laptops will not block projectiles
+ - rscdel: screenshake from explosions will no longer happen when it's really far
+ and not on a station area (turf)
+ grungussuss and Sothanforax:
+ - rscadd: hiss emote
+ - sound: hissssssing sounds
+ imedial:
+ - bugfix: Map vote now cares about current player count
+ lovegreenstuff:
+ - rscadd: catalyst function for plumbing reaction chambers
+ mc-oofert:
+ - balance: A mutation in gatfruit seeds has led to a drastic alteration in the observable
+ traits of the plant, which now fires hardened peas that deal less damage, but
+ poison the target. Additionally, its poison can be, with some botanical engineering,
+ replaced with whatever you wish.
+ - balance: burglars finesse spell range increased from 4 to 6 and it may loot any
+ back storage object, caretakers refuge cooldown is only applied when exiting
+ refuge, labyrinth handbook accepts any crayon instead of a white crayon
+ - qol: you may click an id with the knock heretic id card to make it consume it
+ - rscadd: janitor modsuit space cleaner mister module
+ - bugfix: manufacturing assembling machine crafts junk shells and lizard boots properly,
+ may no longer craft anchored objects (broken check), and sends its crafted stuff
+ at once
+ - qol: you can adjust diagonal walls to be not diagonal walls with a wrench
+ - balance: changeling last resort works as a monkey or animal
+ - rscadd: forensics spoofing kit for traitors/whoever with an uplink
+ - bugfix: The Men in Grey may no longer access birdshots engineering via a certain
+ maintenance airlock
+ - bugfix: holodeck no longer explodes if the server lags while its loading a new
+ sim
+ - image: girders and reinforced girders smooth now (except cult girders, bronze
+ girders, and whatever other special type)
+ - bugfix: multitile airlock assemblies from a broken multitile airlock are the same
+ direction
+ mcbalaam:
+ - bugfix: The mail sorter no longer runtimes processing assistant mail
+ - qol: Now all antagonists are visible to an admin in the orbit menu!
+ - rscadd: Added the mail sorting unit - working with mail has never been simpler!
+ - rscadd: Added two flatpack pre-defined subtypes for the flatpacker and the mail
+ sorter.
+ mikederkan:
+ - bugfix: fixes a minor spelling/grammatical error in the Funeral Supply and Religious
+ Supplies crates.
+ necromanceranne:
+ - code_imp: Various mob attack procs are treated as unarmed attacks as a baseline
+ assumption, rather than melee attacks.
+ - balance: A new maint-kata has taken shape in the periphery of Spinward control.
+ Borrowing techniques from an ancient Martian martial artist, as well as an entire
+ franchise of extremely muscular acrobats and performance artists, talented assistants
+ have discovered that a swift application of a flimsy chair to the back of the
+ head can end fights more promptly than just bashing the opponent's head in with
+ their foot. That, and it looks awesome.
+ - balance: Chairs start to break apart when you hit someone with them or block an
+ attack with them.
+ - balance: Breaking a chair against a target while attacking from behind or while
+ they're staggered will cause them to get knocked down. Chairs made of more sturdy
+ materials can even cause them to become vulnerable to shove stuns.
+ - bugfix: Tackling resulting in a neutral outcome does not force you to the floor.
+ - bugfix: Getting up is now properly influenced by spinal implants.
+ norsvenska:
+ - spellcheck: Station commission plaques (the gold ones that have the date they
+ were added) have been updated, adding Nebula's, fixing Wawa's, and decommissioning
+ Northstar's.
+ - spellcheck: The Lance and Raven shuttle airlocks are now properly labelled emergency
+ airlocks, rather than emegency airlocks.
+ - spellcheck: The radio jammer now releases disruptor waves, rather than distruptor
+ waves.
+ rintherat:
+ - bugfix: Fixes alert level changing during a Delta+ scenario
+ thegrb93:
+ - bugfix: Borg lights not turning off when flashed or empd
+ - bugfix: Air alarms stuck in warning state when area alarms are cleared
+ timothymtorres:
+ - refactor: Sound has been heavily optimized and will now ignore low volume sounds
+ from far away.
+ - admin: Add debugging sound earmuffs to admin equipment inside the debug box. Wear
+ them to determine a sounds max range, distance, volume, and sound name. Highly
+ recommended to walk otherwise you will get spammed with footstep sounds.
+ - sound: Add fishtank looping sounds to cryo cells when in use. Sound is from https://freesound.org/people/DudeAwesome/sounds/386023/
+ - bugfix: Fix drink labels for alcohol bottles
+ - bugfix: Fix holoparasite using gas ability while not summoned
+ - spellcheck: Update teleporter machine desc to be accurate
+ - rscadd: Add medical human organ crate emergency medical holodeck simulation
+ - spellcheck: Fix holodeck emag message claiming to increase power
+ - bugfix: Fix kidnapping pod returning a player to unsafe location where air is
+ bad
+ - sound: Add water sound to sinks
+ - bugfix: Fix gravity not updating for mobs when teleporting, wormhole jaunters,
+ wizard spells, tile creation/destruction, mech entry/ejection and other methods.
+ - sound: Add dice rolling sound
+ - code_imp: Add better logging for ruins
+ - rscadd: Add power efficiency when stasis bed stock parts are upgraded
+ - bugfix: Fix vent clog event triggering on non-station areas
+ - bugfix: Fix gravity for areas in space near station (solars, nearspace, bomb testing,
+ etc.)
+ - bugfix: Fix holodeck computer using wrong power settings and not updating properly
+ - code_imp: Improve looping sounds to allow nested and non-associative lists
+ - bugfix: Fix custom map loading ignoring JSON values that were ignored previously.
+ (minetype, planetary, etc.)
+ tontyGH:
+ - bugfix: /datum/gas_machine_connector will abstract_move() if their parent also
+ abstract_move()s, preventing a runtime
+ - bugfix: Pubby's whiteship no longer breaks when it tries to dock
+ - bugfix: /datum/component/PostTransfer() procs that didn't have their new_parent
+ arguments have now been fixed
+ - bugfix: This means that turning into a Domain gondola shouldn't RR people anymore
+ - bugfix: Underlining your messages in loud mode shouldn't break anymore
+ - bugfix: runetext fades in correctly in bulk. signers rejoice
+ zxaber:
+ - bugfix: Mechs with crowbar-like tools can now hold adjacent firelocks open correctly.
diff --git a/html/statbrowser.css b/html/statbrowser.css
index d8c0f92b626f..d49cb3d6e266 100644
--- a/html/statbrowser.css
+++ b/html/statbrowser.css
@@ -1,3 +1,13 @@
+.light:root {
+ --scrollbar-base: #f2f2f2;
+ --scrollbar-thumb: #a7a7a7;
+}
+
+html,
+body {
+ scrollbar-color: var(--scrollbar-thumb) var(--scrollbar-base);
+}
+
body {
font-family: Verdana, Geneva, Tahoma, sans-serif;
font-size: 12px;
@@ -177,9 +187,16 @@ img {
margin-bottom: 1em;
}
-/* Dark theme colors */
+/**
+ * MARK: Dark theme colors
+ */
+.dark:root {
+ --scrollbar-base: #151515;
+ --scrollbar-thumb: #363636;
+}
+
body.dark {
- background-color: #131313;
+ background-color: #151515;
color: #b2c4dd;
scrollbar-base-color: #1c1c1c;
scrollbar-face-color: #3b3b3b;
@@ -201,7 +218,7 @@ body.dark {
}
.dark #menu {
- background-color: #131313;
+ background-color: #151515;
}
.dark #menu.tabs-classic .button.active {
diff --git a/html/statbrowser.js b/html/statbrowser.js
index 3fe115943a70..d4f5c6cd428e 100644
--- a/html/statbrowser.js
+++ b/html/statbrowser.js
@@ -375,7 +375,7 @@ function draw_mc() {
var td2 = document.createElement("td");
if (part[2]) {
var a = document.createElement("a");
- a.href = "?_src_=vars;admin_token=" + href_token + ";Vars=" + part[2];
+ a.href = "byond://?_src_=vars;admin_token=" + href_token + ";Vars=" + part[2];
a.textContent = part[1];
td2.appendChild(a);
} else {
@@ -460,7 +460,7 @@ function draw_listedturf() {
// rather than every onmousedown getting the "part" of the last entry.
return function (e) {
e.preventDefault();
- clickcatcher = "?src=" + part[1];
+ clickcatcher = "byond://?src=" + part[1];
switch (e.button) {
case 1:
clickcatcher += ";statpanel_item_click=middle"
@@ -516,7 +516,7 @@ function draw_sdql2() {
var td2 = document.createElement("td");
if (part[2]) {
var a = document.createElement("a");
- a.href = "?src=" + part[2] + ";statpanel_item_click=left";
+ a.href = "byond://?src=" + part[2] + ";statpanel_item_click=left";
a.textContent = part[1];
td2.appendChild(a);
} else {
@@ -543,12 +543,12 @@ function draw_tickets() {
var td2 = document.createElement("td");
if (part[2]) {
var a = document.createElement("a");
- a.href = "?_src_=holder;admin_token=" + href_token + ";ahelp=" + part[2] + ";ahelp_action=ticket;statpanel_item_click=left;action=ticket";
+ a.href = "byond://?_src_=holder;admin_token=" + href_token + ";ahelp=" + part[2] + ";ahelp_action=ticket;statpanel_item_click=left;action=ticket";
a.textContent = part[1];
td2.appendChild(a);
} else if (part[3]) {
var a = document.createElement("a");
- a.href = "?src=" + part[3] + ";statpanel_item_click=left";
+ a.href = "byond://?src=" + part[3] + ";statpanel_item_click=left";
a.textContent = part[1];
td2.appendChild(a);
} else {
@@ -570,7 +570,7 @@ function draw_interviews() {
manDiv.className = "interview_panel_controls"
var manLink = document.createElement("a");
manLink.textContent = "Open Interview Manager Panel";
- manLink.href = "?_src_=holder;admin_token=" + href_token + ";interview_man=1;statpanel_item_click=left";
+ manLink.href = "byond://?_src_=holder;admin_token=" + href_token + ";interview_man=1;statpanel_item_click=left";
manDiv.appendChild(manLink);
body.appendChild(manDiv);
@@ -603,7 +603,7 @@ function draw_interviews() {
var td = document.createElement("td");
var a = document.createElement("a");
a.textContent = part["status"];
- a.href = "?_src_=holder;admin_token=" + href_token + ";interview=" + part["ref"] + ";statpanel_item_click=left";
+ a.href = "byond://?_src_=holder;admin_token=" + href_token + ";interview=" + part["ref"] + ";statpanel_item_click=left";
td.appendChild(a);
tr.appendChild(td);
table.appendChild(tr);
@@ -623,7 +623,7 @@ function draw_spells(cat) {
var td2 = document.createElement("td");
if (part[3]) {
var a = document.createElement("a");
- a.href = "?src=" + part[3] + ";statpanel_item_click=left";
+ a.href = "byond://?src=" + part[3] + ";statpanel_item_click=left";
a.textContent = part[2];
td2.appendChild(a);
} else {
@@ -705,9 +705,11 @@ function draw_verbs(cat) {
function set_theme(which) {
if (which == "light") {
document.body.className = "";
+ document.documentElement.className = 'light';
set_style_sheet("browserOutput_white");
} else if (which == "dark") {
document.body.className = "dark";
+ document.documentElement.className = 'dark';
set_style_sheet("browserOutput");
}
}
diff --git a/icons/effects/beam.dmi b/icons/effects/beam.dmi
index 41bdf992bbf4..12bbd4788f0f 100644
Binary files a/icons/effects/beam.dmi and b/icons/effects/beam.dmi differ
diff --git a/icons/effects/mouse_pointers/pet_paw.dmi b/icons/effects/mouse_pointers/pet_paw.dmi
new file mode 100644
index 000000000000..a4443a689672
Binary files /dev/null and b/icons/effects/mouse_pointers/pet_paw.dmi differ
diff --git a/icons/effects/nav_computer_indicators.dmi b/icons/effects/nav_computer_indicators.dmi
new file mode 100644
index 000000000000..ff6a616c2968
Binary files /dev/null and b/icons/effects/nav_computer_indicators.dmi differ
diff --git a/icons/hud/64x16_actions.dmi b/icons/hud/64x16_actions.dmi
index 23865a80f035..6a54c8e4bb36 100644
Binary files a/icons/hud/64x16_actions.dmi and b/icons/hud/64x16_actions.dmi differ
diff --git a/icons/hud/radial.dmi b/icons/hud/radial.dmi
index f6e141ab6855..5e32a89fe5d0 100644
Binary files a/icons/hud/radial.dmi and b/icons/hud/radial.dmi differ
diff --git a/icons/hud/radial_pets.dmi b/icons/hud/radial_pets.dmi
new file mode 100644
index 000000000000..a9ce7a1d0062
Binary files /dev/null and b/icons/hud/radial_pets.dmi differ
diff --git a/icons/hud/screen_alien.dmi b/icons/hud/screen_alien.dmi
index 5f3806dc7bb5..e1f513f5b8fd 100644
Binary files a/icons/hud/screen_alien.dmi and b/icons/hud/screen_alien.dmi differ
diff --git a/icons/hud/screen_clockwork.dmi b/icons/hud/screen_clockwork.dmi
index 809dfe8f1a83..ea0e545d4e3e 100644
Binary files a/icons/hud/screen_clockwork.dmi and b/icons/hud/screen_clockwork.dmi differ
diff --git a/icons/hud/screen_detective.dmi b/icons/hud/screen_detective.dmi
index 818bbcfc78b6..0516ce6f079b 100644
Binary files a/icons/hud/screen_detective.dmi and b/icons/hud/screen_detective.dmi differ
diff --git a/icons/hud/screen_glass.dmi b/icons/hud/screen_glass.dmi
index 3cf16106cd86..f60603ef6381 100644
Binary files a/icons/hud/screen_glass.dmi and b/icons/hud/screen_glass.dmi differ
diff --git a/icons/hud/screen_midnight.dmi b/icons/hud/screen_midnight.dmi
index 5de157be030f..496982bfa9e3 100644
Binary files a/icons/hud/screen_midnight.dmi and b/icons/hud/screen_midnight.dmi differ
diff --git a/icons/hud/screen_operative.dmi b/icons/hud/screen_operative.dmi
index 5fbaa7c5d5d2..8719ef18d396 100644
Binary files a/icons/hud/screen_operative.dmi and b/icons/hud/screen_operative.dmi differ
diff --git a/icons/hud/screen_plasmafire.dmi b/icons/hud/screen_plasmafire.dmi
index d8b669c96c80..8cb1230ea503 100644
Binary files a/icons/hud/screen_plasmafire.dmi and b/icons/hud/screen_plasmafire.dmi differ
diff --git a/icons/hud/screen_retro.dmi b/icons/hud/screen_retro.dmi
index f3621569d76d..21275e5320cf 100644
Binary files a/icons/hud/screen_retro.dmi and b/icons/hud/screen_retro.dmi differ
diff --git a/icons/hud/screen_slimecore.dmi b/icons/hud/screen_slimecore.dmi
index a6160087ab88..6b1d25d2689d 100644
Binary files a/icons/hud/screen_slimecore.dmi and b/icons/hud/screen_slimecore.dmi differ
diff --git a/icons/hud/screen_trasenknox.dmi b/icons/hud/screen_trasenknox.dmi
index 4e17feb5211d..bb6f82caafb2 100644
Binary files a/icons/hud/screen_trasenknox.dmi and b/icons/hud/screen_trasenknox.dmi differ
diff --git a/icons/mob/actions/actions_silicon.dmi b/icons/mob/actions/actions_silicon.dmi
index 9c4b7ec7fbe8..b8f5caafc3fa 100644
Binary files a/icons/mob/actions/actions_silicon.dmi and b/icons/mob/actions/actions_silicon.dmi differ
diff --git a/icons/mob/clothing/back.dmi b/icons/mob/clothing/back.dmi
index a67830ac5e91..9fd317494ad8 100644
Binary files a/icons/mob/clothing/back.dmi and b/icons/mob/clothing/back.dmi differ
diff --git a/icons/mob/clothing/belt_mirror.dmi b/icons/mob/clothing/belt_mirror.dmi
index 95f7bc00ae9d..0ffdb70219cf 100644
Binary files a/icons/mob/clothing/belt_mirror.dmi and b/icons/mob/clothing/belt_mirror.dmi differ
diff --git a/icons/mob/clothing/head/winterhood.dmi b/icons/mob/clothing/head/winterhood.dmi
index ba722a5a0f28..a173364c9945 100644
Binary files a/icons/mob/clothing/head/winterhood.dmi and b/icons/mob/clothing/head/winterhood.dmi differ
diff --git a/icons/mob/clothing/neck.dmi b/icons/mob/clothing/neck.dmi
index bd57cb6eee91..cb02e72603bb 100644
Binary files a/icons/mob/clothing/neck.dmi and b/icons/mob/clothing/neck.dmi differ
diff --git a/icons/mob/clothing/suits/wintercoat.dmi b/icons/mob/clothing/suits/wintercoat.dmi
index 9bcfca4d6a3a..921e3991846f 100644
Binary files a/icons/mob/clothing/suits/wintercoat.dmi and b/icons/mob/clothing/suits/wintercoat.dmi differ
diff --git a/icons/mob/human/hair_masks.dmi b/icons/mob/human/hair_masks.dmi
index 5dbd4917a87e..bb7b55e0cc3a 100644
Binary files a/icons/mob/human/hair_masks.dmi and b/icons/mob/human/hair_masks.dmi differ
diff --git a/icons/mob/human/textures.dmi b/icons/mob/human/textures.dmi
index 4408c3e06728..78bf3a18e10d 100644
Binary files a/icons/mob/human/textures.dmi and b/icons/mob/human/textures.dmi differ
diff --git a/icons/mob/inhands/equipment/toolbox_lefthand.dmi b/icons/mob/inhands/equipment/toolbox_lefthand.dmi
index e3aca82d9e83..3dbd5ea013d4 100644
Binary files a/icons/mob/inhands/equipment/toolbox_lefthand.dmi and b/icons/mob/inhands/equipment/toolbox_lefthand.dmi differ
diff --git a/icons/mob/inhands/equipment/toolbox_righthand.dmi b/icons/mob/inhands/equipment/toolbox_righthand.dmi
index a7b538a13000..13dc226fcea3 100644
Binary files a/icons/mob/inhands/equipment/toolbox_righthand.dmi and b/icons/mob/inhands/equipment/toolbox_righthand.dmi differ
diff --git a/icons/mob/inhands/weapons/guns_lefthand.dmi b/icons/mob/inhands/weapons/guns_lefthand.dmi
index 90df2a892f98..369365131c6d 100644
Binary files a/icons/mob/inhands/weapons/guns_lefthand.dmi and b/icons/mob/inhands/weapons/guns_lefthand.dmi differ
diff --git a/icons/mob/inhands/weapons/guns_righthand.dmi b/icons/mob/inhands/weapons/guns_righthand.dmi
index eebed61656aa..fb77baa515b0 100644
Binary files a/icons/mob/inhands/weapons/guns_righthand.dmi and b/icons/mob/inhands/weapons/guns_righthand.dmi differ
diff --git a/icons/mob/simple/pets.dmi b/icons/mob/simple/pets.dmi
index b7e864238722..512a48e813b0 100644
Binary files a/icons/mob/simple/pets.dmi and b/icons/mob/simple/pets.dmi differ
diff --git a/icons/mob/simple/turtle_trees.dmi b/icons/mob/simple/turtle_trees.dmi
new file mode 100644
index 000000000000..3e5cf1d4065f
Binary files /dev/null and b/icons/mob/simple/turtle_trees.dmi differ
diff --git a/icons/obj/antags/cult/structures.dmi b/icons/obj/antags/cult/structures.dmi
index 982742e87649..e42ce6c2202f 100644
Binary files a/icons/obj/antags/cult/structures.dmi and b/icons/obj/antags/cult/structures.dmi differ
diff --git a/icons/obj/aquarium/fish.dmi b/icons/obj/aquarium/fish.dmi
index 49856cc649fe..4d18520cd915 100644
Binary files a/icons/obj/aquarium/fish.dmi and b/icons/obj/aquarium/fish.dmi differ
diff --git a/icons/obj/aquarium/rift.dmi b/icons/obj/aquarium/rift.dmi
new file mode 100644
index 000000000000..63c739fb32f9
Binary files /dev/null and b/icons/obj/aquarium/rift.dmi differ
diff --git a/icons/obj/aquarium/wide.dmi b/icons/obj/aquarium/wide.dmi
index 33c8e43950f9..02393fb3a7f7 100644
Binary files a/icons/obj/aquarium/wide.dmi and b/icons/obj/aquarium/wide.dmi differ
diff --git a/icons/obj/clothing/head/winterhood.dmi b/icons/obj/clothing/head/winterhood.dmi
index 34e0abf39bee..591f99ec313f 100644
Binary files a/icons/obj/clothing/head/winterhood.dmi and b/icons/obj/clothing/head/winterhood.dmi differ
diff --git a/icons/obj/clothing/neck.dmi b/icons/obj/clothing/neck.dmi
index ca90eb8a3291..3a1bd3d2ccff 100644
Binary files a/icons/obj/clothing/neck.dmi and b/icons/obj/clothing/neck.dmi differ
diff --git a/icons/obj/clothing/suits/wintercoat.dmi b/icons/obj/clothing/suits/wintercoat.dmi
index 377c9ef61e30..a70b4eb6cbe9 100644
Binary files a/icons/obj/clothing/suits/wintercoat.dmi and b/icons/obj/clothing/suits/wintercoat.dmi differ
diff --git a/icons/obj/devices/voice.dmi b/icons/obj/devices/voice.dmi
index 4188d7867eeb..807308a76180 100644
Binary files a/icons/obj/devices/voice.dmi and b/icons/obj/devices/voice.dmi differ
diff --git a/icons/obj/fishing.dmi b/icons/obj/fishing.dmi
index 58ab9944366d..59c40c02103c 100644
Binary files a/icons/obj/fishing.dmi and b/icons/obj/fishing.dmi differ
diff --git a/icons/obj/machines/coffeemaker.dmi b/icons/obj/machines/coffeemaker.dmi
index 13bf70c93fe6..a42c0bebde1f 100644
Binary files a/icons/obj/machines/coffeemaker.dmi and b/icons/obj/machines/coffeemaker.dmi differ
diff --git a/icons/obj/machines/engine/turbine.dmi b/icons/obj/machines/engine/turbine.dmi
index 1ae45eb2a1b1..afd9839af134 100644
Binary files a/icons/obj/machines/engine/turbine.dmi and b/icons/obj/machines/engine/turbine.dmi differ
diff --git a/icons/obj/machines/mailsorter.dmi b/icons/obj/machines/mailsorter.dmi
new file mode 100644
index 000000000000..8d09e36796f9
Binary files /dev/null and b/icons/obj/machines/mailsorter.dmi differ
diff --git a/icons/obj/machines/vending.dmi b/icons/obj/machines/vending.dmi
index 319771e4e7fb..8c39296a155f 100644
Binary files a/icons/obj/machines/vending.dmi and b/icons/obj/machines/vending.dmi differ
diff --git a/icons/obj/medical/chemical.dmi b/icons/obj/medical/chemical.dmi
index e362c5126e19..6cadd13c60e3 100644
Binary files a/icons/obj/medical/chemical.dmi and b/icons/obj/medical/chemical.dmi differ
diff --git a/icons/obj/medical/reagent_fillings.dmi b/icons/obj/medical/reagent_fillings.dmi
index 163f41641ddc..0e4bd7f53ddf 100644
Binary files a/icons/obj/medical/reagent_fillings.dmi and b/icons/obj/medical/reagent_fillings.dmi differ
diff --git a/icons/obj/smooth_structures/girder.dmi b/icons/obj/smooth_structures/girder.dmi
new file mode 100644
index 000000000000..7360273cd128
Binary files /dev/null and b/icons/obj/smooth_structures/girder.dmi differ
diff --git a/icons/obj/smooth_structures/girder.png b/icons/obj/smooth_structures/girder.png
new file mode 100644
index 000000000000..6180650c2293
Binary files /dev/null and b/icons/obj/smooth_structures/girder.png differ
diff --git a/icons/obj/smooth_structures/girder.png.toml b/icons/obj/smooth_structures/girder.png.toml
new file mode 100644
index 000000000000..781b9b2abf22
--- /dev/null
+++ b/icons/obj/smooth_structures/girder.png.toml
@@ -0,0 +1,2 @@
+output_name = "girder"
+template = "bitmask/diagonal_32x32.toml"
diff --git a/icons/obj/smooth_structures/reinforced_girder.dmi b/icons/obj/smooth_structures/reinforced_girder.dmi
new file mode 100644
index 000000000000..0e7d2f520bce
Binary files /dev/null and b/icons/obj/smooth_structures/reinforced_girder.dmi differ
diff --git a/icons/obj/smooth_structures/reinforced_girder.png b/icons/obj/smooth_structures/reinforced_girder.png
new file mode 100644
index 000000000000..fba248a74e25
Binary files /dev/null and b/icons/obj/smooth_structures/reinforced_girder.png differ
diff --git a/icons/obj/smooth_structures/reinforced_girder.png.toml b/icons/obj/smooth_structures/reinforced_girder.png.toml
new file mode 100644
index 000000000000..4e30db6adaea
--- /dev/null
+++ b/icons/obj/smooth_structures/reinforced_girder.png.toml
@@ -0,0 +1,2 @@
+output_name = "reinforced"
+template = "bitmask/diagonal_32x32.toml"
diff --git a/icons/obj/storage/case.dmi b/icons/obj/storage/case.dmi
index 94b7251f93f4..65b40a403ea2 100644
Binary files a/icons/obj/storage/case.dmi and b/icons/obj/storage/case.dmi differ
diff --git a/icons/obj/storage/toolbox.dmi b/icons/obj/storage/toolbox.dmi
index 9ca99565f317..49385d5b73cf 100644
Binary files a/icons/obj/storage/toolbox.dmi and b/icons/obj/storage/toolbox.dmi differ
diff --git a/icons/obj/wallmounts.dmi b/icons/obj/wallmounts.dmi
index e70024a9edb2..3bc4510a47f1 100644
Binary files a/icons/obj/wallmounts.dmi and b/icons/obj/wallmounts.dmi differ
diff --git a/icons/obj/weapons/grenade.dmi b/icons/obj/weapons/grenade.dmi
index c65f6d0e9fb8..628b271d423e 100644
Binary files a/icons/obj/weapons/grenade.dmi and b/icons/obj/weapons/grenade.dmi differ
diff --git a/icons/obj/weapons/guns/ammo.dmi b/icons/obj/weapons/guns/ammo.dmi
index 2dab0cb3d8d0..0f3b0620198e 100644
Binary files a/icons/obj/weapons/guns/ammo.dmi and b/icons/obj/weapons/guns/ammo.dmi differ
diff --git a/icons/obj/weapons/guns/ballistic.dmi b/icons/obj/weapons/guns/ballistic.dmi
index ef61f1d24949..6f208f4d8b1c 100644
Binary files a/icons/obj/weapons/guns/ballistic.dmi and b/icons/obj/weapons/guns/ballistic.dmi differ
diff --git a/icons/obj/weapons/guns/projectiles.dmi b/icons/obj/weapons/guns/projectiles.dmi
index d3ecd385a709..2776fbd4961a 100644
Binary files a/icons/obj/weapons/guns/projectiles.dmi and b/icons/obj/weapons/guns/projectiles.dmi differ
diff --git a/icons/obj/weapons/guns/wide_guns.dmi b/icons/obj/weapons/guns/wide_guns.dmi
index 7e18f60eeb86..a193dcc53015 100644
Binary files a/icons/obj/weapons/guns/wide_guns.dmi and b/icons/obj/weapons/guns/wide_guns.dmi differ
diff --git a/icons/turf/floors.dmi b/icons/turf/floors.dmi
index ff9d1e62e9b3..3b67fc7927e5 100644
Binary files a/icons/turf/floors.dmi and b/icons/turf/floors.dmi differ
diff --git a/icons/ui/inventory/back.png b/icons/ui/inventory/back.png
index 210045c1c669..e8226bc55e32 100644
Binary files a/icons/ui/inventory/back.png and b/icons/ui/inventory/back.png differ
diff --git a/icons/ui/inventory/belt.png b/icons/ui/inventory/belt.png
index 9bec60c7bb21..ef3e5586e476 100644
Binary files a/icons/ui/inventory/belt.png and b/icons/ui/inventory/belt.png differ
diff --git a/icons/ui/inventory/collar.png b/icons/ui/inventory/collar.png
index a7d1b5278497..2e8c875851ed 100644
Binary files a/icons/ui/inventory/collar.png and b/icons/ui/inventory/collar.png differ
diff --git a/icons/ui/inventory/ears.png b/icons/ui/inventory/ears.png
index 3c3ba3492949..19752ed10d71 100644
Binary files a/icons/ui/inventory/ears.png and b/icons/ui/inventory/ears.png differ
diff --git a/icons/ui/inventory/glasses.png b/icons/ui/inventory/glasses.png
index 3dff0cd5992d..9c8b0950abca 100644
Binary files a/icons/ui/inventory/glasses.png and b/icons/ui/inventory/glasses.png differ
diff --git a/icons/ui/inventory/gloves.png b/icons/ui/inventory/gloves.png
index 87c829eb626e..f5c9de27b8ef 100644
Binary files a/icons/ui/inventory/gloves.png and b/icons/ui/inventory/gloves.png differ
diff --git a/icons/ui/inventory/hand_l.png b/icons/ui/inventory/hand_l.png
index ab9535180d6a..8923d81b5b75 100644
Binary files a/icons/ui/inventory/hand_l.png and b/icons/ui/inventory/hand_l.png differ
diff --git a/icons/ui/inventory/hand_r.png b/icons/ui/inventory/hand_r.png
index e6b0d3cb9daf..b3b16b4e0cdf 100644
Binary files a/icons/ui/inventory/hand_r.png and b/icons/ui/inventory/hand_r.png differ
diff --git a/icons/ui/inventory/head.png b/icons/ui/inventory/head.png
index 2d1b65159bb8..650be20fe630 100644
Binary files a/icons/ui/inventory/head.png and b/icons/ui/inventory/head.png differ
diff --git a/icons/ui/inventory/id.png b/icons/ui/inventory/id.png
index 397b30ca42f8..0505f38bc19e 100644
Binary files a/icons/ui/inventory/id.png and b/icons/ui/inventory/id.png differ
diff --git a/icons/ui/inventory/mask.png b/icons/ui/inventory/mask.png
index dbaa80ec1ac7..3690e6eca76c 100644
Binary files a/icons/ui/inventory/mask.png and b/icons/ui/inventory/mask.png differ
diff --git a/icons/ui/inventory/neck.png b/icons/ui/inventory/neck.png
index 74cf1e33eafa..a6018e149c78 100644
Binary files a/icons/ui/inventory/neck.png and b/icons/ui/inventory/neck.png differ
diff --git a/icons/ui/inventory/pocket.png b/icons/ui/inventory/pocket.png
index 2e08ed41b05a..0d31594a5a55 100644
Binary files a/icons/ui/inventory/pocket.png and b/icons/ui/inventory/pocket.png differ
diff --git a/icons/ui/inventory/shoes.png b/icons/ui/inventory/shoes.png
index b1b19c633972..170f81b5daa5 100644
Binary files a/icons/ui/inventory/shoes.png and b/icons/ui/inventory/shoes.png differ
diff --git a/icons/ui/inventory/suit.png b/icons/ui/inventory/suit.png
index 71b877677feb..e3122ed47dc3 100644
Binary files a/icons/ui/inventory/suit.png and b/icons/ui/inventory/suit.png differ
diff --git a/icons/ui/inventory/suit_storage.png b/icons/ui/inventory/suit_storage.png
index 0dd12ed4f8f1..f35bd3783f7a 100644
Binary files a/icons/ui/inventory/suit_storage.png and b/icons/ui/inventory/suit_storage.png differ
diff --git a/icons/ui/inventory/uniform.png b/icons/ui/inventory/uniform.png
index 56576429e8d0..7f5951e3a0f4 100644
Binary files a/icons/ui/inventory/uniform.png and b/icons/ui/inventory/uniform.png differ
diff --git a/interface/skin.dmf b/interface/skin.dmf
index e1b9737dfb0c..6b317f6a14b3 100644
--- a/interface/skin.dmf
+++ b/interface/skin.dmf
@@ -239,7 +239,7 @@ window "infowindow"
window "outputwindow"
elem "outputwindow"
type = MAIN
- pos = 281,0
+ pos = 0,0
size = 640x480
anchor1 = -1,-1
anchor2 = -1,-1
@@ -296,15 +296,26 @@ window "outputwindow"
command = ".winset \"mebutton.is-checked=true ? input.command=\"!me \\\"\" : input.command=\"\"mebutton.is-checked=true ? saybutton.is-checked=false\"\"mebutton.is-checked=true ? oocbutton.is-checked=false\""
is-flat = true
button-type = pushbox
- elem "browseroutput"
- type = BROWSER
+ elem "legacy_output_selector"
+ type = CHILD
pos = 0,0
size = 640x456
anchor1 = 0,0
anchor2 = 100,100
- is-visible = false
- is-disabled = true
- saved-params = ""
+ saved-params = "splitter"
+ left = "output_legacy"
+ is-vert = false
+
+window "output_legacy"
+ elem "output_legacy"
+ type = MAIN
+ pos = 0,0
+ size = 640x456
+ anchor1 = -1,-1
+ anchor2 = -1,-1
+ background-color = none
+ saved-params = "pos;size;is-minimized;is-maximized"
+ is-pane = true
elem "output"
type = OUTPUT
pos = 0,0
@@ -314,6 +325,25 @@ window "outputwindow"
is-default = true
saved-params = ""
+window "output_browser"
+ elem "output_browser"
+ type = MAIN
+ pos = 0,0
+ size = 640x456
+ anchor1 = -1,-1
+ anchor2 = -1,-1
+ background-color = none
+ saved-params = "pos;size;is-minimized;is-maximized"
+ is-pane = true
+ elem "browseroutput"
+ type = BROWSER
+ pos = 0,0
+ size = 640x456
+ anchor1 = 0,0
+ anchor2 = 100,100
+ background-color = none
+ saved-params = ""
+
window "popupwindow"
elem "popupwindow"
type = MAIN
diff --git a/modular_nova/master_files/code/datums/traits/neutral.dm b/modular_nova/master_files/code/datums/traits/neutral.dm
index 836de55adc0f..d979e64d7c38 100644
--- a/modular_nova/master_files/code/datums/traits/neutral.dm
+++ b/modular_nova/master_files/code/datums/traits/neutral.dm
@@ -49,16 +49,9 @@ GLOBAL_VAR_INIT(DNR_trait_overlay, generate_DNR_trait_overlay())
/// Adds the DNR HUD element if src has TRAIT_DNR. Removes it otherwise.
/mob/living/proc/update_dnr_hud()
- var/image/dnr_holder = hud_list?[DNR_HUD]
- if(isnull(dnr_holder))
- return
-
- var/icon/temporary_icon = icon(icon, icon_state, dir)
- dnr_holder.pixel_y = temporary_icon.Height() - ICON_SIZE_Y
-
+ set_hud_image_state(DNR_HUD, "hud_dnr")
if(HAS_TRAIT(src, TRAIT_DNR))
set_hud_image_active(DNR_HUD)
- dnr_holder.icon_state = "hud_dnr"
else
set_hud_image_inactive(DNR_HUD)
diff --git a/modular_nova/master_files/code/modules/admin/admin.dm b/modular_nova/master_files/code/modules/admin/admin.dm
index 350d37ccdb89..e56f1033611c 100644
--- a/modular_nova/master_files/code/modules/admin/admin.dm
+++ b/modular_nova/master_files/code/modules/admin/admin.dm
@@ -28,9 +28,9 @@ ADMIN_VERB(toggledchat, R_ADMIN, "Toggle Dead Chat", "Toggle dis bitch.", ADMIN_
/datum/admin_help/ClosureLinks(ref_src)
. = ..()
- . += " (HANDLE)" //NOVA EDIT ADDITION
- . += " (PING MUTE)" //NOVA EDIT ADDITION
- . += " (MHELP)"
+ . += " (HANDLE)" //NOVA EDIT ADDITION
+ . += " (PING MUTE)" //NOVA EDIT ADDITION
+ . += " (MHELP)"
//Let the initiator know their ahelp is being handled
/datum/admin_help/proc/handle_issue(key_name = key_name_admin(usr))
diff --git a/modular_nova/master_files/code/modules/antagonists/traitor/objectives/kill_pet.dm b/modular_nova/master_files/code/modules/antagonists/traitor/objectives/kill_pet.dm
index f67f5d45b776..d024cce93068 100644
--- a/modular_nova/master_files/code/modules/antagonists/traitor/objectives/kill_pet.dm
+++ b/modular_nova/master_files/code/modules/antagonists/traitor/objectives/kill_pet.dm
@@ -65,11 +65,11 @@
/// How many uses does it have left?
var/charges = 1
/// Who summoned this?
- var/caller
+ var/creator
/obj/item/card/emag/one_shot/examine(mob/user)
. = ..()
- if(user == caller)
+ if(user == creator)
. += span_notice("It looks cheapo, they did say it gives just one shot...")
else
. += span_notice("It looks flimsy and identical to the \"Donk Co.\" toy.")
@@ -100,7 +100,7 @@
if(one_shot_emag)
return
one_shot_emag = new(user.drop_location())
- one_shot_emag.caller = user
+ one_shot_emag.creator = user
user.put_in_hands(one_shot_emag)
one_shot_emag.balloon_alert(user, "the card materializes in your hand")
// No penalty for losing this objective item, it is up to the traitor if this is the emag they use or another
diff --git a/modular_nova/master_files/code/modules/client/preferences.dm b/modular_nova/master_files/code/modules/client/preferences.dm
index 75fffe732a21..7ba8e822bbef 100644
--- a/modular_nova/master_files/code/modules/client/preferences.dm
+++ b/modular_nova/master_files/code/modules/client/preferences.dm
@@ -69,14 +69,14 @@
else if (SA.color_src == USE_ONE_COLOR)
shown_colors = 1
if((allow_advanced_colors || SA.always_color_customizable) && shown_colors)
- dat += "R"
- dat += "[acc_name]"
+ dat += "R"
+ dat += "[acc_name]"
if(allow_advanced_colors || SA.always_color_customizable)
if(shown_colors)
dat += " "
var/list/colorlist = mutant_bodyparts[key][MUTANT_INDEX_COLOR_LIST]
for(var/i in 1 to shown_colors)
- dat += " "
+ dat += " "
return dat
/datum/preferences/proc/reset_colors()
diff --git a/modular_nova/master_files/code/modules/client/preferences/tgui_prefs_migration.dm b/modular_nova/master_files/code/modules/client/preferences/tgui_prefs_migration.dm
index 4d259a3a15c6..8f6c683374da 100644
--- a/modular_nova/master_files/code/modules/client/preferences/tgui_prefs_migration.dm
+++ b/modular_nova/master_files/code/modules/client/preferences/tgui_prefs_migration.dm
@@ -90,7 +90,7 @@ MUTANT_SYNTH_CHASSIS, \
write_preference(preference, colors)
continue
- to_chat(parent, examine_block(span_greentext("Preference migration successful! You may safely interact with the preferences menu.")))
+ to_chat(parent, boxed_message(span_greentext("Preference migration successful! You may safely interact with the preferences menu.")))
tgui_prefs_migration = TRUE
nova_data["tgui_prefs_migration"] = tgui_prefs_migration
diff --git a/modular_nova/master_files/code/modules/client/preferences_savefile.dm b/modular_nova/master_files/code/modules/client/preferences_savefile.dm
index c0d0c94c6033..f34eeab91a08 100644
--- a/modular_nova/master_files/code/modules/client/preferences_savefile.dm
+++ b/modular_nova/master_files/code/modules/client/preferences_savefile.dm
@@ -69,7 +69,7 @@
tgui_prefs_migration = save_data["tgui_prefs_migration"]
if(!tgui_prefs_migration)
- to_chat(parent, examine_block(span_redtext("PREFERENCE MIGRATION BEGINNING FOR.\
+ to_chat(parent, boxed_message(span_redtext("PREFERENCE MIGRATION BEGINNING FOR.\
\nDO NOT INTERACT WITH YOUR PREFERENCES UNTIL THIS PROCESS HAS BEEN COMPLETED.\
\nDO NOT DISCONNECT UNTIL THIS PROCESS HAS BEEN COMPLETED.\
")))
@@ -247,7 +247,7 @@
/datum/preferences/proc/check_migration()
if(!tgui_prefs_migration)
- to_chat(parent, examine_block(span_redtext("CRITICAL FAILURE IN PREFERENCE MIGRATION, REPORT THIS IMMEDIATELY.")))
+ to_chat(parent, boxed_message(span_redtext("CRITICAL FAILURE IN PREFERENCE MIGRATION, REPORT THIS IMMEDIATELY.")))
message_admins("PREFERENCE MIGRATION: [ADMIN_LOOKUPFLW(parent)] has failed the process for migrating PREFERENCES. Check runtimes.")
diff --git a/modular_nova/master_files/code/modules/entombed_quirk/code/entombed_mod.dm b/modular_nova/master_files/code/modules/entombed_quirk/code/entombed_mod.dm
index 29933e63a7f6..76ccb58362b9 100644
--- a/modular_nova/master_files/code/modules/entombed_quirk/code/entombed_mod.dm
+++ b/modular_nova/master_files/code/modules/entombed_quirk/code/entombed_mod.dm
@@ -100,7 +100,8 @@
if (!host_suit)
//if we have no host suit, we shouldn't exist, so delete
host = null
- qdel(parent)
+ if(!QDELETED(parent))
+ qdel(parent)
return
var/obj/item/clothing/piece = parent
@@ -132,7 +133,7 @@
who.balloon_alert(who, "can't strip a fused MODsuit!")
return ..()
-/obj/item/mod/control/pre_equipped/entombed/retract(mob/user, obj/item/part)
+/obj/item/mod/control/pre_equipped/entombed/retract(mob/user, obj/item/part, instant)
if (ishuman(user))
var/mob/living/carbon/human/human_user = user
var/datum/quirk/equipping/entombed/tomb_quirk = human_user.get_quirk(/datum/quirk/equipping/entombed)
diff --git a/modular_nova/master_files/code/modules/mob/living/carbon/human/species_type/ethereal.dm b/modular_nova/master_files/code/modules/mob/living/carbon/human/species_type/ethereal.dm
index 8d8d5e7edeb9..1a1472e833dc 100644
--- a/modular_nova/master_files/code/modules/mob/living/carbon/human/species_type/ethereal.dm
+++ b/modular_nova/master_files/code/modules/mob/living/carbon/human/species_type/ethereal.dm
@@ -1,4 +1,4 @@
-/datum/species/ethereal/on_species_gain(mob/living/carbon/human/new_ethereal, datum/species/old_species, pref_load)
+/datum/species/ethereal/on_species_gain(mob/living/carbon/human/new_ethereal, datum/species/old_species, pref_load, regenerate_icons)
. = ..()
var/datum/action/sing_tones/sing_action = new
sing_action.Grant(new_ethereal)
diff --git a/modular_nova/master_files/code/modules/mob/living/carbon/human/species_type/snail.dm b/modular_nova/master_files/code/modules/mob/living/carbon/human/species_type/snail.dm
index 7e416865a7a4..5ab48a352e98 100644
--- a/modular_nova/master_files/code/modules/mob/living/carbon/human/species_type/snail.dm
+++ b/modular_nova/master_files/code/modules/mob/living/carbon/human/species_type/snail.dm
@@ -5,7 +5,7 @@
mutantheart = /obj/item/organ/heart/snail //This gives them the shell buff where they take less damage from behind, and their heart's more durable.
exotic_blood = null
-/datum/species/snail/on_species_gain(mob/living/carbon/new_snailperson, datum/species/old_species, pref_load)
+/datum/species/snail/on_species_gain(mob/living/carbon/new_snailperson, datum/species/old_species, pref_load, regenerate_icons)
. = ..()
new_snailperson.update_icons()
diff --git a/modular_nova/master_files/code/modules/mob/living/pets/cat/fennec.dm b/modular_nova/master_files/code/modules/mob/living/pets/cat/fennec.dm
index 55bc0e7526ea..4e46b31d57e1 100644
--- a/modular_nova/master_files/code/modules/mob/living/pets/cat/fennec.dm
+++ b/modular_nova/master_files/code/modules/mob/living/pets/cat/fennec.dm
@@ -57,5 +57,5 @@
AddComponent(\
/datum/component/breed,\
can_breed_with = typecacheof(list(/mob/living/basic/pet/cat/fennec)),\
- baby_path = /mob/living/basic/pet/cat/fennec,\
+ baby_paths = list(/mob/living/basic/pet/cat/fennec),\
)
diff --git a/modular_nova/master_files/code/modules/mob/living/pets/dog/markus.dm b/modular_nova/master_files/code/modules/mob/living/pets/dog/markus.dm
index 5521a1bd9146..f20b9476b2d0 100644
--- a/modular_nova/master_files/code/modules/mob/living/pets/dog/markus.dm
+++ b/modular_nova/master_files/code/modules/mob/living/pets/dog/markus.dm
@@ -23,13 +23,8 @@
/mob/living/basic/pet/dog/markus/Initialize(mapload)
. = ..()
ADD_TRAIT(src, TRAIT_TRASHMAN, TRAIT_GENERIC) //The burgers in his belly protect him
- if(!can_breed)
- return
- AddComponent(\
- /datum/component/breed,\
- can_breed_with = typecacheof(list(/mob/living/basic/pet/dog/corgi)),\
- baby_path = /mob/living/basic/pet/dog/corgi/puppy,\
- ) // no mixed breed puppies sadly
+ if(can_breed)
+ add_breeding_component()
/mob/living/basic/pet/dog/markus/treat_message(message)
if(client)
@@ -40,6 +35,17 @@
. = ..()
speech.speak = markus_speak
+/mob/living/basic/pet/dog/markus/proc/add_breeding_component()
+ var/static/list/partner_paths = typecacheof(list(/mob/living/basic/pet/dog/corgi))
+ var/static/list/baby_paths = list(
+ /mob/living/basic/pet/dog/corgi/puppy = 1,
+ )
+ AddComponent(\
+ /datum/component/breed,\
+ can_breed_with = partner_paths,\
+ baby_paths = baby_paths,\
+ )
+
/datum/chemical_reaction/mark_reaction
results = list(/datum/reagent/consumable/liquidgibs = 15)
required_reagents = list(
diff --git a/modular_nova/master_files/code/modules/mob/living/simple_animal/hostile/grabbagmob.dm b/modular_nova/master_files/code/modules/mob/living/simple_animal/hostile/grabbagmob.dm
index 457ae4f53362..a83ace787c8d 100644
--- a/modular_nova/master_files/code/modules/mob/living/simple_animal/hostile/grabbagmob.dm
+++ b/modular_nova/master_files/code/modules/mob/living/simple_animal/hostile/grabbagmob.dm
@@ -912,4 +912,6 @@
loot = list(/obj/effect/spawner/random/engineering/material_rare = 4)
projectiletype = /obj/projectile/beam/laser
projectilesound = 'sound/items/weapons/laser3.ogg'
+ minbodytemp = 0
+ atmos_requirements = null
diff --git a/modular_nova/modules/aesthetics/guns/code/guns.dm b/modular_nova/modules/aesthetics/guns/code/guns.dm
index 3aaa51472fa3..5f7199df43c3 100644
--- a/modular_nova/modules/aesthetics/guns/code/guns.dm
+++ b/modular_nova/modules/aesthetics/guns/code/guns.dm
@@ -101,12 +101,14 @@
righthand_file = 'modular_nova/modules/aesthetics/guns/icons/guns_righthand.dmi'
/obj/item/gun/ballistic/automatic/pistol/m1911
+ desc = "A classic handgun, modern variants of which take .460 Ceres."
icon = 'modular_nova/modules/aesthetics/guns/icons/guns.dmi'
inhand_icon_state = "colt"
lefthand_file = 'modular_nova/modules/aesthetics/guns/icons/guns_lefthand.dmi'
righthand_file = 'modular_nova/modules/aesthetics/guns/icons/guns_righthand.dmi'
/obj/item/gun/ballistic/automatic/c20r
+ desc = "A bullpup three-round burst .460 Ceres SMG, designated 'C-20r'. Has a 'Scarborough Arms - Per falcis, per pravitas' buttstamp."
icon = 'modular_nova/modules/aesthetics/guns/icons/guns.dmi'
/obj/item/gun/ballistic/automatic/m90
@@ -118,8 +120,12 @@
/obj/item/gun/ballistic/automatic/pistol
icon = 'modular_nova/modules/aesthetics/guns/icons/guns.dmi'
+/obj/item/gun/ballistic/automatic/pistol/deagle
+ desc = "A robust .454 Trucidator handgun."
+
/obj/item/gun/ballistic/automatic/pistol/deagle/regal
icon = 'icons/obj/weapons/guns/ballistic.dmi'
+ desc = "A gold plated Desert Eagle folded over a million times by superior martian gunsmiths. Uses .454 Trucidator ammo."
/obj/item/gun/ballistic/automatic/pistol/clandestine/fisher
icon = 'icons/obj/weapons/guns/ballistic.dmi'
@@ -230,7 +236,7 @@
/obj/item/gun/ballistic/automatic/sniper_rifle/syndicate
name = "syndicate sniper rifle"
- desc = "An illegally modified .50 cal sniper rifle with suppression compatibility. Quickscoping still doesn't work."
+ desc = "An illegally modified .416 Stabilis sniper rifle with suppression compatibility. Quickscoping still doesn't work."
icon = 'modular_nova/modules/aesthetics/guns/icons/guns_gubman2.dmi'
icon_state = "sniper2"
worn_icon_state = "sniper"
@@ -419,6 +425,20 @@
custom_materials = AMMO_MATS_TEMP
advanced_print_req = TRUE
+/obj/item/ammo_casing/c45/hp
+ name = ".460 Ceres hollow point bullet casing"
+ desc = "A .460 bullet casing."
+ projectile_type = /obj/projectile/bullet/c45/hp
+
+
+/obj/item/ammo_box/c45
+ name = "ammo box (.460 Ceres)"
+ desc = "A box of .460 Ceres ammunition, a modern successor to the .45 round."
+
+/obj/item/ammo_box/magazine/m45
+ name = "handgun magazine (.460 Ceres)"
+ desc = "A magazine chambered in .460 meant to fit in handguns."
+
// overrides for .50AE, used in the deagle
/obj/item/ammo_casing/a50ae
name = ".454 Trucidator bullet casing"
@@ -427,18 +447,18 @@
HAND CANNON: Fired out of a handgun, deals disproportionately large damage."
// overrides for .357, used in the .357 revolver
-/obj/item/ammo_casing/a357 //We can keep the Magnum classic.
+/obj/item/ammo_casing/c357 //We can keep the Magnum classic.
name = ".357 bullet casing"
desc = "A .357 bullet casing.\
\
HAND CANNON: Fired out of a handgun, deals disproportionately large damage."
-/obj/item/ammo_casing/a357/match
+/obj/item/ammo_casing/c357/match
desc = "A .357 bullet casing, manufactured to exceedingly high standards.\
\
MATCH: Ricochets everywhere. Like crazy."
-/obj/item/ammo_casing/a357/phasic
+/obj/item/ammo_casing/c357/phasic
desc = "A .357 phasic bullet casing.\
\
PHASIC: Ignores all surfaces except organic matter."
@@ -446,7 +466,7 @@
custom_materials = AMMO_MATS_PHASIC
can_be_printed = FALSE // shot from cargo to sec with cameras needed to see if you hit your target, it can be fun for event where we play extreme battleships.
-/obj/item/ammo_casing/a357/heartseeker
+/obj/item/ammo_casing/c357/heartseeker
desc = "A .357 heartseeker bullet casing.\
\
HEARTSEEKER: Has homing capabilities, methodology unknown."
@@ -497,6 +517,8 @@
/obj/projectile/bullet/c45/ap
name = ".460 armor-piercing bullet"
+ armour_penetration = 20
+ damage = 20
/obj/projectile/bullet/incendiary/c45
name = ".460 incendiary bullet"
@@ -535,6 +557,10 @@
/obj/item/ammo_box/magazine/wt550m9/wtic
name = "\improper WT-550 IND magazine"
+/obj/item/ammo_box/magazine/smgm45/hp
+ name = ".460 Ceres HP SMG magazine"
+ ammo_type = /obj/item/ammo_casing/c45/hp
+
/obj/item/ammo_box/magazine/smgm45
name = ".460 Ceres SMG magazine"
desc = "A magazine chambered for .460 meant to fit in submachine guns."
diff --git a/modular_nova/modules/aesthetics/kitchen/coffeemaker.dmi b/modular_nova/modules/aesthetics/kitchen/coffeemaker.dmi
index 5d9706b2504c..69ed7e051252 100644
Binary files a/modular_nova/modules/aesthetics/kitchen/coffeemaker.dmi and b/modular_nova/modules/aesthetics/kitchen/coffeemaker.dmi differ
diff --git a/modular_nova/modules/aesthetics/kitchen/kitchen.dm b/modular_nova/modules/aesthetics/kitchen/kitchen.dm
index 358df6b09244..c01b02ee87f4 100644
--- a/modular_nova/modules/aesthetics/kitchen/kitchen.dm
+++ b/modular_nova/modules/aesthetics/kitchen/kitchen.dm
@@ -17,15 +17,7 @@
/obj/structure/showcase/machinery/microwave
icon = 'modular_nova/modules/aesthetics/kitchen/microwave.dmi'
-// Nova Coffee Maker Icons
-/obj/machinery/coffeemaker
- icon = 'modular_nova/modules/aesthetics/kitchen/coffeemaker.dmi'
-
-/obj/item/reagent_containers/cup/coffeepot
- icon = 'modular_nova/modules/aesthetics/kitchen/coffeemaker.dmi'
- fill_icon = 'modular_nova/modules/aesthetics/kitchen/coffeemaker.dmi'
- fill_icon_state = "coffeepot"
-
+// Nova Coffee Cartridge Icons
/obj/item/coffee_cartridge
icon = 'modular_nova/modules/aesthetics/kitchen/coffeemaker.dmi'
diff --git a/modular_nova/modules/ashwalkers/code/species/Ashwalkers.dm b/modular_nova/modules/ashwalkers/code/species/Ashwalkers.dm
index aa76162c67da..f40c96eaf510 100644
--- a/modular_nova/modules/ashwalkers/code/species/Ashwalkers.dm
+++ b/modular_nova/modules/ashwalkers/code/species/Ashwalkers.dm
@@ -9,7 +9,7 @@
BODY_ZONE_R_LEG = /obj/item/bodypart/leg/right/lizard/ashwalker,
)
-/datum/species/lizard/ashwalker/on_species_gain(mob/living/carbon/carbon_target, datum/species/old_species)
+/datum/species/lizard/ashwalker/on_species_gain(mob/living/carbon/carbon_target, datum/species/old_species, pref_load, regenerate_icons)
. = ..()
ADD_TRAIT(carbon_target, TRAIT_ASHSTORM_IMMUNE, SPECIES_TRAIT)
RegisterSignal(carbon_target, COMSIG_MOB_ITEM_ATTACK, PROC_REF(mob_attack))
diff --git a/modular_nova/modules/better_vox/code/vox_species.dm b/modular_nova/modules/better_vox/code/vox_species.dm
index 2cb8723a3f16..be02c139cffc 100644
--- a/modular_nova/modules/better_vox/code/vox_species.dm
+++ b/modular_nova/modules/better_vox/code/vox_species.dm
@@ -72,7 +72,7 @@
/datum/species/vox_primalis/get_species_lore()
return list(placeholder_lore)
-/datum/species/vox_primalis/on_species_gain(mob/living/carbon/human/transformer)
+/datum/species/vox_primalis/on_species_gain(mob/living/carbon/human/transformer, datum/species/old_species, pref_load, regenerate_icons)
. = ..()
var/vox_color = transformer.dna.features["vox_bodycolor"]
if(!vox_color || vox_color == "default")
diff --git a/modular_nova/modules/bitrunning/code/virtual_domains/ancient_milsim/mod.dm b/modular_nova/modules/bitrunning/code/virtual_domains/ancient_milsim/mod.dm
index 2cbace5a1536..1f7caf3546f0 100644
--- a/modular_nova/modules/bitrunning/code/virtual_domains/ancient_milsim/mod.dm
+++ b/modular_nova/modules/bitrunning/code/virtual_domains/ancient_milsim/mod.dm
@@ -20,7 +20,7 @@
/obj/item/mod/module/visor/medhud,
)
insignia_type = /obj/item/mod/module/insignia/milsim_mechanic
- additional_module = /obj/item/mod/module/dispenser/ancient_milsim/mechanic
+ additional_modules = /obj/item/mod/module/dispenser/ancient_milsim/mechanic
/obj/item/mod/control/pre_equipped/responsory/milsim/trapper
applied_modules = list(
@@ -35,7 +35,7 @@
/obj/item/mod/module/visor/thermal,
)
insignia_type = /obj/item/mod/module/insignia/milsim_trapper
- additional_module = /obj/item/mod/module/dispenser/ancient_milsim/trapper
+ additional_modules = /obj/item/mod/module/dispenser/ancient_milsim/trapper
/obj/item/mod/control/pre_equipped/responsory/milsim/marksman
applied_modules = list(
@@ -52,7 +52,7 @@
/obj/item/mod/module/visor/night,
)
insignia_type = /obj/item/mod/module/insignia/milsim_marksman
- additional_module = /obj/item/mod/module/dispenser/ancient_milsim/marksman
+ additional_modules = /obj/item/mod/module/dispenser/ancient_milsim/marksman
/obj/item/mod/control/pre_equipped/responsory/milsim/medic
applied_modules = list(
@@ -67,7 +67,7 @@
/obj/item/mod/module/visor/medhud,
)
insignia_type = /obj/item/mod/module/insignia/milsim_medic
- additional_module = /obj/item/mod/module/dispenser/ancient_milsim/medic
+ additional_modules = /obj/item/mod/module/dispenser/ancient_milsim/medic
/obj/item/mod/control/pre_equipped/responsory/milsim/saboteur
applied_modules = list(
@@ -82,7 +82,7 @@
/obj/item/mod/module/visor/meson,
)
insignia_type = /obj/item/mod/module/insignia/milsim_saboteur
- additional_module = /obj/item/mod/module/dispenser/ancient_milsim/saboteur
+ additional_modules = /obj/item/mod/module/dispenser/ancient_milsim/saboteur
/obj/item/mod/control/pre_equipped/responsory/milsim/sentinel
applied_modules = list(
@@ -99,7 +99,7 @@
/obj/item/mod/module/visor/night,
)
insignia_type = /obj/item/mod/module/insignia/milsim_sentinel
- additional_module = /obj/item/mod/module/dispenser/ancient_milsim/sentinel
+ additional_modules = /obj/item/mod/module/dispenser/ancient_milsim/sentinel
/obj/item/mod/control/pre_equipped/responsory/milsim/trooper
applied_modules = list(
@@ -116,7 +116,7 @@
/obj/item/mod/module/visor/night,
)
insignia_type = /obj/item/mod/module/insignia/milsim_trooper
- additional_module = /obj/item/mod/module/dispenser/ancient_milsim/trooper
+ additional_modules = /obj/item/mod/module/dispenser/ancient_milsim/trooper
/obj/item/mod/module/dispenser/ancient_milsim
removable = FALSE
diff --git a/modular_nova/modules/bsrpd/code/bsrpd.dm b/modular_nova/modules/bsrpd/code/bsrpd.dm
index 51391d508700..e6cb6dbef7e7 100644
--- a/modular_nova/modules/bsrpd/code/bsrpd.dm
+++ b/modular_nova/modules/bsrpd/code/bsrpd.dm
@@ -64,7 +64,7 @@
user.Beam(interacting_with, icon_state = "rped_upgrade", time = 1 SECONDS)
in_use = TRUE // So people can't just spam click and get more uses
addtimer(VARSET_CALLBACK(src, in_use, FALSE), 1 SECONDS, TIMER_UNIQUE)
- if(pre_attack(interacting_with, user))
+ if(interact_with_atom(interacting_with, user, modifiers))
current_capacity -= ranged_use_cost
return ITEM_INTERACT_SUCCESS
diff --git a/modular_nova/modules/clock_cult/code/scriptures/_scripture.dm b/modular_nova/modules/clock_cult/code/scriptures/_scripture.dm
index 9c6d9ef6b87d..5217ad38bc62 100644
--- a/modular_nova/modules/clock_cult/code/scriptures/_scripture.dm
+++ b/modular_nova/modules/clock_cult/code/scriptures/_scripture.dm
@@ -361,7 +361,7 @@ GLOBAL_LIST_EMPTY(clock_scriptures_by_type)
return ..()
-/datum/action/cooldown/spell/pointed/slab/InterceptClickOn(mob/living/caller, params, atom/target)
+/datum/action/cooldown/spell/pointed/slab/InterceptClickOn(mob/living/clicker, params, atom/target)
parent_scripture?.click_on(target)
diff --git a/modular_nova/modules/customization/modules/mob/living/carbon/human/species/akula.dm b/modular_nova/modules/customization/modules/mob/living/carbon/human/species/akula.dm
index 6bbdc42d8f71..0ee25e7f2705 100644
--- a/modular_nova/modules/customization/modules/mob/living/carbon/human/species/akula.dm
+++ b/modular_nova/modules/customization/modules/mob/living/carbon/human/species/akula.dm
@@ -232,7 +232,7 @@
// more about grab_resists in `code\modules\mob\living\living.dm` at li 1119
// more about slide_distance in `code\game\turfs\open\_open.dm` at li 233
/// Lets register the signal which calls when we are above 10 wet_stacks
-/datum/species/akula/on_species_gain(mob/living/carbon/akula, datum/species/old_species, pref_load)
+/datum/species/akula/on_species_gain(mob/living/carbon/akula, datum/species/old_species, pref_load, regenerate_icons)
. = ..()
RegisterSignal(akula, COMSIG_MOB_TRIGGER_WET_SKIN, PROC_REF(wetted), akula)
// lets give 15 wet_stacks on initial
diff --git a/modular_nova/modules/customization/modules/mob/living/carbon/human/species/ghoul.dm b/modular_nova/modules/customization/modules/mob/living/carbon/human/species/ghoul.dm
index 9975eb59cc72..4bb7ffb94d3b 100644
--- a/modular_nova/modules/customization/modules/mob/living/carbon/human/species/ghoul.dm
+++ b/modular_nova/modules/customization/modules/mob/living/carbon/human/species/ghoul.dm
@@ -87,7 +87,7 @@
limb.replace_limb(src, TRUE)
qdel(right_leg)
-/datum/species/ghoul/on_species_gain(mob/living/carbon/new_ghoul, datum/species/old_species, pref_load)
+/datum/species/ghoul/on_species_gain(mob/living/carbon/new_ghoul, datum/species/old_species, pref_load, regenerate_icons)
// Missing Defaults in DNA? Randomize!
proof_ghoul_features(new_ghoul.dna.features)
diff --git a/modular_nova/modules/customization/modules/mob/living/carbon/human/species/hemophage/hemophage_species.dm b/modular_nova/modules/customization/modules/mob/living/carbon/human/species/hemophage/hemophage_species.dm
index bc9255a6ac9e..9d12e0b457a5 100644
--- a/modular_nova/modules/customization/modules/mob/living/carbon/human/species/hemophage/hemophage_species.dm
+++ b/modular_nova/modules/customization/modules/mob/living/carbon/human/species/hemophage/hemophage_species.dm
@@ -42,7 +42,7 @@
return ..()
-/datum/species/hemophage/on_species_gain(mob/living/carbon/human/new_hemophage, datum/species/old_species, pref_load)
+/datum/species/hemophage/on_species_gain(mob/living/carbon/human/new_hemophage, datum/species/old_species, pref_load, regenerate_icons)
. = ..()
to_chat(new_hemophage, HEMOPHAGE_SPAWN_TEXT)
new_hemophage.update_body()
diff --git a/modular_nova/modules/customization/modules/mob/living/carbon/human/species/roundstartslime.dm b/modular_nova/modules/customization/modules/mob/living/carbon/human/species/roundstartslime.dm
index a5e16d62936f..3be663b89f96 100644
--- a/modular_nova/modules/customization/modules/mob/living/carbon/human/species/roundstartslime.dm
+++ b/modular_nova/modules/customization/modules/mob/living/carbon/human/species/roundstartslime.dm
@@ -26,7 +26,7 @@
/// Ability to allow them to turn their core's GPS on or off.
var/datum/action/innate/core_signal/core_signal
-/datum/species/jelly/on_species_gain(mob/living/carbon/new_jellyperson, datum/species/old_species, pref_load)
+/datum/species/jelly/on_species_gain(mob/living/carbon/new_jellyperson, datum/species/old_species, pref_load, regenerate_icons)
. = ..()
if(ishuman(new_jellyperson))
alter_form = new
diff --git a/modular_nova/modules/customization/modules/mob/living/carbon/human/species/vampire.dm b/modular_nova/modules/customization/modules/mob/living/carbon/human/species/vampire.dm
new file mode 100644
index 000000000000..38e9cf064dc8
--- /dev/null
+++ b/modular_nova/modules/customization/modules/mob/living/carbon/human/species/vampire.dm
@@ -0,0 +1,2 @@
+/datum/species/human/vampire/prepare_human_for_preview(mob/living/carbon/human/human)
+ return // Skip parent proc
diff --git a/modular_nova/modules/customization/modules/mob/living/carbon/human/species/xeno.dm b/modular_nova/modules/customization/modules/mob/living/carbon/human/species/xeno.dm
index 0c4439c31dc4..176946f9929a 100644
--- a/modular_nova/modules/customization/modules/mob/living/carbon/human/species/xeno.dm
+++ b/modular_nova/modules/customization/modules/mob/living/carbon/human/species/xeno.dm
@@ -84,7 +84,7 @@
regenerate_organs(xeno, src, visual_only = TRUE)
xeno.update_body(TRUE)
-/datum/species/xeno/on_species_gain(mob/living/carbon/human/human_who_gained_species, datum/species/old_species, pref_load)
+/datum/species/xeno/on_species_gain(mob/living/carbon/human/human_who_gained_species, datum/species/old_species, pref_load, regenerate_icons)
. = ..()
human_who_gained_species.gib_type = /obj/effect/decal/cleanable/xenoblood/xgibs
diff --git a/modular_nova/modules/customization/modules/mob/living/silicon/examine.dm b/modular_nova/modules/customization/modules/mob/living/silicon/examine.dm
index 30fc53cf571a..a5a49d6b3891 100644
--- a/modular_nova/modules/customization/modules/mob/living/silicon/examine.dm
+++ b/modular_nova/modules/customization/modules/mob/living/silicon/examine.dm
@@ -8,7 +8,7 @@
/// The first 1-FLAVOR_PREVIEW_LIMIT characters in the mob's client's silicon_flavor_text preference datum. FLAVOR_PREVIEW_LIMIT is defined in flavor_defines.dm.
var/silicon_preview_text = copytext_char((client?.prefs.read_preference(/datum/preference/text/silicon_flavor_text)), 1, FLAVOR_PREVIEW_LIMIT)
- flavor_text_link = span_notice("[silicon_preview_text]... Look closer?")
+ flavor_text_link = span_notice("[silicon_preview_text]... Look closer?")
if (flavor_text_link)
. += flavor_text_link
@@ -23,4 +23,4 @@
if(length_char(temporary_flavor_text) <= 40)
. += span_notice("They look different than usual: [temporary_flavor_text]")
else
- . += span_notice("They look different than usual: [copytext_char(temporary_flavor_text, 1, 37)]... More...")
+ . += span_notice("They look different than usual: [copytext_char(temporary_flavor_text, 1, 37)]... More...")
diff --git a/modular_nova/modules/customization/modules/mob/living/simple_mob/examine.dm b/modular_nova/modules/customization/modules/mob/living/simple_mob/examine.dm
index 2746ae43c512..d2dd6db5b534 100644
--- a/modular_nova/modules/customization/modules/mob/living/simple_mob/examine.dm
+++ b/modular_nova/modules/customization/modules/mob/living/simple_mob/examine.dm
@@ -5,4 +5,4 @@
if(length_char(temporary_flavor_text) <= 40)
. += span_notice("[temporary_flavor_text]")
else
- . += span_notice("[copytext_char(temporary_flavor_text, 1, 37)]... More...")
+ . += span_notice("[copytext_char(temporary_flavor_text, 1, 37)]... More...")
diff --git a/modular_nova/modules/customization/modules/reagents/chemistry/reagents/other_reagents.dm b/modular_nova/modules/customization/modules/reagents/chemistry/reagents/other_reagents.dm
index cf72d5a93b95..43469c59a8aa 100644
--- a/modular_nova/modules/customization/modules/reagents/chemistry/reagents/other_reagents.dm
+++ b/modular_nova/modules/customization/modules/reagents/chemistry/reagents/other_reagents.dm
@@ -73,7 +73,6 @@
/datum/reagent/medicine/dermagen
name = "Dermagen"
description = "Heals scars formed by past physical trauma when applied. Minimum 10u needed, only works when applied topically."
- reagent_state = LIQUID
color = "#FFEBEB"
ph = 6
chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
diff --git a/modular_nova/modules/death_consequences_perk/death_consequences_trauma.dm b/modular_nova/modules/death_consequences_perk/death_consequences_trauma.dm
index 70ec68d57e7d..4446cdee099d 100644
--- a/modular_nova/modules/death_consequences_perk/death_consequences_trauma.dm
+++ b/modular_nova/modules/death_consequences_perk/death_consequences_trauma.dm
@@ -424,7 +424,7 @@
return message
message += span_danger("\nCurrent degradation/max: [span_blue("[current_degradation]")]/[max_degradation].")
- message += span_notice("\nView degradation specifics?")
+ message += span_notice("\nView degradation specifics?")
if (permakill_if_at_max_degradation)
message += span_revenwarning("\n\nSUBJECT WILL BE PERMANENTLY KILLED IF DEGRADATION REACHES MAXIMUM!")
@@ -446,7 +446,7 @@
if (href_list[DEATH_CONSEQUENCES_SHOW_HEALTH_ANALYZER_DATA])
if (world.time <= time_til_scan_expires[usr])
- to_chat(usr, examine_block(get_specific_data()), trailing_newline = FALSE, type = MESSAGE_TYPE_INFO)
+ to_chat(usr, boxed_message(get_specific_data()), trailing_newline = FALSE, type = MESSAGE_TYPE_INFO)
else
to_chat(usr, span_warning("Your scan has expired! Try scanning again!"))
diff --git a/modular_nova/modules/deforest_medical_items/code/chemicals/demoneye.dm b/modular_nova/modules/deforest_medical_items/code/chemicals/demoneye.dm
index 7ac1ec64bbe6..715eb69339e8 100644
--- a/modular_nova/modules/deforest_medical_items/code/chemicals/demoneye.dm
+++ b/modular_nova/modules/deforest_medical_items/code/chemicals/demoneye.dm
@@ -21,7 +21,6 @@
name = "DemonEye"
description = "A performance enhancing drug originally developed on mars. \
A favorite among gangs and other outlaws on the planet, though overuse can cause terrible addiction and bodily damage."
- reagent_state = LIQUID
color = "#af00be"
taste_description = "industrial shuttle fuel"
metabolization_rate = 0.65 * REAGENTS_METABOLISM
diff --git a/modular_nova/modules/deforest_medical_items/code/chemicals/twitch.dm b/modular_nova/modules/deforest_medical_items/code/chemicals/twitch.dm
index 352b1d8939b2..013860babe2a 100644
--- a/modular_nova/modules/deforest_medical_items/code/chemicals/twitch.dm
+++ b/modular_nova/modules/deforest_medical_items/code/chemicals/twitch.dm
@@ -25,7 +25,6 @@
description = "A drug originally developed by and for plutonians to assist them during raids. \
Does not see wide use due to the whole reality-disassociation and heart disease thing afterwards. \
Can be intentionally overdosed to increase the drug's effects"
- reagent_state = LIQUID
color = "#c22a44"
taste_description = "television static"
metabolization_rate = 0.65 * REAGENTS_METABOLISM
diff --git a/modular_nova/modules/deforest_medical_items/code/healing_stack_items.dm b/modular_nova/modules/deforest_medical_items/code/healing_stack_items.dm
index 30595292e961..cd73a61a34cd 100644
--- a/modular_nova/modules/deforest_medical_items/code/healing_stack_items.dm
+++ b/modular_nova/modules/deforest_medical_items/code/healing_stack_items.dm
@@ -33,7 +33,7 @@
var/treatment_sound = 'sound/items/duct_tape/duct_tape_rip.ogg'
// This is only relevant for the types of wounds defined, we can't work if there are none
-/obj/item/stack/medical/wound_recovery/try_heal(mob/living/patient, mob/user, silent, looping)
+/obj/item/stack/medical/wound_recovery/try_heal(mob/living/patient, mob/living/user, healed_zone, silent, auto_change_zone)
if(patient.has_status_effect(/datum/status_effect/vulnerable_to_damage))
patient.balloon_alert(user, "still recovering from last use!")
diff --git a/modular_nova/modules/delam_emergency_stop/code/delam.dm b/modular_nova/modules/delam_emergency_stop/code/delam.dm
index 29342d265117..18b391cb838d 100644
--- a/modular_nova/modules/delam_emergency_stop/code/delam.dm
+++ b/modular_nova/modules/delam_emergency_stop/code/delam.dm
@@ -23,7 +23,7 @@
for(var/client/staff as anything in GLOB.admins)
if(staff?.prefs.read_preference(/datum/preference/toggle/comms_notification))
SEND_SOUND(staff, sound('sound/misc/server-ready.ogg'))
- message_admins("DELAM: Round timer under 30 minutes! [ADMIN_VERBOSEJMP(sm)] will perform an automatic delam suppression once integrity reaches 0%. (TOGGLE AUTOMATIC INTERVENTION))")
+ message_admins("DELAM: Round timer under 30 minutes! [ADMIN_VERBOSEJMP(sm)] will perform an automatic delam suppression once integrity reaches 0%. (TOGGLE AUTOMATIC INTERVENTION))")
sm.station_notified = TRUE
/datum/sm_delam/Topic(href, href_list)
diff --git a/modular_nova/modules/events/code/event_spawner_menu.dm b/modular_nova/modules/events/code/event_spawner_menu.dm
index 6cfaf9619e69..4a3edd7b3b52 100644
--- a/modular_nova/modules/events/code/event_spawner_menu.dm
+++ b/modular_nova/modules/events/code/event_spawner_menu.dm
@@ -96,10 +96,10 @@
var/datum/event_spawner_instance/ESI = managed_instances["[panel_id]"]
if(!ESI)
return
- dat += "Return ----- ExportImport"
- dat += "Job Name:[ESI.job_name]"
+ dat += "Return ----- ExportImport"
+ dat += "Job Name:[ESI.job_name]"
dat += " This will appear on the person's ID"
- dat += " Flavor Text:[ESI.flavor_text]"
+ dat += " Flavor Text:[ESI.flavor_text]"
dat += " The player will be greeted with the flavor text. Tell him who his supervisors are, if any."
var/outfit_name
var/datum/outfit/OU
@@ -109,12 +109,12 @@
else
OU = ESI.used_outfit
outfit_name = initial(OU.name)
- dat += " Allow Loadout: [ESI.gets_loadout ? "Yes" : "No"]"
+ dat += " Allow Loadout: [ESI.gets_loadout ? "Yes" : "No"]"
dat += " Whether loadout from prefs is allowed."
- dat += " Used Outfit: [outfit_name]"
+ dat += " Used Outfit: [outfit_name]"
dat += " Which outfit is used for the spawned player. Below you can preview contents of the selected one."
if(ESI.used_outfit)
- dat += " [ESI.show_outfit_equipment ? "Hide outfit equipment" : "Show outfit equipment"]"
+ dat += " [ESI.show_outfit_equipment ? "Hide outfit equipment" : "Show outfit equipment"]"
if(OU && ESI.show_outfit_equipment)
dat += "Outfit equipment:"
var/obj/item/display = initial(OU.uniform)
@@ -172,43 +172,43 @@
dat += ""
dat += "Here you can add extra equipment on top of loadout, if possible those will be auto equipped."
dat += " You HAVE to add them as typed paths(/obj/item/gun/energy/e_gun). Search in Game Panel to find desired paths."
- dat += " Additional equipment: Add"
+ dat += " Additional equipment: Add"
for(var/eq in ESI.additional_equipment)
var/obj/item/equip = eq
- dat += " [initial(equip.name)] - ([eq])"
+ dat += " [initial(equip.name)] - ([eq])"
dat += ""
dat += "Outfits limit on what airlocks can someone access, or what headset and frequences they have. Here you can edit those things"
dat += " Access overrides: "
for(var/access_key in ESI.access_override)
- dat += "[access_key]"
- dat += " <- Add"
+ dat += "[access_key]"
+ dat += " <- Add"
dat += " Add access in NUMBERS. If no numbers are in the list, then the access will not be overriden. If you want to remove all access, add only a 0"
dat += " See '/code/__DEFINES/access.dm' for an access list. (401-Faction Public, 402-Faction Crew, 403-Faction Command)"
dat += " Headset override: "
if(ESI.headset_override)
var/obj/item/headset = ESI.headset_override
- dat += "[initial(headset.name)] - ([ESI.headset_override])"
- dat += " <- Set"
+ dat += "[initial(headset.name)] - ([ESI.headset_override])"
+ dat += " <- Set"
dat += " Here you can override the headset, make sure to pick one which has the proper key with radio frequences for your role"
dat += ""
dat += "Allowed Species:"
for(var/spec in ESI.species_whitelist)
- dat += "[spec]"
- dat += " <- Add"
+ dat += "[spec]"
+ dat += " <- Add"
dat += " If no species are in the list, then any species can join as this role."
dat += " Allowed Genders:"
for(var/gend in ESI.gender_whitelist)
- dat += "[gend]"
- dat += " <- Add"
+ dat += "[gend]"
+ dat += " <- Add"
dat += " If no genders are in the list, then any gender can join as this role."
dat += " Allowed CKEYs:"
for(var/ckey in ESI.ckey_whitelist)
- dat += "[ckey]"
- dat += " <- Add"
+ dat += "[ckey]"
+ dat += " <- Add"
dat += " If no ckeys are in the list, then any ckey can join as this role."
- dat += " Spawner disappears after spawn: [ESI.disappear_after_spawn ? "Yes" : "No"]"
- dat += " Prompt players when spawner created: [ESI.prompt_players ? "Yes" : "No"]This will only prompt ckeys which can access the spawner."
- dat += "
"
dat += ""
winshow(usr, "event_spawn_window", TRUE)
diff --git a/modular_nova/modules/icspawning/code/standard.dm b/modular_nova/modules/icspawning/code/standard.dm
index 658883be6b95..9ab409d75987 100644
--- a/modular_nova/modules/icspawning/code/standard.dm
+++ b/modular_nova/modules/icspawning/code/standard.dm
@@ -83,7 +83,16 @@
atom_storage.max_total_storage = 20000
/// An extension to the default RPED part replacement action - if you don't have the requisite parts in the RPED already, it will spawn T4 versions to use.
-/obj/item/storage/part_replacer/bluespace/tier4/bst/part_replace_action(obj/attacked_object, mob/living/user)
+/obj/item/storage/part_replacer/bluespace/tier4/bst/interact_with_atom(obj/attacked_object, mob/living/user, list/modifiers)
+ //duplicate checks from parent since
+ if(user.combat_mode)
+ return ITEM_INTERACT_SKIP_TO_ATTACK
+ if(!ismachinery(attacked_object) || istype(attacked_object, /obj/machinery/computer))
+ return NONE
+ var/obj/machinery/attacked_machinery = attacked_object
+ if(!LAZYLEN(attacked_machinery.component_parts))
+ return ITEM_INTERACT_FAILURE
+
// We start with setting up a list of the current contents of the RPED when using auto-clear. This is used to detect new items after upgrades are applied & remove them.
var/list/old_contents = list()
var/list/inv_grab = atom_storage.return_inv(FALSE)
@@ -102,7 +111,6 @@
else
// It's not a machine frame, so let's check if it's a regular machine.
if(ismachinery(attacked_object) && !istype(attacked_object, /obj/machinery/computer))
- var/obj/machinery/attacked_machinery = attacked_object
var/obj/item/circuitboard/machine/circuit = attacked_machinery.circuit
// If it is, we need to use the circuit's components; there's no good way to get required components off of an already-built machine.
if(istype(circuit))
diff --git a/modular_nova/modules/implants/code/augments_arms.dm b/modular_nova/modules/implants/code/augments_arms.dm
index cbf945382363..23c108e4e9ed 100644
--- a/modular_nova/modules/implants/code/augments_arms.dm
+++ b/modular_nova/modules/implants/code/augments_arms.dm
@@ -155,7 +155,6 @@
icon = 'modular_nova/modules/implants/icons/drillimplant.dmi'
icon_state = "steel"
items_to_create = list(/obj/item/pickaxe/drill/implant)
- implant_overlay = null
implant_color = null
/// The bodypart overlay datum we should apply to whatever mob we are put into's someone's arm
var/datum/bodypart_overlay/simple/steel_drill/drill_overlay
diff --git a/modular_nova/modules/implants/code/augments_chest.dm b/modular_nova/modules/implants/code/augments_chest.dm
index 16191776a3c7..ffb05afb9cf9 100644
--- a/modular_nova/modules/implants/code/augments_chest.dm
+++ b/modular_nova/modules/implants/code/augments_chest.dm
@@ -7,7 +7,6 @@
slot = ORGAN_SLOT_SCANNER
icon = 'modular_nova/modules/implants/icons/internal_HA.dmi'
icon_state = "internal_HA"
- implant_overlay = null
implant_color = null
actions_types = list(/datum/action/item_action/organ_action/use/internal_analyzer)
w_class = WEIGHT_CLASS_SMALL
diff --git a/modular_nova/modules/implants/code/augments_head.dm b/modular_nova/modules/implants/code/augments_head.dm
index 3c0b784d373e..6239e0069c4f 100644
--- a/modular_nova/modules/implants/code/augments_head.dm
+++ b/modular_nova/modules/implants/code/augments_head.dm
@@ -14,7 +14,6 @@
icon_state = "sandy"
slot = ORGAN_SLOT_BRAIN_CNS
zone = BODY_ZONE_HEAD
- implant_overlay = null
implant_color = null
actions_types = list(
/datum/action/cooldown/sensory_enhancer,
@@ -133,7 +132,6 @@
icon_state = "hackerman"
slot = ORGAN_SLOT_BRAIN_CNS
zone = BODY_ZONE_HEAD
- implant_overlay = null
implant_color = null
actions_types = list(/datum/action/cooldown/spell/pointed/hackerman_deck)
w_class = WEIGHT_CLASS_SMALL
diff --git a/modular_nova/modules/liquids/code/ocean_areas.dm b/modular_nova/modules/liquids/code/ocean_areas.dm
index ce9a64463c59..fd61b4900d8c 100644
--- a/modular_nova/modules/liquids/code/ocean_areas.dm
+++ b/modular_nova/modules/liquids/code/ocean_areas.dm
@@ -25,7 +25,7 @@
map_generator = /datum/map_generator/cave_generator/trench
/area/ruin/ocean
- has_gravity = TRUE
+ default_gravity = STANDARD_GRAVITY
area_flags = UNIQUE_AREA
/area/ruin/ocean/listening_outpost
diff --git a/modular_nova/modules/loadouts/loadout_ui/loadout_manager.dm b/modular_nova/modules/loadouts/loadout_ui/loadout_manager.dm
index 95dae3b5b6d1..0a7b3f086af6 100644
--- a/modular_nova/modules/loadouts/loadout_ui/loadout_manager.dm
+++ b/modular_nova/modules/loadouts/loadout_ui/loadout_manager.dm
@@ -101,12 +101,12 @@
if("donator_explain")
if(GLOB.donator_list[owner.ckey])
- to_chat(owner, examine_block("Thank you for donating, this item is for you <3!"))
+ to_chat(owner, boxed_message("Thank you for donating, this item is for you <3!"))
else
- to_chat(owner, examine_block(span_boldnotice("This item is restricted to donators only, for more information, please check the discord(#server-info) for more information!")))
+ to_chat(owner, boxed_message(span_boldnotice("This item is restricted to donators only, for more information, please check the discord(#server-info) for more information!")))
if("ckey_explain")
- to_chat(owner, examine_block(span_green("This item is restricted to your ckey only. Thank you!")))
+ to_chat(owner, boxed_message(span_green("This item is restricted to your ckey only. Thank you!")))
return TRUE
@@ -227,7 +227,7 @@
for(var/job_type in item.restricted_roles)
composed_message += span_green("[job_type] ")
- to_chat(owner, examine_block(composed_message))
+ to_chat(owner, boxed_message(composed_message))
/// If certain jobs aren't allowed to equip this loadout item, display which
/datum/loadout_manager/proc/display_job_blacklists(datum/loadout_item/item)
@@ -237,7 +237,7 @@
for(var/job_type in item.blacklisted_roles)
composed_message += span_red("[job_type] ")
- to_chat(owner, examine_block(composed_message))
+ to_chat(owner, boxed_message(composed_message))
/// If only a certain species is allowed to equip this loadout item, display which
/datum/loadout_manager/proc/display_species_restrictions(datum/loadout_item/item)
@@ -247,7 +247,7 @@
for(var/species_type in item.restricted_species)
composed_message += span_grey("[species_type] ")
- to_chat(owner, examine_block(composed_message))
+ to_chat(owner, boxed_message(composed_message))
/datum/loadout_manager/ui_data(mob/user)
var/list/data = list()
diff --git a/modular_nova/modules/magfed_turret/code/turret_framework.dm b/modular_nova/modules/magfed_turret/code/turret_framework.dm
index 2674f7c86fe1..0bbe1dc834bd 100644
--- a/modular_nova/modules/magfed_turret/code/turret_framework.dm
+++ b/modular_nova/modules/magfed_turret/code/turret_framework.dm
@@ -356,7 +356,7 @@ DEFINE_BITFIELD(turret_flags, list(
////// Can this turret load more than one ammunition type. Mostly for sound handling. Might be more important if used in a rework.
var/adjustable_magwell = TRUE
////// Does this turret auto-eject its magazines? Will be used later.
- var/auto_mag_drop = FALSE
+ var/mag_drop_collect = FALSE
//////This is for manual target acquisition stuff. If present, should immediately over-ride as a target.
var/datum/weakref/target_override
//////Target Assessment System. Whether or not it's targeting according to flags or even ignoring everyone.
@@ -556,7 +556,7 @@ DEFINE_BITFIELD(turret_flags, list(
if(magazine_ref)
var/obj/item/ammo_box/magazine/mag = magazine_ref?.resolve()
if(istype(mag))
- if(auto_mag_drop)
+ if(mag_drop_collect)
var/obj/item/storage/toolbox/emergency/turret/mag_fed/auto_loader = mag_box?.resolve()
auto_loader.atom_storage?.attempt_insert(mag, override = TRUE)
UnregisterSignal(magazine_ref, COMSIG_MOVABLE_MOVED)
@@ -574,7 +574,6 @@ DEFINE_BITFIELD(turret_flags, list(
if(!auto_loader.get_mag())
balloon_alert_to_viewers("magazine well empty!") // hey, this is actually important info to convey.
toggle_on(FALSE) // I know i added the shupt-up toggle after adding this, This is just to prevent rapid proccing
- timer_id = addtimer(CALLBACK(src, PROC_REF(toggle_on), TRUE), 5 SECONDS, TIMER_STOPPABLE)
return
magazine_ref = WEAKREF(auto_loader.get_mag(FALSE))
var/obj/item/ammo_box/magazine/get_that_mag = magazine_ref?.resolve()
@@ -609,6 +608,7 @@ DEFINE_BITFIELD(turret_flags, list(
return
balloon_alert(guy_with_mag, "magazine inserted!")
auto_loader?.atom_storage.attempt_insert(magaroni, guy_with_mag, TRUE)
+ toggle_on(TRUE)
return
////// I rewrite/add to the entire proccess. //////
diff --git a/modular_nova/modules/mapping/code/areas/space.dm b/modular_nova/modules/mapping/code/areas/space.dm
index d39420dabbf8..06b38752fddd 100644
--- a/modular_nova/modules/mapping/code/areas/space.dm
+++ b/modular_nova/modules/mapping/code/areas/space.dm
@@ -144,7 +144,7 @@
/area/solars/tarkon
name = "P-T Solar Array"
icon_state = "space_near"
- has_gravity = STANDARD_GRAVITY
+ default_gravity = STANDARD_GRAVITY
outdoors = TRUE
/**
diff --git a/modular_nova/modules/medical/code/carbon_examine.dm b/modular_nova/modules/medical/code/carbon_examine.dm
index b4e385badfb8..3f59fc8e52a5 100644
--- a/modular_nova/modules/medical/code/carbon_examine.dm
+++ b/modular_nova/modules/medical/code/carbon_examine.dm
@@ -48,7 +48,7 @@
if(WOUND_SEVERITY_CRITICAL)
. += "\t[t_His] [LB.name] is suffering [W.a_or_from] [W.get_topic_name(user)]!!"
if(LB.current_gauze)
- . += "\t[t_His] [LB.name] is [LB.current_gauze.get_gauze_usage_prefix()] with [LB.current_gauze.get_gauze_description()]."
+ . += "\t[t_His] [LB.name] is [LB.current_gauze.get_gauze_usage_prefix()] with [LB.current_gauze.get_gauze_description()]."
if(!any_bodypart_damage)
. += "\t[t_He] [t_Has] no significantly damaged bodyparts."
diff --git a/modular_nova/modules/medical/code/wounds/_wounds.dm b/modular_nova/modules/medical/code/wounds/_wounds.dm
index 4b730ae6cd94..bc8ac1ecbd1e 100644
--- a/modular_nova/modules/medical/code/wounds/_wounds.dm
+++ b/modular_nova/modules/medical/code/wounds/_wounds.dm
@@ -4,4 +4,4 @@
/// Gets the name of the wound with any interactable topic if possible
/datum/wound/proc/get_topic_name(mob/user)
- return show_wound_topic(user) ? "[LOWER_TEXT(name)]" : LOWER_TEXT(name)
+ return show_wound_topic(user) ? "[LOWER_TEXT(name)]" : LOWER_TEXT(name)
diff --git a/modular_nova/modules/mentor/code/follow.dm b/modular_nova/modules/mentor/code/follow.dm
index 5c33da00be04..3060d41f004d 100644
--- a/modular_nova/modules/mentor/code/follow.dm
+++ b/modular_nova/modules/mentor/code/follow.dm
@@ -6,7 +6,7 @@
mentor_datum.following = M
usr.reset_perspective(M)
add_verb(src,/client/proc/mentor_unfollow)
- to_chat(usr, span_info("Click the \"Stop Following\" button here or in the Mentor tab to stop following [key_name(M)]."))
+ to_chat(usr, span_info("Click the \"Stop Following\" button here or in the Mentor tab to stop following [key_name(M)]."))
orbiting = FALSE
else
var/mob/dead/observer/O = usr
diff --git a/modular_nova/modules/mentor/code/mentorhelp.dm b/modular_nova/modules/mentor/code/mentorhelp.dm
index d3bc2f343bdf..2740ba719cde 100644
--- a/modular_nova/modules/mentor/code/mentorhelp.dm
+++ b/modular_nova/modules/mentor/code/mentorhelp.dm
@@ -73,9 +73,9 @@
if(key)
if(include_link)
if(CONFIG_GET(flag/mentors_mobname_only))
- . += ""
+ . += ""
else
- . += ""
+ . += ""
if(target_client && target_client?.holder && target_client?.holder.fakekey)
. += "Administrator"
@@ -97,6 +97,6 @@
. += "*no key*"
if(include_follow)
- . += " (F)"
+ . += " (F)"
return .
diff --git a/modular_nova/modules/modular_reagents/code/reagents/medicine.dm b/modular_nova/modules/modular_reagents/code/reagents/medicine.dm
index 0c91f395fce8..5af7d44ce53a 100644
--- a/modular_nova/modules/modular_reagents/code/reagents/medicine.dm
+++ b/modular_nova/modules/modular_reagents/code/reagents/medicine.dm
@@ -1,7 +1,6 @@
/datum/reagent/medicine/lidocaine
name = "Lidocaine"
description = "A numbing agent used often for surgeries, metabolizes slowly."
- reagent_state = LIQUID
color = "#6dbdbd" // 109, 189, 189
metabolization_rate = 0.2 * REAGENTS_METABOLISM
overdose_threshold = 20
@@ -20,7 +19,6 @@
/datum/reagent/inverse/lidocaine
name = "Lidopaine"
description = "A paining agent used often for... being a jerk, metabolizes faster than lidocaine."
- reagent_state = LIQUID
color = "#85111f" // 133, 17, 31
metabolization_rate = 0.4 * REAGENTS_METABOLISM
ph = 6.09
diff --git a/modular_nova/modules/modular_vending/code/vending.dm b/modular_nova/modules/modular_vending/code/vending.dm
index f343b356acc7..96555daf12d7 100644
--- a/modular_nova/modules/modular_vending/code/vending.dm
+++ b/modular_nova/modules/modular_vending/code/vending.dm
@@ -58,6 +58,11 @@
QDEL_NULL(contraband_nova)
return ..()
+/obj/machinery/vending/spawn_frame(disassembled)
+ if(ai_controller) // Vendor uprising, vending machines that are jumping around shouldn't be anchored
+ set_anchored(FALSE)
+ return ..()
+
/// This proc checks for forbidden traits cause it'd be pretty bad to have 5 insuls available to assistants roundstart at the vendor!
/obj/machinery/vending/proc/allow_increase(obj/item/clothing/clothing_path)
var/obj/item/clothing/clothing = new clothing_path()
diff --git a/modular_nova/modules/morenarcotics/code/cocaine.dm b/modular_nova/modules/morenarcotics/code/cocaine.dm
index 30c7935e2fd7..73cc0db8a8fb 100644
--- a/modular_nova/modules/morenarcotics/code/cocaine.dm
+++ b/modular_nova/modules/morenarcotics/code/cocaine.dm
@@ -25,7 +25,6 @@
/datum/reagent/drug/cocaine
name = "cocaine"
description = "A powerful stimulant extracted from coca leaves. Reduces stun times, but causes drowsiness and severe brain damage if overdosed."
- reagent_state = LIQUID
color = "#ffffff"
overdose_threshold = 20
ph = 9
diff --git a/modular_nova/modules/morenarcotics/code/opium.dm b/modular_nova/modules/morenarcotics/code/opium.dm
index 72d0028f9a55..b388f6df3cc1 100644
--- a/modular_nova/modules/morenarcotics/code/opium.dm
+++ b/modular_nova/modules/morenarcotics/code/opium.dm
@@ -99,7 +99,6 @@
/datum/reagent/drug/opium
name = "opium"
description = "A extract from opium poppies. Puts the user in a slightly euphoric state."
- reagent_state = LIQUID
color = "#ffe669"
overdose_threshold = 30
ph = 8
@@ -134,7 +133,6 @@
/datum/reagent/drug/opium/heroin
name = "heroin"
description = "She's like heroin to me, she's like heroin to me! She cannot... miss a vein!"
- reagent_state = LIQUID
color = "#ffe669"
overdose_threshold = 20
ph = 6
@@ -155,7 +153,6 @@
/datum/reagent/drug/opium/blacktar
name = "black tar heroin"
description = "An impure, freebase form of heroin. Probably not a good idea to take this..."
- reagent_state = LIQUID
color = "#242423"
overdose_threshold = 10 //more easy to overdose on
ph = 8
diff --git a/modular_nova/modules/morenarcotics/code/pcp.dm b/modular_nova/modules/morenarcotics/code/pcp.dm
index 5d3dff7ca295..dde040809b3d 100644
--- a/modular_nova/modules/morenarcotics/code/pcp.dm
+++ b/modular_nova/modules/morenarcotics/code/pcp.dm
@@ -23,7 +23,6 @@
/datum/reagent/drug/pcp //to an extent this is pretty much just super bath salts
name = "PCP"
description = "Pure rage put into chemical form."
- reagent_state = LIQUID
color = "#ffea2e"
overdose_threshold = 10 //really low overdose to keep people from abusing it too much
ph = 8
@@ -95,7 +94,6 @@
/datum/reagent/pcc
name = "PCC"
description = "A chemical precursor to PCP."
- reagent_state = SOLID
color = "#ffea2e" // rgb: 128, 128, 128
taste_description = "satiated rage"
ph = 7.3
diff --git a/modular_nova/modules/morenarcotics/code/quaalude.dm b/modular_nova/modules/morenarcotics/code/quaalude.dm
index 5e1c3dad5e19..7e96ca7fe7e3 100644
--- a/modular_nova/modules/morenarcotics/code/quaalude.dm
+++ b/modular_nova/modules/morenarcotics/code/quaalude.dm
@@ -6,7 +6,6 @@
/datum/reagent/drug/quaalude
name = "Quaalude"
description = "Relaxes the user, putting them in a hypnotic, drugged state. A favorite drug of kids from Brooklyn." //THAT WAS THE BEST FUCKIN DRUG EVER MADE
- reagent_state = LIQUID
color = "#ffe669"
overdose_threshold = 20
ph = 8
diff --git a/modular_nova/modules/morenarcotics/code/thc.dm b/modular_nova/modules/morenarcotics/code/thc.dm
index 925ce3a02a55..9fbff593f6e8 100644
--- a/modular_nova/modules/morenarcotics/code/thc.dm
+++ b/modular_nova/modules/morenarcotics/code/thc.dm
@@ -41,7 +41,6 @@
/datum/reagent/drug/thc
name = "THC"
description = "A chemical found in cannabis that serves as its main psychoactive component."
- reagent_state = LIQUID
color = "#cfa40c"
overdose_threshold = 30 //just gives funny effects, but doesnt hurt you; thc has no actual known overdose
ph = 6
diff --git a/modular_nova/modules/moretraitoritems/code/weapons.dm b/modular_nova/modules/moretraitoritems/code/weapons.dm
index c0c3a069ab6a..2e3b5dfa841a 100644
--- a/modular_nova/modules/moretraitoritems/code/weapons.dm
+++ b/modular_nova/modules/moretraitoritems/code/weapons.dm
@@ -6,7 +6,7 @@
icon_state = "c38_panther"
accepted_magazine_type = /obj/item/ammo_box/magazine/internal/cylinder
-/obj/item/ammo_casing/a357/peacemaker
+/obj/item/ammo_casing/c357/peacemaker
name = ".357 Peacemaker bullet casing"
desc = "A .357 Peacemaker bullet casing."
caliber = CALIBER_357
@@ -40,7 +40,7 @@
name = "speed loader (.357 Peacemaker)"
desc = "Designed to quickly reload revolvers."
icon_state = "357"
- ammo_type = /obj/item/ammo_casing/a357/peacemaker
+ ammo_type = /obj/item/ammo_casing/c357/peacemaker
max_ammo = 7
multiple_sprites = AMMO_BOX_PER_BULLET
item_flags = NO_MAT_REDEMPTION
diff --git a/modular_nova/modules/mutants/code/mutant_species.dm b/modular_nova/modules/mutants/code/mutant_species.dm
index e353d724a640..e5af27ea3c03 100644
--- a/modular_nova/modules/mutants/code/mutant_species.dm
+++ b/modular_nova/modules/mutants/code/mutant_species.dm
@@ -72,7 +72,7 @@
/// The cooldown before the mutant can start regenerating
COOLDOWN_DECLARE(regen_cooldown)
-/datum/species/mutant/infectious/on_species_gain(mob/living/carbon/human/human_who_gained_species, datum/species/old_species, pref_load)
+/datum/species/mutant/infectious/on_species_gain(mob/living/carbon/human/human_who_gained_species, datum/species/old_species, pref_load, regenerate_icons)
. = ..()
human_who_gained_species.AddComponent(/datum/component/mutant_hands, mutant_hand_path = hands_to_give)
RegisterSignal(human_who_gained_species, COMSIG_MOB_AFTER_APPLY_DAMAGE, PROC_REF(queue_regeneration))
diff --git a/modular_nova/modules/oneclickantag/code/oneclickantag.dm b/modular_nova/modules/oneclickantag/code/oneclickantag.dm
index 068282ecaeb2..c812dac2bd6d 100644
--- a/modular_nova/modules/oneclickantag/code/oneclickantag.dm
+++ b/modular_nova/modules/oneclickantag/code/oneclickantag.dm
@@ -49,7 +49,7 @@ If anyone can figure out how to get Obsessed to work I would be very appreciativ
for(var/role in GLOB.special_roles)
if(role in list(ROLE_MALF, ROLE_PAI, ROLE_SENTIENCE, ROLE_OBSESSED))
continue
- dat += "Make [role](s)."
+ dat += "Make [role](s)."
if(antag_is_ghostrole(role))
dat += " (Requires Ghosts)"
dat += " "
diff --git a/modular_nova/modules/opposing_force/code/admin_procs.dm b/modular_nova/modules/opposing_force/code/admin_procs.dm
index f91b48218746..3515faab938b 100644
--- a/modular_nova/modules/opposing_force/code/admin_procs.dm
+++ b/modular_nova/modules/opposing_force/code/admin_procs.dm
@@ -2,7 +2,7 @@ ADMIN_VERB(request_more_opfor, R_FUN, "Request OPFOR", "Request players sign up
var/asked = 0
for(var/mob/living/carbon/human/human in GLOB.alive_player_list)
if(human.client?.prefs?.read_preference(/datum/preference/toggle/be_antag))
- to_chat(human, examine_block(span_greentext("The admins are looking for OPFOR players, if you're interested, sign up in the OOC tab!")))
+ to_chat(human, boxed_message(span_greentext("The admins are looking for OPFOR players, if you're interested, sign up in the OOC tab!")))
asked++
message_admins("[ADMIN_LOOKUP(user)] has requested more OPFOR players! (Asked: [asked] players)")
diff --git a/modular_nova/modules/opposing_force/code/opposing_force_datum.dm b/modular_nova/modules/opposing_force/code/opposing_force_datum.dm
index 62d8b7781fd2..d6f9255f423b 100644
--- a/modular_nova/modules/opposing_force/code/opposing_force_datum.dm
+++ b/modular_nova/modules/opposing_force/code/opposing_force_datum.dm
@@ -104,10 +104,10 @@
/// Builds the HTML panel entry for the round end report
/datum/opposing_force/proc/build_html_panel_entry()
var/list/opfor_entry = list("[mind_reference.key] - ")
- opfor_entry += "PM "
+ opfor_entry += "PM "
if(mind_reference.current)
- opfor_entry += "FLW "
- opfor_entry += "Show OPFOR Panel"
+ opfor_entry += "FLW "
+ opfor_entry += "Show OPFOR Panel"
return opfor_entry.Join()
/datum/opposing_force/ui_interact(mob/user, datum/tgui/ui)
@@ -308,11 +308,11 @@
return
for(var/datum/opposing_force_objective/objective as anything in objectives)
if(objective.status == OPFOR_OBJECTIVE_STATUS_NOT_REVIEWED)
- to_chat(usr, examine_block(span_command_headset(span_pink("OPFOR: ERROR, some objectives have not been reviewed. Please approve/deny all objectives."))))
+ to_chat(usr, boxed_message(span_command_headset(span_pink("OPFOR: ERROR, some objectives have not been reviewed. Please approve/deny all objectives."))))
return
for(var/datum/opposing_force_selected_equipment/equipment as anything in selected_equipment)
if(equipment.status == OPFOR_EQUIPMENT_STATUS_NOT_REVIEWED)
- to_chat(usr, examine_block(span_command_headset(span_pink("OPFOR: ERROR, some equipment requests have not been reviewed. Please approve/deny all equipment requests."))))
+ to_chat(usr, boxed_message(span_command_headset(span_pink("OPFOR: ERROR, some equipment requests have not been reviewed. Please approve/deny all equipment requests."))))
return
SSopposing_force.approve(src, usr)
if("approve_all")
@@ -388,7 +388,7 @@
if(choice == "No")
return
handling_admin = get_admin_ckey(user)
- to_chat(mind_reference.current, examine_block(span_nicegreen("Your OPFOR application is now being handled by [handling_admin].")))
+ to_chat(mind_reference.current, boxed_message(span_nicegreen("Your OPFOR application is now being handled by [handling_admin].")))
send_admins_opfor_message("HANDLE: [ADMIN_LOOKUPFLW(user)] is handling [mind_reference.key]'s OPFOR application.")
send_system_message("[handling_admin] has assigned themselves to this application")
add_log(user.ckey, "Assigned self to application")
@@ -519,7 +519,7 @@
add_log(user.ckey, "Submitted to the OPFOR subsystem")
send_system_message("[user ? get_admin_ckey(user) : "The OPFOR subsystem"] has submitted the application for review")
send_admins_opfor_message(span_command_headset("SUBMISSION: [ADMIN_LOOKUPFLW(user)] has submitted their OPFOR application. They are number [queue_position] in the queue."))
- to_chat(usr, examine_block(span_nicegreen(("You have been added to the queue for the OPFOR subsystem. You are number [queue_position] in line."))))
+ to_chat(usr, boxed_message(span_nicegreen(("You have been added to the queue for the OPFOR subsystem. You are number [queue_position] in line."))))
/datum/opposing_force/proc/modify_request(mob/user)
if(status == OPFOR_STATUS_CHANGES_REQUESTED)
@@ -552,7 +552,7 @@
opfor.status = OPFOR_OBJECTIVE_STATUS_DENIED
SEND_SOUND(mind_reference.current, sound('modular_nova/modules/opposing_force/sound/denied.ogg'))
add_log(denier.ckey, "Denied application")
- to_chat(mind_reference.current, examine_block(span_redtext("Your OPFOR application has been denied by [denier ? get_admin_ckey(denier) : "the OPFOR subsystem"]!")))
+ to_chat(mind_reference.current, boxed_message(span_redtext("Your OPFOR application has been denied by [denier ? get_admin_ckey(denier) : "the OPFOR subsystem"]!")))
send_system_message(get_admin_ckey(denier) + " has denied the application with the following reason: [reason]")
send_admins_opfor_message("[span_red("DENIED")]: [ADMIN_LOOKUPFLW(denier)] has denied [ckey]'s application([reason ? reason : "No reason specified"])")
ticket_counter_add_handled(denier.key, 1)
@@ -571,7 +571,7 @@
continue
objective_denied = TRUE
break
- to_chat(mind_reference.current, examine_block(span_greentext("Your OPFOR application has been [objective_denied ? span_bold("partially approved (please view your OPFOR for details)") : span_bold("fully approved")] by [approver ? get_admin_ckey(approver) : "the OPFOR subsystem"]!")))
+ to_chat(mind_reference.current, boxed_message(span_greentext("Your OPFOR application has been [objective_denied ? span_bold("partially approved (please view your OPFOR for details)") : span_bold("fully approved")] by [approver ? get_admin_ckey(approver) : "the OPFOR subsystem"]!")))
send_system_message("[approver ? get_admin_ckey(approver) : "The OPFOR subsystem"] has approved the application")
send_admins_opfor_message("[span_green("APPROVED")]: [ADMIN_LOOKUPFLW(approver)] has approved [ckey]'s application")
ticket_counter_add_handled(approver.key, 1)
@@ -713,7 +713,7 @@
log_admin(msg)
/datum/opposing_force/proc/send_admins_opfor_message(message)
- message = "[span_pink("OPFOR:")] [span_admin("[message] (Show Panel)")]"
+ message = "[span_pink("OPFOR:")] [span_admin("[message] (Show Panel)")]"
to_chat(GLOB.admins,
type = MESSAGE_TYPE_ADMINLOG,
html = message,
@@ -744,7 +744,7 @@
/datum/opposing_force/proc/broadcast_queue_change()
var/queue_number = SSopposing_force.get_queue_position(src)
- to_chat(mind_reference.current, examine_block(span_nicegreen("Your OPFOR application is now number [queue_number] in the queue.")))
+ to_chat(mind_reference.current, boxed_message(span_nicegreen("Your OPFOR application is now number [queue_number] in the queue.")))
send_system_message("Application is now number [queue_number] in the queue")
/datum/opposing_force/proc/send_message(mob/user, message)
@@ -847,7 +847,7 @@
send_system_message("ERROR: You are muted.")
return
if(user.ckey != handling_admin && GLOB.directory[handling_admin])
- to_chat(GLOB.directory[handling_admin], span_pink("OPFOR: [user] has pinged their OPFOR admin chat! (Show Panel)"))
+ to_chat(GLOB.directory[handling_admin], span_pink("OPFOR: [user] has pinged their OPFOR admin chat! (Show Panel)"))
SEND_SOUND(GLOB.directory[handling_admin], sound('sound/misc/bloop.ogg'))
send_system_message("Handling admin pinged.")
COOLDOWN_START(src, ping_cooldown, OPFOR_PING_COOLDOWN)
diff --git a/modular_nova/modules/primitive_catgirls/code/species.dm b/modular_nova/modules/primitive_catgirls/code/species.dm
index 6cdab65720a1..88d1bbd0de5a 100644
--- a/modular_nova/modules/primitive_catgirls/code/species.dm
+++ b/modular_nova/modules/primitive_catgirls/code/species.dm
@@ -36,7 +36,7 @@
always_customizable = TRUE
-/datum/species/human/felinid/primitive/on_species_gain(mob/living/carbon/new_primitive, datum/species/old_species, pref_load)
+/datum/species/human/felinid/primitive/on_species_gain(mob/living/carbon/new_primitive, datum/species/old_species, pref_load, regenerate_icons)
. = ..()
var/mob/living/carbon/human/hearthkin = new_primitive
if(!istype(hearthkin))
diff --git a/modular_nova/modules/reagent_forging/code/forge.dm b/modular_nova/modules/reagent_forging/code/forge.dm
index 3ce81f9e4ec5..1da40c87f120 100644
--- a/modular_nova/modules/reagent_forging/code/forge.dm
+++ b/modular_nova/modules/reagent_forging/code/forge.dm
@@ -672,7 +672,7 @@
balloon_alert_to_viewers("setting [ceramic_item]")
if(!do_after(user, ceramic_speed, target = src))
- fail_message("stopped setting [ceramic_item]")
+ fail_message(user, "stopped setting [ceramic_item]")
return
balloon_alert(user, "finished setting [ceramic_item]")
diff --git a/modular_nova/modules/serenitystation/code/areas.dm b/modular_nova/modules/serenitystation/code/areas.dm
index b58619b34334..f8fd73610006 100644
--- a/modular_nova/modules/serenitystation/code/areas.dm
+++ b/modular_nova/modules/serenitystation/code/areas.dm
@@ -2,7 +2,7 @@
name = "Forest Planet"
icon = 'icons/area/areas_station.dmi'
icon_state = "explored"
- has_gravity = STANDARD_GRAVITY
+ default_gravity = STANDARD_GRAVITY
flags_1 = NONE
area_flags = UNIQUE_AREA | FLORA_ALLOWED
ambience_index = AMBIENCE_FOREST
diff --git a/modular_nova/modules/synths/code/reagents/reagents.dm b/modular_nova/modules/synths/code/reagents/reagents.dm
index d474297b641a..0cebf5111856 100644
--- a/modular_nova/modules/synths/code/reagents/reagents.dm
+++ b/modular_nova/modules/synths/code/reagents/reagents.dm
@@ -18,7 +18,6 @@
/datum/reagent/medicine/system_cleaner
name = "System Cleaner"
description = "Neutralizes harmful chemical compounds inside synthetic systems and refreshes system software."
- reagent_state = LIQUID
color = "#F1C40F"
taste_description = "ethanol"
metabolization_rate = 2 * REAGENTS_METABOLISM
@@ -38,7 +37,6 @@
/datum/reagent/medicine/liquid_solder
name = "Liquid Solder"
description = "Repairs brain damage in synthetics."
- reagent_state = LIQUID
color = "#727272"
taste_description = "metal"
process_flags = REAGENT_SYNTHETIC
@@ -52,7 +50,6 @@
/datum/reagent/medicine/nanite_slurry
name = "Nanite Slurry"
description = "A localized swarm of nanomachines specialized in repairing mechanical parts. Due to the nanites needing to interface with the host's systems to repair them, a surplus of them will cause them to overheat, or for the swarm to forcefully eject out of the mouth of organics for safety."
- reagent_state = LIQUID
color = "#cccccc"
overdose_threshold = 20
metabolization_rate = 1.25 * REAGENTS_METABOLISM
@@ -86,7 +83,6 @@
name = "Taste Suppressor"
description = "A colorless medicine aimed to dull the sense of taste of those that consumed it, as long as it's in their system."
color = "#AAAAAA77"
- reagent_state = LIQUID
metabolization_rate = 0.5 * REAGENTS_METABOLISM
chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
chemical_flags_nova = REAGENT_BLOOD_REGENERATING // It has REAGENT_BLOOD_REGENERATING only because it makes it so Hemophages can safely drink it, which makes complete sense considering this is meant to suppress their tumor's reactiveness to anything that doesn't regenerate blood.
diff --git a/modular_nova/modules/synths/code/species/synthetic.dm b/modular_nova/modules/synths/code/species/synthetic.dm
index c5ef22643291..6413c9560813 100644
--- a/modular_nova/modules/synths/code/species/synthetic.dm
+++ b/modular_nova/modules/synths/code/species/synthetic.dm
@@ -86,7 +86,7 @@
playsound(transformer.loc, 'sound/machines/chime.ogg', 50, TRUE)
transformer.visible_message(span_notice("[transformer]'s [screen ? "monitor lights up" : "eyes flicker to life"]!"), span_notice("All systems nominal. You're back online!"))
-/datum/species/synthetic/on_species_gain(mob/living/carbon/human/transformer)
+/datum/species/synthetic/on_species_gain(mob/living/carbon/human/transformer, datum/species/old_species, pref_load, regenerate_icons)
. = ..()
RegisterSignal(transformer, COMSIG_ATOM_EMAG_ACT, PROC_REF(on_emag_act))
diff --git a/modular_nova/modules/taur_mechanics/code/constrict.dm b/modular_nova/modules/taur_mechanics/code/constrict.dm
index 10b8c415be64..4e7b371276bb 100644
--- a/modular_nova/modules/taur_mechanics/code/constrict.dm
+++ b/modular_nova/modules/taur_mechanics/code/constrict.dm
@@ -37,7 +37,7 @@
return FALSE
return TRUE
-/datum/action/innate/constrict/do_ability(mob/living/caller, atom/clicked_on)
+/datum/action/innate/constrict/do_ability(mob/living/clicker, atom/clicked_on)
if (!isliving(clicked_on))
if (tail)
qdel(tail)
@@ -48,19 +48,19 @@
var/mob/living/living_target = clicked_on
- if (living_target == caller)
+ if (living_target == clicker)
return TRUE
if (!can_coil_target(living_target))
return TRUE
- caller.balloon_alert_to_viewers("starts coiling tail")
- caller.visible_message(span_warning("[caller] starts coiling [caller.p_their()] tail around [living_target]..."), span_notice("You start coiling your tail around [living_target]..."), ignored_mobs = list(living_target))
- to_chat(living_target, span_userdanger("[caller] starts coiling [caller.p_their()] tail around you!"))
+ clicker.balloon_alert_to_viewers("starts coiling tail")
+ clicker.visible_message(span_warning("[clicker] starts coiling [clicker.p_their()] tail around [living_target]..."), span_notice("You start coiling your tail around [living_target]..."), ignored_mobs = list(living_target))
+ to_chat(living_target, span_userdanger("[clicker] starts coiling [clicker.p_their()] tail around you!"))
owner.changeNext_move(base_coil_delay) // prevent interaction during this
unset_ranged_ability(owner) // because we sleep
- var/result = do_after(caller, base_coil_delay, living_target, IGNORE_HELD_ITEM, extra_checks = CALLBACK(src, PROC_REF(can_coil_target), living_target))
+ var/result = do_after(clicker, base_coil_delay, living_target, IGNORE_HELD_ITEM, extra_checks = CALLBACK(src, PROC_REF(can_coil_target), living_target))
owner.changeNext_move(-base_coil_delay)
if (!result)
return TRUE
diff --git a/modular_nova/modules/teshari/code/_teshari.dm b/modular_nova/modules/teshari/code/_teshari.dm
index 5a035aa617a3..70b16b4fb57f 100644
--- a/modular_nova/modules/teshari/code/_teshari.dm
+++ b/modular_nova/modules/teshari/code/_teshari.dm
@@ -69,7 +69,7 @@
regenerate_organs(tesh, src, visual_only = TRUE)
tesh.update_body(TRUE)
-/datum/species/teshari/on_species_gain(mob/living/carbon/human/new_teshari, datum/species/old_species, pref_load)
+/datum/species/teshari/on_species_gain(mob/living/carbon/human/new_teshari, datum/species/old_species, pref_load, regenerate_icons)
. = ..()
passtable_on(new_teshari, SPECIES_TRAIT)
diff --git a/modular_nova/modules/title_screen/code/new_player.dm b/modular_nova/modules/title_screen/code/new_player.dm
index ef571bc88fd5..5445f13aa159 100644
--- a/modular_nova/modules/title_screen/code/new_player.dm
+++ b/modular_nova/modules/title_screen/code/new_player.dm
@@ -188,9 +188,9 @@
qdel(query_get_new_polls)
return
if(query_get_new_polls.NextRow())
- output +={"POLLS (NEW)"}
+ output +={"POLLS (NEW)"}
else
- output +={"POLLS"}
+ output +={"POLLS"}
qdel(query_get_new_polls)
if(QDELETED(src))
return
diff --git a/modular_nova/modules/title_screen/code/title_screen_html.dm b/modular_nova/modules/title_screen/code/title_screen_html.dm
index 30f3f2949e2f..4648d839b16f 100644
--- a/modular_nova/modules/title_screen/code/title_screen_html.dm
+++ b/modular_nova/modules/title_screen/code/title_screen_html.dm
@@ -100,22 +100,22 @@ GLOBAL_LIST_EMPTY(startup_messages)
dat += {"
"}
if(!SSticker || SSticker.current_state <= GAME_STATE_PREGAME)
- dat += {"[ready == PLAYER_READY_TO_PLAY ? "☑ READY" : "☒ READY"]"}
+ dat += {"[ready == PLAYER_READY_TO_PLAY ? "☑ READY" : "☒ READY"]"}
else
dat += {"
- JOIN GAME
- CREW MANIFEST
+ JOIN GAME
+ CREW MANIFEST
"}
- dat += {"OBSERVE"}
+ dat += {"OBSERVE"}
dat += {"
- SETUP CHARACTER ([uppertext(client.prefs.read_preference(/datum/preference/name/real_name))])
- GAME OPTIONS
- [client.prefs.read_preference(/datum/preference/toggle/be_antag) ? "☑ BE ANTAGONIST" : "☒ BE ANTAGONIST"]
+ SETUP CHARACTER ([uppertext(client.prefs.read_preference(/datum/preference/name/real_name))])
+ GAME OPTIONS
+ [client.prefs.read_preference(/datum/preference/toggle/be_antag) ? "☑ BE ANTAGONIST" : "☒ BE ANTAGONIST"]
- SWAP SERVERS
+ SWAP SERVERS
"}
if(!is_guest_key(src.key))
diff --git a/modular_nova/modules/wargame_projectors/code/game_kit.dm b/modular_nova/modules/wargame_projectors/code/game_kit.dm
index aebd53df90dd..4296175100e6 100644
--- a/modular_nova/modules/wargame_projectors/code/game_kit.dm
+++ b/modular_nova/modules/wargame_projectors/code/game_kit.dm
@@ -1,7 +1,6 @@
/datum/map_template/holodeck/wargame
name = "Holodeck - Naval Wargames"
template_id = "holodeck_wargame"
- description = "more than enough space for a quick bout of naval warfare"
mappath = "_maps/nova/holodeck_wargame.dmm"
/obj/item/storage/briefcase/secure/white/wargame_kit
diff --git a/modular_nova/modules/xenoarchartifacts/artifacts/artifact_replicator.dm b/modular_nova/modules/xenoarchartifacts/artifacts/artifact_replicator.dm
index aadf4379780f..ac37c1f0d6f7 100644
--- a/modular_nova/modules/xenoarchartifacts/artifacts/artifact_replicator.dm
+++ b/modular_nova/modules/xenoarchartifacts/artifacts/artifact_replicator.dm
@@ -128,7 +128,7 @@
var/dat = "The control panel displays an incomprehensible selection of controls, many with unusual markings or text around them. "
dat += " "
for(var/index=1, index<=construction.len, index++)
- dat += "\[[construction[index]]\] "
+ dat += "\[[construction[index]]\] "
var/datum/browser/popup = new(user, "alien_replicator")
popup.set_content(dat)
diff --git a/modular_nova/modules/xenoarchartifacts/machines/artifact_analyzer.dm b/modular_nova/modules/xenoarchartifacts/machines/artifact_analyzer.dm
index 3ae414ccbc36..412716bf548a 100644
--- a/modular_nova/modules/xenoarchartifacts/machines/artifact_analyzer.dm
+++ b/modular_nova/modules/xenoarchartifacts/machines/artifact_analyzer.dm
@@ -65,10 +65,10 @@
dat += "Unable to locate analysis pad. "
else if(scan_in_progress)
dat += "Please wait. Analysis in progress. "
- dat += "Halt scanning "
+ dat += "Halt scanning "
else
dat += "Scanner is ready. "
- dat += "Begin scanning "
+ dat += "Begin scanning "
dat += " "
dat += ""
diff --git a/modular_nova/modules/xenoarchartifacts/machines/artifact_harvester.dm b/modular_nova/modules/xenoarchartifacts/machines/artifact_harvester.dm
index 7fc411256c36..b8ec4a9294f1 100644
--- a/modular_nova/modules/xenoarchartifacts/machines/artifact_harvester.dm
+++ b/modular_nova/modules/xenoarchartifacts/machines/artifact_harvester.dm
@@ -69,14 +69,14 @@
if(harvesting || draining)
if(harvesting)
dat += "Please wait. Harvesting in progress ([round((inserted_battery.stored_charge/inserted_battery.capacity)*100)]%). "
- dat += "Halt early "
+ dat += "Halt early "
if(draining)
dat += "Please wait. Energy dump in progress ([round((inserted_battery.stored_charge/inserted_battery.capacity)*100)]%). "
else if(inserted_battery)
dat += "[inserted_battery.name] inserted, charge level: [round(inserted_battery.stored_charge,1)]/[inserted_battery.capacity] ([round((inserted_battery.stored_charge/inserted_battery.capacity)*100)]%) "
- dat += "Eject battery "
- dat += "Drain battery of all charge "
- dat += "Begin harvesting "
+ dat += "Eject battery "
+ dat += "Drain battery of all charge "
+ dat += "Begin harvesting "
else
dat += "No battery inserted. "
@@ -84,7 +84,7 @@
dat += "Unable to locate analysis pad. "
dat += ""
- dat += "Refresh"
+ dat += "Refresh"
var/datum/browser/popup = new(user, "artharvester", name, 450, 500)
popup.set_content(dat)
diff --git a/modular_nova/modules/xenoarchartifacts/obj/particle_battery.dm b/modular_nova/modules/xenoarchartifacts/obj/particle_battery.dm
index f392a4b5dfa6..5fdb64ad6ff1 100644
--- a/modular_nova/modules/xenoarchartifacts/obj/particle_battery.dm
+++ b/modular_nova/modules/xenoarchartifacts/obj/particle_battery.dm
@@ -90,17 +90,17 @@
dat += "Device is inactive. "
dat += "Total Power: [round(inserted_battery.stored_charge, 1)]/[inserted_battery.capacity]
"
- dat += "Timed activation:--- [time >= 1000 ? "[time/10]" : time >= 100 ? " [time/10]" : " [time/10]" ] +++ "
+ dat += "Timed activation:--- [time >= 1000 ? "[time/10]" : time >= 100 ? " [time/10]" : " [time/10]" ] +++ "
if(cooldown)
dat += "Cooldown in progress, please wait. "
dat += " "
else if(!activated && world.time >= cooldown_to_start)
- dat += "Start "
- dat += "Start in timed mode "
+ dat += "Start "
+ dat += "Start in timed mode "
else
- dat += "Shutdown emission "
+ dat += "Shutdown emission "
dat += " "
- dat += "Eject battery "
+ dat += "Eject battery "
else
dat += "Please insert battery "
@@ -113,7 +113,7 @@
dat += " "
dat += ""
- dat += "Refresh"
+ dat += "Refresh"
var/datum/browser/popup = new(user, "utilizer", name, 400, 500)
popup.set_content(dat)
diff --git a/rust_g.dll b/rust_g.dll
index d3aebf712170..157fb64acaf3 100644
Binary files a/rust_g.dll and b/rust_g.dll differ
diff --git a/sound/attributions.txt b/sound/attributions.txt
index a6cbf21f8369..bd2e408c6c55 100644
--- a/sound/attributions.txt
+++ b/sound/attributions.txt
@@ -23,7 +23,6 @@ champagne_pop.ogg is credited to ultradust on freesound https://freesound.org/pe
can_open.ogg adapted from https://freesound.org/people/MaxDemianAGL/sounds/130031/
can_shake.ogg adapted from https://freesound.org/people/mcmast/sounds/456703/
-
splatter.ogg adapted from https://freesound.org/people/Rocktopus/sounds/233418/
hohoho.ogg and hehe.ogg are cut from a recording by Nanakisan on freesound: https://freesound.org/people/Nanakisan/sounds/253534/
mbox_full.ogg and mbox_end.ogg make use of The Ragtime Drummer by James Lent, in the public domain
@@ -181,8 +180,6 @@ https://freesound.org/people/shw489/sounds/234389/
soup_boil1.ogg through soup_boil5.ogg and soup_boil_end.ogg are taken from Boiling Soup from Freesoung.org (CC4) and converted to OGG / split apart (but is otherwise unchanged):
https://freesound.org/people/jorickhoofd/sounds/632783/
-
-
valve_opening.ogg was made by mixing water flowing samples from:
https://freesound.org/people/scriotxstudios/sounds/349111/?attribution=1 and squeaky scrape sound from:
https://freesound.org/people/Department64/sounds/669028/ which was modified with lower pitch
@@ -191,7 +188,6 @@ liquid_pour2.ogg and liquid_pour3.ogg were cut from
https://freesound.org/people/MattRuthSound/sounds/561896/
https://freesound.org/people/MattRuthSound/sounds/561895/
-
roaring_fire.ogg made from: 10835 big fire loop.wav by Robinhood76 -- https://freesound.org/s/612277/ -- License: Attribution NonCommercial 4.0
fire_puff made from: Bonfire Being Lit by samararaine -- https://freesound.org/s/186374/ -- License: Creative Commons 0
@@ -208,8 +204,6 @@ Bottle Tap.wav by alex_alexalex -- https://freesound.org/s/395492/ -- License: A
beaker_place.ogg was made by cutting and lowering pitch:
place glass object.wav by milpower -- https://freesound.org/s/353105/ -- License: Creative Commons 0
-
-
glass_reverse.ogg is adapted from a combination of:
https://freesound.org/people/C_Rogers/sounds/203368/ -- glass-shattering-hit_01.ogg by C_Rogers on freesound.org (CC0)
https://freesound.org/people/Czarcazas/sounds/330800/ -- Audio reversal/fading of Shattering Glass (Small) by Czarcazas -- https://freesound.org/s/330800/ -- License: Attribution 3.0
@@ -217,3 +211,10 @@ https://freesound.org/people/Czarcazas/sounds/330800/ -- Audio reversal/fading o
sound/effects/bonk.ogg - recorded by oranges on a coke zero bottle, edited by ninjanomnom, released to public domain
sound\items\weapons\hammer_death_scream.ogg - Undefeatablesos' scream recorded by Niron3206, edited by Niron3206, License: Creative Commons 0
+
+sound/machines/sink-faucet.ogg -- https://freesound.org/people/FOSSarts/sounds/740086/ -- by FOSSarts (CC0)
+
+cryo_1.ogg, cryo_2.ogg, cryo_3.ogg, cryo_4.ogg, cryo_5.ogg, cryo_6.ogg, cryo_7.ogg, cryo_8.ogg, cryo_9.ogg, cryo_10.ogg:
+converted to OGG / split apart (but is otherwise unchanged) -- original from https://freesound.org/people/DudeAwesome/sounds/386023/ by DudeAwesome -- License: CC BY 4.0
+
+sound/items/dice_roll.ogg -- https://freesound.org/people/Crovic/sounds/661935/ -- by Crovic (CC0)
diff --git a/sound/effects/compressed_air/attribution.txt b/sound/effects/compressed_air/attribution.txt
index 1eff1ab75122..12d30ee0e19e 100644
--- a/sound/effects/compressed_air/attribution.txt
+++ b/sound/effects/compressed_air/attribution.txt
@@ -1,8 +1,6 @@
-compressed_air1.ogg is taken from Freesound and converted to ogg:
+tank_insert_clunky.ogg was created by mixing
https://freesound.org/people/Geoff-Bremner-Audio/sounds/682952/
-compressed_air2.ogg is taken from Freesound and converted to ogg:
-https://freesound.org/people/Geoff-Bremner-Audio/sounds/682816/
-tank_insert_clunky.ogg was created by mixing compressed_air1 and clunk sound from Freesound:
+and
https://freesound.org/people/BinaryMonkFlint/sounds/333296/
tank_remove_thunk.ogg was made by mixing two sound tracks from Freesound:
https://freesound.org/people/lowdjinn/sounds/533885/ and;
diff --git a/sound/effects/compressed_air/compressed_air1.ogg b/sound/effects/compressed_air/compressed_air1.ogg
deleted file mode 100644
index 95ff29fcb07a..000000000000
Binary files a/sound/effects/compressed_air/compressed_air1.ogg and /dev/null differ
diff --git a/sound/effects/compressed_air/compressed_air2.ogg b/sound/effects/compressed_air/compressed_air2.ogg
deleted file mode 100644
index b05f52ee4857..000000000000
Binary files a/sound/effects/compressed_air/compressed_air2.ogg and /dev/null differ
diff --git a/sound/items/dice_roll.ogg b/sound/items/dice_roll.ogg
new file mode 100644
index 000000000000..71048df839b4
Binary files /dev/null and b/sound/items/dice_roll.ogg differ
diff --git a/sound/items/weapons/peashoot.ogg b/sound/items/weapons/peashoot.ogg
new file mode 100644
index 000000000000..de4d5c1e4645
Binary files /dev/null and b/sound/items/weapons/peashoot.ogg differ
diff --git a/sound/machines/closet/attribution.txt b/sound/machines/closet/attribution.txt
new file mode 100644
index 000000000000..b3a195838e8f
--- /dev/null
+++ b/sound/machines/closet/attribution.txt
@@ -0,0 +1,2 @@
+closet_unlock.ogg made by sadboysuss, license: CC-BY-SA
+closet_lock.ogg made by sadboysuss, license: CC-BY-SA
\ No newline at end of file
diff --git a/sound/machines/closet/closet_lock.ogg b/sound/machines/closet/closet_lock.ogg
new file mode 100644
index 000000000000..d66d0cafbe93
Binary files /dev/null and b/sound/machines/closet/closet_lock.ogg differ
diff --git a/sound/machines/closet/closet_unlock.ogg b/sound/machines/closet/closet_unlock.ogg
new file mode 100644
index 000000000000..0c78ba219b0b
Binary files /dev/null and b/sound/machines/closet/closet_unlock.ogg differ
diff --git a/sound/machines/cryo/cryo_1.ogg b/sound/machines/cryo/cryo_1.ogg
new file mode 100644
index 000000000000..daad20b1f493
Binary files /dev/null and b/sound/machines/cryo/cryo_1.ogg differ
diff --git a/sound/machines/cryo/cryo_10.ogg b/sound/machines/cryo/cryo_10.ogg
new file mode 100644
index 000000000000..eedc518222f9
Binary files /dev/null and b/sound/machines/cryo/cryo_10.ogg differ
diff --git a/sound/machines/cryo/cryo_2.ogg b/sound/machines/cryo/cryo_2.ogg
new file mode 100644
index 000000000000..7375eff09ebe
Binary files /dev/null and b/sound/machines/cryo/cryo_2.ogg differ
diff --git a/sound/machines/cryo/cryo_3.ogg b/sound/machines/cryo/cryo_3.ogg
new file mode 100644
index 000000000000..4cbb033daff5
Binary files /dev/null and b/sound/machines/cryo/cryo_3.ogg differ
diff --git a/sound/machines/cryo/cryo_4.ogg b/sound/machines/cryo/cryo_4.ogg
new file mode 100644
index 000000000000..6dff7c7a1c17
Binary files /dev/null and b/sound/machines/cryo/cryo_4.ogg differ
diff --git a/sound/machines/cryo/cryo_5.ogg b/sound/machines/cryo/cryo_5.ogg
new file mode 100644
index 000000000000..ff11f6d173d3
Binary files /dev/null and b/sound/machines/cryo/cryo_5.ogg differ
diff --git a/sound/machines/cryo/cryo_6.ogg b/sound/machines/cryo/cryo_6.ogg
new file mode 100644
index 000000000000..575b727958ba
Binary files /dev/null and b/sound/machines/cryo/cryo_6.ogg differ
diff --git a/sound/machines/cryo/cryo_7.ogg b/sound/machines/cryo/cryo_7.ogg
new file mode 100644
index 000000000000..f6fcb2bd0e36
Binary files /dev/null and b/sound/machines/cryo/cryo_7.ogg differ
diff --git a/sound/machines/cryo/cryo_8.ogg b/sound/machines/cryo/cryo_8.ogg
new file mode 100644
index 000000000000..2884ffa5bd2c
Binary files /dev/null and b/sound/machines/cryo/cryo_8.ogg differ
diff --git a/sound/machines/cryo/cryo_9.ogg b/sound/machines/cryo/cryo_9.ogg
new file mode 100644
index 000000000000..c650837ed8b1
Binary files /dev/null and b/sound/machines/cryo/cryo_9.ogg differ
diff --git a/sound/machines/door/door_locked.ogg b/sound/machines/door/door_locked.ogg
deleted file mode 100644
index 81807127b091..000000000000
Binary files a/sound/machines/door/door_locked.ogg and /dev/null differ
diff --git a/sound/machines/license.txt b/sound/machines/license.txt
index dbccfd7ea096..69e52c94e4b7 100644
--- a/sound/machines/license.txt
+++ b/sound/machines/license.txt
@@ -4,3 +4,5 @@ This is licensed under CC-BY 4.0, found at https://creativecommons.org/licenses/
shutter.ogg adapted from Joseph Sardin on BigSoundBank
https://bigsoundbank.com/detail-2475-manual-roller-shutter-closing-out-2.html
+
+mail_sort.ogg adapted from csigusz_foxoup ob Freesound https://freesound.org/people/csigusz_foxoup/sounds/711428/
\ No newline at end of file
diff --git a/sound/machines/mail_sort.ogg b/sound/machines/mail_sort.ogg
new file mode 100644
index 000000000000..66ec79468d50
Binary files /dev/null and b/sound/machines/mail_sort.ogg differ
diff --git a/sound/machines/sink-faucet.ogg b/sound/machines/sink-faucet.ogg
new file mode 100644
index 000000000000..7102a3940308
Binary files /dev/null and b/sound/machines/sink-faucet.ogg differ
diff --git a/sound/misc/license.txt b/sound/misc/license.txt
index 2e596a4e128e..945c48e279d1 100644
--- a/sound/misc/license.txt
+++ b/sound/misc/license.txt
@@ -1,2 +1,4 @@
bloop.ogg by my man Tim Khan
(https://freesound.org/people/tim.kahn/sounds/130377/)
+
+prompt.ogg by Flleeppyy (https://github.com/Flleeppyy), originally for https://github.com/Monkestation/Monkestation2.0/pull/2621
diff --git a/sound/misc/prompt.ogg b/sound/misc/prompt.ogg
new file mode 100644
index 000000000000..e32942e0d3cb
Binary files /dev/null and b/sound/misc/prompt.ogg differ
diff --git a/sound/mobs/humanoids/ethereal/credits.txt b/sound/mobs/humanoids/ethereal/credits.txt
new file mode 100644
index 000000000000..a157ceacf9ed
--- /dev/null
+++ b/sound/mobs/humanoids/ethereal/credits.txt
@@ -0,0 +1,2 @@
+ethereal_hiss.ogg majorly edited/mixed by Sothanforax, based off of the original audio:
+Remix of 101127__CGEffex__Bug_Zapper_Long_moth_electrocution_Remix.wav by Timbre -- https://freesound.org/s/101334/ -- License: Attribution NonCommercial 4.0
diff --git a/sound/mobs/humanoids/ethereal/ethereal_hiss.ogg b/sound/mobs/humanoids/ethereal/ethereal_hiss.ogg
new file mode 100644
index 000000000000..969944ea4daa
Binary files /dev/null and b/sound/mobs/humanoids/ethereal/ethereal_hiss.ogg differ
diff --git a/sound/mobs/humanoids/felinid/attribution.txt b/sound/mobs/humanoids/felinid/attribution.txt
new file mode 100644
index 000000000000..dfd416150c6b
--- /dev/null
+++ b/sound/mobs/humanoids/felinid/attribution.txt
@@ -0,0 +1 @@
+felinid_hiss is catHisses2.wav by Zabuhailo -- https://freesound.org/s/146962/ -- License: Creative Commons 0
\ No newline at end of file
diff --git a/sound/mobs/humanoids/felinid/felinid_hiss.ogg b/sound/mobs/humanoids/felinid/felinid_hiss.ogg
new file mode 100644
index 000000000000..f343bd77fd1f
Binary files /dev/null and b/sound/mobs/humanoids/felinid/felinid_hiss.ogg differ
diff --git a/sound/mobs/humanoids/human/attribution.txt b/sound/mobs/humanoids/human/attribution.txt
index 20b8c14889a0..f56dc03f794b 100644
--- a/sound/mobs/humanoids/human/attribution.txt
+++ b/sound/mobs/humanoids/human/attribution.txt
@@ -1,4 +1,5 @@
The male sharp gasps are from https://freesound.org/people/bacruz666/sounds/341908/ and https://freesound.org/people/nettoi/sounds/677540/, the female sharp gasps are from https://freesound.org/people/drotzruhn/sounds/405203/
+human_hiss.ogg is all original work by Sothanforax, hereby licensed under CC BY-SA 3.0
{
male_sniff.ogg - https://freesound.org/people/Fluffayfish/sounds/327799/ , License: CC BY-NC 3.0
diff --git a/sound/mobs/humanoids/human/hiss/human_hiss.ogg b/sound/mobs/humanoids/human/hiss/human_hiss.ogg
new file mode 100644
index 000000000000..15f643b42208
Binary files /dev/null and b/sound/mobs/humanoids/human/hiss/human_hiss.ogg differ
diff --git a/sound/mobs/humanoids/lizard/credits.txt b/sound/mobs/humanoids/lizard/credits.txt
index 814b758f44da..820a38fb5937 100644
--- a/sound/mobs/humanoids/lizard/credits.txt
+++ b/sound/mobs/humanoids/lizard/credits.txt
@@ -1,2 +1,3 @@
lizard_scream_1 by n Beats. Lizard_scream_2 and lizard_scream_3 by -sihiL. Lizard_scream_3 edited Lord Saladin. Original PR by super12pl.
deathsound.ogg is originally "demon dying.wav" by THE_bizniss. It was converted and compressed into .ogg format. It and a link to its license can be found at https://freesound.org/s/37823/
+lizard_hiss was originally recorded by Garuda1982, minor editing by Sothanforax. license is at https://freesound.org/s/541656/
diff --git a/sound/mobs/humanoids/lizard/lizard_hiss.ogg b/sound/mobs/humanoids/lizard/lizard_hiss.ogg
new file mode 100644
index 000000000000..202c7929a137
Binary files /dev/null and b/sound/mobs/humanoids/lizard/lizard_hiss.ogg differ
diff --git a/sound/voice/repairbot/brick.ogg b/sound/mobs/non-humanoids/repairbot/brick.ogg
similarity index 100%
rename from sound/voice/repairbot/brick.ogg
rename to sound/mobs/non-humanoids/repairbot/brick.ogg
diff --git a/sound/voice/repairbot/cantanymore.ogg b/sound/mobs/non-humanoids/repairbot/cantanymore.ogg
similarity index 100%
rename from sound/voice/repairbot/cantanymore.ogg
rename to sound/mobs/non-humanoids/repairbot/cantanymore.ogg
diff --git a/sound/voice/repairbot/entropy.ogg b/sound/mobs/non-humanoids/repairbot/entropy.ogg
similarity index 100%
rename from sound/voice/repairbot/entropy.ogg
rename to sound/mobs/non-humanoids/repairbot/entropy.ogg
diff --git a/sound/voice/repairbot/fixit.ogg b/sound/mobs/non-humanoids/repairbot/fixit.ogg
similarity index 100%
rename from sound/voice/repairbot/fixit.ogg
rename to sound/mobs/non-humanoids/repairbot/fixit.ogg
diff --git a/sound/voice/repairbot/fixtouch.ogg b/sound/mobs/non-humanoids/repairbot/fixtouch.ogg
similarity index 100%
rename from sound/voice/repairbot/fixtouch.ogg
rename to sound/mobs/non-humanoids/repairbot/fixtouch.ogg
diff --git a/sound/voice/repairbot/passionproject.ogg b/sound/mobs/non-humanoids/repairbot/passionproject.ogg
similarity index 100%
rename from sound/voice/repairbot/passionproject.ogg
rename to sound/mobs/non-humanoids/repairbot/passionproject.ogg
diff --git a/sound/voice/repairbot/patchingholes.ogg b/sound/mobs/non-humanoids/repairbot/patchingholes.ogg
similarity index 100%
rename from sound/voice/repairbot/patchingholes.ogg
rename to sound/mobs/non-humanoids/repairbot/patchingholes.ogg
diff --git a/sound/voice/repairbot/pay.ogg b/sound/mobs/non-humanoids/repairbot/pay.ogg
similarity index 100%
rename from sound/voice/repairbot/pay.ogg
rename to sound/mobs/non-humanoids/repairbot/pay.ogg
diff --git a/sound/voice/repairbot/strings.ogg b/sound/mobs/non-humanoids/repairbot/strings.ogg
similarity index 100%
rename from sound/voice/repairbot/strings.ogg
rename to sound/mobs/non-humanoids/repairbot/strings.ogg
diff --git a/strings/round_start_sounds.txt b/strings/round_start_sounds.txt
index 9981097c3085..908abe2e58e4 100644
--- a/strings/round_start_sounds.txt
+++ b/strings/round_start_sounds.txt
@@ -1,3 +1,4 @@
+sound/music/lobby_music/title0.ogg
sound/music/lobby_music/title1.mod
sound/music/lobby_music/title2.ogg
sound/music/lobby_music/title3.ogg
diff --git a/strings/tips.txt b/strings/tips.txt
index a97e17a07832..ff5c83496422 100644
--- a/strings/tips.txt
+++ b/strings/tips.txt
@@ -228,7 +228,7 @@ Basketball requires teamwork. Passing the ball with LMB is instant and costs no
Clicking on a windoor rather then bumping into it will keep it open, you can click it again to close it.
Different weapons have different strengths. Some weapons, such as spears, floor tiles, and throwing stars, deal more damage when thrown compared to when attacked normally.
Spears are capable of breaking into secure lockers by striking them in melee!
-Don't be afraid to ask for help, whether from your peers or from admins.
+Don't be afraid to ask for help, whether from your peers or from admins and mentors.
Experiment with different setups of the Supermatter engine to maximize output, but don't risk the crew's safety to do so!
Felinids get temporarily distracted by laser pointers. Use this to your advantage when being pursued by one.
Firesuits and winter coats offer mild protection from the cold, allowing you to spend longer periods of time near breaches and space than if wearing nothing at all.
diff --git a/tgstation.dme b/tgstation.dme
index 69f73c25c569..ed495661710e 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -186,6 +186,7 @@
#include "code\__DEFINES\pronouns.dm"
#include "code\__DEFINES\qdel.dm"
#include "code\__DEFINES\quirks.dm"
+#include "code\__DEFINES\radial_defines.dm"
#include "code\__DEFINES\radiation.dm"
#include "code\__DEFINES\radio.dm"
#include "code\__DEFINES\radioactive_nebula.dm"
@@ -533,6 +534,7 @@
#include "code\__HELPERS\heap.dm"
#include "code\__HELPERS\hearted.dm"
#include "code\__HELPERS\honkerblast.dm"
+#include "code\__HELPERS\hud.dm"
#include "code\__HELPERS\icon_smoothing.dm"
#include "code\__HELPERS\icons.dm"
#include "code\__HELPERS\jatum.dm"
@@ -562,6 +564,7 @@
#include "code\__HELPERS\screen_objs.dm"
#include "code\__HELPERS\see_through_maps.dm"
#include "code\__HELPERS\shell.dm"
+#include "code\__HELPERS\shuttle.dm"
#include "code\__HELPERS\spatial_info.dm"
#include "code\__HELPERS\spawns.dm"
#include "code\__HELPERS\stack_trace.dm"
@@ -626,6 +629,7 @@
#include "code\_globalvars\lighting.dm"
#include "code\_globalvars\logging.dm"
#include "code\_globalvars\phobias.dm"
+#include "code\_globalvars\pipe_info.dm"
#include "code\_globalvars\rcd.dm"
#include "code\_globalvars\religion.dm"
#include "code\_globalvars\silo.dm"
@@ -648,6 +652,7 @@
#include "code\_globalvars\lists\names.dm"
#include "code\_globalvars\lists\objects.dm"
#include "code\_globalvars\lists\ores_spawned.dm"
+#include "code\_globalvars\lists\pipe_recipes.dm"
#include "code\_globalvars\lists\plumbing.dm"
#include "code\_globalvars\lists\poll_ignore.dm"
#include "code\_globalvars\lists\quirks.dm"
@@ -853,6 +858,7 @@
#include "code\controllers\subsystem\persistence\scars.dm"
#include "code\controllers\subsystem\persistence\tattoos.dm"
#include "code\controllers\subsystem\persistence\trophies.dm"
+#include "code\controllers\subsystem\persistence\trophy_fishes.dm"
#include "code\controllers\subsystem\processing\acid.dm"
#include "code\controllers\subsystem\processing\ai_basic_avoidance.dm"
#include "code\controllers\subsystem\processing\ai_behaviors.dm"
@@ -1145,6 +1151,7 @@
#include "code\datums\components\ai_has_target_timer.dm"
#include "code\datums\components\ai_listen_to_weather.dm"
#include "code\datums\components\ai_retaliate_advanced.dm"
+#include "code\datums\components\alternative_sharpness.dm"
#include "code\datums\components\amputating_limbs.dm"
#include "code\datums\components\anti_magic.dm"
#include "code\datums\components\appearance_on_aggro.dm"
@@ -1633,6 +1640,7 @@
#include "code\datums\elements\movement_turf_changer.dm"
#include "code\datums\elements\movetype_handler.dm"
#include "code\datums\elements\muffles_speech.dm"
+#include "code\datums\elements\nav_computer_icon.dm"
#include "code\datums\elements\nerfed_pulling.dm"
#include "code\datums\elements\no_crit_hitting.dm"
#include "code\datums\elements\noisy_movement.dm"
@@ -2498,6 +2506,8 @@
#include "code\game\objects\items\extinguisher.dm"
#include "code\game\objects\items\fireaxe.dm"
#include "code\game\objects\items\flamethrower.dm"
+#include "code\game\objects\items\flatpacks.dm"
+#include "code\game\objects\items\forensicsspoofer.dm"
#include "code\game\objects\items\frog_statue.dm"
#include "code\game\objects\items\gift.dm"
#include "code\game\objects\items\gun_maintenance.dm"
@@ -4311,6 +4321,7 @@
#include "code\modules\fishing\admin.dm"
#include "code\modules\fishing\bait.dm"
#include "code\modules\fishing\fish_catalog.dm"
+#include "code\modules\fishing\fish_mount.dm"
#include "code\modules\fishing\fish_movement.dm"
#include "code\modules\fishing\fishing_equipment.dm"
#include "code\modules\fishing\fishing_minigame.dm"
@@ -4329,6 +4340,7 @@
#include "code\modules\fishing\fish\types\freshwater.dm"
#include "code\modules\fishing\fish\types\holographic.dm"
#include "code\modules\fishing\fish\types\mining.dm"
+#include "code\modules\fishing\fish\types\rift.dm"
#include "code\modules\fishing\fish\types\ruins.dm"
#include "code\modules\fishing\fish\types\saltwater.dm"
#include "code\modules\fishing\fish\types\station.dm"
@@ -4860,7 +4872,6 @@
#include "code\modules\mining\equipment\explorer_gear.dm"
#include "code\modules\mining\equipment\grapple_gun.dm"
#include "code\modules\mining\equipment\kheiral_cuffs.dm"
-#include "code\modules\mining\equipment\kinetic_crusher.dm"
#include "code\modules\mining\equipment\lazarus_injector.dm"
#include "code\modules\mining\equipment\marker_beacons.dm"
#include "code\modules\mining\equipment\mineral_scanner.dm"
@@ -4870,6 +4881,11 @@
#include "code\modules\mining\equipment\survival_pod.dm"
#include "code\modules\mining\equipment\vent_pointer.dm"
#include "code\modules\mining\equipment\wormhole_jaunter.dm"
+#include "code\modules\mining\equipment\kinetic_crusher\kinetic_crusher.dm"
+#include "code\modules\mining\equipment\kinetic_crusher\kinetic_crusher_trophies.dm"
+#include "code\modules\mining\equipment\kinetic_crusher\trophies_fauna.dm"
+#include "code\modules\mining\equipment\kinetic_crusher\trophies_megafauna.dm"
+#include "code\modules\mining\equipment\kinetic_crusher\trophies_misc.dm"
#include "code\modules\mining\equipment\monster_organs\brimdust_sac.dm"
#include "code\modules\mining\equipment\monster_organs\monster_organ.dm"
#include "code\modules\mining\equipment\monster_organs\regenerative_core.dm"
@@ -5076,7 +5092,6 @@
#include "code\modules\mob\living\basic\lavaland\goliath\goliath.dm"
#include "code\modules\mob\living\basic\lavaland\goliath\goliath_actions.dm"
#include "code\modules\mob\living\basic\lavaland\goliath\goliath_ai.dm"
-#include "code\modules\mob\living\basic\lavaland\goliath\goliath_trophy.dm"
#include "code\modules\mob\living\basic\lavaland\goliath\tentacle.dm"
#include "code\modules\mob\living\basic\lavaland\gutlunchers\gutluncher_foodtrough.dm"
#include "code\modules\mob\living\basic\lavaland\gutlunchers\gutlunchers.dm"
@@ -5093,7 +5108,6 @@
#include "code\modules\mob\living\basic\lavaland\legion\spawn_legions.dm"
#include "code\modules\mob\living\basic\lavaland\lobstrosity\lobstrosity.dm"
#include "code\modules\mob\living\basic\lavaland\lobstrosity\lobstrosity_ai.dm"
-#include "code\modules\mob\living\basic\lavaland\lobstrosity\lobstrosity_trophy.dm"
#include "code\modules\mob\living\basic\lavaland\mook\mook.dm"
#include "code\modules\mob\living\basic\lavaland\mook\mook_abilities.dm"
#include "code\modules\mob\living\basic\lavaland\mook\mook_ai.dm"
@@ -5263,6 +5277,9 @@
#include "code\modules\mob\living\basic\trooper\syndicate.dm"
#include "code\modules\mob\living\basic\trooper\trooper.dm"
#include "code\modules\mob\living\basic\trooper\trooper_ai.dm"
+#include "code\modules\mob\living\basic\turtle\turtle.dm"
+#include "code\modules\mob\living\basic\turtle\turtle_ability.dm"
+#include "code\modules\mob\living\basic\turtle\turtle_ai.dm"
#include "code\modules\mob\living\basic\vermin\axolotl.dm"
#include "code\modules\mob\living\basic\vermin\butterfly.dm"
#include "code\modules\mob\living\basic\vermin\cockroach.dm"
@@ -5627,6 +5644,7 @@
#include "code\modules\photography\photos\photo.dm"
#include "code\modules\plumbing\ducts.dm"
#include "code\modules\plumbing\plumbers\_plumb_machinery.dm"
+#include "code\modules\plumbing\plumbers\_plumb_reagents.dm"
#include "code\modules\plumbing\plumbers\acclimator.dm"
#include "code\modules\plumbing\plumbers\bottler.dm"
#include "code\modules\plumbing\plumbers\destroyer.dm"
@@ -5943,6 +5961,7 @@
#include "code\modules\research\designs.dm"
#include "code\modules\research\destructive_analyzer.dm"
#include "code\modules\research\experimentor.dm"
+#include "code\modules\research\part_replacer.dm"
#include "code\modules\research\rdconsole.dm"
#include "code\modules\research\rdmachines.dm"
#include "code\modules\research\research_disk.dm"
@@ -6044,28 +6063,32 @@
#include "code\modules\research\xenobiology\vatgrowing\samples\viruses\_virus.dm"
#include "code\modules\security_levels\keycard_authentication.dm"
#include "code\modules\security_levels\security_level_datums.dm"
-#include "code\modules\shuttle\arrivals.dm"
-#include "code\modules\shuttle\assault_pod.dm"
-#include "code\modules\shuttle\battlecruiser_starfury.dm"
-#include "code\modules\shuttle\computer.dm"
-#include "code\modules\shuttle\docking.dm"
-#include "code\modules\shuttle\elevator.dm"
-#include "code\modules\shuttle\emergency.dm"
-#include "code\modules\shuttle\ferry.dm"
-#include "code\modules\shuttle\infiltrator.dm"
-#include "code\modules\shuttle\manipulator.dm"
-#include "code\modules\shuttle\medisim.dm"
-#include "code\modules\shuttle\monastery.dm"
-#include "code\modules\shuttle\navigation_computer.dm"
-#include "code\modules\shuttle\on_move.dm"
-#include "code\modules\shuttle\ripple.dm"
#include "code\modules\shuttle\shuttle.dm"
-#include "code\modules\shuttle\shuttle_rotate.dm"
-#include "code\modules\shuttle\spaceship_navigation_beacon.dm"
-#include "code\modules\shuttle\special.dm"
-#include "code\modules\shuttle\supply.dm"
-#include "code\modules\shuttle\syndicate.dm"
-#include "code\modules\shuttle\white_ship.dm"
+#include "code\modules\shuttle\misc\manipulator.dm"
+#include "code\modules\shuttle\misc\medisim.dm"
+#include "code\modules\shuttle\misc\ripple.dm"
+#include "code\modules\shuttle\misc\spaceship_navigation_beacon.dm"
+#include "code\modules\shuttle\misc\special.dm"
+#include "code\modules\shuttle\mobile_port\mobile_port.dm"
+#include "code\modules\shuttle\mobile_port\shuttle_move.dm"
+#include "code\modules\shuttle\mobile_port\shuttle_move_callbacks.dm"
+#include "code\modules\shuttle\mobile_port\shuttle_rotate_callbacks.dm"
+#include "code\modules\shuttle\mobile_port\variants\arrivals.dm"
+#include "code\modules\shuttle\mobile_port\variants\assault_pod.dm"
+#include "code\modules\shuttle\mobile_port\variants\battlecruiser_starfury.dm"
+#include "code\modules\shuttle\mobile_port\variants\elevator.dm"
+#include "code\modules\shuttle\mobile_port\variants\ferry.dm"
+#include "code\modules\shuttle\mobile_port\variants\infiltrator.dm"
+#include "code\modules\shuttle\mobile_port\variants\supply.dm"
+#include "code\modules\shuttle\mobile_port\variants\emergency\emergency.dm"
+#include "code\modules\shuttle\mobile_port\variants\emergency\emergency_console.dm"
+#include "code\modules\shuttle\mobile_port\variants\emergency\emergency_types.dm"
+#include "code\modules\shuttle\mobile_port\variants\emergency\pods.dm"
+#include "code\modules\shuttle\shuttle_consoles\monastery.dm"
+#include "code\modules\shuttle\shuttle_consoles\navigation_computer.dm"
+#include "code\modules\shuttle\shuttle_consoles\shuttle_console.dm"
+#include "code\modules\shuttle\shuttle_consoles\syndicate.dm"
+#include "code\modules\shuttle\shuttle_consoles\white_ship.dm"
#include "code\modules\shuttle\shuttle_events\_shuttle_events.dm"
#include "code\modules\shuttle\shuttle_events\blackhole.dm"
#include "code\modules\shuttle\shuttle_events\carp.dm"
@@ -6075,6 +6098,8 @@
#include "code\modules\shuttle\shuttle_events\player_controlled.dm"
#include "code\modules\shuttle\shuttle_events\projectile.dm"
#include "code\modules\shuttle\shuttle_events\turbulence.dm"
+#include "code\modules\shuttle\stationary_port\port_types.dm"
+#include "code\modules\shuttle\stationary_port\stationary_port.dm"
#include "code\modules\spatial_grid\cell_tracker.dm"
#include "code\modules\spells\spell.dm"
#include "code\modules\spells\spell_types\madness_curse.dm"
@@ -6426,6 +6451,7 @@
#include "code\modules\vending\liberation.dm"
#include "code\modules\vending\liberation_toy.dm"
#include "code\modules\vending\magivend.dm"
+#include "code\modules\vending\mail.dm"
#include "code\modules\vending\medical.dm"
#include "code\modules\vending\medical_wall.dm"
#include "code\modules\vending\megaseed.dm"
@@ -7696,6 +7722,7 @@
#include "modular_nova\modules\customization\modules\mob\living\carbon\human\species\skrell.dm"
#include "modular_nova\modules\customization\modules\mob\living\carbon\human\species\tajaran.dm"
#include "modular_nova\modules\customization\modules\mob\living\carbon\human\species\unathi.dm"
+#include "modular_nova\modules\customization\modules\mob\living\carbon\human\species\vampire.dm"
#include "modular_nova\modules\customization\modules\mob\living\carbon\human\species\vox.dm"
#include "modular_nova\modules\customization\modules\mob\living\carbon\human\species\vulpkanin.dm"
#include "modular_nova\modules\customization\modules\mob\living\carbon\human\species\xeno.dm"
diff --git a/tgui/global.d.ts b/tgui/global.d.ts
index d0bfdecf8909..35c0e9f57da1 100644
--- a/tgui/global.d.ts
+++ b/tgui/global.d.ts
@@ -41,6 +41,21 @@ type ByondType = {
*/
windowId: string;
+ /**
+ * True if javascript is running in BYOND.
+ */
+ IS_BYOND: boolean;
+
+ /**
+ * Version of Trident engine of Internet Explorer. Null if N/A.
+ */
+ TRIDENT: number | null;
+
+ /**
+ * Version of Blink engine of WebView2. Null if N/A.
+ */
+ BLINK: number | null;
+
/**
* If `true`, unhandled errors and common mistakes result in a blue screen
* of death, which stops this window from handling incoming messages and
@@ -175,4 +190,13 @@ interface Window {
Byond: ByondType;
__store__: Store;
__augmentStack__: (store: Store) => StackAugmentor;
+
+ // IE IndexedDB stuff.
+ msIndexedDB: IDBFactory;
+ msIDBTransaction: IDBTransaction;
+
+ // 516 byondstorage API.
+ hubStorage: Storage;
+ domainStorage: Storage;
+ serverStorage: Storage;
}
diff --git a/tgui/packages/common/storage.js b/tgui/packages/common/storage.ts
similarity index 53%
rename from tgui/packages/common/storage.js
rename to tgui/packages/common/storage.ts
index acf842f64083..b2564acf36dc 100644
--- a/tgui/packages/common/storage.js
+++ b/tgui/packages/common/storage.ts
@@ -7,9 +7,14 @@
*/
export const IMPL_MEMORY = 0;
-export const IMPL_LOCAL_STORAGE = 1;
+export const IMPL_HUB_STORAGE = 1;
export const IMPL_INDEXED_DB = 2;
+type StorageImplementation =
+ | typeof IMPL_MEMORY
+ | typeof IMPL_HUB_STORAGE
+ | typeof IMPL_INDEXED_DB;
+
const INDEXED_DB_VERSION = 1;
const INDEXED_DB_NAME = 'tgui';
const INDEXED_DB_STORE_NAME = 'storage-v1';
@@ -17,7 +22,15 @@ const INDEXED_DB_STORE_NAME = 'storage-v1';
const READ_ONLY = 'readonly';
const READ_WRITE = 'readwrite';
-const testGeneric = (testFn) => () => {
+type StorageBackend = {
+ impl: StorageImplementation;
+ get(key: string): Promise;
+ set(key: string, value: any): Promise;
+ remove(key: string): Promise;
+ clear(): Promise;
+};
+
+const testGeneric = (testFn: () => boolean) => (): boolean => {
try {
return Boolean(testFn());
} catch {
@@ -25,72 +38,77 @@ const testGeneric = (testFn) => () => {
}
};
-// Localstorage can sometimes throw an error, even if DOM storage is not
-// disabled in IE11 settings.
-// See: https://superuser.com/questions/1080011
-// prettier-ignore
-const testLocalStorage = testGeneric(() => (
- window.localStorage && window.localStorage.getItem
-));
+const testHubStorage = testGeneric(
+ () => window.hubStorage && !!window.hubStorage.getItem,
+);
+// TODO: Remove with 516
// prettier-ignore
const testIndexedDb = testGeneric(() => (
(window.indexedDB || window.msIndexedDB)
- && (window.IDBTransaction || window.msIDBTransaction)
+ && !!(window.IDBTransaction || window.msIDBTransaction)
));
-class MemoryBackend {
+class MemoryBackend implements StorageBackend {
+ private store: Record;
+ public impl: StorageImplementation;
+
constructor() {
this.impl = IMPL_MEMORY;
this.store = {};
}
- get(key) {
+ async get(key: string): Promise {
return this.store[key];
}
- set(key, value) {
+ async set(key: string, value: any): Promise {
this.store[key] = value;
}
- remove(key) {
+ async remove(key: string): Promise {
this.store[key] = undefined;
}
- clear() {
+ async clear(): Promise {
this.store = {};
}
}
-class LocalStorageBackend {
+class HubStorageBackend implements StorageBackend {
+ public impl: StorageImplementation;
+
constructor() {
- this.impl = IMPL_LOCAL_STORAGE;
+ this.impl = IMPL_HUB_STORAGE;
}
- get(key) {
- const value = localStorage.getItem(key);
+ async get(key: string): Promise {
+ const value = await window.hubStorage.getItem(key);
if (typeof value === 'string') {
return JSON.parse(value);
}
+ return undefined;
}
- set(key, value) {
- localStorage.setItem(key, JSON.stringify(value));
+ async set(key: string, value: any): Promise {
+ window.hubStorage.setItem(key, JSON.stringify(value));
}
- remove(key) {
- localStorage.removeItem(key);
+ async remove(key: string): Promise {
+ window.hubStorage.removeItem(key);
}
- clear() {
- localStorage.clear();
+ async clear(): Promise {
+ window.hubStorage.clear();
}
}
-class IndexedDbBackend {
+class IndexedDbBackend implements StorageBackend {
+ public impl: StorageImplementation;
+ public dbPromise: Promise;
+
constructor() {
this.impl = IMPL_INDEXED_DB;
- /** @type {Promise} */
this.dbPromise = new Promise((resolve, reject) => {
const indexedDB = window.indexedDB || window.msIndexedDB;
const req = indexedDB.open(INDEXED_DB_NAME, INDEXED_DB_VERSION);
@@ -98,7 +116,12 @@ class IndexedDbBackend {
try {
req.result.createObjectStore(INDEXED_DB_STORE_NAME);
} catch (err) {
- reject(new Error('Failed to upgrade IDB: ' + req.error));
+ reject(
+ new Error(
+ 'Failed to upgrade IDB: ' +
+ (err instanceof Error ? err.message : String(err)),
+ ),
+ );
}
};
req.onsuccess = () => resolve(req.result);
@@ -108,14 +131,14 @@ class IndexedDbBackend {
});
}
- getStore(mode) {
- // prettier-ignore
- return this.dbPromise.then((db) => db
+ private async getStore(mode: IDBTransactionMode): Promise {
+ const db = await this.dbPromise;
+ return db
.transaction(INDEXED_DB_STORE_NAME, mode)
- .objectStore(INDEXED_DB_STORE_NAME));
+ .objectStore(INDEXED_DB_STORE_NAME);
}
- async get(key) {
+ async get(key: string): Promise {
const store = await this.getStore(READ_ONLY);
return new Promise((resolve, reject) => {
const req = store.get(key);
@@ -124,26 +147,19 @@ class IndexedDbBackend {
});
}
- async set(key, value) {
- // The reason we don't _save_ null is because IE 10 does
- // not support saving the `null` type in IndexedDB. How
- // ironic, given the bug below!
- // See: https://github.com/mozilla/localForage/issues/161
- if (value === null) {
- value = undefined;
- }
+ async set(key: string, value: any): Promise {
// NOTE: We deliberately make this operation transactionless
const store = await this.getStore(READ_WRITE);
store.put(value, key);
}
- async remove(key) {
+ async remove(key: string): Promise {
// NOTE: We deliberately make this operation transactionless
const store = await this.getStore(READ_WRITE);
store.delete(key);
}
- async clear() {
+ async clear(): Promise {
// NOTE: We deliberately make this operation transactionless
const store = await this.getStore(READ_WRITE);
store.clear();
@@ -154,9 +170,16 @@ class IndexedDbBackend {
* Web Storage Proxy object, which selects the best backend available
* depending on the environment.
*/
-class StorageProxy {
+class StorageProxy implements StorageBackend {
+ private backendPromise: Promise;
+ public impl: StorageImplementation = IMPL_MEMORY;
+
constructor() {
this.backendPromise = (async () => {
+ if (!Byond.TRIDENT && testHubStorage()) {
+ return new HubStorageBackend();
+ }
+ // TODO: Remove with 516
if (testIndexedDb()) {
try {
const backend = new IndexedDbBackend();
@@ -164,29 +187,29 @@ class StorageProxy {
return backend;
} catch {}
}
- if (testLocalStorage()) {
- return new LocalStorageBackend();
- }
+ console.warn(
+ 'No supported storage backend found. Using in-memory storage.',
+ );
return new MemoryBackend();
})();
}
- async get(key) {
+ async get(key: string): Promise {
const backend = await this.backendPromise;
return backend.get(key);
}
- async set(key, value) {
+ async set(key: string, value: any): Promise {
const backend = await this.backendPromise;
return backend.set(key, value);
}
- async remove(key) {
+ async remove(key: string): Promise {
const backend = await this.backendPromise;
return backend.remove(key);
}
- async clear() {
+ async clear(): Promise {
const backend = await this.backendPromise;
return backend.clear();
}
diff --git a/tgui/packages/tgui-panel/chat/constants.ts b/tgui/packages/tgui-panel/chat/constants.ts
index 0e1a79bc1d63..68eb9394bd41 100644
--- a/tgui/packages/tgui-panel/chat/constants.ts
+++ b/tgui/packages/tgui-panel/chat/constants.ts
@@ -24,6 +24,7 @@ export const MESSAGE_TYPE_INTERNAL = 'internal';
export const MESSAGE_TYPE_SYSTEM = 'system';
export const MESSAGE_TYPE_LOCALCHAT = 'localchat';
export const MESSAGE_TYPE_RADIO = 'radio';
+export const MESSAGE_TYPE_ENTERTAINMENT = 'entertainment';
export const MESSAGE_TYPE_INFO = 'info';
export const MESSAGE_TYPE_WARNING = 'warning';
export const MESSAGE_TYPE_DEADCHAT = 'deadchat';
@@ -61,7 +62,13 @@ export const MESSAGE_TYPES = [
name: 'Radio',
description: 'All departments of radio messages',
selector:
- '.alert, .minorannounce, .syndradio, .centcomradio, .aiprivradio, .enteradio, .comradio, .secradio, .gangradio, .engradio, .medradio, .sciradio, .suppradio, .servradio, .radio, .deptradio, .binarysay, .newscaster, .resonate, .abductor, .alien, .changeling',
+ '.alert, .minorannounce, .syndradio, .centcomradio, .aiprivradio, .comradio, .secradio, .gangradio, .engradio, .medradio, .sciradio, .suppradio, .servradio, .radio, .deptradio, .binarysay, .resonate, .abductor, .alien, .changeling',
+ },
+ {
+ type: MESSAGE_TYPE_ENTERTAINMENT,
+ name: 'Entertainment',
+ description: 'Entertainment and newscaster broadcasts',
+ selector: '.enteradio, .newscaster',
},
{
type: MESSAGE_TYPE_INFO,
diff --git a/tgui/packages/tgui-panel/index.tsx b/tgui/packages/tgui-panel/index.tsx
index a70a53bac251..9fccceb4d217 100644
--- a/tgui/packages/tgui-panel/index.tsx
+++ b/tgui/packages/tgui-panel/index.tsx
@@ -75,14 +75,8 @@ const setupApp = () => {
Byond.subscribe((type, payload) => store.dispatch({ type, payload }));
// Unhide the panel
- Byond.winset('output', {
- 'is-visible': false,
- });
- Byond.winset('browseroutput', {
- 'is-visible': true,
- 'is-disabled': false,
- pos: '0x0',
- size: '0x0',
+ Byond.winset('legacy_output_selector', {
+ left: 'output_browser',
});
// Resize the panel to match the non-browser output
diff --git a/tgui/packages/tgui-panel/styles/tgchat/chat-dark.scss b/tgui/packages/tgui-panel/styles/tgchat/chat-dark.scss
index 09cdd23495a6..f0baa666dc58 100644
--- a/tgui/packages/tgui-panel/styles/tgchat/chat-dark.scss
+++ b/tgui/packages/tgui-panel/styles/tgchat/chat-dark.scss
@@ -42,6 +42,10 @@ a.popt {
text-decoration: none;
}
+.center {
+ text-align: center;
+}
+
/* POPUPS */
.popup {
@@ -770,6 +774,10 @@ em {
font-size: 60%;
}
+.smaller {
+ font-size: 90%;
+}
+
.slightly_larger {
font-size: 115%;
}
@@ -1065,16 +1073,70 @@ em {
margin-left: 2.5em;
}
-.examine_block {
- background: hsl(220, 5.3%, 11.2%);
- border: 1px solid hsl(213.6, 37.9%, 74.1%);
- margin: 0.5em;
- padding: 0.5em 0.75em;
-}
-
.tooltip {
font-style: italic;
- border-bottom: 1px dashed #fff;
+ border-bottom: 1px dashed;
+}
+
+.fieldset_legend {
+ position: relative;
+ max-width: 95%;
+ font-size: 120%;
+ padding: 0.2em 0.5em;
+ background: #151515; // Chat background color
+ border: 1px solid;
+ border-color: inherit;
+ border-radius: 0.33em;
+ z-index: 0;
+
+ // "Mask" a half of the border
+ // It very rough but it only possible way i see with IE compat
+ // Replace it with normal mask-image when 516 got stable
+ &:before {
+ content: '';
+ position: absolute;
+ left: 0;
+ height: 1.15em;
+ width: 100%;
+ background: #151515; // Chat background color
+ transform: translateY(-50%) scaleX(1.05);
+ z-index: -1;
+ }
+}
+
+.boxed_message {
+ background: hsl(220, 10%, 10%);
+ border: 1em * calc(1px / 12px) solid;
+ border-left: 1em * calc(4px / 12px) solid;
+ border-color: hsla(220, 40%, 75%, 0.25);
+ margin: 0.5em 0;
+ padding: 0.5em 0.75em;
+ border-radius: 0.33em;
+
+ &.red_box {
+ background: hsl(0, 20%, 10%);
+ border-color: hsla(0, 100%, 50%, 0.5);
+ }
+
+ &.green_box {
+ background: hsl(140, 20%, 10%);
+ border-color: hsla(120, 100%, 50%, 0.5);
+ }
+
+ &.blue_box {
+ background: hsl(220, 20%, 10%);
+ border-color: hsla(225, 90%, 65%, 0.5);
+ }
+
+ &.purple_box {
+ background: hsl(260, 25%, 12.5%);
+ border-color: hsla(260, 100%, 75%, 0.5);
+ }
+
+ hr {
+ margin: 0.5em -0.75em;
+ border-color: inherit;
+ }
}
// Provides a horizontal bar with text in the middle
@@ -1333,11 +1395,19 @@ $border-width-px: $border-width * 1px;
}
.chat_alert_#{$color-name} .minor_announcement_text {
- background-color: darken(map.get($alert-stripe-colors, $color-name), 5);
+ background-color: color.adjust(
+ map.get($alert-stripe-colors, $color-name),
+ $lightness: -5%,
+ $space: hsl
+ );
}
.chat_alert_#{$color-name} .major_announcement_text {
- background-color: darken(map.get($alert-stripe-colors, $color-name), 5);
+ background-color: color.adjust(
+ map.get($alert-stripe-colors, $color-name),
+ $lightness: -5%,
+ $space: hsl
+ );
}
}
/* NOVA EDIT ADDITION START - DARK MODE CLASSES */
@@ -1352,21 +1422,6 @@ $border-width-px: $border-width * 1px;
color: #b09448;
}
-.examine_block {
- background: rgba(0, 0, 0, 0.2);
- border: 1px solid rgba(55, 55, 55, 0.33);
- margin: 7px 4px;
- padding: 8px 12px;
- max-width: 550px;
-}
-
-.examine_block hr {
- border: none;
- background: #222;
- height: 1px;
- margin: 8px 16px 8px 16px;
-}
-
.userlove {
color: #ff1493;
font-style: italic;
diff --git a/tgui/packages/tgui-panel/styles/tgchat/chat-light.scss b/tgui/packages/tgui-panel/styles/tgchat/chat-light.scss
index 8f606cbdd6f2..6418c60349d2 100644
--- a/tgui/packages/tgui-panel/styles/tgchat/chat-light.scss
+++ b/tgui/packages/tgui-panel/styles/tgchat/chat-light.scss
@@ -985,16 +985,42 @@ h2.alert {
margin-left: 3em;
}
-.examine_block {
- background: hsl(202.5, 44.4%, 96.5%);
- border: 1px solid hsl(215.5, 39.3%, 11%);
- margin: 0.5em;
- padding: 0.5em 0.75em;
-}
-
.tooltip {
font-style: italic;
- border-bottom: 1px dashed #000;
+ border-bottom: 1px dashed;
+}
+
+.fieldset_legend {
+ background: #ffffff; // Chat background color
+
+ &:before {
+ background: #ffffff; // Chat background color
+ }
+}
+
+.boxed_message {
+ background: hsl(220, 100%, 97.5%);
+ border-color: hsla(220, 75%, 25%, 0.5);
+
+ &.red_box {
+ background: hsl(0, 100%, 97.5%);
+ border-color: hsla(0, 100%, 50%, 0.5);
+ }
+
+ &.green_box {
+ background: hsl(140, 100%, 97.5%);
+ border-color: hsl(120, 100%, 33%, 0.5);
+ }
+
+ &.blue_box {
+ background: hsl(220, 100%, 97.5%);
+ border-color: hsla(225, 100%, 50%, 0.5);
+ }
+
+ &.purple_box {
+ background: hsl(260, 100%, 97.5%);
+ border-color: hsla(260, 100%, 50%, 0.5);
+ }
}
// Provides a horizontal bar with text in the middle
@@ -1253,16 +1279,18 @@ $border-width-px: $border-width * 1px;
}
.chat_alert_#{$color-name} .minor_announcement_text {
- background-color: lighten(
+ background-color: color.adjust(
map.get($alert-stripe-alternate-colors, $color-name),
- 5
+ $lightness: 5%,
+ $space: hsl
);
}
.chat_alert_#{$color-name} .major_announcement_text {
- background-color: lighten(
+ background-color: color.adjust(
map.get($alert-stripe-alternate-colors, $color-name),
- 5
+ $lightness: 5%,
+ $space: hsl
);
}
}
@@ -1280,21 +1308,6 @@ $border-width-px: $border-width * 1px;
font-weight: bold;
}
-.examine_block {
- background: rgba(0, 0, 0, 0.1);
- border: 1px solid #111a27;
- margin: 2px 8px;
- padding: 8px 12px;
- max-width: 550px;
-}
-
-.examine_block hr {
- border: none;
- background: #333;
- height: 1px;
- margin: 8px 16px 8px 16px;
-}
-
.crossooc {
color: #9f6efc;
font-weight: bold;
diff --git a/tgui/packages/tgui-say/styles/button.scss b/tgui/packages/tgui-say/styles/button.scss
index 89e1cceca943..69802c045ec5 100644
--- a/tgui/packages/tgui-say/styles/button.scss
+++ b/tgui/packages/tgui-say/styles/button.scss
@@ -15,7 +15,11 @@
padding: 0;
width: 2.6rem;
&:hover {
- background-color: lighten(colors.$button, 10%);
+ background-color: color.adjust(
+ colors.$button,
+ $lightness: 10%,
+ $space: hsl
+ );
}
}
diff --git a/tgui/packages/tgui-say/styles/colors.scss b/tgui/packages/tgui-say/styles/colors.scss
index 337887e358ff..3512a4287ba3 100644
--- a/tgui/packages/tgui-say/styles/colors.scss
+++ b/tgui/packages/tgui-say/styles/colors.scss
@@ -43,7 +43,7 @@ $channel_keys: map.keys($_channel_map) !default;
$channel-map: ();
@each $channel in $channel_keys {
- $channel-map: map-merge(
+ $channel-map: map.merge(
$channel-map,
(
$channel: map.get($_channel_map, $channel),
diff --git a/tgui/packages/tgui-say/styles/main.scss b/tgui/packages/tgui-say/styles/main.scss
index 8e164aa21fa7..bafc850e242a 100644
--- a/tgui/packages/tgui-say/styles/main.scss
+++ b/tgui/packages/tgui-say/styles/main.scss
@@ -26,14 +26,14 @@
}
@each $channel, $color in colors.$channel-map {
- $darkened: darken($color, 20%);
+ $darkened: color.adjust($color, $lightness: -20%, $space: hsl);
.button-#{$channel} {
- border-color: darken($color, 10%);
+ border-color: color.adjust($color, $lightness: -10%, $space: hsl);
color: $color;
&:hover {
- border-color: lighten($color, 10%);
- color: lighten($color, 5%);
+ border-color: color.adjust($color, $lightness: 10%, $space: hsl);
+ color: color.adjust($color, $lightness: 5%, $space: hsl);
}
}
@@ -50,11 +50,11 @@
animation: gradient 10s linear infinite;
background: linear-gradient(
to right,
- darken($color, 35%),
+ color.adjust($color, $lightness: -35%, $space: hsl),
$color,
- lighten($color, 10%),
+ color.adjust($color, $lightness: 10%, $space: hsl),
$color,
- darken($color, 35%)
+ color.adjust($color, $lightness: -35%, $space: hsl)
);
background-position: 0% 0%;
background-size: 500% auto;
diff --git a/tgui/packages/tgui/interfaces/ChemMixingChamber.tsx b/tgui/packages/tgui/interfaces/ChemMixingChamber.tsx
index 765862ab4b86..2f9a9cd90163 100644
--- a/tgui/packages/tgui/interfaces/ChemMixingChamber.tsx
+++ b/tgui/packages/tgui/interfaces/ChemMixingChamber.tsx
@@ -13,7 +13,7 @@ import { BooleanLike } from 'tgui-core/react';
import { useBackend } from '../backend';
import { Window } from '../layouts';
-type Reagent = {
+export type Reagent = {
name: string;
volume: number;
};
diff --git a/tgui/packages/tgui/interfaces/ChemReactionChamber.tsx b/tgui/packages/tgui/interfaces/ChemReactionChamber.tsx
index cdc2b6fac97b..255e669efcf0 100644
--- a/tgui/packages/tgui/interfaces/ChemReactionChamber.tsx
+++ b/tgui/packages/tgui/interfaces/ChemReactionChamber.tsx
@@ -13,12 +13,13 @@ import { round, toFixed } from 'tgui-core/math';
import { useBackend } from '../backend';
import { Window } from '../layouts';
-import { MixingData } from './ChemMixingChamber';
+import { MixingData, Reagent } from './ChemMixingChamber';
type ReactingData = MixingData & {
ph: number;
reagentAcidic: number;
reagentAlkaline: number;
+ catalysts: Reagent[];
};
export const ChemReactionChamber = (props) => {
@@ -36,8 +37,9 @@ export const ChemReactionChamber = (props) => {
reagentAlkaline,
} = data;
const reagents = data.reagents || [];
+ const catalysts = data.catalysts || [];
return (
-
+
@@ -130,8 +132,9 @@ export const ChemReactionChamber = (props) => {
@@ -189,7 +192,6 @@ export const ChemReactionChamber = (props) => {
{
{reagent.volume}
+
+
+
+
+
+
+
+
+ {catalysts.map((reagent) => (
+
+
+
+ {reagent.name + ':'}
+
+
+ {reagent.volume}
+
+
+
+
+
+
+ ))}
+
+
+
+
diff --git a/tgui/packages/tgui/interfaces/ForensicsSpoofer.tsx b/tgui/packages/tgui/interfaces/ForensicsSpoofer.tsx
new file mode 100644
index 000000000000..8d527ec72fd1
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/ForensicsSpoofer.tsx
@@ -0,0 +1,150 @@
+import { BooleanLike } from 'common/react';
+import { useState } from 'react';
+
+import { useBackend } from '../backend';
+import {
+ Box,
+ Button,
+ Divider,
+ Icon,
+ Section,
+ Stack,
+ Tabs,
+} from '../components';
+import { Window } from '../layouts';
+
+type Data = {
+ silent: BooleanLike;
+ scanmode: BooleanLike;
+ fibers: string[];
+ fingerprints: string[];
+ chosen_fiber: string;
+ chosen_fingerprint: string;
+ max_storage: number;
+};
+export const ForensicsSpoofer = (props) => {
+ const { act, data } = useBackend();
+ const {
+ silent,
+ scanmode,
+ fibers,
+ fingerprints,
+ chosen_fiber,
+ chosen_fingerprint,
+ max_storage,
+ } = data;
+ const [currentTab, setTab] = useState(0);
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ setTab(0)}
+ width="50%"
+ >
+ Fingerprints {Object.keys(fingerprints).length}/{max_storage}
+
+ setTab(1)}
+ >
+ Fibers {Object.keys(fibers).length}/{max_storage}
+
+
+
+
+ {Object.keys(currentTab === 0 ? fingerprints : fibers).map(
+ (forensic_data, _) => (
+
+
+
+
+ act('choose', { chosen: forensic_data })
+ }
+ />
+
+
+
+
+
+
+ {currentTab === 0
+ ? forensic_data.substring(0, 25)
+ : forensic_data}
+
+
+ {currentTab === 0 && (
+
+
+ ({fingerprints[forensic_data]})
+
+
+ )}
+
+
+ ),
+ )}
+
+
+
+
+
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/Orbit/JobIcon.tsx b/tgui/packages/tgui/interfaces/Orbit/JobIcon.tsx
index db6d08b1ffe2..812d83682107 100644
--- a/tgui/packages/tgui/interfaces/Orbit/JobIcon.tsx
+++ b/tgui/packages/tgui/interfaces/Orbit/JobIcon.tsx
@@ -25,18 +25,20 @@ const antagIcon: IconSettings = {
export function JobIcon(props: Props) {
const { item, realNameDisplay } = props;
+ // We don't need to cast here but typescript isn't smart enough to know that
+ const { icon = '', job = '', mind_icon = '', mind_job = '' } = item;
+ let usedIcon = realNameDisplay ? mind_icon || icon : icon;
+ let usedJob = realNameDisplay ? mind_job || job : job;
+
let iconSettings: IconSettings;
- if ('antag' in item) {
+ if ('antag' in item && !realNameDisplay) {
iconSettings = antagIcon;
+ usedJob = item.antag;
+ usedIcon = item.antag_icon;
} else {
iconSettings = normalIcon;
}
- // We don't need to cast here but typescript isn't smart enough to know that
- const { icon = '', job = '', mind_icon = '', mind_job = '' } = item;
- const usedIcon = realNameDisplay ? mind_icon || icon : icon;
- const usedJob = realNameDisplay ? mind_job || job : job;
-
return (