Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bow Attack + Weapon Swap #148

Open
wants to merge 20 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion assets/player.cfg.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ fall_accel = 4.5
# How many seconds does our characters innate hover boots work?
max_coyote_time = 0.1

# Onlly applies in the downward y direction while the player is falling
# Only applies in the downward y direction while the player is falling
# and trying to walk into the wall
sliding_friction = 0.25

Expand Down Expand Up @@ -86,3 +86,6 @@ melee_pushback_ticks = 12.0

# Number of kills to trigger passive gain
passive_gain_rate = 3.0

# Velocity of the projectiles fired by the Bow weapon
arrow_velocity = 1000.0
2 changes: 1 addition & 1 deletion engine/src/physics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -395,7 +395,7 @@ fn init_physics_world(mut world: ResMut<PhysicsWorld>) {
///
/// Make sure if you are reading from this in a system, you run after this finishes
///
/// TODO make sure this always runs after the GameTickUpdate; consider creating a seperate
/// TODO make sure this always runs after the GameTickUpdate; consider creating a separate
/// [`ScheduleLabel`] for immediately after transform propagation
pub fn update_query_pipeline(
// Mutable reference because collider data is stored in an Arena that pipeline modifies
Expand Down
13 changes: 10 additions & 3 deletions game/src/game/attack/arc_attack.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,22 +45,29 @@ impl Projectile {
}
}

/// Marker that identifies arrows shot by the Player.
#[derive(Component)]
pub struct Arrow;

/// Applies gravity to the projectile
/// It will not add gravity to Arrows shot by the Player.
pub fn arc_projectile(
mut query: Query<
(
&mut Transform,
&Collider,
&mut Projectile,
Has<Arrow>,
),
With<Attack>,
>,
config: Res<PlayerConfig>,
time: Res<GameTime>,
) {
let fall_accel = config.fall_accel;
for (mut transform, collider, mut projectile) in query.iter_mut() {
projectile.vel.0.y -= fall_accel;
for (mut transform, mut projectile, arrow) in query.iter_mut() {
if !arrow {
projectile.vel.0.y -= fall_accel;
}
let z = transform.translation.z;
transform.translation = (transform.translation.xy()
+ *projectile.vel * (1.0 / time.hz as f32))
Expand Down
38 changes: 36 additions & 2 deletions game/src/game/attack/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ pub mod particles;

use std::mem;

use arc_attack::Arrow;
use rapier2d::prelude::InteractionGroups;
use theseeker_engine::gent::Gent;
use theseeker_engine::physics::{
update_sprite_colliders, Collider, PhysicsWorld, GROUND,
update_sprite_colliders, Collider, PhysicsWorld, GROUND, PLAYER_ATTACK,
};

use super::enemy::{Defense, EnemyGfx, EnemyStateSet, JustGotHitMarker};
Expand Down Expand Up @@ -38,7 +40,7 @@ impl Plugin for AttackPlugin {
(
determine_attack_targets,
apply_attack_modifications,
// DamageInfo event emited here
// DamageInfo event emitted here
apply_attack_damage,
// OnAttackFirstHitSet
track_crits,
Expand All @@ -60,6 +62,7 @@ impl Plugin for AttackPlugin {
// cleanup
attack_tick,
despawn_projectile,
despawn_player_arrows,
attack_cleanup,
)
.chain()
Expand Down Expand Up @@ -135,6 +138,11 @@ impl Attack {
self.status_mod = Some(modif);
self
}

pub fn with_max_targets(mut self, max_targets: u32) -> Self {
self.max_targets = max_targets;
self
}
}

/// Event sent when damage is applied
Expand Down Expand Up @@ -412,6 +420,32 @@ pub fn despawn_projectile(
}
}

pub fn despawn_player_arrows(
query: Query<(Entity, &Transform, &Collider), With<Arrow>>,
spatial_query: Res<PhysicsWorld>,
mut commands: Commands,
) {
for (entity, transform, collider) in query.iter() {
let is_arrow_intersecting_with_ground = !spatial_query
.intersect(
transform.translation.xy(),
collider.0.shape(),
InteractionGroups {
memberships: PLAYER_ATTACK,
filter: GROUND,
},
None,
)
.is_empty();

if is_arrow_intersecting_with_ground {
// Note: purposefully does not despawn child entities, nor remove the
// reference, so that child particle systems have the option of lingering
commands.entity(entity).despawn();
}
}
}

pub fn kill_on_damage(
query: Query<(Entity, &Health), With<Gent>>,
mut damage_events: EventReader<DamageInfo>,
Expand Down
14 changes: 6 additions & 8 deletions game/src/game/enemy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ fn setup_enemy(
if !bp.is_added() {
continue;
}
// TODO: ensure propper z order
// TODO: ensure proper z order
xf_gent.translation.z = 14.0 * 0.000001;
xf_gent.translation.y += 2.0; // Sprite offset so it looks like it is standing on the ground
let e_gfx = commands.spawn(()).id();
Expand Down Expand Up @@ -547,7 +547,7 @@ struct Decay;

// Check how far the player is, set our range, set our target if applicable, turn to face player if
// in range
// TODO: check x and y distance independantly?
// TODO: check x and y distance independently?
fn check_player_range(
mut query: Query<
(
Expand Down Expand Up @@ -795,11 +795,9 @@ fn aggro(
transitions.push(Aggroed::new_transition(Patrolling));
} else if matches!(range, Range::Melee) {
match role {
Role::Melee => {
transitions.push(Waiting::new_transition(
MeleeAttack::default(),
))
},
Role::Melee => transitions.push(Waiting::new_transition(
MeleeAttack::default(),
)),
Role::Ranged => {
velocity.x = 0.;
transitions.push(Waiting::new_transition(
Expand Down Expand Up @@ -865,7 +863,7 @@ fn ranged_attack(
));
add_q.add(Idle);
}
// if player isnt alive, do nothing, we will transiton back once animation finishes
// if player isnt alive, do nothing, we will transition back once animation finishes
let Ok(transform) = player_query.get(attack.target) else {
continue;
};
Expand Down
10 changes: 9 additions & 1 deletion game/src/game/gentstate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,14 @@ impl Facing {
Facing::Left => -1.,
}
}

/// Returns the opposite of a given [`Facing`] variant.
pub fn invert(&self) -> Self {
match self {
Facing::Right => Facing::Left,
Facing::Left => Facing::Right,
}
}
}

/// States
Expand All @@ -92,7 +100,7 @@ impl Facing {
pub trait GentState: Component<Storage = SparseStorage> {}

/// A GenericState has a blanket Transitionable impl for any GentState,
/// it will remove itsself on transition
/// it will remove itself on transition
pub trait GenericState: Component<Storage = SparseStorage> {}

impl<T: GentState, N: GentState + GenericState> Transitionable<T> for N {
Expand Down
24 changes: 20 additions & 4 deletions game/src/game/player/mod.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
mod player_anim;
mod player_behaviour;
mod player_weapon;
use bevy::utils::hashbrown::HashMap;
use leafwing_input_manager::action_state::ActionState;
use leafwing_input_manager::axislike::VirtualAxis;
use leafwing_input_manager::input_map::InputMap;
use leafwing_input_manager::{Actionlike, InputManagerBundle};
use player_anim::PlayerAnimationPlugin;
use player_behaviour::PlayerBehaviorPlugin;
use player_weapon::PlayerWeaponPlugin;
use rapier2d::geometry::{Group, InteractionGroups};
use strum::IntoEnumIterator;
use strum_macros::EnumIter;
Expand Down Expand Up @@ -59,6 +61,7 @@ impl Plugin for PlayerPlugin {
PlayerBehaviorPlugin,
PlayerTransitionPlugin,
PlayerAnimationPlugin,
PlayerWeaponPlugin,
));

#[cfg(feature = "dev")]
Expand Down Expand Up @@ -121,6 +124,7 @@ pub enum PlayerAction {
Dash,
Whirl,
Stealth,
SwapWeapon,
}

#[derive(Component, Debug, Deref, DerefMut)]
Expand Down Expand Up @@ -326,9 +330,10 @@ fn setup_player(
.with(PlayerAction::Attack, KeyCode::KeyJ)
.with(PlayerAction::Dash, KeyCode::KeyK)
.with(PlayerAction::Whirl, KeyCode::KeyL)
.with(PlayerAction::Stealth, KeyCode::KeyI),
.with(PlayerAction::Stealth, KeyCode::KeyI)
.with(PlayerAction::SwapWeapon, KeyCode::KeyH),
},
// bundling things up becuase we reached max tuple
// bundling things up because we reached max tuple
(
Falling,
CanDash {
Expand Down Expand Up @@ -573,6 +578,13 @@ impl WallSlideTime {
fn strict_sliding(&self, cfg: &PlayerConfig) -> bool {
self.0 <= cfg.max_coyote_time * 1.0
}

/// Checks that player is actually against the wall, rather then it being close
/// enough time from the player having left the wall to still jump
/// (ie: not wall_jump_coyote_time)
fn is_pressed_against_wall(&self, time: &Res<GameTime>) -> bool {
self.0 <= 1.0 / time.hz as f32
}
}

/// Tracks the cooldown for the available energy for the players whirl
Expand Down Expand Up @@ -623,7 +635,7 @@ pub struct PlayerConfig {
/// How many seconds does our characters innate hover boots work?
max_coyote_time: f32,

/// Onlly applies in the downward y direction while the player is falling
/// Only applies in the downward y direction while the player is falling
/// and trying to walk into the wall
sliding_friction: f32,

Expand Down Expand Up @@ -674,6 +686,9 @@ pub struct PlayerConfig {
/// Ticks for melee knockback velocity; determines how long movement is locked for
melee_pushback_ticks: u32,

/// Velocity of the projectiles fired by the Bow weapon
arrow_velocity: f32,

/// How many kills to trigger a passive gain
passive_gain_rate: u32,
}
Expand Down Expand Up @@ -747,6 +762,7 @@ fn update_player_config(config: &mut PlayerConfig, cfg: &DynamicConfig) {
update_field(&mut errors, &cfg.0, "melee_pushback", |val| config.melee_pushback = val);
update_field(&mut errors, &cfg.0, "melee_pushback_ticks", |val| config.melee_pushback_ticks = val as u32);
update_field(&mut errors, &cfg.0, "passive_gain_rate", |val| config.passive_gain_rate = val as u32);
update_field(&mut errors, &cfg.0, "arrow_velocity", |val| config.arrow_velocity = val);

for error in errors{
warn!("failed to load player cfg value: {}", error);
Expand Down Expand Up @@ -779,7 +795,7 @@ pub struct StatusModifier {

/// Multiplying Factor on Stat, e.g. 102.0 * 0.5 = 51.0
scalar: Vec<f32>,
/// Offseting Value on Stat, e.g. 100.0 - 10.0 = 90.0
/// Offsetting Value on Stat, e.g. 100.0 - 10.0 = 90.0
delta: Vec<f32>,

effect_col: Color,
Expand Down
40 changes: 19 additions & 21 deletions game/src/game/player/player_anim.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ use crate::prelude::{
Res, With, Without,
};

use super::player_weapon::PlayerWeapon;

/// play animations here, run after transitions
pub struct PlayerAnimationPlugin;

Expand Down Expand Up @@ -152,10 +154,11 @@ fn player_attacking_animation(
Option<&HitFreezeTime>,
Ref<Attacking>,
),
(Without<Whirling>),
Without<Whirling>,
>,
mut gfx_query: Query<&mut ScriptPlayer<SpriteAnimation>, With<PlayerGfx>>,
config: Res<PlayerConfig>,
weapon: Res<PlayerWeapon>,
) {
for (gent, is_falling, is_jumping, is_running, hitfrozen, attacking) in
query.iter()
Expand All @@ -166,31 +169,29 @@ fn player_attacking_animation(
.unwrap_or(false);
// let current = player.current_key().unwrap_or("").clone();
player.set_slot("AttackTransition", false);
let basic_air_anim_key_str = &weapon.get_anim_key("BasicAir");
let basic_run_anim_key_str = &weapon.get_anim_key("BasicRun");
let basic_idle_anim_key_str = &weapon.get_anim_key("BasicIdle");

if is_falling || is_jumping {
// TODO: These need a way to resume the new animation from the current frame index
// or specified offset
if player.current_key().unwrap_or("")
!= "anim.player.SwordBasicAir"
{
player.play_key("anim.player.SwordBasicAir");
if player.current_key() != Some(basic_air_anim_key_str) {
player.play_key(basic_air_anim_key_str);
if !attacking.is_added() {
player.set_slot("AttackTransition", true);
}
}
} else if is_running && !hitfrozen {
if player.current_key().unwrap_or("")
!= "anim.player.SwordBasicRun"
{
player.play_key("anim.player.SwordBasicRun");
if player.current_key() != Some(basic_run_anim_key_str) {
player.play_key(basic_run_anim_key_str);
if !attacking.is_added() {
player.set_slot("AttackTransition", true);
}
}
} else {
if player.current_key().unwrap_or("")
!= "anim.player.SwordBasicIdle"
{
player.play_key("anim.player.SwordBasicIdle");
if player.current_key() != Some(basic_idle_anim_key_str) {
player.play_key(basic_idle_anim_key_str);
if !attacking.is_added() {
player.set_slot("AttackTransition", true);
}
Expand Down Expand Up @@ -228,6 +229,7 @@ fn sprite_flip(
mut current_direction: Local<bool>,
mut old_direction: Local<bool>,
time: Res<GameTime>,
weapon: Res<PlayerWeapon>,
) {
for (facing, gent, wall_slide_time) in query.iter() {
if let Ok(mut player) = gfx_query.get_mut(gent.e_gfx) {
Expand All @@ -236,14 +238,10 @@ fn sprite_flip(

// Have the player face away from the wall if they are attacking while wall sliding
let pressed_on_wall = wall_slide_time
// checks that player is actually against the wall, rather then it being close
// enough time from the player having left the wall to still jump
// (ie: not wall_jump_coyote_time)
.map(|s| s.0 <= 1.0 / time.hz as f32)
.unwrap_or(false);
if pressed_on_wall
&& player.current_key() == Some("anim.player.SwordBasicAir")
{
.is_some_and(|s| s.is_pressed_against_wall(&time));
let is_attacking_while_falling =
player.current_key() == Some(&weapon.get_anim_key("BasicAir"));
if pressed_on_wall && is_attacking_while_falling {
facing = match facing {
Facing::Right => Facing::Left,
Facing::Left => Facing::Right,
Expand Down
Loading