266 lines
8.4 KiB
C++
266 lines
8.4 KiB
C++
/* 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 "alcachofa/global-ui.h"
|
|
#include "alcachofa/menu.h"
|
|
#include "alcachofa/alcachofa.h"
|
|
#include "alcachofa/script.h"
|
|
|
|
using namespace Common;
|
|
|
|
namespace Alcachofa {
|
|
|
|
// originally the inventory only reacts to exactly top-left/bottom-right which is fine in
|
|
// fullscreen when you just slam the mouse cursor into the corner.
|
|
// In any other scenario this is cumbersome so I expand this area.
|
|
// And it is still pretty bad, especially in windowed mode so there is a key to open/close as well
|
|
static constexpr int16 kInventoryTriggerSize = 10;
|
|
|
|
Rect openInventoryTriggerBounds() {
|
|
int16 size = kInventoryTriggerSize * 1024 / g_system->getWidth();
|
|
return Rect(0, 0, size, size);
|
|
}
|
|
|
|
Rect closeInventoryTriggerBounds() {
|
|
int16 size = kInventoryTriggerSize * 1024 / g_system->getWidth();
|
|
return Rect(g_system->getWidth() - size, g_system->getHeight() - size, g_system->getWidth(), g_system->getHeight());
|
|
}
|
|
|
|
GlobalUI::GlobalUI() {
|
|
auto &world = g_engine->world();
|
|
_generalFont.reset(new Font(world.getGlobalAnimationName(GlobalAnimationKind::GeneralFont)));
|
|
_dialogFont.reset(new Font(world.getGlobalAnimationName(GlobalAnimationKind::DialogFont)));
|
|
_iconMortadelo.reset(new Animation(world.getGlobalAnimationName(GlobalAnimationKind::MortadeloIcon)));
|
|
_iconFilemon.reset(new Animation(world.getGlobalAnimationName(GlobalAnimationKind::FilemonIcon)));
|
|
_iconInventory.reset(new Animation(world.getGlobalAnimationName(GlobalAnimationKind::InventoryIcon)));
|
|
|
|
_generalFont->load();
|
|
_dialogFont->load();
|
|
_iconMortadelo->load();
|
|
_iconFilemon->load();
|
|
_iconInventory->load();
|
|
}
|
|
|
|
void GlobalUI::startClosingInventory() {
|
|
_isOpeningInventory = false;
|
|
_isClosingInventory = true;
|
|
_timeForInventory = g_engine->getMillis();
|
|
updateClosingInventory(); // prevents the first frame of closing to not render the inventory overlay
|
|
}
|
|
|
|
void GlobalUI::updateClosingInventory() {
|
|
static constexpr uint32 kDuration = 300;
|
|
static constexpr float kSpeed = -10 / 3.0f / 1000.0f;
|
|
|
|
uint32 deltaTime = g_engine->getMillis() - _timeForInventory;
|
|
if (!_isClosingInventory || deltaTime >= kDuration)
|
|
_isClosingInventory = false;
|
|
else
|
|
g_engine->world().inventory().drawAsOverlay((int32)(g_system->getHeight() * (deltaTime * kSpeed)));
|
|
}
|
|
|
|
bool GlobalUI::updateOpeningInventory() {
|
|
static constexpr float kSpeed = 10 / 3.0f / 1000.0f;
|
|
if (g_engine->menu().isOpen() || !g_engine->player().isGameLoaded())
|
|
return false;
|
|
|
|
const bool userWantsToOpenInventory =
|
|
openInventoryTriggerBounds().contains(g_engine->input().mousePos2D()) ||
|
|
g_engine->input().wasInventoryKeyPressed();
|
|
|
|
if (_isOpeningInventory) {
|
|
uint32 deltaTime = g_engine->getMillis() - _timeForInventory;
|
|
if (deltaTime >= 1000) {
|
|
_isOpeningInventory = false;
|
|
g_engine->world().inventory().open();
|
|
} else {
|
|
deltaTime = MIN<uint32>(300, deltaTime);
|
|
g_engine->world().inventory().drawAsOverlay((int32)(g_system->getHeight() * (deltaTime * kSpeed - 1)));
|
|
}
|
|
return true;
|
|
} else if (userWantsToOpenInventory) {
|
|
_isClosingInventory = false;
|
|
_isOpeningInventory = true;
|
|
_timeForInventory = g_engine->getMillis();
|
|
g_engine->player().activeCharacter()->stopWalking();
|
|
g_engine->world().inventory().updateItemsByActiveCharacter();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
Animation *GlobalUI::activeAnimation() const {
|
|
return g_engine->player().activeCharacterKind() == MainCharacterKind::Mortadelo
|
|
? _iconFilemon.get()
|
|
: _iconMortadelo.get();
|
|
}
|
|
|
|
bool GlobalUI::isHoveringChangeButton() const {
|
|
auto mousePos = g_engine->input().mousePos2D();
|
|
auto anim = activeAnimation();
|
|
auto offset = anim->totalFrameOffset(0);
|
|
auto bounds = anim->frameBounds(0);
|
|
|
|
const int minX = g_system->getWidth() + offset.x;
|
|
const int maxY = bounds.height() + offset.y;
|
|
return mousePos.x >= minX && mousePos.y <= maxY;
|
|
}
|
|
|
|
bool GlobalUI::updateChangingCharacter() {
|
|
auto &player = g_engine->player();
|
|
if (g_engine->menu().isOpen() ||
|
|
!player.isGameLoaded() ||
|
|
_isOpeningInventory)
|
|
return false;
|
|
_changeButton.frameI() = 0;
|
|
|
|
if (!isHoveringChangeButton())
|
|
return false;
|
|
if (g_engine->input().wasMouseLeftPressed()) {
|
|
player.pressedObject() = &_changeButton;
|
|
return true;
|
|
}
|
|
if (player.pressedObject() != &_changeButton)
|
|
return true;
|
|
|
|
player.setActiveCharacter(player.inactiveCharacter()->kind());
|
|
player.heldItem() = nullptr;
|
|
g_engine->camera().setFollow(player.activeCharacter());
|
|
g_engine->camera().restore(0);
|
|
player.changeRoom(player.activeCharacter()->room()->name(), false);
|
|
g_engine->game().onUserChangedCharacter();
|
|
|
|
int32 characterJingle = g_engine->script().variable(
|
|
player.activeCharacterKind() == MainCharacterKind::Mortadelo
|
|
? "PistaMorta"
|
|
: "PistaFile"
|
|
);
|
|
g_engine->sounds().startMusic(characterJingle);
|
|
g_engine->sounds().queueMusic(player.currentRoom()->musicID());
|
|
|
|
_changeButton.setAnimation(activeAnimation());
|
|
_changeButton.start(false);
|
|
return true;
|
|
}
|
|
|
|
void GlobalUI::drawChangingButton() {
|
|
auto &player = g_engine->player();
|
|
if (g_engine->menu().isOpen() ||
|
|
!player.isGameLoaded() ||
|
|
!player.semaphore().isReleased() ||
|
|
_isOpeningInventory ||
|
|
_isClosingInventory)
|
|
return;
|
|
|
|
auto anim = activeAnimation();
|
|
if (!_changeButton.hasAnimation() || &_changeButton.animation() != anim) {
|
|
_changeButton.setAnimation(anim);
|
|
_changeButton.pause();
|
|
_changeButton.lastTime() = 42 * (anim->frameCount() - 1) + 1;
|
|
}
|
|
|
|
_changeButton.topLeft() = { (int16)(g_system->getWidth() + 2), -2 };
|
|
if (isHoveringChangeButton() &&
|
|
g_engine->input().isMouseLeftDown() &&
|
|
player.pressedObject() == &_changeButton) {
|
|
_changeButton.topLeft().x -= 2;
|
|
_changeButton.topLeft().y += 2;
|
|
}
|
|
|
|
_changeButton.order() = -9;
|
|
_changeButton.update();
|
|
g_engine->drawQueue().add<AnimationDrawRequest>(_changeButton, false, BlendMode::AdditiveAlpha);
|
|
}
|
|
|
|
struct CenterBottomTextTask final : public Task {
|
|
CenterBottomTextTask(Process &process, int32 dialogId, uint32 durationMs)
|
|
: Task(process)
|
|
, _dialogId(dialogId)
|
|
, _durationMs(durationMs) {}
|
|
|
|
CenterBottomTextTask(Process &process, Serializer &s)
|
|
: Task(process) {
|
|
CenterBottomTextTask::syncGame(s);
|
|
}
|
|
|
|
TaskReturn run() override {
|
|
Font &font = g_engine->globalUI().dialogFont();
|
|
const char *text = g_engine->world().getDialogLine(_dialogId);
|
|
const Point pos(
|
|
g_system->getWidth() / 2,
|
|
g_system->getHeight() - 200
|
|
);
|
|
|
|
TASK_BEGIN;
|
|
_startTime = g_engine->getMillis();
|
|
while (g_engine->getMillis() - _startTime < _durationMs) {
|
|
if (process().isActiveForPlayer()) {
|
|
g_engine->drawQueue().add<TextDrawRequest>(
|
|
font, text, pos, -1, true, kWhite, -kForegroundOrderCount + 1);
|
|
}
|
|
TASK_YIELD(1);
|
|
}
|
|
TASK_END;
|
|
}
|
|
|
|
void debugPrint() override {
|
|
uint32 remaining = g_engine->getMillis() - _startTime <= _durationMs
|
|
? _durationMs - (g_engine->getMillis() - _startTime)
|
|
: 0;
|
|
g_engine->console().debugPrintf("CenterBottomText (%d) with %ums remaining\n", _dialogId, remaining);
|
|
}
|
|
|
|
void syncGame(Serializer &s) override {
|
|
Task::syncGame(s);
|
|
s.syncAsSint32LE(_dialogId);
|
|
s.syncAsUint32LE(_startTime);
|
|
s.syncAsUint32LE(_durationMs);
|
|
}
|
|
|
|
const char *taskName() const override;
|
|
|
|
private:
|
|
int32 _dialogId = 0;
|
|
uint32 _startTime = 0, _durationMs = 0;
|
|
};
|
|
DECLARE_TASK(CenterBottomTextTask)
|
|
|
|
Task *showCenterBottomText(Process &process, int32 dialogId, uint32 durationMs) {
|
|
return new CenterBottomTextTask(process, dialogId, durationMs);
|
|
}
|
|
|
|
void GlobalUI::drawScreenStates() {
|
|
if (g_engine->menu().isOpen())
|
|
return;
|
|
|
|
auto &drawQueue = g_engine->drawQueue();
|
|
if (_isPermanentFaded)
|
|
drawQueue.add<FadeDrawRequest>(FadeType::ToBlack, 1.0f, -9);
|
|
else
|
|
g_engine->game().drawScreenStates();
|
|
}
|
|
|
|
void GlobalUI::syncGame(Serializer &s) {
|
|
s.syncAsByte(_isPermanentFaded);
|
|
}
|
|
|
|
}
|