Skip to content
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
81 commits
Select commit Hold shift + click to select a range
ad8d2eb
melies-u image handling
NBKelly Feb 14, 2026
a44a56c
Meleis U: Only the Brightest
NBKelly Feb 14, 2026
621e6d8
Virtual Intelligence, PI
NBKelly Feb 14, 2026
1a8ebeb
remove unused import
NBKelly Feb 15, 2026
295ba13
functional rules change for bad publicity
NBKelly Feb 15, 2026
9ba9173
unit tests updated for the bad publicity rule change
NBKelly Feb 15, 2026
ce3d1e2
Editorial Division, Nihilo Agent, Vulture Fund
NBKelly Feb 15, 2026
50d979c
hiram 0mission svensson
NBKelly Feb 16, 2026
dcd0065
qol for meiles u
NBKelly Feb 16, 2026
23dac31
side doing the damage trashes the cards
NBKelly Feb 17, 2026
81e034a
let them dream, melies city luxury line
NBKelly Feb 18, 2026
4b688d1
startup validation is three 3+pointers nows
NBKelly Feb 18, 2026
6762650
fixed tests
NBKelly Feb 18, 2026
edc7665
less hacky bad pub routing
NBKelly Feb 18, 2026
e086444
stick and poke
NBKelly Feb 18, 2026
4329a80
magistrate revontulet
NBKelly Feb 19, 2026
0e35973
flagship, access updates
NBKelly Feb 19, 2026
13b105b
Merge branch 'master' into vantage-point-spoilers-2
NBKelly Feb 21, 2026
88276c9
Merge branch 'master' of github.com:mtgred/netrunner into vantage-poi…
NBKelly Feb 21, 2026
7edb010
compiles
NBKelly Feb 21, 2026
09f0974
Async
NBKelly Feb 21, 2026
c85c9ce
tailgate, kompromat
NBKelly Feb 21, 2026
efdd9e2
witch hunt test
NBKelly Feb 21, 2026
116f06d
paywall
NBKelly Feb 21, 2026
ee23fa5
bad pub redo now works with unit tests
NBKelly Feb 21, 2026
54bf283
flywheel, event horizon
NBKelly Feb 21, 2026
b90e2a5
stowaway
NBKelly Feb 21, 2026
f063a4d
myoshu and tests
NBKelly Feb 21, 2026
dd03d1a
beta build
NBKelly Feb 22, 2026
db91d80
borrowed goods
NBKelly Feb 22, 2026
4fba084
rules says there will be an upadte to make flagship not silly with cu…
NBKelly Feb 23, 2026
13be4c3
dont prune cards from the set of already accessed cards
NBKelly Feb 23, 2026
df76104
touchstone, rotary
NBKelly Feb 23, 2026
24c5158
knowledge seeker
NBKelly Feb 23, 2026
9bda6ed
Ansel 2.0, baker
NBKelly Feb 23, 2026
f05934b
tocsin, viksek, vertigo
NBKelly Feb 24, 2026
78305e4
unit tests for the ice
NBKelly Feb 24, 2026
fa92c9f
esca
NBKelly Feb 24, 2026
046c862
unleash and tests
NBKelly Feb 24, 2026
e3306de
ezaM and tests
NBKelly Feb 24, 2026
d88fb11
The Red Room and tests
NBKelly Feb 24, 2026
2e08750
show other player discard option for prompts
NBKelly Feb 24, 2026
98bc6bd
Methuselah test
NBKelly Feb 26, 2026
157b1af
hype machine and test
NBKelly Feb 26, 2026
6fe8951
read-write share
NBKelly Feb 26, 2026
20fd10d
grubber and unit etsts
NBKelly Feb 26, 2026
75864f5
support for cards with hosting discounts to work for cost computation
NBKelly Feb 26, 2026
2519f3a
chain reaction and test
NBKelly Feb 26, 2026
97978ad
hackerspace and test, test for class act triggering correctly on host…
NBKelly Feb 26, 2026
9894225
perfect recall and test
NBKelly Feb 26, 2026
52b5ca7
tungsten tailor and tests
NBKelly Feb 27, 2026
55b3613
reverb and tests
NBKelly Feb 27, 2026
6b24596
realloc and tests
NBKelly Feb 27, 2026
5eb84b3
expand run from choices ab
NBKelly Feb 27, 2026
316f1fa
aircheck and take a dive
NBKelly Feb 27, 2026
fb90ccf
label fix for read write
NBKelly Feb 28, 2026
2372454
install-req -> install-req + legal-zones
NBKelly Feb 28, 2026
5bf7ca1
lotus haze and unit tests
NBKelly Feb 28, 2026
97ec8d6
fix hackerspace test
NBKelly Feb 28, 2026
582fad6
cannot lose credits if the effect is live
NBKelly Feb 28, 2026
c3cb1cf
pick-counters, costs updated for effects which restrict spending cred…
NBKelly Feb 28, 2026
10162c5
limit restrictions to runner side
NBKelly Feb 28, 2026
f8175c5
tag vp cards with interactive/silent/automatic
NBKelly Feb 28, 2026
ac15bb4
lethe and tests, and test frameworku pdate for concise run events
NBKelly Feb 28, 2026
894c6c7
sell out and unit test
NBKelly Feb 28, 2026
8076959
nurse hanh and test, access fires archives-flipped event
NBKelly Feb 28, 2026
26d914f
cultivate and unit tests. A little documentation update for test frak…
NBKelly Feb 28, 2026
c41fd0d
fix ordering
NBKelly Feb 28, 2026
991f52f
rework bacpro to allow redo of all three modes
NBKelly Feb 28, 2026
f3b5d85
unit test updates for bacpro
NBKelly Feb 28, 2026
6912b6d
sacrifice zone expansion and tests
NBKelly Feb 28, 2026
c4084cc
luana campos and tests
NBKelly Feb 28, 2026
e2a0785
sipa and tests
NBKelly Mar 1, 2026
8d38a18
support for cannot-forfeit flags
NBKelly Mar 1, 2026
b52d6b5
man in the middle and tests
NBKelly Mar 1, 2026
db1989f
sleipnir and tests
NBKelly Mar 1, 2026
61e82ad
caveat emptor and tests
NBKelly Mar 1, 2026
4b26dbf
lionsmane and tests
NBKelly Mar 1, 2026
806f0e6
caveat emptor and tests
NBKelly Mar 1, 2026
e71ff8d
shackleton grid and tests
NBKelly Mar 1, 2026
f741c97
Merge branch 'master' of github.com:mtgred/netrunner into vantage-poi…
NBKelly Mar 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 38 additions & 1 deletion src/clj/game/cards/agendas.clj
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
[game.core.prompts :refer [cancellable clear-wait-prompt show-wait-prompt]]
[game.core.props :refer [add-counter add-prop]]
[game.core.purging :refer [purge]]
[game.core.revealing :refer [reveal]]
[game.core.revealing :refer [reveal reveal-loud]]
[game.core.rezzing :refer [derez rez rez-multiple-cards]]
[game.core.runs :refer [clear-encounter end-run get-current-encounter force-ice-encounter redirect-run start-next-phase]]
[game.core.say :refer [play-sfx system-msg]]
Expand Down Expand Up @@ -1304,6 +1304,38 @@
:duration :end-of-run})
(effect-completed state side eid)))}}]})

(defcard "Let Them Dream"
(letfn [(move-to [c from]
(choose-one-helper
(let [and-then (fn [s] (str (if (= from :rd) ", shuffle R&D, and then " " and ") s))]
{:prompt (str "Move " (:title c) " where?")}
[{:option "HQ"
:ability {:async true
:effect (req (when (= from :rd) (shuffle! state side :deck))
(move state side c :hand)
(reveal-loud state side eid card {:and-then (and-then "add it to HQ")} c))}}
{:option "Bottom of R&D"
:ability {:async true
:effect (req (when (= from :rd) (shuffle! state side :deck))
(move state side c :deck)
(reveal-loud state side eid card {:and-then (and-then "add it to the bottom of R&D")} c))}}])))
(find-ab [zone]
{:prompt "Choose an agenda"
:show-discard (= zone :archives)
:choices (if (= zone :rd)
(req (cancellable (filter agenda? (:deck corp)) :sorted))
{:card #(and (agenda? %) (if (= zone :hq) (in-hand? %) (in-discard? %)))})
:effect (req (continue-ability state side (move-to target zone) card nil))
:async true
:cancel-effect shuffle-my-deck!})]
{:on-score (choose-one-helper
{:optional true
:prompt "Search for an Agenda from where?"}
[{:option "HQ" :ability (find-ab :hq)}
{:option "R&D" :ability (find-ab :rd)}
{:option "Archives" :ability (find-ab :archives)}])
:agendapoints-runner (req 1)}))

(defcard "License Acquisition"
{:on-score {:interactive (req true)
:prompt "Choose an asset or upgrade to install from Archives or HQ"
Expand Down Expand Up @@ -1432,6 +1464,11 @@
:req (req (= (:title target) "Medical Breakthrough"))
:value -1}]})

(defcard "Méliès City Luxury Line"
{:steal-cost-bonus (req [(->c :click 1)])
:on-score {:msg "gain [Click]"
:effect (req (gain-clicks state :corp 1))}})

(defcard "Megaprix Qualifier"
{:on-score {:silent (req true)
:req (req (< 1 (count (filter #(= (:title %) "Megaprix Qualifier")
Expand Down
31 changes: 30 additions & 1 deletion src/clj/game/cards/assets.clj
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@
[game.core.set-aside :refer [swap-set-aside-cards]]
[game.core.shuffling :refer [shuffle! shuffle-into-deck
shuffle-into-rd-effect]]
[game.core.tags :refer [gain-tags]]
[game.core.tags :refer [gain-tags lose-tags]]
[game.core.threat :refer [threat-level]]
[game.core.to-string :refer [card-str]]
[game.core.toasts :refer [toast]]
Expand Down Expand Up @@ -1842,6 +1842,16 @@
:effect (req (access-bonus state :runner target -1))}
card targets))}]})

(defcard "Magistrate Revontulet"
{:static-abilities [{:type :steal-additional-cost
:req (req (agenda? target))
:value (req [(->c :credit 3)])}]
:events [{:event :agenda-scored
:async true
:interactive (req true)
:msg "force the Runner to lose 3 [Credits]"
:effect (req (lose-credits state :runner eid 3))}]})

(defcard "Malia Z0L0K4"
(let [unmark
(req (when-let [malia-target (get-in card [:special :malia-target])]
Expand Down Expand Up @@ -2239,6 +2249,25 @@
(do (as-agenda state :runner card -1)
(effect-completed state side eid))))}})

(defcard "Nihilo Agent"
{:data {:counter {:power 3}}
:events [(trash-on-empty :power)
{:event :corp-turn-ends
:msg "take 1 bad publicity and give the Runner 1 tag"
:async true
:effect (req (wait-for
(gain-bad-publicity state :corp 1 {:suppress-checkpoint true})
(wait-for
(add-counter state side card :power -1 {:suppress-checkpoint true})
(gain-tags state side eid 1))))}
{:event :corp-turn-begins
:change-in-game-state {:silent true
:req (req (or tagged (pos? (count-bad-pub state))))}
:msg "remove 1 bad publicity and 1 tag"
:async true
:effect (req (wait-for (lose-bad-publicity state :corp 1 {:suppress-checkpoint true})
(lose-tags state side eid 1)))}]})

(defcard "Open Forum"
{:events [{:event :corp-mandatory-draw
:interactive (req true)
Expand Down
101 changes: 100 additions & 1 deletion src/clj/game/cards/identities.clj
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
[game.core.say :refer [system-msg]]
[game.core.servers :refer [central->name is-central? is-remote? name-zone
target-server zone->name]]
[game.core.shuffling :refer [shuffle! shuffle-into-deck shuffle-cards-into-deck!]]
[game.core.shuffling :refer [shuffle! shuffle-into-deck shuffle-cards-into-deck! shuffle-my-deck!]]
[game.core.tags :refer [gain-tags lose-tags]]
[game.core.to-string :refer [card-str]]
[game.core.toasts :refer [toast]]
Expand Down Expand Up @@ -755,6 +755,20 @@
:req (req (:flipped card))
:effect flip-effect}]}))

(defcard "Editorial Division: Ad Nihilum"
{:events [{:event :corp-gain-bad-publicity
:optional {:req (req (let [valid-ctx? (fn [[ctx]] (pos? (:amount ctx)))]
(and (valid-ctx? targets)
(first-event? state side :corp-gain-bad-publicity valid-ctx?))))
:prompt "Search for a card?"
:waiting-prompt true
:yes-ability {:prompt "Choose a card"
:msg (msg "add " (:title target) " to HQ from R&D") ;; TODO - once the illicit->liability change is through, we can adjust this (or just leave it)
:choices (req (cancellable (filter #(has-any-subtype? % ["Illicit" "Black Ops" "Gray Ops" "Liability"]) (filter (complement agenda?) (:deck corp))) :sorted))
Copy link
Collaborator Author

@NBKelly NBKelly Feb 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Illicit is here as a placeholder for now. Since every illicit is now a liability, I don't see any harm in leaving it permanently.

:cancel shuffle-my-deck!
:effect (effect (shuffle! :deck)
(move target :hand))}}}]})

(defcard "Edward Kim: Humanity's Hammer"
{:events [{:event :access
:req (req (and (operation? target)
Expand Down Expand Up @@ -1039,6 +1053,18 @@
:prompt-type :bogus}))
card nil))}]})

(defcard "Hiram \"0mission\" Svensson: Shadow of the Past"
(let [scry {:change-in-game-state {:silent (req true)
:req (req (seq (:deck corp)))}
:msg "look at the top card of R&D"
:interactive (req true)
:skippable true
:waiting-prompt true
:prompt (msg "The top card of R&D is " (:title (first (:deck corp))))
:choices ["Noted"]}]
{:events [(assoc scry :event :runner-install :req (req (hardware? (:card context))))
(assoc scry :event :runner-trash :req (req (some hardware? (map :card targets))))]}))

(defcard "Hoshiko Shiro: Untold Protagonist"
(let [flip-effect (req (update! state side (if (:flipped card)
(assoc card
Expand Down Expand Up @@ -1481,6 +1507,66 @@
:events [(assoc ability :event :runner-turn-begins)]
:abilities [ability]}))

(defcard "Méliès U: Only the Brightest"
{:events [;; At game start, you're on the front face
{:event :pre-first-turn
:req (req (= side :corp))
:effect (effect (update!
(assoc card
:face :front
:melies-target (first (shuffle ["HQ" "R&D" "Archives"]))))
(system-msg "reveals that the three hidden faces of Méliès U: Only the Brightest are: Tenure Floors: Méliès U, Subsurface Labs: Méliès U, and Disposal Grounds: Méliès U"))}
;; When your turn ends, you secretly choose a server
{:event :corp-turn-ends
:prompt "Choose a server"
:interactive (req true)
:waiting-prompt true
:choices ["HQ" "R&D" "Archives"]
:msg (msg "secretly choose a server")
:effect (req (update! state side (assoc card :melies-target target)))}
;; When the runner discard phase ends while you're on the front, you gain 1c
{:event :runner-turn-ends
:req (req (= (:face card) :front))
:msg "gain 1 [Credit]"
:async true
:effect (req (gain-credits state side eid 1))}
;; when our turn begins and we are not on the front face, we flip
{:event :corp-turn-begins
:silent (req true)
:effect (effect (update! (assoc card :face :front)))}
;; When the runner makes a successful run on a central
;; while we're on a front face, we flip and maybe do something
{:event :successful-run
:req (req (and (= (:face card) :front) (is-central? (:server context))))
:msg (msg "flip to "
(case (:melies-target card)
"HQ" "Tenure Floors: Méliès U"
"R&D" "Subsurface Labs: Méliès U"
"Archives" "Disposal Grounds: Méliès U"
"this shouldn't occur"))
:async true
:effect (req (let [[target-zone face] (case (:melies-target card)
"HQ" [:hq :tenure]
"R&D" [:rd :subsurface]
"Archives" [:archives :disposal]
[:hq :tenure])]
(update! state side (assoc card :face face))
(if (and (-> context :server first (= target-zone))
(seq (:deck corp)))
(continue-ability
state side
{:optional
{:prompt (msg "The top card of R&D is " (:title (first (:deck corp))) ". Trash it?")
:waiting-prompt true
:req (req (seq (:hand runner)))
:yes-ability {:cost [(->c :trash-from-deck 1)]
:once :per-turn
:msg "add 1 card from Archives to HQ"
:async true
:effect (effect (continue-ability (corp-recur) card nil))}}}
card nil)
(effect-completed state side eid))))}]})

(defcard "Mercury: Chrome Libertador"
{:events [{:event :breach-server
:automatic :pre-breach
Expand Down Expand Up @@ -2747,6 +2833,19 @@
;; This doesn't use `gain-bad-publicity` to avoid the event
:effect (effect (gain :corp :bad-publicity 1))}]})

(defcard "Virtual Intelligence, P.I.: \"You Can Call Me Vic\""
{:abilities [{:cost [(->c :click 1) (->c :credit 1)]
:action true
:once :per-turn
:label "Draw 1 card and remove 1 tag."
:msg (msg (if tagged "draw 1 card and remove 1 tag" "draw 1 card"))
:async true
:change-in-game-state {:req (req (or tagged (seq (:deck runner))))}
:effect (req (if tagged
(wait-for (draw state side 1 {:suppress-checkpoint true})
(lose-tags state side eid 1))
(draw state side eid 1)))}]})

(defcard "Weyland Consortium: Because We Built It"
{:recurring 1
:interactions {:pay-credits {:req (req (let [ab-target (:card (get-ability-targets eid))]
Expand Down
6 changes: 6 additions & 0 deletions src/clj/game/cards/operations.clj
Original file line number Diff line number Diff line change
Expand Up @@ -3319,6 +3319,12 @@
{:psi {:req (req (seq (:scored runner)))
:not-equal (trash-type "resource" resource? :loud)}}})

(defcard "Vulture Fund"
{:on-play {:msg "gain 14 [Credits] and take 1 bad publicity"
:async true
:effect (req (wait-for (gain-credits state side 14 {:suppress-checkpoint true})
(gain-bad-publicity state side eid 1)))}})

(defcard "Wake Up Call"
{:on-play
{:rfg-instead-of-trashing true
Expand Down
21 changes: 20 additions & 1 deletion src/clj/game/cards/resources.clj
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@
[game.core.revealing :refer [reveal reveal-loud]]
[game.core.rezzing :refer [derez rez]]
[game.core.runs :refer [active-encounter? bypass-ice can-run-server? get-runnable-zones
gain-run-credits get-current-encounter
get-current-encounter
update-current-encounter
make-run set-next-phase
successful-run-replace-breach total-cards-accessed]]
Expand Down Expand Up @@ -3265,6 +3265,25 @@
(swap! state assoc-in [:runner :register :double-ignore-additional] true))}]
:leave-play (req (swap! state update-in [:runner :register] dissoc :double-ignore-additional))})

(defcard "Stick and Poke"
{:events [{:event :encounter-ice
:req (req (first-event? state side :encounter-ice))
:interactive (req true)
:effect (req (register-lingering-effect
state side card
(let [ice (:ice context)]
{:duration :end-of-encounter
:type :additional-subroutines
:req (req (and (rezzed? target) (same-card? target ice)))
:value {:position :front
:subroutines
[{:label "[Stick] Do 1 net damage. The Runner draws 1 card."
:msg "Do 1 net damage"
:async true
:effect (req (wait-for
(damage state side :net 1)
(draw-loud state :runner eid card 1)))}]}})))}]})

(defcard "Stim Dealer"
{:events [{:event :runner-turn-begins
:async true
Expand Down
41 changes: 40 additions & 1 deletion src/clj/game/cards/upgrades.clj
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
[game.core.eid :refer [effect-completed get-ability-targets is-basic-advance-action? make-eid]]
[game.core.engine :refer [dissoc-req pay register-default-events
register-events resolve-ability unregister-events]]
[game.core.events :refer [first-event? first-run-event? no-event? turn-events]]
[game.core.events :refer [first-event? first-run-event? no-event? turn-events run-event-count]]
[game.core.finding :refer [find-cid find-latest]]
[game.core.flags :refer [clear-persistent-flag! is-scored? register-persistent-flag!
register-run-flag!]]
Expand Down Expand Up @@ -726,6 +726,45 @@
:base 3
:successful (give-tags 2)}}})

(defcard "Flagship"
(let [lockdown (fn [state side card]
(register-lingering-effect
state side card
{:type :disable-random-accesses
:value true
:duration :end-of-run})
(register-lingering-effect
state side card
{:type :disable-access-candidacy
:req (req (not (same-card? card target)))
:duration :end-of-run
:value true}))
ev {:event :post-access-card
:once :per-run
:msg "prevent the Runner from accessing other cards this run"
:req (req (and run this-server
(not (same-card? card target))))
:effect (req ;; random accesses get disabled after your first access that is not this card
(lockdown state side card))}]
;; NOTE: Based on the CR, this invalidates all other cards as access candidates
;; when you touch a card other than this card.
;; This means that if you cupellate this, you dodge it,
;; but if you access another card first, then cupellate this, you do not
{:static-abilities [{:type :block-successful-run
:req (req this-server)
:value true}]
:events [ev]
:on-trash {:req (req (and run (= :runner side)))
:effect (req (if (>= (run-event-count state side :access) 2)
(lockdown state side card)
(register-events
state side card
[{:duration :end-of-run
:event :access
:req (req run)
:effect (req (lockdown state side card))}])))}}))


(defcard "Fractal Threat Matrix"
{:events [{:event :subroutines-broken
:req (req (and (:all-subs-broken context)
Expand Down
8 changes: 5 additions & 3 deletions src/clj/game/core/access.clj
Original file line number Diff line number Diff line change
Expand Up @@ -509,7 +509,8 @@
([state server]
(->> (get-in @state [:corp :servers server :content])
get-all-content
(filter #(can-access? state :runner %))))
(filter #(can-access? state :runner %))
(filter #(not (any-effects state :runner :disable-access-candidacy true? % [%])))))
([state server already-accessed-fn]
(remove already-accessed-fn (root-content state server))))

Expand Down Expand Up @@ -626,7 +627,7 @@

(defn access-helper-rd
[state {:keys [chosen random-access-limit] :as access-amount} already-accessed {:keys [no-root] :as args}]
(let [current-available (set (concat (map :cid (get-in @state [:corp :deck]))
(let [current-available (set (concat (if-not (any-effects state :runner :disable-random-accesses true? {:server :rd}) (map :cid (get-in @state [:corp :deck])) [])
(map :cid (root-content state :rd))))
already-accessed (clj-set/intersection already-accessed current-available)
already-accessed-fn (fn [card] (contains? already-accessed (:cid card)))
Expand Down Expand Up @@ -814,7 +815,8 @@
(defn access-helper-hq
[state {:keys [chosen random-access-limit] :as access-amount}
already-accessed {:keys [no-root access-first] :as args}]
(let [hand (when (not (:prevent-hand-access (:run @state)))
(let [hand (when (not (or (:prevent-hand-access (:run @state))
(any-effects state :runner :disable-random-accesses true? {:server :hq})))
(get-in @state [:corp :hand]))
current-available (set (concat (map :cid hand)
(map :cid (root-content state :hq))))
Expand Down
Loading
Loading