Initial commit

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

7
engines/grim/POTFILES Normal file
View File

@@ -0,0 +1,7 @@
engines/grim/grim.cpp
engines/grim/inputdialog.cpp
engines/grim/md5check.cpp
engines/grim/md5checkdialog.cpp
engines/grim/metaengine.cpp
engines/grim/resource.cpp
engines/grim/emi/sound/emisound.cpp

2638
engines/grim/actor.cpp Normal file

File diff suppressed because it is too large Load Diff

738
engines/grim/actor.h Normal file
View File

@@ -0,0 +1,738 @@
/* 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 GRIM_ACTOR_H
#define GRIM_ACTOR_H
#include "engines/grim/pool.h"
#include "engines/grim/object.h"
#include "engines/grim/color.h"
#include "math/vector3d.h"
#include "math/angle.h"
#include "math/quat.h"
namespace Grim {
class TextObject;
class Sector;
class Costume;
class LipSync;
class Font;
class Set;
class Material;
struct SetShadow;
struct Joint;
class EMIModel;
struct Plane {
Common::String setName;
Sector *sector;
};
typedef Common::List<Plane> SectorListType;
#define MAX_SHADOWS 8
struct Shadow {
Shadow();
Common::String name;
Math::Vector3d pos;
SectorListType planeList;
bool active;
bool dontNegate;
Color color;
void *userData;
};
/**
* @class Actor
*
* @short Actor represents a 3D character on screen.
*/
class Actor : public PoolObject<Actor> {
public:
enum CollisionMode {
CollisionOff = 0,
CollisionBox = 1,
CollisionSphere = 2
};
enum AlphaMode {
AlphaOff = -1,
AlphaReplace = 2,
AlphaModulate = 3 // Seems to be unused
};
enum LightMode {
LightStatic = 0,
LightFastDyn = 1,
LightNormDyn = 2,
LightNone = 3
};
/**
* Builds an actor setting up only the minimal variables.
*/
Actor();
/**
* Destroys the actor.
* The actor is automatically removed from the GrimEngine instance.
*/
~Actor();
static int32 getStaticTag() { return MKTAG('A', 'C', 'T', 'R'); }
/**
* Saves the actor state.
*
* @param savedState The save state to which save the actor's state.
*/
void saveState(SaveGame *savedState) const;
/**
* Restores the actor state.
*
* @param savedState The saved state from which the actor will be restored.
*/
bool restoreState(SaveGame *savedState);
/**
* Returns the name of the actor.
*/
const Common::String &getName() const { return _name; }
/**
* Sets the name of the actor.
*
* @param name The name.
*/
void setName(const Common::String &name) { _name = name; }
/**
* Sets the color of the subtitles of the actor.
*
* @param color The color.
* @see getTalkColor
*/
void setTalkColor(const Color &color) { _talkColor = color; }
/**
* Returns the color of the subtitles of the actor.
*
* @see setTalkColor
*/
Color getTalkColor() const { return _talkColor; }
/**
* Sets the position of the actor on the 3D scene.
*
* @param position The position.
* @see getPos
*/
void setPos(const Math::Vector3d &position);
/**
* Returns the position of the actor on the 3D scene.
*
* @see setPos
*/
Math::Vector3d getPos() const { return _pos; }
inline Math::Vector3d getDestPos() const { return _destPos; }
/**
* Tells the actor to go to the wanted position.
* If the actor follows the walkboxes it will find the best
* route to go there, otherwise it will walk on a straight line.
*
* @param position The destination position.
* @see stopWalking
* @see isWalking
*/
void walkTo(const Math::Vector3d &position);
/**
* Stops immediately the actor's walk.
*
* @see walkTo
* @see isWalking
*/
void stopWalking() { _walking = false; }
/**
* Returns true if the actor is walking to a position.
*
* @see walkTo
* @see stopWalking
*/
bool isWalking() const;
/**
* Sets the rotation of thes actor in the 3D scene.
* The effect is not immediate, the actor will slowly rotate
* to the destination orientation.
*
* @param pitch The rotation of the x axis
* @param yaw The rotation of the z axis
* @param roll The rotation of the y axis
* @param snap If true tells the actor to increate its turn speed.
* @see getPitch
* @see getYaw
* @see getRoll
* @see setRot
* @see turn
* @see isTurning
*/
void turnTo(const Math::Angle &pitch, const Math::Angle &yaw, const Math::Angle &roll, bool snap = false);
/**
* Turn the actor towards a point in space.
* The effect is not immediate, the actor will slowly rotate
* to the destination orientation.
*
* @param pos The position the actor should turn to.
* @param snap If true tells the actor to increate its turn speed.
* @see turnTo
* @see setRot
*/
void turnTo(const Math::Vector3d &pos, bool snap = false);
/**
* Turn the actor towards a point in space, by an amount defined by the turn rate.
* This function does not make an actor point at the given position, but it makes
* it rotate towards that.
*
* @param pos The position the actor should turn to.
* @return true if the actor has reached the desired orientation
* @see turnTo
* @see setRot
*/
bool singleTurnTo(const Math::Vector3d &pos);
/**
* Returns true if the actor is turning.
*
* @see turnTo
*/
bool isTurning() const;
/**
* Stops the actor from turning
*/
void stopTurning();
/**
* Sets the rotation of the actor in the 3D scene.
* The effect is immediate.
*
* @param pitch The rotation of the x axis
* @param yaw The rotation of the z axis
* @param roll The rotation of the y axis
* @see getPitch
* @see getYaw
* @see getRoll
* @see turnTo
* @see turn
* @see isTurning
*/
void setRot(const Math::Angle &pitch, const Math::Angle &yaw, const Math::Angle &roll);
/**
* Turn the actor towards a point in space.
* The effect is immediate.
*
* @param pos The position the actor should turn to.
* @see turnTo
* @see setRot
*/
void setRot(const Math::Vector3d &pos);
/**
* Turns the actor by the given parameter on the z axis.
* The actual movement depends also on the turn rate.
*
* @param dir The quantity of the movement.
* @see getPitch
* @see getYaw
* @see getRoll
* @see setRot
* @see turnTo
* @see isTurning
*/
void turn(int dir);
/**
* Returns the pitch of the actor, which is the rotation
* on the x axis.
*
* @see getYaw
* @see getRoll
* @see setRot
* @see turnTo
* @see isTurning
*/
Math::Angle getPitch() const { return _pitch; }
/**
* Returns the yaw of the actor, which is the rotation
* on the z axis.
*
* @see getPitch
* @see getRoll
* @see setRot
* @see turnTo
* @see isTurning
*/
Math::Angle getYaw() const { return _yaw; }
/**
* Returns the roll of the actor, which is the rotation
* on the y axis.
*
* @see getPitch
* @see getYaw
* @see setRot
* @see turnTo
* @see isTurning
*/
Math::Angle getRoll() const { return _roll; }
/**
* Calculates and returns the angle between the direction the
* actor is facing and the direction towards another actor.
*
* @param actor The actor to look at.
*/
Math::Angle getYawTo(const Actor *actor) const;
/**
* Calculates and returns the angle between the direction the
* actor is facing and the direction towards a point.
*
* @param actor The point to look at.
*/
Math::Angle getYawTo(const Math::Vector3d &p) const;
/**
* Sets the actor visibility.
*
* @param val The value: true if visible, false otherwise.
* @see isVisible
*/
void setVisibility(bool val) { _visible = val; }
/**
* Returns true if the actor is visible.
*
* @see setVisibility
*/
bool isVisible() const { return _visible; }
/**
* Sets the scale of the actor.
* A value of 1 is the natural size.
*
* @param scale The scale of the actor.
*/
void setScale(float scale) { _scale = scale; }
float getScale() const { return _scale; }
/**
* Sets the time scale of the actor, used to calculate the
* speed of its animations.
* A value of 1 is the normal speed.
*
* @param scale The time scale.
* @see getTimeScale
*/
void setTimeScale(float scale) { _timeScale = scale; }
/**
* Returns the time scale of the actor.
*
* @see setTimeScale
*/
float getTimeScale() const { return _timeScale; }
/**
* Puts the actor in a set.
*
* @param setName The name of the set.
*/
void putInSet(const Common::String &setName);
/**
* Returns true if the actor is in the given set.
* For engine internal use only, do not expose via lua API.
*
* @param setName The name of the set.
*/
bool isDrawableInSet(const Common::String &setName) const;
/**
* Returns true if the actor is in the given set.
* Can be exposed via lua API.
*
* @param setName The name of the set.
*/
bool isInSet(const Common::String &setName) const;
/**
* Sets the rate of the turning.
*
* @param rat The wanted rate.
* @see getTurnRate
*/
void setTurnRate(float rate) { _turnRate = rate; }
/**
* Returns the turn rate.
*
* @see setTurnRate
*/
float getTurnRate() const { return _turnRate; }
/**
* Sets the rate of the walk movement.
*
* @param rate The wanted rate.
* @see getWalkRate
*/
void setWalkRate(float rate) { _walkRate = rate; }
/**
* Returns the walk rate of the actor.
*
* @see setWalkRate
*/
float getWalkRate() const { return _walkRate; }
void setLooking(bool lookingMode) { _lookingMode = lookingMode; }
/**
* Makes the actor move forward, the length of the movement based
* on the walk rate.
* If it is following boxes it will not go into not walkable areas.
*
* @see walkTo
*/
void walkForward();
void moveTo(const Math::Vector3d &pos);
/**
* Used to tell the actor if it is running or not.
*
* @param running The value: true if it is running.
*/
void setRunning(bool running) { _running = running; }
void setReflection(float angle) { _reflectionAngle = angle; }
/**
* Returns a vector representing the direction the actor
* is facing.
*/
Math::Vector3d getPuckVector() const;
void setPuckOrient(bool orient);
/**
* Makes the actor say the given line.
* It will show a subtitle and/or play the voice, depending
* on the speech mode set in the GrimEngine instance.
*
* @param msgId The id of the message to say.
* @param background ?? actual meaning unknown yet.
* @see isTalking
* @see shutUp
*/
void sayLine(const char *msgId, bool background, float x, float y);
// When we clean all text objects we don't want the actors to clean their
// objects again since they're already freed
void lineCleanup();
/**
* Makes the actor discard any subtitle and voice.
*
* @see sayLine
* @see isTalking
*/
void shutUp();
/**
* Returns true if the actor is saying something.
*
* @see sayLine
* @see shutUp
*/
bool isTalking();
void setRestChore(int choreNumber, Costume *cost);
int getRestChore() const;
void setWalkChore(int choreNumber, Costume *cost);
void setTurnChores(int left_chore, int right_chore, Costume *cost);
void setTalkChore(int index, int choreNumber, Costume *cost);
int getTalkChore(int index) const;
Costume *getTalkCostume(int index) const;
void setMumbleChore(int choreNumber, Costume *cost);
void stopAllChores(bool ignoreLoopingChores = false);
void setColormap(const char *map);
void pushCostume(const char *name);
void setCostume(const char *name);
void popCostume();
void clearCostumes();
Costume *getCurrentCostume() const;
void setLocalAlphaMode(unsigned int vertex, AlphaMode alphamode);
void setLocalAlpha(unsigned int vertex, float alpha);
bool hasLocalAlpha() const;
float getLocalAlpha(unsigned int vertex) const;
Costume *findCostume(const Common::String &name);
int getCostumeStackDepth() const {
return _costumeStack.size();
}
const Common::List<Costume *> &getCostumes() const { return _costumeStack; }
void setActiveShadow(int shadowId);
void setShadowPoint(const Math::Vector3d &pos);
void setShadowColor(const Color &color);
void setShadowPlane(const char *name);
void addShadowPlane(const char *name);
void clearShadowPlanes();
void clearShadowPlane(int i);
void setShadowValid(int);
void setActivateShadow(int, bool);
void setFollowBoxes(bool follow);
bool hasFollowBoxes() const { return _followBoxes; }
bool hasFollowedBoxes() const { return _hasFollowedBoxes; }
void update(uint frameTime);
/**
* Check if the actor is still talking. If it is returns true, otherwise false.
*/
bool updateTalk(uint frameTime);
void draw();
bool isLookAtVectorZero() {
return _lookAtVector.isZero();
}
void setLookAtVectorZero() {
_lookAtVector.set(0.f, 0.f, 0.f);
_lookAtActor = 0;
}
void setLookAtVector(const Math::Vector3d &vector) {
_lookAtVector = vector;
_lookAtActor = 0;
}
Math::Vector3d getLookAtVector() {
return _lookAtVector;
}
void setLookAtActor(Actor *other) { _lookAtActor = other->getId(); }
void setLookAtRate(float rate);
float getLookAtRate() const;
void setHead(int joint1, int joint2, int joint3, float maxRoll, float maxPitch, float maxYaw);
void setHead(const char *joint, const Math::Vector3d &offset);
void setHeadLimits(float yawRange, float maxPitch, float minPitch);
void setCollisionMode(CollisionMode mode);
void setCollisionScale(float scale);
bool handleCollisionWith(Actor *actor, CollisionMode mode, Math::Vector3d *vec) const;
static void saveStaticState(SaveGame *state);
static void restoreStaticState(SaveGame *state);
bool isAttached() const { return _attachedActor != 0; }
Math::Vector3d getWorldPos() const;
void attachToActor(Actor *other, const char *joint);
void detach();
Math::Quaternion getRotationQuat() const;
const Math::Matrix4 getFinalMatrix() const;
Math::Vector3d getHeadPos() const;
void setInOverworld(bool inOverworld) { _inOverworld = inOverworld; }
bool isInOverworld() const { return _inOverworld; }
float getGlobalAlpha() const { return _globalAlpha; }
AlphaMode getAlphaMode() const { return _alphaMode; }
float getEffectiveAlpha() const { return _alphaMode != AlphaOff ? _globalAlpha : 1.f; }
void setGlobalAlpha(float alpha, const Common::String &mesh);
void setAlphaMode(AlphaMode mode, const Common::String &mesh);
int getSortOrder() const;
void setSortOrder(const int order);
int getEffectiveSortOrder() const;
void activateShadow(bool active, const char *shadowName);
void activateShadow(bool active, SetShadow *shadow);
void drawToCleanBuffer();
bool isTalkingForeground() const;
LightMode getLightMode() const { return _lightMode; }
void setLightMode(LightMode lightMode) { _lightMode = lightMode; }
ObjectPtr<Material> loadMaterial(const Common::String &name, bool clamp);
ObjectPtr<Material> findMaterial(const Common::String &name);
void getBBoxInfo(Math::Vector3d &bboxPos, Math::Vector3d &bboxSize) const;
private:
void costumeMarkerCallback(int marker);
void collisionHandlerCallback(Actor *other) const;
void updateWalk();
void addShadowPlane(const char *n, Set *scene, int shadowId);
bool shouldDrawShadow(int shadowId);
void stopTalking();
bool stopMumbleChore();
void drawCostume(Costume *costume);
/**
* Given a start point and a destination this function returns a position
* that doesn't collide with any actor.
*/
Math::Vector3d handleCollisionTo(const Math::Vector3d &from, const Math::Vector3d &pos) const;
/**
* Check if the line from pos to dest collides with this actor's bounding
* box, and if yes return a point that, together with pos, defines a line
* tangent with the bounding box.
*/
Math::Vector3d getTangentPos(const Math::Vector3d &pos, const Math::Vector3d &dest) const;
Math::Vector3d getSimplePuckVector() const;
void calculateOrientation(const Math::Vector3d &pos, Math::Angle *pitch, Math::Angle *yaw, Math::Angle *roll);
bool getSphereInfo(bool adjustZ, float &size, Math::Vector3d &pos) const;
EMIModel *findModelWithMesh(const Common::String &mesh);
Common::String _name;
Common::String _setName; // The actual current set
Color _talkColor;
Math::Vector3d _pos;
Math::Angle _pitch, _yaw, _roll;
float _walkRate, _turnRate;
bool _followBoxes; // Constrain to walkboxes
bool _hasFollowedBoxes;
float _reflectionAngle; // Maximum angle to turn by at walls
bool _visible;
float _scale;
float _timeScale;
bool _lookingMode;
Common::String _talkSoundName;
bool _talking;
bool _backgroundTalk;
ObjectPtr<LipSync> _lipSync;
Common::List<Costume *> _costumeStack;
// Variables for gradual turning
bool _turning;
bool _singleTurning;
// NOTE: The movement direction is separate from the direction
// the actor's model is facing. The model's direction is gradually
// updated to match the movement direction. This produces a smooth
// turning animation while still allowing the actor to move in a
// new direction immediately after reflecting off a wall.
Math::Angle _moveYaw;
Math::Angle _movePitch;
Math::Angle _moveRoll;
// This is used to increase momentarily the turn rate when needed
float _turnRateMultiplier;
// Variables for walking to a point
bool _walking;
Math::Vector3d _destPos;
//chores
class ActionChore {
public:
ActionChore();
ActionChore(Costume *cost, int chore);
void play(bool fade = false, unsigned int time = fadeTime);
void playLooping(bool fade = false, unsigned int time = fadeTime);
void stop(bool fade = false, unsigned int time = fadeTime);
void setLastFrame();
inline bool isValid() const { return _chore > -1 && _costume != nullptr; }
bool isPlaying() const;
inline bool equals(const Costume *cost, int chore) const {
return (_costume == cost && _chore == chore);
}
void saveState(SaveGame *state) const;
void restoreState(SaveGame *state, Actor *actor);
Costume *_costume;
int _chore;
static const unsigned int fadeTime;
static const unsigned int talkFadeTime;
};
ActionChore _restChore;
ActionChore _walkChore;
bool _walkedLast, _walkedCur;
bool _running;
ActionChore _leftTurnChore, _rightTurnChore;
int _lastTurnDir, _currTurnDir;
ActionChore _talkChore[10];
int _talkAnim;
ActionChore _mumbleChore;
Shadow *_shadowArray;
int _activeShadowSlot;
static ObjectPtr<Font> _sayLineFont;
int _sayLineText;
bool _mustPlaceText;
ActionChore *getTurnChore(int dir) {
return (dir > 0 ? &_leftTurnChore : &_rightTurnChore);
}
void freeCostume(Costume *costume);
void freeCostumeChore(const Costume *toFree, ActionChore *chore);
// lookAt
Math::Vector3d _lookAtVector;
// struct used for path finding
struct PathNode {
Sector *sect;
PathNode *parent;
Math::Vector3d pos;
float dist;
float cost;
};
Common::List<Math::Vector3d> _path;
CollisionMode _collisionMode;
float _collisionScale;
bool _puckOrient;
static bool _isTalkingBackground;
int _talkDelay;
int _attachedActor;
int _lookAtActor;
Common::String _attachedJoint;
AlphaMode _alphaMode;
float _globalAlpha;
bool _inOverworld;
int _sortOrder;
int _sectorSortOrder;
bool _useParentSortOrder;
bool _fakeUnbound;
bool _drawnToClean;
LightMode _lightMode;
Common::List<ObjectPtr<Material> > _materials;
// Highest vertex used in EMI
const static unsigned int MAX_LOCAL_ALPHA_VERTICES = 48;
Common::Array<float> _localAlpha;
Common::Array<int> _localAlphaMode;
};
} // end of namespace Grim
#endif

284
engines/grim/animation.cpp Normal file
View File

@@ -0,0 +1,284 @@
/* 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 "engines/grim/animation.h"
#include "engines/grim/resource.h"
#include "engines/grim/model.h"
#include "engines/grim/debug.h"
#include "engines/grim/savegame.h"
namespace Grim {
Animation::Animation(const Common::String &keyframe, AnimManager *manager, int pr1, int pr2) :
_manager(manager), _priority1(pr1), _priority2(pr2), _paused(true),
_active(false), _time(-1), _fade(1.f), _fadeMode(None) {
_keyframe = g_resourceloader->getKeyframe(keyframe);
}
Animation::~Animation() {
deactivate();
}
void Animation::activate() {
if (!_active) {
_active = true;
_manager->addAnimation(this, _priority1, _priority2);
}
}
void Animation::deactivate() {
if (_active) {
_active = false;
_manager->removeAnimation(this);
}
}
void Animation::play(RepeatMode repeatMode) {
_repeatMode = repeatMode;
if (_repeatMode != Looping)
_time = -1;
_paused = false;
// Reset the fading, so that a fading out a chore and playing another one with an animation in common
// results in the animation being actually played. (You can check that with Olivia by the car in set me,
// when jumping with j+ts; me.olivia_search_idles() in me.lua)
if (_fadeMode == FadeOut)
_fadeMode = None;
activate();
}
void Animation::fade(FadeMode fadeMode, int fadeLength) {
if (!_active) {
if (fadeMode == FadeIn) {
_repeatMode = PauseAtEnd;
_time = -1;
_fade = 0.f;
_paused = false;
}
}
_fadeMode = fadeMode;
_fadeLength = fadeLength;
}
void Animation::pause(bool p) {
_paused = p;
}
void Animation::stop() {
_fadeMode = None;
_time = -1;
_fade = 1.f;
_paused = false;
deactivate();
}
bool Animation::getIsActive() const {
return _active;
}
Animation::FadeMode Animation::getFadeMode() const {
return _fadeMode;
}
int Animation::update(uint time) {
// For first time through newTime will be 0
int newTime = 0;
if (_time >= 0 && !_paused)
newTime = _time + time;
int marker = 0;
if (!_paused) {
marker = _keyframe->getMarker(_time / 1000.f, newTime / 1000.f);
_time = newTime;
}
int animLength = (int)(_keyframe->getLength() * 1000);
if (_fadeMode != None) {
if (_fadeMode == FadeIn) {
_fade += (float)time / (float)_fadeLength;
if (_fade >= 1.f) {
_fade = 1.f;
_fadeMode = None;
}
} else {
_fade -= (float)time / (float)_fadeLength;
if (_fade <= 0.f) {
_fade = 0.f;
// Don't reset the _fadeMode here. This way if fadeOut() was called
// on a looping chore its keyframe animations will remain faded out
// when it calls play() again.
deactivate();
return 0;
}
}
} else {
_fade = 1.f;
}
if (_time > animLength) { // What to do at end?
switch (_repeatMode) {
case Once:
if (_fadeMode == None)
deactivate();
else
_time = animLength;
break;
case Looping:
_time = -1;
break;
case PauseAtEnd:
_time = animLength;
_paused = true;
break;
case FadeAtEnd:
if (_fadeMode != FadeOut) {
_fadeMode = FadeOut;
_fadeLength = 250;
}
_time = animLength;
break;
default:
Debug::warning(Debug::Keyframes, "Unknown repeat mode %d for keyframe %s", _repeatMode, _keyframe->getFilename().c_str());
}
}
return marker;
}
void Animation::saveState(SaveGame *state) const {
state->writeBool(_active);
state->writeLESint32((int)_repeatMode);
state->writeLESint32(_time);
state->writeLESint32((int)_fadeMode);
state->writeFloat(_fade);
state->writeLESint32(_fadeLength);
state->writeBool(_paused);
}
void Animation::restoreState(SaveGame *state) {
bool active = state->readBool();
_repeatMode = (RepeatMode)state->readLESint32();
_time = state->readLESint32();
_fadeMode = (FadeMode)state->readLESint32();
_fade = state->readFloat();
_fadeLength = state->readLESint32();
_paused = state->readBool();
if (active)
activate();
}
/**
* @class AnimManager
*/
AnimManager::AnimManager() {
}
AnimManager::~AnimManager() {
for (const AnimationEntry &entry : _activeAnims) {
Animation *anim = entry._anim;
// Don't call deactivate() here so we don't mess with the list we're using.
anim->_manager = nullptr;
anim->_active = false;
}
}
void AnimManager::addAnimation(Animation *anim, int priority1, int priority2) {
// Keep the list of animations sorted by priorities in descending order. Because
// the animations have two different priorities, we add the animation to the list
// with both priorities.
Common::List<AnimationEntry>::iterator i;
AnimationEntry entry;
entry._anim = anim;
entry._priority = priority1;
entry._tagged = false;
for (i = _activeAnims.begin(); i != _activeAnims.end(); ++i) {
if (i->_priority < entry._priority) {
_activeAnims.insert(i, entry);
break;
}
}
if (i == _activeAnims.end())
_activeAnims.push_back(entry);
entry._priority = priority2;
entry._tagged = true;
for (i = _activeAnims.begin(); i != _activeAnims.end(); ++i) {
if (i->_priority < entry._priority) {
_activeAnims.insert(i, entry);
break;
}
}
if (i == _activeAnims.end())
_activeAnims.push_back(entry);
}
void AnimManager::removeAnimation(const Animation *anim) {
Common::List<AnimationEntry>::iterator i;
for (i = _activeAnims.begin(); i != _activeAnims.end(); ++i) {
if (i->_anim == anim) {
i = _activeAnims.erase(i);
--i;
}
}
}
void AnimManager::animate(ModelNode *hier, int numNodes) {
// Apply animation to each hierarchy node separately.
for (int i = 0; i < numNodes; i++) {
float remainingWeight = 1.0f;
int currPriority = -1;
float layerWeight = 0.0f;
// The animations are layered so that animations with a higher priority
// are played regardless of the blend weights of lower priority animations.
// The highest priority layer gets as much weight as it wants, while the
// next layer gets the remaining amount and so on.
for (Common::List<AnimationEntry>::iterator j = _activeAnims.begin(); j != _activeAnims.end(); ++j) {
if (currPriority != j->_priority) {
remainingWeight *= 1.0f - layerWeight;
layerWeight = 0.0f;
for (Common::List<AnimationEntry>::iterator k = j; k != _activeAnims.end(); ++k) {
if (j->_priority != k->_priority)
break;
float time = k->_anim->_time / 1000.0f;
if (k->_anim->_keyframe->isNodeAnimated(hier, i, time, k->_tagged))
layerWeight += k->_anim->_fade;
}
currPriority = j->_priority;
if (remainingWeight <= 0.0f)
break;
}
float time = j->_anim->_time / 1000.0f;
float weight = j->_anim->_fade;
if (layerWeight > 1.0f)
weight /= layerWeight;
weight *= remainingWeight;
j->_anim->_keyframe->animate(hier, i, time, weight, j->_tagged);
}
}
}
}

99
engines/grim/animation.h Normal file
View File

@@ -0,0 +1,99 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef GRIM_ANIMATION_H
#define GRIM_ANIMATION_H
#include "engines/grim/keyframe.h"
namespace Grim {
class SaveGame;
class AnimManager;
class Animation {
public:
enum RepeatMode {
Once = 0,
Looping = 1,
PauseAtEnd = 2,
FadeAtEnd = 3
};
enum FadeMode {
None = 0,
FadeIn = 1,
FadeOut = 2
};
Animation(const Common::String &keyframe, AnimManager *manager, int pr1, int pr2);
~Animation();
void activate();
void deactivate();
void play(RepeatMode repeatMode);
void fade(FadeMode fadeMode, int fadeLength);
void pause(bool pause);
void stop();
bool getIsActive() const;
FadeMode getFadeMode() const;
int update(uint time);
void saveState(SaveGame *state) const;
void restoreState(SaveGame *state);
private:
AnimManager *_manager;
ObjectPtr<KeyframeAnim> _keyframe;
int _priority1;
int _priority2;
bool _paused;
bool _active;
int _time;
float _fade;
RepeatMode _repeatMode;
FadeMode _fadeMode;
int _fadeLength;
friend class AnimManager;
};
class AnimManager {
public:
AnimManager();
~AnimManager();
void addAnimation(Animation *anim, int pr1, int pr2);
void removeAnimation(const Animation *anim);
void animate(ModelNode *hier, int numNodes);
private:
struct AnimationEntry {
Animation *_anim;
int _priority;
bool _tagged;
};
Common::List<AnimationEntry> _activeAnims;
};
}
#endif

537
engines/grim/bitmap.cpp Normal file
View File

@@ -0,0 +1,537 @@
/* 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/endian.h"
#include "image/tga.h"
#include "engines/grim/savegame.h"
#include "engines/grim/debug.h"
#include "engines/grim/bitmap.h"
#include "engines/grim/resource.h"
#include "engines/grim/gfx_base.h"
namespace Grim {
static bool decompress_codec3(const char *compressed, char *result, int maxBytes);
Common::HashMap<Common::String, BitmapData *> *BitmapData::_bitmaps = nullptr;
BitmapData *BitmapData::getBitmapData(const Common::String &fname) {
Common::String str(fname);
if (_bitmaps && _bitmaps->contains(str)) {
BitmapData *b = (*_bitmaps)[str];
++b->_refCount;
return b;
}
BitmapData *b = new BitmapData(fname);
if (!_bitmaps) {
_bitmaps = new Common::HashMap<Common::String, BitmapData *>();
}
(*_bitmaps)[str] = b;
return b;
}
BitmapData::BitmapData(const Common::String &fname) {
_fname = fname;
_refCount = 1;
_data = nullptr;
_loaded = false;
_keepData = true;
// Initialize members to avoid warnings:
_numImages = 0;
_width = 0;
_height = 0;
_x = 0;
_y = 0;
_format = 0;
_numTex = 0;
_bpp = 0;
_texIds = nullptr;
_hasTransparency = 0;
_texc = nullptr;
_verts = nullptr;
_layers = nullptr;
_numCoords = 0;
_numVerts = 0;
_numLayers = 0;
_userData = nullptr;
}
void BitmapData::load() {
if (_loaded) {
return;
}
Common::SeekableReadStream *data = g_resourceloader->openNewStreamFile(_fname.c_str());
if (!data)
error("Couldn't open %s", _fname.c_str());
uint32 tag = data->readUint32BE();
switch(tag) {
case(MKTAG('B','M',' ',' ')): //Grim bitmap
loadGrimBm(data);
break;
case(MKTAG('T','I','L','0')): // MI4 bitmap
loadTile(data);
break;
default:
if (!loadTGA(data)) // Try to load as TGA.
Debug::error(Debug::Bitmaps, "Invalid magic loading bitmap");
break;
}
delete data;
_loaded = true;
}
bool BitmapData::loadGrimBm(Common::SeekableReadStream *data) {
uint32 tag2 = data->readUint32BE();
if (tag2 != (MKTAG('F','\0','\0','\0')))
return false;
int codec = data->readUint32LE();
data->readUint32LE(); //_paletteIncluded
_numImages = data->readUint32LE();
_x = data->readUint32LE();
_y = data->readUint32LE();
data->readUint32LE(); //_transparentColor
_format = data->readUint32LE();
_bpp = data->readUint32LE();
// uint32 redBits = data->readUint32LE();
// uint32 greenBits = data->readUint32LE();
// uint32 blueBits = data->readUint32LE();
// uint32 redShift = data->readUint32LE();
// uint32 greenShift = data->readUint32LE();
// uint32 blueShift = data->readUint32LE();
// Hardcode the format, since the values saved in the files are garbage for some, like "ha_0_elvos.zbm".
Graphics::PixelFormat pixelFormat(2, 5, 6, 5, 0, 11, 5, 0, 0);
data->seek(128, SEEK_SET);
_width = data->readUint32LE();
_height = data->readUint32LE();
_hasTransparency = false;
_data = new Graphics::Surface[_numImages];
data->seek(0x80, SEEK_SET);
for (int i = 0; i < _numImages; i++) {
data->seek(8, SEEK_CUR);
_data[i].create(_width, _height, pixelFormat);
if (codec == 0) {
uint32 dsize = _bpp / 8 * _width * _height;
data->read(_data[i].getPixels(), dsize);
} else if (codec == 3) {
int compressed_len = data->readUint32LE();
char *compressed = new char[compressed_len];
data->read(compressed, compressed_len);
bool success = decompress_codec3(compressed, (char *)_data[i].getPixels(), _bpp / 8 * _width * _height);
delete[] compressed;
if (!success)
warning(".. when loading image %s.", _fname.c_str());
} else
Debug::error(Debug::Bitmaps, "Unknown image codec in BitmapData ctor!");
#ifdef SCUMM_BIG_ENDIAN
uint16 *d = (uint16 *)_data[i].getPixels();
for (int j = 0; j < _width * _height; ++j) {
d[j] = SWAP_BYTES_16(d[j]);
}
#endif
}
// Initially, no GPU-side textures created. the createBitmap
// function will allocate some if necessary (and successful)
_numTex = 0;
_texIds = nullptr;
g_driver->createBitmap(this);
return true;
}
BitmapData::BitmapData(const Graphics::Surface &buf, int w, int h, const char *fname) : _fname(fname) {
_refCount = 1;
Debug::debug(Debug::Bitmaps, "New bitmap loaded: %s\n", fname);
_numImages = 1;
_x = 0;
_y = 0;
_width = w;
_height = h;
_format = 1;
_numTex = 0;
_texIds = nullptr;
_bpp = buf.format.bytesPerPixel * 8;
_hasTransparency = false;
_data = new Graphics::Surface[_numImages];
_data[0].copyFrom(buf);
_loaded = true;
_keepData = true;
_userData = nullptr;
_texc = nullptr;
_verts = nullptr;
_layers = nullptr;
_numCoords = 0;
_numVerts = 0;
_numLayers = 0;
g_driver->createBitmap(this);
}
BitmapData::BitmapData() :
_numImages(0), _width(0), _height(0), _x(0), _y(0), _format(0), _numTex(0),
_bpp(0), _texIds(nullptr), _hasTransparency(false), _data(nullptr),
_refCount(1), _loaded(false), _keepData(false), _texc(nullptr), _verts(nullptr),
_layers(nullptr), _numCoords(0), _numVerts(0), _numLayers(0), _userData(nullptr) {
}
BitmapData::~BitmapData() {
_keepData = false;
if (_loaded) {
g_driver->destroyBitmap(this);
}
freeData();
if (_bitmaps) {
if (_bitmaps->contains(_fname)) {
_bitmaps->erase(_fname);
}
if (_bitmaps->empty()) {
delete _bitmaps;
_bitmaps = nullptr;
}
}
delete[] _texc;
delete[] _layers;
delete[] _verts;
}
void BitmapData::freeData() {
if (!_keepData && _data) {
for (int i = 0; i < _numImages; ++i) {
_data[i].free();
}
delete[] _data;
_data = nullptr;
}
}
bool BitmapData::loadTGA(Common::SeekableReadStream *data) {
Image::TGADecoder dec;
bool success = dec.loadStream(*data);
if (!success)
return false;
const Graphics::Surface *surf = dec.getSurface();
_width = surf->w;
_height = surf->h;
_format = 1;
_x = _y = 0;
_bpp = surf->format.bytesPerPixel * 8;
_numImages = 1;
_data = new Graphics::Surface[1];
_data[0].copyFrom(*surf);
g_driver->createBitmap(this);
freeData();
return true;
}
bool BitmapData::loadTile(Common::SeekableReadStream *o) {
#ifdef ENABLE_MONKEY4
_x = 0;
_y = 0;
_format = 1;
o->seek(0, SEEK_SET);
/*uint32 id = */o->readUint32LE();
// Should check that we actually HAVE a TIL
uint32 bmoffset = o->readUint32LE();
_numCoords = o->readUint32LE();
_numLayers = o->readUint32LE();
_numVerts = o->readUint32LE();
// skip some 0
o->seek(16, SEEK_CUR);
_texc = new float[_numCoords * 4];
for (uint32 i = 0; i < _numCoords * 4; ++i) {
_texc[i] = o->readFloatLE();
}
_layers = new Layer[_numLayers];
for (uint32 i = 0; i < _numLayers; ++i) {
_layers[i]._offset = o->readUint32LE();
_layers[i]._numImages = o->readUint32LE();
}
_verts = new Vert[_numVerts];
for (uint32 i = 0; i < _numVerts; ++i) {
_verts[i]._texid = o->readUint32LE();
_verts[i]._pos = o->readUint32LE();
_verts[i]._verts = o->readUint32LE();
}
o->seek(16, SEEK_CUR);
_numImages = o->readUint32LE();
o->seek(16, SEEK_CUR);
_bpp = o->readUint32LE();
o->seek(bmoffset + 128);
_width = o->readUint32LE();
_height = o->readUint32LE();
o->seek(-8, SEEK_CUR);
_data = new Graphics::Surface[_numImages];
Graphics::PixelFormat format_16bpp = Graphics::PixelFormat(2, 5, 5, 5, 1, 0, 5, 10, 15);
Graphics::PixelFormat format_32bpp = Graphics::PixelFormat::createFormatRGBA32();
int width = 256;
int height = 256;
for (int i = 0; i < _numImages; ++i) {
_data[i].create(width, height, (_bpp == 16) ? format_16bpp : format_32bpp);
uint8 *d = (uint8 *)_data[i].getPixels();
o->seek(8, SEEK_CUR);
switch (_bpp) {
case 16:
#ifndef SCUMM_LITTLE_ENDIAN
for (int y = 0; y < _height; ++y) {
uint16 *d16 = (uint16 *)d;
for (int x = 0; x < _width; ++x)
d16[x] = o->readUint16LE();
d += _data[i].pitch;
}
break;
#endif
case 32:
for (int y = 0; y < _height; ++y) {
o->read(d, _width * (_bpp / 8));
d += _data[i].pitch;
}
break;
}
}
_width = width;
_height = height;
g_driver->createBitmap(this);
#endif // ENABLE_MONKEY4
return true;
}
const Graphics::Surface &BitmapData::getImageData(int num) const {
assert(num >= 0);
assert(num < _numImages);
return _data[num];
}
// Bitmap
Bitmap::Bitmap(const Common::String &fname) {
_data = BitmapData::getBitmapData(fname);
_currImage = 1;
}
Bitmap::Bitmap(const Graphics::Surface &buf, int w, int h, const char *fname) {
_data = new BitmapData(buf, w, h, fname);
_currImage = 1;
}
Bitmap::Bitmap() {
_data = new BitmapData();
_currImage = 0;
}
Bitmap *Bitmap::create(const Common::String &filename) {
if (!SearchMan.hasFile(Common::Path(filename))) {
warning("Could not find bitmap %s", filename.c_str());
return nullptr;
}
Bitmap *b = new Bitmap(filename);
return b;
}
void Bitmap::saveState(SaveGame *state) const {
state->writeString(getFilename());
state->writeLESint32(getActiveImage());
}
void Bitmap::restoreState(SaveGame *state) {
freeData();
Common::String fname = state->readString();
_data = BitmapData::getBitmapData(fname);
_currImage = state->readLESint32();
}
void Bitmap::draw() {
_data->load();
if (_currImage == 0)
return;
g_driver->drawBitmap(this, _data->_x, _data->_y);
}
void Bitmap::draw(int x, int y) {
_data->load();
if (_currImage == 0)
return;
g_driver->drawBitmap(this, x, y, _data->_numLayers - 1);
}
void Bitmap::drawLayer(uint32 layer) {
_data->load();
if (_currImage == 0)
return;
g_driver->drawBitmap(this, _data->_x, _data->_y, layer);
}
void Bitmap::setActiveImage(int n) {
assert(n >= 0);
_data->load();
if ((n - 1) >= _data->_numImages) {
warning("Bitmap::setActiveImage: no anim image: %d. (%s)", n, _data->_fname.c_str());
} else {
_currImage = n;
}
}
int Bitmap::getNumImages() const {
_data->load();
return _data->_numImages;
}
int Bitmap::getNumLayers() const {
_data->load();
return _data->_numLayers;
}
void Bitmap::freeData() {
--_data->_refCount;
if (_data->_refCount < 1) {
delete _data;
_data = nullptr;
}
}
Bitmap::~Bitmap() {
freeData();
}
const Graphics::PixelFormat &Bitmap::getPixelFormat(int num) const {
return getData(num).format;
}
void BitmapData::convertToColorFormat(int num, const Graphics::PixelFormat &format) {
if (_data[num].format == format) {
return;
}
_data[num].convertToInPlace(format);
}
void BitmapData::convertToColorFormat(const Graphics::PixelFormat &format) {
for (int i = 0; i < _numImages; ++i) {
convertToColorFormat(i, format);
}
}
#define GET_BIT do { bit = bitstr_value & 1; \
bitstr_len--; \
bitstr_value >>= 1; \
if (bitstr_len == 0) { \
bitstr_value = READ_LE_UINT16(compressed); \
bitstr_len = 16; \
compressed += 2; \
} \
} while (0)
static bool decompress_codec3(const char *compressed, char *result, int maxBytes) {
int bitstr_value = READ_LE_UINT16(compressed);
int bitstr_len = 16;
compressed += 2;
bool bit;
int byteIndex = 0;
for (;;) {
GET_BIT;
if (bit == 1) {
if (byteIndex >= maxBytes) {
warning("Buffer overflow when decoding image: decompress_codec3 walked past the input buffer!");
return false;
} else {
*result++ = *compressed++;
}
++byteIndex;
} else {
GET_BIT;
int copy_len, copy_offset;
if (bit == 0) {
GET_BIT;
copy_len = 2 * bit;
GET_BIT;
copy_len += bit + 3;
copy_offset = *(const uint8 *)(compressed++) - 0x100;
} else {
copy_offset = (*(const uint8 *)(compressed) | (*(const uint8 *)(compressed + 1) & 0xf0) << 4) - 0x1000;
copy_len = (*(const uint8 *)(compressed + 1) & 0xf) + 3;
compressed += 2;
if (copy_len == 3) {
copy_len = *(const uint8 *)(compressed++) + 1;
if (copy_len == 1)
return true;
}
}
while (copy_len > 0) {
if (byteIndex >= maxBytes) {
warning("Buffer overflow when decoding image: decompress_codec3 walked past the input buffer!");
return false;
} else {
assert(byteIndex + copy_offset >= 0);
assert(byteIndex + copy_offset < maxBytes);
*result = result[copy_offset];
result++;
}
++byteIndex;
copy_len--;
}
}
}
return true;
}
} // end of namespace Grim

189
engines/grim/bitmap.h Normal file
View File

@@ -0,0 +1,189 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef GRIM_BITMAP_H
#define GRIM_BITMAP_H
#include "graphics/pixelformat.h"
#include "common/endian.h"
#include "common/hashmap.h"
#include "common/hash-str.h"
#include "engines/grim/pool.h"
namespace Graphics {
struct Surface;
}
namespace Common {
class SeekableReadStream;
}
namespace Grim {
/**
* This BitmapData class keeps the actual bitmap data and can be shared
* between Bitmap instances, by using getBitmapData.
* Bitmap still keeps the data that can change between the instances
* i.e. _x, _y and _currImage.
* They are automatically deleted if they are not used by any bitmap anymore.
*/
class BitmapData {
public:
BitmapData(const Common::String &fname);
BitmapData(const Graphics::Surface &buf, int w, int h, const char *fname);
BitmapData();
~BitmapData();
void freeData();
void load();
/**
* Loads an EMI TILE-bitmap.
*
* @param data the data for the TILE.
* @param len the length of the data.
*/
bool loadTile(Common::SeekableReadStream *data);
bool loadGrimBm(Common::SeekableReadStream *data);
bool loadTGA(Common::SeekableReadStream *data);
static BitmapData *getBitmapData(const Common::String &fname);
static Common::HashMap<Common::String, BitmapData *> *_bitmaps;
const Graphics::Surface &getImageData(int num) const;
/**
* Convert a bitmap to another color-format.
*
* @param format the format to convert to.
*/
void convertToColorFormat(const Graphics::PixelFormat &format);
/**
* Convert a bitmap to another color-format.
*
* @param format the format to convert to.
*/
void convertToColorFormat(int num, const Graphics::PixelFormat &format);
Common::String _fname;
int _numImages;
int _width, _height, _x, _y;
int _format;
int _numTex;
int _bpp;
void *_texIds;
bool _hasTransparency;
bool _loaded;
bool _keepData;
int _refCount;
float *_texc;
struct Vert {
uint32 _texid;
uint32 _pos;
uint32 _verts;
};
struct Layer {
uint32 _offset;
uint32 _numImages;
};
Vert *_verts;
Layer *_layers;
uint32 _numCoords;
uint32 _numVerts;
uint32 _numLayers;
//private:
Graphics::Surface *_data;
void *_userData;
};
class Bitmap : public PoolObject<Bitmap> {
public:
/**
* Construct a bitmap from the given data.
*
* @oaram filename the filename of the bitmap
* @param data the actual data to construct from
* @param len the length of the data
*/
Bitmap(const Common::String &filename);
Bitmap(const Graphics::Surface &buf, int width, int height, const char *filename);
Bitmap();
static int32 getStaticTag() { return MKTAG('V', 'B', 'U', 'F'); }
static Bitmap *create(const Common::String &filename);
const Common::String &getFilename() const { return _data->_fname; }
void draw();
void draw(int x, int y);
void drawLayer(uint32 layer);
/**
* Set which image in an animated bitmap to use
*
* @param n the image to be selected
*/
void setActiveImage(int n);
int getNumImages() const;
int getNumLayers() const;
int getActiveImage() const { return _currImage; }
bool getHasTransparency() const { return _data->_hasTransparency; }
int getFormat() const { return _data->_format; }
int getWidth() const { return _data->_width; }
int getHeight() const { return _data->_height; }
const Graphics::Surface &getData(int num) const { return _data->getImageData(num); }
const Graphics::Surface &getData() const { return getData(_currImage); }
BitmapData *getBitmapData() const { return _data; }
void *getTexIds() const { return _data->_texIds; }
int getNumTex() const { return _data->_numTex; }
const Graphics::PixelFormat &getPixelFormat(int num) const;
void saveState(SaveGame *state) const;
void restoreState(SaveGame *state);
virtual ~Bitmap();
//private:
void freeData();
BitmapData *_data;
/**
* Specifies a one-based index to the current image in BitmapData.
* _currImage==0 means a null image is chosen.
*/
int _currImage;
};
} // end of namespace Grim
#endif

66
engines/grim/color.cpp Normal file
View File

@@ -0,0 +1,66 @@
/* 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 "engines/grim/color.h"
namespace Grim {
Color::Color() {
_vals[0] = _vals[1] = _vals[2] = 0;
}
Color::Color(byte r, byte g, byte b) {
_vals[0] = r;
_vals[1] = g;
_vals[2] = b;
}
Color::Color(const Color &c) {
_vals[0] = c._vals[0];
_vals[1] = c._vals[1];
_vals[2] = c._vals[2];
}
Color::Color(uint32 c) {
_vals[0] = (c >> 16) & 0xFF;
_vals[1] = (c >> 8) & 0xFF;
_vals[2] = c & 0xFF;
}
uint32 Color::toEncodedValue() {
return (_vals[0] << 16) | (_vals[1] << 8) | _vals[2];
}
Color &Color::operator =(const Color &c) {
_vals[0] = c._vals[0];
_vals[1] = c._vals[1];
_vals[2] = c._vals[2];
return *this;
}
Color &Color::operator =(const Color *c) {
_vals[0] = c->_vals[0];
_vals[1] = c->_vals[1];
_vals[2] = c->_vals[2];
return *this;
}
} // end of namespace Grim

54
engines/grim/color.h Normal file
View File

@@ -0,0 +1,54 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef GRIM_COLOR_H
#define GRIM_COLOR_H
#include "common/endian.h"
namespace Grim {
class Color {
public:
byte _vals[3];
Color();
Color(byte r, byte g, byte b);
Color(const Color &c);
Color(uint32 c);
byte &getRed() { return _vals[0]; }
byte getRed() const { return _vals[0]; }
byte &getGreen() { return _vals[1]; }
byte getGreen() const { return _vals[1]; }
byte &getBlue() { return _vals[2]; }
byte getBlue() const { return _vals[2]; }
uint32 toEncodedValue();
Color &operator =(const Color &c);
Color &operator =(const Color *c);
};
} // end of namespace Grim
#endif

55
engines/grim/colormap.cpp Normal file
View File

@@ -0,0 +1,55 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "common/stream.h"
#include "engines/grim/colormap.h"
#include "engines/grim/resource.h"
namespace Grim {
// Load a colormap from the given data.
CMap::CMap(const Common::String &fileName, Common::SeekableReadStream *data) :
Object(), _fname(fileName) {
uint32 tag = data->readUint32BE();
if (tag != MKTAG('C','M','P',' '))
error("Invalid magic loading colormap");
data->seek(64, SEEK_SET);
data->read(_colors, sizeof(_colors));
}
CMap::~CMap() {
if (g_resourceloader)
g_resourceloader->uncacheColormap(this);
}
bool CMap::operator==(const CMap &c) const {
if (_fname != c._fname) {
return false;
}
return true;
}
} // end of namespace Grim

51
engines/grim/colormap.h Normal file
View File

@@ -0,0 +1,51 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef GRIM_COLORMAP_H
#define GRIM_COLORMAP_H
#include "common/str.h"
#include "engines/grim/object.h"
namespace Common {
class SeekableReadStream;
}
namespace Grim {
class CMap : public Object {
public:
// Load a colormap from the given data.
CMap(const Common::String &fileName, Common::SeekableReadStream *data);
~CMap();
const Common::String &getFilename() const { return _fname; }
// The color data, in RGB format
char _colors[256 * 3];
Common::String _fname;
bool operator==(const CMap &c) const;
};
} // end of namespace Grim
#endif

View File

@@ -0,0 +1,4 @@
# This file is included from the main "configure" script
# add_engine [name] [desc] [build-by-default] [subengines] [base games] [deps] [components]
add_engine grim "Grim" yes "monkey4" "Grim Fandango" "16bit 3d highres" "theoradec tinygl mpeg2"
add_engine monkey4 "Escape from Monkey Island" no "" "" "bink"

574
engines/grim/costume.cpp Normal file
View File

@@ -0,0 +1,574 @@
/* 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/endian.h"
#include "engines/grim/debug.h"
#include "engines/grim/colormap.h"
#include "engines/grim/costume.h"
#include "engines/grim/textsplit.h"
#include "engines/grim/resource.h"
#include "engines/grim/model.h"
#include "engines/grim/savegame.h"
#include "engines/grim/emi/modelemi.h"
#include "engines/grim/costume/chore.h"
#include "engines/grim/costume/head.h"
#include "engines/grim/emi/costume/emianim_component.h"
#include "engines/grim/emi/costume/emimesh_component.h"
#include "engines/grim/emi/costume/emiskel_component.h"
#include "engines/grim/costume/main_model_component.h"
#include "engines/grim/costume/colormap_component.h"
#include "engines/grim/costume/keyframe_component.h"
#include "engines/grim/costume/mesh_component.h"
#include "engines/grim/costume/lua_var_component.h"
#include "engines/grim/costume/sound_component.h"
#include "engines/grim/costume/bitmap_component.h"
#include "engines/grim/costume/material_component.h"
#include "engines/grim/costume/sprite_component.h"
#include "engines/grim/costume/anim_component.h"
namespace Grim {
// A costume in the Residual/GrimE engine consists of a set of
// components, and a set of chores. Each component represents an
// on-screen object, or a keyframe animation, or a sound effect; each
// chore gives a set of instructions for how to move and/or activate
// or deactivate each component at certain times.
//
// Each actor contains a stack of costumes, on which a new costume can
// be pushed or from which an old costume can be popped at any time.
// For the most part, these costumes are independent. The exception
// is the main model component ('MMDL'), for which multiple costumes
// share the same base 3D object (if they refer to the same file).
//
// This is complicated by the fact that multiple keyframe animations
// can have an effect on the positions of the 3D objects. Each
// keyframe animation has certain nodes internally "tagged", and the
// keyframe components specify precedences for the tagged nodes and
// for the non-tagged nodes. If the highest precedence for a given
// node is given by multiple keyframe animations, their contributions
// are averaged.
//
// Each component can implement several virtual methods which are
// called by the costume:
//
// init() -- allows the component to initialize itself. This is
// separate from the constructor since there are cases where
// information from child components may be needed before
// the object can be fully constructed. This is particularly
// the case with colormaps, which are needed before even
// starting to load a 3D model.
// setKey(val) -- notifies the component of a change in the "state"
// given by a playing chore
// update() -- gives the component a chance to update its internal
// state once every frame
// draw() -- actually draws the component onto the screen
// reset() -- notifies the component that a chore controlling it
// has stopped
//
// For the 3D objects, a model's component first initializes internal
// state for the model's nodes in its update() method. Then the
// keyframes' update() methods work with this data to implement the
// precedence and add up all contributions for the highest precedence.
// Then the model's draw() method does the averaging and draws the
// polygons accordingly. (Actually, this is a lie -- the top-level
// 3D objects draw themselves and all their children. This makes it
// easier to move objects Manny is holding when his hands move, for
// example.)
//
// For bitmaps, the actual drawing is handled by the Set class. The
// bitmaps to be drawn are associated to the needed camera setups
// using NewObjectState; bitmaps marked OBJSTATE_UNDERLAY and
// OBJSTATE_STATE are drawn first, then the 3D objects, then bitmaps
// marked OBJSTATE_OVERLAY. So the BitmapComponent just needs to pass
// along setKey requests to the actual bitmap object.
Costume::Costume(const Common::String &fname, Actor *owner, Costume *prevCost) :
Object(), _head(nullptr), _chores(nullptr), _components(nullptr),
_numComponents(0), _numChores(0), _fname(fname), _owner(owner) {
_lookAtRate = 200;
_prevCostume = prevCost;
}
void Costume::load(Common::SeekableReadStream *data) {
TextSplitter ts(_fname, data);
ts.expectString("costume v0.1");
ts.expectString("section tags");
int numTags;
ts.scanString(" numtags %d", 1, &numTags);
tag32 *tags = new tag32[numTags];
for (int i = 0; i < numTags; i++) {
unsigned char t[4];
int which;
// Obtain a tag ID from the file
ts.scanString(" %d '%c%c%c%c'", 5, &which, &t[0], &t[1], &t[2], &t[3]);
// Force characters to upper case
for (int j = 0; j < 4; j++)
t[j] = toupper(t[j]);
memcpy(&tags[which], t, sizeof(tag32));
tags[which] = FROM_BE_32(tags[which]);
}
ts.expectString("section components");
ts.scanString(" numcomponents %d", 1, &_numComponents);
_components = new Component *[_numComponents]{};
for (int i = 0; i < _numComponents; i++) {
int id, tagID, hash, parentID, namePos;
const char *line = ts.getCurrentLine();
Component *prevComponent = nullptr;
if (sscanf(line, " %d %d %d %d %n", &id, &tagID, &hash, &parentID, &namePos) < 4)
error("Bad component specification line: `%s'", line);
ts.nextLine();
// A Parent ID of "-1" indicates that the component should
// use the properties of the previous costume as a base
if (parentID == -1) {
if (_prevCostume) {
// However, only the first item can actually share the
// node hierarchy with the previous costume, so flag
// that component so it knows what to do
if (i == 0)
parentID = -2;
prevComponent = _prevCostume->_components[0];
// Make sure that the component is valid
if (!prevComponent->isComponentType('M','M','D','L'))
prevComponent = nullptr;
} else if (id > 0) {
// Use the MainModelComponent of this costume as prevComponent,
// so that the component can use its colormap.
prevComponent = _components[0];
}
}
// Actually load the appropriate component
_components[id] = loadComponent(tags[tagID], parentID < 0 ? nullptr : _components[parentID], parentID, line + namePos, prevComponent);
_components[id]->setCostume(this);
}
delete[] tags;
for (int i = 0; i < _numComponents; i++) {
if (_components[i]) {
_components[i]->init();
}
}
ts.expectString("section chores");
ts.scanString(" numchores %d", 1, &_numChores);
_chores = new Chore *[_numChores];
for (int i = 0; i < _numChores; i++) {
int id, length, tracks;
char name[32];
ts.scanString(" %d %d %d %32s", 4, &id, &length, &tracks, name);
_chores[id] = new Chore(name, i, this, length, tracks);
Debug::debug(Debug::Chores, "Loaded chore: %s\n", name);
}
ts.expectString("section keys");
for (int i = 0; i < _numChores; i++) {
int which;
ts.scanString("chore %d", 1, &which);
_chores[which]->load(ts);
}
_head = new Head();
}
Costume::~Costume() {
stopChores();
for (int i = _numComponents - 1; i >= 0; i--) {
delete _components[i];
}
delete[] _components;
for (int i = 0; i < _numChores; ++i) {
delete _chores[i];
}
delete[] _chores;
delete _head;
}
Component *Costume::loadComponent (tag32 tag, Component *parent, int parentID, const char *name, Component *prevComponent) {
if (tag == MKTAG('M','M','D','L'))
return new MainModelComponent(parent, parentID, name, prevComponent, tag);
else if (tag == MKTAG('M','O','D','L'))
return new ModelComponent(parent, parentID, name, prevComponent, tag);
else if (tag == MKTAG('C','M','A','P'))
return new ColormapComponent(parent, parentID, name, tag);
else if (tag == MKTAG('K','E','Y','F'))
return new KeyframeComponent(parent, parentID, name, tag);
else if (tag == MKTAG('M','E','S','H'))
return new MeshComponent(parent, parentID, name, tag);
else if (tag == MKTAG('L','U','A','V'))
return new LuaVarComponent(parent, parentID, name, tag);
else if (tag == MKTAG('I','M','L','S'))
return new SoundComponent(parent, parentID, name, tag);
else if (tag == MKTAG('B','K','N','D'))
return new BitmapComponent(parent, parentID, name, tag);
else if (tag == MKTAG('M','A','T',' '))
return new MaterialComponent(parent, parentID, name, tag);
else if (tag == MKTAG('S','P','R','T'))
return new SpriteComponent(parent, parentID, name, tag);
else if (tag == MKTAG('A','N','I','M')) //Used in the demo
return new AnimComponent(parent, parentID, name, tag);
char t[4];
memcpy(t, &tag, sizeof(tag32));
warning("loadComponent: Unknown tag '%c%c%c%c', name '%s'", t[0], t[1], t[2], t[3], name);
return nullptr;
}
ModelComponent *Costume::getMainModelComponent() const {
for (int i = 0; i < _numComponents; i++) {
// Needs to handle Main Models (pigeons) and normal Models
// (when Manny climbs the rope)
if (_components[i] && _components[i]->isComponentType('M','M','D','L'))
return static_cast<ModelComponent *>(_components[i]);
}
return nullptr;
}
ModelNode *Costume::getModelNodes() {
ModelComponent *comp = getMainModelComponent();
if (comp) {
return comp->getHierarchy();
}
return nullptr;
}
Model *Costume::getModel() {
ModelComponent *comp = getMainModelComponent();
if (comp) {
return comp->getModel();
}
return nullptr;
}
void Costume::setChoreLastFrame(int num) {
if (num < 0 || num >= _numChores) {
Debug::warning(Debug::Chores, "Requested chore number %d is outside the range of chores (0-%d)", num, _numChores);
return;
}
_chores[num]->setLastFrame();
}
void Costume::setChoreLooping(int num, bool val) {
if (num < 0 || num >= _numChores) {
Debug::warning(Debug::Chores, "Requested chore number %d is outside the range of chores (0-%d)", num, _numChores);
return;
}
_chores[num]->setLooping(val);
}
void Costume::playChoreLooping(const char *name, uint msecs) {
for (int i = 0; i < _numChores; ++i) {
if (strcmp(_chores[i]->getName(), name) == 0) {
playChoreLooping(i, msecs);
return;
}
}
warning("Costume::playChoreLooping: Could not find chore: %s", name);
return;
}
void Costume::playChoreLooping(int num, uint msecs) {
if (num < 0 || num >= _numChores) {
Debug::warning(Debug::Chores, "Requested chore number %d is outside the range of chores (0-%d)", num, _numChores);
return;
}
_chores[num]->playLooping(msecs);
if (Common::find(_playingChores.begin(), _playingChores.end(), _chores[num]) == _playingChores.end())
_playingChores.push_back(_chores[num]);
}
Chore *Costume::getChore(const char *name) {
for (int i = 0; i < _numChores; ++i) {
if (strcmp(_chores[i]->getName(), name) == 0) {
return _chores[i];
}
}
return nullptr;
}
int Costume::getChoreId(const char *name) {
if (name == nullptr) {
return -1;
}
for (int i = 0; i < _numChores; ++i) {
if (strcmp(_chores[i]->getName(), name) == 0) {
return i;
}
}
return -1;
}
void Costume::playChore(const char *name, uint msecs) {
for (int i = 0; i < _numChores; ++i) {
if (strcmp(_chores[i]->getName(), name) == 0) {
playChore(i, msecs);
return;
}
}
warning("Costume::playChore: Could not find chore: %s", name);
return;
}
void Costume::playChore(int num, uint msecs) {
if (num < 0 || num >= _numChores) {
Debug::warning(Debug::Chores, "Requested chore number %d is outside the range of chores (0-%d)", num, _numChores);
return;
}
_chores[num]->play(msecs);
if (Common::find(_playingChores.begin(), _playingChores.end(), _chores[num]) == _playingChores.end())
_playingChores.push_back(_chores[num]);
}
void Costume::stopChore(int num, uint msecs) {
if (num < 0 || num >= _numChores) {
Debug::warning(Debug::Chores, "Requested chore number %d is outside the range of chores (0-%d)", num, _numChores);
return;
}
_chores[num]->stop(msecs);
}
void Costume::setColormap(const Common::String &map) {
// Sometimes setColormap is called on a null costume,
// see where raoul is gone in hh.set
if (!map.size())
return;
_cmap = g_resourceloader->getColormap(map);
for (int i = 0; i < _numComponents; i++)
if (_components[i])
_components[i]->setColormap(nullptr);
}
void Costume::stopChores(bool ignoreLoopingChores, int msecs) {
for (int i = 0; i < _numChores; i++) {
if (ignoreLoopingChores && _chores[i]->isLooping()) {
continue;
}
_chores[i]->stop(msecs);
}
}
void Costume::fadeChoreIn(int chore, uint msecs) {
if (chore < 0 || chore >= _numChores) {
Debug::warning(Debug::Chores, "Requested chore number %d is outside the range of chores (0-%d)", chore, _numChores);
return;
}
_chores[chore]->fadeIn(msecs);
if (Common::find(_playingChores.begin(), _playingChores.end(), _chores[chore]) == _playingChores.end())
_playingChores.push_back(_chores[chore]);
}
void Costume::fadeChoreOut(int chore, uint msecs) {
if (chore < 0 || chore >= _numChores) {
Debug::warning(Debug::Chores, "Requested chore number %d is outside the range of chores (0-%d)", chore, _numChores);
return;
}
_chores[chore]->fadeOut(msecs);
}
int Costume::isChoring(const char *name, bool excludeLooping) {
for (int i = 0; i < _numChores; i++) {
if (!strcmp(_chores[i]->getName(), name) && _chores[i]->isPlaying() && !(excludeLooping && _chores[i]->isLooping()))
return i;
}
return -1;
}
int Costume::isChoring(int num, bool excludeLooping) {
if (num < 0 || num >= _numChores) {
Debug::warning(Debug::Chores, "Requested chore number %d is outside the range of chores (0-%d)", num, _numChores);
return -1;
}
if (_chores[num]->isPlaying() && !(excludeLooping && _chores[num]->isLooping()))
return num;
else
return -1;
}
int Costume::isChoring(bool excludeLooping) {
for (int i = 0; i < _numChores; i++) {
if (_chores[i]->isPlaying() && !(excludeLooping && _chores[i]->isLooping()))
return i;
}
return -1;
}
void Costume::draw() {
for (int i = 0; i < _numComponents; i++)
if (_components[i])
_components[i]->draw();
}
void Costume::getBoundingBox(int *x1, int *y1, int *x2, int *y2) {
for (int i = 0; i < _numComponents; i++) {
if (_components[i] &&(_components[i]->isComponentType('M','M','D','L') ||
_components[i]->isComponentType('M','O','D','L'))) {
ModelComponent *c = static_cast<ModelComponent *>(_components[i]);
c->getBoundingBox(x1, y1, x2, y2);
}
if (_components[i] &&(_components[i]->isComponentType('m','e','s','h'))) {
EMIMeshComponent *c = static_cast<EMIMeshComponent *>(_components[i]);
c->getBoundingBox(x1, y1, x2, y2);
}
}
}
int Costume::update(uint time) {
for (Common::List<Chore*>::iterator i = _playingChores.begin(); i != _playingChores.end(); ++i) {
(*i)->update(time);
if (!(*i)->isPlaying()) {
i = _playingChores.erase(i);
--i;
}
}
int marker = 0;
for (int i = 0; i < _numComponents; i++) {
if (_components[i]) {
_components[i]->setMatrix(_matrix);
int m = _components[i]->update(time);
if (m > 0) {
marker = m;
}
}
}
return marker;
}
void Costume::animate() {
for (int i = 0; i < _numComponents; i++) {
if (_components[i]) {
_components[i]->animate();
}
}
}
void Costume::moveHead(bool entering, const Math::Vector3d &lookAt) {
_head->lookAt(entering, lookAt, _lookAtRate, _matrix);
}
int Costume::getHeadJoint() const {
return static_cast<Head *>(_head)->getJoint3();
}
void Costume::setHead(int joint1, int joint2, int joint3, float maxRoll, float maxPitch, float maxYaw) {
Head *head = static_cast<Head *>(_head);
head->setJoints(joint1, joint2, joint3);
head->loadJoints(getModelNodes());
head->setMaxAngles(maxPitch, maxYaw, maxRoll);
}
void Costume::setLookAtRate(float rate) {
_lookAtRate = rate;
}
float Costume::getLookAtRate() const {
return _lookAtRate;
}
void Costume::setPosRotate(const Math::Vector3d &pos, const Math::Angle &pitch,
const Math::Angle &yaw, const Math::Angle &roll) {
_matrix.setPosition(pos);
_matrix.buildFromEuler(yaw, pitch, roll, Math::EO_ZXY);
}
Math::Matrix4 Costume::getMatrix() const {
return _matrix;
}
Costume *Costume::getPreviousCostume() const {
return _prevCostume;
}
void Costume::saveState(SaveGame *state) const {
if (_cmap) {
state->writeBool(true);
state->writeString(_cmap->getFilename());
} else {
state->writeBool(false);
}
for (int i = 0; i < _numChores; ++i) {
_chores[i]->saveState(state);
}
for (int i = 0; i < _numComponents; ++i) {
Component *c = _components[i];
if (c) {
state->writeBool(c->_visible);
c->saveState(state);
}
}
state->writeLEUint32(_playingChores.size());
for (Common::List<Chore*>::const_iterator i = _playingChores.begin(); i != _playingChores.end(); ++i) {
state->writeLESint32((*i)->getChoreId());
}
state->writeFloat(_lookAtRate);
_head->saveState(state);
}
bool Costume::restoreState(SaveGame *state) {
if (state->readBool()) {
Common::String str = state->readString();
setColormap(str);
}
for (int i = 0; i < _numChores; ++i) {
_chores[i]->restoreState(state);
}
for (int i = 0; i < _numComponents; ++i) {
Component *c = _components[i];
if (c) {
c->_visible = state->readBool();
if (state->saveMinorVersion() < 14) {
// skip the old _matrix vector
state->readVector3d();
}
c->restoreState(state);
}
}
int numPlayingChores = state->readLEUint32();
for (int i = 0; i < numPlayingChores; ++i) {
int id = state->readLESint32();
_playingChores.push_back(_chores[id]);
}
_lookAtRate = state->readFloat();
_head->restoreState(state);
_head->loadJoints(getModelNodes());
return true;
}
} // end of namespace Grim

127
engines/grim/costume.h Normal file
View File

@@ -0,0 +1,127 @@
/* 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 GRIM_COSTUME_H
#define GRIM_COSTUME_H
#include "common/str.h"
#include "math/matrix4.h"
#include "engines/grim/object.h"
namespace Grim {
typedef uint32 tag32;
class CMap;
class Model;
class ModelNode;
class TextSplitter;
class ModelComponent;
class Component;
class Chore;
class BaseHead;
class Actor;
class Costume : public Object {
public:
Costume(const Common::String &filename, Actor *owner, Costume *prevCost);
virtual ~Costume();
virtual void load(Common::SeekableReadStream *data);
const Common::String &getFilename() const { return _fname; }
void playChore(const char *name, uint msecs = 0);
virtual void playChore(int num, uint msecs = 0);
void playChoreLooping(const char *name, uint msecs = 0);
virtual void playChoreLooping(int num, uint msecs = 0);
void setChoreLastFrame(int num);
void setChoreLooping(int num, bool val);
void stopChore(int num, uint msecs = 0);
void fadeChoreIn(int chore, uint msecs);
void fadeChoreOut(int chore, uint msecs);
ModelNode *getModelNodes();
Model *getModel();
void setColormap(const Common::String &map);
void stopChores(bool ignoreLoopingChores = false, int msecs = 0);
int isChoring(const char *name, bool excludeLooping);
int isChoring(int num, bool excludeLooping);
int isChoring(bool excludeLooping);
int getNumChores() const { return _numChores; }
Chore *getChore(const char *name);
Chore *getChore(int i) { return _chores[i]; }
int getChoreId(const char *name);
const Common::List<Chore *> &getPlayingChores() const { return _playingChores; }
void setHead(int joint1, int joint2, int joint3, float maxRoll, float maxPitch, float maxYaw);
void setLookAtRate(float rate);
float getLookAtRate() const;
virtual void moveHead(bool entering, const Math::Vector3d &lookAt);
int getHeadJoint() const;
CMap *getCMap() { return _cmap; }
virtual int update(uint frameTime);
void animate();
virtual void draw();
void getBoundingBox(int *x1, int *y1, int *x2, int *y2);
void setPosRotate(const Math::Vector3d &pos, const Math::Angle &pitch,
const Math::Angle &yaw, const Math::Angle &roll);
Math::Matrix4 getMatrix() const;
Actor *getOwner() const { return _owner; }
Costume *getPreviousCostume() const;
virtual void saveState(SaveGame *state) const;
virtual bool restoreState(SaveGame *state);
Component *getComponent(int num) { return _components[num]; }
protected:
virtual Component *loadComponent(tag32 tag, Component *parent, int parentID, const char *name, Component *prevComponent);
void load(TextSplitter &ts, Costume *prevCost);
ModelComponent *getMainModelComponent() const;
Common::String _fname;
Costume *_prevCostume;
int _numComponents;
Component **_components;
BaseHead *_head;
ObjectPtr<CMap> _cmap;
int _numChores;
Chore **_chores;
Common::List<Chore*> _playingChores;
Math::Matrix4 _matrix;
Actor *_owner;
float _lookAtRate;
friend class Chore;
};
} // end of namespace Grim
#endif

View File

@@ -0,0 +1,81 @@
/* 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 "engines/grim/objectstate.h"
#include "engines/grim/grim.h"
#include "engines/grim/debug.h"
#include "engines/grim/set.h"
#include "engines/grim/costume/anim_component.h"
namespace Grim {
AnimComponent::AnimComponent(Component *p, int parentID, const char *filename, tag32 t) :
Component(p, parentID, filename, t) {
_overlay = false;
_created = false;
const char *comma = strchr(filename, ',');
if (comma) {
_name = Common::String(filename, comma);
_overlay = atoi(comma + 1) == 1;
}
}
void AnimComponent::setKey(int val) {
ObjectState *state = g_grim->getCurrSet()->findState(_name);
if (!state) {
Set *set = g_grim->getCurrSet();
state = set->addObjectState(set->getSetup(), (_overlay ? ObjectState::OBJSTATE_OVERLAY : ObjectState::OBJSTATE_UNDERLAY),
_name.c_str(), nullptr, false);
}
_created = true;
if (state) {
state->setActiveImage(val);
return;
}
// Complain that we couldn't find the bitmap. This means we probably
// didn't handle something correctly. Example: Before the tube-switcher
// bitmaps were not loading with the scene. This was because they were requested
// as a different case then they were stored (tu_0_dorcu_door_open versus
// TU_0_DORCU_door_open), which was causing problems in the string comparison.
Debug::warning(Debug::Bitmaps | Debug::Costumes, "Missing scene bitmap: %s", _name.c_str());
/* In case you feel like drawing the missing bitmap anyway...
// Assume that all objects the scene file forgot about are OBJSTATE_STATE class
state = new ObjectState(0, ObjectState::OBJSTATE_STATE, bitmap, NULL, true);
if (!state) {
if (gDebugLevel == DEBUG_BITMAPS || gDebugLevel == DEBUG_WARN || gDebugLevel == DEBUG_ALL)
warning("Couldn't find bitmap %s in current scene", _filename.c_str());
return;
}
g_grim->getCurrSet()->addObjectState(state);
state->setNumber(val);
*/
}
void AnimComponent::reset() {
if (_created) {
setKey(0);
}
}
} // end of namespace Grim

View File

@@ -0,0 +1,43 @@
/* 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 GRIM_ANIM_COMPONENT_H
#define GRIM_ANIM_COMPONENT_H
#include "engines/grim/costume/component.h"
namespace Grim {
// This is used in Grim demo only
class AnimComponent : public Component {
public:
AnimComponent(Component *parent, int parentID, const char *filename, tag32 tag);
void setKey(int val) override;
void reset() override;
private:
bool _created;
bool _overlay;
};
} // end of namespace Grim
#endif

View File

@@ -0,0 +1,61 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "engines/grim/objectstate.h"
#include "engines/grim/grim.h"
#include "engines/grim/debug.h"
#include "engines/grim/set.h"
#include "engines/grim/costume/bitmap_component.h"
namespace Grim {
BitmapComponent::BitmapComponent(Component *p, int parentID, const char *filename, tag32 t) :
Component(p, parentID, filename, t) {
}
void BitmapComponent::setKey(int val) {
ObjectState *state = g_grim->getCurrSet()->findState(_name);
if (state) {
state->setActiveImage(val);
return;
}
// Complain that we couldn't find the bitmap. This means we probably
// didn't handle something correctly. Example: Before the tube-switcher
// bitmaps were not loading with the scene. This was because they were requested
// as a different case then they were stored (tu_0_dorcu_door_open versus
// TU_0_DORCU_door_open), which was causing problems in the string comparison.
Debug::warning(Debug::Bitmaps | Debug::Costumes, "Missing scene bitmap: %s", _name.c_str());
/* In case you feel like drawing the missing bitmap anyway...
// Assume that all objects the scene file forgot about are OBJSTATE_STATE class
state = new ObjectState(0, ObjectState::OBJSTATE_STATE, bitmap, NULL, true);
if (!state) {
if (gDebugLevel == DEBUG_BITMAPS || gDebugLevel == DEBUG_WARN || gDebugLevel == DEBUG_ALL)
warning("Couldn't find bitmap %s in current scene", _filename.c_str());
return;
}
g_grim->getCurrSet()->addObjectState(state);
state->setNumber(val);
*/
}
} // end of namespace Grim

View File

@@ -0,0 +1,37 @@
/* 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 GRIM_BITMAP_COMPONENT_H
#define GRIM_BITMAP_COMPONENT_H
#include "engines/grim/costume/component.h"
namespace Grim {
class BitmapComponent : public Component {
public:
BitmapComponent(Component *parent, int parentID, const char *filename, tag32 tag);
void setKey(int val) override;
};
} // end of namespace Grim
#endif

View File

@@ -0,0 +1,246 @@
/* 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 "engines/grim/costume.h"
#include "engines/grim/savegame.h"
#include "engines/grim/textsplit.h"
#include "engines/grim/costume/chore.h"
#include "engines/grim/costume/component.h"
#include "engines/grim/costume/keyframe_component.h"
namespace Grim {
// Should initialize the status variables so the chore can't play unexpectedly
Chore::Chore(char name[32], int id, Costume *owner, int length, int numTracks) :
_hasPlayed(false), _playing(false), _looping(false), _paused(false), _currTime(-1),
_numTracks(numTracks), _length(length), _choreId(id), _owner(owner) {
memcpy(_name, name, 32);
_tracks = new ChoreTrack[_numTracks];
}
Chore::~Chore() {
if (_tracks) {
for (int i = 0; i < _numTracks; i++)
delete[] _tracks[i].keys;
delete[] _tracks;
_tracks = nullptr;
}
}
void Chore::load(TextSplitter &ts) {
_hasPlayed = _playing = false;
for (int i = 0; i < _numTracks; i++) {
int compID, numKeys;
ts.scanString(" %d %d", 2, &compID, &numKeys);
_tracks[i].compID = compID;
_tracks[i].numKeys = numKeys;
_tracks[i].keys = new TrackKey[numKeys];
for (int j = 0; j < numKeys; j++) {
ts.scanString(" %d %d", 2, &_tracks[i].keys[j].time, &_tracks[i].keys[j].value);
}
}
}
void Chore::play(uint msecs) {
_playing = true;
_paused = false;
_hasPlayed = true;
_looping = false;
_currTime = -1;
if (msecs > 0)
fade(Animation::FadeIn, msecs);
else
fade(Animation::None, 0);
}
void Chore::playLooping(uint msecs) {
_playing = true;
_paused = false;
_hasPlayed = true;
_looping = true;
_currTime = -1;
if (msecs > 0)
fade(Animation::FadeIn, msecs);
else
fade(Animation::None, 0);
}
Component *Chore::getComponentForTrack(int i) const {
if (_tracks[i].compID == -1)
return _tracks[i].component;
else
return _owner->_components[_tracks[i].compID];
}
void Chore::stop(uint msecs) {
if (msecs > 0)
fade(Animation::FadeOut, msecs);
_playing = false;
_hasPlayed = false;
for (int i = 0; i < _numTracks; i++) {
Component *comp = getComponentForTrack(i);
if (comp)
comp->reset();
}
}
void Chore::setKeys(int startTime, int stopTime) {
for (int i = 0; i < _numTracks; i++) {
Component *comp = getComponentForTrack(i);
if (!comp)
continue;
for (int j = 0; j < _tracks[i].numKeys; j++) {
if (_tracks[i].keys[j].time > stopTime && stopTime != -1)
break;
if (_tracks[i].keys[j].time > startTime)
comp->setKey(_tracks[i].keys[j].value);
}
}
}
void Chore::setLastFrame() {
// If the chore has already played then don't set it to the end
// Example: This executing would result in Glottis being
// choppy when he hands Manny the work order
// if (_hasPlayed)
// return;
// This comment above is perfectly right, but unfortunately doing that
// breaks glottis movements when he answers to "i'm calavera, manny calavera".
// Moreover, the choppy behaviour stated above happens with grim original too,
// meaning the bug is not in Residual but in the scripts or in GrimE design.
_currTime = -1;
_playing = false;
_paused = false;
_hasPlayed = true;
_looping = false;
// In the demo, the chore 4 (stop_talk) of ms.cos, has length 67, and 4 keys,
// the last two of which are at time 133 and 200. We use -1 as stopTime here
// as a special value, instead of _length, to ensure all the keys are run.
// (failing to do so will result in manny's mouth not closing when he stops talking)
setKeys(-1, -1);
}
void Chore::update(uint time) {
if (!_playing || _paused)
return;
int newTime;
if (_currTime < 0)
newTime = 0; // For first time through
else
newTime = _currTime + time;
setKeys(_currTime, newTime);
if (newTime > _length) {
if (!_looping) {
_playing = false;
} else {
do {
newTime -= _length;
setKeys(-1, newTime);
} while (newTime > _length);
}
}
_currTime = newTime;
}
void Chore::fade(Animation::FadeMode mode, uint msecs) {
if (mode == Animation::FadeIn) {
if (!_playing) {
_playing = true;
_hasPlayed = true;
_currTime = -1;
}
} else if (mode == Animation::FadeOut) {
// Stop the chore, but do not alter the components state.
_playing = false;
}
for (int i = 0; i < _numTracks; i++) {
Component *comp = getComponentForTrack(i);
if (comp) {
comp->fade(mode, msecs);
}
}
}
void Chore::fadeIn(uint msecs) {
fade(Animation::FadeIn, msecs);
}
void Chore::fadeOut(uint msecs) {
// Note: It doesn't matter whether the chore is playing or not. The keyframe
// components should fade out in either case.
fade(Animation::FadeOut, msecs);
}
void Chore::setPaused(bool paused) {
_paused = paused;
for (int i = 0; i < _numTracks; i++) {
Component *comp = getComponentForTrack(i);
if (comp) {
comp->setPaused(paused);
}
}
}
void Chore::advance(uint msecs) {
setKeys(_currTime, _currTime + msecs);
for (int i = 0; i < _numTracks; i++) {
Component *comp = getComponentForTrack(i);
if (comp) {
comp->advance(msecs);
}
}
_currTime += msecs;
}
void Chore::saveState(SaveGame *state) const {
state->writeBool(_hasPlayed);
state->writeBool(_playing);
state->writeBool(_looping);
state->writeLESint32(_currTime);
state->writeBool(_paused);
}
void Chore::restoreState(SaveGame *state) {
_hasPlayed = state->readBool();
_playing = state->readBool();
_looping = state->readBool();
_currTime = state->readLESint32();
if (state->saveMinorVersion() >= 10)
_paused = state->readBool();
}
} // end of namespace Grim

View File

@@ -0,0 +1,96 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef GRIM_CHORE_H
#define GRIM_CHORE_H
#include "engines/grim/animation.h"
namespace Grim {
class Costume;
class Animation;
class Component;
class TextSplitter;
struct TrackKey {
int time, value;
};
struct ChoreTrack {
int compID;
int numKeys;
TrackKey *keys;
Component *component;
};
class Chore {
public:
Chore(char name[32], int id, Costume *owner, int length, int numTracks);
virtual ~Chore();
void load(TextSplitter &ts);
virtual void play(uint msecs);
virtual void playLooping(uint msecs);
void setLooping(bool val) { _looping = val; }
virtual void stop(uint msecs);
virtual void update(uint time);
void setLastFrame();
void fadeIn(uint msecs);
void fadeOut(uint msecs);
void setPaused(bool paused);
bool isPlaying() { return _playing; }
bool isPaused() { return _paused; }
bool isLooping() { return _looping; }
void advance(uint msecs);
const char *getName() const { return _name; }
int getChoreId() { return _choreId; }
Costume *getOwner() { return _owner; }
virtual void saveState(SaveGame *state) const;
virtual void restoreState(SaveGame *state);
protected:
void setKeys(int startTime, int stopTime);
virtual void fade(Animation::FadeMode, uint msecs);
Component *getComponentForTrack(int i) const;
Costume *_owner;
int _choreId;
int _length;
int _numTracks;
ChoreTrack *_tracks;
char _name[32];
bool _hasPlayed, _playing, _looping, _paused;
int _currTime;
friend class EMICostume;
};
} // end of namespace Grim
#endif

View File

@@ -0,0 +1,44 @@
/* 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 "engines/grim/costume.h"
#include "engines/grim/resource.h"
#include "engines/grim/colormap.h"
#include "engines/grim/costume/colormap_component.h"
namespace Grim {
ColormapComponent::ColormapComponent(Component *p, int parentID, const char *filename, tag32 t) :
Component(p, parentID, filename, t) {
_cmap = g_resourceloader->getColormap(_name);
// Set the colormap here in the ctor and not in init()!
if (p)
p->setColormap(_cmap);
}
void ColormapComponent::init() {
if (!_parent)
warning("No parent to apply colormap object on. CMap: %s, Costume: %s",
_cmap->getFilename().c_str(), _cost->getFilename().c_str());
}
} // end of namespace Grim

View File

@@ -0,0 +1,39 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef GRIM_COLORMAP_COMPONENT_H
#define GRIM_COLORMAP_COMPONENT_H
#include "engines/grim/costume/component.h"
namespace Grim {
class ColormapComponent : public Component {
public:
ColormapComponent(Component *parent, int parentID, const char *filename, tag32 tag);
ColormapComponent *copy(Component *newParent);
void init() override;
};
} // end of namespace Grim
#endif

View File

@@ -0,0 +1,100 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "engines/grim/costume/component.h"
#include "engines/grim/colormap.h"
#include "engines/grim/costume.h"
namespace Grim {
Component::Component(Component *p, int parentID, const char *name, tag32 t) :
_visible(true), _tag(t), _parentID(parentID), _name(name),
_previousCmap(nullptr), _cmap(nullptr), _cost(nullptr) {
setParent(p);
}
Component::~Component() {
if (_parent)
_parent->removeChild(this);
Component *child = _child;
while (child) {
child->_parent = nullptr;
child = child->_sibling;
}
}
void Component::setColormap(CMap *c) {
if (c)
_cmap = c;
if (getCMap()) {
resetHierCMap();
}
}
bool Component::isVisible() {
if (_visible && _parent)
return _parent->isVisible();
return _visible;
}
CMap *Component::getCMap() {
if (!_cmap && _previousCmap)
return _previousCmap;
else if (!_cmap && _parent)
return _parent->getCMap();
else if (!_cmap && !_parent && _cost)
return _cost->getCMap();
else
return _cmap;
}
void Component::setParent(Component *newParent) {
_parent = newParent;
_child = nullptr;
_sibling = nullptr;
if (_parent) {
_sibling = _parent->_child;
_parent->_child = this;
}
}
void Component::removeChild(Component *child) {
Component **childPos = &_child;
while (*childPos && *childPos != child)
childPos = &(*childPos)->_sibling;
if (*childPos) {
*childPos = child->_sibling;
child->_parent = nullptr;
}
}
void Component::resetHierCMap() {
resetColormap();
Component *child = _child;
while (child) {
child->resetHierCMap();
child = child->_sibling;
}
}
} // end of namespace Grim

View File

@@ -0,0 +1,84 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef GRIM_COMPONENT_H
#define GRIM_COMPONENT_H
#include "math/matrix4.h"
#include "engines/grim/object.h"
#include "engines/grim/animation.h"
namespace Grim {
typedef uint32 tag32;
class Costume;
class CMap;
class SaveGame;
class Component {
public:
Component(Component *parent, int parentID, const char *name, tag32 tag);
CMap *getCMap();
virtual void setColormap(CMap *c);
bool isVisible();
Component *getParent() { return _parent; }
virtual void setMatrix(const Math::Matrix4 &) { };
virtual void init() { }
virtual void setKey(int) { }
virtual void setMapName(char *) { }
virtual int update(uint time) { return 0; }
virtual void animate() { }
virtual void draw() { }
virtual void reset() { }
virtual void fade(Animation::FadeMode, int) { }
virtual void advance(uint msecs) { }
virtual void setPaused(bool paused) { }
virtual void resetColormap() { }
virtual void saveState(SaveGame *) { }
virtual void restoreState(SaveGame *) { }
virtual ~Component();
bool isComponentType(char a0, char a1, char a2, char a3) { return _tag == MKTAG(a0, a1, a2, a3); }
protected:
ObjectPtr<CMap> _cmap, _previousCmap;
tag32 _tag;
int _parentID;
bool _visible;
Component *_parent, *_child, *_sibling;
Costume *_cost;
Common::String _name;
void setCostume(Costume *cost) { _cost = cost; }
void setParent(Component *newParent);
void removeChild(Component *child);
void resetHierCMap();
friend class Costume;
friend class EMICostume;
};
} // end of namespace Grim
#endif

View File

@@ -0,0 +1,249 @@
/* 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 "engines/grim/model.h"
#include "engines/grim/grim.h"
#include "engines/grim/savegame.h"
#include "engines/grim/costume/head.h"
namespace Grim {
Head::Joint::Joint() :
_node(nullptr), _pitch(0.f), _yaw(0.f), _roll(0.f) {
}
void Head::Joint::init(ModelNode *node) {
_node = node;
}
void Head::Joint::orientTowards(bool entering, const Math::Vector3d &point, float rate, const Math::Matrix4 &matrix,
float maxPitch, float maxYaw, float maxRoll, float constrain) {
float step = g_grim->getPerSecond(rate);
float yawStep = step;
float pitchStep = step / 3.0f;
float rollStep = step / 3.0f;
if (!_node)
return;
// Make sure we have up-to-date world transform matrices computed for the joint nodes of this character.
_node->_needsUpdate = true;
ModelNode *p = _node;
while (p->_parent) {
p = p->_parent;
p->_needsUpdate = true;
}
p->setMatrix(matrix);
p->update();
Math::Vector3d modelFront; // the modeling convention for the forward direction.
Math::Vector3d modelUp; // the modeling convention for the upward direction.
Math::Vector3d frontDir; // Character front facing direction vector in world space (global scene coordinate space)
// the character head coordinate frame is: +Y forward, +Z up, +X right.
frontDir = Math::Vector3d(_node->_matrix(0, 1), _node->_matrix(1, 1), _node->_matrix(2, 1)); // Look straight ahead. (+Y)
modelFront = Math::Vector3d(0, 1, 0);
modelUp = Math::Vector3d(0, 0, 1);
// v is the world space direction vector this character should be looking towards.
Math::Vector3d targetDir = point - _node->_pivotMatrix.getPosition();
if (!entering)
targetDir = frontDir;
if (targetDir.isZero())
return;
targetDir.normalize();
// The vector v is in world space, so generate the world space lookat matrix for the desired head facing
// orientation.
Math::Matrix4 lookAtTM;
lookAtTM.setToIdentity();
const Math::Vector3d worldUp(0, 0, 1); // The Residual scene convention: +Z is world space up.
if (Math::Vector3d::dotProduct(targetDir, worldUp) >= 0.98f) // Avoid singularity if trying to look straight up.
lookAtTM.buildFromTargetDir(modelFront, targetDir, modelUp, -frontDir); // Instead of orienting head towards scene up, orient head towards character "back",
// i.e. when you look straight up, your head up vector tilts/arches to point straight backwards.
else if (Math::Vector3d::dotProduct(targetDir, worldUp) <= -0.98f) // Avoid singularity if trying to look straight down.
lookAtTM.buildFromTargetDir(modelFront, targetDir, modelUp, frontDir); // Instead of orienting head towards scene down, orient head towards character "front",
// i.e. when you look straight down, your head up vector tilts/arches to point straight forwards.
else
lookAtTM.buildFromTargetDir(modelFront, targetDir, modelUp, worldUp);
// The above specifies the world space orientation of this bone, but we need to output
// the orientation in parent space (as yaw/pitch/roll).
// Get the coordinate frame in which we need to produce the character head yaw/pitch/roll values.
Math::Matrix4 parentWorldTM;
if (_node->_parent)
parentWorldTM = _node->_parent->_matrix;
// While we could compute the desired lookat direction directly in the above coordinate frame,
// it is preferrable to compute the lookat direction with respect to the head orientation in
// the keyframe animation. This is because the LUA scripts specify the maximum head yaw, pitch and
// roll values with respect to those keyframe animations. If the lookat was simply computed
// directly in the space of the parent, we couldn't apply the head maxYaw/Pitch/Roll constraints
// properly. So, compute the coordinate frame of this bone in the keyframe animation.
Math::Matrix4 animFrame = _node->_localMatrix;
parentWorldTM = parentWorldTM * animFrame;
parentWorldTM.invertAffineOrthonormal();
// Convert lookAtTM orientation from world space to parent-with-keyframe-animation space.
lookAtTM = parentWorldTM * lookAtTM;
// Decompose to yaw-pitch-roll (+Z, +X, +Y).
// In this space, Yaw is +Z. Pitch is +X. Roll is +Y.
Math::Angle y, pt, r;
lookAtTM.getEuler(&y, &pt, &r, Math::EO_ZXY);
y = y * constrain;
pt = pt * constrain;
r = r * constrain;
// Constrain the maximum head movement, as desired by the game LUA scripts.
y.clampDegrees(maxYaw);
pt.clampDegrees(maxPitch);
r.clampDegrees(maxRoll);
// Also limit yaw, pitch and roll to make at most a movement as large as the given max step size during this frame.
// This will produce a slow head-turning animation instead of immediately snapping to the
// target lookat orientation.
if (y - _yaw > yawStep)
y = _yaw + yawStep;
if (_yaw - y > yawStep)
y = _yaw - yawStep;
if (pt - _pitch > pitchStep)
pt = _pitch + pitchStep;
if (_pitch - pt > pitchStep)
pt = _pitch - pitchStep;
if (r - _roll > rollStep)
r = _roll + rollStep;
if (_roll - r > rollStep)
r = _roll - rollStep;
// Remember how far we animated the head this frame, and we'll continue from here the next frame.
_pitch = pt;
_yaw = y;
_roll = r;
// Assemble ypr to a quaternion.
// This is the head orientation with respect to parent-with-keyframe-animation space.
Math::Quaternion lookAtQuat = Math::Quaternion::fromEuler(y, pt, r, Math::EO_ZXY);
_node->_animRot = _node->_animRot * lookAtQuat;
}
void Head::Joint::saveState(SaveGame *state) const {
state->writeFloat(_pitch.getDegrees());
state->writeFloat(_yaw.getDegrees());
state->writeFloat(_roll.getDegrees());
}
void Head::Joint::restoreState(SaveGame *state) {
_pitch = state->readFloat();
_yaw = state->readFloat();
_roll = state->readFloat();
}
Head::Head() :
_maxPitch(0), _maxYaw(0), _maxRoll(0),
_joint1Node(-1), _joint2Node(-1), _joint3Node(-1) {
}
void Head::setJoints(int joint1, int joint2, int joint3) {
_joint1Node = joint1;
_joint2Node = joint2;
_joint3Node = joint3;
}
void Head::loadJoints(ModelNode *nodes) {
if (_joint1Node >= 0 && _joint2Node >= 0 && _joint3Node >= 0 && nodes) {
_joint1.init(nodes + _joint1Node);
_joint2.init(nodes + _joint2Node);
_joint3.init(nodes + _joint3Node);
}
}
void Head::setMaxAngles(float maxPitch, float maxYaw, float maxRoll) {
_maxRoll = maxRoll;
_maxPitch = maxPitch;
_maxYaw = maxYaw;
}
void Head::lookAt(bool entering, const Math::Vector3d &point, float rate, const Math::Matrix4 &matrix) {
if (_joint1Node != -1) {
// NOTE: By default, the _head.maxRoll for Manny's head is constrained to 165 degrees, which
// comes in from the original Lua data scripts. (also, maxYaw == 80, maxPitch == 28).
// The very small maxPitch angle, and a very large maxRoll angle causes problems when Manny
// is trying to look straight up to an object, in which case the euler roll angles vary
// wildly compared to the pitch angles, which get clamped to a much smaller interval. Therefore,
// restrict the maximum roll angle to a smaller value than 165 degrees to avoid this behavior.
// If you want to change this, good places to test are:
// A) Year 1, outside the Department of Death, run/walk up & down the stairs, there's a sign
// right above the stairs, and Manny looks dead up.
// B) Year 3, when Manny and Meche are imprisoned in the vault. Walk inside the room where Meche
// is in, to look straight up to the sprinklers.
if (_joint1Node == _joint2Node && _joint1Node == _joint3Node) {
// Most characters only have one head joint instead of three, so we can orient the head
// with a single call.
_joint3.orientTowards(entering, point, rate, matrix, _maxPitch, _maxYaw, 30.f, 1.0f);
} else {
// For characters like Manny, we'll have to orient each of the three head joints.
_joint1.orientTowards(entering, point, rate / 3, matrix, _maxPitch / 3, _maxYaw / 3, 10.f, 0.333f);
_joint2.orientTowards(entering, point, rate / 3, matrix, _maxPitch / 3, _maxYaw / 3, 10.f, 0.666f);
_joint3.orientTowards(entering, point, rate / 3, matrix, _maxPitch / 3, _maxYaw / 3, 10.f, 1.000f);
}
}
}
void Head::saveState(SaveGame *state) const {
state->writeLESint32(_joint1Node);
state->writeLESint32(_joint2Node);
state->writeLESint32(_joint3Node);
state->writeFloat(_maxPitch);
state->writeFloat(_maxYaw);
state->writeFloat(_maxRoll);
_joint1.saveState(state);
_joint2.saveState(state);
_joint3.saveState(state);
}
void Head::restoreState(SaveGame *state) {
_joint1Node = state->readLESint32();
_joint2Node = state->readLESint32();
_joint3Node = state->readLESint32();
_maxPitch = state->readFloat();
_maxYaw = state->readFloat();
_maxRoll = state->readFloat();
if (state->saveMinorVersion() < 2) {
state->readFloat();
state->readFloat();
} else {
_joint1.restoreState(state);
_joint2.restoreState(state);
_joint3.restoreState(state);
}
}
} // end of namespace Grim

View File

@@ -0,0 +1,98 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef GRIM_HEAD_H
#define GRIM_HEAD_H
#include "math/matrix4.h"
namespace Grim {
class ModelNode;
class SaveGame;
class BaseHead {
public:
virtual ~BaseHead() {}
virtual void lookAt(bool entering, const Math::Vector3d &point, float rate, const Math::Matrix4 &matrix) = 0;
virtual void saveState(SaveGame *state) const = 0;
virtual void restoreState(SaveGame *state) = 0;
virtual void loadJoints(ModelNode *nodes) = 0;
};
class Head : public BaseHead {
public:
class Joint {
public:
Joint();
void init(ModelNode *node);
void orientTowards(bool entering, const Math::Vector3d &point, float rate, const Math::Matrix4 &matrix,
float maxPitch, float maxYaw, float maxRoll, float constrain);
void saveState(SaveGame *state) const;
void restoreState(SaveGame *state);
private:
ModelNode *_node;
Math::Angle _pitch;
Math::Angle _yaw;
Math::Angle _roll;
};
Head();
void setJoints(int joint1, int joint2, int joint3);
void loadJoints(ModelNode *nodes);
void setMaxAngles(float maxPitch, float maxYaw, float maxRoll);
void lookAt(bool entering, const Math::Vector3d &point, float rate, const Math::Matrix4 &matrix);
void saveState(SaveGame *state) const;
void restoreState(SaveGame *state);
int getJoint1() const { return _joint1Node; }
int getJoint2() const { return _joint2Node; }
int getJoint3() const { return _joint3Node; }
private:
int _joint1Node;
int _joint2Node;
int _joint3Node;
float _maxRoll;
float _maxPitch;
float _maxYaw;
// Specifies the three head joint bones of this character.
// These joint bones are animated by the moveHead function to make
// the characters face different directions.
// Note that for some characters, these variables may all be equal.
Joint _joint1;
Joint _joint2;
Joint _joint3;
};
} // end of namespace Grim
#endif

View File

@@ -0,0 +1,133 @@
/* 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 "engines/grim/debug.h"
#include "engines/grim/costume/keyframe_component.h"
#include "engines/grim/costume/model_component.h"
namespace Grim {
KeyframeComponent::KeyframeComponent(Component *p, int parentID, const char *filename, tag32 t) :
Component(p, parentID, filename, t), _priority1(1), _priority2(5), _anim(nullptr) {
const char *comma = strchr(filename, ',');
if (comma) {
_name = Common::String(filename, comma);
sscanf(comma + 1, "%d,%d", &_priority1, &_priority2);
}
}
KeyframeComponent::~KeyframeComponent() {
delete _anim;
}
void KeyframeComponent::fade(Animation::FadeMode fadeMode, int fadeLength) {
_anim->fade(fadeMode, fadeLength);
}
void KeyframeComponent::setKey(int val) {
switch (val) {
case 0: // "Play Once"
_anim->play(Animation::Once);
break;
case 1: // "Play Looping"
_anim->play(Animation::Looping);
break;
case 2: // "Play and Endpause"
_anim->play(Animation::PauseAtEnd);
break;
case 3: // "Play and Endfade"
_anim->play(Animation::FadeAtEnd);
break;
case 4: // "Stop"
reset();
break;
case 5: // "Pause"
_anim->pause(true);
break;
case 6: // "Unpause"
_anim->pause(false);
break;
case 7: // "1.0 Fade in"
fade(Animation::FadeIn, 1000);
_anim->activate();
break;
case 8: // "0.5 Fade in"
fade(Animation::FadeIn, 500);
_anim->activate();
break;
case 9: // "0.25 Fade in"
fade(Animation::FadeIn, 250);
_anim->activate();
break;
case 10: // "0.125 Fade in"
fade(Animation::FadeIn, 125);
_anim->activate();
break;
case 11: // "1.0 Fade out"
fade(Animation::FadeOut, 1000);
break;
case 12: // "0.5 Fade out
fade(Animation::FadeOut, 500);
break;
case 13: // "0.25 Fade out"
fade(Animation::FadeOut, 250);
break;
case 14: // "0.125 Fade out"
fade(Animation::FadeOut, 125);
break;
default:
Debug::warning(Debug::Costumes, "Unknown key %d for component %s", val, _name.c_str());
}
}
void KeyframeComponent::reset() {
if (_anim->getFadeMode() != Animation::FadeOut) {
_anim->stop();
}
}
int KeyframeComponent::update(uint time) {
if (!_anim->getIsActive())
return 0;
return _anim->update((int)time);
}
void KeyframeComponent::init() {
if (_parent->isComponentType('M','M','D','L') ||
_parent->isComponentType('M','O','D','L')) {
ModelComponent *mc = static_cast<ModelComponent *>(_parent);
_anim = new Animation(_name, mc->getAnimManager(), _priority1, _priority2);
} else {
Debug::warning(Debug::Costumes, "Parent of %s was not a model", _name.c_str());
_anim = nullptr;
}
}
void KeyframeComponent::saveState(SaveGame *state) {
_anim->saveState(state);
}
void KeyframeComponent::restoreState(SaveGame *state) {
_anim->restoreState(state);
}
} // end of namespace Grim

View File

@@ -0,0 +1,51 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef GRIM_KEYFRAME_COMPONENT_H
#define GRIM_KEYFRAME_COMPONENT_H
#include "engines/grim/costume/component.h"
#include "engines/grim/animation.h"
namespace Grim {
class Animation;
class KeyframeComponent : public Component {
public:
KeyframeComponent(Component *parent, int parentID, const char *filename, tag32 tag);
~KeyframeComponent();
void init() override;
void fade(Animation::FadeMode, int fadeLength) override;
void setKey(int val) override;
int update(uint time) override;
void reset() override;
void saveState(SaveGame *state) override;
void restoreState(SaveGame *state) override;
private:
Animation *_anim;
int _priority1, _priority2;
};
} // end of namespace Grim
#endif

View File

@@ -0,0 +1,36 @@
/* 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 "engines/grim/costume/lua_var_component.h"
#include "engines/grim/lua/lua.h"
namespace Grim {
LuaVarComponent::LuaVarComponent(Component *p, int parentID, const char *name, tag32 t) :
Component(p, parentID, name, t) {
}
void LuaVarComponent::setKey(int val) {
lua_pushnumber(val);
lua_setglobal(_name.c_str());
}
} // end of namespace Grim

View File

@@ -0,0 +1,37 @@
/* 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 GRIM_LUA_VAR_COMPONENT_H
#define GRIM_LUA_VAR_COMPONENT_H
#include "engines/grim/costume/component.h"
namespace Grim {
class LuaVarComponent : public Component {
public:
LuaVarComponent(Component *parent, int parentID, const char *name, tag32 tag);
void setKey(int val) override;
};
} // end of namespace Grim
#endif

View File

@@ -0,0 +1,83 @@
/* 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 "engines/grim/model.h"
#include "engines/grim/costume/model_component.h"
#include "engines/grim/costume/main_model_component.h"
namespace Grim {
MainModelComponent::MainModelComponent(Component *p, int parentID, const char *filename, Component *prevComponent, tag32 t) :
ModelComponent(p, parentID, filename, prevComponent, t), _hierShared(false), _parentModel(nullptr) {
if (parentID == -2 && prevComponent && prevComponent->isComponentType('M','M','D','L')) {
MainModelComponent *mmc = static_cast<MainModelComponent *>(prevComponent);
if (mmc->_name == _name) {
_animation = mmc->_animation;
_obj = mmc->_obj;
_hier = mmc->_hier;
_hierShared = true;
mmc->_children.push_back(this);
_parentModel = mmc;
}
}
}
MainModelComponent::~MainModelComponent() {
if (_hierShared) {
_obj = nullptr; // Keep ~ModelComp from deleting it
_animation = nullptr;
}
for (MainModelComponent *child : _children) {
child->_obj = nullptr;
child->_hier = nullptr;
child->_parentModel = nullptr;
}
if (_parentModel) {
_parentModel->_children.remove(this);
}
}
void MainModelComponent::init() {
ModelComponent::init();
_visible = true;
_hier->_hierVisible = _visible;
}
void MainModelComponent::setColormap(CMap *cmap) {
Component::setColormap(cmap);
if (_parentModel) {
_parentModel->setColormap(cmap);
}
}
void MainModelComponent::reset() {
_visible = true;
// Can be NULL if this was attached to another costume which
// was deleted.
if (_hier) {
_hier->_hierVisible = _visible;
}
}
} // end of namespace Grim

View File

@@ -0,0 +1,47 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef GRIM_MAIN_MODEL_COMPONENT_H
#define GRIM_MAIN_MODEL_COMPONENT_H
#include "engines/grim/costume/model_component.h"
namespace Grim {
class CMap;
class MainModelComponent : public ModelComponent {
public:
MainModelComponent(Component *parent, int parentID, const char *filename, Component *prevComponent, tag32 tag);
~MainModelComponent();
void init();
void setColormap(CMap *cmap);
void reset();
private:
bool _hierShared;
Common::List<MainModelComponent*> _children;
MainModelComponent *_parentModel;
};
} // end of namespace Grim
#endif

View File

@@ -0,0 +1,78 @@
/* 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/textconsole.h"
#include "engines/grim/costume.h"
#include "engines/grim/debug.h"
#include "engines/grim/model.h"
#include "engines/grim/material.h"
#include "engines/grim/savegame.h"
#include "engines/grim/costume/material_component.h"
#include "engines/grim/costume/model_component.h"
namespace Grim {
MaterialComponent::MaterialComponent(Component *p, int parentID, const char *filename, tag32 t) :
Component(p, parentID, filename, t) {
Debug::debug(Debug::Costumes, "Constructing MaterialComponent %s", filename);
}
void MaterialComponent::init() {
_mat = nullptr;
if (_parent->isComponentType('M','M','D','L') ||
_parent->isComponentType('M','O','D','L')) {
ModelComponent *p = static_cast<ModelComponent *>(_parent);
Model *model = p->getModel();
if (model) {
for (int i = 0; i < model->_numMaterials; ++i) {
if (_name.compareToIgnoreCase(model->_materials[i]->getFilename()) == 0) {
_mat = model->_materials[i];
return;
}
}
}
} else {
warning("Parent of a MaterialComponent not a ModelComponent. %s %s", _name.c_str(), _cost->getFilename().c_str());
}
}
void MaterialComponent::setKey(int val) {
_mat->setActiveTexture(val);
}
void MaterialComponent::reset() {
_mat->setActiveTexture(0);
}
void MaterialComponent::resetColormap() {
init();
}
void MaterialComponent::saveState(SaveGame *state) {
state->writeLESint32(_mat->getActiveTexture());
}
void MaterialComponent::restoreState(SaveGame *state) {
_mat->setActiveTexture(state->readLESint32());
}
} // end of namespace Grim

View File

@@ -0,0 +1,47 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef GRIM_MATERIAL_COMPONENT_H
#define GRIM_MATERIAL_COMPONENT_H
#include "engines/grim/costume/component.h"
namespace Grim {
class Material;
class MaterialComponent : public Component {
public:
MaterialComponent(Component *parent, int parentID, const char *filename, tag32 tag);
void init() override;
void setKey(int val) override;
void reset() override;
void resetColormap() override;
void saveState(SaveGame *state) override;
void restoreState(SaveGame *state) override;
private:
Material *_mat;
};
} // end of namespace Grim
#endif

View File

@@ -0,0 +1,92 @@
/* 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/textconsole.h"
#include "engines/grim/debug.h"
#include "engines/grim/model.h"
#include "engines/grim/savegame.h"
#include "engines/grim/costume/mesh_component.h"
#include "engines/grim/costume/model_component.h"
namespace Grim {
MeshComponent::MeshComponent(Component *p, int parentID, const char *name, tag32 t) :
Component(p, parentID, name, t), _node(nullptr) {
if (sscanf(name, "mesh %d", &_num) < 1)
error("Couldn't parse mesh name %s", name);
}
void MeshComponent::init() {
if (_parent->isComponentType('M','M','D','L') ||
_parent->isComponentType('M','O','D','L')) {
ModelComponent *mc = static_cast<ModelComponent *>(_parent);
_node = mc->getHierarchy() + _num;
_model = mc->getModel();
} else {
Debug::warning(Debug::Costumes, "Parent of mesh %d was not a model", _num);
_node = nullptr;
_model = nullptr;
}
}
CMap *MeshComponent::cmap() {
if (_parent->isComponentType('M','M','D','L') ||
_parent->isComponentType('M','O','D','L')) {
ModelComponent *mc = static_cast<ModelComponent *>(_parent);
return mc->getCMap();
}
return nullptr;
}
void MeshComponent::setKey(int val) {
_node->_meshVisible = (val != 0);
}
void MeshComponent::reset() {
// NOTE: Setting the visibility to true here causes a bug with the thunderboy costume:
// closing the inventory causes the hat to appear, while it shouldn't.
// This could however introduce regressions somewhere else, so if there is something
// disappearing or not behaving properly in a costume the cause might be here.
//_node->_meshVisible = true;
}
int MeshComponent::update(uint /*time*/) {
_node->setMatrix(_matrix);
return 0;
}
void MeshComponent::saveState(SaveGame *state) {
state->writeBool(_node->_meshVisible);
state->writeVector3d(_matrix.getPosition());
}
void MeshComponent::restoreState(SaveGame *state) {
_node->_meshVisible = state->readBool();
if (state->saveMinorVersion() >= 14) {
_matrix.setPosition(state->readVector3d());
_node->setMatrix(_matrix);
}
}
} // end of namespace Grim

View File

@@ -0,0 +1,58 @@
/* 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 GRIM_MESH_COMPONENT_H
#define GRIM_MESH_COMPONENT_H
#include "engines/grim/costume/component.h"
namespace Grim {
class CMap;
class Model;
class ModelNode;
class MeshComponent : public Component {
public:
MeshComponent(Component *parent, int parentID, const char *name, tag32 tag);
void init() override;
CMap *cmap();
void setKey(int val) override;
int update(uint time) override;
void reset() override;
void saveState(SaveGame *state) override;
void restoreState(SaveGame *state) override;
void setMatrix(const Math::Matrix4 &matrix) override { _matrix = matrix; };
ModelNode *getNode() { return _node; }
Model *getModel() { return _model; }
private:
int _num;
Model *_model;
ModelNode *_node;
Math::Matrix4 _matrix;
};
} // end of namespace Grim
#endif

View File

@@ -0,0 +1,210 @@
/* 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 "engines/grim/debug.h"
#include "engines/grim/model.h"
#include "engines/grim/resource.h"
#include "engines/grim/grim.h"
#include "engines/grim/set.h"
#include "engines/grim/gfx_base.h"
#include "engines/grim/colormap.h"
#include "engines/grim/animation.h"
#include "engines/grim/costume/model_component.h"
#include "engines/grim/costume/main_model_component.h"
#include "engines/grim/costume/mesh_component.h"
namespace Grim {
#define DEFAULT_COLORMAP "item.cmp"
ModelComponent::ModelComponent(Component *p, int parentID, const char *filename, Component *prevComponent, tag32 t) :
Component(p, parentID, filename, t),
_obj(nullptr), _hier(nullptr), _animation(nullptr), _animated(false) {
const char *comma = strchr(filename, ',');
// Can be called with a comma and a numeric parameter afterward, but
// the use for this parameter is currently unknown
// Example: At the "scrimshaw parlor" in Rubacava the object
// "manny_cafe.3do,1" is requested
if (comma) {
_name = Common::String(filename, comma);
warning("Comma in model components not supported: %s", filename);
}
_prevComp = prevComponent;
}
ModelComponent::~ModelComponent() {
if (_hier && _hier->_parent) {
_hier->_parent->removeChild(_hier);
}
delete _obj;
delete _animation;
}
void ModelComponent::init() {
if (_prevComp && _prevComp->isComponentType('M','M','D','L')) {
_previousCmap = _prevComp->getCMap();
}
// Skip loading if it was initialized
// by the sharing MainModelComponent
// constructor before
if (!_obj) {
CMapPtr cm = getCMap();
// Get the default colormap if we haven't found
// a valid colormap
if (!cm && g_grim->getCurrSet())
cm = g_grim->getCurrSet()->getCMap();
if (!cm) {
Debug::warning(Debug::Costumes, "No colormap specified for %s, using %s", _name.c_str(), DEFAULT_COLORMAP);
cm = g_resourceloader->getColormap(DEFAULT_COLORMAP);
}
// If we're the child of a mesh component, put our nodes in the
// parent object's tree.
if (_parent) {
MeshComponent *mc = static_cast<MeshComponent *>(_parent);
_obj = g_resourceloader->loadModel(_name, cm, mc->getModel());
_hier = _obj->getHierarchy();
mc->getNode()->addChild(_hier);
} else {
_obj = g_resourceloader->loadModel(_name, cm);
_hier = _obj->getHierarchy();
Debug::warning(Debug::Costumes, "Parent of model %s wasn't a mesh", _name.c_str());
}
// Use parent availability to decide whether to default the
// component to being visible
if (_parent)
setKey(0);
else
setKey(1);
}
if (!_animation) {
_animation = new AnimManager();
}
}
void ModelComponent::setKey(int val) {
_visible = (val != 0);
_hier->_hierVisible = _visible;
}
void ModelComponent::reset() {
_visible = false;
_hier->_hierVisible = _visible;
}
AnimManager *ModelComponent::getAnimManager() const {
return _animation;
}
int ModelComponent::update(uint time) {
// First reset the current animation.
for (int i = 0; i < getNumNodes(); i++) {
_hier[i]._animPos = _hier[i]._pos;
_hier[i]._animRot = _hier[i]._rot;
}
_animated = false;
return 0;
}
void ModelComponent::animate() {
if (_animated) {
return;
}
_animation->animate(_hier, getNumNodes());
_animated = true;
}
void ModelComponent::resetColormap() {
CMap *cm;
cm = getCMap();
if (_obj && cm)
_obj->reload(cm);
}
void ModelComponent::restoreState(SaveGame *state) {
_hier->_hierVisible = _visible;
}
int ModelComponent::getNumNodes() {
return _obj->getNumNodes();
}
void ModelComponent::translateObject(ModelNode *node, bool reset) {
if (node->_parent)
translateObject(node->_parent, reset);
if (reset) {
node->translateViewpointFinish();
} else {
node->translateViewpointStart();
node->translateViewpoint();
}
}
void ModelComponent::translateObject(bool res) {
ModelNode *node = _hier->_parent;
if (node) {
translateObject(node, res);
}
}
void ModelComponent::draw() {
// If the object was drawn by being a component
// of it's parent then don't draw it
if (_parent && _parent->isVisible())
return;
// Need to translate object to be in accordance
// with the setup of the parent
translateObject(false);
_hier->draw();
// Need to un-translate when done
translateObject(true);
}
void ModelComponent::getBoundingBox(int *x1, int *y1, int *x2, int *y2) {
// If the object was drawn by being a component
// of it's parent then don't draw it
if (_parent && _parent->isVisible())
return;
// Need to translate object to be in accordance
// with the setup of the parent
translateObject(false);
_hier->getBoundingBox(x1, y1, x2, y2);
// Need to un-translate when done
translateObject(true);
}
} // end of namespace Grim

View File

@@ -0,0 +1,64 @@
/* 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 GRIM_MODEL_COMPONENT_H
#define GRIM_MODEL_COMPONENT_H
#include "engines/grim/costume/component.h"
namespace Grim {
class Model;
class AnimManager;
class ModelComponent : public Component {
public:
ModelComponent(Component *parent, int parentID, const char *filename, Component *prevComponent, tag32 tag);
~ModelComponent();
void init();
void setKey(int val);
int update(uint time);
void animate();
void reset();
void resetColormap();
void restoreState(SaveGame *state);
void translateObject(bool reset);
static void translateObject(ModelNode *node, bool reset);
AnimManager *getAnimManager() const;
ModelNode *getHierarchy() { return _hier; }
int getNumNodes();
Model *getModel() { return _obj; }
void draw();
void getBoundingBox(int *x1, int *y1, int *x2, int *y2);
protected:
Model *_obj;
ModelNode *_hier;
AnimManager *_animation;
Component *_prevComp;
bool _animated;
};
} // end of namespace Grim
#endif

View File

@@ -0,0 +1,72 @@
/* 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 "engines/grim/grim.h"
#include "engines/grim/set.h"
#include "engines/grim/debug.h"
#include "engines/grim/costume.h"
#include "engines/grim/costume/sound_component.h"
#include "engines/grim/imuse/imuse.h"
namespace Grim {
SoundComponent::SoundComponent(Component *p, int parentID, const char *filename, tag32 t) :
Component(p, parentID, filename, t) {
const char *comma = strchr(filename, ',');
if (comma) {
_name = Common::String(filename, comma);
}
}
SoundComponent::~SoundComponent() {
// Stop the sound if it's in progress
reset();
}
void SoundComponent::setKey(int val) {
switch (val) {
case 0: // "Play"
// No longer a need to check the sound status, if it's already playing
// then it will just use the existing handle
g_imuse->startSfx(_name.c_str());
if (g_grim->getCurrSet()) {
Math::Vector3d pos = _cost->getMatrix().getPosition();
g_grim->getCurrSet()->setSoundPosition(_name.c_str(), pos);
}
break;
case 1: // "Stop"
g_imuse->stopSound(_name.c_str());
break;
case 2: // "Stop Looping"
g_imuse->setHookId(_name.c_str(), 0x80);
break;
default:
Debug::warning(Debug::Costumes, "Unknown key %d for sound %s", val, _name.c_str());
}
}
void SoundComponent::reset() {
// A lot of the sound components this gets called against aren't actually running
if (g_imuse && g_imuse->getSoundStatus(_name.c_str()))
g_imuse->stopSound(_name.c_str());
}
} // end of namespace Grim

View File

@@ -0,0 +1,40 @@
/* 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 GRIM_SOUND_COMPONENT_H
#define GRIM_SOUND_COMPONENT_H
#include "engines/grim/costume/component.h"
namespace Grim {
class SoundComponent : public Component {
public:
SoundComponent(Component *parent, int parentID, const char *name, tag32 tag);
~SoundComponent();
void setKey(int val) override;
void reset() override;
};
} // end of namespace Grim
#endif

View File

@@ -0,0 +1,111 @@
/* 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 "engines/grim/debug.h"
#include "engines/grim/savegame.h"
#include "engines/grim/model.h"
#include "engines/grim/sprite.h"
#include "engines/grim/material.h"
#include "engines/grim/resource.h"
#include "engines/grim/costume/mesh_component.h"
#include "engines/grim/costume/model_component.h"
#include "engines/grim/costume/sprite_component.h"
namespace Grim {
SpriteComponent::SpriteComponent(Component *p, int parentID, const char *filename, tag32 t) :
Component(p, parentID, filename, t), _sprite(nullptr) {
}
SpriteComponent::~SpriteComponent() {
if (_sprite) {
if (_parent) {
MeshComponent *mc = static_cast<MeshComponent *>(_parent);
if (mc) {
if (mc->getParent()->isComponentType('M','M','D','L') ||
mc->getParent()->isComponentType('M','O','D','L')) {
ModelComponent *mdlc = static_cast<ModelComponent *>(mc->getParent());
if (mdlc->getHierarchy())
mc->getNode()->removeSprite(_sprite);
}
}
}
delete _sprite->_material;
delete _sprite;
}
}
void SpriteComponent::init() {
const char *comma = strchr(_name.c_str(), ',');
Common::String name(_name.c_str(), comma);
if (_sprite) {
if (_parent) {
MeshComponent *mc = static_cast<MeshComponent *>(_parent);
mc->getNode()->removeSprite(_sprite);
}
delete _sprite;
_sprite = nullptr;
}
if (comma) {
_sprite = new Sprite();
_sprite->loadGrim(name, comma, getCMap());
if (_parent) {
if (_parent->isComponentType('M','E','S','H')) {
MeshComponent *mc = static_cast<MeshComponent *>(_parent);
mc->getNode()->addSprite(_sprite);
} else
Debug::warning(Debug::Costumes, "Parent of sprite %s wasn't a mesh", _name.c_str());
}
}
}
void SpriteComponent::setKey(int val) {
if (!_sprite)
return;
if (val == 0) {
_sprite->_visible = false;
} else {
_sprite->_visible = true;
_sprite->_material->setActiveTexture(val - 1);
}
}
void SpriteComponent::reset() {
if (_sprite)
_sprite->_visible = false;
}
void SpriteComponent::saveState(SaveGame *state) {
state->writeBool(_sprite->_visible);
state->writeLESint32(_sprite->_material->getActiveTexture());
}
void SpriteComponent::restoreState(SaveGame *state) {
_sprite->_visible = state->readBool();
_sprite->_material->setActiveTexture(state->readLESint32());
}
} // end of namespace Grim

View File

@@ -0,0 +1,48 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef GRIM_SPRITE_COMPONENT_H
#define GRIM_SPRITE_COMPONENT_H
#include "engines/grim/costume/component.h"
namespace Grim {
class Sprite;
class SpriteComponent : public Component {
public:
SpriteComponent(Component *parent, int parentID, const char *filename, tag32 tag);
~SpriteComponent();
void init() override;
void setKey(int val) override;
void reset() override;
void saveState(SaveGame *state) override;
void restoreState(SaveGame *state) override;
private:
Sprite *_sprite;
};
} // end of namespace Grim
#endif

11
engines/grim/credits.pl Normal file
View File

@@ -0,0 +1,11 @@
begin_section("Grim");
add_person("James Brown", "ender", "Grim (retired)");
add_person("Giulio Camuffo", "giucam", "Grim (retired)");
add_person("Daniel Schepler", "", "Initial engine contributor");
add_person("Dries Harnie", "Botje", "EMI");
add_person("Pawe&#322; Ko&#322;odziejski", "aquadran", "Grim");
add_person("Christian Krause", "chkr", "EMI (retired)");
add_person("Einar Johan T. S&oslash;m&aring;en", "somaen", "Grim, EMI");
add_person("Joel Teichroeb", "klusark", "EMI");
add_person("Joni V&auml;h&auml;m&auml;ki", "Akz", "EMI (retired)");
end_section();

79
engines/grim/debug.cpp Normal file
View File

@@ -0,0 +1,79 @@
/* 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/debug-channels.h"
#include "engines/grim/debug.h"
namespace Grim {
void Debug::registerDebugChannels() {
}
bool Debug::isChannelEnabled(DebugChannel chan) {
return DebugMan.isDebugChannelEnabled(chan);
}
void Debug::debug(DebugChannel channel, const char *s, ...) {
if (isChannelEnabled(channel | Debug::Info)) {
va_list args;
va_start(args, s);
Common::String buf = Common::String::vformat(s, args);
va_end(args);
::debug("%s", buf.c_str());
}
}
void Debug::warning(DebugChannel channel, const char *s, ...) {
if (isChannelEnabled(channel | Debug::Warning)) {
va_list args;
va_start(args, s);
Common::String buf = Common::String::vformat(s, args);
va_end(args);
::warning("%s", buf.c_str());
}
}
void Debug::error(DebugChannel channel, const char *s, ...) {
if (isChannelEnabled(channel | Debug::Error)) {
va_list args;
va_start(args, s);
Common::String buf = Common::String::vformat(s, args);
va_end(args);
::error("%s", buf.c_str());
}
}
void Debug::error(const char *s, ...) {
if (isChannelEnabled(Debug::Error)) {
va_list args;
va_start(args, s);
Common::String buf = Common::String::vformat(s, args);
va_end(args);
::error("%s", buf.c_str());
}
}
}

95
engines/grim/debug.h Normal file
View File

@@ -0,0 +1,95 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef GRIM_DEBUG_H
#define GRIM_DEBUG_H
#include "common/debug.h"
#include "common/streamdebug.h"
namespace Grim {
class Debug {
public:
enum DebugChannel {
Info = 1,
Warning = 2 << 0,
Error = 2 << 1,
Engine = 2 << 2,
Lua = 2 << 3,
Bitmaps = 2 << 4,
Models = 2 << 5,
Actors = 2 << 6,
Costumes = 2 << 7,
Chores = 2 << 8,
Fonts = 2 << 9,
Keyframes = 2 << 10,
Materials = 2 << 11,
Movie = 2 << 12,
Sound = 2 << 13,
Scripts = 2 << 14,
Sets = 2 << 15,
TextObjects = 2 << 16,
Patchr = 2 << 17,
Lipsync = 2 << 18,
Sprites = 2 << 19
};
static void registerDebugChannels();
static bool isChannelEnabled(DebugChannel chan);
/**
* Prints a message to the console (stdout), only if the specified debug channel
* or the channel Info are active.
*
* @param channel The debug channel to use.
*/
static void debug(DebugChannel channel, const char *s, ...);
/**
* Prints a message to the console (sterr), only if the specified debug channel
* or the channel Warning are active.
*
* @param channel The debug channel to use.
*/
static void warning(DebugChannel channel, const char *s, ...);
/**
* Prints a message to the console (stderr) and exit the program immediately,
* only if the specified debug channel or the channel Error are active.
*
* @param channel The debug channel to use.
*/
static void error(DebugChannel channel, const char *s, ...);
/**
* Prints a message to the console (stderr) and exit the program immediately,
* only if the debug channel Error is active.
*
* @param channel The debug channel to use.
*/
static void error(const char *s, ...);
};
inline Debug::DebugChannel operator|(Debug::DebugChannel a, Debug::DebugChannel b) {
return (Debug::DebugChannel)((int)a | (int) b);
}
}
#endif

137
engines/grim/debugger.cpp Normal file
View File

@@ -0,0 +1,137 @@
/* 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 "graphics/renderer.h"
#include "engines/grim/debugger.h"
#include "engines/grim/md5check.h"
#include "engines/grim/grim.h"
namespace Grim {
Debugger::Debugger() :
GUI::Debugger() {
registerCmd("check_gamedata", WRAP_METHOD(Debugger, cmd_checkFiles));
registerCmd("lua_do", WRAP_METHOD(Debugger, cmd_lua_do));
registerCmd("jump", WRAP_METHOD(Debugger, cmd_jump));
registerCmd("renderer_set", WRAP_METHOD(Debugger, cmd_renderer_set));
registerCmd("renderer_get", WRAP_METHOD(Debugger, cmd_renderer_get));
registerCmd("save", WRAP_METHOD(Debugger, cmd_save));
registerCmd("load", WRAP_METHOD(Debugger, cmd_load));
}
Debugger::~Debugger() {
}
bool Debugger::cmd_checkFiles(int argc, const char **argv) {
if (MD5Check::checkFiles()) {
debugPrintf("All files are ok.\n");
} else {
debugPrintf("Some files are corrupted or missing.\n");
}
return true;
}
bool Debugger::cmd_lua_do(int argc, const char **argv) {
if (argc < 2) {
debugPrintf("Usage: lua_do <lua command>\n");
return true;
}
Common::String cmd;
for (int i = 1; i < argc; ++i) {
cmd += argv[i];
cmd += " ";
}
cmd.deleteLastChar();
debugPrintf("Executing command: <%s>\n", cmd.c_str());
cmd = Common::String::format("__temp_fn__ = function()\n%s\nend\nstart_script(__temp_fn__)", cmd.c_str());
g_grim->debugLua(cmd);
return true;
}
bool Debugger::cmd_jump(int argc, const char **argv) {
if (argc < 2) {
debugPrintf("Usage: jump <jump target>\n");
return true;
}
// Escape from Monkey Island keeps the jump script in a separate file, so load it first
if (g_grim->getGameType() == GType_MONKEY4) {
Common::String loadJS = Common::String::format("dofile(\"_jumpscripts.lua\")\n");
g_grim->debugLua(loadJS.c_str());
}
// Start the jump_script Lua function with the desired target
Common::String cmd = Common::String::format("start_script(jump_script,\"%s\")", argv[1]);
g_grim->debugLua(cmd.c_str());
return true;
}
bool Debugger::cmd_renderer_set(int argc, const char **argv) {
if (argc < 2) {
debugPrintf("Usage: renderer_set <renderer>\n");
debugPrintf("Where <renderer> is 'software', 'opengl' or 'opengl_shaders'\n");
return true;
}
Graphics::RendererType renderer = Graphics::Renderer::parseTypeCode(argv[1]);
if (renderer == Graphics::kRendererTypeDefault) {
debugPrintf("Invalid renderer '%s'\n", argv[1]);
return true;
}
ConfMan.set("renderer", Graphics::Renderer::getTypeCode(renderer));
g_grim->changeHardwareState();
return false;
}
bool Debugger::cmd_renderer_get(int argc, const char **argv) {
auto rendererCodeStr = Graphics::Renderer::getTypeCode(g_grim->getRendererType());
debugPrintf("%s\n", rendererCodeStr.c_str());
return true;
}
bool Debugger::cmd_save(int argc, const char **argv) {
if (argc < 2) {
debugPrintf("Usage: save <save name>\n");
return true;
}
Common::String file = Common::String::format("%s.gsv", argv[1]);
g_grim->saveGame(file);
return true;
}
bool Debugger::cmd_load(int argc, const char **argv) {
if (argc < 2) {
debugPrintf("Usage: load <save name>\n");
return true;
}
Common::String file = Common::String::format("%s.gsv", argv[1]);
g_grim->loadGame(file);
return true;
}
}

47
engines/grim/debugger.h Normal file
View File

@@ -0,0 +1,47 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef GRIM_DEBUGGER_H
#define GRIM_DEBUGGER_H
#include "common/debug.h"
#include "gui/debugger.h"
namespace Grim {
class Debugger : public GUI::Debugger {
public:
Debugger();
virtual ~Debugger();
bool cmd_checkFiles(int argc, const char **argv);
bool cmd_lua_do(int argc, const char **argv);
bool cmd_jump(int argc, const char **argv);
bool cmd_renderer_get(int argc, const char **argv);
bool cmd_renderer_set(int argc, const char **argv);
bool cmd_save(int argc, const char **argv);
bool cmd_load(int argc, const char **argv);
};
}
#endif

107
engines/grim/detection.cpp Normal file
View File

@@ -0,0 +1,107 @@
/* 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 "engines/advancedDetector.h"
#include "grim/debug.h"
#include "grim/detection.h"
#include "grim/detection_tables.h"
static const DebugChannelDef debugFlagList[] = {
{Grim::Debug::Info, "info", ""},
{Grim::Debug::Warning, "warning", ""},
{Grim::Debug::Error, "error", ""},
{Grim::Debug::Engine, "engine", ""},
{Grim::Debug::Lua, "lua", ""},
{Grim::Debug::Bitmaps, "bitmaps", ""},
{Grim::Debug::Models, "models", ""},
{Grim::Debug::Actors, "actors", ""},
{Grim::Debug::Costumes, "costumes", ""},
{Grim::Debug::Chores, "chores", ""},
{Grim::Debug::Fonts, "fonts", ""},
{Grim::Debug::Keyframes, "keyframes", ""},
{Grim::Debug::Movie, "movie", ""},
{Grim::Debug::Sound, "sound", ""},
{Grim::Debug::Scripts, "scripts", ""},
{Grim::Debug::Sets, "sets", ""},
{Grim::Debug::TextObjects, "textobjects", ""},
{Grim::Debug::Patchr, "patchr", ""},
{Grim::Debug::Lipsync, "lipsync", ""},
{Grim::Debug::Sprites, "sprites", ""},
DEBUG_CHANNEL_END
};
namespace Grim {
static const PlainGameDescriptor grimGames[] = {
{"grim", "Grim Fandango"},
{"monkey4", "Escape From Monkey Island"},
{nullptr, nullptr}
};
class GrimMetaEngineDetection : public AdvancedMetaEngineDetection<Grim::GrimGameDescription> {
public:
GrimMetaEngineDetection() : AdvancedMetaEngineDetection(Grim::gameDescriptions, grimGames) {
_guiOptions = GUIO_NOMIDI;
_flags |= kADFlagCanTranscodeTraditionalChineseToSimplified;
}
PlainGameDescriptor findGame(const char *gameid) const override {
return Engines::findGameID(gameid, _gameIds, obsoleteGameIDsTable);
}
Common::Error identifyGame(DetectedGame &game, const void **descriptor) override {
Engines::upgradeTargetIfNecessary(obsoleteGameIDsTable);
return AdvancedMetaEngineDetection::identifyGame(game, descriptor);
}
const char *getEngineName() const override {
return "Grim";
}
const char *getName() const override {
return "grim";
}
const char *getOriginalCopyright() const override {
return "LucasArts GrimE Games (C) LucasArts";
}
const DebugChannelDef *getDebugChannels() const override {
return debugFlagList;
}
DetectedGame toDetectedGame(const ADDetectedGame &adGame, ADDetectedGameExtraInfo *extraInfo) const override {
DetectedGame game = AdvancedMetaEngineDetection::toDetectedGame(adGame, extraInfo);
GrimGameType gameID = reinterpret_cast<const GrimGameDescription *>(adGame.desc)->gameType;
if (gameID == GType_MONKEY4 && adGame.desc->language == Common::Language::ZH_TWN) {
game.appendGUIOptions(Common::getGameGUIOptionsDescriptionLanguage(Common::ZH_TWN));
game.appendGUIOptions(Common::getGameGUIOptionsDescriptionLanguage(Common::ZH_CHN));
}
return game;
}
};
} // End of namespace Grim
REGISTER_PLUGIN_STATIC(GRIM_DETECTION, PLUGIN_TYPE_ENGINE_DETECTION, Grim::GrimMetaEngineDetection);

54
engines/grim/detection.h Normal file
View File

@@ -0,0 +1,54 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef GRIM_DETECTION_H
#define GRIM_DETECTION_H
#include "engines/advancedDetector.h"
#include "engines/obsolete.h"
static const Engines::ObsoleteGameID obsoleteGameIDsTable[] = {
{"grimdemo", "grim", Common::kPlatformWindows},
{nullptr, nullptr, Common::kPlatformUnknown}
};
namespace Grim {
enum GrimGameType {
GType_GRIM,
GType_MONKEY4
};
struct GrimGameDescription {
AD_GAME_DESCRIPTION_HELPERS(desc);
ADGameDescription desc;
GrimGameType gameType;
};
#define GAMEOPTION_LOAD_DATAUSR GUIO_GAMEOPTIONS1
#define GAMEOPTION_SHOW_FPS GUIO_GAMEOPTIONS2
#define GUI_OPTIONS_GRIME GUIO2(GAMEOPTION_LOAD_DATAUSR, GAMEOPTION_SHOW_FPS)
} // End of namespace Grim
#endif // GRIM_DETECTION_H

View File

@@ -0,0 +1,727 @@
/* 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 GRIM_DETECTION_TABLES_H
#define GRIM_DETECTION_TABLES_H
#include "grim/detection.h"
namespace Grim {
static const GrimGameDescription gameDescriptions[] = {
{
// Grim Fandango English version (patched)
{
"grim",
"",
AD_ENTRY1s("VOX0001.LAB", "444f05f2af689c1bffd179b8b6a632bd", 57993159),
Common::EN_ANY,
Common::kPlatformWindows,
ADGF_NO_FLAGS,
GUI_OPTIONS_GRIME
},
GType_GRIM
},
{
// Grim Fandango English version (unpatched)
{
"grim",
"",
AD_ENTRY1s("VOX0001.LAB", "8b12ed530195c6c577436df27df62ecb", 58011176),
Common::EN_ANY,
Common::kPlatformWindows,
ADGF_NO_FLAGS,
GUI_OPTIONS_GRIME
},
GType_GRIM
},
{
// Grim Fandango English version (unpatched) + Chinese Fan translation
{
"grim",
"",
AD_ENTRY2s("VOX0001.LAB", "8b12ed530195c6c577436df27df62ecb", 58011176,
"GRIM.TAB", "613d5c80480229837a14829cbc1e1d22", 273523),
Common::ZH_CHN,
Common::kPlatformWindows,
ADGF_NO_FLAGS,
GUI_OPTIONS_GRIME
},
GType_GRIM
},
{
// Grim Fandango English version (unpatched) + Polish Fan translation
{
"grim",
"Fanmade",
AD_ENTRY2s("VOX0001.LAB", "8b12ed530195c6c577436df27df62ecb", 58011176,
"GRIM.TAB", "2b99efd92f782b791a464b1b8a16187a", 351510),
Common::PL_POL,
Common::kPlatformWindows,
ADGF_NO_FLAGS,
GUI_OPTIONS_GRIME
},
GType_GRIM
},
{
// Grim Fandango English version (patched) + Polish Fan translation
{
"grim",
"Fanmade",
AD_ENTRY2s("VOX0001.LAB", "444f05f2af689c1bffd179b8b6a632bd", 57993159,
"GRIM.TAB", "2b99efd92f782b791a464b1b8a16187a", 351510),
Common::PL_POL,
Common::kPlatformWindows,
ADGF_NO_FLAGS,
GUI_OPTIONS_GRIME
},
GType_GRIM
},
{
// Grim Fandango English version (unpatched) + Russian Enpy translation
{
"grim",
"ENPY",
AD_ENTRY2s("VOX0001.LAB", "8b12ed530195c6c577436df27df62ecb", 58011176,
"rus_font.lab", "df658c6a491a831d47a58eb21ccc7126", 162443),
Common::RU_RUS,
Common::kPlatformWindows,
ADGF_NO_FLAGS,
GUI_OPTIONS_GRIME
},
GType_GRIM
},
{
// Grim Fandango French version (un/patched ???)
{
"grim",
"",
AD_ENTRY1s("VOX0001.LAB", "19bc0dc9554257b1f021463de54f359f", 56268691),
Common::FR_FRA,
Common::kPlatformWindows,
ADGF_NO_FLAGS,
GUI_OPTIONS_GRIME
},
GType_GRIM
},
{
// Grim Fandango Russian (7Wolf) version
{
"grim",
"7Wolf",
AD_ENTRY1s("VOX0001.LAB", "b517dbb493679b1679036de1a1bfc8a5", 57180788),
Common::RU_RUS,
Common::kPlatformWindows,
ADGF_NO_FLAGS,
GUI_OPTIONS_GRIME
},
GType_GRIM
},
{
// Grim Fandango Russian (Fargus) version
{
"grim",
"Fargus",
AD_ENTRY1s("VOX0001.LAB", "a38708ba97960ee7ee59218489a485eb", 44691160),
Common::RU_RUS,
Common::kPlatformWindows,
ADGF_NO_FLAGS,
GUI_OPTIONS_GRIME
},
GType_GRIM
},
{
// Grim Fandango Portuguese version
{
"grim",
"",
AD_ENTRY1s("VOX0001.LAB", "89da4d4f4f90a8ea390450ed5a617f08", 57875710),
Common::PT_BRA,
Common::kPlatformWindows,
ADGF_NO_FLAGS,
GUI_OPTIONS_GRIME
},
GType_GRIM
},
{
// Grim Fandango Italian version
{
"grim",
"",
AD_ENTRY1s("VOX0001.LAB", "9e7075f3fb0427ae8136b290538d07dd", 62185775),
Common::IT_ITA,
Common::kPlatformWindows,
ADGF_NO_FLAGS,
GUI_OPTIONS_GRIME
},
GType_GRIM
},
{
// Grim Fandango Spanish version (patched)
{
"grim",
"",
AD_ENTRY1s("VOX0001.LAB", "85d3e9504c481c5ccf2119ea6e0f4e2f", 53831340),
Common::ES_ESP,
Common::kPlatformWindows,
ADGF_NO_FLAGS,
GUI_OPTIONS_GRIME
},
GType_GRIM
},
{
// Grim Fandango German version
{
"grim",
"",
AD_ENTRY1s("VOX0001.LAB", "d304aa402098de5966816c0a11e45816", 66829347),
Common::DE_DEU,
Common::kPlatformWindows,
ADGF_NO_FLAGS,
GUI_OPTIONS_GRIME
},
GType_GRIM
},
{
// Grim Fandango German version (CD protected) - unsupported
{
"grim",
"",
AD_ENTRY1s("VOX0001.LAB", "88e8e13a8164c0df62b1f2f4e9ab4583", 388753408),
Common::DE_DEU,
Common::kPlatformWindows,
ADGF_UNSUPPORTED,
GUI_OPTIONS_GRIME
},
GType_GRIM
},
{
// Grim Fandango Hebrew Fan translation (patched)
{
"grim",
"",
AD_ENTRY2s("LOCAL.LAB", "d22648d6787c2f8f0a789ee3ed0c08f7", 608,
"VOX0001.LAB", "444f05f2af689c1bffd179b8b6a632bd", 57993159),
Common::HE_ISR,
Common::kPlatformWindows,
ADGF_NO_FLAGS,
GUI_OPTIONS_GRIME
},
GType_GRIM
},
{
// Grim Fandango Korean Fan translation (patched)
{
"grim",
"",
AD_ENTRY2s("VOX0001.LAB", "444f05f2af689c1bffd179b8b6a632bd", 57993159,
"grim.ko.tab", NULL, AD_NO_SIZE),
Common::KO_KOR,
Common::kPlatformWindows,
ADGF_NO_FLAGS,
GUI_OPTIONS_GRIME
},
GType_GRIM
},
/* {
// Grim Fandango German version (patched)
{
"grim",
"",
AD_ENTRY1s("grim.tab", "464138caf47e580cbb237dee10674b16", 398671),
Common::DE_DEU,
Common::kPlatformWindows,
ADGF_NO_FLAGS,
GUI_OPTIONS_GRIME
},
GType_GRIM
},
{
// Grim Fandango Spanish version
{
"grim",
"",
AD_ENTRY1s("grim.tab", "b1460cd029f13718f7f62c2403e047ec", 372709),
Common::ES_ESP,
Common::kPlatformWindows,
ADGF_NO_FLAGS,
GUI_OPTIONS_GRIME
},
GType_GRIM
},
{
// Grim Fandango Spanish version (patched)
{
"grim",
"",
AD_ENTRY1s("grim.tab", "b1460cd029f13718f7f62c2403e047ec", 372020),
Common::ES_ESP,
Common::kPlatformWindows,
ADGF_NO_FLAGS,
GUI_OPTIONS_GRIME
},
GType_GRIM
},
{
// Grim Fandango Italian version (patched)
{
"grim",
"",
AD_ENTRY1s("grim.tab", "2d99c796b7a4e5c421cae49dc29dab6c", 369242),
Common::IT_ITA,
Common::kPlatformWindows,
ADGF_NO_FLAGS,
GUI_OPTIONS_GRIME
},
GType_GRIM
},
{
// Grim Fandango French version (patched)
{
"grim",
"",
AD_ENTRY1s("grim.tab", "3bd00ca87214862c012ac99e1758dd83", 386292),
Common::FR_FRA,
Common::kPlatformWindows,
ADGF_NO_FLAGS,
GUI_OPTIONS_GRIME
},
GType_GRIM
},
{
// Grim Fandango Portuguese version (patched)
{
"grim",
"",
AD_ENTRY1s("grim.tab", "4dc16be476bb6036b423bc331ca8281a", 362994),
Common::PT_BRA,
Common::kPlatformWindows,
ADGF_NO_FLAGS,
GUI_OPTIONS_GRIME
},
GType_GRIM
},
*/ {
// Grim Fandango English demo version
{
"grim",
"Demo",
AD_ENTRY1s("gfdemo01.lab", "755cdac083f7f751bec7506402278f1a", 29489930),
Common::EN_ANY,
Common::kPlatformWindows,
ADGF_DEMO,
GUI_OPTIONS_GRIME
},
GType_GRIM
},
{
// Grim Fandango English demo version (with intro video)
{
"grim",
"Demo",
AD_ENTRY1s("gdemo001.lab", "c04c814093be829c4811a3a0aa80833d", 46615911),
Common::EN_ANY,
Common::kPlatformWindows,
ADGF_DEMO,
GUI_OPTIONS_GRIME
},
GType_GRIM
},
{
// Grim Fandango French demo version
{
"grim",
"Demo",
AD_ENTRY2s("gfdemo01.lab", "7df813f3809f2c0234213cfa4f6da062", 29533695,
"voice001.lab", "7df474e03c23692ed02e4ce45f1a6b30", 13764168),
Common::FR_FRA,
Common::kPlatformWindows,
ADGF_DEMO,
GUI_OPTIONS_GRIME
},
GType_GRIM
},
{
// Grim Fandango German demo version
{
"grim",
"Demo",
AD_ENTRY2s("gfdemo01.lab", "7df813f3809f2c0234213cfa4f6da062", 29533695,
"voice001.lab", "2788dc7fd226787f3a68ac9c853d2580", 16561196),
Common::DE_DEU,
Common::kPlatformWindows,
ADGF_DEMO,
GUI_OPTIONS_GRIME
},
GType_GRIM
},
{
// Grim Fandango Italian demo version
{
"grim",
"Demo",
AD_ENTRY2s("gfdemo01.lab", "7df813f3809f2c0234213cfa4f6da062", 29533695,
"voice001.lab", "3b8ace62584380c66b73981e014ea40e", 14907410),
Common::IT_ITA,
Common::kPlatformWindows,
ADGF_DEMO,
GUI_OPTIONS_GRIME
},
GType_GRIM
},
{
// Grim Fandango Spanish demo version
{
"grim",
"Demo",
AD_ENTRY2s("gfdemo01.lab", "7df813f3809f2c0234213cfa4f6da062", 29533695,
"voice001.lab", "a810ec11acaf9d76cd04d2f68fcdc912", 13367206),
Common::ES_ESP,
Common::kPlatformWindows,
ADGF_DEMO,
GUI_OPTIONS_GRIME
},
GType_GRIM
},
{
// Grim Fandango Remastered
{
"grim",
"Remastered",
AD_ENTRY1s("VOX0001.LAB", "0ff872fb353707fbdb9579038d4cf31c", 382736476),
Common::EN_ANY,
Common::kPlatformWindows,
ADGF_REMASTERED,
GUI_OPTIONS_GRIME
},
GType_GRIM
},
{
// Escape from Monkey Island English
{
"monkey4",
"",
AD_ENTRY1s("artAll.m4b", "61959da91d864bf5f4588daa4a5a3019", 18515664),
Common::EN_ANY,
Common::kPlatformWindows,
ADGF_UNSTABLE,
GUI_OPTIONS_GRIME
},
GType_MONKEY4
},
{
// Escape from Monkey Island Chinese
{
"monkey4",
"",
AD_ENTRY2s("artAll.m4b", "61959da91d864bf5f4588daa4a5a3019", 18515664,
"Script.tab", "ee08a95b6820f7b876940f6cd41dbae7", 618346),
Common::ZH_TWN,
Common::kPlatformWindows,
ADGF_UNSTABLE,
GUI_OPTIONS_GRIME
},
GType_MONKEY4
},
{
// Escape from Monkey Island German
{
"monkey4",
"",
AD_ENTRY1s("artAll.m4b", "007a33881478be6b6e0228d8888536ae", 18512568),
Common::DE_DEU,
Common::kPlatformWindows,
ADGF_UNSTABLE,
GUI_OPTIONS_GRIME
},
GType_MONKEY4
},
{
// Escape from Monkey Island Italian
{
"monkey4",
"",
AD_ENTRY1s("artAll.m4b", "d2f010c1cd1fd002eea403282a6b9a1e", 18513451),
Common::IT_ITA,
Common::kPlatformWindows,
ADGF_UNSTABLE,
GUI_OPTIONS_GRIME
},
GType_MONKEY4
},
{
// Escape from Monkey Island Spanish
{
"monkey4",
"",
AD_ENTRY1s("artAll.m4b", "0d459954031c086a0448d2eb3fa068a1", 18514404),
Common::ES_ESP,
Common::kPlatformWindows,
ADGF_UNSTABLE,
GUI_OPTIONS_GRIME
},
GType_MONKEY4
},
{
// Escape from Monkey Island French
{
"monkey4",
"",
AD_ENTRY1s("artAll.m4b", "151af0a694382af873f325fcea293bb1", 18514420),
Common::FR_FRA,
Common::kPlatformWindows,
ADGF_UNSTABLE,
GUI_OPTIONS_GRIME
},
GType_MONKEY4
},
{
// Escape from Monkey Island Portuguese
{
"monkey4",
"",
AD_ENTRY1s("artAll.m4b", "030e7637aee7886a3caad60cf102f797", 18515747),
Common::PT_BRA,
Common::kPlatformWindows,
ADGF_UNSTABLE,
GUI_OPTIONS_GRIME
},
GType_MONKEY4
},
{
// Escape from Monkey Island Russian
{
"monkey4",
"",
AD_ENTRY1s("artAll.m4b", "779561a70a11dd5686974f122fc1516c", 18500052),
Common::RU_RUS,
Common::kPlatformWindows,
ADGF_UNSTABLE,
GUI_OPTIONS_GRIME
},
GType_MONKEY4
},
{
// Escape from Monkey Island Polish fanmade
{
"monkey4",
"Fanmade",
AD_ENTRY1s("artAll.m4b", "451332012f4d947ef12da4f7d7f0e560", 18751712),
Common::PL_POL,
Common::kPlatformWindows,
ADGF_UNSTABLE,
GUI_OPTIONS_GRIME
},
GType_MONKEY4
},
{
// Escape from Monkey Island English (Mac)
{
"monkey4",
"",
AD_ENTRY2s("artAll.m4b", "61959da91d864bf5f4588daa4a5a3019", 18515664,
"Monkey Island 4 Installer", "r:8230927789935674546c4b3f9b1368ea", 560139),
Common::EN_ANY,
Common::kPlatformMacintosh,
ADGF_UNSTABLE,
GUI_OPTIONS_GRIME
},
GType_MONKEY4
},
{
// Escape from Monkey Island German (Mac)
{
"monkey4",
"",
AD_ENTRY2s("artAll.m4b", "007a33881478be6b6e0228d8888536ae", 18512568,
"EFMI Installer", "54298c7440dafedf33d2b27c7bb24052", 9241784),
Common::DE_DEU,
Common::kPlatformMacintosh,
ADGF_UNSTABLE,
GUI_OPTIONS_GRIME
},
GType_MONKEY4
},
{
// Escape from Monkey Island French (Mac)
{
"monkey4",
"",
AD_ENTRY2s("artAll.m4b", "151af0a694382af873f325fcea293bb1", 18514420,
"Installation de Monkey Island 4", "8b22e2fadb4e72e8041c7bd896f1759d", 9242911),
Common::FR_FRA,
Common::kPlatformMacintosh,
ADGF_UNSTABLE,
GUI_OPTIONS_GRIME
},
GType_MONKEY4
},
{
// Escape from Monkey Island English PS2
{
"monkey4",
"",
AD_ENTRY1s("artAll.m4b", "0dc9a4df0d8553f277d8dc8e23b6249d", 34593974),
Common::EN_ANY,
Common::kPlatformPS2,
ADGF_UNSTABLE,
GUI_OPTIONS_GRIME
},
GType_MONKEY4
},
{
// Escape from Monkey Island German PS2
{
"monkey4",
"",
AD_ENTRY1s("artAll.m4b", "5b5c7a3964c168eab44b82981db357d8", 34642186),
Common::DE_DEU,
Common::kPlatformPS2,
ADGF_UNSTABLE,
GUI_OPTIONS_GRIME
},
GType_MONKEY4
},
{
// Escape from Monkey Island Italian PS2
{
"monkey4",
"",
AD_ENTRY1s("artAll.m4b", "2de68c8fd955c1a3c50202b072bde0cb", 34642651),
Common::IT_ITA,
Common::kPlatformPS2,
ADGF_UNSTABLE,
GUI_OPTIONS_GRIME
},
GType_MONKEY4
},
{
// Escape from Monkey Island Spanish PS2
{
"monkey4",
"",
AD_ENTRY1s("artAll.m4b", "ff6689dcca36c249ec834a3019aeb397", 34642656),
Common::ES_ESP,
Common::kPlatformPS2,
ADGF_UNSTABLE,
GUI_OPTIONS_GRIME
},
GType_MONKEY4
},
{
// Escape from Monkey Island French PS2
{
"monkey4",
"",
AD_ENTRY1s("artAll.m4b", "5ce964a19a8672944b9b62170e45ce28", 34593681),
Common::FR_FRA,
Common::kPlatformPS2,
ADGF_UNSTABLE,
GUI_OPTIONS_GRIME
},
GType_MONKEY4
},
{
// Escape from Monkey Island CD demo (English)
{
"monkey4",
"CD Demo",
AD_ENTRY2s("magdemo.lab", "9e7eaa1b9317ff47d5deeda0b2c42ce3", 19826116,
"monkey4.exe", "119ee6b9380cc0e373287fabb25e0578", 884736),
Common::EN_ANY,
Common::kPlatformWindows,
ADGF_DEMO | ADGF_UNSTABLE,
GUI_OPTIONS_GRIME
},
GType_MONKEY4
},
{
// Escape from Monkey Island web demo (English)
{
"monkey4",
"Web Demo",
AD_ENTRY2s("magdemo.lab", "9e7eaa1b9317ff47d5deeda0b2c42ce3", 19826116,
"i9n.lab", "274f8579b01e0872fe6f1ba267266149", 26951),
Common::EN_ANY,
Common::kPlatformWindows,
ADGF_DEMO | ADGF_UNSTABLE,
GUI_OPTIONS_GRIME
},
GType_MONKEY4
},
{
// Escape from Monkey Island demo (French)
{
"monkey4",
"Demo",
AD_ENTRY2s("magdemo.lab", "9e7eaa1b9317ff47d5deeda0b2c42ce3", 19826116,
"i9n.lab", "7f1744990472261bdcbc02036ba9f7ec", 1718385),
Common::FR_FRA,
Common::kPlatformWindows,
ADGF_DEMO | ADGF_UNSTABLE,
GUI_OPTIONS_GRIME
},
GType_MONKEY4
},
{
// Escape from Monkey Island demo (German)
{
"monkey4",
"Demo",
AD_ENTRY2s("magdemo.lab", "9e7eaa1b9317ff47d5deeda0b2c42ce3", 19826116,
"i9n.lab", "28f6bc270b5c31970cc110c7656ff598", 1749051),
Common::DE_DEU,
Common::kPlatformWindows,
ADGF_DEMO | ADGF_UNSTABLE,
GUI_OPTIONS_GRIME
},
GType_MONKEY4
},
{
// Escape from Monkey Island demo (Spanish)
{
"monkey4",
"Demo",
AD_ENTRY2s("magdemo.lab", "9e7eaa1b9317ff47d5deeda0b2c42ce3", 19826116,
"i9n.lab", "53b20d930f6e8c2e0880ed7e336eeebc", 1740761),
Common::ES_ESP,
Common::kPlatformWindows,
ADGF_DEMO | ADGF_UNSTABLE,
GUI_OPTIONS_GRIME
},
GType_MONKEY4
},
{ AD_TABLE_END_MARKER, GType_GRIM }
};
} // End of namespace Grim
#endif

View File

@@ -0,0 +1,380 @@
/* 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/stream.h"
#include "common/textconsole.h"
#include "math/vector3d.h"
#include "math/quat.h"
#include "engines/grim/resource.h"
#include "engines/grim/emi/animationemi.h"
namespace Grim {
AnimationEmi::AnimationEmi(const Common::String &filename, Common::SeekableReadStream *data) :
_name(""), _duration(0.0f), _numBones(0), _bones(nullptr) {
_fname = filename;
loadAnimation(data);
}
// Use modelemi's solution for the LA-strings.
void AnimationEmi::loadAnimation(Common::SeekableReadStream *data) {
int len = data->readUint32LE();
char *inString = new char[len];
data->read(inString, len);
_name = inString;
delete[] inString;
_duration = 1000 * data->readFloatLE();
_numBones = data->readUint32LE();
_bones = new Bone[_numBones];
for (int i = 0; i < _numBones; i++) {
_bones[i].loadBinary(data);
}
}
AnimationEmi::~AnimationEmi() {
g_resourceloader->uncacheAnimationEmi(this);
delete[] _bones;
}
void Bone::loadBinary(Common::SeekableReadStream *data) {
uint32 len = data->readUint32LE();
char *inString = new char[len];
data->read(inString, len);
_boneName = inString;
delete[] inString;
_operation = data->readUint32LE();
_priority = data->readUint32LE();
_c = data->readUint32LE();
_count = data->readUint32LE();
if (_operation == 3) { // Translation
_translations = new AnimTranslation[_count];
for (int j = 0; j < _count; j++) {
_translations[j]._vec.readFromStream(data);
_translations[j]._time = 1000 * data->readFloatLE();
}
} else if (_operation == 4) { // Rotation
_rotations = new AnimRotation[_count];
for (int j = 0; j < _count; j++) {
_rotations[j]._quat.readFromStream(data);
_rotations[j]._time = 1000 * data->readFloatLE();
}
} else {
error("Unknown animation-operation %d", _operation);
}
}
Bone::~Bone() {
if (_operation == 3) {
delete[] _translations;
} else if (_operation == 4) {
delete[] _rotations;
}
}
AnimationStateEmi::AnimationStateEmi(const Common::String &anim) :
_skel(nullptr), _looping(false), _active(false), _paused(false),
_fadeMode(Animation::None), _fade(1.0f), _fadeLength(0), _time(-1), _startFade(1.0f),
_boneJoints(nullptr) {
_anim = g_resourceloader->getAnimationEmi(anim);
if (_anim)
_boneJoints = new int[_anim->_numBones];
}
AnimationStateEmi::~AnimationStateEmi() {
deactivate();
delete[] _boneJoints;
}
void AnimationStateEmi::activate() {
if (!_active) {
_active = true;
if (_skel)
_skel->addAnimation(this);
}
}
void AnimationStateEmi::deactivate() {
if (_active) {
_active = false;
if (_skel)
_skel->removeAnimation(this);
}
}
void AnimationStateEmi::update(uint time) {
if (!_active)
return;
if (!_anim) {
deactivate();
return;
}
if (!_paused) {
int durationMs = (int)_anim->_duration;
if (_time >= durationMs) {
if (_looping) {
_time = _time % durationMs;
} else {
if (_fadeMode != Animation::FadeOut)
deactivate();
}
}
if (_time < 0) {
_time = 0;
} else {
_time += time;
}
}
if (_fadeMode != Animation::None) {
if (_fadeMode == Animation::FadeIn) {
_fade += (float)time * (1.0f - _startFade) / _fadeLength;
if (_fade >= 1.f) {
_fade = 1.f;
_fadeMode = Animation::None;
}
} else {
_fade -= (float)time * _startFade / _fadeLength;
if (_fade <= 0.f) {
_fade = 0.f;
// Don't reset the _fadeMode here. This way if fadeOut() was called
// on a looping chore its keyframe animations will remain faded out
// when it calls play() again.
deactivate();
return;
}
}
}
}
void AnimationStateEmi::computeWeights() {
if (_fade <= 0.0f)
return;
for (int bone = 0; bone < _anim->_numBones; ++bone) {
Bone &curBone = _anim->_bones[bone];
int jointIndex = _boneJoints[bone];
if (jointIndex == -1)
continue;
AnimationLayer *layer = _skel->getLayer(curBone._priority);
JointAnimation &jointAnim = layer->_jointAnims[jointIndex];
if (curBone._rotations) {
jointAnim._rotWeight += _fade;
}
if (curBone._translations) {
jointAnim._transWeight += _fade;
}
}
}
void AnimationStateEmi::animate() {
if (_fade <= 0.0f)
return;
if (_time < 0)
return;
for (int bone = 0; bone < _anim->_numBones; ++bone) {
Bone &curBone = _anim->_bones[bone];
int jointIndex = _boneJoints[bone];
if (jointIndex == -1)
continue;
Joint *target = &_skel->_joints[jointIndex];
AnimationLayer *layer = _skel->getLayer(curBone._priority);
JointAnimation &jointAnim = layer->_jointAnims[jointIndex];
if (curBone._rotations) {
int keyfIdx = -1;
Math::Quaternion quat;
// Normalize the weight so that the sum of applied weights will equal 1.
float normalizedRotWeight = _fade;
if (jointAnim._rotWeight > 1.0f) {
// Note: Division by unnormalized sum of weights.
normalizedRotWeight = _fade / jointAnim._rotWeight;
}
for (int curKeyFrame = 0; curKeyFrame < curBone._count; curKeyFrame++) {
if (curBone._rotations[curKeyFrame]._time >= _time) {
keyfIdx = curKeyFrame;
break;
}
}
if (keyfIdx == 0) {
quat = curBone._rotations[0]._quat;
}
else if (keyfIdx != -1) {
float timeDelta = curBone._rotations[keyfIdx]._time - curBone._rotations[keyfIdx - 1]._time;
float interpVal = (_time - curBone._rotations[keyfIdx - 1]._time) / timeDelta;
quat = curBone._rotations[keyfIdx - 1]._quat.slerpQuat(curBone._rotations[keyfIdx]._quat, interpVal);
}
else {
quat = curBone._rotations[curBone._count - 1]._quat;
}
Math::Quaternion &quatFinal = jointAnim._quat;
quat = target->_quat.inverse() * quat;
quat = quatFinal * quat;
quatFinal = quatFinal.slerpQuat(quat, normalizedRotWeight);
}
if (curBone._translations) {
int keyfIdx = -1;
Math::Vector3d vec;
// Normalize the weight so that the sum of applied weights will equal 1.
float normalizedTransWeight = _fade;
if (jointAnim._transWeight > 1.0f) {
// Note: Division by unnormalized sum of weights.
normalizedTransWeight = _fade / jointAnim._transWeight;
}
for (int curKeyFrame = 0; curKeyFrame < curBone._count; curKeyFrame++) {
if (curBone._translations[curKeyFrame]._time >= _time) {
keyfIdx = curKeyFrame;
break;
}
}
if (keyfIdx == 0) {
vec = curBone._translations[0]._vec;
}
else if (keyfIdx != -1) {
float timeDelta = curBone._translations[keyfIdx]._time - curBone._translations[keyfIdx - 1]._time;
float interpVal = (_time - curBone._translations[keyfIdx - 1]._time) / timeDelta;
vec = curBone._translations[keyfIdx - 1]._vec +
(curBone._translations[keyfIdx]._vec - curBone._translations[keyfIdx - 1]._vec) * interpVal;
}
else {
vec = curBone._translations[curBone._count - 1]._vec;
}
Math::Vector3d &posFinal = jointAnim._pos;
vec = vec - target->_relMatrix.getPosition();
posFinal = posFinal + vec * normalizedTransWeight;
}
}
}
void AnimationStateEmi::play() {
if (!_active) {
_time = -1;
if (_fadeMode == Animation::FadeOut)
_fadeMode = Animation::None;
if (_fadeMode == Animation::FadeIn || _fade > 0.f)
activate();
}
_paused = false;
}
void AnimationStateEmi::stop() {
_fadeMode = Animation::None;
_time = -1;
deactivate();
}
void AnimationStateEmi::setPaused(bool paused) {
_paused = paused;
}
void AnimationStateEmi::setLooping(bool loop) {
_looping = loop;
}
void AnimationStateEmi::setSkeleton(Skeleton *skel) {
if (skel != _skel) {
if (_skel)
_skel->removeAnimation(this);
_skel = skel;
if (_active)
skel->addAnimation(this);
if (_anim) {
for (int i = 0; i < _anim->_numBones; ++i) {
_boneJoints[i] = skel->findJointIndex(_anim->_bones[i]._boneName);
}
}
}
}
void AnimationStateEmi::fade(Animation::FadeMode mode, int fadeLength) {
if (mode == Animation::None) {
_fade = 1.f;
} else if (_fadeMode != Animation::FadeOut && mode == Animation::FadeIn) {
_fade = 0.f;
}
_startFade = _fade;
_fadeMode = mode;
_fadeLength = fadeLength;
}
void AnimationStateEmi::advance(uint msecs) {
if (_time >= 0) {
_time += msecs;
} else {
_time = msecs;
}
}
void AnimationStateEmi::saveState(SaveGame *state) {
state->writeBool(_looping);
state->writeBool(_active);
state->writeBool(_paused);
state->writeLESint32(_time);
state->writeFloat(_fade);
state->writeFloat(_startFade);
state->writeLESint32((int)_fadeMode);
state->writeLESint32(_fadeLength);
}
void AnimationStateEmi::restoreState(SaveGame *state) {
if (state->saveMinorVersion() >= 10) {
_looping = state->readBool();
bool active = state->readBool();
_paused = state->readBool();
if (state->saveMinorVersion() < 22) {
_time = (uint)state->readFloat();
} else {
_time = state->readLESint32();
}
_fade = state->readFloat();
_startFade = state->readFloat();
_fadeMode = (Animation::FadeMode)state->readLESint32();
_fadeLength = state->readLESint32();
if (active)
activate();
}
}
} // end of namespace Grim

View File

@@ -0,0 +1,113 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef GRIM_ANIMATIONEMI_H
#define GRIM_ANIMATIONEMI_H
#include "common/str.h"
#include "math/mathfwd.h"
#include "math/quat.h"
#include "engines/grim/animation.h"
#include "engines/grim/object.h"
#include "engines/grim/emi/skeleton.h"
namespace Grim {
struct AnimRotation {
Math::Quaternion _quat;
float _time;
};
struct AnimTranslation {
Math::Vector3d _vec;
float _time;
};
struct Bone {
Common::String _boneName;
int _operation;
int _priority;
int _c;
int _count;
AnimRotation *_rotations;
AnimTranslation *_translations;
Joint *_target;
Bone() : _rotations(NULL), _translations(NULL), _boneName(""), _operation(0), _target(NULL) {}
~Bone();
void loadBinary(Common::SeekableReadStream *data);
};
class AnimationEmi : public Object {
void loadAnimation(Common::SeekableReadStream *data);
public:
Common::String _name;
Common::String _fname;
float _duration;
int _numBones;
Bone *_bones;
AnimationEmi(const Common::String &filename, Common::SeekableReadStream *data);
~AnimationEmi();
const Common::String &getFilename() const { return _fname; }
};
class AnimationStateEmi {
public:
AnimationStateEmi(const Common::String &anim);
~AnimationStateEmi();
void update(uint time);
void computeWeights();
void animate();
void play();
void stop();
void setPaused(bool paused);
void setLooping(bool loop);
void setSkeleton(Skeleton *skel);
void fade(Animation::FadeMode mode, int fadeLength);
void advance(uint msecs);
void saveState(SaveGame *state);
void restoreState(SaveGame *state);
private:
void activate();
void deactivate();
friend class Skeleton;
Skeleton *_skel;
ObjectPtr<AnimationEmi> _anim;
bool _looping;
bool _active;
bool _paused;
int _time;
float _fade;
float _startFade;
Animation::FadeMode _fadeMode;
int _fadeLength;
int *_boneJoints;
};
} // end of namespace Grim
#endif

View File

@@ -0,0 +1,138 @@
/* 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 "engines/grim/debug.h"
#include "engines/grim/emi/costume/emianim_component.h"
#include "engines/grim/emi/costume/emiskel_component.h"
#include "engines/grim/resource.h"
#include "engines/grim/costume.h"
#include "engines/grim/emi/costumeemi.h"
#include "engines/grim/emi/modelemi.h"
#include "engines/grim/emi/skeleton.h"
#include "engines/grim/emi/animationemi.h"
namespace Grim {
EMIAnimComponent::EMIAnimComponent(Component *p, int parentID, const char *filename, Component *prevComponent, tag32 t) :
Component(p, parentID, filename, t), _animState(nullptr) {
}
EMIAnimComponent::~EMIAnimComponent() {
delete _animState;
}
void EMIAnimComponent::init() {
_visible = true;
_animState = new AnimationStateEmi(_name);
}
int EMIAnimComponent::update(uint time) {
EMISkelComponent *skel = ((EMICostume *)_cost)->_emiSkel;
if (skel) {
_animState->setSkeleton(skel->_obj);
_animState->update(time);
}
return 0;
}
void EMIAnimComponent::setKey(int f) {
switch (f) {
case 0: // Stop
_animState->stop();
break;
case 1: // Play
_animState->play();
break;
case 2: // Pause
_animState->setPaused(true);
break;
case 3: // Loop
_animState->setLooping(true);
_animState->play();
break;
case 4: // No loop
_animState->setLooping(false);
break;
case 5: // Fade in 1
_animState->fade(Animation::FadeIn, 1000);
break;
case 6: // Fade in 3/4
_animState->fade(Animation::FadeIn, 750);
break;
case 7: // Fade in 1/2
_animState->fade(Animation::FadeIn, 500);
break;
case 8: // Fade in 1/4
_animState->fade(Animation::FadeIn, 250);
break;
case 9: // Fade in 1/8
_animState->fade(Animation::FadeIn, 125);
break;
case 10: // Fade out 1
_animState->fade(Animation::FadeOut, 1000);
break;
case 11: // Fade out 3/4
_animState->fade(Animation::FadeOut, 750);
break;
case 12: // Fade out 1/2
_animState->fade(Animation::FadeOut, 500);
break;
case 13: // Fade out 1/4
_animState->fade(Animation::FadeOut, 250);
break;
case 14: // Fade out 1/8
_animState->fade(Animation::FadeOut, 125);
break;
default:
Debug::warning(Debug::Costumes, "Unknown key %d for component %s", f, _name.c_str());
break;
}
}
void EMIAnimComponent::reset() {
_visible = true;
_animState->stop();
}
void EMIAnimComponent::fade(Animation::FadeMode mode, int fadeLength) {
_animState->fade(mode, fadeLength);
}
void EMIAnimComponent::advance(uint msecs) {
_animState->advance(msecs);
}
void EMIAnimComponent::setPaused(bool paused) {
_animState->setPaused(paused);
}
void EMIAnimComponent::draw() {
}
void EMIAnimComponent::saveState(SaveGame *state) {
_animState->saveState(state);
}
void EMIAnimComponent::restoreState(SaveGame *state) {
_animState->restoreState(state);
}
} // end of namespace Grim

View File

@@ -0,0 +1,54 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef GRIM_EMI_ANIM_COMPONENT_H
#define GRIM_EMI_ANIM_COMPONENT_H
#include "engines/grim/costume/component.h"
// This is mostly stubbed for testing the animation loading at the moment.
namespace Grim {
class AnimationStateEmi;
class EMIAnimComponent : public Component {
public:
EMIAnimComponent(Component *parent, int parentID, const char *filename, Component *prevComponent, tag32 tag);
~EMIAnimComponent();
void init() override;
void setKey(int) override;
int update(uint time) override;
void reset() override ;
void fade(Animation::FadeMode mode, int fadeLength) override ;
void advance(uint msecs) override ;
void setPaused(bool paused) override;
void draw() override;
void saveState(SaveGame *state) override;
void restoreState(SaveGame *state) override;
private:
AnimationStateEmi *_animState;
};
} // end of namespace Grim
#endif

View File

@@ -0,0 +1,140 @@
/* 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 "engines/grim/emi/costume/emichore.h"
#include "engines/grim/emi/modelemi.h"
namespace Grim {
EMIChore::EMIChore(char name[32], int id, Costume *owner, int length, int numTracks) :
Chore(name, id, owner, length, numTracks), _mesh(nullptr), _skeleton(nullptr),
_fadeMode(Animation::None), _fade(1.f), _fadeLength(0), _startFade(1.0f) {
}
void EMIChore::addComponent(Component *component) {
if (component->isComponentType('m', 'e', 's', 'h')) {
_mesh = static_cast<EMIMeshComponent *>(component);
} else if (component->isComponentType('s', 'k', 'e', 'l')) {
_skeleton = static_cast<EMISkelComponent *>(component);
}
if (_mesh && _mesh->_obj && _skeleton) {
_mesh->_obj->setSkeleton(_skeleton->_obj);
}
}
void EMIChore::update(uint time) {
if (!_playing || _paused)
return;
if (_fadeMode != Animation::None) {
if (_fadeMode == Animation::FadeIn) {
_fade += (float)time * (1.0f - _startFade) / _fadeLength;
if (_fade >= 1.f) {
_fade = 1.f;
_fadeMode = Animation::None;
}
} else {
_fade -= (float)time * _startFade / _fadeLength;
if (_fade <= 0.f) {
_fade = 0.f;
stop(0);
return;
}
}
}
int newTime;
if (_currTime < 0)
newTime = 0; // For first time through
else
newTime = _currTime + time;
setKeys(_currTime, newTime);
if (_length >= 0 && newTime > _length) {
if (!_looping && _fadeMode != Animation::FadeOut) {
stop(0);
}
else {
do {
newTime -= _length;
setKeys(-1, newTime);
} while (newTime > _length);
}
}
_currTime = newTime;
}
void EMIChore::stop(uint msecs) {
if (msecs > 0) {
fade(Animation::FadeOut, msecs);
} else {
_playing = false;
_hasPlayed = false;
for (int i = 0; i < _numTracks; i++) {
Component *comp = getComponentForTrack(i);
if (comp)
comp->reset();
}
}
}
void EMIChore::fade(Animation::FadeMode mode, uint msecs) {
if (mode == Animation::None) {
_fade = 1.0f;
}
_startFade = _fade;
_fadeMode = mode;
_fadeLength = msecs;
for (int i = 0; i < _numTracks; i++) {
Component *comp = getComponentForTrack(i);
if (comp) {
comp->fade(mode, msecs);
}
}
}
void EMIChore::saveState(SaveGame *state) const {
Chore::saveState(state);
state->writeLESint32((int)_fadeMode);
state->writeFloat(_fade);
state->writeFloat(_startFade);
state->writeLESint32(_fadeLength);
}
void EMIChore::restoreState(SaveGame *state) {
Chore::restoreState(state);
if (state->saveMinorVersion() >= 10) {
_fadeMode = (Animation::FadeMode)state->readLESint32();
_fade = state->readFloat();
_startFade = state->readFloat();
_fadeLength = state->readLESint32();
} else {
if (_length == -1 && _playing)
_currTime = -1;
}
}
} // end of namespace Grim

View File

@@ -0,0 +1,60 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef GRIM_EMICHORE_H
#define GRIM_EMICHORE_H
#include "engines/grim/costume/chore.h"
#include "engines/grim/pool.h"
#include "engines/grim/emi/costume/emimesh_component.h"
#include "engines/grim/emi/costume/emiskel_component.h"
namespace Grim {
class EMIChore : public PoolObject<EMIChore>, public Chore {
public:
EMIChore(char name[32], int id, Costume *owner, int length, int numTracks);
static int32 getStaticTag() { return MKTAG('C', 'H', 'O', 'R'); }
void update(uint msecs) override;
void stop(uint msecs) override;
void addComponent(Component *component);
bool isWearChore() { return _mesh && _skeleton; }
void saveState(SaveGame *state) const override;
void restoreState(SaveGame *state) override;
EMIMeshComponent *getMesh() { return _mesh; }
EMISkelComponent *getSkeleton() { return _skeleton; }
private:
void fade(Animation::FadeMode mode, uint msecs) override;
Animation::FadeMode _fadeMode;
float _fade;
float _startFade;
int _fadeLength;
EMIMeshComponent *_mesh;
EMISkelComponent *_skeleton;
};
} // end of namespace Grim
#endif

View File

@@ -0,0 +1,168 @@
/* 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 "engines/grim/grim.h"
#include "engines/grim/emi/costume/emihead.h"
#include "engines/grim/emi/costumeemi.h"
#include "engines/grim/emi/skeleton.h"
namespace Grim {
EMIHead::EMIHead(EMICostume *costume) : _yawRange(80.0f), _minPitch(-30.0f), _maxPitch(30.0f) {
_cost = costume;
}
void EMIHead::setJoint(const char *joint, const Math::Vector3d &offset) {
_jointName = joint;
_offset = offset;
}
void EMIHead::setLimits(float yawRange, float maxPitch, float minPitch) {
_yawRange = yawRange;
_maxPitch = maxPitch;
_minPitch = minPitch;
}
void EMIHead::lookAt(bool entering, const Math::Vector3d &point, float rate, const Math::Matrix4 &matrix) {
if (!_cost->_emiSkel || !_cost->_emiSkel->_obj)
return;
if (_jointName.empty())
return;
Joint *joint = _cost->_emiSkel->_obj->getJointNamed(_jointName);
if (!joint)
return;
Math::Quaternion lookAtQuat; // Note: Identity if not looking at anything.
if (entering) {
Math::Matrix4 jointToWorld = _cost->getOwner()->getFinalMatrix() * joint->_finalMatrix;
Math::Vector3d jointWorldPos = jointToWorld.getPosition();
Math::Matrix4 worldToJoint = jointToWorld;
worldToJoint.invertAffineOrthonormal();
Math::Vector3d targetDir = (point + _offset) - jointWorldPos;
targetDir.normalize();
const Math::Vector3d worldUp(0, 1, 0);
Math::Vector3d frontDir = Math::Vector3d(worldToJoint(0, 1), worldToJoint(1, 1), worldToJoint(2, 1)); // Look straight ahead. (+Y)
Math::Vector3d modelFront(0, 0, 1);
Math::Vector3d modelUp(0, 1, 0);
modelFront = modelFront * joint->_absMatrix.getRotation();
modelUp = modelUp * joint->_absMatrix.getRotation();
// Generate a world-space look at matrix.
Math::Matrix4 lookAtTM;
lookAtTM.setToIdentity();
if (Math::Vector3d::dotProduct(targetDir, worldUp) >= 0.98f) // Avoid singularity if trying to look straight up.
lookAtTM.buildFromTargetDir(modelFront, targetDir, modelUp, -frontDir); // Instead of orienting head towards scene up, orient head towards character "back",
else if (Math::Vector3d::dotProduct(targetDir, worldUp) <= -0.98f) // Avoid singularity if trying to look straight down.
lookAtTM.buildFromTargetDir(modelFront, targetDir, modelUp, frontDir); // Instead of orienting head towards scene down, orient head towards character "front",
else
lookAtTM.buildFromTargetDir(modelFront, targetDir, modelUp, worldUp);
// Convert from world-space to joint-space.
lookAtTM = worldToJoint * lookAtTM;
// Apply angle limits.
Math::Angle p, y, r;
lookAtTM.getEuler(&y, &p, &r, Math::EO_ZXY);
y.clampDegrees(_yawRange);
p.clampDegrees(_minPitch, _maxPitch);
r.clampDegrees(30.0f);
lookAtTM.buildFromEuler(y, p, r, Math::EO_ZXY);
lookAtQuat.fromMatrix(lookAtTM.getRotation());
}
if (_headRot != lookAtQuat) {
Math::Quaternion diff = _headRot.inverse() * lookAtQuat;
diff.normalize();
float angle = 2 * acos(MIN(MAX(diff.w(), -1.0f), 1.0f));
if (diff.w() < 0.0f) {
angle = 2 * (float)M_PI - angle;
}
float turnAmount = g_grim->getPerSecond(rate * ((float)M_PI / 180.0f));
if (turnAmount < angle)
_headRot = _headRot.slerpQuat(lookAtQuat, turnAmount / angle);
else
_headRot = lookAtQuat;
}
if (_headRot != Math::Quaternion()) { // If not identity..
joint->_animMatrix = joint->_animMatrix * _headRot.toMatrix();
joint->_animQuat = joint->_animQuat * _headRot;
_cost->_emiSkel->_obj->commitAnim();
}
}
void EMIHead::saveState(SaveGame *state) const {
state->writeString(_jointName);
state->writeVector3d(_offset);
state->writeFloat(_headRot.x());
state->writeFloat(_headRot.y());
state->writeFloat(_headRot.z());
state->writeFloat(_headRot.w());
state->writeFloat(_yawRange);
state->writeFloat(_minPitch);
state->writeFloat(_maxPitch);
}
void EMIHead::restoreState(SaveGame *state) {
if (state->saveMinorVersion() >= 15) {
_jointName = state->readString();
_offset = state->readVector3d();
_headRot.x() = state->readFloat();
_headRot.y() = state->readFloat();
_headRot.z() = state->readFloat();
_headRot.w() = state->readFloat();
if (state->saveMinorVersion() >= 16) {
_yawRange = state->readFloat();
_minPitch = state->readFloat();
_maxPitch = state->readFloat();
}
} else {
state->readLESint32();
state->readLESint32();
state->readLESint32();
state->readFloat();
state->readFloat();
state->readFloat();
if (state->saveMinorVersion() < 2) {
state->readFloat();
state->readFloat();
} else {
for (int i = 0; i < 3; ++i) {
state->readFloat();
state->readFloat();
state->readFloat();
}
}
}
}
} // end of namespace Grim

View File

@@ -0,0 +1,57 @@
/* 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 GRIM_EMIHEAD_H
#define GRIM_EMIHEAD_H
#include "math/vector3d.h"
#include "math/quat.h"
#include "engines/grim/costume/head.h"
namespace Grim {
class EMICostume;
class EMIHead : public BaseHead {
public:
EMIHead(EMICostume *costume);
void setJoint(const char *joint, const Math::Vector3d &offset);
void setLimits(float yawRange, float maxPitch, float minPitch);
void lookAt(bool entering, const Math::Vector3d &point, float rate, const Math::Matrix4 &matrix) override;
void loadJoints(ModelNode *nodes) override {}
void saveState(SaveGame *state) const override;
void restoreState(SaveGame *state) override;
private:
EMICostume *_cost;
Common::String _jointName;
Math::Vector3d _offset;
Math::Quaternion _headRot;
float _yawRange;
float _maxPitch;
float _minPitch;
};
} // end of namespace Grim
#endif

View File

@@ -0,0 +1,53 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "common/debug.h"
#include "engines/grim/debug.h"
#include "engines/grim/emi/costume/emiluacode_component.h"
namespace Grim {
EMILuaCodeComponent::EMILuaCodeComponent(Component *p, int parentID, const char *name, Component *prevComponent, tag32 t) : Component(p, parentID, name, t) {
}
EMILuaCodeComponent::~EMILuaCodeComponent() {
}
void EMILuaCodeComponent::init() {
}
int EMILuaCodeComponent::update(uint time) {
return 0;
}
void EMILuaCodeComponent::setKey(int val) {
Debug::debug(Debug::Lua, "LuaC component: executing code [%s]", _name.c_str());
lua_dostring(_name.c_str());
}
void EMILuaCodeComponent::reset() {
}
void EMILuaCodeComponent::draw() {
}
} // end of namespace Grim

View File

@@ -0,0 +1,44 @@
/* 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 GRIM_EMI_LUACODE_COMPONENT_H
#define GRIM_EMI_LUACODE_COMPONENT_H
#include "engines/grim/costume/component.h"
#include "engines/grim/lua/lua.h"
namespace Grim {
class EMILuaCodeComponent : public Component {
public:
EMILuaCodeComponent(Component *parent, int parentID, const char *name, Component *prevComponent, tag32 tag);
~EMILuaCodeComponent();
void init() override;
int update(uint time) override;
void reset() override;
void draw() override;
void setKey(int val) override;
private:
};
} // end of namespace Grim
#endif

View File

@@ -0,0 +1,55 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "common/debug.h"
#include "common/textconsole.h"
#include "engines/grim/debug.h"
#include "engines/grim/emi/costume/emiluavar_component.h"
namespace Grim {
EMILuaVarComponent::EMILuaVarComponent(Component *p, int parentID, const char *name, Component *prevComponent, tag32 t) : Component(p, parentID, name, t) {
}
EMILuaVarComponent::~EMILuaVarComponent() {
}
void EMILuaVarComponent::init() {
}
int EMILuaVarComponent::update(uint time) {
return 0;
}
void EMILuaVarComponent::setKey(int val) {
Debug::debug(Debug::Lua, "LuaV component: setting %s to %d", _name.c_str(), val);
lua_pushnumber(val);
lua_setglobal(_name.c_str());
}
void EMILuaVarComponent::reset() {
}
void EMILuaVarComponent::draw() {
}
} // end of namespace Grim

View File

@@ -0,0 +1,44 @@
/* 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 GRIM_EMI_LUAVAR_COMPONENT_H
#define GRIM_EMI_LUAVAR_COMPONENT_H
#include "engines/grim/lua/lua.h"
#include "engines/grim/costume/component.h"
namespace Grim {
class EMILuaVarComponent : public Component {
public:
EMILuaVarComponent(Component *parent, int parentID, const char *name, Component *prevComponent, tag32 tag);
~EMILuaVarComponent();
void init() override;
int update(uint time) override;
void reset() override;
void draw() override;
void setKey(int val) override;
private:
};
} // end of namespace Grim
#endif

View File

@@ -0,0 +1,99 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "engines/grim/emi/costume/emimesh_component.h"
#include "engines/grim/emi/modelemi.h"
#include "engines/grim/resource.h"
#include "engines/grim/costume.h"
namespace Grim {
EMIMeshComponent::EMIMeshComponent(Component *p, int parentID, const char *filename, Component *prevComponent, tag32 t, EMICostume *costume) :
Component(p, parentID, filename, t), _costume(costume), _obj(nullptr), _parentModel(nullptr), _hasComplained(false) {
_hierShared = false;
}
EMIMeshComponent::~EMIMeshComponent() {
if (_hierShared) {
_obj = nullptr; // Keep ~ModelComp from deleting it
//_animation = NULL;
} else {
delete _obj;
}
for (EMIMeshComponent *child : _children) {
child->_obj = nullptr;
//child->_hier = NULL;
child->_parentModel = nullptr;
}
if (_parentModel) {
_parentModel->_children.remove(this);
}
}
void EMIMeshComponent::init() {
_visible = true;
_obj = g_resourceloader->loadModelEMI(_name, _costume);
}
int EMIMeshComponent::update(uint time) {
return 0;
}
void EMIMeshComponent::reset() {
_visible = true;
}
void EMIMeshComponent::draw() {
// If the object was drawn by being a component
// of it's parent then don't draw it
if (_parent && _parent->isVisible())
return;
if (!_obj) {
if (!_hasComplained) {
warning("Tried to draw component we have no file for %s", _name.c_str());
_hasComplained = true;
}
return;
}
// Need to translate object to be in accordance
// with the setup of the parent
//translateObject(false);
_obj->draw();
// Need to un-translate when done
//translateObject(true);
}
void EMIMeshComponent::getBoundingBox(int *x1, int *y1, int *x2, int *y2) const {
// If the object was drawn by being a component
// of it's parent then don't draw it
if (_parent && _parent->isVisible())
return;
if (_obj)
_obj->getBoundingBox(x1, y1, x2, y2);
}
} // end of namespace Grim

View File

@@ -0,0 +1,53 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef GRIM_EMI_MESH_COMPONENT_H
#define GRIM_EMI_MESH_COMPONENT_H
#include "engines/grim/costume/component.h"
namespace Grim {
class EMICostume;
class EMIModel;
class EMIMeshComponent : public Component {
public:
EMIMeshComponent(Component *parent, int parentID, const char *filename, Component *prevComponent, tag32 tag, EMICostume *costume);
~EMIMeshComponent();
void init() override;
int update(uint time) override;
void reset() override;
void draw() override;
void getBoundingBox(int *x1, int *y1, int *x2, int *y2) const;
public:
EMICostume *_costume;
bool _hierShared;
Common::List<EMIMeshComponent*> _children;
EMIMeshComponent *_parentModel;
EMIModel *_obj;
bool _hasComplained; // Temporary fix for warning-spam.
};
} // end of namespace Grim
#endif

View File

@@ -0,0 +1,58 @@
/* 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 "engines/grim/emi/costume/emiskel_component.h"
#include "engines/grim/resource.h"
#include "engines/grim/emi/modelemi.h"
#include "engines/grim/emi/skeleton.h"
#include "engines/grim/costume.h"
namespace Grim {
EMISkelComponent::EMISkelComponent(Component *p, int parentID, const char *filename, Component *prevComponent, tag32 t) : Component(p, parentID, filename, t), _obj(nullptr), _parentModel(nullptr), _hierShared(false) {
}
EMISkelComponent::~EMISkelComponent() {
delete _obj;
}
void EMISkelComponent::init() {
_visible = true;
_obj = g_resourceloader->loadSkeleton(_name);
}
void EMISkelComponent::animate() {
if (_obj)
_obj->animate();
}
int EMISkelComponent::update(uint time) {
return 0;
}
void EMISkelComponent::reset() {
_visible = true;
}
void EMISkelComponent::draw() {
}
} // end of namespace Grim

View File

@@ -0,0 +1,51 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef GRIM_EMI_SKEL_COMPONENT_H
#define GRIM_EMI_SKEL_COMPONENT_H
#include "engines/grim/costume/component.h"
// This is mostly stubbed for testing the skeletonloading at the moment.
namespace Grim {
class Skeleton;
class EMISkelComponent : public Component {
public:
EMISkelComponent(Component *parent, int parentID, const char *filename, Component *prevComponent, tag32 tag);
~EMISkelComponent();
void init() override;
void animate() override;
int update(uint time) override;
void reset() override;
void draw() override;
public:
bool _hierShared;
Component *_parentModel;
Skeleton *_obj;
};
} // end of namespace Grim
#endif

View File

@@ -0,0 +1,55 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "engines/grim/emi/costume/emisprite_component.h"
#include "engines/grim/emi/costumeemi.h"
#include "engines/grim/resource.h"
#include "engines/grim/costume.h"
#include "engines/grim/sprite.h"
namespace Grim {
EMISpriteComponent::EMISpriteComponent(Component *p, int parentID, const char *filename, Component *prevComponent, tag32 t) : Component(p, parentID, filename, t), _sprite(nullptr) {
}
EMISpriteComponent::~EMISpriteComponent() {
delete _sprite;
}
void EMISpriteComponent::init() {
EMICostume *c = static_cast<EMICostume *>(_cost);
_sprite = g_resourceloader->loadSprite(_name, c);
}
int EMISpriteComponent::update(uint time) {
return 0;
}
void EMISpriteComponent::reset() {
}
void EMISpriteComponent::draw() {
if (_sprite) {
_sprite->draw();
}
}
} // end of namespace Grim

View File

@@ -0,0 +1,46 @@
/* 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 GRIM_EMI_SPRITE_COMPONENT_H
#define GRIM_EMI_SPRITE_COMPONENT_H
#include "engines/grim/costume/component.h"
namespace Grim {
class Sprite;
class EMISpriteComponent : public Component {
public:
EMISpriteComponent(Component *parent, int parentID, const char *filename, Component *prevComponent, tag32 tag);
~EMISpriteComponent();
void init() override;
int update(uint time) override;
void reset() override;
void draw() override;
public:
Sprite *_sprite;
};
} // end of namespace Grim
#endif

View File

@@ -0,0 +1,59 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "engines/grim/costume.h"
#include "engines/grim/debug.h"
#include "engines/grim/material.h"
#include "engines/grim/savegame.h"
#include "engines/grim/emi/costumeemi.h"
#include "engines/grim/emi/modelemi.h"
#include "engines/grim/emi/costume/emimesh_component.h"
#include "engines/grim/emi/costume/emitexi_component.h"
namespace Grim {
EMITexiComponent::EMITexiComponent(Component *parent, int parentID, const char *filename, Component *prevComponent, tag32 tag) : Component(parent, parentID, filename, tag) {
}
EMITexiComponent::~EMITexiComponent() {
}
void EMITexiComponent::init() {
EMICostume *c = static_cast<EMICostume *>(_cost);
_mat = c->findMaterial(_name);
}
int EMITexiComponent::update(uint time) {
return 0;
}
void EMITexiComponent::setKey(int k) {
if (_mat && _mat->getNumTextures() > k)
_mat->setActiveTexture(k);
}
void EMITexiComponent::reset() {
}
void EMITexiComponent::draw() {
}
} // end of namespace Grim

View File

@@ -0,0 +1,49 @@
/* 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 GRIM_EMI_TEXI_COMPONENT_H
#define GRIM_EMI_TEXI_COMPONENT_H
#include "engines/grim/costume/component.h"
#include "engines/grim/material.h"
#include "engines/grim/resource.h"
#include "engines/grim/model.h"
namespace Grim {
class EMITexiComponent : public Component {
public:
EMITexiComponent(Component *parent, int parentID, const char *filename, Component *prevComponent, tag32 tag);
~EMITexiComponent();
void init() override;
int update(uint time) override;
void reset() override;
void draw() override;
void setKey(int k) override;
private:
Material *_mat;
};
} // end of namespace Grim
#endif

View File

@@ -0,0 +1,328 @@
/* 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/endian.h"
#include "engines/grim/debug.h"
#include "engines/grim/costume.h"
#include "engines/grim/grim.h"
#include "engines/grim/resource.h"
#include "engines/grim/actor.h"
#include "engines/grim/emi/costumeemi.h"
#include "engines/grim/emi/modelemi.h"
#include "engines/grim/emi/skeleton.h"
#include "engines/grim/emi/costume/emihead.h"
#include "engines/grim/emi/costume/emianim_component.h"
#include "engines/grim/emi/costume/emiluavar_component.h"
#include "engines/grim/emi/costume/emiluacode_component.h"
#include "engines/grim/emi/costume/emimesh_component.h"
#include "engines/grim/emi/costume/emiskel_component.h"
#include "engines/grim/emi/costume/emisprite_component.h"
#include "engines/grim/emi/costume/emitexi_component.h"
namespace Grim {
EMICostume::EMICostume(const Common::String &fname, Actor *owner, Costume *prevCost) :
Costume(fname, owner, prevCost), _wearChore(nullptr), _emiSkel(nullptr) {
}
void EMICostume::load(Common::SeekableReadStream *data) {
Common::Array<Component *> components;
_numChores = data->readUint32LE();
_chores = new Chore *[_numChores];
for (int i = 0; i < _numChores; i++) {
uint32 nameLength;
Component *prevComponent = nullptr;
nameLength = data->readUint32LE();
assert(nameLength < 32);
char name[32];
data->read(name, nameLength);
float length = data->readFloatLE();
int numTracks = data->readUint32LE();
if (length == 1000)
length = -1.0f;
else
length *= 1000;
EMIChore *chore = new EMIChore(name, i, this, (int)length, numTracks);
_chores[i] = chore;
for (int k = 0; k < numTracks; k++) {
int componentNameLength = data->readUint32LE();
char *componentName = new char[componentNameLength];
data->read(componentName, componentNameLength);
data->readUint32LE();
int parentID = data->readUint32LE();
if (parentID == -1 && _prevCostume) {
// However, only the first item can actually share the
// node hierarchy with the previous costume, so flag
// that component so it knows what to do
if (i == 0)
parentID = -2;
prevComponent = _prevCostume->getComponent(0);
// Make sure that the component is valid
if (!prevComponent->isComponentType('M', 'M', 'D', 'L'))
prevComponent = nullptr;
}
// Actually load the appropriate component
Component *component = loadEMIComponent(parentID < 0 ? nullptr : components[parentID], parentID, componentName, prevComponent);
if (component) {
component->setCostume(this);
component->init();
chore->addComponent(component);
}
components.push_back(component);
ChoreTrack &track = chore->_tracks[k];
track.numKeys = data->readUint32LE();
track.keys = new TrackKey[track.numKeys];
track.component = component;
track.compID = -1; // -1 means "look at .component"
for (int j = 0; j < track.numKeys; j++) {
float time, value;
time = data->readFloatLE();
value = data->readFloatLE();
track.keys[j].time = (int)(time * 1000);
length = MAX(length, time * 1000);
track.keys[j].value = (int)value;
}
delete[] componentName;
}
}
_numComponents = components.size();
_components = new Component *[_numComponents];
for (int i = 0; i < _numComponents; ++i) {
_components[i] = components[i];
}
_head = new EMIHead(this);
}
void EMICostume::playChore(int num, uint msecs) {
// FIXME: Original EMI can play multiple instances of a chore at the same time.
EMIChore *chore = static_cast<EMIChore *>(_chores[num]);
if (chore->isWearChore()) {
setWearChore(chore);
}
Costume::playChore(num, msecs);
}
void EMICostume::playChoreLooping(int num, uint msecs) {
// FIXME: Original EMI can play multiple instances of a chore at the same time.
EMIChore *chore = static_cast<EMIChore *>(_chores[num]);
if (chore->isWearChore()) {
setWearChore(chore);
}
Costume::playChoreLooping(num, msecs);
}
Component *EMICostume::loadEMIComponent(Component *parent, int parentID, const char *name, Component *prevComponent) {
assert(name[0] == '!');
++name;
char type[5];
tag32 tag = 0;
memcpy(&tag, name, 4);
memcpy(&type, name, 4);
type[4] = 0;
tag = FROM_BE_32(tag);
name += 4;
if (tag == MKTAG('m', 'e', 's', 'h')) {
return new EMIMeshComponent(parent, parentID, name, prevComponent, tag, this);
} else if (tag == MKTAG('s', 'k', 'e', 'l')) {
return new EMISkelComponent(parent, parentID, name, prevComponent, tag);
} else if (tag == MKTAG('t', 'e', 'x', 'i')) {
return new EMITexiComponent(parent, parentID, name, prevComponent, tag);
} else if (tag == MKTAG('a', 'n', 'i', 'm')) {
return new EMIAnimComponent(parent, parentID, name, prevComponent, tag);
} else if (tag == MKTAG('l', 'u', 'a', 'c')) {
return new EMILuaCodeComponent(parent, parentID, name, prevComponent, tag);
} else if (tag == MKTAG('l', 'u', 'a', 'v')) {
return new EMILuaVarComponent(parent, parentID, name, prevComponent, tag);
} else if (tag == MKTAG('s', 'p', 'r', 't')) {
return new EMISpriteComponent(parent, parentID, name, prevComponent, tag);
} else if (tag == MKTAG('s', 'h', 'a', 'd')) {
Debug::warning(Debug::Costumes, "Actor::loadComponentEMI Implement SHAD-handling: %s" , name);
} else if (tag == MKTAG('a', 'w', 'g', 't')) {
Debug::warning(Debug::Costumes, "Actor::loadComponentEMI Implement AWGT-handling: %s" , name);
} else if (tag == MKTAG('s', 'n', 'd', '2')) {
// ignore, this is a leftover from an earlier engine.
} else {
error("Actor::loadComponentEMI missing tag: %s for %s", name, type);
}
return nullptr;
}
void EMICostume::draw() {
bool drewMesh = false;
for (Common::List<Chore*>::iterator it = _playingChores.begin(); it != _playingChores.end(); ++it) {
Chore *c = (*it);
if (!c->_playing)
continue;
for (int i = 0; i < c->_numTracks; ++i) {
if (c->_tracks[i].component) {
c->_tracks[i].component->draw();
if (c->_tracks[i].component->isComponentType('m', 'e', 's', 'h'))
drewMesh = true;
}
}
}
if (_wearChore && !drewMesh) {
_wearChore->getMesh()->draw();
}
}
int EMICostume::update(uint time) {
for (Common::List<Chore*>::iterator i = _playingChores.begin(); i != _playingChores.end(); ++i) {
Chore *c = *i;
c->update(time);
for (int t = 0; t < c->_numTracks; ++t) {
if (c->_tracks[t].component) {
c->_tracks[t].component->update(time);
}
}
if (!c->isPlaying()) {
i = _playingChores.erase(i);
--i;
}
}
return 0;
}
void EMICostume::saveState(SaveGame *state) const {
Costume::saveState(state);
for (int i = 0; i < _numChores; ++i) {
EMIChore *chore = (EMIChore *)_chores[i];
state->writeLESint32(chore->getId());
}
state->writeLESint32(_wearChore ? _wearChore->getChoreId() : -1);
}
bool EMICostume::restoreState(SaveGame *state) {
bool ret = Costume::restoreState(state);
if (ret) {
if (state->saveMinorVersion() >= 11) {
EMIChore::Pool &pool = EMIChore::getPool();
for (int i = 0; i < _numChores; ++i) {
EMIChore *chore = (EMIChore *)_chores[i];
int id = state->readLESint32();
pool.removeObject(chore->getId());
EMIChore* oldChore = pool.getObject(id);
if (oldChore) {
pool.removeObject(id);
oldChore->setId(chore->getId());
pool.addObject(oldChore);
}
chore->setId(id);
pool.addObject(chore);
}
}
if (state->saveMinorVersion() < 13) {
// Used to be active texture IDs for materials. Materials are now
// managed by the owner Actor of this Costume.
for (uint i = 0; i < _materials.size(); ++i) {
state->readLESint32();
}
}
int id = state->readLESint32();
if (id >= 0) {
EMIChore *chore = static_cast<EMIChore *>(_chores[id]);
setWearChore(chore);
}
}
return ret;
}
Material *EMICostume::findMaterial(const Common::String &name) {
return _owner->findMaterial(name);
}
Material *EMICostume::loadMaterial(const Common::String &name, bool clamp) {
MaterialPtr mat = _owner->loadMaterial(name, clamp);
if (mat) {
// Save a reference to the material, so it will not be freed during the
// lifetime of this costume.
if (Common::find(_materials.begin(), _materials.end(), mat) == _materials.end())
_materials.push_back(mat);
}
return mat;
}
void EMICostume::setWearChore(EMIChore *chore) {
if (chore != _wearChore) {
_wearChore = chore;
if (_emiSkel) {
_emiSkel->reset();
}
_emiSkel = chore->getSkeleton();
}
}
void EMICostume::setHead(const char *joint, const Math::Vector3d &offset) {
static_cast<EMIHead *>(_head)->setJoint(joint, offset);
}
void EMICostume::setHeadLimits(float yawRange, float maxPitch, float minPitch) {
static_cast<EMIHead *>(_head)->setLimits(yawRange, maxPitch, minPitch);
}
EMIModel *EMICostume::getEMIModel() const {
if (!_wearChore)
return nullptr;
return _wearChore->getMesh()->_obj;
}
EMIModel *EMICostume::getEMIModel(int num) const {
if (num >= _numChores) {
return nullptr;
}
EMIChore *chore = static_cast<EMIChore *>(_chores[num]);
if (chore == nullptr) {
return nullptr;
}
EMIMeshComponent *mesh = chore->getMesh();
if (mesh == nullptr) {
return nullptr;
}
return mesh->_obj;
}
} // end of namespace Grim

View File

@@ -0,0 +1,77 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef GRIM_COSTUMEEMI_H
#define GRIM_COSTUMEEMI_H
#include "common/stream.h"
#include "engines/grim/object.h"
#include "engines/grim/costume.h"
#include "engines/grim/emi/costume/emichore.h"
namespace Grim {
typedef uint32 tag32;
class EMISkelComponent;
class EMIMeshComponent;
class Material;
class EMIModel;
class EMICostume : public Costume {
public:
EMICostume(const Common::String &filename, Actor *owner, Costume *prevCost);
void load(Common::SeekableReadStream *data) override;
void draw() override;
int update(uint time) override;
void playChore(int num, uint msecs = 0) override;
void playChoreLooping(int num, uint msecs = 0) override;
void saveState(SaveGame *state) const override;
bool restoreState(SaveGame *state) override;
Material *loadMaterial(const Common::String &name, bool clamp);
Material *findMaterial(const Common::String &name);
void setHead(const char *joint, const Math::Vector3d &offset);
void setHeadLimits(float yawRange, float maxPitch, float minPitch);
EMIModel *getEMIModel() const;
EMIModel *getEMIModel(int num) const;
public:
EMIChore *_wearChore;
EMISkelComponent *_emiSkel;
private:
Common::List<ObjectPtr<Material> > _materials;
static bool compareChores(const Chore *c1, const Chore *c2);
Component *loadEMIComponent(Component *parent, int parentID, const char *name, Component *prevComponent);
void setWearChore(EMIChore *chore);
friend class Chore;
};
} // end of namespace Grim
#endif

334
engines/grim/emi/emi.cpp Normal file
View File

@@ -0,0 +1,334 @@
/* 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 "engines/grim/emi/emi.h"
#include "engines/grim/emi/emi_registry.h"
#include "engines/grim/emi/lua_v2.h"
#include "engines/grim/primitives.h"
#include "engines/grim/set.h"
#include "engines/grim/gfx_base.h"
#include "engines/grim/actor.h"
#include "graphics/surface.h"
namespace Grim {
EMIEngine *g_emi = nullptr;
EMIEngine::EMIEngine(OSystem *syst, uint32 gameFlags, GrimGameType gameType, Common::Platform platform, Common::Language language) :
GrimEngine(syst, gameFlags, gameType, platform, language), _sortOrderInvalidated(false), _textObjectsSortOrderInvalidated(true) {
g_emi = this;
g_emiregistry = new EmiRegistry();
}
EMIEngine::~EMIEngine() {
g_emi = nullptr;
delete g_emiregistry;
g_emiregistry = nullptr;
}
LuaBase *EMIEngine::createLua() {
return new Lua_V2();
}
const char *EMIEngine::getUpdateFilename() {
if (getGamePlatform() == Common::kPlatformWindows && !(getGameFlags() & ADGF_DEMO)) {
switch (getGameLanguage()) {
case Common::FR_FRA:
return "MonkeyUpdate_FRA.exe";
break;
case Common::DE_DEU:
return "MonkeyUpdate_DEU.exe";
break;
case Common::IT_ITA:
return "MonkeyUpdate_ITA.exe";
break;
case Common::PT_BRA:
return "MonkeyUpdate_BRZ.exe";
break;
case Common::ES_ESP:
return "MonkeyUpdate_ESP.exe";
break;
case Common::EN_ANY:
case Common::EN_GRB:
case Common::EN_USA:
default:
return "MonkeyUpdate.exe";
break;
}
} else
return nullptr;
}
void EMIEngine::pushText() {
for (TextObject *t : TextObject::getPool()) {
t->incStackLevel();
}
invalidateTextObjectsSortOrder();
}
void EMIEngine::popText() {
Common::List<TextObject *> toDelete;
for (TextObject *t : TextObject::getPool()) {
if (t->getStackLevel() == 0) {
warning("Text stack top not empty; deleting object");
toDelete.push_back(t);
} else {
t->decStackLevel();
}
}
while (!toDelete.empty()) {
TextObject *t = toDelete.front();
toDelete.pop_front();
delete t;
}
invalidateTextObjectsSortOrder();
}
void EMIEngine::purgeText() {
Common::List<TextObject *> toDelete;
for (TextObject *t : TextObject::getPool()) {
if (t->getStackLevel() == 0) {
toDelete.push_back(t);
}
}
while (!toDelete.empty()) {
TextObject *t = toDelete.front();
toDelete.pop_front();
delete t;
}
invalidateTextObjectsSortOrder();
}
void EMIEngine::drawNormalMode() {
_currSet->setupCamera();
g_driver->set3DMode();
if (_setupChanged) {
cameraPostChangeHandle(_currSet->getSetup());
_setupChanged = false;
}
// Draw actors
buildActiveActorsList();
sortActiveActorsList();
sortLayers();
Bitmap *background = _currSet->getCurrSetup()->_bkgndBm;
background->_data->load();
uint32 numLayers = background->_data->_numLayers;
Common::List<Layer *>::const_iterator nextLayer = _layers.begin();
Common::List<Actor *>::const_iterator nextActor = _activeActors.begin();
int32 currentLayer = numLayers - 1;
int aso = (nextActor != _activeActors.end()) ? (*nextActor)->getEffectiveSortOrder() : -1;
int lso = (nextLayer != _layers.end()) ? (*nextLayer)->getSortOrder() : -1;
int bgso = currentLayer * 10;
// interleave actors, background layers and additional stand-alone layers based
// on their sortorder
//
// priority for same sort order:
// background layers (highest priority)
// stand-alone layers
// actors
while (1) {
if (aso >= 0 && aso > bgso && aso > lso) {
if ((*nextActor)->isVisible() && ! (*nextActor)->isInOverworld())
(*nextActor)->draw();
nextActor++;
aso = (nextActor != _activeActors.end()) ? (*nextActor)->getEffectiveSortOrder() : -1;
continue;
}
if (bgso >= 0 && bgso >= lso && bgso >= aso) {
background->drawLayer(currentLayer);
currentLayer--;
bgso = currentLayer * 10;
continue;
}
if (lso >= 0 && lso > bgso && lso >= aso) {
(*nextLayer)->draw();
nextLayer++;
lso = (nextLayer != _layers.end()) ? (*nextLayer)->getSortOrder() : -1;
continue;
}
break;
}
/* Clear depth buffer before starting to draw the Overworld:
* - all actors of the Overworld should cover any non-Overworld drawings
* - Overworld actors need to use the depth Buffer so that e.g. the pause screen
* is drawn above the inventory
*/
g_driver->clearDepthBuffer();
g_driver->drawDimPlane();
for (Actor *a : _activeActors) {
if (a->isInOverworld())
a->draw();
}
// Draw Primitives
for (PrimitiveObject *p : PrimitiveObject::getPool()) {
p->draw();
}
flagRefreshShadowMask(false);
}
void EMIEngine::storeSaveGameImage(SaveGame *state) {
unsigned int width = 160, height = 120;
Bitmap *screenshot = g_driver->getScreenshot(width, height, true);
if (!screenshot) {
warning("Unable to store screenshot.");
return;
}
// screenshots are not using the whole size of the texture
// copy the actual screenshot to the correct position
unsigned int texWidth = 256, texHeight = 128;
unsigned int size = texWidth * texHeight;
Graphics::Surface tmp = screenshot->getData(0);
Graphics::Surface *buffer = tmp.scale(texWidth, texHeight, true);
buffer->convertToInPlace(Graphics::PixelFormat(2, 5, 6, 5, 0, 11, 5, 0, 0));
state->beginSection('SIMG');
uint16 *data = (uint16 *)buffer->getPixels();
for (unsigned int l = 0; l < size; l++) {
state->writeLEUint16(data[l]);
}
state->endSection();
delete screenshot;
buffer->free();
delete buffer;
}
void EMIEngine::temporaryStoreSaveGameImage() {
// store current rendered screen in g_driver
g_grim->updateDisplayScene();
g_driver->storeDisplay();
}
void EMIEngine::updateDrawMode() {
// For EMI, draw mode is just like normal mode with frozen frame time.
updateNormalMode();
}
void EMIEngine::invalidateTextObjectsSortOrder() {
_textObjectsSortOrderInvalidated = true;
}
void EMIEngine::invalidateActiveActorsList() {
GrimEngine::invalidateActiveActorsList();
invalidateSortOrder();
}
void EMIEngine::invalidateSortOrder() {
_sortOrderInvalidated = true;
}
bool EMIEngine::compareTextLayer(const TextObject *x, const TextObject *y) {
int xl = x->getLayer();
int yl = y->getLayer();
if (xl == yl) {
return x->getId() < y->getId();
} else {
return xl < yl;
}
}
bool EMIEngine::compareLayer(const Layer *x, const Layer *y) {
return x->getSortOrder() > y->getSortOrder();
}
void EMIEngine::drawTextObjects() {
sortTextObjects();
for (TextObject *t : _textObjects) {
t->draw();
}
}
void EMIEngine::sortTextObjects() {
if (!_textObjectsSortOrderInvalidated)
return;
_textObjectsSortOrderInvalidated = false;
_textObjects.clear();
for (TextObject *t : TextObject::getPool()) {
if (t->getStackLevel() == 0) {
_textObjects.push_back(t);
}
}
Common::sort(_textObjects.begin(), _textObjects.end(), compareTextLayer);
}
void EMIEngine::sortLayers() {
_layers.clear();
for (Layer *l : Layer::getPool()) {
_layers.push_back(l);
}
Common::sort(_layers.begin(), _layers.end(), compareLayer);
}
bool EMIEngine::compareActor(const Actor *x, const Actor *y) {
if (x->getEffectiveSortOrder() == y->getEffectiveSortOrder()) {
Set::Setup *setup = g_grim->getCurrSet()->getCurrSetup();
Math::Matrix4 camRot = setup->_rot;
Math::Vector3d xp(x->getWorldPos() - setup->_pos);
Math::Vector3d yp(y->getWorldPos() - setup->_pos);
xp = xp * camRot.getRotation();
yp = yp * camRot.getRotation();
if (fabs(xp.z() - yp.z()) < 0.001f) {
return x->getId() < y->getId();
} else {
return xp.z() > yp.z();
}
}
return x->getEffectiveSortOrder() > y->getEffectiveSortOrder();
}
void EMIEngine::sortActiveActorsList() {
if (!_sortOrderInvalidated) {
return;
}
_sortOrderInvalidated = false;
Common::sort(_activeActors.begin(), _activeActors.end(), compareActor);
}
} // end of namespace Grim

73
engines/grim/emi/emi.h Normal file
View File

@@ -0,0 +1,73 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef EMI_ENGINE_H
#define EMI_ENGINE_H
#include "engines/grim/grim.h"
#include "engines/grim/emi/layer.h"
namespace Grim {
class TextObject;
class Actor;
class EMIEngine : public GrimEngine {
public:
EMIEngine(OSystem *syst, uint32 gameFlags, GrimGameType gameType, Common::Platform platform, Common::Language language);
virtual ~EMIEngine();
const char *getUpdateFilename() override;
void pushText();
void popText();
void purgeText();
void invalidateActiveActorsList() override;
void invalidateTextObjectsSortOrder() override;
void invalidateSortOrder();
void sortActiveActorsList();
void temporaryStoreSaveGameImage();
void storeSaveGameImage(SaveGame *state) override;
private:
LuaBase *createLua() override;
void drawNormalMode() override;
void updateDrawMode() override;
static bool compareTextLayer(const TextObject *x, const TextObject *y);
void drawTextObjects() override;
static bool compareActor(const Actor *x, const Actor *y);
void sortTextObjects();
static bool compareLayer(const Layer *x, const Layer *y);
void sortLayers();
Common::List<TextObject *> _textObjects;
Common::List<Layer *> _layers;
bool _textObjectsSortOrderInvalidated;
bool _sortOrderInvalidated;
};
extern EMIEngine *g_emi;
} // end of namespace Grim
#endif

View File

@@ -0,0 +1,178 @@
/* 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 "engines/grim/emi/emi_registry.h"
#include "engines/grim/debug.h"
#include "audio/mixer.h"
#include "common/config-manager.h"
namespace Grim {
EmiRegistry *g_emiregistry = nullptr;
const char *EmiRegistry::_translTable[][2] = {
{"speech_mode", ""}, // Translated key not needed, see below
{"vocvolume", "speech_volume"},
{"sfxvolume", "sfx_volume"},
{"musvolume", "music_volume"},
{"textspeed", "talkspeed"},
{"gamma", "gamma"},
{"joystick_enabled", "joystick_enabled"},
{"analog_mode", "analog_joystick_mode"},
{"subtitles", "movie_subtitles"},
{"camera_mode", "camera_relative_mode"},
{"shadowfx", "shadow_effects"},
{"vocfx", "audio_effects"},
{"miscfx", "misc_video_effects"},
{"moviequality", "movie_quality"},
{"musicquality", "music_quality"},
{nullptr,nullptr}
};
const char *EmiRegistry::_boolValues[] = {
"joystick_enabled",
"analog_mode",
"subtitles",
"camera_mode",
"vocfx",
"moviequality",
"musicquality",
nullptr
};
EmiRegistry::EmiRegistry() {
int i = 0;
while (_translTable[i][0] != nullptr) {
_transMap[_translTable[i][0]] = _translTable[i][1];
++i;
}
i = 0;
while (_boolValues[i] != nullptr) {
_boolSet[_boolValues[i]] = true;
++i;
}
}
uint EmiRegistry::convertTalkSpeedToGUI(uint talkspeed) const {
return CLIP<uint>(talkspeed * 255 / 10, 0, 255);
}
uint EmiRegistry::convertTalkSpeedFromGUI(uint talkspeed) const {
return CLIP<uint>(talkspeed * 10 / 255, 1, 10);
}
const Common::String EmiRegistry::convertGammaToRegistry(float gamma) const {
return Common::String().format("%.2f", gamma);
}
float EmiRegistry::convertGammaFromRegistry(const Common::String &gamma) const {
float gamma_f;
sscanf(gamma.c_str(), "%f", &gamma_f);
return CLIP<float>(gamma_f, 0.5, 1.5);
}
uint EmiRegistry::convertVolumeToMixer(uint emiVolume) const {
float vol = float(emiVolume - 25)/(100 - 25)*Audio::Mixer::kMaxMixerVolume;
return CLIP<uint>(uint(vol), 0, Audio::Mixer::kMaxMixerVolume);
}
uint EmiRegistry::convertVolumeFromMixer(uint volume) const {
float vol = float(volume)*(100 - 25)/Audio::Mixer::kMaxMixerVolume + 25;
return CLIP<uint>(uint(vol), 0, Audio::Mixer::kMaxMixerVolume);
}
uint EmiRegistry::convertSpeechModeFromGUI(bool subtitles, bool speechMute) const {
if (!subtitles && !speechMute) // Speech only
return 2;
else if (subtitles && !speechMute) // Speech and subtitles
return 3;
else if (subtitles && speechMute) // Subtitles only
return 1;
else
warning("Wrong configuration: Both subtitles and speech are off. Assuming subtitles only");
return 1;
}
bool EmiRegistry::Get(const Common::String &key, float &res) const {
Debug::debug(Debug::Engine, "GetResidualVMPreference(%s)", key.c_str());
if (!_transMap.contains(key))
return false;
res = 0.;
if (key == "speech_mode") {
if (!(ConfMan.hasKey("subtitles") && ConfMan.hasKey("speech_mute")))
return false;
res = convertSpeechModeFromGUI(ConfMan.getBool("subtitles"), ConfMan.getBool("speech_mute"));
} else {
if (!(ConfMan.hasKey(_transMap[key])))
return false;
if (key == "vocvolume" || key == "sfxvolume" || key == "musvolume")
res = convertVolumeFromMixer(ConfMan.getInt(_transMap[key]));
else if (key == "textspeed")
res = convertTalkSpeedFromGUI(ConfMan.getInt(_transMap[key]));
else if (key == "gamma")
res = convertGammaFromRegistry(ConfMan.get(_transMap[key]));
else if (key == "shadowfx")
res = ConfMan.getBool(_transMap[key]) + 1;
else if (_boolSet.contains(key))
res = ConfMan.getBool(_transMap[key]);
else
res = ConfMan.getInt(_transMap[key]);
}
Debug::debug(Debug::Engine, "Pushing %f", res);
return true;
}
void EmiRegistry::Set(const Common::String &key, float &value) {
Debug::debug(Debug::Engine, "SetResidualVMPreference(%s, %f)", key.c_str(), value);
if (!_transMap.contains(key))
return;
uint value_i = uint(value);
if (key == "speech_mode") {
ConfMan.setBool("subtitles", (value_i == 1 || value_i == 3));
ConfMan.setBool("speech_mute", (value_i == 1));
} else if (key == "vocvolume" || key == "sfxvolume" || key == "musvolume")
ConfMan.setInt(_transMap[key], convertVolumeToMixer(value_i));
else if (key == "textspeed")
ConfMan.setInt(_transMap[key], convertTalkSpeedToGUI(value_i));
else if (key == "gamma")
ConfMan.set(_transMap[key], convertGammaToRegistry(value));
else if (key == "shadowfx")
ConfMan.setBool(_transMap[key], (value_i == 2));
else if (_boolSet.contains(key))
ConfMan.setBool(_transMap[key], (value_i == 1));
else
ConfMan.setInt(_transMap[key], value_i);
}
} // end of namespace Grim

View File

@@ -0,0 +1,63 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef EMI_REGISTRY_H
#define EMI_REGISTRY_H
#include "common/str.h"
#include "common/hashmap.h"
#include "common/hash-str.h"
namespace Grim {
class EmiRegistry {
public:
EmiRegistry();
~EmiRegistry() { }
bool Get(const Common::String &key, float &res) const;
void Set(const Common::String &key, float &value);
private:
uint convertVolumeToMixer(uint volume) const;
uint convertVolumeFromMixer(uint volume) const;
uint convertTalkSpeedToGUI(uint talkspeed) const;
uint convertTalkSpeedFromGUI(uint talkspeed) const;
bool convertSubtitlesToGUI(uint speechmode) const;
bool convertSpeechMuteToGUI(uint speechmode) const;
uint convertSpeechModeFromGUI(bool subtitles, bool speechMute) const;
const Common::String convertGammaToRegistry(float gamma) const;
float convertGammaFromRegistry(const Common::String &gamma) const;
typedef Common::HashMap<Common::String, bool, Common::IgnoreCase_Hash> StringSet;
Common::StringMap _transMap;
StringSet _boolSet;
static const char *_boolValues[];
static const char *_translTable[][2];
};
extern EmiRegistry *g_emiregistry;
} // end of namespace Grim
#endif

View File

@@ -0,0 +1,82 @@
/* 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/str.h"
#include "engines/grim/emi/layer.h"
#include "engines/grim/bitmap.h"
namespace Grim {
Layer::Layer() : _filename(""), _sortOrder(0), _frame(0), _bitmap(nullptr) {
}
Layer::Layer(const Common::String &filename, int sortOrder) : _filename(filename), _sortOrder(sortOrder), _frame(0) {
_bitmap = Bitmap::create(filename);
}
Layer::~Layer() {
delete _bitmap;
}
void Layer::draw() {
_bitmap->drawLayer(_frame);
}
void Layer::setFrame(int frame) {
int numframes = _bitmap->getNumLayers();
if (frame >= numframes || frame < 0) {
warning("Layer::setFrame: invalid frame number: %d, numLayers: %d", frame, numframes);
return;
}
_frame = frame;
}
void Layer::setSortOrder(int order) {
_sortOrder = order;
}
void Layer::advanceFrame(int num) {
_frame += num;
int numframes = _bitmap->getNumLayers();
_frame %= numframes;
}
void Layer::saveState(SaveGame *state) {
if (_bitmap) {
state->writeBool(true);
state->writeString(_bitmap->getFilename());
} else {
state->writeBool(false);
}
state->writeLESint32(_frame);
state->writeLESint32(_sortOrder);
}
void Layer::restoreState(SaveGame *state) {
bool hasBitmap = state->readBool();
if (hasBitmap)
_bitmap = Bitmap::create(state->readString());
_frame = state->readLESint32();
_sortOrder = state->readLESint32();
}
}

59
engines/grim/emi/layer.h Normal file
View File

@@ -0,0 +1,59 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef GRIM_LAYER_H
#define GRIM_LAYER_H
#include "common/endian.h"
#include "engines/grim/pool.h"
namespace Grim {
class Bitmap;
class Layer : public PoolObject<Layer> {
public:
Layer();
Layer(const Common::String &filename, int sortOrder);
~Layer();
static int32 getStaticTag() { return MKTAG('L','A','Y','R'); }
void draw();
void setFrame(int frame);
void setSortOrder(int order);
void advanceFrame(int num);
int getSortOrder() const { return _sortOrder; }
void saveState(SaveGame *state);
void restoreState(SaveGame *state);
private:
Common::String _filename;
Bitmap *_bitmap;
int _sortOrder;
int _frame;
};
}
#endif

914
engines/grim/emi/lua_v2.cpp Normal file
View File

@@ -0,0 +1,914 @@
/* 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/endian.h"
#include "common/savefile.h"
#include "graphics/surface.h"
#include "engines/grim/emi/lua_v2.h"
#include "engines/grim/emi/emi_registry.h"
#include "engines/grim/emi/sound/emisound.h"
#include "engines/grim/lua/lauxlib.h"
#include "engines/grim/resource.h"
#include "engines/grim/set.h"
#include "engines/grim/grim.h"
#include "engines/grim/gfx_base.h"
#include "engines/grim/font.h"
#include "engines/grim/emi/layer.h"
#include "engines/grim/emi/emi.h"
#include "engines/grim/movie/movie.h"
namespace Grim {
void Lua_V2::UndimAll() {
g_driver->setDimLevel(0);
warning("Lua_V2::UndimAll: stub");
}
void Lua_V2::UndimRegion() {
lua_Object regionObj = lua_getparam(1);
if (lua_isnumber(regionObj)) {
int region = (int)lua_getnumber(regionObj);
// FIXME func(region);
warning("Lua_V2::UndimRegion: region: %d", region);
} else {
lua_pushnil();
// HACK: The demo uses this to undim the intro-screen.
// thus UndimRegion(nil) might mean UndimScreen.
g_driver->setDimLevel(0);
}
}
void Lua_V2::DimScreen() {
lua_Object dimObj = lua_getparam(1);
float dim = 0.6999f;
if (lua_isnumber(dimObj))
dim = lua_getnumber(dimObj);
g_driver->setDimLevel(dim);
// FIXME func(dim);
warning("Lua_V2::DimScreen: dim: %f", dim);
}
void Lua_V2::MakeCurrentSetup() {
lua_Object setupObj = lua_getparam(1);
if (lua_isnumber(setupObj)) {
int num = (int)lua_getnumber(setupObj);
g_grim->makeCurrentSetup(num);
} else if (lua_isstring(setupObj)) {
const char *setupName = lua_getstring(setupObj);
error("Lua_V2::MakeCurrentSetup: Not implemented case: setup: %s", setupName);
}
}
void Lua_V2::LockBackground() {
lua_Object filenameObj = lua_getparam(1);
if (!lua_isstring(filenameObj)) {
lua_pushnil();
return;
}
const char *filename = lua_getstring(filenameObj);
warning("Lua_V2::LockBackground, filename: %s", filename);
// FIXME: implement missing rest part of code
}
void Lua_V2::UnLockBackground() {
lua_Object filenameObj = lua_getparam(1);
if (!lua_isstring(filenameObj)) {
lua_pushnil();
return;
}
const char *filename = lua_getstring(filenameObj);
// FIXME: implement missin code
warning("Lua_V2::UnLockBackground: stub, filename: %s", filename);
}
void Lua_V2::MakeScreenTextures() {
lua_Object indexObj = lua_getparam(1);
if (!lua_isnil(indexObj) && lua_isnumber(indexObj)) {
//int index = (int)lua_getnumber(indexObj);
// The index does not seem to matter
g_driver->makeScreenTextures();
lua_pushnumber(1.0);
} else {
lua_pushnil();
}
}
void Lua_V2::ClearSpecialtyTexture() {
// This seems to be used in the save/load menu
// Not sure why the specialty textures need to be cleared.
warning("Lua_V2::ClearSpecialtyTexture: stub");
}
void Lua_V2::LoadBundle() {
lua_Object paramObj = lua_getparam(1);
if (lua_isstring(paramObj) || lua_isnil(paramObj)) {
const char *name = lua_getstring(paramObj);
// FIXME: implement missing function
/* if (!func(name))
lua_pushnil();
else*/
lua_pushnumber(1.0);
warning("Lua_V2::LoadBundle: stub, name: %s", name);
}
}
void Lua_V2::AreWeInternational() {
if (g_grim->getGameLanguage() != Common::EN_ANY)
lua_pushnumber(1.0);
}
void Lua_V2::GetCPUSpeed() {
lua_pushnumber(500); // anything above 333 make best configuration
}
// This should be correct, judging by the Demo
// the only real difference from L1 is the lack of looping
void Lua_V2::StartMovie() {
lua_Object name = lua_getparam(1);
lua_Object subtitlesObj = lua_getparam(2);
if (!lua_isstring(name)) {
lua_pushnil();
return;
}
Lua_V1::CleanBuffer();
bool showSubtitles = false;
if (lua_isnumber(subtitlesObj)) {
if ((int)lua_getnumber(subtitlesObj)) {
showSubtitles = true;
}
}
if (g_grim->getGameFlags() & ADGF_DEMO) {
showSubtitles = true;
}
GrimEngine::EngineMode prevEngineMode = g_grim->getMode();
g_grim->setMode(GrimEngine::SmushMode);
g_grim->setMovieSubtitle(nullptr);
bool result = g_movie->play(lua_getstring(name), false, 0, 0, true, showSubtitles);
if (!result)
g_grim->setMode(prevEngineMode);
pushbool(result);
// The following line causes issues after 9547a9b61674546077301bf09f89a2d120046d8e
//g_grim->setMode(GrimEngine::SmushMode);
}
void Lua_V2::EscapeMovie() {
g_movie->stop();
}
void Lua_V2::IsMoviePlaying() {
pushbool(g_movie->isPlaying());
}
void Lua_V2::SetActiveCD() {
lua_Object cdObj = lua_getparam(1);
int cd = (int)lua_getnumber(cdObj);
if (cd == 1 || cd == 2) {
warning("Lua_V2::GetActiveCD: set to CD: %d", cd);
// FIXME
lua_pushnumber(1.0);
}
}
void Lua_V2::GetActiveCD() {
// FIXME: return current CD number 1 or 2, original can also avoid push any numer
warning("Lua_V2::GetActiveCD: return const CD 1");
lua_pushnumber(1);
}
void Lua_V2::PurgeText() {
g_emi->purgeText();
}
void Lua_V2::GetFontDimensions() {
lua_Object fontObj = lua_getparam(1);
if (!lua_isstring(fontObj))
return;
const char *fontName = lua_getstring(fontObj);
Font *font = Font::getByFileName(fontName);
if (!font) {
font = g_resourceloader->loadFont(fontName);
}
if (font) {
int32 h = font->getBaseOffsetY();
int32 w = font->getFontWidth();
lua_pushnumber(w);
lua_pushnumber(h);
} else {
warning("Lua_V2::GetFontDimensions for font '%s': returns 0,0", fontName);
lua_pushnumber(0.f);
lua_pushnumber(0.f);
}
}
void Lua_V2::GetTextCharPosition() {
lua_Object textObj = lua_getparam(1);
lua_Object posObj = lua_getparam(2);
if (lua_isuserdata(textObj) && lua_tag(textObj) == MKTAG('T', 'E', 'X', 'T')) {
TextObject *textObject = gettextobject(textObj);
int pos = (int)lua_getnumber(posObj);
float textPos = textObject->getTextCharPosition(pos);
lua_pushnumber(textPos / 320.f);
}
}
void Lua_V2::GetTextObjectDimensions() {
lua_Object textObj = lua_getparam(1);
if (lua_isuserdata(textObj) && lua_tag(textObj) == MKTAG('T', 'E', 'X', 'T')) {
TextObject *textObject = gettextobject(textObj);
lua_pushnumber(textObject->getBitmapWidth() / 320.f);
lua_pushnumber(textObject->getBitmapHeight() / 240.f);
}
}
void Lua_V2::ToggleOverworld() {
lua_Object boolObj = lua_getparam(1);
bool backToNormal = (lua_isnil(boolObj) == 0);
if (backToNormal) {
GrimEngine::EngineMode previous = g_grim->getPreviousMode();
g_grim->setPreviousMode(GrimEngine::OverworldMode);
// HACK: ToggleOverworld is only called after we load a save game.
// However, the engine saved PreviousMode as OverworldMode.
// Reset it to normal here.
if (previous == GrimEngine::OverworldMode)
previous = GrimEngine::NormalMode;
g_grim->setMode(previous);
} else {
GrimEngine::EngineMode previous = g_grim->getMode();
g_grim->setPreviousMode(previous);
g_grim->setMode(GrimEngine::OverworldMode);
}
}
void Lua_V2::ClearOverworld() {
warning("Lua_V2::ClearOverworld: implement opcode");
}
void Lua_V2::ScreenshotForSavegame() {
g_emi->temporaryStoreSaveGameImage();
}
void Lua_V2::EngineDisplay() {
// dummy
}
void Lua_V2::SetAmbientLight() {
// dummy
}
void Lua_V2::Display() {
// dummy
}
void Lua_V2::GetCameraPitch() {
Set *set = g_grim->getCurrSet();
if (set == nullptr) {
lua_pushnil();
return;
}
Set::Setup *setup = set->getCurrSetup();
float pitch;
if (g_grim->getGameType() == GType_MONKEY4) {
setup->getRotation(nullptr, nullptr, &pitch);
} else {
setup->getRotation(nullptr, &pitch, nullptr);
}
lua_pushnumber(pitch);
}
void Lua_V2::GetCameraYaw() {
Set *set = g_grim->getCurrSet();
if (set == nullptr) {
lua_pushnil();
return;
}
Set::Setup *setup = set->getCurrSetup();
float yaw;
if (g_grim->getGameType() == GType_MONKEY4) {
setup->getRotation(nullptr, &yaw, nullptr);
} else {
setup->getRotation(&yaw, nullptr, nullptr);
}
lua_pushnumber(yaw);
}
void Lua_V2::GetCameraRoll() {
Set *set = g_grim->getCurrSet();
if (set == nullptr) {
lua_pushnil();
return;
}
Set::Setup *setup = set->getCurrSetup();
float roll;
if (g_grim->getGameType() == GType_MONKEY4) {
setup->getRotation(&roll, nullptr, nullptr);
} else {
setup->getRotation(nullptr, nullptr, &roll);
}
lua_pushnumber(roll);
}
void Lua_V2::PitchCamera() {
lua_Object pitchObj = lua_getparam(1);
if (!lua_isnumber(pitchObj)) {
error("Lua_V2::PitchCamera - Parameter is not a number!");
return;
}
Set *set = g_grim->getCurrSet();
if (set == nullptr)
return;
Set::Setup *setup = set->getCurrSetup();
setup->setPitch(Math::Angle(lua_getnumber(pitchObj)));
}
void Lua_V2::YawCamera() {
lua_Object yawObj = lua_getparam(1);
if (!lua_isnumber(yawObj)) {
error("Lua_V2::YawCamera - Parameter is not a number!");
return;
}
Set *set = g_grim->getCurrSet();
if (set == nullptr)
return;
Set::Setup *setup = set->getCurrSetup();
setup->setYaw(Math::Angle(lua_getnumber(yawObj)));
}
void Lua_V2::RollCamera() {
lua_Object rollObj = lua_getparam(1);
if (!lua_isnumber(rollObj)) {
error("Lua_V2::RollCamera - Parameter is not a number!");
return;
}
Set *set = g_grim->getCurrSet();
if (set == nullptr)
return;
Set::Setup *setup = set->getCurrSetup();
setup->setRoll(Math::Angle(lua_getnumber(rollObj)));
}
void Lua_V2::PushText() {
g_emi->pushText();
}
void Lua_V2::PopText() {
g_emi->popText();
}
void Lua_V2::GetSectorName() {
lua_Object xObj = lua_getparam(1);
lua_Object yObj = lua_getparam(2);
lua_Object zObj = lua_getparam(3);
if (!lua_isnumber(xObj) || !lua_isnumber(yObj) || !lua_isnumber(zObj)) {
lua_pushnil();
return;
}
float x, y, z;
x = lua_getnumber(xObj);
y = lua_getnumber(yObj);
z = lua_getnumber(zObj);
Math::Vector3d pos(x, y, z);
Set *set = g_grim->getCurrSet();
Sector *sector = set->findPointSector(pos, Sector::NoneType);
if (sector) {
lua_pushstring(sector->getName().c_str());
}
}
void Lua_V2::GammaEnabled() {
warning("Lua_V2::GammaEnabled: implement opcode, pushing nil");
lua_pushnil();
}
void Lua_V2::FileFindFirst() {
lua_Object extObj = lua_getparam(1);
if (!lua_isstring(extObj)) {
lua_pushnil();
return;
}
FileFindDispose();
const char *extension = lua_getstring(extObj);
if (0 == strncmp(extension, "Saves/", 6))
extension += 6;
// _menus.lua: saveload_menu.get_file_list searches for *.gsv.
// This avoids conflicts with grim saves.
if (0 == strcmp(extension, "*.gsv"))
extension = "efmi###.gsv";
Common::SaveFileManager *saveFileMan = g_system->getSavefileManager();
g_grim->_listFiles = saveFileMan->listSavefiles(extension);
Common::sort(g_grim->_listFiles.begin(), g_grim->_listFiles.end());
g_grim->_listFilesIter = g_grim->_listFiles.begin();
if (g_grim->_listFilesIter == g_grim->_listFiles.end())
lua_pushnil();
else
FileFindNext();
}
void Lua_V2::ThumbnailFromFile() {
lua_Object texIdObj = lua_getparam(1);
lua_Object filenameObj = lua_getparam(2);
if (!lua_isnumber(texIdObj) || !lua_isstring(filenameObj)) {
warning("Lua_V2::ThumbnailFromFile: wrong parameters");
return;
}
int index = (int)lua_getnumber(texIdObj);
Common::String filename(lua_getstring(filenameObj));
if (g_grim->getGameType() == GType_MONKEY4 &&
g_grim->getGamePlatform() == Common::kPlatformPS2) {
filename += ".ps2";
}
int width = 256, height = 128;
SaveGame *savedState = SaveGame::openForLoading(filename);
if (!savedState || !savedState->isCompatible()) {
delete savedState;
warning("Lua_V2::ThumbnailFromFile: savegame %s not compatible", filename.c_str());
lua_pushnil();
return;
}
int dataSize = savedState->beginSection('SIMG');
if (dataSize != width * height * 2) {
warning("Lua_V2::ThumbnailFromFile: savegame uses unexpected thumbnail size, ignore it");
lua_pushnil();
delete savedState;
return;
}
uint16 *data = new uint16[dataSize / 2];
for (int l = 0; l < dataSize / 2; l++) {
data[l] = savedState->readLEUint16();
}
Graphics::Surface buf;
buf.init(width, height, width * 2, (void *)data, Graphics::PixelFormat(2, 5, 6, 5, 0, 11, 5, 0, 0));
Bitmap *screenshot = new Bitmap(buf, width, height, "screenshot");
if (!screenshot) {
lua_pushnil();
warning("Lua_V2::ThumbnailFromFile: Could not restore screenshot from file %s", filename.c_str());
delete screenshot;
delete[] data;
delete savedState;
return;
}
screenshot->_data->convertToColorFormat(Graphics::PixelFormat::createFormatRGBA32());
g_driver->createSpecialtyTexture(index, (const uint8 *)screenshot->getData(0).getPixels(), width, height);
delete screenshot;
delete[] data;
savedState->endSection();
delete savedState;
pushbool(true);
}
void Lua_V2::GetMemoryCardId() {
// 0 - No mem card
// 1 - Not formatted
// 2 - Not enough space
// 3 - Error occurred
lua_pushnumber(4);
}
void Lua_V2::SetReplayMode() {
lua_Object intObj = lua_getparam(1);
lua_Object strObj = lua_getparam(2);
if (!lua_isnumber(intObj) || (!lua_isnil(strObj) && !lua_isstring(strObj))) {
warning("Lua_V2::SetReplayMode: wrong parameters");
return;
}
int num = lua_getnumber(intObj);
if (lua_isstring(strObj)) {
const char *str = lua_getstring(strObj);
warning("SetReplayMode(%d, %s)", num, str);
} else {
warning("SetReplayMode(%d)", num);
}
}
void Lua_V2::LocalizeString() {
char msgId[50], buf[1000];
lua_Object strObj = lua_getparam(1);
msgId[0] = 0;
if (lua_isstring(strObj)) {
const char *str = lua_getstring(strObj);
Common::String msg = parseMsgText(str, msgId);
Common::sprintf_s(buf, "/%s/%s", msgId, msg.c_str());
lua_pushstring(buf);
}
}
void Lua_V2::OverWorldToScreen() { // TODO
lua_Object param1 = lua_getparam(1);
lua_Object param2 = lua_getparam(2);
lua_Object param3 = lua_getparam(3);
float x = 0, y = 0, z = 0;
if (!lua_isnumber(param1) || !lua_isnumber(param2) || !lua_isnumber(param3)) {
error("Param not a number for OverWorldToScreen");
} else {
x = lua_getnumber(param1);
y = lua_getnumber(param2);
z = lua_getnumber(param3);
}
warning("Stub function: OverWorldToScreen(%f, %f, %f) - returning 0,0", x, y, z);
lua_pushnumber(0);
lua_pushnumber(0);
}
void Lua_V2::WorldToScreen() {
lua_Object xObj = lua_getparam(1);
lua_Object yObj = lua_getparam(2);
lua_Object zObj = lua_getparam(3);
if (!lua_isnumber(xObj) || !lua_isnumber(yObj) || !lua_isnumber(zObj)) {
lua_pushnumber(0.0);
lua_pushnumber(0.0);
return;
}
float x = lua_getnumber(xObj);
float y = lua_getnumber(yObj);
float z = lua_getnumber(zObj);
Math::Vector3d pos = Math::Vector3d(x, y, z);
const Set::Setup *setup = g_emi->getCurrSet()->getCurrSetup();
Math::Matrix4 view = setup->_rot;
view.transpose();
pos -= setup->_pos;
pos = view.getRotation() * pos;
pos.z() = -pos.z();
Math::Matrix4 proj = GfxBase::makeProjMatrix(setup->_fov, setup->_nclip, setup->_fclip);
proj.transpose();
Math::Vector4d screen = proj * Math::Vector4d(pos.x(), pos.y(), pos.z(), 1.0);
screen /= screen.w();
lua_pushnumber(screen.x());
lua_pushnumber(screen.y());
}
void Lua_V2::NewLayer() {
lua_Object param1 = lua_getparam(1);
lua_Object param2 = lua_getparam(2);
lua_Object param3 = lua_getparam(3);
const char *til = nullptr;
int sortorder = 0; // zero = 0;
if (lua_isstring(param1) && lua_isnumber(param2) && lua_isnumber(param3)) {
til = lua_getstring(param1);
sortorder = (int)lua_getnumber(param2);
// This one is always specified, but also always 0...
//zero = (int)lua_getnumber(param3);
Layer *layer = new Layer(til, sortorder);
// Need to return something that can be looked up later
lua_pushusertag(layer->getId(), MKTAG('L','A','Y','R'));
}
}
void Lua_V2::FreeLayer() {
lua_Object param1 = lua_getparam(1);
if (lua_isuserdata(param1) && lua_tag(param1) == MKTAG('L','A','Y','R')) {
int layer = (int)lua_getuserdata(param1);
Layer *l = Layer::getPool().getObject(layer);
delete l;
}
}
void Lua_V2::AdvanceLayerFrame() {
lua_Object param1 = lua_getparam(1);
lua_Object param2 = lua_getparam(2);
if (lua_isuserdata(param1) && lua_tag(param1) == MKTAG('L','A','Y','R') && lua_isnumber(param2)) {
int layer = (int)lua_getuserdata(param1);
int one = (int)lua_getnumber(param2);
Layer *l = Layer::getPool().getObject(layer);
if (!l) {
warning("Lua_V2::AdvanceLayerFrame: no layer found");
return;
}
l->advanceFrame(one);
}
}
void Lua_V2::SetLayerFrame() {
lua_Object param1 = lua_getparam(1);
lua_Object param2 = lua_getparam(2);
if (lua_isuserdata(param1) && lua_tag(param1) == MKTAG('L','A','Y','R') && lua_isnumber(param2)) {
int layer = (int)lua_getuserdata(param1);
int one = (int)lua_getnumber(param2);
Layer *l = Layer::getPool().getObject(layer);
l->setFrame(one);
}
}
void Lua_V2::SetLayerSortOrder() {
lua_Object param1 = lua_getparam(1);
lua_Object param2 = lua_getparam(2);
if (lua_isuserdata(param1) && lua_tag(param1) == MKTAG('L','A','Y','R') && lua_isnumber(param2)) {
int layer = (int)lua_getuserdata(param1);
int sortorder = (int)lua_getnumber(param2);
Layer *l = Layer::getPool().getObject(layer);
l->setSortOrder(sortorder);
} else {
warning("Lua_V2::SetLayerSortOrder: wrong parameters");
}
}
// Stub function for builtin functions not yet implemented
/*static void stubWarning(const char *funcName) {
warning("Stub function: %s", funcName);
}*/
static void stubError(const char *funcName) {
error("Stub function: %s", funcName);
}
#define STUB_FUNC(name) void name() { stubWarning(#name); }
#define STUB_FUNC2(name) void name() { stubError(#name); }
// Opcodes more or less differ to Grim Lua_V1::* LUA_OPCODEs
// STUB_FUNC2(Lua_V2::SetActorWalkChore)
// STUB_FUNC2(Lua_V2::SetActorTurnChores)
// STUB_FUNC2(Lua_V2::SetActorRestChore)
// STUB_FUNC2(Lua_V2::SetActorMumblechore)
// STUB_FUNC2(Lua_V2::SetActorTalkChore)
// STUB_FUNC2(Lua_V2::SetActorLookRate)
// STUB_FUNC2(Lua_V2::GetActorLookRate)
// STUB_FUNC2(Lua_V2::GetVisibleThings)
// STUB_FUNC2(Lua_V2::GetActorRot)
// STUB_FUNC2(Lua_V2::LockSet)
// STUB_FUNC2(Lua_V2::UnLockSet)
// STUB_FUNC2(Lua_V2::PlaySound)
// STUB_FUNC2(Lua_V2::IsSoundPlaying)
// STUB_FUNC2(Lua_V2::MakeSectorActive)
// STUB_FUNC2(Lua_V2::TurnActorTo)
// STUB_FUNC2(Lua_V2::GetAngleBetweenActors)
// STUB_FUNC2(Lua_V2::ImStartSound)
// STUB_FUNC2(Lua_V2::ImGetSfxVol)
// STUB_FUNC2(Lua_V2::ImGetVoiceVol)
// STUB_FUNC2(Lua_V2::ImGetMusicVol)
// STUB_FUNC2(Lua_V2::ImSetSequence)
// STUB_FUNC2(Lua_V2::ChangeTextObject)
// STUB_FUNC2(Lua_V2::GetTextCharPosition)
// STUB_FUNC2(Lua_V2::SetOffscreenTextPos)
// STUB_FUNC2(Lua_V2::FadeInChore)
// STUB_FUNC2(Lua_V2::FadeOutChore)
// STUB_FUNC2(Lua_V2::SetLightPosition)
// STUB_FUNC2(Lua_V2::GetAngleBetweenVectors)
// STUB_FUNC2(Lua_V2::IsPointInSector)
// Stubbed functions with semi-known arguments:
// TODO: Verify and implement these: (And add type-checking), also rename params
void Lua_V2::NukeAllScriptLocks() {
warning("Lua_V2::NukeAllScriptLocks() - TODO: Implement opcode");
}
void Lua_V2::FRUTEY_Begin() {
lua_Object param1 = lua_getparam(1);
if (!lua_isstring(param1))
error("Lua_V2::FRUTEY_Begin - Unknown parameters");
const char *paramText = lua_getstring(param1);
error("Lua_V2::FRUTEY_Begin(%s) - TODO: Implement opcode", paramText);
}
void Lua_V2::FRUTEY_End() {
error("Lua_V2::FRUTEY_End() - TODO: Implement opcode");
}
void Lua_V2::RenderModeUser() {
lua_Object param1 = lua_getparam(1);
if (!lua_isnil(param1) && g_grim->getMode() != GrimEngine::DrawMode) {
g_grim->setPreviousMode(g_grim->getMode());
g_movie->pause(true);
g_emiSound->pause(true);
g_grim->setMode(GrimEngine::DrawMode);
} else if (lua_isnil(param1) && g_grim->getMode() == GrimEngine::DrawMode) {
g_movie->pause(false);
g_emiSound->pause(false);
g_grim->setMode(g_grim->getPreviousMode());
}
}
// Monkey specific LUA_OPCODEs only used for debug
STUB_FUNC2(Lua_V2::ToggleDebugDraw)
STUB_FUNC2(Lua_V2::ToggleDrawCameras)
STUB_FUNC2(Lua_V2::ToggleDrawLights)
STUB_FUNC2(Lua_V2::ToggleDrawSectors)
STUB_FUNC2(Lua_V2::ToggleDrawBBoxes)
STUB_FUNC2(Lua_V2::ToggleDrawFPS)
STUB_FUNC2(Lua_V2::ToggleDrawPerformance)
STUB_FUNC2(Lua_V2::ToggleDrawActorStats)
STUB_FUNC2(Lua_V2::SectEditSelect)
STUB_FUNC2(Lua_V2::SectEditPlace)
STUB_FUNC2(Lua_V2::SectEditDelete)
STUB_FUNC2(Lua_V2::SectEditInsert)
STUB_FUNC2(Lua_V2::SectEditSortAdd)
STUB_FUNC2(Lua_V2::SectEditForgetIt)
// ResidualVM-hacks:
void Lua_V2::GetResidualVMPreference() {
lua_Object keyObj = lua_getparam(1);
if (lua_isstring(keyObj)) {
const Common::String key = lua_getstring(keyObj);
float value;
if (g_emiregistry->Get(key, value))
lua_pushnumber(value);
else
lua_pushnil();
} else
lua_pushnil();
}
void Lua_V2::SetResidualVMPreference() {
lua_Object keyObj = lua_getparam(1);
lua_Object valueObj = lua_getparam(2);
if (lua_isstring(keyObj) && lua_isnumber(valueObj)) {
const Common::String key = lua_getstring(keyObj);
float value = lua_getnumber(valueObj);
g_emiregistry->Set(key, value);
}
}
struct luaL_reg monkeyMainOpcodes[] = {
// Monkey specific LUA_OPCODEs:
{ "ScreenshotForSavegame", LUA_OPCODE(Lua_V2, ScreenshotForSavegame) },
{ "GetActorWorldPos", LUA_OPCODE(Lua_V2, GetActorWorldPos) },
{ "SetActiveCD", LUA_OPCODE(Lua_V2, SetActiveCD) },
{ "GetActiveCD", LUA_OPCODE(Lua_V2, GetActiveCD) },
{ "AreWeInternational", LUA_OPCODE(Lua_V2, AreWeInternational) },
{ "MakeScreenTextures", LUA_OPCODE(Lua_V2, MakeScreenTextures) },
{ "ThumbnailFromFile", LUA_OPCODE(Lua_V2, ThumbnailFromFile) },
{ "ClearSpecialtyTexture", LUA_OPCODE(Lua_V2, ClearSpecialtyTexture) },
{ "UnloadActor", LUA_OPCODE(Lua_V2, UnloadActor) },
{ "PutActorInOverworld", LUA_OPCODE(Lua_V2, PutActorInOverworld) },
{ "RemoveActorFromOverworld", LUA_OPCODE(Lua_V2, RemoveActorFromOverworld) },
{ "ClearOverworld", LUA_OPCODE(Lua_V2, ClearOverworld) },
{ "ToggleOverworld", LUA_OPCODE(Lua_V2, ToggleOverworld) },
{ "ActorStopMoving", LUA_OPCODE(Lua_V2, ActorStopMoving) },
{ "SetActorFOV", LUA_OPCODE(Lua_V2, SetActorFOV) },
{ "SetActorLighting", LUA_OPCODE(Lua_V2, SetActorLighting) },
{ "SetActorHeadLimits", LUA_OPCODE(Lua_V2, SetActorHeadLimits) },
{ "ActorActivateShadow", LUA_OPCODE(Lua_V2, ActorActivateShadow) },
{ "EnableActorPuck", LUA_OPCODE(Lua_V2, EnableActorPuck) },
{ "SetActorGlobalAlpha", LUA_OPCODE(Lua_V2, SetActorGlobalAlpha) },
{ "SetActorLocalAlpha", LUA_OPCODE(Lua_V2, SetActorLocalAlpha) },
{ "SetActorSortOrder", LUA_OPCODE(Lua_V2, SetActorSortOrder) },
{ "GetActorSortOrder", LUA_OPCODE(Lua_V2, GetActorSortOrder) },
{ "AttachActor", LUA_OPCODE(Lua_V2, AttachActor) },
{ "DetachActor", LUA_OPCODE(Lua_V2, DetachActor) },
{ "IsChoreValid", LUA_OPCODE(Lua_V2, IsChoreValid) },
{ "IsChorePlaying", LUA_OPCODE(Lua_V2, IsChorePlaying) },
{ "IsChoreLooping", LUA_OPCODE(Lua_V2, IsChoreLooping) },
{ "SetChoreLooping", LUA_OPCODE(Lua_V2, SetChoreLooping) },
{ "StopActorChores", LUA_OPCODE(Lua_V2, StopActorChores) },
{ "PlayChore", LUA_OPCODE(Lua_V2, PlayChore) },
{ "StopChore", LUA_OPCODE(Lua_V2, StopChore) },
{ "PauseChore", LUA_OPCODE(Lua_V2, PauseChore) },
{ "AdvanceChore", LUA_OPCODE(Lua_V2, AdvanceChore) },
{ "CompleteChore", LUA_OPCODE(Lua_V2, CompleteChore) },
{ "LockChore", LUA_OPCODE(Lua_V2, LockChore) },
{ "UnlockChore", LUA_OPCODE(Lua_V2, UnlockChore) },
{ "LockChoreSet", LUA_OPCODE(Lua_V2, LockChoreSet) },
{ "UnlockChoreSet", LUA_OPCODE(Lua_V2, UnlockChoreSet) },
{ "LockBackground", LUA_OPCODE(Lua_V2, LockBackground) },
{ "UnLockBackground", LUA_OPCODE(Lua_V2, UnLockBackground) },
{ "EscapeMovie", LUA_OPCODE(Lua_V2, EscapeMovie) },
{ "StopAllSounds", LUA_OPCODE(Lua_V2, StopAllSounds) },
{ "LoadSound", LUA_OPCODE(Lua_V2, LoadSound) },
{ "FreeSound", LUA_OPCODE(Lua_V2, FreeSound) },
{ "PlayLoadedSound", LUA_OPCODE(Lua_V2, PlayLoadedSound) },
{ "StopSound", LUA_OPCODE(Lua_V2, StopSound) },
{ "SetGroupVolume", LUA_OPCODE(Lua_V2, SetGroupVolume) },
{ "GetSoundVolume", LUA_OPCODE(Lua_V2, GetSoundVolume) },
{ "SetSoundVolume", LUA_OPCODE(Lua_V2, SetSoundVolume) },
{ "EnableAudioGroup", LUA_OPCODE(Lua_V2, EnableAudioGroup) },
{ "EnableVoiceFX", LUA_OPCODE(Lua_V2, EnableVoiceFX) },
{ "PlaySoundFrom", LUA_OPCODE(Lua_V2, PlaySoundFrom) },
{ "PlayLoadedSoundFrom", LUA_OPCODE(Lua_V2, PlayLoadedSoundFrom) },
{ "SetReverb", LUA_OPCODE(Lua_V2, SetReverb) },
{ "UpdateSoundPosition", LUA_OPCODE(Lua_V2, UpdateSoundPosition) },
{ "ImSelectSet", LUA_OPCODE(Lua_V2, ImSelectSet) },
{ "ImStateHasLooped", LUA_OPCODE(Lua_V2, ImStateHasLooped) },
{ "ImStateHasEnded", LUA_OPCODE(Lua_V2, ImStateHasEnded) },
{ "ImPushState", LUA_OPCODE(Lua_V2, ImPushState) },
{ "ImPopState", LUA_OPCODE(Lua_V2, ImPopState) },
{ "ImFlushStack", LUA_OPCODE(Lua_V2, ImFlushStack) },
{ "ImGetMillisecondPosition", LUA_OPCODE(Lua_V2, ImGetMillisecondPosition) },
{ "GetSectorName", LUA_OPCODE(Lua_V2, GetSectorName) },
{ "GetCameraYaw", LUA_OPCODE(Lua_V2, GetCameraYaw) },
{ "YawCamera", LUA_OPCODE(Lua_V2, YawCamera) },
{ "GetCameraPitch", LUA_OPCODE(Lua_V2, GetCameraPitch) },
{ "PitchCamera", LUA_OPCODE(Lua_V2, PitchCamera) },
{ "RollCamera", LUA_OPCODE(Lua_V2, RollCamera) },
{ "UndimAll", LUA_OPCODE(Lua_V2, UndimAll) },
{ "UndimRegion", LUA_OPCODE(Lua_V2, UndimRegion) },
{ "GetCPUSpeed", LUA_OPCODE(Lua_V2, GetCPUSpeed) },
{ "NewLayer", LUA_OPCODE(Lua_V2, NewLayer) },
{ "FreeLayer", LUA_OPCODE(Lua_V2, FreeLayer) },
{ "SetLayerSortOrder", LUA_OPCODE(Lua_V2, SetLayerSortOrder) },
{ "SetLayerFrame", LUA_OPCODE(Lua_V2, SetLayerFrame) },
{ "AdvanceLayerFrame", LUA_OPCODE(Lua_V2, AdvanceLayerFrame) },
{ "PushText", LUA_OPCODE(Lua_V2, PushText) },
{ "PopText", LUA_OPCODE(Lua_V2, PopText) },
{ "NukeAllScriptLocks", LUA_OPCODE(Lua_V2, NukeAllScriptLocks) },
{ "ToggleDebugDraw", LUA_OPCODE(Lua_V2, ToggleDebugDraw) },
{ "ToggleDrawCameras", LUA_OPCODE(Lua_V2, ToggleDrawCameras) },
{ "ToggleDrawLights", LUA_OPCODE(Lua_V2, ToggleDrawLights) },
{ "ToggleDrawSectors", LUA_OPCODE(Lua_V2, ToggleDrawSectors) },
{ "ToggleDrawBBoxes", LUA_OPCODE(Lua_V2, ToggleDrawBBoxes) },
{ "ToggleDrawFPS", LUA_OPCODE(Lua_V2, ToggleDrawFPS) },
{ "ToggleDrawPerformance", LUA_OPCODE(Lua_V2, ToggleDrawPerformance) },
{ "ToggleDrawActorStats", LUA_OPCODE(Lua_V2, ToggleDrawActorStats) },
{ "SectEditSelect", LUA_OPCODE(Lua_V2, SectEditSelect) },
{ "SectEditPlace", LUA_OPCODE(Lua_V2, SectEditPlace) },
{ "SectEditDelete", LUA_OPCODE(Lua_V2, SectEditDelete) },
{ "SectEditInsert", LUA_OPCODE(Lua_V2, SectEditInsert) },
{ "SectEditSortAdd", LUA_OPCODE(Lua_V2, SectEditSortAdd) },
{ "SectEditForgetIt", LUA_OPCODE(Lua_V2, SectEditForgetIt) },
{ "GammaEnabled", LUA_OPCODE(Lua_V2, GammaEnabled) },
{ "FRUTEY_Begin", LUA_OPCODE(Lua_V2, FRUTEY_Begin) },
{ "FRUTEY_End", LUA_OPCODE(Lua_V2, FRUTEY_End) },
{ "LocalizeString", LUA_OPCODE(Lua_V2, LocalizeString) },
// PS2:
{ "GetMemoryCardId", LUA_OPCODE(Lua_V2, GetMemoryCardId) },
{ "OverWorldToScreen", LUA_OPCODE(Lua_V2, OverWorldToScreen) },
{ "SetReplayMode", LUA_OPCODE(Lua_V2, SetReplayMode) },
// ResidualVM-hacks:
{ "GetResidualVMPreference", LUA_OPCODE(Lua_V2, GetResidualVMPreference) },
{ "SetResidualVMPreference", LUA_OPCODE(Lua_V2, SetResidualVMPreference) }
};
void Lua_V2::registerOpcodes() {
Lua_V1::registerOpcodes();
// Register main opcodes functions
luaL_openlib(monkeyMainOpcodes, ARRAYSIZE(monkeyMainOpcodes));
}
} // end of namespace Grim

194
engines/grim/emi/lua_v2.h Normal file
View File

@@ -0,0 +1,194 @@
/* 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 GRIM_LUA_V2
#define GRIM_LUA_V2
#include "engines/grim/lua_v1.h"
namespace Grim {
class Lua_V2 : public Lua_V1 {
public:
typedef Lua_V2 LuaClass;
void registerOpcodes() override;
protected:
bool findCostume(lua_Object costumeObj, Actor *actor, Costume **costume) override;
void setChoreAndCostume(lua_Object choreObj, lua_Object costumeObj, Actor *actor, Costume *&costume, int &chore);
static uint convertEmiVolumeToMixer(uint emiVolume);
static uint convertMixerVolumeToEmi(uint volume);
static const uint MAX_EMI_VOLUME = 100;
DECLARE_LUA_OPCODE(UndimAll);
DECLARE_LUA_OPCODE(SetActorLocalAlpha);
DECLARE_LUA_OPCODE(UndimRegion);
DECLARE_LUA_OPCODE(DimScreen) override;
DECLARE_LUA_OPCODE(MakeCurrentSetup) override;
DECLARE_LUA_OPCODE(SetActorGlobalAlpha);
DECLARE_LUA_OPCODE(ImGetMillisecondPosition);
DECLARE_LUA_OPCODE(RemoveActorFromOverworld);
DECLARE_LUA_OPCODE(UnloadActor);
DECLARE_LUA_OPCODE(SetActorWalkRate) override;
DECLARE_LUA_OPCODE(GetActorWalkRate) override;
DECLARE_LUA_OPCODE(SetActorTurnRate) override;
DECLARE_LUA_OPCODE(SetReverb);
DECLARE_LUA_OPCODE(LockBackground);
DECLARE_LUA_OPCODE(UnLockBackground);
DECLARE_LUA_OPCODE(LockChore);
DECLARE_LUA_OPCODE(IsActorChoring) override;
DECLARE_LUA_OPCODE(IsChoreValid);
DECLARE_LUA_OPCODE(IsChorePlaying);
DECLARE_LUA_OPCODE(StopChore);
DECLARE_LUA_OPCODE(AdvanceChore);
DECLARE_LUA_OPCODE(SetActorSortOrder);
DECLARE_LUA_OPCODE(ActorActivateShadow);
DECLARE_LUA_OPCODE(ActorStopMoving);
DECLARE_LUA_OPCODE(ActorLookAt) override;
DECLARE_LUA_OPCODE(PutActorInOverworld);
DECLARE_LUA_OPCODE(GetActorWorldPos);
DECLARE_LUA_OPCODE(MakeScreenTextures);
DECLARE_LUA_OPCODE(PutActorInSet) override;
DECLARE_LUA_OPCODE(LoadBundle) override;
DECLARE_LUA_OPCODE(AreWeInternational);
DECLARE_LUA_OPCODE(ImSetState) override;
DECLARE_LUA_OPCODE(EnableVoiceFX);
DECLARE_LUA_OPCODE(SetGroupVolume);
DECLARE_LUA_OPCODE(EnableAudioGroup);
DECLARE_LUA_OPCODE(ImSelectSet);
DECLARE_LUA_OPCODE(GetActorChores) override;
DECLARE_LUA_OPCODE(PlayActorChore) override;
DECLARE_LUA_OPCODE(StopActorChores);
DECLARE_LUA_OPCODE(SetActorLighting);
DECLARE_LUA_OPCODE(SetActorCollisionMode) override;
DECLARE_LUA_OPCODE(SetActorCollisionScale) override;
DECLARE_LUA_OPCODE(GetActorPuckVector) override;
DECLARE_LUA_OPCODE(SetActorHeadLimits);
DECLARE_LUA_OPCODE(SetActorHead) override;
DECLARE_LUA_OPCODE(SetActorFOV);
DECLARE_LUA_OPCODE(AttachActor);
DECLARE_LUA_OPCODE(DetachActor);
DECLARE_LUA_OPCODE(GetCPUSpeed);
DECLARE_LUA_OPCODE(StartMovie) override;
DECLARE_LUA_OPCODE(IsMoviePlaying) override;
DECLARE_LUA_OPCODE(SetActiveCD);
DECLARE_LUA_OPCODE(GetActiveCD);
DECLARE_LUA_OPCODE(PurgeText) override;
DECLARE_LUA_OPCODE(ImFlushStack);
DECLARE_LUA_OPCODE(LoadSound);
DECLARE_LUA_OPCODE(ImSetMusicVol) override;
DECLARE_LUA_OPCODE(ImSetSfxVol) override;
DECLARE_LUA_OPCODE(ImSetVoiceVol) override;
DECLARE_LUA_OPCODE(ImSetVoiceEffect) override;
DECLARE_LUA_OPCODE(ToggleOverworld);
DECLARE_LUA_OPCODE(ScreenshotForSavegame);
DECLARE_LUA_OPCODE(EngineDisplay) override;
DECLARE_LUA_OPCODE(SetAmbientLight) override;
DECLARE_LUA_OPCODE(Display) override;
DECLARE_LUA_OPCODE(ThumbnailFromFile);
DECLARE_LUA_OPCODE(ClearSpecialtyTexture);
DECLARE_LUA_OPCODE(ClearOverworld);
DECLARE_LUA_OPCODE(EnableActorPuck);
DECLARE_LUA_OPCODE(GetActorSortOrder);
DECLARE_LUA_OPCODE(IsChoreLooping);
DECLARE_LUA_OPCODE(SetChoreLooping);
DECLARE_LUA_OPCODE(PlayChore);
DECLARE_LUA_OPCODE(PauseChore);
DECLARE_LUA_OPCODE(CompleteChore);
DECLARE_LUA_OPCODE(UnlockChore);
DECLARE_LUA_OPCODE(LockChoreSet);
DECLARE_LUA_OPCODE(UnlockChoreSet);
DECLARE_LUA_OPCODE(EscapeMovie);
DECLARE_LUA_OPCODE(StopAllSounds);
DECLARE_LUA_OPCODE(FreeSound);
DECLARE_LUA_OPCODE(PlayLoadedSound);
DECLARE_LUA_OPCODE(StopSound);
DECLARE_LUA_OPCODE(PlaySound) override;
DECLARE_LUA_OPCODE(IsSoundPlaying) override;
DECLARE_LUA_OPCODE(GetSoundVolume);
DECLARE_LUA_OPCODE(SetSoundVolume);
DECLARE_LUA_OPCODE(PlaySoundFrom);
DECLARE_LUA_OPCODE(PlayLoadedSoundFrom);
DECLARE_LUA_OPCODE(UpdateSoundPosition);
DECLARE_LUA_OPCODE(ImStateHasLooped);
DECLARE_LUA_OPCODE(ImStateHasEnded);
DECLARE_LUA_OPCODE(ImPushState);
DECLARE_LUA_OPCODE(ImPopState);
DECLARE_LUA_OPCODE(ImPause) override;
DECLARE_LUA_OPCODE(ImResume) override;
DECLARE_LUA_OPCODE(GetSectorName);
DECLARE_LUA_OPCODE(GetCameraYaw);
DECLARE_LUA_OPCODE(YawCamera);
DECLARE_LUA_OPCODE(GetCameraPitch);
DECLARE_LUA_OPCODE(GetCameraRoll) override;
DECLARE_LUA_OPCODE(PitchCamera);
DECLARE_LUA_OPCODE(RollCamera);
DECLARE_LUA_OPCODE(NewLayer);
DECLARE_LUA_OPCODE(FreeLayer);
DECLARE_LUA_OPCODE(SetLayerSortOrder);
DECLARE_LUA_OPCODE(SetLayerFrame);
DECLARE_LUA_OPCODE(AdvanceLayerFrame);
DECLARE_LUA_OPCODE(PushText);
DECLARE_LUA_OPCODE(PopText);
DECLARE_LUA_OPCODE(NukeAllScriptLocks);
DECLARE_LUA_OPCODE(ToggleDebugDraw);
DECLARE_LUA_OPCODE(ToggleDrawCameras);
DECLARE_LUA_OPCODE(ToggleDrawLights);
DECLARE_LUA_OPCODE(ToggleDrawSectors);
DECLARE_LUA_OPCODE(ToggleDrawBBoxes);
DECLARE_LUA_OPCODE(ToggleDrawFPS);
DECLARE_LUA_OPCODE(ToggleDrawPerformance);
DECLARE_LUA_OPCODE(ToggleDrawActorStats);
DECLARE_LUA_OPCODE(SectEditSelect);
DECLARE_LUA_OPCODE(SectEditPlace);
DECLARE_LUA_OPCODE(SectEditDelete);
DECLARE_LUA_OPCODE(SectEditInsert);
DECLARE_LUA_OPCODE(SectEditSortAdd);
DECLARE_LUA_OPCODE(SectEditForgetIt);
DECLARE_LUA_OPCODE(FRUTEY_Begin);
DECLARE_LUA_OPCODE(FRUTEY_End);
DECLARE_LUA_OPCODE(GetFontDimensions) override;
DECLARE_LUA_OPCODE(GetTextObjectDimensions) override;
DECLARE_LUA_OPCODE(GetTextCharPosition) override;
DECLARE_LUA_OPCODE(SetActorRestChore) override;
DECLARE_LUA_OPCODE(SetActorWalkChore) override;
DECLARE_LUA_OPCODE(SetActorTurnChores) override;
DECLARE_LUA_OPCODE(SetActorTalkChore) override;
DECLARE_LUA_OPCODE(SetActorMumblechore) override;
DECLARE_LUA_OPCODE(GammaEnabled);
DECLARE_LUA_OPCODE(FileFindFirst) override;
DECLARE_LUA_OPCODE(WalkActorToAvoiding) override;
DECLARE_LUA_OPCODE(WalkActorVector) override;
DECLARE_LUA_OPCODE(LocalizeString) override;
DECLARE_LUA_OPCODE(WorldToScreen) override;
DECLARE_LUA_OPCODE(RenderModeUser) override;
// PS2:
DECLARE_LUA_OPCODE(GetMemoryCardId);
DECLARE_LUA_OPCODE(OverWorldToScreen);
DECLARE_LUA_OPCODE(SetReplayMode);
// ResidualVM-hacks:
DECLARE_LUA_OPCODE(GetResidualVMPreference);
DECLARE_LUA_OPCODE(SetResidualVMPreference);
};
} // end of namespace Grim
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,592 @@
/* 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 "audio/mixer.h"
#include "audio/audiostream.h"
#include "common/system.h"
#include "engines/grim/set.h"
#include "engines/grim/emi/sound/emisound.h"
#include "engines/grim/emi/lua_v2.h"
#include "engines/grim/emi/poolsound.h"
#include "engines/grim/lua/lua.h"
#include "engines/grim/debug.h"
#include "engines/grim/sound.h"
#include "engines/grim/grim.h"
#include "engines/grim/resource.h"
namespace Grim {
// Helper function, not called from LUA directly
uint Lua_V2::convertEmiVolumeToMixer(uint emiVolume) {
// EmiVolume range: 0 .. 100
// Mixer range: 0 .. kMaxChannelVolume
float vol = float(emiVolume) / MAX_EMI_VOLUME * Audio::Mixer::kMaxChannelVolume;
return CLIP<uint>(uint(vol), 0, Audio::Mixer::kMaxChannelVolume);
}
// Helper function, not called from LUA directly
uint Lua_V2::convertMixerVolumeToEmi(uint volume) {
float vol = float(volume) * MAX_EMI_VOLUME / Audio::Mixer::kMaxChannelVolume;
return CLIP<uint>(uint(vol), 0, MAX_EMI_VOLUME);
}
// Note: debug output for volume values uses the engine's mixer range
void Lua_V2::ImGetMillisecondPosition() {
lua_Object soundObj = lua_getparam(1);
if (lua_isnumber(soundObj)) {
int sound = (int)lua_getnumber(soundObj);
// FIXME int ms = func(sound);
// lua_pushnumber(ms);
// push -1 for now
// Currently a bit of guesswork, and probably wrong, as the stateId
// is ignored by emisound (which only has one music-track now).
uint32 msPos = g_emiSound->getMsPos(sound);
Debug::debug(Debug::Sound | Debug::Scripts, "Lua_V2::ImGetMillisecondPosition: sound: %d ms: %d", sound, msPos);
lua_pushnumber(msPos);
}
}
void Lua_V2::SetReverb() {
lua_Object eaxObj = lua_getparam(1);
lua_Object decayObj = lua_getparam(2);
lua_Object mixObj = lua_getparam(3);
lua_Object predelayObj = lua_getparam(4);
lua_Object dampingObj = lua_getparam(5);
if (!lua_isnumber(eaxObj))
return;
int eax = (int)lua_getnumber(eaxObj);
int param = 0;
float decay = -1;
float mix = -1;
float predelay = -1;
float damping = -1;
if (eax == 60) {
param = 26;
} else if (eax == 70) {
param = 27;
} else if (eax >= 0 && eax <= 25) {
param = eax;
// there is some table, initialy is like eax
} else {
return;
}
if (lua_isnumber(decayObj))
decay = lua_getnumber(decayObj);
if (lua_isnumber(mixObj))
mix = lua_getnumber(mixObj);
if (lua_isnumber(predelayObj))
predelay = lua_getnumber(predelayObj);
if (lua_isnumber(dampingObj))
damping = lua_getnumber(dampingObj);
Debug::debug(Debug::Sound | Debug::Scripts, "Lua_V2::SetReverb, eax: %d, decay: %f, mix: %f, predelay: %f, damping: %f", param, decay, mix, predelay, damping);
// FIXME: func(param, decay, mix, predelay, damping);
}
void Lua_V2::ImSetState() {
lua_Object stateObj = lua_getparam(1);
if (!lua_isnumber(stateObj))
return;
int state = (int)lua_getnumber(stateObj);
g_imuseState = state;
Debug::debug(Debug::Sound | Debug::Scripts, "Lua_V2::ImSetState: stub, state: %d", state);
}
void Lua_V2::ImStateHasEnded() {
lua_Object stateObj = lua_getparam(1);
if (!lua_isnumber(stateObj))
return;
int state = (int)lua_getnumber(stateObj);
pushbool(g_emiSound->stateHasEnded(state));
Debug::debug(Debug::Sound | Debug::Scripts, "Lua_V2::ImStateHasEnded: state %d.", state);
}
// TODO: Implement this:
void Lua_V2::ImStateHasLooped() {
lua_Object stateObj = lua_getparam(1);
if (!lua_isnumber(stateObj))
return;
int state = (int)lua_getnumber(stateObj);
pushbool(g_emiSound->stateHasLooped(state));
}
void Lua_V2::EnableVoiceFX() {
lua_Object stateObj = lua_getparam(1);
bool state = false;
if (!lua_isnil(stateObj))
state = true;
// FIXME: func(state);
Debug::debug(Debug::Sound | Debug::Scripts, "Lua_V2::EnableVoiceFX: implement opcode, state: %d", (int)state);
}
void Lua_V2::SetGroupVolume() {
lua_Object groupObj = lua_getparam(1);
lua_Object volumeObj = lua_getparam(2);
if (!lua_isnumber(groupObj))
return;
int group = (int)lua_getnumber(groupObj);
int volume = Audio::Mixer::kMaxChannelVolume;
if (lua_isnumber(volumeObj))
volume = convertEmiVolumeToMixer((int)lua_getnumber(volumeObj));
switch (group) {
case 1: // SFX
g_system->getMixer()->setVolumeForSoundType(Audio::Mixer::kSFXSoundType, volume);
g_system->getMixer()->setVolumeForSoundType(Audio::Mixer::kPlainSoundType, volume);
break;
case 2: // Voice
g_system->getMixer()->setVolumeForSoundType(Audio::Mixer::kSpeechSoundType, volume);
break;
case 3: // Music
g_system->getMixer()->setVolumeForSoundType(Audio::Mixer::kMusicSoundType, volume);
break;
default:
error("Lua_V2::SetGroupVolume - unknown group %d", group);
}
// FIXME: func(group, volume);
Debug::debug(Debug::Sound | Debug::Scripts, "Lua_V2::SetGroupVolume: group: %d, volume %d", group, volume);
}
void Lua_V2::EnableAudioGroup() {
lua_Object groupObj = lua_getparam(1);
lua_Object stateObj = lua_getparam(2);
if (!lua_isnumber(groupObj))
return;
int group = (int)lua_getnumber(groupObj);
bool state = false;
if (!lua_isnil(stateObj))
state = true;
// FIXME: func(group, state);
switch (group) {
case 1: // SFX
g_system->getMixer()->muteSoundType(Audio::Mixer::kSFXSoundType, !state);
g_system->getMixer()->muteSoundType(Audio::Mixer::kPlainSoundType, !state);
break;
case 2: // Voice
g_system->getMixer()->muteSoundType(Audio::Mixer::kSpeechSoundType, !state);
break;
case 3: // Music
g_system->getMixer()->muteSoundType(Audio::Mixer::kMusicSoundType, !state);
break;
default:
error("Lua_V2::EnableAudioGroup - unknown group %d", group);
}
Debug::debug(Debug::Sound | Debug::Scripts, "Lua_V2::EnableAudioGroup: group: %d, state %d", group, (int)state);
}
void Lua_V2::ImSelectSet() {
lua_Object qualityObj = lua_getparam(1);
if (lua_isnumber(qualityObj)) {
int quality = (int)lua_getnumber(qualityObj);
// FIXME: func(quality);
g_emiSound->selectMusicSet(quality);
Debug::debug(Debug::Sound | Debug::Scripts, "Lua_V2::ImSelectSet: quality mode: %d", quality);
}
}
void Lua_V2::ImFlushStack() {
// FIXME
Debug::debug(Debug::Sound | Debug::Scripts, "Lua_V2::ImFlushStack: currently guesswork");
g_emiSound->flushStack();
}
static Common::String addSoundSuffix(const char *fname) {
Common::String filename = fname;
if (!(g_grim->getGameFlags() & ADGF_DEMO)) {
if (g_grim->getGamePlatform() == Common::kPlatformPS2) {
filename += ".scx";
} else {
if (!filename.hasSuffix(".aif") && !filename.hasSuffix(".AIF")) {
filename += ".aif";
}
}
}
return filename;
}
void Lua_V2::LoadSound() {
lua_Object strObj = lua_getparam(1);
if (!lua_isstring(strObj))
return;
const char *str = lua_getstring(strObj);
Common::String filename = addSoundSuffix(str);
PoolSound *sound = new PoolSound(filename);
lua_pushusertag(sound->getId(), MKTAG('A', 'I', 'F', 'F'));
}
void Lua_V2::FreeSound() {
lua_Object idObj = lua_getparam(1);
if (!lua_isuserdata(idObj) || lua_tag(idObj) != MKTAG('A', 'I', 'F', 'F'))
return;
PoolSound *sound = PoolSound::getPool().getObject(lua_getuserdata(idObj));
delete sound;
}
void Lua_V2::PlayLoadedSound() {
lua_Object idObj = lua_getparam(1);
lua_Object loopingObj = lua_getparam(2);
lua_Object volumeObj = lua_getparam(3);
// FIXME: unknown parameter
/*lua_Object bool2Obj =*/ lua_getparam(4);
if (!lua_isuserdata(idObj) || lua_tag(idObj) != MKTAG('A', 'I', 'F', 'F')) {
// can't use error since it actually may happen:
// when entering the bait shop after the termites were already put on Mandrill's cane,
// the LUA code will not load the termite sound files but the script which starts
// the sounds is running anyway
warning("Lua_V2::PlayLoadedSound - ERROR: Unknown parameters");
return;
}
bool looping = !lua_isnil(loopingObj);
PoolSound *sound = PoolSound::getPool().getObject(lua_getuserdata(idObj));
if (!sound) {
warning("Lua_V2::PlayLoadedSound: can't find requested sound object");
return;
}
int volume = MAX_EMI_VOLUME;
if (!lua_isnumber(volumeObj)) {
// In the demo when the dart hits the balloon in the scumm bar, nil is passed
// to the volume parameter.
warning("Lua_V2::PlayLoadedSound - Unexpected parameter found, using default volume");
} else {
volume = (int)lua_getnumber(volumeObj);
}
sound->setVolume(convertEmiVolumeToMixer(volume));
sound->play(looping);
}
void Lua_V2::PlayLoadedSoundFrom() {
lua_Object idObj = lua_getparam(1);
lua_Object xObj = lua_getparam(2);
lua_Object yObj = lua_getparam(3);
lua_Object zObj = lua_getparam(4);
lua_Object volumeOrLoopingObj = lua_getparam(5);
lua_Object volumeObj = lua_getparam(6);
if (!lua_isuserdata(idObj) || lua_tag(idObj) != MKTAG('A', 'I', 'F', 'F')) {
warning("Lua_V2::PlayLoadedSoundFrom - ERROR: Unknown parameters");
return;
}
if (!lua_isnumber(xObj) || !lua_isnumber(yObj) || !lua_isnumber(zObj) ||
!lua_isnumber(volumeObj)) {
error("Lua_V2::PlayLoadedSoundFrom - ERROR: Unknown parameters");
return;
}
float x = lua_getnumber(xObj);
float y = lua_getnumber(yObj);
float z = lua_getnumber(zObj);
int volume = MAX_EMI_VOLUME;
bool looping = false;
if (lua_isnumber(volumeOrLoopingObj)) {
volume = (int)lua_getnumber(volumeOrLoopingObj);
// special handling if 5th parameter is a boolean
if (volume <= 1) {
looping = volume;
volume = (int)lua_getnumber(volumeObj);
}
} else {
volume = (int)lua_getnumber(volumeObj);
looping = !lua_isnil(volumeOrLoopingObj);
}
PoolSound *sound = PoolSound::getPool().getObject(lua_getuserdata(idObj));
if (!sound) {
warning("Lua_V2::PlayLoadedSoundFrom: can't find requested sound object");
return;
}
Math::Vector3d pos(x, y, z);
sound->setVolume(convertEmiVolumeToMixer(volume));
sound->playFrom(pos, looping);
}
void Lua_V2::StopSound() {
lua_Object idObj = lua_getparam(1);
if (!lua_isuserdata(idObj) || lua_tag(idObj) != MKTAG('A', 'I', 'F', 'F')) {
warning("Lua_V2::StopSound - ERROR: Unknown parameters");
return;
}
PoolSound *sound = PoolSound::getPool().getObject(lua_getuserdata(idObj));
if (!sound) {
warning("Lua_V2::StopSound: can't find requested sound object");
return;
}
sound->stop();
}
void Lua_V2::IsSoundPlaying() {
lua_Object idObj = lua_getparam(1);
if (!lua_isuserdata(idObj) || lua_tag(idObj) != MKTAG('A', 'I', 'F', 'F')) {
// can't use error since it actually may happen during normal operation
warning("Lua_V2::IsSoundPlaying - ERROR: Unknown parameters");
pushbool(false);
return;
}
PoolSound *sound = PoolSound::getPool().getObject(lua_getuserdata(idObj));
if (sound) {
if (sound->isPlaying()) {
pushbool(true);
return;
}
} else {
warning("Lua_V2::IsSoundPlaying: no sound track associated");
}
pushbool(false);
}
void Lua_V2::PlaySound() {
lua_Object strObj = lua_getparam(1);
lua_Object volumeObj = lua_getparam(2);
if (!lua_isstring(strObj)) {
error("Lua_V2::PlaySound - ERROR: Unknown parameters");
return;
}
const char *str = lua_getstring(strObj);
int volume = MAX_EMI_VOLUME;
if (!lua_isnumber(volumeObj)) {
warning("Lua_V2::PlaySound - Unexpected parameter(s) found, using default volume for %s", str);
} else {
volume = (int)lua_getnumber(volumeObj);
}
Common::String filename = addSoundSuffix(str);
if (!g_emiSound->startSfx(filename, convertEmiVolumeToMixer(volume))) {
Debug::debug(Debug::Sound | Debug::Scripts, "Lua_V2::PlaySound: Could not open sound '%s'", filename.c_str());
}
}
void Lua_V2::PlaySoundFrom() {
lua_Object strObj = lua_getparam(1);
lua_Object xObj = lua_getparam(2);
lua_Object yObj = lua_getparam(3);
lua_Object zObj = lua_getparam(4);
// FIXME: unknown parameter
lua_Object volumeOrUnknownObj = lua_getparam(5);
lua_Object volumeObj = lua_getparam(6);
int volume = MAX_EMI_VOLUME;
if (!lua_isstring(strObj)) {
error("Lua_V2::PlaySoundFrom - ERROR: Unknown parameters");
return;
}
if (!lua_isnumber(xObj) || !lua_isnumber(yObj) || !lua_isnumber(zObj)) {
error("Lua_V2::PlayLoadedSoundFrom - ERROR: Unknown parameters");
return;
}
float x = lua_getnumber(xObj);
float y = lua_getnumber(yObj);
float z = lua_getnumber(zObj);
// arg5 is optional and if present, it is FALSE
if (lua_isnumber(volumeObj)) {
volume = (int)lua_getnumber(volumeObj);
} else if (lua_isnumber(volumeOrUnknownObj)) {
volume = (int)lua_getnumber(volumeOrUnknownObj);
} else {
error("Lua_V2::PlaySoundFrom - ERROR: Unknown parameters");
return;
}
const char *str = lua_getstring(strObj);
Common::String filename = addSoundSuffix(str);
Math::Vector3d pos(x, y, z);
if (!g_emiSound->startSfxFrom(filename.c_str(), pos, convertEmiVolumeToMixer(volume))) {
Debug::debug(Debug::Sound | Debug::Scripts, "Lua_V2::PlaySoundFrom: Could not open sound '%s'", filename.c_str());
}
}
void Lua_V2::GetSoundVolume() {
lua_Object idObj = lua_getparam(1);
if (!lua_isuserdata(idObj) || lua_tag(idObj) != MKTAG('A', 'I', 'F', 'F')) {
error("Lua_V2::GetSoundVolume: Unknown Parameters");
return;
}
PoolSound *sound = PoolSound::getPool().getObject(lua_getuserdata(idObj));
if (sound) {
lua_pushnumber(convertMixerVolumeToEmi(sound->getVolume()));
} else {
warning("Lua_V2::GetSoundVolume: can't find sound track");
lua_pushnumber(convertMixerVolumeToEmi(Audio::Mixer::kMaxChannelVolume));
}
}
void Lua_V2::SetSoundVolume() {
lua_Object idObj = lua_getparam(1);
lua_Object volumeObj = lua_getparam(2);
if (!lua_isuserdata(idObj) || lua_tag(idObj) != MKTAG('A', 'I', 'F', 'F')) {
error("Lua_V2::SetSoundVolume: no valid sound id");
return;
}
if (!lua_isnumber(volumeObj)) {
error("Lua_V2::SetSoundVolume - ERROR: Unknown parameters");
return;
}
int volume = (int)lua_getnumber(volumeObj);
PoolSound *sound = PoolSound::getPool().getObject(lua_getuserdata(idObj));
if (sound) {
sound->setVolume(convertEmiVolumeToMixer(volume));
} else {
warning("Lua_V2:SetSoundVolume: can't find sound track");
}
}
void Lua_V2::UpdateSoundPosition() {
lua_Object idObj = lua_getparam(1);
lua_Object param2 = lua_getparam(2);
lua_Object param3 = lua_getparam(3);
lua_Object param4 = lua_getparam(4);
if (!lua_isuserdata(idObj) || lua_tag(idObj) != MKTAG('A', 'I', 'F', 'F'))
return;
if (!lua_isnumber(param2) || !lua_isnumber(param3) || !lua_isnumber(param4))
return;
float x = lua_getnumber(param2);
float y = lua_getnumber(param3);
float z = lua_getnumber(param4);
PoolSound *sound = PoolSound::getPool().getObject(lua_getuserdata(idObj));
if (!sound)
return;
Math::Vector3d pos(x, y, z);
sound->setPosition(pos);
}
void Lua_V2::ImSetMusicVol() {
// This only seems to be used in the demo.
lua_Object volumeObj = lua_getparam(1);
if (!lua_isnumber(volumeObj))
return;
int volume = (int)lua_getnumber(volumeObj);
Debug::debug(Debug::Sound | Debug::Scripts, "Lua_V2::ImSetMusicVol: implement opcode, wants volume %d", convertEmiVolumeToMixer(volume));
}
void Lua_V2::ImSetSfxVol() {
// This only seems to be used in the demo.
lua_Object volumeObj = lua_getparam(1);
if (!lua_isnumber(volumeObj))
return;
int volume = (int)lua_getnumber(volumeObj);
Debug::debug(Debug::Sound | Debug::Scripts, "Lua_V2::ImSetSfxVol: implement opcode, wants volume %d", convertEmiVolumeToMixer(volume));
}
void Lua_V2::ImSetVoiceVol() {
// This only seems to be used in the demo.
lua_Object volumeObj = lua_getparam(1);
if (!lua_isnumber(volumeObj))
return;
int volume = (int)lua_getnumber(volumeObj);
Debug::debug(Debug::Sound | Debug::Scripts, "Lua_V2::ImSetVoiceVol: implement opcode, wants volume %d", convertEmiVolumeToMixer(volume));
}
void Lua_V2::ImSetVoiceEffect() {
// This only seems to be used in the demo.
lua_Object strObj = lua_getparam(1);
if (!lua_isstring(strObj))
return;
const char *str = lua_getstring(strObj);
Debug::debug(Debug::Sound | Debug::Scripts, "Lua_V2::ImSetVoiceEffect: implement opcode, wants effect %s", str);
}
void Lua_V2::StopAllSounds() {
Debug::debug(Debug::Sound | Debug::Scripts, "Lua_V2::StopAllSounds: implement opcode");
}
void Lua_V2::ImPushState() {
lua_Object stateObj = lua_getparam(1);
//lua_Object unknownBoolObj = lua_getparam(2);
g_emiSound->pushStateToStack();
if (lua_isnumber(stateObj)) {
int state = (int)lua_getnumber(stateObj);
g_imuseState = state;
}
Debug::debug(Debug::Sound | Debug::Scripts, "Lua_V2::ImPushState: currently guesswork");
}
void Lua_V2::ImPopState() {
g_emiSound->popStateFromStack();
Debug::debug(Debug::Sound | Debug::Scripts, "Lua_V2::ImPopState: currently guesswork");
}
// Used in the demo only.
void Lua_V2::ImPause() {
g_emiSound->pause(true);
}
// Used in the demo only.
void Lua_V2::ImResume() {
g_emiSound->pause(false);
}
} // end of namespace Grim

View File

@@ -0,0 +1,484 @@
/* 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/endian.h"
#include "engines/grim/debug.h"
#include "engines/grim/grim.h"
#include "engines/grim/material.h"
#include "engines/grim/gfx_base.h"
#include "engines/grim/resource.h"
#include "engines/grim/set.h"
#include "engines/grim/emi/costumeemi.h"
#include "engines/grim/emi/modelemi.h"
#include "engines/grim/emi/animationemi.h"
#include "engines/grim/emi/skeleton.h"
namespace Grim {
struct Vector3int {
uint16 _x;
uint16 _y;
uint16 _z;
void setVal(uint16 x, uint16 y, uint16 z) {
_x = x; _y = y; _z = z;
}
};
struct BoneInfo {
int _incFac;
int _joint;
float _weight;
};
Common::String readLAString(Common::ReadStream *ms) {
int strLength = ms->readUint32LE();
char *readString = new char[strLength];
ms->read(readString, strLength);
Common::String retVal(readString);
delete[] readString;
return retVal;
}
void EMIMeshFace::loadFace(Common::SeekableReadStream *data) {
_flags = data->readUint32LE();
_hasTexture = data->readUint32LE();
if (_hasTexture)
_texID = data->readUint32LE();
_faceLength = data->readUint32LE();
_faceLength = _faceLength / 3;
int x = 0, y = 0, z = 0;
_indexes = new Vector3int[_faceLength];
int j = 0;
for (uint32 i = 0; i < _faceLength; i ++) {
if (g_grim->getGamePlatform() == Common::kPlatformPS2) {
x = data->readUint32LE();
y = data->readUint32LE();
z = data->readUint32LE();
} else {
x = data->readUint16LE();
y = data->readUint16LE();
z = data->readUint16LE();
}
_indexes[j++].setVal(x, y, z);
}
}
EMIMeshFace::~EMIMeshFace() {
delete[] _indexes;
}
void EMIModel::setTex(uint32 index) {
if (index < _numTextures && _mats[index]) {
_mats[index]->select();
g_driver->setBlendMode(_texFlags[index] & BlendAdditive);
}
}
void EMIModel::loadMesh(Common::SeekableReadStream *data) {
//int strLength = 0; // Useful for PS2-strings
Common::String nameString = readLAString(data);
for (uint l = 0; l < nameString.size(); ++l) {
if (nameString[l] == '\\') {
nameString.setChar('/', l);
}
}
_meshName = nameString;
_radius = data->readFloatLE();
_center->readFromStream(data);
_boxData->readFromStream(data);
_boxData2->readFromStream(data);
_numTexSets = data->readUint32LE();
_setType = data->readUint32LE();
_numTextures = data->readUint32LE();
_texNames = new Common::String[_numTextures];
_texFlags = new uint32[_numTextures];
for (uint32 i = 0; i < _numTextures; i++) {
_texNames[i] = readLAString(data);
_texFlags[i] = data->readUint32LE();
if (_texFlags[i] & ~(BlendAdditive)) {
Debug::debug(Debug::Models, "Model %s has unknown flags (%d) for texture %s", nameString.c_str(), _texFlags[i], _texNames[i].c_str());
}
}
prepareTextures();
int type = data->readUint32LE();
// Check that it is one of the known types
// 3 is no texture vertecies
// 18 is no normals
// 19 is regular
assert(type == 19 || type == 18 || type == 3);
_numVertices = data->readUint32LE();
_lighting = new Math::Vector3d[_numVertices];
for (int i = 0; i < _numVertices; i++) {
_lighting[i].set(1.0f, 1.0f, 1.0f);
}
// Vertices
_vertices = new Math::Vector3d[_numVertices];
_drawVertices = new Math::Vector3d[_numVertices];
for (int i = 0; i < _numVertices; i++) {
_vertices[i].readFromStream(data);
_drawVertices[i] = _vertices[i];
}
_normals = new Math::Vector3d[_numVertices];
_drawNormals = new Math::Vector3d[_numVertices];
if (type != 18) {
for (int i = 0; i < _numVertices; i++) {
_normals[i].readFromStream(data);
_drawNormals[i] = _normals[i];
}
}
_colorMap = new EMIColormap[_numVertices];
for (int i = 0; i < _numVertices; ++i) {
_colorMap[i].r = data->readByte();
_colorMap[i].g = data->readByte();
_colorMap[i].b = data->readByte();
_colorMap[i].a = data->readByte();
}
if (type != 3) {
_texVerts = new Math::Vector2d[_numVertices];
for (int i = 0; i < _numVertices; i++) {
_texVerts[i].readFromStream(data);
}
}
// Faces
_numFaces = data->readUint32LE();
if (data->eos()) {
_numFaces = 0;
_faces = nullptr;
return;
}
_faces = new EMIMeshFace[_numFaces];
for (uint32 j = 0; j < _numFaces; j++) {
_faces[j].setParent(this);
_faces[j].loadFace(data);
}
int hasBones = data->readUint32LE();
if (hasBones == 1) {
_numBones = data->readUint32LE();
_boneNames = new Common::String[_numBones];
for (int i = 0; i < _numBones; i++) {
_boneNames[i] = readLAString(data);
}
_numBoneInfos = data->readUint32LE();
_boneInfos = new BoneInfo[_numBoneInfos];
for (int i = 0; i < _numBoneInfos; i++) {
_boneInfos[i]._incFac = data->readUint32LE();
_boneInfos[i]._joint = data->readUint32LE();
_boneInfos[i]._weight = data->readFloatLE();
}
} else {
_numBones = 0;
_numBoneInfos = 0;
}
prepareForRender();
}
void EMIModel::setSkeleton(Skeleton *skel) {
if (_skeleton == skel) {
return;
}
_skeleton = skel;
if (!skel || !_numBoneInfos) {
return;
}
delete[] _vertexBoneInfo; _vertexBoneInfo = nullptr;
_vertexBoneInfo = new int[_numBoneInfos];
for (int i = 0; i < _numBoneInfos; i++) {
_vertexBoneInfo[i] = _skeleton->findJointIndex(_boneNames[_boneInfos[i]._joint]);
}
}
void EMIModel::prepareForRender() {
if (!_skeleton || !_vertexBoneInfo)
return;
for (int i = 0; i < _numVertices; i++) {
_drawVertices[i].set(0.0f, 0.0f, 0.0f);
_drawNormals[i].set(0.0f, 0.0f, 0.0f);
}
int boneVert = -1;
for (int i = 0; i < _numBoneInfos; i++) {
if (_boneInfos[i]._incFac == 1) {
boneVert++;
}
int jointIndex = _vertexBoneInfo[i];
const Math::Matrix4 &jointMatrix = _skeleton->_joints[jointIndex]._finalMatrix;
const Math::Matrix4 &bindPose = _skeleton->_joints[jointIndex]._absMatrix;
Math::Vector3d vert = _vertices[boneVert];
vert -= bindPose.getPosition();
vert = vert * bindPose.getRotation();
jointMatrix.transform(&vert, true);
_drawVertices[boneVert] += vert * _boneInfos[i]._weight;
Math::Vector3d normal = _normals[boneVert];
normal = normal * bindPose.getRotation();
jointMatrix.transform(&normal, false);
_drawNormals[boneVert] += normal * _boneInfos[i]._weight;
}
for (int i = 0; i < _numVertices; i++) {
_drawNormals[i].normalize();
}
g_driver->updateEMIModel(this);
}
void EMIModel::prepareTextures() {
_mats = new Material*[_numTextures];
for (uint32 i = 0; i < _numTextures; i++) {
_mats[i] = _costume->loadMaterial(_texNames[i], false);
}
}
void EMIModel::draw() {
prepareForRender();
Actor *actor = _costume->getOwner();
Math::Matrix4 modelToWorld = actor->getFinalMatrix();
if (!actor->isInOverworld()) {
Math::AABB bounds = calculateWorldBounds(modelToWorld);
if (bounds.isValid() && !g_grim->getCurrSet()->getFrustum().isInside(bounds))
return;
}
if (!g_driver->supportsShaders()) {
// If shaders are not available, we calculate lighting in software.
Actor::LightMode lightMode = actor->getLightMode();
if (lightMode != Actor::LightNone) {
if (lightMode != Actor::LightStatic)
_lightingDirty = true;
if (_lightingDirty) {
updateLighting(modelToWorld);
_lightingDirty = false;
}
}
} else {
if (actor->getLightMode() == Actor::LightNone) {
g_driver->disableLights();
}
}
// We will need to add a call to the skeleton, to get the modified vertices, but for now,
// I'll be happy with just static drawing
for (uint32 i = 0; i < _numFaces; i++) {
setTex(_faces[i]._texID);
g_driver->drawEMIModelFace(this, &_faces[i]);
}
if (g_driver->supportsShaders() && actor->getLightMode() == Actor::LightNone) {
g_driver->enableLights();
}
}
void EMIModel::updateLighting(const Math::Matrix4 &modelToWorld) {
// Current lighting implementation mimics the NormDyn mode of the original game, even if
// FastDyn is requested. We assume that FastDyn mode was used only for the purpose of
// performance optimization, but NormDyn mode is visually superior in all cases.
Common::Array<Grim::Light *> activeLights;
bool hasAmbient = false;
Actor *actor = _costume->getOwner();
for (Light *l : g_grim->getCurrSet()->getLights(actor->isInOverworld())) {
if (l->_enabled) {
activeLights.push_back(l);
if (l->_type == Light::Ambient)
hasAmbient = true;
}
}
for (int i = 0; i < _numVertices; i++) {
Math::Vector3d &result = _lighting[i];
result.set(0.0f, 0.0f, 0.0f);
Math::Vector3d normal = _drawNormals[i];
Math::Vector3d vertex = _drawVertices[i];
modelToWorld.transform(&vertex, true);
modelToWorld.transform(&normal, false);
for (uint j = 0; j < activeLights.size(); ++j) {
Light *l = activeLights[j];
float shade = l->_intensity;
if (l->_type != Light::Ambient) {
// Direction of incident light
Math::Vector3d dir = l->_dir;
if (l->_type != Light::Direct) {
dir = l->_pos - vertex;
float distSq = dir.getSquareMagnitude();
if (distSq > l->_falloffFar * l->_falloffFar)
continue;
dir.normalize();
if (distSq > l->_falloffNear * l->_falloffNear) {
float dist = sqrt(distSq);
float attn = 1.0f - (dist - l->_falloffNear) / (l->_falloffFar - l->_falloffNear);
shade *= attn;
}
}
if (l->_type == Light::Spot) {
float cosAngle = l->_dir.dotProduct(dir);
if (cosAngle < 0.0f)
continue;
float angle = acos(MIN(cosAngle, 1.0f));
if (angle > l->_penumbraangle)
continue;
if (angle > l->_umbraangle)
shade *= 1.0f - (angle - l->_umbraangle) / (l->_penumbraangle - l->_umbraangle);
}
float dot = MAX(0.0f, normal.dotProduct(dir));
shade *= dot;
}
Math::Vector3d color;
color.x() = l->_color.getRed() / 255.0f;
color.y() = l->_color.getGreen() / 255.0f;
color.z() = l->_color.getBlue() / 255.0f;
result += color * shade;
}
if (!hasAmbient) {
// If the set does not specify an ambient light, a default ambient light is used
// instead. The effect of this is visible for example in the set gmi.
result += Math::Vector3d(0.5f, 0.5f, 0.5f);
}
float max = MAX(MAX(result.x(), result.y()), result.z());
if (max > 1.0f) {
result.x() = result.x() / max;
result.y() = result.y() / max;
result.z() = result.z() / max;
}
}
}
void EMIModel::getBoundingBox(int *x1, int *y1, int *x2, int *y2) const {
int winX1, winY1, winX2, winY2;
g_driver->getScreenBoundingBox(this, &winX1, &winY1, &winX2, &winY2);
if (winX1 != -1 && winY1 != -1 && winX2 != -1 && winY2 != -1) {
*x1 = MIN(*x1, winX1);
*y1 = MIN(*y1, winY1);
*x2 = MAX(*x2, winX2);
*y2 = MAX(*y2, winY2);
}
}
Math::AABB EMIModel::calculateWorldBounds(const Math::Matrix4 &matrix) const {
Math::AABB bounds;
for (int i = 0; i < _numVertices; i++) {
bounds.expand(_drawVertices[i]);
}
bounds.transform(matrix);
return bounds;
}
EMIModel::EMIModel(const Common::String &filename, Common::SeekableReadStream *data, EMICostume *costume) :
_fname(filename), _costume(costume) {
_meshAlphaMode = Actor::AlphaOff;
_meshAlpha = 1.0;
_numVertices = 0;
_vertices = nullptr;
_drawVertices = nullptr;
_normals = nullptr;
_drawNormals = nullptr;
_colorMap = nullptr;
_texVerts = nullptr;
_numFaces = 0;
_faces = nullptr;
_numTextures = 0;
_texNames = nullptr;
_mats = nullptr;
_numBones = 0;
_boneInfos = nullptr;
_numBoneInfos = 0;
_vertexBoneInfo = nullptr;
_skeleton = nullptr;
_radius = 0;
_center = new Math::Vector3d();
_boxData = new Math::Vector3d();
_boxData2 = new Math::Vector3d();
_numTexSets = 0;
_setType = 0;
_boneNames = nullptr;
_lighting = nullptr;
_lightingDirty = true;
_texFlags = nullptr;
loadMesh(data);
g_driver->createEMIModel(this);
}
EMIModel::~EMIModel() {
g_driver->destroyEMIModel(this);
delete[] _vertices;
delete[] _drawVertices;
delete[] _normals;
delete[] _drawNormals;
delete[] _colorMap;
delete[] _texVerts;
delete[] _faces;
delete[] _texNames;
delete[] _mats;
delete[] _boneInfos;
delete[] _vertexBoneInfo;
delete[] _boneNames;
delete[] _lighting;
delete[] _texFlags;
delete _center;
delete _boxData;
delete _boxData2;
}
} // end of namespace Grim

148
engines/grim/emi/modelemi.h Normal file
View File

@@ -0,0 +1,148 @@
/* 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 GRIM_MODELEMI_H
#define GRIM_MODELEMI_H
#include "engines/grim/object.h"
#include "engines/grim/actor.h"
#include "math/matrix4.h"
#include "math/vector2d.h"
#include "math/vector3d.h"
#include "math/vector4d.h"
#include "math/aabb.h"
namespace Common {
class SeekableReadStream;
}
namespace Grim {
class Material;
struct EMIColormap {
unsigned char r, g, b, a;
};
// Todo: port this to math::vector
struct Vector3int;
class EMICostume;
class EMIModel;
struct BoneInfo;
struct Bone;
class Skeleton;
class EMIMeshFace {
public:
Vector3int *_indexes;
uint32 _indicesEBO;
uint32 _faceLength;
uint32 _numFaces;
uint32 _hasTexture;
uint32 _texID;
uint32 _flags;
EMIModel *_parent;
enum MeshFaceFlags {
kNoLighting = 0x20, // guessed, but distinctive for screen actors
kAlphaBlend = 0x10000,
kUnknownBlend = 0x40000 // used only in intro screen actors
};
EMIMeshFace() : _faceLength(0), _numFaces(0), _hasTexture(0), _texID(0), _flags(0), _indexes(NULL), _parent(NULL), _indicesEBO(0) { }
~EMIMeshFace();
void loadFace(Common::SeekableReadStream *data);
void setParent(EMIModel *m) { _parent = m; }
void render();
};
/* TODO: Remember to credit JohnDoe for his EMIMeshViewer, as most of the Skeletal
* math, and understandings comes from his Delphi-code.
*/
class EMIModel : public Object {
public:
enum TextureFlags {
BlendAdditive = 0x400
// There are more flags, but their purpose is currently unknown.
};
Common::String _meshName;
Actor::AlphaMode _meshAlphaMode;
float _meshAlpha;
int _numVertices;
Math::Vector3d *_vertices;
Math::Vector3d *_drawVertices;
Math::Vector3d *_normals;
Math::Vector3d *_drawNormals;
Math::Vector3d *_lighting;
EMIColormap *_colorMap;
Math::Vector2d *_texVerts;
uint32 _numFaces;
EMIMeshFace *_faces;
uint32 _numTextures;
Common::String *_texNames;
uint32 *_texFlags;
Material **_mats;
Skeleton *_skeleton;
int _numBones;
// Bone-stuff:
int _numBoneInfos;
BoneInfo *_boneInfos;
Common::String *_boneNames;
int *_vertexBoneInfo;
// Stuff we dont know how to use:
float _radius;
Math::Vector3d *_center;
Math::Vector3d *_boxData;
Math::Vector3d *_boxData2;
int _numTexSets;
int _setType;
Common::String _fname;
EMICostume *_costume;
void *_userData;
bool _lightingDirty;
public:
EMIModel(const Common::String &filename, Common::SeekableReadStream *data, EMICostume *costume);
~EMIModel();
void setTex(uint32 index);
void setSkeleton(Skeleton *skel);
void loadMesh(Common::SeekableReadStream *data);
void prepareForRender();
void prepareTextures();
void draw();
void updateLighting(const Math::Matrix4 &modelToWorld);
void getBoundingBox(int *x1, int *y1, int *x2, int *y2) const;
Math::AABB calculateWorldBounds(const Math::Matrix4 &matrix) const;
};
} // end of namespace Grim
#endif

View File

@@ -0,0 +1,118 @@
/* 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/str.h"
#include "engines/grim/emi/sound/emisound.h"
#include "engines/grim/emi/poolsound.h"
#include "engines/grim/resource.h"
namespace Grim {
PoolSound::PoolSound() : _filename(""), _loaded(false), _soundId(0) {
}
PoolSound::PoolSound(const Common::String &filename) : _filename(""), _loaded(false), _soundId(0) {
openFile(filename);
}
// Called when the engine restarts or Lua code calls FreeSound
PoolSound::~PoolSound() {
if (_loaded) {
g_emiSound->freeLoadedSound(_soundId);
}
}
void PoolSound::setVolume(int volume) {
if (_loaded) {
g_emiSound->setLoadedSoundVolume(_soundId, volume);
}
}
void PoolSound::setBalance(int balance) {
if (_loaded) {
g_emiSound->setLoadedSoundPan(_soundId, balance);
}
}
void PoolSound::setPosition(Math::Vector3d &pos) {
if (_loaded) {
g_emiSound->setLoadedSoundPosition(_soundId, pos);
}
}
void PoolSound::play(bool looping) {
if (_loaded) {
g_emiSound->playLoadedSound(_soundId, looping);
}
}
void PoolSound::playFrom(const Math::Vector3d &pos, bool looping) {
if (_loaded) {
g_emiSound->playLoadedSoundFrom(_soundId, pos, looping);
}
}
void PoolSound::stop() {
if (_loaded) {
g_emiSound->stopLoadedSound(_soundId);
}
}
int PoolSound::getVolume() {
if (_loaded) {
return g_emiSound->getLoadedSoundVolume(_soundId);
}
return 0;
}
bool PoolSound::isPlaying() {
if (_loaded) {
return g_emiSound->getLoadedSoundStatus(_soundId);
}
return false;
}
void PoolSound::openFile(const Common::String &filename) {
_filename = filename;
_loaded = g_emiSound->loadSfx(filename.c_str(), _soundId);
if (!_loaded) {
warning("Could not open PoolSound file %s", filename.c_str());
}
}
void PoolSound::saveState(SaveGame *state) {
state->writeBool(_loaded);
state->writeLESint32(_soundId);
}
void PoolSound::restoreState(SaveGame *state) {
if (state->saveMinorVersion() >= 21) {
_loaded = state->readBool();
_soundId = state->readLESint32();
} else {
bool hasStream = state->readBool();
if (hasStream)
openFile(state->readString());
}
}
}

View File

@@ -0,0 +1,59 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef GRIM_POOLSOUND_H
#define GRIM_POOLSOUND_H
#include "common/endian.h"
#include "engines/grim/pool.h"
#include "math/vector3d.h"
namespace Grim {
class PoolSound : public PoolObject<PoolSound> {
public:
PoolSound();
PoolSound(const Common::String &filename);
~PoolSound();
void openFile(const Common::String &filename);
void play(bool looping);
void playFrom(const Math::Vector3d &pos, bool looping);
void setVolume(int volume);
void setBalance(int balance);
void setPosition(Math::Vector3d &pos);
void stop();
int getVolume();
bool isPlaying();
void saveState(SaveGame *state);
void restoreState(SaveGame *state);
static int32 getStaticTag() { return MKTAG('A','I','F','F'); }
private:
Common::String _filename;
int _soundId;
bool _loaded;
};
}
#endif

View File

@@ -0,0 +1,228 @@
/* 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/stream.h"
#include "math/vector3d.h"
#include "math/vector4d.h"
#include "math/quat.h"
#include "engines/grim/debug.h"
#include "engines/grim/emi/animationemi.h"
#include "engines/grim/emi/skeleton.h"
namespace Grim {
#define ROTATE_OP 4
#define TRANSLATE_OP 3
Skeleton::Skeleton(const Common::String &filename, Common::SeekableReadStream *data) :
_numJoints(0), _joints(nullptr), _animLayers(nullptr) {
loadSkeleton(data);
}
Skeleton::~Skeleton() {
for (int i = 0; i < MAX_ANIMATION_LAYERS; ++i) {
delete[] _animLayers[i]._jointAnims;
}
delete[] _animLayers;
delete[] _joints;
}
void Skeleton::loadSkeleton(Common::SeekableReadStream *data) {
_numJoints = data->readUint32LE();
_joints = new Joint[_numJoints];
char inString[32];
for (int i = 0; i < _numJoints; i++) {
data->read(inString, 32);
_joints[i]._name = inString;
data->read(inString, 32);
_joints[i]._parent = inString;
_joints[i]._trans.readFromStream(data);
_joints[i]._quat.readFromStream(data);
_joints[i]._parentIndex = findJointIndex(_joints[i]._parent);
_jointsMap[_joints[i]._name] = i;
}
initBones();
resetAnim();
}
void Skeleton::initBone(int index) {
// The matrix should have identity at this point.
_joints[index]._quat.toMatrix(_joints[index]._relMatrix);
// Might need to be translate instead.
_joints[index]._relMatrix.setPosition(_joints[index]._trans);
if (_joints[index]._parentIndex == -1) {
_joints[index]._absMatrix = _joints[index]._relMatrix;
} else {
_joints[index]._absMatrix = _joints[_joints[index]._parentIndex]._absMatrix;
// Might be the other way around.
_joints[index]._absMatrix = _joints[index]._absMatrix * _joints[index]._relMatrix;
}
}
void Skeleton::initBones() {
for (int i = 0; i < _numJoints; i++) {
initBone(i);
}
_animLayers = new AnimationLayer[MAX_ANIMATION_LAYERS];
for (int i = 0; i < MAX_ANIMATION_LAYERS; ++i) {
_animLayers[i]._jointAnims = new JointAnimation[_numJoints];
}
}
void Skeleton::resetAnim() {
for (int i = 0; i < MAX_ANIMATION_LAYERS; ++i) {
AnimationLayer &layer = _animLayers[i];
for (int j = 0; j < _numJoints; ++j) {
JointAnimation &jointAnim = layer._jointAnims[j];
jointAnim._pos.set(0.f, 0.f, 0.f);
jointAnim._quat.set(0.f, 0.f, 0.f, 1.f);
jointAnim._transWeight = 0.0f;
jointAnim._rotWeight = 0.0f;
}
}
for (int i = 0; i < _numJoints; ++i) {
_joints[i]._animMatrix = _joints[i]._relMatrix;
_joints[i]._animQuat = _joints[i]._quat;
}
}
void Skeleton::animate() {
resetAnim();
// This first pass over the animations calculates bone-specific sums of blend weights for all
// animation layers. The sums must be pre-computed in order to be able to normalize the blend
// weights properly in the next step.
for (Common::List<AnimationStateEmi*>::iterator j = _activeAnims.begin(); j != _activeAnims.end(); ++j) {
(*j)->computeWeights();
}
// Now make a second pass over the animations to actually accumulate animation to layers.
for (Common::List<AnimationStateEmi*>::iterator j = _activeAnims.begin(); j != _activeAnims.end(); ++j) {
(*j)->animate();
}
// Blend the layers together in priority order to produce the final result. Highest priority
// layer will get as much weight as it wants, while the next highest priority will get the
// amount that remains and so on.
for (int i = 0; i < _numJoints; ++i) {
float remainingTransWeight = 1.0f;
float remainingRotWeight = 1.0f;
for (int j = MAX_ANIMATION_LAYERS - 1; j >= 0; --j) {
AnimationLayer &layer = _animLayers[j];
JointAnimation &jointAnim = layer._jointAnims[i];
if (remainingRotWeight > 0.0f && jointAnim._rotWeight != 0.0f) {
Math::Vector3d pos = _joints[i]._animMatrix.getPosition();
_joints[i]._animQuat = _joints[i]._animQuat.slerpQuat(_joints[i]._animQuat * jointAnim._quat, remainingRotWeight);
_joints[i]._animQuat.toMatrix(_joints[i]._animMatrix);
_joints[i]._animMatrix.setPosition(pos);
remainingRotWeight *= 1.0f - jointAnim._rotWeight;
}
if (remainingTransWeight > 0.0f && jointAnim._transWeight != 0.0f) {
Math::Vector3d pos = _joints[i]._animMatrix.getPosition();
Math::Vector3d delta = jointAnim._pos;
_joints[i]._animMatrix.setPosition(pos + delta * remainingTransWeight);
remainingTransWeight *= 1.0f - jointAnim._transWeight;
}
if (remainingRotWeight <= 0.0f && remainingTransWeight <= 0.0f)
break;
}
}
commitAnim();
}
void Skeleton::addAnimation(AnimationStateEmi *anim) {
_activeAnims.push_back(anim);
}
void Skeleton::removeAnimation(AnimationStateEmi *anim) {
_activeAnims.remove(anim);
}
void Skeleton::commitAnim() {
for (int m = 0; m < _numJoints; ++m) {
const Joint *parent = getParentJoint(&_joints[m]);
if (parent) {
_joints[m]._finalMatrix = parent->_finalMatrix * _joints[m]._animMatrix;
_joints[m]._finalQuat = parent->_finalQuat * _joints[m]._animQuat;
} else {
_joints[m]._finalMatrix = _joints[m]._animMatrix;
_joints[m]._finalQuat = _joints[m]._animQuat;
}
}
}
int Skeleton::findJointIndex(const Common::String &name) const {
JointMap::const_iterator it = _jointsMap.find(name);
if (it != _jointsMap.end())
return it->_value;
return -1;
}
bool Skeleton::hasJoint(const Common::String &name) const {
return name.empty() || findJointIndex(name) >= 0;
}
Joint *Skeleton::getJointNamed(const Common::String &name) const {
int idx = findJointIndex(name);
if (name.empty()) {
return & _joints[0];
} else if (idx == -1) {
warning("Skeleton has no joint named '%s'!", name.c_str());
return nullptr;
} else {
return & _joints[idx];
}
}
Joint *Skeleton::getParentJoint(const Joint *j) const {
assert(j);
if (j->_parentIndex == -1)
return nullptr;
return &_joints[j->_parentIndex];
}
int Skeleton::getJointIndex(const Joint *j) const {
int idx = j - _joints;
assert(idx >= 0 && idx < _numJoints);
return idx;
}
AnimationLayer* Skeleton::getLayer(int priority) const {
assert(priority >= 0 && priority < MAX_ANIMATION_LAYERS);
return &_animLayers[priority];
}
} // end of namespace Grim

103
engines/grim/emi/skeleton.h Normal file
View File

@@ -0,0 +1,103 @@
/* 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 GRIM_SKELETON_H
#define GRIM_SKELETON_H
#include "common/hashmap.h"
#include "common/hash-str.h"
#include "math/mathfwd.h"
#include "math/quat.h"
#include "engines/grim/object.h"
#include "engines/grim/actor.h"
namespace Common {
class SeekableReadStream;
}
namespace Grim {
class AnimationStateEmi;
class AnimationEmi;
struct Joint {
Common::String _name;
Common::String _parent;
Math::Vector3d _trans;
Math::Quaternion _quat;
int _parentIndex;
Math::Matrix4 _absMatrix;
Math::Matrix4 _relMatrix;
Math::Matrix4 _animMatrix;
Math::Quaternion _animQuat;
Math::Matrix4 _finalMatrix;
Math::Quaternion _finalQuat;
};
struct JointAnimation {
Math::Vector3d _pos;
Math::Quaternion _quat;
float _transWeight;
float _rotWeight;
};
struct AnimationLayer {
JointAnimation* _jointAnims;
};
class Skeleton : public Object {
void loadSkeleton(Common::SeekableReadStream *data);
void initBone(int index);
void initBones();
void resetAnim();
public:
// Note: EMI uses priority 5 at most.
static const int MAX_ANIMATION_LAYERS = 8;
int _numJoints;
Joint *_joints;
typedef Common::HashMap<Common::String, int, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo> JointMap;
JointMap _jointsMap;
Skeleton(const Common::String &filename, Common::SeekableReadStream *data);
~Skeleton();
void animate();
void commitAnim();
void addAnimation(AnimationStateEmi *anim);
void removeAnimation(AnimationStateEmi *anim);
int findJointIndex(const Common::String &name) const;
bool hasJoint(const Common::String &name) const;
Joint *getJointNamed(const Common::String &name) const;
Joint *getParentJoint(const Joint *j) const;
int getJointIndex(const Joint *j) const;
AnimationLayer* getLayer(int priority) const;
private:
AnimationLayer *_animLayers;
Common::List<AnimationStateEmi*> _activeAnims;
};
} // end of namespace Grim
#endif

View File

@@ -0,0 +1,101 @@
/* 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/mutex.h"
#include "audio/mixer.h"
#include "audio/audiostream.h"
#include "audio/decoders/aiff.h"
#include "engines/grim/debug.h"
#include "engines/grim/resource.h"
#include "engines/grim/emi/sound/aifftrack.h"
namespace Grim {
AIFFTrack::AIFFTrack(Audio::Mixer::SoundType soundType) {
_soundType = soundType;
_looping = false;
// A preloaded AIFF track may be played multiple times, so we don't
// want to dispose after playing. The destructor of SoundTrack will
// take care of disposing the stream instead.
_disposeAfterPlaying = DisposeAfterUse::NO;
}
AIFFTrack::~AIFFTrack() {
stop();
if (_handle) {
g_system->getMixer()->stopHandle(*_handle);
delete _handle;
}
}
bool AIFFTrack::openSound(const Common::String &filename, const Common::String &soundName, const Audio::Timestamp *start) {
Common::SeekableReadStream *file = g_resourceloader->openNewStreamFile(filename, true);
if (!file) {
Debug::debug(Debug::Sound, "Stream for %s not open", soundName.c_str());
return false;
}
_soundName = soundName;
Audio::RewindableAudioStream *aiffStream = Audio::makeAIFFStream(file, DisposeAfterUse::YES);
Audio::SeekableAudioStream *seekStream = dynamic_cast<Audio::SeekableAudioStream *>(aiffStream);
_stream = aiffStream;
if (start)
seekStream->seek(*start);
if (!_stream)
return false;
_handle = new Audio::SoundHandle();
return true;
}
void AIFFTrack::setLooping(bool looping) {
if (_looping == looping)
return;
_looping = looping;
if (looping && _stream) {
_stream = Audio::makeLoopingAudioStream(dynamic_cast<Audio::SeekableAudioStream *>(_stream), 0);
}
}
bool AIFFTrack::play() {
if (_stream) {
Audio::RewindableAudioStream *stream = dynamic_cast<Audio::RewindableAudioStream *>(_stream);
if (!_looping) {
stream->rewind();
}
return SoundTrack::play();
}
return false;
}
bool AIFFTrack::isPlaying() {
if (!_handle)
return false;
return g_system->getMixer()->isSoundHandleActive(*_handle);
}
Audio::Timestamp AIFFTrack::getPos() {
// FIXME: Return actual stream position.
return g_system->getMixer()->getSoundElapsedTime(*_handle);
}
} // end of namespace Grim

View File

@@ -0,0 +1,53 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef GRIM_AIFFTRACK_H
#define GRIM_AIFFTRACK_H
#include "common/str.h"
#include "common/stream.h"
#include "engines/grim/emi/sound/track.h"
namespace Audio {
class AudioStream;
class SoundHandle;
}
namespace Grim {
class AIFFTrack : public SoundTrack {
public:
AIFFTrack(Audio::Mixer::SoundType soundType);
~AIFFTrack();
bool openSound(const Common::String &filename, const Common::String &soundName, const Audio::Timestamp *start = nullptr) override;
bool isPlaying() override;
bool isStreamOpen() { return _stream != NULL; }
void setLooping(bool looping) override;
bool isLooping() const override { return _looping; }
bool play() override;
Audio::Timestamp getPos() override;
private:
bool _looping;
};
}
#endif

View File

@@ -0,0 +1,207 @@
/* 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 "audio/audiostream.h"
#include "audio/decoders/xa.h"
#include "common/memstream.h"
#include "common/textconsole.h"
#include "common/stream.h"
#include "engines/grim/emi/sound/codecs/scx.h"
namespace Grim {
SCXStream::SCXStream(Common::SeekableReadStream *stream, const Audio::Timestamp *start, DisposeAfterUse::Flag disposeAfterUse) {
static const uint32 stereoChannelNames[SCX_MAX_CHANNELS] = { MKTAG('L', 'E', 'F', 'T'), MKTAG('R', 'G', 'H', 'T') };
stream->readUint32BE(); // 'SCRX'
stream->readUint32LE();
_blockSize = stream->readUint16LE();
/* totalBlockSize = */ stream->readUint16LE();
if (_blockSize & 0xf)
error("Bad SCX block size %04x", _blockSize);
// Base our channel count based off the block size
_channels = (_blockSize == 0) ? 1 : 2;
stream->skip(12);
uint32 channelSize[SCX_MAX_CHANNELS];
for (int i = 0; i < _channels; i++) {
uint32 tag = stream->readUint32BE();
if (isStereo()) {
if (tag != stereoChannelNames[i])
error("Bad stereo channel tag found '%s'", tag2str(tag));
} else if (tag != MKTAG('M', 'O', 'N', 'O'))
error("Bad mono channel tag found '%s'", tag2str(tag));
channelSize[i] = stream->readUint32LE();
}
stream->seek(0x80);
uint32 leftRate = 0, rightRate = 0;
for (int i = 0; i < _channels; i++) {
if (stream->readUint32BE() != MKTAG('V', 'A', 'G', 'p'))
error("Bad VAG header");
/* uint32 version = */ stream->readUint32BE();
stream->readUint32BE();
stream->readUint32BE();
if (i == 0)
leftRate = stream->readUint32BE();
else
rightRate = stream->readUint32BE();
stream->skip(12); // skip useless info
stream->skip(16); // skip name
stream->skip(16); // skip zeroes
}
if (isStereo() && leftRate != rightRate)
error("Mismatching SCX rates");
_rate = leftRate;
if (isStereo()) {
// TODO: Make XAStream allow for appending data (similar to how ScummVM
// handles AAC/QDM2. For now, we de-interleave the XA ADPCM data and then
// re-interleave in readBuffer().
// Of course, in doing something that does better streaming, it would
// screw up the XA loop points. So, I'm not really sure what is best atm.
byte *leftOut = (byte*)malloc(channelSize[0]);
byte *rightOut = (byte*)malloc(channelSize[1]);
Common::MemoryWriteStream *leftStream = new Common::MemoryWriteStream(leftOut, channelSize[0]);
Common::MemoryWriteStream *rightStream = new Common::MemoryWriteStream(rightOut, channelSize[1]);
byte *buf = new byte[_blockSize];
while (stream->pos() < stream->size()) {
stream->read(buf, _blockSize);
leftStream->write(buf, _blockSize);
stream->read(buf, _blockSize);
rightStream->write(buf, _blockSize);
}
_fileStreams[0] = new Common::MemoryReadStream(leftOut, channelSize[0], DisposeAfterUse::YES);
_fileStreams[1] = new Common::MemoryReadStream(rightOut, channelSize[1], DisposeAfterUse::YES);
_xaStreams[0] = Audio::makeXAStream(_fileStreams[0], _rate);
_xaStreams[1] = Audio::makeXAStream(_fileStreams[1], _rate);
delete[] buf;
delete leftStream;
delete rightStream;
} else {
_fileStreams[0] = stream->readStream(channelSize[0]);
_fileStreams[1] = nullptr;
_xaStreams[0] = Audio::makeXAStream(_fileStreams[0], _rate);
_xaStreams[1] = nullptr;
}
if (start) {
// Read data from the sound stream until we hit the desired start position.
// We do this instead of seeking so the loop point gets set up properly.
int samples = (int)((int64)start->msecs() * _rate / 1000);
int16 temp[1024];
while (samples > 0) {
samples -= _xaStreams[0]->readBuffer(temp, samples < 1024 ? samples : 1024);
if (_xaStreams[1]) {
_xaStreams[1]->readBuffer(temp, samples < 1024 ? samples : 1024);
}
}
}
if (disposeAfterUse == DisposeAfterUse::YES)
delete stream;
}
SCXStream::~SCXStream() {
for (int i = 0; i < SCX_MAX_CHANNELS; i++)
delete _xaStreams[i];
}
int SCXStream::readBuffer(int16 *buffer, const int numSamples) {
if (isStereo()) {
// Needs to be divisible by the channel count
assert((numSamples % 2) == 0);
// TODO: As per above, this probably should do more actual streaming
// Decode enough data from each channel
int samplesPerChannel = numSamples / 2;
int16 *leftSamples = new int16[samplesPerChannel];
int16 *rightSamples = new int16[samplesPerChannel];
int samplesDecodedLeft = _xaStreams[0]->readBuffer(leftSamples, samplesPerChannel);
int samplesDecodedRight = _xaStreams[1]->readBuffer(rightSamples, samplesPerChannel);
assert(samplesDecodedLeft == samplesDecodedRight);
(void)samplesDecodedRight;
// Now re-interleave the data
int samplesDecoded = 0;
int16 *leftSrc = leftSamples, *rightSrc = rightSamples;
for (; samplesDecoded < samplesDecodedLeft * 2; samplesDecoded += 2) {
*buffer++ = *leftSrc++;
*buffer++ = *rightSrc++;
}
delete[] leftSamples;
delete[] rightSamples;
return samplesDecoded;
}
// Just read from the stream directly for mono
return _xaStreams[0]->readBuffer(buffer, numSamples);
}
bool SCXStream::rewind() {
if (!_xaStreams[0]->rewind())
return false;
return !isStereo() || _xaStreams[1]->rewind();
}
Audio::Timestamp SCXStream::getPos() const {
int32 pos = _fileStreams[0]->pos();
// Each XA ADPCM block of 16 bytes decompresses to 28 samples.
int32 samples = pos * 28 / 16;
uint32 msecs = (uint32)((int64)samples * 1000 / _rate);
return Audio::Timestamp(msecs);
}
SCXStream *makeSCXStream(Common::SeekableReadStream *stream, const Audio::Timestamp *start, DisposeAfterUse::Flag disposeAfterUse) {
if (stream->readUint32BE() != MKTAG('S', 'C', 'R', 'X')) {
delete stream;
return nullptr;
}
stream->seek(0);
return new SCXStream(stream, start, disposeAfterUse);
}
} // End of namespace Grim

View File

@@ -0,0 +1,71 @@
/* 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 GRIM_SCX_H
#define GRIM_SCX_H
namespace Common {
class SeekableReadStream;
}
namespace Grim {
// I've only ever seen up to two
#define SCX_MAX_CHANNELS 2
class SCXStream : public Audio::RewindableAudioStream {
public:
SCXStream(Common::SeekableReadStream *stream, const Audio::Timestamp *start, DisposeAfterUse::Flag disposeAfterUse);
~SCXStream();
bool isStereo() const override { return _channels == 2; }
bool endOfData() const override { return _xaStreams[0]->endOfData(); }
int getRate() const override { return _rate; }
int readBuffer(int16 *buffer, const int numSamples) override;
bool rewind() override;
Audio::Timestamp getPos() const;
private:
int _channels;
int _rate;
uint16 _blockSize;
Common::SeekableReadStream *_fileStreams[SCX_MAX_CHANNELS];
Audio::RewindableAudioStream *_xaStreams[SCX_MAX_CHANNELS];
};
/**
* Takes an input stream containing SCX sound data and creates
* a RewindableAudioStream from that.
*
* @param stream the SeekableReadStream from which to read the SCX data
* @param disposeAfterUse whether to delete the stream after use
* @return a new RewindableAudioStream, or NULL, if an error occurred
*/
SCXStream *makeSCXStream(
Common::SeekableReadStream *stream,
const Audio::Timestamp *start,
DisposeAfterUse::Flag disposeAfterUse = DisposeAfterUse::YES);
} // End of namespace Grim
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,146 @@
/* 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 GRIM_MSS_H
#define GRIM_MSS_H
#include "audio/mixer.h"
#include "common/str.h"
#include "common/stack.h"
#include "common/mutex.h"
#include "common/hashmap.h"
#include "math/vector3d.h"
namespace Grim {
class SoundTrack;
class SaveGame;
struct MusicEntry {
int _x;
int _y;
int _sync;
int _trim;
int _id;
Common::String _type;
Common::String _name;
Common::String _filename;
};
// Currently this class only implements the exact functions called in iMuse
// from Actor, to allow for splitting that into EMI-sound and iMuse without
// changing iMuse.
class EMISound {
public:
EMISound(int fps);
~EMISound();
bool startVoice(const Common::String &soundName, int volume = static_cast<int>(Audio::Mixer::kMaxChannelVolume), int pan = 64);
bool startSfx(const Common::String &soundName, int volume = static_cast<int>(Audio::Mixer::kMaxChannelVolume), int pan = 64);
bool startSfxFrom(const Common::String &soundName, const Math::Vector3d &pos, int volume = static_cast<int>(Audio::Mixer::kMaxChannelVolume));
bool getSoundStatus(const Common::String &soundName);
void stopSound(const Common::String &soundName);
int32 getPosIn16msTicks(const Common::String &soundName);
void setVolume(const Common::String &soundName, int volume);
void setPan(const Common::String &soundName, int pan); /* pan: 0 .. 127 */
bool loadSfx(const Common::String &soundName, int &id);
void playLoadedSound(int id, bool looping);
void playLoadedSoundFrom(int id, const Math::Vector3d &pos, bool looping);
void setLoadedSoundLooping(int id, bool looping);
void stopLoadedSound(int id);
void freeLoadedSound(int id);
void setLoadedSoundVolume(int id, int volume);
void setLoadedSoundPan(int id, int pan);
void setLoadedSoundPosition(int id, const Math::Vector3d &pos);
bool getLoadedSoundStatus(int id);
int getLoadedSoundVolume(int id);
void setMusicState(int stateId);
void selectMusicSet(int setId);
bool stateHasLooped(int stateId);
bool stateHasEnded(int stateId);
void restoreState(SaveGame *savedState);
void saveState(SaveGame *savedState);
void pushStateToStack();
void popStateFromStack();
void flushStack();
void pause(bool paused);
void flushTracks();
uint32 getMsPos(int stateId);
void updateSoundPositions();
private:
struct StackEntry {
int _state;
SoundTrack *_track;
};
typedef Common::List<SoundTrack *> TrackList;
TrackList _playingTracks;
SoundTrack *_musicTrack;
MusicEntry *_musicTable;
Common::String _musicPrefix;
Common::Stack<StackEntry> _stateStack;
// A mutex to avoid concurrent modification of the sound channels by the engine thread
// and the timer callback, which may run in a different thread.
Common::Mutex _mutex;
typedef Common::HashMap<int, SoundTrack *> TrackMap;
TrackMap _preloadedTrackMap;
int _curMusicState;
int _numMusicStates;
int _callbackFps;
int _curTrackId;
static void timerHandler(void *refConf);
void removeItem(SoundTrack *item);
TrackList::iterator getPlayingTrackByName(const Common::String &name);
void freeChannel(int32 channel);
void initMusicTable();
void callback();
void updateTrack(SoundTrack *track);
void freePlayingSounds();
void freeLoadedSounds();
SoundTrack *initTrack(const Common::String &soundName, Audio::Mixer::SoundType soundType, const Audio::Timestamp *start = nullptr) const;
SoundTrack *restartTrack(SoundTrack *track);
bool startSound(const Common::String &soundName, Audio::Mixer::SoundType soundType, int volume, int pan);
bool startSoundFrom(const Common::String &soundName, Audio::Mixer::SoundType soundType, const Math::Vector3d &pos, int volume);
void saveTrack(SoundTrack *track, SaveGame *savedState);
SoundTrack *restoreTrack(SaveGame *savedState);
MusicEntry *initMusicTableDemo(const Common::String &filename);
void initMusicTableRetail(MusicEntry *table, const Common::String &filename);
};
extern EMISound *g_emiSound;
}
#endif

View File

@@ -0,0 +1,223 @@
/* 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/mutex.h"
#include "audio/mixer.h"
#include "audio/audiostream.h"
#include "audio/decoders/mp3.h"
#include "engines/grim/debug.h"
#include "engines/grim/resource.h"
#include "engines/grim/textsplit.h"
#include "engines/grim/emi/sound/mp3track.h"
namespace Grim {
/**
* This is a an extension of Audio::SubLooppingAudioStream that adds a start
* time parameter as well as a getter for the stream position.
*/
class EMISubLoopingAudioStream : public Audio::AudioStream {
public:
EMISubLoopingAudioStream(Audio::SeekableAudioStream *stream, uint loops,
const Audio::Timestamp start,
const Audio::Timestamp loopStart,
const Audio::Timestamp loopEnd,
DisposeAfterUse::Flag disposeAfterUse = DisposeAfterUse::YES)
: _parent(stream, disposeAfterUse),
_pos(convertTimeToStreamPos(start, getRate(), isStereo())),
_loopStart(convertTimeToStreamPos(loopStart, getRate(), isStereo())),
_loopEnd(convertTimeToStreamPos(loopEnd, getRate(), isStereo())),
_done(false), _hasLooped(false) {
assert(loopStart < loopEnd);
if (!_parent->seek(_pos))
_done = true;
}
int readBuffer(int16 *buffer, const int numSamples) override {
if (_done)
return 0;
int framesLeft = MIN(_loopEnd.frameDiff(_pos), numSamples);
int framesRead = _parent->readBuffer(buffer, framesLeft);
_pos = _pos.addFrames(framesRead);
if (framesRead < framesLeft && _parent->endOfData()) {
// TODO: Proper error indication.
_done = true;
return framesRead;
}
else if (_pos == _loopEnd) {
if (!_parent->seek(_loopStart)) {
// TODO: Proper error indication.
_done = true;
return framesRead;
}
_pos = _loopStart;
framesLeft = numSamples - framesLeft;
_hasLooped = true;
return framesRead + readBuffer(buffer + framesRead, framesLeft);
}
else {
return framesRead;
}
}
bool hasLooped() const { return _hasLooped; }
bool endOfData() const override { return _done; }
bool isStereo() const override { return _parent->isStereo(); }
int getRate() const override { return _parent->getRate(); }
Audio::Timestamp getPos() const { return _pos; }
private:
Common::DisposablePtr<Audio::SeekableAudioStream> _parent;
Audio::Timestamp _pos;
Audio::Timestamp _loopStart, _loopEnd;
bool _done;
bool _hasLooped;
};
void MP3Track::parseRIFFHeader(Common::SeekableReadStream *data) {
uint32 tag = data->readUint32BE();
if (tag == MKTAG('R','I','F','F')) {
_endFlag = false;
data->seek(18, SEEK_CUR);
_channels = data->readByte();
data->readByte();
_freq = data->readUint32LE();
data->seek(6, SEEK_CUR);
_bits = data->readByte();
data->seek(5, SEEK_CUR);
_regionLength = data->readUint32LE();
_headerSize = 44;
} else {
error("Unknown file header");
}
}
MP3Track::JMMCuePoints MP3Track::parseJMMFile(const Common::String &filename) {
JMMCuePoints cuePoints;
Common::SeekableReadStream *stream = g_resourceloader->openNewStreamFile(filename);
if (stream) {
TextSplitter ts(filename, stream);
float startMs = 0.0f;
float loopStartMs = 0.0f, loopEndMs = 0.0f;
ts.scanString(".start %f", 1, &startMs);
if (ts.checkString(".jump"))
ts.scanString(".jump %f %f", 2, &loopEndMs, &loopStartMs);
// Use microsecond precision for the timestamps.
cuePoints._start = Audio::Timestamp(startMs / 1000, (int)(startMs * 1000) % 1000000, 1000000);
cuePoints._loopStart = Audio::Timestamp(loopStartMs / 1000, (int)(loopStartMs * 1000) % 1000000, 1000000);
cuePoints._loopEnd = Audio::Timestamp(loopEndMs / 1000, (int)(loopEndMs * 1000) % 1000000, 1000000);
}
delete stream;
return cuePoints;
}
MP3Track::MP3Track(Audio::Mixer::SoundType soundType) {
_soundType = soundType;
_headerSize = 0;
_regionLength = 0;
_freq = 0;
_bits = 0,
_channels = 0;
_endFlag = false;
_looping = false;
}
MP3Track::~MP3Track() {
stop();
if (_handle) {
g_system->getMixer()->stopHandle(*_handle);
delete _handle;
}
}
bool MP3Track::openSound(const Common::String &filename, const Common::String &soundName, const Audio::Timestamp *start) {
Common::SeekableReadStream *file = g_resourceloader->openNewStreamFile(filename);
if (!file) {
Debug::debug(Debug::Sound, "Stream for %s not open", soundName.c_str());
return false;
}
_soundName = soundName;
#ifndef USE_MAD
warning("Cannot open %s, MP3 support not enabled", soundName.c_str());
return true;
#else
parseRIFFHeader(file);
MP3Track::JMMCuePoints cuePoints;
if (soundName.size() > 4) {
cuePoints = parseJMMFile(Common::String(filename.c_str(), filename.size() - 4) + ".jmm");
}
if (start)
cuePoints._start = *start;
Audio::SeekableAudioStream *mp3Stream = Audio::makeMP3Stream(file, DisposeAfterUse::YES);
if (cuePoints._loopEnd <= cuePoints._loopStart) {
_stream = mp3Stream;
mp3Stream->seek(cuePoints._start);
_looping = false;
} else {
_stream = new EMISubLoopingAudioStream(mp3Stream, 0, cuePoints._start, cuePoints._loopStart, cuePoints._loopEnd);
_looping = true;
}
_handle = new Audio::SoundHandle();
return true;
#endif
}
bool MP3Track::hasLooped() {
if (!_stream || !_looping)
return false;
EMISubLoopingAudioStream *las = static_cast<EMISubLoopingAudioStream*>(_stream);
return las->hasLooped();
}
bool MP3Track::isPlaying() {
if (!_handle)
return false;
return g_system->getMixer()->isSoundHandleActive(*_handle);
}
Audio::Timestamp MP3Track::getPos() {
if (!_stream)
return Audio::Timestamp(0);
if (_looping) {
EMISubLoopingAudioStream *slas = static_cast<EMISubLoopingAudioStream*>(_stream);
return slas->getPos();
} else {
return g_system->getMixer()->getSoundElapsedTime(*_handle);
}
}
} // end of namespace Grim

View File

@@ -0,0 +1,64 @@
/* 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 GRIM_MP3TRACK_H
#define GRIM_MP3TRACK_H
#include "common/str.h"
#include "common/stream.h"
#include "audio/timestamp.h"
#include "engines/grim/emi/sound/track.h"
namespace Audio {
class AudioStream;
class SoundHandle;
}
namespace Grim {
class MP3Track : public SoundTrack {
struct JMMCuePoints {
Audio::Timestamp _start;
Audio::Timestamp _loopStart;
Audio::Timestamp _loopEnd;
};
uint32 _headerSize;
uint32 _regionLength;
uint32 _freq;
char _bits;
char _channels;
bool _endFlag;
bool _looping;
void parseRIFFHeader(Common::SeekableReadStream *data);
JMMCuePoints parseJMMFile(const Common::String &filename);
public:
MP3Track(Audio::Mixer::SoundType soundType);
~MP3Track();
bool openSound(const Common::String &filename, const Common::String &soundName, const Audio::Timestamp *start = nullptr) override;
bool hasLooped() override;
bool isPlaying() override;
Audio::Timestamp getPos() override;
};
}
#endif

View File

@@ -0,0 +1,95 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "common/mutex.h"
#include "common/textconsole.h"
#include "audio/mixer.h"
#include "audio/audiostream.h"
#include "engines/grim/debug.h"
#include "engines/grim/resource.h"
#include "engines/grim/emi/sound/codecs/scx.h"
#include "engines/grim/emi/sound/scxtrack.h"
namespace Grim {
SCXTrack::SCXTrack(Audio::Mixer::SoundType soundType) {
_disposeAfterPlaying = DisposeAfterUse::NO;
_soundType = soundType;
_looping = false;
}
SCXTrack::~SCXTrack() {
stop();
if (_handle) {
g_system->getMixer()->stopHandle(*_handle);
delete _handle;
}
}
bool SCXTrack::openSound(const Common::String &filename, const Common::String &soundName, const Audio::Timestamp *start) {
Common::SeekableReadStream *file = g_resourceloader->openNewStreamFile(filename);
if (!file) {
Debug::debug(Debug::Sound, "Stream for %s not open", soundName.c_str());
return false;
}
_soundName = soundName;
Audio::RewindableAudioStream *scxStream = makeSCXStream(file, start, DisposeAfterUse::YES);
_stream = scxStream;
_handle = new Audio::SoundHandle();
return true;
}
bool SCXTrack::isPlaying() {
if (!_handle)
return false;
return g_system->getMixer()->isSoundHandleActive(*_handle);
}
Audio::Timestamp SCXTrack::getPos() {
if (!_stream || _looping)
return Audio::Timestamp(0);
return dynamic_cast<SCXStream*>(_stream)->getPos();
}
bool SCXTrack::play() {
if (_stream) {
Audio::RewindableAudioStream *stream = dynamic_cast<Audio::RewindableAudioStream *>(_stream);
if (!_looping) {
stream->rewind();
}
return SoundTrack::play();
}
return false;
}
void SCXTrack::setLooping(bool looping) {
if (_looping == looping)
return;
_looping = looping;
if (looping && _stream) {
_stream = Audio::makeLoopingAudioStream(dynamic_cast<Audio::RewindableAudioStream *>(_stream), 0);
}
}
} // end of namespace Grim

View File

@@ -0,0 +1,52 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef GRIM_SCXTRACK_H
#define GRIM_SCXTRACK_H
#include "common/str.h"
#include "common/stream.h"
#include "engines/grim/emi/sound/track.h"
namespace Audio {
class AudioStream;
class SoundHandle;
}
namespace Grim {
class SCXTrack : public SoundTrack {
public:
SCXTrack(Audio::Mixer::SoundType soundType);
~SCXTrack();
bool openSound(const Common::String &filename, const Common::String &soundName, const Audio::Timestamp *start = nullptr) override;
bool isPlaying() override;
Audio::Timestamp getPos() override;
bool play() override;
void setLooping(bool looping) override;
private:
bool _looping;
};
}
#endif

View File

@@ -0,0 +1,154 @@
/* 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/mutex.h"
#include "common/str.h"
#include "common/stream.h"
#include "common/textconsole.h"
#include "audio/mixer.h"
#include "audio/audiostream.h"
#include "engines/grim/savegame.h"
#include "engines/grim/emi/sound/track.h"
#include "engines/grim/grim.h"
#include "engines/grim/set.h"
namespace Grim {
SoundTrack::SoundTrack() {
_stream = nullptr;
_handle = nullptr;
_paused = false;
_positioned = false;
_balance = 0;
_volume = Audio::Mixer::kMaxChannelVolume;
_disposeAfterPlaying = DisposeAfterUse::YES;
_sync = 0;
_fadeMode = FadeNone;
_fade = 1.0f;
_attenuation = 1.0f;
// Initialize to a plain sound for now
_soundType = Audio::Mixer::kPlainSoundType;
}
SoundTrack::~SoundTrack() {
if (_stream && (_disposeAfterPlaying == DisposeAfterUse::NO || !_handle))
delete _stream;
}
Common::String SoundTrack::getSoundName() {
return _soundName;
}
void SoundTrack::setSoundName(const Common::String &name) {
_soundName = name;
}
void SoundTrack::setVolume(int volume) {
_volume = MIN(volume, static_cast<int>(Audio::Mixer::kMaxChannelVolume));
if (_handle) {
g_system->getMixer()->setChannelVolume(*_handle, (byte)getEffectiveVolume());
}
}
void SoundTrack::setPosition(bool positioned, const Math::Vector3d &pos) {
_positioned = positioned;
_pos = pos;
updatePosition();
}
void SoundTrack::updatePosition() {
if (!_positioned)
return;
Set *set = g_grim->getCurrSet();
Set::Setup *setup = set->getCurrSetup();
Math::Vector3d cameraPos = setup->_pos;
Math::Vector3d vector = _pos - cameraPos;
float distance = vector.getMagnitude();
if (_volume == 0) {
_attenuation = 0.0f;
} else {
_attenuation = MAX(0.0f, 1.0f - distance / (_volume * 100.0f / Audio::Mixer::kMaxChannelVolume));
}
Math::Matrix4 worldRot = setup->_rot;
Math::Vector3d relPos = (_pos - setup->_pos);
Math::Vector3d p(relPos);
p = p * worldRot.getRotation();
float angle = atan2(p.x(), p.z());
float pan = sin(angle);
_balance = (int)(pan * 127.0f);
if (_handle) {
g_system->getMixer()->setChannelBalance(*_handle, _balance);
g_system->getMixer()->setChannelVolume(*_handle, (byte)getEffectiveVolume());
}
}
void SoundTrack::setBalance(int balance) {
if (_positioned)
return;
_balance = balance;
if (_handle) {
g_system->getMixer()->setChannelBalance(*_handle, _balance);
}
}
bool SoundTrack::play() {
if (_stream) {
if (isPlaying()) {
warning("sound: %s already playing, don't start again!", _soundName.c_str());
return true;
}
// If _disposeAfterPlaying is NO, the destructor will take care of the stream.
g_system->getMixer()->playStream(_soundType, _handle, _stream, -1, (byte)getEffectiveVolume(), _balance, _disposeAfterPlaying);
return true;
}
return false;
}
void SoundTrack::pause() {
_paused = !_paused;
if (_stream) {
g_system->getMixer()->pauseHandle(*_handle, _paused);
}
}
void SoundTrack::stop() {
if (_handle)
g_system->getMixer()->stopHandle(*_handle);
}
void SoundTrack::setFade(float fade) {
_fade = fade;
if (_handle) {
g_system->getMixer()->setChannelVolume(*_handle, (byte)getEffectiveVolume());
}
}
int SoundTrack::getEffectiveVolume() {
return _volume * _attenuation * _fade;
}
} // end of namespace Grim

View File

@@ -0,0 +1,106 @@
/* 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 GRIM_SOUNDTRACK_H
#define GRIM_SOUNDTRACK_H
#include "audio/mixer.h"
#include "audio/timestamp.h"
#include "math/vector3d.h"
namespace Common {
class String;
}
namespace Audio {
class AudioStream;
class SoundHandle;
}
namespace Grim {
class SaveGame;
/**
* @class Super-class for the different codecs used in EMI
*/
class SoundTrack {
public:
enum FadeMode {
FadeNone,
FadeIn,
FadeOut
};
protected:
Common::String _soundName;
Audio::AudioStream *_stream;
Audio::SoundHandle *_handle;
Audio::Mixer::SoundType _soundType;
DisposeAfterUse::Flag _disposeAfterPlaying;
bool _paused;
bool _positioned;
Math::Vector3d _pos;
FadeMode _fadeMode;
float _fade;
float _attenuation;
int _balance;
int _volume;
int _sync;
public:
SoundTrack();
virtual ~SoundTrack();
virtual bool openSound(const Common::String &filename, const Common::String &voiceName, const Audio::Timestamp *start = nullptr) = 0;
virtual bool isPlaying() = 0;
virtual bool play();
virtual void pause();
virtual void stop();
void fadeIn() { _fadeMode = FadeIn; }
void fadeOut() { _fadeMode = FadeOut; }
void setFadeMode(FadeMode fadeMode) { _fadeMode = fadeMode; }
void setFade(float fade);
float getFade() const { return _fade; }
FadeMode getFadeMode() const { return _fadeMode; }
void setBalance(int balance);
void setVolume(int volume);
void setPosition(bool positioned, const Math::Vector3d &pos = Math::Vector3d());
void updatePosition();
void setSync(int sync) { _sync = sync; }
int getEffectiveVolume();
int getVolume() const { return _volume; }
int getBalance() const { return _balance; }
int getSync() const { return _sync; }
virtual Audio::Timestamp getPos() = 0;
Common::String getSoundName();
void setSoundName(const Common::String &name);
virtual bool hasLooped() { return false; }
virtual void setLooping(bool looping) { }
virtual bool isLooping() const { return false; }
bool isPaused() const { return _paused; }
bool isPositioned() const { return _positioned; }
Math::Vector3d getWorldPos() const { return _pos; }
Audio::Mixer::SoundType getSoundType() const { return _soundType; }
};
}
#endif

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