diff --git a/code/datums/components/pricetag.dm b/code/datums/components/pricetag.dm
index 3220dc425a..2ad63c6464 100644
--- a/code/datums/components/pricetag.dm
+++ b/code/datums/components/pricetag.dm
@@ -1,13 +1,17 @@
/datum/component/pricetag
- var/datum/bank_account/owner
- var/profit_ratio = 1
+ ///Payee gets 100% of the value if no ratio has been set.
+ var/default_profit_ratio = 1
+ ///List of bank accounts this pricetag pays out to. Format is payees[bank_account] = profit_ratio.
+ var/list/payees = list()
+
/datum/component/pricetag/Initialize(_owner,_profit_ratio)
if(!isobj(parent)) //Has to account for both objects and sellable structures like crates.
return COMPONENT_INCOMPATIBLE
- owner = _owner
if(_profit_ratio)
- profit_ratio = _profit_ratio
+ payees[_owner] = _profit_ratio
+ else
+ payees[_owner] = default_profit_ratio
RegisterSignal(parent, COMSIG_ITEM_SOLD, .proc/split_profit)
RegisterSignal(parent, COMSIG_STRUCTURE_UNWRAPPED, .proc/Unwrapped)
RegisterSignal(parent, COMSIG_ITEM_UNWRAPPED, .proc/Unwrapped)
@@ -19,10 +23,13 @@
/datum/component/pricetag/proc/split_profit(var/item_value)
var/price = item_value
if(price)
- var/adjusted_value = price*(profit_ratio/100)
- owner.adjust_money(adjusted_value)
- owner.bank_card_talk("Sale recorded. [adjusted_value] credits added to account.")
+ for(var/datum/bank_account/payee in payees)
+ var/profit_ratio = payees[payee]
+ var/adjusted_value = price * profit_ratio
+ var/datum/bank_account/bank_account = payee
+ bank_account.adjust_money(adjusted_value)
+ bank_account.bank_card_talk("Sale of [parent] recorded. [adjusted_value] credits added to account.")
return TRUE
/datum/component/pricetag/proc/return_ratio()
- return profit_ratio
+ return default_profit_ratio
diff --git a/code/game/machinery/civilian_bountys.dm b/code/game/machinery/civilian_bountys.dm
index 9c913ee9cf..36ce96ea7e 100644
--- a/code/game/machinery/civilian_bountys.dm
+++ b/code/game/machinery/civilian_bountys.dm
@@ -98,12 +98,7 @@
inserted_scan_id.registered_account.reset_bounty()
SSeconomy.civ_bounty_tracker++
var/obj/item/bounty_cube/reward = new /obj/item/bounty_cube(drop_location())
- reward.bounty_value = curr_bounty.reward
- reward.bounty_name = curr_bounty.name
- reward.bounty_holder = inserted_scan_id.registered_name
- reward.name = "\improper [reward.bounty_value] cr [reward.name]"
- reward.desc += " The tag indicates it was [reward.bounty_holder]'s reward for completing the [reward.bounty_name] bounty and that it was created at [station_time_timestamp(format = "hh:mm")]."
- reward.AddComponent(/datum/component/pricetag, inserted_scan_id.registered_account, CIV_BOUNTY_SPLIT)
+ reward.set_up(curr_bounty, inserted_scan_id)
pad.visible_message("[pad] activates!")
flick(pad.sending_state,pad)
pad.icon_state = pad.idle_state
@@ -245,6 +240,92 @@
var/bounty_holder
///What the bounty was for.
var/bounty_name
+ ///Multiplier for the bounty payout received by the Supply budget if the cube is sent without having to nag.
+ var/speed_bonus = 0.2
+ ///Multiplier for the bounty payout received by the person who completed the bounty.
+ var/holder_cut = 0.3
+ ///Multiplier for the bounty payout received by the person who claims the handling tip.
+ var/handler_tip = 0.1
+ ///Time between nags.
+ var/nag_cooldown = 5 MINUTES
+ ///How much the time between nags extends each nag.
+ var/nag_cooldown_multiplier = 1.25
+ ///Next world tick to nag Supply listeners.
+ var/next_nag_time
+ ///What job the bounty holder had.
+ var/bounty_holder_job
+ ///Bank account of the person who receives the handling tip.
+ var/datum/bank_account/bounty_handler_account
+ ///Bank account of the person who completed the bounty.
+ var/datum/bank_account/bounty_holder_account
+ ///Our internal radio.
+ var/obj/item/radio/radio
+ ///The key our internal radio uses.
+ var/radio_key = /obj/item/encryptionkey/headset_cargo
+
+/obj/item/bounty_cube/Initialize()
+ . = ..()
+ radio = new(src)
+ radio.keyslot = new radio_key
+ radio.listening = FALSE
+ radio.recalculateChannels()
+
+/obj/item/bounty_cube/Destroy()
+ QDEL_NULL(radio)
+ . = ..()
+
+/obj/item/bounty_cube/examine()
+ . = ..()
+ if(speed_bonus)
+ . += "[time2text(next_nag_time - world.time,"mm:ss")] remains until [bounty_value * speed_bonus] credit speedy delivery bonus lost."
+ if(handler_tip && !bounty_handler_account)
+ . += "Scan this in the cargo shuttle with an export scanner to register your bank account for the [bounty_value * handler_tip] credit handling tip."
+
+/obj/item/bounty_cube/process(delta_time)
+ if(COOLDOWN_FINISHED(src, next_nag_time))
+ if(!is_centcom_level(z) && !is_reserved_level(z)) //don't send message if we're on Centcom or in transit
+ radio.talk_into(src, "Unsent in [get_area(src)].[speed_bonus ? " Speedy delivery bonus of [bounty_value * speed_bonus] credit\s lost." : ""]", RADIO_CHANNEL_SUPPLY)
+ speed_bonus = 0
+ bounty_holder_account.bank_card_talk("\The [src] is unsent in [get_area(src)].")
+ if(bounty_handler_account)
+ bounty_handler_account.bank_card_talk("\The [src] is unsent in [get_area(src)].")
+ nag_cooldown = nag_cooldown * nag_cooldown_multiplier
+ COOLDOWN_START(src, next_nag_time, nag_cooldown)
+
+/obj/item/bounty_cube/proc/set_up(datum/bounty/my_bounty, obj/item/card/id/holder_id)
+ bounty_value = my_bounty.reward
+ bounty_name = my_bounty.name
+ bounty_holder = holder_id.registered_name
+ bounty_holder_job = holder_id.assignment
+ bounty_holder_account = holder_id.registered_account
+ name = "\improper [bounty_value] cr [name]"
+ desc += " The sales tag indicates it was [bounty_holder] ([bounty_holder_job])'s reward for completing the [bounty_name] bounty."
+ AddComponent(/datum/component/pricetag, holder_id.registered_account, holder_cut)
+ START_PROCESSING(SSobj, src)
+ COOLDOWN_START(src, next_nag_time, nag_cooldown)
+ radio.talk_into(src,"Created in [get_area(src)] by [bounty_holder] ([bounty_holder_job]). Speedy delivery bonus lost in [time2text(next_nag_time - world.time,"mm:ss")].", RADIO_CHANNEL_SUPPLY)
+
+//for when you need a REAL bounty cube to test with and don't want to do a bounty each time your code changes
+/obj/item/bounty_cube/test_cube
+ name = "debug bounty cube"
+ desc = "Use in-hand to set it up with a random bounty. Requires an ID it can detect with a bank account attached. \
+ This will alert Supply over the radio with your name and location, and cargo techs will be dispatched to your location with kill on sight clearance."
+ var/set_up = FALSE
+
+/obj/item/bounty_cube/test_cube/attack_self(mob/user)
+ if(!isliving(user))
+ to_chat(user, "You aren't eligible to use this!")
+ return ..()
+
+ if(!set_up)
+ var/mob/living/squeezer = user
+ if(squeezer.get_bank_account())
+ set_up(random_bounty(), squeezer.get_idcard())
+ set_up = TRUE
+ return ..()
+ to_chat(user, "It can't detect your bank account.")
+
+ return ..()
///Beacon to launch a new bounty setup when activated.
diff --git a/code/modules/cargo/export_scanner.dm b/code/modules/cargo/export_scanner.dm
index 80fd05cf8c..dcb476c459 100644
--- a/code/modules/cargo/export_scanner.dm
+++ b/code/modules/cargo/export_scanner.dm
@@ -43,5 +43,24 @@
to_chat(user, "Scanned [O], value: [price] credits[O.contents.len ? " (contents included)" : ""].")
else
to_chat(user, "Scanned [O], no export value.")
- if(bounty_ship_item_and_contents(O, dry_run=TRUE))
- to_chat(user, "Scanned item is eligible for one or more bounties.")
+
+
+ if(ishuman(user))
+ var/mob/living/carbon/human/scan_human = user
+ if(istype(O, /obj/item/bounty_cube))
+ var/obj/item/bounty_cube/cube = O
+
+ if(!istype(get_area(cube), /area/shuttle/supply))
+ to_chat(user, "Shuttle placement not detected. Handling tip not registered.")
+
+ else if(cube.bounty_handler_account)
+ to_chat(user, "Bank account for handling tip already registered!")
+
+ else if(scan_human.get_bank_account() && cube.GetComponent(/datum/component/pricetag))
+ var/datum/component/pricetag/pricetag = cube.GetComponent(/datum/component/pricetag)
+ cube.bounty_handler_account = scan_human.get_bank_account()
+ pricetag.payees[cube.bounty_handler_account] += cube.handler_tip
+ cube.bounty_handler_account.bank_card_talk("Bank account for [price ? "[price * cube.handler_tip] credit " : ""]handling tip successfully registered.")
+ cube.bounty_holder_account.bank_card_talk("[cube] was scanned in \the [get_area(cube)] by [scan_human] ([scan_human.job]).")
+ else
+ to_chat(user, "Bank account not detected. Handling tip not registered.")
diff --git a/code/modules/cargo/exports/civilain_bounty.dm b/code/modules/cargo/exports/civilain_bounty.dm
index 3d443fbad0..58486f176b 100644
--- a/code/modules/cargo/exports/civilain_bounty.dm
+++ b/code/modules/cargo/exports/civilain_bounty.dm
@@ -5,4 +5,4 @@
export_types = list(/obj/item/bounty_cube)
/datum/export/bounty_box/get_cost(obj/item/bounty_cube/cube, allowed_categories, apply_elastic)
- return cube.bounty_value
+ return cube.bounty_value + (cube.bounty_value * (cube.speed_bonus / 100))
diff --git a/code/modules/mob/living/carbon/human/human_helpers.dm b/code/modules/mob/living/carbon/human/human_helpers.dm
index a3c6294aec..470eb626ae 100644
--- a/code/modules/mob/living/carbon/human/human_helpers.dm
+++ b/code/modules/mob/living/carbon/human/human_helpers.dm
@@ -123,16 +123,21 @@
to_chat(src, span_warning("You can't bring yourself to use a ranged weapon!"))
return FALSE
-/mob/living/carbon/human/proc/get_bank_account()
+//Returns the bank account of an ID the user may be holding.
+/mob/living/proc/get_bank_account()
RETURN_TYPE(/datum/bank_account)
var/datum/bank_account/account
var/obj/item/card/id/I = get_idcard()
- if(I && I.registered_account)
+ if(I?.registered_account)
account = I.registered_account
return account
- return FALSE
+/mob/living/proc/toggle_resting()
+ set name = "Rest"
+ set category = "IC"
+
+ set_resting(!resting, FALSE)
/mob/living/carbon/human/can_see_reagents()
. = ..()