diff --git a/_maps/shuttles/whiteship_delta.dmm b/_maps/shuttles/whiteship_delta.dmm index a6be9a81cf3..6ed39a4701c 100644 --- a/_maps/shuttles/whiteship_delta.dmm +++ b/_maps/shuttles/whiteship_delta.dmm @@ -1396,22 +1396,6 @@ /obj/structure/cable, /turf/open/floor/iron, /area/shuttle/abandoned/medbay) -"cU" = ( -/obj/effect/turf_decal/bot_white, -/obj/effect/decal/cleanable/dirt/dust, -/obj/structure/table, -/obj/item/stack/sheet/iron/fifty, -/obj/item/storage/toolbox/electrical{ - pixel_x = -3; - pixel_y = 8 - }, -/obj/item/stock_parts/cell/high{ - pixel_x = 3; - pixel_y = -1 - }, -/obj/effect/decal/cleanable/cobweb, -/turf/open/floor/iron/dark, -/area/shuttle/abandoned/medbay) "cV" = ( /obj/effect/turf_decal/bot_white, /obj/effect/decal/cleanable/dirt/dust, @@ -1952,6 +1936,22 @@ /obj/effect/turf_decal/tile/neutral/fourcorners, /turf/open/floor/iron/dark, /area/shuttle/abandoned/cargo) +"Ch" = ( +/obj/effect/turf_decal/bot_white, +/obj/effect/decal/cleanable/dirt/dust, +/obj/structure/table, +/obj/item/stack/sheet/iron/fifty, +/obj/item/storage/toolbox/electrical{ + pixel_x = -3; + pixel_y = 8 + }, +/obj/item/stock_parts/cell/high{ + pixel_x = 3; + pixel_y = -1 + }, +/obj/effect/decal/cleanable/cobweb, +/turf/open/floor/iron/dark, +/area/shuttle/abandoned/medbay) "DZ" = ( /obj/effect/turf_decal/delivery/white, /obj/effect/decal/cleanable/dirt/dust, @@ -1966,7 +1966,7 @@ /obj/effect/turf_decal/tile/neutral/fourcorners, /turf/open/floor/iron/dark, /area/shuttle/abandoned/cargo) -"FO" = ( +"IO" = ( /obj/effect/decal/cleanable/dirt/dust, /obj/structure/table, /obj/item/toy/cards/deck{ @@ -1974,7 +1974,7 @@ pixel_y = 12 }, /obj/item/toy/cards/cardhand{ - init_cards = list("2 of Diamonds","3 of Clubs"); + cards = list("2 of Diamonds","3 of Clubs"); pixel_x = -5 }, /obj/item/reagent_containers/food/drinks/bottle/beer{ @@ -2366,7 +2366,7 @@ aa aa aa vm -cU +Ch dn vm dS @@ -2450,7 +2450,7 @@ cy "} (20,1,1) = {" af -FO +IO aH bb bu diff --git a/code/__DEFINES/memory_defines.dm b/code/__DEFINES/memory_defines.dm index 7229b70c4da..9eeb823b431 100644 --- a/code/__DEFINES/memory_defines.dm +++ b/code/__DEFINES/memory_defines.dm @@ -53,30 +53,30 @@ ///this memory can be memorized by unconscious people. #define MEMORY_SKIP_UNCONSCIOUS (1<<8) -//These defines are for what notable event happened. they correspond to the json lists related to the memory -///a memory of completing a surgery. +// These defines are for what notable event happened. they correspond to the json lists related to the memory +/// A memory of completing a surgery. #define MEMORY_SUCCESSFUL_SURGERY "surgery" -///a memory of priming a bomb +/// A memory of priming a bomb #define MEMORY_BOMB_PRIMED "bomb" -///a memory of pulling off either a high five or a high ten +/// A memory of pulling off either a high five or a high ten #define MEMORY_HIGH_FIVE "highfive" -///a memory of getting borged +/// A memory of getting borged #define MEMORY_BORGED "borged" -///a memory of dying! includes time of death +/// A memory of dying! includes time of death #define MEMORY_DEATH "death" -///a memory of being creampied! Mentions where +/// A memory of being creampied! Mentions where #define MEMORY_CREAMPIED "creampied" -///a memory of being slipped! Mentions on what +/// A memory of being slipped! Mentions on what #define MEMORY_SLIPPED "slipped" -///A memory of letting my spaghetti spill, how embarrasing! +/// A memory of letting my spaghetti spill, how embarrasing! #define MEMORY_SPAGHETTI_SPILL "spaghetti_spilled" -///A memory of getting a kiss blown. Provides the kisser and kissee. +/// A memory of getting a kiss blown. Provides the kisser and kissee. #define MEMORY_KISS "kiss" -///A memory of a really good meal +/// A memory of a really good meal #define MEMORY_MEAL "meal" -///A memory of a really good drink +/// A memory of a really good drink #define MEMORY_DRINK "drink" -///A memory of being lit +/// A memory of being lit #define MEMORY_FIRE "fire" /// A memory of limb loss #define MEMORY_DISMEMBERED "dismembered" @@ -98,6 +98,11 @@ #define MEMORY_SUPERMATTER_DUSTED "supermatter_dusted" /// Nuke ops nuke code memory #define MEMORY_NUKECODE "nuke_code" +/// A memory of having to play 52 card pickup +#define MEMORY_PLAYING_52_PICKUP "playing_52_pickup" +/// A memory of playing cards with others +#define MEMORY_PLAYING_CARDS "playing_cards" + /** * These are also memories, but they're examples of what I kinda don't want to be memories. They're stuff that I had to port @@ -152,5 +157,10 @@ #define DETAIL_MEDAL_TYPE "MEDAL_TYPE" #define DETAIL_MEDAL_REASON "MEDAL_REASON" #define DETAIL_NUKE_CODE "NUKE_CODE" +// for cardgames +#define DETAIL_PLAYERS "PLAYERS" +#define DETAIL_CARDGAME "CARDGAME" +#define DETAIL_DEALER "DEALER" +#define DETAIL_HELD_CARD_ITEM "HELD_CARD_ITEM" // could either be a singlecard, cardhand, or a deck diff --git a/code/_globalvars/lists/maintenance_loot.dm b/code/_globalvars/lists/maintenance_loot.dm index f68208cf3e8..81224a93dc6 100644 --- a/code/_globalvars/lists/maintenance_loot.dm +++ b/code/_globalvars/lists/maintenance_loot.dm @@ -354,6 +354,7 @@ GLOBAL_LIST_INIT(oddity_loot, list(//oddity: strange or crazy items /obj/item/instrument/trombone/spectral = 1, /obj/item/instrument/trumpet/spectral = 1, ) = 1, + /obj/item/toy/cards/deck/tarot/haunted = 1, )) //Maintenance loot spawner pools diff --git a/code/_globalvars/phobias.dm b/code/_globalvars/phobias.dm index b137ba3779a..2c7c90f70f0 100644 --- a/code/_globalvars/phobias.dm +++ b/code/_globalvars/phobias.dm @@ -309,11 +309,8 @@ GLOBAL_LIST_INIT(phobia_objs, list( /obj/item/clothing/suit/hooded/cultrobes, /obj/item/clothing/suit/wizrobe, /obj/item/clothing/under/rank/civilian/chaplain, - /obj/item/clothing/under/rank/civilian/chaplain, /obj/item/cult_bastard, /obj/item/gun/magic/staff, - /obj/item/gun/magic/staff, - /obj/item/gun/magic/wand, /obj/item/gun/magic/wand, /obj/item/melee/cultblade, /obj/item/necromantic_stone, @@ -333,6 +330,7 @@ GLOBAL_LIST_INIT(phobia_objs, list( /obj/narsie, /obj/structure/destructible/cult, /obj/structure/spirit_board, + /obj/item/toy/cards/deck/tarot, )), "aliens" = typecacheof(list( diff --git a/code/datums/mood_events/generic_negative_events.dm b/code/datums/mood_events/generic_negative_events.dm index 8b1073b5d07..9eb1e961bc7 100644 --- a/code/datums/mood_events/generic_negative_events.dm +++ b/code/datums/mood_events/generic_negative_events.dm @@ -370,3 +370,8 @@ description = "If I'm not good at video games, can I truly call myself a gamer?" mood_change = -10 timeout = 10 MINUTES + +/datum/mood_event/lost_52_card_pickup + description = "This is really embarrassing! I'm ashamed to pick up all these cards off the floor..." + mood_change = -3 + timeout = 3 MINUTES diff --git a/code/datums/mood_events/generic_positive_events.dm b/code/datums/mood_events/generic_positive_events.dm index fbfdde4d707..44acc5a3228 100644 --- a/code/datums/mood_events/generic_positive_events.dm +++ b/code/datums/mood_events/generic_positive_events.dm @@ -272,3 +272,24 @@ mood_change = 10 timeout = 5 MINUTES +/datum/mood_event/won_52_card_pickup + description = "HA! That loser will be picking cards up for a long time!" + mood_change = 3 + timeout = 3 MINUTES + +/datum/mood_event/playing_cards + description = "I'm enjoying playing cards with other people!" + mood_change = 2 + timeout = 3 MINUTES + +/datum/mood_event/playing_cards/add_effects(param) + var/card_players = 1 + for(var/mob/living/carbon/player in viewers(COMBAT_MESSAGE_RANGE, owner)) + var/player_has_cards = player.is_holding(/obj/item/toy/singlecard) || player.is_holding_item_of_type(/obj/item/toy/cards) + if(player_has_cards) + card_players++ + if(card_players > 5) + break + + mood_change *= card_players + return ..() diff --git a/code/game/objects/effects/spawners/random/entertainment.dm b/code/game/objects/effects/spawners/random/entertainment.dm index 84d5842a116..fc3b1bbd8cb 100644 --- a/code/game/objects/effects/spawners/random/entertainment.dm +++ b/code/game/objects/effects/spawners/random/entertainment.dm @@ -208,12 +208,10 @@ name = "deck spawner" icon_state = "deck" loot = list( - /obj/item/toy/cards/deck = 10, - /obj/item/toy/cards/deck/kotahi = 3, - /obj/item/toy/cards/deck/wizoff = 3, - /obj/item/toy/cards/deck/tarot = 2, - /obj/item/toy/cards/deck/cas = 1, - /obj/item/toy/cards/deck/cas/black = 1, + /obj/item/toy/cards/deck = 5, + /obj/item/toy/cards/deck/kotahi = 2, + /obj/item/toy/cards/deck/wizoff = 2, + /obj/item/toy/cards/deck/tarot = 1, ) /obj/effect/spawner/random/entertainment/toy_figure diff --git a/code/game/objects/items/toys.dm b/code/game/objects/items/toys.dm index b980975edcf..60aa2e9f61e 100644 --- a/code/game/objects/items/toys.dm +++ b/code/game/objects/items/toys.dm @@ -10,7 +10,6 @@ * AI core prizes * Toy codex gigas * Skeleton toys - * Cards * Toy nuke * Fake meteor * Foam armblade @@ -27,14 +26,12 @@ * Fake Pierced Reality * Intento */ - /obj/item/toy throwforce = 0 throw_speed = 3 throw_range = 7 force = 0 - /* * Balloons */ @@ -45,7 +42,6 @@ icon_state = "waterballoon-e" inhand_icon_state = "balloon-empty" - /obj/item/toy/waterballoon/Initialize(mapload) . = ..() create_reagents(10) @@ -152,7 +148,6 @@ else return ..() - /obj/item/toy/balloon/proc/pop_balloon(monkey_pop = FALSE) playsound(src, 'sound/effects/cartoon_pop.ogg', 50, vary = TRUE) if(monkey_pop) // Monkeys make money from popping bloons @@ -191,14 +186,12 @@ SEND_SIGNAL(user, COMSIG_CLEAR_MOOD_EVENT, "badass_antag", /datum/mood_event/badass_antag) . = ..() - /obj/item/toy/balloon/syndicate/Destroy() if(ismob(loc)) var/mob/M = loc SEND_SIGNAL(M, COMSIG_CLEAR_MOOD_EVENT, "badass_antag", /datum/mood_event/badass_antag) . = ..() - /obj/item/toy/balloon/arrest name = "arreyst balloon" desc = "A half inflated balloon about a boyband named Arreyst that was popular about ten years ago, famous for making fun of red jumpsuits as unfashionable." @@ -303,8 +296,6 @@ user.death(FALSE) user.ghostize(FALSE) - - /* * Toy gun: Why isn't this an /obj/item/gun? */ @@ -412,7 +403,6 @@ clumsy_check = FALSE) RegisterSignal(src, COMSIG_TRANSFORMING_ON_TRANSFORM, .proc/on_transform) - /* * Signal proc for [COMSIG_TRANSFORMING_ON_TRANSFORM]. * @@ -473,7 +463,6 @@ w_class = WEIGHT_CLASS_SMALL resistance_flags = FLAMMABLE - /obj/item/toy/windup_toolbox name = "windup toolbox" desc = "A replica toolbox that rumbles when you turn the key." @@ -580,7 +569,6 @@ /* * Snap pops */ - /obj/item/toy/snappop name = "snap pop" desc = "Wow!" @@ -733,462 +721,9 @@ phomeme = "griffin" w_class = WEIGHT_CLASS_SMALL -/* -|| A Deck of Cards for playing various games of chance || -*/ -/obj/item/toy/cards - resistance_flags = FLAMMABLE - max_integrity = 50 - ///The parent deck of the cards - var/datum/weakref/parentdeck - ///Artistic style of the deck - var/deckstyle = "nanotrasen" - var/card_hitsound = null - var/card_force = 0 - var/card_throwforce = 0 - var/card_throw_speed = 3 - var/card_throw_range = 7 - var/list/card_attack_verb_continuous = list("attacks") - var/list/card_attack_verb_simple = list("attack") - - -/obj/item/toy/cards/Initialize(mapload) - . = ..() - if(card_attack_verb_continuous) - card_attack_verb_continuous = string_list(card_attack_verb_continuous) - if(card_attack_verb_simple) - card_attack_verb_simple = string_list(card_attack_verb_simple) - - -/obj/item/toy/cards/suicide_act(mob/living/carbon/user) - user.visible_message(span_suicide("[user] is slitting [user.p_their()] wrists with \the [src]! It looks like [user.p_they()] [user.p_have()] a crummy hand!")) - playsound(src, 'sound/items/cardshuffle.ogg', 50, TRUE) - return BRUTELOSS - -/** - * ## apply_card_vars - * - * Applies variables for supporting multiple types of card deck - */ -/obj/item/toy/cards/proc/apply_card_vars(obj/item/toy/cards/newobj, obj/item/toy/cards/sourceobj) - if(!istype(sourceobj)) - return - -/** - * ## add_card - * - * Adds a card to the deck (or hand of cards). - * - * Arguments: - * * mob/user - The user adding the card. - * * list/cards - The list of cards the user is adding to. - * * obj/item/toy/cards/card_to_add - The card (or hand of cards) that will be added back into the deck - */ -/obj/item/toy/cards/proc/add_card(mob/user, list/cards, obj/item/toy/cards/card_to_add) - ///Are we adding a hand of cards to the deck? - var/from_cardhand = FALSE - ///Are we adding to a hand of cards? - var/to_cardhand = FALSE - if (istype(card_to_add, /obj/item/toy/cards/cardhand)) - from_cardhand = TRUE - if (istype(src, /obj/item/toy/cards/cardhand)) - to_cardhand = TRUE - - if ((card_to_add.parentdeck != src.parentdeck) && (card_to_add.parentdeck != WEAKREF(src))) - to_chat(user, span_warning("You can't mix cards from other decks!")) - return - if (!user.temporarilyRemoveItemFromInventory(card_to_add)) - to_chat(user, span_warning("The [from_cardhand ? "hand of cards" : "card"] is stuck to your hand, you can't add it to [to_cardhand ? "your hand" : "the deck"]!")) - return - - if (from_cardhand) - var/obj/item/toy/cards/cardhand/cards_to_add = card_to_add - for (var/obj/item/toy/cards/singlecard/card in cards_to_add.cards) - card.loc = src - cards += card - else - var/obj/item/toy/cards/singlecard/card = card_to_add - card.loc = src - cards += card - - user.visible_message(span_notice("[user] adds [from_cardhand ? "the hand of cards" : "a card"] to [to_cardhand ? "[user.p_their()] hand" : "the bottom of the deck"]."), span_notice("You add the [from_cardhand ? "hand of cards" : "card"] to [to_cardhand ? "your hand" : "the bottom of the deck"].")) - update_appearance() - -/** - * ## draw_card - * - * Draws a card from the deck (or hand of cards). - * - * Arguments: - * * mob/user - The user drawing from the deck. - * * list/cards - The list of cards the user is drawing from. - * * obj/item/toy/cards/singlecard/forced_card (optional) - Used to force the card drawn from the deck - */ -/obj/item/toy/cards/proc/draw_card(mob/user, list/cards, obj/item/toy/cards/singlecard/forced_card = null) - if(isliving(user)) - var/mob/living/living_user = user - if(!(living_user.mobility_flags & MOBILITY_PICKUP)) - return - if(cards.len == 0) - to_chat(user, span_warning("There are no more cards to draw!")) - return - - ///Are we drawing from a hand of cards? - var/from_cardhand = FALSE - if (istype(src, /obj/item/toy/cards/cardhand)) - from_cardhand = TRUE - - var/obj/item/toy/cards/singlecard/card_to_draw - if (forced_card) - card_to_draw = forced_card - else - card_to_draw = cards[1] - - cards -= card_to_draw - card_to_draw.pickup(user) - user.put_in_hands(card_to_draw) - user.visible_message(span_notice("[user] draws a card from [from_cardhand ? "[user.p_their()] hand" : "the deck"]."), span_notice("You draw a card from [from_cardhand ? "your hand" : "the deck"].")) - update_appearance() - return card_to_draw - -/obj/item/toy/cards/deck - name = "deck of cards" - desc = "A deck of space-grade playing cards." - icon = 'icons/obj/toy.dmi' - deckstyle = "nanotrasen" - icon_state = "deck_nanotrasen_full" - worn_icon_state = "card" - w_class = WEIGHT_CLASS_SMALL - ///Deck shuffling cooldown. - COOLDOWN_DECLARE(shuffle_cooldown) - ///Tracks holodeck cards, since they shouldn't be infinite - var/obj/machinery/computer/holodeck/holo = null - ///Cards in this deck - var/list/cards = list() - -/obj/item/toy/cards/deck/Initialize(mapload) - . = ..() - AddElement(/datum/element/drag_pickup) - populate_deck() - -/** - * ## generate_card - * - * Generates a new playing card, and assigns all of the necessary variables. - * - * Arguments: - * * name - The name of the playing card. - */ -/obj/item/toy/cards/deck/proc/generate_card(name) - var/obj/item/toy/cards/singlecard/card_to_add = new/obj/item/toy/cards/singlecard() - if(holo) - holo.spawned += card_to_add - card_to_add.cardname = name - card_to_add.parentdeck = WEAKREF(src) - card_to_add.apply_card_vars(card_to_add, src) - return card_to_add - -/** - * ## populate_deck - * - * Generates all the cards within the deck. - */ -/obj/item/toy/cards/deck/proc/populate_deck() - icon_state = "deck_[deckstyle]_full" - for(var/suit in list("Hearts", "Spades", "Clubs", "Diamonds")) - cards += generate_card("Ace of [suit]") - for(var/i in 2 to 10) - cards += generate_card("[i] of [suit]") - for(var/person in list("Jack", "Queen", "King")) - cards += generate_card("[person] of [suit]") - -/obj/item/toy/cards/deck/attack_hand(mob/living/user, list/modifiers) - if (!user.combat_mode) - draw_card(user, cards) - else - return ..() - -/obj/item/toy/cards/deck/update_icon_state() - switch(cards.len) - if(27 to INFINITY) - icon_state = "deck_[deckstyle]_full" - if(11 to 27) - icon_state = "deck_[deckstyle]_half" - if(1 to 11) - icon_state = "deck_[deckstyle]_low" - else - icon_state = "deck_[deckstyle]_empty" - return ..() - -#define DECK_SHUFFLE_COOLDOWN 5 SECONDS -/obj/item/toy/cards/deck/attack_self(mob/user) - if(!COOLDOWN_FINISHED(src, shuffle_cooldown)) - return - COOLDOWN_START(src, shuffle_cooldown, DECK_SHUFFLE_COOLDOWN) - cards = shuffle(cards) - playsound(src, 'sound/items/cardshuffle.ogg', 50, TRUE) - user.visible_message(span_notice("[user] shuffles the deck."), span_notice("You shuffle the deck.")) -#undef DECK_SHUFFLE_COOLDOWN - -/obj/item/toy/cards/deck/attackby(obj/item/item, mob/living/user, params) - if(istype(item, /obj/item/toy/cards/singlecard) || istype(item, /obj/item/toy/cards/cardhand)) - add_card(user, cards, item) - else - return ..() - -/obj/item/toy/cards/cardhand - name = "hand of cards" - desc = "A number of cards not in a deck, customarily held in ones hand." - icon = 'icons/obj/toy.dmi' - icon_state = "none" - worn_icon_state = "card" - w_class = WEIGHT_CLASS_TINY - ///Cards in this hand of cards. - var/list/cards = list() - ///List of cards to add into the hand on initialization (used for mapping mostly) - var/list/init_cards = list() - -/obj/item/toy/cards/cardhand/Initialize() - . = ..() - if (init_cards.len > 0) - for (var/card in init_cards) - var/obj/item/toy/cards/singlecard/new_card = new /obj/item/toy/cards/singlecard(src) - new_card.cardname = card - new_card.Flip() - cards += new_card - update_sprite() - -/obj/item/toy/cards/cardhand/add_card(mob/user, list/cards, obj/item/toy/cards/card_to_add) - . = ..() - interact(user) - update_sprite() - -/obj/item/toy/cards/cardhand/attack_self(mob/user) - if(ishuman(user)) - var/mob/living/carbon/human/human_user = user - if(!(human_user.mobility_flags & MOBILITY_USE)) - return - if(user.stat || !ishuman(user)) - return - interact(user) - - var/list/handradial = list() - for(var/obj/item/toy/cards/singlecard/card in cards) - handradial[card] = image(icon = src.icon, icon_state = "sc_[card.name]_[deckstyle]") - - var/obj/item/toy/cards/singlecard/choice = show_radial_menu(usr, src, handradial, custom_check = CALLBACK(src, .proc/check_menu, user), radius = 36, require_near = TRUE) - if(!choice) - return FALSE - draw_card(user, cards, choice) - - interact(user) - if(length(cards) == 1) - var/obj/item/toy/cards/singlecard/last_card = draw_card(user, cards) - qdel(src) - last_card.pickup(user) - user.put_in_hands(last_card) - to_chat(user, span_notice("You also take [last_card.cardname] and hold it.")) - else - update_sprite() - -/obj/item/toy/cards/cardhand/attackby(obj/item/toy/cards/singlecard/card, mob/living/user, params) - if(istype(card)) - if (!card.flipped) - card.Flip() // flip so that the card appears properly in the hand - add_card(user, cards, card) - else - return ..() - -/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 - newobj.card_throw_speed = sourceobj.card_throw_speed - newobj.card_throw_range = sourceobj.card_throw_range - newobj.card_attack_verb_continuous = sourceobj.card_attack_verb_continuous //null or unique list made by string_list() - newobj.card_attack_verb_simple = sourceobj.card_attack_verb_simple //null or unique list made by string_list() - newobj.resistance_flags = sourceobj.resistance_flags - -/** - * ## check_menu - * - * Checks if we are allowed to interact with a radial menu - * - * Arguments: - * * user - The mob interacting with a menu - */ -/obj/item/toy/cards/cardhand/proc/check_menu(mob/living/user) - if(!istype(user)) - return FALSE - if(user.incapacitated()) - return FALSE - return TRUE - -/** - * ## update_sprite - * - * This proc updates the sprite for when you create a hand of cards - */ -/obj/item/toy/cards/cardhand/proc/update_sprite() - cut_overlays() - var/overlay_cards = cards.len - - var/k = overlay_cards == 2 ? 1 : overlay_cards - 2 - for(var/i = k; i <= overlay_cards; i++) - var/obj/item/toy/cards/singlecard/card = cards[i] - var/card_overlay = image(icon = src.icon, icon_state = "sc_[card.cardname]_[deckstyle]", pixel_x = (1 - i + k) * 3, pixel_y = (1 - i + k) * 3) - add_overlay(card_overlay) - -/obj/item/toy/cards/singlecard - name = "card" - desc = "A playing card used to play card games like poker." - icon = 'icons/obj/toy.dmi' - icon_state = "singlecard_down_nanotrasen" - worn_icon_state = "card" - w_class = WEIGHT_CLASS_TINY - pixel_x = -5 - ///The name of the card - var/cardname = null - ///is the card facedown (F), or faceup (T)? - var/flipped = FALSE - -/obj/item/toy/cards/singlecard/examine(mob/user) - . = ..() - if(ishuman(user)) - var/mob/living/carbon/human/cardUser = user - if(cardUser.is_holding(src)) - cardUser.visible_message(span_notice("[cardUser] checks [cardUser.p_their()] card."), span_notice("The card reads: [cardname].")) - else - . += span_warning("You need to have the card in your hand to check it!") - -/** - * ## Flip - * - * flips the card over - */ -/obj/item/toy/cards/singlecard/verb/Flip() - set name = "Flip Card" - set category = "Object" - set src in range(1) - if(!ishuman(usr) || !usr.canUseTopic(src, BE_CLOSE)) - return - if(!flipped) - src.flipped = TRUE - if (cardname) - src.icon_state = "sc_[cardname]_[deckstyle]" - src.name = src.cardname - else - src.icon_state = "sc_Ace of Spades_[deckstyle]" - src.name = "What Card" - src.pixel_x = 5 - else if(flipped) - src.flipped = FALSE - src.icon_state = "singlecard_down_[deckstyle]" - src.name = "card" - src.pixel_x = -5 - -/** - * ## do_cardhand - * - * Creates, or adds to an existing hand of cards - * - * Arguments: - * * mob/living/user - the user - * * list/cards - the list of cards being added together (/obj/item/toy/cards/singlecard) - * * obj/item/toy/cards/cardhand/given_hand (optional) - the cardhand to add said cards into - */ -/obj/item/toy/cards/singlecard/proc/do_cardhand(mob/living/user, list/cards, obj/item/toy/cards/cardhand/given_hand = null) - if (given_hand && (given_hand?.parentdeck != parentdeck)) - to_chat(user, span_warning("You can't mix cards from other decks!")) - return - for (var/obj/item/toy/cards/singlecard/card in cards) - if (card.parentdeck != parentdeck) - to_chat(user, span_warning("You can't mix cards from other decks!")) - return - - var/obj/item/toy/cards/cardhand/new_cardhand = given_hand - var/preexisting = TRUE // does the cardhand already exist, or are we making a new one - if (!new_cardhand) - preexisting = FALSE - new_cardhand = new /obj/item/toy/cards/cardhand(user.loc) - - for (var/obj/item/toy/cards/singlecard/card in cards) - user.dropItemToGround(card) // drop them all so the loc will properly update - if (!card.flipped) - card.Flip() // flip so the card shows up in the cardhand properly - new_cardhand.cards += card - - if (preexisting) - new_cardhand.interact(user) - new_cardhand.update_sprite() - - user.visible_message(span_notice("[user] adds a card to [user.p_their()] hand."), span_notice("You add the [cardname] to your hand.")) - else - new_cardhand.parentdeck = parentdeck - new_cardhand.apply_card_vars(new_cardhand, src) - to_chat(user, span_notice("You combine the cards into a hand.")) - - new_cardhand.pickup(user) - user.put_in_active_hand(new_cardhand) - - for (var/obj/item/toy/cards/singlecard/card in cards) - card.loc = new_cardhand // move the cards into the cardhand - -/obj/item/toy/cards/singlecard/attackby(obj/item/item, mob/living/user, params) - if(istype(item, /obj/item/toy/cards/singlecard/)) - do_cardhand(user, list(src, item)) - if(istype(item, /obj/item/toy/cards/cardhand/)) - do_cardhand(user, list(src), item) - else - return ..() - -/obj/item/toy/cards/singlecard/attack_self(mob/living/carbon/human/user) - if(!ishuman(user) || !(user.mobility_flags & MOBILITY_USE)) - return - Flip() - -/obj/item/toy/cards/singlecard/apply_card_vars(obj/item/toy/cards/singlecard/newobj,obj/item/toy/cards/sourceobj) - ..() - newobj.deckstyle = sourceobj.deckstyle - newobj.icon_state = "singlecard_down_[deckstyle]" // Without this the card is invisible until flipped. It's an ugly hack, but it works. - newobj.card_hitsound = sourceobj.card_hitsound - newobj.hitsound = newobj.card_hitsound - newobj.card_force = sourceobj.card_force - newobj.force = newobj.card_force - newobj.card_throwforce = sourceobj.card_throwforce - newobj.throwforce = newobj.card_throwforce - newobj.card_throw_speed = sourceobj.card_throw_speed - newobj.throw_speed = newobj.card_throw_speed - newobj.card_throw_range = sourceobj.card_throw_range - newobj.throw_range = newobj.card_throw_range - newobj.attack_verb_continuous = newobj.card_attack_verb_continuous = sourceobj.card_attack_verb_continuous //null or unique list made by string_list() - newobj.attack_verb_simple = newobj.card_attack_verb_simple = sourceobj.card_attack_verb_simple //null or unique list made by string_list() - -/* -|| Syndicate playing cards, for pretending you're Gambit and playing poker for the nuke disk. || -*/ - -/obj/item/toy/cards/deck/syndicate - name = "suspicious looking deck of cards" - desc = "A deck of space-grade playing cards. They seem unusually rigid." - icon_state = "deck_syndicate_full" - deckstyle = "syndicate" - card_hitsound = 'sound/weapons/bladeslice.ogg' - card_force = 5 - card_throwforce = 10 - card_throw_speed = 3 - card_throw_range = 7 - card_attack_verb_continuous = list("attacks", "slices", "dices", "slashes", "cuts") - card_attack_verb_simple = list("attack", "slice", "dice", "slash", "cut") - resistance_flags = NONE - /* * Fake nuke */ - /obj/item/toy/nuke name = "\improper Nuclear Fission Explosive toy" desc = "A plastic model of a Nuclear Fission Explosive." @@ -1222,16 +757,15 @@ to_chat(user, span_alert("Nothing happens, and '[round(timeleft/10)]' appears on the small display.")) sleep(5) - /obj/item/toy/nuke/emag_act(mob/user) if (obj_flags & EMAGGED) return to_chat(user, span_warning("You short-circuit \the [src].")) obj_flags |= EMAGGED + /* * Fake meteor */ - /obj/item/toy/minimeteor name = "\improper Mini-Meteor" desc = "Relive the excitement of a meteor shower! SweetMeat-eor Co. is not responsible for any injuries, headaches or hearing loss caused by Mini-Meteor." @@ -1286,7 +820,6 @@ /* * Snowballs */ - /obj/item/toy/snowball name = "snowball" desc = "A compact ball of snow. Good for throwing at people." @@ -1322,7 +855,6 @@ /* * Clockwork Watch */ - /obj/item/toy/clockwork_watch name = "steampunk watch" desc = "A stylish steampunk watch made out of thousands of tiny cogwheels." @@ -1348,7 +880,6 @@ /* * Toy Dagger */ - /obj/item/toy/toy_dagger name = "toy dagger" desc = "A cheap plastic replica of a dagger. Produced by THE ARM Toys, Inc." @@ -1362,7 +893,6 @@ /* * Xenomorph action figure */ - /obj/item/toy/toy_xeno icon = 'icons/obj/toy.dmi' icon_state = "toy_xeno" @@ -1387,7 +917,6 @@ return // TOY MOUSEYS :3 :3 :3 - /obj/item/toy/cattoy name = "toy mouse" desc = "A colorful toy mouse!" @@ -1401,7 +930,6 @@ /* * Action Figures */ - /obj/item/toy/figure name = "\improper Non-Specific Action Figure action figure" icon = 'icons/obj/toy.dmi' @@ -1624,7 +1152,6 @@ icon_state = "warden" toysay = "Seventeen minutes for coughing at an officer!" - /obj/item/toy/dummy name = "ventriloquist dummy" desc = "It's a dummy, dummy." @@ -1697,11 +1224,9 @@ cooldown = (world.time + 10) addtimer(CALLBACK(GLOBAL_PROC, .proc/playsound, src, 'sound/effects/blobattack.ogg', 50, FALSE), 0.5 SECONDS) - /* * Eldritch Toys */ - /obj/item/toy/eldritch_book name = "Codex Cicatrix" desc = "A toy book that closely resembles the Codex Cicatrix. Covered in fake polyester human flesh and has a huge goggly eye attached to the cover. The runes are gibberish and cannot be used to summon demons... Hopefully?" @@ -1724,7 +1249,6 @@ /* * Fake tear */ - /obj/item/toy/reality_pierce name = "Pierced reality" desc = "Hah. You thought it was the real deal!" @@ -1795,7 +1319,6 @@ GLOBAL_LIST_EMPTY(intento_players) #define TIME_PER_DEMO_STEP 0.6 SECONDS #define TIME_TO_RESET_ICON 0.5 SECONDS - /obj/item/toy/intento name = "\improper Intento" desc = "Fundamentally useless for all intentsive purposes." @@ -1877,7 +1400,6 @@ GLOBAL_LIST_EMPTY(intento_players) state = STATE_STARTING COOLDOWN_START(src, next_process, TIME_TO_BEGIN) - /obj/item/toy/intento/process() if(next_icon_reset && next_icon_reset <= world.time) icon_state = initial(icon_state) diff --git a/code/game/objects/structures/tables_racks.dm b/code/game/objects/structures/tables_racks.dm index 51b6e811129..9b863f7ce69 100644 --- a/code/game/objects/structures/tables_racks.dm +++ b/code/game/objects/structures/tables_racks.dm @@ -60,6 +60,7 @@ ) AddElement(/datum/element/contextual_screentip_tools, tool_behaviors) + register_context() /obj/structure/table/examine(mob/user) . = ..() @@ -114,11 +115,9 @@ user.stop_pulling() return ..() - /obj/structure/table/attack_tk(mob/user) return - /obj/structure/table/CanAllowThrough(atom/movable/mover, border_dir) . = ..() if(.) @@ -215,6 +214,14 @@ return // If the tray IS empty, continue on (tray will be placed on the table like other items) + if(istype(I, /obj/item/toy/cards/deck)) + var/obj/item/toy/cards/deck/dealer_deck = I + if(dealer_deck.wielded) // deal a card facedown on the table + var/obj/item/toy/singlecard/card = dealer_deck.draw(user) + if(card) + attackby(card, user, params) + return + if(istype(I, /obj/item/riding_offhand)) var/obj/item/riding_offhand/riding_item = I var/mob/living/carried_mob = riding_item.rider @@ -252,6 +259,27 @@ else return ..() +/obj/structure/table/attackby_secondary(obj/item/weapon, mob/user, params) + if(istype(weapon, /obj/item/toy/cards/deck)) + var/obj/item/toy/cards/deck/dealer_deck = weapon + if(dealer_deck.wielded) // deal a card faceup on the table + var/obj/item/toy/singlecard/card = dealer_deck.draw(user) + if(card) + card.Flip() + attackby(card, user, params) + return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN + ..() + return SECONDARY_ATTACK_CONTINUE_CHAIN + +/obj/structure/table/add_context(atom/source, list/context, obj/item/held_item, mob/living/user) + if(istype(held_item, /obj/item/toy/cards/deck)) + var/obj/item/toy/cards/deck/dealer_deck = held_item + if(dealer_deck.wielded) + context[SCREENTIP_CONTEXT_LMB] = "Deal card" + context[SCREENTIP_CONTEXT_RMB] = "Deal card faceup" + return CONTEXTUAL_SCREENTIP_SET + return NONE + /obj/structure/table/proc/AfterPutItemOnTable(obj/item/I, mob/living/user) return diff --git a/code/modules/cards/cardhand.dm b/code/modules/cards/cardhand.dm new file mode 100644 index 00000000000..a2606ae3b1f --- /dev/null +++ b/code/modules/cards/cardhand.dm @@ -0,0 +1,143 @@ +/obj/item/toy/cards/cardhand + name = "hand of cards" + desc = "A number of cards not in a deck, customarily held in ones hand." + icon = 'icons/obj/playing_cards.dmi' + icon_state = "none" + w_class = WEIGHT_CLASS_TINY + worn_icon_state = "card" + +/obj/item/toy/cards/cardhand/Initialize(mapload, list/cards_to_combine) + . = ..() + + if(!LAZYLEN(cards_to_combine) && (mapload && !LAZYLEN(cards))) + CRASH("[src] is being made into a cardhand without a list of cards to combine") + + if(mapload && LAZYLEN(cards)) // these cards have not been initialized + for(var/card_name in cards) + var/obj/item/toy/singlecard/new_card = new (loc, card_name) + new_card.update_appearance() + cards_to_combine += new_card + cards = list() // reset our cards to an empty list + + for(var/obj/item/toy/singlecard/new_card in cards_to_combine) + new_card.forceMove(src) + cards += new_card + + register_context() + update_appearance() + +/obj/item/toy/cards/cardhand/suicide_act(mob/living/carbon/user) + user.visible_message(span_suicide("[user] is slitting [user.p_their()] wrists with \the [src]! It looks like [user.p_they()] [user.p_have()] a crummy hand!")) + playsound(src, 'sound/items/cardshuffle.ogg', 50, TRUE) + return BRUTELOSS + +/obj/item/toy/cards/cardhand/examine(mob/user) + . = ..() + for(var/obj/item/toy/singlecard/card in cards) + if(HAS_TRAIT(user, TRAIT_XRAY_VISION)) + . += span_notice("You scan the cardhand with your x-ray vision and there is a: [card.cardname]") + var/marked_color = card.getMarkedColor(user) + if(marked_color) + . += span_notice("There is a [marked_color] mark on the corner of a card in the cardhand!") + +/obj/item/toy/cards/cardhand/add_context(atom/source, list/context, obj/item/held_item, mob/living/user) + if(istype(held_item, /obj/item/toy/cards/deck)) + var/obj/item/toy/cards/deck/dealer_deck = held_item + if(dealer_deck.wielded) + context[SCREENTIP_CONTEXT_LMB] = "Deal card" + context[SCREENTIP_CONTEXT_RMB] = "Deal card faceup" + return CONTEXTUAL_SCREENTIP_SET + context[SCREENTIP_CONTEXT_LMB] = "Recycle cards" + return CONTEXTUAL_SCREENTIP_SET + + if(istype(held_item, /obj/item/toy/singlecard)) + context[SCREENTIP_CONTEXT_LMB] = "Combine cards" + context[SCREENTIP_CONTEXT_RMB] = "Combine cards faceup" + return CONTEXTUAL_SCREENTIP_SET + + return NONE + +/obj/item/toy/cards/cardhand/attack_self(mob/living/user) + if(!isliving(user) || !user.canUseTopic(src, BE_CLOSE, NO_DEXTERITY, NO_TK)) + return + + var/list/handradial = list() + for(var/obj/item/toy/singlecard/card in cards) + handradial[card] = image(icon = src.icon, icon_state = card.icon_state) + + var/obj/item/toy/singlecard/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/singlecard/selected_card = draw(user, choice) + selected_card.pickup(user) + user.put_in_hands(selected_card) + + if(cards.len == 1) + user.temporarilyRemoveItemFromInventory(src, TRUE) + var/obj/item/toy/singlecard/last_card = draw(user) + last_card.pickup(user) + user.put_in_hands(last_card) + qdel(src) // cardhand is empty now so delete it + +/obj/item/toy/cards/cardhand/proc/check_menu(mob/living/user) + return isliving(user) && !user.incapacitated() + +/obj/item/toy/cards/cardhand/attackby(obj/item/weapon, mob/living/user, params, flip_card = FALSE) + var/obj/item/toy/singlecard/card + + if(istype(weapon, /obj/item/toy/singlecard)) + card = weapon + + if(istype(weapon, /obj/item/toy/cards/deck)) + var/obj/item/toy/cards/deck/dealer_deck = weapon + if(!dealer_deck.wielded) // recycle cardhand into deck (if unwielded) + dealer_deck.insert(src) + user.balloon_alert_to_viewers("puts card in deck") + return + card = dealer_deck.draw(user) + + if(card) + if(flip_card) + card.Flip() + insert(card) + return + + return ..() + +/obj/item/toy/cards/cardhand/attackby_secondary(obj/item/weapon, mob/user, params) + attackby(weapon, user, params, flip_card = TRUE) + return SECONDARY_ATTACK_CONTINUE_CHAIN + +#define CARDS_MAX_DISPLAY_LIMIT 5 // the amount of cards that are displayed in a hand +#define CARDS_PIXEL_X_OFFSET -5 // start out displaying the 1st card -5 pixels left +#define CARDS_ANGLE_OFFSET -45 // start out displaying the 1st card -45 degrees counter clockwise + +/obj/item/toy/cards/cardhand/update_overlays() + . = ..() + cut_overlays() + if(cards.len <= 1) + icon_state = null // we want an error icon to appear if this doesn't get qdel + return + + var/starting_card_pos = max(1, cards.len - CARDS_MAX_DISPLAY_LIMIT) // only display the top cards in the cardhand + var/cards_to_display = min(CARDS_MAX_DISPLAY_LIMIT, cards.len) + // 90 degrees from the 1st card to the last, so split the divider by total cards displayed + var/angle_divider = round(90/(cards_to_display - 1)) + // 10 pixels from the 1st card to the last, so split the divider by total cards displayed + var/pixel_divider = round(10/(cards_to_display - 1)) + + // starting from the 1st card to last, we want to slowly increase the angle and pixel_x offset + // to spread the cards out using our dividers + for(var/i in 0 to cards_to_display - 1) + var/obj/item/toy/singlecard/card = cards[starting_card_pos + i] + var/image/card_overlay = image(icon, icon_state = card.icon_state, pixel_x = CARDS_PIXEL_X_OFFSET + (i * pixel_divider)) + var/rotation_angle = CARDS_ANGLE_OFFSET + (i * angle_divider) + var/matrix/M = matrix() + M.Turn(rotation_angle) + card_overlay.transform = M + add_overlay(card_overlay) + +#undef CARDS_MAX_DISPLAY_LIMIT +#undef CARDS_PIXEL_X_OFFSET +#undef CARDS_ANGLE_OFFSET diff --git a/code/modules/cards/cards.dm b/code/modules/cards/cards.dm new file mode 100644 index 00000000000..ce5e9c88a5f --- /dev/null +++ b/code/modules/cards/cards.dm @@ -0,0 +1,114 @@ +/* +** The base card class that is used by decks and cardhands +*/ +/obj/item/toy/cards + resistance_flags = FLAMMABLE + max_integrity = 50 + /// Do all the cards drop to the floor when thrown at a person + var/can_play_52_card_pickup = TRUE + /// List of cards for a hand or deck + var/list/cards = list() + +/obj/item/toy/cards/Destroy() + if(LAZYLEN(cards)) + QDEL_LIST(cards) + return ..() + +/// This is how we play 52 card pickup +/obj/item/toy/cards/throw_impact(mob/living/target, datum/thrownthing/throwingdatum) + . = ..() + if(. || !istype(target)) // was it caught or is the target not a living mob + return . + + if(!throwingdatum?.thrower) // if a mob didn't throw it (need two people to play 52 pickup) + return + + var/has_no_cards = !LAZYLEN(cards) + if(has_no_cards) + return + + for(var/obj/item/toy/singlecard/card in cards) + cards -= card + card.forceMove(target.drop_location()) + if(prob(50)) + card.Flip() + card.pixel_x = rand(-16, 16) + card.pixel_y = rand(-16, 16) + var/matrix/Matrix = matrix() + var/angle = pick(0, 90, 180, 270) + Matrix.Turn(angle) + card.transform = Matrix + card.update_appearance() + + playsound(src, 'sound/items/cardshuffle.ogg', 50, TRUE) + + if(istype(src, /obj/item/toy/cards/cardhand)) + qdel(src) + return + + update_appearance() + +/** + * This is used to insert a list of cards into a deck or cardhand + * + * All cards that are inserted have their angle and pixel offsets reset to zero however their + * flip state does not change unless it's being inserted into a deck which is always facedown + * (see the deck/insert proc) + * + * Arguments: + * * card_item - Either a singlecard or cardhand that gets inserted into the src + */ +/obj/item/toy/cards/proc/insert(obj/item/toy/card_item) + var/cards_to_add = list() + var/obj/item/toy/cards/cardhand/recycled_cardhand + + if(istype(card_item, /obj/item/toy/singlecard)) + cards_to_add += card_item + + if(istype(card_item, /obj/item/toy/cards/cardhand)) + recycled_cardhand = card_item + + for(var/obj/item/toy/singlecard/card in recycled_cardhand.cards) + cards_to_add += card + recycled_cardhand.cards -= card + card.moveToNullspace() + qdel(recycled_cardhand) + + for(var/obj/item/toy/singlecard/card in cards_to_add) + card.forceMove(src) + // reset the position and angle + card.pixel_x = 0 + card.pixel_y = 0 + var/matrix/M = matrix() + M.Turn(0) + card.transform = M + card.update_appearance() + cards += card + cards_to_add -= card + update_appearance() + +/** + * Draws a card from the deck or hand of cards. + * + * Draws the top card unless a card arg is supplied then it picks that specific card + * and returns it (the card arg is used by the radial menu for cardhands to select + * specific cards out of the cardhand) + * Arguments: + * * mob/living/user - The user drawing the card. + * * obj/item/toy/singlecard/card (optional) - The card drawn from the hand +**/ +/obj/item/toy/cards/proc/draw(mob/living/user, obj/item/toy/singlecard/card) + if(!isliving(user) || !user.canUseTopic(src, BE_CLOSE, NO_DEXTERITY, NO_TK)) + return + + var/has_no_cards = !LAZYLEN(cards) + if(has_no_cards) + to_chat(user, span_warning("There are no more cards to draw!")) + return + + card = card || cards[1] //draw the card on top + cards -= card + + update_appearance() + playsound(src, 'sound/items/cardflip.ogg', 50, TRUE) + return card diff --git a/code/modules/cards/deck/blank.dm b/code/modules/cards/deck/blank.dm new file mode 100644 index 00000000000..2a47588adef --- /dev/null +++ b/code/modules/cards/deck/blank.dm @@ -0,0 +1,21 @@ +/obj/item/toy/cards/deck/blank + name = "custom deck of cards" + desc = "A deck of playing cards that can be customized with writing." + cardgame_desc = "custom card game" + icon_state = "deck_white_full" + deckstyle = "white" + has_unique_card_icons = FALSE + is_standard_deck = FALSE + decksize = 25 + can_play_52_card_pickup = FALSE + +/obj/item/toy/cards/deck/blank/black + icon_state = "deck_black_full" + deckstyle = "black" + +/obj/item/toy/cards/deck/blank/Initialize(mapload) + . = ..() + for(var/i in 1 to decksize) + var/obj/item/toy/singlecard/blank_card = new (src, "blank card", src) + blank_card.blank = TRUE + cards += blank_card diff --git a/code/modules/cards/deck/cas.dm b/code/modules/cards/deck/cas.dm new file mode 100644 index 00000000000..bb39a9802a0 --- /dev/null +++ b/code/modules/cards/deck/cas.dm @@ -0,0 +1,38 @@ +// CARDS AGAINST SPESS +// 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 +/obj/item/toy/cards/deck/cas + name = "\improper CAS deck (white)" + desc = "A deck for the game Cards Against Spess, still popular after all these centuries. Warning: may include traces of broken fourth wall. This is the white deck." + cardgame_desc = "Cards Against Spess game" + icon_state = "deck_white_full" + deckstyle = "white" + has_unique_card_icons = FALSE + is_standard_deck = FALSE + decksize = 150 + can_play_52_card_pickup = FALSE + +/obj/item/toy/cards/deck/cas/black + name = "\improper CAS deck (black)" + desc = "A deck for the game Cards Against Spess, still popular after all these centuries. Warning: may include traces of broken fourth wall. This is the black deck." + icon_state = "deck_black_full" + deckstyle = "black" + decksize = 50 + +GLOBAL_LIST_INIT(card_decks, list( + black = world.file2list("strings/cas_black.txt"), + white = world.file2list("strings/cas_white.txt") +)) + +/obj/item/toy/cards/deck/cas/Initialize(mapload) + . = ..() + var/list/cards_against_space = GLOB.card_decks[deckstyle] + var/list/possible_cards = cards_against_space.Copy() + var/list/random_cards = list() + + for(var/i in 1 to decksize) + random_cards += pick_n_take(possible_cards) + for(var/card in random_cards) + cards += new /obj/item/toy/singlecard(src, card, src) diff --git a/code/modules/cards/deck/deck.dm b/code/modules/cards/deck/deck.dm new file mode 100644 index 00000000000..f42c2f786ee --- /dev/null +++ b/code/modules/cards/deck/deck.dm @@ -0,0 +1,246 @@ +#define DECK_SHUFFLE_TIME (5 SECONDS) +#define DECK_SYNDIE_SHUFFLE_TIME (3 SECONDS) + +/obj/item/toy/cards/deck + name = "deck of cards" + desc = "A deck of space-grade playing cards." + icon = 'icons/obj/playing_cards.dmi' + icon_state = "deck_nanotrasen_full" + w_class = WEIGHT_CLASS_SMALL + worn_icon_state = "card" + hitsound = null + attack_verb_continuous = list("attacks") + attack_verb_simple = list("attack") + /// The amount of time it takes to shuffle + var/shuffle_time = DECK_SHUFFLE_TIME + /// Deck shuffling cooldown. + COOLDOWN_DECLARE(shuffle_cooldown) + /// If the deck is the standard 52 playing card deck (used for poker and blackjack) + var/is_standard_deck = TRUE + /// The amount of cards to spawn in the deck (optional) + var/decksize = INFINITY + /// The description of the cardgame that is played with this deck (used for memories) + var/cardgame_desc = "card game" + /// Wielding status for holding with two hands + var/wielded = FALSE + /// The holodeck computer used to spawn a holographic deck (see /obj/item/toy/cards/deck/syndicate/holographic) + var/obj/machinery/computer/holodeck/holodeck + /// If the cards in the deck have different card faces icons (blank and CAS decks do not) + var/has_unique_card_icons = TRUE + /// The art style of deck used (determines both deck and card icons used) + var/deckstyle = "nanotrasen" + +/obj/item/toy/cards/deck/Initialize(mapload) + . = ..() + AddElement(/datum/element/drag_pickup) + RegisterSignal(src, COMSIG_TWOHANDED_WIELD, .proc/on_wield) + RegisterSignal(src, COMSIG_TWOHANDED_UNWIELD, .proc/on_unwield) + AddComponent(/datum/component/two_handed, attacksound='sound/items/cardflip.ogg') + register_context() + + if(!is_standard_deck) + return + + // generate a normal playing card deck + cards += new /obj/item/toy/singlecard(src, "Joker Clown", src) + cards += new /obj/item/toy/singlecard(src, "Joker Mime", src) + for(var/suit in list("Hearts", "Spades", "Clubs", "Diamonds")) + cards += new /obj/item/toy/singlecard(src, "Ace of [suit]", src) + for(var/i in 2 to 10) + cards += new /obj/item/toy/singlecard(src, "[i] of [suit]", src) + for(var/person in list("Jack", "Queen", "King")) + cards += new /obj/item/toy/singlecard(src, "[person] of [suit]", src) + +/// triggered on wield of two handed item +/obj/item/toy/cards/deck/proc/on_wield(obj/item/source, mob/user) + SIGNAL_HANDLER + + wielded = TRUE + +/// triggered on unwield of two handed item +/obj/item/toy/cards/deck/proc/on_unwield(obj/item/source, mob/user) + SIGNAL_HANDLER + + wielded = FALSE + +/obj/item/toy/cards/deck/suicide_act(mob/living/carbon/user) + user.visible_message(span_suicide("[user] is slitting [user.p_their()] wrists with \the [src]! It looks like their luck ran out!")) + playsound(src, 'sound/items/cardshuffle.ogg', 50, TRUE) + return BRUTELOSS + +/obj/item/toy/cards/deck/examine(mob/user) + . = ..() + + if(cards.len > 0) + var/obj/item/toy/singlecard/card = cards[1] + if(HAS_TRAIT(user, TRAIT_XRAY_VISION)) + . += span_notice("You scan the deck with your x-ray vision and the top card reads: [card.cardname].") + var/marked_color = card.getMarkedColor(user) + if(marked_color) + . += span_notice("The top card of the deck has a [marked_color] mark on the corner!") + . += span_notice("Click and drag the deck to yourself to pickup.") // This should be a context screentip + +/obj/item/toy/cards/deck/add_context(atom/source, list/context, obj/item/held_item, mob/living/user) + if(src == held_item) + var/obj/item/toy/cards/deck/dealer_deck = held_item + context[SCREENTIP_CONTEXT_LMB] = dealer_deck.wielded ? "Recycle mode" : "Dealer mode" + context[SCREENTIP_CONTEXT_ALT_LMB] = "Shuffle" + return CONTEXTUAL_SCREENTIP_SET + + if(isnull(held_item)) + context[SCREENTIP_CONTEXT_LMB] = "Draw card" + context[SCREENTIP_CONTEXT_RMB] = "Draw card faceup" + // add drag & drop screentip here in the future + return CONTEXTUAL_SCREENTIP_SET + + if(istype(held_item, /obj/item/toy/singlecard)) + context[SCREENTIP_CONTEXT_LMB] = "Recycle card" + return CONTEXTUAL_SCREENTIP_SET + + if(istype(held_item, /obj/item/toy/cards/cardhand)) + context[SCREENTIP_CONTEXT_LMB] = "Recycle cards" + return CONTEXTUAL_SCREENTIP_SET + + return NONE + +/** + * Shuffles the cards in the deck + * + * Arguments: + * * user - The person shuffling the cards. + */ +/obj/item/toy/cards/deck/proc/shuffle_cards(mob/living/user) + if(!COOLDOWN_FINISHED(src, shuffle_cooldown)) + return + COOLDOWN_START(src, shuffle_cooldown, shuffle_time) + cards = shuffle(cards) + playsound(src, 'sound/items/cardshuffle.ogg', 50, TRUE) + user.balloon_alert_to_viewers("shuffles the deck") + addtimer(CALLBACK(src, .proc/CardgameEvent, user), 60 SECONDS, TIMER_OVERRIDE|TIMER_UNIQUE) + +/// This checks if nearby mobs are playing a cardgame and triggers a mood and memory +/obj/item/toy/cards/deck/proc/CardgameEvent(mob/living/dealer) + var/card_players = list() + for(var/mob/living/carbon/person in viewers(loc, COMBAT_MESSAGE_RANGE)) + var/obj/item/toy/held_card_item = person.is_holding_item_of_type(/obj/item/toy/singlecard) || person.is_holding_item_of_type(/obj/item/toy/cards/deck) || person.is_holding_item_of_type(/obj/item/toy/cards/cardhand) + if(held_card_item) + card_players[person] = held_card_item + + if(length(card_players) >= 2) // need at least 2 people to play a cardgame, duh! + for(var/mob/living/carbon/player in card_players) + var/other_players = english_list(card_players - player) + var/obj/item/toy/held_card_item = card_players[player] + + SEND_SIGNAL(player, COMSIG_ADD_MOOD_EVENT, "playing_cards", /datum/mood_event/playing_cards) + player.mind?.add_memory( + MEMORY_PLAYING_CARDS, + list( + DETAIL_PROTAGONIST = player, + DETAIL_PLAYERS = other_players, + DETAIL_CARDGAME = cardgame_desc, + DETAIL_DEALER = dealer, + DETAIL_HELD_CARD_ITEM = held_card_item, + ), + story_value = STORY_VALUE_OKAY, + memory_flags = MEMORY_CHECK_BLINDNESS + ) + + +/obj/item/toy/cards/deck/attack_hand(mob/living/user, list/modifiers, flip_card = FALSE) + if(!ishuman(user) || !user.canUseTopic(src, BE_CLOSE, NO_DEXTERITY, NO_TK, !iscyborg(user))) + return + + var/obj/item/toy/singlecard/card = draw(user) + if(!card) + return + if(flip_card) + card.Flip() + card.pickup(user) + user.put_in_hands(card) + user.balloon_alert_to_viewers("draws a card") + +/obj/item/toy/cards/deck/attack_hand_secondary(mob/living/user, list/modifiers) + attack_hand(user, modifiers, flip_card = TRUE) + return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN + +/obj/item/toy/cards/deck/AltClick(mob/living/user) + if(user.canUseTopic(src, BE_CLOSE, NO_DEXTERITY, NO_TK, !iscyborg(user))) + if(wielded) + shuffle_cards(user) + else + to_chat(user, span_notice("You must hold the [src] with both hands to shuffle.")) + return ..() + +/obj/item/toy/cards/deck/update_icon_state() + switch(cards.len) + if(27 to INFINITY) + icon_state = "deck_[deckstyle]_full" + if(11 to 27) + icon_state = "deck_[deckstyle]_half" + if(1 to 11) + icon_state = "deck_[deckstyle]_low" + else + icon_state = "deck_[deckstyle]_empty" + return ..() + +/obj/item/toy/cards/deck/insert(obj/item/toy/card_item) + // any card inserted into the deck is always facedown + if(istype(card_item, /obj/item/toy/singlecard)) + var/obj/item/toy/singlecard/card = card_item + card.Flip(CARD_FACEDOWN) + if(istype(card_item, /obj/item/toy/cards/cardhand)) + var/obj/item/toy/cards/cardhand/cardhand = card_item + for(var/obj/item/toy/singlecard/card in cardhand.cards) + card.Flip(CARD_FACEDOWN) + . = ..() + +/obj/item/toy/cards/deck/attackby(obj/item/item, mob/living/user, params) + if(istype(item, /obj/item/toy/singlecard) || istype(item, /obj/item/toy/cards/cardhand)) + insert(item) + var/card_grammar = istype(item, /obj/item/toy/singlecard) ? "card" : "cards" + user.balloon_alert_to_viewers("puts [card_grammar] in deck") + return + return ..() + +/// This is how we play 52 card pickup +/obj/item/toy/cards/deck/throw_impact(mob/living/target, datum/thrownthing/throwingdatum) + . = ..() + if(. || !istype(target)) // was it caught or is the target not a living mob + return . + + if(!throwingdatum?.thrower) // if a mob didn't throw it (need two people to play 52 pickup) + return + + var/mob/living/thrower = throwingdatum.thrower + + target.visible_message(span_warning("[target] is forced to play 52 card pickup!"), span_warning("You are forced to play 52 card pickup.")) + SEND_SIGNAL(target, COMSIG_ADD_MOOD_EVENT, "lost_52_card_pickup", /datum/mood_event/lost_52_card_pickup) + SEND_SIGNAL(thrower, COMSIG_ADD_MOOD_EVENT, "won_52_card_pickup", /datum/mood_event/won_52_card_pickup) + add_memory_in_range( + target, + 7, + MEMORY_PLAYING_52_PICKUP, + list(DETAIL_PROTAGONIST = thrower, DETAIL_DEUTERAGONIST = target, DETAIL_WHAT_BY = src), + story_value = STORY_VALUE_OKAY, + memory_flags = MEMORY_CHECK_BLINDNESS + ) + +/* +|| Syndicate playing cards, for pretending you're Gambit and playing poker for the nuke disk. || +*/ +/obj/item/toy/cards/deck/syndicate + name = "suspicious looking deck of cards" + desc = "A deck of space-grade playing cards. They seem unusually rigid." + cardgame_desc = "suspicious card game" + icon_state = "deck_syndicate_full" + deckstyle = "syndicate" + hitsound = 'sound/weapons/bladeslice.ogg' + force = 5 + throwforce = 10 + attack_verb_continuous = list("attacks", "slices", "dices", "slashes", "cuts") + attack_verb_simple = list("attack", "slice", "dice", "slash", "cut") + resistance_flags = NONE + shuffle_time = DECK_SYNDIE_SHUFFLE_TIME + +#undef DECK_SHUFFLE_TIME +#undef DECK_SYNDIE_SHUFFLE_TIME diff --git a/code/modules/cards/deck/kotahi.dm b/code/modules/cards/deck/kotahi.dm new file mode 100644 index 00000000000..19e832f1d28 --- /dev/null +++ b/code/modules/cards/deck/kotahi.dm @@ -0,0 +1,21 @@ +/obj/item/toy/cards/deck/kotahi + name = "\improper KOTAHI deck" + desc = "A deck of kotahi cards. House rules to argue over not included." + cardgame_desc = "KOTAHI game" + icon_state = "deck_kotahi_full" + deckstyle = "kotahi" + is_standard_deck = FALSE + +/obj/item/toy/cards/deck/kotahi/Initialize(mapload) + . = ..() + for(var/colour in list("Red","Yellow","Green","Blue")) + cards += new /obj/item/toy/singlecard(src, "[colour] 0", src) //kotahi decks have only one colour of each 0, weird huh? + for(var/k in 0 to 1) //two of each colour of number + cards += new /obj/item/toy/singlecard(src, "[colour] skip", src) + cards += new /obj/item/toy/singlecard(src, "[colour] reverse", src) + cards += new /obj/item/toy/singlecard(src, "[colour] draw 2", src) + for(var/i in 1 to 9) + cards += new /obj/item/toy/singlecard(src, "[colour] [i]", src) + for(var/k in 0 to 3) //4 wilds and draw 4s + cards += new /obj/item/toy/singlecard(src, "Wildcard", src) + cards += new /obj/item/toy/singlecard(src, "Draw 4", src) diff --git a/code/modules/cards/deck/tarot.dm b/code/modules/cards/deck/tarot.dm new file mode 100644 index 00000000000..eb56c320cf6 --- /dev/null +++ b/code/modules/cards/deck/tarot.dm @@ -0,0 +1,59 @@ +#define TAROT_GHOST_TIMER (666 SECONDS) // this translates into 11 mins and 6 seconds + +//These cards certainly won't tell the future, but you can play some nice games with them. +/obj/item/toy/cards/deck/tarot + name = "tarot game deck" + desc = "A full 78 card game deck of tarot cards. Complete with 4 suites of 14 cards, and a full suite of trump cards." + cardgame_desc = "tarot card reading" + icon_state = "deck_tarot_full" + deckstyle = "tarot" + is_standard_deck = FALSE + +/obj/item/toy/cards/deck/tarot/Initialize(mapload) + . = ..() + for(var/suit in list("Hearts", "Pikes", "Clovers", "Tiles")) + for(var/i in 1 to 10) + cards += new /obj/item/toy/singlecard(src, "[i] of [suit]", src) + for(var/person in list("Valet", "Chevalier", "Dame", "Roi")) + cards += new /obj/item/toy/singlecard(src, "[person] of [suit]", src) + for(var/trump in list("The Magician", "The High Priestess", "The Empress", "The Emperor", "The Hierophant", "The Lover", "The Chariot", "Justice", "The Hermit", "The Wheel of Fortune", "Strength", "The Hanged Man", "Death", "Temperance", "The Devil", "The Tower", "The Star", "The Moon", "The Sun", "Judgement", "The World", "The Fool")) + cards += new /obj/item/toy/singlecard(src, trump, src) + +/obj/item/toy/cards/deck/tarot/draw(mob/user) + . = ..() + if(prob(50)) + var/obj/item/toy/singlecard/card = . + var/matrix/M = matrix() + M.Turn(180) + card.transform = M + +/obj/item/toy/cards/deck/tarot/haunted + name = "haunted tarot game deck" + desc = "A spooky looking tarot deck. You can sense a supernatural presence linked to the cards..." + /// ghost notification cooldown + COOLDOWN_DECLARE(ghost_alert_cooldown) + +/obj/item/toy/cards/deck/tarot/haunted/on_wield(obj/item/source, mob/living/carbon/user) + . = ..() + ADD_TRAIT(user, TRAIT_SIXTHSENSE, MAGIC_TRAIT) + to_chat(user, span_notice("The veil to the underworld is opened. You can sense the dead souls calling out...")) + + if(!COOLDOWN_FINISHED(src, ghost_alert_cooldown)) + return + + COOLDOWN_START(src, ghost_alert_cooldown, TAROT_GHOST_TIMER) + notify_ghosts( + "Someone has begun playing with a [src.name] in [get_area(src)]!", + source = src, + header = "Haunted Tarot Deck", + ghost_sound = 'sound/effects/ghost2.ogg', + notify_volume = 75, + action = NOTIFY_ORBIT, + ) + +/obj/item/toy/cards/deck/tarot/haunted/on_unwield(obj/item/source, mob/living/carbon/user) + . = ..() + REMOVE_TRAIT(user, TRAIT_SIXTHSENSE, MAGIC_TRAIT) + to_chat(user, span_notice("The veil to the underworld closes shut. You feel your senses returning to normal.")) + +#undef TAROT_GHOST_TIMER diff --git a/code/modules/cards/deck/wizoff.dm b/code/modules/cards/deck/wizoff.dm new file mode 100644 index 00000000000..ab6cb2e6f45 --- /dev/null +++ b/code/modules/cards/deck/wizoff.dm @@ -0,0 +1,34 @@ +//It's Wiz-Off, the wizard themed card game! It's modular too, in case you might want to make it Syndie, Sec and Clown themed or something stupid like that. +/obj/item/toy/cards/deck/wizoff + name = "\improper Wiz-Off deck" + desc = "A Wiz-Off deck. Fight an arcane battle for the fate of the universe: Draw 5! Play 5! Best of 5! A rules card is attached." + cardgame_desc = "Wiz-Off game" + icon_state = "deck_wizoff_full" + deckstyle = "wizoff" + is_standard_deck = FALSE + +/obj/item/toy/cards/deck/wizoff/Initialize(mapload) + . = ..() + var/card_list = strings("wizoff.json", "wizard") + cards += new /obj/item/toy/singlecard/wizoff_ruleset(src) // ruleset should be the top card + for(var/card in card_list) + cards += new /obj/item/toy/singlecard(src, card, src) + +/obj/item/toy/singlecard/wizoff_ruleset + desc = "A ruleset for the playing card game Wiz-Off." + cardname = "Wizoff Ruleset" + deckstyle = "black" + has_unique_card_icons = FALSE + icon_state = "singlecard_down_black" + +/obj/item/toy/singlecard/wizoff_ruleset/examine(mob/living/carbon/human/user) + . = ..() + . += span_notice("Remember the rules of Wiz-Off!") + . += span_info("Each player draws 5 cards.") + . += span_info("There are five rounds. Each round, a player selects a card to play, and the winner is selected based on the following rules:") + . += span_info("Defensive beats Offensive!") + . += span_info("Offensive beats Utility!") + . += span_info("Utility beats Defensive!") + . += span_info("If both players play the same type of spell, the higher number wins!") + . += span_info("The player who wins the most of the 5 rounds wins the game!") + . += span_notice("Now get ready to battle for the fate of the universe: Wiz-Off!") diff --git a/code/modules/cards/singlecard.dm b/code/modules/cards/singlecard.dm new file mode 100644 index 00000000000..457561b76c5 --- /dev/null +++ b/code/modules/cards/singlecard.dm @@ -0,0 +1,242 @@ +#define CARD_FACEDOWN 0 +#define CARD_FACEUP 1 + +/obj/item/toy/singlecard + name = "card" + desc = "A playing card used to play card games like poker." + icon = 'icons/obj/playing_cards.dmi' + icon_state = "sc_Ace of Spades_nanotrasen" + w_class = WEIGHT_CLASS_TINY + worn_icon_state = "card" + pixel_x = -5 + resistance_flags = FLAMMABLE + max_integrity = 50 + force = 0 + throwforce = 0 + throw_speed = 3 + throw_range = 7 + attack_verb_continuous = list("attacks") + attack_verb_simple = list("attack") + /// Artistic style of the deck + var/deckstyle = "nanotrasen" + /// If the cards in the deck have different icon states (blank and CAS decks do not) + var/has_unique_card_icons = TRUE + /// The name of the card + var/cardname = "Ace of Spades" + /// Is the card flipped facedown (FALSE) or flipped faceup (TRUE) + var/flipped = FALSE + /// The card is blank and can be written on with a pen. + var/blank = FALSE + /// The color used to mark a card for cheating (by pens or crayons) + var/marked_color + +/obj/item/toy/singlecard/Initialize(mapload, cardname, obj/item/toy/cards/deck/parent_deck) + . = ..() + src.cardname = cardname || src.cardname + if(istype(parent_deck)) + deckstyle = parent_deck.deckstyle + has_unique_card_icons = parent_deck.has_unique_card_icons + icon_state = "singlecard_down_[parent_deck.deckstyle]" + hitsound = parent_deck.hitsound + force = parent_deck.force + throwforce = parent_deck.throwforce + throw_speed = parent_deck.throw_speed + throw_range = parent_deck.throw_range + attack_verb_continuous = parent_deck.attack_verb_continuous + attack_verb_simple = parent_deck.attack_verb_simple + + if(parent_deck.holodeck) + flags_1 |= HOLOGRAM_1 + parent_deck.holodeck.spawned += src + + register_context() + + +/obj/item/toy/singlecard/examine(mob/living/carbon/human/user) + . = ..() + if(!ishuman(user)) + return + + if(user.is_holding(src)) + user.visible_message(span_notice("[user] checks [user.p_their()] card."), span_notice("The card reads: [cardname].")) + if(blank) + . += span_notice("The card is blank. Write on it with a pen.") + else if(HAS_TRAIT(user, TRAIT_XRAY_VISION)) + . += span_notice("You scan the card with your x-ray vision and it reads: [cardname].") + else + . += span_warning("You need to have the card in your hand to check it!") + + var/marked_color = getMarkedColor(user) + if(marked_color) + . += span_notice("The card has a [marked_color] mark on the corner!") + +/obj/item/toy/singlecard/add_context(atom/source, list/context, obj/item/held_item, mob/living/user) + if(isnull(held_item) || src == held_item) + context[SCREENTIP_CONTEXT_ALT_LMB] = "Rotate counter-clockwise" // add a ALT_RMB screentip to rotate clockwise + context[SCREENTIP_CONTEXT_RMB] = "Flip card" + return CONTEXTUAL_SCREENTIP_SET + + if(istype(held_item, /obj/item/toy/cards/deck)) + var/obj/item/toy/cards/deck/dealer_deck = held_item + if(dealer_deck.wielded) + context[SCREENTIP_CONTEXT_LMB] = "Deal card" + context[SCREENTIP_CONTEXT_RMB] = "Deal card faceup" + return CONTEXTUAL_SCREENTIP_SET + context[SCREENTIP_CONTEXT_LMB] = "Recycle card" + return CONTEXTUAL_SCREENTIP_SET + + if(istype(held_item, /obj/item/toy/singlecard)) + context[SCREENTIP_CONTEXT_LMB] = "Combine cards" + context[SCREENTIP_CONTEXT_RMB] = "Combine cards faceup" + return CONTEXTUAL_SCREENTIP_SET + + if(istype(held_item, /obj/item/toy/cards/cardhand)) + context[SCREENTIP_CONTEXT_LMB] = "Combine cards" + return CONTEXTUAL_SCREENTIP_SET + + if(istype(held_item, /obj/item/toy/crayon) || istype(held_item, /obj/item/pen)) + context[SCREENTIP_CONTEXT_LMB] = blank ? "Write on card" : "Mark card" + return CONTEXTUAL_SCREENTIP_SET + + return NONE + +/obj/item/toy/singlecard/suicide_act(mob/living/carbon/user) + user.visible_message(span_suicide("[user] is slitting [user.p_their()] wrists with \the [src]! It looks like [user.p_they()] [user.p_have()] an unlucky card!")) + playsound(src, 'sound/weapons/bladeslice.ogg', 50, TRUE) + return BRUTELOSS + +/** + * Flips the card over + * + * * Arguments: + * * orientation (optional) - Sets flipped state to CARD_FACEDOWN or CARD_FACEUP if given orientation (otherwise just invert the flipped state) + */ +/obj/item/toy/singlecard/proc/Flip(orientation) + if(!isnull(orientation)) + flipped = orientation + else + flipped = !flipped + + name = flipped ? cardname : "card" + update_appearance() + +/** + * Returns a color if a card is marked and the user can see it + * + * * Arguments: + * * user - We need to check if the user see the marked card + */ +/obj/item/toy/singlecard/proc/getMarkedColor(mob/living/carbon/user) + if(!istype(user)) + return + var/is_marked_with_visible_color = (marked_color && marked_color != "invisible") + if(is_marked_with_visible_color || (marked_color == "invisible" && HAS_TRAIT(user, TRAIT_REAGENT_SCANNER))) + return marked_color + +/obj/item/toy/singlecard/update_icon_state() + if(!flipped) + icon_state = "singlecard_down_[deckstyle]" + else if(has_unique_card_icons) // each card in a deck has a different icon + icon_state = "sc_[cardname]_[deckstyle]" + else // all cards are the same icon state (blank or scribble) + icon_state = blank ? "sc_blank_[deckstyle]" : "sc_scribble_[deckstyle]" + return ..() + +/obj/item/toy/singlecard/update_name() + name = flipped ? cardname : "card" + return ..() + +/obj/item/toy/singlecard/attackby(obj/item/item, mob/living/user, params, flip_card=FALSE) + var/obj/item/toy/singlecard/card + + if(istype(item, /obj/item/toy/cards/deck)) + var/obj/item/toy/cards/deck/dealer_deck = item + if(!dealer_deck.wielded) // recycle card into deck (if unwielded) + dealer_deck.insert(src) + user.balloon_alert_to_viewers("puts card in deck") + return + card = dealer_deck.draw(user) + + if(istype(item, /obj/item/toy/singlecard)) + card = item + + if(card) // card + card = combine into cardhand + if(flip_card) + card.Flip() + + if(istype(item, /obj/item/toy/cards/deck)) + // only decks cause a balloon alert + user.balloon_alert_to_viewers("deals a card") + + var/obj/item/toy/cards/cardhand/new_cardhand = new (drop_location(), list(src, card)) + new_cardhand.pixel_x = pixel_x + new_cardhand.pixel_y = pixel_y + + if(!isturf(loc)) // make a cardhand in our active hand + user.temporarilyRemoveItemFromInventory(src, TRUE) + new_cardhand.pickup(user) + user.put_in_active_hand(new_cardhand) + return + + if(istype(item, /obj/item/toy/cards/cardhand)) // insert into cardhand + var/obj/item/toy/cards/cardhand/target_cardhand = item + target_cardhand.insert(src) + return + + var/can_item_write + var/marked_cheating_color + + if(istype(item, /obj/item/pen)) + var/obj/item/pen/pen = item + can_item_write = TRUE + marked_cheating_color = (pen.colour == "white" && "invisible") || pen.colour + + if(istype(item, /obj/item/toy/crayon)) + var/obj/item/toy/crayon/crayon = item + can_item_write = TRUE + marked_cheating_color = (crayon.crayon_color == "mime" && "invisible") || crayon.crayon_color + + if(can_item_write && !blank) // You cheated not only the game, but yourself + marked_color = marked_cheating_color + to_chat(user, span_notice("You put a [marked_color] mark in the corner of [src] with the [item]. Cheat to win!")) + return + + if(can_item_write) + if(!user.is_literate()) + to_chat(user, span_notice("You scribble illegibly on [src]!")) + 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 + + cardname = cardtext + blank = FALSE + update_appearance() + return + return ..() + +/obj/item/toy/singlecard/attackby_secondary(obj/item/item, mob/living/user, modifiers) + attackby(item, user, modifiers, flip_card=TRUE) + return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN + +/obj/item/toy/singlecard/attack_hand_secondary(mob/living/carbon/human/user, modifiers) + attack_self(user) + return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN + +/obj/item/toy/singlecard/attack_self_secondary(mob/living/carbon/human/user, modifiers) + attack_self(user) + +/obj/item/toy/singlecard/attack_self(mob/living/carbon/human/user) + if(!ishuman(user) || !user.canUseTopic(src, BE_CLOSE, NO_DEXTERITY, NO_TK, !iscyborg(user))) + return + + Flip() + if(isturf(src.loc)) // only display tihs message when flipping in a visible spot like on a table + user.balloon_alert_to_viewers("flips a card") + +/obj/item/toy/singlecard/AltClick(mob/living/carbon/human/user) + if(user.canUseTopic(src, BE_CLOSE, NO_DEXTERITY, NO_TK, !iscyborg(user))) + transform = turn(transform, 90) + // use the simple_rotation component to make this turn with Alt+RMB & Alt+LMB at some point in the future - TimT + return ..() diff --git a/code/modules/clothing/glasses/_glasses.dm b/code/modules/clothing/glasses/_glasses.dm index 8670f9344fc..9afebfa4371 100644 --- a/code/modules/clothing/glasses/_glasses.dm +++ b/code/modules/clothing/glasses/_glasses.dm @@ -448,6 +448,16 @@ desc = "A pair of xray goggles manufactured by the Syndicate." vision_flags = SEE_TURFS|SEE_MOBS|SEE_OBJS +/obj/item/clothing/glasses/thermal/xray/equipped(mob/living/carbon/human/user, slot) + . = ..() + if(slot != ITEM_SLOT_EYES || !istype(user)) + return + ADD_TRAIT(user, TRAIT_XRAY_VISION, GLASSES_TRAIT) + +/obj/item/clothing/glasses/thermal/xray/dropped(mob/living/carbon/human/user) + . = ..() + REMOVE_TRAIT(user, TRAIT_XRAY_VISION, GLASSES_TRAIT) + /obj/item/clothing/glasses/thermal/syndi //These are now a traitor item, concealed as mesons. -Pete name = "chameleon thermals" desc = "A pair of thermal optic goggles with an onboard chameleon generator." @@ -551,11 +561,14 @@ H.add_hud_to(user) ADD_TRAIT(user, TRAIT_MEDICAL_HUD, GLASSES_TRAIT) ADD_TRAIT(user, TRAIT_SECURITY_HUD, GLASSES_TRAIT) + if(xray) + ADD_TRAIT(user, TRAIT_XRAY_VISION, GLASSES_TRAIT) /obj/item/clothing/glasses/debug/dropped(mob/user) . = ..() REMOVE_TRAIT(user, TRAIT_MEDICAL_HUD, GLASSES_TRAIT) REMOVE_TRAIT(user, TRAIT_SECURITY_HUD, GLASSES_TRAIT) + REMOVE_TRAIT(user, TRAIT_XRAY_VISION, GLASSES_TRAIT) if(ishuman(user)) for(var/hud in hudlist) var/datum/atom_hud/H = GLOB.huds[hud] @@ -566,8 +579,10 @@ if(ishuman(user)) if(xray) vision_flags -= SEE_MOBS|SEE_OBJS + REMOVE_TRAIT(user, TRAIT_XRAY_VISION, GLASSES_TRAIT) else vision_flags += SEE_MOBS|SEE_OBJS + ADD_TRAIT(user, TRAIT_XRAY_VISION, GLASSES_TRAIT) xray = !xray var/mob/living/carbon/human/H = user H.update_sight() diff --git a/code/modules/events/holiday/vday.dm b/code/modules/events/holiday/vday.dm index bf1c0d51c23..23dc60ef6e0 100644 --- a/code/modules/events/holiday/vday.dm +++ b/code/modules/events/holiday/vday.dm @@ -55,8 +55,8 @@ /obj/item/valentine name = "valentine" desc = "A Valentine's card! Wonder what it says..." - icon = 'icons/obj/toy.dmi' - icon_state = "sc_Ace of Hearts_syndicate" // shut up + icon = 'icons/obj/playing_cards.dmi' + icon_state = "sc_Ace of Hearts_syndicate" // shut up // bye felicia var/message = "A generic message of love or whatever." resistance_flags = FLAMMABLE w_class = WEIGHT_CLASS_TINY diff --git a/code/modules/games/cas.dm b/code/modules/games/cas.dm deleted file mode 100644 index d4738889162..00000000000 --- a/code/modules/games/cas.dm +++ /dev/null @@ -1,157 +0,0 @@ -// CARDS AGAINST SPESS -// 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 - -/obj/item/toy/cards/deck/cas - name = "\improper CAS deck (white)" - desc = "A deck for the game Cards Against Spess, still popular after all these centuries. Warning: may include traces of broken fourth wall. This is the white deck." - icon = 'icons/obj/toy.dmi' - icon_state = "deck_caswhite_full" - deckstyle = "caswhite" - var/card_face = "cas_white" - 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)" - desc = "A deck for the game Cards Against Spess, still popular after all these centuries. Warning: may include traces of broken fourth wall. This is the black deck." - icon_state = "deck_casblack_full" - deckstyle = "casblack" - card_face = "cas_black" - blanks = 0 - decksize = 50 - 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() - if(possiblecards.len < decksize) // sanity check - decksize = (possiblecards.len - 1) - var/list/randomcards = list() - for(var/x in 1 to decksize) - 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 - if(!blanks) - 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 - -/obj/item/toy/cards/deck/cas/draw_card(mob/user) - if(isliving(user)) - var/mob/living/L = user - if(!(L.mobility_flags & MOBILITY_PICKUP)) - return - if(cards.len == 0) - to_chat(user, span_warning("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 = WEAKREF(src) - src.cards -= choice - H.pickup(user) - user.put_in_hands(H) - user.visible_message(span_notice("[user] draws a card from the deck."), span_notice("You draw a card from the deck.")) - update_appearance() - -/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, span_warning("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(span_notice("[user] adds a card to the bottom of the deck."),span_notice("You add the card to the bottom of the deck.")) - qdel(SC) - update_appearance() - -/obj/item/toy/cards/deck/cas/update_icon_state() - if(cards.len < 26) - icon_state = "deck_[deckstyle]_low" - return ..() - -/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 (flipped) - . += span_notice("The card is face down.") - else if (blank) - . += span_notice("The card is blank. Write on it with a pen.") - else - . += span_notice("The card reads: [name]") - . += span_notice("Alt-click to flip it.") - -/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_appearance() - -/obj/item/toy/cards/singlecard/cas/AltClick(mob/living/user) - if(!ishuman(user) || !user.canUseTopic(src, BE_CLOSE)) - return - Flip() - -/obj/item/toy/cards/singlecard/cas/update_icon_state() - icon_state = "[card_face][flipped ? "_flipped" : null]" - return ..() - -/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, span_notice("You scribble illegibly on [src]!")) - return - if(!blank) - to_chat(user, span_warning("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/code/modules/games/kotahi.dm b/code/modules/games/kotahi.dm deleted file mode 100644 index d435d68905a..00000000000 --- a/code/modules/games/kotahi.dm +++ /dev/null @@ -1,20 +0,0 @@ -/obj/item/toy/cards/deck/kotahi - name = "\improper KOTAHI deck" - desc = "A deck of kotahi cards. House rules to argue over not included." - icon = 'icons/obj/toy.dmi' - icon_state = "deck_kotahi_full" - deckstyle = "kotahi" - -//Populate the deck. -/obj/item/toy/cards/deck/kotahi/populate_deck() - for(var/colour in list("Red","Yellow","Green","Blue")) - cards += generate_card("[colour] 0") //kotahi decks have only one colour of each 0, weird huh? - for(var/k in 0 to 1) //two of each colour of number - cards += generate_card("[colour] skip") - cards += generate_card("[colour] reverse") - cards += generate_card("[colour] draw 2") - for(var/i in 1 to 9) - cards += generate_card("[colour] [i]") - for(var/k in 0 to 3) //4 wilds and draw 4s - cards += generate_card("Wildcard") - cards += generate_card("Draw 4") diff --git a/code/modules/games/tarot.dm b/code/modules/games/tarot.dm deleted file mode 100644 index 91ef92c41ea..00000000000 --- a/code/modules/games/tarot.dm +++ /dev/null @@ -1,25 +0,0 @@ -//These cards certainly won't tell the future, but you can play some nice games with them. -/obj/item/toy/cards/deck/tarot - name = "tarot game deck" - desc = "A full 78 card game deck of tarot cards. Complete with 4 suites of 14 cards, and a full suite of trump cards." - icon = 'icons/obj/toy.dmi' - icon_state = "deck_tarot_full" - deckstyle = "tarot" - -/obj/item/toy/cards/deck/tarot/populate_deck() - for(var/suit in list("Hearts", "Pikes", "Clovers", "Tiles")) - for(var/i in 1 to 10) - cards += generate_card("[i] of [suit]") - for(var/person in list("Valet", "Chevalier", "Dame", "Roi")) - cards += generate_card("[person] of [suit]") - for(var/trump in list("The Magician", "The High Priestess", "The Empress", "The Emperor", "The Hierophant", "The Lover", "The Chariot", "Justice", "The Hermit", "The Wheel of Fortune", "Strength", "The Hanged Man", "Death", "Temperance", "The Devil", "The Tower", "The Star", "The Moon", "The Sun", "Judgement", "The World", "The Fool")) - cards += generate_card("[trump]") - -/obj/item/toy/cards/deck/tarot/draw_card(mob/user, list/cards, obj/item/toy/cards/singlecard/forced_card = null) - . = ..() - var/obj/item/toy/cards/singlecard/C = . - var/matrix/M = matrix() - M.Turn(180) - if(prob(50)) - C.transform = M - return diff --git a/code/modules/games/wizoff.dm b/code/modules/games/wizoff.dm deleted file mode 100644 index 73be649593a..00000000000 --- a/code/modules/games/wizoff.dm +++ /dev/null @@ -1,26 +0,0 @@ -//It's Wiz-Off, the wizard themed card game! It's modular too, in case you might want to make it Syndie, Sec and Clown themed or something stupid like that. -/obj/item/toy/cards/deck/wizoff - name = "\improper Wiz-Off deck" - desc = "A Wiz-Off deck. Fight an arcane battle for the fate of the universe: Draw 5! Play 5! Best of 5! A rules card is attached." - icon = 'icons/obj/toy.dmi' - icon_state = "deck_wizoff_full" - deckstyle = "wizoff" - var/theme = "wizard" - -/obj/item/toy/cards/deck/wizoff/populate_deck() - var/card_list = strings("wizoff.json", theme) - for(var/card in card_list) - cards += generate_card("[card]") - -/obj/item/toy/cards/deck/wizoff/examine_more(mob/user) - . = ..() - . += span_notice("Remember the rules of Wiz-Off!") - . += "\t[span_info("Each player draws 5 cards.")]" - . += "\t[span_info("There are five rounds. Each round, a player selects a card to play, and the winner is selected based on the following rules:")]" - . += "\t[span_info("Defensive beats Offensive!")]" - . += "\t[span_info("Offensive beats Utility!")]" - . += "\t[span_info("Utility beats Defensive!")]" - . += "\t[span_info("If both players play the same type of spell, the higher number wins!")]" - . += "\t[span_info("The player who wins the most of the 5 rounds wins the game!")]" - . += "\t[span_notice("Now get ready to battle for the fate of the universe: Wiz-Off!")]" - return . diff --git a/code/modules/holodeck/holo_effect.dm b/code/modules/holodeck/holo_effect.dm index f86c2ee27de..279c771a211 100644 --- a/code/modules/holodeck/holo_effect.dm +++ b/code/modules/holodeck/holo_effect.dm @@ -23,43 +23,16 @@ /obj/effect/holodeck_effect/proc/safety(active) return - // Generates a holodeck-tracked card deck /obj/effect/holodeck_effect/cards - icon = 'icons/obj/toy.dmi' - icon_state = "deck_nanotrasen_full" - var/obj/item/toy/cards/deck/deck + icon = 'icons/obj/playing_cards.dmi' + icon_state = "deck_syndicate_full" -/obj/effect/holodeck_effect/cards/activate(obj/machinery/computer/holodeck/HC) - deck = new(loc) - safety(!(HC.obj_flags & EMAGGED)) - deck.holo = HC - RegisterSignal(deck, COMSIG_PARENT_QDELETING, .proc/handle_card_delete) +/obj/effect/holodeck_effect/cards/activate(obj/machinery/computer/holodeck/holodeck) + var/obj/item/toy/cards/deck/syndicate/holographic/deck = new(loc, holodeck) + deck.flags_1 |= HOLOGRAM_1 return deck -/obj/effect/holodeck_effect/cards/proc/handle_card_delete(datum/source) - SIGNAL_HANDLER - deck = null - -/obj/effect/holodeck_effect/cards/safety(active) - if(!deck) - return - if(active) - deck.card_hitsound = null - deck.card_force = 0 - deck.card_throwforce = 0 - deck.card_throw_speed = 3 - deck.card_throw_range = 7 - deck.card_attack_verb_continuous = string_list(list("attacks")) - else - deck.card_hitsound = 'sound/weapons/bladeslice.ogg' - deck.card_force = 5 - deck.card_throwforce = 10 - deck.card_throw_speed = 3 - deck.card_throw_range = 7 - deck.card_attack_verb_continuous = string_list(list("attacks", "slices", "dices", "slashes", "cuts")) - - /obj/effect/holodeck_effect/sparks/activate(obj/machinery/computer/holodeck/HC) var/turf/T = get_turf(src) if(T) diff --git a/code/modules/holodeck/items.dm b/code/modules/holodeck/items.dm index c5113edac1f..1000d0e29a8 100644 --- a/code/modules/holodeck/items.dm +++ b/code/modules/holodeck/items.dm @@ -32,6 +32,24 @@ /obj/item/melee/energy/sword/holographic/red sword_color_icon = "red" +/obj/item/toy/cards/deck/syndicate/holographic + desc = "A deck of holographic playing cards." + +/obj/item/toy/cards/deck/syndicate/holographic/Initialize(mapload, obj/machinery/computer/holodeck/holodeck) + src.holodeck = holodeck + RegisterSignal(src, COMSIG_PARENT_QDELETING, .proc/handle_card_delete) + . = ..() + +/obj/item/toy/cards/deck/syndicate/holographic/proc/handle_card_delete(datum/source) + SIGNAL_HANDLER + + //if any REAL cards have been inserted into the deck they are moved outside before destroying it + for(var/obj/item/toy/singlecard/card in cards) + if(card.flags_1 & HOLOGRAM_1) + continue + cards -= card + card.forceMove(drop_location()) + //BASKETBALL OBJECTS /obj/item/toy/beach_ball/holoball diff --git a/code/modules/mob/living/simple_animal/guardian/guardian.dm b/code/modules/mob/living/simple_animal/guardian/guardian.dm index 1befcfa7a4f..3e807015109 100644 --- a/code/modules/mob/living/simple_animal/guardian/guardian.dm +++ b/code/modules/mob/living/simple_animal/guardian/guardian.dm @@ -508,7 +508,7 @@ GLOBAL_LIST_EMPTY(parasites) //all currently existing/living guardians /obj/item/guardiancreator name = "enchanted deck of tarot cards" desc = "An enchanted deck of tarot cards, rumored to be a source of unimaginable power." - icon = 'icons/obj/toy.dmi' + icon = 'icons/obj/playing_cards.dmi' icon_state = "deck_tarot_full" var/used = FALSE var/theme = "magic" diff --git a/code/modules/surgery/organs/eyes.dm b/code/modules/surgery/organs/eyes.dm index 44c9dbee9f1..fa9897a4417 100644 --- a/code/modules/surgery/organs/eyes.dm +++ b/code/modules/surgery/organs/eyes.dm @@ -193,6 +193,14 @@ see_in_dark = NIGHTVISION_FOV_RANGE sight_flags = SEE_MOBS | SEE_OBJS | SEE_TURFS +/obj/item/organ/eyes/robotic/xray/Insert(mob/living/carbon/eye_owner, special = FALSE) + . = ..() + ADD_TRAIT(eye_owner, TRAIT_XRAY_VISION, ORGAN_TRAIT) + +/obj/item/organ/eyes/robotic/xray/Remove(mob/living/carbon/eye_owner, special = FALSE) + REMOVE_TRAIT(eye_owner, TRAIT_XRAY_VISION, ORGAN_TRAIT) + return ..() + /obj/item/organ/eyes/robotic/thermals name = "thermal eyes" desc = "These cybernetic eye implants will give you thermal vision. Vertical slit pupil included." diff --git a/code/modules/unit_tests/create_and_destroy.dm b/code/modules/unit_tests/create_and_destroy.dm index 087a81b31d0..d678d0a5d77 100644 --- a/code/modules/unit_tests/create_and_destroy.dm +++ b/code/modules/unit_tests/create_and_destroy.dm @@ -86,6 +86,8 @@ ignore += typesof(/obj/effect/skyfall_landingzone) //Expects a mob to holderize, we have nothing to give ignore += typesof(/obj/item/clothing/head/mob_holder) + //Needs cards passed into the initilazation args + ignore += typesof(/obj/item/toy/cards/cardhand) var/list/cached_contents = spawn_at.contents.Copy() var/baseturf_count = length(spawn_at.baseturfs) diff --git a/code/modules/vending/games.dm b/code/modules/vending/games.dm index 6e4b7727022..4839620c8cb 100644 --- a/code/modules/vending/games.dm +++ b/code/modules/vending/games.dm @@ -8,6 +8,8 @@ /obj/item/storage/crayons = 2, /obj/item/toy/cards/deck = 5, /obj/item/storage/dice = 10, + /obj/item/toy/cards/deck/blank = 3, + /obj/item/toy/cards/deck/blank/black = 3, /obj/item/toy/cards/deck/cas = 3, /obj/item/toy/cards/deck/cas/black = 3, /obj/item/toy/cards/deck/kotahi = 3, diff --git a/icons/obj/playing_cards.dmi b/icons/obj/playing_cards.dmi new file mode 100644 index 00000000000..37fc465eab7 Binary files /dev/null and b/icons/obj/playing_cards.dmi differ diff --git a/icons/obj/toy.dmi b/icons/obj/toy.dmi index ce211baf91e..ea545d7132c 100644 Binary files a/icons/obj/toy.dmi and b/icons/obj/toy.dmi differ diff --git a/sound/items/cardflip.ogg b/sound/items/cardflip.ogg new file mode 100644 index 00000000000..88e909cdc10 Binary files /dev/null and b/sound/items/cardflip.ogg differ diff --git a/strings/cas_white.txt b/strings/cas_white.txt index 644ffdf21cf..f874eb15cb8 100644 --- a/strings/cas_white.txt +++ b/strings/cas_white.txt @@ -340,7 +340,6 @@ Cursed duffle bags. Fetish content. The shotgun. Lavaland bestiality. -░░░░░▄▄▄▄▀▀▀▀▀▀▀▀▄▄▄▄▄▄░░░░░░░ ░░░░░█░░░░▒▒▒▒▒▒▒▒▒▒▒▒░░▀▀▄░░░░ ░░░░█░░░▒▒▒▒▒▒░░░░░░░░▒▒▒░░█░░░ ░░░█░░░░░░▄██▀▄▄░░░░░▄▄▄░░░░█░░ ░▄▀▒▄▄▄▒░█▀▀▀▀▄▄█░░░██▄▄█░░░░█░ █░▒█▒▄░▀▄▄▄▀░░░░░░░░█░░░▒▒▒▒▒░█ █░▒█░█▀▄▄░░░░░█▀░░░░▀▄░░▄▀▀▀▄▒█ ░█░▀▄░█▄░█▀▄▄░▀░▀▀░▄▄▀░░░░█░░█░ ░░█░░░▀▄▀█▄▄░█▀▀▀▄▄▄▄▀▀█▀██░█░░ ░░░█░░░░██░░▀█▄▄▄█▄▄█▄████░█░░░ ░░░░█░░░░▀▀▄░█░░░█░█▀██████░█░░ ░░░░░▀▄░░░░░▀▀▄▄▄█▄█▄█▄█▄▀░░█░░ ░░░░░░░▀▄▄░▒▒▒▒░░░░░░░░░░▒░░░█░ ░░░░░░░░░░▀▀▄▄░▒▒▒▒▒▒▒▒▒▒░░░░█░ ░░░░░░░░░░░░░░▀▄▄▄▄▄░░░░░░░░█░░ Vore. CAT WITH NO UNDIES Surgical implantation of cat body parts. diff --git a/strings/memories.json b/strings/memories.json index b4035088d85..10cda761ea4 100644 --- a/strings/memories.json +++ b/strings/memories.json @@ -195,7 +195,7 @@ "The borging of %PROTAGONIST." ], "borged_starts":[ - "%PROTAGONIST havign their brain put into a robot", + "%PROTAGONIST having their brain put into a robot", "%PROTAGONIST getting turned into a bucket of bolts" ], "borged_moods":[ @@ -418,5 +418,29 @@ "nuke_code_starts":[ "The number %NUKE_CODE written on a sticky note with the words \"FOR SYNDICATE EYES ONLY\" scrawled next to it.", "A piece of paper with the number %NUKE_CODE being handed to %PROTAGONIST from a figure in a blood-red hardsuit." + ], + "playing_cards_names":[ + "The %CARDGAME of %PROTAGONIST with %PLAYERS." + ], + "playing_cards_starts":[ + "%PLAYERS are waiting for %PROTAGONIST to start the %CARDGAME", + "The %CARDGAME has been setup by %DEALER", + "%DEALER starts shuffling the deck for the %CARDGAME" + ], + "playing_cards_moods":[ + "%PROTAGONIST %MOOD as they hold the %HELD_CARD_ITEM for the %CARDGAME.", + "%PROTAGONIST %MOOD as they pickup the %HELD_CARD_ITEM.", + "%PROTAGONIST %MOOD as they put down the %HELD_CARD_ITEM." + ], + "playing_52_pickup_names":[ + "%PROTAGONIST tricking %DEUTERAGONIST into playing 52 pickup with %WHAT_BY." + ], + "playing_52_pickup_starts":[ + "%PROTAGONIST tosses the %WHAT_BY at %DEUTERAGONIST spilling cards all over the floor", + "A %WHAT_BY thrown by %PROTAGONIST splatters across %DEUTERAGONIST face" + ], + "playing_52_pickup_moods":[ + "%PROTAGONIST %MOOD as they taunt %DEUTERAGONIST.", + "%DEUTERAGONIST %MOOD as they shamefully pickup the cards." ] } diff --git a/strings/tips.txt b/strings/tips.txt index 6cc986f3496..bf057da3e9c 100644 --- a/strings/tips.txt +++ b/strings/tips.txt @@ -251,3 +251,4 @@ Laying down will help slow down bloodloss. Death will halt it entirely. ♪ Hey, have you ever tried appending the % character before your messages when speaking in-game? ♫ @You can use the |, + and _ characters to emphasize parts of what you say in-game (e.g. say"my _ass_ |is| +heavy+." will be outputted as "my ass is heavy."). You can also escape these emphasizers by appending backslashes before them (e.g. say"1\+2\+3" will come out as "1+2+3" and not "1\2\3"). If you knock into somebody while doing a wicked grind on a skateboard, they will be floored for double the time. Radical! +You can cheat games by baking dice in microwaves to make them loaded. Cards can be seen with x-ray vision or be marked with either a pen or crayon to gain an edge. diff --git a/tgstation.dme b/tgstation.dme index a7d59786db7..e43c5515a11 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -2395,6 +2395,15 @@ #include "code\modules\capture_the_flag\ctf_panel.dm" #include "code\modules\capture_the_flag\medieval_sim\medisim_classes.dm" #include "code\modules\capture_the_flag\medieval_sim\medisim_game.dm" +#include "code\modules\cards\cardhand.dm" +#include "code\modules\cards\cards.dm" +#include "code\modules\cards\singlecard.dm" +#include "code\modules\cards\deck\blank.dm" +#include "code\modules\cards\deck\cas.dm" +#include "code\modules\cards\deck\deck.dm" +#include "code\modules\cards\deck\kotahi.dm" +#include "code\modules\cards\deck\tarot.dm" +#include "code\modules\cards\deck\wizoff.dm" #include "code\modules\cargo\bounty.dm" #include "code\modules\cargo\centcom_podlauncher.dm" #include "code\modules\cargo\coupon.dm" @@ -2824,10 +2833,6 @@ #include "code\modules\food_and_drinks\restaurant\custom_order.dm" #include "code\modules\food_and_drinks\restaurant\generic_venues.dm" #include "code\modules\food_and_drinks\restaurant\customers\_customer.dm" -#include "code\modules\games\cas.dm" -#include "code\modules\games\kotahi.dm" -#include "code\modules\games\tarot.dm" -#include "code\modules\games\wizoff.dm" #include "code\modules\holiday\easter.dm" #include "code\modules\holiday\foreign_calendar.dm" #include "code\modules\holiday\holidays.dm"