scummvm/engines/saga2/tilemode.cpp
2022-10-29 00:01:10 +02:00

1449 lines
41 KiB
C++

/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* aint32 with this program; if not, write to the Free Software
*
*
* Based on the original sources
* Faery Tale II -- The Halls of the Dead
* (c) 1993-1996 The Wyrmkeep Entertainment Co.
*/
#include "common/events.h"
#include "saga2/saga2.h"
#include "saga2/detection.h"
#include "saga2/display.h"
#include "saga2/tilemode.h"
#include "saga2/tile.h"
#include "saga2/setup.h"
#include "saga2/objects.h"
#include "saga2/grabinfo.h"
#include "saga2/mouseimg.h"
#include "saga2/motion.h"
#include "saga2/task.h"
#include "saga2/transit.h"
#include "saga2/magic.h"
#include "saga2/sensor.h"
#include "saga2/timers.h"
#include "saga2/intrface.h"
#include "saga2/dispnode.h"
#include "saga2/uidialog.h"
#include "saga2/contain.h"
#include "saga2/saveload.h"
#include "saga2/oncall.h"
namespace Saga2 {
#define CHEATMOVE 1 // For moving with keypad in 8 directions
/* ===================================================================== *
gStickyDragControl class: a gGenericControl with a sticky mouse
* ===================================================================== */
class gStickyDragControl : public gGenericControl {
bool _sticky;
public:
gStickyDragControl(gPanelList &, const Rect16 &, uint16, AppFunc *cmd = nullptr);
void setSticky(bool s) {
_sticky = s;
}
bool isSticky() {
return _sticky;
}
private:
void deactivate() override;
// void pointerMove( gPanelMessage &msg );
bool pointerHit(gPanelMessage &msg) override;
void pointerRelease(gPanelMessage &msg) override;
};
/* ===================================================================== *
Globals
* ===================================================================== */
//void startVideo( char *fileName,int x, int y );
//int16 OptionsDialog( bool disableSaveResume=false );
extern int16 speechButtonCount; // count of speech buttons
extern void abortSpeech();
extern const uint32 imageGroupID = MKTAG('I', 'M', 'A', 'G');
extern hResContext *tileRes; // tile resource handle
extern CycleHandle cycleList; // list of tile cycling info
extern int16 cycleCount;
extern int32 lastUpdateTime; // time of last display update
//Prototypes For Tile Mode GameMode Object Init
void TileModeHandleTask();
void TileModeHandleKey(int16 key, int16 qual);
void initTileBanks();
void freeAllTileBanks();
void navigateDirect(TilePoint pick, bool runFlag);
void navigatePath(TilePoint pick);
void moveActors(int32 deltaTime);
void updateMainDisplay();
void toggleMusic();
#if CHEATMOVE
void cheatMove(int16 key);
#endif
void incrementActiveFaction(Actor *a);
// dispatch functions
static APPFUNC(cmdClickTileMap); // appFunc for map display
static StaticTilePoint tilePickPos = {0, 0, 0}, // mouse coord over tilemap (floor)
tilePickExactPos = {0, 0, 0}, // mouse coord of click on tilemap
objPickPos = {0, 0, 0}, // coord of mouse picked object
walkToPos = {0, 0, 0}; // navigation target location
ObjectID pickedObject; // which object picked by mouse
ActiveItemPtr pickedTAI; // which active item instance
ObjectID lastPickedObject = Nothing; // ID of last picked object
Alarm dispObjNameAlarm; // Alarm used for time delay
// in displaying mouse object's
// name
Alarm containerObjTextAlarm; // time delay for container view object text
ObjectID pickedActor;
#if CHEATMOVE
ObjectID selectedObject = Nothing;
bool nudge = false;
#endif
extern ObjectID viewCenterObject;
static struct _delayedNavigation {
StaticTilePoint walkToPos;
bool pathFindFlag;
Alarm delay;
} delayedNavigation = {{0, 0, 0}, false, {0, 0}};
static bool navigationDelayed = false;
//Tile Mode GameMode Object
GameMode TileMode = {
nullptr, // no previous mode
false, // mode is not nestable
TileModeSetup,
TileModeCleanup,
TileModeHandleTask,
TileModeHandleKey,
drawMainDisplay,
};
// Duration, in timer ticks, that a character must walk before
// they can begin to run.
const int runThreshhold = 32;
extern Alarm frameAlarm; // 10 fps frame rate
Alarm updateAlarm, // max coord update rate
pathFindAlarm; // mouse click rate for path find
bool tileLockFlag; // true if tile mode is locked
GameObject *mouseObject = nullptr; // object being dragged
StaticPoint32 lastMousePos = {0, 0}; // Last mouse position over map
static bool mousePressed, // State of mouse button
clickActionDone = true; // Flag indication whether current
// mouse click action is done
static bool runFlag = false; // Reflexs whether the mouse is
// the run zone
static bool uiKeysEnabled = true;
static char lastUnusedKey = '\0';
// The control that covers the scrolling tile display
gStickyDragControl *tileMapControl;
extern gPanelList *tileControls, // panelList of play controls
*playControls;
extern BackWindow *mainWindow;
extern uint32 frames;
// Resource handle for UI imagery
extern hResContext *imageRes; // image resource handle
// Combat related data
static bool aggressiveActFlag = false; // Indicates whether or not
static bool inCombat,
combatPaused;
// This is a correction required by MSVC's inability to provided
// precompiled header services if data is assigned during declaration
// inside a header. GT 09/11/95
static StaticWindow mainWindowDecorations[] = {
{{0, 0, 640, 20}, nullptr, MWTopBorder}, // top border
{{0, 440, 640, 40}, nullptr, MWBottomBorder}, // bottom border
{{0, 20, 20, 420}, nullptr, MWLeftBorder}, // left border
{{460, 20, 180, 142}, nullptr, MWRightBorder1}, // right border #1
{{460, 162, 180, 151}, nullptr, MWRightBorder2}, // right border #2
{{460, 313, 180, 127}, nullptr, MWRightBorder3}, // right border #3
};
/* ===================================================================== *
TileMode utility functions
* ===================================================================== */
bool InCombatPauseKludge() {
return (inCombat && combatPaused);
}
//-----------------------------------------------------------------------
// Function to enable/disable user interface keys
bool enableUIKeys(bool enabled) {
bool oldVal = uiKeysEnabled;
uiKeysEnabled = enabled;
return oldVal;
}
char luckyKey(char *choices) {
return lastUnusedKey;
}
//-----------------------------------------------------------------------
// This function performs all combat pausing tasks
static void pauseCombat() {
pauseCalendar();
pauseBackgroundSimulation();
pauseInterruptableMotions();
pauseObjectStates();
pauseActorStates();
pauseActorTasks();
setCenterActorIndicator(true);
}
//-----------------------------------------------------------------------
// This function performs all combat un-pausing tasks
static void resumeCombat() {
setCenterActorIndicator(false);
resumeActorTasks();
resumeActorStates();
resumeObjectStates();
resumeInterruptableMotions();
resumeBackgroundSimulation();
resumeCalendar();
}
//-----------------------------------------------------------------------
// This function performs all combat initialization tasks
static void startCombat() {
if (g_vm->_autoAggression)
autoAdjustAggression();
setCombatBehavior(true);
combatPaused = false;
}
//-----------------------------------------------------------------------
// This function performs all combat cleanup tasks
static void endCombat() {
if (combatPaused) {
combatPaused = false;
resumeCombat();
}
setCombatBehavior(false);
handleEndOfCombat();
}
//-----------------------------------------------------------------------
void toggleAutoAggression() {
g_vm->_autoAggression = !g_vm->_autoAggression;
updateAutoAggressionButton(g_vm->_autoAggression);
}
//-----------------------------------------------------------------------
bool isAutoAggressionSet() {
return g_vm->_autoAggression;
}
//-----------------------------------------------------------------------
void toggleAutoWeapon() {
g_vm->_autoWeapon = !g_vm->_autoWeapon;
updateAutoWeaponButton(g_vm->_autoWeapon);
}
//-----------------------------------------------------------------------
bool isAutoWeaponSet() {
return g_vm->_autoWeapon;
}
//-----------------------------------------------------------------------
// Called to notify this module of an aggressive act
void logAggressiveAct(ObjectID attackerID, ObjectID attackeeID) {
if (isPlayerActor(attackerID) || isPlayerActor(attackeeID)) {
PlayerActorID playerID;
if (actorIDToPlayerID(attackeeID, playerID))
handlePlayerActorAttacked(playerID);
aggressiveActFlag = true;
*g_vm->_tmm->_timeOfLastAggressiveAct = *g_vm->_calendar;
}
}
//-----------------------------------------------------------------------
// Determine how much time has elapsed since the last aggressive act
// involving a player actor
uint16 timeSinceLastAggressiveAct() {
return aggressiveActFlag ? *g_vm->_calendar - *g_vm->_tmm->_timeOfLastAggressiveAct : maxuint16;
}
//-----------------------------------------------------------------------
// Determine if there are any enemies within the active regions.
bool areThereActiveEnemies() {
ActiveRegionObjectIterator iter;
GameObject *obj = nullptr;
for (iter.first(&obj); obj != nullptr; iter.next(&obj)) {
if (isActor(obj)
&& !((Actor *)obj)->isDead()
&& ((Actor *)obj)->_disposition == kDispositionEnemy)
return true;
}
return false;
}
void CheckCombatMood() {
GameObject *obj;
TilePoint centerLoc;
ActiveRegion *ar;
GameWorld *world;
static bool wasHostile = false;
ar = getActiveRegion(getCenterActorPlayerID());
if (ar == nullptr) return;
world = ar->getWorld();
if (world == nullptr || !isWorld(world)) return;
// Search for hostile monsters.
// If hostile monsters were found last time we searched, then expand the
// search radius slightly to provide a little bit of hysterisis to prevent
// indecision in music selection.
CircularObjectIterator iter8(world,
getCenterActor()->getLocation(),
wasHostile ? 220 : 180);
bool agress = isCenterActorAggressive();
wasHostile = false;
clearActiveFactions();
for (iter8.first(&obj); obj != nullptr; iter8.next(&obj)) {
if (isActor(obj)
&& !((Actor *)obj)->isDead()
&& ((Actor *)obj)->_disposition == kDispositionEnemy) {
if (agress || !(((Actor *)obj)->_flags & Actor::kAFAfraid)) {
incrementActiveFaction((Actor *) obj);
wasHostile = true;
}
}
}
useActiveFactions();
}
//-----------------------------------------------------------------------
// This function evaluates the mouse state in the standard manner
static void evalMouseState() {
GameObject *obj = GameObject::objectAddress(pickedObject);
Actor *a = getCenterActor();
bool interruptable = a->isInterruptable();
g_vm->_mouseInfo->setDoable(interruptable);
if (g_vm->_mouseInfo->getObject() != nullptr) {
GameObject *mObj = g_vm->_mouseInfo->getObject();
// If the mouse pointer has an object and the intention
// is set to use, modify the doable setting depending
// on whether the mouse is pointing at another object
// and if so, whether the other object is within the
// use range of the center actor
if (g_vm->_mouseInfo->getIntent() == GrabInfo::kIntUse) {
assert(obj != nullptr);
if (mObj->containmentSet() & (ProtoObj::isSkill | ProtoObj::isSpell)) {
GameObject *tob = pickedObject != Nothing ? obj : nullptr;
// If it's a spell we need to do more complex testing
// to see if the current target is valid
g_vm->_mouseInfo->setDoable(
interruptable
&& validTarget(
a,
tob,
pickedTAI,
(SkillProto *)GameObject::protoAddress(
mObj->thisID())));
} else {
g_vm->_mouseInfo->setDoable(
interruptable
&& (pickedObject == Nothing
|| (a->inUseRange(
obj->getLocation(),
mObj)
&& (a->inRange(obj->getLocation(), 8)
|| lineOfSight(a, obj, terrainTransparent)))));
}
}
} else {
// Determine if the mouse is being dragged
if (mousePressed) {
// Adjust the intention and doable settings based
// factors such as center actor aggression, whether
// the mouse is pointing at an object, etc...
// Determine if the center actor is aggressive
if (isCenterActorAggressive()) {
// Determine if the mouse is pointing at an
// object
if (pickedObject != Nothing
&& !isPlayerActor(pickedObject)) {
// If in attack range, set the intention
// to attack, else set the intention to walk
// to the picked object
if (a->inAttackRange(obj->getLocation())
&& (a->inRange(obj->getLocation(), 8)
|| lineOfSight(a, obj, terrainTransparent)))
g_vm->_mouseInfo->setIntent(GrabInfo::kIntAttack);
else {
g_vm->_mouseInfo->setIntent(GrabInfo::kIntWalkTo);
walkToPos.set(obj->getLocation().u,
obj->getLocation().v,
obj->getLocation().z);
}
} else
// The mouse is not pointing at an object
{
// Since there is no picked object,
// determine whether the center actor has
// finished can initiate a new action, if so, set
// the intention to walk to the mouse pointer
if (interruptable) {
g_vm->_mouseInfo->setIntent(GrabInfo::kIntWalkTo);
if (tileMapControl->isSticky())
setMouseImage(kMouseAutoWalkImage, -8, -8);
walkToPos = tilePickPos;
}
}
} else
// The center actor is not aggressive
{
// Set the intention to walk to the mouse
// pointer
g_vm->_mouseInfo->setIntent(GrabInfo::kIntWalkTo);
if (tileMapControl->isSticky())
setMouseImage(kMouseAutoWalkImage, -8, -8);
walkToPos = tilePickPos;
}
} else
// The mouse is not being dragged
{
// Determine if mouse is pointing at an object
if (pickedObject != Nothing) {
// Determine if the center actor is aggressive
if (isCenterActorAggressive()
&& !isPlayerActor(pickedObject)) {
// If center actor is in attack range of
// the picked object, set the intention to
// attack, else set the intention to walk
// to the object
if (a->inAttackRange(obj->getLocation())
&& (a->inRange(obj->getLocation(), 8)
|| lineOfSight(a, obj, terrainTransparent))) {
g_vm->_mouseInfo->setIntent(GrabInfo::kIntAttack);
g_vm->_mouseInfo->setDoable(true);
} else {
g_vm->_mouseInfo->setIntent(GrabInfo::kIntWalkTo);
walkToPos.set(obj->getLocation().u,
obj->getLocation().v,
obj->getLocation().z);
}
} else
// Center actor is not aggressive
{
// If pointing at an actor, set the
// intention to walk to the actor, else
// set the intention to pick up the object
if (isActor(pickedObject)) {
a = (Actor *)obj;
g_vm->_mouseInfo->setIntent(
!a->isDead()
? GrabInfo::kIntWalkTo
: GrabInfo::kIntOpen);
walkToPos.set(obj->getLocation().u,
obj->getLocation().v,
obj->getLocation().z);
} else {
g_vm->_mouseInfo->setIntent(obj->isCarryable()
? GrabInfo::kIntPickUp
: GrabInfo::kIntOpen);
g_vm->_mouseInfo->setDoable(
interruptable
&& a->inReach(obj->getLocation())
&& (a->inRange(obj->getLocation(), 8)
|| lineOfSight(a, obj, terrainTransparent)));
}
}
} else
// The mouse is not pointing at an object
{
// Simply set the intention to walk to the mouse
// pointer
g_vm->_mouseInfo->setIntent(GrabInfo::kIntWalkTo);
if (tileMapControl->isSticky())
setMouseImage(kMouseAutoWalkImage, -8, -8);
walkToPos = tilePickPos;
}
}
}
if (mousePressed
&& !clickActionDone
&& g_vm->_mouseInfo->getObject() == nullptr) {
a = getCenterActor();
// Since the mouse is being dragged, initiate
// the effects of the mouse drag
if (g_vm->_mouseInfo->getIntent() == GrabInfo::kIntWalkTo) {
if (g_vm->_mouseInfo->getDoable()
&& !navigationDelayed) {
MotionTask *mt = a->_moveTask;
if (mt == nullptr || !mt->isWalk()) {
navigateDirect(walkToPos, runFlag);
} else if (updateAlarm.check()) {
mt->changeDirectTarget(
walkToPos,
runFlag);
updateAlarm.set(ticksPerSecond / 2);
}
}
} else if (g_vm->_mouseInfo->getIntent() == GrabInfo::kIntAttack) {
if (g_vm->_mouseInfo->getDoable())
a->attack(GameObject::objectAddress(pickedObject));
}
}
}
//-----------------------------------------------------------------------
// Initialize the tile mode state
void initTileModeState() {
assert(uiKeysEnabled);
aggressiveActFlag = false;
inCombat = false;
combatPaused = false;
}
void saveTileModeState(Common::OutSaveFile *outS) {
debugC(2, kDebugSaveload, "Saving TileModeState");
assert(uiKeysEnabled);
outS->write("TMST", 4);
CHUNK_BEGIN;
out->writeUint16LE(aggressiveActFlag);
out->writeUint16LE(inCombat);
out->writeUint16LE(combatPaused);
debugC(3, kDebugSaveload, "... aggressiveActFlag = %d", aggressiveActFlag);
debugC(3, kDebugSaveload, "... inCombat = %d", inCombat);
debugC(3, kDebugSaveload, "... combatPaused = %d", combatPaused);
if (aggressiveActFlag)
g_vm->_tmm->_timeOfLastAggressiveAct->write(out);
CHUNK_END;
}
void loadTileModeState(Common::InSaveFile *in) {
assert(uiKeysEnabled);
// Simply read in the data
aggressiveActFlag = in->readUint16LE();
inCombat = in->readUint16LE();
combatPaused = in->readUint16LE();
debugC(3, kDebugSaveload, "... aggressiveActFlag = %d", aggressiveActFlag);
debugC(3, kDebugSaveload, "... inCombat = %d", inCombat);
debugC(3, kDebugSaveload, "... combatPaused = %d", combatPaused);
if (aggressiveActFlag)
g_vm->_tmm->_timeOfLastAggressiveAct->read(in);
tileLockFlag = false;
}
/* ===================================================================== *
TileMode management functions
* ===================================================================== */
//-----------------------------------------------------------------------
// Initialize the Tile mode
void TileModeSetup() {
// Load in decorative panels for the main window (for this mode)
mainWindow->setDecorations(mainWindowDecorations, ARRAYSIZE(mainWindowDecorations), imageRes);
// Test to draw borders.
// REM: We should actually have a routine to refresh the window...
mainWindow->draw();
// Create a control covering the map area.
tileMapControl = new gStickyDragControl(*playControls, Rect16(kTileRectX, kTileRectY, kTileRectWidth, kTileRectHeight), 0, cmdClickTileMap);
//Enable Tile Mode Specific Controls
tileControls->enable(true);
initTileBanks();
lastUpdateTime = gameTime;
setCurrentWorld(WorldBaseID);
setCurrentMap(currentWorld->_mapNum);
}
//-----------------------------------------------------------------------
// Cleanup function for Tile mode
void TileModeCleanup() {
//Disable Tile Mode Specific Controls
tileControls->enable(false);
freeAllTileBanks();
delete g_vm->_tileImageBanks;
// freePalette();
// if (tileRes) resFile->disposeContext( tileRes );
// tileRes = NULL;
delete tileMapControl;
// This Fixes the mousePanel That's not set up
g_vm->_toolBase->_mousePanel = nullptr;
mainWindow->removeDecorations();
}
//-----------------------------------------------------------------------
// This code handles most of the periodic repetitive tasks in
// the main game mode.
static int postDisplayFrame = 3;
// We need to test if UI is locked, so as to not pause combat
extern int lockUINest;
void CheckCombat() {
static int flipper = 0;
// Get the actor we're controlling.
Actor *a = getCenterActor();
audioEnvironmentSetAggression(isCenterActorAggressive());
// Check combat mood once every 8 frames or so.
// Otherwise, check for combat start/stop
// (Kludge to balance CPU usage).
if ((++flipper & 0xF) == 0)
CheckCombatMood();
else if (timeSinceLastAggressiveAct() < 60 && areThereActiveEnemies()) {
if (!inCombat) {
inCombat = true;
startCombat();
}
} else {
if (inCombat) {
inCombat = false;
endCombat();
}
}
if (inCombat) {
if (!a->isMoving() && a->isInterruptable() && lockUINest == 0) {
if (!combatPaused) {
combatPaused = true;
pauseCombat();
}
} else {
if (combatPaused) {
combatPaused = false;
resumeCombat();
}
}
}
}
void TileModeHandleTask() {
bool taskChek = false;
// Run any SAGA scripts which are waiting to run.
dispatchScripts();
// update day and night
//mm("daytime transition update loop");
dayNightUpdate();
// If it's time to do a new frame.
if (frameAlarm.check()
&& tileLockFlag == 0) {
if (g_vm->getGameId() == GID_FTA2)
CheckCombat();
updateCalendar();
// update the text status line
StatusLine->experationCheck();
if (g_vm->getGameId() == GID_FTA2)
doBackgroundSimulation();
// do an alarm check for container views
if (containerObjTextAlarm.check()) {
g_vm->_cnm->_objTextAlarm = true;
}
if (g_vm->_cnm->_objTextAlarm == true) {
// if the mouse is in a container...
if (g_vm->_cnm->_mouseInView) {
g_vm->_mouseInfo->setText(g_vm->_cnm->_mouseText);
}
}
if (g_vm->_toolBase->isMousePanel(tileMapControl)) {
// Get the actor we're controlling.
Actor *a = getCenterActor();
// If mouse is near edge of screen, then run.
runFlag = lastMousePos.x < runThreshhold
|| lastMousePos.x >= kTileRectWidth - runThreshhold
|| lastMousePos.y < runThreshhold
|| lastMousePos.y >= kTileRectHeight - runThreshhold;
// Calculate the mouse's position on the tile map.
if (runFlag) {
// Calculate the mouse's position on the tilemap,
// without regard to the actual shape of the terrain.
tilePickPos = pickTilePos(lastMousePos, a->getLocation());
tilePickExactPos = tilePickPos;
pickedTAI = nullptr;
} else {
// Calculate the mouse's position on the tilemap,
// including the shape of the terrain. Actually
// this returns two separate coords: The exact point
// clicked on, and the projection on the floor
// beneath the clicked point.
tilePickExactPos = pickTile(lastMousePos,
a->getLocation(),
&tilePickPos,
&pickedTAI);
}
pickedObject = pickObject(lastMousePos, objPickPos);
GameObject *item = GameObject::objectAddress(pickedObject);
// Find Out If Terrain Or Object Is Deeper
if (!item->isObscured() || (!isActor(item) && !objRoofRipped(item))) {
if (tilePickExactPos.z > objPickPos.z) {
pickedObject = Nothing;
item = GameObject::objectAddress(pickedObject);
}
}
// Determine if the mouse is pointing at a new object
if (pickedObject != lastPickedObject) {
lastPickedObject = pickedObject;
// Remove current mouse cursor text and gauge
g_vm->_mouseInfo->setText(nullptr);
g_vm->_mouseInfo->clearGauge();
// If mouse in on object set alarm to determine when
// to display the object's name
if (pickedObject != Nothing)
dispObjNameAlarm.set(ticksPerSecond / 2);
}
if (pickedObject != Nothing) {
// Determine if it is time to display the name of the
// object at which the mouse is pointing
if (dispObjNameAlarm.check()) {
const int bufSize = 40;
char cursorText[bufSize];
// get the object text into the buffer
item->objCursorText(cursorText, bufSize);
g_vm->_mouseInfo->setText(cursorText);
if (isActor(pickedObject)) {
a = (Actor *)GameObject::objectAddress(pickedObject);
g_vm->_mouseInfo->setGauge(a->getStats()->vitality, a->getBaseStats()->vitality);
} else {
g_vm->_mouseInfo->clearGauge();
}
}
}
evalMouseState();
}
if (navigationDelayed && delayedNavigation.delay.check()) {
if (delayedNavigation.pathFindFlag)
navigatePath(delayedNavigation.walkToPos);
else
navigateDirect(delayedNavigation.walkToPos, false);
navigationDelayed = false;
}
updateContainerWindows();
updateActiveRegions();
checkTimers();
checkSensors();
updateObjectStates();
updateActorStates();
// Update positions of all objects
moveActors(0); // for objects with motion task.
// Update the states of all active terrain
moveActiveTerrain(0); // for terrain with activity tasks.
// Set the time of the next frame
frameAlarm.set(framePeriod);
updateMainDisplay();
if (inCombat || postDisplayFrame++ > 2)
taskChek = true;
} else if (postDisplayFrame) {
taskChek = true;
}
if (taskChek) {
postDisplayFrame = 0;
updateActorTasks();
}
}
//-----------------------------------------------------------------------
extern void toggleAgression(PlayerActorID bro, bool all);
extern void toggleBanding(PlayerActorID bro, bool all);
extern void toggleIndivMode();
void TileModeHandleKey(int16 key, int16 qual) {
TilePoint Pos, ActorTP;
Actor *a = getCenterActor();
Location l(a->getLocation(), a->IDParent());
//GameObject *object = (GameObject *)getCenterActor();
lastUnusedKey = '\0';
//This is for moving center actor in cardinal directions
//by amount specified by loc const int moveDist and using keypad
//without num lock on
#if CHEATMOVE
cheatMove(key);
#endif
// If there is currently a speech balloon up, and the
// speech balloon has embedded buttons, then disallow
// user input -- except that for now we will still allow
// the special 'quit' key.
if (speechButtonCount > 0) {
if (key != 0x1b && key != 'b') return;
}
//-----------------------------------------------------------------------
switch (tolower(key)) {
case ' ':
abortSpeech();
if (uiKeysEnabled) {
if (tileMapControl->isSticky()) {
tileMapControl->setSticky(false);
mousePressed = false;
setMouseImage(kMouseArrowImage, 0, 0);
evalMouseState();
}
MotionTask::wait(*a);
}
break;
case 'a':
if (uiKeysEnabled)
toggleAgression(getCenterActorPlayerID(), qual & kQualifierShift);
break;
case 'b':
if (uiKeysEnabled)
toggleBanding(getCenterActorPlayerID(), qual & kQualifierShift);
break;
case '\t':
if (uiKeysEnabled)
toggleIndivMode();
break;
case '1':
if (uiKeysEnabled)
setCenterBrother(FTA_JULIAN);
break;
case '2':
if (uiKeysEnabled)
setCenterBrother(FTA_PHILIP);
break;
case '3':
if (uiKeysEnabled)
setCenterBrother(FTA_KEVIN);
break;
case 'o':
if (uiKeysEnabled)
OptionsDialog();
break;
// Keyboard equivalents for mental containers
case 'i':
if (uiKeysEnabled)
OpenMindContainer(getCenterActorPlayerID(), true, 0);
break;
case 's':
if (uiKeysEnabled)
OpenMindContainer(getCenterActorPlayerID(), true, 1);
break;
case 'k':
if (uiKeysEnabled)
OpenMindContainer(getCenterActorPlayerID(), true, 2);
break;
case 'm':
toggleMusic();
break;
case 0x1b: // Escape key
if (uiKeysEnabled)
OptionsDialog();
break;
default:
if (uiKeysEnabled)
lastUnusedKey = key;
}
}
//-----------------------------------------------------------------------
// Handle mouse actions on the tile map "control".
static APPFUNC(cmdClickTileMap) {
static bool dblClick = false;
// REM: This code needs to be moved elsewhere. We put it
// here for testing purposes only. It should actually go on
// it's own panel, which overrides all other panels, and
// that should be part of a special "speech button" mode.
if (!uiKeysEnabled) return;
switch (ev.eventType) {
case gEventRMouseDown:
#if CHEATMOVE
selectedObject = pickedObject;
#endif
if (g_vm->_teleportOnClick) {
if (g_vm->getEventManager()->getModifierState() & Common::KBD_SHIFT) {
for (ObjectID pid = ActorBaseID; pid < ActorBaseID + kPlayerActors; ++pid) {
Actor *p = (Actor *)GameObject::objectAddress(pid);
p->setLocation(walkToPos);
}
} else {
getCenterActor()->setLocation(walkToPos);
}
} else if (isActor(pickedObject)) {
PlayerActorID playerID;
if (actorIDToPlayerID(pickedObject, playerID))
setAggression(playerID, !isAggressive(playerID));
}
break;
case gEventMouseMove:
case gEventMouseDrag:
if (ev.value & gGenericControl::leave) {
mousePressed = false;
if (g_vm->_mouseInfo->getObject() == nullptr)
g_vm->_mouseInfo->setIntent(GrabInfo::kIntWalkTo);
g_vm->_mouseInfo->setDoable(true);
// Remove any mouse text
lastPickedObject = Nothing;
g_vm->_mouseInfo->setText(nullptr);
g_vm->_mouseInfo->clearGauge();
}
lastMousePos.set(ev.mouse.x, ev.mouse.y);
break;
case gEventMouseDown:
mousePressed = true;
clickActionDone = false;
{
// Get the center actor's ID and a pointer to the center
// actor's structure
ObjectID centerActorID = getCenterActorID();
Actor *centerActorPtr =
(Actor *)GameObject::objectAddress(centerActorID);
if ((mouseObject = g_vm->_mouseInfo->getObject()) != nullptr) {
// If we are using an intangible object (spell) then consider
// the owner of the spell to be the center actor for the rest
// of this action.
if (mouseObject->proto()->containmentSet() & (ProtoObj::isIntangible | ProtoObj::isSpell | ProtoObj::isSkill)) {
ObjectID possessor = mouseObject->possessor();
if (possessor != Nothing) {
centerActorID = possessor;
centerActorPtr = (Actor *)GameObject::objectAddress(possessor);
}
}
if (pickedObject != Nothing) {
// we dropped the object onto another object
if (g_vm->_mouseInfo->getDoable()) {
int16 intent = g_vm->_mouseInfo->getIntent();
g_vm->_mouseInfo->replaceObject();
if (intent == GrabInfo::kIntUse) {
MotionTask::useObjectOnObject(
*centerActorPtr,
*mouseObject,
*GameObject::objectAddress(pickedObject));
} else if (intent == GrabInfo::kIntDrop) {
MotionTask::dropObjectOnObject(
*centerActorPtr,
*mouseObject,
*GameObject::objectAddress(pickedObject),
g_vm->_mouseInfo->getMoveCount());
}
((gGenericControl *)ev.panel)->disableDblClick();
clickActionDone = true;
} else if (g_vm->_mouseInfo->getIntent() == GrabInfo::kIntUse) {
g_vm->_mouseInfo->replaceObject();
clickActionDone = true;
}
} else if (pickedTAI != nullptr) {
// we dropped the object onto active terrain
if (g_vm->_mouseInfo->getDoable()) {
if (g_vm->_mouseInfo->getIntent() == GrabInfo::kIntDrop
|| g_vm->_mouseInfo->getIntent() == GrabInfo::kIntUse) {
int16 intent = g_vm->_mouseInfo->getIntent();
g_vm->_mouseInfo->replaceObject();
if (intent == GrabInfo::kIntDrop) {
MotionTask::dropObjectOnTAI(
*centerActorPtr,
*mouseObject,
*pickedTAI,
Location(tilePickPos, currentWorld->thisID()));
} else {
TilePoint TAILoc;
TAILoc = getClosestPointOnTAI(pickedTAI, centerActorPtr);
if (centerActorPtr->inReach(TAILoc)
&& (centerActorPtr->inRange(TAILoc, 8)
|| lineOfSight(
centerActorPtr,
TAILoc,
terrainTransparent)))
MotionTask::useObjectOnTAI(
*centerActorPtr,
*mouseObject,
*pickedTAI);
}
((gGenericControl *)ev.panel)->disableDblClick();
clickActionDone = true;
}
} else if (g_vm->_mouseInfo->getIntent() == GrabInfo::kIntUse) {
g_vm->_mouseInfo->replaceObject();
clickActionDone = true;
}
} else if (pickedObject == Nothing) {
// we dropped the object on the ground
if (g_vm->_mouseInfo->getIntent() == GrabInfo::kIntDrop
&& g_vm->_mouseInfo->getDoable()) {
g_vm->_mouseInfo->replaceObject();
MotionTask::dropObject(
*centerActorPtr,
*mouseObject,
Location(tilePickPos, currentWorld->thisID()),
g_vm->_mouseInfo->getMoveCount());
((gGenericControl *)ev.panel)->disableDblClick();
clickActionDone = true;
} else if (g_vm->_mouseInfo->getIntent() == GrabInfo::kIntUse
&& g_vm->_mouseInfo->getDoable()) {
// New for spells - this enables objects to be used on a
// general location (for area spells etc)
g_vm->_mouseInfo->replaceObject();
MotionTask::useObjectOnLocation(
*centerActorPtr,
*mouseObject,
Location(tilePickPos, currentWorld->thisID()));
clickActionDone = true;
} else if (g_vm->_mouseInfo->getIntent() == GrabInfo::kIntUse) {
g_vm->_mouseInfo->replaceObject();
clickActionDone = true;
}
}
} else if (pickedObject != Nothing) {
//GameObject *obj = GameObject::objectAddress(pickedObject);
if (g_vm->_mouseInfo->getDoable()) {
PlayerActorID pID;
if (actorIDToPlayerID(pickedObject, pID) && !isBrotherDead(pID)) {
setCenterBrother(pID);
clickActionDone = true;
} else if (g_vm->_mouseInfo->getIntent() == GrabInfo::kIntPickUp
|| g_vm->_mouseInfo->getIntent() == GrabInfo::kIntOpen) {
GameObject *pickedObjPtr =
GameObject::objectAddress(pickedObject);
int16 quantity = 1;
MotionTask::turnTowards(
*centerActorPtr,
GameObject::objectAddress(pickedObject)->getLocation());
if (pickedObjPtr->proto()->flags & ResourceObjectPrototype::objPropMergeable)
quantity = pickedObjPtr->getExtra();
if (pickedObjPtr->take(centerActorID, quantity))
clickActionDone = true;
} else if (g_vm->_mouseInfo->getIntent() == GrabInfo::kIntAttack) {
centerActorPtr->attack(
GameObject::objectAddress(pickedObject));
((gGenericControl *)ev.panel)->disableDblClick();
}
}
}
// We're not pointing at an object and the mouse cursor
// does not have an object
else {
if (g_vm->_mouseInfo->getIntent() == GrabInfo::kIntWalkTo
&& g_vm->_mouseInfo->getDoable()) {
if (pickedTAI == nullptr) {
navigateDirect(walkToPos, false);
// ( ( gGenericControl * )ev.panel )->disableDblClick();
} else {
navigationDelayed = true;
delayedNavigation.walkToPos = walkToPos;
delayedNavigation.pathFindFlag = false;
delayedNavigation.delay.set(ticksPerSecond / 2);
}
pathFindAlarm.set(ticksPerSecond / 2);
}
}
}
break;
case gEventMouseUp:
mousePressed = false;
if (dblClick)
dblClick = false;
else {
if (pathFindAlarm.check()) { // mouse click was too long for path find
if (g_vm->_mouseInfo->getIntent() == GrabInfo::kIntWalkTo) {
Actor *a = getCenterActor();
if (a->_moveTask && a->_moveTask->isWalk())
a->_moveTask->finishWalk();
}
navigationDelayed = false;
} else {
if (navigationDelayed) {
delayedNavigation.walkToPos = walkToPos;
delayedNavigation.pathFindFlag = true;
delayedNavigation.delay.set(ticksPerSecond / 2);
} else {
Actor *a = getCenterActor();
if ((walkToPos - a->getLocation()).quickHDistance() > 24)
navigatePath(walkToPos);
else
navigateDirect(walkToPos, false);
}
}
}
break;
case gEventDoubleClick:
dblClick = true;
navigationDelayed = false;
if ((mouseObject = g_vm->_mouseInfo->getObject()) != nullptr) {
g_vm->_mouseInfo->replaceObject();
MotionTask::useObject(*getCenterActor(), *mouseObject);
} else if (pickedObject != Nothing) {
GameObject *obj = GameObject::objectAddress(pickedObject);
if (g_vm->_mouseInfo->getDoable()) {
// Double-click on an actor is the same as "greet".
if (isActor(pickedObject)
&& !((Actor *)obj)->isDead()
&& !isCenterActorAggressive()) {
ActorProto *proto = (ActorProto *)obj->proto();
proto->greetActor(pickedObject, getCenterActorID());
} else if (g_vm->_mouseInfo->getIntent() == GrabInfo::kIntPickUp
|| g_vm->_mouseInfo->getIntent() == GrabInfo::kIntOpen
|| (isActor(pickedObject) && ((Actor *)obj)->isDead())) {
GameObject *pickedObjPtr =
GameObject::objectAddress(pickedObject);
MotionTask::useObject(*getCenterActor(), *pickedObjPtr);
clickActionDone = true;
}
}
} else if (pickedTAI != nullptr) {
Actor *a = getCenterActor();
TilePoint TAILoc;
TAILoc = getClosestPointOnTAI(pickedTAI, a);
if (a->inRange(TAILoc, 32)
&& (a->inRange(TAILoc, 8)
|| lineOfSight(a, TAILoc, terrainTransparent)))
MotionTask::useTAI(*a, *pickedTAI);
} else {
tileMapControl->setSticky(true);
setMouseImage(kMouseAutoWalkImage, -8, -8);
mousePressed = true;
}
break;
default:
break;
}
}
//-----------------------------------------------------------------------
// Sets up a motion task for the main character.
void navigateDirect(TilePoint pick, bool runFlag_) {
Actor *a = getCenterActor(); // addr of actor we control
if (a) {
updateAlarm.set(ticksPerSecond / 2);
// REM: Do running here...
MotionTask::walkToDirect(*a, pick, runFlag_, false);
}
}
//-----------------------------------------------------------------------
// Sets up a motion task and a path find request for the main character.
void navigatePath(TilePoint pick) {
Actor *a = getCenterActor(); // addr of actor we control
if (a) {
if (a->isMoving())
// if motion task already exists, change the target
a->_moveTask->changeTarget(pick);
else
// else create a new motion task
MotionTask::walkTo(*a, pick, false, false);
}
}
#if CHEATMOVE
void cheatMove(int16 key) {
if (selectedObject == Nothing) return;
if (tolower(key) == 'n') {
nudge = !nudge;
return;
}
union {
int16 key1;
char key_ch[2];
} get;
GameObject *obj = GameObject::objectAddress(selectedObject);
TilePoint t = obj->getLocation();
int moveDist = nudge ? 1 : 64;
get.key1 = key;
if (get.key_ch[0] == 0) {
switch (get.key_ch[1]) {
case 72: //Up
t.u += moveDist;
t.v += moveDist;
obj->move(t);
break;
case 80: //Down
t.u -= moveDist;
t.v -= moveDist;
obj->move(t);
break;
case 73: //Up Right
t.u += moveDist;
obj->move(t);
break;
case 71: //Up Left
t.v += moveDist;
obj->move(t);
break;
case 81: //Down Right
t.v -= moveDist;
obj->move(t);
break;
case 79: //Down Left
t.u -= moveDist;
obj->move(t);
break;
case 75: //Left
t.u -= moveDist;
t.v += moveDist;
obj->move(t);
break;
case 77: //Right
t.u += moveDist;
t.v -= moveDist;
obj->move(t);
break;
}
WriteStatusF(3, "U %d V %d Z %d", t.u, t.v, t.z);
}
}
#endif
/* ===================================================================== *
gStickyDragControl class: a gGenericControl with a _sticky mouse
* ===================================================================== */
gStickyDragControl::gStickyDragControl(gPanelList &list, const Rect16 &box,
uint16 ident, AppFunc *cmd)
: gGenericControl(list, box, ident, cmd) {
_sticky = false;
}
void gStickyDragControl::deactivate() {
if (_sticky) setMouseImage(kMouseArrowImage, 0, 0);
_sticky = false;
gGenericControl::deactivate();
}
//void gStickyDragControl::pointerMove( gPanelMessage & )
//{
// notify( gEventMouseMove, 0 );
//}
bool gStickyDragControl::pointerHit(gPanelMessage &msg) {
if (_sticky) setMouseImage(kMouseArrowImage, 0, 0);
_sticky = false;
return gGenericControl::pointerHit(msg);
}
void gStickyDragControl::pointerRelease(gPanelMessage &msg) {
if (_sticky == false)
gGenericControl::pointerRelease(msg);
}
void noStickyMap() {
((gPanel *)tileMapControl)->deactivate();
mousePressed = false;
}
TileModeManager::TileModeManager() {
_timeOfLastAggressiveAct = new CalendarTime;
}
TileModeManager::~TileModeManager() {
delete _timeOfLastAggressiveAct;
}
} // end of namespace Saga2