1 /* This file is part of the Spring engine (GPL v2 or later), see LICENSE.html */
2 
3 #include "GuiHandler.h"
4 
5 
6 #include "CommandColors.h"
7 #include "KeyBindings.h"
8 #include "KeyCodes.h"
9 #include "MiniMap.h"
10 #include "MouseHandler.h"
11 #include "Game/Camera.h"
12 #include "Game/Game.h"
13 #include "Game/GameHelper.h"
14 #include "Game/GlobalUnsynced.h"
15 #include "Game/SelectedUnitsHandler.h"
16 #include "Game/TraceRay.h"
17 #include "Lua/LuaConfig.h"
18 #include "Lua/LuaTextures.h"
19 #include "Lua/LuaGaia.h"
20 #include "Lua/LuaRules.h"
21 #include "Lua/LuaUI.h"
22 #include "Map/BaseGroundDrawer.h"
23 #include "Map/Ground.h"
24 #include "Map/MapInfo.h"
25 #include "Map/MetalMap.h"
26 #include "Map/ReadMap.h"
27 #include "Rendering/Fonts/glFont.h"
28 #include "Rendering/IconHandler.h"
29 #include "Rendering/UnitDrawer.h"
30 #include "Rendering/GL/glExtra.h"
31 #include "Rendering/Textures/Bitmap.h"
32 #include "Rendering/Textures/NamedTextures.h"
33 #include "Sim/Features/Feature.h"
34 #include "Sim/Misc/LosHandler.h"
35 #include "Sim/Units/CommandAI/CommandAI.h"
36 #include "Sim/Units/CommandAI/BuilderCAI.h"
37 #include "Sim/Units/UnitDefHandler.h"
38 #include "Sim/Units/Unit.h"
39 #include "Sim/Units/UnitHandler.h"
40 #include "Sim/Units/UnitLoader.h"
41 #include "Sim/Weapons/WeaponDefHandler.h"
42 #include "Sim/Weapons/Weapon.h"
43 #include "System/Config/ConfigHandler.h"
44 #include "System/EventHandler.h"
45 #include "System/GlobalConfig.h"
46 #include "System/Log/ILog.h"
47 #include "System/myMath.h"
48 #include "System/Util.h"
49 #include "System/Input/KeyInput.h"
50 #include "System/Sound/ISound.h"
51 #include "System/Sound/ISoundChannels.h"
52 #include "System/FileSystem/FileHandler.h"
53 #include "System/FileSystem/SimpleParser.h"
54 
55 #include <map>
56 #include <set>
57 #include <list>
58 
59 #include <SDL_keycode.h>
60 #include <SDL_mouse.h>
61 
62 CONFIG(bool, MiniMapMarker).defaultValue(true);
63 CONFIG(bool, InvertQueueKey).defaultValue(false);
64 
65 //////////////////////////////////////////////////////////////////////
66 // Construction/Destruction
67 //////////////////////////////////////////////////////////////////////
68 
69 
70 CGuiHandler* guihandler = NULL;
71 
72 
CGuiHandler()73 CGuiHandler::CGuiHandler():
74 	inCommand(-1),
75 	buildFacing(FACING_SOUTH),
76 	buildSpacing(0),
77 	needShift(false),
78 	showingMetal(false),
79 	activeMousePress(false),
80 	forceLayoutUpdate(false),
81 	maxPage(0),
82 	activePage(0),
83 	defaultCmdMemory(-1),
84 	explicitCommand(-1),
85 	curIconCommand(-1),
86 	actionOffset(0),
87 	drawSelectionInfo(true),
88 	gatherMode(false)
89 {
90 	icons = new IconInfo[16];
91 	iconsSize = 16;
92 	iconsCount = 0;
93 
94 	LoadDefaults();
95 	LoadConfig("ctrlpanel.txt");
96 
97 	miniMapMarker = configHandler->GetBool("MiniMapMarker");
98 	invertQueueKey = configHandler->GetBool("InvertQueueKey");
99 
100 	autoShowMetal = mapInfo->gui.autoShowMetal;
101 
102 	useStencil = false;
103 	if (GLEW_NV_depth_clamp) {
104 		GLint stencilBits;
105 		glGetIntegerv(GL_STENCIL_BITS, &stencilBits);
106 		useStencil = (stencilBits >= 1);
107 	}
108 
109 	failedSound = sound->GetSoundId("FailedCommand");
110 }
111 
112 
~CGuiHandler()113 CGuiHandler::~CGuiHandler()
114 {
115 	CLuaUI::FreeHandler();
116 
117 	delete[] icons;
118 
119 	for (auto& p: textureMap) {
120 		glDeleteTextures(1, &p.second);
121 	}
122 }
123 
124 
GetQueueKeystate() const125 bool CGuiHandler::GetQueueKeystate() const
126 {
127 	return (!invertQueueKey && KeyInput::GetKeyModState(KMOD_SHIFT)) ||
128 	       (invertQueueKey && !KeyInput::GetKeyModState(KMOD_SHIFT));
129 }
130 
131 
LoadDefaults()132 void CGuiHandler::LoadDefaults()
133 {
134 	xIcons = 2;
135 	yIcons = 8;
136 
137 	xPos        = 0.000f;
138 	yPos        = 0.175f;
139 	xIconSize   = 0.060f;
140 	yIconSize   = 0.060f;
141 	textBorder  = 0.003f;
142 	iconBorder  = 0.003f;
143 	frameBorder = 0.003f;
144 
145 	xSelectionPos = 0.018f;
146 	ySelectionPos = 0.127f;
147 
148 	frameAlpha = -1.0f;
149 	textureAlpha = 0.8f;
150 
151 	dropShadows = true;
152 	useOptionLEDs = true;
153 
154 	selectGaps = true;
155 	selectThrough = false;
156 
157 	menuName = "";
158 
159 	outlineFonts = false;
160 
161 	attackRect = false;
162 	newAttackMode = true;
163 	invColorSelect = true;
164 	frontByEnds = false;
165 }
166 
167 
SafeAtoF(float & var,const std::string & value)168 static bool SafeAtoF(float& var, const std::string& value)
169 {
170 	char* endPtr;
171 	const char* startPtr = value.c_str();
172 	const float tmp = (float)strtod(startPtr, &endPtr);
173 	if (endPtr == startPtr) {
174 		return false;
175 	}
176 	var = tmp;
177 	return true;
178 }
179 
180 
LoadConfig(const std::string & cfg)181 bool CGuiHandler::LoadConfig(const std::string& cfg)
182 {
183 	CSimpleParser parser(cfg);
184 
185 	std::string deadStr = "";
186 	std::string prevStr = "";
187 	std::string nextStr = "";
188 	std::string fillOrderStr = "";
189 
190 	while (true) {
191 		const std::string line = parser.GetCleanLine();
192 		if (line.empty()) {
193 			break;
194 		}
195 
196 		const std::vector<std::string> &words = parser.Tokenize(line, 1);
197 
198 		const std::string command = StringToLower(words[0]);
199 
200 		if ((command == "dropshadows") && (words.size() > 1)) {
201 			dropShadows = !!atoi(words[1].c_str());
202 		}
203 		else if ((command == "useoptionleds") && (words.size() > 1)) {
204 			useOptionLEDs = !!atoi(words[1].c_str());
205 		}
206 		else if ((command == "selectgaps") && (words.size() > 1)) {
207 			selectGaps = !!atoi(words[1].c_str());
208 		}
209 		else if ((command == "selectthrough") && (words.size() > 1)) {
210 			selectThrough = !!atoi(words[1].c_str());
211 		}
212 		else if ((command == "deadiconslot") && (words.size() > 1)) {
213 			deadStr = StringToLower(words[1]);
214 		}
215 		else if ((command == "prevpageslot") && (words.size() > 1)) {
216 			prevStr = StringToLower(words[1]);
217 		}
218 		else if ((command == "nextpageslot") && (words.size() > 1)) {
219 			nextStr = StringToLower(words[1]);
220 		}
221 		else if ((command == "fillorder") && (words.size() > 1)) {
222 			fillOrderStr = StringToLower(words[1]);
223 		}
224 		else if ((command == "xicons") && (words.size() > 1)) {
225 			xIcons = atoi(words[1].c_str());
226 		}
227 		else if ((command == "yicons") && (words.size() > 1)) {
228 			yIcons = atoi(words[1].c_str());
229 		}
230 		else if ((command == "xiconsize") && (words.size() > 1)) {
231 			SafeAtoF(xIconSize, words[1]);
232 		}
233 		else if ((command == "yiconsize") && (words.size() > 1)) {
234 			SafeAtoF(yIconSize, words[1]);
235 		}
236 		else if ((command == "xpos") && (words.size() > 1)) {
237 			SafeAtoF(xPos, words[1]);
238 		}
239 		else if ((command == "ypos") && (words.size() > 1)) {
240 			SafeAtoF(yPos, words[1]);
241 		}
242 		else if ((command == "textborder") && (words.size() > 1)) {
243 			SafeAtoF(textBorder, words[1]);
244 		}
245 		else if ((command == "iconborder") && (words.size() > 1)) {
246 			SafeAtoF(iconBorder, words[1]);
247 		}
248 		else if ((command == "frameborder") && (words.size() > 1)) {
249 			SafeAtoF(frameBorder, words[1]);
250 		}
251 		else if ((command == "xselectionpos") && (words.size() > 1)) {
252 			SafeAtoF(xSelectionPos, words[1]);
253 		}
254 		else if ((command == "yselectionpos") && (words.size() > 1)) {
255 			SafeAtoF(ySelectionPos, words[1]);
256 		}
257 		else if ((command == "framealpha") && (words.size() > 1)) {
258 			SafeAtoF(frameAlpha, words[1]);
259 		}
260 		else if ((command == "texturealpha") && (words.size() > 1)) {
261 			SafeAtoF(textureAlpha, words[1]);
262 		}
263 		else if ((command == "outlinefont") && (words.size() > 1)) {
264 			outlineFonts = !!atoi(words[1].c_str());
265 		}
266 		else if ((command == "attackrect") && (words.size() > 1)) {
267 			attackRect = !!atoi(words[1].c_str());
268 		}
269 		else if ((command == "newattackmode") && (words.size() > 1)) {
270 			newAttackMode = !!atoi(words[1].c_str());
271 		}
272 		else if ((command == "invcolorselect") && (words.size() > 1)) {
273 			invColorSelect = !!atoi(words[1].c_str());
274 		}
275 		else if ((command == "frontbyends") && (words.size() > 1)) {
276 			frontByEnds = !!atoi(words[1].c_str());
277 		}
278 	}
279 
280 	// sane clamps
281 	xIcons      = std::max(2,      xIcons);
282 	yIcons      = std::max(2,      yIcons);
283 	xIconSize   = std::max(0.010f, xIconSize);
284 	yIconSize   = std::max(0.010f, yIconSize);
285 	iconBorder  = std::max(0.0f,   iconBorder);
286 	frameBorder = std::max(0.0f,   frameBorder);
287 
288 	xIconStep = xIconSize + (iconBorder * 2.0f);
289 	yIconStep = yIconSize + (iconBorder * 2.0f);
290 
291 	iconsPerPage = (xIcons * yIcons);
292 
293 	buttonBox.x1 = xPos;
294 	buttonBox.x2 = xPos + (frameBorder * 2.0f) + (xIcons * xIconStep);
295 	buttonBox.y1 = yPos;
296 	buttonBox.y2 = yPos + (frameBorder * 2.0f) + (yIcons * yIconStep);
297 
298 	if (!deadStr.empty()) {
299 		deadIconSlot = ParseIconSlot(deadStr);
300 	}	else {
301 		deadIconSlot = -1;
302 	}
303 
304 	if (!prevStr.empty() && (prevStr != "auto")) {
305 		if (prevStr == "none") {
306 			prevPageSlot = -1;
307 		} else {
308 			prevPageSlot = ParseIconSlot(prevStr);
309 		}
310 	}	else {
311 		prevPageSlot = iconsPerPage - 2;
312 	}
313 
314 	if (!nextStr.empty() && (nextStr != "auto")) {
315 		if (nextStr == "none") {
316 			nextPageSlot = -1;
317 		} else {
318 			nextPageSlot = ParseIconSlot(nextStr);
319 		}
320 	}	else {
321 		nextPageSlot = iconsPerPage - 1;
322 	}
323 
324 	if (deadIconSlot >= 0) {
325 		const float fullBorder = frameBorder + iconBorder;
326 		const float fx = (float)(deadIconSlot % xIcons);
327 		const float fy = (float)(deadIconSlot / xIcons);
328 		xBpos = buttonBox.x1 + (fullBorder + (0.5 * xIconSize) + (fx * xIconStep));
329 		yBpos = buttonBox.y2 - (fullBorder + (0.5 * yIconSize) + (fy * yIconStep));
330 	}
331 	else if ((prevPageSlot >= 0) && (nextPageSlot >= 0)) {
332 		// place in the middle of adjacent paging icons
333 		const int delta = abs(prevPageSlot - nextPageSlot);
334 		if ((delta == 1) || (delta == xIcons)) {
335 			const float fullBorder = frameBorder + iconBorder;
336 			const float fxp = (float)(prevPageSlot % xIcons);
337 			const float fyp = (float)(prevPageSlot / xIcons);
338 			const float fxn = (float)(nextPageSlot % xIcons);
339 			const float fyn = (float)(nextPageSlot / xIcons);
340 			const float fx = 0.5f * (fxp + fxn);
341 			const float fy = 0.5f * (fyp + fyn);
342 			xBpos = buttonBox.x1 + (fullBorder + (0.5 * xIconSize) + (fx * xIconStep));
343 			yBpos = buttonBox.y2 - (fullBorder + (0.5 * yIconSize) + (fy * yIconStep));
344 		}
345 		else {
346 			xBpos = yBpos = -1.0f; // off screen
347 		}
348 	}
349 	else {
350 		xBpos = yBpos = -1.0f; // off screen
351 	}
352 
353 	ParseFillOrder(fillOrderStr);
354 
355 	return true;
356 }
357 
358 
ParseFillOrder(const std::string & text)359 void CGuiHandler::ParseFillOrder(const std::string& text)
360 {
361 	// setup the default order
362 	fillOrder.clear();
363 	for (int i = 0; i < iconsPerPage; i++) {
364 		fillOrder.push_back(i);
365 	}
366 
367 	// split the std::string into slot names
368 	const std::vector<std::string> &slotNames = CSimpleParser::Tokenize(text, 0);
369 	if ((int)slotNames.size() != iconsPerPage) {
370 		return;
371 	}
372 
373 	std::set<int>    slotSet;
374 	std::vector<int> slotVec;
375 	for (int s = 0; s < iconsPerPage; s++) {
376 		const int slotNumber = ParseIconSlot(slotNames[s]);
377 		if ((slotNumber < 0) || (slotNumber >= iconsPerPage) ||
378 				(slotSet.find(slotNumber) != slotSet.end())) {
379 			return;
380 		}
381 		slotSet.insert(slotNumber);
382 		slotVec.push_back(slotNumber);
383 	}
384 
385 	fillOrder = slotVec;
386 }
387 
388 
ParseIconSlot(const std::string & text) const389 int CGuiHandler::ParseIconSlot(const std::string& text) const
390 {
391 	const char rowLetter = tolower(text[0]);
392 	if ((rowLetter < 'a') || (rowLetter > 'z')) {
393 		return -1;
394 	}
395 	const int row = rowLetter - 'a';
396 	if (row >= yIcons) {
397 		return -1;
398 	}
399 
400 	char* endPtr;
401 	const char* startPtr = text.c_str() + 1;
402 	int column = strtol(startPtr, &endPtr, 10);
403 	if ((endPtr == startPtr) || (column < 0) || (column >= xIcons)) {
404 		return -1;
405 	}
406 
407 	return (row * xIcons) + column;
408 }
409 
410 
ReloadConfigFromFile(const std::string & fileName)411 bool CGuiHandler::ReloadConfigFromFile(const std::string& fileName)
412 {
413 	CFileHandler ifs(fileName);
414 	std::string cfg;
415 	ifs.LoadStringData(cfg);
416 	return ReloadConfigFromString(cfg);
417 }
418 
419 
ReloadConfigFromString(const std::string & cfg)420 bool CGuiHandler::ReloadConfigFromString(const std::string& cfg)
421 {
422 	LoadConfig(cfg);
423 	activePage = 0;
424 	selectedUnitsHandler.SetCommandPage(activePage);
425 	LayoutIcons(false);
426 	return true;
427 }
428 
429 
ResizeIconArray(unsigned int size)430 void CGuiHandler::ResizeIconArray(unsigned int size)
431 {
432 	unsigned int minIconsSize = iconsSize;
433 	while (minIconsSize < size) {
434 		minIconsSize *= 2;
435 	}
436 	if (iconsSize < minIconsSize) {
437 		iconsSize = minIconsSize;
438 		delete[] icons;
439 		icons = NULL; // to prevent a dead-pointer in case of an out-of-memory exception on the next line
440 		icons = new IconInfo[iconsSize];
441 	}
442 }
443 
444 
AppendPrevAndNext(std::vector<CommandDescription> & cmds)445 void CGuiHandler::AppendPrevAndNext(std::vector<CommandDescription>& cmds)
446 {
447 	CommandDescription cd;
448 
449 	cd.id=CMD_INTERNAL;
450 	cd.action="prevmenu";
451 	cd.type=CMDTYPE_PREV;
452 	cd.name="";
453 	cd.tooltip = "Previous menu";
454 	cmds.push_back(cd);
455 
456 	cd.id=CMD_INTERNAL;
457 	cd.action="nextmenu";
458 	cd.type=CMDTYPE_NEXT;
459 	cd.name="";
460 	cd.tooltip = "Next menu";
461 	cmds.push_back(cd);
462 }
463 
464 
FindInCommandPage()465 int CGuiHandler::FindInCommandPage()
466 {
467 	if ((inCommand < 0) || ((size_t)inCommand >= commands.size())) {
468 		return -1;
469 	}
470 	for (int ii = 0; ii < iconsCount; ii++) {
471 		const IconInfo& icon = icons[ii];
472 		if (icon.commandsID == inCommand) {
473 			return (ii / iconsPerPage);
474 		}
475 	}
476 	return -1;
477 }
478 
479 
RevertToCmdDesc(const CommandDescription & cmdDesc,bool defaultCommand,bool samePage)480 void CGuiHandler::RevertToCmdDesc(const CommandDescription& cmdDesc,
481                                   bool defaultCommand, bool samePage)
482 {
483 	for (size_t a = 0; a < commands.size(); ++a) {
484 		if (commands[a].id == cmdDesc.id) {
485 			if (defaultCommand) {
486 				defaultCmdMemory = a;
487 				return;
488 			}
489 			inCommand = a;
490 			if (commands[a].type == CMDTYPE_ICON_BUILDING) {
491 				const UnitDef* ud = unitDefHandler->GetUnitDefByID(-commands[a].id);
492 				SetShowingMetal(ud->extractsMetal > 0);
493 			} else {
494 				SetShowingMetal(false);
495 			}
496 			if (samePage) {
497 				for (int ii = 0; ii < iconsCount; ii++) {
498 					if (inCommand == icons[ii].commandsID) {
499 						activePage = std::min(maxPage, (ii / iconsPerPage));;
500 						selectedUnitsHandler.SetCommandPage(activePage);
501 					}
502 				}
503 			}
504 			return;
505 		}
506 	}
507 }
508 
509 
LayoutIcons(bool useSelectionPage)510 void CGuiHandler::LayoutIcons(bool useSelectionPage)
511 {
512 	bool defCmd, validInCommand, samePage;
513 	CommandDescription cmdDesc;
514 	{
515 		defCmd =
516 			(mouse->buttons[SDL_BUTTON_RIGHT].pressed &&
517 			(defaultCmdMemory >= 0) && (inCommand < 0) &&
518 			((activeReceiver == this) || (minimap->ProxyMode())));
519 
520 		const int activeCmd = defCmd ? defaultCmdMemory : inCommand;
521 
522 		// copy the current command state
523 		validInCommand = (activeCmd >= 0) && ((size_t)activeCmd < commands.size());
524 		if (validInCommand) {
525 			cmdDesc = commands[activeCmd];
526 		}
527 		samePage = validInCommand && (activePage == FindInCommandPage());
528 		useSelectionPage = useSelectionPage && !samePage;
529 
530 		// reset some of our state
531 		inCommand = -1;
532 		defaultCmdMemory = -1;
533 		commands.clear();
534 		forceLayoutUpdate = false;
535 	}
536 
537 	if (luaUI && luaUI->WantsEvent("LayoutButtons")) {
538 		if (LayoutCustomIcons(useSelectionPage)) {
539 			if (validInCommand) {
540 				RevertToCmdDesc(cmdDesc, defCmd, samePage);
541 			}
542 			return; // we're done here
543 		}
544 		else {
545 			CLuaUI::FreeHandler();
546 		}
547 	}
548 
549 	// get the commands to process
550 	CSelectedUnitsHandler::AvailableCommandsStruct ac;
551 	ac = selectedUnitsHandler.GetAvailableCommands();
552 	ConvertCommands(ac.commands);
553 
554 	std::vector<CommandDescription> hidden;
555 	std::vector<CommandDescription>::const_iterator cdi;
556 
557 	// separate the visible/hidden icons
558 	for (cdi = ac.commands.begin(); cdi != ac.commands.end(); ++cdi){
559 		if (cdi->hidden) {
560 			hidden.push_back(*cdi);
561 		} else {
562 			commands.push_back(*cdi);
563 		}
564 	}
565 
566 	// assign extra icons for internal commands
567 	int extraIcons = 0;
568 	if (deadIconSlot >= 0) { extraIcons++; }
569 	if (prevPageSlot >= 0) { extraIcons++; }
570 	if (nextPageSlot >= 0) { extraIcons++; }
571 
572 	const int cmdCount        = (int)commands.size();
573 	const int cmdIconsPerPage = (iconsPerPage - extraIcons);
574 	const int pageCount       = ((cmdCount + (cmdIconsPerPage - 1)) / cmdIconsPerPage);
575 
576 	const bool multiPage = (pageCount > 1);
577 
578 	const int prevPageCmd = cmdCount + 0;
579 	const int nextPageCmd = cmdCount + 1;
580 
581 	maxPage    = std::max(0, pageCount - 1);
582 	iconsCount = pageCount * iconsPerPage;
583 
584 	// resize the icon array if required
585 	ResizeIconArray(iconsCount);
586 
587 	int ci = 0; // command index
588 
589 	for (int ii = 0; ii < iconsCount; ii++) {
590 
591 		// map the icon order
592 		const int tmpSlot = (ii % iconsPerPage);
593 		const int mii = ii - tmpSlot + fillOrder[tmpSlot]; // mapped icon index
594 
595 		IconInfo& icon = icons[mii];
596 		const int slot = (mii % iconsPerPage);
597 
598 		if (slot == deadIconSlot) {
599 			icon.commandsID = -1;
600 		}
601 		else if (slot == nextPageSlot) {
602 			icon.commandsID = multiPage ? nextPageCmd : -1;
603 		}
604 		else if (slot == prevPageSlot) {
605 			icon.commandsID = multiPage ? prevPageCmd : -1;
606 		}
607 		else if (ci >= cmdCount) {
608 			icon.commandsID = -1;
609 		}
610 		else {
611 			icon.commandsID = ci;
612 			ci++;
613 		}
614 
615 		// setup the geometry
616 		if (icon.commandsID >= 0) {
617 			const float fx = (float)(slot % xIcons);
618 			const float fy = (float)(slot / xIcons);
619 
620 			const float fullBorder = frameBorder + iconBorder;
621 			icon.visual.x1 = buttonBox.x1 + (fullBorder + (fx * xIconStep));
622 			icon.visual.x2 = icon.visual.x1 + xIconSize;
623 			icon.visual.y1 = buttonBox.y2 - (fullBorder + (fy * yIconStep));
624 			icon.visual.y2 = icon.visual.y1 - yIconSize;
625 
626 			const float noGap = selectGaps ? 0.0f : (iconBorder + 0.0005f);
627 			icon.selection.x1 = icon.visual.x1 - noGap;
628 			icon.selection.x2 = icon.visual.x2 + noGap;
629 			icon.selection.y1 = icon.visual.y1 + noGap;
630 			icon.selection.y2 = icon.visual.y2 - noGap;
631 		}
632 		else {
633 			// make sure this icon does not get selected
634 			icon.selection.x1 = icon.selection.x2 = -1.0f;
635 			icon.selection.y1 = icon.selection.y2 = -1.0f;
636 		}
637 	}
638 
639 	// append the Prev and Next commands  (if required)
640 	if (multiPage) {
641 		AppendPrevAndNext(commands);
642 	}
643 
644 	// append the hidden commands
645 	for (cdi = hidden.begin(); cdi != hidden.end(); ++cdi) {
646 		commands.push_back(*cdi);
647 	}
648 
649 	// try to setup the old command state
650 	// (inCommand, activePage, showingMetal)
651 	if (validInCommand) {
652 		RevertToCmdDesc(cmdDesc, defCmd, samePage);
653 	} else if (useSelectionPage) {
654 		activePage = std::min(maxPage, ac.commandPage);
655 	}
656 	activePage = std::min(maxPage, activePage);
657 }
658 
659 
LayoutCustomIcons(bool useSelectionPage)660 bool CGuiHandler::LayoutCustomIcons(bool useSelectionPage)
661 {
662 	if (luaUI == NULL) {
663 		return false;
664 	}
665 
666 	// get the commands to process
667 	CSelectedUnitsHandler::AvailableCommandsStruct ac;
668 	ac = selectedUnitsHandler.GetAvailableCommands();
669 	std::vector<CommandDescription> cmds = ac.commands;
670 	if (!cmds.empty()) {
671 		ConvertCommands(cmds);
672 		AppendPrevAndNext(cmds);
673 	}
674 
675 	// call for a custom layout
676 	int tmpXicons = xIcons;
677 	int tmpYicons = yIcons;
678 	std::vector<int> removeCmds;
679 	std::vector<CommandDescription> customCmds;
680 	std::vector<int> onlyTextureCmds;
681 	std::vector<CLuaUI::ReStringPair> reTextureCmds;
682 	std::vector<CLuaUI::ReStringPair> reNamedCmds;
683 	std::vector<CLuaUI::ReStringPair> reTooltipCmds;
684 	std::vector<CLuaUI::ReParamsPair> reParamsCmds;
685 	std::map<int, int> iconMap;
686 
687 	if (!luaUI->LayoutButtons(tmpXicons, tmpYicons, cmds,
688 	                          removeCmds, customCmds,
689 	                          onlyTextureCmds, reTextureCmds,
690 	                          reNamedCmds, reTooltipCmds, reParamsCmds,
691 	                          iconMap, menuName)) {
692 		return false;
693 	}
694 
695 	if ((tmpXicons < 2) || (tmpYicons < 2)) {
696 		LOG_L(L_ERROR, "LayoutCustomIcons() bad xIcons or yIcons (%i, %i)",
697 				tmpXicons, tmpYicons);
698 		return false;
699 	}
700 
701 	unsigned int i;
702 	const int tmpIconsPerPage = (tmpXicons * tmpYicons);
703 
704 	// build a set to better find unwanted commands
705 	set<int> removeIDs;
706 	for (i = 0; i < removeCmds.size(); i++) {
707 		const int index = removeCmds[i];
708 		if ((index >= 0) || ((size_t)index < cmds.size())) {
709 			removeIDs.insert(index);
710 		} else {
711 			LOG_L(L_ERROR, "LayoutCustomIcons() skipping bad removeCmd (%i)",
712 					index);
713 		}
714 	}
715 	// remove unwanted commands  (and mark all as hidden)
716 	std::vector<CommandDescription> tmpCmds;
717 	for (i = 0; i < cmds.size(); i++) {
718 		if (removeIDs.find(i) == removeIDs.end()) {
719 			cmds[i].hidden = true;
720 			tmpCmds.push_back(cmds[i]);
721 		}
722 	}
723 	cmds = tmpCmds;
724 
725 	// add the custom commands
726 	for (i = 0; i < customCmds.size(); i++) {
727 		customCmds[i].hidden = true;
728 		cmds.push_back(customCmds[i]);
729 	}
730 	const int cmdCount = (int)cmds.size();
731 
732 	// set commands to onlyTexture
733 	for (i = 0; i < onlyTextureCmds.size(); i++) {
734 		const int index = onlyTextureCmds[i];
735 		if ((index >= 0) && (index < cmdCount)) {
736 			cmds[index].onlyTexture = true;
737 		} else {
738 			LOG_L(L_ERROR, "LayoutCustomIcons() skipping bad onlyTexture (%i)",
739 					index);
740 		}
741 	}
742 
743 	// retexture commands
744 	for (i = 0; i < reTextureCmds.size(); i++) {
745 		const int index = reTextureCmds[i].cmdIndex;
746 		if ((index >= 0) && (index < cmdCount)) {
747 			cmds[index].iconname = reTextureCmds[i].texture;
748 		} else {
749 			LOG_L(L_ERROR, "LayoutCustomIcons() skipping bad reTexture (%i)",
750 					index);
751 		}
752 	}
753 
754 	// reNamed commands
755 	for (i = 0; i < reNamedCmds.size(); i++) {
756 		const int index = reNamedCmds[i].cmdIndex;
757 		if ((index >= 0) && (index < cmdCount)) {
758 			cmds[index].name = reNamedCmds[i].texture;
759 		} else {
760 			LOG_L(L_ERROR, "LayoutCustomIcons() skipping bad reNamed (%i)",
761 					index);
762 		}
763 	}
764 
765 	// reTooltip commands
766 	for (i = 0; i < reTooltipCmds.size(); i++) {
767 		const int index = reTooltipCmds[i].cmdIndex;
768 		if ((index >= 0) && (index < cmdCount)) {
769 			cmds[index].tooltip = reTooltipCmds[i].texture;
770 		} else {
771 			LOG_L(L_ERROR, "LayoutCustomIcons() skipping bad reNamed (%i)",
772 					index);
773 		}
774 	}
775 
776 	// reParams commands
777 	for (i = 0; i < reParamsCmds.size(); i++) {
778 		const int index = reParamsCmds[i].cmdIndex;
779 		if ((index >= 0) && (index < cmdCount)) {
780 			const map<int, std::string>& params = reParamsCmds[i].params;
781 			map<int, std::string>::const_iterator pit;
782 			for (pit = params.begin(); pit != params.end(); ++pit) {
783 				const int p = pit->first;
784 				if ((p >= 0) && (p < (int)cmds[index].params.size())) {
785 					cmds[index].params[p] = pit->second;
786 				}
787 			}
788 		} else {
789 			LOG_L(L_ERROR, "LayoutCustomIcons() skipping bad reParams (%i)",
790 					index);
791 		}
792 	}
793 
794 	// build the iconList from the map
795 	std::vector<int> iconList;
796 	int nextPos = 0;
797 	map<int, int>::iterator mit;
798 	for (mit = iconMap.begin(); mit != iconMap.end(); ++mit) {
799 		const int iconPos = mit->first;
800 		if (iconPos < nextPos) {
801 			continue;
802 		}
803 		else if (iconPos > nextPos) {
804 			// fill in the blanks
805 			for (int p = nextPos; p < iconPos; p++) {
806 				iconList.push_back(-1);
807 			}
808 		}
809 		iconList.push_back(mit->second); // cmdIndex
810 		nextPos = iconPos + 1;
811 	}
812 
813 	const int iconListCount = (int)iconList.size();
814 	const int pageCount = ((iconListCount + (tmpIconsPerPage - 1)) / tmpIconsPerPage);
815 	const int tmpIconsCount = (pageCount * tmpIconsPerPage);
816 
817 	// resize the icon array if required
818 	ResizeIconArray(tmpIconsCount);
819 
820 	// build the iconList
821 	for (int ii = 0; ii < tmpIconsCount; ii++) {
822 		IconInfo& icon = icons[ii];
823 
824 		const int index = (ii < (int)iconList.size()) ? iconList[ii] : -1;
825 
826 		if ((index >= 0) && (index < cmdCount)) {
827 
828 			icon.commandsID = index;
829 			cmds[index].hidden = false;
830 
831 			const int slot = (ii % tmpIconsPerPage);
832 			const float fx = (float)(slot % tmpXicons);
833 			const float fy = (float)(slot / tmpXicons);
834 
835 			const float fullBorder = frameBorder + iconBorder;
836 			icon.visual.x1 = buttonBox.x1 + (fullBorder + (fx * xIconStep));
837 			icon.visual.x2 = icon.visual.x1 + xIconSize;
838 			icon.visual.y1 = buttonBox.y2 - (fullBorder + (fy * yIconStep));
839 			icon.visual.y2 = icon.visual.y1 - yIconSize;
840 
841 			const float noGap = selectGaps ? 0.0f : (iconBorder + 0.0005f);
842 			icon.selection.x1 = icon.visual.x1 - noGap;
843 			icon.selection.x2 = icon.visual.x2 + noGap;
844 			icon.selection.y1 = icon.visual.y1 + noGap;
845 			icon.selection.y2 = icon.visual.y2 - noGap;
846 		}
847 		else {
848 			// make sure this icon does not get selected
849 			icon.commandsID = -1;
850 			icon.selection.x1 = icon.selection.x2 = -1.0f;
851 			icon.selection.y1 = icon.selection.y2 = -1.0f;
852 		}
853 	}
854 
855 	commands = cmds;
856 
857 	xIcons       = tmpXicons;
858 	yIcons       = tmpYicons;
859 	iconsCount   = tmpIconsCount;
860 	iconsPerPage = tmpIconsPerPage;
861 
862 	maxPage = std::max(0, pageCount - 1);
863 	if (useSelectionPage) {
864 		activePage = std::min(maxPage, ac.commandPage);
865 	} else {
866 		activePage = std::min(maxPage, activePage);
867 	}
868 
869 	buttonBox.x1 = xPos;
870 	buttonBox.x2 = xPos + (frameBorder * 2.0f) + (xIcons * xIconStep);
871 	buttonBox.y1 = yPos;
872 	buttonBox.y2 = yPos + (frameBorder * 2.0f) + (yIcons * yIconStep);
873 
874 	return true;
875 }
876 
877 
GiveCommand(Command & cmd,bool fromUser)878 void CGuiHandler::GiveCommand(Command& cmd, bool fromUser)
879 {
880 	commandsToGive.push_back(std::pair<const Command, bool>(cmd, fromUser));
881 }
882 
883 
GiveCommandsNow()884 void CGuiHandler::GiveCommandsNow() {
885 	std::vector< std::pair<Command, bool> > commandsToGiveTemp;
886 	{
887 		commandsToGiveTemp.swap(commandsToGive);
888 	}
889 
890 	for(std::vector< std::pair<Command, bool> >::iterator i = commandsToGiveTemp.begin(); i != commandsToGiveTemp.end(); ++i) {
891 		const Command& cmd = (*i).first;
892 		if (eventHandler.CommandNotify(cmd)) {
893 			continue;
894 		}
895 
896 		selectedUnitsHandler.GiveCommand(cmd, (*i).second);
897 
898 		if (gatherMode) {
899 			if ((cmd.GetID() == CMD_MOVE) || (cmd.GetID() == CMD_FIGHT)) {
900 				Command gatherCmd(CMD_GATHERWAIT);
901 				GiveCommand(gatherCmd, false);
902 			}
903 		}
904 	}
905 }
906 
907 
ConvertCommands(std::vector<CommandDescription> & cmds)908 void CGuiHandler::ConvertCommands(std::vector<CommandDescription>& cmds)
909 {
910 	if (newAttackMode) {
911 		const int count = (int)cmds.size();
912 		for (int i = 0; i < count; i++) {
913 			CommandDescription& cd = cmds[i];
914 			if ((cd.id == CMD_ATTACK) && (cd.type == CMDTYPE_ICON_UNIT_OR_MAP)) {
915 				if (attackRect) {
916 					cd.type = CMDTYPE_ICON_UNIT_OR_RECTANGLE;
917 				} else {
918 					cd.type = CMDTYPE_ICON_UNIT_OR_AREA;
919 				}
920 			}
921 		}
922 	}
923 }
924 
925 
SetShowingMetal(bool show)926 void CGuiHandler::SetShowingMetal(bool show)
927 {
928 	CBaseGroundDrawer* gd = readMap->GetGroundDrawer();
929 
930 	if (!show) {
931 		if (showingMetal) {
932 			gd->DisableExtraTexture();
933 			showingMetal = false;
934 		}
935 	} else {
936 		if (autoShowMetal) {
937 			if (gd->GetDrawMode() != CBaseGroundDrawer::drawMetal) {
938 				gd->SetMetalTexture();
939 				showingMetal = true;
940 			}
941 		}
942 	}
943 }
944 
945 
Update()946 void CGuiHandler::Update()
947 {
948 	SetCursorIcon();
949 
950 	{
951 		if (!invertQueueKey && (needShift && !KeyInput::GetKeyModState(KMOD_SHIFT))) {
952 			SetShowingMetal(false);
953 			inCommand=-1;
954 			needShift=false;
955 		}
956 	}
957 
958 	GiveCommandsNow();
959 
960 	const bool commandsChanged = selectedUnitsHandler.CommandsChanged();
961 
962 	if (commandsChanged) {
963 		SetShowingMetal(false);
964 		LayoutIcons(true);
965 	}
966 	else if (forceLayoutUpdate) {
967 		LayoutIcons(false);
968 	}
969 }
970 
971 
972 /******************************************************************************/
973 /******************************************************************************/
974 
SetCursorIcon() const975 void CGuiHandler::SetCursorIcon() const
976 {
977 	string newCursor = "cursornormal";
978 	float cursorScale = 1.0f;
979 
980 	CInputReceiver* ir = NULL;
981 	if (!game->hideInterface)
982 		ir = GetReceiverAt(mouse->lastx, mouse->lasty);
983 
984 	if ((ir != NULL) && (ir != minimap)) {
985 		mouse->ChangeCursor(newCursor, cursorScale);
986 		return;
987 	}
988 
989 	if (ir == minimap)
990 		cursorScale = minimap->CursorScale();
991 
992 	const bool useMinimap = (minimap->ProxyMode() || ((activeReceiver != this) && (ir == minimap)));
993 
994 	if ((inCommand >= 0) && ((size_t)inCommand<commands.size())) {
995 		const CommandDescription& cmdDesc = commands[inCommand];
996 
997 		if (!cmdDesc.mouseicon.empty()) {
998 			newCursor=cmdDesc.mouseicon;
999 		} else {
1000 			newCursor=cmdDesc.name;
1001 		}
1002 
1003 		if (useMinimap && (cmdDesc.id < 0)) {
1004 			BuildInfo bi;
1005 			bi.pos = minimap->GetMapPosition(mouse->lastx, mouse->lasty);
1006 			bi.buildFacing = buildFacing;
1007 			bi.def = unitDefHandler->GetUnitDefByID(-cmdDesc.id);
1008 			bi.pos = CGameHelper::Pos2BuildPos(bi, false);
1009 			// if an unit (enemy), is not in LOS, then TestUnitBuildSquare()
1010 			// does not consider it when checking for position blocking
1011 			CFeature* feature = NULL;
1012 			if (!CGameHelper::TestUnitBuildSquare(bi, feature, gu->myAllyTeam, false)) {
1013 				newCursor = "BuildBad";
1014 			} else {
1015 				newCursor = "BuildGood";
1016 			}
1017 		}
1018 
1019 		if (!TryTarget(cmdDesc)) {
1020 			newCursor = "AttackBad";
1021 		}
1022 	}
1023 	else if (!useMinimap || minimap->FullProxy()) {
1024 		int defcmd;
1025 		if (mouse->buttons[SDL_BUTTON_RIGHT].pressed &&
1026 				((activeReceiver == this) || (minimap->ProxyMode()))) {
1027 			defcmd = defaultCmdMemory;
1028 		} else {
1029 			defcmd = GetDefaultCommand(mouse->lastx, mouse->lasty);
1030 		}
1031 		if ((defcmd >= 0) && ((size_t)defcmd < commands.size())) {
1032 			const CommandDescription& cmdDesc = commands[defcmd];
1033 			if (!cmdDesc.mouseicon.empty()) {
1034 				newCursor=cmdDesc.mouseicon;
1035 			} else {
1036 				newCursor=cmdDesc.name;
1037 			}
1038 		}
1039 	}
1040 
1041 	if (gatherMode &&
1042 	    ((newCursor == "Move") ||
1043 	    (newCursor == "Fight"))) {
1044 		newCursor = "GatherWait";
1045 	}
1046 
1047 	mouse->ChangeCursor(newCursor, cursorScale);
1048 }
1049 
1050 
TryTarget(const CommandDescription & cmdDesc) const1051 bool CGuiHandler::TryTarget(const CommandDescription& cmdDesc) const
1052 {
1053 	if (cmdDesc.id != CMD_ATTACK)
1054 		return true;
1055 
1056 	if (selectedUnitsHandler.selectedUnits.empty())
1057 		return true;
1058 
1059 	// get mouse-hovered map pos
1060 	CUnit* targetUnit = NULL;
1061 	CFeature* targetFeature = NULL;
1062 
1063 	const float viewRange = globalRendering->viewRange * 1.4f;
1064 	const float dist = TraceRay::GuiTraceRay(camera->GetPos(), mouse->dir, viewRange, NULL, targetUnit, targetFeature, true);
1065 	const float3 groundPos = camera->GetPos() + mouse->dir * dist;
1066 
1067 	float3 modGroundPos;
1068 
1069 	if (dist <= 0.0f)
1070 		return false;
1071 
1072 	for (const CUnit* u: selectedUnitsHandler.selectedUnits) {
1073 		// mobile kamikaze can always move into range
1074 		//FIXME do a range check in case of immobile kamikaze (-> mines)
1075 		if (u->unitDef->canKamikaze && !u->immobile)
1076 			return true;
1077 
1078 		for (const CWeapon* w: u->weapons) {
1079 			w->AdjustTargetPosToWater(modGroundPos = groundPos, targetUnit == NULL);
1080 
1081 			if (u->immobile) {
1082 				// immobile unit
1083 				// check range and weapon target properties
1084 				if (w->TryTarget(modGroundPos, false, targetUnit)) {
1085 					return true;
1086 				}
1087 			} else {
1088 				// mobile units can always move into range
1089 				// only check if we got a weapon that can shot the target (i.e. anti-air/anti-sub)
1090 				if (w->TestTarget(modGroundPos, false, targetUnit)) {
1091 					return true;
1092 				}
1093 			}
1094 		}
1095 	}
1096 
1097 	return false;
1098 }
1099 
1100 
MousePress(int x,int y,int button)1101 bool CGuiHandler::MousePress(int x, int y, int button)
1102 {
1103 	{
1104 		if (button != SDL_BUTTON_LEFT && button != SDL_BUTTON_RIGHT && button != -SDL_BUTTON_RIGHT && button != -SDL_BUTTON_LEFT)
1105 			return false;
1106 
1107 		if (button < 0) {
1108 			// proxied click from the minimap
1109 			button = -button;
1110 			activeMousePress=true;
1111 		}
1112 		else if (AboveGui(x,y)) {
1113 			activeMousePress = true;
1114 			if ((curIconCommand < 0) && !game->hideInterface) {
1115 				const int iconPos = IconAtPos(x, y);
1116 				if (iconPos >= 0) {
1117 					curIconCommand = icons[iconPos].commandsID;
1118 				}
1119 			}
1120 			if (button == SDL_BUTTON_RIGHT)
1121 				inCommand = defaultCmdMemory = -1;
1122 			return true;
1123 		}
1124 		else if (minimap && minimap->IsAbove(x, y)) {
1125 			return false; // let the minimap do its job
1126 		}
1127 
1128 		if (inCommand >= 0) {
1129 			if (invertQueueKey && (button == SDL_BUTTON_RIGHT) &&
1130 				!mouse->buttons[SDL_BUTTON_LEFT].pressed) { // for rocker gestures
1131 					SetShowingMetal(false);
1132 					inCommand = -1;
1133 					needShift = false;
1134 					return false;
1135 			}
1136 			activeMousePress = true;
1137 			return true;
1138 		}
1139 	}
1140 	if (button == SDL_BUTTON_RIGHT) {
1141 		activeMousePress = true;
1142 		defaultCmdMemory = GetDefaultCommand(x, y);
1143 		return true;
1144 	}
1145 
1146 	return false;
1147 }
1148 
1149 
MouseRelease(int x,int y,int button,const float3 & cameraPos,const float3 & mouseDir)1150 void CGuiHandler::MouseRelease(int x, int y, int button, const float3& cameraPos, const float3& mouseDir)
1151 {
1152 	if (button != SDL_BUTTON_LEFT && button != SDL_BUTTON_RIGHT && button != -SDL_BUTTON_RIGHT && button != -SDL_BUTTON_LEFT)
1153 		return;
1154 
1155 	int lastIconCmd = curIconCommand;
1156 	curIconCommand = -1;
1157 
1158 	int iconCmd = -1;
1159 	explicitCommand = inCommand;
1160 
1161 	if (activeMousePress) {
1162 		activeMousePress = false;
1163 	} else {
1164 		return;
1165 	}
1166 
1167 	if (!invertQueueKey && needShift && !KeyInput::GetKeyModState(KMOD_SHIFT)) {
1168 		SetShowingMetal(false);
1169 		inCommand = -1;
1170 		needShift = false;
1171 	}
1172 
1173 	if (button < 0) {
1174 		button = -button; // proxied click from the minimap
1175 	} else {
1176 		// setup iconCmd
1177 		if (!game->hideInterface) {
1178 			const int iconPos = IconAtPos(x, y);
1179 			if (iconPos >= 0) {
1180 				iconCmd = icons[iconPos].commandsID;
1181 				if(iconCmd != lastIconCmd)
1182 					iconCmd = -1; // mouse was pressed on one button and released on another one --> ignore the command
1183 			}
1184 		}
1185 	}
1186 
1187 	if ((button == SDL_BUTTON_RIGHT) && (iconCmd == -1)) {
1188 		// right click -> set the default cmd
1189 		inCommand = defaultCmdMemory;
1190 		defaultCmdMemory = -1;
1191 	}
1192 
1193 	if ((iconCmd >= 0) && ((size_t)iconCmd < commands.size())) {
1194 		const bool rightMouseButton = (button == SDL_BUTTON_RIGHT);
1195 		SetActiveCommand(iconCmd, rightMouseButton);
1196 		return;
1197 	}
1198 
1199 	// not over a button, try to execute a command
1200 	Command c = GetCommand(x, y, button, false, cameraPos, mouseDir);
1201 
1202 	if (c.GetID() == CMD_FAILED) { // indicates we should not finish the current command
1203 		Channels::UserInterface->PlaySample(failedSound, 5);
1204 		return;
1205 	}
1206 
1207 	// if cmd_stop is returned it indicates that no good command could be found
1208 	if (c.GetID() != CMD_STOP) {
1209 		GiveCommand(c);
1210 
1211 		lastKeySet.Reset();
1212 	}
1213 
1214 	FinishCommand(button);
1215 }
1216 
1217 
SetActiveCommand(int cmdIndex,bool rightMouseButton)1218 bool CGuiHandler::SetActiveCommand(int cmdIndex, bool rightMouseButton)
1219 {
1220 	if (cmdIndex >= (int)commands.size()) {
1221 		return false;
1222 	}
1223 	else if (cmdIndex < 0) {
1224 		// cancel the current command
1225 		inCommand = -1;
1226 		defaultCmdMemory = -1;
1227 		needShift = false;
1228 		activeMousePress = false;
1229 		SetShowingMetal(false);
1230 		return true;
1231 	}
1232 
1233 	CommandDescription& cd = commands[cmdIndex];
1234 	if (cd.disabled) {
1235 		return false;
1236 	}
1237 
1238 	lastKeySet.Reset();
1239 
1240 	switch (cd.type) {
1241 		case CMDTYPE_ICON: {
1242 			Command c(cd.id);
1243 			if (cd.id != CMD_STOP) {
1244 				c.options = CreateOptions(rightMouseButton);
1245 				if (invertQueueKey && ((cd.id < 0) || (cd.id == CMD_STOCKPILE))) {
1246 					c.options = c.options ^ SHIFT_KEY;
1247 				}
1248 			}
1249 			GiveCommand(c);
1250 			break;
1251 		}
1252 		case CMDTYPE_ICON_MODE: {
1253 			int newMode = atoi(cd.params[0].c_str()) + 1;
1254 			if (newMode > (static_cast<int>(cd.params.size())-2)) {
1255 				newMode = 0;
1256 			}
1257 
1258 			// not really required
1259 			char t[10];
1260 			SNPRINTF(t, 10, "%d", newMode);
1261 			cd.params[0] = t;
1262 
1263 			Command c(cd.id, CreateOptions(rightMouseButton), newMode);
1264 			GiveCommand(c);
1265 			forceLayoutUpdate = true;
1266 			break;
1267 		}
1268 		case CMDTYPE_NUMBER:
1269 		case CMDTYPE_ICON_MAP:
1270 		case CMDTYPE_ICON_AREA:
1271 		case CMDTYPE_ICON_UNIT:
1272 		case CMDTYPE_ICON_UNIT_OR_MAP:
1273 		case CMDTYPE_ICON_FRONT:
1274 		case CMDTYPE_ICON_UNIT_OR_AREA:
1275 		case CMDTYPE_ICON_UNIT_OR_RECTANGLE:
1276 		case CMDTYPE_ICON_UNIT_FEATURE_OR_AREA: {
1277 			inCommand = cmdIndex;
1278 			SetShowingMetal(false);
1279 			activeMousePress = false;
1280 			break;
1281 		}
1282 		case CMDTYPE_ICON_BUILDING: {
1283 			const UnitDef* ud = unitDefHandler->GetUnitDefByID(-cd.id);
1284 			inCommand = cmdIndex;
1285 			SetShowingMetal(ud->extractsMetal > 0);
1286 			activeMousePress = false;
1287 			break;
1288 		}
1289 		case CMDTYPE_NEXT: {
1290 			++activePage;
1291 			if (activePage > maxPage) {
1292 				activePage = 0;
1293 			}
1294 			selectedUnitsHandler.SetCommandPage(activePage);
1295 			break;
1296 		}
1297 		case CMDTYPE_PREV: {
1298 			--activePage;
1299 			if (activePage < 0) {
1300 				activePage=maxPage;
1301 			}
1302 			selectedUnitsHandler.SetCommandPage(activePage);
1303 			break;
1304 		}
1305 		case CMDTYPE_CUSTOM: {
1306 			RunCustomCommands(cd.params, rightMouseButton);
1307 			break;
1308 		}
1309 		default:
1310 			break;
1311 	}
1312 
1313 	return true;
1314 }
1315 
1316 
SetActiveCommand(int cmdIndex,int button,bool leftMouseButton,bool rightMouseButton,bool alt,bool ctrl,bool meta,bool shift)1317 bool CGuiHandler::SetActiveCommand(int cmdIndex, int button,
1318                                    bool leftMouseButton, bool rightMouseButton,
1319                                    bool alt, bool ctrl, bool meta, bool shift)
1320 {
1321 	// use the button value instead of rightMouseButton
1322 	const bool effectiveRMB = (button == SDL_BUTTON_LEFT) ? false : true;
1323 
1324 	// setup the mouse and key states
1325 	const bool  prevLMB   = mouse->buttons[SDL_BUTTON_LEFT].pressed;
1326 	const bool  prevRMB   = mouse->buttons[SDL_BUTTON_RIGHT].pressed;
1327 	const boost::uint8_t prevAlt   = KeyInput::GetKeyModState(KMOD_ALT);
1328 	const boost::uint8_t prevCtrl  = KeyInput::GetKeyModState(KMOD_CTRL);
1329 	const boost::uint8_t prevMeta  = KeyInput::GetKeyModState(KMOD_GUI);
1330 	const boost::uint8_t prevShift = KeyInput::GetKeyModState(KMOD_SHIFT);
1331 
1332 	mouse->buttons[SDL_BUTTON_LEFT].pressed  = leftMouseButton;
1333 	mouse->buttons[SDL_BUTTON_RIGHT].pressed = rightMouseButton;
1334 
1335 	KeyInput::SetKeyModState(KMOD_ALT,   alt);
1336 	KeyInput::SetKeyModState(KMOD_CTRL,  ctrl);
1337 	KeyInput::SetKeyModState(KMOD_GUI,   meta);
1338 	KeyInput::SetKeyModState(KMOD_SHIFT, shift);
1339 
1340 	const bool retval = SetActiveCommand(cmdIndex, effectiveRMB);
1341 
1342 	// revert the mouse and key states
1343 	KeyInput::SetKeyModState(KMOD_SHIFT, prevShift);
1344 	KeyInput::SetKeyModState(KMOD_GUI,   prevMeta);
1345 	KeyInput::SetKeyModState(KMOD_CTRL,  prevCtrl);
1346 	KeyInput::SetKeyModState(KMOD_ALT,   prevAlt);
1347 
1348 	mouse->buttons[SDL_BUTTON_RIGHT].pressed = prevRMB;
1349 	mouse->buttons[SDL_BUTTON_LEFT].pressed  = prevLMB;
1350 
1351 	return retval;
1352 }
1353 
1354 
IconAtPos(int x,int y)1355 int CGuiHandler::IconAtPos(int x, int y) // GetToolTip --> IconAtPos
1356 {
1357 	const float fx = MouseX(x);
1358 	const float fy = MouseY(y);
1359 
1360 	if ((fx < buttonBox.x1) || (fx > buttonBox.x2) ||
1361 	    (fy < buttonBox.y1) || (fy > buttonBox.y2)) {
1362 		return -1;
1363 	}
1364 
1365 	int xSlot = int((fx - (buttonBox.x1 + frameBorder)) / xIconStep);
1366 	int ySlot = int(((buttonBox.y2 - frameBorder) - fy) / yIconStep);
1367 	xSlot = std::min(std::max(xSlot, 0), xIcons - 1);
1368 	ySlot = std::min(std::max(ySlot, 0), yIcons - 1);
1369   const int ii = (activePage * iconsPerPage) + (ySlot * xIcons) + xSlot;
1370   if ((ii >= 0) && (ii < iconsCount)) {
1371 		if ((fx > icons[ii].selection.x1) && (fx < icons[ii].selection.x2) &&
1372 				(fy > icons[ii].selection.y2) && (fy < icons[ii].selection.y1)) {
1373 			return ii;
1374 		}
1375 	}
1376 
1377 	return -1;
1378 }
1379 
1380 
1381 /******************************************************************************/
1382 
1383 enum ModState {
1384 	DontCare, Required, Forbidden
1385 };
1386 
1387 struct ModGroup {
ModGroupModGroup1388 	ModGroup()
1389 	: alt(DontCare),
1390 	  ctrl(DontCare),
1391 	  meta(DontCare),
1392 	  shift(DontCare),
1393 	  right(DontCare) {}
1394 	ModState alt, ctrl, meta, shift, right;
1395 };
1396 
1397 
ParseCustomCmdMods(std::string & cmd,ModGroup & in,ModGroup & out)1398 static bool ParseCustomCmdMods(std::string& cmd, ModGroup& in, ModGroup& out)
1399 {
1400 	const char* c = cmd.c_str();
1401 	if (*c != '@') {
1402 		return false;
1403 	}
1404 	c++;
1405 	bool neg = false;
1406 	while ((*c != 0) && (*c != '@')) {
1407 		char ch = *c;
1408 		if (ch == '-') {
1409 			neg = true;
1410 		}
1411 		else if (ch == '+') {
1412 			neg = false;
1413 		}
1414 		else if (ch == 'a') { in.alt    = neg ? Forbidden : Required; neg = false; }
1415 		else if (ch == 'c') { in.ctrl   = neg ? Forbidden : Required; neg = false; }
1416 		else if (ch == 'm') { in.meta   = neg ? Forbidden : Required; neg = false; }
1417 		else if (ch == 's') { in.shift  = neg ? Forbidden : Required; neg = false; }
1418 		else if (ch == 'r') { in.right  = neg ? Forbidden : Required; neg = false; }
1419 		else if (ch == 'A') { out.alt   = neg ? Forbidden : Required; neg = false; }
1420 		else if (ch == 'C') { out.ctrl  = neg ? Forbidden : Required; neg = false; }
1421 		else if (ch == 'M') { out.meta  = neg ? Forbidden : Required; neg = false; }
1422 		else if (ch == 'S') { out.shift = neg ? Forbidden : Required; neg = false; }
1423 		else if (ch == 'R') { out.right = neg ? Forbidden : Required; neg = false; }
1424 
1425 		c++;
1426 	}
1427 
1428 	if (*c == 0) {
1429 		return false;
1430 	}
1431 	cmd = cmd.substr((c + 1) - cmd.c_str());
1432 
1433 	return true;
1434 }
1435 
1436 
CheckCustomCmdMods(bool rightMouseButton,ModGroup & inMods)1437 static bool CheckCustomCmdMods(bool rightMouseButton, ModGroup& inMods)
1438 {
1439 	if (((inMods.alt   == Required)  && !KeyInput::GetKeyModState(KMOD_ALT))   ||
1440 	    ((inMods.alt   == Forbidden) &&  KeyInput::GetKeyModState(KMOD_ALT))   ||
1441 	    ((inMods.ctrl  == Required)  && !KeyInput::GetKeyModState(KMOD_CTRL))  ||
1442 	    ((inMods.ctrl  == Forbidden) &&  KeyInput::GetKeyModState(KMOD_CTRL))  ||
1443 	    ((inMods.meta  == Required)  && !KeyInput::GetKeyModState(KMOD_GUI))  ||
1444 	    ((inMods.meta  == Forbidden) &&  KeyInput::GetKeyModState(KMOD_GUI))  ||
1445 	    ((inMods.shift == Required)  && !KeyInput::GetKeyModState(KMOD_SHIFT)) ||
1446 	    ((inMods.shift == Forbidden) &&  KeyInput::GetKeyModState(KMOD_SHIFT)) ||
1447 	    ((inMods.right == Required)  && !rightMouseButton) ||
1448 	    ((inMods.right == Forbidden) &&  rightMouseButton)) {
1449 		return false;
1450 	}
1451 	return true;
1452 }
1453 
1454 
RunCustomCommands(const std::vector<std::string> & cmds,bool rightMouseButton)1455 void CGuiHandler::RunCustomCommands(const std::vector<std::string>& cmds, bool rightMouseButton)
1456 {
1457 	static int depth = 0;
1458 	if (depth > 8) {
1459 		return; // recursion protection
1460 	}
1461 	depth++;
1462 
1463 	for (int p = 0; p < (int)cmds.size(); p++) {
1464 		std::string copy = cmds[p];
1465 		ModGroup inMods;  // must match for the action to execute
1466 		ModGroup outMods; // controls the state of the modifiers  (ex: "group1")
1467 		if (ParseCustomCmdMods(copy, inMods, outMods)) {
1468 			if (CheckCustomCmdMods(rightMouseButton, inMods)) {
1469 				const bool tmpAlt   = !!KeyInput::GetKeyModState(KMOD_ALT);
1470 				const bool tmpCtrl  = !!KeyInput::GetKeyModState(KMOD_CTRL);
1471 				const bool tmpMeta  = !!KeyInput::GetKeyModState(KMOD_GUI);
1472 				const bool tmpShift = !!KeyInput::GetKeyModState(KMOD_SHIFT);
1473 
1474 				if (outMods.alt   != DontCare)  { KeyInput::SetKeyModState(KMOD_ALT,   int(outMods.alt   == Required)); }
1475 				if (outMods.ctrl  != DontCare)  { KeyInput::SetKeyModState(KMOD_CTRL,  int(outMods.ctrl  == Required)); }
1476 				if (outMods.meta  != DontCare)  { KeyInput::SetKeyModState(KMOD_GUI,   int(outMods.meta  == Required)); }
1477 				if (outMods.shift != DontCare)  { KeyInput::SetKeyModState(KMOD_SHIFT, int(outMods.shift == Required)); }
1478 
1479 				Action action(copy);
1480 				if (!ProcessLocalActions(action)) {
1481 					game->ProcessAction(action);
1482 				}
1483 
1484 				KeyInput::SetKeyModState(KMOD_ALT,   tmpAlt);
1485 				KeyInput::SetKeyModState(KMOD_CTRL,  tmpCtrl);
1486 				KeyInput::SetKeyModState(KMOD_GUI,   tmpMeta);
1487 				KeyInput::SetKeyModState(KMOD_SHIFT, tmpShift);
1488 			}
1489 		}
1490 	}
1491 	depth--;
1492 }
1493 
1494 
AboveGui(int x,int y)1495 bool CGuiHandler::AboveGui(int x, int y)
1496 {
1497 	if (iconsCount <= 0) {
1498 		return false;
1499 	}
1500 	if (!selectThrough) {
1501 		const float fx = MouseX(x);
1502 		const float fy = MouseY(y);
1503 		if ((fx > buttonBox.x1) && (fx < buttonBox.x2) &&
1504 				(fy > buttonBox.y1) && (fy < buttonBox.y2)) {
1505 			return true;
1506 		}
1507 	}
1508 	return (IconAtPos(x,y) >= 0);
1509 }
1510 
1511 
CreateOptions(bool rightMouseButton)1512 unsigned char CGuiHandler::CreateOptions(bool rightMouseButton)
1513 {
1514 	unsigned char options = 0;
1515 
1516 	if (rightMouseButton) {
1517 		options |= RIGHT_MOUSE_KEY;
1518 	}
1519 	if (GetQueueKeystate()) {
1520 		// allow mouse button 'rocker' movements to force
1521 		// immediate mode (when queuing is the default mode)
1522 		if (!invertQueueKey ||
1523 		    (!mouse->buttons[SDL_BUTTON_LEFT].pressed &&
1524 		     !mouse->buttons[SDL_BUTTON_RIGHT].pressed)) {
1525 			options |= SHIFT_KEY;
1526 		}
1527 	}
1528 	if (KeyInput::GetKeyModState(KMOD_CTRL)) { options |= CONTROL_KEY; }
1529 	if (KeyInput::GetKeyModState(KMOD_ALT) ) { options |= ALT_KEY;     }
1530 	if (KeyInput::GetKeyModState(KMOD_GUI))  { options |= META_KEY;    }
1531 
1532 	return options;
1533 }
CreateOptions(int button)1534 unsigned char CGuiHandler::CreateOptions(int button)
1535 {
1536 	return CreateOptions(button != SDL_BUTTON_LEFT);
1537 }
1538 
1539 
GetNumberInput(const CommandDescription & cd) const1540 float CGuiHandler::GetNumberInput(const CommandDescription& cd) const
1541 {
1542 	float minV = 0.0f;
1543 	float maxV = 100.0f;
1544 	if (!cd.params.empty()) { minV = atof(cd.params[0].c_str()); }
1545 	if (cd.params.size() >= 2) { maxV = atof(cd.params[1].c_str()); }
1546 	const int minX = (globalRendering->viewSizeX * 1) / 4;
1547 	const int maxX = (globalRendering->viewSizeX * 3) / 4;
1548 	const int effX = std::max(std::min(mouse->lastx, maxX), minX);
1549 	const float factor = float(effX - minX) / float(maxX - minX);
1550 
1551 	return (minV + (factor * (maxV - minV)));
1552 }
1553 
1554 // CALLINFO:
1555 // DrawMapStuff --> GetDefaultCommand
1556 // CMouseHandler::DrawCursor --> DrawCentroidCursor --> GetDefaultCommand
1557 // LuaUnsyncedRead::GetDefaultCommand --> GetDefaultCommand
GetDefaultCommand(int x,int y,const float3 & cameraPos,const float3 & mouseDir) const1558 int CGuiHandler::GetDefaultCommand(int x, int y, const float3& cameraPos, const float3& mouseDir) const
1559 {
1560 	CInputReceiver* ir = NULL;
1561 	if (!game->hideInterface) {
1562 		ir = GetReceiverAt(x, y);
1563 	}
1564 
1565 	if ((ir != NULL) && (ir != minimap)) {
1566 		return -1;
1567 	}
1568 
1569 	int cmdID = -1;
1570 
1571 	{
1572 		CUnit* unit = NULL;
1573 		CFeature* feature = NULL;
1574 		if ((ir == minimap) && (minimap->FullProxy())) {
1575 			unit = minimap->GetSelectUnit(minimap->GetMapPosition(x, y));
1576 		}
1577 		else {
1578 			const float viewRange = globalRendering->viewRange * 1.4f;
1579 			const float dist = TraceRay::GuiTraceRay(cameraPos, mouseDir, viewRange, NULL, unit, feature, true);
1580 			const float3 hit = cameraPos + mouseDir * dist;
1581 
1582 			// make sure the ray hit in the map
1583 			if (!unit && !feature && !hit.IsInBounds())
1584 				return -1;
1585 		}
1586 
1587 		cmdID = selectedUnitsHandler.GetDefaultCmd(unit, feature);
1588 	}
1589 
1590 	// make sure the command is currently available
1591 	for (int c = 0; c < (int)commands.size(); c++) {
1592 		if (cmdID == commands[c].id) {
1593 			return c;
1594 		}
1595 	}
1596 	return -1;
1597 }
1598 
1599 
ProcessLocalActions(const Action & action)1600 bool CGuiHandler::ProcessLocalActions(const Action& action)
1601 {
1602 	// do not process these actions if the control panel is not visible
1603 	if (iconsCount <= 0) {
1604 		return false;
1605 	}
1606 
1607 	// only process the build options while building
1608 	// (conserve the keybinding space where we can)
1609 	if ((inCommand >= 0) && ((size_t)inCommand < commands.size()) &&
1610 			((commands[inCommand].type == CMDTYPE_ICON_BUILDING) ||
1611 			(commands[inCommand].id == CMD_UNLOAD_UNITS))) {
1612 		if (ProcessBuildActions(action)) {
1613 			return true;
1614 		}
1615 	}
1616 
1617 	if (action.command == "buildiconsfirst") {
1618 		activePage = 0;
1619 		selectedUnitsHandler.SetCommandPage(activePage);
1620 		selectedUnitsHandler.ToggleBuildIconsFirst();
1621 		LayoutIcons(false);
1622 		return true;
1623 	}
1624 	else if (action.command == "firstmenu") {
1625 		activePage = 0;
1626 		selectedUnitsHandler.SetCommandPage(activePage);
1627 		return true;
1628 	}
1629 	else if (action.command == "showcommands") {
1630 		// bonus command for debugging
1631 		LOG("Available Commands:");
1632 		for(size_t i = 0; i < commands.size(); ++i){
1633 			LOG("  command: " _STPF_ ", id = %i, action = %s",
1634 					i, commands[i].id, commands[i].action.c_str());
1635 		}
1636 		// show the icon/command linkage
1637 		LOG("Icon Linkage:");
1638 		for(int ii = 0; ii < iconsCount; ++ii){
1639 			LOG("  icon: %i, commandsID = %i", ii, icons[ii].commandsID);
1640 		}
1641 		LOG("maxPage         = %i", maxPage);
1642 		LOG("activePage      = %i", activePage);
1643 		LOG("iconsSize       = %u", iconsSize);
1644 		LOG("iconsCount      = %i", iconsCount);
1645 		LOG("commands.size() = " _STPF_, commands.size());
1646 		return true;
1647 	}
1648 	else if (action.command == "invqueuekey") {
1649 		if (action.extra.empty()) {
1650 			invertQueueKey = !invertQueueKey;
1651 		} else {
1652 			invertQueueKey = !!atoi(action.extra.c_str());
1653 		}
1654 		needShift = false;
1655 		configHandler->Set("InvertQueueKey", invertQueueKey ? 1 : 0);
1656 		return true;
1657 	}
1658 
1659 	return false;
1660 }
1661 
1662 
ProcessBuildActions(const Action & action)1663 bool CGuiHandler::ProcessBuildActions(const Action& action)
1664 {
1665 	const std::string& arg = StringToLower(action.extra);
1666 	bool ret = false;
1667 
1668 	if (action.command == "buildspacing") {
1669 		if (arg == "inc") {
1670 			buildSpacing++;
1671 			ret = true;
1672 		}
1673 		else if (arg == "dec") {
1674 			if (buildSpacing > 0) {
1675 				buildSpacing--;
1676 			}
1677 			ret = true;
1678 		}
1679 	}
1680 	else if (action.command == "buildfacing") {
1681 		static const char* buildFaceDirs[] = {"South", "East", "North", "West"};
1682 
1683 		if (arg == "inc") {
1684 			buildFacing = (buildFacing +               1) % NUM_FACINGS;
1685 			ret = true;
1686 		}
1687 		else if (arg == "dec") {
1688 			buildFacing = (buildFacing + NUM_FACINGS - 1) % NUM_FACINGS;
1689 			ret = true;
1690 		}
1691 		else if (arg == "south") {
1692 			buildFacing = FACING_SOUTH;
1693 			ret = true;
1694 		}
1695 		else if (arg == "east") {
1696 			buildFacing = FACING_EAST;
1697 			ret = true;
1698 		}
1699 		else if (arg == "north") {
1700 			buildFacing = FACING_NORTH;
1701 			ret = true;
1702 		}
1703 		else if (arg == "west") {
1704 			buildFacing = FACING_WEST;
1705 			ret = true;
1706 		}
1707 
1708 		LOG("Buildings set to face %s", buildFaceDirs[buildFacing]);
1709 	}
1710 
1711 	return ret;
1712 }
1713 
1714 
GetIconPosCommand(int slot) const1715 int CGuiHandler::GetIconPosCommand(int slot) const // only called by SetActiveCommand
1716 {
1717 	if (slot < 0) {
1718 		return -1;
1719 	}
1720 	const int iconPos = slot + (activePage * iconsPerPage);
1721 	if (iconPos < iconsCount) {
1722 		return icons[iconPos].commandsID;
1723 	}
1724 	return -1;
1725 }
1726 
1727 
KeyPressed(int key,bool isRepeat)1728 bool CGuiHandler::KeyPressed(int key, bool isRepeat)
1729 {
1730 	if (key == SDLK_ESCAPE && activeMousePress) {
1731 		activeMousePress = false;
1732 		inCommand = -1;
1733 		SetShowingMetal(false);
1734 		return true;
1735 	}
1736 	if (key == SDLK_ESCAPE && inCommand >= 0) {
1737 		inCommand=-1;
1738 		SetShowingMetal(false);
1739 		return true;
1740 	}
1741 
1742 	const CKeySet ks(key, false);
1743 
1744 	// setup actionOffset
1745 	//WTF a bit more documentation???
1746 	int tmpActionOffset = actionOffset;
1747 	if ((inCommand < 0) || (lastKeySet.Key() < 0)){
1748 		actionOffset = 0;
1749 		tmpActionOffset = 0;
1750 		lastKeySet.Reset();
1751 	}
1752 	else if (!ks.IsPureModifier()) {
1753 		// not a modifier
1754 		if ((ks == lastKeySet) && (ks.Key() >= 0)) {
1755 			actionOffset++;
1756 			tmpActionOffset = actionOffset;
1757 		} else {
1758 			tmpActionOffset = 0;
1759 		}
1760 	}
1761 
1762 	const CKeyBindings::ActionList& al = keyBindings->GetActionList(ks);
1763 	for (int ali = 0; ali < (int)al.size(); ++ali) {
1764 		const int actionIndex = (ali + tmpActionOffset) % (int)al.size(); //????
1765 		const Action& action = al[actionIndex];
1766 		if (SetActiveCommand(action, ks, actionIndex)) {
1767 			return true;
1768 		}
1769 	}
1770 
1771 	return false;
1772 }
1773 
1774 
SetActiveCommand(const Action & action,const CKeySet & ks,int actionIndex)1775 bool CGuiHandler::SetActiveCommand(const Action& action,
1776                                    const CKeySet& ks, int actionIndex)
1777 {
1778 	if (ProcessLocalActions(action)) {
1779 		return true;
1780 	}
1781 
1782 	// See if we have a positional icon command
1783 	int iconCmd = -1;
1784 	if (!action.extra.empty() && (action.command == "iconpos")) {
1785 		const int iconSlot = ParseIconSlot(action.extra);
1786 		iconCmd = GetIconPosCommand(iconSlot);
1787 	}
1788 
1789 	for (size_t a = 0; a < commands.size(); ++a) {
1790 
1791 		CommandDescription& cmdDesc = commands[a];
1792 
1793 		if ((static_cast<int>(a) != iconCmd) && (cmdDesc.action != action.command)) {
1794 			continue; // not a match
1795 		}
1796 
1797 		if (cmdDesc.disabled) {
1798 			continue; // can not use this command
1799 		}
1800 
1801 		const int cmdType = cmdDesc.type;
1802 
1803 		// set the activePage
1804 		if (!cmdDesc.hidden &&
1805 				(((cmdType == CMDTYPE_ICON) &&
1806 					 ((cmdDesc.id < 0) ||
1807 						(cmdDesc.id == CMD_STOCKPILE))) ||
1808 				 (cmdType == CMDTYPE_ICON_MODE) ||
1809 				 (cmdType == CMDTYPE_ICON_BUILDING))) {
1810 			for (int ii = 0; ii < iconsCount; ii++) {
1811 				if (icons[ii].commandsID == static_cast<int>(a)) {
1812 					activePage = std::min(maxPage, (ii / iconsPerPage));
1813 					selectedUnitsHandler.SetCommandPage(activePage);
1814 				}
1815 			}
1816 		}
1817 
1818 		switch (cmdType) {
1819 			case CMDTYPE_ICON:{
1820 				Command c(cmdDesc.id);
1821 				if ((cmdDesc.id < 0) || (cmdDesc.id == CMD_STOCKPILE)) {
1822 					if (action.extra == "+5") {
1823 						c.options = SHIFT_KEY;
1824 					} else if (action.extra == "+20") {
1825 						c.options = CONTROL_KEY;
1826 					} else if (action.extra == "+100") {
1827 						c.options = SHIFT_KEY | CONTROL_KEY;
1828 					} else if (action.extra == "-1") {
1829 						c.options = RIGHT_MOUSE_KEY;
1830 					} else if (action.extra == "-5") {
1831 						c.options = RIGHT_MOUSE_KEY | SHIFT_KEY;
1832 					} else if (action.extra == "-20") {
1833 						c.options = RIGHT_MOUSE_KEY | CONTROL_KEY;
1834 					} else if (action.extra == "-100") {
1835 						c.options = RIGHT_MOUSE_KEY | SHIFT_KEY | CONTROL_KEY;
1836 					}
1837 				}
1838 				else if (action.extra.find("queued") != std::string::npos) {
1839 					c.options |= SHIFT_KEY;
1840 				}
1841 				GiveCommand(c);
1842 				break;
1843 			}
1844 			case CMDTYPE_ICON_MODE: {
1845 				int newMode;
1846 
1847 				if (!action.extra.empty() && (iconCmd < 0)) {
1848 					newMode = atoi(action.extra.c_str());
1849 				} else {
1850 					newMode = atoi(cmdDesc.params[0].c_str()) + 1;
1851 				}
1852 
1853 				if ((newMode < 0) || ((size_t)newMode > (cmdDesc.params.size() - 2))) {
1854 					newMode = 0;
1855 				}
1856 
1857 				// not really required
1858 				char t[10];
1859 				SNPRINTF(t, 10, "%d", newMode);
1860 				cmdDesc.params[0] = t;
1861 
1862 				Command c(cmdDesc.id, 0, newMode);
1863 				GiveCommand(c);
1864 				forceLayoutUpdate = true;
1865 				break;
1866 			}
1867 			case CMDTYPE_NUMBER:{
1868 				if (!action.extra.empty()) {
1869 					const CommandDescription& cd = cmdDesc;
1870 					float value = atof(action.extra.c_str());
1871 					float minV = 0.0f;
1872 					float maxV = 100.0f;
1873 					if (!cd.params.empty()) { minV = atof(cd.params[0].c_str()); }
1874 					if (cd.params.size() >= 2) { maxV = atof(cd.params[1].c_str()); }
1875 					value = std::max(std::min(value, maxV), minV);
1876 					Command c(cd.id, 0, value);
1877 					if (action.extra.find("queued") != std::string::npos) {
1878 						c.options = SHIFT_KEY;
1879 					}
1880 					GiveCommand(c);
1881 					break;
1882 				}
1883 				else {
1884 					// fall through
1885 				}
1886 			}
1887 			case CMDTYPE_ICON_MAP:
1888 			case CMDTYPE_ICON_AREA:
1889 			case CMDTYPE_ICON_UNIT:
1890 			case CMDTYPE_ICON_UNIT_OR_MAP:
1891 			case CMDTYPE_ICON_FRONT:
1892 			case CMDTYPE_ICON_UNIT_OR_AREA:
1893 			case CMDTYPE_ICON_UNIT_OR_RECTANGLE:
1894 			case CMDTYPE_ICON_UNIT_FEATURE_OR_AREA: {
1895 				SetShowingMetal(false);
1896 				actionOffset = actionIndex;
1897 				lastKeySet = ks;
1898 				inCommand = a;
1899 				break;
1900 			}
1901 			case CMDTYPE_ICON_BUILDING: {
1902 				const UnitDef* ud=unitDefHandler->GetUnitDefByID(-cmdDesc.id);
1903 				SetShowingMetal(ud->extractsMetal > 0);
1904 				actionOffset = actionIndex;
1905 				lastKeySet = ks;
1906 				inCommand = a;
1907 				break;
1908 			}
1909 			case CMDTYPE_NEXT: {
1910 				++activePage;
1911 				if(activePage > maxPage)
1912 					activePage=0;
1913 				selectedUnitsHandler.SetCommandPage(activePage);
1914 				break;
1915 			}
1916 			case CMDTYPE_PREV:{
1917 				--activePage;
1918 				if(activePage<0)
1919 					activePage = maxPage;
1920 				selectedUnitsHandler.SetCommandPage(activePage);
1921 				break;
1922 			}
1923 			case CMDTYPE_CUSTOM: {
1924 				RunCustomCommands(cmdDesc.params, false);
1925 				break;
1926 			}
1927 			default:{
1928 				lastKeySet.Reset();
1929 				SetShowingMetal(false);
1930 				inCommand = a;
1931 			}
1932 		}
1933 		return true; // we used the command
1934 	}
1935 
1936 	return false;	// couldn't find a match
1937 }
1938 
1939 
KeyReleased(int key)1940 bool CGuiHandler::KeyReleased(int key)
1941 {
1942 	return false;
1943 }
1944 
FinishCommand(int button)1945 void CGuiHandler::FinishCommand(int button)
1946 {
1947 	if ((button == SDL_BUTTON_LEFT) && (KeyInput::GetKeyModState(KMOD_SHIFT) || invertQueueKey)) {
1948 		needShift = true;
1949 	} else {
1950 		SetShowingMetal(false);
1951 		inCommand = -1;
1952 	}
1953 }
1954 
1955 
IsAbove(int x,int y)1956 bool CGuiHandler::IsAbove(int x, int y)
1957 {
1958 	return AboveGui(x, y);
1959 }
1960 
1961 
GetTooltip(int x,int y)1962 std::string CGuiHandler::GetTooltip(int x, int y)
1963 {
1964 	std::string s;
1965 
1966 	const int iconPos = IconAtPos(x, y);
1967 	const int iconCmd = (iconPos >= 0) ? icons[iconPos].commandsID : -1;
1968 	if ((iconCmd >= 0) && (iconCmd < (int)commands.size())) {
1969 		if (commands[iconCmd].tooltip != "") {
1970 			s = commands[iconCmd].tooltip;
1971 		}else{
1972 			s = commands[iconCmd].name;
1973 		}
1974 
1975 		const CKeyBindings::HotkeyList& hl = keyBindings->GetHotkeys(commands[iconCmd].action);
1976 		if(!hl.empty()){
1977 			s += "\nHotkeys:";
1978 			for (const std::string& hk: hl) {
1979 				s += " " + hk;
1980 			}
1981 		}
1982 	}
1983 	return s;
1984 }
1985 
1986 // CALLINFO:
1987 // luaunsyncedread::getcurrenttooltip --> mousehandler::getcurrenttooltip
1988 // tooltipconsole::draw --> mousehandler::getcurrenttooltip
1989 // mousehandler::getcurrenttooltip --> GetBuildTooltip
1990 // mousehandler::getcurrenttooltip --> CMiniMap::gettooltip --> GetBuildTooltip
GetBuildTooltip() const1991 std::string CGuiHandler::GetBuildTooltip() const
1992 {
1993 	if ((inCommand >= 0) && ((size_t)inCommand < commands.size()) &&
1994 	    (commands[inCommand].type == CMDTYPE_ICON_BUILDING)) {
1995 		return commands[inCommand].tooltip;
1996 	}
1997 	return std::string("");
1998 }
1999 
2000 
GetOrderPreview()2001 Command CGuiHandler::GetOrderPreview()
2002 {
2003 	return GetCommand(mouse->lastx, mouse->lasty, -1, true);
2004 }
2005 
2006 
CheckCommand(Command c)2007 inline Command CheckCommand(Command c) {
2008 	if (selectedUnitsHandler.selectedUnits.empty() || (c.options & SHIFT_KEY))
2009 		return c; // always allow queued commands, since conditions may change so the command becomes valid
2010 	for (CUnitSet::iterator ui = selectedUnitsHandler.selectedUnits.begin(); ui != selectedUnitsHandler.selectedUnits.end(); ++ui) {
2011 		if((*ui)->commandAI->AllowedCommand(c, false))
2012 			return c;
2013 	}
2014 	Command failedRet(CMD_FAILED);
2015 	return failedRet;
2016 }
2017 
ZeroRadiusAllowed(const Command & c)2018 bool ZeroRadiusAllowed(const Command &c) {
2019 	switch(c.GetID()) {
2020 	case CMD_CAPTURE:
2021 	case CMD_GUARD:
2022 	case CMD_LOAD_UNITS:
2023 	case CMD_RECLAIM:
2024 	case CMD_REPAIR:
2025 	case CMD_RESTORE:
2026 	case CMD_RESURRECT:
2027 		return false;
2028 	}
2029 	return true;
2030 }
2031 
GetCommand(int mouseX,int mouseY,int buttonHint,bool preview,const float3 & cameraPos,const float3 & mouseDir)2032 Command CGuiHandler::GetCommand(int mouseX, int mouseY, int buttonHint, bool preview, const float3& cameraPos, const float3& mouseDir)
2033 {
2034 	Command defaultRet(CMD_FAILED);
2035 
2036 	int button;
2037 	if (buttonHint >= SDL_BUTTON_LEFT) {
2038 		button = buttonHint;
2039 	} else if (inCommand != -1) {
2040 		button = SDL_BUTTON_LEFT;
2041 	} else if (mouse->buttons[SDL_BUTTON_RIGHT].pressed) {
2042 		button = SDL_BUTTON_RIGHT;
2043 	} else {
2044 		return Command(CMD_STOP);
2045 	}
2046 
2047 	int tempInCommand = inCommand;
2048 
2049 	if (button == SDL_BUTTON_RIGHT && preview) {
2050 		// right click -> default cmd
2051 		// (in preview we might not have default cmd memory set)
2052 		if (mouse->buttons[SDL_BUTTON_RIGHT].pressed) {
2053 			tempInCommand = defaultCmdMemory;
2054 		} else {
2055 			tempInCommand = GetDefaultCommand(mouseX, mouseY, cameraPos, mouseDir);
2056 		}
2057 	}
2058 
2059 	if ((tempInCommand >= 0) && ((size_t)tempInCommand < commands.size())) {
2060 		switch(commands[tempInCommand].type) {
2061 
2062 		case CMDTYPE_NUMBER: {
2063 			const float value = GetNumberInput(commands[tempInCommand]);
2064 			Command c(commands[tempInCommand].id, CreateOptions(button), value);
2065 			return CheckCommand(c);
2066 		}
2067 
2068 		case CMDTYPE_ICON: {
2069 			Command c(commands[tempInCommand].id, CreateOptions(button));
2070 			if (button == SDL_BUTTON_LEFT && !preview) {
2071 				LOG_L(L_WARNING, "CMDTYPE_ICON left button press in incommand test? This should not happen.");
2072 			}
2073 			return CheckCommand(c);
2074 		}
2075 
2076 		case CMDTYPE_ICON_MAP: {
2077 			const float dist = CGround::LineGroundCol(cameraPos, cameraPos + (mouseDir * globalRendering->viewRange * 1.4f), false);
2078 			if (dist < 0) {
2079 				return defaultRet;
2080 			}
2081 			const float3 pos = cameraPos + (mouseDir * dist);
2082 			Command c(commands[tempInCommand].id, CreateOptions(button), pos);
2083 			return CheckCommand(c);
2084 		}
2085 
2086 		case CMDTYPE_ICON_BUILDING: {
2087 			const float dist = CGround::LineGroundCol(cameraPos, cameraPos + mouseDir * globalRendering->viewRange * 1.4f, false);
2088 			if (dist < 0) {
2089 				return defaultRet;
2090 			}
2091 			const UnitDef* unitdef = unitDefHandler->GetUnitDefByID(-commands[inCommand].id);
2092 
2093 			if(!unitdef){
2094 				return Command(CMD_STOP);
2095 			}
2096 
2097 			const float3 pos = cameraPos + mouseDir * dist;
2098 			std::vector<BuildInfo> buildPos;
2099 			BuildInfo bi(unitdef, pos, buildFacing);
2100 			if (GetQueueKeystate() && (button == SDL_BUTTON_LEFT)) {
2101 				const float dist = CGround::LineGroundCol(
2102 					mouse->buttons[SDL_BUTTON_LEFT].camPos,
2103 					mouse->buttons[SDL_BUTTON_LEFT].camPos +
2104 					mouse->buttons[SDL_BUTTON_LEFT].dir * globalRendering->viewRange * 1.4f,
2105 					false
2106 				);
2107 				const float3 pos2 = mouse->buttons[SDL_BUTTON_LEFT].camPos + mouse->buttons[SDL_BUTTON_LEFT].dir * dist;
2108 				buildPos = GetBuildPos(BuildInfo(unitdef, pos2, buildFacing), bi, cameraPos, mouseDir);
2109 			} else
2110 				buildPos = GetBuildPos(bi, bi, cameraPos, mouseDir);
2111 
2112 			if (buildPos.empty()) {
2113 				return Command(CMD_STOP);
2114 			}
2115 
2116 			if (buildPos.size() == 1) {
2117 				CFeature* feature = NULL;
2118 				// TODO Maybe also check out-of-range for immobile builder?
2119 				if (!CGameHelper::TestUnitBuildSquare(buildPos[0], feature, gu->myAllyTeam, false)) {
2120 					return defaultRet;
2121 				}
2122 			}
2123 
2124 			int a = 0; // limit the number of max commands possible to send to avoid overflowing the network buffer
2125 			for (std::vector<BuildInfo>::iterator bpi = buildPos.begin(); bpi != --buildPos.end() && a < 200; ++bpi) {
2126 				++a;
2127 				Command c = bpi->CreateCommand(CreateOptions(button));
2128 				if (!preview) {
2129 					GiveCommand(c);
2130 				}
2131 			}
2132 			Command c = buildPos.back().CreateCommand(CreateOptions(button));
2133 			return CheckCommand(c);
2134 		}
2135 
2136 		case CMDTYPE_ICON_UNIT: {
2137 			CUnit* unit = NULL;
2138 			CFeature* feature = NULL;
2139 			Command c(commands[tempInCommand].id, CreateOptions(button));
2140 
2141 			TraceRay::GuiTraceRay(cameraPos, mouseDir, globalRendering->viewRange * 1.4f, NULL, unit, feature, true);
2142 			if (!unit) {
2143 				return defaultRet;
2144 			}
2145 			c.PushParam(unit->id);
2146 			return CheckCommand(c);
2147 		}
2148 
2149 		case CMDTYPE_ICON_UNIT_OR_MAP: {
2150 			Command c(commands[tempInCommand].id, CreateOptions(button));
2151 
2152 			CUnit* unit = NULL;
2153 			CFeature* feature = NULL;
2154 			const float dist2 = TraceRay::GuiTraceRay(cameraPos, mouseDir, globalRendering->viewRange * 1.4f, NULL, unit, feature, true);
2155 			if (dist2 > (globalRendering->viewRange * 1.4f - 300)) {
2156 				return defaultRet;
2157 			}
2158 
2159 			if (unit) {
2160 				// clicked on unit
2161 				c.PushParam(unit->id);
2162 			} else {
2163 				// clicked in map
2164 				c.PushPos(cameraPos + (mouseDir * dist2));
2165 			}
2166 			return CheckCommand(c);
2167 		}
2168 
2169 		case CMDTYPE_ICON_FRONT: {
2170 			float dist = CGround::LineGroundCol(
2171 				mouse->buttons[button].camPos,
2172 				mouse->buttons[button].camPos +
2173 				mouse->buttons[button].dir * globalRendering->viewRange * 1.4f,
2174 				false
2175 			);
2176 			if (dist < 0) {
2177 				return defaultRet;
2178 			}
2179 
2180 			float3 pos = mouse->buttons[button].camPos + (mouse->buttons[button].dir * dist);
2181 
2182 			Command c(commands[tempInCommand].id, CreateOptions(button), pos);
2183 
2184 			if (mouse->buttons[button].movement > 30) { // only create the front if the mouse has moved enough
2185 				dist = CGround::LineGroundCol(cameraPos, cameraPos + mouseDir * globalRendering->viewRange * 1.4f, false);
2186 				if (dist < 0) {
2187 					return defaultRet;
2188 				}
2189 				float3 pos2 = cameraPos + (mouseDir * dist);
2190 
2191 				ProcessFrontPositions(pos, pos2);
2192 
2193 				c.SetPos(0, pos);
2194 
2195 				if (!commands[tempInCommand].params.empty() &&
2196 				    pos.SqDistance2D(pos2) > Square(atof(commands[tempInCommand].params[0].c_str()))) {
2197 					float3 dif = pos2 - pos;
2198 					dif.ANormalize();
2199 					pos2 = pos + dif * atoi(commands[tempInCommand].params[0].c_str());
2200 				}
2201 
2202 				c.PushPos(pos2);
2203 			}
2204 			return CheckCommand(c);
2205 		}
2206 
2207 		case CMDTYPE_ICON_UNIT_OR_AREA:
2208 		case CMDTYPE_ICON_UNIT_FEATURE_OR_AREA:
2209 		case CMDTYPE_ICON_AREA: {
2210 			float maxRadius = 100000;
2211 			if (commands[tempInCommand].params.size() == 1) {
2212 				maxRadius=atof(commands[tempInCommand].params[0].c_str());
2213 			}
2214 
2215 			Command c(commands[tempInCommand].id, CreateOptions(button));
2216 
2217 			if (mouse->buttons[button].movement < 4) {
2218 				CUnit* unit = NULL;
2219 				CFeature* feature = NULL;
2220 				const float dist2 = TraceRay::GuiTraceRay(cameraPos, mouseDir, globalRendering->viewRange * 1.4f, NULL, unit, feature, true);
2221 
2222 				if (dist2 > (globalRendering->viewRange * 1.4f - 300) && (commands[tempInCommand].type != CMDTYPE_ICON_UNIT_FEATURE_OR_AREA)) {
2223 					return defaultRet;
2224 				}
2225 
2226 				if (feature && commands[tempInCommand].type == CMDTYPE_ICON_UNIT_FEATURE_OR_AREA) { // clicked on feature
2227 					c.PushParam(unitHandler->MaxUnits() + feature->id);
2228 				} else if (unit && commands[tempInCommand].type != CMDTYPE_ICON_AREA) { // clicked on unit
2229 					if (c.GetID() == CMD_RESURRECT)
2230 						return defaultRet; // cannot resurrect units!
2231 					c.PushParam(unit->id);
2232 				} else { // clicked in map
2233 					if (explicitCommand < 0 || !ZeroRadiusAllowed(c)) // only attack ground if explicitly set the command
2234 						return defaultRet;
2235 					c.PushPos(cameraPos + (mouseDir * dist2));
2236 					c.PushParam(0); // zero radius
2237 					if (c.GetID() == CMD_UNLOAD_UNITS) {
2238 						c.PushParam((float)buildFacing);
2239 					}
2240 				}
2241 			} else { // created area
2242 				float dist = CGround::LineGroundCol(
2243 					mouse->buttons[button].camPos,
2244 					mouse->buttons[button].camPos +
2245 					mouse->buttons[button].dir * globalRendering->viewRange * 1.4f,
2246 					false
2247 				);
2248 
2249 				if (dist < 0) {
2250 					return defaultRet;
2251 				}
2252 				const float3 pos = mouse->buttons[button].camPos + mouse->buttons[button].dir * dist;
2253 				c.PushPos(pos);
2254 				dist = CGround::LineGroundCol(cameraPos, cameraPos + mouseDir * globalRendering->viewRange * 1.4f, false);
2255 				if (dist < 0) {
2256 					return defaultRet;
2257 				}
2258 				const float3 pos2 = cameraPos + mouseDir * dist;
2259 				c.PushParam(std::min(maxRadius, pos.distance2D(pos2)));
2260 				if (c.GetID() == CMD_UNLOAD_UNITS) {
2261 					c.PushParam((float)buildFacing);
2262 				}
2263 			}
2264 			return CheckCommand(c);
2265 		}
2266 
2267 		case CMDTYPE_ICON_UNIT_OR_RECTANGLE: {
2268 			Command c(commands[tempInCommand].id, CreateOptions(button));
2269 
2270 			if (mouse->buttons[button].movement < 16) {
2271 				CUnit* unit;
2272 				CFeature* feature;
2273 				const float dist2 = TraceRay::GuiTraceRay(cameraPos, mouseDir, globalRendering->viewRange * 1.4f, NULL, unit, feature, true);
2274 
2275 				if (dist2 > (globalRendering->viewRange * 1.4f - 300)) {
2276 					return defaultRet;
2277 				}
2278 
2279 				if (unit) {
2280 					// clicked on unit
2281 					c.PushParam(unit->id);
2282 				} else {
2283 					// clicked in map
2284 					if (explicitCommand < 0) { // only attack ground if explicitly set the command
2285 						return defaultRet;
2286 					}
2287 					c.PushPos(cameraPos + (mouseDir * dist2));
2288 				}
2289 			} else {
2290 				// created rectangle
2291 				float dist = CGround::LineGroundCol(
2292 					mouse->buttons[button].camPos,
2293 					mouse->buttons[button].camPos +
2294 					mouse->buttons[button].dir * globalRendering->viewRange * 1.4f,
2295 					false
2296 				);
2297 				if (dist < 0) {
2298 					return defaultRet;
2299 				}
2300 				const float3 startPos = mouse->buttons[button].camPos + mouse->buttons[button].dir * dist;
2301 				dist = CGround::LineGroundCol(cameraPos, cameraPos + mouseDir * globalRendering->viewRange * 1.4f, false);
2302 				if (dist < 0) {
2303 					return defaultRet;
2304 				}
2305 				const float3 endPos = cameraPos + (mouseDir * dist);
2306 				c.PushPos(startPos);
2307 				c.PushPos(endPos);
2308 			}
2309 			return CheckCommand(c);
2310 		}
2311 
2312 		default:
2313 			return Command(CMD_STOP);
2314 		}
2315 	} else {
2316 		if (!preview) {
2317 			inCommand = -1;
2318 		}
2319 	}
2320 	return Command(CMD_STOP);
2321 }
2322 
2323 
2324 
WouldCancelAnyQueued(const BuildInfo & b)2325 static bool WouldCancelAnyQueued(const BuildInfo& b)
2326 {
2327 	Command c = b.CreateCommand();
2328 	CUnitSet::iterator ui = selectedUnitsHandler.selectedUnits.begin();
2329 	for (; ui != selectedUnitsHandler.selectedUnits.end(); ++ui) {
2330 		if ((*ui)->commandAI->WillCancelQueued(c)) {
2331 			return true;
2332 		}
2333 	}
2334 	return false;
2335 }
2336 
FillRowOfBuildPos(const BuildInfo & startInfo,float x,float z,float xstep,float zstep,int n,int facing,bool nocancel,std::vector<BuildInfo> & ret)2337 static void FillRowOfBuildPos(const BuildInfo& startInfo, float x, float z, float xstep, float zstep, int n, int facing, bool nocancel, std::vector<BuildInfo>& ret)
2338 {
2339 	for (int i = 0; i < n; ++i) {
2340 		BuildInfo bi(startInfo.def, float3(x, 0.0f, z), (startInfo.buildFacing + facing) % NUM_FACINGS);
2341 		bi.pos=CGameHelper::Pos2BuildPos(bi, false);
2342 		if (!nocancel || !WouldCancelAnyQueued(bi)) {
2343 			ret.push_back(bi);
2344 		}
2345 		x += xstep;
2346 		z += zstep;
2347 	}
2348 }
2349 
2350 // Assuming both builds have the same unitdef
GetBuildPos(const BuildInfo & startInfo,const BuildInfo & endInfo,const float3 & cameraPos,const float3 & mouseDir)2351 std::vector<BuildInfo> CGuiHandler::GetBuildPos(const BuildInfo& startInfo, const BuildInfo& endInfo, const float3& cameraPos, const float3& mouseDir)
2352 {
2353 	std::vector<BuildInfo> ret;
2354 
2355 	float3 start = CGameHelper::Pos2BuildPos(startInfo, false);
2356 	float3 end = CGameHelper::Pos2BuildPos(endInfo, false);
2357 
2358 	BuildInfo other; // the unit around which buildings can be circled
2359 
2360 	if (GetQueueKeystate() && KeyInput::GetKeyModState(KMOD_CTRL)) {
2361 		CUnit* unit;
2362 		CFeature* feature;
2363 		TraceRay::GuiTraceRay(cameraPos, mouseDir, globalRendering->viewRange * 1.4f, NULL, unit, feature, true);
2364 
2365 		if (unit) {
2366 			other.def = unit->unitDef;
2367 			other.pos = unit->pos;
2368 			other.buildFacing = unit->buildFacing;
2369 		} else {
2370 			Command c = CGameHelper::GetBuildCommand(cameraPos, mouseDir);
2371 			if (c.GetID() < 0 && c.params.size() == 4) {
2372 				other.pos = c.GetPos(0);
2373 				other.def = unitDefHandler->GetUnitDefByID(-c.GetID());
2374 				other.buildFacing = int(c.params[3]);
2375 			}
2376 		}
2377 	}
2378 
2379 	if (other.def && GetQueueKeystate() && KeyInput::GetKeyModState(KMOD_CTRL)) {
2380 		// circle build around building
2381 		int oxsize = other.GetXSize() * SQUARE_SIZE;
2382 		int ozsize = other.GetZSize() * SQUARE_SIZE;
2383 		int xsize = startInfo.GetXSize() * SQUARE_SIZE;
2384 		int zsize = startInfo.GetZSize() * SQUARE_SIZE;
2385 
2386 		start = end = CGameHelper::Pos2BuildPos(other, false);
2387 		start.x -= oxsize / 2;
2388 		start.z -= ozsize / 2;
2389 		end.x += oxsize / 2;
2390 		end.z += ozsize / 2;
2391 
2392 		int nvert = 1 + (oxsize / xsize);
2393 		int nhori = 1 + (ozsize / xsize);
2394 
2395 		FillRowOfBuildPos(startInfo, end.x   + zsize / 2, start.z + xsize / 2,      0,  xsize, nhori, 3, true, ret);
2396 		FillRowOfBuildPos(startInfo, end.x   - xsize / 2, end.z   + zsize / 2, -xsize,      0, nvert, 2, true, ret);
2397 		FillRowOfBuildPos(startInfo, start.x - zsize / 2, end.z   - xsize / 2,      0, -xsize, nhori, 1, true, ret);
2398 		FillRowOfBuildPos(startInfo, start.x + xsize / 2, start.z - zsize / 2,  xsize,      0, nvert, 0, true, ret);
2399 
2400 	} else { // rectangle or line
2401 		const float3 delta = end - start;
2402 
2403 		const float xsize = SQUARE_SIZE * (startInfo.GetXSize() + buildSpacing * 2);
2404 		const int xnum = (int)((math::fabs(delta.x) + xsize * 1.4f)/xsize);
2405 		float xstep = (int)((0 < delta.x) ? xsize : -xsize);
2406 
2407 		const float zsize = SQUARE_SIZE * (startInfo.GetZSize() + buildSpacing * 2);
2408 		const int znum = (int)((math::fabs(delta.z) + zsize * 1.4f)/zsize);
2409 		float zstep = (int)((0 < delta.z) ? zsize : -zsize);
2410 
2411 		if (KeyInput::GetKeyModState(KMOD_ALT)) { // build a rectangle
2412 			if (KeyInput::GetKeyModState(KMOD_CTRL)) { // hollow rectangle
2413 				if ((1 < xnum) && (1 < znum)) {
2414 					// go "down" on the "left" side
2415 					FillRowOfBuildPos(startInfo, start.x                     , start.z + zstep             ,      0,  zstep, znum - 1, 0, false, ret);
2416 					// go "right" on the "bottom" side
2417 					FillRowOfBuildPos(startInfo, start.x + xstep             , start.z + (znum - 1) * zstep,  xstep,      0, xnum - 1, 0, false, ret);
2418 					// go "up" on the "right" side
2419 					FillRowOfBuildPos(startInfo, start.x + (xnum - 1) * xstep, start.z + (znum - 2) * zstep,      0, -zstep, znum - 1, 0, false, ret);
2420 					// go "left" on the "top" side
2421 					FillRowOfBuildPos(startInfo, start.x + (xnum - 2) * xstep, start.z                     , -xstep,      0, xnum - 1, 0, false, ret);
2422 				} else if (1 == xnum) {
2423 					FillRowOfBuildPos(startInfo, start.x, start.z, 0, zstep, znum, 0, false, ret);
2424 				} else if (1 == znum) {
2425 					FillRowOfBuildPos(startInfo, start.x, start.z, xstep, 0, xnum, 0, false, ret);
2426 				}
2427 			} else { // filled rectangle
2428 				int zn = 0;
2429 				for (float z = start.z; zn < znum; ++zn) {
2430 					if (zn & 1) {
2431 						// every odd line "right" to "left"
2432 						FillRowOfBuildPos(startInfo, start.x + (xnum - 1) * xstep, z, -xstep, 0, xnum, 0, false, ret);
2433 					} else {
2434 						// every even line "left" to "right"
2435 						FillRowOfBuildPos(startInfo, start.x                     , z,  xstep, 0, xnum, 0, false, ret);
2436 					}
2437 					z += zstep;
2438 				}
2439 			}
2440 		} else { // build a line
2441 			const bool xDominatesZ = math::fabs(delta.x) > math::fabs(delta.z);
2442 			if (xDominatesZ) {
2443 				zstep = KeyInput::GetKeyModState(KMOD_CTRL) ? 0 : xstep * delta.z / (delta.x ? delta.x : 1);
2444 			} else {
2445 				xstep = KeyInput::GetKeyModState(KMOD_CTRL) ? 0 : zstep * delta.x / (delta.z ? delta.z : 1);
2446 			}
2447 			FillRowOfBuildPos(startInfo, start.x, start.z, xstep, zstep, xDominatesZ ? xnum : znum, 0, false, ret);
2448 		}
2449 	}
2450 	return ret;
2451 }
2452 
2453 
ProcessFrontPositions(float3 & pos0,const float3 & pos1)2454 void CGuiHandler::ProcessFrontPositions(float3& pos0, const float3& pos1)
2455 {
2456 	if (!frontByEnds) {
2457 		return; // leave it centered
2458 	}
2459 	pos0 = pos1 + ((pos0 - pos1) * 0.5f);
2460 	pos0.y = CGround::GetHeightReal(pos0.x, pos0.z, false);
2461 }
2462 
2463 
2464 /******************************************************************************/
2465 /******************************************************************************/
2466 
Draw()2467 void CGuiHandler::Draw()
2468 {
2469 	if ((iconsCount <= 0) && (luaUI == NULL)) {
2470 		return;
2471 	}
2472 
2473 	glPushAttrib(GL_ENABLE_BIT);
2474 
2475 	glDisable(GL_FOG);
2476 	glDisable(GL_DEPTH_TEST);
2477 	glDisable(GL_LIGHTING);
2478 	glDisable(GL_TEXTURE_2D);
2479 	glEnable(GL_BLEND);
2480 	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
2481 	glEnable(GL_ALPHA_TEST);
2482 	glAlphaFunc(GL_GEQUAL, 0.01f);
2483 
2484 	if (iconsCount > 0) {
2485 		DrawButtons();
2486 	}
2487 
2488 	glPopAttrib();
2489 }
2490 
2491 
FindCornerText(const std::string & corner,const vector<std::string> & params)2492 static std::string FindCornerText(const std::string& corner, const vector<std::string>& params)
2493 {
2494 	for (int p = 0; p < (int)params.size(); p++) {
2495 		if (params[p].find(corner) == 0) {
2496 			return params[p].substr(corner.length());
2497 		}
2498 	}
2499 	return std::string("");
2500 }
2501 
2502 
DrawCustomButton(const IconInfo & icon,bool highlight)2503 void CGuiHandler::DrawCustomButton(const IconInfo& icon, bool highlight)
2504 {
2505 	const CommandDescription& cmdDesc = commands[icon.commandsID];
2506 
2507 	const bool usedTexture = DrawTexture(icon, cmdDesc.iconname);
2508 
2509 	// highlight overlay before text is applied
2510 	if (highlight) {
2511 		DrawHilightQuad(icon);
2512 	}
2513 
2514 	if (!usedTexture || !cmdDesc.onlyTexture) {
2515 		DrawName(icon, cmdDesc.name, false);
2516 	}
2517 
2518 	DrawNWtext(icon, FindCornerText("$nw$", cmdDesc.params));
2519 	DrawSWtext(icon, FindCornerText("$sw$", cmdDesc.params));
2520 	DrawNEtext(icon, FindCornerText("$ne$", cmdDesc.params));
2521 	DrawSEtext(icon, FindCornerText("$se$", cmdDesc.params));
2522 
2523 	if (!usedTexture) {
2524 		glColor4f(1.0f, 1.0f, 1.0f, 0.1f);
2525 		DrawIconFrame(icon);
2526 	}
2527 }
2528 
2529 
DrawUnitBuildIcon(const IconInfo & icon,int unitDefID)2530 bool CGuiHandler::DrawUnitBuildIcon(const IconInfo& icon, int unitDefID)
2531 {
2532 	const UnitDef* ud = unitDefHandler->GetUnitDefByID(unitDefID);
2533 	if (ud) {
2534 		const Box& b = icon.visual;
2535 		glEnable(GL_TEXTURE_2D);
2536 		glColor4f(1.0f, 1.0f, 1.0f, textureAlpha);
2537 		glBindTexture(GL_TEXTURE_2D, unitDefHandler->GetUnitDefImage(ud));
2538 		glBegin(GL_QUADS);
2539 			glTexCoord2f(0.0f, 0.0f); glVertex2f(b.x1, b.y1);
2540 			glTexCoord2f(1.0f, 0.0f); glVertex2f(b.x2, b.y1);
2541 			glTexCoord2f(1.0f, 1.0f); glVertex2f(b.x2, b.y2);
2542 			glTexCoord2f(0.0f, 1.0f); glVertex2f(b.x1, b.y2);
2543 		glEnd();
2544 		return true;
2545 	}
2546 
2547 	return false;
2548 }
2549 
2550 
ParseTextures(const std::string & texString,std::string & tex1,std::string & tex2,float & xscale,float & yscale)2551 static inline bool ParseTextures(const std::string& texString,
2552 		std::string& tex1, std::string& tex2, float& xscale, float& yscale)
2553 {
2554 	// format:  "&<xscale>x<yscale>&<tex>1&<tex2>"  --  <>'s are not included
2555 
2556 	char* endPtr;
2557 
2558 	const char* c = texString.c_str() + 1;
2559 	xscale = strtod(c, &endPtr);
2560 	if ((endPtr == c) || (endPtr[0] != 'x')) { return false; }
2561 	c = endPtr + 1;
2562 	yscale = strtod(c, &endPtr);
2563 	if ((endPtr == c) || (endPtr[0] != '&')) { return false; }
2564 	c = endPtr + 1;
2565 	const char* tex1Start = c;
2566 	while ((c[0] != 0) && (c[0] != '&')) { c++; }
2567 	if (c[0] != '&') { return false; }
2568 	const int tex1Len = c - tex1Start;
2569 	c++;
2570 	tex1 = c; // draw 'tex2' first
2571 	tex2 = std::string(tex1Start, tex1Len);
2572 
2573 	return true;
2574 }
2575 
2576 
BindUnitTexByString(const std::string & str)2577 static inline bool BindUnitTexByString(const std::string& str)
2578 {
2579 	char* endPtr;
2580 	const char* startPtr = str.c_str() + 1; // skip the '#'
2581 	const int unitDefID = (int)strtol(startPtr, &endPtr, 10);
2582 	if (endPtr == startPtr) {
2583 		return false; // bad unitID spec
2584 	}
2585 
2586 	const UnitDef* ud = unitDefHandler->GetUnitDefByID(unitDefID);
2587 	if (ud == NULL) {
2588 		return false;
2589 	}
2590 
2591 	glBindTexture(GL_TEXTURE_2D, unitDefHandler->GetUnitDefImage(ud));
2592 
2593 	return true;
2594 }
2595 
2596 
BindIconTexByString(const std::string & str)2597 static inline bool BindIconTexByString(const std::string& str)
2598 {
2599 	char* endPtr;
2600 	const char* startPtr = str.c_str() + 1; // skip the '^'
2601 	const int unitDefID = (int)strtol(startPtr, &endPtr, 10);
2602 	if (endPtr == startPtr) {
2603 		return false; // bad unitID spec
2604 	}
2605 
2606 	const UnitDef* ud = unitDefHandler->GetUnitDefByID(unitDefID);
2607 	if (ud == NULL) {
2608 		return false;
2609 	}
2610 
2611 	ud->iconType->BindTexture();
2612 
2613 	return true;
2614 }
2615 
2616 
BindLuaTexByString(const std::string & str)2617 static inline bool BindLuaTexByString(const std::string& str)
2618 {
2619 	CLuaHandle* luaHandle = NULL;
2620 	const char scriptType = str[1];
2621 	switch (scriptType) {
2622 		case 'u': { luaHandle = luaUI; break; }
2623 		case 'g': { luaHandle = &luaGaia->unsyncedLuaHandle; break; }
2624 		case 'm': { luaHandle = &luaRules->unsyncedLuaHandle; break; }
2625 		default:  { break; }
2626 	}
2627 	if (luaHandle == NULL) {
2628 		return false;
2629 	}
2630 	if (str[2] != LuaTextures::prefix) { // '!'
2631 		return false;
2632 	}
2633 
2634 	const string luaTexStr = str.substr(2);
2635 	const LuaTextures::Texture* texInfo =
2636 		luaHandle->GetTextures().GetInfo(luaTexStr);
2637 	if (texInfo == NULL) {
2638 		return false;
2639 	}
2640 
2641 	if (texInfo->target != GL_TEXTURE_2D) {
2642 		return false;
2643 	}
2644 	glBindTexture(GL_TEXTURE_2D, texInfo->id);
2645 
2646 	return true;
2647 }
2648 
2649 
BindTextureString(const std::string & str)2650 static bool BindTextureString(const std::string& str)
2651 {
2652 	if (str[0] == '#') {
2653 		return BindUnitTexByString(str);
2654 	} else if (str[0] == '^') {
2655 		return BindIconTexByString(str);
2656 	} else if (str[0] == LuaTextures::prefix) { // '!'
2657 		return BindLuaTexByString(str);
2658 	} else {
2659 		return CNamedTextures::Bind(str);
2660 	}
2661 }
2662 
2663 
DrawTexture(const IconInfo & icon,const std::string & texName)2664 bool CGuiHandler::DrawTexture(const IconInfo& icon, const std::string& texName)
2665 {
2666 	if (texName.empty()) {
2667 		return false;
2668 	}
2669 
2670 	std::string tex1;
2671 	std::string tex2;
2672 	float xscale = 1.0f;
2673 	float yscale = 1.0f;
2674 
2675 	// double texture?
2676 	if (texName[0] == '&') {
2677 		if (!ParseTextures(texName, tex1, tex2, xscale, yscale)) {
2678 			return false;
2679 		}
2680 	} else {
2681 		tex1 = texName;
2682 	}
2683 
2684 	// bind the texture for the full size quad
2685 	if (!BindTextureString(tex1)) {
2686 		if (tex2.empty()) {
2687 			return false;
2688 		} else {
2689 			if (!BindTextureString(tex2)) {
2690 				return false;
2691 			} else {
2692 				tex2.clear(); // cancel the scaled draw
2693 			}
2694 		}
2695 	}
2696 
2697 	glEnable(GL_TEXTURE_2D);
2698 	glColor4f(1.0f, 1.0f, 1.0f, textureAlpha);
2699 
2700 	// draw the full size quad
2701 	const Box& b = icon.visual;
2702 	glBegin(GL_QUADS);
2703 	glTexCoord2f(0.0f, 0.0f); glVertex2f(b.x1, b.y1);
2704 	glTexCoord2f(1.0f, 0.0f); glVertex2f(b.x2, b.y1);
2705 	glTexCoord2f(1.0f, 1.0f); glVertex2f(b.x2, b.y2);
2706 	glTexCoord2f(0.0f, 1.0f); glVertex2f(b.x1, b.y2);
2707 	glEnd();
2708 
2709 	if (tex2.empty()) {
2710 		return true; // success, no second texture to draw
2711 	}
2712 
2713 	// bind the texture for the scaled quad
2714 	if (!BindTextureString(tex2)) {
2715 		return false;
2716 	}
2717 
2718 	assert(xscale<=0.5); //border >= 50% makes no sence
2719 	assert(yscale<=0.5);
2720 
2721 	// calculate the scaled quad
2722 	const float x1 = b.x1 + (xIconSize * xscale);
2723 	const float x2 = b.x2 - (xIconSize * xscale);
2724 	const float y1 = b.y1 - (yIconSize * yscale);
2725 	const float y2 = b.y2 + (yIconSize * yscale);
2726 
2727 	// draw the scaled quad
2728 	glBegin(GL_QUADS);
2729 	glTexCoord2f(0.0f, 0.0f); glVertex2f(x1, y1);
2730 	glTexCoord2f(1.0f, 0.0f); glVertex2f(x2, y1);
2731 	glTexCoord2f(1.0f, 1.0f); glVertex2f(x2, y2);
2732 	glTexCoord2f(0.0f, 1.0f); glVertex2f(x1, y2);
2733 	glEnd();
2734 
2735 	return true;
2736 }
2737 
2738 
DrawIconFrame(const IconInfo & icon)2739 void CGuiHandler::DrawIconFrame(const IconInfo& icon)
2740 {
2741 	const Box& b = icon.visual;
2742 	glDisable(GL_TEXTURE_2D);
2743 	glBegin(GL_LINE_LOOP);
2744 	const float fudge = 0.001f; // avoids getting creamed if iconBorder == 0.0
2745 	glVertex2f(b.x1 + fudge, b.y1 - fudge);
2746 	glVertex2f(b.x2 - fudge, b.y1 - fudge);
2747 	glVertex2f(b.x2 - fudge, b.y2 + fudge);
2748 	glVertex2f(b.x1 + fudge, b.y2 + fudge);
2749 	glEnd();
2750 }
2751 
2752 
DrawName(const IconInfo & icon,const std::string & text,bool offsetForLEDs)2753 void CGuiHandler::DrawName(const IconInfo& icon, const std::string& text,
2754 		bool offsetForLEDs)
2755 {
2756 	if (text.empty()) {
2757 		return;
2758 	}
2759 	const Box& b = icon.visual;
2760 
2761 	const float yShrink = offsetForLEDs ? (0.125f * yIconSize) : 0.0f;
2762 
2763 	const float tWidth  = std::max(0.01f, font->GetSize() * font->GetTextWidth(text) * globalRendering->pixelX);  // FIXME
2764 	const float tHeight = std::max(0.01f, font->GetSize() * font->GetTextHeight(text) * globalRendering->pixelY); // FIXME merge in 1 function?
2765 	const float textBorder2 = (2.0f * textBorder);
2766 	const float xScale = (xIconSize - textBorder2          ) / tWidth;
2767 	const float yScale = (yIconSize - textBorder2 - yShrink) / tHeight;
2768 	const float fontScale = std::min(xScale, yScale);
2769 
2770 	const float xCenter = 0.5f * (b.x1 + b.x2);
2771 	const float yCenter = 0.5f * (b.y1 + b.y2 + yShrink);
2772 
2773 	glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
2774 	font->glPrint(xCenter, yCenter, fontScale, (dropShadows ? FONT_SHADOW : 0) | FONT_CENTER | FONT_VCENTER | FONT_SCALE | FONT_NORM, text);
2775 }
2776 
2777 
DrawNWtext(const IconInfo & icon,const std::string & text)2778 void CGuiHandler::DrawNWtext(const IconInfo& icon, const std::string& text)
2779 {
2780 	if (text.empty()) {
2781 		return;
2782 	}
2783 	const Box& b = icon.visual;
2784 	const float tHeight = font->GetSize() * font->GetTextHeight(text) * globalRendering->pixelY;
2785 	const float fontScale = (yIconSize * 0.2f) / tHeight;
2786 	const float xPos = b.x1 + textBorder + 0.002f;
2787 	const float yPos = b.y1 - textBorder - 0.006f;
2788 
2789 	font->glPrint(xPos, yPos, fontScale, FONT_TOP | FONT_SCALE | FONT_NORM, text);
2790 }
2791 
2792 
DrawSWtext(const IconInfo & icon,const std::string & text)2793 void CGuiHandler::DrawSWtext(const IconInfo& icon, const std::string& text)
2794 {
2795 	if (text.empty()) {
2796 		return;
2797 	}
2798 	const Box& b = icon.visual;
2799 	const float tHeight = font->GetSize() * font->GetTextHeight(text) * globalRendering->pixelY;
2800 	const float fontScale = (yIconSize * 0.2f) / tHeight;
2801 	const float xPos = b.x1 + textBorder + 0.002f;
2802 	const float yPos = b.y2 + textBorder + 0.002f;
2803 
2804 	font->glPrint(xPos, yPos, fontScale, FONT_SCALE | FONT_NORM, text);
2805 }
2806 
2807 
DrawNEtext(const IconInfo & icon,const std::string & text)2808 void CGuiHandler::DrawNEtext(const IconInfo& icon, const std::string& text)
2809 {
2810 	if (text.empty()) {
2811 		return;
2812 	}
2813 	const Box& b = icon.visual;
2814 	const float tHeight = font->GetSize() * font->GetTextHeight(text) * globalRendering->pixelY;
2815 	const float fontScale = (yIconSize * 0.2f) / tHeight;
2816 	const float xPos = b.x2 - textBorder - 0.002f;
2817 	const float yPos = b.y1 - textBorder - 0.006f;
2818 
2819 	font->glPrint(xPos, yPos, fontScale, FONT_TOP | FONT_RIGHT | FONT_SCALE | FONT_NORM, text);
2820 }
2821 
2822 
DrawSEtext(const IconInfo & icon,const std::string & text)2823 void CGuiHandler::DrawSEtext(const IconInfo& icon, const std::string& text)
2824 {
2825 	if (text.empty()) {
2826 		return;
2827 	}
2828 	const Box& b = icon.visual;
2829 	const float tHeight = font->GetSize() * font->GetTextHeight(text) * globalRendering->pixelY;
2830 	const float fontScale = (yIconSize * 0.2f) / tHeight;
2831 	const float xPos = b.x2 - textBorder - 0.002f;
2832 	const float yPos = b.y2 + textBorder + 0.002f;
2833 
2834 	font->glPrint(xPos, yPos, fontScale, FONT_RIGHT | FONT_SCALE | FONT_NORM, text);
2835 }
2836 
2837 
DrawHilightQuad(const IconInfo & icon)2838 void CGuiHandler::DrawHilightQuad(const IconInfo& icon)
2839 {
2840 	if (icon.commandsID == inCommand) {
2841 		glColor4f(0.3f, 0.0f, 0.0f, 1.0f);
2842 	} else if (mouse->buttons[SDL_BUTTON_LEFT].pressed) {
2843 		glColor4f(0.2f, 0.0f, 0.0f, 1.0f);
2844 	} else {
2845 		glColor4f(0.0f, 0.0f, 0.2f, 1.0f);
2846 	}
2847 	const Box& b = icon.visual;
2848 	glDisable(GL_TEXTURE_2D);
2849 	glBlendFunc(GL_ONE, GL_ONE); // additive blending
2850 	glBegin(GL_QUADS);
2851 		glVertex2f(b.x1, b.y1);
2852 		glVertex2f(b.x2, b.y1);
2853 		glVertex2f(b.x2, b.y2);
2854 		glVertex2f(b.x1, b.y2);
2855 	glEnd();
2856 	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
2857 }
2858 
2859 
DrawButtons()2860 void CGuiHandler::DrawButtons() // Only called by Draw
2861 {
2862 	glLineWidth(1.0f);
2863 	font->Begin();
2864 
2865 	// frame box
2866 	const float alpha = (frameAlpha < 0.0f) ? guiAlpha : frameAlpha;
2867 	if (alpha > 0.0f) {
2868 		glColor4f(0.2f, 0.2f, 0.2f, alpha);
2869 		glBegin(GL_QUADS);
2870 			glVertex2f(buttonBox.x1, buttonBox.y1);
2871 			glVertex2f(buttonBox.x1, buttonBox.y2);
2872 			glVertex2f(buttonBox.x2, buttonBox.y2);
2873 			glVertex2f(buttonBox.x2, buttonBox.y1);
2874 		glEnd();
2875 	}
2876 
2877 	const int mouseIcon   = IconAtPos(mouse->lastx, mouse->lasty);
2878 	const int buttonStart = std::min(iconsCount, activePage * iconsPerPage);
2879 	const int buttonEnd   = std::min(iconsCount, buttonStart + iconsPerPage);
2880 
2881 	for (int ii = buttonStart; ii < buttonEnd; ii++) {
2882 
2883 		const IconInfo& icon = icons[ii];
2884 		if (icon.commandsID < 0) {
2885 			continue; // inactive icon
2886 		}
2887 		const CommandDescription& cmdDesc = commands[icon.commandsID];
2888 		const bool customCommand = (cmdDesc.id == CMD_INTERNAL) &&
2889 		                           (cmdDesc.type == CMDTYPE_CUSTOM);
2890 		const bool highlight = ((mouseIcon == ii) ||
2891 		                        (icon.commandsID == inCommand)) &&
2892 		                       (!customCommand || !cmdDesc.params.empty());
2893 
2894 		if (customCommand) {
2895 			DrawCustomButton(icon, highlight);
2896 		}
2897 		else {
2898 			bool usedTexture = false;
2899 			bool onlyTexture = cmdDesc.onlyTexture;
2900 			const bool useLEDs = useOptionLEDs && (cmdDesc.type == CMDTYPE_ICON_MODE);
2901 
2902 			// specified texture
2903 			if (DrawTexture(icon, cmdDesc.iconname)) {
2904 				usedTexture = true;
2905 			}
2906 
2907 			// unit buildpic
2908 			if (!usedTexture) {
2909 				if (cmdDesc.id < 0) {
2910 					const UnitDef* ud = unitDefHandler->GetUnitDefByID(-cmdDesc.id);
2911 					if (ud != NULL) {
2912 						DrawUnitBuildIcon(icon, -cmdDesc.id);
2913 						usedTexture = true;
2914 						onlyTexture = true;
2915 					}
2916 				}
2917 			}
2918 
2919 			// highlight background before text is applied
2920 			if (highlight) {
2921 				DrawHilightQuad(icon);
2922 			}
2923 
2924 			// build count text (NOTE the weird bracing)
2925 			if (cmdDesc.id < 0) {
2926 				const int psize = (int)cmdDesc.params.size();
2927 				if (psize > 0) { DrawSWtext(icon, cmdDesc.params[0]);
2928 					if (psize > 1) { DrawNEtext(icon, cmdDesc.params[1]);
2929 						if (psize > 2) { DrawNWtext(icon, cmdDesc.params[2]);
2930 							if (psize > 3) { DrawSEtext(icon, cmdDesc.params[3]); } } } }
2931 			}
2932 
2933 			// draw arrows, or a frame for text
2934 			if (!usedTexture || !onlyTexture) {
2935 				if ((cmdDesc.type == CMDTYPE_PREV) || (cmdDesc.type == CMDTYPE_NEXT)) {
2936 					// pick the color for the arrow
2937 					if (highlight) {
2938 						glColor4f(1.0f, 1.0f, 0.0f, 1.0f); // selected
2939 					} else {
2940 						glColor4f(0.7f, 0.7f, 0.7f, 1.0f); // normal
2941 					}
2942 					if (cmdDesc.type == CMDTYPE_PREV) {
2943 						DrawPrevArrow(icon);
2944 					} else {
2945 						DrawNextArrow(icon);
2946 					}
2947 				}
2948 				else if (!usedTexture) {
2949 					// no texture, no arrow, ... draw a frame
2950 					glColor4f(1.0f, 1.0f, 1.0f, 0.1f);
2951 					DrawIconFrame(icon);
2952 				}
2953 
2954 				// draw the text
2955 				// command name (or parameter)
2956 				std::string toPrint = cmdDesc.name;
2957 				if (cmdDesc.type == CMDTYPE_ICON_MODE
2958 						&& !cmdDesc.params.empty()) {
2959 					const int opt = atoi(cmdDesc.params[0].c_str()) + 1;
2960 					if (opt < 0 || static_cast<size_t>(opt) < cmdDesc.params.size()) {
2961 						toPrint = cmdDesc.params[opt];
2962 					}
2963 				}
2964 				DrawName(icon, toPrint, useLEDs);
2965 			}
2966 
2967 			// draw the mode indicators
2968 			if (useLEDs) {
2969 				DrawOptionLEDs(icon);
2970 			}
2971 		}
2972 
2973 		// darken disabled commands
2974 		if (cmdDesc.disabled) {
2975 			glDisable(GL_TEXTURE_2D);
2976 			glBlendFunc(GL_DST_COLOR, GL_ZERO);
2977 			glColor4f(0.5f, 0.5f, 0.5f, 0.5f);
2978 			const Box& vb = icon.visual;
2979 			glRectf(vb.x1, vb.y1, vb.x2, vb.y2);
2980 			glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
2981 		}
2982 
2983 		// highlight outline
2984 		if (highlight) {
2985 			if (icon.commandsID == inCommand) {
2986 				glColor4f(1.0f, 1.0f, 0.0f, 0.75f);
2987 			} else if (mouse->buttons[SDL_BUTTON_LEFT].pressed ||
2988 			           mouse->buttons[SDL_BUTTON_RIGHT].pressed) {
2989 				glColor4f(1.0f, 0.0f, 0.0f, 0.50f);
2990 			} else {
2991 				glColor4f(1.0f, 1.0f, 1.0f, 0.50f);
2992 			}
2993 			glLineWidth(1.49f);
2994 			DrawIconFrame(icon);
2995 			glLineWidth(1.0f);
2996 		}
2997 	}
2998 
2999 	// active page indicator
3000 	if (luaUI == NULL) {
3001 		if (selectedUnitsHandler.BuildIconsFirst()) {
3002 			glColor4fv(cmdColors.build);
3003 		} else {
3004 			glColor4f(0.7f, 0.7f, 0.7f, 1.0f);
3005 		}
3006 		const float textSize = 1.2f;
3007 		font->glFormat(xBpos, yBpos, textSize, FONT_CENTER | FONT_VCENTER | FONT_SCALE | FONT_NORM, "%i", activePage + 1);
3008 	}
3009 
3010 	DrawMenuName();
3011 
3012 	font->End();
3013 
3014 	// LuaUI can handle this
3015 	if (luaUI == NULL || drawSelectionInfo)
3016 		DrawSelectionInfo();
3017 
3018 	DrawNumberInput();
3019 }
3020 
3021 
DrawMenuName()3022 void CGuiHandler::DrawMenuName() // Only called by drawbuttons
3023 {
3024 	if (!menuName.empty() && (iconsCount > 0)) {
3025 		const float fontScale = 1.0f;
3026 		const float xp = 0.5f * (buttonBox.x1 + buttonBox.x2);
3027 		const float yp = buttonBox.y2 + (yIconSize * 0.125f);
3028 
3029 		if (!outlineFonts) {
3030 			const float textHeight = fontScale * font->GetTextHeight(menuName) * globalRendering->pixelY;
3031 			glDisable(GL_TEXTURE_2D);
3032 			glColor4f(0.2f, 0.2f, 0.2f, guiAlpha);
3033 			glRectf(buttonBox.x1,
3034 			        buttonBox.y2,
3035 			        buttonBox.x2,
3036 			        buttonBox.y2 + textHeight + (yIconSize * 0.25f));
3037 			font->glPrint(xp, yp, fontScale, FONT_CENTER | FONT_SCALE | FONT_NORM, menuName);
3038 		}
3039 		else {
3040 			font->SetColors(); // default
3041 			font->glPrint(xp, yp, fontScale, FONT_CENTER | FONT_OUTLINE | FONT_SCALE | FONT_NORM, menuName);
3042 		}
3043 	}
3044 
3045 }
3046 
3047 
DrawSelectionInfo()3048 void CGuiHandler::DrawSelectionInfo()
3049 {
3050 	if (!selectedUnitsHandler.selectedUnits.empty()) {
3051 		std::ostringstream buf;
3052 
3053 		if (selectedUnitsHandler.GetSelectedGroup() != -1) {
3054 			buf << "Selected units " << selectedUnitsHandler.selectedUnits.size() << " [Group " << selectedUnitsHandler.GetSelectedGroup() << "]";
3055 		} else {
3056 			buf << "Selected units " << selectedUnitsHandler.selectedUnits.size();
3057 		}
3058 
3059 		const float fontScale = 1.0f;
3060 		const float fontSize  = fontScale * smallFont->GetSize();
3061 
3062 		if (!outlineFonts) {
3063 			float descender;
3064 			const float textWidth  = fontSize * smallFont->GetTextWidth(buf.str()) * globalRendering->pixelX;
3065 			float textHeight = fontSize * smallFont->GetTextHeight(buf.str(), &descender) * globalRendering->pixelY;
3066 			const float textDescender = fontSize * descender * globalRendering->pixelY; //! descender is always negative
3067 			textHeight -= textDescender;
3068 
3069 			glDisable(GL_TEXTURE_2D);
3070 			glColor4f(0.2f, 0.2f, 0.2f, guiAlpha);
3071 			glRectf(xSelectionPos - frameBorder,
3072 			        ySelectionPos - frameBorder,
3073 			        xSelectionPos + frameBorder + textWidth,
3074 			        ySelectionPos + frameBorder + textHeight);
3075 			glColor4f(1.0f, 1.0f, 1.0f, 0.8f);
3076 			smallFont->glPrint(xSelectionPos, ySelectionPos - textDescender, fontSize, FONT_BASELINE | FONT_NORM, buf.str());
3077 		} else {
3078 			smallFont->SetColors(); // default
3079 			smallFont->glPrint(xSelectionPos, ySelectionPos, fontSize, FONT_OUTLINE | FONT_NORM, buf.str());
3080 		}
3081 	}
3082 }
3083 
3084 
DrawNumberInput()3085 void CGuiHandler::DrawNumberInput() // Only called by drawbuttons
3086 {
3087 	// draw the value for CMDTYPE_NUMBER commands
3088 	if ((inCommand >= 0) && ((size_t)inCommand < commands.size())) {
3089 		const CommandDescription& cd = commands[inCommand];
3090 		if (cd.type == CMDTYPE_NUMBER) {
3091 			const float value = GetNumberInput(cd);
3092 			glDisable(GL_TEXTURE_2D);
3093 			glColor4f(1.0f, 1.0f, 1.0f, 0.8f);
3094 			const float mouseX = (float)mouse->lastx / (float)globalRendering->viewSizeX;
3095 			const float slideX = std::min(std::max(mouseX, 0.25f), 0.75f);
3096 			//const float mouseY = 1.0f - (float)(mouse->lasty - 16) / (float)globalRendering->viewSizeY;
3097 			glColor4f(1.0f, 1.0f, 0.0f, 0.8f);
3098 			glRectf(0.235f, 0.45f, 0.25f, 0.55f);
3099 			glRectf(0.75f, 0.45f, 0.765f, 0.55f);
3100 			glColor4f(0.0f, 0.0f, 1.0f, 0.8f);
3101 			glRectf(0.25f, 0.49f, 0.75f, 0.51f);
3102 			glBegin(GL_TRIANGLES);
3103 				glColor4f(1.0f, 0.0f, 0.0f, 1.0f);
3104 				glVertex2f(slideX + 0.015f, 0.55f);
3105 				glVertex2f(slideX - 0.015f, 0.55f);
3106 				glVertex2f(slideX, 0.50f);
3107 				glVertex2f(slideX - 0.015f, 0.45f);
3108 				glVertex2f(slideX + 0.015f, 0.45f);
3109 				glVertex2f(slideX, 0.50f);
3110 			glEnd();
3111 			glColor4f(1.0f, 1.0f, 1.0f, 0.9f);
3112 			font->glFormat(slideX, 0.56f, 2.0f, FONT_CENTER | FONT_SCALE | FONT_NORM, "%i", (int)value);
3113 		}
3114 	}
3115 }
3116 
3117 
DrawPrevArrow(const IconInfo & icon)3118 void CGuiHandler::DrawPrevArrow(const IconInfo& icon)
3119 {
3120 	const Box& b = icon.visual;
3121 	const float yCenter = 0.5f * (b.y1 + b.y2);
3122 	const float xSize = 0.166f * math::fabs(b.x2 - b.x1);
3123 	const float ySize = 0.125f * math::fabs(b.y2 - b.y1);
3124 	const float xSiz2 = 2.0f * xSize;
3125 	glDisable(GL_TEXTURE_2D);
3126 	glBegin(GL_POLYGON);
3127 		glVertex2f(b.x2 - xSize, yCenter - ySize);
3128 		glVertex2f(b.x1 + xSiz2, yCenter - ySize);
3129 		glVertex2f(b.x1 + xSize, yCenter);
3130 		glVertex2f(b.x1 + xSiz2, yCenter + ySize);
3131 		glVertex2f(b.x2 - xSize, yCenter + ySize);
3132 	glEnd();
3133 }
3134 
3135 
DrawNextArrow(const IconInfo & icon)3136 void CGuiHandler::DrawNextArrow(const IconInfo& icon)
3137 {
3138 	const Box& b = icon.visual;
3139 	const float yCenter = 0.5f * (b.y1 + b.y2);
3140 	const float xSize = 0.166f * math::fabs(b.x2 - b.x1);
3141 	const float ySize = 0.125f * math::fabs(b.y2 - b.y1);
3142 	const float xSiz2 = 2.0f * xSize;
3143 	glDisable(GL_TEXTURE_2D);
3144 	glBegin(GL_POLYGON);
3145 		glVertex2f(b.x1 + xSize, yCenter - ySize);
3146 		glVertex2f(b.x2 - xSiz2, yCenter - ySize);
3147 		glVertex2f(b.x2 - xSize, yCenter);
3148 		glVertex2f(b.x2 - xSiz2, yCenter + ySize);
3149 		glVertex2f(b.x1 + xSize, yCenter + ySize);
3150 	glEnd();
3151 }
3152 
3153 
DrawOptionLEDs(const IconInfo & icon)3154 void CGuiHandler::DrawOptionLEDs(const IconInfo& icon)
3155 {
3156 	const CommandDescription& cmdDesc = commands[icon.commandsID];
3157 
3158 	const int pCount = (int)cmdDesc.params.size() - 1;
3159 	if (pCount < 2) {
3160 		return;
3161 	}
3162 	const int option = atoi(cmdDesc.params[0].c_str());
3163 
3164 	glLoadIdentity();
3165 
3166 	glDisable(GL_TEXTURE_2D);
3167 
3168 	const float xs = xIconSize / float(1 + (pCount * 2));
3169 	const float ys = yIconSize * 0.125f;
3170 	const float x1 = icon.visual.x1;
3171 	const float y2 = icon.visual.y2;
3172 	const float yp = 1.0f / float(globalRendering->viewSizeY);
3173 
3174 	for (int x = 0; x < pCount; x++) {
3175 		if (x != option) {
3176 			glColor4f(0.25f, 0.25f, 0.25f, 0.50f); // dark
3177 		} else {
3178 			if (pCount == 2) {
3179 				if (option == 0) {
3180 					glColor4f(1.0f, 0.0f, 0.0f, 0.75f); // red
3181 				} else {
3182 					glColor4f(0.0f, 1.0f, 0.0f, 0.75f); // green
3183 				}
3184 			} else if (pCount == 3) {
3185 				if (option == 0) {
3186 					glColor4f(1.0f, 0.0f, 0.0f, 0.75f); // red
3187 				} else if (option == 1) {
3188 					glColor4f(1.0f, 1.0f, 0.0f, 0.75f); // yellow
3189 				} else {
3190 					glColor4f(0.0f, 1.0f, 0.0f, 0.75f); // green
3191 				}
3192 			} else {
3193 				glColor4f(0.75f, 0.75f, 0.75f, 0.75f); // light
3194 			}
3195 		}
3196 
3197 		const float startx = x1 + (xs * float(1 + (2 * x)));
3198 		const float starty = y2 + (3.0f * yp) + textBorder;
3199 
3200 		glRectf(startx, starty, startx + xs, starty + ys);
3201 
3202 		glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
3203 		glColor4f(1.0f, 1.0f, 1.0f, 0.5f);
3204 		glRectf(startx, starty, startx + xs, starty + ys);
3205 		glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
3206 	}
3207 }
3208 
3209 
3210 /******************************************************************************/
3211 /******************************************************************************/
3212 
DrawSensorRange(int radius,const float * color,const float3 & pos)3213 static inline void DrawSensorRange(int radius,
3214                                    const float* color, const float3& pos)
3215 {
3216 	const int sensorScale = radarHandler->radarDiv;
3217 	const int realRadius = ((radius / sensorScale) * sensorScale);
3218 	if (realRadius > 0) {
3219 		glColor4fv(color);
3220 		glSurfaceCircle(pos, (float)realRadius, 40);
3221 	}
3222 }
3223 
3224 
GetConeList()3225 static inline GLuint GetConeList()
3226 {
3227 	static GLuint list = 0; // FIXME: put in the class
3228 	if (list != 0) {
3229 		return list;
3230 	}
3231 	list = glGenLists(1);
3232 	glNewList(list, GL_COMPILE); {
3233 		glBegin(GL_TRIANGLE_FAN);
3234 		const int divs = 64;
3235 		glVertex3f(0.0f, 0.0f, 0.0f);
3236 		for (int i = 0; i <= divs; i++) {
3237 			const float rad = (PI * 2.0) * (float)i / (float)divs;
3238 			glVertex3f(1.0f, math::sin(rad), math::cos(rad));
3239 		}
3240 		glEnd();
3241 	}
3242 	glEndList();
3243 	return list;
3244 }
3245 
3246 
DrawWeaponCone(const float3 & pos,float len,float hrads,float heading,float pitch)3247 static void DrawWeaponCone(const float3& pos,
3248                            float len, float hrads, float heading, float pitch)
3249 {
3250 	glPushMatrix();
3251 
3252 	const float xlen = len * math::cos(hrads);
3253 	const float yzlen = len * math::sin(hrads);
3254 	glTranslatef(pos.x, pos.y, pos.z);
3255 	glRotatef(heading * (180.0 / PI), 0.0f, 1.0f, 0.0f);
3256 	glRotatef(pitch   * (180.0 / PI), 0.0f, 0.0f, 1.0f);
3257 	glScalef(xlen, yzlen, yzlen);
3258 
3259 	glEnable(GL_CULL_FACE);
3260 
3261 	glCullFace(GL_FRONT);
3262 	glColor4f(1.0f, 0.0f, 0.0f, 0.25f);
3263 	glCallList(GetConeList());
3264 
3265 	glCullFace(GL_BACK);
3266 	glColor4f(0.0f, 1.0f, 0.0f, 0.25f);
3267 	glCallList(GetConeList());
3268 
3269 	glDisable(GL_CULL_FACE);
3270 
3271 	glPopMatrix();
3272 }
3273 
3274 
DrawWeaponArc(const CUnit * unit)3275 static inline void DrawWeaponArc(const CUnit* unit)
3276 {
3277 	for (unsigned int n = 0; n < unit->weapons.size(); n++) {
3278 		const CWeapon* w = unit->weapons[n];
3279 
3280 		// attack order needs to have been issued or wantedDir is undefined
3281 		if (w->targetType == Target_None)
3282 			continue;
3283 		if (w->weaponDef->projectileType == WEAPON_BASE_PROJECTILE)
3284 			continue;
3285 
3286 		const float3 weaponDir = (unit->frontdir * w->onlyForward) + (w->wantedDir * (1 - w->onlyForward));
3287 
3288 		const float hrads   = math::acos(w->maxForwardAngleDif);
3289 		const float heading = math::atan2(-weaponDir.z, weaponDir.x);
3290 		const float pitch   = math::asin(weaponDir.y);
3291 
3292 		// note: cone visualization is invalid for ballistic weapons
3293 		DrawWeaponCone(w->weaponMuzzlePos, w->range, hrads, heading, pitch);
3294 	}
3295 }
3296 
3297 
DrawMapStuff(bool onMinimap)3298 void CGuiHandler::DrawMapStuff(bool onMinimap)
3299 {
3300 	if (!onMinimap) {
3301 		glEnable(GL_DEPTH_TEST);
3302 		glDepthMask(GL_FALSE);
3303 		glDisable(GL_TEXTURE_2D);
3304 		glEnable(GL_BLEND);
3305 		glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
3306 		glDisable(GL_ALPHA_TEST);
3307 	}
3308 
3309 	float3 cameraPos=camera->GetPos();
3310 	float3 mouseDir=mouse->dir;
3311 
3312 	// setup for minimap proxying
3313 	const bool minimapCoords =
3314 		(minimap->ProxyMode() ||
3315 		 ((activeReceiver != this) && !game->hideInterface &&
3316 		  (GetReceiverAt(mouse->lastx, mouse->lasty) == minimap)));
3317 	if (minimapCoords) {
3318 		cameraPos = minimap->GetMapPosition(mouse->lastx, mouse->lasty);
3319 		mouseDir = -UpVector;
3320 		if (miniMapMarker && minimap->FullProxy() &&
3321 		    !onMinimap && !minimap->GetMinimized()) {
3322 			DrawMiniMapMarker(cameraPos);
3323 		}
3324 	}
3325 
3326 
3327 	if (activeMousePress) {
3328 		int cmdIndex = -1;
3329 		int button = SDL_BUTTON_LEFT;
3330 		if ((inCommand >= 0) && ((size_t)inCommand < commands.size())) {
3331 			cmdIndex = inCommand;
3332 		} else {
3333 			if (mouse->buttons[SDL_BUTTON_RIGHT].pressed &&
3334 			    ((activeReceiver == this) || (minimap->ProxyMode()))) {
3335 				cmdIndex = defaultCmdMemory;
3336 				button = SDL_BUTTON_RIGHT;
3337 			}
3338 		}
3339 
3340 		if (mouse->buttons[button].pressed && (cmdIndex >= 0) && ((size_t)cmdIndex < commands.size())) {
3341 			const CommandDescription& cmdDesc = commands[cmdIndex];
3342 			switch (cmdDesc.type) {
3343 				case CMDTYPE_ICON_FRONT: {
3344 					if (mouse->buttons[button].movement > 30) {
3345 						float maxSize = 1000000.0f;
3346 						float sizeDiv = 0.0f;
3347 						if (!cmdDesc.params.empty()) {
3348 							maxSize = atof(cmdDesc.params[0].c_str());
3349 							if (cmdDesc.params.size() > 1) {
3350 								sizeDiv = atof(cmdDesc.params[1].c_str());
3351 							}
3352 						}
3353 						DrawFront(button, maxSize, sizeDiv, onMinimap, cameraPos, mouseDir);
3354 					}
3355 					break;
3356 				}
3357 				case CMDTYPE_ICON_UNIT_OR_AREA:
3358 				case CMDTYPE_ICON_UNIT_FEATURE_OR_AREA:
3359 				case CMDTYPE_ICON_AREA: {
3360 					float maxRadius=100000;
3361 					if (cmdDesc.params.size() == 1) {
3362 						maxRadius = atof(cmdDesc.params[0].c_str());
3363 					}
3364 					if (mouse->buttons[button].movement > 4) {
3365 						float dist = CGround::LineGroundCol(
3366 							mouse->buttons[button].camPos,
3367 							mouse->buttons[button].camPos +
3368 							mouse->buttons[button].dir * globalRendering->viewRange * 1.4f,
3369 							false
3370 						);
3371 						if(dist<0){
3372 							break;
3373 						}
3374 						float3 pos=mouse->buttons[button].camPos+mouse->buttons[button].dir*dist;
3375 						dist = CGround::LineGroundCol(cameraPos, cameraPos + mouseDir * globalRendering->viewRange * 1.4f, false);
3376 						if (dist < 0) {
3377 							break;
3378 						}
3379 						float3 pos2 = cameraPos + mouseDir * dist;
3380 						const float* color;
3381 						switch (cmdDesc.id) {
3382 							case CMD_ATTACK:
3383 							case CMD_AREA_ATTACK:  { color = cmdColors.attack;      break; }
3384 							case CMD_REPAIR:       { color = cmdColors.repair;      break; }
3385 							case CMD_RECLAIM:      { color = cmdColors.reclaim;     break; }
3386 							case CMD_RESTORE:      { color = cmdColors.restore;     break; }
3387 							case CMD_RESURRECT:    { color = cmdColors.resurrect;   break; }
3388 							case CMD_LOAD_UNITS:   { color = cmdColors.load;        break; }
3389 							case CMD_UNLOAD_UNIT:
3390 							case CMD_UNLOAD_UNITS: { color = cmdColors.unload;      break; }
3391 							case CMD_CAPTURE:      { color = cmdColors.capture;     break; }
3392 							default: {
3393 								static const float grey[4] = { 0.5f, 0.5f, 0.5f, 0.5f };
3394 								color = grey;
3395 							}
3396 						}
3397 						const float radius = std::min(maxRadius, pos.distance2D(pos2));
3398 						if (!onMinimap) {
3399 							DrawArea(pos, radius, color);
3400 						}
3401 						else {
3402 							glColor4f(color[0], color[1], color[2], 0.5f);
3403 							glBegin(GL_TRIANGLE_FAN);
3404 							const int divs = 256;
3405 							for(int i = 0; i <= divs; ++i) {
3406 								const float radians = (2 * PI) * (float)i / (float)divs;
3407 								float3 p(pos.x, 0.0f, pos.z);
3408 								p.x += fastmath::sin(radians) * radius;
3409 								p.z += fastmath::cos(radians) * radius;
3410 								glVertexf3(p);
3411 							}
3412 							glEnd();
3413 						}
3414 					}
3415 					break;
3416 				}
3417 				case CMDTYPE_ICON_UNIT_OR_RECTANGLE:{
3418 					if (mouse->buttons[button].movement >= 16) {
3419 						float dist = CGround::LineGroundCol(
3420 							mouse->buttons[button].camPos,
3421 							mouse->buttons[button].camPos +
3422 							mouse->buttons[button].dir * globalRendering->viewRange * 1.4f,
3423 							false
3424 						);
3425 						if (dist < 0) {
3426 							break;
3427 						}
3428 						const float3 pos1 = mouse->buttons[button].camPos+mouse->buttons[button].dir*dist;
3429 						dist = CGround::LineGroundCol(cameraPos, cameraPos + mouseDir * globalRendering->viewRange * 1.4f, false);
3430 						if (dist < 0) {
3431 							break;
3432 						}
3433 						const float3 pos2 = cameraPos+mouseDir*dist;
3434 						if (!onMinimap) {
3435 							DrawSelectBox(pos1, pos2, cameraPos);
3436 						} else {
3437 							glColor4f(1.0f, 0.0f, 0.0f, 0.5f);
3438 							glBegin(GL_QUADS);
3439 							glVertex3f(pos1.x, 0.0f, pos1.z);
3440 							glVertex3f(pos2.x, 0.0f, pos1.z);
3441 							glVertex3f(pos2.x, 0.0f, pos2.z);
3442 							glVertex3f(pos1.x, 0.0f, pos2.z);
3443 							glEnd();
3444 						}
3445 					}
3446 					break;
3447 				}
3448 			}
3449 		}
3450 	}
3451 
3452 	if (!onMinimap) {
3453 		glBlendFunc((GLenum)cmdColors.SelectedBlendSrc(),
3454 								(GLenum)cmdColors.SelectedBlendDst());
3455 		glLineWidth(cmdColors.SelectedLineWidth());
3456 	} else {
3457 		glLineWidth(1.49f);
3458 	}
3459 
3460 	// draw the ranges for the unit that is being pointed at
3461 	const CUnit* pointedAt = NULL;
3462 
3463 	if (GetQueueKeystate()) {
3464 		CUnit* unit = NULL;
3465 		CFeature* feature = NULL;
3466 
3467 		if (minimapCoords) {
3468 			unit = minimap->GetSelectUnit(cameraPos);
3469 		} else {
3470 			// ignore the returned distance, we don't care about it here
3471 			TraceRay::GuiTraceRay(cameraPos, mouseDir, globalRendering->viewRange * 1.4f, NULL, unit, feature, false);
3472 		}
3473 
3474 		if (unit && ((unit->losStatus[gu->myAllyTeam] & LOS_INLOS) || gu->spectatingFullView)) {
3475 			pointedAt = unit;
3476 			const UnitDef* unitdef = unit->unitDef;
3477 			const bool enemyUnit = ((unit->allyteam != gu->myAllyTeam) && !gu->spectatingFullView);
3478 			if (enemyUnit && unitdef->decoyDef) {
3479 				unitdef = unitdef->decoyDef;
3480 			}
3481 
3482 			// draw weapon range
3483 			if (unitdef->maxWeaponRange > 0) {
3484 				glDisable(GL_DEPTH_TEST);
3485 				glColor4fv(cmdColors.rangeAttack);
3486 				glBallisticCircle(unit->pos, unitdef->maxWeaponRange,
3487 				                  unit->weapons[0], 40);
3488 				glEnable(GL_DEPTH_TEST);
3489 			}
3490 			// draw decloak distance
3491 			if (unit->decloakDistance > 0.0f) {
3492 				glColor4fv(cmdColors.rangeDecloak);
3493 				if (unit->unitDef->decloakSpherical && globalRendering->drawdebug) {
3494 					glPushMatrix();
3495 					glTranslatef(unit->midPos.x, unit->midPos.y, unit->midPos.z);
3496 					glRotatef(90.0f, 1.0f, 0.0f, 0.0f);
3497 					GLUquadricObj* q = gluNewQuadric();
3498 					gluQuadricDrawStyle(q, GLU_LINE);
3499 					gluSphere(q, unit->decloakDistance, 10, 10);
3500 					gluDeleteQuadric(q);
3501 					glPopMatrix();
3502 				} else { // cylindrical
3503 					glSurfaceCircle(unit->pos, unit->decloakDistance, 40);
3504 				}
3505 			}
3506 			// draw self destruct and damage distance
3507 			if (unitdef->kamikazeDist > 0) {
3508 				glColor4fv(cmdColors.rangeKamikaze);
3509 				glSurfaceCircle(unit->pos, unitdef->kamikazeDist, 40);
3510 
3511 				const WeaponDef* wd = unitdef->selfdExpWeaponDef;
3512 
3513 				if (wd != NULL) {
3514 					glColor4fv(cmdColors.rangeSelfDestruct);
3515 					glSurfaceCircle(unit->pos, wd->damageAreaOfEffect, 40);
3516 				}
3517 			}
3518 			// draw build distance for immobile builders
3519 			if (unitdef->builder) {
3520 				const float radius = unitdef->buildDistance;
3521 				if (radius > 0.0f) {
3522 					glColor4fv(cmdColors.rangeBuild);
3523 					glSurfaceCircle(unit->pos, radius, 40);
3524 				}
3525 			}
3526 			// draw shield range for immobile units
3527 			if (unitdef->shieldWeaponDef) {
3528 				glColor4fv(cmdColors.rangeShield);
3529 				glSurfaceCircle(unit->pos, unitdef->shieldWeaponDef->shieldRadius, 40);
3530 			}
3531 			// draw sensor and jammer ranges
3532 			if (unitdef->onoffable || unitdef->activateWhenBuilt) {
3533 				const float3& p = unit->pos;
3534 				DrawSensorRange(unitdef->radarRadius,    cmdColors.rangeRadar, p);
3535 				DrawSensorRange(unitdef->sonarRadius,    cmdColors.rangeSonar, p);
3536 				DrawSensorRange(unitdef->seismicRadius,  cmdColors.rangeSeismic, p);
3537 				DrawSensorRange(unitdef->jammerRadius,   cmdColors.rangeJammer, p);
3538 				DrawSensorRange(unitdef->sonarJamRadius, cmdColors.rangeSonarJammer, p);
3539 			}
3540 			// draw interceptor range
3541 			if (unitdef->maxCoverage > 0.0f) {
3542 				const CWeapon* w = NULL; //will be checked if any missiles are ready
3543 				if (!enemyUnit) {
3544 					w = unit->stockpileWeapon;
3545 					if (w != NULL && !w->weaponDef->interceptor) {
3546 						w = NULL; //if this isn't the interceptor, then don't use it
3547 					}
3548 				} //shows as on if enemy, a non-stockpiled weapon, or if the stockpile has a missile
3549 				if (enemyUnit || (w == NULL) || w->numStockpiled) {
3550 					glColor4fv(cmdColors.rangeInterceptorOn);
3551 				} else {
3552 					glColor4fv(cmdColors.rangeInterceptorOff);
3553 				}
3554 				glSurfaceCircle(unit->pos, unitdef->maxCoverage, 40);
3555 			}
3556 		}
3557 	}
3558 
3559 	// draw buildings we are about to build
3560 	if ((inCommand >= 0) && ((size_t)inCommand < commands.size()) &&
3561 	    (commands[inCommand].type == CMDTYPE_ICON_BUILDING)) {
3562 		{ // limit the locking scope to avoid deadlock
3563 			// draw build distance for all immobile builders during build commands
3564 			const std::map<unsigned int, CBuilderCAI*>& builderCAIs = unitHandler->builderCAIs;
3565 			      std::map<unsigned int, CBuilderCAI*>::const_iterator bi;
3566 
3567 			for (bi = builderCAIs.begin(); bi != builderCAIs.end(); ++bi) {
3568 				const CBuilderCAI* builderCAI = bi->second;
3569 				const CUnit* builder = builderCAI->owner;
3570 				const UnitDef* builderDef = builder->unitDef;
3571 
3572 				if ((builder == pointedAt) || (builder->team != gu->myTeam)) {
3573 					continue;
3574 				}
3575 
3576 				if (builderDef->builder && (!builderDef->canmove || selectedUnitsHandler.IsUnitSelected(builder))) {
3577 					const float radius = builderDef->buildDistance;
3578 					if (radius > 0.0f) {
3579 						glDisable(GL_TEXTURE_2D);
3580 						const float* color = cmdColors.rangeBuild;
3581 						glColor4f(color[0], color[1], color[2], color[3] * 0.333f);
3582 						glSurfaceCircle(builder->pos, radius, 40);
3583 					}
3584 				}
3585 			}
3586 		}
3587 
3588 		float dist = CGround::LineGroundCol(cameraPos, cameraPos + mouseDir * globalRendering->viewRange * 1.4f, false);
3589 		if (dist > 0) {
3590 			const UnitDef* unitdef = unitDefHandler->GetUnitDefByID(-commands[inCommand].id);
3591 			if (unitdef) {
3592 				// get the build information
3593 				float3 pos = cameraPos+mouseDir*dist;
3594 				std::vector<BuildInfo> buildPos;
3595 				const CMouseHandler::ButtonPressEvt& bp = mouse->buttons[SDL_BUTTON_LEFT];
3596 				if (GetQueueKeystate() && bp.pressed) {
3597 					const float dist = CGround::LineGroundCol(bp.camPos, bp.camPos + bp.dir * globalRendering->viewRange * 1.4f, false);
3598 					const float3 pos2 = bp.camPos + bp.dir * dist;
3599 					buildPos = GetBuildPos(BuildInfo(unitdef, pos2, buildFacing),
3600 					                       BuildInfo(unitdef, pos, buildFacing), cameraPos, mouseDir);
3601 				} else {
3602 					BuildInfo bi(unitdef, pos, buildFacing);
3603 					buildPos = GetBuildPos(bi, bi, cameraPos, mouseDir);
3604 				}
3605 
3606 				for (std::vector<BuildInfo>::iterator bpi = buildPos.begin(); bpi != buildPos.end(); ++bpi) {
3607 					const float3& buildpos = bpi->pos;
3608 					// draw weapon range
3609 					if (!unitdef->weapons.empty()) {
3610 						glDisable(GL_DEPTH_TEST);
3611 						glColor4fv(cmdColors.rangeAttack);
3612 						glBallisticCircle(buildpos, unitdef->weapons[0].def->range,
3613 						                  NULL, 40, unitdef->weapons[0].def->heightmod);
3614 						glEnable(GL_DEPTH_TEST);
3615 					}
3616 					// draw extraction range
3617 					if (unitdef->extractRange > 0) {
3618 						glColor4fv(cmdColors.rangeExtract);
3619 						glSurfaceCircle(buildpos, unitdef->extractRange, 40);
3620 					}
3621 					// draw build range for immobile builders
3622 					if (unitdef->builder) {
3623 						const float radius = unitdef->buildDistance;
3624 						if (radius > 0.0f) {
3625 							glColor4fv(cmdColors.rangeBuild);
3626 							glSurfaceCircle(buildpos, radius, 40);
3627 						}
3628 					}
3629 					// draw shield range for immobile units
3630 					if (unitdef->shieldWeaponDef) {
3631 						glColor4fv(cmdColors.rangeShield);
3632 						glSurfaceCircle(buildpos, unitdef->shieldWeaponDef->shieldRadius, 40);
3633 					}
3634 					// draw interceptor range
3635 					const WeaponDef* wd = unitdef->stockpileWeaponDef;
3636 					if ((wd != NULL) && wd->interceptor) {
3637 						glColor4fv(cmdColors.rangeInterceptorOn);
3638 						glSurfaceCircle(buildpos, wd->coverageRange, 40);
3639 					}
3640 					// draw sensor and jammer ranges
3641 					if (unitdef->onoffable || unitdef->activateWhenBuilt) {
3642 						const float3& p = buildpos;
3643 						DrawSensorRange(unitdef->radarRadius,    cmdColors.rangeRadar, p);
3644 						DrawSensorRange(unitdef->sonarRadius,    cmdColors.rangeSonar, p);
3645 						DrawSensorRange(unitdef->seismicRadius,  cmdColors.rangeSeismic, p);
3646 						DrawSensorRange(unitdef->jammerRadius,   cmdColors.rangeJammer, p);
3647 						DrawSensorRange(unitdef->sonarJamRadius, cmdColors.rangeSonarJammer, p);
3648 					}
3649 
3650 					std::vector<Command> cv;
3651 					if (GetQueueKeystate()) {
3652 
3653 						Command c = bpi->CreateCommand();
3654 						std::vector<Command> temp;
3655 						CUnitSet::iterator ui = selectedUnitsHandler.selectedUnits.begin();
3656 						for (; ui != selectedUnitsHandler.selectedUnits.end(); ++ui) {
3657 							temp = (*ui)->commandAI->GetOverlapQueued(c);
3658 							std::vector<Command>::iterator ti = temp.begin();
3659 							for (; ti != temp.end(); ++ti) {
3660 								cv.insert(cv.end(),*ti);
3661 							}
3662 						}
3663 					}
3664 					if (unitDrawer->ShowUnitBuildSquare(*bpi, cv)) {
3665 						glColor4f(0.7f,1,1,0.4f);
3666 					} else {
3667 						glColor4f(1,0.5f,0.5f,0.4f);
3668 					}
3669 
3670 					if (!onMinimap) {
3671 						CUnitDrawer::DrawBuildingSample(bpi->def, gu->myTeam, buildpos, bpi->buildFacing);
3672 
3673 						glBlendFunc((GLenum)cmdColors.SelectedBlendSrc(), (GLenum)cmdColors.SelectedBlendDst());
3674 					}
3675 				}
3676 			}
3677 		}
3678 	}
3679 
3680 	// draw range circles if attack orders are imminent
3681 	int defcmd = GetDefaultCommand(mouse->lastx, mouse->lasty, cameraPos, mouseDir);
3682 	if ((inCommand>=0 && (size_t)inCommand<commands.size() && commands[inCommand].id==CMD_ATTACK) ||
3683 		(inCommand==-1 && defcmd>0 && commands[defcmd].id==CMD_ATTACK)
3684 	) {
3685 		for(CUnitSet::iterator si=selectedUnitsHandler.selectedUnits.begin(); si!=selectedUnitsHandler.selectedUnits.end(); ++si) {
3686 			CUnit* unit = *si;
3687 			if (unit == pointedAt) {
3688 				continue;
3689 			}
3690 			if (onMinimap && (unit->unitDef->speed > 0.0f)) {
3691 				continue;
3692 			}
3693 			if(unit->maxRange>0 && ((unit->losStatus[gu->myAllyTeam] & LOS_INLOS) || gu->spectatingFullView)) {
3694 				glDisable(GL_DEPTH_TEST);
3695 				glColor4fv(cmdColors.rangeAttack);
3696 				glBallisticCircle(unit->pos, unit->maxRange,
3697 				                  unit->weapons.front(), 40);
3698 				glEnable(GL_DEPTH_TEST);
3699 				if (!onMinimap && gs->cheatEnabled && globalRendering->drawdebug) {
3700 					DrawWeaponArc(unit);
3701 				}
3702 			}
3703 		}
3704 	}
3705 
3706 	glLineWidth(1.0f);
3707 
3708 	if (!onMinimap) {
3709 		glDepthMask(GL_TRUE);
3710 		glDisable(GL_BLEND);
3711 	}
3712 }
3713 
3714 
DrawMiniMapMarker(const float3 & cameraPos)3715 void CGuiHandler::DrawMiniMapMarker(const float3& cameraPos)
3716 {
3717 	const float w = 10.0f;
3718 	const float h = 30.0f;
3719 
3720 	const float bc[4] = { 0.1f, 0.333f, 1.0f, 0.75f }; // base color
3721 	const float d[8] = { 0.4f, 0.8f, 0.6f, 1.0f, 0.3f, 0.7f, 0.5f, 0.9f };
3722 	const float colors[8][4] = {
3723 		{ bc[0] * d[0],  bc[1] * d[0],  bc[2] * d[0],  bc[3] },
3724 		{ bc[0] * d[1],  bc[1] * d[1],  bc[2] * d[1],  bc[3] },
3725 		{ bc[0] * d[2],  bc[1] * d[2],  bc[2] * d[2],  bc[3] },
3726 		{ bc[0] * d[3],  bc[1] * d[3],  bc[2] * d[3],  bc[3] },
3727 		{ bc[0] * d[4],  bc[1] * d[4],  bc[2] * d[4],  bc[3] },
3728 		{ bc[0] * d[5],  bc[1] * d[5],  bc[2] * d[5],  bc[3] },
3729 		{ bc[0] * d[6],  bc[1] * d[6],  bc[2] * d[6],  bc[3] },
3730 		{ bc[0] * d[7],  bc[1] * d[7],  bc[2] * d[7],  bc[3] },
3731 	};
3732 
3733 	const float groundLevel = CGround::GetHeightAboveWater(cameraPos.x, cameraPos.z, false);
3734 
3735 	static float spinTime = 0.0f;
3736 	spinTime = math::fmod(spinTime + globalRendering->lastFrameTime * 0.001f, 60.0f);
3737 
3738 	glPushMatrix();
3739 	glTranslatef(cameraPos.x, groundLevel, cameraPos.z);
3740 	glRotatef(360.0f * (spinTime / 2.0f), 0.0f, 1.0f, 0.0f);
3741 
3742 	glEnable(GL_BLEND);
3743 	glShadeModel(GL_FLAT);
3744 	glBlendFunc(GL_SRC_ALPHA, GL_ONE);
3745 	glBegin(GL_TRIANGLE_FAN);
3746 		                       glVertex3f(0.0f, 0.0f, 0.0f);
3747 		                       glVertex3f(  +w,   +h, 0.0f);
3748 		glColor4fv(colors[4]); glVertex3f(0.0f,   +h,   +w);
3749 		glColor4fv(colors[5]); glVertex3f(  -w,   +h, 0.0f);
3750 		glColor4fv(colors[6]); glVertex3f(0.0f,   +h,   -w);
3751 		glColor4fv(colors[7]); glVertex3f(  +w,   +h, 0.0f);
3752 	glEnd();
3753 	glBegin(GL_TRIANGLE_FAN);
3754 		                       glVertex3f(0.0f, h * 2.0f, 0.0f);
3755 		                       glVertex3f(  +w,   +h, 0.0f);
3756 		glColor4fv(colors[3]); glVertex3f(0.0f,   +h,   -w);
3757 		glColor4fv(colors[2]); glVertex3f(  -w,   +h, 0.0f);
3758 		glColor4fv(colors[1]); glVertex3f(0.0f,   +h,   +w);
3759 		glColor4fv(colors[0]); glVertex3f(  +w,   +h, 0.0f);
3760 	glEnd();
3761 	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
3762 	glShadeModel(GL_SMOOTH);
3763 	glPopMatrix();
3764 }
3765 
3766 
DrawCentroidCursor()3767 void CGuiHandler::DrawCentroidCursor()
3768 {
3769 	int cmd = -1;
3770 	if ((inCommand >= 0) && ((size_t)inCommand < commands.size())) {
3771 		cmd = commands[inCommand].id;
3772 	} else {
3773 		int defcmd;
3774 		if (mouse->buttons[SDL_BUTTON_RIGHT].pressed &&
3775 				((activeReceiver == this) || (minimap->ProxyMode()))) {
3776 			defcmd = defaultCmdMemory;
3777 		} else {
3778 			defcmd = GetDefaultCommand(mouse->lastx, mouse->lasty);
3779 		}
3780 		if ((defcmd >= 0) && ((size_t)defcmd < commands.size())) {
3781 			cmd = commands[defcmd].id;
3782 		}
3783 	}
3784 
3785 	if ((cmd == CMD_MOVE) || (cmd == CMD_GATHERWAIT)) {
3786 		if (!KeyInput::GetKeyModState(KMOD_CTRL) && !KeyInput::GetKeyModState(KMOD_ALT) && !KeyInput::GetKeyModState(KMOD_GUI)) {
3787 			return;
3788 		}
3789 	} else if ((cmd == CMD_FIGHT) || (cmd == CMD_PATROL)) {
3790 		if (!KeyInput::GetKeyModState(KMOD_CTRL)) {
3791 			return;
3792 		}
3793 	} else {
3794 		return;
3795 	}
3796 
3797 	const CUnitSet& selUnits = selectedUnitsHandler.selectedUnits;
3798 	if (selUnits.size() < 2) {
3799 		return;
3800 	}
3801 
3802 	float3 pos;
3803 	CUnitSet::const_iterator it;
3804 	for (it = selUnits.begin(); it != selUnits.end(); ++it) {
3805 		pos += (*it)->midPos;
3806 	}
3807 	pos /= (float)selUnits.size();
3808 	const float3 winPos = camera->CalcWindowCoordinates(pos);
3809 	if (winPos.z <= 1.0f) {
3810 		const CMouseCursor* mc = mouse->FindCursor("Centroid");
3811 		if (mc != NULL) {
3812 			glDisable(GL_DEPTH_TEST);
3813 			mc->Draw((int)winPos.x, globalRendering->viewSizeY - (int)winPos.y, 1.0f);
3814 			glEnable(GL_DEPTH_TEST);
3815 		}
3816 	}
3817 }
3818 
3819 
DrawArea(float3 pos,float radius,const float * color)3820 void CGuiHandler::DrawArea(float3 pos, float radius, const float* color)
3821 {
3822 	if (useStencil) {
3823 		DrawSelectCircle(pos, radius, color);
3824 		return;
3825 	}
3826 
3827 	glDisable(GL_TEXTURE_2D);
3828 	glEnable(GL_BLEND);
3829 	glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
3830 	glColor4f(color[0], color[1], color[2], 0.25f);
3831 
3832 	glDisable(GL_DEPTH_TEST);
3833 	glDisable(GL_FOG);
3834 	glBegin(GL_TRIANGLE_FAN);
3835 		glVertexf3(pos);
3836 		for(int a=0;a<=40;++a){
3837 			float3 p(fastmath::cos(a*2*PI/40)*radius,0,fastmath::sin(a*2*PI/40)*radius);
3838 			p+=pos;
3839 			p.y=CGround::GetHeightAboveWater(p.x, p.z, false);
3840 			glVertexf3(p);
3841 		}
3842 	glEnd();
3843 	glEnable(GL_DEPTH_TEST);
3844 	glEnable(GL_FOG);
3845 }
3846 
3847 
DrawFront(int button,float maxSize,float sizeDiv,bool onMinimap,const float3 & cameraPos,const float3 & mouseDir)3848 void CGuiHandler::DrawFront(int button, float maxSize, float sizeDiv, bool onMinimap, const float3& cameraPos, const float3& mouseDir)
3849 {
3850 	CMouseHandler::ButtonPressEvt& bp = mouse->buttons[button];
3851 	if(bp.movement<5){
3852 		return;
3853 	}
3854 	float dist = CGround::LineGroundCol(bp.camPos, bp.camPos + bp.dir * globalRendering->viewRange * 1.4f, false);
3855 	if(dist<0){
3856 		return;
3857 	}
3858 	float3 pos1=bp.camPos+bp.dir*dist;
3859 	dist = CGround::LineGroundCol(cameraPos, cameraPos + mouseDir * globalRendering->viewRange * 1.4f, false);
3860 	if(dist<0){
3861 		return;
3862 	}
3863 	float3 pos2 = cameraPos + (mouseDir * dist);
3864 
3865 	ProcessFrontPositions(pos1, pos2);
3866 
3867 	float3 forward=(pos1-pos2).cross(UpVector);
3868 	forward.ANormalize();
3869 	float3 side=forward.cross(UpVector);
3870 	if(pos1.SqDistance2D(pos2)>maxSize*maxSize){
3871 		pos2=pos1+side*maxSize;
3872 		pos2.y=CGround::GetHeightAboveWater(pos2.x, pos2.z, false);
3873 	}
3874 
3875 	glColor4f(0.5f, 1.0f, 0.5f, 0.5f);
3876 
3877 	if (onMinimap) {
3878 		pos1 += (pos1 - pos2);
3879 		glLineWidth(2.0f);
3880 		glBegin(GL_LINES);
3881 		glVertexf3(pos1);
3882 		glVertexf3(pos2);
3883 		glEnd();
3884 		return;
3885 	}
3886 
3887 	glDisable(GL_TEXTURE_2D);
3888 	glEnable(GL_BLEND);
3889 	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
3890 
3891 	glDisable(GL_DEPTH_TEST);
3892 	glBegin(GL_QUADS);
3893 		glVertexf3(pos1+side*25);
3894 		glVertexf3(pos1-side*25);
3895 		glVertexf3(pos1-side*25+forward*50);
3896 		glVertexf3(pos1+side*25+forward*50);
3897 
3898 		glVertexf3(pos1+side*40+forward*50);
3899 		glVertexf3(pos1-side*40+forward*50);
3900 		glVertexf3(pos1+forward*100);
3901 		glVertexf3(pos1+forward*100);
3902 	glEnd();
3903 	glEnable(GL_DEPTH_TEST);
3904 
3905 	pos1 += (pos1 - pos2);
3906 	const int maxSteps = 256;
3907 	const float frontLen = (pos1 - pos2).Length2D();
3908 	const int steps = std::min(maxSteps, std::max(1, int(frontLen / 16.0f)));
3909 
3910 	glDisable(GL_FOG);
3911 	glBegin(GL_QUAD_STRIP);
3912 	const float3 delta = (pos2 - pos1) / (float)steps;
3913 	for (int i = 0; i <= steps; i++) {
3914 		float3 p;
3915 		const float d = (float)i;
3916 		p.x = pos1.x + (d * delta.x);
3917 		p.z = pos1.z + (d * delta.z);
3918 		p.y = CGround::GetHeightAboveWater(p.x, p.z, false);
3919 		p.y -= 100.f; glVertexf3(p);
3920 		p.y += 200.f; glVertexf3(p);
3921 	}
3922 	glEnd();
3923 
3924 	glEnable(GL_FOG);
3925 }
3926 
3927 
3928 /******************************************************************************/
3929 /******************************************************************************/
3930 
3931 struct BoxData {
3932 	float3 mins;
3933 	float3 maxs;
3934 };
3935 
3936 
DrawBoxShape(const void * data)3937 static void DrawBoxShape(const void* data)
3938 {
3939 	const BoxData* boxData = static_cast<const BoxData*>(data);
3940 	const float3& mins = boxData->mins;
3941 	const float3& maxs = boxData->maxs;
3942 	glBegin(GL_QUADS);
3943 		// the top
3944 		glVertex3f(mins.x, maxs.y, mins.z);
3945 		glVertex3f(mins.x, maxs.y, maxs.z);
3946 		glVertex3f(maxs.x, maxs.y, maxs.z);
3947 		glVertex3f(maxs.x, maxs.y, mins.z);
3948 		// the bottom
3949 		glVertex3f(mins.x, mins.y, mins.z);
3950 		glVertex3f(maxs.x, mins.y, mins.z);
3951 		glVertex3f(maxs.x, mins.y, maxs.z);
3952 		glVertex3f(mins.x, mins.y, maxs.z);
3953 	glEnd();
3954 	glBegin(GL_QUAD_STRIP);
3955 		// the sides
3956 		glVertex3f(mins.x, maxs.y, mins.z);
3957 		glVertex3f(mins.x, mins.y, mins.z);
3958 		glVertex3f(mins.x, maxs.y, maxs.z);
3959 		glVertex3f(mins.x, mins.y, maxs.z);
3960 		glVertex3f(maxs.x, maxs.y, maxs.z);
3961 		glVertex3f(maxs.x, mins.y, maxs.z);
3962 		glVertex3f(maxs.x, maxs.y, mins.z);
3963 		glVertex3f(maxs.x, mins.y, mins.z);
3964 		glVertex3f(mins.x, maxs.y, mins.z);
3965 		glVertex3f(mins.x, mins.y, mins.z);
3966 	glEnd();
3967 }
3968 
3969 
DrawCornerPosts(const float3 & pos0,const float3 & pos1)3970 static void DrawCornerPosts(const float3& pos0, const float3& pos1)
3971 {
3972 	const float3 lineVector(0.0f, 128.0f, 0.0f);
3973 	const float3 corner0(pos0.x, CGround::GetHeightAboveWater(pos0.x, pos0.z, false), pos0.z);
3974 	const float3 corner1(pos1.x, CGround::GetHeightAboveWater(pos1.x, pos1.z, false), pos1.z);
3975 	const float3 corner2(pos0.x, CGround::GetHeightAboveWater(pos0.x, pos1.z, false), pos1.z);
3976 	const float3 corner3(pos1.x, CGround::GetHeightAboveWater(pos1.x, pos0.z, false), pos0.z);
3977 	glEnable(GL_BLEND);
3978 	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
3979 	glLineWidth(2.0f);
3980 	glBegin(GL_LINES);
3981 		glColor4f(1.0f, 1.0f, 0.0f, 0.9f);
3982 		glVertexf3(corner0); glVertexf3(corner0 + lineVector);
3983 		glColor4f(0.0f, 1.0f, 0.0f, 0.9f);
3984 		glVertexf3(corner1); glVertexf3(corner1 + lineVector);
3985 		glColor4f(0.0f, 0.0f, 1.0f, 0.9f);
3986 		glVertexf3(corner2); glVertexf3(corner2 + lineVector);
3987 		glVertexf3(corner3); glVertexf3(corner3 + lineVector);
3988 	glEnd();
3989 	glLineWidth(1.0f);
3990 }
3991 
3992 
StencilDrawSelectBox(const float3 & pos0,const float3 & pos1,bool invColorSelect)3993 static void StencilDrawSelectBox(const float3& pos0, const float3& pos1,
3994 		bool invColorSelect)
3995 {
3996 	BoxData boxData;
3997 	boxData.mins = float3(std::min(pos0.x, pos1.x), readMap->GetCurrMinHeight() -   250.0f, std::min(pos0.z, pos1.z));
3998 	boxData.maxs = float3(std::max(pos0.x, pos1.x), readMap->GetCurrMaxHeight() + 10000.0f, std::max(pos0.z, pos1.z));
3999 
4000 	glDisable(GL_TEXTURE_2D);
4001 	glDisable(GL_FOG);
4002 
4003 	glEnable(GL_BLEND);
4004 
4005 	if (!invColorSelect) {
4006 		glBlendFunc(GL_SRC_ALPHA, GL_ONE);
4007 		glColor4f(1.0f, 0.0f, 0.0f, 0.25f);
4008 		glDrawVolume(DrawBoxShape, &boxData);
4009 	} else {
4010 		glEnable(GL_COLOR_LOGIC_OP);
4011 		glLogicOp(GL_INVERT);
4012 		glDrawVolume(DrawBoxShape, &boxData);
4013 		glDisable(GL_COLOR_LOGIC_OP);
4014 	}
4015 
4016 	DrawCornerPosts(pos0, pos1);
4017 
4018 	glEnable(GL_FOG);
4019 }
4020 
4021 
FullScreenDraw()4022 static void FullScreenDraw()
4023 {
4024 	glMatrixMode(GL_PROJECTION);
4025 	glPushMatrix();
4026 	glLoadIdentity();
4027 	glMatrixMode(GL_MODELVIEW);
4028 	glLoadIdentity();
4029 	glPushMatrix();
4030 	glRectf(-1.0f, -1.0f, +1.0f, +1.0f);
4031 	glMatrixMode(GL_PROJECTION);
4032 	glPopMatrix();
4033 	glMatrixMode(GL_MODELVIEW);
4034 	glPopMatrix();
4035 }
4036 
4037 
DrawMinMaxBox(const float3 & mins,const float3 & maxs)4038 static void DrawMinMaxBox(const float3& mins, const float3& maxs)
4039 {
4040 	glBegin(GL_QUADS);
4041 		// the top
4042 		glVertex3f(mins.x, maxs.y, mins.z);
4043 		glVertex3f(maxs.x, maxs.y, mins.z);
4044 		glVertex3f(maxs.x, maxs.y, maxs.z);
4045 		glVertex3f(mins.x, maxs.y, maxs.z);
4046 	glEnd();
4047 	glBegin(GL_QUAD_STRIP);
4048 		// the sides
4049 		glVertex3f(mins.x, mins.y, mins.z);
4050 		glVertex3f(mins.x, maxs.y, mins.z);
4051 		glVertex3f(mins.x, mins.y, maxs.z);
4052 		glVertex3f(mins.x, maxs.y, maxs.z);
4053 		glVertex3f(maxs.x, mins.y, maxs.z);
4054 		glVertex3f(maxs.x, maxs.y, maxs.z);
4055 		glVertex3f(maxs.x, mins.y, mins.z);
4056 		glVertex3f(maxs.x, maxs.y, mins.z);
4057 		glVertex3f(mins.x, mins.y, mins.z);
4058 		glVertex3f(mins.x, maxs.y, mins.z);
4059 	glEnd();
4060 }
4061 
4062 
DrawSelectBox(const float3 & pos0,const float3 & pos1,const float3 & cameraPos)4063 void CGuiHandler::DrawSelectBox(const float3& pos0, const float3& pos1, const float3& cameraPos)
4064 {
4065 	if (useStencil) {
4066 		StencilDrawSelectBox(pos0, pos1, invColorSelect);
4067 		return;
4068 	}
4069 
4070 	const float3 mins(std::min(pos0.x, pos1.x), readMap->GetCurrMinHeight() -   250.0f, std::min(pos0.z, pos1.z));
4071 	const float3 maxs(std::max(pos0.x, pos1.x), readMap->GetCurrMaxHeight() + 10000.0f, std::max(pos0.z, pos1.z));
4072 
4073 	glDisable(GL_TEXTURE_2D);
4074 	glDisable(GL_FOG);
4075 	glDisable(GL_BLEND);
4076 
4077 	glDepthMask(GL_FALSE);
4078 	glEnable(GL_CULL_FACE);
4079 
4080 	glEnable(GL_COLOR_LOGIC_OP);
4081 	glLogicOp(GL_INVERT);
4082 
4083 	// invert the color for objects within the box
4084 	glCullFace(GL_FRONT); DrawMinMaxBox(mins, maxs);
4085 	glCullFace(GL_BACK);  DrawMinMaxBox(mins, maxs);
4086 
4087 	glDisable(GL_CULL_FACE);
4088 
4089 	// do a full screen inversion if the camera is within the box
4090 	if ((cameraPos.x > mins.x) && (cameraPos.x < maxs.x) &&
4091 	    (cameraPos.y > mins.y) && (cameraPos.y < maxs.y) &&
4092 	    (cameraPos.z > mins.z) && (cameraPos.z < maxs.z)) {
4093 		glDisable(GL_DEPTH_TEST);
4094 		FullScreenDraw();
4095 		glEnable(GL_DEPTH_TEST);
4096 	}
4097 
4098 	glDisable(GL_COLOR_LOGIC_OP);
4099 
4100 	glEnable(GL_BLEND);
4101 	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
4102 	DrawCornerPosts(pos0, pos1);
4103 
4104 //	glDepthMask(GL_TRUE);
4105 	glEnable(GL_FOG);
4106 }
4107 
4108 
4109 /******************************************************************************/
4110 
4111 struct CylinderData {
4112 	int divs;
4113 	float xc, zc, yp, yn;
4114 	float radius;
4115 };
4116 
4117 
DrawCylinderShape(const void * data)4118 static void DrawCylinderShape(const void* data)
4119 {
4120 	const CylinderData& cyl = *static_cast<const CylinderData*>(data);
4121 	const float step = fastmath::PI2 / (float)cyl.divs;
4122 	int i;
4123 	glBegin(GL_QUAD_STRIP); // the sides
4124 	for (i = 0; i <= cyl.divs; i++) {
4125 		const float radians = step * float(i % cyl.divs);
4126 		const float x = cyl.xc + (cyl.radius * fastmath::sin(radians));
4127 		const float z = cyl.zc + (cyl.radius * fastmath::cos(radians));
4128 		glVertex3f(x, cyl.yp, z);
4129 		glVertex3f(x, cyl.yn, z);
4130 	}
4131 	glEnd();
4132 	glBegin(GL_TRIANGLE_FAN); // the top
4133 	for (i = 0; i < cyl.divs; i++) {
4134 		const float radians = step * float(i);
4135 		const float x = cyl.xc + (cyl.radius * fastmath::sin(radians));
4136 		const float z = cyl.zc + (cyl.radius * fastmath::cos(radians));
4137 		glVertex3f(x, cyl.yp, z);
4138 	}
4139 	glEnd();
4140 	glBegin(GL_TRIANGLE_FAN); // the bottom
4141 	for (i = (cyl.divs - 1); i >= 0; i--) {
4142 		const float radians = step * float(i);
4143 		const float x = cyl.xc + (cyl.radius * fastmath::sin(radians));
4144 		const float z = cyl.zc + (cyl.radius * fastmath::cos(radians));
4145 		glVertex3f(x, cyl.yn, z);
4146 	}
4147 	glEnd();
4148 }
4149 
4150 
DrawSelectCircle(const float3 & pos,float radius,const float * color)4151 void CGuiHandler::DrawSelectCircle(const float3& pos, float radius,
4152                                    const float* color)
4153 {
4154 	CylinderData cylData;
4155 	cylData.xc = pos.x;
4156 	cylData.zc = pos.z;
4157 	cylData.yp = readMap->GetCurrMaxHeight() + 10000.0f;
4158 	cylData.yn = readMap->GetCurrMinHeight() -   250.0f;
4159 	cylData.radius = radius;
4160 	cylData.divs = 128;
4161 
4162 	glDisable(GL_TEXTURE_2D);
4163 	glDisable(GL_FOG);
4164 
4165 	glEnable(GL_BLEND);
4166 	glBlendFunc(GL_SRC_ALPHA, GL_ONE);
4167 	glColor4f(color[0], color[1], color[2], 0.25f);
4168 
4169 	glDrawVolume(DrawCylinderShape, &cylData);
4170 
4171 	// draw the center line
4172 	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
4173 	glColor4f(color[0], color[1], color[2], 0.9f);
4174 	glLineWidth(2.0f);
4175 	const float3 base(pos.x, CGround::GetHeightAboveWater(pos.x, pos.z, false), pos.z);
4176 	glBegin(GL_LINES);
4177 		glVertexf3(base);
4178 		glVertexf3(base + float3(0.0f, 128.0f, 0.0f));
4179 	glEnd();
4180 	glLineWidth(1.0f);
4181 
4182 	glEnable(GL_FOG);
4183 }
4184 
4185 
4186 /******************************************************************************/
4187 /******************************************************************************/
4188 
SetBuildFacing(unsigned int facing)4189 void CGuiHandler::SetBuildFacing(unsigned int facing)
4190 {
4191 	buildFacing = facing % NUM_FACINGS;
4192 }
4193 
SetBuildSpacing(int spacing)4194 void CGuiHandler::SetBuildSpacing(int spacing)
4195 {
4196 	buildSpacing = spacing;
4197 	if (buildSpacing < 0)
4198 		buildSpacing = 0;
4199 }
4200 
4201 /******************************************************************************/
4202 /******************************************************************************/
4203