Skip to content

Commit 26e2102

Browse files
committed
add spread
1 parent 1c96def commit 26e2102

File tree

3 files changed

+167
-51
lines changed

3 files changed

+167
-51
lines changed

octbots/src/behavior.rs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,8 +97,8 @@ pub extern "C" fn init_bot(edict: u16, client: &CClient) {
9797
]);
9898

9999
let targetting = Sequence(vec![
100-
Action(TargetingAction::TargetSwitching.into()),
101100
Action(TargetingAction::Shoot.into()),
101+
Action(TargetingAction::TargetSwitching.into()),
102102
]);
103103

104104
let target_tracking = Sequence(vec![
@@ -142,6 +142,9 @@ pub extern "C" fn init_bot(edict: u16, client: &CClient) {
142142
t: Targeting {
143143
current_target: Target::None,
144144
last_target: Target::None,
145+
reacts_at: 0.,
146+
spread: Vec::new(),
147+
spread_rigth: true,
145148
hates: BTreeMap::default(),
146149
},
147150

@@ -264,7 +267,11 @@ pub extern "C" fn wallpathfining_bots(helper: &CUserCmdHelper, bot: &mut CPlayer
264267
if status != RUNNING {
265268
brain.path_receiver.take();
266269
}
267-
status
270+
if status.0 == Status::Failure {
271+
RUNNING
272+
} else {
273+
status
274+
}
268275
}
269276
BotAction::RenderPath => 'render: {
270277
if brain.path.is_empty() {

octbots/src/movement.rs

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ use std::{f32::consts::PI, ops::Not};
1414

1515
use crate::behavior::BotBrain;
1616
use crate::pathfinding::AreaCost;
17+
use crate::targeting::natural_aim;
1718
use crate::{
1819
behavior::BotAction,
1920
loader::{Navmesh, NavmeshStatus},
@@ -202,11 +203,17 @@ pub fn run_movement(
202203
let angle = (target.y - brain.origin.y).atan2(target.x - brain.origin.x);
203204

204205
if !brain.m.view_lock {
205-
brain.next_cmd.world_view_angles.y = angle
206-
.clamp(angle - TURN_RATE, angle + TURN_RATE)
207-
.to_degrees()
208-
+ brain.angles.y * brain.m.view_lock as i32 as f32;
209-
brain.next_cmd.world_view_angles.x = 0.;
206+
brain.next_cmd.world_view_angles = natural_aim(
207+
brain.angles,
208+
Vector3::new(
209+
0.,
210+
angle
211+
.clamp(angle - TURN_RATE, angle + TURN_RATE)
212+
.to_degrees()
213+
+ brain.angles.y * brain.m.view_lock as i32 as f32,
214+
0.,
215+
),
216+
);
210217
}
211218

212219
let forward_vector = Vec2::new(

octbots/src/targeting.rs

Lines changed: 146 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,17 @@ use rrplug::{
77
},
88
prelude::*,
99
};
10+
use rustc_hash::FxHasher;
1011
use shared::{
1112
bindings::{Action as MoveAction, Contents, TraceCollisionGroup},
1213
cmds_helper::CUserCmdHelper,
1314
utils::{get_entity_handle, get_player_index, is_alive, lookup_ent, nudge_type, trace_ray},
1415
};
15-
use std::collections::BTreeMap;
16+
use std::{
17+
collections::BTreeMap,
18+
f32::consts::{PI, TAU},
19+
hash::{DefaultHasher, Hash, Hasher},
20+
};
1621

1722
use crate::behavior::BotAction;
1823
use crate::behavior::BotBrain;
@@ -21,6 +26,9 @@ use crate::behavior::BotBrain;
2126
pub struct Targeting {
2227
pub current_target: Target,
2328
pub last_target: Target,
29+
pub reacts_at: f32,
30+
pub spread: Vec<Vector3>,
31+
pub spread_rigth: bool,
2432
pub hates: BTreeMap<usize, u32>,
2533
}
2634

@@ -82,6 +90,7 @@ pub fn run_targeting(
8290
) -> (Status, f64) {
8391
match targeting {
8492
TargetingAction::FindTarget => 'target: {
93+
const Z_OFFSET: Vector3 = Vector3::new(0., 0., 25.);
8594
let base = Vec3::new(brain.origin.x, brain.origin.y, brain.origin.z);
8695

8796
fn make_player_iterator<'a>(
@@ -122,45 +131,39 @@ pub fn run_targeting(
122131
) < (rigth.0.distance(base) as u32).saturating_sub(
123132
brain.t.hates.get(&rigth.2).copied().unwrap_or_default() * 50,
124133
)
125-
&& std::ptr::eq(
126-
trace_ray(
127-
brain.origin,
128-
Vector3::from(left.0.to_array()),
129-
Some(bot),
130-
TraceCollisionGroup::None,
131-
Contents::SOLID
132-
| Contents::MOVEABLE
133-
| Contents::WINDOW
134-
| Contents::MONSTER
135-
| Contents::GRATE
136-
| Contents::PLAYER_CLIP,
137-
helper.sv_funcs,
138-
helper.engine_funcs,
139-
)
140-
.hit_ent,
141-
left.1,
134+
&& let trace = trace_ray(
135+
brain.origin + Z_OFFSET,
136+
Vector3::from(left.0.to_array()) + Z_OFFSET,
137+
Some(bot),
138+
TraceCollisionGroup::BlockWeaponsAndPhysics,
139+
Contents::SOLID
140+
| Contents::MOVEABLE
141+
| Contents::WINDOW
142+
| Contents::MONSTER
143+
| Contents::GRATE
144+
| Contents::PLAYER_CLIP,
145+
helper.sv_funcs,
146+
helper.engine_funcs,
142147
)
148+
&& (trace.hit_ent == left.1 || trace.fraction == 1.0)
143149
{
144150
Some(left)
145151
} else if left.is_none()
146-
&& std::ptr::eq(
147-
trace_ray(
148-
brain.origin,
149-
Vector3::from(rigth.0.to_array()),
150-
Some(bot),
151-
TraceCollisionGroup::None,
152-
Contents::SOLID
153-
| Contents::MOVEABLE
154-
| Contents::WINDOW
155-
| Contents::MONSTER
156-
| Contents::GRATE
157-
| Contents::PLAYER_CLIP,
158-
helper.sv_funcs,
159-
helper.engine_funcs,
160-
)
161-
.hit_ent,
162-
rigth.1,
152+
&& let trace = trace_ray(
153+
brain.origin + Z_OFFSET,
154+
Vector3::from(rigth.0.to_array()) + Z_OFFSET,
155+
Some(bot),
156+
TraceCollisionGroup::BlockWeaponsAndPhysics,
157+
Contents::SOLID
158+
| Contents::MOVEABLE
159+
| Contents::WINDOW
160+
| Contents::MONSTER
161+
| Contents::GRATE
162+
| Contents::PLAYER_CLIP,
163+
helper.sv_funcs,
164+
helper.engine_funcs,
163165
)
166+
&& (trace.hit_ent == rigth.1 || trace.fraction == 1.0)
164167
{
165168
Some(rigth)
166169
} else {
@@ -184,7 +187,8 @@ pub fn run_targeting(
184187
.map(|(_, _, _, handle)| (handle, false))
185188
})
186189
else {
187-
break 'target (Status::Failure, 0.);
190+
brain.t.current_target = Target::None;
191+
return (Status::Success, 0.);
188192
};
189193
brain.t.current_target = Target::Entity(current_target.0, current_target.1);
190194

@@ -193,9 +197,11 @@ pub fn run_targeting(
193197

194198
TargetingAction::TargetSwitching => {
195199
if brain.t.current_target != brain.t.last_target {
196-
brain.t.last_target = brain.t.current_target;
197200
brain.needs_new_path = true;
198201
brain.path_receiver = None; // honestly should figure smth better
202+
203+
brain.t.last_target = brain.t.current_target;
204+
brain.t.spread.clear();
199205
}
200206

201207
(Status::Success, 0.)
@@ -205,16 +211,49 @@ pub fn run_targeting(
205211
if let Target::Entity(handle, true) = brain.t.current_target
206212
&& let Some(ent) = lookup_ent(handle, helper.sv_funcs)
207213
{
208-
let mut v = Vector3::ZERO;
214+
if helper.globals.curTime > brain.t.reacts_at {
215+
let mut v = Vector3::ZERO;
216+
217+
if brain.t.spread.is_empty() {
218+
generate_spread(
219+
&mut brain.t.spread,
220+
brain.t.spread_rigth,
221+
helper.globals.tickCount as u64 + get_player_index(bot) as u64,
222+
);
223+
brain.t.spread_rigth = !brain.t.spread_rigth;
224+
}
209225

210-
brain.next_cmd.buttons |= MoveAction::Attack as u32 | MoveAction::Zoom as u32;
211-
brain.next_cmd.world_view_angles =
212-
look_at(brain.origin, unsafe { *ent.get_origin(&mut v) });
213-
brain.m.can_move = true;
214-
brain.m.view_lock = true;
215-
} else {
226+
brain.next_cmd.buttons |= MoveAction::Attack as u32 | MoveAction::Zoom as u32;
227+
brain.next_cmd.world_view_angles = natural_aim(
228+
brain.angles,
229+
look_at(brain.origin, unsafe { *ent.get_origin(&mut v) })
230+
+ brain.t.spread.pop().unwrap_or_default(),
231+
);
232+
// brain.next_cmd.world_view_angles =
233+
// look_at(brain.origin, unsafe { *ent.get_origin(&mut v) })
234+
// + brain.t.spread.pop().unwrap_or_default();
235+
brain.m.can_move = true;
236+
brain.m.view_lock = true;
237+
}
238+
} else if let Target::Entity(handle, false) = brain.t.current_target
239+
&& let Target::Entity(last_handle, true) = brain.t.last_target
240+
&& handle == last_handle
241+
&& let Some(ent) = lookup_ent(handle, helper.sv_funcs)
242+
&& !is_alive(ent)
243+
{
244+
brain.next_cmd.buttons |= MoveAction::Reload as u32;
245+
} else if let Target::Entity(handle, false) = brain.t.last_target
246+
&& let Some(ent) = lookup_ent(handle, helper.sv_funcs)
247+
&& !is_alive(ent)
248+
{
216249
brain.next_cmd.buttons |= MoveAction::Reload as u32;
217250
}
251+
// maybe add a check if ammo isn't full? then reload
252+
253+
if let Target::Entity(_, false) = brain.t.current_target {
254+
const REACTON_TIME: f32 = 0.4;
255+
brain.t.reacts_at = helper.globals.curTime + REACTON_TIME;
256+
}
218257

219258
(Status::Success, 0.)
220259
}
@@ -231,3 +270,66 @@ pub fn look_at(origin: Vector3, target: Vector3) -> Vector3 {
231270

232271
Vector3::new(-anglex, angley, 0.)
233272
}
273+
274+
fn generate_spread(spread_buf: &mut Vec<Vector3>, spread_rigth: bool, seed: u64) {
275+
const VALUES: usize = 30;
276+
const VALUES_BOTTOM: i32 = VALUES as i32 / -3;
277+
const VALUES_TOP: i32 = VALUES as i32 * 2 / 3;
278+
const VALUES_STEP: f32 = 0.2;
279+
const PERMUTATIONS: u64 = 15;
280+
const Y_FLUTUATION_PERMUTATIONS: u64 = 10;
281+
const Y_FLUTUATION_PERMUTATIONS_MIN: u64 = 1;
282+
const Y_FLUTUATION_FRAC: f32 = 0.01;
283+
284+
spread_buf.clear();
285+
286+
let rigth_way = (VALUES_BOTTOM..VALUES_TOP).filter(|_| spread_rigth);
287+
let left_way = (-VALUES_TOP..-VALUES_BOTTOM)
288+
.filter(|_| !spread_rigth)
289+
.rev();
290+
291+
let mut hasher = FxHasher::default();
292+
seed.hash(&mut hasher);
293+
let angle = (hasher.finish() % PERMUTATIONS) as f32 / PERMUTATIONS as f32;
294+
spread_buf.extend(
295+
rigth_way
296+
.chain(left_way)
297+
.map(|i| {
298+
let mut hasher = FxHasher::default();
299+
seed.hash(&mut hasher);
300+
Y_FLUTUATION_PERMUTATIONS.hash(&mut hasher);
301+
(
302+
i as f32,
303+
((hasher.finish() % Y_FLUTUATION_PERMUTATIONS)
304+
.min(Y_FLUTUATION_PERMUTATIONS_MIN) as f32
305+
/ Y_FLUTUATION_PERMUTATIONS as f32
306+
- Y_FLUTUATION_PERMUTATIONS as f32 / 2.)
307+
* Y_FLUTUATION_FRAC,
308+
)
309+
})
310+
.map(|(x, fluctuation)| {
311+
Vector3::new(angle * x * VALUES_STEP - fluctuation, x * VALUES_STEP, 0.)
312+
}),
313+
);
314+
}
315+
316+
const AIM_DELTA: f32 = PI / 20.;
317+
pub fn natural_aim(current: Vector3, target: Vector3) -> Vector3 {
318+
Vector3::new(
319+
angle_move_toward(current.x, target.x, AIM_DELTA),
320+
angle_move_toward(current.y, target.y, AIM_DELTA),
321+
angle_move_toward(current.z, target.z, AIM_DELTA),
322+
)
323+
}
324+
325+
fn angle_move_toward(from: f32, to: f32, delta: f32) -> f32 {
326+
let (from, to) = (from.to_radians(), to.to_radians());
327+
let diff = angle_diff(from, to);
328+
// When `delta < 0` move no further than to PI radians away from `to` (as PI is the max possible angle distance).
329+
(from + delta.clamp(diff.abs() - PI, diff.abs()) * if diff >= 0.0 { 1. } else { -1. })
330+
.to_degrees()
331+
}
332+
fn angle_diff(from: f32, to: f32) -> f32 {
333+
let diff = (to - from).rem_euclid(TAU);
334+
(2.0 * diff).rem_euclid(TAU) - diff
335+
}

0 commit comments

Comments
 (0)