Merge pull request #15634 from deathride58/jukeboxrefresh
Jukebox refresh - Jukebox queue, working volume knob, emagging, preliminary multiz support, and more!
This commit is contained in:
@@ -54656,6 +54656,7 @@
|
||||
/obj/structure/sign/poster/contraband/robust_softdrinks{
|
||||
pixel_y = 32
|
||||
},
|
||||
/obj/machinery/vending/snack/orange,
|
||||
/turf/open/floor/wood,
|
||||
/area/service/bar)
|
||||
"fZm" = (
|
||||
@@ -57444,10 +57445,10 @@
|
||||
/turf/open/floor/plasteel/dark,
|
||||
/area/security/prison/cells)
|
||||
"kgB" = (
|
||||
/obj/machinery/vending/snack/orange,
|
||||
/obj/machinery/light{
|
||||
dir = 1
|
||||
},
|
||||
/obj/machinery/jukebox,
|
||||
/turf/open/floor/wood,
|
||||
/area/service/bar)
|
||||
"khb" = (
|
||||
|
||||
@@ -5011,6 +5011,7 @@
|
||||
dir = 1
|
||||
},
|
||||
/obj/machinery/light,
|
||||
/obj/machinery/jukebox,
|
||||
/turf/open/floor/plasteel,
|
||||
/area/service/bar)
|
||||
"nb" = (
|
||||
|
||||
@@ -65631,6 +65631,14 @@
|
||||
},
|
||||
/turf/open/floor/plasteel/dark,
|
||||
/area/service/chapel/main)
|
||||
"mWS" = (
|
||||
/obj/effect/turf_decal/tile/bar,
|
||||
/obj/effect/turf_decal/tile/bar{
|
||||
dir = 1
|
||||
},
|
||||
/obj/machinery/jukebox,
|
||||
/turf/open/floor/plasteel,
|
||||
/area/service/bar)
|
||||
"mXi" = (
|
||||
/turf/open/floor/wood,
|
||||
/area/command/heads_quarters/captain/private)
|
||||
@@ -119802,7 +119810,7 @@ teZ
|
||||
teZ
|
||||
wbZ
|
||||
hCA
|
||||
msI
|
||||
mWS
|
||||
pzY
|
||||
kPZ
|
||||
upO
|
||||
|
||||
@@ -13604,14 +13604,7 @@
|
||||
/turf/open/floor/plasteel,
|
||||
/area/service/bar/atrium)
|
||||
"ayN" = (
|
||||
/obj/item/storage/fancy/cigarettes/cigars{
|
||||
pixel_y = 6
|
||||
},
|
||||
/obj/item/storage/fancy/cigarettes/cigars/cohiba{
|
||||
pixel_y = 3
|
||||
},
|
||||
/obj/item/storage/fancy/cigarettes/cigars/havana,
|
||||
/obj/structure/table/reinforced,
|
||||
/obj/machinery/jukebox,
|
||||
/turf/open/floor/plasteel/dark/corner{
|
||||
dir = 4
|
||||
},
|
||||
@@ -13622,6 +13615,13 @@
|
||||
pixel_x = 32
|
||||
},
|
||||
/obj/machinery/atmospherics/pipe/simple/scrubbers/hidden,
|
||||
/obj/item/storage/fancy/cigarettes/cigars/havana,
|
||||
/obj/item/storage/fancy/cigarettes/cigars/cohiba{
|
||||
pixel_y = 3
|
||||
},
|
||||
/obj/item/storage/fancy/cigarettes/cigars{
|
||||
pixel_y = 6
|
||||
},
|
||||
/turf/open/floor/plasteel/dark/side{
|
||||
dir = 1
|
||||
},
|
||||
@@ -84771,7 +84771,7 @@ awp
|
||||
awX
|
||||
axR
|
||||
ayN
|
||||
aue
|
||||
asg
|
||||
blj
|
||||
aCb
|
||||
aCe
|
||||
|
||||
@@ -42,6 +42,7 @@
|
||||
#define LOG_MECHA (1 << 17)
|
||||
#define LOG_SHUTTLE (1 << 18)
|
||||
#define LOG_VICTIM (1 << 19)
|
||||
#define LOG_ECON (1 << 20)
|
||||
|
||||
//Individual logging panel pages
|
||||
#define INDIVIDUAL_ATTACK_LOG (LOG_ATTACK | LOG_VICTIM)
|
||||
@@ -50,7 +51,7 @@
|
||||
#define INDIVIDUAL_COMMS_LOG (LOG_PDA | LOG_CHAT | LOG_COMMENT | LOG_TELECOMMS)
|
||||
#define INDIVIDUAL_OOC_LOG (LOG_OOC | LOG_ADMIN)
|
||||
#define INDIVIDUAL_OWNERSHIP_LOG (LOG_OWNERSHIP)
|
||||
#define INDIVIDUAL_SHOW_ALL_LOG (LOG_ATTACK | LOG_SAY | LOG_WHISPER | LOG_EMOTE | LOG_DSAY | LOG_PDA | LOG_CHAT | LOG_COMMENT | LOG_TELECOMMS | LOG_OOC | LOG_ADMIN | LOG_OWNERSHIP | LOG_GAME | LOG_VICTIM)
|
||||
#define INDIVIDUAL_SHOW_ALL_LOG (LOG_ATTACK | LOG_SAY | LOG_WHISPER | LOG_EMOTE | LOG_DSAY | LOG_PDA | LOG_CHAT | LOG_COMMENT | LOG_TELECOMMS | LOG_OOC | LOG_ADMIN | LOG_OWNERSHIP | LOG_GAME | LOG_VICTIM | LOG_ECON)
|
||||
|
||||
#define LOGSRC_CLIENT "Client"
|
||||
#define LOGSRC_MOB "Mob"
|
||||
|
||||
@@ -161,6 +161,10 @@
|
||||
if (CONFIG_GET(flag/log_telecomms))
|
||||
WRITE_LOG(GLOB.world_telecomms_log, "TCOMMS: [text]")
|
||||
|
||||
/proc/log_econ(text)
|
||||
if (CONFIG_GET(flag/log_econ))
|
||||
WRITE_LOG(GLOB.world_econ_log, "MONEY: [text]")
|
||||
|
||||
/proc/log_chat(text)
|
||||
if (CONFIG_GET(flag/log_pda))
|
||||
//same thing here
|
||||
|
||||
@@ -548,7 +548,7 @@
|
||||
parts += "There were [station_vault] credits collected by crew this shift.<br>"
|
||||
if(total_players > 0)
|
||||
parts += "An average of [station_vault/total_players] credits were collected.<br>"
|
||||
// log_econ("Roundend credit total: [station_vault] credits. Average Credits: [station_vault/total_players]")
|
||||
log_econ("Roundend credit total: [station_vault] credits. Average Credits: [station_vault/total_players]")
|
||||
if(mr_moneybags)
|
||||
parts += "The most affluent crew member at shift end was <b>[mr_moneybags.account_holder] with [mr_moneybags.account_balance]</b> cr!</div>"
|
||||
else
|
||||
|
||||
@@ -49,6 +49,8 @@ GLOBAL_VAR(tgui_log)
|
||||
GLOBAL_PROTECT(tgui_log)
|
||||
GLOBAL_VAR(world_shuttle_log)
|
||||
GLOBAL_PROTECT(world_shuttle_log)
|
||||
GLOBAL_VAR(world_econ_log)
|
||||
GLOBAL_PROTECT(world_econ_log)
|
||||
|
||||
GLOBAL_VAR(perf_log)
|
||||
GLOBAL_PROTECT(perf_log)
|
||||
|
||||
@@ -79,6 +79,10 @@
|
||||
/datum/config_entry/flag/log_telecomms
|
||||
config_entry_value = TRUE
|
||||
|
||||
/// log economy
|
||||
/datum/config_entry/flag/log_econ
|
||||
config_entry_value = TRUE
|
||||
|
||||
/// log certain expliotable parrots and other such fun things in a JSON file of twitter valid phrases.
|
||||
/datum/config_entry/flag/log_twitter
|
||||
config_entry_value = TRUE
|
||||
|
||||
@@ -1,3 +1,19 @@
|
||||
//As a brief warning to all those who dare tread upon these grounds:
|
||||
//The bulk of this code here was written years ago, back in the days of 512.
|
||||
//We were incredibly drunk back then. And nowadays, we've found that being drunk is a hard requirement for working with this code.
|
||||
//So if you're here to make changes? Brandish a glass. There are many sins here, but it's exactly as engineered as it needs to be.
|
||||
//We physically won't be able to tell you what half of this code does. The only thing that'll help you here is the ballmer peak.
|
||||
//Bottoms up, friend. And be sure to drink responsibly. Be sure to fetch some water, too; it eases the hangover. - Bhijn & Myr
|
||||
|
||||
|
||||
// Jukelist indices
|
||||
#define JUKE_TRACK 1
|
||||
#define JUKE_CHANNEL 2
|
||||
#define JUKE_BOX 3
|
||||
#define JUKE_FALLOFF 4
|
||||
#define JUKE_SOUND 5
|
||||
|
||||
|
||||
SUBSYSTEM_DEF(jukeboxes)
|
||||
name = "Jukeboxes"
|
||||
wait = 5
|
||||
@@ -25,27 +41,41 @@ SUBSYSTEM_DEF(jukeboxes)
|
||||
var/channeltoreserve = pick(freejukeboxchannels)
|
||||
if(!channeltoreserve)
|
||||
return FALSE
|
||||
var/sound/song_to_init = sound(T.song_path)
|
||||
freejukeboxchannels -= channeltoreserve
|
||||
var/list/youvegotafreejukebox = list(T, channeltoreserve, jukebox, jukefalloff)
|
||||
var/list/youvegotafreejukebox = list(T, channeltoreserve, jukebox, jukefalloff, song_to_init)
|
||||
|
||||
song_to_init.status = SOUND_MUTE
|
||||
song_to_init.environment = 7
|
||||
song_to_init.channel = channeltoreserve
|
||||
song_to_init.volume = 1
|
||||
song_to_init.falloff = jukefalloff
|
||||
song_to_init.echo = list(0, null, -10000, null, null, null, null, null, null, null, null, null, null, 1, 1, 1, null, null)
|
||||
|
||||
activejukeboxes.len++
|
||||
activejukeboxes[activejukeboxes.len] = youvegotafreejukebox
|
||||
|
||||
//Due to changes in later versions of 512, SOUND_UPDATE no longer properly plays audio when a file is defined in the sound datum. As such, we are now required to init the audio before we can actually do anything with it.
|
||||
//Downsides to this? This means that you can *only* hear the jukebox audio if you were present on the server when it started playing, and it means that it's now impossible to add loops to the jukebox track list.
|
||||
var/sound/song_to_init = sound(T.song_path)
|
||||
song_to_init.status = SOUND_MUTE
|
||||
for(var/mob/M in GLOB.player_list)
|
||||
if(!M.client)
|
||||
continue
|
||||
if(!(M.client.prefs.toggles & SOUND_INSTRUMENTS))
|
||||
continue
|
||||
|
||||
M.playsound_local(M, null, 100, channel = youvegotafreejukebox[2], S = song_to_init)
|
||||
SEND_SOUND(M, song_to_init)
|
||||
return activejukeboxes.len
|
||||
|
||||
|
||||
//Updates jukebox by transferring to different object or modifying falloff.
|
||||
/datum/controller/subsystem/jukeboxes/proc/updatejukebox(IDtoupdate, obj/jukebox, jukefalloff)
|
||||
if(islist(activejukeboxes[IDtoupdate]))
|
||||
if(istype(jukebox))
|
||||
activejukeboxes[IDtoupdate][JUKE_BOX] = jukebox
|
||||
if(!isnull(jukefalloff))
|
||||
activejukeboxes[IDtoupdate][JUKE_FALLOFF] = jukefalloff
|
||||
|
||||
/datum/controller/subsystem/jukeboxes/proc/removejukebox(IDtoremove)
|
||||
if(islist(activejukeboxes[IDtoremove]))
|
||||
var/jukechannel = activejukeboxes[IDtoremove][2]
|
||||
var/jukechannel = activejukeboxes[IDtoremove][JUKE_CHANNEL]
|
||||
for(var/mob/M in GLOB.player_list)
|
||||
if(!M.client)
|
||||
continue
|
||||
@@ -85,37 +115,74 @@ SUBSYSTEM_DEF(jukeboxes)
|
||||
if(!jukeinfo.len)
|
||||
stack_trace("Active jukebox without any associated metadata.")
|
||||
continue
|
||||
var/datum/track/juketrack = jukeinfo[1]
|
||||
var/datum/track/juketrack = jukeinfo[JUKE_TRACK]
|
||||
if(!istype(juketrack))
|
||||
stack_trace("Invalid jukebox track datum.")
|
||||
continue
|
||||
var/obj/jukebox = jukeinfo[3]
|
||||
var/obj/jukebox = jukeinfo[JUKE_BOX]
|
||||
if(!istype(jukebox))
|
||||
stack_trace("Nonexistant or invalid object associated with jukebox.")
|
||||
continue
|
||||
var/sound/song_played = sound(juketrack.song_path)
|
||||
|
||||
var/list/audible_zlevels = get_multiz_accessible_levels(jukebox.z) //TODO - for multiz refresh, this should use the cached zlevel connections var in SSMapping. For now this is fine!
|
||||
|
||||
var/sound/song_played = jukeinfo[JUKE_SOUND]
|
||||
var/turf/currentturf = get_turf(jukebox)
|
||||
var/area/currentarea = get_area(jukebox)
|
||||
var/list/hearerscache = hearers(7, jukebox)
|
||||
var/targetfalloff = jukeinfo[JUKE_FALLOFF]
|
||||
var/mixes = ((targetfalloff*250)-750)
|
||||
var/inrange
|
||||
var/pressure_factor
|
||||
|
||||
song_played.falloff = jukeinfo[4]
|
||||
|
||||
var/datum/gas_mixture/source_env = (istype(currentturf) ? currentturf.return_air() : null)
|
||||
var/datum/gas_mixture/hearer_env //We init this var outside of the mob loop for the sake of performance
|
||||
var/turf/hearerturf //ditto
|
||||
|
||||
var/source_pressure = (istype(source_env) ? source_env.return_pressure() : 0)
|
||||
|
||||
song_played.falloff = targetfalloff
|
||||
song_played.volume = min((targetfalloff * 50), 100)
|
||||
|
||||
for(var/mob/M in GLOB.player_list)
|
||||
if(!M.client)
|
||||
continue
|
||||
if(!(M.client.prefs.toggles & SOUND_INSTRUMENTS) || !M.can_hear())
|
||||
M.stop_sound_channel(jukeinfo[2])
|
||||
if(!(M.client.prefs.toggles & SOUND_INSTRUMENTS))
|
||||
M.stop_sound_channel(jukeinfo[JUKE_CHANNEL])
|
||||
continue
|
||||
|
||||
var/inrange = FALSE
|
||||
if(jukebox.z == M.z) //todo - expand this to work with mining planet z-levels when robust jukebox audio gets merged to master
|
||||
song_played.status = SOUND_UPDATE
|
||||
if(get_area(M) == currentarea)
|
||||
inrange = TRUE
|
||||
else if(M in hearerscache)
|
||||
inrange = TRUE
|
||||
else
|
||||
song_played.status = SOUND_MUTE | SOUND_UPDATE //Setting volume = 0 doesn't let the sound properties update at all, which is lame.
|
||||
M.playsound_local(currentturf, null, 100, channel = jukeinfo[2], S = song_played, envwet = (inrange ? -250 : 0), envdry = (inrange ? 0 : -10000))
|
||||
inrange = FALSE
|
||||
song_played.status = SOUND_MUTE | SOUND_UPDATE
|
||||
|
||||
if(source_pressure)
|
||||
hearerturf = get_turf(M)
|
||||
hearer_env = (istype(hearerturf) ? hearerturf.return_air() : null)
|
||||
if(istype(hearer_env))
|
||||
pressure_factor = min(source_pressure, hearer_env.return_pressure())
|
||||
|
||||
if(pressure_factor && targetfalloff && M.can_hear() && (M.z in audible_zlevels))
|
||||
if(get_area(M) == currentarea)
|
||||
inrange = TRUE
|
||||
else if(M in hearerscache)
|
||||
inrange = TRUE
|
||||
|
||||
song_played.x = (currentturf.x - M.x) * SOUND_DEFAULT_DISTANCE_MULTIPLIER
|
||||
song_played.z = (currentturf.y - M.y) * SOUND_DEFAULT_DISTANCE_MULTIPLIER
|
||||
song_played.y = (((currentturf.z - M.z) * 10 * SOUND_DEFAULT_DISTANCE_MULTIPLIER) + ((currentturf.z < M.z) ? -5 : 5))
|
||||
|
||||
if(pressure_factor < ONE_ATMOSPHERE)
|
||||
song_played.volume = (min((targetfalloff * 50), 100) * max((pressure_factor - SOUND_MINIMUM_PRESSURE)/(ONE_ATMOSPHERE - SOUND_MINIMUM_PRESSURE), 1))
|
||||
|
||||
song_played.echo[1] = (inrange ? 0 : -10000)
|
||||
song_played.echo[3] = (inrange ? mixes : max(mixes, 0))
|
||||
song_played.status = SOUND_UPDATE
|
||||
SEND_SOUND(M, song_played)
|
||||
CHECK_TICK
|
||||
return
|
||||
|
||||
#undef JUKE_TRACK
|
||||
#undef JUKE_CHANNEL
|
||||
#undef JUKE_BOX
|
||||
#undef JUKE_FALLOFF
|
||||
#undef JUKE_SOUND
|
||||
|
||||
@@ -1192,6 +1192,8 @@
|
||||
log_mecha(log_text)
|
||||
if(LOG_SHUTTLE)
|
||||
log_shuttle(log_text)
|
||||
if(LOG_ECON)
|
||||
log_econ(log_text)
|
||||
else
|
||||
stack_trace("Invalid individual logging type: [message_type]. Defaulting to [LOG_GAME] (LOG_GAME).")
|
||||
log_game(log_text)
|
||||
|
||||
@@ -312,6 +312,36 @@ Class Procs:
|
||||
|
||||
return TRUE // If we passed all of those checks, woohoo! We can interact with this machine.
|
||||
|
||||
/obj/machinery/proc/can_transact(obj/item/card/id/thecard, allowdepartment, silent)
|
||||
if(!istype(thecard))
|
||||
if(!silent)
|
||||
say("No card found.")
|
||||
return FALSE
|
||||
else if (!thecard.registered_account)
|
||||
if(!silent)
|
||||
say("No account found.")
|
||||
return FALSE
|
||||
else if(!allowdepartment && !thecard.registered_account.account_job)
|
||||
if(!silent)
|
||||
say("Departmental accounts have been blacklisted from personal expenses due to embezzlement.")
|
||||
return FALSE
|
||||
return TRUE
|
||||
|
||||
/obj/machinery/proc/attempt_transact(obj/item/card/id/thecard, transaction_cost)
|
||||
if(!istype(thecard))
|
||||
return FALSE
|
||||
var/datum/bank_account/account = thecard.registered_account
|
||||
if(!istype(account))
|
||||
return FALSE
|
||||
|
||||
if(transaction_cost)
|
||||
if(!account.adjust_money(-transaction_cost))
|
||||
return FALSE
|
||||
var/datum/bank_account/D = SSeconomy.get_dep_account(payment_department)
|
||||
if(D)
|
||||
D.adjust_money(transaction_cost)
|
||||
return TRUE
|
||||
|
||||
/obj/machinery/proc/check_nap_violations()
|
||||
if(!SSeconomy.full_ancap)
|
||||
return TRUE
|
||||
|
||||
@@ -6,17 +6,22 @@
|
||||
verb_say = "states"
|
||||
density = TRUE
|
||||
req_one_access = list(ACCESS_BAR, ACCESS_KITCHEN, ACCESS_HYDROPONICS, ACCESS_ENGINE, ACCESS_CARGO, ACCESS_THEATRE)
|
||||
payment_department = ACCOUNT_SRV
|
||||
var/active = FALSE
|
||||
var/list/rangers = list()
|
||||
var/stop = 0
|
||||
var/volume = 70
|
||||
var/datum/track/selection = null
|
||||
var/queuecost = PRICE_CHEAP //Set to -1 to make this jukebox require access for queueing
|
||||
var/datum/track/playing = null
|
||||
var/datum/track/selectedtrack = null
|
||||
var/list/queuedplaylist = list()
|
||||
var/queuecooldown //This var exists solely to prevent accidental repeats of John Mulaney's 'What's New Pussycat?' incident. Intentional, however......
|
||||
|
||||
/obj/machinery/jukebox/disco
|
||||
name = "radiant dance machine mark IV"
|
||||
desc = "The first three prototypes were discontinued after mass casualty incidents."
|
||||
icon_state = "disco"
|
||||
req_access = list(ACCESS_ENGINE)
|
||||
req_one_access = list(ACCESS_ENGINE)
|
||||
anchored = FALSE
|
||||
var/list/spotlights = list()
|
||||
var/list/sparkles = list()
|
||||
@@ -24,7 +29,7 @@
|
||||
/obj/machinery/jukebox/disco/indestructible
|
||||
name = "radiant dance machine mark V"
|
||||
desc = "Now redesigned with data gathered from the extensive disco and plasma research."
|
||||
req_access = null
|
||||
req_one_access = null
|
||||
anchored = TRUE
|
||||
resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF
|
||||
flags_1 = NODECONSTRUCT_1
|
||||
@@ -46,6 +51,16 @@
|
||||
return
|
||||
return ..()
|
||||
|
||||
/obj/machinery/jukebox/emag_act(mob/user)
|
||||
. = ..()
|
||||
if(obj_flags & EMAGGED)
|
||||
return
|
||||
obj_flags |= EMAGGED
|
||||
queuecost = PRICE_FREE
|
||||
req_one_access = null
|
||||
to_chat(user, "<span class='notice'>You've bypassed [src]'s audio volume limiter, and enabled free play.</span>")
|
||||
return TRUE
|
||||
|
||||
/obj/machinery/jukebox/update_icon_state()
|
||||
if(active)
|
||||
icon_state = "[initial(icon_state)]-active"
|
||||
@@ -56,7 +71,7 @@
|
||||
if(!anchored)
|
||||
to_chat(user,"<span class='warning'>This device must be anchored by a wrench!</span>")
|
||||
return UI_CLOSE
|
||||
if(!allowed(user) && !isobserver(user))
|
||||
if((queuecost < 0 && !allowed(user)) && !isobserver(user))
|
||||
to_chat(user,"<span class='warning'>Error: Access Denied.</span>")
|
||||
user.playsound_local(src, 'sound/misc/compiler-failure.ogg', 25, TRUE)
|
||||
return UI_CLOSE
|
||||
@@ -77,18 +92,21 @@
|
||||
data["active"] = active
|
||||
data["songs"] = list()
|
||||
for(var/datum/track/S in SSjukeboxes.songs)
|
||||
var/list/track_data = list(
|
||||
name = S.song_name
|
||||
)
|
||||
var/list/track_data = list(name = S.song_name)
|
||||
data["songs"] += list(track_data)
|
||||
data["queued_tracks"] = list()
|
||||
for(var/datum/track/S in queuedplaylist)
|
||||
var/list/track_data = list(name = S.song_name)
|
||||
data["queued_tracks"] += list(track_data)
|
||||
data["track_selected"] = null
|
||||
data["track_length"] = null
|
||||
data["track_beat"] = null
|
||||
if(selection)
|
||||
data["track_selected"] = selection.song_name
|
||||
data["track_length"] = DisplayTimeText(selection.song_length)
|
||||
data["track_beat"] = selection.song_beat
|
||||
if(playing)
|
||||
data["track_selected"] = playing.song_name
|
||||
data["track_length"] = DisplayTimeText(playing.song_length)
|
||||
data["volume"] = volume
|
||||
data["is_emagged"] = (obj_flags & EMAGGED)
|
||||
data["cost_for_play"] = queuecost
|
||||
data["has_access"] = allowed(user)
|
||||
return data
|
||||
|
||||
/obj/machinery/jukebox/ui_act(action, list/params)
|
||||
@@ -100,57 +118,91 @@
|
||||
if("toggle")
|
||||
if(QDELETED(src))
|
||||
return
|
||||
if(!active)
|
||||
if(stop > world.time)
|
||||
to_chat(usr, "<span class='warning'>Error: The device is still resetting from the last activation, it will be ready again in [DisplayTimeText(stop-world.time)].</span>")
|
||||
playsound(src, 'sound/misc/compiler-failure.ogg', 50, TRUE)
|
||||
return
|
||||
if(!allowed(usr))
|
||||
return
|
||||
if(!active && !playing)
|
||||
activate_music()
|
||||
START_PROCESSING(SSobj, src)
|
||||
return TRUE
|
||||
else
|
||||
stop = 0
|
||||
return TRUE
|
||||
if("select_track")
|
||||
if(active)
|
||||
to_chat(usr, "<span class='warning'>Error: You cannot change the song until the current one is over.</span>")
|
||||
return TRUE
|
||||
if("add_to_queue")
|
||||
if(QDELETED(src))
|
||||
return
|
||||
if(world.time < queuecooldown)
|
||||
return
|
||||
if(!istype(selectedtrack, /datum/track))
|
||||
return
|
||||
if(!allowed(usr) && queuecost)
|
||||
var/obj/item/card/id/C
|
||||
if(isliving(usr))
|
||||
var/mob/living/L = usr
|
||||
C = L.get_idcard(TRUE)
|
||||
if(!can_transact(C))
|
||||
queuecooldown = world.time + (1 SECONDS)
|
||||
playsound(src, 'sound/misc/compiler-failure.ogg', 25, TRUE)
|
||||
return
|
||||
if(!attempt_transact(C, queuecost))
|
||||
say("Insufficient funds.")
|
||||
queuecooldown = world.time + (1 SECONDS)
|
||||
playsound(src, 'sound/misc/compiler-failure.ogg', 25, TRUE)
|
||||
return
|
||||
to_chat(usr, "<span class='notice'>You spend [queuecost] credits to queue [selectedtrack.song_name].</span>")
|
||||
log_econ("[queuecost] credits were inserted into [src] by [key_name(usr)] (ID: [C.registered_name]) to queue [selectedtrack.song_name].")
|
||||
queuedplaylist += selectedtrack
|
||||
if(active)
|
||||
say("[selectedtrack.song_name] has been added to the queue.")
|
||||
else if(!playing)
|
||||
activate_music()
|
||||
playsound(src, 'sound/machines/ping.ogg', 50, TRUE)
|
||||
queuecooldown = world.time + (3 SECONDS)
|
||||
return TRUE
|
||||
if("select_track")
|
||||
var/list/available = list()
|
||||
for(var/datum/track/S in SSjukeboxes.songs)
|
||||
available[S.song_name] = S
|
||||
var/selected = params["track"]
|
||||
if(QDELETED(src) || !selected || !istype(available[selected], /datum/track))
|
||||
return
|
||||
selection = available[selected]
|
||||
selectedtrack = available[selected]
|
||||
return TRUE
|
||||
if("set_volume")
|
||||
if(!allowed(usr))
|
||||
return
|
||||
var/new_volume = params["volume"]
|
||||
if(new_volume == "reset")
|
||||
volume = initial(volume)
|
||||
return TRUE
|
||||
else if(new_volume == "min")
|
||||
volume = 0
|
||||
return TRUE
|
||||
else if(new_volume == "max")
|
||||
volume = 100
|
||||
return TRUE
|
||||
volume = ((obj_flags & EMAGGED) ? 210 : 100)
|
||||
else if(text2num(new_volume) != null)
|
||||
volume = text2num(new_volume)
|
||||
return TRUE
|
||||
volume = clamp(0, text2num(new_volume), ((obj_flags & EMAGGED) ? 210 : 100))
|
||||
var/wherejuke = SSjukeboxes.findjukeboxindex(src)
|
||||
if(wherejuke)
|
||||
SSjukeboxes.updatejukebox(wherejuke, jukefalloff = volume/35)
|
||||
return TRUE
|
||||
|
||||
/obj/machinery/jukebox/proc/activate_music()
|
||||
var/jukeboxslottotake = SSjukeboxes.addjukebox(src, selection, 2)
|
||||
if(playing || !queuedplaylist.len)
|
||||
return FALSE
|
||||
playing = queuedplaylist[1]
|
||||
var/jukeboxslottotake = SSjukeboxes.addjukebox(src, playing, volume/35)
|
||||
if(jukeboxslottotake)
|
||||
active = TRUE
|
||||
update_icon()
|
||||
START_PROCESSING(SSobj, src)
|
||||
stop = world.time + selection.song_length
|
||||
stop = world.time + playing.song_length
|
||||
queuedplaylist.Cut(1, 2)
|
||||
say("Now playing: [playing.song_name]")
|
||||
playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, TRUE)
|
||||
return TRUE
|
||||
else
|
||||
return FALSE
|
||||
|
||||
/obj/machinery/jukebox/disco/activate_music()
|
||||
..()
|
||||
. = ..()
|
||||
if(!.)
|
||||
return
|
||||
dance_setup()
|
||||
lights_spin()
|
||||
|
||||
@@ -243,7 +295,7 @@
|
||||
S.pixel_y = 7
|
||||
S.forceMove(get_turf(src))
|
||||
sleep(7)
|
||||
if(selection.song_name == "Engineering's Ultimate High-Energy Hustle")
|
||||
if(playing.song_name == "Engineering's Ultimate High-Energy Hustle")
|
||||
sleep(280)
|
||||
for(var/obj/reveal in sparkles)
|
||||
reveal.alpha = 255
|
||||
@@ -299,7 +351,7 @@
|
||||
continue
|
||||
if(prob(2)) // Unique effects for the dance floor that show up randomly to mix things up
|
||||
INVOKE_ASYNC(src, .proc/hierofunk)
|
||||
sleep(selection.song_beat)
|
||||
sleep(playing.song_beat)
|
||||
|
||||
#undef DISCO_INFENO_RANGE
|
||||
|
||||
@@ -431,6 +483,7 @@
|
||||
return
|
||||
SSjukeboxes.removejukebox(position)
|
||||
STOP_PROCESSING(SSobj, src)
|
||||
playing = null
|
||||
rangers = list()
|
||||
|
||||
/obj/machinery/jukebox/disco/dance_over()
|
||||
@@ -439,12 +492,21 @@
|
||||
QDEL_LIST(sparkles)
|
||||
|
||||
/obj/machinery/jukebox/process()
|
||||
if(active && world.time >= stop)
|
||||
active = FALSE
|
||||
dance_over()
|
||||
playsound(src,'sound/machines/terminal_off.ogg',50,1)
|
||||
update_icon()
|
||||
stop = world.time + 100
|
||||
if(active)
|
||||
if(world.time >= stop)
|
||||
active = FALSE
|
||||
dance_over()
|
||||
if(stop && queuedplaylist.len)
|
||||
activate_music()
|
||||
else
|
||||
playsound(src,'sound/machines/terminal_off.ogg',50,1)
|
||||
update_icon()
|
||||
playing = null
|
||||
stop = 0
|
||||
else if(volume > 140) // BOOM BOOM BOOM BOOM
|
||||
for(var/mob/living/carbon/C in hearers(round(volume/35), src)) // I WANT YOU IN MY ROOM
|
||||
if(istype(C)) // LETS SPEND THE NIGHT TOGETHER
|
||||
C.adjustEarDamage(max((((volume/35) - sqrt(get_dist(C, src) * 4)) - C.get_ear_protection())*0.1, 0)) // FROM NOW UNTIL FOREVER
|
||||
|
||||
|
||||
/obj/machinery/jukebox/disco/process()
|
||||
|
||||
+5
-2
@@ -111,12 +111,15 @@ distance_multiplier - Can be used to multiply the distance at which the sound is
|
||||
|
||||
/mob/proc/playsound_local(turf/turf_source, soundin, vol as num, vary, frequency, falloff_exponent = SOUND_FALLOFF_EXPONENT, channel = 0, pressure_affected = TRUE, sound/S, max_distance,
|
||||
falloff_distance = SOUND_DEFAULT_FALLOFF_DISTANCE, distance_multiplier = SOUND_DEFAULT_DISTANCE_MULTIPLIER, envwet = -10000, envdry = 0)
|
||||
if(!client || !can_hear())
|
||||
if(!client)
|
||||
return
|
||||
|
||||
if(!S)
|
||||
S = sound(get_sfx(soundin))
|
||||
|
||||
if(!can_hear() && !(S.status & SOUND_UPDATE)) //This is primarily to make sure sound updates still go through when a spaceman's deaf
|
||||
return
|
||||
|
||||
S.wait = 0 //No queue
|
||||
S.channel = channel || SSsounds.random_available_channel()
|
||||
S.volume = vol
|
||||
@@ -173,7 +176,7 @@ distance_multiplier - Can be used to multiply the distance at which the sound is
|
||||
var/dz = turf_source.y - T.y // Hearing from infront/behind
|
||||
S.z = dz * distance_multiplier
|
||||
var/dy = (turf_source.z - T.z) * 5 * distance_multiplier // Hearing from above / below, multiplied by 5 because we assume height is further along coords.
|
||||
S.y = dy + 1
|
||||
S.y = dy + distance_multiplier
|
||||
|
||||
S.falloff = isnull(max_distance)? FALLOFF_SOUNDS : max_distance //use max_distance, else just use 1 as we are a direct sound so falloff isnt relevant.
|
||||
|
||||
|
||||
@@ -117,6 +117,7 @@ GLOBAL_LIST(topic_status_cache)
|
||||
GLOB.world_asset_log = "[GLOB.log_directory]/asset.log"
|
||||
GLOB.world_attack_log = "[GLOB.log_directory]/attack.log"
|
||||
GLOB.world_victim_log = "[GLOB.log_directory]/victim.log"
|
||||
GLOB.world_econ_log = "[GLOB.log_directory]/econ.log"
|
||||
GLOB.world_pda_log = "[GLOB.log_directory]/pda.log"
|
||||
GLOB.world_telecomms_log = "[GLOB.log_directory]/telecomms.log"
|
||||
GLOB.world_manifest_log = "[GLOB.log_directory]/manifest.log"
|
||||
|
||||
@@ -103,7 +103,7 @@
|
||||
return
|
||||
account.adjust_money(-deposit_value) //The money vanishes, not paid to any accounts.
|
||||
SSblackbox.record_feedback("amount", "BEPIS_credits_spent", deposit_value)
|
||||
//log_econ("[deposit_value] credits were inserted into [src] by [account.account_holder]")
|
||||
log_econ("[deposit_value] credits were inserted into [src] by [key_name(usr)] (account: [account.account_holder])")
|
||||
banked_cash += deposit_value
|
||||
use_power(1000 * power_saver)
|
||||
say("Cash deposit successful. There is [banked_cash] in the chamber.")
|
||||
|
||||
@@ -18,10 +18,9 @@
|
||||
// to hear anything.
|
||||
var/deaf = 0
|
||||
|
||||
// `ear_damage` measures long term damage to the ears, if too high,
|
||||
// `damage` in this case measures long term damage to the ears, if too high,
|
||||
// the person will not have either `deaf` or `ear_damage` decrease
|
||||
// without external aid (earmuffs, drugs)
|
||||
var/ear_damage = 0
|
||||
|
||||
//Resistance against loud noises
|
||||
var/bang_protect = 0
|
||||
@@ -44,16 +43,22 @@
|
||||
|
||||
/obj/item/organ/ears/proc/restoreEars()
|
||||
deaf = 0
|
||||
ear_damage = 0
|
||||
damage = 0
|
||||
prev_damage = 0
|
||||
organ_flags &= ~ORGAN_FAILING
|
||||
|
||||
var/mob/living/carbon/C = owner
|
||||
|
||||
if(iscarbon(owner) && HAS_TRAIT(C, TRAIT_DEAF))
|
||||
deaf = 1
|
||||
var/mess = check_damage_thresholds()
|
||||
if(mess && owner)
|
||||
to_chat(owner, mess)
|
||||
|
||||
/obj/item/organ/ears/proc/adjustEarDamage(ddmg, ddeaf)
|
||||
ear_damage = max(ear_damage + (ddmg*damage_multiplier), 0)
|
||||
if(owner.status_flags & GODMODE)
|
||||
return
|
||||
setOrganDamage(max(damage + (ddmg*damage_multiplier), 0))
|
||||
deaf = max(deaf + (ddeaf*damage_multiplier), 0)
|
||||
|
||||
/obj/item/organ/ears/proc/minimumDeafTicks(value)
|
||||
|
||||
@@ -864,21 +864,10 @@ GLOBAL_LIST_EMPTY(vending_products)
|
||||
if(isliving(usr))
|
||||
var/mob/living/L = usr
|
||||
C = L.get_idcard(TRUE)
|
||||
if(!C)
|
||||
say("No card found.")
|
||||
if(!can_transact(C))
|
||||
flick(icon_deny,src)
|
||||
vend_ready = TRUE
|
||||
return
|
||||
else if (!C.registered_account)
|
||||
say("No account found.")
|
||||
flick(icon_deny,src)
|
||||
vend_ready = TRUE
|
||||
return
|
||||
else if(!C.registered_account.account_job)
|
||||
say("Departmental accounts have been blacklisted from personal expenses due to embezzlement.")
|
||||
flick(icon_deny, src)
|
||||
vend_ready = TRUE
|
||||
return
|
||||
// else if(age_restrictions && R.age_restricted && (!C.registered_age || C.registered_age < AGE_MINOR))
|
||||
// say("You are not of legal age to purchase [R.name].")
|
||||
// if(!(usr in GLOB.narcd_underages))
|
||||
@@ -901,16 +890,13 @@ GLOBAL_LIST_EMPTY(vending_products)
|
||||
price_to_use = 0 // it's free shut up
|
||||
if(coin_records.Find(R) || hidden_records.Find(R))
|
||||
price_to_use = R.custom_premium_price ? R.custom_premium_price : extra_price
|
||||
if(price_to_use && !account.adjust_money(-price_to_use))
|
||||
if(price_to_use && !attempt_transact(C, price_to_use))
|
||||
say("You do not possess the funds to purchase [R.name].")
|
||||
flick(icon_deny,src)
|
||||
vend_ready = TRUE
|
||||
return
|
||||
var/datum/bank_account/D = SSeconomy.get_dep_account(payment_department)
|
||||
if(D)
|
||||
D.adjust_money(price_to_use)
|
||||
SSblackbox.record_feedback("amount", "vending_spent", price_to_use)
|
||||
// log_econ("[price_to_use] credits were inserted into [src] by [D.account_holder] to buy [R].")
|
||||
SSblackbox.record_feedback("amount", "vending_spent", price_to_use)
|
||||
log_econ("[price_to_use] credits were inserted into [src] by [key_name(usr)] (account: [account.account_holder]) to buy [R].")
|
||||
if(last_shopper != REF(usr) || purchase_message_cooldown < world.time)
|
||||
say("Thank you for shopping with [src]!")
|
||||
purchase_message_cooldown = world.time + 5 SECONDS
|
||||
@@ -1169,7 +1155,7 @@ GLOBAL_LIST_EMPTY(vending_products)
|
||||
if(owner)
|
||||
owner.adjust_money(S.custom_price)
|
||||
SSblackbox.record_feedback("amount", "vending_spent", S.custom_price)
|
||||
// log_econ("[S.custom_price] credits were spent on [src] buying a [S] by [owner.account_holder], owned by [private_a.account_holder].")
|
||||
log_econ("[S.custom_price] credits were spent on [src] buying a [S] by [key_name(usr)] (account: [owner.account_holder]), owned by [private_a.account_holder].")
|
||||
vending_machine_input[N] = max(vending_machine_input[N] - 1, 0)
|
||||
S.forceMove(drop_location())
|
||||
loaded_items--
|
||||
|
||||
@@ -61,6 +61,9 @@ LOG_JOB_DEBUG
|
||||
## Log shuttle related actions
|
||||
LOG_SHUTTLE
|
||||
|
||||
## Log economy-related actions
|
||||
LOG_ECON
|
||||
|
||||
## log all world.Topic() calls
|
||||
LOG_WORLD_TOPIC
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { sortBy } from 'common/collections';
|
||||
import { flow } from 'common/fp';
|
||||
import { useBackend } from '../backend';
|
||||
import { Box, Button, Dropdown, Section, Knob, LabeledControls, LabeledList } from '../components';
|
||||
import { Box, Button, Dropdown, Section, Knob, LabeledControls, LabeledList, Stack, Tabs } from '../components';
|
||||
import { Window } from '../layouts';
|
||||
|
||||
export const Jukebox = (props, context) => {
|
||||
@@ -10,98 +10,134 @@ export const Jukebox = (props, context) => {
|
||||
active,
|
||||
track_selected,
|
||||
track_length,
|
||||
track_beat,
|
||||
volume,
|
||||
is_emagged,
|
||||
cost_for_play,
|
||||
has_access,
|
||||
} = data;
|
||||
const songs = flow([
|
||||
sortBy(
|
||||
song => song.name),
|
||||
])(data.songs || []);
|
||||
const queued_tracks = data.queued_tracks || [];
|
||||
return (
|
||||
<Window
|
||||
width={370}
|
||||
height={313}>
|
||||
width={420}
|
||||
height={480}>
|
||||
<Window.Content>
|
||||
<Section
|
||||
title="Song Player"
|
||||
title="Machine Controls"
|
||||
buttons={(
|
||||
<Button
|
||||
icon={active ? 'pause' : 'play'}
|
||||
content={active ? 'Stop' : 'Play'}
|
||||
selected={active}
|
||||
disabled={!has_access}
|
||||
onClick={() => act('toggle')} />
|
||||
)}>
|
||||
<Stack>
|
||||
<Stack.Item>
|
||||
<LabeledList>
|
||||
<LabeledList.Item label="Current Track">
|
||||
{track_selected ? track_selected : "No Track Selected"}
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Track Length">
|
||||
{track_selected ? track_length : "No Track Selected"}
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<LabeledControls justify="center">
|
||||
<LabeledControls.Item label="Volume">
|
||||
<Box position="relative">
|
||||
<Knob
|
||||
size={2.4}
|
||||
color={volume > 140 ? 'red' : 'green'}
|
||||
value={volume}
|
||||
unit="%"
|
||||
minValue={0}
|
||||
maxValue={is_emagged ? 210 : 100}
|
||||
step={1}
|
||||
stepPixelSize={1}
|
||||
disabled={!has_access}
|
||||
onDrag={(e, value) => act('set_volume', {
|
||||
volume: value,
|
||||
})} />
|
||||
<Button
|
||||
fluid
|
||||
position="absolute"
|
||||
top="67px"
|
||||
right="62px"
|
||||
color="transparent"
|
||||
icon="fast-backward"
|
||||
disabled={!has_access}
|
||||
onClick={() => act('set_volume', {
|
||||
volume: "min",
|
||||
})} />
|
||||
<Button
|
||||
fluid
|
||||
position="absolute"
|
||||
top="67px"
|
||||
right="-8px"
|
||||
color="transparent"
|
||||
icon="fast-forward"
|
||||
disabled={!has_access}
|
||||
onClick={() => act('set_volume', {
|
||||
volume: "max",
|
||||
})} />
|
||||
<Button
|
||||
fluid
|
||||
position="absolute"
|
||||
top="67px"
|
||||
right="84px"
|
||||
color="transparent"
|
||||
icon="undo"
|
||||
disabled={!has_access}
|
||||
onClick={() => act('set_volume', {
|
||||
volume: "reset",
|
||||
})} />
|
||||
</Box>
|
||||
</LabeledControls.Item>
|
||||
</LabeledControls>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
<LabeledList>
|
||||
<LabeledList.Item label="Track Selected">
|
||||
<Dropdown
|
||||
overflow-y="scroll"
|
||||
width="240px"
|
||||
options={songs.map(song => song.name)}
|
||||
disabled={active}
|
||||
selected={track_selected || "Select a Track"}
|
||||
onSelected={value => act('select_track', {
|
||||
track: value,
|
||||
})} />
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Track Length">
|
||||
{track_selected ? track_length : "No Track Selected"}
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Track Beat">
|
||||
{track_selected ? track_beat : "No Track Selected"}
|
||||
{track_beat === 1 ? " beat" : " beats"}
|
||||
<LabeledList.Item label="Cost to Queue">
|
||||
{cost_for_play} CR {has_access ? "(Cost waived)" : ""}
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
</Section>
|
||||
<Section title="Machine Settings">
|
||||
<LabeledControls justify="center">
|
||||
<LabeledControls.Item label="Volume">
|
||||
<Box position="relative">
|
||||
<Knob
|
||||
size={3.2}
|
||||
color={volume >= 50 ? 'red' : 'green'}
|
||||
value={volume}
|
||||
unit="%"
|
||||
minValue={0}
|
||||
maxValue={100}
|
||||
step={1}
|
||||
stepPixelSize={1}
|
||||
disabled={active}
|
||||
onDrag={(e, value) => act('set_volume', {
|
||||
volume: value,
|
||||
})} />
|
||||
<Button
|
||||
fluid
|
||||
position="absolute"
|
||||
top="-2px"
|
||||
right="-22px"
|
||||
color="transparent"
|
||||
icon="fast-backward"
|
||||
onClick={() => act('set_volume', {
|
||||
volume: "min",
|
||||
})} />
|
||||
<Button
|
||||
fluid
|
||||
position="absolute"
|
||||
top="16px"
|
||||
right="-22px"
|
||||
color="transparent"
|
||||
icon="fast-forward"
|
||||
onClick={() => act('set_volume', {
|
||||
volume: "max",
|
||||
})} />
|
||||
<Button
|
||||
fluid
|
||||
position="absolute"
|
||||
top="34px"
|
||||
right="-22px"
|
||||
color="transparent"
|
||||
icon="undo"
|
||||
onClick={() => act('set_volume', {
|
||||
volume: "reset",
|
||||
})} />
|
||||
</Box>
|
||||
</LabeledControls.Item>
|
||||
</LabeledControls>
|
||||
<Section fill vertical>
|
||||
<Stack>
|
||||
<Stack.Item grow>
|
||||
<Dropdown
|
||||
width="100%"
|
||||
overflow-y="scroll"
|
||||
options={songs.map(song => song.name)}
|
||||
selected="Select a Track"
|
||||
onSelected={value => act('select_track', {
|
||||
track: value,
|
||||
})} />
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Button
|
||||
icon="play"
|
||||
content="Queue"
|
||||
onClick={() => act('add_to_queue')}
|
||||
/>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
<Section fill vertical>
|
||||
<Tabs vertical>
|
||||
{queued_tracks.map(song => (
|
||||
<Tabs.Tab
|
||||
key={song.name}
|
||||
>
|
||||
{song.name}
|
||||
</Tabs.Tab>
|
||||
))}
|
||||
</Tabs>
|
||||
</Section>
|
||||
</Section>
|
||||
</Window.Content>
|
||||
</Window>
|
||||
|
||||
Reference in New Issue
Block a user