diff --git a/code/game/machinery/slotmachine.dm b/code/game/machinery/slotmachine.dm
index ddfe76f56af..b7eec3994b8 100644
--- a/code/game/machinery/slotmachine.dm
+++ b/code/game/machinery/slotmachine.dm
@@ -10,7 +10,7 @@
#define JACKPOT 10000
#define SPIN_TIME 65 //As always, deciseconds.
#define REEL_DEACTIVATE_DELAY 7
-#define SEVEN "7 "
+#define JACKPOT_SEVENS FA_ICON_7
#define HOLOCHIP 1
#define COIN 2
@@ -24,7 +24,7 @@
density = TRUE
circuit = /obj/item/circuitboard/computer/slot_machine
light_color = LIGHT_COLOR_BROWN
- interaction_flags_machine = INTERACT_MACHINE_ALLOW_SILICON|INTERACT_MACHINE_SET_MACHINE // don't need to be literate to play slots
+ interaction_flags_machine = INTERACT_MACHINE_ALLOW_SILICON // don't need to be literate to play slots
var/money = 3000 //How much money it has CONSUMED
var/plays = 0
var/working = FALSE
@@ -32,9 +32,19 @@
var/jackpots = 0
var/paymode = HOLOCHIP //toggles between HOLOCHIP/COIN, defined above
var/cointype = /obj/item/coin/iron //default cointype
+ /// Icons that can be displayed by the slot machine.
+ var/static/list/icons = list(
+ FA_ICON_LEMON = list("value" = 2, "colour" = "yellow"),
+ FA_ICON_STAR = list("value" = 2, "colour" = "yellow"),
+ FA_ICON_BOMB = list("value" = 2, "colour" = "red"),
+ FA_ICON_BIOHAZARD = list("value" = 2, "colour" = "green"),
+ FA_ICON_APPLE_WHOLE = list("value" = 2, "colour" = "red"),
+ FA_ICON_7 = list("value" = 1, "colour" = "yellow"),
+ FA_ICON_DOLLAR_SIGN = list("value" = 2, "colour" = "green"),
+ )
+
var/static/list/coinvalues
var/list/reels = list(list("", "", "") = 0, list("", "", "") = 0, list("", "", "") = 0, list("", "", "") = 0, list("", "", "") = 0)
- var/list/symbols = list(SEVEN = 1, "& " = 2, "@ " = 2, "$ " = 2, "? " = 2, "# " = 2, "! " = 2, "% " = 2) //if people are winning too much, multiply every number in this list by 2 and see if they are still winning too much.
var/static/list/ray_filter = list(type = "rays", y = 16, size = 40, density = 4, color = COLOR_RED_LIGHT, factor = 15, flags = FILTER_OVERLAY)
/obj/machinery/computer/slot_machine/Initialize(mapload)
@@ -42,13 +52,13 @@
jackpots = rand(1, 4) //false hope
plays = rand(75, 200)
- INVOKE_ASYNC(src, PROC_REF(toggle_reel_spin), TRUE)//The reels won't spin unless we activate them
+ toggle_reel_spin_sync(1) //The reels won't spin unless we activate them
var/list/reel = reels[1]
for(var/i in 1 to reel.len) //Populate the reels.
randomize_reels()
- INVOKE_ASYNC(src, PROC_REF(toggle_reel_spin), FALSE)
+ toggle_reel_spin_sync(0)
if (isnull(coinvalues))
coinvalues = list()
@@ -84,46 +94,49 @@
icon_screen = "slots_screen"
return ..()
-/obj/machinery/computer/slot_machine/attackby(obj/item/I, mob/living/user, params)
- if(istype(I, /obj/item/coin))
- var/obj/item/coin/C = I
+
+/obj/machinery/computer/slot_machine/item_interaction(mob/living/user, obj/item/inserted, list/modifiers, is_right_clicking)
+ if(istype(inserted, /obj/item/coin))
+ var/obj/item/coin/inserted_coin = inserted
if(paymode == COIN)
if(prob(2))
- if(!user.transferItemToLoc(C, drop_location(), silent = FALSE))
+ if(!user.transferItemToLoc(inserted_coin, drop_location(), silent = FALSE))
return
- C.throw_at(user, 3, 10)
+ inserted_coin.throw_at(user, 3, 10)
if(prob(10))
balance = max(balance - SPIN_PRICE, 0)
to_chat(user, span_warning("[src] spits your coin back out!"))
else
- if(!user.temporarilyRemoveItemFromInventory(C))
+ if(!user.temporarilyRemoveItemFromInventory(inserted_coin))
return
- to_chat(user, span_notice("You insert [C] into [src]'s slot!"))
- balance += C.value
- qdel(C)
+ balloon_alert(user, "coin insterted")
+ balance += inserted_coin.value
+ qdel(inserted_coin)
else
- to_chat(user, span_warning("This machine is only accepting holochips!"))
- else if(istype(I, /obj/item/holochip))
+ balloon_alert(user, "holochips only!")
+
+ else if(istype(inserted, /obj/item/holochip))
if(paymode == HOLOCHIP)
- var/obj/item/holochip/H = I
- if(!user.temporarilyRemoveItemFromInventory(H))
+ var/obj/item/holochip/inserted_chip = inserted
+ if(!user.temporarilyRemoveItemFromInventory(inserted_chip))
return
- to_chat(user, span_notice("You insert [H.credits] holocredits into [src]'s slot!"))
- balance += H.credits
- qdel(H)
+ balloon_alert(user, "[inserted_chip.credits] credit[inserted_chip.credits == 1 ? "" : "s"] inserted")
+ balance += inserted_chip.credits
+ qdel(inserted_chip)
else
- to_chat(user, span_warning("This machine is only accepting coins!"))
- else if(I.tool_behaviour == TOOL_MULTITOOL)
+ balloon_alert(user, "coins only!")
+
+ else if(inserted.tool_behaviour == TOOL_MULTITOOL)
if(balance > 0)
visible_message("[src] says, 'ERROR! Please empty the machine balance before altering paymode'") //Prevents converting coins into holocredits and vice versa
else
if(paymode == HOLOCHIP)
paymode = COIN
- visible_message("[src] says, 'This machine now works with COINS!'")
+ balloon_alert(user, "now using coins")
else
paymode = HOLOCHIP
- visible_message("[src] says, 'This machine now works with HOLOCHIPS!'")
+ balloon_alert(user, "now using holochips")
else
return ..()
@@ -138,49 +151,54 @@
balloon_alert(user, "machine rigged")
return TRUE
-/obj/machinery/computer/slot_machine/ui_interact(mob/living/user)
+/obj/machinery/computer/slot_machine/ui_interact(mob/living/user, datum/tgui/ui)
+ . = ..()
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "SlotMachine", name)
+ ui.open()
+
+/obj/machinery/computer/slot_machine/ui_static_data(mob/user)
+ var/list/data = list()
+ data["icons"] = list()
+ for(var/icon_name in icons)
+ var/list/icon = icons[icon_name]
+ icon += list("icon" = icon_name)
+ data["icons"] += list(icon)
+ data["cost"] = SPIN_PRICE
+ data["jackpot"] = JACKPOT
+
+ return data
+
+/obj/machinery/computer/slot_machine/ui_data(mob/user)
+ var/list/data = list()
+ var/list/reel_states = list()
+ for(var/reel_state in reels)
+ reel_states += list(reel_state)
+ data["state"] = reel_states
+ data["balance"] = balance
+ data["working"] = working
+ data["money"] = money
+ data["plays"] = plays
+ data["jackpots"] = jackpots
+ data["paymode"] = paymode
+ return data
+
+
+/obj/machinery/computer/slot_machine/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
. = ..()
- var/reeltext = {"
- /*****^*****^*****^*****^*****\\
- | \[[reels[1][1]]\] | \[[reels[2][1]]\] | \[[reels[3][1]]\] | \[[reels[4][1]]\] | \[[reels[5][1]]\] |
- | \[[reels[1][2]]\] | \[[reels[2][2]]\] | \[[reels[3][2]]\] | \[[reels[4][2]]\] | \[[reels[5][2]]\] |
- | \[[reels[1][3]]\] | \[[reels[2][3]]\] | \[[reels[3][3]]\] | \[[reels[4][3]]\] | \[[reels[5][3]]\] |
- \\*****v*****v*****v*****v*****/
- "}
-
- var/dat
- if(working)
- dat = reeltext
-
- else
- dat = {"Five credits to play!
- Prize Money Available: [money] (jackpot payout is ALWAYS 100%!)
- Credit Remaining: [balance]
- [plays] players have tried their luck today, and [jackpots] have won a jackpot!
-
- Play!
-
- [reeltext]
- "}
- if(balance > 0)
- dat+="Refund balance "
-
- var/datum/browser/popup = new(user, "slotmachine", "Slot Machine")
- popup.set_content(dat)
- popup.open()
-
-/obj/machinery/computer/slot_machine/Topic(href, href_list)
- . = ..() //Sanity checks.
if(.)
- return .
-
- if(href_list["spin"])
- spin(usr)
+ return
- else if(href_list["refund"])
- if(balance > 0)
- give_payout(balance)
- balance = 0
+ switch(action)
+ if("spin")
+ spin(ui.user)
+ return TRUE
+ if("payout")
+ if(balance > 0)
+ give_payout(balance)
+ balance = 0
+ return TRUE
/obj/machinery/computer/slot_machine/emp_act(severity)
. = ..()
@@ -214,8 +232,6 @@
toggle_reel_spin(1)
update_appearance()
- updateDialog()
-
var/spin_loop = addtimer(CALLBACK(src, PROC_REF(do_spin)), 2, TIMER_LOOP|TIMER_STOPPABLE)
addtimer(CALLBACK(src, PROC_REF(finish_spinning), spin_loop, user, the_name), SPIN_TIME - (REEL_DEACTIVATE_DELAY * reels.len))
@@ -223,7 +239,6 @@
/obj/machinery/computer/slot_machine/proc/do_spin()
randomize_reels()
- updateDialog()
use_power(active_power_usage)
/obj/machinery/computer/slot_machine/proc/finish_spinning(spin_loop, mob/user, the_name)
@@ -232,50 +247,64 @@
deltimer(spin_loop)
give_prizes(the_name, user)
update_appearance()
- updateDialog()
+/// Check if the machine can be spun
/obj/machinery/computer/slot_machine/proc/can_spin(mob/user)
if(machine_stat & NOPOWER)
- to_chat(user, span_warning("The slot machine has no power!"))
+ balloon_alert(user, "no power!")
return FALSE
if(machine_stat & BROKEN)
- to_chat(user, span_warning("The slot machine is broken!"))
+ balloon_alert(user, "machine broken!")
return FALSE
if(working)
- to_chat(user, span_warning("You need to wait until the machine stops spinning before you can play again!"))
+ balloon_alert(user, "already spinning!")
return FALSE
if(balance < SPIN_PRICE)
- to_chat(user, span_warning("Insufficient money to play!"))
+ balloon_alert(user, "insufficient balance!")
return FALSE
return TRUE
+/// Sets the spinning states of all reels to value, with a delay between them
/obj/machinery/computer/slot_machine/proc/toggle_reel_spin(value, delay = 0) //value is 1 or 0 aka on or off
for(var/list/reel in reels)
if(!value)
playsound(src, 'sound/machines/ding_short.ogg', 50, TRUE, SHORT_RANGE_SOUND_EXTRARANGE)
reels[reel] = value
- sleep(delay)
+ if(delay)
+ sleep(delay)
+/// Same as toggle_reel_spin, but without the delay and runs synchronously
+/obj/machinery/computer/slot_machine/proc/toggle_reel_spin_sync(value)
+ for(var/list/reel in reels)
+ reels[reel] = value
+
+/// Randomize the states of all reels
/obj/machinery/computer/slot_machine/proc/randomize_reels()
for(var/reel in reels)
if(reels[reel])
reel[3] = reel[2]
reel[2] = reel[1]
- reel[1] = pick(symbols)
+ var/chosen = pick(icons)
+ reel[1] = icons[chosen] + list("icon_name" = chosen)
+/// Checks if any prizes have been won, and pays them out
/obj/machinery/computer/slot_machine/proc/give_prizes(usrname, mob/user)
var/linelength = get_lines()
var/did_player_win = TRUE
- if(reels[1][2] + reels[2][2] + reels[3][2] + reels[4][2] + reels[5][2] == "[SEVEN][SEVEN][SEVEN][SEVEN][SEVEN]")
- visible_message("[src] says, 'JACKPOT! You win [money] credits!'")
+ if(check_jackpot(FA_ICON_BOMB))
+ var/obj/item/grenade/flashbang/bang = new(get_turf(src))
+ bang.arm_grenade(null, 1 SECONDS)
+
+ else if(check_jackpot(JACKPOT_SEVENS))
+ var/prize = money + JACKPOT
+ visible_message("[src] says, 'JACKPOT! You win [prize] credits!'")
priority_announce("Congratulations to [user ? user.real_name : usrname] for winning the jackpot at the slot machine in [get_area(src)]!")
jackpots += 1
- balance += money - give_payout(JACKPOT)
money = 0
if(paymode == HOLOCHIP)
- new /obj/item/holochip(loc, JACKPOT)
+ new /obj/item/holochip(loc, prize)
else
for(var/i in 1 to 5)
cointype = pick(subtypesof(/obj/item/coin))
@@ -298,7 +327,8 @@
money = max(money - SPIN_PRICE * 4, money)
else
- to_chat(user, span_warning("No luck!"))
+ balloon_alert(user, "no luck!")
+ playsound(src, 'sound/machines/buzz-sigh.ogg', 50)
did_player_win = FALSE
if(did_player_win)
@@ -307,31 +337,38 @@
addtimer(CALLBACK(src, TYPE_PROC_REF(/datum, remove_filter), "jackpot_rays"), 3 SECONDS)
playsound(src, 'sound/machines/roulettejackpot.ogg', 50, TRUE)
+/// Checks for a jackpot (5 matching icons in the middle row) with the given icon name
+/obj/machinery/computer/slot_machine/proc/check_jackpot(name)
+ return reels[1][2]["icon_name"] + reels[2][2]["icon_name"] + reels[3][2]["icon_name"] + reels[4][2]["icon_name"] + reels[5][2]["icon_name"] == "[name][name][name][name][name]"
+
+/// Finds the largest number of consecutive matching icons in a row
/obj/machinery/computer/slot_machine/proc/get_lines()
var/amountthesame
for(var/i in 1 to 3)
- var/inputtext = reels[1][i] + reels[2][i] + reels[3][i] + reels[4][i] + reels[5][i]
- for(var/symbol in symbols)
+ var/inputtext = reels[1][i]["icon_name"] + reels[2][i]["icon_name"] + reels[3][i]["icon_name"] + reels[4][i]["icon_name"] + reels[5][i]["icon_name"]
+ for(var/icon in icons)
var/j = 3 //The lowest value we have to check for.
- var/symboltext = symbol + symbol + symbol
+ var/symboltext = icon + icon + icon
while(j <= 5)
if(findtext(inputtext, symboltext))
amountthesame = max(j, amountthesame)
j++
- symboltext += symbol
+ symboltext += icon
if(amountthesame)
break
return amountthesame
+/// Give the specified amount of money. If the amount is greater than the amount of prize money available, add the difference as balance
/obj/machinery/computer/slot_machine/proc/give_money(amount)
- var/amount_to_give = money >= amount ? amount : money
- var/surplus = amount_to_give - give_payout(amount_to_give)
- money = max(0, money - amount)
+ var/amount_to_give = min(amount, money)
+ var/surplus = amount - give_payout(amount_to_give)
+ money -= amount_to_give
balance += surplus
+/// Pay out the specified amount in either coins or holochips
/obj/machinery/computer/slot_machine/proc/give_payout(amount)
if(paymode == HOLOCHIP)
cointype = /obj/item/holochip
@@ -348,23 +385,25 @@
return amount
-/obj/machinery/computer/slot_machine/proc/dispense(amount = 0, cointype = /obj/item/coin/silver, mob/living/target, throwit = 0)
+/// Dispense the given amount. If machine is set to use coins, will use the specified coin type.
+/// If throwit and target are set, will launch the payment at the target
+/obj/machinery/computer/slot_machine/proc/dispense(amount = 0, cointype = /obj/item/coin/silver, throwit = FALSE, mob/living/target)
if(paymode == HOLOCHIP)
- var/obj/item/holochip/H = new /obj/item/holochip(loc,amount)
+ var/obj/item/holochip/chip = new /obj/item/holochip(loc,amount)
if(throwit && target)
- H.throw_at(target, 3, 10)
+ chip.throw_at(target, 3, 10)
else
var/value = coinvalues["[cointype]"]
if(value <= 0)
CRASH("Coin value of zero, refusing to payout in dispenser")
while(amount >= value)
- var/obj/item/coin/C = new cointype(loc) //DOUBLE THE PAIN
+ var/obj/item/coin/thrown_coin = new cointype(loc) //DOUBLE THE PAIN
amount -= value
if(throwit && target)
- C.throw_at(target, 3, 10)
+ thrown_coin.throw_at(target, 3, 10)
else
- random_step(C, 2, 40)
+ random_step(thrown_coin, 2, 40)
playsound(src, pick(list('sound/machines/coindrop.ogg', 'sound/machines/coindrop2.ogg')), 50, TRUE)
return amount
@@ -374,7 +413,7 @@
#undef HOLOCHIP
#undef JACKPOT
#undef REEL_DEACTIVATE_DELAY
-#undef SEVEN
+#undef JACKPOT_SEVENS
#undef SMALL_PRIZE
#undef SPIN_PRICE
#undef SPIN_TIME
diff --git a/tgui/packages/tgui/interfaces/SlotMachine.tsx b/tgui/packages/tgui/interfaces/SlotMachine.tsx
new file mode 100644
index 00000000000..6d6d464f913
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/SlotMachine.tsx
@@ -0,0 +1,151 @@
+import { useBackend } from '../backend';
+import { Button, Icon, Section } from '../components';
+import { Window } from '../layouts';
+
+type IconInfo = {
+ value: number;
+ colour: string;
+ icon_name: string;
+};
+
+type BackendData = {
+ icons: IconInfo[];
+ state: any[];
+ balance: number;
+ working: boolean;
+ money: number;
+ cost: number;
+ plays: number;
+ jackpots: number;
+ jackpot: number;
+ paymode: number;
+};
+
+type SlotsTileProps = {
+ icon: string;
+ color?: string;
+ background?: string;
+};
+
+type SlotsReelProps = {
+ reel: IconInfo[];
+};
+
+const pluralS = (amount: number) => {
+ return amount === 1 ? '' : 's';
+};
+
+const SlotsReel = (props: SlotsReelProps) => {
+ const { reel } = props;
+ return (
+
+ {reel.map((slot, i) => (
+
+ ))}
+
+ );
+};
+
+const SlotsTile = (props: SlotsTileProps) => {
+ return (
+
+
+
+ );
+};
+
+export const SlotMachine = (props) => {
+ const { act, data } = useBackend();
+ // icons: The list of possible icons, including colour and name
+ // backendState: the current state of the slots according to the backend
+ const {
+ plays,
+ jackpots,
+ money,
+ cost,
+ state,
+ balance,
+ jackpot,
+ working: rolling,
+ paymode,
+ } = data;
+
+ return (
+
+
+
+
+ Only {cost} credit{pluralS(cost)} for a chance to win big!
+
+
+ Available prize money:{' '}
+
+ {money} credit{pluralS(money)}
+ {' '}
+
+ {paymode === 1 && (
+
+ Current jackpot:{' '}
+
+ {money + jackpot} credit{pluralS(money + jackpot)}!
+
+
+ )}
+
+ So far people have spun{' '}
+
+ {plays} time{pluralS(plays)},
+ {' '}
+ and won{' '}
+
+ {jackpots} jackpot{pluralS(jackpots)}!
+
+
+
+
+
+ {state.map((reel, i) => {
+ return ;
+ })}
+
+
+ act('spin')}
+ disabled={rolling || balance < cost}
+ >
+ Spin!
+
+
+ Balance: {balance}
+
+ act('payout')} disabled={!(balance > 0)}>
+ Refund balance
+
+
+
+
+ );
+};