diff --git a/src/clj/game/cards/ice.clj b/src/clj/game/cards/ice.clj index f95bc6eebb..7fefc41b51 100644 --- a/src/clj/game/cards/ice.clj +++ b/src/clj/game/cards/ice.clj @@ -2666,15 +2666,11 @@ (defcard "Lycian Multi-Munition" (letfn [(ice-subtype-choice [choices] {:prompt "Choose an ice subtype" - :waiting-prompt true :choices choices :async true :effect (req (if (= target "Done") (effect-completed state side eid) - (let [new-choices (->> choices - (remove #{target}) - (cons "Done") - distinct)] + (let [new-choices (remove #{target} choices)] ;; note this is a lingering ability and persists so ;; long as the card is rezzed ;; if the card is hushed, it will not derez, so the subtypes will stay! @@ -2691,7 +2687,8 @@ (ice-subtype-choice new-choices) card nil))))})] {:on-rez {:async true - :effect (effect (continue-ability (ice-subtype-choice ["Barrier" "Code Gate" "Sentry"]) card nil))} + :waiting-prompt true + :effect (effect (continue-ability (ice-subtype-choice ["Barrier" "Code Gate" "Sentry" "Done"]) card nil))} :derez-effect {:effect (req (unregister-effects-for-card state side card #(= :gain-subtype (:type %))))} :static-abilities [{:type :gain-subtype :req (req (and (same-card? card target) (:subtype-target card))) diff --git a/src/clj/game/core/access.clj b/src/clj/game/core/access.clj index 578526d0c0..1e0a3768b1 100644 --- a/src/clj/game/core/access.clj +++ b/src/clj/game/core/access.clj @@ -137,19 +137,20 @@ ; Pay credits (from pool or cards) to trash (= target (first trash-cost-str)) - (wait-for (pay state side (make-eid state trash-eid) card [(->c :credit trash-cost)]) - (when (:breach @state) - (swap! state assoc-in [:breach :did-trash] true)) - (when (:run @state) - (swap! state assoc-in [:run :did-trash] true) - (when must-trash? - (swap! state assoc-in [:run :did-access] true))) - (swap! state assoc-in [:runner :register :trashed-card] true) - (system-msg state side (str (:msg async-result) " to trash " - (:title card) " from " - (name-zone :corp (get-zone card)))) - (wait-for (trash state side card {:accessed true}) - (access-end state side eid (first async-result) {:trashed true}))) + (let [card (update! state side (assoc c :seen true))] + (wait-for (pay state side (make-eid state trash-eid) card [(->c :credit trash-cost)]) + (when (:breach @state) + (swap! state assoc-in [:breach :did-trash] true)) + (when (:run @state) + (swap! state assoc-in [:run :did-trash] true) + (when must-trash? + (swap! state assoc-in [:run :did-access] true))) + (swap! state assoc-in [:runner :register :trashed-card] true) + (system-msg state side (str (:msg async-result) " to trash " + (:title card) " from " + (name-zone :corp (get-zone card)))) + (wait-for (trash state side card {:accessed true}) + (access-end state side eid (first async-result) {:trashed true})))) ; Use access ability (find-first #(same-card? % target) access-ab-cards) diff --git a/src/clj/game/core/actions.clj b/src/clj/game/core/actions.clj index c57f515ccf..a71bc010f2 100644 --- a/src/clj/game/core/actions.clj +++ b/src/clj/game/core/actions.clj @@ -244,7 +244,6 @@ (sort-by #(-> % first :auto-pump-sort)) (apply min-key #(let [costs (second %)] (reduce (fnil + 0 0) 0 (keep :cost/amount costs))))) - cost-req (or (:cost-req pump-ability) identity) pump-strength (get-pump-strength state side pump-ability card) strength-diff (when (and current-ice (get-strength current-ice) @@ -257,7 +256,7 @@ 0) total-pump-cost (when (and pump-ability times-pump) - (repeat times-pump (cost-req pump-cost)))] + (repeat times-pump pump-cost))] (when (can-pay? state side eid card (:title card) total-pump-cost) (wait-for (pay state side (make-eid state eid) card total-pump-cost) (dotimes [_ times-pump] @@ -386,7 +385,6 @@ (sort-by #(-> % first :auto-pump-sort)) (apply min-key #(let [costs (second %)] (reduce (fnil + 0 0) 0 (mapv :cost/amount costs))))) - pump-cost-req (or (:cost-req pump-ability) identity) pump-strength (get-pump-strength state side pump-ability card) strength-diff (when (and current-ice (get-strength current-ice) @@ -399,7 +397,7 @@ 0) total-pump-cost (when (and pump-ability times-pump) - (repeat times-pump (pump-cost-req [pump-cost]))) + (repeat times-pump pump-cost)) ;; break all subs can-break (fn [ability] (when (and (:break-req ability) @@ -413,7 +411,6 @@ (sort-by #(-> % first :auto-break-sort)) (apply min-key #(let [costs (second %)] (reduce (fnil + 0 0) 0 (mapv :cost/amount costs))))) - break-cost-req (or (:cost-req break-ability) identity) subs-broken-at-once (when break-ability (:break break-ability 1)) unbroken-subs (when (:subroutines current-ice) @@ -426,7 +423,7 @@ 1)) total-break-cost (when (and break-cost times-break) - (repeat times-break (break-cost-req [break-cost]))) + (repeat times-break break-cost)) total-cost (merge-costs (conj total-pump-cost total-break-cost))] (when (and break-ability (can-pay? state side eid card (:title card) total-cost)) diff --git a/src/clj/game/core/costs.clj b/src/clj/game/core/costs.clj index 64178e9111..17352ed0d0 100644 --- a/src/clj/game/core/costs.clj +++ b/src/clj/game/core/costs.clj @@ -46,9 +46,9 @@ ;; and so we can look through the events and figure out WHICH abilities were used ;; I don't think it will break anything (swap! state assoc-in [side :register :spent-click] true) - (complete-with-result state side eid {:msg (str "spends " (label cost)) - :type :click - :value (value cost)})))) + (complete-with-result state side eid {:paid/msg (str "spends " (label cost)) + :paid/type :click + :paid/value (value cost)})))) ;; Lose Click (defn lose-click-label @@ -70,9 +70,9 @@ (if (= side :corp) :corp-spent-click :runner-spent-click) nil (value cost)) (swap! state assoc-in [side :register :spent-click] true) - (complete-with-result state side eid {:msg (str "loses " (lose-click-label cost)) - :type :lose-click - :value (value cost)}))) + (complete-with-result state side eid {:paid/msg (str "loses " (lose-click-label cost)) + :paid/type :lose-click + :paid/value (value cost)}))) (defn- all-active-pay-credit-cards @@ -144,10 +144,10 @@ (value cost)) (swap! state update-in [:stats side :spent :credit] (fnil + 0) (value cost)) (complete-with-result state side eid - {:msg (str "pays " (:msg pay-async-result)) - :type :credit - :value (:number pay-async-result) - :targets (:targets pay-async-result)})))) + {:paid/msg (str "pays " (:msg pay-async-result)) + :paid/type :credit + :paid/value (:number pay-async-result) + :paid/targets (:targets pay-async-result)})))) (pos? (value cost)) (do (lose state side :credit (value cost)) (wait-for (trigger-event-sync @@ -155,13 +155,13 @@ (if (= side :corp) :corp-spent-credits :runner-spent-credits) (value cost)) (swap! state update-in [:stats side :spent :credit] (fnil + 0) (value cost)) - (complete-with-result state side eid {:msg (str "pays " (value cost) " [Credits]") - :type :credit - :value (value cost)}))) + (complete-with-result state side eid {:paid/msg (str "pays " (value cost) " [Credits]") + :paid/type :credit + :paid/value (value cost)}))) :else - (complete-with-result state side eid {:msg (str "pays 0 [Credits]") - :type :credit - :value 0})))) + (complete-with-result state side eid {:paid/msg "pays 0 [Credits]" + :paid/type :credit + :paid/value 0})))) ;; X Credits (defmethod value :x-credits [_] 0) @@ -189,10 +189,10 @@ (pos? (count (provider-func)))) (wait-for (resolve-ability state side (pick-credit-providing-cards provider-func eid cost stealth-value) card nil) (swap! state update-in [:stats side :spent :credit] (fnil + 0) cost) - (complete-with-result state side eid {:msg (str "pays " (:msg async-result)) - :type :x-credits - :value (:number async-result) - :targets (:targets async-result)})) + (complete-with-result state side eid {:paid/msg (str "pays " (:msg async-result)) + :paid/type :x-credits + :paid/value (:number async-result) + :paid/targets (:targets async-result)})) (pos? cost) (do (lose state side :credit cost) (wait-for (trigger-event-sync @@ -200,13 +200,13 @@ (if (= side :corp) :corp-spent-credits :runner-spent-credits) cost) (swap! state update-in [:stats side :spent :credit] (fnil + 0) cost) - (complete-with-result state side eid {:msg (str "pays " cost " [Credits]") - :type :x-credits - :value cost}))) + (complete-with-result state side eid {:paid/msg (str "pays " cost " [Credits]") + :paid/type :x-credits + :paid/value cost}))) :else - (complete-with-result state side eid {:msg (str "pays 0 [Credits]") - :type :x-credits - :value 0}))))} + (complete-with-result state side eid {:paid/msg (str "pays 0 [Credits]") + :paid/type :x-credits + :paid/value 0}))))} card nil)) ;; Expend Helper - this is a dummy cost just for cost strings @@ -221,10 +221,10 @@ (wait-for (trash state :corp (make-eid state eid) (assoc (get-card state card) :seen true)) (complete-with-result state side eid - {:msg (str "trashes " (:title card) " from HQ") - :type :expend - :value 1 - :targets [card]})))) + {:paid/msg (str "trashes " (:title card) " from HQ") + :paid/type :expend + :paid/value 1 + :paid/targets [card]})))) ;; Trash (defmethod value :trash-can [cost] 1) @@ -236,10 +236,10 @@ [cost state side eid card] (wait-for (trash state side card {:cause :ability-cost :unpreventable true}) - (complete-with-result state side eid {:msg (str "trashes " (:title card)) - :type :trash-can - :value 1 - :targets [card]}))) + (complete-with-result state side eid {:paid/msg (str "trashes " (:title card)) + :paid/type :trash-can + :paid/value 1 + :paid/targets [card]}))) ;; Forfeit (defmethod value :forfeit [cost] (:cost/amount cost)) @@ -266,11 +266,11 @@ (wait-for (checkpoint state nil (make-eid state eid) {:durations [:game-trash]}) (complete-with-result state side eid - {:msg (str "forfeits " (quantify (value cost) "agenda") - " (" (enumerate-str (map :title targets)) ")") - :type :forfeit - :value (value cost) - :targets targets})))} + {:paid/msg (str "forfeits " (quantify (value cost) "agenda") + " (" (enumerate-str (map :title targets)) ")") + :paid/type :forfeit + :paid/value (value cost) + :paid/targets targets})))} card nil)) ;; ForfeitSelf @@ -284,10 +284,10 @@ (wait-for (forfeit state side (make-eid state eid) card {:msg false}) (complete-with-result state side eid - {:msg (str "forfeits " (:title card)) - :type :forfeit-self - :value 1 - :targets [card]}))) + {:paid/msg (str "forfeits " (:title card)) + :paid/type :forfeit-self + :paid/value 1 + :paid/targets [card]}))) ;; Gain tag @@ -301,9 +301,9 @@ (defmethod handler :gain-tag [cost state side eid card] (wait-for (gain-tags state side (value cost)) - (complete-with-result state side eid {:msg (str "takes " (quantify (value cost) "tag")) - :type :gain-tag - :value (value cost)}))) + (complete-with-result state side eid {:paid/msg (str "takes " (quantify (value cost) "tag")) + :paid/type :gain-tag + :paid/value (value cost)}))) ;; Tag (defmethod value :tag [cost] (:cost/amount cost)) @@ -314,9 +314,9 @@ (defmethod handler :tag [cost state side eid card] (wait-for (lose-tags state side (value cost)) - (complete-with-result state side eid {:msg (str "removes " (quantify (value cost) "tag")) - :type :tag - :value (value cost)}))) + (complete-with-result state side eid {:paid/msg (str "removes " (quantify (value cost) "tag")) + :paid/type :tag + :paid/value (value cost)}))) ;; Tag-or-bad-pub (defmethod value :tag-or-bad-pub [cost] (:cost/amount cost)) @@ -328,9 +328,9 @@ [cost state side eid card] (if-not (<= 0 (- (get-in @state [:runner :tag :base] 0) (value cost))) (wait-for (gain-bad-publicity state side (make-eid state eid) (value cost) nil) - (complete-with-result state side eid {:msg (str "gains " (value cost) " bad publicity") - :type :tag-or-bad-pub - :value (value cost)})) + (complete-with-result state side eid {:paid/msg (str "gains " (value cost) " bad publicity") + :paid/type :tag-or-bad-pub + :paid/value (value cost)})) (continue-ability state side {:prompt "Choose one" @@ -339,13 +339,13 @@ :async true :effect (req (if (= target (str "Gain " (value cost) " bad publicity")) (wait-for (gain-bad-publicity state side (make-eid state eid) (value cost) nil) - (complete-with-result state side eid {:msg (str "gains " (value cost) " bad publicity") - :type :tag-or-bad-pub - :value (value cost)})) + (complete-with-result state side eid {:paid/msg (str "gains " (value cost) " bad publicity") + :paid/type :tag-or-bad-pub + :paid/value (value cost)})) (wait-for (lose-tags state side (value cost)) - (complete-with-result state side eid {:msg (str "removes " (quantify (value cost) "tag")) - :type :tag-or-bad-pub - :value (value cost)}))))} + (complete-with-result state side eid {:paid/msg (str "removes " (quantify (value cost) "tag")) + :paid/type :tag-or-bad-pub + :paid/value (value cost)}))))} card nil))) ;; ReturnToHand @@ -359,11 +359,11 @@ (move state side card :hand) (complete-with-result state side eid - {:msg (str "returns " (:title card) - " to " (if (= :corp side) "HQ" "their grip")) - :type :return-to-hand - :value 1 - :targets [card]})) + {:paid/msg (str "returns " (:title card) + " to " (if (= :corp side) "HQ" "their grip")) + :paid/type :return-to-hand + :paid/value 1 + :paid/targets [card]})) ;; RemoveFromGame (defmethod value :remove-from-game [cost] 1) @@ -376,10 +376,10 @@ (move state side card :rfg) (complete-with-result state side eid - {:msg (str "removes " (:title card) " from the game") - :type :remove-from-game - :value 1 - :targets [card]})) + {:paid/msg (str "removes " (:title card) " from the game") + :paid/type :remove-from-game + :paid/value 1 + :paid/targets [card]})) ;; RfgProgram (defmethod value :rfg-program [cost] (:cost/amount cost)) @@ -403,12 +403,12 @@ (move state side (assoc-in t [:persistent :from-cid] (:cid card)) :rfg)) (complete-with-result state side eid - {:msg (str "removes " (quantify (value cost) "installed program") - " from the game" - " (" (enumerate-str (map #(card-str state %) targets)) ")") - :type :rfg-program - :value (value cost) - :targets targets}))} + {:paid/msg (str "removes " (quantify (value cost) "installed program") + " from the game" + " (" (enumerate-str (map #(card-str state %) targets)) ")") + :paid/type :rfg-program + :paid/value (value cost) + :paid/targets targets}))} card nil)) ;; TrashOtherInstalledCard - this may NOT target the source card (itself), use :trash-installed instead @@ -435,11 +435,11 @@ :unpreventable true}) (complete-with-result state side eid - {:msg (str "trashes " (quantify (count async-result) "installed card") - " (" (enumerate-str (map #(card-str state %) targets)) ")") - :type :trash-other-installed - :value (count async-result) - :targets targets})))} + {:paid/msg (str "trashes " (quantify (count async-result) "installed card") + " (" (enumerate-str (map #(card-str state %) targets)) ")") + :paid/type :trash-other-installed + :paid/value (count async-result) + :paid/targets targets})))} card nil)) ;; TrashInstalledCard - this may target the source card (itself) @@ -465,11 +465,11 @@ :unpreventable true}) (complete-with-result state side eid - {:msg (str "trashes " (quantify (count async-result) "installed card") - " (" (enumerate-str (map #(card-str state %) targets)) ")") - :type :trash-installed - :value (count async-result) - :targets targets})))} + {:paid/msg (str "trashes " (quantify (count async-result) "installed card") + " (" (enumerate-str (map #(card-str state %) targets)) ")") + :paid/type :trash-installed + :paid/value (count async-result) + :paid/targets targets})))} card nil)) ;; TrashInstalledHardware @@ -492,12 +492,12 @@ :unpreventable true}) (complete-with-result state side eid - {:msg (str "trashes " (quantify (count async-result) "installed piece") - " of hardware" - " (" (enumerate-str (map #(card-str state %) targets)) ")") - :type :hardware - :value (count async-result) - :targets targets})))} + {:paid/msg (str "trashes " (quantify (count async-result) "installed piece") + " of hardware" + " (" (enumerate-str (map #(card-str state %) targets)) ")") + :paid/type :hardware + :paid/value (count async-result) + :paid/targets targets})))} card nil)) ;; DerezOtherHarmonic - this may NOT target the source card (itself) @@ -525,11 +525,11 @@ (derez state side harmonic)) (complete-with-result state side eid - {:msg (str "derezzes " (count targets) - " Harmonic ice (" (enumerate-str (map #(card-str state %) targets)) ")") - :type :derez - :value (count targets) - :targets targets}))} + {:paid/msg (str "derezzes " (count targets) + " Harmonic ice (" (enumerate-str (map #(card-str state %) targets)) ")") + :paid/type :derez + :paid/value (count targets) + :paid/targets targets}))} card nil)) ;; TrashInstalledProgram @@ -552,11 +552,11 @@ :unpreventable true}) (complete-with-result state side eid - {:msg (str "trashes " (quantify (count async-result) "installed program") - " (" (enumerate-str (map #(card-str state %) targets)) ")") - :type :program - :value (count async-result) - :targets targets})))} + {:paid/msg (str "trashes " (quantify (count async-result) "installed program") + " (" (enumerate-str (map #(card-str state %) targets)) ")") + :paid/type :program + :paid/value (count async-result) + :paid/targets targets})))} card nil)) ;; TrashInstalledResource @@ -579,11 +579,11 @@ :unpreventable true}) (complete-with-result state side eid - {:msg (str "trashes " (quantify (count async-result) "installed resource") - " (" (enumerate-str (map #(card-str state %) targets)) ")") - :type :resource - :value (count async-result) - :targets targets})))} + {:paid/msg (str "trashes " (quantify (count async-result) "installed resource") + " (" (enumerate-str (map #(card-str state %) targets)) ")") + :paid/type :resource + :paid/value (count async-result) + :paid/targets targets})))} card nil)) ;; TrashInstalledConnection @@ -609,11 +609,11 @@ :unpreventable true}) (complete-with-result state side eid - {:msg (str "trashes " (quantify (count async-result) "installed connection resource") - " (" (enumerate-str (map #(card-str state %) targets)) ")") - :type :connection - :value (count async-result) - :targets targets})))} + {:paid/msg (str "trashes " (quantify (count async-result) "installed connection resource") + " (" (enumerate-str (map #(card-str state %) targets)) ")") + :paid/type :connection + :paid/value (count async-result) + :paid/targets targets})))} card nil)) ;; TrashRezzedIce @@ -636,11 +636,11 @@ :unpreventable true}) (complete-with-result state side eid - {:msg (str "trashes " (quantify (count async-result) "installed rezzed ice" "") - " (" (enumerate-str (map #(card-str state %) targets)) ")") - :type :ice - :value (count async-result) - :targets targets})))} + {:paid/msg (str "trashes " (quantify (count async-result) "installed rezzed ice" "") + " (" (enumerate-str (map #(card-str state %) targets)) ")") + :paid/type :ice + :paid/value (count async-result) + :paid/targets targets})))} card nil)) ;; TrashFromDeck @@ -655,12 +655,12 @@ (wait-for (mill state side side (value cost)) (complete-with-result state side eid - {:msg (str "trashes " (quantify (count async-result) "card") - " from the top of " - (if (= :corp side) "R&D" "the stack")) - :type :trash-from-deck - :value (count async-result) - :targets async-result}))) + {:paid/msg (str "trashes " (quantify (count async-result) "card") + " from the top of " + (if (= :corp side) "R&D" "the stack")) + :paid/type :trash-from-deck + :paid/value (count async-result) + :paid/targets async-result}))) ;; TrashFromHand (defmethod value :trash-from-hand [cost] (:cost/amount cost)) @@ -673,7 +673,6 @@ [cost state side eid card] (let [select-fn #(and ((if (= :corp side) corp? runner?) %) (in-hand? %)) - prompt-hand (if (= :corp side) "HQ" "the grip") hand (if (= :corp side) "HQ" "the grip")] (continue-ability state side @@ -685,14 +684,14 @@ :effect (req (wait-for (trash-cards state side targets {:unpreventable true :seen false}) (complete-with-result state side eid - {:msg (str "trashes " (quantify (count async-result) "card") - (when (and (= :runner side) - (pos? (count async-result))) - (str " (" (enumerate-str (map #(card-str state %) targets)) ")")) - " from " hand) - :type :trash-from-hand - :value (count async-result) - :targets async-result})))} + {:paid/msg (str "trashes " (quantify (count async-result) "card") + (when (and (= :runner side) + (pos? (count async-result))) + (str " (" (enumerate-str (map #(card-str state %) targets)) ")")) + " from " hand) + :paid/type :trash-from-hand + :paid/value (count async-result) + :paid/targets async-result})))} nil nil))) ;; RandomlyTrashFromHand @@ -707,12 +706,12 @@ (wait-for (discard-from-hand state side side (value cost)) (complete-with-result state side eid - {:msg (str "trashes " (quantify (count async-result) "card") - " randomly from " - (if (= :corp side) "HQ" "the grip")) - :type :randomly-trash-from-hand - :value (count async-result) - :targets async-result}))) + {:paid/msg (str "trashes " (quantify (count async-result) "card") + " randomly from " + (if (= :corp side) "HQ" "the grip")) + :paid/type :randomly-trash-from-hand + :paid/value (count async-result) + :paid/targets async-result}))) ;; TrashEntireHand (defmethod value :trash-entire-hand [cost] 1) @@ -725,14 +724,14 @@ (wait-for (trash-cards state side cards {:unpreventable true}) (complete-with-result state side eid - {:msg (str "trashes all (" (count async-result) ") cards in " - (if (= :runner side) "their grip" "HQ") - (when (and (= :runner side) - (pos? (count async-result))) - (str " (" (enumerate-str (map :title async-result)) ")"))) - :type :trash-entire-hand - :value (count async-result) - :targets async-result})))) + {:paid/msg (str "trashes all (" (count async-result) ") cards in " + (if (= :runner side) "their grip" "HQ") + (when (and (= :runner side) + (pos? (count async-result))) + (str " (" (enumerate-str (map :title async-result)) ")"))) + :paid/type :trash-entire-hand + :paid/value (count async-result) + :paid/targets async-result})))) ;; TrashHardwareFromHand (defmethod value :trash-hardware-from-hand [cost] (:cost/amount cost)) @@ -753,13 +752,13 @@ :effect (req (wait-for (trash-cards state side targets {:unpreventable true}) (complete-with-result state side eid - {:msg (str "trashes " (quantify (count async-result) "piece") - " of hardware" - " (" (enumerate-str (map :title targets)) ")" - " from their grip") - :type :trash-hardware-from-hand - :value (count async-result) - :targets async-result})))} + {:paid/msg (str "trashes " (quantify (count async-result) "piece") + " of hardware" + " (" (enumerate-str (map :title targets)) ")" + " from their grip") + :paid/type :trash-hardware-from-hand + :paid/value (count async-result) + :paid/targets async-result})))} nil nil)) ;; TrashProgramFromHand @@ -781,12 +780,12 @@ :effect (req (wait-for (trash-cards state side targets {:unpreventable true}) (complete-with-result state side eid - {:msg (str "trashes " (quantify (count async-result) "program") - " (" (enumerate-str (map :title targets)) ")" - " from the grip") - :type :trash-program-from-hand - :value (count async-result) - :targets async-result})))} + {:paid/msg (str "trashes " (quantify (count async-result) "program") + " (" (enumerate-str (map :title targets)) ")" + " from the grip") + :paid/type :trash-program-from-hand + :paid/value (count async-result) + :paid/targets async-result})))} nil nil)) ;; TrashResourceFromHand @@ -808,12 +807,12 @@ :effect (req (wait-for (trash-cards state side targets {:unpreventable true}) (complete-with-result state side eid - {:msg (str "trashes " (quantify (count async-result) "resource") - " (" (enumerate-str (map :title targets)) ")" - " from the grip") - :type :trash-resource-from-hand - :value (count async-result) - :targets async-result})))} + {:paid/msg (str "trashes " (quantify (count async-result) "resource") + " (" (enumerate-str (map :title targets)) ")" + " from the grip") + :paid/type :trash-resource-from-hand + :paid/value (count async-result) + :paid/targets async-result})))} nil nil)) ;; NetDamage @@ -827,10 +826,10 @@ (wait-for (damage state side :net (value cost) {:unpreventable true :card card}) (complete-with-result state side eid - {:msg (str "suffers " (count async-result) " net damage") - :type :net - :value (count async-result) - :targets async-result}))) + {:paid/msg (str "suffers " (count async-result) " net damage") + :paid/type :net + :paid/value (count async-result) + :paid/targets async-result}))) ;; MeatDamage (defmethod value :meat [cost] (:cost/amount cost)) @@ -843,10 +842,10 @@ (wait-for (damage state side :meat (value cost) {:unpreventable true :card card}) (complete-with-result state side eid - {:msg (str "suffers " (count async-result) " meat damage") - :type :meat - :value (count async-result) - :targets async-result}))) + {:paid/msg (str "suffers " (count async-result) " meat damage") + :paid/type :meat + :paid/value (count async-result) + :paid/targets async-result}))) ;; BrainDamage (defmethod value :brain [cost] (:cost/amount cost)) @@ -859,10 +858,10 @@ (wait-for (damage state side :brain (value cost) {:unpreventable true :card card}) (complete-with-result state side eid - {:msg (str "suffers " (count async-result) " core damage") - :type :brain - :value (count async-result) - :targets async-result}))) + {:paid/msg (str "suffers " (count async-result) " core damage") + :paid/type :brain + :paid/value (count async-result) + :paid/targets async-result}))) ;; ShuffleInstalledToDeck (defmethod value :shuffle-installed-to-stack [cost] (:cost/amount cost)) @@ -885,12 +884,12 @@ (shuffle! state side :deck) (complete-with-result state side eid - {:msg (str "shuffles " (quantify (count cards) "card") - " (" (enumerate-str (map :title cards)) ")" - " into " (if (= :corp side) "R&D" "the stack")) - :type :shuffle-installed-to-stack - :value (count cards) - :targets cards})))} + {:paid/msg (str "shuffles " (quantify (count cards) "card") + " (" (enumerate-str (map :title cards)) ")" + " into " (if (= :corp side) "R&D" "the stack")) + :paid/type :shuffle-installed-to-stack + :paid/value (count cards) + :paid/targets cards})))} nil nil)) ;; AddInstalledToBottomOfDeck @@ -914,12 +913,12 @@ :effect (req (let [cards (keep #(move state side % :deck) targets)] (complete-with-result state side eid - {:msg (str "adds " (quantify (count cards) "installed card") - " to the bottom of " deck - " (" (enumerate-str (map #(card-str state %) targets)) ")") - :type :add-installed-to-bottom-of-deck - :value (count cards) - :targets cards})))} + {:paid/msg (str "adds " (quantify (count cards) "installed card") + " to the bottom of " deck + " (" (enumerate-str (map #(card-str state %) targets)) ")") + :paid/type :add-installed-to-bottom-of-deck + :paid/value (count cards) + :paid/targets cards})))} nil nil))) ;; AddRandomToBottom @@ -938,11 +937,11 @@ (move state side c :deck)) (complete-with-result state side eid - {:msg (str "adds " (quantify (value cost) "random card") - " to the bottom of " deck) - :type :add-random-from-hand-to-bottom-of-deck - :value (value cost) - :targets chosen}))) + {:paid/msg (str "adds " (quantify (value cost) "random card") + " to the bottom of " deck) + :paid/type :add-random-from-hand-to-bottom-of-deck + :paid/value (value cost) + :paid/targets chosen}))) ;; AnyAgendaCounter (defmethod value :any-agenda-counter [cost] (:cost/amount cost)) @@ -963,12 +962,12 @@ (wait-for (trigger-event-sync state side :agenda-counter-spent target) (complete-with-result state side eid - {:msg (str "spends " + {:paid/msg (str "spends " (quantify (value cost) (str "hosted agenda counter")) " from on " title) - :type :any-agenda-counter - :value (value cost) - :targets [target]}))))} + :paid/type :any-agenda-counter + :paid/value (value cost) + :paid/targets [target]}))))} nil nil)) ;; AnyVirusCounter @@ -983,10 +982,10 @@ (wait-for (resolve-ability state side (pick-virus-counters-to-spend (value cost)) card nil) (complete-with-result state side eid - {:msg (str "spends " (:msg async-result)) - :type :any-virus-counter - :value (:number async-result) - :targets (:targets async-result)}))) + {:paid/msg (str "spends " (:msg async-result)) + :paid/type :any-virus-counter + :paid/value (:number async-result) + :paid/targets (:targets async-result)}))) ;; AdvancementCounter (defmethod value :advancement [cost] (:cost/amount cost)) @@ -1004,11 +1003,11 @@ (wait-for (trigger-event-sync state side :counter-added card) (complete-with-result state side eid - {:msg (str "spends " - (quantify (value cost) (str "hosted advancement counter")) - " from on " title) - :type :advancement - :value (value cost)})))) + {:paid/msg (str "spends " + (quantify (value cost) (str "hosted advancement counter")) + " from on " title) + :paid/type :advancement + :paid/value (value cost)})))) ;; AgendaCounter (defmethod value :agenda [cost] (:cost/amount cost)) @@ -1026,11 +1025,11 @@ (wait-for (trigger-event-sync state side :agenda-counter-spent card) (complete-with-result state side eid - {:msg (str "spends " - (quantify (value cost) "hosted agenda counter") - " from on " title) - :type :agenda - :value (value cost)})))) + {:paid/msg (str "spends " + (quantify (value cost) "hosted agenda counter") + " from on " title) + :paid/type :agenda + :paid/value (value cost)})))) ;; PowerCounter (defmethod value :power [cost] (:cost/amount cost)) @@ -1048,11 +1047,11 @@ (wait-for (trigger-event-sync state side :counter-added card) (complete-with-result state side eid - {:msg (str "spends " - (quantify (value cost) "hosted power counter") - " from on " title) - :type :power - :value (value cost)})))) + {:paid/msg (str "spends " + (quantify (value cost) "hosted power counter") + " from on " title) + :paid/type :power + :paid/value (value cost)})))) ;; XPowerCounter (defmethod value :x-power [_] 0) @@ -1074,11 +1073,11 @@ (wait-for (trigger-event-sync state side :counter-added card) (complete-with-result state side eid - {:msg (str "spends " - (quantify cost "hosted power counter") - " from on " title) - :type :x-power - :value cost}))))} + {:paid/msg (str "spends " + (quantify cost "hosted power counter") + " from on " title) + :paid/type :x-power + :paid/value cost}))))} card nil)) ;; VirusCounter @@ -1104,17 +1103,17 @@ (wait-for (resolve-ability state side (pick-virus-counters-to-spend card (value cost)) card nil) (complete-with-result state side eid - {:msg (str "spends " (:msg async-result)) - :type :virus - :value (:number async-result) - :targets (:targets async-result)})) + {:paid/msg (str "spends " (:msg async-result)) + :paid/type :virus + :paid/value (:number async-result) + :paid/targets (:targets async-result)})) (let [title (:title card) card (update! state side (update-in card [:counter :virus] - (value cost)))] (wait-for (trigger-event-sync state side :counter-added card) (complete-with-result state side eid - {:msg (str "spends " - (quantify (value cost) (str "hosted virus counter")) - " from on " title) - :type :virus - :value (value cost)}))))) + {:paid/msg (str "spends " + (quantify (value cost) (str "hosted virus counter")) + " from on " title) + :paid/type :virus + :paid/value (value cost)}))))) diff --git a/src/clj/game/core/engine.clj b/src/clj/game/core/engine.clj index 3ed5371a85..29410cb477 100644 --- a/src/clj/game/core/engine.clj +++ b/src/clj/game/core/engine.clj @@ -2,14 +2,13 @@ (:require [clj-uuid :as uuid] [clojure.stacktrace :refer [print-stack-trace]] - [clojure.string :as str] [cond-plus.core :refer [cond+]] [game.core.board :refer [clear-empty-remotes all-installed-runner-type all-active-installed]] [game.core.card :refer [active? facedown? faceup? get-card get-cid get-title in-discard? in-hand? installed? rezzed? program? console? unique?]] [game.core.card-defs :refer [card-def]] [game.core.effects :refer [get-effect-maps unregister-lingering-effects]] [game.core.eid :refer [complete-with-result effect-completed make-eid]] - [game.core.payment :refer [build-spend-msg can-pay? handler merge-costs]] + [game.core.payment :refer [build-spend-msg can-pay? handler]] [game.core.prompt-state :refer [add-to-prompt-queue]] [game.core.prompts :refer [clear-wait-prompt show-prompt show-select show-wait-prompt]] [game.core.say :refer [system-msg system-say]] @@ -74,8 +73,6 @@ ; Mark the ability as "async", meaning the :effect function must call effect-completed itself. ; Without this being set to true, resolve-ability will call effect-completed once it's done. ; This part of the engine is really dumb and complicated, so ask someone on slack about it. -; :cost-req -- 1-fn -; A function which will be applied to the cost of an ability immediatly prior to being paid. See all-stealth or min-stealth for examples. ; PROMPT KEYS ; :prompt -- string or 5-fn @@ -322,58 +319,34 @@ (ability-effect state side eid card targets) (effect-completed state side eid))) -(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-costs - (->> cost - (merge-costs) - (filter #(#{:advancement :agenda :power :virus :bad-publicity} (:cost/type %))) - (seq))] - (if counter-costs - (reduce - (fn [card {counter-type :cost/type - counter-amount :cost/amount}] - (let [counter (if (= :advancement counter-type) - [:advance-counter] - [:counter counter-type])] - (update-in card counter - counter-amount))) - card - counter-costs) - card))) - (defn merge-costs-paid - ([cost-paid] - (into {} (map (fn [[k {:keys [type value targets]}]] - [k {:type type - :value value - :targets targets}])) - cost-paid)) + ([cost-paid] cost-paid) ([cost-paid1 cost-paid2] - (let [costs-paid [cost-paid1 cost-paid2] - cost-keys (mapcat keys costs-paid)] + (let [costs-paid (concat (vals cost-paid1) (vals cost-paid2))] (reduce (fn [acc cur] - (let [costs (map cur costs-paid) - cost-obj {:type cur - :value (apply + (keep :value costs)) - :targets (seq (apply concat (keep :targets costs)))}] - (assoc acc cur cost-obj))) + (let [existing (get acc (:paid/type cur)) + cost-obj {:paid/type (:paid/type cur) + :paid/value (+ (:paid/value existing 0) (:paid/value cur 0)) + :paid/targets (seq (concat (:paid/targets existing) (:paid/targets cur)))}] + (assoc acc (:paid/type cur) cost-obj))) {} - cost-keys))) + costs-paid))) ([cost-paid1 cost-paid2 & costs-paid] (reduce merge-costs-paid (merge-costs-paid cost-paid1 cost-paid2) costs-paid))) -(defn- do-paid-ability [state side {:keys [eid cost] :as ability} card targets async-result] +(defn- do-paid-ability [state side {:keys [eid] :as ability} card targets async-result] (let [payment-str (:msg async-result) cost-paid (merge-costs-paid (:cost-paid eid) (:cost-paid async-result)) - ability (assoc-in ability [:eid :cost-paid] cost-paid)] + ability (assoc-in ability [:eid :cost-paid] cost-paid) + ;; After paying costs, counters will be removed, so fetch the latest version. + ;; We still want the card if the card is trashed, so default to given + ;; when the latest is gone. + card (or (get-card state card) card)] ;; Print the message (print-msg state side ability card targets payment-str) ;; Trigger the effect (register-once state side ability card) - (do-effect state side ability (ugly-counter-hack card cost) targets) + (do-effect state side ability card targets) ;; If the ability isn't async, complete it (when-not (:async ability) (effect-completed state side eid)))) @@ -1159,11 +1132,11 @@ (complete-with-result state side eid {:msg (->> payment-result - (keep :msg) - enumerate-str) + (keep :paid/msg) + (enumerate-str)) :cost-paid (->> payment-result - (keep #(not-empty (select-keys % [:type :targets :value]))) + (keep #(not-empty (dissoc % :paid/msg))) (reduce - (fn [acc cost] - (assoc acc (:type cost) cost)) + (fn [acc paid] + (assoc acc (:paid/type paid) paid)) {}))}))))))) diff --git a/src/clj/game/core/ice.clj b/src/clj/game/core/ice.clj index 8268e92159..a60956edca 100644 --- a/src/clj/game/core/ice.clj +++ b/src/clj/game/core/ice.clj @@ -627,7 +627,6 @@ :breaks subtypes :break-cost cost :auto-break-sort (:auto-break-sort args) - :cost-req (:cost-req args) :break-cost-bonus (:break-cost-bonus args) :additional-ability (:additional-ability args) :label (str (or (:label args) @@ -672,7 +671,6 @@ (str-req state side eid card targets) true)) :cost [cost] - :cost-req (:cost-req args) :pump strength :pump-bonus (:pump-bonus args) :auto-pump-sort (:auto-break-sort args) diff --git a/src/clj/game/core/payment.clj b/src/clj/game/core/payment.clj index c326660b4b..97a52b1abd 100644 --- a/src/clj/game/core/payment.clj +++ b/src/clj/game/core/payment.clj @@ -100,14 +100,7 @@ ([state side eid card title & args] (let [remove-zero-credit-cost (and (= (:source-type eid) :corp-install) (not (ice? card))) - cost-req (when (and (:abilities (:source eid)) - (:ability-idx (:source-info eid))) - (:cost-req (nth (:abilities (:source eid)) (:ability-idx (:source-info eid)) nil))) - cost-filter (if (fn? cost-req) cost-req identity) - costs (->> (merge-costs (filter some? args) remove-zero-credit-cost) - (cost-filter) - (flatten) - (into []))] + costs (merge-costs (filter some? args) remove-zero-credit-cost)] (if (every? #(and (not (flag-stops-pay? state side %)) (payable? % state side eid card)) costs) @@ -117,7 +110,7 @@ (defn cost-targets [eid cost-type] - (get-in eid [:cost-paid cost-type :targets])) + (get-in eid [:cost-paid cost-type :paid/targets])) (defn cost-target [eid cost-type] @@ -125,7 +118,7 @@ (defn cost-value [eid cost-type] - (get-in eid [:cost-paid cost-type :value])) + (get-in eid [:cost-paid cost-type :paid/value])) ;; the function `pay` is defined in resolve-ability because they're all intermingled ;; fuck the restriction against circular dependencies, for real diff --git a/src/clj/game/core/runs.clj b/src/clj/game/core/runs.clj index 1a316592ce..eb015122a5 100644 --- a/src/clj/game/core/runs.clj +++ b/src/clj/game/core/runs.clj @@ -114,7 +114,8 @@ ([state side eid server card] (make-run state side eid server card nil)) ([state side eid server card {:keys [click-run ignore-costs] :as args}] (let [cost-args (assoc args :server (unknown->kw server)) - costs (total-run-cost state side card cost-args)] + costs (total-run-cost state side card cost-args) + card (or (get-card state card) card)] (if-not (and (can-run? state :runner) (can-run-server? state server) (can-pay? state :runner eid card "a run" costs)) diff --git a/test/clj/game/core/engine_test.clj b/test/clj/game/core/engine_test.clj index 79fe186a28..2ef48815db 100644 --- a/test/clj/game/core/engine_test.clj +++ b/test/clj/game/core/engine_test.clj @@ -63,50 +63,48 @@ (:order @state))))))) (deftest merge-costs-paid - (let [eid1 {:cost-paid {:click {:type :click - :value 3} - :credit {:type :credit - :value 1} - :forfeit {:type :forfeit - :targets [{:title "NAPD Contract"}] - :value 1}}} - eid2 {:cost-paid {:click {:type :click - :value 1}}} - eid3 {:cost-paid {:trash {:type :trash - :value 1 - :targets [{:title "C.I. Fund"}]}}}] - (is (= {:click {:type :click - :targets nil - :value 3} - :credit {:type :credit - :targets nil - :value 1} - :forfeit {:type :forfeit - :targets [{:title "NAPD Contract"}] - :value 1}} + (let [eid1 {:cost-paid {:click {:paid/type :click + :paid/value 3} + :credit {:paid/type :credit + :paid/value 1} + :forfeit {:paid/type :forfeit + :paid/targets [{:title "NAPD Contract"}] + :paid/value 1}}} + eid2 {:cost-paid {:click {:paid/type :click + :paid/value 1}}} + eid3 {:cost-paid {:trash {:paid/type :trash + :paid/value 1 + :paid/targets [{:title "C.I. Fund"}]}}}] + (is (= {:click {:paid/type :click + :paid/value 3} + :credit {:paid/type :credit + :paid/value 1} + :forfeit {:paid/type :forfeit + :paid/targets [{:title "NAPD Contract"}] + :paid/value 1}} (e/merge-costs-paid (:cost-paid eid1)))) - (is (= {:click {:type :click - :targets nil - :value 4} - :credit {:type :credit - :targets nil - :value 1} - :forfeit {:type :forfeit - :targets [{:title "NAPD Contract"}] - :value 1}} + (is (= {:click {:paid/type :click + :paid/targets nil + :paid/value 4} + :credit {:paid/type :credit + :paid/targets nil + :paid/value 1} + :forfeit {:paid/type :forfeit + :paid/targets [{:title "NAPD Contract"}] + :paid/value 1}} (e/merge-costs-paid (:cost-paid eid1) (:cost-paid eid2)))) - (is (= {:click {:type :click - :targets nil - :value 4} - :credit {:type :credit - :targets nil - :value 1} - :forfeit {:type :forfeit - :targets [{:title "NAPD Contract"}] - :value 1} - :trash {:type :trash - :targets [{:title "C.I. Fund"}] - :value 1}} + (is (= {:click {:paid/type :click + :paid/targets nil + :paid/value 4} + :credit {:paid/type :credit + :paid/targets nil + :paid/value 1} + :forfeit {:paid/type :forfeit + :paid/targets [{:title "NAPD Contract"}] + :paid/value 1} + :trash {:paid/type :trash + :paid/targets [{:title "C.I. Fund"}] + :paid/value 1}} (e/merge-costs-paid (:cost-paid eid1) (:cost-paid eid2) (:cost-paid eid3)))))) (deftest trash-currents-test diff --git a/test/clj/game/core/rules_test.clj b/test/clj/game/core/rules_test.clj index dfd3dbfe0b..0685481631 100644 --- a/test/clj/game/core/rules_test.clj +++ b/test/clj/game/core/rules_test.clj @@ -232,9 +232,10 @@ (deftest trash-seen-and-unseen ;; Trash installed assets that are both seen and unseen by runner (do-game - (new-game {:corp {:deck [(qty "PAD Campaign" 3)]}}) - (play-from-hand state :corp "PAD Campaign" "New remote") + (new-game {:corp {:deck [(qty "Ice Wall" 7)] + :hand ["PAD Campaign" "Sandburg" "NGO Front"]}}) (play-from-hand state :corp "PAD Campaign" "New remote") + (play-from-hand state :corp "Sandburg" "New remote") (take-credits state :corp 1) (run-empty-server state "Server 1") (click-prompt state :runner "No action") @@ -242,11 +243,12 @@ (run-empty-server state "Server 2") (click-prompt state :runner "Pay 4 [Credits] to trash") (take-credits state :runner 2) - (play-from-hand state :corp "PAD Campaign" "Server 1") + (play-from-hand state :corp "NGO Front" "Server 1") + (is (= "The PAD Campaign in Server 1 will now be trashed." (:msg (prompt-map :corp)))) (click-prompt state :corp "OK") (is (= 2 (count (:discard (get-corp)))) "Trashed existing asset") - (is (:seen (first (get-in @state [:corp :discard]))) "Asset trashed by runner is Seen") - (is (not (:seen (second (get-in @state [:corp :discard])))) + (is (:seen (find-card "Sandburg" (:discard (get-corp)))) "Asset trashed by runner is Seen") + (is (not (:seen (find-card "PAD Campaign" (:discard (get-corp))))) "Asset trashed by corp is Unseen") (is (not (:seen (get-content state :remote1 0))) "New asset is unseen")))