From 4078eeb8770fae2cc4004e51ba5628d3bf0393c9 Mon Sep 17 00:00:00 2001 From: Snowram Date: Sat, 30 Jan 2021 15:14:25 +0100 Subject: [PATCH] Various fixes to object animation, cleanup --- .../common/abilities/unique/turret/arrows.ron | 15 + .../turret/{basic.ron => flamethrower.ron} | 10 +- .../abilities/weapon_ability_manifest.ron | 8 +- .../items/npc_weapons/unique/turret.ron | 9 +- assets/voxygen/voxel/object/crossbow.vox | 3 - .../voxygen/voxel/object/crossbow/bone0.vox | 3 + .../voxygen/voxel/object/crossbow/bone1.vox | 3 + assets/voxygen/voxel/object_manifest.ron | 264 ++- common/src/comp/ability.rs | 6 +- common/src/comp/agent.rs | 3 + common/src/comp/body.rs | 6 +- common/src/comp/inventory/item/tool.rs | 2 +- common/src/comp/inventory/loadout_builder.rs | 12 +- common/src/states/basic_beam.rs | 9 +- common/src/states/utils.rs | 2 +- common/sys/src/agent.rs | 1573 ----------------- server/src/sys/agent.rs | 33 + voxygen/anim/src/character/run.rs | 6 +- voxygen/anim/src/object/beam.rs | 45 + voxygen/anim/src/object/idle.rs | 5 +- voxygen/anim/src/object/mod.rs | 21 +- voxygen/anim/src/object/shoot.rs | 56 +- voxygen/src/audio/sfx/mod.rs | 5 +- voxygen/src/ecs/sys/interpolation.rs | 10 +- voxygen/src/scene/figure/load.rs | 18 +- voxygen/src/scene/figure/mod.rs | 34 +- voxygen/src/scene/particle.rs | 18 +- world/src/site/dungeon/mod.rs | 6 + world/src/site/settlement/mod.rs | 6 +- 29 files changed, 519 insertions(+), 1672 deletions(-) create mode 100644 assets/common/abilities/unique/turret/arrows.ron rename assets/common/abilities/unique/turret/{basic.ron => flamethrower.ron} (60%) delete mode 100644 assets/voxygen/voxel/object/crossbow.vox create mode 100644 assets/voxygen/voxel/object/crossbow/bone0.vox create mode 100644 assets/voxygen/voxel/object/crossbow/bone1.vox delete mode 100644 common/sys/src/agent.rs create mode 100644 voxygen/anim/src/object/beam.rs diff --git a/assets/common/abilities/unique/turret/arrows.ron b/assets/common/abilities/unique/turret/arrows.ron new file mode 100644 index 0000000000..f605922937 --- /dev/null +++ b/assets/common/abilities/unique/turret/arrows.ron @@ -0,0 +1,15 @@ +BasicRanged( + energy_cost: 0, + buildup_duration: 1.0, + recover_duration: 0.3, + projectile: Arrow( + damage: 200.0, + knockback: 5.0, + energy_regen: 100, + ), + projectile_body: Object(ArrowTurret), + projectile_light: None, + projectile_gravity: Some(Gravity(0.1)), + projectile_speed: 100.0, + can_continue: true, +) diff --git a/assets/common/abilities/unique/turret/basic.ron b/assets/common/abilities/unique/turret/flamethrower.ron similarity index 60% rename from assets/common/abilities/unique/turret/basic.ron rename to assets/common/abilities/unique/turret/flamethrower.ron index 9a17bb4f0f..512104ffdd 100644 --- a/assets/common/abilities/unique/turret/basic.ron +++ b/assets/common/abilities/unique/turret/flamethrower.ron @@ -1,12 +1,12 @@ BasicBeam( - buildup_duration: 250, - recover_duration: 250, - beam_duration: 500, + buildup_duration: 0.25, + recover_duration: 0.25, + beam_duration: 0.5, base_hps: 0, - base_dps: 150, + base_dps: 9001, tick_rate: 3.0, range: 30.0, - max_angle: 5.0, + max_angle: 1.0, lifesteal_eff: 0.0, energy_regen: 0, energy_cost: 0, diff --git a/assets/common/abilities/weapon_ability_manifest.ron b/assets/common/abilities/weapon_ability_manifest.ron index 9682d4a883..bf5bb437ac 100644 --- a/assets/common/abilities/weapon_ability_manifest.ron +++ b/assets/common/abilities/weapon_ability_manifest.ron @@ -132,10 +132,10 @@ secondary: "common.abilities.unique.theropodbird.triplestrike", abilities: [], ), - Unique(Turret): ( - primary: "common.abilities.unique.turret.basic", - secondary: "common.abilities.unique.turret.basic", - skills: [], + Unique(ObjectTurret): ( + primary: "common.abilities.unique.turret.arrows", + secondary: "common.abilities.unique.turret.arrows", + abilities: [], ), Debug: ( primary: "common.abilities.debug.forwardboost", diff --git a/assets/common/items/npc_weapons/unique/turret.ron b/assets/common/items/npc_weapons/unique/turret.ron index 736cf71775..fcca6452c1 100644 --- a/assets/common/items/npc_weapons/unique/turret.ron +++ b/assets/common/items/npc_weapons/unique/turret.ron @@ -3,13 +3,16 @@ ItemDef( description: "Turret weapon", kind: Tool( ( - kind: Unique(Turret), - stats: ( + kind: Unique(ObjectTurret), + hands: Two, + stats: Direct(( equip_time_millis: 10, power: 1.00, + poise_strength: 1.00, speed: 1.00, - ), + )), ) ), quality: Low, + tags: [], ) diff --git a/assets/voxygen/voxel/object/crossbow.vox b/assets/voxygen/voxel/object/crossbow.vox deleted file mode 100644 index bbf5a5fc0c..0000000000 --- a/assets/voxygen/voxel/object/crossbow.vox +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8f4824ad33b17103b0701734007a231d353cdce3950dbe1f4f61ccd25bb1f480 -size 5784 diff --git a/assets/voxygen/voxel/object/crossbow/bone0.vox b/assets/voxygen/voxel/object/crossbow/bone0.vox new file mode 100644 index 0000000000..2dc5e608ae --- /dev/null +++ b/assets/voxygen/voxel/object/crossbow/bone0.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:adec2cb3277fda6f213e7d2497157cfd00ef661bfb18ba28c0f2746c7dd9ac05 +size 4680 diff --git a/assets/voxygen/voxel/object/crossbow/bone1.vox b/assets/voxygen/voxel/object/crossbow/bone1.vox new file mode 100644 index 0000000000..071738dd1e --- /dev/null +++ b/assets/voxygen/voxel/object/crossbow/bone1.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6387e52b376c6a8a539ba96af23e36deb3a40717ef2db7c9539a7b220d7bd875 +size 2200 diff --git a/assets/voxygen/voxel/object_manifest.ron b/assets/voxygen/voxel/object_manifest.ron index 377d477acf..d136326373 100644 --- a/assets/voxygen/voxel/object_manifest.ron +++ b/assets/voxygen/voxel/object_manifest.ron @@ -3,390 +3,650 @@ bone0: ( offset: (-0.5, -6.0, -1.5), central: ("weapon.projectile.simple-arrow"), + ), + bone1: ( + offset: (0.0, 0.0, 0.0), + central: ("armor.empty"), ) ), Bomb: ( bone0: ( offset: (-5.5, -5.5, 0.0), central: ("object.bomb"), + ), + bone1: ( + offset: (0.0, 0.0, 0.0), + central: ("armor.empty"), ) ), FireworkBlue: ( bone0: ( offset: (0.0, 0.0, 0.0), central: ("weapon.projectile.fireworks_blue-0"), + ), + bone1: ( + offset: (0.0, 0.0, 0.0), + central: ("armor.empty"), ) ), FireworkGreen: ( bone0: ( offset: (0.0, 0.0, 0.0), central: ("weapon.projectile.fireworks_green-0"), + ), + bone1: ( + offset: (0.0, 0.0, 0.0), + central: ("armor.empty"), ) ), FireworkPurple: ( bone0: ( offset: (0.0, 0.0, 0.0), central: ("weapon.projectile.fireworks_purple-0"), + ), + bone1: ( + offset: (0.0, 0.0, 0.0), + central: ("armor.empty"), ) ), FireworkRed: ( bone0: ( offset: (0.0, 0.0, 0.0), central: ("weapon.projectile.fireworks_red-0"), + ), + bone1: ( + offset: (0.0, 0.0, 0.0), + central: ("armor.empty"), ) ), FireworkYellow: ( bone0: ( offset: (0.0, 0.0, 0.0), central: ("weapon.projectile.fireworks_yellow-0"), + ), + bone1: ( + offset: (0.0, 0.0, 0.0), + central: ("armor.empty"), ) ), Scarecrow: ( bone0: ( offset: (-9.5, -4.0, 0.0), central: ("object.scarecrow"), + ), + bone1: ( + offset: (0.0, 0.0, 0.0), + central: ("armor.empty"), ) ), Cauldron: ( bone0: ( offset: (-10.0, -10.0, 0.0), central: ("object.cauldron"), + ), + bone1: ( + offset: (0.0, 0.0, 0.0), + central: ("armor.empty"), ) ), ChestVines: ( bone0: ( offset: (-7.5, -6.0, 0.0), central: ("object.chest_vines"), + ), + bone1: ( + offset: (0.0, 0.0, 0.0), + central: ("armor.empty"), ) ), Chest: ( bone0: ( offset: (-7.5, -6.0, 0.0), central: ("object.chest"), + ), + bone1: ( + offset: (0.0, 0.0, 0.0), + central: ("armor.empty"), ) ), ChestDark: ( bone0: ( offset: (-7.5, -6.0, 0.0), central: ("object.chest_dark"), + ), + bone1: ( + offset: (0.0, 0.0, 0.0), + central: ("armor.empty"), ) ), ChestDemon: ( bone0: ( offset: (-7.5, -6.0, 0.0), central: ("object.chest_demon"), + ), + bone1: ( + offset: (0.0, 0.0, 0.0), + central: ("armor.empty"), ) ), ChestGold: ( bone0: ( offset: (-7.5, -6.0, 0.0), central: ("object.chest_gold"), + ), + bone1: ( + offset: (0.0, 0.0, 0.0), + central: ("armor.empty"), ) ), ChestLight: ( bone0: ( offset: (-7.5, -6.0, 0.0), central: ("object.chest_light"), + ), + bone1: ( + offset: (0.0, 0.0, 0.0), + central: ("armor.empty"), ) ), ChestOpen: ( bone0: ( offset: (-7.5, -6.0, 0.0), central: ("object.chest_open"), + ), + bone1: ( + offset: (0.0, 0.0, 0.0), + central: ("armor.empty"), ) ), ChestSkull: ( bone0: ( offset: (-7.5, -6.0, 0.0), central: ("object.chest_skull"), + ), + bone1: ( + offset: (0.0, 0.0, 0.0), + central: ("armor.empty"), ) ), Pumpkin: ( bone0: ( offset: (-5.5, -4.0, 0.0), central: ("object.pumpkin"), + ), + bone1: ( + offset: (0.0, 0.0, 0.0), + central: ("armor.empty"), ) ), Pumpkin2: ( bone0: ( offset: (-5.0, -4.0, 0.0), central: ("object.pumpkin_2"), + ), + bone1: ( + offset: (0.0, 0.0, 0.0), + central: ("armor.empty"), ) ), Pumpkin3: ( bone0: ( offset: (-5.0, -4.0, 0.0), central: ("object.pumpkin_3"), + ), + bone1: ( + offset: (0.0, 0.0, 0.0), + central: ("armor.empty"), ) ), Pumpkin4: ( bone0: ( offset: (-5.0, -4.0, 0.0), central: ("object.pumpkin_4"), + ), + bone1: ( + offset: (0.0, 0.0, 0.0), + central: ("armor.empty"), ) ), Pumpkin5: ( bone0: ( offset: (-4.0, -5.0, 0.0), central: ("object.pumpkin_5"), + ), + bone1: ( + offset: (0.0, 0.0, 0.0), + central: ("armor.empty"), ) ), Campfire: ( bone0: ( offset: (-9.0, -10.0, 0.0), central: ("object.campfire"), + ), + bone1: ( + offset: (0.0, 0.0, 0.0), + central: ("armor.empty"), ) ), CampfireLit: ( bone0: ( offset: (-9.0, -10.0, 0.0), central: ("object.campfire_lit"), + ), + bone1: ( + offset: (0.0, 0.0, 0.0), + central: ("armor.empty"), ) ), LanternGround: ( bone0: ( offset: (-3.5, -3.5, 0.0), central: ("object.lantern_ground"), + ), + bone1: ( + offset: (0.0, 0.0, 0.0), + central: ("armor.empty"), ) ), LanternGroundOpen: ( bone0: ( offset: (-3.5, -3.5, 0.0), central: ("object.lantern_ground_open"), + ), + bone1: ( + offset: (0.0, 0.0, 0.0), + central: ("armor.empty"), ) ), LanternStanding: ( bone0: ( offset: (-7.5, -3.5, 0.0), central: ("object.lantern_standing"), + ), + bone1: ( + offset: (0.0, 0.0, 0.0), + central: ("armor.empty"), ) ), LanternStanding2: ( bone0: ( offset: (-11.5, -3.5, 0.0), central: ("object.lantern_standing_2"), + ), + bone1: ( + offset: (0.0, 0.0, 0.0), + central: ("armor.empty"), ) ), PotionRed: ( bone0: ( offset: (-2.0, -2.0, 0.0), central: ("object.potion_red"), + ), + bone1: ( + offset: (0.0, 0.0, 0.0), + central: ("armor.empty"), ) ), PotionBlue: ( bone0: ( offset: (-2.0, -2.0, 0.0), central: ("object.potion_blue"), + ), + bone1: ( + offset: (0.0, 0.0, 0.0), + central: ("armor.empty"), ) ), PotionGreen: ( bone0: ( offset: (-2.0, -2.0, 0.0), central: ("object.potion_green"), + ), + bone1: ( + offset: (0.0, 0.0, 0.0), + central: ("armor.empty"), ) ), Crate: ( bone0: ( offset: (-7.0, -7.0, 0.0), central: ("object.crate"), + ), + bone1: ( + offset: (0.0, 0.0, 0.0), + central: ("armor.empty"), ) ), Tent: ( bone0: ( offset: (-18.5, -19.5, 0.0), central: ("object.tent"), + ), + bone1: ( + offset: (0.0, 0.0, 0.0), + central: ("armor.empty"), ) ), WindowSpooky: ( bone0: ( offset: (-15.0, -1.5, -1.0), central: ("object.window_spooky"), + ), + bone1: ( + offset: (0.0, 0.0, 0.0), + central: ("armor.empty"), ) ), DoorSpooky: ( bone0: ( offset: (-15.0, -4.5, 0.0), central: ("object.door_spooky"), + ), + bone1: ( + offset: (0.0, 0.0, 0.0), + central: ("armor.empty"), ) ), Table: ( bone0: ( offset: (-12.0, -8.0, 0.0), central: ("object.table"), + ), + bone1: ( + offset: (0.0, 0.0, 0.0), + central: ("armor.empty"), ) ), Table2: ( bone0: ( offset: (-8.0, -8.0, 0.0), central: ("object.table_2"), + ), + bone1: ( + offset: (0.0, 0.0, 0.0), + central: ("armor.empty"), ) ), Table3: ( bone0: ( offset: (-10.0, -10.0, 0.0), central: ("object.table_3"), + ), + bone1: ( + offset: (0.0, 0.0, 0.0), + central: ("armor.empty"), ) ), Drawer: ( bone0: ( offset: (-11.0, -7.5, 0.0), central: ("object.drawer"), + ), + bone1: ( + offset: (0.0, 0.0, 0.0), + central: ("armor.empty"), ) ), BedBlue: ( bone0: ( offset: (-11.0, -15.0, 0.0), central: ("object.bed_human_blue"), + ), + bone1: ( + offset: (0.0, 0.0, 0.0), + central: ("armor.empty"), ) ), Anvil: ( bone0: ( offset: (-3.0, -7.0, 0.0), central: ("object.anvil"), + ), + bone1: ( + offset: (0.0, 0.0, 0.0), + central: ("armor.empty"), ) ), Gravestone: ( bone0: ( offset: (-5.0, -2.0, 0.0), central: ("object.gravestone"), + ), + bone1: ( + offset: (0.0, 0.0, 0.0), + central: ("armor.empty"), ) ), Gravestone2: ( bone0: ( offset: (-8.5, -3.0, 0.0), central: ("object.gravestone_2"), + ), + bone1: ( + offset: (0.0, 0.0, 0.0), + central: ("armor.empty"), ) ), Chair: ( bone0: ( offset: (-5.0, -4.5, 0.0), central: ("object.chair"), + ), + bone1: ( + offset: (0.0, 0.0, 0.0), + central: ("armor.empty"), ) ), Chair2: ( bone0: ( offset: (-5.0, -4.5, 0.0), central: ("object.chair_2"), + ), + bone1: ( + offset: (0.0, 0.0, 0.0), + central: ("armor.empty"), ) ), Chair3: ( bone0: ( offset: (-5.0, -4.5, 0.0), central: ("object.chair_3"), + ), + bone1: ( + offset: (0.0, 0.0, 0.0), + central: ("armor.empty"), ) ), Bench: ( bone0: ( offset: (-8.8, -5.0, 0.0), central: ("object.bench"), + ), + bone1: ( + offset: (0.0, 0.0, 0.0), + central: ("armor.empty"), ) ), Carpet: ( bone0: ( offset: (-14.0, -14.0, -0.5), central: ("object.carpet"), + ), + bone1: ( + offset: (0.0, 0.0, 0.0), + central: ("armor.empty"), ) ), Bedroll: ( bone0: ( offset: (-11.0, -19.5, -0.5), central: ("object.bedroll"), + ), + bone1: ( + offset: (0.0, 0.0, 0.0), + central: ("armor.empty"), ) ), CarpetHumanRound: ( bone0: ( offset: (-14.0, -14.0, -0.5), central: ("object.carpet_human_round"), + ), + bone1: ( + offset: (0.0, 0.0, 0.0), + central: ("armor.empty"), ) ), CarpetHumanSquare: ( bone0: ( offset: (-13.5, -14.0, -0.5), central: ("object.carpet_human_square"), + ), + bone1: ( + offset: (0.0, 0.0, 0.0), + central: ("armor.empty"), ) ), CarpetHumanSquare2: ( bone0: ( offset: (-13.5, -14.0, -0.5), central: ("object.carpet_human_square_2"), + ), + bone1: ( + offset: (0.0, 0.0, 0.0), + central: ("armor.empty"), ) ), CarpetHumanSquircle: ( bone0: ( offset: (-21.0, -21.0, -0.5), central: ("object.carpet_human_squircle"), + ), + bone1: ( + offset: (0.0, 0.0, 0.0), + central: ("armor.empty"), ) ), Pouch: ( bone0: ( offset: (-5.5, -4.5, 0.0), central: ("object.pouch"), + ), + bone1: ( + offset: (0.0, 0.0, 0.0), + central: ("armor.empty"), ) ), CraftingBench: ( bone0: ( offset: (-9.5, -7.0, 0.0), central: ("object.crafting_bench"), + ), + bone1: ( + offset: (0.0, 0.0, 0.0), + central: ("armor.empty"), ) ), ArrowSnake: ( bone0: ( offset: (-1.5, -6.5, 0.0), central: ("weapon.projectile.snake-arrow"), + ), + bone1: ( + offset: (0.0, 0.0, 0.0), + central: ("armor.empty"), ) ), BoltFire: ( bone0: ( offset: (-3.0, -5.5, -3.0), central: ("weapon.projectile.fire-bolt-0"), + ), + bone1: ( + offset: (0.0, 0.0, 0.0), + central: ("armor.empty"), ) ), BoltFireBig: ( bone0: ( offset: (-6.0, -6.0, -6.0), central: ("weapon.projectile.fire-bolt-1"), + ), + bone1: ( + offset: (0.0, 0.0, 0.0), + central: ("armor.empty"), ) ), TrainingDummy: ( bone0: ( offset: (-7.0, -5.0, 0.0), central: ("object.training_dummy"), + ), + bone1: ( + offset: (0.0, 0.0, 0.0), + central: ("armor.empty"), ) ), MultiArrow: ( bone0: ( offset: (-4.0, -9.5, -5.0), central: ("weapon.projectile.multi-arrow"), + ), + bone1: ( + offset: (0.0, 0.0, 0.0), + central: ("armor.empty"), ) ), BoltNature: ( bone0: ( offset: (-6.0, -6.0, -6.0), central: ("weapon.projectile.nature-bolt"), + ), + bone1: ( + offset: (0.0, 0.0, 0.0), + central: ("armor.empty"), ) ), MeatDrop: ( bone0: ( offset: (-3.5, -8.0, 0.0), central: ("object.meat_drop"), + ), + bone1: ( + offset: (0.0, 0.0, 0.0), + central: ("armor.empty"), ) ), Steak: ( bone0: ( offset: (-3.5, -6.0, 0.0), central: ("object.steak"), + ), + bone1: ( + offset: (0.0, 0.0, 0.0), + central: ("armor.empty"), ) ), Crossbow: ( bone0: ( - offset: (-18.0, -15.5, 0.0), - central: ("object.crossbow"), + offset: (-18.0, -15.5, -8.0), + central: ("object.crossbow.bone0"), + ), + bone1: ( + offset: (-9.0, -7.0, -5.0 ), + central: ("object.crossbow.bone1"), ) ), ArrowTurret: ( bone0: ( offset: (-1.5, -6.5, -1.5), central: ("weapon.projectile.turret-arrow"), + ), + bone1: ( + offset: (0.0, 0.0, 0.0), + central: ("armor.empty"), ) ), }) \ No newline at end of file diff --git a/common/src/comp/ability.rs b/common/src/comp/ability.rs index 17adf25c74..4e7f1325ff 100644 --- a/common/src/comp/ability.rs +++ b/common/src/comp/ability.rs @@ -222,9 +222,9 @@ pub enum CharacterAbility { range: f32, max_angle: f32, lifesteal_eff: f32, - energy_regen: u32, - energy_cost: u32, - energy_drain: u32, + energy_regen: f32, + energy_cost: f32, + energy_drain: f32, orientation_behavior: basic_beam::MovementBehavior, }, } diff --git a/common/src/comp/agent.rs b/common/src/comp/agent.rs index 43fe21911f..b80c4b1c06 100644 --- a/common/src/comp/agent.rs +++ b/common/src/comp/agent.rs @@ -29,6 +29,9 @@ pub enum Tactic { QuadMedBasic, Lavadrake, Theropod, + Turret, + FixedTurret, + RotatingTurret, } #[derive(Copy, Clone, Debug, PartialEq)] diff --git a/common/src/comp/body.rs b/common/src/comp/body.rs index b814ce23ee..742a487adb 100644 --- a/common/src/comp/body.rs +++ b/common/src/comp/body.rs @@ -339,7 +339,11 @@ impl Body { biped_large::Species::Mindflayer => 8000, _ => 1000, }, - Body::Object(_) => 10000, + Body::Object(object) => match object { + object::Body::TrainingDummy => 10000, + object::Body::Crossbow => 800, + _ => 10000, + }, Body::Golem(_) => 2740, Body::Theropod(theropod) => match theropod.species { theropod::Species::Archaeos => 3000, diff --git a/common/src/comp/inventory/item/tool.rs b/common/src/comp/inventory/item/tool.rs index 4f95fabb40..c7eefbb07a 100644 --- a/common/src/comp/inventory/item/tool.rs +++ b/common/src/comp/inventory/item/tool.rs @@ -295,5 +295,5 @@ pub enum UniqueKind { QuadSmallBasic, TheropodBasic, TheropodBird, - Turret, + ObjectTurret, } diff --git a/common/src/comp/inventory/loadout_builder.rs b/common/src/comp/inventory/loadout_builder.rs index 5194ada70f..ac7b8ab904 100644 --- a/common/src/comp/inventory/loadout_builder.rs +++ b/common/src/comp/inventory/loadout_builder.rs @@ -227,14 +227,10 @@ impl LoadoutBuilder { )); }, }, - #[allow(clippy::collapsible_match)] // to be removed when more entries are added - Body::Object(object) => match object { - object::Body::Crossbow => { - main_tool = Some(Item::new_from_asset_expect( - "common.items.npc_weapons.unique.turret", - )); - }, - _ => {}, + Body::Object(object::Body::Crossbow) => { + main_tool = Some(Item::new_from_asset_expect( + "common.items.npc_weapons.unique.turret", + )); }, _ => {}, }; diff --git a/common/src/states/basic_beam.rs b/common/src/states/basic_beam.rs index c62741efe3..3a740dcb22 100644 --- a/common/src/states/basic_beam.rs +++ b/common/src/states/basic_beam.rs @@ -10,7 +10,6 @@ use crate::{ utils::*, }, uid::Uid, - Damage, DamageSource, GroupTarget, }; use serde::{Deserialize, Serialize}; use std::time::Duration; @@ -43,7 +42,7 @@ pub struct StaticData { /// Energy consumed per second for heal ticks pub energy_cost: f32, /// Energy drained per - pub energy_drain: u32, + pub energy_drain: f32, /// Used to dictate how orientation functions in this state pub orientation_behavior: MovementBehavior, /// What key is used to press ability @@ -70,7 +69,7 @@ impl CharacterBehavior for Data { match self.static_data.orientation_behavior { MovementBehavior::Normal => {}, MovementBehavior::Turret => { - update.ori.0 = data.inputs.look_dir; + update.ori = Ori::from(data.inputs.look_dir); }, } @@ -106,8 +105,8 @@ impl CharacterBehavior for Data { }); // Gets offsets let body_offsets = Vec3::new( - (data.body.radius() + 1.0) * data.ori.0.x, - (data.body.radius() + 1.0) * data.ori.0.y, + (data.body.radius() + 1.0) * data.inputs.look_dir.x, + (data.body.radius() + 1.0) * data.inputs.look_dir.y, data.body.eye_height(), ) * 0.55; // Build up diff --git a/common/src/states/utils.rs b/common/src/states/utils.rs index 0374e32df0..a37682adba 100644 --- a/common/src/states/utils.rs +++ b/common/src/states/utils.rs @@ -116,7 +116,7 @@ impl Body { Body::BirdSmall(_) => 35.0, Body::FishSmall(_) => 10.0, Body::BipedLarge(_) => 12.0, - Body::Object(_) => 0.0, + Body::Object(_) => 10.0, Body::Golem(_) => 8.0, Body::Theropod(theropod) => match theropod.species { theropod::Species::Archaeos => 2.5, diff --git a/common/sys/src/agent.rs b/common/sys/src/agent.rs deleted file mode 100644 index b17169ef85..0000000000 --- a/common/sys/src/agent.rs +++ /dev/null @@ -1,1573 +0,0 @@ -use common::{ - comp::{ - self, - agent::Activity, - group, - group::Invite, - inventory::slot::EquipSlot, - item::{ - tool::{ToolKind, UniqueKind}, - ItemKind, - }, - skills::{AxeSkill, BowSkill, HammerSkill, Skill, StaffSkill, SwordSkill}, - Agent, Alignment, Body, CharacterState, ControlAction, ControlEvent, Controller, Energy, - GroupManip, Health, Inventory, LightEmitter, MountState, Ori, PhysicsState, Pos, Scale, - Stats, UnresolvedChatMsg, Vel, - }, - event::{EventBus, ServerEvent}, - metrics::SysMetrics, - path::{Chaser, TraversalConfig}, - resources::{DeltaTime, Time, TimeOfDay}, - span, - terrain::{Block, TerrainGrid}, - time::DayPeriod, - uid::{Uid, UidAllocator}, - util::Dir, - vol::ReadVol, -}; -use rand::{thread_rng, Rng}; -use rayon::iter::ParallelIterator; -use specs::{ - saveload::{Marker, MarkerAllocator}, - Entities, Join, ParJoin, Read, ReadExpect, ReadStorage, System, Write, WriteStorage, -}; -use std::f32::consts::PI; -use vek::*; - -/// This system will allow NPCs to modify their controller -pub struct Sys; -impl<'a> System<'a> for Sys { - #[allow(clippy::type_complexity)] - type SystemData = ( - ( - Read<'a, UidAllocator>, - Read<'a, Time>, - Read<'a, DeltaTime>, - Read<'a, group::GroupManager>, - ), - ReadExpect<'a, SysMetrics>, - Write<'a, EventBus>, - Entities<'a>, - ReadStorage<'a, Energy>, - ReadStorage<'a, Pos>, - ReadStorage<'a, Vel>, - ReadStorage<'a, Ori>, - ReadStorage<'a, Scale>, - ReadStorage<'a, Health>, - ReadStorage<'a, Inventory>, - ReadStorage<'a, Stats>, - ReadStorage<'a, PhysicsState>, - ReadStorage<'a, Uid>, - ReadStorage<'a, group::Group>, - ReadExpect<'a, TerrainGrid>, - ReadStorage<'a, Alignment>, - ReadStorage<'a, Body>, - WriteStorage<'a, Agent>, - WriteStorage<'a, Controller>, - ReadStorage<'a, MountState>, - ReadStorage<'a, Invite>, - Read<'a, TimeOfDay>, - ReadStorage<'a, LightEmitter>, - ReadStorage<'a, CharacterState>, - ); - - #[allow(clippy::or_fun_call)] // TODO: Pending review in #587 - fn run( - &mut self, - ( - (uid_allocator, time, dt, group_manager), - sys_metrics, - event_bus, - entities, - energies, - positions, - velocities, - orientations, - scales, - healths, - inventories, - stats, - physics_states, - uids, - groups, - terrain, - alignments, - bodies, - mut agents, - mut controllers, - mount_states, - invites, - time_of_day, - light_emitter, - char_states, - ): Self::SystemData, - ) { - let start_time = std::time::Instant::now(); - span!(_guard, "run", "agent::Sys::run"); - - ( - &entities, - &energies, - &positions, - &velocities, - &orientations, - alignments.maybe(), - &inventories, - &stats, - &physics_states, - bodies.maybe(), - &uids, - &mut agents, - &mut controllers, - mount_states.maybe(), - groups.maybe(), - light_emitter.maybe(), - ) - .par_join() - .filter(|(_, _, _, _, _, _, _, _, _, _, _, _, _, mount_state, _, _)| { - // Skip mounted entities - mount_state.map(|ms| *ms == MountState::Unmounted).unwrap_or(true) - }) - .for_each(|( - entity, - energy, - pos, - vel, - ori, - alignment, - inventory, - stats, - physics_state, - body, - uid, - agent, - controller, - _, - group, - light_emitter, - )| { - // Hack, replace with better system when groups are more sophisticated - // Override alignment if in a group unless entity is owned already - let alignment = if !matches!(alignment, Some(Alignment::Owned(_))) { - group - .and_then(|g| group_manager.group_info(*g)) - .and_then(|info| uids.get(info.leader)) - .copied() - .map(Alignment::Owned) - .or(alignment.copied()) - } else { - alignment.copied() - }; - - controller.reset(); - let mut event_emitter = event_bus.emitter(); - // Light lanterns at night - // TODO Add a method to turn on NPC lanterns underground - let lantern_equipped = inventory.equipped(EquipSlot::Lantern).as_ref().map_or(false, |item| { - matches!(item.kind(), comp::item::ItemKind::Lantern(_)) - }); - let lantern_turned_on = light_emitter.is_some(); - let day_period = DayPeriod::from(time_of_day.0); - // Only emit event for agents that have a lantern equipped - if lantern_equipped { - let mut rng = thread_rng(); - if day_period.is_dark() && !lantern_turned_on { - // Agents with turned off lanterns turn them on randomly once it's nighttime and - // keep them on - // Only emit event for agents that sill need to - // turn on their lantern - if let 0 = rng.gen_range(0..1000) { - controller.events.push(ControlEvent::EnableLantern) - } - } else if lantern_turned_on && day_period.is_light() { - // agents with turned on lanterns turn them off randomly once it's daytime and - // keep them off - if let 0 = rng.gen_range(0..2000) { - controller.events.push(ControlEvent::DisableLantern) - } - } - }; - - let mut inputs = &mut controller.inputs; - - // Default to looking in orientation direction (can be overridden below) - inputs.look_dir = ori.0; - - const AVG_FOLLOW_DIST: f32 = 6.0; - const MAX_FOLLOW_DIST: f32 = 12.0; - const MAX_CHASE_DIST: f32 = 18.0; - const LISTEN_DIST: f32 = 16.0; - const SEARCH_DIST: f32 = 48.0; - const SIGHT_DIST: f32 = 80.0; - const MAX_FLEE_DIST: f32 = 20.0; - const SNEAK_COEFFICIENT: f32 = 0.25; - - let scale = scales.get(entity).map(|s| s.0).unwrap_or(1.0); - - let min_attack_dist = body.map_or(2.0, |b| b.radius() * scale * 1.5); - - // This controls how picky NPCs are about their pathfinding. Giants are larger - // and so can afford to be less precise when trying to move around - // the world (especially since they would otherwise get stuck on - // obstacles that smaller entities would not). - let node_tolerance = scale * 1.5; - let slow_factor = body.map(|b| b.base_accel() / 250.0).unwrap_or(0.0).min(1.0); - let traversal_config = TraversalConfig { - node_tolerance, - slow_factor, - on_ground: physics_state.on_ground, - in_liquid: physics_state.in_liquid.is_some(), - min_tgt_dist: 1.0, - can_climb: body.map(|b| b.can_climb()).unwrap_or(false), - can_fly: body.map(|b| b.can_fly()).unwrap_or(false), - }; - - let mut do_idle = false; - let mut choose_target = false; - - 'activity: { - match &mut agent.activity { - Activity::Idle { bearing, chaser } => { - if let Some(travel_to) = agent.rtsim_controller.travel_to { - // if it has an rtsim destination and can fly then it should - // if it is flying and bumps something above it then it should move down - inputs.fly.set_state(traversal_config.can_fly && !terrain - .ray( - pos.0, - pos.0 + (Vec3::unit_z() * 3.0)) - .until(Block::is_solid) - .cast() - .1 - .map_or(true, |b| b.is_some())); - if let Some((bearing, speed)) = - chaser.chase(&*terrain, pos.0, vel.0, travel_to, TraversalConfig { - min_tgt_dist: 1.25, - ..traversal_config - }) - { - inputs.move_dir = - bearing.xy().try_normalized().unwrap_or(Vec2::zero()) - * speed.min(agent.rtsim_controller.speed_factor); - inputs.jump.set_state(bearing.z > 1.5 || traversal_config.can_fly && traversal_config.on_ground); - inputs.climb = Some(comp::Climb::Up); - //.filter(|_| bearing.z > 0.1 || physics_state.in_liquid.is_some()); - - inputs.move_z = bearing.z + if traversal_config.can_fly { - if terrain - .ray( - pos.0 + Vec3::unit_z(), - pos.0 - + bearing - .try_normalized() - .unwrap_or(Vec3::unit_y()) - * 60.0 - + Vec3::unit_z(), - ) - .until(Block::is_solid) - .cast() - .1 - .map_or(true, |b| b.is_some()) - { - 1.0 //fly up when approaching obstacles - } else { -0.1 } //flying things should slowly come down from the stratosphere - } else { - 0.05 //normal land traveller offset - }; - } - } else { - *bearing += Vec2::new( - thread_rng().gen::() - 0.5, - thread_rng().gen::() - 0.5, - ) * 0.1 - - *bearing * 0.003 - - agent.patrol_origin.map_or(Vec2::zero(), |patrol_origin| { - (pos.0 - patrol_origin).xy() * 0.0002 - }); - - // Stop if we're too close to a wall - *bearing *= 0.1 - + if terrain - .ray( - pos.0 + Vec3::unit_z(), - pos.0 - + Vec3::from(*bearing) - .try_normalized() - .unwrap_or(Vec3::unit_y()) - * 5.0 - + Vec3::unit_z(), - ) - .until(Block::is_solid) - .cast() - .1 - .map_or(true, |b| b.is_none()) - { - 0.9 - } else { - 0.0 - }; - - if bearing.magnitude_squared() > 0.5f32.powi(2) { - inputs.move_dir = *bearing * 0.65; - } - - // Put away weapon - if thread_rng().gen::() < 0.005 { - controller.actions.push(ControlAction::Unwield); - } - - // Sit - if thread_rng().gen::() < 0.0035 { - controller.actions.push(ControlAction::Sit); - } - } - - controller.actions.push(ControlAction::Unwield); - - // Sometimes try searching for new targets - if thread_rng().gen::() < 0.1 { - choose_target = true; - } - }, - Activity::Follow { target, chaser } => { - if let (Some(tgt_pos), _tgt_health) = - (positions.get(*target), healths.get(*target)) - { - let dist = pos.0.distance(tgt_pos.0); - // Follow, or return to idle - if dist > AVG_FOLLOW_DIST { - if let Some((bearing, speed)) = chaser.chase( - &*terrain, - pos.0, - vel.0, - tgt_pos.0, - TraversalConfig { - min_tgt_dist: AVG_FOLLOW_DIST, - ..traversal_config - }, - ) { - inputs.move_dir = - bearing.xy().try_normalized().unwrap_or(Vec2::zero()) - * speed.min(0.2 + (dist - AVG_FOLLOW_DIST) / 8.0); - inputs.jump.set_state(bearing.z > 1.5); - inputs.move_z = bearing.z; - } - } else { - do_idle = true; - } - } else { - do_idle = true; - } - }, - Activity::Attack { - target, - chaser, - been_close, - powerup, - .. - } => { - #[derive(Eq, PartialEq)] - enum Tactic { - Melee, - Axe, - Hammer, - Sword, - Bow, - Staff, - StoneGolemBoss, - CircleCharge { radius: u32, circle_time: u32 }, - QuadLowRanged, - TailSlap, - QuadLowQuick, - QuadLowBasic, - QuadMedJump, - QuadMedBasic, - Lavadrake, - Theropod, - Turret, - } - - let tactic = match inventory.equipped(EquipSlot::Mainhand).as_ref().and_then(|item| { - if let ItemKind::Tool(tool) = &item.kind() { - Some(&tool.kind) - } else { - None - } - }) { - Some(ToolKind::Bow) => Tactic::Bow, - Some(ToolKind::Staff) => Tactic::Staff, - Some(ToolKind::Hammer) => Tactic::Hammer, - Some(ToolKind::Sword) => Tactic::Sword, - Some(ToolKind::Axe) => Tactic::Axe, - Some(ToolKind::Unique(UniqueKind::StoneGolemFist)) => { - Tactic::StoneGolemBoss - }, - Some(ToolKind::Unique(UniqueKind::QuadMedQuick)) => { - Tactic::CircleCharge { - radius: 3, - circle_time: 2, - } - }, - Some(ToolKind::Unique(UniqueKind::QuadMedCharge)) => { - Tactic::CircleCharge { - radius: 15, - circle_time: 1, - } - }, - - Some(ToolKind::Unique(UniqueKind::QuadMedJump)) => Tactic::QuadMedJump, - Some(ToolKind::Unique(UniqueKind::QuadMedBasic)) => { - Tactic::QuadMedBasic - }, - Some(ToolKind::Unique(UniqueKind::QuadLowRanged)) => { - Tactic::QuadLowRanged - }, - Some(ToolKind::Unique(UniqueKind::QuadLowTail)) => Tactic::TailSlap, - Some(ToolKind::Unique(UniqueKind::QuadLowQuick)) => { - Tactic::QuadLowQuick - }, - Some(ToolKind::Unique(UniqueKind::QuadLowBasic)) => { - Tactic::QuadLowBasic - }, - Some(ToolKind::Unique(UniqueKind::QuadLowBreathe)) => Tactic::Lavadrake, - Some(ToolKind::Unique(UniqueKind::TheropodBasic)) => Tactic::Theropod, - Some(ToolKind::Unique(UniqueKind::TheropodBird)) => Tactic::Theropod, - Some(ToolKind::Unique(UniqueKind::Turret)) => Tactic::Turret, - _ => Tactic::Melee, - }; - - if let (Some(tgt_pos), Some(tgt_health), tgt_alignment) = ( - positions.get(*target), - healths.get(*target), - alignments.get(*target).copied().unwrap_or( - uids.get(*target) - .copied() - .map(Alignment::Owned) - .unwrap_or(Alignment::Wild), - ), - ) { - // Wield the weapon as running towards the target - controller.actions.push(ControlAction::Wield); - - let eye_offset = body.map_or(0.0, |b| b.eye_height()); - - let tgt_eye_offset = bodies.get(*target).map_or(0.0, |b| b.eye_height()) + - // Special case for jumping attacks to jump at the body - // of the target and not the ground around the target - // For the ranged it is to shoot at the feet and not - // the head to get splash damage - if tactic == Tactic::QuadMedJump { - 1.0 - } else if matches!(tactic, Tactic::QuadLowRanged) { - -1.0 - } else { - 0.0 - }; - - // Hacky distance offset for ranged weapons - let distance_offset = match tactic { - Tactic::Bow => 0.0004 /* Yay magic numbers */ * pos.0.distance_squared(tgt_pos.0), - Tactic::Staff => 0.0015 /* Yay magic numbers */ * pos.0.distance_squared(tgt_pos.0), - Tactic::QuadLowRanged => 0.03 /* Yay magic numbers */ * pos.0.distance_squared(tgt_pos.0), - _ => 0.0, - }; - - // Apply the distance and eye offsets to make the - // look_dir the vector from projectile launch to - // target point - if let Some(dir) = Dir::from_unnormalized( - Vec3::new( - tgt_pos.0.x, - tgt_pos.0.y, - tgt_pos.0.z + tgt_eye_offset + distance_offset, - ) - Vec3::new(pos.0.x, pos.0.y, pos.0.z + eye_offset), - ) { - inputs.look_dir = dir; - } - - // Don't attack entities we are passive towards - // TODO: This is here, it's a bit of a hack - if let Some(alignment) = alignment { - if alignment.passive_towards(tgt_alignment) || tgt_health.is_dead { - do_idle = true; - break 'activity; - } - } - - let dist_sqrd = pos.0.distance_squared(tgt_pos.0); - - let damage = healths - .get(entity) - .map(|h| h.current() as f32 / h.maximum() as f32) - .unwrap_or(0.5); - - // Flee - let flees = alignment - .map(|a| !matches!(a, Alignment::Enemy | Alignment::Owned(_))) - .unwrap_or(true); - if 1.0 - agent.psyche.aggro > damage && flees { - if let Some(body) = body { - if body.can_strafe() { - controller.actions.push(ControlAction::Unwield); - } - } - if dist_sqrd < MAX_FLEE_DIST.powi(2) { - if let Some((bearing, speed)) = chaser.chase( - &*terrain, - pos.0, - vel.0, - // Away from the target (ironically) - pos.0 - + (pos.0 - tgt_pos.0) - .try_normalized() - .unwrap_or_else(Vec3::unit_y) - * 50.0, - TraversalConfig { - min_tgt_dist: 1.25, - ..traversal_config - }, - ) { - inputs.move_dir = - bearing.xy().try_normalized().unwrap_or(Vec2::zero()) - * speed; - inputs.jump.set_state(bearing.z > 1.5); - inputs.move_z = bearing.z; - } - } else { - do_idle = true; - } - } else { - // Match on tactic. Each tactic has different controls - // depending on the distance from the agent to the target - match tactic { - Tactic::Melee => { - if dist_sqrd < (min_attack_dist * scale).powi(2) { - inputs.primary.set_state(true); - inputs.move_dir = Vec2::zero(); - } else if dist_sqrd < MAX_CHASE_DIST.powi(2) - || (dist_sqrd < SIGHT_DIST.powi(2) && !*been_close) - { - if dist_sqrd < MAX_CHASE_DIST.powi(2) { - *been_close = true; - } - if let Some((bearing, speed)) = chaser.chase( - &*terrain, - pos.0, - vel.0, - tgt_pos.0, - TraversalConfig { - min_tgt_dist: 1.25, - ..traversal_config - }, - ) { - inputs.move_dir = bearing - .xy() - .try_normalized() - .unwrap_or(Vec2::zero()) - * speed; - inputs.jump.set_state(bearing.z > 1.5); - inputs.move_z = bearing.z; - } - - if body.map(|b| b.is_humanoid()).unwrap_or(false) && dist_sqrd < 16.0f32.powi(2) - && thread_rng().gen::() < 0.02 - { - inputs.roll.set_state(true); - } - } else { - do_idle = true; - } - }, - Tactic::Axe => { - if dist_sqrd < (min_attack_dist * scale).powi(2) { - inputs.move_dir = Vec2::zero(); - if *powerup > 6.0 { - inputs.secondary.set_state(false); - *powerup = 0.0; - } else if *powerup > 4.0 && energy.current() > 10 { - inputs.secondary.set_state(true); - *powerup += dt.0; - } else if stats.skill_set.has_skill(Skill::Axe(AxeSkill::UnlockLeap)) && energy.current() > 800 && thread_rng().gen_bool(0.5) { - inputs.ability3.set_state(true); - *powerup += dt.0; - } else { - inputs.primary.set_state(true); - *powerup += dt.0; - } - } else if dist_sqrd < MAX_CHASE_DIST.powi(2) - || (dist_sqrd < SIGHT_DIST.powi(2) && !*been_close) - { - if dist_sqrd < MAX_CHASE_DIST.powi(2) { - *been_close = true; - } - if let Some((bearing, speed)) = chaser.chase( - &*terrain, - pos.0, - vel.0, - tgt_pos.0, - TraversalConfig { - min_tgt_dist: 1.25, - ..traversal_config - }, - ) { - inputs.move_dir = bearing - .xy() - .try_normalized() - .unwrap_or(Vec2::zero()) - * speed; - inputs.jump.set_state(bearing.z > 1.5); - inputs.move_z = bearing.z; - } - if body.map(|b| b.is_humanoid()).unwrap_or(false) && dist_sqrd < 16.0f32.powi(2) - && thread_rng().gen::() < 0.02 - { - inputs.roll.set_state(true); - } - } else { - do_idle = true; - } - }, - Tactic::Hammer => { - if dist_sqrd < (min_attack_dist * scale).powi(2) { - inputs.move_dir = Vec2::zero(); - if *powerup > 4.0 { - inputs.secondary.set_state(false); - *powerup = 0.0; - } else if *powerup > 2.0 { - inputs.secondary.set_state(true); - *powerup += dt.0; - } else if stats.skill_set.has_skill(Skill::Hammer(HammerSkill::UnlockLeap)) && energy.current() > 700 - && thread_rng().gen_bool(0.9) { - inputs.ability3.set_state(true); - *powerup += dt.0; - } else { - inputs.primary.set_state(true); - *powerup += dt.0; - } - } else if dist_sqrd < MAX_CHASE_DIST.powi(2) - || (dist_sqrd < SIGHT_DIST.powi(2) && !*been_close) - { - if dist_sqrd < MAX_CHASE_DIST.powi(2) { - *been_close = true; - } - if let Some((bearing, speed)) = chaser.chase( - &*terrain, - pos.0, - vel.0, - tgt_pos.0, - TraversalConfig { - min_tgt_dist: 1.25, - ..traversal_config - }, - ) { - if can_see_tgt(&*terrain, pos, tgt_pos, dist_sqrd) { - inputs.move_dir = bearing - .xy() - .try_normalized() - .unwrap_or(Vec2::zero()) - * speed; - if stats.skill_set.has_skill(Skill::Hammer(HammerSkill::UnlockLeap)) && *powerup > 5.0 { - inputs.ability3.set_state(true); - *powerup = 0.0; - } else { - *powerup += dt.0; - } - } else { - inputs.move_dir = bearing - .xy() - .try_normalized() - .unwrap_or(Vec2::zero()) - * speed; - inputs.jump.set_state(bearing.z > 1.5); - inputs.move_z = bearing.z; - } - } - if body.map(|b| b.is_humanoid()).unwrap_or(false) && dist_sqrd < 16.0f32.powi(2) - && thread_rng().gen::() < 0.02 - { - inputs.roll.set_state(true); - } - } else { - do_idle = true; - } - }, - Tactic::Sword => { - if dist_sqrd < (min_attack_dist * scale).powi(2) { - inputs.move_dir = Vec2::zero(); - if stats.skill_set.has_skill(Skill::Sword(SwordSkill::UnlockSpin)) && *powerup < 2.0 && energy.current() > 600 { - inputs.ability3.set_state(true); - *powerup += dt.0; - } else if *powerup > 2.0 { - *powerup = 0.0; - } else { - inputs.primary.set_state(true); - *powerup += dt.0; - } - } else if dist_sqrd < MAX_CHASE_DIST.powi(2) - || (dist_sqrd < SIGHT_DIST.powi(2) && !*been_close) - { - if dist_sqrd < MAX_CHASE_DIST.powi(2) { - *been_close = true; - } - if let Some((bearing, speed)) = chaser.chase( - &*terrain, - pos.0, - vel.0, - tgt_pos.0, - TraversalConfig { - min_tgt_dist: 1.25, - ..traversal_config - }, - ) { - if can_see_tgt(&*terrain, pos, tgt_pos, dist_sqrd) { - inputs.move_dir = bearing - .xy() - .try_normalized() - .unwrap_or(Vec2::zero()) - * speed; - if *powerup > 4.0 { - inputs.secondary.set_state(true); - *powerup = 0.0; - } else { - *powerup += dt.0; - } - } else { - inputs.move_dir = bearing - .xy() - .try_normalized() - .unwrap_or(Vec2::zero()) - * speed; - inputs.jump.set_state(bearing.z > 1.5); - inputs.move_z = bearing.z; - } - } - if body.map(|b| b.is_humanoid()).unwrap_or(false) && dist_sqrd < 16.0f32.powi(2) - && thread_rng().gen::() < 0.02 - { - inputs.roll.set_state(true); - } - } else { - do_idle = true; - } - }, - Tactic::Bow => { - if body.map(|b| b.is_humanoid()).unwrap_or(false) && dist_sqrd < (2.0 * min_attack_dist * scale).powi(2) { - inputs.roll.set_state(true); - } else if dist_sqrd < MAX_CHASE_DIST.powi(2) - || (dist_sqrd < SIGHT_DIST.powi(2) && !*been_close) - { - if dist_sqrd < MAX_CHASE_DIST.powi(2) { - *been_close = true; - } - if let Some((bearing, speed)) = chaser.chase( - &*terrain, - pos.0, - vel.0, - tgt_pos.0, - TraversalConfig { - min_tgt_dist: 1.25, - ..traversal_config - }, - ) { - if can_see_tgt(&*terrain, pos, tgt_pos, dist_sqrd) { - inputs.move_dir = bearing - .xy() - .rotated_z( - thread_rng().gen_range(0.5..1.57), - ) - .try_normalized() - .unwrap_or(Vec2::zero()) - * speed; - if *powerup > 4.0 { - inputs.secondary.set_state(false); - *powerup = 0.0; - } else if *powerup > 2.0 - && energy.current() > 300 - { - inputs.secondary.set_state(true); - *powerup += dt.0; - } else if stats.skill_set.has_skill(Skill::Bow(BowSkill::UnlockRepeater)) && energy.current() > 400 - && thread_rng().gen_bool(0.8) - { - inputs.secondary.set_state(false); - inputs.ability3.set_state(true); - *powerup += dt.0; - } else { - inputs.secondary.set_state(false); - inputs.primary.set_state(true); - *powerup += dt.0; - } - } else { - inputs.move_dir = bearing - .xy() - .try_normalized() - .unwrap_or(Vec2::zero()) - * speed; - inputs.jump.set_state(bearing.z > 1.5); - inputs.move_z = bearing.z; - } - } - if body.map(|b| b.is_humanoid()).unwrap_or(false) && dist_sqrd < 16.0f32.powi(2) - && thread_rng().gen::() < 0.02 - { - inputs.roll.set_state(true); - } - } else { - do_idle = true; - } - }, - Tactic::Staff => { - if body.map(|b| b.is_humanoid()).unwrap_or(false) && dist_sqrd < (min_attack_dist * scale).powi(2) { - inputs.roll.set_state(true); - } else if dist_sqrd - < (5.0 * min_attack_dist * scale).powi(2) - { - if *powerup < 1.5 { - inputs.move_dir = (tgt_pos.0 - pos.0) - .xy() - .rotated_z(0.47 * PI) - .try_normalized() - .unwrap_or(Vec2::unit_y()); - *powerup += dt.0; - } else if *powerup < 3.0 { - inputs.move_dir = (tgt_pos.0 - pos.0) - .xy() - .rotated_z(-0.47 * PI) - .try_normalized() - .unwrap_or(Vec2::unit_y()); - *powerup += dt.0; - } else { - *powerup = 0.0; - } - if stats.skill_set.has_skill(Skill::Staff(StaffSkill::UnlockShockwave)) && energy.current() > 800 - && thread_rng().gen::() > 0.8 - { - inputs.ability3.set_state(true); - } else if energy.current() > 10 { - inputs.secondary.set_state(true); - } else { - inputs.primary.set_state(true); - } - } else if dist_sqrd < MAX_CHASE_DIST.powi(2) - || (dist_sqrd < SIGHT_DIST.powi(2) && !*been_close) - { - if dist_sqrd < MAX_CHASE_DIST.powi(2) { - *been_close = true; - } - if let Some((bearing, speed)) = chaser.chase( - &*terrain, - pos.0, - vel.0, - tgt_pos.0, - TraversalConfig { - min_tgt_dist: 1.25, - ..traversal_config - }, - ) { - if can_see_tgt(&*terrain, pos, tgt_pos, dist_sqrd) { - inputs.move_dir = bearing - .xy() - .rotated_z( - thread_rng().gen_range(-1.57..-0.5), - ) - .try_normalized() - .unwrap_or(Vec2::zero()) - * speed; - inputs.primary.set_state(true); - } else { - inputs.move_dir = bearing - .xy() - .try_normalized() - .unwrap_or(Vec2::zero()) - * speed; - inputs.jump.set_state(bearing.z > 1.5); - inputs.move_z = bearing.z; - } - } - if body.map(|b| b.is_humanoid()).unwrap_or(false) && dist_sqrd < 16.0f32.powi(2) - && thread_rng().gen::() < 0.02 - { - inputs.roll.set_state(true); - } - } else { - do_idle = true; - } - }, - Tactic::StoneGolemBoss => { - if dist_sqrd < (min_attack_dist * scale).powi(2) { - inputs.move_dir = Vec2::zero(); - inputs.primary.set_state(true); - } else if dist_sqrd < MAX_CHASE_DIST.powi(2) - || (dist_sqrd < SIGHT_DIST.powi(2) && !*been_close) - { - if vel.0.is_approx_zero() { - inputs.ability3.set_state(true); - } - if dist_sqrd < MAX_CHASE_DIST.powi(2) { - *been_close = true; - } - if let Some((bearing, speed)) = chaser.chase( - &*terrain, - pos.0, - vel.0, - tgt_pos.0, - TraversalConfig { - min_tgt_dist: 1.25, - ..traversal_config - }, - ) { - if can_see_tgt(&*terrain, pos, tgt_pos, dist_sqrd) { - inputs.move_dir = bearing - .xy() - .try_normalized() - .unwrap_or(Vec2::zero()) - * speed; - if *powerup > 5.0 { - inputs.secondary.set_state(true); - *powerup = 0.0; - } else { - *powerup += dt.0; - } - } else { - inputs.move_dir = bearing - .xy() - .try_normalized() - .unwrap_or(Vec2::zero()) - * speed; - inputs.jump.set_state(bearing.z > 1.5); - inputs.move_z = bearing.z; - } - } - } else { - do_idle = true; - } - }, - Tactic::CircleCharge { - radius, - circle_time, - } => { - if dist_sqrd < (min_attack_dist * scale).powi(2) - && thread_rng().gen_bool(0.5) - { - inputs.move_dir = Vec2::zero(); - inputs.primary.set_state(true); - } else if dist_sqrd - < (radius as f32 * min_attack_dist * scale).powi(2) - { - inputs.move_dir = (pos.0 - tgt_pos.0) - .xy() - .try_normalized() - .unwrap_or(Vec2::unit_y()); - } else if dist_sqrd - < ((radius as f32 + 1.0) * min_attack_dist * scale) - .powi(2) - && dist_sqrd - > (radius as f32 * min_attack_dist * scale).powi(2) - { - if *powerup < circle_time as f32 { - inputs.move_dir = (tgt_pos.0 - pos.0) - .xy() - .rotated_z(0.47 * PI) - .try_normalized() - .unwrap_or(Vec2::unit_y()); - *powerup += dt.0; - } else if *powerup < circle_time as f32 + 0.5 { - inputs.secondary.set_state(true); - *powerup += dt.0; - } else if *powerup < 2.0 * circle_time as f32 + 0.5 { - inputs.move_dir = (tgt_pos.0 - pos.0) - .xy() - .rotated_z(-0.47 * PI) - .try_normalized() - .unwrap_or(Vec2::unit_y()); - *powerup += dt.0; - } else if *powerup < 2.0 * circle_time as f32 + 1.0 { - inputs.secondary.set_state(true); - *powerup += dt.0; - } else { - *powerup = 0.0; - } - } else if dist_sqrd < MAX_CHASE_DIST.powi(2) - || (dist_sqrd < SIGHT_DIST.powi(2) && !*been_close) - { - if dist_sqrd < MAX_CHASE_DIST.powi(2) { - *been_close = true; - } - if let Some((bearing, speed)) = chaser.chase( - &*terrain, - pos.0, - vel.0, - tgt_pos.0, - TraversalConfig { - min_tgt_dist: 1.25, - ..traversal_config - }, - ) { - inputs.move_dir = bearing - .xy() - .try_normalized() - .unwrap_or(Vec2::zero()) - * speed; - inputs.jump.set_state(bearing.z > 1.5); - inputs.move_z = bearing.z; - } - } else { - do_idle = true; - } - }, - Tactic::QuadLowRanged => { - if dist_sqrd < (5.0 * min_attack_dist * scale).powi(2) { - inputs.move_dir = (tgt_pos.0 - pos.0) - .xy() - .try_normalized() - .unwrap_or(Vec2::unit_y()); - inputs.primary.set_state(true); - } else if dist_sqrd < MAX_CHASE_DIST.powi(2) - || (dist_sqrd < SIGHT_DIST.powi(2) && !*been_close) - { - if dist_sqrd < MAX_CHASE_DIST.powi(2) { - *been_close = true; - } - if let Some((bearing, speed)) = chaser.chase( - &*terrain, - pos.0, - vel.0, - tgt_pos.0, - TraversalConfig { - min_tgt_dist: 1.25, - ..traversal_config - }, - ) { - if can_see_tgt(&*terrain, pos, tgt_pos, dist_sqrd) { - if *powerup > 5.0 { - *powerup = 0.0; - } else if *powerup > 2.5 { - inputs.move_dir = (tgt_pos.0 - pos.0) - .xy() - .rotated_z(1.75 * PI) - .try_normalized() - .unwrap_or(Vec2::zero()) - * speed; - *powerup += dt.0; - } else { - inputs.move_dir = (tgt_pos.0 - pos.0) - .xy() - .rotated_z(0.25 * PI) - .try_normalized() - .unwrap_or(Vec2::zero()) - * speed; - *powerup += dt.0; - } - inputs.secondary.set_state(true); - inputs.jump.set_state(bearing.z > 1.5); - inputs.move_z = bearing.z; - } else { - inputs.move_dir = bearing - .xy() - .try_normalized() - .unwrap_or(Vec2::zero()) - * speed; - inputs.jump.set_state(bearing.z > 1.5); - inputs.move_z = bearing.z; - } - } else { - do_idle = true; - } - } else { - do_idle = true; - } - }, - Tactic::TailSlap => { - if dist_sqrd < (1.5 * min_attack_dist * scale).powi(2) { - if *powerup > 4.0 { - inputs.primary.set_state(false); - *powerup = 0.0; - } else if *powerup > 1.0 { - inputs.primary.set_state(true); - *powerup += dt.0; - } else { - inputs.secondary.set_state(true); - *powerup += dt.0; - } - inputs.move_dir = (tgt_pos.0 - pos.0) - .xy() - .try_normalized() - .unwrap_or(Vec2::unit_y()) - * 0.1; - } else if dist_sqrd < MAX_CHASE_DIST.powi(2) - || (dist_sqrd < SIGHT_DIST.powi(2) && !*been_close) - { - if dist_sqrd < MAX_CHASE_DIST.powi(2) { - *been_close = true; - } - if let Some((bearing, speed)) = chaser.chase( - &*terrain, - pos.0, - vel.0, - tgt_pos.0, - TraversalConfig { - min_tgt_dist: 1.25, - ..traversal_config - }, - ) { - inputs.move_dir = bearing - .xy() - .try_normalized() - .unwrap_or(Vec2::zero()) - * speed; - inputs.jump.set_state(bearing.z > 1.5); - inputs.move_z = bearing.z; - } - } else { - do_idle = true; - } - }, - Tactic::QuadLowQuick => { - if dist_sqrd < (1.5 * min_attack_dist * scale).powi(2) { - inputs.move_dir = Vec2::zero(); - inputs.secondary.set_state(true); - } else if dist_sqrd - < (3.0 * min_attack_dist * scale).powi(2) - && dist_sqrd > (2.0 * min_attack_dist * scale).powi(2) - { - inputs.primary.set_state(true); - inputs.move_dir = (tgt_pos.0 - pos.0) - .xy() - .rotated_z(-0.47 * PI) - .try_normalized() - .unwrap_or(Vec2::unit_y()); - } else if dist_sqrd < MAX_CHASE_DIST.powi(2) - || (dist_sqrd < SIGHT_DIST.powi(2) && !*been_close) - { - if dist_sqrd < MAX_CHASE_DIST.powi(2) { - *been_close = true; - } - if let Some((bearing, speed)) = chaser.chase( - &*terrain, - pos.0, - vel.0, - tgt_pos.0, - TraversalConfig { - min_tgt_dist: 1.25, - ..traversal_config - }, - ) { - inputs.move_dir = bearing - .xy() - .try_normalized() - .unwrap_or(Vec2::zero()) - * speed; - inputs.jump.set_state(bearing.z > 1.5); - inputs.move_z = bearing.z; - } - } else { - do_idle = true; - } - }, - Tactic::QuadLowBasic => { - if dist_sqrd < (1.5 * min_attack_dist * scale).powi(2) { - inputs.move_dir = Vec2::zero(); - if *powerup > 5.0 { - *powerup = 0.0; - } else if *powerup > 2.0 { - inputs.secondary.set_state(true); - *powerup += dt.0; - } else { - inputs.primary.set_state(true); - *powerup += dt.0; - } - } else if dist_sqrd < MAX_CHASE_DIST.powi(2) - || (dist_sqrd < SIGHT_DIST.powi(2) && !*been_close) - { - if dist_sqrd < MAX_CHASE_DIST.powi(2) { - *been_close = true; - } - if let Some((bearing, speed)) = chaser.chase( - &*terrain, - pos.0, - vel.0, - tgt_pos.0, - TraversalConfig { - min_tgt_dist: 1.25, - ..traversal_config - }, - ) { - inputs.move_dir = bearing - .xy() - .try_normalized() - .unwrap_or(Vec2::zero()) - * speed; - inputs.jump.set_state(bearing.z > 1.5); - inputs.move_z = bearing.z; - } - } else { - do_idle = true; - } - }, - Tactic::QuadMedJump => { - if dist_sqrd < (1.5 * min_attack_dist * scale).powi(2) { - inputs.move_dir = Vec2::zero(); - inputs.secondary.set_state(true); - } else if dist_sqrd - < (5.0 * min_attack_dist * scale).powi(2) - { - inputs.ability3.set_state(true); - } else if dist_sqrd < MAX_CHASE_DIST.powi(2) - || (dist_sqrd < SIGHT_DIST.powi(2) && !*been_close) - { - if dist_sqrd < MAX_CHASE_DIST.powi(2) { - *been_close = true; - } - if let Some((bearing, speed)) = chaser.chase( - &*terrain, - pos.0, - vel.0, - tgt_pos.0, - TraversalConfig { - min_tgt_dist: 1.25, - ..traversal_config - }, - ) { - if can_see_tgt(&*terrain, pos, tgt_pos, dist_sqrd) { - inputs.primary.set_state(true); - inputs.move_dir = bearing - .xy() - .try_normalized() - .unwrap_or(Vec2::zero()) - * speed; - } else { - inputs.move_dir = bearing - .xy() - .try_normalized() - .unwrap_or(Vec2::zero()) - * speed; - inputs.jump.set_state(bearing.z > 1.5); - inputs.move_z = bearing.z; - } - } - } else { - do_idle = true; - } - }, - Tactic::QuadMedBasic => { - if dist_sqrd < (min_attack_dist * scale).powi(2) { - inputs.move_dir = Vec2::zero(); - if *powerup < 2.0 { - inputs.secondary.set_state(true); - *powerup += dt.0; - } else if *powerup < 3.0 { - inputs.primary.set_state(true); - *powerup += dt.0; - } else { - *powerup = 0.0; - } - } else if dist_sqrd < MAX_CHASE_DIST.powi(2) - || (dist_sqrd < SIGHT_DIST.powi(2) && !*been_close) - { - if dist_sqrd < MAX_CHASE_DIST.powi(2) { - *been_close = true; - } - if let Some((bearing, speed)) = chaser.chase( - &*terrain, - pos.0, - vel.0, - tgt_pos.0, - TraversalConfig { - min_tgt_dist: 1.25, - ..traversal_config - }, - ) { - inputs.move_dir = bearing - .xy() - .try_normalized() - .unwrap_or(Vec2::zero()) - * speed; - inputs.jump.set_state(bearing.z > 1.5); - inputs.move_z = bearing.z; - } - } else { - do_idle = true; - } - }, - Tactic::Lavadrake => { - if dist_sqrd < (2.5 * min_attack_dist * scale).powi(2) { - inputs.move_dir = Vec2::zero(); - inputs.secondary.set_state(true); - } else if dist_sqrd - < (7.0 * min_attack_dist * scale).powi(2) - { - if *powerup < 2.0 { - inputs.move_dir = (tgt_pos.0 - pos.0) - .xy() - .rotated_z(0.47 * PI) - .try_normalized() - .unwrap_or(Vec2::unit_y()); - inputs.primary.set_state(true); - *powerup += dt.0; - } else if *powerup < 4.0 { - inputs.move_dir = (tgt_pos.0 - pos.0) - .xy() - .rotated_z(-0.47 * PI) - .try_normalized() - .unwrap_or(Vec2::unit_y()); - inputs.primary.set_state(true); - *powerup += dt.0; - } else if *powerup < 6.0 { - inputs.ability3.set_state(true); - *powerup += dt.0; - } else { - *powerup = 0.0; - } - } else if dist_sqrd < MAX_CHASE_DIST.powi(2) - || (dist_sqrd < SIGHT_DIST.powi(2) && !*been_close) - { - if dist_sqrd < MAX_CHASE_DIST.powi(2) { - *been_close = true; - } - if let Some((bearing, speed)) = chaser.chase( - &*terrain, - pos.0, - vel.0, - tgt_pos.0, - TraversalConfig { - min_tgt_dist: 1.25, - ..traversal_config - }, - ) { - inputs.move_dir = bearing - .xy() - .try_normalized() - .unwrap_or(Vec2::zero()) - * speed; - inputs.jump.set_state(bearing.z > 1.5); - inputs.move_z = bearing.z; - } - } else { - do_idle = true; - } - }, - Tactic::Theropod => { - if dist_sqrd < (2.0 * min_attack_dist * scale).powi(2) { - inputs.move_dir = Vec2::zero(); - inputs.primary.set_state(true); - } else if dist_sqrd < MAX_CHASE_DIST.powi(2) - || (dist_sqrd < SIGHT_DIST.powi(2) && !*been_close) - { - if dist_sqrd < MAX_CHASE_DIST.powi(2) { - *been_close = true; - } - if let Some((bearing, speed)) = chaser.chase( - &*terrain, - pos.0, - vel.0, - tgt_pos.0, - TraversalConfig { - min_tgt_dist: 1.25, - ..traversal_config - }, - ) { - inputs.move_dir = bearing - .xy() - .try_normalized() - .unwrap_or(Vec2::zero()) - * speed; - inputs.jump.set_state(bearing.z > 1.5); - inputs.move_z = bearing.z; - } - } else { - do_idle = true; - } - }, - Tactic::Turret => { - //inputs.look_dir = ori.0; - inputs.look_dir = Dir::new( - Quaternion::from_xyzw(ori.0.x, ori.0.y, 0.0, 0.0) - .rotated_z(4.0 * dt.0 as f32) - .into_vec3() - .try_normalized() - .unwrap_or_default(), - ); - if can_see_tgt(&*terrain, pos, tgt_pos, dist_sqrd) - { - inputs.primary.set_state(true); - } else { - do_idle = true; - } - }, - } - } - } else { - do_idle = true; - } - }, - } - } - - if do_idle { - agent.activity = Activity::Idle { - bearing: Vec2::zero(), - chaser: Chaser::default(), - }; - } - - // Choose a new target to attack: only go out of our way to attack targets we - // are hostile toward! - if choose_target { - // Search for new targets (this looks expensive, but it's only run occasionally) - // TODO: Replace this with a better system that doesn't consider *all* entities - let closest_entity = (&entities, &positions, &healths, alignments.maybe(), char_states.maybe()) - .join() - .filter(|(e, e_pos, e_health, e_alignment, char_state)| { - let mut search_dist = SEARCH_DIST; - let mut listen_dist = LISTEN_DIST; - if char_state.map_or(false, |c_s| c_s.is_stealthy()) { - // TODO: make sneak more effective based on a stat like e_stats.fitness - search_dist *= SNEAK_COEFFICIENT; - listen_dist *= SNEAK_COEFFICIENT; - } - ((e_pos.0.distance_squared(pos.0) < search_dist.powi(2) && - // Within our view - (e_pos.0 - pos.0).try_normalized().map(|v| v.dot(*inputs.look_dir) > 0.15).unwrap_or(true)) - // Within listen distance - || e_pos.0.distance_squared(pos.0) < listen_dist.powi(2)) - && *e != entity - && !e_health.is_dead - && alignment - .and_then(|a| e_alignment.map(|b| a.hostile_towards(*b))) - .unwrap_or(false) - }) - // Can we even see them? - .filter(|(_, e_pos, _, _, _)| terrain - .ray(pos.0 + Vec3::unit_z(), e_pos.0 + Vec3::unit_z()) - .until(Block::is_opaque) - .cast() - .0 >= e_pos.0.distance(pos.0)) - .min_by_key(|(_, e_pos, _, _, _)| (e_pos.0.distance_squared(pos.0) * 100.0) as i32) - .map(|(e, _, _, _, _)| e); - - if let Some(target) = closest_entity { - agent.activity = Activity::Attack { - target, - chaser: Chaser::default(), - time: time.0, - been_close: false, - powerup: 0.0, - }; - } - } - - // --- Activity overrides (in reverse order of priority: most important goes - // last!) --- - - // Attack a target that's attacking us - if let Some(my_health) = healths.get(entity) { - // Only if the attack was recent - if my_health.last_change.0 < 3.0 { - if let comp::HealthSource::Damage { by: Some(by), .. } = - my_health.last_change.1.cause - { - if !agent.activity.is_attack() { - if let Some(attacker) = uid_allocator.retrieve_entity_internal(by.id()) - { - if healths.get(attacker).map_or(false, |a| !a.is_dead) { - match agent.activity { - Activity::Attack { target, .. } if target == attacker => {}, - _ => { - if agent.can_speak { - let msg = - "npc.speech.villager_under_attack".to_string(); - event_emitter.emit(ServerEvent::Chat( - UnresolvedChatMsg::npc(*uid, msg), - )); - } - - agent.activity = Activity::Attack { - target: attacker, - chaser: Chaser::default(), - time: time.0, - been_close: false, - powerup: 0.0, - }; - }, - } - } - } - } - } - } - } - - // Follow owner if we're too far, or if they're under attack - if let Some(Alignment::Owned(owner)) = alignment { - (|| { - let owner = uid_allocator.retrieve_entity_internal(owner.id())?; - - let owner_pos = positions.get(owner)?; - let dist_sqrd = pos.0.distance_squared(owner_pos.0); - if dist_sqrd > MAX_FOLLOW_DIST.powi(2) && !agent.activity.is_follow() { - agent.activity = Activity::Follow { - target: owner, - chaser: Chaser::default(), - }; - } - - // Attack owner's attacker - let owner_health = healths.get(owner)?; - if owner_health.last_change.0 < 5.0 && owner_health.last_change.1.amount < 0 { - if let comp::HealthSource::Damage { by: Some(by), .. } = - owner_health.last_change.1.cause - { - if !agent.activity.is_attack() { - let attacker = uid_allocator.retrieve_entity_internal(by.id())?; - - agent.activity = Activity::Attack { - target: attacker, - chaser: Chaser::default(), - time: time.0, - been_close: false, - powerup: 0.0, - }; - } - } - } - - Some(()) - })(); - } - - debug_assert!(inputs.move_dir.map(|e| !e.is_nan()).reduce_and()); - debug_assert!(inputs.look_dir.map(|e| !e.is_nan()).reduce_and()); - }); - - // Process group invites - for (_invite, /*alignment,*/ agent, controller) in - (&invites, /*&alignments,*/ &mut agents, &mut controllers).join() - { - let accept = false; // set back to "matches!(alignment, Alignment::Npc)" when we got better NPC recruitment mechanics - if accept { - // Clear agent comp - *agent = Agent::default(); - controller - .events - .push(ControlEvent::GroupManip(GroupManip::Accept)); - } else { - controller - .events - .push(ControlEvent::GroupManip(GroupManip::Decline)); - } - } - sys_metrics.agent_ns.store( - start_time.elapsed().as_nanos() as u64, - std::sync::atomic::Ordering::Relaxed, - ); - } -} - -fn can_see_tgt(terrain: &TerrainGrid, pos: &Pos, tgt_pos: &Pos, dist_sqrd: f32) -> bool { - terrain - .ray(pos.0 + Vec3::unit_z(), tgt_pos.0 + Vec3::unit_z()) - .until(Block::is_opaque) - .cast() - .0 - .powi(2) - >= dist_sqrd -} diff --git a/server/src/sys/agent.rs b/server/src/sys/agent.rs index 8d3c76d8c2..10be4bec33 100644 --- a/server/src/sys/agent.rs +++ b/server/src/sys/agent.rs @@ -525,6 +525,7 @@ impl<'a> System<'a> for Sys { Some(ToolKind::Unique(UniqueKind::QuadLowBreathe)) => Tactic::Lavadrake, Some(ToolKind::Unique(UniqueKind::TheropodBasic)) => Tactic::Theropod, Some(ToolKind::Unique(UniqueKind::TheropodBird)) => Tactic::Theropod, + Some(ToolKind::Unique(UniqueKind::ObjectTurret)) => Tactic::Turret, _ => Tactic::Melee, }; @@ -1429,6 +1430,38 @@ impl<'a> System<'a> for Sys { do_idle = true; } }, + Tactic::Turret => { + if can_see_tgt(&*terrain, pos, tgt_pos, dist_sqrd) + { + inputs.primary.set_state(true); + } else { + do_idle = true; + } + }, + Tactic::FixedTurret => { + inputs.look_dir = ori.look_dir(); + if can_see_tgt(&*terrain, pos, tgt_pos, dist_sqrd) + { + inputs.primary.set_state(true); + } else { + do_idle = true; + } + }, + Tactic::RotatingTurret => { + inputs.look_dir = Dir::new( + Quaternion::from_xyzw(ori.look_dir().x, ori.look_dir().y, 0.0, 0.0) + .rotated_z(6.0 * dt.0 as f32) + .into_vec3() + .try_normalized() + .unwrap_or_default(), + ); + if can_see_tgt(&*terrain, pos, tgt_pos, dist_sqrd) + { + inputs.primary.set_state(true); + } else { + do_idle = true; + } + }, } } else { do_idle = true; diff --git a/voxygen/anim/src/character/run.rs b/voxygen/anim/src/character/run.rs index fb95b4879e..826eb40246 100644 --- a/voxygen/anim/src/character/run.rs +++ b/voxygen/anim/src/character/run.rs @@ -160,11 +160,7 @@ impl Animation for RunAnimation { Quaternion::rotation_x(0.6 * speednorm + (footrotr * -1.2) * speednorm) * Quaternion::rotation_y(footrotr * 0.4 * speednorm); - next.hand_r.position = Vec3::new( - s_a.hand.0 + foothoril * 1.3 * speednorm, - 3.0 * speednorm + s_a.hand.1 + foothoril * -7.0 * speednorm, - 1.5 * speednorm + s_a.hand.2 - foothoril * 5.5 * speednorm, - ); + next.hand_r.position = -next.hand_l.position; next.hand_r.orientation = Quaternion::rotation_x(0.6 * speednorm + (footrotl * -1.2) * speednorm) * Quaternion::rotation_y(footrotl * -0.4 * speednorm); diff --git a/voxygen/anim/src/object/beam.rs b/voxygen/anim/src/object/beam.rs new file mode 100644 index 0000000000..56386e154b --- /dev/null +++ b/voxygen/anim/src/object/beam.rs @@ -0,0 +1,45 @@ +use super::{ + super::{vek::*, Animation}, + ObjectSkeleton, SkeletonAttr, +}; +use common::{ + comp::{item::ToolKind, object::Body}, + states::utils::StageSection, +}; +pub struct BeamAnimation; + +type BeamAnimationDependency = ( + Option, + Option, + Option, + Body, +); +impl Animation for BeamAnimation { + type Dependency = BeamAnimationDependency; + type Skeleton = ObjectSkeleton; + + #[cfg(feature = "use-dyn-lib")] + const UPDATE_FN: &'static [u8] = b"object_beam\0"; + + #[cfg_attr(feature = "be-dyn-lib", export_name = "object_beam")] + #[allow(clippy::approx_constant)] // TODO: Pending review in #587 + fn update_skeleton_inner( + skeleton: &Self::Skeleton, + (_active_tool_kind, _second_tool_kind, _stage_section, _body): Self::Dependency, + _anim_time: f64, + rate: &mut f32, + s_a: &SkeletonAttr, + ) -> Self::Skeleton { + *rate = 1.0; + + let mut next = (*skeleton).clone(); + + next.bone0.position = Vec3::new(s_a.bone0.0, s_a.bone0.1, s_a.bone0.2) / 11.0; + next.bone0.orientation = Quaternion::rotation_z(0.0); + + next.bone1.position = Vec3::new(s_a.bone1.0, s_a.bone1.1, s_a.bone1.2) / 11.0; + next.bone1.orientation = Quaternion::rotation_z(0.0); + + next + } +} diff --git a/voxygen/anim/src/object/idle.rs b/voxygen/anim/src/object/idle.rs index 07a84d25cb..f69f801fc2 100644 --- a/voxygen/anim/src/object/idle.rs +++ b/voxygen/anim/src/object/idle.rs @@ -24,8 +24,9 @@ impl Animation for IdleAnimation { ) -> Self::Skeleton { let mut next = (*skeleton).clone(); - next.bone0.position = Vec3::new(s_a.bone0.0, s_a.bone0.1, s_a.bone0.2); - next.bone0.orientation = Quaternion::rotation_z(0.0); + next.bone0.position = Vec3::new(s_a.bone0.0, s_a.bone0.1, s_a.bone0.2) / 11.0; + + next.bone1.position = Vec3::new(s_a.bone1.0, s_a.bone1.1, s_a.bone1.2) / 11.0; next } diff --git a/voxygen/anim/src/object/mod.rs b/voxygen/anim/src/object/mod.rs index eb484e3ad3..abcb6630ae 100644 --- a/voxygen/anim/src/object/mod.rs +++ b/voxygen/anim/src/object/mod.rs @@ -1,8 +1,9 @@ +pub mod beam; pub mod idle; pub mod shoot; // Reexports -pub use self::{idle::IdleAnimation, shoot::ShootAnimation}; +pub use self::{beam::BeamAnimation, idle::IdleAnimation, shoot::ShootAnimation}; use super::{make_bone, vek::*, FigureBoneData, Skeleton}; use common::comp::{self}; @@ -12,13 +13,14 @@ pub type Body = comp::object::Body; skeleton_impls!(struct ObjectSkeleton { + bone0, + + bone1, }); impl Skeleton for ObjectSkeleton { type Attr = SkeletonAttr; type Body = Body; - const BONE_COUNT: usize = 1; + const BONE_COUNT: usize = 2; #[cfg(feature = "use-dyn-lib")] const COMPUTE_FN: &'static [u8] = b"object_compute_mats\0"; @@ -30,14 +32,17 @@ impl Skeleton for ObjectSkeleton { ) -> Vec3 { let bone0_mat = base_mat * Mat4::::from(self.bone0); - *(<&mut [_; Self::BONE_COUNT]>::try_from(&mut buf[0..Self::BONE_COUNT]).unwrap()) = - [make_bone(bone0_mat * Mat4::scaling_3d(1.0 / 11.0))]; + *(<&mut [_; Self::BONE_COUNT]>::try_from(&mut buf[0..Self::BONE_COUNT]).unwrap()) = [ + make_bone(bone0_mat * Mat4::scaling_3d(1.0 / 11.0)), + make_bone(Mat4::::from(self.bone1) * Mat4::scaling_3d(1.0 / 11.0)), /* Decorellated from ori */ + ]; Vec3::default() } } pub struct SkeletonAttr { bone0: (f32, f32, f32), + bone1: (f32, f32, f32), } impl<'a> std::convert::TryFrom<&'a comp::Body> for SkeletonAttr { @@ -55,6 +60,7 @@ impl Default for SkeletonAttr { fn default() -> Self { Self { bone0: (0.0, 0.0, 0.0), + bone1: (0.0, 0.0, 0.0), } } } @@ -64,8 +70,11 @@ impl<'a> From<&'a Body> for SkeletonAttr { use comp::object::Body::*; Self { bone0: match body { - CampfireLit => (2.0, 0.5, 1.0), - Pouch => (2.0, 0.5, 1.0), + Crossbow => (0.0, 0.0, 14.0), + _ => (0.0, 0.0, 0.0), + }, + bone1: match body { + Crossbow => (0.0, 0.0, 8.0), _ => (0.0, 0.0, 0.0), }, } diff --git a/voxygen/anim/src/object/shoot.rs b/voxygen/anim/src/object/shoot.rs index df5c015a9f..d2d558e948 100644 --- a/voxygen/anim/src/object/shoot.rs +++ b/voxygen/anim/src/object/shoot.rs @@ -2,17 +2,17 @@ use super::{ super::{vek::*, Animation}, ObjectSkeleton, SkeletonAttr, }; -use common::{comp::item::ToolKind, states::utils::StageSection}; +use common::{ + comp::{item::ToolKind, object::Body}, + states::utils::StageSection, +}; pub struct ShootAnimation; type ShootAnimationDependency = ( Option, Option, - f32, - Vec3, - Vec3, - f64, Option, + Body, ); impl Animation for ShootAnimation { type Dependency = ShootAnimationDependency; @@ -25,15 +25,7 @@ impl Animation for ShootAnimation { #[allow(clippy::approx_constant)] // TODO: Pending review in #587 fn update_skeleton_inner( skeleton: &Self::Skeleton, - ( - _active_tool_kind, - _second_tool_kind, - _velocity, - orientation, - last_ori, - _global_time, - _stage_section, - ): Self::Dependency, + (_active_tool_kind, _second_tool_kind, stage_section, body): Self::Dependency, anim_time: f64, rate: &mut f32, s_a: &SkeletonAttr, @@ -42,22 +34,28 @@ impl Animation for ShootAnimation { let mut next = (*skeleton).clone(); - let ori: Vec2 = Vec2::from(orientation); - let last_ori = Vec2::from(last_ori); - let _tilt = if ::vek::Vec2::new(ori, last_ori) - .map(|o| o.magnitude_squared()) - .map(|m| m > 0.001 && m.is_finite()) - .reduce_and() - && ori.angle_between(last_ori).is_finite() - { - ori.angle_between(last_ori).min(0.2) - * last_ori.determine_side(Vec2::zero(), ori).signum() - } else { - 0.0 - } * 1.3; + let (movement1, movement2, movement3) = match stage_section { + Some(StageSection::Buildup) => (anim_time as f32, 0.0, 0.0), + Some(StageSection::Swing) => (1.0, (anim_time as f32).powf(0.25), 0.0), + Some(StageSection::Recover) => (1.0, 1.0, anim_time as f32), + _ => (0.0, 0.0, 0.0), + }; - next.bone0.position = Vec3::new(s_a.bone0.0, s_a.bone0.1, s_a.bone0.2); - next.bone0.orientation = Quaternion::rotation_z(anim_time as f32 * 1.0); + next.bone0.position = Vec3::new(s_a.bone0.0, s_a.bone0.1, s_a.bone0.2) / 11.0; + next.bone1.position = Vec3::new(s_a.bone1.0, s_a.bone1.1, s_a.bone1.2) / 11.0; + + #[allow(clippy::single_match)] + match body { + Body::Crossbow => { + next.bone0.position = Vec3::new(s_a.bone0.0, s_a.bone0.1, s_a.bone0.2) / 11.0; + next.bone0.orientation = + Quaternion::rotation_x(movement1 * 0.05 + movement2 * 0.1) * (1.0 - movement3); + + next.bone1.position = Vec3::new(s_a.bone1.0, s_a.bone1.1, s_a.bone1.2) / 11.0; + next.bone1.orientation = Quaternion::rotation_z(0.0); + }, + _ => {}, + } next } diff --git a/voxygen/src/audio/sfx/mod.rs b/voxygen/src/audio/sfx/mod.rs index 76b22b4fe7..a4afb5a4bb 100644 --- a/voxygen/src/audio/sfx/mod.rs +++ b/voxygen/src/audio/sfx/mod.rs @@ -317,7 +317,10 @@ impl SfxMgr { // TODO: from sfx config? match body { Body::Object( - object::Body::Arrow | object::Body::MultiArrow | object::Body::ArrowSnake, + object::Body::Arrow + | object::Body::MultiArrow + | object::Body::ArrowSnake + | object::Body::ArrowTurret, ) => { let file_ref = vec![ "voxygen.audio.sfx.abilities.arrow_shot_1", diff --git a/voxygen/src/ecs/sys/interpolation.rs b/voxygen/src/ecs/sys/interpolation.rs index 26ea707c9d..9a09f852ee 100644 --- a/voxygen/src/ecs/sys/interpolation.rs +++ b/voxygen/src/ecs/sys/interpolation.rs @@ -23,22 +23,22 @@ impl<'a> System<'a> for Sys { fn run( &mut self, - (entities, dt, positions, orientations, velocities, mut interpolated, bodies): Self::SystemData, + (entities, dt, positions, orientations, velocities, bodies, mut interpolated): Self::SystemData, ) { // Update interpolated positions and orientations - for (pos, ori, i, vel, body) in ( + for (pos, ori, i, body, vel) in ( &positions, &orientations, &mut interpolated, - &velocities, &bodies, + &velocities, ) .join() { // Update interpolation values if i.pos.distance_squared(pos.0) < 64.0 * 64.0 { - i.pos = Lerp::lerp(i.pos, pos.0 + vel.0 * 0.03, 5.0 * dt.0); - i.ori = Dir::slerp(i.ori, ori.0, base_ori_interp(body) * dt.0); + i.pos = Lerp::lerp(i.pos, pos.0 + vel.0 * 0.03, 10.0 * dt.0); + i.ori = Ori::slerp(i.ori, *ori, base_ori_interp(body) * dt.0); } else { i.pos = pos.0; i.ori = *ori; diff --git a/voxygen/src/scene/figure/load.rs b/voxygen/src/scene/figure/load.rs index 089c216c86..a981d6614e 100644 --- a/voxygen/src/scene/figure/load.rs +++ b/voxygen/src/scene/figure/load.rs @@ -3747,6 +3747,7 @@ struct ObjectCentralSpec(HashMap); #[derive(Deserialize)] struct SidedObjectCentralVoxSpec { bone0: ObjectCentralSubSpec, + bone1: ObjectCentralSubSpec, } #[derive(Deserialize)] struct ObjectCentralSubSpec { @@ -3764,7 +3765,9 @@ make_vox_spec!( Some(spec.central.read().0.mesh_bone0( body, )), - None, + Some(spec.central.read().0.mesh_bone1( + body, + )), None, None, None, @@ -3796,4 +3799,17 @@ impl ObjectCentralSpec { (central, Vec3::from(spec.bone0.offset)) } + + fn mesh_bone1(&self, obj: &object::Body) -> BoneMeshes { + let spec = match self.0.get(&obj) { + Some(spec) => spec, + None => { + error!("No specification exists for {:?}", obj); + return load_mesh("not_found", Vec3::new(-5.0, -5.0, -2.5)); + }, + }; + let central = graceful_load_segment(&spec.bone1.central.0); + + (central, Vec3::from(spec.bone1.offset)) + } } diff --git a/voxygen/src/scene/figure/mod.rs b/voxygen/src/scene/figure/mod.rs index d9361cf0a6..1d02054369 100644 --- a/voxygen/src/scene/figure/mod.rs +++ b/voxygen/src/scene/figure/mod.rs @@ -3490,7 +3490,9 @@ impl FigureMgr { let (character, last_character) = match (character, last_character) { (Some(c), Some(l)) => (c, l), - _ => continue, + _ => (&CharacterState::Idle, &Last { + 0: CharacterState::Idle, + }), }; if !character.same_variant(&last_character.0) { @@ -3538,11 +3540,33 @@ impl FigureMgr { ( active_tool_kind, second_tool_kind, - vel.0.magnitude(), - ori, - state.last_ori, - time, Some(s.stage_section), + *body, + ), + stage_progress, + &mut state_animation_rate, + skeleton_attr, + ) + }, + CharacterState::BasicBeam(s) => { + let stage_time = s.timer.as_secs_f64(); + let stage_progress = match s.stage_section { + StageSection::Buildup => { + stage_time / s.static_data.buildup_duration.as_secs_f64() + }, + StageSection::Cast => s.timer.as_secs_f64(), + StageSection::Recover => { + stage_time / s.static_data.recover_duration.as_secs_f64() + }, + _ => 0.0, + }; + anim::object::BeamAnimation::update_skeleton( + &target_base, + ( + active_tool_kind, + second_tool_kind, + Some(s.stage_section), + *body, ), stage_progress, &mut state_animation_rate, diff --git a/voxygen/src/scene/particle.rs b/voxygen/src/scene/particle.rs index 1455c649ee..83d8ceb793 100644 --- a/voxygen/src/scene/particle.rs +++ b/voxygen/src/scene/particle.rs @@ -8,6 +8,7 @@ use crate::{ }; use common::{ assets::{AssetExt, DotVoxAsset}, + combat::CombatEffect, comp::{item::Reagent, object, BeamSegment, Body, CharacterState, Ori, Pos, Shockwave}, figure::Segment, outcome::Outcome, @@ -376,7 +377,12 @@ impl ParticleMgr { .filter(|(_, _, b)| b.creation.map_or(true, |c| (c + dt as f64) >= time)) { let range = beam.properties.speed * beam.properties.duration.as_secs_f32(); - if beam.properties.lifesteal_eff > 0.0 { + if beam + .properties + .attack + .effects() + .any(|e| matches!(e.effect(), CombatEffect::Heal(h) if *h > 0.0)) + { // Emit a light when using healing lights.push(Light::new(pos.0, Rgb::new(0.1, 1.0, 0.15), 1.0)); for i in 0..self.scheduler.heartbeats(Duration::from_millis(1)) { @@ -385,25 +391,25 @@ impl ParticleMgr { time + i as f64 / 1000.0, ParticleMode::HealingBeam, pos.0, - pos.0 + *ori.0 * range, + pos.0 + *ori.look_dir() * range, )); } } else { let mut rng = thread_rng(); - let (from, to) = (Vec3::::unit_z(), *ori.0); + let (from, to) = (Vec3::::unit_z(), *ori.look_dir()); let m = Mat3::::rotation_from_to_3d(from, to); // Emit a light when using flames lights.push(Light::new( pos.0, - Rgb::new(1.0, 0.25, 0.05).map(|e| e * rng.gen_range(0.8, 1.2)), + Rgb::new(1.0, 0.25, 0.05).map(|e| e * rng.gen_range(0.8..1.2)), 2.0, )); self.particles.resize_with( self.particles.len() + 2 * usize::from(self.scheduler.heartbeats(Duration::from_millis(1))), || { - let phi: f32 = rng.gen_range(0.0, beam.properties.angle.to_radians()); - let theta: f32 = rng.gen_range(0.0, 2.0 * PI); + let phi: f32 = rng.gen_range(0.0..beam.properties.angle.to_radians()); + let theta: f32 = rng.gen_range(0.0..2.0 * PI); let offset_z = Vec3::new(phi.sin() * theta.cos(), phi.sin() * theta.sin(), phi.cos()); let random_ori = offset_z * m * Vec3::new(-1.0, -1.0, 1.0); diff --git a/world/src/site/dungeon/mod.rs b/world/src/site/dungeon/mod.rs index 41e9184715..5ba43459ee 100644 --- a/world/src/site/dungeon/mod.rs +++ b/world/src/site/dungeon/mod.rs @@ -701,6 +701,12 @@ impl Floor { .with_main_tool(comp::Item::new_from_asset_expect( "common.items.weapons.staff.cultist_staff", )), + 1 => entity + .with_body(comp::Body::Object(comp::object::Body::Crossbow)) + .with_name("Possessed Turret".to_string()) + .with_loot_drop(comp::Item::new_from_asset_expect( + "common.items.crafting_ing.twigs", + )), _ => entity .with_name("Cultist Warlord") .with_loadout_config(loadout_builder::LoadoutConfig::Warlord) diff --git a/world/src/site/settlement/mod.rs b/world/src/site/settlement/mod.rs index d8ccd32c97..35fdeea176 100644 --- a/world/src/site/settlement/mod.rs +++ b/world/src/site/settlement/mod.rs @@ -884,7 +884,7 @@ impl Settlement { .with_body(match dynamic_rng.gen_range(0..5) { _ if is_dummy => { is_human = false; - object::Body::Crossbow.into() + object::Body::TrainingDummy.into() }, 0 => { let species = match dynamic_rng.gen_range(0..3) { @@ -916,9 +916,9 @@ impl Settlement { comp::Body::Humanoid(humanoid::Body::random()) }, }) - .with_agency(true) // TEMPORARY + .with_agency(!is_dummy) .with_alignment(if is_dummy { - comp::Alignment::Enemy // TEMPORARY + comp::Alignment::Passive } else if is_human { comp::Alignment::Npc } else {