diff --git a/src/clj/game/cards/agendas.clj b/src/clj/game/cards/agendas.clj index 935a847fb6..86a524a562 100644 --- a/src/clj/game/cards/agendas.clj +++ b/src/clj/game/cards/agendas.clj @@ -155,14 +155,14 @@ {:implementation "Runner must trash cards manually when required" :effect (effect (add-counter card :agenda 1)) :silent (req true) - :abilities [{:counter-cost [:agenda 1] + :abilities [{:cost [:agenda 1] :req (req (:run @state)) :msg "make the Runner trash a card from their grip to jack out or break subroutines for the remainder of the run"}]} "AstroScript Pilot Program" {:effect (effect (add-counter card :agenda 1)) :silent (req true) - :abilities [{:counter-cost [:agenda 1] + :abilities [{:cost [:agenda 1] :msg (msg "place 1 advancement token on " (card-str state target)) :choices {:req can-be-advanced?} :effect (effect (add-prop target :advance-counter 1 {:placed true}))}]} @@ -339,8 +339,7 @@ (add-counters state side card eid)))} :no-ability {:effect (effect (add-counters card eid))}}} card nil)) - :abilities [{:cost [:click 1] - :counter-cost [:agenda 1] + :abilities [{:cost [:click 1 :agenda 1] :async true :label "Do 2 meat damage" :once :per-turn @@ -511,7 +510,7 @@ "Efficiency Committee" {:silent (req true) :effect (effect (add-counter card :agenda 3)) - :abilities [{:cost [:click 1] :counter-cost [:agenda 1] + :abilities [{:cost [:click 1 :agenda 1] :effect (effect (gain :click 2) (register-turn-flag! card :can-advance @@ -523,8 +522,7 @@ "Elective Upgrade" {:silent (req true) :effect (effect (add-counter card :agenda 2)) - :abilities [{:cost [:click 1] - :counter-cost [:agenda 1] + :abilities [{:cost [:click 1 :agenda 1] :once :per-turn :effect (effect (gain :click 2)) :msg "gain [Click][Click]"}]} @@ -543,8 +541,7 @@ {:effect (effect (add-counter card :agenda 1) (shuffle-into-deck :hand)) :interactive (req true) - :abilities [{:cost [:click 1] - :counter-cost [:agenda 1] + :abilities [{:cost [:click 1 :agenda 1] :msg "draw 5 cards" :effect (effect (draw 5))}]} @@ -561,22 +558,23 @@ card nil))}} "False Lead" - {:abilities [{:req (req (>= (:click runner) 2)) + {:abilities [{:req (req (<= 2 (:click runner))) :msg "force the Runner to lose [Click][Click]" - :effect (effect (forfeit card) - (lose :runner :click 2))}]} + :cost [:forfeit-self] + :effect (effect (lose :runner :click 2))}]} "Fetal AI" {:flags {:rd-reveal (req true)} :access {:async true - :req (req (not= (first (:zone card)) :discard)) :msg "do 2 net damage" + :req (req (not= (first (:zone card)) :discard)) + :msg "do 2 net damage" :effect (effect (damage eid :net 2 {:card card}))} :steal-cost-bonus (req [:credit 2])} "Firmware Updates" {:silent (req true) :effect (effect (add-counter card :agenda 3)) - :abilities [{:counter-cost [:agenda 1] + :abilities [{:cost [:agenda 1] :choices {:req #(and (ice? %) (can-be-advanced? %))} :req (req (pos? (get-counters card :agenda))) @@ -598,8 +596,7 @@ "Geothermal Fracking" {:effect (effect (add-counter card :agenda 2)) :silent (req true) - :abilities [{:cost [:click 1] - :counter-cost [:agenda 1] + :abilities [{:cost [:click 1 :agenda 1] :msg "gain 7 [Credits] and take 1 bad publicity" :effect (effect (gain-credits 7) (gain-bad-publicity :corp 1))}]} @@ -685,8 +682,7 @@ "High-Risk Investment" {:effect (effect (add-counter card :agenda 1)) :silent (req true) - :abilities [{:cost [:click 1] - :counter-cost [:agenda 1] + :abilities [{:cost [:click 1 :agenda 1] :msg (msg "gain " (:credit runner) " [Credits]") :effect (effect (gain-credits (:credit runner)))}]} @@ -715,7 +711,7 @@ "House of Knives" {:effect (effect (add-counter card :agenda 3)) :silent (req true) - :abilities [{:counter-cost [:agenda 1] + :abilities [{:cost [:agenda 1] :msg "do 1 net damage" :req (req (:run @state)) :once :per-run @@ -780,7 +776,7 @@ :silent (req true) :effect (effect (add-counter card :power 2)) :abilities [{:req (req (:run @state)) - :counter-cost [:power 1] + :cost [:power 1] :effect (req (let [ls (filter #(= "Labyrinthine Servers" (:title %)) (:scored corp))] (jack-out-prevent state side) (when (zero? (reduce + (for [c ls] (get-counters c :power)))) @@ -915,7 +911,7 @@ {:silent (req true) :effect (effect (add-counter card :agenda 1)) :abilities [{:req (req (:run @state)) - :counter-cost [:agenda 1] + :cost [:agenda 1] :msg "end the run" :async true :effect (effect (end-run eid card))}]} @@ -940,8 +936,12 @@ "Personality Profiles" (let [pp {:req (req (pos? (count (:hand runner)))) - :effect (effect (trash (first (shuffle (:hand runner))))) - :msg (msg "force the Runner to trash " (:title (last (:discard runner))) " from their Grip at random")}] + :effect (effect + (continue-ability + (let [c (first (shuffle (:hand runner)))] + {:msg (msg "force the Runner to trash " (:title c) " from their Grip at random") + :effect (effect (trash eid c nil))}) + card nil))}] {:events {:searched-stack pp :runner-install (assoc pp :req (req (and (some #{:discard} (:previous-zone target)) (pos? (count (:hand runner))))))}}) @@ -1008,7 +1008,7 @@ "Project Atlas" {:silent (req true) :effect (effect (add-counter card :agenda (max 0 (- (get-counters card :advancement) 3)))) - :abilities [{:counter-cost [:agenda 1] + :abilities [{:cost [:agenda 1] :prompt "Choose a card" :label "Search R&D and add 1 card to HQ" ;; we need the req or the prompt will still show @@ -1030,13 +1030,13 @@ "Project Kusanagi" {:silent (req true) :effect (effect (add-counter card :agenda (- (get-counters card :advancement) 2))) - :abilities [{:counter-cost [:agenda 1] + :abilities [{:cost [:agenda 1] :msg "make a piece of ICE gain \"[Subroutine] Do 1 net damage\" after all its other subroutines for the remainder of the run"}]} "Project Vitruvius" {:silent (req true) :effect (effect (add-counter card :agenda (- (get-counters card :advancement) 3))) - :abilities [{:counter-cost [:agenda 1] + :abilities [{:cost [:agenda 1] :prompt "Choose a card in Archives to add to HQ" :show-discard true :choices {:req #(and (in-discard? %) @@ -1054,7 +1054,7 @@ :abilities [{:req (req (and (ice? current-ice) (rezzed? current-ice) (has-subtype? current-ice "Bioroid"))) - :counter-cost [:agenda 1] + :cost [:agenda 1] :msg (str "make the approached piece of Bioroid ICE gain \"[Subroutine] End the run\"" "after all its other subroutines for the remainder of this run")}]} @@ -1088,7 +1088,7 @@ (clear-wait-prompt :runner))})] {:silent (req true) :effect (effect (add-counter card :agenda (- (get-counters card :advancement) 3))) - :abilities [{:counter-cost [:agenda 1] + :abilities [{:cost [:agenda 1] :req (req run) :effect (effect (show-wait-prompt :runner "Corp to use Project Yagi-Uda") (continue-ability (choose-card (:server run)) @@ -1186,7 +1186,7 @@ "Remastered Edition" {:effect (effect (add-counter card :agenda 1)) :silent (req true) - :abilities [{:counter-cost [:agenda 1] + :abilities [{:cost [:agenda 1] :msg (msg "place 1 advancement token on " (card-str state target)) :choices {:req installed?} :effect (effect (add-prop target :advance-counter 1 {:placed true}))}]} @@ -1273,7 +1273,7 @@ "Sensor Net Activation" {:effect (effect (add-counter card :agenda 1)) :silent (req true) - :abilities [{:counter-cost [:agenda 1] + :abilities [{:cost [:agenda 1] :req (req (some #(and (has-subtype? % "Bioroid") (not (rezzed? %))) (all-installed state :corp))) :prompt "Choose a bioroid to rez, ignoring all costs" :choices {:req #(and (has-subtype? % "Bioroid") (not (rezzed? %)))} @@ -1409,7 +1409,7 @@ "Timely Public Release" {:silent (req true) :effect (effect (add-counter card :agenda 1)) - :abilities [{:counter-cost [:agenda 1] + :abilities [{:cost [:agenda 1] :label "Install a piece of ice in any position, ignoring all costs" :prompt "Select a piece of ice to install" :show-discard true diff --git a/src/clj/game/cards/assets.clj b/src/clj/game/cards/assets.clj index e0571e4db8..6ef5c3d344 100644 --- a/src/clj/game/cards/assets.clj +++ b/src/clj/game/cards/assets.clj @@ -75,7 +75,7 @@ "Advanced Assembly Lines" {:effect (effect (gain-credits 3)) :msg (msg "gain 3 [Credits]") - :abilities [{:label "[Trash]: Install a non-agenda card from HQ" + :abilities [{:label "Install a non-agenda card from HQ" :async true :prompt "Select a non-agenda card to install from HQ" :req (req (not (:run @state))) @@ -84,8 +84,8 @@ (in-hand? %) (corp? %))} :msg (msg (corp-install-msg target)) - :effect (req (wait-for (trash state side card {:cause :ability-cost}) - (corp-install state side eid target nil nil)))}]} + :cost [:trash] + :effect (req (corp-install state side eid target nil nil))}]} "Aggressive Secretary" (advance-ambush 2 {:req (req (pos? (get-counters (get-card state card) :advancement))) @@ -106,9 +106,9 @@ "Pay 2 [Credits] to use Aggressive Secretary ability?") "Alexa Belsky" - {:abilities [{:label "[Trash]: Shuffle all cards in HQ into R&D" - :effect (effect (trash card {:cause :ability-cost}) - (show-wait-prompt :corp "Runner to decide whether or not to prevent Alexa Belsky") + {:abilities [{:label "Shuffle all cards in HQ into R&D" + :cost [:trash] + :effect (effect (show-wait-prompt :corp "Runner to decide whether or not to prevent Alexa Belsky") (resolve-ability {:prompt "Prevent Alexa Belsky from shuffling back in 1 card for every 2 [Credits] spent. How many credits?" :choices :credit @@ -135,16 +135,15 @@ "Alix T4LB07" {:events {:corp-install {:effect (effect (add-counter card :power 1))}} :abilities [{:label "Gain 2 [Credits] for each counter on Alix T4LB07" - :cost [:click 1] + :cost [:click 1 :trash] :msg (msg "gain " (* 2 (get-counters card :power)) " [Credits]") - :effect (effect (trash card {:cause :ability-cost}) - (gain-credits (* 2 (get-counters card :power))))}]} + :effect (effect (gain-credits (* 2 (get-counters card :power))))}]} "Allele Repression" {:implementation "Card swapping is manual" :advanceable :always :abilities [{:label "Swap 1 card in HQ and Archives for each advancement token" - :effect (effect (trash card {:cause :ability-cost})) + :cost [:trash] :msg (msg "swap " (get-counters card :advancement) " cards in HQ and Archives")}]} "Amani Senai" @@ -229,14 +228,14 @@ "Bioroid Work Crew" {:implementation "Timing restriction of ability use not enforced" - :abilities [{:label "[Trash]: Install 1 card, paying all costs" + :abilities [{:label "Install 1 card, paying all costs" :req (req (= (:active-player @state) :corp)) :prompt "Select a card in HQ to install" :choices {:req #(and (not (operation? %)) (in-hand? %) (corp? %))} - :effect (effect (trash card {:cause :ability-cost}) - (corp-install target nil)) + :cost [:trash] + :effect (effect (corp-install target nil)) :msg (msg (corp-install-msg target))}]} "Blacklist" @@ -293,10 +292,9 @@ (add-counter card :credit target)) :msg (msg "move " target " [Credit] to C.I. Fund")} {:label "Take all credits from C.I. Fund" - :cost [:credit 2] + :cost [:credit 2 :trash] :msg (msg "trash it and gain " (get-counters card :credit) " [Credits]") - :effect (effect (trash card {:cause :ability-cost}) - (gain-credits (get-counters card :credit)))}] + :effect (effect (gain-credits (get-counters card :credit)))}] :events {:corp-turn-begins {:req (req (>= (get-counters card :credit) 6)) :effect (effect (add-counter card :credit 2) (system-msg (str "adds 2 [Credits] to C.I. Fund")))}}} @@ -325,8 +323,7 @@ {:abilities [{:label "Add 1 power counter" :effect (effect (add-counter card :power 1) (system-msg (str "adds 1 power counter to Chief Slee")))} - {:counter-cost [:power 5] - :cost [:click 1] + {:cost [:click 1 :power 5] :async true :msg "do 5 meat damage" :effect (effect (damage eid :meat 5 {:card card}))}]} @@ -451,19 +448,17 @@ {:advanceable :always :abilities [{:label "Trash a connection" :async true - :cost [:click 1] + :cost [:click 1 :trash] :req (req (>= (get-counters card :advancement) 2)) :choices {:req #(has-subtype? % "Connection")} :msg (msg "trash " (:title target)) - :effect (effect (trash card {:cause :ability-cost}) - (trash eid target nil))} + :effect (effect (trash eid target nil))} {:label "Do 2 meat damage" :async true - :cost [:click 1] + :cost [:click 1 :trash] :req (req (>= (get-counters card :advancement) 2)) :msg "do 2 meat damage" - :effect (effect (trash card {:cause :ability-cost}) - (damage eid :meat 2 {:card card}))}]} + :effect (effect (damage eid :meat 2 {:card card}))}]} "Corporate Town" {:derezzed-events {:runner-turn-ends corp-rez-toast} @@ -588,8 +583,7 @@ "Drudge Work" {:effect (effect (add-counter card :power 3)) - :abilities [{:cost [:click 1] - :counter-cost [:power 1] + :abilities [{:cost [:click 1 :power 1] :async true :choices {:req #(and (agenda? %) (or (in-hand? %) @@ -604,7 +598,7 @@ (gain-credits state :corp (get-agenda-points state :corp target)) (move state :corp target :deck) (shuffle! state :corp :deck) - (if (zero? (get-counters card :power)) + (if (not (pos? (get-counters (get-card state card) :power))) (trash state side eid card nil) (effect-completed state side eid)))}]} @@ -645,11 +639,11 @@ "Elizabeth Mills" {:effect (effect (lose-bad-publicity 1)) :msg "remove 1 bad publicity" - :abilities [{:cost [:click 1] :label "Trash a location" + :abilities [{:cost [:click 1 :trash] + :label "Trash a location" :msg (msg "trash " (:title target) " and take 1 bad publicity") :choices {:req #(has-subtype? % "Location")} - :effect (effect (trash card {:cause :ability-cost}) - (trash target) + :effect (effect (trash target) (gain-bad-publicity :corp 1))}]} "Encryption Protocol" @@ -662,9 +656,9 @@ :effect (effect (add-counter card :power 1) (system-msg (str "places 1 power counter on Estelle Moon")))}} :abilities [{:label "Draw 1 card and gain 2 [Credits] for each power counter" + :cost [:trash] :effect (req (let [counters (get-counters card :power) credits (* 2 counters)] - (trash state side card {:cause :ability-cost}) (draw state side counters) (gain-credits state side credits) (system-msg state side (str "uses Estelle Moon to draw " counters @@ -694,10 +688,9 @@ :choices (req (cancellable (filter asset? (:deck corp)) :sorted)) - :cost [:credit 1] + :cost [:credit 1 :trash] :label "Search R&D for an asset" - :effect (effect (trash card {:cause :ability-cost}) - (reveal target) + :effect (effect (reveal target) (shuffle! :deck) (move target :hand))}]} @@ -719,8 +712,8 @@ {:advanceable :always :abilities [{:label "Remove 1 bad publicity for each advancement token on Exposé" :msg (msg "remove " (get-counters card :advancement) " bad publicity") - :effect (effect (trash card {:cause :ability-cost}) - (lose-bad-publicity (get-counters card :advancement)))}]} + :cost [:trash] + :effect (effect (lose-bad-publicity (get-counters card :advancement)))}]} "False Flag" (letfn [(tag-count [false-flag] @@ -730,8 +723,7 @@ :msg (msg "give the runner " (quantify (tag-count (get-card state card)) "tag")) :async true :effect (effect (gain-tags :corp eid (tag-count (get-card state card))))} - :abilities [{:cost [:click 1] - :advance-counter-cost 7 + :abilities [{:cost [:click 1 :advancement 7] :label "Add False Flag to your score area as an agenda worth 3 agenda points" :msg "add it to their score area as an agenda worth 3 agenda points" :async true @@ -781,8 +773,7 @@ :async true :effect (effect (damage eid :net (get-counters (get-card state card) :advancement) {:card card}))} - :abilities [{:cost [:click 1] - :advance-counter-cost 3 + :abilities [{:cost [:click 1 :advancement 2] :label "Add Gene Splicing to your score area as an agenda worth 1 agenda point" :msg "add it to their score area as an agenda worth 1 agenda point" :async true @@ -805,18 +796,16 @@ "GRNDL Refinery" {:advanceable :always :abilities [{:label "Gain 4 [Credits] for each advancement token on GRNDL Refinery" - :cost [:click 1] + :cost [:click 1 :trash] :msg (msg "gain " (* 4 (get-counters card :advancement)) " [Credits]") - :effect (effect (trash card {:cause :ability-cost}) - (gain-credits (* 4 (get-counters card :advancement))))}]} + :effect (effect (gain-credits (* 4 (get-counters card :advancement))))}]} "Haas Arcology AI" {:advanceable :while-unrezzed :abilities [{:label "Gain [Click][Click]" :once :per-turn :msg "gain [Click][Click]" - :cost [:click 1] - :advance-counter-cost 1 + :cost [:click 1 :advancement 1] :effect (effect (gain :click 2))}]} "Honeyfarm" @@ -896,7 +885,7 @@ :effect (effect (move target :hand))}]} "IT Department" - {:abilities [{:counter-cost [:power 1] + {:abilities [{:cost [:power 1] :label "Add strength to a rezzed ICE" :choices {:req #(and (ice? %) (:rezzed %))} :req (req (pos? (get-counters card :power))) @@ -922,8 +911,8 @@ :msg "draw 2 cards" :effect (effect (draw 2))} {:label "Shuffle up to 3 cards from Archives into R&D" - :activatemsg "removes Jackson Howard from the game" - :effect (effect (rfg-and-shuffle-rd-effect card 3))}]} + :cost [:remove-from-game] + :effect (effect (shuffle-into-rd-effect card 3))}]} "Jeeves Model Bioroids" (let [jeeves (effect (gain :click 1)) @@ -950,10 +939,10 @@ :abilities [{:msg "look at the top card of the Runner's Stack" :effect (effect (prompt! card (str "The top card of the Runner's Stack is " (:title (first (:deck runner)))) ["OK"] {}))} - {:label "[Trash]: Trash the top card of the Runner's Stack" + {:label "Trash the top card of the Runner's Stack" :msg (msg "trash " (:title (first (:deck runner))) " from the Runner's Stack") - :effect (effect (trash card {:cause :ability-cost}) - (mill :runner))}]} + :cost [:trash] + :effect (effect (mill :runner))}]} "Kuwinda K4H1U3" {:derezzed-events {:runner-turn-ends corp-rez-toast} @@ -1035,19 +1024,19 @@ {:async true :effect (effect (draw eid 3 nil)) :msg (msg "draw 3 cards") - :abilities [{:label "Remove a tag to search R&D for an operation" + :abilities [{:label "Search R&D for an operation" :prompt "Choose an operation to put on top of R&D" - :cost [:click 1] - :choices (req (cancellable (filter operation? (:deck corp)) :sorted)) - :req (req (pos? (get-in @state [:runner :tag :base]))) - :effect (req (lose-tags state :corp 1) - (let [c (move state :corp target :play-area)] + :cost [:click 1 :tag 1] + :msg (msg (if (= target "No action") + "search R&D, but does not find an operation" + (str "put " (:title target) " on top of R&D"))) + :choices (req (conj (vec (sort-by :title (filter operation? (:deck corp)))) "No action")) + :effect (req (if (= target "No action") (shuffle! state :corp :deck) - (move state :corp c :deck {:front true}) - (system-msg state side (str "uses Lily Lockwell to put " (:title c) " on top of R&D")))) - :cancel-effect (effect (lose-tags :corp 1) - (shuffle! :corp :deck) - (system-msg (str "uses Lily Lockwell, but did not find an Operation in R&D")))}]} + (let [c (move state :corp target :play-area)] + (reveal state side c) + (shuffle! state :corp :deck) + (move state :corp c :deck {:front true}))))}]} "Long-Term Investment" {:derezzed-events {:runner-turn-ends corp-rez-toast} @@ -1080,18 +1069,19 @@ :move-zone re-enable-target}) "Marilyn Campaign" - (let [ability {:msg "gain 2 [Credits]" - :counter-cost [:credit 2] - :once :per-turn + (let [ability {:once :per-turn :interactive (req true) :req (req (:corp-phase-12 @state)) :label (str "Gain 2 [Credits] (start of turn)") + :msg (msg "gain " (min 2 (get-counters card :credit)) " [Credits]") :async true - :effect (req (gain-credits state :corp 2) - (if (zero? (get-counters (get-card state card) :credit)) + :effect (req (let [credits (min 2 (get-counters card :credit))] + (add-counter state side card :credit (- credits)) + (gain-credits state :corp credits)) + (if (not (pos? (get-counters (get-card state card) :credit))) (trash state :corp eid card {:unpreventable true}) (effect-completed state :corp eid)))}] - {:effect (effect (add-counter card :credit 8)) + {:data {:counter {:credit 8}} :derezzed-events {:runner-turn-ends corp-rez-toast} :events {:corp-turn-begins ability} :abilities [(set-autoresolve :auto-reshuffle "Marilyn reshuffle")] @@ -1115,27 +1105,21 @@ "Mark Yale" {:events {:agenda-counter-spent {:msg "gain 1 [Credits]" :effect (effect (gain-credits 1))}} - :abilities [{:label "Trash to gain 2 [Credits]" + :abilities [{:label "Gain 2 [Credits]" :msg "gain 2 [Credits]" - :effect (effect (trash card {:cause :ability-cost}) - (gain-credits 2))} - {:label "Spend an agenda counter to gain 2 [Credits]" - :effect (effect (continue-ability - {:prompt "Select an agenda with a counter" - :choices {:req #(and (agenda? %) - (pos? (get-counters % :agenda)))} - :msg (msg "spend an agenda token on " (:title target) " and gain 2 [Credits]") - :effect (effect (add-counter target :agenda -1) - (gain-credits 2) - (trigger-event :agenda-counter-spent card))} - card nil))}]} + :cost [:trash] + :effect (effect (gain-credits 2))} + {:label "Gain 2 [Credits]" + :msg "gain 2 [Credits]" + :cost [:any-agenda-counter] + :effect (effect (gain-credits 2))}]} "Marked Accounts" (let [ability {:msg "take 1 [Credits]" :label "Take 1 [Credits] (start of turn)" :once :per-turn - :counter-cost [:credit 1] - :effect (effect (gain-credits 1))}] + :effect (effect (add-counter card :credit -1) + (gain-credits 1))}] {:abilities [ability {:cost [:click 1] :msg "store 3 [Credits]" @@ -1148,12 +1132,9 @@ :msg "to force the Runner to lose a [Click] next turn and place a power counter on itself" :effect (req (swap! state update-in [:runner :extra-click-temp] (fnil dec 0)) (add-counter state side card :power 1))} - {:cost [:click 1] - :counter-cost [:power 3] + {:cost [:click 1 :power 3 :trash] :msg "gain 4 [Click] and trash itself" - :effect (effect (trash card {:cause :ability-cost - :unpreventable true}) - (gain :click 4))}]} + :effect (effect (gain :click 4))}]} "Melange Mining Corp." {:abilities [{:cost [:click 3] @@ -1280,10 +1261,9 @@ :effect (effect (add-counter card :power 2) (system-msg (str "places 2 power counters on NASX")))} {:label "[Trash] and gain 2 [Credits] for each power counter" - :cost [:click 1] + :cost [:click 1 :trash] :msg (msg "gain " (* 2 (get-counters card :power)) " [Credits]") - :effect (effect (trash card {:cause :ability-cost}) - (gain-credits (* 2 (get-counters card :power))))}]}) + :effect (effect (gain-credits (* 2 (get-counters card :power))))}]}) "Net Analytics" (let [ability {:req (req (seq (filter #(some #{:tag} %) targets))) @@ -1343,10 +1323,9 @@ "NGO Front" (letfn [(builder [cost cred] - {:advance-counter-cost cost - :effect (effect (trash card {:cause :ability-cost}) - (gain-credits cred)) - :label (str "[Trash]: Gain " cred " [Credits]") + {:cost [:advancement cost :trash] + :effect (effect (gain-credits cred)) + :label (str "Gain " cred " [Credits]") :msg (str "gain " cred " [Credits]")})] {:advanceable :always :abilities [(builder 1 5) @@ -1481,10 +1460,11 @@ "Private Contracts" {:effect (effect (add-counter card :credit 14)) :abilities [{:cost [:click 1] - :counter-cost [:credit 2] - :msg "gain 2 [Credits]" - :effect (req (gain-credits state :corp 2) - (when (zero? (get-counters (get-card state card) :credit)) + :msg (msg "gain " (min 2 (get-counters card :credit)) " [Credits]") + :effect (req (let [credits (min 2 (get-counters card :credit))] + (add-counter state side card :credit (- credits)) + (gain-credits state :corp credits)) + (when (not (pos? (get-counters (get-card state card) :credit))) (trash state :corp card)))}]} "Project Junebug" @@ -1618,7 +1598,7 @@ {:events {:damage {:req (req (and (pos? (nth targets 2)) (= :meat target))) :effect (effect (add-counter card :advancement 1) (system-msg "adds 1 advancement token to Reconstruction Contract"))}} - :abilities [{:label "[Trash]: Move advancement tokens to another card" + :abilities [{:label "Move advancement tokens to another card" :prompt "Select a card that can be advanced" :choices {:req can-be-advanced?} :effect (req (let [move-to target] @@ -1627,8 +1607,8 @@ {:prompt "Move how many tokens?" :choices {:number (req (get-counters card :advancement)) :default (req (get-counters card :advancement))} - :effect (effect (trash card {:cause :ability-cost}) - (add-counter move-to :advancement target {:placed true}) + :cost [:trash] + :effect (effect (add-counter move-to :advancement target {:placed true}) (system-msg (str "trashes Reconstruction Contract to move " target (pluralize " advancement token" target) " to " (card-str state move-to))))} @@ -1636,18 +1616,17 @@ "Reversed Accounts" {:advanceable :always - :abilities [{:cost [:click 1] + :abilities [{:cost [:click 1 :trash] :label "Force the Runner to lose 4 [Credits] per advancement" :msg (msg "force the Runner to lose " (min (* 4 (get-counters card :advancement)) (:credit runner)) " [Credits]") - :effect (effect (trash card {:cause :ability-cost}) - (lose-credits :runner (* 4 (get-counters card :advancement))))}]} + :effect (effect (lose-credits :runner (* 4 (get-counters card :advancement))))}]} "Rex Campaign" (let [ability {:once :per-turn :req (req (:corp-phase-12 @state)) :label "Remove 1 counter (start of turn)" :effect (req (add-counter state side card :power -1) - (when (zero? (get-counters (get-card state card) :power)) + (when (not (pos? (get-counters (get-card state card) :power))) (trash state side card) (resolve-ability state side @@ -1672,12 +1651,11 @@ "Ronin" {:advanceable :always - :abilities [{:cost [:click 1] + :abilities [{:cost [:click 1 :trash] :req (req (>= (get-counters card :advancement) 4)) :msg "do 3 net damage" :async true - :effect (effect (trash card {:cause :ability-cost}) - (damage eid :net 3 {:card card}))}]} + :effect (effect (damage eid :net 3 {:card card}))}]} "Roughneck Repair Squad" {:abilities [{:label "Gain 6 [Credits], may remove 1 bad publicity" @@ -1719,21 +1697,18 @@ :choices {:counter :credit} :msg (msg "gain " target " [Credits]") :effect (effect (gain-credits target))} - {:label "[Trash]: Move any number of [Credits] to your credit pool" + {:label "Move any number of [Credits] to your credit pool" :prompt "How many [Credits]?" :choices {:counter :credit} :msg (msg "trash it and gain " target " [Credits]") - :effect (effect (trash card {:cause :ability-cost}) - (gain-credits target))}]} + :cost [:trash] + :effect (effect (gain-credits target))}]} "Security Subcontract" - {:abilities [{:choices {:req #(and (ice? %) - (rezzed? %))} - :cost [:click 1] - :msg (msg "trash " (:title target) " to gain 4 [Credits]") - :label "Trash a rezzed ICE to gain 4 [Credits]" - :effect (effect (trash target {:cause :ability-cost}) - (gain-credits 4))}]} + {:abilities [{:cost [:click 1 :ice] + :msg "gain 4 [Credits]" + :label "Gain 4 [Credits]" + :effect (effect (gain-credits 4))}]} "Sensie Actors Union" {:derezzed-events {:runner-turn-ends corp-rez-toast} @@ -1768,20 +1743,20 @@ {:abilities [{:cost [:click 1] :msg "draw 1 card from the bottom of R&D" :effect (effect (move (last (:deck corp)) :hand))} - {:label "[Trash]: Search R&D for an agenda" + {:label "Search R&D for an agenda" :prompt "Choose an agenda to add to the bottom of R&D" :msg (msg "reveal " (:title target) " from R&D and add it to the bottom of R&D") :choices (req (cancellable (filter agenda? (:deck corp)) :sorted)) + :cost [:trash] :effect (effect (reveal target) - (trash card {:cause :ability-cost}) (shuffle! :deck) (move target :deck))} - {:label "[Trash]: Search Archives for an agenda" + {:label "Search Archives for an agenda" :prompt "Choose an agenda to add to the bottom of R&D" :msg (msg "reveal " (:title target) " from Archives and add it to the bottom of R&D") :choices (req (cancellable (filter agenda? (:discard corp)) :sorted)) + :cost [:trash] :effect (effect (reveal target) - (trash card {:cause :ability-cost}) (move target :deck))}]} "Shattered Remains" @@ -1843,8 +1818,8 @@ :abilities [{:label "Trace 3 - Give the Runner 1 tag" :req (req (:corp-phase-12 @state)) :async true - :effect (effect (trash card {:cause :ability-cost}) - (resolve-ability + :cost [:trash] + :effect (effect (continue-ability {:trace {:base 3 :label "Trace 3 - Give the Runner 1 tag" :successful {:msg "give the Runner 1 tag" @@ -1887,8 +1862,7 @@ card nil))}} "Storgotic Resonator" - {:abilities [{:cost [:click 1] - :counter-cost [:power 1] + {:abilities [{:cost [:click 1 :power 1] :label "Do 1 net damage" :msg "do 1 net damage" :async true @@ -1995,8 +1969,8 @@ :abilities [{:label "Derez 1 card for each advancement token" :req (req (pos? (get-counters card :advancement))) :msg (msg "derez " (quantify (get-counters card :advancement) "card")) + :cost [:trash] :effect (req (let [advancements (get-counters card :advancement)] - (trash state side card {:cause :ability-cost}) (show-wait-prompt state :runner (str "Corp to derez " (quantify advancements "card"))) (wait-for (resolve-ability state side (derez-card advancements) card nil) @@ -2041,8 +2015,8 @@ {:advanceable :always :abilities [{:label "Gain credits" :msg (msg "gain " (* 2 (get-counters card :advancement)) " [Credits]") - :effect (effect (trash card {:cause :ability-cost}) - (gain-credits (* 2 (get-counters card :advancement))))}]} + :cost [:trash] + :effect (effect (gain-credits (* 2 (get-counters card :advancement))))}]} "Toshiyuki Sakai" (advance-ambush @@ -2088,7 +2062,7 @@ :events {:corp-turn-begins {:async true :effect (req (add-counter state side card :power -1) - (if (zero? (get-counters (get-card state card) :power)) + (if (not (pos? (get-counters (get-card state card) :power))) (wait-for (trash state side card nil) (do (system-msg state :corp "uses Urban Renewal to do 4 meat damage") (damage state side eid :meat 4 {:card card}))) @@ -2136,27 +2110,23 @@ :effect (req (swap! state assoc-in [:per-turn (:cid card)] true))}}} "Whampoa Reclamation" - {:abilities [{:label "Trash 1 card from HQ: Add 1 card from Archives to the bottom of R&D" + {:abilities [{:label "Add 1 card from Archives to the bottom of R&D" :once :per-turn :req (req (and (pos? (count (:hand corp))) (pos? (count (:discard corp))))) :async true - :effect (req (show-wait-prompt state :runner "Corp to use Whampoa Reclamation") - (wait-for (resolve-ability state side - {:prompt "Choose a card in HQ to trash" - :choices {:req #(and (in-hand? %) (corp? %))} - :effect (effect (trash target))} - card nil) - (continue-ability - state side - {:prompt "Select a card in Archives to add to the bottom of R&D" - :show-discard true - :choices {:req #(and (in-discard? %) (corp? %))} - :msg (msg "trash 1 card from HQ and add " - (if (:seen target) (:title target) "a card") " from Archives to the bottom of R&D") - :effect (effect (move target :deck) - (clear-wait-prompt :runner))} - card nil)))}]} + :cost [:trash-from-hand 1] + :effect (effect (show-wait-prompt :runner "Corp to use Whampoa Reclamation") + (continue-ability + {:prompt "Select a card in Archives to add to the bottom of R&D" + :show-discard true + :choices {:req #(and (in-discard? %) + (corp? %))} + :msg (msg "trash 1 card from HQ and add " + (if (:seen target) (:title target) "a card") " from Archives to the bottom of R&D") + :effect (effect (move target :deck) + (clear-wait-prompt :runner))} + card nil))}]} "Worlds Plaza" {:abilities [{:label "Install an asset on Worlds Plaza" @@ -2189,9 +2159,9 @@ :cost [:credit 1] :effect (effect (expose-prevent 1))} {:msg "prevent 1 card from being exposed" - :label "[Trash]: Prevent 1 card from being exposed" - :effect (effect (trash card {:cause :ability-cost}) - (expose-prevent 1))}]} + :label "Prevent 1 card from being exposed" + :cost [:trash] + :effect (effect (expose-prevent 1))}]} "Zealous Judge" {:rez-req (req tagged) diff --git a/src/clj/game/cards/events.clj b/src/clj/game/cards/events.clj index a4f9ebf770..bef9fdaebf 100644 --- a/src/clj/game/cards/events.clj +++ b/src/clj/game/cards/events.clj @@ -507,7 +507,7 @@ card nil)) :events {:run-ends nil} :interactions {:access-ability - {:label "[Demolition Run]: Trash card" + {:label "Trash card" :msg (msg "trash " (:title target) " at no cost") :async true :effect (effect (trash-no-cost eid target))}}} @@ -1645,16 +1645,16 @@ (system-msg (str "hosts On the Lam on " (:title target)))) :interactions {:prevent [{:type #{:net :brain :meat :tag} :req (req true)}]} - :abilities [{:label "[Trash]: Avoid 3 tags" + :abilities [{:label "Avoid 3 tags" :msg "avoid up to 3 tags" - :effect (effect (tag-prevent :runner 3) - (trash card {:cause :ability-cost}))} - {:label "[Trash]: Prevent up to 3 damage" + :cost [:trash] + :effect (effect (tag-prevent :runner 3))} + {:label "Prevent up to 3 damage" :msg "prevent up to 3 damage" + :cost [:trash] :effect (effect (damage-prevent :net 3) (damage-prevent :meat 3) - (damage-prevent :brain 3) - (trash card {:cause :ability-cost}))}]} + (damage-prevent :brain 3))}]} "Out of the Ashes" (let [ashes-run {:prompt "Choose a server" diff --git a/src/clj/game/cards/hardware.clj b/src/clj/game/cards/hardware.clj index 8acdee3dee..909c5083d0 100644 --- a/src/clj/game/cards/hardware.clj +++ b/src/clj/game/cards/hardware.clj @@ -110,8 +110,8 @@ (continue-ability state side {:optional - {:prompt (msg (build-cost-str [:credit cost]) - ", plus " (build-cost-str additional-costs) + {:prompt (msg (build-cost-string [:credit cost]) + ", plus " (lower-case (build-cost-string additional-costs)) " as an additional cost to rez " cname "?") :player :corp :yes-ability {:effect (effect (rez :corp c))} @@ -124,20 +124,26 @@ "Bookmark" {:abilities [{:label "Host up to 3 cards from your Grip facedown" - :cost [:click 1] :msg "host up to 3 cards from their Grip facedown" + :cost [:click 1] + :msg "host up to 3 cards from their Grip facedown" :choices {:max 3 :req #(and (runner? %) (in-hand? %))} :effect (req (doseq [c targets] (host state side (get-card state card) c {:facedown true})))} - {:label "Add all hosted cards to Grip" :cost [:click 1] :msg "add all hosted cards to their Grip" + {:label "Add all hosted cards to Grip" + :cost [:click 1] + :msg "add all hosted cards to their Grip" :effect (req (doseq [c (:hosted card)] (move state side c :hand)))} - {:label "[Trash]: Add all hosted cards to Grip" :msg "add all hosted cards to their Grip" + {:label "Add all hosted cards to Grip" :effect (req (doseq [c (:hosted card)] (move state side c :hand)) - (update! state side (dissoc card :hosted)) - (trash state side (get-card state card) {:cause :ability-cost}))}]} + (continue-ability + state side + {:cost [:trash] + :msg "add all hosted cards to their Grip"} + card nil))}]} "Box-E" {:in-play [:memory 2 :hand-size 2]} @@ -211,17 +217,23 @@ card nil)))}]} "Clone Chip" - {:abilities [{:prompt "Select a program to install from your Heap" - :priority true - :show-discard true - :req (req (and (not (seq (get-in @state [:runner :locked :discard]))) - (not (install-locked? state side)))) - :choices {:req #(and (program? %) - (in-discard? %))} - :effect (req (when (>= (:credit runner) (:cost target)) - (runner-install state side target) - (trash state side card {:cause :ability-cost}) - (system-msg state side (str "uses " (:title card) " to install " (:title target)))))}]} + {:abilities [{:effect + (effect + (continue-ability + (let [can-pay-for? #(can-pay? state side eid card nil [:credit (:cost %)])] + {:prompt "Select a program to install from your Heap" + :priority true + :show-discard true + :req (req (and (not (seq (get-in @state [:runner :locked :discard]))) + (not (install-locked? state side)) + (some can-pay-for? (filter program? (:discard runner))))) + :choices {:req #(and (program? %) + (in-discard? %) + (can-pay-for? %))} + :cost [:trash] + :msg (msg "install " (:title target)) + :effect (effect (runner-install eid target nil))}) + card nil))}]} "Comet" {:in-play [:memory 1] @@ -242,8 +254,10 @@ :choices {:req ice?} :msg (msg "trashes Cortez Chip to increase the rez cost of " (card-str state target) " by 2 [Credits] until the end of the turn") - :effect (effect (update! (assoc card :cortez-target target)) - (trash (get-card state card) {:cause :ability-cost}))}] + :cost [:trash] + :effect (effect (update! (assoc card + :zone '(:discard) + :cortez-target target)))}] :trash-effect {:effect (effect (register-events {:pre-rez {:req (req (same-card? target (:cortez-target card))) :effect (effect (rez-additional-cost-bonus [:credit 2]))} :runner-turn-ends {:effect (effect (unregister-events card))} @@ -382,8 +396,7 @@ "Dorm Computer" {:data {:counter {:power 4}} - :abilities [{:counter-cost [:power 1] - :cost [:click 1] + :abilities [{:cost [:click 1 :power 1] :req (req (not run)) :prompt "Choose a server" :choices (req runnable-servers) @@ -422,6 +435,7 @@ "EMP Device" {:abilities [{:req (req (:run @state)) :msg "prevent the Corp from rezzing more than 1 piece of ICE for the remainder of the run" + :cost [:trash] :effect (effect (register-events {:rez {:req (req (ice? target)) :effect (effect (register-run-flag! @@ -431,8 +445,7 @@ ((constantly false) (toast state :corp "Cannot rez ICE the rest of this run due to EMP Device")) true))))} - :run-ends {:effect (effect (unregister-events card))}} (assoc card :zone '(:discard))) - (trash card {:cause :ability-cost}))}] + :run-ends {:effect (effect (unregister-events card))}} (assoc card :zone '(:discard))))}] :events {:rez nil :run-ends nil}} @@ -442,10 +455,10 @@ :abilities [{:cost [:credit 3] :msg "prevent 1 net damage" :effect (effect (damage-prevent :net 1))} - {:label "[Trash]: Prevent up to 2 brain damage" + {:label "Prevent up to 2 brain damage" :msg "prevent up to 2 brain damage" - :effect (effect (trash card {:cause :ability-cost}) - (damage-prevent :brain 2))}]} + :cost [:trash] + :effect (effect (damage-prevent :brain 2))}]} "Flame-out" (let [turn-end {:async true @@ -460,8 +473,8 @@ :abilities [{:label "Take 1 [Credits] from Flame-out" :req (req (and (not-empty (:hosted card)) (pos? (get-counters card :credit)))) - :counter-cost [:credit 1] - :effect (req (gain-credits state :runner 1) + :effect (req (add-counter state side card :credit -1) + (gain-credits state :runner 1) (system-msg state :runner "takes 1 [Credits] from Flame-out") (register-events state :runner @@ -524,32 +537,36 @@ {:optional {:prompt "Use Flip Switch to reduce base trace strength to 0?" :yes-ability {:msg "reduce the base trace strength to 0" - :effect (req (wait-for (trash state side card {:cause :ability-cost}) - (swap! state assoc-in [:trace :force-base] 0) - (effect-completed state side eid)))} + :cost [:trash] + :effect (req (swap! state assoc-in [:trace :force-base] 0) + (effect-completed state side eid))} :end-effect (effect (clear-wait-prompt :corp))}} card nil))}} :abilities [{:label "Jack out" :req (req (and run (= :runner (:active-player @state)))) :msg "jack out" - :effect (req (wait-for (trash state side card {:cause :ability-cost}) - (jack-out state side eid)))} + :cost [:trash] + :effect (effect (jack-out eid))} {:label "Remove 1 tag" :req (req (and (pos? (count-tags state)) (= :runner (:active-player @state)))) :msg "remove 1 tag" - :effect (req (wait-for (trash state side card {:cause :ability-cost}) - (lose-tags state side eid 1)))}]} + :cost [:trash] + :effect (effect (lose-tags eid 1))}]} "Forger" {:interactions {:prevent [{:type #{:tag} :req (req true)}]} :in-play [:link 1] - :abilities [{:msg "avoid 1 tag" :label "[Trash]: Avoid 1 tag" - :effect (effect (tag-prevent :runner 1) (trash card {:cause :ability-cost}))} - {:msg "remove 1 tag" :label "[Trash]: Remove 1 tag" - :effect (effect (trash card {:cause :ability-cost}) (lose-tags 1))}]} + :abilities [{:msg "avoid 1 tag" + :label "Avoid 1 tag" + :cost [:trash] + :effect (effect (tag-prevent :runner 1))} + {:msg "remove 1 tag" + :label "Remove 1 tag" + :cost [:trash] + :effect (effect (lose-tags 1))}]} "Friday Chip" (let [ability {:msg (msg "move 1 virus counter to " (:title target)) @@ -615,13 +632,10 @@ {:in-play [:memory 1] :interactions {:prevent [{:type #{:net :brain :meat} :req (req true)}]} - :abilities [{:msg (msg "prevent 1 damage, trashing " - (when (facedown? target) "a facedown ") - (:title target)) - :choices {:req #(and (runner? %) (installed? %))} - :priority 50 - :effect (effect (trash target {:unpreventable true}) - (damage-prevent :brain 1) + :abilities [{:label "Prevent 1 damage" + :msg "prevent 1 damage" + :cost [:installed 1] + :effect (effect (damage-prevent :brain 1) (damage-prevent :meat 1) (damage-prevent :net 1))}]} @@ -715,13 +729,13 @@ (corp? (:card-cause target))))}]} :abilities [{:msg "prevent the run from ending" :req (req (some #{:hq} (:successful-run runner-reg))) - :effect (effect (end-run-prevent) - (move card :rfg))}]} + :cost [:remove-from-game] + :effect (effect (end-run-prevent))}]} "Mâché" {:abilities [{:label "Draw 1 card" :msg "draw 1 card" - :counter-cost [:power 3] + :cost [:power 3] :async true :effect (effect (draw :runner eid 1 nil))}] :events {:runner-trash {:once :per-turn @@ -818,8 +832,7 @@ :req (req (= target :rd)) :effect (effect (add-counter card :power 1))}} :abilities [{:async true - :cost [:click 1] - :counter-cost [:power 3] + :cost [:click 1 :power 3] :msg "access the top card of R&D" :effect (req (do-access state side eid [:rd] {:no-root true}))}]} @@ -847,13 +860,9 @@ :req (req true)}]} :in-play [:memory 3] :effect (effect (resolve-ability (mhelper 1) card nil)) - :abilities [{:msg (msg "prevent 1 brain or net damage by trashing " (:title target)) - :priority 50 - :choices {:req #(and (program? %) - (in-hand? %))} - :prompt "Choose a program to trash from your Grip" - :effect (effect (trash target) - (damage-prevent :brain 1) + :abilities [{:msg (msg "prevent 1 brain or net damage") + :cost [:trash-program-from-grip 1] + :effect (effect (damage-prevent :brain 1) (damage-prevent :net 1))}]}) "Muresh Bodysuit" @@ -1058,7 +1067,7 @@ {:data [:counter {:power 4}] :interactions {:prevent [{:type #{:meat} :req (req true)}]} - :abilities [{:counter-cost [:power 1] + :abilities [{:cost [:power 1] :msg "prevent 1 meat damage" :effect (req (damage-prevent state side :meat 1) (when (zero? (get-counters (get-card state card) :power)) @@ -1147,10 +1156,11 @@ :choices {:number (req (min n (count (:deck runner))))} :msg (msg "trash " (join ", " (map :title (take target (:deck runner)))) " from their Stack and prevent " target " damage") + :cost [:trash] :effect (effect (damage-prevent :net target) (damage-prevent :brain target) - (mill :runner target) - (trash card {:cause :ability-cost}))} card nil)))}]} + (mill :runner target))} + card nil)))}]} "Recon Drone" ; eventmap uses reverse so we get the most recent event of each kind into map @@ -1166,8 +1176,8 @@ :choices {:number (req (min (last (:pre-damage (eventmap @state))) (:credit runner)))} :msg (msg "prevent " target " damage") - :effect (effect (trash card {:cause :ability-cost}) - (damage-prevent (first (:pre-damage (eventmap @state))) target) + :cost [:trash] + :effect (effect (damage-prevent (first (:pre-damage (eventmap @state))) target) (lose-credits target))} card nil))}]}) @@ -1234,25 +1244,28 @@ {:abilities [{:cost [:click 1] :once :per-turn :async true - :prompt "How many [Credits]?" :choices :credit + :prompt "How many [Credits]?" + :choices :credit :effect (effect (system-msg (str "spends a [Click] and " target " [Credit] on Rubicon Switch")) - (resolve-ability {:choices {:req #(and (ice? %) - (= :this-turn (:rezzed %)) - (<= (:cost %) target))} - :effect (effect (derez target)) - :msg (msg "derez " (:title target))} card nil))}]} + (continue-ability + {:choices {:req #(and (ice? %) + (= :this-turn (:rezzed %)) + (<= (:cost %) target))} + :effect (effect (derez target)) + :msg (msg "derez " (:title target))} + card nil))}]} "Security Chip" - {:abilities [{:label "[Trash]: Add [Link] strength to a non-Cloud icebreaker until the end of the run" + {:abilities [{:label "Add [Link] strength to a non-Cloud icebreaker until the end of the run" :msg (msg "add " (:link runner) " strength to " (:title target) " until the end of the run") :req (req (:run @state)) :prompt "Select one non-Cloud icebreaker" :choices {:req #(and (has-subtype? % "Icebreaker") (not (has-subtype? % "Cloud")) (installed? %))} - :effect (effect (pump target (:link runner) :all-run) - (trash (get-card state card) {:cause :ability-cost}))} - {:label "[Trash]: Add [Link] strength to any Cloud icebreakers until the end of the run" + :cost [:trash] + :effect (effect (pump target (:link runner) :all-run))} + {:label "Add [Link] strength to any Cloud icebreakers until the end of the run" :msg (msg "add " (:link runner) " strength to " (count targets) " Cloud icebreakers until the end of the run") :req (req (:run @state)) :prompt "Select any number of Cloud icebreakers" @@ -1260,10 +1273,10 @@ :req #(and (has-subtype? % "Icebreaker") (has-subtype? % "Cloud") (installed? %))} + :cost [:trash] :effect (req (doseq [t targets] (pump state side t (:link runner) :all-run) - (update-breaker-strength state side t)) - (trash state side (get-card state card) {:cause :ability-cost}))}]} + (update-breaker-strength state side t)))}]} "Security Nexus" {:implementation "Bypass is manual" @@ -1348,7 +1361,8 @@ :abilities [{:label "Draw 3 cards" :msg "draw 3 cards" :async true - :effect (req (wait-for (trash state :runner card {:cause :ability-cost}) (draw state :runner eid 3 nil)))}]} + :cost [:trash] + :effect (effect (draw :runner eid 3 nil))}]} "Spy Camera" {:abilities [{:cost [:click 1] @@ -1364,10 +1378,10 @@ (count from) from) card nil) (do (clear-wait-prompt state :corp) (effect-completed state side eid)))))} - {:label "[Trash]: Look at the top card of R&D" + {:label "Look at the top card of R&D" :msg "trash it and look at the top card of R&D" - :effect (effect (prompt! card (str "The top card of R&D is " (:title (first (:deck corp)))) ["OK"] {}) - (trash card {:cause :ability-cost}))}]} + :cost [:trash] + :effect (effect (prompt! card (str "The top card of R&D is " (:title (first (:deck corp)))) ["OK"] {}))}]} "Supercorridor" {:in-play [:memory 2 :hand-size 1] diff --git a/src/clj/game/cards/ice.clj b/src/clj/game/cards/ice.clj index 562d27f316..172f73d7a9 100644 --- a/src/clj/game/cards/ice.clj +++ b/src/clj/game/cards/ice.clj @@ -17,7 +17,7 @@ (defn runner-pay-or-break "Ability to break a subroutine by spending a resource (Bioroids, Negotiator, etc)" [cost subs label] - (let [cost-str (build-cost-str [cost]) + (let [cost-str (build-cost-string cost cost->label) subs-str (quantify subs "subroutine")] {:cost cost :label (str label " " subs-str) @@ -97,7 +97,7 @@ (assoc ability :label (str "Hosted power counter: " label) :msg (str message " using 1 power counter") - :counter-cost [:power 1])) + :cost [:power 1])) (defn do-psi "Start a psi game, if not equal do ability" @@ -482,8 +482,8 @@ {:abilities [{:label "End the run" :msg (msg "end the run") :async true - :effect (effect (trash card {:cause :ability-cost}) - (end-run eid card))}] + :cost [:trash] + :effect (effect (end-run eid card))}] :subroutines [{:label "Gain 1 [Credits] for each ice protecting this server" :msg (msg "gain " (count (:ices (card->server state card))) @@ -2304,7 +2304,7 @@ :label "Reveal all cards in the Runner's Grip" :msg (msg "reveal the Runner's Grip ( " (join ", " (map :title (:hand runner))) " )")} {:req (req (pos? (get-counters card :power))) - :counter-cost [:power 1] + :cost [:power 1] :label "Hosted power counter: Reveal all cards in Grip and trash 1 card" :msg (msg "look at all cards in Grip and trash " (:title target) " using 1 power counter") diff --git a/src/clj/game/cards/identities.clj b/src/clj/game/cards/identities.clj index 0a58fe9219..e4b988b32c 100644 --- a/src/clj/game/cards/identities.clj +++ b/src/clj/game/cards/identities.clj @@ -419,7 +419,7 @@ {:access-ability {:async true :once :per-turn - :label "[Freedom]: Trash card" + :label "Trash card" :req (req (and (not (:disabled card)) (not (agenda? target)) (<= (:cost target) diff --git a/src/clj/game/cards/operations.clj b/src/clj/game/cards/operations.clj index ff6f990ea9..78a764e481 100644 --- a/src/clj/game/cards/operations.clj +++ b/src/clj/game/cards/operations.clj @@ -440,7 +440,7 @@ "Distract the Masses" (let [shuffle-two {:async true - :effect (effect (rfg-and-shuffle-rd-effect (find-cid (:cid card) (:discard corp)) 2))} + :effect (effect (rfg-and-shuffle-rd-effect eid (find-cid (:cid card) (:discard corp)) 2 nil))} trash-from-hq {:async true :prompt "Select up to 2 cards in HQ to trash" :choices {:max 2 @@ -796,8 +796,8 @@ "Genotyping" {:async true + :msg "trash the top 2 cards of R&D" :effect (effect (mill :corp 2) - (system-msg "trashes the top 2 cards of R&D") (rfg-and-shuffle-rd-effect eid (first (:play-area corp)) 4 false))} "Green Level Clearance" @@ -1285,7 +1285,7 @@ {:events {:pre-steal-cost {:effect (effect (steal-cost-bonus [:credit 2]))}}} "Preemptive Action" - {:effect (effect (rfg-and-shuffle-rd-effect (first (:play-area corp)) (min (count (:discard corp)) 3) true))} + {:effect (effect (rfg-and-shuffle-rd-effect eid (first (:play-area corp)) (min (count (:discard corp)) 3) true))} "Priority Construction" (letfn [(install-card [chosen] diff --git a/src/clj/game/cards/programs.clj b/src/clj/game/cards/programs.clj index c8cae07205..0111a67249 100644 --- a/src/clj/game/cards/programs.clj +++ b/src/clj/game/cards/programs.clj @@ -145,16 +145,16 @@ [ice-type] (auto-icebreaker [ice-type] {:data {:counter {:power 4}} - :abilities [{:counter-cost [:power 1] + :abilities [{:cost [:power 1] :msg (str "break up to 2 " (lower-case ice-type) " subroutines")} (strength-pump 1 1)]})) (defn- break-and-enter "Breakers from the Break and Entry set" [ice-type] - (cloud-icebreaker {:abilities [{:label (str "[Trash]: Break up to 3 " (lower-case ice-type) "subroutines") + (cloud-icebreaker {:abilities [{:label (str "Break up to 3 " (lower-case ice-type) "subroutines") :msg (str "break up to 3 " (lower-case ice-type) " subroutines") - :effect (effect (trash card {:cause :ability-cost}))}] + :cost [:trash]}] :events (let [cloud {:silent (req true) :req (req (has-subtype? target "Icebreaker")) :effect (effect (update-breaker-strength card))}] @@ -301,7 +301,7 @@ {:label "Bypass Code Gate being encountered" :req (req (has-subtype? current-ice "Code Gate")) :msg (msg "trash it and bypass " (:title current-ice)) - :effect (effect (trash card {:cause :ability-cost}))}]}) + :cost [:trash]}]}) "Adept" (ancient-greek-breaker "adept" [{:cost [:credit 2] @@ -456,15 +456,13 @@ {:implementation "Bankroll gains credits automatically." :events {:successful-run {:effect (effect (add-counter card :credit 1) (system-msg "places 1 [Credit] on Bankroll"))}} - :abilities [{:label "[Trash]: Take all credits from Bankroll" + :abilities [{:label "Take all credits from Bankroll" :async true ;; Cannot trash unless there are counters (so game state changes) :req (req (pos? (get-counters card :credit))) - :effect (req (let [credits-on-bankroll (get-counters card :credit)] - (wait-for (trash state :runner card {:cause :ability-cost}) - (gain-credits state :runner credits-on-bankroll) - (system-msg state :runner (str "trashes Bankroll and takes " - credits-on-bankroll " credits from it")))))}]} + :msg (msg "gain " (get-counters card :credit) " credits") + :cost [:trash] + :effect (effect (gain-credits (get-counters card :credit)))}]} "Battering Ram" (auto-icebreaker ["Barrier"] @@ -579,7 +577,7 @@ :effect (effect (gain-credits :runner 2))}]}) "Cache" - {:abilities [{:counter-cost [:virus 1] + {:abilities [{:cost [:virus 1] :effect (effect (gain-credits 1)) :msg "gain 1 [Credits]"}] :data {:counter {:virus 3}}} @@ -693,11 +691,11 @@ (ice? %) (= (:title %) icename))} :msg "redirect the run" + :cost [:trash] :effect (req (let [dest (second (:zone target)) tgtndx (ice-index state target)] (swap! state update-in [:run] - #(assoc % :position tgtndx :server [dest])) - (trash state side card {:cause :ability-cost})))} + #(assoc % :position tgtndx :server [dest]))))} card nil)))}]} "Corroder" @@ -729,7 +727,8 @@ {:implementation "Does not check that all subroutines were broken" :abilities [{:req (req (rezzed? current-ice)) :msg (msg "derez " (:title current-ice)) - :effect (effect (trash card {:cause :ability-cost}) (derez current-ice))}]} + :cost [:trash] + :effect (effect (derez current-ice))}]} "Crowbar" (break-and-enter "Code Gate") @@ -795,7 +794,7 @@ "D4v1d" {:implementation "Does not check that ICE strength is 5 or greater" :data {:counter {:power 3}} - :abilities [{:counter-cost [:power 1] + :abilities [{:cost [:power 1] :msg "break 1 subroutine"}]} "Dagger" @@ -834,7 +833,7 @@ :effect (req (let [c (:datasucker-count (get-card state card))] (ice-strength-bonus state side (- c) target)))} :pass-ice ds :run-ends ds}) - :abilities [{:counter-cost [:virus 1] + :abilities [{:cost [:virus 1] :msg (msg "give -1 strength to " (:title current-ice)) :req (req (and current-ice (:rezzed current-ice))) :effect (req (update! state side (update-in card [:datasucker-count] (fnil #(+ % 1) 0))) @@ -852,8 +851,8 @@ (in-hand? %))} :req (req (not (install-locked? state side))) :msg (msg "install " (:title target) " at no cost") - :effect (effect (trash card {:cause :ability-cost}) - (runner-install (assoc eid :source card :source-type :runner-install) target {:ignore-install-cost true}))} + :cost [:trash] + :effect (effect (runner-install (assoc eid :source card :source-type :runner-install) target {:ignore-install-cost true}))} card nil)))}]} "Deep Thought" @@ -872,16 +871,16 @@ {:label "Bypass Barrier being encountered" :req (req (has-subtype? current-ice "Barrier")) :msg (msg "trash it and bypass " (:title current-ice)) - :effect (effect (trash card {:cause :ability-cost}))}]}) + :cost [:trash]}]}) "Deus X" {:interactions {:prevent [{:type #{:net} :req (req true)}]} :abilities [{:msg "break any number of AP subroutines" - :effect (effect (trash card {:cause :ability-cost}))} + :cost [:trash]} {:msg "prevent any amount of net damage" - :effect (effect (trash card {:cause :ability-cost}) - (damage-prevent :net Integer/MAX_VALUE))}]} + :cost [:trash] + :effect (effect (damage-prevent :net Integer/MAX_VALUE))}]} "Dhegdheer" {:abilities [{:label "Install a program on Dhegdheer" @@ -940,8 +939,8 @@ {:optional {:prompt "Use Disrupter's ability?" :yes-ability - {:effect (req (trash state side card {:cause :ability-cost}) - (swap! state assoc-in [:trace :force-base] 0))} + {:cost [:trash] + :effect (req (swap! state assoc-in [:trace :force-base] 0))} :end-effect (effect (clear-wait-prompt :corp))}} card nil))}}} @@ -1023,12 +1022,9 @@ (continue state side nil))}}} "Endless Hunger" - {:abilities [{:label "Trash 1 installed card to break 1 \"End the run.\" subroutine" - :prompt "Select a card to trash for Endless Hunger" - :choices {:req #(and (runner? %) (installed? %))} - :msg (msg "trash " (:title target) - " and break 1 \"[Subroutine] End the run.\" subroutine") - :effect (effect (trash target {:unpreventable true}))}]} + {:abilities [{:label "Break 1 \"End the run.\" subroutine" + :msg (msg "break 1 \"[Subroutine] End the run.\" subroutine") + :cost [:installed 1]}]} "Engolo" (auto-icebreaker @@ -1168,7 +1164,7 @@ (auto-icebreaker ["All"] {:flags {:runner-phase-12 (req true)} :abilities [(strength-pump 2 1) - {:counter-cost [:virus 1] + {:cost [:virus 1] :msg "break 1 subroutine"} {:label "Take 1 tag to place 2 virus counters (start of turn)" :once :per-turn @@ -1184,12 +1180,12 @@ (auto-icebreaker ["Sentry"] {:abilities [(break-sub 2 2 "Sentry") (strength-pump 2 4) - {:label "Derez a Sentry and return Golden to your Grip" - :cost [:credit 2] - :req (req (and (rezzed? current-ice) (has-subtype? current-ice "Sentry"))) - :msg (msg "derez " (:title current-ice) " and return Golden to their Grip") - :effect (effect (derez current-ice) - (move card :hand))}]}) + {:label "Derez a Sentry" + :cost [:credit 2 :return-to-hand] + :req (req (and (rezzed? current-ice) + (has-subtype? current-ice "Sentry"))) + :msg (msg "derez " (:title current-ice)) + :effect (effect (derez current-ice))}]}) "Gordian Blade" (auto-icebreaker ["Code Gate"] @@ -1197,21 +1193,21 @@ (strength-pump 1 1 :all-run)]}) "Gorman Drip v1" - {:abilities [{:cost [:click 1] :effect (effect (gain-credits (get-virus-counters state card)) - (trash card {:cause :ability-cost})) + {:abilities [{:cost [:click 1 :trash] + :effect (effect (gain-credits (get-virus-counters state card))) :msg (msg "gain " (get-virus-counters state card) " [Credits]")}] :events {:corp-click-credit {:effect (effect (add-counter :runner card :virus 1))} :corp-click-draw {:effect (effect (add-counter :runner card :virus 1))}}} "Grappling Hook" - {:abilities [{:msg "break all but 1 subroutine" :effect (effect (trash card {:cause :ability-cost}))}]} + {:abilities [{:msg "break all but 1 subroutine" + :cost [:trash]}]} "Gravedigger" {:events (let [e {:req (req (and (installed? target) (= (:side target) "Corp"))) :effect (effect (add-counter :runner card :virus 1))}] {:runner-trash e :corp-trash e}) - :abilities [{:counter-cost [:virus 1] - :cost [:click 1] + :abilities [{:cost [:click 1 :virus 1] :msg "force the Corp to trash the top card of R&D" :effect (effect (mill :corp))}]} @@ -1235,8 +1231,7 @@ "Hemorrhage" {:events {:successful-run {:silent (req true) :effect (effect (add-counter card :virus 1))}} - :abilities [{:counter-cost [:virus 2] - :cost [:click 1] + :abilities [{:cost [:click 1 :virus 2] :req (req (pos? (count (:hand corp)))) :msg "force the Corp to trash 1 card from HQ" :effect (req (show-wait-prompt state :runner "Corp to trash a card from HQ") @@ -1311,10 +1306,10 @@ "Imp" {:data {:counter {:virus 2}} - :interactions {:access-ability {:label "[Imp]: Trash card" + :interactions {:access-ability {:label "Trash card" :req (req (and (not (get-in @state [:per-turn (:cid card)])) (pos? (get-counters card :virus)))) - :counter-cost [:virus 1] + :cost [:virus 1] :msg (msg "trash " (:title target) " at no cost") :once :per-turn :async true @@ -1322,12 +1317,11 @@ "Incubator" {:events {:runner-turn-begins {:effect (effect (add-counter card :virus 1))}} - :abilities [{:cost [:click 1] + :abilities [{:cost [:click 1 :trash] :msg (msg "move " (get-counters card :virus) " virus counter to " (:title target)) :choices {:req #(and (installed? %) (has-subtype? % "Virus"))} - :effect (effect (trash card {:cause :ability-cost}) - (add-counter target :virus (get-counters card :virus)))}]} + :effect (effect (add-counter target :virus (get-counters card :virus)))}]} "Inti" (auto-icebreaker ["Barrier"] @@ -1461,10 +1455,10 @@ :abilities [{:cost [:credit 3] :msg "prevent a hardware from being trashed" :effect (effect (trash-prevent :hardware 1))} - {:label "[Trash]: Prevent a hardware from being trashed" + {:label "Prevent a hardware from being trashed" :msg "prevent a hardware from being trashed" - :effect (effect (trash-prevent :hardware 1) - (trash card {:cause :ability-cost}))}]} + :cost [:trash] + :effect (effect (trash-prevent :hardware 1))}]} "Lustig" (auto-icebreaker ["Sentry"] @@ -1473,7 +1467,7 @@ {:label "Bypass Sentry being encountered" :req (req (has-subtype? current-ice "Sentry")) :msg (msg "trash it and bypass " (:title current-ice)) - :effect (effect (trash card {:cause :ability-cost}))}]}) + :cost [:trash]}]}) "Magnum Opus" {:abilities [{:cost [:click 1] @@ -1490,7 +1484,7 @@ :effect (effect (lose-credits target) (add-counter card :power target)) :msg (msg "place " target " power counters on it")} - {:counter-cost [:power 1] + {:cost [:power 1] :label "Hosted power counter: Break ICE subroutine" :msg "break 1 ICE subroutine"} (strength-pump 2 2)] @@ -1649,7 +1643,7 @@ "Overmind" (auto-icebreaker ["All"] {:effect (effect (add-counter card :power (available-mu state))) - :abilities [{:counter-cost [:power 1] + :abilities [{:cost [:power 1] :msg "break 1 subroutine"} (strength-pump 1 1)]}) @@ -1772,12 +1766,12 @@ (auto-icebreaker ["Code Gate"] {:abilities [(break-sub 1 1 "Code Gate") (strength-pump 3 3) - {:label "Derez a Code Gate and return Peregrine to your Grip" - :cost [:credit 2] - :req (req (and (rezzed? current-ice) (has-subtype? current-ice "Code Gate"))) - :msg (msg "derez " (:title current-ice) " and return Peregrine to their Grip") - :effect (effect (derez current-ice) - (move card :hand))}]}) + {:label "Derez a Code Gate" + :cost [:credit 2 :return-to-hand] + :req (req (and (rezzed? current-ice) + (has-subtype? current-ice "Code Gate"))) + :msg (msg "derez " (:title current-ice)) + :effect (effect (derez current-ice))}]}) "Persephone" (auto-icebreaker ["Sentry"] @@ -1802,7 +1796,7 @@ :abilities [{:once :per-turn :req (req (and current-ice (rezzed? current-ice))) - :counter-cost [:virus 1] + :cost [:virus 1] :label "Make currently encountered ice gain a subtype" :prompt "Choose an ICE subtype" :choices (req (->> (server-cards) @@ -2000,12 +1994,12 @@ (auto-icebreaker ["Barrier"] {:abilities [(break-sub 1 1 "Barrier") (strength-pump 2 2) - {:label "Derez a Barrier and return Saker to your Grip" - :cost [:credit 2] - :req (req (and (rezzed? current-ice) (has-subtype? current-ice "Barrier"))) - :msg (msg "derez " (:title current-ice) " and return Saker to their Grip") - :effect (effect (derez current-ice) - (move card :hand))}]}) + {:label "Derez a Barrier" + :cost [:credit 2 :return-to-hand] + :req (req (and (rezzed? current-ice) + (has-subtype? current-ice "Barrier"))) + :msg (msg "derez " (:title current-ice)) + :effect (effect (derez current-ice))}]}) "Savant" (ancient-greek-breaker "savant" [{:cost [:credit 2] :req (req (has-subtype? current-ice "Sentry")) @@ -2044,28 +2038,29 @@ "Self-modifying Code" {:abilities [{:req (req (not (install-locked? state side))) - :effect (req (wait-for (trash state side card {:cause :ability-cost}) - (continue-ability state side - {:prompt "Choose a program to install" - :msg (req (if (not= target "No install") - (str "install " (:title target)) - (str "shuffle their Stack"))) - :priority true - :choices (req (cancellable - (conj (vec (sort-by :title (filter program? - (:deck runner)))) - "No install"))) - :cost [:credit 2] - :effect (req (trigger-event state side :searched-stack nil) - (shuffle! state side :deck) - (when (not= target "No install") - (runner-install state side (make-eid state {:source card :source-type :runner-install}) target nil)))} card nil)))}]} + :cost [:trash] + :effect (effect (continue-ability + {:prompt "Choose a program to install" + :msg (req (if (not= target "No install") + (str "install " (:title target)) + (str "shuffle their Stack"))) + :priority true + :choices (req (cancellable + (conj (vec (sort-by :title (filter program? + (:deck runner)))) + "No install"))) + :cost [:credit 2] + :effect (req (trigger-event state side :searched-stack nil) + (shuffle! state side :deck) + (when (not= target "No install") + (runner-install state side (make-eid state {:source card :source-type :runner-install}) target nil)))} + card nil))}]} "Sharpshooter" (auto-icebreaker ["Destroyer"] - {:abilities [{:label "[Trash]: Break any number of Destroyer subroutines" + {:abilities [{:label "Break any number of Destroyer subroutines" :msg "break any number of Destroyer subroutines" - :effect (effect (trash card {:cause :ability-cost}))} + :cost [:trash]} (strength-pump 1 2)]}) "Shiv" @@ -2196,7 +2191,7 @@ {:req (req (and (:run @state) (rezzed? current-ice) (>= (get-counters card :power) 2))) - :counter-cost [:power 2] + :cost [:power 2] :label "Increase non-AI icebreaker strength by +3 until end of encounter" :prompt "Choose an installed non-AI icebreaker" :choices {:req #(and (has-subtype? % "Icebreaker") @@ -2236,21 +2231,21 @@ "Trope" {:events {:runner-turn-begins {:effect (effect (add-counter card :power 1))}} - :abilities [{:cost [:click 1] - :label "[Click], remove Trope from the game: Reshuffle cards from Heap back into Stack" - :effect (effect - (move card :rfg) - (resolve-ability - {:show-discard true - :choices {:max (min (get-counters card :power) (count (:discard runner))) - :all true - :req #(and (runner? %) - (in-discard? %))} - :msg (msg "shuffle " (join ", " (map :title targets)) - " into their Stack") - :effect (req (doseq [c targets] (move state side c :deck)) - (shuffle! state side :deck))} - card nil))}]} + :abilities [{:effect + (effect + (continue-ability + {:cost [:click 1 :remove-from-game] + :label "Reshuffle cards from Heap back into Stack" + :show-discard true + :choices {:max (min (get-counters card :power) (count (:discard runner))) + :all true + :req #(and (runner? %) + (in-discard? %))} + :msg (msg "shuffle " (join ", " (map :title targets)) + " into their Stack") + :effect (req (doseq [c targets] (move state side c :deck)) + (shuffle! state side :deck))} + card nil))}]} "Trypano" (let [trash-if-5 (req (when-let [h (get-card state (:host card))] @@ -2292,8 +2287,7 @@ :events {:successful-run {:silent (req true) :req (req (= target :rd)) :effect (effect (add-counter card :power 1))}} - :abilities [{:cost [:click 1] - :counter-cost [:power 3] + :abilities [{:cost [:click 1 :power 3] :once :per-turn :msg "gain [Click][Click]" :effect (effect (gain :click 2))}]} diff --git a/src/clj/game/cards/resources.clj b/src/clj/game/cards/resources.clj index 2cee4c964b..ca83544400 100644 --- a/src/clj/game/cards/resources.clj +++ b/src/clj/game/cards/resources.clj @@ -27,7 +27,8 @@ (zero? (:position run)) (not (:access @state))))] {:implementation "Click Shard to install when last ICE is passed, but before hitting Successful Run button" - :abilities [(merge {:effect (effect (trash card {:cause :ability-cost}) (effect-fn eid card target)) + :abilities [(merge {:effect (effect (effect-fn eid card target)) + :cost [:trash] :msg message} ability-options)] :install-cost-bonus (req (when (can-install-shard? state run) @@ -77,7 +78,7 @@ {"Aaron Marrón" (let [am {:effect (effect (add-counter card :power 2) (system-msg :runner (str "places 2 power counters on Aaron Marrón")))}] - {:abilities [{:counter-cost [:power 1] + {:abilities [{:cost [:power 1] :msg "remove 1 tag and draw 1 card" :async true :effect (effect (lose-tags 1) (draw eid 1 nil))}] @@ -147,18 +148,16 @@ (add-counter card :credit target)) :msg (msg "move " target " [Credit] to Algo Trading")} {:label "Take all credits from Algo Trading" - :cost [:click 1] + :cost [:click 1 :trash] :msg (msg "trash it and gain " (get-counters card :credit) " [Credits]") - :effect (effect (gain-credits (get-counters card :credit)) - (trash card {:cause :ability-cost}))}] + :effect (effect (gain-credits (get-counters card :credit)))}] :events {:runner-turn-begins {:req (req (>= (get-counters card :credit) 6)) :effect (effect (add-counter card :credit 2) (system-msg (str "adds 2 [Credit] to Algo Trading")))}}} "All-nighter" - {:abilities [{:cost [:click 1] - :effect (effect (trash card {:cause :ability-cost}) - (gain :click 2)) + {:abilities [{:cost [:click 1 :trash] + :effect (effect (gain :click 2)) :msg "gain [Click][Click]"}]} "Always Be Running" @@ -174,9 +173,9 @@ :choices :credit :msg (msg "add " target " power counters") :effect (effect (add-counter card :power target)) - :abilities [{:counter-cost [:power 1] + :abilities [{:cost [:power 1] :msg "look at the top card of Stack" - :effect (req (when (zero? (get-counters (get-card state card) :power)) + :effect (req (when (not (pos? (get-counters (get-card state card) :power))) (trash state :runner card {:unpreventable true}))) :optional {:prompt (msg "Add " (:title (first (:deck runner))) " to bottom of Stack?") :yes-ability {:msg "add the top card of Stack to the bottom" @@ -185,10 +184,11 @@ "Armitage Codebusting" {:data {:counter {:credit 12}} :abilities [{:cost [:click 1] - :counter-cost [:credit 2] - :msg "gain 2 [Credits]" - :effect (req (gain-credits state :runner 2) - (when (zero? (get-counters (get-card state card) :credit)) + :msg (msg "gain " (min 2 (get-counters card :credit)) " [Credits]") + :effect (req (let [credits (min 2 (get-counters card :credit))] + (add-counter state side card :credit (- credits)) + (gain-credits state :runner credits)) + (when (not (pos? (get-counters (get-card state card) :credit))) (trash state :runner card {:unpreventable true})))}]} "Artist Colony" @@ -223,13 +223,13 @@ :once :per-run :msg (msg "places 1 power counter on " (:title card)) :effect (effect (add-counter card :power 1))} - {:label "[Trash]: Derez a piece of ice currently being encountered" + {:label "Derez a piece of ice currently being encountered" :msg "derez a piece of ice currently being encountered and take 1 tag" :req (req (and current-ice (rezzed? current-ice) (<= (get-strength current-ice) (get-counters (get-card state card) :power)))) - :effect (effect (trash card {:cause :ability-cost}) - (derez current-ice) + :cost [:trash] + :effect (effect (derez current-ice) (gain-tags eid 1))}]} "Bank Job" @@ -253,7 +253,7 @@ :choices {:number (req (get-counters (get-card state c) :credit))} :msg (msg "gain " target " [Credits]") :effect (req (gain-credits state side target) - (set-prop state side c :counter {:credit (- creds target)}) + (add-counter state side card :credit (- target)) (when (not (pos? (get-counters (get-card state c) :credit))) (trash state side c {:unpreventable true})))} card nil)))} @@ -263,11 +263,10 @@ {:prompt "How many Bank Job credits?" :choices {:number (req (get-counters (get-card state card) :credit))} :msg (msg "gain " target " [Credits]") - :effect (req (let [creds (get-counters (get-card state card) :credit)] - (gain-credits state side target) - (set-prop state side card :counter {:credit (- creds target)}) - (when (not (pos? (get-counters (get-card state card) :credit))) - (trash state side card {:unpreventable true}))))} + :effect (req (gain-credits state side target) + (add-counter state side card :credit (- target)) + (when (not (pos? (get-counters (get-card state card) :credit))) + (trash state side card {:unpreventable true})))} bj nil)))}))))}}} "Bazaar" @@ -334,16 +333,16 @@ :events {:pre-damage {:req (req (= target :net)) :effect (effect (update! (assoc card :dmg-amount (nth targets 2))))}} :abilities [{:msg (msg "prevent " (dec (:dmg-amount card)) " net damage") - :effect (effect (damage-prevent :net (dec (:dmg-amount card))) - (trash card {:cause :ability-cost}))}]} + :cost [:trash] + :effect (effect (damage-prevent :net (dec (:dmg-amount card))))}]} "Biometric Spoofing" {:interactions {:prevent [{:type #{:net :brain :meat} :req (req true)}]} - :abilities [{:label "[Trash]: Prevent 2 damage" + :abilities [{:label "Prevent 2 damage" :msg "prevent 2 damage" - :effect (effect (trash card {:cause :ability-cost}) - (damage-prevent :brain 2) + :cost [:trash] + :effect (effect (damage-prevent :brain 2) (damage-prevent :net 2) (damage-prevent :meat 2))}]} @@ -432,12 +431,10 @@ "Citadel Sanctuary" {:interactions {:prevent [{:type #{:meat} :req (req true)}]} - :abilities [{:label "[Trash] and trash all cards in Grip to prevent all meat damage" - :msg "trash all cards in their Grip and prevent all meat damage" - :effect (req (trash state side card {:cause :ability-cost}) - (doseq [c (:hand runner)] - (trash state side c {:unpreventable true})) - (damage-prevent state side :meat Integer/MAX_VALUE))}] + :abilities [{:label "Prevent all meat damage" + :msg "prevent all meat damage" + :cost [:trash :trash-entire-hand] + :effect (effect (damage-prevent :meat Integer/MAX_VALUE))}] :events {:runner-turn-ends {:req (req (pos? (count-tags state))) :msg "force the Corp to initiate a trace" @@ -451,12 +448,12 @@ {:events {:pre-resolve-damage {:req (req (pos? (last targets))) :effect (effect (add-counter card :power 1) (system-msg :runner (str "places 1 power counter on Clan Vengeance")))}} - :abilities [{:label "[Trash]: Trash 1 random card from HQ for each power counter" + :abilities [{:label "Trash 1 random card from HQ for each power counter" :req (req (pos? (get-counters card :power))) :msg (msg "trash " (min (get-counters card :power) (count (:hand corp))) " cards from HQ") + :cost [:trash] :effect (effect (trash-cards (take (min (get-counters card :power) (count (:hand corp))) - (shuffle (:hand corp)))) - (trash card {:cause :ability-cost}))}]} + (shuffle (:hand corp)))))}]} "Climactic Showdown" (letfn [(iced-servers [state] @@ -540,13 +537,12 @@ "Counter Surveillance" {:implementation "Does not prevent access of cards installed in the root of a server" - :abilities [{:cost [:click 1] + :abilities [{:cost [:click 1 :trash] :makes-run true :prompt "Choose a server to run with Counter Surveillance" :msg (msg "run " target " and trashes Counter Surveillance") :choices (req (cancellable runnable-servers)) - :effect (req (trash state side card {:cause :ability-cost}) - (make-run state side target nil card) + :effect (req (make-run state side target nil card) (register-events state side {:successful-run {:silent (req true) @@ -574,17 +570,18 @@ :recurring 2 :abilities [{:label "Trash to prevent up to 3 meat damage" :msg "prevent up to 3 meat damage" - :effect (effect (trash card {:cause :ability-cost}) (damage-prevent :meat 3))}]} + :cost [:trash] + :effect (effect (damage-prevent :meat 3))}]} "Crowdfunding" (let [ability {:once :per-turn :label "Take 1 [Credits] (start of turn)" :msg "gain 1 [Credits]" :req (req (:runner-phase-12 @state)) - :counter-cost [:credit 1] :async true - :effect (req (gain-credits state :runner 1) - (if (zero? (get-counters (get-card state card) :credit)) + :effect (req (add-counter state side card :credit -1) + (gain-credits state :runner 1) + (if (not (pos? (get-counters (get-card state card) :credit))) (do (trash state :runner card {:unpreventable true}) (system-msg state :runner (str "trashes Crowdfunding" (when (not (empty? (:deck runner))) @@ -628,24 +625,22 @@ :autoresolve (get-autoresolve :auto-add) :yes-ability {:effect (effect (add-counter card :virus 1) (system-msg "places a virus counter on Crypt"))}}}} - :abilities [{:label "[Click][Trash]: install a virus program from the stack" + :abilities [{:label "[Click]install a virus program from the stack" :prompt "Choose a virus" :msg (msg "install " (:title target) " from the stack") :choices (req (cancellable (filter #(and (program? %) (has-subtype? % "Virus")) (:deck runner)) :sorted)) - :cost [:click 1] - :counter-cost [:virus 3] + :cost [:click 1 :virus 3 :trash] :effect (effect (trigger-event :searched-stack nil) (shuffle! :deck) - (runner-install target) - (trash card {:cause :ability-cost}))} + (runner-install target))} (set-autoresolve :auto-add "adding virus counters to Crypt")]} "Dadiana Chacon" (let [trashme {:effect (effect (unregister-events card) - (damage eid :meat 3 {:unboostable true :card card}) - (trash card {:cause :ability-cost})) + (damage eid :meat 3 {:unboostable true :card card})) + :cost [:trash] :msg (msg "trashes Dadiana Chacon and suffers 3 meat damage")} ability {:once :per-turn :msg "gain 1 [Credits]" @@ -666,11 +661,12 @@ "Daily Casts" (let [ability {:once :per-turn :label "Take 2 [Credits] (start of turn)" - :msg "gain 2 [Credits]" :req (req (:runner-phase-12 @state)) - :counter-cost [:credit 2] - :effect (req (gain-credits state :runner 2) - (when (zero? (get-counters (get-card state card) :credit)) + :msg (msg "gain " (min 2 (get-counters card :credit)) " [Credits]") + :effect (req (let [credits (min 2 (get-counters card :credit))] + (add-counter state side card :credit (- credits)) + (gain-credits state :runner credits)) + (when (not (pos? (get-counters (get-card state card) :credit))) (trash state :runner card {:unpreventable true})))}] {:data {:counter {:credit 8}} :flags {:drip-economy true} @@ -678,7 +674,8 @@ :events {:runner-turn-begins ability}}) "Data Dealer" - {:abilities [{:cost [:click 1 :forfeit] :effect (effect (gain-credits 9)) + {:abilities [{:cost [:click 1 :forfeit] + :effect (effect (gain-credits 9)) :msg (msg "gain 9 [Credits]")}]} "Data Folding" @@ -700,6 +697,7 @@ "DDoS" {:abilities [{:msg "prevent the corp from rezzing the outermost piece of ice during a run on any server this turn" + :cost [:trash] :effect (effect (register-turn-flag! card :can-rez @@ -710,14 +708,14 @@ (= (count (get-in @state (concat [:corp :servers] (:server (:run @state)) [:ices]))) (inc idx))) ((constantly false) (toast state :corp "Cannot rez any outermost ICE due to DDoS." "warning")) - true)))) - (trash card {:cause :ability-cost}))}]} + true)))))}]} "Dean Lister" {:abilities [{:req (req (:run @state)) :msg (msg "add +1 strength for each card in their Grip to " (:title target) " until the end of the run") :choices {:req #(and (has-subtype? % "Icebreaker") (installed? %))} + ; :cost [:trash] :effect (effect (update! (assoc card :dean-target target)) (trash (get-card state card) {:cause :ability-cost}) (update-breaker-strength target))}] @@ -734,7 +732,9 @@ "Decoy" {:interactions {:prevent [{:type #{:tag} :req (req true)}]} - :abilities [{:msg "avoid 1 tag" :effect (effect (tag-prevent :runner 1) (trash card {:cause :ability-cost}))}]} + :abilities [{:cost [:trash] + :msg "avoid 1 tag" + :effect (effect (tag-prevent :runner 1))}]} "District 99" (letfn [(eligible-cards [runner] (filter #(same-card? :faction (:identity runner) %) @@ -742,8 +742,7 @@ {:implementation "Adding power counters must be done manually for programs/hardware trashed manually (e.g. by being over MU)" :abilities [{:label "Add a card from your heap to your grip" :req (req (seq (eligible-cards runner))) - :counter-cost [:power 3] - :cost [:click 1] + :cost [:click 1 :power 3] :prompt "Select a card to add to grip?" :choices (req (eligible-cards runner)) :effect (effect (move target :hand)) @@ -868,11 +867,11 @@ "Earthrise Hotel" (let [ability {:msg "draw 2 cards" :once :per-turn - :counter-cost [:power 1] + :cost [:power 1] :req (req (:runner-phase-12 @state)) :async true :effect (req (wait-for (draw state :runner 2 nil) - (if (zero? (get-counters (get-card state card) :power)) + (if (not (pos? (get-counters (get-card state card) :power))) (trash state :runner eid card {:unpreventable true}) (effect-completed state side eid))))}] {:flags {:runner-turn-draw true @@ -905,13 +904,13 @@ "Fall Guy" {:interactions {:prevent [{:type #{:trash-resource} :req (req true)}]} - :abilities [{:label "[Trash]: Prevent another installed resource from being trashed" - :effect (effect (trash card {:unpreventable true :cause :ability-cost}) - (trash-prevent :resource 1))} - {:label "[Trash]: Gain 2 [Credits]" + :abilities [{:label "Prevent another installed resource from being trashed" + :cost [:trash] + :effect (effect (trash-prevent :resource 1))} + {:label "Gain 2 [Credits]" :msg "gain 2 [Credits]" - :effect (effect (trash card {:cause :ability-cost}) - (gain-credits 2))}]} + :cost [:trash] + :effect (effect (gain-credits 2))}]} "Fan Site" {:events {:agenda-scored {:msg "add it to their score area as an agenda worth 0 agenda points" @@ -1014,10 +1013,10 @@ (continue-ability state :runner ability card nil))))}}} "Gbahali" - {:abilities [{:label "[Trash]: Break the last subroutine on the encountered piece of ice" + {:abilities [{:label "Break the last subroutine on the encountered piece of ice" :req (req (and (:run @state) (rezzed? current-ice))) - :effect (effect (trash card {:cause :ability-cost}) - (system-msg :runner + :cost [:trash] + :effect (effect (system-msg :runner (str "trashes Gbahali to break the last subroutine on " (:title current-ice))))}]} @@ -1028,12 +1027,12 @@ "Ghost Runner" {:data {:counter {:credit 3}} - :abilities [{:counter-cost [:credit 1] - :msg "gain 1 [Credits]" + :abilities [{:msg "gain 1 [Credits]" :req (req (:run @state)) - :effect (req (gain-credits state side 1) + :effect (req (add-counter state side card :credit -1) + (gain-credits state side 1) (trigger-event state side :spent-stealth-credit card) - (when (zero? (get-counters (get-card state card) :credit)) + (when (not (pos? (get-counters (get-card state card) :credit))) (trash state :runner card {:unpreventable true})))}] :events (trash-on-empty :credit) ; See Net Mercur for why this implementation was chosen @@ -1080,8 +1079,7 @@ "Hades Shard" (shard-constructor :archives "access all cards in Archives" {:async true} - (req (trash state side card {:cause :ability-cost}) - (swap! state update-in [:corp :discard] #(map (fn [c] (assoc c :seen true)) %)) + (req (swap! state update-in [:corp :discard] #(map (fn [c] (assoc c :seen true)) %)) (wait-for (trigger-event-sync state side :pre-access :archives) (resolve-ability state :runner (choose-access (get-in @state [:corp :discard]) @@ -1111,10 +1109,10 @@ {:abilities [{:label "Prevent a \"when encountered\" ability on a piece of ICE" :msg "prevent a \"when encountered\" ability on a piece of ICE" :once :per-turn} - {:label "[Trash]: Install the top 3 cards of your Stack facedown" + {:label "Install the top 3 cards of your Stack facedown" :msg "install the top 3 cards of their Stack facedown" - :effect (req (trash state side card {:cause :ability-cost}) - (doseq [c (take 3 (get-in @state [:runner :deck]))] + :cost [:trash] + :effect (req (doseq [c (take 3 (get-in @state [:runner :deck]))] (runner-install state side (make-eid state {:source card :source-type :runner-install}) c {:facedown true})))}]} "Ice Analyzer" @@ -1122,8 +1120,8 @@ :events {:rez {:req (req (ice? target)) :msg "place 1 [Credits] on Ice Analyzer" :effect (effect (add-counter :runner card :credit 1))}} - :abilities [{:counter-cost [:credit 1] - :effect (effect (gain-credits 1)) + :abilities [{:effect (effect (add-counter card :credit -1) + (gain-credits 1)) :msg "take 1 [Credits] to install programs"}] :interactions {:pay-credits {:req (req (and (= :runner-install (:source-type eid)) (program? target))) @@ -1142,9 +1140,8 @@ "Investigative Journalism" {:req (req (has-bad-pub? state)) - :abilities [{:cost [:click 4] :msg "give the Corp 1 bad publicity" - :effect (effect (gain-bad-publicity :corp 1) - (trash card {:cause :ability-cost}))}]} + :abilities [{:cost [:click 4 :trash] :msg "give the Corp 1 bad publicity" + :effect (effect (gain-bad-publicity :corp 1))}]} "Jackpot!" (let [jackpot {:interactive (req true) @@ -1195,9 +1192,9 @@ :interactions {:prevent [{:type #{:meat} :req (req true)}]} :abilities [{:label "Prevent 1 meat damage" - :counter-cost [:power 1] + :cost [:power 1] :effect (req (damage-prevent state side :meat 1) - (when (zero? (get-counters (get-card state card) :power)) + (when (not (pos? (get-counters (get-card state card) :power))) (trash state :runner card {:unpreventable true})))}]} "John Masanori" @@ -1262,12 +1259,10 @@ :effect (effect (gain-credits 2))}}} "Kongamato" - {:abilities [{:label "[Trash]: Break the first subroutine on the encountered piece of ice" + {:abilities [{:label "Break the first subroutine on the encountered piece of ice" :req (req (and (:run @state) (rezzed? current-ice))) - :effect (effect (trash card {:cause :ability-cost}) - (system-msg :runner - (str "trashes Kongamato to break the first subroutine on " - (:title current-ice))))}]} + :cost [:trash] + :msg (msg "break the first subroutine on " (:title current-ice))}]} "Laguna Velasco District" {:events {:pre-runner-click-draw {:msg "draw 1 additional card" @@ -1363,8 +1358,8 @@ (when (pos? (:click runner)) (str " and loses " (apply str (repeat (:click runner) "[Click]"))))) - :effect (effect (trash card {:cause :ability-cost}) - (lose :click (:click runner)))}]} + :cost [:trash] + :effect (effect (lose :click (:click runner)))}]} "London Library" {:abilities [{:label "Install a non-virus program on London Library" @@ -1374,7 +1369,7 @@ (not (has-subtype? % "Virus")) (in-hand? %))} :msg (msg "host " (:title target)) - :effect (effect (runner-install (assoc eid :source card :source-type :runner-install) target + :effect (effect (runner-install (assoc eid :source card :source-type :runner-install) target {:host-card card :ignore-install-cost true}))} {:label "Add a program hosted on London Library to your Grip" @@ -1392,8 +1387,8 @@ :prompt "Choose a piece of ICE protecting a remote server" :choices {:req #(and (ice? %) (rezzed? %) (is-remote? (second (:zone %))))} :msg "derez a piece of ICE protecting a remote server" - :effect (effect (derez target) - (trash card {:cause :ability-cost}))}]} + :cost [:trash] + :effect (effect (derez target))}]} "Miss Bones" {:data {:counter {:credit 12}} @@ -1451,13 +1446,14 @@ card nil)) :abilities [{:msg "draw 1 card" :async true - :effect (effect (trash card {:cause :ability-cost}) (draw eid 1 nil))}]} + :cost [:trash] + :effect (effect (draw eid 1 nil))}]} "Net Mercur" - {:abilities [{:counter-cost [:credit 1] - :msg "gain 1 [Credits]" - :effect (effect (gain-credits 1) - (trigger-event :spent-stealth-credit card))}] + {:abilities [{:msg "gain 1 [Credits]" + :effect (effect (add-counter card :credit -1) + (trigger-event :spent-stealth-credit card) + (gain-credits 1))}] :events {:spent-stealth-credit {:req (req (and (:run @state) (has-subtype? target "Stealth"))) @@ -1563,11 +1559,10 @@ :effect (effect (draw eid 1 nil))}}} "Officer Frank" - {:abilities [{:cost [:credit 1] + {:abilities [{:cost [:credit 1 :trash] :req (req (some #(= :meat %) (map first (turn-events state :runner :damage)))) :msg "force the Corp to trash 2 random cards from HQ" - :effect (effect (trash-cards :corp (take 2 (shuffle (:hand corp)))) - (trash card {:cause :ability-cost}))}]} + :effect (effect (trash-cards :corp (take 2 (shuffle (:hand corp)))))}]} "Oracle May" {:abilities [{:cost [:click 1] @@ -1687,7 +1682,7 @@ :once :per-turn :msg (msg "remove 1 counter from " (:title target)) :choices {:req #(:host %)} - :effect (req (if (zero? (get-counters (get-card state target) :power)) + :effect (req (if (not (pos? (get-counters (get-card state target) :power))) (runner-install state side (dissoc target :counter) {:ignore-all-cost true}) (add-counter state side target :power -1)))}] {:flags {:drip-economy true} @@ -1696,7 +1691,7 @@ :choices {:req #(and (#{"Program" "Hardware"} (:type %)) (in-hand? %) (runner? %))} - :effect (req (if (zero? (:cost target)) + :effect (req (if (not (pos? (:cost target))) (runner-install state side (assoc eid :source card :source-type :runner-install) target nil) (host state side card (assoc target :counter {:power (:cost target)})))) @@ -1728,15 +1723,14 @@ :abilities [{:prompt "Select a rezzed card with a trash cost" :choices {:req #(and (:trash %) (rezzed? %))} - :effect (req (let [cost (modified-trash-cost state :runner target)] - (when (can-pay? state side eid card nil [:credit cost]) - (resolve-ability - state side - {:msg (msg "pay " cost " [Credit] and trash " (:title target)) - :effect (effect (lose-credits cost) - (trash card {:cause :ability-cost}) - (trash target))} - card targets))))}]} + :effect (effect + (continue-ability + (let [cost (modified-trash-cost state :runner target)] + (when (can-pay? state side eid card nil [:credit cost]) + {:msg (msg "trash " (:title target)) + :cost [:credit cost :trash] + :effect (effect (trash eid target nil))})) + card targets))}]} "Power Tap" {:events {:pre-init-trace {:msg "gain 1 [Credits]" @@ -1775,44 +1769,42 @@ :label "Expose 1 installed card" :choices {:req installed?} :async true - :effect (effect (expose eid target) - (trash card {:cause :ability-cost}))}]} + :cost [:trash] + :effect (effect (expose eid target))}]} "Reclaim" {:abilities [{:label "Install a program, piece of hardware, or virtual resource from your Heap" - :cost [:click 1] - :req (req (not-empty (:hand runner))) - :prompt "Choose a card to trash" - :choices (req (cancellable (:hand runner) :sorted)) - :async true - :effect (req (wait-for - (trash state :runner card {:cause :ability-cost}) - (wait-for - (trash state :runner target {:unpreventable true}) - (continue-ability - state :runner - {:prompt "Choose a card to install" - :choices (req (cancellable - (filter #(and (or (program? %) - (hardware? %) - (and (resource? %) - (has-subtype? % "Virtual"))) - (can-pay? state :runner eid card nil (:cost %))) - (:discard runner)) - :sorted)) - :msg (msg "install " (:title target) " from the Heap") - :async true - :effect (req (runner-install state :runner (assoc eid :source card :source-type :runner-install) target nil))} - card nil))))}]} + :cost [:click 1 :trash :trash-from-hand] + :effect + (effect + (continue-ability + {:prompt "Choose a card to install" + :choices (req (conj (vec (sort-by + :title + (filter #(and (or (program? %) + (hardware? %) + (and (resource? %) + (has-subtype? % "Virtual"))) + (can-pay? state :runner eid card nil (:cost %))) + (:discard runner)))) + "No install")) + :msg (msg (if (= target "No install") + (str "search the heap, but does not find anything to install") + (str "install " (:title target) " from the heap"))) + :async true + :effect (req (when (not= target "No install") + (runner-install state :runner (assoc eid :source card :source-type :runner-install) target nil)))} + card nil))}]} "Rogue Trading" {:data {:counter {:credit 18}} :abilities [{:cost [:click 2] - :counter-cost [:credit 6] :msg "gain 6 [Credits] and take 1 tag" - :effect (req (gain-credits state :runner 6) - (when (zero? (get-counters (get-card state card) :credit)) + :effect (req (let [credits (min 6 (get-counters card :credit))] + (add-counter state side card :credit (- credits)) + (gain-credits state :runner 6)) + (when (not (pos? (get-counters (get-card state card) :credit))) (trash state :runner card {:unpreventable true})) (gain-tags state :runner eid 1))}]} @@ -1835,42 +1827,38 @@ (mill :runner 3))}} "Rosetta 2.0" - {:abilities [{:req (req (and (not (install-locked? state side)) - (some program? (all-active-installed state :runner)))) - :cost [:click 1] - :prompt "Choose an installed program to remove from the game" - :choices {:req #(and (installed? %) (program? %))} - :effect (req (let [n (:cost target) - t (:title target)] - (move state side target :rfg) - (resolve-ability - state side - {:prompt "Choose a non-virus program to install" - :msg (req (if (not= target "No install") - (str "remove " t - " from the game and install " (:title target) - ", lowering its cost by " n) - (str "shuffle their Stack"))) - :priority true - :choices (req (cancellable - (conj (vec (sort-by :title (filter #(and (program? %) - (not (has-subtype? % "Virus"))) - (:deck runner)))) - "No install"))) - :effect (req (trigger-event state side :searched-stack nil) - (shuffle! state side :deck) - (when (not= target "No install") - (install-cost-bonus state side [:credit (- n)]) - (runner-install state side (make-eid state {:source card :source-type :runner-install}) target nil)))} card nil)))}]} + (let [find-rfg (fn [state card] + (first (filter #(= (:cid card) (get-in % [:persistent :from-cid])) (get-in @state [:runner :rfg]))))] + {:abilities [{:req (req (not (install-locked? state side))) + :async true + :cost [:click 1 :rfg-program 1] + :prompt "Choose a non-virus program to install" + :msg (msg "search the stack" + (if (= target "No install") + ", but does not find a program to install" + (str "and install " (:title target) + ", lowering its cost by " (:cost (find-rfg state card))))) + :choices (req (conj (vec (sort-by :title (filter #(and (program? %) + (not (has-subtype? % "Virus"))) + (:deck runner)))) + "No install")) + :effect (req (trigger-event state side :searched-stack nil) + (shuffle! state side :deck) + (when (not= target "No install") + (install-cost-bonus state side [:credit (- (:cost (find-rfg state card)))]) + (runner-install state side (make-eid state {:source card :source-type :runner-install}) target nil)) + (when-let [c (find-rfg state card)] + (update! state side (dissoc-in card [:persistent :from-cid]))))}]}) "Sacrificial Clone" {:interactions {:prevent [{:type #{:net :brain :meat} :req (req true)}]} - :abilities [{:effect (req (doseq [c (concat (get-in runner [:rig :hardware]) + :abilities [{:cost [:trash] + :effect (req (doseq [c (concat (get-in runner [:rig :hardware]) (filter #(not (has-subtype? % "Virtual")) (get-in runner [:rig :resource])) (:hand runner))] - (trash state side c {:cause :ability-cost})) + (trash state side c)) (lose-credits state side :all) (lose-tags state side :all) (lose state side :run-credit :all) @@ -1881,8 +1869,9 @@ "Sacrificial Construct" {:interactions {:prevent [{:type #{:trash-program :trash-hardware} :req (req true)}]} - :abilities [{:effect (effect (trash-prevent :program 1) (trash-prevent :hardware 1) - (trash card {:cause :ability-cost}))}]} + :abilities [{:cost [:trash] + :effect (effect (trash-prevent :program 1) + (trash-prevent :hardware 1))}]} "Safety First" {:in-play [:hand-size -2] @@ -1896,7 +1885,7 @@ "Salsette Slums" {:interactions {:access-ability - {:label "[Salsette Slums]: Remove card from game" + {:label "Remove card from game" :req (req (and (not (get-in @state [:per-turn (:cid card)])) (:trash target) (can-pay? state :runner {:source card :source-type :ability} @@ -1930,7 +1919,7 @@ card nil))}}} "Same Old Thing" - {:abilities [{:cost [:click 2] + {:abilities [{:cost [:click 2 :trash] :req (req (and (not (seq (get-in @state [:runner :locked :discard]))) (pos? (count (filter event? (:discard runner)))))) :prompt "Select an event to play" @@ -1938,7 +1927,7 @@ :show-discard true :choices {:req #(and (event? %) (in-discard? %))} - :effect (effect (trash card {:cause :ability-cost}) (play-instant target))}]} + :effect (effect (play-instant target))}]} "Scrubber" {:recurring 2 @@ -2045,8 +2034,8 @@ "Tallie Perrault" {:abilities [{:label "Draw 1 card for each Corp bad publicity" :async true - :effect (req (wait-for (trash state side card {:cause :ability-cost}) - (draw state side eid (count-bad-pub state) nil))) + :cost [:trash] + :effect (effect (draw eid (count-bad-pub state) nil)) :msg (msg "draw " (count-bad-pub state) " cards")}] :events {:play-operation {:req (req (or (has-subtype? target "Black Ops") @@ -2075,10 +2064,9 @@ :req (req (some #(= % (:type target)) '("Hardware" "Program"))) :effect (effect (add-counter :runner card :credit 1) (system-msg (str "places 1 [Credits] on Technical Writer")))}} - :abilities [{:cost [:click 1] + :abilities [{:cost [:click 1 :trash] :msg (msg "gain " (get-counters card :credit) " [Credits]") - :effect (effect (trash card {:cause :ability-cost}) - (gain-credits (get-counters card :credit)))}]} + :effect (effect (gain-credits (get-counters card :credit)))}]} "Temple of the Liberated Mind" {:abilities [{:cost [:click 1] @@ -2086,7 +2074,7 @@ :msg "place 1 power counter on it" :effect (effect (add-counter card :power 1))} {:label "Gain [Click]" - :counter-cost [:power 1] + :cost [:power 1] :req (req (= (:active-player @state) :runner)) :msg "gain [Click]" :once :per-turn :effect (effect (gain :click 1))}]} @@ -2100,12 +2088,12 @@ :effect (effect (update! (assoc card :server-target target))) :events {:successful-run {:req (req (= (zone->name (get-in @state [:run :server])) (:server-target (get-card state card)))) - :msg "gain 4 [Credits]" - :effect (req (let [creds (get-counters card :credit)] - (gain-credits state side 4) - (set-prop state side card :counter {:credit (- creds 4)}) - (when (zero? (get-counters (get-card state card) :credit)) - (trash state side card {:unpreventable true}))))}}} + :msg (msg "gain " (min 4 (get-counters card :credit)) " [Credits]") + :effect (req (let [credits (min 4 (get-counters card :credit))] + (add-counter state side card :credit (- credits)) + (gain-credits state side credits)) + (when (not (pos? (get-counters (get-card state card) :credit))) + (trash state side card {:unpreventable true})))}}} "The Archivist" {:in-play [:link 1] @@ -2188,6 +2176,7 @@ :abilities [{:msg (msg "give +2 strength to " (:title target)) :choices {:req #(and (has-subtype? % "Icebreaker") (installed? %))} + ; :cost [:trash] :effect (effect (update! (assoc card :hai-target target)) (trash (get-card state card) {:cause :ability-cost}) (update-breaker-strength target))}] @@ -2284,7 +2273,7 @@ "The Turning Wheel" (let [ttw-ab (fn [m s] {:label (str "Access an additional card in " m) - :counter-cost [:power 2] + :cost [:power 2] :req (req run) :msg (msg "access 1 additional card from " m " for the remainder of the run") :effect (req (access-bonus state side s 1))})] @@ -2324,7 +2313,7 @@ :source-type :runner-install}) target nil (install-cost state side target [:credit (dec (:cost target))]))) (do (install-cost-bonus state side [:credit -1]) - (system-msg state side "uses Thunder Art Gallery to install a card.") + (system-msg state side "uses Thunder Art Gallery to install a card") (runner-install state side (merge eid {:source card :source-type :runner-install}) target nil)) (effect-completed state side eid))) diff --git a/src/clj/game/cards/upgrades.clj b/src/clj/game/cards/upgrades.clj index bfa879a358..b9966dafa8 100644 --- a/src/clj/game/cards/upgrades.clj +++ b/src/clj/game/cards/upgrades.clj @@ -175,13 +175,12 @@ "Bio Vault" {:install-req (req (remove #{"HQ" "R&D" "Archives"} targets)) :advanceable :always - :abilities [{:label "[Trash]: End the run" - :advance-counter-cost 2 + :abilities [{:label "End the run" :req (req (:run @state)) :msg "end the run" :async true - :effect (req (wait-for (trash state :corp card {:cause :ability-cost}) - (end-run state :corp eid card)))}]} + :cost [:advancement 2 :trash] + :effect (effect (end-run eid card))}]} "Black Level Clearance" {:events {:successful-run @@ -224,15 +223,15 @@ "Calibration Testing" {:install-req (req (remove #{"HQ" "R&D" "Archives"} targets)) - :abilities [{:label "[Trash]: Place 1 advancement token on a card in this server" + :abilities [{:label "Place 1 advancement token on a card in this server" :async true :effect (effect (continue-ability {:prompt "Select a card in this server" :choices {:req #(in-same-server? % card)} :async true :msg (msg "place an advancement token on " (card-str state target)) - :effect (effect (add-prop target :advance-counter 1 {:placed true}) - (trash eid card {:cause :ability-cost}))} + :cost [:trash] + :effect (effect (add-prop target :advance-counter 1 {:placed true}))} card nil))}]} "Caprice Nisei" @@ -250,7 +249,7 @@ :msg "give the Runner 1 tag"}}} "Code Replicator" - {:abilities [{:label "[Trash]: Force the runner to approach the passed piece of ice again" + {:abilities [{:label "Force the runner to approach the passed piece of ice again" :req (req (and this-server (> (count (get-run-ices state)) (:position run)) (:rezzed (get-in (:ices (card->server state card)) [(:position run)])))) @@ -271,7 +270,7 @@ :effect (effect (add-counter card :power 1))}]}) "Corporate Troubleshooter" - {:abilities [{:label "[Trash]: Add strength to a rezzed ICE protecting this server" :choices :credit + {:abilities [{:label "Add strength to a rezzed ICE protecting this server" :choices :credit :prompt "How many credits?" :effect (req (let [boost target] (resolve-ability @@ -322,7 +321,7 @@ (purge))} :no-ability {:effect (effect (clear-wait-prompt :runner))}}} card nil))} - :abilities [{:label "[Trash]: Purge virus counters" + :abilities [{:label "Purge virus counters" :msg "purge virus counters" :effect (effect (trash card) (purge))}]} "Daruma" @@ -365,7 +364,7 @@ "Defense Construct" {:advanceable :always - :abilities [{:label "[Trash]: Add 1 facedown card from Archives to HQ for each advancement token" + :abilities [{:label "Add 1 facedown card from Archives to HQ for each advancement token" :req (req (and run (= (:server run) [:archives]) (pos? (get-counters card :advancement)))) @@ -431,7 +430,7 @@ :msg "pay 1 [Credit] to place a power counter on Embolus"}}} card nil))} etr {:req (req this-server) - :counter-cost [:power 1] + :cost [:power 1] :msg "end the run" :effect (effect (end-run eid card))}] {:derezzed-events {:runner-turn-ends corp-rez-toast} @@ -535,34 +534,24 @@ (lose :runner :run-credit :all))}]} "Helheim Servers" - {:abilities [{:label "Trash 1 card from HQ: All ice protecting this server has +2 strength until the end of the run" + {:abilities [{:label "All ice protecting this server has +2 strength until the end of the run" :req (req (and this-server (pos? (count run-ices)) (pos? (count (:hand corp))))) :async true - :effect (req (show-wait-prompt state :runner "Corp to use Helheim Servers") - (wait-for - (resolve-ability - state side - {:prompt "Choose a card in HQ to trash" - :choices {:req #(and (in-hand? %) - (corp? %))} - :msg "trash a card from HQ and give all ice protecting this server +2 strength until the end of the run" - :effect (effect (clear-wait-prompt :runner) - (trash eid target nil))} - card nil) - (register-events - state side - {:pre-ice-strength {:req (req (= (card->server state card) - (card->server state target))) - :effect (effect (ice-strength-bonus 2 target))} - :run-ends {:effect (effect (unregister-events card))}} - card) - (continue-ability - state side - {:effect (req (update-ice-in-server - state side (card->server state card)))} - card nil)))}] + :cost [:trash-from-hand 1] + :effect (req (register-events + state side + {:pre-ice-strength {:req (req (= (card->server state card) + (card->server state target))) + :effect (effect (ice-strength-bonus 2 target))} + :run-ends {:effect (effect (unregister-events card))}} + card) + (continue-ability + state side + {:effect (req (update-ice-in-server + state side (card->server state card)))} + card nil))}] :events {:pre-ice-strength nil}} "Henry Phillips" @@ -740,8 +729,8 @@ :autoresolve (get-autoresolve :auto-fire) :yes-ability {:msg "force the Runner to approach outermost piece of ice" - :effect (req (swap! state assoc-in [:run :position] (count run-ices)) - (trash state side eid card {:cause :ability-cost}))} + :cost [:trash] + :effect (req (swap! state assoc-in [:run :position] (count run-ices)))} :end-effect (effect (clear-wait-prompt :runner))}} card nil))}}}] {:events {:approach-server ability} @@ -749,15 +738,17 @@ (set-autoresolve :auto-fire "Fire Letheia Nisei?")]}) "Keegan Lane" - {:abilities [{:label "[Trash], remove a tag: Trash a program" - :req (req (and this-server - (pos? (get-in @state [:runner :tag :base])) - (not (empty? (filter program? - (all-active-installed state :runner)))))) - :msg (msg "remove 1 tag") - :effect (req (resolve-ability state side trash-program card nil) - (trash state side card {:cause :ability-cost}) - (lose-tags state :corp 1))}]} + {:abilities [{:req (req (and this-server + (some? (first (filter program? (all-active-installed state :runner)))))) + :prompt "Select a program to trash" + :label "Trash a program" + :msg (msg "trash " (:title target)) + :choices {:req #(and (installed? %) + (program? %))} + :cost [:tag 1 :trash] + :effect (effect (trash eid target nil))}]} + + "Khondi Plaza" {:recurring (effect (set-prop card :rec-counter (count (get-remotes state)))) @@ -776,12 +767,12 @@ "Marcus Batty" {:abilities [{:req (req this-server) - :label "[Trash]: Start a Psi game" + :label "Start a Psi game" :psi {:not-equal {:prompt "Select a rezzed piece of ICE to resolve one of its subroutines" :choices {:req #(and (ice? %) (rezzed? %))} :msg (msg "resolve a subroutine on " (:title target))}} - :effect (effect (trash card {:cause :ability-cost}))}]} + :cost [:trash]}]} "Mason Bellamy" {:implementation "Manually triggered by Corp" @@ -1084,7 +1075,7 @@ :effect (effect (init-trace-bonus 2))}}} "Ryon Knight" - {:abilities [{:label "[Trash]: Do 1 brain damage" + {:abilities [{:label "Do 1 brain damage" :msg "do 1 brain damage" :req (req (and this-server (zero? (:click runner)))) :async true :effect (effect (trash card) (damage eid :brain 1 {:card card}))}]} @@ -1113,7 +1104,7 @@ "Self-destruct" {:install-req (req (remove #{"HQ" "R&D" "Archives"} targets)) :abilities [{:req (req this-server) - :label "[Trash]: Trace X - Do 3 net damage" + :label "Trace X - Do 3 net damage" :effect (req (let [serv (card->server state card) cards (concat (:ices serv) (:content serv))] (trash state side card) @@ -1140,10 +1131,10 @@ (set-prop card :counter {:credit 0}))}]} "Signal Jamming" - {:abilities [{:label "[Trash]: Cards cannot be installed until the end of the run" + {:abilities [{:label "Cards cannot be installed until the end of the run" :msg (msg "prevent cards being installed until the end of the run") :req (req this-server) - :effect (effect (trash (get-card state card) {:cause :ability-cost}))}] + :cost [:trash]}] :trash-effect {:effect (effect (register-run-flag! card :corp-lock-install (constantly true)) (register-run-flag! card :runner-lock-install (constantly true)) (toast :runner "Cannot install until the end of the run") @@ -1287,7 +1278,7 @@ :effect (effect (gain-credits 1))}}}}} "Tyr's Hand" - {:abilities [{:label "[Trash]: Prevent a subroutine on a piece of Bioroid ICE from being broken" + {:abilities [{:label "Prevent a subroutine on a piece of Bioroid ICE from being broken" :req (req (and (= (butlast (:zone current-ice)) (butlast (:zone card))) (has-subtype? current-ice "Bioroid"))) :effect (effect (trash card)) diff --git a/src/clj/game/core/abilities.clj b/src/clj/game/core/abilities.clj index 5622939266..d69789cbca 100644 --- a/src/clj/game/core/abilities.clj +++ b/src/clj/game/core/abilities.clj @@ -96,7 +96,6 @@ Example: Hayley Kaplan will not show a prompt if there are no valid targets in the grip. OTHER KEYS - :counter-cost / :advance-counter-cost -- number of counters to remove to resolve the ability :once -- its only value is :per-turn; signifies an effect that can only be triggered once per turn. :once-key -- by default, each :once is distinct per card. If multiple copies of a card can only resolve some ability once between all of them, then the card should specify a manual :once-key that can @@ -207,39 +206,6 @@ (if not-distinct cards (distinct-by :title cards))))] (prompt! state s card prompt cs ab args))))) -(declare print-msg do-effect register-end-turn register-once) - -(defn- do-ability - "Perform the ability, checking all costs can be paid etc." - [state side {:keys [eid cost counter-cost advance-counter-cost] :as ability} {:keys [advance-counter] :as card} targets] - ;; Ensure counter costs can be paid - (let [[counter-type counter-amount] counter-cost] - (when (and (or (not counter-cost) - (<= (or counter-amount 0) - (get-in card [:counter counter-type] 0))) - (or (not advance-counter-cost) - (<= advance-counter-cost (or advance-counter 0)))) - ;; Ensure that any costs can be paid - (wait-for - (pay-sync state side (make-eid state eid) card cost {:action (:cid card)}) - (if-let [cost-str async-result] - (let [c (if counter-cost - (update-in card [:counter counter-type] #(- (or % 0) (or counter-amount 0))) - card) - c (if advance-counter-cost - (update-in c [:advance-counter] #(- (or % 0) (or advance-counter-cost 0))) - c)] - ;; Remove any counters - (when (or counter-cost advance-counter-cost) - (update! state side c) - (when (agenda? card) - (trigger-event state side :agenda-counter-spent card))) - ;; Print the message - (print-msg state side ability card targets cost-str) - ;; Trigger the effect - (register-once state ability card) - (do-effect state side ability c targets))))))) - (defn- print-msg "Prints the ability message" [state side {:keys [eid] :as ability} card targets cost-str] @@ -249,16 +215,41 @@ (str (build-spend-msg cost-str "use") (:title card) (when desc (str " to " desc))))))) +(defn register-once + "Register ability as having happened if :once specified" + [state {:keys [once once-key] :as ability} {:keys [cid] :as card}] + (when once (swap! state assoc-in [once (or once-key cid)] true))) + (defn- do-effect "Trigger the effect" [state side {:keys [eid] :as ability} card targets] (when-let [ability-effect (:effect ability)] (ability-effect state side eid card targets))) -(defn register-once - "Register ability as having happened if :once specified" - [state {:keys [once once-key] :as ability} {:keys [cid] :as card}] - (when once (swap! state assoc-in [once (or once-key cid)] true))) +(defn- ugly-counter-hack + "This is brought over from the old do-ability because using `get-card` or `find-latest` + currently doesn't work properly with `pay-counters`" + [card cost] + ;; TODO: Remove me some day + (let [[counter-type counter-amount] (first (filter #(some #{:advancement :agenda :power :virus} %) (partition 2 cost)))] + (if counter-type + (let [counter (if (= :advancement counter-type) + [:advance-counter] + [:counter counter-type])] + (update-in card counter - counter-amount)) + card))) + +(defn- do-ability + "Perform the ability, checking all costs can be paid etc." + [state side {:keys [eid cost] :as ability} card targets] + ;; Ensure that any costs can be paid + (wait-for (pay-sync state side (make-eid state eid) card cost {:action (:cid card)}) + (when-let [cost-str async-result] + ;; Print the message + (print-msg state side ability card targets cost-str) + ;; Trigger the effect + (register-once state ability card) + (do-effect state side ability (ugly-counter-hack card cost) targets)))) (defn active-prompt? "Checks if this card has an active prompt" @@ -491,11 +482,10 @@ :runner-credits runner-credits})] (trace-start state side eid card trace))))) -(defn rfg-and-shuffle-rd-effect - ([state side card n] (rfg-and-shuffle-rd-effect state side (make-eid state) card n false)) - ([state side card n all?] (rfg-and-shuffle-rd-effect state side (make-eid state) card n all?)) +(defn shuffle-into-rd-effect + ([state side card n] (shuffle-into-rd-effect state side (make-eid state) card n false)) + ([state side card n all?] (shuffle-into-rd-effect state side (make-eid state) card n all?)) ([state side eid card n all?] - (move state side card :rfg) (continue-ability state side {:show-discard true :choices {:max n @@ -514,3 +504,10 @@ (shuffle! state side :deck)) :cancel-effect (req (shuffle! state side :deck))} card nil))) + +(defn rfg-and-shuffle-rd-effect + ([state side card n] (rfg-and-shuffle-rd-effect state side (make-eid state) card n false)) + ([state side card n all?] (rfg-and-shuffle-rd-effect state side (make-eid state) card n all?)) + ([state side eid card n all?] + (move state side card :rfg) + (shuffle-into-rd-effect state side eid card n all?))) diff --git a/src/clj/game/core/card_defs.clj b/src/clj/game/core/card_defs.clj index 79b10db2db..6015b2d5b1 100644 --- a/src/clj/game/core/card_defs.clj +++ b/src/clj/game/core/card_defs.clj @@ -45,4 +45,4 @@ (.println *err* (with-out-str (print-stack-trace (Exception. (str "Tried to select card def for non-existent card: " card)) - 25))))) + 2500))))) diff --git a/src/clj/game/core/cards.clj b/src/clj/game/core/cards.clj index e86ca3f7e1..b3d5acc527 100644 --- a/src/clj/game/core/cards.clj +++ b/src/clj/game/core/cards.clj @@ -1,7 +1,7 @@ (in-ns 'game.core) (declare all-installed all-active-installed cards card-init deactivate - card-flag? gain lose get-card-hosted handle-end-run + card-flag? gain get-all-installed lose get-card-hosted handle-end-run register-events remove-from-host remove-icon make-card toast-check-mu trash trigger-event update-breaker-strength update-hosted! update-ice-strength unregister-events diff --git a/src/clj/game/core/costs.clj b/src/clj/game/core/costs.clj index eb138bc2a5..09f6cd86fb 100644 --- a/src/clj/game/core/costs.clj +++ b/src/clj/game/core/costs.clj @@ -3,7 +3,7 @@ (declare forfeit prompt! damage mill is-scored? system-msg unknown->kw discard-from-hand card-str trash trash-cards all-installed-runner-type pick-credit-providing-cards all-active - eligible-pay-credit-cards) + eligible-pay-credit-cards lose-tags) (defn deduct "Deduct the value from the player's attribute." @@ -108,16 +108,28 @@ :credit (or (<= 0 (- (get-in @state [side :credit]) amount)) (<= 0 (- (total-available-credits state side eid card) amount))) :click (<= 0 (- (get-in @state [side :click]) amount)) + :trash (installed? (get-card state card)) :forfeit (<= 0 (- (count (get-in @state [side :scored])) amount)) + :forfeit-self (is-scored? state side (get-card state card)) + ; Can't use count-tags as we can't remove additional tags + :tag (<= 0 (- (get-in @state [:runner :tag :base] 0) amount)) + :return-to-hand (active? (get-card state card)) + :remove-from-game (active? (get-card state card)) + :rfg-program (<= 0 (- (count (all-installed-runner-type state :program)) amount)) + :installed (<= 0 (- (count (all-installed state side)) amount)) :hardware (<= 0 (- (count (all-installed-runner-type state :hardware)) amount)) :program (<= 0 (- (count (all-installed-runner-type state :program)) amount)) :resource (<= 0 (- (count (all-installed-runner-type state :resource)) amount)) :connection (<= 0 (- (count (filter #(has-subtype? % "Connection") (all-active-installed state :runner))) amount)) :ice (<= 0 (- (count (filter (every-pred rezzed? ice?) (all-installed state :corp))) amount)) (:net :meat :brain) (<= amount (count (get-in @state [:runner :hand]))) - :mill (<= 0 (- (count (get-in @state [side :deck])) amount)) + :trash-from-deck (<= 0 (- (count (get-in @state [side :deck])) amount)) (:trash-from-hand :randomly-trash-from-hand) (<= 0 (- (count (get-in @state [side :hand])) amount)) + :trash-program-from-grip (<= 0 (- (count (filter program? (get-in @state [:runner :hand]))) amount)) + :trash-entire-hand true :shuffle-installed-to-stack (<= 0 (- (count (all-installed state :runner)) amount)) + :any-agenda-counter (<= 0 (- (reduce + (map #(get-counters % :agenda) (get-in @state [:corp :scored]))) amount)) + (:advancement :agenda :power :virus) (<= 0 (- (get-counters card cost-type) amount)) ;; default to cannot afford false))) @@ -145,38 +157,141 @@ (str (or verb2 (str verb "s")) " ") (str cost-str " to " verb " ")))) -(defn- cost-names - "Converts a cost (amount attribute pair) to a string for printing" +(defn cost->label [[cost-type amount]] (when (and (number? amount) - (pos? amount)) + (not (neg? amount))) (case cost-type - :credit (str "pay " amount " [Credits]") - :click (str "spend " (->> "[Click]" repeat (take amount) (apply str))) + :credit (str amount " [Credits]") + :click (->> "[Click]" repeat (take amount) (apply str)) + :trash "[Trash]" :forfeit (str "forfeit " (quantify amount "Agenda")) + :forfeit-self "forfeit this Agenda" + :tag (str "remove " (quantify amount "tag")) + :return-to-hand "return this card to your hand" + :remove-from-game "remove this card from the game" + :rfg-program (str "remove " (quantify amount "installed program") " from the game") + :installed (str "trash " (quantify amount "installed card")) :hardware (str "trash " (quantify amount "installed hardware" "")) :program (str "trash " (quantify amount "installed program")) :resource (str "trash " (quantify amount "installed resource")) :connection (str "trash " (quantify amount "installed connection resource")) :ice (str "trash " (quantify amount "installed rezzed ICE" "")) - (:net :meat :brain) (str "suffer " (quantify amount (str (name cost-type) " damage") "")) - :mill (str "trash " (quantify amount "card") " from the top of your deck") + :trash-from-deck (str "trash " (quantify amount "card") " from the top of your deck") :trash-from-hand (str "trash " (quantify amount "card") " from your hand") :randomly-trash-from-hand (str "trash " (quantify amount "card") " randomly from your hand") + :trash-entire-hand "trash all cards in your hand" + :trash-program-from-grip "trash a program in your graip" + (:net :meat :brain) (str "suffer " (quantify amount (str (name cost-type) " damage") "")) + (:advancement :agenda :power :virus) (if (< 1 amount) + (quantify amount (str "hosted " (name cost-type) " counter")) + (str "hosted " (name cost-type) " counter")) :shuffle-installed-to-stack (str "shuffle " (quantify amount "installed card") " into the stack") - (str "pay " (quantify amount (name cost-type)))))) + :any-agenda-counter "any agenda counter" + (str (quantify amount (name cost-type)))))) -(defn build-cost-str +(defn build-cost-label "Gets the complete cost-str for specified costs" [costs] (let [cost-string (->> costs merge-costs - (map #(cost-names %)) + (map cost->label) (filter some?) - (interpose " and ") + (interpose ", ") (apply str))] - (capitalize cost-string))) + (when (not (string/blank? cost-string)) + (capitalize cost-string)))) + +(defn make-label + "Looks into an ability for :label, if it doesn't find it, capitalizes :msg instead." + [ability] + (let [label (or (:label ability) + (and (string? (:msg ability)) + (capitalize (:msg ability))) + "") + cost (:cost ability)] + (if (and (seq cost) (not (string/blank? label))) + (str (build-cost-label cost) ": " label) + label))) + +(defn cost->string + "Converts a cost (amount attribute pair) to a string for printing" + [[cost-type amount]] + (when (and (number? amount) + (not (neg? amount))) + (let [cost-string (cost->label [cost-type amount])] + (cond + (= :click cost-type) (str "spend " cost-string) + (= :credit cost-type) (str "pay " cost-string) + :else cost-string)))) + +(defn build-cost-string + "Gets the complete cost-str for specified costs" + ([costs] (build-cost-string costs cost->string)) + ([costs f] + (let [cost-string + (->> costs + merge-costs + (map f) + (filter some?) + (interpose " and ") + (apply str))] + (when (not (string/blank? cost-string)) + (capitalize cost-string))))) + +;; Cost Handler functions + +(defn all-active-pay-credit-cards + [state side eid card] + (filter #(when-let [pc (-> % card-def :interactions :pay-credits)] + (if (:req pc) + ((:req pc) state side eid % [card]) + true)) + (all-active state side))) + +(defn eligible-pay-credit-cards + [state side eid card] + (filter + #(case (-> % card-def :interactions :pay-credits :type) + :recurring + (pos? (get-counters (get-card state %) :recurring)) + :credit + (pos? (get-counters (get-card state %) :credit)) + :custom + ((-> % card-def :interactions :pay-credits :req) state side eid % [card])) + (all-active-pay-credit-cards state side eid card))) + +(defn pay-credits + [state side eid card amount] + (let [provider-func #(eligible-pay-credit-cards state side eid card)] + (if (and (pos? amount) + (pos? (count (provider-func)))) + (wait-for (resolve-ability state side (pick-credit-providing-cards provider-func eid amount) card nil) + (swap! state update-in [:stats side :spent :credit] (fnil + 0) amount) + (complete-with-result state side eid (str "pays " (:msg async-result)))) + (do + (swap! state update-in [:stats side :spent :credit] (fnil + 0) amount) + (complete-with-result state side eid (deduct state side [:credit amount])))))) + +(defn pay-clicks + [state side eid action costs cost-type amount] + (let [a (first (keep :action action))] + (when (not= a :steal-cost) + ;; do not create an undo state if click is being spent due to a steal cost (eg. Ikawah Project) + (swap! state assoc :click-state (dissoc @state :log))) + (trigger-event state side + (if (= side :corp) :corp-spent-click :runner-spent-click) + a (:click (into {} costs))) + (swap! state assoc-in [side :register :spent-click] true) + (complete-with-result state side eid (deduct state side [cost-type amount])))) + +(defn pay-trash + "[Trash] cost as part of an ability" + [state side eid card amount] + (wait-for (trash state side card {:cause :ability-cost + :unpreventable true}) + (complete-with-result state side eid (str "trashes " (:title card))))) (defn pay-forfeit "Forfeit agenda as part of paying for a card or ability @@ -196,9 +311,59 @@ " (" (:title target) ")"))))} card nil)) -(defn pay-trash +(defn pay-forfeit-self + "Forfeit an agenda as part of paying for the ability on that agenda. (False Lead)" + [state side eid card] + (wait-for (forfeit state side card {:msg false}) + (complete-with-result + state side eid + (str "forfeits " (:title card))))) + +(defn pay-tags + "Removes a tag from the runner. (Keegan Lane)" + [state side eid card amount] + (wait-for (lose-tags state side amount) + (complete-with-result + state side eid + (str "removes " (quantify amount "tag"))))) + +(defn pay-return-to-hand + "Returns an installed card to the player's hand." + [state side eid card] + (move state side card :hand) + (complete-with-result + state side eid + (str "returns " (:title card) + " to " (if (= :corp side) "HQ" "their grip")))) + +(defn pay-remove-from-game + "Remove an installed card from the game." + [state side eid card] + (move state side card :rfg) + (complete-with-result state side eid (str "removes " (:title card) " from the game"))) + +(defn pay-rfg-installed + "Remove a card of a given kind from the game" + [state side eid card card-type amount select-fn] + (continue-ability + state side + {:prompt (str "Choose " (quantify amount card-type) " to remove from the game") + :choices {:all true + :max amount + :req select-fn} + :async true + :effect (req (doseq [t targets] + (move state side (assoc-in t [:persistent :from-cid] (:cid card)) :rfg)) + (complete-with-result + state side eid + (str "removes " (quantify amount card-type) + " from the game" + " (" (join ", " (map #(card-str state %) targets)) ")")))} + card nil)) + +(defn pay-trash-installed "Trash a card as part of paying for a card or ability" - ([state side eid card card-type amount select-fn] (pay-trash state side eid card card-type amount select-fn nil)) + ([state side eid card card-type amount select-fn] (pay-trash-installed state side eid card card-type amount select-fn nil)) ([state side eid card card-type amount select-fn args] (continue-ability state side {:prompt (str "Choose " (quantify amount card-type) " to trash") @@ -206,6 +371,7 @@ :max amount :req select-fn} :async true + :priority 11 :effect (req (wait-for (trash-cards state side targets (merge args {:unpreventable true})) (complete-with-result state side eid @@ -221,27 +387,7 @@ state side eid (str "suffers " amount " " (name dmg-type) " damage")))) -(defn pay-shuffle-installed-to-stack - "Shuffle installed runner cards into the stack as part of paying for a card or ability" - [state side eid card amount] - (continue-ability state :runner - {:prompt (str "Choose " (quantify amount "card") " to shuffle into the stack") - :choices {:max amount - :all true - :req #(and (installed? %) - (runner? %))} - :async true - :effect (req (doseq [c targets] - (move state :runner c :deck)) - (shuffle! state :runner :deck) - (complete-with-result - state side eid - (str "shuffles " (quantify amount "card") - " (" (join ", " (map :title targets)) ")" - " into their stack")))} - card nil)) - -(defn pay-mill +(defn pay-trash-from-deck [state side eid amount] (let [cards (take amount (get-in @state [side :deck]))] (wait-for (trash-cards state side cards {:unpreventable true :seen false}) @@ -280,70 +426,126 @@ (str "trashes " (quantify amount "card") " randomly from " (if (= :corp side) "HQ" "the grip")))))) -(defn all-active-pay-credit-cards - [state side eid card] - (filter #(when-let [pc (-> % card-def :interactions :pay-credits)] - (if (:req pc) - ((:req pc) state side eid % [card]) - true)) - (all-active state side))) +(defn pay-trash-entire-hand + [state side eid] + (let [cards (get-in @state [side :hand])] + (wait-for (trash-cards state side cards {:unpreventable true}) + (complete-with-result + state side eid + (str "trashes all (" (count cards) ") cards in " + (if (= :runner side) "their grip" "HQ") + (when (and (= :runner side) + (pos? (count cards))) + (str " (" (map :title cards) ")"))))))) -(defn eligible-pay-credit-cards - [state side eid card] - (filter - #(case (-> % card-def :interactions :pay-credits :type) - :recurring - (pos? (get-counters (get-card state %) :recurring)) - :credit - (pos? (get-counters (get-card state %) :credit)) - :custom - ((-> % card-def :interactions :pay-credits :req) state side eid % [card])) - (all-active-pay-credit-cards state side eid card))) +(defn pay-trash-program-from-grip + [state side eid amount] + (continue-ability + state side + {:prompt "Choose a program to trash from your grip" + :async true + :choices {:all true + :max amount + :req #(and (program? %) + (in-hand? %))} + :effect (req (wait-for (trash-cards state side targets {:unpreventable true}) + (complete-with-result + state side eid + (str "trashes " (quantify amount "card") + " (" (join ", " (map :title targets)) ")" + " from the grip"))))} + nil nil)) -(defn pay-credits +(defn pay-shuffle-installed-to-stack + "Shuffle installed runner cards into the stack as part of paying for a card or ability" [state side eid card amount] - (let [provider-func #(eligible-pay-credit-cards state side eid card)] - (if (and (pos? amount) - (pos? (count (provider-func)))) - (wait-for (resolve-ability state side (pick-credit-providing-cards provider-func eid amount) card nil) - (swap! state update-in [:stats side :spent :credit] (fnil + 0) amount) - (complete-with-result state side eid (str "pays " (:msg async-result)))) - (do - (swap! state update-in [:stats side :spent :credit] (fnil + 0) amount) - (complete-with-result state side eid (deduct state side [:credit amount])))))) + (continue-ability state :runner + {:prompt (str "Choose " (quantify amount "card") " to shuffle into the stack") + :choices {:max amount + :all true + :req #(and (installed? %) + (runner? %))} + :async true + :effect (req (doseq [c targets] + (move state :runner c :deck)) + (shuffle! state :runner :deck) + (complete-with-result + state side eid + (str "shuffles " (quantify amount "card") + " (" (join ", " (map :title targets)) ")" + " into their stack")))} + card nil)) + +(defn pay-any-agenda-counter + [state side eid] + (continue-ability + state side + {:prompt "Select an agenda with a counter" + :choices {:req #(and (agenda? %) + (is-scored? state side %) + (pos? (get-counters % :agenda)))} + :effect (effect (add-counter target :agenda -1) + (trigger-event :agenda-counter-spent (get-card state target)) + (complete-with-result + eid (str "spends an agenda counter from on " (:title target))))} + nil nil)) + +(defn pay-counter + [state side eid card counter amount] + (update! state side + (if (= counter :advancement) + (update card :advance-counter - amount) + (update-in card [:counter counter] - amount))) + (when (agenda? card) + (trigger-event state side :agenda-counter-spent (get-card state card))) + (complete-with-result + state side eid + (str "spends " + (if (< 1 amount) + (quantify amount (str "hosted " (name counter) " counter")) + (str "a hosted " (name counter) " counter")) + " from on " (:title card)))) (defn- cost-handler "Calls the relevant function for a cost depending on the keyword passed in" ([state side card action costs cost] (cost-handler state side (make-eid state) card action costs cost)) ([state side eid card action costs [cost-type amount]] (case cost-type - :click (let [a (first (keep :action action))] - (when (not= a :steal-cost) - ;; do not create an undo state if click is being spent due to a steal cost (eg. Ikawah Project) - (swap! state assoc :click-state (dissoc @state :log))) - (trigger-event state side - (if (= side :corp) :corp-spent-click :runner-spent-click) - a (:click (into {} costs))) - (swap! state assoc-in [side :register :spent-click] true) - (complete-with-result state side eid (deduct state side [cost-type amount]))) + ; Symbols + :credit (pay-credits state side eid card amount) + :click (pay-clicks state side eid action costs cost-type amount) + :trash (pay-trash state side eid card amount) + ; Forfeit an agenda :forfeit (pay-forfeit state side eid card amount) + :forfeit-self (pay-forfeit-self state side eid card) + + ; Remove a tag from the runner + :tag (pay-tags state side eid card amount) + + ; Return installed card to hand + :return-to-hand (pay-return-to-hand state side eid card) + + ; Remove card from the game + :remove-from-game (pay-remove-from-game state side eid card) + :rfg-program (pay-rfg-installed state side eid card "installed program" amount + (every-pred installed? program? (complement facedown?))) ;; Trash installed cards - :hardware (pay-trash state side eid card "piece of hardware" amount - (every-pred installed? hardware? (complement facedown?)) - {:plural "pieces of hardware"}) - :program (pay-trash state side eid card "program" amount - (every-pred installed? program? (complement facedown?))) - :resource (pay-trash state side eid card "resource" amount - (every-pred installed? resource? (complement facedown?))) - :connection (pay-trash state side eid card "connection" amount - (every-pred installed? #(has-subtype? % "Connection") (complement facedown?))) - :ice (pay-trash state :corp eid card "rezzed ICE" amount - (every-pred rezzed? ice?) - {:cause :ability-cost - :keep-server-alive true - :plural "rezzed ICE"}) + :installed (pay-trash-installed state side eid card "installed card" amount runner?) + :hardware (pay-trash-installed state side eid card "piece of hardware" amount + (every-pred installed? hardware? (complement facedown?)) + {:plural "pieces of hardware"}) + :program (pay-trash-installed state side eid card "program" amount + (every-pred installed? program? (complement facedown?))) + :resource (pay-trash-installed state side eid card "resource" amount + (every-pred installed? resource? (complement facedown?))) + :connection (pay-trash-installed state side eid card "connection" amount + (every-pred installed? #(has-subtype? % "Connection") (complement facedown?))) + :ice (pay-trash-installed state :corp eid card "rezzed ICE" amount + (every-pred rezzed? ice?) + {:keep-server-alive true + :plural "rezzed ICE"}) ;; Suffer damage :net (pay-damage state side eid :net amount) @@ -351,15 +553,23 @@ :brain (pay-damage state side eid :brain amount) ;; Discard cards from deck or hand - :mill (pay-mill state side eid amount) + :trash-from-deck (pay-trash-from-deck state side eid amount) :trash-from-hand (pay-trash-from-hand state side eid amount) :randomly-trash-from-hand (pay-randomly-trash-from-hand state side eid amount) + :trash-entire-hand (pay-trash-entire-hand state side eid) + :trash-program-from-grip (pay-trash-program-from-grip state side eid amount) ;; Shuffle installed runner cards into the stack (eg Degree Mill) :shuffle-installed-to-stack (pay-shuffle-installed-to-stack state side eid card amount) - ;; Pay credits - :credit (pay-credits state side eid card amount) + ;; Spend an agenda counter on another card + :any-agenda-counter (pay-any-agenda-counter state side eid) + + ;; Counter costs + :advancement (pay-counter state side eid card :advancement amount) + :agenda (pay-counter state side eid card :agenda amount) + :power (pay-counter state side eid card :power amount) + :virus (pay-counter state side eid card :virus amount) ;; Else (do diff --git a/src/clj/game/core/runs.clj b/src/clj/game/core/runs.clj index 77fb994449..7b34e201fe 100644 --- a/src/clj/game/core/runs.clj +++ b/src/clj/game/core/runs.clj @@ -157,6 +157,12 @@ [card] (interactions card :access-ability)) +(defn- access-ab-label + [card] + (let [title (first (string/split (:title card) #":")) + label (make-label (access-ab card))] + (str "[" title "] " label))) + (defn access-non-agenda "Access a non-agenda. Show a prompt to trash for trashable cards." [state side eid c & {:keys [skip-trigger-event]}] @@ -198,7 +204,7 @@ (card-flag-fn? state side card :must-trash true))) ; If we must trash, make the label only from the trash abilities ; Otherwise, make the label from all abilities - ability-strs (mapv #(make-label (access-ab %)) + ability-strs (mapv access-ab-label (if must-trash? trash-ab-cards access-ab-cards)) ; Only display "No action" when we're not forced to do anything no-action-str (when-not (or must-trash? must-trash-with-credits?) @@ -241,14 +247,6 @@ (access-end state side eid c)))))} card nil)))) -(defn- join-cost-strs - [& costs] - (->> costs - flatten - (filter some?) - (interpose " and ") - (apply str))) - (defn- access-agenda "Rules interactions for a runner that has accessed an agenda and may be able to steal it." [state side eid card] @@ -256,23 +254,23 @@ (swap! state update-in [:stats :runner :access :cards] (fnil inc 0)) (let [cost (steal-cost state side card) part-cost (partition 2 cost) - cost-strs (seq (map build-cost-str part-cost)) + cost-strs (build-cost-string cost) can-pay (can-pay? state side (make-eid state eid) card (:title card) cost) can-steal (can-steal? state side card) ; Access abilities are useless in the discard access-ab-cards (when-not (in-discard? card) (seq (filter #(can-trigger? state :runner (access-ab %) % [card]) (all-active state :runner)))) - ability-strs (mapv #(make-label (access-ab %)) access-ab-cards) + ability-strs (mapv access-ab-label access-ab-cards) ;; strs steal-str (when (and can-steal can-pay) - (if cost-strs + (if (not (string/blank? cost-strs)) ["Pay to steal"] ["Steal"])) no-action-str (when-not (= steal-str ["Steal"]) ["No action"]) - prompt-str (if cost-strs - (str " " (join-cost-strs (string/capitalize (first cost-strs)) (map lower-case (rest cost-strs))) " to steal?") + prompt-str (if (not (string/blank? cost-strs)) + (str " " cost-strs " to steal?") "") prompt-str (str "You accessed " (:title card) "." prompt-str) choices (vec (concat ability-strs steal-str no-action-str))] @@ -331,6 +329,14 @@ (when-let [reveal-fn (get-in cdef [:flags reveal-kw])] (reveal-fn state side (make-eid state) card nil)))) +(defn- join-cost-strs + [& costs] + (->> costs + flatten + (filter some?) + (interpose " and ") + (apply str))) + (defn msg-handle-access "Generate the message from the access" [state side {:keys [zone] :as card} title cost-msg] diff --git a/src/clj/game/utils.clj b/src/clj/game/utils.clj index 6525c6f998..5dbc699e2f 100644 --- a/src/clj/game/utils.clj +++ b/src/clj/game/utils.clj @@ -108,11 +108,6 @@ m) (dissoc m k))) -(defn make-label - "Looks into an ability for :label, if it doesn't find it, capitalizes :msg instead." - [ability] - (or (:label ability) (and (string? (:msg ability)) (capitalize (:msg ability))) "")) - (defn click-spent? "Returns true if player has spent at least one click" [side state] diff --git a/src/cljs/nr/gameboard.cljs b/src/cljs/nr/gameboard.cljs index a90fc213b3..c7690eb002 100644 --- a/src/cljs/nr/gameboard.cljs +++ b/src/cljs/nr/gameboard.cljs @@ -443,15 +443,6 @@ (let [cardinfo (-> @touchmove :card ((.-parse js/JSON)) (js->clj :keywordize-keys true))] (send-command "move" {:card cardinfo :server server}))))) -(defn ability-costs [ab] - (when-let [costs (:cost ab)] - (str (join ", " (for [[cost amount] (partition 2 costs) - :let [cost-symbol (str "[" (capitalize (name cost)) "]")]] - (case cost - "credit" (str amount " " cost-symbol) - (join "" (repeat amount cost-symbol))))) - ": "))) - (defn remote->num [server] (-> server str (clojure.string/split #":remote") last str->int)) @@ -582,7 +573,7 @@ [:div {:key i :on-click #(do (send-command "runner-ability" {:card card :ability i}))} - (render-icons (str (ability-costs ab) (:label ab)))]) + (render-icons (:label ab))]) runner-abilities) (when (> (count subroutines) 1) [:div {:on-click #(send-command "system-msg" @@ -603,7 +594,7 @@ (fn [i ab] [:div {:on-click #(do (send-command "corp-ability" {:card card :ability i}))} - (render-icons (str (ability-costs ab) (:label ab)))]) + (render-icons (:label ab))]) corp-abilities)]) (defn server-menu [card c-state remotes type zone] @@ -639,11 +630,11 @@ [:div {:key i :on-click #(do (send-command "dynamic-ability" (assoc (select-keys ab [:dynamic :source :index]) :card card)))} - (render-icons (str (ability-costs ab) (:label ab)))] + (render-icons (:label ab))] [:div {:key i :on-click #(do (send-command "ability" {:card card :ability (- i dynabi-count)}))} - (render-icons (str (ability-costs ab) (:label ab)))])) + (render-icons (:label ab))])) abilities) (map-indexed (fn [i sub] diff --git a/test/clj/game_test/cards/agendas.clj b/test/clj/game_test/cards/agendas.clj index 95fa6b6432..b87c248f86 100644 --- a/test/clj/game_test/cards/agendas.clj +++ b/test/clj/game_test/cards/agendas.clj @@ -1771,9 +1771,8 @@ (testing "basic test" (do-game (new-game {:corp {:deck ["Personality Profiles"]} - :runner {:deck ["Self-modifying Code" "Clone Chip" - "Corroder" (qty "Patron" 2)]}}) - (starting-hand state :runner ["Self-modifying Code" "Clone Chip" "Patron" "Patron"]) + :runner {:deck ["Corroder"] + :hand ["Self-modifying Code" "Clone Chip" (qty "Patron" 2)]}}) (play-and-score state "Personality Profiles") (take-credits state :corp) (play-from-hand state :runner "Self-modifying Code") @@ -1785,7 +1784,7 @@ (let [chip (get-hardware state 0)] (card-ability state :runner chip 0) (click-card state :runner (find-card "Self-modifying Code" (:discard (get-runner)))) - (is (second-last-log-contains? state "Patron") + (is (last-log-contains? state "Patron") "Personality Profiles trashed card name is in log") (is (= 3 (count (:discard (get-runner)))))))) (testing "Ensure effects still fire with an empty hand, #1840" diff --git a/test/clj/game_test/cards/assets.clj b/test/clj/game_test/cards/assets.clj index cb5a2071ef..79ead121eb 100644 --- a/test/clj/game_test/cards/assets.clj +++ b/test/clj/game_test/cards/assets.clj @@ -2128,11 +2128,11 @@ (deftest lily-lockwell ;; Lily Lockwell (do-game - (new-game {:corp {:deck ["Lily Lockwell" "Beanstalk Royalties" (qty "Fire Wall" 10)]}}) - (core/gain state :corp :click 10) - (starting-hand state :corp ["Lily Lockwell" "Beanstalk Royalties"]) + (new-game {:corp {:deck [(qty "Fire Wall" 10)] + :hand ["Lily Lockwell" "Beanstalk Royalties"] + :credits 10} + :runner {:tags 2}}) (play-from-hand state :corp "Lily Lockwell" "New remote") - (core/gain-tags state :runner 2) (let [lily (get-content state :remote1 0) clicks (:click (get-corp)) number-of-shuffles (count (core/turn-events state :corp :corp-shuffle-deck)) @@ -2141,15 +2141,15 @@ (is (= (+ 3 hand) (-> (get-corp) :hand count)) "Rezzing Lily Lockwell should draw 3 cards") (core/move state :corp (find-card "Beanstalk Royalties" (:hand (get-corp))) :deck) (card-ability state :corp (refresh lily) 0) - (click-prompt state :corp (find-card "Beanstalk Royalties" (-> (get-corp) :prompt first :choices))) + (click-prompt state :corp "Beanstalk Royalties") (is (= "Beanstalk Royalties" (-> (get-corp) :deck first :title)) "Beanstalk Royalties should be moved to top of R&D") (is (= 1 (count-tags state)) "Runner should have 1 tag from Lily Lockwell ability") (is (= (dec clicks) (:click (get-corp))) "Lily Lockwell ability should cost 1 click") (is (< number-of-shuffles (count (core/turn-events state :corp :corp-shuffle-deck))) "Corp should shuffle deck") (core/draw state :corp) (card-ability state :corp (refresh lily) 0) - (click-prompt state :corp "Cancel") - (is (last-log-contains? state "did not find") "Lily Lockwell's ability didn't find an operation") + (click-prompt state :corp "No action") + (is (last-log-contains? state "does not find") "Lily Lockwell's ability didn't find an operation") (is (zero? (count-tags state)) "Runner should have 0 tags from Lily Lockwell ability even when no operation found")))) (deftest long-term-investment @@ -3385,9 +3385,9 @@ iw (get-ice state :hq 0)] (core/rez state :corp ss) (core/rez state :corp iw) - (card-ability state :corp ss 0) (let [credits (:credit (get-corp)) clicks (:click (get-corp))] + (card-ability state :corp ss 0) (click-card state :corp iw) (is (= (+ credits 4) (:credit (get-corp))) "Corp should gain 4 from Security Subcontract ability") (is (= "Ice Wall" (-> (get-corp) :discard first :title)) "Ice Wall should be in Archives from Security Subcontract ability") diff --git a/test/clj/game_test/cards/events.clj b/test/clj/game_test/cards/events.clj index ea224b7415..66b457f5d9 100644 --- a/test/clj/game_test/cards/events.clj +++ b/test/clj/game_test/cards/events.clj @@ -623,7 +623,7 @@ (click-prompt state :runner "HQ") (is (= 4 (get-counters (find-card "Cold Read" (get-in @state [:runner :play-area])) :recurring)) "Cold Read has 4 counters") (run-successful state) - (click-prompt state :runner "[Imp]: Trash card") + (click-prompt state :runner "[Imp] Hosted virus counter: Trash card") (click-card state :runner (get-program state 0)) (is (= 2 (count (:discard (get-runner)))) "Imp and Cold Read in discard") ; Cold Read works when Blacklist rezzed - #2378 @@ -913,10 +913,10 @@ (is (= [:rd] (get-in @state [:run :server])) "Run initiated on R&D") (run-successful state) (click-prompt state :runner "Unrezzed upgrade in R&D") - (click-prompt state :runner "[Demolition Run]: Trash card") + (click-prompt state :runner "[Demolition Run] Trash card") (is (= 3 (:credit (get-runner))) "Trashed Shell Corporation at no cost") (click-prompt state :runner "Card from deck") - (click-prompt state :runner "[Demolition Run]: Trash card") + (click-prompt state :runner "[Demolition Run] Trash card") (is (zero? (:agenda-point (get-runner))) "Didn't steal False Lead") (is (= 2 (count (:discard (get-corp)))) "2 cards in Archives") (is (empty? (:prompt (get-runner))) "Run concluded"))) diff --git a/test/clj/game_test/cards/hardware.clj b/test/clj/game_test/cards/hardware.clj index 02e7f9996c..8890136b14 100644 --- a/test/clj/game_test/cards/hardware.clj +++ b/test/clj/game_test/cards/hardware.clj @@ -165,6 +165,23 @@ (is (= 6 (core/available-mu state))) (is (= 7 (hand-size :runner))))) +(deftest bookmark + ;; Bookmark + (do-game + (new-game {:corp {:deck [(qty "Hedge Fund" 10)]} + :runner {:hand ["Bookmark" "Sure Gamble" "Daily Casts" "Brain Chip"]}}) + (take-credits state :corp) + (play-from-hand state :runner "Bookmark") + (let [bm (get-hardware state 0)] + (card-ability state :runner bm 0) + (click-card state :runner "Sure Gamble") + (click-card state :runner "Daily Casts") + (click-card state :runner "Brain Chip") + (is (= 3 (count (:hosted (refresh bm)))) "Bookmark can host cards of any type") + (card-ability state :runner bm 2) + (is (nil? (refresh bm)) "Bookmark is now trashed") + (is (= 3 (count (:hand (get-runner)))) "Bookmark moved all hosted card into the grip")))) + (deftest brain-chip ;; Brain Chip handsize and memory limit (do-game @@ -216,22 +233,17 @@ (let [ds (get-program state 0)] (is (not (nil? ds))) (is (= (:title ds) "Datasucker")))))) - (testing "don't show inavalid choices" + (testing "don't show invalid choices" (do-game - (new-game {:runner {:deck ["Inti" "Magnum Opus" "Clone Chip"]}}) + (new-game {:runner {:deck ["Clone Chip"] + :discard ["Inti" "Magnum Opus"]}}) (take-credits state :corp) - (trash-from-hand state :runner "Inti") - (trash-from-hand state :runner "Magnum Opus") (play-from-hand state :runner "Clone Chip") (is (= (get-in @state [:runner :click]) 3) "Runner has 3 clicks left") (let [chip (get-hardware state 0)] (card-ability state :runner chip 0) (click-card state :runner (find-card "Magnum Opus" (:discard (get-runner)))) - (is (nil? (get-program state 0)) "No program was installed")) - (let [chip (get-hardware state 0)] - (is (not (nil? chip)) "Clone Chip is still installed") - (is (= (get-in @state [:runner :click]) 3) "Runner has 3 clicks left") - (card-ability state :runner chip 0) + (is (nil? (get-program state 0)) "No program was installed") (click-card state :runner (find-card "Inti" (:discard (get-runner)))) (let [inti (get-program state 0)] (is (not (nil? inti)) "Program was installed") @@ -363,7 +375,7 @@ (play-from-hand state :runner "Demolisher") (let [credits (:credit (get-runner))] (run-empty-server state :hq) - (click-prompt state :runner "[Imp]: Trash card") + (click-prompt state :runner "[Imp] Hosted virus counter: Trash card") (is (= (:credit (get-runner)) (+ 1 credits)) "Demolisher earns a credit when trashing with Imp"))))) (deftest desperado @@ -685,7 +697,7 @@ (click-card state :runner cache) (is (= 1 (count (:discard (get-runner)))) "Prevented 1 net damage") (is (= 2 (count (:hand (get-runner))))) - (is (second-last-log-contains? state "Runner uses Heartbeat to prevent 1 damage, trashing Cache\\.") "Prompt correct") + (is (second-last-log-contains? state "Runner trashes 1 installed card \\(Cache\\) to use Heartbeat to prevent 1 damage\\.")) (card-subroutine state :corp (refresh nk) 0) (is (= (-> (get-runner) :prompt first :msg) "Prevent any of the 3 net damage?") @@ -697,7 +709,7 @@ "Damage prevention message correct.") (click-prompt state :runner "Done") (is (= 4 (count (:discard (get-runner)))) "Prevented 1 of 3 net damage; used facedown card") - (is (last-n-log-contains? state 2 "Runner uses Heartbeat to prevent 1 damage, trashing a facedown Heartbeat\\.") "Prompt correct")))) + (is (last-n-log-contains? state 2 "Runner trashes 1 installed card \\(a facedown card\\) to use Heartbeat to prevent 1 damage\\."))))) (deftest hijacked-router ;; Hijacked Router @@ -947,7 +959,7 @@ counters (get-counters (refresh mache) :power) hand (-> (get-runner) :hand count)] (run-empty-server state :hq) - (click-prompt state :runner "[Imp]: Trash card") + (click-prompt state :runner "[Imp] Hosted virus counter: Trash card") (is (= counters (get-counters (refresh mache) :power)) "Mache should gain no counters from trashing a card with no trash cost") (run-empty-server state :remote1) (click-prompt state :runner "Pay 4 [Credits] to trash") diff --git a/test/clj/game_test/cards/identities.clj b/test/clj/game_test/cards/identities.clj index e7ee246b4b..c4db4dadcd 100644 --- a/test/clj/game_test/cards/identities.clj +++ b/test/clj/game_test/cards/identities.clj @@ -633,7 +633,7 @@ (take-credits state :corp) (play-from-hand state :runner "Cache") (run-empty-server state "HQ") - (click-prompt state :runner "[Freedom]: Trash card") + (click-prompt state :runner "[Freedom Khumalo] Trash card") (click-card state :runner (get-program state 0)) (click-card state :runner (get-program state 0)) (is (= 1 (count (:discard (get-corp)))) @@ -653,7 +653,7 @@ (play-from-hand state :runner "Cache") (run-empty-server state "HQ") (let [cost (->> (get-corp) :hand first :cost)] - (click-prompt state :runner "[Freedom]: Trash card") + (click-prompt state :runner "[Freedom Khumalo] Trash card") (when (pos? cost) (dotimes [_ cost] (click-card state :runner (get-program state 0)))) @@ -691,7 +691,7 @@ (take-credits state :runner) (take-credits state :corp) (run-empty-server state "HQ") - (click-prompt state :runner "[Freedom]: Trash card") + (click-prompt state :runner "[Freedom Khumalo] Trash card") (click-card state :runner (get-program state 0)) (click-card state :runner (get-resource state 0)) (is (= 1 (count (:discard (get-corp)))) @@ -710,7 +710,7 @@ (take-credits state :corp) (click-prompt state :runner "Yes") (run-empty-server state "HQ") - (click-prompt state :runner "[Freedom]: Trash card") + (click-prompt state :runner "[Freedom Khumalo] Trash card") (click-card state :runner (-> (refresh iw) :hosted first))) (is (= 1 (count (:discard (get-corp)))) "Accessed Ice Wall should be discarded after selecting 1 virus counter"))) (testing "Doesn't trigger when accessing an Agenda" @@ -743,13 +743,13 @@ (play-from-hand state :runner "Cache") (run-empty-server state "HQ") (is (= 3 (->> @state :runner :prompt first :choices count)) "Should have 3 choices: Freedom, Trash, No action") - (click-prompt state :runner "[Freedom]: Trash card") + (click-prompt state :runner "[Freedom Khumalo] Trash card") (click-card state :runner (get-program state 0)) (click-prompt state :runner "Done") (is (= 3 (-> @state :runner :prompt first :choices count)) (str "Should go back to access prompts, with 3 choices: Freedom, Trash, No action. " "Choices seen: " (-> @state :runner :prompt first :choices))) - (click-prompt state :runner "[Freedom]: Trash card") + (click-prompt state :runner "[Freedom Khumalo] Trash card") (click-card state :runner (get-program state 0)) (click-card state :runner (get-program state 0)) (is (= 1 (count (:discard (get-corp)))) "Card should now be properly discarded"))) @@ -762,7 +762,7 @@ (take-credits state :corp) (play-from-hand state :runner "Cache") (run-empty-server state "R&D") - (click-prompt state :runner "[Freedom]: Trash card") + (click-prompt state :runner "[Freedom Khumalo] Trash card") (click-card state :runner (get-program state 0)) (is (= 1 (count (:discard (get-corp)))) "Accessed Ice Wall should be discarded now") (is (not (:run @state)) "Run ended"))) @@ -777,7 +777,7 @@ (play-from-hand state :runner "Aumakua") (run-empty-server state "R&D") (is (zero? (get-counters (get-program state 1) :virus)) "Aumakuma shouldn't have any virus counters yet.") - (click-prompt state :runner "[Freedom]: Trash card") + (click-prompt state :runner "[Freedom Khumalo] Trash card") (click-card state :runner (get-program state 0)) (is (= 1 (count (:discard (get-corp)))) "Ice Wall should be discarded now") (is (zero? (get-counters (get-program state 1) :virus)) "Aumakua doesn't gain any virus counters from trash ability.") @@ -800,12 +800,12 @@ (run-empty-server state "Server 1") (is (= 6 (core/trash-cost state :runner (get-content state :remote1 0))) "The Board should cost 6 to trash") (is (= 3 (-> (get-runner) :prompt first :choices count)) "Runner can use Freedom or Imp to trash") - (click-prompt state :runner "[Freedom]: Trash card") + (click-prompt state :runner "[Freedom Khumalo] Trash card") (click-card state :runner (get-program state 0)) (click-prompt state :runner "Done") (is (= 6 (core/trash-cost state :runner (get-content state :remote1 0))) "Skulljack shouldn't trigger a second time") (is (= 3 (-> (get-runner) :prompt first :choices count)) "Runner can still use Freedom or Imp the second time around") - (click-prompt state :runner "[Imp]: Trash card") + (click-prompt state :runner "[Imp] Hosted virus counter: Trash card") (is (= 2 (:agenda-point (get-runner))) "Runner should trash The Board with Imp and gain 2 agenda points"))) (testing "Doesn't trigger when Cerberal Static installed" (do-game @@ -2447,8 +2447,7 @@ (is (zero? (get-counters (refresh scored) :agenda)) "Agenda counter used by Mark Yale") (is (= 10 (get-counters (refresh scored) :credit)) "Credits not used by Mark Yale") (card-ability state :corp my 1) - (click-card state :corp (refresh scored)) - (is (zero? (get-counters (refresh scored) :agenda)) "No agenda counter used by Mark Yale") + (is (empty? (:prompt (get-corp))) "No prompt for the Corp as no counters exist to spend") (is (= 10 (get-counters (refresh scored) :credit)) "Credits not used by Mark Yale")))))) (deftest weyland-consortium-because-we-built-it diff --git a/test/clj/game_test/cards/programs.clj b/test/clj/game_test/cards/programs.clj index 619fd97a3f..1bcb975cc3 100644 --- a/test/clj/game_test/cards/programs.clj +++ b/test/clj/game_test/cards/programs.clj @@ -1105,7 +1105,7 @@ (take-credits state :corp) (play-from-hand state :runner "Imp") (run-empty-server state "HQ") - (click-prompt state :runner "[Imp]: Trash card") + (click-prompt state :runner "[Imp] Hosted virus counter: Trash card") (is (= 1 (count (:discard (get-corp)))))))] (doall (map imp-test ["Hostile Takeover" @@ -1126,7 +1126,7 @@ (play-from-hand state :runner "Imp") (run-empty-server state :remote1) (click-prompt state :corp "Yes") - (click-prompt state :runner "[Imp]: Trash card") + (click-prompt state :runner "[Imp] Hosted virus counter: Trash card") (is (= 2 (- credits (:credit (get-corp)))) "Corp paid 2 for Prisec") (is (= 1 (- (count-tags state) tags)) "Runner has 1 tag") (is (= 2 (- grip (count (:hand (get-runner))))) "Runner took 1 meat damage") @@ -1143,7 +1143,7 @@ ;; Should access TFP at this point (click-prompt state :corp "1 [Credits]") (click-prompt state :runner "0 [Credits]") - (click-prompt state :runner "[Imp]: Trash card") + (click-prompt state :runner "[Imp] Hosted virus counter: Trash card") (take-credits state :runner) (is (= "The Future Perfect" (get-in @state [:corp :discard 0 :title])) "TFP trashed") (is (zero? (:agenda-point (get-runner))) "Runner did not steal TFP") @@ -1156,7 +1156,7 @@ (click-prompt state :corp "0 [Credits]") (click-prompt state :runner "0 [Credits]") ;; Fail psi game - (click-prompt state :runner "[Imp]: Trash card") + (click-prompt state :runner "[Imp] Hosted virus counter: Trash card") (is (= "The Future Perfect" (get-in @state [:corp :discard 0 :title])) "TFP trashed") (is (zero? (:agenda-point (get-runner))) "Runner did not steal TFP")))) (testing "vs cards in Archives" diff --git a/test/clj/game_test/cards/resources.clj b/test/clj/game_test/cards/resources.clj index 1cd63eca76..ca813e7c92 100644 --- a/test/clj/game_test/cards/resources.clj +++ b/test/clj/game_test/cards/resources.clj @@ -312,6 +312,19 @@ (deftest citadel-sanctuary ;; Citadel Sanctuary + (testing "basic test" + (do-game + (new-game {:corp {:deck [(qty "Hedge Fund" 5)] + :hand ["Scorched Earth"]} + :runner {:deck ["Citadel Sanctuary" (qty "Sure Gamble" 2)]}}) + (take-credits state :corp) + (play-from-hand state :runner "Citadel Sanctuary") + (take-credits state :runner) + (core/gain-tags state :runner 1) + (play-from-hand state :corp "Scorched Earth") + (is (zero? (count (:discard (get-runner)))) "No cards have been discarded or trashed yet") + (card-ability state :runner (get-resource state 0) 0) + (is (= 3 (count (:discard (get-runner)))) "CS and all cards in grip are trashed"))) (testing "Interaction with Corporate Grant and Thunder Art Gallery" (do-game (new-game {:runner {:deck ["Citadel Sanctuary" "Thunder Art Gallery" "Corroder" "Corporate \"Grant\""]}}) @@ -1961,7 +1974,7 @@ (card-ability state :runner (get-resource state 0) 0) (is (zero? (:click (get-runner))) "Should now have 0 clicks") (is (= 1 (count (:discard (get-runner)))) "Logic Bomb should be discarded") - (is (last-log-contains? state "uses Logic Bomb")) + (is (last-log-contains? state "use Logic Bomb")) (is (last-log-contains? state "\\[Click\\]\\[Click\\]") "Log should mention 2 clicks"))) (testing "if the runner has no clicks left" (do-game @@ -1978,7 +1991,7 @@ (card-ability state :runner (get-resource state 0) 0) (is (zero? (:click (get-runner))) "Should still have 0 clicks") (is (= 1 (count (:discard (get-runner)))) "Logic Bomb should be discarded") - (is (last-log-contains? state "uses Logic Bomb")) + (is (last-log-contains? state "use Logic Bomb")) (is (not (last-log-contains? state "\\[Click\\]")) "Log shouldn't mention any clicks")))) (deftest london-library @@ -2556,7 +2569,7 @@ (is (empty? (get-program state)) "No programs installed") (is (= 5 (:credit (get-runner))) "Runner starts with 5c.") (card-ability state :runner (get-resource state 0) 0) - (click-prompt state :runner (find-card "Clone Chip" (:hand (get-runner)))) + (click-card state :runner (find-card "Clone Chip" (:hand (get-runner)))) (click-prompt state :runner (find-card "Mimic" (:discard (get-runner)))) (is (= 1 (count (get-program state))) "1 Program installed") (is (= 2 (:credit (get-runner))) "Runner paid install cost"))) @@ -2575,7 +2588,7 @@ (is (empty? (get-program state)) "No programs installed") (is (= 5 (:credit (get-runner))) "Runner starts with 5c.") (card-ability state :runner (get-resource state 0) 0) - (click-prompt state :runner (find-card "Mimic" (:hand (get-runner)))) + (click-card state :runner (find-card "Mimic" (:hand (get-runner)))) (click-prompt state :runner (find-card "Mimic" (:discard (get-runner)))) (is (= 1 (count (get-program state))) "1 Program installed") (is (= 2 (:credit (get-runner))) "Runner paid install cost"))) @@ -2585,10 +2598,7 @@ (take-credits state :corp) (play-from-hand state :runner "Reclaim") (card-ability state :runner (get-resource state 0) 0) - (is (empty? (get-program state)) "No programs installed") - (is (= 5 (:credit (get-runner))) "Runner starts with 5c.") - (card-ability state :runner (get-resource state 0) 0) - (click-prompt state :runner (find-card "Alpha" (:hand (get-runner)))) + (click-card state :runner (find-card "Alpha" (:hand (get-runner)))) (is (empty? (get-program state)) "Did not install program") (is (= 5 (:credit (get-runner))) "Runner did not spend credits")))) @@ -2628,22 +2638,24 @@ (deftest rosetta-2-0 ;; Rosetta 2.0 remove an installed program from the game and install one from the heap lower install cost (do-game - (new-game {:runner {:deck ["Rosetta 2.0" "Corroder" "Gordian Blade"]}}) + (new-game {:runner {:deck ["Gordian Blade"] + :hand ["Rosetta 2.0" "Corroder"] + :credits 10}}) (take-credits state :corp) - (starting-hand state :runner ["Rosetta 2.0" "Corroder"]) - (core/gain state :runner :credit 2) (play-from-hand state :runner "Rosetta 2.0") (play-from-hand state :runner "Corroder") - (is (= 3 (core/available-mu state)) "Corrder cost 1 mu") - (is (= 2 (:credit (get-runner))) "Starting with 2 credits") - (card-ability state :runner (get-resource state 0) 0) - (click-card state :runner (get-program state 0)) - (click-prompt state :runner (find-card "Gordian Blade" (:deck (get-runner)))) - (is (= 3 (core/available-mu state)) "Gordian cost 1 mu, Corroder freed") - (is (zero? (:credit (get-runner))) "Ending with 0 credits") - (is (= 1 (count (:rfg (get-runner)))) "Corroder removed from game") - (is (= 1 (count (get-program state))) "One program installed") - (is (= "Gordian Blade" (:title (get-program state 0))) "Gordian installed"))) + (let [rosetta (get-resource state 0) + corroder (get-program state 0)] + (is (= 3 (core/available-mu state)) "Corrder cost 1 mu") + (is (= 5 (:credit (get-runner))) "Starting with 5 credits") + (card-ability state :runner rosetta 0) + (click-prompt state :runner (find-card "Gordian Blade" (:deck (get-runner)))) + (click-card state :runner corroder) + (is (= 3 (core/available-mu state)) "Gordian cost 1 mu, Corroder freed") + (is (= 3 (:credit (get-runner))) "Ending with 3 credits") + (is (= 1 (count (:rfg (get-runner)))) "Corroder removed from game") + (is (= 1 (count (get-program state))) "One program installed") + (is (= "Gordian Blade" (:title (get-program state 0))) "Gordian installed")))) (deftest sacrificial-construct ;; Sacrificial Construct - Trash to prevent trash of installed program or hardware @@ -2704,7 +2716,7 @@ (core/rez state :corp hostile) (run-empty-server state "Server 1") (is (seq (:prompt (get-runner))) "Prompting to trash.") - (click-prompt state :runner "[Salsette Slums]: Remove card from game") + (click-prompt state :runner "[Salsette Slums] Remove card from game") (is (empty? (:prompt (get-runner))) "All prompts done") (is (= 3 (count (:hand (get-runner)))) "On-trash ability of other Hostile didn't fire") (is (= (:cid ts) (:cid (last (:rfg (get-corp))))) "Tech Startup was removed from game") @@ -2717,9 +2729,9 @@ (click-prompt state :runner "No action") (run-empty-server state :remote3) (is (seq (:prompt (get-runner))) "Prompting to trash") - (is (= ["[Salsette Slums]: Remove card from game" "Pay 1 [Credits] to trash" "No action"] + (is (= ["[Salsette Slums] Remove card from game" "Pay 1 [Credits] to trash" "No action"] (->> (get-runner) :prompt first :choices)) "Second Salsette Slums can be used") - (click-prompt state :runner "[Salsette Slums]: Remove card from game") + (click-prompt state :runner "[Salsette Slums] Remove card from game") (is (= 2 (count (:rfg (get-corp)))) "Two cards should be RFG now")))) (deftest scrubber diff --git a/test/clj/game_test/cards/upgrades.clj b/test/clj/game_test/cards/upgrades.clj index 220528b464..dadcec58c8 100644 --- a/test/clj/game_test/cards/upgrades.clj +++ b/test/clj/game_test/cards/upgrades.clj @@ -1242,100 +1242,100 @@ (deftest mumbad-virtual-tour ;; Tests that Mumbad Virtual Tour forces trash - ; (do-game - ; (new-game {:corp {:deck [(qty "Mumbad Virtual Tour" 2)]}}) - ; (play-from-hand state :corp "Mumbad Virtual Tour" "New remote") - ; (take-credits state :corp) - ; (run-empty-server state "HQ") - ; ;; MVT does not force trash when not installed - ; (click-prompt state :runner "No action") - ; (is (= 5 (:credit (get-runner))) "Runner not forced to trash MVT in HQ") - ; (is (empty? (:discard (get-corp))) "MVT in HQ is not trashed") - ; (run-empty-server state "Server 1") - ; (is (= 1 (-> @state :runner :prompt first :choices count)) "Should only have a single option") - ; (click-prompt state :runner "Pay 5 [Credits] to trash") - ; (is (zero? (:credit (get-runner))) "Runner forced to trash MVT") - ; (is (= "Mumbad Virtual Tour" (:title (first (:discard (get-corp))))) "MVT trashed")) - ; (testing "interaction with Imp" - ; (do-game - ; (new-game {:corp {:deck [(qty "Mumbad Virtual Tour" 2)]} - ; :runner {:deck ["Imp"]}}) - ; (play-from-hand state :corp "Mumbad Virtual Tour" "New remote") - ; (play-from-hand state :corp "Mumbad Virtual Tour" "New remote") - ; (take-credits state :corp) - ; (play-from-hand state :runner "Imp") - ; ;; Reset credits to 5 - ; (core/gain state :runner :credit 2) - ; (run-empty-server state "Server 1") - ; ;; Runner not force to trash since Imp is installed - ; (is (= 2 (-> @state :runner :prompt first :choices count)) "Runner has 2 choices when Imp is installed") - ; (is (= 5 (:credit (get-runner))) "Runner not forced to trash MVT when Imp installed") - ; (is (empty? (:discard (get-corp))) "MVT is not force-trashed when Imp installed") - ; (let [imp (get-program state 0)] - ; (click-prompt state :runner "[Imp]: Trash card") - ; (is (= "Mumbad Virtual Tour" (:title (first (:discard (get-corp))))) "MVT trashed with Imp")))) - ; (testing "interactions with Imp and various amounts of money" - ; (do-game - ; (new-game {:corp {:deck [(qty "Mumbad Virtual Tour" 3)]} - ; :runner {:deck ["Imp"]}}) - ; (play-from-hand state :corp "Mumbad Virtual Tour" "New remote") - ; (take-credits state :corp) - ; (play-from-hand state :runner "Imp") - ; (is (= 3 (:credit (get-runner))) "Runner paid install costs") - ; (core/gain state :runner :credit 2) - ; (run-empty-server state "Server 1") - ; (is (= #{"[Imp]: Trash card" "Pay 5 [Credits] to trash"} - ; (->> (get-runner) :prompt first :choices (into #{}))) "Should have Imp and MVT options") - ; (click-prompt state :runner "[Imp]: Trash card") - ; (take-credits state :runner) - ; (core/lose state :runner :credit (:credit (get-runner))) - ; (play-from-hand state :corp "Mumbad Virtual Tour" "New remote") - ; (take-credits state :corp) - ; (run-empty-server state "Server 2") - ; (is (= ["[Imp]: Trash card"] (-> (get-runner) :prompt first :choices)) "Should only have Imp option") - ; (click-prompt state :runner "[Imp]: Trash card") - ; (take-credits state :runner) - ; (core/lose state :runner :credit (:credit (get-runner))) - ; (play-from-hand state :corp "Mumbad Virtual Tour" "New remote") - ; (take-credits state :corp) - ; (run-empty-server state "Server 3") - ; (is (= ["No action"] (-> (get-runner) :prompt first :choices)) "Should only have no action option") - ; (click-prompt state :runner "No action") - ; (is (= 2 (->> (get-corp) :discard count)) "Runner was not forced to trash MVT"))) - ; (testing "not forced to trash when credits below 5" - ; (do-game - ; (new-game {:corp {:deck [(qty "Mumbad Virtual Tour" 3)]} - ; :runner {:deck ["Daily Casts"]}}) - ; (play-from-hand state :corp "Mumbad Virtual Tour" "New remote") - ; (take-credits state :corp) - ; (play-from-hand state :runner "Daily Casts") - ; (is (= 2 (:credit (get-runner))) "Runner paid install costs") - ; (run-empty-server state "Server 1") - ; (is (= ["No action"] (-> (get-runner) :prompt first :choices)) "Runner is not given the choice"))) - ; (testing "forced to trash when playing as Khumalo" - ; (do-game - ; (new-game {:corp {:deck [(qty "Mumbad Virtual Tour" 3)]} - ; :runner {:id "Freedom Khumalo: Crypto-Anarchist" - ; :deck ["Daily Casts"]}}) - ; (play-from-hand state :corp "Mumbad Virtual Tour" "New remote") - ; (take-credits state :corp) - ; (play-from-hand state :runner "Daily Casts") - ; (is (= 2 (:credit (get-runner))) "Runner paid install costs") - ; (run-empty-server state "Server 1") - ; (is (= ["[Freedom]: Trash card"] (-> (get-runner) :prompt first :choices)) "Runner is not given the choice"))) - ; (testing "forced to trash after playing Demolition Run" - ; (do-game - ; (new-game {:corp {:deck [(qty "Hedge Fund" 5)] - ; :hand ["Mumbad Virtual Tour"]} - ; :runner {:hand ["Demolition Run"]}}) - ; (play-from-hand state :corp "Mumbad Virtual Tour" "R&D") - ; (take-credits state :corp) - ; (play-from-hand state :runner "Demolition Run") - ; (is (= 3 (:credit (get-runner))) "Runner paid play costs") - ; (click-prompt state :runner "R&D") - ; (run-successful state) - ; (click-prompt state :runner "Unrezzed upgrade in R&D") - ; (is (= ["[Demolition Run]: Trash card"] (-> (get-runner) :prompt first :choices)) "Runner is not given the choice"))) + (do-game + (new-game {:corp {:deck [(qty "Mumbad Virtual Tour" 2)]}}) + (play-from-hand state :corp "Mumbad Virtual Tour" "New remote") + (take-credits state :corp) + (run-empty-server state "HQ") + ;; MVT does not force trash when not installed + (click-prompt state :runner "No action") + (is (= 5 (:credit (get-runner))) "Runner not forced to trash MVT in HQ") + (is (empty? (:discard (get-corp))) "MVT in HQ is not trashed") + (run-empty-server state "Server 1") + (is (= 1 (-> @state :runner :prompt first :choices count)) "Should only have a single option") + (click-prompt state :runner "Pay 5 [Credits] to trash") + (is (zero? (:credit (get-runner))) "Runner forced to trash MVT") + (is (= "Mumbad Virtual Tour" (:title (first (:discard (get-corp))))) "MVT trashed")) + (testing "interaction with Imp" + (do-game + (new-game {:corp {:deck [(qty "Mumbad Virtual Tour" 2)]} + :runner {:deck ["Imp"]}}) + (play-from-hand state :corp "Mumbad Virtual Tour" "New remote") + (play-from-hand state :corp "Mumbad Virtual Tour" "New remote") + (take-credits state :corp) + (play-from-hand state :runner "Imp") + ;; Reset credits to 5 + (core/gain state :runner :credit 2) + (run-empty-server state "Server 1") + ;; Runner not force to trash since Imp is installed + (is (= 2 (-> @state :runner :prompt first :choices count)) "Runner has 2 choices when Imp is installed") + (is (= 5 (:credit (get-runner))) "Runner not forced to trash MVT when Imp installed") + (is (empty? (:discard (get-corp))) "MVT is not force-trashed when Imp installed") + (let [imp (get-program state 0)] + (click-prompt state :runner "[Imp] Hosted virus counter: Trash card") + (is (= "Mumbad Virtual Tour" (:title (first (:discard (get-corp))))) "MVT trashed with Imp")))) + (testing "interactions with Imp and various amounts of money" + (do-game + (new-game {:corp {:deck [(qty "Mumbad Virtual Tour" 3)]} + :runner {:deck ["Imp"]}}) + (play-from-hand state :corp "Mumbad Virtual Tour" "New remote") + (take-credits state :corp) + (play-from-hand state :runner "Imp") + (is (= 3 (:credit (get-runner))) "Runner paid install costs") + (core/gain state :runner :credit 2) + (run-empty-server state "Server 1") + (is (= #{"[Imp] Hosted virus counter: Trash card" "Pay 5 [Credits] to trash"} + (->> (get-runner) :prompt first :choices (into #{}))) "Should have Imp and MVT options") + (click-prompt state :runner "[Imp] Hosted virus counter: Trash card") + (take-credits state :runner) + (core/lose state :runner :credit (:credit (get-runner))) + (play-from-hand state :corp "Mumbad Virtual Tour" "New remote") + (take-credits state :corp) + (run-empty-server state "Server 2") + (is (= ["[Imp] Hosted virus counter: Trash card"] (-> (get-runner) :prompt first :choices)) "Should only have Imp option") + (click-prompt state :runner "[Imp] Hosted virus counter: Trash card") + (take-credits state :runner) + (core/lose state :runner :credit (:credit (get-runner))) + (play-from-hand state :corp "Mumbad Virtual Tour" "New remote") + (take-credits state :corp) + (run-empty-server state "Server 3") + (is (= ["No action"] (-> (get-runner) :prompt first :choices)) "Should only have no action option") + (click-prompt state :runner "No action") + (is (= 2 (->> (get-corp) :discard count)) "Runner was not forced to trash MVT"))) + (testing "not forced to trash when credits below 5" + (do-game + (new-game {:corp {:deck [(qty "Mumbad Virtual Tour" 3)]} + :runner {:deck ["Daily Casts"]}}) + (play-from-hand state :corp "Mumbad Virtual Tour" "New remote") + (take-credits state :corp) + (play-from-hand state :runner "Daily Casts") + (is (= 2 (:credit (get-runner))) "Runner paid install costs") + (run-empty-server state "Server 1") + (is (= ["No action"] (-> (get-runner) :prompt first :choices)) "Runner is not given the choice"))) + (testing "forced to trash when playing as Khumalo" + (do-game + (new-game {:corp {:deck [(qty "Mumbad Virtual Tour" 3)]} + :runner {:id "Freedom Khumalo: Crypto-Anarchist" + :deck ["Daily Casts"]}}) + (play-from-hand state :corp "Mumbad Virtual Tour" "New remote") + (take-credits state :corp) + (play-from-hand state :runner "Daily Casts") + (is (= 2 (:credit (get-runner))) "Runner paid install costs") + (run-empty-server state "Server 1") + (is (= ["[Freedom Khumalo] Trash card"] (-> (get-runner) :prompt first :choices)) "Runner is not given the choice"))) + (testing "forced to trash after playing Demolition Run" + (do-game + (new-game {:corp {:deck [(qty "Hedge Fund" 5)] + :hand ["Mumbad Virtual Tour"]} + :runner {:hand ["Demolition Run"]}}) + (play-from-hand state :corp "Mumbad Virtual Tour" "R&D") + (take-credits state :corp) + (play-from-hand state :runner "Demolition Run") + (is (= 3 (:credit (get-runner))) "Runner paid play costs") + (click-prompt state :runner "R&D") + (run-successful state) + (click-prompt state :runner "Unrezzed upgrade in R&D") + (is (= ["[Demolition Run] Trash card"] (-> (get-runner) :prompt first :choices)) "Runner is not given the choice"))) (testing "not to trash after installing Salsette Slums" (do-game (new-game {:corp {:deck [(qty "Hedge Fund" 5)] diff --git a/test/clj/game_test/engine/rules.clj b/test/clj/game_test/engine/rules.clj index 4344eec986..f4bfb45e0a 100644 --- a/test/clj/game_test/engine/rules.clj +++ b/test/clj/game_test/engine/rules.clj @@ -246,7 +246,7 @@ (play-from-hand state :runner "Imp") (let [imp (get-program state 0)] (run-empty-server state "HQ") - (click-prompt state :runner "[Imp]: Trash card") + (click-prompt state :runner "[Imp] Hosted virus counter: Trash card") (is (= 1 (count (:discard (get-corp)))) "Accessed Hedge Fund is trashed") (run-empty-server state "HQ") (click-prompt state :runner "No action") @@ -257,7 +257,7 @@ (let [imp (get-program state 0)] (is (= 2 (get-counters (refresh imp) :virus)) "Reinstalled Imp has 2 counters") (run-empty-server state "HQ") - (click-prompt state :runner "[Imp]: Trash card")) + (click-prompt state :runner "[Imp] Hosted virus counter: Trash card")) (is (= 2 (count (:discard (get-corp)))) "Hedge Fund trashed, reinstalled Imp used on same turn"))) (deftest trash-seen-and-unseen