feat: added parry and homerun
This commit is contained in:
parent
71169690b7
commit
67131c7985
9 changed files with 440 additions and 181 deletions
|
|
@ -3,8 +3,9 @@ use proxisim_models::bundle::{
|
|||
armour::{ArmourBodyPart, ArmourBodyParts, BodyPartCoverage},
|
||||
bonus::{ArmourBypassBonus, DamageMitigationBonus, MiscBonus},
|
||||
player::{
|
||||
Attacker, BodyPart, ChooseWeapon, CombatTurns, Current, CurrentTarget, Defeated, Defender,
|
||||
FightEndType, Health, HealthChange, PartDamageBonus, Player, PlayerStrategy, Weapons,
|
||||
ActionNullification, Attacker, BodyPart, ChooseWeapon, CombatTurns, Current, CurrentTarget,
|
||||
Defeated, Defender, FightEndType, Health, HealthChange, PartDamageBonus, PickedAction,
|
||||
Player, PlayerStrategy, Weapons,
|
||||
},
|
||||
stat::{
|
||||
AmmoControl, Clips, CritRate, DamageBonus, Defence, Dexterity, EffectiveStat, MaxHealth,
|
||||
|
|
@ -33,12 +34,12 @@ use crate::{
|
|||
pub mod stats;
|
||||
pub mod status_effect;
|
||||
|
||||
fn select_weapon<'a>(
|
||||
fn select_weapon(
|
||||
weapons: &Weapons,
|
||||
slot: WeaponSlot,
|
||||
reload: bool,
|
||||
usable_q: &'a Query<(Has<NeedsReload>, Option<&Children>), With<Usable>>,
|
||||
) -> Option<(Entity, Option<&'a Children>)> {
|
||||
usable_q: &Query<Has<NeedsReload>, With<Usable>>,
|
||||
) -> Option<WeaponSlot> {
|
||||
let id = match slot {
|
||||
WeaponSlot::Primary => weapons.primary?,
|
||||
WeaponSlot::Secondary => weapons.secondary?,
|
||||
|
|
@ -48,12 +49,12 @@ fn select_weapon<'a>(
|
|||
WeaponSlot::Kick => weapons.kick,
|
||||
};
|
||||
|
||||
let (needs_reload, children) = usable_q.get(id).ok()?;
|
||||
let needs_reload = usable_q.get(id).ok()?;
|
||||
|
||||
if !reload && needs_reload {
|
||||
None
|
||||
} else {
|
||||
Some((id, children))
|
||||
Some(slot)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -99,37 +100,61 @@ fn check_term_condition(
|
|||
}
|
||||
|
||||
pub fn pick_action(
|
||||
p_query: Query<(Entity, &Weapons, &PlayerStrategy), (With<Player>, Without<PickedAction>)>,
|
||||
usable_q: Query<Has<NeedsReload>, With<Usable>>,
|
||||
mut commands: Commands,
|
||||
) {
|
||||
for (player, weapons, strat) in p_query {
|
||||
let slot = match strat {
|
||||
PlayerStrategy::AlwaysFists => WeaponSlot::Fists,
|
||||
PlayerStrategy::AlwaysKicks => {
|
||||
select_weapon(weapons, WeaponSlot::Kick, true, &usable_q)
|
||||
.unwrap_or(WeaponSlot::Fists)
|
||||
}
|
||||
PlayerStrategy::PrimaryMelee { reload } => {
|
||||
select_weapon(weapons, WeaponSlot::Primary, *reload, &usable_q)
|
||||
.or_else(|| select_weapon(weapons, WeaponSlot::Melee, true, &usable_q))
|
||||
.unwrap_or(WeaponSlot::Fists)
|
||||
}
|
||||
PlayerStrategy::InOrder { order, reload } => order
|
||||
.iter()
|
||||
.find_map(|slot| select_weapon(weapons, *slot, *reload, &usable_q))
|
||||
.unwrap_or(WeaponSlot::Fists),
|
||||
};
|
||||
|
||||
commands.entity(player).insert(PickedAction(slot));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn prepare_weapon(
|
||||
mut p_query: Query<
|
||||
(Entity, &Weapons, &PlayerStrategy, &mut CombatTurns),
|
||||
(Entity, &Weapons, &PickedAction, &mut CombatTurns),
|
||||
(With<Current>, With<Player>),
|
||||
>,
|
||||
target_q: Query<Entity, With<CurrentTarget>>,
|
||||
usable_q: Query<(Has<NeedsReload>, Option<&Children>), With<Usable>>,
|
||||
child_q: Query<Option<&Children>>,
|
||||
weapon_trigger_q: Query<&TurnTriggeredEffect>,
|
||||
mut commands: Commands,
|
||||
mut effects: Effects,
|
||||
metrics: Res<Metrics>,
|
||||
) {
|
||||
let (current, weapons, strat, mut turns) = p_query.single_mut().unwrap();
|
||||
let (weapon, children) = match strat {
|
||||
PlayerStrategy::AlwaysFists => (weapons.fists, None),
|
||||
PlayerStrategy::AlwaysKicks => select_weapon(weapons, WeaponSlot::Kick, true, &usable_q)
|
||||
.unwrap_or_else(|| (weapons.fists, Default::default())),
|
||||
PlayerStrategy::PrimaryMelee { reload } => {
|
||||
select_weapon(weapons, WeaponSlot::Primary, *reload, &usable_q)
|
||||
.or_else(|| select_weapon(weapons, WeaponSlot::Melee, true, &usable_q))
|
||||
.unwrap_or_else(|| (weapons.fists, Default::default()))
|
||||
}
|
||||
PlayerStrategy::InOrder { order, reload } => order
|
||||
.iter()
|
||||
.find_map(|slot| select_weapon(weapons, *slot, *reload, &usable_q))
|
||||
.unwrap_or_else(|| (weapons.fists, Default::default())),
|
||||
let (current, weapons, picked_action, mut turns) = p_query.single_mut().unwrap();
|
||||
let weapon = match picked_action.0 {
|
||||
WeaponSlot::Primary => weapons.primary.unwrap_or(weapons.fists),
|
||||
WeaponSlot::Secondary => weapons.secondary.unwrap_or(weapons.fists),
|
||||
WeaponSlot::Melee => weapons.melee.unwrap_or(weapons.fists),
|
||||
WeaponSlot::Temporary => weapons.temporary.unwrap_or(weapons.fists),
|
||||
WeaponSlot::Fists => weapons.fists,
|
||||
WeaponSlot::Kick => weapons.kick,
|
||||
};
|
||||
|
||||
metrics.increment_counter(Some(current), "turn", 1);
|
||||
metrics.increment_counter(Some(weapon), "turn", 1);
|
||||
|
||||
commands.entity(weapon).insert(Current);
|
||||
|
||||
let children = child_q.get(weapon).unwrap();
|
||||
|
||||
let target = target_q.single().unwrap();
|
||||
|
||||
if let Some(children) = children {
|
||||
|
|
@ -297,6 +322,46 @@ fn check_damage_bonuses(
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(SystemParam)]
|
||||
struct NullificationParams<'w, 's> {
|
||||
nullification_q: Query<'w, 's, &'static ActionNullification>,
|
||||
}
|
||||
|
||||
fn check_action_nullification(
|
||||
params: &NullificationParams,
|
||||
shared: &mut SharedParams,
|
||||
target_children: &Children,
|
||||
target_action: &PickedAction,
|
||||
player_action: &PickedAction,
|
||||
) -> Option<&'static str> {
|
||||
for bonus in params.nullification_q.iter_many(target_children) {
|
||||
match bonus {
|
||||
ActionNullification::Parry { chance }
|
||||
if player_action.0 == WeaponSlot::Melee
|
||||
&& target_action.0 == WeaponSlot::Melee
|
||||
&& (*chance >= 1.0 || shared.rng.random_bool(*chance)) =>
|
||||
{
|
||||
return Some("parry");
|
||||
}
|
||||
ActionNullification::Homerun { chance }
|
||||
if player_action.0 == WeaponSlot::Temporary
|
||||
&& target_action.0 == WeaponSlot::Melee
|
||||
&& (*chance >= 1.0 || shared.rng.random_bool(*chance)) =>
|
||||
{
|
||||
return Some("homerun");
|
||||
}
|
||||
ActionNullification::GentsStripClub
|
||||
if player_action.0 == WeaponSlot::Melee && shared.rng.random_bool(0.25) =>
|
||||
{
|
||||
return Some("dodged");
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
#[derive(SystemParam)]
|
||||
struct DeferredDamageParams<'w, 's> {
|
||||
damage_q: Query<'w, 's, (Entity, &'static DeferredDamage)>,
|
||||
|
|
@ -379,6 +444,7 @@ struct EntityParams<'w, 's> {
|
|||
&'static SimpleStatEffective<DamageBonus>,
|
||||
&'static SimpleStatEffective<MaxHealth>,
|
||||
&'static Children,
|
||||
&'static PickedAction,
|
||||
&'static mut Health,
|
||||
Has<Attacker>,
|
||||
),
|
||||
|
|
@ -395,6 +461,8 @@ struct EntityParams<'w, 's> {
|
|||
&'static EffectiveStat<Defence>,
|
||||
&'static SimpleStatEffective<MaxHealth>,
|
||||
&'static ArmourBodyParts,
|
||||
&'static Children,
|
||||
&'static PickedAction,
|
||||
&'static mut Health,
|
||||
),
|
||||
(With<CurrentTarget>, Without<Current>),
|
||||
|
|
@ -405,11 +473,12 @@ struct EntityParams<'w, 's> {
|
|||
// of multi turn bonuses
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn use_damaging_weapon(
|
||||
(mut shared, mut bonus_mitigation, deferred, misc_bonus): (
|
||||
(mut shared, mut bonus_mitigation, deferred, misc_bonus, nullification): (
|
||||
SharedParams,
|
||||
BonusMitigationParams,
|
||||
DeferredDamageParams,
|
||||
DamageBonusParams,
|
||||
NullificationParams,
|
||||
),
|
||||
mut entities: EntityParams,
|
||||
armour_q: Query<&ArmourBodyPart>,
|
||||
|
|
@ -440,11 +509,20 @@ fn use_damaging_weapon(
|
|||
p_dmg_bonus,
|
||||
p_max_health,
|
||||
p_children,
|
||||
p_action,
|
||||
mut p_health,
|
||||
attacker,
|
||||
) = entities.player_q.single_mut().unwrap();
|
||||
let (target, target_dex, target_def, target_max_health, armour_parts, mut health) =
|
||||
entities.target_q.single_mut().unwrap();
|
||||
let (
|
||||
target,
|
||||
target_dex,
|
||||
target_def,
|
||||
target_max_health,
|
||||
armour_parts,
|
||||
t_children,
|
||||
t_action,
|
||||
mut health,
|
||||
) = entities.target_q.single_mut().unwrap();
|
||||
|
||||
if check_deferred_damage(
|
||||
&deferred,
|
||||
|
|
@ -549,6 +627,26 @@ fn use_damaging_weapon(
|
|||
return;
|
||||
};
|
||||
} else {
|
||||
if multi_attack_proc.is_none()
|
||||
&& let Some(kind) = check_action_nullification(
|
||||
&nullification,
|
||||
&mut shared,
|
||||
t_children,
|
||||
t_action,
|
||||
p_action,
|
||||
)
|
||||
{
|
||||
log!(shared.logger, "nullified", {
|
||||
actor: player,
|
||||
recipient: player,
|
||||
weapon: weapon,
|
||||
rounds,
|
||||
kind,
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
let body_part = if !non_targeted {
|
||||
shared.rng.sample(crit)
|
||||
} else {
|
||||
|
|
@ -641,7 +739,7 @@ fn use_damaging_weapon(
|
|||
shared.metrics.record_histogram(Some(player), "dmg", dmg_i);
|
||||
shared.metrics.record_histogram(Some(weapon), "dmg", dmg_i);
|
||||
|
||||
if dmg_i > 0 {
|
||||
if multi_attack_proc.is_none() && dmg_i > 0 {
|
||||
for effect in damage_proc_q.iter_many(children) {
|
||||
match *effect {
|
||||
DamageProcEffect::MultiTurn { value, bonus } => {
|
||||
|
|
@ -930,6 +1028,24 @@ fn process_health_changes(
|
|||
}
|
||||
}
|
||||
|
||||
fn remove_action_end_of_turn(
|
||||
attacker_turn: Query<Has<Attacker>, (With<Current>, With<Player>)>,
|
||||
picked_q: Query<Entity, With<PickedAction>>,
|
||||
mut commands: Commands,
|
||||
) {
|
||||
if !attacker_turn.single().unwrap() {
|
||||
for player in picked_q {
|
||||
commands.entity(player).remove::<PickedAction>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn remove_picked_actions(picked_q: Query<Entity, With<PickedAction>>, mut commands: Commands) {
|
||||
for player in picked_q {
|
||||
commands.entity(player).remove::<PickedAction>();
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn configure(stages: &mut Stages) {
|
||||
stats::configure(stages);
|
||||
status_effect::configure(stages);
|
||||
|
|
@ -937,19 +1053,28 @@ pub(crate) fn configure(stages: &mut Stages) {
|
|||
stages.add_event::<ChooseWeapon>();
|
||||
stages.add_event::<HealthChange>();
|
||||
stages.equip.add_systems(designate_first);
|
||||
stages.pre_fight.add_systems(restore_health);
|
||||
stages.pre_turn.add_systems(pick_action);
|
||||
stages.turn.add_systems(use_damaging_weapon);
|
||||
stages.pre_fight.add_systems((restore_health, pick_action));
|
||||
stages.pre_turn.add_systems(prepare_weapon);
|
||||
stages
|
||||
.turn
|
||||
.add_systems((use_damaging_weapon, remove_action_end_of_turn));
|
||||
stages
|
||||
.post_turn
|
||||
.add_systems((check_term_condition, change_roles, process_health_changes))
|
||||
.add_systems((
|
||||
check_term_condition,
|
||||
change_roles,
|
||||
process_health_changes,
|
||||
pick_action,
|
||||
))
|
||||
.add_systems(
|
||||
check_stalemate
|
||||
.after(check_term_condition)
|
||||
.before(change_roles),
|
||||
);
|
||||
stages.post_fight.add_systems(record_post_fight_stats);
|
||||
stages
|
||||
.post_fight
|
||||
.add_systems((record_post_fight_stats, remove_picked_actions));
|
||||
stages
|
||||
.restore
|
||||
.add_systems((restore_initial_state, restore_health));
|
||||
.add_systems((restore_initial_state, restore_health, pick_action));
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue