1 /* GemRB - Infinity Engine Emulator
2 * Copyright (C) 2003 The GemRB Project
3 *
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License
6 * as published by the Free Software Foundation; either version 2
7 * of the License, or (at your option) any later version.
8
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 *
18 */
19
20 #include "GUI/GameControl.h"
21
22 #include "strrefs.h"
23
24 #include "CharAnimations.h"
25 #include "DialogHandler.h"
26 #include "DisplayMessage.h"
27 #include "Game.h"
28 #include "GameData.h"
29 #include "GlobalTimer.h"
30 #include "GUIScriptInterface.h"
31 #include "ImageMgr.h"
32 #include "Interface.h"
33 #include "KeyMap.h"
34 #include "PathFinder.h"
35 #include "ScriptEngine.h"
36 #include "TileMap.h"
37 #include "Video.h"
38 #include "damages.h"
39 #include "ie_cursors.h"
40 #include "opcode_params.h"
41 #include "GameScript/GSUtils.h"
42 #include "GUI/EventMgr.h"
43 #include "GUI/TextArea.h"
44 #include "GUI/Window.h"
45 #include "RNG.h"
46 #include "Scriptable/Container.h"
47 #include "Scriptable/Door.h"
48 #include "Scriptable/InfoPoint.h"
49
50 namespace GemRB {
51
52 #define FORMATIONSIZE 10
53 typedef Point formation_type[FORMATIONSIZE];
54 ieDword formationcount;
55 static formation_type *formations=NULL;
56 static ieResRef TestSpell="SPWI207";
57
58 uint32_t GameControl::DebugFlags = 0;
59
60 //If one of the actors has tracking on, the gamecontrol needs to display
61 //arrow markers on the edges to point at detected monsters
62 //tracterID is the tracker actor's global ID
63 //distance is the detection distance
SetTracker(Actor * actor,ieDword dist)64 void GameControl::SetTracker(Actor *actor, ieDword dist)
65 {
66 trackerID = actor->GetGlobalID();
67 distance = dist;
68 }
69
GameControl(const Region & frame)70 GameControl::GameControl(const Region& frame)
71 : View(frame)
72 {
73 if (!formations) {
74 ReadFormations();
75 }
76 //this is the default action, individual actors should have one too
77 //at this moment we use only this
78 //maybe we don't even need it
79 spellCount = spellIndex = spellOrItem = spellSlot = 0;
80 spellUser = NULL;
81 spellName[0] = 0;
82 user = NULL;
83 lastActorID = 0;
84 trackerID = 0;
85 distance = 0;
86 overDoor = NULL;
87 overContainer = NULL;
88 overInfoPoint = NULL;
89 drawPath = NULL;
90 lastCursor = IE_CURSOR_INVALID;
91 moveX = moveY = 0;
92 numScrollCursor = 0;
93
94 ieDword tmp = 0;
95 core->GetDictionary()->Lookup("Always Run", tmp);
96 AlwaysRun = tmp != 0;
97
98 ClearMouseState();
99 ResetTargetMode();
100
101 tmp = 0;
102 core->GetDictionary()->Lookup("Center",tmp);
103 if (tmp) {
104 ScreenFlags = SF_ALWAYSCENTER | SF_CENTERONACTOR;
105 } else {
106 ScreenFlags = SF_CENTERONACTOR;
107 }
108 // the game always starts paused so nothing happens till we are ready
109 DialogueFlags = DF_FREEZE_SCRIPTS;
110 dialoghandler = new DialogHandler();
111 DisplayText = NULL;
112 DisplayTextTime = 0;
113 updateVPTimer = true;
114
115 EventMgr::EventCallback cb = METHOD_CALLBACK(&GameControl::OnGlobalMouseMove, this);
116 eventMonitors[0] = EventMgr::RegisterEventMonitor(cb, Event::MouseMoveMask);
117 EventMgr::EventCallback cb2 = METHOD_CALLBACK(&GameControl::DispatchEvent, this);
118 eventMonitors[1] = EventMgr::RegisterEventMonitor(cb2, Event::KeyDownMask);
119 }
120
~GameControl()121 GameControl::~GameControl()
122 {
123 for (size_t i = 0; i < 2; ++i) {
124 EventMgr::UnRegisterEventMonitor(eventMonitors[i]);
125 }
126
127 if (formations) {
128 free( formations );
129 formations = NULL;
130 }
131 delete dialoghandler;
132 delete DisplayText;
133 }
134
135 //TODO:
136 //There could be a custom formation which is saved in the save game
137 //alternatively, all formations could be saved in some compatible way
138 //so it doesn't cause problems with the original engine
ReadFormations() const139 void GameControl::ReadFormations() const
140 {
141 AutoTable tab("formatio");
142 if (!tab) {
143 // fallback
144 formationcount = 1;
145 formations = (formation_type *) calloc(1,sizeof(formation_type) );
146 return;
147 }
148 formationcount = tab->GetRowCount();
149 formations = (formation_type *) calloc(formationcount, sizeof(formation_type));
150 for (unsigned int i = 0; i < formationcount; i++) {
151 for (unsigned int j = 0; j < FORMATIONSIZE; j++) {
152 short k = (short) atoi(tab->QueryField(i, j*2));
153 formations[i][j].x = k;
154 k = (short) atoi(tab->QueryField(i, j*2+1));
155 formations[i][j].y = k;
156 }
157 }
158 }
159
160 //returns a single point offset for a formation
161 //formation: the formation type
162 //pos: the actor's slot ID
GetFormationOffset(ieDword formation,ieDword pos) const163 Point GameControl::GetFormationOffset(ieDword formation, ieDword pos) const
164 {
165 if (formation>=formationcount) formation = 0;
166 if (pos>=FORMATIONSIZE) pos=FORMATIONSIZE-1;
167 return formations[formation][pos];
168 }
169
GetFormationPoint(const Point & origin,size_t pos,double angle,const std::vector<Point> & exclude) const170 Point GameControl::GetFormationPoint(const Point& origin, size_t pos, double angle, const std::vector<Point>& exclude) const
171 {
172 Point vec;
173
174 const Game* game = core->GetGame();
175 const Map* area = game->GetCurrentArea();
176 assert(area);
177
178 static constexpr int radius = 36 / 2; // 36 diameter is copied from make_formation.py
179 const auto& formation = game->GetFormation();
180 assert(formation < formationcount);
181
182 Point stepVec;
183 int direction = (pos % 2 == 0) ? 1 : -1;
184
185 /* Correct for the innate orientation of the points. */
186 angle += M_PI_2;
187
188 if (pos < FORMATIONSIZE) {
189 // calculate new coordinates by rotating formation around (0,0)
190 vec = RotatePoint(formations[formation][pos], angle);
191 stepVec.y = radius;
192 } else {
193
194 // create a line formation perpendicular to the formation start point and beginning at the last point
195 // of the formation table. Alternate between +90 degrees and -90 degrees to keep it balanced
196 // the formation table is created along the x axis starting at (0,0)
197 Point p = formations[formation][FORMATIONSIZE-1];
198 vec = RotatePoint(p, angle);
199 stepVec.x = radius * direction;
200 }
201
202 Point dest = vec + origin;
203 int step = 0;
204 constexpr int maxStep = 4;
205 double stepAngle = 0.0;
206 const Point& start = vec;
207
208 auto NextDest = [&]() -> Point {
209 // adjust the point if the actor cant get to `dest`
210 // we do this by sweeping an M_PI arc a `radius` (stepVec) away from the point
211 // and oriented according to `direction`
212 // if nothing is found, reset to `start` and increase the `stepVec` and sweep again
213 // each incremental sweep step the `stepAngle` increment shrinks because we have more area to fit
214 // if nothing is found after `maxStep` sweeps we just give up and leave it to the path finder to work out
215
216 // FIXME: we should precalculate these into a table and use step as an index
217 // there is a precission/rounding problem here with comparing against M_PI
218 // and we may not use one of the arc end points
219 stepAngle += (M_PI_4 / (step + 1)) * direction;
220 if (stepAngle > M_PI || stepAngle < -M_PI) {
221 ++step;
222 stepAngle = 0.0;
223 if (stepVec.y != 0) {
224 stepVec.y += radius;
225 } else {
226 stepVec.x += radius * direction;
227 }
228 }
229
230 return origin + start + RotatePoint(stepVec, angle + stepAngle);
231 };
232
233 while (step < maxStep) {
234 auto it = std::find_if(exclude.begin(), exclude.end(), [&](const Point& p) {
235 // look for points within some radius
236 return p.isWithinRadius(radius, dest);
237 });
238
239 if (it != exclude.end()) {
240 dest = NextDest();
241 continue;
242 }
243
244 if (area->IsExplored(dest) == false || !(area->GetBlockedNavmap(dest) & PathMapFlags::PASSABLE)) {
245 dest = NextDest();
246 continue;
247 }
248
249 break;
250 }
251
252 if (step == maxStep) {
253 // we never found a suitable point
254 // to garauntee a point that is reachable just fall back to origin
255 // let the pathfinder sort it out
256 return origin;
257 }
258
259 return dest;
260 }
261
GetFormationPoints(const Point & origin,const std::vector<Actor * > & actors,double angle) const262 GameControl::FormationPoints GameControl::GetFormationPoints(const Point& origin, const std::vector<Actor*>& actors,
263 double angle) const
264 {
265 FormationPoints formation;
266 for (size_t i = 0; i < actors.size(); ++i) {
267 formation.emplace_back(GetFormationPoint(origin, i, angle, formation));
268 }
269 return formation;
270 }
271
DrawFormation(const std::vector<Actor * > & actors,const Point & formationPoint,double angle) const272 void GameControl::DrawFormation(const std::vector<Actor*>& actors, const Point& formationPoint, double angle) const
273 {
274 std::vector<Point> formationPoints = GetFormationPoints(formationPoint, actors, angle);
275 for (size_t i = 0; i < actors.size(); ++i) {
276 DrawTargetReticle(actors[i], formationPoints[i] - vpOrigin);
277 }
278 }
279
ClearMouseState()280 void GameControl::ClearMouseState()
281 {
282 isSelectionRect = false;
283 isFormationRotation = false;
284
285 SetCursor(NULL);
286 }
287
288 // generate an action to do the actual movement
289 // only PST supports RunToPoint
CreateMovement(Actor * actor,const Point & p,bool append,bool tryToRun) const290 void GameControl::CreateMovement(Actor *actor, const Point &p, bool append, bool tryToRun) const
291 {
292 char Tmp[256];
293 Action *action = NULL;
294 tryToRun |= AlwaysRun;
295
296 if (append) {
297 sprintf(Tmp, "AddWayPoint([%d.%d])", p.x, p.y);
298 action = GenerateAction(Tmp);
299 assert(action);
300 } else {
301 //try running (in PST) only if not encumbered
302 if (tryToRun && CanRun(actor)) {
303 sprintf( Tmp, "RunToPoint([%d.%d])", p.x, p.y );
304 action = GenerateAction( Tmp );
305 }
306
307 // check again because GenerateAction can fail (non PST)
308 if (!action) {
309 sprintf(Tmp, "MoveToPoint([%d.%d])", p.x, p.y);
310 action = GenerateAction( Tmp );
311 }
312 }
313
314 actor->CommandActor(action, !append);
315 actor->Destination = p; // just to force target reticle drawing if paused
316 }
317
318 // can we handle it (no movement impairments)?
CanRun(const Actor * actor) const319 bool GameControl::CanRun(const Actor *actor) const
320 {
321 if (!actor) return false;
322 if (actor->GetEncumbranceFactor(true) != 1) {
323 return false;
324 }
325 return true;
326 }
327
ShouldRun(const Actor * actor) const328 bool GameControl::ShouldRun(const Actor *actor) const
329 {
330 return CanRun(actor) && AlwaysRun;
331 }
332
333 // ArrowSprite cycles
334 // 321
335 // 4 0
336 // 567
337
338 #define D_LEFT 1
339 #define D_UP 2
340 #define D_RIGHT 4
341 #define D_BOTTOM 8
342 // Direction Bits
343 // 326
344 // 1 4
345 // 98c
346
347 static const int arrow_orientations[16]={
348 // 0 1 2 3 4 5 6 7 8 9 a b c d e f
349 -1, 4, 2, 3, 0,-1, 1,-1, 6, 5,-1,-1, 7,-1,-1,-1
350 };
351
352 //Draws arrow markers along the edge of the game window
353 //WARNING:don't use reference for point, because it is altered
DrawArrowMarker(Point p,const Color & color) const354 void GameControl::DrawArrowMarker(Point p, const Color& color) const
355 {
356 WindowManager* wm = core->GetWindowManager();
357 auto lock = wm->DrawHUD();
358
359 ieDword draw = 0;
360 if (p.x < vpOrigin.x) {
361 p.x = vpOrigin.x;
362 draw|= D_LEFT;
363 }
364 if (p.y < vpOrigin.y) {
365 p.y = vpOrigin.y;
366 draw |= D_UP;
367 }
368
369 Holder<Sprite2D> spr = core->GetScrollCursorSprite(0,0);
370 int tmp = spr->Frame.w;
371 if (p.x > vpOrigin.x + frame.w - tmp) {
372 p.x = vpOrigin.x + frame.w;
373 draw |= D_RIGHT;
374 }
375
376 tmp = spr->Frame.h;
377 if (p.y > vpOrigin.y + frame.h - tmp) {
378 p.y = vpOrigin.y + frame.h;
379 draw |= D_BOTTOM;
380 }
381
382 if (arrow_orientations[draw]>=0) {
383 Video* video = core->GetVideoDriver();
384 Holder<Sprite2D> arrow = core->GetScrollCursorSprite(arrow_orientations[draw], 0);
385 video->BlitGameSprite(arrow, p - vpOrigin, BlitFlags::COLOR_MOD | BlitFlags::BLENDED, color);
386 }
387 }
388
DrawTargetReticle(int size,const Color & color,const Point & p) const389 void GameControl::DrawTargetReticle(int size, const Color& color, const Point& p) const
390 {
391 Video* video = core->GetVideoDriver();
392
393 uint8_t offset = GlobalColorCycle.Step() >> 1;
394
395 /* segments should not go outside selection radius */
396 unsigned short xradius = (size * 4) - 5;
397 unsigned short yradius = (size * 3) - 5;
398
399 // NOTE: 0.5 and 0.7 are pretty much random values
400 // right segment
401 video->DrawEllipseSegment( p + Point(offset, 0),
402 xradius, yradius, color, -0.5, 0.5, true);
403 // top segment
404 video->DrawEllipseSegment( p - Point(0, offset),
405 xradius, yradius, color, -0.7 - M_PI_2, 0.7 - M_PI_2, true);
406 // left segment
407 video->DrawEllipseSegment( p - Point(offset, 0),
408 xradius, yradius, color, -0.5 - M_PI, 0.5 - M_PI, true);
409 // bottom segment
410 video->DrawEllipseSegment( p + Point(0, offset),
411 xradius, yradius, color, -0.7 - M_PI - M_PI_2, 0.7 - M_PI - M_PI_2, true);
412 }
413
DrawTargetReticle(const Movable * target,const Point & p) const414 void GameControl::DrawTargetReticle(const Movable* target, const Point& p) const
415 {
416 int size = std::max((target->size - 1) * 4, 3);
417
418 const Color& green = target->selectedColor;
419 const Color& color = (target->Over) ? GlobalColorCycle.Blend(target->overColor, green) : green;
420
421 DrawTargetReticle(size, color, p);
422 }
423
WillDraw(const Region &,const Region &)424 void GameControl::WillDraw(const Region& /*drawFrame*/, const Region& /*clip*/)
425 {
426 UpdateCursor();
427
428 bool update_scripts = !(DialogueFlags & DF_FREEZE_SCRIPTS);
429
430 // handle keeping the actor in the spotlight, but only when unpaused
431 if ((ScreenFlags & SF_ALWAYSCENTER) && update_scripts) {
432 const Actor *star = core->GetFirstSelectedActor();
433 if (star) {
434 moveX = star->Pos.x - vpOrigin.x - frame.w/2;
435 moveY = star->Pos.y - vpOrigin.y - frame.h/2;
436 }
437 }
438
439 if (moveX || moveY) {
440 if (MoveViewportTo( vpOrigin + Point(moveX, moveY), false )) {
441 if ((Flags() & IgnoreEvents) == 0 && core->GetMouseScrollSpeed()) {
442 int cursorFrame = 0; // right
443 if (moveY < 0) {
444 cursorFrame = 2; // up
445 if (moveX > 0) cursorFrame--; // +right
446 else if (moveX < 0) cursorFrame++; // +left
447 } else if (moveY > 0) {
448 cursorFrame = 6; // down
449 if (moveX > 0) cursorFrame++; // +right
450 else if (moveX < 0) cursorFrame--; // +left
451 } else if (moveX < 0) {
452 cursorFrame = 4; // left
453 }
454
455 if ((ScreenFlags & SF_ALWAYSCENTER) == 0) {
456 // set these cursors on game window so they are universal
457 window->SetCursor(core->GetScrollCursorSprite(cursorFrame, numScrollCursor));
458
459 numScrollCursor = (numScrollCursor+1) % 15;
460 }
461 }
462 } else {
463 window->SetCursor(NULL);
464 }
465 } else if (!window->IsDisabled()) {
466 window->SetCursor(NULL);
467 }
468
469 const Map* area = CurrentArea();
470 assert(area);
471
472 Actor **ab;
473 int flags = GA_NO_DEAD|GA_NO_UNSCHEDULED|GA_SELECT|GA_NO_ENEMY|GA_NO_NEUTRAL;
474 int count = area->GetActorsInRect(ab, SelectionRect(), flags);
475
476 std::vector<Actor*>::iterator it = highlighted.begin();
477 for (; it != highlighted.end(); ++it) {
478 Actor* act = *it;
479 act->SetOver(false);
480 }
481
482 highlighted.clear();
483 for (int i = 0; i < count; i++) {
484 Actor* actor = ab[i];
485 actor->SetOver(true);
486 highlighted.push_back(actor);
487 }
488 free( ab );
489 }
490
491 /** Draws the Control on the Output Display */
DrawSelf(Region screen,const Region &)492 void GameControl::DrawSelf(Region screen, const Region& /*clip*/)
493 {
494 const Game* game = core->GetGame();
495 Map *area = game->GetCurrentArea();
496
497 // FIXME: some of this should happen during mouse events
498 // setup outlines
499 InfoPoint *i;
500 for (size_t idx = 0; (i = area->TMap->GetInfoPoint(idx)); idx++) {
501 i->Highlight = false;
502 if (i->VisibleTrap(0)) {
503 if (overInfoPoint == i && target_mode) {
504 i->outlineColor = ColorGreen;
505 } else {
506 i->outlineColor = ColorRed;
507 }
508 i->Highlight = true;
509 continue;
510 }
511 }
512
513 // FIXME: some of this should happen during mouse events
514 Door *d;
515 for (size_t idx = 0; (d = area->TMap->GetDoor(idx)); idx++) {
516 d->Highlight = false;
517 if (d->Flags & DOOR_HIDDEN) {
518 continue;
519 }
520
521 if (d->Flags & DOOR_SECRET) {
522 if (d->Flags & DOOR_FOUND) {
523 d->Highlight = true;
524 d->outlineColor = ColorMagenta; // found hidden door
525 } else {
526 continue;
527 }
528 }
529
530 if (overDoor == d) {
531 d->Highlight = true;
532 if (target_mode) {
533 if (d->Visible() && (d->VisibleTrap(0) || (d->Flags & DOOR_LOCKED))) {
534 // only highlight targettable doors
535 d->outlineColor = ColorGreen;
536 }
537 } else if (!(d->Flags & DOOR_SECRET)) {
538 // mouse over, not in target mode, no secret door
539 d->outlineColor = ColorCyan;
540 }
541 }
542
543 // traps always take precedence
544 if (d->VisibleTrap(0)) {
545 d->Highlight = true;
546 d->outlineColor = ColorRed;
547 }
548 }
549
550 // FIXME: some of this should happen during mouse events
551 Container *c;
552 for (size_t idx = 0; (c = area->TMap->GetContainer(idx)); idx++) {
553 c->Highlight = false;
554 if (c->Flags & CONT_DISABLED) {
555 continue;
556 }
557
558 if (overContainer == c) {
559 c->Highlight = true;
560 if (target_mode) {
561 if (c->Flags & CONT_LOCKED) {
562 c->outlineColor = ColorGreen;
563 }
564 } else {
565 c->outlineColor = ColorCyan;
566 }
567 }
568
569 // traps always take precedence
570 if (c->VisibleTrap(0)) {
571 c->Highlight = true;
572 c->outlineColor = ColorRed; // traps
573 }
574 }
575
576 //drawmap should be here so it updates fog of war
577 area->DrawMap(Viewport(), DebugFlags);
578
579 if (trackerID) {
580 const Actor *actor = area->GetActorByGlobalID(trackerID);
581
582 if (actor) {
583 std::vector<Actor*> monsters = area->GetAllActorsInRadius(actor->Pos, GA_NO_DEAD|GA_NO_LOS|GA_NO_UNSCHEDULED, distance);
584 for (auto monster : monsters) {
585 if (monster->IsPartyMember()) continue;
586 if (monster->GetStat(IE_NOTRACKING)) continue;
587 DrawArrowMarker(monster->Pos, ColorBlack);
588 }
589 } else {
590 trackerID = 0;
591 }
592 }
593
594 if (lastActorID) {
595 const Actor* actor = GetLastActor();
596 if (actor) {
597 DrawArrowMarker(actor->Pos, ColorGreen);
598 }
599 }
600
601 Video* video = core->GetVideoDriver();
602 // Draw selection rect
603 if (isSelectionRect) {
604 Region r = SelectionRect();
605 r.x -= vpOrigin.x;
606 r.y -= vpOrigin.y;
607 video->DrawRect(r, ColorGreen, false );
608 }
609
610 const Point& gameMousePos = GameMousePos();
611 // draw reticles
612 if (isFormationRotation) {
613 double angle = formationBaseAngle;
614 if (Distance(gameMousePos, gameClickPoint) > EventMgr::mouseDragRadius) {
615 angle = AngleFromPoints(gameMousePos, gameClickPoint);
616 }
617 DrawFormation(game->selected, gameClickPoint, angle);
618 } else {
619 int max = game->GetPartySize(false);
620 for (int idx = 1; idx <= max; idx++) {
621 const Actor* actor = game->FindPC(idx);
622 assert(actor);
623 if (actor->ShouldDrawReticle()) {
624 DrawTargetReticle(actor, actor->Destination - vpOrigin);
625 }
626 }
627 }
628
629 // Draw path
630 if (drawPath) {
631 PathNode* node = drawPath;
632 while (true) {
633 Point p( ( node-> x*16) + 8, ( node->y*12 ) + 6 );
634 if (!node->Parent) {
635 video->DrawCircle( p, 2, ColorRed );
636 } else {
637 short oldX = ( node->Parent-> x*16) + 8, oldY = ( node->Parent->y*12 ) + 6;
638 video->DrawLine( Point(oldX, oldY), p, ColorGreen );
639 }
640 if (!node->Next) {
641 video->DrawCircle( p, 2, ColorGreen );
642 break;
643 }
644 node = node->Next;
645 }
646 }
647
648 // Draw lightmap
649 if (DebugFlags & DEBUG_SHOW_LIGHTMAP) {
650 Holder<Sprite2D> spr = area->LightMap->GetSprite2D();
651 video->BlitSprite(spr, Point());
652 Region point(gameMousePos.x / 16, gameMousePos.y / 12, 2, 2);
653 video->DrawRect(point, ColorRed);
654 }
655
656 if (core->HasFeature(GF_ONSCREEN_TEXT) && DisplayText) {
657 core->GetTextFont()->Print(screen, *DisplayText, IE_FONT_ALIGN_CENTER | IE_FONT_ALIGN_MIDDLE, {core->InfoTextColor, ColorBlack});
658 if (!(DialogueFlags & DF_FREEZE_SCRIPTS)) {
659 // just replicating original engine behaviour
660 if (DisplayTextTime == 0) {
661 SetDisplayText((String*)NULL, 0);
662 } else {
663 DisplayTextTime--;
664 }
665 }
666 }
667 }
668
669 // this existly only so tab can be handled
670 // it's used both for tooltips everywhere and hp display on game control
DispatchEvent(const Event & event) const671 bool GameControl::DispatchEvent(const Event& event) const
672 {
673 const Game *game = core->GetGame();
674 if (!game) return false;
675
676 if (event.keyboard.keycode == GEM_TAB) {
677 // show partymember hp/maxhp as overhead text
678 for (int pm=0; pm < game->GetPartySize(false); pm++) {
679 Actor *pc = game->GetPC(pm, true);
680 if (!pc) continue;
681 pc->DisplayHeadHPRatio();
682 }
683 return true;
684 } else if (event.keyboard.keycode == GEM_ESCAPE) {
685 core->SetEventFlag(EF_ACTION|EF_RESETTARGET);
686 }
687 return false;
688 }
689
690 /** Key Press Event */
OnKeyPress(const KeyboardEvent & Key,unsigned short mod)691 bool GameControl::OnKeyPress(const KeyboardEvent& Key, unsigned short mod)
692 {
693 unsigned int i, pc;
694 Game* game = core->GetGame();
695
696 KeyboardKey keycode = Key.keycode;
697 if (mod) {
698 switch (keycode) {
699 case GEM_ALT:
700 DebugFlags |= DEBUG_SHOW_CONTAINERS|DEBUG_SHOW_DOORS;
701 break;
702 default:
703 // the random bitshift is to skip checking hotkeys with mods
704 // eg. ctrl-j should be ignored for keymap.ini handling and
705 // passed straight on
706 if (!core->GetKeyMap()->ResolveKey(Key.keycode, mod<<20)) {
707 game->SendHotKey(toupper(Key.character));
708 return View::OnKeyPress(Key, mod);
709 }
710 break;
711 }
712 } else {
713 switch (keycode) {
714 case GEM_UP:
715 case GEM_DOWN:
716 case GEM_LEFT:
717 case GEM_RIGHT:
718 {
719 ieDword keyScrollSpd = 64;
720 core->GetDictionary()->Lookup("Keyboard Scroll Speed", keyScrollSpd);
721 if (keycode >= GEM_UP) {
722 int v = (keycode == GEM_UP) ? -1 : 1;
723 Scroll( Point(0, keyScrollSpd * v) );
724 } else {
725 int v = (keycode == GEM_LEFT) ? -1 : 1;
726 Scroll( Point(keyScrollSpd * v, 0) );
727 }
728 }
729 break;
730 #ifdef ANDROID
731 case 'c': // show containers in ANDROID, GEM_ALT is not possible to use
732
733 DebugFlags |= DEBUG_SHOW_CONTAINERS|DEBUG_SHOW_DOORS;
734 break;
735 #endif
736 case GEM_TAB: // show partymember hp/maxhp as overhead text
737 // fallthrough
738 case GEM_ESCAPE: // redraw actionbar
739 // do nothing; these are handled in DispatchEvent due to tab having two functions
740 break;
741 case '0':
742 game->SelectActor( NULL, false, SELECT_NORMAL );
743 i = game->GetPartySize(false)/2+1;
744 while(i--) {
745 SelectActor(i, true);
746 }
747 break;
748 case '-':
749 game->SelectActor( NULL, true, SELECT_NORMAL );
750 i = game->GetPartySize(false)/2+1;
751 while(i--) {
752 SelectActor(i, false);
753 }
754 break;
755 case '=':
756 SelectActor(-1);
757 break;
758 case '7': // 1 & 2
759 case '8': // 3 & 4
760 case '9': // 5 & 6
761 // We do not handle the range 1..6, these are handled as hotkeys
762 // for the portrait buttons, so that they remain working when the
763 // inventory screen is open.
764 game->SelectActor( NULL, false, SELECT_NORMAL );
765 i = game->GetPartySize(false);
766 pc = 2*(keycode - '6')-1;
767 if (pc >= i) {
768 SelectActor(i, true);
769 break;
770 }
771 SelectActor(pc, true);
772 SelectActor(pc+1, true);
773 break;
774 default:
775 if (!core->GetKeyMap()->ResolveKey(Key.keycode, 0)) {
776 game->SendHotKey(toupper(Key.character));
777 return View::OnKeyPress(Key, 0);
778 }
779 break;
780 }
781 }
782 return true;
783 }
784
785 //Select (or deselect) a new actor (or actors)
SelectActor(int whom,int type)786 void GameControl::SelectActor(int whom, int type)
787 {
788 Game* game = core->GetGame();
789 if (whom==-1) {
790 game->SelectActor( NULL, true, SELECT_NORMAL );
791 return;
792 }
793
794 /* doesn't fall through here */
795 Actor* actor = game->FindPC( whom );
796 if (!actor)
797 return;
798
799 if (type==0) {
800 game->SelectActor( actor, false, SELECT_NORMAL );
801 return;
802 }
803 if (type==1) {
804 game->SelectActor( actor, true, SELECT_NORMAL );
805 return;
806 }
807
808 bool was_selected = actor->IsSelected();
809 if (game->SelectActor( actor, true, SELECT_REPLACE )) {
810 if (was_selected || (ScreenFlags & SF_ALWAYSCENTER)) {
811 ScreenFlags |= SF_CENTERONACTOR;
812 }
813 }
814 }
815
816 //Effect for the ctrl-r cheatkey (resurrect)
817 static EffectRef heal_ref = { "CurrentHPModifier", -1 };
818 static EffectRef damage_ref = { "Damage", -1 };
819
820 /** Key Release Event */
OnKeyRelease(const KeyboardEvent & Key,unsigned short Mod)821 bool GameControl::OnKeyRelease(const KeyboardEvent& Key, unsigned short Mod)
822 {
823 Point gameMousePos = GameMousePos();
824 //cheatkeys with ctrl-
825 if (Mod & GEM_MOD_CTRL) {
826 if (!core->CheatEnabled()) {
827 return false;
828 }
829 Game* game = core->GetGame();
830 Map* area = game->GetCurrentArea( );
831 if (!area)
832 return false;
833 Actor *lastActor = area->GetActorByGlobalID(lastActorID);
834 switch (Key.character) {
835 case 'a': //switches through the avatar animations
836 if (lastActor) {
837 lastActor->GetNextAnimation();
838 }
839 break;
840 // b
841 case 'c': //force cast a hardcoded spell
842 //caster is the last selected actor
843 //target is the door/actor currently under the pointer
844 if (game->selected.size() > 0) {
845 Actor *src = game->selected[0];
846 Scriptable *target = lastActor;
847 if (overDoor) {
848 target = overDoor;
849 }
850 if (target) {
851 src->SetSpellResRef(TestSpell);
852 src->CastSpell(target, false);
853 if (src->LastSpellTarget) {
854 src->CastSpellEnd(0, 0);
855 } else {
856 src->CastSpellPointEnd(0, 0);
857 }
858 }
859 }
860 break;
861 case 'd': //detect a trap or door
862 if (overInfoPoint) {
863 overInfoPoint->DetectTrap(256, lastActorID);
864 }
865 if (overContainer) {
866 overContainer->DetectTrap(256, lastActorID);
867 }
868 if (overDoor) {
869 overDoor->TryDetectSecret(256, lastActorID);
870 overDoor->DetectTrap(256, lastActorID);
871 }
872 break;
873 case 'e':// reverses pc order (useful for parties bigger than 6)
874 game->ReversePCs();
875 break;
876 // f
877 case 'g'://shows loaded areas and other game information
878 game->dump();
879 break;
880 // h
881 case 'i'://interact trigger (from the original game)
882 if (!lastActor) {
883 lastActor = area->GetActor( gameMousePos, GA_DEFAULT);
884 }
885 if (lastActor && !(lastActor->GetStat(IE_MC_FLAGS)&MC_EXPORTABLE)) {
886 int size = game->GetPartySize(true);
887 if (size < 2 || lastActor->GetCurrentArea() != game->GetCurrentArea()) break;
888 for (int i = core->Roll(1, size, 0); i < 2*size; i++) {
889 const Actor *target = game->GetPC(i % size, true);
890 if (target == lastActor) continue;
891 if (target->GetStat(IE_MC_FLAGS) & MC_EXPORTABLE) continue; //not NPC
892 lastActor->HandleInteractV1(target);
893 break;
894 }
895 }
896 break;
897 case 'j': //teleports the selected actors
898 for (Actor *selectee : game->selected) {
899 selectee->ClearActions();
900 MoveBetweenAreasCore(selectee, core->GetGame()->CurrentArea, gameMousePos, -1, true);
901 }
902 break;
903 case 'k': //kicks out actor
904 if (lastActor && lastActor->InParty) {
905 lastActor->Stop();
906 lastActor->AddAction( GenerateAction("LeaveParty()") );
907 }
908 break;
909 case 'l': //play an animation (vvc/bam) over an actor
910 //the original engine was able to swap through all animations
911 if (lastActor) {
912 lastActor->AddAnimation("S056ICBL", 0, 0, 0);
913 }
914 break;
915 case 'M':
916 if (!lastActor) {
917 lastActor = area->GetActor( gameMousePos, GA_DEFAULT);
918 }
919 if (!lastActor) {
920 // ValidTarget never returns immobile targets, making debugging a nightmare
921 // so if we don't have an actor, we make really really sure by checking manually
922 unsigned int count = area->GetActorCount(true);
923 while (count--) {
924 const Actor *actor = area->GetActor(count, true);
925 if (actor->IsOver(gameMousePos)) {
926 actor->GetAnims()->DebugDump();
927 }
928 }
929 }
930 if (lastActor) {
931 lastActor->GetAnims()->DebugDump();
932 break;
933 }
934 break;
935 case 'm': //prints a debug dump (ctrl-m in the original game too)
936 if (!lastActor) {
937 lastActor = area->GetActor( gameMousePos, GA_DEFAULT);
938 }
939 if (!lastActor) {
940 // ValidTarget never returns immobile targets, making debugging a nightmare
941 // so if we don't have an actor, we make really really sure by checking manually
942 unsigned int count = area->GetActorCount(true);
943 while (count--) {
944 const Actor *actor = area->GetActor(count, true);
945 if (actor->IsOver(gameMousePos)) {
946 actor->dump();
947 }
948 }
949 }
950 if (lastActor) {
951 lastActor->dump();
952 break;
953 }
954 if (overDoor) {
955 overDoor->dump();
956 break;
957 }
958 if (overContainer) {
959 overContainer->dump();
960 break;
961 }
962 if (overInfoPoint) {
963 overInfoPoint->dump();
964 break;
965 }
966 core->GetGame()->GetCurrentArea()->dump(false);
967 break;
968 case 'n': //prints a list of all the live actors in the area
969 core->GetGame()->GetCurrentArea()->dump(true);
970 break;
971 // o
972 case 'p': //center on actor
973 ScreenFlags|=SF_CENTERONACTOR;
974 ScreenFlags^=SF_ALWAYSCENTER;
975 break;
976 case 'q': //joins actor to the party
977 if (lastActor && !lastActor->InParty) {
978 lastActor->Stop();
979 lastActor->AddAction( GenerateAction("JoinParty()") );
980 }
981 break;
982 case 'r'://resurrects actor
983 if (!lastActor) {
984 lastActor = area->GetActor( gameMousePos, GA_DEFAULT);
985 }
986 if (lastActor) {
987 Effect *fx = EffectQueue::CreateEffect(heal_ref, lastActor->GetStat(IE_MAXHITPOINTS), 0x30001, FX_DURATION_INSTANT_PERMANENT);
988 if (fx) {
989 core->ApplyEffect(fx, lastActor, lastActor);
990 }
991 delete fx;
992 }
993 break;
994 case 's': //switches through the stance animations
995 if (lastActor) {
996 lastActor->GetNextStance();
997 }
998 break;
999 case 't': // advances time by 1 hour
1000 game->AdvanceTime(core->Time.hour_size);
1001 //refresh gui here once we got it
1002 break;
1003 // u
1004 case 'V': //
1005 core->GetDictionary()->DebugDump();
1006 break;
1007 case 'v': //marks some of the map visited (random vision distance)
1008 area->ExploreMapChunk( gameMousePos, RAND(0,29), 1 );
1009 break;
1010 case 'w': // consolidates found ground piles under the pointed pc
1011 area->MoveVisibleGroundPiles(gameMousePos);
1012 break;
1013 case 'x': // shows coordinates on the map
1014 Log(MESSAGE, "GameControl", "Position: %s [%d.%d]", area->GetScriptName(), gameMousePos.x, gameMousePos.y );
1015 break;
1016 case 'Y': // damages all enemies by 300 (resistances apply)
1017 // mwahaha!
1018 {
1019 Effect *newfx = EffectQueue::CreateEffect(damage_ref, 300, DAMAGE_MAGIC<<16, FX_DURATION_INSTANT_PERMANENT);
1020 int i = area->GetActorCount(false);
1021 while(i--) {
1022 Actor *victim = area->GetActor(i, false);
1023 if (victim->Modified[IE_EA] == EA_ENEMY) {
1024 core->ApplyEffect(newfx, victim, victim);
1025 }
1026 }
1027 delete newfx;
1028 }
1029 // fallthrough
1030 case 'y': //kills actor
1031 if (lastActor) {
1032 //using action so the actor is killed
1033 //correctly (synchronisation)
1034 lastActor->Stop();
1035
1036 Effect *newfx;
1037 newfx = EffectQueue::CreateEffect(damage_ref, 300, DAMAGE_MAGIC<<16, FX_DURATION_INSTANT_PERMANENT);
1038 core->ApplyEffect(newfx, lastActor, lastActor);
1039 delete newfx;
1040 if (! (lastActor->GetInternalFlag() & IF_REALLYDIED)) {
1041 newfx = EffectQueue::CreateEffect(damage_ref, 300, DAMAGE_ACID<<16, FX_DURATION_INSTANT_PERMANENT);
1042 core->ApplyEffect(newfx, lastActor, lastActor);
1043 delete newfx;
1044 newfx = EffectQueue::CreateEffect(damage_ref, 300, DAMAGE_CRUSHING<<16, FX_DURATION_INSTANT_PERMANENT);
1045 core->ApplyEffect(newfx, lastActor, lastActor);
1046 delete newfx;
1047 }
1048 } else if (overContainer) {
1049 overContainer->SetContainerLocked(0);
1050 } else if (overDoor) {
1051 overDoor->SetDoorLocked(0,0);
1052 }
1053 break;
1054 case 'z': //shift through the avatar animations backward
1055 if (lastActor) {
1056 lastActor->GetPrevAnimation();
1057 }
1058 break;
1059 case '1': //change paperdoll armour level
1060 if (! lastActor)
1061 break;
1062 lastActor->NewStat(IE_ARMOR_TYPE,1,MOD_ADDITIVE);
1063 break;
1064 case '4': //show all traps and infopoints
1065 DebugFlags ^= DEBUG_SHOW_INFOPOINTS;
1066 Log(MESSAGE, "GameControl", "Show traps and infopoints %s", DebugFlags & DEBUG_SHOW_INFOPOINTS ? "ON" : "OFF");
1067 break;
1068 case '5':
1069 {
1070 constexpr int flagCnt = 6;
1071 static uint32_t wallFlags[flagCnt]{
1072 0,
1073 DEBUG_SHOW_WALLS_ALL,
1074 DEBUG_SHOW_DOORS_SECRET,
1075 DEBUG_SHOW_DOORS_DISABLED,
1076 DEBUG_SHOW_WALLS,
1077 DEBUG_SHOW_WALLS_ANIM_COVER
1078 };
1079 static uint32_t flagIdx = 0;
1080 DebugFlags &= ~DEBUG_SHOW_WALLS_ALL;
1081 DebugFlags |= wallFlags[flagIdx++];
1082 flagIdx = flagIdx % flagCnt;
1083 }
1084 break;
1085 case '6': //show the lightmap
1086 DebugFlags ^= DEBUG_SHOW_LIGHTMAP;
1087 Log(MESSAGE, "GameControl", "Show lightmap %s", DebugFlags & DEBUG_SHOW_LIGHTMAP ? "ON" : "OFF");
1088 break;
1089 case '7': //toggles fog of war
1090 case '8': // searchmap debugging
1091 {
1092 constexpr int flagCnt = 4;
1093 static uint32_t fogFlags[flagCnt]{
1094 0,
1095 DEBUG_SHOW_FOG_ALL,
1096 DEBUG_SHOW_FOG_INVISIBLE,
1097 DEBUG_SHOW_FOG_UNEXPLORED
1098 };
1099 static uint32_t flagIdx = 0;
1100
1101 DebugFlags &= ~DEBUG_SHOW_FOG_ALL;
1102 if (Key.character == '8') {
1103 DebugFlags ^= DEBUG_SHOW_SEARCHMAP;
1104 flagIdx = (DebugFlags & DEBUG_SHOW_SEARCHMAP) ? 1 : 0;
1105 Log(MESSAGE, "GameControl", "Show searchmap %s", DebugFlags & DEBUG_SHOW_SEARCHMAP ? "ON" : "OFF");
1106 }
1107
1108 DebugFlags |= fogFlags[flagIdx++];
1109 flagIdx = flagIdx % flagCnt;
1110 }
1111 break;
1112 }
1113 return true; //return from cheatkeys
1114 }
1115 const Game* game = core->GetGame();
1116 switch (Key.keycode) {
1117 //FIXME: move these to guiscript
1118 case ' ': //soft pause
1119 core->TogglePause();
1120 break;
1121 case GEM_ALT: //alt key (shows containers)
1122 #ifdef ANDROID
1123 case 'c': // show containers in ANDROID, GEM_ALT is not possible to use
1124 #endif
1125 DebugFlags &= ~(DEBUG_SHOW_CONTAINERS|DEBUG_SHOW_DOORS);
1126 break;
1127 case GEM_TAB: // remove overhead partymember hp/maxhp
1128 for (int pm = 0; pm < game->GetPartySize(false); pm++) {
1129 Actor *pc = game->GetPC(pm, true);
1130 if (!pc) continue;
1131 pc->DisplayOverheadText(false);
1132 }
1133 break;
1134 default:
1135 return false;
1136 }
1137 return true;
1138 }
1139
TooltipText() const1140 String GameControl::TooltipText() const {
1141 const Map* area = CurrentArea();
1142 if (area == NULL) {
1143 return View::TooltipText();
1144 }
1145
1146 const Point& gameMousePos = GameMousePos();
1147 if (!area->IsVisible(gameMousePos)) {
1148 return View::TooltipText();
1149 }
1150
1151 const Actor* actor = area->GetActor(gameMousePos, GA_NO_DEAD|GA_NO_UNSCHEDULED);
1152 if (actor == NULL) {
1153 return View::TooltipText();
1154 }
1155
1156 static String tip; // only one game control and we return a const& so cant be temporary.
1157 const char *name = actor->GetName(-1);
1158 // FIME: make the actor name a String instead
1159 String* wname = StringFromCString(name);
1160 if (wname) {
1161 tip = *wname;
1162 delete wname;
1163 }
1164
1165 int hp = actor->GetStat(IE_HITPOINTS);
1166 int maxhp = actor->GetStat(IE_MAXHITPOINTS);
1167
1168 if (actor->InParty) {
1169 if (core->HasFeature(GF_ONSCREEN_TEXT)) {
1170 tip += L": ";
1171 } else {
1172 tip += L"\n";
1173 }
1174
1175 if (actor->HasVisibleHP()) {
1176 wchar_t hpstring[20];
1177 swprintf(hpstring, 20, L"%d/%d", hp, maxhp);
1178 tip += hpstring;
1179 } else {
1180 tip += L"?";
1181 }
1182 } else {
1183 // a guess at a neutral check
1184 bool enemy = actor->GetStat(IE_EA) != EA_NEUTRAL;
1185 // test for an injured string being present for this game
1186 int strindex = displaymsg->GetStringReference(STR_UNINJURED);
1187 if (enemy && strindex != -1) {
1188 // non-neutral, not in party: display injured string
1189 // these boundaries are just a guess
1190 if (hp == maxhp) {
1191 strindex = STR_UNINJURED;
1192 } else if (hp > (maxhp*3)/4) {
1193 strindex = STR_INJURED1;
1194 } else if (hp > maxhp/2) {
1195 strindex = STR_INJURED2;
1196 } else if (hp > maxhp/3) {
1197 strindex = STR_INJURED3;
1198 } else {
1199 strindex = STR_INJURED4;
1200 }
1201 strindex = displaymsg->GetStringReference(strindex);
1202 String* injuredstring = core->GetString(strindex, 0);
1203 assert(injuredstring); // we just "checked" for these (by checking for STR_UNINJURED)
1204 tip += L"\n" + *injuredstring;
1205 delete injuredstring;
1206 }
1207 }
1208
1209 return tip;
1210 }
1211
1212 //returns the appropriate cursor over an active region (trap, infopoint, travel region)
GetCursorOverInfoPoint(const InfoPoint * overInfoPoint) const1213 int GameControl::GetCursorOverInfoPoint(const InfoPoint *overInfoPoint) const
1214 {
1215 if (target_mode == TARGET_MODE_PICK) {
1216 if (overInfoPoint->VisibleTrap(0)) {
1217 return IE_CURSOR_TRAP;
1218 }
1219
1220 return IE_CURSOR_STEALTH|IE_CURSOR_GRAY;
1221 }
1222 // traps always display a walk cursor?
1223 if (overInfoPoint->Type == ST_PROXIMITY) {
1224 return IE_CURSOR_WALK;
1225 }
1226 return overInfoPoint->Cursor;
1227 }
1228
1229 //returns the appropriate cursor over a door
GetCursorOverDoor(const Door * overDoor) const1230 int GameControl::GetCursorOverDoor(const Door *overDoor) const
1231 {
1232 if (!overDoor->Visible()) {
1233 if (target_mode == TARGET_MODE_NONE) {
1234 // most secret doors are in walls, so default to the blocked cursor to not give them away
1235 // iwd ar6010 table/door/puzzle is walkable, secret and undetectable
1236 const Map *area = overDoor->GetCurrentArea();
1237 return area->GetCursor(overDoor->Pos);
1238 } else {
1239 return lastCursor|IE_CURSOR_GRAY;
1240 }
1241 }
1242 if (target_mode == TARGET_MODE_PICK) {
1243 if (overDoor->VisibleTrap(0)) {
1244 return IE_CURSOR_TRAP;
1245 }
1246 if (overDoor->Flags & DOOR_LOCKED) {
1247 return IE_CURSOR_LOCK;
1248 }
1249
1250 return IE_CURSOR_STEALTH|IE_CURSOR_GRAY;
1251 }
1252 return overDoor->Cursor;
1253 }
1254
1255 //returns the appropriate cursor over a container (or pile)
GetCursorOverContainer(const Container * overContainer) const1256 int GameControl::GetCursorOverContainer(const Container *overContainer) const
1257 {
1258 if (overContainer->Flags & CONT_DISABLED) {
1259 return lastCursor;
1260 }
1261
1262 if (target_mode == TARGET_MODE_PICK) {
1263 if (overContainer->VisibleTrap(0)) {
1264 return IE_CURSOR_TRAP;
1265 }
1266 if (overContainer->Flags & CONT_LOCKED) {
1267 return IE_CURSOR_LOCK2;
1268 }
1269
1270 return IE_CURSOR_STEALTH|IE_CURSOR_GRAY;
1271 }
1272 return IE_CURSOR_TAKE;
1273 }
1274
GetTargetActionCursor() const1275 Holder<Sprite2D> GameControl::GetTargetActionCursor() const
1276 {
1277 int curIdx = -1;
1278 switch(target_mode) {
1279 case TARGET_MODE_TALK:
1280 curIdx = IE_CURSOR_TALK;
1281 break;
1282 case TARGET_MODE_ATTACK:
1283 curIdx = IE_CURSOR_ATTACK;
1284 break;
1285 case TARGET_MODE_CAST:
1286 curIdx = IE_CURSOR_CAST;
1287 break;
1288 case TARGET_MODE_DEFEND:
1289 curIdx = IE_CURSOR_DEFEND;
1290 break;
1291 case TARGET_MODE_PICK:
1292 curIdx = IE_CURSOR_PICK;
1293 break;
1294 }
1295 if (curIdx != -1) {
1296 return core->Cursors[curIdx];
1297 }
1298 return nullptr;
1299 }
1300
Cursor() const1301 Holder<Sprite2D> GameControl::Cursor() const
1302 {
1303 Holder<Sprite2D> cursor = View::Cursor();
1304 if (cursor == NULL && lastCursor != IE_CURSOR_INVALID) {
1305 int idx = lastCursor & ~IE_CURSOR_GRAY;
1306 if (EventMgr::MouseDown()) {
1307 ++idx;
1308 }
1309 cursor = core->Cursors[idx];
1310 }
1311 return cursor;
1312 }
1313
1314 /** Mouse Over Event */
OnMouseOver(const MouseEvent &)1315 bool GameControl::OnMouseOver(const MouseEvent& /*me*/)
1316 {
1317 const Map* area = CurrentArea();
1318 if (area == NULL) {
1319 return false;
1320 }
1321
1322 Actor *lastActor = area->GetActorByGlobalID(lastActorID);
1323 if (lastActor) {
1324 lastActor->SetOver( false );
1325 }
1326
1327 Point gameMousePos = GameMousePos();
1328 // let us target party members even if they are invisible
1329 lastActor = area->GetActor(gameMousePos, GA_NO_DEAD|GA_NO_UNSCHEDULED);
1330 if (lastActor && lastActor->Modified[IE_EA] >= EA_CONTROLLED) {
1331 if (!lastActor->ValidTarget(target_types) || !area->IsVisible(gameMousePos)) {
1332 lastActor = NULL;
1333 }
1334 }
1335
1336 if ((target_types & GA_NO_SELF) && lastActor ) {
1337 if (lastActor == core->GetFirstSelectedActor()) {
1338 lastActor=NULL;
1339 }
1340 }
1341
1342 SetLastActor(lastActor);
1343
1344 return true;
1345 }
1346
UpdateCursor()1347 void GameControl::UpdateCursor()
1348 {
1349 const Map *area = CurrentArea();
1350 if (area == NULL) {
1351 lastCursor = IE_CURSOR_BLOCKED;
1352 return;
1353 }
1354
1355 Point gameMousePos = GameMousePos();
1356 int nextCursor = area->GetCursor( gameMousePos );
1357 //make the invisible area really invisible
1358 if (nextCursor == IE_CURSOR_INVALID) {
1359 lastCursor = IE_CURSOR_BLOCKED;
1360 return;
1361 }
1362
1363 if (overDoor) {
1364 overDoor->Highlight = false;
1365 }
1366 if (overContainer) {
1367 overContainer->Highlight = false;
1368 }
1369
1370 overDoor = area->TMap->GetDoor(gameMousePos);
1371 // ignore infopoints and containers beneath doors
1372 if (overDoor) {
1373 if (overDoor->Visible()) {
1374 nextCursor = GetCursorOverDoor(overDoor);
1375 } else {
1376 overDoor = nullptr;
1377 }
1378 } else {
1379 overInfoPoint = area->TMap->GetInfoPoint(gameMousePos, true);
1380 if (overInfoPoint) {
1381 nextCursor = GetCursorOverInfoPoint(overInfoPoint);
1382 }
1383 // recheck in case the position was different, resulting in a new isVisible check
1384 if (nextCursor == IE_CURSOR_INVALID) {
1385 lastCursor = IE_CURSOR_BLOCKED;
1386 return;
1387 }
1388
1389 // don't allow summons to try travelling (alone), since it causes tons of loading
1390 if (nextCursor == IE_CURSOR_TRAVEL && core->GetGame()->OnlyNPCsSelected()) {
1391 lastCursor = IE_CURSOR_BLOCKED;
1392 return;
1393 }
1394
1395 overContainer = area->TMap->GetContainer( gameMousePos );
1396 }
1397
1398 if (overContainer) {
1399 nextCursor = GetCursorOverContainer(overContainer);
1400 }
1401 // recheck in case the position was different, resulting in a new isVisible check
1402 // fixes bg2 long block door in ar0801 above vamp beds, crashing on mouseover (too big)
1403 if (nextCursor == IE_CURSOR_INVALID) {
1404 lastCursor = IE_CURSOR_BLOCKED;
1405 return;
1406 }
1407
1408 const Actor *lastActor = area->GetActorByGlobalID(lastActorID);
1409 if (lastActor) {
1410 // don't change the cursor for birds
1411 if (lastActor->GetStat(IE_DONOTJUMP) == DNJ_BIRD) return;
1412
1413 ieDword type = lastActor->GetStat(IE_EA);
1414 if (type >= EA_EVILCUTOFF || type == EA_GOODBUTRED) {
1415 nextCursor = IE_CURSOR_ATTACK;
1416 } else if ( type > EA_CHARMED ) {
1417 nextCursor = IE_CURSOR_TALK;
1418 //don't let the pc to talk to frozen/stoned creatures
1419 ieDword state = lastActor->GetStat(IE_STATE_ID);
1420 if (state & (STATE_CANTMOVE^STATE_SLEEP)) {
1421 nextCursor |= IE_CURSOR_GRAY;
1422 }
1423 } else {
1424 nextCursor = IE_CURSOR_NORMAL;
1425 }
1426 }
1427
1428 if (target_mode == TARGET_MODE_TALK) {
1429 nextCursor = IE_CURSOR_TALK;
1430 if (!lastActor) {
1431 nextCursor |= IE_CURSOR_GRAY;
1432 } else {
1433 //don't let the pc to talk to frozen/stoned creatures
1434 ieDword state = lastActor->GetStat(IE_STATE_ID);
1435 if (state & (STATE_CANTMOVE^STATE_SLEEP)) {
1436 nextCursor |= IE_CURSOR_GRAY;
1437 }
1438 }
1439 } else if (target_mode == TARGET_MODE_ATTACK) {
1440 nextCursor = IE_CURSOR_ATTACK;
1441 if (!overDoor && !lastActor && !overContainer) {
1442 nextCursor |= IE_CURSOR_GRAY;
1443 }
1444 } else if (target_mode == TARGET_MODE_CAST) {
1445 nextCursor = IE_CURSOR_CAST;
1446 //point is always valid
1447 if (!(target_types & GA_POINT)) {
1448 if(!lastActor) {
1449 nextCursor |= IE_CURSOR_GRAY;
1450 }
1451 }
1452 } else if (target_mode == TARGET_MODE_DEFEND) {
1453 nextCursor = IE_CURSOR_DEFEND;
1454 if(!lastActor) {
1455 nextCursor |= IE_CURSOR_GRAY;
1456 }
1457 } else if (target_mode == TARGET_MODE_PICK) {
1458 if (lastActor) {
1459 nextCursor = IE_CURSOR_PICK;
1460 } else {
1461 if (!overContainer && !overDoor && !overInfoPoint) {
1462 nextCursor = IE_CURSOR_STEALTH|IE_CURSOR_GRAY;
1463 }
1464 }
1465 }
1466
1467 if (nextCursor >= 0) {
1468 lastCursor = nextCursor ;
1469 }
1470 }
1471
IsDisabledCursor() const1472 bool GameControl::IsDisabledCursor() const
1473 {
1474 bool isDisabled = View::IsDisabledCursor();
1475 if (lastCursor != IE_CURSOR_INVALID)
1476 isDisabled |= bool(lastCursor&IE_CURSOR_GRAY);
1477
1478 return isDisabled;
1479 }
1480
OnMouseDrag(const MouseEvent & me)1481 bool GameControl::OnMouseDrag(const MouseEvent& me)
1482 {
1483 if (me.ButtonState(GEM_MB_MIDDLE)) {
1484 Scroll(me.Delta());
1485 return true;
1486 }
1487
1488 if (me.ButtonState(GEM_MB_MENU)) {
1489 InitFormation(gameClickPoint);
1490 return true;
1491 }
1492
1493 if (target_mode != TARGET_MODE_NONE) {
1494 // we are in a target mode; nothing here applies
1495 return true;
1496 }
1497
1498 if (overDoor || overContainer || overInfoPoint) {
1499 return true;
1500 }
1501
1502 if (me.ButtonState(GEM_MB_ACTION) && !isFormationRotation) {
1503 isSelectionRect = true;
1504 SetCursor(core->Cursors[IE_CURSOR_PRESSED]);
1505 }
1506
1507 return true;
1508 }
1509
OnTouchDown(const TouchEvent & te,unsigned short mod)1510 bool GameControl::OnTouchDown(const TouchEvent& te, unsigned short mod)
1511 {
1512 if (EventMgr::NumFingersDown() == 2) {
1513 // container highlights
1514 DebugFlags |= DEBUG_SHOW_CONTAINERS|DEBUG_SHOW_DOORS;
1515 }
1516
1517 // TODO: check pressure to distinguish between tooltip and HP modes
1518 if (View::OnTouchDown(te, mod)) {
1519 if (te.numFingers == 1) {
1520 screenMousePos = te.Pos();
1521
1522 // if an actor is being touched show HP
1523 Actor* actor = GetLastActor();
1524 if (actor) {
1525 actor->DisplayHeadHPRatio();
1526 }
1527 }
1528 return true;
1529 }
1530 return false;
1531 }
1532
OnTouchUp(const TouchEvent & te,unsigned short mod)1533 bool GameControl::OnTouchUp(const TouchEvent& te, unsigned short mod)
1534 {
1535 if (EventMgr::ModState(GEM_MOD_ALT) == false) {
1536 DebugFlags &= ~(DEBUG_SHOW_CONTAINERS|DEBUG_SHOW_DOORS);
1537 }
1538
1539 return View::OnTouchUp(te, mod);
1540 }
1541
OnTouchGesture(const GestureEvent & gesture)1542 bool GameControl::OnTouchGesture(const GestureEvent& gesture)
1543 {
1544 if (gesture.numFingers == 1) {
1545 if (target_mode != TARGET_MODE_NONE) {
1546 // we are in a target mode; nothing here applies
1547 return true;
1548 }
1549
1550 screenMousePos = gesture.Pos();
1551 isSelectionRect = true;
1552 } else if (gesture.numFingers == 2) {
1553 if (gesture.dTheta < -0.2 || gesture.dTheta > 0.2) { // TODO: actually figure out a good number
1554 if (EventMgr::ModState(GEM_MOD_ALT) == false) {
1555 DebugFlags &= ~(DEBUG_SHOW_CONTAINERS|DEBUG_SHOW_DOORS);
1556 }
1557
1558 isSelectionRect = false;
1559
1560 if (core->GetGame()->selected.size() <= 1) {
1561 isFormationRotation = false;
1562 } else {
1563 screenMousePos = gesture.fingers[1].Pos();
1564 InitFormation(screenMousePos);
1565 }
1566 } else { // scroll viewport
1567 MoveViewportTo( vpOrigin - gesture.Delta(), false );
1568 }
1569 } else if (gesture.numFingers == 3) { // keyboard/console
1570 Video* video = core->GetVideoDriver();
1571
1572 enum SWIPE {DOWN = -1, NONE = 0, UP = 1};
1573 SWIPE swipe = NONE;
1574 if (gesture.deltaY < -EventMgr::mouseDragRadius) {
1575 swipe = UP;
1576 } else if (gesture.deltaY > EventMgr::mouseDragRadius) {
1577 swipe = DOWN;
1578 }
1579
1580 Window* consoleWin = GemRB::GetWindow(0, "WIN_CON");
1581 assert(consoleWin);
1582
1583 // swipe up to show the keyboard
1584 // if the kwyboard is showing swipe up to access console
1585 // swipe down to hide both keyboard and console
1586 switch (swipe) {
1587 case DOWN:
1588 consoleWin->Close();
1589 video->StopTextInput();
1590 consoleWin->Close();
1591 break;
1592 case UP:
1593 if (video->InTextInput()) {
1594 consoleWin->Focus();
1595 }
1596 video->StartTextInput();
1597 break;
1598 case NONE:
1599 break;
1600 }
1601 }
1602 return true;
1603 }
1604
GameMousePos() const1605 Point GameControl::GameMousePos() const
1606 {
1607 return vpOrigin + ConvertPointFromScreen(screenMousePos);
1608 }
1609
OnGlobalMouseMove(const Event & e)1610 bool GameControl::OnGlobalMouseMove(const Event& e)
1611 {
1612 // we are using the window->IsDisabled on purpose
1613 // to avoid bugs, we are disabling the window when we open one of the "top window"s
1614 // GC->IsDisabled is for other uses
1615 if (!window || window->IsDisabled() || (Flags()&IgnoreEvents)) {
1616 return false;
1617 }
1618
1619 if (e.mouse.ButtonState(GEM_MB_MIDDLE)) {
1620 // if we are panning the map don't scroll from being at the edge
1621 moveX = 0;
1622 moveY = 0;
1623 return false;
1624 }
1625
1626 #define SCROLL_AREA_WIDTH 5
1627 Region mask = frame;
1628 mask.x += SCROLL_AREA_WIDTH;
1629 mask.y += SCROLL_AREA_WIDTH;
1630 mask.w -= SCROLL_AREA_WIDTH*2;
1631 mask.h -= SCROLL_AREA_WIDTH*2;
1632 #undef SCROLL_AREA_WIDTH
1633
1634 screenMousePos = e.mouse.Pos();
1635 Point mp = ConvertPointFromScreen(screenMousePos);
1636 int mousescrollspd = core->GetMouseScrollSpeed();
1637
1638 if (mp.x < mask.x) {
1639 moveX = -mousescrollspd;
1640 } else if (mp.x > mask.x + mask.w) {
1641 moveX = mousescrollspd;
1642 } else {
1643 moveX = 0;
1644 }
1645
1646 if (mp.y < mask.y) {
1647 moveY = -mousescrollspd;
1648 } else if (mp.y > mask.y + mask.h) {
1649 moveY = mousescrollspd;
1650 } else {
1651 moveY = 0;
1652 }
1653
1654 if (moveX || moveY) {
1655 // cancel any scripted moves
1656 // we are not in dialog or cutscene mode anymore
1657 // and the user is attempting to move the viewport
1658 core->timer.SetMoveViewPort(vpOrigin, 0, false);
1659 }
1660
1661 return true;
1662 }
1663
MoveViewportUnlockedTo(Point p,bool center)1664 void GameControl::MoveViewportUnlockedTo(Point p, bool center)
1665 {
1666 if (center) {
1667 p.x -= frame.w / 2;
1668 p.y -= frame.h / 2;
1669 }
1670
1671 core->GetAudioDrv()->UpdateListenerPos(p.x + frame.w / 2, p.y + frame.h / 2);
1672 vpOrigin = p;
1673 }
1674
MoveViewportTo(Point p,bool center,int speed)1675 bool GameControl::MoveViewportTo(Point p, bool center, int speed)
1676 {
1677 const Map* area = CurrentArea();
1678 bool canMove = area != NULL;
1679
1680 if (updateVPTimer && speed) {
1681 updateVPTimer = false;
1682 core->timer.SetMoveViewPort(p, speed, center);
1683 } else if (canMove && p != vpOrigin) {
1684 updateVPTimer = true;
1685
1686 Size mapsize = area->GetSize();
1687
1688 if (center) {
1689 p.x -= frame.w/2;
1690 p.y -= frame.h/2;
1691 }
1692
1693 // TODO: make the overflow more dynamic
1694 if (frame.w >= mapsize.w) {
1695 p.x = (mapsize.w - frame.w)/2;
1696 canMove = false;
1697 } else if (p.x + frame.w >= mapsize.w + 64) {
1698 p.x = mapsize.w - frame.w + 64;
1699 canMove = false;
1700 } else if (p.x < -64) {
1701 p.x = -64;
1702 canMove = false;
1703 }
1704
1705 Region mwinframe;
1706 const TextArea* mta = core->GetMessageTextArea();
1707 if (mta) {
1708 mwinframe = mta->GetWindow()->Frame();
1709 }
1710
1711 constexpr int padding = 50;
1712 if (frame.h >= mapsize.h + mwinframe.h + padding) {
1713 p.y = (mapsize.h - frame.h)/2 + padding;
1714 canMove = false;
1715 } else if (p.y + frame.h >= mapsize.h + mwinframe.h + padding) {
1716 p.y = mapsize.h - frame.h + mwinframe.h + padding;
1717 canMove = false;
1718 } else if (p.y < 0) {
1719 p.y = 0;
1720 canMove = false;
1721 }
1722
1723 MoveViewportUnlockedTo(p, false); // we already handled centering
1724 } else {
1725 updateVPTimer = true;
1726 canMove = (p == vpOrigin);
1727 }
1728
1729 return canMove;
1730 }
1731
Viewport() const1732 Region GameControl::Viewport() const
1733 {
1734 return Region(vpOrigin, frame.Dimensions());
1735 }
1736
1737 //generate action code for source actor to try to attack a target
TryToAttack(Actor * source,const Actor * tgt) const1738 void GameControl::TryToAttack(Actor *source, const Actor *tgt) const
1739 {
1740 if (source->GetStat(IE_SEX) == SEX_ILLUSION) return;
1741 source->CommandActor(GenerateActionDirect( "NIDSpecial3()", tgt));
1742 }
1743
1744 //generate action code for source actor to try to defend a target
TryToDefend(Actor * source,const Actor * tgt) const1745 void GameControl::TryToDefend(Actor *source, const Actor *tgt) const
1746 {
1747 source->SetModal(MS_NONE);
1748 source->CommandActor(GenerateActionDirect( "NIDSpecial4()", tgt));
1749 }
1750
1751 // generate action code for source actor to try to pick pockets of a target (if an actor)
1752 // else if door/container try to pick a lock/disable trap
1753 // The -1 flag is a placeholder for dynamic target IDs
TryToPick(Actor * source,const Scriptable * tgt) const1754 void GameControl::TryToPick(Actor *source, const Scriptable *tgt) const
1755 {
1756 source->SetModal(MS_NONE);
1757 const char* cmdString = NULL;
1758 switch (tgt->Type) {
1759 case ST_ACTOR:
1760 cmdString = "PickPockets([-1])";
1761 break;
1762 case ST_DOOR:
1763 case ST_CONTAINER:
1764 if (((const Highlightable *) tgt)->Trapped && ((const Highlightable *) tgt)->TrapDetected) {
1765 cmdString = "RemoveTraps([-1])";
1766 } else {
1767 cmdString = "PickLock([-1])";
1768 }
1769 break;
1770 default:
1771 Log(ERROR, "GameControl", "Invalid pick target of type %d", tgt->Type);
1772 return;
1773 }
1774 source->CommandActor(GenerateActionDirect(cmdString, tgt));
1775 }
1776
1777 //generate action code for source actor to try to disable trap (only trap type active regions)
TryToDisarm(Actor * source,const InfoPoint * tgt) const1778 void GameControl::TryToDisarm(Actor *source, const InfoPoint *tgt) const
1779 {
1780 if (tgt->Type!=ST_PROXIMITY) return;
1781
1782 source->SetModal(MS_NONE);
1783 source->CommandActor(GenerateActionDirect( "RemoveTraps([-1])", tgt ));
1784 }
1785
1786 //generate action code for source actor to use item/cast spell on a point
TryToCast(Actor * source,const Point & tgt)1787 void GameControl::TryToCast(Actor *source, const Point &tgt)
1788 {
1789 char Tmp[40];
1790
1791 if ((target_types&GA_POINT) == false) {
1792 return; // not allowed to target point
1793 }
1794
1795 if (!spellCount) {
1796 ResetTargetMode();
1797 return; // not casting or using an own item
1798 }
1799 source->Stop();
1800
1801 spellCount--;
1802 if (spellOrItem>=0) {
1803 if (spellIndex<0) {
1804 strlcpy(Tmp, "SpellPointNoDec(\"\",[0.0])", sizeof(Tmp));
1805 } else {
1806 strlcpy(Tmp, "SpellPoint(\"\",[0.0])", sizeof(Tmp));
1807 }
1808 } else {
1809 //using item on target
1810 strlcpy(Tmp, "UseItemPoint(\"\",[0,0],0)", sizeof(Tmp));
1811 }
1812 Action* action = GenerateAction( Tmp );
1813 action->pointParameter=tgt;
1814 if (spellOrItem>=0) {
1815 if (spellIndex<0) {
1816 snprintf(action->string0Parameter, sizeof(action->string0Parameter), "%.8s", spellName);
1817 } else {
1818 CREMemorizedSpell *si;
1819 //spell casting at target
1820 si = source->spellbook.GetMemorizedSpell(spellOrItem, spellSlot, spellIndex);
1821 if (!si) {
1822 ResetTargetMode();
1823 delete action;
1824 return;
1825 }
1826 snprintf(action->string0Parameter, sizeof(action->string0Parameter), "%.8s", si->SpellResRef);
1827 }
1828 } else {
1829 action->int0Parameter = spellSlot;
1830 action->int1Parameter = spellIndex;
1831 action->int2Parameter = UI_SILENT;
1832 //for multi-shot items like BG wand of lightning
1833 if (spellCount) {
1834 action->int2Parameter |= UI_NOAURA|UI_NOCHARGE;
1835 }
1836 }
1837 source->AddAction( action );
1838 if (!spellCount) {
1839 ResetTargetMode();
1840 }
1841 }
1842
1843 //generate action code for source actor to use item/cast spell on another actor
TryToCast(Actor * source,const Actor * tgt)1844 void GameControl::TryToCast(Actor *source, const Actor *tgt)
1845 {
1846 char Tmp[40];
1847
1848 // pst has no aura pollution
1849 bool aural = true;
1850 if (spellCount >= 1000) {
1851 spellCount -= 1000;
1852 aural = false;
1853 }
1854
1855 if (!spellCount) {
1856 ResetTargetMode();
1857 return; //not casting or using an own item
1858 }
1859 source->Stop();
1860
1861 // cannot target spells on invisible or sanctuaried creatures
1862 // invisible actors are invisible, so this is usually impossible by itself, but improved invisibility changes that
1863 if (source != tgt && tgt->Untargetable(spellName)) {
1864 displaymsg->DisplayConstantStringName(STR_NOSEE_NOCAST, DMC_RED, source);
1865 ResetTargetMode();
1866 return;
1867 }
1868
1869 spellCount--;
1870 if (spellOrItem>=0) {
1871 if (spellIndex<0) {
1872 sprintf(Tmp, "NIDSpecial7()");
1873 } else {
1874 sprintf(Tmp, "NIDSpecial6()");
1875 }
1876 } else {
1877 //using item on target
1878 sprintf(Tmp, "NIDSpecial5()");
1879 }
1880 Action* action = GenerateActionDirect( Tmp, tgt);
1881 if (spellOrItem>=0) {
1882 if (spellIndex<0) {
1883 snprintf(action->string0Parameter, sizeof(action->string0Parameter), "%.8s", spellName);
1884 } else {
1885 const CREMemorizedSpell *si;
1886 //spell casting at target
1887 si = source->spellbook.GetMemorizedSpell(spellOrItem, spellSlot, spellIndex);
1888 if (!si) {
1889 ResetTargetMode();
1890 delete action;
1891 return;
1892 }
1893 snprintf(action->string0Parameter, sizeof(action->string0Parameter), "%.8s", si->SpellResRef);
1894 }
1895 } else {
1896 action->int0Parameter = spellSlot;
1897 action->int1Parameter = spellIndex;
1898 action->int2Parameter = UI_SILENT;
1899 if (!aural) {
1900 action->int2Parameter |= UI_NOAURA;
1901 }
1902 //for multi-shot items like BG wand of lightning
1903 if (spellCount) {
1904 action->int2Parameter |= UI_NOAURA|UI_NOCHARGE;
1905 }
1906 }
1907 source->AddAction( action );
1908 if (!spellCount) {
1909 ResetTargetMode();
1910 }
1911 }
1912
1913 //generate action code for source actor to use talk to target actor
TryToTalk(Actor * source,const Actor * tgt) const1914 void GameControl::TryToTalk(Actor *source, const Actor *tgt) const
1915 {
1916 if (source->GetStat(IE_SEX) == SEX_ILLUSION) return;
1917 //Nidspecial1 is just an unused action existing in all games
1918 //(non interactive demo)
1919 //i found no fitting action which would emulate this kind of
1920 //dialog initation
1921 source->SetModal(MS_NONE);
1922 dialoghandler->SetTarget(tgt); //this is a hack, but not so deadly
1923 source->CommandActor(GenerateActionDirect( "NIDSpecial1()", tgt));
1924 }
1925
1926 //generate action code for actor appropriate for the target mode when the target is a container
HandleContainer(Container * container,Actor * actor)1927 void GameControl::HandleContainer(Container *container, Actor *actor)
1928 {
1929 if (actor->GetStat(IE_SEX) == SEX_ILLUSION) return;
1930 //container is disabled, it should not react
1931 if (container->Flags & CONT_DISABLED) {
1932 return;
1933 }
1934
1935 if ((target_mode == TARGET_MODE_CAST) && spellCount) {
1936 //we'll get the container back from the coordinates
1937 TryToCast(actor, container->Pos);
1938 //Do not reset target_mode, TryToCast does it for us!!
1939 return;
1940 }
1941
1942 core->SetEventFlag(EF_RESETTARGET);
1943
1944 if (target_mode == TARGET_MODE_ATTACK) {
1945 char Tmp[256];
1946 snprintf(Tmp, sizeof(Tmp), "BashDoor(\"%s\")", container->GetScriptName());
1947 actor->CommandActor(GenerateAction(Tmp));
1948 return;
1949 }
1950
1951 if (target_mode == TARGET_MODE_PICK) {
1952 TryToPick(actor, container);
1953 return;
1954 }
1955
1956 container->AddTrigger(TriggerEntry(trigger_clicked, actor->GetGlobalID()));
1957 core->SetCurrentContainer( actor, container);
1958 actor->CommandActor(GenerateAction("UseContainer()"));
1959 }
1960
1961 //generate action code for actor appropriate for the target mode when the target is a door
HandleDoor(Door * door,Actor * actor)1962 void GameControl::HandleDoor(Door *door, Actor *actor)
1963 {
1964 if (actor->GetStat(IE_SEX) == SEX_ILLUSION) return;
1965 if ((target_mode == TARGET_MODE_CAST) && spellCount) {
1966 //we'll get the door back from the coordinates
1967 const Point *p = door->toOpen;
1968 const Point *otherp = door->toOpen+1;
1969 if (Distance(*p,actor)>Distance(*otherp,actor)) {
1970 p=otherp;
1971 }
1972 TryToCast(actor, *p);
1973 return;
1974 }
1975
1976 core->SetEventFlag(EF_RESETTARGET);
1977
1978 if (target_mode == TARGET_MODE_ATTACK) {
1979 char Tmp[256];
1980 snprintf(Tmp, sizeof(Tmp), "BashDoor(\"%s\")", door->GetScriptName());
1981 actor->CommandActor(GenerateAction(Tmp));
1982 return;
1983 }
1984
1985 if (target_mode == TARGET_MODE_PICK) {
1986 TryToPick(actor, door);
1987 return;
1988 }
1989
1990 door->AddTrigger(TriggerEntry(trigger_clicked, actor->GetGlobalID()));
1991 actor->TargetDoor = door->GetGlobalID();
1992 // internal gemrb toggle door action hack - should we use UseDoor instead?
1993 actor->CommandActor(GenerateAction("NIDSpecial9()"));
1994 }
1995
1996 //generate action code for actor appropriate for the target mode when the target is an active region (infopoint, trap or travel)
HandleActiveRegion(InfoPoint * trap,Actor * actor,const Point & p)1997 bool GameControl::HandleActiveRegion(InfoPoint *trap, Actor * actor, const Point& p)
1998 {
1999 if (actor->GetStat(IE_SEX) == SEX_ILLUSION) return false;
2000 if ((target_mode == TARGET_MODE_CAST) && spellCount) {
2001 //we'll get the active region from the coordinates (if needed)
2002 TryToCast(actor, p);
2003 //don't bother with this region further
2004 return true;
2005 }
2006 if (target_mode == TARGET_MODE_PICK) {
2007 TryToDisarm(actor, trap);
2008 return true;
2009 }
2010
2011 switch(trap->Type) {
2012 case ST_TRAVEL:
2013 trap->AddTrigger(TriggerEntry(trigger_clicked, actor->GetGlobalID()));
2014 actor->LastMarked = trap->GetGlobalID();
2015 //clear the go closer flag
2016 trap->GetCurrentArea()->LastGoCloser = 0;
2017 return false;
2018 case ST_TRIGGER:
2019 // pst, eg. ar1500
2020 if (trap->GetDialog()[0]) {
2021 trap->AddAction(GenerateAction("Dialogue([PC])"));
2022 return true;
2023 }
2024
2025 // always display overhead text; totsc's ar0511 library relies on it
2026 if (!trap->GetOverheadText().empty()) {
2027 if (!trap->OverheadTextIsDisplaying()) {
2028 trap->DisplayOverheadText(true);
2029 DisplayString( trap );
2030 }
2031 }
2032 //the importer shouldn't load the script
2033 //if it is unallowed anyway (though
2034 //deactivated scripts could be reactivated)
2035 //only the 'trapped' flag should be honoured
2036 //there. Here we have to check on the
2037 //reset trap and deactivated flags
2038 if (trap->Scripts[0]) {
2039 if (!(trap->Flags & TRAP_DEACTIVATED) && !(GetDialogueFlags() & DF_FREEZE_SCRIPTS)) {
2040 trap->AddTrigger(TriggerEntry(trigger_clicked, actor->GetGlobalID()));
2041 actor->LastMarked = trap->GetGlobalID();
2042 //directly feeding the event, even if there are actions in the queue
2043 //trap->Scripts[0]->Update();
2044 // FIXME
2045 trap->ExecuteScript(1);
2046 trap->ProcessActions();
2047 }
2048 }
2049 if (trap->GetUsePoint() ) {
2050 char Tmp[256];
2051 sprintf(Tmp, "TriggerWalkTo(\"%s\")", trap->GetScriptName());
2052 actor->CommandActor(GenerateAction(Tmp));
2053 return true;
2054 }
2055 return true;
2056 default:;
2057 }
2058 return false;
2059 }
2060
2061 // Calculate the angle between a clicked point and the first selected character,
2062 // so that we can set a sensible orientation for the formation.
InitFormation(const Point & clickPoint)2063 void GameControl::InitFormation(const Point &clickPoint)
2064 {
2065 // Of course single actors don't get rotated, but we need to ensure
2066 // isFormationRotation is set in all cases where we initiate movement,
2067 // since OnMouseUp tests for it.
2068 if (isFormationRotation || core->GetGame()->selected.empty()) {
2069 return;
2070 }
2071
2072 const Game *game = core->GetGame();
2073 const Actor *selectedActor = core->GetFirstSelectedPC(false);
2074 if (!selectedActor) {
2075 selectedActor = game->selected[0];
2076 }
2077
2078 isFormationRotation = true;
2079 formationBaseAngle = AngleFromPoints(clickPoint, selectedActor->Pos);
2080 SetCursor(core->Cursors[IE_CURSOR_USE]);
2081 }
2082
2083 /** Mouse Button Down */
OnMouseDown(const MouseEvent & me,unsigned short Mod)2084 bool GameControl::OnMouseDown(const MouseEvent& me, unsigned short Mod)
2085 {
2086 Point p = ConvertPointFromScreen(me.Pos());
2087 gameClickPoint = p + vpOrigin;
2088
2089 switch(me.button) {
2090 case GEM_MB_MENU: //right click.
2091 if (core->HasFeature(GF_HAS_FLOAT_MENU) && !Mod) {
2092 core->GetGUIScriptEngine()->RunFunction( "GUICommon", "OpenFloatMenuWindow", false, p);
2093 }
2094 break;
2095 case GEM_MB_ACTION:
2096 // PST uses alt + left click for formation rotation
2097 // is there any harm in this being true in all games?
2098 if (me.repeats != 2 && EventMgr::ModState(GEM_MOD_ALT)) {
2099 InitFormation(gameClickPoint);
2100 }
2101
2102 break;
2103 }
2104 return true;
2105 }
2106
2107 // list of allowed area and exit combinations in pst that trigger worldmap travel
2108 static std::map<std::string, std::vector<std::string>> pstWMapExits {
2109 {"ar0100", {"to0300", "to0200", "to0101"}},
2110 {"ar0101", {"to0100"}},
2111 {"ar0200", {"to0100", "to0301", "to0400"}},
2112 {"ar0300", {"to0100", "to0301", "to0400"}},
2113 {"ar0301", {"to0200", "to0300"}},
2114 {"ar0400", {"to0200", "to0300"}},
2115 {"ar0500", {"to0405", "to0600"}},
2116 {"ar0600", {"to0500"}}
2117 };
2118
2119 // pst: determine if we need to trigger worldmap travel, since it had it's own system
2120 // eg. it doesn't use the searchmap for this in ar0500 when travelling globally
2121 // has to be a plain travel region and on the whitelist
ShouldTriggerWorldMap(const Actor * pc) const2122 bool GameControl::ShouldTriggerWorldMap(const Actor *pc) const
2123 {
2124 if (!core->HasFeature(GF_TEAM_MOVEMENT)) return false;
2125
2126 bool keyAreaVisited = CheckVariable(pc, "AR0500_Visited", "GLOBAL") == 1;
2127 if (!keyAreaVisited) return false;
2128
2129 bool teamMoved = (pc->GetInternalFlag() & IF_USEEXIT) && overInfoPoint && overInfoPoint->Type == ST_TRAVEL;
2130 if (!teamMoved) return false;
2131
2132 teamMoved = false;
2133 auto wmapExits = pstWMapExits.find(pc->GetCurrentArea()->GetScriptName());
2134 if (wmapExits != pstWMapExits.end()) {
2135 for (std::string exit : wmapExits->second) {
2136 if (!stricmp(exit.c_str(), overInfoPoint->GetScriptName())) {
2137 teamMoved = true;
2138 break;
2139 }
2140 }
2141 }
2142
2143 return teamMoved;
2144 }
2145
2146 /** Mouse Button Up */
OnMouseUp(const MouseEvent & me,unsigned short Mod)2147 bool GameControl::OnMouseUp(const MouseEvent& me, unsigned short Mod)
2148 {
2149 //heh, i found no better place
2150 core->CloseCurrentContainer();
2151
2152 Point p = ConvertPointFromScreen(me.Pos()) + vpOrigin;
2153 bool isDoubleClick = me.repeats == 2;
2154
2155 // right click
2156 if (me.button == GEM_MB_MENU) {
2157 if (target_mode != TARGET_MODE_NONE) {
2158 if (!core->HasFeature(GF_HAS_FLOAT_MENU)) {
2159 SetTargetMode(TARGET_MODE_NONE);
2160 }
2161 // update the action bar
2162 core->SetEventFlag(EF_ACTION);
2163 ClearMouseState();
2164 return true;
2165 } else {
2166 p = gameClickPoint;
2167
2168 ieDword actionLevel = core->GetDictionary()->Lookup("ActionLevel", actionLevel);
2169 if (actionLevel) {
2170 // update the action bar
2171 core->GetDictionary()->SetAt("ActionLevel", 0, false);
2172 core->SetEventFlag(EF_ACTION);
2173 ClearMouseState();
2174 }
2175 }
2176 } else if (me.button == GEM_MB_MIDDLE) {
2177 // do nothing, so middle button panning doesn't trigger a move
2178 return true;
2179 } else {
2180 // any other button behaves as left click (scrollwhell buttons are mouse wheel events now)
2181 if (isDoubleClick)
2182 MoveViewportTo(p, true);
2183
2184 // handle actions
2185 if (target_mode == TARGET_MODE_NONE && lastActorID) {
2186 switch (lastCursor & ~IE_CURSOR_GRAY) {
2187 case IE_CURSOR_TALK:
2188 SetTargetMode(TARGET_MODE_TALK);
2189 break;
2190 case IE_CURSOR_ATTACK:
2191 SetTargetMode(TARGET_MODE_ATTACK);
2192 break;
2193 case IE_CURSOR_CAST:
2194 SetTargetMode(TARGET_MODE_CAST);
2195 break;
2196 case IE_CURSOR_DEFEND:
2197 SetTargetMode(TARGET_MODE_DEFEND);
2198 break;
2199 case IE_CURSOR_PICK:
2200 SetTargetMode(TARGET_MODE_PICK);
2201 break;
2202 default: break;
2203 }
2204 }
2205
2206 if (target_mode == TARGET_MODE_NONE) {
2207 if (isSelectionRect || lastActorID) {
2208 MakeSelection(Mod&GEM_MOD_SHIFT);
2209 ClearMouseState();
2210 return true;
2211 }
2212 }
2213
2214 if (lastCursor == IE_CURSOR_BLOCKED) {
2215 // don't allow travel if the destination is actually blocked
2216 return false;
2217 }
2218
2219 if (overContainer || overDoor || (overInfoPoint && overInfoPoint->Type==ST_TRAVEL && target_mode == TARGET_MODE_NONE)) {
2220 // move to the object before trying to interact with it
2221 CommandSelectedMovement(p, false, isDoubleClick);
2222 }
2223
2224 if (target_mode != TARGET_MODE_NONE || overInfoPoint || overContainer || overDoor) {
2225 PerformSelectedAction(p);
2226 ClearMouseState();
2227 return true;
2228 }
2229
2230 // Ensure that left-click movement also orients the formation
2231 // in the direction of movement.
2232 InitFormation(p);
2233 }
2234
2235 // handle movement/travel, but not if we just opened the float window
2236 if ((!core->HasFeature(GF_HAS_FLOAT_MENU) || me.button != GEM_MB_MENU) && lastCursor != IE_CURSOR_BLOCKED) {
2237 CommandSelectedMovement(p, Mod & GEM_MOD_SHIFT, isDoubleClick);
2238 }
2239 ClearMouseState();
2240 return true;
2241 }
2242
PerformSelectedAction(const Point & p)2243 void GameControl::PerformSelectedAction(const Point& p)
2244 {
2245 // TODO: consolidate the 'over' members into a single Scriptable*
2246 // then we simply switch on its type
2247
2248 const Game* game = core->GetGame();
2249 const Map* area = game->GetCurrentArea();
2250 Actor* targetActor = area->GetActor(p, target_types & ~GA_NO_HIDDEN);
2251
2252 Actor* selectedActor = core->GetFirstSelectedPC(false);
2253 if (!selectedActor) {
2254 //this could be a non-PC
2255 selectedActor = game->selected[0];
2256 }
2257
2258 //add a check if you don't want some random monster handle doors and such
2259 if (targetActor) {
2260 PerformActionOn(targetActor);
2261 } else if (target_mode == TARGET_MODE_CAST) {
2262 //the player is using an item or spell on the ground
2263 TryToCast(selectedActor, p);
2264 } else if (overDoor) {
2265 HandleDoor(overDoor, selectedActor);
2266 } else if (overContainer) {
2267 HandleContainer(overContainer, selectedActor);
2268 } else if (overInfoPoint) {
2269 if (overInfoPoint->Type==ST_TRAVEL && target_mode == TARGET_MODE_NONE) {
2270 ieDword exitID = overInfoPoint->GetGlobalID();
2271 if (core->HasFeature(GF_TEAM_MOVEMENT)) {
2272 // pst forces everyone to travel (eg. ar0201 outside_portal)
2273 int i = game->GetPartySize(false);
2274 while(i--) {
2275 game->GetPC(i, false)->UseExit(exitID);
2276 }
2277 } else {
2278 size_t i = game->selected.size();
2279 while(i--) {
2280 game->selected[i]->UseExit(exitID);
2281 }
2282 }
2283 CommandSelectedMovement(p);
2284 }
2285 if (HandleActiveRegion(overInfoPoint, selectedActor, p)) {
2286 core->SetEventFlag(EF_RESETTARGET);
2287 }
2288 }
2289 }
2290
CommandSelectedMovement(const Point & p,bool append,bool tryToRun)2291 void GameControl::CommandSelectedMovement(const Point& p, bool append, bool tryToRun)
2292 {
2293 const Game* game = core->GetGame();
2294
2295 // construct a sorted party
2296 std::vector<Actor *> party;
2297 // first, from the actual party
2298 int max = game->GetPartySize(false);
2299 for (int idx = 1; idx <= max; idx++) {
2300 Actor *act = game->FindPC(idx);
2301 assert(act);
2302 if (act->IsSelected()) {
2303 party.push_back(act);
2304 }
2305 }
2306 // then summons etc.
2307 for (Actor *selected : game->selected) {
2308 if (!selected->InParty) {
2309 party.push_back(selected);
2310 }
2311 }
2312
2313 if (party.empty())
2314 return;
2315
2316 double angle = 0.0;
2317 if (isFormationRotation) {
2318 angle = formationBaseAngle;
2319 Point mp = GameMousePos();
2320 if (Distance(mp, p) > EventMgr::mouseDragRadius) {
2321 angle = AngleFromPoints(mp, p);
2322 }
2323 }
2324
2325 bool doWorldMap = ShouldTriggerWorldMap(party[0]);
2326
2327 std::vector<Point> formationPoints = GetFormationPoints(p, party, angle);
2328 for (size_t i = 0; i < party.size(); i++) {
2329 Actor *actor = party[i];
2330 // don't stop the party if we're just trying to add a waypoint
2331 if (!append) {
2332 actor->Stop();
2333 }
2334
2335 if (party.size() > 1) {
2336 CreateMovement(actor, formationPoints[i], append, tryToRun);
2337 } else {
2338 CreateMovement(actor, p, append, tryToRun);
2339 }
2340
2341 // don't trigger the travel region, so everyone can bunch up there and NIDSpecial2 can take over
2342 if (doWorldMap) actor->SetInternalFlag(IF_PST_WMAPPING, OP_OR);
2343 }
2344
2345 // p is a searchmap travel region or a plain travel region in pst (matching several other criteria)
2346 if (party[0]->GetCurrentArea()->GetCursor(p) == IE_CURSOR_TRAVEL || doWorldMap) {
2347 char Tmp[256];
2348 sprintf(Tmp, "NIDSpecial2()");
2349 party[0]->AddAction(GenerateAction(Tmp));
2350 }
2351 }
OnMouseWheelScroll(const Point & delta)2352 bool GameControl::OnMouseWheelScroll(const Point& delta)
2353 {
2354 // Game coordinates start at the top left to the bottom right
2355 // so we need to invert the 'y' axis
2356 Point d = delta;
2357 d.y *= -1;
2358 Scroll(d);
2359 return true;
2360 }
2361
OnControllerButtonDown(const ControllerEvent & ce)2362 bool GameControl::OnControllerButtonDown(const ControllerEvent& ce)
2363 {
2364 switch (ce.button) {
2365 case CONTROLLER_BUTTON_Y:
2366 return core->GetGUIScriptEngine()->RunFunction("GUIINV", "ToggleInventoryWindow", false);
2367 case CONTROLLER_BUTTON_X:
2368 return core->GetGUIScriptEngine()->RunFunction("GUIMA", "ToggleMapWindow", false);
2369 case CONTROLLER_BUTTON_BACK:
2370 core->SetEventFlag(EF_ACTION|EF_RESETTARGET);
2371 return true;
2372 default:
2373 return View::OnControllerButtonDown(ce);
2374 }
2375 }
2376
Scroll(const Point & amt)2377 void GameControl::Scroll(const Point& amt)
2378 {
2379 MoveViewportTo(vpOrigin + amt, false);
2380 }
2381
PerformActionOn(Actor * actor)2382 void GameControl::PerformActionOn(Actor *actor)
2383 {
2384 const Game* game = core->GetGame();
2385
2386 //determining the type of the clicked actor
2387 ieDword type = actor->GetStat(IE_EA);
2388 if (type >= EA_EVILCUTOFF || type == EA_GOODBUTRED) {
2389 type = ACT_ATTACK; //hostile
2390 } else if (type > EA_CHARMED) {
2391 type = ACT_TALK; //neutral
2392 } else {
2393 type = ACT_NONE; //party
2394 }
2395
2396 if (target_mode == TARGET_MODE_ATTACK) {
2397 type = ACT_ATTACK;
2398 } else if (target_mode == TARGET_MODE_TALK) {
2399 type = ACT_TALK;
2400 } else if (target_mode == TARGET_MODE_CAST) {
2401 type = ACT_CAST;
2402 } else if (target_mode == TARGET_MODE_DEFEND) {
2403 type = ACT_DEFEND;
2404 } else if (target_mode == TARGET_MODE_PICK) {
2405 type = ACT_THIEVING;
2406 }
2407
2408 if (type != ACT_NONE && !actor->ValidTarget(target_types)) {
2409 return;
2410 }
2411
2412 //we shouldn't zero this for two reasons in case of spell or item
2413 //1. there could be multiple targets
2414 //2. the target mode is important
2415 if (!(target_mode == TARGET_MODE_CAST) || !spellCount) {
2416 ResetTargetMode();
2417 }
2418
2419 switch (type) {
2420 case ACT_NONE: //none
2421 if (!actor->ValidTarget(GA_SELECT)) {
2422 return;
2423 }
2424
2425 if (actor->InParty)
2426 SelectActor( actor->InParty );
2427 else if (actor->GetStat(IE_EA) <= EA_CHARMED) {
2428 /*let's select charmed/summoned creatures
2429 EA_CHARMED is the maximum value known atm*/
2430 core->GetGame()->SelectActor(actor, true, SELECT_REPLACE);
2431 }
2432 break;
2433 case ACT_TALK:
2434 if (!actor->ValidTarget(GA_TALK)) {
2435 return;
2436 }
2437
2438 //talk (first selected talks)
2439 if (game->selected.size()) {
2440 //if we are in PST modify this to NO!
2441 Actor *source;
2442 if (core->HasFeature(GF_PROTAGONIST_TALKS) ) {
2443 source = game->GetPC(0, false); //protagonist
2444 } else {
2445 source = core->GetFirstSelectedPC(false);
2446 }
2447 // only party members can start conversations
2448 if (source) {
2449 TryToTalk(source, actor);
2450 }
2451 }
2452 break;
2453 case ACT_ATTACK:
2454 //all of them attacks the red circled actor
2455 for (Actor *selectee : game->selected) {
2456 TryToAttack(selectee, actor);
2457 }
2458 break;
2459 case ACT_CAST: //cast on target or use item on target
2460 if (game->selected.size()==1) {
2461 Actor *source = core->GetFirstSelectedActor();
2462 if (source) {
2463 TryToCast(source, actor);
2464 }
2465 }
2466 break;
2467 case ACT_DEFEND:
2468 for (Actor *selectee : game->selected) {
2469 TryToDefend(selectee, actor);
2470 }
2471 break;
2472 case ACT_THIEVING:
2473 if (game->selected.size()==1) {
2474 Actor *source = core->GetFirstSelectedActor();
2475 if (source) {
2476 TryToPick(source, actor);
2477 }
2478 }
2479 break;
2480 }
2481 }
2482
2483 //sets target mode, and resets the cursor
SetTargetMode(int mode)2484 void GameControl::SetTargetMode(int mode) {
2485 target_mode = mode;
2486 }
2487
ResetTargetMode()2488 void GameControl::ResetTargetMode() {
2489 target_types = GA_NO_DEAD|GA_NO_HIDDEN|GA_NO_UNSCHEDULED;
2490 SetTargetMode(TARGET_MODE_NONE);
2491 }
2492
UpdateTargetMode()2493 void GameControl::UpdateTargetMode() {
2494 SetTargetMode(target_mode);
2495 }
2496
SelectionRect() const2497 Region GameControl::SelectionRect() const
2498 {
2499 Point pos = GameMousePos();
2500 if (isSelectionRect) {
2501 return Region::RegionFromPoints(pos, gameClickPoint);
2502 }
2503 return Region(pos.x, pos.y, 1, 1);
2504 }
2505
MakeSelection(bool extend)2506 void GameControl::MakeSelection(bool extend)
2507 {
2508 Game* game = core->GetGame();
2509
2510 if (!extend && !highlighted.empty()) {
2511 game->SelectActor( NULL, false, SELECT_NORMAL );
2512 }
2513
2514 std::vector<Actor*>::iterator it = highlighted.begin();
2515 for (; it != highlighted.end(); ++it) {
2516 Actor* act = *it;
2517 act->SetOver(false);
2518 game->SelectActor(act, true, SELECT_NORMAL);
2519 }
2520 }
2521
SetCutSceneMode(bool active)2522 void GameControl::SetCutSceneMode(bool active)
2523 {
2524 WindowManager* wm = core->GetWindowManager();
2525 if (active) {
2526 ScreenFlags |= SF_CUTSCENE;
2527 moveX = 0;
2528 moveY = 0;
2529 wm->SetCursorFeedback(WindowManager::MOUSE_NONE);
2530 } else {
2531 ScreenFlags &= ~SF_CUTSCENE;
2532 wm->SetCursorFeedback(WindowManager::CursorFeedback(core->MouseFeedback));
2533 }
2534 SetFlags(IgnoreEvents, (active || DialogueFlags&DF_IN_DIALOG) ? OP_OR : OP_NAND);
2535 }
2536
2537 //Create an overhead text over a scriptable target
2538 //Multiple texts are possible, as this code copies the text to a new object
DisplayString(const Scriptable * target) const2539 void GameControl::DisplayString(const Scriptable* target) const
2540 {
2541 Scriptable* scr = new Scriptable( ST_TRIGGER );
2542 scr->SetOverheadText(target->GetOverheadText());
2543 scr->Pos = target->Pos;
2544
2545 // add as a "subtitle" to the main message window
2546 ieDword tmp = 0;
2547 core->GetDictionary()->Lookup("Duplicate Floating Text", tmp);
2548 if (tmp && !target->GetOverheadText().empty()) {
2549 // pass NULL target so pst does not display multiple
2550 displaymsg->DisplayString(target->GetOverheadText());
2551 }
2552 }
2553
2554 /** changes displayed map to the currently selected PC */
ChangeMap(Actor * pc,bool forced)2555 void GameControl::ChangeMap(Actor *pc, bool forced)
2556 {
2557 //swap in the area of the actor
2558 Game* game = core->GetGame();
2559 if (forced || (pc && stricmp( pc->Area, game->CurrentArea) != 0) ) {
2560 // disable so that drawing and events dispatched doesn't happen while there is not an area
2561 // we are single threaded, but game loading runs its own event loop which will cause events/drawing to come in
2562 SetDisabled(true);
2563 ClearMouseState();
2564
2565 dialoghandler->EndDialog();
2566 overInfoPoint = NULL;
2567 overContainer = NULL;
2568 overDoor = NULL;
2569 /*this is loadmap, because we need the index, not the pointer*/
2570 char *areaname = game->CurrentArea;
2571 if (pc) {
2572 areaname = pc->Area;
2573 }
2574 game->GetMap( areaname, true );
2575
2576 if (!core->InCutSceneMode()) {
2577 // don't interfere with any scripted moves of the viewport
2578 // checking core->timer->ViewportIsMoving() is not enough
2579 ScreenFlags |= SF_CENTERONACTOR;
2580 }
2581
2582 SetDisabled(false);
2583
2584 if (window) {
2585 window->Focus();
2586 }
2587 }
2588 //center on first selected actor
2589 if (pc && (ScreenFlags&SF_CENTERONACTOR)) {
2590 MoveViewportTo( pc->Pos, true );
2591 ScreenFlags&=~SF_CENTERONACTOR;
2592 }
2593 }
2594
FlagsChanged(unsigned int)2595 void GameControl::FlagsChanged(unsigned int /*oldflags*/)
2596 {
2597 if (Flags()&IgnoreEvents) {
2598 ClearMouseState();
2599 moveX = 0;
2600 moveY = 0;
2601 }
2602 }
2603
SetScreenFlags(unsigned int value,int mode)2604 bool GameControl::SetScreenFlags(unsigned int value, int mode)
2605 {
2606 return SetBits(ScreenFlags, value, mode);
2607 }
2608
SetDialogueFlags(unsigned int value,int mode)2609 void GameControl::SetDialogueFlags(unsigned int value, int mode)
2610 {
2611 SetBits(DialogueFlags, value, mode);
2612 SetFlags(IgnoreEvents, (DialogueFlags&DF_IN_DIALOG || ScreenFlags&SF_CUTSCENE) ? OP_OR : OP_NAND);
2613 }
2614
CurrentArea() const2615 Map* GameControl::CurrentArea() const
2616 {
2617 const Game *game = core->GetGame();
2618 if (game) {
2619 return game->GetCurrentArea();
2620 }
2621 return NULL;
2622 }
2623
GetLastActor()2624 Actor *GameControl::GetLastActor()
2625 {
2626 Actor* actor = NULL;
2627 const Map* area = CurrentArea();
2628 if (area) {
2629 actor = area->GetActorByGlobalID(lastActorID);
2630 }
2631 return actor;
2632 }
2633
SetLastActor(Actor * lastActor)2634 void GameControl::SetLastActor(Actor* lastActor)
2635 {
2636 if (lastActorID) {
2637 const Map* area = CurrentArea();
2638 if (area == NULL) {
2639 return;
2640 }
2641
2642 Actor* current = area->GetActorByGlobalID(lastActorID);
2643 if (current)
2644 current->SetOver(false);
2645 lastActorID = 0;
2646 }
2647
2648 if (lastActor) {
2649 lastActorID = lastActor->GetGlobalID();
2650 lastActor->SetOver(true);
2651 }
2652 }
2653
2654 //Set up an item use which needs targeting
2655 //Slot is an inventory slot
2656 //header is the used item extended header
2657 //u is the user
2658 //target type is a bunch of GetActor flags that alter valid targets
2659 //cnt is the number of different targets (usually 1)
SetupItemUse(int slot,int header,Actor * u,int targettype,int cnt)2660 void GameControl::SetupItemUse(int slot, int header, Actor *u, int targettype, int cnt)
2661 {
2662 memset(spellName, 0, sizeof(ieResRef));
2663 spellOrItem = -1;
2664 spellUser = u;
2665 spellSlot = slot;
2666 spellIndex = header;
2667 //item use also uses the casting icon, this might be changed in some custom game?
2668 SetTargetMode(TARGET_MODE_CAST);
2669 target_types = targettype;
2670 spellCount = cnt;
2671 }
2672
2673 //Set up spell casting which needs targeting
2674 //type is the spell's type
2675 //level is the caster level
2676 //idx is the spell's number
2677 //u is the caster
2678 //target type is a bunch of GetActor flags that alter valid targets
2679 //cnt is the number of different targets (usually 1)
SetupCasting(ieResRef spellname,int type,int level,int idx,Actor * u,int targettype,int cnt)2680 void GameControl::SetupCasting(ieResRef spellname, int type, int level, int idx, Actor *u, int targettype, int cnt)
2681 {
2682 memcpy(spellName, spellname, sizeof(ieResRef));
2683 spellOrItem = type;
2684 spellUser = u;
2685 spellSlot = level;
2686 spellIndex = idx;
2687 SetTargetMode(TARGET_MODE_CAST);
2688 target_types = targettype;
2689 spellCount = cnt;
2690 }
2691
SetDisplayText(String * text,unsigned int time)2692 void GameControl::SetDisplayText(String* text, unsigned int time)
2693 {
2694 delete DisplayText;
2695 DisplayTextTime = time;
2696 DisplayText = text;
2697 }
2698
SetDisplayText(ieStrRef text,unsigned int time)2699 void GameControl::SetDisplayText(ieStrRef text, unsigned int time)
2700 {
2701 SetDisplayText(core->GetString(displaymsg->GetStringReference(text), 0), time);
2702 }
2703
ToggleAlwaysRun()2704 void GameControl::ToggleAlwaysRun()
2705 {
2706 AlwaysRun = !AlwaysRun;
2707 core->GetDictionary()->SetAt("Always Run", AlwaysRun);
2708 }
2709
2710 }
2711