diff --git a/code/modules/games/52card.dm b/code/modules/games/52card.dm new file mode 100644 index 00000000000..036df3dd0c3 --- /dev/null +++ b/code/modules/games/52card.dm @@ -0,0 +1,50 @@ +/obj/item/deck/holder + name = "card box" + desc = "A small leather case to show how classy you are compared to everyone else." + icon_state = "card_holder" + +/obj/item/deck/cards + name = "deck of cards" + desc = "A simple deck of playing cards." + icon_state = "deck" + +/obj/item/deck/cards/New() + ..() + for(var/suit in list("spades","clubs","diamonds","hearts")) + var/colour + if(suit == "spades" || suit == "clubs") + colour = "black_" + else + colour = "red_" + + for(var/number in list("ace","two","three","four","five","six","seven","eight","nine","ten")) + cards += new /datum/playingcard("[number] of [suit]", "[colour]num") + + for(var/number in list("jack","queen","king")) + cards += new /datum/playingcard("[number] of [suit]", "[colour]col") + + for(var/i = 0, i<2, i++) + cards += new /datum/playingcard("joker", "joker") + +/obj/item/deck/doublecards + name = "double deck of cards" + desc = "A simple deck of playing cards. Multiplied by two. Does not necessarily come with twice the fun." + icon_state = "doubledeck" + +/obj/item/deck/doublecards/New() + ..() + for(var/f = 0, f<2, f++) + for(var/suit in list("spades","clubs","diamonds","hearts")) + + var/colour + if(suit == "spades" || suit == "clubs") + colour = "black_" + else + colour = "red_" + for(var/number in list("ace","two","three","four","five","six","seven","eight","nine","ten",)) + cards += new /datum/playingcard("[number] of [suit]", "[colour]num") + for(var/number in list("jack","queen","king")) + cards += new /datum/playingcard("[number] of [suit]", "[colour]col") + + for(var/i = 0, i<2, i++) + cards += new /datum/playingcard("joker", "joker") diff --git a/code/modules/games/cards.dm b/code/modules/games/cards.dm new file mode 100644 index 00000000000..e14aaa09ba7 --- /dev/null +++ b/code/modules/games/cards.dm @@ -0,0 +1,481 @@ +/datum/playingcard + var/name = "playing card" + var/card_icon = "card_back" + var/back_icon = "card_back" + +/datum/playingcard/New(newname, newcard_icon, newback_icon) + ..() + if(newname) + name = newname + if(newcard_icon) + card_icon = newcard_icon + if(newback_icon) + back_icon = newback_icon + +/obj/item/deck + w_class = WEIGHT_CLASS_SMALL + icon = 'icons/obj/playing_cards.dmi' + actions_types = list(/datum/action/item_action/draw_card, /datum/action/item_action/deal_card, /datum/action/item_action/deal_card_multi, /datum/action/item_action/shuffle) + var/list/cards = list() + var/cooldown = 0 // to prevent spam shuffle + throw_speed = 3 + throw_range = 10 + throwforce = 0 + force = 0 + +/obj/item/deck/attackby(obj/O as obj, mob/user as mob) + if(istype(O,/obj/item/cardhand)) + var/obj/item/cardhand/H = O + if(H.parentdeck == src) + for(var/datum/playingcard/P in H.cards) + cards += P + qdel(H) + to_chat(user,"You place your cards on the bottom of [src].") + return + else + to_chat(user,"You can't mix cards from different decks!") + return + + ..() + +/obj/item/deck/examine(mob/user) + ..() + to_chat(user,"It contains [cards.len ? cards.len : "no"] cards") + +/obj/item/deck/attack_hand(mob/user as mob) + draw_card(user) + +// Datum actions +/datum/action/item_action/draw_card + name = "Draw - Draw one card" + button_icon_state = "draw" + use_itemicon = FALSE + +/datum/action/item_action/draw_card/Trigger() + if(istype(target, /obj/item/deck)) + var/obj/item/deck/D = target + return D.draw_card(owner) + return ..() + +/datum/action/item_action/deal_card + name = "Deal - deal one card to a person next to you" + button_icon_state = "deal_card" + use_itemicon = FALSE + +/datum/action/item_action/deal_card/Trigger() + if(istype(target, /obj/item/deck)) + var/obj/item/deck/D = target + return D.deal_card() + return ..() + +/datum/action/item_action/deal_card_multi + name = "Deal multiple card - Deal multiple card to a person next to you" + button_icon_state = "deal_card_multi" + use_itemicon = FALSE + +/datum/action/item_action/deal_card_multi/Trigger() + if(istype(target, /obj/item/deck)) + var/obj/item/deck/D = target + return D.deal_card_multi() + return ..() + +/datum/action/item_action/shuffle + name = "Shuffle - shuffle the deck" + button_icon_state = "shuffle" + use_itemicon = FALSE + +/datum/action/item_action/shuffle/Trigger() + if(istype(target, /obj/item/deck)) + var/obj/item/deck/D = target + return D.deckshuffle() + return ..() + +// Datum actions + +/obj/item/deck/verb/draw_card(mob/user) + + set category = "Object" + set name = "Draw" + set desc = "Draw a card from a deck." + set src in view(1) + + var/mob/living/carbon/human/M = user + + if(user.incapacitated() || !Adjacent(user)) + return + + if(!cards.len) + to_chat(user,"There are no cards in the deck.") + return + + var/obj/item/cardhand/H = M.is_in_hands(/obj/item/cardhand) + if(H && !(H.parentdeck == src)) + to_chat(user,"You can't mix cards from different decks!") + return + if(H && ((1 + H.cards.len) > H.maxcardlen)) + to_chat(user,"You can't hold that many cards in one hand!") + return + + if(!H) + H = new(get_turf(src)) + user.put_in_hands(H) + + var/datum/playingcard/P = cards[1] + H.cards += P + cards -= P + H.parentdeck = src + H.update_icon() + user.visible_message("[user] draws a card.","You draws a card.") + to_chat(user,"It's [P].") + +/obj/item/deck/verb/deal_card() + + set category = "Object" + set name = "Deal" + set desc = "Deal a card from a deck." + set src in view(1) + + if(usr.incapacitated() || !Adjacent(usr)) + return + + if(!cards.len) + to_chat(usr,"There are no cards in the deck.") + return + + var/list/players = list() + for(var/mob/living/player in viewers(3)) + if(!player.incapacitated()) + players += player + //players -= usr + + var/mob/living/M = input("Who do you wish to deal a card to?") as null|anything in players + if(!usr || !src || !M) return + + deal_at(usr, M, 1) + +/obj/item/deck/verb/deal_card_multi() + + set category = "Object" + set name = "Deal Multiple Cards" + set desc = "Deal multiple cards from a deck." + set src in view(1) + + if(usr.incapacitated() || !Adjacent(usr)) + return + + if(!cards.len) + to_chat(usr,"There are no cards in the deck.") + return + + var/list/players = list() + for(var/mob/living/player in viewers(3)) + if(!player.incapacitated()) + players += player + var/maxcards = max(min(cards.len,10),1) + var/dcard = input("How many card(s) do you wish to deal? You may deal up to [maxcards] cards.") as num + if(dcard > maxcards) + return + var/mob/living/M = input("Who do you wish to deal [dcard] card(s)?") as null|anything in players + if(!usr || !src || !M || !Adjacent(usr)) + return + + deal_at(usr, M, dcard) + +/obj/item/deck/proc/deal_at(mob/user, mob/target, dcard) // Take in the no. of card to be dealt + var/obj/item/cardhand/H = new(get_step(user, user.dir)) + for(var/i in 1 to dcard) + H.cards += cards[1] + cards -= cards[1] + H.parentdeck = src + H.concealed = TRUE + H.update_icon() + if(user == target) + user.visible_message("[user] deals [dcard] card(s) to \himself.") + else + user.visible_message("[user] deals [dcard] card(s) to [target].") + H.throw_at(get_step(target,target.dir),3,1,H) + + +/obj/item/deck/attack_self() + deckshuffle() + +/obj/item/deck/verb/verb_shuffle() + set category = "Object" + set name = "Shuffle" + set desc = "Shuffle the cards in the deck." + set src in view(1) + deckshuffle() + +/obj/item/deck/proc/deckshuffle() + var/mob/living/user = usr + if(cooldown < world.time - 5 SECONDS) + cards = shuffle(cards) + user.visible_message("[user] shuffles [src].") + playsound(user, 'sound/items/cardshuffle.ogg', 50, 1) + cooldown = world.time + + +/obj/item/deck/MouseDrop(atom/over_object) // Code from Paper bin, so you can still pick up the deck + var/mob/M = usr + if(M.incapacitated() || !Adjacent(M)) + return + + if(over_object == M) + M.put_in_hands(src) + + else if(istype(over_object, /obj/screen)) + switch(over_object.name) + if("r_hand") + if(!remove_item_from_storage(M)) + M.unEquip(src) + M.put_in_r_hand(src) + if("l_hand") + if(!remove_item_from_storage(M)) + M.unEquip(src) + M.put_in_l_hand(src) + + add_fingerprint(M) + usr.visible_message("[usr] picks up the deck.") + +/obj/item/pack + name = "Card Pack" + desc = "For those with disposable income." + + icon_state = "card_pack" + icon = 'icons/obj/playing_cards.dmi' + w_class = WEIGHT_CLASS_TINY + var/list/cards = list() + var/parentdeck = null // For future card pack that need to be compatible with eachother i.e. cardemon + + +/obj/item/pack/attack_self(mob/user as mob) + user.visible_message("[name] rips open the [src]!", "You rips open the [src]!") + var/obj/item/cardhand/H = new() + + H.cards += cards + cards.Cut() + user.unEquip(src, force = 1) + qdel(src) + + H.update_icon() + user.put_in_active_hand(H) + +/obj/item/cardhand + name = "hand of cards" + desc = "Some playing cards." + icon = 'icons/obj/playing_cards.dmi' + icon_state = "empty" + w_class = WEIGHT_CLASS_TINY + throw_speed = 4 + throw_range = 20 + throwforce = 0 + force = 0 + var/maxcardlen = 20 + actions_types = list(/datum/action/item_action/remove_card, /datum/action/item_action/discard) + + var/concealed = FALSE + var/list/cards = list() + var/parentdeck = null + +/obj/item/cardhand/attackby(obj/O as obj, mob/user as mob) + if(cards.len == 1 && istype(O, /obj/item/pen)) + var/datum/playingcard/P = cards[1] + if(P.name != "Blank Card") + to_chat(user,"You cannot write on that card.") + return + var/cardtext = sanitize(input(user, "What do you wish to write on the card?", "Card Editing") as text|null, MAX_PAPER_MESSAGE_LEN) + if(!cardtext) + return + P.name = cardtext + // SNOWFLAKE FOR CAG, REMOVE IF OTHER CARDS ARE ADDED THAT USE THIS. + P.card_icon = "cag_white_card" + update_icon() + else if(istype(O,/obj/item/cardhand)) + var/obj/item/cardhand/H = O + if((H.cards.len + cards.len) > maxcardlen) + to_chat(user,"You can't hold that many cards in one hand!") + return + if(H.parentdeck == src.parentdeck) + for(var/datum/playingcard/P in cards) + H.cards += P + H.concealed = src.concealed + qdel(src) + H.update_icon() + return + else + to_chat(user,"You cannot mix cards from other deck!") + return + ..() + +/obj/item/cardhand/attack_self(var/mob/user as mob) + concealed = !concealed + update_icon() + user.visible_message("[user] [concealed ? "conceals" : "reveals"] their hand.") + +/obj/item/cardhand/examine(mob/user) + ..(user) + if((!concealed) && cards.len) + to_chat(user,"It contains:") + for(var/datum/playingcard/P in cards) + to_chat(user,"the [P.name].") + +// Datum action here + +/datum/action/item_action/remove_card + name = "Remove a card - Remove a single card from the hand." + button_icon_state = "remove_card" + use_itemicon = FALSE + +/datum/action/item_action/remove_card/Trigger() + if(istype(target, /obj/item/cardhand)) + var/obj/item/cardhand/C = target + return C.Removecard() + return ..() + +/datum/action/item_action/discard + name = "Discard - Place (a) card(s) from your hand in front of you." + button_icon_state = "discard" + use_itemicon = FALSE + +/datum/action/item_action/discard/Trigger() + if(istype(target, /obj/item/cardhand)) + var/obj/item/cardhand/C = target + return C.discard() + return ..() + +// No more datum action here + +/obj/item/cardhand/verb/Removecard() + + set category = "Object" + set name = "Remove card" + set desc = "Remove a card from the hand." + set src in view(1) + + var/mob/living/carbon/user = usr + + if(user.incapacitated() || !Adjacent(user)) + return + + var/pickablecards = list() + for(var/datum/playingcard/P in cards) + pickablecards[P.name] = P + var/pickedcard = input("Which card do you want to remove from the hand?") as null|anything in pickablecards + + if(QDELETED(src)) + return + + var/datum/playingcard/card = pickablecards[pickedcard] + + var/obj/item/cardhand/H = new(get_turf(src)) + user.put_in_active_hand(H) + H.cards += card + cards -= card + H.parentdeck = parentdeck + H.concealed = concealed + H.update_icon() + update_icon() + + if(!cards.len) + qdel(src) + +/obj/item/cardhand/verb/discard(var/mob/user as mob) + + set category = "Object" + set name = "Discard" + set desc = "Place (a) card(s) from your hand in front of you." + + var/maxcards = min(cards.len,5) + var/discards = input("How many cards do you want to discard? You may discard up to [maxcards] card(s)") as num + if(discards > maxcards) + return + for(var/i in 1 to discards) + var/list/to_discard = list() + for(var/datum/playingcard/P in cards) + to_discard[P.name] = P + var/discarding = input("Which card do you wish to put down?") as null|anything in to_discard + + if(QDELETED(src)) + return + + var/datum/playingcard/card = to_discard[discarding] + to_discard.Cut() + + var/obj/item/cardhand/H = new(get_turf(src)) + H.cards += card + cards -= card + H.concealed = FALSE + H.parentdeck = parentdeck + H.update_icon() + if(cards.len) + update_icon() + if(H.cards.len) + usr.visible_message("The [user] plays the [discarding].", "You play the [discarding].") + H.loc = get_step(usr,usr.dir) + + if(!cards.len) + qdel(src) + +/obj/item/cardhand/update_icon(var/direction = 0) + + if(!cards.len) + qdel(src) + return + else if(cards.len > 1) + name = "hand of cards" + desc = "Some playing cards." + else + name = "a playing card" + desc = "A playing card." + + overlays.Cut() + + if(cards.len == 1) + var/datum/playingcard/P = cards[1] + var/image/I = new(icon, (concealed ? "[P.back_icon]" : "[P.card_icon]") ) + I.pixel_x += (-5+rand(10)) + I.pixel_y += (-5+rand(10)) + overlays += I + return + + var/offset = Floor(20/cards.len + 1) + + var/matrix/M = matrix() + if(direction) + switch(direction) + if(NORTH) + M.Translate( 0, 0) + if(SOUTH) + M.Translate( 0, 4) + if(WEST) + M.Turn(90) + M.Translate( 3, 0) + if(EAST) + M.Turn(90) + M.Translate(-2, 0) + var/i = 0 + for(var/datum/playingcard/P in cards) + var/image/I = new(icon, (concealed ? "[P.back_icon]" : "[P.card_icon]") ) + //I.pixel_x = origin+(offset*i) + switch(direction) + if(SOUTH) + I.pixel_x = 8-(offset*i) + if(WEST) + I.pixel_y = -6+(offset*i) + if(EAST) + I.pixel_y = 8-(offset*i) + else + I.pixel_x = -7+(offset*i) + I.transform = M + overlays += I + i++ + +/obj/item/cardhand/dropped(mob/user as mob) + ..() + if(locate(/obj/structure/table, loc)) + update_icon(user.dir) + else + update_icon() + +/obj/item/cardhand/pickup(mob/user as mob) + update_icon() diff --git a/code/modules/games/tarot.dm b/code/modules/games/tarot.dm new file mode 100644 index 00000000000..bd26c34d437 --- /dev/null +++ b/code/modules/games/tarot.dm @@ -0,0 +1,32 @@ +/* this is a playing card deck based off of the Rider-Waite Tarot Deck. +*/ + +/obj/item/deck/tarot + name = "deck of tarot cards" + desc = "For all your occult needs!" + icon_state = "deck_tarot" + +/obj/item/deck/tarot/New() + ..() + + for(var/tarotname in list("Fool","Magician","High Priestess","Empress","Emperor","Hierophant","Lovers","Chariot","Strength","Hermit","Wheel of Fortune","Justice","Hanged Man","Death","Temperance","Devil","Tower","Star","Moon","Sun","Judgement","World")) + cards += new /datum/playingcard("[tarotname]", "tarot_major", "card_back_tarot") + for(var/suit in list("wands","pentacles","cups","swords")) + for(var/number in list("ace","two","three","four","five","six","seven","eight","nine","ten","page","knight","queen","king")) + cards += new /datum/playingcard("[number] of [suit]", "tarot_[suit]", "card_back_tarot") + +/obj/item/deck/tarot/deckshuffle() + var/mob/living/user = usr + if(cooldown < world.time - 5 SECONDS) + var/list/newcards = list() + while(cards.len) + var/datum/playingcard/P = pick(cards) + P.name = replacetext(P.name," reversed","") + if(prob(50)) + P.name += " reversed" + newcards += P + cards -= P + cards = newcards + playsound(user, 'sound/items/cardshuffle.ogg', 50, 1) + user.visible_message("[user] shuffles [src].", "You shuffle the [src].") + cooldown = world.time diff --git a/icons/mob/actions.dmi b/icons/mob/actions.dmi index 3acfa100085..5dd923111e6 100644 Binary files a/icons/mob/actions.dmi and b/icons/mob/actions.dmi differ diff --git a/icons/obj/playing_cards.dmi b/icons/obj/playing_cards.dmi new file mode 100644 index 00000000000..315e4335979 Binary files /dev/null and b/icons/obj/playing_cards.dmi differ diff --git a/paradise.dme b/paradise.dme index 4415ebe5f48..09d89d97bcd 100644 --- a/paradise.dme +++ b/paradise.dme @@ -1416,6 +1416,9 @@ #include "code\modules\food_and_drinks\recipes\recipes_microwave.dm" #include "code\modules\food_and_drinks\recipes\recipes_oven.dm" #include "code\modules\food_and_drinks\recipes\tablecraft\recipes_table.dm" +#include "code\modules\games\52card.dm" +#include "code\modules\games\cards.dm" +#include "code\modules\games\tarot.dm" #include "code\modules\holiday\christmas.dm" #include "code\modules\holiday\holiday.dm" #include "code\modules\hydroponics\biogenerator.dm"