Initial commit
This commit is contained in:
2878
engines/ultima/ultima8/world/actors/actor.cpp
Normal file
2878
engines/ultima/ultima8/world/actors/actor.cpp
Normal file
File diff suppressed because it is too large
Load Diff
495
engines/ultima/ultima8/world/actors/actor.h
Normal file
495
engines/ultima/ultima8/world/actors/actor.h
Normal 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
|
||||
54
engines/ultima/ultima8/world/actors/actor_anim.h
Normal file
54
engines/ultima/ultima8/world/actors/actor_anim.h
Normal 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
|
||||
730
engines/ultima/ultima8/world/actors/actor_anim_process.cpp
Normal file
730
engines/ultima/ultima8/world/actors/actor_anim_process.cpp
Normal 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
|
||||
94
engines/ultima/ultima8/world/actors/actor_anim_process.h
Normal file
94
engines/ultima/ultima8/world/actors/actor_anim_process.h
Normal 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
|
||||
@@ -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
|
||||
@@ -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
|
||||
90
engines/ultima/ultima8/world/actors/ambush_process.cpp
Normal file
90
engines/ultima/ultima8/world/actors/ambush_process.cpp
Normal 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
|
||||
51
engines/ultima/ultima8/world/actors/ambush_process.h
Normal file
51
engines/ultima/ultima8/world/actors/ambush_process.h
Normal 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
|
||||
110
engines/ultima/ultima8/world/actors/anim_action.cpp
Normal file
110
engines/ultima/ultima8/world/actors/anim_action.cpp
Normal 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
|
||||
188
engines/ultima/ultima8/world/actors/anim_action.h
Normal file
188
engines/ultima/ultima8/world/actors/anim_action.h
Normal 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
|
||||
112
engines/ultima/ultima8/world/actors/animation.cpp
Normal file
112
engines/ultima/ultima8/world/actors/animation.cpp
Normal 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
|
||||
182
engines/ultima/ultima8/world/actors/animation.h
Normal file
182
engines/ultima/ultima8/world/actors/animation.h
Normal 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
|
||||
657
engines/ultima/ultima8/world/actors/animation_tracker.cpp
Normal file
657
engines/ultima/ultima8/world/actors/animation_tracker.cpp
Normal 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
|
||||
147
engines/ultima/ultima8/world/actors/animation_tracker.h
Normal file
147
engines/ultima/ultima8/world/actors/animation_tracker.h
Normal 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
|
||||
1203
engines/ultima/ultima8/world/actors/attack_process.cpp
Normal file
1203
engines/ultima/ultima8/world/actors/attack_process.cpp
Normal file
File diff suppressed because it is too large
Load Diff
181
engines/ultima/ultima8/world/actors/attack_process.h
Normal file
181
engines/ultima/ultima8/world/actors/attack_process.h
Normal 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
|
||||
93
engines/ultima/ultima8/world/actors/auto_firer_process.cpp
Normal file
93
engines/ultima/ultima8/world/actors/auto_firer_process.cpp
Normal 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
|
||||
51
engines/ultima/ultima8/world/actors/auto_firer_process.h
Normal file
51
engines/ultima/ultima8/world/actors/auto_firer_process.h
Normal 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
|
||||
102
engines/ultima/ultima8/world/actors/avatar_death_process.cpp
Normal file
102
engines/ultima/ultima8/world/actors/avatar_death_process.cpp
Normal 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
|
||||
45
engines/ultima/ultima8/world/actors/avatar_death_process.h
Normal file
45
engines/ultima/ultima8/world/actors/avatar_death_process.h
Normal 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
|
||||
@@ -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
|
||||
48
engines/ultima/ultima8/world/actors/avatar_gravity_process.h
Normal file
48
engines/ultima/ultima8/world/actors/avatar_gravity_process.h
Normal 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
|
||||
381
engines/ultima/ultima8/world/actors/avatar_mover_process.cpp
Normal file
381
engines/ultima/ultima8/world/actors/avatar_mover_process.cpp
Normal 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
|
||||
145
engines/ultima/ultima8/world/actors/avatar_mover_process.h
Normal file
145
engines/ultima/ultima8/world/actors/avatar_mover_process.h
Normal 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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
47
engines/ultima/ultima8/world/actors/combat_dat.cpp
Normal file
47
engines/ultima/ultima8/world/actors/combat_dat.cpp
Normal 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
|
||||
75
engines/ultima/ultima8/world/actors/combat_dat.h
Normal file
75
engines/ultima/ultima8/world/actors/combat_dat.h
Normal 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
|
||||
329
engines/ultima/ultima8/world/actors/combat_process.cpp
Normal file
329
engines/ultima/ultima8/world/actors/combat_process.cpp
Normal 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
|
||||
75
engines/ultima/ultima8/world/actors/combat_process.h
Normal file
75
engines/ultima/ultima8/world/actors/combat_process.h
Normal 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
|
||||
737
engines/ultima/ultima8/world/actors/cru_avatar_mover_process.cpp
Normal file
737
engines/ultima/ultima8/world/actors/cru_avatar_mover_process.cpp
Normal 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
|
||||
@@ -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
|
||||
100
engines/ultima/ultima8/world/actors/cru_healer_process.cpp
Normal file
100
engines/ultima/ultima8/world/actors/cru_healer_process.cpp
Normal 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
|
||||
51
engines/ultima/ultima8/world/actors/cru_healer_process.h
Normal file
51
engines/ultima/ultima8/world/actors/cru_healer_process.h
Normal 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
|
||||
370
engines/ultima/ultima8/world/actors/cru_pathfinder_process.cpp
Normal file
370
engines/ultima/ultima8/world/actors/cru_pathfinder_process.cpp
Normal 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
|
||||
84
engines/ultima/ultima8/world/actors/cru_pathfinder_process.h
Normal file
84
engines/ultima/ultima8/world/actors/cru_pathfinder_process.h
Normal 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
|
||||
230
engines/ultima/ultima8/world/actors/grant_peace_process.cpp
Normal file
230
engines/ultima/ultima8/world/actors/grant_peace_process.cpp
Normal 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
|
||||
54
engines/ultima/ultima8/world/actors/grant_peace_process.h
Normal file
54
engines/ultima/ultima8/world/actors/grant_peace_process.h
Normal 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
|
||||
87
engines/ultima/ultima8/world/actors/guard_process.cpp
Normal file
87
engines/ultima/ultima8/world/actors/guard_process.cpp
Normal 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
|
||||
52
engines/ultima/ultima8/world/actors/guard_process.h
Normal file
52
engines/ultima/ultima8/world/actors/guard_process.h
Normal 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
|
||||
129
engines/ultima/ultima8/world/actors/heal_process.cpp
Normal file
129
engines/ultima/ultima8/world/actors/heal_process.cpp
Normal 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
|
||||
54
engines/ultima/ultima8/world/actors/heal_process.h
Normal file
54
engines/ultima/ultima8/world/actors/heal_process.h
Normal 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
|
||||
143
engines/ultima/ultima8/world/actors/loiter_process.cpp
Normal file
143
engines/ultima/ultima8/world/actors/loiter_process.cpp
Normal 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
|
||||
52
engines/ultima/ultima8/world/actors/loiter_process.h
Normal file
52
engines/ultima/ultima8/world/actors/loiter_process.h
Normal 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
|
||||
1080
engines/ultima/ultima8/world/actors/main_actor.cpp
Normal file
1080
engines/ultima/ultima8/world/actors/main_actor.cpp
Normal file
File diff suppressed because it is too large
Load Diff
216
engines/ultima/ultima8/world/actors/main_actor.h
Normal file
216
engines/ultima/ultima8/world/actors/main_actor.h
Normal 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
|
||||
51
engines/ultima/ultima8/world/actors/monster_info.h
Normal file
51
engines/ultima/ultima8/world/actors/monster_info.h
Normal 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
|
||||
189
engines/ultima/ultima8/world/actors/npc_dat.cpp
Normal file
189
engines/ultima/ultima8/world/actors/npc_dat.cpp
Normal 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
|
||||
84
engines/ultima/ultima8/world/actors/npc_dat.h
Normal file
84
engines/ultima/ultima8/world/actors/npc_dat.h
Normal 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
|
||||
124
engines/ultima/ultima8/world/actors/pace_process.cpp
Normal file
124
engines/ultima/ultima8/world/actors/pace_process.cpp
Normal 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
|
||||
53
engines/ultima/ultima8/world/actors/pace_process.h
Normal file
53
engines/ultima/ultima8/world/actors/pace_process.h
Normal 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
|
||||
609
engines/ultima/ultima8/world/actors/pathfinder.cpp
Normal file
609
engines/ultima/ultima8/world/actors/pathfinder.cpp
Normal 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
|
||||
116
engines/ultima/ultima8/world/actors/pathfinder.h
Normal file
116
engines/ultima/ultima8/world/actors/pathfinder.h
Normal 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
|
||||
268
engines/ultima/ultima8/world/actors/pathfinder_process.cpp
Normal file
268
engines/ultima/ultima8/world/actors/pathfinder_process.cpp
Normal 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
|
||||
65
engines/ultima/ultima8/world/actors/pathfinder_process.h
Normal file
65
engines/ultima/ultima8/world/actors/pathfinder_process.h
Normal 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
|
||||
@@ -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
|
||||
110
engines/ultima/ultima8/world/actors/quick_avatar_mover_process.h
Normal file
110
engines/ultima/ultima8/world/actors/quick_avatar_mover_process.h
Normal 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
|
||||
90
engines/ultima/ultima8/world/actors/resurrection_process.cpp
Normal file
90
engines/ultima/ultima8/world/actors/resurrection_process.cpp
Normal 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
|
||||
48
engines/ultima/ultima8/world/actors/resurrection_process.h
Normal file
48
engines/ultima/ultima8/world/actors/resurrection_process.h
Normal 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
|
||||
348
engines/ultima/ultima8/world/actors/rolling_thunder_process.cpp
Normal file
348
engines/ultima/ultima8/world/actors/rolling_thunder_process.cpp
Normal 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
|
||||
@@ -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
|
||||
93
engines/ultima/ultima8/world/actors/scheduler_process.cpp
Normal file
93
engines/ultima/ultima8/world/actors/scheduler_process.cpp
Normal 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
|
||||
50
engines/ultima/ultima8/world/actors/scheduler_process.h
Normal file
50
engines/ultima/ultima8/world/actors/scheduler_process.h
Normal 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
|
||||
189
engines/ultima/ultima8/world/actors/surrender_process.cpp
Normal file
189
engines/ultima/ultima8/world/actors/surrender_process.cpp
Normal 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
|
||||
60
engines/ultima/ultima8/world/actors/surrender_process.h
Normal file
60
engines/ultima/ultima8/world/actors/surrender_process.h
Normal 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
|
||||
@@ -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
|
||||
54
engines/ultima/ultima8/world/actors/targeted_anim_process.h
Normal file
54
engines/ultima/ultima8/world/actors/targeted_anim_process.h
Normal 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
|
||||
@@ -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
|
||||
@@ -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
|
||||
54
engines/ultima/ultima8/world/actors/treasure_info.h
Normal file
54
engines/ultima/ultima8/world/actors/treasure_info.h
Normal 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
|
||||
811
engines/ultima/ultima8/world/actors/u8_avatar_mover_process.cpp
Normal file
811
engines/ultima/ultima8/world/actors/u8_avatar_mover_process.cpp
Normal 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
|
||||
@@ -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
|
||||
80
engines/ultima/ultima8/world/actors/weapon_overlay.h
Normal file
80
engines/ultima/ultima8/world/actors/weapon_overlay.h
Normal 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
|
||||
39
engines/ultima/ultima8/world/armour_info.h
Normal file
39
engines/ultima/ultima8/world/armour_info.h
Normal 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
|
||||
98
engines/ultima/ultima8/world/bobo_boomer_process.cpp
Normal file
98
engines/ultima/ultima8/world/bobo_boomer_process.cpp
Normal 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
|
||||
59
engines/ultima/ultima8/world/bobo_boomer_process.h
Normal file
59
engines/ultima/ultima8/world/bobo_boomer_process.h
Normal 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
|
||||
396
engines/ultima/ultima8/world/camera_process.cpp
Normal file
396
engines/ultima/ultima8/world/camera_process.cpp
Normal 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
|
||||
128
engines/ultima/ultima8/world/camera_process.h
Normal file
128
engines/ultima/ultima8/world/camera_process.h
Normal 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
|
||||
367
engines/ultima/ultima8/world/container.cpp
Normal file
367
engines/ultima/ultima8/world/container.cpp
Normal 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
|
||||
132
engines/ultima/ultima8/world/container.h
Normal file
132
engines/ultima/ultima8/world/container.h
Normal 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
|
||||
77
engines/ultima/ultima8/world/coord_utils.h
Normal file
77
engines/ultima/ultima8/world/coord_utils.h
Normal 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
|
||||
95
engines/ultima/ultima8/world/create_item_process.cpp
Normal file
95
engines/ultima/ultima8/world/create_item_process.cpp
Normal 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
|
||||
61
engines/ultima/ultima8/world/create_item_process.h
Normal file
61
engines/ultima/ultima8/world/create_item_process.h
Normal 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
|
||||
116
engines/ultima/ultima8/world/crosshair_process.cpp
Normal file
116
engines/ultima/ultima8/world/crosshair_process.cpp
Normal 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
|
||||
59
engines/ultima/ultima8/world/crosshair_process.h
Normal file
59
engines/ultima/ultima8/world/crosshair_process.h
Normal 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
|
||||
1298
engines/ultima/ultima8/world/current_map.cpp
Normal file
1298
engines/ultima/ultima8/world/current_map.cpp
Normal file
File diff suppressed because it is too large
Load Diff
227
engines/ultima/ultima8/world/current_map.h
Normal file
227
engines/ultima/ultima8/world/current_map.h
Normal 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
|
||||
109
engines/ultima/ultima8/world/damage_info.cpp
Normal file
109
engines/ultima/ultima8/world/damage_info.cpp
Normal 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
|
||||
99
engines/ultima/ultima8/world/damage_info.h
Normal file
99
engines/ultima/ultima8/world/damage_info.h
Normal 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
|
||||
85
engines/ultima/ultima8/world/destroy_item_process.cpp
Normal file
85
engines/ultima/ultima8/world/destroy_item_process.cpp
Normal 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
|
||||
52
engines/ultima/ultima8/world/destroy_item_process.h
Normal file
52
engines/ultima/ultima8/world/destroy_item_process.h
Normal 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
|
||||
130
engines/ultima/ultima8/world/egg.cpp
Normal file
130
engines/ultima/ultima8/world/egg.cpp
Normal 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
|
||||
87
engines/ultima/ultima8/world/egg.h
Normal file
87
engines/ultima/ultima8/world/egg.h
Normal 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
|
||||
117
engines/ultima/ultima8/world/egg_hatcher_process.cpp
Normal file
117
engines/ultima/ultima8/world/egg_hatcher_process.cpp
Normal 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
|
||||
55
engines/ultima/ultima8/world/egg_hatcher_process.h
Normal file
55
engines/ultima/ultima8/world/egg_hatcher_process.h
Normal 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
|
||||
271
engines/ultima/ultima8/world/fire_type.cpp
Normal file
271
engines/ultima/ultima8/world/fire_type.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
113
engines/ultima/ultima8/world/fire_type.h
Normal file
113
engines/ultima/ultima8/world/fire_type.h
Normal 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
Reference in New Issue
Block a user