Initial commit

This commit is contained in:
2026-02-02 04:50:13 +01:00
commit 5b11698731
22592 changed files with 7677434 additions and 0 deletions

View File

@@ -0,0 +1,62 @@
/* 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/kernel/delay_process.h"
namespace Ultima {
namespace Ultima8 {
DEFINE_RUNTIME_CLASSTYPE_CODE(DelayProcess)
DelayProcess::DelayProcess(int count) : Process(), _count(count) {
}
DelayProcess::~DelayProcess() {
}
void DelayProcess::run() {
if (--_count == 0)
terminate();
}
Common::String DelayProcess::dumpInfo() const {
return Process::dumpInfo() +
Common::String::format(", frames left: %d", _count);
}
bool DelayProcess::loadData(Common::ReadStream *rs, uint32 version) {
if (!Process::loadData(rs, version)) return false;
_count = static_cast<int>(rs->readUint32LE());
return true;
}
void DelayProcess::saveData(Common::WriteStream *ws) {
Process::saveData(ws);
ws->writeUint32LE(static_cast<uint32>(_count));
}
} // End of namespace Ultima8
} // End of namespace Ultima

View File

@@ -0,0 +1,54 @@
/* 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/>.
*
*/
#ifndef ULTIMA8_KERNEL_DELAYPROCESS_H
#define ULTIMA8_KERNEL_DELAYPROCESS_H
#include "ultima/ultima8/kernel/process.h"
#include "ultima/ultima8/misc/classtype.h"
namespace Ultima {
namespace Ultima8 {
// a process that waits a number of ticks before terminating
class DelayProcess : public Process {
public:
explicit DelayProcess(int count_ = 0);
~DelayProcess() override;
ENABLE_RUNTIME_CLASSTYPE()
void run() override;
Common::String dumpInfo() const override;
bool loadData(Common::ReadStream *rs, uint32 version);
void saveData(Common::WriteStream *ws) override;
protected:
int _count;
};
} // End of namespace Ultima8
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,500 @@
/* 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/misc/debugger.h"
#include "ultima/ultima8/kernel/kernel.h"
#include "ultima/ultima8/kernel/process.h"
#include "ultima/ultima8/misc/id_man.h"
#include "ultima/ultima8/misc/set.h"
#include "ultima/ultima8/ultima8.h"
namespace Ultima {
namespace Ultima8 {
Kernel *Kernel::_kernel = nullptr;
const uint32 Kernel::TICKS_PER_FRAME = 2;
const uint32 Kernel::TICKS_PER_SECOND = 60;
const uint32 Kernel::FRAMES_PER_SECOND = Kernel::TICKS_PER_SECOND / Kernel::TICKS_PER_FRAME;
// A special proc type which means "all"
const uint16 Kernel::PROC_TYPE_ALL = 6;
// The same as above, but for Crusader.
// Used in Usecode functions to translate.
static const uint16 CRU_PROC_TYPE_ALL = 0xc;
Kernel::Kernel() : _loading(false), _tickNum(0), _paused(0),
_runningProcess(nullptr), _frameByFrame(false) {
debug(1, "Creating Kernel...");
_kernel = this;
_pIDs = new idMan(1, 32766, 128);
_currentProcess = _processes.end();
}
Kernel::~Kernel() {
reset();
debug(1, "Destroying Kernel...");
_kernel = nullptr;
delete _pIDs;
}
void Kernel::reset() {
debug(1, "Resetting Kernel...");
for (auto *p : _processes) {
if (p->_flags & Process::PROC_TERM_DISPOSE && p != _runningProcess) {
delete p;
} else {
p->_flags |= Process::PROC_TERMINATED;
}
}
_processes.clear();
_currentProcess = _processes.end();
_pIDs->clearAll();
_paused = 0;
_runningProcess = nullptr;
// if we're in frame-by-frame mode, reset to a _paused state
if (_frameByFrame) _paused = 1;
}
ProcId Kernel::assignPID(Process *proc) {
// to prevent new processes from getting a PID while loading
if (_loading) return 0xFFFF;
// Get a pID
proc->_pid = _pIDs->getNewID();
return proc->_pid;
}
ProcId Kernel::addProcess(Process *proc, bool dispose) {
#if 0
for (const auto *p : _processes) {
if (p == proc)
return 0;
}
#endif
assert(proc->_pid != 0 && proc->_pid != 0xFFFF);
#if 0
debug(1, "[Kernel] Adding process %p, pid = %u type %s",
proc, proc->_pid, proc->GetClassType()._className);
#endif
if (dispose) {
proc->_flags |= Process::PROC_TERM_DISPOSE;
}
setNextProcess(proc);
return proc->_pid;
}
ProcId Kernel::addProcessExec(Process *proc, bool dispose) {
#if 0
for (const auto *p : _processes) {
if (p == proc)
return 0;
}
#endif
assert(proc->_pid != 0 && proc->_pid != 0xFFFF);
#if 0
debug(1, "[Kernel] Adding process %p, pid = %u type %s",
proc, proc->_pid, proc->GetClassType()._className);
#endif
if (dispose) {
proc->_flags |= Process::PROC_TERM_DISPOSE;
}
_processes.push_back(proc);
proc->_flags |= Process::PROC_ACTIVE;
Process *oldrunning = _runningProcess;
_runningProcess = proc;
proc->run();
_runningProcess = oldrunning;
return proc->_pid;
}
void Kernel::runProcesses() {
if (!_paused)
_tickNum++;
if (_processes.size() == 0) {
warning("Process queue is empty?! Aborting.");
return;
}
int num_run = 0;
_currentProcess = _processes.begin();
while (_currentProcess != _processes.end()) {
Process *p = *_currentProcess;
if (!_paused && ((p->_flags & (Process::PROC_TERMINATED |
Process::PROC_TERM_DEFERRED))
== Process::PROC_TERM_DEFERRED)) {
p->terminate();
}
if (!(p->is_terminated() || p->is_suspended()) &&
(!_paused || (p->_flags & Process::PROC_RUNPAUSED)) &&
(_paused || _tickNum % p->getTicksPerRun() == 0)) {
_runningProcess = p;
p->run();
_runningProcess = nullptr;
num_run++;
//
// WORKAROUND:
// In Crusader: No Remorse, the HOVER near the end of Mission 3
// (Floor 1) gets stuck in a tight loop after moving to the
// destination (path egg frame 0).
//
// Something is probably not right about the switch trigger, but until
// we can work out what it is avoid the game totally hanging at this
// point.
//
// If this threshold is set too low, it can cause issues with U8 map
// transitions (eg, bug #12913). If it's too high, Crusader locks up
// for a really long time at this point. Set it high enough that
// a process going through all map items should still terminate.
//
if (((num_run > 8192 && GAME_IS_CRUSADER) || num_run > 65534)
&& !p->is_terminated()) {
warning("Seem to be stuck in process loop - killing current process");
p->fail();
}
if (_currentProcess == _processes.end()) {
// If this happens then the list was reset so delete the process and return.
if (p->_flags & Process::PROC_TERM_DISPOSE) {
delete p;
}
return;
}
}
if (!_paused && (p->_flags & Process::PROC_TERMINATED)) {
// process is killed, so remove it from the list
_currentProcess = _processes.erase(_currentProcess);
// Clear pid
_pIDs->clearID(p->_pid);
if (p->_flags & Process::PROC_TERM_DISPOSE) {
delete p;
}
} else if (!_paused && (p->_flags & Process::PROC_TERM_DEFERRED) && GAME_IS_CRUSADER) {
//
// In Crusader, move term deferred processes to the end to clean up after
// others have run. This gets the right speed on ELEVAT (which should
// execute one movement per tick)
//
// In U8, frame-count comparison for Devon turning at the start shows this
// *shouldn't* be used, and the process should be cleaned up next tick.
//
_processes.push_back(p);
_currentProcess = _processes.erase(_currentProcess);
} else {
++_currentProcess;
}
}
if (!_paused && _frameByFrame) pause();
}
void Kernel::setNextProcess(Process *proc) {
if (_currentProcess != _processes.end() && *_currentProcess == proc) return;
if (proc->_flags & Process::PROC_ACTIVE) {
for (ProcessIterator it = _processes.begin();
it != _processes.end(); ++it) {
if (*it == proc) {
_processes.erase(it);
break;
}
}
} else {
proc->_flags |= Process::PROC_ACTIVE;
}
if (_currentProcess == _processes.end()) {
// Not currently running processes, add to the start of the next run.
_processes.push_front(proc);
} else {
ProcessIterator t = _currentProcess;
++t;
_processes.insert(t, proc);
}
}
Process *Kernel::getProcess(ProcId pid) {
for (auto *p : _processes) {
if (p->_pid == pid)
return p;
}
return nullptr;
}
void Kernel::kernelStats() {
g_debugger->debugPrintf("Kernel memory stats:\n");
g_debugger->debugPrintf("Processes : %u/32765\n", _processes.size());
}
void Kernel::processTypes() {
g_debugger->debugPrintf("Current process types:\n");
Common::HashMap<Common::String, unsigned int> processtypes;
for (const auto *p : _processes) {
processtypes[p->GetClassType()._className]++;
}
for (const auto &i : processtypes) {
g_debugger->debugPrintf("%s: %u\n", i._key.c_str(), i._value);
}
}
uint32 Kernel::getNumProcesses(ObjId objid, uint16 processtype) {
uint32 count = 0;
for (const auto *p : _processes) {
// Don't count us, we are not really here
if (p->is_terminated()) continue;
if ((objid == 0 || objid == p->_itemNum) &&
(processtype == PROC_TYPE_ALL || processtype == p->_type))
count++;
}
return count;
}
Process *Kernel::findProcess(ObjId objid, uint16 processtype) {
for (auto *p : _processes) {
// Don't count us, we are not really here
if (p->is_terminated()) continue;
if ((objid == 0 || objid == p->_itemNum) &&
(processtype == PROC_TYPE_ALL || processtype == p->_type)) {
return p;
}
}
return nullptr;
}
void Kernel::killProcesses(ObjId objid, uint16 processtype, bool fail) {
for (auto *p : _processes) {
if (p->_itemNum != 0 && (objid == 0 || objid == p->_itemNum) &&
(processtype == PROC_TYPE_ALL || processtype == p->_type) &&
!(p->_flags & Process::PROC_TERMINATED) &&
!(p->_flags & Process::PROC_TERM_DEFERRED)) {
if (fail)
p->fail();
else
p->terminate();
}
}
}
void Kernel::killProcessesNotOfType(ObjId objid, uint16 processtype, bool fail) {
for (auto *p : _processes) {
// * If objid is 0, terminate procs for all objects.
// * Never terminate procs with objid 0
if (p->_itemNum != 0 && (objid == 0 || objid == p->_itemNum) &&
(p->_type != processtype) &&
!(p->_flags & Process::PROC_TERMINATED) &&
!(p->_flags & Process::PROC_TERM_DEFERRED)) {
if (fail)
p->fail();
else
p->terminate();
}
}
}
void Kernel::killAllProcessesNotOfTypeExcludeCurrent(uint16 processtype, bool fail) {
Common::HashMap<ProcId, bool> procsToSave;
Common::Array<ProcId> queue;
// Create a list of all the processes and their waiting parents that we can't kill.
if (_runningProcess) {
queue.push_back(_runningProcess->_pid);
while (!queue.empty()) {
ProcId procToSave = queue.back();
queue.pop_back();
if (!procsToSave.contains(procToSave)) {
Process *p = getProcess(procToSave);
if (p) {
procsToSave[procToSave] = true;
queue.push_back(p->_waiting);
}
}
}
}
for (auto *p : _processes) {
// Don't kill the running process
if (procsToSave.contains(p->_pid))
continue;
if ((p->_type != processtype) &&
!(p->_flags & Process::PROC_TERMINATED) &&
!(p->_flags & Process::PROC_TERM_DEFERRED)) {
if (fail)
p->fail();
else
p->terminate();
}
}
}
bool Kernel::canSave() {
for (const auto *p : _processes) {
if (!p->is_terminated() && p->_flags & Process::PROC_PREVENT_SAVE) {
return false;
}
}
return true;
}
void Kernel::save(Common::WriteStream *ws) {
ws->writeUint32LE(_tickNum);
_pIDs->save(ws);
ws->writeUint32LE(_processes.size());
for (auto *p : _processes) {
const Std::string & classname = p->GetClassType()._className; // virtual
assert(classname.size());
Common::HashMap<Common::String, ProcessLoadFunc>::iterator iter;
iter = _processLoaders.find(classname);
if (iter == _processLoaders.end()) {
error("Process class cannot save without registered loader: %s", classname.c_str());
}
ws->writeUint16LE(classname.size());
ws->write(classname.c_str(), classname.size());
p->saveData(ws);
}
}
bool Kernel::load(Common::ReadStream *rs, uint32 version) {
_tickNum = rs->readUint32LE();
if (!_pIDs->load(rs, version)) return false;
const uint32 pcount = rs->readUint32LE();
for (unsigned int i = 0; i < pcount; ++i) {
Process *p = loadProcess(rs, version);
if (!p) return false;
_processes.push_back(p);
}
// Integrity check for processes
Set<ProcId> procs;
for (const auto *p : _processes) {
if (!_pIDs->isIDUsed(p->getPid())) {
warning("Process id %d exists but not marked used. Corrupt save?", p->getPid());
return false;
}
if (procs.find(p->getPid()) != procs.end()) {
warning("Duplicate process id %d in processes. Corrupt save?", p->getPid());
return false;
}
procs.insert(p->getPid());
if (!p->validateWaiters()) {
return false;
}
if (p->getTicksPerRun() > 100) {
warning("Improbable value for ticks per run %d in process id %d . Corrupt save?", p->getTicksPerRun(), p->getPid());
return false;
}
if (p->getType() > 0x1000) {
warning("Improbable value for proctype %x in process id %d . Corrupt save?", p->getType(), p->getPid());
return false;
}
}
return true;
}
Process *Kernel::loadProcess(Common::ReadStream *rs, uint32 version) {
const uint16 classlen = rs->readUint16LE();
assert(classlen > 0);
char *buf = new char[classlen + 1];
rs->read(buf, classlen);
buf[classlen] = 0;
Std::string classname = buf;
delete[] buf;
Common::HashMap<Common::String, ProcessLoadFunc>::iterator iter;
iter = _processLoaders.find(classname);
if (iter == _processLoaders.end()) {
warning("Unknown Process class: %s", classname.c_str());
return nullptr;
}
_loading = true;
Process *p = (*(iter->_value))(rs, version);
_loading = false;
return p;
}
uint32 Kernel::I_getNumProcesses(const uint8 *args, unsigned int /*argsize*/) {
ARG_OBJID(item);
ARG_UINT16(type);
if (GAME_IS_CRUSADER && type == CRU_PROC_TYPE_ALL)
type = PROC_TYPE_ALL;
return Kernel::get_instance()->getNumProcesses(item, type);
}
uint32 Kernel::I_resetRef(const uint8 *args, unsigned int /*argsize*/) {
ARG_OBJID(item);
ARG_UINT16(type);
if (GAME_IS_CRUSADER && type == CRU_PROC_TYPE_ALL)
type = PROC_TYPE_ALL;
Kernel::get_instance()->killProcesses(item, type, true);
return 0;
}
} // End of namespace Ultima8
} // End of namespace Ultima

View File

@@ -0,0 +1,171 @@
/* 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/>.
*
*/
#ifndef ULTIMA8_KERNEL_KERNEL_H
#define ULTIMA8_KERNEL_KERNEL_H
#include "ultima/shared/std/containers.h"
#include "ultima/shared/std/string.h"
#include "ultima/ultima8/usecode/intrinsics.h"
namespace Ultima {
namespace Ultima8 {
class Debugger;
class Process;
class idMan;
typedef Process *(*ProcessLoadFunc)(Common::ReadStream *rs, uint32 version);
typedef Std::list<Process *>::const_iterator ProcessIter;
typedef Std::list<Process *>::iterator ProcessIterator;
class Kernel {
friend class Debugger;
public:
Kernel();
~Kernel();
static Kernel *get_instance() {
return _kernel;
}
void reset();
// returns pid of new process
ProcId addProcess(Process *proc, bool dispose = true);
//! add a process and run it immediately
//! \return pid of process
ProcId addProcessExec(Process *proc, bool dispose = true);
void runProcesses();
Process *getProcess(ProcId pid);
ProcId assignPID(Process *proc);
void setNextProcess(Process *proc);
Process *getRunningProcess() const {
return _runningProcess;
}
// objid = 0 means any object, type = 6 means any type
uint32 getNumProcesses(ObjId objid, uint16 processtype);
//! find a (any) process of the given objid, processtype
Process *findProcess(ObjId objid, uint16 processtype);
//! kill (fail) processes of a certain object and/or of a certain type
//! \param objid the object, or 0 for any object (except objid 0)
//! \param type the type, or 6 for any type
//! \param fail if true, fail the processes instead of terminating them
void killProcesses(ObjId objid, uint16 processtype, bool fail);
//! kill (fail) processes of a certain object and not of a certain type
//! \param objid the object, or 0 for any object (except objid 0)
//! \param type the type not to kill
//! \param fail if true, fail the processes instead of terminating them
void killProcessesNotOfType(ObjId objid, uint16 processtype, bool fail);
//! kill (fail) processes not of a certain type, regardless of object ID
//! except for the current running process (for switching levels in Crusader)
//! \param type the type not to kill
//! \param fail if true, fail the processes instead of terminating them
void killAllProcessesNotOfTypeExcludeCurrent(uint16 processtype, bool fail);
//! get an iterator of the process list.
ProcessIter getProcessBeginIterator() {
return _processes.begin();
}
ProcessIter getProcessEndIterator() {
return _processes.end();
}
void kernelStats();
void processTypes();
bool canSave();
void save(Common::WriteStream *ws);
bool load(Common::ReadStream *rs, uint32 version);
void pause() {
_paused++;
}
void unpause() {
if (_paused > 0)
_paused--;
}
bool isPaused() const {
return _paused > 0;
}
void setFrameByFrame(bool fbf) {
_frameByFrame = fbf;
}
bool isFrameByFrame() const {
return _frameByFrame;
}
void addProcessLoader(Std::string classname, ProcessLoadFunc func) {
_processLoaders[classname] = func;
}
uint32 getFrameNum() const {
return _tickNum / TICKS_PER_FRAME;
};
uint32 getTickNum() const {
return _tickNum;
};
static const uint32 TICKS_PER_FRAME;
static const uint32 TICKS_PER_SECOND;
static const uint32 FRAMES_PER_SECOND;
// A special process type which means kill all the processes.
static const uint16 PROC_TYPE_ALL;
INTRINSIC(I_getNumProcesses);
INTRINSIC(I_resetRef);
private:
Process *loadProcess(Common::ReadStream *rs, uint32 version);
Std::list<Process *> _processes;
idMan *_pIDs;
Std::list<Process *>::iterator _currentProcess;
Common::HashMap<Common::String, ProcessLoadFunc> _processLoaders;
bool _loading;
uint32 _tickNum;
unsigned int _paused;
bool _frameByFrame;
Process *_runningProcess;
static Kernel *_kernel;
};
} // End of namespace Ultima8
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,591 @@
/* 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 "common/config-manager.h"
#include "graphics/cursorman.h"
#include "ultima/ultima.h"
#include "ultima/ultima8/kernel/mouse.h"
#include "ultima/ultima8/games/game_data.h"
#include "ultima/ultima8/gfx/render_surface.h"
#include "ultima/ultima8/gumps/gump.h"
#include "ultima/ultima8/kernel/kernel.h"
#include "ultima/ultima8/misc/direction_util.h"
#include "ultima/ultima8/world/get_object.h"
#include "ultima/ultima8/world/actors/avatar_mover_process.h"
#include "ultima/ultima8/world/actors/main_actor.h"
#include "ultima/ultima8/gfx/shape.h"
#include "ultima/ultima8/gfx/shape_frame.h"
#include "ultima/ultima8/gfx/palette.h"
namespace Ultima {
namespace Ultima8 {
Mouse *Mouse::_instance = nullptr;
Mouse::Mouse() : _lastMouseFrame(-1), _flashingCursorTime(0), _mouseOverGump(0),
_dragging(DRAG_NOT), _dragging_objId(0), _draggingItem_startGump(0),
_draggingItem_lastGump(0) {
_instance = this;
_cursors.push(MOUSE_NONE);
CursorMan.showMouse(false);
// The original game switches cursors from small -> medium -> large on
// rectangles - in x, ~30px and ~130px away from the avatar (center) on
// the 320px screen, and approximately the same proportions in y.
//
// These cursors correspond to the player movement of step -> walk -> run.
//
// Modern players may be in a window so give them a little bit more
// space to make the large cursor without having to hit the edge.
// Walk & run threshold range of 0-255
ConfMan.registerDefault("walk_threshold", 50);
ConfMan.registerDefault("run_threshold", 160);
_walkThreshold = CLIP<int>(ConfMan.getInt("walk_threshold"), 0, 255);
_runThreshold = CLIP<int>(ConfMan.getInt("run_threshold"), 0, 255);
}
Mouse::~Mouse() {
_instance = nullptr;
}
bool Mouse::buttonDown(MouseButton button) {
assert(button != MOUSE_LAST);
bool handled = false;
MButton &mbutton = _mouseButton[button];
Gump *desktopGump = Ultima8Engine::get_instance()->getDesktopGump();
Gump *mousedowngump = desktopGump->onMouseDown(button, _mousePos.x, _mousePos.y);
if (mousedowngump) {
mbutton._downGump = mousedowngump->getObjId();
handled = true;
} else {
mbutton._downGump = 0;
}
mbutton._lastDown = mbutton._curDown;
mbutton._curDown = g_system->getMillis();
mbutton._downPoint = _mousePos;
mbutton.setState(MBS_DOWN);
mbutton.clearState(MBS_HANDLED);
uint32 timeout = getDoubleClickTime();
if (mbutton.isUnhandledDoubleClick(timeout)) {
if (_dragging == Mouse::DRAG_NOT) {
Gump *gump = getGump(mbutton._downGump);
if (gump) {
int32 mx2 = _mousePos.x, my2 = _mousePos.y;
Gump *parent = gump->GetParent();
if (parent) parent->ScreenSpaceToGump(mx2, my2);
gump->onMouseDouble(button, mx2, my2);
}
mbutton.setState(MBS_HANDLED);
mbutton._lastDown = 0;
}
}
return handled;
}
bool Mouse::buttonUp(MouseButton button) {
assert(button != MOUSE_LAST);
bool handled = false;
_mouseButton[button].clearState(MBS_DOWN);
// Need to store the last down position of the mouse
// when the button is released.
_mouseButton[button]._downPoint = _mousePos;
// Always send mouse up to the gump
Gump *gump = getGump(_mouseButton[button]._downGump);
if (gump) {
int32 mx2 = _mousePos.x, my2 = _mousePos.y;
Gump *parent = gump->GetParent();
if (parent)
parent->ScreenSpaceToGump(mx2, my2);
gump->onMouseUp(button, mx2, my2);
handled = true;
}
if (button == BUTTON_LEFT && _dragging != Mouse::DRAG_NOT) {
stopDragging(_mousePos.x, _mousePos.y);
handled = true;
}
return handled;
}
void Mouse::popAllCursors() {
_cursors.clear();
_cursors.push(MOUSE_NONE);
update();
}
bool Mouse::isMouseDownEvent(MouseButton button) const {
return _mouseButton[button].isState(MBS_DOWN);
}
int Mouse::getMouseLength(int mx, int my) const {
Ultima8Engine *engine = Ultima8Engine::get_instance();
AvatarMoverProcess *proc = engine->getAvatarMoverProcess();
if (proc) {
if (proc->hasMovementFlags(AvatarMoverProcess::MOVE_STEP))
return 0;
if (proc->hasMovementFlags(AvatarMoverProcess::MOVE_RUN))
return 2;
}
RenderSurface *screen = engine->getRenderScreen();
Common::Rect32 dims = screen->getSurfaceDims();
// Reference point is the center of the screen
int dx = abs(mx - dims.width() / 2);
int dy = abs((dims.height() / 2) - my);
int xmed = dims.width() / 2 * _runThreshold / 255;
int ymed = dims.height() / 2 * _runThreshold / 255;
if (dx > xmed || dy > ymed)
return 2;
// For short cursor, reference point is near the avatar's feet
dy = abs((dims.height() / 2 + 14) - my); //! constant
int xshort = dims.width() / 2 * _walkThreshold / 255;
int yshort = dims.height() / 2 * _walkThreshold / 255;
if (dx > xshort || dy > yshort)
return 1;
return 0;
}
Direction Mouse::getMouseDirectionWorld(int mx, int my) const {
RenderSurface *screen = Ultima8Engine::get_instance()->getRenderScreen();
Common::Rect32 dims = screen->getSurfaceDims();
// For now, reference point is (near) the center of the screen
int dx = mx - dims.width() / 2;
int dy = (dims.height() / 2 + 14) - my; //! constant
return Direction_Get(dy * 2, dx, dirmode_8dirs);
}
Direction Mouse::getMouseDirectionScreen(int mx, int my) const {
return Direction_OneRight(getMouseDirectionWorld(mx, my), dirmode_8dirs);
}
int Mouse::getMouseFrame() {
// Ultima 8 mouse cursors:
// 0-7 = short (0 = up, 1 = up-right, 2 = right, ...)
// 8-15 = medium
// 16-23 = long
// 24 = blue dot
// 25-32 = combat
// 33 = red dot
// 34 = target
// 35 = pentagram
// 36 = skeletal hand
// 38 = quill
// 39 = magnifying glass
// 40 = red cross
if (_cursors.empty())
return -1;
MouseCursor cursor = _cursors.top();
if (_flashingCursorTime > 0) {
if (g_system->getMillis() < _flashingCursorTime + 250)
cursor = MOUSE_CROSS;
else
_flashingCursorTime = 0;
}
switch (cursor) {
case MOUSE_NORMAL: {
if (GAME_IS_CRUSADER)
return -1;
bool combat = false;
bool combatRun = false;
const MainActor *av = getMainActor();
if (av) {
combat = av->isInCombat();
combatRun = av->hasActorFlags(Actor::ACT_COMBATRUN);
}
// Calculate frame based on direction
Direction mousedir = getMouseDirectionScreen();
int frame = mouseFrameForDir(mousedir);
/** length --- frame offset
* 0 0
* 1 8
* 2 16
* combat 25
**/
int offset = 25;
if (!combat || combatRun) //combat mouse is off if running
offset = getMouseLength() * 8;
return frame + offset;
}
//!! constants...
case MOUSE_NONE:
return -1;
case MOUSE_TARGET:
return 34;
case MOUSE_WAIT:
return 35;
case MOUSE_HAND:
return 36;
case MOUSE_QUILL:
return 38;
case MOUSE_MAGGLASS:
return 39;
case MOUSE_CROSS:
return 40;
default:
return -1;
}
}
int Mouse::mouseFrameForDir(Direction mousedir) const {
switch (mousedir) {
case dir_north: return 0;
case dir_northeast: return 1;
case dir_east: return 2;
case dir_southeast: return 3;
case dir_south: return 4;
case dir_southwest: return 5;
case dir_west: return 6;
case dir_northwest: return 7;
default: return 0;
}
}
void Mouse::setMouseCoords(int mx, int my) {
RenderSurface *screen = Ultima8Engine::get_instance()->getRenderScreen();
Common::Rect32 dims = screen->getSurfaceDims();
if (mx < dims.left)
mx = dims.left;
else if (mx >= dims.right)
mx = dims.right - 1;
if (my < dims.top)
my = dims.top;
else if (my >= dims.bottom)
my = dims.bottom - 1;
_mousePos.x = mx;
_mousePos.y = my;
Gump *desktopGump = Ultima8Engine::get_instance()->getDesktopGump();
Gump *gump = desktopGump->onMouseMotion(mx, my);
if (gump && _mouseOverGump != gump->getObjId()) {
Gump *oldGump = getGump(_mouseOverGump);
Std::list<Gump *> oldgumplist;
Std::list<Gump *> newgumplist;
// create lists of parents of old and new 'mouseover' gumps
if (oldGump) {
while (oldGump) {
oldgumplist.push_front(oldGump);
oldGump = oldGump->GetParent();
}
}
Gump *newGump = gump;
while (newGump) {
newgumplist.push_front(newGump);
newGump = newGump->GetParent();
}
Std::list<Gump *>::iterator olditer = oldgumplist.begin();
Std::list<Gump *>::iterator newiter = newgumplist.begin();
// strip common prefix from lists
while (olditer != oldgumplist.end() &&
newiter != newgumplist.end() &&
*olditer == *newiter) {
++olditer;
++newiter;
}
// send events to remaining gumps
for (; olditer != oldgumplist.end(); ++olditer)
(*olditer)->onMouseLeft();
_mouseOverGump = gump->getObjId();
for (; newiter != newgumplist.end(); ++newiter)
(*newiter)->onMouseOver();
}
if (_dragging == DRAG_NOT) {
if (_mouseButton[BUTTON_LEFT].isState(MBS_DOWN)) {
int startx = _mouseButton[BUTTON_LEFT]._downPoint.x;
int starty = _mouseButton[BUTTON_LEFT]._downPoint.y;
if (ABS(startx - mx) > 2 ||
ABS(starty - my) > 2) {
startDragging(startx, starty);
}
}
}
if (_dragging == DRAG_OK || _dragging == DRAG_TEMPFAIL) {
moveDragging(mx, my);
}
}
void Mouse::setMouseCursor(MouseCursor cursor) {
_cursors.pop();
_cursors.push(cursor);
update();
}
void Mouse::flashCrossCursor() {
_flashingCursorTime = g_system->getMillis();
}
void Mouse::pushMouseCursor(MouseCursor cursor) {
_cursors.push(cursor);
update();
}
void Mouse::popMouseCursor() {
_cursors.pop();
update();
}
void Mouse::startDragging(int startx, int starty) {
setDraggingOffset(0, 0); // initialize
Gump *desktopGump = Ultima8Engine::get_instance()->getDesktopGump();
_dragging_objId = desktopGump->TraceObjId(startx, starty);
Gump *gump = getGump(_dragging_objId);
Item *item = getItem(_dragging_objId);
// for a Gump, notify the Gump's parent that we started _dragging:
if (gump) {
debugC(kDebugObject, "Dragging gump %u (class=%s)", _dragging_objId, gump->GetClassType()._className);
Gump *parent = gump->GetParent();
assert(parent); // can't drag root gump
int32 px = startx, py = starty;
parent->ScreenSpaceToGump(px, py);
if (gump->IsDraggable() && gump->onDragStart(px, py))
_dragging = DRAG_OK;
else {
_dragging_objId = 0;
return;
}
} else if (item) {
// for an Item, notify the gump the item is in that we started _dragging
debugC(kDebugObject, "Dragging item %u (class=%s)", _dragging_objId, item->GetClassType()._className);
// find gump item was in
gump = desktopGump->FindGump(startx, starty);
int32 gx = startx, gy = starty;
gump->ScreenSpaceToGump(gx, gy);
bool ok = !Ultima8Engine::get_instance()->isAvatarInStasis() &&
gump->StartDraggingItem(item, gx, gy);
if (!ok) {
_dragging = DRAG_INVALID;
} else {
_dragging = DRAG_OK;
// this is the gump that'll get StopDraggingItem
_draggingItem_startGump = gump->getObjId();
// this is the gump the item is currently over
_draggingItem_lastGump = gump->getObjId();
}
} else {
_dragging = DRAG_INVALID;
}
pushMouseCursor(MOUSE_NORMAL);
// pause the kernel
Kernel::get_instance()->pause();
_mouseButton[BUTTON_LEFT].setState(MBS_HANDLED);
if (_dragging == DRAG_INVALID) {
setMouseCursor(MOUSE_CROSS);
}
}
void Mouse::moveDragging(int mx, int my) {
Gump *gump = getGump(_dragging_objId);
Item *item = getItem(_dragging_objId);
setMouseCursor(MOUSE_NORMAL);
if (gump) {
// for a gump, notify Gump's parent that it was dragged
Gump *parent = gump->GetParent();
assert(parent); // can't drag root gump
int32 px = mx, py = my;
parent->ScreenSpaceToGump(px, py);
gump->onDrag(px, py);
} else if (item) {
// for an item, notify the gump it's on
Gump *desktopGump = Ultima8Engine::get_instance()->getDesktopGump();
gump = desktopGump->FindGump(mx, my);
assert(gump);
if (gump->getObjId() != _draggingItem_lastGump) {
// item switched gump, so notify previous gump item left
Gump *last = getGump(_draggingItem_lastGump);
if (last) last->DraggingItemLeftGump(item);
}
_draggingItem_lastGump = gump->getObjId();
int32 gx = mx, gy = my;
gump->ScreenSpaceToGump(gx, gy);
bool ok = gump->DraggingItem(item, gx, gy);
if (!ok) {
_dragging = DRAG_TEMPFAIL;
} else {
_dragging = DRAG_OK;
}
} else {
warning("Unknown object id on mouse drag");
}
if (_dragging == DRAG_TEMPFAIL) {
setMouseCursor(MOUSE_CROSS);
}
}
void Mouse::stopDragging(int mx, int my) {
debugC(kDebugObject, "Dropping object %u", _dragging_objId);
Gump *gump = getGump(_dragging_objId);
Item *item = getItem(_dragging_objId);
// for a Gump: notify parent
if (gump) {
Gump *parent = gump->GetParent();
assert(parent); // can't drag root gump
int32 px = mx, py = my;
parent->ScreenSpaceToGump(px, py);
gump->onDragStop(px, py);
} else if (item) {
// for an item: notify gumps
if (_dragging != DRAG_INVALID) {
Gump *startgump = getGump(_draggingItem_startGump);
assert(startgump); // can't have disappeared
bool moved = (_dragging == DRAG_OK);
if (_dragging != DRAG_OK) {
Gump *last = getGump(_draggingItem_lastGump);
if (last && last != startgump)
last->DraggingItemLeftGump(item);
}
startgump->StopDraggingItem(item, moved);
}
if (_dragging == DRAG_OK) {
item->movedByPlayer();
Gump *desktopGump = Ultima8Engine::get_instance()->getDesktopGump();
gump = desktopGump->FindGump(mx, my);
int32 gx = mx, gy = my;
gump->ScreenSpaceToGump(gx, gy);
gump->DropItem(item, gx, gy);
}
} else {
assert(_dragging == DRAG_INVALID);
}
_dragging = DRAG_NOT;
Kernel::get_instance()->unpause();
popMouseCursor();
}
uint32 Mouse::getDoubleClickTime() const {
uint32 timeout = g_system->getDoubleClickTime();
return timeout > 0 ? timeout : 400;
}
void Mouse::handleDelayedEvents() {
uint32 now = g_system->getMillis();
uint32 timeout = getDoubleClickTime();
for (int button = 0; button < MOUSE_LAST; ++button) {
if (!_mouseButton[button].isState(MBS_DOWN) &&
_mouseButton[button].isUnhandledPastTimeout(now, timeout)) {
Gump *gump = getGump(_mouseButton[button]._downGump);
if (gump) {
int32 mx = _mouseButton[button]._downPoint.x;
int32 my = _mouseButton[button]._downPoint.y;
Gump *parent = gump->GetParent();
if (parent)
parent->ScreenSpaceToGump(mx, my);
gump->onMouseClick(button, mx, my);
}
_mouseButton[button]._downGump = 0;
_mouseButton[button].setState(MBS_HANDLED);
}
}
}
Gump *Mouse::getMouseOverGump() const {
return getGump(_mouseOverGump);
}
void Mouse::update() {
GameData *gamedata = GameData::get_instance();
if (!gamedata)
return;
const Shape *mouse = gamedata->getMouse();
if (mouse) {
int frame = getMouseFrame();
if (frame != _lastMouseFrame) {
_lastMouseFrame = frame;
if (frame >= 0 && (uint)frame < mouse->frameCount()) {
const ShapeFrame *f = mouse->getFrame(frame);
CursorMan.replaceCursor(f->getSurface(), f->_xoff, f->_yoff, f->_keycolor);
CursorMan.replaceCursorPalette(mouse->getPalette()->data(), 0, 256);
CursorMan.showMouse(true);
} else {
CursorMan.showMouse(false);
}
}
}
}
} // End of namespace Ultima8
} // End of namespace Ultima

View File

@@ -0,0 +1,223 @@
/* 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/>.
*
*/
#ifndef ULTIMA8_KERNEL_MOUSE_H
#define ULTIMA8_KERNEL_MOUSE_H
#include "common/system.h"
#include "common/rect.h"
#include "ultima/ultima8/misc/common_types.h"
#include "ultima/ultima8/misc/direction.h"
namespace Ultima {
namespace Ultima8 {
enum MouseButtonState {
MBS_DOWN = 0x1,
MBS_HANDLED = 0x2 // Mousedown event handled
};
struct MButton {
uint16 _downGump;
uint32 _lastDown;
uint32 _curDown;
Common::Point _downPoint;
int _state;
MButton() : _downGump(0), _curDown(0), _lastDown(0), _state(MBS_HANDLED)
{
}
bool isState(MouseButtonState state) const {
return _state & state;
}
void setState(MouseButtonState state) {
_state |= state;
}
void clearState(MouseButtonState state) {
_state &= ~state;
}
//! True if the current state is unhandled and past the double click timeout.
bool isUnhandledPastTimeout(uint32 now, uint32 timeout) {
return !isState(MBS_HANDLED) && _curDown > 0 && (now - _curDown) > timeout;
}
//! True if the current state is unhandled and within the double click timeout.
bool isUnhandledDoubleClick(uint32 timeout) {
return !isState(MBS_HANDLED) && _lastDown > 0 && (_curDown - _lastDown) <= timeout;
}
};
class Gump;
class Mouse {
public:
enum MouseButton {
BUTTON_NONE = 0,
BUTTON_LEFT = 1,
BUTTON_RIGHT = 2,
BUTTON_MIDDLE = 3,
MOUSE_LAST
};
enum MouseCursor {
MOUSE_NORMAL = 0,
MOUSE_NONE = 1,
MOUSE_TARGET = 2,
MOUSE_WAIT = 3,
MOUSE_HAND = 4,
MOUSE_QUILL = 5,
MOUSE_MAGGLASS = 6,
MOUSE_CROSS = 7
};
enum DraggingState {
DRAG_NOT = 0,
DRAG_OK = 1,
DRAG_INVALID = 2,
DRAG_TEMPFAIL = 3
};
private:
static Mouse *_instance;
Common::Stack<MouseCursor> _cursors;
int _lastMouseFrame;
/**
* Time mouse started flashing, or 0
*/
uint32 _flashingCursorTime;
// mouse input state
MButton _mouseButton[MOUSE_LAST];
uint16 _mouseOverGump;
Common::Point _mousePos;
Common::Point _draggingOffset;
DraggingState _dragging;
ObjId _dragging_objId;
uint16 _draggingItem_startGump;
uint16 _draggingItem_lastGump;
int _walkThreshold;
int _runThreshold;
private:
void startDragging(int mx, int my);
void moveDragging(int mx, int my);
void stopDragging(int mx, int my);
int mouseFrameForDir(Direction mousedir) const;
public:
static Mouse *get_instance() { return _instance; }
public:
Mouse();
~Mouse();
/**
* Called when a mouse button is pressed down
*/
bool buttonDown(MouseButton button);
/**
* Called when a mouse ubtton is released
*/
bool buttonUp(MouseButton button);
//! get mouse cursor length. 0 = short, 1 = medium, 2 = long
int getMouseLength(int mx, int my) const;
//! get mouse cursor length for the current coordinates
int getMouseLength() const {
return getMouseLength(_mousePos.x, _mousePos.y);
}
//! get mouse cursor direction on the screen. 0 = up, 1 = up-right, 2 = right, etc...
Direction getMouseDirectionScreen(int mx, int my) const;
//! get mouse cursor direction on the screen using the current coordinates.
Direction getMouseDirectionScreen() const {
return getMouseDirectionScreen(_mousePos.x, _mousePos.y);
}
//! get mouse cursor direction in the world. 0 = up, 1 = up-right, 2 = right, etc...
Direction getMouseDirectionWorld(int mx, int my) const;
//! get mouse cursor direction in the world using the current coordinates.
Direction getMouseDirectionWorld() const {
return getMouseDirectionWorld(_mousePos.x, _mousePos.y);
}
//! get current mouse cursor location
void getMouseCoords(int32 &mx, int32 &my) const {
mx = _mousePos.x;
my = _mousePos.y;
}
//! set current mouse cursor location
void setMouseCoords(int mx, int my);
bool isMouseDownEvent(MouseButton button) const;
//! remove all existing cursors
void popAllCursors();
//! set the current mouse cursor
void setMouseCursor(MouseCursor cursor);
//! flash the red cross mouse cursor for a brief while
void flashCrossCursor();
//! push the current mouse cursor to the stack
void pushMouseCursor(MouseCursor cursor);
//! pop the last mouse cursor from the stack
void popMouseCursor();
//! get the current mouse frame
int getMouseFrame();
DraggingState dragging() const { return _dragging; }
void setDraggingOffset(int32 x, int32 y) {
_draggingOffset.x = x;
_draggingOffset.y = y;
}
void getDraggingOffset(int32 &x, int32 &y) {
x = _draggingOffset.x;
y = _draggingOffset.y;
}
uint32 getDoubleClickTime() const;
void handleDelayedEvents();
Gump *getMouseOverGump() const;
void resetMouseOverGump() { _mouseOverGump = 0; }
void update();
};
} // End of namespace Ultima8
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,85 @@
/* 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/kernel/object.h"
#include "ultima/ultima8/kernel/kernel.h"
#include "ultima/ultima8/kernel/object_manager.h"
#include "ultima/ultima8/usecode/uc_process.h"
#include "ultima/ultima8/usecode/uc_machine.h"
namespace Ultima {
namespace Ultima8 {
DEFINE_RUNTIME_CLASSTYPE_CODE(Object)
Object::~Object() {
if (_objId != 0xFFFF)
ObjectManager::get_instance()->clearObjId(_objId);
}
ObjId Object::assignObjId() {
if (_objId == 0xFFFF)
_objId = ObjectManager::get_instance()->assignObjId(this);
return _objId;
}
void Object::clearObjId() {
// On clearObjId we kill all processes that belonged to us
Kernel::get_instance()->killProcesses(_objId, Kernel::PROC_TYPE_ALL, true);
if (_objId != 0xFFFF)
ObjectManager::get_instance()->clearObjId(_objId);
_objId = 0xFFFF;
}
Common::String Object::dumpInfo() const {
return Common::String::format("Object %d (class %s)", getObjId(), GetClassType()._className);
}
ProcId Object::callUsecode(uint16 classid, uint16 offset,
const uint8 *args, int argsize) {
uint32 objptr = UCMachine::objectToPtr(getObjId());
UCProcess *p = new UCProcess(classid, offset, objptr, 2, args, argsize);
return Kernel::get_instance()->addProcess(p);
}
void Object::saveData(Common::WriteStream *ws) {
// note: Object is unversioned. If we ever want to version it,
// increase the global savegame version
ws->writeUint16LE(_objId);
}
bool Object::loadData(Common::ReadStream *rs, uint32 version) {
// If we are loading into an object that already got an ID defined, then
// there is a problem - default constructors should not allocate object
// IDs, otherwise we can end up with the wrong IDs during load, because
// we blindly reload the old object IDs and then assign the kernel pointer
// table using those.
assert(_objId == 0xFFFF);
_objId = rs->readUint16LE();
return true;
}
} // End of namespace Ultima8
} // End of namespace Ultima

View File

@@ -0,0 +1,81 @@
/* 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/>.
*
*/
#ifndef ULTIMA8_KERNEL_OBJECT_H
#define ULTIMA8_KERNEL_OBJECT_H
#include "ultima/ultima8/misc/classtype.h"
#include "ultima/ultima8/misc/debugger.h"
namespace Ultima {
namespace Ultima8 {
enum ReservedObjId {
kUndefinedId = 0,
kMainActorId = 1,
kGuardianId = 666
};
class Usecode;
class Object {
public:
Object() : _objId(0xFFFF) {}
virtual ~Object();
ENABLE_RUNTIME_CLASSTYPE_BASE()
//! get this Object's objID
inline ObjId getObjId() const {
return _objId;
}
//! Assign self and contents (if any) an objID
//! \return the assiged ID
virtual ObjId assignObjId();
//! Clear objID of self and contents (if any)
virtual void clearObjId();
//! dump some info about this object to a string
virtual Common::String dumpInfo() const;
//! Spawn a usecode function on this object
//! \param classid The usecode class to run
//! \param offset The offset in that class to run
//! \param u The Usecode object containing the class
//! \param args Optional arguments to the spawned process
//! \param argsize The size (in bytes) of the optional arguments
//! \return the PID of the spawned process
ProcId callUsecode(uint16 classid, uint16 offset,
const uint8 *args = 0, int argsize = 0);
bool loadData(Common::ReadStream *rs, uint32 version);
virtual void saveData(Common::WriteStream *ws);
protected:
ObjId _objId;
};
} // End of namespace Ultima8
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,392 @@
/* 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/misc/debugger.h"
#include "ultima/ultima8/kernel/object_manager.h"
#include "ultima/ultima8/misc/id_man.h"
#include "ultima/ultima8/ultima8.h"
#include "ultima/ultima8/world/actors/main_actor.h"
#include "ultima/ultima8/world/monster_egg.h"
#include "ultima/ultima8/world/teleport_egg.h"
#include "ultima/ultima8/world/glob_egg.h"
#include "ultima/ultima8/gumps/ask_gump.h"
#include "ultima/ultima8/gumps/bark_gump.h"
#include "ultima/ultima8/gumps/paperdoll_gump.h"
#include "ultima/ultima8/gumps/widgets/text_widget.h"
#include "ultima/ultima8/gumps/widgets/button_widget.h"
#include "ultima/ultima8/gumps/widgets/sliding_widget.h"
#include "ultima/ultima8/gumps/mini_stats_gump.h"
#include "ultima/ultima8/gumps/minimap_gump.h"
#include "ultima/ultima8/gumps/cru_status_gump.h"
#include "ultima/ultima8/gumps/cru_pickup_gump.h"
#include "ultima/ultima8/gumps/cru_pickup_area_gump.h"
#include "ultima/ultima8/gumps/translucent_gump.h"
namespace Ultima {
namespace Ultima8 {
ObjectManager *ObjectManager::_objectManager = nullptr;
// a template class to prevent having to write a load function for
// every object separately
template<class T>
struct ObjectLoader {
static Object *load(Common::ReadStream *rs, uint32 version) {
T *p = new T();
bool ok = p->loadData(rs, version);
if (!ok) {
delete p;
p = nullptr;
}
return p;
}
};
ObjectManager::ObjectManager() {
debug(1, "Creating ObjectManager...");
_objectManager = this;
setupLoaders();
_objects.resize(65536);
//!CONSTANTS
_objIDs = new idMan(256, 32766, 8192); // Want range of 256 to 32766
_actorIDs = new idMan(1, 255, 255);
}
ObjectManager::~ObjectManager() {
reset();
debug(1, "Destroying ObjectManager...");
_objectManager = nullptr;
delete _objIDs;
delete _actorIDs;
}
void ObjectManager::reset() {
debug(1, "Resetting ObjectManager...");
unsigned int i;
for (i = 0; i < _objects.size(); ++i) {
if (_objects[i] == nullptr) continue;
#if 0
Item *item = dynamic_cast<Item *>(_objects[i]);
if (item && item->getParent()) continue; // will be deleted by parent
#endif
Gump *gump = dynamic_cast<Gump *>(_objects[i]);
if (gump && gump->GetParent()) continue; // will be deleted by parent
delete _objects[i];
}
for (i = 0; i < _objects.size(); ++i) {
assert(_objects[i] == nullptr);
}
//!CONSTANTS
_objects.clear();
_objects.resize(65536);
_objIDs->clearAll(32766);
_objIDs->reserveID(kGuardianId); // Reserved for the Guardian Bark hack
_actorIDs->clearAll();
}
void ObjectManager::objectStats() {
unsigned int i, npccount = 0, objcount = 0;
//!constants
for (i = 1; i < 256; i++) {
if (_objects[i] != nullptr)
npccount++;
}
for (i = 256; i < _objects.size(); i++) {
if (_objects[i] != nullptr)
objcount++;
}
g_debugger->debugPrintf("Object memory stats:\n");
g_debugger->debugPrintf("NPCs : %u/255\n", npccount);
g_debugger->debugPrintf("Objects : %u/32511\n", objcount);
}
void ObjectManager::objectTypes() {
g_debugger->debugPrintf("Current object types:\n");
Common::HashMap<Common::String, unsigned int> objecttypes;
for (unsigned int i = 1; i < _objects.size(); ++i) {
Object *o = _objects[i];
if (!o) continue;
objecttypes[o->GetClassType()._className]++;
}
for (const auto &i : objecttypes) {
g_debugger->debugPrintf("%s: %u\n", i._key.c_str(), i._value);
}
}
uint16 ObjectManager::assignObjId(Object *obj, ObjId new_objid) {
if (new_objid == 0xFFFF)
new_objid = _objIDs->getNewID();
else
_objIDs->reserveID(new_objid);
// failure???
if (new_objid != 0) {
assert(_objects[new_objid] == nullptr);
_objects[new_objid] = obj;
}
return new_objid;
}
uint16 ObjectManager::assignActorObjId(Actor *actor, ObjId new_objid) {
if (new_objid == 0xFFFF)
new_objid = _actorIDs->getNewID();
else
_actorIDs->reserveID(new_objid);
// failure???
if (new_objid != 0) {
assert(_objects[new_objid] == 0);
_objects[new_objid] = actor;
}
return new_objid;
}
bool ObjectManager::reserveObjId(ObjId objid) {
if (objid >= 256) // !constant
return _objIDs->reserveID(objid);
else
return _actorIDs->reserveID(objid);
}
void ObjectManager::clearObjId(ObjId objid) {
// need to make this assert check only permanent NPCs
// assert(objid >= 256); // !constant
if (objid >= 256) // !constant
_objIDs->clearID(objid);
else
_actorIDs->clearID(objid);
_objects[objid] = nullptr;
}
Object *ObjectManager::getObject(ObjId objid) const {
return _objects[objid];
}
void ObjectManager::allow64kObjects() {
_objIDs->setNewMax(65534);
}
void ObjectManager::save(Common::WriteStream *ws) {
_objIDs->save(ws);
_actorIDs->save(ws);
for (unsigned int i = 0; i < _objects.size(); ++i) {
Object *object = _objects[i];
if (!object) continue;
// child items/gumps are saved by their parent.
Item *item = dynamic_cast<Item *>(object);
if (item && item->getParent()) continue;
Gump *gump = dynamic_cast<Gump *>(object);
// don't save Gumps with DONT_SAVE and Gumps with parents, unless
// the parent is a core gump
// FIXME: This leaks _objIDs. See comment in ObjectManager::load().
if (gump && !gump->mustSave(true)) continue;
saveObject(ws, object);
}
ws->writeUint16LE(0);
}
bool ObjectManager::load(Common::ReadStream *rs, uint32 version) {
if (!_objIDs->load(rs, version)) return false;
if (!_actorIDs->load(rs, version)) return false;
do {
// peek ahead for terminator
uint16 classlen = rs->readUint16LE();
if (classlen == 0) break;
char *buf = new char[classlen + 1];
rs->read(buf, classlen);
buf[classlen] = 0;
Std::string classname = buf;
delete[] buf;
Object *obj = loadObject(rs, classname, version);
if (!obj) return false;
// top level gumps have to be added to the correct core gump
Gump *gump = dynamic_cast<Gump *>(obj);
if (gump) {
assert(gump->GetParent() == nullptr);
Ultima8Engine::get_instance()->addGump(gump);
}
} while (true);
// ObjectManager::save() doesn't save Gumps with the DONT_SAVE flag, but
// their IDs are still marked in use in _objIDs.
// As a workaround, we clear all IDs still in use without actual _objects.
// We only do this with IDs >= 1024 because below there are truly reserved
// _objIDs (up to 511 is reserved by U8Game, 666 is reserved for Guardian
// barks).
// FIXME: Properly fix this objID leak and increment the savegame number.
// This check can then be turned into a savegame corruption check
// for saves with the new savegame version.
// We also fail loading when we're out of _objIDs since this could
// have caused serious issues when critical _objects haven't been created.
if (_objIDs->isFull()) {
warning("Savegame has been corrupted by running out of _objIDs.");
return false;
}
unsigned int count = 0;
for (unsigned int i = 1024; i < _objects.size(); i++) {
if (_objects[i] == nullptr && _objIDs->isIDUsed(i)) {
_objIDs->clearID(i);
count++;
}
}
debug(1, "Reclaimed %u _objIDs on load.", count);
// Integrity check items - their ids should match, and if they have
// parents, those should be valid.
for (unsigned int i = 0; i < _objects.size(); i++) {
if (!_objects[i])
continue;
const Object *obj = _objects[i];
ObjId oid = obj->getObjId();
if (oid != i) {
warning("Corrupt save? Object %d thinks its id is %d", i, oid);
return false;
}
const Item *it = dynamic_cast<const Item *>(obj);
if (it) {
ObjId parent = it->getParent();
if (parent && !_objects[parent]) {
warning("Corrupt save? Object %d has parent %d which no longer exists", i, parent);
return false;
}
}
}
return true;
}
void ObjectManager::saveObject(Common::WriteStream *ws, Object *obj) const {
const Std::string & classname = obj->GetClassType()._className; // note: virtual
Common::HashMap<Common::String, ObjectLoadFunc>::iterator iter;
iter = _objectLoaders.find(classname);
if (iter == _objectLoaders.end()) {
error("Object class cannot save without registered loader: %s", classname.c_str());
}
ws->writeUint16LE(classname.size());
ws->write(classname.c_str(), classname.size());
obj->saveData(ws);
}
Object *ObjectManager::loadObject(Common::ReadStream *rs, uint32 version) {
uint16 classlen = rs->readUint16LE();
char *buf = new char[classlen + 1];
rs->read(buf, classlen);
buf[classlen] = 0;
Std::string classname = buf;
delete[] buf;
return loadObject(rs, classname, version);
}
Object *ObjectManager::loadObject(Common::ReadStream *rs, Std::string classname,
uint32 version) {
Common::HashMap<Common::String, ObjectLoadFunc>::iterator iter;
iter = _objectLoaders.find(classname);
if (iter == _objectLoaders.end()) {
warning("Unknown Object class: %s", classname.c_str());
return nullptr;
}
Object *obj = (*(iter->_value))(rs, version);
if (!obj) {
warning("Error loading object of type %s", classname.c_str());
return nullptr;
}
uint16 objid = obj->getObjId();
if (objid != 0xFFFF) {
_objects[objid] = obj;
bool used;
if (objid >= 256)
used = _objIDs->isIDUsed(objid);
else
used = _actorIDs->isIDUsed(objid);
if (!used) {
warning("Error: object ID %u used but marked available.", objid);
return nullptr;
}
}
return obj;
}
void ObjectManager::setupLoaders() {
addObjectLoader("Item", ObjectLoader<Item>::load);
addObjectLoader("Container", ObjectLoader<Container>::load);
addObjectLoader("Actor", ObjectLoader<Actor>::load);
addObjectLoader("MainActor", ObjectLoader<MainActor>::load);
addObjectLoader("Egg", ObjectLoader<Egg>::load);
addObjectLoader("MonsterEgg", ObjectLoader<MonsterEgg>::load);
addObjectLoader("TeleportEgg", ObjectLoader<TeleportEgg>::load);
addObjectLoader("GlobEgg", ObjectLoader<GlobEgg>::load);
addObjectLoader("Gump", ObjectLoader<Gump>::load);
addObjectLoader("ItemRelativeGump", ObjectLoader<ItemRelativeGump>::load);
addObjectLoader("AskGump", ObjectLoader<AskGump>::load);
addObjectLoader("BarkGump", ObjectLoader<BarkGump>::load);
addObjectLoader("ContainerGump", ObjectLoader<ContainerGump>::load);
addObjectLoader("PaperdollGump", ObjectLoader<PaperdollGump>::load);
addObjectLoader("TextWidget", ObjectLoader<TextWidget>::load);
addObjectLoader("ButtonWidget", ObjectLoader<ButtonWidget>::load);
addObjectLoader("SlidingWidget", ObjectLoader<SlidingWidget>::load);
addObjectLoader("MiniStatsGump", ObjectLoader<MiniStatsGump>::load);
addObjectLoader("MiniMapGump", ObjectLoader<MiniMapGump>::load);
addObjectLoader("CruStatusGump", ObjectLoader<CruStatusGump>::load);
addObjectLoader("CruPickupAreaGump", ObjectLoader<CruPickupAreaGump>::load);
addObjectLoader("CruPickupGump", ObjectLoader<CruPickupGump>::load);
addObjectLoader("TranslucentGump", ObjectLoader<TranslucentGump>::load);
}
} // End of namespace Ultima8
} // End of namespace Ultima

View File

@@ -0,0 +1,92 @@
/* 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/>.
*
*/
#ifndef ULTIMA8_KERNEL_OBJECTMANAGER_H
#define ULTIMA8_KERNEL_OBJECTMANAGER_H
#include "ultima/shared/std/string.h"
#include "ultima/shared/std/containers.h"
#include "ultima/ultima8/misc/common_types.h"
namespace Ultima {
namespace Ultima8 {
class idMan;
class Object;
class Actor;
typedef Object *(*ObjectLoadFunc)(Common::ReadStream *rs, uint32);
class ObjectManager {
public:
ObjectManager();
~ObjectManager();
static ObjectManager *get_instance() {
return _objectManager;
}
void reset();
uint16 assignObjId(Object *obj, ObjId id = 0xFFFF);
uint16 assignActorObjId(Actor *obj, ObjId id = 0xFFFF);
bool reserveObjId(ObjId objid);
void clearObjId(ObjId objid);
Object *getObject(ObjId objid) const;
//! increase the maximum allowed object ID
//! Note: this shouldn't be used in normal circumstances.
//! It exists for dumpMap currently. If that is rewritten not
//! to need more than 32768 object IDs, this function should be
//! deleted.
void allow64kObjects();
void objectStats();
void objectTypes();
void save(Common::WriteStream *ws);
bool load(Common::ReadStream *rs, uint32 version);
void saveObject(Common::WriteStream *ws, Object *obj) const;
Object *loadObject(Common::ReadStream *rs, uint32 version);
Std::vector<Object *> _objects;
idMan *_objIDs;
idMan *_actorIDs;
private:
Object *loadObject(Common::ReadStream *rs, Std::string classname, uint32 version);
void setupLoaders();
void addObjectLoader(Std::string classname, ObjectLoadFunc func) {
_objectLoaders[classname] = func;
}
Common::HashMap<Common::String, ObjectLoadFunc> _objectLoaders;
static ObjectManager *_objectManager;
};
} // End of namespace Ultima8
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,177 @@
/* 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/kernel/process.h"
#include "ultima/ultima8/kernel/kernel.h"
#include "ultima/ultima8/ultima8.h"
namespace Ultima {
namespace Ultima8 {
DEFINE_RUNTIME_CLASSTYPE_CODE(Process)
Process::Process(ObjId it, uint16 ty)
: _pid(0xFFFF), _flags(0), _itemNum(it), _type(ty), _result(0), _ticksPerRun(2) {
Kernel::get_instance()->assignPID(this);
if (GAME_IS_CRUSADER) {
// Default kernel ticks per run of processes in Crusader
_ticksPerRun = 1;
}
}
void Process::fail() {
_flags |= PROC_FAILED;
terminate();
}
void Process::terminate() {
if (_flags & PROC_TERMINATED)
return;
Kernel *kernel = Kernel::get_instance();
// wake up waiting processes
for (const auto &pid : _waiting) {
Process *p = kernel->getProcess(pid);
if (p)
p->wakeUp(_result);
}
_waiting.clear();
_flags |= PROC_TERMINATED;
}
void Process::wakeUp(uint32 result) {
_result = result;
_flags &= ~PROC_SUSPENDED;
Kernel::get_instance()->setNextProcess(this);
onWakeUp();
}
void Process::waitFor(ProcId pid) {
assert(pid != _pid);
if (pid) {
Kernel *kernel = Kernel::get_instance();
// add this process to waiting list of other process
Process *p = kernel->getProcess(pid);
assert(p);
if (p->getProcessFlags() & PROC_TERMINATED) {
//warning("Proc %d wait for proc %d which is already terminated", _pid, pid);
return;
}
p->_waiting.push_back(_pid);
// Note: The original games sync itemnum between processes
// here if either one is zero, but that seems to break things
// for us so we don't do it.
}
_flags |= PROC_SUSPENDED;
}
void Process::waitFor(Process *proc) {
assert(this != proc);
ProcId pid = 0;
if (proc) pid = proc->getPid();
waitFor(pid);
}
void Process::suspend() {
_flags |= PROC_SUSPENDED;
}
Common::String Process::dumpInfo() const {
Common::String info = Common::String::format(
"Process %d class %s, item %d, type %x, status ",
getPid(), GetClassType()._className, _itemNum, _type);
if (_flags & PROC_ACTIVE) info += "A";
if (_flags & PROC_SUSPENDED) info += "S";
if (_flags & PROC_TERMINATED) info += "T";
if (_flags & PROC_TERM_DEFERRED) info += "t";
if (_flags & PROC_FAILED) info += "F";
if (_flags & PROC_RUNPAUSED) info += "R";
if (_flags & PROC_TERM_DISPOSE) info += "D";
if (!_waiting.empty()) {
info += ", notify: ";
for (Std::vector<ProcId>::const_iterator i = _waiting.begin(); i != _waiting.end(); ++i) {
if (i != _waiting.begin()) info += ", ";
info += Common::String::format("%d", *i);
}
}
return info;
}
void Process::saveData(Common::WriteStream *ws) {
ws->writeUint16LE(_pid);
ws->writeUint32LE(_flags);
ws->writeUint16LE(_itemNum);
ws->writeUint16LE(_type);
ws->writeUint32LE(_result);
ws->writeUint32LE(static_cast<uint32>(_waiting.size()));
for (unsigned int i = 0; i < _waiting.size(); ++i)
ws->writeUint16LE(_waiting[i]);
}
bool Process::loadData(Common::ReadStream *rs, uint32 version) {
_pid = rs->readUint16LE();
_flags = rs->readUint32LE();
_itemNum = rs->readUint16LE();
_type = rs->readUint16LE();
_result = rs->readUint32LE();
uint32 waitcount = rs->readUint32LE();
if (waitcount > 1024*1024) {
warning("Improbable waitcount %d for proc %d. Corrupt save?", waitcount, _pid);
return false;
}
_waiting.resize(waitcount);
for (unsigned int i = 0; i < waitcount; ++i)
_waiting[i] = rs->readUint16LE();
return true;
}
bool Process::validateWaiters() const {
for (const auto &pid : _waiting) {
const Process *p = Kernel::get_instance()->getProcess(pid);
if (!p) {
// This can happen if a waiting process gets forcibly terminated.
warning("Invalid procid %d in waitlist for proc %d. Maybe a bug?", pid, _pid);
} else if (!p->is_suspended()) {
// This should never happen.
warning("Procid %d in waitlist for proc %d but not marked suspended", pid, _pid);
return false;
}
}
return true;
}
} // End of namespace Ultima8
} // End of namespace Ultima

View File

@@ -0,0 +1,166 @@
/* 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/>.
*
*/
#ifndef ULTIMA8_KERNEL_USECODE_PROCESS_H
#define ULTIMA8_KERNEL_USECODE_PROCESS_H
#include "ultima/shared/std/containers.h"
#include "ultima/ultima8/misc/classtype.h"
#include "ultima/ultima8/misc/debugger.h"
namespace Ultima {
namespace Ultima8 {
class Debugger;
class Process {
friend class Kernel;
friend class Debugger;
public:
virtual void run() = 0;
Process(ObjId _itemNum = 0, uint16 type = 0);
virtual ~Process() { }
ENABLE_RUNTIME_CLASSTYPE_BASE()
uint32 getProcessFlags() const {
return _flags;
}
bool is_active() const {
return (_flags & PROC_ACTIVE);
}
bool is_terminated() const {
return (_flags & (PROC_TERMINATED |
PROC_TERM_DEFERRED)) != 0;
}
bool is_suspended() const {
return (_flags & PROC_SUSPENDED) != 0;
}
//! terminate the process and recursively fail all processes waiting for it
void fail();
//! terminate the process. This wakes up all processes waiting for it.
virtual void terminate();
//! terminate next frame
void terminateDeferred() {
_flags |= PROC_TERM_DEFERRED;
}
//! run even when paused
void setRunPaused() {
_flags |= PROC_RUNPAUSED;
};
//! suspend until process 'pid' returns. If pid is 0, suspend indefinitely
void waitFor(ProcId pid);
//! suspend until process returns. If proc is 0, suspend indefinitely
void waitFor(Process *proc);
//! suspend process
void suspend();
//! Wake up when the process we were waiting for has finished
void wakeUp(uint32 result);
//! A hook to add aditional behavior on wakeup, before anything else happens
virtual void onWakeUp() {};
void setItemNum(ObjId it) {
_itemNum = it;
}
void setType(uint16 ty) {
_type = ty;
}
void setTicksPerRun(uint32 val) {
_ticksPerRun = val;
}
ProcId getPid() const {
return _pid;
}
ObjId getItemNum() const {
return _itemNum;
}
uint16 getType() const {
return _type;
}
uint32 getTicksPerRun() const {
return _ticksPerRun;
}
//! dump some info about this process to a string
virtual Common::String dumpInfo() const;
//! load Process data
bool loadData(Common::ReadStream *rs, uint32 version);
//! save Process data
virtual void saveData(Common::WriteStream *ws);
//! Check the waiting processes. This is used after loading a game.
//! Ensures they are all valid processes and suspended. Can't be done in
//! loadData because the waiters may not be loaded yet at that point.
bool validateWaiters() const;
protected:
//! process id
ProcId _pid;
uint32 _flags;
//! how many kernel ticks between when this process should be run.
//! not saved because it's fixed by process type and game.
uint32 _ticksPerRun;
//! item we are assigned to
ObjId _itemNum;
uint16 _type;
//! process result
uint32 _result;
//! Processes waiting for this one to finish.
//! When this process terminates, awaken them and pass them the result val.
Std::vector<ProcId> _waiting;
public:
enum processflags {
PROC_ACTIVE = 0x0001, //!< is the process in the run-list?
PROC_SUSPENDED = 0x0002, //!< suspended? (because it's waiting)
PROC_TERMINATED = 0x0004,
PROC_TERM_DEFERRED = 0x0008, //!< automatically call terminate next frame
PROC_FAILED = 0x0010,
PROC_RUNPAUSED = 0x0020, //!< run even if game is paused
PROC_TERM_DISPOSE = 0x0040, //!< Dispose after termination
PROC_PREVENT_SAVE = 0x0080 //!< When set, prevent game from saving
};
};
} // End of namespace Ultima8
} // End of namespace Ultima
#endif