mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Merge branch 'sam/small-fixes' into 'master'
Fixed bow AI and inactive weapon exp gain See merge request veloren/veloren!2349
This commit is contained in:
commit
a0fc75ad18
@ -38,7 +38,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
- Sort inventory button
|
- Sort inventory button
|
||||||
- Option to change the master volume when window is unfocused
|
- Option to change the master volume when window is unfocused
|
||||||
- Crafting stations in towns
|
- Crafting stations in towns
|
||||||
- Option to change the master volume when window is unfocused
|
- Option to change the master volume when window is unfocused
|
||||||
- Entities now have mass
|
- Entities now have mass
|
||||||
- Entities now have density
|
- Entities now have density
|
||||||
- Buoyancy is calculated from the difference in density between an entity and surrounding fluid
|
- Buoyancy is calculated from the difference in density between an entity and surrounding fluid
|
||||||
@ -63,7 +63,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
- NPC's now hear certain sounds
|
- NPC's now hear certain sounds
|
||||||
- Renamed Animal Trainers to Beastmasters and gave them their own set of armor to wear
|
- Renamed Animal Trainers to Beastmasters and gave them their own set of armor to wear
|
||||||
- ChargedRanged attacks (such as some bow attacks) use an FOV zoom effect to indicate charge.
|
- ChargedRanged attacks (such as some bow attacks) use an FOV zoom effect to indicate charge.
|
||||||
- Add chest to each dungeon with unique loot
|
- Add chest to each dungeon with unique loot
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
@ -111,6 +111,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
- Entities catch on fire if they stand too close to campfires
|
- Entities catch on fire if they stand too close to campfires
|
||||||
- Water extinguishes entities on fire
|
- Water extinguishes entities on fire
|
||||||
- Item pickups are shown in separate window and inventory-full shows above item
|
- Item pickups are shown in separate window and inventory-full shows above item
|
||||||
|
- Reworked bow
|
||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
|
|
||||||
|
@ -909,19 +909,33 @@ fn handle_exp_gain(
|
|||||||
uid: &Uid,
|
uid: &Uid,
|
||||||
outcomes: &mut Vec<Outcome>,
|
outcomes: &mut Vec<Outcome>,
|
||||||
) {
|
) {
|
||||||
let (main_tool_kind, second_tool_kind) = combat::get_weapons(inventory);
|
use comp::inventory::{item::ItemKind, slot::EquipSlot};
|
||||||
|
// Create hash set of xp pools to consider splitting xp amongst
|
||||||
let mut xp_pools = HashSet::<SkillGroupKind>::new();
|
let mut xp_pools = HashSet::<SkillGroupKind>::new();
|
||||||
|
// Insert general pool since it is always accessible
|
||||||
xp_pools.insert(SkillGroupKind::General);
|
xp_pools.insert(SkillGroupKind::General);
|
||||||
if let Some(w) = main_tool_kind {
|
// Closure to add xp pool corresponding to weapon type equipped in a particular
|
||||||
if skill_set.contains_skill_group(SkillGroupKind::Weapon(w)) {
|
// EquipSlot
|
||||||
xp_pools.insert(SkillGroupKind::Weapon(w));
|
let mut add_tool_from_slot = |equip_slot| {
|
||||||
|
let tool_kind = inventory.equipped(equip_slot).and_then(|i| {
|
||||||
|
if let ItemKind::Tool(tool) = &i.kind() {
|
||||||
|
Some(tool.kind)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if let Some(weapon) = tool_kind {
|
||||||
|
// Only adds to xp pools if entity has that skill group available
|
||||||
|
if skill_set.contains_skill_group(SkillGroupKind::Weapon(weapon)) {
|
||||||
|
xp_pools.insert(SkillGroupKind::Weapon(weapon));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
if let Some(w) = second_tool_kind {
|
// Add weapons to xp pools considered
|
||||||
if skill_set.contains_skill_group(SkillGroupKind::Weapon(w)) {
|
add_tool_from_slot(EquipSlot::ActiveMainhand);
|
||||||
xp_pools.insert(SkillGroupKind::Weapon(w));
|
add_tool_from_slot(EquipSlot::ActiveOffhand);
|
||||||
}
|
add_tool_from_slot(EquipSlot::InactiveMainhand);
|
||||||
}
|
add_tool_from_slot(EquipSlot::InactiveOffhand);
|
||||||
let num_pools = xp_pools.len() as f32;
|
let num_pools = xp_pools.len() as f32;
|
||||||
for pool in xp_pools {
|
for pool in xp_pools {
|
||||||
skill_set.change_experience(pool, (exp_reward / num_pools).ceil() as i32);
|
skill_set.change_experience(pool, (exp_reward / num_pools).ceil() as i32);
|
||||||
|
@ -1644,15 +1644,27 @@ impl<'a> AgentData<'a> {
|
|||||||
| Tactic::Turret
|
| Tactic::Turret
|
||||||
if dist_sqrd > 0.0 =>
|
if dist_sqrd > 0.0 =>
|
||||||
{
|
{
|
||||||
aim_projectile(
|
if matches!(self.char_state, CharacterState::ChargedRanged(_)) {
|
||||||
90.0, // + self.vel.0.magnitude(),
|
aim_projectile(
|
||||||
Vec3::new(self.pos.0.x, self.pos.0.y, self.pos.0.z + eye_offset),
|
175.0,
|
||||||
Vec3::new(
|
Vec3::new(self.pos.0.x, self.pos.0.y, self.pos.0.z + eye_offset),
|
||||||
tgt_data.pos.0.x,
|
Vec3::new(
|
||||||
tgt_data.pos.0.y,
|
tgt_data.pos.0.x,
|
||||||
tgt_data.pos.0.z + tgt_eye_offset,
|
tgt_data.pos.0.y,
|
||||||
),
|
tgt_data.pos.0.z + tgt_eye_offset,
|
||||||
)
|
),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
aim_projectile(
|
||||||
|
90.0, // + self.vel.0.magnitude(),
|
||||||
|
Vec3::new(self.pos.0.x, self.pos.0.y, self.pos.0.z + eye_offset),
|
||||||
|
Vec3::new(
|
||||||
|
tgt_data.pos.0.x,
|
||||||
|
tgt_data.pos.0.y,
|
||||||
|
tgt_data.pos.0.z + tgt_eye_offset,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Tactic::ClayGolem if matches!(self.char_state, CharacterState::BasicRanged(_)) => {
|
Tactic::ClayGolem if matches!(self.char_state, CharacterState::BasicRanged(_)) => {
|
||||||
const ROCKET_SPEED: f32 = 30.0;
|
const ROCKET_SPEED: f32 = 30.0;
|
||||||
@ -2040,10 +2052,64 @@ impl<'a> AgentData<'a> {
|
|||||||
tgt_data: &TargetData,
|
tgt_data: &TargetData,
|
||||||
read_data: &ReadData,
|
read_data: &ReadData,
|
||||||
) {
|
) {
|
||||||
if attack_data.dist_sqrd < (2.0 * attack_data.min_attack_dist).powi(2) {
|
const MIN_CHARGE_FRAC: f32 = 0.5;
|
||||||
if self.body.map(|b| b.is_humanoid()).unwrap_or(false)
|
const OPTIMAL_TARGET_VELOCITY: f32 = 5.0;
|
||||||
&& self.energy.current() > CharacterAbility::default_roll().get_energy_cost()
|
const DESIRED_ENERGY_LEVEL: u32 = 500;
|
||||||
|
// Logic to use abilities
|
||||||
|
if let CharacterState::ChargedRanged(c) = self.char_state {
|
||||||
|
if !matches!(c.stage_section, StageSection::Recover) {
|
||||||
|
// Don't even bother with this logic if in recover
|
||||||
|
let target_speed_sqd = agent
|
||||||
|
.target
|
||||||
|
.as_ref()
|
||||||
|
.map(|t| t.target)
|
||||||
|
.and_then(|e| read_data.velocities.get(e))
|
||||||
|
.map_or(0.0, |v| v.0.magnitude_squared());
|
||||||
|
if c.charge_frac() < MIN_CHARGE_FRAC
|
||||||
|
|| (target_speed_sqd > OPTIMAL_TARGET_VELOCITY.powi(2) && c.charge_frac() < 1.0)
|
||||||
|
{
|
||||||
|
// If haven't charged to desired level, or target is moving too fast and haven't
|
||||||
|
// fully charged, keep charging
|
||||||
|
controller
|
||||||
|
.actions
|
||||||
|
.push(ControlAction::basic_input(InputKind::Primary));
|
||||||
|
}
|
||||||
|
// Else don't send primary input to release the shot
|
||||||
|
}
|
||||||
|
} else if matches!(self.char_state, CharacterState::RepeaterRanged(c) if self.energy.current() > 50 && !matches!(c.stage_section, StageSection::Recover))
|
||||||
|
{
|
||||||
|
// If in repeater ranged, have enough energy, and aren't in recovery, try to
|
||||||
|
// keep firing
|
||||||
|
if attack_data.dist_sqrd > attack_data.min_attack_dist.powi(2)
|
||||||
|
&& can_see_tgt(
|
||||||
|
&*read_data.terrain,
|
||||||
|
self.pos,
|
||||||
|
tgt_data.pos,
|
||||||
|
attack_data.dist_sqrd,
|
||||||
|
)
|
||||||
{
|
{
|
||||||
|
// Only keep firing if not in melee range or if can see target
|
||||||
|
controller
|
||||||
|
.actions
|
||||||
|
.push(ControlAction::basic_input(InputKind::Secondary));
|
||||||
|
}
|
||||||
|
} else if attack_data.dist_sqrd < (2.0 * attack_data.min_attack_dist).powi(2) {
|
||||||
|
if self
|
||||||
|
.skill_set
|
||||||
|
.has_skill(Skill::Bow(BowSkill::UnlockShotgun))
|
||||||
|
&& self.energy.current() > 450
|
||||||
|
&& thread_rng().gen_bool(0.5)
|
||||||
|
{
|
||||||
|
// Use shotgun if target close and have sufficient energy
|
||||||
|
controller
|
||||||
|
.actions
|
||||||
|
.push(ControlAction::basic_input(InputKind::Ability(0)));
|
||||||
|
} else if self.body.map(|b| b.is_humanoid()).unwrap_or(false)
|
||||||
|
&& self.energy.current() > CharacterAbility::default_roll().get_energy_cost()
|
||||||
|
&& !matches!(self.char_state, CharacterState::BasicRanged(c) if !matches!(c.stage_section, StageSection::Recover))
|
||||||
|
{
|
||||||
|
// Else roll away if can roll and have enough energy, and not using shotgun
|
||||||
|
// (other 2 attacks have interrupt handled above) unless in recover
|
||||||
controller
|
controller
|
||||||
.actions
|
.actions
|
||||||
.push(ControlAction::basic_input(InputKind::Roll));
|
.push(ControlAction::basic_input(InputKind::Roll));
|
||||||
@ -2055,7 +2121,46 @@ impl<'a> AgentData<'a> {
|
|||||||
.push(ControlAction::basic_input(InputKind::Primary));
|
.push(ControlAction::basic_input(InputKind::Primary));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if attack_data.dist_sqrd < MAX_PATH_DIST.powi(2)
|
||||||
|
&& can_see_tgt(
|
||||||
|
&*read_data.terrain,
|
||||||
|
self.pos,
|
||||||
|
tgt_data.pos,
|
||||||
|
attack_data.dist_sqrd,
|
||||||
|
)
|
||||||
|
{
|
||||||
|
// If not really far, and can see target, attempt to shoot bow
|
||||||
|
if self.energy.current() < DESIRED_ENERGY_LEVEL {
|
||||||
|
// If low on energy, use primary to attempt to regen energy
|
||||||
|
controller
|
||||||
|
.actions
|
||||||
|
.push(ControlAction::basic_input(InputKind::Primary));
|
||||||
|
} else {
|
||||||
|
// Else we have enough energy, use repeater
|
||||||
|
controller
|
||||||
|
.actions
|
||||||
|
.push(ControlAction::basic_input(InputKind::Secondary));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Logic to move. Intentionally kept separate from ability logic so duplicated
|
||||||
|
// work is less necessary.
|
||||||
|
if attack_data.dist_sqrd < (2.0 * attack_data.min_attack_dist).powi(2) {
|
||||||
|
// Attempt to move away from target if too close
|
||||||
|
if let Some((bearing, speed)) = agent.chaser.chase(
|
||||||
|
&*read_data.terrain,
|
||||||
|
self.pos.0,
|
||||||
|
self.vel.0,
|
||||||
|
tgt_data.pos.0,
|
||||||
|
TraversalConfig {
|
||||||
|
min_tgt_dist: 1.25,
|
||||||
|
..self.traversal_config
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
controller.inputs.move_dir =
|
||||||
|
-bearing.xy().try_normalized().unwrap_or_else(Vec2::zero) * speed;
|
||||||
|
}
|
||||||
} else if attack_data.dist_sqrd < MAX_PATH_DIST.powi(2) {
|
} else if attack_data.dist_sqrd < MAX_PATH_DIST.powi(2) {
|
||||||
|
// Else attempt to circle target if neither too close nor too far
|
||||||
if let Some((bearing, speed)) = agent.chaser.chase(
|
if let Some((bearing, speed)) = agent.chaser.chase(
|
||||||
&*read_data.terrain,
|
&*read_data.terrain,
|
||||||
self.pos.0,
|
self.pos.0,
|
||||||
@ -2079,54 +2184,25 @@ impl<'a> AgentData<'a> {
|
|||||||
.try_normalized()
|
.try_normalized()
|
||||||
.unwrap_or_else(Vec2::zero)
|
.unwrap_or_else(Vec2::zero)
|
||||||
* speed;
|
* speed;
|
||||||
if agent.action_state.timer > 4.0 {
|
|
||||||
controller
|
|
||||||
.actions
|
|
||||||
.push(ControlAction::CancelInput(InputKind::Secondary));
|
|
||||||
agent.action_state.timer = 0.0;
|
|
||||||
} else if agent.action_state.timer > 2.0 && self.energy.current() > 300 {
|
|
||||||
controller
|
|
||||||
.actions
|
|
||||||
.push(ControlAction::basic_input(InputKind::Secondary));
|
|
||||||
agent.action_state.timer += read_data.dt.0;
|
|
||||||
} else if self
|
|
||||||
.skill_set
|
|
||||||
.has_skill(Skill::Bow(BowSkill::UnlockShotgun))
|
|
||||||
&& self.energy.current() > 400
|
|
||||||
&& thread_rng().gen_bool(0.8)
|
|
||||||
{
|
|
||||||
controller
|
|
||||||
.actions
|
|
||||||
.push(ControlAction::CancelInput(InputKind::Secondary));
|
|
||||||
controller
|
|
||||||
.actions
|
|
||||||
.push(ControlAction::basic_input(InputKind::Ability(0)));
|
|
||||||
agent.action_state.timer += read_data.dt.0;
|
|
||||||
} else {
|
|
||||||
controller
|
|
||||||
.actions
|
|
||||||
.push(ControlAction::CancelInput(InputKind::Secondary));
|
|
||||||
controller
|
|
||||||
.actions
|
|
||||||
.push(ControlAction::basic_input(InputKind::Primary));
|
|
||||||
agent.action_state.timer += read_data.dt.0;
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
|
// Unless cannot see target, then move towards them
|
||||||
controller.inputs.move_dir =
|
controller.inputs.move_dir =
|
||||||
bearing.xy().try_normalized().unwrap_or_else(Vec2::zero) * speed;
|
bearing.xy().try_normalized().unwrap_or_else(Vec2::zero) * speed;
|
||||||
self.jump_if(controller, bearing.z > 1.5);
|
self.jump_if(controller, bearing.z > 1.5);
|
||||||
controller.inputs.move_z = bearing.z;
|
controller.inputs.move_z = bearing.z;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Sometimes try to roll
|
||||||
if self.body.map(|b| b.is_humanoid()).unwrap_or(false)
|
if self.body.map(|b| b.is_humanoid()).unwrap_or(false)
|
||||||
&& attack_data.dist_sqrd < 16.0f32.powi(2)
|
&& attack_data.dist_sqrd < 16.0f32.powi(2)
|
||||||
&& thread_rng().gen::<f32>() < 0.02
|
&& thread_rng().gen::<f32>() < 0.01
|
||||||
{
|
{
|
||||||
controller
|
controller
|
||||||
.actions
|
.actions
|
||||||
.push(ControlAction::basic_input(InputKind::Roll));
|
.push(ControlAction::basic_input(InputKind::Roll));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
// If too far, move towards target
|
||||||
self.path_toward_target(agent, controller, tgt_data, read_data, false, None);
|
self.path_toward_target(agent, controller, tgt_data, read_data, false, None);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user