diff --git a/code/__DEFINES/reagents.dm b/code/__DEFINES/reagents.dm index e9d417699f2..03bb57f017b 100644 --- a/code/__DEFINES/reagents.dm +++ b/code/__DEFINES/reagents.dm @@ -91,6 +91,8 @@ #define REAGENT_IGNORE_STASIS (1<<6) ///This reagent won't be used in most randomized recipes. Meant for reagents that could be synthetized but are normally inaccessible or TOO hard to get. #define REAGENT_NO_RANDOM_RECIPE (1<<7) +///Does this reagent clean things? +#define REAGENT_CLEANS (1<<8) //Chemical reaction flags, for determining reaction specialties ///Convert into impure/pure on reaction completion diff --git a/code/_globalvars/bitfields.dm b/code/_globalvars/bitfields.dm index 5da614dd693..530b79d938b 100644 --- a/code/_globalvars/bitfields.dm +++ b/code/_globalvars/bitfields.dm @@ -322,6 +322,9 @@ DEFINE_BITFIELD(chemical_flags, list( "REAGENT_SNEAKYNAME" = REAGENT_SNEAKYNAME, "REAGENT_SPLITRETAINVOL" = REAGENT_SPLITRETAINVOL, "REAGENT_CAN_BE_SYNTHESIZED" = REAGENT_CAN_BE_SYNTHESIZED, + "REAGENT_IGNORE_STASIS" = REAGENT_IGNORE_STASIS, + "REAGENT_NO_RANDOM_RECIPE" = REAGENT_NO_RANDOM_RECIPE, + "REAGENT_CLEANS" = REAGENT_CLEANS, )) DEFINE_BITFIELD(reaction_flags, list( diff --git a/code/datums/looping_sounds/_looping_sound.dm b/code/datums/looping_sounds/_looping_sound.dm index b893ef6a6fa..d71a05d4312 100644 --- a/code/datums/looping_sounds/_looping_sound.dm +++ b/code/datums/looping_sounds/_looping_sound.dm @@ -1,6 +1,4 @@ /* - output_atoms (list of atoms) The destination(s) for the sounds - mid_sounds (list or soundfile) Since this can be either a list or a single soundfile you can have random sounds. May contain further lists but must contain a soundfile at the end. mid_length (num) The length to wait between playing mid_sounds @@ -60,7 +58,7 @@ return on_start() -/datum/looping_sound/proc/stop(null_parent) +/datum/looping_sound/proc/stop(null_parent = FALSE) if(null_parent) set_parent(null) if(!timerid) @@ -114,6 +112,9 @@ if(parent) RegisterSignal(parent, COMSIG_PARENT_QDELETING, .proc/handle_parent_del) +/datum/looping_sound/proc/is_active() + return !!timerid + /datum/looping_sound/proc/handle_parent_del(datum/source) SIGNAL_HANDLER set_parent(null) diff --git a/code/datums/looping_sounds/cyborg.dm b/code/datums/looping_sounds/cyborg.dm new file mode 100644 index 00000000000..499e61757c6 --- /dev/null +++ b/code/datums/looping_sounds/cyborg.dm @@ -0,0 +1,10 @@ +/datum/looping_sound/wash + mid_sounds = list('sound/creatures/cyborg/wash1.ogg' = 1, 'sound/creatures/cyborg/wash2.ogg' = 1) + mid_length = 1.5 SECONDS // This makes them overlap slightly, which works out well for masking the fade in/out + start_volume = 100 + start_sound = 'sound/creatures/cyborg/wash_start.ogg' + start_length = 3.6 SECONDS // again, slightly shorter then the real time of 4 seconds, will make the transition to midsounds more seemless + end_volume = 100 + end_sound = 'sound/creatures/cyborg/wash_end.ogg' + vary = TRUE + extra_range = 5 diff --git a/code/game/objects/items/mop.dm b/code/game/objects/items/mop.dm index 4f41f59adf5..8d7af51a9a2 100644 --- a/code/game/objects/items/mop.dm +++ b/code/game/objects/items/mop.dm @@ -39,7 +39,7 @@ //SKYRAT EDIT END /obj/item/mop/proc/clean(turf/A, mob/living/cleaner) - if(reagents.has_reagent(/datum/reagent/water, 1) || reagents.has_reagent(/datum/reagent/water/holywater, 1) || reagents.has_reagent(/datum/reagent/consumable/ethanol/vodka, 1) || reagents.has_reagent(/datum/reagent/space_cleaner, 1)) + if(reagents.has_chemical_flag(REAGENT_CLEANS, 1)) // If there's a cleaner with a mind, let's gain some experience! if(cleaner?.mind) var/total_experience_gain = 0 diff --git a/code/modules/mob/living/silicon/robot/robot.dm b/code/modules/mob/living/silicon/robot/robot.dm index f57bb0199e8..bd6e97976bd 100644 --- a/code/modules/mob/living/silicon/robot/robot.dm +++ b/code/modules/mob/living/silicon/robot/robot.dm @@ -768,11 +768,6 @@ for(var/trait in model.model_traits) ADD_TRAIT(src, trait, MODEL_TRAIT) - if(model.clean_on_move) - AddElement(/datum/element/cleaning) - else - RemoveElement(/datum/element/cleaning) - hat_offset = model.hat_offset INVOKE_ASYNC(src, .proc/updatename) diff --git a/code/modules/mob/living/silicon/robot/robot_model.dm b/code/modules/mob/living/silicon/robot/robot_model.dm index 14a8c434f55..dfe269a26f4 100644 --- a/code/modules/mob/living/silicon/robot/robot_model.dm +++ b/code/modules/mob/living/silicon/robot/robot_model.dm @@ -37,8 +37,6 @@ var/list/model_traits = null ///List of radio channels added to the cyborg var/list/radio_channels = list() - ///Do we clean when we move - var/clean_on_move = FALSE ///Whether the borg loses tool slots with damage. var/breakable_modules = TRUE ///Whether swapping to this configuration should lockcharge the borg @@ -361,14 +359,212 @@ /obj/item/reagent_containers/glass/bucket, /obj/item/paint/paint_remover, /obj/item/lightreplacer/cyborg, - /obj/item/holosign_creator/janibarrier, + /obj/item/holosign_creator, /obj/item/reagent_containers/spray/cyborg_drying) radio_channels = list(RADIO_CHANNEL_SERVICE) emag_modules = list(/obj/item/reagent_containers/spray/cyborg_lube) cyborg_base_icon = "janitor" model_select_icon = "janitor" hat_offset = -5 - clean_on_move = TRUE + /// Weakref to the wash toggle action we own + var/datum/weakref/wash_toggle_ref + +/obj/item/robot_model/janitor/be_transformed_to(obj/item/robot_model/old_model, forced = FALSE) + . = ..() + if(!.) + return + var/datum/action/wash_toggle = new /datum/action/toggle_buffer(loc) + wash_toggle.Grant(loc) + wash_toggle_ref = WEAKREF(wash_toggle) + +/obj/item/robot_model/janitor/Destroy() + QDEL_NULL(wash_toggle_ref) + return ..() + +/datum/action/toggle_buffer + name = "Activate Auto-Wash" + desc = "Trade speed and water for a clean floor." + icon_icon = 'icons/mob/actions/actions_silicon.dmi' + button_icon_state = "activate_wash" + var/static/datum/callback/allow_buffer_activate + var/block_buffer_change = FALSE + var/buffer_on = FALSE + ///The bucket we draw water from + var/datum/weakref/bucket_ref + ///Our looping sound + var/datum/looping_sound/wash/wash_audio + ///Toggle cooldown to prevent sound spam + COOLDOWN_DECLARE(toggle_cooldown) + +/datum/action/toggle_buffer/New(Target) + if(!allow_buffer_activate) + allow_buffer_activate = CALLBACK(src, .proc/allow_buffer_activate) + return ..() + +/datum/action/toggle_buffer/Destroy() + if(buffer_on) + turn_off_wash() + QDEL_NULL(wash_audio) + return ..() + +/datum/action/toggle_buffer/Grant(mob/M) + . = ..() + wash_audio = new(owner) + +/datum/action/toggle_buffer/IsAvailable() + if(!istype(owner, /mob/living/silicon/robot)) + return FALSE + return ..() + +/datum/action/toggle_buffer/Trigger(trigger_flags) + . = ..() + if(!.) + return + var/mob/living/silicon/robot/robot_owner = owner + + block_buffer_change = DOING_INTERACTION(owner, "auto_wash_toggle") + if(block_buffer_change) + return FALSE + + var/obj/item/reagent_containers/glass/bucket/our_bucket = locate(/obj/item/reagent_containers/glass/bucket) in robot_owner.model.modules + bucket_ref = WEAKREF(our_bucket) + + if(!buffer_on) + if(!COOLDOWN_FINISHED(src, toggle_cooldown)) + robot_owner.balloon_alert(robot_owner, "auto-wash refreshing, please hold...") + return FALSE + COOLDOWN_START(src, toggle_cooldown, 4 SECONDS) + if(!allow_buffer_activate()) + return FALSE + + robot_owner.balloon_alert(robot_owner, "activating auto-wash...") + // Start the sound. it'll just last the 4 seconds it takes for us to rev up + wash_audio.start() + // We're just gonna shake the borg a bit. Not a ton, but just enough that it feels like the audio makes sense + var/base_x = robot_owner.base_pixel_x + var/base_y = robot_owner.base_pixel_y + animate(robot_owner, pixel_x = base_x, pixel_y = base_y, time = 1, loop = -1) + for(var/i in 1 to 17) //Startup rumble + var/x_offset = base_x + rand(-1, 1) + var/y_offset = base_y + rand(-1, 1) + animate(pixel_x = x_offset, pixel_y = y_offset, time = 1) + + if(!do_after(robot_owner, 4 SECONDS, interaction_key = "auto_wash_toggle", extra_checks = allow_buffer_activate)) + wash_audio.stop() // Coward + animate(robot_owner, pixel_x = base_x, pixel_y = base_y, time = 1) + return FALSE + else + if(!COOLDOWN_FINISHED(src, toggle_cooldown)) + robot_owner.balloon_alert(robot_owner, "auto-wash deactivating, please hold...") + return FALSE + robot_owner.balloon_alert(robot_owner, "de-activating auto-wash...") + + toggle_wash() + +/// Toggle our wash mode +/datum/action/toggle_buffer/proc/toggle_wash() + if(buffer_on) + deactivate_wash() + else + activate_wash() + +/// Activate the buffer, comes with a nice animation that loops while it's on +/datum/action/toggle_buffer/proc/activate_wash() + var/mob/living/silicon/robot/robot_owner = owner + buffer_on = TRUE + // Slow em down a bunch + robot_owner.add_movespeed_modifier(/datum/movespeed_modifier/auto_wash) + RegisterSignal(robot_owner, COMSIG_MOVABLE_MOVED, .proc/clean) + //This is basically just about adding a shake to the borg, effect should look ilke an engine's running + var/base_x = robot_owner.base_pixel_x + var/base_y = robot_owner.base_pixel_y + robot_owner.pixel_x = base_x + rand(-7, 7) + robot_owner.pixel_y = base_y + rand(-7, 7) + //Larger shake with more changes to start out, feels like "Revving" + animate(robot_owner, pixel_x = base_x, pixel_y = base_y, time = 1, loop = -1) + for(var/i in 1 to 100) + var/x_offset = base_x + rand(-2, 2) + var/y_offset = base_y + rand(-2, 2) + animate(pixel_x = x_offset, pixel_y = y_offset, time = 1) + if(!wash_audio.is_active()) + wash_audio.start() + clean() + UpdateButtonIcon() + +/// Start the process of disabling the buffer. Plays some effects, waits a bit, then finishes +/datum/action/toggle_buffer/proc/deactivate_wash() + var/mob/living/silicon/robot/robot_owner = owner + var/time_left = timeleft(wash_audio.timerid) // We delay by the timer of our wash cause well, we want to hear the ramp down + var/finished_by = time_left + 2.6 SECONDS + // Need to ensure that people don't spawn the deactivate button + COOLDOWN_START(src, toggle_cooldown, finished_by) + // Diable the cleaning, we're revving down + UnregisterSignal(robot_owner, COMSIG_MOVABLE_MOVED) + // Do the rumble animation till we're all finished + var/base_x = robot_owner.base_pixel_x + var/base_y = robot_owner.base_pixel_y + animate(robot_owner, pixel_x = base_x, pixel_y = base_y, time = 1) + for(var/i in 1 to finished_by - 0.1 SECONDS) //We rumble until we're finished making noise + var/x_offset = base_x + rand(-1, 1) + var/y_offset = base_y + rand(-1, 1) + animate(pixel_x = x_offset, pixel_y = y_offset, time = 1) + // Reset our animations + animate(pixel_x = base_x, pixel_y = base_y, time = 2) + addtimer(CALLBACK(wash_audio, /datum/looping_sound/proc/stop), time_left) + addtimer(CALLBACK(src, .proc/turn_off_wash), finished_by) + +/// Called by [deactivate_wash] on a timer to allow noises and animation to play out. +/// Finally disables the buffer. Doesn't do everything mind, just the stuff that we wanted to delay +/datum/action/toggle_buffer/proc/turn_off_wash() + var/mob/living/silicon/robot/robot_owner = owner + buffer_on = FALSE + robot_owner.remove_movespeed_modifier(/datum/movespeed_modifier/auto_wash) + UpdateButtonIcon() + +/// Should we keep trying to activate our buffer, or did you fuck it up somehow +/datum/action/toggle_buffer/proc/allow_buffer_activate() + var/mob/living/silicon/robot/robot_owner = owner + if(block_buffer_change) + robot_owner.balloon_alert(robot_owner, "activation cancelled!") + return FALSE + + var/obj/item/reagent_containers/glass/bucket/our_bucket = bucket_ref?.resolve() + if(!buffer_on && our_bucket?.reagents?.total_volume < 0.1) + robot_owner.balloon_alert(robot_owner, "bucket is empty!") + return FALSE + return TRUE + +/// Call this to attempt to actually clean the turf underneath us +/datum/action/toggle_buffer/proc/clean() + SIGNAL_HANDLER + var/mob/living/silicon/robot/robot_owner = owner + + var/obj/item/reagent_containers/glass/bucket/our_bucket = bucket_ref?.resolve() + var/datum/reagents/reagents = our_bucket?.reagents + + if(!reagents || reagents.total_volume < 0.1) + robot_owner.balloon_alert(robot_owner, "bucket is empty, de-activating...") + deactivate_wash() + return + + var/turf/our_turf = get_turf(robot_owner) + + if(reagents.has_chemical_flag(REAGENT_CLEANS, 1)) + our_turf.wash(CLEAN_SCRUB) + + reagents.expose(our_turf, TOUCH, 10) + // We use more water doing this then mopping + reagents.remove_any(2) //reaction() doesn't use up the reagents + +/datum/action/toggle_buffer/UpdateButtonIcon(status_only = FALSE, force = FALSE) + if(buffer_on) + name = "De-Activate Auto-Wash" + button_icon_state = "deactivate_wash" + else + name = "Activate Auto-Wash" + button_icon_state = "activate_wash" + + return ..() /obj/item/reagent_containers/spray/cyborg_drying name = "drying agent spray" diff --git a/code/modules/movespeed/modifiers/mobs.dm b/code/modules/movespeed/modifiers/mobs.dm index 4afd7946752..1c373b4de40 100644 --- a/code/modules/movespeed/modifiers/mobs.dm +++ b/code/modules/movespeed/modifiers/mobs.dm @@ -126,3 +126,6 @@ /datum/movespeed_modifier/morph_disguised multiplicative_slowdown = 1 + +/datum/movespeed_modifier/auto_wash + multiplicative_slowdown = 3 diff --git a/code/modules/reagents/chemistry/chem_wiki_render.dm b/code/modules/reagents/chemistry/chem_wiki_render.dm index c56aa592eb8..f060cf9d00d 100644 --- a/code/modules/reagents/chemistry/chem_wiki_render.dm +++ b/code/modules/reagents/chemistry/chem_wiki_render.dm @@ -194,5 +194,8 @@ if(reagent.chemical_flags & REAGENT_DEAD_PROCESS) outstring += "\n
Works on the dead" + if(reagent.chemical_flags & REAGENT_CLEANS) + outstring += "\n
Sanitizes well" + outstring += "\n|-" return outstring diff --git a/code/modules/reagents/chemistry/holder.dm b/code/modules/reagents/chemistry/holder.dm index d126d69ddf2..c7ad9e12402 100644 --- a/code/modules/reagents/chemistry/holder.dm +++ b/code/modules/reagents/chemistry/holder.dm @@ -428,6 +428,21 @@ return holder_reagent return FALSE +/** + * Check if this holder contains a reagent with a chemical_flags containing this flag + * Reagent takes the bitflag to search for + * Amount checks for having a specific amount of reagents matching that chemical + */ +/datum/reagents/proc/has_chemical_flag(chemical_flag, amount = 0) + var/found_amount = 0 + var/list/cached_reagents = reagent_list + for(var/datum/reagent/holder_reagent as anything in cached_reagents) + if (holder_reagent.chemical_flags & chemical_flag) + found_amount += holder_reagent.volume + if(found_amount >= amount) + return TRUE + return FALSE + /** * Transfer some stuff from this holder to a target object diff --git a/code/modules/reagents/chemistry/reagents/alcohol_reagents.dm b/code/modules/reagents/chemistry/reagents/alcohol_reagents.dm index 46f843862d5..40a212708bd 100644 --- a/code/modules/reagents/chemistry/reagents/alcohol_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/alcohol_reagents.dm @@ -290,7 +290,7 @@ All effects don't start immediately, but rather get worse over time; the rate is glass_desc = "The glass contain wodka. Xynta." shot_glass_icon_state = "shotglassclear" ph = 8.1 - chemical_flags = REAGENT_CAN_BE_SYNTHESIZED + chemical_flags = REAGENT_CAN_BE_SYNTHESIZED|REAGENT_CLEANS //Very high proof /datum/reagent/consumable/ethanol/bilk name = "Bilk" diff --git a/code/modules/reagents/chemistry/reagents/other_reagents.dm b/code/modules/reagents/chemistry/reagents/other_reagents.dm index ad81cdb964e..ee61e0ac2ce 100644 --- a/code/modules/reagents/chemistry/reagents/other_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/other_reagents.dm @@ -156,7 +156,7 @@ glass_name = "glass of water" glass_desc = "The father of all refreshments." shot_glass_icon_state = "shotglassclear" - chemical_flags = REAGENT_CAN_BE_SYNTHESIZED + chemical_flags = REAGENT_CAN_BE_SYNTHESIZED|REAGENT_CLEANS evaporates = TRUE //SKYRAT EDIT ADDITION /* @@ -242,7 +242,7 @@ glass_desc = "A glass of holy water." self_consuming = TRUE //divine intervention won't be limited by the lack of a liver ph = 7.5 //God is alkaline - chemical_flags = REAGENT_CAN_BE_SYNTHESIZED + chemical_flags = REAGENT_CAN_BE_SYNTHESIZED|REAGENT_CLEANS // Holy water. Mostly the same as water, it also heals the plant a little with the power of the spirits. Also ALSO increases instability. /datum/reagent/water/holywater/on_hydroponics_apply(obj/item/seeds/myseed, datum/reagents/chems, obj/machinery/hydroponics/mytray) @@ -1157,7 +1157,7 @@ penetrates_skin = NONE var/clean_types = CLEAN_WASH ph = 5.5 - chemical_flags = REAGENT_CAN_BE_SYNTHESIZED + chemical_flags = REAGENT_CAN_BE_SYNTHESIZED|REAGENT_CLEANS /datum/reagent/space_cleaner/expose_obj(obj/exposed_obj, reac_volume) . = ..() diff --git a/icons/mob/actions/actions_silicon.dmi b/icons/mob/actions/actions_silicon.dmi index 8a03ef91cab..7be634e9f6c 100644 Binary files a/icons/mob/actions/actions_silicon.dmi and b/icons/mob/actions/actions_silicon.dmi differ diff --git a/sound/creatures/cyborg/wash1.ogg b/sound/creatures/cyborg/wash1.ogg new file mode 100644 index 00000000000..8bbd1d54e5c Binary files /dev/null and b/sound/creatures/cyborg/wash1.ogg differ diff --git a/sound/creatures/cyborg/wash2.ogg b/sound/creatures/cyborg/wash2.ogg new file mode 100644 index 00000000000..6e950e4132b Binary files /dev/null and b/sound/creatures/cyborg/wash2.ogg differ diff --git a/sound/creatures/cyborg/wash_end.ogg b/sound/creatures/cyborg/wash_end.ogg new file mode 100644 index 00000000000..57b2ce1838f Binary files /dev/null and b/sound/creatures/cyborg/wash_end.ogg differ diff --git a/sound/creatures/cyborg/wash_start.ogg b/sound/creatures/cyborg/wash_start.ogg new file mode 100644 index 00000000000..c6faf4126d1 Binary files /dev/null and b/sound/creatures/cyborg/wash_start.ogg differ diff --git a/tgstation.dme b/tgstation.dme index 099cf563b15..7dc2c782be1 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -1032,6 +1032,7 @@ #include "code\datums\keybinding\robot.dm" #include "code\datums\looping_sounds\_looping_sound.dm" #include "code\datums\looping_sounds\acid.dm" +#include "code\datums\looping_sounds\cyborg.dm" #include "code\datums\looping_sounds\item_sounds.dm" #include "code\datums\looping_sounds\machinery_sounds.dm" #include "code\datums\looping_sounds\weather.dm"