Skip to content

Commit

Permalink
Steel (#135)
Browse files Browse the repository at this point in the history
  • Loading branch information
zeitlinger authored Jan 25, 2025
1 parent 42338f2 commit 3a50a28
Show file tree
Hide file tree
Showing 8 changed files with 314 additions and 77 deletions.
1 change: 1 addition & 0 deletions client/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ fn render_active_dialog(rc: &RenderContext) -> StateUpdate {
ActiveDialog::Retreat => combat_ui::retreat_dialog(rc),
ActiveDialog::RemoveCasualties(s) => combat_ui::remove_casualties_dialog(rc, s),
ActiveDialog::SiegecraftPayment(p) => combat_ui::pay_siegecraft_dialog(p, rc),
ActiveDialog::SteelWeaponPayment(p) => combat_ui::pay_steel_weapons_dialog(rc, p),
}
}

Expand Down
52 changes: 39 additions & 13 deletions client/src/client_state.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
use macroquad::prelude::*;
use server::action::Action;
use server::city::{City, MoodState};
use server::combat::{active_attackers, active_defenders, CombatPhase};
use server::content::advances::{NAVIGATION, ROADS, SIEGECRAFT};
use server::content::custom_phase_actions::CustomPhaseState;
use server::combat::{active_attackers, active_defenders, Combat, CombatPhase};
use server::content::advances::{NAVIGATION, ROADS, SIEGECRAFT, STEEL_WEAPONS};
use server::content::custom_phase_actions::{steel_weapons_cost, CustomPhaseState};
use server::game::{CulturalInfluenceResolution, CurrentMove, Game, GameState};
use server::position::Position;
use server::status_phase::{StatusPhaseAction, StatusPhaseState};
Expand All @@ -12,14 +12,14 @@ use crate::assets::Assets;
use crate::city_ui::building_name;
use crate::client::{Features, GameSyncRequest};
use crate::collect_ui::CollectResources;
use crate::combat_ui::{RemoveCasualtiesSelection, SiegecraftPaymentModel};
use crate::combat_ui::{RemoveCasualtiesSelection, SiegecraftPaymentDialog, SteelWeaponDialog};
use crate::construct_ui::ConstructionPayment;
use crate::happiness_ui::IncreaseHappinessConfig;
use crate::layout_ui::FONT_SIZE;
use crate::log_ui::advance_help;
use crate::log_ui::{add_advance_help, advance_help};
use crate::map_ui::ExploreResolutionConfig;
use crate::move_ui::{MoveDestination, MoveIntent, MoveSelection};
use crate::payment_ui::Payment;
use crate::payment_ui::{new_payment, Payment};
use crate::recruit_unit_ui::{RecruitAmount, RecruitSelection};
use crate::render_context::RenderContext;
use crate::status_phase_ui::ChooseAdditionalAdvances;
Expand Down Expand Up @@ -57,7 +57,8 @@ pub enum ActiveDialog {
PlaceSettler,
Retreat,
RemoveCasualties(RemoveCasualtiesSelection),
SiegecraftPayment(SiegecraftPaymentModel),
SiegecraftPayment(SiegecraftPaymentDialog),
SteelWeaponPayment(SteelWeaponDialog),
}

impl ActiveDialog {
Expand Down Expand Up @@ -90,6 +91,7 @@ impl ActiveDialog {
ActiveDialog::Retreat => "retreat",
ActiveDialog::RemoveCasualties(_) => "remove casualties",
ActiveDialog::SiegecraftPayment(_) => "siegecraft payment",
ActiveDialog::SteelWeaponPayment(_) => "steel weapon payment",
}
}

Expand Down Expand Up @@ -130,8 +132,8 @@ impl ActiveDialog {
{
result.push("Click on a carrier to embark units".to_string());
};
advance_help(rc, &mut result, NAVIGATION);
advance_help(rc, &mut result, ROADS);
add_advance_help(rc, &mut result, NAVIGATION);
add_advance_help(rc, &mut result, ROADS);
result
} else {
vec!["Click on a unit to move".to_string()]
Expand Down Expand Up @@ -174,9 +176,13 @@ impl ActiveDialog {
r.needed
)],
ActiveDialog::WaitingForUpdate => vec!["Waiting for server update".to_string()],
ActiveDialog::SiegecraftPayment(_) => {
let mut result = vec![];
advance_help(rc, &mut result, SIEGECRAFT);
ActiveDialog::SiegecraftPayment(_) => advance_help(rc, SIEGECRAFT),
ActiveDialog::SteelWeaponPayment(p) => {
let mut result = vec![format!(
"{}: ",
if p.attacker { "Attacker" } else { "Defender" },
)];
add_advance_help(rc, &mut result, STEEL_WEAPONS);
result
}
}
Expand Down Expand Up @@ -548,12 +554,32 @@ impl State {
}
GameState::CustomPhase(c) => match c {
CustomPhaseState::SiegecraftPayment(_) => {
ActiveDialog::SiegecraftPayment(SiegecraftPaymentModel::new(game))
ActiveDialog::SiegecraftPayment(SiegecraftPaymentDialog::new(game))
}
CustomPhaseState::SteelWeaponsAttacker(c) => {
Self::steel_weapons_dialog(game, c, c.attacker)
}
CustomPhaseState::SteelWeaponsDefender(c) => {
Self::steel_weapons_dialog(game, c, c.defender)
}
},
}
}

fn steel_weapons_dialog(game: &Game, c: &Combat, player_index: usize) -> ActiveDialog {
let payment = new_payment(
&steel_weapons_cost(game, c, player_index),
&game.get_player(player_index).resources,
"Use steel weapons",
true,
);
ActiveDialog::SteelWeaponPayment(SteelWeaponDialog {
attacker: player_index == c.attacker,
payment,
combat: c.clone(),
})
}

#[must_use]
pub fn measure_text(&self, text: &str) -> TextDimensions {
measure_text(text, Some(&self.assets.font), FONT_SIZE, 1.0)
Expand Down
49 changes: 43 additions & 6 deletions client/src/combat_ui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use crate::select_ui::ConfirmSelection;
use crate::unit_ui;
use crate::unit_ui::UnitSelection;
use server::action::{Action, CombatAction, PlayActionCard};
use server::combat::Combat;
use server::content::custom_phase_actions::{
CustomPhaseAction, SiegecraftPayment, SIEGECRAFT_EXTRA_DIE, SIEGECRAFT_IGNORE_HIT,
};
Expand Down Expand Up @@ -98,15 +99,15 @@ pub fn play_action_card_dialog(rc: &RenderContext) -> StateUpdate {
}

#[derive(Clone)]
pub struct SiegecraftPaymentModel {
pub struct SiegecraftPaymentDialog {
extra_die: Payment,
ignore_hit: Payment,
}

impl SiegecraftPaymentModel {
pub fn new(game: &Game) -> SiegecraftPaymentModel {
impl SiegecraftPaymentDialog {
pub fn new(game: &Game) -> SiegecraftPaymentDialog {
let available = game.get_player(game.active_player()).resources.clone();
SiegecraftPaymentModel {
SiegecraftPaymentDialog {
extra_die: new_payment(
&SIEGECRAFT_EXTRA_DIE,
&available,
Expand All @@ -123,12 +124,12 @@ impl SiegecraftPaymentModel {
}
}

pub fn pay_siegecraft_dialog(p: &SiegecraftPaymentModel, rc: &RenderContext) -> StateUpdate {
pub fn pay_siegecraft_dialog(p: &SiegecraftPaymentDialog, rc: &RenderContext) -> StateUpdate {
payment_model_dialog(
rc,
&[p.extra_die.clone(), p.ignore_hit.clone()],
|p| {
ActiveDialog::SiegecraftPayment(SiegecraftPaymentModel {
ActiveDialog::SiegecraftPayment(SiegecraftPaymentDialog {
extra_die: p[0].clone(),
ignore_hit: p[1].clone(),
})
Expand All @@ -144,3 +145,39 @@ pub fn pay_siegecraft_dialog(p: &SiegecraftPaymentModel, rc: &RenderContext) ->
},
)
}

#[derive(Clone)]
pub struct SteelWeaponDialog {
pub attacker: bool,
pub payment: Payment,
pub combat: Combat,
}

pub(crate) fn pay_steel_weapons_dialog(
rc: &RenderContext,
dialog: &SteelWeaponDialog,
) -> StateUpdate {
let attacker = dialog.attacker;

payment_model_dialog(
rc,
&[dialog.payment.clone()],
|p| {
let mut n = dialog.clone();
n.payment = p[0].clone();
ActiveDialog::SteelWeaponPayment(n)
},
false,
|p| {
if attacker {
StateUpdate::Execute(Action::CustomPhase(
CustomPhaseAction::SteelWeaponsAttackerAction(p[0].clone()),
))
} else {
StateUpdate::Execute(Action::CustomPhase(
CustomPhaseAction::SteelWeaponsDefenderAction(p[0].clone()),
))
}
},
)
}
8 changes: 7 additions & 1 deletion client/src/log_ui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,13 @@ pub fn break_text(label: &str, len: usize, result: &mut Vec<String>) {
});
}

pub fn advance_help(rc: &RenderContext, result: &mut Vec<String>, advance: &str) {
pub fn advance_help(rc: &RenderContext, advance: &str) -> Vec<String> {
let mut result = vec![];
add_advance_help(rc, &mut result, advance);
result
}

pub fn add_advance_help(rc: &RenderContext, result: &mut Vec<String>, advance: &str) {
if rc.shown_player.has_advance(advance) {
break_text(
&format!("{}: {}", advance, get_advance_by_name(advance).description),
Expand Down
95 changes: 79 additions & 16 deletions server/src/combat.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
use crate::action::{CombatAction, PlayActionCard};
use crate::content::custom_phase_actions::start_siegecraft_phase;
use crate::combat::CombatModifier::{
CancelFortressExtraDie, SteelWeaponsAttacker, SteelWeaponsDefender,
};
use crate::content::advances::STEEL_WEAPONS;
use crate::content::custom_phase_actions::{start_siegecraft_phase, start_steel_weapons_phase};
use crate::game::GameState::Playing;
use crate::game::{Game, GameState};
use crate::position::Position;
Expand Down Expand Up @@ -33,10 +37,12 @@ impl CombatPhase {
}
}

#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug, Copy)]
pub enum CombatModifier {
CancelFortressExtraDie,
CancelFortressIgnoreHit,
SteelWeaponsAttacker,
SteelWeaponsDefender,
}

#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
Expand Down Expand Up @@ -127,8 +133,34 @@ pub fn initiate_combat(
can_retreat,
);

if start_siegecraft_phase(game, attacker, defender_position, combat.clone()) {
return;
start_combat(game, combat, None);
}

pub(crate) fn start_combat(game: &mut Game, combat: Combat, skip: Option<CombatModifier>) {
if skip != Some(CancelFortressExtraDie) {
if skip != Some(SteelWeaponsDefender) {
if skip != Some(SteelWeaponsAttacker)
&& start_steel_weapons_phase(
game,
combat.clone(),
combat.attacker,
SteelWeaponsAttacker,
)
{
return;
}
if start_steel_weapons_phase(
game,
combat.clone(),
combat.defender,
SteelWeaponsDefender,
) {
return;
}
}
if start_siegecraft_phase(game, combat.clone()) {
return;
}
}

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

let steel_weapon_value = if game.get_player(c.attacker).has_advance(STEEL_WEAPONS)
&& game.get_player(c.defender).has_advance(STEEL_WEAPONS)
{
1
} else {
2
};
let mut steel_weapon_log = vec![];
let attacker_name = game.players[c.attacker].get_name();
let active_attackers = c.active_attackers(game);
let attacker_extra = if c.modifiers.contains(&SteelWeaponsAttacker) {
steel_weapon_log.push(format!(
"Attacker used steel weapons to add {steel_weapon_value} to their combat value"
));
steel_weapon_value
} else {
0
};
let mut attacker_log = vec![];
let attacker_rolls = roll(game, c.attacker, &active_attackers, 0, &mut attacker_log);
let attacker_rolls = roll(
game,
c.attacker,
&active_attackers,
0,
attacker_extra,
&mut attacker_log,
);
let attacker_log_str = roll_log_str(&attacker_log);
let active_defenders = active_defenders(game, c.defender, c.defender_position);

let active_defenders = active_defenders(game, c.defender, c.defender_position);
let defender_name = game.players[c.defender].get_name();
let mut defender_log = vec![];
let mut fortress_log = vec![];
let extra_defender_dies = if c.defender_fortress(game)
&& !c
.modifiers
.contains(&CombatModifier::CancelFortressExtraDie)
{
fortress_log.push("added one extra die");
1
let extra_defender_dies =
if c.defender_fortress(game) && !c.modifiers.contains(&CancelFortressExtraDie) {
fortress_log.push("added one extra die");
1
} else {
0
};
let defender_extra = if c.modifiers.contains(&SteelWeaponsDefender) {
steel_weapon_log.push(format!(
"Defender used steel weapons to add {steel_weapon_value} to their combat value"
));
steel_weapon_value
} else {
0
};
Expand All @@ -255,6 +315,7 @@ pub fn combat_loop(game: &mut Game, mut c: Combat) {
c.defender,
&active_defenders,
extra_defender_dies,
defender_extra,
&mut defender_log,
);
let defender_log_str = roll_log_str(&defender_log);
Expand All @@ -273,6 +334,9 @@ pub fn combat_loop(game: &mut Game, mut c: Combat) {
let attacker_hits = (attacker_combat_value / 5).saturating_sub(defender_hit_cancels);
let defender_hits = (defender_combat_value / 5).saturating_sub(attacker_hit_cancels);
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."));
if !steel_weapon_log.is_empty() {
game.add_info_log_item(steel_weapon_log.join(", "));
}
if !fortress_log.is_empty() {
game.add_info_log_item(format!(
" {defender_name} has a fortress, which {}",
Expand Down Expand Up @@ -472,6 +536,7 @@ fn roll(
player_index: usize,
units: &Vec<u32>,
extra_dies: u8,
extra_combat_value: u8,
roll_log: &mut Vec<String>,
) -> CombatRolls {
let mut dice_rolls = extra_dies;
Expand All @@ -486,11 +551,9 @@ fn roll(
}

let mut rolls = CombatRolls {
combat_value: 0,
combat_value: extra_combat_value,
hit_cancels: 0,
};
rolls.combat_value = 0;
rolls.hit_cancels = 0;
for _ in 0..dice_rolls {
let dice_roll = dice_roll_with_leader_reroll(game, &mut unit_types, roll_log);
let value = dice_roll.value;
Expand Down
Loading

0 comments on commit 3a50a28

Please sign in to comment.