1203 lines
32 KiB
C++
1203 lines
32 KiB
C++
|
|
/* ScummVM - Graphic Adventure Engine
|
||
|
|
*
|
||
|
|
* ScummVM is the legal property of its developers, whose names
|
||
|
|
* are too numerous to list here. Please refer to the COPYRIGHT
|
||
|
|
* file distributed with this source distribution.
|
||
|
|
*
|
||
|
|
* This program is free software: you can redistribute it and/or modify
|
||
|
|
* it under the terms of the GNU General Public License as published by
|
||
|
|
* the Free Software Foundation, either version 3 of the License, or
|
||
|
|
* (at your option) any later version.
|
||
|
|
*
|
||
|
|
* This program is distributed in the hope that it will be useful,
|
||
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
|
|
* GNU General Public License for more details.
|
||
|
|
*
|
||
|
|
* You should have received a copy of the GNU General Public License
|
||
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||
|
|
*
|
||
|
|
*/
|
||
|
|
|
||
|
|
|
||
|
|
#include "ultima/ultima8/world/actors/attack_process.h"
|
||
|
|
#include "ultima/ultima8/ultima8.h"
|
||
|
|
#include "ultima/ultima8/audio/audio_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/usecode/uc_list.h"
|
||
|
|
#include "ultima/ultima8/world/actors/actor.h"
|
||
|
|
#include "ultima/ultima8/world/current_map.h"
|
||
|
|
#include "ultima/ultima8/world/get_object.h"
|
||
|
|
#include "ultima/ultima8/world/world.h"
|
||
|
|
#include "ultima/ultima8/world/loop_script.h"
|
||
|
|
#include "ultima/ultima8/world/actors/combat_dat.h"
|
||
|
|
#include "ultima/ultima8/world/actors/loiter_process.h"
|
||
|
|
#include "ultima/ultima8/world/actors/cru_pathfinder_process.h"
|
||
|
|
#include "ultima/ultima8/misc/direction_util.h"
|
||
|
|
|
||
|
|
namespace Ultima {
|
||
|
|
namespace Ultima8 {
|
||
|
|
|
||
|
|
//#define WATCHACTOR 3
|
||
|
|
|
||
|
|
DEFINE_RUNTIME_CLASSTYPE_CODE(AttackProcess)
|
||
|
|
|
||
|
|
// These sound number arrays are in the order they appear in the original exes
|
||
|
|
|
||
|
|
static const int16 REM_SFX_1[] = {0x15, 0x78, 0x80, 0x83, 0xDC, 0xDD};
|
||
|
|
static const int16 REM_SFX_2[] = {0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xE7};
|
||
|
|
static const int16 REM_SFX_3[] = {0xFC, 0xFD, 0xFE, 0xC8};
|
||
|
|
static const int16 REM_SFX_4[] = {0xCC, 0xCD, 0xCE, 0xCF};
|
||
|
|
static const int16 REM_SFX_5[] = {0xC7, 0xCA, 0xC9};
|
||
|
|
static const int16 REM_SFX_6[] = {0x82, 0x84, 0x85};
|
||
|
|
static const int16 REM_SFX_7[] = {0x9B, 0x9C, 0x9D, 0x9E, 0x9F};
|
||
|
|
|
||
|
|
static const int16 REG_SFX_1[] = { 0xD2, 0xD3, 0xD4, 0xD5, 0xE5, 0x100 };
|
||
|
|
static const int16 REG_SFX_2[] = { 0x9, 0x79, 0x7A, 0x7B, 0x7C, 0x7D };
|
||
|
|
static const int16 REG_SFX_3[] = { 0x7E, 0x7F, 0x90, 0xB6, 0xC2, 0xD0 };
|
||
|
|
static const int16 REG_SFX_4[] = { 0x101, 0x102, 0x103, 0x104, 0x105, 0x106 };
|
||
|
|
static const int16 REG_SFX_5[] = { 0x108, 0x109, 0x1AB, 0x1AC, 0x1AD, 0x1AF, 0x1AE };
|
||
|
|
static const int16 REG_SFX_6[] = { 0x1B0, 0x1B1, 0x1B2, 0x1B3, 0x1B4 };
|
||
|
|
static const int16 REG_SFX_7[] = { 0x1B5, 0x1B6, 0x1B7, 0x1B8, 0x1B9, 0x1BA, 0x1BB };
|
||
|
|
static const int16 REG_SFX_8[] = { 0x1C1, 0x1C0, 0x1BF, 0x1BE, 0x1BD, 0x1BC };
|
||
|
|
static const int16 REG_SFX_9[] = { 0x1C2, 0x1C3, 0x1C4, 0x1C5, 0x1C6, 0x1C7 };
|
||
|
|
static const int16 REG_SFX_10[] = { 0x1C8, 0x1C9, 0x1CA, 0x1CB, 0x1CC, 0x1CD };
|
||
|
|
static const int16 REG_SFX_11[] = { 0x1D0, 0x1D1, 0x1D2, 0x1D3, 0x1D4, 0x1D5 };
|
||
|
|
static const int16 REG_SFX_12[] = { 0x1D7, 0x1D8, 0x1D9, 0x1DA, 0x1DB, 0x1DC };
|
||
|
|
static const int16 REG_SFX_13[] = { 0x1DD, 0x1DE, 0x1DF, 0x1E0, 0x1E1, 0x1E2, 0x1E3 };
|
||
|
|
static const int16 REG_SFX_14[] = { 0x9B, 0x9C, 0x9D, 0x9E, 0x9F };
|
||
|
|
static const int16 REG_SFX_15[] = { 0x1E7, 0x1E8, 0x1E9, 0x1EA, 0x1ED };
|
||
|
|
|
||
|
|
#define RANDOM_ELEM(array) (array[rs.getRandomNumber(ARRAYSIZE(array) - 1)])
|
||
|
|
|
||
|
|
// If data is referenced in the metalang with an offset of this or greater,
|
||
|
|
// read from the data array.
|
||
|
|
static const int MAGIC_DATA_OFF = 33000;
|
||
|
|
|
||
|
|
int16 AttackProcess::_lastAttackSound = -1;
|
||
|
|
int16 AttackProcess::_lastLastAttackSound = -1;
|
||
|
|
|
||
|
|
static uint16 someSleepGlobal = 0;
|
||
|
|
|
||
|
|
AttackProcess::AttackProcess() : Process(), _block(0), _target(1), _tactic(0), _tacticDat(nullptr),
|
||
|
|
_tacticDatReadStream(nullptr), _tacticDatStartOffset(0), _soundNo(-1), _playedStartSound(false),
|
||
|
|
_npcInitialDir(dir_invalid), _field57(0), _field59(0), _field7f(false), _field96(false), _field97(false),
|
||
|
|
_isActivity9orB(false), _isActivityAorB(false), _timer3set(false), _timer2set(false),
|
||
|
|
_doubleDelay(false), _wpnField8(1), _wpnBasedTimeout(0), _difficultyBasedTimeout(0), _timer2(0),
|
||
|
|
_timer3(0), _timer4(0), _timer5(0), _soundTimestamp(0), _soundDelayTicks(480), _fireTimestamp(0) {
|
||
|
|
for (int i = 0; i < ARRAYSIZE(_dataArray); i++) {
|
||
|
|
_dataArray[i] = 0;
|
||
|
|
}
|
||
|
|
if (GAME_IS_REGRET) {
|
||
|
|
Common::RandomSource &rs = Ultima8Engine::get_instance()->getRandomSource();
|
||
|
|
_soundDelayTicks = rs.getRandomNumberRng(10, 24) * 60;
|
||
|
|
if (rs.getRandomNumber(2) == 0)
|
||
|
|
_soundTimestamp = Kernel::get_instance()->getTickNum();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
AttackProcess::AttackProcess(Actor *actor) : _block(0), _target(1), _tactic(0), _tacticDat(nullptr),
|
||
|
|
_tacticDatReadStream(nullptr), _tacticDatStartOffset(0), _soundNo(-1), _playedStartSound(false),
|
||
|
|
_field57(0), _field59(0), _field7f(false), _field96(false), _field97(false), _isActivity9orB(false),
|
||
|
|
_isActivityAorB(false), _timer3set(false), _timer2set(false), _doubleDelay(false), _wpnField8(1),
|
||
|
|
_wpnBasedTimeout(0), _difficultyBasedTimeout(0), _timer2(0), _timer3(0), _timer4(0), _timer5(0),
|
||
|
|
_soundTimestamp(0), _soundDelayTicks(480), _fireTimestamp(0) {
|
||
|
|
assert(actor);
|
||
|
|
_itemNum = actor->getObjId();
|
||
|
|
_npcInitialDir = actor->getDir();
|
||
|
|
|
||
|
|
// Note: this isn't actually initialized in the original which
|
||
|
|
// suggests it can't ever get used before setting, but to make
|
||
|
|
// coverity etc happy clear it anyway.
|
||
|
|
for (int i = 0; i < ARRAYSIZE(_dataArray); i++) {
|
||
|
|
_dataArray[i] = 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (GAME_IS_REGRET) {
|
||
|
|
Common::RandomSource &rs = Ultima8Engine::get_instance()->getRandomSource();
|
||
|
|
_soundDelayTicks = rs.getRandomNumberRng(10, 24) * 60;
|
||
|
|
if (rs.getRandomNumber(2) == 0)
|
||
|
|
_soundTimestamp = Kernel::get_instance()->getTickNum();
|
||
|
|
}
|
||
|
|
|
||
|
|
actor->setAttackAimFlag(false);
|
||
|
|
|
||
|
|
const Item *wpn = getItem(actor->getActiveWeapon());
|
||
|
|
if (wpn) {
|
||
|
|
const uint32 wpnshape = wpn->getShape();
|
||
|
|
const uint32 npcshape = actor->getShape();
|
||
|
|
const uint8 difficulty = World::get_instance()->getGameDifficulty();
|
||
|
|
if (wpnshape == 0x386 || wpnshape == 0x388 || wpnshape == 0x38e) {
|
||
|
|
_wpnBasedTimeout = 0x3c;
|
||
|
|
switch (difficulty) {
|
||
|
|
case 1:
|
||
|
|
_difficultyBasedTimeout = 0x78;
|
||
|
|
break;
|
||
|
|
case 2:
|
||
|
|
_difficultyBasedTimeout = 0x5a;
|
||
|
|
break;
|
||
|
|
case 3:
|
||
|
|
case 4:
|
||
|
|
default:
|
||
|
|
if (npcshape == 0x3ac)
|
||
|
|
_difficultyBasedTimeout = 0xf;
|
||
|
|
else
|
||
|
|
_difficultyBasedTimeout = 0x3c;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
_wpnBasedTimeout = 0x1e;
|
||
|
|
switch (difficulty) {
|
||
|
|
case 1:
|
||
|
|
_difficultyBasedTimeout = _wpnBasedTimeout;
|
||
|
|
break;
|
||
|
|
case 2:
|
||
|
|
_difficultyBasedTimeout = 0x14;
|
||
|
|
break;
|
||
|
|
case 3:
|
||
|
|
_difficultyBasedTimeout = 0xf;
|
||
|
|
break;
|
||
|
|
case 4:
|
||
|
|
default:
|
||
|
|
_difficultyBasedTimeout = 0;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
_type = ATTACK_PROC_TYPE;
|
||
|
|
|
||
|
|
setTacticNo(actor->getCombatTactic());
|
||
|
|
actor->setToStartOfAnim(Animation::stand);
|
||
|
|
}
|
||
|
|
|
||
|
|
AttackProcess::~AttackProcess() {
|
||
|
|
delete _tacticDatReadStream;
|
||
|
|
}
|
||
|
|
|
||
|
|
void AttackProcess::terminate() {
|
||
|
|
Actor *a = getActor(_itemNum);
|
||
|
|
if (a)
|
||
|
|
a->clearActorFlag(Actor::ACT_INCOMBAT);
|
||
|
|
|
||
|
|
Process::terminate();
|
||
|
|
}
|
||
|
|
|
||
|
|
void AttackProcess::run() {
|
||
|
|
Actor *a = getActor(_itemNum);
|
||
|
|
Actor *target = getActor(_target);
|
||
|
|
|
||
|
|
if (!a || a->isDead() || !_tacticDatReadStream) {
|
||
|
|
terminate();
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!a->hasFlags(Item::FLG_FASTAREA))
|
||
|
|
return;
|
||
|
|
|
||
|
|
if (_tactic == 0) {
|
||
|
|
genericAttack();
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!target || target->isDead()) {
|
||
|
|
warning("got into attack process with invalid target");
|
||
|
|
terminate();
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
Common::RandomSource &rs = Ultima8Engine::get_instance()->getRandomSource();
|
||
|
|
const Direction curdir = a->getDir();
|
||
|
|
|
||
|
|
const uint8 opcode = _tacticDatReadStream->readByte();
|
||
|
|
switch (opcode) {
|
||
|
|
case 0x81:
|
||
|
|
// Seems like field 0x53 is never used anywhere?
|
||
|
|
/*_field53 = */readNextWordWithData();
|
||
|
|
return;
|
||
|
|
case 0x82:
|
||
|
|
/*_field53 = 0*/;
|
||
|
|
return;
|
||
|
|
case 0x84:
|
||
|
|
_target = readNextWordWithData();
|
||
|
|
// This is called in the original, but basically redundant.
|
||
|
|
// a->setActivity(5);
|
||
|
|
return;
|
||
|
|
case 0x85:
|
||
|
|
a->doAnim(Animation::walk, dir_current);
|
||
|
|
return;
|
||
|
|
case 0x86:
|
||
|
|
a->doAnim(Animation::run, dir_current);
|
||
|
|
return;
|
||
|
|
case 0x87:
|
||
|
|
a->doAnim(Animation::retreat, dir_current);
|
||
|
|
return;
|
||
|
|
case 0x88:
|
||
|
|
{
|
||
|
|
// Turn 90 degrees left
|
||
|
|
Direction newdir = Direction_TurnByDelta(curdir, -2, dirmode_8dirs);
|
||
|
|
a->turnTowardDir(newdir);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
case 0x89:
|
||
|
|
{
|
||
|
|
// Turn 90 degrees right
|
||
|
|
Direction newdir = Direction_TurnByDelta(curdir, 2, dirmode_8dirs);
|
||
|
|
a->turnTowardDir(newdir);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
case 0x8a:
|
||
|
|
{
|
||
|
|
bool result = a->fireDistance(target, curdir, 0, 0, 0);
|
||
|
|
// Fire small weapon
|
||
|
|
if (result)
|
||
|
|
a->doAnim(Animation::attack, dir_current);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
case 0x8b:
|
||
|
|
{
|
||
|
|
bool result = a->fireDistance(target, curdir, 0, 0, 0);
|
||
|
|
// Fire large weapon
|
||
|
|
if (result)
|
||
|
|
a->doAnim(Animation::attack, dir_current);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
case 0x8c:
|
||
|
|
a->doAnim(Animation::stand, dir_current);
|
||
|
|
return;
|
||
|
|
case 0x8d:
|
||
|
|
{
|
||
|
|
// Pathfind to home
|
||
|
|
int32 x, y, z;
|
||
|
|
a->getHomePosition(x, y, z);
|
||
|
|
ProcId pid = Kernel::get_instance()->addProcess(
|
||
|
|
new CruPathfinderProcess(a, Point3(x, y, z), 100, 0x80, true));
|
||
|
|
waitFor(pid);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
case 0x8e:
|
||
|
|
{
|
||
|
|
// Pathfind to target
|
||
|
|
Point3 pt = target->getLocation();
|
||
|
|
ProcId pid = Kernel::get_instance()->addProcess(
|
||
|
|
new CruPathfinderProcess(a, pt, 12, 0x80, true));
|
||
|
|
waitFor(pid);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
case 0x8f:
|
||
|
|
{
|
||
|
|
// Pathfind to a point between npc and the target
|
||
|
|
Point3 apt = a->getLocation();
|
||
|
|
Point3 tpt = target->getLocation();
|
||
|
|
int32 x = (tpt.x + apt.x) / 2;
|
||
|
|
int32 y = (tpt.y + apt.y) / 2;
|
||
|
|
int32 z = (tpt.z + apt.z) / 2;
|
||
|
|
ProcId pid = Kernel::get_instance()->addProcess(
|
||
|
|
new CruPathfinderProcess(a, Point3(x, y, z), 12, 0x80, true));
|
||
|
|
waitFor(pid);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
case 0x92:
|
||
|
|
a->doAnim(Animation::kneelAndFire, dir_current);
|
||
|
|
return;
|
||
|
|
case 0x93:
|
||
|
|
{
|
||
|
|
// Sleep for a random value scaled by difficult level
|
||
|
|
int ticks = readNextWordWithData();
|
||
|
|
if (ticks == someSleepGlobal) {
|
||
|
|
ticks = rs.getRandomNumberRng(0x31, 0x45);
|
||
|
|
}
|
||
|
|
ticks /= World::get_instance()->getGameDifficulty();
|
||
|
|
sleep(ticks);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
case 0x94:
|
||
|
|
{
|
||
|
|
// Loiter a bit..
|
||
|
|
uint16 data = readNextWordWithData();
|
||
|
|
ProcId pid = Kernel::get_instance()->addProcess(new LoiterProcess(a, data));
|
||
|
|
waitFor(pid);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
case 0x95:
|
||
|
|
{
|
||
|
|
Direction dir = a->getDirToItemCentre(*target);
|
||
|
|
a->turnTowardDir(dir);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
case 0x96:
|
||
|
|
// do activity specified by next word
|
||
|
|
a->setActivity(readNextWordWithData());
|
||
|
|
return;
|
||
|
|
case 0x97:
|
||
|
|
// switch to tactic no specified by next word
|
||
|
|
setTacticNo(readNextWordWithData());
|
||
|
|
return;
|
||
|
|
case 0x98:
|
||
|
|
{
|
||
|
|
a->setDir(_npcInitialDir);
|
||
|
|
a->moveToEtherealVoid();
|
||
|
|
int32 hx, hy, hz;
|
||
|
|
a->getHomePosition(hx, hy, hz);
|
||
|
|
a->move(hx, hy, hz);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
case 0x99:
|
||
|
|
terminate();
|
||
|
|
return;
|
||
|
|
case 0x9a:
|
||
|
|
{
|
||
|
|
// get next word and jump to that offset if distance < 481
|
||
|
|
Point3 apt = a->getLocation();
|
||
|
|
Point3 tpt = target->getLocation();
|
||
|
|
int maxdiff = apt.maxDistXYZ(tpt);
|
||
|
|
int16 data = readNextWordWithData();
|
||
|
|
if (maxdiff < 481) {
|
||
|
|
_tacticDatReadStream->seek(data, SEEK_SET);
|
||
|
|
}
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
case 0x9b:
|
||
|
|
{
|
||
|
|
// get next word and jump to that offset if distance > 160
|
||
|
|
Point3 apt = a->getLocation();
|
||
|
|
Point3 tpt = target->getLocation();
|
||
|
|
int maxdiff = apt.maxDistXYZ(tpt);
|
||
|
|
int16 data = readNextWordWithData();
|
||
|
|
if (maxdiff > 160) {
|
||
|
|
_tacticDatReadStream->seek(data, SEEK_SET);
|
||
|
|
}
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
case 0x9c:
|
||
|
|
{
|
||
|
|
bool result = a->fireDistance(target, curdir, 0, 0, 0);
|
||
|
|
uint16 data = readNextWordWithData();
|
||
|
|
if (!result) {
|
||
|
|
_tacticDatReadStream->seek(data, SEEK_SET);
|
||
|
|
}
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
case 0x9d:
|
||
|
|
{
|
||
|
|
bool result = a->fireDistance(target, curdir, 0, 0, 0);
|
||
|
|
uint16 data = readNextWordWithData();
|
||
|
|
if (result) {
|
||
|
|
_tacticDatReadStream->seek(data, SEEK_SET);
|
||
|
|
}
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
case 0x9e:
|
||
|
|
{
|
||
|
|
uint16 maxval = readNextWordWithData();
|
||
|
|
uint16 offset = readNextWordWithData();
|
||
|
|
if (maxval != 0) {
|
||
|
|
uint16 randval = rs.getRandomNumber(maxval - 1);
|
||
|
|
if (randval != 0) {
|
||
|
|
_tacticDatReadStream->seek(offset);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
case 0x9f:
|
||
|
|
_field57 = readNextWordWithData();
|
||
|
|
_field59 = _tacticDatReadStream->pos();
|
||
|
|
return;
|
||
|
|
case 0xa6:
|
||
|
|
{
|
||
|
|
const uint16 targetFrame = readNextWordWithData();
|
||
|
|
const uint16 targetQ = a->getUnkByte();
|
||
|
|
|
||
|
|
UCList uclist(2);
|
||
|
|
// loopscript to find shape = 0x33A (826), the numbers that NPCs wander between
|
||
|
|
LOOPSCRIPT(script, LS_SHAPE_EQUAL(0x33a));
|
||
|
|
CurrentMap *currentmap = World::get_instance()->getCurrentMap();
|
||
|
|
currentmap->areaSearch(&uclist, script, sizeof(script), nullptr,
|
||
|
|
0x200 * 16, true);
|
||
|
|
for (unsigned int i = 0; i < uclist.getSize(); ++i) {
|
||
|
|
Item *founditem = getItem(uclist.getuint16(i));
|
||
|
|
uint16 itemQlo = founditem->getQuality() & 0xff;
|
||
|
|
uint32 itemFrame = founditem->getFrame();
|
||
|
|
|
||
|
|
if (itemFrame == targetFrame && (targetQ == 0 || itemQlo == targetQ)) {
|
||
|
|
ProcId pid = Kernel::get_instance()->addProcess(
|
||
|
|
new CruPathfinderProcess(a, founditem, 100, 0x80, true));
|
||
|
|
waitFor(pid);
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
case 0xa7:
|
||
|
|
a->turnTowardDir(dir_north);
|
||
|
|
return;
|
||
|
|
case 0xa8:
|
||
|
|
a->turnTowardDir(dir_south);
|
||
|
|
return;
|
||
|
|
case 0xa9:
|
||
|
|
a->turnTowardDir(dir_east);
|
||
|
|
return;
|
||
|
|
case 0xaa:
|
||
|
|
a->turnTowardDir(dir_west);
|
||
|
|
return;
|
||
|
|
case 0xab:
|
||
|
|
a->turnTowardDir(dir_northeast);
|
||
|
|
return;
|
||
|
|
case 0xac:
|
||
|
|
a->turnTowardDir(dir_southwest);
|
||
|
|
return;
|
||
|
|
case 0xad:
|
||
|
|
a->turnTowardDir(dir_southeast);
|
||
|
|
return;
|
||
|
|
case 0xae:
|
||
|
|
a->turnTowardDir(dir_northwest);
|
||
|
|
return;
|
||
|
|
case 0xaf:
|
||
|
|
{
|
||
|
|
uint16 next = readNextWordWithData();
|
||
|
|
uint16 offset = readNextWordRaw();
|
||
|
|
setAttackData(offset, next);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
case 0xb0:
|
||
|
|
{
|
||
|
|
uint16 offset = readNextWordRaw();
|
||
|
|
uint16 val = getAttackData(offset);
|
||
|
|
setAttackData(opcode, val + readNextWordWithData());
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
case 0xb1:
|
||
|
|
{
|
||
|
|
uint16 offset = readNextWordRaw();
|
||
|
|
uint16 val = getAttackData(offset);
|
||
|
|
setAttackData(offset, val - readNextWordWithData());
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
case 0xb2:
|
||
|
|
{
|
||
|
|
uint16 offset = readNextWordRaw();
|
||
|
|
uint16 val = getAttackData(offset);
|
||
|
|
setAttackData(offset, val * readNextWordWithData());
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
case 0xb3:
|
||
|
|
{
|
||
|
|
uint16 offset = readNextWordRaw();
|
||
|
|
uint16 val = getAttackData(offset);
|
||
|
|
uint16 divisor = readNextWordWithData();
|
||
|
|
if (!divisor)
|
||
|
|
divisor = 1; // shouldn't happen in real data, but just to be sure..
|
||
|
|
setAttackData(offset, val / divisor);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
case 0xb4:
|
||
|
|
{
|
||
|
|
uint16 dir = Direction_ToUsecodeDir(curdir);
|
||
|
|
uint16 offset = readNextWordRaw();
|
||
|
|
setAttackData(offset, dir);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
case 0xb5:
|
||
|
|
{
|
||
|
|
uint16 dir = readNextWordWithData();
|
||
|
|
a->setDir(Direction_FromUsecodeDir(dir));
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
case 0xb6:
|
||
|
|
{
|
||
|
|
uint16 offset = readNextWordRaw();
|
||
|
|
uint16 dir = Direction_ToUsecodeDir(curdir);
|
||
|
|
setAttackData(offset, dir);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
case 0xb7:
|
||
|
|
a->doAnim(Animation::kneelingRetreat, dir_current);
|
||
|
|
return;
|
||
|
|
case 0xb8:
|
||
|
|
a->doAnim(Animation::kneelingAdvance, dir_current);
|
||
|
|
return;
|
||
|
|
case 0xb9:
|
||
|
|
a->doAnim(Animation::kneelingSlowRetreat, dir_current);
|
||
|
|
return;
|
||
|
|
case 0xc0:
|
||
|
|
_tacticDatReadStream->seek(readNextWordWithData(), SEEK_SET);
|
||
|
|
return;
|
||
|
|
case 0xc1:
|
||
|
|
_field57--;
|
||
|
|
if (_field57 > 0) {
|
||
|
|
_tacticDatReadStream->seek(_field59, SEEK_SET);
|
||
|
|
}
|
||
|
|
return;
|
||
|
|
case 0xff:
|
||
|
|
// flip to block 1 and restart
|
||
|
|
if (_block == 0) {
|
||
|
|
setBlockNo(1);
|
||
|
|
}
|
||
|
|
_tacticDatReadStream->seek(_tacticDatStartOffset, SEEK_SET);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
void AttackProcess::genericAttack() {
|
||
|
|
Actor *a = getActor(_itemNum);
|
||
|
|
assert(a);
|
||
|
|
|
||
|
|
if (a->isBusy() || a->hasActorFlags(Actor::ACT_PATHFINDING)) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
#ifdef WATCHACTOR
|
||
|
|
if (_itemNum == WATCHACTOR)
|
||
|
|
debug("Attack: actor %d genericAttack (not busy or pathfinding)", _itemNum);
|
||
|
|
#endif
|
||
|
|
|
||
|
|
// This should never be running on the controlled npc.
|
||
|
|
if (_itemNum == World::get_instance()->getControlledNPCNum()) {
|
||
|
|
terminate();
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
const Item *wpn = getItem(a->getActiveWeapon());
|
||
|
|
/*if (!wpn) {
|
||
|
|
warning("started attack for NPC %d with no weapon", _itemNum);
|
||
|
|
}*/
|
||
|
|
|
||
|
|
Common::RandomSource &rs = Ultima8Engine::get_instance()->getRandomSource();
|
||
|
|
AudioProcess *audio = AudioProcess::get_instance();
|
||
|
|
const Direction curdir = a->getDir();
|
||
|
|
const int32 ticknow = Kernel::get_instance()->getTickNum();
|
||
|
|
int wpnField8 = wpn ? wpn->getShapeInfo()->_weaponInfo->_field8 : 1;
|
||
|
|
const uint16 controlledNPC = World::get_instance()->getControlledNPCNum();
|
||
|
|
Direction targetdir = dir_invalid;
|
||
|
|
|
||
|
|
if (_target != controlledNPC) {
|
||
|
|
if (controlledNPC <= 1)
|
||
|
|
_target = 1;
|
||
|
|
else
|
||
|
|
_target = controlledNPC;
|
||
|
|
}
|
||
|
|
|
||
|
|
#ifdef WATCHACTOR
|
||
|
|
if (_itemNum == WATCHACTOR)
|
||
|
|
debug("Attack: genericAttack chose target %d", _target);
|
||
|
|
#endif
|
||
|
|
|
||
|
|
Actor *target = getActor(_target);
|
||
|
|
if (!target || !target->isOnScreen() || target->isDead()) {
|
||
|
|
// Walk around randomly in hope of finding target
|
||
|
|
_target = 0;
|
||
|
|
if (!_isActivity9orB) {
|
||
|
|
#ifdef WATCHACTOR
|
||
|
|
if (_itemNum == WATCHACTOR)
|
||
|
|
debug("Attack: genericAttack walking around looking for target %d", _target);
|
||
|
|
#endif
|
||
|
|
Point3 pt = a->getLocation();
|
||
|
|
pt.x += rs.getRandomNumberRngSigned(-0x1ff, 0x1ff);
|
||
|
|
pt.y += rs.getRandomNumberRngSigned(-0x1ff, 0x1ff);
|
||
|
|
_field96 = true;
|
||
|
|
const ProcId pid = Kernel::get_instance()->addProcess(
|
||
|
|
new CruPathfinderProcess(a, pt, 12, 0x80, true));
|
||
|
|
// add a tiny delay to avoid tight loops
|
||
|
|
Process *delayproc = new DelayProcess(2);
|
||
|
|
Kernel::get_instance()->addProcess(delayproc);
|
||
|
|
delayproc->waitFor(pid);
|
||
|
|
waitFor(delayproc);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
Animation::Sequence anim;
|
||
|
|
if (a->isInCombat()) {
|
||
|
|
anim = Animation::combatStand;
|
||
|
|
} else {
|
||
|
|
anim = Animation::stand;
|
||
|
|
}
|
||
|
|
DirectionMode standDirMode = a->animDirMode(anim);
|
||
|
|
if (_timer3set) {
|
||
|
|
#ifdef WATCHACTOR
|
||
|
|
if (_itemNum == WATCHACTOR)
|
||
|
|
debug("Attack: _timer3set");
|
||
|
|
#endif
|
||
|
|
if (_timer3 >= ticknow) {
|
||
|
|
if (a->isInCombat()) {
|
||
|
|
if (rs.getRandomNumber(2) != 0) {
|
||
|
|
#ifdef WATCHACTOR
|
||
|
|
if (_itemNum == WATCHACTOR)
|
||
|
|
debug("Attack: toggle weapon state");
|
||
|
|
#endif
|
||
|
|
const Animation::Sequence lastanim = a->getLastAnim();
|
||
|
|
if ((lastanim != Animation::unreadyWeapon) && (lastanim != Animation::unreadyLargeWeapon))
|
||
|
|
a->doAnim(Animation::unreadyWeapon, dir_current);
|
||
|
|
else
|
||
|
|
a->doAnim(Animation::readyWeapon, dir_current);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (rs.getRandomNumber(2) == 0) {
|
||
|
|
a->turnTowardDir(Direction_TurnByDelta(curdir, rs.getRandomNumber(7), dirmode_8dirs));
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!a->hasActorFlags(Actor::ACT_WEAPONREADY))
|
||
|
|
return;
|
||
|
|
|
||
|
|
if (curdir != a->getDirToItemCentre(*target))
|
||
|
|
return;
|
||
|
|
|
||
|
|
if (_soundNo != -1) {
|
||
|
|
if (audio->isSFXPlayingForObject(_soundNo, _itemNum))
|
||
|
|
return;
|
||
|
|
_soundNo = -1;
|
||
|
|
}
|
||
|
|
|
||
|
|
_wpnField8 = wpnField8;
|
||
|
|
if (_wpnField8 < 3) {
|
||
|
|
_wpnField8 = 1;
|
||
|
|
} else if ((_doubleDelay && rs.getRandomNumber(1) == 0) || (rs.getRandomNumber(4) == 0)) {
|
||
|
|
a->setAttackAimFlag(true);
|
||
|
|
_wpnField8 *= 4;
|
||
|
|
}
|
||
|
|
_fireTimestamp = ticknow;
|
||
|
|
if (_timer4 == 0)
|
||
|
|
_timer4 = ticknow;
|
||
|
|
|
||
|
|
#ifdef WATCHACTOR
|
||
|
|
if (_itemNum == WATCHACTOR)
|
||
|
|
debug("Attack: firing weapon at tick %d!", ticknow);
|
||
|
|
#endif
|
||
|
|
|
||
|
|
const ProcId animpid = a->doAnim(Animation::attack, dir_current); // fire small weapon.
|
||
|
|
if (animpid == 0) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
waitFor(animpid);
|
||
|
|
_wpnField8--;
|
||
|
|
if (_wpnField8 == 0)
|
||
|
|
return;
|
||
|
|
|
||
|
|
// TODO: this is not correct - should be Process_11e0_15ab(animpid); (not waitFor)..
|
||
|
|
waitFor(animpid);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
_timer3set = false;
|
||
|
|
a->setActivity(5);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if (targetdir == dir_invalid) {
|
||
|
|
targetdir = a->getDirToItemCentre(*target);
|
||
|
|
}
|
||
|
|
|
||
|
|
Point3 apt = a->getLocation();
|
||
|
|
Point3 tpt = target->getLocation();
|
||
|
|
const int32 dist = apt.maxDistXYZ(tpt);
|
||
|
|
const int32 zdiff = abs(a->getZ() - target->getZ());
|
||
|
|
const bool onscreen = a->isPartlyOnScreen(); // note: original uses "isMajorityOnScreen", this is close enough.
|
||
|
|
if ((!_isActivity9orB && !onscreen) || (dist <= zdiff)) {
|
||
|
|
#ifdef WATCHACTOR
|
||
|
|
if (_itemNum == WATCHACTOR)
|
||
|
|
debug("Attack: Not 9/B and actor not onscreen or dist %d < zdiff %d, pathfinding", dist, zdiff);
|
||
|
|
#endif
|
||
|
|
pathfindToItemInNPCData();
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
if (targetdir == curdir) {
|
||
|
|
#ifdef WATCHACTOR
|
||
|
|
if (_itemNum == WATCHACTOR)
|
||
|
|
debug("Attack: targetdir == currentdir");
|
||
|
|
#endif
|
||
|
|
const uint16 rnd = rs.getRandomNumber(9);
|
||
|
|
const uint32 frameno = Kernel::get_instance()->getFrameNum();
|
||
|
|
const uint32 timeoutfinish = target->getAttackMoveTimeoutFinishFrame();
|
||
|
|
|
||
|
|
if (!onscreen ||
|
||
|
|
(!_field96 && !timer4and5Update(ticknow) && frameno < timeoutfinish
|
||
|
|
&& rnd > 2 && (!_isActivityAorB || rnd > 3))) {
|
||
|
|
sleep(0x14);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
_field96 = false;
|
||
|
|
bool ready;
|
||
|
|
if (ticknow - a->getLastTickWasHit() <= 120)
|
||
|
|
ready = true;
|
||
|
|
else
|
||
|
|
ready = checkReady(ticknow, targetdir);
|
||
|
|
|
||
|
|
if (_timer2set && (rs.getRandomNumber(4) == 0 || checkTimer2PlusDelayElapsed(ticknow))) {
|
||
|
|
_timer2set = false;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!ready) {
|
||
|
|
if (!_isActivity9orB)
|
||
|
|
pathfindToItemInNPCData();
|
||
|
|
else
|
||
|
|
sleep(0xf);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
checkRandomAttackSound(ticknow, a->getShape());
|
||
|
|
|
||
|
|
if (!a->hasActorFlags(Actor::ACT_WEAPONREADY)) {
|
||
|
|
_timer4 = ticknow;
|
||
|
|
a->doAnim(Animation::readyWeapon, dir_current); // ready small wpn
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Wait until sound is finished playing.
|
||
|
|
if (_soundNo != -1) {
|
||
|
|
if (audio->isSFXPlayingForObject(_soundNo, _itemNum))
|
||
|
|
return;
|
||
|
|
_soundNo = -1;
|
||
|
|
}
|
||
|
|
|
||
|
|
const int32 t5elapsed = ticknow - _timer5;
|
||
|
|
if (t5elapsed > _wpnBasedTimeout) {
|
||
|
|
const int32 fireelapsed = ticknow - _fireTimestamp;
|
||
|
|
if (fireelapsed <= _difficultyBasedTimeout) {
|
||
|
|
sleep(_difficultyBasedTimeout - fireelapsed);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!wpn) {
|
||
|
|
_wpnField8 = 1;
|
||
|
|
} else {
|
||
|
|
_wpnField8 = wpnField8;
|
||
|
|
if (_wpnField8 > 2 && ((_doubleDelay && rs.getRandomNumber(1) == 0) || rs.getRandomNumber(4) == 0)) {
|
||
|
|
a->setAttackAimFlag(true);
|
||
|
|
_wpnField8 *= 4;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
_fireTimestamp = ticknow;
|
||
|
|
if (_timer4 == 0) {
|
||
|
|
_timer4 = ticknow;
|
||
|
|
}
|
||
|
|
|
||
|
|
const ProcId firepid = a->doAnim(Animation::attack, dir_current); // Fire SmallWpn
|
||
|
|
if (firepid != 0) {
|
||
|
|
waitFor(firepid);
|
||
|
|
_wpnField8--;
|
||
|
|
if (_wpnField8 != 0) {
|
||
|
|
// TODO: this is not correct - should be Process_11e0_15ab(firepid); (not waitFor)..
|
||
|
|
waitFor(firepid);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
} else if (t5elapsed != 0) {
|
||
|
|
sleep(_wpnBasedTimeout - t5elapsed);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
#ifdef WATCHACTOR
|
||
|
|
if (_itemNum == WATCHACTOR)
|
||
|
|
debug("Attack: targetdir != currentdir");
|
||
|
|
#endif
|
||
|
|
bool ready;
|
||
|
|
if (!timer4and5Update(ticknow) && !_field7f) {
|
||
|
|
if (standDirMode != dirmode_16dirs) {
|
||
|
|
targetdir = a->getDirToItemCentre(*target);
|
||
|
|
}
|
||
|
|
ready = a->fireDistance(target, targetdir, 0, 0, 0);
|
||
|
|
if (ready)
|
||
|
|
timeNowToTimerVal2(ticknow);
|
||
|
|
} else {
|
||
|
|
timeNowToTimerVal2(ticknow);
|
||
|
|
ready = true;
|
||
|
|
_field7f = false;
|
||
|
|
}
|
||
|
|
|
||
|
|
// 5a flag 1 set?
|
||
|
|
if (!a->hasActorFlags(Actor::ACT_WEAPONREADY) && ready) {
|
||
|
|
_timer4 = ticknow;
|
||
|
|
a->doAnim(Animation::readyWeapon, dir_current); // ready SmallWpn
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
if (ready || _isActivity9orB) {
|
||
|
|
a->turnTowardDir(targetdir);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
pathfindToItemInNPCData();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
void AttackProcess::checkRandomAttackSoundRegret(const Actor *actor) {
|
||
|
|
if (!readyForNextSound(Kernel::get_instance()->getTickNum()))
|
||
|
|
return;
|
||
|
|
|
||
|
|
AudioProcess *audio = AudioProcess::get_instance();
|
||
|
|
if (audio->isSFXPlayingForObject(-1, actor->getObjId()))
|
||
|
|
return;
|
||
|
|
|
||
|
|
int16 sndno = getRandomAttackSoundRegret(actor);
|
||
|
|
|
||
|
|
if (sndno != -1 && _lastAttackSound != sndno && _lastLastAttackSound != sndno) {
|
||
|
|
_lastLastAttackSound = _lastAttackSound;
|
||
|
|
_lastAttackSound = sndno;
|
||
|
|
_soundNo = sndno;
|
||
|
|
audio->playSFX(sndno, 0x80, actor->getObjId(), 1);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/* static */
|
||
|
|
int16 AttackProcess::getRandomAttackSoundRegret(const Actor *actor) {
|
||
|
|
if (World::get_instance()->getControlledNPCNum() != kMainActorId)
|
||
|
|
return -1;
|
||
|
|
|
||
|
|
if (actor->isDead())
|
||
|
|
return -1;
|
||
|
|
|
||
|
|
Common::RandomSource &rs = Ultima8Engine::get_instance()->getRandomSource();
|
||
|
|
uint32 shapeno = actor->getShape();
|
||
|
|
|
||
|
|
int16 sndno = -1;
|
||
|
|
// The order here is pretty random, how it comes out of the disasm.
|
||
|
|
switch (shapeno) {
|
||
|
|
case 0x4e0:
|
||
|
|
sndno = RANDOM_ELEM(REG_SFX_8);
|
||
|
|
break;
|
||
|
|
case 899:
|
||
|
|
sndno = RANDOM_ELEM(REG_SFX_14);
|
||
|
|
break;
|
||
|
|
case 900:
|
||
|
|
sndno = RANDOM_ELEM(REG_SFX_7);
|
||
|
|
break;
|
||
|
|
case 0x4d1:
|
||
|
|
case 0x528:
|
||
|
|
sndno = RANDOM_ELEM(REG_SFX_3);
|
||
|
|
break;
|
||
|
|
case 0x344:
|
||
|
|
sndno = RANDOM_ELEM(REG_SFX_13);
|
||
|
|
break;
|
||
|
|
case 0x371:
|
||
|
|
case 0x62f:
|
||
|
|
case 0x630:
|
||
|
|
sndno = RANDOM_ELEM(REG_SFX_2);
|
||
|
|
break;
|
||
|
|
case 0x2f5:
|
||
|
|
sndno = RANDOM_ELEM(REG_SFX_9);
|
||
|
|
break;
|
||
|
|
case 0x2f6:
|
||
|
|
sndno = RANDOM_ELEM(REG_SFX_12);
|
||
|
|
break;
|
||
|
|
case 0x2f7:
|
||
|
|
case 0x595:
|
||
|
|
sndno = RANDOM_ELEM(REG_SFX_11);
|
||
|
|
break;
|
||
|
|
case 0x2df:
|
||
|
|
sndno = RANDOM_ELEM(REG_SFX_6);
|
||
|
|
break;
|
||
|
|
case 0x597:
|
||
|
|
sndno = RANDOM_ELEM(REG_SFX_10);
|
||
|
|
break;
|
||
|
|
case 0x5b1:
|
||
|
|
sndno = RANDOM_ELEM(REG_SFX_15);
|
||
|
|
break;
|
||
|
|
case 0x5ff:
|
||
|
|
case 0x5d7:
|
||
|
|
sndno = RANDOM_ELEM(REG_SFX_5);
|
||
|
|
break;
|
||
|
|
case 0x1b4:
|
||
|
|
case 0x625:
|
||
|
|
sndno = RANDOM_ELEM(REG_SFX_4);
|
||
|
|
break;
|
||
|
|
case 0x5f0:
|
||
|
|
case 0x308:
|
||
|
|
sndno = RANDOM_ELEM(REG_SFX_1);
|
||
|
|
break;
|
||
|
|
default:
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
|
||
|
|
return sndno;
|
||
|
|
}
|
||
|
|
|
||
|
|
void AttackProcess::checkRandomAttackSound(int now, uint32 shapeno) {
|
||
|
|
if (GAME_IS_REGRET) {
|
||
|
|
checkRandomAttackSoundRegret(getActor(_itemNum));
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
Common::RandomSource &rs = Ultima8Engine::get_instance()->getRandomSource();
|
||
|
|
AudioProcess *audio = AudioProcess::get_instance();
|
||
|
|
int16 attacksound = -1;
|
||
|
|
if (!_playedStartSound) {
|
||
|
|
_playedStartSound = true;
|
||
|
|
if (rs.getRandomNumber(2) == 0) {
|
||
|
|
switch(shapeno) {
|
||
|
|
case 0x371:
|
||
|
|
attacksound = RANDOM_ELEM(REM_SFX_3);
|
||
|
|
break;
|
||
|
|
case 0x1b4:
|
||
|
|
attacksound = RANDOM_ELEM(REM_SFX_5);
|
||
|
|
break;
|
||
|
|
case 0x2fd:
|
||
|
|
case 0x319:
|
||
|
|
attacksound = RANDOM_ELEM(REM_SFX_1);
|
||
|
|
break;
|
||
|
|
case 900:
|
||
|
|
attacksound = RANDOM_ELEM(REM_SFX_2);
|
||
|
|
break;
|
||
|
|
case 0x4d1:
|
||
|
|
case 0x528:
|
||
|
|
attacksound = RANDOM_ELEM(REM_SFX_4);
|
||
|
|
break;
|
||
|
|
default:
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
if (readyForNextSound(now)) {
|
||
|
|
if (shapeno == 0x2df)
|
||
|
|
attacksound = RANDOM_ELEM(REM_SFX_6);
|
||
|
|
else if (shapeno == 899)
|
||
|
|
attacksound = RANDOM_ELEM(REM_SFX_7);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if (attacksound != -1) {
|
||
|
|
_soundNo = attacksound;
|
||
|
|
audio->playSFX(attacksound, 0x80, _itemNum, 1);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
bool AttackProcess::readyForNextSound(uint32 now) {
|
||
|
|
if (_soundTimestamp == 0 || now - _soundTimestamp >= _soundDelayTicks) {
|
||
|
|
_soundTimestamp = now;
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
bool AttackProcess::checkTimer2PlusDelayElapsed(int now) {
|
||
|
|
int delay = 60;
|
||
|
|
if (_doubleDelay)
|
||
|
|
delay *= 2;
|
||
|
|
return (now >= _timer2 + delay);
|
||
|
|
}
|
||
|
|
|
||
|
|
void AttackProcess::setAttackData(uint16 off, uint16 val) {
|
||
|
|
if (off >= MAGIC_DATA_OFF && off < MAGIC_DATA_OFF + ARRAYSIZE(_dataArray) - 1)
|
||
|
|
_dataArray[off - MAGIC_DATA_OFF] = val;
|
||
|
|
|
||
|
|
warning("Invalid offset to setAttackDataArray %d %d", off, val);
|
||
|
|
}
|
||
|
|
|
||
|
|
uint16 AttackProcess::getAttackData(uint16 off) const {
|
||
|
|
if (off >= MAGIC_DATA_OFF && off < MAGIC_DATA_OFF + ARRAYSIZE(_dataArray) - 1)
|
||
|
|
return _dataArray[off - MAGIC_DATA_OFF];
|
||
|
|
|
||
|
|
warning("Invalid offset to getAttackDataArray: %d", off);
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
void AttackProcess::pathfindToItemInNPCData() {
|
||
|
|
#ifdef WATCHACTOR
|
||
|
|
if (_itemNum == WATCHACTOR)
|
||
|
|
debug("Attack: pathfindToItemInNPCData");
|
||
|
|
#endif
|
||
|
|
_doubleDelay = false;
|
||
|
|
_timer2set = false;
|
||
|
|
_field96 = true;
|
||
|
|
|
||
|
|
Actor *a = getActor(_itemNum);
|
||
|
|
Actor *target = getActor(_target);
|
||
|
|
|
||
|
|
Process *pathproc = new CruPathfinderProcess(a, target, 12, 0x80, false);
|
||
|
|
// In case pathfinding fails delay for a bit to ensure we don't get
|
||
|
|
// stuck in a tight loop using all the cpu
|
||
|
|
Process *delayproc = new DelayProcess(10);
|
||
|
|
Kernel::get_instance()->addProcess(pathproc);
|
||
|
|
Kernel::get_instance()->addProcess(delayproc);
|
||
|
|
delayproc->waitFor(pathproc);
|
||
|
|
waitFor(delayproc);
|
||
|
|
}
|
||
|
|
|
||
|
|
bool AttackProcess::timer4and5Update(int now) {
|
||
|
|
int32 delay = 120;
|
||
|
|
if (_doubleDelay) {
|
||
|
|
delay = 240;
|
||
|
|
}
|
||
|
|
|
||
|
|
#ifdef WATCHACTOR
|
||
|
|
if (_itemNum == WATCHACTOR)
|
||
|
|
debug("Attack: timer4and5Update (doubledelay=%d, timer4=%d, timer5=%d)",
|
||
|
|
_doubleDelay, _timer4, _timer5);
|
||
|
|
#endif
|
||
|
|
|
||
|
|
if (_timer4) {
|
||
|
|
_timer5 = _timer4;
|
||
|
|
if (_timer4 + delay >= now) {
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
_timer4 = 0;
|
||
|
|
_doubleDelay = false;
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
bool AttackProcess::checkReady(int now, Direction targetdir) {
|
||
|
|
if (timer4and5Update(now) || _timer2set) {
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
Actor *a = getActor(_itemNum);
|
||
|
|
Actor *target = getActor(_target);
|
||
|
|
if (!a || !target)
|
||
|
|
return false;
|
||
|
|
return a->fireDistance(target, targetdir, 0, 0, 0) > 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
void AttackProcess::timeNowToTimerVal2(int now) {
|
||
|
|
_timer2 = now;
|
||
|
|
_timer2set = true;
|
||
|
|
}
|
||
|
|
|
||
|
|
void AttackProcess::setTimer3() {
|
||
|
|
Common::RandomSource &rs = Ultima8Engine::get_instance()->getRandomSource();
|
||
|
|
const int32 now = Kernel::get_instance()->getTickNum();
|
||
|
|
_timer3set = true;
|
||
|
|
_timer3 = rs.getRandomNumber(9) * 60 + now;
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
void AttackProcess::sleep(int ticks) {
|
||
|
|
// waiting less than 2 ticks can cause a tight loop
|
||
|
|
#ifdef WATCHACTOR
|
||
|
|
if (_itemNum == WATCHACTOR)
|
||
|
|
debug("Attack: sleeping for %d", ticks);
|
||
|
|
#endif
|
||
|
|
ticks = MAX(ticks, 2);
|
||
|
|
Process *delayProc = new DelayProcess(ticks);
|
||
|
|
ProcId pid = Kernel::get_instance()->addProcess(delayProc);
|
||
|
|
waitFor(pid);
|
||
|
|
}
|
||
|
|
|
||
|
|
void AttackProcess::setTacticNo(int tactic) {
|
||
|
|
assert(tactic < 32);
|
||
|
|
_tactic = tactic;
|
||
|
|
_tacticDat = GameData::get_instance()->getCombatDat(tactic);
|
||
|
|
delete _tacticDatReadStream;
|
||
|
|
_tacticDatReadStream = new Common::MemoryReadStream(_tacticDat->getData(), _tacticDat->getDataLen());
|
||
|
|
setBlockNo(0);
|
||
|
|
}
|
||
|
|
|
||
|
|
void AttackProcess::setBlockNo(int block) {
|
||
|
|
_block = block;
|
||
|
|
|
||
|
|
if (!_tacticDat)
|
||
|
|
return;
|
||
|
|
|
||
|
|
_tacticDatStartOffset = _tacticDat->getOffset(block);
|
||
|
|
_tacticDatReadStream->seek(_tacticDatStartOffset, SEEK_SET);
|
||
|
|
}
|
||
|
|
|
||
|
|
uint16 AttackProcess::readNextWordWithData() {
|
||
|
|
uint16 data = _tacticDatReadStream->readUint16LE();
|
||
|
|
if (data >= MAGIC_DATA_OFF) {
|
||
|
|
data = getAttackData(data);
|
||
|
|
}
|
||
|
|
|
||
|
|
return data;
|
||
|
|
}
|
||
|
|
|
||
|
|
uint16 AttackProcess::readNextWordRaw() {
|
||
|
|
assert(_tacticDatReadStream);
|
||
|
|
return _tacticDatReadStream->readUint16LE();
|
||
|
|
}
|
||
|
|
|
||
|
|
Common::String AttackProcess::dumpInfo() const {
|
||
|
|
return Process::dumpInfo();
|
||
|
|
}
|
||
|
|
|
||
|
|
void AttackProcess::saveData(Common::WriteStream *ws) {
|
||
|
|
Process::saveData(ws);
|
||
|
|
|
||
|
|
ws->writeUint16LE(_target);
|
||
|
|
ws->writeUint16LE(_tactic);
|
||
|
|
ws->writeUint16LE(_block);
|
||
|
|
ws->writeUint16LE(_tacticDatStartOffset);
|
||
|
|
|
||
|
|
ws->writeUint16LE(_soundNo);
|
||
|
|
ws->writeByte(_playedStartSound ? 1 : 0);
|
||
|
|
ws->writeByte(Direction_ToUsecodeDir(_npcInitialDir));
|
||
|
|
|
||
|
|
ws->writeSint16LE(_field57);
|
||
|
|
ws->writeUint16LE(_field59);
|
||
|
|
ws->writeByte(_field7f ? 1 : 0);
|
||
|
|
ws->writeByte(_field96 ? 1 : 0);
|
||
|
|
ws->writeByte(_field97 ? 1 : 0);
|
||
|
|
|
||
|
|
ws->writeByte(_isActivity9orB ? 1 : 0);
|
||
|
|
ws->writeByte(_isActivityAorB ? 1 : 0);
|
||
|
|
ws->writeByte(_timer2set ? 1 : 0);
|
||
|
|
ws->writeByte(_timer3set ? 1 : 0);
|
||
|
|
ws->writeByte(_doubleDelay ? 1 : 0);
|
||
|
|
|
||
|
|
ws->writeUint16LE(_wpnField8);
|
||
|
|
|
||
|
|
for (int i = 0; i < ARRAYSIZE(_dataArray); i++) {
|
||
|
|
ws->writeUint16LE(_dataArray[i]);
|
||
|
|
}
|
||
|
|
|
||
|
|
ws->writeSint32LE(_wpnBasedTimeout);
|
||
|
|
ws->writeSint32LE(_difficultyBasedTimeout);
|
||
|
|
|
||
|
|
ws->writeSint32LE(_timer2);
|
||
|
|
ws->writeSint32LE(_timer3);
|
||
|
|
ws->writeSint32LE(_timer4);
|
||
|
|
ws->writeSint32LE(_timer5);
|
||
|
|
ws->writeSint32LE(_soundTimestamp); // bug: this should ideally be unsigned, probably won't make any difference.
|
||
|
|
// Don't write the sound delay because it only affects
|
||
|
|
// No Regret, adding it now would need a version bump, and
|
||
|
|
// and it doesn't make much difference to re-randomize it.
|
||
|
|
ws->writeSint32LE(_fireTimestamp);
|
||
|
|
}
|
||
|
|
|
||
|
|
bool AttackProcess::loadData(Common::ReadStream *rs, uint32 version) {
|
||
|
|
if (!Process::loadData(rs, version)) return false;
|
||
|
|
|
||
|
|
_target = rs->readUint16LE();
|
||
|
|
setTacticNo(rs->readUint16LE());
|
||
|
|
setBlockNo(rs->readUint16LE());
|
||
|
|
_tacticDatStartOffset = rs->readUint16LE();
|
||
|
|
|
||
|
|
_soundNo = rs->readUint16LE();
|
||
|
|
_playedStartSound = rs->readByte();
|
||
|
|
_npcInitialDir = Direction_FromUsecodeDir(rs->readByte());
|
||
|
|
|
||
|
|
_field57 = rs->readSint16LE();
|
||
|
|
_field59 = rs->readUint16LE();
|
||
|
|
_field7f = rs->readByte();
|
||
|
|
_field96 = rs->readByte();
|
||
|
|
_field97 = rs->readByte();
|
||
|
|
|
||
|
|
_isActivity9orB= rs->readByte();
|
||
|
|
_isActivityAorB = rs->readByte();
|
||
|
|
_timer2set = rs->readByte();
|
||
|
|
_timer3set = rs->readByte();
|
||
|
|
_doubleDelay = rs->readByte();
|
||
|
|
|
||
|
|
_wpnField8 = rs->readUint16LE();
|
||
|
|
|
||
|
|
for (int i = 0; i < ARRAYSIZE(_dataArray); i++) {
|
||
|
|
_dataArray[i] = rs->readUint16LE();
|
||
|
|
}
|
||
|
|
|
||
|
|
_wpnBasedTimeout = rs->readSint32LE();
|
||
|
|
_difficultyBasedTimeout = rs->readSint32LE();
|
||
|
|
|
||
|
|
_timer2 = rs->readSint32LE();
|
||
|
|
_timer3 = rs->readSint32LE();
|
||
|
|
_timer4 = rs->readSint32LE();
|
||
|
|
_timer5 = rs->readSint32LE();
|
||
|
|
_soundTimestamp = rs->readSint32LE();
|
||
|
|
_fireTimestamp = rs->readSint32LE();
|
||
|
|
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
} // End of namespace Ultima8
|
||
|
|
} // End of namespace Ultima
|