Skip to content

Commit b22c175

Browse files
authored
Merge pull request #7978 from NBKelly/turmoil
April fools content
2 parents 77faa8d + 6774c2a commit b22c175

File tree

11 files changed

+223
-5
lines changed

11 files changed

+223
-5
lines changed

resources/public/i18n/en.ftl

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -718,6 +718,16 @@ ingame-settings_sort-heap = Sort Heap
718718
719719
ingame-settings_stack-cards = Stack cards
720720
721+
lobby_turmoil = Turmoil
722+
723+
lobby_turmoil-details = The fickle winds of fate shall decide your future.
724+
725+
lobby_turmoil-theme = "FINUKA DISPOSES"
726+
727+
lobby_turmoil-info = This lobby is running in turmoil mode. The winds of fate shall decide your path to the future.
728+
729+
lobby_span-turmoil = (turmoil)
730+
721731
lobby_aborted = Connection aborted
722732
723733
lobby_api-access = Allow API access to game information

src/clj/game/core/actions.clj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -628,7 +628,7 @@
628628
[state _ {:keys [card]}]
629629
(if-let [card (get-card state card)]
630630
(if (expendable? state card)
631-
(swap! state assoc-in [:corp :install-list] (conj (installable-servers state card) "Expend")) ;;april fools we can make this "cast as a sorcery"
631+
(swap! state assoc-in [:corp :install-list] (conj (installable-servers state card) "Cast as a Sorcery")) ;;april fools we can make this "cast as a sorcery"
632632
(swap! state assoc-in [:corp :install-list] (installable-servers state card)))
633633
(swap! state dissoc-in [:corp :install-list])))
634634

src/clj/game/core/set_up.clj

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@
7878

7979
(defn- init-game-state
8080
"Initialises the game state"
81-
[{:keys [players gameid timer spectatorhands api-access save-replay room] :as game}]
81+
[{:keys [players gameid timer spectatorhands api-access save-replay room turmoil] :as game}]
8282
(let [corp (some #(when (corp? %) %) players)
8383
runner (some #(when (runner? %) %) players)
8484
corp-deck (create-deck (:deck corp))
@@ -106,6 +106,7 @@
106106
(inst/now)
107107
{:timer timer
108108
:spectatorhands spectatorhands
109+
:turmoil turmoil
109110
:api-access api-access
110111
:save-replay save-replay}
111112
(new-corp (:user corp) corp-identity corp-options (map #(assoc % :zone [:deck]) corp-deck) corp-deck-id corp-quote)

src/clj/game/core/turmoil.clj

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
(ns game.core.turmoil
2+
(:require
3+
[game.core.card :refer [agenda? asset? event? has-subtype? hardware? resource? program? upgrade? ice? operation? identity? corp? runner?]]
4+
[game.core.commands :refer [lobby-command]]
5+
[game.core.identities :refer [disable-identity]]
6+
[game.core.initializing :refer [card-init make-card]]
7+
[game.core.hosting :refer [host]]
8+
[game.core.moving :refer [move]]
9+
[game.core.payment :refer [->c]]
10+
[game.core.say :refer [system-msg]]
11+
[game.core.set-up :refer [build-card]]
12+
[game.utils :refer [same-card? server-cards server-card]]
13+
[clojure.string :as string]))
14+
15+
;; store all this in memory once so we don't need to recalculate it a trillion times
16+
;; everything should be a vec, so rand-nth will be O[1] instead of O[n/2]
17+
18+
(defonce agenda-by-points (atom {}))
19+
(defonce identity-by-side (atom {}))
20+
(defonce program-by-icebreaker (atom {}))
21+
(defonce cards-by-type (atom {}))
22+
(defonce has-been-set? (atom nil))
23+
24+
(defn- is-econ?
25+
"Is a card an economy card?
26+
Something like:
27+
* gain x credits
28+
* take x/all host/ed credits"
29+
[card]
30+
(re-find #".(ain|ake) (\d+|(.? host.*)).?.?.?redit" (or (:text (server-card (:title card))) "")))
31+
32+
(defonce filter-by-econ-types #{:asset :operation :resource :event})
33+
34+
(defn- set-cards! []
35+
(when-not @has-been-set?
36+
(println "assigning server cards for turmoil")
37+
(reset! agenda-by-points
38+
(->> (server-cards)
39+
(filterv agenda?)
40+
(group-by :agendapoints)))
41+
(reset! identity-by-side {:corp (->> (server-cards)
42+
(filterv identity?)
43+
(filterv corp?))
44+
:runner (->> (server-cards)
45+
(filterv identity?)
46+
(filterv runner?))})
47+
(reset! program-by-icebreaker {:icebreaker (->> (server-cards)
48+
(filterv program?)
49+
(filterv #(has-subtype? % "Icebreaker")))
50+
:regular (->> (server-cards)
51+
(filterv program?)
52+
(filterv #(not (has-subtype? % "Icebreaker"))))})
53+
(reset! cards-by-type (let [types {:asset asset?
54+
:event event?
55+
:hardware hardware?
56+
:resource resource?
57+
:program program?
58+
:upgrade upgrade?
59+
:ice ice?
60+
:operation operation?}
61+
keys-sorted (sort (keys types))]
62+
(zipmap keys-sorted (mapv #(if (contains? filter-by-econ-types %)
63+
{:economy (filterv (every-pred (types %) is-econ?) (server-cards))
64+
:regular (filterv (every-pred (types %) (complement is-econ?)) (server-cards))}
65+
(filterv (types %) (server-cards)))
66+
keys-sorted))))
67+
(reset! has-been-set? true)))
68+
69+
(def replacement-factor
70+
"how often should we replace these cards?"
71+
{:hand 4 :deck 8 :discard 3 :id 4 :side 50 :card-type-cross-contam 10 :icebreaker-cross-contam 10 :econ-cross-contam 10})
72+
73+
(defn- should-replace?
74+
([key] (-> (key replacement-factor 25) rand-int zero?))
75+
([key ex] (-> (key replacement-factor 25) (min ex) (max 1) rand-int zero?)))
76+
77+
(def corp-card-types #{:asset :upgrade :ice :operation})
78+
(def runner-card-types #{:resource :hardware :program :event})
79+
80+
(defn- pick-replacement-card
81+
"given a card, pick a suitable replacement card at random
82+
agendas maintain point value,
83+
programs maintain if they are/aren't icebreakers,
84+
everything else is random"
85+
[card]
86+
(set-cards!)
87+
(let [c-type (-> card :type string/lower-case keyword)]
88+
(cond
89+
;; agenda (x points) -> agenda (x points) should stop people gaming density
90+
(= c-type :agenda)
91+
(let [target-points (:agendapoints card)]
92+
(rand-nth (get @agenda-by-points target-points)))
93+
(= c-type :identity)
94+
(let [target-side (-> card :side string/lower-case keyword)]
95+
(rand-nth (get @identity-by-side target-side)))
96+
;; icebreaker -> icebreaker should make it reasonable to not get completely locked out
97+
(= c-type :program)
98+
;; allow 10% cross-contain for icebreakers
99+
(let [choice (should-replace? :icebreaker-cross-contam)
100+
choice (if (= 1 (count (filter identity [choice (has-subtype? card "Icebreaker")])))
101+
:icebreaker
102+
:regular)]
103+
(rand-nth (get @program-by-icebreaker choice)))
104+
(contains? filter-by-econ-types c-type)
105+
;; allow 10% cross-conta for econ
106+
(let [choice (rand-nth
107+
(if (is-econ? card)
108+
[:economy :economy :economy :economy :economy :economy :economy :economy :economy :regular]
109+
[:regular :regular :regular :regular :regular :regular :regular :regular :regular :economy]))]
110+
(rand-nth (get-in @cards-by-type [c-type choice])))
111+
:else (rand-nth (get @cards-by-type c-type)))))
112+
113+
(defn- replace-hand [state side]
114+
(let [new-hand (mapv #(if (should-replace? :hand (count (get-in @state [side :hand])))
115+
(assoc (build-card (pick-replacement-card %)) :zone [:hand])
116+
%)
117+
(get-in @state [side :hand]))]
118+
(swap! state assoc-in [side :hand] new-hand)))
119+
120+
(defn- replace-discard [state side]
121+
(let [new-discard (mapv #(if (should-replace? :discard)
122+
(assoc (build-card (pick-replacement-card %)) :zone [:discard] :seen (:seen %))
123+
%)
124+
(get-in @state [side :discard]))]
125+
(swap! state assoc-in [side :discard] new-discard)))
126+
127+
(defn- replace-deck [state side]
128+
(let [new-deck (mapv #(if (should-replace? :deck)
129+
(assoc (build-card (pick-replacement-card %)) :zone [:deck])
130+
%)
131+
(get-in @state [side :deck]))]
132+
(swap! state assoc-in [side :deck] new-deck)))
133+
134+
(defn- replace-id [state side]
135+
;; defuse any sillyness with 'replace-id'
136+
(when (should-replace? :id)
137+
(let [old-id (get-in @state [side :identity])
138+
new-id (pick-replacement-card {:type "Identity" :side (name side)})]
139+
;; Handle hosted cards (Ayla) - Part 1
140+
(doseq [c (:hosted old-id)]
141+
(move state side c :temp-hosted))
142+
(disable-identity state side)
143+
;; Move the selected ID to [:runner :identity] and set the zone
144+
(let [new-id (-> new-id make-card (assoc :zone [(->c :identity)]))
145+
num-old-blanks (:num-disabled old-id)]
146+
(swap! state assoc-in [side :identity] new-id)
147+
(card-init state side new-id)
148+
(when num-old-blanks
149+
(dotimes [_ num-old-blanks]
150+
(disable-identity state side))))
151+
;; Handle hosted cards (Ayla) - Part 2
152+
(doseq [c (get-in @state [side :temp-hosted])]
153+
;; Currently assumes all hosted cards are hosted facedown (Ayla)
154+
(host state side (get-in @state [side :identity]) c {:facedown true})))))
155+
156+
(defn- transpose-sides
157+
[state side]
158+
;; this is kosher I promise -> Emphyrio, Jack Vance (it's a neat book, I recommend it)
159+
(system-msg state side "FINUKA TRANSPOSES")
160+
(lobby-command {:command :swap-sides
161+
:gameid (:gameid @state)}))
162+
163+
(defn shuffle-cards-for-side
164+
[state side]
165+
(do (replace-hand state side)
166+
(replace-deck state side)
167+
(replace-discard state side)
168+
(replace-id state side)
169+
;; 1 in 50 chance at the start of each turn to swap the sides you're playing on
170+
;; realistically, this should happen on average about 2 games in 4
171+
;; and 0.75 of those games will have 2+ swaps
172+
(when (should-replace? :side)
173+
(transpose-sides state side))))

src/clj/game/core/turns.clj

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
[game.core.say :refer [system-msg]]
1616
[game.core.set-aside :refer [clean-set-aside!]]
1717
[game.core.toasts :refer [toast]]
18+
[game.core.turmoil :as turmoil]
1819
[game.core.update :refer [update!]]
1920
[game.core.winning :refer [flatline]]
2021
[game.macros :refer [continue-ability req wait-for]]
@@ -73,6 +74,10 @@
7374
(swap! state assoc :click-states [])
7475
(swap! state assoc :turn-state (dissoc @state :log :history :turn-state))
7576

77+
;; resolve turmoil (april fools)
78+
(when (get-in @state [:options :turmoil])
79+
(turmoil/shuffle-cards-for-side state side))
80+
7681
(when (= side :corp)
7782
(swap! state update-in [:turn] inc))
7883

src/clj/web/lobby.clj

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,8 @@
8585
user :user
8686
{:keys [gameid now
8787
allow-spectator api-access format mute-spectators password room save-replay
88-
precon gateway-type side singleton spectatorhands timer title open-decklists]
88+
precon gateway-type side singleton spectatorhands timer title open-decklists
89+
turmoil]
8990
:or {gameid (random-uuid)
9091
now (inst/now)}} :options}]
9192
(let [player {:user user
@@ -109,6 +110,7 @@
109110
:mute-spectators mute-spectators
110111
:password (when (not-empty password) (bcrypt/encrypt password))
111112
:room room
113+
:turmoil turmoil
112114
:save-replay save-replay
113115
:spectatorhands spectatorhands
114116
:singleton (when (some #{format} `("standard" "startup" "casual" "eternal")) singleton)
@@ -189,6 +191,7 @@
189191
:save-replay
190192
:singleton
191193
:spectators
194+
:turmoil
192195
:corp-spectators
193196
:runner-spectators
194197
:spectatorhands

src/cljs/nr/game_row.cljs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,12 +191,13 @@
191191
(when (and open-decklists (not precon))
192192
[:span.open-decklists (str " " (tr [:lobby_open-decklists-b] "(open decklists)"))]))
193193

194-
(defn game-format [{fmt :format singleton? :singleton precon :precon open-decklists :open-decklists}]
194+
(defn game-format [{fmt :format singleton? :singleton turmoil? :turmoil precon :precon open-decklists :open-decklists}]
195195
[:div {:class "game-format"}
196196
[:span.format-label (tr [:lobby_format "Format"]) ": "]
197197
[:span.format-type (tr-format (slug->format fmt "Unknown"))]
198198
[precon-span precon]
199199
[:span.format-singleton (str (when singleton? (str " " (tr [:lobby_singleton-b "(singleton)"]))))]
200+
[:span.turmoil (when turmoil? (str " " (tr [:lobby_span-turmoil "(turmoil)"])))]
200201
[open-decklists-span precon open-decklists]
201202
[precon-under-span precon]])
202203

src/cljs/nr/gameboard/board.cljs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -444,7 +444,7 @@
444444
^{:key label}
445445
[card-menu-item (label-fn label)
446446
#(do (close-card-menu)
447-
(if (= "Expend" label)
447+
(if (= "Cast as a Sorcery" label)
448448
(send-command "expend" {:card card :server label})
449449
(send-command "play" {:card card :server label})))])
450450
servers))]])))

src/cljs/nr/new_game.cljs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
:save-replay
1919
:side
2020
:singleton
21+
:turmoil
2122
:spectatorhands
2223
:precon
2324
:gateway-type
@@ -79,6 +80,12 @@
7980
:on-change #(swap! options assoc :singleton (.. % -target -checked))}]
8081
(tr [:lobby_singleton "Singleton"])])
8182

83+
(defn turmoil-mode [options]
84+
[:span [:label
85+
[:input {:type "checkbox" :checked (:turmoil @options)
86+
:on-change #(swap! options assoc :turmoil (.. % -target -checked))}]
87+
(tr [:lobby_turmoil "Turmoil"])]])
88+
8289
(defn open-decklists [options]
8390
[:label
8491
[:input {:type "checkbox" :checked (:open-decklists @options)
@@ -124,8 +131,13 @@
124131
^{:key k}
125132
[:option {:value k} (tr-format v)]))]
126133
[singleton-only options fmt-state]
134+
[turmoil-mode options]
127135
[gateway-constructed-choice fmt-state gateway-type]
128136
[precon-choice fmt-state precon]
137+
[:div.infobox.blue-shade
138+
{:style {:display (if (:turmoil @options) "block" "none")}}
139+
[:p (tr [:lobby_turmoil-details "The fickle winds of fate shall decide your future."])]
140+
[:p (tr [:lobby_turmoil-theme "\"FINUKA DISPOSES\""])]]
129141
[:div.infobox.blue-shade
130142
{:style {:display (if (:singleton @options) "block" "none")}}
131143
[:p (tr [:lobby_singleton-details "This will restrict decklists to only those which do not contain any duplicate cards. It is recommended you use the listed singleton-based identities."])]
@@ -246,6 +258,7 @@
246258
:protected false
247259
:save-replay (not= "casual" (:room @lobby-state))
248260
:singleton false
261+
:turmoil false
249262
:spectatorhands false
250263
:open-decklists false
251264
:timed false

src/cljs/nr/pending_game.cljs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,11 @@
9999
[:div.infobox.blue-shade
100100
[:p (tr [:lobby_singleton-restriction "This lobby is running in singleton mode. This means decklists will be restricted to only those which do not contain any duplicate cards."])]]))
101101

102+
(defn turmoil-info-box [current-game]
103+
(when (:turmoil-mode @current-game)
104+
[:div.infobox.blue-shade
105+
[:p (tr [:lobby_turmoil-info "This lobby is running in turmoil mode. The winds of fate shall decide your path to the future."])]]))
106+
102107
(defn swap-sides-button [user gameid players]
103108
(when (first-user? @players @user)
104109
(if (< 1 (count @players))
@@ -205,6 +210,7 @@
205210
[:h2 (:title @current-game)]
206211
[precon-info-box current-game]
207212
[singleton-info-box current-game]
213+
[turmoil-info-box current-game]
208214
(when-not (or (every? :deck @players)
209215
(not (is-constructed? current-game)))
210216
[:div.flash-message

0 commit comments

Comments
 (0)