diff --git a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_main.dm b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_main.dm index e81529fd70f..b1f9e2ff159 100644 --- a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_main.dm +++ b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_main.dm @@ -78,6 +78,8 @@ #define COMSIG_ATOM_SET_OPACITY "atom_set_opacity" ///from base of atom/hitby(atom/movable/AM, skipcatch, hitpush, blocked, datum/thrownthing/throwingdatum) #define COMSIG_ATOM_HITBY "atom_hitby" +///when an atom starts playing a song datum (datum/song) +#define COMSIG_ATOM_STARTING_INSTRUMENT "atom_starting_instrument" ///When the transform or an atom is varedited through vv topic. #define COMSIG_ATOM_VV_MODIFY_TRANSFORM "atom_vv_modify_transform" diff --git a/code/__DEFINES/dcs/signals/signals_music.dm b/code/__DEFINES/dcs/signals/signals_music.dm index e41207713e2..69c4fc5b12e 100644 --- a/code/__DEFINES/dcs/signals/signals_music.dm +++ b/code/__DEFINES/dcs/signals/signals_music.dm @@ -1,8 +1,12 @@ // /datum/song signals ///sent to the instrument when a song starts playing -#define COMSIG_SONG_START "song_start" +#define COMSIG_INSTRUMENT_START "instrument_start" ///sent to the instrument when a song stops playing -#define COMSIG_SONG_END "song_end" +#define COMSIG_INSTRUMENT_END "instrument_end" ///sent to the instrument on /should_stop_playing(): (atom/player). Return values can be found in DEFINES/song.dm -#define COMSIG_SONG_SHOULD_STOP_PLAYING "song_should_stop_playing" +#define COMSIG_INSTRUMENT_SHOULD_STOP_PLAYING "instrument_should_stop_playing" +///sent to the instrument (and player if available) when a song repeats (datum/song) +#define COMSIG_INSTRUMENT_REPEAT "instrument_repeat" +///sent to the instrument when tempo changes, skipped on new. (datum/song) +#define COMSIG_INSTRUMENT_TEMPO_CHANGE "instrument_tempo_change" diff --git a/code/__DEFINES/is_helpers.dm b/code/__DEFINES/is_helpers.dm index f0da816ef2b..a4932e54b66 100644 --- a/code/__DEFINES/is_helpers.dm +++ b/code/__DEFINES/is_helpers.dm @@ -211,6 +211,8 @@ GLOBAL_LIST_INIT(turfs_openspace, typecacheof(list( #define isgun(A) (istype(A, /obj/item/gun)) +#define isinstrument(A) (istype(A, /obj/item/instrument) || istype(A, /obj/structure/musician)) + #define is_reagent_container(O) (istype(O, /obj/item/reagent_containers)) //Assemblies diff --git a/code/__DEFINES/traits.dm b/code/__DEFINES/traits.dm index 1baba7cc67d..bbd4320f8c8 100644 --- a/code/__DEFINES/traits.dm +++ b/code/__DEFINES/traits.dm @@ -831,6 +831,9 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai /// This human wants to see the color of their glasses, for some reason #define TRAIT_SEE_GLASS_COLORS "see_glass_colors" +/// this mob is under the effects of the power chord +#define TRAIT_POWER_CHORD "power_chord" + // Radiation defines /// Marks that this object is irradiated diff --git a/code/datums/components/religious_tool.dm b/code/datums/components/religious_tool.dm index dcf64a899e3..d66aee4c782 100644 --- a/code/datums/components/religious_tool.dm +++ b/code/datums/components/religious_tool.dm @@ -48,8 +48,6 @@ after_sect_select_cb.Invoke() return TRUE - - /** * Since all of these involve attackby, we require mega proc. Handles Invocation, Sacrificing, And Selection of Sects. */ @@ -112,6 +110,7 @@ switch(action) if("sect_select") select_sect(usr, params["path"]) + return TRUE //they picked a sect lets update so some weird spammy shit doesn't happen if("perform_rite") perform_rite(usr, params["path"]) @@ -162,7 +161,10 @@ else performing_rite.invoke_effect(user, parent) easy_access_sect.adjust_favor(-performing_rite.favor_cost) - QDEL_NULL(performing_rite) + if(performing_rite.auto_delete) + QDEL_NULL(performing_rite) + else + performing_rite = null /** * Generates a list of available sects to the user. Intended to support custom-availability sects. diff --git a/code/datums/components/smooth_tunes.dm b/code/datums/components/smooth_tunes.dm new file mode 100644 index 00000000000..15248b768db --- /dev/null +++ b/code/datums/components/smooth_tunes.dm @@ -0,0 +1,107 @@ +///how many lines multiplied by tempo should at least be higher than this. +#define LONG_ENOUGH_SONG 220 + +///Smooth tunes component! Applied to musicians to give the songs they play special effects, according to a rite! +///Comes with BARTICLES!!! +/datum/component/smooth_tunes + ///if applied due to a rite, we link it here + var/datum/religion_rites/song_tuner/linked_songtuner_rite + ///linked song + var/datum/song/linked_song + ///if repeats count as continuations instead of a song's end, TRUE + var/allow_repeats = TRUE + ///particles to apply, if applicable + var/particles_path + ///the particle holder of the particle path (created when song starts) ((no i cant think of a better var name because i made the typepath and im perfect)) + var/obj/effect/abstract/particle_holder/particle_holder + ///a funny little glow applied to the instrument while playing + var/glow_color + ///whether to call the rite's finish effect, only true when the song is long enough + var/viable_for_final_effect = FALSE + +/datum/component/smooth_tunes/Initialize(linked_songtuner_rite, allow_repeats, particles_path, glow_color) + if(!isinstrument(parent) && !isliving(parent)) + return COMPONENT_INCOMPATIBLE + src.linked_songtuner_rite = linked_songtuner_rite + src.allow_repeats = allow_repeats + src.particles_path = particles_path + src.glow_color = glow_color + +/datum/component/smooth_tunes/Destroy(force, silent) + if(particle_holder) + QDEL_NULL(particle_holder) + qdel(linked_songtuner_rite) + return ..() + +/datum/component/smooth_tunes/RegisterWithParent() + RegisterSignal(parent, COMSIG_ATOM_STARTING_INSTRUMENT,.proc/start_singing) + +/datum/component/smooth_tunes/UnregisterFromParent() + UnregisterSignal(parent, COMSIG_ATOM_STARTING_INSTRUMENT) + +///Initiates the effect when the song begins playing. +/datum/component/smooth_tunes/proc/start_singing(datum/source, datum/song/starting_song) + SIGNAL_HANDLER + if(!starting_song) + return + if(istype(starting_song.parent, /obj/structure/musician)) + return //TODO: make stationary instruments work with no hiccups + + if(starting_song.lines.len * starting_song.tempo > LONG_ENOUGH_SONG) + viable_for_final_effect = TRUE + else + to_chat(parent, span_warning("This song is too short, so it won't include the song finishing effect.")) + + START_PROCESSING(SSobj, src) //even though WE aren't an object, our parent is! + if(linked_songtuner_rite.song_start_message) + starting_song.parent.visible_message(linked_songtuner_rite.song_start_message) + + ///prevent more songs from being blessed concurrently, mob signal + UnregisterSignal(parent, COMSIG_ATOM_STARTING_INSTRUMENT) + ///and hook into the instrument this time, preventing other weird exploity stuff. + RegisterSignal(starting_song.parent, COMSIG_INSTRUMENT_TEMPO_CHANGE, .proc/tempo_change) + RegisterSignal(starting_song.parent, COMSIG_INSTRUMENT_END, .proc/stop_singing) + if(!allow_repeats) + RegisterSignal(starting_song.parent, COMSIG_INSTRUMENT_REPEAT, .proc/stop_singing) + + linked_song = starting_song + + //barticles + if(particles_path && ismovable(linked_song.parent)) + particle_holder = new(linked_song.parent, particles_path) + //filters + linked_song.parent?.add_filter("smooth_tunes_outline", 9, list("type" = "outline", "color" = glow_color)) + +///Prevents changing tempo during a song to sneak in final effects quicker + +/datum/component/smooth_tunes/proc/tempo_change(datum/source, datum/song/modified_song) + SIGNAL_HANDLER + if(modified_song.playing && viable_for_final_effect) + to_chat(parent, span_warning("Modifying the song mid-performance has removed your ability to perform the song finishing effect.")) + viable_for_final_effect = FALSE + +///Ends the effect when the song is no longer playing. +/datum/component/smooth_tunes/proc/stop_singing(datum/source, finished) + SIGNAL_HANDLER + STOP_PROCESSING(SSobj, src) + if(viable_for_final_effect) + if(!finished) + to_chat(parent, span_warning("The song was interrupted, you cannot activate the finishing ability!")) + else + linked_songtuner_rite.finish_effect(parent, linked_song) + linked_song.parent?.remove_filter("smooth_tunes_outline") + UnregisterSignal(linked_song.parent, list( + COMSIG_INSTRUMENT_TEMPO_CHANGE, + COMSIG_INSTRUMENT_END, + COMSIG_INSTRUMENT_REPEAT, + )) + linked_song = null + qdel(src) + +/datum/component/smooth_tunes/process(delta_time = SSOBJ_DT) + if(linked_songtuner_rite) + linked_songtuner_rite.song_effect(parent, linked_song) + else + stop_singing() + +#undef LONG_ENOUGH_SONG diff --git a/code/datums/status_effects/buffs.dm b/code/datums/status_effects/buffs.dm index 59f1806d957..6dcf460e0af 100644 --- a/code/datums/status_effects/buffs.dm +++ b/code/datums/status_effects/buffs.dm @@ -348,9 +348,19 @@ /datum/status_effect/antimagic id = "antimagic" + status_type = STATUS_EFFECT_REFRESH duration = 10 SECONDS examine_text = "They seem to be covered in a dull, grey aura." +/datum/status_effect/antimagic/on_creation(mob/living/new_owner, duration = 10 SECONDS) + src.duration = duration + return ..() + +/datum/status_effect/antimagic/refresh(effect, duration = 10 SECONDS) + if(duration == -1) + return + duration = world.time + duration + /datum/status_effect/antimagic/on_apply() owner.visible_message(span_notice("[owner] is coated with a dull aura!")) ADD_TRAIT(owner, TRAIT_ANTIMAGIC, MAGIC_TRAIT) diff --git a/code/datums/wounds/slash.dm b/code/datums/wounds/slash.dm index 045f20b123c..14107d85b4f 100644 --- a/code/datums/wounds/slash.dm +++ b/code/datums/wounds/slash.dm @@ -316,3 +316,9 @@ status_effect_type = /datum/status_effect/wound/slash/critical scar_keyword = "slashcritical" wound_flags = (FLESH_WOUND | ACCEPTS_GAUZE | MANGLES_FLESH) + +/datum/wound/slash/moderate/many_cuts + name = "Numerous Small Slashes" + desc = "Patient's skin has numerous small slashes and cuts, generating moderate blood loss." + examine_desc = "has a ton of small cuts" + occur_text = "is cut numerous times, leaving many small slashes." diff --git a/code/game/objects/effects/effects.dm b/code/game/objects/effects/effects.dm index de108310469..92ab7d3f0cb 100644 --- a/code/game/objects/effects/effects.dm +++ b/code/game/objects/effects/effects.dm @@ -43,6 +43,9 @@ /obj/effect/singularity_act() qdel(src) +///The abstract effect ignores even more effects and is often typechecked for atoms that should truly not be fucked with. +/obj/effect/abstract + /obj/effect/abstract/singularity_pull() return diff --git a/code/game/objects/effects/particle_holder.dm b/code/game/objects/effects/particle_holder.dm new file mode 100644 index 00000000000..e6e8918a5e8 --- /dev/null +++ b/code/game/objects/effects/particle_holder.dm @@ -0,0 +1,66 @@ +///objects can only have one particle on them at a time, so we use these abstract effects to hold and display the effects. You know, so multiple particle effects can exist at once. +///also because some objects do not display particles due to how their visuals are built +/obj/effect/abstract/particle_holder + anchored = TRUE + mouse_opacity = MOUSE_OPACITY_TRANSPARENT + layer = ABOVE_ALL_MOB_LAYER + ///typepath of the last location we're in, if it's different when moved then we need to update vis contents + var/last_attached_location_type + ///the main item we're attached to at the moment, particle holders hold particles for something + var/datum/weakref/weak_attached + ///besides the item we're also sometimes attached to other stuff! (items held emitting particles on a mob) + var/datum/weakref/weak_additional + +/obj/effect/abstract/particle_holder/Initialize(mapload, particle_path = /particles/smoke) + . = ..() + if(!loc) + stack_trace("particle holder was created with no loc!") + return INITIALIZE_HINT_QDEL + if(ismovable(loc)) + RegisterSignal(loc, COMSIG_MOVABLE_MOVED, .proc/on_move) + RegisterSignal(loc, COMSIG_PARENT_QDELETING, .proc/on_qdel) + weak_attached = WEAKREF(loc) + particles = new particle_path + update_visual_contents(loc) + +/obj/effect/abstract/particle_holder/Destroy(force) + var/atom/movable/attached = weak_attached.resolve() + var/atom/movable/additional_attached + if(weak_additional) + additional_attached = weak_additional.resolve() + if(attached) + attached.vis_contents -= src + UnregisterSignal(loc, list(COMSIG_MOVABLE_MOVED, COMSIG_PARENT_QDELETING)) + if(additional_attached) + additional_attached.vis_contents -= src + QDEL_NULL(particles) + return ..() + +///signal called when parent is moved +/obj/effect/abstract/particle_holder/proc/on_move(atom/movable/attached, atom/oldloc, direction) + SIGNAL_HANDLER + if(attached.loc.type != last_attached_location_type) + update_visual_contents(attached) + +///signal called when parent is deleted +/obj/effect/abstract/particle_holder/proc/on_qdel(atom/movable/attached, force) + SIGNAL_HANDLER + qdel(src)//our parent is gone and we need to be as well + +///logic proc for particle holders, aka where they move. +///subtypes of particle holders can override this for particles that should always be turf level or do special things when repositioning. +///this base subtype has some logic for items, as the loc of items becomes mobs very often hiding the particles +/obj/effect/abstract/particle_holder/proc/update_visual_contents(atom/movable/attached_to) + //remove old + if(weak_additional) + var/atom/movable/resolved_location = weak_additional.resolve() + if(resolved_location) + resolved_location.vis_contents -= src + //add to new + if(isitem(attached_to) && ismob(attached_to.loc)) //special case we want to also be emitting from the mob + var/mob/particle_mob = attached_to.loc + last_attached_location_type = attached_to.loc + weak_additional = WEAKREF(particle_mob) + particle_mob.vis_contents += src + //readd to ourselves + attached_to.vis_contents |= src diff --git a/code/game/objects/structures/beds_chairs/chair.dm b/code/game/objects/structures/beds_chairs/chair.dm index 99b14326ebe..606e6f64d79 100644 --- a/code/game/objects/structures/beds_chairs/chair.dm +++ b/code/game/objects/structures/beds_chairs/chair.dm @@ -539,3 +539,15 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/structure/chair/stool/bar, 0) custom_materials = list(/datum/material/plastic = 2000) break_chance = 25 origin_type = /obj/structure/chair/plastic + +/obj/structure/chair/musical + name = "musical chair" + desc = "You listen to this. Either by will or by force." + item_chair = /obj/item/chair/musical + particles = new /particles/musical_notes + +/obj/item/chair/musical + name = "musical chair" + desc = "Oh, so this is like the fucked up Monopoly rules where there are no rules and you can pick up and place the musical chairs as you please." + particles = new /particles/musical_notes + origin_type = /obj/structure/chair/musical diff --git a/code/modules/actionspeed/modifiers/status_effects.dm b/code/modules/actionspeed/modifiers/status_effects.dm index a6f812890ef..fa080edb8d0 100644 --- a/code/modules/actionspeed/modifiers/status_effects.dm +++ b/code/modules/actionspeed/modifiers/status_effects.dm @@ -7,5 +7,8 @@ /datum/actionspeed_modifier/nooartrium multiplicative_slowdown = 0.5 +/datum/actionspeed_modifier/power_chord + multiplicative_slowdown = -0.15 + /datum/actionspeed_modifier/status_effect/hazard_area multiplicative_slowdown = 4 diff --git a/code/modules/instruments/piano_synth.dm b/code/modules/instruments/piano_synth.dm index 90748d0f18f..bf5190675c6 100644 --- a/code/modules/instruments/piano_synth.dm +++ b/code/modules/instruments/piano_synth.dm @@ -32,8 +32,8 @@ /obj/item/instrument/piano_synth/headphones/ComponentInitialize() . = ..() AddElement(/datum/element/update_icon_updates_onmob) - RegisterSignal(src, COMSIG_SONG_START, .proc/start_playing) - RegisterSignal(src, COMSIG_SONG_END, .proc/stop_playing) + RegisterSignal(src, COMSIG_INSTRUMENT_START, .proc/start_playing) + RegisterSignal(src, COMSIG_INSTRUMENT_END, .proc/stop_playing) /** * Called by a component signal when our song starts playing. @@ -120,15 +120,15 @@ /obj/item/circuit_component/synth/register_shell(atom/movable/shell) . = ..() synth = shell - RegisterSignal(synth, COMSIG_SONG_START, .proc/on_song_start) - RegisterSignal(synth, COMSIG_SONG_END, .proc/on_song_end) - RegisterSignal(synth, COMSIG_SONG_SHOULD_STOP_PLAYING, .proc/continue_if_autoplaying) + RegisterSignal(synth, COMSIG_INSTRUMENT_START, .proc/on_song_start) + RegisterSignal(synth, COMSIG_INSTRUMENT_END, .proc/on_song_end) + RegisterSignal(synth, COMSIG_INSTRUMENT_SHOULD_STOP_PLAYING, .proc/continue_if_autoplaying) /obj/item/circuit_component/synth/unregister_shell(atom/movable/shell) if(synth.song.music_player == src) synth.song.stop_playing() synth = null - UnregisterSignal(synth, list(COMSIG_SONG_START, COMSIG_SONG_END, COMSIG_SONG_SHOULD_STOP_PLAYING)) + UnregisterSignal(synth, list(COMSIG_INSTRUMENT_START, COMSIG_INSTRUMENT_END, COMSIG_INSTRUMENT_SHOULD_STOP_PLAYING)) return ..() /obj/item/circuit_component/synth/proc/start_playing(datum/port/input/port) diff --git a/code/modules/instruments/songs/_song.dm b/code/modules/instruments/songs/_song.dm index 9ce10835c3e..cfa4af0bde4 100644 --- a/code/modules/instruments/songs/_song.dm +++ b/code/modules/instruments/songs/_song.dm @@ -126,7 +126,7 @@ /datum/song/New(atom/parent, list/instrument_ids, new_range) SSinstruments.on_song_new(src) lines = list() - tempo = sanitize_tempo(tempo) + tempo = sanitize_tempo(tempo, TRUE) src.parent = parent if(instrument_ids) allowed_instrument_ids = islist(instrument_ids)? instrument_ids : list(instrument_ids) @@ -211,7 +211,8 @@ //we can not afford to runtime, since we are going to be doing sound channel reservations and if we runtime it means we have a channel allocation leak. //wrap the rest of the stuff to ensure stop_playing() is called. do_hearcheck() - SEND_SIGNAL(parent, COMSIG_SONG_START) + SEND_SIGNAL(parent, COMSIG_INSTRUMENT_START, src) + SEND_SIGNAL(user, COMSIG_ATOM_STARTING_INSTRUMENT, src) elapsed_delay = 0 delay_by = 0 current_chord = 1 @@ -222,15 +223,18 @@ /** * Stops playing, terminating all sounds if in synthesized mode. Clears hearing_mobs. + * + * Arguments: + * * finished: boolean, whether the song ended via reaching the end. */ -/datum/song/proc/stop_playing() +/datum/song/proc/stop_playing(finished = FALSE) if(!playing) return playing = FALSE if(!debug_mode) compiled_chords = null STOP_PROCESSING(SSinstruments, src) - SEND_SIGNAL(parent, COMSIG_SONG_END) + SEND_SIGNAL(parent, COMSIG_INSTRUMENT_END, finished) terminate_all_sounds(TRUE) hearing_mobs.len = 0 music_player = null @@ -239,23 +243,27 @@ * Processes our song. */ /datum/song/proc/process_song(wait) - if(!length(compiled_chords) || should_stop_playing(music_player) == STOP_PLAYING) - stop_playing() + if(!length(compiled_chords)) + stop_playing(TRUE) + return + if(should_stop_playing(music_player) == STOP_PLAYING) + stop_playing(FALSE) return var/list/chord = compiled_chords[current_chord] - if(++elapsed_delay >= delay_by) - play_chord(chord) - elapsed_delay = 0 - delay_by = tempodiv_to_delay(chord[length(chord)]) - current_chord++ - if(current_chord > length(compiled_chords)) - if(repeat) - repeat-- - current_chord = 1 - return - else - stop_playing() - return + if(++elapsed_delay < delay_by) + return + play_chord(chord) + elapsed_delay = 0 + delay_by = tempodiv_to_delay(chord[length(chord)]) + current_chord++ + if(current_chord <= length(compiled_chords)) + return + if(!repeat) + stop_playing(TRUE) + return + repeat-- + current_chord = 1 + SEND_SIGNAL(parent, COMSIG_INSTRUMENT_REPEAT, TRUE) /** * Converts a tempodiv to ticks to elapse before playing the next chord, taking into account our tempo. @@ -285,7 +293,7 @@ /datum/song/proc/should_stop_playing(atom/player) if(QDELETED(player) || !using_instrument || !playing) return STOP_PLAYING - return SEND_SIGNAL(parent, COMSIG_SONG_SHOULD_STOP_PLAYING, player) + return SEND_SIGNAL(parent, COMSIG_INSTRUMENT_SHOULD_STOP_PLAYING, player) /// Sets and sanitizes the repeats variable. /datum/song/proc/set_repeats(new_repeats_value) @@ -301,8 +309,10 @@ /** * Sanitizes tempo to a value that makes sense and fits the current world.tick_lag. */ -/datum/song/proc/sanitize_tempo(new_tempo) +/datum/song/proc/sanitize_tempo(new_tempo, initializing = FALSE) new_tempo = abs(new_tempo) + if(!initializing) // not only is it not helpful while initializing but it will runtime really hard since nothing is set up + SEND_SIGNAL(parent, COMSIG_INSTRUMENT_TEMPO_CHANGE, src) return clamp(round(new_tempo, world.tick_lag), world.tick_lag, 5 SECONDS) /** diff --git a/code/modules/mob/living/carbon/life.dm b/code/modules/mob/living/carbon/life.dm index 3b013b98a97..74062b0efa5 100644 --- a/code/modules/mob/living/carbon/life.dm +++ b/code/modules/mob/living/carbon/life.dm @@ -458,7 +458,7 @@ All effects don't start immediately, but rather get worse over time; the rate is adjust_drowsyness(-1 * restingpwr * delta_time) blur_eyes(1 * delta_time) if(DT_PROB(2.5, delta_time)) - AdjustSleeping(100) + AdjustSleeping(10 SECONDS) //Jitteriness if(jitteriness) diff --git a/code/modules/movespeed/modifiers/status_effects.dm b/code/modules/movespeed/modifiers/status_effects.dm index 2d3d69117e1..27384d28aeb 100644 --- a/code/modules/movespeed/modifiers/status_effects.dm +++ b/code/modules/movespeed/modifiers/status_effects.dm @@ -16,6 +16,8 @@ variable = TRUE blacklisted_movetypes = (FLYING|FLOATING) +/datum/movespeed_modifier/status_effect/power_chord + multiplicative_slowdown = -0.2 + /datum/movespeed_modifier/status_effect/hazard_area multiplicative_slowdown = 4 - diff --git a/code/modules/religion/festival/instrument_rites.dm b/code/modules/religion/festival/instrument_rites.dm new file mode 100644 index 00000000000..8463d57a3bc --- /dev/null +++ b/code/modules/religion/festival/instrument_rites.dm @@ -0,0 +1,149 @@ +///prototype for rites that tune a song. +/datum/religion_rites/song_tuner + name = "Tune Song" + desc = "this is a prototype." + ritual_length = 10 SECONDS + favor_cost = 10 + ///if repeats count as continuations instead of a song's end, TRUE + var/repeats_okay = TRUE + ///personal message sent to the chaplain as feedback for their chosen song + var/song_invocation_message = "beep borp you forgot to fill in a variable report to git hub" + ///visible message sent to indicate a song will have special properties + var/song_start_message + ///particle effect of playing this tune + var/particles_path = /particles/musical_notes + ///what the instrument will glow when playing + var/glow_color = "#000000" + +/datum/religion_rites/song_tuner/invoke_effect(mob/living/user, obj/structure/altar_of_gods/altar) + . = ..() + to_chat(user, span_notice(song_invocation_message)) + user.AddComponent(/datum/component/smooth_tunes, src, repeats_okay, particles_path, glow_color) + +/** + * Perform the song effect. + * + * Arguments: + * * song_player - parent of the smooth_tunes component. This is limited to the compatible items of said component, which currently includes mobs and objects so we'll have to type appropriately. + * * song_datum - Datum song being played + */ +/datum/religion_rites/song_tuner/proc/song_effect(atom/song_player, datum/song/song_datum) + return + +/** + * When the song is long enough, it will have a special effect when it ends. + * + * If you want something that ALWAYS goes off regardless of song length, affix it to the Destroy proc. The rite is destroyed when smooth tunes is done. + * + * Arguments: + * * song_player - parent of the smooth_tunes component. This is limited to the compatible items of said component, which currently includes mobs and objects so we'll have to type appropriately. + * * song_datum - Datum song being played + */ +/datum/religion_rites/song_tuner/proc/finish_effect(atom/song_player, datum/song/song_datum) + return + +/datum/religion_rites/song_tuner/evangelism + name = "Evangelical Hymn" + desc = "Spreads the word of your god, gaining favor for each non-holy listener. At the end of the song, you'll bless all listeners, improving mood." + particles_path = /particles/musical_notes/holy + song_invocation_message = "You've prepared a holy song!" + song_start_message = span_notice("This music sounds blessed!") + glow_color = "#FEFFE0" + favor_cost = 0 + +/datum/religion_rites/song_tuner/evangelism/song_effect(atom/song_player, datum/song/song_datum) + if(!song_datum || !GLOB.religious_sect) + return + for(var/mob/living/carbon/human/listener in song_datum.hearing_mobs) + if(listener == song_player || listener.can_block_magic(MAGIC_RESISTANCE_HOLY, charge_cost = 0)) + continue + if(listener.mind?.holy_role) + continue + if(!listener.ckey) //good requirement to have for favor, trust me + continue + GLOB.religious_sect.adjust_favor(0.2) + +/datum/religion_rites/song_tuner/evangelism/finish_effect(atom/song_player, datum/song/song_datum) + for(var/mob/living/carbon/human/listener in song_datum.hearing_mobs) + SEND_SIGNAL(listener, COMSIG_ADD_MOOD_EVENT, "blessing", /datum/mood_event/blessing) + +/datum/religion_rites/song_tuner/nullwave + name = "Nullwave Vibrato" + desc = "Sing a dull song, protecting those who listen from magic." + particles_path = /particles/musical_notes/nullwave + song_invocation_message = "You've prepared an antimagic song!" + song_start_message = span_nicegreen("This music makes you feel protected!") + glow_color = "#a9a9b8" + repeats_okay = FALSE + +/datum/religion_rites/song_tuner/nullwave/song_effect(atom/song_player, datum/song/song_datum) + if(!song_datum) + return + for(var/mob/living/listener in song_datum.hearing_mobs) + if(listener.can_block_magic(MAGIC_RESISTANCE_HOLY, charge_cost = 1)) + continue + listener.apply_status_effect(/datum/status_effect/antimagic, 2 MINUTES) + +/datum/religion_rites/song_tuner/pain + name = "Murderous Chord" + desc = "Sing a sharp song, cutting those around you. Works less effectively on fellow priests. At the end of the song, you'll open the wounds of all listeners." + particles_path = /particles/musical_notes/harm + song_invocation_message = "You've prepared a painful song!" + song_start_message = span_danger("This music cuts like a knife!") + glow_color = "#FF4460" + repeats_okay = FALSE + +/datum/religion_rites/song_tuner/pain/song_effect(atom/song_player, datum/song/song_datum) + if(!song_datum) + return + for(var/mob/living/listener in song_datum.hearing_mobs) + if(listener.can_block_magic(MAGIC_RESISTANCE_HOLY, charge_cost = 0)) + continue + var/pain_juice = 1 + if(listener.mind?.holy_role) + pain_juice *= 0.5 + listener.adjustBruteLoss(pain_juice) + +/datum/religion_rites/song_tuner/pain/finish_effect(atom/song_player, datum/song/song_datum) + for(var/mob/living/carbon/human/listener in song_datum.hearing_mobs) + if(listener.can_block_magic(MAGIC_RESISTANCE_HOLY, charge_cost = 1)) + continue + var/obj/item/bodypart/sliced_limb = pick(listener.bodyparts) + sliced_limb.force_wound_upwards(/datum/wound/slash/moderate/many_cuts) + +/datum/religion_rites/song_tuner/lullaby + name = "Spiritual Lullaby" + desc = "Sing a lullaby, tiring those around you, making them slower. At the end of the song, you'll put people who are tired enough to sleep." + particles_path = /particles/musical_notes/sleepy + song_invocation_message = "You've prepared a sleepy song!" + song_start_message = span_warning("This music's making you feel drowsy...") + favor_cost = 40 //actually really strong + glow_color = "#83F6FF" + repeats_okay = FALSE + ///assoc list of weakrefs to who heard the song, for the finishing effect to look at. + var/list/listener_counter = list() + +/datum/religion_rites/song_tuner/lullaby/Destroy() + listener_counter.Cut() + return ..() + +/datum/religion_rites/song_tuner/lullaby/song_effect(atom/song_player, datum/song/song_datum) + if(!song_datum) + return + for(var/mob/living/listener in song_datum.hearing_mobs) + if(listener.can_block_magic(MAGIC_RESISTANCE_HOLY, charge_cost = 0)) + continue + if(listener.mind?.holy_role) + continue + if(prob(20)) + to_chat(listener, span_warning(pick("The music is putting you to sleep...", "The music makes you nod off for a moment.", "You try to focus on staying awake through the song."))) + listener.emote("yawn") + listener.blur_eyes(2) + +/datum/religion_rites/song_tuner/lullaby/finish_effect(atom/song_player, datum/song/song_datum) + for(var/mob/living/carbon/human/listener in song_datum.hearing_mobs) + if(listener.can_block_magic(MAGIC_RESISTANCE_HOLY, charge_cost = 1)) + continue + to_chat(listener, span_danger("Wow, the ending of that song was... pretty...")) + listener.AdjustSleeping(5 SECONDS) + diff --git a/code/modules/religion/festival/note_particles.dm b/code/modules/religion/festival/note_particles.dm new file mode 100644 index 00000000000..cf8072b8854 --- /dev/null +++ b/code/modules/religion/festival/note_particles.dm @@ -0,0 +1,83 @@ +///musical notes! Try to use these sparingly, gents. +/particles/musical_notes + icon = 'icons/effects/particles/notes/note.dmi' + icon_state = list( + "note_1" = 1, + "note_2" = 1, + "note_3" = 1, + "note_4" = 1, + "note_5" = 1, + "note_6" = 1, + "note_7" = 1, + "note_8" = 1, + ) + width = 100 + height = 100 + count = 250 + spawning = 0.6 + lifespan = 0.7 SECONDS + fade = 1 SECONDS + grow = -0.01 + velocity = list(0, 0) + position = generator("circle", 0, 16, NORMAL_RAND) + drift = generator("vector", list(0, -0.2), list(0, 0.2)) + gravity = list(0, 0.95) + +/particles/musical_notes/holy + icon = 'icons/effects/particles/notes/note_holy.dmi' + icon_state = list( + "holy_1" = 1, + "holy_2" = 1, + "holy_3" = 1, + "holy_4" = 1, + "holy_5" = 1, + "holy_6" = 1, + "holy_7" = 1, + "holy_8" = 1, + "holy_9" = 4, //holy theme specific + ) + +/particles/musical_notes/nullwave + icon = 'icons/effects/particles/notes/note_null.dmi' + icon_state = list( + "null_1" = 1, + "null_2" = 1, + "null_3" = 1, + "null_4" = 1, + "null_5" = 1, + "null_6" = 1, + "null_7" = 1, + "null_8" = 1, + "null_9" = 2, //heal theme specific + "null_10" = 2, //heal theme specific + ) + +/particles/musical_notes/harm + icon = 'icons/effects/particles/notes/note_harm.dmi' + icon_state = list( + "harm_1" = 1, + "harm_2" = 1, + "harm_3" = 1, + "harm_4" = 1, + "harm_5" = 1, + "harm_6" = 1, + "harm_7" = 1, + "harm_8" = 1, + "harm_9" = 2, //harm theme specific + "harm_10" = 2, //harm theme specific + ) + +/particles/musical_notes/sleepy + icon = 'icons/effects/particles/notes/note_sleepy.dmi' + icon_state = list( + "sleepy_1" = 1, + "sleepy_2" = 1, + "sleepy_3" = 1, + "sleepy_4" = 1, + "sleepy_5" = 1, + "sleepy_6" = 1, + "sleepy_7" = 1, + "sleepy_8" = 1, + "sleepy_9" = 2, //sleepy theme specific + "sleepy_10" = 2, //sleepy theme specific + ) diff --git a/code/modules/religion/religion_sects.dm b/code/modules/religion/religion_sects.dm index 88f299227a5..037408f04cd 100644 --- a/code/modules/religion/religion_sects.dm +++ b/code/modules/religion/religion_sects.dm @@ -40,6 +40,8 @@ var/altar_icon_state /// Currently Active (non-deleted) rites var/list/active_rites + /// Whether the structure has CANDLE OVERLAYS! + var/candle_overlay = TRUE /datum/religion_sect/New() . = ..() @@ -315,6 +317,7 @@ altar_icon_state = "convertaltar-burden" alignment = ALIGNMENT_NEUT invalidating_qualities = TRAIT_GENELESS + candle_overlay = FALSE /datum/religion_sect/burden/on_conversion(mob/living/carbon/human/new_convert) ..() @@ -405,3 +408,22 @@ /datum/religion_sect/spar/tool_examine(mob/living/holy_creature) return "You have [round(favor)] sparring matches won in [GLOB.deity]'s name to redeem. You have lost [matches_lost] holy matches. You will be excommunicated after losing three matches." + +/datum/religion_sect/music + name = "Festival God" + quote = "Everything follows a rhythm- The heartbeat of the universe!" + desc = "Make wonderful music! Sooth or serrate your friends and foes with the beat." + tgui_icon = "music" + altar_icon_state = "convertaltar-festival" + alignment = ALIGNMENT_GOOD + candle_overlay = FALSE + rites_list = list( + /datum/religion_rites/song_tuner/evangelism, + /datum/religion_rites/song_tuner/nullwave, + /datum/religion_rites/song_tuner/pain, + /datum/religion_rites/song_tuner/lullaby, + ) + +/datum/religion_sect/music/on_conversion(mob/living/chap) + . = ..() + new /obj/item/choice_beacon/music(get_turf(chap)) diff --git a/code/modules/religion/religion_structures.dm b/code/modules/religion/religion_structures.dm index f3df8eb9f6e..d7f97bb81c5 100644 --- a/code/modules/religion/religion_structures.dm +++ b/code/modules/religion/religion_structures.dm @@ -27,8 +27,11 @@ return ..() /obj/structure/altar_of_gods/update_overlays() - . = ..() - . += "convertaltarcandle" + var/list/new_overlays = ..() + if(GLOB.religious_sect) + return new_overlays + new_overlays += "convertaltarcandle" + return new_overlays /obj/structure/altar_of_gods/attack_hand(mob/living/user, list/modifiers) if(!Adjacent(user) || !user.pulling) diff --git a/code/modules/religion/rites.dm b/code/modules/religion/rites.dm index 9a054b4712d..61198d31d3a 100644 --- a/code/modules/religion/rites.dm +++ b/code/modules/religion/rites.dm @@ -1,15 +1,17 @@ /datum/religion_rites -/// name of the religious rite + /// name of the religious rite var/name = "religious rite" -/// Description of the religious rite + /// Description of the religious rite var/desc = "immm gonna rooon" -/// length it takes to complete the ritual + /// length it takes to complete the ritual var/ritual_length = (10 SECONDS) //total length it'll take -/// list of invocations said (strings) throughout the rite + /// list of invocations said (strings) throughout the rite var/list/ritual_invocations //strings that are by default said evenly throughout the rite -/// message when you invoke + /// message when you invoke var/invoke_msg var/favor_cost = 0 + /// does the altar auto-delete the rite + var/auto_delete = TRUE /datum/religion_rites/New() . = ..() diff --git a/icons/effects/particles/notes/note.dmi b/icons/effects/particles/notes/note.dmi new file mode 100644 index 00000000000..766a29e6a89 Binary files /dev/null and b/icons/effects/particles/notes/note.dmi differ diff --git a/icons/effects/particles/notes/note_harm.dmi b/icons/effects/particles/notes/note_harm.dmi new file mode 100644 index 00000000000..0fe386e1ed3 Binary files /dev/null and b/icons/effects/particles/notes/note_harm.dmi differ diff --git a/icons/effects/particles/notes/note_holy.dmi b/icons/effects/particles/notes/note_holy.dmi new file mode 100644 index 00000000000..f817e8253d3 Binary files /dev/null and b/icons/effects/particles/notes/note_holy.dmi differ diff --git a/icons/effects/particles/notes/note_null.dmi b/icons/effects/particles/notes/note_null.dmi new file mode 100644 index 00000000000..14c941f9e4c Binary files /dev/null and b/icons/effects/particles/notes/note_null.dmi differ diff --git a/icons/effects/particles/notes/note_sleepy.dmi b/icons/effects/particles/notes/note_sleepy.dmi new file mode 100644 index 00000000000..21030914a9c Binary files /dev/null and b/icons/effects/particles/notes/note_sleepy.dmi differ diff --git a/icons/obj/hand_of_god_structures.dmi b/icons/obj/hand_of_god_structures.dmi index 4c19d560b22..fc630457d2b 100644 Binary files a/icons/obj/hand_of_god_structures.dmi and b/icons/obj/hand_of_god_structures.dmi differ diff --git a/tgstation.dme b/tgstation.dme index c1ce38d6f07..707de2fd14c 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -769,6 +769,7 @@ #include "code\datums\components\sitcomlaughter.dm" #include "code\datums\components\sizzle.dm" #include "code\datums\components\slippery.dm" +#include "code\datums\components\smooth_tunes.dm" #include "code\datums\components\soulstoned.dm" #include "code\datums\components\sound_player.dm" #include "code\datums\components\spawner.dm" @@ -1343,6 +1344,7 @@ #include "code\game\objects\effects\mines.dm" #include "code\game\objects\effects\misc.dm" #include "code\game\objects\effects\overlays.dm" +#include "code\game\objects\effects\particle_holder.dm" #include "code\game\objects\effects\phased_mob.dm" #include "code\game\objects\effects\portals.dm" #include "code\game\objects\effects\powerup.dm" @@ -3853,6 +3855,8 @@ #include "code\modules\religion\religion_sects.dm" #include "code\modules\religion\religion_structures.dm" #include "code\modules\religion\rites.dm" +#include "code\modules\religion\festival\instrument_rites.dm" +#include "code\modules\religion\festival\note_particles.dm" #include "code\modules\religion\sparring\ceremonial_gear.dm" #include "code\modules\religion\sparring\sparring_contract.dm" #include "code\modules\religion\sparring\sparring_datum.dm"