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

182 lines
4.7 KiB
C++

/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "ultima/ultima.h"
#include "ultima/ultima8/misc/debugger.h"
#include "ultima/ultima8/world/missile_tracker.h"
#include "ultima/ultima8/world/current_map.h"
#include "ultima/ultima8/world/item.h"
#include "ultima/ultima8/world/world.h"
#include "ultima/ultima8/world/get_object.h"
namespace Ultima {
namespace Ultima8 {
MissileTracker::MissileTracker(const Item *item, ObjId owner,
int32 sx, int32 sy, int32 sz,
int32 tx, int32 ty, int32 tz,
int32 speed, int32 gravity) :
_owner(owner), _destX(tx), _destY(ty), _destZ(tz), _gravity(gravity) {
_objId = item->getObjId();
init(sx, sy, sz, speed);
}
MissileTracker::MissileTracker(const Item *item, ObjId owner,
int32 tx, int32 ty, int32 tz,
int32 speed, int32 gravity) :
_owner(owner), _destX(tx), _destY(ty), _destZ(tz), _gravity(gravity) {
assert(item->getParent() == 0);
_objId = item->getObjId();
Point3 pt = item->getLocation();
init(pt.x, pt.y, pt.z, speed);
}
void MissileTracker::init(int32 x, int32 y, int32 z, int32 speed) {
int range = ABS(x - _destX) + ABS(y - _destY);
// rounded division: range/speed
_frames = (range + (speed / 2)) / speed;
/*
Item's vertical trajectory:
z_{i+1} = z_i + s_i
s_{i+1} = s_i - g
(z_i = vertical position after i _frames,
s_i = vertical speed after i _frames, g = _gravity)
So:
z_i = z + sum_{j=0}^{i-1} ( s_0 - jg)
= z + is_0 - 1/2 i(i-1)g
Conclusion: if we want to reach the destination vertical level in i _frames,
we need to set
s_0 = ((1/2 gi(i-1)) + z_i-z) / i
*/
if (_frames > 0) {
_speedZ = ((_gravity * _frames * (_frames - 1) / 2) + _destZ - z) / _frames;
// check if vertical speed isn't too high
if (_speedZ > speed / 4) {
if (_gravity == 0 || (speed / (4 * _gravity)) <= _frames) {
if (speed >= 4 && (_destZ - z) / (speed / 4) > _frames)
_frames = (_destZ - z) / (speed / 4);
} else {
_frames = speed / (4 * _gravity);
}
}
_speedZ = ((_gravity * _frames * (_frames - 1) / 2) + _destZ - z) / _frames;
// horizontal speed is easier: just divide distance by _frames
_speedX = ((_destX - x) + (_frames / 2)) / _frames;
_speedY = ((_destY - y) + (_frames / 2)) / _frames;
debugC(kDebugCollision, "MissileTracker: from (%d,%d,%d) to (%d,%d,%d)", x, y, z, _destX, _destY, _destZ);
debugC(kDebugCollision, "speed: %d, _gravity: %d, _frames: %d", speed, _gravity, _frames);
debugC(kDebugCollision, "resulting speed: (%d,%d,%d)", _speedX, _speedY, _speedZ);
} else {
// no significant horizontal movement
if (_destZ > z)
_speedZ = speed / 4;
else
_speedZ = -speed / 4;
}
}
MissileTracker::~MissileTracker() {
}
bool MissileTracker::isPathClear() const {
Point3 start;
Point3 end;
int32 dims[3];
int32 sx, sy, sz;
sx = _speedX;
sy = _speedY;
sz = _speedZ;
World *world = World::get_instance();
CurrentMap *map = world->getCurrentMap();
Item *item = getItem(_objId);
if (!item) {
// Item disappeared? shouldn't happen, but call the path clear.
return true;
}
item->getFootpadWorld(dims[0], dims[1], dims[2]);
start = item->getLocation();
for (int f = 0; f < _frames; ++f) {
end.x = start.x + sx;
end.y = start.y + sy;
end.z = start.z + sz;
// Do the sweep test
Std::list<CurrentMap::SweepItem> collisions;
map->sweepTest(start, end, dims, item->getShapeInfo()->_flags, _objId,
false, &collisions);
int32 hit = 0x4000;
for (const auto &collision : collisions) {
if (collision._blocking && !collision._touching && collision._item != _owner) {
hit = collision._hitTime;
break;
}
}
if (hit != 0x4000) {
// didn't reach end of this path segment
return false;
}
sz -= _gravity;
start = end;
}
return true;
}
void MissileTracker::launchItem() {
Item *item = getItem(_objId);
if (!item) return;
item->hurl(_speedX, _speedY, _speedZ, _gravity);
}
} // End of namespace Ultima8
} // End of namespace Ultima