Skip to content

Commit

Permalink
Merge pull request #7759 from NBKelly/patchwork-cursed-rules-update
Browse files Browse the repository at this point in the history
Patchwork cursed rules update
  • Loading branch information
NoahTheDuke authored Oct 10, 2024
2 parents 724f7a2 + 6767209 commit f8d9fc3
Show file tree
Hide file tree
Showing 7 changed files with 254 additions and 75 deletions.
40 changes: 39 additions & 1 deletion src/clj/game/cards/hardware.clj
Original file line number Diff line number Diff line change
Expand Up @@ -1647,9 +1647,47 @@
:abilities [(set-autoresolve :auto-fire "Paragon")]})

(defcard "Patchwork"
(let [patchwork-ability {:once :per-turn
(let [install-word (fn [c] (if (event? c) "play" "install"))
patchwork-manual-prognosis {:cost [(->c :click 1)]
:action true
:once :per-turn
:label "Manually resolve patchwork"
:req (req (seq (:hand runner)))
:prompt "Designate a card to play or install"
:choices {:card (every-pred runner? in-hand?)}
:waiting-prompt true
:async true
:effect
(req (let [to-play target]
(update! state side (assoc-in card [:special :patchwork] true))
(continue-ability
state side
{:prompt (msg "Designate a card to trash")
:choices {:card (every-pred runner? in-hand?)
:all true}
:async true
:effect (req
(let [to-trash target]
(continue-ability
state side
(if (same-card? to-trash to-play)
{:msg (msg "trash " (:title to-trash) " from the Grip, and is no longer able to " (install-word to-trash) " it")
:async true
:effect (req (trash state side eid to-trash {:cause-card card}))}
{:msg (msg "trash " (:title to-trash) " to " (install-word to-play) " " (:title to-play) " from the Grip, paying 2 [Credits] less")
:async true
:effect (req (wait-for
(trash state side to-trash {:cause-card card})
(if (event? to-play)
(play-instant state :runner eid to-play {:cost-bonus -2})
(runner-install state :runner eid to-play {:cost-bonus -2}))))})
card nil)))}
card nil)))}
patchwork-ability {:once :per-turn
:effect (effect (update! (assoc-in card [:special :patchwork] true)))}]
{:static-abilities [(mu+ 1)]
:abilities [patchwork-manual-prognosis]
:implementation "click on patchwork to manually resolve it (for tricks)"
:interactions
{:pay-credits
{:req (req (and (#{:play :runner-install} (:source-type eid))
Expand Down
63 changes: 33 additions & 30 deletions src/clj/game/core/costs.clj
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
[game.core.gaining :refer [deduct lose]]
[game.core.moving :refer [discard-from-hand 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.pick-counters :refer [pick-credit-providing-cards pick-credit-reducers pick-virus-counters-to-spend]]
[game.core.revealing :refer [reveal]]
[game.core.rezzing :refer [derez]]
[game.core.shuffling :refer [shuffle!]]
Expand Down Expand Up @@ -149,35 +149,38 @@
(defmethod handler :credit
[cost state side eid card]
(let [provider-func #(eligible-pay-credit-cards state side eid card)]
(cond
(and (pos? (value cost))
(pos? (count (provider-func))))
(wait-for (resolve-ability state side (pick-credit-providing-cards provider-func eid (value cost) (stealth-value cost)) card nil)
(let [pay-async-result async-result]
(wait-for (trigger-event-sync
state side (make-eid state eid)
(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
{: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
state side (make-eid state eid)
(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 {:paid/msg (str "pays " (value cost) " [Credits]")
:paid/type :credit
:paid/value (value cost)})))
:else
(complete-with-result state side eid {:paid/msg "pays 0 [Credits]"
:paid/type :credit
:paid/value 0}))))
(wait-for
(resolve-ability state side (pick-credit-reducers provider-func eid (value cost) (stealth-value cost)) card nil)
(let [updated-cost (max 0 (- (value cost) (or (:reduction async-result) 0)))]
(cond
(and (pos? updated-cost)
(pos? (count (provider-func))))
(wait-for (resolve-ability state side (pick-credit-providing-cards provider-func eid updated-cost (stealth-value cost)) card nil)
(let [pay-async-result async-result]
(wait-for (trigger-event-sync
state side (make-eid state eid)
(if (= side :corp) :corp-spent-credits :runner-spent-credits)
updated-cost)
(swap! state update-in [:stats side :spent :credit] (fnil + 0) updated-cost)
(complete-with-result state side eid
{:paid/msg (str "pays " (:msg pay-async-result))
:paid/type :credit
:paid/value (:number pay-async-result)
:paid/targets (:targets pay-async-result)}))))
(pos? updated-cost)
(do (lose state side :credit updated-cost)
(wait-for (trigger-event-sync
state side (make-eid state eid)
(if (= side :corp) :corp-spent-credits :runner-spent-credits)
updated-cost)
(swap! state update-in [:stats side :spent :credit] (fnil + 0) updated-cost)
(complete-with-result state side eid {:paid/msg (str "pays " updated-cost " [Credits]")
:paid/type :credit
:paid/value updated-cost})))
:else
(complete-with-result state side eid {:paid/msg "pays 0 [Credits]"
:paid/type :credit
:paid/value 0}))))))

;; X Credits
(defmethod value :x-credits [_] 0)
Expand Down
50 changes: 48 additions & 2 deletions src/clj/game/core/pick_counters.clj
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,17 @@
:msg message
:targets (keep #(:card (second %)) selected-cards)})))

(defn- pick-reducer-triggers
[state side eid current-cards selected-cards counter-count message]
(if-let [[_ selected] (first current-cards)]
(if-let [{:keys [card number]} selected]
(wait-for (trigger-event-sync state side :counter-added (get-card state card) number)
(pick-counter-triggers state side eid (rest current-cards) selected-cards counter-count message))
(pick-counter-triggers state side eid (rest current-cards) selected-cards counter-count message))
(complete-with-result state side eid {:number counter-count
:msg message
:targets (keep #(:card (second %)) selected-cards)})))

(defn pick-virus-counters-to-spend
"Pick virus counters to spend. For use with Freedom Khumalo and virus breakers, and any other relevant cards.
This function returns a map for use with resolve-ability or continue-ability.
Expand Down Expand Up @@ -80,6 +91,40 @@
(req (update! state side (assoc-in card [:counter counter-type] (dec (get-counters card counter-type))))
(complete-with-result state side eid 1)))

(defn pick-credit-reducers
"Similar to pick-credit-providing-cards, but this happens first and is (currently) only used for patchwork"
([provider-func outereid] (pick-credit-reducers provider-func outereid nil 0 (hash-map)))
([provider-func outereid target-count] (pick-credit-reducers provider-func outereid target-count 0 (hash-map)))
([provider-func outereid target-count stealth-target] (pick-credit-reducers provider-func outereid target-count stealth-target (hash-map)))
([provider-func outereid target-count stealth-target selected-cards]
(let [counter-count (reduce + 0 (map #(:number (second %) 0) selected-cards))
provider-cards (provider-func)
discount-provider (filter #(get-in (card-def %) [:interactions :pay-credits :cost-reduction]) provider-cards)]
(if (empty? discount-provider)
{:async true
:effect (req (complete-with-result state side eid {:reduction counter-count
:targets (keep #(:card (second %)) selected-cards)}))}
{:async true
:prompt (str "Choose a cost-reducing card")
:choices {:card #(in-coll? (map :cid discount-provider) (:cid %))}
:effect (req (let [pay-credits-type (-> target card-def :interactions :pay-credits :type)
pay-function (if (= :custom pay-credits-type)
(-> target card-def :interactions :pay-credits :custom)
(take-counters-of-type pay-credits-type))
custom-ability {:async true
:effect pay-function}
neweid (make-eid state outereid)
providing-card target]
(wait-for (resolve-ability state side neweid custom-ability providing-card [card])
(continue-ability state side
(pick-credit-reducers
provider-func eid target-count stealth-target
(update selected-cards (:cid providing-card)
#(assoc % :card providing-card :number (+ (:number % 0) async-result))))
card targets))))
:cancel-effect (req (complete-with-result state side eid {:reduction counter-count
:targets (keep #(:card (second %)) selected-cards)}))}))))

(defn pick-credit-providing-cards
"Similar to pick-virus-counters-to-spend. Works on :recurring and normal credits."
([provider-func outereid] (pick-credit-providing-cards provider-func outereid nil 0 (hash-map)))
Expand All @@ -90,8 +135,9 @@
selected-stealth (filter #(has-subtype? (:card (second %)) "Stealth") selected-cards)
stealth-count (reduce + 0 (map #(:number (second %) 0) selected-stealth))
provider-cards (if (= (- counter-count target-count) (- stealth-count stealth-target))
(filter #(has-subtype? % "Stealth") (provider-func))
(provider-func))
(filter #(has-subtype? % "Stealth") (provider-func))
(provider-func))
provider-cards (filter #(not (get-in (card-def %) [:interactions :pay-credits :cost-reduction])) provider-cards)
pay-rest (req
(if (and (<= (- target-count counter-count) (get-in @state [side :credit]))
(<= stealth-target stealth-count))
Expand Down
Loading

0 comments on commit f8d9fc3

Please sign in to comment.