diff --git a/project.clj b/project.clj index 4998826bb2..14ddb3f2f0 100644 --- a/project.clj +++ b/project.clj @@ -63,7 +63,7 @@ [org.clj-commons/claypoole "1.2.2"] [org.slf4j/slf4j-nop "1.7.32"] [integrant "0.8.0"] - [cljc.java-time "0.1.18"] + [com.widdindustries/cljc.java-time "0.1.21"] [time-literals "0.1.5"] [metosin/reitit "0.5.18"]] diff --git a/src/clj/game/cards/agendas.clj b/src/clj/game/cards/agendas.clj index f05cb02025..9c59aeb7c2 100644 --- a/src/clj/game/cards/agendas.clj +++ b/src/clj/game/cards/agendas.clj @@ -34,7 +34,7 @@ [game.core.ice :refer [add-extra-sub! remove-sub! update-all-ice update-all-icebreakers]] [game.core.initializing :refer [card-init]] [game.core.installing :refer [corp-install corp-install-msg]] - [game.core.moving :refer [forfeit mill move move-zone swap-cards swap-ice + [game.core.moving :refer [forfeit mill move move-zone swap-cards swap-cards-async swap-ice trash trash-cards]] [game.core.optional :refer [get-autoresolve set-autoresolve]] [game.core.payment :refer [can-pay? ->c]] @@ -1685,8 +1685,8 @@ (asset? %) (upgrade? %))))} :msg (msg "swap " (card-str state to-swap) " with a card from HQ") - :effect (effect (swap-cards to-swap target) - (continue-ability :runner (offer-jack-out) card nil)) + :effect (req (wait-for (swap-cards-async state side to-swap target) + (continue-ability state :runner (offer-jack-out) card nil))) :cancel-effect (effect (put-back-counter card) (effect-completed eid))}) (choose-card [run-server] diff --git a/src/clj/game/cards/events.clj b/src/clj/game/cards/events.clj index f186b41733..582baf4059 100644 --- a/src/clj/game/cards/events.clj +++ b/src/clj/game/cards/events.clj @@ -3201,7 +3201,7 @@ :choices {:card #(and (valid-target? %) (installed? %))} :effect (req (move state side target :hand) - (effect-completed state side (make-result eid (:cost target))))} + (complete-with-result state side eid (:cost target)))} put-down (fn [bonus] {:async true :prompt "Choose a program or piece of hardware to install" @@ -3216,6 +3216,7 @@ :display-origin true}}))})] {:on-play {:req (req (some valid-target? (all-installed state :runner))) + :async true :effect (req (wait-for (resolve-ability state side pick-up card nil) (continue-ability state side (put-down async-result) diff --git a/src/clj/game/cards/operations.clj b/src/clj/game/cards/operations.clj index bd826e65f0..c253eb8e34 100644 --- a/src/clj/game/cards/operations.clj +++ b/src/clj/game/cards/operations.clj @@ -1647,8 +1647,7 @@ (defcard "Media Blitz" {:on-play - {:async true - :prompt "Choose an agenda in the runner's score area" + {:prompt "Choose an agenda in the runner's score area" :choices {:req (req (and (agenda? target) (is-scored? state :runner target)))} :change-in-game-state (req (seq (:scored runner))) diff --git a/src/clj/game/cards/resources.clj b/src/clj/game/cards/resources.clj index 3de6a6197e..4991e3077f 100644 --- a/src/clj/game/cards/resources.clj +++ b/src/clj/game/cards/resources.clj @@ -1180,9 +1180,7 @@ c (make-card c) c (assoc c :host (dissoc card :hosted) - :zone [:onhost] - ;; semi hack to get deactivate to work - :installed true)] + :zone [:onhost])] ;; Manually host id on card (update! state side (assoc card :hosted [c])) (card-init state :runner c) diff --git a/src/clj/game/cards/upgrades.clj b/src/clj/game/cards/upgrades.clj index ea8d881f24..e98d85aa1c 100644 --- a/src/clj/game/cards/upgrades.clj +++ b/src/clj/game/cards/upgrades.clj @@ -33,7 +33,7 @@ unbroken-subroutines-choice update-all-ice update-all-icebreakers]] [game.core.installing :refer [corp-install]] [game.core.moving :refer [mill move remove-from-currently-drawing - swap-cards swap-ice trash trash-cards]] + swap-cards swap-cards-async swap-ice trash trash-cards]] [game.core.optional :refer [get-autoresolve set-autoresolve]] [game.core.payment :refer [can-pay? cost-value ->c]] [game.core.play-instants :refer [play-instant]] @@ -371,7 +371,8 @@ :effect (effect (play-instant eid (-> target (assoc :rfg-instead-of-trashing true) (assoc-in [:special :rfg-when-trashed] true)) - {:ignore-cost true}))}]}) + {:no-additional-cost true + :ignore-cost true}))}]}) (defcard "Calibration Testing" {:install-req (req (remove #{"HQ" "R&D" "Archives"} targets)) @@ -570,7 +571,8 @@ :cost [(->c :trash-can)] :msg (msg "swap " (card-str state to-swap) " with " (card-str state target)) - :effect (effect (swap-cards to-swap target))}) + :async true + :effect (effect (swap-cards-async eid to-swap target))}) ability {:optional {:waiting-prompt true @@ -1183,8 +1185,10 @@ (in-hand? %))} :msg (msg "swap " (card-str state current-ice) " with a piece of ice from HQ") - :effect (req (swap-cards state :corp current-ice target) - (continue-ability state :runner (offer-jack-out) card nil))}}}]}) + :effect (req (wait-for (swap-cards-async state :corp current-ice target) + (continue-ability state :runner + (offer-jack-out) + card nil)))}}}]}) (defcard "Midway Station Grid" {:static-abilities [{:type :break-sub-additional-cost diff --git a/src/clj/game/core/effects.clj b/src/clj/game/core/effects.clj index 074d261093..2152e564c8 100644 --- a/src/clj/game/core/effects.clj +++ b/src/clj/game/core/effects.clj @@ -1,6 +1,6 @@ (ns game.core.effects (:require [clj-uuid :as uuid] - [game.core.card :refer [get-card]] + [game.core.card :refer [facedown? get-card runner?]] [game.core.card-defs :refer [card-def]] [game.core.eid :refer [make-eid]] [game.core.board :refer [get-all-cards]] @@ -93,7 +93,9 @@ "Gets all cards currently disabled" [state] (let [all-cards (get-all-cards state) - disabled-cards (filter #(is-disabled? state nil %) all-cards)] + disabled-cards (filter #(or (is-disabled? state nil %) + (and (runner? %) (facedown? %))) + all-cards)] (into {} (map (juxt :cid identity)) disabled-cards))) (defn update-disabled-cards [state] diff --git a/src/cljc/i18n/en.cljc b/src/cljc/i18n/en.cljc index 980786b582..4a4be5d709 100644 --- a/src/cljc/i18n/en.cljc +++ b/src/cljc/i18n/en.cljc @@ -748,8 +748,8 @@ :bad-pub-count (fn [[base additional]] (str base (when (pos? additional) (str " + " additional)) " Bad Publicity")) :mu-count (fn [[unused available]] (str unused " of " available " MU unused")) :special-mu-count (fn [[unused available mu-type]] (str unused " of " available " " mu-type " MU unused")) - :indicate-action "Indicate action" :show-decklists "Show/Hide decklists" + :indicate-action "Indicate paid ability" :spec-count (fn [[c]] (str c " Spectator" (when (> c 1) "s"))) :spec-view "Spectator View" :runner-view "Runner View" diff --git a/src/cljs/nr/gameboard/log.cljs b/src/cljs/nr/gameboard/log.cljs index 1927d902a6..e6a1ce4691 100644 --- a/src/cljs/nr/gameboard/log.cljs +++ b/src/cljs/nr/gameboard/log.cljs @@ -61,7 +61,7 @@ [:button.indicate-action {:on-click #(do (.preventDefault %) (send-command "indicate-action")) :key "Indicate action"} - (tr [:game.indicate-action "Indicate action"])])) + (tr [:game.indicate-action "Indicate paid ability"])])) (defn show-decklists [] (when (get-in @app-state [:current-game :open-decklists]) diff --git a/test/clj/game/cards/agendas_test.clj b/test/clj/game/cards/agendas_test.clj index 1ac3e42885..e3ed058d75 100644 --- a/test/clj/game/cards/agendas_test.clj +++ b/test/clj/game/cards/agendas_test.clj @@ -3396,6 +3396,26 @@ (is (= (:title (get-ice state :hq 0)) "Eli 1.0") "Swapped Ice Wall with Eli 1.0") (click-prompt state :runner "No")))) +(deftest yagi-uda-swap-triggers-install + (do-game + (new-game {:corp {:deck [(qty "Hedge Fund" 5)] + :credits 20 + :hand ["Tranquility Home Grid" "Project Yagi-Uda" "Project Atlas" "Project Beale"]}}) + (core/gain state :corp :click 10) + (play-from-hand state :corp "Tranquility Home Grid" "New remote") + (play-from-hand state :corp "Project Beale" "Server 1") + (rez state :corp (get-content state :remote1 0)) + (play-from-hand state :corp "Project Yagi-Uda" "New remote") + (let [pyu (get-content state :remote2 0)] + (advance state pyu 4) + (score state :corp (refresh pyu))) + (take-credits state :corp) + (run-on state :remote1) + (card-ability state :corp (get-scored state :corp 0) 0) + (click-card state :corp "Project Beale") + (click-card state :corp "Project Atlas") + (click-prompt state :corp "Gain 2 [Credits]"))) + (deftest puppet-master ;; Puppet Master - game progresses if no valid targets. Issue #1661. (do-game diff --git a/test/clj/game/cards/events_test.clj b/test/clj/game/cards/events_test.clj index f4faad39c5..767bbfc658 100644 --- a/test/clj/game/cards/events_test.clj +++ b/test/clj/game/cards/events_test.clj @@ -6002,6 +6002,19 @@ (run-continue state) (click-prompt state :runner "Hedge Fund")))) +(deftest rejig-vs-aniccam + ;; Rejig + (do-game + (new-game {:options {:start-as :runner} + :runner {:hand ["Rejig" "Aniccam"] + :deck ["Ika"]}}) + (play-from-hand state :runner "Aniccam") + (play-from-hand state :runner "Rejig") + (click-card state :runner "Aniccam") + (is-hand? state :runner ["Aniccam"]) + (click-card state :runner "Aniccam") + (is-hand? state :runner ["Ika"]))) + (deftest reprise ;; Reprise (do-game diff --git a/test/clj/game/cards/operations_test.clj b/test/clj/game/cards/operations_test.clj index c2ba01f3cb..b230a37f60 100644 --- a/test/clj/game/cards/operations_test.clj +++ b/test/clj/game/cards/operations_test.clj @@ -2804,6 +2804,24 @@ (is (not (has-subtype? (refresh bla) "Advertisement")) "Not an ad") (is (= "Media Blitz" (:title (second (:discard (get-corp))))))))) +(deftest media-blitz-na-sol-async-issues + (do-game + (new-game {:corp {:hand ["Media Blitz"] + :discard ["Better Citizen Program"] + :id "New Angeles Sol: Your News"} + :runner {:hand ["Ika"]}}) + (take-credits state :corp) + (run-empty-server state :archives) + (click-prompt state :runner "Steal") + (click-prompt state :corp "Yes") + (click-card state :corp "Media Blitz") + (click-card state :corp "Better Citizen Program") + (is (not (waiting? state :runner)) "Prompt closed") + (is (not (waiting? state :corp)) "Prompt closed") + (play-from-hand state :runner "Ika") + (click-prompt state :corp "Yes") + (is (is-tagged? state) "BCP tag worked"))) + (deftest medical-research-fundraiser ;; Medical Research Fundraiser - runner gains 8creds, runner gains 3creds (do-game diff --git a/test/clj/game/cards/resources_test.clj b/test/clj/game/cards/resources_test.clj index c9b5748bd1..bf2760dce6 100644 --- a/test/clj/game/cards/resources_test.clj +++ b/test/clj/game/cards/resources_test.clj @@ -2011,6 +2011,25 @@ (click-prompt state :runner "Pay 3 [Credits] to trash")) "Loup triggered (see https://nullsignal.games/blog/card-text-updates-v1-2-released/ for ruling")))) +(deftest dj-vs-degree-mill + (do-game + (new-game {:runner {:hand ["DJ Fenris" "Ika"]} + :corp {:hand ["Degree Mill"]}}) + (take-credits state :corp) + (play-from-hand state :runner "DJ Fenris") + (click-prompt state :runner "René \"Loup\" Arcemont: Party Animal") + (run-empty-server state :hq) + (is (= '("No action") (prompt-buttons :runner)) "Cannot steal degree mill with just dj") + (click-prompt state :runner "No action") + (play-from-hand state :runner "Ika") + (run-empty-server state :hq) + (click-prompt state :runner "Pay to steal") + (click-card state :runner "DJ Fenris") + (click-card state :runner (first (:hosted (get-resource state 0)))) ;; not selected + (is (not (no-prompt? state :runner)) "did not confirm by picking fake id") + (click-card state :runner "Ika") + (is (no-prompt? state :runner) "Stole degree mill"))) + (deftest donut-taganes ;; Donut Taganes - add 1 to play cost of Operations & Events when this is in play (do-game diff --git a/test/clj/game/cards/upgrades_test.clj b/test/clj/game/cards/upgrades_test.clj index 06772810ba..a709ac56ae 100644 --- a/test/clj/game/cards/upgrades_test.clj +++ b/test/clj/game/cards/upgrades_test.clj @@ -702,6 +702,17 @@ (click-prompt state :corp (find-card "IPO" (:discard (get-corp)))) (is (find-card "IPO" (:rfg (get-corp))) "IPO is removed from game")))) +(deftest stinson-ignores-all-costs + (do-game + (new-game {:corp {:hand ["Bryan Stinson"] :discard ["Ultraviolet Clearance"]}}) + (play-from-hand state :corp "Bryan Stinson" "HQ") + (rez state :corp (get-content state :hq 0)) + (is (changed? [(:click (get-corp)) -1 + (:credit (get-corp)) 10] + (card-ability state :corp (get-content state :hq 0) 0) + (click-prompt state :corp "Ultraviolet Clearance")) + "Gained 10c in one click (ignoring the req and extra 2 clicks)"))) + (deftest calibration-testing ;; Calibration Testing - advanceable / non-advanceable (do-game diff --git a/test/clj/game/test_framework.clj b/test/clj/game/test_framework.clj index 1943f988d4..c3dab450e8 100644 --- a/test/clj/game/test_framework.clj +++ b/test/clj/game/test_framework.clj @@ -44,26 +44,27 @@ '[game.cards.upgrades]))) (load-all-cards) -(defn is-hand? +(defn is-zone-impl + "Is the hand exactly equal to a given set of cards?" + [state side zone expected] + (let [expected (seq (sort (flatten expected))) + contents (seq (sort (map :title (get-in @state [side zone]))))] + (is' (= expected contents) (str (name zone) " is not " expected)))) + +(defmacro is-hand? "Is the hand exactly equal to a given set of cards?" [state side expected-hand] - (let [expected-hand (seq (sort (flatten expected-hand))) - hand (seq (sort (map :title (get-in @state [side :hand]))))] - (is (= expected-hand hand) (str "hand is not " expected-hand)))) + `(error-wrapper (is-zone-impl ~state ~side :hand ~expected-hand))) -(defn is-deck? - "Is the discard exactly equal to a given set of cards?" +(defmacro is-deck? + "Is the hand exactly equal to a given set of cards?" [state side expected-deck] - (let [expected-deck (seq (sort (flatten expected-deck))) - deck (seq (sort (map :title (get-in @state [side :deck]))))] - (is (= expected-deck deck) (str "deck is not " expected-deck)))) + `(error-wrapper (is-zone-impl ~state ~side :deck ~expected-deck))) -(defn is-discard? - "Is the set of cards in the deck exactly equal to a given set of cards? (this is order agnostic)" +(defmacro is-discard? + "Is the hand exactly equal to a given set of cards?" [state side expected-discard] - (let [expected-discard (seq (sort (flatten expected-discard))) - discard (seq (sort (map :title (get-in @state [side :discard]))))] - (is (= expected-discard discard) (str "discard is not " expected-discard)))) + `(error-wrapper (is-zone-impl ~state ~side :discard ~expected-discard))) ;;; helper functions for prompt interaction (defn get-prompt