Skip to content

Commit 0486faf

Browse files
authored
refactor state change events (#147)
1 parent c5b1b89 commit 0486faf

17 files changed

+560
-219
lines changed

client/src/client.rs

+1-4
Original file line numberDiff line numberDiff line change
@@ -156,10 +156,6 @@ fn render_active_dialog(rc: &RenderContext) -> StateUpdate {
156156
status_phase_ui::choose_additional_advances_dialog(rc, a)
157157
}
158158
ActiveDialog::DetermineFirstPlayer => status_phase_ui::determine_first_player_dialog(rc),
159-
ActiveDialog::TradeRouteSelection(p) => {
160-
custom_actions_ui::trade_route_selection_dialog(rc, p)
161-
}
162-
163159
//combat
164160
ActiveDialog::PlayActionCard => combat_ui::play_action_card_dialog(rc),
165161
ActiveDialog::Retreat => combat_ui::retreat_dialog(rc),
@@ -168,6 +164,7 @@ fn render_active_dialog(rc: &RenderContext) -> StateUpdate {
168164
ActiveDialog::CustomPhasePaymentRequest(c) => {
169165
custom_actions_ui::custom_phase_payment_dialog(rc, c)
170166
}
167+
ActiveDialog::CustomPhaseRewardRequest(p) => custom_actions_ui::reward_dialog(rc, p),
171168
}
172169
}
173170

client/src/client_state.rs

+9-8
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use server::action::Action;
44
use server::city::{City, MoodState};
55
use server::combat::{active_attackers, active_defenders, CombatPhase};
66
use server::content::advances::{NAVIGATION, ROADS};
7-
use server::content::custom_phase_actions::{CustomPhaseRequest, CustomPhaseState};
7+
use server::content::custom_phase_actions::CustomPhaseRequest;
88
use server::game::{CulturalInfluenceResolution, CurrentMove, Game, GameState};
99
use server::position::Position;
1010
use server::status_phase::{StatusPhaseAction, StatusPhaseState};
@@ -14,7 +14,6 @@ use crate::client::{Features, GameSyncRequest};
1414
use crate::collect_ui::CollectResources;
1515
use crate::combat_ui::RemoveCasualtiesSelection;
1616
use crate::construct_ui::ConstructionPayment;
17-
use crate::custom_actions_ui::trade_route_dialog;
1817
use crate::happiness_ui::IncreaseHappinessConfig;
1918
use crate::layout_ui::FONT_SIZE;
2019
use crate::log_ui::{add_advance_help, advance_help};
@@ -58,8 +57,8 @@ pub enum ActiveDialog {
5857
PlaceSettler,
5958
Retreat,
6059
RemoveCasualties(RemoveCasualtiesSelection),
61-
TradeRouteSelection(Payment), // it's actually a gain
6260

61+
CustomPhaseRewardRequest(Payment),
6362
CustomPhasePaymentRequest(Vec<Payment>),
6463
}
6564

@@ -92,7 +91,7 @@ impl ActiveDialog {
9291
ActiveDialog::PlaceSettler => "place settler",
9392
ActiveDialog::Retreat => "retreat",
9493
ActiveDialog::RemoveCasualties(_) => "remove casualties",
95-
ActiveDialog::TradeRouteSelection(_) => "trade route selection",
94+
ActiveDialog::CustomPhaseRewardRequest(_) => "trade route selection",
9695
ActiveDialog::CustomPhasePaymentRequest(_) => "custom phase payment request",
9796
}
9897
}
@@ -178,7 +177,9 @@ impl ActiveDialog {
178177
r.needed
179178
)],
180179
ActiveDialog::WaitingForUpdate => vec!["Waiting for server update".to_string()],
181-
ActiveDialog::TradeRouteSelection(_) => vec!["Select trade route reward".to_string()],
180+
ActiveDialog::CustomPhaseRewardRequest(_) => {
181+
vec!["Select trade route reward".to_string()]
182+
}
182183
ActiveDialog::CustomPhasePaymentRequest(_r) => {
183184
match &rc.game.custom_phase_state.current.as_ref().unwrap().origin {
184185
EventOrigin::Advance(a) => advance_help(rc, a),
@@ -516,6 +517,9 @@ impl State {
516517
})
517518
.collect(),
518519
),
520+
CustomPhaseRequest::Reward(r) => ActiveDialog::CustomPhaseRewardRequest(
521+
Payment::new_gain(r.model.clone(), &r.name),
522+
),
519523
};
520524
}
521525
match &game.state {
@@ -568,9 +572,6 @@ impl State {
568572
rotation: r.block.position.rotation,
569573
})
570574
}
571-
GameState::CustomPhase(c) => match c {
572-
CustomPhaseState::TradeRouteSelection => trade_route_dialog(game),
573-
},
574575
}
575576
}
576577

client/src/custom_actions_ui.rs

+6-13
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,17 @@ use crate::client_state::{ActiveDialog, StateUpdate};
22
use crate::payment_ui::{multi_payment_dialog, payment_dialog, Payment};
33
use crate::render_context::RenderContext;
44
use server::action::Action;
5-
use server::content::custom_phase_actions::{CustomPhaseAction, CustomPhaseEventAction};
6-
use server::content::trade_routes::trade_route_reward;
7-
use server::game::Game;
5+
use server::content::custom_phase_actions::CustomPhaseEventAction;
86

9-
pub fn trade_route_dialog(game: &Game) -> ActiveDialog {
10-
let model = trade_route_reward(game).unwrap().0;
11-
ActiveDialog::TradeRouteSelection(Payment::new_gain(model, "Select trade route reward"))
12-
}
13-
14-
pub fn trade_route_selection_dialog(rc: &RenderContext, payment: &Payment) -> StateUpdate {
7+
pub fn reward_dialog(rc: &RenderContext, payment: &Payment) -> StateUpdate {
158
payment_dialog(
169
rc,
1710
payment,
18-
|p| ActiveDialog::TradeRouteSelection(p.clone()),
11+
|p| ActiveDialog::CustomPhaseRewardRequest(p.clone()),
1912
|p| {
20-
StateUpdate::Execute(Action::CustomPhase(
21-
CustomPhaseAction::TradeRouteSelectionAction(p),
22-
))
13+
StateUpdate::Execute(Action::CustomPhaseEvent(CustomPhaseEventAction::Reward(
14+
p.clone(),
15+
)))
2316
},
2417
)
2518
}

server/src/ability_initializer.rs

+84-11
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
use crate::action::Action;
22
use crate::content::custom_phase_actions::{
3-
CustomPhaseEvent, CustomPhaseEventAction, CustomPhaseEventType, CustomPhasePaymentRequest,
4-
CustomPhaseRequest,
3+
CurrentCustomPhaseEvent, CustomPhaseEventAction, CustomPhaseEventState, CustomPhaseEventType,
4+
CustomPhasePaymentRequest, CustomPhaseRequest, CustomPhaseRewardRequest,
55
};
6+
use crate::game::UndoContext;
67
use crate::resource_pile::ResourcePile;
78
use crate::{
89
content::custom_actions::CustomActionType, events::EventMut, game::Game,
@@ -94,39 +95,46 @@ pub(crate) trait AbilityInitializerSetup: Sized {
9495
+ 'static
9596
+ Clone,
9697
{
98+
let end = end_custom_phase.clone();
9799
let origin = self.get_key();
98100
self.add_player_event_listener(
99101
event,
100102
move |game, &player_index, event_type| {
101-
let s = &mut game.custom_phase_state;
102-
103103
let player_name = game.players[player_index].get_name();
104104

105-
if let Some(c) = s.current.as_ref() {
105+
if let Some(c) = &game.custom_phase_state.current.as_ref() {
106106
if let Some(action) = &c.response {
107107
assert_eq!(&c.event_type, event_type);
108108
if c.priority != priority {
109109
// not our request
110110
return;
111111
}
112112

113+
let mut ctx = game.custom_phase_state.clone();
114+
ctx.current.as_mut().expect("current missing").response = None;
115+
game.undo_context_stack
116+
.push(UndoContext::CustomPhaseEvent(ctx));
113117
let r = c.request.clone();
114118
let a = action.clone();
115-
s.current = None;
116-
end_custom_phase(game, player_index, &player_name, a, r);
119+
game.custom_phase_state.current = None;
120+
end_custom_phase.clone()(game, player_index, &player_name, a, r);
117121
}
118122
return;
119123
}
120124

121-
if s.last_priority_used.is_some_and(|last| last < priority) {
125+
if game
126+
.custom_phase_state
127+
.last_priority_used
128+
.is_some_and(|last| last < priority)
129+
{
122130
// already handled before
123131
return;
124132
}
125133

126134
if let Some(request) = start_custom_phase(game, player_index, &player_name) {
127135
let s = &mut game.custom_phase_state;
128136
s.last_priority_used = Some(priority);
129-
s.current = Some(CustomPhaseEvent {
137+
s.current = Some(CurrentCustomPhaseEvent {
130138
event_type: event_type.clone(),
131139
priority,
132140
player_index,
@@ -138,14 +146,33 @@ pub(crate) trait AbilityInitializerSetup: Sized {
138146
},
139147
priority,
140148
)
149+
.add_player_event_listener(
150+
|event| &mut event.redo_custom_phase_action,
151+
move |game, state, action| {
152+
if priority != state.priority {
153+
return;
154+
}
155+
156+
let player_index = state.player_index;
157+
let player_name = game.players[player_index].get_name();
158+
let mut ctx = game.custom_phase_state.clone();
159+
ctx.current.as_mut().expect("current missing").response = None;
160+
game.undo_context_stack
161+
.push(UndoContext::CustomPhaseEvent(ctx));
162+
let r = state.request.clone();
163+
let a = action.clone();
164+
game.custom_phase_state = CustomPhaseEventState::new();
165+
end(game, player_index, &player_name, a, r);
166+
},
167+
0,
168+
)
141169
}
142170

143-
#[allow(irrefutable_let_patterns)]
144171
fn add_payment_request_listener<E>(
145172
self,
146173
event: E,
147174
priority: i32,
148-
request: impl Fn(&mut Game, usize) -> Option<Vec<CustomPhasePaymentRequest>> + 'static + Clone, //return option<custom phase state>
175+
request: impl Fn(&mut Game, usize) -> Option<Vec<CustomPhasePaymentRequest>> + 'static + Clone,
149176
gain_reward: impl Fn(&mut Game, usize, &str, &Vec<ResourcePile>) + 'static + Clone,
150177
) -> Self
151178
where
@@ -180,6 +207,52 @@ pub(crate) trait AbilityInitializerSetup: Sized {
180207
)
181208
}
182209

210+
fn add_reward_request_listener<E>(
211+
self,
212+
event: E,
213+
priority: i32,
214+
request: impl Fn(&mut Game, usize) -> Option<CustomPhaseRewardRequest> + 'static + Clone,
215+
gain_reward: impl Fn(&mut Game, usize, &str, &ResourcePile, bool) + 'static + Clone,
216+
) -> Self
217+
where
218+
E: Fn(&mut PlayerEvents) -> &mut EventMut<Game, usize, CustomPhaseEventType>
219+
+ 'static
220+
+ Clone,
221+
{
222+
let g = gain_reward.clone();
223+
self.add_state_change_event_listener(
224+
event,
225+
priority,
226+
move |game, player_index, _player_name| {
227+
let req = request(game, player_index);
228+
if let Some(r) = &req {
229+
if r.model.possible_resource_types().len() == 1 {
230+
let player_name = game.players[player_index].get_name();
231+
g(
232+
game,
233+
player_index,
234+
&player_name,
235+
&r.model.default_payment(),
236+
false,
237+
);
238+
return None;
239+
}
240+
}
241+
req.map(CustomPhaseRequest::Reward)
242+
},
243+
move |game, player_index, player_name, action, request| {
244+
if let CustomPhaseRequest::Reward(request) = &request {
245+
if let CustomPhaseEventAction::Reward(reward) = action {
246+
assert!(request.model.is_valid_payment(&reward), "Invalid payment");
247+
gain_reward(game, player_index, player_name, &reward, true);
248+
return;
249+
}
250+
}
251+
panic!("Invalid state");
252+
},
253+
)
254+
}
255+
183256
fn add_once_per_turn_effect<P>(self, name: &str, pred: P) -> Self
184257
where
185258
P: Fn(&Action) -> bool + 'static + Clone,

server/src/action.rs

+1-10
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::content::custom_phase_actions::{CustomPhaseAction, CustomPhaseEventAction};
1+
use crate::content::custom_phase_actions::CustomPhaseEventAction;
22
use crate::map::Rotation;
33
use crate::playing_actions::PlayingAction;
44
use crate::position::Position;
@@ -15,7 +15,6 @@ pub enum Action {
1515
Combat(CombatAction),
1616
PlaceSettler(Position),
1717
ExploreResolution(Rotation),
18-
CustomPhase(CustomPhaseAction),
1918
CustomPhaseEvent(CustomPhaseEventAction),
2019
Undo,
2120
Redo,
@@ -94,14 +93,6 @@ impl Action {
9493
}
9594
}
9695

97-
#[must_use]
98-
pub fn custom_phase(self) -> Option<CustomPhaseAction> {
99-
if let Self::CustomPhase(v) = self {
100-
Some(v)
101-
} else {
102-
None
103-
}
104-
}
10596
#[must_use]
10697
pub fn custom_phase_event(self) -> Option<CustomPhaseEventAction> {
10798
if let Self::CustomPhaseEvent(v) = self {

server/src/content/advances.rs

+31-18
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ use crate::city_pieces::Building::{Academy, Fortress, Market, Obelisk, Observato
55
use crate::collect::CollectContext;
66
use crate::combat::CombatModifier::*;
77
use crate::combat::{Combat, CombatModifier, CombatStrength};
8-
use crate::content::custom_phase_actions::CustomPhasePaymentRequest;
9-
use crate::content::trade_routes::collect_trade_routes_for_current_player;
8+
use crate::content::custom_phase_actions::{CustomPhasePaymentRequest, CustomPhaseRewardRequest};
9+
use crate::content::trade_routes::{gain_trade_route_reward, trade_route_reward};
1010
use crate::game::GameState;
1111
use crate::payment::PaymentModel;
1212
use crate::playing_actions::{PlayingAction, PlayingActionType};
@@ -474,32 +474,45 @@ fn economy() -> Vec<Advance> {
474474
advance_group(
475475
BARTERING,
476476
vec![
477-
Advance::builder(
478-
BARTERING,
479-
"todo")
477+
Advance::builder(BARTERING, "todo")
480478
.with_advance_bonus(MoodToken)
481479
.with_unlocked_building(Market),
482-
Advance::builder(
483-
"Trade Routes",
484-
"At the beginning of your turn, you gain 1 food for every trade route you can make, to a maximum of 4. A trade route is made between one of your Settlers or Ships and a non-Angry enemy player city within 2 spaces (without counting through unrevealed Regions). Each Settler or Ship can only be paired with one enemy player city. Likewise, each enemy player city must be paired with a different Settler or Ship. In other words, to gain X food you must have at least X Units (Settlers or Ships), each paired with X different enemy cities.")
485-
.with_advance_bonus(MoodToken)
486-
.add_player_event_listener(
487-
|event| &mut event.on_turn_start,
488-
|game, (), ()| {
489-
collect_trade_routes_for_current_player(game);
490-
},
491-
0,
492-
),
480+
trade_routes(),
493481
Advance::builder(
494482
CURRENCY,
495483
// also for Taxation
496-
"You may collect gold instead of food for Trade Routes"
484+
"You may collect gold instead of food for Trade Routes",
497485
)
498-
.with_advance_bonus(CultureToken)
486+
.with_advance_bonus(CultureToken),
499487
],
500488
)
501489
}
502490

491+
fn trade_routes() -> AdvanceBuilder {
492+
Advance::builder(
493+
"Trade Routes",
494+
"At the beginning of your turn, you gain 1 food for every trade route you can make, to a maximum of 4. A trade route is made between one of your Settlers or Ships and a non-Angry enemy player city within 2 spaces (without counting through unrevealed Regions). Each Settler or Ship can only be paired with one enemy player city. Likewise, each enemy player city must be paired with a different Settler or Ship. In other words, to gain X food you must have at least X Units (Settlers or Ships), each paired with X different enemy cities.")
495+
.with_advance_bonus(MoodToken)
496+
.add_reward_request_listener(
497+
|event| &mut event.on_turn_start,
498+
0,
499+
|game, _player_index| {
500+
trade_route_reward(game).map(|(reward, _routes)| {
501+
CustomPhaseRewardRequest {
502+
model: reward,
503+
name: "Collect trade routes reward".to_string(),
504+
}
505+
})
506+
},
507+
|game, player_index, _player_name, p, selected| {
508+
let (reward, routes) =
509+
trade_route_reward(game).expect("No trade route reward");
510+
assert!(reward.is_valid_payment(p), "Invalid payment"); // it's a gain
511+
gain_trade_route_reward(game, player_index, &routes, p, selected);
512+
},
513+
)
514+
}
515+
503516
fn culture() -> Vec<Advance> {
504517
advance_group(
505518
"Arts",

0 commit comments

Comments
 (0)