Initial commit

This commit is contained in:
2026-02-02 04:50:13 +01:00
commit 5b11698731
22592 changed files with 7677434 additions and 0 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,495 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef WORLD_ACTORS_ACTOR_H
#define WORLD_ACTORS_ACTOR_H
#include "ultima/ultima8/world/container.h"
#include "ultima/ultima8/usecode/intrinsics.h"
#include "ultima/ultima8/world/actors/animation.h"
namespace Ultima {
namespace Ultima8 {
class ActorAnimProcess;
struct PathfindingState;
class CombatProcess;
class AttackProcess;
class Actor : public Container {
friend class ActorAnimProcess;
friend class AnimationTracker;
public:
Actor();
~Actor() override;
int16 getStr() const {
return _strength;
}
void setStr(int16 str) {
_strength = str;
}
int16 getDex() const {
return _dexterity;
}
void setDex(int16 dex) {
_dexterity = dex;
}
int16 getInt() const {
return _intelligence;
}
void setInt(int16 intl) {
_intelligence = intl;
}
uint16 getHP() const {
return _hitPoints;
}
void setHP(uint16 hp) {
_hitPoints = hp;
}
int16 getMana() const {
return _mana;
}
void setMana(int16 mp) {
_mana = mp;
}
int16 getMaxMana() const;
uint16 getMaxHP() const;
bool isDead() const {
return (_actorFlags & ACT_DEAD) != 0;
}
bool isInCombat() const {
return (_actorFlags & ACT_INCOMBAT) != 0;
}
bool isKneeling() const {
return (_actorFlags & ACT_KNEELING) != 0;
}
bool isFalling() const;
CombatProcess *getCombatProcess() const; // in U8
AttackProcess *getAttackProcess() const; // in Crusader
virtual void setInCombat(int activity);
virtual void clearInCombat();
uint16 getAlignment() const {
return _alignment;
}
void setAlignment(uint16 a) {
_alignment = a;
}
uint16 getEnemyAlignment() const {
return _enemyAlignment;
}
void setEnemyAlignment(uint16 a) {
_enemyAlignment = a;
}
Animation::Sequence getLastAnim() const {
return _lastAnim;
}
void setLastAnim(Animation::Sequence anim) {
_lastAnim = anim;
}
Direction getDir() const {
return _direction;
}
void setDir(Direction dir) {
_direction = dir;
}
int32 getFallStart() const {
return _fallStart;
}
void setFallStart(int32 zp) {
_fallStart = zp;
}
void setUnkByte(uint8 b) {
_unkByte = b;
}
uint8 getUnkByte() const {
return _unkByte;
}
bool hasActorFlags(uint32 flags) const {
return (_actorFlags & flags) != 0;
}
void setActorFlag(uint32 mask) {
_actorFlags |= mask;
if (mask & ACT_KNEELING)
_cachedShapeInfo = nullptr;
}
void clearActorFlag(uint32 mask) {
_actorFlags &= ~mask;
if (mask & ACT_KNEELING)
_cachedShapeInfo = nullptr;
}
void setCombatTactic(int no) {
_combatTactic = no;
}
//! set stats from MonsterInfo (hp, dex, alignment, enemyAlignment)
//! in Crusader this comes from the NPC Data
//! \return true if info was found, false otherwise
bool loadMonsterStats();
//! add treasure according to the TreasureInfo in the MonsterInfo
//! \return true if a MonsterInfo struct was found, false otherwise
bool giveTreasure();
virtual void teleport(int mapnum, int32 x, int32 y, int32 z);
bool removeItem(Item *item) override;
//! \return the PID of the spawned usecode process if any (otherwise 0)
uint16 schedule(uint32 time);
bool setEquip(Item *item, bool checkwghtvol = false);
uint16 getEquip(uint32 type) const;
virtual uint32 getArmourClass() const;
virtual uint16 getDefenseType() const;
virtual int16 getAttackingDex() const;
virtual int16 getDefendingDex() const;
uint16 getDamageType() const override;
virtual int getDamageAmount() const;
void setDefaultActivity(int no, uint16 activity);
uint16 getDefaultActivity(int no) const;
void setHomePosition(int32 x, int32 y, int32 z);
void getHomePosition(int32 &x, int32 &y, int32 &z) const;
//! calculate the damage an attack against this Actor does.
//! \param other the attacker (can be zero)
//! \param damage base damage
//! \param type damage type
//! \return the amount of damage to be applied. Zero if attack missed.
int calculateAttackDamage(uint16 other, int damage, uint16 type);
//! receive a hit
//! \param damage base damage (or zero to use attacker's default damage)
//! \param type damage type (or zero to use attacker's default type)
void receiveHit(uint16 other, Direction dir, int damage, uint16 type) override;
//! die
//! \param damageType damage type that caused the death
//! \param damagPts damage points that caused the death
//! \param srcDir direction damage came from
//! \return the process ID of the death animation
virtual ProcId die(uint16 damageType, uint16 damagePts, Direction srcDir);
//! kill all processes except those related to combat
void killAllButCombatProcesses();
//! kill all animation processes except those related to dying/falling
//! \return PID of animprocess doing the falling (or getting up)
ProcId killAllButFallAnims(bool death);
//! check if NPCs are near which are in combat mode and hostile
bool areEnemiesNear();
//! starts an activity
//! \return processID of process handling the activity or zero
uint16 setActivity(int activity);
uint16 getCurrentActivityNo() const {
return _currentActivityNo;
}
uint16 getLastActivityNo() const {
return _lastActivityNo;
}
void clearLastActivityNo() {
_lastActivityNo = 0;
}
int32 getLastTickWasHit() const {
return _lastTickWasHit;
}
//! run the given animation
//! \return the PID of the ActorAnimProcess
uint16 doAnim(Animation::Sequence anim, Direction dir, unsigned int steps = 0);
//! run the given anim after the other animation (waitfor).
//! Safe for either anim to be 0.
//! \return the new anim pid, or 0 if failed
uint16 doAnimAfter(Animation::Sequence anim, Direction dir, ProcId waitfor);
//! check if this actor has a specific animation
bool hasAnim(Animation::Sequence anim);
//! Set the frame to the first frame of an anim (used in resetting NPCs etc)
//! Uses current direction and sets last anim no.
void setToStartOfAnim(Animation::Sequence anim);
//! check if the given animation can be done from the location in state,
//! without walking into things. If state is non-zero, and successful,
//! state will be updated to after the animation. If unsuccessful,
//! the contents of state are undefined.
//! \param anim Action to try
//! \param dir direction to walk in
//! \param state the state to start from, or 0 to use the current state
Animation::Result tryAnim(Animation::Sequence anim, Direction dir, unsigned int steps = 0, PathfindingState *state = 0);
//! Get the number of directions supported by a given animation
DirectionMode animDirMode(Animation::Sequence anim) const;
//! True if the actor is currently doing an animation.
bool isBusy() const;
//! overrides the standard item collideMove so we can notify nearby objects.
int32 collideMove(int32 x, int32 y, int32 z, bool teleport, bool force,
ObjId *hititem = 0, uint8 *dirs = 0) override;
//! Turn one step toward the given direction. If the current direction is already the same,
//! do nothing. Returns an anim process or 0 if no move needed.
//! If a previous pid is specified, wait for that process.
uint16 turnTowardDir(Direction dir, ProcId prevpid = 0);
//! create an actor, assign objid, make it ethereal and load monster stats.
static Actor *createActor(uint32 shape, uint32 frame);
uint16 assignObjId() override; // assign an NPC objid
Common::String dumpInfo() const override;
bool loadData(Common::ReadStream *rs, uint32 version);
void saveData(Common::WriteStream *ws) override;
//! take a hit and optionally adjust it with the shields for this NPC.
virtual int receiveShieldHit(int damage, uint16 damage_type) {
return damage;
}
uint16 getActiveWeapon() const {
return _activeWeapon;
}
uint16 getCombatTactic() const {
return _combatTactic;
}
bool activeWeaponIsSmall() const;
//! A cru-specific behavior - mostly make "ugh" noises, or explode for some robots.
void tookHitCru();
//! Whether this NPC has the controlled actor in their sights (Crusader only)
bool canSeeControlledActor(bool forcombat);
//! Add the x/y/z fire offsets given the current state of the actor
void addFireAnimOffsets(int32 &x, int32 &y, int32 &z);
uint32 getAttackMoveTimeoutFinishFrame() const {
return _attackMoveStartFrame + _attackMoveTimeout;
}
uint16 getAttackMoveDodgeFactor() const {
return _attackMoveDodgeFactor;
}
bool getAttackAimFlag() const {
return _attackAimFlag;
}
void setAttackAimFlag(bool val) {
_attackAimFlag = val;
}
ENABLE_RUNTIME_CLASSTYPE()
INTRINSIC(I_isNPC);
INTRINSIC(I_getDir);
INTRINSIC(I_getLastAnimSet);
INTRINSIC(I_pathfindToItem);
INTRINSIC(I_pathfindToPoint);
INTRINSIC(I_getStr);
INTRINSIC(I_getDex);
INTRINSIC(I_getInt);
INTRINSIC(I_getHp);
INTRINSIC(I_getMaxHp);
INTRINSIC(I_getMana);
INTRINSIC(I_getAlignment);
INTRINSIC(I_getEnemyAlignment);
INTRINSIC(I_setStr);
INTRINSIC(I_setDex);
INTRINSIC(I_setInt);
INTRINSIC(I_setHp);
INTRINSIC(I_setMana);
INTRINSIC(I_setAlignment);
INTRINSIC(I_setEnemyAlignment);
INTRINSIC(I_getMap);
INTRINSIC(I_addHp);
INTRINSIC(I_teleport);
INTRINSIC(I_doAnim);
INTRINSIC(I_isInCombat);
INTRINSIC(I_setInCombat);
INTRINSIC(I_clrInCombat);
INTRINSIC(I_setTarget);
INTRINSIC(I_getTarget);
INTRINSIC(I_isEnemy);
INTRINSIC(I_isDead);
INTRINSIC(I_setDead);
INTRINSIC(I_clrDead);
INTRINSIC(I_isImmortal);
INTRINSIC(I_setImmortal);
INTRINSIC(I_clrImmortal);
INTRINSIC(I_isWithstandDeath);
INTRINSIC(I_setWithstandDeath);
INTRINSIC(I_clrWithstandDeath);
INTRINSIC(I_isFeignDeath);
INTRINSIC(I_setFeignDeath);
INTRINSIC(I_clrFeignDeath);
INTRINSIC(I_areEnemiesNear);
INTRINSIC(I_isBusy);
INTRINSIC(I_createActor);
INTRINSIC(I_createActorCru);
INTRINSIC(I_setActivity);
INTRINSIC(I_setAirWalkEnabled);
INTRINSIC(I_getAirWalkEnabled);
INTRINSIC(I_schedule);
INTRINSIC(I_getEquip);
INTRINSIC(I_setEquip);
INTRINSIC(I_setDefaultActivity0);
INTRINSIC(I_setDefaultActivity1);
INTRINSIC(I_setDefaultActivity2);
INTRINSIC(I_getDefaultActivity0);
INTRINSIC(I_getDefaultActivity1);
INTRINSIC(I_getDefaultActivity2);
INTRINSIC(I_setCombatTactic);
INTRINSIC(I_setUnkByte);
INTRINSIC(I_getUnkByte);
INTRINSIC(I_getLastActivityNo);
INTRINSIC(I_getCurrentActivityNo);
INTRINSIC(I_turnToward);
INTRINSIC(I_isKneeling);
INTRINSIC(I_isFalling);
enum ActorFlags {
ACT_INVINCIBLE = 0x000001, // flags from npcdata byte 0x1B
ACT_ASCENDING = 0x000002,
ACT_DESCENDING = 0x000004,
ACT_ANIMLOCK = 0x000008,
ACT_KNEELING = 0x000100, // not the same bit used in Crusader, but use this because it's empty.
ACT_FIRSTSTEP = 0x000400, // flags from npcdata byte 0x2F
ACT_INCOMBAT = 0x000800,
ACT_DEAD = 0x001000,
ACT_SURRENDERED = 0x002000, // not the same bit used in Crusader, but use this because it's empty.
ACT_WEAPONREADY = 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
ACT_IMMORTAL = 0x040000,
ACT_WITHSTANDDEATH = 0x080000,
ACT_FEIGNDEATH = 0x100000,
ACT_STUNNED = 0x200000,
ACT_POISONED = 0x400000,
ACT_PATHFINDING = 0x800000
};
protected:
int16 _strength;
int16 _dexterity;
int16 _intelligence;
uint16 _hitPoints;
int16 _mana;
uint16 _alignment, _enemyAlignment;
Animation::Sequence _lastAnim;
uint16 _animFrame;
Direction _direction;
int32 _fallStart;
//! Unknown byte 0x0C from npcdata.dat in U8, or
//! Unknown byte 0x99 from NPC struct in Crusader.
uint8 _unkByte;
//! tactic being used in combat (for Crusader), the entry in the combat.dat flex.
uint16 _combatTactic;
uint32 _actorFlags;
//! the 3 default NPC activities from Crusader
uint16 _defaultActivity[3];
//! The "home" position used in some Crusader attack tactics
int32 _homeX;
int32 _homeY;
int32 _homeZ;
//! Current and last activity (only used in Crusader)
uint16 _currentActivityNo;
uint16 _lastActivityNo;
//! Active weapon item (only used in Crusader)
uint16 _activeWeapon;
//! Kernel timer last time NPC was hit (only used in Crusader)
int32 _lastTickWasHit;
//! The frame certain animations last happened (for Crusader).
//! Used in calcualting how hard controlled actor is to hit.
uint32 _attackMoveStartFrame;
//! The number of frames the above effect lasts for.
uint32 _attackMoveTimeout;
//! A spread divisor used by shots targeting the controlled actor when they
//! are within the above timeout.
uint16 _attackMoveDodgeFactor;
//! A flag used in Crusader attack process which adjusts the aim accuracy.
bool _attackAimFlag;
//! starts an activity (Ultima 8 version)
//! \return processID of process handling the activity or zero
uint16 setActivityU8(int activity);
//! starts an activity (Crusader version)
//! \return processID of process handling the activity or zero
uint16 setActivityCru(int activity);
bool loadMonsterStatsU8();
bool loadMonsterStatsCru();
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);
ProcId dieU8(uint16 damageType);
ProcId dieCru(uint16 damageType, uint16 damagePts, Direction srcDir);
};
} // End of namespace Ultima8
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,54 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef WORLD_ACTORS_ACTORANIM_H
#define WORLD_ACTORS_ACTORANIM_H
#include "ultima/shared/std/containers.h"
#include "ultima/ultima8/world/actors/anim_action.h"
namespace Ultima {
namespace Ultima8 {
class ActorAnim {
friend class AnimDat;
public:
ActorAnim() {}
~ActorAnim() {
for (unsigned int i = 0; i < _actions.size(); ++i)
delete _actions[i];
}
const AnimAction *getAction(unsigned int n) const {
if (n >= _actions.size())
return nullptr;
return _actions[n];
}
private:
Std::vector<AnimAction *> _actions; // list of this actor's actions
// (0 if actor doesn't have action)
};
} // End of namespace Ultima8
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,730 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "common/config-manager.h"
#include "ultima/ultima8/world/actors/actor_anim_process.h"
#include "ultima/ultima8/world/actors/anim_action.h"
#include "ultima/ultima8/world/actors/main_actor.h"
#include "ultima/ultima8/misc/direction_util.h"
#include "ultima/ultima8/world/world.h"
#include "ultima/ultima8/kernel/kernel.h"
#include "ultima/ultima8/usecode/uc_list.h"
#include "ultima/ultima8/world/loop_script.h"
#include "ultima/ultima8/world/current_map.h"
#include "ultima/ultima8/world/actors/animation_tracker.h"
#include "ultima/ultima8/audio/audio_process.h"
#include "ultima/ultima8/world/actors/combat_process.h"
#include "ultima/ultima8/world/actors/auto_firer_process.h"
#include "ultima/ultima8/world/sprite_process.h"
#include "ultima/ultima8/gfx/palette_fader_process.h"
#include "ultima/ultima8/world/create_item_process.h"
#include "ultima/ultima8/world/destroy_item_process.h"
#include "ultima/ultima8/kernel/delay_process.h"
#include "ultima/ultima8/world/get_object.h"
namespace Ultima {
namespace Ultima8 {
//#define WATCHACTOR 1
#ifdef WATCHACTOR
static const int watchactor = WATCHACTOR;
#endif
DEFINE_RUNTIME_CLASSTYPE_CODE(ActorAnimProcess)
ActorAnimProcess::ActorAnimProcess() : Process(), _tracker(nullptr),
_dir(dir_north), _action(Animation::walk), _steps(0), _firstFrame(true),
_currentStep(0), _repeatCounter(0), _animAborted(false),
_attackedSomething(false), _interpolate(false) {
}
ActorAnimProcess::ActorAnimProcess(Actor *actor, Animation::Sequence action,
Direction dir, uint32 steps) :
_dir(dir), _action(action), _steps(steps), _tracker(nullptr),
_firstFrame(true), _currentStep(0), _repeatCounter(0),
_animAborted(false), _attackedSomething(false), _interpolate(false) {
assert(actor);
_itemNum = actor->getObjId();
_type = ACTOR_ANIM_PROC_TYPE;
#ifdef WATCHACTOR
if (_itemNum == watchactor)
debugC(kDebugActor, "Animation [%u] ActorAnimProcess created (%u, %d, %d) steps %u",
Kernel::get_instance()->getFrameNum(), _itemNum, _action, _dir, _steps);
#endif
}
bool ActorAnimProcess::init() {
_repeatCounter = 0;
_animAborted = false;
_attackedSomething = false;
_interpolate = Ultima8Engine::get_instance()->isInterpolationEnabled();
Actor *actor = getActor(_itemNum);
assert(actor);
if (_dir == dir_current)
_dir = actor->getDir();
if (!actor->hasFlags(Item::FLG_FASTAREA)) {
// not in the fast area? Can't play an animation then.
// (If we do, the actor will likely fall because the floor is gone.)
#ifdef WATCHACTOR
if (_itemNum == watchactor)
debugC(kDebugActor, "Animation [%u] ActorAnimProcess %u init failed (actor %u not fast)",
Kernel::get_instance()->getFrameNum(), getPid(), _itemNum);
#endif
return false;
}
if (actor->hasActorFlags(Actor::ACT_ANIMLOCK)) {
//! What do we do if actor was already animating?
//! don't do this animation or kill the previous one?
//! Or maybe wait until the previous one finishes?
warning("ActorAnimProcess [%u]: ANIMLOCK set on actor %u, skipping anim (%d, %d)",
getPid(), _itemNum, _action, _dir);
// for now, just don't play this one.
return false;
}
_tracker = new AnimationTracker();
if (!_tracker->init(actor, _action, _dir)) {
delete _tracker;
_tracker = nullptr;
#ifdef WATCHACTOR
if (_itemNum == watchactor)
debugC(kDebugActor, "Animation [%u] ActorAnimProcess %u init failed (tracker not fast)",
Kernel::get_instance()->getFrameNum(), getPid());
#endif
return false;
}
actor->setActorFlag(Actor::ACT_ANIMLOCK);
actor->_lastAnim = _action;
actor->_direction = _dir;
#ifdef WATCHACTOR
if (_itemNum == watchactor)
debugC(kDebugActor, "Animation [%u] ActorAnimProcess %u initalized (%u, %d, %d) steps %d",
Kernel::get_instance()->getFrameNum(), getPid(), _itemNum, _action, _dir, _steps);
#endif
return true;
}
void ActorAnimProcess::run() {
if (_firstFrame) {
bool ret = init();
if (!ret) {
// initialization failed
terminateDeferred();
return;
}
}
if (_animAborted) {
terminate();
return;
}
assert(_tracker);
if (!_firstFrame)
_repeatCounter++;
if (_repeatCounter > _tracker->getAnimAction()->getFrameRepeat())
_repeatCounter = 0;
Actor *a = getActor(_itemNum);
if (!a) {
// actor gone
terminate();
return;
}
_firstFrame = false;
if (!a->hasFlags(Item::FLG_FASTAREA)) {
// not in the fast area? Kill the animation then.
//! TODO: Decide if this is the right move.
// Animation could do one of three things: pause, move
// without allowing actor to fall, or pretend to move and
// complete the entire movement as the actor reappears
// in fast area (still may need to pause when
// AnimationTracker is done.)
#ifdef WATCHACTOR
if (_itemNum == watchactor)
debugC(kDebugActor, "Animation [%u] ActorAnimProcess left fastarea; terminating",
Kernel::get_instance()->getFrameNum());
#endif
terminate();
return;
}
bool resultVal = true;
if (_repeatCounter == 0) {
// next step:
Point3 pt = a->getLocation();
resultVal = _tracker->stepFrom(pt);
_tracker->updateActorFlags();
_currentStep++;
if (!resultVal) {
// check possible error conditions
if (_tracker->isDone() || (_steps && _currentStep >= _steps)) {
// all done
#ifdef WATCHACTOR
if (_itemNum == watchactor)
debugC(kDebugActor, "Animation [%u] ActorAnimProcess done; terminating",
Kernel::get_instance()->getFrameNum());
#endif
// TODO: there are _three_ places where we can fall; clean up
if (_tracker->isUnsupported() && pt.z > 0) {
#ifdef WATCHACTOR
if (_itemNum == watchactor) {
debugC(kDebugActor, "Animation [%u] ActorAnimProcess falling at end",
Kernel::get_instance()->getFrameNum());
}
#endif
int32 dx, dy, dz;
_tracker->getSpeed(dx, dy, dz);
a->hurl(dx, dy, dz, 2);
}
terminate();
return;
}
if (_tracker->isBlocked() &&
!_tracker->getAnimAction()->hasFlags(AnimAction::AAF_UNSTOPPABLE)) {
// FIXME: For blocked large steps we may still want to do
// a partial move. (But how would that work with
// repeated frames?)
#ifdef WATCHACTOR
if (_itemNum == watchactor)
debugC(kDebugActor, "Animation [%u] ActorAnimProcess blocked; terminating",
Kernel::get_instance()->getFrameNum());
#endif
if (_tracker->isUnsupported() && pt.z > 0) {
#ifdef WATCHACTOR
if (_itemNum == watchactor) {
debugC(kDebugActor, "Animation [%u] ActorAnimProcess falling from blocked",
Kernel::get_instance()->getFrameNum());
}
#endif
// no inertia here because we just crashed into something
a->fall();
}
terminate();
return;
}
}
const AnimFrame *curframe = _tracker->getAnimFrame();
if (curframe) {
if (curframe->_sfx) {
AudioProcess *audioproc = AudioProcess::get_instance();
if (audioproc) audioproc->playSFX(curframe->_sfx, 0x60, _itemNum, 0);
}
if (curframe->_flags & AnimFrame::AFF_SPECIAL) {
// Flag to trigger a special action
// E.g.: play draw/sheathe SFX for avatar when weapon equipped,
// throw skull-fireball when ghost attacks, ...
doSpecial();
} else if (curframe->_flags & AnimFrame::AFF_HURTY && GAME_IS_CRUSADER) {
a->tookHitCru();
} else if (curframe->is_cruattack() && GAME_IS_CRUSADER) {
doFireWeaponCru(a, curframe);
}
}
// attacking?
if (!_attackedSomething) {
ObjId hit = _tracker->hitSomething();
if (hit) {
_attackedSomething = true;
Item *hit_item = getItem(hit);
assert(hit_item);
hit_item->receiveHit(_itemNum, Direction_Invert(_dir), 0, 0);
doHitSpecial(hit_item);
}
}
}
Point3 pt = a->getLocation();
Point3 pt2;
if (_interpolate) {
// Apply interpolated position on repeated frames
pt2 = _tracker->getInterpolatedPosition(_repeatCounter);
if (pt == pt2) {
pt = _tracker->getInterpolatedPosition(_repeatCounter + 1);
a->collideMove(pt.x, pt.y, pt.z, false, true); // forced move
a->setFrame(_tracker->getFrame());
#ifdef WATCHACTOR
} else {
if (_itemNum == watchactor) {
debugC(kDebugActor, "Animation [%u] ActorAnimProcess moved, so aborting this frame.",
Kernel::get_instance()->getFrameNum());
}
#endif // WATCHACTOR
}
} else {
// Just move the whole distance on frame 0 of the repeat.
if (_repeatCounter == 0) {
pt2 = _tracker->getPosition();
a->collideMove(pt2.x, pt2.y, pt2.z, false, true); // forced move
a->setFrame(_tracker->getFrame());
} else {
pt2 = pt;
}
}
// Did we just leave the fast area?
if (!a->hasFlags(Item::FLG_FASTAREA)) {
#ifdef WATCHACTOR
if (_itemNum == watchactor)
debugC(kDebugActor, "Animation [%u] ActorAnimProcess left fastarea; terminating",
Kernel::get_instance()->getFrameNum());
#endif
terminate();
return;
}
#ifdef WATCHACTOR
if (_itemNum == watchactor) {
Common::String info;
if (_tracker->isDone())
info += "D";
if (_tracker->isBlocked())
info += "B";
if (_tracker->isUnsupported())
info += "U";
if (_tracker->hitSomething())
info += "H";
debugC(kDebugActor, "Animation [%u] ActorAnimProcess showing frame (%d, %d, %d)-(%d, %d, %d) shp (%u, %u) sfx %d rep %d flg %04X %s",
Kernel::get_instance()->getFrameNum(), pt.x, pt.y, pt.z, pt2.x, pt2.y, pt2.z,
a->getShape(), _tracker->getFrame(), _tracker->getAnimFrame()->_sfx,
_repeatCounter, _tracker->getAnimFrame()->_flags, info.c_str());
}
#endif
if (_repeatCounter == _tracker->getAnimAction()->getFrameRepeat()) {
if (_tracker->isUnsupported() && pt.z > 0) {
_animAborted = !_tracker->getAnimAction()->hasFlags(AnimAction::AAF_UNSTOPPABLE);
#ifdef WATCHACTOR
if (_itemNum == watchactor) {
debugC(kDebugActor, "Animation [%u] ActorAnimProcess falling from repeat",
Kernel::get_instance()->getFrameNum());
}
#endif
int32 dx, dy, dz;
_tracker->getSpeed(dx, dy, dz);
if (GAME_IS_CRUSADER) {
// HACK: Hurl people a bit less hard in crusader until
// the movement bugs are fixed to make them fall less..
dx /= 4;
dy /= 4;
dz /= 4;
}
a->hurl(dx, dy, dz, 2);
// Note: do not wait for the fall to finish: this breaks
// the scene where Devon kills Mordea
return;
}
}
}
void ActorAnimProcess::doSpecial() {
Actor *a = getActor(_itemNum);
assert(a);
// All this stuff is U8 specific.
if (!GAME_IS_U8)
return;
Common::RandomSource &rs = Ultima8Engine::get_instance()->getRandomSource();
// play SFX when Avatar draws/sheathes weapon
if (_itemNum == kMainActorId &&
(_action == Animation::readyWeapon || _action == Animation::unreadyWeapon) &&
a->getEquip(ShapeInfo::SE_WEAPON) != 0) {
int sfx = rs.getRandomBit() ? 0x51 : 0x52; // constants!
AudioProcess *audioproc = AudioProcess::get_instance();
if (audioproc) audioproc->playSFX(sfx, 0x60, 1, 0);
return;
}
// ghosts
if (a->getShape() == 0x19b) {
Actor *hostile = nullptr;
if (_action == Animation::attack) {
// fireball on attack
unsigned int skullcount = a->countNearby(0x19d, 6 * 256);
if (skullcount > 5) return;
Actor *skull = Actor::createActor(0x19d, 0);
if (!skull) return;
skull->setFlag(Item::FLG_FAST_ONLY);
Point3 pt = a->getLocation();
Direction dirNum = a->getDir();
skull->move(pt.x + 32 * Direction_XFactor(dirNum), pt.y + 32 * Direction_XFactor(dirNum), pt.z);
hostile = skull;
} else if (a->getMapNum() != 54) { // Khumash-Gor doesn't summon ghouls
// otherwise, summon ghoul
unsigned int ghoulcount = a->countNearby(0x8e, 8 * 256);
if (ghoulcount > 2) return;
Point3 pt = a->getLocation();
pt.x += rs.getRandomNumberRngSigned(-3 * 256, 3 * 256);
pt.y += rs.getRandomNumberRngSigned(-3 * 256, 3 * 256);
Actor *ghoul = Actor::createActor(0x8e, 0);
if (!ghoul) return;
ghoul->setFlag(Item::FLG_FAST_ONLY);
if (!ghoul->canExistAt(pt, true)) {
ghoul->destroy();
return;
}
ghoul->move(pt);
ghoul->doAnim(Animation::standUp, dir_north);
hostile = ghoul;
}
if (hostile) {
// 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)
hostilecp->setTarget(cp->getTarget());
}
return;
}
// ghost's fireball
if (a->getShape() == 0x19d) {
Actor *av = getMainActor();
if (a->getRange(*av) < 96) {
a->setActorFlag(Actor::ACT_DEAD);
a->explode(0, true); // explode if close to the avatar
}
return;
}
// play PC/NPC footsteps
bool playavfootsteps = ConfMan.getBool("footsteps");
if (_itemNum != kMainActorId || playavfootsteps) {
int32 xd, yd, zd;
Point3 pt = a->getLocation();
a->getFootpadWorld(xd, yd, zd);
Box start(pt.x, pt.y, pt.z, xd, yd, zd);
pt = _tracker->getPosition();
Box target(pt.x, pt.y, pt.z, xd, yd, zd);
CurrentMap *cm = World::get_instance()->getCurrentMap();
PositionInfo info = cm->getPositionInfo(target, start, a->getShapeInfo()->_flags, _itemNum);
if (info.supported && info.land) {
uint32 floor = info.land->getShape();
bool running = (_action == Animation::run);
bool splash = false;
int sfx = 0;
switch (floor) { // lots of constants!!
case 0x03:
case 0x04:
case 0x09:
case 0x0B:
case 0x5C:
case 0x5E:
sfx = 0x2B;
break;
case 0x7E:
case 0x80:
sfx = 0xCD;
splash = true;
break;
case 0xA1:
case 0xA2:
case 0xA3:
case 0xA4:
sfx = (running ? 0x99 : 0x91);
break;
default:
sfx = (running ? 0x97 : 0x90);
break;
}
if (sfx) {
AudioProcess *audioproc = AudioProcess::get_instance();
if (audioproc)
audioproc->playSFX(sfx, 0x60, _itemNum, 0, false, 0x10000 + rs.getRandomNumber(0x1FFF) - 0x1000);
}
if (splash) {
pt = a->getLocation();
Process *sp = new SpriteProcess(475, 0, 7, 1, 1, pt.x, pt.y, pt.z);
Kernel::get_instance()->addProcess(sp);
}
}
}
}
void ActorAnimProcess::doFireWeaponCru(Actor *a, const AnimFrame *f) {
assert(a);
assert(f);
if (!f->is_cruattack())
return;
const Item *wpn = getItem(a->getActiveWeapon());
if (!wpn)
return;
const ShapeInfo *wpninfo = wpn->getShapeInfo();
if (!wpninfo || !wpninfo->_weaponInfo)
return;
if (a->getObjId() == kMainActorId && wpninfo->_weaponInfo->_damageType == 6) {
Process *auto_firer = new AutoFirerProcess();
Kernel::get_instance()->addProcess(auto_firer);
}
a->fireWeapon(f->cru_attackx(), f->cru_attacky(), f->cru_attackz(),
a->getDir(), wpninfo->_weaponInfo->_damageType, true);
AudioProcess *audioproc = AudioProcess::get_instance();
if (audioproc)
audioproc->playSFX(wpninfo->_weaponInfo->_sound, 0x80, a->getObjId(), 0, false);
}
void ActorAnimProcess::doHitSpecial(Item *hit) {
Actor *a = getActor(_itemNum);
assert(a);
Actor *attacked = dynamic_cast<Actor *>(hit);
if (_itemNum == 1 && _action == Animation::attack) {
// some magic weapons have some special effects
AudioProcess *audioproc = AudioProcess::get_instance();
MainActor *av = getMainActor();
ObjId weaponid = av->getEquip(ShapeInfo::SE_WEAPON);
Item *weapon = getItem(weaponid);
if (!weapon) return;
Common::RandomSource &rs = Ultima8Engine::get_instance()->getRandomSource();
uint32 weaponshape = weapon->getShape();
switch (weaponshape) {
case 0x32F: // magic hammer
if (audioproc) audioproc->playSFX(23, 0x60, 1, 0, false,
0x10000 + rs.getRandomNumber(0x1FFF) - 0x1000);
break;
case 0x330: { // Slayer
// if we killed somebody, thunder&lightning
if (attacked && attacked->hasActorFlags(Actor::ACT_DEAD)) {
// calling intrinsic...
PaletteFaderProcess::I_lightningBolt(0, 0);
int sfx;
switch (rs.getRandomNumber(2)) {
case 0:
sfx = 91;
break;
case 1:
sfx = 94;
break;
default:
sfx = 96;
break;
}
if (audioproc) audioproc->playSFX(sfx, 0x60, 1, 0);
}
break;
}
case 0x331: { // Flame Sting
int sfx = 33;
if (rs.getRandomBit())
sfx = 101;
if (audioproc) audioproc->playSFX(sfx, 0x60, 1, 0, false,
0x10000 + rs.getRandomNumber(0x1FFF) - 0x1000);
Point3 pt = a->getLocation();
// 1: create flame sprite
// 2: create flame object
// 3: wait
// 4a: destroy flame object
// 4b: create douse-flame sprite
Kernel *kernel = Kernel::get_instance();
int32 fx, fy, fz;
fx = pt.x + 96 * Direction_XFactor(_dir);
fy = pt.y + 96 * Direction_YFactor(_dir);
fz = pt.z;
// CONSTANTS!! (lots of them)
SpriteProcess *sp1 = new SpriteProcess(480, 0, 9, 1, 2, fx, fy, fz);
kernel->addProcess(sp1);
DelayProcess *dp1 = new DelayProcess(3);
ProcId dp1id = kernel->addProcess(dp1);
CreateItemProcess *cip = new CreateItemProcess(400, 0, 0,
Item::FLG_FAST_ONLY,
0, 0, 0, fx, fy, fz);
ProcId cipid = kernel->addProcess(cip);
DelayProcess *dp2 = new DelayProcess(rs.getRandomNumberRng(60, 120)); //2-4s
ProcId dp2id = kernel->addProcess(dp2);
DestroyItemProcess *dip = new DestroyItemProcess(0);
kernel->addProcess(dip);
SpriteProcess *sp2 = new SpriteProcess(381, 0, 9, 1, 1,
fx, fy, fz, true);
kernel->addProcess(sp2);
cip->waitFor(dp1id);
dp2->waitFor(cipid);
dip->waitFor(dp2id);
sp2->waitFor(dp2id);
break;
}
default:
break;
}
return ;
}
}
void ActorAnimProcess::terminate() {
#ifdef WATCHACTOR
if (_itemNum == watchactor)
debugC(kDebugActor, "Animation [%u] ActorAnimProcess %u terminating",
Kernel::get_instance()->getFrameNum(), getPid());
#endif
Actor *a = getActor(_itemNum);
if (a) {
if (_tracker) { // if we were really animating...
a->clearActorFlag(Actor::ACT_ANIMLOCK);
if (_tracker->getAnimAction()->hasFlags(AnimAction::AAF_DESTROYACTOR)) {
// destroy the actor
#ifdef WATCHACTOR
if (_itemNum == watchactor)
debugC(kDebugActor, "Animation [%u] ActorAnimProcess destroying actor %u",
Kernel::get_instance()->getFrameNum(), _itemNum);
#endif
Process *vanishproc = new DestroyItemProcess(a);
Kernel::get_instance()->addProcess(vanishproc);
return;
}
}
}
delete _tracker;
Process::terminate();
}
Common::String ActorAnimProcess::dumpInfo() const {
return Process::dumpInfo() +
Common::String::format(", _action: %d, _dir: %d", _action, _dir);
}
void ActorAnimProcess::saveData(Common::WriteStream *ws) {
Process::saveData(ws);
uint8 ff = _firstFrame ? 1 : 0;
ws->writeByte(ff);
uint8 ab = _animAborted ? 1 : 0;
ws->writeByte(ab);
uint8 attacked = _attackedSomething ? 1 : 0;
ws->writeByte(attacked);
ws->writeByte(static_cast<uint8>(Direction_ToUsecodeDir(_dir)));
ws->writeUint16LE(static_cast<uint16>(_action));
ws->writeUint16LE(static_cast<uint16>(_steps));
ws->writeUint16LE(static_cast<uint16>(_repeatCounter));
ws->writeUint16LE(static_cast<uint16>(_currentStep));
if (_tracker) {
ws->writeByte(1);
_tracker->save(ws);
} else
ws->writeByte(0);
}
bool ActorAnimProcess::loadData(Common::ReadStream *rs, uint32 version) {
if (!Process::loadData(rs, version)) return false;
_firstFrame = (rs->readByte() != 0);
_animAborted = (rs->readByte() != 0);
_attackedSomething = (rs->readByte() != 0);
_dir = Direction_FromUsecodeDir(rs->readByte());
_action = static_cast<Animation::Sequence>(rs->readUint16LE());
_steps = rs->readUint16LE();
_repeatCounter = rs->readUint16LE();
_currentStep = rs->readUint16LE();
assert(_tracker == nullptr);
if (rs->readByte() != 0) {
_tracker = new AnimationTracker();
if (!_tracker->load(rs, version))
return false;
}
return true;
}
} // End of namespace Ultima8
} // End of namespace Ultima

View File

@@ -0,0 +1,94 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef WORLD_ACTORS_ACTORANIMPROCESS_H
#define WORLD_ACTORS_ACTORANIMPROCESS_H
#include "ultima/ultima8/kernel/process.h"
#include "ultima/ultima8/misc/direction.h"
#include "ultima/ultima8/world/actors/animation.h"
namespace Ultima {
namespace Ultima8 {
class Actor;
class AnimAction;
struct AnimFrame;
class AnimationTracker;
class Item;
class ActorAnimProcess : public Process {
public:
ActorAnimProcess();
ActorAnimProcess(Actor *actor, Animation::Sequence action, Direction dir,
uint32 steps = 0);
ENABLE_RUNTIME_CLASSTYPE()
static const uint16 ACTOR_ANIM_PROC_TYPE = 0x00F0;
void run() override;
void terminate() override;
Common::String dumpInfo() const override;
Animation::Sequence getAction() const {
return _action;
}
bool loadData(Common::ReadStream *rs, uint32 version);
void saveData(Common::WriteStream *ws) override;
protected:
virtual bool init();
//! perform special action for an animation
void doSpecial();
//! perform special action when hitting an opponent
void doHitSpecial(Item *hit);
//! Fire weapon
void doFireWeaponCru(Actor *actor, const AnimFrame *frame);
Animation::Sequence _action;
Direction _dir;
uint32 _steps;
AnimationTracker *_tracker;
int _repeatCounter;
uint32 _currentStep;
bool _firstFrame;
bool _animAborted;
bool _attackedSomething; // attacked and hit something with this animation
//! Interpolate position on repeated frames
bool _interpolate;
};
} // End of namespace Ultima8
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,86 @@
/* 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/actor_bark_notify_process.h"
#include "ultima/ultima8/ultima8.h"
#include "ultima/ultima8/kernel/delay_process.h"
#include "ultima/ultima8/world/actors/actor.h"
#include "ultima/ultima8/kernel/kernel.h"
#include "ultima/ultima8/world/get_object.h"
namespace Ultima {
namespace Ultima8 {
DEFINE_RUNTIME_CLASSTYPE_CODE(ActorBarkNotifyProcess)
ActorBarkNotifyProcess::ActorBarkNotifyProcess()
: GumpNotifyProcess() {
}
ActorBarkNotifyProcess::ActorBarkNotifyProcess(uint16 it)
: GumpNotifyProcess(it) {
}
ActorBarkNotifyProcess::~ActorBarkNotifyProcess(void) {
}
void ActorBarkNotifyProcess::run() {
Actor *a = getActor(_itemNum);
if (!a) return;
if (a->isDead() || !a->hasAnim(Animation::talk))
return;
bool doAnim = true;
// if not standing or talking, don't do talk animation
Animation::Sequence lastanim = a->getLastAnim();
if (lastanim != Animation::stand && lastanim != Animation::talk)
doAnim = false;
else if (a->isBusy())
// if busy, don't do talk animation
doAnim = false;
// wait a short while (1-2.5 seconds) before doing the next animation
// (or even if not doing the animation)
Common::RandomSource &rs = Ultima8Engine::get_instance()->getRandomSource();
Process *delayproc = new DelayProcess(rs.getRandomNumberRng(30, 75));
ProcId delaypid = Kernel::get_instance()->addProcess(delayproc);
if (doAnim)
a->doAnim(Animation::talk, dir_current);
waitFor(delaypid);
}
void ActorBarkNotifyProcess::saveData(Common::WriteStream *ws) {
GumpNotifyProcess::saveData(ws);
}
bool ActorBarkNotifyProcess::loadData(Common::ReadStream *rs, uint32 version) {
if (!GumpNotifyProcess::loadData(rs, version)) return false;
return true;
}
} // End of namespace Ultima8
} // End of namespace Ultima

View File

@@ -0,0 +1,47 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef WORLD_ACTORS_ACTORBARKNOTIFYPROCESS_H
#define WORLD_ACTORS_ACTORBARKNOTIFYPROCESS_H
#include "ultima/ultima8/gumps/gump_notify_process.h"
namespace Ultima {
namespace Ultima8 {
class ActorBarkNotifyProcess : public GumpNotifyProcess {
public:
ENABLE_RUNTIME_CLASSTYPE()
ActorBarkNotifyProcess();
ActorBarkNotifyProcess(uint16 it);
~ActorBarkNotifyProcess(void) override;
void run() override;
bool loadData(Common::ReadStream *rs, uint32 version);
void saveData(Common::WriteStream *ws) override;
};
} // End of namespace Ultima8
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,90 @@
/* 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/ambush_process.h"
#include "ultima/ultima8/world/actors/main_actor.h"
#include "ultima/ultima8/world/actors/combat_process.h"
#include "ultima/ultima8/world/get_object.h"
namespace Ultima {
namespace Ultima8 {
DEFINE_RUNTIME_CLASSTYPE_CODE(AmbushProcess)
AmbushProcess::AmbushProcess() : Process(), _delayCount(0) {
}
AmbushProcess::AmbushProcess(Actor *actor) : _delayCount(0) {
assert(actor);
_itemNum = actor->getObjId();
_type = 0x21E; // CONSTANT !
}
void AmbushProcess::run() {
if (_delayCount > 0) {
_delayCount--;
return;
}
_delayCount = 10;
Actor *a = getActor(_itemNum);
if (!a) {
// this shouldn't happen
terminate();
return;
}
CombatProcess *cp = a->getCombatProcess();
if (!cp) {
// this shouldn't have happened
terminate();
return;
}
ObjId targetid = cp->seekTarget();
Item *target = getItem(targetid);
// no target in range yet, continue waiting
if (!target || a->getRange(*target) > 192)
return;
// target in range, so terminate and let parent take over
terminate();
}
void AmbushProcess::saveData(Common::WriteStream *ws) {
Process::saveData(ws);
ws->writeUint32LE(_delayCount);
}
bool AmbushProcess::loadData(Common::ReadStream *rs, uint32 version) {
if (!Process::loadData(rs, version)) return false;
_delayCount = rs->readUint32LE();
return true;
}
} // End of namespace Ultima8
} // End of namespace Ultima

View File

@@ -0,0 +1,51 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef WORLD_ACTORS_AMBUSHPROCESS_H
#define WORLD_ACTORS_AMBUSHPROCESS_H
#include "ultima/ultima8/kernel/process.h"
namespace Ultima {
namespace Ultima8 {
class Actor;
class AmbushProcess : public Process {
public:
AmbushProcess();
AmbushProcess(Actor *actor);
ENABLE_RUNTIME_CLASSTYPE()
void run() override;
bool loadData(Common::ReadStream *rs, uint32 version);
void saveData(Common::WriteStream *ws) override;
protected:
uint32 _delayCount;
};
} // End of namespace Ultima8
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,110 @@
/* 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/anim_action.h"
#include "ultima/ultima8/world/actors/actor.h"
#include "ultima/ultima8/ultima8.h"
namespace Ultima {
namespace Ultima8 {
void AnimAction::getAnimRange(unsigned int lastanim, Direction lastdir,
bool firststep, Direction dir,
unsigned int &startframe, unsigned int &endframe) const {
startframe = 0;
endframe = _size;
if (_flags & AAF_TWOSTEP) {
const bool looping = hasFlags(AAF_LOOPING);
// two-step animation?
if (firststep) {
if (looping) {
// for a looping animation, start at the end to
// make things more fluid
startframe = _size - 1;
} else {
startframe = 0;
}
endframe = _size / 2;
} else {
// second step starts halfway
startframe = _size / 2;
if (looping) {
endframe = _size - 1;
}
}
} else {
if (lastanim == _action && lastdir == dir && _size > 1) {
// skip first frame if repeating an animation
startframe = 1;
}
}
}
void AnimAction::getAnimRange(const Actor *actor, Direction dir,
unsigned int &startframe,
unsigned int &endframe) const {
getAnimRange(actor->getLastAnim(), actor->getDir(),
actor->hasActorFlags(Actor::ACT_FIRSTSTEP),
dir, startframe, endframe);
}
const AnimFrame &AnimAction::getFrame(Direction dir, unsigned int frameno) const {
uint32 diroff = static_cast<uint32>(dir);
// HACK for 16 dir support
if (_dirCount == 8)
diroff /= 2;
assert(diroff < _dirCount);
assert(frameno < _frames[diroff].size());
return _frames[diroff][frameno];
}
/**
Translate data file flags of U8 or Crusader into single format, to avoid
having to check against game type each time they are used
*/
/*static*/
AnimAction::AnimActionFlags AnimAction::loadAnimActionFlags(uint32 rawflags) {
uint32 ret = AAF_NONE;
ret |= (rawflags & AAF_COMMONFLAGS);
if (GAME_IS_U8) {
if (rawflags & AAF_ATTACK)
ret |= AAF_ATTACK;
if (rawflags & AAF_LOOPING2_U8)
ret |= AAF_LOOPING; // FIXME: handled like this is in pentagram code.. is it used?
} else {
assert(GAME_IS_CRUSADER);
if (rawflags & AAF_ROTATED)
ret |= AAF_ROTATED;
if (rawflags & AAF_16DIRS)
ret |= AAF_16DIRS;
}
return static_cast<AnimActionFlags>(ret);
}
} // End of namespace Ultima8
} // End of namespace Ultima

View File

@@ -0,0 +1,188 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef WORLD_ACTORS_ANIMACTION_H
#define WORLD_ACTORS_ANIMACTION_H
#include "ultima/shared/std/containers.h"
#include "ultima/ultima8/misc/direction.h"
namespace Ultima {
namespace Ultima8 {
class Actor;
struct AnimFrame {
int _frame;
int _deltaZ;
int _deltaDir;
int _sfx;
uint32 _flags;
/** Frame level flags */
enum AnimFrameFlags {
AFF_ONGROUND = 0x00000002,
AFF_FLIPPED = 0x00000020,
AFF_SPECIAL = 0x00000800, // U8 only
AFF_HURTY = 0x00001000, // Crusader only - TODO: find a better name for this.
AFF_USECODE = 0x00004000,
AFF_CRUFLIP = 0x00008000 // Crusader only
//AFF_UNKNOWN = 0xF0E0B01C,
//AFF_FIRE = 0x0F1F00C0
};
inline bool is_onground() const {
return (_flags & AFF_ONGROUND) != 0;
}
inline bool is_flipped() const {
return (_flags & AFF_FLIPPED) != 0;
}
inline bool is_callusecode() const {
return (_flags & AFF_USECODE) != 0;
}
inline bool is_cruflipped() const {
return (_flags & AFF_CRUFLIP) != 0;
}
inline int attack_range() const {
return ((_flags >> 2) & 0x07);
}
// Note: The next 3 functions each have a 4-bit
// value to unpack from the flags. x/y are signed, z is unsigned.
inline int cru_attackx() const {
uint32 rawx = (_flags & 0x00000780) << 5;
int16 signedx = static_cast<int16>(rawx) >> 12;
return signedx * 16;
}
inline int cru_attacky() const {
uint32 rawy = (_flags & 0x00F00000) >> 16;
return static_cast<int8>(rawy);
}
inline int cru_attackz() const {
return (_flags & 0x0F000000) >> 21;
}
inline bool is_cruattack() const {
return (cru_attackx() || cru_attacky() || cru_attackz());
}
};
class AnimAction {
friend class AnimDat;
public:
//! return the range of the animation to play
//! \param actor The actor to play the animation for
//! \param dir The direction
//! \param startframe The first frame to play
//! \param endframe The frame after the last frame to play
void getAnimRange(const Actor *actor, Direction dir,
unsigned int &startframe, unsigned int &endframe) const;
//! return the range of the animation to play
//! \param lastanim The lastanim of the Actor
//! \param lastdir The direction of the Actor
//! \param firststep The firststep flag of the Actor
//! \param dir The direction
//! \param startframe The first frame to play
//! \param endframe The frame after the last frame to play
void getAnimRange(unsigned int lastanim, Direction lastdir,
bool firststep, Direction dir,
unsigned int &startframe, unsigned int &endframe) const;
unsigned int getDirCount() const {
return _dirCount;
}
unsigned int getSize() const {
return _size;
}
int getFrameRepeat() const {
return _frameRepeat;
}
uint32 getShapeNum() const {
return _shapeNum;
}
uint32 getAction() const {
return _action;
}
bool hasFlags(uint32 mask) const {
return (_flags & mask) != 0;
}
uint32 getFlags() const {
return _flags;
}
const AnimFrame &getFrame(Direction dir, unsigned int frameno) const;
/**
* Animation level flags
*
* Note: Although these match the original values in the dat files, there is cleanup
* at load time to avoid having to check game type in many places in the code.
* See loadAnimActionFlags in anim_action.cpp
*/
enum AnimActionFlags {
AAF_NONE = 0x0000,
AAF_TWOSTEP = 0x0001,
AAF_ATTACK = 0x0002, // U8 only? also present in crusader, but ignored.
AAF_LOOPING = 0x0004,
AAF_UNSTOPPABLE = 0x0008,
AAF_LOOPING2_U8 = 0x0010,
AAF_ENDLOOP_U8 = 0x0020, // TODO: This starts a new anim at the end if pathfinding
AAF_ENDLOOP_CRU = 0x0040, // TODO: This starts a new anim at the end if pathfinding
AAF_HANGING = 0x0080,
AAF_16DIRS = 0x4000, // Cru only
AAF_DESTROYACTOR = 0x8000, // destroy actor after animation finishes
AAF_ROTATED = 0x10000, // Cru only
AAF_COMMONFLAGS = (AAF_TWOSTEP | AAF_LOOPING | AAF_UNSTOPPABLE | AAF_HANGING | AAF_DESTROYACTOR)
};
private:
static AnimActionFlags loadAnimActionFlags(uint32 rawflags);
uint32 _shapeNum;
uint32 _action;
Std::vector<AnimFrame> _frames[16]; // 8 or 16 directions
unsigned int _size;
int _frameRepeat;
AnimActionFlags _flags;
unsigned int _dirCount;
};
} // End of namespace Ultima8
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,112 @@
/* 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/animation.h"
#include "ultima/ultima8/ultima8.h"
namespace Ultima {
namespace Ultima8 {
namespace Animation {
bool isCombatAnim(const Sequence anim) {
if (GAME_IS_U8)
return isCombatAnimU8(anim);
else
return isCombatAnimCru(anim);
}
bool isCombatAnimU8(const Sequence anim) {
switch (anim) {
case combatStand:
case readyWeapon:
case advance:
case retreat:
case attack:
case kick:
case startBlock:
case stopBlock:
return true;
default:
return false;
}
}
bool isCastAnimU8(const Sequence anim) {
switch (anim) {
case cast1:
case cast2:
case cast3:
case cast4:
case cast5:
return true;
default:
return false;
}
}
bool isCombatAnimCru(const Sequence anim) {
switch (anim & ~crusaderAbsoluteAnimFlag) {
case combatStand:
case combatStandSmallWeapon:
case combatStandLargeWeapon:
case readyWeapon:
case advance:
case retreat:
case attack:
case reloadSmallWeapon:
case kick:
case kneel:
case kneelStartCru:
case kneelEndCru:
case kneelAndFire:
case brightFireLargeWpn:
case kneelCombatRollLeft:
case kneelCombatRollRight:
case combatRollLeft:
case combatRollRight:
case slideLeft:
case slideRight:
case startRun:
case run:
case stopRunningAndDrawSmallWeapon:
case kneelingAdvance:
case kneelingRetreat:
return true;
default:
return false;
}
}
/** determines if we need to ready or unready our weapon */
Sequence checkWeapon(const Sequence nextanim,
const Sequence lastanim) {
Sequence anim = nextanim;
if (isCombatAnim(nextanim) && !isCombatAnim(lastanim)) {
anim = readyWeapon;
} else if (!isCombatAnim(nextanim) && isCombatAnim(lastanim)) {
anim = unreadyWeapon;
}
return anim;
}
} // End of namespace Animation
} // End of namespace Ultima8
} // End of namespace Ultima

View File

@@ -0,0 +1,182 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef WORLD_ACTORS_ANIMATION_H
#define WORLD_ACTORS_ANIMATION_H
namespace Ultima {
namespace Ultima8 {
namespace Animation {
enum Sequence {
walk = 0,
run = 1,
stand = 2,
jumpUp = 3,
standUp = 4,
readyWeapon = 5,
unreadyWeapon = 6,
attack = 7,
advance = 8,
retreat = 9,
runningJump = 10,
shakeHead = 11,
step = 12,
stumbleBackwards = 13,
die = 14,
combatStand = 15,
land = 16,
jump = 17,
airwalkJump = 18,
//19-26: climbing up on increasingly high objects
climb16 = 19,
climb24 = 20,
climb32 = 21,
climb40 = 22,
climb48 = 23,
climb56 = 24,
climb64 = 25,
climb72 = 26,
//27-31: casting magic
cast1 = 27,
cast2 = 28,
cast3 = 29,
cast4 = 30,
cast5 = 31,
lookLeft = 32,
lookRight = 33,
startKneeling = 34,
kneel = 35,
//36: Vividos only: magic?
//37: Mythran only: magic?
//38: Vividos only: ?
//39: unused in u8
//40: ? - could be a slow attack or quick block ???
//41: unused in u8
keepBalance = 42,
//43: unused in u8
fallBackwards = 44,
hang = 45,
climbUp = 46,
idle1 = 47,
idle2 = 48,
kneel2 = 49,
stopKneeling = 50,
sitDownInChair = 51,
standUpFromChair = 52,
talk = 53,
//54: Mythran and Vividos only: magic?
work = 55,
drown = 56,
burn = 57,
kick = 58,
startBlock = 59,
stopBlock = 60,
//61: unused in u8
//62: unused in u8
//63: unused in u8
// All belowa are crusader-specific animations (some use the same IDs as above)
standCru = 0,
walkCru = 1,
retreatSmallWeapon = 2,
runCru = 3,
combatStandSmallWeapon = 4,
readySmallWeapon = 7,
fireSmallWeapon = 8,
reloadSmallWeapon = 10,
unreadySmallWeapon = 11,
readyLargeWeapon = 12,
fireLargeWeapon = 13,
reload = 14,
reloadLargeWeapon = 15,
unreadyLargeWeapon = 16,
fallBackwardsCru = 18,
fallForwardsCru = 20,
kneelCombatRollLeft = 23,
kneelCombatRollRight = 24,
stopRunningAndDrawLargeWeapon = 25,
kneelAndFire = 26,
slideLeft = 28,
slideRight = 29,
lookLeftCru = 30,
lookRightCru = 31,
teleportIn = 32,
teleportOut = 33,
startRunSmallWeapon = 34,
startRunLargeWeapon = 35,
advanceSmallWeapon = 36,
combatStandLargeWeapon = 37,
startRun = 38,
stopRunningAndDrawSmallWeapon = 39,
kneelStartCru = 40,
kneelEndCru = 41,
kneelAndFireSmallWeapon = 42,
kneelAndFireLargeWeapon = 43,
advanceLargeWeapon = 44,
retreatLargeWeapon = 45,
kneelingWithSmallWeapon = 46,
kneelingWithLargeWeapon = 47,
combatRunSmallWeapon = 48,
combatRunLargeWeapon = 49,
brightKneelAndFireLargeWeapon = 50,
kneelingRetreat = 51,
kneelingAdvance = 52,
kneelingSlowRetreat = 53,
brightFireLargeWpn = 54,
electrocuted = 55,
jumpForward = 56,
surrender = 57,
quickJumpCru = 58,
jumpLanding = 59,
surrenderStand = 60,
combatRollLeft = 61,
combatRollRight = 62,
finishFiring = 63,
/// A flag to say we want an exact number, don't do mapping from U8 animation
/// numbers. This is a bit of a hack because for most code we want to do
/// translations from U8 nums, but sometimes we have exact animation numbers
/// provided by usecode or from some translated code.
crusaderAbsoluteAnimFlag = 0x1000,
};
static inline Animation::Sequence absAnim(Animation::Sequence seq) {
return static_cast<Animation::Sequence>(seq | crusaderAbsoluteAnimFlag);
}
enum Result {
FAILURE = 0,
SUCCESS = 1,
END_OFF_LAND = 2
};
bool isCombatAnim(const Sequence anim);
bool isCombatAnimU8(const Sequence anim);
bool isCombatAnimCru(const Sequence anim);
bool isCastAnimU8(const Sequence anim);
Sequence checkWeapon(const Sequence nextanim, const Sequence lastanim);
} // End of namespace Animation
} // End of namespace Ultima8
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,657 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "ultima/ultima8/misc/debugger.h"
#include "ultima/ultima8/world/actors/animation_tracker.h"
#include "ultima/ultima8/games/game_data.h"
#include "ultima/ultima8/world/actors/actor.h"
#include "ultima/ultima8/world/world.h"
#include "ultima/ultima8/world/current_map.h"
#include "ultima/ultima8/gfx/main_shape_archive.h"
#include "ultima/ultima8/gfx/anim_dat.h"
#include "ultima/ultima8/world/actors/anim_action.h"
#include "ultima/ultima8/misc/direction_util.h"
#include "ultima/ultima8/usecode/uc_list.h"
#include "ultima/ultima8/world/loop_script.h"
#include "ultima/ultima8/world/get_object.h"
namespace Ultima {
namespace Ultima8 {
//#define WATCHACTOR 1
#ifdef WATCHACTOR
static const int watchactor = WATCHACTOR;
#endif
AnimationTracker::AnimationTracker() : _firstFrame(true), _done(false),
_blocked(false), _unsupported(false), _hitObject(0), _mode(NormalMode),
_actor(0), _dir(dir_north), _animAction(nullptr),
_prev(), _curr(), _start(),
_targetDx(0), _targetDy(0), _targetDz(0), _targetOffGroundLeft(0),
_firstStep(false), _shapeFrame(0), _currentFrame(0), _startFrame(0),
_endFrame(0), _flipped(false) {
}
AnimationTracker::~AnimationTracker() {
}
bool AnimationTracker::init(const Actor *actor, Animation::Sequence action,
Direction dir, const PathfindingState *state) {
assert(actor);
_actor = actor->getObjId();
uint32 shape = actor->getShape();
uint32 actionnum = AnimDat::getActionNumberForSequence(action, actor);
_animAction = GameData::get_instance()->getMainShapes()->
getAnim(shape, actionnum);
if (!_animAction) {
#ifdef WATCHACTOR
if (actor && actor->getObjId() == watchactor) {
debugC(kDebugActor, "AnimationTracker: no animation action %d for shape %d",
actionnum, shape);
}
#endif
return false;
}
_dir = dir;
if (state == 0) {
_animAction->getAnimRange(actor, _dir, _startFrame, _endFrame);
_curr = actor->getLocation();
_flipped = actor->hasFlags(Item::FLG_FLIPPED);
_firstStep = actor->hasActorFlags(Actor::ACT_FIRSTSTEP);
} else {
_animAction->getAnimRange(state->_lastAnim, state->_direction,
state->_firstStep, _dir, _startFrame, _endFrame);
_flipped = state->_flipped;
_firstStep = state->_firstStep;
_curr = state->_point;
}
_start = _curr;
#ifdef WATCHACTOR
if (actor && actor->getObjId() == watchactor) {
debugC(kDebugActor, "AnimationTracker: playing action %d %d-%d (animAction flags: 0x04%x)",
actionnum, _startFrame, _endFrame, _animAction->getFlags());
}
#endif
_firstFrame = true;
_done = false;
_blocked = false;
_unsupported = false;
_hitObject = 0;
_mode = NormalMode;
return true;
}
unsigned int AnimationTracker::getNextFrame(unsigned int frame) const {
frame++;
if (!_animAction || frame == _endFrame)
return _endFrame;
// loop if necessary
if (frame >= _animAction->getSize()) {
if (_animAction->hasFlags(AnimAction::AAF_LOOPING)) {
// CHECKME: unknown flag
frame = 1;
} else {
frame = 0;
}
}
return frame;
}
bool AnimationTracker::stepFrom(const Point3 &pt) {
_curr = pt;
return step();
}
void AnimationTracker::evaluateMaxAnimTravel(int32 &max_endx, int32 &max_endy, Direction dir) {
max_endx = _curr.x;
max_endy = _curr.y;
if (_done) return;
Actor *a = getActor(_actor);
assert(a);
unsigned int testframe;
if (_firstFrame)
testframe = _startFrame;
else
testframe = getNextFrame(_currentFrame);
for (;;) {
const AnimFrame &f = _animAction->getFrame(dir, testframe);
// determine movement for this frame
int32 dx = 4 * Direction_XFactor(dir) * f._deltaDir;
int32 dy = 4 * Direction_YFactor(dir) * f._deltaDir;
max_endx += dx;
max_endy += dy;
testframe = getNextFrame(testframe);
if (testframe == _endFrame)
return;
}
}
bool AnimationTracker::step() {
if (_done) return false;
if (_firstFrame)
_currentFrame = _startFrame;
else
_currentFrame = getNextFrame(_currentFrame);
if (_currentFrame == _endFrame) {
_done = true;
// toggle ACT_FIRSTSTEP flag if necessary. This is remembered
// between two-step animations.
if (_animAction->hasFlags(AnimAction::AAF_TWOSTEP))
_firstStep = !_firstStep;
return false;
}
const bool is_u8 = GAME_IS_U8;
const bool is_crusader = !is_u8;
_prev = _curr;
// reset status flags
_unsupported = false;
_blocked = false;
_firstFrame = false;
const AnimFrame &f = _animAction->getFrame(_dir, _currentFrame);
_shapeFrame = f._frame;
_flipped = (is_u8 && f.is_flipped())
|| (is_crusader && f.is_cruflipped());
// determine movement for this frame
Direction movedir = _dir;
if (_animAction->hasFlags(AnimAction::AAF_ROTATED)) {
movedir = Direction_TurnByDelta(movedir, 4, dirmode_16dirs);
}
int32 dx = 4 * Direction_XFactor(movedir) * f._deltaDir;
int32 dy = 4 * Direction_YFactor(movedir) * f._deltaDir;
int32 dz = f._deltaZ;
if (_mode == TargetMode && !f.is_onground()) {
dx += _targetDx / _targetOffGroundLeft;
dy += _targetDy / _targetOffGroundLeft;
dz += _targetDz / _targetOffGroundLeft;
_targetDx -= _targetDx / _targetOffGroundLeft;
_targetDy -= _targetDy / _targetOffGroundLeft;
_targetDz -= _targetDz / _targetOffGroundLeft;
--_targetOffGroundLeft;
}
// determine footpad
Actor *a = getActor(_actor);
assert(a);
bool actorflipped = a->hasFlags(Item::FLG_FLIPPED);
int32 xd, yd, zd;
a->getFootpadWorld(xd, yd, zd);
if (actorflipped != _flipped) {
int32 t = xd;
xd = yd;
yd = t;
}
CurrentMap *cm = World::get_instance()->getCurrentMap();
// TODO: check if this step is allowed
// * can move?
// if not:
// - try to step up a bit
// - try to shift left/right a bit
// CHECKME: how often can we do these minor adjustments?
// CHECKME: for which animation types can we do them?
// if still fails: _blocked
// * if ONGROUND
// - is supported if ONGROUND?
// if not:
// * try to step down a bit
// * try to shift left/right a bit
// if still fails: _unsupported
// - if supported by non-land item: _unsupported
// It might be worth it creating a 'scanForValidPosition' function
// (in CurrentMap maybe) that scans a small area around the given
// coordinates for a valid position (with 'must be supported' as a flag).
// Note that it should only check in directions orthogonal to the movement
// _direction (to prevent it becoming impossible to step off a ledge).
// I seem to recall that the teleporter from the Upper Catacombs teleporter
// to the Upper Catacombs places you inside the floor. Using this
// scanForValidPosition after a teleport would work around that problem.
int32 tx, ty, tz;
tx = _curr.x + dx;
ty = _curr.y + dy;
tz = _curr.z + dz;
// Only for particularly large steps we do a full sweepTest
if (ABS(dx) >= xd - 8 || ABS(dy) >= yd - 8 || ABS(dz) >= zd - 8) {
Point3 start = _curr;
Point3 end(tx, ty, tz);
int32 dims[3] = { xd, yd, zd };
// Do the sweep test
Std::list<CurrentMap::SweepItem> collisions;
cm->sweepTest(start, end, dims, a->getShapeInfo()->_flags, _actor,
false, &collisions);
for (const auto &collision : collisions) {
// hit something, can't move
if (!collision._touching && collision._blocking) {
#ifdef WATCHACTOR
if (a->getObjId() == watchactor) {
debugC(kDebugActor, "AnimationTracker: did sweepTest for large step; collision at time %d", it->_hitTime);
}
#endif
_blocked = true;
_curr = collision.GetInterpolatedCoords(end, start);
return false;
}
}
// If it succeeded, we proceed as usual
}
Box target(tx, ty, tz, xd, yd, zd);
Box start(_start.x, _start.y, _start.z, xd, yd, zd);
PositionInfo info = cm->getPositionInfo(target, start, a->getShapeInfo()->_flags, _actor);
if (is_u8 && info.valid && info.supported && info.land) {
// Might need to check for bridge traversal adjustments
uint32 supportshape = info.land->getShape();
if (supportshape >= 675 && supportshape <= 681) {
// Could be a sloping portion of a bridge. For a bridge along the
// X axis, positive descent delta is a positive change in Y when
// moving to higher X (left to right). Units are 60x the needed
// dy/dx
int descentdelta = 0;
if (supportshape == 675)
descentdelta = -20; // Descend
else if (supportshape == 676)
descentdelta = 12; // Ascend
else if (supportshape == 681)
descentdelta = -20; // Descend
if (descentdelta) {
if (dy == 0 && dx != 0 && !info.land->hasFlags(Item::FLG_FLIPPED)) {
// Moving left or right on horizontal bridge
// descentdelta = 60*dy/dx
// 60*dy = descentdelta * dx
// dy = descentdelta * dx / 60;
ty += descentdelta * dx / 60;
} else if (dx == 0 && dy != 0 && info.land->hasFlags(Item::FLG_FLIPPED)) {
// Moving up or down on vertical bridge
tx += descentdelta * dy / 60;
}
}
}
}
if (!info.valid || (f.is_onground() && !info.supported)) {
// If on ground, try to adjust properly. Never do it for dead Crusader NPCs,
// as they don't get gravity and the death process gets stuck.
// TODO: Profile the effect of disabling this for pathfinding.
// It shouldn't be necessary in that case, and may provide a
// worthwhile speed-up.
if (f.is_onground() && zd > 8 && !(is_crusader && a->isDead())) {
bool targetok = cm->scanForValidPosition(tx, ty, tz, a, _dir,
true, tx, ty, tz);
if (!targetok) {
_blocked = true;
return false;
} else {
#ifdef WATCHACTOR
if (a->getObjId() == watchactor) {
debugC(kDebugActor, "AnimationTracker: adjusted step: x: %d, %d, %d y: %d, %d, %d z: %d, %d, %d",
tx, _x, dx, ty, _y, dy, tz, _z, dz);
}
#endif
}
} else {
if (!info.valid) {
_blocked = true;
return false;
}
}
}
#ifdef WATCHACTOR
if (a->getObjId() == watchactor) {
debugC(kDebugActor, "AnimationTracker: step (%d, %d, %d) + (%d, %d, %d)",
_x, _y, _z, tx - _x, ty - _y, tz - _z);
}
#endif
_curr.x = tx;
_curr.y = ty;
_curr.z = tz;
// if attack animation, see if we hit something
if (_animAction->hasFlags(AnimAction::AAF_ATTACK) &&
(_hitObject == 0) && f.attack_range() > 0) {
checkWeaponHit();
}
if (f.is_onground()) {
// needs support
target = Box(tx, ty, tz, xd, yd, zd);
info = cm->getPositionInfo(target, start, a->getShapeInfo()->_flags, _actor);
if (!info.supported) {
_unsupported = true;
return false;
}
}
if (f.is_callusecode()) {
a->callUsecodeEvent_calledFromAnim();
}
return true;
}
const AnimFrame *AnimationTracker::getAnimFrame() const {
return &_animAction->getFrame(_dir, _currentFrame);
}
void AnimationTracker::setTargetedMode(const Point3 &pt) {
unsigned int i;
int totaldir = 0;
int totalz = 0;
int offGround = 0;
int32 end_dx, end_dy, end_dz;
for (i = _startFrame; i != _endFrame; i = getNextFrame(i)) {
const AnimFrame &f = _animAction->getFrame(_dir, i);
totaldir += f._deltaDir; // This line sometimes seg faults.. ????
totalz += f._deltaZ;
if (!f.is_onground())
++offGround;
}
end_dx = 4 * Direction_XFactor(_dir) * totaldir;
end_dy = 4 * Direction_YFactor(_dir) * totaldir;
end_dz = totalz;
if (offGround) {
_mode = TargetMode;
_targetOffGroundLeft = offGround;
_targetDx = pt.x - _curr.x - end_dx;
_targetDy = pt.y - _curr.y - end_dy;
_targetDz = pt.z - _curr.z - end_dz;
// Don't allow large changes in Z
if (_targetDz > 16)
_targetDz = 16;
if (_targetDz < -16)
_targetDz = -16;
}
}
void AnimationTracker::checkWeaponHit() {
int range = _animAction->getFrame(_dir, _currentFrame).attack_range();
const Actor *a = getActor(_actor);
assert(a);
Box abox = a->getWorldBox();
abox.moveTo(_curr.x, _curr.y, _curr.z);
abox.translate(Direction_XFactor(_dir) * 32 * range, Direction_YFactor(_dir) * 32 * range, 0);
#ifdef WATCHACTOR
if (a->getObjId() == watchactor) {
debugC(kDebugActor, "AnimationTracker: Checking hit, range %d, box %d, %d, %d : %d, %d, %d",
range, abox._x, abox._y, abox._z, abox._xd, abox._yd, abox._zd);
}
#endif
CurrentMap *cm = World::get_instance()->getCurrentMap();
UCList itemlist(2);
LOOPSCRIPT(script, LS_TOKEN_END);
cm->areaSearch(&itemlist, script, sizeof(script), 0, 320, false, _curr.x, _curr.y);
ObjId hit = 0;
for (unsigned int i = 0; i < itemlist.getSize(); ++i) {
ObjId itemid = itemlist.getuint16(i);
if (itemid == _actor) continue; // don't want to hit self
Actor *item = getActor(itemid);
if (!item) continue;
Box ibox = item->getWorldBox();
if (abox.overlaps(ibox)) {
hit = itemid;
#ifdef WATCHACTOR
if (a->getObjId() == watchactor) {
debugC(kDebugActor, "hit: %s", item->dumpInfo().c_str());
}
#endif
break;
}
}
#ifdef WATCHACTOR
if (a->getObjId() == watchactor && !hit) {
debugC(kDebugActor, "nothing");
}
#endif
_hitObject = hit;
}
void AnimationTracker::updateState(PathfindingState &state) {
state._point = _curr;
state._flipped = _flipped;
state._firstStep = _firstStep;
}
void AnimationTracker::updateActorFlags() {
Actor *a = getActor(_actor);
assert(a);
if (_flipped)
a->setFlag(Item::FLG_FLIPPED);
else
a->clearFlag(Item::FLG_FLIPPED);
if (_firstStep)
a->setActorFlag(Actor::ACT_FIRSTSTEP);
else
a->clearActorFlag(Actor::ACT_FIRSTSTEP);
if (_animAction && GAME_IS_U8) {
bool hanging = _animAction->hasFlags(AnimAction::AAF_HANGING);
if (hanging)
a->setFlag(Item::FLG_HANGING);
else
a->clearFlag(Item::FLG_HANGING);
}
if (_currentFrame != _endFrame)
a->_animFrame = _currentFrame;
}
Point3 AnimationTracker::getInterpolatedPosition(int fc) const {
int32 dx = _curr.x - _prev.x;
int32 dy = _curr.y - _prev.y;
int32 dz = _curr.z - _prev.z;
int repeat = _animAction->getFrameRepeat();
return Point3(_prev.x + (dx * fc) / (repeat + 1),
_prev.y + (dy * fc) / (repeat + 1),
_prev.z + (dz * fc) / (repeat + 1));
}
void AnimationTracker::getSpeed(int32 &dx, int32 &dy, int32 &dz) const {
dx = _curr.x - _prev.x;
dy = _curr.y - _prev.y;
dz = _curr.z - _prev.z;
}
void AnimationTracker::save(Common::WriteStream *ws) {
ws->writeUint32LE(_startFrame);
ws->writeUint32LE(_endFrame);
uint8 ff = _firstFrame ? 1 : 0;
ws->writeByte(ff);
ws->writeUint32LE(_currentFrame);
ws->writeUint16LE(_actor);
ws->writeByte(static_cast<uint8>(Direction_ToUsecodeDir(_dir)));
if (_animAction) {
ws->writeUint32LE(_animAction->getShapeNum());
ws->writeUint32LE(_animAction->getAction());
} else {
ws->writeUint32LE(0);
ws->writeUint32LE(0);
}
ws->writeUint32LE(static_cast<uint32>(_prev.x));
ws->writeUint32LE(static_cast<uint32>(_prev.y));
ws->writeUint32LE(static_cast<uint32>(_prev.z));
ws->writeUint32LE(static_cast<uint32>(_curr.x));
ws->writeUint32LE(static_cast<uint32>(_curr.y));
ws->writeUint32LE(static_cast<uint32>(_curr.z));
ws->writeUint16LE(static_cast<uint16>(_mode));
if (_mode == TargetMode) {
ws->writeUint32LE(static_cast<uint32>(_targetDx));
ws->writeUint32LE(static_cast<uint32>(_targetDy));
ws->writeUint32LE(static_cast<uint32>(_targetDz));
ws->writeUint32LE(static_cast<uint32>(_targetOffGroundLeft));
}
uint8 fs = _firstStep ? 1 : 0;
ws->writeByte(fs);
uint8 fl = _flipped ? 1 : 0;
ws->writeByte(fl);
ws->writeUint32LE(_shapeFrame);
uint8 flag = _done ? 1 : 0;
ws->writeByte(flag);
flag = _blocked ? 1 : 0;
ws->writeByte(flag);
flag = _unsupported ? 1 : 0;
ws->writeByte(flag);
ws->writeUint16LE(_hitObject);
}
bool AnimationTracker::load(Common::ReadStream *rs, uint32 version) {
_startFrame = rs->readUint32LE();
_endFrame = rs->readUint32LE();
_firstFrame = (rs->readByte() != 0);
_currentFrame = rs->readUint32LE();
_actor = rs->readUint16LE();
_dir = Direction_FromUsecodeDir(rs->readByte());
uint32 shapenum = rs->readUint32LE();
uint32 action = rs->readUint32LE();
if (shapenum == 0) {
_animAction = nullptr;
} else {
_animAction = GameData::get_instance()->getMainShapes()->
getAnim(shapenum, action);
assert(_animAction);
}
_prev.x = rs->readUint32LE();
_prev.y = rs->readUint32LE();
_prev.z = rs->readUint32LE();
_curr.x = rs->readUint32LE();
_curr.y = rs->readUint32LE();
_curr.z = rs->readUint32LE();
_mode = static_cast<Mode>(rs->readUint16LE());
if (_mode == TargetMode) {
_targetDx = rs->readUint32LE();
_targetDy = rs->readUint32LE();
if (version >= 5) {
_targetDz = rs->readUint32LE();
_targetOffGroundLeft = rs->readUint32LE();
} else {
// Versions before 5 stored the only _x,_y adjustment
// to be made per frame. This is less accurate and ignores _z.
_targetOffGroundLeft = 0;
unsigned int i = _currentFrame;
if (!_firstFrame) i = getNextFrame(i);
for (; _animAction && i != _endFrame; i = getNextFrame(i)) {
const AnimFrame &f = _animAction->getFrame(_dir, i);
if (f.is_onground())
++_targetOffGroundLeft;
}
_targetDx *= _targetOffGroundLeft;
_targetDy *= _targetOffGroundLeft;
_targetDz = 0;
}
}
_firstStep = (rs->readByte() != 0);
_flipped = (rs->readByte() != 0);
_shapeFrame = rs->readUint32LE();
_done = (rs->readByte() != 0);
_blocked = (rs->readByte() != 0);
_unsupported = (rs->readByte() != 0);
_hitObject = rs->readUint16LE();
return true;
}
} // End of namespace Ultima8
} // End of namespace Ultima

View File

@@ -0,0 +1,147 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef WORLD_ACTORS_ANIMATIONTRACKER_H
#define WORLD_ACTORS_ANIMATIONTRACKER_H
#include "ultima/ultima8/world/actors/animation.h"
#include "ultima/ultima8/world/actors/pathfinder.h"
#include "ultima/ultima8/misc/point3.h"
namespace Ultima {
namespace Ultima8 {
class Actor;
class AnimAction;
struct AnimFrame;
class AnimationTracker {
public:
AnimationTracker();
~AnimationTracker();
//! initialize the AnimationTracker for the given actor, action, dir
//! if state is non-zero, start from that state instead of the Actor's
//! current state
bool init(const Actor *actor, Animation::Sequence action, Direction dir,
const PathfindingState *state = 0);
//! evaluate the maximum distance the actor will travel if the current
//! animation runs to completion by incremental calls to step
void evaluateMaxAnimTravel(int32 &max_endx, int32 &max_endy, Direction dir);
//! do a single step of the animation
//! returns true if everything ok, false if not
//! caller must decide if animation should continue after a 'false'
bool step();
//! do a single step of the animation, starting at the point
//! returns true if everything ok, false if not
//! caller must decide if animation should continue after a 'false'
bool stepFrom(const Point3 &pt);
//! update the PathfindingState with latest coordinates and flags
void updateState(PathfindingState &state);
//! update the Actor with latest flags and animframe
void updateActorFlags();
//! get the current position
Point3 getPosition() const {
return _curr;
}
Point3 getInterpolatedPosition(int fc) const;
//! get the difference between current position and previous position
void getSpeed(int32 &dx, int32 &dy, int32 &dz) const;
//! get the current (shape)frame
uint32 getFrame() const {
return _shapeFrame;
}
//! get the current AnimAction
const AnimAction *getAnimAction() const {
return _animAction;
}
//! get the current AnimFrame
const AnimFrame *getAnimFrame() const;
void setTargetedMode(const Point3 &pt);
bool isDone() const {
return _done;
}
bool isBlocked() const {
return _blocked;
}
bool isUnsupported() const {
return _unsupported;
}
ObjId hitSomething() const {
return _hitObject;
}
bool load(Common::ReadStream *rs, uint32 version);
void save(Common::WriteStream *ods);
private:
enum Mode {
NormalMode = 0,
TargetMode
};
unsigned int getNextFrame(unsigned int frame) const;
void checkWeaponHit();
unsigned int _startFrame, _endFrame;
bool _firstFrame;
unsigned int _currentFrame;
ObjId _actor;
Direction _dir;
const AnimAction *_animAction;
// actor state
Point3 _prev;
Point3 _curr;
Point3 _start;
int32 _targetDx, _targetDy, _targetDz;
int32 _targetOffGroundLeft;
bool _firstStep, _flipped;
uint32 _shapeFrame;
// status flags
bool _done;
bool _blocked;
bool _unsupported;
ObjId _hitObject;
Mode _mode;
};
} // End of namespace Ultima8
} // End of namespace Ultima
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,181 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef 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;
/**
* The NPC attack process used in Crusader games. This is more advanced than the Ultima
* CombatProcess, and contains a small language to implement the AI, which is specified in
* the combat.dat file (see CombatDat class)
*/
class AttackProcess : public Process {
public:
AttackProcess();
AttackProcess(Actor *actor);
virtual ~AttackProcess();
ENABLE_RUNTIME_CLASSTYPE()
void run() override;
void terminate() override;
Common::String dumpInfo() const override;
void setIsActivityAOrB() {
_isActivityAorB = true;
}
void setIsActivity9OrB() {
_isActivity9orB = true;
}
void setField97() {
_field97 = true;
}
void setField7F() {
_field7f = true;
}
void setTimer3();
uint16 getTarget() const {
return _target;
}
void setTarget(uint16 target) {
_target = target;
}
/** Get the right "attack" sound for No Regret for the
given actor. This is actually used for surrender sounds too, hence
being public static so it can be used from SurrenderProcess. */
static int16 getRandomAttackSoundRegret(const Actor *actor);
bool loadData(Common::ReadStream *rs, uint32 version);
void saveData(Common::WriteStream *ws) override;
static const uint16 ATTACK_PROC_TYPE = 0x259;
private:
/** Set the current tactic in use from the combat.dat file. If 0,
* will use the genericAttack function. */
void setTacticNo(int block);
/** Set the sub-tactic block - should be 0 or 1 (although 0-3 are
* supported in the dat file format, only 0/1 are ever used) */
void setBlockNo(int block);
/// Read the next word and return the value without using array
uint16 readNextWordRaw();
/** Read the next word and pull from the data array if its value
* is over the magic number*/
uint16 readNextWordWithData();
/// set data in the array - offset includes the magic number
void setAttackData(uint16 offset, uint16 val);
/// get data from the array - offset includes the magic number
uint16 getAttackData(uint16 offset) const;
/// This is the equivalent of run() when a tactic hasn't been selected yet.
void genericAttack();
/// Sleep the process for the given number of ticks
void sleep(int ticks);
/// Check the sound timer and return if we are ready for a new sound
bool readyForNextSound(uint32 now);
bool checkTimer2PlusDelayElapsed(int now);
void pathfindToItemInNPCData();
bool timer4and5Update(int now);
void timeNowToTimerVal2(int now);
bool checkReady(int now, Direction targetdir);
/** Check if it's time to make a sound and if so start one - for most NPCs
* that's on startup, but some make regular sounds (see readyForNextSound) */
void checkRandomAttackSound(int now, uint32 shapeno);
/** Check if it's time to make a new sound, Regret version. */
void checkRandomAttackSoundRegret(const Actor *actor);
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;
/// an array used to hold data for the combat lang
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 orig
uint32 _soundTimestamp; /// 0x84/0x86 in orig - time a sound was last played
uint32 _soundDelayTicks; /// Delay between playing sounds, always 480 in No Remorse - Not saved.
int32 _fireTimestamp; /// 0x90/0x92 in orig - time NPC last fired
// Used in No Regret only, to avoid replaying the same sfx for attack twice in a row.
static int16 _lastAttackSound;
static int16 _lastLastAttackSound;
};
} // End of namespace Ultima8
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,93 @@
/* 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/auto_firer_process.h"
#include "ultima/ultima8/world/actors/actor.h"
#include "ultima/ultima8/audio/audio_process.h"
#include "ultima/ultima8/kernel/kernel.h"
#include "ultima/ultima8/ultima8.h"
#include "ultima/ultima8/world/get_object.h"
namespace Ultima {
namespace Ultima8 {
DEFINE_RUNTIME_CLASSTYPE_CODE(AutoFirerProcess)
AutoFirerProcess::AutoFirerProcess() : Process() {
Actor *a = getControlledActor();
if (a)
_itemNum = a->getObjId();
_type = 0x260; // CONSTANT !
_startTicks = Kernel::get_instance()->getTickNum();
}
void AutoFirerProcess::run() {
if (Kernel::get_instance()->getTickNum() > _startTicks + 10) {
Actor *a = getControlledActor();
if (!a) {
terminate();
return;
}
uint16 weaponno = a->getActiveWeapon();
const Item *wpn = getItem(weaponno);
if (wpn && wpn->getShape() == 0x38d && wpn->getShapeInfo()->_weaponInfo) {
const WeaponInfo *info = wpn->getShapeInfo()->_weaponInfo;
int shotsleft;
if (info->_ammoShape) {
shotsleft = wpn->getQuality();
} else if (info->_energyUse) {
shotsleft = a->getMana() / info->_energyUse;
} else {
shotsleft = 1;
}
if (shotsleft > 0) {
int32 x = 0;
int32 y = 0;
int32 z = 0;
a->addFireAnimOffsets(x, y, z);
a->fireWeapon(x, y, z, a->getDir(), info->_damageType, true);
AudioProcess *audioproc = AudioProcess::get_instance();
if (audioproc && info->_sound)
audioproc->playSFX(info->_sound, 0x80, a->getObjId(), 0, false);
}
}
terminate();
}
}
void AutoFirerProcess::saveData(Common::WriteStream *ws) {
Process::saveData(ws);
ws->writeUint32LE(_startTicks);
}
bool AutoFirerProcess::loadData(Common::ReadStream *rs, uint32 version) {
if (!Process::loadData(rs, version)) return false;
_startTicks = rs->readUint32LE();
return true;
}
} // End of namespace Ultima8
} // End of namespace Ultima

View File

@@ -0,0 +1,51 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef WORLD_ACTORS_AUTOFIRERPROCESS_H
#define WORLD_ACTORS_AUTOFIRERPROCESS_H
#include "ultima/ultima8/kernel/process.h"
namespace Ultima {
namespace Ultima8 {
/**
* A process which fires another shot after a short delay, then terminates
*/
class AutoFirerProcess : public Process {
public:
AutoFirerProcess();
ENABLE_RUNTIME_CLASSTYPE()
void run() override;
bool loadData(Common::ReadStream *rs, uint32 version);
void saveData(Common::WriteStream *ws) override;
private:
uint32 _startTicks;
};
} // End of namespace Ultima8
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,102 @@
/* 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/avatar_death_process.h"
#include "ultima/ultima8/world/actors/main_actor.h"
#include "ultima/ultima8/gumps/readable_gump.h"
#include "ultima/ultima8/games/game_data.h"
#include "ultima/ultima8/kernel/kernel.h"
#include "ultima/ultima8/ultima8.h"
#include "ultima/ultima8/kernel/delay_process.h"
#include "ultima/ultima8/gumps/main_menu_process.h"
#include "ultima/ultima8/gumps/gump_notify_process.h"
#include "ultima/ultima8/gfx/palette_manager.h"
#include "ultima/ultima8/audio/audio_process.h"
#include "ultima/ultima8/world/get_object.h"
namespace Ultima {
namespace Ultima8 {
DEFINE_RUNTIME_CLASSTYPE_CODE(AvatarDeathProcess)
AvatarDeathProcess::AvatarDeathProcess() : Process() {
_itemNum = 1;
_type = 1; // CONSTANT !
}
void AvatarDeathProcess::run() {
MainActor *av = getMainActor();
if (!av) {
warning("AvatarDeathProcess: MainActor object missing");
// avatar gone??
terminate();
return;
}
if (!av->hasActorFlags(Actor::ACT_DEAD)) {
warning("AvatarDeathProcess: MainActor not dead");
// avatar not dead?
terminate();
return;
}
PaletteManager *palman = PaletteManager::get_instance();
palman->untransformPalette(PaletteManager::Pal_Game);
Process *menuproc = new MainMenuProcess();
Kernel::get_instance()->addProcess(menuproc);
if (GAME_IS_U8) {
// Show the avatar gravestone
ReadableGump *gump = new ReadableGump(1, 27, 11,
_TL_("HERE LIES*THE AVATAR*REST IN PEACE"));
gump->InitGump(0);
gump->setRelativePosition(Gump::CENTER);
Process *gumpproc = gump->GetNotifyProcess();
menuproc->waitFor(gumpproc);
} else {
// Play "Silencer Terminated" audio and wait
// a couple of seconds before showing menu
AudioProcess *ap = AudioProcess::get_instance();
ap->playSFX(9, 0x10, 0, 1);
DelayProcess *delayproc = new DelayProcess(120);
Kernel::get_instance()->addProcess(delayproc);
menuproc->waitFor(delayproc);
}
// done
terminate();
}
void AvatarDeathProcess::saveData(Common::WriteStream *ws) {
Process::saveData(ws);
}
bool AvatarDeathProcess::loadData(Common::ReadStream *rs, uint32 version) {
if (!Process::loadData(rs, version)) return false;
return true;
}
} // End of namespace Ultima8
} // End of namespace Ultima

View File

@@ -0,0 +1,45 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef WORLD_ACTORS_AVATARDEATHPROCESS_H
#define WORLD_ACTORS_AVATARDEATHPROCESS_H
#include "ultima/ultima8/kernel/process.h"
namespace Ultima {
namespace Ultima8 {
class AvatarDeathProcess : public Process {
public:
AvatarDeathProcess();
ENABLE_RUNTIME_CLASSTYPE()
void run() override;
bool loadData(Common::ReadStream *rs, uint32 version);
void saveData(Common::WriteStream *ws) override;
};
} // End of namespace Ultima8
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,76 @@
/* 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/avatar_gravity_process.h"
#include "ultima/ultima8/world/actors/avatar_mover_process.h"
#include "ultima/ultima8/world/actors/main_actor.h"
#include "ultima/ultima8/ultima8.h"
#include "ultima/ultima8/kernel/mouse.h"
#include "ultima/ultima8/world/get_object.h"
namespace Ultima {
namespace Ultima8 {
DEFINE_RUNTIME_CLASSTYPE_CODE(AvatarGravityProcess)
AvatarGravityProcess::AvatarGravityProcess()
: GravityProcess() {
}
AvatarGravityProcess::AvatarGravityProcess(MainActor *avatar, int gravity)
: GravityProcess(avatar, gravity) {
}
void AvatarGravityProcess::run() {
AvatarMoverProcess *amp = Ultima8Engine::get_instance()->getAvatarMoverProcess();
if (amp && amp->hasMovementFlags(AvatarMoverProcess::MOVE_ANY_DIRECTION)) {
// See if we can cling to a ledge
MainActor *avatar = getMainActor();
if (avatar->tryAnim(Animation::climb40, dir_current) == Animation::SUCCESS) {
// We can climb, so perform a hang animation
// CHECKME: do we need to perform any other checks?
if (avatar->getLastAnim() != Animation::hang)
avatar->doAnim(Animation::hang, dir_current);
return;
}
}
// fall normally
GravityProcess::run();
return;
}
void AvatarGravityProcess::saveData(Common::WriteStream *ws) {
GravityProcess::saveData(ws);
}
bool AvatarGravityProcess::loadData(Common::ReadStream *rs, uint32 version) {
if (!GravityProcess::loadData(rs, version)) return false;
return true;
}
} // End of namespace Ultima8
} // End of namespace Ultima

View File

@@ -0,0 +1,48 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef WORLD_ACTORS_AVATARGRAVITYPROCESS_H
#define WORLD_ACTORS_AVATARGRAVITYPROCESS_H
#include "ultima/ultima8/world/gravity_process.h"
namespace Ultima {
namespace Ultima8 {
class MainActor;
class AvatarGravityProcess : public GravityProcess {
public:
AvatarGravityProcess();
AvatarGravityProcess(MainActor *avatar, int gravity);
ENABLE_RUNTIME_CLASSTYPE()
void run() override;
bool loadData(Common::ReadStream *rs, uint32 version);
void saveData(Common::WriteStream *ws) override;
};
} // End of namespace Ultima8
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,381 @@
/* 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/avatar_mover_process.h"
#include "ultima/ultima8/world/actors/actor.h"
#include "ultima/ultima8/kernel/kernel.h"
#include "ultima/ultima8/world/actors/targeted_anim_process.h"
#include "ultima/ultima8/world/get_object.h"
#include "ultima/ultima8/misc/direction_util.h"
namespace Ultima {
namespace Ultima8 {
AvatarMoverProcess::AvatarMoverProcess() : Process(),
_lastAttack(0), _idleTime(0), _movementFlags(0) {
_type = 1; // CONSTANT! (type 1 = persistent)
}
AvatarMoverProcess::~AvatarMoverProcess() {
}
void AvatarMoverProcess::run() {
Actor *avatar = getControlledActor();
assert(avatar);
// busy, so don't move
if (avatar->isBusy()) {
_idleTime = 0;
return;
}
if (avatar->getLastAnim() == Animation::hang) {
handleHangingMode();
return;
}
// falling, so don't move
if (avatar->getGravityPID() != 0) {
Process *proc = Kernel::get_instance()->getProcess(avatar->getGravityPID());
if (!proc || !proc->is_active()) {
warning("FIXME: Removing stale gravity pid %d from Avatar.", avatar->getGravityPID());
avatar->setGravityPID(0);
} else {
_idleTime = 0;
return;
}
}
// not in fast area, don't move (can happen for some death sequences
// in Crusader)
if (!avatar->hasFlags(Item::FLG_FASTAREA))
return;
bool combatRun = avatar->hasActorFlags(Actor::ACT_COMBATRUN);
if (avatar->isInCombat() && !combatRun)
handleCombatMode();
else
handleNormalMode();
}
bool AvatarMoverProcess::checkTurn(Direction direction, bool moving) {
Actor *avatar = getControlledActor();
Direction curdir = avatar->getDir();
if (direction == curdir)
return false;
if (!moving) {
turnToDirection(direction);
return true;
}
// Do not turn if moving backward in combat stance
bool combat = avatar->isInCombat() && !avatar->hasActorFlags(Actor::ACT_COMBATRUN);
if (combat && Direction_Invert(direction) == curdir)
return false;
Animation::Sequence lastanim = avatar->getLastAnim();
// Check if we don't need to explicitly do a turn animation
if ((lastanim == Animation::walk || lastanim == Animation::run ||
lastanim == Animation::combatStand ||
(GAME_IS_CRUSADER && (lastanim == Animation::startRunSmallWeapon ||
lastanim == Animation::combatRunSmallWeapon))) &&
(ABS(direction - curdir) + 2) % 16 <= 4) {
return false;
}
if (lastanim == Animation::run) {
// slow down to a walk first
waitFor(avatar->doAnim(Animation::walk, curdir));
return true;
}
turnToDirection(direction);
return true;
}
void AvatarMoverProcess::turnToDirection(Direction direction) {
Actor *avatar = getControlledActor();
uint16 turnpid = avatar->turnTowardDir(direction);
if (turnpid)
waitFor(turnpid);
}
void AvatarMoverProcess::slowFromRun(Direction direction) {
Actor *avatar = getControlledActor();
ProcId walkpid = avatar->doAnim(Animation::walk, direction);
ProcId standpid = avatar->doAnimAfter(Animation::stand, direction, walkpid);
waitFor(standpid);
}
void AvatarMoverProcess::putAwayWeapon(Direction direction) {
Actor *avatar = getControlledActor();
ProcId anim1 = avatar->doAnim(Animation::unreadyWeapon, direction);
ProcId anim2 = avatar->doAnimAfter(Animation::stand, direction, anim1);
waitFor(anim2);
}
bool AvatarMoverProcess::standUpIfNeeded(Direction direction) {
Actor *avatar = getControlledActor();
Animation::Sequence lastanim = avatar->getLastAnim();
bool stasis = Ultima8Engine::get_instance()->isAvatarInStasis();
if (lastanim == Animation::die || lastanim == Animation::fallBackwards) {
if (!stasis) {
ProcId pid = avatar->doAnim(Animation::standUp, direction);
if (avatar->hasActorFlags(Actor::ACT_STUNNED)) {
avatar->clearActorFlag(Actor::ACT_STUNNED);
// Shake head twice
pid = avatar->doAnimAfter(Animation::shakeHead, direction, pid);
pid = avatar->doAnimAfter(Animation::shakeHead, direction, pid);
}
waitFor(pid);
}
return true;
}
return false;
}
void AvatarMoverProcess::getMovementFlagAxes(int &x, int &y) {
y = 0;
x = 0;
if (hasMovementFlags(MOVE_UP)) {
y++;
}
if (hasMovementFlags(MOVE_DOWN)) {
y--;
}
if (hasMovementFlags(MOVE_LEFT)) {
x--;
}
if (hasMovementFlags(MOVE_RIGHT)) {
x++;
}
}
Direction AvatarMoverProcess::getTurnDirForTurnFlags(Direction direction, DirectionMode dirmode) {
if (hasMovementFlags(MOVE_TURN_LEFT | MOVE_PENDING_TURN_LEFT)) {
direction = Direction_OneLeft(direction, dirmode);
}
if (hasMovementFlags(MOVE_TURN_RIGHT | MOVE_PENDING_TURN_RIGHT)) {
direction = Direction_OneRight(direction, dirmode);
}
return direction;
}
void AvatarMoverProcess::onMouseDown(int button, int32 mx, int32 my) {
int bid = 0;
switch (button) {
case Mouse::BUTTON_LEFT: {
bid = 0;
break;
}
case Mouse::BUTTON_RIGHT: {
bid = 1;
break;
}
default:
warning("Invalid MouseDown passed to AvatarMoverProcess");
break;
};
_mouseButton[bid]._lastDown = _mouseButton[bid]._curDown;
_mouseButton[bid]._curDown = g_system->getMillis();
_mouseButton[bid].setState(MBS_DOWN);
_mouseButton[bid].clearState(MBS_HANDLED);
}
void AvatarMoverProcess::onMouseUp(int button) {
int bid = 0;
if (button == Mouse::BUTTON_LEFT) {
bid = 0;
} else if (button == Mouse::BUTTON_RIGHT) {
bid = 1;
} else {
warning("Invalid MouseUp passed to AvatarMoverProcess");
}
_mouseButton[bid].clearState(MBS_DOWN);
}
bool AvatarMoverProcess::onActionDown(KeybindingAction action) {
bool handled = true;
switch (action) {
case ACTION_JUMP:
setMovementFlag(MOVE_JUMP);
break;
case ACTION_SHORT_JUMP:
setMovementFlag(MOVE_SHORT_JUMP);
break;
case ACTION_TURN_LEFT:
setMovementFlag(MOVE_TURN_LEFT);
break;
case ACTION_TURN_RIGHT:
setMovementFlag(MOVE_TURN_RIGHT);
break;
case ACTION_MOVE_FORWARD:
setMovementFlag(MOVE_FORWARD);
break;
case ACTION_MOVE_BACK:
setMovementFlag(MOVE_BACK);
break;
case ACTION_MOVE_UP:
setMovementFlag(MOVE_UP);
break;
case ACTION_MOVE_DOWN:
setMovementFlag(MOVE_DOWN);
break;
case ACTION_MOVE_LEFT:
setMovementFlag(MOVE_LEFT);
break;
case ACTION_MOVE_RIGHT:
setMovementFlag(MOVE_RIGHT);
break;
case ACTION_MOVE_RUN:
setMovementFlag(MOVE_RUN);
break;
case ACTION_MOVE_STEP:
setMovementFlag(MOVE_STEP);
break;
case ACTION_ATTACK:
setMovementFlag(MOVE_ATTACKING);
break;
case ACTION_STEP_LEFT:
setMovementFlag(MOVE_STEP_LEFT);
break;
case ACTION_STEP_RIGHT:
setMovementFlag(MOVE_STEP_RIGHT);
break;
case ACTION_STEP_FORWARD:
setMovementFlag(MOVE_STEP_FORWARD);
break;
case ACTION_STEP_BACK:
setMovementFlag(MOVE_STEP_BACK);
break;
case ACTION_ROLL_LEFT:
setMovementFlag(MOVE_ROLL_LEFT);
break;
case ACTION_ROLL_RIGHT:
setMovementFlag(MOVE_ROLL_RIGHT);
break;
case ACTION_TOGGLE_CROUCH:
setMovementFlag(MOVE_TOGGLE_CROUCH);
break;
default:
handled = false;
}
return handled;
}
bool AvatarMoverProcess::onActionUp(KeybindingAction action) {
bool handled = true;
switch (action) {
case ACTION_JUMP:
clearMovementFlag(MOVE_JUMP);
break;
case ACTION_SHORT_JUMP:
// Cleared when handled
break;
case ACTION_TURN_LEFT:
clearMovementFlag(MOVE_TURN_LEFT);
break;
case ACTION_TURN_RIGHT:
clearMovementFlag(MOVE_TURN_RIGHT);
break;
case ACTION_MOVE_FORWARD:
clearMovementFlag(MOVE_FORWARD);
break;
case ACTION_MOVE_BACK:
// Clear both back and forward as avatar turns then moves forward when not in combat
clearMovementFlag(MOVE_BACK | MOVE_FORWARD);
break;
case ACTION_MOVE_UP:
clearMovementFlag(MOVE_UP);
break;
case ACTION_MOVE_DOWN:
clearMovementFlag(MOVE_DOWN);
break;
case ACTION_MOVE_LEFT:
clearMovementFlag(MOVE_LEFT);
break;
case ACTION_MOVE_RIGHT:
clearMovementFlag(MOVE_RIGHT);
break;
case ACTION_MOVE_RUN:
clearMovementFlag(MOVE_RUN);
break;
case ACTION_MOVE_STEP:
clearMovementFlag(MOVE_STEP);
break;
case ACTION_ATTACK:
clearMovementFlag(MOVE_ATTACKING);
break;
case ACTION_STEP_LEFT:
// Cleared when handled
break;
case ACTION_STEP_RIGHT:
// Cleared when handled
break;
case ACTION_STEP_FORWARD:
// Cleared when handled
break;
case ACTION_STEP_BACK:
// Cleared when handled
break;
case ACTION_ROLL_LEFT:
// Cleared when handled
break;
case ACTION_ROLL_RIGHT:
// Cleared when handled
break;
case ACTION_TOGGLE_CROUCH:
// Cleared when handled
break;
default:
handled = false;
}
return handled;
}
void AvatarMoverProcess::saveData(Common::WriteStream *ws) {
Process::saveData(ws);
ws->writeUint32LE(_lastAttack);
ws->writeUint32LE(_idleTime);
}
bool AvatarMoverProcess::loadData(Common::ReadStream *rs, uint32 version) {
if (!Process::loadData(rs, version)) return false;
_lastAttack = rs->readUint32LE();
_idleTime = rs->readUint32LE();
return true;
}
} // End of namespace Ultima8
} // End of namespace Ultima

View File

@@ -0,0 +1,145 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef WORLD_ACTORS_AVATARMOVERPROCESS_H
#define WORLD_ACTORS_AVATARMOVERPROCESS_H
#include "ultima/ultima8/metaengine.h"
#include "ultima/ultima8/kernel/process.h"
#include "ultima/ultima8/kernel/mouse.h"
#include "ultima/ultima8/world/actors/animation.h"
namespace Ultima {
namespace Ultima8 {
/**
* Base class for mover processes that decide which animation to
* do next based on last anim and keyboard / mouse / etc.
*/
class AvatarMoverProcess : public Process {
public:
AvatarMoverProcess();
~AvatarMoverProcess() override;
void run() override;
void resetIdleTime() {
_idleTime = 0;
}
bool loadData(Common::ReadStream *rs, uint32 version);
void saveData(Common::WriteStream *ws) override;
bool hasMovementFlags(uint32 flags) const {
return (_movementFlags & flags) != 0;
}
void setMovementFlag(uint32 mask) {
_movementFlags |= mask;
}
virtual void clearMovementFlag(uint32 mask) {
_movementFlags &= ~mask;
}
void resetMovementFlags() {
_movementFlags = 0;
}
void onMouseDown(int button, int32 mx, int32 my);
void onMouseUp(int button);
// Return true if handled, false if not.
bool onActionDown(KeybindingAction action);
bool onActionUp(KeybindingAction action);
enum MovementFlags {
MOVE_MOUSE_DIRECTION = 0x001,
MOVE_RUN = 0x0002,
MOVE_STEP = 0x0004, // also side-steps in crusader
MOVE_JUMP = 0x0008, // used for roll in crusader (when combined with left/right), and crouch (when combined with back)
// Tank controls
MOVE_TURN_LEFT = 0x0010,
MOVE_TURN_RIGHT = 0x0020,
MOVE_FORWARD = 0x0040,
MOVE_BACK = 0x0080,
// Directional controls
MOVE_LEFT = 0x0100,
MOVE_RIGHT = 0x0200,
MOVE_UP = 0x0400,
MOVE_DOWN = 0x0800,
// Firing weapon (Crusader only)
MOVE_ATTACKING = 0x1000,
// Pending turn (Crusader only)
MOVE_PENDING_TURN_LEFT = 0x2000,
MOVE_PENDING_TURN_RIGHT = 0x4000,
// Single-button moves (Crusader only)
MOVE_SHORT_JUMP = 0x008000,
MOVE_ROLL_LEFT = 0x010000,
MOVE_ROLL_RIGHT = 0x020000,
MOVE_STEP_LEFT = 0x040000,
MOVE_STEP_RIGHT = 0x080000,
MOVE_STEP_FORWARD = 0x100000,
MOVE_STEP_BACK = 0x200000,
MOVE_TOGGLE_CROUCH = 0x400000,
MOVE_ANY_DIRECTION = MOVE_MOUSE_DIRECTION | MOVE_FORWARD | MOVE_BACK | MOVE_LEFT | MOVE_RIGHT | MOVE_UP | MOVE_DOWN
};
protected:
virtual void handleHangingMode() = 0;
virtual void handleCombatMode() = 0;
virtual void handleNormalMode() = 0;
void turnToDirection(Direction direction);
bool checkTurn(Direction direction, bool moving);
// Walk and then stop in the given direction
void slowFromRun(Direction direction);
// Stow weapon and stand
void putAwayWeapon(Direction direction);
// If the last animation was falling or die but we're not dead, stand up!
// return true if we are waiting to get up
bool standUpIfNeeded(Direction direction);
// Get directions based on what movement flags are set, eg y=+1 for up, x=-1 for left.
void getMovementFlagAxes(int &x, int &y);
// Adjust the direction based on the current turn flags
Direction getTurnDirForTurnFlags(Direction direction, DirectionMode dirmode);
// attack speed limiting
uint32 _lastAttack;
// shake head when idle
uint32 _idleTime;
MButton _mouseButton[2];
uint32 _movementFlags;
};
} // End of namespace Ultima8
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,94 @@
/* 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/battery_charger_process.h"
#include "ultima/ultima8/world/actors/main_actor.h"
#include "ultima/ultima8/kernel/kernel.h"
#include "ultima/ultima8/world/get_object.h"
#include "ultima/ultima8/world/world.h"
#include "ultima/ultima8/audio/audio_process.h"
namespace Ultima {
namespace Ultima8 {
DEFINE_RUNTIME_CLASSTYPE_CODE(BatteryChargerProcess)
// These SFX IDs are the same in both No Regret and No Remorse.
static const uint16 CHARGE_START_SFX = 0xa4;
static const uint16 CHARGE_GOING_SFX = 0x10b;
BatteryChargerProcess::BatteryChargerProcess() : Process() {
MainActor *avatar = dynamic_cast<MainActor *>(getActor(World::get_instance()->getControlledNPCNum()));
if (!avatar) {
_itemNum = 0;
_targetMaxEnergy = 0;
} else {
_itemNum = avatar->getObjId();
_targetMaxEnergy = avatar->getMaxEnergy();
AudioProcess *audio = AudioProcess::get_instance();
if (audio) {
audio->playSFX(CHARGE_START_SFX, 0x80, _itemNum, 1, false);
}
}
_type = 0x254; // CONSTANT!
}
void BatteryChargerProcess::run() {
MainActor *avatar = dynamic_cast<MainActor *>(getActor(World::get_instance()->getControlledNPCNum()));
AudioProcess *audio = AudioProcess::get_instance();
if (!avatar || avatar->isDead() || avatar->getMana() >= _targetMaxEnergy) {
// dead or finished healing or switched to robot
terminate();
if (audio)
audio->stopSFX(CHARGE_START_SFX, _itemNum);
return;
}
if (!audio->isSFXPlayingForObject(CHARGE_GOING_SFX, _itemNum))
audio->playSFX(CHARGE_GOING_SFX, 0x80, _itemNum, 1);
uint16 newEnergy = avatar->getMana() + 25;
if (newEnergy > _targetMaxEnergy)
newEnergy = _targetMaxEnergy;
avatar->setMana(newEnergy);
}
uint32 BatteryChargerProcess::I_create(const uint8 *args, unsigned int /*argsize*/) {
return Kernel::get_instance()->addProcess(new BatteryChargerProcess());
}
void BatteryChargerProcess::saveData(Common::WriteStream *ws) {
Process::saveData(ws);
ws->writeUint16LE(_targetMaxEnergy);
}
bool BatteryChargerProcess::loadData(Common::ReadStream *rs, uint32 version) {
if (!Process::loadData(rs, version)) return false;
_targetMaxEnergy = rs->readUint16LE();
return true;
}
} // End of namespace Ultima8
} // End of namespace Ultima

View File

@@ -0,0 +1,51 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef WORLD_ACTORS_BATTERY_CHARGER_PROCESS_H
#define WORLD_ACTORS_BATTERY_CHARGER_PROCESS_H
#include "ultima/ultima8/kernel/process.h"
#include "ultima/ultima8/usecode/intrinsics.h"
namespace Ultima {
namespace Ultima8 {
class BatteryChargerProcess : public Process {
public:
BatteryChargerProcess();
ENABLE_RUNTIME_CLASSTYPE()
void run() override;
INTRINSIC(I_create);
bool loadData(Common::ReadStream *rs, uint32 version);
void saveData(Common::WriteStream *ws) override;
private:
uint16 _targetMaxEnergy;
};
} // End of namespace Ultima8
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,73 @@
/* 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/clear_feign_death_process.h"
#include "ultima/ultima8/world/actors/actor.h"
#include "ultima/ultima8/audio/audio_process.h"
#include "ultima/ultima8/world/get_object.h"
namespace Ultima {
namespace Ultima8 {
DEFINE_RUNTIME_CLASSTYPE_CODE(ClearFeignDeathProcess)
ClearFeignDeathProcess::ClearFeignDeathProcess() : Process() {
}
ClearFeignDeathProcess::ClearFeignDeathProcess(Actor *actor) {
assert(actor);
_itemNum = actor->getObjId();
_type = 0x243; // constant !
}
void ClearFeignDeathProcess::run() {
Actor *a = getActor(_itemNum);
if (!a) {
// actor gone?
terminate();
return;
}
a->clearActorFlag(Actor::ACT_FEIGNDEATH);
AudioProcess *audioproc = AudioProcess::get_instance();
if (audioproc) audioproc->playSFX(59, 0x60, _itemNum, 0);
// done
terminate();
}
void ClearFeignDeathProcess::saveData(Common::WriteStream *ws) {
Process::saveData(ws);
}
bool ClearFeignDeathProcess::loadData(Common::ReadStream *rs, uint32 version) {
if (!Process::loadData(rs, version)) return false;
return true;
}
} // End of namespace Ultima8
} // End of namespace Ultima

View File

@@ -0,0 +1,48 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef WORLD_ACTORS_CLEARFEIGNDEATHPROCESS_H
#define WORLD_ACTORS_CLEARFEIGNDEATHPROCESS_H
#include "ultima/ultima8/kernel/process.h"
namespace Ultima {
namespace Ultima8 {
class Actor;
class ClearFeignDeathProcess : public Process {
public:
ClearFeignDeathProcess();
ClearFeignDeathProcess(Actor *actor);
ENABLE_RUNTIME_CLASSTYPE()
void run() override;
bool loadData(Common::ReadStream *rs, uint32 version);
void saveData(Common::WriteStream *ws) override;
};
} // End of namespace Ultima8
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,47 @@
/* 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/combat_dat.h"
namespace Ultima {
namespace Ultima8 {
CombatDat::CombatDat(Common::SeekableReadStream &rs) {
char namebuf[17] = {0};
rs.read(namebuf, 16);
_name.assign(namebuf);
for (int i = 0; i < 4; i++)
_offsets[i] = rs.readUint16LE();
int datasize = rs.size();
rs.seek(0, SEEK_SET);
_data = new uint8[datasize];
_dataLen = rs.read(_data, datasize);
}
CombatDat::~CombatDat() {
delete [] _data;
}
} // End of namespace Ultima8
} // End of namespace Ultima

View File

@@ -0,0 +1,75 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef WORLD_ACTORS_COMBAT_DAT_H
#define WORLD_ACTORS_COMBAT_DAT_H
#include "common/stream.h"
#include "ultima/shared/std/string.h"
namespace Ultima {
namespace Ultima8 {
/**
* A single entry in the Crusader combat.dat flex. The files consist of 3 parts:
* 1. human-readable name (zero-padded 16 bytes)
* 2. offset table (10x2-byte offsets, in practice only the first 2 offsets are ever used)
* 3. tactic blocks starting at the offsets given in the offset (in practice only 2 blocks are used)
*
* The tactic blocks are a sequence of opcodes of things the NPC should
* do - eg, turn towards direction X.
*/
class CombatDat {
public:
CombatDat(Common::SeekableReadStream &rs);
~CombatDat();
const Std::string &getName() const {
return _name;
};
const uint8 *getData() const {
return _data;
}
uint16 getOffset(int block) const {
assert(block < ARRAYSIZE(_offsets));
return _offsets[block];
}
uint16 getDataLen() const {
return _dataLen;
}
private:
Std::string _name;
uint16 _offsets[4];
uint8 *_data;
uint16 _dataLen;
};
} // End of namespace Ultima8
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,329 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "ultima/ultima.h"
#include "ultima/ultima8/ultima8.h"
#include "ultima/ultima8/world/actors/combat_process.h"
#include "ultima/ultima8/world/actors/actor.h"
#include "ultima/ultima8/world/current_map.h"
#include "ultima/ultima8/world/world.h"
#include "ultima/ultima8/usecode/uc_list.h"
#include "ultima/ultima8/world/loop_script.h"
#include "ultima/ultima8/world/actors/animation_tracker.h"
#include "ultima/ultima8/kernel/kernel.h"
#include "ultima/ultima8/kernel/delay_process.h"
#include "ultima/ultima8/world/actors/pathfinder_process.h"
#include "ultima/ultima8/world/get_object.h"
#include "ultima/ultima8/world/actors/loiter_process.h"
#include "ultima/ultima8/world/actors/ambush_process.h"
namespace Ultima {
namespace Ultima8 {
DEFINE_RUNTIME_CLASSTYPE_CODE(CombatProcess)
CombatProcess::CombatProcess() : Process(), _target(0), _fixedTarget(0), _combatMode(CM_WAITING) {
}
CombatProcess::CombatProcess(Actor *actor) : _target(0), _fixedTarget(0), _combatMode(CM_WAITING) {
assert(actor);
_itemNum = actor->getObjId();
_type = 0x00F2; // CONSTANT !
}
void CombatProcess::terminate() {
Actor *a = getActor(_itemNum);
if (a)
a->clearActorFlag(Actor::ACT_INCOMBAT);
Process::terminate();
}
void CombatProcess::run() {
// TODO: handle invisible targets.
// Monsters should attack you only if you are standing directly
// next to them, or maybe only when you are attacking them.
// They should not try to approach.
Actor *a = getActor(_itemNum);
if (!a || !a->hasFlags(Item::FLG_FASTAREA))
return;
Actor *t = getActor(_target);
if (!t || !isValidTarget(t)) {
// no _target? try to find one
_target = seekTarget();
if (!_target) {
waitForTarget();
return;
}
debugC(kDebugActor, "[COMBAT %u] _target found: %u", _itemNum, _target);
_combatMode = CM_WAITING;
}
Direction targetdir = getTargetDirection();
if (a->getDir() != targetdir) {
turnToDirection(targetdir);
return;
}
if (inAttackRange()) {
_combatMode = CM_ATTACKING;
debugC(kDebugActor, "[COMBAT %u] _target (%u) in range", _itemNum, _target);
Common::RandomSource &rs = Ultima8Engine::get_instance()->getRandomSource();
bool hasidle1 = a->hasAnim(Animation::idle1);
bool hasidle2 = a->hasAnim(Animation::idle2);
if ((hasidle1 || hasidle2) && rs.getRandomNumber(4) == 0) {
// every once in a while, act threatening instead of attacking
// TODO: maybe make frequency depend on monster type
Animation::Sequence idleanim;
if (!hasidle1) {
idleanim = Animation::idle2;
} else if (!hasidle2) {
idleanim = Animation::idle1;
} else {
if (rs.getRandomBit())
idleanim = Animation::idle1;
else
idleanim = Animation::idle2;
}
uint16 idlepid = a->doAnim(idleanim, dir_current);
waitFor(idlepid);
} else {
// attack
ProcId attackanim = a->doAnim(Animation::attack, dir_current);
// wait a while, depending on dexterity, before attacking again
int dex = a->getDex();
if (dex < 25) {
int recoverytime = 3 * (25 - dex);
Process *waitproc = new DelayProcess(recoverytime);
ProcId waitpid = Kernel::get_instance()->addProcess(waitproc);
waitproc->waitFor(attackanim);
waitFor(waitpid);
} else {
waitFor(attackanim);
}
}
return;
} else if (_combatMode != CM_PATHFINDING) {
// not in range? See if we can get in range
Process *pfproc = new PathfinderProcess(a, _target, true);
waitFor(Kernel::get_instance()->addProcess(pfproc));
_combatMode = CM_PATHFINDING;
return;
}
_combatMode = CM_WAITING;
waitForTarget();
}
ObjId CombatProcess::getTarget() {
const Actor *t = getActor(_target);
if (!t || !isValidTarget(t))
_target = 0;
return _target;
}
void CombatProcess::setTarget(ObjId newtarget) {
if (_fixedTarget == 0) {
_fixedTarget = newtarget; // want to prevent seekTarget from changing it
}
_target = newtarget;
}
bool CombatProcess::isValidTarget(const Actor *target) const {
assert(target);
const Actor *a = getActor(_itemNum);
if (!a) return false; // uh oh
// don't target_ self
if (target == a) return false;
// not in the fastarea
if (!target->hasFlags(Item::FLG_FASTAREA)) return false;
// dead actors don't make good targets
if (target->isDead()) return false;
// feign death only works on undead and demons
if (target->hasActorFlags(Actor::ACT_FEIGNDEATH)) {
if ((a->getDefenseType() & WeaponInfo::DMG_UNDEAD) ||
(a->getShape() == 96)) return false; // CONSTANT!
}
// otherwise, ok
return true;
}
bool CombatProcess::isEnemy(const Actor *target) const {
assert(target);
const Actor *a = getActor(_itemNum);
if (!a) return false; // uh oh
return ((a->getEnemyAlignment() & target->getAlignment()) != 0);
}
ObjId CombatProcess::seekTarget() {
Actor *a = getActor(_itemNum);
if (!a) return 0; // uh oh
if (_fixedTarget) {
Actor *t = getActor(_fixedTarget);
if (t && isValidTarget(t))
return _fixedTarget; // no need to search
}
UCList itemlist(2);
LOOPSCRIPT(script, LS_TOKEN_TRUE);
CurrentMap *cm = World::get_instance()->getCurrentMap();
cm->areaSearch(&itemlist, script, sizeof(script), a, 768, false);
for (unsigned int i = 0; i < itemlist.getSize(); ++i) {
const Actor *t = getActor(itemlist.getuint16(i));
if (t && isValidTarget(t) && isEnemy(t)) {
// found _target
return itemlist.getuint16(i);
}
}
// no suitable targets
return 0;
}
Direction CombatProcess::getTargetDirection() const {
const Actor *a = getActor(_itemNum);
const Actor *t = getActor(_target);
if (!a || !t)
return dir_north; // shouldn't happen
return a->getDirToItemCentre(*t);
}
void CombatProcess::turnToDirection(Direction direction) {
Actor *a = getActor(_itemNum);
if (!a)
return;
assert(a->isInCombat());
uint16 waitpid = a->turnTowardDir(direction);
if (waitpid)
waitFor(waitpid);
}
bool CombatProcess::inAttackRange() const {
const Actor *a = getActor(_itemNum);
if (!a)
return false; // shouldn't happen
const ShapeInfo *shapeinfo = a->getShapeInfo();
const MonsterInfo *mi = nullptr;
if (shapeinfo) mi = shapeinfo->_monsterInfo;
if (mi && mi->_ranged)
return true; // ranged attacks (ghost's fireball) always in range
AnimationTracker tracker;
if (!tracker.init(a, Animation::attack, a->getDir(), 0))
return false;
while (tracker.step()) {
if (tracker.hitSomething()) break;
}
ObjId hit = tracker.hitSomething();
if (hit == _target) return true;
return false;
}
void CombatProcess::waitForTarget() {
Actor *a = getActor(_itemNum);
if (!a)
return; // shouldn't happen
const ShapeInfo *shapeinfo = a->getShapeInfo();
const MonsterInfo *mi = nullptr;
if (shapeinfo) mi = shapeinfo->_monsterInfo;
Common::RandomSource &rs = Ultima8Engine::get_instance()->getRandomSource();
if (mi && mi->_shifter && a->getMapNum() != 43 && rs.getRandomBit()) {
// changelings (except the ones at the U8 endgame pentagram)
// shift into a tree if nobody is around
ProcId shift1pid = a->doAnim(static_cast<Animation::Sequence>(20), dir_current);
Process *ambushproc = new AmbushProcess(a);
ProcId ambushpid = Kernel::get_instance()->addProcess(ambushproc);
ProcId shift2pid = a->doAnim(static_cast<Animation::Sequence>(21), dir_current);
Process *shift2proc = Kernel::get_instance()->getProcess(shift2pid);
ambushproc->waitFor(shift1pid);
shift2proc->waitFor(ambushpid);
waitFor(shift2proc);
} else {
waitFor(Kernel::get_instance()->addProcess(new LoiterProcess(a, 1)));
}
}
Common::String CombatProcess::dumpInfo() const {
return Process::dumpInfo() +
Common::String::format(", target: %u", _target);
}
void CombatProcess::saveData(Common::WriteStream *ws) {
Process::saveData(ws);
ws->writeUint16LE(_target);
ws->writeUint16LE(_fixedTarget);
ws->writeByte(static_cast<uint8>(_combatMode));
}
bool CombatProcess::loadData(Common::ReadStream *rs, uint32 version) {
if (!Process::loadData(rs, version)) return false;
_target = rs->readUint16LE();
_fixedTarget = rs->readUint16LE();
_combatMode = static_cast<CombatMode>(rs->readByte());
return true;
}
} // End of namespace Ultima8
} // End of namespace Ultima

View File

@@ -0,0 +1,75 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef WORLD_ACTORS_COMBATPROCESS_H
#define WORLD_ACTORS_COMBATPROCESS_H
#include "ultima/ultima8/kernel/process.h"
#include "ultima/ultima8/misc/direction.h"
namespace Ultima {
namespace Ultima8 {
class Actor;
class CombatProcess : public Process {
public:
CombatProcess();
CombatProcess(Actor *actor);
ENABLE_RUNTIME_CLASSTYPE()
void run() override;
void terminate() override;
ObjId getTarget();
void setTarget(ObjId target);
ObjId seekTarget();
Common::String dumpInfo() const override;
bool loadData(Common::ReadStream *rs, uint32 version);
void saveData(Common::WriteStream *ws) override;
protected:
bool isValidTarget(const Actor *target) const;
bool isEnemy(const Actor *target) const;
bool inAttackRange() const;
Direction getTargetDirection() const;
void turnToDirection(Direction direction);
void waitForTarget();
ObjId _target;
ObjId _fixedTarget;
enum CombatMode {
CM_WAITING = 0,
CM_PATHFINDING,
CM_ATTACKING
} _combatMode;
};
} // End of namespace Ultima8
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,737 @@
/* 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/cru_avatar_mover_process.h"
#include "ultima/ultima8/world/actors/main_actor.h"
#include "ultima/ultima8/kernel/kernel.h"
#include "ultima/ultima8/world/actors/actor_anim_process.h"
#include "ultima/ultima8/world/get_object.h"
#include "ultima/ultima8/world/current_map.h"
#include "ultima/ultima8/world/world.h"
#include "ultima/ultima8/misc/direction_util.h"
#include "ultima/ultima8/audio/audio_process.h"
#include "ultima/ultima8/kernel/delay_process.h"
namespace Ultima {
namespace Ultima8 {
DEFINE_RUNTIME_CLASSTYPE_CODE(CruAvatarMoverProcess)
static const int REBEL_BASE_MAP = 40;
CruAvatarMoverProcess::CruAvatarMoverProcess() : AvatarMoverProcess(),
_avatarAngle(-1), _SGA1Loaded(false), _nextFireTick(0), _lastNPCAlertTick(0) {
}
CruAvatarMoverProcess::~CruAvatarMoverProcess() {
}
static bool _isAnimRunningWalking(Animation::Sequence anim) {
return (anim == Animation::run || anim == Animation::combatRunSmallWeapon ||
anim == Animation::walk);
}
void CruAvatarMoverProcess::run() {
// Even when we are not doing anything (because we're waiting for an anim)
// we check if the combat angle needs updating - this keeps it smooth.
const Actor *avatar = getControlledActor();
// Controlled actor may have gone
if (!avatar)
return;
// When in combat and not running, update the angle.
// Otherwise, angle is kept as -1 and direction is just actor dir.
if (avatar->isInCombat() && (avatar->getLastAnim() != Animation::run)) {
if (_avatarAngle < 0) {
_avatarAngle = Direction_ToCentidegrees(avatar->getDir());
}
if (!hasMovementFlags(MOVE_FORWARD | MOVE_JUMP | MOVE_STEP)) {
// See comment on _avatarAngle in header about these constants
if (hasMovementFlags(MOVE_TURN_LEFT)) {
if (hasMovementFlags(MOVE_RUN))
_avatarAngle -= 375;
else
_avatarAngle -= 150;
if (_avatarAngle < 0)
_avatarAngle += 36000;
}
if (hasMovementFlags(MOVE_TURN_RIGHT)) {
if (hasMovementFlags(MOVE_RUN))
_avatarAngle += 375;
else
_avatarAngle += 150;
_avatarAngle = _avatarAngle % 36000;
}
}
} else {
_avatarAngle = -1;
// Check for a turn request while running or walking. This only happens
// once per arrow keydown, so clear the flag.
if (_isAnimRunningWalking(avatar->getLastAnim())
&& hasMovementFlags(MOVE_FORWARD)
&& (hasMovementFlags(MOVE_TURN_LEFT) || hasMovementFlags(MOVE_TURN_RIGHT) ||
hasMovementFlags(MOVE_PENDING_TURN_LEFT) || hasMovementFlags(MOVE_PENDING_TURN_RIGHT))) {
Kernel *kernel = Kernel::get_instance();
// Stop the current animation and turn now.
kernel->killProcesses(avatar->getObjId(), ActorAnimProcess::ACTOR_ANIM_PROC_TYPE, true);
Direction curdir = avatar->getDir();
Animation::Sequence anim = hasMovementFlags(MOVE_RUN) ? Animation::run : Animation::walk;
DirectionMode dirmode = avatar->animDirMode(anim);
Direction dir = getTurnDirForTurnFlags(curdir, dirmode);
clearMovementFlag(MOVE_TURN_LEFT | MOVE_TURN_RIGHT |
MOVE_PENDING_TURN_LEFT | MOVE_PENDING_TURN_RIGHT);
step(anim, dir);
return;
}
}
// Pending turns shouldn't stick around.
clearMovementFlag(MOVE_PENDING_TURN_LEFT | MOVE_PENDING_TURN_RIGHT);
// Now do the regular process
AvatarMoverProcess::run();
}
void CruAvatarMoverProcess::clearMovementFlag(uint32 mask) {
// Set a pending turn if we haven't already cleared the turn
if ((mask & MOVE_TURN_LEFT) && hasMovementFlags(MOVE_TURN_LEFT))
setMovementFlag(MOVE_PENDING_TURN_LEFT);
else if ((mask & MOVE_TURN_RIGHT) && hasMovementFlags(MOVE_TURN_RIGHT))
setMovementFlag(MOVE_PENDING_TURN_RIGHT);
AvatarMoverProcess::clearMovementFlag(mask);
}
void CruAvatarMoverProcess::handleHangingMode() {
// No hanging in crusader, this shouldn't happen?
assert(false);
}
static bool _isAnimRunningJumping(Animation::Sequence anim) {
return (anim == Animation::run || anim == Animation::combatRunSmallWeapon ||
anim == Animation::combatRunLargeWeapon || anim == Animation::jumpForward);
}
static bool _isAnimStartRunning(Animation::Sequence anim) {
return (anim == Animation::startRun || anim == Animation::startRunSmallWeapon /*||
// don't test this as it overlaps with kneel :(
anim == Animation::startRunLargeWeapon*/);
}
bool CruAvatarMoverProcess::checkOneShotMove(Direction direction) {
Actor *avatar = getControlledActor();
MainActor *mainactor = dynamic_cast<MainActor *>(avatar);
static const MovementFlags oneShotFlags[] = {
MOVE_ROLL_LEFT, MOVE_ROLL_RIGHT,
MOVE_STEP_LEFT, MOVE_STEP_RIGHT,
MOVE_STEP_FORWARD, MOVE_STEP_BACK,
MOVE_SHORT_JUMP, MOVE_TOGGLE_CROUCH
};
static const Animation::Sequence oneShotAnims[] = {
Animation::combatRollLeft, Animation::combatRollRight,
Animation::slideLeft, Animation::slideRight,
Animation::advance, Animation::retreat,
Animation::jumpForward, Animation::kneelStartCru
};
static const Animation::Sequence oneShotKneelingAnims[] = {
Animation::kneelCombatRollLeft, Animation::kneelCombatRollRight,
Animation::slideLeft, Animation::slideRight,
Animation::kneelingAdvance, Animation::kneelingRetreat,
Animation::jumpForward, Animation::kneelEndCru
};
for (int i = 0; i < ARRAYSIZE(oneShotFlags); i++) {
if (hasMovementFlags(oneShotFlags[i])) {
Animation::Sequence anim = (avatar->isKneeling() ?
oneShotKneelingAnims[i] : oneShotAnims[i]);
// All the animations should finish with gun drawn, *except*
// jump which should finish with gun stowed. For other cases we should
// toggle.
bool incombat = avatar->isInCombat();
bool isjump = (anim == Animation::jumpForward);
if (mainactor && (incombat == isjump)) {
mainactor->toggleInCombat();
}
clearMovementFlag(oneShotFlags[i]);
if (anim == Animation::advance || anim == Animation::retreat ||
anim == Animation::kneelingAdvance || anim == Animation::kneelingRetreat) {
step(anim, direction);
} else {
avatar->doAnim(anim, direction);
}
return true;
}
}
return false;
}
void CruAvatarMoverProcess::handleCombatMode() {
Actor *avatar = getControlledActor();
MainActor *mainactor = dynamic_cast<MainActor *>(avatar);
const Animation::Sequence lastanim = avatar->getLastAnim();
Direction direction = (_avatarAngle >= 0 ? Direction_FromCentidegrees(_avatarAngle) : avatar->getDir());
const Direction curdir = avatar->getDir();
const bool stasis = Ultima8Engine::get_instance()->isAvatarInStasis();
if (avatar->getMapNum() == REBEL_BASE_MAP) {
avatar->clearInCombat();
return;
}
// never idle when in combat
_idleTime = 0;
if (stasis)
return;
if (checkOneShotMove(direction))
return;
if (hasMovementFlags(MOVE_FORWARD)) {
Animation::Sequence nextanim;
if (hasMovementFlags(MOVE_STEP)) {
nextanim = avatar->isKneeling() ?
Animation::kneelingAdvance : Animation::advance;
} else if (hasMovementFlags(MOVE_RUN) && avatar->hasAnim(Animation::combatRunSmallWeapon)) {
// Take a step before running
if (lastanim == Animation::walk || _isAnimRunningJumping(lastanim) || _isAnimStartRunning(lastanim))
nextanim = Animation::combatRunSmallWeapon;
else
nextanim = Animation::startRunSmallWeapon;
} else if (hasMovementFlags(MOVE_JUMP) && avatar->hasAnim(Animation::jumpForward)) {
if (lastanim == Animation::walk || lastanim == Animation::run || lastanim == Animation::combatRunSmallWeapon)
nextanim = Animation::jumpForward;
else
nextanim = Animation::jump;
// Jump always ends out of combat
avatar->clearInCombat();
} else if (avatar->isKneeling()) {
avatar->doAnim(Animation::kneelEndCru, direction);
avatar->clearActorFlag(Actor::ACT_KNEELING);
return;
} else {
// moving forward from combat stows weapon
nextanim = Animation::walk;
if (mainactor)
mainactor->toggleInCombat();
}
// Ensure the dir we are about to use is valid
if (avatar->animDirMode(nextanim) == dirmode_8dirs)
direction = static_cast<Direction>(direction - (static_cast<uint32>(direction) % 2));
// don't check weapon here, Avatar can go straight from drawn-weapon to
// walking forward.
step(nextanim, direction);
return;
} else if (hasMovementFlags(MOVE_BACK)) {
Animation::Sequence nextanim;
if (hasMovementFlags(MOVE_JUMP)) {
if (!avatar->isKneeling() && avatar->hasAnim(Animation::kneelStartCru)) {
nextanim = Animation::kneelStartCru;
avatar->setActorFlag(Actor::ACT_KNEELING);
} else {
// Do nothing if already kneeling
return;
}
} else {
nextanim = Animation::retreat;
}
step(nextanim, direction);
return;
} else if (hasMovementFlags(MOVE_STEP)) {
if (avatar->isKneeling()) {
avatar->doAnim(Animation::kneelEndCru, direction);
return;
} else {
if (hasMovementFlags(MOVE_TURN_LEFT)) {
avatar->doAnim(Animation::slideLeft, direction);
return;
} else if (hasMovementFlags(MOVE_TURN_RIGHT)) {
avatar->doAnim(Animation::slideRight, direction);
return;
}
}
} else if (hasMovementFlags(MOVE_JUMP)) {
if (hasMovementFlags(MOVE_TURN_LEFT)) {
if (avatar->isKneeling())
avatar->doAnim(Animation::kneelCombatRollLeft, direction);
else
avatar->doAnim(Animation::combatRollLeft, direction);
return;
} else if (hasMovementFlags(MOVE_TURN_RIGHT)) {
if (avatar->isKneeling())
avatar->doAnim(Animation::kneelCombatRollRight, direction);
else
avatar->doAnim(Animation::combatRollRight, direction);
return;
}
}
int x, y;
getMovementFlagAxes(x, y);
if (x != 0 || y != 0) {
Direction nextdir = (_avatarAngle >= 0 ? Direction_FromCentidegrees(_avatarAngle) : avatar->getDir());
if (checkTurn(nextdir, true))
return;
Animation::Sequence nextanim = Animation::combatStand;
if ((lastanim == Animation::run || lastanim == Animation::combatRunSmallWeapon) && !hasMovementFlags(MOVE_RUN)) {
// want to go back to combat mode from run
nextanim = Animation::stopRunningAndDrawSmallWeapon;
} else if (hasMovementFlags(MOVE_BACK)) {
nextanim = Animation::retreat;
nextdir = Direction_Invert(direction);
}
if (hasMovementFlags(MOVE_RUN)) {
nextanim = Animation::combatRunSmallWeapon;
}
Animation::Sequence wpnanim = Animation::checkWeapon(nextanim, lastanim);
step(wpnanim, nextdir);
return;
}
Animation::Sequence idleanim = avatar->isKneeling() ?
Animation::kneel : Animation::combatStand;
if (curdir != direction) {
// Slight hack: don't "wait" for this - we want to keep turning smooth,
// and the process will not do anything else if an anim is active, so
// it's safe.
avatar->doAnim(idleanim, direction);
return;
}
if (hasMovementFlags(MOVE_ATTACKING) && !hasMovementFlags(MOVE_FORWARD | MOVE_BACK)) {
tryAttack();
return;
}
if (_isAnimRunningJumping(lastanim) || _isAnimStartRunning(idleanim)) {
idleanim = Animation::stopRunningAndDrawSmallWeapon;
}
// Not doing anything in particular? stand.
if (lastanim != idleanim) {
Animation::Sequence nextanim = Animation::checkWeapon(idleanim, lastanim);
waitFor(avatar->doAnim(nextanim, direction));
}
}
void CruAvatarMoverProcess::handleNormalMode() {
Actor *avatar = getControlledActor();
MainActor *mainactor = dynamic_cast<MainActor *>(avatar);
const Animation::Sequence lastanim = avatar->getLastAnim();
Direction direction = avatar->getDir();
const bool stasis = Ultima8Engine::get_instance()->isAvatarInStasis();
const bool rebelBase = (avatar->getMapNum() == REBEL_BASE_MAP);
if (!rebelBase && hasMovementFlags(MOVE_STEP | MOVE_JUMP) && hasMovementFlags(MOVE_ANY_DIRECTION | MOVE_TURN_LEFT | MOVE_TURN_RIGHT)) {
// All jump and step movements in crusader are handled identically
// whether starting from combat mode or not.
avatar->setInCombat(0);
handleCombatMode();
return;
}
// Store current idle time. (Also see end of function.)
uint32 currentIdleTime = _idleTime;
_idleTime = 0;
// User toggled combat while in combatRun
if (avatar->isInCombat()) {
if (mainactor)
mainactor->toggleInCombat();
}
if (!hasMovementFlags(MOVE_ANY_DIRECTION) && lastanim == Animation::run) {
// if we were running, slow to a walk before stopping
// (even in stasis)
Animation::Sequence nextanim;
if (rebelBase) {
nextanim = Animation::stand;
} else {
nextanim = Animation::stopRunningAndDrawSmallWeapon;
// Robots don't slow down from running
if (!avatar->hasAnim(nextanim))
nextanim = Animation::stand;
}
waitFor(avatar->doAnim(nextanim, direction));
avatar->setInCombat(0);
return;
}
// can't do any new actions if in stasis
if (stasis)
return;
if (checkOneShotMove(direction))
return;
bool moving = (lastanim == Animation::step || lastanim == Animation::run || lastanim == Animation::walk);
DirectionMode dirmode = avatar->animDirMode(Animation::step);
// if we are trying to move, allow change direction only after move occurs to avoid spinning
if (moving || !hasMovementFlags(MOVE_FORWARD | MOVE_BACK)) {
direction = getTurnDirForTurnFlags(direction, dirmode);
}
Animation::Sequence nextanim = Animation::walk;
if (hasMovementFlags(MOVE_RUN)) {
if (lastanim == Animation::run
|| lastanim == Animation::startRun
|| lastanim == Animation::startRunSmallWeapon
|| lastanim == Animation::combatRunSmallWeapon
|| lastanim == Animation::walk) {
// keep running
nextanim = Animation::run;
} else {
// start running
nextanim = Animation::startRun;
}
}
if (hasMovementFlags(MOVE_FORWARD)) {
step(nextanim, direction);
return;
}
if (!rebelBase && hasMovementFlags(MOVE_BACK)) {
if (mainactor)
mainactor->toggleInCombat();
step(Animation::retreat, direction);
return;
}
int x, y;
getMovementFlagAxes(x, y);
if (x != 0 || y != 0) {
direction = Direction_Get(y, x, dirmode_8dirs);
step(nextanim, direction);
return;
}
if (checkTurn(direction, moving))
return;
// doing another animation?
if (avatar->isBusy())
return;
if (hasMovementFlags(MOVE_ATTACKING) && !hasMovementFlags(MOVE_FORWARD | MOVE_BACK)) {
tryAttack();
return;
}
// not doing anything in particular? stand
if (lastanim != Animation::stand && currentIdleTime == 0) {
waitFor(avatar->doAnim(Animation::stand, direction));
return;
}
// idle
_idleTime = currentIdleTime + 1;
}
void CruAvatarMoverProcess::step(Animation::Sequence action, Direction direction,
bool adjusted) {
Actor *avatar = getControlledActor();
// For "start run" animations, don't call it a success unless we can actually run
Animation::Sequence testaction = _isAnimStartRunning(action) ? Animation::run : action;
Animation::Result res = avatar->tryAnim(testaction, direction);
Animation::Result initialres = res;
if (res != Animation::SUCCESS) {
World *world = World::get_instance();
const CurrentMap *currentmap = world->getCurrentMap();
// Search right/left gradually increasing distance to see if we can make the move work.
Direction dir_right = Direction_TurnByDelta(direction, 4, dirmode_16dirs);
Direction dir_left = Direction_TurnByDelta(direction, -4, dirmode_16dirs);
Point3 origpt = avatar->getLocation();
int32 dims[3];
avatar->getFootpadWorld(dims[0], dims[1], dims[2]);
// Double the values in original to match our coordinate space
static const int ADJUSTMENTS[] = {0x20, 0x20, 0x40, 0x40, 0x60, 0x60,
0x80, 0x80, 0xA0, 0xA0};
for (int i = 0; i < ARRAYSIZE(ADJUSTMENTS); i++) {
Direction testdir = (i % 2 ? dir_left : dir_right);
int32 x = origpt.x + Direction_XFactor(testdir) * ADJUSTMENTS[i];
int32 y = origpt.y + Direction_YFactor(testdir) * ADJUSTMENTS[i];
int32 z = origpt.z;
//
// Check if we can slide from the original point to a different
// start point (otherwise we might pop through walls, lasers, etc).
// This is like Item::collideMove, but we want to stop on any blockers
// and not trigger any events
//
bool startvalid = true;
Std::list<CurrentMap::SweepItem> collisions;
Point3 end(x, y, z);
avatar->setLocation(origpt);
currentmap->sweepTest(origpt, end, dims, avatar->getShapeInfo()->_flags,
avatar->getObjId(), true, &collisions);
for (const auto &collision : collisions) {
if (!collision._touching && collision._blocking) {
startvalid = false;
break;
}
}
if (startvalid) {
avatar->setLocation(x, y, z);
res = avatar->tryAnim(testaction, direction);
if (res == Animation::SUCCESS) {
// move to starting point for real (trigger fast area updates etc)
avatar->setLocation(origpt);
avatar->move(x, y, z);
break;
}
}
}
if (res != Animation::SUCCESS) {
// reset location and result (in case it's END_OFF_LAND now)
// couldn't find a better move.
avatar->setLocation(origpt);
res = initialres;
}
}
if ((action == Animation::step || action == Animation::run ||
action == Animation::startRun || action == Animation::walk)
&& res == Animation::FAILURE) {
action = Animation::stand;
}
else if ((action == Animation::advance || action == Animation::retreat ||
action == Animation::combatRunSmallWeapon ||
action == Animation::startRunSmallWeapon)
&& res == Animation::FAILURE) {
action = Animation::combatStand;
}
bool moving = _isAnimRunningWalking(action);
if (checkTurn(direction, moving))
return;
//debug(6, "Cru avatar step: picked action %d dir %d (test result %d)", action, direction, res);
avatar->doAnim(action, direction);
}
void CruAvatarMoverProcess::tryAttack() {
// Don't do it while this process is waiting
if (is_suspended())
return;
Actor *avatar = getControlledActor();
if (!avatar || avatar->getMapNum() == REBEL_BASE_MAP || avatar->isBusy())
return;
Item *wpn = getItem(avatar->getActiveWeapon());
if (!wpn || !wpn->getShapeInfo() || !wpn->getShapeInfo()->_weaponInfo)
return;
Kernel *kernel = Kernel::get_instance();
if (kernel->getTickNum() < _nextFireTick)
return;
if (!avatar->isInCombat()) {
avatar->setInCombat(0);
}
AudioProcess *audio = AudioProcess::get_instance();
const WeaponInfo *wpninfo = wpn->getShapeInfo()->_weaponInfo;
if (avatar->getObjId() != kMainActorId) {
// Non-avatar NPCs never need to reload or run out of energy.
Animation::Sequence fireanim = (avatar->isKneeling() ?
Animation::kneelAndFire : Animation::attack);
waitFor(avatar->doAnim(fireanim, avatar->getDir()));
return;
}
int shotsleft;
if (wpninfo->_ammoShape) {
shotsleft = wpn->getQuality();
} else if (wpninfo->_energyUse) {
shotsleft = avatar->getMana() / wpninfo->_energyUse;
} else {
shotsleft = 1;
}
if (!shotsleft) {
Item *ammo = avatar->getFirstItemWithShape(wpninfo->_ammoShape, true);
if (ammo) {
// reload now
// SGA1 is special, it reloads every shot.
if (wpn->getShape() == 0x332)
_SGA1Loaded = true;
wpn->setQuality(wpninfo->_clipSize);
ammo->setQuality(ammo->getQuality() - 1);
if (ammo->getQuality() == 0)
ammo->destroy();
if (wpninfo->_reloadSound) {
audio->playSFX(0x2a, 0x80, avatar->getObjId(), 1);
}
if (avatar->getObjId() == kMainActorId && !avatar->isKneeling()) {
avatar->doAnim(Animation::reloadSmallWeapon, dir_current);
}
_nextFireTick = kernel->getTickNum() + 15;
} else {
// no shots left
audio->playSFX(0x2a, 0x80, avatar->getObjId(), 1);
_nextFireTick = kernel->getTickNum() + 20;
}
} else {
// Check for SGA1 reload anim (which happens every shot)
if (wpn->getShape() == 0x332 && !avatar->isKneeling() && !_SGA1Loaded) {
if (wpninfo->_reloadSound) {
audio->playSFX(0x2a, 0x80, avatar->getObjId(), 1);
}
if (avatar->getObjId() == kMainActorId) {
avatar->doAnim(Animation::reloadSmallWeapon, dir_current);
}
_SGA1Loaded = true;
} else {
Direction dir = avatar->getDir();
// Fire event happens from animation
Animation::Sequence fireanim = (avatar->isKneeling() ?
Animation::kneelAndFire : Animation::attack);
uint16 fireanimpid = avatar->doAnim(fireanim, dir);
if (wpn->getShape() == 0x332)
_SGA1Loaded = false;
// Use a shot up
if (wpninfo->_ammoShape) {
wpn->setQuality(shotsleft - 1);
} else if (wpninfo->_energyUse) {
avatar->setMana(avatar->getMana() - wpninfo->_energyUse);
}
// Check if we should alert nearby NPCs
checkForAlertingNPCs();
if (wpninfo->_shotDelay) {
_nextFireTick = kernel->getTickNum() + wpninfo->_shotDelay;
} else {
waitFor(fireanimpid);
}
}
}
}
void CruAvatarMoverProcess::checkForAlertingNPCs() {
uint32 nowtick = Kernel::get_instance()->getTickNum();
if (nowtick - _lastNPCAlertTick < 240)
return;
_lastNPCAlertTick = nowtick;
uint16 controllednpc = World::get_instance()->getControlledNPCNum();
for (int i = 2; i < 256; i++) {
if (i == controllednpc)
continue;
Actor *a = getActor(i);
if (!a || a->isDead() || !a->isOnScreen())
continue;
if (!a->isInCombat()) {
uint16 currentactivity = a->getCurrentActivityNo();
uint16 activity2 = a->getDefaultActivity(2);
if (currentactivity == activity2) {
// note: original game also seems to check surrendering flag here?
if (currentactivity == 8) {
// Was guarding, attack!
a->setActivity(5);
}
} else {
uint16 range = 0;
uint32 npcshape = a->getShape();
if (npcshape == 0x2f5 || npcshape == 0x2f6 || npcshape == 0x2f7 ||
(GAME_IS_REMORSE && (npcshape == 0x595 || npcshape == 0x597)) ||
(GAME_IS_REGRET && (npcshape == 0x344 || npcshape == 0x384))) {
Actor *c = getActor(controllednpc);
if (c)
range = a->getRangeIfVisible(*c);
} else {
range = 1;
}
if (range) {
a->setActivity(a->getDefaultActivity(2));
}
}
} else {
// Was guarding, attack!
a->setActivity(5);
}
}
}
void CruAvatarMoverProcess::saveData(Common::WriteStream *ws) {
AvatarMoverProcess::saveData(ws);
ws->writeSint32LE(_avatarAngle);
ws->writeByte(_SGA1Loaded ? 1 : 0);
// We don't bother saving _lastNPCAlertTick or _nextFireTick, they both
// will get reset to 0 which will behave almost identically in practice.
}
bool CruAvatarMoverProcess::loadData(Common::ReadStream *rs, uint32 version) {
if (!AvatarMoverProcess::loadData(rs, version)) return false;
_avatarAngle = rs->readSint32LE();
_SGA1Loaded = (rs->readByte() != 0);
return true;
}
} // End of namespace Ultima8
} // End of namespace Ultima

View File

@@ -0,0 +1,96 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef WORLD_ACTORS_CRUAVATARMOVERPROCESS_H
#define WORLD_ACTORS_CRUAVATARMOVERPROCESS_H
#include "ultima/ultima8/world/actors/avatar_mover_process.h"
#include "ultima/ultima8/world/actors/animation.h"
namespace Ultima {
namespace Ultima8 {
/**
* Mover process that replicates the feel of Crusader - moving, combat, jumps, etc.
* Tries sliding left and right if movement is blocked. Walking cancels combat.
* TODO: Support combat rolls and side-steps.
*/
class CruAvatarMoverProcess : public AvatarMoverProcess {
public:
CruAvatarMoverProcess();
~CruAvatarMoverProcess() override;
ENABLE_RUNTIME_CLASSTYPE()
void run() override;
bool loadData(Common::ReadStream *rs, uint32 version);
void saveData(Common::WriteStream *ws) override;
double getAvatarAngleDegrees() const {
return static_cast<double>(_avatarAngle) / 100.0;
}
void clearMovementFlag(uint32 mask) override;
private:
/** Try readying or firing weapon. */
void tryAttack();
/** Check if we need to alert NPCs after firing. */
void checkForAlertingNPCs();
/**
* Angle of avatar in centidegrees (1/100deg). The original game runs the keyboard
* process 45 times per second and rotates the crosshair by 2 (regular) or
* 5 ('run') degrees each time. This process runs 60 times per second, so we choose a
* multiplier that can use integers - rotating 3.75 or 1.5 degrees each time.
*/
int32 _avatarAngle;
/**
* Whether we've reloaded the SGA1 yet (it needs to happen every shot)
*/
bool _SGA1Loaded;
/**
* Next tick the avatar can fire a weapon again.
*/
uint32 _nextFireTick;
/**
* Last time we alerted NPCs on a shot.
*/
uint32 _lastNPCAlertTick;
void handleHangingMode() override;
void handleCombatMode() override;
void handleNormalMode() override;
void step(Animation::Sequence action, Direction direction, bool adjusted = false);
bool checkOneShotMove(Direction direction);
};
} // End of namespace Ultima8
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,100 @@
/* 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/cru_healer_process.h"
#include "ultima/ultima8/world/actors/main_actor.h"
#include "ultima/ultima8/kernel/kernel.h"
#include "ultima/ultima8/world/get_object.h"
#include "ultima/ultima8/world/world.h"
#include "ultima/ultima8/ultima8.h"
#include "ultima/ultima8/audio/audio_process.h"
namespace Ultima {
namespace Ultima8 {
// These SFX IDs are the same in both No Regret and No Remorse.
static const uint16 HEAL_START_SFX = 0xdb;
static const uint16 HEAL_GOING_SFX = 0xba;
DEFINE_RUNTIME_CLASSTYPE_CODE(CruHealerProcess)
CruHealerProcess::CruHealerProcess() : Process() {
MainActor *avatar = dynamic_cast<MainActor *>(getActor(World::get_instance()->getControlledNPCNum()));
if (!avatar) {
_itemNum = 0;
_targetMaxHP = 0;
} else {
_itemNum = avatar->getObjId();
_targetMaxHP = avatar->getMaxHP();
AudioProcess *audio = AudioProcess::get_instance();
if (audio) {
// Sound num is the same in both No Remorse and No Regret
audio->playSFX(HEAL_START_SFX, 0x80, _itemNum, 1, false);
}
}
Ultima8Engine::get_instance()->setAvatarInStasis(true);
_type = 0x254; // CONSTANT!
}
void CruHealerProcess::run() {
MainActor *avatar = dynamic_cast<MainActor *>(getActor(World::get_instance()->getControlledNPCNum()));
AudioProcess *audio = AudioProcess::get_instance();
if (!avatar || avatar->isDead() || avatar->getHP() >= _targetMaxHP) {
if (avatar && avatar->getHP() >= _targetMaxHP) {
Ultima8Engine::get_instance()->setAvatarInStasis(false);
}
// dead or finished healing
if (audio)
audio->stopSFX(HEAL_START_SFX, _itemNum);
terminate();
return;
}
if (audio && !audio->isSFXPlayingForObject(HEAL_GOING_SFX, _itemNum))
audio->playSFX(HEAL_GOING_SFX, 0x80, _itemNum, 1);
uint16 newHP = avatar->getHP() + 1;
if (newHP > _targetMaxHP)
newHP = _targetMaxHP;
avatar->setHP(newHP);
}
uint32 CruHealerProcess::I_create(const uint8 *args, unsigned int /*argsize*/) {
return Kernel::get_instance()->addProcess(new CruHealerProcess());
}
void CruHealerProcess::saveData(Common::WriteStream *ws) {
Process::saveData(ws);
ws->writeUint16LE(_targetMaxHP);
}
bool CruHealerProcess::loadData(Common::ReadStream *rs, uint32 version) {
if (!Process::loadData(rs, version)) return false;
_targetMaxHP = rs->readUint16LE();
return true;
}
} // End of namespace Ultima8
} // End of namespace Ultima

View File

@@ -0,0 +1,51 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef WORLD_ACTORS_CRU_HEALER_PROCESS_H
#define WORLD_ACTORS_CRU_HEALER_PROCESS_H
#include "ultima/ultima8/kernel/process.h"
#include "ultima/ultima8/usecode/intrinsics.h"
namespace Ultima {
namespace Ultima8 {
class CruHealerProcess : public Process {
public:
CruHealerProcess();
ENABLE_RUNTIME_CLASSTYPE()
void run() override;
INTRINSIC(I_create);
bool loadData(Common::ReadStream *rs, uint32 version);
void saveData(Common::WriteStream *ws) override;
private:
uint16 _targetMaxHP;
};
} // End of namespace Ultima8
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,370 @@
/* 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/cru_pathfinder_process.h"
#include "ultima/ultima8/world/actors/pathfinder_process.h"
#include "ultima/ultima8/world/actors/attack_process.h"
#include "ultima/ultima8/world/actors/animation.h"
#include "ultima/ultima8/world/actors/actor.h"
#include "ultima/ultima8/world/item.h"
#include "ultima/ultima8/world/get_object.h"
#include "ultima/ultima8/world/world.h"
#include "ultima/ultima8/world/actors/pathfinder.h"
#include "ultima/ultima8/misc/direction_util.h"
#include "ultima/ultima8/kernel/kernel.h"
namespace Ultima {
namespace Ultima8 {
static const int8 PATHFIND_DIR_OFFSETS_1[8] = {0, 2, -2, 4, -4, 6, -6, -8};
static const int8 PATHFIND_DIR_OFFSETS_2[8] = {0, -2, 2, -4, 4, -6, 6, -8};
DEFINE_RUNTIME_CLASSTYPE_CODE(CruPathfinderProcess)
CruPathfinderProcess::CruPathfinderProcess() : Process(),
_currentStep(0), _targetItem(0), _currentDistance(0),
_target(), _randomFlag(false),
_nextTurn(false), _turnAtEnd(false), _lastDir(dir_current),
_nextDir(dir_current), _nextDir2(dir_current),
_solidObject(true), _directPathBlocked(false), _noShotAvailable(true),
_dir16Flag(false), _stopDistance(0), _maxSteps(0)
{
}
CruPathfinderProcess::CruPathfinderProcess(Actor *actor, Item *target, int maxsteps, int stopdistance, bool turnatend) :
_currentStep(0), _currentDistance(0), _target(),
_maxSteps(maxsteps), _stopDistance(stopdistance), _nextTurn(false), _turnAtEnd(turnatend),
_lastDir(dir_current), _nextDir(dir_current), _nextDir2(dir_current),
_directPathBlocked(false), _noShotAvailable(true), _dir16Flag(false) {
assert(actor && target);
_itemNum = actor->getObjId();
_type = PathfinderProcess::PATHFINDER_PROC_TYPE;
Common::RandomSource &rs = Ultima8Engine::get_instance()->getRandomSource();
_randomFlag = rs.getRandomBit() != 0;
_targetItem = target->getObjId();
_target = target->getLocation();
Point3 pt = actor->getLocation();
_currentDistance = MAX(abs(pt.x - _target.x), abs(pt.y - _target.y));
const ShapeInfo *si = actor->getShapeInfo();
_solidObject = (si->_flags & ShapeInfo::SI_SOLID) && si->_z > 0;
// TODO: check if flag already set? kill other pathfinders?
assert(!actor->hasActorFlags(Actor::ACT_PATHFINDING));
actor->setActorFlag(Actor::ACT_PATHFINDING);
if (actor->isInCombat() && actor->hasActorFlags(Actor::ACT_WEAPONREADY))
actor->doAnim(Animation::unreadyWeapon, dir_current);
}
CruPathfinderProcess::CruPathfinderProcess(Actor *actor, const Point3 &target, int maxsteps, int stopdistance, bool turnatend) :
_target(target), _targetItem(0), _currentStep(0),
_maxSteps(maxsteps), _stopDistance(stopdistance), _nextTurn(false), _turnAtEnd(turnatend),
_lastDir(dir_current), _nextDir(dir_current), _nextDir2(dir_current),
_directPathBlocked(false), _noShotAvailable(true), _dir16Flag(false) {
assert(actor);
_itemNum = actor->getObjId();
_type = PathfinderProcess::PATHFINDER_PROC_TYPE;
Common::RandomSource &rs = Ultima8Engine::get_instance()->getRandomSource();
_randomFlag = rs.getRandomBit() != 0;
Point3 pt = actor->getLocation();
_currentDistance = MAX(abs(pt.x - _target.x), abs(pt.y - _target.y));
const ShapeInfo *si = actor->getShapeInfo();
_solidObject = (si->_flags & ShapeInfo::SI_SOLID) && si->_z > 0;
// TODO: check if flag already set? kill other pathfinders?
assert(!actor->hasActorFlags(Actor::ACT_PATHFINDING));
actor->setActorFlag(Actor::ACT_PATHFINDING);
if (actor->isInCombat() && actor->hasActorFlags(Actor::ACT_WEAPONREADY))
actor->doAnim(Animation::unreadyWeapon, dir_current);
}
CruPathfinderProcess::~CruPathfinderProcess() {
}
void CruPathfinderProcess::terminate() {
Actor *actor = getActor(_itemNum);
if (actor && !actor->isDead()) {
// TODO: only clear if it was set by us?
// (slightly more complicated if we kill other pathfinders on startup)
actor->clearActorFlag(Actor::ACT_PATHFINDING);
uint16 turnproc = 0;
if (_turnAtEnd) {
Direction destdir = dir_current;
// TODO: this logic can be cleaned up a bit by just updating targetx/y?
Point3 i = actor->getLocationAbsolute();
if (_targetItem == 0) {
destdir = Direction_GetWorldDir(_target.y - i.y, _target.x - i.x, dirmode_8dirs);
} else {
const Item *target = getItem(_targetItem);
if (target) {
Point3 t = target->getLocationAbsolute();
destdir = Direction_GetWorldDir(t.y - i.y, t.x - i.x, dirmode_8dirs);
}
}
if (destdir != dir_current)
turnproc = actor->turnTowardDir(destdir);
}
if (!turnproc && _noShotAvailable) {
Animation::Sequence standanim = (actor->isInCombat() ? Animation::combatStandSmallWeapon : Animation::stand);
actor->doAnim(standanim, dir_current);
}
}
Process::terminate();
}
Direction CruPathfinderProcess::nextDirFromPoint(struct Point3 &npcpt) {
const Direction dirtotarget = Direction_GetWorldDir(_target.y - npcpt.y, _target.x - npcpt.x, dirmode_8dirs);
Actor *npc = getActor(_itemNum);
//assert(npc);
const int maxdiffxy = MAX(abs(npcpt.x - _target.x), abs(npcpt.y - _target.y));
if (maxdiffxy < _currentDistance) {
// each time we get closer, check again if we can walk toward the target.
_currentDistance = maxdiffxy;
PathfindingState state;
state._point = npcpt;
state._direction = dirtotarget;
state._combat = npc->isInCombat();
Animation::Sequence anim = npc->isInCombat() ? Animation::walk : Animation::advanceSmallWeapon;
Animation::Result result = npc->tryAnim(anim, dirtotarget, 0, &state);
if (result == Animation::SUCCESS) {
_directPathBlocked = false;
}
}
int startoff = 0;
Direction dirtable[8];
Direction nextdir_table[8];
if (!_directPathBlocked) {
for (int i = 0; i < 8; i++) {
if (_randomFlag) {
dirtable[i] = Direction_TurnByDelta(dirtotarget, PATHFIND_DIR_OFFSETS_1[i], dirmode_16dirs);
} else {
dirtable[i] = Direction_TurnByDelta(dirtotarget, PATHFIND_DIR_OFFSETS_2[i], dirmode_16dirs);
}
}
} else {
int diroffset;
if (_randomFlag) {
diroffset = (_nextTurn ? 2 : -2);
} else {
diroffset = (_nextTurn ? -2 : 2);
}
nextdir_table[0] = Direction_TurnByDelta(_nextDir, diroffset + 8, dirmode_16dirs);
for (int i = 1; i < 8; i++) {
if (_randomFlag) {
diroffset = (_nextTurn ? 2 : -2);
} else {
diroffset = (_nextTurn ? -2 : 2);
}
nextdir_table[i] = Direction_TurnByDelta(nextdir_table[i - 1], diroffset, dirmode_16dirs);
}
startoff = 1;
}
PathfindingState state;
Animation::Result animresult = Animation::SUCCESS;
int i;
for (i = startoff; i < 8; i++) {
// TODO: double-check these in disasm
if (_directPathBlocked && i == 2)
continue;
if (_directPathBlocked && i == 7)
break;
if (_directPathBlocked)
_nextDir2 = nextdir_table[i];
else
_nextDir2 = dirtable[i];
// LAB_1110_0c26:
Animation::Sequence anim = npc->isInCombat() ? Animation::walk : Animation::advanceSmallWeapon;
state._point = npcpt;
state._direction = _nextDir2;
state._combat = npc->isInCombat();
animresult = npc->tryAnim(anim, _nextDir2, 0, &state);
if (_solidObject && (animresult == Animation::SUCCESS)) {
_turnAtEnd = true;
return dir_invalid;
}
if (_stopDistance && (MAX(abs(_target.x - state._point.x), abs(_target.y - state._point.y)) <= _stopDistance)) {
_turnAtEnd = true;
return dir_invalid;
}
if (animresult == Animation::SUCCESS)
break;
}
// LAB_1110_0dd5:
if (animresult != Animation::SUCCESS)
return dir_current;
if ((_nextDir2 != dirtotarget) && !_directPathBlocked) {
_directPathBlocked = true;
_nextTurn = (i % 2);
}
npcpt = state._point;
bool is_controlled = World::get_instance()->getControlledNPCNum() == _itemNum;
if (npc->isInCombat() && !is_controlled) {
AttackProcess *attackproc = dynamic_cast<AttackProcess *>
(Kernel::get_instance()->findProcess(_itemNum, AttackProcess::ATTACK_PROC_TYPE));
if (attackproc) {
const Actor *target = getActor(attackproc->getTarget());
if (target && npc->isOnScreen() && npc->fireDistance(target, dirtotarget, 0, 0, 0)) {
npc->doAnim(Animation::stand, dir_current);
attackproc->setField7F();
_noShotAvailable = false;
_turnAtEnd = true;
return dir_invalid;
}
}
}
return _nextDir2;
}
void CruPathfinderProcess::run() {
Actor *npc = getActor(_itemNum);
if (!npc || !npc->hasFlags(Item::FLG_FASTAREA))
return;
if (npc->isDead()) {
terminate();
return;
}
if (_dir16Flag) {
terminate(); // terminate 1
return;
}
// Update target location if tracking to an item
if (_targetItem != 0 && _solidObject) {
Item *target = getItem(_targetItem);
if (target)
_target = target->getLocation();
}
Point3 npcpt = npc->getLocation();
if (_target.x == npcpt.x && _target.y == npcpt.y) {
terminate(); // _destpt.z != npcpt.z
return;
}
const Direction lastdir = _nextDir;
_nextDir = nextDirFromPoint(npcpt);
_lastDir = lastdir;
if (_nextDir == dir_current) {
terminate(); //0
return;
}
if (_nextDir == dir_invalid) {
_dir16Flag = true;
} else {
if (_currentStep >= _maxSteps) {
terminate(); //0
return;
}
}
Direction newdir;
if (!_dir16Flag) {
newdir = _nextDir;
} else {
newdir = _nextDir2;
}
uint16 turnpid = npc->turnTowardDir(newdir);
Animation::Sequence anim = npc->isInCombat() ? Animation::advanceSmallWeapon : Animation::walk;
uint16 animpid = npc->doAnim(anim, newdir);
if (turnpid)
Kernel::get_instance()->getProcess(animpid)->waitFor(turnpid);
waitFor(animpid);
_currentStep += 1;
}
void CruPathfinderProcess::saveData(Common::WriteStream *ws) {
Process::saveData(ws);
ws->writeUint16LE(_targetItem);
ws->writeUint16LE(static_cast<uint16>(_target.x));
ws->writeUint16LE(static_cast<uint16>(_target.y));
ws->writeUint16LE(static_cast<uint16>(_target.z));
ws->writeUint16LE(static_cast<uint16>(_currentDistance));
ws->writeByte(_randomFlag ? 1 : 0);
ws->writeByte(_nextTurn ? 1 : 0);
ws->writeByte(_turnAtEnd ? 1 : 0);
ws->writeByte(_lastDir);
ws->writeByte(_nextDir);
ws->writeByte(_nextDir2);
ws->writeByte(_solidObject ? 1 : 0);
ws->writeByte(_directPathBlocked ? 1 : 0);
ws->writeByte(_noShotAvailable ? 1 : 0);
ws->writeByte(_dir16Flag ? 1 : 0);
ws->writeUint16LE(static_cast<uint16>(_currentStep));
ws->writeUint16LE(static_cast<uint16>(_maxSteps));
ws->writeUint16LE(static_cast<uint16>(_stopDistance));
}
bool CruPathfinderProcess::loadData(Common::ReadStream *rs, uint32 version) {
if (!Process::loadData(rs, version)) return false;
_targetItem = rs->readUint16LE();
_target.x = rs->readUint16LE();
_target.y = rs->readUint16LE();
_target.z = rs->readUint16LE();
_currentDistance = rs->readUint16LE();
_randomFlag = rs->readByte();
_nextTurn = rs->readByte();
_turnAtEnd = rs->readByte();
_lastDir = static_cast<Direction>(rs->readByte());
_nextDir = static_cast<Direction>(rs->readByte());
_nextDir2 = static_cast<Direction>(rs->readByte());
_solidObject = rs->readByte();
_directPathBlocked = rs->readByte();
_noShotAvailable = rs->readByte();
_dir16Flag = rs->readByte();
_currentStep = rs->readUint16LE();
_maxSteps = rs->readUint16LE();
_stopDistance = rs->readUint16LE();
return true;
}
} // End of namespace Ultima8
} // End of namespace Ultima

View File

@@ -0,0 +1,84 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef WORLD_ACTORS_CRU_PATHFINDERPROCESS_H
#define WORLD_ACTORS_CRU_PATHFINDERPROCESS_H
#include "ultima/ultima8/kernel/process.h"
#include "ultima/ultima8/misc/direction.h"
#include "ultima/ultima8/misc/point3.h"
namespace Ultima {
namespace Ultima8 {
class Actor;
class Item;
/**
* A simplified pathfinder used in Crusader for the AttackProcess.
*
* The code and variable names for this are not very well written as
* they are are based on the disassembly.
*/
class CruPathfinderProcess : public Process {
public:
CruPathfinderProcess();
CruPathfinderProcess(Actor *actor, Item *item, int maxsteps, int stopdistance, bool turnatend);
CruPathfinderProcess(Actor *actor, const Point3 &target, int maxsteps, int stopdistance, bool turnatend);
~CruPathfinderProcess() override;
ENABLE_RUNTIME_CLASSTYPE()
void run() override;
void terminate() override;
bool loadData(Common::ReadStream *rs, uint32 version);
void saveData(Common::WriteStream *ws) override;
private:
Direction nextDirFromPoint(struct Point3 &npcpt);
Point3 _target;
ObjId _targetItem;
int _currentDistance;
bool _randomFlag;
bool _nextTurn;
bool _turnAtEnd;
Direction _lastDir;
Direction _nextDir;
Direction _nextDir2;
bool _solidObject;
bool _directPathBlocked;
bool _noShotAvailable;
bool _dir16Flag;
unsigned int _currentStep;
unsigned int _maxSteps;
int _stopDistance;
};
} // End of namespace Ultima8
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,230 @@
/* 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/grant_peace_process.h"
#include "ultima/ultima8/world/world.h"
#include "ultima/ultima8/world/current_map.h"
#include "ultima/ultima8/gumps/target_gump.h"
#include "ultima/ultima8/gfx/palette_fader_process.h"
#include "ultima/ultima8/usecode/uc_list.h"
#include "ultima/ultima8/world/loop_script.h"
#include "ultima/ultima8/kernel/kernel.h"
#include "ultima/ultima8/gumps/gump_notify_process.h"
#include "ultima/ultima8/world/actors/main_actor.h"
#include "ultima/ultima8/world/sprite_process.h"
#include "ultima/ultima8/audio/audio_process.h"
#include "ultima/ultima8/world/get_object.h"
#include "ultima/ultima8/ultima8.h"
namespace Ultima {
namespace Ultima8 {
DEFINE_RUNTIME_CLASSTYPE_CODE(GrantPeaceProcess)
GrantPeaceProcess::GrantPeaceProcess() : Process(), _haveTarget(false) {
}
GrantPeaceProcess::GrantPeaceProcess(Actor *caster) {
assert(caster);
_itemNum = caster->getObjId();
_type = 0x21d; // CONSTANT !
_haveTarget = false;
}
void GrantPeaceProcess::run() {
Actor *caster = getActor(_itemNum);
if (!caster) {
terminate();
return;
}
if (!_haveTarget) {
TargetGump *targetgump = new TargetGump(0, 0);
targetgump->InitGump(0);
waitFor(targetgump->GetNotifyProcess()->getPid());
_haveTarget = true;
return;
}
// get target _result
ObjId targetid = static_cast<ObjId>(_result);
Actor *target = getActor(targetid);
if (targetid == kMainActorId || !target) {
// targeting the avatar, no target or not an Actor
terminate();
return;
}
Common::RandomSource &rs = Ultima8Engine::get_instance()->getRandomSource();
bool hit = false;
if (target->getDefenseType() & WeaponInfo::DMG_UNDEAD) {
// undead
// first see if we're near Khumash-Gor
CurrentMap *currentmap = World::get_instance()->getCurrentMap();
UCList KGlist(2);
LOOPSCRIPT(script, LS_SHAPE_EQUAL(289));
currentmap->areaSearch(&KGlist, script, sizeof(script),
caster, 2048, false);
bool khumash = (KGlist.getSize() > 0);
// then find all the undead in the area
UCList itemlist(2);
LOOPSCRIPT(script2, LS_TOKEN_TRUE);
currentmap->areaSearch(&itemlist, script2, sizeof(script2),
caster, 768, false);
for (unsigned int i = 0; i < itemlist.getSize(); ++i) {
Actor *t = getActor(itemlist.getuint16(i));
if (!t) continue;
if (t == caster) continue;
if (t->isDead()) continue;
// undead?
if (t->getDefenseType() & WeaponInfo::DMG_UNDEAD) {
t->receiveHit(_itemNum, dir_current, target->getHP(),
(WeaponInfo::DMG_MAGIC |
WeaponInfo::DMG_PIERCE |
WeaponInfo::DMG_FIRE));
hit = true;
if (t->getShape() == 411 && khumash) { // CONSTANT!
Point3 pt = t->getLocation();
// CONSTANT! (shape 480, frame 0-9, repeat 1, delay 1)
Process *sp = new SpriteProcess(480, 0, 9, 1, 1, pt.x, pt.y, pt.z);
Kernel::get_instance()->addProcess(sp);
Item *throne = getItem(KGlist.getuint16(0));
if (throne) {
throne->setFrame(1); // CONSTANT!
}
}
#if 0
// FIXME: this seems to screw up the death animation; why?
int dir = caster->getDirToItemCentre(*t);
t->hurl(engine->getRandomNumber(5, 9) * x_fact[dir],
engine->getRandomNumber(5, 9) * y_fact[dir],
engine->getRandomNumber(5, 9),
4);
#endif
}
}
} else {
// not undead
if (!target->hasActorFlags(Actor::ACT_DEAD |
Actor::ACT_IMMORTAL |
Actor::ACT_INVINCIBLE)) {
if (rs.getRandomNumber(9) == 0) {
target->receiveHit(_itemNum, dir_current, target->getHP(),
(WeaponInfo::DMG_MAGIC |
WeaponInfo::DMG_PIERCE |
WeaponInfo::DMG_FIRE));
hit = true;
}
}
}
if (hit) {
// lightning
// calling intrinsic...
PaletteFaderProcess::I_lightningBolt(0, 0);
int sfx;
switch (rs.getRandomNumber(2)) {
case 0:
sfx = 91;
break;
case 1:
sfx = 94;
break;
default:
sfx = 96;
break;
}
AudioProcess *audioproc = AudioProcess::get_instance();
if (audioproc) audioproc->playSFX(sfx, 0x60, 1, 0); //constants!!
}
// done
terminate();
}
uint32 GrantPeaceProcess::I_castGrantPeace(const uint8 *args,
unsigned int /*argsize*/) {
MainActor *avatar = getMainActor();
GrantPeaceProcess *gpp = new GrantPeaceProcess(avatar);
Kernel::get_instance()->addProcess(gpp);
// start casting
ProcId anim1 = avatar->doAnim(Animation::cast1, dir_current);
// cast
ProcId anim2 = avatar->doAnim(Animation::cast3, dir_current);
Process *anim2p = Kernel::get_instance()->getProcess(anim2);
// end casting
ProcId anim3 = avatar->doAnim(Animation::cast2, dir_current);
Process *anim3p = Kernel::get_instance()->getProcess(anim3);
anim2p->waitFor(anim1);
anim3p->waitFor(anim2);
gpp->waitFor(anim2);
return 0;
}
void GrantPeaceProcess::saveData(Common::WriteStream *ws) {
Process::saveData(ws);
uint8 ht = _haveTarget ? 1 : 0;
ws->writeByte(ht);
}
bool GrantPeaceProcess::loadData(Common::ReadStream *rs, uint32 version) {
if (!Process::loadData(rs, version)) return false;
_haveTarget = (rs->readByte() != 0);
return true;
}
} // End of namespace Ultima8
} // End of namespace Ultima

View File

@@ -0,0 +1,54 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef WORLD_ACTORS_GRANTPEACEPROCESS_H
#define WORLD_ACTORS_GRANTPEACEPROCESS_H
#include "ultima/ultima8/kernel/process.h"
#include "ultima/ultima8/usecode/intrinsics.h"
namespace Ultima {
namespace Ultima8 {
class Actor;
class GrantPeaceProcess : public Process {
public:
GrantPeaceProcess();
GrantPeaceProcess(Actor *caster);
ENABLE_RUNTIME_CLASSTYPE()
void run() override;
INTRINSIC(I_castGrantPeace);
bool loadData(Common::ReadStream *rs, uint32 version);
void saveData(Common::WriteStream *ws) override;
protected:
bool _haveTarget;
};
} // End of namespace Ultima8
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,87 @@
/* 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/guard_process.h"
#include "ultima/ultima8/world/actors/main_actor.h"
#include "ultima/ultima8/world/actors/actor_anim_process.h"
#include "ultima/ultima8/kernel/kernel.h"
#include "ultima/ultima8/kernel/delay_process.h"
#include "ultima/ultima8/world/get_object.h"
#include "ultima/ultima8/ultima8.h"
namespace Ultima {
namespace Ultima8 {
DEFINE_RUNTIME_CLASSTYPE_CODE(GuardProcess)
GuardProcess::GuardProcess() : Process() {
}
GuardProcess::GuardProcess(Actor *actor) {
assert(actor);
_itemNum = actor->getObjId();
_type = 0x25e;
}
void GuardProcess::run() {
Actor *a = getActor(_itemNum);
if (!a || a->isDead()) {
// dead?
terminate();
return;
}
// Do nothing if busy
if (a->isBusy())
return;
Actor *mainactor = getMainActor();
if (!mainactor)
return;
if (!a->canSeeControlledActor(false)) {
Common::RandomSource &rs = Ultima8Engine::get_instance()->getRandomSource();
if (rs.getRandomBit()) {
DelayProcess *dp = new DelayProcess(30 * rs.getRandomNumberRng(1, 3));
Kernel::get_instance()->addProcess(dp);
waitFor(dp);
} else {
Animation::Sequence anim = Animation::absAnim(rs.getRandomBit() ? Animation::lookLeftCru : Animation::lookRightCru);
uint16 animproc = a->doAnim(anim, dir_current);
a->doAnimAfter(Animation::stand, dir_current, animproc);
}
return;
}
// Saw the silencer, go to combat.
a->setActivity(5);
}
void GuardProcess::saveData(Common::WriteStream *ws) {
Process::saveData(ws);
}
bool GuardProcess::loadData(Common::ReadStream *rs, uint32 version) {
return Process::loadData(rs, version);
}
} // End of namespace Ultima8
} // End of namespace Ultima

View File

@@ -0,0 +1,52 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef WORLD_ACTORS_GUARDPROCESS_H
#define WORLD_ACTORS_GUARDPROCESS_H
#include "ultima/ultima8/kernel/process.h"
namespace Ultima {
namespace Ultima8 {
class Actor;
/**
* A process for Crusader where the NPC mostly just stands around until
* they see the avatar.
*/
class GuardProcess : public Process {
public:
GuardProcess();
GuardProcess(Actor *actor);
ENABLE_RUNTIME_CLASSTYPE()
void run() override;
bool loadData(Common::ReadStream *rs, uint32 version);
void saveData(Common::WriteStream *ws) override;
};
} // End of namespace Ultima8
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,129 @@
/* 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/heal_process.h"
#include "ultima/ultima8/world/actors/main_actor.h"
#include "ultima/ultima8/kernel/kernel.h"
#include "ultima/ultima8/world/get_object.h"
namespace Ultima {
namespace Ultima8 {
DEFINE_RUNTIME_CLASSTYPE_CODE(HealProcess)
HealProcess::HealProcess() : Process() {
_hungerCounter = 0;
_healCounter = 0;
_itemNum = 0;
_type = 0x222; // CONSTANT!
}
void HealProcess::run() {
MainActor *avatar = getMainActor();
if (!avatar || avatar->isDead()) {
// dead?
terminate();
return;
}
// heal one hitpoint and one manapoint every minute (1800 frames)
_healCounter++;
if (_healCounter == 900) {
int16 mana = avatar->getMana();
if (mana < avatar->getMaxMana()) {
mana++;
avatar->setMana(mana);
}
}
if (_healCounter == 1800) {
uint16 hp = avatar->getHP();
if (hp < avatar->getMaxHP()) {
hp++;
avatar->setHP(hp);
}
_healCounter = 0;
if (_hungerCounter < 200)
_hungerCounter++;
}
}
void HealProcess::feedAvatar(uint16 food) {
MainActor *avatar = getMainActor();
if (!avatar || avatar->isDead()) {
// dead?
terminate();
return;
}
if (food > _hungerCounter)
food = _hungerCounter;
if (food == 0) return;
uint16 oldCounter = _hungerCounter;
_hungerCounter -= food;
uint16 hp = avatar->getHP() - (_hungerCounter / 4) + (oldCounter / 4);
if (hp > avatar->getMaxHP()) hp = avatar->getMaxHP();
avatar->setHP(hp);
}
uint32 HealProcess::I_feedAvatar(const uint8 *args, unsigned int /*argsize*/) {
ARG_UINT16(food);
Process *p = Kernel::get_instance()->findProcess(0, 0x222); // CONSTANT!
HealProcess *hp = dynamic_cast<HealProcess *>(p);
if (!hp) {
warning("I_feedAvatar: unable to find HealProcess");
return 0;
}
hp->feedAvatar(food);
return 0;
}
void HealProcess::saveData(Common::WriteStream *ws) {
Process::saveData(ws);
ws->writeUint16LE(_healCounter);
ws->writeUint16LE(_hungerCounter);
}
bool HealProcess::loadData(Common::ReadStream *rs, uint32 version) {
if (!Process::loadData(rs, version)) return false;
_healCounter = rs->readUint16LE();
_hungerCounter = rs->readUint16LE();
return true;
}
} // End of namespace Ultima8
} // End of namespace Ultima

View File

@@ -0,0 +1,54 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef WORLD_ACTORS_HEALPROCESS_H
#define WORLD_ACTORS_HEALPROCESS_H
#include "ultima/ultima8/kernel/process.h"
#include "ultima/ultima8/usecode/intrinsics.h"
namespace Ultima {
namespace Ultima8 {
class HealProcess : public Process {
public:
HealProcess();
ENABLE_RUNTIME_CLASSTYPE()
void run() override;
INTRINSIC(I_feedAvatar);
bool loadData(Common::ReadStream *rs, uint32 version);
void saveData(Common::WriteStream *ws) override;
protected:
void feedAvatar(uint16 food);
uint16 _healCounter;
uint16 _hungerCounter;
};
} // End of namespace Ultima8
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,143 @@
/* 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/loiter_process.h"
#include "ultima/ultima8/world/actors/actor.h"
#include "ultima/ultima8/world/actors/pathfinder_process.h"
#include "ultima/ultima8/world/actors/cru_pathfinder_process.h"
#include "ultima/ultima8/kernel/kernel.h"
#include "ultima/ultima8/kernel/delay_process.h"
#include "ultima/ultima8/ultima8.h"
#include "ultima/ultima8/world/get_object.h"
namespace Ultima {
namespace Ultima8 {
DEFINE_RUNTIME_CLASSTYPE_CODE(LoiterProcess)
LoiterProcess::LoiterProcess() : Process(), _count(0) {
}
LoiterProcess::LoiterProcess(Actor *actor, int32 c) : _count(c) {
assert(actor);
_itemNum = actor->getObjId();
if (GAME_IS_U8)
_type = 0x205; // CONSTANT!
else
_type = 599;
// Only loiter with one process at a time.
Process *previous = Kernel::get_instance()->findProcess(_itemNum, _type);
if (previous)
previous->terminate();
Process *prevpf = Kernel::get_instance()->findProcess(_itemNum, PathfinderProcess::PATHFINDER_PROC_TYPE);
if (prevpf)
prevpf->terminate();
}
void LoiterProcess::run() {
if (!_count) {
terminate();
return;
}
if (_count > 0)
_count--;
Actor *a = getActor(_itemNum);
if (!a || a->isDead()) {
// dead?
terminate();
return;
}
Point3 pt = a->getLocation();
Common::RandomSource &rs = Ultima8Engine::get_instance()->getRandomSource();
pt.x += 32 * rs.getRandomNumberRngSigned(-10, 10);
pt.y += 32 * rs.getRandomNumberRngSigned(-10, 10);
Process *pfp;
if (GAME_IS_U8)
pfp = new PathfinderProcess(a, pt);
else
pfp = new CruPathfinderProcess(a, pt, 0xc, 0x80, false);
Kernel::get_instance()->addProcess(pfp);
bool hasidle1 = a->hasAnim(Animation::idle1);
bool hasidle2 = a->hasAnim(Animation::idle2);
if ((hasidle1 || hasidle2) && (rs.getRandomNumber(2) == 0)) {
Animation::Sequence idleanim;
if (!hasidle1) {
idleanim = Animation::idle2;
} else if (!hasidle2) {
idleanim = Animation::idle1;
} else {
if (rs.getRandomBit())
idleanim = Animation::idle1;
else
idleanim = Animation::idle2;
}
uint16 idlepid = a->doAnim(idleanim, dir_current);
Process *idlep = Kernel::get_instance()->getProcess(idlepid);
idlep->waitFor(pfp);
waitFor(idlep);
} else {
// wait 4-7 sec
DelayProcess *dp = new DelayProcess(30 * rs.getRandomNumberRng(4, 7));
Kernel::get_instance()->addProcess(dp);
dp->waitFor(pfp);
waitFor(dp);
}
}
Common::String LoiterProcess::dumpInfo() const {
return Process::dumpInfo() +
Common::String::format(", frames left: %d", _count);
}
void LoiterProcess::saveData(Common::WriteStream *ws) {
Process::saveData(ws);
ws->writeUint32LE(_count);
}
bool LoiterProcess::loadData(Common::ReadStream *rs, uint32 version) {
if (!Process::loadData(rs, version)) return false;
if (version >= 3)
_count = rs->readUint32LE();
else
_count = 0; // default to loitering indefinitely
return true;
}
} // End of namespace Ultima8
} // End of namespace Ultima

View File

@@ -0,0 +1,52 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef WORLD_ACTORS_LOITERPROCESS_H
#define WORLD_ACTORS_LOITERPROCESS_H
#include "ultima/ultima8/kernel/process.h"
namespace Ultima {
namespace Ultima8 {
class Actor;
class LoiterProcess : public Process {
public:
LoiterProcess();
LoiterProcess(Actor *actor, int32 count = -1);
ENABLE_RUNTIME_CLASSTYPE()
void run() override;
bool loadData(Common::ReadStream *rs, uint32 version);
void saveData(Common::WriteStream *ws) override;
Common::String dumpInfo() const override;
protected:
int32 _count;
};
} // End of namespace Ultima8
} // End of namespace Ultima
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,216 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef WORLD_ACTORS_MAINACTOR_H
#define WORLD_ACTORS_MAINACTOR_H
#include "ultima/ultima8/world/actors/actor.h"
namespace Ultima {
namespace Ultima8 {
class Debugger;
struct WeaponOverlayFrame;
class MainActor : public Actor {
friend class Debugger;
friend class Ultima8Engine;
public:
enum CruBatteryType {
NoBattery = 0,
ChemicalBattery = 1,
FissionBattery = 2,
FusionBattery = 3
};
MainActor();
~MainActor() override;
bool CanAddItem(Item *item, bool checkwghtvol = false) override;
bool addItem(Item *item, bool checkwghtvol = false) override;
//! Get the ShapeInfo object for this MainActor. Overridden because it changes
//! when Crusader is kneeling.
const ShapeInfo *getShapeInfoFromGameInstance() const override;
void move(int32 X, int32 Y, int32 Z) override;
//! Add item to avatar's inventory, but with some extra logic to do things like combine
//! ammo and credits, use batteries, etc.
int16 addItemCru(Item *item, bool showtoast);
//! Remove a single item - only called from an intrinsic
bool removeItemCru(Item *item);
//! teleport to the given location on the given map
void teleport(int mapNum, int32 x, int32 y, int32 z) override;
//! teleport to a teleport-destination egg
//! \param mapnum The map to teleport to
//! \param teleport_id The ID of the egg to teleport to
void teleport(int mapNum, int teleport_id); // to teleportegg
bool hasJustTeleported() const {
return _justTeleported;
}
void setJustTeleported(bool t) {
_justTeleported = t;
}
//! accumulate a little bit of strength. When you reach 650 you gain
//! one strength point. (There's a chance you gain strength sooner)
void accumulateStr(int n);
//! accumulate a little bit of dexterity. When you reach 650 you gain
//! one dex. point. (There's a chance you gain dex. sooner)
void accumulateDex(int n);
//! accumulate a little bit of intelligence. When you reach 650 you gain
//! one int. point. (There's a chance you gain int. sooner)
void accumulateInt(int n);
//! Get the GravityProcess of this Item, creating it if necessary
GravityProcess *ensureGravityProcess() override;
uint32 getArmourClass() const override;
uint16 getDefenseType() const override;
int16 getAttackingDex() const override;
int16 getDefendingDex() const override;
uint16 getDamageType() const override;
int getDamageAmount() const 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, uint16 damagePts, Direction srcDir) override;
const Std::string &getName() const {
return _name;
}
void setName(const Std::string &name) {
_name = name;
}
int16 getMaxEnergy();
CruBatteryType getBatteryType() const {
return _cruBatteryType;
}
void setBatteryType(CruBatteryType newbattery) {
_cruBatteryType = newbattery;
setMana(getMaxEnergy());
}
void setShieldType(uint16 shieldtype) {
_shieldType = shieldtype;
}
uint16 getShieldType() {
return _shieldType;
}
bool hasKeycard(int num) const;
void addKeycard(int bitno);
void clrKeycards() {
_keycards = 0;
}
uint16 getActiveInvItem() const {
return _activeInvItem;
}
//! Swap to the next active weapon (Crusader)
void nextWeapon();
//! Swap to the next inventory item (Crusader)
void nextInvItem();
//! Drop the current weapon (Crusader)
void dropWeapon();
//! Check if we can absorb a hit with the shield. Returns the modified damage value.
int receiveShieldHit(int damage, uint16 damage_type) override;
//! Detonate used bomb (Crusader)
void detonateBomb();
bool loadData(Common::ReadStream *rs, uint32 version);
void saveData(Common::WriteStream *ws) override;
ENABLE_RUNTIME_CLASSTYPE()
INTRINSIC(I_teleportToEgg);
INTRINSIC(I_accumulateStrength);
INTRINSIC(I_accumulateDexterity);
INTRINSIC(I_accumulateIntelligence);
INTRINSIC(I_clrAvatarInCombat);
INTRINSIC(I_setAvatarInCombat);
INTRINSIC(I_isAvatarInCombat);
INTRINSIC(I_getMaxEnergy);
INTRINSIC(I_hasKeycard);
INTRINSIC(I_clrKeycards);
INTRINSIC(I_addItemCru);
INTRINSIC(I_getNumberOfCredits);
INTRINSIC(I_switchMap);
INTRINSIC(I_removeItemCru);
void getWeaponOverlay(const WeaponOverlayFrame *&frame, uint32 &shape);
protected:
void useInventoryItem(uint32 shapenum);
void useInventoryItem(Item *item);
bool _justTeleported;
int _accumStr;
int _accumDex;
int _accumInt;
uint32 _keycards;
CruBatteryType _cruBatteryType;
uint16 _activeInvItem;
Std::string _name;
//! Process for a shield zap animation sprite
uint16 _shieldSpriteProc;
//! Type of shield (only used in Crusader)
uint16 _shieldType;
static ShapeInfo *_kneelingShapeInfo;
};
} // End of namespace Ultima8
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,51 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef WORLD_ACTORS_MONSTERINFO_H
#define WORLD_ACTORS_MONSTERINFO_H
#include "ultima/ultima8/world/actors/treasure_info.h"
namespace Ultima {
namespace Ultima8 {
struct MonsterInfo {
uint32 _shape;
uint16 _minHp, _maxHp;
uint16 _minDex, _maxDex;
uint16 _minDmg, _maxDmg;
uint16 _armourClass;
uint8 _alignment;
bool _unk;
uint16 _damageType;
uint16 _defenseType;
bool _resurrection; // auto-resurrection after being killed
bool _ranged; // ranged attack
bool _shifter; // shapeshifter
uint32 _explode; // shape to hurl around after being killed (or 0)
Std::vector<TreasureInfo> _treasure;
};
} // End of namespace Ultima8
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,189 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "ultima/ultima8/misc/debugger.h"
#include "ultima/ultima8/world/actors/npc_dat.h"
#include "ultima/ultima8/kernel/kernel.h"
#include "ultima/ultima8/ultima8.h"
#include "common/memstream.h"
namespace Ultima {
namespace Ultima8 {
NPCDat::NPCDat(Common::SeekableReadStream &rs, Common::SeekableReadStream &namers) {
char namebuf[33] = {0};
namers.read(namebuf, 32);
_name.assign(namebuf);
_minHp = rs.readUint16LE();
_maxHp = rs.readUint16LE();
//
rs.skip(20);
// offset 0x18 (24): wpntable offset
_wpnType2 = rs.readUint16LE();
// offset 0x1a (26): wpntype
_wpnType = rs.readUint16LE();
rs.skip(2);
// offset 30: default activity 0x6
_defaultActivity[0] = rs.readUint16LE();
// offset 0x3e (62): shape
rs.skip(62 - 32);
_shapeNo = rs.readUint16LE();
// offset 64: default activity 0x8
_defaultActivity[1] = rs.readUint16LE();
// offset 66: default activity 0xA
_defaultActivity[2] = rs.readUint16LE();
rs.skip(142 - 68);
}
/*static*/
Std::vector<NPCDat *> NPCDat::load(RawArchive *archive) {
Std::vector<NPCDat *> result;
assert(archive);
if (archive->getCount() < 2) {
warning("NPCDat: Archive does not include the expected objects.");
return result;
}
Common::MemoryReadStream datars(archive->get_object_nodel(0), archive->get_size(0));
Common::MemoryReadStream namers(archive->get_object_nodel(2), archive->get_size(2));
if (!datars.size() || !namers.size()) {
warning("NPCDat: Archive appears to be corrupt.");
return result;
}
while (!datars.eos() && !namers.eos() && (datars.size() - datars.pos() >= 142)) {
result.push_back(new NPCDat(datars, namers));
}
return result;
}
/*static*/
uint16 NPCDat::randomlyGetStrongerWeaponTypes(uint shapeno) {
// Apologies for the massive stack of constants, that's how
// it is in the original (fn at 10a0:3b10) :(
// This also combines the version from No Regret, which is the same
// for 899, 0x371, 0x385, 0x1b4, 0x2cb, 0x528, 0x338, 0x4d1, 0x4e6,
// and other differences as noted.
// Some shapes are only valid in each game, but that's ok.
Common::RandomSource &rs = Ultima8Engine::get_instance()->getRandomSource();
uint rnd = rs.getRandomNumber(UINT_MAX);
switch (shapeno) {
case 899: /* shape 899 - android */
if (rnd % 3 == 0)
return 10;
else
return 7;
case 0x2fd:
case 0x319: /* shape 793 - guardsq */
if (GAME_IS_REMORSE) {
if (rnd % 4 == 0)
return 0xc;
else
return 3;
} else {
if (rnd % 2 == 0)
return 8;
else
return 9;
}
case 0x1b4:
if (rnd % 4 == 0)
return 0xd;
else
return 9;
case 0x2cb: /* shape 715 - roaming (robot) */
if (rnd % 2 == 0)
return 3;
else
return 7;
case 0x338: /* shape 824 - thermatr (robot) */
if (rnd % 3 == 0)
return 5;
else
return 7;
case 0x371:
if (rnd % 3 == 0)
return 9;
else
return 10;
case 0x4d1:
if (rnd % 2 == 0)
return 4;
else
return 0xb;
case 900:
if (rnd % 3 == 0)
return 5;
else
return 10;
case 0x385:
if (rnd % 4 == 0)
return 8;
else
return 9;
case 0x3ac:
if (rnd % 2 == 0)
return 9;
else
return 0xd;
case 0x4e6:
if (rnd % 3 == 0)
return 5;
else
return 0xb;
case 0x528:
if (rnd % 3 == 0)
return 9;
else
return 8;
case 0x30c: // for No Remorse
if (rnd % 2 == 0)
return 4;
else
return 0xf;
case 0x308: // for No Remorse
if (rnd % 2 == 0)
return 10;
else
return 0xb;
case 0x57a: // for No Remorse
if (rnd % 2 == 0)
return 0xd;
else
return 0xf;
case 0x5e2: // for No Remorse
return 0xe;
default:
return GAME_IS_REMORSE ? 7 : 0xf;
}
}
} // End of namespace Ultima8
} // End of namespace Ultima

View File

@@ -0,0 +1,84 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef WORLD_ACTORS_NPC_DAT_H
#define WORLD_ACTORS_NPC_DAT_H
#include "ultima/shared/std/string.h"
#include "ultima/ultima8/filesys/raw_archive.h"
namespace Ultima {
namespace Ultima8 {
class NPCDat {
public:
NPCDat();
static Std::vector<NPCDat *> load(RawArchive *archive);
const Std::string &getName() const {
return _name;
};
uint16 getShapeNo() const {
return _shapeNo;
};
uint16 getMinHp() const {
return _minHp;
};
uint16 getMaxHp() const {
return _maxHp;
};
uint16 getWpnType() const {
return _wpnType;
};
uint16 getWpnType2() const {
return _wpnType2;
};
uint16 getDefaultActivity(int no) const {
assert(no >= 0 && no < 3);
return _defaultActivity[no];
}
//!< A function for randomly assigning stronger weapons for the highest difficulty level.
static uint16 randomlyGetStrongerWeaponTypes(uint shapeno);
private:
NPCDat(Common::SeekableReadStream &datars, Common::SeekableReadStream &namers);
Std::string _name;
uint16 _minHp;
uint16 _maxHp;
uint16 _shapeNo;
uint16 _wpnType;
uint16 _wpnType2;
uint16 _defaultActivity[3]; // activities 0x6, 0x8, and 0xA in game.
};
} // End of namespace Ultima8
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,124 @@
/* 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/pace_process.h"
#include "ultima/ultima8/world/actors/main_actor.h"
#include "ultima/ultima8/misc/direction_util.h"
#include "ultima/ultima8/kernel/kernel.h"
#include "ultima/ultima8/kernel/delay_process.h"
#include "ultima/ultima8/world/get_object.h"
namespace Ultima {
namespace Ultima8 {
DEFINE_RUNTIME_CLASSTYPE_CODE(PaceProcess)
PaceProcess::PaceProcess() : Process(), _counter(0) {
}
PaceProcess::PaceProcess(Actor *actor): _counter(0) {
assert(actor);
_itemNum = actor->getObjId();
_type = 0x255;
// Only pace with one process at a time.
Process *previous = Kernel::get_instance()->findProcess(_itemNum, _type);
if (previous)
previous->terminate();
}
bool PaceProcess::maybeStartDefaultActivity1(Actor *actor) {
uint16 activity = actor->getDefaultActivity(1);
uint16 cur_activity = actor->getCurrentActivityNo();
if (activity != 0 && cur_activity != activity && actor->canSeeControlledActor(false)) {
actor->setActivity(activity);
return true;
}
return false;
}
void PaceProcess::run() {
Actor *a = getActor(_itemNum);
Kernel *kernel = Kernel::get_instance();
assert(kernel);
if (!a || a->isDead()) {
// dead?
terminate();
return;
}
if (!a->hasFlags(Item::FLG_FASTAREA))
return;
if (maybeStartDefaultActivity1(a))
return;
if (a->isBusy()) {
return;
}
Animation::Result result = a->tryAnim(Animation::walk, a->getDir());
if (result == Animation::SUCCESS) {
_counter = 0;
uint16 walkprocid = a->doAnim(Animation::walk, a->getDir());
waitFor(walkprocid);
} else {
_counter++;
if (_counter > 1) {
uint32 shapeno = a->getShape();
if (shapeno == 0x2f5 || shapeno == 0x2f7 || shapeno != 0x2f6 ||
shapeno == 0x344 || shapeno == 0x597) {
a->setActivity(7); // surrender
} else {
a->setActivity(5); // attack
}
return;
}
// Stand, turn around, and wait for 60.
uint16 standprocid = a->doAnim(Animation::stand, a->getDir());
//debug("PaceProcess: actor %d turning from %d to %d", a->getObjId(),
// a->getDir(), Direction_Invert(a->getDir()));
uint16 turnprocid = a->turnTowardDir(Direction_Invert(a->getDir()), standprocid);
Process *waitproc = new DelayProcess(60);
Kernel::get_instance()->addProcess(waitproc);
waitproc->waitFor(turnprocid);
waitFor(waitproc);
}
}
void PaceProcess::saveData(Common::WriteStream *ws) {
Process::saveData(ws);
ws->writeByte(_counter);
}
bool PaceProcess::loadData(Common::ReadStream *rs, uint32 version) {
bool result = Process::loadData(rs, version);
if (result)
_counter = rs->readByte();
return result;
}
} // End of namespace Ultima8
} // End of namespace Ultima

View File

@@ -0,0 +1,53 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef WORLD_ACTORS_PACEPROCESS_H
#define WORLD_ACTORS_PACEPROCESS_H
#include "ultima/ultima8/kernel/process.h"
namespace Ultima {
namespace Ultima8 {
class Actor;
class PaceProcess : public Process {
public:
PaceProcess();
PaceProcess(Actor *actor);
ENABLE_RUNTIME_CLASSTYPE()
void run() override;
bool loadData(Common::ReadStream *rs, uint32 version);
void saveData(Common::WriteStream *ws) override;
private:
bool maybeStartDefaultActivity1(Actor *actor);
uint8 _counter;
};
} // End of namespace Ultima8
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,609 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "common/system.h"
#include "ultima/ultima.h"
#include "ultima/ultima8/misc/direction_util.h"
#include "ultima/ultima8/world/actors/actor.h"
#include "ultima/ultima8/world/actors/animation_tracker.h"
#ifdef DEBUG_PATHFINDER
#include "graphics/screen.h"
#include "ultima/ultima8/gumps/game_map_gump.h"
#endif
namespace Ultima {
namespace Ultima8 {
#ifdef DEBUG_PATHFINDER
ObjId Pathfinder::_visualDebugActor = 0xFFFF;
#endif
struct PathNode {
PathfindingState state;
unsigned int depth;
unsigned int cost;
unsigned int heuristicTotalCost;
PathNode *parent;
uint32 stepsfromparent;
};
// NOTE: this is just to keep some statistics
static unsigned int expandednodes = 0;
void PathfindingState::load(const Actor *_actor) {
_point = _actor->getLocation();
_lastAnim = _actor->getLastAnim();
_direction = _actor->getDir();
_firstStep = _actor->hasActorFlags(Actor::ACT_FIRSTSTEP);
_flipped = _actor->hasFlags(Item::FLG_FLIPPED);
_combat = _actor->isInCombat();
}
bool PathfindingState::checkPoint(const Point3 &pt,
int sqr_range) const {
int distance = _point.sqrDist(pt);
return distance < sqr_range;
}
bool PathfindingState::checkItem(const Item *item, int xyRange, int zRange) const {
int32 itemXd, itemYd, itemZd;
int32 itemXmin, itemYmin;
Point3 pt = item->getLocationAbsolute();
item->getFootpadWorld(itemXd, itemYd, itemZd);
itemXmin = pt.x - itemXd;
itemYmin = pt.y - itemYd;
int range = 0;
if (_point.x - pt.x > range)
range = _point.x - pt.x;
if (itemXmin - _point.x > range)
range = itemXmin - _point.x;
if (_point.y - pt.y > range)
range = _point.y - pt.y;
if (itemYmin - _point.y > range)
range = itemYmin - _point.y;
// FIXME: check _point.z properly
return (range <= xyRange);
}
bool PathfindingState::checkHit(const Actor *_actor, const Item *target) const {
assert(target);
debugC(kDebugPath, "Trying hit in _direction %d", _actor->getDirToItemCentre(*target));
AnimationTracker tracker;
if (!tracker.init(_actor, Animation::attack,
_actor->getDirToItemCentre(*target), this)) {
return false;
}
while (tracker.step()) {
if (tracker.hitSomething()) break;
}
ObjId hit = tracker.hitSomething();
if (hit == target->getObjId()) return true;
return false;
}
bool PathNodeCmp::operator()(const PathNode *n1, const PathNode *n2) const {
return (n1->heuristicTotalCost > n2->heuristicTotalCost);
}
Pathfinder::Pathfinder() : _actor(nullptr), _targetItem(nullptr),
_hitMode(false), _expandTime(0), _target(),
_actorXd(0), _actorYd(0), _actorZd(0) {
expandednodes = 0;
_visited.reserve(1500);
}
Pathfinder::~Pathfinder() {
debugC(kDebugPath, "~Pathfinder: %u nodes to clean up, visited %u and %u expanded nodes in %dms.",
_cleanupNodes.size(), _visited.size(), expandednodes, _expandTime);
// clean up _nodes
for (auto *node : _cleanupNodes)
delete node;
_cleanupNodes.clear();
}
void Pathfinder::init(Actor *actor, PathfindingState *state) {
_actor = actor;
_actor->getFootpadWorld(_actorXd, _actorYd, _actorZd);
if (state)
_start = *state;
else
_start.load(_actor);
}
void Pathfinder::setTarget(const Point3 &pt) {
_target = pt;
_targetItem = 0;
_hitMode = false;
}
void Pathfinder::setTarget(Item *item, bool hit) {
Container *root = item->getRootContainer();
_targetItem = root ? root : item;
// set target to centre of item for the cost heuristic
_target = item->getCentre();
_target.z = item->getZ();
if (hit) {
assert(_start._combat);
assert(dynamic_cast<Actor *>(_targetItem));
_hitMode = true;
} else {
_hitMode = false;
}
}
bool Pathfinder::canReach() {
Std::vector<PathfindingAction> path;
return pathfind(path);
}
bool Pathfinder::alreadyVisited(const Point3 &pt) const {
//
// There are more efficient search structures we could use for
// this, but for the number of points we end up having even on
// pathfind failure (~1200) the fancy structures don't justify
// their extra overhead.
//
// Linear search of an array is just as fast, or slightly faster.
//
for (const auto &i : _visited) {
if (i.checkPoint(pt, 8*8))
return true;
}
return false;
}
bool Pathfinder::checkTarget(const PathNode *node) const {
// TODO: these ranges are probably a bit too high,
// but otherwise it won't work properly yet -wjp
if (_targetItem) {
if (_hitMode) {
return node->state.checkHit(_actor, _targetItem);
} else {
return node->state.checkItem(_targetItem, 32, 8);
}
} else {
return node->state.checkPoint(_target, 48*48);
}
}
unsigned int Pathfinder::costHeuristic(PathNode *node) const {
unsigned int cost = node->cost;
#if 0
double sqrddist;
sqrddist = (_target.x - node->state._point.x + _actorXd / 2) *
(_target.x - node->state._point.x + _actorXd / 2);
sqrddist += (_target.y - node->state._point.y + _actorYd / 2) *
(_target.y - node->state._point.y + _actorYd / 2);
unsigned int dist = static_cast<unsigned int>(sqrt(sqrddist));
#else
// This calculates the distance to the target using only lines in
// the 8 available directions (instead of the straight line above)
int xd = ABS(_target.x - node->state._point.x + _actorXd / 2);
int yd = ABS(_target.y - node->state._point.y + _actorYd / 2);
double m = (xd < yd) ? xd : yd;
unsigned int dist = ABS(xd - yd) + static_cast<unsigned int>(m * 1.41421356);
#endif
#if 0
//!! TODO: divide dist by walking distance
// (using 32 for now)
dist /= 32;
node->heuristicTotalCost = cost + (dist * 4); //!! constant
#else
// Weigh remaining distance more than already travelled distance,
// to try to explore more nodes closer to the target.
node->heuristicTotalCost = 2 * cost + 3 * dist;
#endif
return node->heuristicTotalCost;
}
#ifdef DEBUG_PATHFINDER
static void drawbox(Graphics::ManagedSurface *screen, const Item *item) {
int32 cx, cy, cz;
Ultima8Engine::get_instance()->getGameMapGump()->GetCameraLocation(cx, cy, cz);
Common::Rect d = screen->getBounds();
int32 ix, iy, iz;
item->getLocation(ix, iy, iz);
int32 xd, yd, zd;
item->getFootpadWorld(xd, yd, zd);
ix -= cx;
iy -= cy;
iz -= cz;
int32 x0, y0, x1, y1, x2, y2, x3, y3;
x0 = (d.width() / 2) + (ix - iy) / 4;
y0 = (d.height() / 2) + (ix + iy) / 8 - iz;
x1 = (d.width() / 2) + (ix - iy) / 4;
y1 = (d.height() / 2) + (ix + iy) / 8 - (iz + zd);
x2 = (d.width() / 2) + (ix - xd - iy) / 4;
y2 = (d.height() / 2) + (ix - xd + iy) / 8 - iz;
x3 = (d.width() / 2) + (ix - iy + yd) / 4;
y3 = (d.height() / 2) + (ix + iy - yd) / 8 - iz;
uint32 color = screen->format.RGBToColor(0x00, 0x00, 0xFF);
screen->fillRect(Common::Rect(x0 - 1, y0 - 1, x0 + 2, y0 + 2), color);
color = screen->format.RGBToColor(0x00, 0xFF, 0x00);
screen->drawLine(x0, y0, x1, y1, color);
screen->drawLine(x0, y0, x2, y2, color);
screen->drawLine(x0, y0, x3, y3, color);
}
static void drawdot(Graphics::ManagedSurface *screen, int32 x, int32 y, int32 Z, int size, uint32 rgb) {
int32 cx, cy, cz;
Ultima8Engine::get_instance()->getGameMapGump()->GetCameraLocation(cx, cy, cz);
Common::Rect d = screen->getBounds();
x -= cx;
y -= cy;
Z -= cz;
int32 x0, y0;
x0 = (d.width() / 2) + (x - y) / 4;
y0 = (d.height() / 2) + (x + y) / 8 - Z;
screen->fillRect(Common::Rect(x0 - size, y0 - size, x0 + size + 1, y0 + size + 1), rgb);
}
static void drawedge(Graphics::ManagedSurface *screen, const PathNode *from, const PathNode *to, uint32 rgb) {
int32 cx, cy, cz;
Ultima8Engine::get_instance()->getGameMapGump()->GetCameraLocation(cx, cy, cz);
Common::Rect d = screen->getBounds();
int32 x0, y0, x1, y1;
cx = from->state._point.x - cx;
cy = from->state._point.y - cy;
cz = from->state._point.z - cz;
x0 = (d.width() / 2) + (cx - cy) / 4;
y0 = (d.height() / 2) + (cx + cy) / 8 - cz;
Ultima8Engine::get_instance()->getGameMapGump()->GetCameraLocation(cx, cy, cz);
cx = to->state._point.x - cx;
cy = to->state._point.y - cy;
cz = to->state._point.z - cz;
x1 = (d.width() / 2) + (cx - cy) / 4;
y1 = (d.height() / 2) + (cx + cy) / 8 - cz;
screen->drawLine(x0, y0, x1, y1, rgb);
}
static void drawpath(Graphics::ManagedSurface *screen, PathNode *to, uint32 rgb, bool done) {
PathNode *n1 = to;
PathNode *n2 = to->parent;
uint32 color1 = screen->format.RGBToColor(0xFF, 0x00, 0x00);
uint32 color2 = screen->format.RGBToColor(0xFF, 0xFF, 0xFF);
while (n2) {
drawedge(screen, n1, n2, rgb);
if (done && n1 == to)
drawdot(screen, n1->state._point.x, n1->state._point.y, n1->state._point.z, 2, color1);
else
drawdot(screen, n1->state._point.x, n1->state._point.y, n1->state._point.z, 1, color2);
drawdot(screen, n2->state._point.x, n2->state._point.y, n2->state._point.z, 2, color2);
n1 = n2;
n2 = n1->parent;
}
}
#endif
void Pathfinder::newNode(PathNode *oldnode, PathfindingState &state,
unsigned int steps) {
PathNode *newnode = new PathNode();
newnode->state = state;
newnode->parent = oldnode;
newnode->depth = oldnode->depth + 1;
newnode->stepsfromparent = 0;
double sqrddist;
sqrddist = ((newnode->state._point.x - oldnode->state._point.x) *
(newnode->state._point.x - oldnode->state._point.x));
sqrddist += ((newnode->state._point.y - oldnode->state._point.y) *
(newnode->state._point.y - oldnode->state._point.y));
sqrddist += ((newnode->state._point.z - oldnode->state._point.z) *
(newnode->state._point.z - oldnode->state._point.z));
unsigned int dist;
dist = static_cast<unsigned int>(sqrt(sqrddist));
int turn = 0;
if (oldnode->depth > 0) {
turn = state._direction - oldnode->state._direction;
if (turn < 0) turn = -turn;
if (turn > 8) turn = 16 - turn;
}
newnode->cost = oldnode->cost + dist + 32 * turn; //!! constant
bool done = checkTarget(newnode);
if (done)
newnode->heuristicTotalCost = 0;
else
costHeuristic(newnode);
debugC(kDebugPath, "Trying dir %d, steps %d from (%d, %d) to (%d, %d), cost %d, heurtotcost %d",
state._direction, steps,
oldnode->state._point.x, oldnode->state._point.y, newnode->state._point.x, newnode->state._point.y,
newnode->cost, newnode->heuristicTotalCost);
#ifdef DEBUG_PATHFINDER
if (_actor->getObjId() == _visualDebugActor) {
Graphics::Screen *screen = Ultima8Engine::get_instance()->getScreen();
uint32 color = screen->format.RGBToColor(0xFF, 0xFF, 0x00);
drawpath(screen, newnode, color, done);
screen->update();
g_system->delayMillis(50);
if (!done) {
color = screen->format.RGBToColor(0xB0, 0xB0, 0x00);
drawpath(screen, newnode, color, done);
screen->update();
}
}
#endif
_nodes.push(newnode);
}
void Pathfinder::expandNode(PathNode *node) {
Animation::Sequence walkanim = Animation::walk;
PathfindingState state, closeststate;
AnimationTracker tracker;
expandednodes++;
if (_actor->isInCombat())
walkanim = Animation::advance;
// try walking in all 8 directions - TODO: should this support 16 dirs?
Direction dir = dir_north;
for (int i = 0; i < 8; i++) {
dir = Direction_OneRight(dir, dirmode_8dirs);
state = node->state;
state._lastAnim = walkanim;
state._direction = dir;
state._combat = _actor->isInCombat();
if (!tracker.init(_actor, walkanim, dir, &state)) continue;
// determine how far the _actor will travel if the animation runs to completion
Point3 max_end;
tracker.evaluateMaxAnimTravel(max_end.x, max_end.y, dir);
max_end.z = state._point.z;
if (alreadyVisited(max_end))
continue;
const int x_travel = ABS(max_end.x - state._point.x);
const int y_travel = ABS(max_end.y - state._point.y);
const int xy_maxtravel = MAX(x_travel, y_travel);
int sqrddist = x_travel * x_travel + y_travel * y_travel;
if (sqrddist > 400) {
// range is greater than 20; see if a node has been visited at range 10
Point3 pt = state._point;
pt.x += x_travel * 10 / xy_maxtravel;
pt.y += y_travel * 10 / xy_maxtravel;
if (alreadyVisited(pt)) {
continue;
}
}
uint32 steps = 0, beststeps = 0;
int bestsqdist;
bestsqdist = (_target.x - node->state._point.x + _actorXd / 2) *
(_target.x - node->state._point.x + _actorXd / 2);
bestsqdist += (_target.y - node->state._point.y + _actorYd / 2) *
(_target.y - node->state._point.y + _actorYd / 2);
while (tracker.step()) {
steps++;
tracker.updateState(state);
sqrddist = (_target.x - state._point.x + _actorXd / 2) *
(_target.x - state._point.x + _actorXd / 2);
sqrddist += (_target.y - state._point.y + _actorYd / 2) *
(_target.y - state._point.y + _actorYd / 2);
if (sqrddist < bestsqdist) {
bestsqdist = sqrddist;
beststeps = steps;
closeststate = state;
}
}
if (tracker.isDone()) {
tracker.updateState(state);
if (!alreadyVisited(state._point)) {
newNode(node, state, 0);
_visited.push_back(state);
}
} else {
// an obstruction was encountered, so generate a visited node to block
// future evaluation at the endpoint.
_visited.push_back(state);
}
// TODO: maybe only allow partial steps close to target?
if (beststeps != 0 && (beststeps != steps ||
(!tracker.isDone() && _targetItem))) {
newNode(node, closeststate, beststeps);
_visited.push_back(closeststate);
}
}
}
bool Pathfinder::pathfind(Std::vector<PathfindingAction> &path) {
if (_targetItem) {
debugC(kDebugPath, "Actor %u pathfinding to item %u", _actor->getObjId(), _targetItem->getObjId());
debugC(kDebugPath, "Target Item: %s", _targetItem->dumpInfo().c_str());
} else {
debugC(kDebugPath, "Actor %u pathfinding to (%d, %d, %d)", _actor->getObjId(), _target.x, _target.y, _target.z);
}
#ifdef DEBUG_PATHFINDER
if (_actor->getObjId() == _visualDebugActor) {
Graphics::Screen *screen = Ultima8Engine::get_instance()->getScreen();
if (_targetItem) {
drawbox(screen, _targetItem);
} else {
uint32 color = screen->format.RGBToColor(0x00, 0x00, 0xFF);
drawdot(screen, _targetX, _targetY, _targetZ, 2, color);
}
screen->update();
}
#endif
path.clear();
PathNode *startnode = new PathNode();
startnode->state = _start;
startnode->cost = 0;
startnode->parent = nullptr;
startnode->depth = 0;
startnode->stepsfromparent = 0;
_nodes.push(startnode);
unsigned int expandedNodes = 0;
const unsigned int NODELIMIT_MIN = 30; //! constant
const unsigned int NODELIMIT_MAX = 200; //! constant
bool found = false;
uint32 starttime = g_system->getMillis();
while (expandedNodes < NODELIMIT_MAX && !_nodes.empty() && !found) {
// Take a copy here as the pop() below deletes the old node
PathNode *node = new PathNode(*_nodes.top());
_cleanupNodes.push_back(node);
_nodes.pop();
debugC(kDebugPath, "Trying node: (%d, %d, %d) target=(%d, %d, %d)",
node->state._point.x, node->state._point.y, node->state._point.z,
_target.x, _target.y, _target.z);
if (checkTarget(node)) {
// done!
// find path length
const PathNode *n = node;
unsigned int length = 0;
while (n->parent) {
n = n->parent;
length++;
}
debugC(kDebugPath, "Pathfinder: path found (length = %u)", length);
unsigned int i = length;
if (length > 0) length++; // add space for final 'stand' action
path.resize(length);
// now backtrack through the _nodes to assemble the final animation
while (node->parent) {
PathfindingAction action;
action._action = node->state._lastAnim;
action._direction = node->state._direction;
action._steps = node->stepsfromparent;
path[--i] = action;
debugC(kDebugPath, "anim = %d, dir = %d, steps = %d",
node->state._lastAnim, node->state._direction, node->stepsfromparent);
//TODO: check how turns work
//TODO: append final 'stand' animation
node = node->parent;
}
if (length) {
if (node->state._combat)
path[length - 1]._action = Animation::combatStand;
else
path[length - 1]._action = Animation::stand;
path[length - 1]._direction = path[length - 2]._direction;
}
_expandTime = g_system->getMillis() - starttime;
return true;
}
expandNode(node);
expandedNodes++;
if (expandedNodes >= NODELIMIT_MIN && ((expandedNodes) % 5) == 0) {
uint32 elapsed_ms = g_system->getMillis() - starttime;
if (elapsed_ms > 350) break;
}
}
_expandTime = g_system->getMillis() - starttime;
static int32 pfcalls = 0;
static int32 pftotaltime = 0;
pfcalls++;
pftotaltime += _expandTime;
debugC(kDebugPath, "maxout average = %dms.", pftotaltime / pfcalls);
return false;
}
} // End of namespace Ultima8
} // End of namespace Ultima

View File

@@ -0,0 +1,116 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef ULTIMA8_WORLD_ACTORS_PATHFINDER_H
#define ULTIMA8_WORLD_ACTORS_PATHFINDER_H
#include "ultima/shared/std/containers.h"
#include "ultima/ultima8/misc/direction.h"
#include "ultima/ultima8/misc/point3.h"
#include "ultima/ultima8/misc/priority_queue.h"
#include "ultima/ultima8/world/actors/animation.h"
//#define DEBUG_PATHFINDER
namespace Ultima {
namespace Ultima8 {
class Actor;
class Item;
struct PathfindingState {
PathfindingState() : _point(), _direction(dir_north),
_lastAnim(Animation::walk), _flipped(false),
_firstStep(true), _combat(false) {};
Point3 _point;
Animation::Sequence _lastAnim;
Direction _direction;
bool _flipped;
bool _firstStep;
bool _combat;
void load(const Actor *actor);
bool checkPoint(const Point3 &pt, int range) const;
bool checkItem(const Item *item, int xyRange, int zRange) const;
bool checkHit(const Actor *actor, const Item *target) const;
};
struct PathfindingAction {
Animation::Sequence _action;
Direction _direction;
uint32 _steps;
};
struct PathNode;
class PathNodeCmp {
public:
bool operator()(const PathNode *n1, const PathNode *n2) const;
};
class Pathfinder {
public:
Pathfinder();
~Pathfinder();
void init(Actor *actor, PathfindingState *state = 0);
void setTarget(const Point3 &pt);
void setTarget(Item *item, bool hit = false);
//! try to reach the target by pathfinding
bool canReach();
//! pathfind. If true, the found path is returned in path
bool pathfind(Std::vector<PathfindingAction> &path);
#ifdef DEBUG_PATHFINDER
static ObjId _visualDebugActor;
#endif
protected:
PathfindingState _start;
Actor *_actor;
Point3 _target;
Item *_targetItem;
bool _hitMode;
int32 _expandTime;
int32 _actorXd, _actorYd, _actorZd;
Common::Array<PathfindingState> _visited;
PriorityQueue<PathNode *, Std::vector<PathNode *>, PathNodeCmp> _nodes;
/** List of nodes for garbage collection later and order is not important */
Std::vector<PathNode *> _cleanupNodes;
bool alreadyVisited(const Point3 &pt) const;
void newNode(PathNode *oldnode, PathfindingState &state,
unsigned int steps);
void expandNode(PathNode *node);
unsigned int costHeuristic(PathNode *node) const;
bool checkTarget(const PathNode *node) const;
};
} // End of namespace Ultima8
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,268 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "ultima/ultima.h"
#include "ultima/ultima8/world/actors/pathfinder_process.h"
#include "ultima/ultima8/world/actors/actor.h"
#include "ultima/ultima8/world/get_object.h"
#include "ultima/ultima8/misc/direction_util.h"
namespace Ultima {
namespace Ultima8 {
static const unsigned int PATH_OK = 1;
static const unsigned int PATH_FAILED = 0;
DEFINE_RUNTIME_CLASSTYPE_CODE(PathfinderProcess)
PathfinderProcess::PathfinderProcess() : Process(),
_currentStep(0), _targetItem(0), _hitMode(false),
_target() {
}
PathfinderProcess::PathfinderProcess(Actor *actor, ObjId itemid, bool hit) :
_currentStep(0), _targetItem(itemid), _hitMode(hit),
_target() {
assert(actor);
_itemNum = actor->getObjId();
_type = PATHFINDER_PROC_TYPE;
Item *item = getItem(itemid);
if (!item) {
warning("PathfinderProcess: non-existent target");
// can't get there...
_result = PATH_FAILED;
terminateDeferred();
return;
}
assert(_targetItem);
_target = item->getLocation();
Pathfinder pf;
pf.init(actor);
pf.setTarget(item, hit);
bool ok = pf.pathfind(_path);
if (!ok) {
// can't get there...
debugC(kDebugPath, "PathfinderProcess: actor %d failed to find path", _itemNum);
_result = PATH_FAILED;
terminateDeferred();
return;
}
// TODO: check if flag already set? kill other pathfinders?
actor->setActorFlag(Actor::ACT_PATHFINDING);
}
PathfinderProcess::PathfinderProcess(Actor *actor, const Point3 &target) :
_target(target), _targetItem(0), _currentStep(0),
_hitMode(false) {
assert(actor);
_itemNum = actor->getObjId();
_type = PATHFINDER_PROC_TYPE;
Pathfinder pf;
pf.init(actor);
pf.setTarget(_target);
bool ok = pf.pathfind(_path);
if (!ok) {
// can't get there...
debugC(kDebugPath, "PathfinderProcess: actor %d failed to find path", _itemNum);
_result = PATH_FAILED;
terminateDeferred();
return;
}
// TODO: check if flag already set? kill other pathfinders?
actor->setActorFlag(Actor::ACT_PATHFINDING);
}
PathfinderProcess::~PathfinderProcess() {
}
void PathfinderProcess::terminate() {
Actor *actor = getActor(_itemNum);
if (actor) {
// TODO: only clear if it was set by us?
// (slightly more complicated if we kill other pathfinders on startup)
actor->clearActorFlag(Actor::ACT_PATHFINDING);
}
Process::terminate();
}
void PathfinderProcess::run() {
Actor *actor = getActor(_itemNum);
assert(actor);
// if not in the fastarea, do nothing
if (!actor->hasFlags(Item::FLG_FASTAREA)) return;
bool ok = true;
if (_targetItem) {
Item *item = getItem(_targetItem);
if (!item) {
warning("PathfinderProcess: target missing");
_result = PATH_FAILED;
terminate();
return;
}
Point3 cur = item->getLocation();
if (ABS(cur.x - _target.x) >= 32 || ABS(cur.y - _target.y) >= 32 ||
ABS(cur.z - _target.z) >= 8) {
// target moved
ok = false;
}
}
if (ok && _currentStep >= _path.size()) {
// done
debugC(kDebugPath, "PathfinderProcess: done");
_result = PATH_OK;
terminate();
return;
}
// try to take the next step
debugC(kDebugPath, "PathfinderProcess: trying step");
// if actor is still animating for whatever reason, wait until he stopped
// FIXME: this should happen before the pathfinder is actually called,
// since the running animation may move the actor, which could break
// the found _path.
if (actor->hasActorFlags(Actor::ACT_ANIMLOCK)) {
debugC(kDebugPath, "PathfinderProcess: ANIMLOCK, waiting");
return;
}
if (ok) {
ok = actor->tryAnim(_path[_currentStep]._action,
_path[_currentStep]._direction,
_path[_currentStep]._steps) == Animation::SUCCESS;
}
if (!ok) {
debugC(kDebugPath, "PathfinderProcess: recalculating _path");
// need to redetermine _path
ok = true;
Pathfinder pf;
pf.init(actor);
if (_targetItem) {
Item *item = getItem(_targetItem);
if (!item)
ok = false;
else {
if (_hitMode && !actor->isInCombat()) {
// Actor exited combat mode
_hitMode = false;
}
pf.setTarget(item, _hitMode);
_target = item->getLocation();
}
} else {
pf.setTarget(_target);
}
if (ok)
ok = pf.pathfind(_path);
_currentStep = 0;
if (!ok) {
// can't get there anymore
debugC(kDebugPath, "PathfinderProcess: actor %d failed to find path", _itemNum);
_result = PATH_FAILED;
terminate();
return;
}
}
if (_currentStep >= _path.size()) {
debugC(kDebugPath, "PathfinderProcess: done");
// done
_result = PATH_OK;
terminate();
return;
}
uint16 animpid = actor->doAnim(_path[_currentStep]._action,
_path[_currentStep]._direction,
_path[_currentStep]._steps);
debugC(kDebugPath, "PathfinderProcess(%u): taking step %d, %d (animpid=%u)",
getPid(), _path[_currentStep]._action, _path[_currentStep]._direction, animpid);
_currentStep++;
waitFor(animpid);
}
void PathfinderProcess::saveData(Common::WriteStream *ws) {
Process::saveData(ws);
ws->writeUint16LE(_targetItem);
ws->writeUint16LE(static_cast<uint16>(_target.x));
ws->writeUint16LE(static_cast<uint16>(_target.y));
ws->writeUint16LE(static_cast<uint16>(_target.z));
ws->writeByte(_hitMode ? 1 : 0);
ws->writeUint16LE(static_cast<uint16>(_currentStep));
ws->writeUint16LE(static_cast<uint16>(_path.size()));
for (unsigned int i = 0; i < _path.size(); ++i) {
ws->writeUint16LE(static_cast<uint16>(_path[i]._action));
ws->writeUint16LE(static_cast<uint16>(Direction_ToUsecodeDir(_path[i]._direction)));
}
}
bool PathfinderProcess::loadData(Common::ReadStream *rs, uint32 version) {
if (!Process::loadData(rs, version)) return false;
// Type previously missing from constructor
if (_type == 0) {
_type = PATHFINDER_PROC_TYPE;
}
_targetItem = rs->readUint16LE();
_target.x = rs->readUint16LE();
_target.y = rs->readUint16LE();
_target.z = rs->readUint16LE();
_hitMode = (rs->readByte() != 0);
_currentStep = rs->readUint16LE();
unsigned int pathsize = rs->readUint16LE();
_path.resize(pathsize);
for (unsigned int i = 0; i < pathsize; ++i) {
_path[i]._action = static_cast<Animation::Sequence>(rs->readUint16LE());
_path[i]._direction = Direction_FromUsecodeDir(rs->readUint16LE());
}
return true;
}
} // End of namespace Ultima8
} // End of namespace Ultima

View File

@@ -0,0 +1,65 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef WORLD_ACTORS_PATHFINDERPROCESS_H
#define WORLD_ACTORS_PATHFINDERPROCESS_H
#include "ultima/ultima8/kernel/process.h"
#include "ultima/ultima8/world/actors/pathfinder.h"
#include "ultima/ultima8/misc/point3.h"
namespace Ultima {
namespace Ultima8 {
class Actor;
class PathfinderProcess : public Process {
public:
PathfinderProcess();
PathfinderProcess(Actor *actor, ObjId item, bool hit = false);
PathfinderProcess(Actor *actor, const Point3 &target);
~PathfinderProcess() override;
ENABLE_RUNTIME_CLASSTYPE()
void run() override;
void terminate() override;
bool loadData(Common::ReadStream *rs, uint32 version);
void saveData(Common::WriteStream *ws) override;
protected:
Point3 _target;
ObjId _targetItem;
bool _hitMode;
Std::vector<PathfindingAction> _path;
unsigned int _currentStep;
public:
static const uint16 PATHFINDER_PROC_TYPE = 0x204;
};
} // End of namespace Ultima8
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,363 @@
/* 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/quick_avatar_mover_process.h"
#include "ultima/ultima8/misc/direction_util.h"
#include "ultima/ultima8/world/actors/main_actor.h"
#include "ultima/ultima8/world/world.h"
#include "ultima/ultima8/world/current_map.h"
#include "ultima/ultima8/kernel/kernel.h"
#include "ultima/ultima8/ultima8.h"
#include "ultima/ultima8/world/camera_process.h"
#include "ultima/ultima8/world/get_object.h"
#include "ultima/ultima8/world/actors/avatar_mover_process.h"
namespace Ultima {
namespace Ultima8 {
DEFINE_RUNTIME_CLASSTYPE_CODE(QuickAvatarMoverProcess)
ProcId QuickAvatarMoverProcess::_amp = 0;
bool QuickAvatarMoverProcess::_enabled = false;
bool QuickAvatarMoverProcess::_clipping = false;
QuickAvatarMoverProcess::QuickAvatarMoverProcess() : Process(1), _movementFlags(0) {
_amp = getPid();
}
QuickAvatarMoverProcess::~QuickAvatarMoverProcess() {
}
void QuickAvatarMoverProcess::run() {
if (!isEnabled()) {
terminate();
return;
}
MainActor *avatar = getMainActor();
Direction direction = avatar->getDir();
DirectionMode mode = GAME_IS_U8 ? dirmode_8dirs : dirmode_16dirs;
int32 dx = 0;
int32 dy = 0;
int32 dz = 0;
if (hasMovementFlags(MOVE_UP)) {
dx -= 64;
dy -= 64;
}
if (hasMovementFlags(MOVE_DOWN)) {
dx += 64;
dy += 64;
}
if (hasMovementFlags(MOVE_LEFT)) {
dx -= 64;
dy += 64;
}
if (hasMovementFlags(MOVE_RIGHT)) {
dx += 64;
dy -= 64;
}
if (hasMovementFlags(MOVE_ASCEND)) {
dz += 8;
}
if (hasMovementFlags(MOVE_DESCEND)) {
dz -= 8;
}
// Limit speed of turning by checking
uint32 frameNum = Kernel::get_instance()->getFrameNum();
if (frameNum % 4 == 0) {
if (hasMovementFlags(MOVE_TURN_LEFT)) {
direction = Direction_OneLeft(direction, mode);
}
if (hasMovementFlags(MOVE_TURN_RIGHT)) {
direction = Direction_OneRight(direction, mode);
}
}
if (hasMovementFlags(MOVE_FORWARD)) {
int xoff = 32 * Direction_XFactor(direction);
int yoff = 32 * Direction_YFactor(direction);
if (dirmode_8dirs) {
xoff *= 2;
yoff *= 2;
}
dx += xoff;
dy += yoff;
}
if (hasMovementFlags(MOVE_BACK)) {
int xoff = 32 * Direction_XFactor(direction);
int yoff = 32 * Direction_YFactor(direction);
if (dirmode_8dirs) {
xoff *= 2;
yoff *= 2;
}
dx -= xoff;
dy -= yoff;
}
if (direction != avatar->getDir()) {
avatar->setDir(direction);
avatar->setToStartOfAnim(Animation::stand);
}
if (!dx && !dy && !dz) {
return;
}
if (hasMovementFlags(MOVE_SLOW)) {
dx /= 4;
dy /= 4;
dz /= 4;
} else if (hasMovementFlags(MOVE_FAST)) {
dx *= 4;
dy *= 4;
dz *= 4;
}
Point3 pt = avatar->getLocation();
int32 ixd, iyd, izd;
avatar->getFootpadWorld(ixd, iyd, izd);
CurrentMap *cm = World::get_instance()->getCurrentMap();
int32 dxv = dx;
int32 dyv = dy;
int32 dzv = dz;
if (_clipping) {
for (int j = 0; j < 3; j++) {
dxv = dx;
dyv = dy;
dzv = dz;
if (j == 1)
dxv = 0;
else if (j == 2)
dyv = 0;
bool ok = false;
while (dxv || dyv || dzv) {
uint32 shapeFlags = avatar->getShapeInfo()->_flags;
Box start(pt.x, pt.y, pt.z, ixd, iyd, izd);
PositionInfo info = cm->getPositionInfo(Box(pt.x + dxv, pt.y + dyv, pt.z + dzv, ixd, iyd, izd), start, shapeFlags, 1);
if (info.valid) {
if (!dzv && !info.supported) {
// Adjust to stay on ground
if (cm->getPositionInfo(Box(pt.x + dxv, pt.y + dyv, pt.z - 8, ixd, iyd, izd), start, shapeFlags, 1).valid &&
!cm->getPositionInfo(Box(pt.x, pt.y, pt.z - 8, ixd, iyd, izd), start, shapeFlags, 1).valid) {
dzv = -8;
} else if (cm->getPositionInfo(Box(pt.x + dxv, pt.y + dyv, pt.z - 16, ixd, iyd, izd), start, shapeFlags, 1).valid &&
!cm->getPositionInfo(Box(pt.x, pt.y, pt.z - 16, ixd, iyd, izd), start, shapeFlags, 1).valid) {
dzv = -16;
} else if (cm->getPositionInfo(Box(pt.x + dxv, pt.y + dyv, pt.z - 24, ixd, iyd, izd), start, shapeFlags, 1).valid &&
!cm->getPositionInfo(Box(pt.x, pt.y, pt.z - 24, ixd, iyd, izd), start, shapeFlags, 1).valid) {
dzv = -24;
} else if (cm->getPositionInfo(Box(pt.x + dxv, pt.y + dyv, pt.z - 32, ixd, iyd, izd), start, shapeFlags, 1).valid &&
!cm->getPositionInfo(Box(pt.x, pt.y, pt.z - 32, ixd, iyd, izd), start, shapeFlags, 1).valid) {
dzv = -32;
}
}
ok = true;
break;
} else if (cm->getPositionInfo(Box(pt.x + dxv, pt.y + dyv, pt.z + dzv + 8, ixd, iyd, izd), start, shapeFlags, 1).valid) {
dzv += 8;
ok = true;
break;
}
dxv /= 2;
dyv /= 2;
dzv /= 2;
}
if (ok)
break;
}
}
// Yes, i know, not entirely correct
avatar->collideMove(pt.x + dxv, pt.y + dyv, pt.z + dzv, false, true);
if (GAME_IS_CRUSADER) {
// Keep the camera on the avatar while we're quick-moving.
Point3 cpt(pt.x + dxv, pt.y + dyv, pt.z + dzv);
CameraProcess::SetCameraProcess(new CameraProcess(cpt));
}
// Prevent avatar from running an idle animation while moving around
Ultima8Engine::get_instance()->getAvatarMoverProcess()->resetIdleTime();
}
void QuickAvatarMoverProcess::terminate() {
Process::terminate();
_amp = 0;
}
QuickAvatarMoverProcess *QuickAvatarMoverProcess::get_instance() {
Kernel *kernel = Kernel::get_instance();
QuickAvatarMoverProcess *p = nullptr;
if (_amp) {
p = dynamic_cast<QuickAvatarMoverProcess *>(kernel->getProcess(_amp));
}
if (!p) {
p = new QuickAvatarMoverProcess();
Kernel::get_instance()->addProcess(p);
}
return p;
}
bool QuickAvatarMoverProcess::onActionDown(KeybindingAction action) {
if (!isEnabled()) {
return false;
}
bool handled = true;
switch (action) {
case ACTION_MOVE_ASCEND:
setMovementFlag(MOVE_ASCEND);
break;
case ACTION_MOVE_DESCEND:
setMovementFlag(MOVE_DESCEND);
break;
case ACTION_TURN_LEFT:
setMovementFlag(MOVE_TURN_LEFT);
break;
case ACTION_TURN_RIGHT:
setMovementFlag(MOVE_TURN_RIGHT);
break;
case ACTION_MOVE_FORWARD:
setMovementFlag(MOVE_FORWARD);
break;
case ACTION_MOVE_BACK:
setMovementFlag(MOVE_BACK);
break;
case ACTION_MOVE_UP:
setMovementFlag(MOVE_UP);
break;
case ACTION_MOVE_DOWN:
setMovementFlag(MOVE_DOWN);
break;
case ACTION_MOVE_LEFT:
setMovementFlag(MOVE_LEFT);
break;
case ACTION_MOVE_RIGHT:
setMovementFlag(MOVE_RIGHT);
break;
case ACTION_MOVE_STEP:
setMovementFlag(MOVE_SLOW);
// Allow others to handle as well
handled = false;
break;
case ACTION_MOVE_RUN:
setMovementFlag(MOVE_FAST);
// Allow others to handle as well
handled = false;
break;
default:
handled = false;
}
return handled;
}
bool QuickAvatarMoverProcess::onActionUp(KeybindingAction action) {
if (!isEnabled()) {
return false;
}
bool handled = true;
switch (action) {
case ACTION_MOVE_ASCEND:
clearMovementFlag(MOVE_ASCEND);
break;
case ACTION_MOVE_DESCEND:
clearMovementFlag(MOVE_DESCEND);
break;
case ACTION_TURN_LEFT:
clearMovementFlag(MOVE_TURN_LEFT);
break;
case ACTION_TURN_RIGHT:
clearMovementFlag(MOVE_TURN_RIGHT);
break;
case ACTION_MOVE_FORWARD:
clearMovementFlag(MOVE_FORWARD);
break;
case ACTION_MOVE_BACK:
clearMovementFlag(MOVE_BACK);
break;
case ACTION_MOVE_UP:
clearMovementFlag(MOVE_UP);
break;
case ACTION_MOVE_DOWN:
clearMovementFlag(MOVE_DOWN);
break;
case ACTION_MOVE_LEFT:
clearMovementFlag(MOVE_LEFT);
break;
case ACTION_MOVE_RIGHT:
clearMovementFlag(MOVE_RIGHT);
break;
case ACTION_MOVE_STEP:
clearMovementFlag(MOVE_SLOW);
// Allow others to handle as well
handled = false;
break;
case ACTION_MOVE_RUN:
clearMovementFlag(MOVE_FAST);
// Allow others to handle as well
handled = false;
break;
default:
handled = false;
}
return handled;
}
void QuickAvatarMoverProcess::saveData(Common::WriteStream *ws) {
Process::saveData(ws);
ws->writeUint32LE(_movementFlags);
// don't save more information. We plan to terminate upon load
}
bool QuickAvatarMoverProcess::loadData(Common::ReadStream *rs, uint32 version) {
if (!Process::loadData(rs, version)) return false;
_movementFlags = rs->readUint32LE();
terminateDeferred(); // Don't allow this process to continue
return true;
}
} // End of namespace Ultima8
} // End of namespace Ultima

View File

@@ -0,0 +1,110 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef WORLD_ACTORS_QUICKAVATARMOVERPROCESS_H
#define WORLD_ACTORS_QUICKAVATARMOVERPROCESS_H
#include "ultima/ultima8/metaengine.h"
#include "ultima/ultima8/kernel/process.h"
namespace Ultima {
namespace Ultima8 {
class QuickAvatarMoverProcess : public Process {
public:
QuickAvatarMoverProcess();
~QuickAvatarMoverProcess() override;
ENABLE_RUNTIME_CLASSTYPE()
static QuickAvatarMoverProcess *get_instance();
void run() override;
void terminate() override;
static bool isEnabled() {
return _enabled;
}
static void setEnabled(bool value) {
_enabled = value;
}
static bool isClipping() {
return _clipping;
}
static void setClipping(bool value) {
_clipping = value;
}
static void toggleClipping() {
_clipping = !_clipping;
}
bool hasMovementFlags(uint32 flags) const {
return (_movementFlags & flags) != 0;
}
void setMovementFlag(uint32 mask) {
_movementFlags |= mask;
}
virtual void clearMovementFlag(uint32 mask) {
_movementFlags &= ~mask;
}
void resetMovementFlags() {
_movementFlags = 0;
}
// Return true if handled, false if not.
bool onActionDown(KeybindingAction action);
bool onActionUp(KeybindingAction action);
bool loadData(Common::ReadStream *rs, uint32 version);
void saveData(Common::WriteStream *ws) override;
enum MovementFlags {
MOVE_ASCEND = 0x0001,
MOVE_DESCEND = 0x0002,
MOVE_SLOW = 0x0004,
MOVE_FAST = 0x0008,
// Tank controls
MOVE_TURN_LEFT = 0x0010,
MOVE_TURN_RIGHT = 0x0020,
MOVE_FORWARD = 0x0040,
MOVE_BACK = 0x0080,
// Directional controls
MOVE_LEFT = 0x0100,
MOVE_RIGHT = 0x0200,
MOVE_UP = 0x0400,
MOVE_DOWN = 0x0800,
MOVE_ANY_DIRECTION = MOVE_LEFT | MOVE_RIGHT | MOVE_UP | MOVE_DOWN | MOVE_ASCEND | MOVE_DESCEND
};
protected:
uint32 _movementFlags;
static ProcId _amp;
static bool _enabled;
static bool _clipping;
};
} // End of namespace Ultima8
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,90 @@
/* 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/resurrection_process.h"
#include "ultima/ultima8/world/actors/actor.h"
#include "ultima/ultima8/world/get_object.h"
namespace Ultima {
namespace Ultima8 {
DEFINE_RUNTIME_CLASSTYPE_CODE(ResurrectionProcess)
ResurrectionProcess::ResurrectionProcess() : Process() {
}
ResurrectionProcess::ResurrectionProcess(Actor *actor) {
assert(actor);
_itemNum = actor->getObjId();
_type = 0x229; // CONSTANT !
}
void ResurrectionProcess::run() {
Actor *a = getActor(_itemNum);
if (!a) {
// actor gone... too late for resurrection now :-)
terminate();
return;
}
if (!a->isDead()) {
// not dead?
terminate();
return;
}
if (a->hasFlags(Item::FLG_GUMP_OPEN)) {
// first close gump in case player is still rummaging through us
a->closeGump();
}
a->clearActorFlag(Actor::ACT_WITHSTANDDEATH);
a->clearActorFlag(Actor::ACT_DEAD);
// reload stats
if (!a->loadMonsterStats()) {
warning("ResurrectionProcess::run failed to reset stats for actor (%u).", a->getShape());
}
// go into combat mode
// 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))
terminate();
}
void ResurrectionProcess::saveData(Common::WriteStream *ws) {
Process::saveData(ws);
}
bool ResurrectionProcess::loadData(Common::ReadStream *rs, uint32 version) {
if (!Process::loadData(rs, version)) return false;
return true;
}
} // End of namespace Ultima8
} // End of namespace Ultima

View File

@@ -0,0 +1,48 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef WORLD_ACTORS_RESURRECTIONPROCESS_H
#define WORLD_ACTORS_RESURRECTIONPROCESS_H
#include "ultima/ultima8/kernel/process.h"
namespace Ultima {
namespace Ultima8 {
class Actor;
class ResurrectionProcess : public Process {
public:
ResurrectionProcess();
ResurrectionProcess(Actor *actor);
ENABLE_RUNTIME_CLASSTYPE()
void run() override;
bool loadData(Common::ReadStream *rs, uint32 version);
void saveData(Common::WriteStream *ws) override;
};
} // End of namespace Ultima8
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,348 @@
/* 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/rolling_thunder_process.h"
#include "ultima/ultima8/world/world.h"
#include "ultima/ultima8/world/get_object.h"
#include "ultima/ultima8/world/loop_script.h"
#include "ultima/ultima8/world/current_map.h"
#include "ultima/ultima8/world/actors/actor.h"
#include "ultima/ultima8/world/actors/pathfinder.h"
#include "ultima/ultima8/world/actors/anim_action.h"
#include "ultima/ultima8/games/game_data.h"
#include "ultima/ultima8/gfx/main_shape_archive.h"
#include "ultima/ultima8/gfx/anim_dat.h"
#include "ultima/ultima8/kernel/kernel.h"
#include "ultima/ultima8/kernel/delay_process.h"
#include "ultima/ultima8/usecode/uc_list.h"
#include "ultima/ultima8/misc/direction_util.h"
namespace Ultima {
namespace Ultima8 {
DEFINE_RUNTIME_CLASSTYPE_CODE(RollingThunderProcess)
static const uint16 CRUSPID = 0x584;
static const uint16 BULLET_SPLASH_SHAPE = 0x1d9;
RollingThunderProcess::RollingThunderProcess() : Process(), _target(0), _timer(0) {
}
RollingThunderProcess::RollingThunderProcess(Actor *actor) : _target(0), _timer(0) {
assert(actor);
_itemNum = actor->getObjId();
_type = 0x263; // CONSTANT!
}
void RollingThunderProcess::run() {
Actor *actor = getActor(_itemNum);
if (!actor || actor->isDead()) {
// gone! maybe dead..
terminate();
return;
}
if (actor->isBusy()) {
sleepFor60Ticks();
return;
}
uint16 controllednpc = World::get_instance()->getControlledNPCNum();
const Item *target = getItem(_target);
// Target the controlled npc, unless our current target is a spider bomb
if (_target != controllednpc && (!target || target->getShape() != CRUSPID)) {
_target = controllednpc ? controllednpc : 1;
target = getItem(_target);
}
const Actor *targeta = dynamic_cast<const Actor *>(target);
if (targeta && targeta->isDead()) {
_target = controllednpc;
sleepFor60Ticks();
return;
}
if (!actor->isPartlyOnScreen()) {
sleepFor60Ticks();
return;
}
// Should end up with some target here??
if (!target)
return;
Common::RandomSource &rs = Ultima8Engine::get_instance()->getRandomSource();
Animation::Sequence anim = rs.getRandomBit() ? Animation::combatRollLeft : Animation::combatRollRight;
Direction actordir = actor->getDir();
Direction outdir = actordir;
bool canroll = checkDir(anim, outdir);
if (!canroll) {
// try the other way
if (anim == Animation::combatRollLeft)
anim = Animation::combatRollRight;
else
anim = Animation::combatRollLeft;
canroll = checkDir(anim, outdir);
}
if (!canroll) {
Point3 pta = actor->getLocation();
Point3 ptt = target->getLocation();
Direction dirtotarget = Direction_GetWorldDir(ptt.y - pta.y, ptt.x - pta.x, dirmode_16dirs);
if (dirtotarget == actordir) {
uint32 now = Kernel::get_instance()->getTickNum();
if (now - actor->getLastTickWasHit() >= 120) {
if (actor->fireDistance(target, dirtotarget, 0, 0, 0)) {
actor->doAnim(Animation::attack, dir_current);
return;
}
}
checkForSpiderBomb();
} else {
uint16 turnproc = actor->turnTowardDir(dirtotarget);
waitFor(turnproc);
}
} else {
uint16 animpid = actor->doAnim(anim, dir_current);
if (outdir != actordir) {
animpid = actor->turnTowardDir(outdir, animpid);
}
int attackcount = rs.getRandomNumberRng(1, 3);
for (int i = 0; i < attackcount; i++) {
animpid = actor->doAnimAfter(Animation::attack, outdir, animpid);
}
Animation::Sequence rollback;
if (anim == Animation::combatRollLeft) {
rollback = Animation::combatRollRight;
} else {
rollback = Animation::combatRollLeft;
}
animpid = actor->doAnimAfter(rollback, dir_current, animpid);
waitFor(animpid);
}
}
bool RollingThunderProcess::checkDir(Animation::Sequence anim, Direction &outdir) const {
Actor *actor = getActor(_itemNum);
Direction curdir = actor->getDir();
if (!actor->isPartlyOnScreen())
return false;
const Item *target = getItem(_target);
if (!target)
return false;
PathfindingState state;
state.load(actor);
// Check if the anim is blocked or would take the actor off-screen.
Animation::Result animresult = actor->tryAnim(anim, dir_current, 0, &state);
if (animresult == Animation::FAILURE || !actor->isPartlyOnScreen())
return false;
// check if the dir to the target is within 2 direction steps of the current dir
Point3 pt = target->getLocation();
Direction dirtotarget = Direction_GetWorldDir(pt.y - state._point.y, pt.x - state._point.x, dirmode_16dirs);
static const int DIROFFSETS[] = {0, -1, 1, -2, 2};
outdir = dirtotarget;
// Check that the target is in a nearby direction
bool nearby = false;
for (int i = 0; i < ARRAYSIZE(DIROFFSETS); i++) {
Direction dir = Direction_TurnByDelta(dirtotarget, DIROFFSETS[i], dirmode_16dirs);
if (curdir == dir) {
nearby = true;
break;
}
}
if (!nearby)
return false;
// Check whether we can fire in that direction and hit the target
for (int i = 0; i < ARRAYSIZE(DIROFFSETS); i++) {
Direction dir = Direction_TurnByDelta(dirtotarget, DIROFFSETS[i], dirmode_16dirs);
if (fireDistance(dir, state._point.x, state._point.y, state._point.z))
return true;
}
return false;
}
//
// This is practically a copy of Item::fireDistance, but with some changes
// to measure from the hypothetical position of the actor after rolling.
//
// Ideally it would be refactored, but for now copy it with changes just like
// the game does.
//
bool RollingThunderProcess::fireDistance(Direction dir, int32 x, int32 y, int32 z) const {
int32 xoff = 0;
int32 yoff = 0;
int32 zoff = 0;
int32 xoff2 = 0;
int32 yoff2 = 0;
int32 zoff2 = 0;
const Actor *actor = getActor(_itemNum);
const Item *target = getItem(_target);
if (!actor || !target)
return 0;
Point3 pt = target->getLocation();
uint16 shapeno = actor->getShape();
uint32 actionno = AnimDat::getActionNumberForSequence(Animation::attack, actor);
const AnimAction *animaction = GameData::get_instance()->getMainShapes()->getAnim(shapeno, actionno);
CurrentMap *cm = World::get_instance()->getCurrentMap();
bool other_offsets = false;
bool first_offsets = false;
int nframes = animaction->getSize();
for (int frameno = 0; frameno < nframes; frameno++) {
const AnimFrame &frame = animaction->getFrame(dir, frameno);
if (frame.is_cruattack()) {
if (!first_offsets) {
xoff = frame.cru_attackx();
yoff = frame.cru_attacky();
zoff = frame.cru_attackz();
first_offsets = true;
} else {
xoff2 = frame.cru_attackx();
yoff2 = frame.cru_attacky();
zoff2 = frame.cru_attackz();
other_offsets = true;
break;
}
}
}
if (!first_offsets)
return 0;
int dist = 0;
for (int i = 0; i < (other_offsets ? 2 : 1) && dist == 0; i++) {
int32 cx = x + (i == 0 ? xoff : xoff2);
int32 cy = y + (i == 0 ? yoff : yoff2);
int32 cz = z + (i == 0 ? zoff : zoff2);
PositionInfo info = cm->getPositionInfo(cx, cy, cz, BULLET_SPLASH_SHAPE, _itemNum);
if (!info.valid && info.blocker) {
if (info.blocker->getObjId() == target->getObjId())
dist = MAX(abs(x - pt.x), abs(y - pt.y));
} else {
Point3 oc = target->getCentre();
oc.z = target->getTargetZRelativeToAttackerZ(z);
const Point3 start(cx, cy, cz);
const Point3 end = oc;
const int32 dims[3] = {2, 2, 2};
Std::list<CurrentMap::SweepItem> collisions;
cm->sweepTest(start, end, dims, ShapeInfo::SI_SOLID,
_itemNum, false, &collisions);
for (const auto &collision : collisions) {
if (collision._item == _itemNum)
continue;
if (collision._item != target->getObjId())
break;
Point3 out = collision.GetInterpolatedCoords(start, end);
dist = MAX(abs(x - out.x), abs(y - out.y));
break;
}
}
}
return dist;
}
bool RollingThunderProcess::checkForSpiderBomb() {
const Item *target = getItem(_target);
const Actor *actor = getActor(_itemNum);
if (target && target->getShape() == CRUSPID)
return false;
if (!checkTimer())
return false;
CurrentMap *currentmap = World::get_instance()->getCurrentMap();
UCList spiderlist(2);
LOOPSCRIPT(script, LS_SHAPE_EQUAL(CRUSPID));
currentmap->areaSearch(&spiderlist, script, sizeof(script), actor, 800, false);
for (unsigned int i = 0; i < spiderlist.getSize(); ++i) {
const Item *spider = getItem(spiderlist.getuint16(i));
if (!spider)
continue;
Point3 pta = actor->getLocation();
Point3 pts = spider->getLocation();
Direction dirtospider = Direction_GetWorldDir(pts.y - pta.y, pts.x - pta.x, dirmode_16dirs);
uint16 dist = actor->fireDistance(spider, dirtospider, 0, 0, 0);
if (dist > 0) {
_target = spider->getObjId();
return true;
}
}
return false;
}
bool RollingThunderProcess::checkTimer() {
uint32 ticksnow = Kernel::get_instance()->getTickNum();
if (ticksnow > _timer + 90) {
_timer = ticksnow;
return true;
}
return false;
}
void RollingThunderProcess::sleepFor60Ticks() {
Process *wait = new DelayProcess(60);
Kernel::get_instance()->addProcess(wait);
waitFor(wait);
}
void RollingThunderProcess::saveData(Common::WriteStream *ws) {
Process::saveData(ws);
ws->writeUint16LE(_target);
ws->writeUint32LE(_timer);
}
bool RollingThunderProcess::loadData(Common::ReadStream *rs, uint32 version) {
if (!Process::loadData(rs, version)) return false;
_target = rs->readUint16LE();
_timer = rs->readUint32LE();
return true;
}
} // End of namespace Ultima8
} // End of namespace Ultima

View File

@@ -0,0 +1,68 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef WORLD_ACTORS_ROLLINGTHUNDERPROCESS_H
#define WORLD_ACTORS_ROLLINGTHUNDERPROCESS_H
#include "ultima/ultima8/kernel/process.h"
#include "ultima/ultima8/misc/direction.h"
#include "ultima/ultima8/world/actors/animation.h"
namespace Ultima {
namespace Ultima8 {
class Actor;
/**
* A process to roll out, shoot at the player, and roll back.
* Only used in No Regret.
*/
class RollingThunderProcess : public Process {
public:
RollingThunderProcess();
RollingThunderProcess(Actor *actor);
ENABLE_RUNTIME_CLASSTYPE()
void run() override;
bool loadData(Common::ReadStream *rs, uint32 version);
void saveData(Common::WriteStream *ws) override;
private:
void sleepFor60Ticks();
bool checkForSpiderBomb();
bool checkTimer();
bool fireDistance(Direction dir, int32 x, int32 y, int32 z) const;
bool checkDir(Animation::Sequence anim, Direction &outdir) const;
uint16 _target;
uint32 _timer;
};
} // End of namespace Ultima8
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,93 @@
/* 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/scheduler_process.h"
#include "ultima/ultima8/world/actors/actor.h"
#include "ultima/ultima8/ultima8.h"
#include "ultima/ultima8/world/get_object.h"
namespace Ultima {
namespace Ultima8 {
DEFINE_RUNTIME_CLASSTYPE_CODE(SchedulerProcess)
SchedulerProcess::SchedulerProcess() : Process() {
_lastRun = 0;
_nextActor = 0;
_type = 0x245; // CONSTANT!
}
void SchedulerProcess::run() {
if (_nextActor != 0) {
// doing a scheduling run at the moment
Actor *a = getActor(_nextActor);
if (a) {
// CHECKME: is this the right time to pass? CONSTANT
uint32 stime = Ultima8Engine::get_instance()->getGameTimeInSeconds() / 60;
ProcId schedpid = a->callUsecodeEvent_schedule(stime);
if (schedpid) waitFor(schedpid);
}
_nextActor++;
if (_nextActor == 256) { // CONSTANT
_nextActor = 0; // done
#if 0
debugC(kDebugActor, "Scheduler: finished run at %u",
Kernel::get_instance()->getFrameNum());
#endif
}
return;
}
// CONSTANT!
uint32 currenthour = Ultima8Engine::get_instance()->getGameTimeInSeconds() / 900;
if (currenthour > _lastRun) {
// schedule a new scheduling run
_lastRun = currenthour;
_nextActor = 1;
#if 0
debugC(kDebugActor, "Scheduler: %u" , Kernel::get_instance()->getFrameNum());
#endif
}
}
void SchedulerProcess::saveData(Common::WriteStream *ws) {
Process::saveData(ws);
ws->writeUint32LE(_lastRun);
ws->writeUint16LE(_nextActor);
}
bool SchedulerProcess::loadData(Common::ReadStream *rs, uint32 version) {
if (!Process::loadData(rs, version)) return false;
_lastRun = rs->readUint32LE();
_nextActor = rs->readUint16LE();
return true;
}
} // End of namespace Ultima8
} // End of namespace Ultima

View File

@@ -0,0 +1,50 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef WORLD_ACTORS_SCHEDULERPROCESS_H
#define WORLD_ACTORS_SCHEDULERPROCESS_H
#include "ultima/ultima8/kernel/process.h"
#include "ultima/ultima8/misc/classtype.h"
namespace Ultima {
namespace Ultima8 {
class SchedulerProcess : public Process {
public:
SchedulerProcess();
ENABLE_RUNTIME_CLASSTYPE()
void run() override;
bool loadData(Common::ReadStream *rs, uint32 version);
void saveData(Common::WriteStream *ws) override;
protected:
uint32 _lastRun;
uint16 _nextActor;
};
} // End of namespace Ultima8
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,189 @@
/* 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/audio/audio_process.h"
#include "ultima/ultima8/world/actors/surrender_process.h"
#include "ultima/ultima8/world/actors/attack_process.h"
#include "ultima/ultima8/world/actors/main_actor.h"
#include "ultima/ultima8/kernel/kernel.h"
#include "ultima/ultima8/world/get_object.h"
#include "ultima/ultima8/ultima8.h"
namespace Ultima {
namespace Ultima8 {
DEFINE_RUNTIME_CLASSTYPE_CODE(SurrenderProcess)
static const uint16 SUIT_SUR_SNDS[] = {0xe9, 0xe0, 0xeb, 0xe1, 0xea};
static const uint16 CHEMSUIT_SUR_SNDS[] = {0xb4, 0xc5, 0xc6, 0xe8};
static const uint16 SCIENTIST_SUR_SNDS[] = {0xe3, 0xe4, 0xec, 0xf6};
static const uint16 HARDHAT_SUR_SNDS[] = {0xde, 0xdf, 0x8a, 0x8b};
static const uint16 FEMALE_SUR_SNDS[] = {0xd6, 0xff, 0xd7};
#define RANDOM_ELEM(array) (array[rs.getRandomNumber(ARRAYSIZE(array) - 1)])
SurrenderProcess::SurrenderProcess() :
_playedSound(false), _soundDelayTicks(480), _soundTimestamp(0)
{
}
SurrenderProcess::SurrenderProcess(Actor *actor) :
_playedSound(false), _soundDelayTicks(480), _soundTimestamp(0)
{
assert(actor);
_itemNum = actor->getObjId();
if (!actor->hasActorFlags(Actor::ACT_SURRENDERED))
actor->doAnim(Animation::surrender, actor->getDir());
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();
}
_type = 0x25f; // CONSTANT!
}
void SurrenderProcess::run() {
Actor *a = getActor(_itemNum);
const MainActor *main = getMainActor();
if (!a || a->isDead() || !main) {
// dead
terminate();
return;
}
// do nothing while we are not in the fast area or busy
if (!a->hasFlags(Item::FLG_FASTAREA) || a->isBusy())
return;
a->setActorFlag(Actor::ACT_SURRENDERED);
Direction curdir = a->getDir();
Direction direction = a->getDirToItemCentre(*main);
if (curdir != direction) {
uint16 animpid = a->turnTowardDir(direction);
if (animpid) {
waitFor(animpid);
}
return;
}
int16 soundno;
if (GAME_IS_REMORSE)
soundno = checkRandomSoundRemorse();
else
soundno = checkRandomSoundRegret();
AudioProcess *audio = AudioProcess::get_instance();
if (soundno != -1 && audio) {
audio->playSFX(soundno, 0x80, _itemNum, 1);
}
}
int16 SurrenderProcess::checkRandomSoundRemorse() {
const Actor *a = getActor(_itemNum);
const MainActor *main = getMainActor();
if (_playedSound || a->getRangeIfVisible(*main) == 0)
// Nothing to do.
return - 1;
_playedSound = true;
Common::RandomSource &rs = Ultima8Engine::get_instance()->getRandomSource();
int16 soundno = -1;
switch (a->getShape()) {
case 0x2f7: // suit
soundno = RANDOM_ELEM(SUIT_SUR_SNDS);
break;
case 0x2f5: // hardhat
soundno = RANDOM_ELEM(HARDHAT_SUR_SNDS);
break;
case 0x2f6: // chemsuit
soundno = RANDOM_ELEM(CHEMSUIT_SUR_SNDS);
break;
case 0x344: // chemsuit
soundno = RANDOM_ELEM(SCIENTIST_SUR_SNDS);
break;
case 0x597: // female office worker
soundno = RANDOM_ELEM(FEMALE_SUR_SNDS);
break;
}
return soundno;
}
int16 SurrenderProcess::checkRandomSoundRegret() {
AudioProcess *audio = AudioProcess::get_instance();
const Actor *a = getActor(_itemNum);
if (!readyForNextSoundRegret())
return -1;
if (audio->isSFXPlayingForObject(-1, a->getObjId()))
return -1;
return AttackProcess::getRandomAttackSoundRegret(a);
}
//
// This and the initializer in the constructor are duplicated logic from
// AttackProcess. In the original No Regret code they inherit from the same
// type, but that makes the No Remorse code more messy for us since we support
// both, so just live with a bit of mess in the code.
//
bool SurrenderProcess::readyForNextSoundRegret() {
uint32 now = Kernel::get_instance()->getTickNum();
if (_soundTimestamp == 0 || now - _soundTimestamp >= _soundDelayTicks) {
_soundTimestamp = now;
return true;
}
return false;
}
void SurrenderProcess::saveData(Common::WriteStream *ws) {
Process::saveData(ws);
if (GAME_IS_REMORSE) {
ws->writeByte(_playedSound ? 1 : 0);
} else {
ws->writeUint32LE(_soundDelayTicks);
ws->writeUint32LE(_soundTimestamp);
}
}
bool SurrenderProcess::loadData(Common::ReadStream *rs, uint32 version) {
if (!Process::loadData(rs, version)) return false;
if (GAME_IS_REMORSE) {
_playedSound = rs->readByte() != 0;
} else {
_soundDelayTicks = rs->readUint32LE();
_soundTimestamp = rs->readUint32LE();
}
return true;
}
} // End of namespace Ultima8
} // End of namespace Ultima

View File

@@ -0,0 +1,60 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef WORLD_ACTORS_SURRENDERPROCESS_H
#define WORLD_ACTORS_SURRENDERPROCESS_H
#include "ultima/ultima8/kernel/process.h"
namespace Ultima {
namespace Ultima8 {
class Actor;
class SurrenderProcess : public Process {
public:
SurrenderProcess();
SurrenderProcess(Actor *actor);
ENABLE_RUNTIME_CLASSTYPE()
void run() override;
bool loadData(Common::ReadStream *rs, uint32 version);
void saveData(Common::WriteStream *ws) override;
protected:
bool _playedSound;
uint32 _soundDelayTicks; // Time between playing a sound
uint32 _soundTimestamp; // Last timestamp when a sound was played
private:
int16 checkRandomSoundRemorse();
int16 checkRandomSoundRegret();
bool readyForNextSoundRegret();
};
} // End of namespace Ultima8
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,68 @@
/* 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/targeted_anim_process.h"
#include "ultima/ultima8/world/actors/animation_tracker.h"
namespace Ultima {
namespace Ultima8 {
DEFINE_RUNTIME_CLASSTYPE_CODE(TargetedAnimProcess)
TargetedAnimProcess::TargetedAnimProcess() : ActorAnimProcess(),
_pt() {
}
TargetedAnimProcess::TargetedAnimProcess(Actor *actor, Animation::Sequence action, Direction dir, const Point3 &pt) :
ActorAnimProcess(actor, action, dir),
_pt(pt) {
}
bool TargetedAnimProcess::init() {
if (!ActorAnimProcess::init())
return false;
_tracker->setTargetedMode(_pt);
return true;
}
void TargetedAnimProcess::saveData(Common::WriteStream *ws) {
ActorAnimProcess::saveData(ws);
ws->writeUint32LE(static_cast<uint32>(_pt.x));
ws->writeUint32LE(static_cast<uint32>(_pt.y));
ws->writeUint32LE(static_cast<uint32>(_pt.z));
}
bool TargetedAnimProcess::loadData(Common::ReadStream *rs, uint32 version) {
if (!ActorAnimProcess::loadData(rs, version)) return false;
_pt.x = rs->readUint32LE();
_pt.y = rs->readUint32LE();
_pt.z = rs->readUint32LE();
return true;
}
} // End of namespace Ultima8
} // End of namespace Ultima

View File

@@ -0,0 +1,54 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef WORLD_ACTORS_TARGETEDANIMPROCESS_H
#define WORLD_ACTORS_TARGETEDANIMPROCESS_H
#include "ultima/ultima8/world/actors/actor_anim_process.h"
#include "ultima/ultima8/world/actors/animation.h"
#include "ultima/ultima8/misc/classtype.h"
#include "ultima/ultima8/misc/point3.h"
namespace Ultima {
namespace Ultima8 {
class TargetedAnimProcess : public ActorAnimProcess {
public:
TargetedAnimProcess();
//! note: this probably needs some more parameters
TargetedAnimProcess(Actor *actor, Animation::Sequence action, Direction dir,
const Point3 &pt);
ENABLE_RUNTIME_CLASSTYPE()
bool loadData(Common::ReadStream *rs, uint32 version);
void saveData(Common::WriteStream *ws) override;
protected:
bool init() override;
Point3 _pt;
};
} // End of namespace Ultima8
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,70 @@
/* 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/teleport_to_egg_process.h"
#include "ultima/ultima8/world/actors/main_actor.h"
#include "ultima/ultima8/world/get_object.h"
namespace Ultima {
namespace Ultima8 {
DEFINE_RUNTIME_CLASSTYPE_CODE(TeleportToEggProcess)
TeleportToEggProcess::TeleportToEggProcess() : Process(),
_mapNum(0), _teleportId(0), _arrivalAnim(0) {
}
TeleportToEggProcess::TeleportToEggProcess(int mapNum, int teleportId, int arrivalAnim)
: _mapNum(mapNum), _teleportId(teleportId), _arrivalAnim(0) {
_type = 1; // CONSTANT! (type 1 = persistent)
}
void TeleportToEggProcess::run() {
MainActor *av = getMainActor();
av->teleport(_mapNum, _teleportId);
if (_arrivalAnim)
av->doAnim(static_cast<Animation::Sequence>(_arrivalAnim), av->getDir());
terminate();
}
void TeleportToEggProcess::saveData(Common::WriteStream *ws) {
Process::saveData(ws);
ws->writeUint32LE(static_cast<uint32>(_mapNum));
ws->writeUint32LE(static_cast<uint32>(_teleportId));
}
bool TeleportToEggProcess::loadData(Common::ReadStream *rs, uint32 version) {
if (!Process::loadData(rs, version)) return false;
_mapNum = static_cast<int>(rs->readUint32LE());
_teleportId = static_cast<int>(rs->readUint32LE());
return true;
}
} // End of namespace Ultima8
} // End of namespace Ultima

View File

@@ -0,0 +1,53 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef WORLD_ACTORS_TELEPORTTOEGGPROCESS_H
#define WORLD_ACTORS_TELEPORTTOEGGPROCESS_H
#include "ultima/ultima8/kernel/process.h"
#include "ultima/ultima8/misc/classtype.h"
namespace Ultima {
namespace Ultima8 {
class TeleportToEggProcess : public Process {
public:
TeleportToEggProcess();
TeleportToEggProcess(int mapnum, int teleportId, int arrivalAnim = 0);
ENABLE_RUNTIME_CLASSTYPE()
void run() override;
bool loadData(Common::ReadStream *rs, uint32 version);
void saveData(Common::WriteStream *ws) override;
protected:
int _mapNum;
int _teleportId;
int _arrivalAnim;
};
} // End of namespace Ultima8
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,54 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef WORLD_ACTORS_TREASUREINFO_H
#define WORLD_ACTORS_TREASUREINFO_H
#include "ultima/shared/std/containers.h"
#include "ultima/shared/std/string.h"
namespace Ultima {
namespace Ultima8 {
struct TreasureInfo {
Std::string _special;
double _chance;
int _map;
Std::vector<uint32> _shapes;
Std::vector<uint32> _frames;
unsigned int _minCount, _maxCount;
TreasureInfo() : _chance(1), _map(0), _minCount(1), _maxCount(1) {}
void clear() {
_special.clear();
_chance = 1;
_map = 0;
_shapes.clear();
_frames.clear();
_minCount = _maxCount = 1;
}
};
} // End of namespace Ultima8
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,811 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "common/config-manager.h"
#include "ultima/ultima8/world/actors/u8_avatar_mover_process.h"
#include "ultima/ultima8/world/actors/main_actor.h"
#include "ultima/ultima8/gumps/game_map_gump.h"
#include "ultima/ultima8/kernel/kernel.h"
#include "ultima/ultima8/world/actors/targeted_anim_process.h"
#include "ultima/ultima8/world/actors/avatar_gravity_process.h"
#include "ultima/ultima8/audio/music_process.h"
#include "ultima/ultima8/world/get_object.h"
#include "ultima/ultima8/misc/direction_util.h"
namespace Ultima {
namespace Ultima8 {
DEFINE_RUNTIME_CLASSTYPE_CODE(U8AvatarMoverProcess)
U8AvatarMoverProcess::U8AvatarMoverProcess() : AvatarMoverProcess(),
_lastHeadShakeAnim(Animation::lookLeft) {
}
U8AvatarMoverProcess::~U8AvatarMoverProcess() {
}
void U8AvatarMoverProcess::handleHangingMode() {
bool stasis = Ultima8Engine::get_instance()->isAvatarInStasis();
_idleTime = 0;
if (stasis)
return;
Mouse *mouse = Mouse::get_instance();
uint32 now = g_system->getMillis();
uint32 timeout = mouse->getDoubleClickTime();
bool m0unhandled = _mouseButton[0].isUnhandledPastTimeout(now, timeout);
if (m0unhandled) {
_mouseButton[0].setState(MBS_HANDLED);
}
bool m1unhandled = _mouseButton[1].isUnhandledPastTimeout(now, timeout);
if (m1unhandled) {
_mouseButton[1].setState(MBS_HANDLED);
}
if (!_mouseButton[1].isState(MBS_DOWN)) {
clearMovementFlag(MOVE_MOUSE_DIRECTION);
}
// if left mouse was clicked or down unhandled, try to climb up
if (!_mouseButton[0].isState(MBS_HANDLED) || m0unhandled) {
_mouseButton[0].setState(MBS_HANDLED);
_mouseButton[0]._lastDown = 0;
setMovementFlag(MOVE_JUMP);
}
if (hasMovementFlags(MOVE_JUMP)) {
clearMovementFlag(MOVE_JUMP);
MainActor *avatar = getMainActor();
if (avatar->tryAnim(Animation::climb40, dir_current) == Animation::SUCCESS) {
avatar->ensureGravityProcess()->terminate();
waitFor(avatar->doAnim(Animation::climb40, dir_current));
}
}
}
void U8AvatarMoverProcess::handleCombatMode() {
Mouse *mouse = Mouse::get_instance();
MainActor *avatar = getMainActor();
Animation::Sequence lastanim = avatar->getLastAnim();
Direction direction = avatar->getDir();
bool stasis = Ultima8Engine::get_instance()->isAvatarInStasis();
unsigned int mouselength = mouse->getMouseLength();
Direction mousedir = mouse->getMouseDirectionWorld();
// never idle when in combat
_idleTime = 0;
// If Avatar has fallen down, stand up.
if (standUpIfNeeded(direction))
return;
// if we were blocking, and no longer holding the mouse, stop
if (lastanim == Animation::startBlock &&
!_mouseButton[0].isState(MBS_DOWN)) {
waitFor(avatar->doAnim(Animation::stopBlock, direction));
return;
}
// can't do any new actions if in stasis
if (stasis)
return;
uint32 now = g_system->getMillis();
uint32 timeout = mouse->getDoubleClickTime();
bool m0unhandled = _mouseButton[0].isUnhandledPastTimeout(now, timeout);
if (m0unhandled) {
_mouseButton[0].setState(MBS_HANDLED);
}
bool m1unhandled = _mouseButton[1].isUnhandledPastTimeout(now, timeout);
if (m1unhandled) {
_mouseButton[1].setState(MBS_HANDLED);
}
if (!_mouseButton[0].isState(MBS_DOWN)) {
clearMovementFlag(MOVE_MOUSE_DIRECTION);
}
if (_mouseButton[0].isState(MBS_DOWN) &&
_mouseButton[0].isState(MBS_HANDLED) && _mouseButton[0]._lastDown > 0) {
// left click-and-hold = block
if (lastanim == Animation::startBlock)
return;
// debugC(kDebugActor ,"AvatarMover: combat block");
if (checkTurn(mousedir, false))
return;
waitFor(avatar->doAnim(Animation::startBlock, mousedir));
return;
}
Common::RandomSource &rs = Ultima8Engine::get_instance()->getRandomSource();
if (_mouseButton[0].isUnhandledDoubleClick(timeout)) {
_mouseButton[0].setState(MBS_HANDLED);
_mouseButton[0]._lastDown = 0;
if (canAttack()) {
// double left click = attack
if (hasMovementFlags(MOVE_ANY_DIRECTION)) {
waitFor(avatar->doAnim(Animation::attack, direction));
} else {
if (checkTurn(mousedir, false))
return;
waitFor(avatar->doAnim(Animation::attack, mousedir));
}
_lastAttack = Kernel::get_instance()->getFrameNum();
// attacking gives str/dex
avatar->accumulateStr(rs.getRandomNumberRng(1, 2));
avatar->accumulateDex(rs.getRandomNumberRng(2, 3));
}
return;
}
if (_mouseButton[1].isUnhandledDoubleClick(timeout)) {
_mouseButton[1].setState(MBS_HANDLED);
_mouseButton[1]._lastDown = 0;
Gump *desktopgump = Ultima8Engine::get_instance()->getDesktopGump();
int32 mx, my;
mouse->getMouseCoords(mx, my);
if (desktopgump->TraceObjId(mx, my) == kMainActorId) {
// double right click on avatar = toggle combat mode
avatar->toggleInCombat();
waitFor(avatar->doAnim(Animation::unreadyWeapon, direction));
return;
}
if (canAttack()) {
// double right click = kick
if (hasMovementFlags(MOVE_ANY_DIRECTION)) {
waitFor(avatar->doAnim(Animation::kick, direction));
} else {
if (checkTurn(mousedir, false))
return;
waitFor(avatar->doAnim(Animation::kick, mousedir));
}
_lastAttack = Kernel::get_instance()->getFrameNum();
// kicking gives str/dex
avatar->accumulateStr(rs.getRandomNumberRng(1, 2));
avatar->accumulateDex(rs.getRandomNumberRng(2, 3));
}
return;
}
if (_mouseButton[1].isState(MBS_DOWN) && _mouseButton[1].isState(MBS_HANDLED)) {
// Note: Original game allowed a move animation on a single right click.
// This implementation needs right mouse to be held.
setMovementFlag(MOVE_MOUSE_DIRECTION);
if (checkTurn(mousedir, true))
return;
//!! TODO: check if you can actually take this step
Direction nextdir = mousedir;
Animation::Sequence nextanim;
if (lastanim == Animation::run) {
// want to run while in combat mode?
// first sheath weapon
nextanim = Animation::readyWeapon;
} else if (Direction_Invert(direction) == mousedir) {
nextanim = Animation::retreat;
nextdir = direction;
} else {
nextanim = Animation::advance;
}
if (mouselength == 2 || hasMovementFlags(MOVE_RUN)) {
// Take a step before running
nextanim = Animation::walk;
avatar->setActorFlag(Actor::ACT_COMBATRUN);
avatar->toggleInCombat();
MusicProcess::get_instance()->playCombatMusic(110); // CONSTANT!!
}
nextanim = Animation::checkWeapon(nextanim, lastanim);
waitFor(avatar->doAnim(nextanim, nextdir));
return;
}
// if clicked, turn in mouse direction
if (m0unhandled || m1unhandled)
if (checkTurn(mousedir, false))
return;
bool moving = (lastanim == Animation::advance || lastanim == Animation::retreat);
// if we are trying to move, allow change direction only after move occurs to avoid spinning
if (moving || !hasMovementFlags(MOVE_FORWARD | MOVE_BACK)) {
direction = getTurnDirForTurnFlags(direction, avatar->animDirMode(Animation::combatStand));
}
if (hasMovementFlags(MOVE_FORWARD)) {
Animation::Sequence nextanim = Animation::advance;
if (lastanim == Animation::run) {
// want to run while in combat mode?
// first sheath weapon
nextanim = Animation::readyWeapon;
}
if (hasMovementFlags(MOVE_RUN)) {
// Take a step before running
nextanim = Animation::walk;
avatar->setActorFlag(Actor::ACT_COMBATRUN);
avatar->toggleInCombat();
MusicProcess::get_instance()->playCombatMusic(110); // CONSTANT!!
}
nextanim = Animation::checkWeapon(nextanim, lastanim);
waitFor(avatar->doAnim(nextanim, direction));
return;
}
if (hasMovementFlags(MOVE_BACK)) {
waitFor(avatar->doAnim(Animation::retreat, direction));
return;
}
int x, y;
getMovementFlagAxes(x, y);
if (x != 0 || y != 0) {
Direction nextdir = Direction_Get(y, x, dirmode_8dirs);
if (checkTurn(nextdir, true))
return;
Animation::Sequence nextanim;
if (lastanim == Animation::run) {
// want to run while in combat mode?
// first sheath weapon
nextanim = Animation::readyWeapon;
} else if (Direction_Invert(direction) == nextdir) {
nextanim = Animation::retreat;
nextdir = direction;
} else {
nextanim = Animation::advance;
}
if (hasMovementFlags(MOVE_RUN)) {
// Take a step before running
nextanim = Animation::walk;
avatar->setActorFlag(Actor::ACT_COMBATRUN);
avatar->toggleInCombat();
MusicProcess::get_instance()->playCombatMusic(110); // CONSTANT!!
}
nextanim = Animation::checkWeapon(nextanim, lastanim);
waitFor(avatar->doAnim(nextanim, nextdir));
return;
}
if (checkTurn(direction, false))
return;
// not doing anything in particular? stand
// TODO: make sure falling works properly.
if (lastanim != Animation::combatStand) {
Animation::Sequence nextanim = Animation::checkWeapon(Animation::combatStand, lastanim);
waitFor(avatar->doAnim(nextanim, direction));
}
}
void U8AvatarMoverProcess::handleNormalMode() {
const Mouse *mouse = Mouse::get_instance();
MainActor *avatar = getMainActor();
Animation::Sequence lastanim = avatar->getLastAnim();
Direction direction = avatar->getDir();
bool stasis = Ultima8Engine::get_instance()->isAvatarInStasis();
bool combatRun = avatar->hasActorFlags(Actor::ACT_COMBATRUN);
unsigned int mouselength = mouse->getMouseLength();
Direction mousedir = mouse->getMouseDirectionWorld();
// Store current idle time. (Also see end of function.)
uint32 currentIdleTime = _idleTime;
_idleTime = 0;
// User toggled combat while in combatRun
if (avatar->isInCombat()) {
avatar->clearActorFlag(Actor::ACT_COMBATRUN);
avatar->toggleInCombat();
}
// If Avatar has fallen down, stand up.
if (standUpIfNeeded(direction))
return;
// If still in combat stance, sheathe weapon
if (!stasis && Animation::isCombatAnimU8(lastanim)) {
putAwayWeapon(direction);
return;
}
uint32 now = g_system->getMillis();
uint32 timeout = mouse->getDoubleClickTime();
bool m0unhandled = _mouseButton[0].isUnhandledPastTimeout(now, timeout);
if (m0unhandled) {
_mouseButton[0].setState(MBS_HANDLED);
}
bool m1unhandled = _mouseButton[1].isUnhandledPastTimeout(now, timeout);
if (m1unhandled) {
_mouseButton[1].setState(MBS_HANDLED);
}
if (!_mouseButton[1].isState(MBS_DOWN)) {
clearMovementFlag(MOVE_MOUSE_DIRECTION);
}
if (_mouseButton[1].isState(MBS_DOWN) && _mouseButton[1].isState(MBS_HANDLED)) {
// Note: Original game allowed a move animation on a single right click.
// This implementation needs right mouse to be held.
setMovementFlag(MOVE_MOUSE_DIRECTION);
}
if (!hasMovementFlags(MOVE_ANY_DIRECTION)) {
// if we were running in combat mode, slow to a walk, draw weapon
// (even in stasis)
if (combatRun) {
avatar = getMainActor();
avatar->clearActorFlag(Actor::ACT_COMBATRUN);
avatar->toggleInCombat();
// If we were running, slow to a walk before drawing weapon.
// Note: Original game did not check last animation and always took an extra walk.
if (lastanim == Animation::run || lastanim == Animation::runningJump) {
ProcId walkpid = avatar->doAnim(Animation::walk, direction);
ProcId drawpid = avatar->doAnim(Animation::readyWeapon, direction);
Process *drawproc = Kernel::get_instance()->getProcess(drawpid);
drawproc->waitFor(walkpid);
waitFor(drawpid);
return;
}
waitFor(avatar->doAnim(Animation::readyWeapon, direction));
return;
}
// if we were running, slow to a walk before stopping
// (even in stasis)
if (lastanim == Animation::run) {
slowFromRun(direction);
return;
}
// TODO: if we were hanging, fall
}
// can't do any new actions if in stasis
if (stasis)
return;
// both mouse buttons down and not yet handled, check for jump.
if (!_mouseButton[0].isState(MBS_HANDLED) && !_mouseButton[1].isState(MBS_HANDLED)) {
// Take action if both were clicked within
// double-click timeout of each other.
// notice these are all unsigned.
uint32 down = _mouseButton[1]._curDown;
if (_mouseButton[0]._curDown < down) {
down = down - _mouseButton[0]._curDown;
} else {
down = _mouseButton[0]._curDown - down;
}
if (down < timeout) {
// Both buttons pressed within the timeout
_mouseButton[0].setState(MBS_HANDLED);
_mouseButton[1].setState(MBS_HANDLED);
setMovementFlag(MOVE_JUMP);
}
}
if ((!_mouseButton[0].isState(MBS_HANDLED) || m0unhandled) && hasMovementFlags(MOVE_MOUSE_DIRECTION | MOVE_STEP)) {
_mouseButton[0].setState(MBS_HANDLED);
// We got a left mouse down while already moving in any direction or holding the step button.
// CHECKME: check what needs to happen when keeping left pressed
setMovementFlag(MOVE_JUMP);
}
if (_mouseButton[1].isUnhandledDoubleClick(timeout)) {
Gump *desktopgump = Ultima8Engine::get_instance()->getDesktopGump();
int32 mx, my;
mouse->getMouseCoords(mx, my);
if (desktopgump->TraceObjId(mx, my) == kMainActorId) {
// double right click on avatar = toggle combat mode
_mouseButton[1].setState(MBS_HANDLED);
_mouseButton[1]._lastDown = 0;
avatar->toggleInCombat();
waitFor(avatar->doAnim(Animation::readyWeapon, direction));
return;
}
}
if (hasMovementFlags(MOVE_JUMP) && hasMovementFlags(MOVE_ANY_DIRECTION)) {
clearMovementFlag(MOVE_JUMP);
if (hasMovementFlags(MOVE_MOUSE_DIRECTION)) {
if (checkTurn(mousedir, false))
return;
}
Animation::Sequence nextanim = Animation::jump;
// check if we need to do a running jump
if (lastanim == Animation::run || lastanim == Animation::runningJump) {
nextanim = Animation::runningJump;
}
else if (avatar->hasActorFlags(Actor::ACT_AIRWALK)) {
nextanim = Animation::airwalkJump;
}
else if ((hasMovementFlags(MOVE_MOUSE_DIRECTION) && mouselength == 0) || hasMovementFlags(MOVE_STEP)) {
nextanim = Animation::jumpUp;
}
else if (!hasMovementFlags(MOVE_MOUSE_DIRECTION)) {
// check if there's something we can climb up onto here
Animation::Sequence climbanim = Animation::climb72;
while (climbanim >= Animation::climb16) {
if (avatar->tryAnim(climbanim, direction) ==
Animation::SUCCESS) {
nextanim = climbanim;
}
climbanim = static_cast<Animation::Sequence>(climbanim - 1);
}
if (nextanim >= Animation::climb16 && nextanim <= Animation::climb72) {
// climbing gives str/dex
avatar->accumulateStr(2 + nextanim - Animation::climb16);
avatar->accumulateDex(2 * (2 + nextanim - Animation::climb16));
}
}
nextanim = Animation::checkWeapon(nextanim, lastanim);
waitFor(avatar->doAnim(nextanim, direction));
return;
}
if (hasMovementFlags(MOVE_JUMP)) {
clearMovementFlag(MOVE_JUMP);
if (checkTurn(mousedir, false))
return;
Animation::Sequence nextanim = Animation::jump;
if (mouselength == 0 || hasMovementFlags(MOVE_STEP)) {
nextanim = Animation::jumpUp;
}
// check if there's something we can climb up onto here
Animation::Sequence climbanim = Animation::climb72;
while (climbanim >= Animation::climb16) {
if (avatar->tryAnim(climbanim, direction) ==
Animation::SUCCESS) {
nextanim = climbanim;
}
climbanim = static_cast<Animation::Sequence>(climbanim - 1);
}
if (nextanim == Animation::jump) {
jump(Animation::jump, direction);
}
else {
if (nextanim >= Animation::climb16 && nextanim <= Animation::climb72) {
// climbing gives str/dex
avatar->accumulateStr(2 + nextanim - Animation::climb16);
avatar->accumulateDex(2 * (2 + nextanim - Animation::climb16));
}
nextanim = Animation::checkWeapon(nextanim, lastanim);
waitFor(avatar->doAnim(nextanim, direction));
}
return;
}
if (hasMovementFlags(MOVE_MOUSE_DIRECTION)) {
Animation::Sequence nextanim = Animation::walk;
if (mouselength == 0 || hasMovementFlags(MOVE_STEP)) {
nextanim = Animation::step;
} else if (mouselength == 2 || hasMovementFlags(MOVE_RUN)) {
if (lastanim == Animation::run
|| lastanim == Animation::runningJump
|| lastanim == Animation::walk)
nextanim = Animation::run;
else
nextanim = Animation::walk;
}
step(nextanim, mousedir);
return;
}
if (m1unhandled)
if (checkTurn(mousedir, false))
return;
bool moving = (lastanim == Animation::step || lastanim == Animation::run || lastanim == Animation::walk);
// if we are trying to move, allow change direction only after move occurs to avoid spinning
if (moving || !hasMovementFlags(MOVE_FORWARD | MOVE_BACK)) {
direction = getTurnDirForTurnFlags(direction, avatar->animDirMode(Animation::step));
}
Animation::Sequence nextanim = Animation::walk;
if (hasMovementFlags(MOVE_STEP)) {
nextanim = Animation::step;
} else if (hasMovementFlags(MOVE_RUN)) {
if (lastanim == Animation::run
|| lastanim == Animation::runningJump
|| lastanim == Animation::walk)
nextanim = Animation::run;
else
nextanim = Animation::walk;
}
if (hasMovementFlags(MOVE_FORWARD)) {
step(nextanim, direction);
return;
}
if (hasMovementFlags(MOVE_BACK)) {
step(nextanim, Direction_Invert(direction));
// flip to move forward once turned
setMovementFlag(MOVE_FORWARD);
return;
}
int x, y;
getMovementFlagAxes(x, y);
if (x != 0 || y != 0) {
direction = Direction_Get(y, x, dirmode_8dirs);
step(nextanim, direction);
return;
}
if (checkTurn(direction, moving))
return;
// doing another animation?
if (avatar->isBusy())
return;
// if we were running, slow to a walk before stopping
if (lastanim == Animation::run) {
waitFor(avatar->doAnim(Animation::walk, direction));
return;
}
// not doing anything in particular? stand
// don't interrupt spells though.
if (lastanim != Animation::stand && !Animation::isCastAnimU8(lastanim)
&& currentIdleTime == 0) {
waitFor(avatar->doAnim(Animation::stand, direction));
return;
}
// idle
_idleTime = currentIdleTime + 1;
Common::RandomSource &rs = Ultima8Engine::get_instance()->getRandomSource();
// currently shaking head?
if (lastanim == Animation::lookLeft || lastanim == Animation::lookRight) {
if (rs.getRandomNumber(1500) + 30 < _idleTime) {
_lastHeadShakeAnim = lastanim;
waitFor(avatar->doAnim(Animation::stand, direction));
_idleTime = 1;
return;
}
} else {
if (rs.getRandomNumber(3000) + 150 < _idleTime) {
if (rs.getRandomNumber(4) == 0)
nextanim = _lastHeadShakeAnim;
else if (_lastHeadShakeAnim == Animation::lookLeft)
nextanim = Animation::lookRight;
else
nextanim = Animation::lookLeft;
waitFor(avatar->doAnim(nextanim, direction));
_idleTime = 1;
return;
}
}
}
void U8AvatarMoverProcess::step(Animation::Sequence action, Direction direction,
bool adjusted) {
assert(action == Animation::step || action == Animation::walk ||
action == Animation::run);
MainActor *avatar = getMainActor();
Animation::Sequence lastanim = avatar->getLastAnim();
Animation::Result res = avatar->tryAnim(action, direction);
Direction stepdir = direction;
if (res == Animation::FAILURE ||
(action == Animation::step && res == Animation::END_OFF_LAND)) {
debug(6, "Step: end off land dir %d, try other dir", stepdir);
Direction altdir1 = Direction_OneRight(stepdir, dirmode_8dirs);
Direction altdir2 = Direction_OneLeft(stepdir, dirmode_8dirs);
res = avatar->tryAnim(action, altdir1);
if (res == Animation::FAILURE ||
(action == Animation::step && res == Animation::END_OFF_LAND)) {
debug(6, "Step: end off land dir %d, altdir1 %d failed, try altdir2 %d", stepdir, altdir1, altdir2);
res = avatar->tryAnim(action, altdir2);
if (res == Animation::FAILURE ||
(action == Animation::step && res == Animation::END_OFF_LAND)) {
// Can't walk in this direction.
// Try to take a smaller step
if (action == Animation::walk) {
debug(6, "Step: end off land both altdirs failed, smaller step (step)");
step(Animation::step, direction, true);
return;
} else if (action == Animation::run) {
debug(6, "Step: end off land both altdirs failed, smaller step (walk)");
step(Animation::walk, direction, true);
return;
}
} else {
stepdir = altdir2;
}
} else {
stepdir = altdir1;
}
}
if (action == Animation::step && res == Animation::END_OFF_LAND &&
lastanim != Animation::keepBalance && !adjusted) {
Point3 pt = avatar->getLocation();
if (pt.z > 0) {
if (checkTurn(stepdir, false))
return;
debug(6, "Step: end off land both altdirs failed, keep balance.");
waitFor(avatar->doAnim(Animation::keepBalance, stepdir));
return;
}
}
if (action == Animation::step && res == Animation::FAILURE) {
action = Animation::stand;
}
if (action == Animation::walk && res == Animation::END_OFF_LAND) {
action = Animation::step;
}
if (action == Animation::run && res == Animation::END_OFF_LAND) {
action = Animation::walk;
}
bool moving = (action == Animation::run || action == Animation::walk);
if (checkTurn(stepdir, moving))
return;
//debug(6, "Step: step ok: action %d dir %d", action, stepdir);
action = Animation::checkWeapon(action, lastanim);
waitFor(avatar->doAnim(action, stepdir));
}
void U8AvatarMoverProcess::jump(Animation::Sequence action, Direction direction) {
MainActor *avatar = getMainActor();
// running jump
if (action == Animation::runningJump) {
waitFor(avatar->doAnim(action, direction));
return;
}
// airwalk
if (avatar->hasActorFlags(Actor::ACT_AIRWALK) &&
action == Animation::jump) {
waitFor(avatar->doAnim(Animation::airwalkJump, direction));
return;
}
bool targeting = ConfMan.getBool("targetedjump");
if (targeting) {
Mouse *mouse = Mouse::get_instance();
Point3 coords;
int32 mx, my;
mouse->getMouseCoords(mx, my);
GameMapGump *gameMap = Ultima8Engine::get_instance()->getGameMapGump();
// We need the Gump's x/y for TraceCoordinates
gameMap->ScreenSpaceToGump(mx, my);
ObjId targetId = gameMap->TraceCoordinates(mx, my, coords);
Item *target = getItem(targetId);
Point3 a = avatar->getCentre();
int32 xrange = abs(a.x - coords.x);
int32 yrange = abs(a.y - coords.y);
int maxrange = avatar->getStr() * 32;
if (target && target->getShapeInfo()->is_land() &&
xrange < maxrange && yrange < maxrange) {
// Original also only lets you jump at the Z_FACE
Process *p = new TargetedAnimProcess(avatar, Animation::jumpUp,
direction, coords);
waitFor(Kernel::get_instance()->addProcess(p));
return;
}
// invalid target or out of range
waitFor(avatar->doAnim(Animation::shakeHead, direction));
} else {
waitFor(avatar->doAnim(Animation::jump, direction));
}
}
bool U8AvatarMoverProcess::canAttack() {
MainActor *avatar = getMainActor();
const uint32 frameno = Kernel::get_instance()->getFrameNum();
// Sanity check in case the frame num went backwards - eg, if
// frame counting changed after loading a game.
if (_lastAttack > frameno) {
_lastAttack = frameno;
}
return (Kernel::get_instance()->getFrameNum() > _lastAttack + (25 - avatar->getDex()));
}
void U8AvatarMoverProcess::saveData(Common::WriteStream *ws) {
AvatarMoverProcess::saveData(ws);
// Note: this field used to be the last thing saved in AvatarMoverProcess,
// so this relies on it being in the right order here (and loadData) for
// backwards compatibility.
ws->writeUint16LE(static_cast<uint8>(_lastHeadShakeAnim));
}
bool U8AvatarMoverProcess::loadData(Common::ReadStream *rs, uint32 version) {
if (!AvatarMoverProcess::loadData(rs, version)) return false;
_lastHeadShakeAnim = static_cast<Animation::Sequence>(rs->readUint16LE());
return true;
}
} // End of namespace Ultima8
} // End of namespace Ultima

View File

@@ -0,0 +1,63 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef WORLD_ACTORS_U8AVATARMOVERPROCESS_H
#define WORLD_ACTORS_U8AVATARMOVERPROCESS_H
#include "ultima/ultima8/world/actors/avatar_mover_process.h"
#include "ultima/ultima8/world/actors/animation.h"
namespace Ultima {
namespace Ultima8 {
/**
* Mover process that replicates the feel of U8 - moving, combat, jumps, etc.
* Tries turning one quarter turn if movement is blocked. Running temporarily
* stops combat and plays some special movement.
*/
class U8AvatarMoverProcess : public AvatarMoverProcess {
public:
U8AvatarMoverProcess();
~U8AvatarMoverProcess();
ENABLE_RUNTIME_CLASSTYPE()
bool loadData(Common::ReadStream *rs, uint32 version);
void saveData(Common::WriteStream *ws) override;
protected:
void handleHangingMode() override;
void handleCombatMode() override;
void handleNormalMode() override;
bool canAttack();
void step(Animation::Sequence action, Direction direction, bool adjusted = false);
void jump(Animation::Sequence action, Direction direction);
private:
Animation::Sequence _lastHeadShakeAnim;
};
} // End of namespace Ultima8
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,80 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef WORLD_ACTORS_WEAPONOVERLAY_H
#define WORLD_ACTORS_WEAPONOVERLAY_H
#include "ultima/shared/std/containers.h"
namespace Ultima {
namespace Ultima8 {
struct WeaponOverlayFrame {
int32 _xOff;
int32 _yOff;
uint32 _frame;
};
struct WeaponOverlay {
unsigned int _dirCount;
Std::vector<WeaponOverlayFrame> *_frames; // 8 or 16 directions
WeaponOverlay() : _frames(nullptr), _dirCount(0) {
}
~WeaponOverlay() {
delete[] _frames;
}
};
struct AnimWeaponOverlay {
//! get the weapon overlay info for a specific animation frame
//! \param type the overlay type
//! \param direction the direction
//! \param frame the animation frame
//! \return nullptr if invalid, or pointer to a frame; don't delete it.
const WeaponOverlayFrame *getFrame(unsigned int type,
Direction direction,
unsigned int frame) const {
if (type >= _overlay.size())
return nullptr;
assert(direction != dir_invalid);
uint32 diroff;
if (_overlay[type]._dirCount == 8)
diroff = static_cast<uint32>(direction) / 2;
else
diroff = static_cast<uint32>(direction);
if (diroff >= _overlay[type]._dirCount)
return nullptr;
if (frame >= _overlay[type]._frames[diroff].size())
return nullptr;
return &(_overlay[type]._frames[diroff][frame]);
}
Std::vector<WeaponOverlay> _overlay;
};
} // End of namespace Ultima8
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,39 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef ULTIMA8_WORLD_ARMOURINFO_H
#define ULTIMA8_WORLD_ARMOURINFO_H
namespace Ultima {
namespace Ultima8 {
struct ArmourInfo {
uint32 _shape;
uint32 _frame;
uint16 _armourClass;
uint16 _kickAttackBonus;
uint16 _defenseType; // see WeaponInfo struct
};
} // End of namespace Ultima8
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,98 @@
/* 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/bobo_boomer_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/world/item.h"
#include "ultima/ultima8/world/fire_type.h"
#include "ultima/ultima8/ultima8.h"
namespace Ultima {
namespace Ultima8 {
DEFINE_RUNTIME_CLASSTYPE_CODE(BoboBoomerProcess)
BoboBoomerProcess::BoboBoomerProcess() : Process(),
_counter(0), _x(0), _y(0), _z(0)
{}
BoboBoomerProcess::BoboBoomerProcess(const Item *item) : Process(), _counter(0)
{
assert(item);
Point3 pt = item->getLocation();
_x = pt.x;
_y = pt.y;
_z = pt.z;
_type = 0x264;
}
void BoboBoomerProcess::run() {
const FireType *firetype = GameData::get_instance()->getFireType(4);
assert(firetype);
Common::RandomSource &rs = Ultima8Engine::get_instance()->getRandomSource();
int32 randx = rs.getRandomNumberRngSigned(-7, 7);
int32 randy = rs.getRandomNumberRngSigned(-7, 7);
Point3 pt(_x + randx * 32, _y + randy * 32, _z);
firetype->makeBulletSplashShapeAndPlaySound(pt.x, pt.y, pt.z);
if (firetype->getRange() > 0) {
uint16 damage = firetype->getRandomDamage();
firetype->applySplashDamageAround(pt, damage, 1, nullptr, nullptr);
}
_counter++;
if (_counter > 9) {
terminate();
return;
}
int sleep = rs.getRandomNumberRng(5, 20);
Process *wait = new DelayProcess(sleep);
Kernel::get_instance()->addProcess(wait);
waitFor(wait);
}
void BoboBoomerProcess::saveData(Common::WriteStream *ws) {
Process::saveData(ws);
ws->writeSint32LE(_counter);
ws->writeSint32LE(_x);
ws->writeSint32LE(_y);
ws->writeSint32LE(_z);
}
bool BoboBoomerProcess::loadData(Common::ReadStream *rs, uint32 version) {
if (!Process::loadData(rs, version)) return false;
_counter = rs->readSint32LE();
_x = rs->readSint32LE();
_y = rs->readSint32LE();
_z = rs->readSint32LE();
return true;
}
} // End of namespace Ultima8
} // End of namespace Ultima

View File

@@ -0,0 +1,59 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef ULTIMA8_WORLD_BOBOBOOMERPROCESS_H
#define ULTIMA8_WORLD_BOBOBOOMERPROCESS_H
#include "ultima/ultima8/kernel/process.h"
#include "ultima/ultima8/misc/classtype.h"
namespace Ultima {
namespace Ultima8 {
class Item;
/**
* A process to make a bunch of explosions. bo-bo-boom!
* Only used in No Regret.
*/
class BoboBoomerProcess : public Process {
public:
BoboBoomerProcess();
BoboBoomerProcess(const Item *item);
ENABLE_RUNTIME_CLASSTYPE()
void run() override;
bool loadData(Common::ReadStream *rs, uint32 version);
void saveData(Common::WriteStream *ws) override;
private:
int32 _counter;
int32 _x;
int32 _y;
int32 _z;
};
} // End of namespace Ultima8
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,396 @@
/* 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/camera_process.h"
#include "ultima/ultima8/world/world.h"
#include "ultima/ultima8/world/current_map.h"
#include "ultima/ultima8/world/coord_utils.h"
#include "ultima/ultima8/world/actors/actor.h"
#include "ultima/ultima8/kernel/kernel.h"
#include "ultima/ultima8/ultima8.h"
#include "ultima/ultima8/world/get_object.h"
namespace Ultima {
namespace Ultima8 {
DEFINE_RUNTIME_CLASSTYPE_CODE(CameraProcess)
//
// Statics
//
CameraProcess *CameraProcess::_camera = nullptr;
int32 CameraProcess::_earthquake = 0;
int32 CameraProcess::_eqX = 0;
int32 CameraProcess::_eqY = 0;
CameraProcess::CameraProcess() : Process(), _s(),
_e(), _time(0), _elapsed(0),
_itemNum(0), _lastFrameNum(0) {
}
CameraProcess::~CameraProcess() {
if (_camera == this)
_camera = nullptr;
}
uint16 CameraProcess::SetCameraProcess(CameraProcess *cam) {
if (!cam) cam = new CameraProcess(0);
if (_camera) _camera->terminate();
_camera = cam;
return Kernel::get_instance()->addProcess(_camera);
}
void CameraProcess::ResetCameraProcess() {
if (_camera) _camera->terminate();
_camera = nullptr;
}
void CameraProcess::moveToLocation(int32 x, int32 y, int32 z) {
moveToLocation(Point3(x, y, z));
}
void CameraProcess::moveToLocation(const Point3 &p) {
if (_itemNum) {
Item *item = getItem(_itemNum);
if (item)
item->clearExtFlag(Item::EXT_CAMERA);
_itemNum = 0;
}
_s.x = _s.y = _s.z = _time = _elapsed = _lastFrameNum = 0;
_eqX = _eqY = _earthquake = 0;
_e = p;
_s = GetCameraLocation();
}
Point3 CameraProcess::GetCameraLocation() {
if (_camera) {
return _camera->GetLerped(256, true);
}
World *world = World::get_instance();
CurrentMap *map = world->getCurrentMap();
int map_num = map->getNum();
Actor *av = getControlledActor();
Point3 pt;
if (!av || av->getMapNum() != map_num) {
pt.x = 8192;
pt.y = 8192;
pt.z = 64;
} else {
pt = av->getLocation();
}
if (_earthquake) {
pt.x += 2 * _eqX + 4 * _eqY;
pt.y += -2 * _eqX + 4 * _eqY;
}
return pt;
}
//
// Constructors
//
// Track item, do nothing
CameraProcess::CameraProcess(uint16 _itemnum) :
_e(), _time(0), _elapsed(0), _itemNum(_itemnum), _lastFrameNum(0) {
_s = GetCameraLocation();
if (_itemNum) {
Item *item = getItem(_itemNum);
if (item) {
item->setExtFlag(Item::EXT_CAMERA);
_e = item->getLocation();
_e.z += 20; //!!constant
}
} else {
// No item
_itemNum = 0;
_e = _s;
}
}
// Stay over point
CameraProcess::CameraProcess(const Point3 &p) :
_e(p), _time(0), _elapsed(0), _itemNum(0), _lastFrameNum(0) {
_s = GetCameraLocation();
}
// Scroll
CameraProcess::CameraProcess(const Point3 &p, int32 time) :
_e(p), _time(time), _elapsed(0), _itemNum(0), _lastFrameNum(0) {
_s = GetCameraLocation();
debug(10, "Scrolling from (%d, %d,%d) to (%d, %d, %d) in %d frames",
_s.x, _s.y, _s.z, _e.x, _e.y, _e.z, _time);
}
void CameraProcess::terminate() {
if (_itemNum) {
Item *item = getItem(_itemNum);
if (item)
item->clearExtFlag(Item::EXT_CAMERA);
_itemNum = 0;
}
Process::terminate();
}
void CameraProcess::run() {
if (_earthquake) {
Common::RandomSource &rs = Ultima8Engine::get_instance()->getRandomSource();
_eqX = rs.getRandomNumberRngSigned(-_earthquake, _earthquake);
_eqY = rs.getRandomNumberRngSigned(-_earthquake, _earthquake);
} else {
_eqX = 0;
_eqY = 0;
}
if (_time && _elapsed > _time) {
_result = 0; // do we need this
CameraProcess::SetCameraProcess(nullptr); // This will terminate us
return;
}
_elapsed++;
}
void CameraProcess::itemMoved() {
if (!_itemNum)
return;
Item *item = getItem(_itemNum);
// We only update for now if lerping has been disabled
if (!item || !item->hasExtFlags(Item::EXT_LERP_NOPREV))
return;
Point3 pt = item->getLocation();
int32 maxdist = MAX(MAX(abs(_e.x - pt.z), abs(_e.y - pt.y)), abs(_e.z - pt.z));
if (GAME_IS_U8 || (GAME_IS_CRUSADER && maxdist > 0x40)) {
_s.x = _e.x = pt.x;
_s.y = _e.y = pt.y;
_e.z = pt.z;
_s.z = _e.z += 20;
World::get_instance()->getCurrentMap()->updateFastArea(_s, _e);
}
}
Point3 CameraProcess::GetLerped(int32 factor, bool noupdate) {
Point3 pt;
if (_time == 0) {
if (!noupdate) {
bool inBetween = true;
if (_lastFrameNum != _elapsed) {
// No lerping if we missed a frame
if ((_elapsed - _lastFrameNum) > 1) factor = 256;
_lastFrameNum = _elapsed;
inBetween = false;
}
if (!inBetween) {
_s = _e;
if (_itemNum) {
Item *item = getItem(_itemNum);
// Got it
if (item) {
item->setExtFlag(Item::EXT_CAMERA);
_s = _e;
_e = item->getLocation();
_e.z += 20; //!!constant
}
}
// Update the fast area
World::get_instance()->getCurrentMap()->updateFastArea(_s, _e);
}
}
if (factor == 256) {
pt = _e;
} else if (factor == 0) {
pt = _s;
} else {
// This way while possibly slower is more accurate
pt.x = ((_s.x * (256 - factor) + _e.x * factor) >> 8);
pt.y = ((_s.y * (256 - factor) + _e.y * factor) >> 8);
pt.z = ((_s.z * (256 - factor) + _e.z * factor) >> 8);
}
} else {
// Do a quadratic interpolation here of velocity (maybe), but not yet
int32 sfactor = _elapsed;
int32 efactor = _elapsed + 1;
if (sfactor > _time) sfactor = _time;
if (efactor > _time) efactor = _time;
Point3 ls;
ls.x = ((_s.x * (_time - sfactor) + _e.x * sfactor) / _time);
ls.y = ((_s.y * (_time - sfactor) + _e.y * sfactor) / _time);
ls.z = ((_s.z * (_time - sfactor) + _e.z * sfactor) / _time);
Point3 le;
le.x = ((_s.x * (_time - efactor) + _e.x * efactor) / _time);
le.y = ((_s.y * (_time - efactor) + _e.y * efactor) / _time);
le.z = ((_s.z * (_time - efactor) + _e.z * efactor) / _time);
// Update the fast area
if (!noupdate)
World::get_instance()->getCurrentMap()->updateFastArea(ls, le);
// This way while possibly slower is more accurate
pt.x = ((ls.x * (256 - factor) + le.x * factor) >> 8);
pt.y = ((ls.y * (256 - factor) + le.y * factor) >> 8);
pt.z = ((ls.z * (256 - factor) + le.z * factor) >> 8);
}
if (_earthquake) {
pt.x += 2 * _eqX + 4 * _eqY;
pt.y += -2 * _eqX + 4 * _eqY;
}
return pt;
}
uint16 CameraProcess::findRoof(int32 factor) {
int32 earthquake_old = _earthquake;
_earthquake = 0;
Point3 pt = GetLerped(factor);
_earthquake = earthquake_old;
// Camera box based on 2x2x2 footpad to avoid floor detected as roof
Box target(pt.x, pt.y, pt.z, 64, 64, 16);
PositionInfo info = World::get_instance()->getCurrentMap()->getPositionInfo(target, target, 0, kMainActorId);
return info.roof ? info.roof->getObjId() : 0;
}
void CameraProcess::saveData(Common::WriteStream *ws) {
Process::saveData(ws);
ws->writeUint32LE(static_cast<uint32>(_s.x));
ws->writeUint32LE(static_cast<uint32>(_s.y));
ws->writeUint32LE(static_cast<uint32>(_s.z));
ws->writeUint32LE(static_cast<uint32>(_e.x));
ws->writeUint32LE(static_cast<uint32>(_e.y));
ws->writeUint32LE(static_cast<uint32>(_e.z));
ws->writeUint32LE(static_cast<uint32>(_time));
ws->writeUint32LE(static_cast<uint32>(_elapsed));
ws->writeUint16LE(_itemNum);
ws->writeUint32LE(_lastFrameNum);
ws->writeUint32LE(static_cast<uint32>(_earthquake));
ws->writeUint32LE(static_cast<uint32>(_eqX));
ws->writeUint32LE(static_cast<uint32>(_eqY));
}
bool CameraProcess::loadData(Common::ReadStream *rs, uint32 version) {
if (!Process::loadData(rs, version)) return false;
_s.x = static_cast<int32>(rs->readUint32LE());
_s.y = static_cast<int32>(rs->readUint32LE());
_s.z = static_cast<int32>(rs->readUint32LE());
_e.x = static_cast<int32>(rs->readUint32LE());
_e.y = static_cast<int32>(rs->readUint32LE());
_e.z = static_cast<int32>(rs->readUint32LE());
_time = static_cast<int32>(rs->readUint32LE());
_elapsed = static_cast<int32>(rs->readUint32LE());
_itemNum = rs->readUint16LE();
_lastFrameNum = rs->readUint32LE();
_earthquake = static_cast<int32>(rs->readUint32LE()); //static
_eqX = static_cast<int32>(rs->readUint32LE()); //static
_eqY = static_cast<int32>(rs->readUint32LE()); //static
_camera = this; //static
return true;
}
// "Camera::move_to(uword, uword, ubyte, word)",
uint32 CameraProcess::I_moveTo(const uint8 *args, unsigned int argsize) {
ARG_UINT16(x);
ARG_UINT16(y);
ARG_UINT8(z);
if (argsize > 6) {
ARG_NULL16(); // sint16? what is this?
}
World_FromUsecodeXY(x, y);
Point3 pt(x, y, z);
CameraProcess::SetCameraProcess(new CameraProcess(pt));
return 0;
}
// "Camera::setCenterOn(uword)",
uint32 CameraProcess::I_setCenterOn(const uint8 *args, unsigned int /*argsize*/) {
ARG_OBJID(itemNum);
CameraProcess::SetCameraProcess(new CameraProcess(itemNum));
return 0;
}
// Camera::scrollTo(uword, uword, ubyte, word)
uint32 CameraProcess::I_scrollTo(const uint8 *args, unsigned int /*argsize*/) {
ARG_UINT16(x);
ARG_UINT16(y);
ARG_UINT8(z);
ARG_NULL16(); // some uint16?
World_FromUsecodeXY(x, y);
Point3 pt(x, y, z);
return CameraProcess::SetCameraProcess(new CameraProcess(pt, 25));
}
// Camera::startQuake(word)
uint32 CameraProcess::I_startQuake(const uint8 *args, unsigned int /*argsize*/) {
ARG_UINT16(strength);
SetEarthquake(strength);
return 0;
}
// Camera::stopQuake()
uint32 CameraProcess::I_stopQuake(const uint8 * /*args*/, unsigned int /*argsize*/) {
SetEarthquake(0);
return 0;
}
uint32 CameraProcess::I_getCameraX(const uint8 *args, unsigned int argsize) {
assert(GAME_IS_CRUSADER);
Point3 pt = GetCameraLocation();
return World_ToUsecodeCoord(pt.x);
}
uint32 CameraProcess::I_getCameraY(const uint8 *args, unsigned int argsize) {
assert(GAME_IS_CRUSADER);
Point3 pt = GetCameraLocation();
return World_ToUsecodeCoord(pt.y);
}
uint32 CameraProcess::I_getCameraZ(const uint8 *args, unsigned int argsize) {
Point3 pt = GetCameraLocation();
return pt.z;
}
} // End of namespace Ultima8
} // End of namespace Ultima

View File

@@ -0,0 +1,128 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef ULTIMA8_WORLD_CAMERAPROCESS_H
#define ULTIMA8_WORLD_CAMERAPROCESS_H
#include "ultima/ultima8/kernel/process.h"
#include "ultima/ultima8/usecode/intrinsics.h"
#include "ultima/ultima8/misc/classtype.h"
#include "ultima/ultima8/misc/point3.h"
namespace Ultima {
namespace Ultima8 {
/**
* The camera process. This works in 4 ways:
*
* It can be set to stay where it currently is
* It can be set to follow an item.
* It can be set to scroll to an item
* It can be set to stay at a location
*/
class CameraProcess : public Process {
public:
CameraProcess();
CameraProcess(uint16 itemnum); // Follow item/Do nothing
CameraProcess(const Point3 &p); // Goto location
CameraProcess(const Point3 &p, int32 time); // Scroll to location
~CameraProcess() override;
ENABLE_RUNTIME_CLASSTYPE()
void run() override;
// You will notice that this isn't the same as how Item::GetLerped works
Point3 GetLerped(int32 factor, bool noupdate = false);
//! Find the roof above the camera.
//! \param factor Interpolation factor for this frame
//! \return 0 if no roof found, objid of roof if found
uint16 findRoof(int32 factor);
/**
* Move the existing camera process to a new location. If the current process is focused on
* an item, remove that focus.
*
* This is not the same as setting a new process, because execution order will not change,
* so other pending events will all happen before the fast area is updated
*/
void moveToLocation(int32 x, int32 y, int32 z);
void moveToLocation(const Point3 &p);
INTRINSIC(I_setCenterOn);
INTRINSIC(I_moveTo);
INTRINSIC(I_scrollTo);
INTRINSIC(I_startQuake);
INTRINSIC(I_stopQuake);
INTRINSIC(I_getCameraX);
INTRINSIC(I_getCameraY);
INTRINSIC(I_getCameraZ);
static Point3 GetCameraLocation();
static CameraProcess *GetCameraProcess() {
return _camera;
}
/**
* Set the current camera process. Adds process and returns PID.
* The new process will go on the front of the process queue, so the fast area
* will be updated before any other pending actions occur.
*/
static uint16 SetCameraProcess(CameraProcess *);
static void ResetCameraProcess();
static void SetEarthquake(int32 e) {
_earthquake = e;
if (!e) _eqX = _eqY = 0;
}
/** Notify the Camera that the target item has moved */
void itemMoved();
void terminate() override; // Terminate NOW!
bool loadData(Common::ReadStream *rs, uint32 version);
void saveData(Common::WriteStream *ws) override;
uint16 getTrackedItem() const {
return _itemNum;
}
private:
Point3 _s;
Point3 _e;
int32 _time;
int32 _elapsed;
uint16 _itemNum;
int32 _lastFrameNum;
static CameraProcess *_camera;
static int32 _earthquake;
static int32 _eqX, _eqY;
};
} // End of namespace Ultima8
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,367 @@
/* 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/container.h"
#include "ultima/ultima8/kernel/object_manager.h"
#include "ultima/ultima8/usecode/uc_machine.h"
#include "ultima/ultima8/usecode/uc_list.h"
#include "ultima/ultima8/world/actors/main_actor.h"
#include "ultima/ultima8/world/get_object.h"
#include "ultima/ultima8/ultima8.h"
namespace Ultima {
namespace Ultima8 {
DEFINE_RUNTIME_CLASSTYPE_CODE(Container)
Container::Container() {
}
Container::~Container() {
// TODO: handle container's _contents.
// Either destroy the _contents, or move them up to this container's parent?
// if we don't have an _objId, we _must_ delete children
if (_objId == 0xFFFF) {
for (auto *item : _contents) {
delete item;
}
}
}
ObjId Container::assignObjId() {
ObjId id = Item::assignObjId();
for (auto *item : _contents) {
item->assignObjId();
item->setParent(id);
}
return id;
}
void Container::clearObjId() {
Item::clearObjId();
for (auto *item : _contents) {
// make sure we don't clear the ObjId of an Actor
assert(item->getObjId() >= 256);
item->clearObjId();
}
}
bool Container::CanAddItem(Item *item, bool checkwghtvol) {
if (!item) return false;
if (item->getParent() == this->getObjId()) return true; // already in here
if (item->getObjId() < 256) return false; // actors don't fit in containers
Container *c = dynamic_cast<Container *>(item);
if (c) {
// To quote Exult: "Watch for snake eating itself."
Container *p = this;
do {
if (p == c)
return false;
} while ((p = p->getParentAsContainer()) != nullptr);
}
if (checkwghtvol) {
uint32 volume = getContentVolume();
uint32 capacity = getCapacity();
// Because Avatar should be able to remove backpack and haul a barrel
// or chest on his back instead, artificially increase backpack volume
// for chests or barrels.
uint32 shapeid = item->getShape();
if (GAME_IS_U8 && (shapeid == 115 /*Barrel*/
|| shapeid == 78 || shapeid == 117 /*Chests*/)) {
// TODO: make this off by default, but can enable it through config
MainActor *avatar = getMainActor();
ObjId bp = avatar->getEquip(ShapeInfo::SE_BACKPACK);
Container *avatarbackpack = getContainer(bp);
if (avatarbackpack == this) {
capacity = 500;
}
}
// FIXME: this check isn't entirely correct. (combining items,...?)
if (volume + item->getVolume() > capacity)
return false;
const Item *p = getTopItem();
const Item *current = item->getTopItem();
// From outside to inside Avatar's inventory?
if (p->getObjId() == kMainActorId && current->getObjId() != kMainActorId) {
MainActor *av = getMainActor();
unsigned int str = av->getStr();
// FIXME: this check isn't entirely correct. (combining items,...?)
//CONSTANT!
if (p->getTotalWeight() + item->getTotalWeight() > 40 * str)
return false;
}
}
return true;
}
bool Container::addItem(Item *item, bool checkwghtvol) {
if (!CanAddItem(item, checkwghtvol)) return false;
if (item->getParent() == _objId) return true; // already in here
_contents.push_back(item);
return true;
}
bool Container::removeItem(Item *item) {
Std::list<Item *>::iterator iter;
for (iter = _contents.begin(); iter != _contents.end(); ++iter) {
if (*iter == item) {
_contents.erase(iter);
return true;
}
}
return false;
}
bool Container::moveItemToEnd(Item *item) {
Std::list<Item *>::iterator iter;
for (iter = _contents.begin(); iter != _contents.end(); ++iter) {
if (*iter == item) {
// found; move to end
_contents.erase(iter);
_contents.push_back(item);
return true;
}
}
// not found
return false;
}
void Container::removeContents() {
// CHECKME: ethereal items?
Container *parentCon = getParentAsContainer();
if (parentCon) {
// move _contents to parent
while (_contents.begin() != _contents.end()) {
Item *item = *(_contents.begin());
item->moveToContainer(parentCon);
}
} else {
// move _contents to our coordinates
while (_contents.begin() != _contents.end()) {
Item *item = *(_contents.begin());
item->move(_x, _y, _z);
}
}
}
void Container::destroyContents() {
while (_contents.begin() != _contents.end()) {
Item *item = *(_contents.begin());
assert(item);
Container *cont = dynamic_cast<Container *>(item);
if (cont) cont->destroyContents();
item->destroy(true); // we destroy the item immediately
}
}
void Container::setFlagRecursively(uint32 mask) {
setFlag(mask);
for (auto *item : _contents) {
item->setFlag(mask);
Container *cont = dynamic_cast<Container *>(item);
if (cont) cont->setFlagRecursively(mask);
}
}
void Container::destroy(bool delnow) {
//! What do we do with our _contents?
//! (in Exult we remove the _contents)
removeContents();
Item::destroy(delnow);
}
uint32 Container::getTotalWeight() const {
uint32 weight = Item::getTotalWeight();
// CONSTANT!
if (GAME_IS_U8 && getShape() == 79) {
// _contents of keyring don't weigh anything
return weight;
}
// CONSTANT!
if (GAME_IS_U8 && getShape() == 115) {
// barrel weight is unreasonably heavy
weight = 300;
}
for (const auto *item : _contents) {
weight += item->getTotalWeight();
}
return weight;
}
uint32 Container::getCapacity() const {
uint32 volume = getShapeInfo()->_volume;
return (volume == 0) ? 32 : volume;
}
uint32 Container::getContentVolume() const {
uint32 volume = 0;
for (const auto *item : _contents) {
volume += item->getVolume();
}
return volume;
}
void Container::containerSearch(UCList *itemlist, const uint8 *loopscript,
uint32 scriptsize, bool recurse) const {
for (auto *item : _contents) {
// check item against loopscript
if (item->checkLoopScript(loopscript, scriptsize)) {
assert(itemlist->getElementSize() == 2);
uint16 oId = item->getObjId();
itemlist->appenduint16(oId);
}
if (recurse) {
// recurse into child-containers
Container *container = dynamic_cast<Container *>(item);
if (container)
container->containerSearch(itemlist, loopscript,
scriptsize, recurse);
}
}
}
Item *Container::getFirstItemWithShape(uint16 shapeno, bool recurse) {
for (auto *item : _contents) {
if (item->getShape() == shapeno)
return item;
if (recurse) {
// recurse into child-containers
Container *container = dynamic_cast<Container *>(item);
if (container) {
Item *result = container->getFirstItemWithShape(shapeno, recurse);
if (result)
return result;
}
}
}
return nullptr;
}
void Container::getItemsWithShapeFamily(Std::vector<Item *> &itemlist, uint16 family, bool recurse) {
for (auto *item : _contents) {
if (item->getShapeInfo()->_family == family)
itemlist.push_back(item);
if (recurse) {
// recurse into child-containers
Container *container = dynamic_cast<Container *>(item);
if (container) {
container->getItemsWithShapeFamily(itemlist, family, recurse);
}
}
}
}
Common::String Container::dumpInfo() const {
return Item::dumpInfo() +
Common::String::format("; Container vol: %u/%u, total weight: %u, items: %u",
getContentVolume(), getCapacity(), getTotalWeight(), _contents.size());
}
void Container::saveData(Common::WriteStream *ws) {
Item::saveData(ws);
ws->writeUint32LE(static_cast<uint32>(_contents.size()));
for (auto *item : _contents) {
ObjectManager::get_instance()->saveObject(ws, item);
}
}
bool Container::loadData(Common::ReadStream *rs, uint32 version) {
if (!Item::loadData(rs, version)) return false;
uint32 contentcount = rs->readUint32LE();
// read contents
for (unsigned int i = 0; i < contentcount; ++i) {
Object *obj = ObjectManager::get_instance()->loadObject(rs, version);
Item *item = dynamic_cast<Item *>(obj);
if (!item) return false;
addItem(item);
item->setParent(_objId);
}
return true;
}
uint32 Container::I_removeContents(const uint8 *args, unsigned int /*argsize*/) {
ARG_CONTAINER_FROM_PTR(container);
if (!container) return 0;
container->removeContents();
return 0;
}
uint32 Container::I_destroyContents(const uint8 *args, unsigned int /*argsize*/) {
ARG_CONTAINER_FROM_PTR(container);
if (!container) return 0;
container->destroyContents();
return 0;
}
} // End of namespace Ultima8
} // End of namespace Ultima

View File

@@ -0,0 +1,132 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef ULTIMA8_WORLD_CONTAINER_H
#define ULTIMA8_WORLD_CONTAINER_H
#include "ultima/ultima8/world/item.h"
#include "ultima/shared/std/containers.h"
#include "ultima/ultima8/usecode/intrinsics.h"
#include "ultima/ultima8/misc/classtype.h"
namespace Ultima {
namespace Ultima8 {
class UCList;
class Container : public Item {
friend class ItemFactory;
friend class ContainerGump;
friend class PaperdollGump;
public:
Container();
~Container() override;
ENABLE_RUNTIME_CLASSTYPE()
//! Check if an item can be added to the container
//! \param item The item to check
//! \param checkwghtvol Need to check weight and volume?
//! \return true if item can be added, false if not
virtual bool CanAddItem(Item *item, bool checkwghtvol = false);
//! Add an item to the container. This does NOT update item.
//! \param item The item to add
//! \param checkwghtvol Need to check weight and volume?
//! \return true if item was added, false if failed
virtual bool addItem(Item *item, bool checkwghtvol = false);
//! Remove an item from the container. This does NOT update item.
//! \param item The item to remove
//! \return true if successful, false if item wasn't in container
virtual bool removeItem(Item *item);
//! Move an item to the end of the contents list
//! \param item The item to move
//! \return true if successful, false if item isn't in this container
virtual bool moveItemToEnd(Item *item);
//! Remove all contents, moving them to this container's
//! parent. (Or into the world if this container has no parent.)
//! Note: not yet implemented
void removeContents();
//! Destroy all contents.
void destroyContents();
//! Set flag on container and all its contents recursively
void setFlagRecursively(uint32 mask) override;
//! Search the container for items matching the given loopscript.
//! \param itemlist The matching items are appended to this list
//! \param loopscript The loopscript to match items against
//! \param scriptsize The size (in bytes) of the loopscript
//! \param recurse If true, search through child-containers too
void containerSearch(UCList *itemlist, const uint8 *loopscript,
uint32 scriptsize, bool recurse) const;
//! A simpler search of the container which just gets the
//! first item with a given shape number, optionally recursively.
//! \return The first item with that shape, or nullptr if nothing found.
Item *getFirstItemWithShape(uint16 shapeno, bool recurse);
//! A simpler search of the container which just gets the
//! items with a given shape family, optionally recursively.
//! \return The first item with that shape, or nullptr if nothing found.
void getItemsWithShapeFamily(Std::vector<Item *> &itemlist, uint16 family, bool recurse);
//! Get the weight of the container and its contents
//! \return weight
uint32 getTotalWeight() const override;
//! Get the container's capacity
virtual uint32 getCapacity() const;
//! Get the total volume used up by the container's current contents
virtual uint32 getContentVolume() const;
//! Assign self and contents an objID
//! \return the assiged ID
ObjId assignObjId() override;
//! Clear objIDs of self and contents
void clearObjId() override;
//! Destroy self
void destroy(bool delnow = false) override;
Common::String dumpInfo() const override;
bool loadData(Common::ReadStream *rs, uint32 version);
void saveData(Common::WriteStream *ws) override;
INTRINSIC(I_removeContents);
INTRINSIC(I_destroyContents);
protected:
Std::list<Item *> _contents;
};
} // End of namespace Ultima8
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,77 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef ULTIMA8_WORLD_COORDUTILS_H
#define ULTIMA8_WORLD_COORDUTILS_H
#include "ultima/ultima8/ultima8.h"
namespace Ultima {
namespace Ultima8 {
/**
* Convert from c++ code coordinates to usecode coordinates
*/
template<typename T>
static inline void World_ToUsecodeXY(T &x, T &y) {
if (GAME_IS_CRUSADER) {
x /= 2;
y /= 2;
}
}
/**
* Convert from usecode coordinates to c++ code coordinates
*/
template<typename T>
static inline void World_FromUsecodeXY(T &x, T &y) {
if (GAME_IS_CRUSADER) {
x *= 2;
y *= 2;
}
}
/**
* Convert a single x or y value from c++ coordinate to usecode coordinate
*/
template<typename T>
static inline T World_ToUsecodeCoord(T v) {
if (GAME_IS_CRUSADER)
return v / 2;
else
return v;
}
/**
* Convert a single x or y value from usecode coordinate to c++ code coordinate
*/
template<typename T>
static inline T World_FromUsecodeCoord(T v) {
if (GAME_IS_CRUSADER)
return v * 2;
else
return v;
}
}
}
#endif // ULTIMA8_WORLD_COORDUTILS_H

View File

@@ -0,0 +1,95 @@
/* 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/create_item_process.h"
#include "ultima/ultima8/world/item_factory.h"
#include "ultima/ultima8/world/item.h"
namespace Ultima {
namespace Ultima8 {
// p_dynamic_class stuff
DEFINE_RUNTIME_CLASSTYPE_CODE(CreateItemProcess)
CreateItemProcess::CreateItemProcess()
: Process(), _shape(0), _frame(0), _quality(0), _flags(0),
_npcNum(0), _mapNum(0), _extendedFlags(0),
_x(0), _y(0), _z(0) {
}
CreateItemProcess::CreateItemProcess(uint32 shape, uint32 frame,
uint16 quality, uint16 flags,
uint16 npcnum, uint16 mapnum,
uint32 extendedflags,
int32 x, int32 y, int32 z)
: _shape(shape), _frame(frame), _quality(quality), _flags(flags),
_npcNum(npcnum), _mapNum(mapnum), _extendedFlags(extendedflags),
_x(x), _y(y), _z(z) {
}
CreateItemProcess::~CreateItemProcess(void) {
}
void CreateItemProcess::run() {
Item *item = ItemFactory::createItem(_shape, _frame, _quality, _flags,
_npcNum, _mapNum, _extendedFlags, true);
item->move(_x, _y, _z);
_result = item->getObjId();
terminate();
}
void CreateItemProcess::saveData(Common::WriteStream *ws) {
Process::saveData(ws);
ws->writeUint32LE(_shape);
ws->writeUint32LE(_frame);
ws->writeUint16LE(_quality);
ws->writeUint16LE(_flags);
ws->writeUint16LE(_npcNum);
ws->writeUint16LE(_mapNum);
ws->writeUint32LE(_extendedFlags);
ws->writeUint32LE(static_cast<uint32>(_x));
ws->writeUint32LE(static_cast<uint32>(_y));
ws->writeUint32LE(static_cast<uint32>(_z));
}
bool CreateItemProcess::loadData(Common::ReadStream *rs, uint32 version) {
if (!Process::loadData(rs, version)) return false;
_shape = rs->readUint32LE();
_frame = rs->readUint32LE();
_quality = rs->readUint16LE();
_flags = rs->readUint16LE();
_npcNum = rs->readUint16LE();
_mapNum = rs->readUint16LE();
_extendedFlags = rs->readUint32LE();
_x = static_cast<int32>(rs->readUint32LE());
_y = static_cast<int32>(rs->readUint32LE());
_z = static_cast<int32>(rs->readUint32LE());
return true;
}
} // End of namespace Ultima8
} // End of namespace Ultima

View File

@@ -0,0 +1,61 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef ULTIMA8_WORLD_CREATEITEMPROCESS_H
#define ULTIMA8_WORLD_CREATEITEMPROCESS_H
#include "ultima/ultima8/kernel/process.h"
#include "ultima/ultima8/misc/classtype.h"
namespace Ultima {
namespace Ultima8 {
class CreateItemProcess : public Process {
public:
// p_dynamic_class stuff
ENABLE_RUNTIME_CLASSTYPE()
CreateItemProcess();
CreateItemProcess(uint32 shape, uint32 frame, uint16 quality,
uint16 flags, uint16 npcnum, uint16 mapnum,
uint32 extendedflags, int32 x, int32 y, int32 z);
~CreateItemProcess(void) override;
void run() override;
bool loadData(Common::ReadStream *rs, uint32 version);
void saveData(Common::WriteStream *ws) override;
protected:
uint32 _shape;
uint32 _frame;
uint16 _quality;
uint16 _flags;
uint16 _npcNum;
uint16 _mapNum;
uint32 _extendedFlags;
int32 _x, _y, _z;
};
} // End of namespace Ultima8
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,116 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "ultima/ultima8/misc/debugger.h"
#include "ultima/ultima8/kernel/kernel.h"
#include "ultima/ultima8/world/actors/main_actor.h"
#include "ultima/ultima8/world/actors/cru_avatar_mover_process.h"
#include "ultima/ultima8/world/crosshair_process.h"
#include "ultima/ultima8/world/item_factory.h"
#include "ultima/ultima8/world/get_object.h"
#include "ultima/ultima8/ultima8.h"
#include "math/utils.h"
namespace Ultima {
namespace Ultima8 {
DEFINE_RUNTIME_CLASSTYPE_CODE(CrosshairProcess)
static const uint32 CROSSHAIR_SHAPE = 0x4CC;
static const float CROSSHAIR_DIST = 400.0;
CrosshairProcess *CrosshairProcess::_instance = nullptr;
CrosshairProcess::CrosshairProcess() : Process() {
_instance = this;
_type = 1; // persistent
}
CrosshairProcess::~CrosshairProcess() {
if (_instance == this)
_instance = nullptr;
}
void CrosshairProcess::run() {
Actor *actor = getControlledActor();
if (!actor)
return;
if (actor->isInCombat()) {
Kernel *kernel = Kernel::get_instance();
assert(kernel);
Point3 pt = actor->getLocation();
actor->addFireAnimOffsets(pt.x, pt.y, pt.z);
const CruAvatarMoverProcess *mover = dynamic_cast<CruAvatarMoverProcess *>(Ultima8Engine::get_instance()->getAvatarMoverProcess());
if (!mover) {
warning("lost CruAvatarMoverProcess");
return;
}
double angle = mover->getAvatarAngleDegrees() + 90.0;
if (angle < 90.0) {
// -1 is used to record the avatar is not in combat, so shouldn't happen?
return;
}
// Convert angle to 0~2pi
double rads = Math::deg2rad(angle);
float xoff = CROSSHAIR_DIST * cos(rads);
float yoff = CROSSHAIR_DIST * sin(rads);
pt.x -= static_cast<int32>(xoff);
pt.y -= static_cast<int32>(yoff);
Item *item;
if (_itemNum) {
item = getItem(_itemNum);
} else {
// Create a new sprite
item = ItemFactory::createItem(CROSSHAIR_SHAPE, 0, 0, Item::FLG_DISPOSABLE,
0, 0, Item::EXT_SPRITE, true);
setItemNum(item->getObjId());
}
if (item)
item->move(pt.x, pt.y, pt.z);
else
_itemNum = 0; // sprite gone? can happen during teleport.
} else {
if (_itemNum) {
Item *item = getItem(_itemNum);
if (item)
item->destroy();
_itemNum = 0;
}
}
}
void CrosshairProcess::saveData(Common::WriteStream *ws) {
Process::saveData(ws);
}
bool CrosshairProcess::loadData(Common::ReadStream *rs, uint32 version) {
if (!Process::loadData(rs, version)) return false;
_type = 1; // should be persistent but older savegames may not know that.
return true;
}
} // End of namespace Ultima8
} // End of namespace Ultima

View File

@@ -0,0 +1,59 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef ULTIMA8_WORLD_CROSSHAIRPROCESS_H
#define ULTIMA8_WORLD_CROSSHAIRPROCESS_H
#include "ultima/ultima8/kernel/process.h"
#include "ultima/ultima8/misc/classtype.h"
namespace Ultima {
namespace Ultima8 {
/**
* A process to update the location of the small red crosshairs in Crusader.
* The crosshairs are just based on avatar angle and don't move with the
* environment, which makes them simpler than the dynamic target reticle.
*/
class CrosshairProcess : public Process {
public:
CrosshairProcess();
~CrosshairProcess();
ENABLE_RUNTIME_CLASSTYPE()
void run() override;
bool loadData(Common::ReadStream *rs, uint32 version);
void saveData(Common::WriteStream *ws) override;
static CrosshairProcess *get_instance() {
return _instance;
}
private:
static CrosshairProcess *_instance;
};
} // End of namespace Ultima8
} // End of namespace Ultima
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,227 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef ULTIMA8_WORLD_CURRENTMAP_H
#define ULTIMA8_WORLD_CURRENTMAP_H
#include "ultima/shared/std/containers.h"
#include "ultima/ultima8/usecode/intrinsics.h"
#include "ultima/ultima8/world/position_info.h"
#include "ultima/ultima8/misc/direction.h"
#include "ultima/ultima8/misc/point3.h"
namespace Ultima {
namespace Ultima8 {
struct Box;
class Map;
class Item;
class UCList;
class TeleportEgg;
class EggHatcherProcess;
#define MAP_NUM_CHUNKS 64
#define MAP_NUM_TARGET_ITEMS 200
class CurrentMap {
friend class World;
public:
CurrentMap();
~CurrentMap();
void clear();
void writeback();
void loadMap(Map *map);
//! sets the currently loaded map, without any processing.
//! (Should only be used for loading.)
void setMap(Map *map) {
_currentMap = map;
}
//! Get the map number of the CurrentMap
uint32 getNum() const;
unsigned int getChunkSize() const {
return _mapChunkSize;
}
//! Add an item to the beginning of the item list
void addItem(Item *item);
//! Add an item to the end of the item list
void addItemToEnd(Item *item);
void removeItemFromList(Item *item, int32 oldx, int32 oldy);
void removeItem(Item *item);
//! Add an item to the list of possible targets (in Crusader)
void addTargetItem(const Item *item);
//! Remove an item from the list of possible targets (in Crusader)
void removeTargetItem(const Item *item);
//! Find the best target item in the given direction from the given start point.
Item *findBestTargetItem(int32 x, int32 y, int32 z, Direction dir, DirectionMode dirmode);
//! Update the fast area for the cameras position
void updateFastArea(const Point3 &from, const Point3 &to);
//! search an area for items matching a loopscript
//! \param itemlist the list to return objids in
//! \param loopscript the script to check items against
//! \param scriptsize the size (in bytes) of the loopscript
//! \param item the item around which you want to search, or 0.
//! if item is 0, search around (x,y)
//! \param range the (square) range to search
//! \param recurse if true, search in containers too
//! \param x x coordinate of search center if item is 0.
//! \param y y coordinate of search center if item is 0.
void areaSearch(UCList *itemlist, const uint8 *loopscript,
uint32 scriptsize, const Item *item, uint16 range,
bool recurse, int32 x = 0, int32 y = 0) const;
// Surface search: Search above and below an item.
void surfaceSearch(UCList *itemlist, const uint8 *loopscript,
uint32 scriptsize, const Item *item, bool above,
bool below, bool recurse = false) const;
// Collision detection. Returns position information with valid being true
// when the target box does not collide with any solid items.
// Ignores collisions when overlapping with the start box.
PositionInfo getPositionInfo(const Box &target, const Box &start, uint32 shapeflags, ObjId id) const;
// Note that this version of getPositionInfo can not take 'flipped' into account!
PositionInfo getPositionInfo(int32 x, int32 y, int32 z, uint32 shape, ObjId id) const;
//! Scan for a valid position for item in directions orthogonal to movedir
bool scanForValidPosition(int32 x, int32 y, int32 z, const Item *item,
Direction movedir, bool wantsupport,
int32 &tx, int32 &ty, int32 &tz);
struct SweepItem {
SweepItem(ObjId it, int32 ht, int32 et, bool touch,
bool touchfloor, bool block, uint8 dir)
: _item(it), _hitTime(ht), _endTime(et), _touching(touch),
_touchingFloor(touchfloor), _blocking(block), _dirs(dir) { }
ObjId _item; // Item that was hit
//
// The time values here are 'normalized' fixed point values
// They range from 0 for the start of the move to 0x4000 for the end of
// The move.
//
// Linear interpolate between the start and end positions using
// hit_time to find where the moving item was when the hit occurs
//
int32 _hitTime; // if -1, already hitting when sweep started.
int32 _endTime; // if 0x4000, still hitting when sweep finished
bool _touching; // We are only touching (don't actually overlap)
bool _touchingFloor; // touching and directly below the moving item
bool _blocking; // This item blocks the moving item
uint8 _dirs; // Directions in which the item is being hit.
// Bitmask. Bit 0 is x, 1 is y, 2 is z.
// Use this func to get the interpolated location of the hit
Point3 GetInterpolatedCoords(const Point3 &start, const Point3 &end) const {
Point3 pt;
pt.x = start.x + ((end.x - start.x) * (_hitTime >= 0 ? _hitTime : 0) + (end.x > start.x ? 0x2000 : -0x2000)) / 0x4000;
pt.y = start.y + ((end.y - start.y) * (_hitTime >= 0 ? _hitTime : 0) + (end.y > start.y ? 0x2000 : -0x2000)) / 0x4000;
pt.z = start.z + ((end.z - start.z) * (_hitTime >= 0 ? _hitTime : 0) + (end.z > start.z ? 0x2000 : -0x2000)) / 0x4000;
return pt;
}
};
//! Perform a sweepTest for an item move
//! \param start Start point to sweep from.
//! \param end End point to sweep to.
//! \param dims Bounding size of item to check.
//! \param shapeflags shapeflags of item to check.
//! \param item ObjId of the item being checked. This will allow item to
//! be skipped from being tested against. Use 0 for no item.
//! \param solid_only If true, only test solid items.
//! \param hit Pointer to a list to fill with items hit. Items are sorted
//! by SweepItem::hit_time
//! \return false if no items were hit.
//! true if any items were hit.
bool sweepTest(const Point3 &start, const Point3 &end,
const int32 dims[3], uint32 shapeflags,
ObjId item, bool solid_only, Std::list<SweepItem> *hit) const;
TeleportEgg *findDestination(uint16 id);
// Not allowed to modify the list. Remember to use const_iterator
const Std::list<Item *> *getItemList(int32 gx, int32 gy) const;
bool isChunkFast(int32 cx, int32 cy) const {
// CONSTANTS!
if (cx < 0 || cy < 0 || cx >= MAP_NUM_CHUNKS || cy >= MAP_NUM_CHUNKS)
return false;
return (_fast[cy][cx / 32] & (1 << (cx & 31))) != 0;
}
void setFastAtPoint(const Point3 &pt);
// Set the entire map as being 'fast'
void setWholeMapFast();
void save(Common::WriteStream *ws);
bool load(Common::ReadStream *rs, uint32 version);
INTRINSIC(I_canExistAt);
INTRINSIC(I_canExistAtPoint);
private:
void loadItems(const Std::list<Item *> &itemlist, bool callCacheIn);
void createEggHatcher();
//! clip the given map chunk numbers to iterate over them safely
static void clipMapChunks(int &minx, int &maxx, int &miny, int &maxy);
Map *_currentMap;
// item lists. Lots of them :-)
// items[x][y]
Std::list<Item *> _items[MAP_NUM_CHUNKS][MAP_NUM_CHUNKS];
ProcId _eggHatcher;
// Fast area bit masks -> fast[ry][rx/32]&(1<<(rx&31));
uint32 _fast[MAP_NUM_CHUNKS][MAP_NUM_CHUNKS / 32];
int32 _fastXMin, _fastYMin, _fastXMax, _fastYMax;
int _mapChunkSize;
//! Items that are "targetable" in Crusader. It might be faster to store
//! this in a more fancy data structure, but this works fine.
ObjId _targets[MAP_NUM_TARGET_ITEMS];
void setChunkFast(int32 cx, int32 cy);
void unsetChunkFast(int32 cx, int32 cy);
};
} // End of namespace Ultima8
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,109 @@
/* 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/item.h"
#include "ultima/ultima8/world/item_factory.h"
#include "ultima/ultima8/audio/audio_process.h"
#include "ultima/ultima8/kernel/kernel.h"
#include "ultima/ultima8/ultima8.h"
namespace Ultima {
namespace Ultima8 {
DamageInfo::DamageInfo(uint8 data[6]) {
_flags = data[0];
_sound = data[1];
_data[0] = data[2];
_data[1] = data[3];
_data[2] = data[4];
_damagePoints = data[5];
}
bool DamageInfo::applyToItem(Item *item, uint16 points) const {
if (!item)
return false;
// The game does this.. it seems to be used to mean
// "destroyed" (as distinct from broken?)
if (item->hasFlags(Item::FLG_GUMP_OPEN))
return false;
uint8 itemPts = item->getDamagePoints();
if (points < itemPts) {
item->setDamagePoints(itemPts - points);
return false;
}
item->setDamagePoints(0);
item->setFlag(Item::FLG_GUMP_OPEN | Item::FLG_BROKEN);
// Get some data out of the item before we potentially delete
// it by explosion
uint16 q = item->getQuality();
Point3 pt = item->getLocation();
int32 mapnum = item->getMapNum();
if (explode()) {
item->explode(explosionType(), explodeDestroysItem(), explodeWithDamage());
if (explodeDestroysItem())
item = nullptr;
}
if (_sound) {
AudioProcess *audio = AudioProcess::get_instance();
if (audio) {
ObjId objid = item ? item->getObjId() : 0;
audio->playSFX(_sound, 0x10, objid, 1, true);
}
}
if (replaceItem()) {
uint16 replacementShape = getReplacementShape();
uint8 replacementFrame = getReplacementFrame();
Item *newitem = ItemFactory::createItem(replacementShape, replacementFrame, q, 0, 0, mapnum, 0, true);
newitem->move(pt);
if (item)
item->destroy();
} else if (!explodeDestroysItem()) {
Common::RandomSource &rs = Ultima8Engine::get_instance()->getRandomSource();
if (frameDataIsAbsolute()) {
int frameval = 1;
if (_data[1])
frameval++;
if (_data[2])
frameval++;
item->setFrame(_data[rs.getRandomNumber(frameval - 1)]);
} else {
int frameoff = 0;
for (int i = 0; i < 3; i++)
if (_data[i])
frameoff++;
if (!frameoff) {
item->destroy();
} else {
uint32 frame = item->getFrame();
item->setFrame(frame + _data[rs.getRandomNumber(frameoff - 1)]);
}
}
}
return true;
}
} // End of namespace Ultima8
} // End of namespace Ultima

View File

@@ -0,0 +1,99 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef WORLD_DAMAGE_INFO_H
#define WORLD_DAMAGE_INFO_H
#include "ultima/shared/std/string.h"
namespace Ultima {
namespace Ultima8 {
class Item;
/**
* The damage.dat flex contains data about each shape and how it should be damaged
*/
class DamageInfo {
public:
DamageInfo(uint8 data[6]);
~DamageInfo() {};
//! apply this damage info to the given item. Returns true if the item was destroyed in the process.
bool applyToItem(Item *item, uint16 points) const;
bool frameDataIsAbsolute() const {
return (_flags >> 7) & 1;
}
bool replaceItem() const {
return (_flags >> 6) & 1;
}
bool explodeDestroysItem() const {
return (_flags >> 5) & 1;
}
bool explodeWithDamage() const {
return (_flags >> 3) & 1;
}
bool takesDamage() const {
return _flags & 1;
}
uint8 damagePoints() const {
return _damagePoints;
}
protected:
bool explode() const {
return (_flags & 0x06) != 0;
}
int explosionType() const {
assert(explode());
return ((_flags & 0x06) >> 1) - 1;
}
uint16 getReplacementShape() const {
assert(replaceItem());
return static_cast<uint16>(_data[1]) << 8 | _data[0];
}
uint16 getReplacementFrame() const {
assert(replaceItem());
return static_cast<uint16>(_data[2]);
}
// Flags are ABCxDEEF
// A = frame data is absolute (not relative to current)
// B = item is replaced when destroyed
// C = item destroyed after explosion
// D = explosion damages surrounding items
// EE = 2 bits for explosion type
// F = item takes damage
uint8 _flags;
uint8 _sound;
uint8 _data[3];
uint8 _damagePoints;
};
} // End of namespace Ultima8
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,85 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "ultima/ultima8/world/destroy_item_process.h"
#include "ultima/ultima8/world/item.h"
#include "ultima/ultima8/world/get_object.h"
namespace Ultima {
namespace Ultima8 {
DEFINE_RUNTIME_CLASSTYPE_CODE(DestroyItemProcess)
DestroyItemProcess::DestroyItemProcess() : Process() {
}
DestroyItemProcess::DestroyItemProcess(Item *item) {
if (item)
_itemNum = item->getObjId();
else
_itemNum = 0;
_type = 0x232;
}
void DestroyItemProcess::run() {
if (_itemNum == 0) {
// need to get ObjId to use from process result. (We were apparently
// waiting for a process which returned the ObjId to delete.)
_itemNum = static_cast<ObjId>(_result);
}
Item *it = getItem(_itemNum);
if (!it) {
// somebody did our work for us...
terminate();
return;
}
// FIXME: should probably prevent player from opening gump in the
// first place...
if (it->hasFlags(Item::FLG_GUMP_OPEN)) {
// first close gump in case player is still rummaging through us
it->closeGump();
}
// bye bye
// (note that Container::destroy() calls removeContents())
it->destroy(true);
// NOTE: we're terminated here because this process belongs to the item
}
void DestroyItemProcess::saveData(Common::WriteStream *ws) {
Process::saveData(ws);
}
bool DestroyItemProcess::loadData(Common::ReadStream *rs, uint32 version) {
if (!Process::loadData(rs, version)) return false;
return true;
}
} // End of namespace Ultima8
} // End of namespace Ultima

View File

@@ -0,0 +1,52 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef ULTIMA8_WORLD_DESTROYITEMPROCESS_H
#define ULTIMA8_WORLD_DESTROYITEMPROCESS_H
#include "ultima/ultima8/kernel/process.h"
#include "ultima/ultima8/misc/classtype.h"
namespace Ultima {
namespace Ultima8 {
class Item;
class DestroyItemProcess : public Process {
public:
DestroyItemProcess();
//! DestroyItemProcess
//! \param item The item to destroy. If 0, use process result as ObjId.
DestroyItemProcess(Item *item);
ENABLE_RUNTIME_CLASSTYPE()
void run() override;
bool loadData(Common::ReadStream *rs, uint32 version);
void saveData(Common::WriteStream *ws) override;
};
} // End of namespace Ultima8
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,130 @@
/* 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/egg.h"
#include "ultima/ultima8/ultima8.h"
#include "ultima/ultima8/world/get_object.h"
#include "ultima/ultima8/usecode/uc_machine.h"
namespace Ultima {
namespace Ultima8 {
DEFINE_RUNTIME_CLASSTYPE_CODE(Egg)
Egg::Egg() : _hatched(false) {
}
Egg::~Egg() {
}
uint16 Egg::hatch() {
if (_hatched) return 0;
_hatched = true;
return callUsecodeEvent_hatch();
}
uint16 Egg::unhatch() {
if (GAME_IS_CRUSADER) {
if (!_hatched) return 0;
_hatched = false;
return callUsecodeEvent_unhatch();
}
return 0;
}
Common::String Egg::dumpInfo() const {
return Item::dumpInfo() +
Common::String::format(", range: %d, %d, hatched=%d", getXRange(), getYRange(), _hatched);
}
void Egg::leaveFastArea() {
reset();
Item::leaveFastArea();
}
void Egg::saveData(Common::WriteStream *ws) {
Item::saveData(ws);
uint8 h = _hatched ? 1 : 0;
ws->writeByte(h);
}
bool Egg::loadData(Common::ReadStream *rs, uint32 version) {
if (!Item::loadData(rs, version)) return false;
_hatched = (rs->readByte() != 0);
return true;
}
uint32 Egg::I_getEggXRange(const uint8 *args, unsigned int /*argsize*/) {
ARG_EGG_FROM_PTR(egg);
if (!egg) return 0;
return static_cast<uint32>(egg->getXRange());
}
uint32 Egg::I_getEggYRange(const uint8 *args, unsigned int /*argsize*/) {
ARG_EGG_FROM_PTR(egg);
if (!egg) return 0;
return static_cast<uint32>(egg->getYRange());
}
uint32 Egg::I_setEggXRange(const uint8 *args, unsigned int /*argsize*/) {
ARG_EGG_FROM_PTR(egg);
ARG_UINT16(xr);
if (!egg) return 0;
egg->setXRange(xr);
return 0;
}
uint32 Egg::I_setEggYRange(const uint8 *args, unsigned int /*argsize*/) {
ARG_EGG_FROM_PTR(egg);
ARG_UINT16(yr);
if (!egg) return 0;
egg->setYRange(yr);
return 0;
}
uint32 Egg::I_getEggId(const uint8 *args, unsigned int /*argsize*/) {
ARG_EGG_FROM_PTR(egg);
if (!egg) return 0;
return egg->getMapNum();
}
uint32 Egg::I_setEggId(const uint8 *args, unsigned int /*argsize*/) {
ARG_EGG_FROM_PTR(egg);
ARG_UINT16(eggid);
if (!egg) return 0;
egg->setMapNum(eggid);
return 0;
}
} // End of namespace Ultima8
} // End of namespace Ultima

View File

@@ -0,0 +1,87 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef ULTIMA8_WORLD_EGG_H
#define ULTIMA8_WORLD_EGG_H
#include "ultima/ultima8/world/item.h"
#include "ultima/ultima8/usecode/intrinsics.h"
namespace Ultima {
namespace Ultima8 {
class Egg : public Item {
friend class ItemFactory;
public:
Egg();
~Egg() override;
ENABLE_RUNTIME_CLASSTYPE()
int getXRange() const {
return (_npcNum >> 4) & 0xF;
}
int getYRange() const {
return _npcNum & 0xF;
}
void setXRange(int r) {
_npcNum &= 0x0F;
_npcNum |= (r & 0xF) << 4;
}
void setYRange(int r) {
_npcNum &= 0xF0;
_npcNum |= (r & 0xF);
}
//! hatch the egg
virtual uint16 hatch();
//! unhatch the egg (for Crusader only)
virtual uint16 unhatch();
//! The item has left the fast area
void leaveFastArea() override;
//! clear the '_hatched' flag
void reset() {
_hatched = false;
}
Common::String dumpInfo() const override;
bool loadData(Common::ReadStream *rs, uint32 version);
void saveData(Common::WriteStream *ws) override;
INTRINSIC(I_getEggXRange);
INTRINSIC(I_getEggYRange);
INTRINSIC(I_setEggXRange);
INTRINSIC(I_setEggYRange);
INTRINSIC(I_getEggId);
INTRINSIC(I_setEggId);
protected:
bool _hatched;
};
} // End of namespace Ultima8
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,117 @@
/* 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/egg_hatcher_process.h"
#include "ultima/ultima8/world/actors/main_actor.h"
#include "ultima/ultima8/world/teleport_egg.h"
#include "ultima/ultima8/world/get_object.h"
#include "ultima/ultima8/ultima8.h"
namespace Ultima {
namespace Ultima8 {
DEFINE_RUNTIME_CLASSTYPE_CODE(EggHatcherProcess)
EggHatcherProcess::EggHatcherProcess() {
}
EggHatcherProcess::~EggHatcherProcess() {
}
void EggHatcherProcess::addEgg(uint16 egg) {
_eggs.push_back(egg);
}
void EggHatcherProcess::addEgg(Egg *egg) {
assert(egg);
_eggs.push_back(egg->getObjId());
}
void EggHatcherProcess::run() {
bool nearteleporter = false;
MainActor *av = getMainActor();
if (!av)
return;
// CONSTANTS!
const int range_mul = GAME_IS_U8 ? 32 : 64;
const int z_range = 48;
for (unsigned int i = 0; i < _eggs.size(); i++) {
uint16 eggid = _eggs[i];
Egg *egg = dynamic_cast<Egg *>(getObject(eggid));
if (!egg) continue; // egg gone
Point3 pte = egg->getLocation();
//! constants
int32 x1 = pte.x - range_mul * egg->getXRange();
int32 x2 = pte.x + range_mul * egg->getXRange();
int32 y1 = pte.y - range_mul * egg->getYRange();
int32 y2 = pte.y + range_mul * egg->getYRange();
// get avatar location
int32 axs, ays, azs;
Point3 pta = av->getLocation();
av->getFootpadWorld(axs, ays, azs);
// 'justTeleported':
// if the avatar teleports, set the 'justTeleported' flag.
// if this is set, don't hatch any teleport eggs
// unset it when you're out of range of any teleport eggs
TeleportEgg *tegg = dynamic_cast<TeleportEgg *>(egg);
if (x1 <= pta.x && pta.x - axs < x2 && y1 <= pta.y && pta.y - ays < y2 &&
pte.z - z_range < pta.z && pta.z <= pte.z + z_range) {
if (tegg && tegg->isTeleporter())
nearteleporter = true;
if (tegg && av->hasJustTeleported())
continue;
egg->hatch();
} else {
egg->unhatch();
}
}
if (!nearteleporter)
av->setJustTeleported(false); // clear flag
}
void EggHatcherProcess::saveData(Common::WriteStream *ws) {
Process::saveData(ws);
}
bool EggHatcherProcess::loadData(Common::ReadStream *rs, uint32 version) {
if (!Process::loadData(rs, version)) return false;
// the eggs will be re-added to the EggHatcherProcess when they're
// re-added to the CurrentMap
return true;
}
} // End of namespace Ultima8
} // End of namespace Ultima

View File

@@ -0,0 +1,55 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef ULTIMA8_WORLD_EGGHATCHERPROCESS_H
#define ULTIMA8_WORLD_EGGHATCHERPROCESS_H
#include "ultima/ultima8/kernel/process.h"
#include "ultima/shared/std/containers.h"
namespace Ultima {
namespace Ultima8 {
class Egg;
class EggHatcherProcess : public Process {
public:
EggHatcherProcess();
~EggHatcherProcess() override;
ENABLE_RUNTIME_CLASSTYPE()
void run() override;
void addEgg(Egg *egg);
void addEgg(uint16 egg);
bool loadData(Common::ReadStream *rs, uint32 version);
void saveData(Common::WriteStream *ws) override;
private:
Std::vector<uint16> _eggs;
};
} // End of namespace Ultima8
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,271 @@
/* 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/sprite_process.h"
#include "ultima/ultima8/world/fire_type.h"
#include "ultima/ultima8/world/current_map.h"
#include "ultima/ultima8/world/loop_script.h"
#include "ultima/ultima8/world/get_object.h"
#include "ultima/ultima8/world/world.h"
#include "ultima/ultima8/world/actors/actor.h"
#include "ultima/ultima8/usecode/uc_list.h"
#include "ultima/ultima8/kernel/kernel.h"
#include "ultima/ultima8/audio/audio_process.h"
#include "ultima/ultima8/ultima8.h"
namespace Ultima {
namespace Ultima8 {
uint16 FireType::getRandomDamage() const {
if (_minDamage == _maxDamage)
return _minDamage;
Common::RandomSource &rs = Ultima8Engine::get_instance()->getRandomSource();
return rs.getRandomNumberRng(_minDamage, _maxDamage);
}
// The first 3 arrays are valid for No Remorse and No Regret.
static const int16 FIRESOUND_1[] = { 0x26, 0x27, 0x41, 0x42, 0x45, 0x46 };
static const int16 FIRESOUND_3[] = { 0x1c, 0x6c, 0x3e };
static const int16 FIRESOUND_7[] = { 0x48, 0x5 };
// These ones are No Regret only.
static const int16 FIRESOUND_0x10_REG[] = { 0x8, 0x202 };
static const int16 FIRESOUND_0xE_REG[] = { 0x205, 0x204 };
static const int16 FIRESOUND_0x14_REG[] = { 0x207, 0x208 };
// Shape arrays are very similar but slightly different between games
static const int16 FIRESHAPE_3_REM[] = { 0x326, 0x320, 0x321 };
static const int16 FIRESHAPE_3_REG[] = { 0x326, 0x320, 0x321, 0x323 };
static const int16 FIRESHAPE_10_REM[] = { 0x31c, 0x31f, 0x322 };
static const int16 FIRESHAPE_10_REG[] = { 0x31c, 0x31f, 0x321 };
#define RANDOM_ELEM(array) (array[rs.getRandomNumber(ARRAYSIZE(array) - 1)])
void FireType::makeBulletSplashShapeAndPlaySound(int32 x, int32 y, int32 z) const {
Common::RandomSource &rs = Ultima8Engine::get_instance()->getRandomSource();
int16 sfxno = 0;
int16 shape = 0;
// First randomize the sprite and sound
switch (_typeNo) {
case 1:
case 0xb:
shape = 0x1d8;
sfxno = RANDOM_ELEM(FIRESOUND_1);
break;
case 2:
shape = 0x1d8;
if (GAME_IS_REGRET && (rs.getRandomNumber(2) == 0)) {
sfxno = RANDOM_ELEM(FIRESOUND_1);
}
break;
case 3:
case 4:
if (GAME_IS_REMORSE)
shape = RANDOM_ELEM(FIRESHAPE_3_REM);
else
shape = RANDOM_ELEM(FIRESHAPE_3_REG);
sfxno = RANDOM_ELEM(FIRESOUND_3);
break;
case 5:
shape = 0x537;
if (GAME_IS_REGRET) {
if (rs.getRandomBit())
sfxno = 0x164;
else
sfxno = 0x71;
}
break;
case 6:
shape = 0x578;
if (GAME_IS_REGRET)
sfxno = 0x206;
break;
case 7:
shape = 0x537;
sfxno = RANDOM_ELEM(FIRESOUND_7);
break;
case 10:
if (GAME_IS_REMORSE)
shape = RANDOM_ELEM(FIRESHAPE_10_REM);
else
shape = RANDOM_ELEM(FIRESHAPE_10_REG);
sfxno = RANDOM_ELEM(FIRESOUND_3);
break;
case 0xd:
shape = 0x1d8;
if (GAME_IS_REMORSE || (rs.getRandomNumber(3) == 0))
sfxno = RANDOM_ELEM(FIRESOUND_1);
break;
case 0xe:
shape = 0x56b;
if (GAME_IS_REGRET)
sfxno = RANDOM_ELEM(FIRESOUND_0xE_REG);
break;
case 0xf:
shape = 0x59b;
sfxno = RANDOM_ELEM(FIRESOUND_7);
if (GAME_IS_REGRET)
sfxno = RANDOM_ELEM(FIRESOUND_7);
break;
case 0x10: // No Regret only
shape = 0x643;
sfxno = RANDOM_ELEM(FIRESOUND_0x10_REG);
break;
case 0x11: // No Regret only
shape = 0x642;
sfxno = 0x203;
break;
case 0x12: // No Regret only
shape = 0x59b;
sfxno = RANDOM_ELEM(FIRESOUND_0x10_REG);
break;
case 0x13: // No Regret only
shape = 0x59b;
sfxno = RANDOM_ELEM(FIRESOUND_7);
break;
case 0x14: // No Regret only
shape = 0x641;
sfxno = RANDOM_ELEM(FIRESOUND_0x14_REG);
break;
case 0x15: // No Regret only
shape = 0x641;
sfxno = 0x20a;
break;
case 0x16: // No Regret only
shape = 0x31f;
sfxno = RANDOM_ELEM(FIRESOUND_3);
break;
case 9:
default:
shape = 0x537;
break;
}
int16 firstframe = 0;
int16 lastframe = 0x27;
// now randomize frames
switch (shape) {
case 0x56b:
firstframe = rs.getRandomNumber(2) * 6;
lastframe = firstframe + 5;
break;
case 0x537:
lastframe = 10;
break;
case 0x578:
case 0x642:
firstframe = rs.getRandomNumber(2) * 5;
lastframe = firstframe + 4;
break;
case 0x59b:
firstframe = rs.getRandomNumber(1) * 4;
lastframe = firstframe + 3;
break;
case 0x641: // No Regret only
case 0x643: // No Regret only
lastframe = 3;
break;
case 0x1d8: {
switch (rs.getRandomNumber(3)) {
case 0:
lastframe = 4;
break;
case 1:
firstframe = 5;
lastframe = 8;
break;
case 2:
firstframe = 9;
lastframe = 0xc;
break;
case 3:
firstframe = 0xd;
lastframe = 0x10;
break;
}
break;
}
}
SpriteProcess *sprite = new SpriteProcess(shape, firstframe, lastframe, 1, 3, x, y, z);
Kernel::get_instance()->addProcess(sprite);
AudioProcess *audio = AudioProcess::get_instance();
if (sfxno && audio) {
audio->playSFX(sfxno, 0x10, 0, 1, false);
}
}
void FireType::applySplashDamageAround(const Point3 &pt, int damage, int rangediv, const Item *exclude, const Item *src) const {
assert(rangediv > 0);
if (!getRange())
return;
static const uint32 BULLET_SPLASH_SHAPE = 0x1d9;
CurrentMap *currentmap = World::get_instance()->getCurrentMap();
//
// Find items in range and apply splash damage. Coordinates here are 2x the
// original game code (in line with our other x2 multipliers for game coords)
//
UCList uclist(2);
LOOPSCRIPT(script, LS_TOKEN_TRUE); // we want all items
currentmap->areaSearch(&uclist, script, sizeof(script), nullptr,
getRange() * 32 / rangediv, false, pt.x, pt.y);
for (unsigned int i = 0; i < uclist.getSize(); ++i) {
Item *splashitem = getItem(uclist.getuint16(i));
if (!splashitem) {
// already gone - probably got destroyed by some chain-reaction?
continue;
}
//
// Other items don't get splash damage from their own fire.. but the
// player does. Life is not fair..
//
if (splashitem == exclude || (splashitem == src && src != getControlledActor()) ||
splashitem->getShape() == BULLET_SPLASH_SHAPE)
continue;
int splashitemdamage = damage;
if (_typeNo == 3 || _typeNo == 4 || _typeNo == 10) {
Point3 pt2 = splashitem->getLocation();
int splashrange = pt.maxDistXYZ(pt2);
splashrange = (splashrange / 32) / 3;
if (splashrange)
splashitemdamage /= splashrange;
}
if (!splashitemdamage)
continue;
Direction splashdir;
if (src)
splashdir = src->getDirToItemCentre(pt);
else
splashdir = dir_north;
splashitem->receiveHit(0, splashdir, splashitemdamage, _typeNo);
}
}
}
}

View File

@@ -0,0 +1,113 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef ULTIMA8_WORLD_FIRETYPE_H
#define ULTIMA8_WORLD_FIRETYPE_H
namespace Ultima {
namespace Ultima8 {
class Item;
struct Point3;
/**
* A structure to hold data about the fire that comes from various weapons
*/
class FireType {
public:
constexpr FireType(uint16 typeNo, uint16 minDamage, uint16 maxDamage, uint8 range,
uint8 numShots, uint16 shieldCost, uint8 shieldMask, bool accurate,
uint16 cellsPerRound, uint16 roundDuration, bool nearSprite) :
_typeNo(typeNo), _minDamage(minDamage), _maxDamage(maxDamage),
_range(range), _numShots(numShots), _shieldCost(shieldCost),
_shieldMask(shieldMask), _accurate(accurate),
_cellsPerRound(cellsPerRound), _roundDuration(roundDuration),
_nearSprite(nearSprite) {}
uint16 getTypeNo() const {
return _typeNo;
}
uint16 getMinDamage() const {
return _minDamage;
}
uint16 getMaxDamage() const {
return _maxDamage;
}
uint8 getRange() const {
return _range;
}
uint8 getNumShots() const {
return _numShots;
}
uint16 getShieldCost() const {
return _shieldCost;
}
uint8 getShieldMask() const {
return _shieldMask;
}
bool getAccurate() const {
return _accurate;
}
uint16 getCellsPerRound() const {
return _cellsPerRound;
}
uint16 getRoundDuration() const {
return _roundDuration;
}
bool getNearSprite() const {
return _nearSprite;
}
uint16 getRandomDamage() const;
void applySplashDamageAround(const Point3 &pt, int damage, int rangediv,
const Item *exclude, const Item *src) const;
void makeBulletSplashShapeAndPlaySound(int32 x, int32 y, int32 z) const;
private:
uint16 _typeNo;
uint16 _minDamage;
uint16 _maxDamage;
uint8 _range;
uint8 _numShots;
uint16 _shieldCost;
uint8 _shieldMask;
bool _accurate;
uint16 _cellsPerRound;
uint16 _roundDuration;
bool _nearSprite;
};
}
}
#endif

Some files were not shown because too many files have changed in this diff Show More