diff --git a/assets/common/abilities/sceptre/healingbeam.ron b/assets/common/abilities/sceptre/healingbeam.ron index 3c5822dbb2..8b6b235b6f 100644 --- a/assets/common/abilities/sceptre/healingbeam.ron +++ b/assets/common/abilities/sceptre/healingbeam.ron @@ -11,4 +11,5 @@ BasicBeam( energy_regen: 25, energy_cost: 50, energy_drain: 0, + orientation_behavior: Normal, ) \ No newline at end of file diff --git a/assets/common/abilities/staff/flamethrower.ron b/assets/common/abilities/staff/flamethrower.ron index 42a2571d4a..4d886dbff5 100644 --- a/assets/common/abilities/staff/flamethrower.ron +++ b/assets/common/abilities/staff/flamethrower.ron @@ -11,4 +11,5 @@ BasicBeam( energy_regen: 0, energy_cost: 1, energy_drain: 350, + orientation_behavior: Normal, ) \ No newline at end of file diff --git a/assets/common/abilities/unique/quadlowbreathe/flamethrower.ron b/assets/common/abilities/unique/quadlowbreathe/flamethrower.ron index f8df4454c5..3b8117c98b 100644 --- a/assets/common/abilities/unique/quadlowbreathe/flamethrower.ron +++ b/assets/common/abilities/unique/quadlowbreathe/flamethrower.ron @@ -11,4 +11,5 @@ BasicBeam( energy_regen: 0, energy_cost: 0, energy_drain: 0, + orientation_behavior: Normal, ) \ No newline at end of file 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/flamethrower.ron b/assets/common/abilities/unique/turret/flamethrower.ron new file mode 100644 index 0000000000..512104ffdd --- /dev/null +++ b/assets/common/abilities/unique/turret/flamethrower.ron @@ -0,0 +1,15 @@ +BasicBeam( + buildup_duration: 0.25, + recover_duration: 0.25, + beam_duration: 0.5, + base_hps: 0, + base_dps: 9001, + tick_rate: 3.0, + range: 30.0, + max_angle: 1.0, + lifesteal_eff: 0.0, + energy_regen: 0, + energy_cost: 0, + energy_drain: 0, + orientation_behavior: Turret, +) \ No newline at end of file diff --git a/assets/common/abilities/weapon_ability_manifest.ron b/assets/common/abilities/weapon_ability_manifest.ron index 465d1a4040..bf5bb437ac 100644 --- a/assets/common/abilities/weapon_ability_manifest.ron +++ b/assets/common/abilities/weapon_ability_manifest.ron @@ -132,6 +132,11 @@ secondary: "common.abilities.unique.theropodbird.triplestrike", abilities: [], ), + Unique(ObjectTurret): ( + primary: "common.abilities.unique.turret.arrows", + secondary: "common.abilities.unique.turret.arrows", + abilities: [], + ), Debug: ( primary: "common.abilities.debug.forwardboost", secondary: "common.abilities.debug.upboost", diff --git a/assets/common/items/npc_weapons/unique/turret.ron b/assets/common/items/npc_weapons/unique/turret.ron new file mode 100644 index 0000000000..fcca6452c1 --- /dev/null +++ b/assets/common/items/npc_weapons/unique/turret.ron @@ -0,0 +1,18 @@ +ItemDef( + name: "Turret", + description: "Turret weapon", + kind: Tool( + ( + 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/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 d379e65561..d136326373 100644 --- a/assets/voxygen/voxel/object_manifest.ron +++ b/assets/voxygen/voxel/object_manifest.ron @@ -3,378 +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, -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/assets/voxygen/voxel/weapon/projectile/turret-arrow.vox b/assets/voxygen/voxel/weapon/projectile/turret-arrow.vox new file mode 100644 index 0000000000..0cae2698f1 --- /dev/null +++ b/assets/voxygen/voxel/weapon/projectile/turret-arrow.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a2f2517cf5bba387a4c56a4c872ccc8bc41956dd255e32bcd52cfd1d8e69b5a5 +size 1184 diff --git a/common/src/comp/ability.rs b/common/src/comp/ability.rs index 5e9d904b17..4e7f1325ff 100644 --- a/common/src/comp/ability.rs +++ b/common/src/comp/ability.rs @@ -225,6 +225,7 @@ pub enum CharacterAbility { energy_regen: f32, energy_cost: f32, energy_drain: f32, + orientation_behavior: basic_beam::MovementBehavior, }, } @@ -1445,6 +1446,7 @@ impl From<(&CharacterAbility, AbilityInfo)> for CharacterState { energy_regen, energy_cost, energy_drain, + orientation_behavior, } => CharacterState::BasicBeam(basic_beam::Data { static_data: basic_beam::StaticData { buildup_duration: Duration::from_secs_f32(*buildup_duration), @@ -1460,10 +1462,10 @@ impl From<(&CharacterAbility, AbilityInfo)> for CharacterState { energy_cost: *energy_cost, energy_drain: *energy_drain, ability_info, + orientation_behavior: *orientation_behavior, }, timer: Duration::default(), stage_section: StageSection::Buildup, - particle_ori: None::>, offset: Vec3::zero(), }), } 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 a71f9f810d..742a487adb 100644 --- a/common/src/comp/body.rs +++ b/common/src/comp/body.rs @@ -262,7 +262,10 @@ impl Body { _ => 4.6, }, Body::Golem(_) => 5.0, - Body::Object(_) => 1.0, + Body::Object(object) => match object { + object::Body::Crossbow => 1.7, + _ => 1.0, + }, } } @@ -336,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/body/object.rs b/common/src/comp/body/object.rs index f0bcdeacac..23b971c08f 100644 --- a/common/src/comp/body/object.rs +++ b/common/src/comp/body/object.rs @@ -70,6 +70,8 @@ make_case_elim!( BoltNature = 60, MeatDrop = 61, Steak = 62, + Crossbow = 63, + ArrowTurret = 64, } ); @@ -80,7 +82,7 @@ impl Body { } } -pub const ALL_OBJECTS: [Body; 63] = [ +pub const ALL_OBJECTS: [Body; 65] = [ Body::Arrow, Body::Bomb, Body::Scarecrow, @@ -144,6 +146,8 @@ pub const ALL_OBJECTS: [Body; 63] = [ Body::BoltNature, Body::MeatDrop, Body::Steak, + Body::Crossbow, + Body::ArrowTurret, ]; impl From for super::Body { @@ -216,6 +220,8 @@ impl Body { Body::BoltNature => "bolt_nature", Body::MeatDrop => "meat_drop", Body::Steak => "steak", + Body::Crossbow => "crossbow", + Body::ArrowTurret => "arrow_turret", } } } diff --git a/common/src/comp/inventory/item/tool.rs b/common/src/comp/inventory/item/tool.rs index 2d94ab70d5..c7eefbb07a 100644 --- a/common/src/comp/inventory/item/tool.rs +++ b/common/src/comp/inventory/item/tool.rs @@ -295,4 +295,5 @@ pub enum UniqueKind { QuadSmallBasic, TheropodBasic, TheropodBird, + ObjectTurret, } diff --git a/common/src/comp/inventory/loadout_builder.rs b/common/src/comp/inventory/loadout_builder.rs index 40cda515ce..f35adcb522 100644 --- a/common/src/comp/inventory/loadout_builder.rs +++ b/common/src/comp/inventory/loadout_builder.rs @@ -5,7 +5,7 @@ use crate::comp::{ slot::{ArmorSlot, EquipSlot}, }, item::{Item, ItemKind}, - quadruped_low, quadruped_medium, theropod, Body, + object, quadruped_low, quadruped_medium, theropod, Body, }; use rand::Rng; @@ -227,6 +227,11 @@ impl LoadoutBuilder { )); }, }, + 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 3e7028f53f..3a740dcb22 100644 --- a/common/src/states/basic_beam.rs +++ b/common/src/states/basic_beam.rs @@ -13,7 +13,7 @@ use crate::{ }; use serde::{Deserialize, Serialize}; use std::time::Duration; -use vek::Vec3; +use vek::*; /// Separated out to condense update portions of character state #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] @@ -43,6 +43,8 @@ pub struct StaticData { pub energy_cost: f32, /// Energy drained per pub energy_drain: f32, + /// Used to dictate how orientation functions in this state + pub orientation_behavior: MovementBehavior, /// What key is used to press ability pub ability_info: AbilityInfo, } @@ -56,8 +58,6 @@ pub struct Data { pub timer: Duration, /// What section the character stage is in pub stage_section: StageSection, - /// Used for particle stuffs - pub particle_ori: Option>, /// Used to offset beam and particles pub offset: Vec3, } @@ -66,6 +66,13 @@ impl CharacterBehavior for Data { fn behavior(&self, data: &JoinData) -> StateUpdate { let mut update = StateUpdate::from(data); + match self.static_data.orientation_behavior { + MovementBehavior::Normal => {}, + MovementBehavior::Turret => { + update.ori = Ori::from(data.inputs.look_dir); + }, + } + handle_move(data, &mut update, 0.4); handle_jump(data, &mut update); if !ability_key_is_pressed(data, self.static_data.ability_info.key) { @@ -87,7 +94,6 @@ impl CharacterBehavior for Data { .timer .checked_add(Duration::from_secs_f32(data.dt.0)) .unwrap_or_default(), - particle_ori: Some(*data.inputs.look_dir), ..*self }); } else { @@ -107,7 +113,6 @@ impl CharacterBehavior for Data { update.character = CharacterState::BasicBeam(Data { timer: Duration::default(), stage_section: StageSection::Cast, - particle_ori: Some(*data.inputs.look_dir), offset: body_offsets, ..*self }); @@ -185,7 +190,6 @@ impl CharacterBehavior for Data { .timer .checked_add(Duration::from_secs_f32(data.dt.0)) .unwrap_or_default(), - particle_ori: Some(*data.inputs.look_dir), offset: body_offsets, ..*self }); @@ -199,7 +203,6 @@ impl CharacterBehavior for Data { update.character = CharacterState::BasicBeam(Data { timer: Duration::default(), stage_section: StageSection::Recover, - particle_ori: Some(*data.inputs.look_dir), ..*self }); } @@ -211,7 +214,6 @@ impl CharacterBehavior for Data { .timer .checked_add(Duration::from_secs_f32(data.dt.0)) .unwrap_or_default(), - particle_ori: Some(*data.inputs.look_dir), ..*self }); } else { @@ -232,3 +234,9 @@ impl CharacterBehavior for Data { update } } + +#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] +pub enum MovementBehavior { + Normal, + Turret, +} diff --git a/common/src/states/utils.rs b/common/src/states/utils.rs index 9abcfb8184..a37682adba 100644 --- a/common/src/states/utils.rs +++ b/common/src/states/utils.rs @@ -66,7 +66,7 @@ impl Body { Body::BirdSmall(_) => 75.0, Body::FishSmall(_) => 60.0, Body::BipedLarge(_) => 75.0, - Body::Object(_) => 40.0, + Body::Object(_) => 0.0, Body::Golem(_) => 60.0, Body::Theropod(_) => 135.0, Body::QuadrupedLow(quadruped_low) => match quadruped_low.species { @@ -116,7 +116,7 @@ impl Body { Body::BirdSmall(_) => 35.0, Body::FishSmall(_) => 10.0, Body::BipedLarge(_) => 12.0, - Body::Object(_) => 5.0, + Body::Object(_) => 10.0, Body::Golem(_) => 8.0, Body::Theropod(theropod) => match theropod.species { theropod::Species::Archaeos => 2.5, diff --git a/server/src/sys/agent.rs b/server/src/sys/agent.rs index 00a634c6b4..28cccfd9e2 100644 --- a/server/src/sys/agent.rs +++ b/server/src/sys/agent.rs @@ -533,6 +533,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, }; @@ -1437,6 +1438,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 d72d5aedfc..44025d0af7 100644 --- a/voxygen/anim/src/character/run.rs +++ b/voxygen/anim/src/character/run.rs @@ -159,11 +159,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 new file mode 100644 index 0000000000..f69f801fc2 --- /dev/null +++ b/voxygen/anim/src/object/idle.rs @@ -0,0 +1,33 @@ +use super::{ + super::{vek::*, Animation}, + ObjectSkeleton, SkeletonAttr, +}; +use common::comp::item::ToolKind; + +pub struct IdleAnimation; + +impl Animation for IdleAnimation { + type Dependency = (Option, Option, f64); + type Skeleton = ObjectSkeleton; + + #[cfg(feature = "use-dyn-lib")] + const UPDATE_FN: &'static [u8] = b"object_idle\0"; + + #[cfg_attr(feature = "be-dyn-lib", export_name = "object_idle")] + #[allow(clippy::approx_constant)] // TODO: Pending review in #587 + fn update_skeleton_inner( + skeleton: &Self::Skeleton, + (_active_tool_kind, _second_tool_kind, _global_time): Self::Dependency, + _anim_time: f64, + _rate: &mut f32, + s_a: &SkeletonAttr, + ) -> Self::Skeleton { + 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.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 f7a0549663..abcb6630ae 100644 --- a/voxygen/anim/src/object/mod.rs +++ b/voxygen/anim/src/object/mod.rs @@ -1,30 +1,26 @@ +pub mod beam; +pub mod idle; +pub mod shoot; + +// Reexports +pub use self::{beam::BeamAnimation, idle::IdleAnimation, shoot::ShootAnimation}; + use super::{make_bone, vek::*, FigureBoneData, Skeleton}; use common::comp::{self}; +use core::convert::TryFrom; pub type Body = comp::object::Body; -#[derive(Clone, Default)] -pub struct ObjectSkeleton; - -impl<'a, Factor> Lerp for &'a ObjectSkeleton { - type Output = ObjectSkeleton; - - fn lerp_unclamped_precise(_from: Self, _to: Self, _factor: Factor) -> Self::Output { - ObjectSkeleton - } - - fn lerp_unclamped(_from: Self, _to: Self, _factor: Factor) -> Self::Output { ObjectSkeleton } -} - -pub struct SkeletonAttr; - -const SCALE: f32 = 1.0 / 11.0; +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"; @@ -34,17 +30,53 @@ impl Skeleton for ObjectSkeleton { base_mat: Mat4, buf: &mut [FigureBoneData; super::MAX_BONE_COUNT], ) -> Vec3 { - buf[0] = make_bone(base_mat * Mat4::scaling_3d(SCALE)); - // TODO: Make dependent on bone, when we find an easier way to make that - // information available. - Vec3::unit_z() * 0.5 + 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)), + 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 { + type Error = (); + + fn try_from(body: &'a comp::Body) -> Result { + match body { + comp::Body::Object(body) => Ok(SkeletonAttr::from(body)), + _ => Err(()), + } } } impl Default for SkeletonAttr { - fn default() -> Self { Self } + fn default() -> Self { + Self { + bone0: (0.0, 0.0, 0.0), + bone1: (0.0, 0.0, 0.0), + } + } } impl<'a> From<&'a Body> for SkeletonAttr { - fn from(_body: &'a Body) -> Self { Self } + fn from(body: &'a Body) -> Self { + use comp::object::Body::*; + Self { + bone0: match body { + 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 new file mode 100644 index 0000000000..d2d558e948 --- /dev/null +++ b/voxygen/anim/src/object/shoot.rs @@ -0,0 +1,62 @@ +use super::{ + super::{vek::*, Animation}, + ObjectSkeleton, SkeletonAttr, +}; +use common::{ + comp::{item::ToolKind, object::Body}, + states::utils::StageSection, +}; +pub struct ShootAnimation; + +type ShootAnimationDependency = ( + Option, + Option, + Option, + Body, +); +impl Animation for ShootAnimation { + type Dependency = ShootAnimationDependency; + type Skeleton = ObjectSkeleton; + + #[cfg(feature = "use-dyn-lib")] + const UPDATE_FN: &'static [u8] = b"object_shoot\0"; + + #[cfg_attr(feature = "be-dyn-lib", export_name = "object_shoot")] + #[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(); + + 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) / 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 6229f89306..9a09f852ee 100644 --- a/voxygen/src/ecs/sys/interpolation.rs +++ b/voxygen/src/ecs/sys/interpolation.rs @@ -1,6 +1,6 @@ use crate::ecs::comp::Interpolated; use common::{ - comp::{Ori, Pos, Vel}, + comp::{object, Body, Ori, Pos, Vel}, resources::DeltaTime, }; use specs::{Entities, Join, Read, ReadStorage, System, WriteStorage}; @@ -17,20 +17,28 @@ impl<'a> System<'a> for Sys { ReadStorage<'a, Pos>, ReadStorage<'a, Ori>, ReadStorage<'a, Vel>, + ReadStorage<'a, Body>, WriteStorage<'a, Interpolated>, ); fn run( &mut self, - (entities, dt, positions, orientations, velocities, mut interpolated): Self::SystemData, + (entities, dt, positions, orientations, velocities, bodies, mut interpolated): Self::SystemData, ) { // Update interpolated positions and orientations - for (pos, ori, i, vel) in (&positions, &orientations, &mut interpolated, &velocities).join() + for (pos, ori, i, body, vel) in ( + &positions, + &orientations, + &mut interpolated, + &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, 10.0 * dt.0); - i.ori = Ori::slerp(i.ori, *ori, 5.0 * dt.0); + i.ori = Ori::slerp(i.ori, *ori, base_ori_interp(body) * dt.0); } else { i.pos = pos.0; i.ori = *ori; @@ -72,3 +80,14 @@ impl<'a> System<'a> for Sys { } } } + +#[allow(clippy::collapsible_match)] +fn base_ori_interp(body: &Body) -> f32 { + match body { + Body::Object(object) => match object { + object::Body::Crossbow => 100.0, + _ => 10.0, + }, + _ => 10.0, + } +} diff --git a/voxygen/src/scene/figure/load.rs b/voxygen/src/scene/figure/load.rs index f0c2f3b7e3..0f2bf8a2d8 100644 --- a/voxygen/src/scene/figure/load.rs +++ b/voxygen/src/scene/figure/load.rs @@ -3801,6 +3801,7 @@ struct ObjectCentralSpec(HashMap); #[derive(Deserialize)] struct SidedObjectCentralVoxSpec { bone0: ObjectCentralSubSpec, + bone1: ObjectCentralSubSpec, } #[derive(Deserialize)] struct ObjectCentralSubSpec { @@ -3818,7 +3819,9 @@ make_vox_spec!( Some(spec.central.read().0.mesh_bone0( body, )), - None, + Some(spec.central.read().0.mesh_bone1( + body, + )), None, None, None, @@ -3850,4 +3853,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 f9ebd9e99f..295a3552e5 100644 --- a/voxygen/src/scene/figure/mod.rs +++ b/voxygen/src/scene/figure/mod.rs @@ -3480,7 +3480,7 @@ impl FigureMgr { ); }, Body::Object(body) => { - let (model, _) = self.object_model_cache.get_or_create_model( + let (model, skeleton_attr) = self.object_model_cache.get_or_create_model( renderer, &mut self.col_lights, *body, @@ -3496,6 +3496,96 @@ impl FigureMgr { FigureState::new(renderer, ObjectSkeleton::default()) }); + let (character, last_character) = match (character, last_character) { + (Some(c), Some(l)) => (c, l), + _ => (&CharacterState::Idle, &Last { + 0: CharacterState::Idle, + }), + }; + + if !character.same_variant(&last_character.0) { + state.state_time = 0.0; + } + + let target_base = match ( + physics.on_ground, + vel.0.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving + physics.in_liquid.is_some(), // In water + ) { + // Standing + (true, false, false) => anim::object::IdleAnimation::update_skeleton( + &ObjectSkeleton::default(), + (active_tool_kind, second_tool_kind, time), + state.state_time, + &mut state_animation_rate, + skeleton_attr, + ), + _ => anim::object::IdleAnimation::update_skeleton( + &ObjectSkeleton::default(), + (active_tool_kind, second_tool_kind, time), + state.state_time, + &mut state_animation_rate, + skeleton_attr, + ), + }; + + let target_bones = match &character { + CharacterState::BasicRanged(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::Recover => { + stage_time / s.static_data.recover_duration.as_secs_f64() + }, + + _ => 0.0, + }; + anim::object::ShootAnimation::update_skeleton( + &target_base, + ( + active_tool_kind, + second_tool_kind, + 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, + skeleton_attr, + ) + }, + // TODO! + _ => target_base, + }; + + state.skeleton = anim::vek::Lerp::lerp(&state.skeleton, &target_bones, dt_lerp); state.update( renderer, pos.0, diff --git a/voxygen/src/scene/particle.rs b/voxygen/src/scene/particle.rs index 9ae5e1d97d..83d8ceb793 100644 --- a/voxygen/src/scene/particle.rs +++ b/voxygen/src/scene/particle.rs @@ -8,13 +8,13 @@ use crate::{ }; use common::{ assets::{AssetExt, DotVoxAsset}, - comp::{item::Reagent, object, Body, CharacterState, Ori, Pos, Shockwave}, + combat::CombatEffect, + comp::{item::Reagent, object, BeamSegment, Body, CharacterState, Ori, Pos, Shockwave}, figure::Segment, outcome::Outcome, resources::DeltaTime, span, spiral::Spiral2d, - states::utils::StageSection, terrain::TerrainChunk, vol::{RectRasterableVol, SizedVol}, }; @@ -366,65 +366,62 @@ impl ParticleMgr { let state = scene_data.state; let ecs = state.ecs(); let time = state.get_time(); + let dt = scene_data.state.ecs().fetch::().0; - for (pos, ori, character_state) in ( + for (pos, ori, beam) in ( &ecs.read_storage::(), &ecs.read_storage::(), - &ecs.read_storage::(), + &ecs.read_storage::(), ) .join() + .filter(|(_, _, b)| b.creation.map_or(true, |c| (c + dt as f64) >= time)) { - if let CharacterState::BasicBeam(b) = character_state { - let particle_ori = b.particle_ori.unwrap_or_else(|| ori.look_vec()); - if b.stage_section == StageSection::Cast { - if b.static_data.base_hps > 0.0 { - // Emit a light when using healing - lights.push(Light::new(pos.0 + b.offset, Rgb::new(0.1, 1.0, 0.15), 1.0)); - for i in 0..self.scheduler.heartbeats(Duration::from_millis(1)) { - self.particles.push(Particle::new_beam( - b.static_data.beam_duration, - time + i as f64 / 1000.0, - ParticleMode::HealingBeam, - pos.0 + particle_ori * 0.5 + b.offset, - pos.0 + particle_ori * b.static_data.range + b.offset, - )); - } - } else { - let mut rng = thread_rng(); - let (from, to) = (Vec3::::unit_z(), particle_ori); - let m = Mat3::::rotation_from_to_3d(from, to); - // Emit a light when using flames - lights.push(Light::new( - pos.0 + b.offset, - 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..b.static_data.max_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); - Particle::new_beam( - b.static_data.beam_duration, - time, - ParticleMode::FlameThrower, - pos.0 + random_ori * 0.5 + b.offset, - pos.0 + random_ori * b.static_data.range + b.offset, - ) - }, - ); - } + let range = beam.properties.speed * beam.properties.duration.as_secs_f32(); + 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)) { + self.particles.push(Particle::new_beam( + beam.properties.duration, + time + i as f64 / 1000.0, + ParticleMode::HealingBeam, + pos.0, + pos.0 + *ori.look_dir() * range, + )); } + } else { + let mut rng = thread_rng(); + 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)), + 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 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); + Particle::new_beam( + beam.properties.duration, + time, + ParticleMode::FlameThrower, + pos.0 + random_ori, + pos.0 + random_ori * range, + ) + }, + ); } } } 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)