1 /* Command.cpp
2 Copyright (c) 2015 by Michael Zahniser
3
4 Endless Sky is free software: you can redistribute it and/or modify it under the
5 terms of the GNU General Public License as published by the Free Software
6 Foundation, either version 3 of the License, or (at your option) any later version.
7
8 Endless Sky is distributed in the hope that it will be useful, but WITHOUT ANY
9 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
10 PARTICULAR PURPOSE. See the GNU General Public License for more details.
11 */
12
13 #include "Command.h"
14
15 #include "DataFile.h"
16 #include "DataNode.h"
17 #include "DataWriter.h"
18 #include "text/Format.h"
19
20 #include <SDL2/SDL.h>
21
22 #include <algorithm>
23 #include <cmath>
24 #include <map>
25
26 using namespace std;
27
28 namespace {
29 // These lookup tables make it possible to map a command to its description,
30 // the name of the key it is mapped to, or the SDL keycode it is mapped to.
31 map<Command, string> description;
32 map<Command, string> keyName;
33 map<int, Command> commandForKeycode;
34 map<Command, int> keycodeForCommand;
35 // Keep track of any keycodes that are mapped to multiple commands, in order
36 // to display a warning to the player.
37 map<int, int> keycodeCount;
38 }
39
40 // Command enumeration, including the descriptive strings that are used for the
41 // commands both in the preferences panel and in the saved key settings.
42 const Command Command::NONE(0, "");
43 const Command Command::MENU(1uL << 0, "Show main menu");
44 const Command Command::FORWARD(1uL << 1, "Forward thrust");
45 const Command Command::LEFT(1uL << 2, "Turn left");
46 const Command Command::RIGHT(1uL << 3, "Turn right");
47 const Command Command::BACK(1uL << 4, "Reverse");
48 const Command Command::PRIMARY(1uL << 5, "Fire primary weapon");
49 const Command Command::SECONDARY(1uL << 6, "Fire secondary weapon");
50 const Command Command::SELECT(1uL << 7, "Select secondary weapon");
51 const Command Command::LAND(1uL << 8, "Land on planet / station");
52 const Command Command::BOARD(1uL << 9, "Board selected ship");
53 const Command Command::HAIL(1uL << 10, "Talk to selected ship");
54 const Command Command::SCAN(1uL << 11, "Scan selected ship");
55 const Command Command::JUMP(1uL << 12, "Initiate hyperspace jump");
56 const Command Command::TARGET(1uL << 13, "Select next ship");
57 const Command Command::NEAREST(1uL << 14, "Select nearest hostile ship");
58 const Command Command::DEPLOY(1uL << 15, "Deploy / recall fighters");
59 const Command Command::AFTERBURNER(1uL << 16, "Fire afterburner");
60 const Command Command::CLOAK(1uL << 17, "Toggle cloaking device");
61 const Command Command::MAP(1uL << 18, "View star map");
62 const Command Command::INFO(1uL << 19, "View player info");
63 const Command Command::FULLSCREEN(1uL << 20, "Toggle fullscreen");
64 const Command Command::FASTFORWARD(1uL << 21, "Toggle fast-forward");
65 const Command Command::FIGHT(1uL << 22, "Fleet: Fight my target");
66 const Command Command::GATHER(1uL << 23, "Fleet: Gather around me");
67 const Command Command::HOLD(1uL << 24, "Fleet: Hold position");
68 const Command Command::AMMO(1uL << 25, "Fleet: Toggle ammo usage");
69 const Command Command::WAIT(1uL << 26, "");
70 const Command Command::STOP(1ul << 27, "");
71 const Command Command::SHIFT(1uL << 28, "");
72
73
74
75 // In the given text, replace any instances of command names (in angle brackets)
76 // with key names (in quotes).
ReplaceNamesWithKeys(const string & text)77 string Command::ReplaceNamesWithKeys(const string &text)
78 {
79 map<string, string> subs;
80 for(const auto &it : description)
81 subs['<' + it.second + '>'] = '"' + keyName[it.first] + '"';
82
83 return Format::Replace(text, subs);
84 }
85
86
87
88 // Create a command representing whatever is mapped to the given key code.
Command(int keycode)89 Command::Command(int keycode)
90 {
91 auto it = commandForKeycode.find(keycode);
92 if(it != commandForKeycode.end())
93 *this = it->second;
94 }
95
96
97
98 // Read the current keyboard state.
ReadKeyboard()99 void Command::ReadKeyboard()
100 {
101 Clear();
102 const Uint8 *keyDown = SDL_GetKeyboardState(nullptr);
103
104 // Each command can only have one keycode, but misconfigured settings can
105 // temporarily cause one keycode to be used for two commands. Also, more
106 // than one key can be held down at once.
107 for(const auto &it : keycodeForCommand)
108 if(keyDown[SDL_GetScancodeFromKey(it.second)])
109 *this |= it.first;
110
111 // Check whether the `Shift` modifier key was pressed for this step.
112 if(SDL_GetModState() & KMOD_SHIFT)
113 *this |= SHIFT;
114 }
115
116
117
118 // Load the keyboard preferences.
LoadSettings(const string & path)119 void Command::LoadSettings(const string &path)
120 {
121 DataFile file(path);
122
123 // Create a map of command names to Command objects in the enumeration above.
124 map<string, Command> commands;
125 for(const auto &it : description)
126 commands[it.second] = it.first;
127
128 // Each command can only have one keycode, one keycode can be assigned
129 // to multiple commands.
130 for(const DataNode &node : file)
131 {
132 auto it = commands.find(node.Token(0));
133 if(it != commands.end() && node.Size() >= 2)
134 {
135 Command command = it->second;
136 int keycode = node.Value(1);
137 keycodeForCommand[command] = keycode;
138 keyName[command] = SDL_GetKeyName(keycode);
139 }
140 }
141
142 // Regenerate the lookup tables.
143 commandForKeycode.clear();
144 keycodeCount.clear();
145 for(const auto &it : keycodeForCommand)
146 {
147 commandForKeycode[it.second] = it.first;
148 ++keycodeCount[it.second];
149 }
150 }
151
152
153
154 // Save the keyboard preferences.
SaveSettings(const string & path)155 void Command::SaveSettings(const string &path)
156 {
157 DataWriter out(path);
158
159 for(const auto &it : keycodeForCommand)
160 {
161 auto dit = description.find(it.first);
162 if(dit != description.end())
163 out.Write(dit->second, it.second);
164 }
165 }
166
167
168
169 // Set the key that is mapped to the given command.
SetKey(Command command,int keycode)170 void Command::SetKey(Command command, int keycode)
171 {
172 // Always reset *all* the mappings when one is set. That way, if two commands
173 // are mapped to the same key and you change one of them, the other stays mapped.
174 keycodeForCommand[command] = keycode;
175 keyName[command] = SDL_GetKeyName(keycode);
176
177 commandForKeycode.clear();
178 keycodeCount.clear();
179
180 for(const auto &it : keycodeForCommand)
181 {
182 commandForKeycode[it.second] = it.first;
183 ++keycodeCount[it.second];
184 }
185 }
186
187
188
189 // Get the description of this command. If this command is a combination of more
190 // than one command, an empty string is returned.
Description() const191 const string &Command::Description() const
192 {
193 static const string empty;
194 auto it = description.find(*this);
195 return (it == description.end() ? empty : it->second);
196 }
197
198
199
200 // Get the name of the key that is mapped to this command. If this command is
201 // a combination of more than one command, an empty string is returned.
KeyName() const202 const string &Command::KeyName() const
203 {
204 static const string empty;
205 auto it = keyName.find(*this);
206 return (it == keyName.end() ? empty : it->second);
207 }
208
209
210
211 // Check whether this is the only command mapped to the key it is mapped to.
HasConflict() const212 bool Command::HasConflict() const
213 {
214 auto it = keycodeForCommand.find(*this);
215 if(it == keycodeForCommand.end())
216 return false;
217
218 auto cit = keycodeCount.find(it->second);
219 return (cit != keycodeCount.end() && cit->second > 1);
220 }
221
222
223
224 // Load this command from an input file (for testing or scripted missions).
Load(const DataNode & node)225 void Command::Load(const DataNode &node)
226 {
227 for(int i = 1; i < node.Size(); ++i)
228 {
229 static const map<string, Command> lookup = {
230 {"menu", Command::MENU},
231 {"forward", Command::FORWARD},
232 {"left", Command::LEFT},
233 {"right", Command::RIGHT},
234 {"back", Command::BACK},
235 {"primary", Command::PRIMARY},
236 {"secondary", Command::SECONDARY},
237 {"select", Command::SELECT},
238 {"land", Command::LAND},
239 {"board", Command::BOARD},
240 {"hail", Command::HAIL},
241 {"scan", Command::SCAN},
242 {"jump", Command::JUMP},
243 {"target", Command::TARGET},
244 {"nearest", Command::NEAREST},
245 {"deploy", Command::DEPLOY},
246 {"afterburner", Command::AFTERBURNER},
247 {"cloak", Command::CLOAK},
248 {"map", Command::MAP},
249 {"info", Command::INFO},
250 {"fight", Command::FIGHT},
251 {"gather", Command::GATHER},
252 {"hold", Command::HOLD},
253 {"ammo", Command::AMMO}
254 };
255
256 auto it = lookup.find(node.Token(i));
257 if(it != lookup.end())
258 Set(it->second);
259 else
260 node.PrintTrace("Skipping unrecognized command \"" + node.Token(i) + "\":");
261 }
262 }
263
264
265
266 // Reset this to an empty command.
Clear()267 void Command::Clear()
268 {
269 *this = Command();
270 }
271
272
273
274 // Clear any commands that are set in the given command.
Clear(Command command)275 void Command::Clear(Command command)
276 {
277 state &= ~command.state;
278 }
279
280
281
282 // Set any commands that are set in the given command.
Set(Command command)283 void Command::Set(Command command)
284 {
285 state |= command.state;
286 }
287
288
289
290 // Check if any of the given command's bits that are set, are also set here.
Has(Command command) const291 bool Command::Has(Command command) const
292 {
293 return (state & command.state);
294 }
295
296
297
298 // Get the commands that are set in this and in the given command.
And(Command command) const299 Command Command::And(Command command) const
300 {
301 return Command(state & command.state);
302 }
303
304
305
306 // Get the commands that are set in this and not in the given command.
AndNot(Command command) const307 Command Command::AndNot(Command command) const
308 {
309 return Command(state & ~command.state);
310 }
311
312
313
314 // Set the turn direction and amount to a value between -1 and 1.
SetTurn(double amount)315 void Command::SetTurn(double amount)
316 {
317 turn = max(-1., min(1., amount));
318 }
319
320
321
322 // Get the turn amount.
Turn() const323 double Command::Turn() const
324 {
325 return turn;
326 }
327
328
329
330 // Check if this command includes a command to fire the given weapon.
HasFire(int index) const331 bool Command::HasFire(int index) const
332 {
333 if(index < 0 || index >= 32)
334 return false;
335
336 return state & ((1ull << 32) << index);
337 }
338
339
340
341 // Add to this set of commands a command to fire the given weapon.
SetFire(int index)342 void Command::SetFire(int index)
343 {
344 if(index < 0 || index >= 32)
345 return;
346
347 state |= ((1ull << 32) << index);
348 }
349
350
351
352 // Check if any weapons are firing.
IsFiring() const353 bool Command::IsFiring() const
354 {
355 return (state & 0xFFFFFFFF00000000ull);
356 }
357
358
359
360 // Set the turn rate of the turret with the given weapon index. A value of
361 // -1 or 1 means to turn at the full speed the turret is capable of.
Aim(int index) const362 double Command::Aim(int index) const
363 {
364 if(index < 0 || index >= 32)
365 return 0;
366
367 return aim[index] / 127.;
368 }
369
370
371
SetAim(int index,double amount)372 void Command::SetAim(int index, double amount)
373 {
374 if(index < 0 || index >= 32)
375 return;
376
377 aim[index] = round(127. * max(-1., min(1., amount)));
378 }
379
380
381
382 // Check if any bits are set in this command (including a nonzero turn).
operator bool() const383 Command::operator bool() const
384 {
385 return !!*this;
386 }
387
388
389
390 // Check whether this command is entirely empty.
operator !() const391 bool Command::operator!() const
392 {
393 return !state && !turn;
394 }
395
396
397
398 // For sorting commands (e.g. so a command can be the key in a map):
operator <(const Command & command) const399 bool Command::operator<(const Command &command) const
400 {
401 return (state < command.state);
402 }
403
404
405
406 // Get the commands that are set in either of these commands.
operator |(const Command & command) const407 Command Command::operator|(const Command &command) const
408 {
409 Command result = *this;
410 result |= command;
411 return result;
412 }
413
414
415
416 // Combine everything in the given command with this command. If the given
417 // command has a nonzero turn set, it overrides this command's turn value.
operator |=(const Command & command)418 Command &Command::operator|=(const Command &command)
419 {
420 state |= command.state;
421 if(command.turn)
422 turn = command.turn;
423 return *this;
424 }
425
426
427
428 // Private constructor.
Command(uint64_t state)429 Command::Command(uint64_t state)
430 : state(state)
431 {
432 }
433
434
435
436 // Private constructor that also stores the given description in the lookup
437 // table. (This is used for the enumeration at the top of this file.)
Command(uint64_t state,const string & text)438 Command::Command(uint64_t state, const string &text)
439 : state(state)
440 {
441 if(!text.empty())
442 description[*this] = text;
443 }
444