Skip to content

Commit c6d3173

Browse files
authored
Merge pull request #6581 from NoahTheDuke/nb/overinstall-unique-and-consoles
Allow over installing uniques and consoles
2 parents 4c5a321 + 3a3cee3 commit c6d3173

File tree

9 files changed

+106
-70
lines changed

9 files changed

+106
-70
lines changed

src/clj/game/core.clj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,7 @@
223223
resource?
224224
rezzed?
225225
runner?
226+
unique?
226227
upgrade?
227228
virus-program?])
228229

src/clj/game/core/board.clj

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,8 @@
127127
(->> (concat [(-> @state side :identity)]
128128
(all-active-installed state side)
129129
(-> @state side :current)
130-
(filter (if (= :corp side) operation? event?) (-> @state side :play-area))
130+
(filter (if (= :corp side) operation? event?)
131+
(-> @state side :play-area))
131132
(when (= side :corp)
132133
(-> @state :corp :scored)))
133134
(filter identity)

src/clj/game/core/engine.clj

Lines changed: 39 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
[clojure.stacktrace :refer [print-stack-trace]]
55
[clojure.string :as str]
66
[cond-plus.core :refer [cond+]]
7-
[game.core.board :refer [clear-empty-remotes all-installed-runner-type all-installed]]
8-
[game.core.card :refer [active? facedown? faceup? get-card get-cid get-title in-discard? in-hand? installed? rezzed? program? console?]]
7+
[game.core.board :refer [clear-empty-remotes all-installed-runner-type all-active-installed]]
8+
[game.core.card :refer [active? facedown? faceup? get-card get-cid get-title in-discard? in-hand? installed? rezzed? program? console? unique?]]
99
[game.core.card-defs :refer [card-def]]
1010
[game.core.effects :refer [get-effect-maps unregister-floating-effects]]
1111
[game.core.eid :refer [complete-with-result effect-completed make-eid]]
@@ -18,7 +18,8 @@
1818
[game.macros :refer [continue-ability req wait-for]]
1919
[game.utils :refer [dissoc-in distinct-by in-coll? remove-once same-card? server-cards side-str to-keyword]]
2020
[jinteki.utils :refer [other-side]]
21-
[game.core.memory :refer [update-mu]]))
21+
[game.core.memory :refer [update-mu]]
22+
[game.core.to-string :refer [card-str]]))
2223

2324
;; resolve-ability docs
2425

@@ -1017,21 +1018,46 @@
10171018
(unregister-floating-events state nil duration))
10181019
(effect-completed state nil eid)))
10191020

1020-
(defn check-consoles
1021+
(defn get-old-uniques
1022+
[state side]
1023+
(some->> (all-active-installed state side)
1024+
(filter unique?)
1025+
(group-by :title)
1026+
(reduce-kv
1027+
(fn [acc _title cards]
1028+
(if (< 1 (count cards))
1029+
(conj! acc (butlast cards))
1030+
acc))
1031+
(transient []))
1032+
persistent!
1033+
seq
1034+
(apply concat)))
1035+
1036+
(defn check-unique-and-consoles
10211037
"d. If 2 or more unique (◆) cards with the same name are active, for each such name,
10221038
all of those cards except the one that became active most recently are trashed. If 2
10231039
or more console cards are installed under the control of the same player, for each
10241040
such player, all of those cards except the one that became active most recently are
10251041
trashed."
10261042
[state _ eid]
1027-
(let [consoles (->> (get-in @state [:runner :rig :hardware])
1028-
(filter console?))]
1029-
(if (< 1 (count consoles))
1030-
(wait-for (move* state nil (make-eid state eid) :trash-cards (butlast consoles) {:game-trash true
1031-
:unpreventable true})
1032-
(doseq [card (butlast consoles)]
1033-
(system-say state :runner (str (:title card) " is trashed."))
1034-
(effect-completed state nil eid)))
1043+
(let [corp-uniques (get-old-uniques state :corp)
1044+
runner-uniques (get-old-uniques state :runner)
1045+
consoles (->> (get-in @state [:runner :rig :hardware])
1046+
(filter console?))
1047+
consoles (when (< 1 (count consoles))
1048+
(butlast consoles))
1049+
cards-to-trash (-> (concat corp-uniques runner-uniques consoles)
1050+
distinct
1051+
seq)]
1052+
(if cards-to-trash
1053+
(wait-for (move* state nil (make-eid state eid)
1054+
:trash-cards cards-to-trash
1055+
{:game-trash true
1056+
:unpreventable true})
1057+
(doseq [card cards-to-trash]
1058+
(system-say state (to-keyword (:side card))
1059+
(str (card-str state card) " is trashed."))
1060+
(effect-completed state nil eid)))
10351061
(effect-completed state nil eid))))
10361062

10371063
(defn check-restrictions
@@ -1072,7 +1098,7 @@
10721098
(check-win-by-agenda state)
10731099
;; d: uniqueness/console check
10741100
(wait-for
1075-
(check-consoles state nil)
1101+
(check-unique-and-consoles state nil)
10761102
;; e: restrictions on card abilities or game rules, MU
10771103
(wait-for
10781104
(check-restrictions state nil (make-eid state eid))

src/clj/game/core/installing.clj

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
(:require
33
[cond-plus.core :refer [cond+]]
44
[game.core.agendas :refer [update-advancement-requirement]]
5-
[game.core.board :refer [all-active-installed all-installed get-remotes in-play? installable-servers server->zone all-installed-runner-type]]
5+
[game.core.board :refer [all-installed get-remotes installable-servers server->zone all-installed-runner-type]]
66
[game.core.card :refer [agenda? asset? convert-to-condition-counter corp? event? get-card get-zone has-subtype? ice? operation? program? resource? rezzed? installed?]]
77
[game.core.card-defs :refer [card-def]]
88
[game.core.cost-fns :refer [ignore-install-cost? install-additional-cost-bonus install-cost]]
@@ -276,24 +276,15 @@
276276
"Checks if the specified card can be installed.
277277
Checks uniqueness of card and installed console.
278278
Returns true if there are no problems
279-
Returns :console if Console check fails
280-
Returns :unique if uniqueness check fails
281279
Returns :req if card-def :req check fails
282280
!! NB: This should only be used in a check with `true?` as all return values are truthy"
283281
[state side card facedown]
284-
(let [card-req (:req (card-def card))
285-
uniqueness (:uniqueness card)]
282+
(let [card-req (:req (card-def card))]
286283
(cond
287284
;; Can always install a card facedown
288285
facedown true
289-
;; Console check
290-
(and (has-subtype? card "Console")
291-
(some #(has-subtype? % "Console") (all-active-installed state :runner)))
292-
:console
293286
;; Installing not locked
294287
(install-locked? state :runner) :lock-install
295-
;; Uniqueness check
296-
(and uniqueness (in-play? state card)) :unique
297288
;; Req check
298289
(and card-req (not (card-req state side (make-eid state) card nil))) :req
299290
;; The card's zone is locked
@@ -309,13 +300,8 @@
309300
reason-toast #(do (when-not no-toast (toast state side % "warning")) false)
310301
title (:title card)]
311302
(case reason
312-
:unique
313-
(reason-toast (str "Cannot install a second copy of " title " since it is unique."
314-
" Please trash currently installed copy first"))
315303
:lock-install
316304
(reason-toast (str "Unable to install " title " since installing is currently locked"))
317-
:console
318-
(reason-toast (str "Unable to install " title ": an installed console prevents the installation of a replacement"))
319305
:req
320306
(reason-toast (str "Installation requirements are not fulfilled for " title))
321307
:locked-zone

src/cljc/game/core/card.cljc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,10 @@
290290
(and (hardware? card)
291291
(has-subtype? card "Console")))
292292

293+
(defn unique?
294+
[card]
295+
(:uniqueness card))
296+
293297
(defn corp-installable-type?
294298
"Is the card of an acceptable type to be installed in a server"
295299
[card]

test/clj/game/cards/operations_test.clj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -901,7 +901,7 @@
901901
(is (= 3 (count (:discard (get-runner)))) "2 cards lost to brain damage")
902902
(is (= 3 (:brain-damage (get-runner))) "Brainchips didn't do additional brain dmg"))))
903903

904-
(deftest ^:kaocha/pending digital-rights-management
904+
(deftest ^:kaocha/pending digital-rights-management-cannot-score-agenda-installed-after-playing
905905
;; Cannot score Agenda installed after playing DRM
906906
(do-game
907907
(new-game {:corp {:hand [(qty "Digital Rights Management" 2) "Project Vitruvius" (qty "Hedge Fund" 2)]

test/clj/game/core/engine_test.clj

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,3 +228,15 @@
228228
(click-card state :runner (get-runner-facedown state 0))
229229
(is (last-log-contains? state "Mirror is trashed."))
230230
(is (find-card "Mirror" (:discard (get-runner))))))
231+
232+
(deftest install-second-console-trashes-first
233+
(do-game
234+
(new-game {:corp {:hand ["Hedge Fund"]
235+
:deck [(qty "Hedge Fund" 100)]}
236+
:runner {:hand ["Mirror" "Box-E"]
237+
:credits 100}})
238+
(take-credits state :corp)
239+
(play-from-hand state :runner "Mirror")
240+
(play-from-hand state :runner "Box-E")
241+
(is (= "Box-E" (:title (get-hardware state 0))))
242+
(is (last-log-contains? state "Mirror is trashed."))))

test/clj/game/core/rules_test.clj

Lines changed: 30 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -27,36 +27,44 @@
2727
(is (= (- 5 (:cost gord)) (:credit (get-runner))) "Program cost was applied")
2828
(is (= (- 4 (:memoryunits gord)) (core/available-mu state)) "Program MU was applied"))))
2929

30-
(deftest runner-installing-uniques
31-
;; Installing a copy of an active unique Runner card is prevented
30+
(deftest installing-second-unique-trashes-first-unique-test
3231
(do-game
33-
(new-game {:runner {:deck [(qty "Kati Jones" 2) (qty "Scheherazade" 2)
34-
"Off-Campus Apartment" (qty "Hivemind" 2)]}})
32+
(new-game {:runner {:hand [(qty "Kati Jones" 2)]
33+
:credits 100}})
34+
(take-credits state :corp)
35+
(play-from-hand state :runner "Kati Jones")
36+
(play-from-hand state :runner "Kati Jones")
37+
(is (find-card "Kati Jones" (get-resource state)))
38+
(is (last-log-contains? state "Kati Jones is trashed."))))
39+
40+
(deftest installing-second-unique-on-off-campus-apartment-trashes-first-test
41+
(do-game
42+
(new-game {:runner {:hand [(qty "Kati Jones" 2) "Off-Campus Apartment"]
43+
:credits 100}})
3544
(take-credits state :corp)
36-
(core/gain state :runner :click 1 :memory 2)
37-
(draw state :runner 2)
3845
(play-from-hand state :runner "Kati Jones")
3946
(play-from-hand state :runner "Off-Campus Apartment")
40-
(play-from-hand state :runner "Scheherazade")
41-
(let [oca (get-resource state 1)
42-
scheh (get-program state 0)]
43-
(card-ability state :runner scheh 0)
44-
(click-card state :runner (find-card "Hivemind" (:hand (get-runner))))
45-
(is (= "Hivemind" (:title (first (:hosted (refresh scheh))))) "Hivemind hosted on Scheherazade")
46-
(play-from-hand state :runner "Kati Jones")
47-
(is (= 1 (:click (get-runner))) "Not charged a click")
48-
(is (= 2 (count (get-resource state))) "2nd copy of Kati couldn't install")
47+
(let [oca (get-resource state 1)]
4948
(card-ability state :runner oca 0)
5049
(click-card state :runner (find-card "Kati Jones" (:hand (get-runner))))
51-
(is (empty? (:hosted (refresh oca))) "2nd copy of Kati couldn't be hosted on OCA")
52-
(is (= 1 (:click (get-runner))) "Not charged a click")
53-
(click-prompt state :runner "Done")
54-
(play-from-hand state :runner "Hivemind")
55-
(is (= 1 (count (get-program state))) "2nd copy of Hivemind couldn't install")
50+
(is (find-card "Kati Jones" (:hosted (refresh oca))))
51+
(is (= "Kati Jones" (:title (get-discarded state :runner))))
52+
(is (last-log-contains? state "Kati Jones is trashed.")))))
53+
54+
(deftest installing-second-hivemind-trashes-hosted-hivemind-test
55+
(do-game
56+
(new-game {:runner {:hand ["Scheherazade" (qty "Hivemind" 2)]
57+
:credits 100}})
58+
(take-credits state :corp)
59+
(play-from-hand state :runner "Scheherazade")
60+
(let [scheh (get-program state 0)]
5661
(card-ability state :runner scheh 0)
5762
(click-card state :runner (find-card "Hivemind" (:hand (get-runner))))
58-
(is (= 1 (count (:hosted (refresh scheh)))) "2nd copy of Hivemind couldn't be hosted on Scheherazade")
59-
(is (= 1 (:click (get-runner))) "Not charged a click"))))
63+
(is (find-card "Hivemind" (:hosted (refresh scheh))) "Hivemind hosted on Scheherazade")
64+
(play-from-hand state :runner "Hivemind")
65+
(is (= "Hivemind" (:title (get-discarded state :runner))))
66+
(is (last-log-contains? state "Hivemind hosted on Scheherazade is trashed."))
67+
(is (empty? (:hosted (refresh scheh))) "Hivemind hosted on Scheherazade"))))
6068

6169
(deftest deactivate-program
6270
;; deactivate - Program; ensure MU are restored

test/clj/game/core/say_test.clj

Lines changed: 15 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -273,20 +273,18 @@
273273

274274
(testing "/unique"
275275
(let [user {:username "Runner"}]
276-
(testing "Works with Wireless Net Pavillion"
277-
(do-game
278-
(new-game {:runner {:hand [(qty "Wireless Net Pavilion" 2)]}})
279-
(take-credits state :corp)
280-
(let [wnp1 (nth (:hand (get-runner)) 0)
281-
wnp2 (nth (:hand (get-runner)) 1)]
282-
(core/play state :runner {:card wnp1})
283-
(core/play state :runner {:card wnp2})
284-
(is (= 1 (count (:hand (get-runner)))) "Second WNP was not installed")
285-
(is (:uniqueness wnp2) "WNP is unique")
286-
(core/command-parser state :runner {:user user :text "/unique"})
287-
(is (last-log-contains? state "\\[!\\]Runner uses a command: /unique") "Correct message")
288-
(click-card state :runner wnp2)
289-
(is (not (:uniqueness (refresh wnp2))) "WNP is not unique anymore")
290-
(is (last-log-contains? state "Runner uses /unique command to make Wireless Net Pavilion not unique\\.") "Correct message")
291-
(core/play state :runner {:card wnp2})
292-
(is (zero? (count (:hand (get-runner)))) "Both cards have been installed")))))))
276+
(do-game
277+
(new-game {:runner {:hand [(qty "Wireless Net Pavilion" 2)]
278+
:credit 100}})
279+
(take-credits state :corp)
280+
(play-from-hand state :runner "Wireless Net Pavilion")
281+
(let [wnp1 (get-resource state 0)]
282+
(core/command-parser state :runner {:user user :text "/unique"})
283+
(is (last-log-contains? state "\\[!\\]Runner uses a command: /unique") "Correct message")
284+
(click-card state :runner wnp1)
285+
(is (not (unique? (refresh wnp1))) "WNP is not unique anymore")
286+
(is (last-log-contains? state "Runner uses /unique command to make Wireless Net Pavilion not unique\\.") "Correct message")
287+
(play-from-hand state :runner "Wireless Net Pavilion")
288+
(is (zero? (count (:hand (get-runner)))) "Both cards have been installed")
289+
(is (= 2 (count (get-resource state))))
290+
(is (every? #(= "Wireless Net Pavilion" (:title %)) (get-resource state))))))))

0 commit comments

Comments
 (0)