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

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

View File

@@ -0,0 +1,260 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* 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/mutex.h"
#include "common/textconsole.h"
#include "audio/audiostream.h"
#include "audio/mixer.h"
#include "audio/decoders/raw.h"
#include "engines/grim/debug.h"
#include "engines/grim/resource.h"
#include "engines/grim/imuse/imuse_mcmp_mgr.h"
#include "engines/grim/emi/sound/vimatrack.h"
namespace Grim {
struct Region {
int32 offset; // offset of region
int32 length; // length of region
};
struct SoundDesc {
uint16 freq; // frequency
byte channels; // stereo or mono
byte bits; // 8, 12, 16
int numRegions; // number of Regions
Region *region;
bool endFlag;
bool inUse;
char name[32];
McmpMgr *mcmpMgr;
int type;
int volGroupId;
bool mcmpData;
uint32 headerSize;
Common::SeekableReadStream *inStream;
};
bool VimaTrack::isPlaying() {
// FIXME: Actually clean up the data better
// (we don't currently handle the case where it isn't asked for through isPlaying, or deleted explicitly).
if (!_handle)
return false;
if (g_system->getMixer()->isSoundHandleActive(*_handle)) {
if (_stream->endOfData()) {
g_system->getMixer()->stopHandle(*_handle);
return false;
} else {
return true;
}
}
return false;
}
bool VimaTrack::openSound(const Common::String &filename, const Common::String &voiceName, const Audio::Timestamp *start) {
Common::SeekableReadStream *file = g_resourceloader->openNewStreamFile(filename);
if (!file) {
Debug::debug(Debug::Sound, "Stream for %s not open", voiceName.c_str());
return false;
}
_soundName = voiceName;
_mcmp = new McmpMgr();
_desc = new SoundDesc();
_desc->inStream = file;
_desc->mcmpData = true;
_desc->mcmpMgr = _mcmp;
int headerSize = 0;
if (_mcmp->openSound(voiceName.c_str(), file, headerSize)) {
parseSoundHeader(_desc, headerSize);
_stream = Audio::makeQueuingAudioStream(_desc->freq, (false));
playTrack(start);
return true;
} else {
return false;
}
}
void VimaTrack::parseSoundHeader(SoundDesc *sound, int &headerSize) {
Common::SeekableReadStream *data = sound->inStream;
uint32 tag = data->readUint32BE();
if (tag == MKTAG('R','I','F','F')) {
sound->endFlag = false;
sound->region = new Region[1];
sound->numRegions = 1;
sound->region[0].offset = 0;
data->seek(18, SEEK_CUR);
sound->channels = data->readByte();
data->readByte();
sound->freq = data->readUint32LE();
data->seek(6, SEEK_CUR);
sound->bits = data->readByte();
data->seek(5, SEEK_CUR);
sound->region[0].length = data->readUint32LE();
headerSize = 44;
} else {
assert(tag != MKTAG('i','M','U','S'));
error("VimaTrack::parseSoundHeader() Unknown sound format");
}
}
int32 VimaTrack::getDataFromRegion(SoundDesc *sound, int region, byte **buf, int32 offset, int32 size, int32 *flags) {
//assert(checkForProperHandle(sound));
assert(buf && offset >= 0 && size >= 0);
assert(region >= 0 && region < sound->numRegions);
int32 region_offset = sound->region[region].offset;
int32 region_length = sound->region[region].length;
if (offset + size > region_length) {
size = region_length - offset;
sound->endFlag = true;
} else {
sound->endFlag = false;
}
if (sound->mcmpData) {
size = sound->mcmpMgr->decompressSample(region_offset + offset, size, buf);
*flags |= Audio::FLAG_LITTLE_ENDIAN;
} else {
*buf = new byte[size];
sound->inStream->seek(region_offset + offset + sound->headerSize, SEEK_SET);
sound->inStream->read(*buf, size);
*flags &= ~Audio::FLAG_LITTLE_ENDIAN;
}
return size;
}
void VimaTrack::playTrack(const Audio::Timestamp *start) {
//Common::StackLock lock(_mutex);
if (!_stream) {
error("Stream not loaded");
}
byte *data = nullptr;
int32 result = 0;
int32 curRegion = -1;
int32 regionOffset = 0;
int32 mixerFlags = Audio::FLAG_16BITS;
curRegion++;
int channels = _desc->channels;
//int32 mixer_size = track->feedSize / _callbackFps;
int32 mixer_size = _desc->freq * channels * 2;
if (start) {
regionOffset = (start->msecs() * mixer_size) / 1000;
regionOffset = (regionOffset / 2) * 2; // Ensure that the offset is divisible by 2.
while (regionOffset > _desc->region[curRegion].length) {
regionOffset -= _desc->region[curRegion].length;
++curRegion;
}
if (curRegion > _desc->numRegions - 1)
return;
}
if (_stream->endOfData()) { // FIXME: Currently we just allocate a bunch here, try to find the correct size instead.
mixer_size *= 8;
}
if (channels == 1)
mixer_size &= ~1;
if (channels == 2)
mixer_size &= ~3;
if (mixer_size == 0)
return;
do {
result = getDataFromRegion(_desc, curRegion, &data, regionOffset, mixer_size, &mixerFlags);
if (channels == 1) {
result &= ~1;
}
if (channels == 2) {
result &= ~3;
}
if (result > mixer_size)
result = mixer_size;
if (g_system->getMixer()->isReady()) {
((Audio::QueuingAudioStream *)_stream)->queueBuffer(data, result, DisposeAfterUse::YES, mixerFlags);
regionOffset += result;
} else
delete[] data;
if (curRegion >= 0 && curRegion < _desc->numRegions - 1) {
curRegion++;
regionOffset = 0;
if (!_stream) {
return;
}
}
mixer_size -= result;
assert(mixer_size >= 0);
} while (mixer_size && !_desc->endFlag);
if (g_system->getMixer()->isReady()) {
//g_system->getMixer()->setChannelVolume(track->handle, track->getVol());
//g_system->getMixer()->setChannelBalance(track->handle, track->getPan());
}
}
Audio::Timestamp VimaTrack::getPos() {
// FIXME: Return actual stream position.
return g_system->getMixer()->getSoundElapsedTime(*_handle);
}
VimaTrack::VimaTrack() {
_soundType = Audio::Mixer::kSpeechSoundType;
_handle = new Audio::SoundHandle();
_file = nullptr;
_mcmp = nullptr;
_desc = nullptr;
}
VimaTrack::~VimaTrack() {
stop();
delete _mcmp;
if (_desc) {
delete[] _desc->region;
delete _desc->inStream;
}
if (_handle) {
g_system->getMixer()->stopHandle(*_handle);
delete _handle;
}
delete _desc;
}
} // 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_VIMATRACK_H
#define GRIM_VIMATRACK_H
#include "common/str.h"
#include "engines/grim/emi/sound/track.h"
namespace Grim {
struct SoundDesc;
class McmpMgr;
/**
* @class Vima-implementation for the EMI-sound system
* Vima is used for voices in the PC version of EMI, a
* similar implementation for SCX will be required for PS2-support.
*/
class VimaTrack : public SoundTrack {
Common::SeekableReadStream *_file;
void parseSoundHeader(SoundDesc *sound, int &headerSize);
int32 getDataFromRegion(SoundDesc *sound, int region, byte **buf, int32 offset, int32 size, int32 *flags);
public:
VimaTrack();
virtual ~VimaTrack();
bool isPlaying() override;
bool openSound(const Common::String &filename, const Common::String &soundName, const Audio::Timestamp *start = nullptr) override;
void playTrack(const Audio::Timestamp *start);
Audio::Timestamp getPos() override;
SoundDesc *_desc;
McmpMgr *_mcmp;
};
}
#endif