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