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-08-27 18:49:58 +00:00
agent ::{ Agent , AgentEvent , Sound , SoundKind } ,
2021-05-15 19:36:27 +00:00
skills ::SkillGroupKind ,
2021-09-23 18:43:31 +00:00
BuffKind , BuffSource , PhysicsState ,
2021-04-03 03:03:59 +00:00
} ,
2020-11-11 23:59:09 +00:00
rtsim ::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
} ;
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 } ,
2021-02-23 20:29:27 +00:00
inventory ::item ::MaterialStatManifest ,
2021-09-14 02:16:01 +00:00
object , Alignment , Auras , Body , CharacterState , Energy , Group , Health , HealthChange ,
2021-09-25 18:07:47 +00:00
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 } ,
2020-07-31 17:16:20 +00:00
outcome ::Outcome ,
2021-02-27 19:55:06 +00:00
resources ::Time ,
2020-11-23 15:39:03 +00:00
rtsim ::RtSimEntity ,
2021-07-06 02:13:11 +00:00
terrain ::{ Block , BlockKind , TerrainGrid } ,
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 } ;
2021-04-06 15:47:03 +00:00
use common_state ::BlockChange ;
2021-04-18 20:46:16 +00:00
use comp ::chat ::GenericChatMsg ;
2020-12-13 17:21:51 +00:00
use hashbrown ::HashSet ;
2021-08-10 16:53: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 ,
} ;
use std ::{ collections ::HashMap , iter } ;
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 ,
}
2021-09-25 18:07:47 +00:00
pub fn handle_poise ( server : & Server , entity : EcsEntity , change : f32 , knockback_dir : Vec3 < f32 > ) {
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 ) {
2020-12-19 02:05:39 +00:00
// Entity is invincible to poise change during stunned/staggered character state
2020-12-17 00:14:14 +00:00
if ! character_state . is_stunned ( ) {
2021-01-27 03:05:13 +00:00
if let Some ( mut poise ) = ecs . write_storage ::< Poise > ( ) . get_mut ( entity ) {
2020-12-17 00:14:14 +00:00
poise . change_by ( change , knockback_dir ) ;
}
}
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 ( ) ;
2021-01-13 03:26:51 +00:00
if let Some ( mut health ) = ecs . write_storage ::< Health > ( ) . get_mut ( entity ) {
2020-12-06 02:29:46 +00:00
health . change_by ( change ) ;
}
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 ;
if damage > - 5.0 {
2021-06-17 05:49:09 +00:00
if let Some ( agent ) = ecs . write_storage ::< Agent > ( ) . get_mut ( entity ) {
agent . inbox . push_front ( AgentEvent ::Hurt ) ;
}
}
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-12-05 17:59:02 +00:00
// NOTE: Clippy incorrectly warns about a needless collect here because it does
// not understand that the pet count (which is computed during the first
// iteration over the members in range) is actually used by the second iteration
// over the members in range; since we have no way of knowing the pet count
// before the first loop finishes, we definitely need at least two loops. Then
// (currently) our only options are to store the member list in temporary space
// (e.g. by collecting to a vector), or to repeat the loop; but repeating the
// loop would currently be very inefficient since it has to rescan every entity
// on the server again.
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 ( )
2021-06-29 11:20:26 +00:00
. read_storage ::< comp ::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 ( )
. write_resource ::< Vec < Outcome > > ( )
. push ( Outcome ::Death { pos : pos . 0 } ) ;
}
}
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
state . send_chat ( GenericChatMsg {
chat_type : comp ::ChatType ::Kill ( kill_source , * uid ) ,
message : " " . to_string ( ) ,
} ) ;
2020-02-16 20:04:06 +00:00
}
}
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 > ( ) ;
2021-09-17 05:45:30 +00:00
let poises = state . ecs ( ) . read_storage ::< comp ::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 ) ) ;
( * entry ) . 0 + = damage ;
} ,
}
}
// 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 > ( ) ;
2021-01-16 17:01:57 +00:00
let mut outcomes = state . ecs ( ) . write_resource ::< Vec < Outcome > > ( ) ;
let inventories = state . ecs ( ) . read_storage ::< comp ::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
damage_contributors . iter ( ) . filter_map ( | ( damage_contributor , ( _ , damage_percent ) ) | {
let contributor_exp = exp_reward * damage_percent ;
match damage_contributor {
DamageContrib ::Solo ( attacker ) = > {
// No exp for self kills or PvP
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 ) ;
Some ( iter ::once ( ( * attacker , contributor_exp ) ) . collect ( ) )
} 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 ) ;
Some ( members_in_range . into_iter ( ) . map ( | entity | ( entity , exp_per_member ) ) . collect ::< Vec < ( Entity , f32 ) > > ( ) )
} ,
DamageContrib ::NotFound = > {
// Discard exp for dead/offline individual damage contributors
None
}
}
} ) . flatten ( ) . for_each ( | ( attacker , exp_reward ) | {
// Process the calculated EXP rewards
if let ( Some ( mut attacker_skill_set ) , Some ( attacker_uid ) , Some ( attacker_inventory ) ) = (
skill_sets . get_mut ( attacker ) ,
uids . get ( attacker ) ,
inventories . get ( attacker ) ,
) {
handle_exp_gain (
exp_reward ,
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 ( )
. write_storage ( )
. insert ( entity , comp ::ForceUpdate )
. err ( )
2020-06-21 21:47:49 +00:00
. map ( | e | error! ( ? e , ? entity , " Failed to insert ForceUpdate on dead client " ) ) ;
2020-02-16 20:04:06 +00:00
state
. ecs ( )
. write_storage ::< comp ::Energy > ( )
. 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 ( )
. write_storage ::< comp ::CharacterState > ( )
. insert ( entity , comp ::CharacterState ::default ( ) ) ;
2020-11-11 23:59:09 +00:00
false
2021-03-23 22:41:17 +00:00
} else if state . ecs ( ) . read_storage ::< comp ::Agent > ( ) . contains ( entity )
& & ! 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
let old_body = state . ecs ( ) . write_storage ::< Body > ( ) . remove ( entity ) ;
2020-05-15 15:05:50 +00:00
2021-09-27 20:21:57 +00:00
let item = {
2021-09-27 17:36:18 +00:00
let mut item_drop = state . ecs ( ) . write_storage ::< comp ::ItemDrop > ( ) ;
item_drop . remove ( entity ) . map ( | comp ::ItemDrop ( item ) | item )
2021-09-22 02:25:14 +00:00
} ;
2021-09-27 20:21:57 +00:00
if let Some ( item ) = item {
2021-09-01 23:17:36 +00:00
let pos = state . ecs ( ) . read_storage ::< comp ::Pos > ( ) . get ( entity ) . cloned ( ) ;
let vel = state . ecs ( ) . read_storage ::< comp ::Vel > ( ) . get ( entity ) . cloned ( ) ;
if let Some ( pos ) = pos {
2021-11-09 06:12:16 +00:00
// TODO: This should only be temporary as you'd eventually want to actually
// render the items on the ground, rather than changing the texture depending on
// the body type
2021-09-01 23:17:36 +00:00
let _ = state
. create_object ( comp ::Pos ( pos . 0 + Vec3 ::unit_z ( ) * 0.25 ) , match old_body {
Some ( common ::comp ::Body ::Humanoid ( _ ) ) = > object ::Body ::Pouch ,
2021-11-09 06:12:16 +00:00
Some ( common ::comp ::Body ::BipedSmall ( _ ) )
| Some ( common ::comp ::Body ::BipedLarge ( _ ) ) = > object ::Body ::Pouch ,
2021-09-01 23:17:36 +00:00
Some ( common ::comp ::Body ::Golem ( _ ) ) = > object ::Body ::Chest ,
Some ( common ::comp ::Body ::QuadrupedSmall ( _ ) ) = > object ::Body ::SmallMeat ,
Some ( common ::comp ::Body ::FishMedium ( _ ) )
| Some ( common ::comp ::Body ::FishSmall ( _ ) ) = > object ::Body ::FishMeat ,
Some ( common ::comp ::Body ::QuadrupedMedium ( _ ) ) = > object ::Body ::BeastMeat ,
2021-11-09 06:12:16 +00:00
Some ( common ::comp ::Body ::QuadrupedLow ( _ ) ) = > object ::Body ::ToughMeat ,
2021-09-01 23:17:36 +00:00
Some ( common ::comp ::Body ::BirdLarge ( _ ) )
| Some ( common ::comp ::Body ::BirdMedium ( _ ) ) = > object ::Body ::BirdMeat ,
2021-11-09 06:12:16 +00:00
Some ( common ::comp ::Body ::Theropod ( _ ) ) = > object ::Body ::BeastMeat ,
Some ( common ::comp ::Body ::Dragon ( _ ) ) = > object ::Body ::BeastMeat ,
Some ( common ::comp ::Body ::Object ( _ ) ) = > object ::Body ::Chest ,
_ = > object ::Body ::Pouch ,
2021-09-01 23:17:36 +00:00
} )
. maybe_with ( vel )
. with ( item )
. build ( ) ;
} 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
} ;
if should_delete {
2020-11-23 15:39:03 +00:00
if let Some ( rtsim_entity ) = state
. ecs ( )
. read_storage ::< RtSimEntity > ( )
. get ( entity )
. copied ( )
{
state
. ecs ( )
. write_resource ::< RtSim > ( )
. destroy_entity ( rtsim_entity . 0 ) ;
2020-11-11 23:59:09 +00:00
}
2020-06-11 17:06:11 +00:00
let _ = state
. delete_entity_recorded ( entity )
2020-06-21 21:47:49 +00:00
. map_err ( | e | error! ( ? e , ? entity , " Failed to delete destroyed entity " ) ) ;
2020-06-11 17:06:11 +00:00
}
2020-03-18 19:37:11 +00:00
2020-06-11 17:06:11 +00:00
// TODO: Add Delete(time_left: Duration) component
/*
// If not a player delete the entity
if let Err ( err ) = state . delete_entity_recorded ( entity ) {
2020-06-21 21:47:49 +00:00
error! ( ? e , " Failed to delete destroyed entity " ) ;
2020-02-16 20:04:06 +00:00
}
2020-06-11 17:06:11 +00:00
* /
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 " ) ) ;
}
2020-02-16 20:04:06 +00:00
pub fn handle_land_on_ground ( server : & Server , entity : EcsEntity , vel : Vec3 < f32 > ) {
2021-05-15 19:36:27 +00:00
let ecs = server . state . ecs ( ) ;
2020-04-08 12:40:17 +00:00
if vel . z < = - 30.0 {
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 ( ) ;
2021-05-15 19:36:27 +00:00
let impact_energy = mass . 0 * vel . z . 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 > ( ) ;
2021-01-27 03:05:13 +00:00
// Handle health change
2021-05-15 19:36:27 +00:00
if let Some ( mut health ) = ecs . write_storage ::< comp ::Health > ( ) . get_mut ( entity ) {
2021-01-27 03:05:13 +00:00
let damage = Damage {
source : DamageSource ::Falling ,
2021-05-06 18:50:16 +00:00
kind : DamageKind ::Crushing ,
2021-01-27 03:05:13 +00:00
value : falldmg ,
} ;
2021-05-06 18:50:16 +00:00
let damage_reduction = Damage ::compute_damage_reduction (
inventories . get ( entity ) ,
stats . get ( entity ) ,
Some ( DamageKind ::Crushing ) ,
) ;
2021-11-13 20:46:45 +00:00
let time = server . state . ecs ( ) . read_resource ::< Time > ( ) ;
let change =
damage . calculate_health_change ( damage_reduction , None , false , 0.0 , 1.0 , * time ) ;
2021-01-27 03:05:13 +00:00
health . change_by ( change ) ;
}
// Handle poise change
2021-05-15 19:36:27 +00:00
if let Some ( mut poise ) = ecs . write_storage ::< comp ::Poise > ( ) . get_mut ( entity ) {
2021-09-25 18:07:47 +00:00
let poise_damage = - ( mass . 0 * vel . magnitude_squared ( ) / 1500.0 ) ;
let poise_change = Poise ::apply_poise_reduction ( poise_damage , inventories . get ( entity ) ) ;
2021-01-27 03:05:13 +00:00
poise . change_by ( poise_change , Vec3 ::unit_z ( ) ) ;
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 ( )
2020-10-31 22:34:08 +00:00
. write_storage ::< comp ::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 ( )
. write_storage ::< comp ::Pos > ( )
. get_mut ( entity )
. map ( | pos | pos . 0 = respawn_point ) ;
state
. ecs ( )
. write_storage ( )
. insert ( entity , comp ::ForceUpdate )
. err ( )
2020-06-21 21:47:49 +00:00
. map ( | e | {
2020-02-16 20:04:06 +00:00
error! (
2020-06-21 21:47:49 +00:00
? e ,
" Error inserting ForceUpdate component when respawning client "
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 ( ) ;
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
let explosion_volume = 2.5 * 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 ;
2021-04-04 03:04:02 +00:00
let mut outcomes = ecs . write_resource ::< Vec < Outcome > > ( ) ;
outcomes . push ( Outcome ::Explosion {
pos ,
power : outcome_power ,
radius : explosion . radius ,
is_attack : explosion
. effects
. iter ( )
. any ( | e | matches! ( e , RadiusEffect ::Attack ( _ ) ) ) ,
reagent : explosion . reagent ,
} ) ;
2020-08-08 12:53:07 +00:00
let groups = ecs . read_storage ::< comp ::Group > ( ) ;
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 ( ) ;
2021-08-20 07:44:34 +00:00
' effects : for effect in explosion . effects {
2020-10-30 20:41:21 +00:00
match effect {
RadiusEffect ::TerrainDestruction ( power ) = > {
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 ) {
2021-07-06 02:30:45 +00:00
if ! matches! ( block . kind ( ) , BlockKind ::Lava | BlockKind ::GlowingRock ) {
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
+ ( fade * ( color [ 0 ] as f32 * 0.5 - color [ 0 ] as f32 ) ) ;
let g = color [ 1 ] as f32
+ ( fade * ( color [ 1 ] as f32 * 0.3 - color [ 1 ] as f32 ) ) ;
let b = color [ 2 ] as f32
+ ( fade * ( color [ 2 ] as f32 * 0.3 - color [ 2 ] as f32 ) ) ;
// 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 ) = > {
let energies = & ecs . read_storage ::< comp ::Energy > ( ) ;
2021-03-02 22:19:38 +00:00
let combos = & ecs . read_storage ::< comp ::Combo > ( ) ;
2021-05-19 01:42:14 +00:00
let inventories = & ecs . read_storage ::< comp ::Inventory > ( ) ;
2021-08-27 12:52:52 +00:00
let alignments = & ecs . read_storage ::< Alignment > ( ) ;
let uid_allocator = & ecs . read_resource ::< UidAllocator > ( ) ;
2021-07-26 13:52:43 +00:00
let players = & ecs . read_storage ::< comp ::Player > ( ) ;
2021-04-10 03:40:20 +00:00
for (
entity_b ,
pos_b ,
health_b ,
2021-05-19 01:42:14 +00:00
( body_b_maybe , stats_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 ( ) ,
& ecs . read_storage ::< comp ::Pos > ( ) ,
& ecs . read_storage ::< comp ::Health > ( ) ,
2021-04-10 03:40:20 +00:00
(
ecs . read_storage ::< comp ::Body > ( ) . maybe ( ) ,
ecs . read_storage ::< comp ::Stats > ( ) . maybe ( ) ,
ecs . read_storage ::< comp ::Ori > ( ) . maybe ( ) ,
ecs . read_storage ::< comp ::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
{
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 {
let distance_squared = pos . distance_squared ( pos_b . 0 ) ;
1.0 - distance_squared / explosion . radius . powi ( 2 )
} ;
2021-01-30 22:35:00 +00:00
if strength > 0.0 {
// 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 ) ,
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 ) ,
2021-02-28 20:02:03 +00:00
stats : stats_b_maybe ,
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 ,
2021-02-28 20:02:03 +00:00
} ;
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 {
// cool guyz maybe don't look at explosions
// but they still got hurt, it's not Hollywood
target_dodging : false ,
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 ,
2021-02-28 20:02:03 +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 ) ,
2021-04-04 03:04:02 +00:00
| o | outcomes . push ( o ) ,
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 > ( ) ;
2021-07-26 13:52:43 +00:00
let players = & ecs . read_storage ::< comp ::Player > ( ) ;
2021-03-15 03:45:55 +00:00
for ( entity_b , pos_b , body_b_maybe ) in (
& ecs . entities ( ) ,
& ecs . read_storage ::< comp ::Pos > ( ) ,
ecs . read_storage ::< comp ::Body > ( ) . maybe ( ) ,
)
. 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
. read_storage ::< comp ::Health > ( )
. 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 ( ) ) {
if let Some ( item ) = comp ::Item ::try_reclaim_from_block ( block ) {
if block_change
. try_set ( pos , block . with_sprite ( SpriteKind ::Empty ) )
. is_some ( )
{
drop ( terrain ) ;
drop ( block_change ) ;
2021-11-13 17:20:23 +00:00
server
2021-08-30 15:02:13 +00:00
. state
2021-09-10 08:34:01 +00:00
. create_object ( Default ::default ( ) , 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 ,
2021-11-03 11:15:20 +00:00
Some ( SpriteKind ::Bomb ) = > comp ::object ::Body ::Bomb ,
2021-09-10 08:34:01 +00:00
_ = > comp ::object ::Body ::Pouch ,
} )
2021-08-30 15:02:13 +00:00
. with ( comp ::Pos ( pos . map ( | e | e as f32 ) + Vec3 ::new ( 0.5 , 0.5 , 0.0 ) ) )
2021-11-13 17:20:23 +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
}
2021-09-10 08:34:01 +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 ( ) ;
let mut auras_all = ecs . write_storage ::< comp ::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 > ( ) ;
2021-02-20 14:03:15 +00:00
let bodies = ecs . read_storage ::< comp ::Body > ( ) ;
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
{
buffs . insert ( new_buff ) ;
}
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 ) ;
}
} ,
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 ,
outcomes : & mut Vec < Outcome > ,
) {
2021-05-27 02:12:35 +00:00
use comp ::inventory ::{ item ::ItemKind , slot ::EquipSlot } ;
// 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 )
. and_then ( | i | match & i . kind ( ) {
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
if skill_set . contains_skill_group ( SkillGroupKind ::Weapon ( weapon ) ) {
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 ( ) {
skill_set . change_experience ( * pool , ( exp_reward / num_pools ) . ceil ( ) as i32 ) ;
2021-01-16 17:01:57 +00:00
}
outcomes . push ( Outcome ::ExpChange {
uid : * uid ,
exp : exp_reward as i32 ,
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 > ( ) ;
let mut outcomes = ecs . write_resource ::< Vec < Outcome > > ( ) ;
combo . change_by ( change , time . 0 ) ;
if let Some ( uid ) = ecs . read_storage ::< Uid > ( ) . get ( entity ) {
outcomes . push ( Outcome ::ComboChange {
uid : * uid ,
combo : combo . counter ( ) ,
} ) ;
2021-02-27 19:55:06 +00:00
}
}
}
2021-03-21 03:28:13 +00:00
2021-09-14 02:16:01 +00:00
pub fn handle_parry ( server : & Server , entity : EcsEntity , energy_cost : f32 ) {
2021-08-31 06:47:37 +00:00
let ecs = & server . state . ecs ( ) ;
if let Some ( mut character ) = ecs . write_storage ::< comp ::CharacterState > ( ) . get_mut ( entity ) {
2021-10-05 00:15:58 +00:00
* character =
CharacterState ::Wielding ( common ::states ::wielding ::Data { is_sneaking : false } ) ;
2021-08-31 06:47:37 +00:00
} ;
if let Some ( mut energy ) = ecs . write_storage ::< Energy > ( ) . get_mut ( entity ) {
2021-09-14 02:16:01 +00:00
energy . change_by ( energy_cost ) ;
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 ;
2021-03-28 01:33:45 +00:00
ecs . write_storage ( )
. insert ( entity , comp ::ForceUpdate )
. err ( )
. map ( | e | {
error! (
? e ,
" Error inserting ForceUpdate component when teleporting client "
)
} ) ;
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 > > ( ) ;
2021-10-29 23:24:40 +00:00
let mut outcomes = ecs . write_resource ::< Vec < Outcome > > ( ) ;
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 ( _ )
) {
2021-10-29 23:24:40 +00:00
let poise_state = comp ::poise ::PoiseState ::Dazed ;
let was_wielded = char_state . is_wield ( ) ;
if let ( Some ( stunned_state ) , impulse_strength ) = poise_state . poise_effect ( was_wielded ) {
// Reset poise if there is some stunned state to apply
poise . reset ( ) ;
* char_state = stunned_state ;
outcomes . push ( Outcome ::PoiseChange {
pos : pos . 0 ,
state : poise_state ,
} ) ;
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 ,
buff_change : buff ::BuffChange ::RemoveByKind ( buff ::BuffKind ::Potion ) ,
} ) ;
server_eventbus . emit_now ( ServerEvent ::Buff {
entity ,
buff_change : buff ::BuffChange ::RemoveByKind ( buff ::BuffKind ::Saturation ) ,
} ) ;
}