2020-10-07 02:23:20 +00:00
use crate ::{
2020-10-17 09:36:44 +00:00
client ::Client ,
2021-04-03 03:03:59 +00:00
comp ::{
2021-11-28 04:32:24 +00:00
ability ,
2021-08-27 18:49:58 +00:00
agent ::{ Agent , AgentEvent , Sound , SoundKind } ,
2022-05-28 12:06:49 +00:00
loot_owner ::LootOwner ,
2021-10-17 04:28:39 +00:00
skillset ::SkillGroupKind ,
2021-09-23 18:43:31 +00:00
BuffKind , BuffSource , PhysicsState ,
2021-04-03 03:03:59 +00:00
} ,
2023-03-31 23:20:14 +00:00
rtsim ,
2021-08-20 07:44:34 +00:00
sys ::terrain ::SAFE_ZONE_RADIUS ,
2020-11-23 15:39:03 +00:00
Server , SpawnPoint , StateExt ,
2020-10-07 02:23:20 +00:00
} ;
2023-01-22 23:50:29 +00:00
use authc ::Uuid ;
2020-02-16 20:04:06 +00:00
use common ::{
2020-11-15 02:05:18 +00:00
combat ,
2021-11-13 20:46:45 +00:00
combat ::DamageContributor ,
2020-07-25 23:57:04 +00:00
comp ::{
2020-12-04 22:24:56 +00:00
self , aura , buff ,
2020-12-19 02:05:39 +00:00
chat ::{ KillSource , KillType } ,
2022-06-06 22:45:50 +00:00
inventory ::item ::{ AbilityMap , MaterialStatManifest } ,
2023-04-23 19:17:39 +00:00
item ::flatten_counted_items ,
2022-06-04 17:16:12 +00:00
loot_owner ::LootOwnerKind ,
2022-02-14 02:09:45 +00:00
Alignment , Auras , Body , CharacterState , Energy , Group , Health , HealthChange , Inventory ,
Player , Poise , Pos , SkillSet , Stats ,
2020-07-25 23:57:04 +00:00
} ,
2021-01-30 22:35:00 +00:00
event ::{ EventBus , ServerEvent } ,
2023-04-23 19:17:39 +00:00
lottery ::distribute_many ,
2022-06-14 21:11:58 +00:00
outcome ::{ HealthChangeInfo , Outcome } ,
2023-03-11 21:44:57 +00:00
resources ::{ Secs , Time } ,
2023-04-23 19:17:39 +00:00
spiral ::Spiral2d ,
2022-10-30 18:40:20 +00:00
states ::utils ::StageSection ,
2021-07-06 02:13:11 +00:00
terrain ::{ Block , BlockKind , TerrainGrid } ,
2023-04-16 19:07:34 +00:00
trade ::{ TradeResult , Trades } ,
2020-12-13 17:40:15 +00:00
uid ::{ Uid , UidAllocator } ,
2021-01-30 22:35:00 +00:00
util ::Dir ,
2020-09-21 20:10:32 +00:00
vol ::ReadVol ,
2021-05-06 18:50:16 +00:00
Damage , DamageKind , DamageSource , Explosion , GroupTarget , RadiusEffect ,
2020-02-16 20:04:06 +00:00
} ;
2021-01-03 04:44:54 +00:00
use common_net ::{ msg ::ServerGeneral , sync ::WorldSyncExt } ;
2023-05-15 22:40:53 +00:00
use common_state ::{ AreasContainer , BlockChange , NoDurabilityArea } ;
2020-12-13 17:21:51 +00:00
use hashbrown ::HashSet ;
2023-04-23 19:17:39 +00:00
use rand ::Rng ;
2021-11-13 20:46:45 +00:00
use specs ::{
join ::Join , saveload ::MarkerAllocator , Builder , Entity as EcsEntity , Entity , WorldExt ,
} ;
2023-01-05 23:15:43 +00:00
use std ::{ collections ::HashMap , iter , sync ::Arc , time ::Duration } ;
2021-11-13 20:46:45 +00:00
use tracing ::{ debug , error } ;
2021-03-15 03:45:55 +00:00
use vek ::{ Vec2 , Vec3 } ;
2020-03-22 19:39:50 +00:00
2021-11-13 20:46:45 +00:00
#[ derive(Hash, Eq, PartialEq) ]
enum DamageContrib {
Solo ( EcsEntity ) ,
Group ( Group ) ,
NotFound ,
}
2022-01-07 05:30:28 +00:00
pub fn handle_poise ( server : & Server , entity : EcsEntity , change : comp ::PoiseChange ) {
2020-10-31 22:34:08 +00:00
let ecs = & server . state . ecs ( ) ;
2020-12-17 00:14:14 +00:00
if let Some ( character_state ) = ecs . read_storage ::< CharacterState > ( ) . get ( entity ) {
2022-06-18 01:13:45 +00:00
// Entity is invincible to poise change during stunned character state
if ! matches! ( character_state , CharacterState ::Stunned ( _ ) ) {
if let Some ( mut poise ) = ecs . write_storage ::< Poise > ( ) . get_mut ( entity ) {
poise . change ( change ) ;
}
2020-12-17 00:14:14 +00:00
}
2020-02-16 20:04:06 +00:00
}
}
2020-12-16 23:30:33 +00:00
2021-09-09 04:07:17 +00:00
pub fn handle_health_change ( server : & Server , entity : EcsEntity , change : HealthChange ) {
2020-12-06 02:29:46 +00:00
let ecs = & server . state . ecs ( ) ;
2022-06-15 14:14:50 +00:00
if let Some ( mut health ) = ecs . write_storage ::< Health > ( ) . get_mut ( entity ) {
2022-06-14 21:11:58 +00:00
// If the change amount was not zero
2022-06-15 14:14:50 +00:00
let changed = health . change_by ( change ) ;
if let ( Some ( pos ) , Some ( uid ) ) = (
ecs . read_storage ::< Pos > ( ) . get ( entity ) ,
ecs . read_storage ::< Uid > ( ) . get ( entity ) ,
) {
if changed {
let outcomes = ecs . write_resource ::< EventBus < Outcome > > ( ) ;
outcomes . emit_now ( Outcome ::HealthChange {
pos : pos . 0 ,
info : HealthChangeInfo {
amount : change . amount ,
by : change . by ,
target : * uid ,
2022-07-12 21:01:47 +00:00
cause : change . cause ,
2022-06-15 14:14:50 +00:00
crit : change . crit ,
instance : change . instance ,
} ,
} ) ;
}
2022-02-04 19:02:53 +00:00
}
}
2021-06-17 05:49:09 +00:00
// This if statement filters out anything under 5 damage, for DOT ticks
// TODO: Find a better way to separate direct damage from DOT here
2021-09-10 19:20:14 +00:00
let damage = - change . amount ;
2022-03-23 06:50:38 +00:00
if damage > 5.0 {
2021-06-17 05:49:09 +00:00
if let Some ( agent ) = ecs . write_storage ::< Agent > ( ) . get_mut ( entity ) {
2022-08-06 11:05:36 +00:00
agent . inbox . push_back ( AgentEvent ::Hurt ) ;
2021-06-17 05:49:09 +00:00
}
}
2020-12-06 02:29:46 +00:00
}
2020-02-16 20:04:06 +00:00
2020-09-19 16:55:31 +00:00
pub fn handle_knockback ( server : & Server , entity : EcsEntity , impulse : Vec3 < f32 > ) {
2020-11-07 18:00:07 +00:00
let ecs = & server . state . ecs ( ) ;
let clients = ecs . read_storage ::< Client > ( ) ;
if let Some ( physics ) = ecs . read_storage ::< PhysicsState > ( ) . get ( entity ) {
//Check if the entity is on a surface. If it is not, reduce knockback.
2020-11-15 17:13:03 +00:00
let mut impulse = impulse
2020-11-07 18:00:07 +00:00
* if physics . on_surface ( ) . is_some ( ) {
1.0
} else {
0.4
} ;
2020-11-15 17:13:03 +00:00
if let Some ( mass ) = ecs . read_storage ::< comp ::Mass > ( ) . get ( entity ) {
2021-05-22 17:56:13 +00:00
// we go easy on the little ones (because they fly so far)
impulse / = mass . 0. max ( 40.0 ) ;
2020-11-15 17:13:03 +00:00
}
2020-11-07 18:00:07 +00:00
let mut velocities = ecs . write_storage ::< comp ::Vel > ( ) ;
if let Some ( vel ) = velocities . get_mut ( entity ) {
2020-12-19 02:05:39 +00:00
vel . 0 + = impulse ;
2020-11-07 18:00:07 +00:00
}
if let Some ( client ) = clients . get ( entity ) {
client . send_fallible ( ServerGeneral ::Knockback ( impulse ) ) ;
}
2020-08-23 20:10:58 +00:00
}
}
2020-07-03 17:33:37 +00:00
/// Handle an entity dying. If it is a player, it will send a message to all
/// other players. If the entity that killed it had stats, then give it exp for
/// the kill. Experience given is equal to the level of the entity that was
/// killed times 10.
2021-09-09 04:07:17 +00:00
pub fn handle_destroy ( server : & mut Server , entity : EcsEntity , last_change : HealthChange ) {
2020-02-16 20:04:06 +00:00
let state = server . state_mut ( ) ;
2020-09-05 16:59:14 +00:00
// TODO: Investigate duplicate `Destroy` events (but don't remove this).
// If the entity was already deleted, it can't be destroyed again.
if ! state . ecs ( ) . is_alive ( entity ) {
return ;
}
2020-02-16 20:04:06 +00:00
2020-11-16 02:26:54 +00:00
let get_attacker_name = | cause_of_death : KillType , by : Uid | -> KillSource {
// Get attacker entity
if let Some ( char_entity ) = state . ecs ( ) . entity_from_uid ( by . into ( ) ) {
// Check if attacker is another player or entity with stats (npc)
if state
. ecs ( )
. read_storage ::< Player > ( )
. get ( char_entity )
. is_some ( )
{
KillSource ::Player ( by , cause_of_death )
} else if let Some ( stats ) = state . ecs ( ) . read_storage ::< Stats > ( ) . get ( char_entity ) {
KillSource ::NonPlayer ( stats . name . clone ( ) , cause_of_death )
} else {
2021-01-18 05:46:53 +00:00
KillSource ::NonExistent ( cause_of_death )
2020-11-16 02:26:54 +00:00
}
} else {
2021-01-18 05:46:53 +00:00
KillSource ::NonExistent ( cause_of_death )
2020-11-16 02:26:54 +00:00
}
} ;
2021-06-29 11:20:26 +00:00
// Push an outcome if entity is has a character state (entities that don't have
// one, we probably don't care about emitting death outcome)
2021-06-27 21:45:07 +00:00
if state
. ecs ( )
2022-07-15 12:08:04 +00:00
. read_storage ::< CharacterState > ( )
2021-06-27 21:45:07 +00:00
. get ( entity )
2021-06-29 11:20:26 +00:00
. is_some ( )
2021-06-27 21:45:07 +00:00
{
if let Some ( pos ) = state . ecs ( ) . read_storage ::< Pos > ( ) . get ( entity ) {
state
. ecs ( )
2022-05-09 19:58:13 +00:00
. read_resource ::< EventBus < Outcome > > ( )
. emit_now ( Outcome ::Death { pos : pos . 0 } ) ;
2021-06-27 21:45:07 +00:00
}
}
2020-02-16 20:04:06 +00:00
// Chat message
2020-09-06 19:42:32 +00:00
// If it was a player that died
if let Some ( _player ) = state . ecs ( ) . read_storage ::< Player > ( ) . get ( entity ) {
2021-04-18 20:46:16 +00:00
if let Some ( uid ) = state . ecs ( ) . read_storage ::< Uid > ( ) . get ( entity ) {
2021-11-13 20:46:45 +00:00
let kill_source = match ( last_change . cause , last_change . by . map ( | x | x . uid ( ) ) ) {
2021-09-09 04:07:17 +00:00
( Some ( DamageSource ::Melee ) , Some ( by ) ) = > get_attacker_name ( KillType ::Melee , by ) ,
( Some ( DamageSource ::Projectile ) , Some ( by ) ) = > {
2020-11-16 02:26:54 +00:00
get_attacker_name ( KillType ::Projectile , by )
2020-09-09 20:26:20 +00:00
} ,
2021-09-09 04:07:17 +00:00
( Some ( DamageSource ::Explosion ) , Some ( by ) ) = > {
get_attacker_name ( KillType ::Explosion , by )
} ,
( Some ( DamageSource ::Energy ) , Some ( by ) ) = > get_attacker_name ( KillType ::Energy , by ) ,
( Some ( DamageSource ::Buff ( buff_kind ) ) , Some ( by ) ) = > {
get_attacker_name ( KillType ::Buff ( buff_kind ) , by )
} ,
( Some ( DamageSource ::Other ) , Some ( by ) ) = > get_attacker_name ( KillType ::Other , by ) ,
( Some ( DamageSource ::Falling ) , _ ) = > KillSource ::FallDamage ,
// HealthSource::Suicide => KillSource::Suicide,
_ = > KillSource ::Other ,
2020-09-06 19:42:32 +00:00
} ;
2021-04-18 20:46:16 +00:00
2023-04-11 14:46:36 +00:00
state . send_chat ( comp ::ChatType ::Kill ( kill_source , * uid ) . into_plain_msg ( " " ) ) ;
2020-02-16 20:04:06 +00:00
}
}
2022-06-04 17:16:12 +00:00
let mut exp_awards = Vec ::< ( Entity , f32 , Option < Group > ) > ::new ( ) ;
2021-11-13 20:46:45 +00:00
// Award EXP to damage contributors
//
// NOTE: Debug logging is disabled by default for this module - to enable it add
// veloren_server::events::entity_manipulation=debug to RUST_LOG
2020-07-31 04:30:17 +00:00
( | | {
2021-11-13 20:46:45 +00:00
let mut skill_sets = state . ecs ( ) . write_storage ::< SkillSet > ( ) ;
2021-01-06 23:10:08 +00:00
let healths = state . ecs ( ) . read_storage ::< Health > ( ) ;
2021-08-16 13:12:22 +00:00
let energies = state . ecs ( ) . read_storage ::< Energy > ( ) ;
2021-01-07 05:23:24 +00:00
let inventories = state . ecs ( ) . read_storage ::< Inventory > ( ) ;
2021-01-19 20:09:21 +00:00
let players = state . ecs ( ) . read_storage ::< Player > ( ) ;
2021-01-22 21:12:16 +00:00
let bodies = state . ecs ( ) . read_storage ::< Body > ( ) ;
2022-07-15 12:08:04 +00:00
let poises = state . ecs ( ) . read_storage ::< Poise > ( ) ;
2021-11-13 20:46:45 +00:00
let positions = state . ecs ( ) . read_storage ::< Pos > ( ) ;
let groups = state . ecs ( ) . read_storage ::< Group > ( ) ;
2021-09-26 14:52:16 +00:00
2021-09-17 05:45:30 +00:00
let (
entity_skill_set ,
entity_health ,
entity_energy ,
entity_inventory ,
entity_body ,
entity_poise ,
2021-11-13 20:46:45 +00:00
entity_pos ,
2021-09-26 14:52:16 +00:00
) = match ( | | {
Some ( (
2021-11-13 20:46:45 +00:00
skill_sets . get ( entity ) ? ,
2021-09-26 14:52:16 +00:00
healths . get ( entity ) ? ,
energies . get ( entity ) ? ,
inventories . get ( entity ) ? ,
bodies . get ( entity ) ? ,
poises . get ( entity ) ? ,
2021-11-13 20:46:45 +00:00
positions . get ( entity ) ? ,
2021-09-26 14:52:16 +00:00
) )
} ) ( ) {
Some ( comps ) = > comps ,
None = > return ,
2021-09-17 05:45:30 +00:00
} ;
2020-07-12 04:04:13 +00:00
2021-11-13 20:46:45 +00:00
// Calculate the total EXP award for the kill
2021-02-23 20:29:27 +00:00
let msm = state . ecs ( ) . read_resource ::< MaterialStatManifest > ( ) ;
2021-11-13 20:46:45 +00:00
let exp_reward = combat ::combat_rating (
2021-02-23 20:29:27 +00:00
entity_inventory ,
entity_health ,
2021-08-16 13:12:22 +00:00
entity_energy ,
2021-09-17 05:45:30 +00:00
entity_poise ,
2021-04-14 15:35:34 +00:00
entity_skill_set ,
2021-02-23 20:29:27 +00:00
* entity_body ,
& msm ,
2021-09-17 05:45:30 +00:00
) * 20.0 ;
2020-07-31 04:30:17 +00:00
2021-11-13 20:46:45 +00:00
let mut damage_contributors = HashMap ::< DamageContrib , ( u64 , f32 ) > ::new ( ) ;
for ( damage_contributor , damage ) in entity_health . damage_contributions ( ) {
match damage_contributor {
DamageContributor ::Solo ( uid ) = > {
if let Some ( attacker ) = state . ecs ( ) . entity_from_uid ( uid . 0 ) {
damage_contributors . insert ( DamageContrib ::Solo ( attacker ) , ( * damage , 0.0 ) ) ;
} else {
// An entity who was not in a group contributed damage but is now either
// dead or offline. Add a placeholder to ensure that the contributor's exp
// is discarded, not distributed between the other contributors
damage_contributors . insert ( DamageContrib ::NotFound , ( * damage , 0.0 ) ) ;
}
} ,
DamageContributor ::Group {
entity_uid : _ ,
group ,
} = > {
// Damage made by entities who were in a group at the time of attack is
// attributed to their group rather than themselves. This allows for all
// members of a group to receive EXP, not just the damage dealers.
let entry = damage_contributors
. entry ( DamageContrib ::Group ( * group ) )
. or_insert ( ( 0 , 0.0 ) ) ;
2022-09-08 19:51:02 +00:00
entry . 0 + = damage ;
2021-11-13 20:46:45 +00:00
} ,
}
}
// Calculate the percentage of total damage that each DamageContributor
// contributed
let total_damage : f64 = damage_contributors
. values ( )
. map ( | ( damage , _ ) | * damage as f64 )
. sum ( ) ;
damage_contributors
. iter_mut ( )
. for_each ( | ( _ , ( damage , percentage ) ) | {
* percentage = ( * damage as f64 / total_damage ) as f32
} ) ;
2020-08-07 03:53:19 +00:00
let alignments = state . ecs ( ) . read_storage ::< Alignment > ( ) ;
let uids = state . ecs ( ) . read_storage ::< Uid > ( ) ;
2022-05-09 19:58:13 +00:00
let mut outcomes = state . ecs ( ) . write_resource ::< EventBus < Outcome > > ( ) ;
2022-07-15 12:08:04 +00:00
let inventories = state . ecs ( ) . read_storage ::< Inventory > ( ) ;
2020-08-07 03:53:19 +00:00
2021-11-13 20:46:45 +00:00
let destroyed_group = groups . get ( entity ) ;
2020-07-31 04:30:17 +00:00
2021-11-13 20:46:45 +00:00
let within_range = | attacker_pos : & Pos | {
// Maximum distance that an attacker must be from an entity at the time of its
// death to receive EXP for the kill
const MAX_EXP_DIST : f32 = 150.0 ;
entity_pos . 0. distance_squared ( attacker_pos . 0 ) < MAX_EXP_DIST . powi ( 2 )
} ;
let is_pvp_kill =
| attacker : Entity | players . get ( entity ) . is_some ( ) & & players . get ( attacker ) . is_some ( ) ;
// Iterate through all contributors of damage for the killed entity, calculating
// how much EXP each contributor should be awarded based on their
// percentage of damage contribution
2022-05-28 12:06:49 +00:00
exp_awards = damage_contributors . iter ( ) . filter_map ( | ( damage_contributor , ( _ , damage_percent ) ) | {
2021-11-13 20:46:45 +00:00
let contributor_exp = exp_reward * damage_percent ;
match damage_contributor {
DamageContrib ::Solo ( attacker ) = > {
2021-11-15 01:11:58 +00:00
// No exp for self kills or PvP
2021-11-13 20:46:45 +00:00
if * attacker = = entity | | is_pvp_kill ( * attacker ) { return None ; }
// Only give EXP to the attacker if they are within EXP range of the killed entity
positions . get ( * attacker ) . and_then ( | attacker_pos | {
if within_range ( attacker_pos ) {
debug! ( " Awarding {} exp to individual {:?} who contributed {}% damage to the kill of {:?} " , contributor_exp , attacker , * damage_percent * 100.0 , entity ) ;
2022-06-04 17:16:12 +00:00
Some ( iter ::once ( ( * attacker , contributor_exp , None ) ) . collect ( ) )
2021-11-13 20:46:45 +00:00
} else {
None
}
} )
} ,
DamageContrib ::Group ( group ) = > {
// Don't give EXP to members in the destroyed entity's group
if destroyed_group = = Some ( group ) { return None ; }
// Only give EXP to members of the group that are within EXP range of the killed entity and aren't a pet
let members_in_range = (
& state . ecs ( ) . entities ( ) ,
& groups ,
& positions ,
alignments . maybe ( ) ,
& uids ,
)
. join ( )
. filter_map ( | ( member_entity , member_group , member_pos , alignment , uid ) | {
if * member_group = = * group & & within_range ( member_pos ) & & ! is_pvp_kill ( member_entity ) & & ! matches! ( alignment , Some ( Alignment ::Owned ( owner ) ) if owner ! = uid ) {
Some ( member_entity )
} else {
None
}
} )
. collect ::< Vec < _ > > ( ) ;
if members_in_range . is_empty ( ) { return None ; }
// Divide EXP reward by square root of number of people in group for group EXP scaling
let exp_per_member = contributor_exp / ( members_in_range . len ( ) as f32 ) . sqrt ( ) ;
debug! ( " Awarding {} exp per member of group ID {:?} with {} members which contributed {}% damage to the kill of {:?} " , exp_per_member , group , members_in_range . len ( ) , * damage_percent * 100.0 , entity ) ;
2022-06-04 17:16:12 +00:00
Some ( members_in_range . into_iter ( ) . map ( | entity | ( entity , exp_per_member , Some ( * group ) ) ) . collect ::< Vec < ( Entity , f32 , Option < Group > ) > > ( ) )
2021-11-13 20:46:45 +00:00
} ,
DamageContrib ::NotFound = > {
// Discard exp for dead/offline individual damage contributors
None
}
}
2022-06-04 17:16:12 +00:00
} ) . flatten ( ) . collect ::< Vec < ( Entity , f32 , Option < Group > ) > > ( ) ;
2022-05-28 12:06:49 +00:00
2022-06-04 17:16:12 +00:00
exp_awards . iter ( ) . for_each ( | ( attacker , exp_reward , _ ) | {
2021-11-13 20:46:45 +00:00
// Process the calculated EXP rewards
2022-03-15 18:56:18 +00:00
if let ( Some ( mut attacker_skill_set ) , Some ( attacker_uid ) , Some ( attacker_inventory ) ) = (
2022-05-28 12:06:49 +00:00
skill_sets . get_mut ( * attacker ) ,
uids . get ( * attacker ) ,
inventories . get ( * attacker ) ,
2021-11-13 20:46:45 +00:00
) {
handle_exp_gain (
2022-05-28 12:06:49 +00:00
* exp_reward ,
2021-11-13 20:46:45 +00:00
attacker_inventory ,
& mut attacker_skill_set ,
attacker_uid ,
& mut outcomes ,
) ;
}
} ) ;
2020-07-31 04:30:17 +00:00
} ) ( ) ;
2020-02-16 20:04:06 +00:00
2020-11-11 23:59:09 +00:00
let should_delete = if state
2020-02-16 20:04:06 +00:00
. ecs ( )
. write_storage ::< Client > ( )
. get_mut ( entity )
. is_some ( )
{
state
. ecs ( )
. write_storage ( )
. insert ( entity , comp ::Vel ( Vec3 ::zero ( ) ) )
. err ( )
2020-06-21 21:47:49 +00:00
. map ( | e | error! ( ? e , ? entity , " Failed to set zero vel on dead client " ) ) ;
2020-02-16 20:04:06 +00:00
state
. ecs ( )
2022-07-31 21:01:10 +00:00
. write_storage ::< comp ::ForceUpdate > ( )
. get_mut ( entity )
. map ( | force_update | force_update . update ( ) ) ;
2020-02-16 20:04:06 +00:00
state
. ecs ( )
2022-07-15 12:08:04 +00:00
. write_storage ::< Energy > ( )
2020-02-16 20:04:06 +00:00
. get_mut ( entity )
2021-01-07 20:25:12 +00:00
. map ( | mut energy | {
let energy = & mut * energy ;
2021-09-14 02:16:01 +00:00
energy . refresh ( )
2021-01-07 20:25:12 +00:00
} ) ;
2020-02-16 20:04:06 +00:00
let _ = state
. ecs ( )
2022-07-15 12:08:04 +00:00
. write_storage ::< CharacterState > ( )
. insert ( entity , CharacterState ::default ( ) ) ;
2020-11-11 23:59:09 +00:00
false
2022-07-15 12:08:04 +00:00
} else if state . ecs ( ) . read_storage ::< Agent > ( ) . contains ( entity )
2021-03-23 22:41:17 +00:00
& & ! matches! (
state . ecs ( ) . read_storage ::< comp ::Alignment > ( ) . get ( entity ) ,
Some ( comp ::Alignment ::Owned ( _ ) )
)
{
2021-09-27 20:21:57 +00:00
// Only drop loot if entity has agency (not a player),
// and if it is not owned by another entity (not a pet)
2020-09-04 18:01:38 +00:00
2020-08-18 21:32:34 +00:00
// Decide for a loot drop before turning into a lootbag
2020-05-15 15:05:50 +00:00
2023-04-23 19:17:39 +00:00
let items = {
let mut item_drops = state . ecs ( ) . write_storage ::< comp ::ItemDrops > ( ) ;
item_drops . remove ( entity ) . map ( | comp ::ItemDrops ( item ) | item )
2021-09-22 02:25:14 +00:00
} ;
2023-04-23 19:17:39 +00:00
if let Some ( items ) = items {
2022-07-15 12:08:04 +00:00
let pos = state . ecs ( ) . read_storage ::< Pos > ( ) . get ( entity ) . cloned ( ) ;
2021-09-01 23:17:36 +00:00
let vel = state . ecs ( ) . read_storage ::< comp ::Vel > ( ) . get ( entity ) . cloned ( ) ;
if let Some ( pos ) = pos {
2022-06-04 17:16:12 +00:00
// Remove entries where zero exp was awarded - this happens because some
// entities like Object bodies don't give EXP.
2023-04-23 19:17:39 +00:00
let mut item_receivers = HashMap ::new ( ) ;
for ( entity , exp , group ) in exp_awards {
if exp > = f32 ::EPSILON {
let loot_owner = if let Some ( group ) = group {
Some ( LootOwnerKind ::Group ( group ) )
} else {
let uid = state
. ecs ( )
. read_storage ::< Body > ( )
. get ( entity )
. and_then ( | body | {
// Only humanoids are awarded loot ownership - if the winner
2023-04-25 09:33:44 +00:00
// was a non-humanoid NPC the loot will be free-for-all
2023-04-23 19:17:39 +00:00
if matches! ( body , Body ::Humanoid ( _ ) ) {
Some ( state . ecs ( ) . read_storage ::< Uid > ( ) . get ( entity ) . cloned ( ) )
} else {
None
}
} )
. flatten ( ) ;
uid . map ( LootOwnerKind ::Player )
} ;
2022-05-31 12:37:56 +00:00
2023-04-23 19:17:39 +00:00
* item_receivers . entry ( loot_owner ) . or_insert ( 0.0 ) + = exp ;
}
}
2022-05-28 12:06:49 +00:00
2023-04-25 09:33:44 +00:00
let mut item_offset_spiral =
Spiral2d ::new ( ) . map ( | offset | offset . as_ ::< f32 > ( ) * 0.5 ) ;
let mut spawn_item = | item , loot_owner | {
let offset = item_offset_spiral . next ( ) . unwrap_or_default ( ) ;
2023-05-04 21:12:25 +00:00
state . create_item_drop (
Pos ( pos . 0 + Vec3 ::unit_z ( ) * 0.25 + offset ) ,
vel . unwrap_or ( comp ::Vel ( Vec3 ::zero ( ) ) ) ,
item ,
if let Some ( loot_owner ) = loot_owner {
debug! ( " Assigned UID {loot_owner:?} as the winner for the loot drop " ) ;
Some ( LootOwner ::new ( loot_owner ) )
} else {
None
} ,
) ;
2023-04-25 09:33:44 +00:00
} ;
2022-06-04 17:16:12 +00:00
2023-04-25 09:33:44 +00:00
let msm = & MaterialStatManifest ::load ( ) . read ( ) ;
let ability_map = & AbilityMap ::load ( ) . read ( ) ;
if item_receivers . is_empty ( ) {
for item in flatten_counted_items ( & items , ability_map , msm ) {
spawn_item ( item , None )
}
} else {
2023-04-23 19:17:39 +00:00
let mut rng = rand ::thread_rng ( ) ;
distribute_many (
item_receivers
. iter ( )
. map ( | ( loot_owner , weight ) | ( * weight , * loot_owner ) ) ,
& mut rng ,
& items ,
| ( amount , _ ) | * amount ,
| ( _ , item ) , loot_owner , count | {
for item in item . stacked_duplicates ( ability_map , msm , count ) {
2023-04-25 09:33:44 +00:00
spawn_item ( item , loot_owner )
2023-04-23 19:17:39 +00:00
}
} ,
) ;
2022-05-28 12:06:49 +00:00
}
2021-09-01 23:17:36 +00:00
} else {
error! (
? entity ,
" Entity doesn't have a position, no bag is being dropped "
)
}
2020-09-04 18:01:38 +00:00
}
2020-11-11 23:59:09 +00:00
true
2020-06-11 17:06:11 +00:00
} else {
2020-11-11 23:59:09 +00:00
true
} ;
2023-05-15 22:40:53 +00:00
let resists_durability = state
. ecs ( )
. read_storage ::< Pos > ( )
. get ( entity )
. cloned ( )
. map_or ( false , | our_pos | {
let areas_container = state
. ecs ( )
. read_resource ::< AreasContainer < NoDurabilityArea > > ( ) ;
let our_pos = our_pos . 0. map ( | i | i as i32 ) ;
let is_in_area = areas_container
. areas ( )
. iter ( )
. any ( | ( _ , area ) | area . contains_point ( our_pos ) ) ;
is_in_area
} ) ;
2023-04-22 15:37:42 +00:00
// TODO: Do we need to do this if `should_delete` is true?
2022-06-06 22:45:50 +00:00
// Modify durability on all equipped items
2023-05-15 22:40:53 +00:00
if ! resists_durability & & let Some ( mut inventory ) = state . ecs ( ) . write_storage ::< Inventory > ( ) . get_mut ( entity ) {
2023-04-16 17:54:38 +00:00
let ecs = state . ecs ( ) ;
let ability_map = ecs . read_resource ::< AbilityMap > ( ) ;
let msm = ecs . read_resource ::< MaterialStatManifest > ( ) ;
let time = ecs . read_resource ::< Time > ( ) ;
inventory . damage_items ( & ability_map , & msm , * time ) ;
2022-06-06 22:45:50 +00:00
}
2023-04-13 13:34:31 +00:00
if let Some ( actor ) = state . entity_as_actor ( entity ) {
state
. ecs ( )
. write_resource ::< rtsim ::RtSim > ( )
. hook_rtsim_actor_death (
2023-04-05 22:24:16 +00:00
& state . ecs ( ) . read_resource ::< Arc < world ::World > > ( ) ,
state
. ecs ( )
. read_resource ::< world ::IndexOwned > ( )
. as_index_ref ( ) ,
actor ,
2023-04-06 17:25:33 +00:00
state . ecs ( ) . read_storage ::< Pos > ( ) . get ( entity ) . map ( | p | p . 0 ) ,
2023-04-05 22:24:16 +00:00
last_change
. by
. as_ref ( )
. and_then (
| ( DamageContributor ::Solo ( entity_uid )
| DamageContributor ::Group { entity_uid , .. } ) | {
state
. ecs ( )
. read_resource ::< UidAllocator > ( )
. retrieve_entity_internal ( ( * entity_uid ) . into ( ) )
} ,
)
. and_then ( | killer | state . entity_as_actor ( killer ) ) ,
) ;
2023-04-13 13:34:31 +00:00
}
2020-11-11 23:59:09 +00:00
2023-04-13 13:34:31 +00:00
if should_delete {
2022-08-20 21:19:33 +00:00
if let Err ( e ) = state . delete_entity_recorded ( entity ) {
error! ( ? e , ? entity , " Failed to delete destroyed entity " ) ;
}
2020-02-16 20:04:06 +00:00
}
}
2020-11-11 11:42:22 +00:00
/// Delete an entity without any special actions (this is generally used for
/// temporarily unloading an entity when it leaves the view distance). As much
/// as possible, this function should simply make an entity cease to exist.
pub fn handle_delete ( server : & mut Server , entity : EcsEntity ) {
let _ = server
. state_mut ( )
. delete_entity_recorded ( entity )
. map_err ( | e | error! ( ? e , ? entity , " Failed to delete destroyed entity " ) ) ;
}
2023-05-25 00:21:44 +00:00
pub fn handle_land_on_ground (
server : & Server ,
entity : EcsEntity ,
vel : Vec3 < f32 > ,
surface_normal : Vec3 < f32 > ,
) {
2021-05-15 19:36:27 +00:00
let ecs = server . state . ecs ( ) ;
2023-05-25 00:21:44 +00:00
let relative_vel = vel . dot ( - surface_normal ) ;
2023-04-04 21:25:11 +00:00
// The second part of this if statement disables all fall damage when in the
// water. This was added as a *temporary* fix a bug that causes you to take
// fall damage while swimming downwards. FIXME: Fix the actual bug and
// remove the following relevant part of the if statement.
2023-05-25 00:21:44 +00:00
if relative_vel > = 30.0
2023-04-04 21:25:11 +00:00
& & ecs
. read_storage ::< PhysicsState > ( )
. get ( entity )
. map_or ( true , | ps | ps . in_liquid ( ) . is_none ( ) )
2023-04-04 21:16:00 +00:00
{
2022-10-24 01:12:59 +00:00
let char_states = ecs . read_storage ::< CharacterState > ( ) ;
2023-05-25 00:21:44 +00:00
let reduced_vel = if let Some ( CharacterState ::DiveMelee ( c ) ) = char_states . get ( entity ) {
( relative_vel + c . static_data . vertical_speed ) . min ( 0.0 )
2022-10-24 01:12:59 +00:00
} else {
2023-05-25 00:21:44 +00:00
relative_vel
2022-10-24 01:12:59 +00:00
} ;
2021-05-15 19:36:27 +00:00
let mass = ecs
2021-03-23 09:51:53 +00:00
. read_storage ::< comp ::Mass > ( )
. get ( entity )
. copied ( )
. unwrap_or_default ( ) ;
2023-05-25 00:21:44 +00:00
let impact_energy = mass . 0 * reduced_vel . powi ( 2 ) / 2.0 ;
2021-09-14 10:53:01 +00:00
let falldmg = impact_energy / 1000.0 ;
2021-05-15 19:36:27 +00:00
let inventories = ecs . read_storage ::< Inventory > ( ) ;
let stats = ecs . read_storage ::< Stats > ( ) ;
2022-05-28 23:41:31 +00:00
let time = ecs . read_resource ::< Time > ( ) ;
let msm = ecs . read_resource ::< MaterialStatManifest > ( ) ;
2022-07-12 21:01:47 +00:00
let server_eventbus = ecs . read_resource ::< EventBus < ServerEvent > > ( ) ;
2021-05-15 19:36:27 +00:00
2022-07-12 21:01:47 +00:00
// Emit health change
let damage = Damage {
source : DamageSource ::Falling ,
kind : DamageKind ::Crushing ,
value : falldmg ,
} ;
let damage_reduction = Damage ::compute_damage_reduction (
Some ( damage ) ,
inventories . get ( entity ) ,
stats . get ( entity ) ,
& msm ,
) ;
let change = damage . calculate_health_change (
damage_reduction ,
None ,
false ,
0.0 ,
1.0 ,
* time ,
rand ::random ( ) ,
) ;
server_eventbus . emit_now ( ServerEvent ::HealthChange { entity , change } ) ;
// Emit poise change
2023-05-25 00:21:44 +00:00
let poise_damage = - ( mass . 0 * reduced_vel . powi ( 2 ) / 1500.0 ) ;
2022-03-05 20:51:41 +00:00
let poise_change = Poise ::apply_poise_reduction (
poise_damage ,
inventories . get ( entity ) ,
& msm ,
char_states . get ( entity ) ,
stats . get ( entity ) ,
) ;
2022-07-12 21:01:47 +00:00
let poise_change = comp ::PoiseChange {
amount : poise_change ,
impulse : Vec3 ::unit_z ( ) ,
by : None ,
cause : None ,
time : * time ,
} ;
server_eventbus . emit_now ( ServerEvent ::PoiseChange {
entity ,
change : poise_change ,
} ) ;
2020-02-16 20:04:06 +00:00
}
}
pub fn handle_respawn ( server : & Server , entity : EcsEntity ) {
let state = & server . state ;
// Only clients can respawn
if state
. ecs ( )
. write_storage ::< Client > ( )
. get_mut ( entity )
. is_some ( )
{
let respawn_point = state
2020-08-23 20:29:40 +00:00
. read_component_copied ::< comp ::Waypoint > ( entity )
2020-02-16 20:04:06 +00:00
. map ( | wp | wp . get_pos ( ) )
. unwrap_or ( state . ecs ( ) . read_resource ::< SpawnPoint > ( ) . 0 ) ;
state
. ecs ( )
2022-07-15 12:08:04 +00:00
. write_storage ::< Health > ( )
2020-02-16 20:04:06 +00:00
. get_mut ( entity )
2021-01-07 20:25:12 +00:00
. map ( | mut health | health . revive ( ) ) ;
2021-07-04 15:14:40 +00:00
state
. ecs ( )
. write_storage ::< comp ::Combo > ( )
. get_mut ( entity )
. map ( | mut combo | combo . reset ( ) ) ;
2020-02-16 20:04:06 +00:00
state
. ecs ( )
2022-07-15 12:08:04 +00:00
. write_storage ::< Pos > ( )
2020-02-16 20:04:06 +00:00
. get_mut ( entity )
. map ( | pos | pos . 0 = respawn_point ) ;
state
. ecs ( )
2022-07-31 21:01:10 +00:00
. write_storage ::< comp ::PhysicsState > ( )
. get_mut ( entity )
. map ( | phys_state | phys_state . reset ( ) ) ;
state
. ecs ( )
. write_storage ::< comp ::ForceUpdate > ( )
. get_mut ( entity )
. map ( | force_update | force_update . update ( ) ) ;
2020-02-16 20:04:06 +00:00
}
}
2021-02-20 20:38:27 +00:00
pub fn handle_explosion ( server : & Server , pos : Vec3 < f32 > , explosion : Explosion , owner : Option < Uid > ) {
2020-08-08 12:53:07 +00:00
// Go through all other entities
2020-03-22 19:39:50 +00:00
let ecs = & server . state . ecs ( ) ;
2022-06-14 13:24:08 +00:00
let settings = server . settings ( ) ;
2021-05-15 19:36:27 +00:00
let server_eventbus = ecs . read_resource ::< EventBus < ServerEvent > > ( ) ;
let time = ecs . read_resource ::< Time > ( ) ;
2021-07-14 07:40:43 +00:00
let owner_entity = owner . and_then ( | uid | {
ecs . read_resource ::< UidAllocator > ( )
. retrieve_entity_internal ( uid . into ( ) )
} ) ;
2021-05-15 19:36:27 +00:00
2022-03-08 00:58:44 +00:00
let explosion_volume = 6.25 * explosion . radius ;
2021-11-03 13:26:34 +00:00
let mut emitter = server_eventbus . emitter ( ) ;
emitter . emit ( ServerEvent ::Sound {
2021-05-15 19:36:27 +00:00
sound : Sound ::new ( SoundKind ::Explosion , pos , explosion_volume , time . 0 ) ,
} ) ;
2020-08-07 04:33:24 +00:00
2020-07-31 17:16:20 +00:00
// Add an outcome
2021-02-16 05:18:05 +00:00
// Uses radius as outcome power for now
let outcome_power = explosion . radius ;
2022-05-09 19:58:13 +00:00
let outcomes = ecs . read_resource ::< EventBus < Outcome > > ( ) ;
let mut outcomes_emitter = outcomes . emitter ( ) ;
outcomes_emitter . emit ( Outcome ::Explosion {
2021-04-04 03:04:02 +00:00
pos ,
power : outcome_power ,
radius : explosion . radius ,
is_attack : explosion
. effects
. iter ( )
. any ( | e | matches! ( e , RadiusEffect ::Attack ( _ ) ) ) ,
reagent : explosion . reagent ,
} ) ;
2022-07-15 12:08:04 +00:00
let groups = ecs . read_storage ::< Group > ( ) ;
2020-08-08 12:53:07 +00:00
2021-03-15 03:45:55 +00:00
// Used to get strength of explosion effects as they falloff over distance
fn cylinder_sphere_strength (
sphere_pos : Vec3 < f32 > ,
radius : f32 ,
2021-10-27 19:42:11 +00:00
min_falloff : f32 ,
2021-03-15 03:45:55 +00:00
cyl_pos : Vec3 < f32 > ,
cyl_body : Body ,
) -> f32 {
// 2d check
2021-09-16 10:42:07 +00:00
let horiz_dist = Vec2 ::< f32 > ::from ( sphere_pos - cyl_pos ) . distance ( Vec2 ::default ( ) )
- cyl_body . max_radius ( ) ;
2021-03-15 03:45:55 +00:00
// z check
let half_body_height = cyl_body . height ( ) / 2.0 ;
let vert_distance =
( sphere_pos . z - ( cyl_pos . z + half_body_height ) ) . abs ( ) - half_body_height ;
2021-10-27 19:55:25 +00:00
// Use whichever gives maximum distance as that closer to real value. Sets
// minimum to 0 as negative values would indicate inside entity.
let distance = horiz_dist . max ( vert_distance ) . max ( 0.0 ) ;
if distance > radius {
// If further than exploion radius, no strength
0.0
} else {
// Falloff inversely proportional to radius
2021-10-29 21:12:57 +00:00
let fall_off = ( ( distance / radius ) . min ( 1.0 ) - 1.0 ) . abs ( ) ;
let min_falloff = min_falloff . clamp ( 0.0 , 1.0 ) ;
2021-10-27 19:55:25 +00:00
min_falloff + fall_off * ( 1.0 - min_falloff )
}
2021-03-15 03:45:55 +00:00
}
2021-08-10 16:53:39 +00:00
// TODO: Faster RNG?
let mut rng = rand ::thread_rng ( ) ;
2023-05-21 14:30:18 +00:00
// TODO: Process terrain destruction first so that entities don't get protected
// by terrain that gets destroyed?
2021-08-20 07:44:34 +00:00
' effects : for effect in explosion . effects {
2020-10-30 20:41:21 +00:00
match effect {
2023-02-15 00:10:37 +00:00
RadiusEffect ::TerrainDestruction ( power , new_color ) = > {
2020-10-30 20:41:21 +00:00
const RAYS : usize = 500 ;
2021-08-20 07:44:34 +00:00
let spatial_grid = ecs . read_resource ::< common ::CachedSpatialGrid > ( ) ;
let auras = ecs . read_storage ::< Auras > ( ) ;
let positions = ecs . read_storage ::< Pos > ( ) ;
// Prevent block colour changes within the radius of a safe zone aura
if spatial_grid
. 0
. in_circle_aabr ( pos . xy ( ) , SAFE_ZONE_RADIUS )
. filter_map ( | entity | {
auras
. get ( entity )
. and_then ( | entity_auras | {
positions . get ( entity ) . map ( | pos | ( entity_auras , pos ) )
} )
. and_then ( | ( entity_auras , pos ) | {
entity_auras
. auras
. iter ( )
. find ( | ( _ , aura ) | {
matches! ( aura . aura_kind , aura ::AuraKind ::Buff {
kind : BuffKind ::Invulnerability ,
source : BuffSource ::World ,
..
} )
} )
. map ( | ( _ , aura ) | ( * pos , aura . radius ) )
} )
} )
. any ( | ( aura_pos , aura_radius ) | {
pos . distance_squared ( aura_pos . 0 ) < aura_radius . powi ( 2 )
} )
{
continue 'effects ;
}
2020-10-30 20:41:21 +00:00
// Color terrain
let mut touched_blocks = Vec ::new ( ) ;
let color_range = power * 2.7 ;
for _ in 0 .. RAYS {
let dir = Vec3 ::new (
2021-08-10 16:53:39 +00:00
rng . gen ::< f32 > ( ) - 0.5 ,
rng . gen ::< f32 > ( ) - 0.5 ,
rng . gen ::< f32 > ( ) - 0.5 ,
2020-10-30 20:41:21 +00:00
)
. normalized ( ) ;
let _ = ecs
. read_resource ::< TerrainGrid > ( )
. ray ( pos , pos + dir * color_range )
2021-08-10 16:53:39 +00:00
. until ( | _ | rng . gen ::< f32 > ( ) < 0.05 )
2020-10-30 20:41:21 +00:00
. for_each ( | _ : & Block , pos | touched_blocks . push ( pos ) )
. cast ( ) ;
}
2020-02-16 20:04:06 +00:00
2020-10-30 20:41:21 +00:00
let terrain = ecs . read_resource ::< TerrainGrid > ( ) ;
let mut block_change = ecs . write_resource ::< BlockChange > ( ) ;
for block_pos in touched_blocks {
if let Ok ( block ) = terrain . get ( block_pos ) {
2022-06-14 13:24:08 +00:00
if ! matches! ( block . kind ( ) , BlockKind ::Lava | BlockKind ::GlowingRock )
& & settings . gameplay . explosion_burn_marks
{
2021-07-06 02:30:45 +00:00
let diff2 = block_pos . map ( | b | b as f32 ) . distance_squared ( pos ) ;
let fade = ( 1.0 - diff2 / color_range . powi ( 2 ) ) . max ( 0.0 ) ;
if let Some ( mut color ) = block . get_color ( ) {
let r = color [ 0 ] as f32
2023-02-15 00:10:37 +00:00
+ ( fade
* ( color [ 0 ] as f32 * 0.5 - color [ 0 ] as f32 + new_color [ 0 ] ) ) ;
2021-07-06 02:30:45 +00:00
let g = color [ 1 ] as f32
2023-02-15 00:10:37 +00:00
+ ( fade
* ( color [ 1 ] as f32 * 0.3 - color [ 1 ] as f32 + new_color [ 1 ] ) ) ;
2021-07-06 02:30:45 +00:00
let b = color [ 2 ] as f32
2023-02-15 00:10:37 +00:00
+ ( fade
* ( color [ 2 ] as f32 * 0.3 - color [ 2 ] as f32 + new_color [ 2 ] ) ) ;
2021-07-06 02:30:45 +00:00
// Darken blocks, but not too much
color [ 0 ] = ( r as u8 ) . max ( 30 ) ;
color [ 1 ] = ( g as u8 ) . max ( 30 ) ;
color [ 2 ] = ( b as u8 ) . max ( 30 ) ;
block_change . set ( block_pos , Block ::new ( block . kind ( ) , color ) ) ;
}
2020-10-30 20:41:21 +00:00
}
2021-11-03 13:26:34 +00:00
if block . is_bonkable ( ) {
emitter . emit ( ServerEvent ::Bonk {
pos : block_pos . map ( | e | e as f32 + 0.5 ) ,
owner ,
target : None ,
} ) ;
}
2020-10-30 20:41:21 +00:00
}
}
2020-08-28 21:43:33 +00:00
2020-10-30 20:41:21 +00:00
// Destroy terrain
for _ in 0 .. RAYS {
let dir = Vec3 ::new (
2021-08-10 16:53:39 +00:00
rng . gen ::< f32 > ( ) - 0.5 ,
rng . gen ::< f32 > ( ) - 0.5 ,
rng . gen ::< f32 > ( ) - 0.15 ,
2020-10-30 20:41:21 +00:00
)
. normalized ( ) ;
2020-11-19 00:23:13 +00:00
let mut ray_energy = power ;
2020-10-30 20:41:21 +00:00
let terrain = ecs . read_resource ::< TerrainGrid > ( ) ;
2021-08-10 16:53:39 +00:00
let from = pos ;
let to = pos + dir * power ;
2020-10-30 20:41:21 +00:00
let _ = terrain
2021-08-10 16:53:39 +00:00
. ray ( from , to )
2020-11-19 00:23:13 +00:00
. until ( | block : & Block | {
2021-08-10 16:53:39 +00:00
// Stop if:
// 1) Block is liquid
// 2) Consumed all energy
// 3) Can't explode block (for example we hit stone wall)
let stop = block . is_liquid ( )
| | block . explode_power ( ) . is_none ( )
| | ray_energy < = 0.0 ;
ray_energy - =
block . explode_power ( ) . unwrap_or ( 0.0 ) + rng . gen ::< f32 > ( ) * 0.1 ;
2020-11-19 00:23:13 +00:00
stop
} )
2020-10-30 20:41:21 +00:00
. for_each ( | block : & Block , pos | {
2020-11-19 00:23:13 +00:00
if block . explode_power ( ) . is_some ( ) {
2020-10-30 20:41:21 +00:00
block_change . set ( pos , block . into_vacant ( ) ) ;
}
} )
. cast ( ) ;
2020-10-06 02:19:41 +00:00
}
2020-10-30 20:41:21 +00:00
} ,
2021-01-30 22:35:00 +00:00
RadiusEffect ::Attack ( attack ) = > {
2022-07-15 12:08:04 +00:00
let energies = & ecs . read_storage ::< Energy > ( ) ;
2021-03-02 22:19:38 +00:00
let combos = & ecs . read_storage ::< comp ::Combo > ( ) ;
2022-07-15 12:08:04 +00:00
let inventories = & ecs . read_storage ::< Inventory > ( ) ;
2021-08-27 12:52:52 +00:00
let alignments = & ecs . read_storage ::< Alignment > ( ) ;
let uid_allocator = & ecs . read_resource ::< UidAllocator > ( ) ;
2022-07-15 12:08:04 +00:00
let players = & ecs . read_storage ::< Player > ( ) ;
2022-11-27 21:11:19 +00:00
let buffs = & ecs . read_storage ::< comp ::Buffs > ( ) ;
2022-12-16 02:51:03 +00:00
let stats = & ecs . read_storage ::< comp ::Stats > ( ) ;
2023-05-21 14:30:18 +00:00
let terrain = ecs . read_resource ::< TerrainGrid > ( ) ;
2021-04-10 03:40:20 +00:00
for (
entity_b ,
pos_b ,
health_b ,
2022-12-16 02:51:03 +00:00
( body_b_maybe , ori_b_maybe , char_state_b_maybe , uid_b ) ,
2021-04-10 03:40:20 +00:00
) in (
2021-03-20 20:26:10 +00:00
& ecs . entities ( ) ,
2022-07-15 12:08:04 +00:00
& ecs . read_storage ::< Pos > ( ) ,
& ecs . read_storage ::< Health > ( ) ,
2021-04-10 03:40:20 +00:00
(
2022-07-15 12:08:04 +00:00
ecs . read_storage ::< Body > ( ) . maybe ( ) ,
2021-04-10 03:40:20 +00:00
ecs . read_storage ::< comp ::Ori > ( ) . maybe ( ) ,
2022-07-15 12:08:04 +00:00
ecs . read_storage ::< CharacterState > ( ) . maybe ( ) ,
2021-04-24 04:11:41 +00:00
& ecs . read_storage ::< Uid > ( ) ,
2021-04-10 03:40:20 +00:00
) ,
2021-03-20 20:26:10 +00:00
)
. join ( )
2021-04-10 03:40:20 +00:00
. filter ( | ( _ , _ , h , _ ) | ! h . is_dead )
2020-11-01 17:15:46 +00:00
{
2023-05-21 14:30:18 +00:00
let dist_sqrd = pos . distance_squared ( pos_b . 0 ) ;
2021-01-30 22:35:00 +00:00
// Check if it is a hit
2021-03-15 03:45:55 +00:00
let strength = if let Some ( body ) = body_b_maybe {
2021-10-27 19:42:11 +00:00
cylinder_sphere_strength (
pos ,
explosion . radius ,
explosion . min_falloff ,
pos_b . 0 ,
* body ,
)
2021-03-15 03:45:55 +00:00
} else {
2023-05-21 14:30:18 +00:00
1.0 - dist_sqrd / explosion . radius . powi ( 2 )
2021-03-15 03:45:55 +00:00
} ;
2023-05-21 14:30:18 +00:00
// Cast a ray from the explosion to the entity to check visibility
if strength > 0.0
& & ( terrain . ray ( pos , pos_b . 0 ) . until ( Block ::is_opaque ) . cast ( ) . 0 + 0.1 )
. powi ( 2 )
> = dist_sqrd
{
2021-01-30 22:35:00 +00:00
// See if entities are in the same group
let same_group = owner_entity
. and_then ( | e | groups . get ( e ) )
. map ( | group_a | Some ( group_a ) = = groups . get ( entity_b ) )
. unwrap_or ( Some ( entity_b ) = = owner_entity ) ;
let target_group = if same_group {
GroupTarget ::InGroup
} else {
GroupTarget ::OutOfGroup
} ;
let dir = Dir ::new (
( pos_b . 0 - pos )
. try_normalized ( )
. unwrap_or_else ( Vec3 ::unit_z ) ,
) ;
2021-02-02 18:02:40 +00:00
let attacker_info =
owner_entity
. zip ( owner )
. map ( | ( entity , uid ) | combat ::AttackerInfo {
entity ,
uid ,
2021-11-13 20:46:45 +00:00
group : groups . get ( entity ) ,
2021-02-02 18:02:40 +00:00
energy : energies . get ( entity ) ,
2021-03-02 22:19:38 +00:00
combo : combos . get ( entity ) ,
2021-05-19 01:42:14 +00:00
inventory : inventories . get ( entity ) ,
2022-12-16 02:51:03 +00:00
stats : stats . get ( entity ) ,
2021-02-02 18:02:40 +00:00
} ) ;
2021-02-28 20:02:03 +00:00
let target_info = combat ::TargetInfo {
entity : entity_b ,
2021-04-24 04:11:41 +00:00
uid : * uid_b ,
2021-05-19 01:42:14 +00:00
inventory : inventories . get ( entity_b ) ,
2022-12-16 02:51:03 +00:00
stats : stats . get ( entity_b ) ,
2021-03-20 20:26:10 +00:00
health : Some ( health_b ) ,
2021-04-19 05:35:46 +00:00
pos : pos_b . 0 ,
2021-04-10 03:40:20 +00:00
ori : ori_b_maybe ,
char_state : char_state_b_maybe ,
2022-01-07 05:30:28 +00:00
energy : energies . get ( entity_b ) ,
2022-11-27 21:11:19 +00:00
buffs : buffs . get ( entity_b ) ,
2021-02-28 20:02:03 +00:00
} ;
2022-10-24 01:12:59 +00:00
let target_dodging = char_state_b_maybe
. and_then ( | cs | cs . attack_immunities ( ) )
. map_or ( false , | i | i . explosions ) ;
2021-08-27 12:52:52 +00:00
// PvP check
2021-07-31 17:53:09 +00:00
let may_harm = combat ::may_harm (
2021-08-27 18:49:58 +00:00
alignments ,
players ,
uid_allocator ,
owner_entity ,
entity_b ,
2021-07-30 11:34:05 +00:00
) ;
2021-07-26 13:52:43 +00:00
let attack_options = combat ::AttackOptions {
2022-10-24 01:12:59 +00:00
target_dodging ,
2021-07-31 17:53:09 +00:00
may_harm ,
2021-01-30 22:35:00 +00:00
target_group ,
2021-07-26 13:52:43 +00:00
} ;
2021-11-13 20:46:45 +00:00
let time = server . state . ecs ( ) . read_resource ::< Time > ( ) ;
2021-07-26 13:52:43 +00:00
attack . apply_attack (
2021-02-02 18:02:40 +00:00
attacker_info ,
2023-04-24 01:31:29 +00:00
& target_info ,
2021-01-30 22:35:00 +00:00
dir ,
2021-07-26 13:52:43 +00:00
attack_options ,
2021-01-31 16:40:04 +00:00
strength ,
2021-04-10 03:40:20 +00:00
combat ::AttackSource ::Explosion ,
2021-11-13 20:46:45 +00:00
* time ,
2021-11-03 13:26:34 +00:00
| e | emitter . emit ( e ) ,
2022-05-09 19:58:13 +00:00
| o | outcomes_emitter . emit ( o ) ,
2023-04-06 19:48:23 +00:00
& mut rng ,
2023-04-24 23:55:14 +00:00
0 ,
2021-01-30 22:35:00 +00:00
) ;
2020-11-04 02:01:12 +00:00
}
2021-01-30 22:35:00 +00:00
}
} ,
RadiusEffect ::Entity ( mut effect ) = > {
2021-08-27 12:52:52 +00:00
let alignments = & ecs . read_storage ::< Alignment > ( ) ;
let uid_allocator = & ecs . read_resource ::< UidAllocator > ( ) ;
2022-07-15 12:08:04 +00:00
let players = & ecs . read_storage ::< Player > ( ) ;
2021-03-15 03:45:55 +00:00
for ( entity_b , pos_b , body_b_maybe ) in (
& ecs . entities ( ) ,
2022-07-15 12:08:04 +00:00
& ecs . read_storage ::< Pos > ( ) ,
ecs . read_storage ::< Body > ( ) . maybe ( ) ,
2021-03-15 03:45:55 +00:00
)
. join ( )
2021-01-30 22:35:00 +00:00
{
2021-03-15 03:45:55 +00:00
let strength = if let Some ( body ) = body_b_maybe {
2021-10-27 19:42:11 +00:00
cylinder_sphere_strength (
pos ,
explosion . radius ,
explosion . min_falloff ,
pos_b . 0 ,
* body ,
)
2021-03-15 03:45:55 +00:00
} else {
let distance_squared = pos . distance_squared ( pos_b . 0 ) ;
1.0 - distance_squared / explosion . radius . powi ( 2 )
} ;
2020-11-04 02:01:12 +00:00
2021-08-27 18:49:58 +00:00
// Player check only accounts for PvP/PvE flag, but bombs
// are intented to do friendly fire.
2021-07-31 17:53:09 +00:00
//
2021-08-27 18:49:58 +00:00
// What exactly is friendly fire is subject to discussion.
2021-07-31 17:53:09 +00:00
// As we probably want to minimize possibility of being dick
// even to your group members, the only exception is when
// you want to harm yourself.
//
// This can be changed later.
let may_harm = | | {
2021-08-27 21:08:48 +00:00
combat ::may_harm ( alignments , players , uid_allocator , owner_entity , entity_b )
| | owner_entity . map_or ( true , | entity_a | entity_a = = entity_b )
2021-07-30 11:34:05 +00:00
} ;
2020-11-05 03:48:59 +00:00
if strength > 0.0 {
2021-02-03 05:41:19 +00:00
let is_alive = ecs
2022-07-15 12:08:04 +00:00
. read_storage ::< Health > ( )
2021-02-03 05:41:19 +00:00
. get ( entity_b )
. map_or ( true , | h | ! h . is_dead ) ;
2021-07-26 13:52:43 +00:00
2021-02-03 05:41:19 +00:00
if is_alive {
effect . modify_strength ( strength ) ;
2021-07-31 17:53:09 +00:00
if ! effect . is_harm ( ) | | may_harm ( ) {
2021-07-26 13:52:43 +00:00
server . state ( ) . apply_effect ( entity_b , effect . clone ( ) , owner ) ;
}
2021-02-03 05:41:19 +00:00
}
2020-11-01 17:15:46 +00:00
}
}
} ,
2020-10-30 20:41:21 +00:00
}
2020-02-16 20:04:06 +00:00
}
}
2020-06-01 09:21:33 +00:00
2021-11-03 11:15:20 +00:00
pub fn handle_bonk ( server : & mut Server , pos : Vec3 < f32 > , owner : Option < Uid > , target : Option < Uid > ) {
2021-08-30 15:02:13 +00:00
let ecs = & server . state . ecs ( ) ;
let terrain = ecs . read_resource ::< TerrainGrid > ( ) ;
let mut block_change = ecs . write_resource ::< BlockChange > ( ) ;
if let Some ( _target ) = target {
// TODO: bonk entities but do no damage?
} else {
use common ::terrain ::SpriteKind ;
let pos = pos . map ( | e | e . floor ( ) as i32 ) ;
if let Some ( block ) = terrain . get ( pos ) . ok ( ) . copied ( ) . filter ( | b | b . is_bonkable ( ) ) {
2023-03-10 03:37:45 +00:00
if block_change
. try_set ( pos , block . with_sprite ( SpriteKind ::Empty ) )
. is_some ( )
{
drop ( terrain ) ;
drop ( block_change ) ;
2023-04-23 19:17:39 +00:00
if let Some ( items ) = comp ::Item ::try_reclaim_from_block ( block ) {
let msm = & MaterialStatManifest ::load ( ) . read ( ) ;
let ability_map = & AbilityMap ::load ( ) . read ( ) ;
for item in flatten_counted_items ( & items , ability_map , msm ) {
server
. state
2023-05-04 21:12:25 +00:00
. create_object (
Pos ( pos . map ( | e | e as f32 ) + Vec3 ::new ( 0.5 , 0.5 , 0.0 ) ) ,
match block . get_sprite ( ) {
// Create different containers depending on the original sprite
Some ( SpriteKind ::Apple ) = > comp ::object ::Body ::Apple ,
Some ( SpriteKind ::Beehive ) = > comp ::object ::Body ::Hive ,
Some ( SpriteKind ::Coconut ) = > comp ::object ::Body ::Coconut ,
Some ( SpriteKind ::Bomb ) = > comp ::object ::Body ::Bomb ,
_ = > comp ::object ::Body ::Pouch ,
} ,
)
2023-04-23 19:17:39 +00:00
. with ( item )
. maybe_with ( match block . get_sprite ( ) {
Some ( SpriteKind ::Bomb ) = > Some ( comp ::Object ::Bomb { owner } ) ,
_ = > None ,
} )
. build ( ) ;
}
2021-08-30 15:02:13 +00:00
}
2023-03-10 03:37:45 +00:00
}
2021-08-30 15:02:13 +00:00
}
}
}
2020-12-04 22:24:56 +00:00
pub fn handle_aura ( server : & mut Server , entity : EcsEntity , aura_change : aura ::AuraChange ) {
let ecs = & server . state . ecs ( ) ;
2022-07-15 12:08:04 +00:00
let mut auras_all = ecs . write_storage ::< Auras > ( ) ;
2021-01-07 20:25:12 +00:00
if let Some ( mut auras ) = auras_all . get_mut ( entity ) {
2020-12-04 22:24:56 +00:00
use aura ::AuraChange ;
match aura_change {
AuraChange ::Add ( new_aura ) = > {
auras . insert ( new_aura ) ;
} ,
AuraChange ::RemoveByKey ( keys ) = > {
for key in keys {
auras . remove ( key ) ;
}
} ,
}
}
}
2020-10-25 01:20:03 +00:00
pub fn handle_buff ( server : & mut Server , entity : EcsEntity , buff_change : buff ::BuffChange ) {
2020-10-01 00:40:46 +00:00
let ecs = & server . state . ecs ( ) ;
let mut buffs_all = ecs . write_storage ::< comp ::Buffs > ( ) ;
2022-07-15 12:08:04 +00:00
let bodies = ecs . read_storage ::< Body > ( ) ;
2023-03-09 02:39:28 +00:00
let time = ecs . read_resource ::< Time > ( ) ;
2021-01-07 20:25:12 +00:00
if let Some ( mut buffs ) = buffs_all . get_mut ( entity ) {
2020-10-25 01:20:03 +00:00
use buff ::BuffChange ;
match buff_change {
BuffChange ::Add ( new_buff ) = > {
2021-02-20 14:03:15 +00:00
if ! bodies
. get ( entity )
. map_or ( false , | body | body . immune_to ( new_buff . kind ) )
2021-07-02 15:58:08 +00:00
& & ecs
. read_component ::< Health > ( )
. get ( entity )
. map_or ( true , | h | ! h . is_dead )
2021-02-20 14:03:15 +00:00
{
2023-03-09 02:39:28 +00:00
buffs . insert ( new_buff , * time ) ;
2021-02-20 14:03:15 +00:00
}
2020-10-25 01:20:03 +00:00
} ,
BuffChange ::RemoveById ( ids ) = > {
for id in ids {
buffs . remove ( id ) ;
}
} ,
BuffChange ::RemoveByKind ( kind ) = > {
buffs . remove_kind ( kind ) ;
} ,
BuffChange ::RemoveFromController ( kind ) = > {
if kind . is_buff ( ) {
2020-10-24 20:12:37 +00:00
buffs . remove_kind ( kind ) ;
2020-10-25 01:20:03 +00:00
}
} ,
BuffChange ::RemoveByCategory {
all_required ,
any_required ,
none_required ,
} = > {
let mut ids_to_remove = Vec ::new ( ) ;
for ( id , buff ) in buffs . buffs . iter ( ) {
let mut required_met = true ;
for required in & all_required {
if ! buff . cat_ids . iter ( ) . any ( | cat | cat = = required ) {
required_met = false ;
break ;
2020-10-02 19:09:19 +00:00
}
2020-10-25 01:20:03 +00:00
}
let mut any_met = any_required . is_empty ( ) ;
for any in & any_required {
if buff . cat_ids . iter ( ) . any ( | cat | cat = = any ) {
any_met = true ;
break ;
2020-10-13 00:48:25 +00:00
}
2020-10-25 01:20:03 +00:00
}
let mut none_met = true ;
for none in & none_required {
if buff . cat_ids . iter ( ) . any ( | cat | cat = = none ) {
none_met = false ;
break ;
2020-10-02 19:09:19 +00:00
}
}
2020-10-25 01:20:03 +00:00
if required_met & & any_met & & none_met {
ids_to_remove . push ( * id ) ;
2020-10-02 19:09:19 +00:00
}
2020-10-25 01:20:03 +00:00
}
for id in ids_to_remove {
buffs . remove ( id ) ;
}
} ,
2022-11-16 00:46:34 +00:00
BuffChange ::Refresh ( kind ) = > {
2022-12-16 02:51:03 +00:00
buffs
. buffs
. values_mut ( )
. filter ( | b | b . kind = = kind )
. for_each ( | buff | {
// Resets buff so that its remaining duration is equal to its original
// duration
buff . start_time = * time ;
buff . end_time = buff . data . duration . map ( | dur | Time ( time . 0 + dur . 0 ) ) ;
} )
2022-11-16 00:46:34 +00:00
} ,
2020-10-02 17:15:10 +00:00
}
}
}
2020-10-30 21:49:58 +00:00
2021-09-14 02:16:01 +00:00
pub fn handle_energy_change ( server : & Server , entity : EcsEntity , change : f32 ) {
2020-10-30 21:49:58 +00:00
let ecs = & server . state . ecs ( ) ;
2021-01-07 20:25:12 +00:00
if let Some ( mut energy ) = ecs . write_storage ::< Energy > ( ) . get_mut ( entity ) {
2020-11-02 00:26:01 +00:00
energy . change_by ( change ) ;
2020-10-30 21:49:58 +00:00
}
}
2021-01-16 17:01:57 +00:00
fn handle_exp_gain (
exp_reward : f32 ,
inventory : & Inventory ,
2021-04-14 15:35:34 +00:00
skill_set : & mut SkillSet ,
2021-01-16 17:01:57 +00:00
uid : & Uid ,
2022-05-09 19:58:13 +00:00
outcomes : & mut EventBus < Outcome > ,
2021-01-16 17:01:57 +00:00
) {
2021-05-27 02:12:35 +00:00
use comp ::inventory ::{ item ::ItemKind , slot ::EquipSlot } ;
2022-05-09 19:58:13 +00:00
let mut outcomes_emitter = outcomes . emitter ( ) ;
2021-05-27 02:12:35 +00:00
// Create hash set of xp pools to consider splitting xp amongst
2021-01-18 19:08:13 +00:00
let mut xp_pools = HashSet ::< SkillGroupKind > ::new ( ) ;
2021-05-27 02:12:35 +00:00
// Insert general pool since it is always accessible
2021-01-18 19:08:13 +00:00
xp_pools . insert ( SkillGroupKind ::General ) ;
2021-05-27 02:12:35 +00:00
// Closure to add xp pool corresponding to weapon type equipped in a particular
// EquipSlot
let mut add_tool_from_slot = | equip_slot | {
2021-06-09 05:14:20 +00:00
let tool_kind = inventory
. equipped ( equip_slot )
2021-11-18 15:41:08 +00:00
. and_then ( | i | match & * i . kind ( ) {
2021-06-09 05:14:20 +00:00
ItemKind ::Tool ( tool ) if tool . kind . gains_combat_xp ( ) = > Some ( tool . kind ) ,
_ = > None ,
} ) ;
2021-05-27 02:12:35 +00:00
if let Some ( weapon ) = tool_kind {
// Only adds to xp pools if entity has that skill group available
2021-11-15 01:11:58 +00:00
if skill_set . skill_group_accessible ( SkillGroupKind ::Weapon ( weapon ) ) {
2021-05-27 02:12:35 +00:00
xp_pools . insert ( SkillGroupKind ::Weapon ( weapon ) ) ;
}
2021-01-16 17:01:57 +00:00
}
2021-05-27 02:12:35 +00:00
} ;
// Add weapons to xp pools considered
add_tool_from_slot ( EquipSlot ::ActiveMainhand ) ;
add_tool_from_slot ( EquipSlot ::ActiveOffhand ) ;
add_tool_from_slot ( EquipSlot ::InactiveMainhand ) ;
add_tool_from_slot ( EquipSlot ::InactiveOffhand ) ;
2021-01-16 17:01:57 +00:00
let num_pools = xp_pools . len ( ) as f32 ;
2021-06-09 20:03:25 +00:00
for pool in xp_pools . iter ( ) {
2021-12-02 06:31:20 +00:00
if let Some ( level_outcome ) =
skill_set . add_experience ( * pool , ( exp_reward / num_pools ) . ceil ( ) as u32 )
{
2022-05-09 19:58:13 +00:00
outcomes_emitter . emit ( Outcome ::SkillPointGain {
2021-12-02 06:31:20 +00:00
uid : * uid ,
skill_tree : * pool ,
total_points : level_outcome ,
} ) ;
}
2021-01-16 17:01:57 +00:00
}
2022-05-09 19:58:13 +00:00
outcomes_emitter . emit ( Outcome ::ExpChange {
2021-01-16 17:01:57 +00:00
uid : * uid ,
2021-10-13 14:44:28 +00:00
exp : exp_reward as u32 ,
2021-06-09 20:03:25 +00:00
xp_pools ,
2021-01-16 17:01:57 +00:00
} ) ;
}
2021-02-27 19:55:06 +00:00
pub fn handle_combo_change ( server : & Server , entity : EcsEntity , change : i32 ) {
let ecs = & server . state . ecs ( ) ;
if let Some ( mut combo ) = ecs . write_storage ::< comp ::Combo > ( ) . get_mut ( entity ) {
2021-03-04 20:43:58 +00:00
let time = ecs . read_resource ::< Time > ( ) ;
2022-05-09 19:58:13 +00:00
let outcome_bus = ecs . read_resource ::< EventBus < Outcome > > ( ) ;
2021-03-04 20:43:58 +00:00
combo . change_by ( change , time . 0 ) ;
if let Some ( uid ) = ecs . read_storage ::< Uid > ( ) . get ( entity ) {
2022-05-09 19:58:13 +00:00
outcome_bus . emit_now ( Outcome ::ComboChange {
2021-03-04 20:43:58 +00:00
uid : * uid ,
combo : combo . counter ( ) ,
} ) ;
2021-02-27 19:55:06 +00:00
}
}
}
2021-03-21 03:28:13 +00:00
2022-09-07 01:23:12 +00:00
pub fn handle_parry_hook ( server : & Server , defender : EcsEntity , attacker : Option < EcsEntity > ) {
2021-08-31 06:47:37 +00:00
let ecs = & server . state . ecs ( ) ;
2022-09-07 01:23:12 +00:00
let server_eventbus = ecs . read_resource ::< EventBus < ServerEvent > > ( ) ;
2022-03-05 18:52:43 +00:00
// Reset character state of defender
if let Some ( mut char_state ) = ecs
. write_storage ::< comp ::CharacterState > ( )
. get_mut ( defender )
{
2022-10-24 01:12:59 +00:00
let return_to_wield = match & mut * char_state {
2022-03-05 18:52:43 +00:00
CharacterState ::RiposteMelee ( c ) = > {
c . stage_section = StageSection ::Action ;
c . timer = Duration ::default ( ) ;
2022-10-24 01:12:59 +00:00
false
2022-03-05 18:52:43 +00:00
} ,
2022-10-24 01:12:59 +00:00
CharacterState ::BasicBlock ( c ) = > {
// Refund half the energy of entering the block for a successful parry
server_eventbus . emit_now ( ServerEvent ::EnergyChange {
entity : defender ,
change : c . static_data . energy_cost / 2.0 ,
} ) ;
true
} ,
2023-03-26 23:50:42 +00:00
_ = > false ,
2022-10-24 01:12:59 +00:00
} ;
if return_to_wield {
* char_state =
CharacterState ::Wielding ( common ::states ::wielding ::Data { is_sneaking : false } ) ;
2022-03-05 18:52:43 +00:00
}
2021-08-31 06:47:37 +00:00
} ;
2022-09-07 01:23:12 +00:00
if let Some ( attacker ) = attacker {
2023-03-26 23:50:42 +00:00
// When attacker is parried, add the parried debuff for 2 seconds, which slows
// them
let data = buff ::BuffData ::new ( 1.0 , Some ( Secs ( 2.0 ) ) , None ) ;
let source = if let Some ( uid ) = ecs . read_storage ::< Uid > ( ) . get ( defender ) {
BuffSource ::Character { by : * uid }
} else {
BuffSource ::World
} ;
let time = ecs . read_resource ::< Time > ( ) ;
let stats = ecs . read_storage ::< comp ::Stats > ( ) ;
let healths = ecs . read_storage ::< comp ::Health > ( ) ;
let buff = buff ::Buff ::new (
BuffKind ::Parried ,
data ,
vec! [ buff ::BuffCategory ::Physical ] ,
source ,
* time ,
stats . get ( attacker ) ,
healths . get ( attacker ) ,
) ;
server_eventbus . emit_now ( ServerEvent ::Buff {
entity : attacker ,
buff_change : buff ::BuffChange ::Add ( buff ) ,
} ) ;
2022-09-07 01:23:12 +00:00
}
2021-08-31 06:47:37 +00:00
}
2021-03-21 03:28:13 +00:00
pub fn handle_teleport_to ( server : & Server , entity : EcsEntity , target : Uid , max_range : Option < f32 > ) {
let ecs = & server . state . ecs ( ) ;
let mut positions = ecs . write_storage ::< Pos > ( ) ;
2021-03-28 01:33:45 +00:00
let target_pos = ecs
2021-03-21 03:28:13 +00:00
. entity_from_uid ( target . into ( ) )
. and_then ( | e | positions . get ( e ) )
. copied ( ) ;
if let ( Some ( pos ) , Some ( target_pos ) ) = ( positions . get_mut ( entity ) , target_pos ) {
if max_range . map_or ( true , | r | pos . 0. distance_squared ( target_pos . 0 ) < r . powi ( 2 ) ) {
* pos = target_pos ;
2022-07-31 21:01:10 +00:00
ecs . write_storage ::< comp ::ForceUpdate > ( )
. get_mut ( entity )
. map ( | force_update | force_update . update ( ) ) ;
2021-03-21 03:28:13 +00:00
}
}
}
2021-08-29 01:55:01 +00:00
/// Intended to handle things that should happen for any successful attack,
/// regardless of the damages and effects specific to that attack
pub fn handle_entity_attacked_hook ( server : & Server , entity : EcsEntity ) {
let ecs = & server . state . ecs ( ) ;
let server_eventbus = ecs . read_resource ::< EventBus < ServerEvent > > ( ) ;
2022-05-09 19:58:13 +00:00
2022-01-08 05:36:15 +00:00
let time = ecs . read_resource ::< Time > ( ) ;
2021-08-29 01:55:01 +00:00
2021-10-29 23:24:40 +00:00
if let ( Some ( mut char_state ) , Some ( mut poise ) , Some ( pos ) ) = (
ecs . write_storage ::< CharacterState > ( ) . get_mut ( entity ) ,
ecs . write_storage ::< Poise > ( ) . get_mut ( entity ) ,
ecs . read_storage ::< Pos > ( ) . get ( entity ) ,
) {
2021-08-29 01:55:01 +00:00
// Interrupt sprite interaction and item use if any attack is applied to entity
if matches! (
* char_state ,
CharacterState ::SpriteInteract ( _ ) | CharacterState ::UseItem ( _ )
) {
2022-01-08 05:36:15 +00:00
let poise_state = comp ::poise ::PoiseState ::Interrupted ;
2021-10-29 23:24:40 +00:00
let was_wielded = char_state . is_wield ( ) ;
2022-01-08 05:36:15 +00:00
if let ( Some ( ( stunned_state , stunned_duration ) ) , impulse_strength ) =
2022-10-30 18:40:20 +00:00
poise_state . poise_effect ( was_wielded )
2022-01-08 05:36:15 +00:00
{
2021-10-29 23:24:40 +00:00
// Reset poise if there is some stunned state to apply
2022-01-08 05:36:15 +00:00
poise . reset ( * time , stunned_duration ) ;
2021-10-29 23:24:40 +00:00
* char_state = stunned_state ;
2022-05-09 19:58:13 +00:00
ecs . read_resource ::< EventBus < Outcome > > ( )
. emit_now ( Outcome ::PoiseChange {
pos : pos . 0 ,
state : poise_state ,
} ) ;
2021-10-29 23:24:40 +00:00
if let Some ( impulse_strength ) = impulse_strength {
server_eventbus . emit_now ( ServerEvent ::Knockback {
entity ,
impulse : impulse_strength * * poise . knockback ( ) ,
} ) ;
}
}
2021-08-29 01:55:01 +00:00
}
}
// Remove potion/saturation buff if attacked
server_eventbus . emit_now ( ServerEvent ::Buff {
entity ,
2022-07-15 12:08:04 +00:00
buff_change : buff ::BuffChange ::RemoveByKind ( BuffKind ::Potion ) ,
2021-08-29 01:55:01 +00:00
} ) ;
server_eventbus . emit_now ( ServerEvent ::Buff {
entity ,
2022-07-15 12:08:04 +00:00
buff_change : buff ::BuffChange ::RemoveByKind ( BuffKind ::Saturation ) ,
2021-08-29 01:55:01 +00:00
} ) ;
2023-04-16 19:07:34 +00:00
// If entity was in an active trade, cancel it
let mut trades = ecs . write_resource ::< Trades > ( ) ;
let uids = ecs . read_storage ::< Uid > ( ) ;
if let Some ( uid ) = uids . get ( entity ) {
if let Some ( trade ) = trades . entity_trades . get ( uid ) . copied ( ) {
trades
. decline_trade ( trade , * uid )
. and_then ( | uid | ecs . entity_from_uid ( uid . 0 ) )
2023-04-16 21:31:39 +00:00
. map ( | entity_b | {
// Notify both parties that the trade ended
let clients = ecs . read_storage ::< Client > ( ) ;
let mut agents = ecs . write_storage ::< Agent > ( ) ;
let mut notify_trade_party = | entity | {
2023-04-22 15:37:42 +00:00
// TODO: Can probably improve UX here for the user that sent the trade
// invite, since right now it may seems like their request was
// purposefully declined, rather than e.g. being interrupted.
2023-04-16 21:31:39 +00:00
if let Some ( client ) = clients . get ( entity ) {
client
. send_fallible ( ServerGeneral ::FinishedTrade ( TradeResult ::Declined ) ) ;
}
if let Some ( agent ) = agents . get_mut ( entity ) {
agent
. inbox
. push_back ( AgentEvent ::FinishedTrade ( TradeResult ::Declined ) ) ;
}
} ;
notify_trade_party ( entity ) ;
notify_trade_party ( entity_b ) ;
2023-04-16 19:07:34 +00:00
} ) ;
}
}
2021-08-29 01:55:01 +00:00
}
2021-11-28 04:32:24 +00:00
pub fn handle_change_ability (
server : & Server ,
entity : EcsEntity ,
slot : usize ,
auxiliary_key : ability ::AuxiliaryKey ,
new_ability : ability ::AuxiliaryAbility ,
) {
let ecs = & server . state . ecs ( ) ;
2022-07-15 12:08:04 +00:00
let inventories = ecs . read_storage ::< Inventory > ( ) ;
let skill_sets = ecs . read_storage ::< SkillSet > ( ) ;
2021-11-28 04:32:24 +00:00
if let Some ( mut active_abilities ) = ecs . write_storage ::< comp ::ActiveAbilities > ( ) . get_mut ( entity )
{
active_abilities . change_ability (
slot ,
auxiliary_key ,
new_ability ,
inventories . get ( entity ) ,
skill_sets . get ( entity ) ,
) ;
}
}
2022-02-20 10:10:18 +00:00
pub fn handle_update_map_marker (
server : & mut Server ,
entity : EcsEntity ,
update : comp ::MapMarkerChange ,
) {
use comp ::{ MapMarker , MapMarkerChange ::* } ;
match update {
Update ( waypoint ) = > {
server
. state
. write_component_ignore_entity_dead ( entity , MapMarker ( waypoint ) ) ;
} ,
Remove = > {
server . state . delete_component ::< MapMarker > ( entity ) ;
} ,
}
let ecs = server . state . ecs_mut ( ) ;
// Send updated waypoint to group members
let groups = ecs . read_storage ( ) ;
let uids = ecs . read_storage ( ) ;
if let Some ( ( group_id , uid ) ) = groups . get ( entity ) . zip ( uids . get ( entity ) ) {
let clients = ecs . read_storage ::< Client > ( ) ;
for client in comp ::group ::members (
* group_id ,
& groups ,
& ecs . entities ( ) ,
& ecs . read_storage ( ) ,
& uids ,
)
. filter_map ( | ( e , _ ) | if e ! = entity { clients . get ( e ) } else { None } )
{
client . send_fallible ( ServerGeneral ::MapMarker (
comp ::MapMarkerUpdate ::GroupMember ( * uid , update ) ,
) ) ;
}
}
}
2023-01-22 23:50:29 +00:00
pub fn handle_make_admin ( server : & mut Server , entity : EcsEntity , admin : comp ::Admin , uuid : Uuid ) {
if server
. state
. read_storage ::< Player > ( )
. get ( entity )
. map_or ( false , | player | player . uuid ( ) = = uuid )
{
server
. state
. write_component_ignore_entity_dead ( entity , admin ) ;
}
}
2022-10-30 04:58:16 +00:00
pub fn handle_stance_change ( server : & mut Server , entity : EcsEntity , new_stance : comp ::Stance ) {
if let Some ( mut stance ) = server
. state
. ecs_mut ( )
. write_storage ::< comp ::Stance > ( )
. get_mut ( entity )
{
* stance = new_stance ;
}
}
2023-04-01 17:11:59 +00:00
pub fn handle_change_body ( server : & mut Server , entity : EcsEntity , new_body : comp ::Body ) {
if let Some ( mut body ) = server
. state
. ecs_mut ( )
. write_storage ::< comp ::Body > ( )
. get_mut ( entity )
{
* body = new_body ;
}
}
pub fn handle_remove_light_emitter ( server : & mut Server , entity : EcsEntity ) {
server
. state
. ecs_mut ( )
. write_storage ::< comp ::LightEmitter > ( )
. remove ( entity ) ;
}