Initial commit
This commit is contained in:
62
engines/ultima/ultima8/kernel/delay_process.cpp
Normal file
62
engines/ultima/ultima8/kernel/delay_process.cpp
Normal 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
|
||||
54
engines/ultima/ultima8/kernel/delay_process.h
Normal file
54
engines/ultima/ultima8/kernel/delay_process.h
Normal 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
|
||||
500
engines/ultima/ultima8/kernel/kernel.cpp
Normal file
500
engines/ultima/ultima8/kernel/kernel.cpp
Normal 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
|
||||
171
engines/ultima/ultima8/kernel/kernel.h
Normal file
171
engines/ultima/ultima8/kernel/kernel.h
Normal 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
|
||||
591
engines/ultima/ultima8/kernel/mouse.cpp
Normal file
591
engines/ultima/ultima8/kernel/mouse.cpp
Normal 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
|
||||
223
engines/ultima/ultima8/kernel/mouse.h
Normal file
223
engines/ultima/ultima8/kernel/mouse.h
Normal 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
|
||||
85
engines/ultima/ultima8/kernel/object.cpp
Normal file
85
engines/ultima/ultima8/kernel/object.cpp
Normal 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
|
||||
81
engines/ultima/ultima8/kernel/object.h
Normal file
81
engines/ultima/ultima8/kernel/object.h
Normal 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
|
||||
392
engines/ultima/ultima8/kernel/object_manager.cpp
Normal file
392
engines/ultima/ultima8/kernel/object_manager.cpp
Normal 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
|
||||
92
engines/ultima/ultima8/kernel/object_manager.h
Normal file
92
engines/ultima/ultima8/kernel/object_manager.h
Normal 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
|
||||
177
engines/ultima/ultima8/kernel/process.cpp
Normal file
177
engines/ultima/ultima8/kernel/process.cpp
Normal 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
|
||||
166
engines/ultima/ultima8/kernel/process.h
Normal file
166
engines/ultima/ultima8/kernel/process.h
Normal 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
|
||||
Reference in New Issue
Block a user