Skip to content

Commit

Permalink
feat: add critical hits (#826)
Browse files Browse the repository at this point in the history
Implements #257
  • Loading branch information
TestingPlant authored Jan 22, 2025
1 parent a73f3e0 commit 2fa8137
Showing 1 changed file with 37 additions and 8 deletions.
45 changes: 37 additions & 8 deletions events/tag/src/module/attack.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@ use std::borrow::Cow;
use compact_str::format_compact;
use flecs_ecs::{
core::{
Builder, EntityViewGet, QueryAPI, QueryBuilderImpl, SystemAPI, TableIter, TermBuilderImpl,
World, WorldGet, flecs,
Builder, EntityView, EntityViewGet, QueryAPI, QueryBuilderImpl, SystemAPI, TableIter,
TermBuilderImpl, World, WorldGet, flecs,
},
macros::{Component, system},
prelude::Module,
};
use glam::IVec3;
use hyperion::{
Prev,
net::{
Compose, ConnectionId, agnostic,
packets::{BossBarAction, BossBarS2c},
Expand Down Expand Up @@ -130,6 +131,7 @@ impl Module for AttackModule {
compose.unicast(&pkt, *stream, system).unwrap();
});

// TODO: This code should be split between melee attacks and bow attacks
system!("handle_attacks", world, &mut EventQueue<event::AttackEntity>($), &Compose($))
.multi_threaded()
.each_iter(
Expand All @@ -153,8 +155,9 @@ impl Module for AttackModule {
for event in event_queue.drain() {
let target = world.entity_from_id(event.target);
let origin = world.entity_from_id(event.origin);
let critical_hit = can_critical_hit(origin);
origin.get::<(&ConnectionId, &Position, &mut KillCount, &mut PlayerInventory, &mut Armor, &CombatStats, &PlayerInventory, &Team, &mut Xp)>(|(origin_connection, origin_pos, kill_count, inventory, origin_armor, from_stats, from_inventory, origin_team, origin_xp)| {
let damage = from_stats.damage + calculate_stats(from_inventory).damage;
let damage = from_stats.damage + calculate_stats(from_inventory, critical_hit).damage;
target.try_get::<(
&ConnectionId,
Option<&mut ImmuneUntil>,
Expand Down Expand Up @@ -186,7 +189,7 @@ impl Module for AttackModule {
return;
}

let calculated_stats = calculate_stats(target_inventory);
let calculated_stats = calculate_stats(target_inventory, critical_hit);
let armor = stats.armor + calculated_stats.armor;
let toughness = stats.armor_toughness + calculated_stats.armor_toughness;
let protection = stats.protection + calculated_stats.protection;
Expand All @@ -197,7 +200,7 @@ impl Module for AttackModule {
health.damage(damage_after_protection);

let pkt_health = play::HealthUpdateS2c {
health: health.abs(),
health: **health,
food: VarInt(20),
food_saturation: 5.0
};
Expand All @@ -220,7 +223,7 @@ impl Module for AttackModule {
source_pos: Option::None
};
let sound = agnostic::sound(
ident!("minecraft:entity.player.attack.knockback"),
if critical_hit { ident!("minecraft:entity.player.attack.crit") } else { ident!("minecraft:entity.player.attack.knockback") },
**target_position,
).volume(1.)
.pitch(1.)
Expand All @@ -230,6 +233,21 @@ impl Module for AttackModule {
compose.unicast(&pkt_hurt, *target_connection, system).unwrap();
compose.unicast(&pkt_health, *target_connection, system).unwrap();

if critical_hit {
let particle_pkt = play::ParticleS2c {
particle: Cow::Owned(Particle::Crit),
long_distance: true,
position: target_position.as_dvec3() + DVec3::new(0.0, 1.0, 0.0),
max_speed: 0.5,
count: 100,
offset: Vec3::new(0.5, 0.5, 0.5),
};

// origin is excluded because the crit particles are
// already generated on the client side of the attacker
compose.broadcast(&particle_pkt, system).exclude(*origin_connection).send().unwrap();
}

if health.is_dead() {
let attacker_name = origin.name();
// Even if enable_respawn_screen is false, the client needs this to send ClientCommandC2s and initiate its respawn
Expand Down Expand Up @@ -659,9 +677,11 @@ const fn calculate_toughness(item: &ItemStack) -> f32 {
}
}

fn calculate_stats(inventory: &PlayerInventory) -> CombatStats {
// TODO: split this up into separate functions
fn calculate_stats(inventory: &PlayerInventory, critical_hit: bool) -> CombatStats {
let hand = inventory.get_cursor();
let damage = calculate_damage(&hand.stack);
let multiplier = if critical_hit { 1.5 } else { 1.0 };
let damage = calculate_damage(&hand.stack) * multiplier;
let armor = calculate_armor(&inventory.get_helmet().stack)
+ calculate_armor(&inventory.get_chestplate().stack)
+ calculate_armor(&inventory.get_leggings().stack)
Expand All @@ -680,3 +700,12 @@ fn calculate_stats(inventory: &PlayerInventory) -> CombatStats {
protection: 0.0,
}
}

fn can_critical_hit(player: EntityView<'_>) -> bool {
player.get::<(&(Prev, Position), &Position)>(|(prev_position, position)| {
// TODO: Do not allow critical hits if the player is on a ladder, vine, or water. None of
// these special blocks are currently on the map.
let position_delta_y = position.y - prev_position.y;
position_delta_y < 0.0
})
}

0 comments on commit 2fa8137

Please sign in to comment.