Skip to content

Commit

Permalink
Divide Mega-Player Component into Multiple Smaller Components (#168)
Browse files Browse the repository at this point in the history
* remove * imports for character and player files

* remove unused collider_density for player component

* separate movement and status components in PlayerComponent to PlayerMovementStatsComponent and PlayerStatusComponent, and create PlayerBundle

* remove unused collider dimensions from player component

* change player_index in PlayerComponenet to its own component, PlayerIDComponent

* remove PlayerStatusComponenent and move movement_enabled to PlayerMovement Component

* add PlayerAttractionComponenent and move relevent values into it from PlayerComponent

* add PlayerOutgoingDamageComponent and move collision damage into it from PlayerComponent

* add PlayerIncomingDamageComponent and move incoming damage multiplier into it from PlayerComponent

* add PlayerAbilitiesComponent, change PlayerComponent into a flag component

* change player ui query to Or filters

* add components for player interface module
  • Loading branch information
cdsupina authored Mar 4, 2024
1 parent c05b721 commit b3e796c
Show file tree
Hide file tree
Showing 19 changed files with 409 additions and 220 deletions.
215 changes: 170 additions & 45 deletions crates/thetawave_interface/src/player.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::character::{Character, CharacterType};
use bevy_ecs::prelude::Component;
use bevy_ecs::system::Resource;
use bevy_ecs::{bundle::Bundle, prelude::Component};
use bevy_math::Vec2;
use bevy_time::{Timer, TimerMode};
use derive_more::{Deref, DerefMut};
Expand All @@ -16,17 +16,28 @@ pub struct InputRestrictions {
pub forbid_main_attack_reason: Option<String>,
pub forbid_special_attack_reason: Option<String>,
}

/// Stores all available player slots
#[derive(Resource, Debug)]
pub struct PlayersResource {
pub player_data: Vec<Option<PlayerData>>,
}

/// Information about a player slot
#[derive(Debug, Clone)]
pub struct PlayerData {
pub character: CharacterType,
pub input: PlayerInput,
}

/// Input method for a player
#[derive(Clone, PartialEq, Debug)]
pub enum PlayerInput {
Keyboard,
Gamepad(usize),
}

/// Defaults to all player slots being empty
impl Default for PlayersResource {
fn default() -> Self {
PlayersResource {
Expand All @@ -36,7 +47,7 @@ impl Default for PlayersResource {
}

impl PlayersResource {
// A method to get a vector of all used inputs
/// A method to get a vector of all used inputs
pub fn get_used_inputs(&self) -> Vec<PlayerInput> {
self.player_data
.iter()
Expand All @@ -45,80 +56,181 @@ impl PlayersResource {
}
}

/// Player input
#[derive(Clone, PartialEq, Debug)]
pub enum PlayerInput {
Keyboard,
Gamepad(usize),
/// Component bundle of all player-specific components
#[derive(Bundle)]
pub struct PlayerBundle {
movement_stats: PlayerMovementComponent,
id: PlayerIDComponent,
attraction: PlayerAttractionComponent,
outgoing_damage: PlayerOutgoingDamageComponent,
incoming_damage: PlayerIncomingDamageComponent,
inventory: PlayerInventoryComponent,
abilities: PlayerAbilitiesComponent,
flag: PlayerComponent,
}

/// Component for managing core attributes of the player
#[derive(Component, Debug, Clone)]
pub struct PlayerComponent {
impl From<&Character> for PlayerBundle {
fn from(character: &Character) -> Self {
Self {
movement_stats: character.into(),
abilities: character.into(),
attraction: character.into(),
outgoing_damage: character.into(),
incoming_damage: PlayerIncomingDamageComponent::default(),
inventory: character.into(),
id: PlayerIDComponent::One,
flag: PlayerComponent,
}
}
}

impl PlayerBundle {
pub fn with_id(self, id: PlayerIDComponent) -> Self {
Self { id, ..self }
}
}

/// Identity of a player component, used for syncing UI
#[derive(Component, Clone, Copy, PartialEq)]
pub enum PlayerIDComponent {
One,
Two,
}

/// Useful for mapping an index to a PlayerIDComponent
impl From<usize> for PlayerIDComponent {
fn from(value: usize) -> Self {
match value {
0 => PlayerIDComponent::One,
_ => PlayerIDComponent::Two,
}
}
}

/// Useful for positioning UI
impl From<PlayerIDComponent> for usize {
fn from(value: PlayerIDComponent) -> Self {
match value {
PlayerIDComponent::One => 0,
PlayerIDComponent::Two => 1,
}
}
}

/// Component that stores movement properties of player
#[derive(Component)]
pub struct PlayerMovementComponent {
/// Acceleration of the player
pub acceleration: Vec2,
/// Deceleration of the player
pub deceleration: Vec2,
/// Maximum speed of the player
pub speed: Vec2,
/// Collider dimensions
pub collider_dimensions: Vec2,
/// Collider density
pub collider_density: f32,
/// Whether the player responds to move inputs
pub movement_enabled: bool,
}

/// Component that stores attraction stats for player
/// Used for attracting items and consumables to the player
#[derive(Component)]
pub struct PlayerAttractionComponent {
/// Distance from which to apply acceleration to items and consumables
pub distance: f32,
/// Acceleration applied to items and consumables in within attraction distance
pub acceleration: f32,
}

/// Stores outgoing damage stats for player
/// TODO: add weapon damage stat that weapon abilities can use for a base damage of projectiles
#[derive(Component)]
pub struct PlayerOutgoingDamageComponent {
/// Amount of damage dealt on contact
pub collision_damage: usize,
/// Distance to attract items and consumables
pub attraction_distance: f32,
/// Acceleration applied to items and consumables in attraction distance
pub attraction_acceleration: f32,
/// Amount of money character has collected
}

/// Stores stats that effect damage incoming to the player
#[derive(Component)]
pub struct PlayerIncomingDamageComponent {
/// Multiplier for incoming damage
pub multiplier: f32,
}

impl Default for PlayerIncomingDamageComponent {
fn default() -> Self {
Self { multiplier: 1.0 }
}
}

/// Tracks what the player current has in inventory
/// TODO: track stats of how many of each consumable has been picked up for the run
#[derive(Component)]
pub struct PlayerInventoryComponent {
pub money: usize,
}

/// Currently just handles the "top" ability
/// TODO: Overhaul this component for slotting any abilities in (including weapons)
#[derive(Component, Debug, Clone)]
pub struct PlayerAbilitiesComponent {
/// Timer for ability cooldown
pub ability_cooldown_timer: Timer,
/// Timer for ability action
pub ability_action_timer: Option<Timer>,
/// Type of ability
pub ability_type: AbilityType,
/// Whether the player responds to move inputs
pub movement_enabled: bool,
/// Multiplier for incoming damage
pub incoming_damage_multiplier: f32,
/// Index of the player
pub player_index: usize,
}

impl From<&Character> for PlayerComponent {
/// Flag for Player Entities
#[derive(Component)]
pub struct PlayerComponent;

impl From<&Character> for PlayerMovementComponent {
fn from(character: &Character) -> Self {
PlayerComponent {
Self {
acceleration: character.acceleration,
deceleration: character.deceleration,
speed: character.speed,
collider_dimensions: character.collider_dimensions,
collider_density: character.collider_density,
movement_enabled: true,
}
}
}

impl From<&Character> for PlayerAttractionComponent {
fn from(character: &Character) -> Self {
Self {
acceleration: character.attraction_acceleration,
distance: character.attraction_distance,
}
}
}

impl From<&Character> for PlayerOutgoingDamageComponent {
fn from(character: &Character) -> Self {
Self {
collision_damage: character.collision_damage,
attraction_distance: character.attraction_distance,
attraction_acceleration: character.attraction_acceleration,
}
}
}

impl From<&Character> for PlayerInventoryComponent {
fn from(character: &Character) -> Self {
Self {
money: character.money,
}
}
}

impl From<&Character> for PlayerAbilitiesComponent {
fn from(character: &Character) -> Self {
Self {
ability_cooldown_timer: Timer::from_seconds(character.ability_period, TimerMode::Once),
ability_action_timer: None,
ability_type: character.ability_type.clone(),
movement_enabled: true,
incoming_damage_multiplier: 1.0,
player_index: 0,
}
}
}
impl PlayerComponent {
pub fn from_character_with_params(
character: &Character,
spawn_params: &InputRestrictionsAtSpawn,
) -> Self {
let mut res = Self::from(character);
if spawn_params.forbid_special_attack_reason.is_some() {
res.disable_special_attacks();
}
res
}

impl PlayerAbilitiesComponent {
pub fn disable_special_attacks(&mut self) {
self.ability_cooldown_timer.pause();
}
Expand All @@ -130,6 +242,19 @@ impl PlayerComponent {
}
}

impl PlayerBundle {
pub fn from_character_with_params(
character: &Character,
spawn_params: &InputRestrictionsAtSpawn,
) -> Self {
let mut res = Self::from(character);
if spawn_params.forbid_special_attack_reason.is_some() {
res.abilities.disable_special_attacks();
}
res
}
}

#[derive(Deserialize, Clone, Debug)]
pub enum AbilityType {
Charge(f32), // ability duration
Expand Down
12 changes: 6 additions & 6 deletions src/collision/contact.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use bevy::prelude::*;
use bevy_rapier2d::prelude::*;
use thetawave_interface::{
audio::{CollisionSoundType, PlaySoundEffectEvent, SoundEffectType},
player::PlayerComponent,
player::PlayerOutgoingDamageComponent,
spawnable::{Faction, MobSegmentType, MobType, ProjectileType},
};

Expand All @@ -17,7 +17,7 @@ use super::{CollidingEntityPair, SortedCollisionEvent};
pub fn contact_collision_system(
mut collision_event_writer: EventWriter<SortedCollisionEvent>,
mut collision_events: EventReader<CollisionEvent>,
player_query: Query<(Entity, &PlayerComponent)>,
player_query: Query<(Entity, &PlayerOutgoingDamageComponent)>,
mob_query: Query<(Entity, &MobComponent)>,
mob_segment_query: Query<(Entity, &MobSegmentComponent)>,
barrier_query: Query<Entity, With<ArenaBarrierComponent>>,
Expand Down Expand Up @@ -47,7 +47,7 @@ pub fn contact_collision_system(
};
// Now we pattern match to dynamic dispatch based on component type.
// check if player was collided with. (will be primary due to sort)
if let Ok((_player_entity, player_component)) =
if let Ok((_player_entity, player_damage)) =
player_query.get(colliding_entities.primary)
{
// check if player collided with a mob
Expand All @@ -65,7 +65,7 @@ pub fn contact_collision_system(
MobType::Ally(_) => Faction::Ally,
MobType::Neutral(_) => Faction::Neutral,
},
player_damage: player_component.collision_damage,
player_damage: player_damage.collision_damage,
mob_damage: mob_component.collision_damage,
});
continue 'collision_events;
Expand Down Expand Up @@ -93,7 +93,7 @@ pub fn contact_collision_system(
MobSegmentType::Neutral(_) => Faction::Neutral,
MobSegmentType::Enemy(_) => Faction::Enemy,
},
player_damage: player_component.collision_damage,
player_damage: player_damage.collision_damage,
mob_segment_damage: mob_segment_component.collision_damage,
});
continue 'collision_events;
Expand All @@ -109,7 +109,7 @@ pub fn contact_collision_system(
ProjectileType::Blast(faction) => faction,
ProjectileType::Bullet(faction) => faction,
},
player_damage: player_component.collision_damage,
player_damage: player_damage.collision_damage,
projectile_damage: projectile_component.damage,
});
continue 'collision_events;
Expand Down
Loading

0 comments on commit b3e796c

Please sign in to comment.