<commit_before>use std::collections::{HashMap, HashSet};

use super::FactionId;
use engine::components::{AttackSensor, CollisionHandle, Color, DataStoreComponent, Hp, Movable,
                         Pos, Shape, Speed, Static, UnitTypeTag};

use scaii_defs::protos::{Action, State, Viz};

use specs::prelude::*;
use specs::world::LazyBuilder;

pub mod collision;

pub use self::collision::*;
pub use engine::systems::lua::userdata::DataStore;

// Recommended by ncollide
pub const COLLISION_MARGIN: f64 = 0.02;
// ncollide wants the average size of a collider to be "around" 1
// we should probably set this as a resource from Lua in the future
pub const COLLISION_SCALE: f64 = 5.0;

pub const UNIVERSAL_SENSOR: usize = SENSOR_OFFSET+MAX_FACTIONS;
pub const SENSOR_OFFSET: usize = MAX_FACTIONS;
pub const SENSOR_BLACKLIST: [usize; MAX_FACTIONS+1] = [SENSOR_OFFSET, SENSOR_OFFSET+1, SENSOR_OFFSET+2, SENSOR_OFFSET+3, UNIVERSAL_SENSOR];

pub const MAX_FACTIONS: usize = 4;

pub const STATE_SIZE: usize = 40;
pub const STATE_SCALE: usize = 1;

lazy_static! {
    pub static ref PLAYER_COLORS: Vec<Color> = vec![
        Color {
            r: 255,
            g: 181,
            b: 0,
        },
        Color { r: 255, g: 0, b: 0 },
        Color { r: 0, g: 0, b: 255 },
    ];
}

// 60FPS emulation since we're not
// actually measuring time elapsed
const SIXTY_FPS: f64 = 1.0 / 60.0;

pub(super) fn register_world_resources(world: &mut World) {
    use ndarray::Array3;
    use specs::saveload::U64MarkerAllocator;
    use util;

    let rng = util::make_rng();
    world.add_resource(rng);
    world.add_resource(Episode(0));
    world.add_resource(Terminal(false));
    world.add_resource(Step(0));
    world.add_resource(MaxStep(None));
    world.add_resource(DeltaT(SIXTY_FPS));
    world.add_resource(Render::default());
    world.add_resource(NeedsKeyInfo(true));
    world.add_resource::<Vec<Player>>(Vec::new());
    world.add_resource(UnitTypeMap::default());
    world.add_resource(U64MarkerAllocator::new());
    world.add_resource(ActionInput::default());
    world.add_resource(SkyCollisionWorld::new(COLLISION_MARGIN));
    world.add_resource(RtsState(State {
        features: Array3::zeros([STATE_SIZE, STATE_SIZE, 4]).into_raw_vec(),
        feature_array_dims: vec![STATE_SIZE as u32, STATE_SIZE as u32, 4],
        ..Default::default()
    }));
    world.add_resource(Reward::default());
    world.add_resource(Skip(false, None));
    world.add_resource(SerializeBytes::default());
    world.add_resource(LuaPath(None));
    world.add_resource(ReplayMode(false));
    world.add_resource(RewardTypes::default());
    world.add_resource(SpawnBuffer::default());
    world.add_resource(Deserializing(false));
    world.add_resource(CumReward::default());
    world.add_resource(DataStore::default());
}

#[derive(Default, Debug, Clone, PartialEq)]
pub struct RtsState(pub State);

/// The current episode, only meaningful for sequential runs.
#[derive(Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash, Serialize, Deserialize)]
pub struct Episode(pub usize);

#[derive(Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash, Serialize, Deserialize)]
pub struct Step(pub usize);

#[derive(Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash, Serialize, Deserialize)]
pub struct MaxStep(pub Option<usize>);

/// Is this the final frame of the scenario?
#[derive(Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash, Debug, Serialize, Deserialize)]
pub struct Terminal(pub bool);

/// Time since the last update, in seconds (fixed to one sixtieth of a second for our purposes).
#[derive(Copy, Clone, PartialEq, PartialOrd, Serialize, Deserialize)]
pub struct DeltaT(pub f64);

/// Any associated data with various game factions.
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Player {
    pub color: super::components::Color,
    pub id: FactionId,
}

/// The output of the renderer, for use with Viz.
#[derive(Clone, PartialEq, Default)]
pub struct Render(pub Viz);

/// Tracks whether a FULL rerender (or total state, or whatever else)
/// is needed rather than a delta.
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Default, Debug)]
pub struct NeedsKeyInfo(pub bool);

/// The actions coming from the Agent (or replay mechanism)
#[derive(Clone, PartialEq, Default, Debug)]
pub struct ActionInput(pub Option<Action>);

#[derive(PartialEq, Default, Clone)]
pub struct Reward(pub HashMap<String, f64>);

#[derive(PartialEq, Default, Clone, Serialize, Deserialize, Debug)]
pub struct CumReward(pub HashMap<String, f64>);

#[derive(Eq, PartialEq, Default, Clone, Debug, Hash)]
pub struct Skip(pub bool, pub Option<String>);

#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
pub struct UnitType {
    pub tag: String,
    pub max_hp: f64,
    pub movable: bool,
    pub shape: Shape,
    pub kill_reward: f64,
    pub death_penalty: f64,
    pub damage_deal_reward: Option<f64>,
    pub damage_recv_penalty: Option<f64>,
    pub speed: f64,
    pub attack_range: f64,
    pub attack_damage: f64,
    pub attack_delay: f64,

    pub death_type: String,
    pub dmg_recv_type: String,
    pub dmg_deal_type: String,
    pub kill_type: String,
}

impl Default for UnitType {
    fn default() -> Self {
        UnitType {
            tag: "".to_string(),
            max_hp: 100.0,
            movable: true,
            shape: Shape::Triangle { base_len: 10.0 },
            kill_reward: 0.0,
            death_penalty: 0.0,
            damage_deal_reward: None,
            damage_recv_penalty: None,
            death_type: "death".to_string(),
            dmg_recv_type: "dmg_recvd".to_string(),
            dmg_deal_type: "dmg_dealt".to_string(),
            kill_type: "killed_enemy".to_string(),
            speed: 20.0,
            attack_range: 10.0,
            attack_delay: 1.0,
            attack_damage: 10.0,
        }
    }
}

impl UnitType {
    pub fn build_entity(
        &self,
        world: &mut World,
        pos: Pos,
        curr_hp: Option<f64>,
        faction: usize,
    ) -> Entity {
        let lazy_update = world.read_resource::<LazyUpdate>();
        let lazy_build = lazy_update.create_entity(&world.entities());

        let players = &**world.read_resource::<Vec<Player>>();

        self.build_entity_lazy(lazy_build, &*lazy_update, pos, curr_hp, faction, players)
    }

    /// Creates and places the unit in the game world given its initial
    /// position and faction.
    ///
    /// This also initializes anything it needs such as colliders in the collision system.
    pub fn build_entity_lazy(
        &self,
        entity: LazyBuilder,
        update: &LazyUpdate,
        pos: Pos,
        curr_hp: Option<f64>,
        faction: usize,
        players: &[Player],
    ) -> Entity {
        use specs::saveload::U64Marker;

        let color = players[faction].color;

        // Scoping for borrow shenanigans
        let entity = {
            let entity = entity
                .with(pos)
                .with(self.shape)
                .with(color)
                .with(FactionId(faction))
                .with(UnitTypeTag(self.tag.clone()))
                .with(DataStoreComponent::default())
                .with(Hp {
                    max_hp: self.max_hp,
                    curr_hp: curr_hp.unwrap_or(self.max_hp),
                })
                .marked::<U64Marker>();

            if self.movable {
                entity.with(Movable(0)).with(Speed(self.speed))
            } else {
                entity.with(Static(0))
            }
        }.build();

        let me = self.clone();

        update.execute(move |world| {
            let mut col_storage = world.write::<CollisionHandle>();
            let mut atk_storage = world.write::<AttackSensor>();

            let mut c_world = world.write_resource();
            me.register_collision(
                entity,
                pos,
                faction,
                &mut col_storage,
                &mut atk_storage,
                &mut *c_world,
            );
        });

        entity
    }

    /// Registers a unit with the collision system based on its unit type
    /// and places the collision system handles into the appropriate components
    pub fn register_collision(
        &self,
        entity: Entity,
        pos: Pos,
        faction: usize,
        col_storage: &mut WriteStorage<CollisionHandle>,
        atk_storage: &mut WriteStorage<AttackSensor>,
        c_world: &mut SkyCollisionWorld,
    ) -> Entity {
        use nalgebra;
        use nalgebra::{Isometry2, Vector2};
        use ncollide::shape::{Ball, Cuboid, Cylinder, ShapeHandle};
        use ncollide::world::{CollisionGroups, GeometricQueryType};

        let mut collider_group = CollisionGroups::new();
        collider_group.set_membership(&[faction]);

        let mut sensor_group = CollisionGroups::new();
        sensor_group.set_membership(&[MAX_FACTIONS + faction]);
        sensor_group.set_blacklist(&SENSOR_BLACKLIST);

        let atk_radius = self.attack_range / COLLISION_SCALE;

        let (collider, atk_radius) = match self.shape {
            Shape::Rect { width, height } => {
                let width = width / COLLISION_SCALE;
                let height = height / COLLISION_SCALE;

                // ncollide likes half widths and heights, so divide by 2
                let collider = Cuboid::new(Vector2::new(width / 2.0, height / 2.0));
                let collider = ShapeHandle::new(collider);

                let atk_radius = width.max(height) + (self.attack_range / COLLISION_SCALE);
                let atk_sensor = Ball::new(atk_radius);
                let atk_sensor = ShapeHandle::new(atk_sensor);

                (collider, atk_sensor)
            }
            Shape::Triangle { base_len } => {
                let base_len = base_len / COLLISION_SCALE;

                // equilateral triangle dimensions
                let half_height = base_len / (2.0 as f64).sqrt() / 2.0;
                let radius = base_len / 2.0;

                // A cylinder in 2D is an isoscelese triangle in ncollide
                let collider = Cylinder::new(half_height, radius);
                let collider = ShapeHandle::new(collider);

                let atk_radius = half_height + (self.attack_range / COLLISION_SCALE);
                let atk_sensor = Ball::new(atk_radius);
                let atk_sensor = ShapeHandle::new(atk_sensor);

                (collider, atk_sensor)
            }
        };

        // We need the entity ID for this, so do it after building the entity and then add the component.
        let (collider, atk_radius) = {
            let pos = Isometry2::new(
                Vector2::new(pos.x / COLLISION_SCALE, pos.y / COLLISION_SCALE),
                nalgebra::zero(),
            );

            let q_type = GeometricQueryType::Contacts(0.0, 0.0);
            let collider = c_world.add(
                pos,
                collider,
                collider_group,
                q_type,
                ColliderData {
                    e: entity,
                    detector: false,
                },
            );

            let atk_radius = c_world.add(
                pos,
                atk_radius,
                sensor_group,
                q_type,
                ColliderData {
                    e: entity,
                    detector: true,
                },
            );

            (collider, atk_radius)
        };

        col_storage.insert(entity, CollisionHandle(collider));
        atk_storage.insert(entity, AttackSensor(atk_radius));

        c_world.update();

        entity
    }
}

#[derive(Clone, Debug, Serialize, Deserialize, Default)]
pub struct UnitTypeMap {
    pub typ_ids: HashMap<String, usize>,
    pub tag_map: HashMap<String, UnitType>,
}

#[derive(Clone, Debug, PartialEq, Eq, Default)]
pub struct SerializeBytes(pub Vec<u8>);

#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Debug)]
pub struct LuaPath(pub Option<String>);

#[derive(Copy, Clone, Eq, PartialEq, Default)]
pub struct ReplayMode(pub bool);

#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Debug)]
pub struct RewardTypes(pub HashSet<String>);

impl Default for RewardTypes {
    fn default() -> Self {
        let mut map = HashSet::new();
        map.insert("death".to_string());
        map.insert("kill".to_string());
        map.insert("dmg_dealt".to_string());
        map.insert("dmg_recvd".to_string());
        map.insert("victory".to_string());
        map.insert("defeat".to_string());

        RewardTypes(map)
    }
}

#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
pub struct Spawn {
    pub delay: u64,
    pub pos: super::components::Pos,
    pub curr_hp: Option<f64>,
    pub faction: usize,
    pub u_type: String,
}

#[derive(Clone, PartialEq, Debug, Default, Serialize, Deserialize)]
pub struct SpawnBuffer(pub Vec<Spawn>);

#[derive(Copy, Clone, PartialEq, Eq, Debug, Default)]
pub struct Deserializing(pub bool);
<commit_msg>Make square attack radius more correct<commit_after>use std::collections::{HashMap, HashSet};

use super::FactionId;
use engine::components::{AttackSensor, CollisionHandle, Color, DataStoreComponent, Hp, Movable,
                         Pos, Shape, Speed, Static, UnitTypeTag};

use scaii_defs::protos::{Action, State, Viz};

use specs::prelude::*;
use specs::world::LazyBuilder;

pub mod collision;

pub use self::collision::*;
pub use engine::systems::lua::userdata::DataStore;

// Recommended by ncollide
pub const COLLISION_MARGIN: f64 = 0.02;
// ncollide wants the average size of a collider to be "around" 1
// we should probably set this as a resource from Lua in the future
pub const COLLISION_SCALE: f64 = 5.0;

pub const UNIVERSAL_SENSOR: usize = SENSOR_OFFSET+MAX_FACTIONS;
pub const SENSOR_OFFSET: usize = MAX_FACTIONS;
pub const SENSOR_BLACKLIST: [usize; MAX_FACTIONS+1] = [SENSOR_OFFSET, SENSOR_OFFSET+1, SENSOR_OFFSET+2, SENSOR_OFFSET+3, UNIVERSAL_SENSOR];

pub const MAX_FACTIONS: usize = 4;

pub const STATE_SIZE: usize = 40;
pub const STATE_SCALE: usize = 1;

lazy_static! {
    pub static ref PLAYER_COLORS: Vec<Color> = vec![
        Color {
            r: 255,
            g: 181,
            b: 0,
        },
        Color { r: 255, g: 0, b: 0 },
        Color { r: 0, g: 0, b: 255 },
    ];
}

// 60FPS emulation since we're not
// actually measuring time elapsed
const SIXTY_FPS: f64 = 1.0 / 60.0;

pub(super) fn register_world_resources(world: &mut World) {
    use ndarray::Array3;
    use specs::saveload::U64MarkerAllocator;
    use util;

    let rng = util::make_rng();
    world.add_resource(rng);
    world.add_resource(Episode(0));
    world.add_resource(Terminal(false));
    world.add_resource(Step(0));
    world.add_resource(MaxStep(None));
    world.add_resource(DeltaT(SIXTY_FPS));
    world.add_resource(Render::default());
    world.add_resource(NeedsKeyInfo(true));
    world.add_resource::<Vec<Player>>(Vec::new());
    world.add_resource(UnitTypeMap::default());
    world.add_resource(U64MarkerAllocator::new());
    world.add_resource(ActionInput::default());
    world.add_resource(SkyCollisionWorld::new(COLLISION_MARGIN));
    world.add_resource(RtsState(State {
        features: Array3::zeros([STATE_SIZE, STATE_SIZE, 4]).into_raw_vec(),
        feature_array_dims: vec![STATE_SIZE as u32, STATE_SIZE as u32, 4],
        ..Default::default()
    }));
    world.add_resource(Reward::default());
    world.add_resource(Skip(false, None));
    world.add_resource(SerializeBytes::default());
    world.add_resource(LuaPath(None));
    world.add_resource(ReplayMode(false));
    world.add_resource(RewardTypes::default());
    world.add_resource(SpawnBuffer::default());
    world.add_resource(Deserializing(false));
    world.add_resource(CumReward::default());
    world.add_resource(DataStore::default());
}

#[derive(Default, Debug, Clone, PartialEq)]
pub struct RtsState(pub State);

/// The current episode, only meaningful for sequential runs.
#[derive(Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash, Serialize, Deserialize)]
pub struct Episode(pub usize);

#[derive(Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash, Serialize, Deserialize)]
pub struct Step(pub usize);

#[derive(Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash, Serialize, Deserialize)]
pub struct MaxStep(pub Option<usize>);

/// Is this the final frame of the scenario?
#[derive(Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash, Debug, Serialize, Deserialize)]
pub struct Terminal(pub bool);

/// Time since the last update, in seconds (fixed to one sixtieth of a second for our purposes).
#[derive(Copy, Clone, PartialEq, PartialOrd, Serialize, Deserialize)]
pub struct DeltaT(pub f64);

/// Any associated data with various game factions.
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Player {
    pub color: super::components::Color,
    pub id: FactionId,
}

/// The output of the renderer, for use with Viz.
#[derive(Clone, PartialEq, Default)]
pub struct Render(pub Viz);

/// Tracks whether a FULL rerender (or total state, or whatever else)
/// is needed rather than a delta.
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Default, Debug)]
pub struct NeedsKeyInfo(pub bool);

/// The actions coming from the Agent (or replay mechanism)
#[derive(Clone, PartialEq, Default, Debug)]
pub struct ActionInput(pub Option<Action>);

#[derive(PartialEq, Default, Clone)]
pub struct Reward(pub HashMap<String, f64>);

#[derive(PartialEq, Default, Clone, Serialize, Deserialize, Debug)]
pub struct CumReward(pub HashMap<String, f64>);

#[derive(Eq, PartialEq, Default, Clone, Debug, Hash)]
pub struct Skip(pub bool, pub Option<String>);

#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
pub struct UnitType {
    pub tag: String,
    pub max_hp: f64,
    pub movable: bool,
    pub shape: Shape,
    pub kill_reward: f64,
    pub death_penalty: f64,
    pub damage_deal_reward: Option<f64>,
    pub damage_recv_penalty: Option<f64>,
    pub speed: f64,
    pub attack_range: f64,
    pub attack_damage: f64,
    pub attack_delay: f64,

    pub death_type: String,
    pub dmg_recv_type: String,
    pub dmg_deal_type: String,
    pub kill_type: String,
}

impl Default for UnitType {
    fn default() -> Self {
        UnitType {
            tag: "".to_string(),
            max_hp: 100.0,
            movable: true,
            shape: Shape::Triangle { base_len: 10.0 },
            kill_reward: 0.0,
            death_penalty: 0.0,
            damage_deal_reward: None,
            damage_recv_penalty: None,
            death_type: "death".to_string(),
            dmg_recv_type: "dmg_recvd".to_string(),
            dmg_deal_type: "dmg_dealt".to_string(),
            kill_type: "killed_enemy".to_string(),
            speed: 20.0,
            attack_range: 10.0,
            attack_delay: 1.0,
            attack_damage: 10.0,
        }
    }
}

impl UnitType {
    pub fn build_entity(
        &self,
        world: &mut World,
        pos: Pos,
        curr_hp: Option<f64>,
        faction: usize,
    ) -> Entity {
        let lazy_update = world.read_resource::<LazyUpdate>();
        let lazy_build = lazy_update.create_entity(&world.entities());

        let players = &**world.read_resource::<Vec<Player>>();

        self.build_entity_lazy(lazy_build, &*lazy_update, pos, curr_hp, faction, players)
    }

    /// Creates and places the unit in the game world given its initial
    /// position and faction.
    ///
    /// This also initializes anything it needs such as colliders in the collision system.
    pub fn build_entity_lazy(
        &self,
        entity: LazyBuilder,
        update: &LazyUpdate,
        pos: Pos,
        curr_hp: Option<f64>,
        faction: usize,
        players: &[Player],
    ) -> Entity {
        use specs::saveload::U64Marker;

        let color = players[faction].color;

        // Scoping for borrow shenanigans
        let entity = {
            let entity = entity
                .with(pos)
                .with(self.shape)
                .with(color)
                .with(FactionId(faction))
                .with(UnitTypeTag(self.tag.clone()))
                .with(DataStoreComponent::default())
                .with(Hp {
                    max_hp: self.max_hp,
                    curr_hp: curr_hp.unwrap_or(self.max_hp),
                })
                .marked::<U64Marker>();

            if self.movable {
                entity.with(Movable(0)).with(Speed(self.speed))
            } else {
                entity.with(Static(0))
            }
        }.build();

        let me = self.clone();

        update.execute(move |world| {
            let mut col_storage = world.write::<CollisionHandle>();
            let mut atk_storage = world.write::<AttackSensor>();

            let mut c_world = world.write_resource();
            me.register_collision(
                entity,
                pos,
                faction,
                &mut col_storage,
                &mut atk_storage,
                &mut *c_world,
            );
        });

        entity
    }

    /// Registers a unit with the collision system based on its unit type
    /// and places the collision system handles into the appropriate components
    pub fn register_collision(
        &self,
        entity: Entity,
        pos: Pos,
        faction: usize,
        col_storage: &mut WriteStorage<CollisionHandle>,
        atk_storage: &mut WriteStorage<AttackSensor>,
        c_world: &mut SkyCollisionWorld,
    ) -> Entity {
        use nalgebra;
        use nalgebra::{Isometry2, Vector2};
        use ncollide::shape::{Ball, Cuboid, Cylinder, ShapeHandle};
        use ncollide::world::{CollisionGroups, GeometricQueryType};

        let mut collider_group = CollisionGroups::new();
        collider_group.set_membership(&[faction]);

        let mut sensor_group = CollisionGroups::new();
        sensor_group.set_membership(&[MAX_FACTIONS + faction]);
        sensor_group.set_blacklist(&SENSOR_BLACKLIST);

        let atk_radius = self.attack_range / COLLISION_SCALE;

        let (collider, atk_radius) = match self.shape {
            Shape::Rect { width, height } => {
                let width = width / COLLISION_SCALE;
                let height = height / COLLISION_SCALE;

                // ncollide likes half widths and heights, so divide by 2
                let collider = Cuboid::new(Vector2::new(width / 2.0, height / 2.0));
                let collider = ShapeHandle::new(collider);

                let half_diagonal = (width*width + height*height).sqrt() / 2.0;
                let atk_radius = half_diagonal + (self.attack_range / COLLISION_SCALE);
                let atk_sensor = Ball::new(atk_radius);
                let atk_sensor = ShapeHandle::new(atk_sensor);

                (collider, atk_sensor)
            }
            Shape::Triangle { base_len } => {
                let base_len = base_len / COLLISION_SCALE;

                // equilateral triangle dimensions
                let half_height = base_len / (2.0 as f64).sqrt() / 2.0;
                let radius = base_len / 2.0;

                // A cylinder in 2D is an isoscelese triangle in ncollide
                let collider = Cylinder::new(half_height, radius);
                let collider = ShapeHandle::new(collider);

                let atk_radius = half_height + (self.attack_range / COLLISION_SCALE);
                let atk_sensor = Ball::new(atk_radius);
                let atk_sensor = ShapeHandle::new(atk_sensor);

                (collider, atk_sensor)
            }
        };

        // We need the entity ID for this, so do it after building the entity and then add the component.
        let (collider, atk_radius) = {
            let pos = Isometry2::new(
                Vector2::new(pos.x / COLLISION_SCALE, pos.y / COLLISION_SCALE),
                nalgebra::zero(),
            );

            let q_type = GeometricQueryType::Contacts(0.0, 0.0);
            let collider = c_world.add(
                pos,
                collider,
                collider_group,
                q_type,
                ColliderData {
                    e: entity,
                    detector: false,
                },
            );

            let atk_radius = c_world.add(
                pos,
                atk_radius,
                sensor_group,
                q_type,
                ColliderData {
                    e: entity,
                    detector: true,
                },
            );

            (collider, atk_radius)
        };

        col_storage.insert(entity, CollisionHandle(collider));
        atk_storage.insert(entity, AttackSensor(atk_radius));

        c_world.update();

        entity
    }
}

#[derive(Clone, Debug, Serialize, Deserialize, Default)]
pub struct UnitTypeMap {
    pub typ_ids: HashMap<String, usize>,
    pub tag_map: HashMap<String, UnitType>,
}

#[derive(Clone, Debug, PartialEq, Eq, Default)]
pub struct SerializeBytes(pub Vec<u8>);

#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Debug)]
pub struct LuaPath(pub Option<String>);

#[derive(Copy, Clone, Eq, PartialEq, Default)]
pub struct ReplayMode(pub bool);

#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Debug)]
pub struct RewardTypes(pub HashSet<String>);

impl Default for RewardTypes {
    fn default() -> Self {
        let mut map = HashSet::new();
        map.insert("death".to_string());
        map.insert("kill".to_string());
        map.insert("dmg_dealt".to_string());
        map.insert("dmg_recvd".to_string());
        map.insert("victory".to_string());
        map.insert("defeat".to_string());

        RewardTypes(map)
    }
}

#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
pub struct Spawn {
    pub delay: u64,
    pub pos: super::components::Pos,
    pub curr_hp: Option<f64>,
    pub faction: usize,
    pub u_type: String,
}

#[derive(Clone, PartialEq, Debug, Default, Serialize, Deserialize)]
pub struct SpawnBuffer(pub Vec<Spawn>);

#[derive(Copy, Clone, PartialEq, Eq, Debug, Default)]
pub struct Deserializing(pub bool);
<|endoftext|>


<commit_before>//! Error Reporting for `impl` items that do not match the obligations from their `trait`.

use crate::hir;
use crate::hir::def_id::DefId;
use crate::infer::error_reporting::nice_region_error::NiceRegionError;
use crate::infer::lexical_region_resolve::RegionResolutionError;
use crate::infer::{Subtype, ValuePairs};
use crate::traits::ObligationCauseCode::CompareImplMethodObligation;
use rustc_data_structures::fx::FxHashSet;
use rustc_errors::ErrorReported;
use rustc_middle::ty::error::ExpectedFound;
use rustc_middle::ty::fold::TypeFoldable;
use rustc_middle::ty::{self, Ty};
use rustc_span::Span;

impl<'a, 'tcx> NiceRegionError<'a, 'tcx> {
    /// Print the error message for lifetime errors when the `impl` doesn't conform to the `trait`.
    pub(super) fn try_report_impl_not_conforming_to_trait(&self) -> Option<ErrorReported> {
        if let Some(ref error) = self.error {
            debug!("try_report_impl_not_conforming_to_trait {:?}", error);
            if let RegionResolutionError::SubSupConflict(
                _,
                var_origin,
                sub_origin,
                _sub,
                sup_origin,
                _sup,
            ) = error.clone()
            {
                if let (&Subtype(ref sup_trace), &Subtype(ref sub_trace)) =
                    (&sup_origin, &sub_origin)
                {
                    if let (
                        ValuePairs::Types(sub_expected_found),
                        ValuePairs::Types(sup_expected_found),
                        CompareImplMethodObligation { trait_item_def_id, .. },
                    ) = (&sub_trace.values, &sup_trace.values, &sub_trace.cause.code)
                    {
                        if sup_expected_found == sub_expected_found {
                            self.emit_err(
                                var_origin.span(),
                                sub_expected_found.expected,
                                sub_expected_found.found,
                                *trait_item_def_id,
                            );
                            return Some(ErrorReported);
                        }
                    }
                }
            }
        }
        None
    }

    fn emit_err(&self, sp: Span, expected: Ty<'tcx>, found: Ty<'tcx>, trait_def_id: DefId) {
        let tcx = self.tcx();
        let trait_sp = self.tcx().def_span(trait_def_id);
        let mut err = self
            .tcx()
            .sess
            .struct_span_err(sp, "`impl` item signature doesn't match `trait` item signature");
        err.span_label(sp, &format!("found {:?}", found));
        err.span_label(trait_sp, &format!("expected {:?}", expected));
        let trait_fn_sig = tcx.fn_sig(trait_def_id);

        // Check the `trait`'s method's output to look for type parameters that might have
        // unconstrained lifetimes. If the method returns a type parameter and the `impl` has a
        // borrow as the type parameter being implemented, the lifetimes will not match because
        // a new lifetime is being introduced in the `impl` that is not present in the `trait`.
        // Because this is confusing as hell the first time you see it, we give a short message
        // explaining the situation and proposing constraining the type param with a named lifetime
        // so that the `impl` will have one to tie them together.
        struct AssocTypeFinder(FxHashSet<ty::ParamTy>);
        impl<'tcx> ty::fold::TypeVisitor<'tcx> for AssocTypeFinder {
            fn visit_ty(&mut self, ty: Ty<'tcx>) -> bool {
                debug!("assoc type finder ty {:?} {:?}", ty, ty.kind);
                if let ty::Param(param) = ty.kind {
                    self.0.insert(param);
                }
                ty.super_visit_with(self)
            }
        }
        let mut visitor = AssocTypeFinder(FxHashSet::default());
        trait_fn_sig.output().visit_with(&mut visitor);
        if let Some(id) = tcx.hir().as_local_hir_id(trait_def_id) {
            let parent_id = tcx.hir().get_parent_item(id);
            let trait_item = tcx.hir().expect_item(parent_id);
            if let hir::ItemKind::Trait(_, _, generics, _, _) = &trait_item.kind {
                for param_ty in visitor.0 {
                    if let Some(generic) = generics.get_named(param_ty.name) {
                        err.span_label(generic.span, &format!(
                            "for `impl` items to implement the method, this type parameter might \
                             need a lifetime restriction like `{}: 'a`",
                            param_ty.name,
                        ));
                    }
                }
            }
        }

        if let Some((expected, found)) = tcx
            .infer_ctxt()
            .enter(|infcx| infcx.expected_found_str_ty(&ExpectedFound { expected, found }))
        {
            // Highlighted the differences when showing the "expected/found" note.
            err.note_expected_found(&"", expected, &"", found);
        } else {
            // This fallback shouldn't be necessary, but let's keep it in just in case.
            err.note(&format!("expected `{:?}`\n   found `{:?}`", expected, found));
        }
        err.note("the lifetime requirements from the `trait` could not be satisfied by the `impl`");
        err.help(
            "verify the lifetime relationships in the `trait` and `impl` between the `self` \
             argument, the other inputs and its output",
        );
        err.emit();
    }
}
<commit_msg>review comment: use FxIndexSet<commit_after>//! Error Reporting for `impl` items that do not match the obligations from their `trait`.

use crate::hir;
use crate::hir::def_id::DefId;
use crate::infer::error_reporting::nice_region_error::NiceRegionError;
use crate::infer::lexical_region_resolve::RegionResolutionError;
use crate::infer::{Subtype, ValuePairs};
use crate::traits::ObligationCauseCode::CompareImplMethodObligation;
use rustc_data_structures::fx::FxIndexSet;
use rustc_errors::ErrorReported;
use rustc_middle::ty::error::ExpectedFound;
use rustc_middle::ty::fold::TypeFoldable;
use rustc_middle::ty::{self, Ty};
use rustc_span::Span;

impl<'a, 'tcx> NiceRegionError<'a, 'tcx> {
    /// Print the error message for lifetime errors when the `impl` doesn't conform to the `trait`.
    pub(super) fn try_report_impl_not_conforming_to_trait(&self) -> Option<ErrorReported> {
        if let Some(ref error) = self.error {
            debug!("try_report_impl_not_conforming_to_trait {:?}", error);
            if let RegionResolutionError::SubSupConflict(
                _,
                var_origin,
                sub_origin,
                _sub,
                sup_origin,
                _sup,
            ) = error.clone()
            {
                if let (&Subtype(ref sup_trace), &Subtype(ref sub_trace)) =
                    (&sup_origin, &sub_origin)
                {
                    if let (
                        ValuePairs::Types(sub_expected_found),
                        ValuePairs::Types(sup_expected_found),
                        CompareImplMethodObligation { trait_item_def_id, .. },
                    ) = (&sub_trace.values, &sup_trace.values, &sub_trace.cause.code)
                    {
                        if sup_expected_found == sub_expected_found {
                            self.emit_err(
                                var_origin.span(),
                                sub_expected_found.expected,
                                sub_expected_found.found,
                                *trait_item_def_id,
                            );
                            return Some(ErrorReported);
                        }
                    }
                }
            }
        }
        None
    }

    fn emit_err(&self, sp: Span, expected: Ty<'tcx>, found: Ty<'tcx>, trait_def_id: DefId) {
        let tcx = self.tcx();
        let trait_sp = self.tcx().def_span(trait_def_id);
        let mut err = self
            .tcx()
            .sess
            .struct_span_err(sp, "`impl` item signature doesn't match `trait` item signature");
        err.span_label(sp, &format!("found {:?}", found));
        err.span_label(trait_sp, &format!("expected {:?}", expected));
        let trait_fn_sig = tcx.fn_sig(trait_def_id);

        // Check the `trait`'s method's output to look for type parameters that might have
        // unconstrained lifetimes. If the method returns a type parameter and the `impl` has a
        // borrow as the type parameter being implemented, the lifetimes will not match because
        // a new lifetime is being introduced in the `impl` that is not present in the `trait`.
        // Because this is confusing as hell the first time you see it, we give a short message
        // explaining the situation and proposing constraining the type param with a named lifetime
        // so that the `impl` will have one to tie them together.
        struct AssocTypeFinder(FxIndexSet<ty::ParamTy>);
        impl<'tcx> ty::fold::TypeVisitor<'tcx> for AssocTypeFinder {
            fn visit_ty(&mut self, ty: Ty<'tcx>) -> bool {
                debug!("assoc type finder ty {:?} {:?}", ty, ty.kind);
                if let ty::Param(param) = ty.kind {
                    self.0.insert(param);
                }
                ty.super_visit_with(self)
            }
        }
        let mut visitor = AssocTypeFinder(FxIndexSet::default());
        trait_fn_sig.output().visit_with(&mut visitor);
        if let Some(id) = tcx.hir().as_local_hir_id(trait_def_id) {
            let parent_id = tcx.hir().get_parent_item(id);
            let trait_item = tcx.hir().expect_item(parent_id);
            if let hir::ItemKind::Trait(_, _, generics, _, _) = &trait_item.kind {
                for param_ty in visitor.0 {
                    if let Some(generic) = generics.get_named(param_ty.name) {
                        err.span_label(generic.span, &format!(
                            "for `impl` items to implement the method, this type parameter might \
                             need a lifetime restriction like `{}: 'a`",
                            param_ty.name,
                        ));
                    }
                }
            }
        }

        if let Some((expected, found)) = tcx
            .infer_ctxt()
            .enter(|infcx| infcx.expected_found_str_ty(&ExpectedFound { expected, found }))
        {
            // Highlighted the differences when showing the "expected/found" note.
            err.note_expected_found(&"", expected, &"", found);
        } else {
            // This fallback shouldn't be necessary, but let's keep it in just in case.
            err.note(&format!("expected `{:?}`\n   found `{:?}`", expected, found));
        }
        err.note("the lifetime requirements from the `trait` could not be satisfied by the `impl`");
        err.help(
            "verify the lifetime relationships in the `trait` and `impl` between the `self` \
             argument, the other inputs and its output",
        );
        err.emit();
    }
}
<|endoftext|>


<commit_before>use std::env;
use std::fs;
use std::process;

use clap::ArgMatches;
use tempdir::TempDir;
use hyper::{Client, Url};
use kuchiki;

use extractor;
use downloader;
use profile;
use config::Config;

pub fn init() {
    let config = Config::new();
    if config.install_dir.exists() || config.shim_dir.exists() {
        println!("Already initalized. Reinitializing....");
    }
    fs::create_dir_all(&config.install_dir).expect("Could not create installation directory.");
    fs::create_dir_all(&config.shim_dir).expect("Could not create shims directory.");
    fs::create_dir_all(&config.versions_dir).expect("Could not create versions directory.");
    if !env::home_dir()
        .unwrap_or_else(|| panic!("Cound not found homedir."))
        .join("Documents")
        .join("WindowsPowerShell")
        .join("profile.ps1")
        .exists() {
        println!(r#"Please create profile.ps1 in the following place:

$Env:USERPROFILE\Documents\WindowsPowerShell\profile.ps1

And write the following thing:

. $Env:USERPROFILE\.groonga\shims\bin\source-groonga.ps1
"#)
    }
}

pub fn install(m: &ArgMatches) {
    extern crate env_proxy;

    const BASE_URL: &'static str = "http://packages.groonga.org/windows/groonga";
    let maybe_proxy = env_proxy::for_url(&Url::parse(BASE_URL).unwrap());
    let config = Config::from_matches(m);
    println!("Value for architecture: {}",
             config.arch.clone().expect("unsupported platform"));
    println!("Obtaining Groonga version: {}",
             config.version.clone().unwrap());
    let groonga_dir = format!("groonga-{}-{}",
                              config.version.unwrap(),
                              config.arch.unwrap());
    let groonga_binary = format!("{}.zip", groonga_dir.clone());
    if config.versions_dir.join(groonga_dir.clone()).exists() {
        println!("Already installed. Ready to use it.");
        return ();
    }

    let download_dir = TempDir::new("grnenv-rs")
        .expect("Could not create temp dir.")
        .into_path();

    let client = match maybe_proxy {
        None => Client::new(),
        Some(host_port) => Client::with_http_proxy(host_port.0, host_port.1),
    };
    let filename = downloader::file_download(&client,
                                             &*format!("{}/{}", BASE_URL, groonga_binary),
                                             download_dir,
                                             "groonga.zip")
        .expect("Failed to download");
    assert!(extractor::extract_zip(&filename, &config.versions_dir).is_ok());
}

pub fn switch(m: &ArgMatches) {
    let config = Config::from_matches(m);
    println!("Value for architecture: {}",
             config.arch.clone().expect("unsupported platform"));
    println!("Using Groonga version: {}", config.version.clone().unwrap());
    let groonga_dir = format!("groonga-{}-{}",
                              config.version.unwrap(),
                              config.arch.unwrap());
    match config.version {
        Some("system") => {
            profile::windows::remove_grnenv_profile(&config.shim_dir).unwrap();
            return ();
        }
        Some(_) => {
            profile::windows::create_profile_source(&config.shim_dir,
                                                    &groonga_dir,
                                                    &config.versions_dir)
                .expect("Could not create source-groonga.ps1")
        }
        None => unreachable!(),
    }
}

pub fn uninstall(m: &ArgMatches) {
    let config = Config::from_matches(m);
    let groonga_dir = format!("groonga-{}-{}",
                              config.version.unwrap(),
                              config.arch.unwrap());
    if config.versions_dir.join(groonga_dir.clone()).exists() {
        println!("Removing {}....", groonga_dir.clone());
        fs::remove_dir_all(&config.versions_dir.join(groonga_dir))
            .expect("Could not remove specified directory.");
    } else {
        println!("{} is not installed!", groonga_dir.clone());
        process::exit(1);
    }
}

pub fn list() {
    use kuchiki::traits::*;
    use command::common::MaybeProxyUrl;

    let base_url: &'static str = "http://packages.groonga.org/windows/groonga";
    let maybe_proxy_url = MaybeProxyUrl { url: Url::parse(base_url).unwrap() };
    if let Ok(doc) = kuchiki::parse_html().from_http(maybe_proxy_url) {
        let docs = doc.select("tr").unwrap().collect::<Vec<_>>();
        println!("Installable Groonga:");
        for handle in &docs {
            let texts = handle.as_node().descendants().text_nodes().collect::<Vec<_>>();
            if let Some(text) = texts.first() {
                let package = text.as_node().text_contents();
                if package.contains("groonga") && package.contains("zip") &&
                   (package.contains("x86") || package.contains("x64")) {
                    let package = package.split(".zip").collect::<Vec<_>>();
                    let pkg =
                        package.first().unwrap_or(&"").to_owned().split("-").collect::<Vec<_>>();
                    println!("\t{} --arch {}",
                             pkg.get(1).unwrap_or(&""),
                             pkg.get(2).unwrap_or(&""));
                }
            }
        }
    } else {
        println!("{}", "The page couldn't be fetched");
    }
}
<commit_msg>windows: Confirm uninstallation<commit_after>use std::borrow::Cow;
use std::env;
use std::fs;
use std::io;
use std::process;

use clap::ArgMatches;
use tempdir::TempDir;
use hyper::{Client, Url};
use kuchiki;

use extractor;
use downloader;
use profile;
use config::Config;

pub fn init() {
    let config = Config::new();
    if config.install_dir.exists() || config.shim_dir.exists() {
        println!("Already initalized. Reinitializing....");
    }
    fs::create_dir_all(&config.install_dir).expect("Could not create installation directory.");
    fs::create_dir_all(&config.shim_dir).expect("Could not create shims directory.");
    fs::create_dir_all(&config.versions_dir).expect("Could not create versions directory.");
    if !env::home_dir()
        .unwrap_or_else(|| panic!("Cound not found homedir."))
        .join("Documents")
        .join("WindowsPowerShell")
        .join("profile.ps1")
        .exists() {
        println!(r#"Please create profile.ps1 in the following place:

$Env:USERPROFILE\Documents\WindowsPowerShell\profile.ps1

And write the following thing:

. $Env:USERPROFILE\.groonga\shims\bin\source-groonga.ps1
"#)
    }
}

pub fn install(m: &ArgMatches) {
    extern crate env_proxy;

    const BASE_URL: &'static str = "http://packages.groonga.org/windows/groonga";
    let maybe_proxy = env_proxy::for_url(&Url::parse(BASE_URL).unwrap());
    let config = Config::from_matches(m);
    println!("Value for architecture: {}",
             config.arch.clone().expect("unsupported platform"));
    println!("Obtaining Groonga version: {}",
             config.version.clone().unwrap());
    let groonga_dir = format!("groonga-{}-{}",
                              config.version.unwrap(),
                              config.arch.unwrap());
    let groonga_binary = format!("{}.zip", groonga_dir.clone());
    if config.versions_dir.join(groonga_dir.clone()).exists() {
        println!("Already installed. Ready to use it.");
        return ();
    }

    let download_dir = TempDir::new("grnenv-rs")
        .expect("Could not create temp dir.")
        .into_path();

    let client = match maybe_proxy {
        None => Client::new(),
        Some(host_port) => Client::with_http_proxy(host_port.0, host_port.1),
    };
    let filename = downloader::file_download(&client,
                                             &*format!("{}/{}", BASE_URL, groonga_binary),
                                             download_dir,
                                             "groonga.zip")
        .expect("Failed to download");
    assert!(extractor::extract_zip(&filename, &config.versions_dir).is_ok());
}

pub fn switch(m: &ArgMatches) {
    let config = Config::from_matches(m);
    println!("Value for architecture: {}",
             config.arch.clone().expect("unsupported platform"));
    println!("Using Groonga version: {}", config.version.clone().unwrap());
    let groonga_dir = format!("groonga-{}-{}",
                              config.version.unwrap(),
                              config.arch.unwrap());
    match config.version {
        Some("system") => {
            profile::windows::remove_grnenv_profile(&config.shim_dir).unwrap();
            return ();
        }
        Some(_) => {
            profile::windows::create_profile_source(&config.shim_dir,
                                                    &groonga_dir,
                                                    &config.versions_dir)
                .expect("Could not create source-groonga.ps1")
        }
        None => unreachable!(),
    }
}

pub fn uninstall(m: &ArgMatches) {
    let config = Config::from_matches(m);
    let mut choice = String::new();
    let arch = match config.arch.unwrap() {
        Cow::Borrowed(s) => s.to_owned(),
        Cow::Owned(s) => s
    };
    let groonga_dir = format!("groonga-{}-{}",
                              config.version.unwrap(),
                              arch);
    if config.versions_dir.join(groonga_dir.clone()).exists() {
        println!("Uninstall Groonga version {}, arch {}? [y/N]",
                 config.version.unwrap(), arch);
        io::stdin().read_line(&mut choice).expect("Failed to read line");
        if choice == "y".to_owned() || choice == "Y".to_owned() {
            println!("Removing {}....", groonga_dir.clone());
            fs::remove_dir_all(&config.versions_dir.join(groonga_dir))
                .expect("Could not remove specified directory.");
        }
    } else {
        println!("{} is not installed!", groonga_dir.clone());
        process::exit(1);
    }
}

pub fn list() {
    use kuchiki::traits::*;
    use command::common::MaybeProxyUrl;

    let base_url: &'static str = "http://packages.groonga.org/windows/groonga";
    let maybe_proxy_url = MaybeProxyUrl { url: Url::parse(base_url).unwrap() };
    if let Ok(doc) = kuchiki::parse_html().from_http(maybe_proxy_url) {
        let docs = doc.select("tr").unwrap().collect::<Vec<_>>();
        println!("Installable Groonga:");
        for handle in &docs {
            let texts = handle.as_node().descendants().text_nodes().collect::<Vec<_>>();
            if let Some(text) = texts.first() {
                let package = text.as_node().text_contents();
                if package.contains("groonga") && package.contains("zip") &&
                   (package.contains("x86") || package.contains("x64")) {
                    let package = package.split(".zip").collect::<Vec<_>>();
                    let pkg =
                        package.first().unwrap_or(&"").to_owned().split("-").collect::<Vec<_>>();
                    println!("\t{} --arch {}",
                             pkg.get(1).unwrap_or(&""),
                             pkg.get(2).unwrap_or(&""));
                }
            }
        }
    } else {
        println!("{}", "The page couldn't be fetched");
    }
}
<|endoftext|>


<commit_before>use lalafell::commands::prelude::*;

use lalafell::error::*;

use fflogs::{self, FfLogs};
use fflogs::net::{ServerRegion, Metric};

use std::cmp::Ordering;

pub struct FfLogsCommand {
  fflogs: FfLogs
}

impl ::commands::BotCommand for FfLogsCommand {
  fn new(env: Arc<::bot::BotEnv>) -> Self {
    FfLogsCommand {
      fflogs: FfLogs::new(&env.environment.fflogs_api_key)
    }
  }
}

#[derive(Debug, StructOpt)]
#[structopt(about = "Display information about the character on FF Logs")]
pub struct Params {
  #[structopt(help = "The server the character is on")]
  server: String,
  #[structopt(help = "The character's first name")]
  first_name: String,
  #[structopt(help = "The character's last name")]
  last_name: String,
  #[structopt(
    short = "j",
    long = "job",
    help = "The job to look at"
  )]
  job: Option<String>
}

impl HasParams for FfLogsCommand {
  type Params = Params;
}

impl<'a> Command<'a> for FfLogsCommand {
  fn run(&self, _: &Context, _: &Message, params: &[&str]) -> CommandResult<'a> {
    let params = self.params_then("fflogs", params, |a| a.setting(::structopt::clap::AppSettings::ArgRequiredElseHelp))?;
    let server = params.server;
    let region = match FfLogsCommand::region(&server) {
      Some(r) => r,
      None => return Err("Invalid server.".into())
    };
    let name = format!("{} {}", params.first_name, params.last_name);

    let parses = self.fflogs.parses(
      &name,
      &server,
      region,
      |x| x.metric(Metric::Dps)
    );

    let parses = match parses {
      Ok(r) => r,
      Err(fflogs::errors::Error::FfLogs(fflogs::net::FfLogsError { status, error })) => {
        return Err(format!("FF Logs didn't like that ({}): {}", status, error).into());
      },
      Err(e) => return Err(e).chain_err(|| "could not query fflogs")?
    };

    if parses.is_empty() {
      return Ok("No parses found.".into());
    }

    let first_spec = match parses[0].specs.get(0) {
      Some(s) => s,
      None => return Err("Somehow there was no first spec.".into())
    };
    let first_data = match first_spec.data.get(0) {
      Some(d) => d,
      None => return Err("Somehow there was no first data.".into())
    };

    let job = params.job.as_ref().unwrap_or(&first_spec.spec);
    let lower_job = job.to_lowercase();
    let name = &first_data.character_name;
    let id = first_data.character_id;

    let mut embed = CreateEmbed::default();

    embed = embed
      .title(&name)
      .url(format!("https://www.fflogs.com/character/id/{}", id))
      .field("Job", &job, true)
      .field("Server", &server, true);

    for parse in &parses {
      let spec = match parse.specs.iter().filter(|s| s.spec.to_lowercase() == lower_job).next() {
        Some(s) => s,
        None => continue
      };
      let data = match spec.data.iter().max_by(|a, b| a.historical_percent.partial_cmp(&b.historical_percent).unwrap_or(Ordering::Less)) {
        Some(d) => d,
        None => continue
      };

      let url = format!("https://www.fflogs.com/reports/{}#fight={}", data.report_code, data.report_fight);

      let string = format!("[Link]({url}) – {dps:.2} DPS – {perc:.2} percentile out of {total_parses} parses",
        url = url,
        dps = data.persecondamount,
        perc = data.historical_percent,
        total_parses = data.historical_count
      );

      embed = embed.field(&parse.name, string, false);
    }
    Ok(CommandSuccess::default().message(|_| embed))
  }
}

impl FfLogsCommand {
  fn region(server: &str) -> Option<ServerRegion> {
    match server.to_lowercase().as_str() {
      "adamantoise" | "balmung" | "cactuar" | "coeurl" | "faerie" | "gilgamesh" | "goblin" | "jenova" | "mateus" | "midgardsormr" | "sargatanas" | "siren" | "zalera"
      | "behemoth" | "brynhildr" | "diabolos" | "excalibur" | "exodus" | "famfrit" | "hyperion" | "lamia" | "leviathan" | "malboro" | "ultros"
        => Some(ServerRegion::NorthAmerica),
      "aegis" | "atomos" | "carbuncle" | "garuda" | "gungnir" | "kujata" | "ramuh" | "tonberry" | "typhon" | "unicorn"
      | "alexander" | "bahamut" | "durandal" | "fenrir" | "ifrit" | "ridill" | "tiamat" | "ultima" | "valefor" | "yojimbo" | "zeromus"
      | "anima" | "asura" | "belias" | "chocobo" | "hades" | "ixion" | "mandragora" | "masamune" | "pandaemonium" | "shinryu" | "titan"
        => Some(ServerRegion::Japan),
      "cerberus" | "lich" | "louisoix" | "moogle" | "odin" | "omega" | "phoenix" | "ragnarok" | "shiva" | "zodiark"
        => Some(ServerRegion::Europe),
      _ => None
    }
  }
}
<commit_msg>feat: allow multiple names for each job<commit_after>use lalafell::commands::prelude::*;

use lalafell::error::*;

use fflogs::{self, FfLogs};
use fflogs::net::{ServerRegion, Metric};

use std::cmp::Ordering;
use std::str::FromStr;

pub struct FfLogsCommand {
  fflogs: FfLogs
}

impl ::commands::BotCommand for FfLogsCommand {
  fn new(env: Arc<::bot::BotEnv>) -> Self {
    FfLogsCommand {
      fflogs: FfLogs::new(&env.environment.fflogs_api_key)
    }
  }
}

#[derive(Debug, StructOpt)]
#[structopt(about = "Display information about the character on FF Logs")]
pub struct Params {
  #[structopt(help = "The server the character is on")]
  server: String,
  #[structopt(help = "The character's first name")]
  first_name: String,
  #[structopt(help = "The character's last name")]
  last_name: String,
  #[structopt(
    short = "j",
    long = "job",
    help = "The job to look at",
    parse(try_from_str)
  )]
  job: Option<Job>
}

impl HasParams for FfLogsCommand {
  type Params = Params;
}

impl<'a> Command<'a> for FfLogsCommand {
  fn run(&self, _: &Context, _: &Message, params: &[&str]) -> CommandResult<'a> {
    let params = self.params_then("fflogs", params, |a| a.setting(::structopt::clap::AppSettings::ArgRequiredElseHelp))?;
    let server = params.server;
    let region = match FfLogsCommand::region(&server) {
      Some(r) => r,
      None => return Err("Invalid server.".into())
    };
    let name = format!("{} {}", params.first_name, params.last_name);

    let parses = self.fflogs.parses(
      &name,
      &server,
      region,
      |x| x.metric(Metric::Dps)
    );

    let parses = match parses {
      Ok(r) => r,
      Err(fflogs::errors::Error::FfLogs(fflogs::net::FfLogsError { status, error })) => {
        return Err(format!("FF Logs didn't like that ({}): {}", status, error).into());
      },
      Err(e) => return Err(e).chain_err(|| "could not query fflogs")?
    };

    if parses.is_empty() {
      return Ok("No parses found.".into());
    }

    let first_spec = match parses[0].specs.get(0) {
      Some(s) => s,
      None => return Err("Somehow there was no first spec.".into())
    };
    let first_data = match first_spec.data.get(0) {
      Some(d) => d,
      None => return Err("Somehow there was no first data.".into())
    };

    let job = params.job.or_else(|| Job::from_str(&first_spec.spec).ok()).unwrap().to_string();
    let lower_job = job.to_lowercase().replace(" ", "");
    let name = &first_data.character_name;
    let id = first_data.character_id;

    let mut embed = CreateEmbed::default();

    embed = embed
      .title(&name)
      .url(format!("https://www.fflogs.com/character/id/{}", id))
      .field("Job", &job, true)
      .field("Server", &server, true);

    for parse in &parses {
      let spec = match parse.specs.iter().filter(|s| s.spec.to_lowercase() == lower_job).next() {
        Some(s) => s,
        None => continue
      };
      let data = match spec.data.iter().max_by(|a, b| a.historical_percent.partial_cmp(&b.historical_percent).unwrap_or(Ordering::Less)) {
        Some(d) => d,
        None => continue
      };

      let url = format!("https://www.fflogs.com/reports/{}#fight={}", data.report_code, data.report_fight);

      let string = format!("[Link]({url}) – {dps:.2} DPS – {perc:.2} percentile out of {total_parses} parses",
        url = url,
        dps = data.persecondamount,
        perc = data.historical_percent,
        total_parses = data.historical_count
      );

      embed = embed.field(&parse.name, string, false);
    }
    Ok(CommandSuccess::default().message(|_| embed))
  }
}

impl FfLogsCommand {
  fn region(server: &str) -> Option<ServerRegion> {
    match server.to_lowercase().as_str() {
      "adamantoise" | "balmung" | "cactuar" | "coeurl" | "faerie" | "gilgamesh" | "goblin" | "jenova" | "mateus" | "midgardsormr" | "sargatanas" | "siren" | "zalera"
      | "behemoth" | "brynhildr" | "diabolos" | "excalibur" | "exodus" | "famfrit" | "hyperion" | "lamia" | "leviathan" | "malboro" | "ultros"
        => Some(ServerRegion::NorthAmerica),
      "aegis" | "atomos" | "carbuncle" | "garuda" | "gungnir" | "kujata" | "ramuh" | "tonberry" | "typhon" | "unicorn"
      | "alexander" | "bahamut" | "durandal" | "fenrir" | "ifrit" | "ridill" | "tiamat" | "ultima" | "valefor" | "yojimbo" | "zeromus"
      | "anima" | "asura" | "belias" | "chocobo" | "hades" | "ixion" | "mandragora" | "masamune" | "pandaemonium" | "shinryu" | "titan"
        => Some(ServerRegion::Japan),
      "cerberus" | "lich" | "louisoix" | "moogle" | "odin" | "omega" | "phoenix" | "ragnarok" | "shiva" | "zodiark"
        => Some(ServerRegion::Europe),
      _ => None
    }
  }
}

#[derive(Debug)]
enum Job {
  Astrologian,
  Bard,
  BlackMage,
  DarkKnight,
  Dragoon,
  Machinist,
  Monk,
  Ninja,
  Paladin,
  RedMage,
  Samurai,
  Scholar,
  Summoner,
  Warrior,
  WhiteMage
}

impl FromStr for Job {
  type Err = String;

  fn from_str(s: &str) -> ::std::result::Result<Self, Self::Err> {
    let job = match s.to_lowercase().as_str() {
      "astrologian" | "astro" | "ast" => Job::Astrologian,
      "bard" | "brd" => Job::Bard,
      "blackmage" | "black mage" | "blm" => Job::BlackMage,
      "darkknight" | "dark knight" | "drk" => Job::DarkKnight,
      "dragoon" | "lolgoon" | "loldrg" | "drg" => Job::Dragoon,
      "machinist" | "mch" => Job::Machinist,
      "monk" | "mnk" => Job::Monk,
      "ninja" | "nin" => Job::Ninja,
      "paladin" | "pld" => Job::Paladin,
      "redmage" | "red mage" | "rdm" => Job::RedMage,
      "samurai" | "sam" => Job::Samurai,
      "scholar" | "sch" => Job::Scholar,
      "summoner" | "smn" => Job::Summoner,
      "warrior" | "war" | "best" => Job::Warrior,
      "whitemage" | "white mage" | "whm" => Job::WhiteMage,
      x => return Err(format!("Unknown job: {}", x))
    };
    Ok(job)
  }
}

impl ToString for Job {
  fn to_string(&self) -> String {
    match *self {
      Job::Astrologian => "Astrologian",
      Job::Bard => "Bard",
      Job::BlackMage => "Black Mage",
      Job::DarkKnight => "Dark Knight",
      Job::Dragoon => "Dragoon",
      Job::Machinist => "Machinist",
      Job::Monk => "Monk",
      Job::Ninja => "Ninja",
      Job::Paladin => "Paladin",
      Job::RedMage => "Red Mage",
      Job::Samurai => "Samurai",
      Job::Scholar => "Scholar",
      Job::Summoner => "Summoner",
      Job::Warrior => "Warrior",
      Job::WhiteMage => "White Mage",
    }.to_string()
  }
}
<|endoftext|>


<commit_before>//! Definitions of types and traits related to packet filtering
//!
//! Two different parameters class are going to be mentioned in this module:
//!
//! - Generic parameter: ether type, protocol id, ...
//! - Specific parameter: hardware address, ip address, ...

use core::marker::PhantomData;

use sync::Arc;

use boxed::Box;
use btree_map::BTreeMap;

use net::Packet;

use net::defs::Rule;

use net::conn::MultiConn;

/// Callbacks used by `SpecificFilter`.
///
/// **FIXME**: I feel like this is somehow hacky
pub trait SpecificCallbacks<T: Ord + Clone> {
    /// Determine if a rule has an upper protocol component.
    ///
    /// For example if you implement a network protocol this will return `true`
    /// if the rule has a transport layer component.
    fn has_upper_filter(rule: &Rule) -> bool;

    /// Create a new generic filter that filters the generic parameter `param`
    /// of type T.
    fn filter_from_generic_parameter(param: T) -> Option<Box<GenericFilterTrait>>;

    /// Set the component of the `rule` implemented by this protocol based on
    /// information contained in a packet.
    fn set_layer_rule(rule: &mut Rule, pkt: &Packet);
}

/// Extract generic/specific parameters of type T from a packet or a rule.
///
/// This is used by filters to extract the generic/specific parameter and use
/// it to route the packet to the appropriate connexion (if such connexion
/// exists).
pub trait Extractor<T> {
    /// Extract the parameter from a rule.
    ///
    /// This will return `None` if the method fails to extract the parameter.
    fn from_rule(rule: &Rule) -> Option<T>;

    /// Extract the parameter from a Packet.
    ///
    /// This will return `None` if the method fails to extract the parameter.
    fn from_packet(pkt: &Packet) -> Option<T>;
}

/// Sanitize an incoming packet.
///
/// Packets that arrive from the network need to be checked by every used
/// protocols for correctness (i.e. checksum, ...).
///
/// Some properties or extra information might need to be added to the packet
/// as well.
///
/// This trait let protocols do this.
pub trait PacketSanitizer {
    /// This method is called when a filter needs to sanitize a packet.
    ///
    /// If this method returns an `Err` the packet will be dropped.
    fn sanitize(pkt: &mut Packet) -> Result<(), ()>;
}

/// Trait implemented by generic filters (i.e. filters based on generic
/// parameters)
///
/// Generic filters allow protocols to route packets to the appropriate
/// connexion based on a generic parameter.
///
/// It may drop the packet if no matching connexion exists or if the packet is
/// invalid (failed checksum, ...).
pub trait GenericFilterTrait {
    /// Insert a new multi connexion to the filter based on a rule.
    fn insert_multi(&mut self, conn: Arc<MultiConn>,
                    rule: &Rule) -> Result<(), ()>;

    /// Filter and route an incoming packet to a connexion (uni or multi).
    fn rx(&self, pkt: Packet);

    /// Filter and route an incoming packet to a multi connexion
    fn rx_multi(&self, pkt: Packet, rule: Rule);
}

/// Trait implemented by specific filters (i.e. filters based on specific
/// parameters)
///
/// Specific filters allow protocols to route packets to the appropriate
/// connexion based on a specific parameter
///
/// It may drop the packet if no matching connexion exists.
pub trait SpecificFilterTrait<T> {
    /// Create a new specific filter.
    ///
    /// The generic parameter taken as parameter is used to associate a
    /// specific filter with a generic one.
    fn new(generic_param: T) -> Self;

    /// Insert a new multi connexion to the filter based on a rule.
    fn insert_multi(&mut self, conn: Arc<MultiConn>,
                    rule: &Rule) -> Result<(), ()>;

    /// Filter and route an incoming packet to a connexion (uni or multi).
    fn rx(&self, pkt: Packet);

    /// Filter and route an incoming packet to a multi connexion
    fn rx_multi(&self, pkt: Packet, rule: Rule);
}

/// Filter packets on a generic parameter
///
/// This is a generic class that can be used by protocols to implement a
/// generic filter.
///
/// Here is the explanation of types taken as parameters:
///
/// - `T`: type of the generic parameter.
/// - `U`: type of the filter used to filter on specific parameter.
/// - `E`: type that allows extraction of the generic parameter from rules and
///        packets.
/// - `S`: type that allows the protocol to sanitize incoming packets.
pub struct GenericFilter<T, U, E, S> where T: Ord + Clone,
                                           U: SpecificFilterTrait<T>,
                                           E: Extractor<T>,
                                           S: PacketSanitizer {
    filters: BTreeMap<T, U>,
    _extractor: PhantomData<E>,
    _sanitizer: PhantomData<S>,
}

impl<T, U, E, S> GenericFilter<T, U, E, S> where T: Ord + Clone,
                                                 U: SpecificFilterTrait<T>,
                                                 E: Extractor<T>,
                                                 S: PacketSanitizer {
    /// Create a new generic filter
    pub fn new() -> Self {
        GenericFilter {
            filters: BTreeMap::new(),
            _extractor: PhantomData,
            _sanitizer: PhantomData,
        }
    }
}

impl<T, U, E, S> GenericFilterTrait for GenericFilter<T, U, E, S>
                                    where T: Ord + Clone,
                                          U: SpecificFilterTrait<T>,
                                          E: Extractor<T>,
                                          S: PacketSanitizer {
    fn insert_multi(&mut self, conn: Arc<MultiConn>,
                    rule: &Rule) -> Result<(), ()> {
        // Extract the generic parameter of the rule. If none exists then it's
        // an error and the connexion cannot be added
        if let Some(key) = E::from_rule(rule) {
            // Check if a filter already exists, if none add one
            if !self.filters.contains_key(&key) {
                self.filters.insert(key.clone(), U::new(key.clone()));
            }

            // Insert the connexion in the specific filter
            self.filters.get_mut(&key).unwrap().insert_multi(conn, rule)
        } else {
            Err(())
        }
    }

    fn rx(&self, mut pkt: Packet) {
        // Sanitize the packet
        if S::sanitize(&mut pkt).is_ok() {
            // Get the generic parameter
            if let Some(key) = E::from_packet(&pkt) {
                // Pass the packet to the specific filter if such filter exists
                if let Some(filter) = self.filters.get(&key) {
                    filter.rx(pkt);
                }
            }
        }
    }

    fn rx_multi(&self, mut pkt: Packet, rule: Rule) {
        // This basically behaves like rx() except that it give the packet
        // to the multi in the filter
        if S::sanitize(&mut pkt).is_ok() {
            if let Some(key) = E::from_packet(&pkt) {
                if let Some(filter) = self.filters.get(&key) {
                    filter.rx_multi(pkt, rule);
                }
            }
        }
    }
}
<commit_msg>conn/filter: Add definition of type SpecificFilter<commit_after>//! Definitions of types and traits related to packet filtering
//!
//! Two different parameters class are going to be mentioned in this module:
//!
//! - Generic parameter: ether type, protocol id, ...
//! - Specific parameter: hardware address, ip address, ...

use core::marker::PhantomData;

use sync::Arc;

use boxed::Box;
use btree_map::BTreeMap;

use net::Packet;

use net::defs::Rule;

use net::conn::{UniConn, MultiConn};

enum ConnChoice<C, F: GenericFilterTrait + ?Sized> {
    Conn(Arc<C>),
    Filter(Box<F>),
}

/// Callbacks used by `SpecificFilter`.
///
/// **FIXME**: I feel like this is somehow hacky
pub trait SpecificCallbacks<T: Ord + Clone> {
    /// Determine if a rule has an upper protocol component.
    ///
    /// For example if you implement a network protocol this will return `true`
    /// if the rule has a transport layer component.
    fn has_upper_filter(rule: &Rule) -> bool;

    /// Create a new generic filter that filters the generic parameter `param`
    /// of type T.
    fn filter_from_generic_parameter(param: T) -> Option<Box<GenericFilterTrait>>;

    /// Set the component of the `rule` implemented by this protocol based on
    /// information contained in a packet.
    fn set_layer_rule(rule: &mut Rule, pkt: &Packet);
}

/// Extract generic/specific parameters of type T from a packet or a rule.
///
/// This is used by filters to extract the generic/specific parameter and use
/// it to route the packet to the appropriate connexion (if such connexion
/// exists).
pub trait Extractor<T> {
    /// Extract the parameter from a rule.
    ///
    /// This will return `None` if the method fails to extract the parameter.
    fn from_rule(rule: &Rule) -> Option<T>;

    /// Extract the parameter from a Packet.
    ///
    /// This will return `None` if the method fails to extract the parameter.
    fn from_packet(pkt: &Packet) -> Option<T>;
}

/// Sanitize an incoming packet.
///
/// Packets that arrive from the network need to be checked by every used
/// protocols for correctness (i.e. checksum, ...).
///
/// Some properties or extra information might need to be added to the packet
/// as well.
///
/// This trait let protocols do this.
pub trait PacketSanitizer {
    /// This method is called when a filter needs to sanitize a packet.
    ///
    /// If this method returns an `Err` the packet will be dropped.
    fn sanitize(pkt: &mut Packet) -> Result<(), ()>;
}

/// Trait implemented by generic filters (i.e. filters based on generic
/// parameters)
///
/// Generic filters allow protocols to route packets to the appropriate
/// connexion based on a generic parameter.
///
/// It may drop the packet if no matching connexion exists or if the packet is
/// invalid (failed checksum, ...).
pub trait GenericFilterTrait {
    /// Insert a new multi connexion to the filter based on a rule.
    fn insert_multi(&mut self, conn: Arc<MultiConn>,
                    rule: &Rule) -> Result<(), ()>;

    /// Filter and route an incoming packet to a connexion (uni or multi).
    fn rx(&self, pkt: Packet);

    /// Filter and route an incoming packet to a multi connexion
    fn rx_multi(&self, pkt: Packet, rule: Rule);
}

/// Trait implemented by specific filters (i.e. filters based on specific
/// parameters)
///
/// Specific filters allow protocols to route packets to the appropriate
/// connexion based on a specific parameter
///
/// It may drop the packet if no matching connexion exists.
pub trait SpecificFilterTrait<T> {
    /// Create a new specific filter.
    ///
    /// The generic parameter taken as parameter is used to associate a
    /// specific filter with a generic one.
    fn new(generic_param: T) -> Self;

    /// Insert a new multi connexion to the filter based on a rule.
    fn insert_multi(&mut self, conn: Arc<MultiConn>,
                    rule: &Rule) -> Result<(), ()>;

    /// Filter and route an incoming packet to a connexion (uni or multi).
    fn rx(&self, pkt: Packet);

    /// Filter and route an incoming packet to a multi connexion
    fn rx_multi(&self, pkt: Packet, rule: Rule);
}

/// Filter packets on a generic parameter
///
/// This is a generic class that can be used by protocols to implement a
/// generic filter.
///
/// Here is the explanation of types taken as parameters:
///
/// - `T`: type of the generic parameter.
/// - `U`: type of the filter used to filter on specific parameter.
/// - `E`: type that allows extraction of the generic parameter from rules and
///        packets.
/// - `S`: type that allows the protocol to sanitize incoming packets.
pub struct GenericFilter<T, U, E, S> where T: Ord + Clone,
                                           U: SpecificFilterTrait<T>,
                                           E: Extractor<T>,
                                           S: PacketSanitizer {
    filters: BTreeMap<T, U>,
    _extractor: PhantomData<E>,
    _sanitizer: PhantomData<S>,
}

impl<T, U, E, S> GenericFilter<T, U, E, S> where T: Ord + Clone,
                                                 U: SpecificFilterTrait<T>,
                                                 E: Extractor<T>,
                                                 S: PacketSanitizer {
    /// Create a new generic filter
    pub fn new() -> Self {
        GenericFilter {
            filters: BTreeMap::new(),
            _extractor: PhantomData,
            _sanitizer: PhantomData,
        }
    }
}

impl<T, U, E, S> GenericFilterTrait for GenericFilter<T, U, E, S>
                                    where T: Ord + Clone,
                                          U: SpecificFilterTrait<T>,
                                          E: Extractor<T>,
                                          S: PacketSanitizer {
    fn insert_multi(&mut self, conn: Arc<MultiConn>,
                    rule: &Rule) -> Result<(), ()> {
        // Extract the generic parameter of the rule. If none exists then it's
        // an error and the connexion cannot be added
        if let Some(key) = E::from_rule(rule) {
            // Check if a filter already exists, if none add one
            if !self.filters.contains_key(&key) {
                self.filters.insert(key.clone(), U::new(key.clone()));
            }

            // Insert the connexion in the specific filter
            self.filters.get_mut(&key).unwrap().insert_multi(conn, rule)
        } else {
            Err(())
        }
    }

    fn rx(&self, mut pkt: Packet) {
        // Sanitize the packet
        if S::sanitize(&mut pkt).is_ok() {
            // Get the generic parameter
            if let Some(key) = E::from_packet(&pkt) {
                // Pass the packet to the specific filter if such filter exists
                if let Some(filter) = self.filters.get(&key) {
                    filter.rx(pkt);
                }
            }
        }
    }

    fn rx_multi(&self, mut pkt: Packet, rule: Rule) {
        // This basically behaves like rx() except that it give the packet
        // to the multi in the filter
        if S::sanitize(&mut pkt).is_ok() {
            if let Some(key) = E::from_packet(&pkt) {
                if let Some(filter) = self.filters.get(&key) {
                    filter.rx_multi(pkt, rule);
                }
            }
        }
    }
}

/// Filter packets on a specific parameter
///
/// This is a generic class that can be used by protocols to implement a
/// specific filter.
///
/// Here is the explanation of types taken as parameters:
///
/// - `T`: type of the specific parameter.
/// - `P`: type of the generic parameter.
/// - `E`: type that allows extraction of the specific parameter from rules and
/// packets.
/// - `F`: type that provides various callbacks to be used to route the packet
/// to the appropriate connexion.
pub struct SpecificFilter<T, P, E, F> where T: Ord + Clone,
                                            P: Ord + Clone,
                                            E: Extractor<T>,
                                            F: SpecificCallbacks<P> {
    generic_param: P,
    filters: BTreeMap<T, ConnChoice<UniConn, GenericFilterTrait>>,
    multi: Option<ConnChoice<MultiConn, GenericFilterTrait>>,
    _extractor: PhantomData<E>,
    _factory: PhantomData<F>,
}

impl<T, P, E, F> SpecificFilterTrait<P> for SpecificFilter<T, P, E, F>
                                     where T: Ord + Clone,
                                           P: Ord + Clone,
                                           E: Extractor<T>,
                                           F: SpecificCallbacks<P> {
    fn new(generic_param: P) -> Self {
        SpecificFilter {
            generic_param: generic_param,
            filters: BTreeMap::new(),
            multi: None,
            _extractor: PhantomData,
            _factory: PhantomData,
        }
    }

    fn insert_multi(&mut self, conn: Arc<MultiConn>,
                    rule: &Rule) -> Result<(), ()> {
        // If the rule has an upper layer (for example we are a network
        // protocol and the rule has a transport component), we need to create
        // a new upper layer filter and insert the connexion inside it
        if F::has_upper_filter(rule) {
            if let None = self.multi {
                // Create the upper filter
                let filter = {
                    F::filter_from_generic_parameter(self.generic_param.clone())
                };

                if let Some(f) = filter {
                    self.multi = Some(ConnChoice::Filter(f));
                }
            }

            // Insert the connexion in the upper filter
            match self.multi {
                None => Err(()),
                Some(ref mut multi) => {
                    if let ConnChoice::Filter(ref mut filter) = *multi {
                        filter.insert_multi(conn, rule)
                    } else {
                        Err(())
                    }
                }
            }
        } else {
            // If there is no upper layer try to insert the multi in our filter
            if let None = self.multi {
                self.multi = Some(ConnChoice::Conn(conn));
                Ok(())
            } else {
                // A multi already exist, cannot insert this new one
                Err(())
            }
        }
    }

    fn rx(&self, pkt: Packet) {
        // Extract the specific parameter from the packet
        if let Some(key) = E::from_packet(&pkt) {
            // Do we have a uni connexion to handle that packet
            if let Some(_) = self.filters.get(&key) {
                unimplemented!();
            } else if let Some(ref multi) = self.multi {
                // If we don't have a uni connexion to handle the packet but
                // a multi exists, receive the packet on this multi.
                let mut rule = Rule {
                    eth_rule: None,
                    net_rule: None,
                    tspt_rule: None,
                };

                // Set the rule parameters (both generic and specific, for
                // example ether type and hw source address). This will be used
                // by connexion user to know who sent that packet and how to
                // respond.
                F::set_layer_rule(&mut rule, &pkt);

                // Receive the packet (pass it to an upper filter if one exists,
                // or push the packet to the connexion otherwise).
                match *multi {
                    ConnChoice::Conn(ref conn) => conn.rx(pkt, rule),
                    ConnChoice::Filter(ref filter) => {
                        filter.rx_multi(pkt, rule)
                    }
                }
            }
        }
    }

    fn rx_multi(&self, pkt: Packet, mut rule: Rule) {
        if let Some(ref multi) = self.multi {
            // See comment in Self::rx()
            F::set_layer_rule(&mut rule, &pkt);

            match *multi {
                ConnChoice::Conn(ref conn) => conn.rx(pkt, rule),
                ConnChoice::Filter(ref filter) => filter.rx_multi(pkt, rule),
            }
        }
    }
}
<|endoftext|>