Merge pull request #2107 from CHOMPStationBot/upstream-merge-10518
[MIRROR] Ports musical instruments from /tg/
@@ -6,6 +6,10 @@
|
||||
|
||||
#define SEND_GLOBAL_SIGNAL(sigtype, arguments...) ( SEND_SIGNAL(SSdcs, sigtype, ##arguments) )
|
||||
|
||||
/// Signifies that this proc is used to handle signals.
|
||||
/// Every proc you pass to RegisterSignal must have this.
|
||||
#define SIGNAL_HANDLER SHOULD_NOT_SLEEP(TRUE)
|
||||
|
||||
/// A wrapper for _AddElement that allows us to pretend we're using normal named arguments
|
||||
#define AddElement(arguments...) _AddElement(list(##arguments))
|
||||
/// A wrapper for _RemoveElement that allows us to pretend we're using normal named arguments
|
||||
|
||||
24
code/__defines/instruments.dm
Normal file
@@ -0,0 +1,24 @@
|
||||
#define INSTRUMENT_MIN_OCTAVE 1
|
||||
#define INSTRUMENT_MAX_OCTAVE 9
|
||||
#define INSTRUMENT_MIN_KEY 0
|
||||
#define INSTRUMENT_MAX_KEY 127
|
||||
|
||||
/// Max number of playing notes per instrument.
|
||||
#define CHANNELS_PER_INSTRUMENT 128
|
||||
|
||||
/// Maximum length a note should ever go for
|
||||
#define INSTRUMENT_MAX_TOTAL_SUSTAIN (5 SECONDS)
|
||||
|
||||
/// These are per decisecond.
|
||||
#define INSTRUMENT_EXP_FALLOFF_MIN 1.025 //100/(1.025^50) calculated for [INSTRUMENT_MIN_SUSTAIN_DROPOFF] to be 30.
|
||||
#define INSTRUMENT_EXP_FALLOFF_MAX 10
|
||||
|
||||
/// Minimum volume for when the sound is considered dead.
|
||||
#define INSTRUMENT_MIN_SUSTAIN_DROPOFF 0
|
||||
|
||||
#define SUSTAIN_LINEAR 1
|
||||
#define SUSTAIN_EXPONENTIAL 2
|
||||
|
||||
// /datum/instrument instrument_flags
|
||||
#define INSTRUMENT_LEGACY (1<<0) //Legacy instrument. Implies INSTRUMENT_DO_NOT_AUTOSAMPLE
|
||||
#define INSTRUMENT_DO_NOT_AUTOSAMPLE (1<<1) //Do not automatically sample
|
||||
@@ -423,6 +423,7 @@ GLOBAL_LIST_EMPTY(##LIST_NAME);\
|
||||
#define VOLUME_CHANNEL_ALARMS "Alarms"
|
||||
#define VOLUME_CHANNEL_VORE "Vore"
|
||||
#define VOLUME_CHANNEL_DOORS "Doors"
|
||||
#define VOLUME_CHANNEL_INSTRUMENTS "Instruments"
|
||||
|
||||
// Make sure you update this or clients won't be able to adjust the channel
|
||||
GLOBAL_LIST_INIT(all_volume_channels, list(
|
||||
@@ -431,6 +432,7 @@ GLOBAL_LIST_INIT(all_volume_channels, list(
|
||||
VOLUME_CHANNEL_ALARMS,
|
||||
VOLUME_CHANNEL_VORE,
|
||||
VOLUME_CHANNEL_DOORS,
|
||||
VOLUME_CHANNEL_INSTRUMENTS
|
||||
))
|
||||
|
||||
#define APPEARANCECHANGER_CHANGED_RACE "Race"
|
||||
|
||||
@@ -18,43 +18,47 @@
|
||||
#define SOUND_MINIMUM_PRESSURE 10
|
||||
#define FALLOFF_SOUNDS 0.5
|
||||
|
||||
//Sound environment defines. Reverb preset for sounds played in an area, see sound datum reference for more.
|
||||
#define GENERIC 0
|
||||
#define PADDED_CELL 1
|
||||
#define ROOM 2
|
||||
#define BATHROOM 3
|
||||
#define LIVINGROOM 4
|
||||
#define STONEROOM 5
|
||||
#define AUDITORIUM 6
|
||||
#define CONCERT_HALL 7
|
||||
#define CAVE 8
|
||||
#define ARENA 9
|
||||
#define HANGAR 10
|
||||
#define CARPETED_HALLWAY 11
|
||||
#define HALLWAY 12
|
||||
#define STONE_CORRIDOR 13
|
||||
#define ALLEY 14
|
||||
#define FOREST 15
|
||||
#define CITY 16
|
||||
#define MOUNTAINS 17
|
||||
#define QUARRY 18
|
||||
#define PLAIN 19
|
||||
#define PARKING_LOT 20
|
||||
#define SEWER_PIPE 21
|
||||
#define UNDERWATER 22
|
||||
#define DRUGGED 23
|
||||
#define DIZZY 24
|
||||
#define PSYCHOTIC 25
|
||||
#define MAX_INSTRUMENT_CHANNELS (128 * 6)
|
||||
|
||||
#define STANDARD_STATION STONEROOM
|
||||
#define LARGE_ENCLOSED HANGAR
|
||||
#define SMALL_ENCLOSED BATHROOM
|
||||
#define TUNNEL_ENCLOSED CAVE
|
||||
#define LARGE_SOFTFLOOR CARPETED_HALLWAY
|
||||
#define MEDIUM_SOFTFLOOR LIVINGROOM
|
||||
#define SMALL_SOFTFLOOR ROOM
|
||||
#define ASTEROID CAVE
|
||||
#define SPACE UNDERWATER
|
||||
//default byond sound environments
|
||||
#define SOUND_ENVIRONMENT_NONE -1
|
||||
#define SOUND_ENVIRONMENT_GENERIC 0
|
||||
#define SOUND_ENVIRONMENT_PADDED_CELL 1
|
||||
#define SOUND_ENVIRONMENT_ROOM 2
|
||||
#define SOUND_ENVIRONMENT_BATHROOM 3
|
||||
#define SOUND_ENVIRONMENT_LIVINGROOM 4
|
||||
#define SOUND_ENVIRONMENT_STONEROOM 5
|
||||
#define SOUND_ENVIRONMENT_AUDITORIUM 6
|
||||
#define SOUND_ENVIRONMENT_CONCERT_HALL 7
|
||||
#define SOUND_ENVIRONMENT_CAVE 8
|
||||
#define SOUND_ENVIRONMENT_ARENA 9
|
||||
#define SOUND_ENVIRONMENT_HANGAR 10
|
||||
#define SOUND_ENVIRONMENT_CARPETED_HALLWAY 11
|
||||
#define SOUND_ENVIRONMENT_HALLWAY 12
|
||||
#define SOUND_ENVIRONMENT_STONE_CORRIDOR 13
|
||||
#define SOUND_ENVIRONMENT_ALLEY 14
|
||||
#define SOUND_ENVIRONMENT_FOREST 15
|
||||
#define SOUND_ENVIRONMENT_CITY 16
|
||||
#define SOUND_ENVIRONMENT_MOUNTAINS 17
|
||||
#define SOUND_ENVIRONMENT_QUARRY 18
|
||||
#define SOUND_ENVIRONMENT_PLAIN 19
|
||||
#define SOUND_ENVIRONMENT_PARKING_LOT 20
|
||||
#define SOUND_ENVIRONMENT_SEWER_PIPE 21
|
||||
#define SOUND_ENVIRONMENT_UNDERWATER 22
|
||||
#define SOUND_ENVIRONMENT_DRUGGED 23
|
||||
#define SOUND_ENVIRONMENT_DIZZY 24
|
||||
#define SOUND_ENVIRONMENT_PSYCHOTIC 25
|
||||
//If we ever make custom ones add them here
|
||||
|
||||
#define STANDARD_STATION SOUND_ENVIRONMENT_STONEROOM
|
||||
#define LARGE_ENCLOSED SOUND_ENVIRONMENT_HANGAR
|
||||
#define SMALL_ENCLOSED SOUND_ENVIRONMENT_BATHROOM
|
||||
#define TUNNEL_ENCLOSED SOUND_ENVIRONMENT_CAVE
|
||||
#define LARGE_SOFTFLOOR SOUND_ENVIRONMENT_CARPETED_HALLWAY
|
||||
#define MEDIUM_SOFTFLOOR SOUND_ENVIRONMENT_LIVINGROOM
|
||||
#define SMALL_SOFTFLOOR SOUND_ENVIRONMENT_ROOM
|
||||
#define ASTEROID SOUND_ENVIRONMENT_CAVE
|
||||
#define SPACE SOUND_ENVIRONMENT_UNDERWATER
|
||||
|
||||
// Ambience presets.
|
||||
// All you need to do to make an area play one of these is set their ambience var to one of these lists.
|
||||
|
||||
@@ -58,6 +58,8 @@ var/global/list/runlevel_flags = list(RUNLEVEL_LOBBY, RUNLEVEL_SETUP, RUNLEVEL_G
|
||||
#define INIT_ORDER_CHEMISTRY 35
|
||||
#define INIT_ORDER_SKYBOX 30
|
||||
#define INIT_ORDER_MAPPING 25
|
||||
#define INIT_ORDER_SOUNDS 23
|
||||
#define INIT_ORDER_INSTRUMENTS 22
|
||||
#define INIT_ORDER_DECALS 20
|
||||
#define INIT_ORDER_PLANTS 19 // Must initialize before atoms.
|
||||
#define INIT_ORDER_PLANETS 18
|
||||
@@ -92,8 +94,9 @@ var/global/list/runlevel_flags = list(RUNLEVEL_LOBBY, RUNLEVEL_SETUP, RUNLEVEL_G
|
||||
#define FIRE_PRIORITY_SUPPLY 5
|
||||
#define FIRE_PRIORITY_NIGHTSHIFT 5
|
||||
#define FIRE_PRIORITY_PLANTS 5
|
||||
#define FIRE_PRIORITY_ORBIT 8
|
||||
#define FIRE_PRIORITY_VOTE 9
|
||||
#define FIRE_PRIORITY_ORBIT 7
|
||||
#define FIRE_PRIORITY_VOTE 8
|
||||
#define FIRE_PRIORITY_INSTRUMENTS 9
|
||||
#define FIRE_PRIORITY_AI 10
|
||||
#define FIRE_PRIORITY_GARBAGE 15
|
||||
#define FIRE_PRIORITY_ALARM 20
|
||||
|
||||
56
code/controllers/subsystems/processing/instruments.dm
Normal file
@@ -0,0 +1,56 @@
|
||||
PROCESSING_SUBSYSTEM_DEF(instruments)
|
||||
name = "Instruments"
|
||||
wait = 0.5
|
||||
init_order = INIT_ORDER_INSTRUMENTS
|
||||
flags = SS_KEEP_TIMING
|
||||
priority = FIRE_PRIORITY_INSTRUMENTS
|
||||
/// List of all instrument data, associative id = datum
|
||||
var/static/list/datum/instrument/instrument_data = list()
|
||||
/// List of all song datums.
|
||||
var/static/list/datum/song/songs = list()
|
||||
/// Max lines in songs
|
||||
var/static/musician_maxlines = 600
|
||||
/// Max characters per line in songs
|
||||
var/static/musician_maxlinechars = 300
|
||||
/// Deciseconds between hearchecks. Too high and instruments seem to lag when people are moving around in terms of who can hear it. Too low and the server lags from this.
|
||||
var/static/musician_hearcheck_mindelay = 5
|
||||
/// Maximum instrument channels total instruments are allowed to use. This is so you don't have instruments deadlocking all sound channels.
|
||||
var/static/max_instrument_channels = MAX_INSTRUMENT_CHANNELS
|
||||
/// Current number of channels allocated for instruments
|
||||
var/static/current_instrument_channels = 0
|
||||
/// Single cached list for synthesizer instrument ids, so you don't have to have a new list with every synthesizer.
|
||||
var/static/list/synthesizer_instrument_ids
|
||||
|
||||
/datum/controller/subsystem/processing/instruments/Initialize()
|
||||
initialize_instrument_data()
|
||||
synthesizer_instrument_ids = get_allowed_instrument_ids()
|
||||
return ..()
|
||||
|
||||
/datum/controller/subsystem/processing/instruments/proc/on_song_new(datum/song/S)
|
||||
songs += S
|
||||
|
||||
/datum/controller/subsystem/processing/instruments/proc/on_song_del(datum/song/S)
|
||||
songs -= S
|
||||
|
||||
/datum/controller/subsystem/processing/instruments/proc/initialize_instrument_data()
|
||||
for(var/path in subtypesof(/datum/instrument))
|
||||
var/datum/instrument/I = path
|
||||
if(initial(I.abstract_type) == path)
|
||||
continue
|
||||
I = new path
|
||||
I.Initialize()
|
||||
if(!I.id)
|
||||
qdel(I)
|
||||
continue
|
||||
instrument_data[I.id] = I
|
||||
CHECK_TICK
|
||||
|
||||
/datum/controller/subsystem/processing/instruments/proc/get_instrument(id_or_path)
|
||||
return instrument_data["[id_or_path]"]
|
||||
|
||||
/datum/controller/subsystem/processing/instruments/proc/reserve_instrument_channel(datum/instrument/I)
|
||||
if(current_instrument_channels > max_instrument_channels)
|
||||
return
|
||||
. = SSsounds.reserve_sound_channel(I)
|
||||
if(!isnull(.))
|
||||
current_instrument_channels++
|
||||
135
code/controllers/subsystems/sounds.dm
Normal file
@@ -0,0 +1,135 @@
|
||||
#define DATUMLESS "NO_DATUM"
|
||||
|
||||
SUBSYSTEM_DEF(sounds)
|
||||
name = "Sounds"
|
||||
flags = SS_NO_FIRE
|
||||
init_order = INIT_ORDER_SOUNDS
|
||||
var/static/using_channels_max = CHANNEL_HIGHEST_AVAILABLE //BYOND max channels
|
||||
/// Amount of channels to reserve for random usage rather than reservations being allowed to reserve all channels. Also a nice safeguard for when someone screws up.
|
||||
var/static/random_channels_min = 50
|
||||
|
||||
// Hey uh these two needs to be initialized fast because the whole "things get deleted before init" thing.
|
||||
/// Assoc list, `"[channel]" =` either the datum using it or TRUE for an unsafe-reserved (datumless reservation) channel
|
||||
var/list/using_channels
|
||||
/// Assoc list datum = list(channel1, channel2, ...) for what channels something reserved.
|
||||
var/list/using_channels_by_datum
|
||||
// Special datastructure for fast channel management
|
||||
/// List of all channels as numbers
|
||||
var/list/channel_list
|
||||
/// Associative list of all reserved channels associated to their position. `"[channel_number]" =` index as number
|
||||
var/list/reserved_channels
|
||||
/// lower iteration position - Incremented and looped to get "random" sound channels for normal sounds. The channel at this index is returned when asking for a random channel.
|
||||
var/channel_random_low
|
||||
/// higher reserve position - decremented and incremented to reserve sound channels, anything above this is reserved. The channel at this index is the highest unreserved channel.
|
||||
var/channel_reserve_high
|
||||
|
||||
/datum/controller/subsystem/sounds/Initialize()
|
||||
setup_available_channels()
|
||||
return ..()
|
||||
|
||||
/datum/controller/subsystem/sounds/proc/setup_available_channels()
|
||||
channel_list = list()
|
||||
reserved_channels = list()
|
||||
using_channels = list()
|
||||
using_channels_by_datum = list()
|
||||
for(var/i in 1 to using_channels_max)
|
||||
channel_list += i
|
||||
channel_random_low = 1
|
||||
channel_reserve_high = length(channel_list)
|
||||
|
||||
/// Removes a channel from using list.
|
||||
/datum/controller/subsystem/sounds/proc/free_sound_channel(channel)
|
||||
var/text_channel = num2text(channel)
|
||||
var/using = using_channels[text_channel]
|
||||
using_channels -= text_channel
|
||||
if(using != TRUE) // datum channel
|
||||
using_channels_by_datum[using] -= channel
|
||||
if(!length(using_channels_by_datum[using]))
|
||||
using_channels_by_datum -= using
|
||||
free_channel(channel)
|
||||
|
||||
/// Frees all the channels a datum is using.
|
||||
/datum/controller/subsystem/sounds/proc/free_datum_channels(datum/D)
|
||||
var/list/L = using_channels_by_datum[D]
|
||||
if(!L)
|
||||
return
|
||||
for(var/channel in L)
|
||||
using_channels -= num2text(channel)
|
||||
free_channel(channel)
|
||||
using_channels_by_datum -= D
|
||||
|
||||
/// Frees all datumless channels
|
||||
/datum/controller/subsystem/sounds/proc/free_datumless_channels()
|
||||
free_datum_channels(DATUMLESS)
|
||||
|
||||
/// NO AUTOMATIC CLEANUP - If you use this, you better manually free it later! Returns an integer for channel.
|
||||
/datum/controller/subsystem/sounds/proc/reserve_sound_channel_datumless()
|
||||
. = reserve_channel()
|
||||
if(!.) //oh no..
|
||||
return FALSE
|
||||
var/text_channel = num2text(.)
|
||||
using_channels[text_channel] = DATUMLESS
|
||||
LAZYINITLIST(using_channels_by_datum[DATUMLESS])
|
||||
using_channels_by_datum[DATUMLESS] += .
|
||||
|
||||
/// Reserves a channel for a datum. Automatic cleanup only when the datum is deleted. Returns an integer for channel.
|
||||
/datum/controller/subsystem/sounds/proc/reserve_sound_channel(datum/D)
|
||||
if(!D) //i don't like typechecks but someone will fuck it up
|
||||
CRASH("Attempted to reserve sound channel without datum using the managed proc.")
|
||||
.= reserve_channel()
|
||||
if(!.)
|
||||
return FALSE
|
||||
var/text_channel = num2text(.)
|
||||
using_channels[text_channel] = D
|
||||
LAZYINITLIST(using_channels_by_datum[D])
|
||||
using_channels_by_datum[D] += .
|
||||
|
||||
/**
|
||||
* Reserves a channel and updates the datastructure. Private proc.
|
||||
*/
|
||||
/datum/controller/subsystem/sounds/proc/reserve_channel()
|
||||
PRIVATE_PROC(TRUE)
|
||||
if(channel_reserve_high <= random_channels_min) // out of channels
|
||||
return
|
||||
var/channel = channel_list[channel_reserve_high]
|
||||
reserved_channels[num2text(channel)] = channel_reserve_high--
|
||||
return channel
|
||||
|
||||
/**
|
||||
* Frees a channel and updates the datastructure. Private proc.
|
||||
*/
|
||||
/datum/controller/subsystem/sounds/proc/free_channel(number)
|
||||
PRIVATE_PROC(TRUE)
|
||||
var/text_channel = num2text(number)
|
||||
var/index = reserved_channels[text_channel]
|
||||
if(!index)
|
||||
CRASH("Attempted to (internally) free a channel that wasn't reserved.")
|
||||
reserved_channels -= text_channel
|
||||
// push reserve index up, which makes it now on a channel that is reserved
|
||||
channel_reserve_high++
|
||||
// swap the reserved channel wtih the unreserved channel so the reserve index is now on an unoccupied channel and the freed channel is next to be used.
|
||||
channel_list.Swap(channel_reserve_high, index)
|
||||
// now, an existing reserved channel will likely (exception: unreserving last reserved channel) be at index
|
||||
// get it, and update position.
|
||||
var/text_reserved = num2text(channel_list[index])
|
||||
if(!reserved_channels[text_reserved]) //if it isn't already reserved make sure we don't accidently mistakenly put it on reserved list!
|
||||
return
|
||||
reserved_channels[text_reserved] = index
|
||||
|
||||
/// Random available channel, returns text.
|
||||
/datum/controller/subsystem/sounds/proc/random_available_channel_text()
|
||||
if(channel_random_low > channel_reserve_high)
|
||||
channel_random_low = 1
|
||||
. = "[channel_list[channel_random_low++]]"
|
||||
|
||||
/// Random available channel, returns number
|
||||
/datum/controller/subsystem/sounds/proc/random_available_channel()
|
||||
if(channel_random_low > channel_reserve_high)
|
||||
channel_random_low = 1
|
||||
. = channel_list[channel_random_low++]
|
||||
|
||||
/// How many channels we have left.
|
||||
/datum/controller/subsystem/sounds/proc/available_channels_left()
|
||||
return length(channel_list) - random_channels_min
|
||||
|
||||
#undef DATUMLESS
|
||||
@@ -80,7 +80,7 @@
|
||||
var/list/atoms_cache = output_atoms
|
||||
var/sound/S = sound(soundfile)
|
||||
if(direct)
|
||||
S.channel = open_sound_channel()
|
||||
S.channel = SSsounds.random_available_channel()
|
||||
S.volume = volume
|
||||
for(var/i in 1 to atoms_cache.len)
|
||||
var/atom/thing = atoms_cache[i]
|
||||
|
||||
39
code/datums/supplypacks/musical.dm
Normal file
@@ -0,0 +1,39 @@
|
||||
/datum/supply_pack/musical/strings
|
||||
contains = list(
|
||||
/obj/item/instrument/violin,
|
||||
/obj/item/instrument/banjo,
|
||||
/obj/item/instrument/guitar,
|
||||
/obj/item/instrument/eguitar,
|
||||
)
|
||||
name = "string instruments"
|
||||
cost = 50
|
||||
containertype = /obj/structure/closet/crate
|
||||
containername = "string instrument crate"
|
||||
|
||||
/datum/supply_pack/musical/wind
|
||||
contains = list(
|
||||
/obj/item/instrument/accordion,
|
||||
/obj/item/instrument/trumpet,
|
||||
/obj/item/instrument/saxophone,
|
||||
/obj/item/instrument/trombone,
|
||||
/obj/item/instrument/recorder,
|
||||
/obj/item/instrument/harmonica,
|
||||
/obj/item/instrument/bikehorn,
|
||||
)
|
||||
name = "wind instruments"
|
||||
cost = 50
|
||||
containertype = /obj/structure/closet/crate
|
||||
containername = "wind instrument crate"
|
||||
|
||||
/datum/supply_pack/musical/keys
|
||||
contains = list(
|
||||
/obj/item/instrument/piano_synth,
|
||||
/obj/item/instrument/glockenspiel, // cough
|
||||
/obj/item/instrument/musicalmoth
|
||||
)
|
||||
name = "keyed instruments"
|
||||
cost = 50
|
||||
containertype = /obj/structure/closet/crate
|
||||
containername = "keyed instruments crate"
|
||||
|
||||
// /obj/item/instrument/piano_synth/headphones
|
||||
@@ -1,15 +1,15 @@
|
||||
/datum/supply_pack/recreation/bigband
|
||||
name = "Instrument bundle"
|
||||
contains = list(
|
||||
/obj/item/device/instrument/guitar = 1,
|
||||
/obj/item/device/instrument/keytar = 1,
|
||||
/obj/item/device/instrument/eguitar = 1,
|
||||
/obj/item/device/instrument/xylophone = 1,
|
||||
/obj/item/device/instrument/accordion = 1,
|
||||
/obj/item/device/instrument/saxophone = 1,
|
||||
/obj/item/device/instrument/glockenspiel = 1,
|
||||
/obj/item/device/instrument/harmonica = 1,
|
||||
/obj/item/device/instrument/trombone = 1,
|
||||
/obj/item/instrument/guitar = 1,
|
||||
/obj/item/instrument/keytar = 1,
|
||||
/obj/item/instrument/eguitar = 1,
|
||||
/obj/item/instrument/xylophone = 1,
|
||||
/obj/item/instrument/accordion = 1,
|
||||
/obj/item/instrument/saxophone = 1,
|
||||
/obj/item/instrument/glockenspiel = 1,
|
||||
/obj/item/instrument/harmonica = 1,
|
||||
/obj/item/instrument/trombone = 1,
|
||||
)
|
||||
cost = 100
|
||||
containertype = /obj/structure/closet/crate
|
||||
|
||||
@@ -339,7 +339,7 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
|
||||
icon_state = "thunder"
|
||||
requires_power = 0
|
||||
dynamic_lighting = 0
|
||||
sound_env = ARENA
|
||||
sound_env = SOUND_ENVIRONMENT_ARENA
|
||||
flags = AREA_FLAG_IS_NOT_PERSISTENT
|
||||
|
||||
/area/tdome/tdome1
|
||||
@@ -1289,28 +1289,28 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
|
||||
|
||||
/area/holodeck/source_emptycourt
|
||||
name = "\improper Holodeck - Empty Court"
|
||||
sound_env = ARENA
|
||||
sound_env = SOUND_ENVIRONMENT_ARENA
|
||||
|
||||
/area/holodeck/source_boxingcourt
|
||||
name = "\improper Holodeck - Boxing Court"
|
||||
sound_env = ARENA
|
||||
sound_env = SOUND_ENVIRONMENT_ARENA
|
||||
|
||||
/area/holodeck/source_basketball
|
||||
name = "\improper Holodeck - Basketball Court"
|
||||
sound_env = ARENA
|
||||
sound_env = SOUND_ENVIRONMENT_ARENA
|
||||
|
||||
/area/holodeck/source_thunderdomecourt
|
||||
name = "\improper Holodeck - Thunderdome Court"
|
||||
requires_power = 0
|
||||
sound_env = ARENA
|
||||
sound_env = SOUND_ENVIRONMENT_ARENA
|
||||
|
||||
/area/holodeck/source_courtroom
|
||||
name = "\improper Holodeck - Courtroom"
|
||||
sound_env = AUDITORIUM
|
||||
sound_env = SOUND_ENVIRONMENT_AUDITORIUM
|
||||
|
||||
/area/holodeck/source_beach
|
||||
name = "\improper Holodeck - Beach"
|
||||
sound_env = PLAIN
|
||||
sound_env = SOUND_ENVIRONMENT_PLAIN
|
||||
|
||||
/area/holodeck/source_burntest
|
||||
name = "\improper Holodeck - Atmospheric Burn Test"
|
||||
@@ -1320,23 +1320,23 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
|
||||
|
||||
/area/holodeck/source_meetinghall
|
||||
name = "\improper Holodeck - Meeting Hall"
|
||||
sound_env = AUDITORIUM
|
||||
sound_env = SOUND_ENVIRONMENT_AUDITORIUM
|
||||
|
||||
/area/holodeck/source_theatre
|
||||
name = "\improper Holodeck - Theatre"
|
||||
sound_env = CONCERT_HALL
|
||||
sound_env = SOUND_ENVIRONMENT_CONCERT_HALL
|
||||
|
||||
/area/holodeck/source_picnicarea
|
||||
name = "\improper Holodeck - Picnic Area"
|
||||
sound_env = PLAIN
|
||||
sound_env = SOUND_ENVIRONMENT_PLAIN
|
||||
|
||||
/area/holodeck/source_snowfield
|
||||
name = "\improper Holodeck - Snow Field"
|
||||
sound_env = FOREST
|
||||
sound_env = SOUND_ENVIRONMENT_FOREST
|
||||
|
||||
/area/holodeck/source_desert
|
||||
name = "\improper Holodeck - Desert"
|
||||
sound_env = PLAIN
|
||||
sound_env = SOUND_ENVIRONMENT_PLAIN
|
||||
|
||||
/area/holodeck/source_space
|
||||
name = "\improper Holodeck - Space"
|
||||
|
||||
@@ -669,3 +669,6 @@
|
||||
|
||||
/atom/proc/get_visible_gender()
|
||||
return gender
|
||||
|
||||
/atom/proc/interact(mob/user)
|
||||
return
|
||||
|
||||
@@ -16,9 +16,6 @@
|
||||
|
||||
clicksound = "keyboard"
|
||||
|
||||
/obj/machinery/computer/New()
|
||||
..()
|
||||
|
||||
/obj/machinery/computer/Initialize()
|
||||
. = ..()
|
||||
power_change()
|
||||
|
||||
@@ -1054,7 +1054,9 @@
|
||||
/obj/item/device/radio/headset = 10,
|
||||
/obj/item/device/flashlight = 5,
|
||||
/obj/item/device/laser_pointer = 3,
|
||||
/obj/item/clothing/glasses/omnihud = 10)
|
||||
/obj/item/clothing/glasses/omnihud = 10,
|
||||
/obj/item/instrument/piano_synth/headphones = 2, // You're making a subsystem do work, I don't want it TOO busy
|
||||
/obj/item/instrument/piano_synth/headphones/spacepods = 2)
|
||||
prices = list(/obj/item/clothing/suit/circuitry = 100,
|
||||
/obj/item/clothing/head/circuitry = 100,
|
||||
/obj/item/clothing/shoes/circuitry = 100,
|
||||
@@ -1074,7 +1076,9 @@
|
||||
/obj/item/device/radio/headset = 50,
|
||||
/obj/item/device/flashlight = 100,
|
||||
/obj/item/device/laser_pointer = 200,
|
||||
/obj/item/clothing/glasses/omnihud = 100)
|
||||
/obj/item/clothing/glasses/omnihud = 100,
|
||||
/obj/item/instrument/piano_synth/headphones = 200,
|
||||
/obj/item/instrument/piano_synth/headphones/spacepods = 600)
|
||||
premium = list(/obj/item/device/perfect_tele/one_beacon = 1)
|
||||
contraband = list(/obj/item/weapon/disk/nifsoft/compliance = 1)
|
||||
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
icon = 'icons/effects/effects_vr.dmi'
|
||||
icon_state = "confetti"
|
||||
|
||||
/obj/effect/effect/sparks/New()
|
||||
..()
|
||||
/obj/effect/effect/sparks/Initialize()
|
||||
. = ..()
|
||||
playsound(src, "sounds/items/confetti.ogg ", 100, 1)
|
||||
|
||||
/datum/effect/effect/system/confetti_spread
|
||||
|
||||
@@ -99,15 +99,12 @@ steam.start() -- spawns the effect
|
||||
anchored = 1.0
|
||||
mouse_opacity = 0
|
||||
|
||||
/obj/effect/effect/sparks/New()
|
||||
..()
|
||||
/obj/effect/effect/sparks/Initialize()
|
||||
. = ..()
|
||||
playsound(src, "sparks", 100, 1)
|
||||
var/turf/T = src.loc
|
||||
if (istype(T, /turf))
|
||||
T.hotspot_expose(1000,100)
|
||||
|
||||
/obj/effect/effect/sparks/Initialize()
|
||||
. = ..()
|
||||
QDEL_IN(src, 5 SECONDS)
|
||||
|
||||
/obj/effect/effect/sparks/Destroy()
|
||||
|
||||
@@ -1,105 +0,0 @@
|
||||
//copy pasta of the space violin, don't hurt me -RF
|
||||
|
||||
/obj/item/device/instrument/guitar
|
||||
name = "guitar"
|
||||
desc = "It's made of wood and has bronze strings."
|
||||
icon = 'icons/obj/musician_yw.dmi'
|
||||
icon_state = "guitar"
|
||||
item_state = "guitar"
|
||||
attack_verb = list("smashed")
|
||||
instrumentId = "guitar"
|
||||
instrumentExt = "ogg"
|
||||
|
||||
/obj/item/device/instrument/keytar
|
||||
name = "portable keyboard"
|
||||
desc = "A keyboard, for those interested in the piano on the go! "
|
||||
icon = 'icons/obj/musician_yw.dmi'
|
||||
icon_state = "keyboard"
|
||||
item_state = "keyboard"
|
||||
attack_verb = list("smashed")
|
||||
instrumentId = "piano"
|
||||
instrumentExt = "ogg"
|
||||
|
||||
/obj/item/device/instrument/eguitar
|
||||
name = "electrica guitar"
|
||||
desc = "A metallic musical instrument with strings, made for all your shredding needs."
|
||||
icon = 'icons/obj/musician_yw.dmi'
|
||||
icon_state = "eguitar"
|
||||
item_state = "eguitar"
|
||||
attack_verb = list("smashed")
|
||||
instrumentId = "eguitar"
|
||||
instrumentExt = "ogg"
|
||||
|
||||
/obj/item/device/instrument/xylophone
|
||||
name = "xylophone"
|
||||
desc = "A percussion instrument consisting of a series of wooden bars graduated in length."
|
||||
icon = 'icons/obj/musician_yw.dmi'
|
||||
icon_state = "xylophone"
|
||||
attack_verb = list("smashed")
|
||||
instrumentId = "xylophone"
|
||||
instrumentExt = "mid"
|
||||
|
||||
/obj/item/device/instrument/accordion
|
||||
name = "accordion"
|
||||
desc = "A musical instrument played by blowing and pressing keys. Someone detached Pun-Pun's hands!"
|
||||
icon = 'icons/obj/musician_yw.dmi'
|
||||
icon_state = "accordion"
|
||||
attack_verb = list("smashed")
|
||||
instrumentId = "accordion"
|
||||
instrumentExt = "mid"
|
||||
|
||||
/obj/item/device/instrument/saxophone
|
||||
name = "saxophone"
|
||||
desc = "A metal wind instruments with a single-reed mouthpiece known for it's soothing tones."
|
||||
icon = 'icons/obj/musician_yw.dmi'
|
||||
icon_state = "saxophone"
|
||||
attack_verb = list("smashed")
|
||||
instrumentId = "saxophone"
|
||||
instrumentExt = "mid"
|
||||
|
||||
/obj/item/device/instrument/glockenspiel
|
||||
name = "glockenspiel"
|
||||
desc = "A percussion instrument composed of a set of tuned metal bars perfect for any marching band."
|
||||
icon = 'icons/obj/musician_yw.dmi'
|
||||
icon_state = "glockenspiel"
|
||||
attack_verb = list("smashed")
|
||||
instrumentId = "glockenspiel"
|
||||
instrumentExt = "mid"
|
||||
|
||||
/obj/item/device/instrument/recorder
|
||||
name = "recorder"
|
||||
desc = "A depressing version of a flute. Does anyone even play this?"
|
||||
icon = 'icons/obj/musician_yw.dmi'
|
||||
icon_state = "recorder"
|
||||
attack_verb = list("smashed")
|
||||
instrumentId = "recorder"
|
||||
instrumentExt = "mid"
|
||||
|
||||
/obj/item/device/instrument/trombone
|
||||
name = "trombone"
|
||||
desc = "A large brass wind instrument. Seems to call for incidental music."
|
||||
icon = 'icons/obj/musician_yw.dmi'
|
||||
icon_state = "trombone"
|
||||
attack_verb = list("smashed")
|
||||
instrumentId = "trombone"
|
||||
instrumentExt = "mid"
|
||||
|
||||
/obj/item/device/instrument/harmonica
|
||||
name = "harmonica"
|
||||
desc = "A literal mouth organ, it just smells of the frontier!"
|
||||
icon = 'icons/obj/musician_yw.dmi'
|
||||
icon_state = "harmonica"
|
||||
attack_verb = list("smashed")
|
||||
instrumentId = "harmonica"
|
||||
instrumentExt = "mid"
|
||||
w_class = ITEMSIZE_SMALL
|
||||
|
||||
/obj/item/device/instrument/bikehorn
|
||||
name = "bike horn"
|
||||
desc = "Why... why would you even play this?!"
|
||||
icon = 'icons/obj/musician_yw.dmi'
|
||||
icon_state = "bike_horn"
|
||||
attack_verb = list("smashed")
|
||||
instrumentId = "bikehorn"
|
||||
instrumentExt = "ogg"
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
//copy pasta of the space piano, don't hurt me -Pete
|
||||
/obj/item/device/instrument
|
||||
name = "generic instrument"
|
||||
var/datum/song/handheld/song
|
||||
var/instrumentId = "generic"
|
||||
var/instrumentExt = "mid"
|
||||
icon = 'icons/obj/musician.dmi'
|
||||
force = 10
|
||||
item_icons = list(
|
||||
slot_l_hand_str = 'icons/mob/items/lefthand_instruments.dmi',
|
||||
slot_r_hand_str = 'icons/mob/items/righthand_instruments.dmi',
|
||||
)
|
||||
|
||||
/obj/item/device/instrument/New()
|
||||
..()
|
||||
song = new(instrumentId, src)
|
||||
song.instrumentExt = instrumentExt
|
||||
|
||||
/obj/item/device/instrument/Destroy()
|
||||
qdel(song)
|
||||
song = null
|
||||
..()
|
||||
|
||||
/obj/item/device/instrument/attack_self(mob/user as mob)
|
||||
if(!user.IsAdvancedToolUser())
|
||||
to_chat(user, "<span class='warning'>You don't have the dexterity to do this!</span>")
|
||||
return 1
|
||||
interact(user)
|
||||
|
||||
/obj/item/device/instrument/interact(mob/user as mob)
|
||||
if(!user)
|
||||
return
|
||||
|
||||
if(user.incapacitated() || user.lying)
|
||||
return
|
||||
|
||||
user.set_machine(src)
|
||||
song.interact(user)
|
||||
|
||||
/obj/item/device/instrument/violin
|
||||
name = "violin"
|
||||
desc = "A wooden musical instrument with four strings and a bow. A true classic."
|
||||
icon_state = "violin"
|
||||
attack_verb = list("smashed")
|
||||
instrumentId = "violin"
|
||||
item_state = "violin"
|
||||
|
||||
/obj/item/device/instrument/violin/gold
|
||||
name = "golden violin"
|
||||
desc = "A wooden musical instrument with four strings and a bow. \"The devil went down to space, he was looking for an assistant to grief.\""
|
||||
icon = 'icons/obj/musician_yw.dmi'
|
||||
icon_state = "golden_violin"
|
||||
attack_verb = list("smashed")
|
||||
@@ -104,7 +104,7 @@
|
||||
/obj/item/weapon/reagent_containers/food/snacks/grown/ambrosiadeus,
|
||||
/obj/item/weapon/reagent_containers/food/snacks/grown/ambrosiavulgaris,
|
||||
/obj/item/device/paicard,
|
||||
/obj/item/device/instrument/violin,
|
||||
/obj/item/instrument/violin,
|
||||
/obj/item/weapon/storage/belt/utility/full,
|
||||
/obj/item/clothing/accessory/tie/horrible)
|
||||
|
||||
|
||||
@@ -120,9 +120,6 @@
|
||||
tgui_interact(user)
|
||||
..()
|
||||
|
||||
/obj/proc/interact(mob/user)
|
||||
return
|
||||
|
||||
/mob/proc/unset_machine()
|
||||
machine?.remove_visual(src)
|
||||
src.machine = null
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
/obj/structure/largecrate/piano
|
||||
name = "piano crate"
|
||||
starts_with = list(/obj/structure/device/piano)
|
||||
starts_with = list(/obj/structure/musician/piano)
|
||||
desc = "*Grand piano may end up being a minimoog."
|
||||
|
||||
/obj/structure/largecrate/piano/Initialize() //This is nessesary to get a random one each time.
|
||||
starts_with = list(pick(/obj/structure/device/piano/minimoog,
|
||||
/obj/structure/device/piano))
|
||||
starts_with = list(/obj/structure/musician/piano)
|
||||
return ..()
|
||||
@@ -1,377 +0,0 @@
|
||||
//This file was auto-corrected by findeclaration.exe on 25.5.2012 20:42:32
|
||||
|
||||
#define MUSICIAN_HEARCHECK_MINDELAY 4
|
||||
#define INSTRUMENT_MAX_LINE_LENGTH 300
|
||||
#define INSTRUMENT_MAX_LINE_NUMBER 400
|
||||
|
||||
/datum/song
|
||||
var/name = "Untitled"
|
||||
var/list/lines = new()
|
||||
var/tempo = 5 // delay between notes
|
||||
|
||||
var/playing = 0 // if we're playing
|
||||
var/help = 0 // if help is open
|
||||
var/edit = 1 // if we're in editing mode
|
||||
var/repeat = 0 // number of times remaining to repeat
|
||||
var/max_repeats = 10 // maximum times we can repeat
|
||||
|
||||
var/instrumentDir = "piano" // the folder with the sounds
|
||||
var/instrumentExt = "ogg" // the file extension
|
||||
var/obj/instrumentObj = null // the associated obj playing the sound
|
||||
var/last_hearcheck = 0
|
||||
var/list/hearing_mobs
|
||||
|
||||
/datum/song/New(dir, obj, ext = "ogg")
|
||||
instrumentDir = dir
|
||||
instrumentObj = obj
|
||||
instrumentExt = ext
|
||||
|
||||
/datum/song/Destroy()
|
||||
instrumentObj = null
|
||||
return ..()
|
||||
|
||||
/obj/structure/device/piano
|
||||
name = "space minimoog"
|
||||
icon = 'icons/obj/musician.dmi'
|
||||
icon_state = "minimoog"
|
||||
anchored = 1
|
||||
density = 1
|
||||
var/datum/song/song
|
||||
var/playing = 0
|
||||
var/help = 0
|
||||
var/edit = 1
|
||||
var/repeat = 0
|
||||
var/linelimit = INSTRUMENT_MAX_LINE_NUMBER
|
||||
|
||||
// note is a number from 1-7 for A-G
|
||||
// acc is either "b", "n", or "#"
|
||||
// oct is 1-8 (or 9 for C)
|
||||
/datum/song/proc/playnote(note, acc as text, oct)
|
||||
// handle accidental -> B<>C of E<>F
|
||||
if(acc == "b" && (note == 3 || note == 6)) // C or F
|
||||
if(note == 3)
|
||||
oct--
|
||||
note--
|
||||
acc = "n"
|
||||
else if(acc == "#" && (note == 2 || note == 5)) // B or E
|
||||
if(note == 2)
|
||||
oct++
|
||||
note++
|
||||
acc = "n"
|
||||
else if(acc == "#" && (note == 7)) //G#
|
||||
note = 1
|
||||
acc = "b"
|
||||
else if(acc == "#") // mass convert all sharps to flats, octave jump already handled
|
||||
acc = "b"
|
||||
note++
|
||||
|
||||
// check octave, C is allowed to go to 9
|
||||
if(oct < 1 || (note == 3 ? oct > 9 : oct > 8))
|
||||
return
|
||||
|
||||
// now generate name
|
||||
var/soundfile = "sound/instruments/[instrumentDir]/[ascii2text(note+64)][acc][oct].[instrumentExt]"
|
||||
soundfile = file(soundfile)
|
||||
// make sure the note exists
|
||||
if(!fexists(soundfile))
|
||||
return
|
||||
// and play
|
||||
var/turf/source = get_turf(instrumentObj)
|
||||
if((world.time - MUSICIAN_HEARCHECK_MINDELAY) > last_hearcheck)
|
||||
LAZYCLEARLIST(hearing_mobs)
|
||||
for(var/mob/M in hearers(15, source))
|
||||
if(!M.client || !(M.is_preference_enabled(/datum/client_preference/instrument_toggle)))
|
||||
continue
|
||||
LAZYSET(hearing_mobs, M, TRUE)
|
||||
last_hearcheck = world.time
|
||||
var/sound/music_played = sound(soundfile)
|
||||
for(var/i in hearing_mobs)
|
||||
var/mob/M = i
|
||||
M.playsound_local(source, null, 100, falloff = 0.5, S = music_played)
|
||||
|
||||
/datum/song/proc/updateDialog(mob/user)
|
||||
instrumentObj.updateDialog() // assumes it's an object in world, override if otherwise
|
||||
|
||||
/datum/song/proc/shouldStopPlaying(mob/user)
|
||||
if(instrumentObj)
|
||||
if(!instrumentObj.Adjacent(user) || user.stat)
|
||||
return 1
|
||||
return !instrumentObj.anchored // add special cases to stop in subclasses
|
||||
else
|
||||
return 1
|
||||
|
||||
/datum/song/proc/playsong(mob/user)
|
||||
while(repeat >= 0)
|
||||
var/cur_oct[7]
|
||||
var/cur_acc[7]
|
||||
for(var/i = 1 to 7)
|
||||
cur_oct[i] = 3
|
||||
cur_acc[i] = "n"
|
||||
|
||||
for(var/line in lines)
|
||||
for(var/beat in splittext(lowertext(line), ","))
|
||||
var/list/notes = splittext(beat, "/")
|
||||
for(var/note in splittext(notes[1], "-"))
|
||||
if(!playing || shouldStopPlaying(user))//If the instrument is playing, or special case
|
||||
playing = 0
|
||||
return
|
||||
if(length(note) == 0)
|
||||
continue
|
||||
var/cur_note = text2ascii(note) - 96
|
||||
if(cur_note < 1 || cur_note > 7)
|
||||
continue
|
||||
for(var/i=2 to length(note))
|
||||
var/ni = copytext(note,i,i+1)
|
||||
if(!text2num(ni))
|
||||
if(ni == "#" || ni == "b" || ni == "n")
|
||||
cur_acc[cur_note] = ni
|
||||
else if(ni == "s")
|
||||
cur_acc[cur_note] = "#" // so shift is never required
|
||||
else
|
||||
cur_oct[cur_note] = text2num(ni)
|
||||
playnote(cur_note, cur_acc[cur_note], cur_oct[cur_note])
|
||||
if(notes.len >= 2 && text2num(notes[2]))
|
||||
sleep(sanitize_tempo(tempo / text2num(notes[2])))
|
||||
else
|
||||
sleep(tempo)
|
||||
repeat--
|
||||
playing = 0
|
||||
repeat = 0
|
||||
updateDialog(user)
|
||||
|
||||
/datum/song/proc/interact(mob/user)
|
||||
var/dat = ""
|
||||
if(lines.len > 0)
|
||||
dat += "<H3>Playback</H3>"
|
||||
if(!playing)
|
||||
dat += {"<A href='?src=\ref[src];play=1'>Play</A> <SPAN CLASS='linkOn'>Stop</SPAN><BR><BR>
|
||||
Repeat Song:
|
||||
[repeat > 0 ? "<A href='?src=\ref[src];repeat=-10'>-</A><A href='?src=\ref[src];repeat=-1'>-</A>" : "<SPAN CLASS='linkOff'>-</SPAN><SPAN CLASS='linkOff'>-</SPAN>"]
|
||||
[repeat] times
|
||||
[repeat < max_repeats ? "<A href='?src=\ref[src];repeat=1'>+</A><A href='?src=\ref[src];repeat=10'>+</A>" : "<SPAN CLASS='linkOff'>+</SPAN><SPAN CLASS='linkOff'>+</SPAN>"]
|
||||
<BR>"}
|
||||
else
|
||||
dat += {"<SPAN CLASS='linkOn'>Play</SPAN> <A href='?src=\ref[src];stop=1'>Stop</A><BR>
|
||||
Repeats left: <B>[repeat]</B><BR>"}
|
||||
if(!edit)
|
||||
dat += "<BR><B><A href='?src=\ref[src];edit=2'>Show Editor</A></B><BR>"
|
||||
else
|
||||
var/bpm = round(600 / tempo)
|
||||
dat += {"<H3>Editing</H3>
|
||||
<B><A href='?src=\ref[src];edit=1'>Hide Editor</A></B>
|
||||
<A href='?src=\ref[src];newsong=1'>Start a New Song</A>
|
||||
<A href='?src=\ref[src];import=1'>Import a Song</A><BR><BR>
|
||||
Tempo: <A href='?src=\ref[src];tempo=[world.tick_lag]'>-</A> [bpm] BPM <A href='?src=\ref[src];tempo=-[world.tick_lag]'>+</A><BR><BR>"}
|
||||
var/linecount = 0
|
||||
for(var/line in lines)
|
||||
linecount += 1
|
||||
dat += "Line [linecount]: <A href='?src=\ref[src];modifyline=[linecount]'>Edit</A> <A href='?src=\ref[src];deleteline=[linecount]'>X</A> [line]<BR>"
|
||||
dat += "<A href='?src=\ref[src];newline=1'>Add Line</A><BR><BR>"
|
||||
if(help)
|
||||
dat += {"<B><A href='?src=\ref[src];help=1'>Hide Help</A></B><BR>
|
||||
Lines are a series of chords, separated by commas (,), each with notes seperated by hyphens (-).<br>
|
||||
Every note in a chord will play together, with chord timed by the tempo.<br>
|
||||
<br>
|
||||
Notes are played by the names of the note, and optionally, the accidental, and/or the octave number.<br>
|
||||
By default, every note is natural and in octave 3. Defining otherwise is remembered for each note.<br>
|
||||
Example: <i>C,D,E,F,G,A,B</i> will play a C major scale.<br>
|
||||
After a note has an accidental placed, it will be remembered: <i>C,C4,C,C3</i> is <i>C3,C4,C4,C3</i><br>
|
||||
Chords can be played simply by seperating each note with a hyphon: <i>A-C#,Cn-E,E-G#,Gn-B</i><br>
|
||||
A pause may be denoted by an empty chord: <i>C,E,,C,G</i><br>
|
||||
To make a chord be a different time, end it with /x, where the chord length will be length<br>
|
||||
defined by tempo / x: <i>C,G/2,E/4</i><br>
|
||||
Combined, an example is: <i>E-E4/4,F#/2,G#/8,B/8,E3-E4/4</i>
|
||||
<br>
|
||||
Lines may be up to 50 characters.<br>
|
||||
A song may only contain up to 50 lines.<br>
|
||||
"}
|
||||
else
|
||||
dat += "<B><A href='?src=\ref[src];help=2'>Show Help</A></B><BR>"
|
||||
var/datum/browser/popup = new(user, "instrument", instrumentObj.name, 700, 500)
|
||||
popup.set_content(dat)
|
||||
popup.set_title_image(user.browse_rsc_icon(instrumentObj.icon, instrumentObj.icon_state))
|
||||
popup.open()
|
||||
|
||||
/datum/song/Topic(href, href_list)
|
||||
if(!instrumentObj.Adjacent(usr) || usr.stat)
|
||||
usr << browse(null, "window=instrument")
|
||||
usr.unset_machine()
|
||||
return
|
||||
instrumentObj.add_fingerprint(usr)
|
||||
if(href_list["newsong"])
|
||||
lines = new()
|
||||
tempo = sanitize_tempo(5) // default 120 BPM
|
||||
name = ""
|
||||
else if(href_list["import"])
|
||||
var/t = ""
|
||||
do
|
||||
t = html_encode(input(usr, "Please paste the entire song, formatted:", text("[]", name), t) as message)
|
||||
if(!in_range(instrumentObj, usr))
|
||||
return
|
||||
if(length(t) >= INSTRUMENT_MAX_LINE_LENGTH*INSTRUMENT_MAX_LINE_NUMBER)
|
||||
var/cont = input(usr, "Your message is too long! Would you like to continue editing it?", "", "yes") in list("yes", "no")
|
||||
if(cont == "no")
|
||||
break
|
||||
while(length(t) > INSTRUMENT_MAX_LINE_LENGTH*INSTRUMENT_MAX_LINE_NUMBER)
|
||||
//split into lines
|
||||
spawn()
|
||||
lines = splittext(t, "\n")
|
||||
if(copytext(lines[1],1,6) == "BPM: ")
|
||||
tempo = sanitize_tempo(600 / text2num(copytext(lines[1],6)))
|
||||
lines.Cut(1,2)
|
||||
else
|
||||
tempo = sanitize_tempo(5) // default 120 BPM
|
||||
if(lines.len > INSTRUMENT_MAX_LINE_NUMBER)
|
||||
to_chat(usr, "Too many lines!")
|
||||
lines.Cut(INSTRUMENT_MAX_LINE_NUMBER+1)
|
||||
var/linenum = 1
|
||||
for(var/l in lines)
|
||||
if(length(l) > INSTRUMENT_MAX_LINE_LENGTH)
|
||||
to_chat(usr, "Line [linenum] too long!")
|
||||
lines.Remove(l)
|
||||
else
|
||||
linenum++
|
||||
updateDialog(usr) // make sure updates when complete
|
||||
else if(href_list["help"])
|
||||
help = text2num(href_list["help"]) - 1
|
||||
else if(href_list["edit"])
|
||||
edit = text2num(href_list["edit"]) - 1
|
||||
if(href_list["repeat"]) //Changing this from a toggle to a number of repeats to avoid infinite loops.
|
||||
if(playing)
|
||||
return //So that people cant keep adding to repeat. If the do it intentionally, it could result in the server crashing.
|
||||
repeat += round(text2num(href_list["repeat"]))
|
||||
if(repeat < 0)
|
||||
repeat = 0
|
||||
if(repeat > max_repeats)
|
||||
repeat = max_repeats
|
||||
else if(href_list["tempo"])
|
||||
tempo = sanitize_tempo(tempo + text2num(href_list["tempo"]))
|
||||
else if(href_list["play"])
|
||||
playing = 1
|
||||
spawn()
|
||||
playsong(usr)
|
||||
else if(href_list["newline"])
|
||||
var/newline = html_encode(input("Enter your line: ", instrumentObj.name) as text|null)
|
||||
if(!newline || !in_range(instrumentObj, usr))
|
||||
return
|
||||
if(lines.len > INSTRUMENT_MAX_LINE_NUMBER)
|
||||
return
|
||||
if(length(newline) > INSTRUMENT_MAX_LINE_LENGTH)
|
||||
newline = copytext(newline, 1, INSTRUMENT_MAX_LINE_LENGTH)
|
||||
lines.Add(newline)
|
||||
else if(href_list["deleteline"])
|
||||
var/num = round(text2num(href_list["deleteline"]))
|
||||
if(num > lines.len || num < 1)
|
||||
return
|
||||
lines.Cut(num, num+1)
|
||||
else if(href_list["modifyline"])
|
||||
var/num = round(text2num(href_list["modifyline"]),1)
|
||||
var/content = html_encode(input("Enter your line: ", instrumentObj.name, lines[num]) as text|null)
|
||||
if(!content || !in_range(instrumentObj, usr))
|
||||
return
|
||||
if(length(content) > INSTRUMENT_MAX_LINE_LENGTH)
|
||||
content = copytext(content, 1, INSTRUMENT_MAX_LINE_LENGTH)
|
||||
if(num > lines.len || num < 1)
|
||||
return
|
||||
lines[num] = content
|
||||
else if(href_list["stop"])
|
||||
playing = 0
|
||||
updateDialog(usr)
|
||||
return
|
||||
|
||||
/datum/song/proc/sanitize_tempo(new_tempo)
|
||||
new_tempo = abs(new_tempo)
|
||||
return max(round(new_tempo, world.tick_lag), world.tick_lag)
|
||||
|
||||
// subclass for handheld instruments, like violin
|
||||
/datum/song/handheld
|
||||
|
||||
/datum/song/handheld/updateDialog(mob/user)
|
||||
instrumentObj.interact(user)
|
||||
|
||||
/datum/song/handheld/shouldStopPlaying()
|
||||
if(instrumentObj)
|
||||
return !isliving(instrumentObj.loc)
|
||||
else
|
||||
return 1
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
/obj/structure/device/piano
|
||||
name = "space piano"
|
||||
desc = "This is a space piano; just like a regular piano, but always in tune! Even if the musician isn't."
|
||||
icon = 'icons/obj/musician.dmi'
|
||||
icon_state = "piano"
|
||||
anchored = 1
|
||||
density = 1
|
||||
|
||||
/obj/structure/device/piano/minimoog
|
||||
name = "space minimoog"
|
||||
icon_state = "minimoog"
|
||||
desc = "This is a minimoog; just like a space piano, but more spacey!"
|
||||
|
||||
/obj/structure/device/piano/New()
|
||||
..()
|
||||
song = new("piano", src)
|
||||
|
||||
if(prob(50))
|
||||
name = "space minimoog"
|
||||
desc = "This is a minimoog, like a space piano, but more spacey!"
|
||||
icon_state = "minimoog"
|
||||
else
|
||||
name = "space piano"
|
||||
desc = "This is a space piano, like a regular piano, but always in tune! Even if the musician isn't."
|
||||
icon_state = "piano"
|
||||
|
||||
/obj/structure/device/piano/Destroy()
|
||||
qdel(song)
|
||||
song = null
|
||||
..()
|
||||
|
||||
/obj/structure/device/piano/verb/rotate_clockwise()
|
||||
set name = "Rotate Piano Clockwise"
|
||||
set category = "Object"
|
||||
set src in oview(1)
|
||||
|
||||
if(ismouse(usr))
|
||||
return
|
||||
if(!usr || !isturf(usr.loc) || usr.stat || usr.restrained())
|
||||
return
|
||||
if (isobserver(usr) && !config.ghost_interaction)
|
||||
return
|
||||
src.set_dir(turn(src.dir, 270))
|
||||
|
||||
/obj/structure/device/piano/attack_hand(mob/user)
|
||||
if(!user.IsAdvancedToolUser())
|
||||
to_chat(user, "<span class='warning'>You don't have the dexterity to do this!</span>")
|
||||
return 1
|
||||
interact(user)
|
||||
|
||||
/obj/structure/device/piano/interact(mob/user)
|
||||
if(!user || !anchored)
|
||||
return
|
||||
|
||||
user.set_machine(src)
|
||||
song.interact(user)
|
||||
|
||||
/obj/structure/device/piano/attackby(obj/item/O as obj, mob/user as mob)
|
||||
if(O.is_wrench())
|
||||
if(anchored)
|
||||
playsound(src, O.usesound, 50, 1)
|
||||
to_chat(user, "<span class='notice'>You begin to loosen \the [src]'s casters...</span>")
|
||||
if (do_after(user, 40 * O.toolspeed))
|
||||
user.visible_message( \
|
||||
"[user] loosens \the [src]'s casters.", \
|
||||
"<span class='notice'>You have loosened \the [src]. Now it can be pulled somewhere else.</span>", \
|
||||
"You hear ratchet.")
|
||||
src.anchored = 0
|
||||
else
|
||||
playsound(src, O.usesound, 50, 1)
|
||||
to_chat(user, "<span class='notice'>You begin to tighten \the [src] to the floor...</span>")
|
||||
if (do_after(user, 20 * O.toolspeed))
|
||||
user.visible_message( \
|
||||
"[user] tightens \the [src]'s casters.", \
|
||||
"<span class='notice'>You have tightened \the [src]'s casters. Now it can be played again</span>.", \
|
||||
"You hear ratchet.")
|
||||
src.anchored = 1
|
||||
else
|
||||
..()
|
||||
@@ -9,7 +9,7 @@
|
||||
var/area/area_source = turf_source.loc
|
||||
|
||||
//allocate a channel if necessary now so its the same for everyone
|
||||
channel = channel || open_sound_channel()
|
||||
channel = channel || SSsounds.random_available_channel()
|
||||
|
||||
// Looping through the player list has the added bonus of working for mobs inside containers
|
||||
var/sound/S = sound(get_sfx(soundin))
|
||||
@@ -43,7 +43,7 @@
|
||||
S = sound(get_sfx(soundin))
|
||||
|
||||
S.wait = 0 //No queue
|
||||
S.channel = channel || open_sound_channel()
|
||||
S.channel = channel || SSsounds.random_available_channel()
|
||||
|
||||
// I'm not sure if you can modify S.volume, but I'd rather not try to find out what
|
||||
// horrible things lurk in BYOND's internals, so we're just gonna do vol *=
|
||||
@@ -109,15 +109,14 @@
|
||||
var/mob/MO = M
|
||||
MO.playsound_local(get_turf(MO), sound, volume, vary, pressure_affected = FALSE)
|
||||
|
||||
/proc/open_sound_channel()
|
||||
var/static/next_channel = 1 //loop through the available 1024 - (the ones we reserve) channels and pray that its not still being used
|
||||
. = ++next_channel
|
||||
if(next_channel > CHANNEL_HIGHEST_AVAILABLE)
|
||||
next_channel = 1
|
||||
|
||||
/mob/proc/stop_sound_channel(chan)
|
||||
src << sound(null, repeat = 0, wait = 0, channel = chan)
|
||||
|
||||
/mob/proc/set_sound_channel_volume(channel, volume)
|
||||
var/sound/S = sound(null, FALSE, FALSE, channel, volume)
|
||||
S.status = SOUND_UPDATE
|
||||
src << S
|
||||
|
||||
/proc/get_rand_frequency()
|
||||
return rand(32000, 55000) //Frequency stuff only works with 45kbps oggs.
|
||||
|
||||
|
||||
113
code/modules/instruments/instrument_data/_instrument_data.dm
Normal file
@@ -0,0 +1,113 @@
|
||||
/**
|
||||
* Get all non admin_only instruments as a list of text ids.
|
||||
*/
|
||||
/proc/get_allowed_instrument_ids()
|
||||
. = list()
|
||||
for(var/id in SSinstruments.instrument_data)
|
||||
var/datum/instrument/I = SSinstruments.instrument_data[id]
|
||||
if(!I.admin_only)
|
||||
. += I.id
|
||||
|
||||
/**
|
||||
* # Instrument Datums
|
||||
*
|
||||
* Instrument datums hold the data for any given instrument, as well as data on how to play it and what bounds there are to playing it.
|
||||
*
|
||||
* The datums themselves are kept in SSinstruments in a list by their unique ID. The reason it uses ID instead of typepath is to support the runtime creation of instruments.
|
||||
* Since songs cache them while playing, there isn't realistic issues regarding performance from accessing.
|
||||
*/
|
||||
/datum/instrument
|
||||
/// Name of the instrument
|
||||
var/name = "Generic instrument"
|
||||
/// Uniquely identifies this instrument so runtime changes are possible as opposed to paths. If this is unset, things will use path instead.
|
||||
var/id
|
||||
/// Category
|
||||
var/category = "Unsorted"
|
||||
/// Used for categorization subtypes
|
||||
var/abstract_type = /datum/instrument
|
||||
/// Write here however many samples, follow this syntax: "%note num%"='%sample file%' eg. "27"='synthesizer/e2.ogg'. Key must never be lower than 0 and higher than 127
|
||||
var/list/real_samples
|
||||
/// assoc list key = /datum/instrument_key. do not fill this yourself!
|
||||
var/list/samples
|
||||
/// See __DEFINES/flags/instruments.dm
|
||||
var/instrument_flags = NONE
|
||||
/// For legacy instruments, the path to our notes
|
||||
var/legacy_instrument_path
|
||||
/// For legacy instruments, our file extension
|
||||
var/legacy_instrument_ext
|
||||
/// What songs are using us
|
||||
var/list/datum/song/songs_using = list()
|
||||
/// Don't touch this
|
||||
var/static/HIGHEST_KEY = 127
|
||||
/// Don't touch this x2
|
||||
var/static/LOWEST_KEY = 0
|
||||
/// Oh no - For truly troll instruments.
|
||||
var/admin_only = FALSE
|
||||
/// Volume multiplier. Synthesized instruments are quite loud and I don't like to cut off potential detail via editing. (someone correct me if this isn't a thing)
|
||||
var/volume_multiplier = 0.33
|
||||
|
||||
/datum/instrument/New()
|
||||
if(isnull(id))
|
||||
id = "[type]"
|
||||
|
||||
/**
|
||||
* Initializes the instrument, calculating its samples if necessary.
|
||||
*/
|
||||
/datum/instrument/proc/Initialize()
|
||||
if(instrument_flags & (INSTRUMENT_LEGACY | INSTRUMENT_DO_NOT_AUTOSAMPLE))
|
||||
return
|
||||
calculate_samples()
|
||||
|
||||
/**
|
||||
* Checks if this instrument is ready to play.
|
||||
*/
|
||||
/datum/instrument/proc/ready()
|
||||
if(instrument_flags & INSTRUMENT_LEGACY)
|
||||
return legacy_instrument_path && legacy_instrument_ext
|
||||
else if(instrument_flags & INSTRUMENT_DO_NOT_AUTOSAMPLE)
|
||||
return length(samples)
|
||||
return (length(samples) >= 128)
|
||||
|
||||
/datum/instrument/Destroy()
|
||||
SSinstruments.instrument_data -= id
|
||||
for(var/i in songs_using)
|
||||
var/datum/song/S = i
|
||||
S.set_instrument(null)
|
||||
real_samples = null
|
||||
samples = null
|
||||
songs_using = null
|
||||
return ..()
|
||||
|
||||
/**
|
||||
* For synthesized instruments, this is how the instrument generates the "keys" that a [/datum/song] uses to play notes.
|
||||
* Calculating them on the fly would be unperformant, so we do it during init and keep it all cached in a list.
|
||||
*/
|
||||
/datum/instrument/proc/calculate_samples()
|
||||
if(!length(real_samples))
|
||||
CRASH("No real samples defined for [id] [type] on calculate_samples() call.")
|
||||
var/list/real_keys = list()
|
||||
samples = list()
|
||||
for(var/key in real_samples)
|
||||
real_keys += text2num(key)
|
||||
sortTim(real_keys, /proc/cmp_numeric_asc, associative = FALSE)
|
||||
|
||||
for(var/i in 1 to (length(real_keys) - 1))
|
||||
var/from_key = real_keys[i]
|
||||
var/to_key = real_keys[i+1]
|
||||
var/sample1 = real_samples[num2text(from_key)]
|
||||
var/sample2 = real_samples[num2text(to_key)]
|
||||
var/pivot = FLOOR((from_key + to_key) / 2, 1) //original code was a round but I replaced it because that's effectively a floor, thanks Baystation! who knows what was intended.
|
||||
for(var/key in from_key to pivot)
|
||||
samples[num2text(key)] = new /datum/instrument_key(sample1, key, key - from_key)
|
||||
for(var/key in (pivot + 1) to to_key)
|
||||
samples[num2text(key)] = new /datum/instrument_key(sample2, key, key - to_key)
|
||||
|
||||
// Fill in 0 to first key and last key to 127
|
||||
var/first_key = real_keys[1]
|
||||
var/last_key = real_keys[length(real_keys)]
|
||||
var/first_sample = real_samples[num2text(first_key)]
|
||||
var/last_sample = real_samples[num2text(last_key)]
|
||||
for(var/key in LOWEST_KEY to (first_key - 1))
|
||||
samples[num2text(key)] = new /datum/instrument_key(first_sample, key, key - first_key)
|
||||
for(var/key in last_key to HIGHEST_KEY)
|
||||
samples[num2text(key)] = new /datum/instrument_key(last_sample, key, key - last_key)
|
||||
31
code/modules/instruments/instrument_data/_instrument_key.dm
Normal file
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* Instrument key datums contain everything needed to know how to play a specific
|
||||
* note of an instrument.*
|
||||
*/
|
||||
/datum/instrument_key
|
||||
/// The numerical key of what this is, from 1 to 127 on a standard piano keyboard.
|
||||
var/key
|
||||
/// The actual sample file that will be loaded when playing.
|
||||
var/sample
|
||||
/// The frequency to play the sample to get our desired note.
|
||||
var/frequency
|
||||
/// Deviation up/down from the pivot point that uses its sample. Used to calculate frequency.
|
||||
var/deviation
|
||||
|
||||
/datum/instrument_key/New(sample = src.sample, key = src.key, deviation = src.deviation, frequency = src.frequency)
|
||||
src.sample = sample
|
||||
src.key = key
|
||||
src.deviation = deviation
|
||||
src.frequency = frequency
|
||||
if(!frequency && deviation)
|
||||
calculate()
|
||||
|
||||
/**
|
||||
* Calculates and stores our deviation.
|
||||
*/
|
||||
/datum/instrument_key/proc/calculate()
|
||||
if(!deviation)
|
||||
CRASH("Invalid calculate call: No deviation or sample in instrument_key")
|
||||
#define KEY_TWELTH (1/12)
|
||||
frequency = 2 ** (KEY_TWELTH * deviation)
|
||||
#undef KEY_TWELTH
|
||||
26
code/modules/instruments/instrument_data/brass.dm
Normal file
@@ -0,0 +1,26 @@
|
||||
/datum/instrument/brass
|
||||
name = "Generic brass instrument"
|
||||
category = "Brass"
|
||||
abstract_type = /datum/instrument/brass
|
||||
|
||||
/datum/instrument/brass/crisis_section
|
||||
name = "Crisis Brass Section"
|
||||
id = "crbrass"
|
||||
real_samples = list("36"='sound/instruments/synthesis_samples/brass/crisis_brass/c2.ogg',
|
||||
"48"='sound/instruments/synthesis_samples/brass/crisis_brass/c3.ogg',
|
||||
"60"='sound/instruments/synthesis_samples/brass/crisis_brass/c4.ogg',
|
||||
"72"='sound/instruments/synthesis_samples/brass/crisis_brass/c5.ogg')
|
||||
|
||||
/datum/instrument/brass/crisis_trombone
|
||||
name = "Crisis Trombone"
|
||||
id = "crtrombone"
|
||||
real_samples = list("36"='sound/instruments/synthesis_samples/brass/crisis_trombone/c2.ogg',
|
||||
"48"='sound/instruments/synthesis_samples/brass/crisis_trombone/c3.ogg',
|
||||
"60"='sound/instruments/synthesis_samples/brass/crisis_trombone/c4.ogg',
|
||||
"72"='sound/instruments/synthesis_samples/brass/crisis_trombone/c5.ogg')
|
||||
|
||||
/datum/instrument/brass/crisis_trumpet
|
||||
name = "Crisis Trumpet"
|
||||
id = "crtrumpet"
|
||||
real_samples = list("60"='sound/instruments/synthesis_samples/brass/crisis_trumpet/c4.ogg',
|
||||
"72"='sound/instruments/synthesis_samples/brass/crisis_trumpet/c5.ogg')
|
||||
@@ -0,0 +1,31 @@
|
||||
/datum/instrument/chromatic
|
||||
name = "Generic chromatic percussion instrument"
|
||||
category = "Chromatic percussion"
|
||||
abstract_type = /datum/instrument/chromatic
|
||||
|
||||
/datum/instrument/chromatic/vibraphone1
|
||||
name = "Crisis Vibraphone"
|
||||
id = "crvibr"
|
||||
real_samples = list("36"='sound/instruments/synthesis_samples/chromatic/vibraphone1/c2.ogg',
|
||||
"48"='sound/instruments/synthesis_samples/chromatic/vibraphone1/c3.ogg',
|
||||
"60"='sound/instruments/synthesis_samples/chromatic/vibraphone1/c4.ogg',
|
||||
"72"='sound/instruments/synthesis_samples/chromatic/vibraphone1/c5.ogg')
|
||||
|
||||
/datum/instrument/chromatic/musicbox1
|
||||
name = "SGM Music Box"
|
||||
id = "sgmmbox"
|
||||
real_samples = list("36"='sound/instruments/synthesis_samples/chromatic/sgmbox/c2.ogg',
|
||||
"48"='sound/instruments/synthesis_samples/chromatic/sgmbox/c3.ogg',
|
||||
"60"='sound/instruments/synthesis_samples/chromatic/sgmbox/c4.ogg',
|
||||
"72"='sound/instruments/synthesis_samples/chromatic/sgmbox/c5.ogg')
|
||||
|
||||
/datum/instrument/chromatic/fluid_celeste
|
||||
name = "FluidR3 Celeste"
|
||||
id = "r3celeste"
|
||||
real_samples = list("36"='sound/instruments/synthesis_samples/chromatic/fluid_celeste/c2.ogg',
|
||||
"48"='sound/instruments/synthesis_samples/chromatic/fluid_celeste/c3.ogg',
|
||||
"60"='sound/instruments/synthesis_samples/chromatic/fluid_celeste/c4.ogg',
|
||||
"72"='sound/instruments/synthesis_samples/chromatic/fluid_celeste/c5.ogg',
|
||||
"84"='sound/instruments/synthesis_samples/chromatic/fluid_celeste/c6.ogg',
|
||||
"96"='sound/instruments/synthesis_samples/chromatic/fluid_celeste/c7.ogg',
|
||||
"108"='sound/instruments/synthesis_samples/chromatic/fluid_celeste/c8.ogg')
|
||||
25
code/modules/instruments/instrument_data/fun.dm
Normal file
@@ -0,0 +1,25 @@
|
||||
/datum/instrument/fun
|
||||
name = "Generic Fun Instrument"
|
||||
category = "Fun"
|
||||
abstract_type = /datum/instrument/fun
|
||||
|
||||
/datum/instrument/fun/honk
|
||||
name = "!!HONK!!"
|
||||
id = "honk"
|
||||
real_samples = list("74"='sound/items/bikehorn.ogg') // Cluwne Heaven
|
||||
|
||||
/datum/instrument/fun/signal
|
||||
name = "Ping"
|
||||
id = "ping"
|
||||
real_samples = list("79"='sound/machines/ping.ogg')
|
||||
|
||||
/datum/instrument/fun/chime
|
||||
name = "Chime"
|
||||
id = "chime"
|
||||
real_samples = list("79"='sound/machines/chime.ogg')
|
||||
|
||||
/datum/instrument/fun/mothscream
|
||||
name = "Moth Scream"
|
||||
id = "mothscream"
|
||||
real_samples = list("60"='sound/voice/moth/scream_moth.ogg')
|
||||
admin_only = TRUE
|
||||
36
code/modules/instruments/instrument_data/guitar.dm
Normal file
@@ -0,0 +1,36 @@
|
||||
/datum/instrument/guitar
|
||||
name = "Generic guitar-like instrument"
|
||||
category = "Guitar"
|
||||
abstract_type = /datum/instrument/guitar
|
||||
|
||||
/datum/instrument/guitar/steel_crisis
|
||||
name = "Crisis Steel String Guitar"
|
||||
id = "csteelgt"
|
||||
real_samples = list("36"='sound/instruments/synthesis_samples/guitar/crisis_steel/c2.ogg',
|
||||
"48"='sound/instruments/synthesis_samples/guitar/crisis_steel/c3.ogg',
|
||||
"60"='sound/instruments/synthesis_samples/guitar/crisis_steel/c4.ogg',
|
||||
"72"='sound/instruments/synthesis_samples/guitar/crisis_steel/c5.ogg')
|
||||
|
||||
/datum/instrument/guitar/nylon_crisis
|
||||
name = "Crisis Nylon String Guitar"
|
||||
id = "cnylongt"
|
||||
real_samples = list("36"='sound/instruments/synthesis_samples/guitar/crisis_nylon/c2.ogg',
|
||||
"48"='sound/instruments/synthesis_samples/guitar/crisis_nylon/c3.ogg',
|
||||
"60"='sound/instruments/synthesis_samples/guitar/crisis_nylon/c4.ogg',
|
||||
"72"='sound/instruments/synthesis_samples/guitar/crisis_nylon/c5.ogg')
|
||||
|
||||
/datum/instrument/guitar/clean_crisis
|
||||
name = "Crisis Clean Guitar"
|
||||
id = "ccleangt"
|
||||
real_samples = list("36"='sound/instruments/synthesis_samples/guitar/crisis_clean/c2.ogg',
|
||||
"48"='sound/instruments/synthesis_samples/guitar/crisis_clean/c3.ogg',
|
||||
"60"='sound/instruments/synthesis_samples/guitar/crisis_clean/c4.ogg',
|
||||
"72"='sound/instruments/synthesis_samples/guitar/crisis_clean/c5.ogg')
|
||||
|
||||
/datum/instrument/guitar/muted_crisis
|
||||
name = "Crisis Muted Guitar"
|
||||
id = "cmutedgt"
|
||||
real_samples = list("36"='sound/instruments/synthesis_samples/guitar/crisis_muted/c2.ogg',
|
||||
"48"='sound/instruments/synthesis_samples/guitar/crisis_muted/c3.ogg',
|
||||
"60"='sound/instruments/synthesis_samples/guitar/crisis_muted/c4.ogg',
|
||||
"72"='sound/instruments/synthesis_samples/guitar/crisis_muted/c5.ogg')
|
||||
86
code/modules/instruments/instrument_data/hardcoded.dm
Normal file
@@ -0,0 +1,86 @@
|
||||
//THESE ARE HARDCODED INSTRUMENT SAMPLES.
|
||||
//SONGS WILL BE AUTOMATICALLY SWITCHED TO LEGACY MODE IF THEY USE THIS KIND OF INSTRUMENT!
|
||||
//I'd prefer these stayed. They sound different from the mechanical synthesis of synthed instruments, and I quite like them that way. It's not legacy, it's hardcoded, old style. - kevinz000
|
||||
/datum/instrument/hardcoded
|
||||
abstract_type = /datum/instrument/hardcoded
|
||||
category = "Non-Synthesized"
|
||||
instrument_flags = INSTRUMENT_LEGACY
|
||||
volume_multiplier = 1 //not as loud as synth'd
|
||||
|
||||
/datum/instrument/hardcoded/accordion
|
||||
name = "Accordion"
|
||||
id = "accordion"
|
||||
legacy_instrument_ext = "mid"
|
||||
legacy_instrument_path = "accordion"
|
||||
|
||||
/datum/instrument/hardcoded/bikehorn
|
||||
name = "Bike Horn"
|
||||
id = "bikehorn"
|
||||
legacy_instrument_ext = "ogg"
|
||||
legacy_instrument_path = "bikehorn"
|
||||
|
||||
/datum/instrument/hardcoded/eguitar
|
||||
name = "Electric Guitar"
|
||||
id = "eguitar"
|
||||
legacy_instrument_ext = "ogg"
|
||||
legacy_instrument_path = "eguitar"
|
||||
|
||||
/datum/instrument/hardcoded/glockenspiel
|
||||
name = "Glockenspiel"
|
||||
id = "glockenspiel"
|
||||
legacy_instrument_ext = "mid"
|
||||
legacy_instrument_path = "glockenspiel"
|
||||
|
||||
/datum/instrument/hardcoded/guitar
|
||||
name = "Guitar"
|
||||
id = "guitar"
|
||||
legacy_instrument_ext = "ogg"
|
||||
legacy_instrument_path = "guitar"
|
||||
|
||||
/datum/instrument/hardcoded/harmonica
|
||||
name = "Harmonica"
|
||||
id = "harmonica"
|
||||
legacy_instrument_ext = "mid"
|
||||
legacy_instrument_path = "harmonica"
|
||||
|
||||
/datum/instrument/hardcoded/piano
|
||||
name = "Piano"
|
||||
id = "piano"
|
||||
legacy_instrument_ext = "ogg"
|
||||
legacy_instrument_path = "piano"
|
||||
|
||||
/datum/instrument/hardcoded/recorder
|
||||
name = "Recorder"
|
||||
id = "recorder"
|
||||
legacy_instrument_ext = "mid"
|
||||
legacy_instrument_path = "recorder"
|
||||
|
||||
/datum/instrument/hardcoded/saxophone
|
||||
name = "Saxophone"
|
||||
id = "saxophone"
|
||||
legacy_instrument_ext = "mid"
|
||||
legacy_instrument_path = "saxophone"
|
||||
|
||||
/datum/instrument/hardcoded/trombone
|
||||
name = "Trombone"
|
||||
id = "trombone"
|
||||
legacy_instrument_ext = "mid"
|
||||
legacy_instrument_path = "trombone"
|
||||
|
||||
/datum/instrument/hardcoded/violin
|
||||
name = "Violin"
|
||||
id = "violin"
|
||||
legacy_instrument_ext = "mid"
|
||||
legacy_instrument_path = "violin"
|
||||
|
||||
/datum/instrument/hardcoded/xylophone
|
||||
name = "Xylophone"
|
||||
id = "xylophone"
|
||||
legacy_instrument_ext = "mid"
|
||||
legacy_instrument_path = "xylophone"
|
||||
|
||||
/datum/instrument/hardcoded/banjo
|
||||
name = "Banjo"
|
||||
id = "banjo"
|
||||
legacy_instrument_ext = "ogg"
|
||||
legacy_instrument_path = "banjo"
|
||||
43
code/modules/instruments/instrument_data/organ.dm
Normal file
@@ -0,0 +1,43 @@
|
||||
/datum/instrument/organ
|
||||
name = "Generic organ"
|
||||
category = "Organ"
|
||||
abstract_type = /datum/instrument/organ
|
||||
|
||||
/datum/instrument/organ/crisis_church
|
||||
name = "Crisis Church Organ"
|
||||
id = "crichugan"
|
||||
real_samples = list("36"='sound/instruments/synthesis_samples/organ/crisis_church/c2.ogg',
|
||||
"48"='sound/instruments/synthesis_samples/organ/crisis_church/c3.ogg',
|
||||
"60"='sound/instruments/synthesis_samples/organ/crisis_church/c4.ogg',
|
||||
"72"='sound/instruments/synthesis_samples/organ/crisis_church/c5.ogg')
|
||||
|
||||
/datum/instrument/organ/crisis_hammond
|
||||
name = "Crisis Hammond Organ"
|
||||
id = "crihamgan"
|
||||
real_samples = list("36"='sound/instruments/synthesis_samples/organ/crisis_hammond/c2.ogg',
|
||||
"48"='sound/instruments/synthesis_samples/organ/crisis_hammond/c3.ogg',
|
||||
"60"='sound/instruments/synthesis_samples/organ/crisis_hammond/c4.ogg',
|
||||
"72"='sound/instruments/synthesis_samples/organ/crisis_hammond/c5.ogg')
|
||||
|
||||
/datum/instrument/organ/crisis_accordian
|
||||
name = "Crisis Accordian"
|
||||
id = "crack"
|
||||
real_samples = list("36"='sound/instruments/synthesis_samples/organ/crisis_accordian/c2.ogg',
|
||||
"48"='sound/instruments/synthesis_samples/organ/crisis_accordian/c3.ogg',
|
||||
"60"='sound/instruments/synthesis_samples/organ/crisis_accordian/c4.ogg',
|
||||
"72"='sound/instruments/synthesis_samples/organ/crisis_accordian/c5.ogg')
|
||||
|
||||
/datum/instrument/organ/crisis_harmonica
|
||||
name = "Crisis Harmonica"
|
||||
id = "crharmony"
|
||||
real_samples = list("48"='sound/instruments/synthesis_samples/organ/crisis_harmonica/c3.ogg',
|
||||
"60"='sound/instruments/synthesis_samples/organ/crisis_harmonica/c4.ogg',
|
||||
"72"='sound/instruments/synthesis_samples/organ/crisis_harmonica/c5.ogg')
|
||||
|
||||
/datum/instrument/organ/crisis_tango_accordian
|
||||
name = "Crisis Tango Accordian"
|
||||
id = "crtango"
|
||||
real_samples = list("36"='sound/instruments/synthesis_samples/organ/crisis_tangaccordian/c2.ogg',
|
||||
"48"='sound/instruments/synthesis_samples/organ/crisis_tangaccordian/c3.ogg',
|
||||
"60"='sound/instruments/synthesis_samples/organ/crisis_tangaccordian/c4.ogg',
|
||||
"72"='sound/instruments/synthesis_samples/organ/crisis_tangaccordian/c5.ogg')
|
||||
56
code/modules/instruments/instrument_data/piano.dm
Normal file
@@ -0,0 +1,56 @@
|
||||
/datum/instrument/piano
|
||||
name = "Generic piano"
|
||||
category = "Piano"
|
||||
abstract_type = /datum/instrument/piano
|
||||
|
||||
/datum/instrument/piano/fluid_piano
|
||||
name = "FluidR3 Grand Piano"
|
||||
id = "r3grand"
|
||||
real_samples = list("36"='sound/instruments/synthesis_samples/piano/fluid_piano/c2.ogg',
|
||||
"48"='sound/instruments/synthesis_samples/piano/fluid_piano/c3.ogg',
|
||||
"60"='sound/instruments/synthesis_samples/piano/fluid_piano/c4.ogg',
|
||||
"72"='sound/instruments/synthesis_samples/piano/fluid_piano/c5.ogg',
|
||||
"84"='sound/instruments/synthesis_samples/piano/fluid_piano/c6.ogg',
|
||||
"96"='sound/instruments/synthesis_samples/piano/fluid_piano/c7.ogg',
|
||||
"108"='sound/instruments/synthesis_samples/piano/fluid_piano/c8.ogg')
|
||||
|
||||
/datum/instrument/piano/fluid_harpsichord
|
||||
name = "FluidR3 Harpsichord"
|
||||
id = "r3harpsi"
|
||||
real_samples = list("36"='sound/instruments/synthesis_samples/piano/fluid_harpsi/c2.ogg',
|
||||
"48"='sound/instruments/synthesis_samples/piano/fluid_harpsi/c3.ogg',
|
||||
"60"='sound/instruments/synthesis_samples/piano/fluid_harpsi/c4.ogg',
|
||||
"72"='sound/instruments/synthesis_samples/piano/fluid_harpsi/c5.ogg',
|
||||
"84"='sound/instruments/synthesis_samples/piano/fluid_harpsi/c6.ogg',
|
||||
"96"='sound/instruments/synthesis_samples/piano/fluid_harpsi/c7.ogg',
|
||||
"108"='sound/instruments/synthesis_samples/piano/fluid_harpsi/c8.ogg')
|
||||
|
||||
/datum/instrument/piano/crisis_harpsichord
|
||||
name = "Crisis Harpsichord"
|
||||
id = "crharpsi"
|
||||
real_samples = list("36"='sound/instruments/synthesis_samples/piano/crisis_harpsichord/c2.ogg',
|
||||
"48"='sound/instruments/synthesis_samples/piano/crisis_harpsichord/c3.ogg',
|
||||
"60"='sound/instruments/synthesis_samples/piano/crisis_harpsichord/c4.ogg',
|
||||
"72"='sound/instruments/synthesis_samples/piano/crisis_harpsichord/c5.ogg')
|
||||
|
||||
/datum/instrument/piano/crisis_grandpiano_uni
|
||||
name = "Crisis Grand Piano One"
|
||||
id = "crgrand1"
|
||||
real_samples = list("36"='sound/instruments/synthesis_samples/piano/crisis_grand_piano/c2.ogg',
|
||||
"48"='sound/instruments/synthesis_samples/piano/crisis_grand_piano/c3.ogg',
|
||||
"60"='sound/instruments/synthesis_samples/piano/crisis_grand_piano/c4.ogg',
|
||||
"72"='sound/instruments/synthesis_samples/piano/crisis_grand_piano/c5.ogg',
|
||||
"84"='sound/instruments/synthesis_samples/piano/crisis_grand_piano/c6.ogg',
|
||||
"96"='sound/instruments/synthesis_samples/piano/crisis_grand_piano/c7.ogg',
|
||||
"108"='sound/instruments/synthesis_samples/piano/crisis_grand_piano/c8.ogg')
|
||||
|
||||
/datum/instrument/piano/crisis_brightpiano_uni
|
||||
name = "Crisis Bright Piano One"
|
||||
id = "crbright1"
|
||||
real_samples = list("36"='sound/instruments/synthesis_samples/piano/crisis_bright_piano/c2.ogg',
|
||||
"48"='sound/instruments/synthesis_samples/piano/crisis_bright_piano/c3.ogg',
|
||||
"60"='sound/instruments/synthesis_samples/piano/crisis_bright_piano/c4.ogg',
|
||||
"72"='sound/instruments/synthesis_samples/piano/crisis_bright_piano/c5.ogg',
|
||||
"84"='sound/instruments/synthesis_samples/piano/crisis_bright_piano/c6.ogg',
|
||||
"96"='sound/instruments/synthesis_samples/piano/crisis_bright_piano/c7.ogg',
|
||||
"108"='sound/instruments/synthesis_samples/piano/crisis_bright_piano/c8.ogg')
|
||||
19
code/modules/instruments/instrument_data/synth_tones.dm
Normal file
@@ -0,0 +1,19 @@
|
||||
/datum/instrument/tones
|
||||
name = "Ideal tone"
|
||||
category = "Tones"
|
||||
abstract_type = /datum/instrument/tones
|
||||
|
||||
/datum/instrument/tones/square_wave
|
||||
name = "Ideal square wave"
|
||||
id = "square"
|
||||
real_samples = list("81"='sound/instruments/synthesis_samples/tones/Square.ogg')
|
||||
|
||||
/datum/instrument/tones/sine_wave
|
||||
name = "Ideal sine wave"
|
||||
id = "sine"
|
||||
real_samples = list("81"='sound/instruments/synthesis_samples/tones/Sine.ogg')
|
||||
|
||||
/datum/instrument/tones/saw_wave
|
||||
name = "Ideal sawtooth wave"
|
||||
id = "saw"
|
||||
real_samples = list("81"='sound/instruments/synthesis_samples/tones/Sawtooth.ogg')
|
||||
312
code/modules/instruments/items.dm
Normal file
@@ -0,0 +1,312 @@
|
||||
//copy pasta of the space piano, don't hurt me -Pete
|
||||
/obj/item/instrument
|
||||
name = "generic instrument"
|
||||
force = 10
|
||||
health = 100
|
||||
//resistance_flags = FLAMMABLE
|
||||
icon = 'icons/obj/musician.dmi'
|
||||
item_icons = list(
|
||||
slot_l_hand_str = 'icons/mob/items/lefthand_instruments.dmi',
|
||||
slot_r_hand_str = 'icons/mob/items/righthand_instruments.dmi',
|
||||
)
|
||||
|
||||
/// Our song datum.
|
||||
var/datum/song/handheld/song
|
||||
/// Our allowed list of instrument ids. This is nulled on initialize.
|
||||
var/list/allowed_instrument_ids
|
||||
/// How far away our song datum can be heard.
|
||||
var/instrument_range = 15
|
||||
|
||||
/obj/item/instrument/Initialize(mapload)
|
||||
. = ..()
|
||||
song = new(src, allowed_instrument_ids, instrument_range)
|
||||
allowed_instrument_ids = null //We don't need this clogging memory after it's used.
|
||||
|
||||
/obj/item/instrument/Destroy()
|
||||
QDEL_NULL(song)
|
||||
return ..()
|
||||
|
||||
/obj/item/instrument/proc/should_stop_playing(mob/user)
|
||||
return user.incapacitated() || !((loc == user) || (isturf(loc) && Adjacent(user))) // sorry, no more TK playing.
|
||||
|
||||
/obj/item/instrument/suicide_act(mob/user)
|
||||
var/datum/gender/T = gender_datums[user.get_visible_gender()]
|
||||
user.visible_message("<span class='suicide'>[user] begins to play 'Gloomy Sunday'! It looks like [T.hes] trying to commit suicide!</span>")
|
||||
return (BRUTELOSS)
|
||||
|
||||
/obj/item/instrument/attack_self(mob/user)
|
||||
if(!user.IsAdvancedToolUser())
|
||||
to_chat(user, "<span class='warning'>You don't have the dexterity to do this!</span>")
|
||||
return TRUE
|
||||
interact(user)
|
||||
|
||||
/obj/item/instrument/interact(mob/living/user)
|
||||
if(!isliving(user) || user.incapacitated())
|
||||
return
|
||||
|
||||
user.set_machine(src)
|
||||
song.interact(user)
|
||||
|
||||
/obj/item/instrument/violin
|
||||
name = "space violin"
|
||||
desc = "A wooden musical instrument with four strings and a bow. \"The devil went down to space, he was looking for an assistant to grief.\""
|
||||
icon_state = "violin"
|
||||
hitsound = "swing_hit"
|
||||
allowed_instrument_ids = "violin"
|
||||
|
||||
/obj/item/instrument/violin/golden
|
||||
name = "golden violin"
|
||||
desc = "A golden musical instrument with four strings and a bow. \"The devil went down to space, he was looking for an assistant to grief.\""
|
||||
icon_state = "golden_violin"
|
||||
|
||||
/obj/item/instrument/piano_synth
|
||||
name = "synthesizer"
|
||||
desc = "An advanced electronic synthesizer that can be used as various instruments."
|
||||
icon_state = "synth"
|
||||
allowed_instrument_ids = "piano"
|
||||
|
||||
/obj/item/instrument/piano_synth/Initialize(mapload)
|
||||
. = ..()
|
||||
song.allowed_instrument_ids = SSinstruments.synthesizer_instrument_ids
|
||||
|
||||
/obj/item/instrument/piano_synth/headphones
|
||||
name = "headphones"
|
||||
desc = "Unce unce unce unce. Boop!"
|
||||
icon_state = "headphones"
|
||||
slot_flags = SLOT_EARS | SLOT_HEAD
|
||||
force = 0
|
||||
w_class = ITEMSIZE_SMALL
|
||||
instrument_range = 1
|
||||
|
||||
/obj/item/instrument/piano_synth/headphones/Initialize()
|
||||
. = ..()
|
||||
RegisterSignal(src, COMSIG_SONG_START, .proc/start_playing)
|
||||
RegisterSignal(src, COMSIG_SONG_END, .proc/stop_playing)
|
||||
|
||||
/**
|
||||
* Called by a component signal when our song starts playing.
|
||||
*/
|
||||
/obj/item/instrument/piano_synth/headphones/proc/start_playing()
|
||||
SIGNAL_HANDLER
|
||||
|
||||
icon_state = "[initial(icon_state)]_on"
|
||||
if(ishuman(loc))
|
||||
var/mob/living/carbon/human/H = loc
|
||||
if(H.l_ear == src || H.r_ear == src)
|
||||
H.update_inv_ears()
|
||||
else if(H.head == src)
|
||||
H.update_inv_head()
|
||||
|
||||
/**
|
||||
* Called by a component signal when our song stops playing.
|
||||
*/
|
||||
/obj/item/instrument/piano_synth/headphones/proc/stop_playing()
|
||||
SIGNAL_HANDLER
|
||||
|
||||
icon_state = "[initial(icon_state)]"
|
||||
if(ishuman(loc))
|
||||
var/mob/living/carbon/human/H = loc
|
||||
if(H.l_ear == src || H.r_ear == src)
|
||||
H.update_inv_ears()
|
||||
else if(H.head == src)
|
||||
H.update_inv_head()
|
||||
|
||||
|
||||
/obj/item/instrument/piano_synth/headphones/spacepods
|
||||
name = "\improper Nanotrasen space pods"
|
||||
desc = "Flex your money, AND ignore what everyone else says, all at once!"
|
||||
icon_state = "spacepods"
|
||||
slot_flags = SLOT_EARS
|
||||
//strip_delay = 100 //air pods don't fall out
|
||||
instrument_range = 0 //you're paying for quality here
|
||||
|
||||
/obj/item/instrument/banjo
|
||||
name = "banjo"
|
||||
desc = "A 'Mura' brand banjo. It's pretty much just a drum with a neck and strings."
|
||||
icon_state = "banjo"
|
||||
attack_verb = list("scruggs-styled", "hum-diggitied", "shin-dug", "clawhammered")
|
||||
hitsound = 'sound/weapons/banjoslap.ogg'
|
||||
allowed_instrument_ids = "banjo"
|
||||
|
||||
/obj/item/instrument/guitar
|
||||
name = "guitar"
|
||||
desc = "It's made of wood and has bronze strings."
|
||||
icon_state = "guitar"
|
||||
attack_verb = list("played metal on", "serenaded", "crashed", "smashed")
|
||||
hitsound = 'sound/weapons/stringsmash.ogg'
|
||||
allowed_instrument_ids = list("guitar","csteelgt","cnylongt", "ccleangt", "cmutedgt")
|
||||
|
||||
/obj/item/instrument/eguitar
|
||||
name = "electric guitar"
|
||||
desc = "Makes all your shredding needs possible."
|
||||
icon_state = "eguitar"
|
||||
force = 12
|
||||
attack_verb = list("played metal on", "shreded", "crashed", "smashed")
|
||||
hitsound = 'sound/weapons/stringsmash.ogg'
|
||||
allowed_instrument_ids = "eguitar"
|
||||
|
||||
/obj/item/instrument/glockenspiel
|
||||
name = "glockenspiel"
|
||||
desc = "Smooth metal bars perfect for any marching band."
|
||||
icon_state = "glockenspiel"
|
||||
allowed_instrument_ids = list("glockenspiel","crvibr", "sgmmbox", "r3celeste")
|
||||
|
||||
/obj/item/instrument/accordion
|
||||
name = "accordion"
|
||||
desc = "Pun-Pun not included."
|
||||
icon_state = "accordion"
|
||||
allowed_instrument_ids = list("crack", "crtango", "accordion")
|
||||
|
||||
/obj/item/instrument/trumpet
|
||||
name = "trumpet"
|
||||
desc = "To announce the arrival of the king!"
|
||||
icon_state = "trumpet"
|
||||
allowed_instrument_ids = "crtrumpet"
|
||||
|
||||
/obj/item/instrument/trumpet/spectral
|
||||
name = "spectral trumpet"
|
||||
desc = "Things are about to get spooky!"
|
||||
icon_state = "spectral_trumpet"
|
||||
force = 0
|
||||
attack_verb = list("played", "jazzed", "trumpeted", "mourned", "dooted", "spooked")
|
||||
|
||||
/*
|
||||
/obj/item/instrument/trumpet/spectral/Initialize()
|
||||
. = ..()
|
||||
AddComponent(/datum/component/spooky)
|
||||
*/
|
||||
/obj/item/instrument/trumpet/spectral/attack(mob/living/carbon/C, mob/user)
|
||||
playsound (src, 'sound/instruments/trombone/En4.mid', 100,1,-1)
|
||||
..()
|
||||
|
||||
/obj/item/instrument/saxophone
|
||||
name = "saxophone"
|
||||
desc = "This soothing sound will be sure to leave your audience in tears."
|
||||
icon_state = "saxophone"
|
||||
allowed_instrument_ids = "saxophone"
|
||||
|
||||
/obj/item/instrument/saxophone/spectral
|
||||
name = "spectral saxophone"
|
||||
desc = "This spooky sound will be sure to leave mortals in bones."
|
||||
icon_state = "saxophone"
|
||||
force = 0
|
||||
attack_verb = list("played", "jazzed", "saxed", "mourned", "dooted", "spooked")
|
||||
|
||||
/*
|
||||
/obj/item/instrument/saxophone/spectral/Initialize()
|
||||
. = ..()
|
||||
AddComponent(/datum/component/spooky)
|
||||
*/
|
||||
|
||||
/obj/item/instrument/saxophone/spectral/attack(mob/living/carbon/C, mob/user)
|
||||
playsound (src, 'sound/instruments/saxophone/En4.mid', 100,1,-1)
|
||||
..()
|
||||
|
||||
/obj/item/instrument/trombone
|
||||
name = "trombone"
|
||||
desc = "How can any pool table ever hope to compete?"
|
||||
icon_state = "trombone"
|
||||
allowed_instrument_ids = list("crtrombone", "crbrass", "trombone")
|
||||
|
||||
/obj/item/instrument/trombone/spectral
|
||||
name = "spectral trombone"
|
||||
desc = "A skeleton's favorite instrument. Apply directly on the mortals."
|
||||
icon_state = "trombone"
|
||||
force = 0
|
||||
attack_verb = list("played", "jazzed", "tromboneed", "mourned", "dooted", "spooked")
|
||||
|
||||
/*
|
||||
/obj/item/instrument/trombone/spectral/Initialize()
|
||||
. = ..()
|
||||
AddComponent(/datum/component/spooky)
|
||||
*/
|
||||
|
||||
/obj/item/instrument/trombone/spectral/attack(mob/living/carbon/C, mob/user)
|
||||
playsound (src, 'sound/instruments/trombone/Cn4.mid', 100,1,-1)
|
||||
..()
|
||||
|
||||
/obj/item/instrument/recorder
|
||||
name = "recorder"
|
||||
desc = "Just like in school, playing ability and all."
|
||||
force = 5
|
||||
icon_state = "recorder"
|
||||
allowed_instrument_ids = "recorder"
|
||||
|
||||
/obj/item/instrument/harmonica
|
||||
name = "harmonica"
|
||||
desc = "For when you get a bad case of the space blues."
|
||||
icon_state = "harmonica"
|
||||
allowed_instrument_ids = list("crharmony", "harmonica")
|
||||
slot_flags = SLOT_MASK
|
||||
force = 5
|
||||
w_class = ITEMSIZE_SMALL
|
||||
/*
|
||||
actions_types = list(/datum/action/item_action/instrument)
|
||||
|
||||
/obj/item/instrument/harmonica/proc/handle_speech(datum/source, list/speech_args)
|
||||
SIGNAL_HANDLER
|
||||
if(song.playing && ismob(loc))
|
||||
to_chat(loc, "<span class='warning'>You stop playing the harmonica to talk...</span>")
|
||||
song.playing = FALSE
|
||||
|
||||
/obj/item/instrument/harmonica/equipped(mob/M, slot)
|
||||
. = ..()
|
||||
RegisterSignal(M, COMSIG_MOB_SAY, .proc/handle_speech)
|
||||
|
||||
/obj/item/instrument/harmonica/dropped(mob/M)
|
||||
. = ..()
|
||||
UnregisterSignal(M, COMSIG_MOB_SAY)
|
||||
*/
|
||||
/obj/item/instrument/bikehorn
|
||||
name = "gilded bike horn"
|
||||
desc = "An exquisitely decorated bike horn, capable of honking in a variety of notes."
|
||||
icon_state = "bike_horn"
|
||||
item_icons = list(
|
||||
slot_l_hand_str = 'icons/mob/items/lefthand_horns.dmi',
|
||||
slot_r_hand_str = 'icons/mob/items/righthand_horns.dmi',
|
||||
)
|
||||
allowed_instrument_ids = list("bikehorn", "honk")
|
||||
attack_verb = list("beautifully honked")
|
||||
w_class = ITEMSIZE_SMALL
|
||||
force = 0
|
||||
throw_speed = 3
|
||||
throw_range = 15
|
||||
hitsound = 'sound/items/bikehorn.ogg'
|
||||
/*
|
||||
/obj/item/choice_beacon/music
|
||||
name = "instrument delivery beacon"
|
||||
desc = "Summon your tool of art."
|
||||
icon_state = "gangtool-red"
|
||||
|
||||
/obj/item/choice_beacon/music/generate_display_names()
|
||||
var/static/list/instruments
|
||||
if(!instruments)
|
||||
instruments = list()
|
||||
var/list/templist = list(/obj/item/instrument/violin,
|
||||
/obj/item/instrument/piano_synth,
|
||||
/obj/item/instrument/banjo,
|
||||
/obj/item/instrument/guitar,
|
||||
/obj/item/instrument/eguitar,
|
||||
/obj/item/instrument/glockenspiel,
|
||||
/obj/item/instrument/accordion,
|
||||
/obj/item/instrument/trumpet,
|
||||
/obj/item/instrument/saxophone,
|
||||
/obj/item/instrument/trombone,
|
||||
/obj/item/instrument/recorder,
|
||||
/obj/item/instrument/harmonica,
|
||||
/obj/item/instrument/piano_synth/headphones
|
||||
)
|
||||
for(var/V in templist)
|
||||
var/atom/A = V
|
||||
instruments[initial(A.name)] = A
|
||||
return instruments
|
||||
*/
|
||||
/obj/item/instrument/musicalmoth
|
||||
name = "musical moth"
|
||||
desc = "Despite its popularity, this controversial musical toy was eventually banned due to its unethically sampled sounds of moths screaming in agony."
|
||||
icon_state = "mothsician"
|
||||
allowed_instrument_ids = "mothscream"
|
||||
attack_verb = list("fluttered", "flaped")
|
||||
w_class = ITEMSIZE_SMALL
|
||||
force = 0
|
||||
hitsound = 'sound/voice/moth/scream_moth.ogg'
|
||||
16
code/modules/instruments/items_ch.dm
Normal file
@@ -0,0 +1,16 @@
|
||||
/obj/item/instrument/keytar
|
||||
name = "portable keyboard"
|
||||
desc = "A keyboard, for those interested in the piano on the go! "
|
||||
icon = 'icons/obj/musician_yw.dmi'
|
||||
icon_state = "keyboard"
|
||||
item_state = "keyboard"
|
||||
attack_verb = list("smashed")
|
||||
allowed_instrument_ids = "piano"
|
||||
|
||||
/obj/item/instrument/xylophone
|
||||
name = "xylophone"
|
||||
desc = "A percussion instrument consisting of a series of wooden bars graduated in length."
|
||||
icon = 'icons/obj/musician_yw.dmi'
|
||||
icon_state = "xylophone"
|
||||
attack_verb = list("smashed")
|
||||
allowed_instrument_ids = "xylophone"
|
||||
405
code/modules/instruments/songs/_song.dm
Normal file
@@ -0,0 +1,405 @@
|
||||
#define MUSICIAN_HEARCHECK_MINDELAY 4
|
||||
#define MUSIC_MAXLINES 1000
|
||||
#define MUSIC_MAXLINECHARS 300
|
||||
|
||||
/**
|
||||
* # Song datum
|
||||
*
|
||||
* These are the actual backend behind instruments.
|
||||
* They attach to an atom and provide the editor + playback functionality.
|
||||
*/
|
||||
/datum/song
|
||||
/// Name of the song
|
||||
var/name = "Untitled"
|
||||
|
||||
/// The atom we're attached to/playing from
|
||||
var/atom/parent
|
||||
|
||||
/// Our song lines
|
||||
var/list/lines
|
||||
|
||||
/// delay between notes in deciseconds
|
||||
var/tempo = 5
|
||||
|
||||
/// How far we can be heard
|
||||
var/instrument_range = 15
|
||||
|
||||
/// Are we currently playing?
|
||||
var/playing = FALSE
|
||||
|
||||
/// Are we currently editing?
|
||||
var/editing = TRUE
|
||||
/// Is the help screen open?
|
||||
var/help = FALSE
|
||||
|
||||
/// Repeats left
|
||||
var/repeat = 0
|
||||
/// Maximum times we can repeat
|
||||
var/max_repeats = 10
|
||||
|
||||
/// Our volume
|
||||
var/volume = 35
|
||||
/// Max volume
|
||||
var/max_volume = 75
|
||||
/// Min volume - This is so someone doesn't decide it's funny to set it to 0 and play invisible songs.
|
||||
var/min_volume = 1
|
||||
|
||||
/// What instruments our built in picker can use. The picker won't show unless this is longer than one.
|
||||
var/list/allowed_instrument_ids = list("r3grand")
|
||||
|
||||
//////////// Cached instrument variables /////////////
|
||||
/// Instrument we are currently using
|
||||
var/datum/instrument/using_instrument
|
||||
/// Cached legacy ext for legacy instruments
|
||||
var/cached_legacy_ext
|
||||
/// Cached legacy dir for legacy instruments
|
||||
var/cached_legacy_dir
|
||||
/// Cached list of samples, referenced directly from the instrument for synthesized instruments
|
||||
var/list/cached_samples
|
||||
/// Are we operating in legacy mode (so if the instrument is a legacy instrument)
|
||||
var/legacy = FALSE
|
||||
//////////////////////////////////////////////////////
|
||||
|
||||
/////////////////// Playing variables ////////////////
|
||||
/**
|
||||
* Build by compile_chords()
|
||||
* Must be rebuilt on instrument switch.
|
||||
* Compilation happens when we start playing and is cleared after we finish playing.
|
||||
* Format: list of chord lists, with chordlists having (key1, key2, key3, tempodiv)
|
||||
*/
|
||||
var/list/compiled_chords
|
||||
/// Current section of a long chord we're on, so we don't need to make a billion chords, one for every unit ticklag.
|
||||
var/elapsed_delay
|
||||
/// Amount of delay to wait before playing the next chord
|
||||
var/delay_by
|
||||
/// Current chord we're on.
|
||||
var/current_chord
|
||||
/// Channel as text = current volume percentage but it's 0 to 100 instead of 0 to 1.
|
||||
var/list/channels_playing = list()
|
||||
/// List of channels that aren't being used, as text. This is to prevent unnecessary freeing and reallocations from SSsounds/SSinstruments.
|
||||
var/list/channels_idle = list()
|
||||
/// Person playing us
|
||||
var/mob/user_playing
|
||||
//////////////////////////////////////////////////////
|
||||
|
||||
/// Last world.time we checked for who can hear us
|
||||
var/last_hearcheck = 0
|
||||
/// The list of mobs that can hear us
|
||||
var/list/hearing_mobs
|
||||
/// If this is enabled, some things won't be strictly cleared when they usually are (liked compiled_chords on play stop)
|
||||
var/debug_mode = FALSE
|
||||
/// Max sound channels to occupy
|
||||
var/max_sound_channels = CHANNELS_PER_INSTRUMENT
|
||||
/// Current channels, so we can save a length() call.
|
||||
var/using_sound_channels = 0
|
||||
/// Last channel to play. text.
|
||||
var/last_channel_played
|
||||
/// Should we not decay our last played note?
|
||||
var/full_sustain_held_note = TRUE
|
||||
|
||||
/////////////////////// DO NOT TOUCH THESE ///////////////////
|
||||
var/octave_min = INSTRUMENT_MIN_OCTAVE
|
||||
var/octave_max = INSTRUMENT_MAX_OCTAVE
|
||||
var/key_min = INSTRUMENT_MIN_KEY
|
||||
var/key_max = INSTRUMENT_MAX_KEY
|
||||
var/static/list/note_offset_lookup = list(9, 11, 0, 2, 4, 5, 7)
|
||||
var/static/list/accent_lookup = list("b" = -1, "s" = 1, "#" = 1, "n" = 0)
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
///////////// !!FUN!! - Only works in synthesized mode! /////////////////
|
||||
/// Note numbers to shift.
|
||||
var/note_shift = 0
|
||||
var/note_shift_min = -100
|
||||
var/note_shift_max = 100
|
||||
var/can_noteshift = TRUE
|
||||
/// The kind of sustain we're using
|
||||
var/sustain_mode = SUSTAIN_LINEAR
|
||||
/// When a note is considered dead if it is below this in volume
|
||||
var/sustain_dropoff_volume = 0
|
||||
/// Total duration of linear sustain for 100 volume note to get to SUSTAIN_DROPOFF
|
||||
var/sustain_linear_duration = 5
|
||||
/// Exponential sustain dropoff rate per decisecond
|
||||
var/sustain_exponential_dropoff = 1.4
|
||||
////////// DO NOT DIRECTLY SET THESE!
|
||||
/// Do not directly set, use update_sustain()
|
||||
var/cached_linear_dropoff = 10
|
||||
/// Do not directly set, use update_sustain()
|
||||
var/cached_exponential_dropoff = 1.045
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/datum/song/New(atom/parent, list/instrument_ids, new_range)
|
||||
SSinstruments.on_song_new(src)
|
||||
lines = list()
|
||||
tempo = sanitize_tempo(tempo)
|
||||
src.parent = parent
|
||||
if(instrument_ids)
|
||||
allowed_instrument_ids = islist(instrument_ids)? instrument_ids : list(instrument_ids)
|
||||
if(length(allowed_instrument_ids))
|
||||
set_instrument(allowed_instrument_ids[1])
|
||||
hearing_mobs = list()
|
||||
volume = clamp(volume, min_volume, max_volume)
|
||||
update_sustain()
|
||||
if(new_range)
|
||||
instrument_range = new_range
|
||||
|
||||
/datum/song/Destroy()
|
||||
stop_playing()
|
||||
SSinstruments.on_song_del(src)
|
||||
lines = null
|
||||
if(using_instrument)
|
||||
using_instrument.songs_using -= src
|
||||
using_instrument = null
|
||||
allowed_instrument_ids = null
|
||||
parent = null
|
||||
return ..()
|
||||
|
||||
/**
|
||||
* Checks and stores which mobs can hear us. Terminates sounds for mobs that leave our range.
|
||||
*/
|
||||
/datum/song/proc/do_hearcheck()
|
||||
last_hearcheck = world.time
|
||||
var/list/old = hearing_mobs.Copy()
|
||||
hearing_mobs.len = 0
|
||||
var/turf/source = get_turf(parent)
|
||||
var/list/in_range = get_mobs_and_objs_in_view_fast(source, instrument_range)
|
||||
for(var/mob/M in in_range["mobs"])
|
||||
hearing_mobs[M] = get_dist(M, source)
|
||||
var/list/exited = old - hearing_mobs
|
||||
for(var/i in exited)
|
||||
terminate_sound_mob(i)
|
||||
|
||||
/**
|
||||
* Sets our instrument, caching anything necessary for faster accessing. Accepts an ID, typepath, or instantiated instrument datum.
|
||||
*/
|
||||
/datum/song/proc/set_instrument(datum/instrument/I)
|
||||
terminate_all_sounds()
|
||||
var/old_legacy
|
||||
if(using_instrument)
|
||||
using_instrument.songs_using -= src
|
||||
old_legacy = (using_instrument.instrument_flags & INSTRUMENT_LEGACY)
|
||||
using_instrument = null
|
||||
cached_samples = null
|
||||
cached_legacy_ext = null
|
||||
cached_legacy_dir = null
|
||||
legacy = null
|
||||
if(istext(I) || ispath(I))
|
||||
I = SSinstruments.instrument_data[I]
|
||||
if(istype(I))
|
||||
using_instrument = I
|
||||
I.songs_using += src
|
||||
var/instrument_legacy = (I.instrument_flags & INSTRUMENT_LEGACY)
|
||||
if(instrument_legacy)
|
||||
cached_legacy_ext = I.legacy_instrument_ext
|
||||
cached_legacy_dir = I.legacy_instrument_path
|
||||
legacy = TRUE
|
||||
else
|
||||
cached_samples = I.samples
|
||||
legacy = FALSE
|
||||
if(isnull(old_legacy) || (old_legacy != instrument_legacy))
|
||||
if(playing)
|
||||
compile_chords()
|
||||
|
||||
/**
|
||||
* Attempts to start playing our song.
|
||||
*/
|
||||
/datum/song/proc/start_playing(mob/user)
|
||||
if(playing)
|
||||
return
|
||||
if(!using_instrument?.ready())
|
||||
to_chat(user, "<span class='warning'>An error has occured with [src]. Please reset the instrument.</span>")
|
||||
return
|
||||
compile_chords()
|
||||
if(!length(compiled_chords))
|
||||
to_chat(user, "<span class='warning'>Song is empty.</span>")
|
||||
return
|
||||
playing = TRUE
|
||||
updateDialog(user_playing)
|
||||
//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)
|
||||
elapsed_delay = 0
|
||||
delay_by = 0
|
||||
current_chord = 1
|
||||
user_playing = user
|
||||
START_PROCESSING(SSinstruments, src)
|
||||
|
||||
/**
|
||||
* Stops playing, terminating all sounds if in synthesized mode. Clears hearing_mobs.
|
||||
*/
|
||||
/datum/song/proc/stop_playing()
|
||||
if(!playing)
|
||||
return
|
||||
playing = FALSE
|
||||
if(!debug_mode)
|
||||
compiled_chords = null
|
||||
STOP_PROCESSING(SSinstruments, src)
|
||||
SEND_SIGNAL(parent, COMSIG_SONG_END)
|
||||
terminate_all_sounds(TRUE)
|
||||
hearing_mobs.len = 0
|
||||
user_playing = null
|
||||
|
||||
/**
|
||||
* Processes our song.
|
||||
*/
|
||||
/datum/song/proc/process_song(wait)
|
||||
if(!length(compiled_chords) || should_stop_playing(user_playing))
|
||||
stop_playing()
|
||||
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
|
||||
|
||||
/**
|
||||
* Converts a tempodiv to ticks to elapse before playing the next chord, taking into account our tempo.
|
||||
*/
|
||||
/datum/song/proc/tempodiv_to_delay(tempodiv)
|
||||
if(!tempodiv)
|
||||
tempodiv = 1 // no division by 0. some song converters tend to use 0 for when it wants to have no div, for whatever reason.
|
||||
return max(1, round((tempo/tempodiv) / world.tick_lag, 1))
|
||||
|
||||
/**
|
||||
* Compiles chords.
|
||||
*/
|
||||
/datum/song/proc/compile_chords()
|
||||
legacy ? compile_legacy() : compile_synthesized()
|
||||
|
||||
/**
|
||||
* Plays a chord.
|
||||
*/
|
||||
/datum/song/proc/play_chord(list/chord)
|
||||
// last value is timing information
|
||||
for(var/i in 1 to (length(chord) - 1))
|
||||
legacy? playkey_legacy(chord[i][1], chord[i][2], chord[i][3], user_playing) : playkey_synth(chord[i], user_playing)
|
||||
|
||||
/**
|
||||
* Checks if we should halt playback.
|
||||
*/
|
||||
/datum/song/proc/should_stop_playing(mob/user)
|
||||
return QDELETED(parent) || !using_instrument || !playing
|
||||
|
||||
/**
|
||||
* Sanitizes tempo to a value that makes sense and fits the current world.tick_lag.
|
||||
*/
|
||||
/datum/song/proc/sanitize_tempo(new_tempo)
|
||||
new_tempo = abs(new_tempo)
|
||||
return clamp(round(new_tempo, world.tick_lag), world.tick_lag, 5 SECONDS)
|
||||
|
||||
/**
|
||||
* Gets our beats per minute based on our tempo.
|
||||
*/
|
||||
/datum/song/proc/get_bpm()
|
||||
return 600 / tempo
|
||||
|
||||
/**
|
||||
* Sets our tempo from a beats-per-minute, sanitizing it to a valid number first.
|
||||
*/
|
||||
/datum/song/proc/set_bpm(bpm)
|
||||
tempo = sanitize_tempo(600 / bpm)
|
||||
|
||||
/**
|
||||
* Updates the window for our users. Override down the line.
|
||||
*/
|
||||
/datum/song/proc/updateDialog(mob/user)
|
||||
interact(user)
|
||||
|
||||
/datum/song/process(wait)
|
||||
if(!playing)
|
||||
return PROCESS_KILL
|
||||
// it's expected this ticks at every world.tick_lag. if it lags, do not attempt to catch up.
|
||||
process_song(world.tick_lag)
|
||||
process_decay(world.tick_lag)
|
||||
|
||||
/**
|
||||
* Updates our cached linear/exponential falloff stuff, saving calculations down the line.
|
||||
*/
|
||||
/datum/song/proc/update_sustain()
|
||||
// Exponential is easy
|
||||
cached_exponential_dropoff = sustain_exponential_dropoff
|
||||
// Linear, not so much, since it's a target duration from 100 volume rather than an exponential rate.
|
||||
var/target_duration = sustain_linear_duration
|
||||
var/volume_diff = max(0, 100 - sustain_dropoff_volume)
|
||||
var/volume_decrease_per_decisecond = volume_diff / target_duration
|
||||
cached_linear_dropoff = volume_decrease_per_decisecond
|
||||
|
||||
/**
|
||||
* Setter for setting output volume.
|
||||
*/
|
||||
/datum/song/proc/set_volume(volume)
|
||||
src.volume = clamp(volume, max(0, min_volume), min(100, max_volume))
|
||||
update_sustain()
|
||||
updateDialog()
|
||||
|
||||
/**
|
||||
* Setter for setting how low the volume has to get before a note is considered "dead" and dropped
|
||||
*/
|
||||
/datum/song/proc/set_dropoff_volume(volume)
|
||||
sustain_dropoff_volume = clamp(volume, INSTRUMENT_MIN_SUSTAIN_DROPOFF, 100)
|
||||
update_sustain()
|
||||
updateDialog()
|
||||
|
||||
/**
|
||||
* Setter for setting exponential falloff factor.
|
||||
*/
|
||||
/datum/song/proc/set_exponential_drop_rate(drop)
|
||||
sustain_exponential_dropoff = clamp(drop, INSTRUMENT_EXP_FALLOFF_MIN, INSTRUMENT_EXP_FALLOFF_MAX)
|
||||
update_sustain()
|
||||
updateDialog()
|
||||
|
||||
/**
|
||||
* Setter for setting linear falloff duration.
|
||||
*/
|
||||
/datum/song/proc/set_linear_falloff_duration(duration)
|
||||
sustain_linear_duration = clamp(duration, 0.1, INSTRUMENT_MAX_TOTAL_SUSTAIN)
|
||||
update_sustain()
|
||||
updateDialog()
|
||||
|
||||
/datum/song/vv_edit_var(var_name, var_value)
|
||||
. = ..()
|
||||
if(.)
|
||||
switch(var_name)
|
||||
if(NAMEOF(src, volume))
|
||||
set_volume(var_value)
|
||||
if(NAMEOF(src, sustain_dropoff_volume))
|
||||
set_dropoff_volume(var_value)
|
||||
if(NAMEOF(src, sustain_exponential_dropoff))
|
||||
set_exponential_drop_rate(var_value)
|
||||
if(NAMEOF(src, sustain_linear_duration))
|
||||
set_linear_falloff_duration(var_value)
|
||||
|
||||
// subtype for handheld instruments, like violin
|
||||
/datum/song/handheld
|
||||
|
||||
/datum/song/handheld/updateDialog(mob/user)
|
||||
parent.interact(user || usr)
|
||||
|
||||
/datum/song/handheld/should_stop_playing(mob/user)
|
||||
. = ..()
|
||||
if(.)
|
||||
return TRUE
|
||||
var/obj/item/instrument/I = parent
|
||||
return I.should_stop_playing(user)
|
||||
|
||||
// subtype for stationary structures, like pianos
|
||||
/datum/song/stationary
|
||||
|
||||
/datum/song/stationary/updateDialog(mob/user)
|
||||
parent.interact(user || usr)
|
||||
|
||||
/datum/song/stationary/should_stop_playing(mob/user)
|
||||
. = ..()
|
||||
if(.)
|
||||
return TRUE
|
||||
var/obj/structure/musician/M = parent
|
||||
return M.should_stop_playing(user)
|
||||
251
code/modules/instruments/songs/editor.dm
Normal file
@@ -0,0 +1,251 @@
|
||||
/**
|
||||
* Returns the HTML for the status UI for this song datum.
|
||||
*/
|
||||
/datum/song/proc/instrument_status_ui()
|
||||
. = list()
|
||||
. += "<div class='statusDisplay'>"
|
||||
. += "<b><a href='?src=[REF(src)];switchinstrument=1'>Current instrument</a>:</b> "
|
||||
if(!using_instrument)
|
||||
. += "<span class='danger'>No instrument loaded!</span><br>"
|
||||
else
|
||||
. += "[using_instrument.name]<br>"
|
||||
. += "Playback Settings:<br>"
|
||||
if(can_noteshift)
|
||||
. += "<a href='?src=[REF(src)];setnoteshift=1'>Note Shift/Note Transpose</a>: [note_shift] keys / [round(note_shift / 12, 0.01)] octaves<br>"
|
||||
var/smt
|
||||
var/modetext = ""
|
||||
switch(sustain_mode)
|
||||
if(SUSTAIN_LINEAR)
|
||||
smt = "Linear"
|
||||
modetext = "<a href='?src=[REF(src)];setlinearfalloff=1'>Linear Sustain Duration</a>: [sustain_linear_duration / 10] seconds<br>"
|
||||
if(SUSTAIN_EXPONENTIAL)
|
||||
smt = "Exponential"
|
||||
modetext = "<a href='?src=[REF(src)];setexpfalloff=1'>Exponential Falloff Factor</a>: [sustain_exponential_dropoff]% per decisecond<br>"
|
||||
. += "<a href='?src=[REF(src)];setsustainmode=1'>Sustain Mode</a>: [smt]<br>"
|
||||
. += modetext
|
||||
. += using_instrument?.ready()? "Status: <span class='good'>Ready</span><br>" : "Status: <span class='bad'>!Instrument Definition Error!</span><br>"
|
||||
. += "Instrument Type: [legacy? "Legacy" : "Synthesized"]<br>"
|
||||
. += "<a href='?src=[REF(src)];setvolume=1'>Volume</a>: [volume]<br>"
|
||||
. += "<a href='?src=[REF(src)];setdropoffvolume=1'>Volume Dropoff Threshold</a>: [sustain_dropoff_volume]<br>"
|
||||
. += "<a href='?src=[REF(src)];togglesustainhold=1'>Sustain indefinitely last held note</a>: [full_sustain_held_note? "Enabled" : "Disabled"].<br>"
|
||||
. += "</div>"
|
||||
|
||||
/datum/song/proc/interact(mob/user)
|
||||
var/list/dat = list()
|
||||
|
||||
dat += instrument_status_ui()
|
||||
|
||||
if(lines.len > 0)
|
||||
dat += "<H3>Playback</H3>"
|
||||
if(!playing)
|
||||
dat += "<A href='?src=[REF(src)];play=1'>Play</A> <SPAN CLASS='linkOn'>Stop</SPAN><BR><BR>"
|
||||
dat += "Repeat Song: "
|
||||
dat += repeat > 0 ? "<A href='?src=[REF(src)];repeat=-10'>-</A><A href='?src=[REF(src)];repeat=-1'>-</A>" : "<SPAN CLASS='linkOff'>-</SPAN><SPAN CLASS='linkOff'>-</SPAN>"
|
||||
dat += " [repeat] times "
|
||||
dat += repeat < max_repeats ? "<A href='?src=[REF(src)];repeat=1'>+</A><A href='?src=[REF(src)];repeat=10'>+</A>" : "<SPAN CLASS='linkOff'>+</SPAN><SPAN CLASS='linkOff'>+</SPAN>"
|
||||
dat += "<BR>"
|
||||
else
|
||||
dat += "<SPAN CLASS='linkOn'>Play</SPAN> <A href='?src=[REF(src)];stop=1'>Stop</A><BR>"
|
||||
dat += "Repeats left: <B>[repeat]</B><BR>"
|
||||
if(!editing)
|
||||
dat += "<BR><B><A href='?src=[REF(src)];edit=2'>Show Editor</A></B><BR>"
|
||||
else
|
||||
dat += "<H3>Editing</H3>"
|
||||
dat += "<B><A href='?src=[REF(src)];edit=1'>Hide Editor</A></B>"
|
||||
dat += " <A href='?src=[REF(src)];newsong=1'>Start a New Song</A>"
|
||||
dat += " <A href='?src=[REF(src)];import=1'>Import a Song</A><BR><BR>"
|
||||
var/bpm = round(600 / tempo)
|
||||
dat += "Tempo: <A href='?src=[REF(src)];tempo=[world.tick_lag]'>-</A> [bpm] BPM <A href='?src=[REF(src)];tempo=-[world.tick_lag]'>+</A><BR><BR>"
|
||||
var/linecount = 0
|
||||
for(var/line in lines)
|
||||
linecount += 1
|
||||
dat += "Line [linecount]: <A href='?src=[REF(src)];modifyline=[linecount]'>Edit</A> <A href='?src=[REF(src)];deleteline=[linecount]'>X</A> [line]<BR>"
|
||||
dat += "<A href='?src=[REF(src)];newline=1'>Add Line</A><BR><BR>"
|
||||
if(help)
|
||||
dat += "<B><A href='?src=[REF(src)];help=1'>Hide Help</A></B><BR>"
|
||||
dat += {"
|
||||
Lines are a series of chords, separated by commas (,), each with notes separated by hyphens (-).<br>
|
||||
Every note in a chord will play together, with chord timed by the tempo.<br>
|
||||
<br>
|
||||
Notes are played by the names of the note, and optionally, the accidental, and/or the octave number.<br>
|
||||
By default, every note is natural and in octave 3. Defining otherwise is remembered for each note.<br>
|
||||
Example: <i>C,D,E,F,G,A,B</i> will play a C major scale.<br>
|
||||
After a note has an accidental placed, it will be remembered: <i>C,C4,C,C3</i> is <i>C3,C4,C4,C3</i><br>
|
||||
Chords can be played simply by seperating each note with a hyphon: <i>A-C#,Cn-E,E-G#,Gn-B</i><br>
|
||||
A pause may be denoted by an empty chord: <i>C,E,,C,G</i><br>
|
||||
To make a chord be a different time, end it with /x, where the chord length will be length<br>
|
||||
defined by tempo / x: <i>C,G/2,E/4</i><br>
|
||||
Combined, an example is: <i>E-E4/4,F#/2,G#/8,B/8,E3-E4/4</i>
|
||||
<br>
|
||||
Lines may be up to [MUSIC_MAXLINECHARS] characters.<br>
|
||||
A song may only contain up to [MUSIC_MAXLINES] lines.<br>
|
||||
"}
|
||||
else
|
||||
dat += "<B><A href='?src=[REF(src)];help=2'>Show Help</A></B><BR>"
|
||||
|
||||
var/datum/browser/popup = new(user, "instrument", parent?.name || "instrument", 700, 500)
|
||||
popup.set_content(dat.Join(""))
|
||||
popup.open()
|
||||
|
||||
/**
|
||||
* Parses a song the user has input into lines and stores them.
|
||||
*/
|
||||
/datum/song/proc/ParseSong(text)
|
||||
set waitfor = FALSE
|
||||
//split into lines
|
||||
lines = splittext(text, "\n")
|
||||
if(lines.len)
|
||||
var/bpm_string = "BPM: "
|
||||
if(findtext(lines[1], bpm_string, 1, length(bpm_string) + 1))
|
||||
var/divisor = text2num(copytext(lines[1], length(bpm_string) + 1)) || 120 // default
|
||||
tempo = sanitize_tempo(600 / round(divisor, 1))
|
||||
lines.Cut(1, 2)
|
||||
else
|
||||
tempo = sanitize_tempo(5) // default 120 BPM
|
||||
if(lines.len > MUSIC_MAXLINES)
|
||||
to_chat(usr, "Too many lines!")
|
||||
lines.Cut(MUSIC_MAXLINES + 1)
|
||||
var/linenum = 1
|
||||
for(var/l in lines)
|
||||
if(length_char(l) > MUSIC_MAXLINECHARS)
|
||||
to_chat(usr, "Line [linenum] too long!")
|
||||
lines.Remove(l)
|
||||
else
|
||||
linenum++
|
||||
updateDialog(usr) // make sure updates when complete
|
||||
|
||||
/datum/song/Topic(href, href_list)
|
||||
if(!parent.CanUseTopic(usr))
|
||||
usr << browse(null, "window=instrument")
|
||||
usr.unset_machine()
|
||||
return
|
||||
|
||||
parent.add_fingerprint(usr)
|
||||
|
||||
if(href_list["newsong"])
|
||||
lines = new()
|
||||
tempo = sanitize_tempo(5) // default 120 BPM
|
||||
name = ""
|
||||
|
||||
else if(href_list["import"])
|
||||
var/t = ""
|
||||
do
|
||||
t = html_encode(input(usr, "Please paste the entire song, formatted:", text("[]", name), t) as message)
|
||||
if(!in_range(parent, usr))
|
||||
return
|
||||
|
||||
if(length_char(t) >= MUSIC_MAXLINES * MUSIC_MAXLINECHARS)
|
||||
var/cont = input(usr, "Your message is too long! Would you like to continue editing it?", "", "yes") in list("yes", "no")
|
||||
if(cont == "no")
|
||||
break
|
||||
while(length_char(t) > MUSIC_MAXLINES * MUSIC_MAXLINECHARS)
|
||||
ParseSong(t)
|
||||
|
||||
else if(href_list["help"])
|
||||
help = text2num(href_list["help"]) - 1
|
||||
|
||||
else if(href_list["edit"])
|
||||
editing = text2num(href_list["edit"]) - 1
|
||||
|
||||
if(href_list["repeat"]) //Changing this from a toggle to a number of repeats to avoid infinite loops.
|
||||
if(playing)
|
||||
return //So that people cant keep adding to repeat. If the do it intentionally, it could result in the server crashing.
|
||||
repeat += round(text2num(href_list["repeat"]))
|
||||
if(repeat < 0)
|
||||
repeat = 0
|
||||
if(repeat > max_repeats)
|
||||
repeat = max_repeats
|
||||
|
||||
else if(href_list["tempo"])
|
||||
tempo = sanitize_tempo(tempo + text2num(href_list["tempo"]))
|
||||
|
||||
else if(href_list["play"])
|
||||
INVOKE_ASYNC(src, .proc/start_playing, usr)
|
||||
|
||||
else if(href_list["newline"])
|
||||
var/newline = html_encode(input("Enter your line: ", parent.name) as text|null)
|
||||
if(!newline || !in_range(parent, usr))
|
||||
return
|
||||
if(lines.len > MUSIC_MAXLINES)
|
||||
return
|
||||
if(length(newline) > MUSIC_MAXLINECHARS)
|
||||
newline = copytext(newline, 1, MUSIC_MAXLINECHARS)
|
||||
lines.Add(newline)
|
||||
|
||||
else if(href_list["deleteline"])
|
||||
var/num = round(text2num(href_list["deleteline"]))
|
||||
if(num > lines.len || num < 1)
|
||||
return
|
||||
lines.Cut(num, num+1)
|
||||
|
||||
else if(href_list["modifyline"])
|
||||
var/num = round(text2num(href_list["modifyline"]),1)
|
||||
var/content = stripped_input(usr, "Enter your line: ", parent.name, lines[num], MUSIC_MAXLINECHARS)
|
||||
if(!content || !in_range(parent, usr))
|
||||
return
|
||||
if(num > lines.len || num < 1)
|
||||
return
|
||||
lines[num] = content
|
||||
|
||||
else if(href_list["stop"])
|
||||
stop_playing()
|
||||
|
||||
else if(href_list["setlinearfalloff"])
|
||||
var/amount = input(usr, "Set linear sustain duration in seconds", "Linear Sustain Duration") as null|num
|
||||
if(!isnull(amount))
|
||||
set_linear_falloff_duration(round(amount * 10, world.tick_lag))
|
||||
|
||||
else if(href_list["setexpfalloff"])
|
||||
var/amount = input(usr, "Set exponential sustain factor", "Exponential sustain factor") as null|num
|
||||
if(!isnull(amount))
|
||||
set_exponential_drop_rate(round(amount, 0.00001))
|
||||
|
||||
else if(href_list["setvolume"])
|
||||
var/amount = input(usr, "Set volume", "Volume") as null|num
|
||||
if(!isnull(amount))
|
||||
set_volume(round(amount, 1))
|
||||
|
||||
else if(href_list["setdropoffvolume"])
|
||||
var/amount = input(usr, "Set dropoff threshold", "Dropoff Threshold Volume") as null|num
|
||||
if(!isnull(amount))
|
||||
set_dropoff_volume(round(amount, 0.01))
|
||||
|
||||
else if(href_list["switchinstrument"])
|
||||
if(!length(allowed_instrument_ids))
|
||||
return
|
||||
else if(length(allowed_instrument_ids) == 1)
|
||||
set_instrument(allowed_instrument_ids[1])
|
||||
return
|
||||
var/list/categories = list()
|
||||
for(var/i in allowed_instrument_ids)
|
||||
var/datum/instrument/I = SSinstruments.get_instrument(i)
|
||||
if(I)
|
||||
LAZYSET(categories[I.category || "ERROR CATEGORY"], I.name, I.id)
|
||||
var/cat = input(usr, "Select Category", "Instrument Category") as null|anything in categories
|
||||
if(!cat)
|
||||
return
|
||||
var/list/instruments = categories[cat]
|
||||
var/choice = input(usr, "Select Instrument", "Instrument Selection") as null|anything in instruments
|
||||
if(!choice)
|
||||
return
|
||||
choice = instruments[choice] //get id
|
||||
if(choice)
|
||||
set_instrument(choice)
|
||||
|
||||
else if(href_list["setnoteshift"])
|
||||
var/amount = input(usr, "Set note shift", "Note Shift") as null|num
|
||||
if(!isnull(amount))
|
||||
note_shift = clamp(amount, note_shift_min, note_shift_max)
|
||||
|
||||
else if(href_list["setsustainmode"])
|
||||
var/choice = input(usr, "Choose a sustain mode", "Sustain Mode") as null|anything in list("Linear", "Exponential")
|
||||
switch(choice)
|
||||
if("Linear")
|
||||
sustain_mode = SUSTAIN_LINEAR
|
||||
if("Exponential")
|
||||
sustain_mode = SUSTAIN_EXPONENTIAL
|
||||
|
||||
else if(href_list["togglesustainhold"])
|
||||
full_sustain_held_note = !full_sustain_held_note
|
||||
|
||||
updateDialog()
|
||||
91
code/modules/instruments/songs/play_legacy.dm
Normal file
@@ -0,0 +1,91 @@
|
||||
/**
|
||||
* Compiles our lines into "chords" with filenames for legacy playback. This makes there have to be a bit of lag at the beginning of the song, but repeats will not have to parse it again, and overall playback won't be impacted by as much lag.
|
||||
*/
|
||||
/datum/song/proc/compile_legacy()
|
||||
if(!length(src.lines))
|
||||
return
|
||||
var/list/lines = src.lines //cache for hyepr speed!
|
||||
compiled_chords = list()
|
||||
var/list/octaves = list(3, 3, 3, 3, 3, 3, 3)
|
||||
var/list/accents = list("n", "n", "n", "n", "n", "n", "n")
|
||||
for(var/line in lines)
|
||||
var/list/chords = splittext(lowertext(line), ",")
|
||||
for(var/chord in chords)
|
||||
var/list/compiled_chord = list()
|
||||
var/tempodiv = 1
|
||||
var/list/notes_tempodiv = splittext(chord, "/")
|
||||
var/len = length(notes_tempodiv)
|
||||
if(len >= 2)
|
||||
tempodiv = text2num(notes_tempodiv[2])
|
||||
if(len) //some dunkass is going to do ,,,, to make 3 rests instead of ,/1 because there's no standardization so let's be prepared for that.
|
||||
var/list/notes = splittext(notes_tempodiv[1], "-")
|
||||
for(var/note in notes)
|
||||
if(length(note) == 0)
|
||||
continue
|
||||
// 1-7, A-G
|
||||
var/key = text2ascii(note) - 96
|
||||
if((key < 1) || (key > 7))
|
||||
continue
|
||||
for(var/i in 2 to length(note))
|
||||
var/oct_acc = copytext(note, i, i + 1)
|
||||
var/num = text2num(oct_acc)
|
||||
if(!num) //it's an accidental
|
||||
accents[key] = oct_acc //if they misspelled it/fucked up that's on them lmao, no safety checks.
|
||||
else //octave
|
||||
octaves[key] = clamp(num, octave_min, octave_max)
|
||||
compiled_chord[++compiled_chord.len] = list(key, accents[key], octaves[key])
|
||||
compiled_chord += tempodiv //this goes last
|
||||
if(length(compiled_chord))
|
||||
compiled_chords[++compiled_chords.len] = compiled_chord
|
||||
|
||||
/**
|
||||
* Proc to play a legacy note. Just plays the sound to hearing mobs (and does hearcheck if necessary), no fancy channel/sustain/management.
|
||||
*
|
||||
* Arguments:
|
||||
* * note is a number from 1-7 for A-G
|
||||
* * acc is either "b", "n", or "#"
|
||||
* * oct is 1-8 (or 9 for C)
|
||||
*/
|
||||
/datum/song/proc/playkey_legacy(note, acc as text, oct, mob/user)
|
||||
// handle accidental -> B<>C of E<>F
|
||||
if(acc == "b" && (note == 3 || note == 6)) // C or F
|
||||
if(note == 3)
|
||||
oct--
|
||||
note--
|
||||
acc = "n"
|
||||
else if(acc == "#" && (note == 2 || note == 5)) // B or E
|
||||
if(note == 2)
|
||||
oct++
|
||||
note++
|
||||
acc = "n"
|
||||
else if(acc == "#" && (note == 7)) //G#
|
||||
note = 1
|
||||
acc = "b"
|
||||
else if(acc == "#") // mass convert all sharps to flats, octave jump already handled
|
||||
acc = "b"
|
||||
note++
|
||||
|
||||
// check octave, C is allowed to go to 9
|
||||
if(oct < 1 || (note == 3 ? oct > 9 : oct > 8))
|
||||
return
|
||||
|
||||
// now generate name
|
||||
var/soundfile = "sound/instruments/[cached_legacy_dir]/[ascii2text(note+64)][acc][oct].[cached_legacy_ext]"
|
||||
soundfile = file(soundfile)
|
||||
// make sure the note exists
|
||||
if(!fexists(soundfile))
|
||||
return
|
||||
// and play
|
||||
var/turf/source = get_turf(parent)
|
||||
if((world.time - MUSICIAN_HEARCHECK_MINDELAY) > last_hearcheck)
|
||||
do_hearcheck()
|
||||
var/sound/music_played = sound(soundfile)
|
||||
for(var/i in hearing_mobs)
|
||||
var/mob/M = i
|
||||
/* Would be nice
|
||||
if(user && HAS_TRAIT(user, TRAIT_MUSICIAN) && isliving(M))
|
||||
var/mob/living/L = M
|
||||
L.apply_status_effect(STATUS_EFFECT_GOOD_MUSIC)
|
||||
*/
|
||||
M.playsound_local(source, null, volume * using_instrument.volume_multiplier, S = music_played, preference = /datum/client_preference/instrument_toggle, volume_channel = VOLUME_CHANNEL_INSTRUMENTS)
|
||||
// Could do environment and echo later but not for now
|
||||
150
code/modules/instruments/songs/play_synthesized.dm
Normal file
@@ -0,0 +1,150 @@
|
||||
/**
|
||||
* Compiles our lines into "chords" with numbers. This makes there have to be a bit of lag at the beginning of the song, but repeats will not have to parse it again, and overall playback won't be impacted by as much lag.
|
||||
*/
|
||||
/datum/song/proc/compile_synthesized()
|
||||
if(!length(src.lines))
|
||||
return
|
||||
var/list/lines = src.lines //cache for hyepr speed!
|
||||
compiled_chords = list()
|
||||
var/list/octaves = list(3, 3, 3, 3, 3, 3, 3)
|
||||
var/list/accents = list("n", "n", "n", "n", "n", "n", "n")
|
||||
for(var/line in lines)
|
||||
var/list/chords = splittext(lowertext(line), ",")
|
||||
for(var/chord in chords)
|
||||
var/list/compiled_chord = list()
|
||||
var/tempodiv = 1
|
||||
var/list/notes_tempodiv = splittext(chord, "/")
|
||||
var/len = length(notes_tempodiv)
|
||||
if(len >= 2)
|
||||
tempodiv = text2num(notes_tempodiv[2])
|
||||
if(len) //some dunkass is going to do ,,,, to make 3 rests instead of ,/1 because there's no standardization so let's be prepared for that.
|
||||
var/list/notes = splittext(notes_tempodiv[1], "-")
|
||||
for(var/note in notes)
|
||||
if(length(note) == 0)
|
||||
continue
|
||||
// 1-7, A-G
|
||||
var/key = text2ascii(note) - 96
|
||||
if((key < 1) || (key > 7))
|
||||
continue
|
||||
for(var/i in 2 to length(note))
|
||||
var/oct_acc = copytext(note, i, i + 1)
|
||||
var/num = text2num(oct_acc)
|
||||
if(!num) //it's an accidental
|
||||
accents[key] = oct_acc //if they misspelled it/fucked up that's on them lmao, no safety checks.
|
||||
else //octave
|
||||
octaves[key] = clamp(num, octave_min, octave_max)
|
||||
compiled_chord += clamp((note_offset_lookup[key] + octaves[key] * 12 + accent_lookup[accents[key]]), key_min, key_max)
|
||||
compiled_chord += tempodiv //this goes last
|
||||
if(length(compiled_chord))
|
||||
compiled_chords[++compiled_chords.len] = compiled_chord
|
||||
|
||||
/**
|
||||
* Plays a specific numerical key from our instrument to anyone who can hear us.
|
||||
* Does a hearing check if enough time has passed.
|
||||
*/
|
||||
/datum/song/proc/playkey_synth(key, mob/user)
|
||||
if(can_noteshift)
|
||||
key = clamp(key + note_shift, key_min, key_max)
|
||||
if((world.time - MUSICIAN_HEARCHECK_MINDELAY) > last_hearcheck)
|
||||
do_hearcheck()
|
||||
var/datum/instrument_key/K = using_instrument.samples[num2text(key)] //See how fucking easy it is to make a number text? You don't need a complicated 9 line proc!
|
||||
//Should probably add channel limiters here at some point but I don't care right now.
|
||||
var/channel = pop_channel()
|
||||
if(isnull(channel))
|
||||
return FALSE
|
||||
. = TRUE
|
||||
var/sound/copy = sound(K.sample)
|
||||
var/volume = src.volume * using_instrument.volume_multiplier
|
||||
copy.frequency = K.frequency
|
||||
copy.volume = volume
|
||||
var/channel_text = num2text(channel)
|
||||
channels_playing[channel_text] = 100
|
||||
last_channel_played = channel_text
|
||||
for(var/i in hearing_mobs)
|
||||
var/mob/M = i
|
||||
/* Maybe someday
|
||||
if(user && HAS_TRAIT(user, TRAIT_MUSICIAN) && isliving(M))
|
||||
var/mob/living/L = M
|
||||
L.apply_status_effect(STATUS_EFFECT_GOOD_MUSIC)
|
||||
*/
|
||||
// Jeez
|
||||
M.playsound_local(
|
||||
turf_source = get_turf(parent),
|
||||
soundin = null,
|
||||
vol = volume,
|
||||
vary = FALSE,
|
||||
frequency = K.frequency,
|
||||
falloff = null,
|
||||
is_global = null,
|
||||
channel = channel,
|
||||
pressure_affected = null,
|
||||
S = copy,
|
||||
preference = /datum/client_preference/instrument_toggle,
|
||||
volume_channel = VOLUME_CHANNEL_INSTRUMENTS)
|
||||
// Could do environment and echo later but not for now
|
||||
|
||||
/**
|
||||
* Stops all sounds we are "responsible" for. Only works in synthesized mode.
|
||||
*/
|
||||
/datum/song/proc/terminate_all_sounds(clear_channels = TRUE)
|
||||
for(var/i in hearing_mobs)
|
||||
terminate_sound_mob(i)
|
||||
if(clear_channels)
|
||||
channels_playing.len = 0
|
||||
channels_idle.len = 0
|
||||
SSinstruments.current_instrument_channels -= using_sound_channels
|
||||
using_sound_channels = 0
|
||||
SSsounds.free_datum_channels(src)
|
||||
|
||||
/**
|
||||
* Stops all sounds we are responsible for in a given person. Only works in synthesized mode.
|
||||
*/
|
||||
/datum/song/proc/terminate_sound_mob(mob/M)
|
||||
for(var/channel in channels_playing)
|
||||
M.stop_sound_channel(text2num(channel))
|
||||
|
||||
/**
|
||||
* Pops a channel we have reserved so we don't have to release and re-request them from SSsounds every time we play a note. This is faster.
|
||||
*/
|
||||
/datum/song/proc/pop_channel()
|
||||
if(length(channels_idle)) //just pop one off of here if we have one available
|
||||
. = text2num(channels_idle[1])
|
||||
channels_idle.Cut(1,2)
|
||||
return
|
||||
if(using_sound_channels >= max_sound_channels)
|
||||
return
|
||||
. = SSinstruments.reserve_instrument_channel(src)
|
||||
if(!isnull(.))
|
||||
using_sound_channels++
|
||||
|
||||
/**
|
||||
* Decays our channels and updates their volumes to mobs who can hear us.
|
||||
*
|
||||
* Arguments:
|
||||
* * wait_ds - the deciseconds we should decay by. This is to compensate for any lag, as otherwise songs would get pretty nasty during high time dilation.
|
||||
*/
|
||||
/datum/song/proc/process_decay(wait_ds)
|
||||
var/linear_dropoff = cached_linear_dropoff * wait_ds
|
||||
var/exponential_dropoff = cached_exponential_dropoff ** wait_ds
|
||||
for(var/channel in channels_playing)
|
||||
if(full_sustain_held_note && (channel == last_channel_played))
|
||||
continue
|
||||
var/current_volume = channels_playing[channel]
|
||||
switch(sustain_mode)
|
||||
if(SUSTAIN_LINEAR)
|
||||
current_volume -= linear_dropoff
|
||||
if(SUSTAIN_EXPONENTIAL)
|
||||
current_volume /= exponential_dropoff
|
||||
channels_playing[channel] = current_volume
|
||||
var/dead = current_volume <= sustain_dropoff_volume
|
||||
var/channelnumber = text2num(channel)
|
||||
if(dead)
|
||||
channels_playing -= channel
|
||||
channels_idle += channel
|
||||
for(var/i in hearing_mobs)
|
||||
var/mob/M = i
|
||||
M.stop_sound_channel(channelnumber)
|
||||
else
|
||||
for(var/i in hearing_mobs)
|
||||
var/mob/M = i
|
||||
M.set_sound_channel_volume(channelnumber, (current_volume * 0.01) * volume * using_instrument.volume_multiplier)
|
||||
59
code/modules/instruments/stationary.dm
Normal file
@@ -0,0 +1,59 @@
|
||||
/obj/structure/musician
|
||||
name = "Not A Piano"
|
||||
desc = "Something broke, contact coderbus."
|
||||
var/can_play_unanchored = FALSE
|
||||
var/list/allowed_instrument_ids = list("r3grand","r3harpsi","crharpsi","crgrand1","crbright1", "crichugan", "crihamgan","piano")
|
||||
var/datum/song/song
|
||||
|
||||
/obj/structure/musician/Initialize(mapload)
|
||||
. = ..()
|
||||
song = new(src, allowed_instrument_ids)
|
||||
allowed_instrument_ids = null
|
||||
|
||||
/obj/structure/musician/Destroy()
|
||||
QDEL_NULL(song)
|
||||
return ..()
|
||||
|
||||
/obj/structure/musician/attack_hand(mob/M)
|
||||
if(!M.IsAdvancedToolUser())
|
||||
return
|
||||
|
||||
interact(M)
|
||||
|
||||
/obj/structure/musician/proc/should_stop_playing(mob/user)
|
||||
if(!(anchored || can_play_unanchored))
|
||||
return TRUE
|
||||
if(!user)
|
||||
return FALSE
|
||||
return !CanUseTopic(user)
|
||||
|
||||
/obj/structure/musician/interact(mob/user)
|
||||
. = ..()
|
||||
song.interact(user)
|
||||
|
||||
/*
|
||||
/obj/structure/musician/wrench_act(mob/living/user, obj/item/I)
|
||||
default_unfasten_wrench(user, I, 40)
|
||||
return TRUE
|
||||
*/
|
||||
|
||||
/obj/structure/musician/piano
|
||||
name = "space minimoog"
|
||||
icon = 'icons/obj/musician.dmi'
|
||||
icon_state = "minimoog"
|
||||
anchored = TRUE
|
||||
density = TRUE
|
||||
|
||||
/obj/structure/musician/piano/unanchored
|
||||
anchored = FALSE
|
||||
|
||||
/obj/structure/musician/piano/Initialize(mapload)
|
||||
. = ..()
|
||||
if(prob(50) && icon_state == initial(icon_state))
|
||||
name = "space minimoog"
|
||||
desc = "This is a minimoog, like a space piano, but more spacey!"
|
||||
icon_state = "minimoog"
|
||||
else
|
||||
name = "space piano"
|
||||
desc = "This is a space piano, like a regular piano, but always in tune! Even if the musician isn't."
|
||||
icon_state = "piano"
|
||||
@@ -1059,15 +1059,15 @@
|
||||
|
||||
/mob/living/get_sound_env(var/pressure_factor)
|
||||
if (hallucination)
|
||||
return PSYCHOTIC
|
||||
return SOUND_ENVIRONMENT_PSYCHOTIC
|
||||
else if (druggy)
|
||||
return DRUGGED
|
||||
return SOUND_ENVIRONMENT_DRUGGED
|
||||
else if (drowsyness)
|
||||
return DIZZY
|
||||
return SOUND_ENVIRONMENT_DIZZY
|
||||
else if (confused)
|
||||
return DIZZY
|
||||
return SOUND_ENVIRONMENT_DIZZY
|
||||
else if (sleeping)
|
||||
return UNDERWATER
|
||||
return SOUND_ENVIRONMENT_UNDERWATER
|
||||
else
|
||||
return ..()
|
||||
|
||||
|
||||
@@ -189,9 +189,7 @@ GLOBAL_LIST_EMPTY(apcs)
|
||||
|
||||
pixel_x = (dir & 3)? 0 : (dir == 4 ? 26 : -26) //VOREStation Edit -> 24 to 26
|
||||
pixel_y = (dir & 3)? (dir ==1 ? 26 : -26) : 0 //VOREStation Edit -> 24 to 26
|
||||
if(building==0)
|
||||
init()
|
||||
else
|
||||
if(building)
|
||||
area = get_area(src)
|
||||
area.apc = src
|
||||
opened = 1
|
||||
@@ -200,6 +198,16 @@ GLOBAL_LIST_EMPTY(apcs)
|
||||
stat |= MAINT
|
||||
update_icon()
|
||||
|
||||
/obj/machinery/power/apc/Initialize(mapload, ndir, building)
|
||||
. = ..()
|
||||
if(!building)
|
||||
init()
|
||||
return INITIALIZE_HINT_LATELOAD
|
||||
|
||||
/obj/machinery/power/apc/LateInitialize()
|
||||
. = ..()
|
||||
update()
|
||||
|
||||
/obj/machinery/power/apc/Destroy()
|
||||
GLOB.apcs -= src
|
||||
update()
|
||||
@@ -268,9 +276,6 @@ GLOBAL_LIST_EMPTY(apcs)
|
||||
|
||||
make_terminal()
|
||||
|
||||
spawn(5)
|
||||
update()
|
||||
|
||||
/obj/machinery/power/apc/examine(mob/user)
|
||||
. = ..()
|
||||
if(Adjacent(user))
|
||||
|
||||
@@ -288,8 +288,8 @@ var/global/list/light_type_cache = list()
|
||||
shows_alerts = FALSE //VOREStation Edit
|
||||
var/lamp_shade = 1
|
||||
|
||||
/obj/machinery/light/flamp/New(atom/newloc, obj/machinery/light_construct/construct = null)
|
||||
..(newloc, construct)
|
||||
/obj/machinery/light/flamp/Initialize(mapload, obj/machinery/light_construct/construct = null)
|
||||
. = ..()
|
||||
if(construct)
|
||||
start_with_cell = FALSE
|
||||
lamp_shade = 0
|
||||
@@ -322,15 +322,13 @@ var/global/list/light_type_cache = list()
|
||||
auto_flicker = TRUE
|
||||
|
||||
//VOREStation Add - Shadeless!
|
||||
/obj/machinery/light/flamp/noshade/New()
|
||||
/obj/machinery/light/flamp/noshade
|
||||
lamp_shade = 0
|
||||
update(0)
|
||||
..()
|
||||
//VOREStation Add End
|
||||
|
||||
// create a new lighting fixture
|
||||
/obj/machinery/light/New(atom/newloc, obj/machinery/light_construct/construct = null)
|
||||
..(newloc)
|
||||
/obj/machinery/light/Initialize(mapload, obj/machinery/light_construct/construct = null)
|
||||
. =..()
|
||||
|
||||
if(start_with_cell && !no_emergency)
|
||||
cell = new/obj/item/weapon/cell/emergency_light(src)
|
||||
|
||||
|
Before Width: | Height: | Size: 232 KiB After Width: | Height: | Size: 234 KiB |
BIN
icons/mob/items/lefthand_horns.dmi
Normal file
|
After Width: | Height: | Size: 470 B |
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 10 KiB |
BIN
icons/mob/items/righthand_horns.dmi
Normal file
|
After Width: | Height: | Size: 472 B |
|
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 4.5 KiB After Width: | Height: | Size: 32 KiB |
@@ -1317,7 +1317,7 @@
|
||||
"mQJ" = (/obj/item/toy/chess/bishop_white{color = "teal"},/turf/simulated/floor/holofloor/wmarble,/area/holodeck/source_chess)
|
||||
"mRE" = (/obj/structure/urinal{pixel_y = 32},/obj/structure/window/reinforced/tinted{dir = 8},/turf/unsimulated/floor{icon_state = "freezerfloor"},/area/centcom/specops)
|
||||
"mRN" = (/obj/machinery/chemical_dispenser/full,/obj/item/weapon/reagent_containers/glass/beaker/large,/obj/structure/table/reinforced,/turf/unsimulated/floor{dir = 5; icon_state = "vault"},/area/centcom/specops)
|
||||
"mSB" = (/obj/structure/device/piano{dir = 4},/obj/effect/floor_decal/corner/yellow/diagonal,/obj/effect/floor_decal/corner/blue/diagonal{dir = 4},/turf/unsimulated/floor{icon_state = "steel"},/area/centcom/living)
|
||||
"mSB" = (/obj/structure/musician/piano{dir = 4},/obj/effect/floor_decal/corner/yellow/diagonal,/obj/effect/floor_decal/corner/blue/diagonal{dir = 4},/turf/unsimulated/floor{icon_state = "steel"},/area/centcom/living)
|
||||
"mSP" = (/turf/simulated/shuttle/wall/dark{join_group = "shuttle_ert"},/area/shuttle/response_ship)
|
||||
"mSQ" = (/obj/machinery/door/window/holowindoor{base_state = "right"; dir = 2; icon_state = "right"; name = "Green Team"},/turf/simulated/floor/holofloor/tiled/dark,/area/holodeck/source_basketball)
|
||||
"mTk" = (/obj/machinery/portable_atmospherics/canister/oxygen/prechilled,/obj/machinery/atmospherics/portables_connector,/obj/effect/floor_decal/corner/paleblue{dir = 5},/turf/unsimulated/floor{icon_state = "white"},/area/centcom/medical)
|
||||
|
||||
@@ -96,7 +96,7 @@
|
||||
"YR" = (/obj/structure/cliff/automatic/corner{dir = 10},/obj/structure/loot_pile/maint/technical,/turf/simulated/floor/outdoors/rocks{outdoors = 0},/area/submap/Chasm)
|
||||
"YU" = (/obj/effect/floor_decal/rust,/obj/effect/floor_decal/rust/color_rustedcee,/obj/effect/decal/cleanable/dirt,/obj/random/humanoidremains,/turf/simulated/floor/tiled/asteroid_steel,/area/submap/Chasm)
|
||||
"Za" = (/obj/structure/cliff/automatic{dir = 10},/turf/simulated/floor/outdoors/rocks{outdoors = 0},/area/submap/Chasm)
|
||||
"Zd" = (/obj/item/device/instrument/trombone,/turf/simulated/floor/outdoors/rocks{outdoors = 0},/area/submap/Chasm)
|
||||
"Zd" = (/obj/item/instrument/trombone,/turf/simulated/floor/outdoors/rocks{outdoors = 0},/area/submap/Chasm)
|
||||
"Zn" = (/mob/living/simple_mob/animal/space/jelly{health = 150; maxHealth = 150; name = "Cavern jelly blob"},/obj/item/weapon/bone/skull,/turf/simulated/floor/outdoors/rocks{outdoors = 0},/area/submap/Chasm)
|
||||
"ZS" = (/mob/living/simple_mob/animal/space/jelly{health = 150; maxHealth = 150; name = "Cavern jelly blob"},/turf/simulated/floor/outdoors/rocks{outdoors = 0},/area/submap/Chasm)
|
||||
|
||||
|
||||
@@ -20880,10 +20880,10 @@
|
||||
/turf/simulated/floor/tiled/white,
|
||||
/area/crew_quarters/kitchen)
|
||||
"aHz" = (
|
||||
/obj/structure/device/piano,
|
||||
/obj/effect/floor_decal/spline/plain{
|
||||
dir = 1
|
||||
},
|
||||
/obj/structure/musician/piano,
|
||||
/turf/simulated/floor/lino,
|
||||
/area/tether/surfacebase/entertainment/stage)
|
||||
"aHA" = (
|
||||
@@ -32556,10 +32556,12 @@
|
||||
dir = 4
|
||||
},
|
||||
/obj/structure/table/rack/steel,
|
||||
/obj/item/device/instrument/violin,
|
||||
/obj/effect/floor_decal/spline/plain{
|
||||
dir = 1
|
||||
},
|
||||
/obj/item/instrument/violin,
|
||||
/obj/item/instrument/piano_synth,
|
||||
/obj/item/instrument/recorder,
|
||||
/turf/simulated/floor/lino,
|
||||
/area/tether/surfacebase/entertainment/stage)
|
||||
"bcW" = (
|
||||
|
||||
@@ -142,7 +142,7 @@
|
||||
|
||||
/area/tether/surfacebase/outside
|
||||
name = "Outside - Surface"
|
||||
sound_env = MOUNTAINS
|
||||
sound_env = SOUND_ENVIRONMENT_MOUNTAINS
|
||||
/area/tether/surfacebase/outside/outside1
|
||||
icon_state = "outside1"
|
||||
/area/tether/surfacebase/outside/outside2
|
||||
@@ -541,7 +541,7 @@
|
||||
/area/tether/surfacebase/security/solitary
|
||||
name = "\improper Surface Security Solitary Confinement"
|
||||
lightswitch = 0
|
||||
sound_env =PADDED_CELL
|
||||
sound_env = SOUND_ENVIRONMENT_PADDED_CELL
|
||||
/area/tether/surfacebase/security/gasstorage
|
||||
name = "\improper Surface Security Gas Storage"
|
||||
lightswitch = 0
|
||||
@@ -639,7 +639,7 @@
|
||||
/area/engineering/atmos_intake
|
||||
name = "\improper Atmospherics Intake"
|
||||
icon_state = "atmos"
|
||||
sound_env = MOUNTAINS
|
||||
sound_env = SOUND_ENVIRONMENT_MOUNTAINS
|
||||
|
||||
/area/engineering/atmos/hallway
|
||||
name = "\improper Atmospherics Main Hallway"
|
||||
@@ -1360,7 +1360,7 @@
|
||||
name = "\improper Virology Maintenance"
|
||||
/area/maintenance/station/ai
|
||||
name = "\improper AI Maintenance"
|
||||
sound_env = SEWER_PIPE
|
||||
sound_env = SOUND_ENVIRONMENT_SEWER_PIPE
|
||||
/area/maintenance/station/exploration
|
||||
name = "\improper Exploration Maintenance"
|
||||
/area/maintenance/abandonedlibrary
|
||||
@@ -1372,7 +1372,7 @@
|
||||
/area/maintenance/station/spacecommandmaint
|
||||
name = "\improper Secondary Command Maintenance"
|
||||
icon_state = "bridge"
|
||||
sound_env = SEWER_PIPE
|
||||
sound_env = SOUND_ENVIRONMENT_SEWER_PIPE
|
||||
/area/maintenance/substation/spacecommand
|
||||
name = "\improper Secondary Command Substation"
|
||||
icon_state = "substation"
|
||||
|
||||