@@ -7,12 +7,17 @@ use rrplug::{
77 } ,
88 prelude:: * ,
99} ;
10+ use rustc_hash:: FxHasher ;
1011use 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
1722use crate :: behavior:: BotAction ;
1823use crate :: behavior:: BotBrain ;
@@ -21,6 +26,9 @@ use crate::behavior::BotBrain;
2126pub 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