Fixes CRAB-17 sometimes not draining funds (#92335)

## About The Pull Request

Fixes #92230

Fixes an issue where the CRAB-17 would sometimes fail to drain funds due
to being unable to send money to an account.

In `dump()`:

```
var/datum/bank_account/account = bogdanoff?.get_bank_account()
if (account) // get_bank_account() may return FALSE
	account.transfer_money(B, amount, "?VIVA¿: !LA CRABBE¡")
	B.bank_card_talk("You have lost [percentage_lost * 100]% of your funds! A spacecoin credit deposit machine is located at: [get_area(src)].")
```

If during any call of `dump()` our Bogdanoff takes off their ID, never
had one to begin with, or otherwise makes their bank account not visible
to `get_bank_account()` the `if(account)` check fails and money is never
drained.

Instead this PR adds an internal bank account to the CRAB-17, and any
time `dump()` fails to find a destination account the money is stored in
the CRAB-17's account. When destroyed any funds stored internally are
scattered across the floor in a pile of smaller holochips.

Incidentally adds documentation to the relevant code.

## Why It's Good For The Game

Players expect the CRAB-17 to drain funds even if they don't have an
account to send the money to. It's still preferable to have one so they
get the money, but smearing it across the floor still forces the crew to
fight over who gets it, and is more logical than sending it to the void.
This commit is contained in:
Thunder12345
2025-08-08 19:06:22 +01:00
committed by GitHub
parent 402143e597
commit 6fa5cb32db
+55 -9
View File
@@ -6,6 +6,7 @@
w_class = WEIGHT_CLASS_SMALL
attack_verb_continuous = list("dumps")
attack_verb_simple = list("dump")
/// Has the phone been used already?
var/dumped = FALSE
/obj/item/suspiciousphone/attack_self(mob/living/user)
@@ -32,11 +33,11 @@
new /obj/effect/dumpeet_target(targetturf, L)
to_chat(user, span_notice("You have activated Protocol CRAB-17."))
message_admins("[ADMIN_LOOKUPFLW(user)] has activated Protocol CRAB-17.")
user.log_message("activated Protocol CRAB-17.", LOG_GAME)
dumped = TRUE
/obj/structure/checkoutmachine
name = "\improper Nanotrasen Space-Coin Market"
desc = "This is good for spacecoin because"
@@ -48,15 +49,23 @@
density = TRUE
pixel_z = -8
max_integrity = 5000
/// List of bank accounts to take money from, determines in start_dumping()
var/list/accounts_to_rob
/// The original user of the suspicious phone
var/mob/living/bogdanoff
/// Are we able to start moving?
var/canwalk = FALSE
/// Our own internal bank account, serves as a fallback to transfer money to if Bogdanoff doesn't have one
var/datum/bank_account/internal_account
/obj/structure/checkoutmachine/examine(mob/living/user)
. = ..()
. += span_info("It has a flashing <b>ID card reader</b> for convenient cashing out.")
/**
* Check whether any accounts in the accounts_to_rob list are still being drained.
* Returns TRUE if no accounts are being drained, FALSE otherwise
*/
/obj/structure/checkoutmachine/proc/check_if_finished()
for(var/i in accounts_to_rob)
var/datum/bank_account/B = i
@@ -103,6 +112,7 @@
if(QDELETED(src))
return
bogdanoff = user
internal_account = new /datum/bank_account/remote("CRAB-17", 0, player_account = FALSE)
add_overlay("flaps")
add_overlay("hatch")
add_overlay("legs_retracted")
@@ -110,7 +120,9 @@
QDEL_IN(src, 8 MINUTES) //Self-destruct after 8 min
ADD_TRAIT(SSeconomy, TRAIT_MARKET_CRASHING, REF(src))
/**
* Starts the dumping process and plays a start-up animation before the checkout starts walking.
*/
/obj/structure/checkoutmachine/proc/startUp() //very VERY snowflake code that adds a neat animation when the pod lands.
start_dumping() //The machine doesnt move during this time, giving people close by a small window to grab their funds before it starts running around
sleep(1 SECONDS)
@@ -171,17 +183,23 @@
add_overlay("screen_lines")
cut_overlay("text")
add_overlay("text")
START_PROCESSING(SSfastprocess, src) // we only start doing economy draining stuff once our machinery is initialized, thematically
START_PROCESSING(SSfastprocess, src)
canwalk = TRUE
/obj/structure/checkoutmachine/Destroy()
stop_dumping()
STOP_PROCESSING(SSfastprocess, src)
priority_announce("The credit deposit machine at [get_area(src)] has been destroyed. Station funds have stopped draining!", sender_override = "CRAB-17 Protocol")
if(internal_account.account_balance)
expel_cash()
QDEL_NULL(internal_account)
explosion(src, light_impact_range = 1, flame_range = 2)
REMOVE_TRAIT(SSeconomy, TRAIT_MARKET_CRASHING, REF(src))
return ..()
/**
* Grabs the accounts to be robbed and puts them in accounts_to_rob, tells the accounts they're being drained and calls dump() to start draining.
*/
/obj/structure/checkoutmachine/proc/start_dumping()
accounts_to_rob = flatten_list(SSeconomy.bank_accounts_by_id)
accounts_to_rob -= bogdanoff?.get_bank_account()
@@ -190,6 +208,11 @@
B.dumpeet()
dump()
/**
* For each account being drained, pulls a random percentage of cash out the account and sends it to Bogdanoff's account.
* If Bogdanoff did not have a bank account, stores the funds in the checkout's internal_account.
* Sets a timer to call itself again after an interval.
*/
/obj/structure/checkoutmachine/proc/dump()
var/percentage_lost = (rand(5, 15) / 100)
for(var/i in accounts_to_rob)
@@ -198,10 +221,9 @@
accounts_to_rob -= B
continue
var/amount = round(B.account_balance * percentage_lost) // We don't want fractions of a credit stolen. That's just agony for everyone.
var/datum/bank_account/account = bogdanoff?.get_bank_account()
if (account) // get_bank_account() may return FALSE
account.transfer_money(B, amount, "?VIVA¿: !LA CRABBE¡")
B.bank_card_talk("You have lost [percentage_lost * 100]% of your funds! A spacecoin credit deposit machine is located at: [get_area(src)].")
var/datum/bank_account/account = bogdanoff?.get_bank_account() || internal_account
account.transfer_money(B, amount, "?VIVA¿: !LA CRABBE¡")
B.bank_card_talk("You have lost [percentage_lost * 100]% of your funds! A spacecoin credit deposit machine is located at: [get_area(src)].")
addtimer(CALLBACK(src, PROC_REF(dump)), 15 SECONDS) //Drain every 15 seconds
/obj/structure/checkoutmachine/process()
@@ -209,12 +231,31 @@
if(Process_Spacemove(anydir))
Move(get_step(src, anydir), anydir)
/**
* Goes through accounts_to_rob and tells every account that the drain has stopped.
*/
/obj/structure/checkoutmachine/proc/stop_dumping()
for(var/i in accounts_to_rob)
var/datum/bank_account/B = i
if(B)
B.being_dumped = FALSE
/**
* Splits the balance of the internal_account into several smaller piles of cash and scatters them around the area.
*/
/obj/structure/checkoutmachine/proc/expel_cash()
var/funds_remaining = internal_account.account_balance
var/safety = funds_remaining + 1 // In the absolute worst case scenario the loop will complete in funds_remaining steps, if this is counter reaches 0 something went terribly wrong and we need to leave
while(floor(funds_remaining))
var/amount_to_remove = min(funds_remaining, rand(1, round(internal_account.account_balance)/8))
var/obj/item/holochip/holochip = new (get_turf(src), amount_to_remove)
funds_remaining -= amount_to_remove
holochip.throw_at(pick(oview(7,get_turf(src))),10,1)
safety -= 1
if(safety <= 0)
CRASH("/obj/structure/checkoutmachine/proc/expel_cash() did not complete in the theoretical maximum number of steps. Starting value: [internal_account.account_balance]. Value at crash: [funds_remaining].")
/obj/effect/dumpeet_fall //Falling pod
name = ""
icon = 'icons/obj/machines/money_machine_64.dmi'
@@ -224,6 +265,7 @@
plane = ABOVE_GAME_PLANE
icon_state = "missile_blur"
/obj/effect/dumpeet_target
name = "Landing Zone Indicator"
desc = "A holographic projection designating the landing zone of something. It's probably best to stand back."
@@ -242,6 +284,9 @@
sound_to_playing_players('sound/items/dump_it.ogg', 20)
deadchat_broadcast("Protocol CRAB-17 has been activated. A space-coin market has been launched at the station!", turf_target = get_turf(src), message_type=DEADCHAT_ANNOUNCEMENT)
/**
* Sets up the falling animation for the checkout machine.
*/
/obj/effect/dumpeet_target/proc/startLaunch()
DF = new /obj/effect/dumpeet_fall(drop_location())
dump = new /obj/structure/checkoutmachine(null, bogdanoff)
@@ -250,8 +295,9 @@
playsound(src, 'sound/items/weapons/mortar_whistle.ogg', 70, TRUE, 6)
addtimer(CALLBACK(src, PROC_REF(endLaunch)), 5, TIMER_CLIENT_TIME) //Go onto the last step after a very short falling animation
/**
* Cleans up after the falling animation.
*/
/obj/effect/dumpeet_target/proc/endLaunch()
QDEL_NULL(DF) //Delete the falling machine effect, because at this point its animation is over. We dont use temp_visual because we want to manually delete it as soon as the pod appears
playsound(src, SFX_EXPLOSION, 80, TRUE)