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
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
//! Game cheats. These are what players type in, e.g, `iddqd`

use gameplay::{english, log::debug, GameMission, PlayerCheat, Skill};
use gamestate::Game;
use gamestate_traits::{
    sdl2::keyboard::{Keycode, Scancode},
    GameTraits,
};
use sound_traits::MusTrack;

pub struct Cheats {
    /// `iddqd`: Invulnerable to all (except massive end-of-level damage)
    pub god: Cheat,
    /// `idmus##`: Select music to play, ## is 01-nn
    pub mus: Cheat,
    /// `idkfa: Give all ammo and keys
    pub ammo: Cheat,
    /// `idfa`: Give only ammo
    pub ammonokey: Cheat,
    /// `idspispopd`: no-clip, Doom 1 version
    pub noclip: Cheat,
    /// `idclip`: no-clip, Doom 2 version
    pub commercial_noclip: Cheat,
    /// Give a powerup:
    /// - `idbeholdv`: Invulnerability
    /// - `idbeholds`: Go beserk
    /// - `idbeholdi`: Pertial invisibility
    /// - `idbeholdr`: Radiation suit
    /// - `idbeholda`: Area map
    /// - `idbeholdl`: Light amp visor
    pub powerup: [Cheat; 7],
    /// `idchoppers`: Chainsaw and invulnerability
    pub choppers: Cheat,
    /// `idclev##`: Change level, ## is E#M# or MAP## (01-32)
    pub clev: Cheat,
    /// `idmypos`: Coords and compass direction
    pub mypos: Cheat,
}

impl Cheats {
    pub fn new() -> Self {
        Self {
            god: Cheat::new("iddqd", 0),
            mus: Cheat::new("idmus", 2),
            ammo: Cheat::new("idkfa", 0),
            ammonokey: Cheat::new("idfa", 0),
            noclip: Cheat::new("idspispopd", 0),
            commercial_noclip: Cheat::new("idclip", 0),
            powerup: [
                Cheat::new("idbeholdv", 0),
                Cheat::new("idbeholds", 0),
                Cheat::new("idbeholdi", 0),
                Cheat::new("idbeholdr", 0),
                Cheat::new("idbeholda", 0),
                Cheat::new("idbeholdl", 0),
                Cheat::new("idbehold", 0),
            ],
            choppers: Cheat::new("idchoppers", 0),
            clev: Cheat::new("idclev", 2),
            mypos: Cheat::new("idmypos", 0),
        }
    }

    /// Cheats skip the ticcmd system and directly affect a game-exe
    pub fn check_input(&mut self, sc: Scancode, game: &mut Game) {
        let key = if let Some(key) = Keycode::from_scancode(sc) {
            key as u8 as char
        } else {
            return;
        };

        if !game.is_netgame() && !(game.game_skill() == Skill::Nightmare) {
            if self.god.check(key) {
                let player = &mut game.players[game.consoleplayer];
                player.cheats ^= PlayerCheat::Godmode as u32;

                if player.cheats & PlayerCheat::Godmode as u32 != 0 {
                    if let Some(mobj) = player.mobj_mut() {
                        mobj.health = 100;
                    }
                    player.status.health = 100;
                    player.message = Some(english::STSTR_DQDON);
                } else {
                    player.message = Some(english::STSTR_DQDOFF);
                }
            } else if self.ammonokey.check(key) {
                let player = &mut game.players[game.consoleplayer];
                player.status.armorpoints = 200;
                player.status.armortype = 2;

                for w in player.status.weaponowned.iter_mut() {
                    *w = true;
                }
                for (i, a) in player.status.ammo.iter_mut().enumerate() {
                    *a = player.status.maxammo[i];
                }
                player.message = Some(english::STSTR_FAADDED);
            } else if self.ammo.check(key) {
                let player = &mut game.players[game.consoleplayer];
                player.status.armorpoints = 200;
                player.status.armortype = 2;

                for w in player.status.weaponowned.iter_mut() {
                    *w = true;
                }
                for (i, a) in player.status.ammo.iter_mut().enumerate() {
                    *a = player.status.maxammo[i];
                }
                for k in player.status.cards.iter_mut() {
                    *k = true;
                }
                player.message = Some(english::STSTR_KFAADDED);
            } else if (game.game_mission() == GameMission::Doom && self.noclip.check(key))
                || (game.game_mission() != GameMission::Doom && self.commercial_noclip.check(key))
            {
                let player = &mut game.players[game.consoleplayer];
                player.cheats ^= PlayerCheat::Noclip as u32;
                if player.cheats & PlayerCheat::Noclip as u32 != 0 {
                    player.message = Some(english::STSTR_NCON);
                } else {
                    player.message = Some(english::STSTR_NCOFF);
                }
            } else if self.mus.check(key) {
                debug!(
                    "MUS{}{}",
                    self.mus.parameter_buf[0], self.mus.parameter_buf[1]
                );
                let s = format!("{}{}", self.mus.parameter_buf[0], self.mus.parameter_buf[1]);
                if let Ok(s) = s.as_str().parse::<u8>() {
                    let s = MusTrack::from(s);
                    game.change_music(s);
                    game.players[game.consoleplayer].message = Some(english::STSTR_MUS);
                } else {
                    game.players[game.consoleplayer].message = Some(english::STSTR_NOMUS);
                }
            } else if self.mypos.check(key) {
                debug!("MYPOS",);
                let player = &mut game.players[game.consoleplayer];
                if let Some(mobj) = player.mobj() {
                    println!("MYPOS: X:{} Y:{}", mobj.xy.x as i32, mobj.xy.y as i32);
                }
            }
        }
    }
}

pub struct Cheat {
    /// The sequence of chars to accept
    sequence: &'static str,
    /// `char` read so far
    chars_read: usize,
    /// How many parameter chars there can be
    parameter_chars: usize,
    /// Parameter chars read so far
    parameter_chars_read: usize,
    /// Input buffer for parameters
    parameter_buf: [char; 5],
}

impl Cheat {
    pub const fn new(seq: &'static str, parameters: usize) -> Self {
        Self {
            sequence: seq,
            chars_read: 0,
            parameter_chars: parameters,
            parameter_chars_read: 0,
            parameter_buf: [' '; 5],
        }
    }

    /// Doom function name `cht_CheckCheat`
    pub fn check(&mut self, key: char) -> bool {
        if self.chars_read < self.sequence.len() {
            if key as u8 == self.sequence.as_bytes()[self.chars_read] {
                self.chars_read += 1;
            } else {
                self.chars_read = 0;
            }

            self.parameter_chars_read = 0;
        } else if self.parameter_chars_read < self.parameter_chars {
            self.parameter_buf[self.parameter_chars_read] = key;
            self.parameter_chars_read += 1;
        }

        if self.chars_read >= self.sequence.len()
            && self.parameter_chars_read >= self.parameter_chars
        {
            self.chars_read = 0;
            self.parameter_chars_read = 0;
            return true;
        }

        false
    }
}