mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Give NPCs the ability to use potions.
This commit is contained in:
parent
dc4c02baad
commit
ddf6a26577
@ -26,6 +26,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
- RTsim travellers now follow paths between towns
|
- RTsim travellers now follow paths between towns
|
||||||
- "Poise" renamed to "Stun resilience"
|
- "Poise" renamed to "Stun resilience"
|
||||||
- Stun resilience stat display
|
- Stun resilience stat display
|
||||||
|
- Villagers and guards now spawn with potions, and know how to use them.
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
@ -13,6 +13,7 @@ use crate::{
|
|||||||
};
|
};
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use tracing::warn;
|
||||||
|
|
||||||
/// Builder for character Loadouts, containing weapon and armour items belonging
|
/// Builder for character Loadouts, containing weapon and armour items belonging
|
||||||
/// to a character, along with some helper methods for loading Items and
|
/// to a character, along with some helper methods for loading Items and
|
||||||
@ -55,6 +56,18 @@ pub enum LoadoutConfig {
|
|||||||
Warlock,
|
Warlock,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn make_potion_bag(quantity: u32) -> Item {
|
||||||
|
let mut bag = Item::new_from_asset_expect("common.items.armor.misc.bag.tiny_leather_pouch");
|
||||||
|
if let Some(i) = bag.slots_mut().iter_mut().next() {
|
||||||
|
let mut potions = Item::new_from_asset_expect("common.items.consumable.potion_big");
|
||||||
|
if let Err(e) = potions.set_amount(quantity) {
|
||||||
|
warn!("Failed to set potion quantity: {:?}", e);
|
||||||
|
}
|
||||||
|
*i = Some(potions);
|
||||||
|
}
|
||||||
|
bag
|
||||||
|
}
|
||||||
|
|
||||||
impl LoadoutBuilder {
|
impl LoadoutBuilder {
|
||||||
#[allow(clippy::new_without_default)] // TODO: Pending review in #587
|
#[allow(clippy::new_without_default)] // TODO: Pending review in #587
|
||||||
pub fn new() -> Self { Self(Loadout::new_empty()) }
|
pub fn new() -> Self { Self(Loadout::new_empty()) }
|
||||||
@ -551,6 +564,7 @@ impl LoadoutBuilder {
|
|||||||
0 => Some(Item::new_from_asset_expect("common.items.lantern.black_0")),
|
0 => Some(Item::new_from_asset_expect("common.items.lantern.black_0")),
|
||||||
_ => None,
|
_ => None,
|
||||||
})
|
})
|
||||||
|
.bag(ArmorSlot::Bag1, Some(make_potion_bag(25)))
|
||||||
.build(),
|
.build(),
|
||||||
Merchant => {
|
Merchant => {
|
||||||
let mut backpack =
|
let mut backpack =
|
||||||
@ -934,6 +948,7 @@ impl LoadoutBuilder {
|
|||||||
_ => "common.items.armor.misc.foot.sandals",
|
_ => "common.items.armor.misc.foot.sandals",
|
||||||
},
|
},
|
||||||
)))
|
)))
|
||||||
|
.bag(ArmorSlot::Bag1, Some(make_potion_bag(10)))
|
||||||
.build(),
|
.build(),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -131,6 +131,10 @@ impl Entity {
|
|||||||
.chest(chest)
|
.chest(chest)
|
||||||
.pants(pants)
|
.pants(pants)
|
||||||
.shoulder(shoulder)
|
.shoulder(shoulder)
|
||||||
|
.bag(
|
||||||
|
comp::inventory::slot::ArmorSlot::Bag1,
|
||||||
|
Some(comp::inventory::loadout_builder::make_potion_bag(100)),
|
||||||
|
)
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,13 +11,15 @@ use common::{
|
|||||||
invite::{InviteKind, InviteResponse},
|
invite::{InviteKind, InviteResponse},
|
||||||
item::{
|
item::{
|
||||||
tool::{ToolKind, UniqueKind},
|
tool::{ToolKind, UniqueKind},
|
||||||
ItemDesc, ItemKind,
|
Item, ItemDesc, ItemKind,
|
||||||
},
|
},
|
||||||
skills::{AxeSkill, BowSkill, HammerSkill, Skill, StaffSkill, SwordSkill},
|
skills::{AxeSkill, BowSkill, HammerSkill, Skill, StaffSkill, SwordSkill},
|
||||||
Agent, Alignment, BehaviorCapability, BehaviorState, Body, CharacterState, ControlAction,
|
Agent, Alignment, BehaviorCapability, BehaviorState, Body, CharacterState, ControlAction,
|
||||||
ControlEvent, Controller, Energy, Health, InputKind, Inventory, LightEmitter, MountState,
|
ControlEvent, Controller, Energy, Health, HealthChange, InputKind, Inventory,
|
||||||
Ori, PhysicsState, Pos, Scale, Stats, UnresolvedChatMsg, Vel,
|
InventoryAction, LightEmitter, MountState, Ori, PhysicsState, Pos, Scale, Stats,
|
||||||
|
UnresolvedChatMsg, Vel,
|
||||||
},
|
},
|
||||||
|
effect::{BuffEffect, Effect},
|
||||||
event::{Emitter, EventBus, ServerEvent},
|
event::{Emitter, EventBus, ServerEvent},
|
||||||
path::TraversalConfig,
|
path::TraversalConfig,
|
||||||
resources::{DeltaTime, Time, TimeOfDay},
|
resources::{DeltaTime, Time, TimeOfDay},
|
||||||
@ -109,6 +111,7 @@ const SIGHT_DIST: f32 = 80.0;
|
|||||||
const SNEAK_COEFFICIENT: f32 = 0.25;
|
const SNEAK_COEFFICIENT: f32 = 0.25;
|
||||||
const AVG_FOLLOW_DIST: f32 = 6.0;
|
const AVG_FOLLOW_DIST: f32 = 6.0;
|
||||||
const RETARGETING_THRESHOLD_SECONDS: f64 = 10.0;
|
const RETARGETING_THRESHOLD_SECONDS: f64 = 10.0;
|
||||||
|
const HEALING_ITEM_THRESHOLD: f32 = 0.5;
|
||||||
|
|
||||||
/// This system will allow NPCs to modify their controller
|
/// This system will allow NPCs to modify their controller
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
@ -576,6 +579,11 @@ impl<'a> AgentData<'a> {
|
|||||||
read_data: &ReadData,
|
read_data: &ReadData,
|
||||||
event_emitter: &mut Emitter<'_, ServerEvent>,
|
event_emitter: &mut Emitter<'_, ServerEvent>,
|
||||||
) {
|
) {
|
||||||
|
if self.damage < HEALING_ITEM_THRESHOLD && self.heal_self(agent, controller) {
|
||||||
|
agent.action_timer = 0.01;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(Target {
|
if let Some(Target {
|
||||||
target,
|
target,
|
||||||
selected_at,
|
selected_at,
|
||||||
@ -619,12 +627,14 @@ impl<'a> AgentData<'a> {
|
|||||||
event_emitter
|
event_emitter
|
||||||
.emit(ServerEvent::Chat(UnresolvedChatMsg::npc(*self.uid, msg)));
|
.emit(ServerEvent::Chat(UnresolvedChatMsg::npc(*self.uid, msg)));
|
||||||
}
|
}
|
||||||
// Choose a new target every 10 seconds
|
// Choose a new target every 10 seconds, but only for
|
||||||
|
// enemies
|
||||||
// TODO: This should be more principled. Consider factoring
|
// TODO: This should be more principled. Consider factoring
|
||||||
// health, combat rating, wielded
|
// health, combat rating, wielded weapon, etc, into the
|
||||||
// weapon, etc, into the decision to change
|
// decision to change target.
|
||||||
// target.
|
} else if read_data.time.0 - selected_at > RETARGETING_THRESHOLD_SECONDS
|
||||||
} else if read_data.time.0 - selected_at > RETARGETING_THRESHOLD_SECONDS {
|
&& matches!(self.alignment, Some(Alignment::Enemy))
|
||||||
|
{
|
||||||
self.choose_target(agent, controller, &read_data, event_emitter);
|
self.choose_target(agent, controller, &read_data, event_emitter);
|
||||||
} else if dist_sqrd < SIGHT_DIST.powi(2) {
|
} else if dist_sqrd < SIGHT_DIST.powi(2) {
|
||||||
self.attack(
|
self.attack(
|
||||||
@ -690,6 +700,11 @@ impl<'a> AgentData<'a> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if self.damage < HEALING_ITEM_THRESHOLD && self.heal_self(agent, controller) {
|
||||||
|
agent.action_timer = 0.01;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
agent.action_timer = 0.0;
|
agent.action_timer = 0.0;
|
||||||
if let Some((travel_to, _destination)) = &agent.rtsim_controller.travel_to {
|
if let Some((travel_to, _destination)) = &agent.rtsim_controller.travel_to {
|
||||||
// if it has an rtsim destination and can fly then it should
|
// if it has an rtsim destination and can fly then it should
|
||||||
@ -1289,6 +1304,61 @@ impl<'a> AgentData<'a> {
|
|||||||
agent.action_timer += dt.0;
|
agent.action_timer += dt.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Attempt to consume a healing item, and return whether any healing items
|
||||||
|
/// were queued. Callers should use this to implement a delay so that
|
||||||
|
/// the healing isn't interrupted.
|
||||||
|
fn heal_self(&self, _agent: &mut Agent, controller: &mut Controller) -> bool {
|
||||||
|
let healing_value = |item: &Item| {
|
||||||
|
let mut value = 0;
|
||||||
|
#[allow(clippy::single_match)]
|
||||||
|
match item.kind() {
|
||||||
|
ItemKind::Consumable { effect, .. } => {
|
||||||
|
for e in effect.iter() {
|
||||||
|
use BuffKind::*;
|
||||||
|
match e {
|
||||||
|
Effect::Health(HealthChange { amount, .. }) => {
|
||||||
|
value += *amount;
|
||||||
|
},
|
||||||
|
Effect::Buff(BuffEffect { kind, data, .. })
|
||||||
|
if matches!(kind, Regeneration | Saturation | Potion) =>
|
||||||
|
{
|
||||||
|
value += (data.strength
|
||||||
|
* data.duration.map_or(0.0, |d| d.as_secs() as f32))
|
||||||
|
as i32;
|
||||||
|
}
|
||||||
|
_ => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => {},
|
||||||
|
}
|
||||||
|
value
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut consumables: Vec<_> = self
|
||||||
|
.inventory
|
||||||
|
.slots_with_id()
|
||||||
|
.filter_map(|(id, slot)| match slot {
|
||||||
|
Some(item) if healing_value(item) > 0 => Some((id, item)),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
consumables.sort_by_key(|(_, item)| healing_value(item));
|
||||||
|
|
||||||
|
if let Some((id, _)) = consumables.last() {
|
||||||
|
use comp::inventory::slot::Slot;
|
||||||
|
controller
|
||||||
|
.actions
|
||||||
|
.push(ControlAction::InventoryAction(InventoryAction::Use(
|
||||||
|
Slot::Inventory(*id),
|
||||||
|
)));
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn choose_target(
|
fn choose_target(
|
||||||
&self,
|
&self,
|
||||||
agent: &mut Agent,
|
agent: &mut Agent,
|
||||||
|
Loading…
Reference in New Issue
Block a user