[Scummvm-git-logs] scummvm master -> c5805bdef3717a072820fa6d9a84fb3973f2fa36
mduggan
mgithub at guarana.org
Fri Oct 30 13:18:54 UTC 2020
This automated email contains information about 1 new commit which have been
pushed to the 'scummvm' repo located at https://github.com/scummvm/scummvm .
Summary:
c5805bdef3 ULTIMA8: Add Crusader attack process.
Commit: c5805bdef3717a072820fa6d9a84fb3973f2fa36
https://github.com/scummvm/scummvm/commit/c5805bdef3717a072820fa6d9a84fb3973f2fa36
Author: Matthew Duggan (mgithub at guarana.org)
Date: 2020-10-30T22:17:49+09:00
Commit Message:
ULTIMA8: Add Crusader attack process.
This implementation is from No Remorse - have not compared with No Regret,
although its combat.dat file is identical so expect not many differences. It's
also not complete yet (see TODOs in code), but it's most of the way there.
Changed paths:
A engines/ultima/ultima8/world/actors/attack_process.cpp
A engines/ultima/ultima8/world/actors/attack_process.h
engines/ultima/module.mk
engines/ultima/ultima8/convert/crusader/convert_usecode_crusader.h
engines/ultima/ultima8/graphics/type_flags.cpp
engines/ultima/ultima8/misc/direction.h
engines/ultima/ultima8/ultima8.cpp
engines/ultima/ultima8/usecode/remorse_intrinsics.h
engines/ultima/ultima8/world/actors/actor.cpp
engines/ultima/ultima8/world/actors/actor.h
engines/ultima/ultima8/world/actors/actor_anim_process.cpp
engines/ultima/ultima8/world/actors/animation.h
engines/ultima/ultima8/world/actors/avatar_mover_process.cpp
engines/ultima/ultima8/world/actors/combat_dat.cpp
engines/ultima/ultima8/world/actors/combat_dat.h
engines/ultima/ultima8/world/actors/main_actor.cpp
engines/ultima/ultima8/world/actors/main_actor.h
engines/ultima/ultima8/world/actors/resurrection_process.cpp
engines/ultima/ultima8/world/super_sprite_process.cpp
engines/ultima/ultima8/world/weapon_info.h
diff --git a/engines/ultima/module.mk b/engines/ultima/module.mk
index 59822cf863..0b0a1eeb37 100644
--- a/engines/ultima/module.mk
+++ b/engines/ultima/module.mk
@@ -560,6 +560,7 @@ MODULE_OBJS := \
ultima8/world/actors/animation.o \
ultima8/world/actors/animation_tracker.o \
ultima8/world/actors/anim_action.o \
+ ultima8/world/actors/attack_process.o \
ultima8/world/actors/avatar_death_process.o \
ultima8/world/actors/avatar_gravity_process.o \
ultima8/world/actors/avatar_mover_process.o \
diff --git a/engines/ultima/ultima8/convert/crusader/convert_usecode_crusader.h b/engines/ultima/ultima8/convert/crusader/convert_usecode_crusader.h
index b328e7e8b8..94eea93e34 100644
--- a/engines/ultima/ultima8/convert/crusader/convert_usecode_crusader.h
+++ b/engines/ultima/ultima8/convert/crusader/convert_usecode_crusader.h
@@ -222,7 +222,7 @@ const char* const ConvertUsecodeCrusader::_intrinsics[] = {
"void Actor::I_setDead(Actor *)", // part of same coff set 021, 060, 073, 0A0, 0A8, 0D8, 0E7, 135
"void I_playFlic0A9(char *)", // same coff as 092
"void AudioProcess::I_playSFX(2 bytes)", // same coff as 0D4
- "byte Actor::I_NPCGetField0x59Flag1_0AB(Actor *)",
+ "byte Actor::I_getField0x59Bit1(Actor *)",
"int16 Item::I_getFamilyOfType(Item *)", // per pentagram notes, matches disasm.
"int16 Item::I_getNPCNum(Item *)", // part of same coff set 067, 06D, 089, 08E, 0AD, 0F8, 100, 102, 105, 107, 109, 10B, 10D, 10F, 111, 115, 11C, 123, 129
"int16 Item::I_getQLo(Item *)", // same as 02B based on same coff set 010, 02B, 066, 084, 0A1, 0AE, 0D9, 0EA
diff --git a/engines/ultima/ultima8/graphics/type_flags.cpp b/engines/ultima/ultima8/graphics/type_flags.cpp
index 753e04219e..6916a647a0 100644
--- a/engines/ultima/ultima8/graphics/type_flags.cpp
+++ b/engines/ultima/ultima8/graphics/type_flags.cpp
@@ -267,6 +267,10 @@ void TypeFlags::loadWeaponInfo() {
else
wi->_small = 0;
+ // TODO: this should be 1, 2, or 3 depending on weapon.
+ // It's used in the AttackProcess
+ wi->_field8 = 1;
+
if (wi->_shape > _shapeInfo.size()) {
warning("ignoring weapon info for shape %d beyond size %d.",
wi->_shape, _shapeInfo.size());
diff --git a/engines/ultima/ultima8/misc/direction.h b/engines/ultima/ultima8/misc/direction.h
index 9679f3e3f8..161ce695b7 100644
--- a/engines/ultima/ultima8/misc/direction.h
+++ b/engines/ultima/ultima8/misc/direction.h
@@ -46,7 +46,8 @@ enum Direction {
dir_wnw = 13,
dir_northwest = 14,
dir_nnw = 15,
- dir_current = 16
+ dir_current = 16,
+ dir_invalid = 100
};
enum DirectionMode {
diff --git a/engines/ultima/ultima8/ultima8.cpp b/engines/ultima/ultima8/ultima8.cpp
index aefb6d54e1..a5c86959d5 100644
--- a/engines/ultima/ultima8/ultima8.cpp
+++ b/engines/ultima/ultima8/ultima8.cpp
@@ -110,7 +110,10 @@
#include "ultima/ultima8/world/actors/surrender_process.h"
#include "ultima/ultima8/world/actors/combat_process.h"
#include "ultima/ultima8/world/actors/guard_process.h"
+#include "ultima/ultima8/world/actors/attack_process.h"
+#include "ultima/ultima8/world/actors/pace_process.h"
#include "ultima/ultima8/world/fireball_process.h"
+#include "ultima/ultima8/world/super_sprite_process.h"
#include "ultima/ultima8/world/destroy_item_process.h"
#include "ultima/ultima8/world/actors/ambush_process.h"
#include "ultima/ultima8/world/actors/pathfinder.h"
@@ -306,6 +309,12 @@ bool Ultima8Engine::startup() {
ProcessLoader<CrosshairProcess>::load);
_kernel->addProcessLoader("ItemSelectionProcess",
ProcessLoader<ItemSelectionProcess>::load);
+ _kernel->addProcessLoader("PaceProcess",
+ ProcessLoader<PaceProcess>::load);
+ _kernel->addProcessLoader("SuperSpriteProcess",
+ ProcessLoader<SuperSpriteProcess>::load);
+ _kernel->addProcessLoader("AttackProcess",
+ ProcessLoader<AttackProcess>::load);
_objectManager = new ObjectManager();
_mouse = new Mouse();
diff --git a/engines/ultima/ultima8/usecode/remorse_intrinsics.h b/engines/ultima/ultima8/usecode/remorse_intrinsics.h
index a03cfd2973..06e3e5954e 100644
--- a/engines/ultima/ultima8/usecode/remorse_intrinsics.h
+++ b/engines/ultima/ultima8/usecode/remorse_intrinsics.h
@@ -216,7 +216,7 @@ Intrinsic RemorseIntrinsics[] = {
Actor::I_setDead,
0, // I_playFlic(char *) Intrinsic0A9(void)
AudioProcess::I_playSFX, // void Intrinsic0AA(2 bytes)
- 0, // int Intrinsic0AB(4 bytes)
+ Actor::I_getField0x59Bit1, // int Intrinsic0AB(4 bytes)
Item::I_getFamilyOfType, // void Intrinsic0AC(2 bytes)
Item::I_getNpcNum, // based on same coff as 102 (-> variable name in TRIGGER::ordinal21)
Item::I_getQLo, // based on same coff set as 02B
diff --git a/engines/ultima/ultima8/world/actors/actor.cpp b/engines/ultima/ultima8/world/actors/actor.cpp
index 0dbb68085e..7dc2c3cedf 100644
--- a/engines/ultima/ultima8/world/actors/actor.cpp
+++ b/engines/ultima/ultima8/world/actors/actor.cpp
@@ -46,6 +46,7 @@
#include "ultima/ultima8/world/actors/loiter_process.h"
#include "ultima/ultima8/world/actors/guard_process.h"
#include "ultima/ultima8/world/actors/combat_process.h"
+#include "ultima/ultima8/world/actors/attack_process.h"
#include "ultima/ultima8/world/actors/pace_process.h"
#include "ultima/ultima8/world/actors/surrender_process.h"
#include "ultima/ultima8/world/world.h"
@@ -75,7 +76,7 @@ Actor::Actor() : _strength(0), _dexterity(0), _intelligence(0),
_lastAnim(Animation::stand), _animFrame(0), _direction(dir_north),
_fallStart(0), _unkByte(0), _actorFlags(0), _combatTactic(0),
_homeX(0), _homeY(0), _homeZ(0), _currentActivityNo(0),
- _lastActivityNo(0), _activeWeapon(0) {
+ _lastActivityNo(0), _activeWeapon(0), _lastTimeWasHit(0) {
_defaultActivity[0] = 0;
_defaultActivity[1] = 0;
_defaultActivity[2] = 0;
@@ -635,7 +636,7 @@ uint16 Actor::setActivityU8(int activity) {
return Kernel::get_instance()->addProcess(new DelayProcess(1));
break;
case 1: // combat
- setInCombat();
+ setInCombatU8();
return 0;
case 2: // stand
// NOTE: temporary fall-throughs!
@@ -676,11 +677,11 @@ uint16 Actor::setActivityCru(int activity) {
break;
case 5:
case 9:
- case 10:
+ case 0xa:
case 0xb:
case 0xc:
// attack
- setInCombat();
+ setInCombatCru(activity);
return 0;
case 0xd:
// Only in No Regret
@@ -804,12 +805,14 @@ void Actor::receiveHitCru(uint16 other, Direction dir, int damage, uint16 damage
if (isDead())
return;
+ _lastTimeWasHit = Kernel::get_instance()->getFrameNum() * 2;
+
if (shape != 1 && this != getControlledActor()) {
Actor *controlled = getControlledActor();
if (!isInCombat()) {
setActivity(getDefaultActivity(2)); // get activity from field 0xA
if (!isInCombat()) {
- setInCombat();
+ setInCombatCru(5);
CombatProcess *combat = getCombatProcess();
if (combat && controlled) {
combat->setTarget(controlled->getObjId());
@@ -819,7 +822,7 @@ void Actor::receiveHitCru(uint16 other, Direction dir, int damage, uint16 damage
if (getCurrentActivityNo() == 8) {
setActivity(5);
}
- setInCombat();
+ setInCombatCru(5);
CombatProcess *combat = getCombatProcess();
if (combat && controlled) {
combat->setTarget(controlled->getObjId());
@@ -1004,7 +1007,7 @@ void Actor::receiveHitU8(uint16 other, Direction dir, int damage, uint16 damage_
if (attacker)
target = attacker->getObjId();
if (!isInCombat())
- setInCombat();
+ setInCombatU8();
CombatProcess *cp = getCombatProcess();
assert(cp);
@@ -1303,7 +1306,24 @@ CombatProcess *Actor::getCombatProcess() {
return cp;
}
-void Actor::setInCombat() {
+AttackProcess *Actor::getAttackProcess() {
+ Process *p = Kernel::get_instance()->findProcess(_objId, 0x259); // CONSTANT!
+ if (!p)
+ return nullptr;
+ AttackProcess *ap = dynamic_cast<AttackProcess *>(p);
+ assert(ap);
+
+ return ap;
+}
+
+void Actor::setInCombat(int activity) {
+ if (GAME_IS_U8)
+ setInCombatU8();
+ else
+ setInCombatCru(activity);
+}
+
+void Actor::setInCombatU8() {
if ((_actorFlags & ACT_INCOMBAT) != 0) return;
assert(getCombatProcess() == nullptr);
@@ -1324,12 +1344,55 @@ void Actor::setInCombat() {
setActorFlag(ACT_INCOMBAT);
}
+void Actor::setInCombatCru(int activity) {
+ if ((_actorFlags & ACT_INCOMBAT) != 0) return;
+
+ assert(getAttackProcess() == nullptr);
+
+ setActorFlag(ACT_INCOMBAT);
+
+ AttackProcess *ap = new AttackProcess(this);
+ Kernel::get_instance()->addProcess(ap);
+
+ if (getCurrentActivityNo() == 8) {
+ // Guard process.. set some flag in ap
+ ap->setField97();
+ }
+ if (activity == 0xc) {
+ ap->setTimer3();
+ // This sets fields 0x77 and 0x79 of the attack process
+ // to some random timer value in the future
+ //ap->AttackProcess_1108_1485();
+ }
+
+ uint16 animproc = 0;
+ if (activity == 9 || activity == 0xb) {
+ ap->setIsActivity9OrB();
+ animproc = doAnim(Animation::readyWeapon, dir_current);
+ } else {
+ animproc = doAnim(Animation::stand, dir_current);
+ }
+ if (animproc) {
+ // Do the animation first
+ ap->waitFor(animproc);
+ }
+
+ if (activity == 0xa || activity == 0xb) {
+ ap->setIsActivityAOrB();
+ }
+}
+
void Actor::clearInCombat() {
if ((_actorFlags & ACT_INCOMBAT) == 0) return;
- CombatProcess *cp = getCombatProcess();
- if (cp)
- cp->terminate();
+ Process *p;
+ if (GAME_IS_U8) {
+ p = getCombatProcess();
+ } else {
+ p = getAttackProcess();
+ }
+ if (p)
+ p->terminate();
clearActorFlag(ACT_INCOMBAT);
}
@@ -1477,6 +1540,7 @@ void Actor::saveData(Common::WriteStream *ws) {
ws->writeUint16LE(_currentActivityNo);
ws->writeUint16LE(_lastActivityNo);
ws->writeUint16LE(_activeWeapon);
+ ws->writeSint32LE(_lastTimeWasHit);
}
}
@@ -1508,6 +1572,7 @@ bool Actor::loadData(Common::ReadStream *rs, uint32 version) {
_currentActivityNo = rs->readUint16LE();
_lastActivityNo = rs->readUint16LE();
_activeWeapon = rs->readUint16LE();
+ _lastTimeWasHit = rs->readSint32LE();
}
return true;
@@ -1720,7 +1785,8 @@ uint32 Actor::I_setInCombat(const uint8 *args, unsigned int /*argsize*/) {
ARG_ACTOR_FROM_PTR(actor);
if (!actor) return 0;
- actor->setInCombat();
+ assert(GAME_IS_U8);
+ actor->setInCombatU8();
return 0;
}
@@ -1739,9 +1805,11 @@ uint32 Actor::I_setTarget(const uint8 *args, unsigned int /*argsize*/) {
ARG_UINT16(target);
if (!actor) return 0;
+ assert(GAME_IS_U8);
+
CombatProcess *cp = actor->getCombatProcess();
if (!cp) {
- actor->setInCombat();
+ actor->setInCombatU8();
cp = actor->getCombatProcess();
}
if (!cp) {
@@ -2236,6 +2304,15 @@ uint32 Actor::I_turnToward(const uint8 *args, unsigned int /*argsize*/) {
return actor->turnTowardDir(Direction_FromUsecodeDir(dir));
}
+uint32 Actor::I_getField0x59Bit1(const uint8 *args, unsigned int /*argsize*/) {
+ ARG_ACTOR_FROM_PTR(actor);
+ if (!actor) return 0;
+
+ if (actor->hasActorFlags(ACT_CRU5ABIT1))
+ return 1;
+ return 0;
+}
+
} // End of namespace Ultima8
} // End of namespace Ultima
diff --git a/engines/ultima/ultima8/world/actors/actor.h b/engines/ultima/ultima8/world/actors/actor.h
index 6a86a33736..4897cb4499 100644
--- a/engines/ultima/ultima8/world/actors/actor.h
+++ b/engines/ultima/ultima8/world/actors/actor.h
@@ -33,6 +33,7 @@ namespace Ultima8 {
class ActorAnimProcess;
struct PathfindingState;
class CombatProcess;
+class AttackProcess;
class Actor : public Container {
friend class ActorAnimProcess;
@@ -82,13 +83,10 @@ public:
bool isInCombat() const {
return (_actorFlags & ACT_INCOMBAT) != 0;
}
- void toggleInCombat() {
- if (isInCombat()) clearInCombat();
- else setInCombat();
- }
- CombatProcess *getCombatProcess();
- virtual void setInCombat();
+ CombatProcess *getCombatProcess(); // in U8
+ AttackProcess *getAttackProcess(); // in Crusader
+ virtual void setInCombat(int activity);
virtual void clearInCombat();
uint16 getAlignment() const {
@@ -222,6 +220,10 @@ public:
_lastActivityNo = 0;
}
+ int32 getLastTimeWasHit() const {
+ return _lastTimeWasHit;
+ }
+
//! run the given animation
//! \return the PID of the ActorAnimProcess
uint16 doAnim(Animation::Sequence anim, Direction dir, unsigned int steps = 0);
@@ -272,6 +274,10 @@ public:
return _activeWeapon;
}
+ uint16 getCombatTactic() const {
+ return _combatTactic;
+ }
+
bool activeWeaponIsSmall() const;
ENABLE_RUNTIME_CLASSTYPE()
@@ -339,6 +345,7 @@ public:
INTRINSIC(I_getLastActivityNo);
INTRINSIC(I_getCurrentActivityNo);
INTRINSIC(I_turnToward);
+ INTRINSIC(I_getField0x59Bit1);
enum ActorFlags {
ACT_INVINCIBLE = 0x000001, // flags from npcdata byte 0x1B
@@ -349,7 +356,8 @@ public:
ACT_FIRSTSTEP = 0x000400, // flags from npcdata byte 0x2F
ACT_INCOMBAT = 0x000800,
ACT_DEAD = 0x001000,
- ACT_SURRENDERED = 0x002000, // not the same bit as used in Crusader, but we use this because it's empty.
+ ACT_SURRENDERED = 0x002000, // not the same bit used in Crusader, but use this because it's empty.
+ ACT_CRU5ABIT1 = 0x004000, // not the same bit used in Crusader, but use this because it's empty.
ACT_COMBATRUN = 0x008000,
ACT_AIRWALK = 0x010000, // flags from npcdata byte 0x30
@@ -400,6 +408,9 @@ protected:
//! Active weapon item (only used in Crusader)
uint16 _activeWeapon;
+ //! Kernel timer last time NPC was hit (only used in Crusader)
+ int32 _lastTimeWasHit;
+
//! starts an activity (Ultima 8 version)
//! \return processID of process handling the activity or zero
uint16 setActivityU8(int activity);
@@ -413,6 +424,9 @@ protected:
void receiveHitU8(uint16 other, Direction dir, int damage, uint16 type);
void receiveHitCru(uint16 other, Direction dir, int damage, uint16 type);
+
+ void setInCombatU8();
+ void setInCombatCru(int activity);
};
} // End of namespace Ultima8
diff --git a/engines/ultima/ultima8/world/actors/actor_anim_process.cpp b/engines/ultima/ultima8/world/actors/actor_anim_process.cpp
index 912500efec..9e4a0ad59a 100644
--- a/engines/ultima/ultima8/world/actors/actor_anim_process.cpp
+++ b/engines/ultima/ultima8/world/actors/actor_anim_process.cpp
@@ -413,7 +413,8 @@ void ActorAnimProcess::doSpecial() {
}
if (hostile) {
- hostile->setInCombat();
+ // Note: only happens in U8, so activity num is not important.
+ hostile->setInCombat(0);
CombatProcess *hostilecp = hostile->getCombatProcess();
CombatProcess *cp = a->getCombatProcess();
if (hostilecp && cp)
diff --git a/engines/ultima/ultima8/world/actors/animation.h b/engines/ultima/ultima8/world/actors/animation.h
index ce41a0f98e..adf7abfe69 100644
--- a/engines/ultima/ultima8/world/actors/animation.h
+++ b/engines/ultima/ultima8/world/actors/animation.h
@@ -113,8 +113,8 @@ enum Sequence {
stopRunningAndDrawWeapon = 39,
kneelStart = 40,
kneelEnd = 41,
- kneelAndFire2 = 42,
- kneelAndFire3 = 43,
+ kneelAndFireSmallWeapon = 42,
+ kneelAndFireLargeWeapon = 43,
advanceWithLargeWeapon = 44,
quickRetreat = 45,
kneelingWithSmallWeapon = 46,
diff --git a/engines/ultima/ultima8/world/actors/attack_process.cpp b/engines/ultima/ultima8/world/actors/attack_process.cpp
new file mode 100644
index 0000000000..58aa0d0c6f
--- /dev/null
+++ b/engines/ultima/ultima8/world/actors/attack_process.cpp
@@ -0,0 +1,1010 @@
+/* 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 2
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "ultima/ultima8/misc/pent_include.h"
+
+#include "ultima/ultima8/world/actors/attack_process.h"
+
+#include "common/memstream.h"
+#include "ultima/ultima8/audio/audio_process.h"
+#include "ultima/ultima8/games/game_data.h"
+#include "ultima/ultima8/graphics/shape_info.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/actors/actor_anim_process.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/weapon_info.h"
+#include "ultima/ultima8/world/actors/animation_tracker.h"
+#include "ultima/ultima8/world/actors/combat_dat.h"
+#include "ultima/ultima8/world/actors/loiter_process.h"
+#include "ultima/ultima8/world/actors/pathfinder_process.h"
+#include "ultima/ultima8/misc/direction.h"
+#include "ultima/ultima8/misc/direction_util.h"
+
+namespace Ultima {
+namespace Ultima8 {
+
+// p_dynamic_cast stuff
+DEFINE_RUNTIME_CLASSTYPE_CODE(AttackProcess)
+
+static const int16 ATTACK_SFX_1[] = {0x15, 0x78, 0x80, 0x83, 0xDC, 0xDD};
+static const int16 ATTACK_SFX_2[] = {0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xE7};
+static const int16 ATTACK_SFX_3[] = {0xFC, 0xFD, 0xFE, 0xC8};
+static const int16 ATTACK_SFX_4[] = {0xCC, 0xCD, 0xCE, 0xCF};
+static const int16 ATTACK_SFX_5[] = {0xC7, 0xCA, 0xC9};
+static const int16 ATTACK_SFX_6[] = {0x82, 0x84, 0x85};
+static const int16 ATTACK_SFX_7[] = {0x9B, 0x9C, 0x9D, 0x9E, 0x9F};
+
+#define RANDOM_ELEM(array) (array[getRandom() % ARRAYSIZE(array)])
+
+// If data is referenced in the metalang with an offset of this or greater,
+// read from the data array.
+static const int MAGIC_DATA_OFFSET = 33000;
+
+
+static uint16 someSleepGlobal = 0;
+
+// TODO: work out what this function does - probably like "can see" or "is facing"
+// Should probably be in Actor
+static bool Intrinsic116(Actor *, Actor *, Direction, uint16, uint16, uint16) {
+ return false;
+}
+
+// TODO: Implement me. Set timer for some avatar moves.
+static bool World_FinishedAvatarMoveTimeout() {
+ return true;
+}
+
+
+static inline int32 randomOf(int32 max) {
+ return getRandom() % max;
+}
+
+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),
+_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), _fireTimestamp(0) {
+}
+
+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), _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), _fireTimestamp(0) {
+ assert(actor);
+ _itemNum = actor->getObjId();
+ _npcInitialDir = actor->getDir();
+
+ 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 = 0x0259; // CONSTANT !
+
+ setTacticNo(actor->getCombatTactic());
+}
+
+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->hasFlags(Item::FLG_FASTAREA) || a->isDead() || !_tacticDatReadStream) {
+ terminate();
+ return;
+ }
+
+ if (_tactic == 0) {
+ genericAttack();
+ return;
+ }
+
+ if (!target || target->isDead()) {
+ warning("got into attack process with invalid target");
+ terminate();
+ }
+
+ const Direction curdir = a->getDir();
+
+ const uint8 opcode = _tacticDatReadStream->readByte();
+ switch (opcode) {
+ case 0x81:
+ // Seems like field 0x53 is never used anywhere?
+ /*_field53 = */readNextWordWithGlobals();
+ return;
+ case 0x82:
+ /*_field53 = 0*/;
+ return;
+ case 0x84:
+ _target = readNextWordWithGlobals();
+ // 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_OneLeft(Direction_OneLeft(curdir, dirmode_8dirs), dirmode_8dirs);
+ a->turnTowardDir(newdir);
+ return;
+ }
+ case 0x89:
+ {
+ // Turn 90 degrees right
+ Direction newdir = Direction_OneRight(Direction_OneLeft(curdir, dirmode_8dirs), dirmode_8dirs);
+ a->turnTowardDir(newdir);
+ return;
+ }
+ case 0x8a:
+ {
+ bool result = Intrinsic116(a, target, curdir, 0, 0, 0);
+ // Fire small weapon
+ if (result)
+ a->doAnim(Animation::attack, dir_current);
+ return;
+ }
+ case 0x8b:
+ {
+ bool result = Intrinsic116(a, 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 PathfinderProcess(a, x, y, z));
+ waitFor(pid);
+ return;
+ }
+ case 0x8e:
+ {
+ // Pathfind to target
+ int32 x, y, z;
+ target->getLocation(x, y, z);
+ ProcId pid = Kernel::get_instance()->addProcess(
+ new PathfinderProcess(a, x, y, z));
+ waitFor(pid);
+ return;
+ }
+ case 0x8f:
+ {
+ // Pathfind to a point between npc and the target
+ int32 tx, ty, tz;
+ target->getLocation(tx, ty, tz);
+ int32 ax, ay, az;
+ target->getLocation(ax, ay, az);
+ int32 x = (tx + ax) / 2;
+ int32 y = (ty + ay) / 2;
+ int32 z = (tz + az) / 2;
+ ProcId pid = Kernel::get_instance()->addProcess(
+ new PathfinderProcess(a, x, y, z));
+ 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 = readNextWordWithGlobals();
+ if (ticks == someSleepGlobal) {
+ ticks = randomOf(0x32) + 0x14;
+ }
+ ticks /= World::get_instance()->getGameDifficulty();
+ sleep(ticks);
+ return;
+ }
+ case 0x94:
+ {
+ // Loiter a bit..
+ uint16 data = readNextWordWithGlobals();
+ 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(readNextWordWithGlobals());
+ return;
+ case 0x97:
+ // switch to tactic no specified by next word
+ setTacticNo(readNextWordWithGlobals());
+ 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, tpt;
+ a->getLocation(apt);
+ target->getLocation(tpt);
+ int maxdiff = apt.maxDistXYZ(tpt);
+ int16 data = readNextWordWithGlobals();
+ if (maxdiff < 481) {
+ _tacticDatReadStream->seek(data, SEEK_SET);
+ }
+ return;
+ }
+ case 0x9b:
+ {
+ // get next word and jump to that offset if distance > 160
+ Point3 apt, tpt;
+ a->getLocation(apt);
+ target->getLocation(tpt);
+ int maxdiff = apt.maxDistXYZ(tpt);
+ int16 data = readNextWordWithGlobals();
+ if (maxdiff > 160) {
+ _tacticDatReadStream->seek(data, SEEK_SET);
+ }
+ return;
+ }
+ case 0x9c:
+ {
+ bool result = Intrinsic116(a, target, curdir, 0, 0, 0);
+ uint16 data = readNextWordWithGlobals();
+ if (!result) {
+ _tacticDatReadStream->seek(data, SEEK_SET);
+ }
+ return;
+ }
+ case 0x9d:
+ {
+ bool result = Intrinsic116(a, target, curdir, 0, 0, 0);
+ uint16 data = readNextWordWithGlobals();
+ if (result) {
+ _tacticDatReadStream->seek(data, SEEK_SET);
+ }
+ return;
+ }
+ case 0x9e:
+ {
+ uint16 maxval = readNextWordWithGlobals();
+ uint16 randval = randomOf(maxval);
+ uint16 offset = readNextWordWithGlobals();
+ if (randval != 0) {
+ _tacticDatReadStream->seek(offset);
+ }
+ return;
+ }
+ case 0x9f:
+ _field57 = readNextWordWithGlobals();
+ _field59 = _tacticDatReadStream->pos();
+ return;
+ case 0xa6:
+ {
+ const uint16 targetFrame = readNextWordWithGlobals();
+ 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 PathfinderProcess(a, founditem->getObjId()));
+ 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 = readNextWordWithGlobals();
+ uint16 offset = readNextWord();
+ setAttackDataArray(offset, next);
+ return;
+ }
+ case 0xb0:
+ {
+ uint16 offset = readNextWord();
+ uint16 val = getAttackDataArray(offset);
+ setAttackDataArray(opcode, val + readNextWordWithGlobals());
+ return;
+ }
+ case 0xb1:
+ {
+ uint16 offset = readNextWord();
+ uint16 val = getAttackDataArray(offset);
+ setAttackDataArray(offset, val - readNextWordWithGlobals());
+ return;
+ }
+ case 0xb2:
+ {
+ uint16 offset = readNextWord();
+ uint16 val = getAttackDataArray(offset);
+ setAttackDataArray(offset, val * readNextWordWithGlobals());
+ return;
+ }
+ case 0xb3:
+ {
+ uint16 offset = readNextWord();
+ uint16 val = getAttackDataArray(offset);
+ setAttackDataArray(opcode, val / readNextWordWithGlobals());
+ return;
+ }
+ case 0xb4:
+ {
+ uint16 dir = Direction_ToUsecodeDir(curdir);
+ uint16 offset = readNextWord();
+ setAttackDataArray(offset, dir);
+ return;
+ }
+ case 0xb5:
+ {
+ uint16 dir = readNextWordWithGlobals();
+ a->setDir(Direction_FromUsecodeDir(dir));
+ return;
+ }
+ case 0xb6:
+ {
+ uint16 offset = readNextWord();
+ uint16 dir = Direction_ToUsecodeDir(curdir);
+ setAttackDataArray(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(readNextWordWithGlobals(), 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 (Kernel::get_instance()->getNumProcesses(_itemNum, ActorAnimProcess::ACTOR_ANIM_PROC_TYPE)
+ || a->hasActorFlags(Actor::ACT_PATHFINDING)) {
+ return;
+ }
+
+ const Item *wpn = getItem(a->getActiveWeapon());
+ /*if (!wpn) {
+ warning("started attack for NPC %d with no weapon", _itemNum);
+ }*/
+
+ AudioProcess *audio = AudioProcess::get_instance();
+ const Direction curdir = a->getDir();
+ const int32 now = Kernel::get_instance()->getFrameNum() * 2;
+ 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;
+ }
+
+ Actor *target = getActor(_target);
+ if (!target || !target->isOnScreen() || target->isDead()) {
+ // Walk around randomly in hope of finding target
+ _target = 0;
+ if (!_isActivity9orB) {
+ int32 x, y, z;
+ a->getLocation(x, y, z);
+ x += -0x1ff + randomOf(0x400);
+ y += -0x1ff + randomOf(0x400);
+ _field96 = true;
+ const ProcId pid = Kernel::get_instance()->addProcess(
+ new PathfinderProcess(a, x, y, z));
+ waitFor(pid);
+ return;
+ }
+ } else {
+ // TODO: Get directions anim has (8 or 16)
+ DirectionMode standDirMode = dirmode_8dirs;
+ /*
+ Animation::Sequence anim;
+ if (a->isInCombat()) {
+ anim = Animation::combatStand;
+ } else {
+ anim = Animation::stand;
+ }*/
+ if (_timer3set) {
+ if (_timer3 >= now) {
+ if (a->isInCombat()) {
+ if (randomOf(3) != 0) {
+ const Animation::Sequence lastanim = a->getLastAnim();
+ if ((lastanim != Animation::unreadyWeapon)) // TODO: && (lastanim != Animation::unreadyLargeWeapon))
+ a->doAnim(Animation::unreadyWeapon, dir_current);
+ else
+ a->doAnim(Animation::readyWeapon, dir_current);
+ return;
+ }
+
+ if (randomOf(3) == 0) {
+ // TODO: this should be a random dir based on some
+ // NPC field and stuff.
+ a->turnTowardDir(Direction_TurnByDelta(curdir, getRandom() % 8, dirmode_8dirs));
+ return;
+ }
+
+ if (_target == 0)
+ 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 && (getRandom() % 2 == 0)) || (getRandom() % 5 == 0)) {
+ // TODO: a->setField0x68(1);
+ _wpnField8 *= 4;
+ }
+ _fireTimestamp = now;
+ if (_timer4 == 0)
+ _timer4 = now;
+
+ 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, tpt;
+ a->getLocation(apt);
+ target->getLocation(tpt);
+ const int32 dist = apt.maxDistXYZ(tpt);
+ const int32 zdiff = abs(a->getZ() - target->getZ());
+ // FIXME: is this the right function??
+ const bool onscreen = a->isOnScreen();
+ if ((!_isActivity9orB && !onscreen) || (dist <= zdiff)) {
+ pathfindToItemInNPCData();
+ return;
+ }
+ if (targetdir == curdir) {
+ const uint16 rnd = randomOf(10);
+ if (!onscreen ||
+ (!_field96 && !timer4and5Update(now) && !World_FinishedAvatarMoveTimeout()
+ && rnd > 2 && (!_isActivityAorB || rnd > 3))) {
+ sleep(0x14);
+ return;
+ }
+
+ _field96 = false;
+ bool ready;
+ if (now - a->getLastTimeWasHit() < 120)
+ ready = checkReady(now, targetdir);
+ else
+ ready = true;
+
+ if (_timer2set && (randomOf(5) == 0 || checkTimer2PlusDelayElapsed(now))) {
+ _timer2set = false;
+ }
+
+ if (!ready) {
+ if (_isActivity9orB == 0)
+ pathfindToItemInNPCData();
+ else
+ sleep(0xf);
+ return;
+ }
+
+ checkRandomAttackSound(now, a->getShape());
+
+ if (!a->hasActorFlags(Actor::ACT_CRU5ABIT1)) {
+ _timer4 = now;
+ 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 = now - _timer5;
+ if (t5elapsed > _wpnBasedTimeout) {
+ const int32 fireelapsed = now - _fireTimestamp;
+ if (fireelapsed <= _difficultyBasedTimeout) {
+ sleep(_difficultyBasedTimeout - fireelapsed);
+ return;
+ }
+
+ if (wpn) {
+ _wpnField8 = wpnField8;
+ if (_wpnField8 > 2 && ((_doubleDelay && randomOf(2) == 0) || randomOf(5) == 0)) {
+ // TODO: a->setField0x68(1);
+ _wpnField8 *= 4;
+ }
+ }
+
+ _fireTimestamp = now;
+ if (_timer4 == 0) {
+ _timer4 = now;
+ }
+
+ 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 {
+ bool local_1b;
+ if (!timer4and5Update(now) && !_field7f) {
+ if (standDirMode != dirmode_16dirs) {
+ targetdir = a->getDirToItemCentre(*target);
+ }
+ local_1b = Intrinsic116(a, target, targetdir, 0, 0, 0);
+ if (local_1b)
+ timeNowToTimerVal2(now);
+ } else {
+ timeNowToTimerVal2(now);
+ local_1b = true;
+ _field7f = false;
+ }
+
+ // 5a flag 1 set?
+ if (!a->hasActorFlags(Actor::ACT_CRU5ABIT1) && local_1b) {
+ _timer4 = now;
+ a->doAnim(Animation::readyWeapon, dir_current); // ready SmallWpn
+ return;
+ }
+ if (local_1b || _isActivity9orB) {
+ a->turnTowardDir(targetdir);
+ return;
+ }
+ pathfindToItemInNPCData();
+ }
+ }
+}
+
+void AttackProcess::checkRandomAttackSound(int now, uint32 shapeno) {
+ AudioProcess *audio = AudioProcess::get_instance();
+ int16 attacksound = -1;
+ if (!_playedStartSound) {
+ _playedStartSound = true;
+ if (randomOf(3) == 0) {
+ switch(shapeno) {
+ case 0x371:
+ attacksound = RANDOM_ELEM(ATTACK_SFX_3);
+ break;
+ case 0x1b4:
+ attacksound = RANDOM_ELEM(ATTACK_SFX_5);
+ break;
+ case 0x2fd:
+ case 0x319:
+ attacksound = RANDOM_ELEM(ATTACK_SFX_1);
+ break;
+ case 900:
+ attacksound = RANDOM_ELEM(ATTACK_SFX_2);
+ break;
+ case 0x4d1:
+ case 0x528:
+ attacksound = RANDOM_ELEM(ATTACK_SFX_4);
+ break;
+ default:
+ break;
+ }
+ }
+ } else {
+ if (checkSoundTimeElapsed(now)) {
+ if (shapeno == 0x2df)
+ attacksound = RANDOM_ELEM(ATTACK_SFX_6);
+ else if (shapeno == 899)
+ attacksound = RANDOM_ELEM(ATTACK_SFX_7);
+ }
+ }
+
+ if (attacksound != -1) {
+ _soundNo = attacksound;
+ audio->playSFX(attacksound, 0x80, _itemNum, 1);
+ }
+}
+
+bool AttackProcess::checkSoundTimeElapsed(int now) {
+ if (_soundTimestamp == 0 || _soundTimestamp - now >= 480) {
+ _soundTimestamp = now;
+ return true;
+ }
+ return false;
+}
+
+bool AttackProcess::checkTimer2PlusDelayElapsed(int now) {
+ int delay = 60;
+ if (_doubleDelay)
+ delay *= 2;
+ return (now > _timer2 + delay);
+}
+
+void AttackProcess::setAttackDataArray(uint16 offset, uint16 val) {
+ if (offset >= MAGIC_DATA_OFFSET && offset < MAGIC_DATA_OFFSET + 10)
+ _dataArray[offset] = val;
+
+ warning("Invalid offset to setAttackDataArray %d %d", offset, val);
+}
+
+uint16 AttackProcess::getAttackDataArray(uint16 offset) const {
+ if (offset >= MAGIC_DATA_OFFSET && offset < MAGIC_DATA_OFFSET + 10)
+ return _dataArray[offset];
+
+ warning("Invalid offset to getAttackDataArray: %d", offset);
+ return 0;
+}
+
+void AttackProcess::pathfindToItemInNPCData() {
+ _doubleDelay = false;
+ _timer2set = false;
+ _field96 = true;
+
+ Actor *a = getActor(_itemNum);
+ Actor *target = getActor(_target);
+
+ Process *pathproc = new PathfinderProcess(a, target->getObjId());
+ // In case pathfinding fails just delay for a bit to ensure we don't get stuck in a notification loop.
+ Process *delayproc = new DelayProcess(2);
+ 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;
+ }
+
+ 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 Intrinsic116(a, target, targetdir, 0, 0, 0);
+}
+
+void AttackProcess::timeNowToTimerVal2(int now) {
+ _timer2 = now;
+ _timer2set = true;
+}
+
+void AttackProcess::setTimer3() {
+ const int32 now = Kernel::get_instance()->getFrameNum() * 2;
+ _timer3set = true;
+ _timer3 = randomOf(10) * 60 + now;
+ return;
+}
+
+void AttackProcess::sleep(int ticks) {
+ 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::readNextWordWithGlobals() {
+ uint16 data = _tacticDatReadStream->readUint16LE();
+ if (data >= MAGIC_DATA_OFFSET) {
+ data = getAttackDataArray(data);
+ }
+
+ return data;
+}
+
+uint16 AttackProcess::readNextWord() {
+ assert(_tacticDatReadStream);
+ return _tacticDatReadStream->readUint16LE();
+}
+
+void AttackProcess::dumpInfo() const {
+ 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 < 10; 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);
+ 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());
+
+ _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 < 10; 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
diff --git a/engines/ultima/ultima8/world/actors/attack_process.h b/engines/ultima/ultima8/world/actors/attack_process.h
new file mode 100644
index 0000000000..39efa7aab3
--- /dev/null
+++ b/engines/ultima/ultima8/world/actors/attack_process.h
@@ -0,0 +1,135 @@
+/* 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 2
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef WORLD_ACTORS_ATTACKPROCESS_H
+#define WORLD_ACTORS_ATTACKPROCESS_H
+
+#include "ultima/ultima8/kernel/process.h"
+#include "ultima/ultima8/misc/direction.h"
+
+#include "common/memstream.h"
+
+namespace Ultima {
+namespace Ultima8 {
+
+class Actor;
+class CombatDat;
+
+class AttackProcess : public Process {
+public:
+ AttackProcess();
+ AttackProcess(Actor *actor);
+
+ virtual ~AttackProcess();
+
+ // p_dynamic_cast stuff
+ ENABLE_RUNTIME_CLASSTYPE()
+
+ void run() override;
+
+ void terminate() override;
+
+ void dumpInfo() const override;
+
+ void setIsActivityAOrB() {
+ _isActivityAorB = true;
+ }
+ void setIsActivity9OrB() {
+ _isActivity9orB = true;
+ }
+ void setField97() {
+ _field97 = true;
+ }
+
+ void setTimer3();
+
+ bool loadData(Common::ReadStream *rs, uint32 version);
+ void saveData(Common::WriteStream *ws) override;
+
+private:
+ void setBlockNo(int block);
+ void setTacticNo(int block);
+
+ uint16 readNextWordWithGlobals();
+ uint16 readNextWord();
+
+ void setAttackDataArray(uint16 offset, uint16 val);
+ uint16 getAttackDataArray(uint16 offset) const;
+
+ // This is the equivalent of run() when a tactic hasn't been selected yet.
+ void genericAttack();
+
+ void sleep(int ticks);
+ bool checkSoundTimeElapsed(int now);
+ bool checkTimer2PlusDelayElapsed(int now);
+ void pathfindToItemInNPCData();
+ bool timer4and5Update(int now);
+ void timeNowToTimerVal2(int now);
+ bool checkReady(int now, Direction targetdir);
+ void checkRandomAttackSound(int now, uint32 shapeno);
+
+ uint16 _target; // TODO: this is stored in NPC in game, does it matter?
+ uint16 _tactic;
+ uint16 _block;
+ uint16 _tacticDatStartOffset;
+ const CombatDat *_tacticDat;
+ Common::MemoryReadStream *_tacticDatReadStream;
+
+ int16 _soundNo;
+ bool _playedStartSound;
+
+ Direction _npcInitialDir;
+
+ // Unknown fields..
+ int16 _field57;
+ uint16 _field59;
+ //uint16 _field53; // Never really used?
+ bool _field7f;
+ bool _field96;
+ bool _field97;
+
+ bool _isActivity9orB;
+ bool _isActivityAorB;
+ bool _timer2set;
+ bool _timer3set;
+ bool _doubleDelay;
+
+ uint16 _wpnField8;
+
+ uint16 _dataArray[10];
+
+ int32 _wpnBasedTimeout;
+ int32 _difficultyBasedTimeout;
+
+ int32 _timer2; // 0x73 / 0x75 in orig
+ int32 _timer3; // 0x77 / 0x79 in orig
+ int32 _timer4; // 0x6f / 0x71 in orig
+ int32 _timer5; // 0x8a / 0x8c in oric
+ int32 _soundTimestamp; // 0x84 / 0x86 in orig
+ int32 _fireTimestamp; // 0x90 / 0x92 in orig
+
+};
+
+} // End of namespace Ultima8
+} // End of namespace Ultima
+
+#endif
diff --git a/engines/ultima/ultima8/world/actors/avatar_mover_process.cpp b/engines/ultima/ultima8/world/actors/avatar_mover_process.cpp
index 835f955bad..dd0c3cabb2 100644
--- a/engines/ultima/ultima8/world/actors/avatar_mover_process.cpp
+++ b/engines/ultima/ultima8/world/actors/avatar_mover_process.cpp
@@ -901,7 +901,7 @@ void AvatarMoverProcess::tryAttack() {
MainActor *avatar = getMainActor();
Direction dir = avatar->getDir();
if (!avatar->isInCombat()) {
- avatar->setInCombat();
+ avatar->setInCombat(0);
waitFor(avatar->doAnim(Animation::readyWeapon, dir));
} else {
if (canAttack()) {
diff --git a/engines/ultima/ultima8/world/actors/combat_dat.cpp b/engines/ultima/ultima8/world/actors/combat_dat.cpp
index 6561892de2..f375e775f3 100644
--- a/engines/ultima/ultima8/world/actors/combat_dat.cpp
+++ b/engines/ultima/ultima8/world/actors/combat_dat.cpp
@@ -31,24 +31,18 @@ CombatDat::CombatDat(Common::SeekableReadStream &rs) {
rs.read(namebuf, 16);
_name.assign(namebuf);
- uint16 offset1 = rs.readUint16LE();
- uint16 offset2 = rs.readUint16LE();
+ for (int i = 0; i < 4; i++)
+ _offsets[i] = rs.readUint16LE();
- int data1size = offset2 - offset1;
- int data2size = rs.size() - offset2;
- _sequence1 = new uint8[data1size];
- _sequence2 = new uint8[data2size];
+ int datasize = rs.size();
+ rs.seek(0, SEEK_SET);
+ _data = new uint8[datasize];
- rs.seek(offset1);
- _sequence1len = rs.read(_sequence1, data1size);
-
- rs.seek(offset2);
- _sequence2len = rs.read(_sequence2, data2size);
+ _dataLen = rs.read(_data, datasize);
}
CombatDat::~CombatDat() {
- delete [] _sequence1;
- delete [] _sequence2;
+ delete [] _data;
}
} // End of namespace Ultima8
diff --git a/engines/ultima/ultima8/world/actors/combat_dat.h b/engines/ultima/ultima8/world/actors/combat_dat.h
index 0636202e43..51363852a2 100644
--- a/engines/ultima/ultima8/world/actors/combat_dat.h
+++ b/engines/ultima/ultima8/world/actors/combat_dat.h
@@ -49,29 +49,25 @@ public:
return _name;
};
- const uint8 *getSequence1() const {
- return _sequence1;
+ const uint8 *getData() const {
+ return _data;
}
- const uint8 *getSequence2() const {
- return _sequence2;
+ uint16 getOffset(int block) const {
+ assert(block < ARRAYSIZE(_offsets));
+ return _offsets[block];
}
- uint16 getSequence1Len() const {
- return _sequence1len;
- }
-
- uint16 getSequence2Len() const {
- return _sequence2len;
+ uint16 getDataLen() const {
+ return _dataLen;
}
private:
Std::string _name;
- uint16 _sequence1len;
- uint16 _sequence2len;
- uint8 *_sequence1;
- uint8 *_sequence2;
+ uint16 _offsets[4];
+ uint8 *_data;
+ uint16 _dataLen;
};
} // End of namespace Ultima8
diff --git a/engines/ultima/ultima8/world/actors/main_actor.cpp b/engines/ultima/ultima8/world/actors/main_actor.cpp
index af012edb2f..95201d8fbd 100644
--- a/engines/ultima/ultima8/world/actors/main_actor.cpp
+++ b/engines/ultima/ultima8/world/actors/main_actor.cpp
@@ -478,7 +478,7 @@ int MainActor::getDamageAmount() const {
return damage;
}
-void MainActor::setInCombat() {
+void MainActor::setInCombat(int activity) {
setActorFlag(ACT_INCOMBAT);
if (GAME_IS_U8)
MusicProcess::get_instance()->playCombatMusic(98); // CONSTANT!
@@ -760,7 +760,8 @@ uint32 MainActor::I_clrAvatarInCombat(const uint8 * /*args*/,
uint32 MainActor::I_setAvatarInCombat(const uint8 * /*args*/,
unsigned int /*argsize*/) {
MainActor *av = getMainActor();
- av->setInCombat();
+ // Note: only happens in U8, so activity num is not important.
+ av->setInCombat(0);
return 0;
}
@@ -847,6 +848,9 @@ void MainActor::useInventoryItem(Item *item) {
}
item->callUsecodeEvent_use();
+ // 0x4d4 = datalink, 0x52d = scanner, 0x52e = ionic,
+ // 0x52f = plasma, 0x530 = graviton
+ // TODO: check these for no regret
if (GAME_IS_CRUSADER && (shapenum != 0x4d4 && shapenum != 0x52d &&
shapenum != 0x530 && shapenum != 0x52f &&
shapenum != 0x52e)) {
diff --git a/engines/ultima/ultima8/world/actors/main_actor.h b/engines/ultima/ultima8/world/actors/main_actor.h
index 85d63a95b3..31866f684e 100644
--- a/engines/ultima/ultima8/world/actors/main_actor.h
+++ b/engines/ultima/ultima8/world/actors/main_actor.h
@@ -89,7 +89,15 @@ public:
uint16 getDamageType() const override;
int getDamageAmount() const override;
- void setInCombat() override;
+ void toggleInCombat() {
+ if (isInCombat())
+ clearInCombat();
+ else
+ setInCombat(0);
+ }
+
+ // Note: activity num parameter is ignored for Avatar.
+ void setInCombat(int activity) override;
void clearInCombat() override;
ProcId die(uint16 DamageType) override;
diff --git a/engines/ultima/ultima8/world/actors/resurrection_process.cpp b/engines/ultima/ultima8/world/actors/resurrection_process.cpp
index 0df88c95ba..30a0de2e99 100644
--- a/engines/ultima/ultima8/world/actors/resurrection_process.cpp
+++ b/engines/ultima/ultima8/world/actors/resurrection_process.cpp
@@ -72,7 +72,8 @@ void ResurrectionProcess::run() {
}
// go into combat mode
- a->setInCombat();
+ // Note: only happens in U8, so activity num is not important.
+ a->setInCombat(0);
// we should already be killed by going into combat mode.
if (!(_flags & PROC_TERMINATED))
diff --git a/engines/ultima/ultima8/world/super_sprite_process.cpp b/engines/ultima/ultima8/world/super_sprite_process.cpp
index 3b0d3bfe3b..cfeeefb7ce 100644
--- a/engines/ultima/ultima8/world/super_sprite_process.cpp
+++ b/engines/ultima/ultima8/world/super_sprite_process.cpp
@@ -86,6 +86,7 @@ SuperSpriteProcess::SuperSpriteProcess(int shape, int frame, int sx, int sy, int
rng /= 2;
} else {
// TODO: various other flags are checked in the game (around 1138:0bd1)
+ // such as World_FinishedAvatarMoveTimeout() -> 8
// to make it either 5 or 8. For now just use 5.
rng /= 5;
}
diff --git a/engines/ultima/ultima8/world/weapon_info.h b/engines/ultima/ultima8/world/weapon_info.h
index 6e544272f9..a69220358d 100644
--- a/engines/ultima/ultima8/world/weapon_info.h
+++ b/engines/ultima/ultima8/world/weapon_info.h
@@ -48,6 +48,7 @@ struct WeaponInfo {
uint16 _displayGumpShape; //! The gump shape to use for inventory display (3,4,5)
uint16 _displayGumpFrame; //!< The frame to use in the inventory gump
uint8 _small; //! A flag whether or not the weapon is "small" (changes the animations used)
+ uint8 _field8; //! Not totally sure, used like "cycle time" in Attack Process
enum DmgType {
DMG_NORMAL = 0x0001,
More information about the Scummvm-git-logs
mailing list