/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*
*/
#include "engines/stark/resources/item.h"
#include "engines/stark/formats/xrc.h"
#include "engines/stark/gfx/renderentry.h"
#include "engines/stark/model/animhandler.h"
#include "engines/stark/movement/movement.h"
#include "engines/stark/visual/actor.h"
#include "engines/stark/resources/anim.h"
#include "engines/stark/resources/animhierarchy.h"
#include "engines/stark/resources/bonesmesh.h"
#include "engines/stark/resources/bookmark.h"
#include "engines/stark/resources/floor.h"
#include "engines/stark/resources/floorface.h"
#include "engines/stark/resources/knowledgeset.h"
#include "engines/stark/resources/location.h"
#include "engines/stark/resources/pattable.h"
#include "engines/stark/resources/script.h"
#include "engines/stark/resources/textureset.h"
#include "engines/stark/services/global.h"
#include "engines/stark/services/services.h"
#include "engines/stark/services/stateprovider.h"
#include "engines/stark/services/userinterface.h"
#include "engines/stark/services/settings.h"
namespace Stark {
namespace Resources {
Object *Item::construct(Object *parent, byte subType, uint16 index, const Common::String &name) {
switch (subType) {
case kItemGlobalTemplate:
return new GlobalItemTemplate(parent, subType, index, name);
case kItemInventory:
return new InventoryItem(parent, subType, index, name);
case kItemLevelTemplate:
return new LevelItemTemplate(parent, subType, index, name);
case kItemStaticProp:
case kItemAnimatedProp:
return new FloorPositionedImageItem(parent, subType, index, name);
case kItemBackgroundElement:
case kItemBackground:
return new ImageItem(parent, subType, index, name);
case kItemModel:
return new ModelItem(parent, subType, index, name);
default:
error("Unknown item subtype %d", subType);
}
}
Item::Item(Object *parent, byte subType, uint16 index, const Common::String &name) :
Object(parent, subType, index, name),
_enabled(true),
_characterIndex(0),
_movement(nullptr),
_movementSuspendedScript(nullptr) {
_type = TYPE;
}
Item::~Item() {
delete _movement;
}
void Item::readData(Formats::XRCReadStream *stream) {
_enabled = stream->readBool();
_characterIndex = stream->readSint32LE();
}
void Item::onGameLoop() {
Object::onGameLoop();
if (_enabled && _movement) {
_movement->onGameLoop();
if (_movement && _movement->hasEnded()) {
setMovement(nullptr);
}
}
}
bool Item::isEnabled() const {
return _enabled;
}
void Item::setEnabled(bool enabled) {
if (_enabled && !enabled) {
setMovement(nullptr);
}
_enabled = enabled;
}
int32 Item::getCharacterIndex() const {
return _characterIndex;
}
Gfx::RenderEntry *Item::getRenderEntry(const Common::Point &positionOffset) {
return nullptr;
}
Movement *Item::getMovement() const {
return _movement;
}
void Item::setMovement(Movement *movement) {
if (_movementSuspendedScript) {
if (_movement && _movement->hasReachedDestination()) {
_movementSuspendedScript->setResumeStatus(Script::kResumeComplete);
} else {
_movementSuspendedScript->setResumeStatus(Script::kResumeAbort);
}
_movementSuspendedScript = nullptr;
}
if (_movement && !_movement->hasEnded()) {
_movement->stop(true);
}
delete _movement;
_movement = movement;
}
void Item::setMovementSuspendedScript(Script *script) {
_movementSuspendedScript = script;
}
void Item::printData() {
debug("enabled: %d", _enabled);
debug("character: %d", _characterIndex);
}
void Item::saveLoad(ResourceSerializer *serializer) {
serializer->syncAsSint32LE(_enabled);
}
void Item::saveLoadCurrent(ResourceSerializer *serializer) {
bool hasMovement = _movement != nullptr && !_movement->hasEnded();
serializer->syncAsUint32LE(hasMovement, 8);
if (hasMovement) {
uint32 movementType = _movement != nullptr ? _movement->getType() : 0;
serializer->syncAsUint32LE(movementType);
if (serializer->isLoading()) {
_movement = Movement::construct(movementType, Object::cast(this));
}
_movement->saveLoad(serializer);
serializer->syncAsResourceReference(&_movementSuspendedScript);
if (serializer->isLoading()) {
_movement->start();
}
}
}
Common::Array Item::listExitPositions() {
return Common::Array();
}
ItemVisual::~ItemVisual() {
delete _renderEntry;
}
ItemVisual::ItemVisual(Object *parent, byte subType, uint16 index, const Common::String &name) :
Item(parent, subType, index, name),
_renderEntry(nullptr),
_actionAnim(nullptr),
_animHierarchy(nullptr),
_currentAnimActivity(-1),
_clickable(true) {
_renderEntry = new Gfx::RenderEntry(this, getName());
}
void ItemVisual::readData(Formats::XRCReadStream *stream) {
Item::readData(stream);
_clickable = stream->readBool();
}
void ItemVisual::onAllLoaded() {
Item::onAllLoaded();
_animHierarchy = findChild(false);
_renderEntry->setClickable(_clickable);
if (_subType != kItemModel) {
setAnimActivity(Anim::kActionUsagePassive);
}
if (!_enabled) {
setEnabled(false);
}
Location *location = findParent();
if (location) {
location->registerCharacterItem(_characterIndex, this);
}
}
void ItemVisual::saveLoad(ResourceSerializer *serializer) {
Item::saveLoad(serializer);
serializer->syncAsSint32LE(_currentAnimActivity);
serializer->syncAsResourceReference(&_animHierarchy);
if (serializer->isLoading() && _animHierarchy) {
setAnimHierarchy(_animHierarchy);
}
serializer->syncAsResourceReference(&_actionAnim);
if (serializer->isLoading()) {
if (_actionAnim) {
_actionAnim->applyToItem(this);
} else {
setAnimActivity(_currentAnimActivity);
}
}
}
void ItemVisual::saveLoadCurrent(ResourceSerializer *serializer) {
Item::saveLoadCurrent(serializer);
// Apply the animation once again now the data from the item templates has been loaded.
// This ensures template level textures and models are applied when loading.
serializer->syncAsSint32LE(_currentAnimActivity, 11);
serializer->syncAsResourceReference(&_animHierarchy, 11);
if (serializer->isLoading()) {
if (_animHierarchy) {
setAnimHierarchy(_animHierarchy);
}
}
serializer->syncAsResourceReference(&_actionAnim, 11);
if (serializer->isLoading()) {
if (_actionAnim) {
_actionAnim->applyToItem(this);
} else {
setAnimActivity(_currentAnimActivity);
}
}
}
void ItemVisual::setEnabled(bool enabled) {
Item::setEnabled(enabled);
if (enabled) {
// If an action animation was selected while the item was disabled,
// honor it. This is not what the original engine did, but seems
// logical and fixes the Gribbler hurting animation in the forest.
if (!_actionAnim) {
_animHierarchy->selectItemAnim(this);
}
} else {
resetActionAnim();
_animHierarchy->unselectItemAnim(this);
}
}
ItemVisual *ItemVisual::getSceneInstance() {
return this;
}
int32 ItemVisual::getAnimActivity() const {
return _currentAnimActivity;
}
void ItemVisual::setAnimActivity(int32 activity) {
bool animNeedsUpdate = activity != _currentAnimActivity || _actionAnim != nullptr || _animHierarchy->getCurrentAnim() == nullptr;
resetActionAnim();
_currentAnimActivity = activity;
if (animNeedsUpdate && _animHierarchy) {
_animHierarchy->setItemAnim(this, activity);
}
}
void ItemVisual::printData() {
Item::printData();
debug("clickable: %d", _clickable);
}
Anim *ItemVisual::getAnim() const {
if (_actionAnim) {
return _actionAnim;
}
return _animHierarchy->getCurrentAnim();
}
Anim *ItemVisual::getActionAnim() const {
return _actionAnim;
}
void ItemVisual::setAnimHierarchy(AnimHierarchy *animHierarchy) {
resetActionAnim();
if (_animHierarchy) {
_animHierarchy->unselectItemAnim(this);
}
_animHierarchy = animHierarchy;
}
Visual *ItemVisual::getVisual() {
Anim *anim = getAnim();
if (!anim) {
return nullptr;
}
return anim->getVisual();
}
int ItemVisual::getHotspotIndexForPoint(const Common::Point &point) {
Anim *anim = getAnim();
if (anim) {
return anim->getPointHotspotIndex(point);
}
return -1;
}
bool ItemVisual::canPerformAction(uint32 action, uint32 hotspotIndex) {
PATTable *table = findChildWithOrder(hotspotIndex);
return table && table->canPerformAction(action);
}
bool ItemVisual::doAction(uint32 action, uint32 hotspotIndex) {
PATTable *table = findChildWithOrder(hotspotIndex);
if (table && table->canPerformAction(action)) {
return table->runScriptForAction(action);
}
return false;
}
void ItemVisual::playActionAnim(Anim *anim) {
resetActionAnim();
_animHierarchy->unselectItemAnim(this);
_actionAnim = anim;
anim->applyToItem(this);
anim->playAsAction(this);
}
void ItemVisual::resetActionAnim() {
if (_actionAnim) {
_actionAnim->removeFromItem(this);
_actionAnim = nullptr;
// TODO: Add a condition to this?
_animHierarchy->selectItemAnim(this);
if (_subType == kItemModel) {
_animHierarchy->setItemAnim(this, Anim::kActorActivityIdle);
}
}
}
void ItemVisual::setPosition2D(const Common::Point &position) {
warning("ItemVisual::setPosition2D is not implemented for this item type: %d (%s)", _subType, _name.c_str());
}
Common::String ItemVisual::getHotspotTitle(uint32 hotspotIndex) {
PATTable *table = findChildWithOrder(hotspotIndex);
Common::String title;
if (table) {
title = table->getName();
} else {
title = getName();
}
if (title.equalsIgnoreCase("Default April PAT")) {
return "April"; // The same hack exists in the original
}
return title;
}
Common::Array ItemVisual::listExitPositionsImpl() {
Common::Array pattables = listChildrenRecursive();
Common::Array positions;
Common::Point invalidPosition(-1, -1);
for (uint i = 0; i < pattables.size(); ++i) {
if (pattables[i]->getDefaultAction() != PATTable::kActionExit) continue;
Anim *anim = getAnim();
if (!anim) continue;
Common::Point hotspot = anim->getHotspotPosition(i);
if (hotspot != invalidPosition) {
hotspot += _renderEntry->getPosition();
positions.push_back(hotspot);
}
}
return positions;
}
ItemTemplate::~ItemTemplate() {
}
ItemTemplate::ItemTemplate(Object *parent, byte subType, uint16 index, const Common::String &name) :
Item(parent, subType, index, name),
_meshIndex(-1),
_textureNormalIndex(-1),
_textureFaceIndex(-1),
_animHierarchyIndex(-1),
_referencedItem(nullptr),
_instanciatedItem(nullptr) {
}
void ItemTemplate::onAllLoaded() {
Item::onAllLoaded();
BonesMesh *bonesMesh = findChild(false);
if (bonesMesh) {
_meshIndex = bonesMesh->getIndex();
}
TextureSet *textureNormal = findChildWithSubtype(TextureSet::kTextureNormal, false);
if (textureNormal) {
_textureNormalIndex = textureNormal->getIndex();
}
TextureSet *textureFace = findChildWithSubtype(TextureSet::kTextureFace, false);
if (textureFace) {
_textureFaceIndex = textureFace->getIndex();
}
}
void ItemTemplate::saveLoadCurrent(ResourceSerializer *serializer) {
Item::saveLoadCurrent(serializer);
serializer->syncAsSint32LE(_meshIndex);
serializer->syncAsSint32LE(_textureNormalIndex);
serializer->syncAsSint32LE(_textureFaceIndex);
serializer->syncAsSint32LE(_animHierarchyIndex);
}
void ItemTemplate::setInstanciatedItem(Item *instance) {
_instanciatedItem = instance;
}
ItemVisual *ItemTemplate::getSceneInstance() {
if (_instanciatedItem) {
return _instanciatedItem->getSceneInstance();
}
return nullptr;
}
void ItemTemplate::setStockAnimHierachy(AnimHierarchy *animHierarchy) {
Object *animHierarchyParent = animHierarchy->findParent