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,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