Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions code/__DEFINES/~darkpack/blooper.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#define PREFERENCE_CATEGORY_VOCALS "vocals"

//BLOOPER defines
#define BLOOPER_DEFAULT_MINPITCH 0.4
#define BLOOPER_DEFAULT_MAXPITCH 2
#define BLOOPER_DEFAULT_MINVARY 0.1
#define BLOOPER_DEFAULT_MAXVARY 0.8
#define BLOOPER_DEFAULT_MINSPEED 2
#define BLOOPER_DEFAULT_MAXSPEED 16

#define BLOOPER_SPEED_BASELINE 4 //Used to calculate delay between BLOOPERs, any BLOOPER speeds below this feature higher BLOOPER density, any speeds above feature lower BLOOPER density. Keeps BLOOPERing length consistent

#define BLOOPER_TRANSMIT_VOLUME 55
#define BLOOPER_MAX_BLOOPERS 24
#define BLOOPER_MAX_TIME (1.5 SECONDS) // More or less the amount of time the above takes to process through with a BLOOPER speed of 2.

#define BLOOPER_DO_VARY(pitch, variance) (rand(((pitch * 100) - (variance*50)), ((pitch*100) + (variance*50))) / 100)

#define BLOOPER_SOUND_FALLOFF_EXPONENT 0.5 //At lower ranges, we want the exponent to be below 1 so that whispers don't sound too awkward. At higher ranges, we want the exponent fairly high to make yelling less obnoxious
18 changes: 18 additions & 0 deletions code/modules/antagonists/changeling/changeling.dm
Original file line number Diff line number Diff line change
Expand Up @@ -551,6 +551,12 @@
new_profile.underwear_color = target.underwear_color
new_profile.undershirt = target.undershirt
new_profile.socks = target.socks
// DARKPACK EDIT ADD START
new_profile.blooper = target.blooper
new_profile.blooper_speed = target.blooper_speed
new_profile.blooper_pitch = target.blooper_pitch
new_profile.blooper_pitch_range = target.blooper_pitch_range
// DARKPACK EDIT ADD END

// Grab skillchips they have
new_profile.skillchips = target.clone_skillchip_list(TRUE)
Expand Down Expand Up @@ -780,6 +786,12 @@
user.mind?.set_level(/datum/skill/athletics, chosen_profile.athletics_level, silent = TRUE)
user.voice = chosen_profile.voice
user.voice_filter = chosen_profile.voice_filter
// DARKPACK EDIT ADDITION START
user.blooper = chosen_profile.blooper
user.blooper_speed = chosen_profile.blooper_speed
user.blooper_pitch = chosen_profile.blooper_pitch
user.blooper_pitch_range = chosen_profile.blooper_pitch_range
// DARKPACK EDIT ADDITION END

chosen_dna.copy_dna(user.dna, COPY_DNA_SE|COPY_DNA_SPECIES)

Expand Down Expand Up @@ -970,6 +982,12 @@
new_profile.quirks = quirks.Copy()
new_profile.voice = voice
new_profile.voice_filter = voice_filter
// DARKPACK EDIT ADDITION START
new_profile.blooper = blooper
new_profile.blooper_speed = blooper_speed
new_profile.blooper_pitch = blooper_pitch
new_profile.blooper_pitch_range = blooper_pitch_range
// DARKPACK EDIT ADDITION END

/datum/antagonist/changeling/roundend_report()
var/list/parts = list()
Expand Down
24 changes: 24 additions & 0 deletions config/darkpack_config/bloopers/blooper_config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
[
{
"name": "Ehh 1",
"id": "ehh1",
"files": [
"ehh.ogg"
]
},

{
"name": "Pencil",
"id": "pencil",
"files": [
"pencil.ogg"
]
},
{
"name": "Dot",
"id": "dot",
"files": [
"dot.ogg"
]
}
]
13 changes: 13 additions & 0 deletions config/darkpack_config/bloopers/blooper_config.json.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[
{
"name": "Example Blooper",
"id": "example",
"files": ["example.ogg"],
"min_speed": 2,
"max_speed": 16,
"min_pitch": 0.4,
"max_pitch": 2,
"min_vary": 0.1,
"max_vary": 0.8
}
]
Binary file added config/darkpack_config/bloopers/sounds/dot.ogg
Binary file not shown.
Binary file added config/darkpack_config/bloopers/sounds/ehh.ogg
Binary file not shown.
Binary file added config/darkpack_config/bloopers/sounds/pencil.ogg
Binary file not shown.
15 changes: 15 additions & 0 deletions modular_darkpack/master_files/code/game/atoms_movable.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/atom/movable
Comment thread
chazzyjazzy marked this conversation as resolved.
// Text-to-blooper sounds
// yes. all atoms can have a say.
var/datum/blooper/blooper
var/blooper_speed = 50
var/blooper_pitch = 50
var/blooper_pitch_range = 50 //Actual pitch is (pitch - (blooper_pitch_range*0.5)) to (pitch + (blooper_pitch_range*0.5))
COOLDOWN_DECLARE(blooper_cooldown)

/atom/movable/send_speech(message, range = 7, obj/source = src, bubble_type, list/spans, datum/language/message_language, list/message_mods = list(), forced = FALSE, tts_message, list/tts_filter)
. = ..()
if(!blooper)
return
var/list/listeners = get_hearers_in_view(range, source)
blooper.play_bloop(source, listeners, message, range, BLOOPER_TRANSMIT_VOLUME, blooper_speed, blooper_pitch, blooper_pitch_range)
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,4 @@


. = ..()

Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,10 @@
AddComponent(/datum/component/jumper)
AddComponent(/datum/component/violation_observer, violation_aoe)
update_visible_name()
// clientless mobs are given a random voice
if(!client && length(SSblooper.blooper_list))
var/blooper_key = pick(SSblooper.blooper_list)
blooper = SSblooper.blooper_list[blooper_key]
blooper_speed = rand(0, 100)
blooper_pitch = rand(0, 100)
blooper_pitch_range = rand(0, 100)
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/mob/living/send_speech(message_raw, message_range = 6, obj/source = src, bubble_type = bubble_icon, list/spans, datum/language/message_language = null, list/message_mods = list(), forced = null, tts_message, list/tts_filter)
. = ..()
if(!blooper)
return
if(HAS_TRAIT(src, TRAIT_SIGN_LANG) && !HAS_TRAIT(src, TRAIT_MUTE)) //if you can speak and you sign, your hands don't make a bark. Unless you are completely mute, you can have some hand bark.
return
var/pref_volume = client ? (client?.prefs?.read_preference(/datum/preference/numeric/blooper_sound_volume) / 100) : 1
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If no client... volume at 1... why are we even bothering if we dont have a client.

Copy link
Copy Markdown
Contributor Author

@chazzyjazzy chazzyjazzy Apr 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NPC voices have been fun to see?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am misunderstanding it or is this checking the client of the HEARER not the speaker.

var/volume = BLOOPER_TRANSMIT_VOLUME * pref_volume
if(message_mods[WHISPER_MODE])
volume = BLOOPER_TRANSMIT_VOLUME * 0.5
message_range++
var/list/listeners = get_hearers_in_view(message_range, source)
var/is_yelling = (say_test(message_raw) == "2") // boost the volume if their message ends in !
blooper.play_bloop(source, listeners, message_raw, message_range, volume * (is_yelling ? 2 : 1), blooper_speed, blooper_pitch, blooper_pitch_range)
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// we let borgs have some bark too
/mob/living/silicon/Login()
blooper = SSblooper.blooper_list[client.prefs.read_preference(/datum/preference/choiced/blooper)]
blooper_speed = client.prefs.read_preference(/datum/preference/numeric/blooper_speed)
blooper_pitch = client.prefs.read_preference(/datum/preference/numeric/blooper_pitch)
blooper_pitch_range = client.prefs.read_preference(/datum/preference/numeric/blooper_pitch_range)
. = ..()
51 changes: 51 additions & 0 deletions modular_darkpack/modules/blooper/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
https://github.com/DarkPack13/SecondCity/pull/945

## \<Bloopers>

Module ID: BLOOPERS

### Description:

Adds voice bloopers (voice barks) to the game. Similiar to animal crossing animalese. Noises that get played when a character talks.

By default, no vocal bloopers are included in the repository, a sample config file is provided to demonstrate the format used.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why tho?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

idk - probably because of licensing concerns?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

comment is no longer accurate as we provide a few bloopers.

To set things up, copy `blooper_config.json.example` and rename it to `blooper_config.json`, then add an entry for each blooper.

### Config Format

- name: Required, the user-friendly name of the voice, will be shown in the config menu
- id: Required, an internal ID for the voice, must be unique
- files: Required, a JSON array of audio file names, relative to the bloopers directory.
If there's more than one entry, a file is randomly chosen each time the user speaks
- min_speed: Optional, the minimum value the user can set for the voice speed
- max_speed: Optional, the maximum value the user can set for the voice speed
- min_pitch: Optional, the minimum value the user can set for the voice pitch
- max_pitch: Optional, the maximum value the user can set for the voice pitch
- min_vary: Optional, the minimum value the user can set for the voice pitch variance
- max_vary: Optional, the maximum value the user can set for the voice pitch variance

### TG Proc/File Changes:

- N/A

### Modular Overrides:

- N/A

### Defines:

- N/A

### Included files that are not contained in this module:

- N/A

### Credits:

XeonMations
Comment thread
chazzyjazzy marked this conversation as resolved.
xPokee
LT3
TealSeer
GeneriedJenelle
ghost
SynthTwo
63 changes: 63 additions & 0 deletions modular_darkpack/modules/blooper/code/blooper.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/datum/blooper
/// User friendly name of the blooper, displayed in preferences menu
var/name = null
/// Internal ID of blooper, must be unique
var/id = null
/// List of sounds, one is chosen randomly each time the blooper is played
var/list/sound/soundpath_list = list()

var/min_pitch = BLOOPER_DEFAULT_MINPITCH
var/max_pitch = BLOOPER_DEFAULT_MAXPITCH
var/min_vary = BLOOPER_DEFAULT_MINVARY
var/max_vary = BLOOPER_DEFAULT_MAXVARY

// Speed vars. Speed determines the number of characters required for each blooper, with lower speeds being faster with higher blooper density
var/min_speed = BLOOPER_DEFAULT_MINSPEED
var/max_speed = BLOOPER_DEFAULT_MAXSPEED

/**
* Plays the vocal blooper for all listeners, duration is controlled by length of the message argument
* * speaker - Origin source of the blooper, used for 3D audio calculations and to set the cooldown
* * listeners - List of mobs that will hear the blooper
* * message - Chat message being sent, used to determine how long bloopers play for (longer message, more sounds)
* * distance - Range at which the sounds will be heard
* * volume - Volume the sounds will play at
* * speed - How fast the bloopers will be, percentage between 0 - 100, converted and clamped to datum's min/max values
* * pitch - How high-pitched the bloopers will be, percentage between 0 - 100, converted and clamped to datum's min/max values
* * pitch_range - Amount of pitch variance for each blooper, percentage between 0 - 100, converted and clamped to datum's max/min values
*/
/datum/blooper/proc/play_bloop(atom/movable/speaker, list/listeners, message, distance, volume, speed, pitch, pitch_range)
if(!COOLDOWN_FINISHED(speaker, blooper_cooldown))
return
volume = min(volume, 100)
// convert passed values (which are percentages) into value clamped between min and max of blooper datum
speed = round(min_speed + ((max_speed - min_speed) * ((100 - speed) / 100)), 0.01) // this one gets inverted because lower % = faster isn't intuitive
pitch = round(min_pitch + ((max_pitch - min_pitch) * (pitch / 100)), 0.01)
pitch_range = round(min_vary + ((max_vary - min_vary) * (pitch_range / 100)), 0.01)
for(var/mob/target_mob in listeners)
if(target_mob.client && !(target_mob.client.prefs.read_preference(/datum/preference/toggle/hear_blooper)))
listeners -= target_mob
var/sound/blooper_pick = pick(soundpath_list)
var/num_bloopers = min(round(length(message) / speed, 1) + 1, BLOOPER_MAX_BLOOPERS)
var/total_delay = 0
for(var/i in 1 to num_bloopers)
if(total_delay > BLOOPER_MAX_TIME)
break
addtimer(CALLBACK(src, PROC_REF(play_callback), speaker, listeners, distance, volume, BLOOPER_DO_VARY(pitch, pitch_range), blooper_pick), total_delay)
total_delay += rand(DS2TICKS(speed / BLOOPER_SPEED_BASELINE), DS2TICKS(speed / BLOOPER_SPEED_BASELINE) + DS2TICKS(speed / BLOOPER_SPEED_BASELINE)) TICKS
COOLDOWN_START(speaker, blooper_cooldown, total_delay)

/// Private callback function to actually play the sound, used for timers scheduled in play_bloop
/datum/blooper/proc/play_callback(atom/movable/speaker, list/listeners, distance, volume, pitch, sound/voice)
PRIVATE_PROC(TRUE)
for(var/mob/target_mob in listeners)
target_mob.playsound_local(
turf_source = get_turf(speaker),
soundin = voice,
vol = volume,
vary = TRUE,
frequency = pitch,
falloff_distance = 0,
falloff_exponent = BLOOPER_SOUND_FALLOFF_EXPONENT,
max_distance = distance,
)
40 changes: 40 additions & 0 deletions modular_darkpack/modules/blooper/code/blooper_subsystem.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#define BLOOPER_CONFIG_PATH "[global.config.directory]/darkpack_config/bloopers"

SUBSYSTEM_DEF(blooper)
name = "Blooper"
ss_flags = SS_NO_FIRE | SS_NO_INIT

var/list/blooper_list

/datum/controller/subsystem/blooper/OnConfigLoad()
blooper_list = initialize_blooper_datums()

/datum/controller/subsystem/blooper/proc/initialize_blooper_datums()
var/list/blooper_datums = list()
if(!rustg_file_exists("[BLOOPER_CONFIG_PATH]/blooper_config.json"))
logger.Log(LOG_CATEGORY_DEBUG, "blooper_config.json not found.")
return blooper_datums
var/list/blooper_entries = safe_json_decode(rustg_file_read("[BLOOPER_CONFIG_PATH]/blooper_config.json"))
if(isnull(blooper_entries))
stack_trace("Blooper config is malformed!")
return blooper_datums
for(var/entry in blooper_entries)
// These fields are required
if(isnull(entry["name"]) || isnull(entry["id"]) || isnull(entry["files"]) || !length(entry["files"]))
stack_trace("Blooper config entry was missing required field!")
continue
var/datum/blooper/new_blooper = new()
new_blooper.name = entry["name"]
new_blooper.id = entry["id"]
for(var/file in entry["files"])
new_blooper.soundpath_list += sound("[BLOOPER_CONFIG_PATH]/sounds/[file]")
new_blooper.min_pitch = entry["min_pitch"] || BLOOPER_DEFAULT_MINPITCH
new_blooper.max_pitch = entry["max_pitch"] || BLOOPER_DEFAULT_MAXPITCH
new_blooper.min_vary = entry["min_vary"] || BLOOPER_DEFAULT_MINVARY
new_blooper.max_vary = entry["max_vary"] || BLOOPER_DEFAULT_MAXVARY
new_blooper.min_speed = entry["min_speed"] || BLOOPER_DEFAULT_MINSPEED
new_blooper.max_speed = entry["max_speed"] || BLOOPER_DEFAULT_MAXSPEED
blooper_datums[new_blooper.id] = new_blooper
return blooper_datums

#undef BLOOPER_CONFIG_PATH
9 changes: 9 additions & 0 deletions modular_darkpack/modules/blooper/code/changeling.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/datum/changeling_profile
/// The blooper voice used by the profile source
var/datum/blooper/blooper
/// The blooper speed used by the profile source
var/blooper_speed
/// The blooper pitch used by the profile source
var/blooper_pitch
/// The blooper pitch range used by the profile source
var/blooper_pitch_range
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/datum/preference_middleware/blooper
action_delegations = list(
"play_blooper" = PROC_REF(play_blooper),
)

/datum/preference_middleware/blooper/proc/play_blooper(list/params, mob/user)
var/datum/blooper/blooper_to_use = SSblooper.blooper_list[preferences.read_preference(/datum/preference/choiced/blooper)]
var/blooper_speed = preferences.read_preference(/datum/preference/numeric/blooper_speed)
var/blooper_pitch = preferences.read_preference(/datum/preference/numeric/blooper_pitch)
var/blooper_pitch_range = preferences.read_preference(/datum/preference/numeric/blooper_pitch_range)
blooper_to_use.play_bloop(user, list(user), "This is a test message to hear a blooper.", 7, 70, blooper_speed, blooper_pitch, blooper_pitch_range)
return TRUE
Loading
Loading