diff --git a/code/game/machinery/roulette_machine.dm b/code/game/machinery/roulette_machine.dm index d83065da852c..213273069ddf 100644 --- a/code/game/machinery/roulette_machine.dm +++ b/code/game/machinery/roulette_machine.dm @@ -54,6 +54,11 @@ jackpot_loop = new(list(src), FALSE) wires = new /datum/wires/roulette(src) +/obj/machinery/roulette/Destroy() + QDEL_NULL(jackpot_loop) + my_card = null + . = ..() + /obj/machinery/roulette/obj_break(damage_flag) prize_theft(0.05) . = ..() @@ -71,16 +76,18 @@ data["IsAnchored"] = anchored data["BetAmount"] = chosen_bet_amount data["BetType"] = chosen_bet_type - data["HouseBalance"] = my_card?.registered_account.account_balance + data["HouseBalance"] = my_card?.registered_account?.account_balance || 0 data["LastSpin"] = last_spin data["Spinning"] = playing - var/mob/living/carbon/human/H = user - var/obj/item/card/id/C = H.get_idcard(TRUE) - if(C) - data["AccountBalance"] = C.registered_account.account_balance + + if(ishuman(user)) + var/mob/living/carbon/human/human_user = user + var/obj/item/card/id/id_card = human_user.get_idcard(TRUE) + data["AccountBalance"] = id_card?.registered_account?.account_balance || 0 + data["CanUnbolt"] = (id_card == my_card) else data["AccountBalance"] = 0 - data["CanUnbolt"] = (C == my_card) + data["CanUnbolt"] = FALSE return data @@ -108,17 +115,29 @@ return if(playing) return ..() - if(istype(W, /obj/item/card/id)) - playsound(src, 'sound/machines/card_slide.ogg', 50, TRUE) + var/obj/item/card/id/player_card = W.GetID() + if(player_card) + if(istype(W, /obj/item/card/id)) + playsound(src, 'sound/machines/card_slide.ogg', 50, TRUE) + else + playsound(src, 'sound/machines/terminal_success.ogg', 50, TRUE) if(machine_stat & MAINT || !on || locked) to_chat(user, "The machine appears to be disabled.") return FALSE + if(!player_card.registered_account) + say("You don't have a bank account!") + playsound(src, 'sound/machines/buzz-two.ogg', 30, TRUE) + return FALSE + if(my_card) - var/obj/item/card/id/player_card = W + if(istype(player_card, /obj/item/card/id/departmental_budget)) // Are they using a department ID + say("You cannot gamble with the department budget!") + playsound(src, 'sound/machines/buzz-two.ogg', 30, TRUE) + return FALSE if(player_card.registered_account.account_balance < chosen_bet_amount) //Does the player have enough funds - audible_message("You do not have the funds to play! Lower your bet or get more money.") + say("You do not have the funds to play! Lower your bet or get more money.") playsound(src, 'sound/machines/buzz-two.ogg', 30, TRUE) return FALSE if(!chosen_bet_amount || isnull(chosen_bet_type)) @@ -145,8 +164,8 @@ potential_payout_mult = ROULETTE_SIMPLE_PAYOUT var/potential_payout = chosen_bet_amount * potential_payout_mult - if(!check_bartender_funds(potential_payout)) - return FALSE //bartender is too poor + if(!check_owner_funds(potential_payout)) + return FALSE //owner is too poor if(last_anti_spam > world.time) //do not cheat me return FALSE @@ -161,21 +180,30 @@ addtimer(CALLBACK(src, .proc/play, user, player_card, chosen_bet_type, chosen_bet_amount, potential_payout), 4) //Animation first return TRUE else - var/obj/item/card/id/new_card = W - if(new_card.registered_account) - var/msg = stripped_input(user, "Name of your roulette wheel:", "Roulette Naming", "Roulette Machine") - if(!msg) - return - name = msg - desc = "Owned by [new_card.registered_account.account_holder], draws directly from [user.p_their()] account." - my_card = new_card - to_chat(user, "You link the wheel to your account.") - power_change() + var/msg = stripped_input(user, "Name of your roulette wheel:", "Roulette Naming", "Roulette Machine") + if(!msg) return + name = msg + desc = "Owned by [player_card.registered_account.account_holder], draws directly from [user.p_their()] account." + my_card = player_card + RegisterSignal(my_card, COMSIG_PARENT_QDELETING, .proc/on_my_card_deleted) + to_chat(user, "You link the wheel to your account.") + power_change() + return return ..() +///deletes the my_card ref to prevent harddels +/obj/machinery/roulette/proc/on_my_card_deleted(datum/source) + SIGNAL_HANDLER + my_card = null + ///Proc called when player is going to try and play /obj/machinery/roulette/proc/play(mob/user, obj/item/card/id/player_id, bet_type, bet_amount, potential_payout) + if(!my_card?.registered_account) // Something happened to my_card during the 0.4 seconds delay of the timed callback. + icon_state = "idle" + flick("flick_down", src) + playsound(src, 'sound/machines/piston_lower.ogg', 70) + return var/payout = potential_payout @@ -204,24 +232,30 @@ var/color = numbers["[rolled_number]"] //Weird syntax, but dict uses strings. var/result = "[rolled_number] [color]" //e.g. 31 black - audible_message("The result is: [result]") + say("The result is: [result]") playing = FALSE update_icon(potential_payout, color, rolled_number, is_winner) handle_color_light(color) if(!is_winner) - audible_message("You lost! Better luck next time") + say("You lost! Better luck next time") playsound(src, 'sound/machines/synth_no.ogg', 50) return FALSE - audible_message("You have won [potential_payout] ahn! Congratulations!") + // Prevents money generation exploits. Doesn't prevent the owner being a scrooge and running away with the money. + var/account_balance = my_card?.registered_account?.account_balance + potential_payout = (account_balance >= potential_payout) ? potential_payout : account_balance + + say("You have won [potential_payout] ahn! Congratulations!") playsound(src, 'sound/machines/synth_yes.ogg', 50) dispense_prize(potential_payout) ///Fills a list of coins that should be dropped. /obj/machinery/roulette/proc/dispense_prize(payout) + if(!payout) + return if(payout >= ROULETTE_JACKPOT_AMOUNT) jackpot_loop.start() @@ -270,7 +304,7 @@ if(locked) return locked = TRUE - var/stolen_cash = my_card.registered_account.account_balance * percentage + var/stolen_cash = my_card?.registered_account?.account_balance * percentage //Byond evaluates nulls as 0 in numeric contexts (such as math operations) dispense_prize(stolen_cash) @@ -284,7 +318,7 @@ if(ROULETTE_BET_ODD) return ISODD(rolled_number) if(ROULETTE_BET_EVEN) - return ISEVEN(rolled_number) + return (ISEVEN(rolled_number) && rolled_number != 0) if(ROULETTE_BET_1TO18) return (rolled_number >= 1 && rolled_number <= 18) //between 1 to 18 if(ROULETTE_BET_19TO36) @@ -315,10 +349,10 @@ ///Returns TRUE if the owner has enough funds to payout -/obj/machinery/roulette/proc/check_bartender_funds(payout) +/obj/machinery/roulette/proc/check_owner_funds(payout) if(my_card.registered_account.account_balance >= payout) return TRUE //We got the betting amount - audible_message("The bank account of [my_card.registered_account.account_holder] does not have enough funds to pay out the potential prize, contact them to fill up their account or lower your bet!") + say("The bank account of [my_card.registered_account.account_holder] does not have enough funds to pay out the potential prize, contact them to fill up their account or lower your bet!") playsound(src, 'sound/machines/buzz-two.ogg', 30, TRUE) return FALSE @@ -391,7 +425,7 @@ /obj/item/roulette_wheel_beacon name = "roulette wheel beacon" - desc = "N.T. approved roulette wheel beacon, toss it down and you will have a complementary roulette wheel delivered to you." + desc = "Head approved roulette wheel beacon, toss it down and you will have a complementary roulette wheel delivered to you." icon = 'icons/obj/objects.dmi' icon_state = "floor_beacon" var/used diff --git a/code/modules/projectiles/guns/ballistic.dm b/code/modules/projectiles/guns/ballistic.dm index 86001cda7b59..f3f565330d23 100644 --- a/code/modules/projectiles/guns/ballistic.dm +++ b/code/modules/projectiles/guns/ballistic.dm @@ -89,6 +89,8 @@ var/flip_cooldown = 0 var/suppressor_x_offset ///pixel offset for the suppressor overlay on the x axis. var/suppressor_y_offset ///pixel offset for the suppressor overlay on the y axis. + /// Check if you are able to see if a weapon has a bullet loaded in or not. + var/hidden_chambered = FALSE ///Gun internal magazine modification and misfiring @@ -452,7 +454,7 @@ var/count_chambered = !(bolt_type == BOLT_TYPE_NO_BOLT || bolt_type == BOLT_TYPE_OPEN) . += "It has [get_ammo(count_chambered)] round\s remaining." - if (!chambered) + if (!chambered && !hidden_chambered) . += "It does not seem to have a round chambered." if (bolt_locked) . += "The [bolt_wording] is locked back and needs to be released before firing or de-fouling." diff --git a/code/modules/projectiles/guns/ballistic/revolver.dm b/code/modules/projectiles/guns/ballistic/revolver.dm index 83e3138a4e5b..914325046d6f 100644 --- a/code/modules/projectiles/guns/ballistic/revolver.dm +++ b/code/modules/projectiles/guns/ballistic/revolver.dm @@ -132,6 +132,7 @@ icon_state = "russianrevolver" mag_type = /obj/item/ammo_box/magazine/internal/cylinder/rus357 var/spun = FALSE + hidden_chambered = TRUE //Cheater. /obj/item/gun/ballistic/revolver/russian/do_spin() . = ..() diff --git a/tgui/packages/tgui/interfaces/Roulette.js b/tgui/packages/tgui/interfaces/Roulette.js index d3b1cd51c307..3a60b48c708b 100644 --- a/tgui/packages/tgui/interfaces/Roulette.js +++ b/tgui/packages/tgui/interfaces/Roulette.js @@ -1,231 +1,156 @@ import { classes } from 'common/react'; import { useBackend, useLocalState } from '../backend'; -import { Box, Button, Grid, NumberInput } from '../components'; +import { Box, Button, Grid, NumberInput, Table } from '../components'; import { Window } from '../layouts'; const getNumberColor = number => { - if (number === 0) { - return 'green'; - } - - const evenRedRanges = [ - [1, 10], - [19, 28], - ]; - let oddRed = true; + const inRedOddRange = ( + (number >= 1 && number <= 10) + || (number >= 19 && number <= 28) + ); - for (let i = 0; i < evenRedRanges.length; i++) { - let range = evenRedRanges[i]; - if (number >= range[0] && number <= range[1]) { - oddRed = false; - break; - } + if (number % 2 === 1) { + return inRedOddRange ? 'red' : 'black'; } - - const isOdd = (number % 2 === 0); - - return (oddRed ? isOdd : !isOdd) ? 'red' : 'black'; + return inRedOddRange ? 'black' : 'red'; }; -export const RouletteNumberButton = (props, context) => { - const { number } = props; +export const RouletteNumberCell = (props, context) => { + const { + buttonClass = null, + cellClass = null, + color, + colspan = "1", + rowspan = "1", + text, + value, + } = props; const { act } = useBackend(context); return ( - act('ChangeBetType', { type: number.toString() })} - /> + + act('ChangeBetType', { type: value })} + > + {text} + + ); }; -export const RouletteBoard = (props, context) => { - const { act } = useBackend(context); - +export const RouletteBoard = () => { const firstRow = [3, 6, 9, 12, 15, 18, 21, 24, 27, 30, 33, 36]; const secondRow = [2, 5, 8, 11, 14, 17, 20, 23, 26, 29, 32, 35]; const thirdRow = [1, 4, 7, 10, 13, 16, 19, 22, 25, 28, 31, 34]; + const fourthRow = { + "s1-12": "1st 12", + "s13-24": "2nd 12", + "s25-36": "3rd 12", + }; + const fifthRow = [ + { color: "transparent", text: "1-18", value: "s1-18" }, + { color: "transparent", text: "Even", value: "even" }, + { color: "black", text: "Black", value: "black" }, + { color: "red", text: "Red", value: "red" }, + { color: "transparent", text: "Odd", value: "odd" }, + { color: "transparent", text: "19-36", value: "s19-36" }, + ]; return ( - - - - act('ChangeBetType', { type: "0" })} - /> - - {firstRow.map(number => ( - - - - ))} - - act('ChangeBetType', { type: "s3rd col" })} - /> - - - - {secondRow.map(number => ( - - - - ))} - - + + + act('ChangeBetType', { type: "s2nd col" })} + rowspan="3" + text="0" + value="0" /> - - - - {thirdRow.map(number => ( - - - - ))} - - act('ChangeBetType', { type: "s1st col" })} - /> - - - - - - act('ChangeBetType', { type: "s1-12" })} - /> - - - act('ChangeBetType', { type: "s13-24" })} - /> - - - act('ChangeBetType', { type: "s25-36" })} - /> - - - - - - act('ChangeBetType', { type: "s1-18" })} - /> - - - ( + + ))} + act('ChangeBetType', { type: "even" })} + text="2 to 1" + value="s3rd col" /> - - - act('ChangeBetType', { type: "black" })} - /> - - - act('ChangeBetType', { type: "red" })} - /> - - - + + {secondRow.map(number => ( + + ))} + act('ChangeBetType', { type: "odd" })} + text="2 to 1" + value="s2nd col" /> - - - + + {thirdRow.map(number => ( + + ))} + act('ChangeBetType', { type: "s19-36" })} + text="2 to 1" + value="s1st col" /> - - - + + + + {Object.entries(fourthRow).map(([value, text]) => ( + + ))} + + + + {fifthRow.map(cell => ( + + ))} + + + ); }; @@ -246,35 +171,35 @@ export const RouletteBetTable = (props, context) => { } return ( - - - + + - Last Spun: - - + Current Bet: - - - - + + + {data.LastSpin} - - + { bold mt={1} mb={1} - fontSize="25px" + fontSize="20px" textAlign="center"> {data.BetAmount} cr on {BetType} @@ -339,10 +264,10 @@ export const RouletteBetTable = (props, context) => { - - - - + + + + { textAlign="center"> Swipe an ID card with a connected account to spin! - - - - + + + + House Balance: {data.HouseBalance ? data.HouseBalance + ' cr': "None"} - - + + { textAlign="center" onClick={() => act('anchor')} /> - - - + + + ); }; export const Roulette = (props, context) => { return ( diff --git a/tgui/packages/tgui/styles/interfaces/Roulette.scss b/tgui/packages/tgui/styles/interfaces/Roulette.scss index 04e39db7c6eb..0214fd88d526 100644 --- a/tgui/packages/tgui/styles/interfaces/Roulette.scss +++ b/tgui/packages/tgui/styles/interfaces/Roulette.scss @@ -1,61 +1,61 @@ @use '../base.scss'; @use '../colors.scss'; -.Roulette { - font-family: Palatino; +.Roulette__container { + display: flex; } -.Roulette__board { - display: table; - width: 100%; - border-collapse: collapse; - border: 2px solid white; +.Roulette__board-cell { + display: table-cell; + padding: 0; margin: 0; + border: 1px solid colors.$white; + vertical-align: bottom; } -.Roulette__board-row { - padding: 0; - margin: 0; +.Roulette__board-cell-number { + width: 35px; } -.Roulette__board-cell { - display: table-cell; +.Roulette__board-cell-number--colspan-2 { + width: 71px; +} + +.Roulette__board-cell-number--colspan-4 { + width: 143px; +} + +.Roulette__board-button { + display: table-cell !important; + border: none !important; + width: inherit; + height: 40px; padding: 0; margin: 0; - border: 2px solid white; - font-family: Palatino; - - &:first-child { - padding-left: 0; - } + text-align: center; + vertical-align: middle; + color: colors.$white !important; +} - &:last-child { - padding-right: 0; - } +.Roulette__board-button--rowspan-3 { + height: 122px; } -.Roulette__board-extrabutton { +.Roulette__board-button-text { text-align: center; - font-size: 20px; + font-size: 16px; font-weight: bold; - height: 28px; - border: none !important; - margin: 0 !important; - padding-top: 4px !important; - color: #FFFFFF !important; } .Roulette__lowertable { margin-top: 8px; - margin-left: 80px; - margin-right: 80px; border-collapse: collapse; - border: 2px solid white; + border: 1px solid colors.$white; border-spacing: 0; } .Roulette__lowertable--cell { - border: 2px solid white; + border: 2px solid colors.$white; padding: 0px; margin: 0px; } @@ -95,6 +95,6 @@ .Roulette__lowertable--header { width: 1%; text-align: center; - font-size: 20px; + font-size: 16px; font-weight: bold; }