Skip to content

Commit 3a50a28

Browse files
authored
Steel (#135)
1 parent 42338f2 commit 3a50a28

File tree

8 files changed

+314
-77
lines changed

8 files changed

+314
-77
lines changed

client/src/client.rs

+1
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,7 @@ fn render_active_dialog(rc: &RenderContext) -> StateUpdate {
161161
ActiveDialog::Retreat => combat_ui::retreat_dialog(rc),
162162
ActiveDialog::RemoveCasualties(s) => combat_ui::remove_casualties_dialog(rc, s),
163163
ActiveDialog::SiegecraftPayment(p) => combat_ui::pay_siegecraft_dialog(p, rc),
164+
ActiveDialog::SteelWeaponPayment(p) => combat_ui::pay_steel_weapons_dialog(rc, p),
164165
}
165166
}
166167

client/src/client_state.rs

+39-13
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
use macroquad::prelude::*;
22
use server::action::Action;
33
use server::city::{City, MoodState};
4-
use server::combat::{active_attackers, active_defenders, CombatPhase};
5-
use server::content::advances::{NAVIGATION, ROADS, SIEGECRAFT};
6-
use server::content::custom_phase_actions::CustomPhaseState;
4+
use server::combat::{active_attackers, active_defenders, Combat, CombatPhase};
5+
use server::content::advances::{NAVIGATION, ROADS, SIEGECRAFT, STEEL_WEAPONS};
6+
use server::content::custom_phase_actions::{steel_weapons_cost, CustomPhaseState};
77
use server::game::{CulturalInfluenceResolution, CurrentMove, Game, GameState};
88
use server::position::Position;
99
use server::status_phase::{StatusPhaseAction, StatusPhaseState};
@@ -12,14 +12,14 @@ use crate::assets::Assets;
1212
use crate::city_ui::building_name;
1313
use crate::client::{Features, GameSyncRequest};
1414
use crate::collect_ui::CollectResources;
15-
use crate::combat_ui::{RemoveCasualtiesSelection, SiegecraftPaymentModel};
15+
use crate::combat_ui::{RemoveCasualtiesSelection, SiegecraftPaymentDialog, SteelWeaponDialog};
1616
use crate::construct_ui::ConstructionPayment;
1717
use crate::happiness_ui::IncreaseHappinessConfig;
1818
use crate::layout_ui::FONT_SIZE;
19-
use crate::log_ui::advance_help;
19+
use crate::log_ui::{add_advance_help, advance_help};
2020
use crate::map_ui::ExploreResolutionConfig;
2121
use crate::move_ui::{MoveDestination, MoveIntent, MoveSelection};
22-
use crate::payment_ui::Payment;
22+
use crate::payment_ui::{new_payment, Payment};
2323
use crate::recruit_unit_ui::{RecruitAmount, RecruitSelection};
2424
use crate::render_context::RenderContext;
2525
use crate::status_phase_ui::ChooseAdditionalAdvances;
@@ -57,7 +57,8 @@ pub enum ActiveDialog {
5757
PlaceSettler,
5858
Retreat,
5959
RemoveCasualties(RemoveCasualtiesSelection),
60-
SiegecraftPayment(SiegecraftPaymentModel),
60+
SiegecraftPayment(SiegecraftPaymentDialog),
61+
SteelWeaponPayment(SteelWeaponDialog),
6162
}
6263

6364
impl ActiveDialog {
@@ -90,6 +91,7 @@ impl ActiveDialog {
9091
ActiveDialog::Retreat => "retreat",
9192
ActiveDialog::RemoveCasualties(_) => "remove casualties",
9293
ActiveDialog::SiegecraftPayment(_) => "siegecraft payment",
94+
ActiveDialog::SteelWeaponPayment(_) => "steel weapon payment",
9395
}
9496
}
9597

@@ -130,8 +132,8 @@ impl ActiveDialog {
130132
{
131133
result.push("Click on a carrier to embark units".to_string());
132134
};
133-
advance_help(rc, &mut result, NAVIGATION);
134-
advance_help(rc, &mut result, ROADS);
135+
add_advance_help(rc, &mut result, NAVIGATION);
136+
add_advance_help(rc, &mut result, ROADS);
135137
result
136138
} else {
137139
vec!["Click on a unit to move".to_string()]
@@ -174,9 +176,13 @@ impl ActiveDialog {
174176
r.needed
175177
)],
176178
ActiveDialog::WaitingForUpdate => vec!["Waiting for server update".to_string()],
177-
ActiveDialog::SiegecraftPayment(_) => {
178-
let mut result = vec![];
179-
advance_help(rc, &mut result, SIEGECRAFT);
179+
ActiveDialog::SiegecraftPayment(_) => advance_help(rc, SIEGECRAFT),
180+
ActiveDialog::SteelWeaponPayment(p) => {
181+
let mut result = vec![format!(
182+
"{}: ",
183+
if p.attacker { "Attacker" } else { "Defender" },
184+
)];
185+
add_advance_help(rc, &mut result, STEEL_WEAPONS);
180186
result
181187
}
182188
}
@@ -548,12 +554,32 @@ impl State {
548554
}
549555
GameState::CustomPhase(c) => match c {
550556
CustomPhaseState::SiegecraftPayment(_) => {
551-
ActiveDialog::SiegecraftPayment(SiegecraftPaymentModel::new(game))
557+
ActiveDialog::SiegecraftPayment(SiegecraftPaymentDialog::new(game))
558+
}
559+
CustomPhaseState::SteelWeaponsAttacker(c) => {
560+
Self::steel_weapons_dialog(game, c, c.attacker)
561+
}
562+
CustomPhaseState::SteelWeaponsDefender(c) => {
563+
Self::steel_weapons_dialog(game, c, c.defender)
552564
}
553565
},
554566
}
555567
}
556568

569+
fn steel_weapons_dialog(game: &Game, c: &Combat, player_index: usize) -> ActiveDialog {
570+
let payment = new_payment(
571+
&steel_weapons_cost(game, c, player_index),
572+
&game.get_player(player_index).resources,
573+
"Use steel weapons",
574+
true,
575+
);
576+
ActiveDialog::SteelWeaponPayment(SteelWeaponDialog {
577+
attacker: player_index == c.attacker,
578+
payment,
579+
combat: c.clone(),
580+
})
581+
}
582+
557583
#[must_use]
558584
pub fn measure_text(&self, text: &str) -> TextDimensions {
559585
measure_text(text, Some(&self.assets.font), FONT_SIZE, 1.0)

client/src/combat_ui.rs

+43-6
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use crate::select_ui::ConfirmSelection;
66
use crate::unit_ui;
77
use crate::unit_ui::UnitSelection;
88
use server::action::{Action, CombatAction, PlayActionCard};
9+
use server::combat::Combat;
910
use server::content::custom_phase_actions::{
1011
CustomPhaseAction, SiegecraftPayment, SIEGECRAFT_EXTRA_DIE, SIEGECRAFT_IGNORE_HIT,
1112
};
@@ -98,15 +99,15 @@ pub fn play_action_card_dialog(rc: &RenderContext) -> StateUpdate {
9899
}
99100

100101
#[derive(Clone)]
101-
pub struct SiegecraftPaymentModel {
102+
pub struct SiegecraftPaymentDialog {
102103
extra_die: Payment,
103104
ignore_hit: Payment,
104105
}
105106

106-
impl SiegecraftPaymentModel {
107-
pub fn new(game: &Game) -> SiegecraftPaymentModel {
107+
impl SiegecraftPaymentDialog {
108+
pub fn new(game: &Game) -> SiegecraftPaymentDialog {
108109
let available = game.get_player(game.active_player()).resources.clone();
109-
SiegecraftPaymentModel {
110+
SiegecraftPaymentDialog {
110111
extra_die: new_payment(
111112
&SIEGECRAFT_EXTRA_DIE,
112113
&available,
@@ -123,12 +124,12 @@ impl SiegecraftPaymentModel {
123124
}
124125
}
125126

126-
pub fn pay_siegecraft_dialog(p: &SiegecraftPaymentModel, rc: &RenderContext) -> StateUpdate {
127+
pub fn pay_siegecraft_dialog(p: &SiegecraftPaymentDialog, rc: &RenderContext) -> StateUpdate {
127128
payment_model_dialog(
128129
rc,
129130
&[p.extra_die.clone(), p.ignore_hit.clone()],
130131
|p| {
131-
ActiveDialog::SiegecraftPayment(SiegecraftPaymentModel {
132+
ActiveDialog::SiegecraftPayment(SiegecraftPaymentDialog {
132133
extra_die: p[0].clone(),
133134
ignore_hit: p[1].clone(),
134135
})
@@ -144,3 +145,39 @@ pub fn pay_siegecraft_dialog(p: &SiegecraftPaymentModel, rc: &RenderContext) ->
144145
},
145146
)
146147
}
148+
149+
#[derive(Clone)]
150+
pub struct SteelWeaponDialog {
151+
pub attacker: bool,
152+
pub payment: Payment,
153+
pub combat: Combat,
154+
}
155+
156+
pub(crate) fn pay_steel_weapons_dialog(
157+
rc: &RenderContext,
158+
dialog: &SteelWeaponDialog,
159+
) -> StateUpdate {
160+
let attacker = dialog.attacker;
161+
162+
payment_model_dialog(
163+
rc,
164+
&[dialog.payment.clone()],
165+
|p| {
166+
let mut n = dialog.clone();
167+
n.payment = p[0].clone();
168+
ActiveDialog::SteelWeaponPayment(n)
169+
},
170+
false,
171+
|p| {
172+
if attacker {
173+
StateUpdate::Execute(Action::CustomPhase(
174+
CustomPhaseAction::SteelWeaponsAttackerAction(p[0].clone()),
175+
))
176+
} else {
177+
StateUpdate::Execute(Action::CustomPhase(
178+
CustomPhaseAction::SteelWeaponsDefenderAction(p[0].clone()),
179+
))
180+
}
181+
},
182+
)
183+
}

client/src/log_ui.rs

+7-1
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,13 @@ pub fn break_text(label: &str, len: usize, result: &mut Vec<String>) {
4040
});
4141
}
4242

43-
pub fn advance_help(rc: &RenderContext, result: &mut Vec<String>, advance: &str) {
43+
pub fn advance_help(rc: &RenderContext, advance: &str) -> Vec<String> {
44+
let mut result = vec![];
45+
add_advance_help(rc, &mut result, advance);
46+
result
47+
}
48+
49+
pub fn add_advance_help(rc: &RenderContext, result: &mut Vec<String>, advance: &str) {
4450
if rc.shown_player.has_advance(advance) {
4551
break_text(
4652
&format!("{}: {}", advance, get_advance_by_name(advance).description),

server/src/combat.rs

+79-16
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
use crate::action::{CombatAction, PlayActionCard};
2-
use crate::content::custom_phase_actions::start_siegecraft_phase;
2+
use crate::combat::CombatModifier::{
3+
CancelFortressExtraDie, SteelWeaponsAttacker, SteelWeaponsDefender,
4+
};
5+
use crate::content::advances::STEEL_WEAPONS;
6+
use crate::content::custom_phase_actions::{start_siegecraft_phase, start_steel_weapons_phase};
37
use crate::game::GameState::Playing;
48
use crate::game::{Game, GameState};
59
use crate::position::Position;
@@ -33,10 +37,12 @@ impl CombatPhase {
3337
}
3438
}
3539

36-
#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
40+
#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug, Copy)]
3741
pub enum CombatModifier {
3842
CancelFortressExtraDie,
3943
CancelFortressIgnoreHit,
44+
SteelWeaponsAttacker,
45+
SteelWeaponsDefender,
4046
}
4147

4248
#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
@@ -127,8 +133,34 @@ pub fn initiate_combat(
127133
can_retreat,
128134
);
129135

130-
if start_siegecraft_phase(game, attacker, defender_position, combat.clone()) {
131-
return;
136+
start_combat(game, combat, None);
137+
}
138+
139+
pub(crate) fn start_combat(game: &mut Game, combat: Combat, skip: Option<CombatModifier>) {
140+
if skip != Some(CancelFortressExtraDie) {
141+
if skip != Some(SteelWeaponsDefender) {
142+
if skip != Some(SteelWeaponsAttacker)
143+
&& start_steel_weapons_phase(
144+
game,
145+
combat.clone(),
146+
combat.attacker,
147+
SteelWeaponsAttacker,
148+
)
149+
{
150+
return;
151+
}
152+
if start_steel_weapons_phase(
153+
game,
154+
combat.clone(),
155+
combat.defender,
156+
SteelWeaponsDefender,
157+
) {
158+
return;
159+
}
160+
}
161+
if start_siegecraft_phase(game, combat.clone()) {
162+
return;
163+
}
132164
}
133165

134166
combat_loop(game, combat);
@@ -230,23 +262,51 @@ pub fn combat_loop(game: &mut Game, mut c: Combat) {
230262
game.add_info_log_item(format!("\nCombat round {}", c.round));
231263
//todo: go into tactics phase if either player has tactics card (also if they can not play it unless otherwise specified via setting)
232264

265+
let steel_weapon_value = if game.get_player(c.attacker).has_advance(STEEL_WEAPONS)
266+
&& game.get_player(c.defender).has_advance(STEEL_WEAPONS)
267+
{
268+
1
269+
} else {
270+
2
271+
};
272+
let mut steel_weapon_log = vec![];
233273
let attacker_name = game.players[c.attacker].get_name();
234274
let active_attackers = c.active_attackers(game);
275+
let attacker_extra = if c.modifiers.contains(&SteelWeaponsAttacker) {
276+
steel_weapon_log.push(format!(
277+
"Attacker used steel weapons to add {steel_weapon_value} to their combat value"
278+
));
279+
steel_weapon_value
280+
} else {
281+
0
282+
};
235283
let mut attacker_log = vec![];
236-
let attacker_rolls = roll(game, c.attacker, &active_attackers, 0, &mut attacker_log);
284+
let attacker_rolls = roll(
285+
game,
286+
c.attacker,
287+
&active_attackers,
288+
0,
289+
attacker_extra,
290+
&mut attacker_log,
291+
);
237292
let attacker_log_str = roll_log_str(&attacker_log);
238-
let active_defenders = active_defenders(game, c.defender, c.defender_position);
239293

294+
let active_defenders = active_defenders(game, c.defender, c.defender_position);
240295
let defender_name = game.players[c.defender].get_name();
241296
let mut defender_log = vec![];
242297
let mut fortress_log = vec![];
243-
let extra_defender_dies = if c.defender_fortress(game)
244-
&& !c
245-
.modifiers
246-
.contains(&CombatModifier::CancelFortressExtraDie)
247-
{
248-
fortress_log.push("added one extra die");
249-
1
298+
let extra_defender_dies =
299+
if c.defender_fortress(game) && !c.modifiers.contains(&CancelFortressExtraDie) {
300+
fortress_log.push("added one extra die");
301+
1
302+
} else {
303+
0
304+
};
305+
let defender_extra = if c.modifiers.contains(&SteelWeaponsDefender) {
306+
steel_weapon_log.push(format!(
307+
"Defender used steel weapons to add {steel_weapon_value} to their combat value"
308+
));
309+
steel_weapon_value
250310
} else {
251311
0
252312
};
@@ -255,6 +315,7 @@ pub fn combat_loop(game: &mut Game, mut c: Combat) {
255315
c.defender,
256316
&active_defenders,
257317
extra_defender_dies,
318+
defender_extra,
258319
&mut defender_log,
259320
);
260321
let defender_log_str = roll_log_str(&defender_log);
@@ -273,6 +334,9 @@ pub fn combat_loop(game: &mut Game, mut c: Combat) {
273334
let attacker_hits = (attacker_combat_value / 5).saturating_sub(defender_hit_cancels);
274335
let defender_hits = (defender_combat_value / 5).saturating_sub(attacker_hit_cancels);
275336
game.add_info_log_item(format!("\t{attacker_name} rolled {attacker_log_str} for combined combat value of {attacker_combat_value} and gets {attacker_hits} hits against defending units. {defender_name} rolled {defender_log_str} for combined combat value of {defender_combat_value} and gets {defender_hits} hits against attacking units."));
337+
if !steel_weapon_log.is_empty() {
338+
game.add_info_log_item(steel_weapon_log.join(", "));
339+
}
276340
if !fortress_log.is_empty() {
277341
game.add_info_log_item(format!(
278342
" {defender_name} has a fortress, which {}",
@@ -472,6 +536,7 @@ fn roll(
472536
player_index: usize,
473537
units: &Vec<u32>,
474538
extra_dies: u8,
539+
extra_combat_value: u8,
475540
roll_log: &mut Vec<String>,
476541
) -> CombatRolls {
477542
let mut dice_rolls = extra_dies;
@@ -486,11 +551,9 @@ fn roll(
486551
}
487552

488553
let mut rolls = CombatRolls {
489-
combat_value: 0,
554+
combat_value: extra_combat_value,
490555
hit_cancels: 0,
491556
};
492-
rolls.combat_value = 0;
493-
rolls.hit_cancels = 0;
494557
for _ in 0..dice_rolls {
495558
let dice_roll = dice_roll_with_leader_reroll(game, &mut unit_types, roll_log);
496559
let value = dice_roll.value;

0 commit comments

Comments
 (0)