1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
use super::*;

pub use stage::*;
mod stage;

pub use states::*;
mod states;

/// The state of the player controller.
#[derive(Clone, HasSchema, Default)]
pub struct PlayerState {
    /// The ID for the current state.
    pub current: Ustr,
    /// The number of frames that this state has been active.
    pub age: u64,
    /// The ID of the state that the player was in in the last frame.
    pub last: Ustr,
}
impl PlayerState {
    /// Adds a system to the appropriate stage in a [`Session`] for a player state transition.
    pub fn add_player_state_transition_system<Args>(
        session: &mut Session,
        system: impl IntoSystem<Args, (), (), Sys = StaticSystem<(), ()>>,
    ) {
        session.stages.add_system_to_stage(PlayerStateStage, system);
    }

    /// Adds a system to the appropriate stage in a [`Session`] for a player state update.
    pub fn add_player_state_update_system<Args>(
        session: &mut Session,
        system: impl IntoSystem<Args, (), (), Sys = StaticSystem<(), ()>>,
    ) {
        session
            .stages
            .add_system_to_stage(CoreStage::PreUpdate, system);
    }
}

pub fn plugin(session: &mut Session) {
    // Add the player state stage
    session
        .stages
        .insert_stage_before(CoreStage::PreUpdate, PlayerStateStageImpl::new());

    session
        .stages
        .add_system_to_stage(CoreStage::Last, update_player_state_age);

    crouch::install(session);
    dead::install(session);
    default::install(session);
    drive_jellyfish::install(session);
    idle::install(session);
    incapacitated::install(session);
    ragdoll::install(session);
    midair::install(session);
    walk::install(session);
}

fn update_player_state_age(entities: Res<Entities>, mut player_states: CompMut<PlayerState>) {
    for (_ent, state) in entities.iter_with(&mut player_states) {
        state.age = state.age.saturating_add(1);
    }
}

fn use_drop_or_grab_items_system(id: Ustr) -> StaticSystem<(), ()> {
    (move |entities: Res<Entities>,
           player_inputs: Res<MatchInputs>,
           player_indexes: Comp<PlayerIdx>,
           player_states: Comp<PlayerState>,
           assets: Res<AssetServer>,
           items: Comp<Item>,
           collision_world: CollisionWorld,
           mut inventories: CompMut<Inventory>,
           mut audio_center: ResMut<AudioCenter>,
           mut commands: Commands| {
        // Collect a list of items that are being held by players
        let held_items = entities
            .iter_with(&inventories)
            .filter_map(|(_ent, inventory)| inventory.0)
            .collect::<Vec<_>>();

        for (player_ent, (player_state, player_idx, inventory)) in
            entities.iter_with((&player_states, &player_indexes, &mut inventories))
        {
            if player_state.current != id {
                continue;
            }
            let meta_handle = player_inputs.players[player_idx.0 as usize].selected_player;
            let meta = assets.get(meta_handle);

            let control = &player_inputs.players[player_idx.0 as usize].control;
            // If we are grabbing
            if control.grab_just_pressed {
                if inventory.is_none() {
                    // If we don't have an item
                    let colliders = collision_world
                        // Get all things colliding with the player
                        .actor_collisions(player_ent)
                        .into_iter()
                        // Filter out anything not an item
                        .filter(|ent| items.contains(*ent))
                        // TODO: Use the ItemGrabbed tag for this detection after fixing the ItemGrabbed handling
                        // Filter out any items held by other players
                        .filter(|ent| !held_items.contains(ent))
                        .collect::<Vec<_>>();

                    // Grab the first item we are touching
                    if let Some(item) = colliders.first() {
                        // Add the item to the player inventory
                        commands.add(PlayerCommand::set_inventory(player_ent, Some(*item)));

                        // Play grab sound
                        audio_center.play_sound(meta.sounds.grab, meta.sounds.grab_volume);
                    }

                // If we are already carrying an item
                } else {
                    // Drop it
                    commands.add(PlayerCommand::set_inventory(player_ent, None));

                    // Play drop sound
                    audio_center.play_sound(meta.sounds.drop, meta.sounds.drop_volume);
                }
            }

            // If we are using an item
            if control.shoot_pressed && inventory.is_some() {
                commands.add(PlayerCommand::use_item(player_ent));
            }
        }
    })
    .system()
}