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