Files
Bubberstation/code/modules/cargo/coupon.dm
Ghom fec7ccc6fd The Coupon Master PDA app (#80240)
## About The Pull Request
This PR adds a new PDA program to the supply category, which allows
users to redeems coupons for various cargo packs (mostly goodies), like
the ones also found at the bottom of cig packs.

How it works is fairly simple: 

- Once installed, the modular computer subsystem will periodically, at a
3-5 minutes interval, generate a coupon code datum associated to a plain
text code, which is sent out to everyone with the program installed.
- The user can then open the program and insert the text into an input
box to redeem the coupon code, which is then associated with their bank
account.
- He will then have to find a photocopier, and tap it with the PDA to
print the coupon. Only one coupon can be printed. Photocopier fees
apply, so it'd cost 5 creds to the average assistant to print the
coupon.
- He can then insert the coupon in a cargo console and order/reuest the
associated pack (same deal as cig coupons).
- Some coupon codes however, especially those with juicer discounts,
will expire after a while if not printed.

Albeit mostly innocuous, the program provides negative Detomatix
resistance, slowly fills the computer file storage with trash files with
each redeemed coupon, and halves the download speed of new apps. Not
really the cleanest ware out there.

This PR also extends coupons to several non-goody packs, since they have
been privately buyable for over the last couple years now. Some packs
get discounts less frequently however, with those in the uncommon
category being roughly 1 in a 12 chance and the rare being 1 in 50.

Here's a screenshot of the UI (outdated, I've reduced the height from
500 to 400 and the notice box tip to specify the right click):
![Coupon
Master](https://github.com/tgstation/tgstation/assets/42542238/1d242d09-0f62-4e2e-8a6e-014daa3f6a55)

Fun fact: Right now, the odds of a 75% discount coming from the Coupon
Master for the 1.000.000 credits bycycle pack are roughly 0.0012%, while
that of a 50% for the same pack, from a cig pack coupon are 0.0042%.

## Why It's Good For The Game
These last couple days I've been wanting to test myself at making simple
UIs, as well as contributing to the modular computers feature, which has
started to become pretty neat ever since PDAs were reworked into a
subtype of it.

Beside, coupons are a very small feature limited to the bottom of
cigarette packs (also possibly cursed) in the current state of affairs.
Cargo is filled with packs that are niche or fluff. Modular computers
also has those little things that, while interesting, do not contribute
a whole lot. Maybe this is one of them, but I guess free* coupons are
always a big W.

## Changelog

🆑
add: Added the 'Coupon Master' program for the PDA. Install it to
receive periodical, redeemable coupons for several cargo packs. Requires
NTnet connection and the messenger enabled to work.
add: Coupons are no longer only limited to goodies, but may also apply
discount to some other packs as well.
/🆑

<sup>*minus the photocopier fee</sup>

---------

Co-authored-by: Watermelon914 <37270891+Watermelon914@users.noreply.github.com>
2023-12-25 11:19:56 -08:00

116 lines
4.2 KiB
Plaintext

///datum used by the Coupon Master PDA app to generate coupon items redeemed by a bank account.
/datum/coupon_code
///The pack that'll receive the discount
var/datum/supply_pack/discounted_pack
///The discount of the pack, on a 0 to 1 range.
var/discount
/**
* If set, copies of the coupon code will delete itself after a while if not printed.
* The ones SSmodular_computer.discount_coupons stay intact.
*/
var/expires_in
///Has the coupon been printed. Dictates in which section it's shown, and that it cannot be printed again.
var/printed = FALSE
///The timerid for deletion if expires_in is set.
var/timerid
///Reference to the associated bank account, since we need to clear refs on deletion.
var/datum/bank_account/associated_account
/datum/coupon_code/New(discount, discounted_pack, expires_in)
..()
src.discounted_pack = discounted_pack
src.discount = discount
if(expires_in)
src.expires_in = world.time + expires_in
/datum/coupon_code/Destroy()
if(associated_account)
associated_account.redeemed_coupons -= src
associated_account = null
return ..()
/datum/coupon_code/proc/copy(datum/bank_account/account)
var/datum/coupon_code/copy = new(discount, discounted_pack, expires_in)
copy.associated_account = account
if(account)
LAZYADD(account.redeemed_coupons, src)
if(expires_in)
copy.timerid = QDEL_IN_STOPPABLE(copy, expires_in - world.time)
/datum/coupon_code/proc/generate()
var/obj/item/coupon/coupon = new()
coupon.generate(discount, discounted_pack)
printed = TRUE
deltimer(timerid)
timerid = null
return coupon
/obj/item/coupon
name = "coupon"
desc = "It doesn't matter if you didn't want it before, what matters now is that you've got a coupon for it!"
icon_state = "data_1"
icon = 'icons/obj/card.dmi'
item_flags = NOBLUDGEON
w_class = WEIGHT_CLASS_TINY
var/datum/supply_pack/discounted_pack
var/discount_pct_off = 0.05
var/obj/machinery/computer/cargo/inserted_console
/// Choose what our prize is :D
/obj/item/coupon/proc/generate(discount, datum/supply_pack/discounted_pack)
src.discounted_pack = discounted_pack || pick(GLOB.discountable_packs[pick_weight(GLOB.pack_discount_odds)])
var/static/list/chances = list("0.10" = 4, "0.15" = 8, "0.20" = 10, "0.25" = 8, "0.50" = 4, COUPON_OMEN = 1)
discount_pct_off = discount || pick_weight(chances)
if(discount_pct_off != COUPON_OMEN)
if(!discount) // the discount arg should be a number already, while the keys in the chances list cannot be numbers
discount_pct_off = text2num(discount_pct_off)
name = "coupon - [round(discount_pct_off * 100)]% off [initial(src.discounted_pack.name)]"
return
name = "coupon - fuck you"
desc = "The small text reads, 'You will be slaughtered'... That doesn't sound right, does it?"
if(!ismob(loc))
return FALSE
var/mob/cursed = loc
to_chat(cursed, span_warning("The coupon reads '<b>fuck you</b>' in large, bold text... is- is that a prize, or?"))
if(!cursed.GetComponent(/datum/component/omen))
cursed.AddComponent(/datum/component/omen)
return TRUE
if(HAS_TRAIT(cursed, TRAIT_CURSED))
to_chat(cursed, span_warning("What a horrible night... To have a curse!"))
addtimer(CALLBACK(src, PROC_REF(curse_heart), cursed), 5 SECONDS, TIMER_UNIQUE | TIMER_STOPPABLE)
/// Play stupid games, win stupid prizes
/obj/item/coupon/proc/curse_heart(mob/living/cursed)
if(!iscarbon(cursed))
cursed.gib(DROP_ALL_REMAINS)
return TRUE
var/mob/living/carbon/player = cursed
INVOKE_ASYNC(player, TYPE_PROC_REF(/mob, emote), "scream")
to_chat(player, span_mind_control("What could that coupon mean?"))
to_chat(player, span_userdanger("...The suspense is killing you!"))
player.set_heartattack(status = TRUE)
/obj/item/coupon/attack_atom(obj/O, mob/living/user, params)
if(!istype(O, /obj/machinery/computer/cargo))
return ..()
if(discount_pct_off == COUPON_OMEN)
to_chat(user, span_warning("\The [O] validates the coupon as authentic, but refuses to accept it..."))
O.say("Coupon fulfillment already in progress...")
return
inserted_console = O
LAZYADD(inserted_console.loaded_coupons, src)
inserted_console.say("Coupon for [initial(discounted_pack.name)] applied!")
forceMove(inserted_console)
/obj/item/coupon/Destroy()
if(inserted_console)
LAZYREMOVE(inserted_console.loaded_coupons, src)
inserted_console = null
. = ..()