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