Files
commandergenius/project/jni/application/enigma/lib-src/oxydlib/Level.cpp
2010-10-13 17:30:44 +03:00

2979 lines
78 KiB
C++

// Copyright 2003 Jeremy Sawicki
//
// This file is part of OxydLib.
//
// OxydLib is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// OxydLib 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 OxydLib; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#include "Level.h"
#include <stdio.h>
namespace OxydLib {
using namespace std;
Marble::Marble()
: m_marbleType(MarbleType_Black)
, m_x(0)
, m_y(0)
, m_data()
{
}
Marble::~Marble()
{
}
RubberBand::RubberBand()
: m_naturalLength(0)
, m_force(0)
, m_firstEndMarble(0)
, m_bSecondEndIsMarble(false)
, m_secondEndMarble(0)
, m_secondEndPieceX(0)
, m_secondEndPieceY(0)
{
}
RubberBand::~RubberBand()
{
}
void RubberBand::setSecondEndMarble(int nMarble)
{
m_bSecondEndIsMarble = true;
m_secondEndMarble = nMarble;
m_secondEndPieceX = 0;
m_secondEndPieceY = 0;
}
void RubberBand::setSecondEndPiece(unsigned int x, unsigned int y)
{
m_bSecondEndIsMarble = false;
m_secondEndMarble = 0;
m_secondEndPieceX = x;
m_secondEndPieceY = y;
}
bool RubberBand::operator == (const RubberBand &other) const
{
if (getNaturalLength() != other.getNaturalLength()) {
return false;
}
if (getForce() != other.getForce()) {
return false;
}
if (getFirstEndMarble() != other.getFirstEndMarble()) {
return false;
}
if (isSecondEndMarble() != other.isSecondEndMarble()) {
return false;
}
if (isSecondEndMarble()) {
if (getSecondEndMarble() != other.getSecondEndMarble()) {
return false;
}
} else {
if (getSecondEndPieceX() != other.getSecondEndPieceX() ||
getSecondEndPieceY() != other.getSecondEndPieceY()) {
return false;
}
}
return true;
}
bool RubberBand::operator != (const RubberBand &other) const
{
return !(*this == other);
}
Block::Block()
: m_x(0)
, m_y(0)
{
}
Block::Block(unsigned int x, unsigned int y)
: m_x(x)
, m_y(y)
{
}
Block::~Block()
{
}
bool Block::operator < (const Block &other) const
{
if (getY() != other.getY()) {
return getY() < other.getY();
}
if (getX() != other.getX()) {
return getX() < other.getX();
}
return false;
}
bool Block::operator == (const Block &other) const
{
if (getX() != other.getX()) {
return false;
}
if (getY() != other.getY()) {
return false;
}
return true;
}
bool Block::operator != (const Block &other) const
{
return !(*this == other);
}
ScrambleItem::ScrambleItem()
: m_x(0)
, m_y(0)
, m_dir(Direction_Up)
{
}
ScrambleItem::ScrambleItem(unsigned int x, unsigned int y, Direction dir)
: m_x(x)
, m_y(y)
, m_dir(dir)
{
}
ScrambleItem::~ScrambleItem()
{
}
Laser::Laser()
: m_dir(Direction_Up)
, m_bOn(false)
{
}
Laser::Laser(Direction dir, bool bOn)
: m_dir(dir)
, m_bOn(bOn)
{
}
Laser::~Laser()
{
}
bool Laser::operator == (const Laser &other) const
{
if (getDir() != other.getDir()) {
return false;
}
if (getOn() != other.getOn()) {
return false;
}
return true;
}
bool Laser::operator != (const Laser &other) const
{
return !(*this == other);
}
Oscillator::Oscillator()
: m_period(0)
, m_bOn(true)
{
}
Oscillator::Oscillator(unsigned int period, bool bOn)
: m_period(period)
, m_bOn(bOn)
{
}
Oscillator::~Oscillator()
{
}
bool Oscillator::operator == (const Oscillator &other) const
{
if (getPeriod() != other.getPeriod()) {
return false;
}
if (getOn() != other.getOn()) {
return false;
}
return true;
}
bool Oscillator::operator != (const Oscillator &other) const
{
return !(*this == other);
}
SignalLocation::SignalLocation()
: m_x(0)
, m_y(0)
, m_gridType(GridType_Pieces)
{
}
SignalLocation::SignalLocation(unsigned int x, unsigned int y,
GridType gridType)
: m_x(x)
, m_y(y)
, m_gridType(gridType)
{
}
SignalLocation::~SignalLocation()
{
}
bool SignalLocation::operator < (const SignalLocation &other) const
{
if (getY() != other.getY()) {
return getY() < other.getY();
}
if (getX() != other.getX()) {
return getX() < other.getX();
}
if (getGridType() != other.getGridType()) {
return getGridType() < other.getGridType();
}
return false;
}
bool SignalLocation::operator == (const SignalLocation &other) const
{
return
getX() == other.getX() &&
getY() == other.getY() &&
getGridType() == other.getGridType();
}
Grid::Grid()
: m_width(0)
, m_height(0)
, m_data()
{
}
Grid::Grid(int width, int height)
: m_width(0)
, m_height(0)
, m_data()
{
resize(width, height);
}
Grid::~Grid()
{
}
void Grid::resize(unsigned int width, unsigned int height)
{
unsigned int oldWidth = m_width;
unsigned int oldHeight = m_height;
ByteVec oldData;
oldData.swap(m_data);
m_width = width;
m_height = height;
m_data.resize(width * height);
for (unsigned int y = 0; y < getHeight(); y++) {
for (unsigned int x = 0; x < getWidth(); x++) {
if (x < oldWidth && y < oldHeight) {
set(x, y, oldData[y * oldWidth + x]);
} else {
set(x, y, 0x00);
}
}
}
}
unsigned char Grid::get(unsigned int x, unsigned int y) const
{
return m_data[y * getWidth() + x];
}
void Grid::set(unsigned int x, unsigned int y, unsigned char val)
{
m_data[y * getWidth() + x] = val;
}
namespace
{
enum
{
INFOSURFACES = 0x0000,
INFOGENERAL = 0x0001,
INFOSIGNAL = 0x0002,
INFOEND = 0x0003,
INFOPIECES = 0x0004,
INFOSPECIAL = 0x0005,
INFOOBJECTS = 0x0006,
NUMINFOTYPES
};
bool unpackSignalLocation(const Level &level,
unsigned int packedSignalLocation,
SignalLocation *pSignalLocation,
bool *pWithinGrid,
string *pMsg)
{
if (packedSignalLocation & 0xc000) {
if (pMsg) {
pMsg->assign("High bits set in signal location.");
}
return false;
}
GridType gridType =
((packedSignalLocation & 0x2000) == 0) ?
GridType_Pieces : GridType_Objects;
unsigned int blockNum = packedSignalLocation & 0x1fff;
unsigned int gridSize = level.getWidth() * level.getHeight();
if (blockNum >= gridSize) {
// This can happen.
/*
if (pMsg) {
pMsg->assign("Signal block number too high.");
}
return false;
*/
*pWithinGrid = false;
return true;
}
unsigned int x = blockNum % level.getWidth();
unsigned int y = blockNum / level.getWidth();
pSignalLocation->setX(x);
pSignalLocation->setY(y);
pSignalLocation->setGridType(gridType);
*pWithinGrid = true;
return true;
}
bool packSignalLocation(const Level &level,
const SignalLocation &signalLocation,
unsigned int *pPackedSignalLocation,
string *pMsg)
{
unsigned int blockNum =
signalLocation.getY() * level.getWidth() + signalLocation.getX();
if (blockNum > 0x1fff) {
if (pMsg) {
pMsg->assign("Signal block number too high.");
}
return false;
}
unsigned int gridTypeBit = 0;
switch (signalLocation.getGridType()) {
case GridType_Pieces: gridTypeBit = 0; break;
case GridType_Objects: gridTypeBit = 1; break;
default:
if (pMsg) {
pMsg->assign("Invalid grid type for signal location.");
}
return false;
}
*pPackedSignalLocation =
(gridTypeBit ? 0x2000 : 0x0000) |
blockNum;
return true;
}
Laser s_defaultLasers[3] = {
Laser(Direction_Up, false),
Laser(Direction_Down, false),
Laser(Direction_Right, false)
};
}
Level::Level()
{
clear();
}
Level::~Level()
{
}
void Level::clear()
{
m_bInit = false;
m_bEmpty = true;
m_width = 0;
m_height = 0;
m_bMeditation = false;
m_bHarmlessMeditationMarbles = false;
m_marbles.clear();
m_blackRubberBandObjectMarble = 1;
m_whiteRubberBandObjectMarble = 0;
m_bRequireMagicPiece = false;
m_bScrolling = false;
m_bWalkThroughPuzzle = false;
m_scrambleItems.clear();
for (int nLaser = 0; nLaser < 3; nLaser++) {
m_lasers[nLaser] = s_defaultLasers[nLaser];
}
for (int nNote = 0; nNote < 2; nNote++) {
for (int nLang = Language_First;
nLang <= Language_Last;
nLang++) {
m_noteText[nNote][nLang].assign("");
}
}
for (int gameModeInt = GameMode_First;
gameModeInt <= GameMode_Last;
gameModeInt++) {
GameMode gameMode = GameMode(gameModeInt);
m_rubberBands[gameMode].clear();
m_blackRubberBandPieceNaturalLength[gameMode] = -1;
m_blackRubberBandPieceForce[gameMode] = -1;
m_whiteRubberBandPieceNaturalLength[gameMode] = -1;
m_whiteRubberBandPieceForce[gameMode] = -1;
m_rubberBandObjectNaturalLength[gameMode] = -1;
m_rubberBandObjectForce[gameMode] = -1;
m_bReset[gameMode] = false;
m_flatForce[gameMode] = -1;
m_slopeForce[gameMode] = -1;
m_friction[gameMode] = -1;
m_oscillators[gameMode].clear();
}
for (int gridTypeInt = GridType_First;
gridTypeInt <= GridType_Last;
gridTypeInt++) {
GridType gridType = GridType(gridTypeInt);
m_grids[gridType].resize(0, 0);
}
m_specialItems.clear();
m_signals.clear();
}
void Level::resize(unsigned int width, unsigned int height)
{
m_width = width;
m_height = height;
for (int gridTypeInt = GridType_First;
gridTypeInt <= GridType_Last;
gridTypeInt++) {
GridType gridType = GridType(gridTypeInt);
m_grids[gridType].resize(width, height);
}
}
int Level::getNumRubberBands(GameMode gameMode) const
{
return m_rubberBands[gameMode].size();
}
const RubberBand &Level::getRubberBand(GameMode gameMode,
int nRubberBand) const
{
return m_rubberBands[gameMode][nRubberBand];
}
RubberBand *Level::getRubberBandForWrite(GameMode gameMode,
int nRubberBand)
{
return &m_rubberBands[gameMode][nRubberBand];
}
void Level::addRubberBand(GameMode gameMode,
const RubberBand &rubberBand,
int nRubberBand)
{
vector<RubberBand> &vec = m_rubberBands[gameMode];
if (nRubberBand < 0) {
nRubberBand = vec.size();
}
vec.insert(vec.begin() + nRubberBand, rubberBand);
}
void Level::removeRubberBand(GameMode gameMode, int nRubberBand)
{
vector<RubberBand> &vec = m_rubberBands[gameMode];
vec.erase(vec.begin() + nRubberBand);
}
int Level::getBlackRubberBandPieceNaturalLength(GameMode gameMode) const
{
return m_blackRubberBandPieceNaturalLength[gameMode];
}
void Level::setBlackRubberBandPieceNaturalLength(GameMode gameMode,
int naturalLength)
{
m_blackRubberBandPieceNaturalLength[gameMode] = naturalLength;
}
int Level::getBlackRubberBandPieceForce(GameMode gameMode) const
{
return m_blackRubberBandPieceForce[gameMode];
}
void Level::setBlackRubberBandPieceForce(GameMode gameMode, int force)
{
m_blackRubberBandPieceForce[gameMode] = force;
}
int Level::getWhiteRubberBandPieceNaturalLength(GameMode gameMode) const
{
return m_whiteRubberBandPieceNaturalLength[gameMode];
}
void Level::setWhiteRubberBandPieceNaturalLength(GameMode gameMode,
int naturalLength)
{
m_whiteRubberBandPieceNaturalLength[gameMode] = naturalLength;
}
int Level::getWhiteRubberBandPieceForce(GameMode gameMode) const
{
return m_whiteRubberBandPieceForce[gameMode];
}
void Level::setWhiteRubberBandPieceForce(GameMode gameMode, int force)
{
m_whiteRubberBandPieceForce[gameMode] = force;
}
int Level::getRubberBandObjectNaturalLength(GameMode gameMode) const
{
return m_rubberBandObjectNaturalLength[gameMode];
}
void Level::setRubberBandObjectNaturalLength(GameMode gameMode,
int naturalLength)
{
m_rubberBandObjectNaturalLength[gameMode] = naturalLength;
}
int Level::getRubberBandObjectForce(GameMode gameMode) const
{
return m_rubberBandObjectForce[gameMode];
}
void Level::setRubberBandObjectForce(GameMode gameMode, int force)
{
m_rubberBandObjectForce[gameMode] = force;
}
int Level::getBlackRubberBandObjectMarble() const
{
return m_blackRubberBandObjectMarble;
}
void Level::setBlackRubberBandObjectMarble(int nMarble)
{
m_blackRubberBandObjectMarble = nMarble;
}
int Level::getWhiteRubberBandObjectMarble() const
{
return m_whiteRubberBandObjectMarble;
}
void Level::setWhiteRubberBandObjectMarble(int nMarble)
{
m_whiteRubberBandObjectMarble = nMarble;
}
int Level::getNumScrambleItems() const
{
return m_scrambleItems.size();
}
const ScrambleItem &Level::getScrambleItem(int nScrambleItem) const
{
return m_scrambleItems[nScrambleItem];
}
ScrambleItem *Level::getScrambleItemForWrite(int nScrambleItem)
{
return &m_scrambleItems[nScrambleItem];
}
void Level::addScrambleItem(const ScrambleItem &scrambleItem,
int nScrambleItem)
{
vector<ScrambleItem> &vec = m_scrambleItems;
if (nScrambleItem < 0) {
nScrambleItem = vec.size();
}
vec.insert(vec.begin() + nScrambleItem, scrambleItem);
}
void Level::removeScrambleItem(int nScrambleItem)
{
vector<ScrambleItem> &vec = m_scrambleItems;
vec.erase(vec.begin() + nScrambleItem);
}
const std::string &Level::getNoteText(int nNote, Language lang) const
{
const std::string &note = m_noteText[nNote][lang];
if (note == "" && lang != Language_International)
return m_noteText[nNote][Language_International];
return note;
}
void Level::setNoteText(int nNote, Language lang, const std::string &noteText)
{
m_noteText[nNote][lang].assign(noteText);
}
const Laser &Level::getLaser(int nLaser) const
{
return m_lasers[nLaser];
}
void Level::setLaser(int nLaser, const Laser &laser)
{
m_lasers[nLaser] = laser;
}
const OscillatorMap &Level::getOscillators(GameMode gameMode) const
{
return m_oscillators[gameMode];
}
OscillatorMap *Level::getOscillatorsForWrite(GameMode gameMode)
{
return &m_oscillators[gameMode];
}
void Level::getSenders(set<SignalLocation> *pSenders) const
{
pSenders->clear();
SignalMap::const_iterator iter = m_signals.begin();
SignalMap::const_iterator end = m_signals.end();
for (; iter != end; ++iter) {
pSenders->insert(iter->first);
}
}
int Level::getNumRecipients(const SignalLocation &sender) const
{
SignalMap::const_iterator findIter = m_signals.find(sender);
if (findIter == m_signals.end()) {
return 0;
} else {
return findIter->second.size();
}
}
const SignalLocation &Level::getRecipient(const SignalLocation &sender,
int nRecipient) const
{
SignalMap::const_iterator findIter = m_signals.find(sender);
if (findIter == m_signals.end()) {
return *(SignalLocation *)0;
} else {
return findIter->second[nRecipient];
}
}
void Level::setRecipient(const SignalLocation &sender,
int nRecipient,
const SignalLocation &recipient)
{
SignalMap::iterator findIter = m_signals.find(sender);
SignalLocation *pRecipient = 0;
if (findIter != m_signals.end()) {
pRecipient = &findIter->second[nRecipient];
}
*pRecipient = recipient;
}
void Level::addRecipient(const SignalLocation &sender,
const SignalLocation &recipient,
int nRecipient)
{
vector<SignalLocation> &vec = m_signals[sender];
if (nRecipient < 0) {
nRecipient = vec.size();
}
vec.insert(vec.begin() + nRecipient, recipient);
}
void Level::removeRecipient(const SignalLocation &sender,
int nRecipient)
{
vector<SignalLocation> &vec = m_signals[sender];
vec.erase(vec.begin() + nRecipient);
if (vec.empty()) {
m_signals.erase(sender);
}
}
namespace
{
bool parseGrid(const ByteVec &compressedGrid,
Grid *pGrid,
string *pMsg)
{
string msg;
if (!pMsg) {
pMsg = &msg;
}
int gridSize = pGrid->getHeight() * pGrid->getWidth();
ByteVec data;
data.resize(gridSize);
int curIn = compressedGrid.size() - 1;
int curOut = gridSize - 1;
while (curIn >= 0) {
unsigned char curByte = compressedGrid[curIn];
curIn--;
switch (curByte) {
case 0xFF: // long repeat
{
if (curIn - 3 < -1) {
pMsg->assign("Unexpected end of compressed grid data.");
return false;
}
unsigned char repeatByte = compressedGrid[curIn];
curIn--;
int repeatCount = compressedGrid[curIn];
curIn--;
repeatCount <<= 8;
repeatCount += compressedGrid[curIn];
repeatCount++;
curIn--;
if (curOut - repeatCount < -1) {
pMsg->assign("Too much compresed grid data writing repeated bytes.");
return false;
}
int ii;
for (ii = 0; ii < repeatCount; ii++) {
data[curOut] = repeatByte;
curOut--;
}
}
break;
case 0xFD: // short repeat
{
if (curIn - 2 < -1) {
pMsg->assign("Unexpected end of compressed grid data.");
return false;
}
unsigned char repeatByte = compressedGrid[curIn];
curIn--;
int repeatCount = compressedGrid[curIn] + 1;
curIn--;
if (curOut - repeatCount < -1) {
pMsg->assign("Too much compresed grid data writing repeated bytes.");
return false;
}
int ii;
for (ii = 0; ii < repeatCount; ii++) {
data[curOut] = repeatByte;
curOut--;
}
}
break;
case 0xFE: // pattern
{
if (curIn - 3 < -1) {
pMsg->assign("Unexpected end of compressed grid data.");
return false;
}
unsigned char patternBytes[2];
patternBytes[0] = compressedGrid[curIn];
curIn--;
patternBytes[1] = compressedGrid[curIn];
curIn--;
int patternCount = compressedGrid[curIn] + 1;
curIn--;
int bitPatternSize = (patternCount + 7) / 8;
if (curIn - bitPatternSize < -1) {
pMsg->assign("Unexpected end of compressed grid data.");
return false;
}
if (curOut - patternCount < -1) {
pMsg->assign("Too much compresed grid data writing repeated bytes.");
return false;
}
int ii;
for (ii = patternCount - 1; ii >= 0; ii--) {
unsigned char bitPatternByte = compressedGrid[curIn];
int bitNum = ii % 8;
int bit = (bitPatternByte & (1 << bitNum)) >> bitNum;
data[curOut] = patternBytes[bit];
curOut--;
if (ii % 8 == 0) {
curIn--;
}
}
}
break;
default:
{
if (curOut - 1 < -1) {
pMsg->assign("Too much compresed grid data writing literal byte.");
return false;
}
data[curOut] = curByte;
curOut--;
}
}
}
if (curOut >= 0) {
pMsg->assign("Not enough compressed grid data.");
return false;
}
for (unsigned int y = 0; y < pGrid->getHeight(); y++) {
for (unsigned int x = 0; x < pGrid->getWidth(); x++) {
pGrid->set(x, y, data[y * pGrid->getWidth() + x]);
}
}
return true;
}
bool unparseGrid(const Grid &grid,
ByteVec *pCompressedGrid,
string *pMsg)
{
string msg;
if (!pMsg) {
pMsg = &msg;
}
// No compression for now
pCompressedGrid->resize(grid.getWidth() * grid.getHeight());
for (unsigned int y = 0; y < grid.getHeight(); y++) {
for (unsigned int x = 0; x < grid.getWidth(); x++) {
(*pCompressedGrid)[y * grid.getWidth() + x] = grid.get(x, y);
}
}
return true;
}
void unparseNumberList(const vector<int> &numberList,
string *pStr)
{
for (int i = 0; i < (int)numberList.size(); i++) {
int num = numberList[i];
pStr->append(1, '(');
if (num != -1) {
char buf[1000];
sprintf(buf, "%d", num);
pStr->append(buf);
}
pStr->append(1, ')');
}
}
}
bool parseNumberList(const string &str,
vector<int> *pNumberList)
{
pNumberList->clear();
int cur = 0;
int sz = str.size();
while (cur < sz) {
if (str[cur] != '(') {
return false;
}
cur++;
bool bEmpty = true;
int num = 0;
while (cur < sz && str[cur] >= '0' && str[cur] <= '9') {
bEmpty = false;
num *= 10;
num += (str[cur] - '0');
cur++;
}
if (cur < sz) {
if (str[cur] == ')') {
pNumberList->push_back(bEmpty ? -1 : num);
cur++;
} else {
return false;
}
} else {
return false;
}
}
return true;
}
namespace
{
class CommandParser {
string m_cmd;
string::iterator m_iter;
public:
CommandParser (const string &cmd)
: m_cmd(cmd), m_iter(m_cmd.begin())
{}
char get_char () {
char c = 0;
if (m_iter != m_cmd.end()) {
c = *m_iter;
++m_iter;
}
return c;
}
bool get_bool(bool default_) {
unsigned char c = get_char();
switch (c) {
case 0xf8: return false;
case 0xf9: return true;
default: return default_;
}
}
int get_int(int default_) {
return default_;
}
};
}
bool parseLevel(const ByteVec &in, Level *pLevel, string *pMsg)
{
string msg;
if (!pMsg) {
pMsg = &msg;
}
pLevel->clear();
if (in.empty()) {
pLevel->setInit(true);
pLevel->setEmpty(true);
return true;
}
pLevel->setEmpty(false);
int sz = in.size();
bool found[NUMINFOTYPES];
int nFound;
for (nFound = 0; nFound < NUMINFOTYPES; nFound++) {
found[nFound] = false;
}
int cur1 = 0;
ByteVec infoArr[NUMINFOTYPES];
bool foundAll = false;
do {
if (cur1 + 2 > sz) {
pMsg->assign("Unexpected end of level reading info type code.");
return false;
}
unsigned int infoType = getInt2(in.begin() + cur1);
cur1 += 2;
if (infoType >= NUMINFOTYPES) {
pMsg->assign("Unexpected info type code.");
return false;
}
if (found[infoType]) {
pMsg->assign("Duplicate info type found.");
return false;
}
found[infoType] = true;
int infoSize = 0;
switch (infoType) {
case INFOSURFACES:
case INFOSIGNAL:
case INFOPIECES:
case INFOSPECIAL:
case INFOOBJECTS:
{
if (cur1 + 2 > sz) {
pMsg->assign("Unexpected end of level reading info length.");
return false;
}
infoSize = getInt2(in.begin() + cur1);
cur1 += 2;
}
break;
case INFOGENERAL:
{
infoSize = 68;
break;
}
case INFOEND:
{
infoSize = 0;
break;
}
}
if (cur1 + infoSize > sz) {
pMsg->assign("Unexpected end of level reading info data.");
return false;
}
infoArr[infoType].resize(infoSize);
copy(in.begin() + cur1, in.begin() + cur1 + infoSize,
infoArr[infoType].begin());
cur1 += infoSize;
foundAll = true;
for (nFound = 0; nFound < NUMINFOTYPES; nFound++) {
foundAll = foundAll && found[nFound];
}
if (infoType == INFOEND && !foundAll) {
pMsg->assign("Info type info-end found too soon.");
return false;
}
} while (!foundAll);
if (cur1 != sz) {
pMsg->assign("Unexpected data at the end of level.");
return false;
}
ByteVec &infoGeneral = infoArr[INFOGENERAL];
unsigned int width = getInt2(infoGeneral.begin() + 0x00);
unsigned int height = getInt2(infoGeneral.begin() + 0x02);
pLevel->resize(width, height);
for (int nMarble = 0; nMarble < 8; nMarble++) {
if (infoGeneral[0x04 + (nMarble * 8) + 0x02] != 0x00 ||
infoGeneral[0x04 + (nMarble * 8) + 0x03] != 0x00 ||
infoGeneral[0x04 + (nMarble * 8) + 0x06] != 0x00 ||
infoGeneral[0x04 + (nMarble * 8) + 0x07] != 0x00) {
pMsg->assign("Unexpected non-zero byte in info-general.");
return false;
}
}
{
const ByteVec &infoSpecialVec = infoArr[INFOSPECIAL];
if (infoSpecialVec.size() < 1) {
pMsg->assign("Info special is too small.");
return false;
}
{
for (int i = 0; i < (int)infoSpecialVec.size() - 1; i++) {
if (infoSpecialVec[i] == 0x00) {
pMsg->assign("Info special contains a 0x00 byte before the end.");
return false;
}
}
}
if (infoSpecialVec[infoSpecialVec.size() - 1] != 0x00) {
pMsg->assign("Info special does not end with 0x00.");
return false;
}
string infoSpecial;
infoSpecial.resize(infoSpecialVec.size() - 1);
copy(infoSpecialVec.begin(),
infoSpecialVec.begin() + (infoSpecialVec.size() - 1),
infoSpecial.begin());
//printf("%s\n", infoSpecial.c_str());
vector<string> specialItems;
int start = 0;
int infoSpecialSize = infoSpecial.size();
while (start < infoSpecialSize) {
while (start < infoSpecialSize &&
infoSpecial[start] == 0x20) {
start++;
}
if (start < infoSpecialSize) {
int end = start + 1;
bool inQuote = false;
while (end < infoSpecialSize &&
(infoSpecial[end] != 0x20 || inQuote)) {
if (infoSpecial[end] == '"') {
inQuote = !inQuote;
}
end++;
}
// This can happen
/*
if (inQuote) {
pMsg->assign("Quote not terminated in info-special.");
return false;
}
*/
string specialItem = infoSpecial.substr(start, end - start);
specialItems.push_back(specialItem);
start = end;
}
}
/*
{
for (int nItem = 0; nItem < (int)specialItems.size(); nItem++) {
printf(" %s\n", specialItems[nItem].c_str());
}
}
*/
GameMode gameMode = GameMode_Invalid;
int numMarblesArr[GameMode_Count];
int numExtraRubberBandsArr[GameMode_Count];
{
for (int curGameModeInt = GameMode_First;
curGameModeInt <= GameMode_Last;
curGameModeInt++) {
GameMode curGameMode = GameMode(curGameModeInt);
numMarblesArr[curGameMode] = 0;
numExtraRubberBandsArr[curGameMode] = 0;
}
}
bool foundLaserArr[3];
{
for (int nLaser = 0; nLaser < 3; nLaser++) {
foundLaserArr[nLaser] = false;
}
}
for (int nItem = 0; nItem < (int)specialItems.size(); nItem++) {
const string &item = specialItems[nItem];
// Easy and hard game mode
if (item.compare("[") == 0) {
if (gameMode != GameMode_Invalid) {
pMsg->assign("Hard game mode begin not expected.");
return false;
}
gameMode = GameMode_Hard;
continue;
}
if (item.compare("]") == 0) {
if (gameMode != GameMode_Hard) {
pMsg->assign("Hard game mode end not expected.");
return false;
}
gameMode = GameMode_Invalid;
continue;
}
if (item.compare("{") == 0) {
if (gameMode != GameMode_Invalid) {
pMsg->assign("Easy game mode begin not expected.");
return false;
}
gameMode = GameMode_Easy;
continue;
}
if (item.compare("}") == 0) {
if (gameMode != GameMode_Easy) {
pMsg->assign("Easy game mode end not expected.");
return false;
}
gameMode = GameMode_Invalid;
continue;
}
// Goal
if (item[0] == 'G') {
if (item.size() == 2 && item[1] == 'M') {
if (gameMode != GameMode_Invalid) {
pMsg->assign("Goal must be the same in all game modes.");
return false;
}
pLevel->setMeditation(true);
continue;
}
if (item.size() >= 2 && item[1] == 'N') {
if (gameMode != GameMode_Invalid) {
pMsg->assign("Goal must be the same in all game modes.");
return false;
}
pLevel->setMeditation(false);
continue;
}
}
// Harmless meditation marbles
if (item.compare("n") == 0) {
if (gameMode != GameMode_Invalid) {
pMsg->assign("Harmless meditation marbles setting must be the same in all game modes.");
return false;
}
pLevel->setHarmlessMeditationMarbles(true);
continue;
}
// Marbles
if (item[0] == 'F' && item.size() >= 2) {
MarbleType marbleType = MarbleType_Invalid;
switch (item[1]) {
case 'B': marbleType = MarbleType_Black; break;
case 'b': marbleType = MarbleType_White; break;
case 'M': marbleType = MarbleType_Meditation; break;
case 'G': marbleType = MarbleType_Horse; break;
case 'K': marbleType = MarbleType_Jack; break;
case 'C': marbleType = MarbleType_LifeSpitter; break;
case 'D': marbleType = MarbleType_DynamiteHolder; break;
case 'R': marbleType = MarbleType_Rotor; break;
case 'H': marbleType = MarbleType_Bug; break;
default:
printf ("(Oxydlib) unknown marble type: %c\n", item[1]);
break;
}
if (marbleType != MarbleType_Invalid) {
for (int curGameModeInt = GameMode_First;
curGameModeInt <= GameMode_Last;
curGameModeInt++) {
GameMode curGameMode = GameMode(curGameModeInt);
if (gameMode != GameMode_Invalid &&
curGameMode != gameMode) {
continue;
}
if (numMarblesArr[curGameMode] >= 8) {
pMsg->assign("Too many marbles.");
return false;
}
int nMarble = numMarblesArr[curGameMode];
numMarblesArr[curGameMode]++;
if (pLevel->getNumMarbles() < nMarble) {
pMsg->assign("Something strange has happened parsing marbles.");
return false;
} else if (pLevel->getNumMarbles() == nMarble) {
pLevel->setNumMarbles(nMarble + 1);
Marble *pMarble = pLevel->getMarbleForWrite(nMarble);
pMarble->setMarbleType(marbleType);
pMarble->setX(getInt2(infoGeneral.begin() + 0x04 +
(nMarble * 8)));
pMarble->setY(getInt2(infoGeneral.begin() + 0x04 +
(nMarble * 8) + 0x04));
string *pData = pMarble->getDataForWrite(curGameMode);
*pData = item.substr(2);
} else { // if (pLevel->getNumMarbles() > nMarble)
Marble *pMarble = pLevel->getMarbleForWrite(nMarble);
if (pMarble->getMarbleType() != marbleType) {
pMsg->assign("Marble types differ in easy and hard game mode.");
return false;
}
string *pData = pMarble->getDataForWrite(curGameMode);
*pData = item.substr(2);
}
}
continue;
}
}
// Rubber bands
if (item[0] == 'B') {
string temp1 = item.substr(1);
string::size_type f8Pos = temp1.find(0xf8);
string temp2;
string temp3;
if (f8Pos == string::npos) {
temp2 = temp1;
} else {
temp2 = temp1.substr(0, f8Pos);
temp3 = temp1.substr(f8Pos + 1);
}
if (!temp3.empty()) {
pMsg->assign("Rubber band has extra data after the 0xF8 byte.");
return false;
}
string::size_type f9Pos = temp2.find((char)0xf9);
string strNumberList1;
string strNumberList2;
if (f9Pos == string::npos) {
strNumberList1 = temp2;
} else {
strNumberList1 = temp2.substr(0, f9Pos);
strNumberList2 = temp2.substr(f9Pos + 1);
}
vector<int> numberList1;
if (!parseNumberList(strNumberList1, &numberList1)) {
pMsg->assign("Cannot parse first number list in rubber band.");
return false;
}
vector<int> numberList2;
if (!parseNumberList(strNumberList2, &numberList2)) {
pMsg->assign("Cannot parse second number list in rubber band.");
return false;
}
if (numberList1.size() > 3) {
pMsg->assign("First number list in rubber band is too long.");
return false;
}
if (numberList2.size() > 2) {
pMsg->assign("Second number list in rubber band is too long.");
return false;
}
int naturalLength = -1;
if (numberList1.size() > 0) {
naturalLength = numberList1[0];
}
int force = -1;
if (numberList1.size() > 1) {
force = numberList1[1];
}
int blockNumber = -1;
if (numberList1.size() > 2) {
blockNumber = numberList1[2];
}
int marbleNumber1 = -1;
if (numberList2.size() > 0) {
marbleNumber1 = numberList2[0];
}
int marbleNumber2 = -1;
if (numberList2.size() > 1) {
marbleNumber2 = numberList2[1];
}
bool bInitialRubberBand = false;
RubberBand rubberBand;
if (blockNumber != -1 ||
marbleNumber2 != -1) {
if (blockNumber != -1 &&
marbleNumber2 != -1) {
pMsg->assign("A rubber band can't have a block number and two marble numbers.");
return false;
}
if (marbleNumber1 == -1 &&
marbleNumber2 != -1) {
pMsg->assign("A rubber band can't have a second marble number without a first.");
return false;
}
if (naturalLength == -1 ||
force == -1) {
pMsg->assign("An initial rubber band must have both natural length and force values.");
return false;
}
bInitialRubberBand = true;
rubberBand.setNaturalLength(naturalLength);
rubberBand.setForce(force);
rubberBand.setFirstEndMarble(marbleNumber1 == -1 ?
0 : marbleNumber1);
if (blockNumber != -1) {
unsigned int gridSize = width * height;
if (blockNumber >= (int)gridSize) {
pMsg->assign("Rubber band block number is too large.");
return false;
}
unsigned int x = blockNumber % width;
unsigned int y = blockNumber / width;
rubberBand.setSecondEndPiece(x, y);
} else {
rubberBand.setSecondEndMarble(marbleNumber2);
}
} else if (marbleNumber1 != -1) {
pMsg->assign("A rubber band can't have a first marble number without a block number or a secend marble number.");
return false;
} else {
// No checks on natural length or force for extra rubber bands
}
for (int curGameModeInt = GameMode_First;
curGameModeInt <= GameMode_Last;
curGameModeInt++) {
GameMode curGameMode = GameMode(curGameModeInt);
if (gameMode != GameMode_Invalid &&
curGameMode != gameMode) {
continue;
}
if (bInitialRubberBand) {
pLevel->addRubberBand(curGameMode, rubberBand);
} else {
int nExtraRubberBand = numExtraRubberBandsArr[curGameMode];
if (nExtraRubberBand >= 3) {
pMsg->assign("Too many extra rubber bands.");
return false;
}
numExtraRubberBandsArr[curGameMode]++;
switch (nExtraRubberBand) {
case 0:
{
pLevel->setBlackRubberBandPieceNaturalLength(curGameMode,
naturalLength);
pLevel->setBlackRubberBandPieceForce(curGameMode,
force);
}
break;
case 1:
{
pLevel->setWhiteRubberBandPieceNaturalLength(curGameMode,
naturalLength);
pLevel->setWhiteRubberBandPieceForce(curGameMode,
force);
}
break;
case 2:
{
pLevel->setRubberBandObjectNaturalLength(curGameMode,
naturalLength);
pLevel->setRubberBandObjectForce(curGameMode, force);
}
break;
}
}
}
continue;
}
// Rubber band object marbles
if (item[0] == 'N') {
string strNumberList = item.substr(1);
vector<int> numberList;
if (parseNumberList(strNumberList, &numberList)) {
if (numberList.size() <= 2) {
if (gameMode != GameMode_Invalid) {
pMsg->assign("Rubber band object marbles must be the same in all game modes.");
return false;
}
int nMarbleBlack = 1;
if (numberList.size() > 0 &&
numberList[0] != -1) {
nMarbleBlack = numberList[0];
}
int nMarbleWhite = 0;
if (numberList.size() > 1 &&
numberList[1] != -1) {
nMarbleWhite = numberList[1];
}
pLevel->setBlackRubberBandObjectMarble(nMarbleBlack);
pLevel->setWhiteRubberBandObjectMarble(nMarbleWhite);
}
}
// Skip anything else that begins with 'N', since there is
// a rubber band that mistakenly begins with 'N' instead
// of 'B'.
continue;
}
// Require magic piece
if (item.compare("!") == 0) {
if (gameMode != GameMode_Invalid) {
pMsg->assign("Require magic piece setting must be the same in all game modes.");
return false;
}
pLevel->setRequireMagicPiece(true);
continue;
}
// Scrolling
if (item.compare("f") == 0) {
if (gameMode != GameMode_Invalid) {
pMsg->assign("Scrolling setting must be the same in all game modes.");
return false;
}
pLevel->setScrolling(true);
continue;
}
// Reset
if (item.compare("s") == 0) {
for (int curGameModeInt = GameMode_First;
curGameModeInt <= GameMode_Last;
curGameModeInt++) {
GameMode curGameMode = GameMode(curGameModeInt);
if (gameMode != GameMode_Invalid &&
curGameMode != gameMode) {
continue;
}
pLevel->setReset(curGameMode, true);
}
continue;
}
// Walk-through puzzle
if (item.compare("j") == 0) {
if (gameMode != GameMode_Invalid) {
pMsg->assign("Walk-through puzzle setting must be the same in all game modes.");
return false;
}
pLevel->setWalkThroughPuzzle(true);
continue;
}
// Force
if (item[0] == 'g') {
for (int curGameModeInt = GameMode_First;
curGameModeInt <= GameMode_Last;
curGameModeInt++) {
GameMode curGameMode = GameMode(curGameModeInt);
if (gameMode != GameMode_Invalid &&
curGameMode != gameMode) {
continue;
}
string strNumberList = item.substr(1);
vector<int> numberList;
if (parseNumberList(strNumberList, &numberList)) {
if (numberList.size() >= 1) {
pLevel->setFlatForce(curGameMode, numberList[0]);
}
if (numberList.size() >= 2) {
pLevel->setFriction(curGameMode, numberList[1]);
}
if (numberList.size() >= 3) {
pLevel->setSlopeForce(curGameMode, numberList[2]);
}
} else {
pMsg->assign("Could not parse force special item.");
return false;
}
}
continue;
}
// Scramble items
if (item[0] == 'P') {
if (gameMode != GameMode_Invalid) {
pMsg->assign("Scramble items must be the same in all game modes.");
return false;
}
string strNumberList = item.substr(1);
vector<int> numberList;
if (!parseNumberList(strNumberList, &numberList)) {
pMsg->assign("Could not parse scramble items.");
return false;
}
if (numberList.size() % 2 == 1) {
// This can happen
/*
pMsg->assign("Scramble item number list size should not be odd.");
return false;
*/
numberList.erase(numberList.begin() + (numberList.size() - 1));
}
for (int nScrambleItem = 0;
nScrambleItem < (int)(numberList.size() / 2);
nScrambleItem++) {
int blockNumber = numberList[nScrambleItem * 2];
int dirInt = numberList[nScrambleItem * 2 + 1];
unsigned int gridSize = width * height;
if (blockNumber >= (int)gridSize) {
pMsg->assign("Scramble item block number is too large.");
return false;
}
unsigned int x = blockNumber % width;
unsigned int y = blockNumber / width;
Direction dir = Direction_Invalid;
switch (dirInt) {
case 0: dir = Direction_Up; break;
case 1: dir = Direction_Down; break;
case 2: dir = Direction_Left; break;
case 3: dir = Direction_Right; break;
}
if (dir == Direction_Invalid) {
pMsg->assign("Invalid scramble item direction number.");
return false;
}
pLevel->addScrambleItem(ScrambleItem(x, y, dir));
}
continue;
}
// Lasers
if (item[0] == 'L') {
if (gameMode != GameMode_Invalid) {
pMsg->assign("Lasers must be the same in all game modes.");
return false;
}
string strLaserData = item.substr(1);
string strNumberList;
bool bOn = false;
if (strLaserData.size() > 0) {
char lastByte = strLaserData[strLaserData.size() - 1];
if (lastByte == (char)0xf9 ||
lastByte == (char)0xf8) {
strNumberList = strLaserData.substr(0, strLaserData.size() - 1);
if (lastByte == (char)0xf9) {
bOn = true;
}
} else {
strNumberList = strLaserData;
}
}
vector<int> numberList;
if (!parseNumberList(strNumberList, &numberList)) {
pMsg->assign("Could not parse laser.");
return false;
}
if (numberList.size() != 2) {
pMsg->assign("Laser number list should have two numbers.");
return false;
}
int laserNumber = numberList[0];
int dirInt = numberList[1];
if (laserNumber < 1 || laserNumber > 3) {
pMsg->assign("Laser type must be between 1 and 3.");
return false;
}
Direction dir = Direction_Invalid;
switch (dirInt) {
case 0: dir = Direction_Up; break;
case 1: dir = Direction_Down; break;
case 2: dir = Direction_Left; break;
case 3: dir = Direction_Right; break;
}
if (dir == Direction_Invalid) {
pMsg->assign("Invalid laser direction number.");
return false;
}
if (foundLaserArr[laserNumber - 1]) {
pMsg->assign("Laser type occurs more than once.");
return false;
}
foundLaserArr[laserNumber - 1] = true;
pLevel->setLaser(laserNumber - 1, Laser(dir, bOn));
continue;
}
// Oscillators
if (item[0] == '~') {
string strNumberList = item.substr(1);
vector<int> numberList;
if (!parseNumberList(strNumberList, &numberList)) {
pMsg->assign("Could not parse oscillator.");
return false;
}
if (numberList.size() != 2) {
pMsg->assign("Oscillator number list should have two numbers.");
return false;
}
int blockNumber = numberList[0];
int period = numberList[1];
if (blockNumber == -1) {
pMsg->assign("Oscillator block number is empty.");
return false;
}
if (period == -1) {
pMsg->assign("Oscillator period is empty.");
return false;
}
unsigned int gridSize = width * height;
if (blockNumber >= (int)gridSize) {
pMsg->assign("Oscillator block number is too large.");
return false;
}
unsigned int x = blockNumber % width;
unsigned int y = blockNumber / width;
for (int curGameModeInt = GameMode_First;
curGameModeInt <= GameMode_Last;
curGameModeInt++) {
GameMode curGameMode = GameMode(curGameModeInt);
if (gameMode != GameMode_Invalid &&
curGameMode != gameMode) {
continue;
}
OscillatorMap *pOscillators =
pLevel->getOscillatorsForWrite(curGameMode);
(*pOscillators)[Block(x, y)].setPeriod(period);
}
continue;
}
// Still oscillators
if (item[0] == 'O') {
string strNumberList = item.substr(1);
vector<int> numberList;
if (!parseNumberList(strNumberList, &numberList)) {
pMsg->assign("Could not parse still oscillator.");
return false;
}
if (numberList.size() != 1) {
pMsg->assign("Still oscillator number list should have one number.");
return false;
}
int blockNumber = numberList[0];
if (blockNumber == -1) {
pMsg->assign("Still oscillator block number is empty.");
return false;
}
unsigned int gridSize = width * height;
if (blockNumber >= (int)gridSize) {
pMsg->assign("Still oscillator block number is too large.");
return false;
}
unsigned int x = blockNumber % width;
unsigned int y = blockNumber / width;
for (int curGameModeInt = GameMode_First;
curGameModeInt <= GameMode_Last;
curGameModeInt++) {
GameMode curGameMode = GameMode(curGameModeInt);
if (gameMode != GameMode_Invalid &&
curGameMode != gameMode) {
continue;
}
OscillatorMap *pOscillators =
pLevel->getOscillatorsForWrite(curGameMode);
(*pOscillators)[Block(x, y)].setOn(false);
}
continue;
}
// Notes
if (item[0] == 'Z' || item[0] == 'z') {
if (gameMode != GameMode_Invalid) {
pMsg->assign("Note text must be the same in all game modes.");
return false;
}
int nNote = (item[0] == 'Z' ? 0 : 1);
if (item.size() >= 4) {
Language lang = Language_Invalid;
switch (item[1]) {
case 'g': lang = Language_German; break;
case 'e': lang = Language_English; break;
case 'f': lang = Language_French; break;
case 'i': lang = Language_International; break;
}
if (lang != Language_Invalid) {
if (item[2] == '"') {
int i = 3;
while (i < (int)item.size() &&
item[i] != '"') {
i++;
}
// Allow extra characters after the second quote
if (i < (int)item.size()) {
string noteText = item.substr(3, i-3);
pLevel->setNoteText(nNote, lang, noteText);
continue;
}
}
}
}
}
// Clean up some bad data in notes
if (item[0] == '"') {
continue;
}
/*
if (item.compare(">(0)") == 0) {
pLevel->getSpecialItemsForWrite()->push_back(">(1)");
continue;
}
*/
/*
if (item.compare("#") == 0) {
continue;
}
*/
/*
if (item.compare("r") == 0) {
continue;
}
*/
if (true || gameMode != GameMode_Easy) {
pLevel->getSpecialItemsForWrite()->push_back(item);
}
}
if (gameMode != GameMode_Invalid) {
pMsg->assign("Special items ended while in a game mode.");
return false;
}
if (numMarblesArr[GameMode_Hard] != numMarblesArr[GameMode_Easy]) {
pMsg->assign("Number of marbles is different in different game modes.");
return false;
}
}
if (!parseGrid(infoArr[INFOSURFACES],
pLevel->getGridForWrite(GridType_Surfaces), pMsg)) {
return false;
}
if (!parseGrid(infoArr[INFOPIECES],
pLevel->getGridForWrite(GridType_Pieces), pMsg)) {
return false;
}
if (!parseGrid(infoArr[INFOOBJECTS],
pLevel->getGridForWrite(GridType_Objects), pMsg)) {
return false;
}
/*
{
printf("%d, %d\n", getWidth(), getHeight());
const ByteVec &infoSignal = infoArr[INFOSIGNAL];
int q = 0;
int j = 9999;
for (int i = 0; i < infoSignal.size(); i++) {
printf("%02x ", infoSignal[i]);
if (q == 3) {
j = infoSignal[i] * 2;
}
if (j == 0) {
printf("\n");
q = 0;
j = 9999;
} else {
q++;
j--;
}
}
printf("\n");
}
*/
{
const ByteVec &infoSignal = infoArr[INFOSIGNAL];
int cur = 0;
while (true) {
if (cur + 2 > (int)infoSignal.size()) {
pMsg->assign("Unexpected end of data reading signal sender.");
return false;
}
unsigned int packedSignalSender = getInt2(infoSignal.begin() + cur);
cur += 2;
if (packedSignalSender == 0xffff) {
break;
}
SignalLocation signalSender;
bool senderWithinGrid = true;
if (!unpackSignalLocation(*pLevel,
packedSignalSender,
&signalSender,
&senderWithinGrid,
pMsg)) {
return false;
}
if (senderWithinGrid) {
if (pLevel->getNumRecipients(signalSender) > 0) {
pMsg->assign("Duplicate signal sender.");
return false;
}
}
if (cur + 2 > (int)infoSignal.size()) {
pMsg->assign("Unexpected end of data reading signal recipient count.");
return false;
}
unsigned int signalRecipientCount = getInt2(infoSignal.begin() + cur);
cur += 2;
if (signalRecipientCount <= 0) {
pMsg->assign("Signal sender has no recipients.");
return false;
}
if (cur + (2 * signalRecipientCount) > infoSignal.size()) {
pMsg->assign("Unexpected end of data reading signal recipients.");
return false;
}
for (unsigned int nRecipient = 0;
nRecipient < signalRecipientCount;
nRecipient++) {
unsigned int packedSignalRecipient = getInt2(infoSignal.begin() + cur);
cur += 2;
SignalLocation signalRecipient;
bool recipientWithinGrid = true;
if (!unpackSignalLocation(*pLevel,
packedSignalRecipient,
&signalRecipient,
&recipientWithinGrid,
pMsg)) {
return false;
}
if (senderWithinGrid && recipientWithinGrid) {
pLevel->addRecipient(signalSender, signalRecipient);
}
}
}
if (cur < (int)infoSignal.size()) {
pMsg->assign("Extra data after signals.");
return false;
}
}
pLevel->setInit(true);
return true;
}
bool unparseLevel(const Level &level, ByteVec *pOut, string *pMsg)
{
string msg;
if (!pMsg) {
pMsg = &msg;
}
if (!level.getInit()) {
pMsg->assign("Level not initialized.");
return false;
}
if (level.isEmpty()) {
pOut->clear();
return true;
}
ByteVec out;
int cur = 0;
// General
{
out.resize(out.size() + 2 + 68);
putInt2(out.begin() + cur, INFOGENERAL);
cur += 2;
putInt2(out.begin() + cur, level.getWidth());
cur += 2;
putInt2(out.begin() + cur, level.getHeight());
cur += 2;
if (level.getNumMarbles() > 8) {
pMsg->assign("Too many marbles.");
return false;
}
for (int nMarble = 0; nMarble < 8; nMarble++) {
int x = 0;
int y = 0;
if (nMarble < level.getNumMarbles()) {
const Marble &marble = level.getMarble(nMarble);
x = marble.getX();
y = marble.getY();
}
putInt2(out.begin() + cur, x);
cur += 2;
out[cur++] = 0x00;
out[cur++] = 0x00;
putInt2(out.begin() + cur, y);
cur += 2;
out[cur++] = 0x00;
out[cur++] = 0x00;
}
}
// Signal
{
set<SignalLocation> senders;
level.getSenders(&senders);
int numSignals = 0;
{
set<SignalLocation>::const_iterator iter = senders.begin();
set<SignalLocation>::const_iterator end = senders.end();
for (; iter != end; ++iter) {
const SignalLocation &sender = *iter;
numSignals += level.getNumRecipients(sender);
}
}
int infoSignalSize = 4 * senders.size() + 2 * numSignals + 2;
out.resize(out.size() + 2 + 2 + infoSignalSize);
putInt2(out.begin() + cur, INFOSIGNAL);
cur += 2;
putInt2(out.begin() + cur, infoSignalSize);
cur += 2;
set<SignalLocation>::const_iterator senderIter = senders.begin();
set<SignalLocation>::const_iterator senderEnd = senders.end();
for (; senderIter != senderEnd; ++senderIter) {
const SignalLocation &sender = *senderIter;
unsigned int packedSignalSender = 0;
if (!packSignalLocation(level,
sender,
&packedSignalSender,
pMsg)) {
return false;
}
putInt2(out.begin() + cur, packedSignalSender);
cur += 2;
int numRecipients = level.getNumRecipients(sender);
putInt2(out.begin() + cur, numRecipients);
cur += 2;
for (int nRecipient = 0; nRecipient < numRecipients; nRecipient++) {
const SignalLocation &recipient =
level.getRecipient(sender, nRecipient);
unsigned int packedSignalRecipient = 0;
if (!packSignalLocation(level,
recipient,
&packedSignalRecipient,
pMsg)) {
return false;
}
putInt2(out.begin() + cur, packedSignalRecipient);
cur += 2;
}
}
putInt2(out.begin() + cur, 0xffff);
cur += 2;
}
// Special
vector<string> specialItems;
bool needGameModeArr[GameMode_Count];
{
for (int curGameModeInt = GameMode_First;
curGameModeInt <= GameMode_Last;
curGameModeInt++) {
GameMode curGameMode = GameMode(curGameModeInt);
needGameModeArr[curGameMode] = false;
}
}
bool bMarblesDifferent = false;
{
for (int nMarble = 0; nMarble < level.getNumMarbles(); nMarble++) {
const Marble &marble = level.getMarble(nMarble);
const string &dataHard = marble.getData(GameMode_Hard);
const string &dataEasy = marble.getData(GameMode_Easy);
if (dataHard.compare(dataEasy) != 0) {
bMarblesDifferent = true;
needGameModeArr[GameMode_Hard] = true;
needGameModeArr[GameMode_Easy] = true;
break;
}
}
}
bool bRubberBandsDifferent = false;
{
if (level.getNumRubberBands(GameMode_Hard) !=
level.getNumRubberBands(GameMode_Easy)) {
bRubberBandsDifferent = true;
}
for (int nRubberBand = 0;
(nRubberBand < level.getNumRubberBands(GameMode_Hard) &&
nRubberBand < level.getNumRubberBands(GameMode_Easy));
nRubberBand++) {
const RubberBand &rubberBandHard = level.getRubberBand(GameMode_Hard,
nRubberBand);
const RubberBand &rubberBandEasy = level.getRubberBand(GameMode_Easy,
nRubberBand);
if (rubberBandHard != rubberBandEasy) {
bRubberBandsDifferent = true;
}
}
if ((level.getBlackRubberBandPieceNaturalLength(GameMode_Hard) !=
level.getBlackRubberBandPieceNaturalLength(GameMode_Easy)) ||
(level.getBlackRubberBandPieceForce(GameMode_Hard) !=
level.getBlackRubberBandPieceForce(GameMode_Easy)) ||
(level.getWhiteRubberBandPieceNaturalLength(GameMode_Hard) !=
level.getWhiteRubberBandPieceNaturalLength(GameMode_Easy)) ||
(level.getWhiteRubberBandPieceForce(GameMode_Hard) !=
level.getWhiteRubberBandPieceForce(GameMode_Easy)) ||
(level.getRubberBandObjectNaturalLength(GameMode_Hard) !=
level.getRubberBandObjectNaturalLength(GameMode_Easy)) ||
(level.getRubberBandObjectForce(GameMode_Hard) !=
level.getRubberBandObjectForce(GameMode_Easy))) {
bRubberBandsDifferent = true;
}
if (bRubberBandsDifferent) {
for (int curGameModeInt = GameMode_First;
curGameModeInt <= GameMode_Last;
curGameModeInt++) {
GameMode curGameMode = GameMode(curGameModeInt);
if (level.getNumRubberBands(curGameMode) > 0 ||
level.getBlackRubberBandPieceNaturalLength(curGameMode) != -1 ||
level.getBlackRubberBandPieceForce(curGameMode) != -1 ||
level.getWhiteRubberBandPieceNaturalLength(curGameMode) != -1 ||
level.getWhiteRubberBandPieceForce(curGameMode) != -1 ||
level.getRubberBandObjectNaturalLength(curGameMode) != -1 ||
level.getRubberBandObjectForce(curGameMode) != -1) {
needGameModeArr[curGameMode] = true;
}
}
}
}
bool bResetDifferent = false;
{
if (level.getReset(GameMode_Hard) != level.getReset(GameMode_Easy)) {
bResetDifferent = true;
for (int curGameModeInt = GameMode_First;
curGameModeInt <= GameMode_Last;
curGameModeInt++) {
GameMode curGameMode = GameMode(curGameModeInt);
if (level.getReset(curGameMode)) {
needGameModeArr[curGameMode] = true;
}
}
}
}
bool bForceDifferent = false;
{
if ((level.getFlatForce(GameMode_Hard) !=
level.getFlatForce(GameMode_Easy)) ||
(level.getSlopeForce(GameMode_Hard) !=
level.getSlopeForce(GameMode_Easy)) ||
(level.getFriction(GameMode_Hard) !=
level.getFriction(GameMode_Easy))) {
bForceDifferent = true;
for (int curGameModeInt = GameMode_First;
curGameModeInt <= GameMode_Last;
curGameModeInt++) {
GameMode curGameMode = GameMode(curGameModeInt);
if (level.getFlatForce(curGameMode) != -1 ||
level.getSlopeForce(curGameMode) != -1 ||
level.getFriction(curGameMode) != -1) {
needGameModeArr[curGameMode] = true;
}
}
}
}
bool bOscillatorsDifferent = false;
{
const OscillatorMap &hardOscillators =
level.getOscillators(GameMode_Hard);
const OscillatorMap &easyOscillators =
level.getOscillators(GameMode_Easy);
if (hardOscillators.size() != easyOscillators.size()) {
bOscillatorsDifferent = true;
}
OscillatorMap::const_iterator hardIter = hardOscillators.begin();
OscillatorMap::const_iterator hardEnd = hardOscillators.end();
OscillatorMap::const_iterator easyIter = easyOscillators.begin();
OscillatorMap::const_iterator easyEnd = easyOscillators.end();
for(;
hardIter != hardEnd && easyIter != easyEnd;
++hardIter, ++easyIter) {
const Block &hardBlock = hardIter->first;
const Block &easyBlock = easyIter->first;
const Oscillator &hardOscillator = hardIter->second;
const Oscillator &easyOscillator = easyIter->second;
if (hardBlock != easyBlock ||
hardOscillator != easyOscillator) {
bOscillatorsDifferent = true;
}
}
if (bOscillatorsDifferent) {
for (int curGameModeInt = GameMode_First;
curGameModeInt <= GameMode_Last;
curGameModeInt++) {
GameMode curGameMode = GameMode(curGameModeInt);
const OscillatorMap &oscillators = level.getOscillators(curGameMode);
OscillatorMap::const_iterator iter = oscillators.begin();
OscillatorMap::const_iterator end = oscillators.end();
for (; iter != end; ++iter) {
const Oscillator &oscillator = iter->second;
if (oscillator != Oscillator()) {
needGameModeArr[curGameMode] = true;
}
}
}
}
}
{
// Goal
if (level.getMeditation()) {
specialItems.push_back("GM");
} else {
specialItems.push_back("GN");
}
}
{
// Harmless meditation marbles
if (level.getHarmlessMeditationMarbles()) {
specialItems.push_back("n");
}
}
{
GameMode gameMode = GameMode_Invalid;
bool bFirstPass = true;
bool bDone = false;
while (!bDone) {
if (gameMode == GameMode_Invalid ||
needGameModeArr[gameMode]) {
GameMode realGameMode =
(gameMode == GameMode_Invalid) ? GameMode_Hard : gameMode;
if (gameMode == GameMode_Hard) {
specialItems.push_back("[");
}
if (gameMode == GameMode_Easy) {
specialItems.push_back("{");
}
// Marbles
if ((bMarblesDifferent &&
gameMode != GameMode_Invalid) ||
(!bMarblesDifferent &&
gameMode == GameMode_Invalid &&
bFirstPass)) {
for (int nMarble = 0; nMarble < level.getNumMarbles(); nMarble++) {
const Marble &marble = level.getMarble(nMarble);
unsigned char marbleCode = 0x00;
switch (marble.getMarbleType()) {
case MarbleType_Black: marbleCode = 'B'; break;
case MarbleType_White: marbleCode = 'b'; break;
case MarbleType_Meditation: marbleCode = 'M'; break;
case MarbleType_Horse: marbleCode = 'G'; break;
case MarbleType_Jack: marbleCode = 'K'; break;
case MarbleType_LifeSpitter: marbleCode = 'C'; break;
case MarbleType_DynamiteHolder: marbleCode = 'D'; break;
default:
pMsg->assign("Invalid marble type.");
return false;
}
string item;
item.append(1, 'F');
item.append(1, marbleCode);
item.append(marble.getData(realGameMode));
specialItems.push_back(item);
}
}
// Rubber bands
if ((bRubberBandsDifferent &&
gameMode != GameMode_Invalid) ||
(!bRubberBandsDifferent &&
gameMode == GameMode_Invalid &&
bFirstPass)) {
for (int nRubberBand = 0;
nRubberBand < level.getNumRubberBands(realGameMode);
nRubberBand++) {
const RubberBand &rubberBand =
level.getRubberBand(realGameMode, nRubberBand);
vector<int> numberList1;
vector<int> numberList2;
numberList1.push_back(rubberBand.getNaturalLength());
numberList1.push_back(rubberBand.getForce());
numberList2.push_back(rubberBand.getFirstEndMarble());
if (rubberBand.isSecondEndMarble()) {
numberList1.push_back(-1);
numberList2.push_back(rubberBand.getSecondEndMarble());
} else {
unsigned int x = rubberBand.getSecondEndPieceX();
unsigned int y = rubberBand.getSecondEndPieceY();
if (x > level.getWidth() ||
y > level.getHeight()) {
pMsg->assign("Rubber band block out of range.");
return false;
}
unsigned int blockNumber =
y * level.getWidth() + x;
numberList1.push_back(blockNumber);
}
string strNumberList1;
unparseNumberList(numberList1, &strNumberList1);
string strNumberList2;
unparseNumberList(numberList2, &strNumberList2);
string str;
str.append(1, 'B');
str.append(strNumberList1);
str.append(1, (char)0xf9);
str.append(strNumberList2);
specialItems.push_back(str);
}
{
vector<int> numberList1;
numberList1.push_back(level.getBlackRubberBandPieceNaturalLength(realGameMode));
numberList1.push_back(level.getBlackRubberBandPieceForce(realGameMode));
if (numberList1[0] == -1 && numberList1[1] == -1) {
numberList1.clear();
}
vector<int> numberList2;
numberList2.push_back(level.getWhiteRubberBandPieceNaturalLength(realGameMode));
numberList2.push_back(level.getWhiteRubberBandPieceForce(realGameMode));
if (numberList2[0] == -1 && numberList2[1] == -1) {
numberList2.clear();
}
vector<int> numberList3;
numberList3.push_back(level.getRubberBandObjectNaturalLength(realGameMode));
numberList3.push_back(level.getRubberBandObjectForce(realGameMode));
if (numberList3[0] == -1 && numberList3[1] == -1) {
numberList3.clear();
}
if (!numberList1.empty() ||
!numberList2.empty() ||
!numberList3.empty()) {
string strNumberList1;
unparseNumberList(numberList1, &strNumberList1);
string str;
str.append(1, 'B');
str.append(strNumberList1);
specialItems.push_back(str);
}
if (!numberList2.empty() ||
!numberList3.empty()) {
string strNumberList2;
unparseNumberList(numberList2, &strNumberList2);
string str;
str.append(1, 'B');
str.append(strNumberList2);
specialItems.push_back(str);
}
if (!numberList3.empty()) {
string strNumberList3;
unparseNumberList(numberList3, &strNumberList3);
string str;
str.append(1, 'B');
str.append(strNumberList3);
specialItems.push_back(str);
}
}
}
// Reset
if ((bResetDifferent &&
gameMode != GameMode_Invalid) ||
(!bResetDifferent &&
gameMode == GameMode_Invalid &&
!bFirstPass)) {
if (level.getReset(realGameMode)) {
specialItems.push_back("s");
}
}
// Force
if ((bForceDifferent &&
gameMode != GameMode_Invalid) ||
(!bForceDifferent &&
gameMode == GameMode_Invalid &&
!bFirstPass)) {
vector<int> numberList;
numberList.resize(3);
numberList[0] = level.getFlatForce(realGameMode);
numberList[1] = level.getFriction(realGameMode);
numberList[2] = level.getSlopeForce(realGameMode);
int sz = 3;
while (sz > 0 && numberList[sz - 1] == -1) {
sz--;
}
numberList.resize(sz);
if (sz > 0) {
string strNumberList;
unparseNumberList(numberList, &strNumberList);
string str;
str.append(1, 'g');
str.append(strNumberList);
specialItems.push_back(str);
}
}
// Oscillators
if ((bOscillatorsDifferent &&
gameMode != GameMode_Invalid) ||
(!bOscillatorsDifferent &&
gameMode == GameMode_Invalid &&
!bFirstPass)) {
const OscillatorMap &oscillators =
level.getOscillators(realGameMode);
OscillatorMap::const_iterator iter = oscillators.begin();
OscillatorMap::const_iterator end = oscillators.end();
for (; iter != end; ++iter) {
const Block &block = iter->first;
const Oscillator &oscillator = iter->second;
unsigned int x = block.getX();
unsigned int y = block.getY();
if (x > level.getWidth() || y > level.getHeight()) {
pMsg->assign("Oscillator block out of range.");
return false;
}
unsigned int blockNumber =
y * level.getWidth() + x;
if (oscillator.getPeriod() != 0) {
vector<int> numberList;
numberList.push_back(blockNumber);
numberList.push_back(oscillator.getPeriod());
string strNumberList;
unparseNumberList(numberList, &strNumberList);
string str;
str.append(1, '~');
str.append(strNumberList);
specialItems.push_back(str);
}
}
}
// Still oscillators
if ((bOscillatorsDifferent &&
gameMode != GameMode_Invalid) ||
(!bOscillatorsDifferent &&
gameMode == GameMode_Invalid &&
!bFirstPass)) {
const OscillatorMap &oscillators =
level.getOscillators(realGameMode);
OscillatorMap::const_iterator iter = oscillators.begin();
OscillatorMap::const_iterator end = oscillators.end();
for (; iter != end; ++iter) {
const Block &block = iter->first;
const Oscillator &oscillator = iter->second;
unsigned int x = block.getX();
unsigned int y = block.getY();
if (x > level.getWidth() || y > level.getHeight()) {
pMsg->assign("Oscillator block out of range.");
return false;
}
unsigned int blockNumber =
y * level.getWidth() + x;
if (!oscillator.getOn()) {
vector<int> numberList;
numberList.push_back(blockNumber);
string strNumberList;
unparseNumberList(numberList, &strNumberList);
string str;
str.append(1, 'O');
str.append(strNumberList);
specialItems.push_back(str);
}
}
}
if (gameMode == GameMode_Hard) {
specialItems.push_back("]");
}
if (gameMode == GameMode_Easy) {
specialItems.push_back("}");
}
}
switch (gameMode) {
case GameMode_Invalid:
if (bFirstPass) {
bFirstPass = false;
gameMode = GameMode_Hard;
} else {
bDone = true;
}
break;
case GameMode_Hard:
gameMode = GameMode_Easy;
break;
case GameMode_Easy:
gameMode = GameMode_Invalid;
break;
default:
;
}
}
}
{
// Rubber band object marbles
int nMarbleBlack = level.getBlackRubberBandObjectMarble();
if (nMarbleBlack == -1) {
nMarbleBlack = 1;
}
int nMarbleWhite = level.getWhiteRubberBandObjectMarble();
if (nMarbleWhite == -1) {
nMarbleWhite = 0;
}
if (nMarbleBlack != 1 || nMarbleWhite != 0) {
vector<int> numberList;
numberList.resize(2);
numberList[0] = nMarbleBlack;
numberList[1] = nMarbleWhite;
string strNumberList;
unparseNumberList(numberList, &strNumberList);
string str;
str.append(1, 'N');
str.append(strNumberList);
specialItems.push_back(str);
}
}
{
// Require magic piece
if (level.getRequireMagicPiece()) {
specialItems.push_back("!");
}
}
{
// Scrolling
if (level.getScrolling()) {
specialItems.push_back("f");
}
}
{
// Walk-through puzzle
if (level.getWalkThroughPuzzle()) {
specialItems.push_back("j");
}
}
{
// Scramble items
if (level.getNumScrambleItems() > 0) {
vector<int> numberList;
for (int nScrambleItem = 0;
nScrambleItem < level.getNumScrambleItems();
nScrambleItem++) {
const ScrambleItem &scrambleItem =
level.getScrambleItem(nScrambleItem);
unsigned int x = scrambleItem.getX();
unsigned int y = scrambleItem.getY();
if (x > level.getWidth() ||
y > level.getHeight()) {
pMsg->assign("Scramble item block out of range.");
return false;
}
unsigned int blockNumber =
y * level.getWidth() + x;
Direction dir = scrambleItem.getDir();
int dirInt = -1;
switch (dir) {
case Direction_Up: dirInt = 0; break;
case Direction_Down: dirInt = 1; break;
case Direction_Left: dirInt = 2; break;
case Direction_Right: dirInt = 3; break;
default:
pMsg->assign("Invalid scramble item direction.");
return false;
}
numberList.push_back(blockNumber);
numberList.push_back(dirInt);
}
string strNumberList;
unparseNumberList(numberList, &strNumberList);
string str;
str.append(1, 'P');
str.append(strNumberList);
specialItems.push_back(str);
}
}
{
// Lasers
for (int nLaser = 0; nLaser < 3; nLaser++) {
const Laser &laser = level.getLaser(nLaser);
if (laser != s_defaultLasers[nLaser]) {
vector<int> numberList;
Direction dir = laser.getDir();
int dirInt = -1;
switch (dir) {
case Direction_Up: dirInt = 0; break;
case Direction_Down: dirInt = 1; break;
case Direction_Left: dirInt = 2; break;
case Direction_Right: dirInt = 3; break;
default:
pMsg->assign("Invalid laser direction.");
return false;
}
numberList.push_back(nLaser + 1);
numberList.push_back(dirInt);
string strNumberList;
unparseNumberList(numberList, &strNumberList);
string str;
str.append(1, 'L');
str.append(strNumberList);
if (laser.getOn()) {
str.append(1, (char)0xf9);
}
specialItems.push_back(str);
}
}
}
{
// Stuff we don't know about yet
for (int i = 0; i < (int)level.getSpecialItems().size(); i++) {
specialItems.push_back(level.getSpecialItems()[i]);
}
}
{
// Notes
for (int nNote = 0; nNote < 2; nNote++) {
for (int nLang = Language_First;
nLang <= Language_Last;
nLang++) {
Language lang = (Language)nLang;
const string &noteText = level.getNoteText(nNote, lang);
if (!noteText.empty()) {
unsigned char noteCode = 0x00;
switch (nNote) {
case 0: noteCode = 'Z'; break;
case 1: noteCode = 'z'; break;
}
unsigned char langCode = 0x00;
switch(lang) {
case Language_German: langCode = 'g'; break;
case Language_English: langCode = 'e'; break;
case Language_French: langCode = 'f'; break;
default:
pMsg->assign("Invalid language.");
return false;
}
string noteItem;
noteItem.append(1, noteCode);
noteItem.append(1, langCode);
noteItem.append(1, '"');
noteItem.append(noteText);
noteItem.append(1, '"');
specialItems.push_back(noteItem);
}
}
}
}
/*
printf("---\n");
{
for (int nItem = 0; nItem < specialItems.size(); nItem++) {
printf(" %s\n", specialItems[nItem].c_str());
}
}
*/
int infoSpecialSize = 0;
int nItem;
for (nItem = 0; nItem < (int)specialItems.size(); nItem++) {
if (nItem != 0) {
infoSpecialSize += 1;
}
infoSpecialSize += specialItems[nItem].size();
}
infoSpecialSize += 1;
out.resize(out.size() + 2 + 2 + infoSpecialSize);
putInt2(out.begin() + cur, INFOSPECIAL);
cur += 2;
putInt2(out.begin() + cur, infoSpecialSize);
cur += 2;
for (nItem = 0; nItem < (int)specialItems.size(); nItem++) {
if (nItem != 0) {
out[cur++] = 0x20;
}
copy(specialItems[nItem].begin(),
specialItems[nItem].end(),
out.begin() + cur);
cur += specialItems[nItem].size();
}
out[cur++] = 0x00;
// Surfaces
ByteVec unparsedSurfaces;
if (!unparseGrid(level.getGrid(GridType_Surfaces),
&unparsedSurfaces, pMsg)) {
return false;
}
out.resize(out.size() + 2 + 2 + unparsedSurfaces.size());
putInt2(out.begin() + cur, INFOSURFACES);
cur += 2;
putInt2(out.begin() + cur, unparsedSurfaces.size());
cur += 2;
copy(unparsedSurfaces.begin(), unparsedSurfaces.end(), out.begin() + cur);
cur += unparsedSurfaces.size();
// Pieces
ByteVec unparsedPieces;
if (!unparseGrid(level.getGrid(GridType_Pieces),
&unparsedPieces, pMsg)) {
return false;
}
out.resize(out.size() + 2 + 2 + unparsedPieces.size());
putInt2(out.begin() + cur, INFOPIECES);
cur += 2;
putInt2(out.begin() + cur, unparsedPieces.size());
cur += 2;
copy(unparsedPieces.begin(), unparsedPieces.end(), out.begin() + cur);
cur += unparsedPieces.size();
// Objects
ByteVec unparsedObjects;
if (!unparseGrid(level.getGrid(GridType_Objects),
&unparsedObjects, pMsg)) {
return false;
}
out.resize(out.size() + 2 + 2 + unparsedObjects.size());
putInt2(out.begin() + cur, INFOOBJECTS);
cur += 2;
putInt2(out.begin() + cur, unparsedObjects.size());
cur += 2;
copy(unparsedObjects.begin(), unparsedObjects.end(), out.begin() + cur);
cur += unparsedObjects.size();
// End
out.resize(out.size() + 2);
putInt2(out.begin() + cur, INFOEND);
cur += 2;
if (cur != (int)out.size()) {
pMsg->assign("Error computing size of unparsed level.");
return false;
}
pOut->clear();
pOut->resize(out.size());
copy(out.begin(), out.end(), pOut->begin());
return true;
}
}