initial commit

This commit is contained in:
TotallyNot 2023-12-31 21:26:43 +01:00
commit 86f9333aec
21 changed files with 6449 additions and 0 deletions

284
src/armour/mod.rs Normal file
View file

@ -0,0 +1,284 @@
use bevy_ecs::prelude::*;
use rand::distributions::Distribution;
use strum::IntoEnumIterator;
use crate::{
hierarchy::HierarchyBuilder,
player::{
status_effect::{
ConcussionGrenade, FlashGrenade, PepperSpray, Sand, TearGas, TempDebuffImmunity,
},
BodyPart,
},
Name, Stages,
};
#[derive(Component, Default)]
pub struct Armour;
#[derive(Debug)]
pub struct BodyPartCoverage {
pub armour: Entity,
pub coverage: f32,
pub armour_value: f32,
}
#[derive(Component, Default)]
pub struct ArmourCoverage(ArmourVec<f32>);
#[derive(Component, Default)]
pub struct ArmourValue(pub f32);
#[derive(Component)]
#[cfg_attr(feature = "json", derive(serde::Deserialize))]
#[cfg_attr(feature = "json", serde(rename_all = "snake_case"))]
pub enum Set {
Riot,
Assault,
Dune,
Delta,
Vanguard,
Sentinel,
Marauder,
Eod,
}
#[cfg_attr(feature = "json", derive(serde::Deserialize))]
#[cfg_attr(feature = "json", serde(rename_all = "snake_case"))]
pub enum Immunity {
Radiation,
TearGas,
PepperSpray,
NerveGas,
Sand,
ConcussionGrenades,
FlashGrenades,
}
#[derive(Component)]
pub struct Immunities(pub Vec<Immunity>);
#[derive(Bundle, Default)]
pub struct ArmourBundle {
pub name: Name,
pub armour: Armour,
pub coverage: ArmourCoverage,
pub armour_value: ArmourValue,
}
impl ArmourBundle {
pub fn new(name: String, coverage: [f32; 10], armour_value: f32) -> Self {
Self {
name: Name(name),
armour: Armour,
coverage: ArmourCoverage(ArmourVec(coverage)),
armour_value: ArmourValue(armour_value / 100.0),
}
}
}
#[derive(Component, Default, Debug)]
pub struct ArmourBodyPart {
pub armour_pieces: Vec<BodyPartCoverage>,
}
#[derive(Component, Default)]
pub struct EquippedArmour {
pub head: Option<Entity>,
pub body: Option<Entity>,
pub legs: Option<Entity>,
pub feet: Option<Entity>,
pub hands: Option<Entity>,
}
#[derive(Component, Debug)]
pub struct ArmourBodyParts(pub ArmourVec<Entity>);
enum ArmourIterState {
Head,
Body,
Legs,
Feet,
Hands,
}
pub struct ArmourIter<'a> {
state: ArmourIterState,
equipped_armour: &'a EquippedArmour,
}
impl<'a> Iterator for ArmourIter<'a> {
type Item = Entity;
fn next(&mut self) -> Option<Self::Item> {
loop {
let (next, piece) = match self.state {
ArmourIterState::Head => (ArmourIterState::Body, self.equipped_armour.head),
ArmourIterState::Body => (ArmourIterState::Legs, self.equipped_armour.body),
ArmourIterState::Legs => (ArmourIterState::Feet, self.equipped_armour.legs),
ArmourIterState::Feet => (ArmourIterState::Hands, self.equipped_armour.feet),
ArmourIterState::Hands => return self.equipped_armour.hands,
};
self.state = next;
if piece.is_some() {
return piece;
}
}
}
}
impl<'a> IntoIterator for &'a EquippedArmour {
type Item = Entity;
type IntoIter = ArmourIter<'a>;
fn into_iter(self) -> Self::IntoIter {
ArmourIter {
state: ArmourIterState::Head,
equipped_armour: self,
}
}
}
#[repr(usize)]
#[derive(Clone, Copy, strum::EnumIter)]
pub enum ArmourBodyPartSlot {
Arms,
Stomach,
Heart,
Chest,
Throat,
Hands,
Groin,
Legs,
Head,
Feet,
}
impl From<BodyPart> for ArmourBodyPartSlot {
fn from(value: BodyPart) -> Self {
match value {
BodyPart::LeftArm | BodyPart::RightArm => Self::Arms,
BodyPart::Stomach => Self::Stomach,
BodyPart::Heart => Self::Heart,
BodyPart::Chest => Self::Chest,
BodyPart::Throat => Self::Throat,
BodyPart::LeftHand | BodyPart::RightHand => Self::Hands,
BodyPart::Groin => Self::Groin,
BodyPart::LeftLeg | BodyPart::RightLeg => Self::Legs,
BodyPart::Head => Self::Head,
BodyPart::LeftFoot | BodyPart::RightFoot => Self::Feet,
}
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct ArmourVec<T>([T; 10]);
impl<T> std::ops::Index<ArmourBodyPartSlot> for ArmourVec<T> {
type Output = T;
fn index(&self, index: ArmourBodyPartSlot) -> &Self::Output {
&self.0[index as usize]
}
}
impl<T> std::ops::IndexMut<ArmourBodyPartSlot> for ArmourVec<T> {
fn index_mut(&mut self, index: ArmourBodyPartSlot) -> &mut Self::Output {
&mut self.0[index as usize]
}
}
impl<T> IntoIterator for ArmourVec<T> {
type Item = T;
type IntoIter = std::array::IntoIter<T, 10>;
fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
}
}
impl<'a> Distribution<Option<&'a BodyPartCoverage>> for &'a ArmourBodyPart {
fn sample<R: rand::prelude::Rng + ?Sized>(&self, rng: &mut R) -> Option<&'a BodyPartCoverage> {
let mut current = None;
for piece in &self.armour_pieces {
// NOTE: This is not strictly speaking correct, but the edge cases where this applies
// should be very rare, and it should be a decent enough heuristic to barely make a
// difference from the actual pixel comparisons that torn seems to be using for this.
if current
.map(|c: &BodyPartCoverage| c.armour_value)
.unwrap_or_default()
< piece.armour_value
&& rng.gen_bool(piece.coverage as f64)
{
current = Some(piece);
}
}
current
}
}
fn generate_body_parts(
equip_q: Query<(Entity, &EquippedArmour)>,
armour_q: Query<(Entity, &ArmourCoverage, &ArmourValue, Option<&Immunities>)>,
mut commands: Commands,
) {
for (player, equipped_armour) in equip_q.iter() {
let mut parts = ArmourVec::<ArmourBodyPart>::default();
for (armour, coverage, armour_value, immunities) in armour_q.iter_many(equipped_armour) {
commands.entity(armour).set_parent(player);
if let Some(immunities) = immunities {
let mut player = commands.entity(player);
for immunity in &immunities.0 {
match immunity {
// TODO: Going to need this if irradiate is ever added
Immunity::Radiation => (),
// NOTE: It's an unreleased DOT temp, so the exact effect is currently
// unknwown
Immunity::NerveGas => (),
Immunity::TearGas => {
player.insert(TempDebuffImmunity::<TearGas>::default());
}
Immunity::PepperSpray => {
player.insert(TempDebuffImmunity::<PepperSpray>::default());
}
Immunity::FlashGrenades => {
player.insert(TempDebuffImmunity::<FlashGrenade>::default());
}
Immunity::Sand => {
player.insert(TempDebuffImmunity::<Sand>::default());
}
Immunity::ConcussionGrenades => {
player.insert(TempDebuffImmunity::<ConcussionGrenade>::default());
}
}
}
}
for slot in ArmourBodyPartSlot::iter() {
if coverage.0[slot] > 0.0 {
parts[slot].armour_pieces.push(BodyPartCoverage {
armour,
coverage: coverage.0[slot] / 100.0,
armour_value: armour_value.0,
});
}
}
}
let parts = parts.0.map(|p| commands.spawn(p).id());
commands
.entity(player)
.add_children(parts)
.insert(ArmourBodyParts(ArmourVec(parts)));
}
}
pub(crate) fn configure(stages: &mut Stages) {
stages.equip.add_systems(generate_body_parts);
}