diff --git a/engines/stark/module.mk b/engines/stark/module.mk index 56d43e05085..ae7de6f2bba 100644 --- a/engines/stark/module.mk +++ b/engines/stark/module.mk @@ -98,6 +98,7 @@ MODULE_OBJS := \ ui/world/gamewindow.o \ ui/world/inventorywindow.o \ visual/actor.o \ + visual/explodingimage.o \ visual/image.o \ visual/prop.o \ visual/smacker.o \ diff --git a/engines/stark/resources/knowledgeset.cpp b/engines/stark/resources/knowledgeset.cpp index 4e16ed2f4a7..6d5c1352f41 100644 --- a/engines/stark/resources/knowledgeset.cpp +++ b/engines/stark/resources/knowledgeset.cpp @@ -40,7 +40,7 @@ KnowledgeSet::KnowledgeSet(Object *parent, byte subType, uint16 index, const Com void KnowledgeSet::printData() { } -Gfx::RenderEntryArray KnowledgeSet::getInventoryRenderEntries() { +Gfx::RenderEntryArray KnowledgeSet::getInventoryRenderEntries() const { Common::Array inventoryItems = listChildren(Resources::Item::kItemInventory); // First add the inventory items from old saves which don't have an order @@ -97,5 +97,12 @@ void KnowledgeSet::saveLoad(ResourceSerializer *serializer) { } } +Visual *KnowledgeSet::getInventoryItemVisual(uint16 itemIndex) { + InventoryItem *item = findChildWithIndex(itemIndex, Item::kItemInventory); + assert(item); + + return item->getCursorVisual(); +} + } // End of namespace Resources } // End of namespace Stark diff --git a/engines/stark/resources/knowledgeset.h b/engines/stark/resources/knowledgeset.h index 8ebf65571d0..dda02631afd 100644 --- a/engines/stark/resources/knowledgeset.h +++ b/engines/stark/resources/knowledgeset.h @@ -66,7 +66,10 @@ public: void removeItem(InventoryItem *item); /** Get the render entries for the inventory items, in the order they were obtained */ - Gfx::RenderEntryArray getInventoryRenderEntries(); + Gfx::RenderEntryArray getInventoryRenderEntries() const; + + /** Get a cursor style visual for an inventory item */ + Visual *getInventoryItemVisual(uint16 itemIndex); protected: void printData() override; diff --git a/engines/stark/services/staticprovider.cpp b/engines/stark/services/staticprovider.cpp index 4f79e935019..90affaca5d3 100644 --- a/engines/stark/services/staticprovider.cpp +++ b/engines/stark/services/staticprovider.cpp @@ -20,10 +20,10 @@ * */ -#include #include "engines/stark/services/staticprovider.h" #include "engines/stark/resources/anim.h" +#include "engines/stark/resources/animscript.h" #include "engines/stark/resources/container.h" #include "engines/stark/resources/image.h" #include "engines/stark/resources/item.h" diff --git a/engines/stark/ui/world/button.cpp b/engines/stark/ui/world/button.cpp index 4c8fcd62fe4..938a92db47f 100644 --- a/engines/stark/ui/world/button.cpp +++ b/engines/stark/ui/world/button.cpp @@ -26,6 +26,7 @@ #include "engines/stark/gfx/driver.h" #include "engines/stark/gfx/texture.h" +#include "engines/stark/visual/explodingimage.h" #include "engines/stark/visual/image.h" #include "engines/stark/visual/text.h" @@ -38,27 +39,34 @@ Button::Button(const Common::String &text, StaticProvider::UIElement stockElemen _hintPosition(hintPos), _align(align), _mouseText(nullptr), - _renderHint(false) { + _renderHint(false), + _explodingImageAnimation(nullptr) { } Button::~Button() { + delete _explodingImageAnimation; delete _mouseText; } void Button::render() { VisualImageXMG *image = StarkStaticProvider->getUIElement(_stockElement); image->render(_position, false); + + if (_explodingImageAnimation) { + _explodingImageAnimation->render(_position); + } + if (_renderHint) { Common::Point pos(_hintPosition); if (_align == kAlignRight) { pos.x -= _mouseText->getRect().width(); } _mouseText->render(pos); + _renderHint = false; } - _renderHint = false; } -bool Button::containsPoint(Common::Point point) { +bool Button::containsPoint(const Common::Point &point) { VisualImageXMG *image = StarkStaticProvider->getUIElement(_stockElement); Common::Rect r; @@ -89,4 +97,17 @@ void Button::goToAnimStatement(int animScriptItemIndex) { StarkStaticProvider->goToAnimScriptStatement(_stockElement, animScriptItemIndex); } +void Button::startImageExplosion(VisualImageXMG *image) { + assert(image); + + stopImageExplosion(); + _explodingImageAnimation = new VisualExplodingImage(StarkGfx); + _explodingImageAnimation->initFromSurface(image->getSurface()); +} + +void Button::stopImageExplosion() { + delete _explodingImageAnimation; + _explodingImageAnimation = nullptr; +} + } // End of namespace Stark diff --git a/engines/stark/ui/world/button.h b/engines/stark/ui/world/button.h index f11579c6e96..1ee79b53cfc 100644 --- a/engines/stark/ui/world/button.h +++ b/engines/stark/ui/world/button.h @@ -31,6 +31,7 @@ namespace Stark { +class VisualExplodingImage; class VisualImageXMG; class VisualText; @@ -49,7 +50,7 @@ public: /** Set hint to render for one frame */ void showButtonHint(); void render(); - bool containsPoint(Common::Point point); + bool containsPoint(const Common::Point &point); /** Reset the hint text visual so it is rebuilt with the appropriate texture size */ void resetHintVisual(); @@ -57,12 +58,19 @@ public: /** Move execution of the button's icon anim script to the specified item */ void goToAnimStatement(int animScriptItemIndex); + /** Start overlaying an explosion animation of an image on top of the button */ + void startImageExplosion(VisualImageXMG *image); + + /** Remove the currently playing exploding image animation, if any */ + void stopImageExplosion(); + private: StaticProvider::UIElement _stockElement; Common::Point _position; Common::Point _hintPosition; Common::String _text; VisualText *_mouseText; + VisualExplodingImage *_explodingImageAnimation; const HintAlign _align; bool _renderHint; }; diff --git a/engines/stark/ui/world/topmenu.cpp b/engines/stark/ui/world/topmenu.cpp index 904395014f5..3602d63ea60 100644 --- a/engines/stark/ui/world/topmenu.cpp +++ b/engines/stark/ui/world/topmenu.cpp @@ -27,6 +27,7 @@ #include "engines/stark/gfx/driver.h" +#include "engines/stark/resources/knowledgeset.h" #include "engines/stark/resources/sound.h" #include "engines/stark/services/global.h" @@ -69,6 +70,7 @@ void TopMenu::onRender() { _forceVisibleTimeRemaining -= StarkGlobal->getMillisecondsPerGameloop(); if (_forceVisibleTimeRemaining <= 0) { + _inventoryButton->stopImageExplosion(); _inventoryButton->goToAnimStatement(12); } } @@ -134,6 +136,9 @@ void TopMenu::onScreenChanged() { void TopMenu::notifyInventoryItemEnabled(uint16 itemIndex) { _forceVisibleTimeRemaining = 128 * 33; // 128 frames at 30 fps _inventoryButton->goToAnimStatement(2); + + Visual *inventoryItemImage = StarkGlobal->getInventory()->getInventoryItemVisual(itemIndex); + _inventoryButton->startImageExplosion(inventoryItemImage->get()); _inventoryNewItemSound->stop(); _inventoryNewItemSound->play(); } diff --git a/engines/stark/visual/explodingimage.cpp b/engines/stark/visual/explodingimage.cpp new file mode 100644 index 00000000000..38972cad1ba --- /dev/null +++ b/engines/stark/visual/explodingimage.cpp @@ -0,0 +1,172 @@ +/* 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 AUTHORS + * 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 "engines/stark/visual/explodingimage.h" + +#include "common/random.h" +#include "graphics/surface.h" + +#include "engines/stark/gfx/driver.h" +#include "engines/stark/gfx/surfacerenderer.h" +#include "engines/stark/gfx/texture.h" + +#include "engines/stark/services/global.h" +#include "engines/stark/services/services.h" + +namespace Stark { + +VisualExplodingImage::VisualExplodingImage(Gfx::Driver *gfx) : + Visual(TYPE), + _gfx(gfx), + _texture(nullptr), + _surface(nullptr) { + _surfaceRenderer = _gfx->createSurfaceRenderer(); +} + +VisualExplodingImage::~VisualExplodingImage() { + if (_surface) { + _surface->free(); + } + delete _surface; + delete _texture; + delete _surfaceRenderer; +} + +void VisualExplodingImage::initFromSurface(const Graphics::Surface *surface) { + assert(!_surface && !_texture); + + // Decode the XMG + _surface = new Graphics::Surface(); + _surface->copyFrom(*surface); + _texture = _gfx->createTexture(_surface); + + // Create an explosion unit for each pixel in the surface + _units.resize(_surface->w * _surface->h); + + Common::Point explosionCenter(_surface->w / 2, _surface->h / 2); + Common::Point explosionAmplitude(48, 16); + + uint index = 0; + for (uint y = 0; y < _surface->h; y++) { + for (uint x = 0; x < _surface->w; x++, index++) { + _units[index].setPosition(x, y); + _units[index].setExplosionSettings(explosionCenter, explosionAmplitude); + _units[index].setColor(*static_cast(_surface->getBasePtr(x, y)), _surface->format); + } + } +} + +void VisualExplodingImage::render(const Common::Point &position) { + // Fill with transparent color + _surface->fillRect(Common::Rect(_surface->w, _surface->h), 0); + + for (uint i = 0; i < _units.size(); i++) { + _units[i].update(); + _units[i].draw(_surface); + } + + _texture->update(_surface); + _surfaceRenderer->render(_texture, position); +} + +VisualExplodingImage::ExplosionUnit::ExplosionUnit() : + _stillImageTimeRemaining(33 * 33), + _explosionFastAccelerationTimeRemaining(25 * 33) { + +} + +void VisualExplodingImage::ExplosionUnit::setPosition(int x, int y) { + _position = Math::Vector2d(x, y); +} + +void VisualExplodingImage::ExplosionUnit::setExplosionSettings(const Common::Point ¢er, const Common::Point &litude) { + _center = Math::Vector2d(center.x, center.y); + + _speed.setX(cos(StarkRandomSource->getRandomNumber(M_PI * 100)) * (float)amplitude.x); + _speed.setY(sin(StarkRandomSource->getRandomNumber(M_PI * 100)) * (float)amplitude.y); + + // WTF, ensuring all fragments go in the same direction? + float magnitude = _position.getDistanceTo(_speed); + _speed -= _position; + _speed = _speed / _speed.getMagnitude() * -magnitude; +} + +void VisualExplodingImage::ExplosionUnit::setColor(uint32 color, const Graphics::PixelFormat &format) { + _mainColor = color; + + byte a, r, g, b; + format.colorToARGB(color, a, r, g, b); + r >>= 1; + g >>= 1; + b >>= 1; + + _darkColor = format.ARGBToColor(a, r, g, b); +} + +void VisualExplodingImage::ExplosionUnit::update() { + if (_stillImageTimeRemaining > 0) { + _stillImageTimeRemaining -= StarkGlobal->getMillisecondsPerGameloop(); + return; + } + + if (_position.getDistanceTo(_center) <= 1.f) { + // Units near the center stay there (to make it look like they enter the chest) + return; + } + + Math::Vector2d speed = _speed.getNormalized() * 0.6f; + _position += speed; + + // Update the acceleration to units move towards the center + Math::Vector2d acceleration = _center - _position; + if (_explosionFastAccelerationTimeRemaining > 0) { + acceleration *= 3.0f; + _explosionFastAccelerationTimeRemaining -= StarkGlobal->getMillisecondsPerGameloop(); + } + + _speed += acceleration; + _speed -= speed * 2.5f; +} + +void VisualExplodingImage::ExplosionUnit::draw(Graphics::Surface *surface) { + if (_position.getX() <= 1.f || _position.getX() >= surface->w - 1 + || _position.getY() <= 1.f || _position.getY() >= surface->h - 1) { + return; // Ignore units outside of the surface + } + + if (_stillImageTimeRemaining <= 0 && _position.getDistanceTo(_center) <= 2.f) { + return; // Ignore units close to the center (to make it look like they enter the chest) + } + + uint32 *pixel = static_cast(surface->getBasePtr(_position.getX(), _position.getY() - 1)); + *pixel = _darkColor; + + pixel = static_cast(surface->getBasePtr(_position.getX() - 1, _position.getY())); + *pixel++ = _darkColor; + *pixel++ = _mainColor; + *pixel = _darkColor; + + pixel = static_cast(surface->getBasePtr(_position.getX(), _position.getY() + 1)); + *pixel = _darkColor; +} + +} // End of namespace Stark diff --git a/engines/stark/visual/explodingimage.h b/engines/stark/visual/explodingimage.h new file mode 100644 index 00000000000..549d868ff3b --- /dev/null +++ b/engines/stark/visual/explodingimage.h @@ -0,0 +1,95 @@ +/* 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 AUTHORS + * 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. + * + */ + +#ifndef STARK_VISUAL_EXPLODING_IMAGE_H +#define STARK_VISUAL_EXPLODING_IMAGE_H + +#include "engines/stark/visual/visual.h" + +#include "common/array.h" +#include "common/rect.h" + +#include "graphics/pixelformat.h" + +#include "math/vector2d.h" + +namespace Graphics { +struct Surface; +} + +namespace Stark { + +namespace Gfx { +class Driver; +class SurfaceRenderer; +class Texture; +} + +/** + * An image with an animated explosion effect + * + * Used by the top bar when picking up an inventory item + */ +class VisualExplodingImage : public Visual { +public: + static const VisualType TYPE = Visual::kExplodingImage; + + explicit VisualExplodingImage(Gfx::Driver *gfx); + ~VisualExplodingImage() override; + + /** Prepare exploding the specified image */ + void initFromSurface(const Graphics::Surface *surface); + + /** Render the image at the specified position */ + void render(const Common::Point &position); + +private: + struct ExplosionUnit { + ExplosionUnit(); + + void setPosition(int x, int y); + void setExplosionSettings(const Common::Point ¢er, const Common::Point &litude); + void setColor(uint32 color, const Graphics::PixelFormat &format); + void update(); + void draw(Graphics::Surface *surface); + + Math::Vector2d _position; + Math::Vector2d _speed; + Math::Vector2d _center; + + int _stillImageTimeRemaining; + int _explosionFastAccelerationTimeRemaining; + uint32 _mainColor; + uint32 _darkColor; + }; + + Gfx::Driver *_gfx; + Gfx::SurfaceRenderer *_surfaceRenderer; + Gfx::Texture *_texture; + Graphics::Surface *_surface; + + Common::Array _units; +}; + +} // End of namespace Stark + +#endif // STARK_VISUAL_EXPLODING_IMAGE_H diff --git a/engines/stark/visual/image.cpp b/engines/stark/visual/image.cpp index f0af8682fed..d73ff90e885 100644 --- a/engines/stark/visual/image.cpp +++ b/engines/stark/visual/image.cpp @@ -24,13 +24,10 @@ #include "graphics/surface.h" -#include "engines/stark/debug.h" #include "engines/stark/formats/xmg.h" #include "engines/stark/gfx/driver.h" #include "engines/stark/gfx/surfacerenderer.h" #include "engines/stark/gfx/texture.h" -#include "engines/stark/scene.h" -#include "engines/stark/services/services.h" namespace Stark { @@ -94,4 +91,9 @@ int VisualImageXMG::getHeight() const { return _surface->h; } +const Graphics::Surface *VisualImageXMG::getSurface() const { + assert(_surface); + return _surface; +} + } // End of namespace Stark diff --git a/engines/stark/visual/image.h b/engines/stark/visual/image.h index 2396c6c0b4b..d4c5a9d98c6 100644 --- a/engines/stark/visual/image.h +++ b/engines/stark/visual/image.h @@ -73,6 +73,9 @@ public: /** Get the height in pixels */ int getHeight() const; + /** Get a read only pointer to the surface backing the image */ + const Graphics::Surface *getSurface() const; + private: Gfx::Driver *_gfx; Gfx::SurfaceRenderer *_surfaceRenderer; diff --git a/engines/stark/visual/visual.h b/engines/stark/visual/visual.h index 77a0f40ea4b..98a3ba6df39 100644 --- a/engines/stark/visual/visual.h +++ b/engines/stark/visual/visual.h @@ -30,16 +30,17 @@ namespace Stark { class Visual { public: enum VisualType { - kImageXMG = 2, - kRendered = 3, - kImageText = 4, - kSmackerStream = 5, - kActor = 6, - kSmackerFMV = 7, - kEffectFish = 8, - kEffectBubbles = 9, - kEffectFirefly = 10, - kEffectSmoke = 11 + kImageXMG = 2, + kRendered = 3, + kImageText = 4, + kSmackerStream = 5, + kActor = 6, + kSmackerFMV = 7, + kEffectFish = 8, + kEffectBubbles = 9, + kEffectFirefly = 10, + kEffectSmoke = 11, + kExplodingImage = 100, }; explicit Visual(VisualType type) : _type(type) {}