de_controller 0.0.1

Handling of user input in Digital Extinction.
Documentation
use bevy::{
    input::{keyboard::KeyboardInput, mouse::MouseButtonInput, ButtonState},
    prelude::*,
};
use de_attacking::AttackEvent;
use de_behaviour::ChaseTarget;
use de_core::{
    gconfig::GameConfig,
    objects::{BuildingType, MovableSolid, Playable},
    player::Player,
    projection::ToFlat,
    stages::GameStage,
};
use de_pathing::{PathQueryProps, PathTarget, UpdateEntityPath};
use de_spawner::Draft;
use enum_map::enum_map;
use iyes_loopless::prelude::*;

use crate::{
    draft::{DiscardDraftsEvent, NewDraftEvent, SpawnDraftsEvent},
    pointer::Pointer,
    selection::{SelectEvent, Selected, SelectionMode},
    Labels,
};

pub(crate) struct CommandPlugin;

impl Plugin for CommandPlugin {
    fn build(&self, app: &mut App) {
        app.add_system_set_to_stage(
            GameStage::Input,
            SystemSet::new()
                .with_system(
                    right_click_handler
                        .run_if(on_pressed(MouseButton::Right))
                        .label(Labels::InputUpdate)
                        .after(Labels::PreInputUpdate),
                )
                .with_system(
                    left_click_handler
                        .run_if(on_pressed(MouseButton::Left))
                        .label(Labels::InputUpdate)
                        .after(Labels::PreInputUpdate),
                )
                .with_system(
                    key_press_handler
                        .label(Labels::InputUpdate)
                        .after(Labels::PreInputUpdate),
                ),
        );
    }
}

fn on_pressed(button: MouseButton) -> impl Fn(EventReader<MouseButtonInput>) -> bool {
    move |mut events: EventReader<MouseButtonInput>| {
        // It is desirable to exhaust the iterator, thus .filter().count() is
        // used instead of .any()
        events
            .iter()
            .filter(|e| e.button == button && e.state == ButtonState::Pressed)
            .count()
            > 0
    }
}

type SelectedQuery<'w, 's> =
    Query<'w, 's, (Entity, Option<&'static ChaseTarget>), (With<Selected>, With<MovableSolid>)>;

fn right_click_handler(
    mut commands: Commands,
    config: Res<GameConfig>,
    mut path_events: EventWriter<UpdateEntityPath>,
    mut attack_events: EventWriter<AttackEvent>,
    selected: SelectedQuery,
    targets: Query<&Player>,
    pointer: Res<Pointer>,
) {
    match pointer.entity().filter(|&entity| {
        targets
            .get(entity)
            .map(|&player| !config.is_local_player(player))
            .unwrap_or(false)
    }) {
        Some(enemy) => {
            for (attacker, _) in selected.iter() {
                attack_events.send(AttackEvent::new(attacker, enemy));
            }
        }
        None => {
            let target = match pointer.terrain_point() {
                Some(point) => point.to_flat(),
                None => return,
            };

            for (entity, chase) in selected.iter() {
                if chase.is_some() {
                    commands.entity(entity).remove::<ChaseTarget>();
                }

                path_events.send(UpdateEntityPath::new(
                    entity,
                    PathTarget::new(target, PathQueryProps::exact(), false),
                ));
            }
        }
    }
}

fn left_click_handler(
    mut select_events: EventWriter<SelectEvent>,
    mut draft_events: EventWriter<SpawnDraftsEvent>,
    keys: Res<Input<KeyCode>>,
    pointer: Res<Pointer>,
    playable: Query<(), With<Playable>>,
    drafts: Query<(), With<Draft>>,
) {
    if drafts.is_empty() {
        let selection_mode = if keys.pressed(KeyCode::LControl) {
            SelectionMode::Add
        } else {
            SelectionMode::Replace
        };

        let event = match pointer.entity().filter(|&e| playable.contains(e)) {
            Some(entity) => SelectEvent::single(entity, selection_mode),
            None => SelectEvent::none(selection_mode),
        };
        select_events.send(event);
    } else {
        draft_events.send(SpawnDraftsEvent);
    }
}

fn key_press_handler(
    mut key_events: EventReader<KeyboardInput>,
    pointer: Res<Pointer>,
    mut new_draft_events: EventWriter<NewDraftEvent>,
    mut discard_drafts_events: EventWriter<DiscardDraftsEvent>,
) {
    let key = match key_events
        .iter()
        .filter(|e| e.state == ButtonState::Pressed)
        .last()
    {
        Some(event) => match event.key_code {
            Some(key) => key,
            None => return,
        },
        None => return,
    };

    if key == KeyCode::Escape {
        discard_drafts_events.send(DiscardDraftsEvent);
        return;
    }

    let point = match pointer.terrain_point() {
        Some(point) => point,
        None => return,
    };

    let key_map = enum_map! {
        BuildingType::Base => KeyCode::B,
        BuildingType::PowerHub => KeyCode::P,
    };

    if let Some(building_type) = key_map.iter().find_map(|(building_type, &associated_key)| {
        if associated_key == key {
            Some(building_type)
        } else {
            None
        }
    }) {
        new_draft_events.send(NewDraftEvent::new(point, building_type));
    }
}