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,94 @@
/* 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 "qdengine/minigames/adv/common.h"
#include "qdengine/minigames/adv/EffectManager.h"
#include "qdengine/minigames/adv/qdMath.h"
namespace QDEngine {
EffectManager::EffectManager(HoldData<EffectManagerData> &data, MinigameManager *runtime) {
_runtime = runtime;
const char *effectName = _runtime->parameter("effect_name", "effect");
if (_runtime->testObject(effectName)) {
_effect = _runtime->getObject(effectName);
_data.crd = _effect->R();
_effect->set_screen_scale(mgVect2f(0.01f, 0.01f), mgVect2f(10000.f, 10000.f));
_runtime->hide(_effect);
}
data.process(_data);
_effectTime = clamp(_runtime->getParameter("effect_time", 3.f), 0.5f, 10.f);
_phaseTime = clamp(_runtime->getParameter("effect_phase_time", _effectTime / 20.f), 0.03f, 1.f);
_phaseSpeed = clamp(_runtime->getParameter("effect_phase_speed", 1.5f), 1.05f, 10.f);
_current = EFFECT_COUNT;
_effectTimer = 0;
_phaseTimer = 0;
}
EffectManager::~EffectManager() {
_runtime->release(_effect);
}
void EffectManager::quant(float dt) {
if (_current == EFFECT_COUNT)
return;
if (_runtime->getTime() > _effectTimer) {
stop(_current);
return;
}
if (_runtime->getTime() > _phaseTimer) {
_phaseTimer = _runtime->getTime() + _phaseTime;
mgVect2f scale = _effect->screen_scale();
mgVect2f speed = scale;
scale *= _phaseSpeed;
speed = scale - speed;
speed /= _phaseTime;
_effect->set_screen_scale(scale, speed);
}
}
void EffectManager::start(EffectType id) {
if (_current != EFFECT_COUNT || !_effect)
return;
_effectTimer = _runtime->getTime() + _effectTime;
_current = id;
_phaseTimer = _runtime->getTime();
_effect->set_screen_scale(mgVect2f(0.02f, 0.02f), mgVect2f(10000.f, 10000.f));
_effect->set_R(_data.crd);
}
void EffectManager::stop(EffectType id) {
if (_current == EFFECT_COUNT)
return;
_runtime->hide(_effect);
_effect->set_screen_scale(mgVect2f(0.01f, 0.01f), mgVect2f(10000.f, 10000.f));
_current = EFFECT_COUNT;
}
} // namespace QDEngine

View File

@@ -0,0 +1,61 @@
/* 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 QDENGINE_MINIGAMES_ADV_EFFECT_MANAGER_H
#define QDENGINE_MINIGAMES_ADV_EFFECT_MANAGER_H
#include "qdengine/minigames/adv/RunTime.h"
#include "qdengine/minigames/adv/HoldData.h"
namespace QDEngine {
enum EffectType {
EFFECT_1,
EFFECT_COUNT
};
class EffectManager {
public:
EffectManager(HoldData<EffectManagerData> &data, MinigameManager *runtime);
~EffectManager();
void quant(float dt);
void start(EffectType id);
void stop(EffectType id);
private:
EffectType _current;
EffectManagerData _data;
float _phaseTime;
float _effectTime;
float _phaseSpeed;
float _effectTimer;
float _phaseTimer;
QDObject _effect;
MinigameManager *_runtime;
};
} // namespace QDEngine
#endif // QDENGINE_MINIGAMES_ADV_EFFECT_MANAGER_H

View File

@@ -0,0 +1,151 @@
/* 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 "qdengine/qdengine.h"
#include "qdengine/minigames/adv/common.h"
#include "qdengine/minigames/adv/EventManager.h"
#include "qdengine/minigames/adv/RunTime.h"
#include "qdengine/minigames/adv/TextManager.h"
namespace QDEngine {
EventManager::EventPreset::EventPreset() {
score = 0;
fontID = -1;
escapeID = -1;
triggerEventID = -1;
}
EventManager::EventManager(MinigameManager *runtime) {
_runtime = runtime;
_score = 0;
char str_cache[256];
for (int idx = 0;; ++idx) {
snprintf(str_cache, 127, "register_trigger_%d", idx);
if (const char *descr = _runtime->parameter(str_cache, false))
_triggerEvents.push_back(_runtime->getObject(descr));
else
break;
}
debugC(2, kDebugMinigames, "EventManager(): registered %d trigger objects", _triggerEvents.size());
_eventPresets.resize(SYSTEM_EVENTS_SIZE);
for (int idx = 0; idx < SYSTEM_EVENTS_SIZE; ++idx) {
snprintf(str_cache, 127, "system_event_%d", idx);
if (const char * descr = _runtime->parameter(str_cache, false)) {
EventPreset preset;
int read = sscanf(descr, "%d %d", &preset.score, &preset.triggerEventID);
if (read != 2)
warning("EventManager(): Incorrect description string: %s", str_cache);
if (read == 2) {
if (preset.triggerEventID >= (int)_triggerEvents.size())
error("EventManager(): Reference to an unregistered trigger in %s", str_cache);
if (preset.triggerEventID < (int)_triggerEvents.size())
_eventPresets[idx] = preset;
}
}
}
for (int idx = 0;; ++idx) {
snprintf(str_cache, 127, "register_event_%d", idx);
if (const char * descr = _runtime->parameter(str_cache, false)) {
EventPreset preset;
int read = sscanf(descr, "%d %d %d %d", &preset.score, &preset.fontID, &preset.escapeID, &preset.triggerEventID);
if (read != 4)
warning("EventManager(): Incorrect event description string: %d", idx);
if (preset.triggerEventID >= (int)_triggerEvents.size())
error("EventManager(): Reference to an unregistered trigger in %s", str_cache);
if (read == 4 && preset.triggerEventID < (int)_triggerEvents.size())
_eventPresets.push_back(preset);
else
_eventPresets.push_back(EventPreset());
} else
break;
}
debugC(2, kDebugMinigames, "EventManager(): registered %d events", _eventPresets.size());
if (const char * data = _runtime->parameter("allow_negative", false)) {
int tmp;
sscanf(data, "%d", &tmp);
_enableNegative = tmp;
} else
_enableNegative = false;
}
void EventManager::sysEvent(int eventID) {
assert(eventID >= 0);
debugC(6, kDebugMinigames, "EventManager() System event: %d", eventID);
assert(eventID < SYSTEM_EVENTS_SIZE);
mgVect2i pos = _runtime->screenSize() / 2;
event(eventID - SYSTEM_EVENTS_SIZE, mgVect2f(pos.x, pos.y), 1);
}
void EventManager::event(int eventID, const mgVect2f& pos, int factor) {
debugC(6, kDebugMinigames, "EventManager() Event: %d, pos=(%5.1f, %5.1f), fartor=%d", eventID, pos.x, pos.y, factor);
eventID += SYSTEM_EVENTS_SIZE;
if (eventID >= (int)_eventPresets.size())
return;
const EventPreset& pr = _eventPresets[eventID];
if (pr.triggerEventID >= 0) {
assert(pr.triggerEventID < (int)_triggerEvents.size());
_triggerEvents[pr.triggerEventID]->set_state("on");
}
if (pr.score) {
int diff = addScore(pr.score);
if (pr.fontID >= 0 && pr.escapeID >= 0 && diff != 0)
_runtime->textManager().showNumber(diff, pos, pr.fontID, pr.escapeID);
}
}
int EventManager::addScore(int sc) {
int diff = _score;
_score += sc;
if (_score < 0 && !_enableNegative)
_score = 0;
diff = _score - diff;
if (diff)
_runtime->textManager().updateScore(_score);
return diff;
}
} // namespace QDEngine

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 QDENGINE_MINIGAMES_ADV_EVENT_MANAGER_H
#define QDENGINE_MINIGAMES_ADV_EVENT_MANAGER_H
namespace QDEngine {
class EventManager {
public:
EventManager(MinigameManager *runtime);
void sysEvent(int eventID);
void event(int eventID, const mgVect2f& pos, int factor);
int score() const {
return _score;
}
int addScore(int sc);
private:
int _score;
bool _enableNegative;
struct EventPreset {
EventPreset();
int score;
int fontID;
int escapeID;
int triggerEventID;
};
typedef Std::vector<EventPreset> EventPresets;
EventPresets _eventPresets;
QDObjects _triggerEvents;
MinigameManager *_runtime;
};
} // namespace QDEngine
#endif // QDENGINE_MINIGAMES_ADV_EVENT_MANAGER_H

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/>.
*
*/
#include "common/debug.h"
#include "qdengine/qdengine.h"
#include "qdengine/minigames/adv/common.h"
#include "qdengine/minigames/adv/RunTime.h"
namespace QDEngine {
qdMiniGameInterface *create_adv_minigame(const char *name, MinigameConsCallback callback) {
debugC(3, kDebugMinigames, "open_game_interface: %s, runtime%s", name, g_runtime ? "!=0" : "==0");
if (!g_runtime)
return g_runtime = new MinigameManager(callback);
return new MinigameManager(callback);
}
bool close_adv_minigame(qdMiniGameInterface *game) {
debugC(3, kDebugMinigames, "close_game_interface, runtime%s%s", g_runtime == game ? "==game" : "!=game", g_runtime ? "!=0" : "==0");
delete game;
if (game == g_runtime)
g_runtime = 0;
return true;
}
} // namespace QDEngine

View File

@@ -0,0 +1,56 @@
/* 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 "qdengine/minigames/adv/common.h"
#include "qdengine/minigames/adv/FlyObject.h"
#include "qdengine/minigames/adv/qdMath.h"
#include "qdengine/minigames/adv/RunTime.h"
namespace QDEngine {
FlyObjectBase::FlyObjectBase(const mgVect2f& _c, const mgVect2f& _t, float _s)
: current(_c)
, target(_t)
, speed(_s) {
}
bool FlyObjectBase::quant(float dt) {
mgVect2f dir = target;
dir -= current;
float step = speed * dt;
if (abs(dir) < step) {
current = target;
return false;
}
norm(dir);
dir *= step;
current += dir;
return true;
}
bool FlyQDObject::quant(float dt, QDObject& obj, MinigameManager *runtime) {
bool ret = FlyObjectBase::quant(dt);
obj->set_R(runtime->game2world(current, depth));
return ret;
}
} // namespace QDEngine

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 QDENGINE_MINIGAMES_ADV_FLYOBJECT_H
#define QDENGINE_MINIGAMES_ADV_FLYOBJECT_H
namespace QDEngine {
struct FlyObjectBase {
FlyObjectBase(const mgVect2f& _c = mgVect2f(), const mgVect2f& _t = mgVect2f(), float _s = 1.f);
bool quant(float dt);
mgVect2f current;
mgVect2f target;
float speed;
};
struct FlyQDObject : public FlyObjectBase {
FlyQDObject(float dp = 0.f) : depth(dp), data(-1) {}
FlyQDObject(const FlyObjectBase& crd, float dp, int dat) : FlyObjectBase(crd), depth(dp), data(dat) {}
bool operator== (int dat) const {
return data == dat;
}
bool quant(float dt, QDObject& obj, MinigameManager *runtime);
float depth;
int data;
};
typedef Std::vector<FlyQDObject> FlyQDObjects;
} // namespace QDEngine
#endif // QDENGINE_MINIGAMES_ADV_FLYOBJECT_H

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/>.
*
*/
#ifndef QDENGINE_MINIGAMES_ADV_HOLD_DATA_H
#define QDENGINE_MINIGAMES_ADV_HOLD_DATA_H
namespace QDEngine {
template <class T>
class HoldData {
T _emptyData;
T &_data;
bool _empty;
public:
HoldData() : _data(_emptyData), _empty(true) {}
HoldData(T* data, bool empty)
: _data(data ? * data : _emptyData) {
_empty = data ? empty : true;
}
void process(T& current) {
if (_empty) {
_data = current;
_empty = false;
} else
current = _data;
}
const T &get() const {
assert(!_empty);
return _data;
}
};
} // namespace QDEngine
#endif // QDENGINE_MINIGAMES_ADV_HOLD_DATA_H

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 QDENGINE_MINIGAMES_ADV_MINIGAME_INTERFACE_H
#define QDENGINE_MINIGAMES_ADV_MINIGAME_INTERFACE_H
namespace QDEngine {
class MinigameInterface {
public:
enum StateType {
NOT_INITED,
RUNNING,
GAME_WIN,
GAME_LOST
};
MinigameInterface() : _state(NOT_INITED) {}
virtual ~MinigameInterface() {}
virtual void quant(float dt) = 0;
void setState(StateType state) {
_state = state;
}
StateType state() const {
return _state;
}
private:
StateType _state;
};
} // namespace QDEngine
#endif // QDENGINE_MINIGAMES_ADV_MINIGAME_INTERFACE_H

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/>.
*
*/
#include "qdengine/qdengine.h"
#include "qdengine/minigames/adv/common.h"
#include "qdengine/minigames/adv/ObjectContainer.h"
#include "qdengine/minigames/adv/RunTime.h"
namespace QDEngine {
ObjectContainer::ObjectContainer() {
_current = 0;
}
void ObjectContainer::release(MinigameManager *runtime) {
for (auto &it : _objects)
runtime->release(it);
_objects.clear();
_current = 0;
}
void ObjectContainer::pushObject(QDObject& obj) {
assert(Common::find(_objects.begin(), _objects.end(), obj) == _objects.end());
_objects.push_back(obj);
}
const char *ObjectContainer::name() const {
#ifdef _DEBUG
return _name.c_str();
#else
return "";
#endif
}
bool ObjectContainer::load(const char* base_name, MinigameManager *runtime, bool hide) {
if (!runtime->testObject(base_name)) {
warning("ObjectContainer::load(): Object '%s' not found", transCyrillic(base_name));
return false;
}
#ifdef _DEBUG
_name = base_name;
#endif
QDObject obj = runtime->getObject(base_name);
_coord = runtime->world2game(obj);
pushObject(obj);
if (hide)
runtime->hide(obj);
char name[128];
name[127] = 0;
for (int dubl = 0; ; ++dubl) {
snprintf(name, 127, "%s%04d", base_name, dubl);
if (runtime->testObject(name)) {
obj = runtime->getObject(name);
pushObject(obj);
if (hide)
runtime->hide(obj);
} else
break;
}
return true;
}
void ObjectContainer::hideAll(MinigameManager *runtime) {
for (auto &it : _objects)
runtime->hide(it);
}
QDObject ObjectContainer::getObject() {
if (_current < (int)_objects.size())
return _objects[_current++];
return _objects[0]; // bad, but better than crashing
}
void ObjectContainer::releaseObject(QDObject& obj, MinigameManager *runtime) {
QDObjects::iterator it = Common::find(_objects.begin(), _objects.end(), obj);
if (it != _objects.end()) {
if ((int)Common::distance(_objects.begin(), it) >= _current)
error("ObjectContainer::releaseObject(): Object released more than once in to the pool: %s", transCyrillic(name()));
runtime->hide(obj);
if (_current > 0)
SWAP(*it, _objects[--_current]);
obj = 0;
}
}
} // namespace QDEngine

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 QDENGINE_MINIGAMES_ADV_OBJECT_CONTAINER_H
#define QDENGINE_MINIGAMES_ADV_OBJECT_CONTAINER_H
namespace QDEngine {
class ObjectContainer {
QDObjects _objects;
int _current;
mgVect3f _coord;
#ifdef _DEBUG
Common::String _name;
#endif
const char *name() const;
void pushObject(QDObject& obj);
public:
ObjectContainer();
void release(MinigameManager *runtime);
bool load(const char *_name, MinigameManager *runtime, bool hide = true);
void hideAll(MinigameManager *runtime);
const mgVect3f &coord() const {
return _coord;
}
QDObject getObject();
void releaseObject(QDObject& obj, MinigameManager *runtime);
};
} // namespace QDEngine
#endif // QDENGINE_MINIGAMES_ADV_OBJECT_CONTAINER_H

View File

@@ -0,0 +1,98 @@
/* 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 "qdengine/minigames/adv/Range.h"
namespace QDEngine {
void Rangef::set(float min, float max) {
_min = min;
_max = max;
}
Rangef Rangef::intersection(const Rangef& range) {
float begin;
float end;
if (maximum() < range.minimum() || minimum() > range.maximum())
return Rangef(0.f, 0.f);
if (include(range.minimum()))
begin = range.minimum();
else
begin = minimum();
if (include(range.maximum()))
end = range.maximum();
else
end = maximum();
return Rangef(begin, end);
}
float Rangef::clip(float &value) const {
if (include(value))
return value;
else {
if (value < minimum())
return minimum();
else
return maximum();
}
}
// --------------------- Rangei
void Rangei::set(int min, int max) {
_min = min;
_max = max;
}
Rangei Rangei::intersection(const Rangei& range) {
int begin;
int end;
if (maximum() < range.minimum() || minimum() > range.maximum())
return Rangei(0, 0);
if (include(range.minimum()))
begin = range.minimum();
else
begin = minimum();
if (include(range.maximum()))
end = range.maximum();
else
end = maximum();
return Rangei(begin, end);
}
int Rangei::clip(int &value) {
if (include(value))
return value;
else {
if (value < minimum())
return minimum();
else
return maximum();
}
}
} // namespace QDEngine

View File

@@ -0,0 +1,141 @@
/* 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 QDENGINE_MINIGAMES_ADV_RANGE_H
#define QDENGINE_MINIGAMES_ADV_RANGE_H
namespace QDEngine {
class Rangef {
public:
Rangef(float min = 0.f, float max = 0.f)
: _min(min)
, _max(max)
{}
float minimum() const {
return _min;
}
void setMinimum(float min) {
_min = min;
}
float maximum() const {
return _max;
}
void setMaximum(float max) {
_max = max;
}
void set(float min, float max);
float length() const {
return _max - _min;
}
float center() const {
return (_max + _min) / 2.f;
}
/// Корректен ли интервал (нет - в случае когда minimum > maximum);
bool is_valid() const {
return _min <= _max;
}
/// Включает ли отрезок (закрытый интервал) точку \c _value.
bool include(float value) const {
return (_min <= value) && (_max >= value);
}
/// Включает ли интервал в себя \c _range.
bool include(const Rangef& range) const {
return _min <= range._min && _max >= range._max;
}
/// Возвращает пересечение интервала *this и \c _range.
Rangef intersection(const Rangef& range);
/// Возвращает \c _value в пределах интервала [minimum, maximum].
float clip(float &value) const;
private:
float _min;
float _max;
};
// --------------------- Rangei
class Rangei {
public:
Rangei(int min = 0.f, int max = 0.f)
: _min(min)
, _max(max)
{}
int minimum() const {
return _min;
}
void setMinimum(int min) {
_min = min;
}
int maximum() const {
return _max;
}
void setMaximum(int max) {
_max = max;
}
void set(int min, int max);
int length() const {
return _max - _min;
}
int center() const {
return (_max + _min) / 2;
}
/// Корректен ли интервал (нет - в случае когда minimum > maximum);
bool is_valid() const {
return _min <= _max;
}
/// Включает ли отрезок (закрытый интервал) точку \c _value.
bool include(int value) const {
return (_min <= value) && (_max >= value);
}
/// Включает ли интервал в себя \c _range.
bool include(const Rangei& range) const {
return _min <= range._min && _max >= range._max;
}
/// Возвращает пересечение интервала *this и \c _range.
Rangei intersection(const Rangei& range);
/// Возвращает \c _value в пределах интервала [minimum, maximum].
int clip(int &value);
private:
int _min;
int _max;
};
} // namespace QDEngine
#endif // QDENGINE_MINIGAMES_ADV_RANGE_H

View File

@@ -0,0 +1,364 @@
/* 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 QDENGINE_MINIGAMES_ADV_RECT_H
#define QDENGINE_MINIGAMES_ADV_RECT_H
#include "qdengine/xmath.h"
#include "qdengine/minigames/adv/Range.h"
namespace QDEngine {
/*
* FIXME: Подразумевается, что left < right и top < bottom, добавить
* стратегию для кустомизации этого понятия?
*/
/// Абстрактый прямоугольник.
/**
* @param ScalarType - скалярный тип
* @param VectType - векторный тип
*/
template<typename scalar_type, class vect_type>
struct Rect {
typedef vect_type VectType;
typedef scalar_type ScalarType;
typedef Rect<ScalarType, VectType> RectType;
typedef Rangef RangeType;
// конструкторы
Rect() :
_left(ScalarType(0)),
_top(ScalarType(0)),
_width(ScalarType(0)),
_height(ScalarType(0)) {}
/// Создаёт Rect размера \a _size, левый-верхний угол остаётся в точке (0, 0).
Rect(const VectType& size) :
_top(ScalarType(0)),
_left(ScalarType(0)),
_width(size.x),
_height(size.y) {}
Rect(ScalarType left, ScalarType top, ScalarType width, ScalarType height) :
_left(left),
_top(top),
_width(width),
_height(height) {}
Rect(const VectType& _topleft, const VectType& size) :
_left(_topleft.x),
_top(_topleft.y),
_width(size.x),
_height(size.y) {}
void set(ScalarType left, ScalarType top, ScalarType width, ScalarType height) {
_left = left;
_top = top;
_width = width;
_height = height;
}
inline ScalarType left() const {
return _left;
}
inline ScalarType top() const {
return _top;
}
inline ScalarType width() const {
return _width;
}
inline ScalarType height() const {
return _height;
}
VectType _lefttop() const {
return VectType(_left, _top);
}
VectType right_top() const {
return VectType(_left + _width, _top);
}
VectType _leftbottom() const {
return VectType(_left, _top + _height);
}
VectType right_bottom() const {
return VectType(_left + _width, _top + _height);
}
// аксессоры (вычисляющие):
inline ScalarType right() const {
return _left + _width;
}
inline ScalarType bottom() const {
return _top + _height;
}
/*
* FIXME: для float и double деление на 2 лучше заменить на умножение на 0.5,
* для целых типов лучше исползовать сдвиг.
*/
/// Возвращает координаты цетра прямоугольника.
inline VectType center() const {
return VectType(_left + _width / ScalarType(2),
_top + _height / ScalarType(2));
}
/// Возвращает размер прямоугольника.
inline VectType size() const {
return VectType(_width, _height);
}
// сеттеры:
inline void left(ScalarType left) {
_left = left;
}
inline void top(ScalarType top) {
_top = top;
}
inline void width(ScalarType width) {
_width = width;
}
inline void height(ScalarType height) {
_height = height;
}
// сеттеры (вычисляющие):
inline void right(ScalarType right) {
_left = right - _width;
}
inline void bottom(ScalarType bottom) {
_top = bottom - _height;
}
/// Переносит центр прямоугольника в точку \a _center не изменяя его размер.
inline void center(const VectType& center) {
_left = center.x - _width / ScalarType(2);
_top = center.y - _height / ScalarType(2);
}
/*
* FIXME: размер должен менятся относительно левого-верхнего угла (как у
* сеттеров width и height) или относительно центра? Добавить
* класс-стратегию для этих целей? Фунцию с другим именем (напр
* scale (), которая принимает центр, относительно которого происходит
* скэлинг)?
*/
/// Устанавливает новые размеры, сохраняя левый-верхний угол в преждней точке.
inline void size(const VectType& size) {
_width = size.x;
_height = size.y;
}
// утилиты:
/// Проверяет не находится ли точка \a _point внутри прямоугольника
inline bool point_inside(const VectType& point) const {
if (point.x >= left() && point.y >= top() &&
point.x <= right() && point.y <= bottom())
return true;
else
return false;
}
/// Проверяет не находится ли прямоугольник \a _rect внутри прямоугольника
inline bool rect_inside(const RectType& rect) const {
if (rect.left() >= left() && rect.top() >= top() &&
rect.bottom() <= bottom() && rect.right() <= right())
return true;
else
return false;
}
inline bool rect_overlap(const RectType& rect) const {
if (left() > rect.right() || right() < rect.left()
|| top() > rect.bottom() || bottom() < rect.top())
return false;
return true;
}
/// Производит скэлинг.
/**
* Возвращает копию прямоугольника, над которой произведён скэлинг
* относительно точки \a _origin.
*/
inline RectType scaled(const VectType& scale, const VectType& origin) const {
return (*this - origin) * scale + origin;
}
/// Исправляет отрицательную ширину/высоту
inline void validate() {
if (width() < ScalarType(0)) {
left(left() + width());
width(-width());
}
if (height() < ScalarType(0)) {
top(top() + height());
height(-height());
}
}
inline RectType intersection(const RectType& rect) const {
RangeType xRange = RangeType(left(), right()).intersection(RangeType(rect.left(), rect.right()));
RangeType yRange = RangeType(top(), bottom()).intersection(RangeType(rect.top(), rect.bottom()));
return RectType(xRange.minimum(), yRange.minimum(), xRange.length(), yRange.length());
}
// Операторы
RectType operator+(const VectType& point) const {
return RectType(left() + point.x, top() + point.y,
width(), height());
}
RectType operator-(const VectType& point) const {
return RectType(left() - point.x, top() - point.y,
width(), height());
}
RectType operator*(const VectType& point) const {
return RectType(left() * point.x, top() * point.y,
width() * point.x, height() * point.y);
}
RectType operator*(const RectType& rhs) const {
VectType leftTop(left() + width() * rhs.left(), top() + height() * rhs.top());
VectType size(this->size() * rhs.size());
return RectType(leftTop, size);
}
RectType operator/(const RectType& rhs) const {
VectType leftTop((left() - rhs.left()) / rhs.width(), (top() - rhs.top()) / rhs.height());
VectType size(width() / rhs.width(), height() / rhs.height());
return RectType(leftTop, size);
}
RectType operator/(const VectType& point) const {
return RectType(left() / point.x, top() / point.y,
width() / point.x, height() / point.y);
}
bool operator==(const RectType& rect) const {
return (_left == rect._left && _top == rect._top &&
_width == rect._width && _height == rect._height);
}
bool eq(const RectType& rect, ScalarType eps = FLT_COMPARE_TOLERANCE) const {
return (abs(_left - rect._left) < eps && abs(_top - rect._top) < eps &&
abs(_width - rect._width) < eps && abs(_height - rect._height) < eps);
}
bool operator!=(const RectType& rect) const {
return (_left != rect._left || _top != rect._top ||
_width != rect._width || _height != rect._height);
}
protected:
ScalarType _left;
ScalarType _top;
ScalarType _width;
ScalarType _height;
#if 0
public:
// SideKick на этом обламывается:
template<class ST, class VT>
operator ::Rect<ST, VT>() const {
return ::Rect<ST, VT>(static_cast<ST>(left()),
static_cast<ST>(top()),
static_cast<ST>(width()),
static_cast<ST>(height()));
}
#endif
bool clipLine(VectType& pos0, VectType& pos1) const;
};
template<typename ScalarType, class VectType>
bool Rect<ScalarType, VectType>::clipLine(VectType& pos0, VectType& pos1) const {
VectType p0(pos0), p1(pos1);
bool b0 = point_inside(p0);
bool b1 = point_inside(p1);
if (b0 && b1) // вся линия внутри clip
return true;
else {
float tc;
float t[4] = {-1.0f, -1.0f, -1.0f, -1.0f};
int find = 0;
ScalarType dx = p1.x - p0.x;
ScalarType dy = p1.y - p0.y;
ScalarType crd;
if (abs(dy) > 0) {
tc = (float)(top() - p0.y) / dy;
if (tc >= 0.0f && tc <= 1.0f) {
crd = p0.x + tc * dx;
if (crd >= left() && crd <= right())
t[find++] = tc;
}
tc = (float)(bottom() - p0.y) / dy;
if (tc >= 0.0f && tc <= 1.0f) {
crd = p0.x + tc * dx;
if (crd >= left() && crd <= right())
t[find++] = tc;
}
}
if (abs(dx) > 0) {
tc = (float)(left() - p0.x) / dx;
if (tc >= 0.0f && tc <= 1.0f) {
crd = p0.y + tc * dy;
if (crd >= top() && crd <= bottom())
t[find++] = tc;
}
tc = (float)(right() - p0.x) / dx;
if (tc >= 0.0f && tc <= 1.0f) {
crd = p0.y + tc * dy;
if (crd >= top() && crd <= bottom())
t[find++] = tc;
}
}
if (b0) { //внутри только точка p0
pos1.set(p0.x + t[0]*dx, p0.y + t[0]*dy);
pos0.set(p0.x, p0.y);
} else if (b1) { //внутри только точка p1
pos0.set(p0.x + t[0]*dx, p0.y + t[0]*dy);
pos1.set(p1.x, p1.y);
} else if (find) { //обе точки снаружи, но часть отрезка внутри
if (t[0] < t[1]) {
pos0.set(p0.x + t[0]*dx, p0.y + t[0]*dy);
pos1.set(p0.x + t[1]*dx, p0.y + t[1]*dy);
} else {
pos1.set(p0.x + t[0]*dx, p0.y + t[0]*dy);
pos0.set(p0.x + t[1]*dx, p0.y + t[1]*dy);
}
} else
return false;
}
return true;
}
} // namespace QDEngine
#endif // QDENGINE_MINIGAMES_ADV_RECT_H

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,318 @@
/* 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 QDENGINE_MINIGAMES_ADV_RUNTIME_H
#define QDENGINE_MINIGAMES_ADV_RUNTIME_H
#include "common/hashmap.h"
#include "qdengine/minigames/adv/common.h"
namespace Common {
class SeekableReadStream;
class SeekableWriteStream;
}
namespace QDEngine {
class qdEngineInterface;
class qdMinigameSceneInterface;
class MinigameInterface;
class TextManager;
class TimeManager;
class EventManager;
class EffectManager;
struct TimeManagerData {
mgVect3f crd;
};
struct EffectManagerData {
mgVect3f crd;
};
struct MinigameData {
MinigameData();
int _sequenceIndex;
int _lastScore;
int _lastTime;
int _bestTime;
int _bestScore;
void write(Common::WriteStream &out) const;
void read(Common::ReadStream &out);
};
struct GameInfo {
GameInfo();
void persist(Common::SeekableReadStream &in);
void write(Common::WriteStream &out) const;
void read(Common::ReadStream &in);
void free();
static int version() {
return 9;
}
bool empty() const {
return _empty && _game._sequenceIndex < 0;
}
MinigameData _game;
bool _empty;
TimeManagerData _timeManagerData;
EffectManagerData _effectManagerData;
uint _dataSize;
void *_gameData;
};
typedef MinigameInterface *(*MinigameConsCallback)(MinigameManager *runtime);
qdMiniGameInterface *create_adv_minigame(const char *name, MinigameConsCallback callback);
bool close_adv_minigame(qdMiniGameInterface *game);
class MinigameManager : public qdMiniGameInterface {
friend class TempValue;
public:
MinigameManager(MinigameConsCallback callback);
~MinigameManager();
// begin MiniGame virtual interface
bool init(const qdEngineInterface *engine_interface);
bool quant(float dt);
bool finit();
bool new_game(const qdEngineInterface *engine);
int save_game(const qdEngineInterface *engine, const qdMinigameSceneInterface *scene, char *buffer, int buffer_size);
int load_game(const qdEngineInterface *engine, const qdMinigameSceneInterface *scene, const char *buffer, int buffer_size);
// finish MiniGame virtual interface
// при необходимости заменяет на неизмененные предыдущим прохождением данные
bool processGameData(Common::SeekableReadStream &data);
mgVect2f mousePosition() const {
return _mousePos;
}
bool mouseLeftPressed() const;
bool mouseRightPressed() const;
bool keyPressed(int vKey, bool once = false) const;
mgVect2i screenSize() const {
return _screenSize;
}
float getTime() const {
return _gameTime;
}
GameInfo *getCurrentGameInfo() const { return _currentGameInfo; };
const MinigameData *getScore(int level, int game) const;
bool debugMode() const {
return _debugMode;
}
TextManager &textManager() const {
return *_textManager;
}
void signal(SystemEvent id);
void event(int eventID, const mgVect2f& pos, int factor = 1);
void event(int eventID, const mgVect2i& pos, int factor = 1) {
event(eventID, mgVect2f(pos.x, pos.y), factor);
}
// указывает вариант показа информации о победе (поворот собранной картинки и т.д.)
void setCompleteHelpVariant(int idx);
// указывает номер подсказки для показа, -1 - спрятать подсказку
void setGameHelpVariant(int idx);
// Возвращает параметр из прикрепленного к игре ini файла
const char *parameter(const char *name, bool required = true) const;
const char *parameter(const char *name, const char *def) const;
// Пересчитывает из экранных координат UI игры в 3D координаты R() объекта на мире
mgVect3f game2world(const mgVect3i &coord) const;
mgVect3f game2world(const mgVect3f &coord) const;
mgVect3f game2world(const mgVect2i &coord, int depth = 0) const;
mgVect3f game2world(const mgVect2f &coord, int depth = 0) const;
// Пересчитывает из мировых координат R() в 2D UI координаты и глубину
mgVect2f world2game(const mgVect3f& pos) const;
mgVect3f world2game(qdMinigameObjectInterface *obj) const;
// размер объекта
mgVect2f getSize(qdMinigameObjectInterface *obj) const;
// Меняет глубину объекта, не меняя его 2D положения на экране
void setDepth(qdMinigameObjectInterface *obj, int depth) const;
// Получает глубину объекта, чем меньше, тем ближе к игроку
float getDepth(qdMinigameObjectInterface *obj) const;
// Получает глубину точки, чем меньше, тем ближе к игроку
float getDepth(const mgVect3f& pos) const;
// получает интерфейс к динамическому игровому объекту по имени
QDObject getObject(const char *name) const;
// проверяет существование динамического объекта в сцене
bool testObject(const char *name) const;
// освобождает интерфейс
void release(QDObject& obj);
// задать текст для контрола
void setText(const char *name, const char *text) const;
void setText(const char *name, int toText, const char *format = "%d") const;
// спрятать объект за пределами экрана
void hide(qdMinigameObjectInterface *obj) const;
// случайное значение в диапазоне [min, max]
float rnd(float min, float max) const;
int rnd(int min, int max) const;
// случайный диапазон, из набора вероятностей
int rnd(const Std::vector<float> &prob) const;
// файл со списком игр по уровням
const char *gameListFileName() const {
return "resource/minigames.lst";
}
int getParameter(const char* name, const int& defValue);
bool getParameter(const char* name, int& out, bool obligatory);
float getParameter(const char* name, const float &defValue);
bool getParameter(const char* name, float &out, bool obligatory);
mgVect2f getParameter(const char* name, const mgVect2f& defValue);
bool getParameter(const char* name, mgVect2f& out, bool obligatory);
mgVect2i getParameter(const char* name, const mgVect2i& defValue);
bool getParameter(const char* name, mgVect2i& out, bool obligatory);
private:
MinigameInterface *_game;
// Вывод текста с помощью объектов
TextManager *_textManager;
// Подсчет и визуализация времени
TimeManager *_timeManager;
// Обработка событий игры
EventManager *_eventManager;
// выводимые эффекты
EffectManager *_effectManager;
// Время в секундах с момента стара игры
float _gameTime;
// кеш проверенных на нажатие клавиш, для отслеживания непосредственно нажатия
mutable bool _lastKeyChecked[256];
// Размер играна
mgVect2i _screenSize;
// текущее положение мыши
mgVect2f _mousePos;
// подстройка мыши
mgVect2f _mouseAdjast;
// объект для передачи сигнала об окончании игры в триггеры
qdMinigameObjectInterface *_state_flag;
// объект для получения сигнала о постановке на паузу
qdMinigameObjectInterface *_pause_flag;
// справка по победе
QDObject _complete_help;
QDObject _complete_help_miniature;
// текущее состояние для включения справки
Common::String _complete_help_state_name;
// справка по игре
QDObject _game_help;
QDObject _game_help_trigger;
bool _game_help_enabled;
// текущее состояние для включения справки
Common::String _game_help_state_name;
// интерфейс к движку
const qdEngineInterface *_engine;
// интерфейс к текущей сцене
qdMinigameSceneInterface *_scene;
// игра запущена для отладки
bool _debugMode;
// rnd seed
int _seed;
// кнопки мыши инвертированы
bool _invertMouseButtons;
// имя файла и информацией о минииграх
Common::String _state_container_name;
// количество пройденных игр на каждом уровне
typedef Common::HashMap<int, int> Counters;
Counters _completeCounters;
struct GameInfoIndex {
GameInfoIndex(int idx, int level) : _gameNum(idx), _gameLevel(level) {}
int _gameNum;
int _gameLevel;
void write(Common::WriteStream &out) const;
void read(Common::ReadStream &in);
bool operator< (const GameInfoIndex& rs) const {
return _gameLevel == rs._gameLevel ? _gameNum < rs._gameNum : _gameLevel < rs._gameLevel;
}
};
struct GameInfoIndex_Hash {
uint operator()(const GameInfoIndex& x) const {
return (x._gameNum << 16) + x._gameLevel;
}
};
struct GameInfoIndex_EqualTo {
uint operator()(const GameInfoIndex& x, const GameInfoIndex& y) const {
return x._gameNum == y._gameNum && x._gameLevel == y._gameLevel;
}
};
// информация о пройденных играх
typedef Common::HashMap<GameInfoIndex, GameInfo, GameInfoIndex_Hash, GameInfoIndex_EqualTo> GameInfoMap;
GameInfoMap _gameInfos;
// Информация о текущей игре, при выходе запишется
GameInfoIndex _currentGameIndex;
GameInfo *_currentGameInfo;
// проверить что все необходимые игры пройдены
bool testAllGamesWin();
// Непосредственно создает и инициализирует игру
bool createGame();
// обработка победы
void gameWin();
// обработка поражения
void gameLose();
// чтение данных об играх
bool loadState(bool current = true);
// сохранение данных в файл
void saveState(bool force = false);
// Полуить объект-счетчик
QDCounter getCounter(const char *name);
// Освободить счетчик
void release(QDCounter& counter);
MinigameConsCallback _callback = nullptr;
};
} // namespace QDEngine
#endif // QDENGINE_MINIGAMES_ADV_RUNTIME_H

View File

@@ -0,0 +1,381 @@
/* 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 "qdengine/qdengine.h"
#include "qdengine/minigames/adv/common.h"
#include "qdengine/minigames/adv/TextManager.h"
#include "qdengine/minigames/adv/RunTime.h"
#include "qdengine/minigames/adv/qdMath.h"
namespace QDEngine {
TextManager::TextManager(MinigameManager *runtime) {
char str_cache[256];
_runtime = runtime;
for (int idx = 0;; ++idx) {
snprintf(str_cache, 127, "register_font_%d", idx);
if (const char *descr = _runtime->parameter(str_cache, false)) {
sscanf(descr, "%255s", str_cache);
Font digit;
if (!digit.pool.load(str_cache, _runtime))
break;
debugCN(2, kDebugMinigames, "TextManager(): %d character set \"%s\" loaded, ", idx, str_cache);
snprintf(str_cache, 127, "font_size_%d", idx);
if ((descr = _runtime->parameter(str_cache, false))) {
int read = sscanf(descr, "%f %f", &digit.size.x, &digit.size.y);
if (read != 2)
warning("TextManager(): incorrect font size definition in [%s]", str_cache);
} else {
QDObject obj = digit.pool.getObject();
obj.setState("0");
digit.size = _runtime->getSize(obj);
digit.pool.releaseObject(obj, _runtime);
}
debugC(2, kDebugMinigames, "set size to (%5.1f, %5.1f)\n", digit.size.x, digit.size.y);
_fonts.push_back(digit);
} else
break;
}
for (int idx = 0;; ++idx) {
snprintf(str_cache, 127, "register_particle_escape_%d", idx);
if (const char *descr = _runtime->parameter(str_cache, false)) {
Escape escape;
int read = sscanf(descr, "%d (%f><%f, %f><%f) (%f><%f, %f><%f) %f '%15s",
&escape.depth,
&escape.vel_min.x, &escape.vel_max.x, &escape.vel_min.y, &escape.vel_max.y,
&escape.accel_min.x, &escape.accel_max.x, &escape.accel_min.y, &escape.accel_max.y,
&escape.aliveTime, escape.format);
if (read != 11) {
warning("TextManager(): incorrect particle definition in [%s]", str_cache);
break;
}
_escapes.push_back(escape);
} else
break;
}
debugCN(2, kDebugMinigames, "TextManager(): registered %d particle escapes", _escapes.size());
if (getStaticPreset(_show_scores, "show_scores"))
_show_scores.textID = createStaticText(_show_scores.pos, _show_scores.font, _show_scores.align);
else
_show_scores.textID = -1;
if (getStaticPreset(_show_time, "show_time"))
_show_time.textID = createStaticText(_show_time.pos, _show_time.font, _show_time.align);
else
_show_time.textID = -1;
_targetScore = 0;
_currentScore = 0;
_scoreUpdateTimer = 0.f;
_scoreUpdateTime = _runtime->getParameter("score_update_time", 0.1f);
}
bool TextManager::getStaticPreset(StaticTextPreset& preset, const char *name) const {
if (const char *descr = _runtime->parameter(name, false)) {
int align = 0;
char str[64];
str[63] = 0;
int read = sscanf(descr, "%d %d |%63s", &align, &preset.font, str);
if (read != 3) {
warning("TextManager::getStaticPreset(): Incorrect text format description in %s", transCyrillic(name));
return false;
}
char *pos_obj = strchr(str, '|');
if (!pos_obj) {
warning("TextManager::getStaticPreset(): Incorrect text format description (2) in %s", transCyrillic(name));
return false;
}
*pos_obj = 0;
++pos_obj;
strncpy(preset.format, str, 15);
switch (align) {
case 0:
preset.align = ALIGN_RIGHT;
break;
case 1:
preset.align = ALIGN_LEFT;
break;
default:
preset.align = ALIGN_CENTER;
break;
}
if (QDObject obj = _runtime->getObject(pos_obj)) {
preset.pos = _runtime->world2game(obj);
_runtime->release(obj);
} else
return false;
} else
return false;
return true;
}
TextManager::~TextManager() {
for (auto &mit : _flowMsgs)
mit.release();
for (auto &sit : _staticMsgs)
sit.release();
for (auto &dit : _fonts)
dit.pool.release(_runtime);
}
int TextManager::createStaticText(const mgVect3f& pos, int fontID, TextAlign align) {
assert(fontID >= 0 && fontID < (int)_fonts.size());
StaticMessage msg(_runtime, &_fonts[fontID]);
msg._align = align;
msg._depth = pos.z;
msg._pos = mgVect2f(pos.x, pos.y);
_staticMsgs.push_back(msg);
return (int)_staticMsgs.size() - 1;
}
void TextManager::updateStaticText(int textID, const char *txt) {
assert(textID >= 0 && textID < (int)_staticMsgs.size());
_staticMsgs[textID].setText(txt);
}
void TextManager::showText(const char *txt, const mgVect2f& pos, int fontID, int escapeID) {
assert(fontID >= 0 && fontID < (int)_fonts.size());
assert(escapeID >= 0 && escapeID < (int)_escapes.size());
Escape& es = _escapes[escapeID];
Message msg(_runtime, &_fonts[fontID]);
msg.setText(txt);
if (msg.empty())
return;
msg._time = es.aliveTime > 0 ? es.aliveTime : 1.e6f;
msg._depth = es.depth;
msg._pos = pos;
msg._vel.x = _runtime->rnd(es.vel_min.x, es.vel_max.x);
msg._vel.y = _runtime->rnd(es.vel_min.y, es.vel_max.y);
msg._accel.x = _runtime->rnd(es.accel_min.x, es.accel_max.x);
msg._accel.y = _runtime->rnd(es.accel_min.y, es.accel_max.y);
_flowMsgs.push_back(msg);
}
void TextManager::showNumber(int num, const mgVect2f& pos, int fontID, int escapeID) {
assert(fontID >= 0 && fontID < (int)_fonts.size());
assert(escapeID >= 0 && escapeID < (int)_escapes.size());
char buf[16];
buf[15] = 0;
snprintf(buf, 15, _escapes[escapeID].format, num);
showText(buf, pos, fontID, escapeID);
}
TextManager::Escape::Escape() {
depth = 0;
aliveTime = -1;
format[15] = 0;
}
TextManager::StaticTextPreset::StaticTextPreset() {
font = -1;
align = ALIGN_CENTER;
format[15] = 0;
textID = 0;
}
TextManager::StaticMessage::StaticMessage(MinigameManager *runtime, Font *font, TextAlign align) {
_font = font;
_align = align;
_depth = 0.f;
_runtime = runtime;
}
void TextManager::StaticMessage::release() {
for (auto &it : _objects)
_font->pool.releaseObject(it, _runtime);
_objects.clear();
}
void TextManager::StaticMessage::setText(const char *str) {
assert(_font);
if (!str) {
release();
return;
}
int len = (int)strlen(str);
if ((int)_objects.size() < len)
_objects.resize(len);
else
while ((int)_objects.size() > len) {
if (_objects.back())
_font->pool.releaseObject(_objects.back(), _runtime);
_objects.pop_back();
}
for (int idx = 0; idx < len; ++idx) {
if (validSymbol(str[idx])) {
if (!_objects[idx])
_objects[idx] = _font->pool.getObject();
} else if (_objects[idx])
_font->pool.releaseObject(_objects[idx], _runtime);
}
char name[2];
name[1] = 0;
for (int idx = 0; idx < len; ++idx) {
if (_objects[idx]) {
name[0] = str[idx];
_objects[idx].setState(name);
}
}
update();
}
void TextManager::StaticMessage::update() {
if (_objects.empty())
return;
float width = _font->size.x * (_objects.size() - 1);
float x = _pos.x;
float y = _pos.y;
switch (_align) {
case ALIGN_RIGHT:
x -= width;
break;
case ALIGN_CENTER:
x -= width / 2.f;
break;
default:
break;
}
if (y < -_font->size.y || y > _runtime->screenSize().y + _font->size.y
|| x < -2 * width || x > _runtime->screenSize().x + 2 * width) {
release();
return;
}
for (auto &it : _objects) {
if (it)
it->set_R(_runtime->game2world(mgVect2f(x, y), _depth));
x += _font->size.x;
}
}
TextManager::Message::Message(MinigameManager *runtime, Font *font)
: StaticMessage(runtime, font) {
_time = 0.f;
}
void TextManager::Message::release() {
StaticMessage::release();
_time = 0.f;
}
void TextManager::Message::quant(float dt) {
if (empty())
return;
_time -= dt;
if (_time < 0.f) {
release();
return;
}
_vel += _accel * dt;
_pos += _vel * dt;
update();
}
void TextManager::quant(float dt) {
Messages::iterator it = _flowMsgs.begin();
while (it != _flowMsgs.end()) {
it->quant(dt);
if (it->empty())
it = _flowMsgs.erase(it);
else
++it;
}
if (_show_scores.textID >= 0) {
if (_scoreUpdateTimer >= 0.f && _scoreUpdateTimer <= _runtime->getTime()) {
int sgn = _targetScore - _currentScore < 0 ? -1 : 1;
int mod = abs(_currentScore - _targetScore);
_currentScore += sgn * (mod / 10 + 1);
char buf[16];
buf[15] = 0;
snprintf(buf, 15, _show_scores.format, _currentScore);
updateStaticText(_show_scores.textID, buf);
_scoreUpdateTimer = _currentScore != _targetScore ? _runtime->getTime() + _scoreUpdateTime : -1.f;
}
}
}
void TextManager::updateScore(int score) {
_targetScore = score;
if (_scoreUpdateTimer < 0.f)
_scoreUpdateTimer = _runtime->getTime();
}
void TextManager::updateTime(int seconds) {
if (_show_time.textID >= 0) {
char buf[16];
buf[15] = 0;
int h = seconds / 3600;
seconds -= 3600 * h;
int minutes = seconds / 60;
seconds -= 60 * minutes;
snprintf(buf, 15, _show_time.format, h, minutes, seconds);
updateStaticText(_show_time.textID, buf);
}
}
} // namespace QDEngine

View File

@@ -0,0 +1,137 @@
/* 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 QDENGINE_MINIGAMES_ADV_TEXT_MANAGER_H
#define QDENGINE_MINIGAMES_ADV_TEXT_MANAGER_H
#include "qdengine/minigames/adv/ObjectContainer.h"
namespace QDEngine {
enum TextAlign {
ALIGN_LEFT,
ALIGN_RIGHT,
ALIGN_CENTER
};
class TextManager {
public:
TextManager(MinigameManager *runtime);
~TextManager();
int createStaticText(const mgVect3f& screen_pos, int fontID, TextAlign align);
void updateStaticText(int textID, const char *txt);
void showText(const char *txt, const mgVect2f& pos, int fontID, int escapeID);
void showNumber(int num, const mgVect2f& pos, int fontID, int escapeID);
void quant(float dt);
void updateScore(int score);
void updateTime(int seconds);
private:
struct Font {
mgVect2f size;
ObjectContainer pool;
};
typedef Std::vector<Font> Fonts;
struct Escape {
Escape();
int depth;
float aliveTime;
mgVect2f vel_min;
mgVect2f vel_max;
mgVect2f accel_min;
mgVect2f accel_max;
char format[16];
};
typedef Std::vector<Escape> Escapes;
struct StaticTextPreset {
StaticTextPreset();
mgVect3f pos;
int font;
TextAlign align;
char format[16];
int textID;
};
bool getStaticPreset(StaticTextPreset& preset, const char *name) const;
struct StaticMessage {
StaticMessage(MinigameManager *runtime, Font *font = 0, TextAlign _align = ALIGN_CENTER);
void release();
bool empty() const {
return _objects.empty();
}
void setText(const char *str);
int _depth;
mgVect2f _pos;
TextAlign _align;
protected:
void update();
bool validSymbol(unsigned char ch) const {
return ch > ' ' && ch != '_';
}
private:
Font *_font;
QDObjects _objects;
MinigameManager *_runtime;
};
typedef Std::vector<StaticMessage> StaticMessages;
struct Message : public StaticMessage {
Message(MinigameManager *runtime, Font *font = 0);
void release();
void quant(float dt);
float _time;
mgVect2f _vel;
mgVect2f _accel;
};
typedef Std::vector<Message> Messages;
Fonts _fonts;
Escapes _escapes;
StaticTextPreset _show_scores;
StaticTextPreset _show_time;
StaticMessages _staticMsgs;
Messages _flowMsgs;
int _targetScore;
int _currentScore;
float _scoreUpdateTime;
float _scoreUpdateTimer;
MinigameManager *_runtime;
};
} // namespace QDEngine
#endif // QDENGINE_MINIGAMES_ADV_TEXT_MANAGER_H

View File

@@ -0,0 +1,48 @@
/* 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 "qdengine/minigames/adv/common.h"
#include "qdengine/minigames/adv/RunTime.h"
namespace QDEngine {
const char *QDObject::getName() const {
#ifdef _DEBUG
return _name.c_str();
#else
return "";
#endif
}
bool QDObject::hit(const mgVect2f& point) const {
return _obj->hit_test(mgVect2i(round(point.x), round(point.y)));
}
float QDObject::depth(MinigameManager *runtime) const {
return runtime->getDepth(_obj);
}
void QDObject::setState(const char* name) {
if (!_obj->is_state_active(name))
_obj->set_state(name);
}
} // namespace QDEngine

View File

@@ -0,0 +1,92 @@
/* 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 QDENGINE_MINIGAMES_ADV_COMMON_H
#define QDENGINE_MINIGAMES_ADV_COMMON_H
#include "qdengine/qd_fwd.h"
#include "qdengine/qdcore/qd_minigame_interface.h"
namespace QDEngine {
class MinigameManager;
typedef mgVect3<int> mgVect3i;
class QDObject {
qdMinigameObjectInterface *_obj;
#ifdef _DEBUG
Common::String _name;
#endif
public:
QDObject(qdMinigameObjectInterface* obj = 0, const char* name = "") : _obj(obj) {
#ifdef _DEBUG
_name = name;
#endif
}
const char *getName() const; // DEBUG ONLY
bool hit(const mgVect2f& point) const;
float depth(MinigameManager *runtime) const;
void setState(const char* name);
bool operator==(const QDObject& obj) const {
return _obj == obj._obj;
}
bool operator==(const qdMinigameObjectInterface* obj) const {
return _obj == obj;
}
operator qdMinigameObjectInterface* () const {
return _obj;
}
qdMinigameObjectInterface* operator->() const {
return _obj;
}
};
typedef qdMinigameCounterInterface *QDCounter;
typedef Std::vector<QDObject> QDObjects;
typedef Std::vector<int> Indexes;
typedef Std::vector<mgVect3f> Coords;
class MinigameManager;
extern MinigameManager *g_runtime;
enum SystemEvent {
EVENT_TIME_1_SECOND_TICK,
EVENT_TIME_10_SECOND_TICK,
EVENT_TIME_60_SECOND_TICK,
EVENT_TIME_10_SECOND_LEFT,
EVENT_TIME_LESS_10_SECOND_LEFT_SECOND_TICK,
EVENT_TIME_OUT,
EVENT_GAME_LOSE,
EVENT_GAME_WIN,
SYSTEM_EVENTS_SIZE
};
} // namespace QDEngine
#endif // QDENGINE_MINIGAMES_ADV_COMMON_H

View File

@@ -0,0 +1,216 @@
/* 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/file.h"
#include "common/system.h"
#include "qdengine/qdengine.h"
#include "qdengine/minigames/adv/common.h"
#include "qdengine/minigames/adv/m_karaoke.h"
#include "qdengine/minigames/adv/RunTime.h"
namespace QDEngine {
MinigameInterface *createMinigameKaraoke(MinigameManager *runtime) {
return new Karaoke(runtime);
}
Karaoke::Node::Node() {
type = Karaoke::CLEAR;
time = 0.f;
}
Karaoke::Karaoke(MinigameManager *runtime) {
_runtime = runtime;
_controlName = _runtime->parameter("control_name", true);
if (!_controlName || !*_controlName)
return;
_colorReaded = _runtime->parameter("color_first", true);
if (!_colorReaded || !*_colorReaded)
return;
struct Parse {
Common::File &file;
Nodes &nodes;
float currentTime;
char buf[1024];
const char *begin;
const char *cur;
void putLine(bool putEmpty = false) {
if (cur > begin && *begin) {
Node node;
node.type = STRING;
node.time = currentTime;
node.text = Common::String(begin, cur);
nodes.push_back(node);
} else if (putEmpty) {
Node node;
node.type = STRING;
node.time = currentTime;
nodes.push_back(node);
}
begin = cur = buf;
}
char read() {
if (!file.read((void *)cur, 1)) {
putLine();
return 0;
}
return *cur++;
}
Parse(Common::File& _file, Nodes& _nodes) : file(_file), nodes(_nodes) {
currentTime = 0.f;
begin = cur = buf;
bool prevNumber = false;
while (!file.eos()) {
switch (read()) {
case 0:
return;
case '/': {
if (read() == '/') {
--cur;
break;
}
cur -= 2;
putLine(prevNumber);
prevNumber = true;
file.seek(-1, SEEK_CUR);
float tm = 0;
char c;
do {
c = read();
} while ((c >= '0' && c <= '9') || c == '.');
cur = buf;
tm = strtod(cur, nullptr);
if (tm <= 0.f) {
currentTime = 0.f;
nodes.push_back(Node());
} else
currentTime = tm;
file.seek(-1, SEEK_CUR);
continue;
}
case '>':
if (prevNumber)
--cur;
}
prevNumber = false;
}
putLine();
}
};
const char *fileName = _runtime->parameter("text_file", true);
if (!fileName)
return;
Common::File file;
Common::Path path((const char *)transCyrillic(fileName), '\\');
if (!file.open(path)) {
// Try with punyencoded path
if (!file.open(path.punycodeEncode())) {
assert(!(Common::String() + "Не удалось открыть файл \"" + fileName + "\"").c_str());
return;
}
}
Parse(file, _nodes);
debugC(2, kDebugMinigames, "read %d tags", _nodes.size());
_startScreenTag = 0;
_currentTag = 0;
_startTime = 0.001f * g_system->getMillis();
_startTagTime = 0.f;
setState(MinigameInterface::RUNNING);
}
void Karaoke::quant(float dt) {
float curTime = 0.001f * g_system->getMillis() - _startTime;
if (curTime < 0.f)
curTime = 0.f;
Node& node = _nodes[_currentTag];
if (node.type == CLEAR) {
++_currentTag;
if ((uint)_currentTag == _nodes.size())
setState(MinigameInterface::GAME_WIN);
_startScreenTag = _currentTag;
return;
}
Common::String outText;
outText += _colorReaded;
int idx = _startScreenTag;
while (idx < _currentTag) {
assert((uint)idx < _nodes.size());
assert(_nodes[idx].type == STRING);
outText += _nodes[idx].text.c_str();
++idx;
}
float phase = (curTime - _startTagTime) / node.time;
assert(phase >= 0.f);
if (phase >= 1.f) {
outText += node.text + "&>";
++_currentTag;
_startTagTime += node.time;
if ((uint)_currentTag == _nodes.size())
setState(MinigameInterface::GAME_WIN);
} else {
int part = phase * node.text.size();
outText += Common::String(node.text.begin(), node.text.begin() + part) + "&>";
outText += Common::String(node.text.begin() + part, node.text.end()).c_str();
}
++idx;
while ((uint)idx < _nodes.size()) {
if (_nodes[idx].type == CLEAR)
break;
outText += _nodes[idx].text.c_str();
++idx;
}
if (_runtime->mouseRightPressed())
debugC(2, kDebugMinigames, "%s", outText.c_str());
_runtime->setText(_controlName, outText.c_str());
}
} // namespace QDEngine

View File

@@ -0,0 +1,66 @@
/* 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 QDENGINE_MINIGAMES_ADV_M_KARAOKE_H
#define QDENGINE_MINIGAMES_ADV_M_KARAOKE_H
#include "qdengine/minigames/adv/common.h"
#include "qdengine/minigames/adv/MinigameInterface.h"
namespace QDEngine {
MinigameInterface *createMinigameKaraoke(MinigameManager *runtime);
class Karaoke : public MinigameInterface {
public:
Karaoke(MinigameManager *runtime);
void quant(float dt);
enum TagType {
STRING,
CLEAR
};
private:
const char *_controlName = nullptr;
const char *_colorReaded = nullptr;
struct Node {
Node();
TagType type;
float time;
Common::String text;
};
typedef Std::vector<Node> Nodes;
Nodes _nodes;
float _startTime = 0.f;
int _startScreenTag = 0;
int _currentTag = 0;
float _startTagTime = 0.f;
MinigameManager *_runtime;
};
} // namespace QDEngine
#endif // QDENGINE_MINIGAMES_ADV_M_KARAOKE_H

View File

@@ -0,0 +1,476 @@
/* 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/memstream.h"
#include "qdengine/qdengine.h"
#include "qdengine/minigames/adv/common.h"
#include "qdengine/minigames/adv/m_puzzle.h"
#include "qdengine/minigames/adv/RunTime.h"
#include "qdengine/minigames/adv/Rect.h"
#include "qdengine/minigames/adv/qdMath.h"
#include "qdengine/system/input/keyboard_input.h"
namespace QDEngine {
typedef Rect<float, mgVect2f> Rectf;
MinigameInterface *createMinigamePuzzle(MinigameManager *runtime) {
return new Puzzle(runtime);
}
enum {
EVENT_GET,
EVENT_PUT,
EVENT_SWAP,
EVENT_ROTATE_IN_FIELD,
EVENT_RETURN,
EVENT_PUT_RIGHT,
EVENT_CLICK_RIGHT,
EVENT_CLICK,
EVENT_ROTATE_IN_STACK,
EVENT_FIELD_ROTATE
};
const char *Puzzle::getStateName(int angle, bool selected, bool small) const {
static const char *small_pref = "inv_";
static const char *selected_suf = "_sel";
static char buf[32];
buf[31] = 0;
assert(angle >= 0 && angle < _angles);
angle = (angle + _globalAngle) % _angles;
snprintf(buf, 31, "%s%02d%s", !_singleSize && small ? small_pref : "", angle + 1, selected ? selected_suf : "");
return buf;
}
Puzzle::Puzzle(MinigameManager *runtime) {
_runtime = runtime;
if (!_runtime->getParameter("game_size", _gameSize, true))
return;
assert(_gameSize > 0 && _gameSize < 100);
_field.resize(_gameSize, -1);
_globalAngle = 0;
_singleSize = _runtime->getParameter("small_objects", false);
_angles = _runtime->getParameter("angles", 4);
assert(_angles > 0 && _angles < 10);
if (!(_stackBottom = _runtime->getObject(_runtime->parameter("inventory_bottom"))))
return;
if (!_runtime->getParameter("inventory_size", _stackSize, true))
return;
if (_runtime->getParameter("rotate_period", _rotateTimePeriod, false)) {
assert(sqr(sqrt((float)_gameSize)) == _gameSize);
if (sqr(sqrt((float)_gameSize)) != _gameSize)
return;
} else
_rotateTimePeriod = 86400; // сутки
_nextRotateTime = _runtime->getTime() + _rotateTimePeriod;
_flySpeed = _runtime->getParameter("inventory_drop_speed", 240.f);
assert(_flySpeed > 0.f);
_returnSpeed = _runtime->getParameter("inventory_return_speed", -1.f);
const char *name_begin = _runtime->parameter("obj_name_begin", "obj_");
char buf[128];
buf[127] = 0;
Common::MemoryReadWriteStream gameData(DisposeAfterUse::YES);
for (int idx = 0; idx < _gameSize; ++idx) {
snprintf(buf, 127, "%s%02d", name_begin, idx + 1);
Node node;
node.obj = _runtime->getObject(buf);
if (_runtime->debugMode()) {
node.pos = _nodes.size();
node.angle = 0;
_field[node.pos] = node.pos;
} else
node.angle = _runtime->rnd(0, _angles - 1);
node.obj.setState(getStateName(node.angle, false, true));
node.obj->R().write(gameData);
_nodes.push_back(node);
}
if (!_runtime->processGameData(gameData))
return;
GameInfo *gameInfo = _runtime->getCurrentGameInfo();
if (gameInfo) {
Common::MemoryReadStream data((byte *)gameInfo->_gameData, gameInfo->_dataSize);
for (int idx = 0; idx < _gameSize; ++idx) {
mgVect3f crd;
crd.read(data);
_nodes[idx].obj->set_R(crd);
_positions.push_back(crd);
}
} else {
for (int idx = 0; idx < _gameSize; ++idx) {
mgVect3f crd;
crd.read(gameData);
_nodes[idx].obj->set_R(crd);
_positions.push_back(crd);
}
}
if (_runtime->debugMode())
_nodes[0].angle = _angles - 1;
_size = _runtime->getSize(_nodes[0].obj);
debugC(2, kDebugMinigames, "size = (%6.2f,%6.2f)", _size.x, _size.y);
_depth = _nodes[0].obj.depth(runtime);
_stackPlaceSize = _runtime->getParameter("inventory_place_size", _size * 1.2f);
assert(_stackPlaceSize.x > 0.f && _stackPlaceSize.x < 500.f && _stackPlaceSize.y > 0.f && _stackPlaceSize.y < 500.f);
debugC(2, kDebugMinigames, "stackPlaceSize = (%5.1f, %5.1f)", _stackPlaceSize.x, _stackPlaceSize.y);
_prevPlace = -1;
_pickedItem = -1;
_mouseObjPose = stidx(_stackSize + 1);
_inField = _runtime->debugMode() ? _nodes.size() : 0;
_nextObjTime = _runtime->getTime();
setState(MinigameInterface::RUNNING);
}
Puzzle::~Puzzle() {
for (auto &it : _nodes)
_runtime->release(it.obj);
_runtime->release(_stackBottom);
}
void Puzzle::rotate(int item) {
assert(item >= 0 && item < (int)_nodes.size());
_nodes[item].angle = (_nodes[item].angle + 1) % _angles;
}
int Puzzle::stidx(int idx) const {
return -idx - 2;
}
bool Puzzle::testPlace(int item) const {
assert(item >= 0 && item < (int)_nodes.size());
return _nodes[item].pos == item && _nodes[item].angle == 0;
}
bool Puzzle::isFlying(int idx) const {
for (auto &it : _flyObjs)
if (it.data == idx)
return true;
return false;
}
bool Puzzle::isOnMouse(const Node& node) const {
if (node.pos == _mouseObjPose) {
return true;
}
return false;
}
void Puzzle::put(int where, int what, float flowSpeed) {
assert(where < (int)_field.size());
assert(what >= 0 && what < (int)_nodes.size());
Node& node = _nodes[what];
int start = node.pos;
if (flowSpeed > 0.f || isFlying(what)) {
FlyQDObject* flyObj = 0;
FlyQDObjects::iterator fit;
for (fit = _flyObjs.begin(); fit != _flyObjs.end(); fit++) {
if (fit->data == what)
break;
}
if (fit != _flyObjs.end()) // Этот фрагмент уже летит, просто поменять точку назначения
flyObj = fit;
else { // Добавляем новый летящий фрагмент
_flyObjs.push_back(FlyQDObject());
flyObj = &_flyObjs.back();
flyObj->data = what;
mgVect3f from = isOnMouse(node) ? node.obj->R() : start < -1 ? stackPosition(stidx(start)) : position(start);
flyObj->current = _runtime->world2game(from);
node.obj->set_R(from);
flyObj->speed = flowSpeed;
}
mgVect3f to = where < -1 ? stackPosition(stidx(where)) : position(where);
flyObj->target = _runtime->world2game(to);
flyObj->depth = _runtime->getDepth(to);
}
if (where >= 0)
_field[where] = what;
node.pos = where;
}
void Puzzle::putOnStack(int what, float speed) {
put(stidx((int)_stack.size()), what, speed);
_stack.push_back(what);
}
void Puzzle::returnToStack() {
assert(_pickedItem != -1);
_runtime->event(EVENT_RETURN, _runtime->mousePosition());
if (_prevPlace >= 0)
put(_prevPlace, _pickedItem);
else
putOnStack(_pickedItem, _returnSpeed);
_prevPlace = -1;
_pickedItem = -1;
_runtime->event(EVENT_CLICK, _runtime->mousePosition());
}
void Puzzle::quant(float dt) {
if (_pickedItem == -1)
_runtime->setGameHelpVariant(0);
else
_runtime->setGameHelpVariant(1);
if (_runtime->getTime() > _nextRotateTime) {
_runtime->event(EVENT_FIELD_ROTATE, mgVect2f(400, 300));
_nextRotateTime = _runtime->getTime() + _rotateTimePeriod;
_globalAngle = (_globalAngle + 1) % _angles;
_runtime->setCompleteHelpVariant(_globalAngle);
}
FlyQDObjects::iterator fit = _flyObjs.begin();
while (fit != _flyObjs.end())
if (!isOnMouse(_nodes[fit->data]) && fit->quant(dt, _nodes[fit->data].obj, _runtime))
++fit;
else
fit = _flyObjs.erase(fit);
if (_inField < (int)_nodes.size() && _runtime->getTime() > _nextObjTime &&
((int)_stack.size() < _stackSize - 1 || ((int)_stack.size() < _stackSize && _pickedItem == -1))) { // нужно добавить в инвентори фишку
// ищем случайный не выставленный фрагмент
int freeIdx = round(_runtime->rnd(0.f, _nodes.size() - 1));
Nodes::iterator it = _nodes.begin();
for (;;) {
if (++it == _nodes.end())
it = _nodes.begin();
if (it->isFree())
if (!freeIdx--)
break;
}
int idx = Common::distance(_nodes.begin(), it);
++_inField;
_nextObjTime = _runtime->getTime() + _stackPlaceSize.y / _flySpeed;
it->pos = stidx(_stackSize);
it->obj.setState(getStateName(it->angle, false, true));
putOnStack(idx, _flySpeed);
}
mgVect2f mouse = _runtime->mousePosition();
int hovPlace = -1; // Номер места которое сейчас под мышкой
for (int idx = 0; idx < (int)_stack.size(); ++idx)
if (_nodes[_stack[idx]].obj.hit(mouse)) {
hovPlace = stidx(idx);
break;
}
if (hovPlace == -1) {
float radius = 0.5f * _size.x;
for (int idx = 0; idx < _gameSize; ++idx)
if (dist(_runtime->world2game(position(idx)), mouse) < radius) {
hovPlace = idx;
break;
}
}
if (hovPlace == -1) {
mgVect2i st = _stackBottom->screen_R();
st.y -= _stackPlaceSize.y * _stackSize - 0.5f * _stackPlaceSize.x;
Rectf stackPos(st.x - 0.5f * _stackPlaceSize.x, st.y, _stackPlaceSize.x, _stackPlaceSize.y * _stackSize);
if (stackPos.point_inside(mouse))
hovPlace = stidx(_stackSize);
}
if (_runtime->mouseLeftPressed()) {
if (hovPlace >= 0) { // клик по полю
Indexes::value_type& hovItem = _field[hovPlace];
if (hovItem == -1) // клик по пустой ячейке
if (_pickedItem == -1) // на мыши ничего нет
_runtime->event(EVENT_CLICK, mouse);
else { // кладем фрагмент с мыши
put(hovPlace, _pickedItem);
if (testPlace(_pickedItem)) // положили на свое свое место
_runtime->event(EVENT_PUT_RIGHT, mouse);
else // просто положили
_runtime->event(EVENT_PUT, mouse);
_pickedItem = -1;
_prevPlace = -1;
} else { // клик по непустой ячейке
if (testPlace(hovPlace)) // клик по правильно уложенной фишке
_runtime->event(EVENT_CLICK_RIGHT, mouse);
else if (_pickedItem != -1) { // поменять с тем что на мыше
bool swap = true;
if (_prevPlace >= 0)
put(_prevPlace, hovItem);
else
putOnStack(hovItem, _returnSpeed);
if (testPlace(hovItem)) { // оказалась при обмене на своем месте
_runtime->event(EVENT_PUT_RIGHT, _runtime->world2game(position(_prevPlace)));
swap = false;
}
put(hovPlace, _pickedItem);
if (testPlace(_pickedItem)) { // положили на свое свое место
_runtime->event(EVENT_PUT_RIGHT, mouse);
swap = false;
}
if (swap) // просто обменяли
_runtime->event(EVENT_SWAP, mouse);
_pickedItem = -1;
_prevPlace = -1;
} else { // взять фрагмент на мышь
_runtime->event(EVENT_GET, mouse);
_prevPlace = hovPlace;
_pickedItem = hovItem;
_nodes[_pickedItem].pos = _mouseObjPose;
hovItem = -1;
}
}
} else if (hovPlace < -1) { // клик по стеку
int hovStack = stidx(hovPlace);
if (_pickedItem == -1) // на мыши ничего нет
if (hovStack < (int)_stack.size()) { // взять фрагмент из стека на мышь
_runtime->event(EVENT_GET, mouse);
Indexes::iterator it = _stack.begin() + hovStack;
assert(*it >= 0);
_prevPlace = -1;
_pickedItem = *it;
_nodes[_pickedItem].pos = _mouseObjPose;
_stack.erase(it);
for (int idx = hovStack; idx < (int)_stack.size(); ++idx)
put(stidx(idx), _stack[idx], _flySpeed);
} else // пустой клик в области стека
_runtime->event(EVENT_CLICK, mouse);
else // вернуть фишку на место
returnToStack();
} else // пустой клик мимо игрового поля
_runtime->event(EVENT_CLICK, mouse);
} else if (_runtime->mouseRightPressed()) {
if (_pickedItem == -1) {
if (hovPlace >= 0) { // клик по полю
if (testPlace(hovPlace)) // клик по правильно уложенной фишке
_runtime->event(EVENT_CLICK_RIGHT, mouse);
else {
Indexes::value_type& hovItem = _field[hovPlace];
if (hovItem >= 0) {
rotate(hovItem);
if (testPlace(hovItem)) // повернули на правильный угол
_runtime->event(EVENT_PUT_RIGHT, mouse);
else // просто положили
_runtime->event(EVENT_ROTATE_IN_FIELD, mouse);
} else // попытка прокрутить пустое место
_runtime->event(EVENT_CLICK, mouse);
}
} else if (hovPlace < -1) { // клик по стеку
int hovStack = stidx(hovPlace);
if (hovStack < (int)_stack.size()) { // покрутить внутри стека
_runtime->event(EVENT_ROTATE_IN_STACK, mouse);
rotate(_stack[hovStack]);
} else // попытка прокрутить пустое место
_runtime->event(EVENT_CLICK, mouse);
} else // пустой клик мимо игрового поля
_runtime->event(EVENT_CLICK, mouse);
} else // вернуть фишку на место
returnToStack();
}
bool iWin = true;
for (int idx = 0; idx < (int)_nodes.size(); ++idx) {
Node& node = _nodes[idx];
if (node.pos != -1) {
if (node.pos >= 0) {
if (isFlying(idx))
node.obj.setState(getStateName(node.angle, false, false));
else {
node.obj.setState(getStateName(node.angle, node.pos == hovPlace && !testPlace(idx), false));
node.obj->set_R(position(node.pos));
}
} else if (idx == _pickedItem) {
node.obj.setState(getStateName(node.angle, hovPlace >= 0 && !testPlace(hovPlace), false));
node.obj->set_R(_runtime->game2world(mouse, _stackBottom.depth(_runtime) - 200));
} else {
node.obj.setState(getStateName(node.angle, node.pos == hovPlace && _pickedItem == -1, true));
if (!isFlying(idx))
node.obj->set_R(stackPosition(stidx(node.pos)));
}
iWin = iWin && testPlace(idx);
} else {
_runtime->hide(node.obj);
iWin = false;
}
}
if (iWin)
setState(GAME_WIN);
}
const mgVect3f &Puzzle::position(int num) const {
assert(num >= 0 && num < (int)_positions.size());
// Если глобальный поворот ненулевой, пересчитываем индекс
if (_globalAngle > 0) {
int size = sqrt((float)_gameSize);
int y = num / size;
int x = num - y * size;
--size;
for (int angle = 0; angle < _globalAngle; ++angle) {
int tmp = x;
x = size - y;
y = tmp;
}
num = y * (size + 1) + x;
}
assert(num >= 0 && num < (int)_positions.size());
return _positions[num];
}
mgVect3f Puzzle::stackPosition(int num) const {
mgVect3f bottom = _runtime->world2game(_stackBottom);
bottom.y -= _stackPlaceSize.y * num;
return _runtime->game2world(bottom);
}
} // namespace QDEngine

View File

@@ -0,0 +1,121 @@
/* 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 QDENGINE_MINIGAMES_ADV_M_PUZZLE_H
#define QDENGINE_MINIGAMES_ADV_M_PUZZLE_H
#include "qdengine/minigames/adv/MinigameInterface.h"
#include "qdengine/minigames/adv/FlyObject.h"
namespace QDEngine {
MinigameInterface *createMinigamePuzzle(MinigameManager *runtime);
class Puzzle : public MinigameInterface {
struct Node {
QDObject obj;
int angle;
int pos;
bool inStack() const {
return pos < -1;
}
bool isFree() const {
return pos == -1;
}
Node() : angle(1), pos(-1) {}
};
typedef Std::vector<Node> Nodes;
public:
Puzzle(MinigameManager *runtime);
~Puzzle();
void quant(float dt);
private:
int _gameSize = 0;
int _angles = 0;
int _globalAngle = 0;
float _rotateTimePeriod = 0.f;
float _nextRotateTime = 0.f;
bool _singleSize = false;
mgVect2f _size;
float _depth = 0.f;
Nodes _nodes;
/// Номер места с которого взяли фрагмент
int _prevPlace = -1;
/// Индекс фрагмента на мыши
int _pickedItem = -1;
int _inField = 0; // количество фрагментов на поле
float _nextObjTime = 0.f;
int _mouseObjPose = -1; // индекс положения фрагмента на мыши в стеке
QDObject _stackBottom;
int _stackSize = 0;
mgVect2f _stackPlaceSize;
Indexes _stack;
Indexes _field;
FlyQDObjects _flyObjs;
/// скорость падения новых в стек
float _flySpeed = 0.f;
/// скорость возврата в стек
float _returnSpeed = -1.f;
Coords _positions;
const char *getStateName(int angle, bool selected, bool small) const;
/// повернуть фишку
void rotate(int hovItem);
/// проверить нахождение фишки на своем месте
bool testPlace(int idx) const;
/// фишка на мыши?
bool isOnMouse(const Node& node) const;
/// проверить фишку на предмет самостоятельного управления позиционированием
bool isFlying(int idx) const;
/// конверсия между номером в стеке и индексом положения
int stidx(int idx) const;
/// положить what в ячейку where
void put(int where, int what, float flowSpeed = -1.f);
/// положить на вершину инвентори
void putOnStack(int what, float speed);
/// вернуть с мыши в инвентори
void returnToStack();
/// мировые координаты слота на поле
const mgVect3f &position(int num) const;
/// положение N-ой фишки в инвентори
mgVect3f stackPosition(int N) const;
MinigameManager *_runtime;
};
} // namespace QDEngine
#endif // QDENGINE_MINIGAMES_ADV_M_PUZZLE_H

View File

@@ -0,0 +1,245 @@
/* 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/file.h"
#include "common/memstream.h"
#include "qdengine/qdengine.h"
#include "qdengine/minigames/adv/common.h"
#include "qdengine/minigames/adv/m_scores.h"
namespace QDEngine {
MinigameInterface *createMinigameScores(MinigameManager *runtime) {
return new Scores(runtime);
}
Scores::Scores(MinigameManager *runtime) {
_runtime = runtime;
const char *fileName = _runtime->parameter("minigame_list");
if (!fileName || !*fileName)
return;
if (!scumm_stricmp(fileName, _runtime->gameListFileName())) {
error("[minigame_list] must refer to \"%s\"", transCyrillic(_runtime->gameListFileName()));
}
const char *gameButtonName = _runtime->parameter("game_miniature_button");
if (!gameButtonName || !*gameButtonName)
return;
Common::MemoryReadWriteStream gameData(DisposeAfterUse::YES);
char name[128];
name[127] = 0;
for (int num = 1; ; ++num) {
snprintf(name, 127, "%s%02d", gameButtonName, num);
if (_runtime->testObject(name)) {
QDObject obj = _runtime->getObject(name);
obj->R().write(gameData);
_games.push_back(_runtime->getObject(name));
} else
break;
}
if (_games.empty()) {
error("Game images not found '%s'", transCyrillic(gameButtonName));
}
if (!_runtime->processGameData(gameData))
return;
_positions.resize(_games.size());
GameInfo *gameInfo = _runtime->getCurrentGameInfo();
if (gameInfo) {
Common::MemoryReadStream buf((byte *)gameInfo->_gameData, gameInfo->_dataSize);
for (auto &it : _positions)
it.read(buf);
} else {
for (auto &it : _positions)
it.read(gameData);
}
Common::File file;
if (!file.open(Common::Path(_runtime->gameListFileName()))) {
error("Failed to open games list file '%s'", transCyrillic(fileName));
}
char read_buf[512];
while (!file.eos()) {
file.readLine(read_buf, 512);
Common::MemoryReadStream buf((const byte *)&read_buf[0], strlen(read_buf));
int level = buf.readByte() - '0';
byte ch = buf.readByte();
if (ch != ':') {
error("Wrong file format");
}
Level lvl(level);
debugCN(2, kDebugMinigames, "%d: ", level);
while (buf.pos() < buf.size()) {
ch = buf.readByte();
if (Common::isDigit(ch)) {
int game = ch - '0';
lvl.games.push_back(game);
debugCN(2, kDebugMinigames, "%d, ", game);
if (const MinigameData *data = _runtime->getScore(level, game)) {
if (data->_sequenceIndex != -1)
lvl.data.push_back(GameData(game, *data));
}
}
}
if (lvl.games.size() > _games.size()) {
error("Not enough game images");
}
Common::sort(lvl.data.begin(), lvl.data.end());
levels_.push_back(lvl);
debugC(2, kDebugMinigames, "%s", "");
}
if (levels_.empty())
return;
Common::sort(levels_.begin(), levels_.end());
_level = 0;
_preLevel = -1;
if (!(_bestScore = _runtime->parameter("best_score")))
return;
if (!(_bestTime = _runtime->parameter("best_time")))
return;
if (!(_lastScore = _runtime->parameter("last_score")))
return;
if (!(_lastTime = _runtime->parameter("last_time")))
return;
if (!(_timeFormat = _runtime->parameter("time_format")))
return;
if (!(_currentLevel = _runtime->parameter("current_level")))
return;
if (!(_prev = _runtime->getObject(_runtime->parameter("prev_button"))))
return;
if (!(_next = _runtime->getObject(_runtime->parameter("next_button"))))
return;
if (!(_gameBorder = _runtime->getObject(_runtime->parameter("game_border"))))
return;
_outMaxLevel = _runtime->getObject(_runtime->parameter("for_game_level"));
if (_outMaxLevel) {
uint level = 0;
for (; level < levels_.size(); ++level)
if (levels_[level].data.size() < levels_[level].games.size())
break;
if (level < levels_.size())
_outMaxLevel.setState(Common::String::format("%d", levels_[level].level).c_str());
else
_outMaxLevel.setState("all");
}
setState(MinigameInterface::RUNNING);
}
Scores::~Scores() {
_runtime->release(_prev);
_runtime->release(_next);
for (auto &it : _games)
_runtime->release(it);
}
void Scores::quant(float dt) {
assert(_level >= 0 && _level < (int)levels_.size());
const Level& lvl = levels_[_level];
char timeBuf[32];
if (_level != _preLevel) {
_preLevel = _level;
_runtime->setText(_currentLevel, lvl.level);
for (int idx = 0; idx < (int)_games.size(); ++idx)
_runtime->hide(_games[idx]);
for (int idx = 0; idx < (int)_games.size(); ++idx) {
if (idx < (int)lvl.data.size()) {
const GameData& data = lvl.data[idx];
int gameId = data.num;
int gameNum;
for (gameNum = 0; gameNum < (int)lvl.games.size(); ++gameNum)
if (gameId == lvl.games[gameNum])
break;
assert(gameNum < (int)lvl.games.size());
assert(gameNum < (int)_games.size());
_games[gameNum].setState(Common::String::format("%d", _level).c_str());
_games[gameNum]->set_R(_positions[idx]);
_runtime->setText(getName(_bestScore, idx), data.info._bestScore);
if (!_timeFormat || !*_timeFormat)
snprintf(timeBuf, 31, "%d", data.info._bestTime);
else
snprintf(timeBuf, 31, _timeFormat, data.info._bestTime / 60, data.info._bestTime % 60);
_runtime->setText(getName(_bestTime, idx), timeBuf);
_runtime->setText(getName(_lastScore, idx), data.info._lastScore);
if (!_timeFormat || !*_timeFormat)
snprintf(timeBuf, 31, "%d", data.info._lastTime);
else
snprintf(timeBuf, 31, _timeFormat, data.info._lastTime / 60, data.info._lastTime % 60);
_runtime->setText(getName(_lastTime, idx), timeBuf);
} else {
_runtime->setText(getName(_bestScore, idx), "");
_runtime->setText(getName(_bestTime, idx), "");
_runtime->setText(getName(_lastScore, idx), "");
_runtime->setText(getName(_lastTime, idx), "");
}
}
}
_runtime->hide(_gameBorder);
for (int idx = 0; idx < (int)_games.size(); ++idx) {
if (_games[idx].hit(_runtime->mousePosition())) {
_gameBorder->set_R(_runtime->game2world(_runtime->world2game(_games[idx])));
break;
}
}
if (_runtime->mouseLeftPressed()) {
if (_level < (int)levels_.size() - 1 && lvl.data.size() == lvl.games.size() && _next.hit(_runtime->mousePosition()))
++_level;
else if (_level > 0 && _prev.hit(_runtime->mousePosition()))
--_level;
}
}
const char *Scores::getName(const char *begin, int idx) const {
static char buf[32];
buf[31] = 0;
snprintf(buf, 31, "%s%02d", begin, idx + 1);
return buf;
}
} // namespace QDEngine

View File

@@ -0,0 +1,88 @@
/* 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 QDENGINE_MINIGAMES_ADV_M_SCORES_H
#define QDENGINE_MINIGAMES_ADV_M_SCORES_H
#include "qdengine/minigames/adv/MinigameInterface.h"
#include "qdengine/minigames/adv/RunTime.h"
namespace QDEngine {
MinigameInterface *createMinigameScores(MinigameManager *runtime);
class Scores : public MinigameInterface {
public:
Scores(MinigameManager *runtime);
~Scores();
void quant(float dt);
private:
struct GameData {
GameData(int gameNum, const MinigameData& inf) : num(gameNum), info(inf) {}
int num;
MinigameData info;
bool operator< (const GameData& rsh) const {
return info._sequenceIndex < rsh.info._sequenceIndex;
}
};
typedef Std::vector<GameData> GameDatas;
struct Level {
Level(int lvl = 0) : level(lvl) {}
int level;
Indexes games;
GameDatas data;
bool operator< (const Level& rsh) const {
return level < rsh.level;
}
};
typedef Std::vector<Level> Levels;
Levels levels_;
const char *_currentLevel = nullptr;
const char *_bestScore = nullptr;
const char *_bestTime = nullptr;
const char *_lastScore = nullptr;
const char *_lastTime = nullptr;
const char *_timeFormat = nullptr;
QDObject _prev;
QDObject _next;
QDObject _gameBorder;
QDObject _outMaxLevel;
QDObjects _games;
int _preLevel = -1;
int _level = -1;
Coords _positions;
const char *getName(const char *begin, int idx) const;
MinigameManager *_runtime;
};
} // namespace QDEngine
#endif // QDENGINE_MINIGAMES_ADV_M_SCORES_H

View File

@@ -0,0 +1,301 @@
/* 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/memstream.h"
#include "qdengine/qdengine.h"
#include "qdengine/minigames/adv/common.h"
#include "qdengine/minigames/adv/m_swap.h"
#include "qdengine/minigames/adv/RunTime.h"
#include "qdengine/minigames/adv/Rect.h"
namespace QDEngine {
typedef Rect<float, mgVect2f> Rectf;
MinigameInterface *createMinigameSwap(MinigameManager *runtime) {
return new Swap(runtime);
}
enum {
EVENT_GET,
EVENT_SWAP,
EVENT_ROTATE,
EVENT_RETURN,
EVENT_PUT_RIGHT,
EVENT_GET_RIGHT,
EVENT_CLICK,
EVENT_AUTO_ROTATE
};
const char *Swap::getStateName(int angle, bool selected) const {
static const char *selected_suf = "_sel";
static char buf[32];
buf[31] = 0;
assert(angle >= 0 && angle < _angles);
snprintf(buf, 31, "%02d%s", angle + 1, selected ? selected_suf : "");
return buf;
}
Swap::Swap(MinigameManager *runtime) {
_runtime = runtime;
if (!_runtime->getParameter("game_size", _gameSize, true) || _gameSize < 2)
return;
if ((_angles = _runtime->getParameter("angles", 4)) < 1)
return;
if ((_rotateTimePeriod = _runtime->getParameter("rotate_period", 86400.f)) < 10.f)
return;
_nextRotateTime = _runtime->getTime() + _rotateTimePeriod;
const char *name_begin = _runtime->parameter("obj_name_begin", "obj_");
Common::MemoryReadWriteStream gameData(DisposeAfterUse::YES);
for (int idx = 0; idx < _gameSize; ++idx) {
Common::String buf = Common::String::format("%s%02d", name_begin, idx + 1);
Node node(idx);
node.obj = _runtime->getObject(buf.c_str());
node.angle = 0;
node.obj.setState(getStateName(node.angle, false));
_nodes.push_back(node);
node.obj->R().write(gameData);
}
if (!_runtime->processGameData(gameData))
return;
_positions.resize(_gameSize);
GameInfo *gameInfo = _runtime->getCurrentGameInfo();
if (gameInfo) {
Common::MemoryReadStream buf((byte *)gameInfo->_gameData, gameInfo->_dataSize);
for (auto &it : _positions)
it.read(buf);
} else {
for (auto &it : _positions)
it.read(gameData);
}
_size = _runtime->getParameter("element_size", _runtime->getSize(_nodes[0].obj));
assert(_size.x > 0.f && _size.y > 0.f && _size.x < 500.f && _size.y < 500.f);
debugC(2, kDebugMinigames, "element_size = (%6.2f,%6.2f)", _size.x, _size.y);
_pickedItem = -1;
_last1 = _last2 = -1;
if (_runtime->debugMode()) {
_last1 = 0;
_last2 = 1;
rotate(_last1, _last2, false);
} else
for (int cnt = 0; cnt < 50; ++cnt) {
rotate(_runtime->rnd(0, _gameSize - 1), _runtime->rnd(0, _gameSize - 1), true, true);
swap(_runtime->rnd(0, _gameSize - 1), _runtime->rnd(0, _gameSize - 1), true);
}
setState(MinigameInterface::RUNNING);
}
Swap::~Swap() {
for (auto &it : _nodes)
_runtime->release(it.obj);
}
void Swap::quant(float dt) {
if (_pickedItem >= 0)
_runtime->setGameHelpVariant(1);
else if (_last1 >= 0)
_runtime->setGameHelpVariant(2);
else
_runtime->setGameHelpVariant(0);
if (_runtime->getTime() > _nextRotateTime) {
int item1 = _runtime->rnd(0, _gameSize - 1);
int item2 = _runtime->rnd(0, _gameSize - 1);
if (item1 != _last1 && item1 != _last2 && item1 != _pickedItem && item2 != _last1 && item2 != _last2 && item2 != _pickedItem) {
_nextRotateTime = _runtime->getTime() + _rotateTimePeriod;
rotate(item1, item2, false, true);
_runtime->event(EVENT_AUTO_ROTATE, mgVect2f(400, 300));
return;
}
}
mgVect2f mouse = _runtime->mousePosition();
int hovPlace = -1; // Номер места которое сейчас под мышкой
if (_pickedItem == -1) {
for (auto &it : _nodes)
if (it.obj.hit(mouse)) {
hovPlace = Common::distance(_nodes.begin(), &it);
break;
}
}
if (hovPlace == -1)
for (int idx = 0; idx < _gameSize; ++idx) {
Rectf rect(_size * 0.9f);
rect.center(_runtime->world2game(position(idx)));
if (rect.point_inside(mouse)) {
hovPlace = idx;
break;
}
}
if (_runtime->mouseLeftPressed()) {
if (hovPlace >= 0) { // клик по полю
if (_pickedItem == -1) { // мышь пустая, берем
deactivate();
_runtime->event(EVENT_GET, mouse);
_pickedItem = hovPlace;
} else if (_pickedItem == hovPlace) { // вернуть на место
_runtime->event(EVENT_RETURN, mouse);
put(_pickedItem, false);
_pickedItem = -1;
} else { // поменять местами
_last1 = _pickedItem;
_last2 = hovPlace;
swap(_last1, _last2, false);
_pickedItem = -1;
}
} else { // пустой клик мимо игрового поля
deactivate();
_runtime->event(EVENT_CLICK, mouse);
}
} else if (_runtime->mouseRightPressed()) {
if (_pickedItem >= 0) // если на мыши фрагмент ничего не делаем
_runtime->event(EVENT_CLICK, mouse);
else if (hovPlace == _last1 || hovPlace == _last2) // клик по выделенным
rotate(_last1, _last2, false);
else // пустой клик мимо активного места
_runtime->event(EVENT_CLICK, mouse);
}
if (_pickedItem >= 0)
_nodes[_pickedItem].obj->set_R(_runtime->game2world(mouse, -5000));
int idx = 0;
for (; idx < _gameSize; ++idx)
if (!testPlace(idx))
break;
if (idx == (int)_nodes.size()) {
deactivate();
setState(MinigameInterface::GAME_WIN);
}
}
const mgVect3f &Swap::position(int num) const {
assert(num >= 0 && num < (int)_positions.size());
return _positions[num];
}
void Swap::put(int item, bool hl) {
assert(item >= 0 && item < (int)_nodes.size());
_nodes[item].obj->set_R(position(item));
_nodes[item].obj.setState(getStateName(_nodes[item].angle, hl));
}
void Swap::deactivate() {
if (_last1 >= 0) {
assert(_last2 >= 0);
put(_last1, false);
put(_last2, false);
}
_last1 = -1;
_last2 = -1;
}
bool Swap::testPlace(int item) const {
assert(item >= 0 && item < (int)_nodes.size());
return _nodes[item].home == item && _nodes[item].angle == 0;
}
void Swap::swap(int item1, int item2, bool silent) {
assert(item1 >= 0 && item1 < (int)_nodes.size());
assert(item2 >= 0 && item2 < (int)_nodes.size());
bool res = false;
if (!silent) {
if (testPlace(item1)) { // сняли со своего места
_runtime->event(EVENT_GET_RIGHT, _runtime->world2game(position(item1)));
res = true;
}
if (testPlace(item2)) { // сняли со своего места
_runtime->event(EVENT_GET_RIGHT, _runtime->world2game(position(item2)));
res = true;
}
}
SWAP(_nodes[item1], _nodes[item2]);
put(item1, !silent);
put(item2, !silent);
if (!silent) {
if (testPlace(item1)) { // оказалась при обмене на своем месте
_runtime->event(EVENT_PUT_RIGHT, _runtime->world2game(position(item1)));
res = true;
}
if (testPlace(item2)) { // положили на свое свое место
_runtime->event(EVENT_PUT_RIGHT, _runtime->world2game(position(item2)));
res = true;
}
if (!res) // просто обменяли
_runtime->event(EVENT_SWAP, _runtime->mousePosition());
}
}
void Swap::rotate(int item1, int item2, bool silent, bool avto) {
assert(item1 >= 0 && item1 < (int)_nodes.size());
assert(item2 >= 0 && item2 < (int)_nodes.size());
if (!silent) {
if (testPlace(item1)) // сняли со своего места
_runtime->event(EVENT_GET_RIGHT, _runtime->world2game(position(item1)));
if (testPlace(item2)) // сняли со своего места
_runtime->event(EVENT_GET_RIGHT, _runtime->world2game(position(item2)));
}
_nodes[item1].angle = (_nodes[item1].angle + 1) % _angles;
_nodes[item2].angle = (_nodes[item2].angle + 1) % _angles;
put(item1, !avto);
put(item2, !avto);
if (!silent) {
if (testPlace(item1)) // оказалась при обмене на своем месте
_runtime->event(EVENT_PUT_RIGHT, _runtime->world2game(position(item1)));
if (testPlace(item2)) // положили на свое свое место
_runtime->event(EVENT_PUT_RIGHT, _runtime->world2game(position(item2)));
_runtime->event(EVENT_ROTATE, _runtime->mousePosition());
}
}
} // namespace QDEngine

View File

@@ -0,0 +1,81 @@
/* 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 QDENGINE_MINIGAMES_ADV_M_SWAP_H
#define QDENGINE_MINIGAMES_ADV_M_SWAP_H
#include "qdengine/minigames/adv/MinigameInterface.h"
namespace QDEngine {
MinigameInterface *createMinigameSwap(MinigameManager *runtime);
class Swap : public MinigameInterface {
public:
Swap(MinigameManager *runtime);
~Swap();
void quant(float dt);
private:
int _gameSize = 0;
int _angles = 0;
float _rotateTimePeriod = 0.5f; // время поворота фишки
float _nextRotateTime = 0.f; // время следующего поворота
mgVect2f _size;
struct Node {
Node(int idx = -1) : home(idx), angle(0) {}
QDObject obj;
int angle;
int home;
};
typedef Std::vector<Node> Nodes;
Nodes _nodes;
// Индекс фрагмента на мыши
int _pickedItem = -1;
// активные фрагменты после обмена
int _last1 = 0, _last2 = 0;
Coords _positions;
const char *getStateName(int angle, bool selected) const;
// поменять местами, если было снятие или укладка на/с правильного места, то true
void swap(int item1, int item2, bool silent);
// повернуть фишку
void rotate(int item1, int item2, bool silent, bool avto = false);
// погасить выделенные
void deactivate();
// проверить нахождение фишки на своем месте
bool testPlace(int idx) const;
// поставить объект на свое место и включить нужное состояние
void put(int idx, bool hl);
// мировые координаты слота на поле
const mgVect3f &position(int num) const;
MinigameManager *_runtime;
};
} // namespace QDEngine
#endif // QDENGINE_MINIGAMES_ADV_M_SWAP_H

View File

@@ -0,0 +1,639 @@
/* 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/memstream.h"
#include "qdengine/qdengine.h"
#include "qdengine/minigames/adv/m_triangles.h"
#include "qdengine/minigames/adv/RunTime.h"
#include "qdengine/minigames/adv/EventManager.h"
#include "qdengine/minigames/adv/qdMath.h"
namespace QDEngine {
MinigameInterface *createMinigameTriangle(MinigameManager *runtime) {
return new MinigameTriangle(runtime);
}
enum {
EVENT_TURN,
EVENT_GET_RIGHT,
EVENT_PUT_RIGHT
};
MinigameTriangle::Node::Node(int number, int rot) {
_number = number;
_rotation = rot;
_isBack = false;
_highlight = false;
_animated = false;
_flip = 0;
}
void MinigameTriangle::Node::release(MinigameManager *runtime) {
for (auto &it : _face)
runtime->release(it);
}
bool MinigameTriangle::Node::hit(const mgVect2f &pos) const {
return obj().hit(pos);
}
MinigameTriangle::MinigameTriangle(MinigameManager *runtime) {
_runtime = runtime;
int type = 0;
if (!_runtime->getParameter("game_type", type, true))
return;
switch (type) {
case 1:
_gameType = RECTANGLE;
break;
case 2:
_gameType = HEXAGON;
break;
default:
_gameType = TRIANGLE;
}
_fieldLines = _fieldWidth = 0;
if (!_runtime->getParameter("size", _fieldLines, true))
return;
if (_fieldLines < 2)
return;
if (_gameType == RECTANGLE) {
if (!_runtime->getParameter("width", _fieldWidth, true))
return;
if (_fieldWidth < 2)
return;
}
switch (_gameType) {
case TRIANGLE:
_fieldSize = sqr(_fieldLines);
break;
case RECTANGLE:
_fieldSize = _fieldLines * _fieldWidth;
break;
case HEXAGON:
assert(_fieldLines % 2 == 0);
if (_fieldLines % 2 != 0)
return;
_fieldSize = 3 * sqr(_fieldLines) / 2;
break;
}
if (!_runtime->getParameter("animation_time", _animationTime, true))
return;
_quickReselect = _runtime->getParameter("quick_reselect", false);
const char *faceNameBegin = _runtime->parameter("object_name_begin", "obj_");
const char *backNameBegin = _runtime->parameter("backg_name_begin", "element__back");
const char *selectNameBegin = _runtime->parameter("select_name_begin", "element_select_");
char name[64];
name[63] = 0;
for (int num = 0; num < _fieldSize; ++num) {
_nodes.push_back(Node(num, 0));
Node &node = _nodes.back();
for (int angle = 1; angle <= 3; ++angle) {
snprintf(name, 63, "%s%02d_%1d", faceNameBegin, num + 1, angle);
debugC(5, kDebugMinigames, "Loading face object: %s", name);
QDObject obj = _runtime->getObject(name);
node._face.push_back(obj);
_positions.push_back(obj->R());
}
}
Common::MemoryReadWriteStream gameData(DisposeAfterUse::YES);
for (auto &it : _positions)
it.write(gameData);
if (!_runtime->processGameData(gameData))
return;
GameInfo *gameInfo = _runtime->getCurrentGameInfo();
if (gameInfo) {
Common::MemoryReadStream buf((byte *)gameInfo->_gameData, gameInfo->_dataSize);
for (auto &it : _positions)
it.read(buf);
} else {
for (auto &it : _positions)
it.read(gameData);
}
for (int num = 1; num <= 2; ++num) {
for (int angle = 1; angle <= 3; ++angle) {
snprintf(name, 63, "%s%1d_%1d", backNameBegin, num, angle);
debugC(5, kDebugMinigames, "Loading back object: %s", name);
if (!_backSides[(num - 1) * 3 + angle - 1].load(name, _runtime))
return;
}
snprintf(name, 63, "%s%1d", selectNameBegin, num);
debugC(5, kDebugMinigames, "Loading select object: %s", name);
if (!_selectBorders[num - 1].load(name, _runtime))
return;
}
_selectDepth = _nodes[0]._face[0].depth(_runtime) - 1000;
_selected = -1;
_hovered = -1;
_animationState = NO_ANIMATION;
_animatedNodes[0] = _animatedNodes[1] = -1;
_animationTimer = 0.f;
if (!_runtime->debugMode())
for (int i = 0; i < 150; ++i) {
int pos1 = _runtime->rnd(0, _nodes.size() - 1);
for (int j = 0; j < 20; ++j) {
int pos2 = _runtime->rnd(pos1 - 10, pos1 + 10);
if (compatible(pos1, pos2)) {
swapNodes(pos1, pos2, true);
break;
}
}
}
for (int idx = 0; idx < _fieldSize; ++idx)
updateNode(_nodes[idx], idx);
setState(RUNNING);
}
MinigameTriangle::~MinigameTriangle() {
for (auto &it : _nodes)
it.release(_runtime);
for (int idx = 0; idx < 2; ++idx)
_selectBorders[idx].release(_runtime);
for (int idx = 0; idx < 6; ++idx)
_backSides[idx].release(_runtime);
}
void MinigameTriangle::Node::debugInfo() const {
debugC(5, kDebugMinigames, "name:\"%s\" state:\"%s\" number:%d rotation:%d flip:%d isBack:%d highlight:%d animated:%d", obj().getName(), obj()->current_state_name(), _number, _rotation, _flip, _isBack, _highlight, _animated);
}
const Common::String MinigameTriangle::Node::getFaceStateName(int angle, bool selected, bool animated, bool instantaneous) {
assert(!selected || !animated); // анимированные выделенными быть не могут
static const char *angleNames[3] = {"0", "120", "240"};
assert(angle >= 0 && angle < ARRAYSIZE(angleNames));
Common::String out;
out = Common::String::format("%s%s%s", (animated ? "02_" : "01_"), angleNames[angle], (selected || instantaneous ? "_sel" : ""));
return out;
}
const char *MinigameTriangle::Node::getBackStateName(bool selected, bool animated, bool instantaneous) {
assert(!selected || !animated); // анимированные выделенными быть не могут
if (animated)
return selected || instantaneous ? "02_sel" : "02";
else
return selected || instantaneous ? "01_sel" : "01";
}
const char *MinigameTriangle::Node::getBorderStateName(bool selected) {
return selected ? "01" : "02";
}
void MinigameTriangle::releaseNodeBack(Node &node) {
if (node._back) {
node._back.setState(Node::getBackStateName(false, false, false));
for (int type = 0; type < 6; ++type)
_backSides[type].releaseObject(node._back, _runtime);
}
}
void MinigameTriangle::updateNode(Node &node, int position, int flip, bool quick) {
for (auto &fit : node._face)
_runtime->hide(fit);
node._flip = flip;
if (node._isBack) {
if (!node._back)
node._back = _backSides[orientation(position) * 3 + flip].getObject();
node._back->set_R(slotCoord(position, flip));
node._back->update_screen_R();
node._back.setState(Node::getBackStateName(node._highlight, node._animated, quick));
} else {
releaseNodeBack(node);
QDObject &face = node._face[flip];
face->set_R(slotCoord(position, flip));
face->update_screen_R();
face.setState(Node::getFaceStateName(node._rotation, node._highlight, node._animated, quick).c_str());
}
}
void MinigameTriangle::highlight(int idx, bool hl) {
if (idx >= 0) {
assert(idx < (int)_nodes.size());
_nodes[idx]._highlight = hl;
updateNode(_nodes[idx], idx);
}
}
void MinigameTriangle::beginSwapNodes(int pos1, int pos2) {
assert(compatible(pos1, pos2));
if (pos1 > pos2)
SWAP(pos1, pos2);
_animationState = FIRST_PHASE;
_animationTimer = _animationTime;
_animatedNodes[0] = pos1;
_animatedNodes[1] = pos2;
Node &node1 = _nodes[pos1];
Node &node2 = _nodes[pos2];
node1._animated = true;
node2._animated = true;
releaseNodeBack(node1);
releaseNodeBack(node2);
updateNode(node1, pos1, destination(pos1, pos2));
updateNode(node2, pos2, destination(pos1, pos2));
debugC(5, kDebugMinigames, ">>>>>>>>>>>>>>>>>>>>>>>>>>> change %d <> %d, 1st phase <<<<<<<<<<<<<<<<<<<<<<<<<<<<", pos1, pos2);
_nodes[pos1].debugInfo();
_nodes[pos2].debugInfo();
}
void MinigameTriangle::endSwapNodes(int pos1, int pos2) {
Node &node1 = _nodes[pos1];
Node &node2 = _nodes[pos2];
bool counted = false;
if (node1._number == pos1) { // поставили на свое место
assert(!node1._isBack);
counted = true;
_runtime->event(EVENT_PUT_RIGHT, node1.obj()->screen_R());
}
if (node2._number == pos1) { // сняли со своего места
assert(node2._isBack);
counted = true;
_runtime->event(EVENT_GET_RIGHT, node1.obj()->screen_R());
}
if (node2._number == pos2) { // поставили на свое место
assert(!node2._isBack);
counted = true;
_runtime->event(EVENT_PUT_RIGHT, node2.obj()->screen_R());
}
if (node1._number == pos2) { // сняли со своего места
assert(node1._isBack);
counted = true;
_runtime->event(EVENT_GET_RIGHT, node2.obj()->screen_R());
}
if (!counted) { // просто сделали ход
mgVect2i pos = node1.obj()->screen_R();
pos += node2.obj()->screen_R();
pos /= 2;
_runtime->event(EVENT_TURN, pos);
}
bool isWin = true;
int position = 0;
for (auto &it : _nodes) {
if (it._number != position++) {
isWin = false;
break;
}
}
if (isWin) {
setState(GAME_WIN);
return;
}
}
bool MinigameTriangle::animate(float dt) {
if (_animationState == NO_ANIMATION)
return false;
_animationTimer -= dt;
if (_animationTimer > 0)
return true;
Node &node1 = _nodes[_animatedNodes[0]];
Node &node2 = _nodes[_animatedNodes[1]];
switch (_animationState) {
case FIRST_PHASE: {
node1._rotation = getRotate(_animatedNodes[0], _animatedNodes[1]);
node2._rotation = getRotate(_animatedNodes[1], _animatedNodes[0]);
node1._isBack = !node1._isBack;
node2._isBack = !node2._isBack;
releaseNodeBack(node1);
releaseNodeBack(node2);
for (auto &it : node1._face)
it.setState(Node::getFaceStateName(0, false, false, false).c_str());
for (auto &it : node2._face)
it.setState(Node::getFaceStateName(0, false, false, false).c_str());
updateNode(node1, _animatedNodes[1], destination(_animatedNodes[0], _animatedNodes[1]), true);
updateNode(node2, _animatedNodes[0], destination(_animatedNodes[1], _animatedNodes[0]), true);
_animationTimer = 0.f;
_animationState = SECOND_PHASE;
debugC(5, kDebugMinigames, ">>>>>>>>>>>>>>>>>>>>>>>>>>> change %d <> %d, 2nd phase 1 <<<<<<<<<<<<<<<<<<<<<<<<<<<<", _animatedNodes[0], _animatedNodes[1]);
node1.debugInfo();
node2.debugInfo();
return true;
}
case SECOND_PHASE:
node1._animated = false;
node2._animated = false;
updateNode(node1, _animatedNodes[1], destination(_animatedNodes[0], _animatedNodes[1]));
updateNode(node2, _animatedNodes[0], destination(_animatedNodes[1], _animatedNodes[0]));
SWAP(node1, node2);
_animationTimer = _animationTime;
_animationState = FIRD_PHASE;
debugC(5, kDebugMinigames, ">>>>>>>>>>>>>>>>>>>>>>>>>>> change %d <> %d, 2nd phase 2 <<<<<<<<<<<<<<<<<<<<<<<<<<<<", _animatedNodes[0], _animatedNodes[1]);
node2.debugInfo();
node1.debugInfo();
return true;
case FIRD_PHASE:
_animationTimer = 0.f;
_animationState = NO_ANIMATION;
releaseNodeBack(node1);
releaseNodeBack(node2);
updateNode(node1, _animatedNodes[0]);
updateNode(node2, _animatedNodes[1]);
endSwapNodes(_animatedNodes[0], _animatedNodes[1]);
debugC(5, kDebugMinigames, "^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ change %d <> %d, finished ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^", _animatedNodes[0], _animatedNodes[1]);
_animatedNodes[0] = -1;
_animatedNodes[1] = -1;
return true;
default:
break;
}
return false;
}
void MinigameTriangle::swapNodes(int pos1, int pos2, bool silentQuick) {
if (silentQuick) {
Node &node1 = _nodes[pos1];
Node &node2 = _nodes[pos2];
node1._rotation = getRotate(pos1, pos2);
node2._rotation = getRotate(pos2, pos1);
node1._isBack = !node1._isBack;
node2._isBack = !node2._isBack;
releaseNodeBack(node1);
releaseNodeBack(node2);
SWAP(node1, node2);
updateNode(node1, pos1, 0, true);
updateNode(node2, pos2, 0, true);
} else
beginSwapNodes(pos1, pos2);
}
void MinigameTriangle::quant(float dt) {
if (_selected >= 0)
_runtime->setGameHelpVariant(0);
else
_runtime->setGameHelpVariant(1);
if (animate(dt))
return;
int mousePos = -1;
for (int idx = 0; idx < _fieldSize; ++idx)
if (_nodes[idx].hit(_runtime->mousePosition())) {
mousePos = idx;
break;
}
int startAnimation = -1;
int lastSelected = _selected;
if (_runtime->mouseLeftPressed()) {
if (mousePos < 0) // кликнули мимо - снимаем выделение
_selected = -1;
else if (_selected < 0) // ничего выделено небыло, просто выделяем
_selected = mousePos;
else if (_selected == mousePos) // кликнули на выделенном - снимаем выделение
_selected = -1;
else if (compatible(_selected, mousePos)) { // поменять фишки местами
startAnimation = _selected;
_selected = -1;
} else {
_selected = -1;
if (_quickReselect)
_selected = mousePos;
}
}
if (_selected != lastSelected) {
for (int idx = 0; idx < _fieldSize; ++idx) {
Node &node = _nodes[idx];
if (idx == _selected || compatible(_selected, idx)) { // с этой фишкой можно поменяться
if (!node._border)
node._border = _selectBorders[orientation(idx)].getObject();
node._border.setState(Node::getBorderStateName(idx == _selected));
node._border->set_R(slotCoord(idx));
node._border->update_screen_R();
_runtime->setDepth(node._border, _selectDepth);
} else if (node._border) {
_selectBorders[0].releaseObject(node._border, _runtime);
_selectBorders[1].releaseObject(node._border, _runtime);
}
}
}
if (_hovered != mousePos || _selected != lastSelected) {
highlight(_hovered, false);
highlight(_selected >= 0 ? _selected : lastSelected, false);
_hovered = mousePos;
if (_hovered >= 0 && startAnimation < 0) {
if (_selected >= 0) {
if (compatible(_selected, _hovered)) {
highlight(_hovered, true);
highlight(_selected, true);
}
} else
highlight(_hovered, true);
}
}
if (startAnimation >= 0) {
_hovered = -1;
swapNodes(startAnimation, mousePos, false);
}
if (_runtime->mouseRightPressed() && mousePos >= 0) {
debugC(2, kDebugMinigames, "----- DUBUG INFO FOR %d POSITION --------------------", mousePos);
debugC(2, kDebugMinigames, "row = %d, begin = %d, orientation = %d", rowByNum(mousePos), rowBegin(rowByNum(mousePos)), orientation(mousePos));
_nodes[mousePos].debugInfo();
}
}
int MinigameTriangle::rowBegin(int row) const {
if (row == _fieldLines)
return _fieldSize;
switch (_gameType) {
case TRIANGLE:
return sqr(row);
case RECTANGLE:
return row * _fieldWidth;
default:
break;
}
//case HEXAGON:
assert(row >= 0 && row < _fieldLines);
if (row >= _fieldLines / 2) {
row -= _fieldLines / 2;
return _fieldSize / 2 + (2 * _fieldLines - row) * row;
}
return (_fieldLines + row) * row;
}
int MinigameTriangle::rowByNum(int num) const {
if (num >= _fieldSize)
return _fieldLines;
switch (_gameType) {
case TRIANGLE:
return floor(sqrt((float)num));
case RECTANGLE:
return num / _fieldWidth;
default:
break;
}
//case HEXAGON:
int row = num < _fieldSize / 2 ? 0 : _fieldLines / 2;
while (row < _fieldLines && num >= rowBegin(row))
++row;
return row > 0 ? row - 1 : 0;
}
int MinigameTriangle::orientation(int num) const {
switch (_gameType) {
case TRIANGLE:
return (rowByNum(num) + num) % 2;
case RECTANGLE:
return num % 2;
default:
break;
}
//case HEXAGON:
return (num + rowByNum(num) + (num >= _fieldSize / 2 ? 1 : 0)) % 2;
}
bool MinigameTriangle::compatible(int num1, int num2) const {
if (num1 > num2)
SWAP(num1, num2);
if (num1 < 0)
return false;
int row1 = rowByNum(num1);
int row2 = rowByNum(num2);
if (row2 >= _fieldLines)
return false;
if (row1 == row2) // в одном слое
return num2 - num1 == 1; // должны быть рядом
else if (row2 - row1 != 1) // или на соседних слоях
return false;
else if (orientation(num1) != 0) // широкими сторонами друг к другу
return false;
int center1 = (rowBegin(row1) + rowBegin(row1 + 1) - 1) / 2;
int center2 = (rowBegin(row2) + rowBegin(row2 + 1) - 1) / 2;
return center1 - num1 == center2 - num2; // и точно друг под другом
}
int MinigameTriangle::getRotate(int num1, int num2) const {
static int solves[3][2][3] = {
{{0, 2, 1}, {0, 2, 1}},
{{2, 1, 0}, {1, 0, 2}},
{{1, 0, 2}, {2, 1, 0}}
};
assert(compatible(num1, num2));
return solves[rowByNum(num1) != rowByNum(num2) ? 0 : (num2 < num1 ? 1 : 2)]
[orientation(num1)][_nodes[num1]._rotation];
}
int MinigameTriangle::destination(int num1, int num2) const {
if (orientation(num1) == 0)
return rowByNum(num1) != rowByNum(num2) ? 0 : (num2 < num1 ? 1 : 2);
else
return rowByNum(num1) != rowByNum(num2) ? 0 : (num2 < num1 ? 2 : 1);
}
mgVect3f MinigameTriangle::slotCoord(int pos, int angle) const {
assert(pos * 3 + angle < (int)_positions.size());
return _positions[pos * 3 + angle];
}
} // namespace QDEngine

View File

@@ -0,0 +1,137 @@
/* 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 QDENGINE_MINIGAMES_ADV_M_TRIANGLES_H
#define QDENGINE_MINIGAMES_ADV_M_TRIANGLES_H
#include "qdengine/minigames/adv/common.h"
#include "qdengine/minigames/adv/MinigameInterface.h"
#include "qdengine/minigames/adv/ObjectContainer.h"
namespace QDEngine {
MinigameInterface *createMinigameTriangle(MinigameManager *runtime);
class MinigameTriangle : public MinigameInterface {
enum GameType {
TRIANGLE,
RECTANGLE,
HEXAGON
};
enum AnimationState {
NO_ANIMATION,
FIRST_PHASE,
SECOND_PHASE,
FIRD_PHASE
};
struct Node {
Node(int number = -1, int rot = -1);
void release(MinigameManager *runtime);
void debugInfo() const;
const QDObject &obj() const {
return _isBack ? _back : _face[_flip];
}
bool hit(const mgVect2f &pos) const;
int _number; // правильная позиция (номер слота)
int _rotation; // текущий угол поворота (правильный угол = 0)
int _flip;
QDObjects _face; // набор возможных углов переворота для лицевой стороны
QDObject _back; // обратная сторона
QDObject _border; // рамка
bool _isBack; // повернут лицом (true) или рубашкой (false)
bool _highlight;
bool _animated;
static const Common::String getFaceStateName(int angle, bool selected, bool animated, bool instantaneous);
static const char *getBackStateName(bool selected, bool animated, bool instantaneous);
static const char *getBorderStateName(bool selected);
};
typedef Std::vector<Node> Nodes;
public:
MinigameTriangle(MinigameManager *runtime);
~MinigameTriangle();
void quant(float dt);
private:
GameType _gameType = TRIANGLE;
Coords _positions;
int _selectDepth = 0;
int _fieldLines = 0;
int _fieldWidth = 0;
int _fieldSize = 0;
Nodes _nodes;
ObjectContainer _selectBorders[2];
ObjectContainer _backSides[6];
int _selected = 0;
int _hovered = 0;
bool _quickReselect = false;
AnimationState _animationState = NO_ANIMATION;
int _animatedNodes[2] = { 0 };
float _animationTime = 0.0;
float _animationTimer = 0.0;
/// очистить рубашку фишки
void releaseNodeBack(Node &node);
/// выставить графические состояния соответствующие текущему логическому
void updateNode(Node &node, int position, int flip = 0, bool quick = false);
/// подсветить/потушить фрагмент
void highlight(int idx, bool hl);
/// поменять местами фишки
void swapNodes(int pos1, int pos2, bool quick);
/// начать анимацию обмена
void beginSwapNodes(int pos1, int pos2);
/// отработка анимации переворота фишек
bool animate(float dt);
/// вызывается после окончания переворота
void endSwapNodes(int pos1, int pos2);
/// по номеру фишки вычисляет слой
int rowByNum(int num) const;
/// возвращает с какой фишки начинается слой
int rowBegin(int row) const;
/// 0 - угол вверх
int orientation(int num) const;
/// можно поменять местами
bool compatible(int num1, int num2) const;
/// определить какой будет угол поворота у num1 при переходе в num2
int getRotate(int num1, int num2) const;
/// направление переворота
int destination(int num1, int num2) const;
/// по номеру слота и углу переворота (с учетом типа игры) возвращает экранные координаты
mgVect3f slotCoord(int pos, int angle = 0) const;
MinigameManager *_runtime;
};
} // namespace QDEngine
#endif // QDENGINE_MINIGAMES_ADV_M_TRIANGLES_H

View File

@@ -0,0 +1,65 @@
/* 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 QDENGINE_MINIGAMES_ADV_QDMATH_H
#define QDENGINE_MINIGAMES_ADV_QDMATH_H
#include "qdengine/xmath.h"
namespace QDEngine {
#define SQRT2 1.41421356f
#define SQRT3 1.73205081f
inline float dist(const mgVect2f& v1, const mgVect2f& v2) {
return sqrt((v1.x - v2.x) * (v1.x - v2.x) + (v1.y - v2.y) * (v1.y - v2.y));
}
inline float abs(const mgVect2f& v) {
return sqrt(v.x * v.x + v.y * v.y);
}
inline void norm(mgVect2f& v) {
float mod = abs(v);
if (mod < FLT_EPS) {
v = mgVect2f(0, 1);
return;
}
v.x /= mod;
v.y /= mod;
}
template<class T, class T1, class T2>
inline T clamp(const T& x, const T1& xmin, const T2& xmax) {
if (x < xmin) return xmin;
if (x > xmax) return xmax;
return x;
}
template<class T>
inline T abs(const T& x) {
if (x < 0) return -x;
return x;
}
} // namespace QDEngine
#endif // QDENGINE_MINIGAMES_ADV_QDMATH_H