feat: added armour passives
This commit is contained in:
parent
cfe2631578
commit
b92ad37a5b
17 changed files with 520 additions and 316 deletions
|
|
@ -14,6 +14,8 @@ json = ["dep:serde", "dep:serde_json"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bevy_ecs = { version = "0.17.2", features = [] }
|
bevy_ecs = { version = "0.17.2", features = [] }
|
||||||
|
bevy_utils = { version = "0.17.2", features = ["debug"] }
|
||||||
|
bevy_reflect = { version = "0.17.2", features = ["debug"] }
|
||||||
rand = { version = "0.9.2", default-features = false, features = [
|
rand = { version = "0.9.2", default-features = false, features = [
|
||||||
"std",
|
"std",
|
||||||
"alloc",
|
"alloc",
|
||||||
|
|
|
||||||
|
|
@ -183,7 +183,7 @@ impl BonusPartDamageBonus {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Display)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Component, Display)]
|
||||||
#[cfg_attr(feature = "json", derive(serde::Deserialize, serde::Serialize))]
|
#[cfg_attr(feature = "json", derive(serde::Deserialize, serde::Serialize))]
|
||||||
#[cfg_attr(feature = "json", serde(rename_all = "snake_case"))]
|
#[cfg_attr(feature = "json", serde(rename_all = "snake_case"))]
|
||||||
pub enum ArmourBonusType {
|
pub enum ArmourBonusType {
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ use crate::bundle::{
|
||||||
Name,
|
Name,
|
||||||
bonus::BonusPartDamageBonus,
|
bonus::BonusPartDamageBonus,
|
||||||
passive::EducationPartDamageBonus,
|
passive::EducationPartDamageBonus,
|
||||||
stat::{CritRate, DamageBonus, SimpleStatBundle, WeaponAccuracy},
|
stat::{CritRate, DamageBonus, MaxHealth, SimpleStatBundle, WeaponAccuracy},
|
||||||
weapon::WeaponSlot,
|
weapon::WeaponSlot,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -37,13 +37,7 @@ impl Default for Level {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Component, Debug)]
|
#[derive(Component, Debug)]
|
||||||
pub struct MaxHealth(pub u16);
|
pub struct Health(pub u16);
|
||||||
|
|
||||||
impl Default for MaxHealth {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self(100)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Component, Debug, Default)]
|
#[derive(Component, Debug, Default)]
|
||||||
pub struct CombatTurns(pub u16);
|
pub struct CombatTurns(pub u16);
|
||||||
|
|
@ -158,6 +152,7 @@ pub struct PlayerBundle {
|
||||||
pub name: Name,
|
pub name: Name,
|
||||||
pub player: Player,
|
pub player: Player,
|
||||||
pub level: Level,
|
pub level: Level,
|
||||||
|
pub max_health: SimpleStatBundle<MaxHealth>,
|
||||||
pub crit_rate: SimpleStatBundle<CritRate>,
|
pub crit_rate: SimpleStatBundle<CritRate>,
|
||||||
// TODO: since these two need to be tracked here anyways it might be preferable to shift all
|
// TODO: since these two need to be tracked here anyways it might be preferable to shift all
|
||||||
// player specific passives here instead of tracking them on the weapons
|
// player specific passives here instead of tracking them on the weapons
|
||||||
|
|
@ -174,6 +169,12 @@ impl PlayerBundle {
|
||||||
name: Name(name.to_string()),
|
name: Name(name.to_string()),
|
||||||
player: Player,
|
player: Player,
|
||||||
level: Level(level),
|
level: Level(level),
|
||||||
|
max_health: SimpleStatBundle::new(match level {
|
||||||
|
1..=8 => 100 + (level - 1) * 25,
|
||||||
|
9..=95 => 275 + (level - 8) * 50,
|
||||||
|
96.. => 4625 + (level - 95) * 75,
|
||||||
|
0 => unreachable!(),
|
||||||
|
}),
|
||||||
crit_rate: SimpleStatBundle::new(24),
|
crit_rate: SimpleStatBundle::new(24),
|
||||||
acc_bonus: SimpleStatBundle::new(0.0),
|
acc_bonus: SimpleStatBundle::new(0.0),
|
||||||
dmg_bonus: SimpleStatBundle::new(0.0),
|
dmg_bonus: SimpleStatBundle::new(0.0),
|
||||||
|
|
|
||||||
|
|
@ -226,9 +226,23 @@ impl SimpleStatMarker for Clips {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct Health;
|
pub struct MaxHealth;
|
||||||
|
|
||||||
impl SimpleStatMarker for Health {
|
impl SimpleStatMarker for MaxHealth {
|
||||||
|
type ValueType = u16;
|
||||||
|
type BonusType = f32;
|
||||||
|
fn apply_bonus(value: Self::ValueType, bonus: Self::BonusType) -> Self::ValueType {
|
||||||
|
((value as f32) * bonus) as u16
|
||||||
|
}
|
||||||
|
fn revert_bonus(value: Self::ValueType, bonus: Self::BonusType) -> Self::ValueType {
|
||||||
|
((value as f32) / bonus) as u16
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct ArmourBonusValue;
|
||||||
|
|
||||||
|
impl SimpleStatMarker for ArmourBonusValue {
|
||||||
type ValueType = u16;
|
type ValueType = u16;
|
||||||
type BonusType = u16;
|
type BonusType = u16;
|
||||||
fn apply_bonus(value: Self::ValueType, bonus: Self::BonusType) -> Self::ValueType {
|
fn apply_bonus(value: Self::ValueType, bonus: Self::BonusType) -> Self::ValueType {
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ use crate::{
|
||||||
Name,
|
Name,
|
||||||
armour::{Armour, ArmourCoverage, ArmourValue, Immunities, Immunity},
|
armour::{Armour, ArmourCoverage, ArmourValue, Immunities, Immunity},
|
||||||
bonus::ArmourBonusType,
|
bonus::ArmourBonusType,
|
||||||
|
stat::{ArmourBonusValue, SimpleStatBundle},
|
||||||
},
|
},
|
||||||
dto::draw_id,
|
dto::draw_id,
|
||||||
};
|
};
|
||||||
|
|
@ -27,7 +28,7 @@ pub enum ArmourSlot {
|
||||||
#[cfg_attr(feature = "json", derive(serde::Deserialize, serde::Serialize))]
|
#[cfg_attr(feature = "json", derive(serde::Deserialize, serde::Serialize))]
|
||||||
pub struct ArmourBonusInfo {
|
pub struct ArmourBonusInfo {
|
||||||
pub kind: ArmourBonusType,
|
pub kind: ArmourBonusType,
|
||||||
pub value: Option<i16>,
|
pub value: Option<u16>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
|
@ -60,7 +61,7 @@ pub struct ArmourDto {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ArmourDto {
|
impl ArmourDto {
|
||||||
pub fn new(name: &str, armour: Option<f32>, bonus: Option<i16>) -> Option<Self> {
|
pub fn new(name: &str, armour: Option<f32>, bonus: Option<u16>) -> Option<Self> {
|
||||||
let base = match name {
|
let base = match name {
|
||||||
"Leather Vest" => Self::LEATHER_VEST,
|
"Leather Vest" => Self::LEATHER_VEST,
|
||||||
"Police Vest" => Self::POLICE_VEST,
|
"Police Vest" => Self::POLICE_VEST,
|
||||||
|
|
@ -197,6 +198,13 @@ impl ArmourDto {
|
||||||
Immunities(self.immunities.to_vec()),
|
Immunities(self.immunities.to_vec()),
|
||||||
));
|
));
|
||||||
|
|
||||||
|
if let Some(bonus) = self.bonus {
|
||||||
|
commands.insert((
|
||||||
|
bonus.kind,
|
||||||
|
SimpleStatBundle::<ArmourBonusValue>::new(bonus.value.unwrap_or_default()),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
commands.id()
|
commands.id()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,11 @@ pub enum EntityInfo {
|
||||||
owner: usize,
|
owner: usize,
|
||||||
id: usize,
|
id: usize,
|
||||||
},
|
},
|
||||||
|
Armour {
|
||||||
|
name: String,
|
||||||
|
owner: usize,
|
||||||
|
id: usize,
|
||||||
|
},
|
||||||
Global,
|
Global,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,27 +1,55 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use bevy_ecs::prelude::*;
|
use bevy_ecs::prelude::*;
|
||||||
use proxisim_models::bundle::armour::{
|
use proxisim_models::bundle::{
|
||||||
ArmourBodyPart, ArmourBodyPartSlot, ArmourBodyParts, ArmourCoverage, ArmourValue, ArmourVec,
|
armour::{
|
||||||
BodyPartCoverage, Immunities, Immunity, PlayerArmour,
|
ArmourBodyPart, ArmourBodyPartSlot, ArmourBodyParts, ArmourCoverage, ArmourValue,
|
||||||
|
ArmourVec, BodyPartCoverage, Immunities, Immunity, PlayerArmour,
|
||||||
|
},
|
||||||
|
bonus::ArmourBonusType,
|
||||||
|
stat::{
|
||||||
|
AdditiveBonus, ArmourBonusValue, Defence, Dexterity, MaxHealth, SimpleStatBonus,
|
||||||
|
SimpleStatEffective,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
use strum::IntoEnumIterator;
|
use strum::IntoEnumIterator;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
Stages,
|
Stages,
|
||||||
|
effect::Effects,
|
||||||
player::status_effect::{
|
player::status_effect::{
|
||||||
ConcussionGrenade, FlashGrenade, PepperSpray, TearGas, TempDebuffImmunity,
|
ConcussionGrenade, FlashGrenade, PepperSpray, TearGas, TempDebuffImmunity,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
fn generate_body_parts(
|
fn equip_armour(
|
||||||
equip_q: Query<(Entity, &PlayerArmour)>,
|
equip_q: Query<(Entity, &PlayerArmour)>,
|
||||||
armour_q: Query<(Entity, &ArmourCoverage, &ArmourValue, Option<&Immunities>)>,
|
armour_q: Query<(
|
||||||
|
Entity,
|
||||||
|
&ArmourCoverage,
|
||||||
|
&ArmourValue,
|
||||||
|
Option<&Immunities>,
|
||||||
|
Option<&ArmourBonusType>,
|
||||||
|
)>,
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
|
mut effects: Effects,
|
||||||
) {
|
) {
|
||||||
for (player, equipped_armour) in equip_q.iter() {
|
for (player, equipped_armour) in equip_q.iter() {
|
||||||
let mut parts = ArmourVec::<ArmourBodyPart>::default();
|
let mut parts = ArmourVec::<ArmourBodyPart>::default();
|
||||||
|
|
||||||
for (armour, coverage, armour_value, immunities) in armour_q.iter_many(equipped_armour) {
|
let mut set = Some(None);
|
||||||
// commands.entity(player).add_child(armour);
|
|
||||||
|
for (armour, coverage, armour_value, immunities, bonus) in
|
||||||
|
armour_q.iter_many(equipped_armour)
|
||||||
|
{
|
||||||
|
if let Some(kind) = &mut set
|
||||||
|
&& let Some(bonus) = bonus
|
||||||
|
&& kind.is_none_or(|k| k == *bonus)
|
||||||
|
{
|
||||||
|
set = Some(Some(*bonus));
|
||||||
|
} else {
|
||||||
|
set = None
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(immunities) = immunities {
|
if let Some(immunities) = immunities {
|
||||||
let mut player = commands.entity(player);
|
let mut player = commands.entity(player);
|
||||||
|
|
@ -62,6 +90,28 @@ fn generate_body_parts(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(Some(set)) = set {
|
||||||
|
for piece in equipped_armour {
|
||||||
|
effects.spawn(
|
||||||
|
SimpleStatBonus::<ArmourBonusValue>::new(
|
||||||
|
"set bonus",
|
||||||
|
match set {
|
||||||
|
ArmourBonusType::Impregnable => 10,
|
||||||
|
ArmourBonusType::Impenetrable => 10,
|
||||||
|
ArmourBonusType::Insurmountable => 15,
|
||||||
|
ArmourBonusType::Invulnerable => 15,
|
||||||
|
ArmourBonusType::Imperviable => 1,
|
||||||
|
ArmourBonusType::Immmutable => 5,
|
||||||
|
ArmourBonusType::Irrepressible => 5,
|
||||||
|
ArmourBonusType::Impassable => 10,
|
||||||
|
ArmourBonusType::Kinetokinesis => 0,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
piece,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let parts = parts.0.map(|p| commands.spawn(p).id());
|
let parts = parts.0.map(|p| commands.spawn(p).id());
|
||||||
|
|
||||||
commands
|
commands
|
||||||
|
|
@ -71,6 +121,45 @@ fn generate_body_parts(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn configure(stages: &mut Stages) {
|
fn apply_passives(
|
||||||
stages.equip.add_systems(generate_body_parts);
|
bonus_q: Query<(
|
||||||
|
&ArmourBonusType,
|
||||||
|
&SimpleStatEffective<ArmourBonusValue>,
|
||||||
|
&ChildOf,
|
||||||
|
)>,
|
||||||
|
mut effects: Effects,
|
||||||
|
) {
|
||||||
|
let mut max_health_increase = HashMap::<Entity, u16>::new();
|
||||||
|
for (kind, value, relation) in bonus_q {
|
||||||
|
match kind {
|
||||||
|
ArmourBonusType::Imperviable => {
|
||||||
|
*max_health_increase.entry(relation.parent()).or_default() += value.value;
|
||||||
|
}
|
||||||
|
ArmourBonusType::Immmutable => {
|
||||||
|
effects.spawn(
|
||||||
|
AdditiveBonus::<Defence>::new("immutable", value.value as f32 / 100.0),
|
||||||
|
relation.parent(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
ArmourBonusType::Irrepressible => {
|
||||||
|
effects.spawn(
|
||||||
|
AdditiveBonus::<Dexterity>::new("irrepressible", value.value as f32 / 100.0),
|
||||||
|
relation.parent(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (target, increase) in max_health_increase {
|
||||||
|
effects.spawn(
|
||||||
|
SimpleStatBonus::<MaxHealth>::new("marauder", (increase as f32) * 0.01 + 1.0),
|
||||||
|
target,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn configure(stages: &mut Stages) {
|
||||||
|
stages.equip.add_systems(equip_armour);
|
||||||
|
stages.passives.add_systems(apply_passives);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,10 +10,9 @@
|
||||||
"strategy": {
|
"strategy": {
|
||||||
"type": "in_order",
|
"type": "in_order",
|
||||||
"order": [
|
"order": [
|
||||||
"temporary",
|
"secondary"
|
||||||
"primary"
|
|
||||||
],
|
],
|
||||||
"reload": true
|
"reload": false
|
||||||
},
|
},
|
||||||
"education": {
|
"education": {
|
||||||
"bio2350": true,
|
"bio2350": true,
|
||||||
|
|
@ -86,55 +85,47 @@
|
||||||
"side_effects": 10
|
"side_effects": 10
|
||||||
},
|
},
|
||||||
"weapons": {
|
"weapons": {
|
||||||
"primary": {
|
"primary": null,
|
||||||
"id": 488,
|
"secondary": {
|
||||||
"name": "MP 40",
|
"id": 490,
|
||||||
"kind": "primary",
|
"name": "Blunderbuss",
|
||||||
"cat": "smg",
|
"kind": "secondary",
|
||||||
"base_dmg": 37,
|
"cat": "shotgun",
|
||||||
"base_acc": 41,
|
"base_dmg": 46,
|
||||||
"dmg": 5,
|
"base_acc": 24,
|
||||||
"acc": 41,
|
"dmg": 46,
|
||||||
|
"acc": 24,
|
||||||
"ammo": {
|
"ammo": {
|
||||||
"clip_size": 32,
|
"clip_size": 1,
|
||||||
"rate_of_fire": [
|
"rate_of_fire": [
|
||||||
3,
|
1,
|
||||||
5
|
1
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"mods": [
|
"mods": [
|
||||||
"high_capacity_mags",
|
null,
|
||||||
null
|
null
|
||||||
],
|
],
|
||||||
"bonuses": [
|
"bonuses": [
|
||||||
{
|
{
|
||||||
"bonus": "specialist",
|
"bonus": "bleed",
|
||||||
"value": 10
|
"value": 100
|
||||||
},
|
},
|
||||||
null
|
null
|
||||||
],
|
],
|
||||||
"compatible_mods": [
|
"compatible_mods": [
|
||||||
"reflex_sight",
|
|
||||||
"holographic_sight",
|
|
||||||
"acog_sight",
|
|
||||||
"thermal_sight",
|
|
||||||
"laser1mw",
|
"laser1mw",
|
||||||
"laser5mw",
|
"laser5mw",
|
||||||
"laser30mw",
|
"laser30mw",
|
||||||
"laser100mw",
|
"laser100mw",
|
||||||
"small_suppressor",
|
|
||||||
"standard_suppressor",
|
|
||||||
"large_suppressor",
|
|
||||||
"extended_mags",
|
|
||||||
"high_capacity_mags",
|
|
||||||
"extra_clip",
|
"extra_clip",
|
||||||
"extra_clip2",
|
"extra_clip2",
|
||||||
"adjustable_trigger",
|
"adjustable_trigger",
|
||||||
"hair_trigger",
|
"hair_trigger",
|
||||||
"custom_grip",
|
"custom_grip",
|
||||||
"standard_brake",
|
"skeet_choke",
|
||||||
"heavy_duty_brake",
|
"improved_choke",
|
||||||
"tactical_brake",
|
"full_choke",
|
||||||
"small_light",
|
"small_light",
|
||||||
"precision_light",
|
"precision_light",
|
||||||
"tactical_illuminator"
|
"tactical_illuminator"
|
||||||
|
|
@ -142,36 +133,138 @@
|
||||||
"experience": 0,
|
"experience": 0,
|
||||||
"japanese": false
|
"japanese": false
|
||||||
},
|
},
|
||||||
"secondary": null,
|
|
||||||
"melee": null,
|
"melee": null,
|
||||||
"temporary": {
|
"temporary": null
|
||||||
"id": 464,
|
|
||||||
"name": "Melatonin",
|
|
||||||
"kind": "temporary",
|
|
||||||
"cat": "temporary",
|
|
||||||
"base_dmg": 0,
|
|
||||||
"base_acc": 0,
|
|
||||||
"dmg": 0,
|
|
||||||
"acc": 0,
|
|
||||||
"ammo": null,
|
|
||||||
"mods": [
|
|
||||||
null,
|
|
||||||
null
|
|
||||||
],
|
|
||||||
"bonuses": [
|
|
||||||
null,
|
|
||||||
null
|
|
||||||
],
|
|
||||||
"compatible_mods": [],
|
|
||||||
"experience": 0,
|
|
||||||
"japanese": false
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"armour": {
|
"armour": {
|
||||||
"helmet": null,
|
"helmet": {
|
||||||
"body": null,
|
"slot": "head",
|
||||||
"pants": null,
|
"id": 1355,
|
||||||
"gloves": null,
|
"name": "Vanguard Respirator",
|
||||||
"boots": null
|
"base_armour": 48,
|
||||||
|
"armour": 48,
|
||||||
|
"coverage": {
|
||||||
|
"body": 2.819999933242798,
|
||||||
|
"heart": 0,
|
||||||
|
"stomach": 0,
|
||||||
|
"chest": 0,
|
||||||
|
"arm": 0,
|
||||||
|
"groin": 0,
|
||||||
|
"leg": 0,
|
||||||
|
"throat": 0,
|
||||||
|
"hand": 0,
|
||||||
|
"foot": 0,
|
||||||
|
"head": 39.599998474121094
|
||||||
|
},
|
||||||
|
"immunities": [
|
||||||
|
"pepper_spray",
|
||||||
|
"nerve_gas",
|
||||||
|
"tear_gas"
|
||||||
|
],
|
||||||
|
"bonus": {
|
||||||
|
"kind": "irrepressible",
|
||||||
|
"value": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"body": {
|
||||||
|
"slot": "body",
|
||||||
|
"id": 1356,
|
||||||
|
"name": "Vanguard Body",
|
||||||
|
"base_armour": 48,
|
||||||
|
"armour": 48,
|
||||||
|
"coverage": {
|
||||||
|
"body": 44.279998779296875,
|
||||||
|
"heart": 100,
|
||||||
|
"stomach": 100,
|
||||||
|
"chest": 100,
|
||||||
|
"arm": 100,
|
||||||
|
"groin": 35.38999938964844,
|
||||||
|
"leg": 0.2800000011920929,
|
||||||
|
"throat": 83.52999877929688,
|
||||||
|
"hand": 0.12999999523162842,
|
||||||
|
"foot": 0,
|
||||||
|
"head": 0
|
||||||
|
},
|
||||||
|
"immunities": [],
|
||||||
|
"bonus": {
|
||||||
|
"kind": "irrepressible",
|
||||||
|
"value": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pants": {
|
||||||
|
"slot": "legs",
|
||||||
|
"id": 1357,
|
||||||
|
"name": "Vanguard Pants",
|
||||||
|
"base_armour": 48,
|
||||||
|
"armour": 48,
|
||||||
|
"coverage": {
|
||||||
|
"body": 24.959999084472656,
|
||||||
|
"heart": 0,
|
||||||
|
"stomach": 0,
|
||||||
|
"chest": 0,
|
||||||
|
"arm": 0,
|
||||||
|
"groin": 99.06999969482422,
|
||||||
|
"leg": 100,
|
||||||
|
"throat": 0,
|
||||||
|
"hand": 0,
|
||||||
|
"foot": 25.200000762939453,
|
||||||
|
"head": 0
|
||||||
|
},
|
||||||
|
"immunities": [],
|
||||||
|
"bonus": {
|
||||||
|
"kind": "irrepressible",
|
||||||
|
"value": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"gloves": {
|
||||||
|
"slot": "hands",
|
||||||
|
"id": 1359,
|
||||||
|
"name": "Vanguard Gloves",
|
||||||
|
"base_armour": 48,
|
||||||
|
"armour": 48,
|
||||||
|
"coverage": {
|
||||||
|
"body": 14.399999618530273,
|
||||||
|
"heart": 0,
|
||||||
|
"stomach": 0,
|
||||||
|
"chest": 0,
|
||||||
|
"arm": 0.7300000190734863,
|
||||||
|
"groin": 0,
|
||||||
|
"leg": 0,
|
||||||
|
"throat": 0,
|
||||||
|
"hand": 100,
|
||||||
|
"foot": 0,
|
||||||
|
"head": 0
|
||||||
|
},
|
||||||
|
"immunities": [],
|
||||||
|
"bonus": {
|
||||||
|
"kind": "irrepressible",
|
||||||
|
"value": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"boots": {
|
||||||
|
"slot": "feet",
|
||||||
|
"id": 1358,
|
||||||
|
"name": "Vanguard Boots",
|
||||||
|
"base_armour": 48,
|
||||||
|
"armour": 48,
|
||||||
|
"coverage": {
|
||||||
|
"body": 15.130000114440918,
|
||||||
|
"heart": 0,
|
||||||
|
"stomach": 0,
|
||||||
|
"chest": 0,
|
||||||
|
"arm": 0,
|
||||||
|
"groin": 0,
|
||||||
|
"leg": 5.829999923706055,
|
||||||
|
"throat": 0,
|
||||||
|
"hand": 0,
|
||||||
|
"foot": 100,
|
||||||
|
"head": 0
|
||||||
|
},
|
||||||
|
"immunities": [],
|
||||||
|
"bonus": {
|
||||||
|
"kind": "irrepressible",
|
||||||
|
"value": null
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ use bevy_ecs::prelude::*;
|
||||||
use proxisim_models::{
|
use proxisim_models::{
|
||||||
bundle::{
|
bundle::{
|
||||||
Id, Name,
|
Id, Name,
|
||||||
|
armour::Armour,
|
||||||
player::{Attacker, Player},
|
player::{Attacker, Player},
|
||||||
weapon::Weapon,
|
weapon::Weapon,
|
||||||
},
|
},
|
||||||
|
|
@ -18,6 +19,7 @@ pub struct EntityRegistry(pub HashMap<Entity, EntityInfo>);
|
||||||
fn read_entities(
|
fn read_entities(
|
||||||
player_q: Query<(Entity, &Name, &Id, Has<Attacker>), With<Player>>,
|
player_q: Query<(Entity, &Name, &Id, Has<Attacker>), With<Player>>,
|
||||||
weapon_q: Query<(Entity, &ChildOf, &Name, &Id), With<Weapon>>,
|
weapon_q: Query<(Entity, &ChildOf, &Name, &Id), With<Weapon>>,
|
||||||
|
armour_q: Query<(Entity, &ChildOf, &Name, &Id), With<Armour>>,
|
||||||
mut registry: ResMut<EntityRegistry>,
|
mut registry: ResMut<EntityRegistry>,
|
||||||
) {
|
) {
|
||||||
for (player, name, id, is_attacker) in player_q.iter() {
|
for (player, name, id, is_attacker) in player_q.iter() {
|
||||||
|
|
@ -42,6 +44,18 @@ fn read_entities(
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (weapon, player, name, id) in armour_q.iter() {
|
||||||
|
let (_, _, player_id, _) = player_q.get(player.parent()).unwrap();
|
||||||
|
registry.0.insert(
|
||||||
|
weapon,
|
||||||
|
EntityInfo::Armour {
|
||||||
|
name: name.0.clone(),
|
||||||
|
owner: player_id.0,
|
||||||
|
id: id.0,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn configure(stages: &mut Stages) {
|
pub(crate) fn configure(stages: &mut Stages) {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
#![warn(clippy::perf, clippy::style, clippy::all)]
|
#![warn(clippy::perf, clippy::style, clippy::all)]
|
||||||
#![allow(clippy::type_complexity)]
|
#![allow(clippy::type_complexity)]
|
||||||
use bevy_ecs::{message::MessageRegistry, prelude::*, schedule::ScheduleLabel};
|
use bevy_ecs::{message::MessageRegistry, prelude::*, schedule::ScheduleLabel};
|
||||||
use effect::{register_effect, EffectBuilder};
|
use effect::{EffectBuilder, register_effect};
|
||||||
use metrics::Metrics;
|
use metrics::Metrics;
|
||||||
use proxisim_models::{
|
use proxisim_models::{
|
||||||
bundle::player::{Attacker, Current, Defender},
|
bundle::player::{Attacker, Current, Defender},
|
||||||
|
|
@ -53,6 +53,7 @@ enum FightStatus {
|
||||||
enum Stage {
|
enum Stage {
|
||||||
Equip,
|
Equip,
|
||||||
Snapshot,
|
Snapshot,
|
||||||
|
Passives,
|
||||||
PreFight,
|
PreFight,
|
||||||
PreTurn,
|
PreTurn,
|
||||||
Turn,
|
Turn,
|
||||||
|
|
@ -64,6 +65,7 @@ enum Stage {
|
||||||
struct Stages {
|
struct Stages {
|
||||||
equip: Schedule,
|
equip: Schedule,
|
||||||
snapshot: Schedule,
|
snapshot: Schedule,
|
||||||
|
passives: Schedule,
|
||||||
pre_fight: Schedule,
|
pre_fight: Schedule,
|
||||||
pre_turn: Schedule,
|
pre_turn: Schedule,
|
||||||
turn: Schedule,
|
turn: Schedule,
|
||||||
|
|
@ -102,6 +104,7 @@ impl Simulation {
|
||||||
let mut stages = Stages {
|
let mut stages = Stages {
|
||||||
equip: Schedule::new(Stage::Equip),
|
equip: Schedule::new(Stage::Equip),
|
||||||
snapshot: Schedule::new(Stage::Snapshot),
|
snapshot: Schedule::new(Stage::Snapshot),
|
||||||
|
passives: Schedule::new(Stage::Passives),
|
||||||
pre_fight: Schedule::new(Stage::PreFight),
|
pre_fight: Schedule::new(Stage::PreFight),
|
||||||
pre_turn: Schedule::new(Stage::PreTurn),
|
pre_turn: Schedule::new(Stage::PreTurn),
|
||||||
turn: Schedule::new(Stage::Turn),
|
turn: Schedule::new(Stage::Turn),
|
||||||
|
|
@ -134,8 +137,10 @@ impl Simulation {
|
||||||
defender.spawn(&mut stages.world).insert(Defender);
|
defender.spawn(&mut stages.world).insert(Defender);
|
||||||
|
|
||||||
stages.equip.run(&mut stages.world);
|
stages.equip.run(&mut stages.world);
|
||||||
stages.pre_fight.run(&mut stages.world);
|
|
||||||
effect::run_effects(&mut stages.world);
|
effect::run_effects(&mut stages.world);
|
||||||
|
stages.passives.run(&mut stages.world);
|
||||||
|
effect::run_effects(&mut stages.world);
|
||||||
|
stages.pre_fight.run(&mut stages.world);
|
||||||
stages.snapshot.run(&mut stages.world);
|
stages.snapshot.run(&mut stages.world);
|
||||||
|
|
||||||
Self(stages)
|
Self(stages)
|
||||||
|
|
|
||||||
191
src/log.rs
191
src/log.rs
|
|
@ -3,9 +3,10 @@ use std::sync::Mutex;
|
||||||
use bevy_ecs::{prelude::*, query::QuerySingleError, system::SystemParam};
|
use bevy_ecs::{prelude::*, query::QuerySingleError, system::SystemParam};
|
||||||
use proxisim_models::bundle::{
|
use proxisim_models::bundle::{
|
||||||
stat::{
|
stat::{
|
||||||
AdditiveBonus, BaselineStat, ClipSize, Clips, CritRate, DamageBonus, Defence, Dexterity,
|
AdditiveBonus, ArmourBonusValue, BaselineStat, ClipSize, Clips, CritRate, DamageBonus,
|
||||||
EffectiveStat, MultiplicativeBonus, SimpleStatBaseline, SimpleStatBonus,
|
Defence, Dexterity, EffectiveStat, MultiplicativeBonus, SimpleStatBaseline,
|
||||||
SimpleStatEffective, SimpleStatMarker, Speed, StatMarker, Strength, WeaponAccuracy,
|
SimpleStatBonus, SimpleStatEffective, SimpleStatMarker, Speed, StatMarker, Strength,
|
||||||
|
WeaponAccuracy,
|
||||||
},
|
},
|
||||||
weapon::WeaponVerb,
|
weapon::WeaponVerb,
|
||||||
};
|
};
|
||||||
|
|
@ -124,7 +125,12 @@ where
|
||||||
V: Into<LogValue>,
|
V: Into<LogValue>,
|
||||||
{
|
{
|
||||||
fn from(value: Vec<(&'static str, V)>) -> Self {
|
fn from(value: Vec<(&'static str, V)>) -> Self {
|
||||||
LogValue::Map(value.into_iter().map(|(k, v)| (k, v.into())).collect())
|
LogValue::Array(
|
||||||
|
value
|
||||||
|
.into_iter()
|
||||||
|
.map(|(k, v)| LogValue::Array(vec![LogValue::String(k.to_string()), v.into()]))
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -256,182 +262,6 @@ impl std::fmt::Display for Log {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* impl std::fmt::Display for Log {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
for entry in &self.entries {
|
|
||||||
match entry {
|
|
||||||
LogEntry::Hit {
|
|
||||||
actor,
|
|
||||||
recipient,
|
|
||||||
weapon,
|
|
||||||
dmg,
|
|
||||||
rounds,
|
|
||||||
crit,
|
|
||||||
part,
|
|
||||||
} => {
|
|
||||||
let actor_info = self.player_registry.get(actor).unwrap();
|
|
||||||
let recipient_info = self.player_registry.get(recipient).unwrap();
|
|
||||||
let weapon_info = self.weapon_registry.get(weapon).unwrap();
|
|
||||||
|
|
||||||
write!(f, "{} ", actor_info.name)?;
|
|
||||||
|
|
||||||
match weapon_info.verb {
|
|
||||||
WeaponVerb::Fired => {
|
|
||||||
write!(
|
|
||||||
f,
|
|
||||||
"fired {} rounds from of their {} ",
|
|
||||||
rounds.unwrap(),
|
|
||||||
weapon_info.name
|
|
||||||
)?;
|
|
||||||
if *crit {
|
|
||||||
write!(f, "critically ")?;
|
|
||||||
}
|
|
||||||
writeln!(
|
|
||||||
f,
|
|
||||||
"hitting {} in the {} for {}",
|
|
||||||
recipient_info.name, part, dmg
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
WeaponVerb::Hit => {
|
|
||||||
if *crit {
|
|
||||||
write!(f, "critically ")?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Pronouns and weapon verbs
|
|
||||||
writeln!(
|
|
||||||
f,
|
|
||||||
"hit {} with their {} in the {} for {}",
|
|
||||||
recipient_info.name, weapon_info.name, part, dmg
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
WeaponVerb::Exploded => {
|
|
||||||
writeln!(
|
|
||||||
f,
|
|
||||||
"{} threw a {} at {}, it exploded for {}",
|
|
||||||
actor_info.name, weapon_info.name, recipient_info.name, dmg
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
_ => todo!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
LogEntry::Miss {
|
|
||||||
actor,
|
|
||||||
recipient,
|
|
||||||
weapon,
|
|
||||||
rounds,
|
|
||||||
} => {
|
|
||||||
let actor_info = self.player_registry.get(actor).unwrap();
|
|
||||||
let recipient_info = self.player_registry.get(recipient).unwrap();
|
|
||||||
let weapon_info = self.weapon_registry.get(weapon).unwrap();
|
|
||||||
|
|
||||||
match weapon_info.verb {
|
|
||||||
WeaponVerb::Hit => {
|
|
||||||
writeln!(
|
|
||||||
f,
|
|
||||||
"{} missed {} with their {}",
|
|
||||||
actor_info.name, recipient_info.name, weapon_info.name
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
WeaponVerb::Fired => {
|
|
||||||
writeln!(
|
|
||||||
f,
|
|
||||||
"{} fired {} rounds of their {} missing {}",
|
|
||||||
actor_info.name,
|
|
||||||
rounds.unwrap(),
|
|
||||||
weapon_info.name,
|
|
||||||
recipient_info.name
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
_ => todo!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
LogEntry::Defeat { actor, recipient } => {
|
|
||||||
let actor_info = self.player_registry.get(actor).unwrap();
|
|
||||||
let recipient_info = self.player_registry.get(recipient).unwrap();
|
|
||||||
|
|
||||||
writeln!(f, "{} defeated {}", actor_info.name, recipient_info.name)?;
|
|
||||||
}
|
|
||||||
LogEntry::Stalemate { actor, recipient } => {
|
|
||||||
let actor_info = self.player_registry.get(actor).unwrap();
|
|
||||||
let recipient_info = self.player_registry.get(recipient).unwrap();
|
|
||||||
|
|
||||||
writeln!(
|
|
||||||
f,
|
|
||||||
"{} stalemated against {}",
|
|
||||||
actor_info.name, recipient_info.name
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
LogEntry::Loss { actor, recipient } => {
|
|
||||||
let actor_info = self.player_registry.get(actor).unwrap();
|
|
||||||
let recipient_info = self.player_registry.get(recipient).unwrap();
|
|
||||||
|
|
||||||
writeln!(
|
|
||||||
f,
|
|
||||||
"{} lost against {}",
|
|
||||||
recipient_info.name, actor_info.name
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
LogEntry::Reload { actor, weapon } => {
|
|
||||||
let actor_info = self.player_registry.get(actor).unwrap();
|
|
||||||
let weapon_info = self.weapon_registry.get(weapon).unwrap();
|
|
||||||
|
|
||||||
writeln!(f, "{} reloaded their {}", actor_info.name, weapon_info.name)?;
|
|
||||||
}
|
|
||||||
LogEntry::UsedDebuffTemp {
|
|
||||||
actor,
|
|
||||||
recipient,
|
|
||||||
temp,
|
|
||||||
weapon,
|
|
||||||
immune,
|
|
||||||
} => {
|
|
||||||
let actor_info = self.player_registry.get(actor).unwrap();
|
|
||||||
let recipient_info = self.player_registry.get(recipient).unwrap();
|
|
||||||
let weapon_info = self.weapon_registry.get(weapon).unwrap();
|
|
||||||
|
|
||||||
match temp {
|
|
||||||
DebuffingTemp::SmokeGrenade => {
|
|
||||||
write!(
|
|
||||||
f,
|
|
||||||
"{} threw a Smoke Grenade, smoke clouds around {}",
|
|
||||||
actor_info.name, recipient_info.name
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
DebuffingTemp::TearGas => {
|
|
||||||
write!(
|
|
||||||
f,
|
|
||||||
"{} threw a Tear Gas Grenade near {}",
|
|
||||||
actor_info.name, recipient_info.name
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
DebuffingTemp::PepperSpray => {
|
|
||||||
write!(
|
|
||||||
f,
|
|
||||||
"{} sprayed Pepper Spray in {}'s face",
|
|
||||||
actor_info.name, recipient_info.name
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
write!(
|
|
||||||
f,
|
|
||||||
"{} threw a {} at {}",
|
|
||||||
actor_info.name, recipient_info.name, weapon_info.name
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if *immune {
|
|
||||||
writeln!(f, " but it was ineffective")?;
|
|
||||||
} else {
|
|
||||||
writeln!(f)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
} */
|
|
||||||
|
|
||||||
#[derive(SystemParam)]
|
#[derive(SystemParam)]
|
||||||
pub struct Logger<'w> {
|
pub struct Logger<'w> {
|
||||||
event_writer: MessageWriter<'w, LogEvent>,
|
event_writer: MessageWriter<'w, LogEvent>,
|
||||||
|
|
@ -603,6 +433,7 @@ pub(crate) fn configure(stages: &mut Stages) {
|
||||||
log_simple_stat_changes::<DamageBonus>,
|
log_simple_stat_changes::<DamageBonus>,
|
||||||
log_simple_stat_changes::<Clips>,
|
log_simple_stat_changes::<Clips>,
|
||||||
log_simple_stat_changes::<ClipSize>,
|
log_simple_stat_changes::<ClipSize>,
|
||||||
|
log_simple_stat_changes::<ArmourBonusValue>,
|
||||||
)
|
)
|
||||||
.run_if(logging_enabled),
|
.run_if(logging_enabled),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,12 @@
|
||||||
use bevy_ecs::prelude::*;
|
use bevy_ecs::prelude::*;
|
||||||
use proxisim_models::bundle::{
|
use proxisim_models::bundle::{
|
||||||
passive::{DrugCooldown, Education, FactionUpgrades, Merits},
|
passive::{DrugCooldown, Education, FactionUpgrades, Merits},
|
||||||
stat::{AdditiveBonus, CritRate, Defence, Dexterity, SimpleStatBonus, Speed, Strength},
|
stat::{
|
||||||
|
AdditiveBonus, CritRate, Defence, Dexterity, MaxHealth, SimpleStatBonus, Speed, Strength,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{effect::Effects, Stages};
|
use crate::{Stages, effect::Effects};
|
||||||
|
|
||||||
fn spawn_permanent_effects(
|
fn spawn_permanent_effects(
|
||||||
merit_q: Query<(
|
merit_q: Query<(
|
||||||
|
|
@ -50,6 +52,12 @@ fn spawn_permanent_effects(
|
||||||
player,
|
player,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if merits.life > 0 {
|
||||||
|
effects.spawn(
|
||||||
|
SimpleStatBonus::<MaxHealth>::new("merits", (merits.life as f32) * 0.05 + 1.0),
|
||||||
|
player,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if faction.spd > 0 {
|
if faction.spd > 0 {
|
||||||
effects.spawn(
|
effects.spawn(
|
||||||
|
|
@ -75,6 +83,12 @@ fn spawn_permanent_effects(
|
||||||
player,
|
player,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if faction.life > 0 {
|
||||||
|
effects.spawn(
|
||||||
|
SimpleStatBonus::<MaxHealth>::new("faction", (faction.life as f32) * 0.01 + 1.0),
|
||||||
|
player,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
fn spawn_drug_bonuses(
|
fn spawn_drug_bonuses(
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,13 @@
|
||||||
use bevy_ecs::prelude::*;
|
use bevy_ecs::prelude::*;
|
||||||
use proxisim_models::bundle::{
|
use proxisim_models::bundle::{
|
||||||
armour::{ArmourBodyPart, ArmourBodyParts},
|
armour::{ArmourBodyPart, ArmourBodyParts},
|
||||||
passive::{FactionUpgrades, Merits},
|
|
||||||
player::{
|
player::{
|
||||||
Attacker, BodyPart, ChooseWeapon, CombatTurns, Current, CurrentTarget, Defeated, Defender,
|
Attacker, BodyPart, ChooseWeapon, CombatTurns, Current, CurrentTarget, Defeated, Defender,
|
||||||
FightEndType, Level, MaxHealth, PartDamageBonus, Player, PlayerStrategy, Weapons,
|
FightEndType, Health, PartDamageBonus, Player, PlayerStrategy, Weapons,
|
||||||
},
|
},
|
||||||
stat::{
|
stat::{
|
||||||
AmmoControl, Clips, CritRate, DamageBonus, Defence, Dexterity, EffectiveStat, Health,
|
AmmoControl, Clips, CritRate, DamageBonus, Defence, Dexterity, EffectiveStat, MaxHealth,
|
||||||
SimpleStatBundle, SimpleStatEffective, Speed, Strength, WeaponAccuracy,
|
SimpleStatEffective, Speed, Strength, WeaponAccuracy,
|
||||||
},
|
},
|
||||||
weapon::{
|
weapon::{
|
||||||
Ammo, DamageStat, NeedsReload, NonTargeted, RateOfFire, Usable, Uses, Weapon, WeaponSlot,
|
Ammo, DamageStat, NeedsReload, NonTargeted, RateOfFire, Usable, Uses, Weapon, WeaponSlot,
|
||||||
|
|
@ -22,6 +21,7 @@ use crate::{
|
||||||
log,
|
log,
|
||||||
log::Logger,
|
log::Logger,
|
||||||
metrics::Metrics,
|
metrics::Metrics,
|
||||||
|
player::status_effect::{Bleed, DamageOverTime, DamageOverTimeType, DeferredDamage},
|
||||||
weapon::{DamageProcEffect, TurnTriggeredEffect, bonus::MultiTurnBonus},
|
weapon::{DamageProcEffect, TurnTriggeredEffect, bonus::MultiTurnBonus},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -52,28 +52,6 @@ fn select_weapon<'a>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn derive_max_health(
|
|
||||||
level_query: Query<(Entity, &Level, &Merits, &FactionUpgrades)>,
|
|
||||||
mut cmd: Commands,
|
|
||||||
) {
|
|
||||||
for (entity, level, merits, faction) in level_query.iter() {
|
|
||||||
let base_life = match level.0 {
|
|
||||||
1..=8 => 100 + (level.0 - 1) * 25,
|
|
||||||
9..=95 => 275 + (level.0 - 8) * 50,
|
|
||||||
96.. => 4625 + (level.0 - 95) * 75,
|
|
||||||
0 => unreachable!(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let max_health =
|
|
||||||
((base_life as f32) * (1.0 + ((merits.life * 5 + faction.life) as f32) / 100.0)) as u16;
|
|
||||||
|
|
||||||
cmd.entity(entity).insert((
|
|
||||||
MaxHealth(max_health),
|
|
||||||
SimpleStatBundle::<Health>::new(max_health),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn designate_first(
|
fn designate_first(
|
||||||
attacker_q: Query<Entity, With<Attacker>>,
|
attacker_q: Query<Entity, With<Attacker>>,
|
||||||
defender_q: Query<Entity, With<Defender>>,
|
defender_q: Query<Entity, With<Defender>>,
|
||||||
|
|
@ -183,7 +161,7 @@ pub fn use_damaging_weapon(
|
||||||
),
|
),
|
||||||
(With<Weapon>, With<Current>, Without<NeedsReload>),
|
(With<Weapon>, With<Current>, Without<NeedsReload>),
|
||||||
>,
|
>,
|
||||||
player_q: Query<
|
mut player_q: Query<
|
||||||
(
|
(
|
||||||
Entity,
|
Entity,
|
||||||
&EffectiveStat<Speed>,
|
&EffectiveStat<Speed>,
|
||||||
|
|
@ -191,6 +169,8 @@ pub fn use_damaging_weapon(
|
||||||
&SimpleStatEffective<CritRate>,
|
&SimpleStatEffective<CritRate>,
|
||||||
&SimpleStatEffective<WeaponAccuracy>,
|
&SimpleStatEffective<WeaponAccuracy>,
|
||||||
&SimpleStatEffective<DamageBonus>,
|
&SimpleStatEffective<DamageBonus>,
|
||||||
|
&Children,
|
||||||
|
&mut Health,
|
||||||
Has<Attacker>,
|
Has<Attacker>,
|
||||||
),
|
),
|
||||||
(With<Player>, With<Current>),
|
(With<Player>, With<Current>),
|
||||||
|
|
@ -201,11 +181,11 @@ pub fn use_damaging_weapon(
|
||||||
&EffectiveStat<Dexterity>,
|
&EffectiveStat<Dexterity>,
|
||||||
&EffectiveStat<Defence>,
|
&EffectiveStat<Defence>,
|
||||||
&ArmourBodyParts,
|
&ArmourBodyParts,
|
||||||
&mut SimpleStatEffective<Health>,
|
&mut Health,
|
||||||
),
|
),
|
||||||
With<CurrentTarget>,
|
(With<CurrentTarget>, Without<Current>),
|
||||||
>,
|
>,
|
||||||
armour_q: Query<&ArmourBodyPart>,
|
(armour_q, damage_q): (Query<&ArmourBodyPart>, Query<(Entity, &DeferredDamage)>),
|
||||||
(damage_proc_q, part_bonus_q): (Query<&DamageProcEffect>, Query<&PartDamageBonus>),
|
(damage_proc_q, part_bonus_q): (Query<&DamageProcEffect>, Query<&PartDamageBonus>),
|
||||||
(mut ammo_q, mut temp_q): (
|
(mut ammo_q, mut temp_q): (
|
||||||
Query<(
|
Query<(
|
||||||
|
|
@ -228,10 +208,48 @@ pub fn use_damaging_weapon(
|
||||||
else {
|
else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let (player, player_spd, player_str, player_crit, acc_bonus, p_dmg_bonus, attacker) =
|
let (
|
||||||
player_q.single().unwrap();
|
player,
|
||||||
|
player_spd,
|
||||||
|
player_str,
|
||||||
|
player_crit,
|
||||||
|
acc_bonus,
|
||||||
|
p_dmg_bonus,
|
||||||
|
p_children,
|
||||||
|
mut p_health,
|
||||||
|
attacker,
|
||||||
|
) = player_q.single_mut().unwrap();
|
||||||
let (target, target_dex, target_def, armour_parts, mut health) = target_q.single_mut().unwrap();
|
let (target, target_dex, target_def, armour_parts, mut health) = target_q.single_mut().unwrap();
|
||||||
|
|
||||||
|
for (instance, damage) in damage_q.iter_many(p_children) {
|
||||||
|
let health_before = p_health.0;
|
||||||
|
p_health.0 = p_health.0.saturating_sub(damage.amount);
|
||||||
|
|
||||||
|
log!(logger, "deferred_damage", {
|
||||||
|
health_before: health_before,
|
||||||
|
health_after: p_health.0,
|
||||||
|
amount: damage.amount,
|
||||||
|
label: damage.label,
|
||||||
|
target: player,
|
||||||
|
});
|
||||||
|
|
||||||
|
commands.entity(instance).despawn();
|
||||||
|
|
||||||
|
if p_health.0 == 0 {
|
||||||
|
log!(logger, "fight_end", {
|
||||||
|
actor: target,
|
||||||
|
recipient: player,
|
||||||
|
fight_end_type: %if attacker {
|
||||||
|
FightEndType::Loss
|
||||||
|
} else {
|
||||||
|
FightEndType::Victory
|
||||||
|
},
|
||||||
|
});
|
||||||
|
metrics.increment_counter(Some(target), "victory", 1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if let Ok(mut uses) = temp_q.get_mut(weapon) {
|
if let Ok(mut uses) = temp_q.get_mut(weapon) {
|
||||||
uses.0 -= 1;
|
uses.0 -= 1;
|
||||||
if uses.0 == 0 {
|
if uses.0 == 0 {
|
||||||
|
|
@ -253,7 +271,7 @@ pub fn use_damaging_weapon(
|
||||||
.ok()
|
.ok()
|
||||||
.map(|(ammo, clips, rof, ammo_ctrl)| {
|
.map(|(ammo, clips, rof, ammo_ctrl)| {
|
||||||
let ammo_ctrl = 1.0 - (ammo_ctrl).value;
|
let ammo_ctrl = 1.0 - (ammo_ctrl).value;
|
||||||
let rof_eff = ((rof.0[0] as f32) * ammo_ctrl)..((rof.0[1] as f32) * ammo_ctrl);
|
let rof_eff = ((rof.0[0] as f32) * ammo_ctrl)..=((rof.0[1] as f32) * ammo_ctrl);
|
||||||
(ammo, clips, rof_eff)
|
(ammo, clips, rof_eff)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -414,13 +432,25 @@ pub fn use_damaging_weapon(
|
||||||
bonus.spawn(target, &mut effects, &mut rng.0);
|
bonus.spawn(target, &mut effects, &mut rng.0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
DamageProcEffect::DamageOverTimer { value, kind } => {
|
||||||
|
let chance = (value / 100.0) as f64;
|
||||||
|
if chance > 1.0 || rng.random_bool(chance) {
|
||||||
|
match kind {
|
||||||
|
DamageOverTimeType::Bleed => {
|
||||||
|
commands
|
||||||
|
.entity(target)
|
||||||
|
.insert(DamageOverTime::<Bleed>::new(dmg));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let health_before = health.value;
|
let health_before = health.0;
|
||||||
|
|
||||||
health.value = health.value.saturating_sub(dmg as u16);
|
health.0 = health.0.saturating_sub(dmg as u16);
|
||||||
|
|
||||||
log!(logger, "hit_target", {
|
log!(logger, "hit_target", {
|
||||||
actor: player,
|
actor: player,
|
||||||
|
|
@ -431,7 +461,7 @@ pub fn use_damaging_weapon(
|
||||||
part_mult: mult,
|
part_mult: mult,
|
||||||
rounds,
|
rounds,
|
||||||
health_before: health_before,
|
health_before: health_before,
|
||||||
health_after: health.value,
|
health_after: health.0,
|
||||||
dmg,
|
dmg,
|
||||||
dmg_spread,
|
dmg_spread,
|
||||||
dmg_intrinsic,
|
dmg_intrinsic,
|
||||||
|
|
@ -443,7 +473,7 @@ pub fn use_damaging_weapon(
|
||||||
crit_rate: crit.value,
|
crit_rate: crit.value,
|
||||||
});
|
});
|
||||||
|
|
||||||
if health.value == 0 && !defeated {
|
if health.0 == 0 && !defeated {
|
||||||
defeated = true;
|
defeated = true;
|
||||||
|
|
||||||
commands.entity(target).insert(Defeated);
|
commands.entity(target).insert(Defeated);
|
||||||
|
|
@ -539,12 +569,23 @@ pub fn restore_initial_state(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn record_post_fight_stats(
|
fn record_post_fight_stats(player_q: Query<(Entity, &Health)>, metrics: Res<Metrics>) {
|
||||||
player_q: Query<(Entity, &SimpleStatEffective<Health>)>,
|
|
||||||
metrics: Res<Metrics>,
|
|
||||||
) {
|
|
||||||
for (player, health) in player_q.iter() {
|
for (player, health) in player_q.iter() {
|
||||||
metrics.record_histogram(Some(player), "rem_health", health.value as u32);
|
metrics.record_histogram(Some(player), "rem_health", health.0 as u32);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn restore_health(
|
||||||
|
health_q: Query<(Entity, &SimpleStatEffective<MaxHealth>)>,
|
||||||
|
mut commands: Commands,
|
||||||
|
mut logger: Logger,
|
||||||
|
) {
|
||||||
|
for (player, max_health) in health_q {
|
||||||
|
log!(logger, "initial_health", {
|
||||||
|
target: player,
|
||||||
|
max_health: max_health.value,
|
||||||
|
});
|
||||||
|
commands.entity(player).insert(Health(max_health.value));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -554,7 +595,7 @@ pub(crate) fn configure(stages: &mut Stages) {
|
||||||
|
|
||||||
stages.add_event::<ChooseWeapon>();
|
stages.add_event::<ChooseWeapon>();
|
||||||
stages.equip.add_systems(designate_first);
|
stages.equip.add_systems(designate_first);
|
||||||
stages.pre_fight.add_systems(derive_max_health);
|
stages.pre_fight.add_systems(restore_health);
|
||||||
stages.pre_turn.add_systems(pick_action);
|
stages.pre_turn.add_systems(pick_action);
|
||||||
stages.turn.add_systems(use_damaging_weapon);
|
stages.turn.add_systems(use_damaging_weapon);
|
||||||
stages
|
stages
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,8 @@ use std::marker::PhantomData;
|
||||||
|
|
||||||
use bevy_ecs::prelude::*;
|
use bevy_ecs::prelude::*;
|
||||||
use proxisim_models::bundle::stat::{
|
use proxisim_models::bundle::stat::{
|
||||||
AdditiveBonus, AdditiveBonuses, AmmoControl, BaselineStat, ClipSize, Clips, CritRate,
|
AdditiveBonus, AdditiveBonuses, AmmoControl, ArmourBonusValue, BaselineStat, ClipSize, Clips,
|
||||||
DamageBonus, Defence, Dexterity, EffectiveStat, Health, MultiplicativeBonus,
|
CritRate, DamageBonus, Defence, Dexterity, EffectiveStat, MaxHealth, MultiplicativeBonus,
|
||||||
MultiplicativeBonuses, SimpleStatBonus, SimpleStatEffective, SimpleStatMarker,
|
MultiplicativeBonuses, SimpleStatBonus, SimpleStatEffective, SimpleStatMarker,
|
||||||
SimpleStatSnapshot, Speed, StatMarker, StatSnapshot, Strength, WeaponAccuracy,
|
SimpleStatSnapshot, Speed, StatMarker, StatSnapshot, Strength, WeaponAccuracy,
|
||||||
};
|
};
|
||||||
|
|
@ -196,5 +196,6 @@ pub(crate) fn configure(stages: &mut Stages) {
|
||||||
register_simple_stat_effects::<WeaponAccuracy>(stages);
|
register_simple_stat_effects::<WeaponAccuracy>(stages);
|
||||||
register_simple_stat_effects::<ClipSize>(stages);
|
register_simple_stat_effects::<ClipSize>(stages);
|
||||||
register_simple_stat_effects::<Clips>(stages);
|
register_simple_stat_effects::<Clips>(stages);
|
||||||
register_simple_stat_effects::<Health>(stages);
|
register_simple_stat_effects::<MaxHealth>(stages);
|
||||||
|
register_simple_stat_effects::<ArmourBonusValue>(stages);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
use std::{collections::VecDeque, marker::PhantomData};
|
use std::{collections::VecDeque, marker::PhantomData};
|
||||||
|
|
||||||
use bevy_ecs::prelude::*;
|
use bevy_ecs::prelude::*;
|
||||||
use proxisim_models::bundle::stat::{
|
use proxisim_models::bundle::{
|
||||||
AdditiveBonus, Defence, Dexterity, MultiplicativeBonus, Speed, StatMarker, Strength,
|
player::Current,
|
||||||
|
stat::{AdditiveBonus, Defence, Dexterity, MultiplicativeBonus, Speed, StatMarker, Strength},
|
||||||
};
|
};
|
||||||
use rand::Rng as _;
|
use rand::Rng as _;
|
||||||
|
|
||||||
|
|
@ -340,6 +341,46 @@ impl AdditiveStatusEffectMarker<2> for Frozen {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub trait DamageOverTimeMarker: Send + Sync + 'static {
|
||||||
|
const INCREMENTS: &[f32];
|
||||||
|
const LABEL: &str;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Component)]
|
||||||
|
pub struct DamageOverTime<Dot: DamageOverTimeMarker> {
|
||||||
|
marker: PhantomData<Dot>,
|
||||||
|
pub base_damage: u32,
|
||||||
|
pub turn: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Dot: DamageOverTimeMarker> DamageOverTime<Dot> {
|
||||||
|
pub fn new(base_damage: u32) -> Self {
|
||||||
|
Self {
|
||||||
|
marker: PhantomData,
|
||||||
|
base_damage,
|
||||||
|
turn: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Component)]
|
||||||
|
pub struct DeferredDamage {
|
||||||
|
pub label: &'static str,
|
||||||
|
pub amount: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub enum DamageOverTimeType {
|
||||||
|
Bleed,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Bleed;
|
||||||
|
|
||||||
|
impl DamageOverTimeMarker for Bleed {
|
||||||
|
const INCREMENTS: &[f32] = &[0.45, 0.40, 0.35, 0.30, 0.25, 0.20, 0.15, 0.10, 0.05];
|
||||||
|
const LABEL: &str = "Bleeding";
|
||||||
|
}
|
||||||
|
|
||||||
fn apply_additive_status_effect<const N: usize, M: AdditiveStatusEffectMarker<N>>(
|
fn apply_additive_status_effect<const N: usize, M: AdditiveStatusEffectMarker<N>>(
|
||||||
In(entities): In<Vec<Entity>>,
|
In(entities): In<Vec<Entity>>,
|
||||||
effect_q: Query<(Entity, &ChildOf, &AdditiveStatusEffect<N, M>)>,
|
effect_q: Query<(Entity, &ChildOf, &AdditiveStatusEffect<N, M>)>,
|
||||||
|
|
@ -510,6 +551,34 @@ fn remove_temp_debuff_effect<Temp: DebuffingTempMarker>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn process_dot<Dot: DamageOverTimeMarker>(
|
||||||
|
dot_q: Query<(Entity, &mut DamageOverTime<Dot>), With<Current>>,
|
||||||
|
mut commands: Commands,
|
||||||
|
mut logger: Logger,
|
||||||
|
) {
|
||||||
|
for (player, mut dot) in dot_q {
|
||||||
|
if dot.turn as usize == Dot::INCREMENTS.len() {
|
||||||
|
commands.entity(player).remove::<DamageOverTime<Dot>>();
|
||||||
|
} else {
|
||||||
|
if dot.turn == 0 {
|
||||||
|
log!(logger, "spawned_dot", {
|
||||||
|
target: player,
|
||||||
|
base_damage: dot.base_damage,
|
||||||
|
label: Dot::LABEL,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
let factor = Dot::INCREMENTS[dot.turn as usize];
|
||||||
|
let amount = (factor * dot.base_damage as f32) as u16;
|
||||||
|
commands.entity(player).with_child(DeferredDamage {
|
||||||
|
amount,
|
||||||
|
label: Dot::LABEL,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
dot.turn += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn register_debuff_temp<Temp: DebuffingTempMarker>(stages: &mut Stages) {
|
fn register_debuff_temp<Temp: DebuffingTempMarker>(stages: &mut Stages) {
|
||||||
stages
|
stages
|
||||||
.register_effect::<TempDebuffEffect<Temp>>()
|
.register_effect::<TempDebuffEffect<Temp>>()
|
||||||
|
|
@ -544,4 +613,6 @@ pub(crate) fn configure(stages: &mut Stages) {
|
||||||
register_status_effect::<4, Motivate>(stages);
|
register_status_effect::<4, Motivate>(stages);
|
||||||
register_status_effect::<4, Demoralise>(stages);
|
register_status_effect::<4, Demoralise>(stages);
|
||||||
register_status_effect::<2, Frozen>(stages);
|
register_status_effect::<2, Frozen>(stages);
|
||||||
|
|
||||||
|
stages.post_turn.add_systems(process_dot::<Bleed>);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,8 @@ use crate::{
|
||||||
Stages,
|
Stages,
|
||||||
effect::{Effects, TurnLimitedEffect},
|
effect::{Effects, TurnLimitedEffect},
|
||||||
player::status_effect::{
|
player::status_effect::{
|
||||||
AdditiveStatusEffect, Crippled, Demoralise, Frozen, Motivate, Slow, Weakened, Withered,
|
AdditiveStatusEffect, Crippled, DamageOverTimeType, Demoralise, Frozen, Motivate, Slow,
|
||||||
|
Weakened, Withered,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -427,6 +428,15 @@ pub(crate) fn prepare_bonuses(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
WeaponBonusType::Bleed => {
|
||||||
|
commands
|
||||||
|
.entity(weapon.parent())
|
||||||
|
.with_child(DamageProcEffect::DamageOverTimer {
|
||||||
|
value: value.0,
|
||||||
|
kind: DamageOverTimeType::Bleed,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
val => unimplemented!("{val:?}"),
|
val => unimplemented!("{val:?}"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -434,6 +444,6 @@ pub(crate) fn prepare_bonuses(
|
||||||
|
|
||||||
pub(crate) fn configure(stages: &mut Stages) {
|
pub(crate) fn configure(stages: &mut Stages) {
|
||||||
stages
|
stages
|
||||||
.pre_fight
|
.passives
|
||||||
.add_systems(prepare_bonuses.after(super::apply_passives));
|
.add_systems(prepare_bonuses.after(super::apply_passives));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
use bevy_ecs::prelude::*;
|
use bevy_ecs::prelude::*;
|
||||||
use proxisim_models::bundle::{
|
use proxisim_models::bundle::{
|
||||||
passive::{Education, EducationPartDamageBonus, FactionUpgrades, Merits},
|
passive::{Education, EducationPartDamageBonus, FactionUpgrades, Merits},
|
||||||
player::{Current, PartDamageBonus, Weapons},
|
player::{Current, PartDamageBonus},
|
||||||
stat::{
|
stat::{
|
||||||
AdditiveBonus, AmmoControl, ClipSize, Clips, CritRate, DamageBonus, Dexterity,
|
AdditiveBonus, AmmoControl, ClipSize, Clips, CritRate, DamageBonus, Dexterity,
|
||||||
SimpleStatBonus, SimpleStatEffective, WeaponAccuracy,
|
SimpleStatBonus, SimpleStatEffective, WeaponAccuracy,
|
||||||
|
|
@ -17,6 +17,7 @@ use crate::{
|
||||||
effect::{Effects, TurnLimitedEffect},
|
effect::{Effects, TurnLimitedEffect},
|
||||||
log,
|
log,
|
||||||
log::Logger,
|
log::Logger,
|
||||||
|
player::status_effect::DamageOverTimeType,
|
||||||
};
|
};
|
||||||
|
|
||||||
use self::bonus::{
|
use self::bonus::{
|
||||||
|
|
@ -152,6 +153,10 @@ pub enum DamageProcEffect {
|
||||||
value: f32,
|
value: f32,
|
||||||
bonus: SelfStatusEffect,
|
bonus: SelfStatusEffect,
|
||||||
},
|
},
|
||||||
|
DamageOverTimer {
|
||||||
|
value: f32,
|
||||||
|
kind: DamageOverTimeType,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
fn apply_passives(
|
fn apply_passives(
|
||||||
|
|
@ -520,7 +525,7 @@ pub(crate) fn configure(stages: &mut Stages) {
|
||||||
// running this in the snapshot layer ensures that the stat increases aren't restored at the
|
// running this in the snapshot layer ensures that the stat increases aren't restored at the
|
||||||
// end of the run
|
// end of the run
|
||||||
stages.snapshot.add_systems(apply_first_turn_effects);
|
stages.snapshot.add_systems(apply_first_turn_effects);
|
||||||
stages.pre_fight.add_systems(apply_passives);
|
stages.equip.add_systems(apply_passives);
|
||||||
stages.turn.add_systems(reload_weapon);
|
stages.turn.add_systems(reload_weapon);
|
||||||
stages.post_turn.add_systems(unset_current);
|
stages.post_turn.add_systems(unset_current);
|
||||||
stages
|
stages
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue