diff --git a/code/game/objects/items/toys.dm b/code/game/objects/items/toys.dm index 7d180f8a0..e50c0ccec 100644 --- a/code/game/objects/items/toys.dm +++ b/code/game/objects/items/toys.dm @@ -697,48 +697,9 @@ var/list/currenthand = list() var/choice = null -/obj/item/toy/cards/cardhand/attack_self(mob/user) - var/list/handradial = list() - interact(user) - - for(var/t in currenthand) - handradial[t] = image(icon = src.icon, icon_state = "sc_[t["icon_state"] || t["name"]]_[deckstyle]") - - if(usr.stat || !ishuman(usr)) - return - var/mob/living/carbon/human/cardUser = usr - var/O = src - var/choice = show_radial_menu(usr,src, handradial, custom_check = CALLBACK(src, .proc/check_menu, user), radius = 36, require_near = TRUE) - if(!choice) - return FALSE - var/obj/item/toy/cards/singlecard/C = new/obj/item/toy/cards/singlecard(cardUser.loc) - choice["rotation"] = null - choice["face_up"] = null - currenthand -= choice - handradial -= choice - C.parentdeck = parentdeck - C.card = choice - C.apply_card_vars(C,O) - C.pickup(cardUser) - cardUser.put_in_hands(C) - cardUser.visible_message("[cardUser] draws a card from [cardUser.p_their()] hand.", "You take the [C.card["name"]] from your hand.") - - interact(cardUser) - update_sprite() - if(length(currenthand) == 1) - var/obj/item/toy/cards/singlecard/N = new/obj/item/toy/cards/singlecard(loc) - N.parentdeck = parentdeck - N.card = currenthand[1] - N.apply_card_vars(N,O) - qdel(src) - N.pickup(cardUser) - cardUser.put_in_hands(N) - to_chat(cardUser, "You also take [currenthand[1]] and hold it.") - /obj/item/toy/cards/cardhand/apply_card_vars(obj/item/toy/cards/newobj,obj/item/toy/cards/sourceobj) ..() newobj.deckstyle = sourceobj.deckstyle - update_sprite() newobj.card_hitsound = sourceobj.card_hitsound newobj.card_force = sourceobj.card_force newobj.card_throwforce = sourceobj.card_throwforce @@ -746,6 +707,7 @@ newobj.card_throw_range = sourceobj.card_throw_range newobj.card_attack_verb = sourceobj.card_attack_verb newobj.resistance_flags = sourceobj.resistance_flags + update_icon() /** * check_menu: Checks if we are allowed to interact with a radial menu @@ -763,7 +725,7 @@ /** * This proc updates the sprite for when you create a hand of cards */ -/obj/item/toy/cards/cardhand/proc/update_sprite() +/obj/item/toy/cards/cardhand/update_icon() cut_overlays() var/overlay_cards = currenthand.len if(overlay_cards) @@ -771,18 +733,15 @@ for(var/i = k; i <= overlay_cards; i++) var/_card = currenthand[i] var/_angle = _card["rotation"] || rotation - var/_dir = GetAngle(_angle) - var/card_overlay = image( + var/matrix/rot_matrix = matrix() + rot_matrix.Turn(GetAngle(_angle)) + var/image/card_overlay = image( icon = src.icon, icon_state = "sc_[_card["icon_state"] || _card["name"]]_[deckstyle]", pixel_x = (1 - i + k)*3, pixel_y = (1 - i + k)*3, - dir = ( - _dir == 90 ? EAST : - _dir == 180 ? SOUTH : - NORTH - ) ) + card_overlay.transform = rot_matrix add_overlay(card_overlay) /obj/item/toy/cards/singlecard @@ -812,7 +771,7 @@ set src in range(1) if(!ishuman(usr) || !usr.canUseTopic(src, BE_CLOSE)) return - FlipCards() + FlipCard() /obj/item/toy/cards/singlecard/apply_card_vars(obj/item/toy/cards/singlecard/newobj,obj/item/toy/cards/sourceobj) ..() diff --git a/code/modules/games/cas.dm b/code/modules/games/cas.dm index 2c01284fd..eee596fcb 100644 --- a/code/modules/games/cas.dm +++ b/code/modules/games/cas.dm @@ -2,12 +2,8 @@ // This is a parody of Cards Against Humanity (https://en.wikipedia.org/wiki/Cards_Against_Humanity) // which is licensed under CC BY-NC-SA 2.0, the full text of which can be found at the following URL: // https://creativecommons.org/licenses/by-nc-sa/2.0/legalcode -// Original code by Zuhayr, Polaris Station, ported with modifications -/datum/playingcard - var/name = "playing card" - var/card_icon = "card_back" - var/suit - var/number +// Original code by Zuhayr, Polaris Station, ported with modifications +// April 2022: modified by sarcoph for hyperstation /obj/item/toy/cards/deck/cas name = "\improper CAS deck (white)" @@ -19,7 +15,6 @@ var/blanks = 25 var/decksize = 150 var/card_text_file = "strings/cas_white.txt" - var/list/allcards = list() /obj/item/toy/cards/deck/cas/black name = "\improper CAS deck (black)" @@ -32,9 +27,12 @@ card_text_file = "strings/cas_black.txt" /obj/item/toy/cards/deck/cas/populate_deck() - var/static/list/cards_against_space = list("cas_white" = world.file2list("strings/cas_white.txt"),"cas_black" = world.file2list("strings/cas_black.txt")) - allcards = cards_against_space[card_face] - var/list/possiblecards = allcards.Copy() + var/static/list/cards_against_space = list( + "cas_white" = world.file2list("strings/cas_white.txt"), + "cas_black" = world.file2list("strings/cas_black.txt") + ) + cards = cards_against_space[card_face] + var/list/possiblecards = cards.Copy() if(possiblecards.len < decksize) // sanity check decksize = (possiblecards.len - 1) var/list/randomcards = list() @@ -42,117 +40,75 @@ randomcards += pick_n_take(possiblecards) for(var/x in 1 to randomcards.len) var/cardtext = randomcards[x] - var/datum/playingcard/P - P = new() - P.name = "[cardtext]" - P.card_icon = "[src.card_face]" - cards += P + cards += list(list( + "name" = cardtext, + "icon_state" = src.card_face, + "rotation" = null, + "face_up" = null + )) if(!blanks) + cards = shuffle(cards) return for(var/x in 1 to blanks) - var/datum/playingcard/P - P = new() - P.name = "Blank Card" - P.card_icon = "cas_white" - cards += P - // shuffle_inplace(cards) // distribute blank cards throughout deck + cards += list(list( + "name" = "Blank Card", + "icon_state" = "cas_white", + "rotation" = null, + "face_up" = null + )) + cards = shuffle(cards) // distribute blank cards throughout deck -// /obj/item/toy/cards/deck/cas/draw_card(mob/user) -// if(user.lying) -// return -// if(cards.len == 0) -// to_chat(user, "There are no more cards to draw!") -// return -// var/obj/item/toy/cards/singlecard/cas/H = new/obj/item/toy/cards/singlecard/cas(user.loc) -// var/datum/playingcard/choice = cards[1] -// if (choice.name == "Blank Card") -// H.blank = 1 -// H.name = choice.name -// H.buffertext = choice.name -// H.icon_state = choice.card_icon -// H.card_face = choice.card_icon -// H.parentdeck = src -// src.cards -= choice -// H.pickup(user) -// user.put_in_hands(H) -// user.visible_message("[user] draws a card from the deck.", "You draw a card from the deck.") -// update_icon() +/obj/item/toy/cards/deck/cas/DrawOneCard(list/card_indices) + var/obj/item/toy/cards/singlecard/cas/S = new/obj/item/toy/cards/singlecard/cas(usr.loc) + var/_card = cards[card_indices[1]] + if(_card["name"] == "Blank Card") + S.blank = TRUE + S.name = _card["name"] + S.icon_state = _card["icon_state"] + S.parentdeck = src + cards -= list(_card) + update_icon() + return S -// /obj/item/toy/cards/deck/cas/attackby(obj/item/I, mob/living/user, params) -// if(istype(I, /obj/item/toy/cards/singlecard/cas)) -// var/obj/item/toy/cards/singlecard/cas/SC = I -// if(!user.temporarilyRemoveItemFromInventory(SC)) -// to_chat(user, "The card is stuck to your hand, you can't add it to the deck!") -// return -// var/datum/playingcard/RC // replace null datum for the re-added card -// RC = new() -// RC.name = "[SC.name]" -// RC.card_icon = SC.card_face -// cards += RC -// user.visible_message("[user] adds a card to the bottom of the deck.","You add the card to the bottom of the deck.") -// qdel(SC) -// update_icon() +/obj/item/toy/cards/deck/cas/update_icon() + if(cards.len < 26) + icon_state = "deck_[deckstyle]_low" -// /obj/item/toy/cards/deck/cas/update_icon() -// if(cards.len < 26) -// icon_state = "deck_[deckstyle]_low" +/obj/item/toy/cards/singlecard/cas + name = "CAS card" + desc = "A CAS card." + icon_state = "cas_white" + var/card_face = "cas_white" + var/blank = 0 + var/buffertext = "A funny bit of text." -// /obj/item/toy/cards/singlecard/cas -// name = "CAS card" -// desc = "A CAS card." -// icon_state = "cas_white" -// flipped = 0 -// var/card_face = "cas_white" -// var/blank = 0 -// var/buffertext = "A funny bit of text." +/obj/item/toy/cards/singlecard/cas/examine(mob/user) + . = ..() + if (!face_up) + . += "The card is face down." + else if (blank) + . += "The card is blank. Write on it with a pen." + else + . += "The card reads: [name]" -// /obj/item/toy/cards/singlecard/cas/examine(mob/user) -// . = ..() -// if (flipped) -// . += "The card is face down." -// else if (blank) -// . += "The card is blank. Write on it with a pen." -// else -// . += "The card reads: [name]" -// . += "Alt-click to flip it." +/obj/item/toy/cards/singlecard/cas/update_icon() + if(face_up) + icon_state = "[card_face]_flipped" + name = card["name"] + else + icon_state = "[card_face]" -// /obj/item/toy/cards/singlecard/cas/Flip() -// set name = "Flip Card" -// set category = "Object" -// set src in range(1) -// if(!ishuman(usr) || !usr.canUseTopic(src, BE_CLOSE)) -// return -// if(!flipped) -// name = "CAS card" -// else if(flipped) -// name = buffertext -// flipped = !flipped -// update_icon() - -// /obj/item/toy/cards/singlecard/cas/AltClick(mob/living/user) -// . = ..() -// if(!ishuman(user) || !user.canUseTopic(src, BE_CLOSE)) -// return -// Flip() -// return TRUE - -// /obj/item/toy/cards/singlecard/cas/update_icon() -// if(flipped) -// icon_state = "[card_face]_flipped" -// else -// icon_state = "[card_face]" - -// /obj/item/toy/cards/singlecard/cas/attackby(obj/item/I, mob/living/user, params) -// if(istype(I, /obj/item/pen)) -// if(!user.is_literate()) -// to_chat(user, "You scribble illegibly on [src]!") -// return -// if(!blank) -// to_chat(user, "You cannot write on that card.") -// return -// var/cardtext = stripped_input(user, "What do you wish to write on the card?", "Card Writing", "", 50) -// if(!cardtext || !user.canUseTopic(src, BE_CLOSE)) -// return -// name = cardtext -// buffertext = cardtext -// blank = 0 +/obj/item/toy/cards/singlecard/cas/attackby(obj/item/I, mob/living/user, params) + if(istype(I, /obj/item/pen)) + if(!user.is_literate()) + to_chat(user, "You scribble illegibly on [src]!") + return + if(!blank) + to_chat(user, "You cannot write on that card.") + return + var/cardtext = stripped_input(user, "What do you wish to write on the card?", "Card Writing", "", 50) + if(!cardtext || !user.canUseTopic(src, BE_CLOSE)) + return + name = cardtext + buffertext = cardtext + blank = 0 diff --git a/hyperstation/code/game/objects/items/cards.dm b/hyperstation/code/game/objects/items/cards.dm index b11044cf2..b840d528f 100644 --- a/hyperstation/code/game/objects/items/cards.dm +++ b/hyperstation/code/game/objects/items/cards.dm @@ -27,13 +27,17 @@ _angle = 180 return _angle -/obj/item/toy/cards/proc/RotateCards(angle) +/obj/item/toy/cards/proc/GetNextAngle(angle) var/list/_rotations = list(CARD_ROTATION_UP, CARD_ROTATION_SIDE, CARD_ROTATION_DOWN) var/list/_indexof = _rotations.Find(rotation) + return _rotations[_indexof%3 + 1] + + +/obj/item/toy/cards/proc/RotateCards(angle) if(angle) rotation = angle else - rotation = _rotations[_indexof%3 + 1] + rotation = GetNextAngle(angle) update_icon() /obj/item/toy/cards/proc/FlipCards(side) @@ -41,6 +45,7 @@ else face_up = !face_up update_icon() + /** * Handles functionality for merging different types of cards. * @@ -128,18 +133,17 @@ return H /obj/item/toy/cards/deck/proc/DrawOneCard(list/card_indices) - var/obj/item/toy/cards/singlecard/S = new/obj/item/toy/cards/singlecard(usr.loc) - var/_card = cards[card_indices[1]] - _card["rotation"] = rotation - _card["face_up"] = face_up - S.card = _card - S.rotation = _card["rotation"] - S.face_up = _card["face_up"] - S.parentdeck = src - S.apply_card_vars(S,src) - cards -= list(_card) - update_icon() - return S + var/obj/item/toy/cards/singlecard/S = new/obj/item/toy/cards/singlecard(usr.loc) + var/_card = cards[card_indices[1]] + _card["rotation"] = rotation + _card["face_up"] = face_up + S.card = _card + S.rotation = _card["rotation"] + S.face_up = _card["face_up"] + S.parentdeck = src + S.apply_card_vars(S,src) + cards -= list(_card) + return S /obj/item/toy/cards/deck/FinishMergingCards(obj/item/toy/cards/target, mob/living/user) var/message = "[user] adds \the [target] to the bottom of \the [src]." @@ -284,6 +288,18 @@ if(_rotation != compare_orient) return FALSE return TRUE +/obj/item/toy/cards/cardhand/proc/DrawOneCard(list/card_indices) + var/obj/item/toy/cards/singlecard/S = new/obj/item/toy/cards/singlecard(usr.loc) + var/_card = currenthand[card_indices[1]] + S.card = _card + S.rotation = _card["rotation"] + S.face_up = _card["face_up"] + S.parentdeck = src + S.apply_card_vars(S,src) + currenthand -= list(_card) + update_icon() + return S + /obj/item/toy/cards/cardhand/MergeInto(obj/item/toy/cards/target, mob/living/user) if(..()) return TRUE @@ -320,6 +336,48 @@ // =================== TGUI stuff =================== +/obj/item/toy/cards/cardhand/ui_interact(mob/user, ui_key, datum/tgui/ui, force_open, datum/tgui/master_ui, datum/tgui_state/state) + ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + ui = new(user, src, ui_key, "CardsHand", name, 300, 400, master_ui, state) + ui.open() + +/obj/item/toy/cards/cardhand/ui_act(action, params) + if(..()) + return + switch(action) + if("draw") + if(!params["cards"]) return FALSE + var/list/P = splittext(params["cards"], ",") + var/list/P_cards = list() + for(var/T in P) + P_cards += text2num(T) + 1 + var/obj/item/toy/cards/H = DrawOneCard(P_cards) + H.pickup(usr) + usr.put_in_hands(H) + if("flip") + if(!params["card"]) return FALSE + var/card = currenthand[text2num(params["card"]) + 1] + card["face_up"] = !card["face_up"] + if("rotate") + if(!params["card"]) return FALSE + var/card = currenthand[text2num(params["card"]) + 1] + if(params["angle"]) + card["rotation"] = params["angle"] + update_icon() + return TRUE + +/obj/item/toy/cards/cardhand/ui_data(mob/user) + var/list/data = list() + data["cards"] = currenthand + data["name"] = name + return data + +/obj/item/toy/cards/cardhand/ui_static_data(mob/user) + var/list/data = list() + data["possible_rotations"] = list(CARD_ROTATION_UP, CARD_ROTATION_SIDE, CARD_ROTATION_DOWN) + return data + // ================================= SINGLE CARDS ================================= @@ -353,6 +411,19 @@ return FALSE return TRUE +/obj/item/toy/cards/singlecard/proc/FlipCard(mob/user) + if(!ishuman(usr) || !usr.canUseTopic(src, BE_CLOSE)) + return + FlipCards() + to_chat(user, "You flip \the [src] [(face_up ? "face-up" : "face-down")].") + +/obj/item/toy/cards/singlecard/proc/RotateCard(mob/user, var/rotation_angle) + if(!ishuman(usr) || !usr.canUseTopic(src, BE_CLOSE)) + return + to_chat(user, "You turn \the [src] to \a [rotation_angle] position.") + RotateCards(rotation_angle) + + /obj/item/toy/cards/singlecard/FinishMergingCards(obj/item/toy/cards/singlecard/target, mob/living/user) to_chat(user, "You combine the [target.card["name"]] and the [src.card["name"]] into a hand.") qdel(target) @@ -363,38 +434,30 @@ . = ..() . += "Click to flip. Alt-Click to rotate." -/obj/item/toy/cards/singlecard/CtrlClick(mob/user) - . = ..() - if(!ishuman(usr) || !usr.canUseTopic(src, BE_CLOSE)) - return - RotateCards() - to_chat(user, "You turn \the [src] to \a [rotation] position.") - /obj/item/toy/cards/singlecard/attack_self(mob/user) . = ..() - if(!ishuman(usr) || !usr.canUseTopic(src, BE_CLOSE)) - return - FlipCards() - to_chat(user, "You flip \the [src] [(face_up ? "face-up" : "face-down")].") + FlipCard(user) + +/obj/item/toy/cards/singlecard/AltClick(mob/user) + . = ..() + RotateCard(user, GetNextAngle(rotation)) /obj/item/toy/cards/singlecard/update_icon() . = ..() var/matrix/rot_matrix = matrix() rot_matrix.Turn(GetAngle(rotation)) transform = rot_matrix + + var/rotation_name = (rotation == CARD_ROTATION_UP ? "" : rotation + " ") if(face_up) if(card) src.icon_state = "sc_[card["icon_state"] || card["name"]]_[deckstyle]" - src.name = src.card["name"] + src.name = rotation_name + src.card["name"] else src.icon_state = "sc_aceofspades_[deckstyle]" src.name = "What Card" src.pixel_x = 5 else src.icon_state = "singlecard_down_[deckstyle]" - src.name = "card" - src.pixel_x = -5 - - - -// =================== TGUI stuff =================== \ No newline at end of file + src.name = rotation_name + "card" + src.pixel_x = -5 \ No newline at end of file diff --git a/tgui/packages/tgui/interfaces/Cards/CardStatus.js b/tgui/packages/tgui/interfaces/Cards/CardStatus.js new file mode 100644 index 000000000..4885a0412 --- /dev/null +++ b/tgui/packages/tgui/interfaces/Cards/CardStatus.js @@ -0,0 +1,27 @@ +import { useBackend } from '../../backend'; +import { Box, Section } from '../../components'; + +export const CardStatus = (props, context) => { + const { data } = useBackend(context); + const { face_up, rotation, cards, name } = data; + const { noRotation = false, notOrdered = false, children } = props; + + return ( +
+ + This {name} contains {cards.length} cards. + + {!noRotation && ( + + The deck is {rotation} and turned {face_up ? "face-up" : "face-down"}. + + )} + {!notOrdered & !!face_up && cards.length > 0 && ( + + You can see a {cards[cards.length - 1].name} on top. + + ) || null} + {...children} +
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/Cards/CardsControlList.js b/tgui/packages/tgui/interfaces/Cards/CardsControlList.js new file mode 100644 index 000000000..3713cda75 --- /dev/null +++ b/tgui/packages/tgui/interfaces/Cards/CardsControlList.js @@ -0,0 +1,82 @@ +import { useBackend, useLocalState } from '../../backend'; +import { Box, Button, Dropdown, LabeledList, NumberInput } from '../../components'; + +export const CardsControlList = (props, context) => { + const { data, act } = useBackend(context); + const { face_up, rotation, cards, possible_rotations } = data; + const { flip, rotate, draw, deal, shuffle } = props; + + const [drawCount, setDrawCount] = useLocalState(context, 'drawCount', 1); + const [dealCardsCount, setDealCardsCount] = useLocalState(context, 'dealCards', 1); + const [dealHandsCount, setDealHandsCount] = useLocalState(context, 'dealHands', 1); + const [selectedCards, setSelectedCards] = useLocalState(context, 'selectedCards', []); + + return ( + + {!!flip && ( + +