1 //       _________ __                 __
2 //      /   _____//  |_____________ _/  |______     ____  __ __  ______
3 //      \_____  \\   __\_  __ \__  \\   __\__  \   / ___\|  |  \/  ___/
4 //      /        \|  |  |  | \// __ \|  |  / __ \_/ /_/  >  |  /\___ |
5 //     /_______  /|__|  |__|  (____  /__| (____  /\___  /|____//____  >
6 //             \/                  \/          \//_____/            \/
7 //  ______________________                           ______________________
8 //                        T H E   W A R   B E G I N S
9 //         Stratagus - A free fantasy real time strategy game engine
10 //
11 /**@name mouse.cpp - The mouse handling. */
12 //
13 //      (c) Copyright 1998-2015 by Lutz Sammer, Jimmy Salmon and Andrettin
14 //
15 //      This program is free software; you can redistribute it and/or modify
16 //      it under the terms of the GNU General Public License as published by
17 //      the Free Software Foundation; only version 2 of the License.
18 //
19 //      This program is distributed in the hope that it will be useful,
20 //      but WITHOUT ANY WARRANTY; without even the implied warranty of
21 //      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22 //      GNU General Public License for more details.
23 //
24 //      You should have received a copy of the GNU General Public License
25 //      along with this program; if not, write to the Free Software
26 //      Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
27 //      02111-1307, USA.
28 //
29 
30 //@{
31 
32 #define ICON_SIZE_X (UI.ButtonPanel.Buttons[0].Style->Width)
33 #define ICON_SIZE_Y (UI.ButtonPanel.Buttons[0].Style->Height)
34 
35 /*----------------------------------------------------------------------------
36 --  Includes
37 ----------------------------------------------------------------------------*/
38 
39 #include <ctype.h>
40 #include <stdexcept>
41 
42 #include "stratagus.h"
43 
44 #include "ui.h"
45 
46 #include "action/action_build.h"
47 #include "action/action_train.h"
48 #include "actions.h"
49 #include "commands.h"
50 #include "cursor.h"
51 #include "font.h"
52 #include "interface.h"
53 #include "map.h"
54 #include "menus.h"
55 #include "minimap.h"
56 #include "missile.h"
57 #include "network.h"
58 #include "player.h"
59 #include "sound.h"
60 #include "spells.h"
61 #include "translate.h"
62 #include "unit.h"
63 #include "unit_find.h"
64 #include "unitsound.h"
65 #include "unittype.h"
66 #include "video.h"
67 #include "widgets.h"
68 
69 /*----------------------------------------------------------------------------
70 --  Variables
71 ----------------------------------------------------------------------------*/
72 
73 int MouseButtons;                            /// Current pressed mouse buttons
74 
75 int KeyModifiers;                            /// Current keyboard modifiers
76 
77 CUnit *UnitUnderCursor;                      /// Unit under cursor
78 int ButtonAreaUnderCursor = -1;              /// Button area under cursor
79 int ButtonUnderCursor = -1;                  /// Button under cursor
80 int OldButtonUnderCursor = -1;               /// Button under cursor
81 bool GameMenuButtonClicked;                  /// Menu button was clicked
82 bool GameDiplomacyButtonClicked;             /// Diplomacy button was clicked
83 bool LeaveStops;                             /// Mouse leaves windows stops scroll
84 
85 enum _cursor_on_ CursorOn = CursorOnUnknown; /// Cursor on field
86 
87 /*----------------------------------------------------------------------------
88 --  Functions
89 ----------------------------------------------------------------------------*/
90 static void HandlePieMenuMouseSelection();
91 
GetUnitUnderCursor()92 CUnit *GetUnitUnderCursor()
93 {
94 	return UnitUnderCursor;
95 }
96 
97 /**
98 **  Cancel building cursor mode.
99 */
CancelBuildingMode()100 void CancelBuildingMode()
101 {
102 	CursorBuilding = NULL;
103 	UI.StatusLine.Clear();
104 	UI.StatusLine.ClearCosts();
105 	CurrentButtonLevel = 0;
106 	UI.ButtonPanel.Update();
107 }
108 
CanBuildOnArea(const CUnit & unit,const Vec2i & pos)109 static bool CanBuildOnArea(const CUnit &unit, const Vec2i &pos)
110 {
111 	for (int j = 0; j < unit.Type->TileHeight; ++j) {
112 		for (int i = 0; i < unit.Type->TileWidth; ++i) {
113 			const Vec2i tempPos(i, j);
114 			if (!Map.Field(pos + tempPos)->playerInfo.IsExplored(*ThisPlayer)) {
115 				return false;
116 			}
117 		}
118 	}
119 	return true;
120 }
121 
DoRightButton_ForForeignUnit(CUnit * dest)122 static void DoRightButton_ForForeignUnit(CUnit *dest)
123 {
124 	CUnit &unit = *Selected[0];
125 
126 	if (unit.Player->Index != PlayerNumNeutral || dest == NULL
127 		|| !(dest->Player == ThisPlayer || dest->IsTeamed(*ThisPlayer))) {
128 		return;
129 	}
130 	// tell to go and harvest from a unit
131 	const int res = unit.Type->GivesResource;
132 
133 	if (res
134 		&& dest->Type->BoolFlag[HARVESTER_INDEX].value
135 		&& dest->Type->ResInfo[res]
136 		&& dest->ResourcesHeld < dest->Type->ResInfo[res]->ResourceCapacity
137 		&& unit.Type->BoolFlag[CANHARVEST_INDEX].value) {
138 		unit.Blink = 4;
139 		//  Right mouse with SHIFT appends command to old commands.
140 		const int flush = !(KeyModifiers & ModifierShift);
141 		SendCommandResource(*dest, unit, flush);
142 	}
143 }
144 
DoRightButton_Transporter(CUnit & unit,CUnit * dest,int flush,int & acknowledged)145 static bool DoRightButton_Transporter(CUnit &unit, CUnit *dest, int flush, int &acknowledged)
146 {
147 	//  Enter transporters ?
148 	if (dest == NULL) {
149 		return false;
150 	}
151 	// dest is the transporter
152 	if (dest->Type->CanTransport()) {
153 		// Let the transporter move to the unit. And QUEUE!!!
154 		if (dest->CanMove() && CanTransport(*dest, unit)) {
155 			DebugPrint("Send command follow\n");
156 			// is flush value correct ?
157 			if (!acknowledged) {
158 				PlayUnitSound(unit, VoiceAcknowledging);
159 				acknowledged = 1;
160 			}
161 			SendCommandFollow(*dest, unit, 0);
162 		}
163 		// FIXME : manage correctly production units.
164 		if (!unit.CanMove() || CanTransport(*dest, unit)) {
165 			dest->Blink = 4;
166 			DebugPrint("Board transporter\n");
167 			if (!acknowledged) {
168 				PlayUnitSound(unit, VoiceAcknowledging);
169 				acknowledged = 1;
170 			}
171 			SendCommandBoard(unit, *dest, flush);
172 			return true;
173 		}
174 	}
175 	//  unit is the transporter
176 	//  FIXME : Make it more configurable ? NumSelect == 1, lua option
177 	if (CanTransport(unit, *dest)) {
178 		// Let the transporter move to the unit. And QUEUE!!!
179 		if (unit.CanMove()) {
180 			DebugPrint("Send command follow\n");
181 			// is flush value correct ?
182 			if (!acknowledged) {
183 				PlayUnitSound(unit, VoiceAcknowledging);
184 				acknowledged = 1;
185 			}
186 			SendCommandFollow(unit, *dest, 0);
187 		} else if (!dest->CanMove()) {
188 			DebugPrint("Want to transport but no unit can move\n");
189 			return true;
190 		}
191 		dest->Blink = 4;
192 		DebugPrint("Board transporter\n");
193 		if (!acknowledged) {
194 			PlayUnitSound(unit, VoiceAcknowledging);
195 			acknowledged = 1;
196 		}
197 		SendCommandBoard(*dest, unit, flush);
198 		return true;
199 	}
200 	return false;
201 }
202 
DoRightButton_Harvest_Unit(CUnit & unit,CUnit & dest,int flush,int & acknowledged)203 static bool DoRightButton_Harvest_Unit(CUnit &unit, CUnit &dest, int flush, int &acknowledged)
204 {
205 	// Return a loaded harvester to deposit
206 	if (unit.ResourcesHeld > 0
207 		&& dest.Type->CanStore[unit.CurrentResource]
208 		&& (dest.Player == unit.Player
209 			|| (dest.Player->IsAllied(*unit.Player) && unit.Player->IsAllied(*dest.Player)))) {
210 		const ResourceInfo &resinfo = *unit.Type->ResInfo[unit.CurrentResource];
211 		if (!resinfo.TerrainHarvester || unit.ResourcesHeld >= resinfo.ResourceCapacity) {
212 			dest.Blink = 4;
213 			if (!acknowledged) {
214 				PlayUnitSound(unit, VoiceAcknowledging);
215 				acknowledged = 1;
216 			}
217 			SendCommandReturnGoods(unit, &dest, flush);
218 			return true;
219 		}
220 	}
221 	// Go and harvest from a unit
222 	const int res = dest.Type->GivesResource;
223 	const CUnitType &type = *unit.Type;
224 	if (res && type.ResInfo[res] && dest.Type->BoolFlag[CANHARVEST_INDEX].value
225 		&& (dest.Player == unit.Player || dest.Player->Index == PlayerNumNeutral)) {
226 			if (unit.CurrentResource != res || unit.ResourcesHeld < type.ResInfo[res]->ResourceCapacity) {
227 				dest.Blink = 4;
228 				SendCommandResource(unit, dest, flush);
229 				if (!acknowledged) {
230 					PlayUnitSound(unit, VoiceHarvesting);
231 					acknowledged = 1;
232 				}
233 				return true;
234 			} else {
235 				CUnit *depot = FindDeposit(unit, 1000, unit.CurrentResource);
236 				if (depot) {
237 					dest.Blink = 4;
238 					if (!acknowledged) {
239 						PlayUnitSound(unit, VoiceAcknowledging);
240 						acknowledged = 1;
241 					}
242 					SendCommandReturnGoods(unit, depot, flush);
243 					return true;
244 				}
245 			}
246 	}
247 	return false;
248 }
249 
DoRightButton_Harvest_Pos(CUnit & unit,const Vec2i & pos,int flush,int & acknowledged)250 static bool DoRightButton_Harvest_Pos(CUnit &unit, const Vec2i &pos, int flush, int &acknowledged)
251 {
252 	if (!Map.Field(pos)->playerInfo.IsExplored(*unit.Player)) {
253 		return false;
254 	}
255 	const CUnitType &type = *unit.Type;
256 	// FIXME: support harvesting more types of terrain.
257 	for (int res = 0; res < MaxCosts; ++res) {
258 		if (type.ResInfo[res]
259 			&& type.ResInfo[res]->TerrainHarvester
260 			&& Map.Field(pos)->IsTerrainResourceOnMap(res)) {
261 			if (unit.CurrentResource != res || unit.ResourcesHeld < type.ResInfo[res]->ResourceCapacity) {
262 				SendCommandResourceLoc(unit, pos, flush);
263 				if (!acknowledged) {
264 					PlayUnitSound(unit, VoiceHarvesting);
265 					acknowledged = 1;
266 				}
267 				return true;
268 			} else {
269 				CUnit *depot = FindDeposit(unit, 1000, unit.CurrentResource);
270 				if (depot) {
271 					if (!acknowledged) {
272 						PlayUnitSound(unit, VoiceAcknowledging);
273 						acknowledged = 1;
274 					}
275 					SendCommandReturnGoods(unit, depot, flush);
276 					return true;
277 				}
278 			}
279 		}
280 	}
281 	return false;
282 }
283 
DoRightButton_Worker(CUnit & unit,CUnit * dest,const Vec2i & pos,int flush,int & acknowledged)284 static bool DoRightButton_Worker(CUnit &unit, CUnit *dest, const Vec2i &pos, int flush, int &acknowledged)
285 {
286 	const CUnitType &type = *unit.Type;
287 
288 	// Go and repair
289 	if (type.RepairRange && dest != NULL
290 		&& dest->Type->RepairHP
291 		&& dest->Variable[HP_INDEX].Value < dest->Variable[HP_INDEX].Max
292 		&& (dest->Player == unit.Player || unit.IsAllied(*dest))) {
293 		dest->Blink = 4;
294 		if (!acknowledged) {
295 			PlayUnitSound(unit, VoiceRepairing);
296 			acknowledged = 1;
297 		}
298 		SendCommandRepair(unit, pos, dest, flush);
299 		return true;
300 	}
301 	// Harvest
302 	if (type.BoolFlag[HARVESTER_INDEX].value) {
303 		if (dest != NULL) {
304 			if (DoRightButton_Harvest_Unit(unit, *dest, flush, acknowledged)) {
305 				return true;
306 			}
307 		} else {
308 			if (DoRightButton_Harvest_Pos(unit, pos, flush, acknowledged)) {
309 				return true;
310 			}
311 		}
312 	}
313 	// Follow another unit
314 	if (UnitUnderCursor != NULL && dest != NULL && dest != &unit
315 		&& (dest->Player == unit.Player || unit.IsAllied(*dest) || dest->Player->Index == PlayerNumNeutral)) {
316 		dest->Blink = 4;
317 		if (!acknowledged) {
318 			PlayUnitSound(unit, VoiceAcknowledging);
319 			acknowledged = 1;
320 		}
321 		if (dest->Type->CanMove() == false && !dest->Type->BoolFlag[TELEPORTER_INDEX].value) {
322 			SendCommandMove(unit, pos, flush);
323 		} else {
324 			SendCommandFollow(unit, *dest, flush);
325 		}
326 		return true;
327 	}
328 	// Move
329 	if (!acknowledged) {
330 		PlayUnitSound(unit, VoiceAcknowledging);
331 		acknowledged = 1;
332 	}
333 	SendCommandMove(unit, pos, flush);
334 	return true;
335 }
336 
DoRightButton_AttackUnit(CUnit & unit,CUnit & dest,const Vec2i & pos,int flush,int & acknowledged)337 static bool DoRightButton_AttackUnit(CUnit &unit, CUnit &dest, const Vec2i &pos, int flush, int &acknowledged)
338 {
339 	const CUnitType &type = *unit.Type;
340 	const int action = type.MouseAction;
341 
342 	if (action == MouseActionSpellCast || unit.IsEnemy(dest)) {
343 		dest.Blink = 4;
344 		if (!acknowledged) {
345 			PlayUnitSound(unit, VoiceAttack);
346 			acknowledged = 1;
347 		}
348 		if (action == MouseActionSpellCast) {
349 			// This is for demolition squads and such
350 			Assert(unit.Type->CanCastSpell);
351 			size_t spellnum;
352 			for (spellnum = 0; !type.CanCastSpell[spellnum] && spellnum < SpellTypeTable.size() ; spellnum++) {
353 			}
354 			SendCommandSpellCast(unit, pos, &dest, spellnum, flush);
355 		} else {
356 			if (CanTarget(type, *dest.Type)) {
357 				SendCommandAttack(unit, pos, &dest, flush);
358 			} else { // No valid target
359 				SendCommandAttack(unit, pos, NoUnitP, flush);
360 			}
361 		}
362 		return true;
363 	}
364 	if ((dest.Player == unit.Player || unit.IsAllied(dest) || dest.Player->Index == PlayerNumNeutral) && &dest != &unit) {
365 		dest.Blink = 4;
366 		if (!acknowledged) {
367 			PlayUnitSound(unit, VoiceAcknowledging);
368 			acknowledged = 1;
369 		}
370 		if (!dest.Type->CanMove() && !dest.Type->BoolFlag[TELEPORTER_INDEX].value) {
371 			SendCommandMove(unit, pos, flush);
372 		} else {
373 			SendCommandFollow(unit, dest, flush);
374 		}
375 		return true;
376 	}
377 	return false;
378 }
379 
DoRightButton_Attack(CUnit & unit,CUnit * dest,const Vec2i & pos,int flush,int & acknowledged)380 static void DoRightButton_Attack(CUnit &unit, CUnit *dest, const Vec2i &pos, int flush, int &acknowledged)
381 {
382 	if (dest != NULL && unit.CurrentAction() != UnitActionBuilt) {
383 		if (DoRightButton_AttackUnit(unit, *dest, pos, flush, acknowledged)) {
384 			return;
385 		}
386 	}
387 	if (Map.WallOnMap(pos)) {
388 		if (unit.Player->Race == PlayerRaceHuman && Map.OrcWallOnMap(pos)) {
389 			SendCommandAttack(unit, pos, NoUnitP, flush);
390 			return;
391 		}
392 		if (unit.Player->Race == PlayerRaceOrc && Map.HumanWallOnMap(pos)) {
393 			SendCommandAttack(unit, pos, NoUnitP, flush);
394 			return;
395 		}
396 	}
397 	// empty space
398 	if ((KeyModifiers & ModifierControl)) {
399 		if (RightButtonAttacks) {
400 			SendCommandMove(unit, pos, flush);
401 			if (!acknowledged) {
402 				PlayUnitSound(unit, VoiceAcknowledging);
403 				acknowledged = 1;
404 			}
405 		} else {
406 			if (!acknowledged) {
407 				PlayUnitSound(unit, VoiceAttack);
408 				acknowledged = 1;
409 			}
410 			SendCommandAttack(unit, pos, NoUnitP, flush);
411 		}
412 	} else {
413 		if (RightButtonAttacks) {
414 			if (!acknowledged) {
415 				PlayUnitSound(unit, VoiceAttack);
416 				acknowledged = 1;
417 			}
418 			SendCommandAttack(unit, pos, NoUnitP, flush);
419 		} else {
420 			// Note: move is correct here, right default is move
421 			if (!acknowledged) {
422 				PlayUnitSound(unit, VoiceAcknowledging);
423 				acknowledged = 1;
424 			}
425 			SendCommandMove(unit, pos, flush);
426 		}
427 	}
428 	// FIXME: ALT-RIGHT-CLICK, move but fight back if attacked.
429 }
430 
DoRightButton_Follow(CUnit & unit,CUnit & dest,int flush,int & acknowledged)431 static bool DoRightButton_Follow(CUnit &unit, CUnit &dest, int flush, int &acknowledged)
432 {
433 	if (dest.Player == unit.Player || unit.IsAllied(dest) || dest.Player->Index == PlayerNumNeutral) {
434 		dest.Blink = 4;
435 		if (!acknowledged) {
436 			PlayUnitSound(unit, VoiceAcknowledging);
437 			acknowledged = 1;
438 		}
439 		if (dest.Type->CanMove() == false && !dest.Type->BoolFlag[TELEPORTER_INDEX].value) {
440 			SendCommandMove(unit, dest.tilePos, flush);
441 		} else {
442 			SendCommandFollow(unit, dest, flush);
443 		}
444 		return true;
445 	}
446 	return false;
447 }
448 
DoRightButton_Harvest_Reverse(CUnit & unit,CUnit & dest,int flush,int & acknowledged)449 static bool DoRightButton_Harvest_Reverse(CUnit &unit, CUnit &dest, int flush, int &acknowledged)
450 {
451 	const CUnitType &type = *unit.Type;
452 
453 	// tell to return a loaded harvester to deposit
454 	if (dest.ResourcesHeld > 0
455 		&& type.CanStore[dest.CurrentResource]
456 		&& dest.Player == unit.Player) {
457 		dest.Blink = 4;
458 		SendCommandReturnGoods(dest, &unit, flush);
459 		if (!acknowledged) {
460 			PlayUnitSound(unit, VoiceAcknowledging);
461 			acknowledged = 1;
462 		}
463 		return true;
464 	}
465 	// tell to go and harvest from a building
466 	const int res = type.GivesResource;
467 	if (res
468 		&& dest.Type->ResInfo[res]
469 		&& dest.ResourcesHeld < dest.Type->ResInfo[res]->ResourceCapacity
470 		&& type.BoolFlag[CANHARVEST_INDEX].value
471 		&& dest.Player == unit.Player) {
472 		unit.Blink = 4;
473 		SendCommandResource(dest, unit, flush);
474 		return true;
475 	}
476 	return false;
477 }
478 
DoRightButton_NewOrder(CUnit & unit,CUnit * dest,const Vec2i & pos,int flush,int & acknowledged)479 static bool DoRightButton_NewOrder(CUnit &unit, CUnit *dest, const Vec2i &pos, int flush, int &acknowledged)
480 {
481 	// Go and harvest from a unit
482 	if (dest != NULL && dest->Type->GivesResource && dest->Type->BoolFlag[CANHARVEST_INDEX].value
483 		&& (dest->Player == unit.Player || dest->Player->Index == PlayerNumNeutral)) {
484 		dest->Blink = 4;
485 		if (!acknowledged) {
486 			PlayUnitSound(unit, VoiceAcknowledging);
487 			acknowledged = 1;
488 		}
489 		SendCommandResource(unit, *dest, flush);
490 		return true;
491 	}
492 	// FIXME: support harvesting more types of terrain.
493 	const CMapField &mf = *Map.Field(pos);
494 	if (mf.playerInfo.IsExplored(*unit.Player) && mf.IsTerrainResourceOnMap()) {
495 		if (!acknowledged) {
496 			PlayUnitSound(unit, VoiceAcknowledging);
497 			acknowledged = 1;
498 		}
499 		SendCommandResourceLoc(unit, pos, flush);
500 		return true;
501 	}
502 	return false;
503 }
504 
DoRightButton_ForSelectedUnit(CUnit & unit,CUnit * dest,const Vec2i & pos,int & acknowledged)505 static void DoRightButton_ForSelectedUnit(CUnit &unit, CUnit *dest, const Vec2i &pos, int &acknowledged)
506 {
507 	// don't self targeting.
508 	if (dest == &unit) {
509 		return;
510 	}
511 	if (unit.Removed) {
512 		return;
513 	}
514 	const CUnitType &type = *unit.Type;
515 	const int action = type.MouseAction;
516 	//  Right mouse with SHIFT appends command to old commands.
517 	const int flush = !(KeyModifiers & ModifierShift);
518 
519 	//  Control + alt click - ground attack
520 	if ((KeyModifiers & ModifierControl) && (KeyModifiers & ModifierAlt)) {
521 		if (unit.Type->BoolFlag[GROUNDATTACK_INDEX].value) {
522 			if (!acknowledged) {
523 				PlayUnitSound(unit, VoiceAttack);
524 				acknowledged = 1;
525 			}
526 			SendCommandAttackGround(unit, pos, flush);
527 			return;
528 		}
529 	}
530 	//  Control + right click on unit is follow anything.
531 	if ((KeyModifiers & ModifierControl) && dest) {
532 		dest->Blink = 4;
533 		if (!acknowledged) {
534 			PlayUnitSound(unit, VoiceAcknowledging);
535 			acknowledged = 1;
536 		}
537 		SendCommandFollow(unit, *dest, flush);
538 		return;
539 	}
540 
541 	//  Alt + right click on unit is defend anything.
542 	if ((KeyModifiers & ModifierAlt) && dest) {
543 		dest->Blink = 4;
544 		if (!acknowledged) {
545 			PlayUnitSound(unit, VoiceAcknowledging);
546 			acknowledged = 1;
547 		}
548 		SendCommandDefend(unit, *dest, flush);
549 		return;
550 	}
551 
552 	if (DoRightButton_Transporter(unit, dest, flush, acknowledged)) {
553 		return;
554 	}
555 
556 	//  Handle resource workers.
557 	if (action == MouseActionHarvest) {
558 		DoRightButton_Worker(unit, dest, pos, flush, acknowledged);
559 		return;
560 	}
561 
562 	//  Fighters
563 	if (action == MouseActionSpellCast || action == MouseActionAttack) {
564 		DoRightButton_Attack(unit, dest, pos, flush, acknowledged);
565 		return;
566 	}
567 
568 	// FIXME: attack/follow/board ...
569 	if (dest != NULL && (action == MouseActionMove || action == MouseActionSail)) {
570 		if (DoRightButton_Follow(unit, *dest, flush, acknowledged)) {
571 			return;
572 		}
573 	}
574 
575 	// Manage harvester from the destination side.
576 	if (dest != NULL && dest->Type->BoolFlag[HARVESTER_INDEX].value) {
577 		if (DoRightButton_Harvest_Reverse(unit, *dest, flush, acknowledged)) {
578 			return;
579 		}
580 	}
581 
582 	// Manage new order.
583 	if (!unit.CanMove()) {
584 		if (DoRightButton_NewOrder(unit, dest, pos, flush, acknowledged)) {
585 			return;
586 		}
587 	}
588 	if (!acknowledged) {
589 		PlayUnitSound(unit, VoiceAcknowledging);
590 		acknowledged = 1;
591 	}
592 	SendCommandMove(unit, pos, flush);
593 }
594 
595 /**
596 **  Called when right button is pressed
597 **
598 **  @param mapPixelPos  map position in pixels.
599 */
DoRightButton(const PixelPos & mapPixelPos)600 void DoRightButton(const PixelPos &mapPixelPos)
601 {
602 	// No unit selected
603 	if (Selected.empty()) {
604 		return;
605 	}
606 	const Vec2i pos = Map.MapPixelPosToTilePos(mapPixelPos);
607 	CUnit *dest;            // unit under the cursor if any.
608 
609 	if (UnitUnderCursor != NULL && !UnitUnderCursor->Type->BoolFlag[DECORATION_INDEX].value) {
610 		dest = UnitUnderCursor;
611 	} else {
612 		dest = NULL;
613 	}
614 
615 	//  Unit selected isn't owned by the player.
616 	//  You can't select your own units + foreign unit(s)
617 	//  except if it is neutral and it is a resource.
618 	if (!CanSelectMultipleUnits(*Selected[0]->Player)) {
619 		DoRightButton_ForForeignUnit(dest);
620 		return;
621 	}
622 
623 	if (dest != NULL && dest->Type->CanTransport()) {
624 		for (size_t i = 0; i != Selected.size(); ++i) {
625 			if (CanTransport(*dest, *Selected[i])) {
626 				// We are clicking on a transporter. We have to:
627 				// 1) Flush the transporters orders.
628 				// 2) Tell the transporter to follow the units. We have to queue all
629 				//    these follow orders, so the transport will go and pick them up
630 				// 3) Tell all selected land units to go board the transporter.
631 
632 				// Here we flush the order queue
633 				SendCommandStopUnit(*dest);
634 				break;
635 			}
636 		}
637 	}
638 
639 	int acknowledged = 0; // to play sound
640 	for (size_t i = 0; i != Selected.size(); ++i) {
641 		Assert(Selected[i]);
642 		CUnit &unit = *Selected[i];
643 
644 		DoRightButton_ForSelectedUnit(unit, dest, pos, acknowledged);
645 	}
646 	ShowOrdersCount = GameCycle + Preference.ShowOrders * CYCLES_PER_SECOND;
647 }
648 
649 /**
650 **  Check if the mouse is on a button
651 **
652 **  @param screenPos  screen coordinate.
653 **
654 **  @return True if mouse is on the button, False otherwise.
655 */
Contains(const PixelPos & screenPos) const656 bool CUIButton::Contains(const PixelPos &screenPos) const
657 {
658 	Assert(this->Style);
659 
660 	return this->X <= screenPos.x && screenPos.x < this->X + this->Style->Width
661 		   && this->Y <= screenPos.y && screenPos.y < this->Y + this->Style->Height;
662 }
663 
664 /**
665 **  Set flag on which area is the cursor.
666 **
667 **  @param screenPos  screen position.
668 */
HandleMouseOn(const PixelPos screenPos)669 static void HandleMouseOn(const PixelPos screenPos)
670 {
671 	MouseScrollState = ScrollNone;
672 	ButtonAreaUnderCursor = -1;
673 	ButtonUnderCursor = -1;
674 
675 	if (IsDemoMode()) {
676 		// If we are in "demo mode", do nothing.
677 		return;
678 	}
679 
680 	// BigMapMode is the mode which show only the map (without panel, minimap)
681 	if (BigMapMode) {
682 		CursorOn = CursorOnMap;
683 		//  Scrolling Region Handling.
684 		HandleMouseScrollArea(screenPos);
685 		return;
686 	}
687 
688 	//  Handle buttons
689 	if (!IsNetworkGame()) {
690 		if (UI.MenuButton.X != -1) {
691 			if (UI.MenuButton.Contains(screenPos)) {
692 				ButtonAreaUnderCursor = ButtonAreaMenu;
693 				ButtonUnderCursor = ButtonUnderMenu;
694 				CursorOn = CursorOnButton;
695 				return;
696 			}
697 		}
698 	} else {
699 		if (UI.NetworkMenuButton.X != -1) {
700 			if (UI.NetworkMenuButton.Contains(screenPos)) {
701 				ButtonAreaUnderCursor = ButtonAreaMenu;
702 				ButtonUnderCursor = ButtonUnderNetworkMenu;
703 				CursorOn = CursorOnButton;
704 				return;
705 			}
706 		}
707 		if (UI.NetworkDiplomacyButton.X != -1) {
708 			if (UI.NetworkDiplomacyButton.Contains(screenPos)) {
709 				ButtonAreaUnderCursor = ButtonAreaMenu;
710 				ButtonUnderCursor = ButtonUnderNetworkDiplomacy;
711 				CursorOn = CursorOnButton;
712 				return;
713 			}
714 		}
715 	}
716 	for (size_t i = 0; i < UI.UserButtons.size(); ++i) {
717 		const CUIUserButton &button = UI.UserButtons[i];
718 
719 		if (button.Button.X != -1) {
720 			if (button.Button.Contains(screenPos)) {
721 				ButtonAreaUnderCursor = ButtonAreaUser;
722 				ButtonUnderCursor = i;
723 				CursorOn = CursorOnButton;
724 				return;
725 			}
726 		}
727 
728 	}
729 	const size_t buttonCount = UI.ButtonPanel.Buttons.size();
730 	for (unsigned int j = 0; j < buttonCount; ++j) {
731 		if (UI.ButtonPanel.Buttons[j].Contains(screenPos)) {
732 			ButtonAreaUnderCursor = ButtonAreaButton;
733 			if (!CurrentButtons.empty() && CurrentButtons[j].Pos != -1) {
734 				ButtonUnderCursor = j;
735 				CursorOn = CursorOnButton;
736 				return;
737 			}
738 		}
739 	}
740 	if (!Selected.empty()) {
741 		if (Selected.size() == 1 && Selected[0]->Type->CanTransport() && Selected[0]->BoardCount) {
742 			const size_t size = UI.TransportingButtons.size();
743 
744 			for (size_t i = std::min<size_t>(Selected[0]->BoardCount, size); i != 0;) {
745 				--i;
746 				if (UI.TransportingButtons[i].Contains(screenPos)) {
747 					ButtonAreaUnderCursor = ButtonAreaTransporting;
748 					ButtonUnderCursor = i;
749 					CursorOn = CursorOnButton;
750 					return;
751 				}
752 			}
753 		}
754 		if (Selected.size() == 1) {
755 			if (Selected[0]->CurrentAction() == UnitActionTrain) {
756 				if (Selected[0]->Orders.size() == 1) {
757 					if (UI.SingleTrainingButton->Contains(screenPos)) {
758 						ButtonAreaUnderCursor = ButtonAreaTraining;
759 						ButtonUnderCursor = 0;
760 						CursorOn = CursorOnButton;
761 						return;
762 					}
763 				} else {
764 					const size_t size = UI.TrainingButtons.size();
765 
766 					for (size_t i = std::min(Selected[0]->Orders.size(), size); i != 0;) {
767 						--i;
768 						if (Selected[0]->Orders[i]->Action == UnitActionTrain
769 							&& UI.TrainingButtons[i].Contains(screenPos)) {
770 							ButtonAreaUnderCursor = ButtonAreaTraining;
771 							ButtonUnderCursor = i;
772 							CursorOn = CursorOnButton;
773 							return;
774 						}
775 					}
776 				}
777 			} else if (Selected[0]->CurrentAction() == UnitActionUpgradeTo) {
778 				if (UI.UpgradingButton->Contains(screenPos)) {
779 					ButtonAreaUnderCursor = ButtonAreaUpgrading;
780 					ButtonUnderCursor = 0;
781 					CursorOn = CursorOnButton;
782 					return;
783 				}
784 			} else if (Selected[0]->CurrentAction() == UnitActionResearch) {
785 				if (UI.ResearchingButton->Contains(screenPos)) {
786 					ButtonAreaUnderCursor = ButtonAreaResearching;
787 					ButtonUnderCursor = 0;
788 					CursorOn = CursorOnButton;
789 					return;
790 				}
791 			}
792 		}
793 		if (Selected.size() == 1) {
794 			if (UI.SingleSelectedButton && UI.SingleSelectedButton->Contains(screenPos)) {
795 				ButtonAreaUnderCursor = ButtonAreaSelected;
796 				ButtonUnderCursor = 0;
797 				CursorOn = CursorOnButton;
798 				return;
799 			}
800 		} else {
801 			const size_t size = UI.SelectedButtons.size();
802 
803 			for (size_t i = std::min(Selected.size(), size); i != 0;) {
804 				--i;
805 				if (UI.SelectedButtons[i].Contains(screenPos)) {
806 					ButtonAreaUnderCursor = ButtonAreaSelected;
807 					ButtonUnderCursor = i;
808 					CursorOn = CursorOnButton;
809 					return;
810 				}
811 			}
812 		}
813 	}
814 
815 	//  Minimap
816 	if (UI.Minimap.Contains(screenPos)) {
817 		CursorOn = CursorOnMinimap;
818 		return;
819 	}
820 
821 	//  On UI graphic
822 	bool on_ui = false;
823 	const size_t size = UI.Fillers.size();
824 	for (unsigned int j = 0; j < size; ++j) {
825 		if (UI.Fillers[j].OnGraphic(screenPos.x, screenPos.y)) {
826 			on_ui = true;
827 			break;
828 		}
829 	}
830 
831 	//  Map
832 	if (!on_ui && UI.MapArea.Contains(screenPos)) {
833 		CViewport *vp = GetViewport(screenPos);
834 		Assert(vp);
835 		// viewport changed
836 		if (UI.MouseViewport != vp) {
837 			UI.MouseViewport = vp;
838 			DebugPrint("current viewport changed to %ld.\n" _C_
839 					   static_cast<long int>(vp - UI.Viewports));
840 		}
841 
842 		// Note cursor on map can be in scroll area
843 		CursorOn = CursorOnMap;
844 	} else {
845 		CursorOn = CursorOnUnknown;
846 	}
847 
848 	//  Scrolling Region Handling.
849 	HandleMouseScrollArea(screenPos);
850 }
851 
852 /**
853 **  Handle cursor exits the game window (only for some videomodes)
854 **  @todo FIXME: make it so that the game is partially 'paused'.
855 **         Game should run (for network play), but not react on or show
856 **         interactive events.
857 */
HandleMouseExit()858 void HandleMouseExit()
859 {
860 	// Disabled
861 	if (!LeaveStops) {
862 		return;
863 	}
864 	// Denote cursor not on anything in window (used?)
865 	CursorOn = CursorOnUnknown;
866 
867 	// Prevent scrolling while out of focus (on other applications) */
868 	KeyScrollState = MouseScrollState = ScrollNone;
869 
870 	// Show hour-glass (to denote to the user, the game is waiting)
871 	// FIXME: couldn't define a hour-glass that easily, so used pointer
872 	CursorScreenPos.x = Video.Width / 2;
873 	CursorScreenPos.y = Video.Height / 2;
874 	GameCursor = UI.Point.Cursor;
875 }
876 
877 /**
878 **  Restrict mouse cursor to viewport.
879 */
RestrictCursorToViewport()880 void RestrictCursorToViewport()
881 {
882 	UI.SelectedViewport->Restrict(CursorScreenPos.x, CursorScreenPos.y);
883 	UI.MouseWarpPos = CursorStartScreenPos = CursorScreenPos;
884 	CursorOn = CursorOnMap;
885 }
886 
887 /**
888 **  Restrict mouse cursor to minimap
889 */
RestrictCursorToMinimap()890 void RestrictCursorToMinimap()
891 {
892 	clamp(&CursorScreenPos.x, UI.Minimap.X, UI.Minimap.X + UI.Minimap.W - 1);
893 	clamp(&CursorScreenPos.y, UI.Minimap.Y, UI.Minimap.Y + UI.Minimap.H - 1);
894 
895 	UI.MouseWarpPos = CursorStartScreenPos = CursorScreenPos;
896 	CursorOn = CursorOnMinimap;
897 }
898 
899 /**
900 **  Use the mouse to scroll the map
901 **
902 **  @param pos  Screen position.
903 */
MouseScrollMap(const PixelPos & pos)904 static void MouseScrollMap(const PixelPos &pos)
905 {
906 	const int speed = (KeyModifiers & ModifierControl) ? UI.MouseScrollSpeedControl : UI.MouseScrollSpeedDefault;
907 	const PixelDiff diff(pos - CursorStartScreenPos);
908 
909 	UI.MouseViewport->Set(UI.MouseViewport->MapPos, UI.MouseViewport->Offset + speed * diff);
910 	UI.MouseWarpPos = CursorStartScreenPos;
911 }
912 
913 /**
914 **  Handle movement of the cursor.
915 **
916 **  @param cursorPos  Screen X position.
917 */
UIHandleMouseMove(const PixelPos & cursorPos)918 void UIHandleMouseMove(const PixelPos &cursorPos)
919 {
920 	enum _cursor_on_ OldCursorOn;
921 
922 	if (IsDemoMode()) {
923 		// If we are in "demo mode", exit now.
924 		void ActionDraw();
925 		ActionDraw();
926 		return;
927 	}
928 
929 	OldCursorOn = CursorOn;
930 	//  Selecting units.
931 	if (CursorState == CursorStateRectangle) {
932 		// Restrict cursor to viewport.
933 		UI.SelectedViewport->Restrict(CursorScreenPos.x, CursorScreenPos.y);
934 		UI.MouseWarpPos = CursorScreenPos;
935 		return;
936 	}
937 
938 	//  Move map.
939 	if (GameCursor == UI.Scroll.Cursor) {
940 		MouseScrollMap(cursorPos);
941 		return;
942 	}
943 
944 	UnitUnderCursor = NULL;
945 	GameCursor = UI.Point.Cursor;  // Reset
946 	HandleMouseOn(cursorPos);
947 
948 	//  Make the piemenu "follow" the mouse
949 	if (CursorState == CursorStatePieMenu && CursorOn == CursorOnMap) {
950 		clamp(&CursorStartScreenPos.x, CursorScreenPos.x - UI.PieMenu.X[2], CursorScreenPos.x + UI.PieMenu.X[2]);
951 		clamp(&CursorStartScreenPos.y, CursorScreenPos.y - UI.PieMenu.Y[4], CursorScreenPos.y + UI.PieMenu.Y[4]);
952 		return;
953 	}
954 
955 	// Restrict mouse to minimap when dragging
956 	if (OldCursorOn == CursorOnMinimap && CursorOn != CursorOnMinimap && (MouseButtons & LeftButton)) {
957 		const Vec2i cursorPos = UI.Minimap.ScreenToTilePos(CursorScreenPos);
958 
959 		RestrictCursorToMinimap();
960 		UI.SelectedViewport->Center(Map.TilePosToMapPixelPos_Center(cursorPos));
961 		return;
962 	}
963 
964 	//  User may be draging with button pressed.
965 	if (GameMenuButtonClicked || GameDiplomacyButtonClicked) {
966 		return;
967 	} else {
968 		for (size_t i = 0; i < UI.UserButtons.size(); ++i) {
969 			const CUIUserButton &button = UI.UserButtons[i];
970 
971 			if (button.Clicked) {
972 				return;
973 			}
974 		}
975 	}
976 
977 	// This is forbidden for unexplored and not visible space
978 	// FIXME: This must done new, moving units, scrolling...
979 	if (CursorOn == CursorOnMap && UI.MouseViewport->IsInsideMapArea(CursorScreenPos)) {
980 		const CViewport &vp = *UI.MouseViewport;
981 		const Vec2i tilePos = vp.ScreenToTilePos(cursorPos);
982 
983 		try {
984 			if (CursorBuilding && (MouseButtons & LeftButton) && Selected.at(0)
985 				&& (KeyModifiers & (ModifierAlt | ModifierShift))) {
986 				const CUnit &unit = *Selected[0];
987 				const Vec2i tilePos = UI.MouseViewport->ScreenToTilePos(CursorScreenPos);
988 				bool explored = CanBuildOnArea(*Selected[0], tilePos);
989 
990 				// We now need to check if there are another build commands on this build spot
991 				bool buildable = true;
992 				for (std::vector<COrderPtr>::const_iterator it = unit.Orders.begin();
993 					 it != unit.Orders.end(); ++it) {
994 					COrder &order = **it;
995 					if (order.Action == UnitActionBuild) {
996 						COrder_Build &build = dynamic_cast<COrder_Build &>(order);
997 						if (tilePos.x >= build.GetGoalPos().x
998 							&& tilePos.x < build.GetGoalPos().x + build.GetUnitType().TileWidth
999 							&& tilePos.y >= build.GetGoalPos().y
1000 							&& tilePos.y < build.GetGoalPos().y + build.GetUnitType().TileHeight) {
1001 							buildable = false;
1002 							break;
1003 						}
1004 					}
1005 				}
1006 
1007 				// 0 Test build, don't really build
1008 				if (CanBuildUnitType(Selected[0], *CursorBuilding, tilePos, 0) && buildable && (explored || ReplayRevealMap)) {
1009 					const int flush = !(KeyModifiers & ModifierShift);
1010 					for (size_t i = 0; i != Selected.size(); ++i) {
1011 						SendCommandBuildBuilding(*Selected[i], tilePos, *CursorBuilding, flush);
1012 					}
1013 					if (!(KeyModifiers & (ModifierAlt | ModifierShift))) {
1014 						CancelBuildingMode();
1015 					}
1016 				}
1017 			}
1018 		} catch (const std::out_of_range &oor) {
1019 			DebugPrint("Selected is empty: %s\n" _C_ oor.what());
1020 		}
1021 		if (Preference.ShowNameDelay) {
1022 			ShowNameDelay = GameCycle + Preference.ShowNameDelay;
1023 			ShowNameTime = GameCycle + Preference.ShowNameDelay + Preference.ShowNameTime;
1024 		}
1025 
1026 		bool show = ReplayRevealMap ? true : false;
1027 		if (show == false) {
1028 			CMapField &mf = *Map.Field(tilePos);
1029 			for (int i = 0; i < PlayerMax; ++i) {
1030 				if (mf.playerInfo.IsExplored(Players[i])
1031 					&& (i == ThisPlayer->Index || Players[i].HasSharedVisionWith(*ThisPlayer))) {
1032 					show = true;
1033 					break;
1034 				}
1035 			}
1036 		}
1037 
1038 		if (show) {
1039 			const PixelPos mapPixelPos = vp.ScreenToMapPixelPos(cursorPos);
1040 			UnitUnderCursor = UnitOnScreen(mapPixelPos.x, mapPixelPos.y);
1041 		}
1042 	} else if (CursorOn == CursorOnMinimap) {
1043 		const Vec2i tilePos = UI.Minimap.ScreenToTilePos(cursorPos);
1044 
1045 		if (Map.Field(tilePos)->playerInfo.IsExplored(*ThisPlayer) || ReplayRevealMap) {
1046 			UnitUnderCursor = UnitOnMapTile(tilePos, -1);
1047 		}
1048 	}
1049 
1050 	// NOTE: If unit is not selectable as a goal, you can't get a cursor hint
1051 	if (UnitUnderCursor != NULL && !UnitUnderCursor->IsVisibleAsGoal(*ThisPlayer) &&
1052 		!ReplayRevealMap) {
1053 		UnitUnderCursor = NULL;
1054 	}
1055 
1056 	//  Selecting target.
1057 	if (CursorState == CursorStateSelect) {
1058 		if (CursorOn == CursorOnMap || CursorOn == CursorOnMinimap) {
1059 			if (CustomCursor.length() && CursorByIdent(CustomCursor)) {
1060 				GameCursor = CursorByIdent(CustomCursor);
1061 			} else {
1062 				GameCursor = UI.YellowHair.Cursor;
1063 			}
1064 			if (UnitUnderCursor != NULL && !UnitUnderCursor->Type->BoolFlag[DECORATION_INDEX].value) {
1065 				if (UnitUnderCursor->Player == ThisPlayer ||
1066 					ThisPlayer->IsAllied(*UnitUnderCursor)) {
1067 					if (CustomCursor.length() && CursorByIdent(CustomCursor)) {
1068 						GameCursor = CursorByIdent(CustomCursor);
1069 					} else {
1070 						GameCursor = UI.YellowHair.Cursor;
1071 					}
1072 				} else if (UnitUnderCursor->Player->Index != PlayerNumNeutral) {
1073 					if (CustomCursor.length() && CursorByIdent(CustomCursor)) {
1074 						GameCursor = CursorByIdent(CustomCursor);
1075 					} else {
1076 						GameCursor = UI.YellowHair.Cursor;
1077 					}
1078 				}
1079 			}
1080 			if (CursorOn == CursorOnMinimap && (MouseButtons & RightButton)) {
1081 				const Vec2i cursorPos = UI.Minimap.ScreenToTilePos(CursorScreenPos);
1082 				//  Minimap move viewpoint
1083 				UI.SelectedViewport->Center(Map.TilePosToMapPixelPos_Center(cursorPos));
1084 			}
1085 		}
1086 		// FIXME: must move minimap if right button is down !
1087 		return;
1088 	}
1089 
1090 	//  Cursor pointing.
1091 	if (CursorOn == CursorOnMap) {
1092 		//  Map
1093 		if (UnitUnderCursor != NULL && !UnitUnderCursor->Type->BoolFlag[DECORATION_INDEX].value
1094 			&& (UnitUnderCursor->IsVisible(*ThisPlayer) || ReplayRevealMap)) {
1095 			GameCursor = UI.Glass.Cursor;
1096 		}
1097 		return;
1098 	}
1099 
1100 	if (CursorOn == CursorOnMinimap && (MouseButtons & LeftButton)) {
1101 		//  Minimap move viewpoint
1102 		const Vec2i cursorPos = UI.Minimap.ScreenToTilePos(CursorScreenPos);
1103 
1104 		UI.SelectedViewport->Center(Map.TilePosToMapPixelPos_Center(cursorPos));
1105 		CursorStartScreenPos = CursorScreenPos;
1106 		return;
1107 	}
1108 }
1109 
1110 //.............................................................................
1111 
1112 /**
1113 **  Send selected units to repair
1114 **
1115 **  @param tilePos  tile map position.
1116 */
SendRepair(const Vec2i & tilePos)1117 static int SendRepair(const Vec2i &tilePos)
1118 {
1119 	CUnit *dest = UnitUnderCursor;
1120 	int ret = 0;
1121 
1122 	// Check if the dest is repairable!
1123 	if (dest && dest->Variable[HP_INDEX].Value < dest->Variable[HP_INDEX].Max
1124 		&& dest->Type->RepairHP
1125 		&& (dest->Player == ThisPlayer || ThisPlayer->IsAllied(*dest))) {
1126 		for (size_t i = 0; i != Selected.size(); ++i) {
1127 			CUnit *unit = Selected[i];
1128 
1129 			if (unit->Type->RepairRange) {
1130 				const int flush = !(KeyModifiers & ModifierShift);
1131 
1132 				SendCommandRepair(*unit, tilePos, dest, flush);
1133 				ret = 1;
1134 			} else {
1135 				DebugPrint("Non-worker repairs\n");
1136 			}
1137 		}
1138 	}
1139 	return ret;
1140 }
1141 
1142 /**
1143 **  Send selected units to point.
1144 **
1145 **  @param tilePos  tile map position.
1146 **
1147 **  @todo To reduce the CPU load for pathfinder, we should check if
1148 **        the destination is reachable and handle nice group movements.
1149 */
SendMove(const Vec2i & tilePos)1150 static int SendMove(const Vec2i &tilePos)
1151 {
1152 	CUnit *goal = UnitUnderCursor;
1153 	int ret = 0;
1154 	const int flush = !(KeyModifiers & ModifierShift);
1155 
1156 	// Alt makes unit to defend goal
1157 	if (goal && (KeyModifiers & ModifierAlt)) {
1158 		for (size_t i = 0; i != Selected.size(); ++i) {
1159 			CUnit *unit = Selected[i];
1160 
1161 			goal->Blink = 4;
1162 			SendCommandDefend(*unit, *goal, flush);
1163 			ret = 1;
1164 		}
1165 	} else {
1166 		// Move to a transporter.
1167 		if (goal && goal->Type->CanTransport()) {
1168 			size_t i;
1169 			for (i = 0; i != Selected.size(); ++i) {
1170 				if (CanTransport(*goal, *Selected[i])) {
1171 					SendCommandStopUnit(*goal);
1172 					ret = 1;
1173 					break;
1174 				}
1175 			}
1176 			if (i == Selected.size()) {
1177 				goal = NULL;
1178 			}
1179 		} else {
1180 			goal = NULL;
1181 		}
1182 
1183 		for (size_t i = 0; i != Selected.size(); ++i) {
1184 			CUnit *unit = Selected[i];
1185 
1186 			if (goal && CanTransport(*goal, *unit)) {
1187 				goal->Blink = 4;
1188 				SendCommandFollow(*goal, *unit, 0);
1189 				SendCommandBoard(*unit, *goal, flush);
1190 				ret = 1;
1191 			} else {
1192 				SendCommandMove(*unit, tilePos, flush);
1193 				ret = 1;
1194 			}
1195 		}
1196 	}
1197 	return ret;
1198 }
1199 
1200 /**
1201 **  Send the current selected group attacking.
1202 **
1203 **  To empty field:
1204 **    Move to this field attacking all enemy units in reaction range.
1205 **
1206 **  To unit:
1207 **    Move to unit attacking and tracing the unit until dead.
1208 **
1209 **  @param tilePos  tile map position.
1210 **
1211 **  @return 1 if any unit have a new order, 0 else.
1212 **
1213 **  @see Selected
1214 */
SendAttack(const Vec2i & tilePos)1215 static int SendAttack(const Vec2i &tilePos)
1216 {
1217 	const int flush = !(KeyModifiers & ModifierShift);
1218 	CUnit *dest = UnitUnderCursor;
1219 	int ret = 0;
1220 
1221 	if (dest && dest->Type->BoolFlag[DECORATION_INDEX].value) {
1222 		dest = NULL;
1223 	}
1224 	for (size_t i = 0; i != Selected.size(); ++i) {
1225 		CUnit &unit = *Selected[i];
1226 
1227 		if (unit.Type->CanAttack) {
1228 			if (!dest || (dest != &unit && CanTarget(*unit.Type, *dest->Type))) {
1229 				if (dest) {
1230 					dest->Blink = 4;
1231 				}
1232 				SendCommandAttack(unit, tilePos, dest, flush);
1233 				ret = 1;
1234 			}
1235 		} else {
1236 			if (unit.CanMove()) {
1237 				SendCommandMove(unit, tilePos, flush);
1238 				ret = 1;
1239 			}
1240 		}
1241 	}
1242 	return ret;
1243 }
1244 
1245 /**
1246 **  Send the current selected group ground attacking.
1247 **
1248 **  @param tilePos  tile map position.
1249 */
SendAttackGround(const Vec2i & tilePos)1250 static int SendAttackGround(const Vec2i &tilePos)
1251 {
1252 	const int flush = !(KeyModifiers & ModifierShift);
1253 	int ret = 0;
1254 
1255 	for (size_t i = 0; i != Selected.size(); ++i) {
1256 		CUnit &unit = *Selected[i];
1257 		if (unit.Type->CanAttack) {
1258 			SendCommandAttackGround(unit, tilePos, flush);
1259 			ret = 1;
1260 		} else {
1261 			SendCommandMove(unit, tilePos, flush);
1262 			ret = 1;
1263 		}
1264 	}
1265 	return ret;
1266 }
1267 
1268 /**
1269 **  Let units patrol between current position and the selected.
1270 **
1271 **  @param tilePos  tile map position.
1272 */
SendPatrol(const Vec2i & tilePos)1273 static int SendPatrol(const Vec2i &tilePos)
1274 {
1275 	const int flush = !(KeyModifiers & ModifierShift);
1276 
1277 	for (size_t i = 0; i != Selected.size(); ++i) {
1278 		CUnit &unit = *Selected[i];
1279 		SendCommandPatrol(unit, tilePos, flush);
1280 	}
1281 	return Selected.empty() ? 0 : 1;
1282 }
1283 
1284 /**
1285 **  Let units harvest wood/mine gold/haul oil
1286 **
1287 **  @param pos  tile map position
1288 **
1289 **  @see Selected
1290 */
SendResource(const Vec2i & pos)1291 static int SendResource(const Vec2i &pos)
1292 {
1293 	int res;
1294 	CUnit *dest = UnitUnderCursor;
1295 	int ret = 0;
1296 	const int flush = !(KeyModifiers & ModifierShift);
1297 	const CMapField &mf = *Map.Field(pos);
1298 
1299 	for (size_t i = 0; i != Selected.size(); ++i) {
1300 		CUnit &unit = *Selected[i];
1301 
1302 		if (unit.Type->BoolFlag[HARVESTER_INDEX].value) {
1303 			if (dest
1304 				&& (res = dest->Type->GivesResource) != 0
1305 				&& unit.Type->ResInfo[res]
1306 				&& unit.ResourcesHeld < unit.Type->ResInfo[res]->ResourceCapacity
1307 				&& dest->Type->BoolFlag[CANHARVEST_INDEX].value
1308 				&& (dest->Player == unit.Player || dest->Player->Index == PlayerMax - 1)) {
1309 				dest->Blink = 4;
1310 				SendCommandResource(unit, *dest, flush);
1311 				ret = 1;
1312 				continue;
1313 			} else {
1314 				for (res = 0; res < MaxCosts; ++res) {
1315 					if (unit.Type->ResInfo[res]
1316 						&& unit.Type->ResInfo[res]->TerrainHarvester
1317 						&& mf.playerInfo.IsExplored(*unit.Player)
1318 						/// By disabling this, we allow the harvester to find the nearest tile with a resource by itself, in case mf is empty.
1319 						/*&& mf.IsTerrainResourceOnMap(res)*/
1320 						&& unit.ResourcesHeld < unit.Type->ResInfo[res]->ResourceCapacity
1321 						&& (unit.CurrentResource != res || unit.ResourcesHeld < unit.Type->ResInfo[res]->ResourceCapacity)) {
1322 						SendCommandResourceLoc(unit, pos, flush);
1323 						ret = 1;
1324 						break;
1325 					}
1326 				}
1327 				if (res != MaxCosts) {
1328 					continue;
1329 				}
1330 			}
1331 		}
1332 		if (!unit.CanMove()) {
1333 			if (dest && dest->Type->GivesResource && dest->Type->BoolFlag[CANHARVEST_INDEX].value) {
1334 				dest->Blink = 4;
1335 				SendCommandResource(unit, *dest, flush);
1336 				ret = 1;
1337 				continue;
1338 			}
1339 			if (mf.playerInfo.IsExplored(*unit.Player) && mf.IsTerrainResourceOnMap()) {
1340 				SendCommandResourceLoc(unit, pos, flush);
1341 				ret = 1;
1342 				continue;
1343 			}
1344 			SendCommandMove(unit, pos, flush);
1345 			ret = 1;
1346 			continue;
1347 		}
1348 	}
1349 	return ret;
1350 }
1351 
1352 /**
1353 **  Send selected units to unload passengers.
1354 **
1355 **  @param tilePos  tile map position.
1356 */
SendUnload(const Vec2i & tilePos)1357 static int SendUnload(const Vec2i &tilePos)
1358 {
1359 	const int flush = !(KeyModifiers & ModifierShift);
1360 
1361 	for (size_t i = 0; i != Selected.size(); ++i) {
1362 		// FIXME: not only transporter selected?
1363 		SendCommandUnload(*Selected[i], tilePos, NoUnitP, flush);
1364 	}
1365 	return Selected.empty() ? 0 : 1;
1366 }
1367 
1368 /**
1369 **  Send the current selected group for spell cast.
1370 **
1371 **  To empty field:
1372 **  To unit:
1373 **    Spell cast on unit or on map spot.
1374 **
1375 **  @param tilePos  tile map position.
1376 **
1377 **  @see Selected
1378 */
SendSpellCast(const Vec2i & tilePos)1379 static int SendSpellCast(const Vec2i &tilePos)
1380 {
1381 	const int flush = !(KeyModifiers & ModifierShift);
1382 	CUnit *dest = UnitUnderCursor;
1383 	int ret = 0;
1384 
1385 	/* NOTE: Vladi:
1386 	   This is a high-level function, it sends target spot and unit
1387 	   (if exists). All checks are performed at spell cast handle
1388 	   function which will cancel function if cannot be executed
1389 	 */
1390 	for (size_t i = 0; i != Selected.size(); ++i) {
1391 		CUnit &unit = *Selected[i];
1392 		if (!unit.Type->CanCastSpell) {
1393 			DebugPrint("but unit %d(%s) can't cast spells?\n" _C_
1394 					   UnitNumber(unit) _C_ unit.Type->Name.c_str());
1395 			// this unit cannot cast spell
1396 			continue;
1397 		}
1398 		// CursorValue here holds the spell type id
1399 		const SpellType *spell = SpellTypeTable[CursorValue];
1400 		if (!spell) {
1401 			fprintf(stderr, "unknown spell-id: %d\n", CursorValue);
1402 			ExitFatal(1);
1403 		}
1404 		if (dest && dest == &unit && (!spell->Condition || spell->Condition->TargetSelf == CONDITION_FALSE)) {
1405 			// Only spells with explicit 'self: true' allows self targetting
1406 			continue;
1407 		}
1408 		SendCommandSpellCast(unit, tilePos, spell->Target == TargetPosition ? NULL : dest , CursorValue, flush);
1409 		ret = 1;
1410 	}
1411 	return ret;
1412 }
1413 
1414 /**
1415 **  Send a command to selected units.
1416 **
1417 **  @param tilePos  tile map position.
1418 */
SendCommand(const Vec2i & tilePos)1419 static void SendCommand(const Vec2i &tilePos)
1420 {
1421 	int ret = 0;
1422 
1423 	CurrentButtonLevel = 0;
1424 	UI.ButtonPanel.Update();
1425 	switch (CursorAction) {
1426 		case ButtonMove:
1427 			ret = SendMove(tilePos);
1428 			break;
1429 		case ButtonRepair:
1430 			ret = SendRepair(tilePos);
1431 			break;
1432 		case ButtonAttack:
1433 			ret = SendAttack(tilePos);
1434 			break;
1435 		case ButtonAttackGround:
1436 			ret = SendAttackGround(tilePos);
1437 			break;
1438 		case ButtonPatrol:
1439 			ret = SendPatrol(tilePos);
1440 			break;
1441 		case ButtonHarvest:
1442 			ret = SendResource(tilePos);
1443 			break;
1444 		case ButtonUnload:
1445 			ret = SendUnload(tilePos);
1446 			break;
1447 		case ButtonSpellCast:
1448 			ret = SendSpellCast(tilePos);
1449 			break;
1450 		default:
1451 			DebugPrint("Unsupported send action %d\n" _C_ CursorAction);
1452 			break;
1453 	}
1454 	if (ret) {
1455 		// Acknowledge the command with first selected unit.
1456 		for (size_t i = 0; i != Selected.size(); ++i) {
1457 			if (CursorAction == ButtonAttack || CursorAction == ButtonAttackGround || CursorAction == ButtonSpellCast) {
1458 				if (Selected[i]->Type->MapSound.Attack.Sound) {
1459 					PlayUnitSound(*Selected[i], VoiceAttack);
1460 					break;
1461 				} else if (Selected[i]->Type->MapSound.Acknowledgement.Sound) {
1462 					PlayUnitSound(*Selected[i], VoiceAcknowledging);
1463 					break;
1464 				}
1465 			} else if (CursorAction == ButtonRepair && Selected[i]->Type->MapSound.Repair.Sound) {
1466 				PlayUnitSound(*Selected[i], VoiceRepairing);
1467 				break;
1468 			} else if (CursorAction == ButtonBuild && Selected[i]->Type->MapSound.Build.Sound) {
1469 				PlayUnitSound(*Selected[i], VoiceBuild);
1470 				break;
1471 			} else if (Selected[i]->Type->MapSound.Acknowledgement.Sound) {
1472 				PlayUnitSound(*Selected[i], VoiceAcknowledging);
1473 				break;
1474 			}
1475 		}
1476 		ShowOrdersCount = GameCycle + Preference.ShowOrders * CYCLES_PER_SECOND;
1477 	}
1478 }
1479 
1480 //.............................................................................
1481 
1482 /**
1483 **  Mouse button press on selection/group area.
1484 **
1485 **  @param num     Button number.
1486 **  @param button  Mouse Button pressed.
1487 */
DoSelectionButtons(int num,unsigned)1488 static void DoSelectionButtons(int num, unsigned)
1489 {
1490 	if (GameObserve || GamePaused || GameEstablishing) {
1491 		return;
1492 	}
1493 
1494 	if (static_cast<size_t>(num) >= Selected.size() || !(MouseButtons & LeftButton)) {
1495 		return;
1496 	}
1497 
1498 	CUnit &unit = *Selected[num];
1499 
1500 	if ((KeyModifiers & ModifierControl) || (MouseButtons & (LeftButton << MouseDoubleShift))) {
1501 		if (KeyModifiers & ModifierShift) {
1502 			ToggleUnitsByType(unit);
1503 		} else {
1504 			SelectUnitsByType(unit);
1505 		}
1506 	} else if (KeyModifiers & ModifierAlt) {
1507 		if (KeyModifiers & ModifierShift) {
1508 			AddGroupFromUnitToSelection(unit);
1509 		} else {
1510 			SelectGroupFromUnit(unit);
1511 		}
1512 	} else if (KeyModifiers & ModifierShift) {
1513 		ToggleSelectUnit(unit);
1514 	} else {
1515 		SelectSingleUnit(unit);
1516 	}
1517 
1518 	UI.StatusLine.Clear();
1519 	UI.StatusLine.ClearCosts();
1520 	CurrentButtonLevel = 0;
1521 	SelectionChanged();
1522 }
1523 
1524 //.............................................................................
1525 
1526 /**
1527 **  Handle mouse button pressed in select state.
1528 **
1529 **  Select state is used for target of patrol, attack, move, ....
1530 **
1531 **  @param button  Button pressed down.
1532 */
UISelectStateButtonDown(unsigned)1533 static void UISelectStateButtonDown(unsigned)
1534 {
1535 	if (GameObserve || GamePaused || GameEstablishing) {
1536 		return;
1537 	}
1538 
1539 	//
1540 	//  Clicking on the map.
1541 	//
1542 	if (CursorOn == CursorOnMap && UI.MouseViewport->IsInsideMapArea(CursorScreenPos)) {
1543 		UI.StatusLine.Clear();
1544 		UI.StatusLine.ClearCosts();
1545 		CursorState = CursorStatePoint;
1546 		GameCursor = UI.Point.Cursor;
1547 		CustomCursor.clear();
1548 		CurrentButtonLevel = 0;
1549 		UI.ButtonPanel.Update();
1550 
1551 		if (MouseButtons & LeftButton) {
1552 			const CViewport &vp = *UI.MouseViewport;
1553 			const PixelPos mapPixelPos = vp.ScreenToMapPixelPos(CursorScreenPos);
1554 
1555 			if (!ClickMissile.empty()) {
1556 				MakeLocalMissile(*MissileTypeByIdent(ClickMissile), mapPixelPos, mapPixelPos);
1557 			}
1558 			SendCommand(Map.MapPixelPosToTilePos(mapPixelPos));
1559 		}
1560 		return;
1561 	}
1562 
1563 	//
1564 	//  Clicking on the minimap.
1565 	//
1566 	if (CursorOn == CursorOnMinimap) {
1567 		const Vec2i cursorTilePos = UI.Minimap.ScreenToTilePos(CursorScreenPos);
1568 
1569 		if (MouseButtons & LeftButton) {
1570 			const PixelPos mapPixelPos = Map.TilePosToMapPixelPos_Center(cursorTilePos);
1571 
1572 			UI.StatusLine.Clear();
1573 			UI.StatusLine.ClearCosts();
1574 			CursorState = CursorStatePoint;
1575 			GameCursor = UI.Point.Cursor;
1576 			CustomCursor.clear();
1577 			CurrentButtonLevel = 0;
1578 			UI.ButtonPanel.Update();
1579 			if (!ClickMissile.empty()) {
1580 				MakeLocalMissile(*MissileTypeByIdent(ClickMissile), mapPixelPos, mapPixelPos);
1581 			}
1582 			SendCommand(cursorTilePos);
1583 		} else {
1584 			UI.SelectedViewport->Center(Map.TilePosToMapPixelPos_Center(cursorTilePos));
1585 		}
1586 		return;
1587 	}
1588 
1589 	if (CursorOn == CursorOnButton) {
1590 		// FIXME: other buttons?
1591 		// 74145: Spell-cast on unit portrait
1592 		if (Selected.size() > 1 && ButtonAreaUnderCursor == ButtonAreaSelected
1593 			&& CursorAction == ButtonSpellCast) {
1594 			if (GameObserve || GamePaused || GameEstablishing) {
1595 				return;
1596 			}
1597 			int num = ButtonUnderCursor;
1598 
1599 			if (static_cast<size_t>(num) >= Selected.size() || !(MouseButtons & LeftButton)) {
1600 				return;
1601 			}
1602 
1603 			CUnit &unit = *Selected[num];
1604 
1605 			const Vec2i tilePos = unit.tilePos;
1606 			UnitUnderCursor = &unit;
1607 			SendSpellCast(tilePos);
1608 			UnitUnderCursor = NULL;
1609 		}
1610 		if (ButtonAreaUnderCursor == ButtonAreaButton) {
1611 			OldButtonUnderCursor = ButtonUnderCursor;
1612 			return;
1613 		}
1614 	}
1615 
1616 	UI.StatusLine.Clear();
1617 	UI.StatusLine.ClearCosts();
1618 	CursorState = CursorStatePoint;
1619 	if (CustomCursor.length() && CursorByIdent(CustomCursor)) {
1620 		GameCursor = CursorByIdent(CustomCursor);
1621 	} else {
1622 		GameCursor = UI.YellowHair.Cursor;
1623 	}
1624 	CurrentButtonLevel = 0;
1625 	UI.ButtonPanel.Update();
1626 }
1627 
1628 
UIHandleButtonDown_OnMap(unsigned button)1629 static void UIHandleButtonDown_OnMap(unsigned button)
1630 {
1631 	Assert(UI.MouseViewport);
1632 	if ((MouseButtons & LeftButton) && UI.SelectedViewport != UI.MouseViewport) {
1633 		UI.SelectedViewport = UI.MouseViewport;
1634 		DebugPrint("selected viewport changed to %ld.\n" _C_
1635 				   static_cast<long int>(UI.SelectedViewport - UI.Viewports));
1636 	}
1637 
1638 	// to redraw the cursor immediately (and avoid up to 1 sec delay
1639 	if (CursorBuilding) {
1640 		// Possible Selected[0] was removed from map
1641 		// need to make sure there is a unit to build
1642 		if (Selected[0] && (MouseButtons & LeftButton)
1643 			&& UI.MouseViewport->IsInsideMapArea(CursorScreenPos)) {// enter select mode
1644 			const Vec2i tilePos = UI.MouseViewport->ScreenToTilePos(CursorScreenPos);
1645 			bool explored = CanBuildOnArea(*Selected[0], tilePos);
1646 
1647 			// 0 Test build, don't really build
1648 			if (CanBuildUnitType(Selected[0], *CursorBuilding, tilePos, 0) && (explored || ReplayRevealMap)) {
1649 				const int flush = !(KeyModifiers & ModifierShift);
1650 				PlayGameSound(GameSounds.PlacementSuccess[ThisPlayer->Race].Sound, MaxSampleVolume);
1651 				PlayUnitSound(*Selected[0], VoiceBuild);
1652 				for (size_t i = 0; i != Selected.size(); ++i) {
1653 					SendCommandBuildBuilding(*Selected[i], tilePos, *CursorBuilding, flush);
1654 				}
1655 				if (!(KeyModifiers & (ModifierAlt | ModifierShift))) {
1656 					CancelBuildingMode();
1657 				}
1658 			} else {
1659 				PlayGameSound(GameSounds.PlacementError[ThisPlayer->Race].Sound, MaxSampleVolume);
1660 			}
1661 		} else {
1662 			CancelBuildingMode();
1663 		}
1664 		return;
1665 	}
1666 
1667 	if (MouseButtons & UI.PieMenu.MouseButton) { // enter pie menu
1668 		UnitUnderCursor = NULL;
1669 		GameCursor = UI.Point.Cursor;  // Reset
1670 		CursorStartScreenPos = CursorScreenPos;
1671 		if (!Selected.empty() && Selected[0]->Player == ThisPlayer && CursorState == CursorStatePoint) {
1672 			CursorState = CursorStatePieMenu;
1673 		}
1674 	} else if (MouseButtons & RightButton) {
1675 		if (!GameObserve && !GamePaused && !GameEstablishing && UI.MouseViewport->IsInsideMapArea(CursorScreenPos)) {
1676 			CUnit *unit;
1677 			// FIXME: Rethink the complete chaos of coordinates here
1678 			// FIXME: Johns: Perhaps we should use a pixel map coordinates
1679 			const Vec2i tilePos = UI.MouseViewport->ScreenToTilePos(CursorScreenPos);
1680 
1681 			if (UnitUnderCursor != NULL && (unit = UnitOnMapTile(tilePos, -1))
1682 				&& !UnitUnderCursor->Type->BoolFlag[DECORATION_INDEX].value) {
1683 				unit->Blink = 4;                // if right click on building -- blink
1684 			} else { // if not not click on building -- green cross
1685 				if (!ClickMissile.empty()) {
1686 					const PixelPos mapPixelPos = UI.MouseViewport->ScreenToMapPixelPos(CursorScreenPos);
1687 
1688 					MakeLocalMissile(*MissileTypeByIdent(ClickMissile), mapPixelPos, mapPixelPos);
1689 				}
1690 			}
1691 			const PixelPos mapPixelPos = UI.MouseViewport->ScreenToMapPixelPos(CursorScreenPos);
1692 			DoRightButton(mapPixelPos);
1693 		}
1694 	} else if (MouseButtons & LeftButton) { // enter select mode
1695 		CursorStartScreenPos = CursorScreenPos;
1696 		CursorStartMapPos = UI.MouseViewport->ScreenToMapPixelPos(CursorScreenPos);
1697 		GameCursor = UI.Cross.Cursor;
1698 		CursorState = CursorStateRectangle;
1699 	} else if (MouseButtons & MiddleButton) {// enter move map mode
1700 		CursorStartScreenPos = CursorScreenPos;
1701 		GameCursor = UI.Scroll.Cursor;
1702 	}
1703 }
1704 
UIHandleButtonDown_OnMinimap(unsigned button)1705 static void UIHandleButtonDown_OnMinimap(unsigned button)
1706 {
1707 	const Vec2i cursorTilePos = UI.Minimap.ScreenToTilePos(CursorScreenPos);
1708 
1709 	if (MouseButtons & LeftButton) { // enter move mini-mode
1710 		UI.SelectedViewport->Center(Map.TilePosToMapPixelPos_Center(cursorTilePos));
1711 	} else if (MouseButtons & RightButton) {
1712 		if (!GameObserve && !GamePaused && !GameEstablishing) {
1713 			const PixelPos mapPixelPos = Map.TilePosToMapPixelPos_Center(cursorTilePos);
1714 			if (!ClickMissile.empty()) {
1715 				MakeLocalMissile(*MissileTypeByIdent(ClickMissile), mapPixelPos, mapPixelPos);
1716 			}
1717 			DoRightButton(mapPixelPos);
1718 		}
1719 	}
1720 }
1721 
UIHandleButtonDown_OnButton(unsigned button)1722 static void UIHandleButtonDown_OnButton(unsigned button)
1723 {
1724 	// clicked on info panel - selection shown
1725 	if (Selected.size() > 1 && ButtonAreaUnderCursor == ButtonAreaSelected) {
1726 		PlayGameSound(GameSounds.Click.Sound, MaxSampleVolume);
1727 		DoSelectionButtons(ButtonUnderCursor, button);
1728 	} else if ((MouseButtons & LeftButton)) {
1729 		//  clicked on menu button
1730 		if (ButtonAreaUnderCursor == ButtonAreaMenu) {
1731 			if ((ButtonUnderCursor == ButtonUnderMenu || ButtonUnderCursor == ButtonUnderNetworkMenu)
1732 				&& !GameMenuButtonClicked) {
1733 				PlayGameSound(GameSounds.Click.Sound, MaxSampleVolume);
1734 				GameMenuButtonClicked = true;
1735 			} else if (ButtonUnderCursor == ButtonUnderNetworkDiplomacy && !GameDiplomacyButtonClicked) {
1736 				PlayGameSound(GameSounds.Click.Sound, MaxSampleVolume);
1737 				GameDiplomacyButtonClicked = true;
1738 			}
1739 			//  clicked on user buttons
1740 		} else if (ButtonAreaUnderCursor == ButtonAreaUser) {
1741 			for (size_t i = 0; i < UI.UserButtons.size(); ++i) {
1742 				CUIUserButton &button = UI.UserButtons[i];
1743 
1744 				if (i == size_t(ButtonUnderCursor) && !button.Clicked) {
1745 					PlayGameSound(GameSounds.Click.Sound, MaxSampleVolume);
1746 					button.Clicked = true;
1747 				}
1748 			}
1749 			//  clicked on selected button
1750 		} else if (ButtonAreaUnderCursor == ButtonAreaSelected) {
1751 			//  clicked on single unit shown
1752 			if (ButtonUnderCursor == 0 && Selected.size() == 1) {
1753 				PlayGameSound(GameSounds.Click.Sound, MaxSampleVolume);
1754 				UI.SelectedViewport->Center(Selected[0]->GetMapPixelPosCenter());
1755 			}
1756 			//  clicked on training button
1757 		} else if (ButtonAreaUnderCursor == ButtonAreaTraining) {
1758 			if (!GameObserve && !GamePaused && !GameEstablishing && ThisPlayer->IsTeamed(*Selected[0])) {
1759 				if (static_cast<size_t>(ButtonUnderCursor) < Selected[0]->Orders.size()
1760 					&& Selected[0]->Orders[ButtonUnderCursor]->Action == UnitActionTrain) {
1761 					const COrder_Train &order = *static_cast<COrder_Train *>(Selected[0]->Orders[ButtonUnderCursor]);
1762 
1763 					DebugPrint("Cancel slot %d %s\n" _C_ ButtonUnderCursor _C_ order.GetUnitType().Ident.c_str());
1764 					PlayGameSound(GameSounds.Click.Sound, MaxSampleVolume);
1765 					SendCommandCancelTraining(*Selected[0], ButtonUnderCursor, &order.GetUnitType());
1766 				}
1767 			}
1768 			//  clicked on upgrading button
1769 		} else if (ButtonAreaUnderCursor == ButtonAreaUpgrading) {
1770 			if (!GameObserve && !GamePaused && !GameEstablishing && ThisPlayer->IsTeamed(*Selected[0])) {
1771 				if (ButtonUnderCursor == 0 && Selected.size() == 1) {
1772 					DebugPrint("Cancel upgrade %s\n" _C_ Selected[0]->Type->Ident.c_str());
1773 					PlayGameSound(GameSounds.Click.Sound, MaxSampleVolume);
1774 					SendCommandCancelUpgradeTo(*Selected[0]);
1775 				}
1776 			}
1777 			//  clicked on researching button
1778 		} else if (ButtonAreaUnderCursor == ButtonAreaResearching) {
1779 			if (!GameObserve && !GamePaused && !GameEstablishing && ThisPlayer->IsTeamed(*Selected[0])) {
1780 				if (ButtonUnderCursor == 0 && Selected.size() == 1) {
1781 					DebugPrint("Cancel research %s\n" _C_ Selected[0]->Type->Ident.c_str());
1782 					PlayGameSound(GameSounds.Click.Sound, MaxSampleVolume);
1783 					SendCommandCancelResearch(*Selected[0]);
1784 				}
1785 			}
1786 			//  clicked on button panel
1787 		} else if (ButtonAreaUnderCursor == ButtonAreaTransporting) {
1788 			//  for transporter
1789 			if (!GameObserve && !GamePaused && !GameEstablishing && ThisPlayer->IsTeamed(*Selected[0])) {
1790 				if (Selected[0]->BoardCount >= ButtonUnderCursor) {
1791 					CUnit *uins = Selected[0]->UnitInside;
1792 					size_t j = 0;
1793 
1794 					for (int i = 0; i < Selected[0]->InsideCount; ++i, uins = uins->NextContained) {
1795 						if (!uins->Boarded || j >= UI.TransportingButtons.size() || (Selected[0]->Player != ThisPlayer && uins->Player != ThisPlayer)) {
1796 							continue;
1797 						}
1798 					if (ButtonAreaUnderCursor == ButtonAreaTransporting
1799 						&& static_cast<size_t>(ButtonUnderCursor) == j) {
1800 							Assert(uins->Boarded);
1801 							const int flush = !(KeyModifiers & ModifierShift);
1802 							if (ThisPlayer->IsTeamed(*Selected[0]) || uins->Player == ThisPlayer) {
1803 								PlayGameSound(GameSounds.Click.Sound, MaxSampleVolume);
1804 								SendCommandUnload(*Selected[0], Selected[0]->tilePos, uins, flush);
1805 							}
1806 						}
1807 						++j;
1808 					}
1809 				}
1810 			}
1811 		} else if (ButtonAreaUnderCursor == ButtonAreaButton) {
1812 			if (!GameObserve && !GamePaused && !GameEstablishing && ThisPlayer->IsTeamed(*Selected[0])) {
1813 				PlayGameSound(GameSounds.Click.Sound, MaxSampleVolume);
1814 				OldButtonUnderCursor = ButtonUnderCursor;
1815 			}
1816 		}
1817 	} else if ((MouseButtons & MiddleButton)) {
1818 		//  clicked on info panel - single unit shown
1819 		if (ButtonAreaUnderCursor == ButtonAreaSelected && ButtonUnderCursor == 0 && Selected.size() == 1) {
1820 			PlayGameSound(GameSounds.Click.Sound, MaxSampleVolume);
1821 			if (UI.SelectedViewport->Unit == Selected[0]) {
1822 				UI.SelectedViewport->Unit = NULL;
1823 			} else {
1824 				UI.SelectedViewport->Unit = Selected[0];
1825 			}
1826 		}
1827 	} else if ((MouseButtons & RightButton)) {
1828 	}
1829 }
1830 
1831 /**
1832 **  Called if mouse button pressed down.
1833 **
1834 **  @param button  Button pressed down.
1835 */
UIHandleButtonDown(unsigned button)1836 void UIHandleButtonDown(unsigned button)
1837 {
1838 	// Detect long left selection click
1839 	const bool longLeftButton = (MouseButtons & ((LeftButton << MouseHoldShift))) != 0;
1840 
1841 	if (IsDemoMode()) {
1842 		// If we are in "demo mode", exit no matter what we click on.
1843 		void ActionDraw();
1844 		ActionDraw();
1845 		return;
1846 	}
1847 
1848 	static bool OldShowSightRange;
1849 	static bool OldShowReactionRange;
1850 	static bool OldShowAttackRange;
1851 	static bool OldValid = false;
1852 	OldButtonUnderCursor = -1;
1853 
1854 	// Reset the ShowNameDelay counters
1855 	ShowNameDelay = ShowNameTime = GameCycle;
1856 
1857 	if (longLeftButton) {
1858 		if (!OldValid) {
1859 			OldShowSightRange = Preference.ShowSightRange;
1860 			OldShowAttackRange = Preference.ShowAttackRange;
1861 			OldShowReactionRange = Preference.ShowReactionRange;
1862 			OldValid = true;
1863 
1864 			Preference.ShowSightRange = true;
1865 			Preference.ShowAttackRange = true;
1866 			Preference.ShowReactionRange = true;
1867 		}
1868 	} else if (OldValid) {
1869 		Preference.ShowSightRange = OldShowSightRange;
1870 		Preference.ShowAttackRange = OldShowAttackRange;
1871 		Preference.ShowReactionRange = OldShowReactionRange;
1872 		OldValid = false;
1873 	}
1874 
1875 	// select mode
1876 	if (CursorState == CursorStateRectangle) {
1877 		return;
1878 	}
1879 	// CursorOn should have changed with BigMapMode, so recompute it.
1880 	HandleMouseOn(CursorScreenPos);
1881 	//  Selecting target. (Move,Attack,Patrol,... commands);
1882 	if (CursorState == CursorStateSelect) {
1883 		UISelectStateButtonDown(button);
1884 		return;
1885 	}
1886 
1887 	if (CursorState == CursorStatePieMenu) {
1888 		if (CursorOn == CursorOnMap) {
1889 			HandlePieMenuMouseSelection();
1890 			return;
1891 		} else {
1892 			// Pie Menu canceled
1893 			CursorState = CursorStatePoint;
1894 			// Don't return, we might be over another button
1895 		}
1896 	}
1897 
1898 	//  Cursor is on the map area
1899 	if (CursorOn == CursorOnMap) {
1900 		UIHandleButtonDown_OnMap(button);
1901 	} else if (CursorOn == CursorOnMinimap) {
1902 		//  Cursor is on the minimap area
1903 		UIHandleButtonDown_OnMinimap(button);
1904 	} else if (CursorOn == CursorOnButton) {
1905 		//  Cursor is on the buttons: group or command
1906 		UIHandleButtonDown_OnButton(button);
1907 	}
1908 }
1909 
1910 /**
1911 **  Called if mouse button released.
1912 **
1913 **  @param button  Button released.
1914 */
UIHandleButtonUp(unsigned button)1915 void UIHandleButtonUp(unsigned button)
1916 {
1917 	//
1918 	//  Move map.
1919 	//
1920 	if (GameCursor == UI.Scroll.Cursor) {
1921 		GameCursor = UI.Point.Cursor;
1922 		return;
1923 	}
1924 
1925 	//
1926 	//  Pie Menu
1927 	//
1928 	if (CursorState == CursorStatePieMenu) {
1929 		// Little threshold
1930 		if (1 < abs(CursorStartScreenPos.x - CursorScreenPos.x)
1931 			|| 1 < abs(CursorStartScreenPos.y - CursorScreenPos.y)) {
1932 			// there was a move, handle the selected button/pie
1933 			HandlePieMenuMouseSelection();
1934 		}
1935 	}
1936 
1937 	if ((1 << button) == LeftButton) {
1938 		//
1939 		//  Menu (F10) button
1940 		//
1941 		if (GameMenuButtonClicked) {
1942 			GameMenuButtonClicked = false;
1943 			if (ButtonAreaUnderCursor == ButtonAreaMenu) {
1944 				if (ButtonUnderCursor == ButtonUnderMenu || ButtonUnderCursor == ButtonUnderNetworkMenu) {
1945 					// FIXME: Not if, in input mode.
1946 					if (!IsNetworkGame()) {
1947 						GamePaused = true;
1948 						UI.StatusLine.Set(_("Game Paused"));
1949 					}
1950 					if (ButtonUnderCursor == ButtonUnderMenu) {
1951 						if (UI.MenuButton.Callback) {
1952 							UI.MenuButton.Callback->action("");
1953 						}
1954 					} else {
1955 						if (UI.NetworkMenuButton.Callback) {
1956 							UI.NetworkMenuButton.Callback->action("");
1957 						}
1958 					}
1959 					return;
1960 				}
1961 			}
1962 		}
1963 
1964 		//
1965 		//  Diplomacy button
1966 		//
1967 		if (GameDiplomacyButtonClicked) {
1968 			GameDiplomacyButtonClicked = false;
1969 			if (ButtonAreaUnderCursor == ButtonAreaMenu && ButtonUnderCursor == ButtonUnderNetworkDiplomacy) {
1970 				if (UI.NetworkDiplomacyButton.Callback) {
1971 					UI.NetworkDiplomacyButton.Callback->action("");
1972 				}
1973 				return;
1974 			}
1975 		}
1976 
1977 		//
1978 		//  User buttons
1979 		//
1980 		for (size_t i = 0; i < UI.UserButtons.size(); ++i) {
1981 			CUIUserButton &button = UI.UserButtons[i];
1982 
1983 			if (button.Clicked) {
1984 				button.Clicked = false;
1985 				if (ButtonAreaUnderCursor == ButtonAreaUser) {
1986 					if (button.Button.Callback) {
1987 						button.Button.Callback->action("");
1988 					}
1989 					return;
1990 				}
1991 			}
1992 		}
1993 		if (!GameObserve && !GamePaused && !GameEstablishing && Selected.empty() == false && ThisPlayer->IsTeamed(*Selected[0])) {
1994 			if (OldButtonUnderCursor != -1 && OldButtonUnderCursor == ButtonUnderCursor) {
1995 				UI.ButtonPanel.DoClicked(ButtonUnderCursor);
1996 				OldButtonUnderCursor = -1;
1997 				return;
1998 			}
1999 		}
2000 		if (CursorOn == CursorOnButton) {
2001 			// FIXME: other buttons?
2002 			if (ButtonAreaUnderCursor == ButtonAreaButton && OldButtonUnderCursor != -1 && OldButtonUnderCursor == ButtonUnderCursor) {
2003 				UI.ButtonPanel.DoClicked(ButtonUnderCursor);
2004 				return;
2005 			}
2006 		}
2007 	}
2008 
2009 	// FIXME: should be completly rewritten
2010 	// FIXME: must selecting!  (lokh: what does this mean? is this done now?)
2011 
2012 	// SHIFT toggles select/unselect a single unit and
2013 	// add the content of the rectangle to the selectection
2014 	// ALT takes group of unit
2015 	// CTRL takes all units of same type (st*rcr*ft)
2016 	if (CursorState == CursorStateRectangle && !(MouseButtons & LeftButton)) { // leave select mode
2017 		int num = 0;
2018 		//
2019 		//  Little threshold
2020 		//
2021 		if (CursorStartScreenPos.x < CursorScreenPos.x - 1 || CursorScreenPos.x + 1 < CursorStartScreenPos.x
2022 			|| CursorStartScreenPos.y < CursorScreenPos.y - 1 || CursorScreenPos.y + 1 < CursorStartScreenPos.y) {
2023 			PixelPos pos0 = CursorStartMapPos;
2024 			const PixelPos cursorMapPos = UI.MouseViewport->ScreenToMapPixelPos(CursorScreenPos);
2025 			PixelPos pos1 = cursorMapPos;
2026 
2027 			if (pos0.x > pos1.x) {
2028 				std::swap(pos0.x, pos1.x);
2029 			}
2030 			if (pos0.y > pos1.y) {
2031 				std::swap(pos0.y, pos1.y);
2032 			}
2033 			if (KeyModifiers & ModifierShift) {
2034 				if (KeyModifiers & ModifierAlt) {
2035 					num = AddSelectedGroundUnitsInRectangle(pos0, pos1);
2036 				} else if (KeyModifiers & ModifierControl) {
2037 					num = AddSelectedAirUnitsInRectangle(pos0, pos1);
2038 				} else {
2039 					num = AddSelectedUnitsInRectangle(pos0, pos1);
2040 				}
2041 			} else {
2042 				if (KeyModifiers & ModifierAlt) {
2043 					num = SelectGroundUnitsInRectangle(pos0, pos1);
2044 				} else if (KeyModifiers & ModifierControl) {
2045 					num = SelectAirUnitsInRectangle(pos0, pos1);
2046 				} else {
2047 					num = SelectUnitsInRectangle(pos0, pos1);
2048 				}
2049 			}
2050 		} else {
2051 			//
2052 			// Select single unit
2053 			//
2054 			// cade: cannot select unit on invisible space
2055 			// FIXME: johns: only complete invisibile units
2056 			const Vec2i cursorTilePos = UI.MouseViewport->ScreenToTilePos(CursorScreenPos);
2057 			CUnit *unit = NULL;
2058 			if (ReplayRevealMap || Map.Field(cursorTilePos)->playerInfo.IsTeamVisible(*ThisPlayer)) {
2059 				const PixelPos cursorMapPos = UI.MouseViewport->ScreenToMapPixelPos(CursorScreenPos);
2060 
2061 				unit = UnitOnScreen(cursorMapPos.x, cursorMapPos.y);
2062 			}
2063 			if (unit) {
2064 				// FIXME: Not nice coded, button number hardcoded!
2065 				if ((KeyModifiers & ModifierControl)
2066 					|| (button & (1 << MouseDoubleShift))) {
2067 					if (KeyModifiers & ModifierShift) {
2068 						num = ToggleUnitsByType(*unit);
2069 					} else {
2070 						num = SelectUnitsByType(*unit);
2071 					}
2072 				} else if ((KeyModifiers & ModifierAlt) && unit->LastGroup) {
2073 					if (KeyModifiers & ModifierShift) {
2074 						num = AddGroupFromUnitToSelection(*unit);
2075 					} else {
2076 						num = SelectGroupFromUnit(*unit);
2077 					}
2078 
2079 					// Don't allow to select own and enemy units.
2080 					// Don't allow mixing buildings
2081 				} else if (KeyModifiers & ModifierShift
2082 						   && (unit->Player == ThisPlayer || ThisPlayer->IsTeamed(*unit))
2083 						   && !unit->Type->Building
2084 						   && (Selected.size() != 1 || !Selected[0]->Type->Building)
2085 						   && (Selected.size() != 1 || Selected[0]->Player == ThisPlayer || ThisPlayer->IsTeamed(*Selected[0]))) {
2086 					num = ToggleSelectUnit(*unit);
2087 					if (!num) {
2088 						SelectionChanged();
2089 					}
2090 				} else {
2091 					SelectSingleUnit(*unit);
2092 					num = 1;
2093 				}
2094 			} else {
2095 				num = 0;
2096 			}
2097 		}
2098 
2099 		if (num) {
2100 			UI.StatusLine.Clear();
2101 			UI.StatusLine.ClearCosts();
2102 			CurrentButtonLevel = 0;
2103 			SelectionChanged();
2104 
2105 			//
2106 			//  Play selecting sound.
2107 			//    Buildings,
2108 			//    This player, or neutral unit (goldmine,critter)
2109 			//    Other clicks.
2110 			//
2111 			if (Selected.size() == 1) {
2112 				if (Selected[0]->CurrentAction() == UnitActionBuilt && Selected[0]->Player->Index == ThisPlayer->Index) {
2113 					PlayUnitSound(*Selected[0], VoiceBuilding);
2114 				} else if (Selected[0]->Burning) {
2115 					// FIXME: use GameSounds.Burning
2116 					PlayGameSound(SoundForName("burning"), MaxSampleVolume);
2117 				} else if (Selected[0]->Player == ThisPlayer || ThisPlayer->IsTeamed(*Selected[0])
2118 						   || Selected[0]->Player->Type == PlayerNeutral) {
2119 					PlayUnitSound(*Selected[0], VoiceSelected);
2120 				} else {
2121 					PlayGameSound(GameSounds.Click.Sound, MaxSampleVolume);
2122 				}
2123 				if (Selected[0]->Player == ThisPlayer) {
2124 					char buf[64];
2125 					if (Selected[0]->Player->UnitTypesCount[Selected[0]->Type->Slot] > 1) {
2126 						snprintf(buf, sizeof(buf), _("You have ~<%d~> %ss"),
2127 								 Selected[0]->Player->UnitTypesCount[Selected[0]->Type->Slot],
2128 								 Selected[0]->Type->Name.c_str());
2129 					} else {
2130 						snprintf(buf, sizeof(buf), _("You have ~<%d~> %s(s)"),
2131 								 Selected[0]->Player->UnitTypesCount[Selected[0]->Type->Slot],
2132 								 Selected[0]->Type->Name.c_str());
2133 					}
2134 					UI.StatusLine.Set(buf);
2135 				}
2136 			}
2137 		}
2138 
2139 		CursorStartScreenPos.x = 0;
2140 		CursorStartScreenPos.y = 0;
2141 		GameCursor = UI.Point.Cursor;
2142 		CursorState = CursorStatePoint;
2143 	}
2144 }
2145 
2146 /**
2147 **  Get pie menu under the cursor
2148 **
2149 **  @return  Index of the pie menu under the cursor or -1 for none
2150 */
GetPieUnderCursor()2151 static int GetPieUnderCursor()
2152 {
2153 	int x = CursorScreenPos.x - (CursorStartScreenPos.x - ICON_SIZE_X / 2);
2154 	int y = CursorScreenPos.y - (CursorStartScreenPos.y - ICON_SIZE_Y / 2);
2155 	for (int i = 0; i < 9; ++i) {
2156 		if (x > UI.PieMenu.X[i] && x < UI.PieMenu.X[i] + ICON_SIZE_X
2157 			&& y > UI.PieMenu.Y[i] && y < UI.PieMenu.Y[i] + ICON_SIZE_Y) {
2158 			return i;
2159 		}
2160 	}
2161 	return -1; // no pie under cursor
2162 }
2163 
2164 /**
2165 **  Draw Pie Menu
2166 */
DrawPieMenu()2167 void DrawPieMenu()
2168 {
2169 	char buf[2] = "?";
2170 
2171 	if (CursorState != CursorStatePieMenu) {
2172 		return;
2173 	}
2174 
2175 	if (CurrentButtons.empty()) { // no buttons
2176 		CursorState = CursorStatePoint;
2177 		return;
2178 	}
2179 	std::vector<ButtonAction> &buttons(CurrentButtons);
2180 	CLabel label(GetGameFont());
2181 	CViewport *vp = UI.SelectedViewport;
2182 	PushClipping();
2183 	vp->SetClipping();
2184 
2185 	// Draw background
2186 	if (UI.PieMenu.G) {
2187 		UI.PieMenu.G->DrawFrameClip(0,
2188 									CursorStartScreenPos.x - UI.PieMenu.G->Width / 2,
2189 									CursorStartScreenPos.y - UI.PieMenu.G->Height / 2);
2190 	}
2191 	for (int i = 0; i < (int)UI.ButtonPanel.Buttons.size() && i < 9; ++i) {
2192 		if (buttons[i].Pos != -1) {
2193 			int x = CursorStartScreenPos.x - ICON_SIZE_X / 2 + UI.PieMenu.X[i];
2194 			int y = CursorStartScreenPos.y - ICON_SIZE_Y / 2 + UI.PieMenu.Y[i];
2195 			const PixelPos pos(x, y);
2196 
2197 			bool gray = false;
2198 			for (size_t j = 0; j != Selected.size(); ++j) {
2199 				if (!IsButtonAllowed(*Selected[j], buttons[i])) {
2200 					gray = true;
2201 					break;
2202 				}
2203 			}
2204 			// Draw icon
2205 			if (gray) {
2206 				buttons[i].Icon.Icon->DrawGrayscaleIcon(pos);
2207 			} else {
2208 				buttons[i].Icon.Icon->DrawIcon(pos, ThisPlayer->Index);
2209 			}
2210 
2211 			// Tutorial show command key in icons
2212 			if (UI.ButtonPanel.ShowCommandKey) {
2213 				const char *text;
2214 
2215 				if (buttons[i].Key == 27) {
2216 					text = "ESC";
2217 				} else {
2218 					buf[0] = toupper(buttons[i].Key);
2219 					text = (const char *)buf;
2220 				}
2221 				label.DrawClip(x + 4, y + 4, text);
2222 			}
2223 		}
2224 	}
2225 
2226 	PopClipping();
2227 
2228 	int i = GetPieUnderCursor();
2229 	if (i != -1 && KeyState != KeyStateInput && buttons[i].Pos != -1) {
2230 		if (!Preference.NoStatusLineTooltips) {
2231 			UpdateStatusLineForButton(buttons[i]);
2232 		}
2233 		DrawPopup(buttons[i], UI.ButtonPanel.Buttons[i],
2234 				  CursorStartScreenPos.x + UI.PieMenu.X[i], CursorStartScreenPos.y + UI.PieMenu.Y[i]);
2235 	}
2236 }
2237 
2238 /**
2239 **  Handle pie menu mouse selection
2240 */
HandlePieMenuMouseSelection()2241 static void HandlePieMenuMouseSelection()
2242 {
2243 	if (CurrentButtons.empty()) {  // no buttons
2244 		return;
2245 	}
2246 
2247 	int pie = GetPieUnderCursor();
2248 	if (pie != -1) {
2249 		const ButtonCmd action = CurrentButtons[pie].Action;
2250 		UI.ButtonPanel.DoClicked(pie);
2251 		if (action == ButtonButton) {
2252 			// there is a submenu => stay in piemenu mode
2253 			// and recenter the piemenu around the cursor
2254 			CursorStartScreenPos = CursorScreenPos;
2255 		} else {
2256 			if (CursorState == CursorStatePieMenu) {
2257 				CursorState = CursorStatePoint;
2258 			}
2259 			CursorOn = CursorOnUnknown;
2260 			UIHandleMouseMove(CursorScreenPos); // recompute CursorOn and company
2261 		}
2262 	} else {
2263 		CursorState = CursorStatePoint;
2264 		CursorOn = CursorOnUnknown;
2265 		UIHandleMouseMove(CursorScreenPos); // recompute CursorOn and company
2266 	}
2267 }
2268 //@}
2269