XEEN: Implemented dynamic data loading for new games
This commit is contained in:
parent
971a70a2b3
commit
feacce66b9
6 changed files with 136 additions and 75 deletions
|
@ -29,45 +29,8 @@
|
||||||
|
|
||||||
namespace Xeen {
|
namespace Xeen {
|
||||||
|
|
||||||
/**
|
CCArchive::CCArchive(const Common::String &filename, bool encoded):
|
||||||
* Xeen CC file implementation
|
_filename(filename), _encoded(encoded) {
|
||||||
*/
|
|
||||||
class CCArchive : public Common::Archive {
|
|
||||||
private:
|
|
||||||
/**
|
|
||||||
* Details of a single entry in a CC file index
|
|
||||||
*/
|
|
||||||
struct CCEntry {
|
|
||||||
uint16 _id;
|
|
||||||
uint32 _offset;
|
|
||||||
uint16 _size;
|
|
||||||
|
|
||||||
CCEntry() : _id(0), _offset(0), _size(0) {}
|
|
||||||
CCEntry(uint16 id, uint32 offset, uint32 size)
|
|
||||||
: _id(id), _offset(offset), _size(size) {
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Common::Array<CCEntry> _index;
|
|
||||||
Common::String _filename;
|
|
||||||
|
|
||||||
uint16 convertNameToId(const Common::String &resourceName) const;
|
|
||||||
|
|
||||||
void loadIndex(Common::SeekableReadStream *stream);
|
|
||||||
|
|
||||||
bool getHeaderEntry(const Common::String &resourceName, CCEntry &ccEntry) const;
|
|
||||||
public:
|
|
||||||
CCArchive(const Common::String &filename);
|
|
||||||
virtual ~CCArchive();
|
|
||||||
|
|
||||||
// Archive implementation
|
|
||||||
virtual bool hasFile(const Common::String &name) const;
|
|
||||||
virtual int listMembers(Common::ArchiveMemberList &list) const;
|
|
||||||
virtual const Common::ArchiveMemberPtr getMember(const Common::String &name) const;
|
|
||||||
virtual Common::SeekableReadStream *createReadStreamForMember(const Common::String &name) const;
|
|
||||||
};
|
|
||||||
|
|
||||||
CCArchive::CCArchive(const Common::String &filename): _filename(filename) {
|
|
||||||
File f(filename);
|
File f(filename);
|
||||||
loadIndex(&f);
|
loadIndex(&f);
|
||||||
}
|
}
|
||||||
|
@ -107,9 +70,11 @@ Common::SeekableReadStream *CCArchive::createReadStreamForMember(const Common::S
|
||||||
byte *data = new byte[ccEntry._size];
|
byte *data = new byte[ccEntry._size];
|
||||||
f.read(data, ccEntry._size);
|
f.read(data, ccEntry._size);
|
||||||
|
|
||||||
// Decrypt the data
|
if (_encoded) {
|
||||||
for (int i = 0; i < ccEntry._size; ++i)
|
// Decrypt the data
|
||||||
data[i] ^= 0x35;
|
for (int i = 0; i < ccEntry._size; ++i)
|
||||||
|
data[i] ^= 0x35;
|
||||||
|
}
|
||||||
|
|
||||||
// Return the data as a stream
|
// Return the data as a stream
|
||||||
return new Common::MemoryReadStream(data, ccEntry._size, DisposeAfterUse::YES);
|
return new Common::MemoryReadStream(data, ccEntry._size, DisposeAfterUse::YES);
|
||||||
|
@ -192,6 +157,9 @@ bool CCArchive::getHeaderEntry(const Common::String &resourceName, CCEntry &ccEn
|
||||||
|
|
||||||
/*------------------------------------------------------------------------*/
|
/*------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiates the resource manager
|
||||||
|
*/
|
||||||
void FileManager::init(XeenEngine *vm) {
|
void FileManager::init(XeenEngine *vm) {
|
||||||
Common::File f;
|
Common::File f;
|
||||||
|
|
||||||
|
|
|
@ -33,11 +33,11 @@ namespace Xeen {
|
||||||
|
|
||||||
class XeenEngine;
|
class XeenEngine;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Main resource manager
|
||||||
|
*/
|
||||||
class FileManager {
|
class FileManager {
|
||||||
public:
|
public:
|
||||||
/**
|
|
||||||
* Instantiates the resource manager
|
|
||||||
*/
|
|
||||||
static void init(XeenEngine *vm);
|
static void init(XeenEngine *vm);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -53,6 +53,45 @@ public:
|
||||||
void openFile(const Common::String &filename);
|
void openFile(const Common::String &filename);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Xeen CC file implementation
|
||||||
|
*/
|
||||||
|
class CCArchive : public Common::Archive {
|
||||||
|
private:
|
||||||
|
/**
|
||||||
|
* Details of a single entry in a CC file index
|
||||||
|
*/
|
||||||
|
struct CCEntry {
|
||||||
|
uint16 _id;
|
||||||
|
uint32 _offset;
|
||||||
|
uint16 _size;
|
||||||
|
|
||||||
|
CCEntry() : _id(0), _offset(0), _size(0) {}
|
||||||
|
CCEntry(uint16 id, uint32 offset, uint32 size)
|
||||||
|
: _id(id), _offset(offset), _size(size) {
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Common::Array<CCEntry> _index;
|
||||||
|
Common::String _filename;
|
||||||
|
bool _encoded;
|
||||||
|
|
||||||
|
uint16 convertNameToId(const Common::String &resourceName) const;
|
||||||
|
|
||||||
|
void loadIndex(Common::SeekableReadStream *stream);
|
||||||
|
|
||||||
|
bool getHeaderEntry(const Common::String &resourceName, CCEntry &ccEntry) const;
|
||||||
|
public:
|
||||||
|
CCArchive(const Common::String &filename, bool encoded = true);
|
||||||
|
virtual ~CCArchive();
|
||||||
|
|
||||||
|
// Archive implementation
|
||||||
|
virtual bool hasFile(const Common::String &name) const;
|
||||||
|
virtual int listMembers(Common::ArchiveMemberList &list) const;
|
||||||
|
virtual const Common::ArchiveMemberPtr getMember(const Common::String &name) const;
|
||||||
|
virtual Common::SeekableReadStream *createReadStreamForMember(const Common::String &name) const;
|
||||||
|
};
|
||||||
|
|
||||||
} // End of namespace Xeen
|
} // End of namespace Xeen
|
||||||
|
|
||||||
#endif /* XEEN_FILES_H */
|
#endif /* XEEN_FILES_H */
|
||||||
|
|
|
@ -23,6 +23,8 @@
|
||||||
#include "common/scummsys.h"
|
#include "common/scummsys.h"
|
||||||
#include "common/algorithm.h"
|
#include "common/algorithm.h"
|
||||||
#include "xeen/saves.h"
|
#include "xeen/saves.h"
|
||||||
|
#include "xeen/files.h"
|
||||||
|
#include "xeen/xeen.h"
|
||||||
|
|
||||||
namespace Xeen {
|
namespace Xeen {
|
||||||
|
|
||||||
|
@ -37,7 +39,7 @@ void AttributePair::synchronize(Common::Serializer &s) {
|
||||||
|
|
||||||
/*------------------------------------------------------------------------*/
|
/*------------------------------------------------------------------------*/
|
||||||
|
|
||||||
Roster::Roster() {
|
Party::Party() {
|
||||||
_partyCount = 0;
|
_partyCount = 0;
|
||||||
_realPartyCount = 0;
|
_realPartyCount = 0;
|
||||||
Common::fill(&_partyMembers[0], &_partyMembers[8], 0);
|
Common::fill(&_partyMembers[0], &_partyMembers[8], 0);
|
||||||
|
@ -76,14 +78,17 @@ Roster::Roster() {
|
||||||
_bankGems = 0;
|
_bankGems = 0;
|
||||||
_totalTime = 0;
|
_totalTime = 0;
|
||||||
_rested = false;
|
_rested = false;
|
||||||
Common::fill(&_gameFlags[0], &_gameFlags[256], false);
|
|
||||||
|
Common::fill(&_gameFlags[0], &_gameFlags[512], false);
|
||||||
Common::fill(&_autoNotes[0], &_autoNotes[128], false);
|
Common::fill(&_autoNotes[0], &_autoNotes[128], false);
|
||||||
Common::fill(&_quests[0], &_quests[64], false);
|
Common::fill(&_quests[0], &_quests[64], false);
|
||||||
Common::fill(&_questItems[0], &_questItems[65], 0);
|
Common::fill(&_questItems[0], &_questItems[85], 0);
|
||||||
Common::fill(&_characterFlags[0][0], &_characterFlags[30][24], false);
|
|
||||||
|
for (int i = 0; i < TOTAL_CHARACTERS; ++i)
|
||||||
|
Common::fill(&_characterFlags[i][0], &_characterFlags[i][24], false);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Roster::synchronize(Common::Serializer &s) {
|
void Party::synchronize(Common::Serializer &s) {
|
||||||
byte dummy[30];
|
byte dummy[30];
|
||||||
Common::fill(&dummy[0], &dummy[30], 0);
|
Common::fill(&dummy[0], &dummy[30], 0);
|
||||||
|
|
||||||
|
@ -111,13 +116,13 @@ void Roster::synchronize(Common::Serializer &s) {
|
||||||
s.syncAsByte(_heroismActive);
|
s.syncAsByte(_heroismActive);
|
||||||
s.syncAsByte(_difficulty);
|
s.syncAsByte(_difficulty);
|
||||||
|
|
||||||
for (int i = 0; ITEMS_COUNT; ++i)
|
for (int i = 0; i < ITEMS_COUNT; ++i)
|
||||||
_blacksmithWeapons[i].synchronize(s);
|
_blacksmithWeapons[i].synchronize(s);
|
||||||
for (int i = 0; ITEMS_COUNT; ++i)
|
for (int i = 0; i < ITEMS_COUNT; ++i)
|
||||||
_blacksmithArmor[i].synchronize(s);
|
_blacksmithArmor[i].synchronize(s);
|
||||||
for (int i = 0; ITEMS_COUNT; ++i)
|
for (int i = 0; i < ITEMS_COUNT; ++i)
|
||||||
_blacksmithAccessories[i].synchronize(s);
|
_blacksmithAccessories[i].synchronize(s);
|
||||||
for (int i = 0; ITEMS_COUNT; ++i)
|
for (int i = 0; i < ITEMS_COUNT; ++i)
|
||||||
_blacksmithMisc[i].synchronize(s);
|
_blacksmithMisc[i].synchronize(s);
|
||||||
|
|
||||||
s.syncAsUint16LE(_cloudsEnd);
|
s.syncAsUint16LE(_cloudsEnd);
|
||||||
|
@ -150,16 +155,17 @@ void Roster::synchronize(Common::Serializer &s) {
|
||||||
for (int i = 0; i < 85; ++i)
|
for (int i = 0; i < 85; ++i)
|
||||||
s.syncAsByte(_questItems[i]);
|
s.syncAsByte(_questItems[i]);
|
||||||
|
|
||||||
for (int i = 0; ITEMS_COUNT; ++i)
|
for (int i = 0; i < ITEMS_COUNT; ++i)
|
||||||
_blacksmithWeapons2[i].synchronize(s);
|
_blacksmithWeapons2[i].synchronize(s);
|
||||||
for (int i = 0; ITEMS_COUNT; ++i)
|
for (int i = 0; i < ITEMS_COUNT; ++i)
|
||||||
_blacksmithArmor2[i].synchronize(s);
|
_blacksmithArmor2[i].synchronize(s);
|
||||||
for (int i = 0; ITEMS_COUNT; ++i)
|
for (int i = 0; i < ITEMS_COUNT; ++i)
|
||||||
_blacksmithAccessories2[i].synchronize(s);
|
_blacksmithAccessories2[i].synchronize(s);
|
||||||
for (int i = 0; ITEMS_COUNT; ++i)
|
for (int i = 0; i < ITEMS_COUNT; ++i)
|
||||||
_blacksmithMisc2[i].synchronize(s);
|
_blacksmithMisc2[i].synchronize(s);
|
||||||
|
|
||||||
SavesManager::syncBitFlags(s, &_characterFlags[0][0], &_characterFlags[30][24]);
|
for (int i = 0; i < TOTAL_CHARACTERS; ++i)
|
||||||
|
SavesManager::syncBitFlags(s, &_characterFlags[i][0], &_characterFlags[i][24]);
|
||||||
s.syncBytes(&dummy[0], 30);
|
s.syncBytes(&dummy[0], 30);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -174,8 +180,8 @@ PlayerStruct::PlayerStruct() {
|
||||||
_dbDay = 0;
|
_dbDay = 0;
|
||||||
_tempAge = 0;
|
_tempAge = 0;
|
||||||
Common::fill(&_skills[0], &_skills[18], 0);
|
Common::fill(&_skills[0], &_skills[18], 0);
|
||||||
Common::fill(&_awards[0], &_awards[64], false);
|
Common::fill(&_awards[0], &_awards[512], false);
|
||||||
Common::fill(&_spells[9], &_spells[40], false);
|
Common::fill(&_spells[9], &_spells[312], false);
|
||||||
_lloydMap = 0;
|
_lloydMap = 0;
|
||||||
_hasSpells = false;
|
_hasSpells = false;
|
||||||
_currentSpell = 0;
|
_currentSpell = 0;
|
||||||
|
@ -220,8 +226,8 @@ void PlayerStruct::synchronize(Common::Serializer &s) {
|
||||||
|
|
||||||
for (int i = 0; i < 18; ++i)
|
for (int i = 0; i < 18; ++i)
|
||||||
s.syncAsByte(_skills[i]);
|
s.syncAsByte(_skills[i]);
|
||||||
SavesManager::syncBitFlags(s, &_awards[0], &_awards[64]);
|
SavesManager::syncBitFlags(s, &_awards[0], &_awards[512]);
|
||||||
SavesManager::syncBitFlags(s, &_spells[0], &_spells[40]);
|
SavesManager::syncBitFlags(s, &_spells[0], &_spells[312]);
|
||||||
|
|
||||||
s.syncAsByte(_lloydMap);
|
s.syncAsByte(_lloydMap);
|
||||||
s.syncAsByte(_lloydPosition.x);
|
s.syncAsByte(_lloydPosition.x);
|
||||||
|
@ -262,6 +268,16 @@ void PlayerStruct::synchronize(Common::Serializer &s) {
|
||||||
|
|
||||||
/*------------------------------------------------------------------------*/
|
/*------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
void Roster::synchronize(Common::Serializer &s) {
|
||||||
|
if (s.isLoading())
|
||||||
|
resize(30);
|
||||||
|
|
||||||
|
for (uint i = 0; i < 30; ++i)
|
||||||
|
(*this)[i].synchronize(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*------------------------------------------------------------------------*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Synchronizes a boolean array as a bitfield set
|
* Synchronizes a boolean array as a bitfield set
|
||||||
*/
|
*/
|
||||||
|
@ -269,9 +285,10 @@ void SavesManager::syncBitFlags(Common::Serializer &s, bool *startP, bool *endP)
|
||||||
byte data = 0;
|
byte data = 0;
|
||||||
|
|
||||||
int bitCounter = 0;
|
int bitCounter = 0;
|
||||||
for (bool *p = startP; p <= endP; ++p, ++bitCounter) {
|
for (bool *p = startP; p <= endP; ++p, bitCounter = (bitCounter + 1) % 8) {
|
||||||
if (bitCounter != 0 && (bitCounter % 8) == 0) {
|
if (p == endP || bitCounter == 0) {
|
||||||
s.syncAsByte(data);
|
if (p != endP || s.isSaving())
|
||||||
|
s.syncAsByte(data);
|
||||||
if (p == endP)
|
if (p == endP)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
@ -286,7 +303,22 @@ void SavesManager::syncBitFlags(Common::Serializer &s, bool *startP, bool *endP)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*------------------------------------------------------------------------*/
|
/**
|
||||||
|
* Sets up the dynamic data for the game for a new game
|
||||||
|
*/
|
||||||
|
void SavesManager::reset() {
|
||||||
|
Common::String name(_vm->getGameID() == GType_Clouds ? "xeen.cur" : "dark.cur");
|
||||||
|
CCArchive cur(name, false);
|
||||||
|
|
||||||
|
Common::SeekableReadStream *chr = cur.createReadStreamForMember("maze.chr");
|
||||||
|
Common::Serializer sChr(chr, nullptr);
|
||||||
|
_roster.synchronize(sChr);
|
||||||
|
delete chr;
|
||||||
|
|
||||||
|
Common::SeekableReadStream *pty = cur.createReadStreamForMember("maze.pty");
|
||||||
|
Common::Serializer sPty(pty, nullptr);
|
||||||
|
_party.synchronize(sPty);
|
||||||
|
delete pty;
|
||||||
|
}
|
||||||
|
|
||||||
} // End of namespace Xeen
|
} // End of namespace Xeen
|
||||||
|
|
|
@ -55,8 +55,11 @@ enum Condition { CURSED = 0, HEART_BROKEN = 1, WEAK = 2, POISONED = 3,
|
||||||
};
|
};
|
||||||
|
|
||||||
#define ITEMS_COUNT 36
|
#define ITEMS_COUNT 36
|
||||||
|
#define TOTAL_CHARACTERS 30
|
||||||
|
|
||||||
class Roster {
|
class XeenEngine;
|
||||||
|
|
||||||
|
class Party {
|
||||||
public:
|
public:
|
||||||
int _partyCount;
|
int _partyCount;
|
||||||
int _realPartyCount;
|
int _realPartyCount;
|
||||||
|
@ -102,7 +105,7 @@ public:
|
||||||
int _bankGems;
|
int _bankGems;
|
||||||
int _totalTime;
|
int _totalTime;
|
||||||
bool _rested;
|
bool _rested;
|
||||||
bool _gameFlags[256];
|
bool _gameFlags[512];
|
||||||
bool _autoNotes[128];
|
bool _autoNotes[128];
|
||||||
bool _quests[64];
|
bool _quests[64];
|
||||||
int _questItems[85];
|
int _questItems[85];
|
||||||
|
@ -112,7 +115,7 @@ public:
|
||||||
XeenItem _blacksmithMisc2[ITEMS_COUNT];
|
XeenItem _blacksmithMisc2[ITEMS_COUNT];
|
||||||
bool _characterFlags[30][24];
|
bool _characterFlags[30][24];
|
||||||
public:
|
public:
|
||||||
Roster();
|
Party();
|
||||||
|
|
||||||
void synchronize(Common::Serializer &s);
|
void synchronize(Common::Serializer &s);
|
||||||
};
|
};
|
||||||
|
@ -145,8 +148,8 @@ public:
|
||||||
int _dbDay;
|
int _dbDay;
|
||||||
int _tempAge;
|
int _tempAge;
|
||||||
int _skills[18];
|
int _skills[18];
|
||||||
bool _awards[64];
|
bool _awards[512];
|
||||||
bool _spells[40];
|
bool _spells[312];
|
||||||
int _lloydMap;
|
int _lloydMap;
|
||||||
Common::Point _lloydPosition;
|
Common::Point _lloydPosition;
|
||||||
bool _hasSpells;
|
bool _hasSpells;
|
||||||
|
@ -177,12 +180,25 @@ public:
|
||||||
void synchronize(Common::Serializer &s);
|
void synchronize(Common::Serializer &s);
|
||||||
};
|
};
|
||||||
|
|
||||||
class SavesManager {
|
class Roster: public Common::Array<PlayerStruct> {
|
||||||
public:
|
public:
|
||||||
|
Roster() {}
|
||||||
|
|
||||||
|
void synchronize(Common::Serializer &s);
|
||||||
|
};
|
||||||
|
|
||||||
|
class SavesManager {
|
||||||
|
private:
|
||||||
|
XeenEngine *_vm;
|
||||||
|
public:
|
||||||
|
Party _party;
|
||||||
Roster _roster;
|
Roster _roster;
|
||||||
Common::Array<PlayerStruct> _conditions;
|
|
||||||
public:
|
public:
|
||||||
static void syncBitFlags(Common::Serializer &s, bool *startP, bool *endP);
|
static void syncBitFlags(Common::Serializer &s, bool *startP, bool *endP);
|
||||||
|
public:
|
||||||
|
SavesManager(XeenEngine *vm) : _vm(vm) {}
|
||||||
|
|
||||||
|
void reset();
|
||||||
};
|
};
|
||||||
|
|
||||||
} // End of namespace Xeen
|
} // End of namespace Xeen
|
||||||
|
|
|
@ -37,6 +37,7 @@ XeenEngine::XeenEngine(OSystem *syst, const XeenGameDescription *gameDesc)
|
||||||
: _gameDescription(gameDesc), Engine(syst), _randomSource("Xeen") {
|
: _gameDescription(gameDesc), Engine(syst), _randomSource("Xeen") {
|
||||||
_debugger = nullptr;
|
_debugger = nullptr;
|
||||||
_events = nullptr;
|
_events = nullptr;
|
||||||
|
_saves = nullptr;
|
||||||
_screen = nullptr;
|
_screen = nullptr;
|
||||||
_sound = nullptr;
|
_sound = nullptr;
|
||||||
_eventData = nullptr;
|
_eventData = nullptr;
|
||||||
|
@ -45,6 +46,7 @@ XeenEngine::XeenEngine(OSystem *syst, const XeenGameDescription *gameDesc)
|
||||||
XeenEngine::~XeenEngine() {
|
XeenEngine::~XeenEngine() {
|
||||||
delete _debugger;
|
delete _debugger;
|
||||||
delete _events;
|
delete _events;
|
||||||
|
delete _saves;
|
||||||
delete _screen;
|
delete _screen;
|
||||||
delete _sound;
|
delete _sound;
|
||||||
delete _eventData;
|
delete _eventData;
|
||||||
|
@ -61,6 +63,7 @@ void XeenEngine::initialize() {
|
||||||
FileManager::init(this);
|
FileManager::init(this);
|
||||||
_debugger = new Debugger(this);
|
_debugger = new Debugger(this);
|
||||||
_events = new EventsManager(this);
|
_events = new EventsManager(this);
|
||||||
|
_saves = new SavesManager(this);
|
||||||
_screen = new Screen(this);
|
_screen = new Screen(this);
|
||||||
_screen->setupWindows();
|
_screen->setupWindows();
|
||||||
_sound = new SoundManager(this);
|
_sound = new SoundManager(this);
|
||||||
|
@ -237,7 +240,8 @@ void XeenEngine::showMainMenu() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void XeenEngine::playGame() {
|
void XeenEngine::playGame() {
|
||||||
|
_saves->reset();
|
||||||
|
// drawUI();
|
||||||
}
|
}
|
||||||
|
|
||||||
void XeenEngine::drawUI() {
|
void XeenEngine::drawUI() {
|
||||||
|
|
|
@ -34,6 +34,7 @@
|
||||||
#include "graphics/surface.h"
|
#include "graphics/surface.h"
|
||||||
#include "xeen/debugger.h"
|
#include "xeen/debugger.h"
|
||||||
#include "xeen/events.h"
|
#include "xeen/events.h"
|
||||||
|
#include "xeen/saves.h"
|
||||||
#include "xeen/screen.h"
|
#include "xeen/screen.h"
|
||||||
#include "xeen/sound.h"
|
#include "xeen/sound.h"
|
||||||
|
|
||||||
|
@ -119,6 +120,7 @@ private:
|
||||||
public:
|
public:
|
||||||
Debugger *_debugger;
|
Debugger *_debugger;
|
||||||
EventsManager *_events;
|
EventsManager *_events;
|
||||||
|
SavesManager *_saves;
|
||||||
Screen *_screen;
|
Screen *_screen;
|
||||||
SoundManager *_sound;
|
SoundManager *_sound;
|
||||||
Mode _mode;
|
Mode _mode;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue