Initial commit
This commit is contained in:
518
engines/ultima/ultima8/world/super_sprite_process.cpp
Normal file
518
engines/ultima/ultima8/world/super_sprite_process.cpp
Normal file
@@ -0,0 +1,518 @@
|
||||
/* 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/world/super_sprite_process.h"
|
||||
|
||||
#include "ultima/ultima8/games/game_data.h"
|
||||
#include "ultima/ultima8/kernel/kernel.h"
|
||||
#include "ultima/ultima8/kernel/delay_process.h"
|
||||
#include "ultima/ultima8/misc/direction_util.h"
|
||||
#include "ultima/ultima8/usecode/uc_list.h"
|
||||
#include "ultima/ultima8/world/loop_script.h"
|
||||
#include "ultima/ultima8/world/current_map.h"
|
||||
#include "ultima/ultima8/world/fire_type.h"
|
||||
#include "ultima/ultima8/world/get_object.h"
|
||||
#include "ultima/ultima8/world/item_factory.h"
|
||||
#include "ultima/ultima8/world/world.h"
|
||||
#include "ultima/ultima8/world/actors/actor.h"
|
||||
#include "ultima/ultima8/world/sprite_process.h"
|
||||
|
||||
namespace Ultima {
|
||||
namespace Ultima8 {
|
||||
|
||||
// p_dynamic_class stuff
|
||||
DEFINE_RUNTIME_CLASSTYPE_CODE(SuperSpriteProcess)
|
||||
|
||||
SuperSpriteProcess::SuperSpriteProcess() : Process(),
|
||||
_shape(0), _frame(0), _fireType(0), _damage(0), _source(0),
|
||||
_target(0), _counter(1), _item0x77(0), _spriteNo(0),
|
||||
_startedAsFiretype9(false), _xstep(0), _ystep(0), _zstep(0),
|
||||
_expired(false) {
|
||||
}
|
||||
|
||||
SuperSpriteProcess::SuperSpriteProcess(int shape, int frame, int sx, int sy, int sz,
|
||||
int dx, int dy, int dz,
|
||||
uint16 firetype, uint16 damage, uint16 source,
|
||||
uint16 target, bool inexact) :
|
||||
_shape(shape), _frame(frame), _startpt(sx, sy, sz), _destpt(dx, dy, dz),
|
||||
_nextpt(sx, sy, sz), _fireType(firetype), _damage(damage), _source(source),
|
||||
_target(target), _counter(1), _item0x77(0), _spriteNo(0),
|
||||
_startedAsFiretype9(firetype == 9), _xstep(0), _ystep(0), _zstep(0),
|
||||
_expired(false) {
|
||||
|
||||
const FireType *firetypedat = GameData::get_instance()->getFireType(firetype);
|
||||
assert(firetypedat);
|
||||
if (firetypedat->getAccurate()) {
|
||||
inexact = false;
|
||||
}
|
||||
if (inexact) {
|
||||
int rng = _startpt.maxDistXYZ(_destpt);
|
||||
Item *srcitem = getItem(source);
|
||||
if (srcitem == getControlledActor()) {
|
||||
if (firetype == 2 || firetype == 13)
|
||||
rng /= 8;
|
||||
else if (firetype == 5)
|
||||
rng /= 12;
|
||||
else if (firetype == 10)
|
||||
rng /= 5;
|
||||
else
|
||||
rng /= 10;
|
||||
} else {
|
||||
Actor *srcnpc = dynamic_cast<Actor *>(srcitem);
|
||||
Actor *controlled = getControlledActor();
|
||||
const uint32 frameno = Kernel::get_instance()->getFrameNum();
|
||||
const uint32 timeoutfinish = controlled ? controlled->getAttackMoveTimeoutFinishFrame() : 0;
|
||||
if (!srcnpc || !srcnpc->getAttackAimFlag()) {
|
||||
if (!srcnpc || frameno < timeoutfinish) {
|
||||
if (!srcnpc && (controlled && controlled->isKneeling())) {
|
||||
rng = rng / 5;
|
||||
} else {
|
||||
const uint16 dodgefactor = controlled ? controlled->getAttackMoveDodgeFactor() : 2;
|
||||
if (!srcnpc) {
|
||||
rng = rng / (dodgefactor * 3);
|
||||
} else {
|
||||
rng = rng / dodgefactor;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
rng = rng / 8;
|
||||
}
|
||||
} else {
|
||||
rng = rng / 2;
|
||||
}
|
||||
}
|
||||
|
||||
if (rng > 0x50)
|
||||
rng = 0x50;
|
||||
|
||||
Common::RandomSource &rs = Ultima8Engine::get_instance()->getRandomSource();
|
||||
int xoff = rs.getRandomNumberRngSigned(-rng, rng);
|
||||
int yoff = rs.getRandomNumberRngSigned(-rng, rng);
|
||||
rng /= 3;
|
||||
if (rng > 0x18)
|
||||
rng = 0x18;
|
||||
int zoff = rs.getRandomNumberRngSigned(-rng, rng);
|
||||
|
||||
_destpt.translate(xoff, yoff, zoff);
|
||||
if (_destpt.z > 0xfa)
|
||||
_destpt.z = 0xfa;
|
||||
else if (_destpt.z < 0)
|
||||
_destpt.z = 0;
|
||||
}
|
||||
|
||||
float travel = _destpt.maxDistXYZ(_nextpt);
|
||||
float speed = firetypedat->getCellsPerRound() * 32.0f;
|
||||
float rounds = travel / speed;
|
||||
if (rounds < 1)
|
||||
rounds = 1;
|
||||
|
||||
_xstep = (_destpt.x - sx) / rounds;
|
||||
_ystep = (_destpt.y - sy) / rounds;
|
||||
_zstep = (_destpt.z - sz) / rounds;
|
||||
|
||||
if (_fireType == 2 || _fireType == 0xd) {
|
||||
_destpt.x += travel / 5;
|
||||
_destpt.y += travel / 5;
|
||||
_destpt.z += travel / 5;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
SuperSpriteProcess::~SuperSpriteProcess(void) {
|
||||
Item *item = getItem(_itemNum);
|
||||
if (item) item->destroy();
|
||||
}
|
||||
|
||||
void SuperSpriteProcess::move(int x, int y, int z) {
|
||||
_nowpt.set(x, y, z);
|
||||
|
||||
Item *item = getItem(_itemNum);
|
||||
if (item)
|
||||
item->move(_nowpt);
|
||||
}
|
||||
|
||||
static inline int _sign(int x) {
|
||||
if (x < 0)
|
||||
return -1;
|
||||
else if (x == 0)
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
void SuperSpriteProcess::run() {
|
||||
CurrentMap *map = World::get_instance()->getCurrentMap();
|
||||
int mapChunkSize = map->getChunkSize();
|
||||
const FireType *firetypedat = GameData::get_instance()->getFireType(_fireType);
|
||||
|
||||
if (!firetypedat || !map->isChunkFast(_nextpt.x / mapChunkSize, _nextpt.y / mapChunkSize)) {
|
||||
destroyItemOrTerminate();
|
||||
return;
|
||||
}
|
||||
|
||||
_nowpt = _nextpt;
|
||||
|
||||
Point3 newpt(_startpt);
|
||||
if (!_startedAsFiretype9) {
|
||||
newpt.x += _counter * _xstep;
|
||||
newpt.y += _counter * _ystep;
|
||||
newpt.z += _counter * _zstep;
|
||||
} else {
|
||||
int targetz = 0;
|
||||
if (_counter < firetypedat->getRoundDuration()) {
|
||||
if (!_expired) {
|
||||
Direction dir8 = dir_current;
|
||||
if (_target == 0) {
|
||||
targetz = _nowpt.z;
|
||||
} else {
|
||||
const Item *target = getItem(_target);
|
||||
if (target) {
|
||||
Point3 ptt = target->getLocation();
|
||||
Point3 ptc = target->getCentre();
|
||||
targetz = ptc.z + 8;
|
||||
dir8 = Direction_GetWorldDir(ptt.y - _nowpt.y, ptt.x - _nowpt.x, dirmode_8dirs);
|
||||
}
|
||||
}
|
||||
|
||||
int xoff = Direction_XFactor(dir8);
|
||||
if (_sign(xoff) != _sign(_xstep))
|
||||
_xstep *= 2;
|
||||
int yoff = Direction_YFactor(dir8);
|
||||
if (_sign(yoff) != _sign(_ystep))
|
||||
_ystep *= 2;
|
||||
}
|
||||
} else {
|
||||
_expired = true;
|
||||
}
|
||||
|
||||
if (!_expired) {
|
||||
if (_nowpt.z < targetz) {
|
||||
_zstep++;
|
||||
} else if (targetz < _nowpt.z) {
|
||||
_zstep--;
|
||||
}
|
||||
} else {
|
||||
_zstep--;
|
||||
}
|
||||
|
||||
_xstep = CLIP(_xstep, -32.0f, 32.0f);
|
||||
_ystep = CLIP(_ystep, -32.0f, 32.0f);
|
||||
_zstep = CLIP(_zstep, -16.0f, 16.0f);
|
||||
|
||||
newpt.x += _counter * _xstep;
|
||||
newpt.y += _counter * _ystep;
|
||||
newpt.z += _counter * _zstep;
|
||||
|
||||
if (_fireType == 9 && !_expired) {
|
||||
if (_nowpt.x != newpt.x || _nowpt.y != newpt.y) {
|
||||
Direction dir = Direction_GetWorldDir(_nowpt.y - newpt.y, _nowpt.x - newpt.x, dirmode_8dirs);
|
||||
Item *item;
|
||||
if (_itemNum == 0) {
|
||||
item = getItem(_spriteNo);
|
||||
} else {
|
||||
item = getItem(_itemNum);
|
||||
}
|
||||
if (item) {
|
||||
item->setFrame(Direction_ToUsecodeDir(dir) + 0x11);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_pt3 = newpt;
|
||||
if (_pt3.z < 0)
|
||||
_pt3.z = 0;
|
||||
if (_pt3.z > 0xfa)
|
||||
_pt3.z = 0xfa;
|
||||
|
||||
_counter++;
|
||||
|
||||
if (!_spriteNo && _counter > 1) {
|
||||
Item *sprite = ItemFactory::createItem(_shape, _frame, 0, Item::FLG_DISPOSABLE,
|
||||
0, 0, Item::EXT_SPRITE, true);
|
||||
_spriteNo = sprite->getObjId();
|
||||
sprite->move(_nowpt);
|
||||
}
|
||||
|
||||
if (_pt3.z > 0 && _pt3.z < 0xfa) {
|
||||
int32 duration = firetypedat->getRoundDuration() + 25;
|
||||
|
||||
if (_counter < duration) {
|
||||
// disasm ~line 305
|
||||
if (!map->isChunkFast(_nowpt.x / mapChunkSize, _nowpt.y / mapChunkSize)) {
|
||||
destroyItemOrTerminate();
|
||||
return;
|
||||
}
|
||||
if (areaSearch()) {
|
||||
advanceFrame();
|
||||
Process *delay = new DelayProcess(_fireType == 9 ? 3 : 2);
|
||||
ProcId delayid = Kernel::get_instance()->addProcess(delay);
|
||||
waitFor(delayid);
|
||||
return;
|
||||
}
|
||||
if (_item0x77 && _fireType == 5) {
|
||||
Item *item = getItem(_item0x77);
|
||||
assert(item);
|
||||
const ShapeInfo *info = item->getShapeInfo();
|
||||
if (info->is_roof() && _fireType == 5) {
|
||||
makeBulletSplash(_pt3);
|
||||
// Finish disasm lines 347~381 here.
|
||||
warning("TODO: SuperSpriteProcess::run: Implement special case for firetype 5 hitting a roof");
|
||||
terminate();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (_source && (_item0x77 == _source && _counter < 5)) {
|
||||
Item *source = getItem(_source);
|
||||
assert(source);
|
||||
// CHECKME: This appears to be a hack in the original game
|
||||
// to avoid hitting the source item.. for now reproduce it.
|
||||
source->moveToEtherealVoid();
|
||||
run();
|
||||
source->returnFromEtherealVoid();
|
||||
}
|
||||
hitAndFinish();
|
||||
}
|
||||
|
||||
void SuperSpriteProcess::makeBulletSplash(const Point3 &pt) {
|
||||
const FireType *firetypedat = GameData::get_instance()->getFireType(_fireType);
|
||||
if (!firetypedat)
|
||||
return;
|
||||
|
||||
if (firetypedat->getRange()) {
|
||||
Item *item = getItem(_item0x77);
|
||||
Item *src = getItem(_source);
|
||||
firetypedat->applySplashDamageAround(pt, _damage, 1, item, src);
|
||||
}
|
||||
firetypedat->makeBulletSplashShapeAndPlaySound(pt.x, pt.y, pt.z);
|
||||
}
|
||||
|
||||
static bool _pointOutOfMap(const Point3 &pt, int32 maxxy) {
|
||||
return (pt.x < 0 || pt.y < 0 || pt.z < 0 ||
|
||||
pt.x > maxxy || pt.y > maxxy || pt.z > 255);
|
||||
}
|
||||
|
||||
void SuperSpriteProcess::hitAndFinish() {
|
||||
Point3 pt(_nowpt);
|
||||
//int dist = _nowpt.maxDistXYZ(_pt3);
|
||||
|
||||
int xstep = _pt3.x - _nowpt.x;
|
||||
int ystep = _pt3.y - _nowpt.y;
|
||||
int zstep = _pt3.z - _nowpt.z;
|
||||
// We add a slight hack - our sweep test is off-by-one on Z??
|
||||
Point3 start(_nowpt.x, _nowpt.y, _nowpt.z + 1);
|
||||
Point3 end(_pt3.x, _pt3.y, _pt3.z + 1);
|
||||
int32 dims[3] = {1, 1, 1};
|
||||
// will never get a collision if not stepping at all..
|
||||
bool collision = !(xstep || ystep || zstep);
|
||||
Std::list<CurrentMap::SweepItem> hits;
|
||||
|
||||
while (!collision) {
|
||||
CurrentMap *map = World::get_instance()->getCurrentMap();
|
||||
collision = map->sweepTest(start, end, dims, ShapeInfo::SI_SOLID,
|
||||
_source, true, &hits);
|
||||
if (collision)
|
||||
break;
|
||||
start.x += xstep;
|
||||
start.y += ystep;
|
||||
start.z += zstep;
|
||||
end.x += xstep;
|
||||
end.y += ystep;
|
||||
end.z += zstep;
|
||||
const int32 mapmax = map->getChunkSize() * MAP_NUM_CHUNKS;
|
||||
if (_pointOutOfMap(start, mapmax) || _pointOutOfMap(end, mapmax))
|
||||
break;
|
||||
}
|
||||
|
||||
if (collision && hits.size()) {
|
||||
const CurrentMap::SweepItem &firsthit = hits.front();
|
||||
_item0x77 = firsthit._item;
|
||||
pt = firsthit.GetInterpolatedCoords(start, end);
|
||||
}
|
||||
|
||||
Item *item = getItem(_item0x77);
|
||||
if (item) {
|
||||
int32 ifx, ify, ifz;
|
||||
item->getFootpadData(ifx, ify, ifz);
|
||||
Point3 pti = item->getLocation();
|
||||
|
||||
if (ifx > 2 && ify > 2 && ifz > 2) {
|
||||
int32 ixsize = (ifx - 2) * 16;
|
||||
int32 iysize = (ify - 2) * 16;
|
||||
if (pt.x < pti.x - ixsize)
|
||||
pt.x = pti.x - ixsize;
|
||||
if (pt.y < pti.y - iysize)
|
||||
pt.y = pti.y - iysize;
|
||||
}
|
||||
|
||||
//Actor *actor = dynamic_cast<Actor *>(item);
|
||||
// CHECKME: This is not exactly the same as the game, but
|
||||
// it should work? See disasm 1138:1384, lines 142 ~ 172
|
||||
// There is some random factor added for non-actor items
|
||||
// which needs checking
|
||||
Direction dir = Direction_GetWorldDir(pti.y - _nowpt.y, pti.x - _nowpt.x, dirmode_8dirs);
|
||||
item->receiveHit(_itemNum, dir, _damage, _fireType);
|
||||
}
|
||||
makeBulletSplash(pt);
|
||||
destroyItemOrTerminate();
|
||||
}
|
||||
|
||||
void SuperSpriteProcess::terminate() {
|
||||
if (_spriteNo) {
|
||||
Item *sprite = getItem(_spriteNo);
|
||||
if (sprite)
|
||||
sprite->destroy();
|
||||
_spriteNo = 0;
|
||||
}
|
||||
Process::terminate();
|
||||
}
|
||||
|
||||
void SuperSpriteProcess::destroyItemOrTerminate() {
|
||||
if (_itemNum) {
|
||||
Item *item = getItem(_itemNum);
|
||||
if (item)
|
||||
item->destroy();
|
||||
_itemNum = 0;
|
||||
} else {
|
||||
terminate();
|
||||
}
|
||||
}
|
||||
|
||||
// Implementation of fn at 1138:0f9e
|
||||
void SuperSpriteProcess::advanceFrame() {
|
||||
_nextpt = _pt3;
|
||||
|
||||
Item *item = getItem(_itemNum);
|
||||
if (item) {
|
||||
item->collideMove(_pt3.x, _pt3.y, _pt3.z, false, false);
|
||||
}
|
||||
|
||||
if (_spriteNo) {
|
||||
Item *sprite = getItem(_spriteNo);
|
||||
assert(sprite);
|
||||
sprite->move(_nextpt);
|
||||
uint32 frame = sprite->getFrame() + 1;
|
||||
if (_fireType == 0xe) {
|
||||
if (frame > 0x4b)
|
||||
frame = 0x47;
|
||||
sprite->setFrame(frame);
|
||||
} else if (_fireType == 0x11) { // No Regret only
|
||||
if (frame % 6 == 0)
|
||||
frame = frame - 5;
|
||||
sprite->setFrame(frame);
|
||||
} else if (_fireType == 0x14) { // No Regret only
|
||||
if ((frame - 0xdb) % 3 == 0)
|
||||
frame = frame - 2;
|
||||
sprite->setFrame(frame);
|
||||
}
|
||||
}
|
||||
|
||||
if (_fireType == 3) {
|
||||
if (_pt5.x != -1) {
|
||||
// Create a little sparkly thing
|
||||
Process *p = new SpriteProcess(0x426, 0, 9, 1, 3, _pt5.x, _pt5.y, _pt5.z);
|
||||
Kernel::get_instance()->addProcess(p);
|
||||
}
|
||||
_pt5 = _nextpt;
|
||||
}
|
||||
}
|
||||
|
||||
bool SuperSpriteProcess::areaSearch() {
|
||||
CurrentMap *map = World::get_instance()->getCurrentMap();
|
||||
|
||||
Point3 start(_nowpt.x, _nowpt.y, _nowpt.z + 1);
|
||||
Point3 end(_pt3.x, _pt3.y, _pt3.z + 1);
|
||||
int32 dims[3] = {1, 1, 1};
|
||||
|
||||
Item *item = getItem(_itemNum);
|
||||
if (item) {
|
||||
start = item->getLocation();
|
||||
}
|
||||
|
||||
Std::list<CurrentMap::SweepItem> hits;
|
||||
map->sweepTest(start, end, dims, ShapeInfo::SI_SOLID,
|
||||
_source, true, &hits);
|
||||
|
||||
if (hits.size() > 0) {
|
||||
_item0x77 = hits.front()._item;
|
||||
}
|
||||
|
||||
return hits.size() == 0;
|
||||
}
|
||||
|
||||
|
||||
void SuperSpriteProcess::saveData(Common::WriteStream *ws) {
|
||||
Process::saveData(ws);
|
||||
|
||||
ws->writeUint32LE(static_cast<uint32>(_shape));
|
||||
ws->writeUint32LE(_frame);
|
||||
_nowpt.saveData(ws);
|
||||
_nextpt.saveData(ws);
|
||||
_pt3.saveData(ws);
|
||||
_startpt.saveData(ws);
|
||||
_pt5.saveData(ws);
|
||||
_destpt.saveData(ws);
|
||||
ws->writeUint16LE(_frame);
|
||||
ws->writeUint16LE(_fireType);
|
||||
ws->writeUint16LE(_damage);
|
||||
ws->writeUint16LE(_source);
|
||||
ws->writeUint16LE(_target);
|
||||
ws->writeUint16LE(_counter);
|
||||
ws->writeUint16LE(_item0x77);
|
||||
ws->writeUint16LE(_spriteNo);
|
||||
ws->writeFloatLE(_xstep);
|
||||
ws->writeFloatLE(_ystep);
|
||||
ws->writeFloatLE(_zstep);
|
||||
ws->writeByte(_startedAsFiretype9);
|
||||
ws->writeByte(_expired);
|
||||
}
|
||||
|
||||
bool SuperSpriteProcess::loadData(Common::ReadStream *rs, uint32 version) {
|
||||
if (!Process::loadData(rs, version)) return false;
|
||||
|
||||
_shape = static_cast<int>(rs->readUint32LE());
|
||||
_frame = rs->readUint32LE();
|
||||
_nowpt.loadData(rs, version);
|
||||
_nextpt.loadData(rs, version);
|
||||
_pt3.loadData(rs, version);
|
||||
_startpt.loadData(rs, version);
|
||||
_pt5.loadData(rs, version);
|
||||
_destpt.loadData(rs, version);
|
||||
_frame = rs->readUint16LE();
|
||||
_fireType = rs->readUint16LE();
|
||||
_damage = rs->readUint16LE();
|
||||
_source = rs->readUint16LE();
|
||||
_target = rs->readUint16LE();
|
||||
_counter = rs->readUint16LE();
|
||||
_item0x77 = rs->readUint16LE();
|
||||
_spriteNo = rs->readUint16LE();
|
||||
_xstep = rs->readFloatLE();
|
||||
_ystep = rs->readFloatLE();
|
||||
_zstep = rs->readFloatLE();
|
||||
_startedAsFiretype9 = (rs->readByte() != 0);
|
||||
_expired = (rs->readByte() != 0);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // End of namespace Ultima8
|
||||
} // End of namespace Ultima
|
||||
Reference in New Issue
Block a user