Files
scummvm-cursorfix/engines/ultima/ultima8/world/target_reticle_process.cpp
2026-02-02 04:50:13 +01:00

244 lines
7.0 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
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "ultima/ultima8/ultima8.h"
#include "ultima/ultima8/gumps/message_box_gump.h"
#include "ultima/ultima8/games/game_data.h"
#include "ultima/ultima8/kernel/kernel.h"
#include "ultima/ultima8/world/actors/actor.h"
#include "ultima/ultima8/world/target_reticle_process.h"
#include "ultima/ultima8/world/sprite_process.h"
#include "ultima/ultima8/world/world.h"
#include "ultima/ultima8/world/current_map.h"
#include "ultima/ultima8/world/get_object.h"
namespace Ultima {
namespace Ultima8 {
TargetReticleProcess *TargetReticleProcess::_instance = nullptr;
DEFINE_RUNTIME_CLASSTYPE_CODE(TargetReticleProcess)
TargetReticleProcess::TargetReticleProcess() : Process(), _reticleEnabled(true),
_lastUpdate(0), _reticleSpriteProcess(0), _lastTargetDir(dir_current), _lastTargetItem(0),
_reticleStyle(0) {
_instance = this;
_type = 1; // persistent
if (GAME_IS_REGRET)
_reticleStyle = 3;
}
TargetReticleProcess::~TargetReticleProcess() {
if (_instance == this)
_instance = nullptr;
}
void TargetReticleProcess::run() {
Kernel *kernel = Kernel::get_instance();
assert(kernel);
uint32 frameno = kernel->getFrameNum();
Actor *mainactor = getControlledActor();
Process *spriteProc = nullptr;
if (_reticleSpriteProcess != 0) {
spriteProc = kernel->getProcess(_reticleSpriteProcess);
}
if (!_reticleEnabled || (mainactor && !mainactor->isInCombat()) || !mainactor) {
// Reticle not enabled, actor not in combat, or actor is gone.
if (spriteProc) {
spriteProc->terminate();
}
_reticleSpriteProcess = 0;
return;
}
if (_reticleSpriteProcess && (!spriteProc || spriteProc->is_terminated())) {
// The sprite proc has finished but the target is still valid - replace
// with one that just holds the last frame.
Item *target = getItem(_lastTargetItem);
if (target)
putTargetReticleOnItem(target, true);
}
if (frameno - _lastUpdate < 2 * Kernel::FRAMES_PER_SECOND) {
return;
}
bool changed = findTargetItem();
if (spriteProc && changed) {
// Terminate the old process.
spriteProc->terminate();
}
_lastUpdate = frameno;
}
bool TargetReticleProcess::findTargetItem() {
Actor *mainactor = getControlledActor();
CurrentMap *currentmap = World::get_instance()->getCurrentMap();
bool changed = false;
if (!mainactor || !currentmap)
return false;
Direction dir = mainactor->getDir();
Point3 pt = mainactor->getLocation();
Item *item = currentmap->findBestTargetItem(pt.x, pt.y, pt.z, dir, dirmode_16dirs);
if (item && item->getObjId() != _lastTargetItem) {
Item *lastItem = getItem(_lastTargetItem);
if (lastItem)
lastItem->clearExtFlag(Item::EXT_TARGET);
putTargetReticleOnItem(item, false);
_lastTargetDir = dir;
changed = true;
} else if (!item) {
if (_lastTargetItem) {
debug("New reticle target: NONE");
Item *lastItem = getItem(_lastTargetItem);
if (lastItem)
lastItem->clearExtFlag(Item::EXT_TARGET);
}
clearSprite();
changed = true;
}
// else, already targeting the right thing. do nothing.
return changed;
}
void TargetReticleProcess::avatarMoved() {
_lastUpdate = 0;
}
void TargetReticleProcess::putTargetReticleOnItem(Item *item, bool only_last_frame) {
// TODO: the game does a bunch of other maths here to pick the right location.
// This is an over-simplification and is usually too high so it's
// hacked a little lower.
Point3 pt = item->getCentre();
pt.z -= 8;
Process *p;
const int first_frame = _reticleStyle * 6;
const int last_frame = first_frame + 5;
if (!only_last_frame)
p = new SpriteProcess(0x59a, first_frame, last_frame, 1, 10, pt.x, pt.y, pt.z, false);
else
p = new SpriteProcess(0x59a, last_frame, last_frame, 1, 1000, pt.x, pt.y, pt.z, false);
_reticleSpriteProcess = Kernel::get_instance()->addProcess(p);
_lastTargetItem = item->getObjId();
item->setExtFlag(Item::EXT_TARGET);
debug("New reticle target: %d (%d, %d, %d)", _lastTargetItem, pt.x, pt.y, pt.z);
}
void TargetReticleProcess::itemMoved(Item *item) {
assert(item);
if (!_reticleSpriteProcess || item->getObjId() != _lastTargetItem) {
clearSprite();
return;
}
Point3 pt = item->getCentre();
Actor *mainactor = getControlledActor();
int actordir = -1;
int dirtoitem = -2;
if (mainactor) {
actordir = mainactor->getDir();
dirtoitem = mainactor->getDirToItemCentre(*item);
}
SpriteProcess *spriteproc = dynamic_cast<SpriteProcess *>(Kernel::get_instance()->getProcess(_reticleSpriteProcess));
if (spriteproc) {
if (actordir != _lastTargetDir || dirtoitem != _lastTargetDir) {
spriteproc->terminate();
_reticleSpriteProcess = 0;
clearSprite();
} else {
spriteproc->move(pt.x, pt.y, pt.z);
}
}
}
void TargetReticleProcess::clearSprite() {
_reticleSpriteProcess = 0;
if (_lastTargetItem) {
Item *item = getItem(_lastTargetItem);
if (item) {
item->clearExtFlag(Item::EXT_TARGET);
}
}
_lastTargetItem = 0;
_lastTargetDir = dir_current;
}
void TargetReticleProcess::toggle() {
bool newstate = !getEnabled();
Std::string msg = newstate ? _TL_("TARGETING RETICLE ACTIVE") : _TL_("TARGETING RETICLE INACTIVE");
MessageBoxGump::Show("", msg, TEX32_PACK_RGB(0x70, 0x70, 0x70));
setEnabled(newstate);
}
void TargetReticleProcess::toggleReticleStyle() {
if (GAME_IS_REMORSE) {
_reticleStyle = 0;
return;
}
_reticleStyle++;
if (_reticleStyle >= 4)
_reticleStyle = 0;
}
void TargetReticleProcess::saveData(Common::WriteStream *ws) {
Process::saveData(ws);
ws->writeByte(_reticleEnabled ? 1 : 0);
ws->writeUint32LE(_lastUpdate);
ws->writeUint16LE(_reticleSpriteProcess);
ws->writeByte(_lastTargetDir);
ws->writeUint16LE(_lastTargetItem);
if (GAME_IS_REGRET)
ws->writeUint16LE(_reticleStyle);
}
bool TargetReticleProcess::loadData(Common::ReadStream *rs, uint32 version) {
if (!Process::loadData(rs, version)) return false;
_reticleEnabled = (rs->readByte() != 0);
_lastUpdate = rs->readUint32LE();
_reticleSpriteProcess = rs->readUint16LE();
_lastTargetDir = static_cast<Direction>(rs->readByte());
_lastTargetItem = rs->readUint16LE();
if (GAME_IS_REGRET)
_reticleStyle = rs->readUint16LE();
_type = 1; // should be persistent but older savegames may not know that.
return true;
}
} // End of namespace Ultima8
} // End of namespace Ultima