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