diff --git a/_maps/map_files/BoxStation/BoxStation.dmm b/_maps/map_files/BoxStation/BoxStation.dmm index 62717aa794..836d5a20e6 100644 --- a/_maps/map_files/BoxStation/BoxStation.dmm +++ b/_maps/map_files/BoxStation/BoxStation.dmm @@ -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" = ( diff --git a/_maps/map_files/LambdaStation/dorms.dmm b/_maps/map_files/LambdaStation/dorms.dmm index d52c4e7870..99626ac667 100644 --- a/_maps/map_files/LambdaStation/dorms.dmm +++ b/_maps/map_files/LambdaStation/dorms.dmm @@ -5011,6 +5011,7 @@ dir = 1 }, /obj/machinery/light, +/obj/machinery/jukebox, /turf/open/floor/plasteel, /area/service/bar) "nb" = ( diff --git a/_maps/map_files/MetaStation/MetaStation.dmm b/_maps/map_files/MetaStation/MetaStation.dmm index 3661b49684..dd05cf72ea 100644 --- a/_maps/map_files/MetaStation/MetaStation.dmm +++ b/_maps/map_files/MetaStation/MetaStation.dmm @@ -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 diff --git a/_maps/map_files/OmegaStation/OmegaStation.dmm b/_maps/map_files/OmegaStation/OmegaStation.dmm index 68dfbe5bcf..0726556d5c 100644 --- a/_maps/map_files/OmegaStation/OmegaStation.dmm +++ b/_maps/map_files/OmegaStation/OmegaStation.dmm @@ -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 diff --git a/code/__DEFINES/logging.dm b/code/__DEFINES/logging.dm index 87e7fe3ce4..96201d71c2 100644 --- a/code/__DEFINES/logging.dm +++ b/code/__DEFINES/logging.dm @@ -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" diff --git a/code/__HELPERS/_logging.dm b/code/__HELPERS/_logging.dm index 8a8ccd5023..58b7516be3 100644 --- a/code/__HELPERS/_logging.dm +++ b/code/__HELPERS/_logging.dm @@ -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 diff --git a/code/__HELPERS/roundend.dm b/code/__HELPERS/roundend.dm index 2f7dd7a64f..e9b497e92e 100644 --- a/code/__HELPERS/roundend.dm +++ b/code/__HELPERS/roundend.dm @@ -548,7 +548,7 @@ parts += "There were [station_vault] credits collected by crew this shift.
" if(total_players > 0) parts += "An average of [station_vault/total_players] credits were collected.
" - // 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 [mr_moneybags.account_holder] with [mr_moneybags.account_balance] cr!" else diff --git a/code/_globalvars/logging.dm b/code/_globalvars/logging.dm index 3f3d9c5e25..e9c38546a7 100644 --- a/code/_globalvars/logging.dm +++ b/code/_globalvars/logging.dm @@ -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) diff --git a/code/controllers/configuration/entries/logging.dm b/code/controllers/configuration/entries/logging.dm index 52e4743a22..81013d8030 100644 --- a/code/controllers/configuration/entries/logging.dm +++ b/code/controllers/configuration/entries/logging.dm @@ -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 diff --git a/code/controllers/subsystem/jukeboxes.dm b/code/controllers/subsystem/jukeboxes.dm index 7be6d44a7f..3e87ff256a 100644 --- a/code/controllers/subsystem/jukeboxes.dm +++ b/code/controllers/subsystem/jukeboxes.dm @@ -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 diff --git a/code/game/atoms.dm b/code/game/atoms.dm index 242f3123e1..d8ffccea98 100644 --- a/code/game/atoms.dm +++ b/code/game/atoms.dm @@ -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) diff --git a/code/game/machinery/_machinery.dm b/code/game/machinery/_machinery.dm index e9a1fb61e0..0d95448702 100644 --- a/code/game/machinery/_machinery.dm +++ b/code/game/machinery/_machinery.dm @@ -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 diff --git a/code/game/machinery/dance_machine.dm b/code/game/machinery/dance_machine.dm index 8b24a3804f..956b3e9c79 100644 --- a/code/game/machinery/dance_machine.dm +++ b/code/game/machinery/dance_machine.dm @@ -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, "You've bypassed [src]'s audio volume limiter, and enabled free play.") + 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,"This device must be anchored by a wrench!") return UI_CLOSE - if(!allowed(user) && !isobserver(user)) + if((queuecost < 0 && !allowed(user)) && !isobserver(user)) to_chat(user,"Error: Access Denied.") 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, "Error: The device is still resetting from the last activation, it will be ready again in [DisplayTimeText(stop-world.time)].") - 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, "Error: You cannot change the song until the current one is over.") + 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, "You spend [queuecost] credits to queue [selectedtrack.song_name].") + 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() diff --git a/code/game/sound.dm b/code/game/sound.dm index edc3019f27..9f0473faed 100644 --- a/code/game/sound.dm +++ b/code/game/sound.dm @@ -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. diff --git a/code/game/world.dm b/code/game/world.dm index 092422e822..a9eda444f0 100644 --- a/code/game/world.dm +++ b/code/game/world.dm @@ -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" diff --git a/code/modules/research/bepis.dm b/code/modules/research/bepis.dm index 7b36a614a7..93df1e2351 100644 --- a/code/modules/research/bepis.dm +++ b/code/modules/research/bepis.dm @@ -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.") diff --git a/code/modules/surgery/organs/ears.dm b/code/modules/surgery/organs/ears.dm index 80b7599ec3..d572deca73 100644 --- a/code/modules/surgery/organs/ears.dm +++ b/code/modules/surgery/organs/ears.dm @@ -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) diff --git a/code/modules/vending/_vending.dm b/code/modules/vending/_vending.dm index 700a2fea7e..f84ced508e 100644 --- a/code/modules/vending/_vending.dm +++ b/code/modules/vending/_vending.dm @@ -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-- diff --git a/config/entries/logging.txt b/config/entries/logging.txt index 57588d7cd3..66a7c566ca 100644 --- a/config/entries/logging.txt +++ b/config/entries/logging.txt @@ -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 diff --git a/tgui/packages/tgui/interfaces/Jukebox.js b/tgui/packages/tgui/interfaces/Jukebox.js index b23e55469b..99ddf90be0 100644 --- a/tgui/packages/tgui/interfaces/Jukebox.js +++ b/tgui/packages/tgui/interfaces/Jukebox.js @@ -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 ( + width={420} + height={480}>
act('toggle')} /> )}> + + + + + {track_selected ? track_selected : "No Track Selected"} + + + {track_selected ? track_length : "No Track Selected"} + + + + + + + + 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, + })} /> +
-
- - - - = 50 ? 'red' : 'green'} - value={volume} - unit="%" - minValue={0} - maxValue={100} - step={1} - stepPixelSize={1} - disabled={active} - onDrag={(e, value) => act('set_volume', { - volume: value, - })} /> -