mirror of
https://github.com/libretro/scummvm.git
synced 2025-02-23 20:51:14 +00:00
MADS: Initial implementation of MSurface class and dependant classes
This commit is contained in:
parent
ece3e9a220
commit
0e46c809d1
185
engines/mads/compression.cpp
Normal file
185
engines/mads/compression.cpp
Normal file
@ -0,0 +1,185 @@
|
||||
/* 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 2
|
||||
* of the License, or (at your option) any later version.
|
||||
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "mads/compression.h"
|
||||
|
||||
namespace MADS {
|
||||
|
||||
const char *const madsPackString = "MADSPACK";
|
||||
|
||||
bool MadsPack::isCompressed(Common::SeekableReadStream *stream) {
|
||||
// Check whether the passed stream is packed
|
||||
|
||||
char tempBuffer[8];
|
||||
stream->seek(0);
|
||||
if (stream->read(tempBuffer, 8) == 8) {
|
||||
if (!strncmp(tempBuffer, madsPackString, 8))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
MadsPack::MadsPack(Common::SeekableReadStream *stream) {
|
||||
initialise(stream);
|
||||
}
|
||||
|
||||
MadsPack::MadsPack(const Common::String &resourceName, MADSEngine *vm) {
|
||||
Common::SeekableReadStream *stream = vm->_resources->get(resourceName);
|
||||
initialise(stream);
|
||||
vm->_resources->toss(resourceName);
|
||||
}
|
||||
|
||||
void MadsPack::initialise(Common::SeekableReadStream *stream) {
|
||||
if (!MadsPack::isCompressed(stream))
|
||||
error("Attempted to decompress a resource that was not MadsPacked");
|
||||
|
||||
stream->seek(14);
|
||||
_count = stream->readUint16LE();
|
||||
_items = new MadsPackEntry[_count];
|
||||
|
||||
byte *headerData = new byte[0xA0];
|
||||
byte *header = headerData;
|
||||
stream->read(headerData, 0xA0);
|
||||
|
||||
for (int i = 0; i < _count; ++i, header += 10) {
|
||||
// Get header data
|
||||
_items[i].hash = READ_LE_UINT16(header);
|
||||
_items[i].size = READ_LE_UINT32(header + 2);
|
||||
_items[i].compressedSize = READ_LE_UINT32(header + 6);
|
||||
|
||||
_items[i].data = new byte[_items[i].size];
|
||||
if (_items[i].size == _items[i].compressedSize) {
|
||||
// Entry isn't compressed
|
||||
stream->read(_items[i].data, _items[i].size);
|
||||
} else {
|
||||
// Decompress the entry
|
||||
byte *compressedData = new byte[_items[i].compressedSize];
|
||||
stream->read(compressedData, _items[i].compressedSize);
|
||||
|
||||
FabDecompressor fab;
|
||||
fab.decompress(compressedData, _items[i].compressedSize, _items[i].data, _items[i].size);
|
||||
delete[] compressedData;
|
||||
}
|
||||
}
|
||||
|
||||
delete[] headerData;
|
||||
_dataOffset = stream->pos();
|
||||
}
|
||||
|
||||
MadsPack::~MadsPack() {
|
||||
for (int i = 0; i < _count; ++i)
|
||||
delete[] _items[i].data;
|
||||
delete[] _items;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
const char *FabInputExceededError = "FabDecompressor - Passed end of input buffer during decompression";
|
||||
const char *FabOutputExceededError = "FabDecompressor - Decompressed data exceeded specified size";
|
||||
|
||||
void FabDecompressor::decompress(const byte *srcData, int srcSize, byte *destData, int destSize) {
|
||||
byte copyLen, copyOfsShift, copyOfsMask, copyLenMask;
|
||||
unsigned long copyOfs;
|
||||
byte *destP;
|
||||
|
||||
// Validate that the data starts with the FAB header
|
||||
if (strncmp((const char *)srcData, "FAB", 3) != 0)
|
||||
error("FabDecompressor - Invalid compressed data");
|
||||
|
||||
int shiftVal = srcData[3];
|
||||
if ((shiftVal < 10) || (shiftVal > 13))
|
||||
error("FabDecompressor - Invalid shift start");
|
||||
|
||||
copyOfsShift = 16 - shiftVal;
|
||||
copyOfsMask = 0xFF << (shiftVal - 8);
|
||||
copyLenMask = (1 << copyOfsShift) - 1;
|
||||
copyOfs = 0xFFFF0000;
|
||||
destP = destData;
|
||||
|
||||
// Initialise data fields
|
||||
_srcData = srcData;
|
||||
_srcP = _srcData + 6;
|
||||
_srcSize = srcSize;
|
||||
_bitsLeft = 16;
|
||||
_bitBuffer = READ_LE_UINT16(srcData + 4);
|
||||
|
||||
for (;;) {
|
||||
if (getBit() == 0) {
|
||||
if (getBit() == 0) {
|
||||
copyLen = ((getBit() << 1) | getBit()) + 2;
|
||||
copyOfs = *_srcP++ | 0xFFFFFF00;
|
||||
} else {
|
||||
copyOfs = (((_srcP[1] >> copyOfsShift) | copyOfsMask) << 8) | _srcP[0];
|
||||
copyLen = _srcP[1] & copyLenMask;
|
||||
_srcP += 2;
|
||||
if (copyLen == 0) {
|
||||
copyLen = *_srcP++;
|
||||
if (copyLen == 0)
|
||||
break;
|
||||
else if (copyLen == 1)
|
||||
continue;
|
||||
else
|
||||
copyLen++;
|
||||
} else {
|
||||
copyLen += 2;
|
||||
}
|
||||
copyOfs |= 0xFFFF0000;
|
||||
}
|
||||
while (copyLen-- > 0) {
|
||||
if (destP - destData == destSize)
|
||||
error(FabOutputExceededError);
|
||||
|
||||
*destP = destP[(signed int)copyOfs];
|
||||
destP++;
|
||||
}
|
||||
} else {
|
||||
if (_srcP - srcData == srcSize)
|
||||
error(FabInputExceededError);
|
||||
if (destP - destData == destSize)
|
||||
error(FabOutputExceededError);
|
||||
|
||||
*destP++ = *_srcP++;
|
||||
}
|
||||
}
|
||||
|
||||
if (destP - destData != destSize)
|
||||
error("FabDecompressor - Decompressed data does not match header decompressed size");
|
||||
}
|
||||
|
||||
int FabDecompressor::getBit() {
|
||||
_bitsLeft--;
|
||||
if (_bitsLeft == 0) {
|
||||
if (_srcP - _srcData == _srcSize)
|
||||
error(FabInputExceededError);
|
||||
|
||||
_bitBuffer = (READ_LE_UINT16(_srcP) << 1) | (_bitBuffer & 1);
|
||||
_srcP += 2;
|
||||
_bitsLeft = 16;
|
||||
}
|
||||
|
||||
int bit = _bitBuffer & 1;
|
||||
_bitBuffer >>= 1;
|
||||
return bit;
|
||||
}
|
||||
|
||||
} // End of namespace M4
|
80
engines/mads/compression.h
Normal file
80
engines/mads/compression.h
Normal file
@ -0,0 +1,80 @@
|
||||
/* 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 2
|
||||
* of the License, or (at your option) any later version.
|
||||
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef MADS_COMPRESSION_H
|
||||
#define MADS_COMPRESSION_H
|
||||
|
||||
#include "common/scummsys.h"
|
||||
#include "common/endian.h"
|
||||
#include "common/memstream.h"
|
||||
#include "common/stream.h"
|
||||
|
||||
#include "mads/mads.h"
|
||||
|
||||
namespace MADS {
|
||||
|
||||
struct MadsPackEntry {
|
||||
public:
|
||||
uint16 hash;
|
||||
uint32 size;
|
||||
uint32 compressedSize;
|
||||
byte *data;
|
||||
};
|
||||
|
||||
class MadsPack {
|
||||
private:
|
||||
MadsPackEntry *_items;
|
||||
int _count;
|
||||
int _dataOffset;
|
||||
|
||||
void initialise(Common::SeekableReadStream *stream);
|
||||
public:
|
||||
static bool isCompressed(Common::SeekableReadStream *stream);
|
||||
MadsPack(Common::SeekableReadStream *stream);
|
||||
MadsPack(const Common::String &resourceName, MADSEngine *_vm);
|
||||
~MadsPack();
|
||||
|
||||
int getCount() const { return _count; }
|
||||
MadsPackEntry &getItem(int index) const { return _items[index]; }
|
||||
MadsPackEntry &operator[](int index) const { return _items[index]; }
|
||||
Common::MemoryReadStream *getItemStream(int index) {
|
||||
return new Common::MemoryReadStream(_items[index].data, _items[index].size,
|
||||
DisposeAfterUse::NO);
|
||||
}
|
||||
int getDataOffset() const { return _dataOffset; }
|
||||
};
|
||||
|
||||
class FabDecompressor {
|
||||
private:
|
||||
int _bitsLeft;
|
||||
uint32 _bitBuffer;
|
||||
const byte *_srcData, *_srcP;
|
||||
int _srcSize;
|
||||
|
||||
int getBit();
|
||||
public:
|
||||
void decompress(const byte *srcData, int srcSize, byte *destData, int destSize);
|
||||
};
|
||||
|
||||
} // End of namespace MADS
|
||||
|
||||
#endif
|
@ -38,8 +38,19 @@ namespace MADS {
|
||||
|
||||
struct MADSGameDescription {
|
||||
ADGameDescription desc;
|
||||
|
||||
int gameID;
|
||||
uint32 features;
|
||||
};
|
||||
|
||||
uint32 MADSEngine::getGameID() const {
|
||||
return _gameDescription->gameID;
|
||||
}
|
||||
|
||||
uint32 MADSEngine::getGameFeatures() const {
|
||||
return _gameDescription->gameID;
|
||||
}
|
||||
|
||||
uint32 MADSEngine::getFeatures() const {
|
||||
return _gameDescription->desc.flags;
|
||||
}
|
||||
@ -52,10 +63,6 @@ Common::Platform MADSEngine::getPlatform() const {
|
||||
return _gameDescription->desc.platform;
|
||||
}
|
||||
|
||||
bool MADSEngine::getIsDemo() const {
|
||||
return _gameDescription->desc.flags & ADGF_DEMO;
|
||||
}
|
||||
|
||||
} // End of namespace MADS
|
||||
|
||||
static const PlainGameDescriptor MADSGames[] = {
|
||||
@ -165,7 +172,7 @@ SaveStateDescriptor MADSMetaEngine::querySaveMetaInfos(const char *target, int s
|
||||
|
||||
|
||||
#if PLUGIN_ENABLED_DYNAMIC(MADS)
|
||||
REGISTER_PLUGIN_DYNAMIC(MADS, PLUGIN_TYPE_ENGINE, MADSMetaEngine);
|
||||
REGISTER_PLUGIN_DYNAMIC(MADS, PLUGIN_TYPE_ENGINE, MADSMetaEngine);
|
||||
#else
|
||||
REGISTER_PLUGIN_STATIC(MADS, PLUGIN_TYPE_ENGINE, MADSMetaEngine);
|
||||
REGISTER_PLUGIN_STATIC(MADS, PLUGIN_TYPE_ENGINE, MADSMetaEngine);
|
||||
#endif
|
||||
|
34
engines/mads/events.cpp
Normal file
34
engines/mads/events.cpp
Normal file
@ -0,0 +1,34 @@
|
||||
/* 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 2
|
||||
* of the License, or (at your option) any later version.
|
||||
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "common/scummsys.h"
|
||||
#include "common/events.h"
|
||||
#include "mads/mads.h"
|
||||
#include "mads/events.h"
|
||||
|
||||
namespace MADS {
|
||||
|
||||
EventsManager::EventsManager(MADSEngine *vm) {
|
||||
_vm = vm;
|
||||
}
|
||||
|
||||
} // End of namespace MADS
|
43
engines/mads/events.h
Normal file
43
engines/mads/events.h
Normal file
@ -0,0 +1,43 @@
|
||||
/* 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 2
|
||||
* of the License, or (at your option) any later version.
|
||||
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef MADS_EVENTS_H
|
||||
#define MADS_EVENTS_H
|
||||
|
||||
#include "common/scummsys.h"
|
||||
|
||||
namespace MADS {
|
||||
|
||||
class MADSEngine;
|
||||
|
||||
class EventsManager {
|
||||
private:
|
||||
MADSEngine *_vm;
|
||||
public:
|
||||
EventsManager(MADSEngine *vm);
|
||||
|
||||
void handleEvents() { /* TODO */ }
|
||||
};
|
||||
|
||||
} // End of namespace MADS
|
||||
|
||||
#endif /* MADS_EVENTS_H */
|
@ -20,35 +20,57 @@
|
||||
*
|
||||
*/
|
||||
|
||||
#include "mads/mads.h"
|
||||
#include "mads/sound.h"
|
||||
#include "common/scummsys.h"
|
||||
#include "common/config-manager.h"
|
||||
#include "common/debug-channels.h"
|
||||
#include "engines/util.h"
|
||||
#include "common/events.h"
|
||||
#include "engines/util.h"
|
||||
#include "mads/mads.h"
|
||||
#include "mads/resources.h"
|
||||
#include "mads/sound.h"
|
||||
#include "mads/msurface.h"
|
||||
#include "mads/msprite.h"
|
||||
|
||||
namespace MADS {
|
||||
|
||||
MADSEngine *g_vm;
|
||||
|
||||
MADSEngine::MADSEngine(OSystem *syst, const MADSGameDescription *gameDesc) :
|
||||
Engine(syst), _randomSource("MADS") {
|
||||
DebugMan.addDebugChannel(kDebugPath, "Path", "Pathfinding debug level");
|
||||
DebugMan.addDebugChannel(kDebugScripts, "scripts", "Game scripts");
|
||||
_gameDescription(gameDesc), Engine(syst), _randomSource("MADS") {
|
||||
|
||||
// Initialise fields
|
||||
_easyMouse = true;
|
||||
_invObjectStill = false;
|
||||
_textWindowStill = false;
|
||||
_palette = nullptr;
|
||||
_resources = nullptr;
|
||||
_screen = nullptr;
|
||||
_sound = nullptr;
|
||||
}
|
||||
|
||||
MADSEngine::~MADSEngine() {
|
||||
delete _events;
|
||||
delete _resources;
|
||||
delete _screen;
|
||||
delete _sound;
|
||||
}
|
||||
|
||||
void MADSEngine::initialise() {
|
||||
_soundManager.setVm(this, _mixer);
|
||||
// Set up debug channels
|
||||
DebugMan.addDebugChannel(kDebugPath, "Path", "Pathfinding debug level");
|
||||
DebugMan.addDebugChannel(kDebugScripts, "scripts", "Game scripts");
|
||||
|
||||
// Initial sub-system engine references
|
||||
MSurface::setVm(this);
|
||||
MSprite::setVm(this);
|
||||
|
||||
_events = new EventsManager(this);
|
||||
_resources = new ResourcesManager(this);
|
||||
_screen = MSurface::init();
|
||||
_sound = new SoundManager(this, _mixer);
|
||||
}
|
||||
|
||||
Common::Error MADSEngine::run() {
|
||||
initGraphics(320, 200, false);
|
||||
initialise();
|
||||
_soundManager.test();
|
||||
|
||||
Common::Event e;
|
||||
while (!shouldQuit()) {
|
||||
|
@ -30,6 +30,9 @@
|
||||
#include "common/util.h"
|
||||
#include "engines/engine.h"
|
||||
#include "graphics/surface.h"
|
||||
#include "mads/events.h"
|
||||
#include "mads/msurface.h"
|
||||
#include "mads/resources.h"
|
||||
#include "mads/sound.h"
|
||||
|
||||
/**
|
||||
@ -46,13 +49,22 @@ namespace MADS {
|
||||
#define DEBUG_INTERMEDIATE 2
|
||||
#define DEBUG_DETAILED 3
|
||||
|
||||
#define MAX_RESOLVE 1000
|
||||
|
||||
enum MADSDebugChannels {
|
||||
kDebugPath = 1 << 0,
|
||||
kDebugScripts = 1 << 1
|
||||
};
|
||||
|
||||
enum {
|
||||
GType_RexNebular = 0,
|
||||
GType_DragonSphere = 1,
|
||||
GType_Phantom = 2,
|
||||
GType_Riddle = 3
|
||||
};
|
||||
|
||||
enum {
|
||||
GF_MADS = 1 << 0,
|
||||
GF_M4 = 1 << 1
|
||||
};
|
||||
|
||||
struct MADSGameDescription;
|
||||
|
||||
@ -61,13 +73,26 @@ class MADSEngine : public Engine {
|
||||
private:
|
||||
const MADSGameDescription *_gameDescription;
|
||||
Common::RandomSource _randomSource;
|
||||
SoundManager _soundManager;
|
||||
|
||||
bool _easyMouse;
|
||||
bool _invObjectStill;
|
||||
bool _textWindowStill;
|
||||
|
||||
/**
|
||||
* Handles basic initialisation
|
||||
*/
|
||||
void initialise();
|
||||
protected:
|
||||
// Engine APIs
|
||||
virtual Common::Error run();
|
||||
virtual bool hasFeature(EngineFeature f) const;
|
||||
public:
|
||||
EventsManager *_events;
|
||||
Palette *_palette;
|
||||
ResourcesManager *_resources;
|
||||
MSurface *_screen;
|
||||
SoundManager *_sound;
|
||||
|
||||
public:
|
||||
MADSEngine(OSystem *syst, const MADSGameDescription *gameDesc);
|
||||
virtual ~MADSEngine();
|
||||
@ -76,7 +101,8 @@ public:
|
||||
Common::Language getLanguage() const;
|
||||
Common::Platform getPlatform() const;
|
||||
uint16 getVersion() const;
|
||||
bool getIsDemo() const;
|
||||
uint32 getGameID() const;
|
||||
uint32 getGameFeatures() const;
|
||||
|
||||
int getRandomNumber(int maxNumber);
|
||||
};
|
||||
|
@ -1,10 +1,17 @@
|
||||
MODULE := engines/mads
|
||||
|
||||
MODULE_OBJS := \
|
||||
compression.o \
|
||||
detection.o \
|
||||
events.o \
|
||||
mads.o \
|
||||
msprite.o \
|
||||
msurface.o \
|
||||
palette.o \
|
||||
resources.o \
|
||||
sound.o \
|
||||
sound_nebular.o \
|
||||
mads.o
|
||||
sprite.o
|
||||
|
||||
# This module can be built as a plugin
|
||||
ifeq ($(ENABLE_MADS), DYNAMIC_PLUGIN)
|
||||
|
207
engines/mads/msprite.cpp
Normal file
207
engines/mads/msprite.cpp
Normal file
@ -0,0 +1,207 @@
|
||||
/* 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 2
|
||||
* of the License, or (at your option) any later version.
|
||||
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "common/scummsys.h"
|
||||
#include "engines/util.h"
|
||||
#include "graphics/palette.h"
|
||||
#include "mads/mads.h"
|
||||
#include "mads/msurface.h"
|
||||
#include "mads/msprite.h"
|
||||
|
||||
namespace MADS {
|
||||
|
||||
enum {
|
||||
kEndOfLine = 0,
|
||||
kEndOfSprite = 1,
|
||||
kMarker = 2
|
||||
};
|
||||
|
||||
MADSEngine *MSprite::_vm;
|
||||
|
||||
MSprite *MSprite::init(MSurface &s) {
|
||||
if (_vm->getGameFeatures() & GF_MADS) {
|
||||
return new MSpriteMADS(s);
|
||||
} else {
|
||||
return new MSpriteM4(s);
|
||||
}
|
||||
}
|
||||
|
||||
MSprite *MSprite::init(Common::SeekableReadStream *source, const Common::Point &offset,
|
||||
int widthVal, int heightVal, bool decodeRle, uint8 encodingVal) {
|
||||
|
||||
if (_vm->getGameFeatures() & GF_MADS) {
|
||||
return new MSpriteMADS(source, offset, widthVal, heightVal, decodeRle, encodingVal);
|
||||
} else {
|
||||
return new MSpriteM4(source, offset, widthVal, heightVal, decodeRle, encodingVal);
|
||||
}
|
||||
}
|
||||
|
||||
MSprite::MSprite(MSurface &s): _surface(s) {
|
||||
_encoding = 0;
|
||||
}
|
||||
|
||||
MSprite::MSprite(Common::SeekableReadStream *source, const Common::Point &offset,
|
||||
int widthVal, int heightVal, bool decodeRle, uint8 encodingVal)
|
||||
: _surface(*MSurface::init(widthVal, heightVal)),
|
||||
_encoding(encodingVal), _offset(offset) {
|
||||
|
||||
// Load the sprite data
|
||||
load(source, widthVal, heightVal, decodeRle);
|
||||
}
|
||||
|
||||
MSprite::~MSprite() {
|
||||
}
|
||||
|
||||
/*------------------------------------------------------------------------*/
|
||||
|
||||
void MSpriteMADS::load(Common::SeekableReadStream *stream, int widthVal, int heightVal,
|
||||
bool decodeRle) {
|
||||
loadSprite(stream);
|
||||
}
|
||||
|
||||
// TODO: The sprite outlines (pixel value 0xFD) are not shown
|
||||
void MSpriteMADS::loadSprite(Common::SeekableReadStream *source) {
|
||||
byte *outp, *lineStart;
|
||||
bool newLine = false;
|
||||
|
||||
outp = _surface.getData();
|
||||
lineStart = _surface.getData();
|
||||
|
||||
while (1) {
|
||||
byte cmd1, cmd2, count, pixel;
|
||||
|
||||
if (newLine) {
|
||||
outp = lineStart + _surface.w;
|
||||
lineStart = outp;
|
||||
newLine = false;
|
||||
}
|
||||
|
||||
cmd1 = source->readByte();
|
||||
|
||||
if (cmd1 == 0xFC)
|
||||
break;
|
||||
else if (cmd1 == 0xFF)
|
||||
newLine = true;
|
||||
else if (cmd1 == 0xFD) {
|
||||
while (!newLine) {
|
||||
count = source->readByte();
|
||||
if (count == 0xFF) {
|
||||
newLine = true;
|
||||
} else {
|
||||
pixel = source->readByte();
|
||||
while (count--)
|
||||
*outp++ = (pixel == 0xFD) ? 0 : pixel;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
while (!newLine) {
|
||||
cmd2 = source->readByte();
|
||||
if (cmd2 == 0xFF) {
|
||||
newLine = true;
|
||||
} else if (cmd2 == 0xFE) {
|
||||
count = source->readByte();
|
||||
pixel = source->readByte();
|
||||
while (count--)
|
||||
*outp++ = (pixel == 0xFD) ? 0 : pixel;
|
||||
} else {
|
||||
*outp++ = (cmd2 == 0xFD) ? 0 : cmd2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*------------------------------------------------------------------------*/
|
||||
|
||||
void MSpriteM4::load(Common::SeekableReadStream *stream, int widthVal, int heightVal,
|
||||
bool decodeRle) {
|
||||
if (decodeRle) {
|
||||
loadRle(stream);
|
||||
} else {
|
||||
// Raw sprite data, load directly
|
||||
byte *dst = _surface.getData();
|
||||
stream->read(dst, widthVal * heightVal);
|
||||
}
|
||||
}
|
||||
|
||||
void MSpriteM4::loadRle(Common::SeekableReadStream* rleData) {
|
||||
byte *dst = _surface.getData();
|
||||
for (;;) {
|
||||
byte len = rleData->readByte();
|
||||
if (len == 0) {
|
||||
len = rleData->readByte();
|
||||
if (len <= kMarker) {
|
||||
if (len == kEndOfSprite)
|
||||
break;
|
||||
} else {
|
||||
while (len--) {
|
||||
*dst++ = rleData->readByte();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
byte value = rleData->readByte();
|
||||
while (len--)
|
||||
*dst++ = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MSpriteM4::loadDeltaRle(Common::SeekableReadStream* rleData, int destX, int destY) {
|
||||
int lineNum = 0;
|
||||
byte *dst = _surface.getBasePtr(destX, destY);
|
||||
|
||||
for (;;) {
|
||||
byte len = rleData->readByte();
|
||||
if (len == 0) {
|
||||
len = rleData->readByte();
|
||||
if (len <= kMarker) {
|
||||
if (len == kEndOfLine) {
|
||||
dst = _surface.getBasePtr(destX, destY + lineNum);
|
||||
lineNum++;
|
||||
} else if (len == kEndOfSprite)
|
||||
break;
|
||||
} else {
|
||||
while (len--) {
|
||||
byte pixel = rleData->readByte();
|
||||
if (pixel == 0)
|
||||
dst++;
|
||||
else
|
||||
*dst++ = pixel;
|
||||
/* NOTE: The change below behaved differently than the old code,
|
||||
so I put the old code back in again above.
|
||||
If the pixel value is 0, nothing should be written to the
|
||||
output buffer, since 0 means transparent. */
|
||||
//*dst++ = (pixel == 0xFD) ? 0 : pixel;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
byte value = rleData->readByte();
|
||||
if (value == 0)
|
||||
dst += len;
|
||||
else
|
||||
while (len--)
|
||||
*dst++ = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // End of namespace MADS
|
152
engines/mads/msprite.h
Normal file
152
engines/mads/msprite.h
Normal file
@ -0,0 +1,152 @@
|
||||
/* 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 2
|
||||
* of the License, or (at your option) any later version.
|
||||
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef MADS_MSPRITE_H
|
||||
#define MADS_MSPRITE_H
|
||||
|
||||
#include "common/scummsys.h"
|
||||
#include "mads/msurface.h"
|
||||
|
||||
namespace MADS {
|
||||
|
||||
class MADSEngine;
|
||||
|
||||
struct BGR8 {
|
||||
uint8 b, g, r;
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
int32 x; // x position relative to GrBuff(0, 0)
|
||||
int32 y; // y position relative to GrBuff(0, 0)
|
||||
int32 scale_x; // x scale factor (can be negative for reverse draw)
|
||||
int32 scale_y; // y scale factor (can't be negative)
|
||||
uint8* depth_map; // depth code array for destination (doesn't care if srcDepth is 0)
|
||||
BGR8* Pal; // palette for shadow draw (doesn't care if SHADOW bit is not set in Src.encoding)
|
||||
uint8* ICT; // Inverse Color Table (doesn't care if SHADOW bit is not set in Src.encoding)
|
||||
uint8 depth; // depth code for source (0 if no depth processing)
|
||||
} DrawRequestX;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
uint32 Pack;
|
||||
uint32 Stream;
|
||||
long hot_x;
|
||||
long hot_y;
|
||||
uint32 Width;
|
||||
uint32 Height;
|
||||
uint32 Comp;
|
||||
uint32 Reserved[8];
|
||||
uint8* data;
|
||||
} RendCell;
|
||||
|
||||
#define SS_HEADER_NUM_FIELDS 14
|
||||
struct SpriteSeriesHeader {
|
||||
uint32 header;
|
||||
uint32 size;
|
||||
uint32 packing;
|
||||
uint32 frameRate;
|
||||
uint32 pixSpeed;
|
||||
uint32 maxWidth;
|
||||
uint32 maxHeight;
|
||||
uint32 reserved3;
|
||||
uint32 reserved4;
|
||||
uint32 reserved5;
|
||||
uint32 reserved6;
|
||||
uint32 reserved7;
|
||||
uint32 reserved8;
|
||||
uint32 count;
|
||||
};
|
||||
|
||||
#define SF_HEADER_NUM_FIELDS 15
|
||||
struct SpriteFrameHeader {
|
||||
uint32 pack;
|
||||
uint32 stream;
|
||||
uint32 x;
|
||||
uint32 y;
|
||||
uint32 width;
|
||||
uint32 height;
|
||||
uint32 comp;
|
||||
uint32 reserved1;
|
||||
uint32 reserved2;
|
||||
uint32 reserved3;
|
||||
uint32 reserved4;
|
||||
uint32 reserved5;
|
||||
uint32 reserved6;
|
||||
uint32 reserved7;
|
||||
uint32 reserved8;
|
||||
};
|
||||
|
||||
class MSprite {
|
||||
public:
|
||||
MSprite *init(MSurface &s);
|
||||
MSprite *init(Common::SeekableReadStream *source, const Common::Point &offset, int widthVal,
|
||||
int heightVal, bool decodeRle = true, uint8 encodingVal = 0);
|
||||
protected:
|
||||
static MADSEngine *_vm;
|
||||
|
||||
MSprite(MSurface &s);
|
||||
MSprite(Common::SeekableReadStream *source, const Common::Point &offset,
|
||||
int widthVal, int heightVal, bool decodeRle = true, uint8 encodingVal = 0);
|
||||
|
||||
virtual void load(Common::SeekableReadStream *stream, int widthVal, int heightVal, bool decodeRle) {}
|
||||
public:
|
||||
static void setVm(MADSEngine *vm) { _vm = vm; }
|
||||
virtual ~MSprite();
|
||||
|
||||
MSurface &_surface;
|
||||
Common::Point _pos;
|
||||
Common::Point _offset;
|
||||
uint8 _encoding;
|
||||
};
|
||||
|
||||
class MSpriteMADS: public MSprite {
|
||||
friend class MSprite;
|
||||
private:
|
||||
void loadSprite(Common::SeekableReadStream *source);
|
||||
protected:
|
||||
MSpriteMADS(MSurface &s): MSprite(s) {}
|
||||
MSpriteMADS(Common::SeekableReadStream *source, const Common::Point &offset,
|
||||
int widthVal, int heightVal, bool decodeRle = true, uint8 encodingVal = 0):
|
||||
MSprite(source, offset, widthVal, heightVal, decodeRle, encodingVal) {}
|
||||
|
||||
virtual void load(Common::SeekableReadStream *stream, int widthVal, int heightVal, bool decodeRle);
|
||||
};
|
||||
|
||||
class MSpriteM4: public MSprite {
|
||||
friend class MSprite;
|
||||
private:
|
||||
// Loads a sprite from the given stream, and optionally decompresses the RLE-encoded data
|
||||
// Loads an RLE compressed sprite; the surface must have been created before
|
||||
void loadRle(Common::SeekableReadStream *rleData);
|
||||
void loadDeltaRle(Common::SeekableReadStream *rleData, int destX, int destY);
|
||||
protected:
|
||||
MSpriteM4(MSurface &s): MSprite(s) {}
|
||||
MSpriteM4(Common::SeekableReadStream *source, const Common::Point &offset,
|
||||
int widthVal, int heightVal, bool decodeRle = true, uint8 encodingVal = 0):
|
||||
MSprite(source, offset, widthVal, heightVal, decodeRle, encodingVal) {}
|
||||
|
||||
virtual void load(Common::SeekableReadStream *stream, int widthVal, int heightVal, bool decodeRle);
|
||||
};
|
||||
|
||||
} // End of namespace MADS
|
||||
|
||||
#endif /* MADS_MSPRITE_H */
|
690
engines/mads/msurface.cpp
Normal file
690
engines/mads/msurface.cpp
Normal file
@ -0,0 +1,690 @@
|
||||
/* 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 2
|
||||
* of the License, or (at your option) any later version.
|
||||
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "engines/util.h"
|
||||
#include "mads/mads.h"
|
||||
#include "mads/compression.h"
|
||||
#include "mads/msurface.h"
|
||||
#include "mads/msprite.h"
|
||||
|
||||
namespace MADS {
|
||||
|
||||
MADSEngine *MSurface::_vm = nullptr;
|
||||
|
||||
MSurface *MSurface::init(bool isScreen) {
|
||||
if (_vm->getGameID() == GType_RexNebular) {
|
||||
return new MSurfaceNebular(isScreen);
|
||||
} else if (_vm->getGameFeatures() & GF_MADS) {
|
||||
return new MSurfaceMADS(isScreen);
|
||||
} else {
|
||||
return new MSurfaceM4(isScreen);
|
||||
}
|
||||
}
|
||||
|
||||
MSurface *MSurface::init(int w, int h) {
|
||||
if (_vm->getGameID() == GType_RexNebular) {
|
||||
return new MSurfaceNebular(w, h);
|
||||
} else if (_vm->getGameFeatures() & GF_MADS) {
|
||||
return new MSurfaceMADS(w, h);
|
||||
} else {
|
||||
return new MSurfaceM4(w, h);
|
||||
}
|
||||
}
|
||||
|
||||
MSurface::MSurface(bool isScreen) {
|
||||
create(g_system->getWidth(), g_system->getHeight());
|
||||
_isScreen = isScreen;
|
||||
}
|
||||
|
||||
MSurface::MSurface(int Width, int Height) {
|
||||
create(Width, Height);
|
||||
_isScreen = false;
|
||||
}
|
||||
|
||||
void MSurface::vLine(int x, int y1, int y2) {
|
||||
Graphics::Surface::vLine(x, y1, y2, _color);
|
||||
}
|
||||
|
||||
void MSurface::hLine(int x1, int x2, int y) {
|
||||
Graphics::Surface::hLine(x1, y, x2, _color);
|
||||
}
|
||||
|
||||
void MSurface::vLineXor(int x, int y1, int y2) {
|
||||
// Clipping
|
||||
if (x < 0 || x >= w)
|
||||
return;
|
||||
|
||||
if (y2 < y1)
|
||||
SWAP(y2, y1);
|
||||
|
||||
if (y1 < 0)
|
||||
y1 = 0;
|
||||
if (y2 >= h)
|
||||
y2 = h - 1;
|
||||
|
||||
byte *ptr = (byte *)getBasePtr(x, y1);
|
||||
while (y1++ <= y2) {
|
||||
*ptr ^= 0xFF;
|
||||
ptr += pitch;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void MSurface::hLineXor(int x1, int x2, int y) {
|
||||
// Clipping
|
||||
if (y < 0 || y >= h)
|
||||
return;
|
||||
|
||||
if (x2 < x1)
|
||||
SWAP(x2, x1);
|
||||
|
||||
if (x1 < 0)
|
||||
x1 = 0;
|
||||
if (x2 >= w)
|
||||
x2 = w - 1;
|
||||
|
||||
if (x2 < x1)
|
||||
return;
|
||||
|
||||
byte *ptr = (byte *)getBasePtr(x1, y);
|
||||
while (x1++ <= x2)
|
||||
*ptr++ ^= 0xFF;
|
||||
|
||||
}
|
||||
|
||||
void MSurface::line(int x1, int y1, int x2, int y2, byte color) {
|
||||
Graphics::Surface::drawLine(x1, y1, x2, y2, color);
|
||||
}
|
||||
|
||||
|
||||
void MSurface::frameRect(int x1, int y1, int x2, int y2) {
|
||||
Graphics::Surface::frameRect(Common::Rect(x1, y1, x2, y2), _color);
|
||||
}
|
||||
|
||||
void MSurface::fillRect(int x1, int y1, int x2, int y2) {
|
||||
Graphics::Surface::fillRect(Common::Rect(x1, y1, x2, y2), _color);
|
||||
}
|
||||
|
||||
int MSurface::scaleValue(int value, int scale, int err) {
|
||||
int scaled = 0;
|
||||
while (value--) {
|
||||
err -= scale;
|
||||
while (err < 0) {
|
||||
scaled++;
|
||||
err += 100;
|
||||
}
|
||||
}
|
||||
return scaled;
|
||||
}
|
||||
|
||||
void MSurface::drawSprite(int x, int y, SpriteInfo &info, const Common::Rect &clipRect) {
|
||||
|
||||
enum {
|
||||
kStatusSkip,
|
||||
kStatusScale,
|
||||
kStatusDraw
|
||||
};
|
||||
|
||||
// NOTE: The current clipping code assumes that the top left corner of the clip
|
||||
// rectangle is always 0, 0
|
||||
assert(clipRect.top == 0 && clipRect.left == 0);
|
||||
|
||||
// TODO: Put err* and scaled* into SpriteInfo
|
||||
int errX = info.hotX * info.scaleX % 100;
|
||||
int errY = info.hotY * info.scaleY % 100;
|
||||
int scaledWidth = scaleValue(info.width, info.scaleX, errX);
|
||||
int scaledHeight = scaleValue(info.height, info.scaleY, errY);
|
||||
|
||||
/*
|
||||
printf("MSurface::drawSprite() info.width = %d; info.scaleX = %d; info.height = %d; info.scaleY = %d; scaledWidth = %d; scaledHeight = %d\n",
|
||||
info.width, info.scaleX, info.height, info.scaleY, scaledWidth, scaledHeight); fflush(stdout);
|
||||
*/
|
||||
|
||||
int clipX = 0, clipY = 0;
|
||||
// Clip the sprite's width and height according to the clip rectangle's dimensions
|
||||
// This clips the sprite to the bottom and right
|
||||
if (x >= 0) {
|
||||
scaledWidth = MIN<int>(x + scaledWidth, clipRect.right) - x;
|
||||
} else {
|
||||
clipX = x;
|
||||
scaledWidth = x + scaledWidth;
|
||||
}
|
||||
if (y >= 0) {
|
||||
scaledHeight = MIN<int>(y + scaledHeight, clipRect.bottom) - y;
|
||||
} else {
|
||||
clipY = y;
|
||||
scaledHeight = y + scaledHeight;
|
||||
}
|
||||
|
||||
//printf("MSurface::drawSprite() width = %d; height = %d; scaledWidth = %d; scaledHeight = %d\n", info.width, info.height, scaledWidth, scaledHeight); fflush(stdout);
|
||||
|
||||
// Check if sprite is inside the screen. If it's not, there's no need to draw it
|
||||
if (scaledWidth + x <= 0 || scaledHeight + y <= 0) // check left and top (in case x,y are negative)
|
||||
return;
|
||||
if (scaledWidth <= 0 || scaledHeight <= 0) // check right and bottom
|
||||
return;
|
||||
int heightAmt = scaledHeight;
|
||||
|
||||
byte *src = info.sprite->_surface.getData();
|
||||
byte *dst = getBasePtr(x - info.hotX - clipX, y - info.hotY - clipY);
|
||||
|
||||
int status = kStatusSkip;
|
||||
byte *scaledLineBuf = new byte[scaledWidth];
|
||||
|
||||
while (heightAmt > 0) {
|
||||
|
||||
if (status == kStatusSkip) {
|
||||
// Skip line
|
||||
errY -= info.scaleY;
|
||||
if (errY < 0)
|
||||
status = kStatusScale;
|
||||
else
|
||||
src += info.width;
|
||||
} else {
|
||||
|
||||
if (status == kStatusScale) {
|
||||
// Scale current line
|
||||
byte *lineDst = scaledLineBuf;
|
||||
int curErrX = errX;
|
||||
int widthVal = scaledWidth;
|
||||
byte *tempSrc = src;
|
||||
int startX = clipX;
|
||||
while (widthVal > 0) {
|
||||
byte pixel = *tempSrc++;
|
||||
curErrX -= info.scaleX;
|
||||
while (curErrX < 0) {
|
||||
if (startX == 0) {
|
||||
*lineDst++ = pixel;
|
||||
widthVal--;
|
||||
} else {
|
||||
startX++;
|
||||
}
|
||||
curErrX += 100;
|
||||
}
|
||||
}
|
||||
src += info.width;
|
||||
status = kStatusDraw;
|
||||
}
|
||||
|
||||
if (status == kStatusDraw && clipY == 0) {
|
||||
// Draw previously scaled line
|
||||
// TODO Implement different drawing types (depth, shadow etc.)
|
||||
byte *tempDst = dst;
|
||||
for (int lineX = 0; lineX < scaledWidth; lineX++) {
|
||||
byte pixel = scaledLineBuf[lineX];
|
||||
|
||||
if (info.encoding & 0x80) {
|
||||
|
||||
if (pixel == 0x80) {
|
||||
pixel = 0;
|
||||
} else {
|
||||
byte destPixel = *tempDst;
|
||||
byte r, g, b;
|
||||
r = CLIP((info.palette[destPixel].r * pixel) >> 10, 0, 31);
|
||||
g = CLIP((info.palette[destPixel].g * pixel) >> 10, 0, 31);
|
||||
b = CLIP((info.palette[destPixel].b * pixel) >> 10, 0, 31);
|
||||
pixel = info.inverseColorTable[(b << 10) | (g << 5) | r];
|
||||
}
|
||||
}
|
||||
|
||||
if (pixel)
|
||||
*tempDst = pixel;
|
||||
|
||||
tempDst++;
|
||||
}
|
||||
dst += pitch;
|
||||
heightAmt--;
|
||||
// TODO depth etc.
|
||||
//depthAddress += Destination -> Width;
|
||||
|
||||
errY += 100;
|
||||
if (errY >= 0)
|
||||
status = kStatusSkip;
|
||||
} else if (status == kStatusDraw && clipY < 0) {
|
||||
clipY++;
|
||||
|
||||
errY += 100;
|
||||
if (errY >= 0)
|
||||
status = kStatusSkip;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
delete[] scaledLineBuf;
|
||||
|
||||
}
|
||||
|
||||
// Surface methods
|
||||
|
||||
byte *MSurface::getData() {
|
||||
return (byte *)Graphics::Surface::getPixels();
|
||||
}
|
||||
|
||||
byte *MSurface::getBasePtr(int x, int y) {
|
||||
return (byte *)Graphics::Surface::getBasePtr(x, y);
|
||||
}
|
||||
|
||||
void MSurface::freeData() {
|
||||
}
|
||||
|
||||
void MSurface::empty() {
|
||||
Common::fill(getBasePtr(0, 0), getBasePtr(w, h), _vm->_palette->BLACK);
|
||||
}
|
||||
|
||||
void MSurface::frameRect(const Common::Rect &r, uint8 color) {
|
||||
Graphics::Surface::frameRect(r, color);
|
||||
}
|
||||
|
||||
void MSurface::fillRect(const Common::Rect &r, uint8 color) {
|
||||
Graphics::Surface::fillRect(r, color);
|
||||
}
|
||||
|
||||
void MSurface::copyFrom(MSurface *src, const Common::Rect &srcBounds, int destX, int destY,
|
||||
int transparentColor) {
|
||||
// Validation of the rectangle and position
|
||||
if ((destX >= w) || (destY >= h))
|
||||
return;
|
||||
|
||||
Common::Rect copyRect = srcBounds;
|
||||
if (destX < 0) {
|
||||
copyRect.left += -destX;
|
||||
destX = 0;
|
||||
} else if (destX + copyRect.width() > w) {
|
||||
copyRect.right -= destX + copyRect.width() - w;
|
||||
}
|
||||
if (destY < 0) {
|
||||
copyRect.top += -destY;
|
||||
destY = 0;
|
||||
} else if (destY + copyRect.height() > h) {
|
||||
copyRect.bottom -= destY + copyRect.height() - h;
|
||||
}
|
||||
|
||||
if (!copyRect.isValidRect())
|
||||
return;
|
||||
|
||||
// Copy the specified area
|
||||
|
||||
byte *data = src->getData();
|
||||
byte *srcPtr = data + (src->width() * copyRect.top + copyRect.left);
|
||||
byte *destPtr = (byte *)pixels + (destY * width()) + destX;
|
||||
|
||||
for (int rowCtr = 0; rowCtr < copyRect.height(); ++rowCtr) {
|
||||
if (transparentColor == -1)
|
||||
// No transparency, so copy line over
|
||||
Common::copy(srcPtr, srcPtr + copyRect.width(), destPtr);
|
||||
else {
|
||||
// Copy each byte one at a time checking for the transparency color
|
||||
for (int xCtr = 0; xCtr < copyRect.width(); ++xCtr)
|
||||
if (srcPtr[xCtr] != transparentColor) destPtr[xCtr] = srcPtr[xCtr];
|
||||
}
|
||||
|
||||
srcPtr += src->width();
|
||||
destPtr += width();
|
||||
}
|
||||
|
||||
src->freeData();
|
||||
}
|
||||
|
||||
#undef COL_TRANS
|
||||
|
||||
void MSurface::translate(RGBList *list, bool isTransparent) {
|
||||
byte *p = getBasePtr(0, 0);
|
||||
byte *palIndexes = list->palIndexes();
|
||||
|
||||
for (int i = 0; i < width() * height(); ++i, ++p) {
|
||||
if (!isTransparent || (*p != 0)) {
|
||||
assert(*p < list->size());
|
||||
*p = palIndexes[*p];
|
||||
}
|
||||
}
|
||||
|
||||
freeData();
|
||||
}
|
||||
|
||||
/*------------------------------------------------------------------------*/
|
||||
|
||||
void MSurfaceMADS::loadCodes(Common::SeekableReadStream *source) {
|
||||
if (!source) {
|
||||
free();
|
||||
return;
|
||||
}
|
||||
|
||||
uint16 widthVal = 320;
|
||||
uint16 heightVal = 156;
|
||||
byte *walkMap = new byte[source->size()];
|
||||
|
||||
create(widthVal, heightVal);
|
||||
source->read(walkMap, source->size());
|
||||
|
||||
byte *ptr = (byte *)getBasePtr(0, 0);
|
||||
|
||||
for (int y = 0; y < heightVal; y++) {
|
||||
for (int x = 0; x < widthVal; x++) {
|
||||
int ofs = x + (y * widthVal);
|
||||
if ((walkMap[ofs / 8] << (ofs % 8)) & 0x80)
|
||||
*ptr++ = 1; // walkable
|
||||
else
|
||||
*ptr++ = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MSurfaceMADS::loadBackground(int roomNumber, RGBList **palData) {
|
||||
// clear previous data
|
||||
empty();
|
||||
|
||||
// Get a MadsPack reference to the tile set and mapping
|
||||
char resourceName[20];
|
||||
int i;
|
||||
|
||||
// Uncompressed tile map resource
|
||||
sprintf(resourceName, "rm%d.mm", roomNumber);
|
||||
MadsPack tileMapFile(resourceName, _vm);
|
||||
Common::SeekableReadStream *mapStream = tileMapFile.getItemStream(0);
|
||||
|
||||
// Get the details of the tiles and map
|
||||
mapStream->readUint32LE();
|
||||
int tileCountX = mapStream->readUint16LE();
|
||||
int tileCountY = mapStream->readUint16LE();
|
||||
int tileWidthMap = mapStream->readUint16LE();
|
||||
int tileHeightMap = mapStream->readUint16LE();
|
||||
int screenWidth = mapStream->readUint16LE();
|
||||
int screenHeight = mapStream->readUint16LE();
|
||||
int tileCountMap = tileCountX * tileCountY;
|
||||
delete mapStream;
|
||||
|
||||
// Obtain tile map information
|
||||
typedef Common::List<Common::SharedPtr<MSurface> > TileSetList;
|
||||
typedef TileSetList::iterator TileSetIterator;
|
||||
TileSetList tileSet;
|
||||
uint16 *tileMap = new uint16[tileCountMap];
|
||||
mapStream = tileMapFile.getItemStream(1);
|
||||
for (i = 0; i < tileCountMap; ++i)
|
||||
tileMap[i] = mapStream->readUint16LE();
|
||||
delete mapStream;
|
||||
|
||||
_vm->_resources->toss(resourceName);
|
||||
|
||||
// --------------------------------------------------------------------------------
|
||||
|
||||
// Tile map data, which needs to be kept compressed, as the tile offsets refer to
|
||||
// the compressed data. Each tile is then uncompressed separately
|
||||
sprintf(resourceName, "rm%d.tt", roomNumber);
|
||||
Common::SeekableReadStream *tileDataComp = _vm->_resources->get(resourceName);
|
||||
MadsPack tileData(tileDataComp);
|
||||
Common::SeekableReadStream *tileDataUncomp = tileData.getItemStream(0);
|
||||
|
||||
// Validate that the data matches between the tiles and tile map file and is valid
|
||||
int tileCount = tileDataUncomp->readUint16LE();
|
||||
int tileWidth = tileDataUncomp->readUint16LE();
|
||||
int tileHeight = tileDataUncomp->readUint16LE();
|
||||
delete tileDataUncomp;
|
||||
assert(tileCountMap == tileCount);
|
||||
assert(tileWidth == tileWidthMap);
|
||||
assert(tileHeight == tileHeightMap);
|
||||
assert(screenWidth == _vm->_screen->width());
|
||||
assert(screenHeight <= _vm->_screen->height());
|
||||
|
||||
// --------------------------------------------------------------------------------
|
||||
|
||||
// Get the palette to use
|
||||
tileDataUncomp = tileData.getItemStream(2);
|
||||
// Set palette
|
||||
if (!palData) {
|
||||
_vm->_palette->setMadsPalette(tileDataUncomp, 4);
|
||||
} else {
|
||||
int numColors;
|
||||
RGB8 *rgbList = _vm->_palette->decodeMadsPalette(tileDataUncomp, &numColors);
|
||||
*palData = new RGBList(numColors, rgbList, true);
|
||||
}
|
||||
delete tileDataUncomp;
|
||||
|
||||
// --------------------------------------------------------------------------------
|
||||
|
||||
// Get tile data
|
||||
|
||||
tileDataUncomp = tileData.getItemStream(1);
|
||||
FabDecompressor fab;
|
||||
uint32 compressedTileDataSize = 0;
|
||||
|
||||
for (i = 0; i < tileCount; i++) {
|
||||
tileDataUncomp->seek(i * 4, SEEK_SET);
|
||||
uint32 tileOfs = tileDataUncomp->readUint32LE();
|
||||
MSurface *newTile = MSurface::init(tileWidth, tileHeight);
|
||||
|
||||
if (i == tileCount - 1)
|
||||
compressedTileDataSize = tileDataComp->size() - tileOfs;
|
||||
else
|
||||
compressedTileDataSize = tileDataUncomp->readUint32LE() - tileOfs;
|
||||
|
||||
//printf("Tile: %i, compressed size: %i\n", i, compressedTileDataSize);
|
||||
|
||||
newTile->empty();
|
||||
|
||||
byte *compressedTileData = new byte[compressedTileDataSize];
|
||||
|
||||
tileDataComp->seek(tileData.getDataOffset() + tileOfs, SEEK_SET);
|
||||
tileDataComp->read(compressedTileData, compressedTileDataSize);
|
||||
|
||||
fab.decompress(compressedTileData, compressedTileDataSize, newTile->getData(),
|
||||
tileWidth * tileHeight);
|
||||
tileSet.push_back(TileSetList::value_type(newTile));
|
||||
delete[] compressedTileData;
|
||||
}
|
||||
|
||||
delete tileDataUncomp;
|
||||
|
||||
// --------------------------------------------------------------------------------
|
||||
|
||||
// Loop through the mapping data to place the tiles on the screen
|
||||
|
||||
uint16 *tIndex = &tileMap[0];
|
||||
for (int y = 0; y < tileCountY; y++) {
|
||||
for (int x = 0; x < tileCountX; x++) {
|
||||
int tileIndex = *tIndex++;
|
||||
assert(tileIndex < tileCount);
|
||||
TileSetIterator tile = tileSet.begin();
|
||||
for (i = 0; i < tileIndex; i++)
|
||||
++tile;
|
||||
((*tile).get())->copyTo(this, x * tileWidth, y * tileHeight);
|
||||
}
|
||||
}
|
||||
tileSet.clear();
|
||||
_vm->_resources->toss(resourceName);
|
||||
}
|
||||
|
||||
void MSurfaceMADS::loadInterface(int index, RGBList **palData) {
|
||||
char resourceName[20];
|
||||
sprintf(resourceName, "i%d.int", index);
|
||||
MadsPack intFile(resourceName, _vm);
|
||||
RGB8 *palette = new RGB8[16];
|
||||
|
||||
// Chunk 0, palette
|
||||
Common::SeekableReadStream *intStream = intFile.getItemStream(0);
|
||||
|
||||
for (int i = 0; i < 16; i++) {
|
||||
palette[i].r = intStream->readByte() << 2;
|
||||
palette[i].g = intStream->readByte() << 2;
|
||||
palette[i].b = intStream->readByte() << 2;
|
||||
intStream->readByte();
|
||||
intStream->readByte();
|
||||
intStream->readByte();
|
||||
}
|
||||
*palData = new RGBList(16, palette, true);
|
||||
delete intStream;
|
||||
|
||||
// Chunk 1, data
|
||||
intStream = intFile.getItemStream(1);
|
||||
create(320, 44);
|
||||
intStream->read(pixels, 320 * 44);
|
||||
delete intStream;
|
||||
}
|
||||
|
||||
/*------------------------------------------------------------------------*/
|
||||
|
||||
void MSurfaceNebular::loadBackground(int roomNumber, RGBList **palData) {
|
||||
// clear previous data
|
||||
empty();
|
||||
|
||||
Common::String resourceName = Common::String::format("rm%d.art", roomNumber);
|
||||
Common::SeekableReadStream *stream = _vm->_resources->get(resourceName);
|
||||
loadBackgroundStream(stream, palData);
|
||||
|
||||
_vm->_resources->toss(resourceName);
|
||||
}
|
||||
|
||||
void MSurfaceNebular::loadBackgroundStream(Common::SeekableReadStream *source, RGBList **palData) {
|
||||
MadsPack packData(source);
|
||||
Common::MemoryReadStream *sourceUnc = packData.getItemStream(0);
|
||||
|
||||
int sceneWidth = sourceUnc->readUint16LE();
|
||||
int sceneHeight = sourceUnc->readUint16LE();
|
||||
int sceneSize = sceneWidth * sceneHeight;
|
||||
if (sceneWidth > this->width()) {
|
||||
warning("Background width is %i, too large to fit in screen. Setting it to %i", sceneWidth, this->width());
|
||||
sceneWidth = this->width();
|
||||
sceneSize = sceneWidth * sceneHeight;
|
||||
}
|
||||
if (sceneHeight > this->height()) {
|
||||
warning("Background height is %i, too large to fit in screen.Setting it to %i", sceneHeight, this->height());
|
||||
sceneHeight = this->height();
|
||||
sceneSize = sceneWidth * sceneHeight;
|
||||
}
|
||||
|
||||
// Set palette
|
||||
if (!palData) {
|
||||
_vm->_palette->setMadsPalette(sourceUnc, 4);
|
||||
} else {
|
||||
int numColors;
|
||||
RGB8 *rgbList = _vm->_palette->decodeMadsPalette(sourceUnc, &numColors);
|
||||
*palData = new RGBList(numColors, rgbList, true);
|
||||
}
|
||||
delete sourceUnc;
|
||||
|
||||
// Get the raw data for the background
|
||||
sourceUnc = packData.getItemStream(1);
|
||||
assert((int)sourceUnc->size() >= sceneSize);
|
||||
|
||||
byte *pData = (byte *)pixels;
|
||||
sourceUnc->read(pData, sceneSize);
|
||||
|
||||
freeData();
|
||||
delete sourceUnc;
|
||||
}
|
||||
|
||||
/*------------------------------------------------------------------------*/
|
||||
|
||||
void MSurfaceM4::loadCodes(Common::SeekableReadStream *source) {
|
||||
if (!source) {
|
||||
free();
|
||||
return;
|
||||
}
|
||||
|
||||
uint16 widthVal = source->readUint16LE();
|
||||
uint16 heightVal = source->readUint16LE();
|
||||
|
||||
create(widthVal, heightVal);
|
||||
source->read(pixels, widthVal * heightVal);
|
||||
}
|
||||
|
||||
void MSurfaceM4::loadBackground(int roomNumber, RGBList **palData) {
|
||||
if (palData)
|
||||
*palData = NULL;
|
||||
Common::String resourceName = Common::String::format("%i.tt", roomNumber);
|
||||
Common::SeekableReadStream *stream = _vm->_resources->get(resourceName);
|
||||
loadBackgroundStream(stream);
|
||||
|
||||
_vm->_resources->toss(resourceName);
|
||||
}
|
||||
|
||||
void MSurfaceM4::loadBackgroundStream(Common::SeekableReadStream *source) {
|
||||
MSurface *tileBuffer = MSurface::init();
|
||||
uint curTileX = 0, curTileY = 0;
|
||||
int clipX = 0, clipY = 0;
|
||||
RGB8 palette[256];
|
||||
|
||||
source->skip(4);
|
||||
/*uint32 size =*/ source->readUint32LE();
|
||||
uint32 widthVal = source->readUint32LE();
|
||||
uint32 heightVal = source->readUint32LE();
|
||||
uint32 tilesX = source->readUint32LE();
|
||||
uint32 tilesY = source->readUint32LE();
|
||||
uint32 tileWidth = source->readUint32LE();
|
||||
uint32 tileHeight = source->readUint32LE();
|
||||
uint8 blackIndex = 0;
|
||||
|
||||
// BGR data, which is converted to RGB8
|
||||
for (uint i = 0; i < 256; i++) {
|
||||
palette[i].b = source->readByte() << 2;
|
||||
palette[i].g = source->readByte() << 2;
|
||||
palette[i].r = source->readByte() << 2;
|
||||
palette[i].u = source->readByte() << 2;
|
||||
|
||||
if ((blackIndex == 0) && !palette[i].r && !palette[i].g && !palette[i].b)
|
||||
blackIndex = i;
|
||||
}
|
||||
|
||||
_vm->_palette->setPalette(palette, 0, 256);
|
||||
|
||||
// resize or create the surface
|
||||
// Note that the height of the scene in game scenes is smaller than the screen height,
|
||||
// as the bottom part of the screen is the inventory
|
||||
assert(width() == (int)widthVal);
|
||||
|
||||
tileBuffer->create(tileWidth, tileHeight);
|
||||
|
||||
for (curTileY = 0; curTileY < tilesY; curTileY++) {
|
||||
clipY = MIN(heightVal, (1 + curTileY) * tileHeight) - (curTileY * tileHeight);
|
||||
|
||||
for (curTileX = 0; curTileX < tilesX; curTileX++) {
|
||||
clipX = MIN(widthVal, (1 + curTileX) * tileWidth) - (curTileX * tileWidth);
|
||||
|
||||
// Read a tile and copy it to the destination surface
|
||||
source->read(tileBuffer->getData(), tileWidth * tileHeight);
|
||||
Common::Rect srcBounds(0, 0, clipX, clipY);
|
||||
copyFrom(tileBuffer, srcBounds, curTileX * tileWidth, curTileY * tileHeight);
|
||||
}
|
||||
}
|
||||
|
||||
if (heightVal < (uint)height())
|
||||
fillRect(Common::Rect(0, heightVal, width(), height()), blackIndex);
|
||||
|
||||
delete tileBuffer;
|
||||
}
|
||||
|
||||
/*------------------------------------------------------------------------*/
|
||||
|
||||
void MSurfaceRiddle::loadBackground(const Common::String &sceneName) {
|
||||
char resourceName[20];
|
||||
Common::SeekableReadStream *stream;
|
||||
// Loads a Riddle scene
|
||||
Common::String resName = Common::String::format("%s.tt", sceneName.c_str());
|
||||
stream = _vm->_resources->get(resourceName);
|
||||
|
||||
loadBackgroundStream(stream);
|
||||
|
||||
_vm->_resources->toss(resourceName);
|
||||
}
|
||||
|
||||
} // End of namespace MADS
|
185
engines/mads/msurface.h
Normal file
185
engines/mads/msurface.h
Normal file
@ -0,0 +1,185 @@
|
||||
/* 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 2
|
||||
* of the License, or (at your option) any later version.
|
||||
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef MADS_MSURFACE_H
|
||||
#define MADS_MSURFACE_H
|
||||
|
||||
#include "common/scummsys.h"
|
||||
#include "common/rect.h"
|
||||
#include "graphics/surface.h"
|
||||
#include "mads/palette.h"
|
||||
|
||||
namespace MADS {
|
||||
|
||||
class MADSEngine;
|
||||
class MSprite;
|
||||
|
||||
struct SpriteInfo {
|
||||
MSprite *sprite;
|
||||
int hotX, hotY;
|
||||
int width, height;
|
||||
int scaleX, scaleY;
|
||||
uint8 encoding;
|
||||
byte *inverseColorTable;
|
||||
RGB8 *palette;
|
||||
};
|
||||
|
||||
class MSurface : public Graphics::Surface {
|
||||
public:
|
||||
static MADSEngine *_vm;
|
||||
|
||||
/**
|
||||
* Sets the engine reference
|
||||
*/
|
||||
static void setVm(MADSEngine *vm) { _vm = vm; }
|
||||
|
||||
/**
|
||||
* Create a new surface the same size as the screen.
|
||||
* @param isScreen Set to true for the screen surface
|
||||
*/
|
||||
static MSurface *init(bool isScreen = false);
|
||||
|
||||
/**
|
||||
* Create a surface
|
||||
*/
|
||||
static MSurface *init(int w, int h);
|
||||
private:
|
||||
byte _color;
|
||||
bool _isScreen;
|
||||
protected:
|
||||
MSurface(bool isScreen = false);
|
||||
MSurface(int w, int h);
|
||||
public:
|
||||
void create(int w, int h) {
|
||||
Graphics::Surface::create(w, h, Graphics::PixelFormat::createFormatCLUT8());
|
||||
}
|
||||
|
||||
void setColor(byte value) { _color = value; }
|
||||
byte getColor() { return _color; }
|
||||
void vLine(int x, int y1, int y2);
|
||||
void hLine(int x1, int x2, int y);
|
||||
void vLineXor(int x, int y1, int y2);
|
||||
void hLineXor(int x1, int x2, int y);
|
||||
void line(int x1, int y1, int x2, int y2, byte color);
|
||||
void frameRect(int x1, int y1, int x2, int y2);
|
||||
void fillRect(int x1, int y1, int x2, int y2);
|
||||
|
||||
static int scaleValue(int value, int scale, int err);
|
||||
void drawSprite(int x, int y, SpriteInfo &info, const Common::Rect &clipRect);
|
||||
|
||||
// Surface methods
|
||||
int width() { return w; }
|
||||
int height() { return h; }
|
||||
void setSize(int sizeX, int sizeY);
|
||||
byte *getData();
|
||||
byte *getBasePtr(int x, int y);
|
||||
void freeData();
|
||||
void empty();
|
||||
void frameRect(const Common::Rect &r, uint8 color);
|
||||
void fillRect(const Common::Rect &r, uint8 color);
|
||||
void copyFrom(MSurface *src, const Common::Rect &srcBounds, int destX, int destY,
|
||||
int transparentColor = -1);
|
||||
|
||||
void update() {
|
||||
if (_isScreen) {
|
||||
g_system->copyRectToScreen((const byte *)pixels, pitch, 0, 0, w, h);
|
||||
g_system->updateScreen();
|
||||
}
|
||||
}
|
||||
|
||||
// copyTo methods
|
||||
void copyTo(MSurface *dest, int transparentColor = -1) {
|
||||
dest->copyFrom(this, Common::Rect(width(), height()), 0, 0, transparentColor);
|
||||
}
|
||||
void copyTo(MSurface *dest, int x, int y, int transparentColor = -1) {
|
||||
dest->copyFrom(this, Common::Rect(width(), height()), x, y, transparentColor);
|
||||
}
|
||||
void copyTo(MSurface *dest, const Common::Rect &srcBounds, int destX, int destY,
|
||||
int transparentColor = -1) {
|
||||
dest->copyFrom(this, srcBounds, destX, destY, transparentColor);
|
||||
}
|
||||
|
||||
void translate(RGBList *list, bool isTransparent = false);
|
||||
|
||||
// Base virtual methods
|
||||
virtual void loadBackground(const Common::String &sceneName) {}
|
||||
virtual void loadBackground(int roomNumber, RGBList **palData) = 0;
|
||||
virtual void loadBackground(Common::SeekableReadStream *source, RGBList **palData) {}
|
||||
virtual void loadCodes(Common::SeekableReadStream *source) = 0;
|
||||
virtual void loadInterface(int index, RGBList **palData) {}
|
||||
};
|
||||
|
||||
class MSurfaceMADS: public MSurface {
|
||||
friend class MSurface;
|
||||
protected:
|
||||
MSurfaceMADS(bool isScreen = false): MSurface(isScreen) {}
|
||||
MSurfaceMADS(int w, int h): MSurface(w, h) {}
|
||||
public:
|
||||
virtual void loadCodes(Common::SeekableReadStream *source);
|
||||
virtual void loadBackground(const Common::String &sceneName) {}
|
||||
virtual void loadBackground(int roomNumber, RGBList **palData);
|
||||
virtual void loadInterface(int index, RGBList **palData);
|
||||
};
|
||||
|
||||
class MSurfaceNebular: public MSurfaceMADS {
|
||||
friend class MSurface;
|
||||
protected:
|
||||
MSurfaceNebular(bool isScreen = false): MSurfaceMADS(isScreen) {}
|
||||
MSurfaceNebular(int w, int h): MSurfaceMADS(w, h) {}
|
||||
private:
|
||||
void loadBackgroundStream(Common::SeekableReadStream *source, RGBList **palData);
|
||||
public:
|
||||
virtual void loadBackground(int roomNumber, RGBList **palData);
|
||||
};
|
||||
|
||||
class MSurfaceM4: public MSurface {
|
||||
friend class MSurface;
|
||||
protected:
|
||||
MSurfaceM4(bool isScreen = false): MSurface(isScreen) {}
|
||||
MSurfaceM4(int w, int h): MSurface(w, h) {}
|
||||
|
||||
void loadBackgroundStream(Common::SeekableReadStream *source);
|
||||
public:
|
||||
virtual void loadCodes(Common::SeekableReadStream *source);
|
||||
virtual void loadBackground(int roomNumber, RGBList **palData);
|
||||
};
|
||||
|
||||
class MSurfaceRiddle: public MSurfaceM4 {
|
||||
friend class MSurface;
|
||||
protected:
|
||||
MSurfaceRiddle(bool isScreen = false): MSurfaceM4(isScreen) {}
|
||||
MSurfaceRiddle(int w, int h): MSurfaceM4(w, h) {}
|
||||
public:
|
||||
virtual void loadBackground(const Common::String &sceneName);
|
||||
};
|
||||
/*
|
||||
void rexLoadBackground(Common::SeekableReadStream *source, RGBList **palData = NULL);
|
||||
void madsLoadBackground(int roomNumber, RGBList **palData = NULL);
|
||||
void m4LoadBackground(Common::SeekableReadStream *source);
|
||||
|
||||
void madsloadInterface(int index, RGBList **palData);
|
||||
|
||||
*/
|
||||
|
||||
} // End of namespace MADS
|
||||
|
||||
#endif /* MADS_MSURFACE_H */
|
289
engines/mads/palette.cpp
Normal file
289
engines/mads/palette.cpp
Normal file
@ -0,0 +1,289 @@
|
||||
/* 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 2
|
||||
* of the License, or (at your option) any later version.
|
||||
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "common/scummsys.h"
|
||||
#include "engines/util.h"
|
||||
#include "graphics/palette.h"
|
||||
#include "mads/mads.h"
|
||||
#include "mads/msurface.h"
|
||||
|
||||
namespace MADS {
|
||||
|
||||
RGBList::RGBList(int numEntries, RGB8 *srcData, bool freeData) {
|
||||
_size = numEntries;
|
||||
assert(numEntries <= 256);
|
||||
|
||||
if (srcData == NULL) {
|
||||
_data = new RGB8[numEntries];
|
||||
_freeData = true;
|
||||
} else {
|
||||
_data = srcData;
|
||||
_freeData = freeData;
|
||||
}
|
||||
|
||||
_palIndexes = new byte[numEntries];
|
||||
Common::fill(&_palIndexes[0], &_palIndexes[numEntries], 0);
|
||||
}
|
||||
|
||||
RGBList::~RGBList() {
|
||||
if (_freeData)
|
||||
delete[] _data;
|
||||
delete[] _palIndexes;
|
||||
}
|
||||
|
||||
/*------------------------------------------------------------------------*/
|
||||
|
||||
#define VGA_COLOR_TRANS(x) (x == 0x3f ? 255 : x << 2)
|
||||
|
||||
Palette::Palette(MADSEngine *vm) : _vm(vm) {
|
||||
reset();
|
||||
_fading_in_progress = false;
|
||||
Common::fill(&_usageCount[0], &_usageCount[256], 0);
|
||||
}
|
||||
|
||||
void Palette::setPalette(const byte *colors, uint start, uint num) {
|
||||
g_system->getPaletteManager()->setPalette(colors, start, num);
|
||||
reset();
|
||||
}
|
||||
|
||||
void Palette::setPalette(const RGB8 *colors, uint start, uint num) {
|
||||
g_system->getPaletteManager()->setPalette((const byte *)colors, start, num);
|
||||
reset();
|
||||
}
|
||||
|
||||
void Palette::grabPalette(byte *colors, uint start, uint num) {
|
||||
g_system->getPaletteManager()->grabPalette(colors, start, num);
|
||||
reset();
|
||||
}
|
||||
|
||||
uint8 Palette::palIndexFromRgb(byte r, byte g, byte b, RGB8 *paletteData) {
|
||||
byte index = 0;
|
||||
int32 minDist = 0x7fffffff;
|
||||
RGB8 palData[256];
|
||||
int Rdiff, Gdiff, Bdiff;
|
||||
|
||||
if (paletteData == NULL) {
|
||||
g_system->getPaletteManager()->grabPalette((byte *)palData, 0, 256);
|
||||
paletteData = &palData[0];
|
||||
}
|
||||
|
||||
for (int palIndex = 0; palIndex < 256; ++palIndex) {
|
||||
Rdiff = r - paletteData[palIndex].r;
|
||||
Gdiff = g - paletteData[palIndex].g;
|
||||
Bdiff = b - paletteData[palIndex].b;
|
||||
|
||||
if (Rdiff * Rdiff + Gdiff * Gdiff + Bdiff * Bdiff < minDist) {
|
||||
minDist = Rdiff * Rdiff + Gdiff * Gdiff + Bdiff * Bdiff;
|
||||
index = (uint8)palIndex;
|
||||
}
|
||||
}
|
||||
|
||||
return (uint8)index;
|
||||
}
|
||||
|
||||
void Palette::reset() {
|
||||
RGB8 palData[256];
|
||||
g_system->getPaletteManager()->grabPalette((byte *)palData, 0, 256);
|
||||
|
||||
BLACK = palIndexFromRgb(0, 0, 0, palData);
|
||||
BLUE = palIndexFromRgb(0, 0, 255, palData);
|
||||
GREEN = palIndexFromRgb(0, 255, 0, palData);
|
||||
CYAN = palIndexFromRgb(0, 255, 255, palData);
|
||||
RED = palIndexFromRgb(255, 0, 0, palData);
|
||||
VIOLET = palIndexFromRgb(255, 0, 255, palData);
|
||||
BROWN = palIndexFromRgb(168, 84, 84, palData);
|
||||
LIGHT_GRAY = palIndexFromRgb(168, 168, 168, palData);
|
||||
DARK_GRAY = palIndexFromRgb(84, 84, 84, palData);
|
||||
LIGHT_BLUE = palIndexFromRgb(0, 0, 127, palData);
|
||||
LIGHT_GREEN = palIndexFromRgb(0, 127, 0, palData);
|
||||
LIGHT_CYAN = palIndexFromRgb(0, 127, 127, palData);
|
||||
LIGHT_RED = palIndexFromRgb(84, 0, 0, palData);
|
||||
PINK = palIndexFromRgb(84, 0, 0, palData);
|
||||
YELLOW = palIndexFromRgb(0, 84, 84, palData);
|
||||
WHITE = palIndexFromRgb(255, 255, 255, palData);
|
||||
}
|
||||
|
||||
void Palette::fadeIn(int numSteps, uint delayAmount, RGBList *destPalette) {
|
||||
fadeIn(numSteps, delayAmount, destPalette->data(), destPalette->size());
|
||||
}
|
||||
|
||||
void Palette::fadeIn(int numSteps, uint delayAmount, RGB8 *destPalette, int numColors) {
|
||||
if (_fading_in_progress)
|
||||
return;
|
||||
|
||||
_fading_in_progress = true;
|
||||
RGB8 blackPalette[256];
|
||||
Common::fill((byte *)&blackPalette[0], (byte *)&blackPalette[256], 0);
|
||||
|
||||
// Initially set the black palette
|
||||
_vm->_palette->setPalette(blackPalette, 0, numColors);
|
||||
|
||||
// Handle the actual fading
|
||||
fadeRange(blackPalette, destPalette, 0, numColors - 1, numSteps, delayAmount);
|
||||
|
||||
_fading_in_progress = false;
|
||||
}
|
||||
|
||||
RGB8 *Palette::decodeMadsPalette(Common::SeekableReadStream *palStream, int *numColors) {
|
||||
*numColors = palStream->readUint16LE();
|
||||
assert(*numColors <= 252);
|
||||
|
||||
RGB8 *palData = new RGB8[*numColors];
|
||||
Common::fill((byte *)&palData[0], (byte *)&palData[*numColors], 0);
|
||||
|
||||
for (int i = 0; i < *numColors; ++i) {
|
||||
byte r = palStream->readByte();
|
||||
byte g = palStream->readByte();
|
||||
byte b = palStream->readByte();
|
||||
palData[i].r = VGA_COLOR_TRANS(r);
|
||||
palData[i].g = VGA_COLOR_TRANS(g);
|
||||
palData[i].b = VGA_COLOR_TRANS(b);
|
||||
|
||||
// The next 3 bytes are unused
|
||||
palStream->skip(3);
|
||||
}
|
||||
|
||||
return palData;
|
||||
}
|
||||
|
||||
int Palette::setMadsPalette(Common::SeekableReadStream *palStream, int indexStart) {
|
||||
int colorCount;
|
||||
RGB8 *palData = Palette::decodeMadsPalette(palStream, &colorCount);
|
||||
_vm->_palette->setPalette(palData, indexStart, colorCount);
|
||||
delete palData;
|
||||
return colorCount;
|
||||
}
|
||||
|
||||
void Palette::setMadsSystemPalette() {
|
||||
// Rex Nebular default system palette
|
||||
resetColorCounts();
|
||||
|
||||
RGB8 palData[4];
|
||||
palData[0].r = palData[0].g = palData[0].b = 0;
|
||||
palData[1].r = palData[1].g = palData[1].b = 0x54;
|
||||
palData[2].r = palData[2].g = palData[2].b = 0xb4;
|
||||
palData[3].r = palData[3].g = palData[3].b = 0xff;
|
||||
|
||||
setPalette(palData, 0, 4);
|
||||
blockRange(0, 4);
|
||||
}
|
||||
|
||||
void Palette::resetColorCounts() {
|
||||
Common::fill(&_usageCount[0], &_usageCount[256], 0);
|
||||
}
|
||||
|
||||
void Palette::blockRange(int startIndex, int size) {
|
||||
// Use a reference count of -1 to signal a palette index shouldn't be used
|
||||
Common::fill(&_usageCount[startIndex], &_usageCount[startIndex + size], -1);
|
||||
}
|
||||
|
||||
void Palette::addRange(RGBList *list) {
|
||||
RGB8 *data = list->data();
|
||||
byte *palIndexes = list->palIndexes();
|
||||
RGB8 palData[256];
|
||||
g_system->getPaletteManager()->grabPalette((byte *)&palData[0], 0, 256);
|
||||
bool paletteChanged = false;
|
||||
|
||||
for (int colIndex = 0; colIndex < list->size(); ++colIndex) {
|
||||
// Scan through for an existing copy of the RGB value
|
||||
int palIndex = -1;
|
||||
while (++palIndex < 256) {
|
||||
if (_usageCount[palIndex] <= 0)
|
||||
// Palette index is to be skipped
|
||||
continue;
|
||||
|
||||
if ((palData[palIndex].r == data[colIndex].r) &&
|
||||
(palData[palIndex].g == data[colIndex].g) &&
|
||||
(palData[palIndex].b == data[colIndex].b))
|
||||
// Match found
|
||||
break;
|
||||
}
|
||||
|
||||
if (palIndex == 256) {
|
||||
// No match found, so find a free slot to use
|
||||
palIndex = -1;
|
||||
while (++palIndex < 256) {
|
||||
if (_usageCount[palIndex] == 0)
|
||||
break;
|
||||
}
|
||||
|
||||
if (palIndex == 256)
|
||||
error("addRange - Ran out of palette space to allocate");
|
||||
|
||||
palData[palIndex].r = data[colIndex].r;
|
||||
palData[palIndex].g = data[colIndex].g;
|
||||
palData[palIndex].b = data[colIndex].b;
|
||||
paletteChanged = true;
|
||||
}
|
||||
|
||||
palIndexes[colIndex] = palIndex;
|
||||
++_usageCount[palIndex];
|
||||
}
|
||||
|
||||
if (paletteChanged) {
|
||||
g_system->getPaletteManager()->setPalette((byte *)&palData[0], 0, 256);
|
||||
reset();
|
||||
}
|
||||
}
|
||||
|
||||
void Palette::deleteRange(RGBList *list) {
|
||||
// Release the reference count on each of the palette entries
|
||||
for (int colIndex = 0; colIndex < list->size(); ++colIndex) {
|
||||
int palIndex = list->palIndexes()[colIndex];
|
||||
assert(_usageCount[palIndex] > 0);
|
||||
--_usageCount[palIndex];
|
||||
}
|
||||
}
|
||||
|
||||
void Palette::deleteAllRanges() {
|
||||
for (int colIndex = 0; colIndex < 255; ++colIndex)
|
||||
_usageCount[colIndex] = 0;
|
||||
}
|
||||
|
||||
void Palette::fadeRange(RGB8 *srcPal, RGB8 *destPal, int startIndex, int endIndex,
|
||||
int numSteps, uint delayAmount) {
|
||||
RGB8 tempPal[256];
|
||||
|
||||
// perform the fade
|
||||
for(int stepCtr = 1; stepCtr <= numSteps; ++stepCtr) {
|
||||
// Delay the specified amount
|
||||
uint32 startTime = g_system->getMillis();
|
||||
while ((g_system->getMillis() - startTime) < delayAmount) {
|
||||
_vm->_events->handleEvents();
|
||||
g_system->delayMillis(10);
|
||||
}
|
||||
|
||||
for (int i = startIndex; i <= endIndex; ++i) {
|
||||
// Handle the intermediate rgb values for fading
|
||||
tempPal[i].r = (byte) (srcPal[i].r + (destPal[i].r - srcPal[i].r) * stepCtr / numSteps);
|
||||
tempPal[i].g = (byte) (srcPal[i].g + (destPal[i].g - srcPal[i].g) * stepCtr / numSteps);
|
||||
tempPal[i].b = (byte) (srcPal[i].b + (destPal[i].b - srcPal[i].b) * stepCtr / numSteps);
|
||||
}
|
||||
|
||||
_vm->_palette->setPalette(&tempPal[startIndex], startIndex, endIndex - startIndex + 1);
|
||||
}
|
||||
|
||||
// Make sure the end palette exactly matches what is wanted
|
||||
_vm->_palette->setPalette(&destPal[startIndex], startIndex, endIndex - startIndex + 1);
|
||||
}
|
||||
|
||||
} // End of namespace MADS
|
110
engines/mads/palette.h
Normal file
110
engines/mads/palette.h
Normal file
@ -0,0 +1,110 @@
|
||||
/* 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 2
|
||||
* of the License, or (at your option) any later version.
|
||||
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef MADS_PALETTE_H
|
||||
#define MADS_PALETTE_H
|
||||
|
||||
#include "common/scummsys.h"
|
||||
|
||||
namespace MADS {
|
||||
|
||||
class MADSEngine;
|
||||
|
||||
struct RGB8 {
|
||||
uint8 r, g, b, u;
|
||||
};
|
||||
|
||||
class RGBList {
|
||||
private:
|
||||
int _size;
|
||||
RGB8 *_data;
|
||||
byte *_palIndexes;
|
||||
bool _freeData;
|
||||
public:
|
||||
RGBList(int numEntries = 256, RGB8 *srcData = NULL, bool freeData = true);
|
||||
~RGBList();
|
||||
|
||||
RGB8 *data() { return _data; }
|
||||
byte *palIndexes() { return _palIndexes; }
|
||||
int size() { return _size; }
|
||||
};
|
||||
|
||||
#define PALETTE_COUNT 256
|
||||
|
||||
class Palette {
|
||||
private:
|
||||
MADSEngine *_vm;
|
||||
bool _colorsChanged;
|
||||
bool _fading_in_progress;
|
||||
byte _originalPalette[PALETTE_COUNT * 4];
|
||||
byte _fadedPalette[PALETTE_COUNT * 4];
|
||||
int _usageCount[PALETTE_COUNT];
|
||||
|
||||
void reset();
|
||||
public:
|
||||
Palette(MADSEngine *vm);
|
||||
|
||||
void setPalette(const byte *colors, uint start, uint num);
|
||||
void setPalette(const RGB8 *colors, uint start, uint num);
|
||||
void grabPalette(byte *colors, uint start, uint num);
|
||||
void grabPalette(RGB8 *colors, uint start, uint num) {
|
||||
grabPalette((byte *)colors, start, num);
|
||||
}
|
||||
uint8 palIndexFromRgb(byte r, byte g, byte b, RGB8 *paletteData = NULL);
|
||||
|
||||
void fadeIn(int numSteps, uint delayAmount, RGB8 *destPalette, int numColors);
|
||||
void fadeIn(int numSteps, uint delayAmount, RGBList *destPalette);
|
||||
static RGB8 *decodeMadsPalette(Common::SeekableReadStream *palStream, int *numColors);
|
||||
int setMadsPalette(Common::SeekableReadStream *palStream, int indexStart = 0);
|
||||
void setMadsSystemPalette();
|
||||
void fadeRange(RGB8 *srcPal, RGB8 *destPal, int startIndex, int endIndex,
|
||||
int numSteps, uint delayAmount);
|
||||
|
||||
// Methods used for reference counting color usage
|
||||
void resetColorCounts();
|
||||
void blockRange(int startIndex, int size);
|
||||
void addRange(RGBList *list);
|
||||
void deleteRange(RGBList *list);
|
||||
void deleteAllRanges();
|
||||
|
||||
// Color indexes
|
||||
uint8 BLACK;
|
||||
uint8 BLUE;
|
||||
uint8 GREEN;
|
||||
uint8 CYAN;
|
||||
uint8 RED;
|
||||
uint8 VIOLET;
|
||||
uint8 BROWN;
|
||||
uint8 LIGHT_GRAY;
|
||||
uint8 DARK_GRAY;
|
||||
uint8 LIGHT_BLUE;
|
||||
uint8 LIGHT_GREEN;
|
||||
uint8 LIGHT_CYAN;
|
||||
uint8 LIGHT_RED;
|
||||
uint8 PINK;
|
||||
uint8 YELLOW;
|
||||
uint8 WHITE;
|
||||
};
|
||||
|
||||
} // End of namespace MADS
|
||||
|
||||
#endif /* MADS_PALETTE_H */
|
32
engines/mads/resources.cpp
Normal file
32
engines/mads/resources.cpp
Normal file
@ -0,0 +1,32 @@
|
||||
/* 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 2
|
||||
* of the License, or (at your option) any later version.
|
||||
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "common/scummsys.h"
|
||||
#include "mads/resources.h"
|
||||
|
||||
namespace MADS {
|
||||
|
||||
ResourcesManager::ResourcesManager(MADSEngine *vm) {
|
||||
_vm = vm;
|
||||
}
|
||||
|
||||
} // End of namespace MADS
|
57
engines/mads/resources.h
Normal file
57
engines/mads/resources.h
Normal file
@ -0,0 +1,57 @@
|
||||
/* 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 2
|
||||
* of the License, or (at your option) any later version.
|
||||
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef MADS_RESOURCES_H
|
||||
#define MADS_RESOURCES_H
|
||||
|
||||
#include "common/scummsys.h"
|
||||
#include "common/stream.h"
|
||||
|
||||
namespace MADS {
|
||||
|
||||
class MADSEngine;
|
||||
|
||||
class ResourcesManager {
|
||||
private:
|
||||
MADSEngine *_vm;
|
||||
public:
|
||||
ResourcesManager(MADSEngine *vm);
|
||||
|
||||
/**
|
||||
* Return a named resource
|
||||
*/
|
||||
Common::SeekableReadStream *get(const Common::String &resourceName) {
|
||||
// TODO
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Release a previously loaded resource
|
||||
*/
|
||||
void toss(const Common::String &resourceName) {
|
||||
// TODO
|
||||
}
|
||||
};
|
||||
|
||||
} // End of namespace MADS
|
||||
|
||||
#endif /* MADS_RESOURCES_H */
|
@ -27,7 +27,9 @@
|
||||
|
||||
namespace MADS {
|
||||
|
||||
SoundManager::SoundManager() {
|
||||
SoundManager::SoundManager(MADSEngine *vm, Audio::Mixer *mixer) {
|
||||
_vm = vm;
|
||||
_mixer = mixer;
|
||||
_asound = nullptr;
|
||||
}
|
||||
|
||||
@ -35,11 +37,6 @@ SoundManager::~SoundManager() {
|
||||
delete _asound;
|
||||
}
|
||||
|
||||
void SoundManager::setVm(MADSEngine *vm, Audio::Mixer *mixer) {
|
||||
_vm = vm;
|
||||
_mixer = mixer;
|
||||
}
|
||||
|
||||
void SoundManager::test() {
|
||||
_asound = new Nebular::ASound1(_mixer);
|
||||
_asound->command(5);
|
||||
|
@ -38,10 +38,9 @@ private:
|
||||
Audio::Mixer *_mixer;
|
||||
Nebular::ASound *_asound;
|
||||
public:
|
||||
SoundManager();
|
||||
SoundManager(MADSEngine *vm, Audio::Mixer *mixer);
|
||||
~SoundManager();
|
||||
|
||||
void setVm(MADSEngine *vm, Audio::Mixer *mixer);
|
||||
void test();
|
||||
void poll();
|
||||
};
|
||||
|
@ -33,10 +33,10 @@
|
||||
|
||||
namespace MADS {
|
||||
|
||||
namespace Nebular {
|
||||
|
||||
class SoundManager;
|
||||
|
||||
namespace Nebular {
|
||||
|
||||
/**
|
||||
* Represents the data for a channel on the Adlib
|
||||
*/
|
||||
|
Loading…
x
Reference in New Issue
Block a user