all_is_cubes_ui/apps/
input.rs

1use alloc::vec::Vec;
2use core::time::Duration;
3use std::collections::{HashMap, HashSet};
4
5use all_is_cubes::character::{self, Character};
6use all_is_cubes::euclid::{Point2D, Vector2D};
7use all_is_cubes::inv;
8use all_is_cubes::listen;
9use all_is_cubes::math::{FreeCoordinate, FreeVector, zo32};
10use all_is_cubes::physics;
11use all_is_cubes::time::Tick;
12use all_is_cubes::universe::{self, Handle, Universe};
13use all_is_cubes_render::camera::{
14    FogOption, LightingOption, NdcPoint2, NominalPixel, RenderMethod, TransparencyOption, Viewport,
15};
16
17use crate::apps::{ControlMessage, Settings};
18
19type MousePoint = Point2D<f64, NominalPixel>;
20
21/// Parse input events, particularly key-down/up pairs, into character control and such.
22///
23/// This is designed to be a leaf of the dependency graph: it does not own or send
24/// messages to any other elements of the application. Instead, the following steps
25/// must occur in the given order.
26///
27/// 1. The platform-specific code should call [`InputProcessor::key_down`] and such to
28///    to provide input information.
29/// 2. The game loop should call `InputProcessor::apply_input` to apply the effects
30///    of input on the relevant [`Character`]. (This is currently only possible via
31///    [`Session`].)
32/// 3. The game loop should call `InputProcessor::step` to apply the effects of time
33///    on the input processor. (This is currently only possible via [`Session`].)
34///
35/// TODO: Refactor APIs till this can be explained more cleanly without reference to
36/// private items.
37///
38/// [`Session`]: super::Session
39#[derive(Debug)]
40pub struct InputProcessor {
41    /// All [`Key`]s currently pressed.
42    keys_held: HashSet<Key>,
43    /// As a special feature for supporting input without key-up events, stores all
44    /// keypresses arriving through [`Self::key_momentary`] and virtually holds them
45    /// for a short time. The value is the remaining time.
46    momentary_timeout: HashMap<Key, Duration>,
47    /// [`Key`]s with one-shot effects when pressed which need to be applied
48    /// once per press rather than while held.
49    command_buffer: Vec<Key>,
50
51    /// Do we *want* pointer lock for mouselook?
52    ///
53    /// This is listenable so that the UI can react to this state.
54    mouselook_mode: listen::Cell<bool>,
55    /// Do we *have* pointer lock for mouselook? Reported by calling input implementation.
56    has_pointer_lock: bool,
57
58    /// Net mouse movement since the last [`Self::apply_input`].
59    mouselook_buffer: Vector2D<FreeCoordinate, NominalPixel>,
60
61    /// Mouse position in NDC. None if out of bounds/lost focus.
62    mouse_ndc_position: Option<NdcPoint2>,
63
64    /// Mouse position used for generating mouselook deltas.
65    /// [`None`] if games.
66    mouse_previous_pixel_position: Option<MousePoint>,
67}
68
69impl InputProcessor {
70    /// Constructs a new [`InputProcessor`].
71    ///
72    /// Consider using [`Session`](crate::apps::Session) instead of directly calling this.
73    #[expect(
74        clippy::new_without_default,
75        reason = "I expect it'll grow some parameters"
76    )]
77    pub fn new() -> Self {
78        Self {
79            keys_held: HashSet::new(),
80            momentary_timeout: HashMap::new(),
81            command_buffer: Vec::new(),
82            mouselook_mode: listen::Cell::new(false), // TODO: might want a parameter
83            has_pointer_lock: false,
84            mouselook_buffer: Vector2D::zero(),
85            mouse_ndc_position: Some(NdcPoint2::origin()),
86            mouse_previous_pixel_position: None,
87        }
88    }
89
90    fn is_bound(key: Key) -> bool {
91        // Eventually we'll have actual configurable keybindings...
92        match key {
93            // Used in `InputProcessor::movement()`.
94            Key::Character('w') => true,
95            Key::Character('a') => true,
96            Key::Character('s') => true,
97            Key::Character('d') => true,
98            Key::Character('e') => true,
99            Key::Character('c') => true,
100            // Used in `InputProcessor::apply_input()`.
101            Key::Escape => true,
102            Key::Left => true,
103            Key::Right => true,
104            Key::Up => true,
105            Key::Down => true,
106            Key::Character(' ') => true,
107            Key::Character(d) if d.is_ascii_digit() => true,
108            Key::Character('i') => true,
109            Key::Character('l') => true,
110            Key::Character('o') => true,
111            Key::Character('p') => true,
112            Key::Character('u') => true,
113            Key::Character('y') => true,
114            Key::Character('`' | '~') => true,
115            _ => false,
116        }
117    }
118
119    /// Returns true if the key should go in `command_buffer`.
120    fn is_command(key: Key) -> bool {
121        match key {
122            Key::Escape => true,
123            Key::Character(d) if d.is_ascii_digit() => true,
124            Key::Character('i') => true,
125            Key::Character('l') => true,
126            Key::Character('o') => true,
127            Key::Character('p') => true,
128            Key::Character('u') => true,
129            Key::Character('y') => true,
130            Key::Character('`' | '~') => true,
131            // TODO: move slot selection commands here
132            _ => false,
133        }
134    }
135
136    /// Handles incoming key-down events. Returns whether the key was unbound.
137    pub fn key_down(&mut self, key: Key) -> bool {
138        let bound = Self::is_bound(key);
139        if bound {
140            self.keys_held.insert(key);
141            if Self::is_command(key) {
142                self.command_buffer.push(key);
143            }
144        }
145        bound
146    }
147
148    /// Handles incoming key-up events.
149    pub fn key_up(&mut self, key: Key) {
150        self.keys_held.remove(&key);
151    }
152
153    /// Handles incoming key events in the case where key-up events are not available,
154    /// such that an assumption about equivalent press duration must be made.
155    pub fn key_momentary(&mut self, key: Key) -> bool {
156        self.momentary_timeout.insert(key, Duration::from_millis(200));
157        self.key_up(key);
158        self.key_down(key)
159    }
160
161    /// Handles the keyboard focus being gained or lost. If the platform does not have
162    /// a concept of focus, you need not call this method, but may call it with `true`.
163    ///
164    /// `InputProcessor` will assume that if focus is lost, key-up events may be lost and
165    /// so currently held keys should stop taking effect.
166    pub fn key_focus(&mut self, has_focus: bool) {
167        if has_focus {
168            // Nothing to do.
169        } else {
170            self.keys_held.clear();
171            self.momentary_timeout.clear();
172
173            self.mouselook_mode.set(false);
174        }
175    }
176
177    /// True when the UI is in a state which _should_ have mouse pointer
178    /// lock/capture/disable. This is not the same as actually having it since the window
179    /// may lack focus, the application may lack permission, etc.; use
180    /// [`InputProcessor::has_pointer_lock`] to report that state.
181    pub fn wants_pointer_lock(&self) -> bool {
182        self.mouselook_mode.get()
183    }
184
185    /// Use this method to report whether mouse mouse pointer lock/capture/disable is
186    /// known to be successfully enabled, after [`InputProcessor::wants_pointer_lock`]
187    /// requests it or it is disabled for any reason.
188    pub fn has_pointer_lock(&mut self, value: bool) {
189        self.has_pointer_lock = value;
190    }
191
192    /// Provide relative movement information for mouselook.
193    ///
194    /// This value is an accumulated displacement, not an angular velocity, so it is not
195    /// suitable for joystick-type input.
196    ///
197    /// Note that absolute cursor positions must be provided separately.
198    pub fn mouselook_delta(&mut self, delta: Vector2D<FreeCoordinate, NominalPixel>) {
199        // TODO: sensitivity option
200        if self.has_pointer_lock {
201            self.mouselook_buffer += delta * 0.2;
202        }
203    }
204
205    /// Provide position of mouse pointer or other input device in normalized device
206    /// coordinates (range -1 to 1 upward and rightward).
207    /// [`None`] denotes the cursor being outside the viewport, and out-of-range
208    /// coordinates will be treated the same.
209    ///
210    /// Pixel coordinates may be converted to NDC using [`Viewport::normalize_nominal_point`]
211    /// or by using [`InputProcessor::mouse_pixel_position`].
212    ///
213    /// If this is never called, the default value is (0, 0) which corresponds to the
214    /// center of the screen.
215    pub fn mouse_ndc_position(&mut self, position: Option<NdcPoint2>) {
216        self.mouse_ndc_position = position.filter(|p| p.x.abs() <= 1. && p.y.abs() <= 1.);
217    }
218
219    /// Provide position of mouse pointer or other input device in pixel coordinates
220    /// framed by the given [`Viewport`]'s `nominal_size`.
221    /// [`None`] denotes the cursor being outside the viewport, and out-of-range
222    /// coordinates will be treated the same.
223    ///
224    /// This is equivalent to converting the coordinates and calling
225    /// [`InputProcessor::mouse_ndc_position`].
226    ///
227    /// If this is never called, the default `mouse_ndc_position` value is (0, 0), which
228    /// corresponds to the center of the screen.
229    ///
230    /// TODO: this should take float input, probably
231    pub fn mouse_pixel_position(
232        &mut self,
233        viewport: Viewport,
234        position: Option<Point2D<f64, NominalPixel>>,
235        derive_movement: bool,
236    ) {
237        self.mouse_ndc_position(position.map(|p| viewport.normalize_nominal_point(p)));
238
239        if derive_movement {
240            if let (Some(p1), Some(p2)) = (self.mouse_previous_pixel_position, position) {
241                self.mouselook_delta((p2 - p1).map(FreeCoordinate::from));
242            }
243            self.mouse_previous_pixel_position = position;
244        } else {
245            self.mouse_previous_pixel_position = None;
246        }
247    }
248
249    /// Returns the character movement direction that input is currently requesting.
250    ///
251    /// This is always a vector of length at most 1.
252    pub fn movement(&self) -> FreeVector {
253        let mut vector = FreeVector::new(
254            self.net_movement(Key::Character('a'), Key::Character('d')),
255            self.net_movement(Key::Character('c'), Key::Character('e')),
256            self.net_movement(Key::Character('w'), Key::Character('s')),
257        );
258        if vector != FreeVector::zero() {
259            vector = vector.normalize();
260        }
261        vector
262    }
263
264    /// Advance time insofar as input interpretation is affected by time.
265    ///
266    /// This method should be called *after* [`apply_input`](Self::apply_input), when
267    /// applicable.
268    pub(crate) fn step(&mut self, tick: Tick) {
269        let mut to_drop = Vec::new();
270        for (key, duration) in self.momentary_timeout.iter_mut() {
271            if let Some(reduced) = duration.checked_sub(tick.delta_t()) {
272                *duration = reduced;
273            } else {
274                to_drop.push(*key);
275            }
276        }
277        for key in to_drop.drain(..) {
278            self.momentary_timeout.remove(&key);
279            self.key_up(key);
280        }
281
282        self.mouselook_buffer = Vector2D::zero();
283    }
284
285    /// Applies the accumulated input from previous events.
286    /// `targets` specifies the objects it should be applied to.
287    ///
288    /// Returns an error if `targets` contains inaccessible handles.
289    pub(crate) fn apply_input(
290        &mut self,
291        targets: InputTargets<'_>,
292        tick: Tick,
293    ) -> Result<(), universe::HandleError> {
294        let InputTargets {
295            mut universe,
296            character: character_opt,
297            paused: paused_opt,
298            settings,
299            control_channel,
300            ui,
301        } = targets;
302
303        // TODO: universe input is not yet used but it will be, as we start having inputs that trigger transactions
304        let _ = universe;
305
306        let dt = tick.delta_t().as_secs_f64();
307        let key_turning_step = 80.0 * dt;
308
309        // Effects of UI on input processing.
310        // TODO: this should be done *after* a session step in addition to before, for lower
311        // latency / immediate effects -- but currently this is the only place we have
312        // `InputTargets` available.
313        if ui.is_some_and(|ui| ui.should_focus_on_ui()) && self.mouselook_mode.get() {
314            self.mouselook_mode.set(false)
315        }
316
317        // Direct character controls
318        if let (Some(universe), Some(character_handle)) = (&mut universe, character_opt) {
319            let movement = self.movement();
320
321            let turning = Vector2D::<_, ()>::new(
322                key_turning_step.mul_add(
323                    self.net_movement(Key::Left, Key::Right),
324                    self.mouselook_buffer.x,
325                ),
326                key_turning_step.mul_add(
327                    self.net_movement(Key::Up, Key::Down),
328                    self.mouselook_buffer.y,
329                ),
330            );
331
332            universe.mutate_component(character_handle, |body: &mut physics::Body| {
333                body.yaw = (body.yaw + turning.x).rem_euclid(360.0);
334                body.pitch = (body.pitch + turning.y).clamp(-90.0, 90.0);
335            })?;
336
337            universe.mutate_component(character_handle, |input: &mut character::Input| {
338                input.velocity_input = movement;
339                if self.keys_held.contains(&Key::Character(' ')) {
340                    input.jump = true;
341                }
342            })?;
343        }
344
345        for key in self.command_buffer.drain(..) {
346            match key {
347                Key::Escape => {
348                    if let Some(ch) = control_channel {
349                        let _ = ch.try_send(ControlMessage::Back);
350                    }
351                }
352                Key::Character('i') => {
353                    if let Some(settings) = settings {
354                        settings.mutate_graphics_options(|options| {
355                            options.lighting_display = match options.lighting_display {
356                                LightingOption::None => LightingOption::Flat,
357                                LightingOption::Flat => LightingOption::Smooth,
358                                LightingOption::Smooth => {
359                                    // TODO: the question we actually want to ask is,
360                                    // what does the *current renderer* support?
361                                    if options.render_method == RenderMethod::Reference {
362                                        LightingOption::Bounce
363                                    } else {
364                                        LightingOption::None
365                                    }
366                                }
367                                LightingOption::Bounce => LightingOption::None,
368                                _ => LightingOption::None, // TODO: either stop doing cycle-commands or put it on the enum so it can be exhaustive
369                            };
370                        });
371                    }
372                }
373                Key::Character('l') => {
374                    // TODO: duplicated with fn toggle_mouselook_mode() because of borrow conflicts
375                    let new_state = !self.mouselook_mode.get();
376                    self.mouselook_mode.set(new_state);
377                    if new_state {
378                        // Clear delta tracking just in case
379                        self.mouse_previous_pixel_position = None;
380                    }
381                }
382                Key::Character('o') => {
383                    if let Some(settings) = settings {
384                        settings.mutate_graphics_options(|options| {
385                            options.transparency = match options.transparency {
386                                TransparencyOption::Surface => TransparencyOption::Volumetric,
387                                TransparencyOption::Volumetric => {
388                                    TransparencyOption::Threshold(zo32(0.5))
389                                }
390                                TransparencyOption::Threshold(_) => TransparencyOption::Surface,
391                                _ => TransparencyOption::Surface, // TODO: either stop doing cycle-commands or put it on the enum so it can be exhaustive
392                            };
393                        });
394                    }
395                }
396                Key::Character('p') => {
397                    // TODO: eliminate this weird binding once escape-based pausing is working well
398                    if let Some(paused) = paused_opt {
399                        paused.update_mut(|p| *p = !*p);
400                    }
401                }
402                Key::Character('u') => {
403                    if let Some(settings) = settings {
404                        settings.mutate_graphics_options(|options| {
405                            options.fog = match options.fog {
406                                FogOption::None => FogOption::Abrupt,
407                                FogOption::Abrupt => FogOption::Compromise,
408                                FogOption::Compromise => FogOption::Physical,
409                                FogOption::Physical => FogOption::None,
410                                _ => FogOption::None, // TODO: either stop doing cycle-commands or put it on the enum so it can be exhaustive
411                            };
412                        });
413                    }
414                }
415                Key::Character('y') => {
416                    if let Some(settings) = settings {
417                        settings.mutate_graphics_options(|options| {
418                            options.render_method = match options.render_method {
419                                RenderMethod::Mesh => RenderMethod::Reference,
420                                RenderMethod::Reference => RenderMethod::Mesh,
421                                _ => RenderMethod::Reference,
422                            };
423                        });
424                    }
425                }
426                Key::Character('`' | '~') => {
427                    if let Some(ch) = control_channel {
428                        let _ = ch.try_send(ControlMessage::EnterDebug);
429                    }
430                }
431                Key::Character(numeral) if numeral.is_ascii_digit() => {
432                    let digit = numeral.to_digit(10).unwrap() as inv::Ix;
433                    let slot = (digit + 9).rem_euclid(10); // wrap 0 to 9
434                    if let (Some(universe), Some(character_handle)) = (&mut universe, character_opt)
435                    {
436                        universe.mutate_component(
437                            character_handle,
438                            |c: &mut character::Input| {
439                                c.set_selected_slots[1] = Some(slot);
440                            },
441                        )?;
442                    }
443                }
444                _ => {}
445            }
446        }
447
448        Ok(())
449    }
450
451    /// Returns a source which reports whether the mouselook mode (mouse movement is
452    /// interpreted as view rotation) is currently active.
453    ///
454    /// This value may be toggled by in-game UI.
455    pub fn mouselook_mode(&self) -> listen::DynSource<bool> {
456        self.mouselook_mode.as_source()
457    }
458
459    pub(crate) fn toggle_mouselook_mode(&mut self) {
460        self.set_mouselook_mode(!self.mouselook_mode.get());
461    }
462
463    /// Activates or deactivates mouselook mode, identically to user input doing so.
464    pub fn set_mouselook_mode(&mut self, active: bool) {
465        // Note: duplicated with the keybinding impl because of borrow conflicts
466        let was_active = self.mouselook_mode.get();
467        self.mouselook_mode.set(active);
468        if active && !was_active {
469            // Clear delta tracking just in case
470            self.mouse_previous_pixel_position = None;
471        }
472    }
473
474    /// Returns the position which should be used for click/cursor raycasting.
475    /// This is not necessarily equal to the tracked mouse position.
476    ///
477    /// Returns [`None`] if the mouse position is out of bounds, the window has lost
478    /// focus, or similar conditions under which no cursor should be shown.
479    pub fn cursor_ndc_position(&self) -> Option<NdcPoint2> {
480        if self.mouselook_mode.get() {
481            Some(NdcPoint2::origin())
482        } else {
483            self.mouse_ndc_position
484        }
485    }
486
487    /// Computes the net effect of a pair of opposed inputs (e.g. "forward" and "back").
488    fn net_movement(&self, negative: Key, positive: Key) -> FreeCoordinate {
489        match (
490            self.keys_held.contains(&negative),
491            self.keys_held.contains(&positive),
492        ) {
493            (true, false) => -1.0,
494            (false, true) => 1.0,
495            _ => 0.0,
496        }
497    }
498}
499
500/// Things needed to apply input.
501///
502/// Missing inputs will cause input to be ignored.
503/// TODO: Specify a warning reporting scheme.
504#[derive(Debug, Default)]
505#[non_exhaustive]
506pub(crate) struct InputTargets<'a> {
507    pub universe: Option<&'a mut Universe>,
508    pub character: Option<&'a Handle<Character>>,
509    pub paused: Option<&'a listen::Cell<bool>>,
510    pub settings: Option<&'a Settings>,
511    // TODO: replace cells with control channel?
512    // TODO: make the control channel a type alias?
513    pub control_channel: Option<&'a flume::Sender<ControlMessage>>,
514    pub ui: Option<&'a crate::ui_content::Vui>,
515}
516
517/// A platform-neutral representation of keyboard keys for [`InputProcessor`].
518#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
519#[non_exhaustive]
520pub enum Key {
521    /// Letters should be lowercase.
522    Character(char),
523    /// Escape key (or controller ‘start’ or mobile ‘back’).
524    Escape,
525    /// Left arrow key.
526    Left,
527    /// Right arrow key.
528    Right,
529    /// Up arrow key.
530    Up,
531    /// Down arrow key.
532    Down,
533}
534
535#[cfg(test)]
536mod tests {
537    use super::*;
538    use all_is_cubes::euclid::vec3;
539    use all_is_cubes::space::Space;
540    use all_is_cubes::time;
541    use all_is_cubes::universe::{Handle, ReadTicket};
542
543    fn apply_input_helper(
544        input: &mut InputProcessor,
545        universe: &mut Universe,
546        character: &Handle<Character>,
547    ) {
548        input
549            .apply_input(
550                InputTargets {
551                    universe: Some(universe),
552                    character: Some(character),
553                    paused: None,
554                    settings: None,
555                    control_channel: None,
556                    ui: None,
557                },
558                Tick::arbitrary(),
559            )
560            .unwrap();
561    }
562
563    #[test]
564    fn movement() {
565        let mut input = InputProcessor::new();
566        assert_eq!(input.movement(), vec3(0.0, 0.0, 0.0));
567        input.key_down(Key::Character('d'));
568        assert_eq!(input.movement(), vec3(1.0, 0.0, 0.0));
569        input.key_down(Key::Character('a'));
570        assert_eq!(input.movement(), vec3(0.0, 0.0, 0.0));
571        input.key_up(Key::Character('d'));
572        assert_eq!(input.movement(), vec3(-1.0, 0.0, 0.0));
573    }
574
575    #[test]
576    fn focus_lost_cancels_keys() {
577        let mut input = InputProcessor::new();
578        assert_eq!(input.movement(), FreeVector::zero());
579        input.key_down(Key::Character('d'));
580        assert_eq!(input.movement(), vec3(1., 0., 0.));
581        input.key_focus(false);
582        assert_eq!(input.movement(), FreeVector::zero()); // Lost focus, no movement.
583
584        // Confirm that keys work again afterward.
585        input.key_focus(true);
586        assert_eq!(input.movement(), FreeVector::zero());
587        input.key_down(Key::Character('d'));
588        assert_eq!(input.movement(), vec3(1., 0., 0.));
589        // TODO: test (and handle) key events arriving while focus is lost, just in case.
590    }
591
592    /// Test when the pause menu (or any menu, really) is displayed,
593    /// mouselook is ended so the mouse  can interact with the menus.
594    #[macro_rules_attribute::apply(smol_macros::test)]
595    async fn pause_menu_cancels_mouselook() {
596        let paused = listen::Cell::new(false);
597        // TODO: This test is both verbose and expensive.
598        // We need simpler way to create a cheap Vui for a test, or some abstraction here.
599        let (cctx, _) = flume::bounded(1);
600        let mut ui = crate::ui_content::Vui::new(crate::ui_content::UiTargets {
601            mouselook_mode: listen::constant(false),
602            character_source: listen::constant(None),
603            paused: paused.as_source(),
604            graphics_options: listen::constant(Default::default()),
605            app_control_channel: cctx,
606            viewport_source: listen::constant(Viewport::ARBITRARY),
607            fullscreen_mode: listen::constant(None),
608            set_fullscreen: None,
609            quit: None,
610            custom_commands: listen::constant(Default::default()),
611        })
612        .await;
613
614        // Create input processor and set it to have mouselook mode
615        let mut input = InputProcessor::new();
616        input.toggle_mouselook_mode();
617        assert!(input.mouselook_mode().get());
618
619        // No effect when unpaused...
620        input
621            .apply_input(
622                InputTargets {
623                    ui: Some(&ui),
624                    ..InputTargets::default()
625                },
626                Tick::arbitrary(),
627            )
628            .unwrap();
629        assert!(input.mouselook_mode().get());
630
631        // But when paused, the UI enters the pause menu and, as a consequence, cancels mouselook.
632        paused.set(true);
633        ui.step(Tick::arbitrary(), time::Deadline::Asap, ReadTicket::stub());
634        input
635            .apply_input(
636                InputTargets {
637                    ui: Some(&ui),
638                    ..InputTargets::default()
639                },
640                Tick::arbitrary(),
641            )
642            .unwrap();
643        assert!(!input.mouselook_mode().get());
644    }
645
646    #[test]
647    fn slot_selection() {
648        let u = &mut Universe::new();
649        let space = u.insert_anonymous(Space::empty_positive(1, 1, 1));
650        let character =
651            u.insert("c".into(), Character::spawn_default(u.read_ticket(), space).unwrap()).unwrap();
652        let mut input = InputProcessor::new();
653
654        input.key_down(Key::Character('5'));
655        input.key_up(Key::Character('5'));
656        apply_input_helper(&mut input, u, &character);
657        u.step(false, time::Deadline::Whenever);
658        assert_eq!(
659            character.read(u.read_ticket()).unwrap().selected_slots()[1],
660            4
661        );
662
663        // Tenth slot
664        input.key_down(Key::Character('0'));
665        input.key_up(Key::Character('0'));
666        apply_input_helper(&mut input, u, &character);
667        u.step(false, time::Deadline::Whenever);
668        assert_eq!(
669            character.read(u.read_ticket()).unwrap().selected_slots()[1],
670            9
671        );
672    }
673
674    // TODO: test jump and flying logic
675}