Skip to content

Commit 571b388

Browse files
authored
Merge pull request #7945 from NBKelly/sometimes-automate-skorpios
be smart with skorp automation
2 parents f3c437e + 12dd774 commit 571b388

File tree

6 files changed

+155
-63
lines changed

6 files changed

+155
-63
lines changed

src/clj/game/cards/hardware.clj

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -407,14 +407,16 @@
407407
:req (req (and (grip-or-stack-trash? targets)
408408
(first-trash? state grip-or-stack-trash?)))
409409
:prompt "Choose 1 trashed card to add to the bottom of the stack"
410-
:choices (req (conj (sort (map :title (map :card targets))) "No action"))
410+
:choices (req (conj (sort (keep #(->> (:moved-card %) :title) targets)) "No action"))
411411
:async true
412412
:effect (req (if (= "No action" target)
413413
(effect-completed state side eid)
414414
(do (system-msg state side
415415
(str "uses " (:title card) " to add " target
416416
" to the bottom of the stack"))
417-
(move state side (find-card target (:discard (:runner @state))) :deck)
417+
;; note - need to search in reverse order, to remove the NEWEST copy of the card
418+
;; this is for interactions with the price, etc
419+
(move state side (find-card target (reverse (:discard (:runner @state)))) :deck)
418420
(effect-completed state side eid))))}]
419421
{:events [(assoc triggered-ability :event :runner-trash)
420422
(assoc triggered-ability :event :corp-trash)]

src/clj/game/cards/identities.clj

Lines changed: 65 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,19 @@
88
[game.core.card :refer [agenda? asset? can-be-advanced?
99
corp-installable-type? corp? faceup? get-advancement-requirement
1010
get-agenda-points get-card get-counters get-title get-zone hardware? has-subtype?
11-
has-any-subtype? ice? in-discard? in-hand? in-play-area? in-rfg? installed? is-type?
11+
has-any-subtype? ice? in-discard? in-deck? in-hand? in-play-area? in-rfg? installed? is-type?
1212
operation? program? resource? rezzed? runner? upgrade?]]
1313
[game.core.charge :refer [charge-ability]]
1414
[game.core.cost-fns :refer [install-cost play-cost
1515
rez-additional-cost-bonus rez-cost]]
1616
[game.core.damage :refer [chosen-damage corp-can-choose-damage? damage
1717
enable-corp-damage-choice]]
18-
[game.core.def-helpers :refer [corp-recur defcard offer-jack-out with-revealed-hand]]
18+
[game.core.def-helpers :refer [choose-one-helper corp-recur defcard offer-jack-out with-revealed-hand]]
1919
[game.core.drawing :refer [draw]]
2020
[game.core.effects :refer [register-lingering-effect is-disabled?]]
2121
[game.core.eid :refer [effect-completed get-ability-targets is-basic-advance-action? make-eid]]
2222
[game.core.engine :refer [not-used-once? pay register-events register-once resolve-ability trigger-event]]
23-
[game.core.events :refer [event-count first-event?
23+
[game.core.events :refer [event-count first-event? first-trash?
2424
first-successful-run-on-server? no-event? not-last-turn? run-events run-event-count turn-events]]
2525
[game.core.expose :refer [expose]]
2626
[game.core.finding :refer [find-latest]]
@@ -1916,14 +1916,68 @@
19161916
:effect (effect (expose eid target))}]})
19171917

19181918
(defcard "Skorpios Defense Systems: Persuasive Power"
1919-
{:implementation "Manually triggered, no restriction on which cards in Heap can be targeted. Cannot use on in progress run event"
1920-
:abilities [{:label "Remove a card in the Heap that was just trashed from the game"
1921-
:waiting-prompt true
1922-
:prompt "Choose a card in the Heap that was just trashed"
1923-
:once :per-turn
1924-
:choices (req (cancellable (:discard runner)))
1925-
:msg (msg "remove " (:title target) " from the game")
1926-
:effect (req (move state :runner target :rfg))}]})
1919+
(let [set-resolution-mode
1920+
(fn [x] {:label x
1921+
:effect (req (update! state side (assoc-in card [:special :resolution-mode] x))
1922+
(toast state :corp (str "Set Skorpios resolution to " x " mode"))
1923+
(update! state side (assoc (get-card state card) :card-target x)))})
1924+
grip-or-stack-trash?
1925+
(fn [ctx]
1926+
(some #(and (runner? (:card %))
1927+
(or (in-hand? (:card %))
1928+
(in-deck? (:card %))))
1929+
ctx))
1930+
relevant-cards-general #{"Labor Rights" "The Price"}
1931+
relevant-cards-trashed #{"I've Had Worse" "Strike Fund" "Steelskin Scarring"}
1932+
trigger-ability-req (req (let [res-type (get-in (get-card state card) [:special :resolution-mode])
1933+
valid-cards (mapv #(get-card state %) (filter runner? context))]
1934+
(and (some runner? context)
1935+
(cond
1936+
;; manual: do nothing
1937+
(= res-type "Automatic") true
1938+
;; there's either:
1939+
(= res-type "Smart")
1940+
(or
1941+
;; 1) a relevant card resoluton
1942+
(contains? relevant-cards-general (->> runner :play-area first :title))
1943+
;; 2) a buffer drive that may resolve
1944+
(and (some #(= (:title %) "Buffer Drive") (all-installed state :runner))
1945+
(grip-or-stack-trash? (map (fn [x] {:card x}) context))
1946+
(zero? (+ (event-count state nil :runner-trash grip-or-stack-trash?)
1947+
(event-count state nil :corp-trash grip-or-stack-trash?)
1948+
(event-count state nil :game-trash grip-or-stack-trash?))))
1949+
;; 3) a program among the trashed cards
1950+
(some #(->> % program?) context)
1951+
;; 4) a relevant card is trashed (Steelskin, Strike fund, I've Had Worse)
1952+
(some #(contains? relevant-cards-trashed %) (map #(->> % :title) context)))
1953+
:else nil))))
1954+
triggered-ability {:once :per-turn
1955+
:player :corp
1956+
:event :pre-trash-interrupt
1957+
:waiting-prompt true
1958+
:req trigger-ability-req
1959+
:prompt "Remove a card from the game?"
1960+
:choices (req (cancellable context))
1961+
:msg (msg "remove " (:title target) " from the game")
1962+
:async true
1963+
:effect (req (move state :runner target :rfg)
1964+
(effect-completed state side eid))}]
1965+
{:implementation "Switch between Manual, \"Smart\", and Automatic resolution by using the ability on the card"
1966+
:events [(assoc (set-resolution-mode "Smart")
1967+
:event :pre-first-turn
1968+
:req (req (= side :corp)))
1969+
triggered-ability]
1970+
:abilities [(choose-one-helper
1971+
{:optional true
1972+
:label "Set resolution mode"}
1973+
(mapv (fn [x] {:option x :ability (set-resolution-mode x)}) ["Manual" "Smart" "Automatic"]))
1974+
{:label "Remove a card in the Heap that was just trashed from the game"
1975+
:waiting-prompt true
1976+
:prompt "Choose a card in the Heap that was just trashed"
1977+
:once :per-turn
1978+
:choices (req (cancellable (:discard runner)))
1979+
:msg (msg "remove " (:title target) " from the game")
1980+
:effect (req (move state :runner target :rfg))}]}))
19271981

19281982
(defcard "Spark Agency: Worldswide Reach"
19291983
{:events [{:event :rez

src/clj/game/core/moving.clj

Lines changed: 51 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -402,51 +402,57 @@
402402
([state side eid cards {:keys [accessed cause cause-card keep-server-alive game-trash suppress-checkpoint] :as args}]
403403
(if (empty? (filter identity cards))
404404
(effect-completed state side eid)
405-
(wait-for (prevent-trash state side (make-eid state eid) cards args)
406-
(let [trashlist async-result
407-
_ (update-current-ice-to-trash state trashlist)
408-
trash-event (get-trash-event side game-trash)
409-
;; No card should end up in the opponent's discard pile, so instead
410-
;; of using `side`, we use the card's `:side`.
411-
move-card (fn [card]
412-
(move state (to-keyword (:side card)) card :discard {:keep-server-alive keep-server-alive}))
413-
;; If the trashed card is installed, update all of the indicies
414-
;; of the other installed cards in the same location
415-
update-indicies (fn [card]
416-
(when (installed? card)
417-
(update-installed-card-indices state side (:zone card))))
418-
;; Perform the move of the cards from their current location to
419-
;; the discard. At the same time, gather their `:trash-effect`s
420-
;; to be used in the simult event later.
421-
moved-cards (reduce
422-
(fn [acc card]
423-
(if-let [card (get-card? state card)]
424-
(let [_ (set-duration-on-trash-events state card trash-event)
425-
moved-card (move-card card)
426-
trash-effect (get-trash-effect state side eid card args)]
427-
(update-indicies card)
428-
(conj acc [moved-card trash-effect]))
429-
acc))
430-
[]
431-
trashlist)]
432-
(swap! state update-in [:trash :trash-list] dissoc eid)
433-
(when (and side (seq (remove #{side} (map #(to-keyword (:side %)) trashlist))))
434-
(swap! state assoc-in [side :register :trashed-card] true))
435-
;; Pseudo-shuffle archives. Keeps seen cards in play order and shuffles unseen cards.
436-
(swap! state assoc-in [:corp :discard]
437-
(vec (sort-by #(if (:seen %) -1 1) (get-in @state [:corp :discard]))))
438-
(let [eid (make-result eid (mapv first moved-cards))]
439-
(doseq [[card trash-effect] moved-cards
440-
:when trash-effect]
441-
(register-pending-event state trash-event card trash-effect))
442-
(doseq [trashed-card trashlist]
443-
(queue-event state trash-event {:card trashed-card
444-
:cause cause
445-
:cause-card (trim-cause-card cause-card)
446-
:accessed accessed}))
447-
(if suppress-checkpoint
448-
(effect-completed state nil eid)
449-
(checkpoint state nil eid {:duration trash-event}))))))))
405+
(wait-for
406+
(prevent-trash state side (make-eid state eid) cards args)
407+
(let [trashlist async-result
408+
_ (update-current-ice-to-trash state trashlist)]
409+
(wait-for
410+
(trigger-event-sync state side :pre-trash-interrupt trashlist)
411+
(let [trash-event (get-trash-event side game-trash)
412+
;; No card should end up in the opponent's discard pile, so instead
413+
;; of using `side`, we use the card's `:side`.
414+
move-card (fn [card]
415+
(move state (to-keyword (:side card)) card :discard {:keep-server-alive keep-server-alive}))
416+
;; If the trashed card is installed, update all of the indicies
417+
;; of the other installed cards in the same location
418+
update-indicies (fn [card]
419+
(when (installed? card)
420+
(update-installed-card-indices state side (:zone card))))
421+
;; Perform the move of the cards from their current location to
422+
;; the discard. At the same time, gather their `:trash-effect`s
423+
;; to be used in the simult event later.
424+
moved-cards (reduce
425+
(fn [acc card]
426+
(if-let [card (get-card? state card)]
427+
(let [_ (set-duration-on-trash-events state card trash-event)
428+
moved-card (move-card card)
429+
trash-effect (get-trash-effect state side eid card args)]
430+
(update-indicies card)
431+
(conj acc {:moved-card moved-card
432+
:trash-effect trash-effect
433+
:old-card card}))
434+
(conj acc {:old-card card})))
435+
[]
436+
trashlist)]
437+
(swap! state update-in [:trash :trash-list] dissoc eid)
438+
(when (and side (seq (remove #{side} (map #(to-keyword (:side %)) trashlist))))
439+
(swap! state assoc-in [side :register :trashed-card] true))
440+
;; Pseudo-shuffle archives. Keeps seen cards in play order and shuffles unseen cards.
441+
(swap! state assoc-in [:corp :discard]
442+
(vec (sort-by #(if (:seen %) -1 1) (get-in @state [:corp :discard]))))
443+
(let [eid (make-result eid (vec (keep :moved-card moved-cards)))]
444+
(doseq [{:keys [moved-card trash-effect]} moved-cards
445+
:when trash-effect]
446+
(register-pending-event state trash-event moved-card trash-effect))
447+
(doseq [{:keys [old-card moved-card]} moved-cards]
448+
(queue-event state trash-event {:card old-card
449+
:moved-card moved-card
450+
:cause cause
451+
:cause-card (trim-cause-card cause-card)
452+
:accessed accessed}))
453+
(if suppress-checkpoint
454+
(effect-completed state nil eid)
455+
(checkpoint state nil eid {:duration trash-event}))))))))))
450456

451457
(defmethod engine/move* :trash-cards [state side eid _action cards args]
452458
(trash-cards state side eid cards args))

test/clj/game/cards/events_test.clj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -953,7 +953,7 @@
953953
:runner {:deck [(qty "By Any Means" 2)]}})
954954
(take-credits state :corp)
955955
(play-from-hand state :runner "By Any Means")
956-
(card-ability state :corp (get-in @state [:corp :identity]) 0)
956+
(card-ability state :corp (get-in @state [:corp :identity]) 1)
957957
(click-prompt state :corp (find-card "By Any Means" (:discard (get-runner))))
958958
(is (= 1 (count (get-in @state [:runner :rfg]))) "By Any Means RFGed")
959959
(is (zero? (count (:discard (get-corp)))) "Nothing trashed yet")

test/clj/game/cards/hardware_test.clj

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1306,6 +1306,25 @@
13061306
(is (find-card "I've Had Worse" (:deck (get-runner))))
13071307
(is (find-card "Buffer Drive" (get-hardware state)))))))
13081308

1309+
(deftest buffer-drive-dont-offer-duplicate-option-with-the-price
1310+
(do-game
1311+
(new-game {:runner {:discard ["Boomerang"] :deck ["Ika" "Boomerang" "Sure Gamble"] :hand ["The Price" "Buffer Drive"]}})
1312+
(take-credits state :corp)
1313+
(play-from-hand state :runner "Buffer Drive")
1314+
(play-from-hand state :runner "The Price")
1315+
(click-prompt state :runner "Boomerang")
1316+
(is (not-any? #{"Boomerang"} (prompt-buttons :runner)) "No offer to install boomerang")))
1317+
1318+
(deftest buffer-drive-plays-nice-with-skorpios
1319+
(do-game
1320+
(new-game {:corp {:id "Skorpios Defense Systems: Persuasive Power"}
1321+
:runner {:deck ["Ika" "Boomerang" "Sure Gamble"] :hand ["The Price" "Buffer Drive"]}})
1322+
(take-credits state :corp)
1323+
(play-from-hand state :runner "Buffer Drive")
1324+
(play-from-hand state :runner "The Price")
1325+
(click-prompt state :corp "Boomerang")
1326+
(is (not-any? #{"Boomerang"} (prompt-buttons :runner)) "No offer to install boomerang")))
1327+
13091328
(deftest capstone
13101329
;; Capstone
13111330
(do-game

test/clj/game/cards/identities_test.clj

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4725,7 +4725,7 @@
47254725
(is (= 12 (:credit (get-runner))) "Gained 4cr")
47264726
(is (= 12 (get-counters (get-resource state 0) :credit)) "12 cr on Temujin")))
47274727

4728-
(deftest skorpios-defense-systems-persuasive-power
4728+
(deftest skorpios-defense-systems-persuasive-power-manual-usage
47294729
; Remove a card from game when it moves to discard once per round
47304730
(do-game
47314731
(new-game {:corp {:id "Skorpios Defense Systems: Persuasive Power"
@@ -4734,11 +4734,13 @@
47344734
(play-from-hand state :corp "Hedge Fund")
47354735
(dotimes [_ 4] (core/move state :corp (first (:hand (get-corp))) :deck))
47364736
(take-credits state :corp)
4737+
(card-ability state :corp (get-in @state [:corp :identity]) 0)
4738+
(click-prompt state :corp "Manual")
47374739
(play-from-hand state :runner "Lucky Find")
47384740
(play-from-hand state :runner "The Maker's Eye")
47394741
(is (= [:rd] (:server (get-run))))
47404742
; Don't allow a run-event in progress to be targeted #2963
4741-
(card-ability state :corp (get-in @state [:corp :identity]) 0)
4743+
(card-ability state :corp (get-in @state [:corp :identity]) 1)
47424744
(is (empty? (filter #(= "The Maker's Eye" (:title %)) (-> (get-corp) :prompt first :choices))) "No Maker's Eye choice")
47434745
(click-prompt state :corp "Cancel")
47444746
(run-continue state)
@@ -4749,12 +4751,21 @@
47494751
(is (accessing state "Quandary"))
47504752
(click-prompt state :runner "No action")
47514753
(is (not (:run @state)))
4752-
(card-ability state :corp (get-in @state [:corp :identity]) 0)
4754+
(card-ability state :corp (get-in @state [:corp :identity]) 1)
47534755
(click-prompt state :corp (find-card "The Maker's Eye" (:discard (get-runner))))
47544756
(is (= 1 (count (get-in @state [:runner :rfg]))) "One card RFGed")
4755-
(card-ability state :corp (get-in @state [:corp :identity]) 0)
4757+
(card-ability state :corp (get-in @state [:corp :identity]) 1)
47564758
(is (no-prompt? state :corp) "Cannot use Skorpios twice")))
47574759

4760+
(deftest skorpios-smart-test
4761+
(do-game
4762+
(new-game {:corp {:id "Skorpios Defense Systems: Persuasive Power"}
4763+
:runner {:deck ["Corroder"]}})
4764+
(damage state :corp :brain 1)
4765+
(click-prompt state :corp "Corroder")
4766+
(is (= 1 (count (get-in @state [:runner :rfg]))) "One card RFGed")))
4767+
4768+
47584769
(deftest spark-agency-worldswide-reach
47594770
;; Spark Agency - Rezzing advertisements
47604771
(do-game

0 commit comments

Comments
 (0)