diff --git a/project.clj b/project.clj index e149ead8ed..bbec5a63e1 100644 --- a/project.clj +++ b/project.clj @@ -74,7 +74,8 @@ ;; Misc :test-paths ["test/clj"] - :eftest {:report eftest.report.pretty/report} + :eftest {:report eftest.report.pretty/report + :fast-fail? true} :ring {:handler web.api/app} diff --git a/src/clj/game/cards/assets.clj b/src/clj/game/cards/assets.clj index e1771a1548..cfb4fc2874 100644 --- a/src/clj/game/cards/assets.clj +++ b/src/clj/game/cards/assets.clj @@ -2090,9 +2090,9 @@ (rezzed? %)) (all-installed state :corp))) (remove-one [cid state ice] - (remove-extra-subs state :corp cid ice)) + (remove-extra-subs! state :corp ice cid)) (add-one [cid state ice] - (add-extra-sub state :corp cid ice 0 new-sub)) + (add-extra-sub! state :corp ice new-sub cid 0)) (update-all [state func] (doseq [i (all-rezzed-bios state)] (func state i)))] diff --git a/src/clj/game/cards/ice.clj b/src/clj/game/cards/ice.clj index 40af0c47bc..fce6e534d5 100644 --- a/src/clj/game/cards/ice.clj +++ b/src/clj/game/cards/ice.clj @@ -11,22 +11,22 @@ ;;; Runner abilites for breaking subs (defn runner-pay-or-break "Ability to break a subroutine by spending a resource (Bioroids, Negotiator, etc)" - [cost subs label] + [cost qty label] (let [cost-str (build-cost-str [cost]) - subs-str (quantify subs "subroutine")] + subs-str (quantify qty "subroutine")] {:cost cost :label (str label " " subs-str) :effect (req (system-msg state :runner (str "spends " cost-str " to " label " " subs-str " on " (:title card))))})) (defn runner-break "Ability to break a subroutine by spending a resource (Bioroids, Negotiator, etc)" - [cost subs] - (runner-pay-or-break cost subs "break")) + [cost qty] + (runner-pay-or-break cost qty "break")) (defn runner-pay "Ability to pay to avoid a subroutine by spending a resource (Popup Window, Turing, etc)" - [cost subs] - (runner-pay-or-break cost subs "pay for")) + [cost qty] + (runner-pay-or-break cost qty "pay for")) ;;; General subroutines (def end-the-run @@ -220,7 +220,13 @@ ;; Card definitions (def card-definitions - {"Aiki" + {"Afshar" + {:implementation "Breaking both subs not restricted" + :subroutines [{:msg "make the Runner lose 2 [Credits]" + :effect (effect (lose-credits :runner 2))} + end-the-run]} + + "Aiki" {:subroutines [(do-psi {:label "Runner draws 2 cards" :msg "make the Runner draw 2 cards" :effect (effect (draw :runner 2) @@ -315,9 +321,9 @@ (card-is? % :side :runner))} :label "Add 1 installed card to the Runner's Grip" :msg "add 1 installed card to the Runner's Grip" - :effect (effect (clear-wait-prompt :runner) - (move :runner target :hand true) - (system-msg (str "adds " (:title target) + :effect (req (clear-wait-prompt state :runner) + (move state :runner target :hand true) + (system-msg state side (str "adds " (:title target) " to the Runner's Grip"))) :cancel-effect (effect (clear-wait-prompt :runner) (effect-completed eid))} @@ -353,12 +359,6 @@ :effect (effect (corp-install target nil)) :msg (msg (corp-install-msg target))}]} - "Afshar" - {:implementation "Breaking both subs not restricted" - :subroutines [{:msg "make the Runner lose 2 [Credits]" - :effect (effect (lose-credits :runner 2))} - end-the-run]} - "Ashigaru" {:abilities [{:label "Gain subroutines" :msg (msg "gain " (count (:hand corp)) " subroutines")}] diff --git a/src/clj/game/cards/operations.clj b/src/clj/game/cards/operations.clj index 3f7586984f..1ffdcbf9e8 100644 --- a/src/clj/game/cards/operations.clj +++ b/src/clj/game/cards/operations.clj @@ -504,12 +504,12 @@ :choices {:req #(and (ice? %) (installed? %))} :msg (msg "give " (card-str state target {:visible false}) " additional text") - :effect (req (add-extra-sub state :corp (:cid card) (get-card state target) -1 new-sub) + :effect (req (add-extra-sub! state :corp (get-card state target) new-sub (:cid card)) (update-ice-strength state side target) (host state side (get-card state target) (assoc card :zone [:discard] :seen true :condition true))) - :leave-play (req (remove-extra-subs state :corp (:cid card) (:host card))) + :leave-play (req (remove-extra-subs! state :corp (:cid card) (:host card))) :events {:rez {:req (req (= (:cid target) (:cid (:host card)))) - :effect (req (add-extra-sub state :corp (:cid card) (get-card state target) -1 new-sub))}}}) + :effect (req (add-extra-sub! state :corp (get-card state target) new-sub (:cid card)))}}}) "Economic Warfare" {:req (req (and (last-turn? state :runner :successful-run) @@ -1795,12 +1795,12 @@ :choices {:req #(and (ice? %) (rezzed? %))} :msg (msg "make " (card-str state target) " gain Barrier and \"[Subroutine] End the run\"") :effect (req (update! state side (assoc target :subtype (combine-subtypes true (:subtype target) "Barrier"))) - (add-extra-sub state :corp (:cid card) (get-card state target) -1 new-sub) + (add-extra-sub! state :corp (get-card state target) new-sub (:cid card)) (update-ice-strength state side target) (host state side (get-card state target) (assoc card :zone [:discard] :seen true :condition true))) - :leave-play (req (remove-extra-subs state :corp (:cid card) (:host card))) + :leave-play (req (remove-extra-subs! state :corp (:cid card) (:host card))) :events {:rez {:req (req (= (:cid target) (:cid (:host card)))) - :effect (req (add-extra-sub state :corp (:cid card) (get-card state target) -1 new-sub))}}}) + :effect (req (add-extra-sub! state :corp (get-card state target) new-sub (:cid card)))}}}) "Subcontract" (letfn [(sc [i sccard] @@ -2096,11 +2096,11 @@ (rezzed? %))} :msg (msg "give " (card-str state target) " \"[Subroutine] Do 1 brain damage\" before all its other subroutines") :sub-effect (do-brain-damage 1) - :effect (req (add-extra-sub state :corp (:cid card) target 0 new-sub) + :effect (req (add-extra-sub! state :corp target new-sub (:cid card) 0) (host state side (get-card state target) (assoc card :zone [:discard] :seen true :condition true))) - :leave-play (req (remove-extra-subs state :corp (:cid card) (:host card))) + :leave-play (req (remove-extra-subs! state :corp (:host card) (:cid card))) :events {:rez {:req (req (= (:cid target) (:cid (:host card)))) - :effect (req (add-extra-sub state :corp (:cid card) (get-card state target) 0 new-sub))}}}) + :effect (req (add-extra-sub! state :corp (get-card state target) new-sub (:cid card) 0))}}}) "Witness Tampering" {:msg "remove 2 bad publicity" diff --git a/src/clj/game/core/actions.clj b/src/clj/game/core/actions.clj index 47b6b9da31..e15472550f 100644 --- a/src/clj/game/core/actions.clj +++ b/src/clj/game/core/actions.clj @@ -363,22 +363,14 @@ (let [eid (make-eid state {:source (-> args :card :title) :source-type :subroutine})] (play-subroutine state side eid args))) - ([state side eid {:keys [card subroutine targets] :as args}] + ([state side eid {:keys [card subroutine] :as args}] (let [card (get-card state card) - sub (nth (:subroutines card) subroutine nil)] - (if (or (nil? sub) - (nil? (:from-cid sub))) - (let [cdef-idx (if (nil? sub) subroutine (-> sub :data :cdef-idx)) - cdef (card-def card) - cdef-sub (get-in cdef [:subroutines cdef-idx]) - cost (:cost cdef-sub)] - (when (or (nil? cost) - (apply can-pay? state side (:title card) cost)) - (when-let [activatemsg (:activatemsg cdef-sub)] (system-msg state side activatemsg)) - (resolve-ability state side eid cdef-sub card targets))) - (when-let [sub-card (find-latest state {:cid (:from-cid sub) :side side})] - (when-let [sub-effect (:sub-effect (card-def sub-card))] - (resolve-ability state side eid sub-effect card (assoc (:data sub) :targets targets)))))))) + sub (nth (:subroutines card) subroutine nil) + sub-effect (:sub-effect sub)] + (if (and card sub-effect) + (wait-for (pay-sync state side (make-eid state eid) card (:cost sub)) + (resolve-ability state side eid sub-effect card nil)) + (effect-completed state side eid))))) ;;; Corp actions (defn trash-resource diff --git a/src/clj/game/core/ice.clj b/src/clj/game/core/ice.clj index c33f2b77df..892c0581da 100644 --- a/src/clj/game/core/ice.clj +++ b/src/clj/game/core/ice.clj @@ -3,27 +3,52 @@ (declare card-flag?) ;;; Ice subroutine functions -(defn add-extra-sub +(defn add-sub + ([ice sub] (add-sub ice sub (:cid ice) -1)) + ([ice sub cid] (add-sub ice sub cid -1)) + ([ice sub cid idx] + (let [new-sub {:label (make-label sub) + :from-cid cid + :sub-effect sub} + curr-subs (:subroutines ice []) + offset (if (= -1 idx) (count curr-subs) idx) + new-subs (apply conj (subvec curr-subs 0 offset) new-sub (subvec curr-subs offset))] + (assoc ice :subroutines new-subs)))) + +(defn add-sub! + ([state side ice sub] (update! state :corp (add-sub ice sub (:cid ice) -1))) + ([state side ice sub cid] (update! state :corp (add-sub ice sub cid -1))) + ([state side ice sub cid idx] (update! state :corp (add-sub ice sub cid idx)))) + +(defn remove-sub + "Removes a single sub from" + [ice sub] + (let [curr-subs (:subroutines ice) + new-subs (if (number? sub) + (apply conj (subvec curr-subs 0 sub) (subvec curr-subs (inc sub))) + (remove-once #(= sub %) curr-subs))] + (assoc ice :subroutines new-subs))) + +(defn remove-sub! + [state side ice sub] + (update! state :corp (remove-sub ice sub))) + +(defn add-extra-sub! "Add a run time subroutine to a piece of ice (Warden, Sub Boost, etc). -1 as the idx adds to the end." - [state side cid ice idx sub] - (let [new-sub (assoc sub :from-cid cid) - curr-subs (vec (:subroutines ice)) - offset (if (= -1 idx) (count curr-subs) idx) - new-subs (apply conj (subvec curr-subs 0 offset) new-sub (subvec curr-subs offset))] - (update! state :corp - (-> ice - (assoc :subroutines new-subs) - (assoc-in [:special :extra-subs] true))))) + ([state side ice sub] (add-extra-sub! state side ice sub (:cid ice) -1)) + ([state side ice sub cid] (add-extra-sub! state side ice sub cid -1)) + ([state side ice sub cid idx] + (add-sub! state side (assoc-in ice [:special :extra-subs] true) sub cid idx))) -(defn remove-extra-subs +(defn remove-extra-subs! "Remove runtime subroutines assigned from the given cid from a piece of ice." - [state side cid ice] + [state side ice cid] (let [curr-subs (:subroutines ice) new-subs (remove #(= cid (:from-cid %)) curr-subs) - extra-subs (some #(not (nil? (:from-cid %))) new-subs)] + extra-subs (some #(= (:cid ice) (:from-cid %)) new-subs)] (update! state :corp (-> ice - (assoc :subroutines new-subs) + (assoc :subroutines (vec new-subs)) (assoc-in [:special :extra-subs] extra-subs))))) ;;; Ice strength functions diff --git a/src/clj/game/core/installing.clj b/src/clj/game/core/installing.clj index 53a245ddee..1e81ed8709 100644 --- a/src/clj/game/core/installing.clj +++ b/src/clj/game/core/installing.clj @@ -2,7 +2,7 @@ (declare available-mu free-mu host in-play? install-locked? make-rid rez run-flag? installable-servers server->zone set-prop system-msg turn-flag? - update-breaker-strength update-ice-strength update-run-ice use-mu) + update-breaker-strength update-ice-strength update-run-ice use-mu add-sub) ;;;; Functions for the installation and deactivation of cards. @@ -12,6 +12,7 @@ [card keep-counter] (let [c (dissoc card :current-strength :abilities :subroutines :runner-abilities :corp-abilities :rezzed :special :new :added-virus-counter :subtype-target :sifr-used :sifr-target :pump :server-target) + c (assoc c :subroutines (subroutines-init card (card-def card))) c (if keep-counter c (dissoc c :counter :rec-counter :advance-counter :extra-advance-counter))] c)) @@ -70,15 +71,6 @@ (for [ab (:runner-abilities cdef)] (assoc (select-keys ab [:cost]) :label (make-label ab)))) -(defn- subroutines-init - "Initialised the subroutines associated with the card, these work as abilities" - [cdef] - (map-indexed (fn [idx sub] - {:label (make-label sub) - :from-cid nil - :data {:cdef-idx idx}}) - (:subroutines cdef))) - (defn card-init "Initializes the abilities and events of the given card." ([state side card] (card-init state side card {:resolve-effect true :init-data true})) @@ -89,11 +81,9 @@ abilities (ability-init cdef) run-abs (runner-ability-init cdef) corp-abs (corp-ability-init cdef) - subroutines (subroutines-init cdef) c (merge card (when init-data (:data cdef)) {:abilities abilities - :subroutines subroutines :runner-abilities run-abs :corp-abilities corp-abs}) c (if (number? recurring) (assoc c :rec-counter recurring) c) diff --git a/src/clj/game/core/turns.clj b/src/clj/game/core/turns.clj index e159325320..94a36c35cc 100644 --- a/src/clj/game/core/turns.clj +++ b/src/clj/game/core/turns.clj @@ -1,7 +1,7 @@ (in-ns 'game.core) (declare all-active card-flag-fn? clear-turn-register! clear-wait-prompt create-deck hand-size keep-hand mulligan - show-wait-prompt turn-message in-hand?) + show-wait-prompt turn-message in-hand? add-sub) (def game-states (atom {})) @@ -121,12 +121,21 @@ (init-hands state))))) state)) +(defn- subroutines-init + "Initialised the subroutines associated with the card, these work as abilities" + [card cdef] + (->> (:subroutines cdef) + (reduce (fn [ice sub] (add-sub ice sub (:cid ice) -1)) card) + :subroutines)) + (defn make-card "Makes or remakes (with current cid) a proper card from a server card" ([card] (make-card card (make-cid))) ([card cid] (-> card - (assoc :cid cid :implementation (card-implemented card)) + (assoc :cid cid + :implementation (card-implemented card) + :subroutines (subroutines-init (assoc card :cid cid) (card-def card))) (dissoc :setname :text :_id :influence :number :influencelimit :factioncost)))) (defn create-deck diff --git a/test/clj/game_test/cards/assets.clj b/test/clj/game_test/cards/assets.clj index d9d02a6875..c68f4e3a6a 100644 --- a/test/clj/game_test/cards/assets.clj +++ b/test/clj/game_test/cards/assets.clj @@ -4236,11 +4236,11 @@ (is (= 1 (count (:subroutines (refresh kak)))) "Kakugo starts with 1 sub") (core/rez state :corp eli) (is (= 2 (count (:subroutines (refresh eli)))) "Eli 2.0 starts with 2 subs") - (is (zero? (count (:subroutines (refresh ichi)))) "Unrezzed Ichi 2.0 starts with 0 subs") + (is (= 2 (count (:subroutines (refresh ichi)))) "Unrezzed Ichi 2.0 starts with 2 subs") (core/rez state :corp wf) (is (= 1 (count (:subroutines (refresh kak)))) "Kakugo stays at 1 sub") (is (= 3 (count (:subroutines (refresh eli)))) "Eli 2.0 gains 1 sub") - (is (zero? (count (:subroutines (refresh ichi)))) "Unrezzed Ichi 2.0 stays at 0 subs") + (is (= 2 (count (:subroutines (refresh ichi)))) "Unrezzed Ichi 2.0 stays at 2 subs") (core/rez state :corp ichi) (is (= 1 (count (:subroutines (refresh kak)))) "Kakugo stays at 1 sub") (is (= 3 (count (:subroutines (refresh eli)))) "Eli 2.0 stays at 1 sub") diff --git a/test/clj/game_test/cards/ice.clj b/test/clj/game_test/cards/ice.clj index 926fb9729e..21f0f3adb5 100644 --- a/test/clj/game_test/cards/ice.clj +++ b/test/clj/game_test/cards/ice.clj @@ -26,9 +26,9 @@ (deftest archangel ;; Archangel - accessing from R&D does not cause run to hang. (do-game - (new-game {:corp {:deck ["Archangel" "Hedge Fund"]} + (new-game {:corp {:deck ["Archangel"] + :hand ["Hedge Fund"]} :runner {:deck ["Bank Job"]}}) - (starting-hand state :corp ["Hedge Fund"]) (take-credits state :corp) (play-from-hand state :runner "Bank Job") (run-empty-server state :rd) @@ -760,6 +760,20 @@ (card-subroutine state :corp hydra 2) (is (not (:run @state)) "Hydra sub 3 ended the run when Runner is tagged"))))) +(deftest ice-wall + ;; Ice Wall + (do-game + (new-game {:corp {:deck [(qty "Hedge Fund" 5)] + :hand ["Ice Wall"]}}) + (play-from-hand state :corp "Ice Wall" "New remote") + (let [iw (get-ice state :remote1 0)] + (core/rez state :corp iw) + (take-credits state :corp) + (run-on state :remote1) + (is (:run @state)) + (card-subroutine state :corp iw 0) + (is (nil? (:run @state)))))) + (deftest iq ;; IQ - Rez cost and strength equal to cards in HQ (do-game diff --git a/test/clj/game_test/cards/identities.clj b/test/clj/game_test/cards/identities.clj index 2980e930a1..56bc51db64 100644 --- a/test/clj/game_test/cards/identities.clj +++ b/test/clj/game_test/cards/identities.clj @@ -277,14 +277,14 @@ (is (= 1 (core/access-count state :runner :rd-access)) "Should only access 1 from missed psi game"))) (testing "Shiro interaction: second sub should give Akiko 2 accesses" (do-game - (new-game {:corp {:deck [(qty "Hedge Fund" 10) "Shiro"]} + (new-game {:corp {:deck [(qty "Hedge Fund" 10)] + :hand ["Shiro"]} :runner {:id "Akiko Nisei: Head Case" :deck [(qty "Sure Gamble" 3)]}}) - (starting-hand state :corp ["Shiro"]) (play-from-hand state :corp "Shiro" "New remote") + (take-credits state :corp) (let [shiro (get-ice state :remote1 0)] (core/rez state :corp shiro) - (take-credits state :corp) (run-on state :remote1) (card-subroutine state :corp shiro 1) (click-prompt state :corp "0 [Credits]") diff --git a/test/clj/game_test/core.clj b/test/clj/game_test/core.clj index 78887496d1..227c2d76ac 100644 --- a/test/clj/game_test/core.clj +++ b/test/clj/game_test/core.clj @@ -173,11 +173,9 @@ (defn card-subroutine "Trigger a piece of ice's subroutine with the 0-based index." - ([state side card ability] (card-subroutine state side card ability nil)) - ([state side card ability targets] - (core/play-subroutine state side {:card (core/get-card state card) - :subroutine ability - :targets targets}))) + [state side card ability] + (core/play-subroutine state side {:card (core/get-card state card) + :subroutine ability})) (defn card-side-ability ([state side card ability] (card-side-ability state side card ability nil))