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..44f4895b5a 100644
--- a/code/controllers/subsystem/jukeboxes.dm
+++ b/code/controllers/subsystem/jukeboxes.dm
@@ -1,3 +1,10 @@
+// Jukelist indices
+#define JUKE_TRACK 1
+#define JUKE_CHANNEL 2
+#define JUKE_BOX 3
+#define JUKE_FALLOFF 4
+
+
SUBSYSTEM_DEF(jukeboxes)
name = "Jukeboxes"
wait = 5
@@ -40,12 +47,21 @@ SUBSYSTEM_DEF(jukeboxes)
if(!(M.client.prefs.toggles & SOUND_INSTRUMENTS))
continue
- M.playsound_local(M, null, 100, channel = youvegotafreejukebox[2], S = song_to_init)
+ M.playsound_local(M, null, 100, channel = youvegotafreejukebox[JUKE_CHANNEL], S = 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
@@ -93,22 +109,25 @@ SUBSYSTEM_DEF(jukeboxes)
if(!istype(jukebox))
stack_trace("Nonexistant or invalid object associated with jukebox.")
continue
+
+ 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 = sound(juketrack.song_path)
var/turf/currentturf = get_turf(jukebox)
var/area/currentarea = get_area(jukebox)
var/list/hearerscache = hearers(7, jukebox)
-
- song_played.falloff = jukeinfo[4]
+ var/targetfalloff = jukeinfo[JUKE_FALLOFF]
+ var/mixes = ((targetfalloff*250)-750)
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])
+ 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
+ if(targetfalloff && (M.z in audible_zlevels))
song_played.status = SOUND_UPDATE
if(get_area(M) == currentarea)
inrange = TRUE
@@ -116,6 +135,12 @@ SUBSYSTEM_DEF(jukeboxes)
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))
+ song_played.falloff = (inrange ? targetfalloff : targetfalloff * targetfalloff) //The wet channel uses a sqrt falloff by default. Exponentially increasing the falloff when muffled makes
+ M.playsound_local(currentturf, null, (targetfalloff ? min((targetfalloff * 50), 100) : 1), channel = jukeinfo[JUKE_CHANNEL], S = song_played, envwet = ((inrange) ? mixes : max(mixes, 0)), envdry = (inrange ? max(mixes, 0) : -10000))
CHECK_TICK
return
+
+#undef JUKE_TRACK
+#undef JUKE_CHANNEL
+#undef JUKE_BOX
+#undef JUKE_FALLOFF
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..8a80fcbd04 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,89 @@
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!")
+ playsound(src, 'sound/machines/ping.ogg', 50, TRUE)
+ else if(!playing)
+ activate_music()
+ 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)
return TRUE
else
return FALSE
/obj/machinery/jukebox/disco/activate_music()
- ..()
+ . = ..()
+ if(!.)
+ return
dance_setup()
lights_spin()
@@ -243,7 +293,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 +349,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 +481,7 @@
return
SSjukeboxes.removejukebox(position)
STOP_PROCESSING(SSobj, src)
+ playing = null
rangers = list()
/obj/machinery/jukebox/disco/dance_over()
@@ -442,9 +493,13 @@
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(stop && queuedplaylist.len)
+ activate_music()
+ else
+ playsound(src,'sound/machines/terminal_off.ogg',50,1)
+ update_icon()
+ playing = null
+ stop = 0
/obj/machinery/jukebox/disco/process()
diff --git a/code/game/sound.dm b/code/game/sound.dm
index edc3019f27..49e2a50a4a 100644
--- a/code/game/sound.dm
+++ b/code/game/sound.dm
@@ -173,7 +173,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/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..408a8cba70 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,135 @@ 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"}
+
+
+
+
+
+ {cost_for_play} CR {has_access ? "" : "(Cost waived)"}
+
+
+
+
+
+
+
+ 70 ? '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,
+ })} />
+
+
+
+
+
+
+
+
+
song.name)}
- disabled={active}
- selected={track_selected || "Select a Track"}
+ selected="Select a Track"
onSelected={value => act('select_track', {
track: value,
})} />
-
-
- {track_selected ? track_length : "No Track Selected"}
-
-
- {track_selected ? track_beat : "No Track Selected"}
- {track_beat === 1 ? " beat" : " beats"}
-
-
-
-
-
-
-
- = 50 ? 'red' : 'green'}
- value={volume}
- unit="%"
- minValue={0}
- maxValue={100}
- step={1}
- stepPixelSize={1}
- disabled={active}
- onDrag={(e, value) => act('set_volume', {
- volume: value,
- })} />
- act('set_volume', {
- volume: "min",
- })} />
- act('set_volume', {
- volume: "max",
- })} />
- act('set_volume', {
- volume: "reset",
- })} />
-
-
-
+
+
+ act('add_to_queue')}
+ />
+
+
+
+
+ {queued_tracks.map(song => (
+
+ {song.name}
+
+ ))}
+
+