mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-08-25 06:00:16 +01:00
Merge remote-tracking branch 'tgstation/master' into upstream-feb12-2026
# Conflicts: # .github/CODEOWNERS # .github/workflows/compile_changelogs.yml # .github/workflows/stale.yml # SQL/database_changelog.md # _maps/map_files/CatwalkStation/CatwalkStation_2023.dmm # code/__DEFINES/atom_hud.dm # code/__DEFINES/inventory.dm # code/__DEFINES/mobs.dm # code/__DEFINES/species_clothing_paths.dm # code/__DEFINES/subsystems.dm # code/__DEFINES/surgery.dm # code/__HELPERS/global_lists.dm # code/_globalvars/lists/maintenance_loot.dm # code/_globalvars/traits/_traits.dm # code/controllers/subsystem/minor_mapping.dm # code/controllers/subsystem/processing/quirks.dm # code/controllers/subsystem/shuttle.dm # code/datums/components/palette.dm # code/datums/components/surgery_initiator.dm # code/datums/diseases/advance/advance.dm # code/datums/hud.dm # code/datums/mood.dm # code/datums/mutations/chameleon.dm # code/datums/quirks/negative_quirks/nyctophobia.dm # code/datums/status_effects/debuffs/debuffs.dm # code/datums/status_effects/debuffs/drunk.dm # code/datums/status_effects/debuffs/slime/slime_leech.dm # code/datums/weather/weather.dm # code/game/data_huds.dm # code/game/objects/items.dm # code/game/objects/items/devices/scanners/health_analyzer.dm # code/game/objects/items/frog_statue.dm # code/game/objects/items/rcd/RLD.dm # code/game/objects/items/robot/items/hypo.dm # code/game/objects/items/stacks/medical.dm # code/game/objects/items/stacks/wrap.dm # code/game/objects/items/storage/garment.dm # code/game/objects/items/tools/medical/defib.dm # code/game/objects/items/weaponry.dm # code/game/objects/items/weaponry/melee/misc.dm # code/game/objects/structures/crates_lockers/closets/secure/security.dm # code/game/objects/structures/curtains.dm # code/game/objects/structures/dresser.dm # code/game/objects/structures/girders.dm # code/game/objects/structures/maintenance.dm # code/game/objects/structures/mirror.dm # code/modules/admin/greyscale_modify_menu.dm # code/modules/admin/verbs/light_debug.dm # code/modules/antagonists/ashwalker/ashwalker.dm # code/modules/antagonists/heretic/knowledge/starting_lore.dm # code/modules/antagonists/ninja/ninjaDrainAct.dm # code/modules/art/paintings.dm # code/modules/client/preferences.dm # code/modules/client/verbs/ooc.dm # code/modules/clothing/head/wig.dm # code/modules/events/disease_outbreak.dm # code/modules/holodeck/holo_effect.dm # code/modules/jobs/job_types/head_of_security.dm # code/modules/jobs/job_types/security_officer.dm # code/modules/library/skill_learning/generic_skillchips/point.dm # code/modules/mining/lavaland/ash_flora.dm # code/modules/mining/lavaland/mining_loot/megafauna/ash_drake.dm # code/modules/mob/dead/new_player/new_player.dm # code/modules/mob/living/basic/guardian/guardian.dm # code/modules/mob/living/basic/space_fauna/space_dragon/space_dragon.dm # code/modules/mob/living/carbon/carbon.dm # code/modules/mob/living/carbon/human/human.dm # code/modules/mob/living/carbon/human/human_defines.dm # code/modules/mob/living/carbon/life.dm # code/modules/mob/living/living.dm # code/modules/mob/living/living_defines.dm # code/modules/mob/mob.dm # code/modules/mob_spawn/ghost_roles/mining_roles.dm # code/modules/mod/mod_control.dm # code/modules/mod/modules/modules_general.dm # code/modules/modular_computers/computers/item/computer_ui.dm # code/modules/paperwork/paper.dm # code/modules/paperwork/paperbin.dm # code/modules/power/lighting/light.dm # code/modules/projectiles/guns/energy/kinetic_accelerator.dm # code/modules/projectiles/projectile.dm # code/modules/reagents/chemistry/reagents/drinks/alcohol_reagents.dm # code/modules/reagents/chemistry/reagents/food_reagents.dm # code/modules/reagents/chemistry/reagents/other_reagents.dm # code/modules/reagents/chemistry/reagents/pyrotechnic_reagents.dm # code/modules/research/xenobiology/crossbreeding/_clothing.dm # code/modules/research/xenobiology/crossbreeding/prismatic.dm # code/modules/surgery/advanced/brainwashing.dm # code/modules/surgery/advanced/lobotomy.dm # code/modules/surgery/amputation.dm # code/modules/surgery/blood_filter.dm # code/modules/surgery/bodyparts/_bodyparts.dm # code/modules/surgery/brain_surgery.dm # code/modules/surgery/cavity_implant.dm # code/modules/surgery/coronary_bypass.dm # code/modules/surgery/gastrectomy.dm # code/modules/surgery/healing.dm # code/modules/surgery/limb_augmentation.dm # code/modules/surgery/organ_manipulation.dm # code/modules/surgery/revival.dm # code/modules/surgery/sleeper_protocol.dm # code/modules/surgery/surgery_helpers.dm # code/modules/surgery/surgery_step.dm # code/modules/unit_tests/_unit_tests.dm # code/modules/unit_tests/designs.dm # code/modules/unit_tests/icon_state_worn.dm # code/modules/unit_tests/screenshots/screenshot_antag_icons_cultist.png # code/modules/unit_tests/screenshots/screenshot_antag_icons_headrevolutionary.png # code/modules/unit_tests/screenshots/screenshot_antag_icons_provocateur.png # code/modules/unit_tests/screenshots/screenshot_husk_body.png # code/modules/unit_tests/screenshots/screenshot_husk_body_missing_limbs.png # icons/map_icons/clothing/head/_head.dmi # icons/map_icons/clothing/shoes.dmi # icons/map_icons/items/_item.dmi # icons/mob/huds/hud.dmi # icons/mob/inhands/64x64_lefthand.dmi # icons/mob/inhands/64x64_righthand.dmi # icons/obj/machines/computer.dmi # tgui/packages/tgui/interfaces/OperatingComputer.jsx # tgui/packages/tgui/interfaces/PreferencesMenu/CharacterPreferences/MainPage.tsx # tgui/packages/tgui/interfaces/PreferencesMenu/types.ts # tgui/packages/tgui/interfaces/SurgeryInitiator.tsx # tools/icon_cutter/check.py
This commit is contained in:
@@ -1,110 +0,0 @@
|
||||
///Influences the difficulty of the minigame when worn or if buckled to.
|
||||
/datum/component/adjust_fishing_difficulty
|
||||
///The additive numerical modifier to the difficulty of the minigame
|
||||
var/modifier
|
||||
///For items, in which slot it has to be worn to influence the difficulty of the minigame
|
||||
var/slots
|
||||
|
||||
/datum/component/adjust_fishing_difficulty/Initialize(modifier, slots = NONE)
|
||||
if(!ismovable(parent) || !modifier)
|
||||
return COMPONENT_INCOMPATIBLE
|
||||
|
||||
if(!isitem(parent))
|
||||
var/atom/movable/movable_parent = parent
|
||||
if(!movable_parent.can_buckle)
|
||||
return COMPONENT_INCOMPATIBLE
|
||||
|
||||
src.modifier = modifier
|
||||
src.slots = slots
|
||||
|
||||
/datum/component/adjust_fishing_difficulty/RegisterWithParent()
|
||||
if(isitem(parent))
|
||||
RegisterSignal(parent, COMSIG_ITEM_EQUIPPED, PROC_REF(on_equipped))
|
||||
RegisterSignal(parent, COMSIG_ITEM_DROPPED, PROC_REF(on_dropped))
|
||||
RegisterSignal(parent, COMSIG_ATOM_EXAMINE, PROC_REF(on_item_examine))
|
||||
else
|
||||
RegisterSignal(parent, COMSIG_MOVABLE_BUCKLE, PROC_REF(on_buckle))
|
||||
RegisterSignal(parent, COMSIG_MOVABLE_UNBUCKLE, PROC_REF(on_unbuckle))
|
||||
RegisterSignal(parent, COMSIG_ATOM_EXAMINE, PROC_REF(on_buckle_examine))
|
||||
|
||||
update_check()
|
||||
|
||||
/datum/component/adjust_fishing_difficulty/UnregisterFromParent()
|
||||
UnregisterSignal(parent, list(
|
||||
COMSIG_ATOM_EXAMINE,
|
||||
COMSIG_MOVABLE_BUCKLE,
|
||||
COMSIG_MOVABLE_UNBUCKLE,
|
||||
COMSIG_ITEM_EQUIPPED,
|
||||
COMSIG_ITEM_DROPPED,
|
||||
))
|
||||
|
||||
update_check(TRUE)
|
||||
|
||||
/datum/component/adjust_fishing_difficulty/proc/update_check(removing = FALSE)
|
||||
var/atom/movable/movable_parent = parent
|
||||
for(var/mob/living/buckled_mob as anything in movable_parent.buckled_mobs)
|
||||
update_user(buckled_mob, removing)
|
||||
if(!isitem(movable_parent) || !isliving(movable_parent.loc))
|
||||
return
|
||||
var/mob/living/holder = movable_parent.loc
|
||||
var/obj/item/item = parent
|
||||
if(holder.get_slot_by_item(movable_parent) & (slots || item.slot_flags))
|
||||
update_user(holder, removing)
|
||||
|
||||
/datum/component/adjust_fishing_difficulty/proc/on_item_examine(obj/item/item, mob/user, list/examine_text)
|
||||
SIGNAL_HANDLER
|
||||
if(!HAS_MIND_TRAIT(user, TRAIT_EXAMINE_FISH))
|
||||
return
|
||||
var/method = "[(slots || item.slot_flags) & ITEM_SLOT_HANDS ? "Holding" : "Wearing"] [item.p_them()]"
|
||||
add_examine_line(user, examine_text, method)
|
||||
|
||||
/datum/component/adjust_fishing_difficulty/proc/on_buckle_examine(atom/movable/source, mob/user, list/examine_text)
|
||||
SIGNAL_HANDLER
|
||||
if(!HAS_MIND_TRAIT(user, TRAIT_EXAMINE_FISH))
|
||||
return
|
||||
add_examine_line(user, examine_text, "Buckling to [source.p_them()]")
|
||||
|
||||
/datum/component/adjust_fishing_difficulty/proc/add_examine_line(mob/user, list/examine_text, method)
|
||||
var/percent = HAS_MIND_TRAIT(user, TRAIT_EXAMINE_DEEPER_FISH) ? "[abs(modifier)]% " : ""
|
||||
var/text = "[method] will make fishing [percent][modifier < 0 ? "easier" : "harder"]."
|
||||
if(modifier < 0)
|
||||
examine_text += span_nicegreen(text)
|
||||
else
|
||||
examine_text += span_danger(text)
|
||||
|
||||
/datum/component/adjust_fishing_difficulty/proc/on_buckle(atom/movable/source, mob/living/buckled_mob, forced)
|
||||
SIGNAL_HANDLER
|
||||
update_user(buckled_mob)
|
||||
|
||||
/datum/component/adjust_fishing_difficulty/proc/on_unbuckle(atom/movable/source, mob/living/buckled_mob, forced)
|
||||
SIGNAL_HANDLER
|
||||
update_user(buckled_mob, TRUE)
|
||||
|
||||
/datum/component/adjust_fishing_difficulty/proc/on_equipped(obj/item/source, mob/living/wearer, slot)
|
||||
SIGNAL_HANDLER
|
||||
if(slot & (slots || source.slot_flags))
|
||||
update_user(wearer)
|
||||
|
||||
/datum/component/adjust_fishing_difficulty/proc/on_dropped(obj/item/source, mob/living/dropper)
|
||||
SIGNAL_HANDLER
|
||||
update_user(dropper, TRUE)
|
||||
|
||||
/datum/component/adjust_fishing_difficulty/proc/update_user(mob/living/user, removing = FALSE)
|
||||
var/datum/fishing_challenge/challenge = GLOB.fishing_challenges_by_user[user]
|
||||
if(removing)
|
||||
UnregisterSignal(user, COMSIG_MOB_BEGIN_FISHING)
|
||||
if(challenge)
|
||||
UnregisterSignal(challenge, COMSIG_FISHING_CHALLENGE_GET_DIFFICULTY)
|
||||
else
|
||||
RegisterSignal(user, COMSIG_MOB_BEGIN_FISHING, PROC_REF(on_minigame_started), TRUE)
|
||||
if(challenge)
|
||||
RegisterSignal(challenge, COMSIG_FISHING_CHALLENGE_GET_DIFFICULTY, PROC_REF(adjust_difficulty), TRUE)
|
||||
challenge?.update_difficulty()
|
||||
|
||||
/datum/component/adjust_fishing_difficulty/proc/on_minigame_started(mob/living/source, datum/fishing_challenge/challenge)
|
||||
SIGNAL_HANDLER
|
||||
RegisterSignal(challenge, COMSIG_FISHING_CHALLENGE_GET_DIFFICULTY, PROC_REF(adjust_difficulty), TRUE)
|
||||
|
||||
/datum/component/adjust_fishing_difficulty/proc/adjust_difficulty(datum/fishing_challenge/challenge, reward_path, obj/item/fishing_rod/rod, mob/living/user, list/holder)
|
||||
SIGNAL_HANDLER
|
||||
holder[1] += modifier
|
||||
@@ -0,0 +1,130 @@
|
||||
/datum/component/anomaly_locked_module
|
||||
var/obj/item/assembly/signaler/anomaly/core
|
||||
/// Accepted types of anomaly cores.
|
||||
var/list/accepted_anomalies
|
||||
/// If the core is removable once socketed.
|
||||
var/core_removable
|
||||
/// A proc to call before the core is inserted. Returns an ITEM_INTERACT define, which the component will itself return.
|
||||
var/pre_insert_callback
|
||||
/// A proc to call when the core is inserted.
|
||||
var/core_insert_callback
|
||||
/// A proc to call when the core is removed.
|
||||
var/core_remove_callback
|
||||
|
||||
/datum/component/anomaly_locked_module/Initialize(list/anomaly_types, prebuilt = FALSE, removable = TRUE, pre_insert_callback, insert_callback, remove_callback)
|
||||
. = ..()
|
||||
if(!istype(parent, /obj/item/mod/module))
|
||||
return COMPONENT_INCOMPATIBLE
|
||||
accepted_anomalies = typecacheof(anomaly_types)
|
||||
core_removable = removable
|
||||
src.pre_insert_callback = pre_insert_callback
|
||||
core_insert_callback = insert_callback
|
||||
core_remove_callback = remove_callback
|
||||
if(!(prebuilt && length(anomaly_types)))
|
||||
return
|
||||
var/obj/item/assembly/signaler/anomaly/core_type = pick(anomaly_types)
|
||||
core = new core_type(parent)
|
||||
|
||||
/datum/component/anomaly_locked_module/Destroy(force)
|
||||
QDEL_NULL(core)
|
||||
return ..()
|
||||
|
||||
/datum/component/anomaly_locked_module/RegisterWithParent()
|
||||
RegisterSignal(parent, COMSIG_MODULE_TRIGGERED, PROC_REF(on_module_triggered))
|
||||
RegisterSignal(parent, COMSIG_ATOM_ITEM_INTERACTION, PROC_REF(on_item_interact))
|
||||
RegisterSignal(parent, COMSIG_ATOM_TOOL_ACT(TOOL_SCREWDRIVER), PROC_REF(on_screwdriver_act))
|
||||
RegisterSignal(parent, COMSIG_ATOM_EXAMINE, PROC_REF(on_examine))
|
||||
RegisterSignal(parent, COMSIG_ATOM_UPDATE_ICON_STATE, PROC_REF(on_update_icon_state))
|
||||
|
||||
/datum/component/anomaly_locked_module/UnregisterFromParent()
|
||||
UnregisterSignal(parent, list(
|
||||
COMSIG_MODULE_TRIGGERED,
|
||||
COMSIG_ATOM_ITEM_INTERACTION,
|
||||
COMSIG_ATOM_TOOL_ACT(TOOL_SCREWDRIVER),
|
||||
COMSIG_ATOM_EXAMINE,
|
||||
COMSIG_ATOM_UPDATE_ICON_STATE,
|
||||
))
|
||||
|
||||
/datum/component/anomaly_locked_module/proc/on_module_triggered(obj/item/mod/module/source, mob/living/wearer)
|
||||
SIGNAL_HANDLER
|
||||
if(!core)
|
||||
source.balloon_alert(wearer, "no core!")
|
||||
return MOD_ABORT_USE
|
||||
|
||||
/datum/component/anomaly_locked_module/proc/on_item_interact(obj/item/mod/module/source, mob/living/user, obj/item/tool, list/modifiers)
|
||||
SIGNAL_HANDLER
|
||||
if(!is_type_in_typecache(tool, accepted_anomalies))
|
||||
return 0
|
||||
if(core)
|
||||
source.balloon_alert(user, "already has core!")
|
||||
return ITEM_INTERACT_FAILURE
|
||||
if(pre_insert_callback)
|
||||
var/callback_return
|
||||
if(istype(pre_insert_callback, /datum/callback))
|
||||
var/datum/callback/pre_insert_callback_datum = pre_insert_callback
|
||||
callback_return = pre_insert_callback_datum.Invoke(user, tool, modifiers)
|
||||
else
|
||||
callback_return = call(source, pre_insert_callback)(user, tool, modifiers)
|
||||
if(callback_return)
|
||||
return callback_return
|
||||
return insert_core(source, user, tool, modifiers)
|
||||
|
||||
/datum/component/anomaly_locked_module/proc/insert_core(obj/item/mod/module/source, mob/living/user, obj/item/tool, list/modifiers)
|
||||
if(!user.transferItemToLoc(tool, source))
|
||||
return ITEM_INTERACT_FAILURE
|
||||
core = tool
|
||||
source.balloon_alert(user, "core inserted")
|
||||
playsound(source, 'sound/machines/click.ogg', 30, TRUE)
|
||||
source.update_appearance(UPDATE_ICON_STATE)
|
||||
if(core_insert_callback)
|
||||
if(istype(core_insert_callback, /datum/callback))
|
||||
var/datum/callback/core_insert_callback_datum = core_insert_callback
|
||||
core_insert_callback_datum.Invoke(core, user, modifiers)
|
||||
else
|
||||
call(source, core_insert_callback)(core, user, modifiers)
|
||||
return ITEM_INTERACT_SUCCESS
|
||||
|
||||
/datum/component/anomaly_locked_module/proc/on_screwdriver_act(obj/item/mod/module/source, mob/living/user, obj/item/tool)
|
||||
SIGNAL_HANDLER
|
||||
if(!core)
|
||||
source.balloon_alert(user, "no core!")
|
||||
return ITEM_INTERACT_FAILURE
|
||||
if(!core_removable)
|
||||
source.balloon_alert(user, "cannot remove core!")
|
||||
INVOKE_ASYNC(src, PROC_REF(try_remove_core), source, user, tool)
|
||||
return ITEM_INTERACT_SUCCESS
|
||||
|
||||
/datum/component/anomaly_locked_module/proc/try_remove_core(obj/item/mod/module/source, mob/living/user, obj/item/tool)
|
||||
if(!do_after(user, 3 SECONDS, source))
|
||||
source.balloon_alert(user, "interrupted!")
|
||||
return
|
||||
source.balloon_alert(user, "core removed")
|
||||
core.forceMove(source.drop_location())
|
||||
if(source.Adjacent(user) && !issilicon(user))
|
||||
user.put_in_hands(core)
|
||||
core = null
|
||||
source.update_appearance(UPDATE_ICON_STATE)
|
||||
if(core_remove_callback)
|
||||
if(istype(core_remove_callback, /datum/callback))
|
||||
var/datum/callback/core_remove_callback_datum = core_remove_callback
|
||||
core_remove_callback_datum.Invoke(core, user)
|
||||
else
|
||||
call(source, core_remove_callback)(core, user)
|
||||
|
||||
/datum/component/anomaly_locked_module/proc/on_examine(obj/item/mod/module/source, mob/viewer, list/examine_list)
|
||||
SIGNAL_HANDLER
|
||||
if(!length(accepted_anomalies))
|
||||
return
|
||||
if(core)
|
||||
examine_list += span_notice("There is a [core.name] installed in it. [core_removable ? "You could remove it with a <b>screwdriver</b>..." : "Unfortunately, due to a design quirk, it's unremovable."]")
|
||||
return
|
||||
var/list/core_list = list()
|
||||
for(var/atom/core_path as anything in accepted_anomalies)
|
||||
core_list += initial(core_path.name)
|
||||
examine_list += span_notice("You need to insert \a [english_list(core_list, and_text = " or ")] for this module to function.")
|
||||
if(!core_removable)
|
||||
examine_list += span_notice("Due to some design quirk, once a core is inserted, it won't be removable.")
|
||||
|
||||
/datum/component/anomaly_locked_module/proc/on_update_icon_state(obj/item/mod/module/source)
|
||||
SIGNAL_HANDLER
|
||||
source.icon_state = source::icon_state + (core ? "-core" : "")
|
||||
@@ -43,7 +43,7 @@
|
||||
|
||||
/datum/component/area_sound_manager/proc/react_to_z_move(datum/source, turf/old_turf, turf/new_turf)
|
||||
SIGNAL_HANDLER
|
||||
if(!length(accepted_zs) || (new_turf.z in accepted_zs))
|
||||
if(!length(accepted_zs) || (new_turf?.z in accepted_zs))
|
||||
return
|
||||
qdel(src)
|
||||
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
/// The closed turf our object is currently linked to.
|
||||
var/atom/hanging_support_atom
|
||||
|
||||
/datum/component/atom_mounted/Initialize(target_structure, on_drop_callback)
|
||||
/datum/component/atom_mounted/Initialize(target_structure)
|
||||
. = ..()
|
||||
if(!isobj(parent))
|
||||
if(!isobj(parent) || !isatom(target_structure))
|
||||
return COMPONENT_INCOMPATIBLE
|
||||
hanging_support_atom = target_structure
|
||||
RegisterSignal(hanging_support_atom, COMSIG_ATOM_EXAMINE, PROC_REF(on_examine))
|
||||
@@ -15,19 +15,19 @@
|
||||
RegisterSignal(hanging_support_atom, COMSIG_QDELETING, PROC_REF(on_structure_delete))
|
||||
|
||||
/datum/component/atom_mounted/RegisterWithParent()
|
||||
ADD_TRAIT(parent, TRAIT_WALLMOUNTED, REF(src))
|
||||
ADD_TRAIT(parent, TRAIT_WALLMOUNTED, INNATE_TRAIT)
|
||||
if(is_area_shuttle(get_area(parent)))
|
||||
RegisterSignal(parent, COMSIG_ATOM_BEFORE_SHUTTLE_MOVE, PROC_REF(detach))
|
||||
RegisterSignal(parent, COMSIG_MOVABLE_MOVED, PROC_REF(on_move))
|
||||
|
||||
/datum/component/atom_mounted/UnregisterFromParent()
|
||||
REMOVE_TRAIT(parent, TRAIT_WALLMOUNTED, REF(src))
|
||||
UnregisterSignal(parent, COMSIG_MOVABLE_MOVED)
|
||||
REMOVE_TRAIT(parent, TRAIT_WALLMOUNTED, INNATE_TRAIT)
|
||||
var/list/signals = list(COMSIG_MOVABLE_MOVED)
|
||||
if(is_area_shuttle(get_area(parent)))
|
||||
signals += COMSIG_ATOM_BEFORE_SHUTTLE_MOVE
|
||||
UnregisterSignal(parent, signals)
|
||||
|
||||
/datum/component/atom_mounted/Destroy(force)
|
||||
UnregisterSignal(hanging_support_atom, list(COMSIG_ATOM_EXAMINE))
|
||||
if(isclosedturf(hanging_support_atom))
|
||||
UnregisterSignal(hanging_support_atom, COMSIG_TURF_CHANGE)
|
||||
else
|
||||
UnregisterSignal(hanging_support_atom, COMSIG_QDELETING)
|
||||
hanging_support_atom = null
|
||||
return ..()
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
SIGNAL_HANDLER
|
||||
|
||||
if (parent in view(user.client?.view || world.view, user))
|
||||
examine_list += span_notice("\The [hanging_support_atom] is currently supporting [span_bold("[parent]")]. Deconstruction or excessive damage would cause it to [span_bold("fall to the ground")].")
|
||||
examine_list += span_notice("\The [hanging_support_atom] is currently supporting [span_bold("\the [parent]")]. Deconstruction or excessive damage would cause it to [span_bold("fall to the ground")].")
|
||||
|
||||
/// When the type of turf changes, if it is changing into a floor we should drop our contents
|
||||
/datum/component/atom_mounted/proc/on_turf_changing(datum/source, path, new_baseturfs, flags, post_change_callbacks)
|
||||
@@ -47,6 +47,7 @@
|
||||
if(ispath(path, /turf/open))
|
||||
drop_wallmount()
|
||||
|
||||
///When the atom the object is mounted on is destroyed deconstruct
|
||||
/datum/component/atom_mounted/proc/on_structure_delete(datum/source, force)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
@@ -61,6 +62,12 @@
|
||||
return
|
||||
drop_wallmount()
|
||||
|
||||
///Called when the object is about to be shuttle rotated so we have to delete ourself and mount again later
|
||||
/datum/component/atom_mounted/proc/detach(datum/source, newT, rotation, move_mode, moving_dock)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
qdel(src)
|
||||
|
||||
/**
|
||||
* Handles the dropping of the linked object. This is done via deconstruction, as that should be the most sane way to handle it for most objects.
|
||||
* Except for intercoms, which are handled by creating a new wallframe intercom, as they're apparently items.
|
||||
@@ -72,7 +79,6 @@
|
||||
hanging_parent.visible_message(message = span_warning("\The [hanging_parent] falls apart!"), vision_distance = 5)
|
||||
hanging_parent.deconstruct(FALSE)
|
||||
|
||||
|
||||
/// Returns a list of potential turfs to mount on. This should not check if those turfs are valid but only locate them
|
||||
/obj/proc/get_turfs_to_mount_on()
|
||||
PROTECTED_PROC(TRUE)
|
||||
@@ -107,7 +113,7 @@
|
||||
return isclosedturf(target)
|
||||
|
||||
/// Returns an list of object types we can mount on if the turf is unmountable
|
||||
/obj/proc/get_moutable_objects()
|
||||
/obj/proc/get_mountable_objects()
|
||||
PROTECTED_PROC(TRUE)
|
||||
SHOULD_BE_PURE(TRUE)
|
||||
RETURN_TYPE(/list/obj)
|
||||
@@ -137,11 +143,11 @@
|
||||
return TRUE
|
||||
|
||||
var/area/location = get_area(src)
|
||||
if(!isarea(location) || istype(location, /area/shuttle))
|
||||
if(!isarea(location))
|
||||
return FALSE
|
||||
|
||||
var/msg
|
||||
if(PERFORM_ALL_TESTS(focus_only/atom_mounted) && !mark_for_late_init)
|
||||
if(PERFORM_ALL_TESTS(maptest_log_mapping) && !mark_for_late_init)
|
||||
msg = "[type] Could not find attachable object at [location.type] "
|
||||
|
||||
var/list/turf/attachable_turfs = get_turfs_to_mount_on()
|
||||
@@ -150,19 +156,28 @@
|
||||
if(is_mountable_turf(target))
|
||||
attachable_atom = target //your usual wallmount
|
||||
else
|
||||
var/list/obj/attachables = get_moutable_objects()
|
||||
var/list/obj/attachables = get_mountable_objects()
|
||||
for(var/obj/attachable in target)
|
||||
if(is_type_in_list(attachable, attachables))
|
||||
attachable_atom = attachable
|
||||
break
|
||||
if(attachable_atom)
|
||||
AddComponent(/datum/component/atom_mounted, attachable_atom)
|
||||
if(is_area_shuttle(location))
|
||||
RegisterSignal(src, COMSIG_ATOM_AFTER_SHUTTLE_MOVE, PROC_REF(remount), override = TRUE)
|
||||
return TRUE
|
||||
if(msg)
|
||||
msg += "([target.x],[target.y],[target.z]) "
|
||||
if(msg)
|
||||
stack_trace(msg)
|
||||
log_mapping(msg)
|
||||
|
||||
if(mark_for_late_init)
|
||||
obj_flags |= MOUNT_ON_LATE_INITIALIZE
|
||||
return FALSE
|
||||
|
||||
///Used to remount an object after shuttle move
|
||||
/obj/proc/remount(datum/source, oldT)
|
||||
SIGNAL_HANDLER
|
||||
PRIVATE_PROC(TRUE)
|
||||
|
||||
find_and_mount_on_atom()
|
||||
|
||||
@@ -120,8 +120,7 @@
|
||||
|
||||
for (var/mob/living/candidate as anything in to_heal)
|
||||
if (!current_alerts[candidate])
|
||||
var/atom/movable/screen/alert/aura_healing/alert = candidate.throw_alert(alert_category, /atom/movable/screen/alert/aura_healing, new_master = parent)
|
||||
alert.desc = "You are being healed by [parent]."
|
||||
candidate.throw_alert(alert_category, /atom/movable/screen/alert/aura_healing, new_master = parent)
|
||||
current_alerts[candidate] = TRUE
|
||||
|
||||
if (should_show_effect && candidate.health < candidate.maxHealth)
|
||||
@@ -164,5 +163,10 @@
|
||||
icon_state = "template"
|
||||
use_user_hud_icon = TRUE
|
||||
clickable_glow = TRUE
|
||||
click_master = FALSE
|
||||
|
||||
/atom/movable/screen/alert/aura_healing/update_desc(updates)
|
||||
. = ..()
|
||||
desc = "You are being healed by [master_ref?.resolve()]."
|
||||
|
||||
#undef HEAL_EFFECT_COOLDOWN
|
||||
|
||||
@@ -100,7 +100,8 @@
|
||||
if (isnull(bayonet) || !user.combat_mode)
|
||||
return NONE
|
||||
|
||||
INVOKE_ASYNC(bayonet, TYPE_PROC_REF(/obj/item, melee_attack_chain), user, target, modifiers)
|
||||
// RMB is used for butchering, and bayonets are usually are knives
|
||||
INVOKE_ASYNC(bayonet, TYPE_PROC_REF(/obj/item, melee_attack_chain), user, target, modifiers - RIGHT_CLICK)
|
||||
return COMPONENT_CANCEL_ATTACK_CHAIN
|
||||
|
||||
/datum/component/bayonet_attachable/proc/on_attackby(obj/item/source, obj/item/attacking_item, mob/attacker, list/modifiers)
|
||||
|
||||
@@ -72,7 +72,7 @@
|
||||
|
||||
/datum/component/blob_minion/RegisterWithParent()
|
||||
var/mob/living/living_parent = parent
|
||||
living_parent.faction |= ROLE_BLOB
|
||||
living_parent.add_faction(ROLE_BLOB)
|
||||
ADD_TRAIT(parent, TRAIT_BLOB_ALLY, REF(src))
|
||||
remove_verb(parent, /mob/living/verb/pulled) // No dragging people into the blob
|
||||
RegisterSignal(parent, COMSIG_MOB_MIND_INITIALIZED, PROC_REF(on_mind_init))
|
||||
@@ -96,7 +96,7 @@
|
||||
overmind.blob_mobs -= parent
|
||||
var/mob/living/living_parent = parent
|
||||
living_parent.pass_flags &= ~PASSBLOB
|
||||
living_parent.faction -= ROLE_BLOB
|
||||
living_parent.remove_faction(ROLE_BLOB)
|
||||
REMOVE_TRAIT(parent, TRAIT_BLOB_ALLY, REF(src))
|
||||
add_verb(parent, /mob/living/verb/pulled)
|
||||
UnregisterSignal(parent, list(
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Attaches to a hostile simplemob and plays that music while they have a target.
|
||||
* Attaches to a mob and plays that music while they have a target.
|
||||
*/
|
||||
/datum/component/boss_music
|
||||
///The music track we will play to players.
|
||||
@@ -12,11 +12,16 @@
|
||||
///List of callback timers, used to clear out mobs listening to boss music after `track_duration`.
|
||||
var/list/music_callbacks = list()
|
||||
|
||||
/datum/component/boss_music/Initialize(boss_track)
|
||||
/// Signal that we listen to on our parent to know when to start boss music.
|
||||
/// On basic mobs, the signal should be a variant of `COMSIG_AI_BLACKBOARD_KEY_SET()`, feeding in whatever key you want.
|
||||
var/signal = null
|
||||
|
||||
/datum/component/boss_music/Initialize(boss_track, signal)
|
||||
. = ..()
|
||||
if(!ishostile(parent))
|
||||
if(!ismob(parent) || isnull(signal))
|
||||
return COMPONENT_INCOMPATIBLE
|
||||
src.boss_track = boss_track
|
||||
src.signal = signal
|
||||
track_duration = SSsounds.get_sound_length(boss_track)
|
||||
|
||||
/datum/component/boss_music/Destroy(force)
|
||||
@@ -31,16 +36,26 @@
|
||||
|
||||
/datum/component/boss_music/RegisterWithParent()
|
||||
. = ..()
|
||||
RegisterSignal(parent, COMSIG_HOSTILE_FOUND_TARGET, PROC_REF(on_target_found))
|
||||
if(isbasicmob(parent))
|
||||
RegisterSignal(parent, signal, PROC_REF(basic_target_found))
|
||||
else
|
||||
RegisterSignal(parent, signal, PROC_REF(on_target_found))
|
||||
|
||||
/datum/component/boss_music/UnregisterFromParent()
|
||||
UnregisterSignal(parent, COMSIG_HOSTILE_FOUND_TARGET)
|
||||
UnregisterSignal(parent, signal)
|
||||
return ..()
|
||||
|
||||
/// Handler wrapper for basic mobs getting a target. The signal we pass to basic mobs passes along the blackboard key which we can access off parent.
|
||||
/datum/component/boss_music/proc/basic_target_found(mob/source, key)
|
||||
SIGNAL_HANDLER
|
||||
var/mob/new_target = source.ai_controller.blackboard[key]
|
||||
on_target_found(source, new_target)
|
||||
|
||||
///Handles giving the boss music to a new target the fauna has received.
|
||||
///Keeps track of them to not repeatedly overwrite its own track.
|
||||
/datum/component/boss_music/proc/on_target_found(atom/source, mob/new_target)
|
||||
/datum/component/boss_music/proc/on_target_found(mob/source, mob/new_target)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
if(QDELETED(source) || !istype(new_target))
|
||||
return
|
||||
|
||||
|
||||
@@ -27,72 +27,362 @@
|
||||
src.effectiveness = effectiveness
|
||||
src.bonus_modifier = bonus_modifier
|
||||
src.butcher_sound = butcher_sound
|
||||
if(disabled)
|
||||
if (disabled)
|
||||
src.butchering_enabled = FALSE
|
||||
src.can_be_blunt = can_be_blunt
|
||||
src.butcher_callback = butcher_callback
|
||||
if(isitem(parent))
|
||||
RegisterSignal(parent, COMSIG_ITEM_ATTACK, PROC_REF(onItemAttack))
|
||||
|
||||
/datum/component/butchering/Destroy(force)
|
||||
butcher_callback = null
|
||||
return ..()
|
||||
|
||||
/datum/component/butchering/proc/onItemAttack(obj/item/source, mob/living/M, mob/living/user)
|
||||
/datum/component/butchering/RegisterWithParent()
|
||||
if (!isitem(parent))
|
||||
return
|
||||
var/obj/item/item_parent = parent
|
||||
item_parent.item_flags |= ITEM_HAS_CONTEXTUAL_SCREENTIPS
|
||||
RegisterSignal(parent, COMSIG_ITEM_ATTACK, PROC_REF(on_item_attack))
|
||||
RegisterSignal(parent, COMSIG_ITEM_INTERACTING_WITH_ATOM, PROC_REF(on_item_interaction))
|
||||
RegisterSignal(parent, COMSIG_ITEM_REQUESTING_CONTEXT_FOR_TARGET, PROC_REF(add_item_context))
|
||||
|
||||
/datum/component/butchering/UnregisterFromParent()
|
||||
UnregisterSignal(parent, list(COMSIG_ITEM_ATTACK, COMSIG_ITEM_INTERACTING_WITH_ATOM))
|
||||
|
||||
/datum/component/butchering/proc/on_item_attack(obj/item/source, mob/living/victim, mob/living/user, list/modifiers)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
if(!user.combat_mode)
|
||||
return
|
||||
if(M.stat == DEAD && (M.butcher_results || M.guaranteed_butcher_results)) //can we butcher it?
|
||||
if(butchering_enabled && (can_be_blunt || source.get_sharpness()))
|
||||
INVOKE_ASYNC(src, PROC_REF(startButcher), source, M, user)
|
||||
return COMPONENT_CANCEL_ATTACK_CHAIN
|
||||
|
||||
if(ishuman(M) && source.force && source.get_sharpness())
|
||||
var/mob/living/carbon/human/H = M
|
||||
if((user.pulling == H && user.grab_state >= GRAB_AGGRESSIVE) && user.zone_selected == BODY_ZONE_HEAD) // Only aggressive grabbed can be sliced.
|
||||
if(HAS_TRAIT(user, TRAIT_PACIFISM))
|
||||
to_chat(user, span_warning("You don't want to harm other living beings!"))
|
||||
return COMPONENT_CANCEL_ATTACK_CHAIN
|
||||
|
||||
if(H.has_status_effect(/datum/status_effect/neck_slice))
|
||||
return
|
||||
|
||||
INVOKE_ASYNC(src, PROC_REF(startNeckSlice), source, H, user)
|
||||
return COMPONENT_CANCEL_ATTACK_CHAIN
|
||||
|
||||
/datum/component/butchering/proc/startButcher(obj/item/source, mob/living/M, mob/living/user)
|
||||
to_chat(user, span_notice("You begin to butcher [M]..."))
|
||||
playsound(M.loc, butcher_sound, 50, TRUE, -1)
|
||||
if(do_after(user, speed, M) && M.Adjacent(source))
|
||||
on_butchering(user, M)
|
||||
|
||||
/datum/component/butchering/proc/startNeckSlice(obj/item/source, mob/living/carbon/human/H, mob/living/user)
|
||||
if(DOING_INTERACTION_WITH_TARGET(user, H))
|
||||
to_chat(user, span_warning("You're already interacting with [H]!"))
|
||||
if (!source.get_sharpness() && !can_be_blunt)
|
||||
return
|
||||
|
||||
user.visible_message(span_danger("[user] is slitting [H]'s throat!"), \
|
||||
span_danger("You start slicing [H]'s throat!"), \
|
||||
span_hear("You hear a cutting noise!"), ignored_mobs = H)
|
||||
H.show_message(span_userdanger("Your throat is being slit by [user]!"), MSG_VISUAL, \
|
||||
span_userdanger("Something is cutting into your neck!"), NONE)
|
||||
log_combat(user, H, "attempted throat slitting", source)
|
||||
if (!user.combat_mode)
|
||||
return
|
||||
|
||||
playsound(H.loc, butcher_sound, 50, TRUE, -1)
|
||||
if(do_after(user, clamp(500 / source.force, 30, 100), H) && H.Adjacent(source))
|
||||
if(H.has_status_effect(/datum/status_effect/neck_slice))
|
||||
user.show_message(span_warning("[H]'s neck has already been already cut, you can't make the bleeding any worse!"), MSG_VISUAL, \
|
||||
span_warning("Their neck has already been already cut, you can't make the bleeding any worse!"))
|
||||
// Can we butcher it?
|
||||
if (victim.stat == DEAD && (victim.butcher_results || victim.guaranteed_butcher_results) && butchering_enabled)
|
||||
INVOKE_ASYNC(src, PROC_REF(start_butcher), source, victim, user)
|
||||
return COMPONENT_CANCEL_ATTACK_CHAIN
|
||||
|
||||
if (!ishuman(victim) || !source.force)
|
||||
return
|
||||
|
||||
if (LAZYACCESS(modifiers, RIGHT_CLICK) && butchering_enabled)
|
||||
INVOKE_ASYNC(src, PROC_REF(butcher_human), source, victim, user)
|
||||
return COMPONENT_CANCEL_ATTACK_CHAIN
|
||||
|
||||
// Neckslicing requires aggro grabs
|
||||
if (user.pulling != victim || user.grab_state < GRAB_AGGRESSIVE || user.zone_selected != BODY_ZONE_HEAD)
|
||||
return
|
||||
|
||||
if (HAS_TRAIT(user, TRAIT_PACIFISM))
|
||||
to_chat(user, span_warning("You don't want to harm other living beings!"))
|
||||
return COMPONENT_CANCEL_ATTACK_CHAIN
|
||||
|
||||
if (victim.has_status_effect(/datum/status_effect/neck_slice))
|
||||
return
|
||||
|
||||
INVOKE_ASYNC(src, PROC_REF(start_neck_slice), source, victim, user)
|
||||
return COMPONENT_CANCEL_ATTACK_CHAIN
|
||||
|
||||
/datum/component/butchering/proc/on_item_interaction(obj/item/source, mob/living/user, atom/target)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
if (!source.get_sharpness() && !can_be_blunt)
|
||||
return
|
||||
|
||||
if (!user.combat_mode || !butchering_enabled)
|
||||
return
|
||||
|
||||
if (isbodypart(target))
|
||||
INVOKE_ASYNC(src, PROC_REF(butcher_limb), source, target, user)
|
||||
return COMPONENT_CANCEL_ATTACK_CHAIN
|
||||
|
||||
/datum/component/butchering/proc/add_item_context(obj/item/source, list/context, atom/target, mob/living/user)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
if (!source.get_sharpness() && !can_be_blunt)
|
||||
return NONE
|
||||
|
||||
if (!butchering_enabled)
|
||||
return NONE
|
||||
|
||||
if (isbodypart(target))
|
||||
context[SCREENTIP_CONTEXT_LMB] = "(Combat Mode) Butcher limb"
|
||||
return CONTEXTUAL_SCREENTIP_SET
|
||||
|
||||
if (!isliving(target))
|
||||
return NONE
|
||||
|
||||
var/mob/living/victim = target
|
||||
if (victim.stat != DEAD && (victim.butcher_results || victim.guaranteed_butcher_results))
|
||||
context[SCREENTIP_CONTEXT_LMB] = "(Combat Mode) Butcher"
|
||||
return CONTEXTUAL_SCREENTIP_SET
|
||||
|
||||
if (ishuman(victim))
|
||||
context[SCREENTIP_CONTEXT_RMB] = "(Combat Mode) Butcher"
|
||||
return CONTEXTUAL_SCREENTIP_SET
|
||||
|
||||
return NONE
|
||||
|
||||
/datum/component/butchering/proc/butcher_limb(obj/item/source, obj/item/bodypart/target, mob/living/user)
|
||||
target.add_fingerprint(user)
|
||||
if (LIMB_HAS_SKIN(target) && !HAS_ANY_SURGERY_STATE(target.surgery_state, SURGERY_SKIN_CUT | SURGERY_SKIN_OPEN))
|
||||
to_chat(user, span_warning("[target]'s skin is still intact!"))
|
||||
return
|
||||
|
||||
if (LIMB_HAS_BONES(target) && !HAS_ANY_SURGERY_STATE(target.surgery_state, SURGERY_BONE_DRILLED | SURGERY_BONE_SAWED))
|
||||
// We need to gut the limb before turning it into meat, otherwise just cut around the bone I guess
|
||||
if (length(target.contents))
|
||||
to_chat(user, span_warning("[target]'s bones are still intact!"))
|
||||
return
|
||||
|
||||
H.visible_message(span_danger("[user] slits [H]'s throat!"), \
|
||||
span_userdanger("[user] slits your throat..."))
|
||||
log_combat(user, H, "wounded via throat slitting", source)
|
||||
H.apply_damage(source.force, BRUTE, BODY_ZONE_HEAD, wound_bonus=CANT_WOUND) // easy tiger, we'll get to that in a sec
|
||||
var/obj/item/bodypart/slit_throat = H.get_bodypart(BODY_ZONE_HEAD)
|
||||
if (H.cause_wound_of_type_and_severity(WOUND_SLASH, slit_throat, WOUND_SEVERITY_CRITICAL))
|
||||
H.apply_status_effect(/datum/status_effect/neck_slice)
|
||||
var/speed_modifier = 1
|
||||
if (!target.owner)
|
||||
speed_modifier = 0.5
|
||||
else if (target.owner.stat < UNCONSCIOUS)
|
||||
speed_modifier = 1.5 // yeowch
|
||||
|
||||
var/limb_descriptor = (target.owner ? "[target.owner]'s [target.plaintext_zone]" : target)
|
||||
// Okay *hopefully* they're already dead at this point
|
||||
if (target.body_zone == BODY_ZONE_CHEST && target.owner)
|
||||
// Butchering the chest gibs the victim
|
||||
limb_descriptor = target.owner
|
||||
|
||||
if (target.owner)
|
||||
log_combat(user, target.owner, "attempted to butcher", source)
|
||||
|
||||
if (length(target.contents))
|
||||
user.visible_message(span_warning("[user] begins to gut [limb_descriptor]!"), span_notice("You begin to gut [limb_descriptor]..."), ignored_mobs = target.owner)
|
||||
if (target.owner)
|
||||
to_chat(target.owner, span_warning("[user] begins to gut your [target.plaintext_zone]!"))
|
||||
|
||||
playsound(target.loc, butcher_sound, 50, TRUE, -1)
|
||||
if (!do_after(user, speed * speed_modifier, target.owner || target))
|
||||
return
|
||||
target.drop_organs(user, TRUE)
|
||||
return
|
||||
|
||||
if (!length(target.butcher_drops))
|
||||
to_chat(user, span_warning("There is nothing left inside [limb_descriptor]!"))
|
||||
return
|
||||
|
||||
if (target.body_zone == BODY_ZONE_CHEST && target.owner)
|
||||
// Cannot butcher the chest until we hack off all the other limbs
|
||||
for (var/obj/item/bodypart/limb as anything in target.owner.bodyparts)
|
||||
if (limb != target && limb.butcher_drops && limb.butcher_replacement)
|
||||
to_chat(user, span_warning("You need to butcher all other limbs first!"))
|
||||
return
|
||||
|
||||
user.visible_message(span_warning("[user] begins to cut [limb_descriptor] apart!"), span_notice("You begin to cut [limb_descriptor] apart..."), ignored_mobs = target.owner)
|
||||
if (target.owner)
|
||||
to_chat(target.owner, span_warning("[user] begins to cut your [target.plaintext_zone] apart!"))
|
||||
|
||||
playsound(target.loc, butcher_sound, 50, TRUE, -1)
|
||||
if (!do_after(user, speed * speed_modifier, target.owner || target))
|
||||
return
|
||||
|
||||
var/list/results = list()
|
||||
var/turf/drop_loc = target.owner?.drop_location() || target.drop_location()
|
||||
var/bonus_chance = max(0, (effectiveness - 100) + bonus_modifier) //so 125 total effectiveness = 25% extra chance
|
||||
|
||||
var/list/failures = list()
|
||||
var/list/bonuses = list()
|
||||
|
||||
for (var/obj/item/drop_type as anything in target.butcher_drops)
|
||||
var/amount = target.butcher_drops[drop_type] || 1
|
||||
var/is_stack = ispath(drop_type, /obj/item/stack)
|
||||
|
||||
for (var/i in 1 to amount)
|
||||
if (!prob(effectiveness))
|
||||
failures |= drop_type::name
|
||||
amount -= 1
|
||||
continue
|
||||
|
||||
if (prob(bonus_chance))
|
||||
if (!is_stack)
|
||||
if (ispath(drop_type, /obj/item/food/meat))
|
||||
results += new drop_type(drop_loc, target.blood_dna_info?.Copy())
|
||||
else
|
||||
var/obj/item/butcher_result = new drop_type(drop_loc)
|
||||
if (target.blood_dna_info)
|
||||
butcher_result.add_blood_DNA(target.blood_dna_info.Copy())
|
||||
results += butcher_result
|
||||
amount += 1
|
||||
bonuses |= drop_type::name
|
||||
|
||||
if (is_stack)
|
||||
continue
|
||||
|
||||
if (ispath(drop_type, /obj/item/food/meat))
|
||||
results += new drop_type(drop_loc, target.blood_dna_info?.Copy())
|
||||
continue
|
||||
|
||||
var/obj/item/butcher_result = new drop_type(drop_loc)
|
||||
butcher_result.add_blood_DNA(target.blood_dna_info.Copy())
|
||||
results += butcher_result
|
||||
|
||||
if (is_stack && amount)
|
||||
var/obj/item/stack/butcher_result = null
|
||||
if (ispath(drop_type, /obj/item/stack/sheet/animalhide/carbon))
|
||||
butcher_result = new drop_type(drop_loc, amount, /*merge = */TRUE, /*mat_override = */null, /*mat_amount = */1, target.skin_tone || target.species_color)
|
||||
else
|
||||
butcher_result = new drop_type(drop_loc, amount)
|
||||
if (target.blood_dna_info)
|
||||
butcher_result.add_blood_DNA(target.blood_dna_info.Copy())
|
||||
results += butcher_result
|
||||
|
||||
if (target.reagents)
|
||||
var/meat_produced = 0
|
||||
for (var/obj/item/food/target_meat in results)
|
||||
meat_produced += 1
|
||||
|
||||
for (var/obj/item/food/target_meat in results)
|
||||
target.reagents.trans_to(target_meat, target.reagents.total_volume / meat_produced, remove_blacklisted = TRUE)
|
||||
|
||||
if (target.owner)
|
||||
var/reagents_in_produced = 0
|
||||
for(var/obj/item/result as anything in results)
|
||||
if(result.reagents)
|
||||
reagents_in_produced += 1
|
||||
|
||||
var/list/diseases = target.owner.get_static_viruses()
|
||||
|
||||
for(var/obj/item/result as anything in results)
|
||||
if (reagents_in_produced)
|
||||
if (target.owner.reagents)
|
||||
target.owner.reagents.trans_to(result, target.owner.reagents.total_volume / reagents_in_produced / length(target.owner.bodyparts), remove_blacklisted = TRUE)
|
||||
result.reagents?.add_reagent(/datum/reagent/consumable/nutriment/fat, target.owner.nutrition / /datum/reagent/consumable/nutriment/fat::nutriment_factor / reagents_in_produced)
|
||||
|
||||
if(LAZYLEN(diseases))
|
||||
var/list/datum/disease/diseases_to_add = list()
|
||||
for(var/datum/disease/disease as anything in diseases)
|
||||
// Admin or special viruses that should not be reproduced
|
||||
if(disease.spread_flags & (DISEASE_SPREAD_SPECIAL | DISEASE_SPREAD_NON_CONTAGIOUS))
|
||||
continue
|
||||
|
||||
diseases_to_add += disease
|
||||
|
||||
if(LAZYLEN(diseases_to_add))
|
||||
result.AddComponent(/datum/component/infective, diseases_to_add)
|
||||
|
||||
for (var/obj/item/food/meat/meat in results)
|
||||
meat.name = "[target.owner.real_name]'s [meat.name]"
|
||||
meat.set_custom_materials(list(GET_MATERIAL_REF(/datum/material/meat/mob_meat, target.owner) = 4 * SHEET_MATERIAL_AMOUNT))
|
||||
meat.subjectname = target.owner.real_name
|
||||
meat.subjectjob = target.owner.job
|
||||
|
||||
user.visible_message(span_warning("[user] butchers [limb_descriptor]!"), span_notice("You butcher [limb_descriptor]."), ignored_mobs = target.owner)
|
||||
if (!target.owner)
|
||||
target.drop_organs(violent_removal = TRUE) // Should not happen, but just in case
|
||||
create_replacement_limb(target, drop_loc)
|
||||
qdel(target)
|
||||
return
|
||||
|
||||
var/wound_type = null
|
||||
if (source.damtype == BURN)
|
||||
wound_type = WOUND_BURN
|
||||
else
|
||||
switch (source.get_sharpness())
|
||||
if (SHARP_EDGED)
|
||||
wound_type = WOUND_SLASH
|
||||
if (SHARP_POINTY)
|
||||
wound_type = WOUND_PIERCE
|
||||
else
|
||||
wound_type = WOUND_BLUNT
|
||||
|
||||
to_chat(target.owner, span_userdanger("[user] hacks the meat off your [target.plaintext_zone]!"))
|
||||
var/mob/living/carbon/victim = target.owner
|
||||
|
||||
if (!target.butcher_replacement)
|
||||
target.dismember(source.damtype, wound_type)
|
||||
target.drop_organs(violent_removal = TRUE) // Should not happen, but just in case
|
||||
qdel(target)
|
||||
return
|
||||
|
||||
var/obj/item/bodypart/replacement = create_replacement_limb(target, drop_loc)
|
||||
target.dismember(source.damtype, wound_type)
|
||||
target.drop_organs(violent_removal = TRUE)
|
||||
replacement.replace_limb(victim)
|
||||
replacement.update_limb(is_creating = TRUE)
|
||||
qdel(target)
|
||||
|
||||
/// Creates a replacement (usually skeleton) limb for the butchered one
|
||||
/datum/component/butchering/proc/create_replacement_limb(obj/item/bodypart/target, drop_loc)
|
||||
var/drop_type = target.butcher_replacement
|
||||
var/obj/item/bodypart/replacement = new drop_type(drop_loc)
|
||||
replacement.bodyshape = target.bodyshape
|
||||
replacement.set_initial_damage(target.brute_dam, target.burn_dam)
|
||||
if (IS_ORGANIC_LIMB(replacement) && target.owner)
|
||||
replacement.blood_dna_info = target.owner.get_blood_dna_list()
|
||||
|
||||
for (var/datum/wound/wound as anything in target.wounds)
|
||||
wound.remove_wound()
|
||||
wound.apply_wound(replacement, silent = TRUE)
|
||||
|
||||
return replacement
|
||||
|
||||
/datum/component/butchering/proc/start_butcher(obj/item/source, mob/living/target, mob/living/user)
|
||||
to_chat(user, span_notice("You begin to butcher [target]..."))
|
||||
playsound(target.loc, butcher_sound, 50, TRUE, -1)
|
||||
if (do_after(user, speed, target) && target.Adjacent(source))
|
||||
on_butchering(user, target)
|
||||
|
||||
/datum/component/butchering/proc/butcher_human(obj/item/source, mob/living/carbon/human/victim, mob/living/user)
|
||||
if (DOING_INTERACTION_WITH_TARGET(user, victim))
|
||||
to_chat(user, span_warning("You're already interacting with [victim]!"))
|
||||
return
|
||||
|
||||
var/static/list/butcher_spots = typecacheof(list(
|
||||
/obj/structure/table,
|
||||
/obj/structure/bed,
|
||||
/obj/machinery/stasis,
|
||||
/obj/structure/kitchenspike,
|
||||
))
|
||||
|
||||
var/found_spot = FALSE
|
||||
for (var/obj/thing in victim.loc)
|
||||
if (is_type_in_typecache(thing, butcher_spots))
|
||||
found_spot = TRUE
|
||||
break
|
||||
|
||||
if (!found_spot)
|
||||
to_chat(user, span_warning("You need a better spot to butcher [victim]!"))
|
||||
return
|
||||
|
||||
var/obj/item/bodypart/limb = victim.get_bodypart(deprecise_zone(user.zone_selected))
|
||||
if (!limb)
|
||||
to_chat(user, span_warning("[victim] doesn't have a [parse_zone(deprecise_zone(user.zone_selected))]!"))
|
||||
return
|
||||
|
||||
butcher_limb(source, limb, user)
|
||||
|
||||
/datum/component/butchering/proc/start_neck_slice(obj/item/source, mob/living/carbon/human/victim, mob/living/user)
|
||||
if (DOING_INTERACTION_WITH_TARGET(user, victim))
|
||||
to_chat(user, span_warning("You're already interacting with [victim]!"))
|
||||
return
|
||||
|
||||
user.visible_message(span_danger("[user] is slitting [victim]'s throat!"), \
|
||||
span_danger("You start slicing [victim]'s throat!"), \
|
||||
span_hear("You hear a cutting noise!"), ignored_mobs = victim)
|
||||
victim.show_message(span_userdanger("Your throat is being slit by [user]!"), MSG_VISUAL, \
|
||||
span_userdanger("Something is cutting into your neck!"), NONE)
|
||||
log_combat(user, victim, "attempted throat slitting", source)
|
||||
|
||||
playsound(victim.loc, butcher_sound, 50, TRUE, -1)
|
||||
if (!do_after(user, clamp(500 / source.force, 30, 100), victim) && victim.Adjacent(source))
|
||||
return
|
||||
|
||||
if (victim.has_status_effect(/datum/status_effect/neck_slice))
|
||||
user.show_message(span_warning("[victim]'s neck has already been already cut, you can't make the bleeding any worse!"), MSG_VISUAL, \
|
||||
span_warning("Their neck has already been already cut, you can't make the bleeding any worse!"))
|
||||
return
|
||||
|
||||
victim.visible_message(span_danger("[user] slits [victim]'s throat!"), \
|
||||
span_userdanger("[user] slits your throat..."))
|
||||
log_combat(user, victim, "wounded via throat slitting", source)
|
||||
victim.apply_damage(source.force, BRUTE, BODY_ZONE_HEAD, wound_bonus=CANT_WOUND) // easy tiger, we'll get to that in a sec
|
||||
var/obj/item/bodypart/slit_throat = victim.get_bodypart(BODY_ZONE_HEAD)
|
||||
if (victim.cause_wound_of_type_and_severity(WOUND_SLASH, slit_throat, WOUND_SEVERITY_CRITICAL))
|
||||
victim.apply_status_effect(/datum/status_effect/neck_slice)
|
||||
|
||||
/**
|
||||
* Handles a user butchering a target
|
||||
@@ -107,67 +397,85 @@
|
||||
var/final_effectiveness = effectiveness - target.butcher_difficulty
|
||||
var/bonus_chance = max(0, (final_effectiveness - 100) + bonus_modifier) //so 125 total effectiveness = 25% extra chance
|
||||
|
||||
if(target.flags_1 & HOLOGRAM_1)
|
||||
if (target.flags_1 & HOLOGRAM_1)
|
||||
butcher.visible_message(span_notice("[butcher] tries to butcher [target], but it vanishes."), \
|
||||
span_notice("You try to butcher [target], but it vanishes."))
|
||||
qdel(target)
|
||||
return
|
||||
|
||||
for(var/result_typepath in target.butcher_results)
|
||||
var/obj/remains = result_typepath
|
||||
var/amount = target.butcher_results[remains]
|
||||
for(var/_i in 1 to amount)
|
||||
if(!prob(final_effectiveness))
|
||||
if(butcher)
|
||||
to_chat(butcher, span_warning("You fail to harvest some of the [initial(remains.name)] from [target]."))
|
||||
var/list/failures = list()
|
||||
var/list/bonuses = list()
|
||||
for (var/obj/remains as anything in target.butcher_results)
|
||||
var/amount = target.butcher_results[remains] || 1
|
||||
var/is_stack = ispath(remains, /obj/item/stack)
|
||||
|
||||
for (var/i in 1 to amount)
|
||||
if (!prob(final_effectiveness))
|
||||
failures |= remains::name
|
||||
amount -= 1
|
||||
continue
|
||||
|
||||
if(prob(bonus_chance))
|
||||
if(butcher)
|
||||
to_chat(butcher, span_info("You harvest some extra [initial(remains.name)] from [target]!"))
|
||||
results += new remains (location)
|
||||
results += new remains (location)
|
||||
if (prob(bonus_chance))
|
||||
if (!is_stack)
|
||||
results += new remains(location)
|
||||
amount += 1
|
||||
bonuses |= remains::name
|
||||
|
||||
target.butcher_results.Remove(remains) //in case you want to, say, have it drop its results on gib
|
||||
if (!is_stack)
|
||||
results += new remains(location)
|
||||
|
||||
for(var/guaranteed_result_typepath in target.guaranteed_butcher_results)
|
||||
var/obj/guaranteed_remains = guaranteed_result_typepath
|
||||
if (is_stack && amount)
|
||||
results += new remains(location, amount)
|
||||
|
||||
target.butcher_results?.Cut()
|
||||
|
||||
if (butcher)
|
||||
if (length(failures))
|
||||
to_chat(butcher, span_warning("You fail to harvest some of the [english_list(failures)] from [target]."))
|
||||
if (length(bonuses))
|
||||
to_chat(butcher, span_info("You harvest some extra [english_list(bonuses)] from [target]!"))
|
||||
|
||||
for (var/obj/guaranteed_remains as anything in target.guaranteed_butcher_results)
|
||||
var/amount = target.guaranteed_butcher_results[guaranteed_remains]
|
||||
for(var/i in 1 to amount)
|
||||
results += new guaranteed_remains (location)
|
||||
target.guaranteed_butcher_results.Remove(guaranteed_remains)
|
||||
if (ispath(guaranteed_remains, /obj/item/stack))
|
||||
results += new guaranteed_remains(location, amount)
|
||||
continue
|
||||
|
||||
for(var/obj/item/carrion in results)
|
||||
for (var/i in 1 to amount)
|
||||
results += new guaranteed_remains(location)
|
||||
|
||||
target.guaranteed_butcher_results?.Cut()
|
||||
|
||||
for (var/obj/item/carrion in results)
|
||||
var/list/meat_mats = carrion.has_material_type(/datum/material/meat)
|
||||
if(!length(meat_mats))
|
||||
if (!length(meat_mats))
|
||||
continue
|
||||
carrion.set_custom_materials((carrion.custom_materials - meat_mats) + list(GET_MATERIAL_REF(/datum/material/meat/mob_meat, target) = counterlist_sum(meat_mats)))
|
||||
|
||||
// transfer delicious reagents to meat
|
||||
if(target.reagents)
|
||||
// Transfer delicious reagents to meat
|
||||
if (target.reagents)
|
||||
var/meat_produced = 0
|
||||
for(var/obj/item/food/meat/slab/target_meat in results)
|
||||
for (var/obj/item/food/target_meat in results)
|
||||
meat_produced += 1
|
||||
for(var/obj/item/food/meat/slab/target_meat in results)
|
||||
for (var/obj/item/food/target_meat in results)
|
||||
target.reagents.trans_to(target_meat, target.reagents.total_volume / meat_produced, remove_blacklisted = TRUE)
|
||||
|
||||
// dont forget yummy diseases either!
|
||||
if(iscarbon(target))
|
||||
// Don't forget yummy diseases either!
|
||||
if (iscarbon(target))
|
||||
var/mob/living/carbon/host_target = target
|
||||
var/list/diseases = host_target.get_static_viruses()
|
||||
if(LAZYLEN(diseases))
|
||||
if (LAZYLEN(diseases))
|
||||
var/list/datum/disease/diseases_to_add = list()
|
||||
for(var/datum/disease/disease as anything in diseases)
|
||||
for (var/datum/disease/disease as anything in diseases)
|
||||
// admin or special viruses that should not be reproduced
|
||||
if(disease.spread_flags & (DISEASE_SPREAD_SPECIAL | DISEASE_SPREAD_NON_CONTAGIOUS))
|
||||
continue
|
||||
if (!(disease.spread_flags & (DISEASE_SPREAD_SPECIAL | DISEASE_SPREAD_NON_CONTAGIOUS)))
|
||||
diseases_to_add += disease
|
||||
|
||||
diseases_to_add += disease
|
||||
if(LAZYLEN(diseases_to_add))
|
||||
for(var/obj/diseased_remains in results)
|
||||
if (LAZYLEN(diseases_to_add))
|
||||
for (var/obj/diseased_remains in results)
|
||||
diseased_remains.AddComponent(/datum/component/infective, diseases_to_add)
|
||||
|
||||
if(butcher)
|
||||
if (butcher)
|
||||
butcher.visible_message(span_notice("[butcher] butchers [target]."), \
|
||||
span_notice("You butcher [target]."))
|
||||
butcher_callback?.Invoke(butcher, target)
|
||||
@@ -175,20 +483,9 @@
|
||||
target.log_message("has been butchered by [key_name(butcher)]", LOG_ATTACK)
|
||||
target.gib(DROP_BRAIN|DROP_ORGANS)
|
||||
|
||||
///Enables the butchering mechanic for the mob who has equipped us.
|
||||
/datum/component/butchering/proc/enable_butchering(datum/source)
|
||||
SIGNAL_HANDLER
|
||||
butchering_enabled = TRUE
|
||||
|
||||
///Disables the butchering mechanic for the mob who has dropped us.
|
||||
/datum/component/butchering/proc/disable_butchering(datum/source)
|
||||
SIGNAL_HANDLER
|
||||
butchering_enabled = FALSE
|
||||
|
||||
///Special snowflake component only used for the recycler.
|
||||
/// Special snowflake component only used for the recycler.
|
||||
/datum/component/butchering/recycler
|
||||
|
||||
|
||||
/datum/component/butchering/recycler/Initialize(
|
||||
speed,
|
||||
effectiveness,
|
||||
@@ -198,10 +495,10 @@
|
||||
can_be_blunt,
|
||||
butcher_callback,
|
||||
)
|
||||
if(!istype(parent, /obj/machinery/recycler)) //EWWW
|
||||
if (!istype(parent, /obj/machinery/recycler)) //EWWW
|
||||
return COMPONENT_INCOMPATIBLE
|
||||
. = ..()
|
||||
if(. == COMPONENT_INCOMPATIBLE)
|
||||
if (. == COMPONENT_INCOMPATIBLE)
|
||||
return
|
||||
|
||||
var/static/list/loc_connections = list(
|
||||
@@ -212,13 +509,13 @@
|
||||
/datum/component/butchering/recycler/proc/on_entered(datum/source, atom/movable/arrived, atom/old_loc, list/atom/old_locs)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
if(!isliving(arrived))
|
||||
if (!isliving(arrived))
|
||||
return
|
||||
var/mob/living/victim = arrived
|
||||
var/obj/machinery/recycler/eater = parent
|
||||
if(eater.safety_mode || (eater.machine_stat & (BROKEN|NOPOWER))) //I'm so sorry.
|
||||
if (eater.safety_mode || (eater.machine_stat & (BROKEN|NOPOWER))) //I'm so sorry.
|
||||
return
|
||||
if(victim.stat == DEAD && (victim.butcher_results || victim.guaranteed_butcher_results))
|
||||
if (victim.stat == DEAD && (victim.butcher_results || victim.guaranteed_butcher_results))
|
||||
on_butchering(parent, victim)
|
||||
|
||||
/datum/component/butchering/mecha
|
||||
@@ -237,6 +534,16 @@
|
||||
COMSIG_MECHA_EQUIPMENT_DETACHED,
|
||||
))
|
||||
|
||||
/// Enables the butchering mechanic for the mecha who has equipped us.
|
||||
/datum/component/butchering/mecha/proc/enable_butchering(datum/source)
|
||||
SIGNAL_HANDLER
|
||||
butchering_enabled = TRUE
|
||||
|
||||
/// Disables the butchering mechanic for the mecha who has dropped us.
|
||||
/datum/component/butchering/mecha/proc/disable_butchering(datum/source)
|
||||
SIGNAL_HANDLER
|
||||
butchering_enabled = FALSE
|
||||
|
||||
///When we are ready to drill through a mob
|
||||
/datum/component/butchering/mecha/proc/on_drill(datum/source, obj/vehicle/sealed/mecha/chassis, mob/living/target)
|
||||
SIGNAL_HANDLER
|
||||
@@ -256,16 +563,16 @@
|
||||
COMSIG_ITEM_DROPPED,
|
||||
))
|
||||
|
||||
///Same as enable_butchering but for worn items
|
||||
/// Same as enable_butchering but for worn items
|
||||
/datum/component/butchering/wearable/proc/worn_enable_butchering(obj/item/source, mob/user, slot)
|
||||
SIGNAL_HANDLER
|
||||
//check if the item is being not worn
|
||||
if(!(slot & source.slot_flags))
|
||||
if (!(slot & source.slot_flags))
|
||||
return
|
||||
butchering_enabled = TRUE
|
||||
RegisterSignal(user, COMSIG_LIVING_UNARMED_ATTACK, PROC_REF(butcher_target))
|
||||
|
||||
///Same as disable_butchering but for worn items
|
||||
/// Same as disable_butchering but for worn items
|
||||
/datum/component/butchering/wearable/proc/worn_disable_butchering(obj/item/source, mob/user)
|
||||
SIGNAL_HANDLER
|
||||
butchering_enabled = FALSE
|
||||
@@ -273,6 +580,6 @@
|
||||
|
||||
/datum/component/butchering/wearable/proc/butcher_target(mob/user, atom/target, proximity)
|
||||
SIGNAL_HANDLER
|
||||
if(!isliving(target))
|
||||
if (!isliving(target))
|
||||
return NONE
|
||||
return onItemAttack(parent, target, user)
|
||||
return on_item_attack(parent, target, user)
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
var/oblivion_message = "You stumble and stare into the abyss before you. It stares back, and you fall into the enveloping dark."
|
||||
|
||||
/// List of refs to falling objects -> how many levels deep we've fallen
|
||||
var/static/list/falling_atoms = list()
|
||||
var/static/list/falling_atoms
|
||||
var/static/list/forbidden_types = typecacheof(list(
|
||||
/obj/docking_port,
|
||||
/obj/effect/abstract,
|
||||
@@ -106,7 +106,8 @@
|
||||
/datum/component/chasm/proc/droppable(atom/movable/dropped_thing)
|
||||
var/datum/weakref/falling_ref = WEAKREF(dropped_thing)
|
||||
// avoid an infinite loop, but allow falling a large distance
|
||||
if(falling_atoms[falling_ref] && falling_atoms[falling_ref] > 30)
|
||||
var/falling_atom = LAZYACCESS(falling_atoms, falling_ref)
|
||||
if(falling_atom && falling_atom > 30)
|
||||
return CHASM_NOT_DROPPING
|
||||
if(is_type_in_typecache(dropped_thing, forbidden_types) || (!isliving(dropped_thing) && !isobj(dropped_thing)))
|
||||
return CHASM_NOT_DROPPING
|
||||
@@ -133,14 +134,14 @@
|
||||
var/datum/weakref/falling_ref = WEAKREF(dropped_thing)
|
||||
//Make sure the item is still there after our sleep
|
||||
if(!dropped_thing || !falling_ref?.resolve())
|
||||
falling_atoms -= falling_ref
|
||||
LAZYREMOVE(falling_atoms, falling_ref)
|
||||
return
|
||||
|
||||
falling_atoms[falling_ref] = (falling_atoms[falling_ref] || 0) + 1
|
||||
LAZYSET(falling_atoms, falling_ref, (falling_atoms[falling_ref] || 0) + 1)
|
||||
var/turf/below_turf = target_turf
|
||||
var/atom/parent = src.parent
|
||||
|
||||
if(falling_atoms[falling_ref] > 1)
|
||||
if(LAZYACCESS(falling_atoms, falling_ref) > 1)
|
||||
return // We're already handling this
|
||||
|
||||
if(SEND_SIGNAL(dropped_thing, COMSIG_MOVABLE_CHASM_DROPPED, parent) & COMPONENT_NO_CHASM_DROP)
|
||||
@@ -164,15 +165,28 @@
|
||||
var/mob/living/fallen = dropped_thing
|
||||
fallen.Paralyze(100)
|
||||
fallen.adjust_brute_loss(30)
|
||||
falling_atoms -= falling_ref
|
||||
LAZYREMOVE(falling_atoms, falling_ref)
|
||||
return
|
||||
|
||||
// send to oblivion
|
||||
dropped_thing.visible_message(span_boldwarning("[dropped_thing] falls into [parent]!"), span_userdanger("[oblivion_message]"))
|
||||
|
||||
if (isliving(dropped_thing))
|
||||
var/mob/living/falling_mob = dropped_thing
|
||||
ADD_TRAIT(falling_mob, TRAIT_NO_TRANSFORM, REF(src))
|
||||
falling_mob.Paralyze(20 SECONDS)
|
||||
falling_mob.Stun(20 SECONDS, ignore_canstun = TRUE)
|
||||
|
||||
if (HAS_MIND_TRAIT(falling_mob, TRAIT_NAIVE))
|
||||
falling_mob.do_alert_animation()
|
||||
dropped_thing.visible_message(span_boldwarning("[dropped_thing] kicks [dropped_thing.p_their()] legs in the air, as if running in place!"))
|
||||
dropped_thing.Shake(1, 0, 2 SECONDS, 0.3 SECONDS)
|
||||
sleep(3 SECONDS)
|
||||
|
||||
if (get_turf(falling_mob) != get_turf(parent))
|
||||
REMOVE_TRAIT(falling_mob, TRAIT_NO_TRANSFORM, REF(src))
|
||||
falling_mob.Paralyze(17 SECONDS, ignore_canstun = TRUE) // Wow nice job
|
||||
return
|
||||
|
||||
dropped_thing.visible_message(span_boldwarning("[dropped_thing] falls into [parent]!"), span_userdanger("[oblivion_message]"))
|
||||
|
||||
var/oldtransform = dropped_thing.transform
|
||||
var/oldcolor = dropped_thing.color
|
||||
@@ -222,7 +236,7 @@
|
||||
fallen_mob.death(TRUE)
|
||||
fallen_mob.apply_damage(300)
|
||||
|
||||
falling_atoms -= falling_ref
|
||||
LAZYREMOVE(falling_atoms, falling_ref)
|
||||
|
||||
/**
|
||||
* Called when something has left the chasm depths storage.
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
/// Allows objects that entered parent's tile to move freely through other objects with this component regardless of density
|
||||
/datum/component/climb_walkable
|
||||
|
||||
/datum/component/climb_walkable/RegisterWithParent()
|
||||
var/static/list/turf_connections = list(
|
||||
COMSIG_ATOM_ENTERED = PROC_REF(on_enter),
|
||||
COMSIG_ATOM_AFTER_SUCCESSFUL_INITIALIZED_ON = PROC_REF(on_enter),
|
||||
COMSIG_ATOM_EXITED = PROC_REF(on_exit),
|
||||
)
|
||||
AddComponent(/datum/component/connect_loc_behalf, parent, turf_connections)
|
||||
RegisterSignal(parent, COMSIG_ATOM_TRIED_PASS, PROC_REF(can_allow_through))
|
||||
|
||||
/datum/component/climb_walkable/UnregisterFromParent()
|
||||
UnregisterSignal(parent, COMSIG_ATOM_TRIED_PASS)
|
||||
for (var/atom/movable/climber in get_turf(parent))
|
||||
REMOVE_TRAIT(climber, TRAIT_ON_CLIMBABLE, REF(src))
|
||||
|
||||
/datum/component/climb_walkable/proc/on_enter(datum/source, atom/movable/arrived)
|
||||
SIGNAL_HANDLER
|
||||
ADD_TRAIT(arrived, TRAIT_ON_CLIMBABLE, REF(src))
|
||||
|
||||
/datum/component/climb_walkable/proc/on_exit(datum/source, atom/movable/gone, direction)
|
||||
SIGNAL_HANDLER
|
||||
REMOVE_TRAIT(gone, TRAIT_ON_CLIMBABLE, REF(src))
|
||||
|
||||
/datum/component/climb_walkable/proc/can_allow_through(datum/source, atom/movable/mover, border_dir)
|
||||
SIGNAL_HANDLER
|
||||
if(HAS_TRAIT(mover, TRAIT_ON_CLIMBABLE))
|
||||
return COMSIG_COMPONENT_PERMIT_PASSAGE
|
||||
@@ -42,7 +42,7 @@
|
||||
return
|
||||
|
||||
// don't skip codewords when owner speaks
|
||||
if(!owner.can_hear() || !owner.has_language(hearing_args[HEARING_LANGUAGE]))
|
||||
if(HAS_TRAIT(owner, TRAIT_DEAF) || !owner.has_language(hearing_args[HEARING_LANGUAGE]))
|
||||
return
|
||||
|
||||
var/message = hearing_args[HEARING_RAW_MESSAGE]
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
///type paths of items consumed associated with how many are needed
|
||||
var/list/reqs = list()
|
||||
///type paths of items explicitly not allowed as an ingredient
|
||||
var/list/blacklist = list()
|
||||
var/list/blacklist
|
||||
///type path of item resulting from this craft
|
||||
var/result
|
||||
/// String defines of items needed but not consumed. Lazy list.
|
||||
@@ -23,13 +23,13 @@
|
||||
///time in seconds. Remember to use the SECONDS define!
|
||||
var/time = 3 SECONDS
|
||||
///type paths of items that will be forceMoved() into the result instead of being deleted
|
||||
var/list/parts = list()
|
||||
var/list/parts
|
||||
///items, structures and machineries of types that are in this list won't transfer their materials to the result
|
||||
var/list/requirements_mats_blacklist
|
||||
///if set, the materials in this list and their values will be subtracted from the result.
|
||||
var/list/removed_mats
|
||||
///like tool_behaviors but for reagents
|
||||
var/list/chem_catalysts = list()
|
||||
var/list/chem_catalysts
|
||||
///where it shows up in the crafting UI
|
||||
var/category
|
||||
///Required machines for the craft, set the assigned value of the typepath to CRAFTING_MACHINERY_CONSUME or CRAFTING_MACHINERY_USE. Lazy associative list: type_path key -> flag value.
|
||||
@@ -50,19 +50,15 @@
|
||||
var/delete_contents = TRUE
|
||||
/// Allows you to craft so that you don't have to click the craft button many times.
|
||||
var/mass_craftable = FALSE
|
||||
|
||||
///crafting_flags var to hold bool values
|
||||
var/crafting_flags = CRAFT_CHECK_DENSITY
|
||||
|
||||
/datum/crafting_recipe/New()
|
||||
if(!name && result)
|
||||
var/atom/atom_result = result
|
||||
name = initial(atom_result.name)
|
||||
|
||||
if(!(result in reqs))
|
||||
blacklist += result
|
||||
// These should be excluded from all crafting recipies
|
||||
blacklist += list(
|
||||
/**
|
||||
* Should the recipe blacklist its result? Default behavior is to blacklist any result that isn't in reqs.
|
||||
* Can be set to ALWAYS_BLACKLIST_RESULT or NEVER_BLACKLIST_RESULT to override the default behavior.
|
||||
*/
|
||||
var/blacklist_result = BLACKLIST_RESULT_IF_NOT_IN_REQS
|
||||
/// Global crafting blacklist. These should be excluded from all crafting recipes no matter what.
|
||||
var/static/list/global_blacklist = typecacheof(list(
|
||||
/obj/item/cautery/augment,
|
||||
/obj/item/cautery/cruel/augment,
|
||||
/obj/item/circular_saw/augment,
|
||||
@@ -81,15 +77,25 @@
|
||||
/obj/item/weldingtool/largetank/cyborg,
|
||||
/obj/item/wirecutters/cyborg,
|
||||
/obj/item/wrench/cyborg,
|
||||
)
|
||||
))
|
||||
|
||||
/datum/crafting_recipe/New()
|
||||
if(!name && result)
|
||||
var/atom/atom_result = result
|
||||
name = initial(atom_result.name)
|
||||
if(result && blacklist_result == BLACKLIST_RESULT_IF_NOT_IN_REQS && !(result in reqs))
|
||||
blacklist_result = ALWAYS_BLACKLIST_RESULT
|
||||
if(tool_behaviors)
|
||||
tool_behaviors = string_list(tool_behaviors)
|
||||
if(tool_paths)
|
||||
tool_paths = string_list(tool_paths)
|
||||
for(var/key in parts)
|
||||
if(!parts[key])
|
||||
for(var/key, part in parts)
|
||||
if(!part)
|
||||
//ensure every single, same-type part used for the recipe will be transferred if the value is otherwise not specified
|
||||
parts[key] = INFINITY
|
||||
part = INFINITY
|
||||
|
||||
/datum/crafting_recipe/stack
|
||||
abstract_type = /datum/crafting_recipe/stack
|
||||
|
||||
/datum/crafting_recipe/stack/New(obj/item/stack/material, datum/stack_recipe/stack_recipe)
|
||||
if(!material || !stack_recipe || !stack_recipe.result_type)
|
||||
|
||||
@@ -68,12 +68,17 @@
|
||||
var/list/requirements_list = list()
|
||||
|
||||
// Process all requirements
|
||||
for(var/requirement_path in recipe.reqs)
|
||||
var/recipe_result
|
||||
if(recipe.blacklist_result)
|
||||
recipe_result = recipe.result
|
||||
for(var/requirement_path, needed_amount in recipe.reqs)
|
||||
// Check we have the appropriate amount available in the contents list
|
||||
var/needed_amount = recipe.reqs[requirement_path]
|
||||
for(var/content_item_path in contents)
|
||||
// Right path and not blacklisted
|
||||
if(!ispath(content_item_path, requirement_path) || recipe.blacklist.Find(content_item_path))
|
||||
if(!ispath(content_item_path, requirement_path) || (content_item_path in recipe.blacklist) || is_type_in_typecache(recipe.global_blacklist, content_item_path))
|
||||
continue
|
||||
// If we are a recipe that is blacklisting its result, make sure we skip that path
|
||||
if(recipe_result && content_item_path == recipe_result)
|
||||
continue
|
||||
|
||||
needed_amount -= contents[content_item_path]
|
||||
@@ -85,14 +90,14 @@
|
||||
|
||||
// Store the instances of what we will use for recipe.check_requirements() for requirement_path
|
||||
var/list/instances_list = list()
|
||||
for(var/instance_path in item_instances)
|
||||
for(var/instance_path, item_instance in item_instances)
|
||||
if(ispath(instance_path, requirement_path))
|
||||
instances_list += item_instances[instance_path]
|
||||
instances_list += item_instance
|
||||
|
||||
requirements_list[requirement_path] = instances_list
|
||||
|
||||
for(var/requirement_path in recipe.chem_catalysts)
|
||||
if(contents[requirement_path] < recipe.chem_catalysts[requirement_path])
|
||||
for(var/requirement_path, chem_amount in recipe.chem_catalysts)
|
||||
if(contents[requirement_path] < chem_amount)
|
||||
return FALSE
|
||||
|
||||
var/mech_found = FALSE
|
||||
@@ -250,8 +255,7 @@
|
||||
var/list/total_materials = list()
|
||||
var/list/stuff_to_use = get_used_reqs(recipe, crafter, total_materials)
|
||||
|
||||
for(var/mat in recipe.removed_mats)
|
||||
var/to_remove = recipe.removed_mats[mat]
|
||||
for(var/mat, to_remove in recipe.removed_mats)
|
||||
var/datum/material/ref_mat = locate(mat) in total_materials
|
||||
if(!ref_mat)
|
||||
continue
|
||||
@@ -683,18 +687,18 @@
|
||||
data["structures"] += atoms.Find(req_atom)
|
||||
|
||||
// Ingredients / Materials
|
||||
data["reqs"] = list()
|
||||
if(recipe.reqs.len)
|
||||
data["reqs"] = list()
|
||||
for(var/req_atom in recipe.reqs)
|
||||
var/id = atoms.Find(req_atom)
|
||||
data["reqs"]["[id]"] = recipe.reqs[req_atom]
|
||||
|
||||
// Catalysts
|
||||
if(recipe.chem_catalysts.len)
|
||||
if(LAZYLEN(recipe.chem_catalysts))
|
||||
data["chem_catalysts"] = list()
|
||||
for(var/req_atom in recipe.chem_catalysts)
|
||||
for(var/req_atom, chem_amount in recipe.chem_catalysts)
|
||||
var/id = atoms.Find(req_atom)
|
||||
data["chem_catalysts"]["[id]"] = recipe.chem_catalysts[req_atom]
|
||||
data["chem_catalysts"]["[id]"] = chem_amount
|
||||
|
||||
// Reaction data
|
||||
if(ispath(recipe.reaction))
|
||||
|
||||
@@ -10,8 +10,8 @@
|
||||
category = CAT_EQUIPMENT
|
||||
|
||||
/datum/crafting_recipe/strobeshield/New()
|
||||
..()
|
||||
blacklist |= subtypesof(/obj/item/shield/riot)
|
||||
LAZYADD(blacklist, typecacheof(/obj/item/shield/riot, ignore_root_path = TRUE))
|
||||
return ..()
|
||||
|
||||
/datum/crafting_recipe/improvisedshield
|
||||
name = "Improvised Shield"
|
||||
@@ -33,8 +33,19 @@
|
||||
time = 4 SECONDS
|
||||
category = CAT_EQUIPMENT
|
||||
|
||||
/datum/crafting_recipe/radio_containing
|
||||
abstract_type = /datum/crafting_recipe/radio_containing
|
||||
/// Shared blacklist of all the radio types for anything that uses a radio in its construction, so we don't repeat it.
|
||||
var/static/list/radio_types_blacklist
|
||||
|
||||
/datum/crafting_recipe/radiogloves
|
||||
/datum/crafting_recipe/radio_containing/New()
|
||||
if(isnull(radio_types_blacklist))
|
||||
// because we got shit like /obj/item/radio/off ... WHY!?!
|
||||
radio_types_blacklist = typecacheof(list(/obj/item/radio/headset, /obj/item/radio/intercom))
|
||||
blacklist = radio_types_blacklist
|
||||
return ..()
|
||||
|
||||
/datum/crafting_recipe/radio_containing/radiogloves
|
||||
name = "Radio Gloves"
|
||||
result = /obj/item/clothing/gloves/radio
|
||||
time = 1.5 SECONDS
|
||||
@@ -46,11 +57,6 @@
|
||||
tool_behaviors = list(TOOL_WIRECUTTER)
|
||||
category = CAT_EQUIPMENT
|
||||
|
||||
/datum/crafting_recipe/radiogloves/New()
|
||||
..()
|
||||
blacklist |= typesof(/obj/item/radio/headset)
|
||||
blacklist |= typesof(/obj/item/radio/intercom)
|
||||
|
||||
/datum/crafting_recipe/wheelchair
|
||||
name = "Wheelchair"
|
||||
result = /obj/vehicle/ridden/wheelchair
|
||||
@@ -163,8 +169,8 @@
|
||||
category = CAT_EQUIPMENT
|
||||
|
||||
/datum/crafting_recipe/flashlight_eyes/New()
|
||||
. = ..()
|
||||
blacklist += typesof(/obj/item/flashlight/flare)
|
||||
LAZYADD(blacklist, typecacheof(/obj/item/flashlight/flare))
|
||||
return ..()
|
||||
|
||||
/datum/crafting_recipe/extendohand_r
|
||||
name = "Extendo-Hand (Right Arm)"
|
||||
@@ -248,21 +254,6 @@
|
||||
result = /obj/item/clothing/gloves/tackler/offbrand
|
||||
category = CAT_EQUIPMENT
|
||||
|
||||
/**
|
||||
* Recipe used for upgrading fake N-spect scanners to bananium HONK-spect scanners
|
||||
*/
|
||||
/datum/crafting_recipe/clown_scanner_upgrade
|
||||
name = "Bananium HONK-spect scanner"
|
||||
result = /obj/item/inspector/clown/bananium
|
||||
reqs = list(
|
||||
/obj/item/inspector/clown = 1,
|
||||
/obj/item/stack/sticky_tape = 3,
|
||||
/obj/item/stack/sheet/mineral/bananium = 5,
|
||||
) //the chainsaw of prank tools
|
||||
tool_paths = list(/obj/item/bikehorn)
|
||||
time = 40 SECONDS
|
||||
category = CAT_EQUIPMENT
|
||||
|
||||
/datum/crafting_recipe/rebar_quiver
|
||||
name = "Rebar Storage Quiver"
|
||||
result = /obj/item/storage/bag/rebar_quiver
|
||||
@@ -311,8 +302,8 @@
|
||||
tool_behaviors = list(TOOL_WELDER, TOOL_SCREWDRIVER, TOOL_WIRECUTTER)
|
||||
|
||||
/datum/crafting_recipe/morbid_surgical_toolset/New()
|
||||
..()
|
||||
blacklist |= subtypesof(/obj/item/organ/cyberimp/arm/toolkit/surgery)
|
||||
LAZYADD(blacklist, typecacheof(/obj/item/organ/cyberimp/arm/toolkit/surgery, ignore_root_path = TRUE))
|
||||
return ..()
|
||||
|
||||
/datum/crafting_recipe/surgical_toolset
|
||||
name = "Surgical Toolset Implant"
|
||||
|
||||
@@ -81,3 +81,40 @@
|
||||
tool_behaviors = list(TOOL_SCREWDRIVER)
|
||||
category = CAT_FURNITURE
|
||||
time = 10 SECONDS
|
||||
|
||||
/datum/crafting_recipe/lamp
|
||||
name = "Desk Lamp"
|
||||
result = /obj/item/flashlight/lamp
|
||||
reqs = list(
|
||||
/obj/item/flashlight = 1,
|
||||
/obj/item/stack/rods = 1,
|
||||
/obj/item/light/bulb = 1,
|
||||
)
|
||||
crafting_flags = CRAFT_SKIP_MATERIALS_PARITY
|
||||
tool_behaviors = list(TOOL_SCREWDRIVER)
|
||||
category = CAT_FURNITURE
|
||||
|
||||
/datum/crafting_recipe/lamp/New()
|
||||
. = ..()
|
||||
LAZYADD(blacklist, subtypesof(/obj/item/flashlight))
|
||||
|
||||
/datum/crafting_recipe/lamp/green
|
||||
name = "Green Desk Lamp"
|
||||
result = /obj/item/flashlight/lamp/green
|
||||
|
||||
/datum/crafting_recipe/banana_lamp
|
||||
name = "Banana Lamp"
|
||||
result = /obj/item/flashlight/lamp/bananalamp
|
||||
reqs = list(
|
||||
/obj/item/flashlight/lamp = 1,
|
||||
/obj/item/grown/bananapeel = 1,
|
||||
)
|
||||
blacklist = list(
|
||||
/obj/item/flashlight/lamp/bananalamp,
|
||||
/obj/item/flashlight/lamp/space_bubble,
|
||||
)
|
||||
requirements_mats_blacklist = list(/obj/item/grown/bananapeel)
|
||||
tool_behaviors = list(TOOL_SCREWDRIVER)
|
||||
category = CAT_FURNITURE
|
||||
|
||||
|
||||
|
||||
@@ -89,7 +89,6 @@
|
||||
time = 4 SECONDS
|
||||
category = CAT_WEAPON_MELEE
|
||||
|
||||
|
||||
/datum/crafting_recipe/balloon_mallet
|
||||
name = "Balloon Mallet"
|
||||
result = /obj/item/balloon_mallet
|
||||
@@ -255,3 +254,15 @@
|
||||
)
|
||||
time = 120 SECONDS
|
||||
category = CAT_WEAPON_MELEE
|
||||
|
||||
/datum/crafting_recipe/dragonator_spear_untreated
|
||||
name = "Untreated Giant-Killer Spear"
|
||||
result = /obj/item/spear/dragonator_untreated
|
||||
reqs = list(
|
||||
/obj/item/stack/sheet/plasteel = 15,
|
||||
/obj/item/stack/sheet/mineral/titanium = 5,
|
||||
/obj/item/knife = 7,
|
||||
)
|
||||
time = 5 SECONDS
|
||||
category = CAT_WEAPON_MELEE
|
||||
|
||||
|
||||
@@ -67,8 +67,8 @@
|
||||
category = CAT_WEAPON_RANGED
|
||||
|
||||
/datum/crafting_recipe/advancedegun/New()
|
||||
..()
|
||||
blacklist += subtypesof(/obj/item/gun/energy/e_gun)
|
||||
LAZYADD(blacklist, typecacheof(/obj/item/gun/energy/e_gun, ignore_root_path = TRUE))
|
||||
return ..()
|
||||
|
||||
/datum/crafting_recipe/tempgun
|
||||
name = "Temperature Gun"
|
||||
@@ -81,8 +81,8 @@
|
||||
category = CAT_WEAPON_RANGED
|
||||
|
||||
/datum/crafting_recipe/tempgun/New()
|
||||
..()
|
||||
blacklist += subtypesof(/obj/item/gun/energy/e_gun)
|
||||
LAZYADD(blacklist, typecacheof(/obj/item/gun/energy/e_gun, ignore_root_path = TRUE))
|
||||
return ..()
|
||||
|
||||
/datum/crafting_recipe/beam_rifle
|
||||
name = "Event Horizon Anti-Existential Beam Rifle"
|
||||
@@ -108,7 +108,18 @@
|
||||
time = 10 SECONDS
|
||||
category = CAT_WEAPON_RANGED
|
||||
|
||||
/datum/crafting_recipe/xraylaser
|
||||
/datum/crafting_recipe/laser
|
||||
abstract_type = /datum/crafting_recipe/laser
|
||||
/// We will use the same blacklist for every type here to avoid repeating lists
|
||||
var/static/list/laser_blacklist
|
||||
|
||||
/datum/crafting_recipe/laser/New()
|
||||
if(isnull(laser_blacklist))
|
||||
laser_blacklist = typecacheof(/obj/item/gun/energy/laser, ignore_root_path = TRUE)
|
||||
blacklist = laser_blacklist
|
||||
return ..()
|
||||
|
||||
/datum/crafting_recipe/laser/xraylaser
|
||||
name = "X-ray Laser Gun"
|
||||
result = /obj/item/gun/energy/laser/xray
|
||||
reqs = list(
|
||||
@@ -118,11 +129,7 @@
|
||||
time = 10 SECONDS
|
||||
category = CAT_WEAPON_RANGED
|
||||
|
||||
/datum/crafting_recipe/xraylaser/New()
|
||||
..()
|
||||
blacklist += subtypesof(/obj/item/gun/energy/laser)
|
||||
|
||||
/datum/crafting_recipe/hellgun
|
||||
/datum/crafting_recipe/laser/hellgun
|
||||
name = "Hellfire Laser Gun"
|
||||
result = /obj/item/gun/energy/laser/hellgun
|
||||
reqs = list(
|
||||
@@ -132,11 +139,7 @@
|
||||
time = 10 SECONDS
|
||||
category = CAT_WEAPON_RANGED
|
||||
|
||||
/datum/crafting_recipe/hellgun/New()
|
||||
..()
|
||||
blacklist += subtypesof(/obj/item/gun/energy/laser)
|
||||
|
||||
/datum/crafting_recipe/ioncarbine
|
||||
/datum/crafting_recipe/laser/ioncarbine
|
||||
name = "Ion Carbine"
|
||||
result = /obj/item/gun/energy/ionrifle/carbine
|
||||
reqs = list(
|
||||
@@ -146,10 +149,6 @@
|
||||
time = 10 SECONDS
|
||||
category = CAT_WEAPON_RANGED
|
||||
|
||||
/datum/crafting_recipe/ioncarbine/New()
|
||||
..()
|
||||
blacklist += subtypesof(/obj/item/gun/energy/laser)
|
||||
|
||||
/datum/crafting_recipe/teslacannon
|
||||
name = "Tesla Cannon"
|
||||
result = /obj/item/gun/energy/tesla_cannon
|
||||
@@ -241,8 +240,8 @@
|
||||
/obj/item/gun/ballistic/rifle/rebarxbow = 1,
|
||||
)
|
||||
blacklist = list(
|
||||
/obj/item/gun/ballistic/rifle/rebarxbow/forced,
|
||||
/obj/item/gun/ballistic/rifle/rebarxbow/syndie,
|
||||
/obj/item/gun/ballistic/rifle/rebarxbow/forced,
|
||||
/obj/item/gun/ballistic/rifle/rebarxbow/syndie,
|
||||
)
|
||||
tool_behaviors = list(TOOL_CROWBAR)
|
||||
time = 1 SECONDS
|
||||
@@ -289,8 +288,8 @@
|
||||
crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_MUST_BE_LEARNED
|
||||
|
||||
/datum/crafting_recipe/deagle_prime/New()
|
||||
..()
|
||||
blacklist += subtypesof(/obj/item/gun/ballistic/automatic/pistol)
|
||||
LAZYADD(blacklist, typecacheof(/obj/item/gun/ballistic/automatic/pistol, ignore_root_path = TRUE))
|
||||
return ..()
|
||||
|
||||
/datum/crafting_recipe/deagle_prime_mag
|
||||
name = "Regal Condor Magazine (10mm Reaper)"
|
||||
@@ -316,7 +315,7 @@
|
||||
/datum/crafting_recipe/pipe_organ_gun
|
||||
name = "Pipe Organ Gun"
|
||||
tool_behaviors = list(TOOL_WELDER, TOOL_SCREWDRIVER)
|
||||
result = /obj/structure/mounted_gun/pipe
|
||||
result = /obj/structure/mounted_gun/organ_gun
|
||||
reqs = list(
|
||||
/obj/item/pipe = 8,
|
||||
/obj/item/stack/sheet/mineral/wood = 15,
|
||||
@@ -329,6 +328,47 @@
|
||||
category = CAT_WEAPON_RANGED
|
||||
crafting_flags = CRAFT_CHECK_DENSITY
|
||||
|
||||
/datum/crafting_recipe/ratvarian_repeater
|
||||
name = "Emplaced Ratvarian Repeater"
|
||||
tool_behaviors = list(TOOL_SCREWDRIVER,TOOL_WRENCH)
|
||||
result = /obj/structure/mounted_gun/ratvarian_repeater
|
||||
reqs = list(
|
||||
/obj/item/stack/cable_coil = 15,
|
||||
/obj/item/stock_parts/micro_laser = 2,
|
||||
/obj/item/stock_parts/capacitor = 2,
|
||||
/obj/item/reagent_containers/cup/glass/drinkingglass = 2,
|
||||
/obj/item/stack/sheet/bronze = 5,
|
||||
/obj/item/stack/rods = 10,
|
||||
)
|
||||
time = 15 SECONDS
|
||||
category = CAT_WEAPON_RANGED
|
||||
crafting_flags = CRAFT_CHECK_DENSITY
|
||||
|
||||
/datum/crafting_recipe/detached_ratvarian_repeater
|
||||
name = "Iconoclast's Repeater"
|
||||
tool_behaviors = list(TOOL_WELDER)
|
||||
result = /obj/item/gun/energy/laser/musket/repeater
|
||||
structures = list(
|
||||
/obj/structure/mounted_gun/ratvarian_repeater = CRAFTING_STRUCTURE_CONSUME,
|
||||
)
|
||||
time = 10 SECONDS
|
||||
category = CAT_WEAPON_RANGED
|
||||
crafting_flags = CRAFT_CHECK_DENSITY
|
||||
|
||||
|
||||
/datum/crafting_recipe/large_ballista
|
||||
name = "Improvised Ballista"
|
||||
tool_behaviors = list(TOOL_WELDER,TOOL_SCREWDRIVER,TOOL_WRENCH,TOOL_WIRECUTTER)
|
||||
result = /obj/structure/mounted_gun/ballista
|
||||
reqs = list(
|
||||
/obj/item/stack/cable_coil = 15,
|
||||
/obj/item/stack/sheet/iron = 10,
|
||||
/obj/item/stack/rods = 10,
|
||||
)
|
||||
time = 8 SECONDS
|
||||
category = CAT_WEAPON_RANGED
|
||||
crafting_flags = CRAFT_CHECK_DENSITY
|
||||
|
||||
/datum/crafting_recipe/trash_cannon
|
||||
name = "Trash Cannon"
|
||||
tool_behaviors = list(TOOL_WELDER, TOOL_SCREWDRIVER)
|
||||
|
||||
@@ -117,6 +117,14 @@
|
||||
time = 4 SECONDS
|
||||
category = CAT_CLOTHING
|
||||
|
||||
/datum/crafting_recipe/fannypack
|
||||
name = "Fannypack"
|
||||
result = /obj/item/storage/belt/fannypack
|
||||
reqs = list(/obj/item/stack/sheet/cloth = 2,
|
||||
/obj/item/stack/sheet/leather = 1)
|
||||
time = 2 SECONDS
|
||||
category = CAT_CONTAINERS
|
||||
|
||||
/datum/crafting_recipe/grass_sheath
|
||||
name = "Grass Sabre Sheath"
|
||||
result = /obj/item/storage/belt/sheath/grass_sabre
|
||||
@@ -125,13 +133,31 @@
|
||||
time = 4 SECONDS
|
||||
category = CAT_CONTAINERS
|
||||
|
||||
/datum/crafting_recipe/fannypack
|
||||
name = "Fannypack"
|
||||
result = /obj/item/storage/belt/fannypack
|
||||
reqs = list(/obj/item/stack/sheet/cloth = 2,
|
||||
/obj/item/stack/sheet/leather = 1)
|
||||
time = 2 SECONDS
|
||||
category = CAT_CONTAINERS
|
||||
/datum/crafting_recipe/gunsheath
|
||||
name = "Gun Powered Sabre Sheath"
|
||||
result = /obj/item/storage/belt/sheath/sabre/gunpowered
|
||||
tool_behaviors = list(TOOL_SCREWDRIVER, TOOL_WRENCH)
|
||||
reqs = list(/datum/reagent/gunpowder = 15,
|
||||
/obj/item/storage/belt/sheath/sabre = 1,
|
||||
/obj/item/weaponcrafting/receiver = 1,
|
||||
/obj/item/stack/cable_coil = 5,
|
||||
)
|
||||
time = 15 SECONDS
|
||||
category = CAT_EQUIPMENT
|
||||
crafting_flags = parent_type::crafting_flags | CRAFT_SKIP_MATERIALS_PARITY
|
||||
|
||||
/datum/crafting_recipe/grass_gunsheath
|
||||
name = "Gun Powered Grass Sabre Sheath"
|
||||
result = /obj/item/storage/belt/sheath/grass_sabre/gunpowered
|
||||
tool_behaviors = list(TOOL_SCREWDRIVER, TOOL_WRENCH)
|
||||
reqs = list(/datum/reagent/gunpowder = 15,
|
||||
/obj/item/storage/belt/sheath/grass_sabre = 1,
|
||||
/obj/item/weaponcrafting/receiver = 1,
|
||||
/obj/item/stack/cable_coil = 5,
|
||||
)
|
||||
time = 15 SECONDS
|
||||
category = CAT_EQUIPMENT
|
||||
crafting_flags = parent_type::crafting_flags | CRAFT_SKIP_MATERIALS_PARITY
|
||||
|
||||
/datum/crafting_recipe/hudsunsec
|
||||
name = "Security HUDsunglasses"
|
||||
@@ -219,8 +245,8 @@
|
||||
|
||||
/datum/crafting_recipe/lizardboots
|
||||
name = "Lizard Skin Boots"
|
||||
result = /obj/effect/spawner/random/clothing/lizardboots
|
||||
reqs = list(/obj/item/stack/sheet/animalhide/lizard = 1, /obj/item/stack/sheet/leather = 1)
|
||||
result = /obj/item/clothing/shoes/cowboy/lizard
|
||||
reqs = list(/obj/item/stack/sheet/animalhide/carbon/lizard = 1, /obj/item/stack/sheet/leather = 1)
|
||||
time = 6 SECONDS
|
||||
category = CAT_CLOTHING
|
||||
|
||||
@@ -264,7 +290,7 @@
|
||||
name = "Lizard Cloche Hat"
|
||||
result = /obj/item/clothing/head/costume/lizard
|
||||
time = 1 SECONDS
|
||||
reqs = list(/obj/item/stack/sheet/animalhide/lizard = 1)
|
||||
reqs = list(/obj/item/stack/sheet/animalhide/carbon/lizard = 1)
|
||||
category = CAT_CLOTHING
|
||||
|
||||
/datum/crafting_recipe/kittyears
|
||||
@@ -421,12 +447,7 @@
|
||||
)
|
||||
reqs = list(/obj/item/stack/sheet/cloth = 4)
|
||||
category = CAT_CLOTHING
|
||||
|
||||
/datum/crafting_recipe/chaplain_hood/New()
|
||||
. = ..()
|
||||
//the resulting hoodie can be used to craft other hoodies.
|
||||
//recipe blacklists should be refactored to only affect components and not tools.
|
||||
blacklist -= result
|
||||
blacklist_result = NEVER_BLACKLIST_RESULT //the resulting hoodie can be used to craft other hoodies.
|
||||
|
||||
/datum/crafting_recipe/flower_garland
|
||||
name = "Flower Garland"
|
||||
|
||||
@@ -131,3 +131,15 @@
|
||||
)
|
||||
category = CAT_TOOLS
|
||||
|
||||
/datum/crafting_recipe/lantern
|
||||
name = "Lantern"
|
||||
result = /obj/item/flashlight/lantern
|
||||
reqs = list(
|
||||
/obj/item/flashlight/flare/candle = 1,
|
||||
/obj/item/stack/rods = 1,
|
||||
/obj/item/stack/sheet/glass = 1,
|
||||
)
|
||||
crafting_flags = CRAFT_SKIP_MATERIALS_PARITY
|
||||
tool_behaviors = list(TOOL_SCREWDRIVER)
|
||||
category = CAT_TOOLS
|
||||
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
/// Component to handle the behavior of a nullrod keeping track of cultists it has crit or killed, and converting the item into a cult weapon when sacrificed
|
||||
/datum/component/cult_kill_tracker
|
||||
/// Lazylist, tracks weakrefs()s to all cultists which have been crit or killed by this nullrod.
|
||||
var/list/cultists_slain
|
||||
/// Ref to the last mob hit with this rod.
|
||||
var/last_ref
|
||||
/// The stat of the target being hit with this rod, before actually performing damage calculations.
|
||||
var/last_stat = DEAD
|
||||
|
||||
/datum/component/cult_kill_tracker/Initialize(...)
|
||||
if(!istype(parent, /obj/item))
|
||||
return COMPONENT_INCOMPATIBLE
|
||||
|
||||
/datum/component/cult_kill_tracker/RegisterWithParent()
|
||||
. = ..()
|
||||
RegisterSignal(parent, COMSIG_ITEM_ATTACK_ZONE, PROC_REF(on_attack_zone))
|
||||
RegisterSignal(parent, COMSIG_ITEM_AFTERATTACK, PROC_REF(post_hit))
|
||||
RegisterSignal(parent, COMSIG_ATOM_EXAMINE, PROC_REF(on_examine))
|
||||
RegisterSignal(parent, COMSIG_ITEM_CULT_SACRIFICE, PROC_REF(on_sacrificed))
|
||||
if(isgun(parent))
|
||||
RegisterSignal(parent, COMSIG_PROJECTILE_ON_HIT, PROC_REF(on_projectile_hit))
|
||||
RegisterSignal(parent, COMSIG_PROJECTILE_POST_HIT_LIVING, PROC_REF(post_hit))
|
||||
|
||||
/datum/component/cult_kill_tracker/UnregisterFromParent()
|
||||
. = ..()
|
||||
UnregisterSignal(parent, list(COMSIG_ITEM_ATTACK_ZONE, COMSIG_ITEM_AFTERATTACK, COMSIG_ATOM_EXAMINE, COMSIG_ITEM_CULT_SACRIFICE, COMSIG_PROJECTILE_ON_HIT, COMSIG_PROJECTILE_POST_HIT_LIVING))
|
||||
|
||||
/datum/component/cult_kill_tracker/proc/on_attack_zone(obj/item/source, mob/living/target, mob/living/user)
|
||||
SIGNAL_HANDLER
|
||||
if(!user.mind?.holy_role)
|
||||
return
|
||||
if(!IS_CULTIST(target) || istype(target, /mob/living/carbon/human/cult_ghost))
|
||||
return
|
||||
last_ref = WEAKREF(target)
|
||||
last_stat = target.stat
|
||||
|
||||
/datum/component/cult_kill_tracker/proc/post_hit(source, mob/living/target)
|
||||
SIGNAL_HANDLER
|
||||
if(!last_ref)
|
||||
return
|
||||
var/mob/living/resolved_mob = locate(last_ref)
|
||||
//If they got deleted during the processing of the attack, they're probably fucking dead.
|
||||
if(!istype(resolved_mob) || (resolved_mob == target && resolved_mob.stat > last_stat))
|
||||
LAZYOR(cultists_slain, last_ref)
|
||||
last_ref = null
|
||||
last_stat = DEAD
|
||||
|
||||
/datum/component/cult_kill_tracker/proc/on_examine(obj/item/source, mob/viewer, list/examine_list)
|
||||
SIGNAL_HANDLER
|
||||
if(!IS_CULTIST(viewer) || !GET_ATOM_BLOOD_DNA_LENGTH(source))
|
||||
return
|
||||
|
||||
var/num_slain = LAZYLEN(cultists_slain)
|
||||
examine_list += span_cult_italic("It has the blood of [num_slain] fallen cultist[num_slain == 1 ? "" : "s"] on it. \
|
||||
<b>Offering</b> it to Nar'sie will transform it into a [num_slain >= 3 ? "powerful" : "standard"] cult weapon.")
|
||||
|
||||
/datum/component/cult_kill_tracker/proc/on_sacrificed(obj/item/source, obj/effect/rune/convert/rune)
|
||||
SIGNAL_HANDLER
|
||||
var/num_slain = LAZYLEN(cultists_slain)
|
||||
var/displayed_message = "[source] glows an unholy red and begins to transform..."
|
||||
if(num_slain && GET_ATOM_BLOOD_DNA_LENGTH(source))
|
||||
displayed_message += " The blood of [num_slain] fallen cultist[num_slain == 1 ? "":"s"] is absorbed into [source]!"
|
||||
|
||||
source.visible_message(span_cult_italic(displayed_message))
|
||||
switch(num_slain)
|
||||
if(0)
|
||||
rune.animate_convert_item(source, /obj/item/melee/cultblade/dagger)
|
||||
if(1)
|
||||
rune.animate_convert_item(source, /obj/item/melee/cultblade)
|
||||
else
|
||||
rune.animate_convert_item(source, /obj/item/melee/cultblade/halberd)
|
||||
return COMPONENT_SACRIFICE_SUCCESSFUL
|
||||
|
||||
/datum/component/cult_kill_tracker/proc/on_projectile_hit(obj/projectile/source, atom/movable/firer, atom/target)
|
||||
SIGNAL_HANDLER
|
||||
if(!isliving(firer) || !isliving(target))
|
||||
return
|
||||
var/mob/living/firer_mob = firer
|
||||
var/mob/living/target_mob = target
|
||||
if(!firer_mob.mind.holy_role)
|
||||
return
|
||||
if(!IS_CULTIST(target_mob) || istype(target_mob, /mob/living/carbon/human/cult_ghost))
|
||||
return
|
||||
last_ref = WEAKREF(target_mob)
|
||||
last_stat = target_mob.stat
|
||||
@@ -90,7 +90,7 @@
|
||||
|
||||
/// The requirements for the mob to be effected by the damage aura.
|
||||
/datum/component/damage_aura/proc/check_requirements(mob/living/target_mob)
|
||||
if(target_mob.stat == DEAD || faction_check(target_mob.faction, immune_factions))
|
||||
if(target_mob.stat == DEAD || target_mob.has_faction(immune_factions))
|
||||
return TRUE
|
||||
return FALSE
|
||||
|
||||
|
||||
@@ -137,12 +137,12 @@
|
||||
no_rewinds_message = "\"Rewind complete. You have arrived at: 10 seconds ago.\""
|
||||
|
||||
/datum/component/dejavu/timeline/rewind()
|
||||
playsound(get_turf(parent), 'sound/items/modsuit/rewinder.ogg')
|
||||
. = ..()
|
||||
playsound(get_turf(parent), 'sound/items/modsuit/rewinder.ogg', 50, TRUE)
|
||||
return ..()
|
||||
|
||||
/datum/component/dejavu/wizard
|
||||
rewind_message = "Your temporal ward activated, pulling you through spacetime!"
|
||||
|
||||
/datum/component/dejavu/wizard/rewind()
|
||||
playsound(get_turf(parent), 'sound/items/modsuit/rewinder.ogg')
|
||||
. = ..()
|
||||
playsound(get_turf(parent), 'sound/items/modsuit/rewinder.ogg', 50, TRUE)
|
||||
return ..()
|
||||
|
||||
@@ -23,9 +23,9 @@
|
||||
RegisterSignal(SSticker, COMSIG_TICKER_ROUND_STARTING, PROC_REF(comp_on_round_start))
|
||||
return
|
||||
|
||||
//if the round has already taken long enough, just leap right away.
|
||||
//if the round has already taken long enough, evolve in three seconds.
|
||||
if((world.time - SSticker.round_start_time) > evolve_mark)
|
||||
leap(silent = TRUE)
|
||||
addtimer(CALLBACK(src, PROC_REF(leap)), 3 SECONDS, TIMER_DELETE_ME)
|
||||
return
|
||||
|
||||
setup_timer()
|
||||
@@ -49,12 +49,11 @@
|
||||
var/mark = evolve_mark - sum
|
||||
timer_id = addtimer(CALLBACK(src, PROC_REF(leap), FALSE), mark, TIMER_STOPPABLE)
|
||||
|
||||
/datum/component/evolutionary_leap/proc/leap(silent)
|
||||
/datum/component/evolutionary_leap/proc/leap()
|
||||
var/mob/living/old_mob = parent
|
||||
if (old_mob.stat == DEAD)
|
||||
return
|
||||
var/mob/living/new_mob = evolve_path
|
||||
var/new_mob_name = initial(new_mob.name)
|
||||
if(!silent)
|
||||
old_mob.visible_message(span_warning("[old_mob] evolves into \a [new_mob_name]!"))
|
||||
old_mob.visible_message(span_warning("[old_mob] evolves into \a [new_mob_name]!"))
|
||||
old_mob.change_mob_type(evolve_path, old_mob.loc, new_name = new_mob_name, delete_old_mob = TRUE)
|
||||
|
||||
@@ -51,5 +51,5 @@
|
||||
return
|
||||
|
||||
to_chat(user, grant_message)
|
||||
user.faction |= faction_to_grant
|
||||
user.add_faction(faction_to_grant)
|
||||
used = TRUE
|
||||
|
||||
@@ -148,20 +148,22 @@
|
||||
/datum/fantasy_affix/shrapnel/apply(datum/component/fantasy/comp, newName)
|
||||
. = ..()
|
||||
// higher means more likely
|
||||
var/list/weighted_projectile_types = list(/obj/projectile/meteor = 1,
|
||||
/obj/projectile/energy/nuclear_particle = 1,
|
||||
/obj/projectile/beam/pulse = 1,
|
||||
/obj/projectile/bullet/honker = 15,
|
||||
/obj/projectile/temp = 15,
|
||||
/obj/projectile/ion = 15,
|
||||
/obj/projectile/magic/door = 15,
|
||||
/obj/projectile/magic/locker = 15,
|
||||
/obj/projectile/magic/fetch = 15,
|
||||
/obj/projectile/beam/emitter = 15,
|
||||
/obj/projectile/magic/flying = 15,
|
||||
/obj/projectile/bullet/incendiary/c9mm = 15,
|
||||
/obj/projectile/temp/hot = 15,
|
||||
/obj/projectile/beam/disabler = 15)
|
||||
var/list/weighted_projectile_types = list(
|
||||
/obj/projectile/beam/disabler = 15,
|
||||
/obj/projectile/beam/emitter = 15,
|
||||
/obj/projectile/bullet/honker = 15,
|
||||
/obj/projectile/beam/pulse = 1,
|
||||
/obj/projectile/bullet/incendiary/c9mm = 15,
|
||||
/obj/projectile/energy/nuclear_particle = 1,
|
||||
/obj/projectile/ion = 15,
|
||||
/obj/projectile/magic/door = 15,
|
||||
/obj/projectile/magic/fetch = 15,
|
||||
/obj/projectile/magic/flying = 15,
|
||||
/obj/projectile/magic/locker = 15,
|
||||
/obj/projectile/meteor = 1,
|
||||
/obj/projectile/temp = 15,
|
||||
/obj/projectile/temp/hot = 15,
|
||||
)
|
||||
|
||||
var/obj/projectile/picked_projectiletype = pick_weight(weighted_projectile_types)
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
/datum/terror_handler/heart_problems
|
||||
handler_type = TERROR_HANDLER_EFFECT
|
||||
default = TRUE
|
||||
COOLDOWN_DECLARE(effect_cd)
|
||||
COOLDOWN_DECLARE(message_cd)
|
||||
|
||||
/datum/terror_handler/heart_problems/tick(seconds_per_tick, terror_buildup)
|
||||
. = ..()
|
||||
@@ -67,6 +67,9 @@
|
||||
|
||||
if (terror_buildup < TERROR_BUILDUP_HEART_ATTACK || !prob(15))
|
||||
owner.adjust_oxy_loss(8)
|
||||
if (!COOLDOWN_FINISHED(src, message_cd))
|
||||
return
|
||||
COOLDOWN_START(src, message_cd, TERROR_MESSAGE_CD)
|
||||
if (terror_buildup < TERROR_BUILDUP_FEAR)
|
||||
to_chat(owner, span_warning("Your heart skips a beat."))
|
||||
else
|
||||
@@ -167,4 +170,48 @@
|
||||
deltimer(panic_end_timer)
|
||||
panic_end_timer = null
|
||||
|
||||
/datum/terror_handler/startle
|
||||
handler_type = TERROR_HANDLER_EFFECT
|
||||
COOLDOWN_DECLARE(startle_cd)
|
||||
|
||||
/datum/terror_handler/startle/tick(seconds_per_tick, terror_buildup)
|
||||
. = ..()
|
||||
if (owner.stat >= UNCONSCIOUS || !COOLDOWN_FINISHED(src, startle_cd))
|
||||
return
|
||||
|
||||
if (terror_buildup < TERROR_BUILDUP_FEAR || terror_buildup - component.last_tick_buildup < TERROR_STARTLE_MINIMUM_DIFFERENCE)
|
||||
return
|
||||
|
||||
// The more scared we are, and the more fear we acquired at once last tick, the higher the probability of us being startled is
|
||||
// Not SPT_PROB or FEAR_SCALING because this is only triggered during ticks when we actually acquire fear
|
||||
if (!prob(15 * (terror_buildup - component.last_tick_buildup) / TERROR_STARTLE_MINIMUM_DIFFERENCE * terror_buildup / TERROR_BUILDUP_FEAR))
|
||||
return
|
||||
|
||||
COOLDOWN_START(src, startle_cd, TERROR_STARTLE_COOLDOWN)
|
||||
switch (rand(1, 3))
|
||||
if (1)
|
||||
to_chat(owner, span_warning("You are startled!"))
|
||||
owner.emote("jump")
|
||||
owner.Immobilize(0.1 SECONDS * (terror_buildup / TERROR_BUILDUP_FEAR))
|
||||
|
||||
if (2)
|
||||
owner.emote("scream")
|
||||
owner.say("AAAAH!!", forced = "phobia")
|
||||
if (!prob(15 * (terror_buildup / TERROR_BUILDUP_FEAR)))
|
||||
return
|
||||
var/held_item = owner.get_active_held_item()
|
||||
if (owner.dropItemToGround(held_item))
|
||||
owner.visible_message(
|
||||
span_danger("[owner.name] drops \the [held_item]!"),
|
||||
span_warning("You drop \the [held_item]!"), null, COMBAT_MESSAGE_RANGE)
|
||||
|
||||
if (3)
|
||||
to_chat(owner, span_warning("You lose your balance!"))
|
||||
owner.adjust_staggered_up_to(2 SECONDS * (terror_buildup / TERROR_BUILDUP_FEAR), 20 SECONDS)
|
||||
owner.add_movespeed_modifier(/datum/movespeed_modifier/status_effect/spooked)
|
||||
addtimer(CALLBACK(src, PROC_REF(speed_up)), 3 SECONDS, TIMER_STOPPABLE | TIMER_DELETE_ME)
|
||||
|
||||
/datum/terror_handler/startle/proc/speed_up()
|
||||
owner.remove_movespeed_modifier(/datum/movespeed_modifier/status_effect/spooked)
|
||||
|
||||
#undef FEAR_SCALING
|
||||
|
||||
@@ -73,7 +73,7 @@
|
||||
|
||||
/datum/component/fearful/proc/add_handler(handler_type, source)
|
||||
for (var/datum/terror_handler/existing as anything in terror_handlers)
|
||||
if (existing.type == handler_type)
|
||||
if (existing.type == handler_type && !existing.bespoke)
|
||||
terror_handlers[existing] += source
|
||||
return
|
||||
|
||||
@@ -83,6 +83,7 @@
|
||||
if (!overriden_handlers[override_type])
|
||||
overriden_handlers[override_type] = list()
|
||||
overriden_handlers[override_type][handler_type] = TRUE
|
||||
return handler
|
||||
|
||||
/datum/component/fearful/proc/get_fear_multiplier()
|
||||
var/multiplier = 1
|
||||
@@ -168,11 +169,12 @@
|
||||
)
|
||||
return COMPONENT_BLOCK_MISC_HELP
|
||||
|
||||
for (var/datum/brain_trauma/mild/phobia/phobia in source.get_traumas())
|
||||
if (!phobia.is_scary_mob(hugger))
|
||||
continue
|
||||
var/hug_buildup = 0
|
||||
for (var/datum/terror_handler/handler as anything in terror_handlers)
|
||||
hug_buildup += handler.on_hug(hugger)
|
||||
|
||||
terror_buildup += HUG_TERROR_AMOUNT
|
||||
if (hug_buildup > 0)
|
||||
terror_buildup += hug_buildup
|
||||
source.visible_message(
|
||||
span_warning("[source] recoils in fear as [hugger] attempts to hug [source.p_them()]!"),
|
||||
span_boldwarning("You recoil in terror as [hugger] attempts to hug you!"),
|
||||
|
||||
+12
-3
@@ -71,7 +71,7 @@
|
||||
. = ..()
|
||||
owner.clear_mood_event("nyctophobia")
|
||||
|
||||
// Nightmare spell version with quicker buildup that shadows the quirk one, also handles removing the status upon reaching 0 terror
|
||||
/// Nightmare spell version with quicker buildup that shadows the quirk one, also handles removing the status upon reaching 0 terror
|
||||
/datum/terror_handler/simple_source/nyctophobia/terrified
|
||||
// Takes about 30 seconds to reach maximum if you include 100 from casting the spell
|
||||
buildup_per_second = 15
|
||||
@@ -87,6 +87,7 @@
|
||||
/// Makes the owner afraid of being stuck in closets, crates, mechs, etc
|
||||
/datum/terror_handler/simple_source/claustrophobia
|
||||
buildup_per_second = 15
|
||||
COOLDOWN_DECLARE(message_cd)
|
||||
|
||||
/datum/terror_handler/simple_source/claustrophobia/Destroy(force)
|
||||
owner.clear_mood_event("claustrophobia")
|
||||
@@ -100,8 +101,9 @@
|
||||
if (isturf(owner.loc))
|
||||
return FALSE
|
||||
|
||||
if (SPT_PROB(15, seconds_per_tick))
|
||||
if (COOLDOWN_FINISHED(src, message_cd) && SPT_PROB(15, seconds_per_tick))
|
||||
to_chat(owner, span_warning("You feel trapped! Must escape... can't breathe..."))
|
||||
COOLDOWN_START(src, message_cd, TERROR_MESSAGE_CD)
|
||||
|
||||
return TRUE
|
||||
|
||||
@@ -116,6 +118,7 @@
|
||||
/// Makes the owner afraid of certain jolly figures
|
||||
/datum/terror_handler/simple_source/clausophobia
|
||||
buildup_per_second = 20
|
||||
COOLDOWN_DECLARE(message_cd)
|
||||
|
||||
/datum/terror_handler/simple_source/clausophobia/check_condition(seconds_per_tick, terror_buildup)
|
||||
. = ..()
|
||||
@@ -142,14 +145,16 @@
|
||||
if (!certified_jolly)
|
||||
return FALSE
|
||||
|
||||
if (SPT_PROB(15, seconds_per_tick))
|
||||
if (COOLDOWN_FINISHED(src, message_cd) && SPT_PROB(15, seconds_per_tick))
|
||||
to_chat(owner, span_warning("Santa Claus is here! I gotta get out of here!"))
|
||||
COOLDOWN_START(src, message_cd, TERROR_MESSAGE_CD)
|
||||
|
||||
return TRUE
|
||||
|
||||
/// Makes the owner afraid of being alone
|
||||
/datum/terror_handler/simple_source/monophobia
|
||||
buildup_per_second = 2.5 // Pretty low, ~4 minutes to reach passive cap
|
||||
COOLDOWN_DECLARE(message_cd)
|
||||
|
||||
/datum/terror_handler/simple_source/monophobia/check_condition(seconds_per_tick, terror_buildup)
|
||||
. = ..()
|
||||
@@ -167,4 +172,8 @@
|
||||
if (istype(friend, /mob/living/basic/pet) || friend.ckey)
|
||||
return FALSE
|
||||
|
||||
if (COOLDOWN_FINISHED(src, message_cd) && SPT_PROB(10, seconds_per_tick))
|
||||
to_chat(owner, span_warning("You feel terribly lonely..."))
|
||||
COOLDOWN_START(src, message_cd, TERROR_MESSAGE_CD)
|
||||
|
||||
return TRUE
|
||||
@@ -0,0 +1,170 @@
|
||||
/// Complex terror source that increases buildup whenever the owner hears, sees, or tries to say something they're afraid of
|
||||
/datum/terror_handler/phobia_source
|
||||
handler_type = TERROR_HANDLER_SOURCE
|
||||
bespoke = TRUE
|
||||
/// Last time we got scared shitless for passive increase in fear
|
||||
/// Regex for words that set the phobia off
|
||||
var/regex/trigger_regex
|
||||
// Instead of cycling every atom, only cycle the relevant types
|
||||
var/list/trigger_mobs
|
||||
/// Includes mob equipment
|
||||
var/list/trigger_objs
|
||||
var/list/trigger_turfs
|
||||
var/list/trigger_species
|
||||
/// What mood event to apply when we see the thing & freak out.
|
||||
var/mood_event_type = /datum/mood_event/phobia
|
||||
/// Cooldown for proximity checks so we don't spam a range 7 view every two seconds.
|
||||
COOLDOWN_DECLARE(check_cooldown)
|
||||
/// Cooldown for the actual scare effect so chat spam won't instantly send us spiraling
|
||||
COOLDOWN_DECLARE(scare_cooldown)
|
||||
|
||||
/datum/terror_handler/phobia_source/New(mob/living/new_owner, datum/component/fearful/new_component)
|
||||
. = ..()
|
||||
RegisterSignal(new_owner, COMSIG_MOB_SAY, PROC_REF(handle_speech))
|
||||
RegisterSignal(new_owner, COMSIG_MOVABLE_HEAR, PROC_REF(handle_hearing))
|
||||
|
||||
/datum/terror_handler/phobia_source/Destroy(force)
|
||||
UnregisterSignal(owner, list(COMSIG_MOB_SAY, COMSIG_MOVABLE_HEAR))
|
||||
return ..()
|
||||
|
||||
/datum/terror_handler/phobia_source/proc/can_trigger()
|
||||
return !HAS_TRAIT(owner, TRAIT_FEARLESS) && !HAS_TRAIT(owner, TRAIT_MIND_TEMPORARILY_GONE) && owner.stat < UNCONSCIOUS
|
||||
|
||||
/datum/terror_handler/phobia_source/tick(seconds_per_tick, terror_buildup)
|
||||
. = ..()
|
||||
if (!can_trigger())
|
||||
return
|
||||
|
||||
// If we're still scared from the last trigger, keep adding just a tiiiiny buildup to prevent it from fading away
|
||||
if (!COOLDOWN_FINISHED(src, scare_cooldown))
|
||||
return 0.01
|
||||
|
||||
if(!COOLDOWN_FINISHED(src, check_cooldown) || owner.is_blind())
|
||||
return
|
||||
|
||||
COOLDOWN_START(src, check_cooldown, PHOBIA_CHECK_DELAY)
|
||||
if(LAZYLEN(trigger_objs))
|
||||
for (var/obj/seen_thing in view(owner.client?.view || world.view, owner))
|
||||
if(is_scary_item(seen_thing))
|
||||
return freak_out(seen_thing)
|
||||
|
||||
if(LAZYLEN(trigger_turfs))
|
||||
for(var/turf/checked in view(owner.client?.view || world.view, owner))
|
||||
if(is_type_in_typecache(checked, trigger_turfs))
|
||||
return freak_out(checked)
|
||||
|
||||
if(LAZYLEN(trigger_mobs) || LAZYLEN(trigger_species) || LAZYLEN(trigger_objs))
|
||||
for(var/mob/living/checked in view(owner.client?.view || world.view, owner))
|
||||
if (checked != owner && is_scary_mob(checked))
|
||||
return freak_out(checked)
|
||||
|
||||
/// Returns true if this item should be scary to us
|
||||
/datum/terror_handler/phobia_source/proc/is_scary_item(obj/checked)
|
||||
if (QDELETED(checked) || !is_type_in_typecache(checked, trigger_objs) || checked.invisibility > owner.see_invisible)
|
||||
return FALSE
|
||||
|
||||
if (!isitem(checked) || !ismob(checked.loc) || HAS_TRAIT(checked.loc, TRAIT_UNKNOWN_APPEARANCE))
|
||||
return TRUE
|
||||
|
||||
var/obj/item/checked_item = checked
|
||||
return !HAS_TRAIT(checked_item, TRAIT_EXAMINE_SKIP)
|
||||
|
||||
/datum/terror_handler/phobia_source/proc/is_scary_mob(mob/living/checked)
|
||||
if (checked.invisibility > owner.see_invisible || checked.alpha == 0)
|
||||
return FALSE
|
||||
|
||||
if (is_type_in_typecache(checked, trigger_mobs))
|
||||
return TRUE
|
||||
|
||||
if (!ishuman(checked))
|
||||
return FALSE
|
||||
|
||||
var/mob/living/carbon/human/as_human = checked
|
||||
if (LAZYLEN(trigger_species))
|
||||
// Can't be racist(?) if you can't see their face
|
||||
if (is_type_in_typecache(as_human.dna?.species, trigger_species) && !as_human.is_face_obscured())
|
||||
return TRUE
|
||||
|
||||
if (!LAZYLEN(trigger_objs))
|
||||
return FALSE
|
||||
|
||||
for (var/obj/item/equipped as anything in as_human.get_visible_items())
|
||||
if (is_scary_item(equipped))
|
||||
return TRUE
|
||||
|
||||
return FALSE
|
||||
|
||||
/datum/terror_handler/phobia_source/proc/handle_hearing(datum/source, list/hearing_args)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
if (!can_trigger() || !COOLDOWN_FINISHED(src, scare_cooldown))
|
||||
return
|
||||
|
||||
// Words can't trigger you if you can't hear them *taps head*
|
||||
if(HAS_TRAIT(owner, TRAIT_DEAF) || owner == hearing_args[HEARING_SPEAKER] || !owner.has_language(hearing_args[HEARING_LANGUAGE]))
|
||||
return
|
||||
|
||||
if(trigger_regex.Find(hearing_args[HEARING_RAW_MESSAGE]))
|
||||
// To react AFTER the chat message
|
||||
addtimer(CALLBACK(src, PROC_REF(freak_out), null, trigger_regex.group[2]), 1 SECONDS)
|
||||
hearing_args[HEARING_RAW_MESSAGE] = trigger_regex.Replace(hearing_args[HEARING_RAW_MESSAGE], "[span_phobia("$2")]$3")
|
||||
|
||||
/datum/terror_handler/phobia_source/proc/handle_speech(datum/source, list/speech_args)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
if (!can_trigger())
|
||||
return
|
||||
|
||||
if (trigger_regex.Find(speech_args[SPEECH_MESSAGE]) == 0)
|
||||
return
|
||||
|
||||
var/stutter = prob(50)
|
||||
var/whisper = prob(30)
|
||||
|
||||
if (!stutter && !whisper)
|
||||
return
|
||||
|
||||
if (whisper)
|
||||
speech_args[SPEECH_SPANS] |= SPAN_SMALL_VOICE
|
||||
if (stutter)
|
||||
owner.set_stutter_if_lower(4 SECONDS)
|
||||
to_chat(owner, span_warning("You struggle to say the word \"[span_phobia("[trigger_regex.group[2]]")]\"!"))
|
||||
|
||||
/datum/terror_handler/phobia_source/proc/freak_out(reason)
|
||||
COOLDOWN_START(src, scare_cooldown, 12 SECONDS)
|
||||
var/message = pick("spooks you to the bone", "shakes you up", "terrifies you", "sends you into a panic", "sends chills down your spine")
|
||||
if(istext(reason))
|
||||
to_chat(owner, span_bolddanger("Hearing [span_phobia(reason)] [message]!"))
|
||||
owner.add_mood_event("phobia_minor", /datum/mood_event/startled)
|
||||
// Because this is called from a signal and not the main process, we need to add the buildup by hand
|
||||
if (component.terror_buildup < TERROR_BUILDUP_PASSIVE_MAXIMUM)
|
||||
component.terror_buildup = min(component.terror_buildup + PHOBIA_WORD_TERROR_BUILDUP, TERROR_BUILDUP_PASSIVE_MAXIMUM)
|
||||
return
|
||||
|
||||
if(mood_event_type)
|
||||
owner.add_mood_event("phobia", mood_event_type)
|
||||
|
||||
if(isatom(reason))
|
||||
var/atom/as_atom = reason
|
||||
to_chat(owner, span_bolddanger("Seeing [span_phobia("[as_atom.name]")] [message]!"))
|
||||
else
|
||||
to_chat(owner, span_bolddanger("Something [message]!"))
|
||||
return PHOBIA_FREAKOUT_TERROR_BUILDUP
|
||||
|
||||
/datum/terror_handler/phobia_source/on_hug(mob/living/hugger)
|
||||
if (is_scary_mob(hugger))
|
||||
return HUG_TERROR_AMOUNT
|
||||
return 0
|
||||
|
||||
/// Snowflake handler for hemophobia which triggers on bloodied items and mobs
|
||||
/datum/terror_handler/phobia_source/blood
|
||||
|
||||
/datum/terror_handler/phobia_source/blood/is_scary_item(obj/checked)
|
||||
if (GET_ATOM_BLOOD_DNA_LENGTH(checked))
|
||||
return TRUE
|
||||
return ..()
|
||||
|
||||
/datum/terror_handler/phobia_source/blood/is_scary_mob(mob/living/checked)
|
||||
if (GET_ATOM_BLOOD_DNA_LENGTH(checked))
|
||||
return TRUE
|
||||
return ..()
|
||||
@@ -11,6 +11,8 @@
|
||||
var/default = FALSE
|
||||
/// Other effects which should be disabled while this one is running
|
||||
var/list/overrides
|
||||
/// Is this handler bespoke, and always requires a new instance upon adding even when one is already present?
|
||||
var/bespoke = FALSE
|
||||
|
||||
/datum/terror_handler/New(mob/living/new_owner, datum/component/fearful/new_component)
|
||||
. = ..()
|
||||
@@ -25,3 +27,7 @@
|
||||
/// Single tick of terror handler, returns adjustment to terror buildup
|
||||
/datum/terror_handler/proc/tick(seconds_per_tick, terror_buildup)
|
||||
return 0
|
||||
|
||||
/// Additional effects when we're hugged by a mob, returns fear adjustment per hug. Should only be positive.
|
||||
/datum/terror_handler/proc/on_hug(mob/living/hugger)
|
||||
return 0
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// A thing you can fish in
|
||||
/datum/component/fishing_spot
|
||||
dupe_mode = COMPONENT_DUPE_UNIQUE
|
||||
/// Defines the probabilities and fish availibilty
|
||||
var/datum/fish_source/fish_source
|
||||
|
||||
@@ -26,7 +27,6 @@
|
||||
REMOVE_TRAIT(parent, TRAIT_FISHING_SPOT, REF(src))
|
||||
fish_source.on_fishing_spot_del(src)
|
||||
fish_source = null
|
||||
REMOVE_TRAIT(parent, TRAIT_FISHING_SPOT, REF(src))
|
||||
return ..()
|
||||
|
||||
/datum/component/fishing_spot/proc/handle_cast(datum/source, obj/item/fishing_rod/rod, mob/user)
|
||||
|
||||
@@ -568,8 +568,10 @@ Behavior that's still missing from this component that original food items had t
|
||||
qdel(food)
|
||||
return FALSE
|
||||
|
||||
if(SEND_SIGNAL(eater, COMSIG_CARBON_ATTEMPT_EAT, food) & COMSIG_CARBON_BLOCK_EAT)
|
||||
return
|
||||
if(SEND_SIGNAL(eater, COMSIG_CARBON_ATTEMPT_EAT, food) & BLOCK_EAT_ATTEMPT)
|
||||
return FALSE
|
||||
if(SEND_SIGNAL(food, COMSIG_FOOD_ATTEMPT_EAT, eater, feeder) & BLOCK_EAT_ATTEMPT)
|
||||
return FALSE
|
||||
return TRUE
|
||||
|
||||
///Applies food buffs according to the crafting complexity
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
/// Allows mobs with this component to have all of their limbs operated on without needing drapes
|
||||
/datum/component/free_operation
|
||||
dupe_mode = COMPONENT_DUPE_SOURCES
|
||||
|
||||
/datum/component/free_operation/Initialize(check)
|
||||
if (!iscarbon(parent))
|
||||
return COMPONENT_INCOMPATIBLE
|
||||
ADD_TRAIT(parent, TRAIT_READY_TO_OPERATE, REF(src))
|
||||
var/mob/living/carbon/owner = parent
|
||||
for (var/obj/item/bodypart/limb as anything in owner.bodyparts)
|
||||
ADD_TRAIT(limb, TRAIT_READY_TO_OPERATE, REF(src))
|
||||
|
||||
/datum/component/free_operation/Destroy(force)
|
||||
REMOVE_TRAIT(parent, TRAIT_READY_TO_OPERATE, REF(src))
|
||||
var/mob/living/carbon/owner = parent
|
||||
for (var/obj/item/bodypart/limb as anything in owner.bodyparts)
|
||||
REMOVE_TRAIT(limb, TRAIT_READY_TO_OPERATE, REF(src))
|
||||
return ..()
|
||||
|
||||
/datum/component/free_operation/RegisterWithParent()
|
||||
RegisterSignal(parent, COMSIG_CARBON_ATTACH_LIMB, PROC_REF(flag_limb))
|
||||
RegisterSignal(parent, COMSIG_CARBON_REMOVE_LIMB, PROC_REF(unflag_limb))
|
||||
|
||||
/datum/component/free_operation/UnregisterFromParent()
|
||||
UnregisterSignal(parent, list(COMSIG_CARBON_ATTACH_LIMB, COMSIG_CARBON_REMOVE_LIMB))
|
||||
|
||||
/datum/component/free_operation/proc/flag_limb(mob/living/carbon/source, obj/item/bodypart/limb)
|
||||
SIGNAL_HANDLER
|
||||
ADD_TRAIT(limb, TRAIT_READY_TO_OPERATE, REF(src))
|
||||
|
||||
/datum/component/free_operation/proc/unflag_limb(mob/living/carbon/source, obj/item/bodypart/limb)
|
||||
SIGNAL_HANDLER
|
||||
REMOVE_TRAIT(limb, TRAIT_READY_TO_OPERATE, REF(src))
|
||||
@@ -209,8 +209,7 @@
|
||||
stop_autofiring()
|
||||
return COMPONENT_CLIENT_MOUSEUP_INTERCEPT
|
||||
|
||||
|
||||
/datum/component/automatic_fire/proc/stop_autofiring(datum/source, atom/object, turf/location, control, params)
|
||||
/datum/component/automatic_fire/proc/stop_autofiring(mob/living/source, obj/item/swapped_to, obj/item/swapped_from)
|
||||
SIGNAL_HANDLER
|
||||
if(autofire_stat != AUTOFIRE_STAT_FIRING)
|
||||
return
|
||||
|
||||
@@ -23,11 +23,21 @@ GLOBAL_LIST_EMPTY(GPS_list)
|
||||
/datum/component/gps/kheiral_cuffs/Initialize(_gpstag = "COM0")
|
||||
. = ..()
|
||||
RegisterSignal(parent, COMSIG_ITEM_DROPPED, PROC_REF(deactivate_kheiral_cuffs))
|
||||
RegisterSignal(parent, COMSIG_MOVABLE_Z_CHANGED, PROC_REF(deactivate_if_station))
|
||||
|
||||
/datum/component/gps/kheiral_cuffs/proc/deactivate_kheiral_cuffs(datum/source)
|
||||
SIGNAL_HANDLER
|
||||
qdel(src)
|
||||
|
||||
/datum/component/gps/kheiral_cuffs/proc/deactivate_if_station(atom/movable/moved_atom, turf/old_turf, turf/new_turf)
|
||||
SIGNAL_HANDLER
|
||||
if(!isturf(new_turf))
|
||||
return
|
||||
|
||||
if(is_station_level(new_turf.z))
|
||||
qdel(src)
|
||||
return
|
||||
|
||||
///GPS component subtype. Only gps/item's can be used to open the UI.
|
||||
/datum/component/gps/item
|
||||
var/updating = TRUE //Automatic updating of GPS list. Can be set to manual by user.
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
heal_when_sinked = TRUE,
|
||||
health_per_second = 1,
|
||||
outline_colour = COLOR_PALE_GREEN,
|
||||
damage_res_sinked = list(BRUTE = 1, BURN = 1, TOX = 1, STAMINA = 0, OXY = 1))
|
||||
damage_res_sinked = string_assoc_list(list(BRUTE = 1, BURN = 1, TOX = 1, STAMINA = 0, OXY = 1)))
|
||||
|
||||
if (!isbasicmob(parent))
|
||||
return COMPONENT_INCOMPATIBLE
|
||||
@@ -112,7 +112,7 @@
|
||||
if(sinked && heal_when_sinked)
|
||||
stop_regenerating()
|
||||
living_target.icon_state = target_icon_state
|
||||
living_target.damage_coeff = list(BRUTE = 1, BURN = 1, TOX = 1, STAMINA = 0, OXY = 1)
|
||||
living_target.damage_coeff = string_assoc_list(list(BRUTE = 1, BURN = 1, TOX = 1, STAMINA = 0, OXY = 1))
|
||||
living_target.set_density(TRUE)
|
||||
sinked = FALSE
|
||||
|
||||
|
||||
@@ -19,6 +19,8 @@
|
||||
/datum/component/holographic_nature/RegisterWithParent()
|
||||
AddComponent(/datum/component/connect_loc_behalf, parent, loc_connections)
|
||||
if(isliving(parent))
|
||||
RegisterSignal(parent, COMSIG_LIVING_MOB_BUMP, PROC_REF(on_bumped))
|
||||
RegisterSignal(parent, COMSIG_LIVING_MOB_BUMPED, PROC_REF(on_bumped))
|
||||
RegisterSignal(parent, COMSIG_MOB_APPLY_DAMAGE, PROC_REF(on_mob_damaged))
|
||||
return
|
||||
|
||||
@@ -26,6 +28,7 @@
|
||||
if(isobj(atom_parent) && atom_parent.uses_integrity)
|
||||
RegisterSignal(parent, COMSIG_ATOM_TAKE_DAMAGE, PROC_REF(on_object_damaged))
|
||||
|
||||
|
||||
/datum/component/holographic_nature/proc/on_mob_damaged(mob/living/source, damage_amount, damagetype, def_zone, blocked, wound_bonus, exposed_wound_bonus, sharpness, attack_direction, attacking_item)
|
||||
SIGNAL_HANDLER
|
||||
if(damagetype == BURN || damagetype == BRUTE)
|
||||
@@ -44,6 +47,10 @@
|
||||
if(isprojectile(thing) || thing.density)
|
||||
apply_effects()
|
||||
|
||||
/datum/component/holographic_nature/proc/on_bumped(atom/movable/source, mob/living/crossing_mob)
|
||||
SIGNAL_HANDLER
|
||||
apply_effects()
|
||||
|
||||
/datum/component/holographic_nature/proc/apply_effects()
|
||||
if(!COOLDOWN_FINISHED(src, glitch_cooldown))
|
||||
return
|
||||
|
||||
@@ -94,9 +94,8 @@
|
||||
/datum/component/jetpack/proc/setup_trail(mob/user)
|
||||
if(trail)
|
||||
QDEL_NULL(trail)
|
||||
trail = new effect_type
|
||||
trail = new effect_type(user)
|
||||
trail.auto_process = FALSE
|
||||
trail.set_up(user)
|
||||
trail.start()
|
||||
|
||||
/datum/component/jetpack/proc/activate(datum/source, mob/new_user)
|
||||
|
||||
@@ -1,801 +0,0 @@
|
||||
/*!
|
||||
This datum should be used for handling mineral contents of machines and whatever else is supposed to hold minerals and make use of them.
|
||||
|
||||
Variables:
|
||||
amount - raw amount of the mineral this container is holding, calculated by the defined value SHEET_MATERIAL_AMOUNT=SHEET_MATERIAL_AMOUNT.
|
||||
max_amount - max raw amount of mineral this container can hold.
|
||||
sheet_type - type of the mineral sheet the container handles, used for output.
|
||||
parent - object that this container is being used by, used for output.
|
||||
MAX_STACK_SIZE - size of a stack of mineral sheets. Constant.
|
||||
*/
|
||||
|
||||
//The full item was consumed
|
||||
#define MATERIAL_INSERT_ITEM_SUCCESS 1
|
||||
|
||||
/datum/component/material_container
|
||||
/// The maximum amount of materials this material container can contain
|
||||
var/max_amount
|
||||
/// Map of material ref -> amount
|
||||
var/list/materials //Map of key = material ref | Value = amount
|
||||
/// The list of materials that this material container can accept
|
||||
var/list/allowed_materials
|
||||
/// The typecache of things that this material container can accept
|
||||
var/list/allowed_item_typecache
|
||||
/// Whether or not this material container allows specific amounts from sheets to be inserted
|
||||
var/precise_insertion = FALSE
|
||||
/// The material container flags. See __DEFINES/construction/materials.dm.
|
||||
var/mat_container_flags
|
||||
/// Signals that are registered with this contained
|
||||
var/list/registered_signals
|
||||
|
||||
/// Sets up the proper signals and fills the list of materials with the appropriate references.
|
||||
/datum/component/material_container/Initialize(
|
||||
list/init_mats,
|
||||
max_amt = 0,
|
||||
_mat_container_flags = NONE,
|
||||
list/allowed_mats = init_mats,
|
||||
list/allowed_items,
|
||||
list/container_signals
|
||||
)
|
||||
|
||||
if(!isatom(parent))
|
||||
return COMPONENT_INCOMPATIBLE
|
||||
|
||||
materials = list()
|
||||
max_amount = max(0, max_amt)
|
||||
mat_container_flags = _mat_container_flags
|
||||
|
||||
allowed_materials = allowed_mats || list()
|
||||
if(allowed_items)
|
||||
if(ispath(allowed_items) && allowed_items == /obj/item/stack)
|
||||
allowed_item_typecache = GLOB.typecache_stack
|
||||
else
|
||||
allowed_item_typecache = typecacheof(allowed_items)
|
||||
|
||||
//Make the assoc list material reference -> amount
|
||||
for(var/mat in init_mats)
|
||||
var/mat_ref = GET_MATERIAL_REF(mat)
|
||||
if(isnull(mat_ref))
|
||||
continue
|
||||
var/mat_amt = init_mats[mat]
|
||||
if(isnull(mat_amt))
|
||||
mat_amt = 0
|
||||
materials[mat_ref] += mat_amt
|
||||
|
||||
//all user handled signals
|
||||
if(length(container_signals))
|
||||
for(var/signal in container_signals)
|
||||
parent.RegisterSignal(src, signal, container_signals[signal])
|
||||
|
||||
/datum/component/material_container/Destroy(force)
|
||||
materials = null
|
||||
allowed_materials = null
|
||||
return ..()
|
||||
|
||||
/datum/component/material_container/RegisterWithParent()
|
||||
. = ..()
|
||||
|
||||
var/atom/atom_target = parent
|
||||
|
||||
//can we insert into this container
|
||||
if(!(mat_container_flags & MATCONTAINER_NO_INSERT))
|
||||
//to insert stuff into the container
|
||||
RegisterSignal(atom_target, COMSIG_ATOM_ITEM_INTERACTION, PROC_REF(on_item_insert))
|
||||
RegisterSignal(atom_target, COMSIG_ATOM_ITEM_INTERACTION_SECONDARY, PROC_REF(on_secondary_insert))
|
||||
|
||||
//screen tips for inserting items
|
||||
atom_target.flags_1 |= HAS_CONTEXTUAL_SCREENTIPS_1
|
||||
RegisterSignal(atom_target, COMSIG_ATOM_REQUESTING_CONTEXT_FROM_ITEM, PROC_REF(on_requesting_context_from_item))
|
||||
|
||||
//to see available materials
|
||||
if(mat_container_flags & MATCONTAINER_EXAMINE)
|
||||
RegisterSignal(atom_target, COMSIG_ATOM_EXAMINE, PROC_REF(on_examine))
|
||||
|
||||
//drop sheets when the object is deconstructed but not deleted
|
||||
RegisterSignal(parent, COMSIG_OBJ_DECONSTRUCT, PROC_REF(drop_sheets))
|
||||
|
||||
/datum/component/material_container/UnregisterFromParent()
|
||||
var/list/signals = list()
|
||||
|
||||
if(!(mat_container_flags & MATCONTAINER_NO_INSERT))
|
||||
signals += COMSIG_ATOM_ITEM_INTERACTION
|
||||
signals += COMSIG_ATOM_ITEM_INTERACTION_SECONDARY
|
||||
signals += COMSIG_ATOM_REQUESTING_CONTEXT_FROM_ITEM
|
||||
if(mat_container_flags & MATCONTAINER_EXAMINE)
|
||||
signals += COMSIG_ATOM_EXAMINE
|
||||
signals += COMSIG_OBJ_DECONSTRUCT
|
||||
|
||||
UnregisterSignal(parent, signals)
|
||||
|
||||
/datum/component/material_container/proc/drop_sheets()
|
||||
SIGNAL_HANDLER
|
||||
|
||||
retrieve_all()
|
||||
|
||||
/datum/component/material_container/proc/on_examine(datum/source, mob/user, list/examine_texts)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
for(var/I in materials)
|
||||
var/datum/material/M = I
|
||||
var/amt = materials[I] / SHEET_MATERIAL_AMOUNT
|
||||
if(amt)
|
||||
examine_texts += span_notice("It has [amt] sheets of [LOWER_TEXT(M.name)] stored.")
|
||||
|
||||
/datum/component/material_container/vv_edit_var(var_name, var_value)
|
||||
var/old_flags = mat_container_flags
|
||||
. = ..()
|
||||
if(var_name == NAMEOF(src, mat_container_flags) && parent)
|
||||
if(!(old_flags & MATCONTAINER_EXAMINE) && mat_container_flags & MATCONTAINER_EXAMINE)
|
||||
RegisterSignal(parent, COMSIG_ATOM_EXAMINE, PROC_REF(on_examine))
|
||||
else if(old_flags & MATCONTAINER_EXAMINE && !(mat_container_flags & MATCONTAINER_EXAMINE))
|
||||
UnregisterSignal(parent, COMSIG_ATOM_EXAMINE)
|
||||
|
||||
if(old_flags & MATCONTAINER_NO_INSERT && !(mat_container_flags & MATCONTAINER_NO_INSERT))
|
||||
RegisterSignal(parent, COMSIG_ATOM_ITEM_INTERACTION, PROC_REF(on_item_insert))
|
||||
RegisterSignal(parent, COMSIG_ATOM_ITEM_INTERACTION_SECONDARY, PROC_REF(on_secondary_insert))
|
||||
else if(!(old_flags & MATCONTAINER_NO_INSERT) && mat_container_flags & MATCONTAINER_NO_INSERT)
|
||||
UnregisterSignal(parent, list(COMSIG_ATOM_ITEM_INTERACTION, COMSIG_ATOM_ITEM_INTERACTION_SECONDARY))
|
||||
|
||||
/**
|
||||
* 3 Types of Procs
|
||||
* Material Insertion : Insert materials into the container
|
||||
* Material Validation : Checks how much materials are available, Extracts materials from items if the container can hold them
|
||||
* Material Removal : Removes material from the container
|
||||
*
|
||||
* Each Proc further belongs to a specific category
|
||||
* LOW LEVEL: Procs that are used internally & should not be used anywhere else unless you know what your doing
|
||||
* MID LEVEL: Procs that can be used by machines(like recycler, stacking machines) to bypass majority of checks
|
||||
* HIGH LEVEL: Procs that can be used by anyone publicly and guarantees safety checks & limits
|
||||
*/
|
||||
|
||||
//================================Material Insertion procs==============================
|
||||
|
||||
//======================================LOW LEVEL=========================================
|
||||
/**
|
||||
* Inserts the relevant materials from an item into this material container.
|
||||
* This low level proc should not be used directly by anyone
|
||||
*
|
||||
* Arguments:
|
||||
* - [source][/obj/item]: The source of the materials we are inserting.
|
||||
* - multiplier: The multiplier for the materials extract from this item being inserted.
|
||||
* - context: the atom performing the operation, is used mostly for silo logging, the silo resends this signal on the context to give it a
|
||||
* chance to process the item
|
||||
* - user_data: in the form rendered by ID_DATA(user), for material logging (and if this component is connected to a silo, also for permission checking)
|
||||
*/
|
||||
/datum/component/material_container/proc/insert_item_materials(obj/item/source, multiplier = 1, atom/context = parent, alist/user_data)
|
||||
var/primary_mat
|
||||
var/max_mat_value = 0
|
||||
var/material_amount = 0
|
||||
|
||||
var/list/item_materials = source.get_material_composition(mat_container_flags)
|
||||
var/list/mats_consumed = list()
|
||||
for(var/MAT in item_materials)
|
||||
if(!can_hold_material(MAT))
|
||||
continue
|
||||
var/mat_amount = OPTIMAL_COST(item_materials[MAT] * multiplier)
|
||||
materials[MAT] += mat_amount
|
||||
if(item_materials[MAT] > max_mat_value)
|
||||
max_mat_value = item_materials[MAT]
|
||||
primary_mat = MAT
|
||||
mats_consumed[MAT] = mat_amount
|
||||
material_amount += mat_amount
|
||||
if(length(mats_consumed))
|
||||
SEND_SIGNAL(src, COMSIG_MATCONTAINER_ITEM_CONSUMED, source, primary_mat, mats_consumed, material_amount, context, user_data)
|
||||
|
||||
return primary_mat
|
||||
//===================================================================================
|
||||
|
||||
|
||||
//===============================MID LEVEL===================================================
|
||||
/**
|
||||
* For inserting an amount of material. Use this to add materials to the container directly
|
||||
*
|
||||
* Arguments:
|
||||
* - amt: amount of said material to insert
|
||||
* - mat: the material type to insert
|
||||
*/
|
||||
/datum/component/material_container/proc/insert_amount_mat(amt, datum/material/mat)
|
||||
if(amt <= 0)
|
||||
return 0
|
||||
amt = OPTIMAL_COST(amt)
|
||||
if(!has_space(amt))
|
||||
return 0
|
||||
|
||||
var/total_amount_saved = total_amount()
|
||||
if(mat)
|
||||
if(!istype(mat))
|
||||
mat = GET_MATERIAL_REF(mat)
|
||||
materials[mat] += amt
|
||||
else
|
||||
var/num_materials = length(materials)
|
||||
if(!num_materials)
|
||||
return 0
|
||||
|
||||
amt /= num_materials
|
||||
for(var/i in materials)
|
||||
materials[i] += amt
|
||||
return (total_amount() - total_amount_saved)
|
||||
|
||||
/**
|
||||
* Proc specifically for inserting items, use this when you want to insert any item into the container
|
||||
* this bypasses most of the material flag checks so much be used by machines like recycler, stacking machine etc that
|
||||
* does not care for such checks
|
||||
*
|
||||
* Arguments:
|
||||
* - [weapon][obj/item]: the item you are trying to insert
|
||||
* - multiplier: The multiplier for the materials being inserted
|
||||
* - context: the atom performing the operation, this is the last argument sent in COMSIG_MATCONTAINER_ITEM_CONSUMED and is used mostly for silo logging
|
||||
* * - delete_item: should we delete the item after its materials are consumed. does not apply to stacks if they were split due to lack of space
|
||||
* * - user_data - in the form rendered by ID_DATA(user), for material logging (and if this component is connected to a silo, also for permission checking)
|
||||
*/
|
||||
/datum/component/material_container/proc/insert_item(obj/item/weapon, multiplier = 1, atom/context = parent, delete_item = TRUE, alist/user_data)
|
||||
if(QDELETED(weapon))
|
||||
return MATERIAL_INSERT_ITEM_NO_MATS
|
||||
multiplier = CEILING(multiplier, 0.01)
|
||||
|
||||
var/obj/item/target = weapon
|
||||
|
||||
var/material_amount = OPTIMAL_COST(get_item_material_amount(target) * multiplier)
|
||||
if(!material_amount)
|
||||
return MATERIAL_INSERT_ITEM_NO_MATS
|
||||
var/obj/item/stack/item_stack
|
||||
if(isstack(weapon) && !has_space(material_amount)) //not enough space split and feed as many sheets possible
|
||||
item_stack = weapon
|
||||
var/space_left = max_amount - total_amount()
|
||||
if(!space_left)
|
||||
return MATERIAL_INSERT_ITEM_NO_SPACE
|
||||
var/material_per_sheet = material_amount / item_stack.amount
|
||||
var/sheets_to_insert = floor(space_left / material_per_sheet)
|
||||
if(!sheets_to_insert)
|
||||
return MATERIAL_INSERT_ITEM_NO_SPACE
|
||||
target = fast_split_stack(item_stack, sheets_to_insert)
|
||||
material_amount = get_item_material_amount(target) * multiplier
|
||||
material_amount = OPTIMAL_COST(material_amount)
|
||||
|
||||
//do the insert
|
||||
var/last_inserted_id = insert_item_materials(target, multiplier, context, user_data = user_data)
|
||||
if(!isnull(last_inserted_id))
|
||||
if(delete_item || target != weapon) //we could have split the stack ourselves
|
||||
qdel(target) //item gone
|
||||
return material_amount
|
||||
else if(!isnull(item_stack) && item_stack != target) //insertion failed, merge the split stack back into the original
|
||||
var/obj/item/stack/inserting_stack = target
|
||||
item_stack.add(inserting_stack.amount)
|
||||
qdel(inserting_stack)
|
||||
|
||||
return MATERIAL_INSERT_ITEM_FAILURE
|
||||
//============================================================================================
|
||||
|
||||
|
||||
//===================================HIGH LEVEL===================================================
|
||||
/**
|
||||
* inserts an item from the players hand into the container. Loops through all the contents inside recursively
|
||||
* Does all explicit checking for mat flags & callbacks to check if insertion is valid
|
||||
* This proc is what you should be using for almost all cases
|
||||
*
|
||||
* Arguments:
|
||||
* * held_item - the item to insert
|
||||
* * user - the mob inserting this item
|
||||
* * context - the atom performing the operation, this is the last argument sent in COMSIG_MATCONTAINER_ITEM_CONSUMED and is used mostly for silo logging
|
||||
*/
|
||||
/datum/component/material_container/proc/user_insert(obj/item/held_item, mob/living/user, atom/context = parent)
|
||||
set waitfor = FALSE
|
||||
. = 0
|
||||
|
||||
//All items that do not have any contents
|
||||
var/list/obj/item/items = list(held_item)
|
||||
//is this the first item we are ever processing
|
||||
var/first_checks = TRUE
|
||||
//list of items to delete
|
||||
var/list/obj/item/to_delete = list()
|
||||
//The status of the last insert attempt
|
||||
var/inserted = 0
|
||||
//All messages to be displayed to chat
|
||||
var/list/chat_msgs = list()
|
||||
//differs from held_item when using TK
|
||||
var/obj/item/active_held = user.get_active_held_item()
|
||||
//omni tools can act as any tool so get its real behaviour
|
||||
active_held = active_held.get_proxy_attacker_for(held_item)
|
||||
|
||||
while(items.len)
|
||||
//no point inserting more items
|
||||
if(inserted == MATERIAL_INSERT_ITEM_NO_SPACE)
|
||||
break
|
||||
|
||||
//Pop the 1st item out from the list
|
||||
var/obj/item/target_item = items[1]
|
||||
items -= target_item
|
||||
|
||||
//e.g. projectiles inside bullets are not objects
|
||||
if(!istype(target_item))
|
||||
continue
|
||||
//can't allow abstract, hologram items
|
||||
if((target_item.item_flags & ABSTRACT) || (target_item.flags_1 & HOLOGRAM_1))
|
||||
continue
|
||||
//user defined conditions
|
||||
if(SEND_SIGNAL(src, COMSIG_MATCONTAINER_PRE_USER_INSERT, target_item, user) & MATCONTAINER_BLOCK_INSERT)
|
||||
continue
|
||||
//item is either indestructible, not allowed for redemption or not in the allowed types
|
||||
if((target_item.resistance_flags & INDESTRUCTIBLE) || (target_item.item_flags & NO_MAT_REDEMPTION) || (allowed_item_typecache && !is_type_in_typecache(target_item, allowed_item_typecache)))
|
||||
if(!(mat_container_flags & MATCONTAINER_SILENT))
|
||||
var/list/status_data = chat_msgs["[MATERIAL_INSERT_ITEM_FAILURE]"] || list()
|
||||
var/list/item_data = status_data[target_item.name] || list()
|
||||
item_data["count"] += 1
|
||||
status_data[target_item.name] = item_data
|
||||
chat_msgs["[MATERIAL_INSERT_ITEM_FAILURE]"] = status_data
|
||||
|
||||
if(target_item.resistance_flags & INDESTRUCTIBLE)
|
||||
if(target_item != active_held) //move it out of any storage medium its in so it doesn't get consumed with its parent, but only if that storage medium is not our hand
|
||||
target_item.forceMove(get_turf(context))
|
||||
continue
|
||||
|
||||
//storage items usually come here
|
||||
//this is so players can insert items from their bags into machines for convinience
|
||||
if(!target_item.atom_storage || !target_item.contents.len)
|
||||
continue
|
||||
//at this point we can check if we have enough for all items & other stuff
|
||||
if(first_checks)
|
||||
//duffle bags needs to be unzipped
|
||||
if(target_item.atom_storage?.locked)
|
||||
if(!(mat_container_flags & MATCONTAINER_SILENT))
|
||||
to_chat(user, span_warning("[target_item] has its storage locked"))
|
||||
return
|
||||
|
||||
//anything that isn't a stack cannot be split so find out if we have enough space, we don't want to consume half the contents of an object & leave it in a broken state
|
||||
//for duffle bags and other storage items we can check for space 1 item at a time
|
||||
if(!isstack(target_item) && !target_item.atom_storage)
|
||||
var/total_amount = 0
|
||||
for(var/obj/item/weapon as anything in target_item.get_all_contents_type(/obj/item))
|
||||
total_amount += get_item_material_amount(weapon)
|
||||
if(!has_space(total_amount))
|
||||
if(!(mat_container_flags & MATCONTAINER_SILENT))
|
||||
to_chat(user, span_warning("[parent] does not have enough space for [target_item]!"))
|
||||
return
|
||||
|
||||
first_checks = FALSE
|
||||
|
||||
//if stack, check if we want to read precise amount of sheets to insert
|
||||
var/obj/item/stack/item_stack = null
|
||||
if(isstack(target_item) && precise_insertion)
|
||||
var/atom/current_parent = parent
|
||||
item_stack = target_item
|
||||
var/requested_amount = tgui_input_number(user, "How much do you want to insert?", "Inserting [item_stack.singular_name]s", item_stack.amount, item_stack.amount)
|
||||
if(!requested_amount || QDELETED(target_item) || QDELETED(user) || QDELETED(src))
|
||||
continue
|
||||
if(parent != current_parent || user.get_active_held_item() != active_held)
|
||||
continue
|
||||
if(requested_amount != item_stack.amount) //only split if its not the whole amount
|
||||
target_item = fast_split_stack(item_stack, requested_amount) //split off the requested amount
|
||||
requested_amount = 0
|
||||
|
||||
//is this item a stack and was it split by the player?
|
||||
var/was_stack_split = !isnull(item_stack) && item_stack != target_item
|
||||
//if it was split then item_stack has the reference to the original stack/item
|
||||
var/obj/item/original_item = was_stack_split ? item_stack : target_item
|
||||
//if this item is not the one the player is holding then don't remove it from their hand
|
||||
if(original_item != active_held)
|
||||
original_item = null
|
||||
if(!isnull(original_item) && !user.temporarilyRemoveItemFromInventory(original_item)) //remove from hand(if split remove the original stack else the target)
|
||||
return
|
||||
|
||||
//insert the item
|
||||
var/item_name = target_item.name
|
||||
var/item_count = 1
|
||||
var/is_stack = FALSE
|
||||
var/obj/item/stack/the_stack
|
||||
if(isstack(target_item))
|
||||
the_stack = target_item
|
||||
item_name = the_stack.singular_name
|
||||
item_count = the_stack.amount
|
||||
is_stack = TRUE
|
||||
|
||||
//we typically don't want to consume bags, boxes but only their contents. so we skip processing
|
||||
inserted = !target_item.atom_storage ? insert_item(target_item, 1, context, is_stack, user_data = ID_DATA(user)) : 0
|
||||
if(inserted > 0)
|
||||
. += inserted
|
||||
inserted /= SHEET_MATERIAL_AMOUNT // display units inserted as sheets for improved readability
|
||||
|
||||
//collect all messages to print later
|
||||
var/list/status_data = chat_msgs["[MATERIAL_INSERT_ITEM_SUCCESS]"] || list()
|
||||
var/list/item_data = status_data[item_name] || list()
|
||||
item_data["count"] += item_count
|
||||
item_data["amount"] += inserted
|
||||
item_data["stack"] = is_stack
|
||||
status_data[item_name] = item_data
|
||||
chat_msgs["[MATERIAL_INSERT_ITEM_SUCCESS]"] = status_data
|
||||
|
||||
//delete the item or merge stacks if any left over
|
||||
if(is_stack)
|
||||
//player split it & machine further split that due to lack of space? merge with remaining stack
|
||||
if(!QDELETED(target_item) && was_stack_split)
|
||||
var/obj/item/stack/inserting_stack = target_item
|
||||
item_stack.add(inserting_stack.amount)
|
||||
qdel(inserting_stack)
|
||||
|
||||
//was this the original item in the players hand? put what's left back in the player's hand
|
||||
if(!QDELETED(original_item))
|
||||
user.put_in_active_hand(original_item)
|
||||
|
||||
//skip processing children & other stuff. irrelevant for stacks
|
||||
continue
|
||||
|
||||
//queue the object for deletion
|
||||
to_delete += target_item
|
||||
else
|
||||
//collect all messages to print later
|
||||
var/list/status_data = chat_msgs["[inserted]"] || list()
|
||||
var/list/item_data = status_data[item_name] || list()
|
||||
item_data["count"] += item_count
|
||||
status_data[item_name] = item_data
|
||||
chat_msgs["[inserted]"] = status_data
|
||||
|
||||
//player split the stack by the requested amount but even that split amount could not be salvaged. merge it back with the original
|
||||
if(was_stack_split)
|
||||
var/obj/item/stack/inserting_stack = target_item
|
||||
item_stack.add(inserting_stack.amount)
|
||||
qdel(inserting_stack)
|
||||
|
||||
//was this the original item in the players hand? put it back because we coudn't salvage it
|
||||
if(!QDELETED(original_item))
|
||||
user.put_in_active_hand(original_item)
|
||||
|
||||
//we can stop here as remaining items will fail to insert as well
|
||||
if(inserted == MATERIAL_INSERT_ITEM_NO_SPACE)
|
||||
break
|
||||
|
||||
//we failed to process the item so don't bother going into its contents
|
||||
//but if we are dealing with storage items like bags, boxes etc then we make a exception
|
||||
if(!target_item.atom_storage)
|
||||
continue
|
||||
|
||||
//If any mats were consumed we can proceed to delete the parent
|
||||
//If it has children then we will process them first in the 2nd round
|
||||
//This is done so we don't delete the children when the parent is consumed
|
||||
//We only do this on the 1st iteration so we don't re-iterate through its children again
|
||||
if(target_item.contents.len)
|
||||
if(target_item.atom_storage?.locked) //can't access contents of locked storage(like duffle bags)
|
||||
continue
|
||||
//process children
|
||||
items += target_item.contents
|
||||
|
||||
//we now summarize the chat msgs collected
|
||||
if(!(mat_container_flags & MATCONTAINER_SILENT))
|
||||
for(var/status in chat_msgs)
|
||||
var/list/status_data = chat_msgs[status]
|
||||
|
||||
for(var/item_name in status_data)
|
||||
//read the params
|
||||
var/list/chat_data = status_data[item_name]
|
||||
var/count = chat_data["count"]
|
||||
var/amount = chat_data["amount"]
|
||||
|
||||
//decode the message
|
||||
switch(text2num(status))
|
||||
if(MATERIAL_INSERT_ITEM_SUCCESS) //no problems full item was consumed
|
||||
if(chat_data["stack"])
|
||||
var/sheets = min(count, amount) //minimum between sheets inserted vs sheets consumed(values differ for alloys)
|
||||
to_chat(user, span_notice("[sheets > 1 ? "[sheets] " : ""][item_name][sheets > 1 ? "s were" : " was"] added to [parent]."))
|
||||
else
|
||||
to_chat(user, span_notice("[count > 1 ? "[count] " : ""][item_name][count > 1 ? "s" : ""], worth [amount] sheets, [count > 1 ? "were" : "was"] added to [parent]."))
|
||||
if(MATERIAL_INSERT_ITEM_NO_SPACE) //no space
|
||||
to_chat(user, span_warning("[parent] has no space to accept [item_name]!"))
|
||||
if(MATERIAL_INSERT_ITEM_NO_MATS) //no materials inside these items
|
||||
to_chat(user, span_warning("[item_name][count > 1 ? "s have" : " has"] no materials that can be accepted by [parent]!"))
|
||||
if(MATERIAL_INSERT_ITEM_FAILURE) //could be because the material type was not accepted or other stuff
|
||||
to_chat(user, span_warning("[item_name][count > 1 ? "s were" : " was"] rejected by [parent]!"))
|
||||
|
||||
//finally delete the items
|
||||
for(var/obj/item/deleting as anything in to_delete)
|
||||
if(!QDELETED(deleting)) //deleting parents also delete their children so we check
|
||||
qdel(deleting)
|
||||
|
||||
/datum/component/material_container/proc/on_item_insert(datum/source, mob/living/user, obj/item/weapon)
|
||||
SIGNAL_HANDLER
|
||||
// Don't insert material items with left click
|
||||
if (isstack(weapon))
|
||||
return attempt_insert(user, weapon)
|
||||
|
||||
/datum/component/material_container/proc/on_secondary_insert(datum/source, mob/living/user, obj/item/weapon)
|
||||
SIGNAL_HANDLER
|
||||
return attempt_insert(user, weapon)
|
||||
|
||||
/// Proc that allows players to fill the parent with mats
|
||||
/datum/component/material_container/proc/attempt_insert(mob/living/user, obj/item/weapon)
|
||||
//Allows you to attack the machine with iron sheets for e.g.
|
||||
if(!(mat_container_flags & MATCONTAINER_ANY_INTENT) && user.combat_mode)
|
||||
return
|
||||
|
||||
if(ismachinery(parent))
|
||||
var/obj/machinery/machine = parent
|
||||
if(machine.machine_stat || machine.panel_open)
|
||||
return
|
||||
|
||||
user_insert(weapon, user)
|
||||
return ITEM_INTERACT_SUCCESS
|
||||
//===============================================================================================
|
||||
|
||||
|
||||
//======================================Material Validation=======================================
|
||||
|
||||
//=========================================LOW LEVEL===================================
|
||||
/**
|
||||
* Proc that returns TRUE if the container has space
|
||||
*
|
||||
* Arguments:
|
||||
* * amt - can this container hold this much amount of materials
|
||||
*/
|
||||
/datum/component/material_container/proc/has_space(amt = 0)
|
||||
return (total_amount() + amt) <= max_amount
|
||||
|
||||
/**
|
||||
* The default check for whether we can add materials to this material container.
|
||||
*
|
||||
* Arguments:
|
||||
* - [mat][/atom/material]: The material we are checking for insertability.
|
||||
*/
|
||||
/datum/component/material_container/proc/can_hold_material(datum/material/mat)
|
||||
if(mat in allowed_materials)
|
||||
return TRUE
|
||||
if(istype(mat) && ((mat.id in allowed_materials) || (mat.type in allowed_materials)))
|
||||
allowed_materials += mat // This could get messy with passing lists by ref... but if you're doing that the list expansion is probably being taken care of elsewhere anyway...
|
||||
return TRUE
|
||||
if(SEND_SIGNAL(src, COMSIG_MATCONTAINER_MAT_CHECK, mat) & MATCONTAINER_ALLOW_MAT)
|
||||
allowed_materials += mat
|
||||
return TRUE
|
||||
return FALSE
|
||||
//========================================================================================
|
||||
|
||||
|
||||
//===================================MID LEVEL=============================================
|
||||
/**
|
||||
* Returns the amount of a specific material in this container.
|
||||
*
|
||||
* Arguments:
|
||||
* -[mat][datum/material] : the material type to check for 3 cases
|
||||
* a) If it's an path its ref is retrieved
|
||||
* b) If it's text then its an category material & there is no way to deal with it so return 0
|
||||
* c) If normal material proceeds as usual
|
||||
*/
|
||||
/datum/component/material_container/proc/get_material_amount(datum/material/mat)
|
||||
if(!istype(mat))
|
||||
mat = GET_MATERIAL_REF(mat)
|
||||
return materials[mat]
|
||||
|
||||
/**
|
||||
* Returns the amount of material relevant to this container;
|
||||
* if this container does not support glass, any glass in 'I' will not be taken into account
|
||||
*
|
||||
* Arguments:
|
||||
* - [I][obj/item]: the item whos materials must be retrieved
|
||||
*/
|
||||
/datum/component/material_container/proc/get_item_material_amount(obj/item/item)
|
||||
if(!istype(item) || !item.custom_materials)
|
||||
return 0
|
||||
var/material_amount = 0
|
||||
var/list/item_materials = item.get_material_composition(mat_container_flags)
|
||||
for(var/MAT in item_materials)
|
||||
if(!can_hold_material(MAT))
|
||||
continue
|
||||
material_amount += item_materials[MAT]
|
||||
return material_amount
|
||||
//================================================================================================
|
||||
|
||||
|
||||
//=========================================HIGH LEVEL==========================================
|
||||
/// returns the total amount of material in the container
|
||||
/datum/component/material_container/proc/total_amount()
|
||||
. = 0
|
||||
for(var/i in materials)
|
||||
. += get_material_amount(i)
|
||||
|
||||
/**
|
||||
* Returns TRUE if you have enough of the specified material.
|
||||
*
|
||||
* Arguments:
|
||||
* - [req_mat][datum/material]: the material to check for
|
||||
* - amount: how much material do we need
|
||||
*/
|
||||
/datum/component/material_container/proc/has_enough_of_material(datum/material/req_mat, amount = 1)
|
||||
return get_material_amount(req_mat) >= OPTIMAL_COST(amount)
|
||||
|
||||
|
||||
/**
|
||||
* Checks if its possible to afford a certain amount of materials. Takes a dictionary of materials.
|
||||
* coefficient can be thought of as the machines efficiency & multiplier as the print quantity
|
||||
*
|
||||
* Arguments:
|
||||
* - mats: list of materials(key=material, value= 1 unit of material) to check for
|
||||
* - coefficient: scaling applied to 1 unit of material in the mats list
|
||||
* - multiplier: how many units(after scaling) do we require
|
||||
*/
|
||||
/datum/component/material_container/proc/has_materials(list/mats, coefficient = 1, multiplier = 1)
|
||||
if(!length(mats))
|
||||
return FALSE
|
||||
|
||||
for(var/x in mats) //Loop through all required materials
|
||||
var/wanted = OPTIMAL_COST(mats[x] * coefficient) * multiplier
|
||||
if(!has_enough_of_material(x, wanted))//Not a category, so just check the normal way
|
||||
testing("didn't have: [x] wanted: [wanted]")
|
||||
return FALSE
|
||||
|
||||
return TRUE
|
||||
//==========================================================================================================
|
||||
|
||||
|
||||
//================================================Material Usage============================================
|
||||
|
||||
//==================================================LOW LEVEL=======================================
|
||||
/**
|
||||
* Uses an amount of a specific material, effectively removing it.
|
||||
*
|
||||
* Arguments:
|
||||
* - amt: amount of said material to use
|
||||
* - [mat][datum/material]: type of mat to use
|
||||
*/
|
||||
/datum/component/material_container/proc/use_amount_mat(amt, datum/material/mat)
|
||||
//round amount
|
||||
amt = OPTIMAL_COST(amt)
|
||||
|
||||
//get ref if necessary
|
||||
if(!istype(mat))
|
||||
mat = GET_MATERIAL_REF(mat)
|
||||
|
||||
//check if sufficient is available
|
||||
if(materials[mat] < amt)
|
||||
return 0
|
||||
|
||||
//consume & return amount consumed
|
||||
materials[mat] -= amt
|
||||
return amt
|
||||
//==============================================================================================
|
||||
|
||||
//=========================================MID LEVEL==========================================
|
||||
/**
|
||||
* For consuming a dictionary of materials.
|
||||
*
|
||||
* Arguments:
|
||||
* - mats: map of materials to consume(key = material type, value = amount)
|
||||
* - coefficient: how much fraction of unit material in the mats list must be consumed. This is usually your machines efficiency
|
||||
* - multiplier: how many units of material in the mats list(after each unit is multiplied and rounded with coefficient) must be consumed, This is usually your print quantity
|
||||
*/
|
||||
/datum/component/material_container/proc/use_materials(list/mats, coefficient = 1, multiplier = 1)
|
||||
if(!mats || !length(mats))
|
||||
return FALSE
|
||||
|
||||
var/amount_removed = 0
|
||||
for(var/i in mats)
|
||||
amount_removed += use_amount_mat(OPTIMAL_COST(mats[i] * coefficient) * multiplier, i)
|
||||
|
||||
return amount_removed
|
||||
//============================================================================================
|
||||
|
||||
|
||||
//===========================================HIGH LEVEL=======================================
|
||||
|
||||
/**
|
||||
* For spawning stacks (mineral sheets or ore) at a specific location. Used by machines to output sheets.
|
||||
*
|
||||
* Arguments:
|
||||
* stack_amt: number of sheets to extract
|
||||
* [material][datum/material]: type of sheets present in this container to extract
|
||||
* [target][atom]: drop location
|
||||
* [atom][context]: context - the atom performing the operation, this is the last argument sent in COMSIG_MATCONTAINER_STACK_RETRIEVED and is used mostly for silo logging
|
||||
* user_data - in the form rendered by ID_DATA(user), for material logging (and if this component is connected to a silo, also for permission checking)
|
||||
*/
|
||||
/datum/component/material_container/proc/retrieve_stack(stack_amt, datum/material/material, atom/target = null, atom/context = parent, alist/user_data)
|
||||
//do we support sheets of this material
|
||||
if(!material.sheet_type)
|
||||
return 0 //Add greyscale sheet handling here later
|
||||
if(!can_hold_material(material))
|
||||
return 0
|
||||
|
||||
//requested amount greater than available amount or just an invalid value
|
||||
stack_amt = min(floor(materials[material] / SHEET_MATERIAL_AMOUNT), stack_amt)
|
||||
if(stack_amt <= 0)
|
||||
return 0
|
||||
//auto drop location
|
||||
if(!target)
|
||||
var/atom/parent_atom = parent
|
||||
target = parent_atom.drop_location()
|
||||
if(!target)
|
||||
return 0
|
||||
|
||||
//eject sheets based on available amount after each iteration
|
||||
var/count = 0
|
||||
while(stack_amt > 0)
|
||||
var/type_to_retrieve = material.sheet_type || material.ore_type
|
||||
//don't merge yet. we need to do stuff with it first
|
||||
var/obj/item/stack/new_stack = new type_to_retrieve(target, min(stack_amt, MAX_STACK_SIZE), FALSE)
|
||||
if(istype(new_stack, /obj/item/stack/sheet))
|
||||
var/obj/item/stack/sheet/new_sheets = new_stack
|
||||
new_sheets.manufactured = TRUE
|
||||
count += new_stack.amount
|
||||
//use material & deduct work needed
|
||||
use_amount_mat(new_stack.amount * SHEET_MATERIAL_AMOUNT, material)
|
||||
stack_amt -= new_stack.amount
|
||||
//send signal
|
||||
SEND_SIGNAL(src, COMSIG_MATCONTAINER_STACK_RETRIEVED, new_stack, context, user_data)
|
||||
//no point merging anything into an already full stack
|
||||
if(new_stack.amount == new_stack.max_amount)
|
||||
continue
|
||||
//now we can merge since we are done with it
|
||||
for(var/obj/item/stack/item_stack in target)
|
||||
if(item_stack == new_stack || item_stack.type != type_to_retrieve) //don't merge with self or different type
|
||||
continue
|
||||
//speed merge
|
||||
var/merge_amount = min(item_stack.amount, new_stack.max_amount - new_stack.get_amount())
|
||||
item_stack.use(merge_amount)
|
||||
new_stack.add(merge_amount)
|
||||
break
|
||||
return count
|
||||
|
||||
/**
|
||||
* Proc to get all the materials and dump them as sheets
|
||||
*
|
||||
* Arguments:
|
||||
* - target: drop location of the sheets
|
||||
* - context: the atom which is ejecting the sheets. Used mostly in silo logging
|
||||
*/
|
||||
/datum/component/material_container/proc/retrieve_all(target = null, atom/context = parent)
|
||||
var/result = 0
|
||||
for(var/MAT in materials)
|
||||
result += retrieve_stack(amount2sheet(materials[MAT]), MAT, target, context, user_data = ID_DATA(null))
|
||||
return result
|
||||
//============================================================================================
|
||||
|
||||
|
||||
/datum/component/material_container/ui_static_data(mob/user)
|
||||
var/list/data = list()
|
||||
data["SHEET_MATERIAL_AMOUNT"] = SHEET_MATERIAL_AMOUNT
|
||||
return data
|
||||
|
||||
/// List format is list(material_name = list(amount = ..., ref = ..., etc.))
|
||||
/datum/component/material_container/ui_data(mob/user)
|
||||
var/list/data = list()
|
||||
|
||||
for(var/datum/material/material as anything in materials)
|
||||
var/amount = materials[material]
|
||||
|
||||
data += list(list(
|
||||
"name" = material.name,
|
||||
"ref" = REF(material),
|
||||
"amount" = amount,
|
||||
"color" = material.greyscale_color || material.color
|
||||
))
|
||||
|
||||
return data
|
||||
|
||||
/**
|
||||
* Adds context sensitivy directly to the material container file for screentips
|
||||
* Arguments:
|
||||
* * source - refers to item that will display its screentip
|
||||
* * context - refers to, in this case, an item in the users hand hovering over the material container, such as an autolathe
|
||||
* * held_item - refers to the item that has materials accepted by the material container
|
||||
* * user - refers to user who will see the screentip when the proper context and tool are there
|
||||
*/
|
||||
/datum/component/material_container/proc/on_requesting_context_from_item(datum/source, list/context, obj/item/held_item, mob/living/user)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
if(isnull(held_item))
|
||||
return NONE
|
||||
if(!(mat_container_flags & MATCONTAINER_ANY_INTENT) && user.combat_mode)
|
||||
return NONE
|
||||
if(held_item.item_flags & ABSTRACT)
|
||||
return NONE
|
||||
if((held_item.flags_1 & HOLOGRAM_1) || (held_item.item_flags & NO_MAT_REDEMPTION) || (allowed_item_typecache && !is_type_in_typecache(held_item, allowed_item_typecache)))
|
||||
return NONE
|
||||
var/list/item_materials = held_item.get_material_composition(mat_container_flags)
|
||||
if(!length(item_materials))
|
||||
return NONE
|
||||
for(var/material in item_materials)
|
||||
if(can_hold_material(material))
|
||||
continue
|
||||
return NONE
|
||||
|
||||
if (isstack(held_item))
|
||||
context[SCREENTIP_CONTEXT_LMB] = "Insert stack"
|
||||
context[SCREENTIP_CONTEXT_RMB] = "Insert"
|
||||
|
||||
return CONTEXTUAL_SCREENTIP_SET
|
||||
|
||||
#undef MATERIAL_INSERT_ITEM_SUCCESS
|
||||
@@ -1,292 +0,0 @@
|
||||
/*
|
||||
This component allows machines to connect remotely to a material container
|
||||
(namely an /obj/machinery/ore_silo) elsewhere. It offers optional graceful
|
||||
fallback to a local material storage in case remote storage is unavailable, and
|
||||
handles linking back and forth.
|
||||
*/
|
||||
|
||||
/datum/component/remote_materials
|
||||
// Three possible states:
|
||||
// 1. silo exists, materials is parented to silo
|
||||
// 2. silo is null, materials is parented to parent
|
||||
// 3. silo is null, materials is null
|
||||
|
||||
///The silo machine this container is connected to
|
||||
var/obj/machinery/ore_silo/silo
|
||||
///Material container. the value is either the silo or local
|
||||
var/datum/component/material_container/mat_container
|
||||
///Should we create a local storage if we can't connect to silo
|
||||
var/allow_standalone
|
||||
///Local size of container when silo = null
|
||||
var/local_size = INFINITY
|
||||
///Flags used for the local material container(exceptions for item insert & intent flags)
|
||||
var/mat_container_flags = NONE
|
||||
///List of signals to hook onto the local container
|
||||
var/list/mat_container_signals
|
||||
///Callback for round start silo connections. Have to cancel this if the parent gets destroyed before round start
|
||||
VAR_PRIVATE/datum/callback/connection
|
||||
|
||||
/datum/component/remote_materials/Initialize(
|
||||
mapload,
|
||||
allow_standalone = TRUE,
|
||||
force_connect = FALSE,
|
||||
mat_container_flags = NONE,
|
||||
list/mat_container_signals = null,
|
||||
)
|
||||
if (!isatom(parent))
|
||||
return COMPONENT_INCOMPATIBLE
|
||||
|
||||
src.allow_standalone = allow_standalone
|
||||
src.mat_container_flags = mat_container_flags
|
||||
src.mat_container_signals = mat_container_signals
|
||||
|
||||
RegisterSignal(parent, COMSIG_ATOM_TOOL_ACT(TOOL_MULTITOOL), PROC_REF(OnMultitool))
|
||||
|
||||
var/turf/T = get_turf(parent)
|
||||
var/connect_to_silo = FALSE
|
||||
if(force_connect || (mapload && is_station_level(T.z)))
|
||||
connect_to_silo = TRUE
|
||||
|
||||
if(mapload) // wait for silo to initialize during mapload
|
||||
connection = CALLBACK(src, PROC_REF(_PrepareStorage), connect_to_silo)
|
||||
SSticker.OnRoundstart(connection)
|
||||
else //directly register in round
|
||||
_PrepareStorage(connect_to_silo)
|
||||
|
||||
/**
|
||||
* Internal proc. prepares local storage if onnect_to_silo = FALSE
|
||||
*
|
||||
* Arguments
|
||||
* connect_to_silo- if true connect to global silo. If not successfull then go to local storage
|
||||
* only if allow_standalone = TRUE, else you a null mat_container
|
||||
*/
|
||||
/datum/component/remote_materials/proc/_PrepareStorage(connect_to_silo)
|
||||
PRIVATE_PROC(TRUE)
|
||||
|
||||
connection = null
|
||||
if (connect_to_silo)
|
||||
silo = GLOB.ore_silo_default
|
||||
if (silo)
|
||||
silo.connect_receptacle(src, parent)
|
||||
if(!(mat_container_flags & MATCONTAINER_NO_INSERT))
|
||||
RegisterSignal(parent, COMSIG_ATOM_ITEM_INTERACTION, PROC_REF(on_item_insert))
|
||||
RegisterSignal(parent, COMSIG_ATOM_ITEM_INTERACTION_SECONDARY, PROC_REF(on_secondary_insert))
|
||||
|
||||
if (!mat_container && allow_standalone)
|
||||
_MakeLocal()
|
||||
|
||||
/datum/component/remote_materials/Destroy()
|
||||
if(connection)
|
||||
LAZYREMOVE(SSticker.round_start_events, connection)
|
||||
connection = null
|
||||
if(silo)
|
||||
allow_standalone = FALSE
|
||||
disconnect()
|
||||
if(mat_container)
|
||||
mat_container = null
|
||||
|
||||
return ..()
|
||||
|
||||
/datum/component/remote_materials/proc/_MakeLocal()
|
||||
PRIVATE_PROC(TRUE)
|
||||
|
||||
silo = null
|
||||
|
||||
mat_container = parent.AddComponent( \
|
||||
/datum/component/material_container, \
|
||||
SSmaterials.materials_by_category[MAT_CATEGORY_SILO], \
|
||||
local_size, \
|
||||
mat_container_flags, \
|
||||
container_signals = mat_container_signals, \
|
||||
allowed_items = /obj/item/stack \
|
||||
)
|
||||
|
||||
/// Adds/Removes this connection from the silo
|
||||
/datum/component/remote_materials/proc/toggle_holding()
|
||||
if(isnull(silo))
|
||||
return
|
||||
|
||||
if(!silo.holds[src])
|
||||
silo.holds[src] = TRUE
|
||||
else
|
||||
silo.holds -= src
|
||||
|
||||
/**
|
||||
* Sets the storage size for local materials when not linked with silo
|
||||
* Arguments
|
||||
*
|
||||
* * size - the new size for local storage. measured in SHEET_MATERIAL_SIZE units
|
||||
*/
|
||||
/datum/component/remote_materials/proc/set_local_size(size)
|
||||
local_size = size
|
||||
if (!silo && mat_container)
|
||||
mat_container.max_amount = size
|
||||
|
||||
///Disconnects this component from the silo
|
||||
/datum/component/remote_materials/proc/disconnect()
|
||||
if(isnull(silo))
|
||||
return
|
||||
|
||||
UnregisterSignal(parent, list(COMSIG_ATOM_ITEM_INTERACTION, COMSIG_ATOM_ITEM_INTERACTION_SECONDARY))
|
||||
silo.disconnect_receptacle(src, parent)
|
||||
|
||||
if (allow_standalone)
|
||||
_MakeLocal()
|
||||
|
||||
/datum/component/remote_materials/proc/OnMultitool(datum/source, mob/user, obj/item/multitool/M)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
. = NONE
|
||||
if (!QDELETED(M.buffer) && istype(M.buffer, /obj/machinery/ore_silo))
|
||||
if (silo == M.buffer)
|
||||
to_chat(user, span_warning("[parent] is already connected to [silo]!"))
|
||||
return ITEM_INTERACT_BLOCKING
|
||||
if(!check_z_level(M.buffer))
|
||||
to_chat(user, span_warning("[parent] is too far away to get a connection signal!"))
|
||||
return ITEM_INTERACT_BLOCKING
|
||||
|
||||
var/obj/machinery/ore_silo/new_silo = M.buffer
|
||||
var/datum/component/material_container/new_container = new_silo.GetComponent(/datum/component/material_container)
|
||||
if (silo)
|
||||
silo.disconnect_receptacle(src, parent)
|
||||
else if (mat_container)
|
||||
//transfer all mats to silo. whatever cannot be transfered is dumped out as sheets
|
||||
if(mat_container.total_amount())
|
||||
for(var/datum/material/mat as anything in mat_container.materials)
|
||||
var/mat_amount = mat_container.materials[mat]
|
||||
if(!mat_amount || !new_container.has_space(mat_amount) || !new_container.can_hold_material(mat))
|
||||
continue
|
||||
new_container.materials[mat] += mat_amount
|
||||
mat_container.materials[mat] = 0
|
||||
qdel(mat_container)
|
||||
new_silo.connect_receptacle(src, parent)
|
||||
if(!(mat_container_flags & MATCONTAINER_NO_INSERT))
|
||||
RegisterSignal(parent, COMSIG_ATOM_ITEM_INTERACTION, PROC_REF(on_item_insert))
|
||||
RegisterSignal(parent, COMSIG_ATOM_ITEM_INTERACTION_SECONDARY, PROC_REF(on_secondary_insert))
|
||||
to_chat(user, span_notice("You connect [parent] to [silo] from the multitool's buffer."))
|
||||
return ITEM_INTERACT_SUCCESS
|
||||
|
||||
/datum/component/remote_materials/proc/on_item_insert(datum/source, mob/living/user, obj/item/target)
|
||||
SIGNAL_HANDLER
|
||||
// Only insert stacks with left click
|
||||
if(isstack(target))
|
||||
return attempt_insert(user, target)
|
||||
|
||||
/datum/component/remote_materials/proc/on_secondary_insert(datum/source, mob/living/user, obj/item/target)
|
||||
SIGNAL_HANDLER
|
||||
return attempt_insert(user, target)
|
||||
|
||||
/// Insert mats into silo
|
||||
/datum/component/remote_materials/proc/attempt_insert(mob/living/user, obj/item/target)
|
||||
if(!(mat_container_flags & MATCONTAINER_ANY_INTENT) && user.combat_mode)
|
||||
return
|
||||
|
||||
if(silo)
|
||||
mat_container.user_insert(target, user, parent)
|
||||
return ITEM_INTERACT_SUCCESS
|
||||
|
||||
/**
|
||||
* Checks if the param silo is in the same level as this components parent i.e. connected machine, rcd, etc
|
||||
*
|
||||
* Arguments
|
||||
* silo_to_check- Is this components parent in the same Z level as this param silo. If null
|
||||
* then check this components connected silo
|
||||
*
|
||||
* Returns true if both are on the station or same z level
|
||||
*/
|
||||
/datum/component/remote_materials/proc/check_z_level(obj/silo_to_check = silo)
|
||||
if(isnull(silo_to_check))
|
||||
return FALSE
|
||||
|
||||
return is_valid_z_level(get_turf(silo_to_check), get_turf(parent))
|
||||
|
||||
/// returns TRUE if this connection put on hold by the silo
|
||||
/datum/component/remote_materials/proc/on_hold()
|
||||
return check_z_level() ? silo.holds[src] : FALSE
|
||||
|
||||
/**
|
||||
* Check if this connection can use any materials from the silo
|
||||
* Returns true only if
|
||||
* - The parent is of type movable atom
|
||||
* - A mat container is actually present
|
||||
* - The silo in not on hold
|
||||
* Arguments
|
||||
* * check_hold - should we check if the silo is on hold
|
||||
* * user_data - in the form rendered by ID_DATA(user); used as a reference for silo bans and access checks
|
||||
*/
|
||||
/datum/component/remote_materials/proc/can_use_resource(check_hold = TRUE, alist/user_data)
|
||||
var/atom/movable/movable_parent = parent
|
||||
if (!istype(movable_parent))
|
||||
return FALSE
|
||||
if (!mat_container) //no silolink & local storage not supported
|
||||
movable_parent.say("No access to material storage, please contact the quartermaster.")
|
||||
return FALSE
|
||||
if(check_hold && on_hold()) //silo on hold
|
||||
movable_parent.say("Mineral access is on hold, please contact the quartermaster.")
|
||||
return FALSE
|
||||
if(SEND_SIGNAL(movable_parent, COMSIG_ORE_SILO_PERMISSION_CHECKED, user_data, movable_parent) & COMPONENT_ORE_SILO_DENY)
|
||||
return FALSE
|
||||
return TRUE
|
||||
|
||||
/**
|
||||
* Use materials from either the silo(if connected) or from the local storage. If silo then this action
|
||||
* is logged else not e.g. action="build" & name="matter bin" means you are trying to build a matter bin
|
||||
*
|
||||
* Arguments
|
||||
* [mats][list]- list of materials to use
|
||||
* coefficient- each mat unit is scaled by this value then rounded. This value if usually your machine efficiency e.g. upgraded protolathe has reduced costs
|
||||
* multiplier- each mat unit is scaled by this value then rounded after it is scaled by coefficient. This value is your print quatity e.g. printing multiple items
|
||||
* action- For logging only. e.g. build, create, i.e. the action you are trying to perform
|
||||
* name- For logging only. the design you are trying to build e.g. matter bin, etc.
|
||||
* user_data - in the form rendered by ID_DATA(user), for material logging and (if this component is connected to a silo), permission checking
|
||||
*/
|
||||
/datum/component/remote_materials/proc/use_materials(list/mats, coefficient = 1, multiplier = 1, action = "processed", name = "design", alist/user_data)
|
||||
if(!can_use_resource(user_data = user_data))
|
||||
return 0
|
||||
|
||||
var/amount_consumed = mat_container.use_materials(mats, coefficient, multiplier)
|
||||
|
||||
if (silo)//log only if silo is linked
|
||||
var/list/scaled_mats = list()
|
||||
for(var/i in mats)
|
||||
scaled_mats[i] = OPTIMAL_COST(OPTIMAL_COST(mats[i] * coefficient) * multiplier)
|
||||
silo.silo_log(parent, action, -multiplier, name, scaled_mats, user_data)
|
||||
|
||||
return amount_consumed
|
||||
|
||||
/**
|
||||
* Ejects the given material ref and logs it
|
||||
*
|
||||
* Arguments
|
||||
* [material_ref][datum/material]- The material type you are trying to eject
|
||||
* eject_amount- how many sheets to eject
|
||||
* [drop_target][atom]- optional where to drop the sheets. null means it is dropped at this components parent location
|
||||
* user_data - in the form rendered by ID_DATA(user), for material logging and (if this component is connected to a silo), permission checking
|
||||
*/
|
||||
/datum/component/remote_materials/proc/eject_sheets(datum/material/material_ref, eject_amount, atom/drop_target = null, alist/user_data)
|
||||
if(!can_use_resource(user_data = user_data))
|
||||
return 0
|
||||
|
||||
var/atom/movable/movable_parent = parent
|
||||
if(isnull(drop_target))
|
||||
drop_target = movable_parent.drop_location()
|
||||
|
||||
return mat_container.retrieve_stack(eject_amount, material_ref, target = drop_target, context = parent, user_data = user_data)
|
||||
|
||||
/**
|
||||
* Insert an item into the mat container, helper proc to insert items with the correct context
|
||||
*
|
||||
* Arguments
|
||||
* * obj/item/weapon - the item you are trying to insert
|
||||
* * multiplier - the multiplier applied on the materials consumed
|
||||
* * user_data - an alist in the form rendered by ID_DATA(user), for logging who/where/when the item was inserted
|
||||
*/
|
||||
/datum/component/remote_materials/proc/insert_item(obj/item/weapon, multiplier = 1, alist/user_data)
|
||||
// Inserting materials automatically shouldn't be permission-restricted
|
||||
if(!islist(user_data))
|
||||
user_data = ID_DATA(null)
|
||||
user_data[SILICON_OVERRIDE] = SILICON_OVERRIDE
|
||||
if(!can_use_resource(FALSE, user_data))
|
||||
return MATERIAL_INSERT_ITEM_FAILURE
|
||||
|
||||
return mat_container.insert_item(weapon, multiplier, parent, user_data = user_data)
|
||||
@@ -5,9 +5,10 @@
|
||||
* and call set_painting_tool_color() on the parent for more specific object behavior.
|
||||
*/
|
||||
/datum/component/palette
|
||||
/// The maximum number of colors this palette can have.
|
||||
var/max_colors
|
||||
/*
|
||||
* A list that stores a selection of colors.
|
||||
* The number of available spaces is defined by the available_space arg of Initialize()
|
||||
*/
|
||||
var/list/colors = list()
|
||||
/*
|
||||
@@ -20,25 +21,20 @@
|
||||
/// The radial menu choice datums are stored here as a microop to avoid generating new ones every time the menu is opened or updated.
|
||||
var/list/datum/radial_menu_choice/menu_choices
|
||||
|
||||
/datum/component/palette/Initialize(available_space, selected_color)
|
||||
/datum/component/palette/Initialize(max_colors, selected_color)
|
||||
if(!isitem(parent))
|
||||
return COMPONENT_INCOMPATIBLE
|
||||
|
||||
if(!isnum(available_space) || available_space < 1) /// This component means nothing if there's no space for colors
|
||||
stack_trace("palette component initialized without a proper value for the available_space arg")
|
||||
if(!isnum(max_colors) || max_colors < 1) /// This component means nothing if there's no space for colors
|
||||
stack_trace("palette component initialized without a proper value for the max_colors arg")
|
||||
return COMPONENT_INCOMPATIBLE
|
||||
|
||||
for(var/index in 1 to available_space)
|
||||
colors += "#ffffff"
|
||||
|
||||
src.colors = colors
|
||||
src.max_colors = max_colors
|
||||
src.selected_color = selected_color || "#ffffff"
|
||||
|
||||
RegisterSignal(parent, COMSIG_ITEM_ATTACK_SELF_SECONDARY, PROC_REF(on_attack_self_secondary))
|
||||
RegisterSignal(parent, COMSIG_ATOM_EXAMINE, PROC_REF(on_examine))
|
||||
RegisterSignal(parent, COMSIG_PAINTING_TOOL_SET_COLOR, PROC_REF(on_painting_tool_set_color))
|
||||
RegisterSignal(parent, COMSIG_PAINTING_TOOL_GET_ADDITIONAL_DATA, PROC_REF(get_palette_data))
|
||||
RegisterSignal(parent, COMSIG_PAINTING_TOOL_PALETTE_COLOR_CHANGED, PROC_REF(palette_color_changed))
|
||||
|
||||
/datum/component/palette/Destroy()
|
||||
QDEL_NULL(color_picker_menu)
|
||||
@@ -51,7 +47,7 @@
|
||||
SIGNAL_HANDLER
|
||||
|
||||
examine_list += span_notice("<b>Right-Click</b> this item while it's in your active hand to open/close its color picker menu.")
|
||||
examine_list += span_notice("In the color picker, <b>Left-Click</b> a color button to pick it or <b>Right-Click</b> to edit it.")
|
||||
examine_list += span_notice("In the color picker, <b>Left-Click</b> a color button to pick it or <b>Right-Click</b> to remove it.")
|
||||
|
||||
/datum/component/palette/proc/on_attack_self_secondary(datum/source, mob/user)
|
||||
SIGNAL_HANDLER
|
||||
@@ -72,12 +68,27 @@
|
||||
|
||||
/datum/component/palette/proc/build_radial_list()
|
||||
var/radial_list = list()
|
||||
LAZYSETLEN(menu_choices, length(colors))
|
||||
var/color_count = length(colors)
|
||||
LAZYSETLEN(menu_choices, max(color_count+1))
|
||||
if(color_count < max_colors && !(selected_color in colors))
|
||||
var/datum/radial_menu_choice/add_option = peek(menu_choices)
|
||||
if(!add_option)
|
||||
add_option = new
|
||||
menu_choices[color_count+1] = add_option
|
||||
var/image/element = image(icon = 'icons/hud/radial.dmi', icon_state = "palette_element")
|
||||
element.color = selected_color
|
||||
var/image/plus = image(icon = 'icons/hud/radial.dmi', icon_state = "palette_add")
|
||||
plus.appearance_flags = /image::appearance_flags | RESET_COLOR
|
||||
element.add_overlay(plus)
|
||||
add_option.image = element
|
||||
add_option.name = "Add Color ([selected_color])"
|
||||
radial_list["add"] = add_option
|
||||
for(var/index in 1 to length(colors))
|
||||
var/hexcolor = colors[index]
|
||||
var/datum/radial_menu_choice/option = menu_choices[index]
|
||||
if(!option)
|
||||
option = new
|
||||
menu_choices[index] = option
|
||||
var/icon_state_to_use = hexcolor == selected_color ? "palette_selected" : "palette_element"
|
||||
var/image/element = image(icon = 'icons/hud/radial.dmi', icon_state = icon_state_to_use)
|
||||
element.color = hexcolor
|
||||
@@ -104,11 +115,14 @@
|
||||
close_radial_menu()
|
||||
return
|
||||
var/is_right_clicking = LAZYACCESS(params2list(params), RIGHT_CLICK)
|
||||
if(choice == "add")
|
||||
if(length(colors) < max_colors)
|
||||
colors += selected_color
|
||||
update_radial_list()
|
||||
return
|
||||
var/index = text2num(choice)
|
||||
if(is_right_clicking)
|
||||
var/chosen_color = tgui_color_picker(user, "Pick new color", "[parent]", colors[index]) // BUBBERSTATION EDIT: TGUI COLOR PICKER
|
||||
if(chosen_color && !QDELETED(src) && !IS_DEAD_OR_INCAP(user) && user.is_holding(parent))
|
||||
colors[index] = chosen_color
|
||||
colors.Cut(index, index+1)
|
||||
update_radial_list()
|
||||
else
|
||||
var/obj/item/parent_item = parent
|
||||
@@ -119,24 +133,3 @@
|
||||
|
||||
selected_color = chosen_color
|
||||
update_radial_list()
|
||||
|
||||
/datum/component/palette/proc/get_palette_data(datum/source, data)
|
||||
SIGNAL_HANDLER
|
||||
var/list/painting_data = list()
|
||||
for(var/hexcolor in colors)
|
||||
painting_data += list(list(
|
||||
"color" = hexcolor,
|
||||
"is_selected" = hexcolor == selected_color
|
||||
))
|
||||
data["paint_tool_palette"] = painting_data
|
||||
|
||||
/datum/component/palette/proc/palette_color_changed(datum/source, chosen_color, index)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
var/was_selected_color = selected_color == colors[index]
|
||||
colors[index] = chosen_color
|
||||
if(was_selected_color)
|
||||
var/obj/item/parent_item = parent
|
||||
parent_item.set_painting_tool_color(chosen_color)
|
||||
else
|
||||
update_radial_list()
|
||||
|
||||
@@ -116,7 +116,8 @@
|
||||
var/original_speed = shell.loaded_projectile.speed
|
||||
var/original_wounds_bonus = shell.loaded_projectile.wound_bonus
|
||||
var/original_bare_wounds_bonus = shell.loaded_projectile.exposed_wound_bonus
|
||||
var/original_ignored_faction = shell.loaded_projectile.ignored_factions
|
||||
var/original_ignored_faction = shell.loaded_projectile.get_faction()
|
||||
var/original_ignored_allies = shell.loaded_projectile.allies
|
||||
|
||||
for(var/i in 1 to num_pellets)
|
||||
shell.ready_proj(target, user, SUPPRESSED_VERY, zone_override, fired_from)
|
||||
@@ -133,7 +134,8 @@
|
||||
shell.loaded_projectile.speed = original_speed
|
||||
shell.loaded_projectile.wound_bonus = original_wounds_bonus
|
||||
shell.loaded_projectile.exposed_wound_bonus = original_bare_wounds_bonus
|
||||
shell.loaded_projectile.ignored_factions = original_ignored_faction
|
||||
shell.loaded_projectile.set_faction(original_ignored_faction)
|
||||
shell.loaded_projectile.set_allies(original_ignored_allies)
|
||||
pellets += shell.loaded_projectile
|
||||
var/turf/current_loc = get_turf(fired_from)
|
||||
if (!istype(target_loc) || !istype(current_loc) || !(shell.loaded_projectile))
|
||||
|
||||
@@ -1,150 +1,178 @@
|
||||
/datum/component/plumbing
|
||||
dupe_mode = COMPONENT_DUPE_ALLOWED
|
||||
///Index with "1" = /datum/ductnet/theductpointingnorth etc. "1" being the num2text from NORTH define
|
||||
var/list/datum/ductnet/ducts = list()
|
||||
///shortcut to our parents' reagent holder
|
||||
var/list/datum/ductnet/ducts
|
||||
///shortcut to our parents' reagent holder. The holder that sends reagents into the pipeline
|
||||
var/datum/reagents/reagents
|
||||
///TRUE if we wanna add proper pipe overlays under our parent object. this is pretty good if i may so so myself
|
||||
var/use_overlays = TRUE
|
||||
///Whether our tile is covered and we should hide our ducts
|
||||
var/tile_covered = FALSE
|
||||
///directions in wich we act as a supplier
|
||||
var/supply_connects
|
||||
var/supply_connects = NONE
|
||||
///direction in wich we act as a demander
|
||||
var/demand_connects
|
||||
///FALSE to pretty much just not exist in the plumbing world so we can be moved, TRUE to go plumbo mode
|
||||
var/active = FALSE
|
||||
///if TRUE connects will spin with the parent object visually and codually, so you can have it work in any direction. FALSE if you want it to be static
|
||||
var/turn_connects = TRUE
|
||||
var/demand_connects = NONE
|
||||
///The layer on which we connect. Don't add multiple. If you want multiple layer connects for some reason you can just add multiple components with different layers
|
||||
var/ducting_layer = DUCT_LAYER_DEFAULT
|
||||
///In-case we don't want the main machine to get the reagents, but perhaps whoever is buckled to it
|
||||
var/recipient_reagents_holder
|
||||
///What color is our demand connect?
|
||||
var/demand_color = COLOR_RED
|
||||
///What color is our supply connect?
|
||||
var/supply_color = COLOR_BLUE
|
||||
///Extend the pipe to the edge for wall-mounted plumbed devices, like sinks and showers
|
||||
var/extend_pipe_to_edge = FALSE
|
||||
/// How many distinct reagents can we accept at once
|
||||
/// Ex - if this was set to "3", our component would only request the first 3 reagents found, even if more are available
|
||||
var/distinct_reagent_cap = INFINITY
|
||||
|
||||
///turn_connects is for wheter or not we spin with the object to change our pipes
|
||||
/datum/component/plumbing/Initialize(start=TRUE, ducting_layer, turn_connects=TRUE, datum/reagents/custom_receiver, extend_pipe_to_edge = FALSE)
|
||||
/datum/component/plumbing/Initialize(ducting_layer)
|
||||
if(!ismovable(parent))
|
||||
return COMPONENT_INCOMPATIBLE
|
||||
|
||||
if(ducting_layer)
|
||||
src.ducting_layer = ducting_layer
|
||||
|
||||
var/atom/movable/parent_movable = parent
|
||||
if(!parent_movable.reagents && !custom_receiver)
|
||||
if(!parent_movable.reagents)
|
||||
return COMPONENT_INCOMPATIBLE
|
||||
|
||||
if(GLOB.plumbing_layer_names["[ducting_layer]"])
|
||||
src.ducting_layer = ducting_layer
|
||||
|
||||
ducts = list()
|
||||
|
||||
reagents = parent_movable.reagents
|
||||
src.turn_connects = turn_connects
|
||||
src.extend_pipe_to_edge = extend_pipe_to_edge
|
||||
|
||||
set_recipient_reagents_holder(custom_receiver ? custom_receiver : parent_movable.reagents)
|
||||
|
||||
if(start)
|
||||
on_parent_dir_change(parent_movable, NONE, parent_movable.dir)
|
||||
if(parent_movable.anchored)
|
||||
if(PERFORM_ALL_TESTS(maptest_log_mapping))
|
||||
var/datum/overlap = ducting_layer_check(parent_movable, ducting_layer)
|
||||
if(!isnull(overlap))
|
||||
var/message = GLOB.plumbing_layer_names["[ducting_layer]"]
|
||||
if(istype(overlap, /obj/machinery/duct))
|
||||
message = "plumbing duct on [message]"
|
||||
else
|
||||
message = "plumbing machine on [message]"
|
||||
log_mapping("Overlapping [message] detected at [AREACOORD(parent_movable)]")
|
||||
parent_movable.set_anchored(FALSE)
|
||||
return
|
||||
enable()
|
||||
|
||||
/datum/component/plumbing/RegisterWithParent()
|
||||
RegisterSignals(parent, list(COMSIG_MOVABLE_MOVED, COMSIG_QDELETING), PROC_REF(disable))
|
||||
RegisterSignals(parent, list(COMSIG_OBJ_DEFAULT_UNFASTEN_WRENCH), PROC_REF(toggle_active))
|
||||
RegisterSignal(parent, COMSIG_ATOM_TOOL_ACT(TOOL_WRENCH), PROC_REF(check_wrench))
|
||||
RegisterSignal(parent, COMSIG_MOVABLE_SET_ANCHORED, PROC_REF(toggle_active))
|
||||
RegisterSignal(parent, COMSIG_OBJ_HIDE, PROC_REF(hide))
|
||||
RegisterSignal(parent, COMSIG_ATOM_UPDATE_OVERLAYS, PROC_REF(create_overlays)) //called by lateinit on startup
|
||||
RegisterSignal(parent, COMSIG_ATOM_DIR_CHANGE, PROC_REF(on_parent_dir_change)) //called when placed on a shuttle and it moves, and other edge cases
|
||||
RegisterSignal(parent, COMSIG_ATOM_UPDATE_OVERLAYS, PROC_REF(create_overlays))
|
||||
RegisterSignal(parent, COMSIG_ATOM_POST_DIR_CHANGE, PROC_REF(on_parent_dir_change))
|
||||
RegisterSignal(parent, COMSIG_MOVABLE_CHANGE_DUCT_LAYER, PROC_REF(change_ducting_layer))
|
||||
RegisterSignal(parent, COMSIG_ATOM_EXAMINE, PROC_REF(on_examine))
|
||||
|
||||
/datum/component/plumbing/UnregisterFromParent()
|
||||
UnregisterSignal(parent, list(COMSIG_MOVABLE_MOVED, COMSIG_QDELETING, COMSIG_OBJ_DEFAULT_UNFASTEN_WRENCH, COMSIG_OBJ_HIDE, \
|
||||
COMSIG_ATOM_UPDATE_OVERLAYS, COMSIG_ATOM_DIR_CHANGE, COMSIG_MOVABLE_CHANGE_DUCT_LAYER))
|
||||
REMOVE_TRAIT(parent, TRAIT_UNDERFLOOR, REF(src))
|
||||
UnregisterSignal(parent, list(
|
||||
COMSIG_ATOM_TOOL_ACT(TOOL_WRENCH),
|
||||
COMSIG_MOVABLE_SET_ANCHORED,
|
||||
COMSIG_OBJ_HIDE,
|
||||
COMSIG_ATOM_UPDATE_OVERLAYS,
|
||||
COMSIG_ATOM_POST_DIR_CHANGE,
|
||||
COMSIG_MOVABLE_CHANGE_DUCT_LAYER,
|
||||
COMSIG_ATOM_EXAMINE,
|
||||
))
|
||||
|
||||
/datum/component/plumbing/Destroy()
|
||||
ducts = null
|
||||
disable()
|
||||
ducts.Cut()
|
||||
reagents = null
|
||||
set_recipient_reagents_holder(null) //null is there so it's obvious we're setting this to nothing
|
||||
return ..()
|
||||
|
||||
/datum/component/plumbing/process()
|
||||
if(!demand_connects || !reagents)
|
||||
return PROCESS_KILL
|
||||
///Returns if the machine is active or not
|
||||
/datum/component/plumbing/proc/active()
|
||||
var/atom/movable/parent_movable = parent
|
||||
return parent_movable.anchored
|
||||
|
||||
if(!reagents.holder_full())
|
||||
for(var/D in GLOB.cardinals)
|
||||
if(D & demand_connects)
|
||||
send_request(D)
|
||||
///Returns the reagent holder meant to receive the reagents. Can be different from the one that sends reagents to the network
|
||||
/datum/component/plumbing/proc/recipient_reagents_holder()
|
||||
return reagents
|
||||
|
||||
///Can we be added to the ductnet?
|
||||
/datum/component/plumbing/proc/can_add(datum/ductnet/ductnet, dir)
|
||||
if(!active)
|
||||
return
|
||||
if(!dir || !ductnet)
|
||||
return FALSE
|
||||
if(num2text(dir) in ducts)
|
||||
return FALSE
|
||||
///Give the direction of a pipe, and it'll return wich direction it originally was when its object pointed SOUTH
|
||||
/datum/component/plumbing/proc/get_original_direction(dir)
|
||||
if(!dir)
|
||||
return 0
|
||||
var/atom/movable/parent_movable = parent
|
||||
return turn(dir, dir2angle(parent_movable.dir) - 180)
|
||||
|
||||
return TRUE
|
||||
///settle wherever we are, and start behaving like a piece of plumbing
|
||||
/datum/component/plumbing/proc/enable()
|
||||
if(demand_connects)
|
||||
START_PROCESSING(SSplumbing, src)
|
||||
|
||||
///called from in process(). only calls process_request(), but can be overwritten for children with special behaviour
|
||||
/datum/component/plumbing/proc/send_request(dir)
|
||||
process_request(dir = dir)
|
||||
for(var/direction in GLOB.cardinals)
|
||||
if(!(direction & (demand_connects | supply_connects)))
|
||||
continue
|
||||
|
||||
///check who can give us what we want, and how many each of them will give us
|
||||
/datum/component/plumbing/proc/process_request(amount = MACHINE_REAGENT_TRANSFER, reagent, dir, round_robin = TRUE)
|
||||
//find the duct to take from
|
||||
var/datum/ductnet/net
|
||||
if(!ducts.Find(num2text(dir)))
|
||||
return FALSE
|
||||
net = ducts[num2text(dir)]
|
||||
var/opposite_dir = REVERSE_DIR(direction)
|
||||
for(var/atom/movable/found_atom in get_step(parent, direction))
|
||||
var/obj/machinery/duct/duct = found_atom
|
||||
if(istype(duct))
|
||||
if(duct.neighbours && (duct.duct_layer & ducting_layer))
|
||||
duct.neighbours[parent] = opposite_dir
|
||||
duct.update_appearance(UPDATE_ICON_STATE)
|
||||
duct.net.add_plumber(src, direction)
|
||||
continue
|
||||
|
||||
//find all valid suppliers in the duct
|
||||
var/list/valid_suppliers = list()
|
||||
for(var/datum/component/plumbing/supplier as anything in net.suppliers)
|
||||
if(supplier.can_give(amount, reagent, net))
|
||||
valid_suppliers += supplier
|
||||
var/suppliersLeft = valid_suppliers.len
|
||||
if(!suppliersLeft)
|
||||
return FALSE
|
||||
for(var/datum/component/plumbing/plumber as anything in found_atom.GetComponents(/datum/component/plumbing))
|
||||
if(plumber.active() && (plumber.ducting_layer & ducting_layer))
|
||||
if((plumber.demand_connects & opposite_dir) && (supply_connects & direction) || (plumber.supply_connects & opposite_dir) && (demand_connects & direction)) //make sure we arent connecting two supplies or demands
|
||||
var/datum/ductnet/net = new
|
||||
net.add_plumber(src, direction)
|
||||
net.add_plumber(plumber, opposite_dir)
|
||||
|
||||
//take an equal amount from each supplier
|
||||
var/currentRequest
|
||||
var/target_volume = reagents.total_volume + amount
|
||||
for(var/datum/component/plumbing/give as anything in valid_suppliers)
|
||||
currentRequest = (target_volume - reagents.total_volume) / suppliersLeft
|
||||
give.transfer_to(src, currentRequest, reagent, net, round_robin)
|
||||
suppliersLeft--
|
||||
return TRUE
|
||||
/datum/component/plumbing/proc/disable()
|
||||
SIGNAL_HANDLER
|
||||
|
||||
///returns TRUE when they can give the specified amount and reagent. called by process request
|
||||
/datum/component/plumbing/proc/can_give(amount, reagent, datum/ductnet/net)
|
||||
SHOULD_BE_PURE(TRUE)
|
||||
STOP_PROCESSING(SSplumbing, src)
|
||||
|
||||
if(amount <= 0)
|
||||
return
|
||||
while(ducts.len)
|
||||
var/datum/ductnet/net = ducts[ducts[1]]
|
||||
|
||||
if(reagent) //only asked for one type of reagent
|
||||
for(var/datum/reagent/contained_reagent as anything in reagents.reagent_list)
|
||||
if(contained_reagent.type == reagent)
|
||||
return TRUE
|
||||
else if(reagents.total_volume) //take whatever
|
||||
return TRUE
|
||||
//disconnect ourself from any ducts connected to us
|
||||
for(var/obj/machinery/duct/pipe as anything in net.ducts)
|
||||
if(pipe.neighbours[parent])
|
||||
pipe.neighbours -= parent
|
||||
pipe.update_appearance(UPDATE_ICON_STATE)
|
||||
|
||||
return FALSE
|
||||
//remove ourself from this network and delete it if emtpy
|
||||
if(net.remove_plumber(src))
|
||||
qdel(net)
|
||||
|
||||
///this is where the reagent is actually transferred and is thus the finish point of our process()
|
||||
/datum/component/plumbing/proc/transfer_to(datum/component/plumbing/target, amount, reagent, datum/ductnet/net, round_robin = TRUE)
|
||||
if(!reagents || !target || !target.reagents)
|
||||
return FALSE
|
||||
/datum/component/plumbing/proc/check_wrench(obj/parent_obj, mob/user, tool, processing_recipes)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
reagents.trans_to(target.recipient_reagents_holder, amount, target_id = reagent, methods = round_robin ? LINEAR : NONE)
|
||||
if(!active())
|
||||
var/datum/overlap = ducting_layer_check(parent_obj, -ducting_layer)
|
||||
if(!isnull(overlap))
|
||||
parent_obj.balloon_alert(user, "overlapping [istype(overlap, /obj/machinery/duct) ? "duct" : "machine"] detected!")
|
||||
return ITEM_INTERACT_FAILURE
|
||||
|
||||
/datum/component/plumbing/proc/toggle_active(obj/parent_obj, new_state)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
// Follow atmos's rule of exposing the connection if you unwrench it and only hiding again if tile is placed back down.
|
||||
if(tile_covered)
|
||||
tile_covered = FALSE
|
||||
parent_obj.update_appearance()
|
||||
|
||||
if(new_state)
|
||||
enable()
|
||||
else
|
||||
disable()
|
||||
|
||||
/datum/component/plumbing/proc/hide(atom/movable/parent_obj, underfloor_accessibility)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
var/atom/movable/parent_movable = parent
|
||||
// If machine is unanchored, keep connector visible.
|
||||
// This doesn't necessary map to `active`, so check parent.
|
||||
var/should_hide = !underfloor_accessibility
|
||||
if(parent_movable.anchored || !should_hide)
|
||||
tile_covered = should_hide
|
||||
parent_obj.update_appearance()
|
||||
|
||||
///We create our luxurious piping overlays/underlays, to indicate where we do what. only called once if use_overlays = TRUE in Initialize()
|
||||
/datum/component/plumbing/proc/create_overlays(atom/movable/parent_movable, list/overlays)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
if(tile_covered || !use_overlays)
|
||||
if(tile_covered)
|
||||
return
|
||||
|
||||
//Copied from ducts handle_layer()
|
||||
@@ -165,7 +193,6 @@
|
||||
var/duct_x = offset - parent_movable.pixel_x - parent_movable.pixel_w
|
||||
var/duct_y = offset - parent_movable.pixel_y - parent_movable.pixel_z
|
||||
var/duct_layer = PLUMBING_PIPE_VISIBILE_LAYER + ducting_layer * 0.0003
|
||||
var/extension_handled = FALSE
|
||||
|
||||
for(var/direction in GLOB.cardinals)
|
||||
var/color
|
||||
@@ -176,203 +203,138 @@
|
||||
else
|
||||
continue
|
||||
|
||||
var/direction_text = dir2text(direction)
|
||||
|
||||
var/image/overlay
|
||||
if(turn_connects)
|
||||
overlay = image('icons/obj/pipes_n_cables/hydrochem/connects.dmi', "[direction_text]-[ducting_layer]", layer = duct_layer)
|
||||
else
|
||||
overlay = image('icons/obj/pipes_n_cables/hydrochem/connects.dmi', "[direction_text]-[ducting_layer]-s", layer = duct_layer)
|
||||
overlay.dir = direction
|
||||
|
||||
var/image/overlay = image('icons/obj/pipes_n_cables/hydrochem/connects.dmi', "[dir2text(direction)]-[ducting_layer]", layer = duct_layer)
|
||||
overlay.color = color
|
||||
overlay.pixel_w = duct_x
|
||||
overlay.pixel_z = duct_y
|
||||
|
||||
overlays += overlay
|
||||
|
||||
// This is a little wiggley extension to make wallmounts like sinks and showers visually link to the pipe
|
||||
if(extend_pipe_to_edge && !extension_handled)
|
||||
var/image/edge_overlay = image('icons/obj/pipes_n_cables/hydrochem/connects.dmi', "edge-extension", layer = duct_layer)
|
||||
edge_overlay.dir = parent_movable.dir
|
||||
edge_overlay.color = color
|
||||
edge_overlay.pixel_w = -parent_movable.pixel_x - parent_movable.pixel_w
|
||||
edge_overlay.pixel_z = -parent_movable.pixel_y - parent_movable.pixel_z
|
||||
overlays += edge_overlay
|
||||
// only show extension for the first pipe. This means we'll only reflect that color.
|
||||
extension_handled = TRUE
|
||||
|
||||
///we stop acting like a plumbing thing and disconnect if we are, so we can safely be moved and stuff
|
||||
/datum/component/plumbing/proc/disable()
|
||||
/datum/component/plumbing/proc/on_parent_dir_change(atom/movable/parent_obj, old_dir, new_dir)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
if(!active)
|
||||
return
|
||||
|
||||
STOP_PROCESSING(SSplumbing, src)
|
||||
|
||||
//remove_plumber() can remove all ducts at once if they all belong to the same pipenet
|
||||
//for e.g. in case of circular connections
|
||||
//so we check if we have ducts to remove after each iteration
|
||||
while(ducts.len)
|
||||
var/datum/ductnet/duct = ducts[ducts[1]] //for maps index 1 will return the 1st key
|
||||
duct.remove_plumber(src)
|
||||
|
||||
active = FALSE
|
||||
|
||||
for(var/direction in GLOB.cardinals)
|
||||
if(!(direction & (demand_connects | supply_connects)))
|
||||
continue
|
||||
for(var/obj/machinery/duct/duct in get_step(parent, direction))
|
||||
if(!(duct.duct_layer & ducting_layer))
|
||||
continue
|
||||
duct.remove_connects(REVERSE_DIR(direction))
|
||||
duct.neighbours.Remove(parent)
|
||||
duct.update_appearance()
|
||||
|
||||
///settle wherever we are, and start behaving like a piece of plumbing
|
||||
/datum/component/plumbing/proc/enable()
|
||||
if(active)
|
||||
return
|
||||
|
||||
update_dir()
|
||||
active = TRUE
|
||||
|
||||
var/atom/movable/parent_movable = parent
|
||||
// Destroy any ducts under us on the same layer.
|
||||
// Ducts also self-destruct if placed under a plumbing machine.
|
||||
// Machines disable when they get moved
|
||||
for(var/obj/machinery/duct/duct in parent_movable.loc)
|
||||
if(duct.anchored && (duct.duct_layer & ducting_layer))
|
||||
log_mapping("[duct.name] was found at [AREACOORD(parent_movable)] spawning on top of [parent_movable.name], \
|
||||
which should not occur as it takes ducts itself!")
|
||||
duct.disconnect_duct()
|
||||
|
||||
if(demand_connects)
|
||||
START_PROCESSING(SSplumbing, src)
|
||||
|
||||
for(var/direction in GLOB.cardinals)
|
||||
if(!(direction & (demand_connects | supply_connects)))
|
||||
continue
|
||||
for(var/atom/movable/found_atom in get_step(parent, direction))
|
||||
if(istype(found_atom, /obj/machinery/duct))
|
||||
var/obj/machinery/duct/duct = found_atom
|
||||
duct.attempt_connect()
|
||||
continue
|
||||
|
||||
for(var/datum/component/plumbing/plumber as anything in found_atom.GetComponents(/datum/component/plumbing))
|
||||
if(plumber.ducting_layer & ducting_layer)
|
||||
direct_connect(plumber, direction)
|
||||
|
||||
/// Toggle our machinery on or off. This is called by a hook from default_unfasten_wrench with anchored as only param, so we dont have to copypaste this on every object that can move
|
||||
/datum/component/plumbing/proc/toggle_active(obj/parent_obj, new_state)
|
||||
SIGNAL_HANDLER
|
||||
// Follow atmos's rule of exposing the connection if you unwrench it and only hiding again if tile is placed back down.
|
||||
if(tile_covered)
|
||||
tile_covered = FALSE
|
||||
parent_obj.update_appearance()
|
||||
|
||||
if(new_state)
|
||||
enable()
|
||||
else
|
||||
disable()
|
||||
|
||||
/** We update our connects only when we settle down by taking our current and original direction to find our new connects
|
||||
* If someone wants it to fucking spin while connected to something go actually knock yourself out
|
||||
*/
|
||||
/datum/component/plumbing/proc/update_dir()
|
||||
if(!turn_connects)
|
||||
return
|
||||
|
||||
var/atom/movable/AM = parent
|
||||
var/new_demand_connects
|
||||
var/new_supply_connects
|
||||
var/new_dir = AM.dir
|
||||
var/angle = 180 - dir2angle(new_dir)
|
||||
|
||||
if(new_dir == SOUTH)
|
||||
demand_connects = initial(demand_connects)
|
||||
supply_connects = initial(supply_connects)
|
||||
else
|
||||
demand_connects = initial(demand_connects)
|
||||
supply_connects = initial(supply_connects)
|
||||
if(new_dir != SOUTH)
|
||||
var/angle = 180 - dir2angle(new_dir)
|
||||
var/new_demand_connects = NONE
|
||||
var/new_supply_connects = NONE
|
||||
for(var/direction in GLOB.cardinals)
|
||||
if(direction & initial(demand_connects))
|
||||
new_demand_connects += turn(direction, angle)
|
||||
if(direction & initial(supply_connects))
|
||||
new_supply_connects += turn(direction, angle)
|
||||
if(direction & demand_connects)
|
||||
new_demand_connects |= turn(direction, angle)
|
||||
if(direction & supply_connects)
|
||||
new_supply_connects |= turn(direction, angle)
|
||||
demand_connects = new_demand_connects
|
||||
supply_connects = new_supply_connects
|
||||
|
||||
///Give the direction of a pipe, and it'll return wich direction it originally was when its object pointed SOUTH
|
||||
/datum/component/plumbing/proc/get_original_direction(dir)
|
||||
var/atom/movable/parent_movable = parent
|
||||
return turn(dir, dir2angle(parent_movable.dir) - 180)
|
||||
|
||||
//special case in-case we want to connect directly with another machine without a duct
|
||||
/datum/component/plumbing/proc/direct_connect(datum/component/plumbing/plumbing, dir)
|
||||
if(!plumbing.active)
|
||||
return
|
||||
var/opposite_dir = REVERSE_DIR(dir)
|
||||
if(plumbing.demand_connects & opposite_dir && supply_connects & dir || plumbing.supply_connects & opposite_dir && demand_connects & dir) //make sure we arent connecting two supplies or demands
|
||||
var/datum/ductnet/net = new()
|
||||
net.add_plumber(src, dir)
|
||||
net.add_plumber(plumbing, opposite_dir)
|
||||
|
||||
/datum/component/plumbing/proc/hide(atom/movable/parent_obj, underfloor_accessibility)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
// If machine is unanchored, keep connector visible.
|
||||
// This doesn't necessary map to `active`, so check parent.
|
||||
var/atom/movable/parent_movable = parent
|
||||
|
||||
var/should_hide = !underfloor_accessibility
|
||||
|
||||
if(should_hide)
|
||||
ADD_TRAIT(parent_obj, TRAIT_UNDERFLOOR, REF(src))
|
||||
else
|
||||
REMOVE_TRAIT(parent_obj, TRAIT_UNDERFLOOR, REF(src))
|
||||
|
||||
if(parent_movable.anchored || !should_hide)
|
||||
tile_covered = should_hide
|
||||
parent_obj.update_appearance()
|
||||
if(length(ducts))
|
||||
disable()
|
||||
enable()
|
||||
|
||||
/datum/component/plumbing/proc/change_ducting_layer(obj/source, obj/changer, new_layer = DUCT_LAYER_DEFAULT)
|
||||
SIGNAL_HANDLER
|
||||
ducting_layer = new_layer
|
||||
|
||||
var/atom/movable/parent_movable = parent
|
||||
parent_movable.update_appearance()
|
||||
ducting_layer = new_layer
|
||||
source.update_appearance(UPDATE_OVERLAYS)
|
||||
|
||||
if(changer)
|
||||
playsound(changer, 'sound/items/tools/ratchet.ogg', 10, TRUE) //sound
|
||||
|
||||
//quickly disconnect and reconnect the network.
|
||||
if(active)
|
||||
if(length(ducts))
|
||||
disable()
|
||||
enable()
|
||||
|
||||
/datum/component/plumbing/proc/set_recipient_reagents_holder(datum/reagents/receiver)
|
||||
if(recipient_reagents_holder)
|
||||
UnregisterSignal(recipient_reagents_holder, COMSIG_QDELETING) //stop tracking whoever we were tracking
|
||||
if(receiver)
|
||||
RegisterSignal(receiver, COMSIG_QDELETING, PROC_REF(handle_reagent_del)) //on deletion call a wrapper proc that clears us, and maybe reagents too
|
||||
/datum/component/plumbing/process()
|
||||
if(!demand_connects)
|
||||
return PROCESS_KILL
|
||||
|
||||
recipient_reagents_holder = receiver
|
||||
var/datum/reagents/receiver = recipient_reagents_holder()
|
||||
if(QDELETED(receiver))
|
||||
return PROCESS_KILL
|
||||
|
||||
/datum/component/plumbing/proc/handle_reagent_del(datum/source)
|
||||
SIGNAL_HANDLER
|
||||
if(source == reagents)
|
||||
reagents = null
|
||||
if(source == recipient_reagents_holder)
|
||||
set_recipient_reagents_holder(null)
|
||||
if(!receiver.holder_full())
|
||||
for(var/dir in GLOB.cardinals)
|
||||
if(dir & demand_connects)
|
||||
send_request(dir)
|
||||
|
||||
/**
|
||||
* Called when the dir changes. Need to adjust positioning of pipes.
|
||||
*/
|
||||
/datum/component/plumbing/proc/on_parent_dir_change(atom/movable/parent_obj, old_dir, new_dir)
|
||||
/datum/component/plumbing/proc/on_examine(atom/movable/source, mob/user, list/examine_list)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
if(old_dir == new_dir)
|
||||
if(distinct_reagent_cap != INFINITY)
|
||||
examine_list += span_notice("This plumbing component will only accept up to [distinct_reagent_cap] distinct reagents at once.")
|
||||
|
||||
///called from in process(). only calls process_request(), but can be overwritten for children with special behaviour
|
||||
/datum/component/plumbing/proc/send_request(dir)
|
||||
var/amount_to_give = MACHINE_REAGENT_TRANSFER
|
||||
// infinite cap means we need to special handling, process_request will just grab as much as it wants.
|
||||
if(distinct_reagent_cap == INFINITY)
|
||||
process_request(amount_to_give, null, dir) // null for no specific reagent, we're not picky.
|
||||
return
|
||||
|
||||
// Defer to later frame because pixel_* is actually updated after all callbacks
|
||||
addtimer(CALLBACK(parent_obj, TYPE_PROC_REF(/atom/, update_appearance)), 0.1 SECONDS)
|
||||
// we have a cap, so we need to figure out what reagents we want
|
||||
var/list/all_allowed_reagents = get_all_network_reagents(ducts["[dir]"])
|
||||
if(length(all_allowed_reagents) > distinct_reagent_cap)
|
||||
all_allowed_reagents.Cut(distinct_reagent_cap + 1)
|
||||
else if(!length(all_allowed_reagents))
|
||||
return
|
||||
|
||||
// request an even amount of each allowed reagent
|
||||
var/amount_per_reagent = round(amount_to_give / length(all_allowed_reagents), CHEMICAL_VOLUME_ROUNDING)
|
||||
for(var/allowed_reagent in all_allowed_reagents)
|
||||
process_request(amount_per_reagent, allowed_reagent, dir)
|
||||
|
||||
/// Returns a list of all distinct reagent types available in the passed duct network.
|
||||
/// The passed net can be null, it is handled.
|
||||
/datum/component/plumbing/proc/get_all_network_reagents(datum/ductnet/net)
|
||||
var/list/distinct_reagents = list()
|
||||
for(var/datum/reagent/existing_regent as anything in reagents.reagent_list)
|
||||
distinct_reagents |= existing_regent.type
|
||||
for(var/datum/component/plumbing/supplier as anything in net?.suppliers)
|
||||
for(var/datum/reagent/chemical as anything in supplier.reagents.reagent_list)
|
||||
distinct_reagents |= chemical.type
|
||||
return distinct_reagents
|
||||
|
||||
///check who can give us what we want, and how many each of them will give us
|
||||
/datum/component/plumbing/proc/process_request(amount = MACHINE_REAGENT_TRANSFER, reagent, dir, round_robin = TRUE)
|
||||
//find the duct to take from
|
||||
var/dirtext = num2text(dir)
|
||||
var/datum/ductnet/net = ducts[dirtext]
|
||||
if(QDELETED(net))
|
||||
if(net)
|
||||
ducts -= dirtext
|
||||
return FALSE
|
||||
|
||||
//find all valid suppliers in the duct
|
||||
var/list/valid_suppliers = list()
|
||||
for(var/datum/component/plumbing/supplier as anything in net?.suppliers)
|
||||
if(supplier.can_give(amount, reagent, net))
|
||||
valid_suppliers += supplier
|
||||
var/suppliersLeft = length(valid_suppliers)
|
||||
if(!suppliersLeft)
|
||||
return FALSE
|
||||
|
||||
//take an equal amount from each supplier
|
||||
var/currentRequest
|
||||
var/target_volume = reagents.total_volume + amount
|
||||
for(var/datum/component/plumbing/give as anything in valid_suppliers)
|
||||
currentRequest = (target_volume - reagents.total_volume) / suppliersLeft
|
||||
give.transfer_to(src, currentRequest, reagent, net, round_robin)
|
||||
suppliersLeft--
|
||||
return TRUE
|
||||
|
||||
///returns TRUE when they can give the specified amount and reagent. called by process request
|
||||
/datum/component/plumbing/proc/can_give(amount, reagent, datum/ductnet/net)
|
||||
SHOULD_BE_PURE(TRUE)
|
||||
|
||||
if(amount <= 0)
|
||||
return FALSE
|
||||
|
||||
if(reagent) //only asked for one type of reagent
|
||||
return reagents.has_reagent(reagent)
|
||||
else if(reagents.total_volume) //take whatever
|
||||
return TRUE
|
||||
|
||||
return FALSE
|
||||
|
||||
///this is where the reagent is actually transferred and is thus the finish point of our process()
|
||||
/datum/component/plumbing/proc/transfer_to(datum/component/plumbing/target, amount, reagent, datum/ductnet/net, round_robin = TRUE)
|
||||
reagents.trans_to(target.recipient_reagents_holder(), amount, target_id = reagent, methods = round_robin ? LINEAR : NONE)
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
/datum/component/plumbing/automated_iv
|
||||
demand_connects = SOUTH
|
||||
supply_connects = NORTH
|
||||
///Temporary holder to store all the reagents from the iv drip before transferring it to the ducts
|
||||
distinct_reagent_cap = 3
|
||||
///Temporary holder to store all the reagents from the iv drip before transferring to the ducts
|
||||
var/datum/reagents/plumbing/holder
|
||||
|
||||
/datum/component/plumbing/automated_iv/Initialize(start=TRUE, _ducting_layer, _turn_connects=TRUE, datum/reagents/custom_receiver)
|
||||
. = ..()
|
||||
/datum/component/plumbing/automated_iv/Initialize(ducting_layer)
|
||||
if(!istype(parent, /obj/machinery/iv_drip/plumbing))
|
||||
return COMPONENT_INCOMPATIBLE
|
||||
. = ..()
|
||||
var/obj/machinery/iv_drip/plumbing/drip = parent
|
||||
holder = new(drip.reagents.maximum_volume, drip.reagents.flags)
|
||||
holder.my_atom = drip
|
||||
@@ -17,16 +18,13 @@
|
||||
return ..()
|
||||
|
||||
/datum/component/plumbing/automated_iv/can_give(amount, reagent)
|
||||
. = ..()
|
||||
if(!.)
|
||||
return
|
||||
var/obj/machinery/iv_drip/plumbing/drip = parent
|
||||
return drip.mode == 0
|
||||
return ..() && drip.mode == IV_TAKING
|
||||
|
||||
/datum/component/plumbing/automated_iv/send_request(dir)
|
||||
var/obj/machinery/iv_drip/plumbing/drip = parent
|
||||
if(drip.mode == 1)
|
||||
process_request(dir = dir)
|
||||
if(drip.mode == IV_INJECTING)
|
||||
return ..()
|
||||
|
||||
/datum/component/plumbing/automated_iv/transfer_to(datum/component/plumbing/target, amount, reagent, datum/ductnet/net, round_robin = TRUE)
|
||||
reagents.trans_to(holder, reagents.total_volume)
|
||||
|
||||
@@ -4,10 +4,9 @@
|
||||
demand_connects = WEST
|
||||
supply_connects = EAST
|
||||
|
||||
/datum/component/plumbing/buffer/Initialize(start = TRUE, _turn_connects = TRUE, _ducting_layer, datum/reagents/custom_receiver)
|
||||
/datum/component/plumbing/buffer/Initialize(ducting_layer)
|
||||
if(!istype(parent, /obj/machinery/plumbing/buffer))
|
||||
return COMPONENT_INCOMPATIBLE
|
||||
|
||||
return ..()
|
||||
|
||||
/datum/component/plumbing/buffer/can_give(amount, reagent, datum/ductnet/net)
|
||||
|
||||
@@ -3,23 +3,18 @@
|
||||
supply_connects = EAST
|
||||
var/obj/machinery/plumbing/acclimator/myacclimator
|
||||
|
||||
/datum/component/plumbing/acclimator/Initialize(start=TRUE, _ducting_layer, _turn_connects=TRUE, datum/reagents/custom_receiver)
|
||||
. = ..()
|
||||
/datum/component/plumbing/acclimator/Initialize(ducting_layer)
|
||||
if(!istype(parent, /obj/machinery/plumbing/acclimator))
|
||||
return COMPONENT_INCOMPATIBLE
|
||||
myacclimator = parent
|
||||
|
||||
/datum/component/plumbing/acclimator/Destroy(force)
|
||||
myacclimator = null
|
||||
return ..()
|
||||
|
||||
/datum/component/plumbing/acclimator/can_give(amount, reagent)
|
||||
. = ..()
|
||||
if(. && myacclimator.emptying)
|
||||
return TRUE
|
||||
return FALSE
|
||||
var/obj/machinery/plumbing/acclimator/myacclimator = parent
|
||||
|
||||
return myacclimator.emptying && ..()
|
||||
|
||||
///We're overriding process and not send_request, because all process does is do the requests, so we might aswell cut out the middle man and save some code from running
|
||||
/datum/component/plumbing/acclimator/process()
|
||||
if(myacclimator.emptying)
|
||||
return
|
||||
return ..()
|
||||
var/obj/machinery/plumbing/acclimator/myacclimator = parent
|
||||
if(!myacclimator.emptying)
|
||||
..()
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
///We handle the unity part of plumbing. We track who is connected to who.
|
||||
/datum/ductnet
|
||||
///All the ducts that make this network
|
||||
var/list/obj/machinery/duct/ducts
|
||||
///Stuff that can supply chems used by machines to retrive chems
|
||||
var/list/datum/component/plumbing/suppliers
|
||||
///Stuff that demands chems keep track of components that need their ducts updated as this net evolves
|
||||
var/list/datum/component/plumbing/demanders
|
||||
|
||||
/datum/ductnet/New(obj/machinery/duct/parent)
|
||||
ducts = parent ? list(parent) : list()
|
||||
suppliers = list()
|
||||
demanders = list()
|
||||
return ..()
|
||||
|
||||
/datum/ductnet/Destroy(force)
|
||||
ducts.Cut()
|
||||
for(var/datum/component/plumbing/plumbing as anything in suppliers + demanders)
|
||||
remove_plumber(plumbing)
|
||||
suppliers.Cut()
|
||||
demanders.Cut()
|
||||
return ..()
|
||||
|
||||
///add a plumbing object to either demanders or suppliers
|
||||
/datum/ductnet/proc/add_plumber(datum/component/plumbing/plumbing, dir)
|
||||
var/dirtext = num2text(dir)
|
||||
if(plumbing.ducts[dirtext] == src)
|
||||
return FALSE
|
||||
plumbing.ducts[dirtext] = src
|
||||
if(dir & plumbing.supply_connects)
|
||||
suppliers += plumbing
|
||||
if(dir & plumbing.demand_connects)
|
||||
demanders += plumbing
|
||||
return TRUE
|
||||
|
||||
///remove a plumber. we don't delete ourselves because ductnets don't persist through plumbing objects.
|
||||
/datum/ductnet/proc/remove_plumber(datum/component/plumbing/plumbing)
|
||||
for(var/dir in plumbing.ducts)
|
||||
if(plumbing.ducts[dir] == src)
|
||||
plumbing.ducts -= dir
|
||||
dir = text2num(dir)
|
||||
if(dir & plumbing.supply_connects)
|
||||
suppliers -= plumbing
|
||||
if(dir & plumbing.demand_connects)
|
||||
demanders -= plumbing
|
||||
|
||||
//return if this net has no ducts like when 2 machines are connected
|
||||
return ducts.len == 0
|
||||
@@ -3,10 +3,10 @@
|
||||
demand_connects = NORTH
|
||||
supply_connects = SOUTH | EAST | WEST //SOUTH is straight, EAST is left and WEST is right. We look from the perspective of the insert
|
||||
|
||||
/datum/component/plumbing/filter/Initialize(start=TRUE, _ducting_layer, _turn_connects=TRUE, datum/reagents/custom_receiver)
|
||||
. = ..()
|
||||
/datum/component/plumbing/filter/Initialize(ducting_layer)
|
||||
if(!istype(parent, /obj/machinery/plumbing/filter))
|
||||
return COMPONENT_INCOMPATIBLE
|
||||
return ..()
|
||||
|
||||
/datum/component/plumbing/filter/can_give(amount, reagent, datum/ductnet/net)
|
||||
. = ..()
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
/// Special demand connector that consumes as normal, but redirects water into the magical water space.
|
||||
/datum/component/plumbing/hydroponics
|
||||
demand_connects = SOUTH
|
||||
/// Alternate reagents container to buffer incoming water
|
||||
var/datum/reagents/water_reagents
|
||||
/// Decides if we receive either water or regular reagents
|
||||
var/receive_water = FALSE
|
||||
|
||||
/datum/component/plumbing/hydroponics/Initialize(ducting_layer)
|
||||
. = ..()
|
||||
|
||||
if(!istype(parent, /obj/machinery/hydroponics/constructable))
|
||||
return COMPONENT_INCOMPATIBLE
|
||||
|
||||
var/obj/machinery/hydroponics/constructable/hydro_parent = parent
|
||||
|
||||
water_reagents = new(hydro_parent.maxwater)
|
||||
water_reagents.my_atom = hydro_parent
|
||||
|
||||
/datum/component/plumbing/hydroponics/Destroy()
|
||||
QDEL_NULL(water_reagents)
|
||||
return ..()
|
||||
|
||||
/datum/component/plumbing/hydroponics/recipient_reagents_holder()
|
||||
return receive_water ? water_reagents : reagents
|
||||
|
||||
/datum/component/plumbing/hydroponics/send_request(dir)
|
||||
var/obj/machinery/hydroponics/constructable/hydro_parent = parent
|
||||
|
||||
var/initial_nutri_amount = reagents.total_volume
|
||||
if(initial_nutri_amount < reagents.maximum_volume)
|
||||
// Well boy howdy, we have no way to tell a supply to not mix the water with everything else,
|
||||
// So we'll let it leak in, and move the water over.
|
||||
receive_water = FALSE
|
||||
process_request(dir = dir, round_robin = FALSE)
|
||||
|
||||
// Move the leaked water from nutrients to... water
|
||||
var/leaking_water_amount = reagents.get_reagent_amount(/datum/reagent/water)
|
||||
if(leaking_water_amount)
|
||||
reagents.trans_to(water_reagents, leaking_water_amount, target_id = /datum/reagent/water)
|
||||
|
||||
// We should only take MACHINE_REAGENT_TRANSFER every tick; this is the remaining amount we can take
|
||||
var/remaining_transfer_amount = max(MACHINE_REAGENT_TRANSFER - (reagents.total_volume - initial_nutri_amount), 0)
|
||||
|
||||
// How much extra water we should gather this tick to try to fill the water tray.
|
||||
var/extra_water_to_gather = clamp(hydro_parent.maxwater - hydro_parent.waterlevel - water_reagents.total_volume, 0, remaining_transfer_amount)
|
||||
if(extra_water_to_gather > 0)
|
||||
receive_water = TRUE
|
||||
process_request(
|
||||
amount = extra_water_to_gather,
|
||||
reagent = /datum/reagent/water,
|
||||
dir = dir
|
||||
)
|
||||
|
||||
// Now transfer all remaining water in that buffer and clear it out.
|
||||
var/final_water_amount = water_reagents.total_volume
|
||||
if(final_water_amount)
|
||||
hydro_parent.adjust_waterlevel(round(final_water_amount))
|
||||
// Using a pipe doesn't afford you extra water storage and the baseline behavior for trays is that excess water goes into the shadow realm.
|
||||
water_reagents.clear_reagents()
|
||||
@@ -2,10 +2,10 @@
|
||||
demand_connects = NORTH
|
||||
supply_connects = SOUTH
|
||||
|
||||
/datum/component/plumbing/reaction_chamber/Initialize(start=TRUE, _ducting_layer, _turn_connects=TRUE, datum/reagents/custom_receiver)
|
||||
. = ..()
|
||||
/datum/component/plumbing/reaction_chamber/Initialize(ducting_layer)
|
||||
if(!istype(parent, /obj/machinery/plumbing/reaction_chamber))
|
||||
return COMPONENT_INCOMPATIBLE
|
||||
return ..()
|
||||
|
||||
/datum/component/plumbing/reaction_chamber/can_give(amount, reagent, datum/ductnet/net)
|
||||
. = FALSE
|
||||
@@ -66,22 +66,37 @@
|
||||
if(chamber.emptying) //if we are still emptying then keep checking for reagents until we are emptied out
|
||||
chamber.RegisterSignal(reagents, COMSIG_REAGENTS_HOLDER_UPDATED, TYPE_PROC_REF(/obj/machinery/plumbing/reaction_chamber, on_reagent_change))
|
||||
|
||||
/datum/component/plumbing/buffered
|
||||
///The reagent to request into this buffer
|
||||
VAR_PROTECTED/datum/reagent/request
|
||||
///The buffer meant to store reagents
|
||||
VAR_PRIVATE/datum/reagents/buffer
|
||||
|
||||
/datum/component/plumbing/buffered/Initialize(ducting_layer)
|
||||
. = ..()
|
||||
buffer = new (maximum = 50, new_flags = NO_REACT)
|
||||
buffer.my_atom = parent
|
||||
|
||||
/datum/component/plumbing/buffered/Destroy()
|
||||
QDEL_NULL(buffer)
|
||||
return ..()
|
||||
|
||||
/datum/component/plumbing/buffered/recipient_reagents_holder()
|
||||
return buffer
|
||||
|
||||
/datum/component/plumbing/buffered/send_request(dir)
|
||||
process_request(reagent = request, dir = dir)
|
||||
|
||||
///Special connect that we currently use for reaction chambers. Being used so we can keep certain inputs separate, like into a special internal acid container
|
||||
/datum/component/plumbing/acidic_input
|
||||
/datum/component/plumbing/buffered/acidic_input
|
||||
demand_connects = WEST
|
||||
demand_color = COLOR_YELLOW
|
||||
ducting_layer = SECOND_DUCT_LAYER
|
||||
|
||||
/datum/component/plumbing/acidic_input/send_request(dir)
|
||||
process_request(reagent = /datum/reagent/reaction_agent/acidic_buffer, dir = dir)
|
||||
request = /datum/reagent/reaction_agent/acidic_buffer
|
||||
|
||||
///Special connect that we currently use for reaction chambers. Being used so we can keep certain inputs separate, like into a special internal base container
|
||||
/datum/component/plumbing/alkaline_input
|
||||
/datum/component/plumbing/buffered/alkaline_input
|
||||
demand_connects = EAST
|
||||
demand_color = COLOR_VIBRANT_LIME
|
||||
ducting_layer = FOURTH_DUCT_LAYER
|
||||
|
||||
/datum/component/plumbing/alkaline_input/send_request(dir)
|
||||
process_request(reagent = /datum/reagent/reaction_agent/basic_buffer, dir = dir)
|
||||
|
||||
|
||||
request = /datum/reagent/reaction_agent/basic_buffer
|
||||
|
||||
@@ -3,6 +3,36 @@
|
||||
/datum/component/plumbing/simple_demand
|
||||
demand_connects = SOUTH
|
||||
|
||||
/datum/component/plumbing/simple_demand/Initialize(ducting_layer, distinct_reagent_cap = INFINITY)
|
||||
src.distinct_reagent_cap = distinct_reagent_cap
|
||||
return ..()
|
||||
|
||||
///Component for adding an extended overlay on wall mounts
|
||||
/datum/component/plumbing/simple_demand/extended/create_overlays(atom/movable/parent_movable, list/overlays)
|
||||
. = ..()
|
||||
|
||||
// This is a little wiggley extension to make wallmounts like sinks and showers visually link to the pipe
|
||||
if(overlays.len)
|
||||
var/image/edge_overlay = image('icons/obj/pipes_n_cables/hydrochem/connects.dmi', "edge-extension", layer = PLUMBING_PIPE_VISIBILE_LAYER + ducting_layer * 0.0003)
|
||||
edge_overlay.dir = parent_movable.dir
|
||||
edge_overlay.color = demand_color
|
||||
edge_overlay.pixel_w = -parent_movable.pixel_x - parent_movable.pixel_w
|
||||
edge_overlay.pixel_z = -parent_movable.pixel_y - parent_movable.pixel_z
|
||||
overlays += edge_overlay
|
||||
|
||||
///For disposing reagents
|
||||
/datum/component/plumbing/simple_demand/disposer
|
||||
|
||||
/datum/component/plumbing/simple_demand/disposer/Initialize(ducting_layer, distinct_reagent_cap)
|
||||
if(!istype(parent, /obj/machinery/plumbing/disposer))
|
||||
return COMPONENT_INCOMPATIBLE
|
||||
return ..()
|
||||
|
||||
/datum/component/plumbing/simple_demand/disposer/send_request(dir)
|
||||
var/obj/machinery/plumbing/disposer/target = parent
|
||||
if(target.on)
|
||||
process_request(target.disposal_rate * SSFLUIDS_DT, dir = dir)
|
||||
|
||||
///has one pipe output that only supplies. example is liquid pump and manual input pipe
|
||||
/datum/component/plumbing/simple_supply
|
||||
supply_connects = SOUTH
|
||||
@@ -12,10 +42,13 @@
|
||||
demand_connects = WEST
|
||||
supply_connects = EAST
|
||||
|
||||
///Lazily demand from any direction. Overlays won't look good, and the aquarium sprite occupies about the entire 32x32 area anyway.
|
||||
///Lazily demand from any direction.
|
||||
/datum/component/plumbing/aquarium
|
||||
demand_connects = SOUTH|NORTH|EAST|WEST
|
||||
use_overlays = FALSE
|
||||
|
||||
/datum/component/plumbing/aquarium/create_overlays(atom/movable/parent_movable, list/overlays)
|
||||
//Overlays won't look good, and the aquarium sprite occupies about the entire 32x32 area anyway.
|
||||
return
|
||||
|
||||
///Connects different layer of ducts
|
||||
/datum/component/plumbing/manifold
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
demand_connects = NORTH
|
||||
supply_connects = SOUTH | EAST
|
||||
|
||||
/datum/component/plumbing/splitter/Initialize(start=TRUE, _ducting_layer, _turn_connects=TRUE, datum/reagents/custom_receiver)
|
||||
. = ..()
|
||||
if(. && !istype(parent, /obj/machinery/plumbing/splitter))
|
||||
return FALSE
|
||||
/datum/component/plumbing/splitter/Initialize(ducting_layer)
|
||||
if(!istype(parent, /obj/machinery/plumbing/splitter))
|
||||
return COMPONENT_INCOMPATIBLE
|
||||
return ..()
|
||||
|
||||
/datum/component/plumbing/splitter/can_give(amount, reagent, datum/ductnet/net)
|
||||
. = ..()
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
/// Similar to the projectile drop element but holds an instance of an atom instead
|
||||
/datum/component/projectile_instance_drop
|
||||
dupe_mode = COMPONENT_DUPE_ALLOWED
|
||||
var/atom/movable/dropped_atom
|
||||
|
||||
/datum/component/projectile_instance_drop/Initialize(atom/movable/dropped_atom)
|
||||
if(!isprojectile(parent))
|
||||
return COMPONENT_INCOMPATIBLE
|
||||
if (!dropped_atom)
|
||||
stack_trace("[type] created with no atom to drop.")
|
||||
return COMPONENT_INCOMPATIBLE
|
||||
src.dropped_atom = dropped_atom
|
||||
|
||||
/datum/component/projectile_instance_drop/RegisterWithParent()
|
||||
dropped_atom.forceMove(parent)
|
||||
RegisterSignal(parent, COMSIG_PROJECTILE_RANGE_OUT, PROC_REF(drop_item))
|
||||
RegisterSignal(parent, COMSIG_PROJECTILE_SELF_ON_HIT, PROC_REF(on_projectile_hit))
|
||||
RegisterSignals(dropped_atom, list(COMSIG_MOVABLE_MOVED, COMSIG_QDELETING), PROC_REF(on_instance_left))
|
||||
|
||||
/datum/component/projectile_instance_drop/UnregisterFromParent()
|
||||
UnregisterSignal(parent, list(COMSIG_PROJECTILE_RANGE_OUT, COMSIG_PROJECTILE_SELF_ON_HIT))
|
||||
if (dropped_atom)
|
||||
UnregisterSignal(dropped_atom, list(COMSIG_MOVABLE_MOVED, COMSIG_QDELETING))
|
||||
QDEL_NULL(dropped_atom) // If it left our atom's contents then this is a null reference, if it didn't then it's fucked I guess
|
||||
|
||||
/datum/component/projectile_instance_drop/Destroy(force)
|
||||
dropped_atom = null
|
||||
return ..()
|
||||
|
||||
/// When we hit something check if we should hit the floor
|
||||
/datum/component/projectile_instance_drop/proc/on_projectile_hit(obj/projectile/source, atom/movable/firer, atom/target, angle, hit_limb_zone, blocked, pierce_hit)
|
||||
SIGNAL_HANDLER
|
||||
if (blocked < 100 && !pierce_hit)
|
||||
drop_item(source)
|
||||
|
||||
/// Drop item to ground
|
||||
/datum/component/projectile_instance_drop/proc/drop_item(obj/projectile/source)
|
||||
SIGNAL_HANDLER
|
||||
var/turf/drop_turf = source.drop_location()
|
||||
var/atom/movable/dropping = dropped_atom
|
||||
dropping.forceMove(drop_turf)
|
||||
if (isitem(dropping))
|
||||
var/obj/item/item_dropped = dropping
|
||||
item_dropped.do_drop_animation(drop_turf)
|
||||
|
||||
/// If our projectile exits our contents then remove the component
|
||||
/datum/component/projectile_instance_drop/proc/on_instance_left()
|
||||
SIGNAL_HANDLER
|
||||
dropped_atom = null
|
||||
qdel(src)
|
||||
@@ -13,6 +13,8 @@
|
||||
var/change_base_icon_state = FALSE
|
||||
/// If true, changing the reskin also changes the inhand_icon_state of the atom
|
||||
var/change_inhand_icon_state = FALSE
|
||||
/// If true, changing the reskin also changes the worn_icon_state of the atom
|
||||
var/change_worn_icon_state = TRUE
|
||||
/// If true, unset vars are reset to their original values when applying this skin
|
||||
var/reset_missing = TRUE
|
||||
|
||||
@@ -24,6 +26,44 @@
|
||||
var/new_icon
|
||||
/// Optional, icon_state to change the atom to when applied
|
||||
var/new_icon_state
|
||||
/// Specifies the icon state for the atom's appearance in hand. Should appear in both new_lefthand_file and new_righthand_file.
|
||||
var/new_inhand_icon_state
|
||||
/// Optional, specifies the left hand inhand icon file. Don't forget to set the right hand file as well.
|
||||
var/new_lefthand_file
|
||||
/// Optional, specifies the right hand inhand icon file. Don't forget to set the left hand file as well.
|
||||
var/new_righthand_file
|
||||
/// Optional, specifies the worn icon file.
|
||||
var/new_worn_icon
|
||||
/// When set true, will allow concrete subtypes of abstract subtypes (such as for organizational purposes) to be selectable as reskins in the loadout menu.
|
||||
var/allow_all_subtypes_in_loadout
|
||||
/// Mandatory for GAGs items. The path to the greyscale item this is to be applied to.
|
||||
var/atom/greyscale_item_path
|
||||
/// Auto populated. The greyscale_config from greyscale_item_path.
|
||||
VAR_FINAL/greyscale_config
|
||||
/// Auto populated. The greyscale_colors from greyscale_item_path.
|
||||
VAR_FINAL/greyscale_colors
|
||||
|
||||
/datum/atom_skin/New()
|
||||
. = ..()
|
||||
if(isnull(greyscale_item_path))
|
||||
return
|
||||
|
||||
// Populate the fields required for GAGS previews
|
||||
greyscale_config = greyscale_item_path::greyscale_config
|
||||
greyscale_colors = greyscale_item_path::greyscale_colors
|
||||
|
||||
// The icon isn't 'new'. So we don't make an extra icon in map_icons.
|
||||
if(greyscale_item_path::post_init_icon_state == new_icon_state)
|
||||
new_icon_state = null
|
||||
|
||||
/// Returns the correct preview icon state for this atom skin, whether it be a map_preview or a normal icon. This is assumed from our var population above.
|
||||
/datum/atom_skin/proc/get_preview_icon_state()
|
||||
if(isnull(greyscale_item_path)) // Not a GAGs icon
|
||||
return new_icon_state
|
||||
if(isnull(new_icon_state)) // The base GAGS item icon
|
||||
return "[greyscale_item_path]"
|
||||
|
||||
return "[greyscale_item_path]--[new_icon_state]" // A GAGs reskin of the item icon
|
||||
|
||||
/**
|
||||
* Applies all relevant skin changes to the given atom
|
||||
@@ -31,17 +71,27 @@
|
||||
*
|
||||
* * apply_to: The atom to apply the skin to
|
||||
*/
|
||||
/datum/atom_skin/proc/apply(atom/apply_to)
|
||||
/datum/atom_skin/proc/apply(atom/apply_to, mob/user)
|
||||
SHOULD_CALL_PARENT(TRUE)
|
||||
APPLY_VAR_OR_RESET_INITIAL(apply_to, name, new_name, reset_missing)
|
||||
APPLY_VAR_OR_RESET_INITIAL(apply_to, desc, new_desc, reset_missing)
|
||||
APPLY_VAR_OR_RESET_INITIAL(apply_to, icon, new_icon, reset_missing)
|
||||
if(!HAS_TRAIT(apply_to, TRAIT_WAS_RENAMED))
|
||||
APPLY_VAR_OR_RESET_INITIAL(apply_to, name, new_name, reset_missing)
|
||||
APPLY_VAR_OR_RESET_INITIAL(apply_to, desc, new_desc, reset_missing)
|
||||
if(!apply_to.greyscale_config) // Never want to be resetting a GAGS icon back to initial.
|
||||
APPLY_VAR_OR_RESET_INITIAL(apply_to, icon, new_icon, reset_missing)
|
||||
APPLY_VAR_OR_RESET_TO(apply_to, icon_state, new_icon_state, reset_missing, initial(apply_to.post_init_icon_state) || initial(apply_to.icon_state))
|
||||
if(change_base_icon_state)
|
||||
APPLY_VAR_OR_RESET_INITIAL(apply_to, base_icon_state, new_icon_state, reset_missing)
|
||||
if(change_inhand_icon_state && isitem(apply_to))
|
||||
if(isitem(apply_to))
|
||||
var/obj/item/item_apply_to = apply_to
|
||||
APPLY_VAR_OR_RESET_INITIAL(item_apply_to, inhand_icon_state, new_icon_state, reset_missing)
|
||||
if(!item_apply_to.greyscale_config_worn)
|
||||
APPLY_VAR_OR_RESET_INITIAL(item_apply_to, worn_icon, new_worn_icon, reset_missing)
|
||||
if(!item_apply_to.greyscale_config_inhand_left)
|
||||
APPLY_VAR_OR_RESET_INITIAL(item_apply_to, lefthand_file, new_lefthand_file, reset_missing)
|
||||
APPLY_VAR_OR_RESET_INITIAL(item_apply_to, righthand_file, new_righthand_file, reset_missing)
|
||||
if(change_worn_icon_state)
|
||||
APPLY_VAR_OR_RESET_INITIAL(item_apply_to, worn_icon_state, new_icon_state, reset_missing)
|
||||
if(change_inhand_icon_state || new_inhand_icon_state)
|
||||
APPLY_VAR_OR_RESET_INITIAL(item_apply_to, inhand_icon_state, new_inhand_icon_state || new_icon_state, reset_missing)
|
||||
|
||||
/**
|
||||
* Resets all changes this skin would have made to the given atom
|
||||
@@ -54,13 +104,20 @@
|
||||
SHOULD_CALL_PARENT(TRUE)
|
||||
RESET_INITIAL_IF_SET(clear_from, name, new_name)
|
||||
RESET_INITIAL_IF_SET(clear_from, desc, new_desc)
|
||||
RESET_INITIAL_IF_SET(clear_from, icon, new_icon)
|
||||
var/is_greyscale = clear_from.greyscale_config || clear_from.greyscale_colors
|
||||
if(!is_greyscale)
|
||||
RESET_INITIAL_IF_SET(clear_from, icon, new_icon)
|
||||
RESET_TO_IF_SET(clear_from, icon_state, new_icon_state, initial(clear_from.post_init_icon_state) || initial(clear_from.icon_state))
|
||||
if(change_base_icon_state)
|
||||
RESET_INITIAL_IF_SET(clear_from, base_icon_state, new_icon_state)
|
||||
if(change_inhand_icon_state && isitem(clear_from))
|
||||
if(isitem(clear_from))
|
||||
var/obj/item/item_clear_from = clear_from
|
||||
RESET_INITIAL_IF_SET(item_clear_from, inhand_icon_state, new_icon_state)
|
||||
RESET_INITIAL_IF_SET(item_clear_from, worn_icon, new_worn_icon)
|
||||
RESET_INITIAL_IF_SET(item_clear_from, inhand_icon_state, change_inhand_icon_state ? new_inhand_icon_state : new_icon_state)
|
||||
if(!is_greyscale)
|
||||
RESET_INITIAL_IF_SET(item_clear_from, worn_icon_state, new_icon_state)
|
||||
RESET_INITIAL_IF_SET(item_clear_from, lefthand_file, new_lefthand_file)
|
||||
RESET_INITIAL_IF_SET(item_clear_from, righthand_file, new_righthand_file)
|
||||
|
||||
/// Gets a preview image for this skin based on the given atom's icon and icon_state
|
||||
/datum/atom_skin/proc/get_preview_icon(atom/for_atom)
|
||||
@@ -82,14 +139,24 @@
|
||||
VAR_PRIVATE/infinite_reskin = FALSE
|
||||
/// List of subtypes of /datum/atom_skin that are not allowed to be used for this item
|
||||
VAR_PRIVATE/list/blacklisted_subtypes
|
||||
|
||||
/// Currently applied skin preview_name
|
||||
VAR_PRIVATE/current_skin
|
||||
|
||||
/datum/component/reskinable_item/Initialize(base_reskin_type, infinite = FALSE, initial_skin, list/blacklisted_subtypes = list())
|
||||
/datum/component/reskinable_item/Initialize(base_reskin_type, infinite = FALSE, initial_skin, list/blacklisted_subtypes)
|
||||
if(!isatom(parent) || isarea(parent))
|
||||
return COMPONENT_INCOMPATIBLE
|
||||
|
||||
var/atom/atom_parent = parent
|
||||
#ifdef UNIT_TESTS
|
||||
if(atom_parent.greyscale_config && (atom_parent.type in GLOB.all_loadout_datums)) // We only care about these when they're in the loadout
|
||||
var/datum/atom_skin/reskin_type = base_reskin_type
|
||||
if(reskin_type && isnull(reskin_type::greyscale_item_path)) // greyscale_item_path unset
|
||||
stack_trace("[type] added to a greyscale item without setting the greyscale_item_path! In [reskin_type], add 'greyscale_item_path = [atom_parent::type]'.")
|
||||
else if(atom_parent.type != reskin_type::greyscale_item_path) // greyscale_item_path set but doesn't match the item it's being added to
|
||||
stack_trace("[type] added to an invalid item type, [atom_parent.type]. [reskin_type] is set up to only be added to: [reskin_type::greyscale_item_path]. \
|
||||
Either fix its greyscale_item_path if this is incorrect, or apply a different skin.")
|
||||
#endif
|
||||
|
||||
src.base_reskin_type = base_reskin_type
|
||||
src.infinite_reskin = infinite
|
||||
src.blacklisted_subtypes = blacklisted_subtypes
|
||||
@@ -97,7 +164,10 @@
|
||||
if(initial_skin)
|
||||
set_skin_by_name(initial_skin)
|
||||
|
||||
var/atom/atom_parent = parent
|
||||
var/list/reskin_options = get_skins_by_name()
|
||||
if(length(reskin_options) <= 1) // Check that we actually have reskin options - if not there's no point to existing past this point.
|
||||
return COMPONENT_REDUNDANT
|
||||
|
||||
atom_parent.flags_1 |= HAS_CONTEXTUAL_SCREENTIPS_1
|
||||
|
||||
/datum/component/reskinable_item/RegisterWithParent()
|
||||
@@ -110,15 +180,20 @@
|
||||
UnregisterSignal(parent, COMSIG_ATOM_EXAMINE_TAGS)
|
||||
UnregisterSignal(parent, COMSIG_ATOM_REQUESTING_CONTEXT_FROM_ITEM)
|
||||
|
||||
/datum/component/reskinable_item/CheckDupeComponent(datum/component/comp, base_reskin_type, infinite = FALSE, initial_skin, list/blacklisted_subtypes = list())
|
||||
if(src.base_reskin_type != base_reskin_type)
|
||||
return FALSE // new comp - though the alt-click behavior will collide
|
||||
|
||||
/datum/component/reskinable_item/CheckDupeComponent(datum/component/comp, base_reskin_type, infinite = FALSE, initial_skin, list/blacklisted_subtypes)
|
||||
// Always absorb added components
|
||||
src.base_reskin_type = base_reskin_type
|
||||
src.infinite_reskin = infinite
|
||||
src.blacklisted_subtypes = blacklisted_subtypes
|
||||
|
||||
set_skin_by_name(initial_skin)
|
||||
return TRUE // same comp
|
||||
if(initial_skin)
|
||||
set_skin_by_name(initial_skin)
|
||||
|
||||
var/list/reskin_options = get_skins_by_name()
|
||||
if(length(reskin_options) <= 1) // Check that we actually have reskin options - if not there's no point to existing past this point.
|
||||
qdel(src)
|
||||
|
||||
return TRUE
|
||||
|
||||
/datum/component/reskinable_item/proc/get_skins_by_name()
|
||||
var/list/reskin_options = list()
|
||||
@@ -127,15 +202,16 @@
|
||||
|
||||
return reskin_options
|
||||
|
||||
/datum/component/reskinable_item/proc/set_skin_by_name(input_name)
|
||||
/datum/component/reskinable_item/proc/set_skin_by_name(input_name, mob/user)
|
||||
var/list/reskin_options = get_skins_by_name()
|
||||
var/list/atom_skins = get_atom_skins()
|
||||
if(current_skin)
|
||||
var/datum/atom_skin/previous_skin = GLOB.atom_skins[reskin_options[current_skin]]
|
||||
previous_skin.clear_skin(parent)
|
||||
var/datum/atom_skin/previous_skin = atom_skins[reskin_options[current_skin]]
|
||||
previous_skin.clear_skin(parent, user)
|
||||
|
||||
if(input_name)
|
||||
var/datum/atom_skin/reskin_to_apply = GLOB.atom_skins[reskin_options[input_name]]
|
||||
reskin_to_apply.apply(parent)
|
||||
var/datum/atom_skin/reskin_to_apply = atom_skins[reskin_options[input_name]]
|
||||
reskin_to_apply.apply(parent, user)
|
||||
|
||||
current_skin = input_name
|
||||
|
||||
@@ -179,8 +255,10 @@
|
||||
var/atom/atom_parent = parent
|
||||
|
||||
var/list/items = list()
|
||||
var/list/atom_skins = get_atom_skins()
|
||||
for(var/reskin_name, reskin_typepath in get_skins_by_name())
|
||||
items[reskin_name] = GLOB.atom_skins[reskin_typepath].get_preview_icon(atom_parent)
|
||||
var/datum/atom_skin/reskin = atom_skins[reskin_typepath]
|
||||
items[reskin_name] = image(icon = reskin.new_icon || atom_parent.icon, icon_state = reskin.new_icon_state || atom_parent.icon_state)
|
||||
|
||||
sort_list(items)
|
||||
|
||||
@@ -188,7 +266,7 @@
|
||||
if(!pick || !items[pick])
|
||||
return
|
||||
|
||||
set_skin_by_name(pick)
|
||||
set_skin_by_name(pick, user)
|
||||
to_chat(user, span_info("[parent] is now skinned as '[pick].'"))
|
||||
|
||||
if(!infinite_reskin)
|
||||
|
||||
@@ -32,6 +32,8 @@
|
||||
var/override_allow_spacemove = FALSE
|
||||
/// can anyone other than the rider unbuckle the rider?
|
||||
var/can_force_unbuckle = TRUE
|
||||
/// Like last_bumped for mobs, but for vehicles. Exists to allow the door bump cooldown while also passing door openings through Bumped()
|
||||
var/vehicle_last_bumped = 0
|
||||
|
||||
/**
|
||||
* Ride check flags defined for the specific riding component types, so we know if we need arms, legs, or whatever.
|
||||
@@ -270,10 +272,12 @@
|
||||
/// So we can check all occupants when we bump a door to see if anyone has access
|
||||
/datum/component/riding/proc/vehicle_bump(atom/movable/movable_parent, obj/machinery/door/possible_bumped_door)
|
||||
SIGNAL_HANDLER
|
||||
if(!istype(possible_bumped_door))
|
||||
if(!istype(possible_bumped_door) || world.time - vehicle_last_bumped <= 0.3 SECONDS)
|
||||
return
|
||||
for(var/occupant in movable_parent.buckled_mobs)
|
||||
INVOKE_ASYNC(possible_bumped_door, TYPE_PROC_REF(/obj/machinery/door/, bumpopen), occupant)
|
||||
for(var/mob/living/occupant in movable_parent.buckled_mobs)
|
||||
vehicle_last_bumped = world.time
|
||||
occupant.last_bumped = 0
|
||||
INVOKE_ASYNC(possible_bumped_door, TYPE_PROC_REF(/atom, Bumped), occupant)
|
||||
|
||||
/datum/component/riding/proc/Unbuckle(atom/movable/M)
|
||||
addtimer(CALLBACK(parent, TYPE_PROC_REF(/atom/movable/, unbuckle_mob), M), 0, TIMER_UNIQUE)
|
||||
|
||||
@@ -69,7 +69,7 @@
|
||||
// for fireman carries, check if the ridden is stunned/restrained
|
||||
else if((ride_check_flags & CARRIER_NEEDS_ARM) && (HAS_TRAIT(living_parent, TRAIT_RESTRAINED) || INCAPACITATED_IGNORING(living_parent, INCAPABLE_RESTRAINTS|INCAPABLE_GRAB)))
|
||||
. = FALSE
|
||||
else if((ride_check_flags & JUST_FRIEND_RIDERS) && !(living_parent.faction.Find(REF(rider))))
|
||||
else if((ride_check_flags & JUST_FRIEND_RIDERS) && !(living_parent.has_ally(rider)))
|
||||
. = FALSE
|
||||
|
||||
if(. || !consequences)
|
||||
@@ -234,7 +234,7 @@
|
||||
/datum/component/riding/creature/handle_buckle(mob/living/rider)
|
||||
. = ..()
|
||||
var/mob/living/ridden = parent
|
||||
if(!require_minigame || ridden.faction.Find(REF(rider)))
|
||||
if(!require_minigame || ridden.has_ally(rider))
|
||||
return
|
||||
ridden.Shake(pixelshiftx = 1, pixelshifty = 0, duration = 1 SECONDS)
|
||||
ridden.spin(spintime = 1 SECONDS, speed = 1)
|
||||
|
||||
@@ -1,146 +0,0 @@
|
||||
/datum/component/simple_rotation
|
||||
/// Additional stuff to do after rotation
|
||||
var/datum/callback/post_rotation
|
||||
/// Rotation flags for special behavior
|
||||
var/rotation_flags = NONE
|
||||
|
||||
/**
|
||||
* Adds the ability to rotate an object by Alt-click or using Right-click verbs.
|
||||
*
|
||||
* args:
|
||||
* * rotation_flags (optional) Bitflags that determine behavior for rotation (defined at the top of this file)
|
||||
* * post_rotation (optional) Callback proc that is used after the object is rotated (sound effects, balloon alerts, etc.)
|
||||
**/
|
||||
/datum/component/simple_rotation/Initialize(rotation_flags = NONE, post_rotation)
|
||||
if(!ismovable(parent))
|
||||
return COMPONENT_INCOMPATIBLE
|
||||
|
||||
var/atom/movable/source = parent
|
||||
source.flags_1 |= HAS_CONTEXTUAL_SCREENTIPS_1
|
||||
|
||||
src.rotation_flags = rotation_flags
|
||||
src.post_rotation = post_rotation || CALLBACK(src, PROC_REF(default_post_rotation))
|
||||
|
||||
/datum/component/simple_rotation/RegisterWithParent()
|
||||
RegisterSignal(parent, COMSIG_CLICK_ALT, PROC_REF(rotate_left))
|
||||
RegisterSignal(parent, COMSIG_CLICK_ALT_SECONDARY, PROC_REF(rotate_right))
|
||||
RegisterSignal(parent, COMSIG_ATOM_EXAMINE, PROC_REF(ExamineMessage))
|
||||
RegisterSignal(parent, COMSIG_ATOM_REQUESTING_CONTEXT_FROM_ITEM, PROC_REF(on_requesting_context_from_item))
|
||||
return ..()
|
||||
|
||||
/datum/component/simple_rotation/PostTransfer(datum/new_parent)
|
||||
//Because of the callbacks which we don't track cleanly we can't transfer this
|
||||
//item cleanly, better to let the new of the new item create a new rotation datum
|
||||
//instead (there's no real state worth transferring)
|
||||
return COMPONENT_NOTRANSFER
|
||||
|
||||
/datum/component/simple_rotation/UnregisterFromParent()
|
||||
UnregisterSignal(parent, list(
|
||||
COMSIG_CLICK_ALT,
|
||||
COMSIG_CLICK_ALT_SECONDARY,
|
||||
COMSIG_ATOM_EXAMINE,
|
||||
COMSIG_ATOM_REQUESTING_CONTEXT_FROM_ITEM,
|
||||
))
|
||||
return ..()
|
||||
|
||||
/datum/component/simple_rotation/Destroy()
|
||||
post_rotation = null
|
||||
return ..()
|
||||
|
||||
/datum/component/simple_rotation/proc/ExamineMessage(datum/source, mob/user, list/examine_list)
|
||||
SIGNAL_HANDLER
|
||||
if(rotation_flags & ROTATION_REQUIRE_WRENCH)
|
||||
examine_list += span_notice("This requires a wrench to be rotated.")
|
||||
|
||||
/datum/component/simple_rotation/proc/rotate_right(datum/source, mob/user)
|
||||
SIGNAL_HANDLER
|
||||
rotate(user, ROTATION_CLOCKWISE)
|
||||
return CLICK_ACTION_SUCCESS
|
||||
|
||||
/datum/component/simple_rotation/proc/rotate_left(datum/source, mob/user)
|
||||
SIGNAL_HANDLER
|
||||
rotate(user, ROTATION_COUNTERCLOCKWISE)
|
||||
return CLICK_ACTION_SUCCESS
|
||||
|
||||
/datum/component/simple_rotation/proc/rotate(mob/user, degrees)
|
||||
if(QDELETED(user))
|
||||
CRASH("[src] is being rotated [user ? "with a qdeleting" : "without a"] user")
|
||||
if(!istype(user))
|
||||
CRASH("[src] is being rotated without a user of the wrong type: [user.type]")
|
||||
if(!isnum(degrees))
|
||||
CRASH("[src] is being rotated without providing the amount of degrees needed")
|
||||
|
||||
if(!can_be_rotated(user, degrees) || !can_user_rotate(user, degrees))
|
||||
return
|
||||
|
||||
var/obj/rotated_obj = parent
|
||||
rotated_obj.setDir(turn(rotated_obj.dir, degrees))
|
||||
if(rotation_flags & ROTATION_REQUIRE_WRENCH)
|
||||
playsound(rotated_obj, 'sound/items/tools/ratchet.ogg', 50, TRUE)
|
||||
|
||||
post_rotation.Invoke(user, degrees)
|
||||
|
||||
/datum/component/simple_rotation/proc/can_user_rotate(mob/user, degrees)
|
||||
if(isliving(user) && user.can_perform_action(parent, NEED_DEXTERITY))
|
||||
return TRUE
|
||||
if((rotation_flags & ROTATION_GHOSTS_ALLOWED) && isobserver(user) && CONFIG_GET(flag/ghost_interaction))
|
||||
return TRUE
|
||||
return FALSE
|
||||
|
||||
/datum/component/simple_rotation/proc/can_be_rotated(mob/user, degrees, silent=FALSE)
|
||||
var/obj/rotated_obj = parent
|
||||
if(!rotated_obj.Adjacent(user))
|
||||
silent = TRUE
|
||||
|
||||
if(rotation_flags & ROTATION_REQUIRE_WRENCH)
|
||||
if(!isliving(user))
|
||||
return FALSE
|
||||
var/obj/item/tool = user.get_active_held_item()
|
||||
if(!tool || tool.tool_behaviour != TOOL_WRENCH)
|
||||
if(!silent)
|
||||
rotated_obj.balloon_alert(user, "need a wrench!")
|
||||
return FALSE
|
||||
if(!(rotation_flags & ROTATION_IGNORE_ANCHORED) && rotated_obj.anchored)
|
||||
if(istype(rotated_obj, /obj/structure/window) && !silent)
|
||||
rotated_obj.balloon_alert(user, "need to unscrew!")
|
||||
else if(!silent)
|
||||
rotated_obj.balloon_alert(user, "need to unwrench!")
|
||||
return FALSE
|
||||
|
||||
if(rotation_flags & ROTATION_NEEDS_ROOM)
|
||||
var/target_dir = turn(rotated_obj.dir, degrees)
|
||||
var/obj/structure/window/rotated_window = rotated_obj
|
||||
var/fulltile = istype(rotated_window) ? rotated_window.fulltile : FALSE
|
||||
if(!valid_build_direction(rotated_obj.loc, target_dir, is_fulltile = fulltile))
|
||||
if(!silent)
|
||||
rotated_obj.balloon_alert(user, "can't rotate in that direction!")
|
||||
return FALSE
|
||||
|
||||
if(rotation_flags & ROTATION_NEEDS_UNBLOCKED)
|
||||
var/turf/rotate_turf = get_turf(rotated_obj)
|
||||
if(rotate_turf.is_blocked_turf(source_atom = rotated_obj))
|
||||
if(!silent)
|
||||
rotated_obj.balloon_alert(user, "rotation is blocked!")
|
||||
return FALSE
|
||||
return TRUE
|
||||
|
||||
/datum/component/simple_rotation/proc/default_post_rotation(mob/user, degrees)
|
||||
return
|
||||
|
||||
// maybe we don't need the item context proc but instead the hand one? since we don't need to check held_item
|
||||
/datum/component/simple_rotation/proc/on_requesting_context_from_item(atom/source, list/context, obj/item/held_item, mob/user)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
var/rotation_screentip = FALSE
|
||||
|
||||
if(can_be_rotated(user, ROTATION_CLOCKWISE, silent=TRUE))
|
||||
context[SCREENTIP_CONTEXT_ALT_LMB] = "Rotate left"
|
||||
rotation_screentip = TRUE
|
||||
if(can_be_rotated(user, ROTATION_COUNTERCLOCKWISE, silent=TRUE))
|
||||
context[SCREENTIP_CONTEXT_ALT_RMB] = "Rotate right"
|
||||
rotation_screentip = TRUE
|
||||
|
||||
if(rotation_screentip)
|
||||
return CONTEXTUAL_SCREENTIP_SET
|
||||
|
||||
return NONE
|
||||
@@ -9,8 +9,7 @@
|
||||
if(!ismob(parent))
|
||||
return COMPONENT_INCOMPATIBLE
|
||||
access = new_access
|
||||
RegisterSignal(parent, COMSIG_MOB_TRIED_ACCESS, PROC_REF(on_tried_access))
|
||||
RegisterSignal(parent, COMSIG_MOB_RETRIEVE_SIMPLE_ACCESS, PROC_REF(retrieve_access))
|
||||
RegisterSignal(parent, COMSIG_MOB_RETRIEVE_ACCESS, PROC_REF(retrieve_access))
|
||||
if(!donor_atom)
|
||||
return
|
||||
if(isorgan(donor_atom))
|
||||
@@ -19,16 +18,6 @@
|
||||
RegisterSignal(donor_atom, COMSIG_IMPLANT_REMOVED, PROC_REF(on_donor_removed))
|
||||
RegisterSignal(donor_atom, COMSIG_QDELETING, PROC_REF(on_donor_removed))
|
||||
|
||||
/datum/component/simple_access/proc/on_tried_access(datum/source, atom/locked_thing)
|
||||
SIGNAL_HANDLER
|
||||
if(!isobj(locked_thing))
|
||||
return LOCKED_ATOM_INCOMPATIBLE
|
||||
var/obj/locked_object = locked_thing
|
||||
if(locked_object.check_access_list(access))
|
||||
return ACCESS_ALLOWED
|
||||
else
|
||||
return ACCESS_DISALLOWED
|
||||
|
||||
/datum/component/simple_access/proc/retrieve_access(datum/source, list/access_list)
|
||||
SIGNAL_HANDLER
|
||||
access_list += access
|
||||
|
||||
@@ -0,0 +1,188 @@
|
||||
// A component for basic mobs that makes them able to slide under doors by right-clicking on them.
|
||||
// Sliding under doors has a configurable initial delay, and allows the mob to remain under the door indefinitely.
|
||||
// If the door is opened while the mob is under it, then the mob will be ejected from their hiding spot.
|
||||
// Instantly sliding under doors should be handled by giving the mob the PASSDOORS pass flag.
|
||||
|
||||
/datum/component/slide_under_doors
|
||||
can_transfer = TRUE
|
||||
|
||||
/// The delay for sliding under a door in deciseconds.
|
||||
var/slide_in_delay = 0
|
||||
/// The delay for sliding out from under a door in deciseconds.
|
||||
var/slide_out_delay = 0
|
||||
|
||||
/// The user that is currently under a door, if any.
|
||||
var/mob/living/current_user = null
|
||||
/// The door the user is currently under, if any.
|
||||
var/obj/machinery/door/current_door = null
|
||||
|
||||
/datum/component/slide_under_doors/Initialize(slide_in_delay = 5 SECONDS, slide_out_delay = 1 SECONDS)
|
||||
if (!isbasicmob(parent))
|
||||
return COMPONENT_INCOMPATIBLE
|
||||
|
||||
src.slide_in_delay = slide_in_delay
|
||||
src.slide_out_delay = slide_out_delay
|
||||
|
||||
/datum/component/slide_under_doors/RegisterWithParent()
|
||||
RegisterSignal(parent, COMSIG_MOB_CLIENT_LOGIN, PROC_REF(on_user_login))
|
||||
RegisterSignal(parent, COMSIG_LIVING_UNARMED_ATTACK, PROC_REF(on_user_unarmed_attack))
|
||||
|
||||
notify_user()
|
||||
|
||||
/datum/component/slide_under_doors/UnregisterFromParent()
|
||||
UnregisterSignal(parent, list(COMSIG_MOB_CLIENT_LOGIN, COMSIG_LIVING_UNARMED_ATTACK))
|
||||
|
||||
if (!QDELETED(current_user) && !QDELETED(current_door))
|
||||
slide_out_from_under_door()
|
||||
else if (current_user || current_door)
|
||||
unregister_user_and_door()
|
||||
|
||||
/datum/component/slide_under_doors/proc/on_user_login(mob/living/user, client/user_client)
|
||||
SIGNAL_HANDLER
|
||||
notify_user()
|
||||
|
||||
/datum/component/slide_under_doors/proc/notify_user()
|
||||
to_chat(parent, span_notice("You can slide under doors! <b>Right-click on a door to slide under it.</b>"))
|
||||
|
||||
/datum/component/slide_under_doors/proc/on_user_unarmed_attack(mob/living/user, atom/target, is_adjacent, modifiers)
|
||||
SIGNAL_HANDLER
|
||||
if (!modifiers[RIGHT_CLICK])
|
||||
return
|
||||
if (!istype(target, /obj/machinery/door))
|
||||
return
|
||||
|
||||
INVOKE_ASYNC(src, PROC_REF(try_slide_under_door), user, target)
|
||||
return COMPONENT_CANCEL_ATTACK_CHAIN
|
||||
|
||||
/datum/component/slide_under_doors/proc/try_slide_under_door(mob/living/user, obj/machinery/door/door)
|
||||
if (!can_slide_under_door(user, door))
|
||||
return
|
||||
|
||||
user.visible_message(
|
||||
message = span_danger("\The [user] start[user.p_s()] sliding under \the [door]!"),
|
||||
self_message = span_notice("You start sliding under \the [door]."),
|
||||
blind_message = span_hear("You hear squeezing."),
|
||||
)
|
||||
|
||||
playsound(user, 'sound/effects/footstep/gib_step.ogg', vol = 50, vary = TRUE, ignore_walls = FALSE)
|
||||
|
||||
if (!do_after(user, slide_in_delay, door, extra_checks = CALLBACK(src, PROC_REF(can_slide_under_door), user, door)))
|
||||
return
|
||||
|
||||
slide_under_door(user, door)
|
||||
|
||||
/datum/component/slide_under_doors/proc/can_slide_under_door(mob/living/user, obj/machinery/door/door)
|
||||
if (!user.Adjacent(door))
|
||||
return FALSE
|
||||
if (user.loc == door)
|
||||
return FALSE
|
||||
if (!isturf(user.loc))
|
||||
user.balloon_alert(user, "not on the ground!")
|
||||
return FALSE
|
||||
if (!door.IsReachableBy(user))
|
||||
user.balloon_alert(user, "can't reach!")
|
||||
return FALSE
|
||||
if (HAS_TRAIT(user, TRAIT_INCAPACITATED))
|
||||
user.balloon_alert(user, "incapacitated!")
|
||||
return FALSE
|
||||
if (!(door.pass_flags_self & PASSDOORS))
|
||||
door.balloon_alert(user, "impassable!")
|
||||
return FALSE
|
||||
if (!door.density)
|
||||
door.balloon_alert(user, "already open!")
|
||||
return FALSE
|
||||
return TRUE
|
||||
|
||||
/datum/component/slide_under_doors/proc/slide_under_door(mob/living/user, obj/machinery/door/door)
|
||||
user.visible_message(
|
||||
message = span_danger("\The [user] slide[user.p_s()] under \the [door] with a pop!"),
|
||||
self_message = span_notice("You slide under \the [door] with a pop!"),
|
||||
blind_message = span_hear("You hear a pop."),
|
||||
)
|
||||
|
||||
playsound(user, 'sound/effects/meatslap.ogg', vol = 50, vary = TRUE, ignore_walls = FALSE)
|
||||
|
||||
user.forceMove(door)
|
||||
|
||||
register_user_and_door(user, door)
|
||||
|
||||
/datum/component/slide_under_doors/proc/try_slide_out_from_under_door(move_dir)
|
||||
if (DOING_INTERACTION_WITH_TARGET(current_user, current_door))
|
||||
return
|
||||
|
||||
var/turf/visible_turf = get_step(current_user, move_dir)
|
||||
|
||||
if (visible_turf)
|
||||
visible_turf.visible_message(
|
||||
message = span_danger("Something starts sliding out from under \the [current_door]!"),
|
||||
blind_message = span_hear("You hear squeezing."),
|
||||
ignored_mobs = current_user,
|
||||
)
|
||||
|
||||
playsound(visible_turf, 'sound/effects/footstep/gib_step.ogg', vol = 50, vary = TRUE, ignore_walls = FALSE)
|
||||
|
||||
to_chat(current_user, span_notice("You start sliding out from under \the [current_door]."))
|
||||
|
||||
if (!do_after(current_user, slide_out_delay, current_door, timed_action_flags = IGNORE_INCAPACITATED))
|
||||
return
|
||||
|
||||
slide_out_from_under_door(move_dir)
|
||||
|
||||
/datum/component/slide_under_doors/proc/slide_out_from_under_door(move_dir)
|
||||
// We need to store local references to the user and the door before unregistering them.
|
||||
var/mob/living/user = current_user
|
||||
var/obj/machinery/door/door = current_door
|
||||
|
||||
unregister_user_and_door()
|
||||
|
||||
user.forceMove(door.drop_location())
|
||||
|
||||
if (move_dir)
|
||||
user.Move(get_step(user, move_dir), move_dir)
|
||||
|
||||
user.visible_message(
|
||||
message = span_danger("\The [user] slide[user.p_s()] out from under \the [door] with a pop!"),
|
||||
self_message = span_notice("You slide out from under \the [door] with a pop!"),
|
||||
blind_message = span_hear("You hear a pop."),
|
||||
)
|
||||
|
||||
playsound(user, 'sound/effects/meatslap.ogg', vol = 50, vary = TRUE, ignore_walls = FALSE)
|
||||
|
||||
/datum/component/slide_under_doors/proc/register_user_and_door(mob/living/user, obj/machinery/door/door)
|
||||
ADD_TRAIT(user, TRAIT_INCAPACITATED, UNDER_DOOR_TRAIT)
|
||||
RegisterSignal(user, COMSIG_MOVABLE_MOVED, PROC_REF(on_user_moved))
|
||||
current_user = user
|
||||
|
||||
RegisterSignal(door, COMSIG_QDELETING, PROC_REF(on_door_qdeleting))
|
||||
RegisterSignal(door, COMSIG_ATOM_DENSITY_CHANGED, PROC_REF(on_door_density_changed))
|
||||
RegisterSignal(door, COMSIG_ATOM_RELAYMOVE, PROC_REF(on_door_relaymove))
|
||||
current_door = door
|
||||
|
||||
/datum/component/slide_under_doors/proc/unregister_user_and_door()
|
||||
if (current_user)
|
||||
REMOVE_TRAITS_IN(current_user, UNDER_DOOR_TRAIT)
|
||||
UnregisterSignal(current_user, COMSIG_MOVABLE_MOVED)
|
||||
current_user = null
|
||||
|
||||
if (current_door)
|
||||
UnregisterSignal(current_door, list(COMSIG_QDELETING, COMSIG_ATOM_DENSITY_CHANGED, COMSIG_ATOM_RELAYMOVE))
|
||||
current_door = null
|
||||
|
||||
/datum/component/slide_under_doors/proc/on_user_moved(mob/living/user, obj/machinery/door/door, dir, forced, list/old_locs)
|
||||
SIGNAL_HANDLER
|
||||
if (user.loc != door)
|
||||
unregister_user_and_door()
|
||||
|
||||
/datum/component/slide_under_doors/proc/on_door_qdeleting(obj/machinery/door/door, force)
|
||||
SIGNAL_HANDLER
|
||||
unregister_user_and_door()
|
||||
|
||||
/datum/component/slide_under_doors/proc/on_door_density_changed(obj/machinery/door/door)
|
||||
SIGNAL_HANDLER
|
||||
if (!door.density)
|
||||
slide_out_from_under_door()
|
||||
|
||||
/datum/component/slide_under_doors/proc/on_door_relaymove(obj/machinery/door/door, mob/living/user, move_dir)
|
||||
SIGNAL_HANDLER
|
||||
INVOKE_ASYNC(src, PROC_REF(try_slide_out_from_under_door), move_dir)
|
||||
return COMSIG_BLOCK_RELAYMOVE
|
||||
@@ -32,13 +32,13 @@
|
||||
if(!isliving(parent))
|
||||
return COMPONENT_INCOMPATIBLE
|
||||
var/mob/living/living_parent = parent
|
||||
living_parent.faction |= FACTION_SLIME
|
||||
living_parent.add_faction(FACTION_SLIME)
|
||||
RegisterSignal(living_parent, COMSIG_ENTER_AREA, PROC_REF(start_slime_prodaction))
|
||||
|
||||
/datum/component/slime_friends/Destroy(force)
|
||||
. = ..()
|
||||
var/mob/living/living_parent = parent
|
||||
living_parent.faction -= FACTION_SLIME
|
||||
living_parent.remove_faction(FACTION_SLIME)
|
||||
timer = null
|
||||
|
||||
/// Start slime prodaction when we leave wizden.
|
||||
@@ -56,6 +56,6 @@
|
||||
var/turf/where = get_turf(friend)
|
||||
var/new_colour = pick(colours)
|
||||
var/mob/living/basic/slime/new_friend = new(where, new_colour, SLIME_LIFE_STAGE_ADULT)
|
||||
new_friend.faction = friend.faction.Copy()
|
||||
SET_FACTION_AND_ALLIES_FROM(new_friend, friend)
|
||||
new_friend.set_enraged_behaviour()
|
||||
friend.nutrition -= 50
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/datum/component/soapbox
|
||||
/// List of our current soapboxxer(s) who are gaining loud speech
|
||||
var/list/soapboxers = list()
|
||||
var/list/soapboxers
|
||||
/// Gives atoms moving over us the soapbox speech and takes it away when they leave
|
||||
var/static/list/loc_connections = list(
|
||||
COMSIG_ATOM_ENTERED = PROC_REF(on_loc_entered),
|
||||
@@ -21,21 +21,21 @@
|
||||
if(QDELETED(soapbox_arrive))
|
||||
return
|
||||
RegisterSignal(soapbox_arrive, COMSIG_MOB_SAY, PROC_REF(soapbox_speech))
|
||||
soapboxers += soapbox_arrive
|
||||
LAZYADD(soapboxers, soapbox_arrive)
|
||||
|
||||
///Takes away loud speech from our movable when it leaves the turf our parent is on
|
||||
/datum/component/soapbox/proc/on_loc_exited(datum/source, mob/living/soapbox_leave)
|
||||
SIGNAL_HANDLER
|
||||
if(soapbox_leave in soapboxers)
|
||||
UnregisterSignal(soapbox_leave, COMSIG_MOB_SAY)
|
||||
soapboxers -= soapbox_leave
|
||||
LAZYREMOVE(soapboxers, soapbox_leave)
|
||||
|
||||
///We don't want our soapboxxer to keep their loud say if the parent is moved out from under them
|
||||
/datum/component/soapbox/proc/parent_moved(datum/source)
|
||||
SIGNAL_HANDLER
|
||||
for(var/atom/movable/loud as anything in soapboxers)
|
||||
UnregisterSignal(loud, COMSIG_MOB_SAY)
|
||||
soapboxers.Cut()
|
||||
LAZYNULL(soapboxers)
|
||||
|
||||
///Gives a mob a unique say span
|
||||
/datum/component/soapbox/proc/soapbox_speech(datum/source, list/speech_args)
|
||||
|
||||
@@ -94,19 +94,16 @@
|
||||
|
||||
if (isliving(created))
|
||||
var/mob/living/created_mob = created
|
||||
created_mob.faction = src.faction
|
||||
created_mob.set_faction(faction)
|
||||
RegisterSignal(created, COMSIG_MOB_STATCHANGE, PROC_REF(mob_stat_changed))
|
||||
|
||||
SEND_SIGNAL(src, COMSIG_SPAWNER_SPAWNED, created)
|
||||
RegisterSignal(created, COMSIG_QDELETING, PROC_REF(on_deleted))
|
||||
spawn_callback?.Invoke(created)
|
||||
|
||||
|
||||
if (spawn_text)
|
||||
spawner.visible_message(span_danger("A creature [spawn_text] [spawner]."))
|
||||
|
||||
|
||||
|
||||
/// Remove weakrefs to atoms which have been killed or deleted without us picking it up somehow
|
||||
/datum/component/spawner/proc/validate_references()
|
||||
for (var/datum/weakref/weak_thing as anything in spawned_things)
|
||||
|
||||
@@ -84,6 +84,7 @@
|
||||
var/atom/parent_atom = parent
|
||||
|
||||
sticker_overlay = mutable_appearance(icon = our_sticker.icon, icon_state = our_sticker.icon_state, layer = parent_atom.layer + 0.01, appearance_flags = RESET_COLOR)
|
||||
sticker_overlay.color = our_sticker.color
|
||||
sticker_overlay.pixel_w = px - ICON_SIZE_X / 2
|
||||
sticker_overlay.pixel_z = py - ICON_SIZE_Y / 2
|
||||
|
||||
|
||||
@@ -326,7 +326,7 @@
|
||||
return
|
||||
|
||||
var/mob/living/disrespected = attacked_atom
|
||||
if(disrespected.stat || faction_check(punching_person.faction, disrespected.faction) || !(FACTION_MINING in disrespected.faction))
|
||||
if(disrespected.stat || disrespected.faction_check_atom(punching_person) || !disrespected.has_faction(FACTION_MINING))
|
||||
return
|
||||
|
||||
add_action(ACTION_DISRESPECT, 60 * (ismegafauna(disrespected) ? 2 : 1))
|
||||
@@ -339,7 +339,7 @@
|
||||
|
||||
var/mob/living/attacked = attacked_mob
|
||||
var/mob/mob_parent = parent
|
||||
if(faction_check(attacking_person.faction, attacked.faction) || !(FACTION_MINING in attacked.faction) || (istype(mob_parent.get_active_held_item(), /obj/item/kinetic_crusher) && attacked.has_status_effect(/datum/status_effect/crusher_mark)))
|
||||
if(attacking_person.faction_check_atom(attacked) || !attacked.has_faction(FACTION_MINING) || (istype(mob_parent.get_active_held_item(), /obj/item/kinetic_crusher) && attacked.has_status_effect(/datum/status_effect/crusher_mark)))
|
||||
return
|
||||
|
||||
add_action(ACTION_MELEED, 50 * (ismegafauna(attacked) ? 1.5 : 1))
|
||||
@@ -373,7 +373,7 @@
|
||||
/datum/component/style/proc/on_resonator_burst(datum/source, mob/creator, mob/living/hit_living)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
if(faction_check(creator.faction, hit_living.faction) || (hit_living.stat != CONSCIOUS) || !(FACTION_MINING in hit_living.faction))
|
||||
if(creator.faction_check_atom(hit_living) || (hit_living.stat != CONSCIOUS) || !hit_living.has_faction(FACTION_MINING))
|
||||
return
|
||||
|
||||
add_action(ACTION_TRAPPER, 70)
|
||||
@@ -429,7 +429,7 @@
|
||||
if(died == parent)
|
||||
change_points(-500, use_multiplier = FALSE)
|
||||
return
|
||||
else if(faction_check(mob_parent.faction, died.faction) || !(FACTION_MINING in died.faction) || (died.z != mob_parent.z) || !(died in view(mob_parent.client?.view, get_turf(mob_parent))))
|
||||
else if(mob_parent.faction_check_atom(died) || !died.has_faction(FACTION_MINING) || (died.z != mob_parent.z) || !(died in view(mob_parent.client?.view, get_turf(mob_parent))))
|
||||
return
|
||||
if(ismegafauna(died))
|
||||
add_action(ACTION_MAJOR_KILL, 350)
|
||||
|
||||
@@ -76,8 +76,8 @@
|
||||
var/mob/living/simple_animal/hostile/angry_boy = summoned
|
||||
angry_boy.friends |= summoner // do not attack our summon boy
|
||||
spawned_mobs |= summoned
|
||||
if(faction != null)
|
||||
summoned.faction = faction.Copy()
|
||||
if(faction)
|
||||
summoned.set_faction(faction)
|
||||
RegisterSignals(summoned, list(COMSIG_LIVING_DEATH, COMSIG_QDELETING), PROC_REF(on_spawned_death))
|
||||
spawn_location.visible_message(span_danger("[summoned] [spawn_text]!"))
|
||||
|
||||
|
||||
@@ -1,363 +0,0 @@
|
||||
/// Allows an item to be used to initiate surgeries.
|
||||
/datum/component/surgery_initiator
|
||||
/// The currently selected target that the user is proposing a surgery on
|
||||
var/datum/weakref/surgery_target_ref
|
||||
|
||||
/// The last user, as a weakref
|
||||
var/datum/weakref/last_user_ref
|
||||
|
||||
/datum/component/surgery_initiator/Initialize()
|
||||
. = ..()
|
||||
if(!isitem(parent))
|
||||
return COMPONENT_INCOMPATIBLE
|
||||
|
||||
var/obj/item/surgery_tool = parent
|
||||
surgery_tool.item_flags |= ITEM_HAS_CONTEXTUAL_SCREENTIPS
|
||||
///Make sure we can use the surgery UI while laying down
|
||||
surgery_tool.interaction_flags_atom |= INTERACT_ATOM_IGNORE_MOBILITY
|
||||
|
||||
/datum/component/surgery_initiator/Destroy(force)
|
||||
last_user_ref = null
|
||||
surgery_target_ref = null
|
||||
|
||||
return ..()
|
||||
|
||||
/datum/component/surgery_initiator/RegisterWithParent()
|
||||
RegisterSignal(parent, COMSIG_ITEM_ATTACK, PROC_REF(initiate_surgery_moment))
|
||||
RegisterSignal(parent, COMSIG_ITEM_REQUESTING_CONTEXT_FOR_TARGET, PROC_REF(add_item_context))
|
||||
|
||||
/datum/component/surgery_initiator/UnregisterFromParent()
|
||||
UnregisterSignal(parent, COMSIG_ITEM_ATTACK, COMSIG_ITEM_REQUESTING_CONTEXT_FOR_TARGET)
|
||||
unregister_signals()
|
||||
|
||||
/datum/component/surgery_initiator/proc/unregister_signals()
|
||||
var/mob/living/last_user = last_user_ref?.resolve()
|
||||
if (!isnull(last_user_ref))
|
||||
UnregisterSignal(last_user, COMSIG_MOB_SELECTED_ZONE_SET)
|
||||
|
||||
var/mob/living/surgery_target = surgery_target_ref?.resolve()
|
||||
if (!isnull(surgery_target_ref))
|
||||
UnregisterSignal(surgery_target, COMSIG_MOB_SURGERY_STARTED)
|
||||
|
||||
/// Does the surgery initiation.
|
||||
/datum/component/surgery_initiator/proc/initiate_surgery_moment(datum/source, atom/target, mob/user)
|
||||
SIGNAL_HANDLER
|
||||
if(!isliving(target))
|
||||
return
|
||||
INVOKE_ASYNC(src, PROC_REF(do_initiate_surgery_moment), target, user)
|
||||
return COMPONENT_CANCEL_ATTACK_CHAIN
|
||||
|
||||
/datum/component/surgery_initiator/proc/do_initiate_surgery_moment(mob/living/target, mob/user)
|
||||
var/datum/surgery/current_surgery
|
||||
|
||||
for(var/i_one in target.surgeries)
|
||||
var/datum/surgery/surgeryloop = i_one
|
||||
if(surgeryloop.location == user.zone_selected)
|
||||
current_surgery = surgeryloop
|
||||
break
|
||||
|
||||
if (!isnull(current_surgery) && !current_surgery.step_in_progress)
|
||||
attempt_cancel_surgery(current_surgery, target, user)
|
||||
return
|
||||
|
||||
var/list/available_surgeries = get_available_surgeries(user, target)
|
||||
|
||||
if(!length(available_surgeries))
|
||||
if (target.body_position == LYING_DOWN || !(target.mobility_flags & MOBILITY_LIEDOWN))
|
||||
target.balloon_alert(user, "no surgeries available!")
|
||||
else
|
||||
target.balloon_alert(user, "make them lie down!")
|
||||
|
||||
return
|
||||
|
||||
unregister_signals()
|
||||
|
||||
last_user_ref = WEAKREF(user)
|
||||
surgery_target_ref = WEAKREF(target)
|
||||
|
||||
RegisterSignal(user, COMSIG_MOB_SELECTED_ZONE_SET, PROC_REF(on_set_selected_zone))
|
||||
RegisterSignal(target, COMSIG_MOB_SURGERY_STARTED, PROC_REF(on_mob_surgery_started))
|
||||
|
||||
ui_interact(user)
|
||||
|
||||
/datum/component/surgery_initiator/proc/get_available_surgeries(mob/user, mob/living/target)
|
||||
var/list/available_surgeries = list()
|
||||
|
||||
var/obj/item/bodypart/affecting = target.get_bodypart(check_zone(user.zone_selected))
|
||||
|
||||
for(var/datum/surgery/surgery as anything in GLOB.surgeries_list)
|
||||
if(!surgery.possible_locs.Find(user.zone_selected))
|
||||
continue
|
||||
if(!is_type_in_list(target, surgery.target_mobtypes))
|
||||
continue
|
||||
if(user == target && !HAS_TRAIT(user, TRAIT_SELF_SURGERY) && !(surgery.surgery_flags & SURGERY_SELF_OPERABLE))
|
||||
continue
|
||||
|
||||
if(isnull(affecting))
|
||||
if(surgery.surgery_flags & SURGERY_REQUIRE_LIMB)
|
||||
continue
|
||||
else
|
||||
if(surgery.requires_bodypart_type && !(affecting.bodytype & surgery.requires_bodypart_type))
|
||||
continue
|
||||
if(surgery.targetable_wound && !affecting.get_wound_type(surgery.targetable_wound))
|
||||
continue
|
||||
if((surgery.surgery_flags & SURGERY_REQUIRES_REAL_LIMB) && (affecting.bodypart_flags & BODYPART_PSEUDOPART))
|
||||
continue
|
||||
|
||||
if(IS_IN_INVALID_SURGICAL_POSITION(target, surgery))
|
||||
continue
|
||||
if(!surgery.can_start(user, target))
|
||||
continue
|
||||
|
||||
available_surgeries += surgery
|
||||
|
||||
return available_surgeries
|
||||
|
||||
/// Does the surgery de-initiation.
|
||||
/datum/component/surgery_initiator/proc/attempt_cancel_surgery(datum/surgery/the_surgery, mob/living/patient, mob/user)
|
||||
var/selected_zone = user.zone_selected
|
||||
|
||||
if(the_surgery.status == 1)
|
||||
patient.surgeries -= the_surgery
|
||||
user.visible_message(
|
||||
span_notice("[user] removes [parent] from [patient]'s [patient.parse_zone_with_bodypart(selected_zone)]."),
|
||||
span_notice("You remove [parent] from [patient]'s [patient.parse_zone_with_bodypart(selected_zone)]."),
|
||||
)
|
||||
|
||||
patient.balloon_alert(user, "stopped work on [patient.parse_zone_with_bodypart(selected_zone)]")
|
||||
|
||||
qdel(the_surgery)
|
||||
return
|
||||
|
||||
var/required_tool_type = TOOL_CAUTERY
|
||||
var/obj/item/close_tool = user.get_inactive_held_item()
|
||||
var/is_robotic = the_surgery.requires_bodypart_type == BODYTYPE_ROBOTIC
|
||||
if(is_robotic)
|
||||
required_tool_type = TOOL_SCREWDRIVER
|
||||
|
||||
if(!close_tool || close_tool.tool_behaviour != required_tool_type)
|
||||
patient.balloon_alert(user, "need a [is_robotic ? "screwdriver": "cautery"] in your inactive hand to stop the surgery!")
|
||||
return
|
||||
|
||||
if(the_surgery.operated_bodypart)
|
||||
the_surgery.operated_bodypart.adjustBleedStacks(-5)
|
||||
|
||||
patient.surgeries -= the_surgery
|
||||
|
||||
user.visible_message(
|
||||
span_notice("[user] closes [patient]'s [patient.parse_zone_with_bodypart(selected_zone)] with [close_tool] and removes [parent]."),
|
||||
span_notice("You close [patient]'s [patient.parse_zone_with_bodypart(selected_zone)] with [close_tool] and remove [parent]."),
|
||||
)
|
||||
|
||||
patient.balloon_alert(user, "closed up [patient.parse_zone_with_bodypart(selected_zone)]")
|
||||
|
||||
qdel(the_surgery)
|
||||
|
||||
/datum/component/surgery_initiator/proc/on_mob_surgery_started(mob/source, datum/surgery/surgery, surgery_location)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
var/mob/living/last_user = last_user_ref.resolve()
|
||||
|
||||
if (surgery_location != last_user.zone_selected)
|
||||
return
|
||||
|
||||
if (!isnull(last_user))
|
||||
source.balloon_alert(last_user, "someone else started a surgery!")
|
||||
|
||||
ui_close()
|
||||
|
||||
/datum/component/surgery_initiator/proc/on_set_selected_zone(mob/source, new_zone)
|
||||
ui_interact(source)
|
||||
|
||||
/datum/component/surgery_initiator/ui_interact(mob/user, datum/tgui/ui)
|
||||
ui = SStgui.try_update_ui(user, src, ui)
|
||||
if (!ui)
|
||||
ui = new(user, src, "SurgeryInitiator")
|
||||
ui.open()
|
||||
|
||||
/datum/component/surgery_initiator/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
|
||||
. = ..()
|
||||
if (.)
|
||||
return .
|
||||
|
||||
var/mob/user = usr
|
||||
var/mob/living/surgery_target = surgery_target_ref.resolve()
|
||||
|
||||
if (isnull(surgery_target))
|
||||
return TRUE
|
||||
|
||||
switch (action)
|
||||
if ("change_zone")
|
||||
var/zone = params["new_zone"]
|
||||
if (!(zone in list(
|
||||
BODY_ZONE_HEAD,
|
||||
BODY_ZONE_CHEST,
|
||||
BODY_ZONE_L_ARM,
|
||||
BODY_ZONE_R_ARM,
|
||||
BODY_ZONE_L_LEG,
|
||||
BODY_ZONE_R_LEG,
|
||||
BODY_ZONE_PRECISE_EYES,
|
||||
BODY_ZONE_PRECISE_MOUTH,
|
||||
BODY_ZONE_PRECISE_GROIN,
|
||||
)))
|
||||
return TRUE
|
||||
|
||||
var/atom/movable/screen/zone_sel/zone_selector = user.hud_used?.zone_select
|
||||
zone_selector?.set_selected_zone(zone, user, should_log = FALSE)
|
||||
|
||||
return TRUE
|
||||
if ("start_surgery")
|
||||
for (var/datum/surgery/surgery as anything in get_available_surgeries(user, surgery_target))
|
||||
if (surgery.name == params["surgery_name"])
|
||||
try_choose_surgery(user, surgery_target, surgery)
|
||||
return TRUE
|
||||
|
||||
/datum/component/surgery_initiator/ui_assets(mob/user)
|
||||
return list(
|
||||
get_asset_datum(/datum/asset/simple/body_zones),
|
||||
)
|
||||
|
||||
/datum/component/surgery_initiator/ui_data(mob/user)
|
||||
var/mob/living/surgery_target = surgery_target_ref?.resolve()
|
||||
|
||||
var/list/surgeries = list()
|
||||
if (!isnull(surgery_target))
|
||||
for (var/datum/surgery/surgery as anything in get_available_surgeries(user, surgery_target))
|
||||
var/list/surgery_info = list(
|
||||
"name" = surgery.name,
|
||||
)
|
||||
|
||||
if (surgery_needs_exposure(surgery, surgery_target))
|
||||
surgery_info["blocked"] = TRUE
|
||||
surgery_info["blocked_reason"] = "Their body is covered!" // SKYRAT EDIT ADDITION - Surgically unremovable bodyparts
|
||||
|
||||
// SKYRAT EDIT START - Surgically unremovable bodyparts
|
||||
if (surgery.removes_target_bodypart)
|
||||
if (iscarbon(surgery_target))
|
||||
var/mob/living/carbon/carbon_target = surgery_target
|
||||
var/obj/item/bodypart/affecting_limb = carbon_target.get_bodypart(check_zone(user.zone_selected))
|
||||
if(!affecting_limb.can_be_surgically_removed)
|
||||
surgery_info["blocked"] = TRUE
|
||||
surgery_info["blocked_reason"] = "That limb cannot be surgically removed!"
|
||||
// SKYRAT EDIT END
|
||||
|
||||
surgeries += list(surgery_info)
|
||||
|
||||
return list(
|
||||
"selected_zone" = user.zone_selected,
|
||||
"target_name" = surgery_target?.name,
|
||||
"surgeries" = surgeries,
|
||||
)
|
||||
|
||||
/datum/component/surgery_initiator/ui_close(mob/user)
|
||||
unregister_signals()
|
||||
surgery_target_ref = null
|
||||
|
||||
return ..()
|
||||
|
||||
/datum/component/surgery_initiator/ui_status(mob/user, datum/ui_state/state)
|
||||
var/mob/living/surgery_target = surgery_target_ref?.resolve()
|
||||
if (isnull(surgery_target))
|
||||
return UI_CLOSE
|
||||
|
||||
if (!can_start_surgery(user, surgery_target))
|
||||
return UI_CLOSE
|
||||
|
||||
return ..()
|
||||
|
||||
/datum/component/surgery_initiator/proc/can_start_surgery(mob/user, mob/living/target)
|
||||
if (!user.Adjacent(target))
|
||||
return FALSE
|
||||
|
||||
// The item was moved somewhere else
|
||||
var/atom/movable/tool = parent
|
||||
if (tool.loc != user)
|
||||
return FALSE
|
||||
|
||||
// While we were choosing, another surgery was started at the same location
|
||||
for (var/datum/surgery/surgery in target.surgeries)
|
||||
if (surgery.location == user.zone_selected)
|
||||
return FALSE
|
||||
|
||||
return TRUE
|
||||
|
||||
/datum/component/surgery_initiator/proc/try_choose_surgery(mob/user, mob/living/target, datum/surgery/surgery)
|
||||
if (!can_start_surgery(user, target))
|
||||
// This could have a more detailed message, but the UI closes when this is true anyway, so
|
||||
// if it ever comes up, it'll be because of lag.
|
||||
target.balloon_alert(user, "can't start the surgery!")
|
||||
return
|
||||
|
||||
var/selected_zone = user.zone_selected
|
||||
var/obj/item/bodypart/affecting_limb = target.get_bodypart(check_zone(selected_zone))
|
||||
|
||||
if ((surgery.surgery_flags & SURGERY_REQUIRE_LIMB) && isnull(affecting_limb))
|
||||
target.balloon_alert(user, "patient has no [parse_zone(selected_zone)]!")
|
||||
return
|
||||
|
||||
if (!isnull(affecting_limb))
|
||||
if(surgery.requires_bodypart_type && !(affecting_limb.bodytype & surgery.requires_bodypart_type))
|
||||
target.balloon_alert(user, "not the right type of limb!")
|
||||
return
|
||||
if(surgery.targetable_wound && !affecting_limb.get_wound_type(surgery.targetable_wound))
|
||||
target.balloon_alert(user, "no wound to operate on!")
|
||||
return
|
||||
|
||||
// SKYRAT EDIT START - Limbs that can't be surgically removed
|
||||
if (surgery.removes_target_bodypart && !isnull(affecting_limb) && !affecting_limb.can_be_surgically_removed)
|
||||
target.balloon_alert(user, "limb can't be surgically removed!")
|
||||
return
|
||||
// SKYRAT EDIT END
|
||||
|
||||
if (IS_IN_INVALID_SURGICAL_POSITION(target, surgery))
|
||||
target.balloon_alert(user, "patient is not lying down!")
|
||||
return
|
||||
|
||||
if (!surgery.can_start(user, target))
|
||||
target.balloon_alert(user, "can't start the surgery!")
|
||||
return
|
||||
|
||||
if (surgery_needs_exposure(surgery, target))
|
||||
target.balloon_alert(user, "expose [target.p_their()] [target.parse_zone_with_bodypart(selected_zone)]!")
|
||||
return
|
||||
|
||||
ui_close()
|
||||
|
||||
var/datum/surgery/procedure = new surgery.type(target, selected_zone, affecting_limb)
|
||||
|
||||
target.balloon_alert(user, "starting \"[LOWER_TEXT(procedure.name)]\"")
|
||||
|
||||
user.visible_message(
|
||||
span_notice("[user] drapes [parent] over [target]'s [target.parse_zone_with_bodypart(selected_zone)] to prepare for surgery."),
|
||||
span_notice("You drape [parent] over [target]'s [target.parse_zone_with_bodypart(selected_zone)] to prepare for \an [procedure.name]."),
|
||||
)
|
||||
|
||||
log_combat(user, target, "operated on", null, "(OPERATION TYPE: [procedure.name]) (TARGET AREA: [selected_zone])")
|
||||
|
||||
return TRUE // BUBBER EDIT ADDITION - For modular_zubbers/code/datums/components/surgery_initiator.dm
|
||||
|
||||
/datum/component/surgery_initiator/proc/surgery_needs_exposure(datum/surgery/surgery, mob/living/target)
|
||||
var/mob/living/user = last_user_ref?.resolve()
|
||||
if (isnull(user))
|
||||
return FALSE
|
||||
if(surgery.surgery_flags & SURGERY_IGNORE_CLOTHES)
|
||||
return FALSE
|
||||
|
||||
return !get_location_accessible(target, user.zone_selected)
|
||||
|
||||
/**
|
||||
* Adds context sensitivy directly to the surgery initator file for screentips
|
||||
* Arguments:
|
||||
* * source - the surgery drapes, cloak, or bedsheet calling surgery initator
|
||||
* * context - Preparing Surgery, the component has a lot of ballon alerts to deal with most contexts
|
||||
* * target - the living target mob you are doing surgery on
|
||||
* * user - refers to user who will see the screentip when the drapes are on a living target
|
||||
*/
|
||||
/datum/component/surgery_initiator/proc/add_item_context(obj/item/source, list/context, atom/target, mob/living/user,)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
if(!isliving(target))
|
||||
return NONE
|
||||
|
||||
context[SCREENTIP_CONTEXT_LMB] = "Prepare Surgery"
|
||||
return CONTEXTUAL_SCREENTIP_SET
|
||||
@@ -0,0 +1,39 @@
|
||||
/// Used for tracking mobs that have had synthetic blood injected into them.
|
||||
/// The higher the synth content, the more nerfed the blood is for blood worms.
|
||||
/// This decays over time while the mob is alive.
|
||||
/datum/component/synth_blood
|
||||
// This has a performance improvement over COMPONENT_DUPE_HIGHLANDER as it skips creating a new component instance.
|
||||
dupe_mode = COMPONENT_DUPE_UNIQUE_PASSARGS
|
||||
|
||||
/// 0-1 of how synthetic this mobs blood is right now.
|
||||
var/synth_content = 0
|
||||
|
||||
/datum/component/synth_blood/Initialize(new_synth_content = 0)
|
||||
if (!isliving(parent))
|
||||
return COMPONENT_INCOMPATIBLE
|
||||
if (new_synth_content <= 0)
|
||||
return COMPONENT_REDUNDANT
|
||||
|
||||
synth_content = min(new_synth_content, 1)
|
||||
|
||||
RegisterSignal(parent, COMSIG_LIVING_LIFE, PROC_REF(on_life))
|
||||
|
||||
/datum/component/synth_blood/InheritComponent(datum/component/synth_blood/new_component, i_am_original, new_synth_content)
|
||||
if (new_component)
|
||||
new_synth_content = new_component.synth_content
|
||||
if (new_synth_content <= 0)
|
||||
qdel(src)
|
||||
|
||||
synth_content = min(new_synth_content, 1)
|
||||
|
||||
/datum/component/synth_blood/Destroy(force)
|
||||
UnregisterSignal(parent, COMSIG_LIVING_LIFE)
|
||||
return ..()
|
||||
|
||||
/datum/component/synth_blood/proc/on_life(mob/living/living_parent, seconds_per_tick, times_fired)
|
||||
SIGNAL_HANDLER
|
||||
if (synth_content <= 0)
|
||||
qdel(src)
|
||||
else if (living_parent.stat != DEAD)
|
||||
// Ten minutes (or 600 seconds) to fully clear synth content from one to zero.
|
||||
synth_content = max(synth_content - (1/600) * seconds_per_tick, 0)
|
||||
@@ -1,197 +0,0 @@
|
||||
/// Component which allows mobs to be smashed onto this surface like a wrestling move
|
||||
/datum/component/table_smash
|
||||
/// If true, mobs will be placed gently on the table even if they're in an aggressive grab
|
||||
var/gentle_push
|
||||
/// Callback invoked after a hostile table action
|
||||
var/datum/callback/after_smash
|
||||
|
||||
/datum/component/table_smash/Initialize(gentle_push = FALSE, after_smash = null)
|
||||
. = ..()
|
||||
if (!isobj(parent))
|
||||
return COMPONENT_INCOMPATIBLE
|
||||
|
||||
src.gentle_push = gentle_push
|
||||
src.after_smash = after_smash
|
||||
|
||||
/datum/component/table_smash/RegisterWithParent()
|
||||
RegisterSignal(parent, COMSIG_ATOM_ATTACK_HAND, PROC_REF(on_interaction))
|
||||
RegisterSignal(parent, COMSIG_ATOM_ITEM_INTERACTION, PROC_REF(on_item_interaction))
|
||||
|
||||
var/static/list/loc_connections = list(
|
||||
COMSIG_LIVING_DISARM_COLLIDE = PROC_REF(on_pushed_into),
|
||||
)
|
||||
AddComponent(/datum/component/connect_loc_behalf, parent, loc_connections)
|
||||
|
||||
/datum/component/table_smash/UnregisterFromParent()
|
||||
UnregisterSignal(parent, list(COMSIG_ATOM_ATTACK_HAND, COMSIG_ATOM_ITEM_INTERACTION))
|
||||
|
||||
/// Called when someone clicks on our surface
|
||||
/datum/component/table_smash/proc/on_interaction(obj/table, mob/user)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
if (!table.Adjacent(user) || !user.pulling)
|
||||
return
|
||||
|
||||
if (!isliving(user.pulling))
|
||||
if (!(user.pulling.pass_flags & PASSTABLE))
|
||||
return
|
||||
|
||||
user.Move_Pulled(table)
|
||||
|
||||
if (user.pulling.loc == table.loc)
|
||||
user.visible_message(span_notice("[user] places [user.pulling] onto [table]."),
|
||||
span_notice("You place [user.pulling] onto [table]."))
|
||||
user.stop_pulling()
|
||||
|
||||
return COMPONENT_CANCEL_ATTACK_CHAIN
|
||||
|
||||
var/mob/living/pushed_mob = user.pulling
|
||||
if (pushed_mob.buckled)
|
||||
if (pushed_mob.buckled == table)
|
||||
//Already buckled to the table, you probably meant to unbuckle them
|
||||
return
|
||||
|
||||
to_chat(user, span_warning("[pushed_mob] is buckled to [pushed_mob.buckled]!"))
|
||||
return COMPONENT_CANCEL_ATTACK_CHAIN
|
||||
|
||||
INVOKE_ASYNC(src, PROC_REF(perform_table_smash), table, user)
|
||||
return COMPONENT_CANCEL_ATTACK_CHAIN
|
||||
|
||||
/// We have a mob being pressed onto the table, but how strongly?
|
||||
/datum/component/table_smash/proc/perform_table_smash(obj/table, mob/living/user)
|
||||
var/mob/living/pushed_mob = user.pulling
|
||||
if (user.combat_mode)
|
||||
switch(user.grab_state)
|
||||
if (GRAB_PASSIVE)
|
||||
to_chat(user, span_warning("You need a better grip to do that!"))
|
||||
return
|
||||
if (GRAB_AGGRESSIVE)
|
||||
if (gentle_push)
|
||||
tableplace(user, pushed_mob)
|
||||
else
|
||||
tablepush(user, pushed_mob)
|
||||
if (GRAB_NECK to GRAB_KILL)
|
||||
tablelimbsmash(user, pushed_mob)
|
||||
else
|
||||
pushed_mob.visible_message(span_notice("[user] begins to place [pushed_mob] onto [table]..."), \
|
||||
span_userdanger("[user] begins to place [pushed_mob] onto [table]..."))
|
||||
if (do_after(user, 3.5 SECONDS, target = pushed_mob))
|
||||
tableplace(user, pushed_mob)
|
||||
else
|
||||
return
|
||||
|
||||
user.stop_pulling()
|
||||
|
||||
/// Called when someone clicks on our surface with an item
|
||||
/datum/component/table_smash/proc/on_item_interaction(obj/table, mob/living/user, obj/item/item, modifiers)
|
||||
SIGNAL_HANDLER
|
||||
if (!istype(item, /obj/item/riding_offhand))
|
||||
return NONE
|
||||
|
||||
var/obj/item/riding_offhand/riding_item = item
|
||||
var/mob/living/carried_mob = riding_item.rider
|
||||
if (carried_mob == user) //Piggyback user.
|
||||
return NONE
|
||||
|
||||
INVOKE_ASYNC(src, PROC_REF(riding_offhand_act), user, item)
|
||||
return ITEM_INTERACT_BLOCKING
|
||||
|
||||
/// Called when someone clicks on our surface using a fireman's carry
|
||||
/datum/component/table_smash/proc/riding_offhand_act(mob/living/user, obj/item/riding_offhand/riding_item)
|
||||
var/mob/living/carried_mob = riding_item.rider
|
||||
if (user.combat_mode)
|
||||
user.unbuckle_mob(carried_mob)
|
||||
tablelimbsmash(user, carried_mob)
|
||||
return ITEM_INTERACT_SUCCESS
|
||||
|
||||
var/tableplace_delay = 3.5 SECONDS
|
||||
var/skills_space = ""
|
||||
if (HAS_TRAIT(user, TRAIT_QUICKER_CARRY))
|
||||
tableplace_delay = 2 SECONDS
|
||||
skills_space = " expertly"
|
||||
else if (HAS_TRAIT(user, TRAIT_QUICK_CARRY))
|
||||
tableplace_delay = 2.75 SECONDS
|
||||
skills_space = " quickly"
|
||||
|
||||
var/obj/item/organ/cyberimp/chest/spine/potential_spine = user.get_organ_slot(ORGAN_SLOT_SPINE)
|
||||
if (istype(potential_spine))
|
||||
tableplace_delay *= potential_spine.athletics_boost_multiplier
|
||||
|
||||
carried_mob.visible_message(span_notice("[user] begins to[skills_space] place [carried_mob] onto [parent]..."),
|
||||
span_userdanger("[user] begins to[skills_space] place [carried_mob] onto [parent]..."))
|
||||
if (!do_after(user, tableplace_delay, target = carried_mob))
|
||||
return ITEM_INTERACT_BLOCKING
|
||||
user.unbuckle_mob(carried_mob)
|
||||
tableplace(user, carried_mob)
|
||||
return ITEM_INTERACT_SUCCESS
|
||||
|
||||
/// Gently place the mob onto the table
|
||||
/datum/component/table_smash/proc/tableplace(mob/living/user, mob/living/pushed_mob)
|
||||
var/obj/table = parent
|
||||
pushed_mob.forceMove(table.loc)
|
||||
pushed_mob.set_resting(TRUE, TRUE)
|
||||
pushed_mob.visible_message(span_notice("[user] places [pushed_mob] onto [parent]."), \
|
||||
span_notice("[user] places [pushed_mob] onto [parent]."))
|
||||
log_combat(user, pushed_mob, "places", null, "onto [parent]")
|
||||
|
||||
/// Aggressively smash the mob onto the table
|
||||
/datum/component/table_smash/proc/tablepush(mob/living/user, mob/living/pushed_mob)
|
||||
if (HAS_TRAIT(user, TRAIT_PACIFISM))
|
||||
to_chat(user, span_danger("Throwing [pushed_mob] onto the table might hurt them!"))
|
||||
return
|
||||
|
||||
var/passtable_key = REF(user)
|
||||
passtable_on(pushed_mob, passtable_key)
|
||||
for (var/obj/obj in user.loc.contents)
|
||||
if (!obj.CanAllowThrough(pushed_mob))
|
||||
return
|
||||
|
||||
var/obj/table = parent
|
||||
pushed_mob.Move(table.loc)
|
||||
passtable_off(pushed_mob, passtable_key)
|
||||
if (pushed_mob.loc != table.loc) //Something prevented the tabling
|
||||
return
|
||||
|
||||
pushed_mob.Knockdown(3 SECONDS)
|
||||
pushed_mob.apply_damage(10, BRUTE)
|
||||
pushed_mob.apply_damage(40, STAMINA)
|
||||
playsound(pushed_mob, 'sound/effects/tableslam.ogg', 90, TRUE)
|
||||
pushed_mob.visible_message(span_danger("[user] slams [pushed_mob] onto \the [parent]!"), \
|
||||
span_userdanger("[user] slams you onto \the [parent]!"))
|
||||
log_combat(user, pushed_mob, "tabled", null, "onto [parent]")
|
||||
pushed_mob.add_mood_event("table", /datum/mood_event/table)
|
||||
SEND_SIGNAL(user, COMSIG_LIVING_TABLE_SLAMMING, pushed_mob, parent)
|
||||
after_smash?.Invoke(pushed_mob)
|
||||
|
||||
/// Even more aggressively smash a single part of a mob onto the table
|
||||
/datum/component/table_smash/proc/tablelimbsmash(mob/living/user, mob/living/pushed_mob)
|
||||
var/obj/table = parent
|
||||
pushed_mob.Knockdown(3 SECONDS)
|
||||
var/obj/item/bodypart/banged_limb = pushed_mob.get_bodypart(user.zone_selected) || pushed_mob.get_bodypart(BODY_ZONE_HEAD)
|
||||
var/extra_wound = 0
|
||||
if (HAS_TRAIT(user, TRAIT_HULK))
|
||||
extra_wound = 20
|
||||
pushed_mob.apply_damage(30, BRUTE, banged_limb, wound_bonus = extra_wound)
|
||||
pushed_mob.apply_damage(60, STAMINA)
|
||||
playsound(pushed_mob, 'sound/effects/bang.ogg', 90, TRUE)
|
||||
pushed_mob.visible_message(span_danger("[user] smashes [pushed_mob]'s [banged_limb.plaintext_zone] against \the [parent]!"),
|
||||
span_userdanger("[user] smashes your [banged_limb.plaintext_zone] against \the [parent]"))
|
||||
log_combat(user, pushed_mob, "head slammed", null, "against [parent]")
|
||||
pushed_mob.add_mood_event("table", /datum/mood_event/table_limbsmash, banged_limb)
|
||||
table.take_damage(50)
|
||||
SEND_SIGNAL(user, COMSIG_LIVING_TABLE_LIMB_SLAMMING, pushed_mob, parent)
|
||||
after_smash?.Invoke(pushed_mob)
|
||||
|
||||
/// Called when someone is shoved into our tile
|
||||
/datum/component/table_smash/proc/on_pushed_into(turf/source, mob/living/shover, mob/living/target, shove_flags, obj/item/weapon)
|
||||
SIGNAL_HANDLER
|
||||
if((shove_flags & SHOVE_KNOCKDOWN_BLOCKED) || !(shove_flags & SHOVE_BLOCKED))
|
||||
return
|
||||
target.Knockdown(SHOVE_KNOCKDOWN_TABLE, daze_amount = 3 SECONDS)
|
||||
target.visible_message(span_danger("[shover.name] shoves [target.name] onto \the [parent]!"),
|
||||
span_userdanger("You're shoved onto \the [parent] by [shover.name]!"), span_hear("You hear aggressive shuffling followed by a loud thud!"), COMBAT_MESSAGE_RANGE, shover)
|
||||
to_chat(shover, span_danger("You shove [target.name] onto \the [parent]!"))
|
||||
target.throw_at(parent, 1, 1, null, FALSE) //1 speed throws with no spin are basically just forcemoves with a hard collision check
|
||||
log_combat(shover, target, "shoved", "onto [parent] (table)[weapon ? " with [weapon]" : ""]")
|
||||
after_smash?.Invoke(target)
|
||||
return COMSIG_LIVING_SHOVE_HANDLED
|
||||
@@ -48,6 +48,10 @@
|
||||
ADD_TRAIT(user, TRAIT_UNKNOWN_APPEARANCE, REF(src))
|
||||
|
||||
current_slot = slot
|
||||
source.pixel_x = source.base_pixel_x
|
||||
source.pixel_y = source.base_pixel_y
|
||||
source.pixel_w = source.base_pixel_w
|
||||
source.pixel_z = source.base_pixel_z
|
||||
|
||||
on_icon_update(source)
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@
|
||||
if(!isliving(parent))
|
||||
return FALSE // Figure this out when we actually need it
|
||||
var/mob/living/living_parent = parent
|
||||
return living_parent.faction.Find(REF(potential_friend))
|
||||
return living_parent.has_ally(potential_friend)
|
||||
|
||||
///Ran once taming succeeds
|
||||
/datum/component/tameable/proc/on_tame(atom/source, mob/living/tamer, obj/item/food, inform_tamer = FALSE)
|
||||
|
||||
@@ -91,7 +91,7 @@
|
||||
else
|
||||
end_windup()
|
||||
|
||||
/datum/component/throwbonus_on_windup/proc/on_hands_swap(mob/living/source)
|
||||
/datum/component/throwbonus_on_windup/proc/on_hands_swap(mob/living/source, obj/item/swapped_to, obj/item/swapped_from)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
if(source.get_active_held_item() != parent)
|
||||
|
||||
@@ -37,11 +37,11 @@
|
||||
var/udder_filled_percentage = PERCENT(udder.reagents.total_volume / udder.reagents.maximum_volume)
|
||||
switch(udder_filled_percentage)
|
||||
if(0 to 10)
|
||||
examine_list += span_notice("[parent]'s [udder] is dry.")
|
||||
examine_list += span_notice("[parent]'s [udder.name] is dry.")
|
||||
if(11 to 99)
|
||||
examine_list += span_notice("[parent]'s [udder] can be milked if you have something to contain it.")
|
||||
examine_list += span_notice("[parent]'s [udder.name] can be milked if you have something to contain it.")
|
||||
if(100)
|
||||
examine_list += span_notice("[parent]'s [udder] is round and full, and can be milked if you have something to contain it.")
|
||||
examine_list += span_notice("[parent]'s [udder.name] is round and full, and can be milked if you have something to contain it.")
|
||||
|
||||
|
||||
///signal called on parent being attacked with an item
|
||||
|
||||
@@ -17,10 +17,20 @@
|
||||
/// Overlay added when you are in danger
|
||||
var/state_danger
|
||||
|
||||
/// If null, does nothing
|
||||
/// If set to a ztrait, like ZTRAIT_MINING, we check zlevels with that trait for weather towers
|
||||
/// Having no active radar towers on those levels will reduce the accuracy of the warnings
|
||||
var/radar_z_trait
|
||||
|
||||
/// Modifier to the accuracy of the weather warning times
|
||||
/// Changes every unique storm event
|
||||
VAR_FINAL/accuracy_mod = 0
|
||||
|
||||
/datum/component/weather_announcer/Initialize(
|
||||
state_normal,
|
||||
state_warning,
|
||||
state_danger,
|
||||
radar_z_trait,
|
||||
)
|
||||
. = ..()
|
||||
if (!ismovable(parent))
|
||||
@@ -34,6 +44,7 @@
|
||||
src.state_normal = state_normal
|
||||
src.state_warning = state_warning
|
||||
src.state_danger = state_danger
|
||||
src.radar_z_trait = radar_z_trait
|
||||
var/atom/speaker = parent
|
||||
speaker.update_appearance(UPDATE_ICON)
|
||||
update_light_color()
|
||||
@@ -70,6 +81,14 @@
|
||||
var/atom/speaker = parent
|
||||
speaker.update_appearance(UPDATE_ICON)
|
||||
|
||||
/datum/component/weather_announcer/proc/check_accuracy()
|
||||
for(var/z_level in SSmapping.levels_by_trait(radar_z_trait))
|
||||
for(var/obj/machinery/power/weather_tower/radar as anything in GLOB.weather_towers["[z_level]"])
|
||||
if(radar.active)
|
||||
return TRUE
|
||||
|
||||
return FALSE
|
||||
|
||||
/datum/component/weather_announcer/process(seconds_per_tick)
|
||||
if (!enabled)
|
||||
return
|
||||
@@ -84,7 +103,7 @@
|
||||
var/obj/machinery/announcement_system/aas = get_announcement_system(/datum/aas_config_entry/weather, speaker, list(RADIO_CHANNEL_SUPPLY))
|
||||
// Active AAS will override default announcement lines
|
||||
if (aas)
|
||||
msg = aas.compile_config_message(/datum/aas_config_entry/weather, list(), !is_weather_dangerous ? 4 : warning_level + 1)
|
||||
msg = aas.compile_config_message(/datum/aas_config_entry/weather, list(), is_weather_dangerous ? warning_level + 1 : 4)
|
||||
// Stop toggling on radios for it, please!
|
||||
aas.broadcast(msg, list(RADIO_CHANNEL_SUPPLY))
|
||||
// Still say it, because you can be not on our level
|
||||
@@ -117,17 +136,17 @@
|
||||
return "Error in meteorological calculation. Please report this deviation to a trained programmer."
|
||||
|
||||
/datum/component/weather_announcer/proc/time_till_storm()
|
||||
var/list/mining_z_levels = SSmapping.levels_by_trait(ZTRAIT_MINING)
|
||||
var/list/mining_z_levels = SSmapping.levels_by_trait(radar_z_trait)
|
||||
if(!length(mining_z_levels))
|
||||
return // No problems if there are no mining z levels
|
||||
|
||||
|
||||
for(var/datum/weather/check_weather as anything in SSweather.processing)
|
||||
if(!(check_weather.weather_flags & WEATHER_BAROMETER) || check_weather.stage == WIND_DOWN_STAGE || check_weather.stage == END_STAGE)
|
||||
continue
|
||||
for (var/mining_level in mining_z_levels)
|
||||
if(mining_level in check_weather.impacted_z_levels)
|
||||
warning_level = WEATHER_ALERT_IMMINENT_OR_ACTIVE
|
||||
accuracy_mod = 0 // reset for next storm
|
||||
return 0
|
||||
|
||||
var/time_until_next = INFINITY
|
||||
@@ -135,6 +154,13 @@
|
||||
var/next_time = timeleft(SSweather.next_hit_by_zlevel["[mining_level ]"]) || INFINITY
|
||||
if (next_time && next_time < time_until_next)
|
||||
time_until_next = next_time
|
||||
|
||||
if(!check_accuracy())
|
||||
if(!accuracy_mod && radar_z_trait)
|
||||
accuracy_mod = rand(-9, 9) * 5 SECONDS
|
||||
|
||||
time_until_next = max(10 SECONDS, time_until_next + accuracy_mod)
|
||||
|
||||
return time_until_next
|
||||
|
||||
/// Polls existing weather for what kind of warnings we should be displaying.
|
||||
@@ -156,7 +182,7 @@
|
||||
for(var/datum/weather/check_weather as anything in SSweather.processing)
|
||||
if(!(check_weather.weather_flags & WEATHER_BAROMETER) || check_weather.stage == WIND_DOWN_STAGE || check_weather.stage == END_STAGE)
|
||||
continue
|
||||
var/list/mining_z_levels = SSmapping.levels_by_trait(ZTRAIT_MINING)
|
||||
var/list/mining_z_levels = SSmapping.levels_by_trait(radar_z_trait)
|
||||
for(var/mining_level in mining_z_levels)
|
||||
if(mining_level in check_weather.impacted_z_levels)
|
||||
is_weather_dangerous = (check_weather.weather_flags & FUNCTIONAL_WEATHER)
|
||||
@@ -167,10 +193,13 @@
|
||||
if(isnull(time_until_next))
|
||||
return
|
||||
if (time_until_next == 0)
|
||||
examine_texts += span_warning ("A storm is currently active, please seek shelter.")
|
||||
examine_texts += span_warning("A storm is currently active, please seek shelter.")
|
||||
else
|
||||
examine_texts += span_notice("The next storm is inbound in [DisplayTimeText(time_until_next)].")
|
||||
|
||||
if(!check_accuracy())
|
||||
examine_texts += span_smallnoticeital("Due to insufficient radar coverage, the timing of this forecast may be inaccurate.")
|
||||
|
||||
/datum/component/weather_announcer/RegisterWithParent()
|
||||
RegisterSignal(parent, COMSIG_ATOM_EXAMINE, PROC_REF(on_examine))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user