From 3b6791f2d8971565e9c5d8e3139a35abe0da4850 Mon Sep 17 00:00:00 2001 From: NB Kelly Date: Thu, 19 Sep 2024 19:36:52 +1200 Subject: [PATCH 1/4] make matryoshka a cost --- src/clj/game/cards/programs.clj | 35 +++++++++++---------------------- src/clj/game/core/costs.clj | 25 ++++++++++++++++++++++- 2 files changed, 35 insertions(+), 25 deletions(-) diff --git a/src/clj/game/cards/programs.clj b/src/clj/game/cards/programs.clj index 7071bd2755..44b95e7df4 100644 --- a/src/clj/game/cards/programs.clj +++ b/src/clj/game/cards/programs.clj @@ -2099,31 +2099,18 @@ (defcard "Matryoshka" (let [break-abi {:label "Break X subroutines" - :cost [(->c :x-credits)] - :break-cost [(->c :x-credits)] - :req (req (let [hosted (:hosted (get-card state card)) - valid (filter #(not (facedown? %)) hosted) - same-title (filter #(= (:title card) (:title %)) valid)] - (and (active-encounter? state) - current-ice card - (<= (get-strength current-ice) (get-strength card)) - (not-empty same-title)))) - ; no break-req to not enable auto-pumping + :cost [(->c :x-credits)(->c :turn-hosted-matryoshka-facedown 1)] + :break-cost [(->c :x-credits)(->c :turn-hosted-matryoshka-facedown 1)] + :req (req (and + (active-encounter? state) + (<= (get-strength current-ice) (get-strength card)))) :msg (msg "break " (quantify (cost-value eid :x-credits) "subroutine") - " on " (card-str state current-ice) " and turn 1 hosted copy of " - (:title card) " facedown") - :effect (req - (let [hosted (:hosted (get-card state card)) - valid (filter #(not (facedown? %)) hosted) - same-title (filter #(= (:title card) (:title %)) valid) - first-copy (first same-title)] - (flip-facedown state side (get-card state first-copy)) - (if (pos? (cost-value eid :x-credits)) - (continue-ability - state side - (break-sub nil (cost-value eid :x-credits) "All") - card nil) - (effect-completed state side eid))))} + " on " (card-str state current-ice)) + :effect (effect + (continue-ability + (when (pos? (cost-value eid :x-credits)) + (break-sub nil (cost-value eid :x-credits) "All" {:repeatable false})) + card nil))} host-abi {:action true :label "Host 1 copy of Matryoshka" :prompt (msg "Choose 1 copy of " (:title card) " in the grip") diff --git a/src/clj/game/core/costs.clj b/src/clj/game/core/costs.clj index f34ded4b7d..f1013b42d1 100644 --- a/src/clj/game/core/costs.clj +++ b/src/clj/game/core/costs.clj @@ -10,7 +10,7 @@ [game.core.effects :refer [any-effects is-disabled-reg?]] [game.core.flags :refer [is-scored?]] [game.core.gaining :refer [deduct lose]] - [game.core.moving :refer [discard-from-hand forfeit mill move trash trash-cards]] + [game.core.moving :refer [discard-from-hand flip-facedown forfeit mill move trash trash-cards]] [game.core.payment :refer [handler label payable? value stealth-value]] [game.core.pick-counters :refer [pick-credit-providing-cards pick-virus-counters-to-spend]] [game.core.revealing :refer [reveal]] @@ -937,6 +937,29 @@ :paid/targets cards})))} nil nil))) +;; TurnHostedMatryoshkaFacedown +(defmethod value :turn-hosted-matryoshka-facedown [cost] (:cost/amount cost)) +(defmethod label :turn-hosted-matryoshka-facedown [cost] + (str "turn " (quantify (value cost) "hosted cop" "y" "ies") " of Matryoshka facedown")) +(defmethod payable? :turn-hosted-matryoshka-facedown + [cost state side eid card] + (<= (value cost) + (count (filter #(and (not (facedown? %)) (= (:title %) "Matryoshka")) + (:hosted (get-card state card)))))) +(defmethod handler :turn-hosted-matryoshka-facedown + [cost state side eid card] + (let [pred #(and (not (facedown? %)) (= (:title %) "Matryoshka")) + selected (take (value cost) (filter pred (:hosted (get-card state card))))] + (doseq [c selected] + (flip-facedown state side c)) + (complete-with-result + state side eid + {:paid/msg (str "turns "(quantify (value cost) "hosted cop" "y" "ies") + " of Matryoshka facedown") + :paid/type :turn-hosted-matryoshka-facedown + :paid/value (value cost) + :paid/targets selected}))) + ;; AddRandomToBottom (defmethod value :add-random-from-hand-to-bottom-of-deck [cost] (:cost/amount cost)) (defmethod label :add-random-from-hand-to-bottom-of-deck [cost] From 57944d908f13bce9cabe7d5ac558535800bb5d69 Mon Sep 17 00:00:00 2001 From: NB Kelly Date: Sun, 22 Sep 2024 20:03:30 +1200 Subject: [PATCH 2/4] added auto-breaker functionaly for x-credits breakers --- src/clj/game/core/actions.clj | 7 +++++-- src/clj/game/core/ice.clj | 10 ++++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/clj/game/core/actions.clj b/src/clj/game/core/actions.clj index 631499c40a..793a450e86 100644 --- a/src/clj/game/core/actions.clj +++ b/src/clj/game/core/actions.clj @@ -10,9 +10,9 @@ [game.core.cost-fns :refer [break-sub-ability-cost card-ability-cost score-additional-cost-bonus]] [game.core.effects :refer [any-effects]] [game.core.eid :refer [effect-completed make-eid]] - [game.core.engine :refer [ability-as-handler checkpoint register-pending-event pay queue-event resolve-ability trigger-event-simult]] + [game.core.engine :refer [ability-as-handler checkpoint register-once register-pending-event pay queue-event resolve-ability trigger-event-simult]] [game.core.flags :refer [can-advance? can-score?]] - [game.core.ice :refer [break-subroutine! break-subs-event-context get-current-ice get-pump-strength get-strength pump resolve-subroutine! resolve-unbroken-subs!]] + [game.core.ice :refer [break-subroutine! break-subs-event-context get-current-ice get-pump-strength get-strength pump resolve-subroutine! resolve-unbroken-subs! substitute-x-credit-costs]] [game.core.initializing :refer [card-init]] [game.core.moving :refer [move trash]] [game.core.payment :refer [build-spend-msg can-pay? merge-costs build-cost-string ->c]] @@ -464,6 +464,7 @@ (sort-by #(-> % first :auto-break-sort)) (apply min-key #(let [costs (second %)] (reduce (fnil + 0 0) 0 (mapv :cost/amount costs))))) + once-key (:once break-ability) subs-broken-at-once (when break-ability (:break break-ability 1)) unbroken-subs (when (:subroutines current-ice) @@ -474,6 +475,7 @@ (if (pos? subs-broken-at-once) (int (Math/ceil (/ unbroken-subs subs-broken-at-once))) 1)) + break-cost (substitute-x-credit-costs break-cost unbroken-subs (:auto-break-creds-per-sub break-ability)) total-break-cost (when (and break-cost times-break) (repeat times-break break-cost)) @@ -503,6 +505,7 @@ "all ") unbroken-subs " subroutines on " (:title current-ice)))) + (when once-key (register-once state side {:once once-key} card)) (continue state side nil)))))))) (def dynamic-abilities diff --git a/src/clj/game/core/ice.clj b/src/clj/game/core/ice.clj index e579dc2d22..624f04447b 100644 --- a/src/clj/game/core/ice.clj +++ b/src/clj/game/core/ice.clj @@ -704,6 +704,15 @@ card) duration))}))) +(defn substitute-x-credit-costs + "Substitute out the 'x-credits' part of a cost when the credit count is known" + [cost x scale] + (let [pick-x (fn [c] (= (:cost/type c) :x-credits)) + output-cost (when (and x scale) [(->c :credit (* x scale))]) + adjusted (remove pick-x cost)] + (if (= (count adjusted) (count cost)) + cost + (concat adjusted output-cost)))) (def breaker-auto-pump "Updates an icebreaker's abilities with a pseudo-ability to trigger the @@ -751,6 +760,7 @@ (if (pos? subs-broken-at-once) (int (Math/ceil (/ unbroken-subs subs-broken-at-once))) 1)) + break-cost (substitute-x-credit-costs break-cost unbroken-subs (:auto-break-creds-per-sub break-ability)) total-break-cost (when (and break-cost times-break) (repeat times-break break-cost)) From 72bc4e04ca96641731890cccf6c21048a2ea1fac Mon Sep 17 00:00:00 2001 From: NB Kelly Date: Sun, 22 Sep 2024 20:03:50 +1200 Subject: [PATCH 3/4] auto-break smartly for utae, lobisomem, matryoshka --- src/clj/game/cards/programs.clj | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/clj/game/cards/programs.clj b/src/clj/game/cards/programs.clj index 44b95e7df4..02b0559ffd 100644 --- a/src/clj/game/cards/programs.clj +++ b/src/clj/game/cards/programs.clj @@ -1982,10 +1982,14 @@ (defcard "Lobisomem" (auto-icebreaker {:data {:counter {:power 1}} - :abilities [(break-sub 1 1 "Code Gate") + :abilities [(break-sub 1 1 "Code Gate" {:auto-break-sort 1}) {:label "Break X Barrier subroutines" :cost [(->c :x-credits) (->c :power 1)] :break-cost [(->c :x-credits) (->c :power 1)] + :auto-break-creds-per-sub 1 + :break 0 ;; technically not correct, but it's enough for the engine to pick up for auto-breaking + :break-req (req (and (active-encounter? state) + (has-subtype? current-ice "Barrier"))) :req (req (and (active-encounter? state) (<= (get-strength current-ice) (get-strength card)))) @@ -2101,6 +2105,9 @@ (let [break-abi {:label "Break X subroutines" :cost [(->c :x-credits)(->c :turn-hosted-matryoshka-facedown 1)] :break-cost [(->c :x-credits)(->c :turn-hosted-matryoshka-facedown 1)] + :auto-break-creds-per-sub 1 + :break 0 ;; technically not correct, but it's enough for the engine to pick up for auto-breaking + :break-req (req (active-encounter? state)) :req (req (and (active-encounter? state) (<= (get-strength current-ice) (get-strength card)))) @@ -3399,7 +3406,12 @@ :once :per-run :req (req (and (break-req state side eid card targets) (<= (get-strength current-ice) (get-strength card)))) - ; no break-req to not enable auto-pumping + :auto-break-creds-per-sub 1 + :auto-break-sort 2 + :break 0 ;; technically not correct, but it's enough for the engine to pick up for auto-breaking + :break-req (req (and (active-encounter? state) + (not-used-once? state {:once :per-run} card) + (has-subtype? current-ice "Code Gate"))) :msg (msg "break " (quantify (cost-value eid :x-credits) "subroutine") " on " (card-str state current-ice)) :effect (effect @@ -3408,6 +3420,8 @@ (break-sub nil (cost-value eid :x-credits) "Code Gate")) card nil))} (break-sub 1 1 "Code Gate" {:label "Break 1 Code Gate subroutine (Virtual restriction)" + ;; this should be prioritized when available + :auto-break-sort 1 :req (req (<= 3 (count (filter #(has-subtype? % "Virtual") (all-active-installed state :runner)))))}) (strength-pump 1 1)]}))) From 61879cd9e8115b0d34c14dd5410297a4615ece61 Mon Sep 17 00:00:00 2001 From: NB Kelly Date: Sun, 22 Sep 2024 20:04:18 +1200 Subject: [PATCH 4/4] unit test for utae, updated ice test which used utae as an example --- test/clj/game/cards/programs_test.clj | 34 +++++++++++++++++++++++++++ test/clj/game/core/ice_test.clj | 6 ++++- 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/test/clj/game/cards/programs_test.clj b/test/clj/game/cards/programs_test.clj index 0909662bba..587294e83e 100644 --- a/test/clj/game/cards/programs_test.clj +++ b/test/clj/game/cards/programs_test.clj @@ -8845,6 +8845,40 @@ (is (= (dec credits) (:credit (get-runner))))) (is (= 3 (:credit (get-runner))) "Able to use ability now")))) +(deftest utae-auto-pump + (do-game + (new-game {:corp {:hand ["Enigma" "Hortum"] :credits 50} + :runner {:hand ["Utae" "Paladin Poemu" "Cookbook" "Ice Carver"] :credits 50}}) + (play-from-hand state :corp "Enigma" "HQ") + (play-from-hand state :corp "Hortum" "HQ") + (take-credits state :corp) + (doseq [s (map :title (:hand (get-runner)))] + (play-from-hand state :runner s)) + (take-credits state :runner) + (take-credits state :corp) + (rez state :corp (get-ice state :hq 0)) + (rez state :corp (get-ice state :hq 1)) + (run-on state :hq) + (let [utae (get-program state 0)] + (run-continue-until state :encounter-ice) + (is (changed? [(:credit (get-runner)) -4] + (auto-pump-and-break state (refresh utae))) + (str "spent 5 to break Hortum")) + (run-continue-until state :encounter-ice) + (is (changed? [(:credit (get-runner)) -2] + (auto-pump-and-break state (refresh utae))) + (str "spent 3 to break Enigma")) + (run-continue state) + (run-jack-out state) + (trash state :runner (get-resource state 0)) + (run-on state :hq) + (run-continue-until state :encounter-ice) + (is (changed? [(:credit (get-runner)) -4] + (auto-pump-and-break state (refresh utae))) + (str "spent 5 to break Hortum")) + (run-continue-until state :encounter-ice) + (is (empty? (filter #(:dynamic %) (:abilities (refresh utae)))) "No option to break because of utae restriction")))) + (deftest vamadeva-swap-ability ;; Swap ability (testing "Doesnt work if no Deva in hand" diff --git a/test/clj/game/core/ice_test.clj b/test/clj/game/core/ice_test.clj index 737cbd99c1..0915d9edce 100644 --- a/test/clj/game/core/ice_test.clj +++ b/test/clj/game/core/ice_test.clj @@ -177,17 +177,21 @@ (testing "Auto pump available even with no active break ability" (do-game (new-game {:corp {:deck [(qty "Hedge Fund" 5)] - :hand ["DNA Tracker"] + :hand ["DNA Tracker" "Enigma"] :credits 20} :runner {:deck ["Utae"] :credits 20}}) (play-from-hand state :corp "DNA Tracker" "HQ") + (play-from-hand state :corp "Enigma" "HQ") (rez state :corp (get-ice state :hq 0)) + (rez state :corp (get-ice state :hq 1)) (take-credits state :corp) (play-from-hand state :runner "Utae") (let [utae (get-program state 0)] (run-on state :hq) (run-continue state) + (auto-pump-and-break state (refresh utae)) + (run-continue-until state :encounter-ice) (is (not-empty (filter #(= :auto-pump (:dynamic %)) (:abilities (refresh utae)))) "Auto pump is active") (is (empty? (filter #(= :auto-pump-and-break (:dynamic %)) (:abilities (refresh utae)))) "No auto break dynamic ability")))))