Commit a40afad8 authored by edo9300's avatar edo9300

Merge remote-tracking branch 'refs/remotes/Fluorohydride/master'

parents 287f5d7f 4580cf55
...@@ -654,10 +654,9 @@ void ClientField::ReplaySwap() { ...@@ -654,10 +654,9 @@ void ClientField::ReplaySwap() {
} }
mainGame->dInfo.isFirst = !mainGame->dInfo.isFirst; mainGame->dInfo.isFirst = !mainGame->dInfo.isFirst;
std::swap(mainGame->dInfo.lp[0], mainGame->dInfo.lp[1]); std::swap(mainGame->dInfo.lp[0], mainGame->dInfo.lp[1]);
for(int i = 0; i < 16; ++i) std::swap(mainGame->dInfo.strLP[0], mainGame->dInfo.strLP[1]);
std::swap(mainGame->dInfo.strLP[0][i], mainGame->dInfo.strLP[1][i]); std::swap(mainGame->dInfo.hostname, mainGame->dInfo.clientname);
for(int i = 0; i < 20; ++i) std::swap(mainGame->dInfo.hostname_tag, mainGame->dInfo.clientname_tag);
std::swap(mainGame->dInfo.hostname[i], mainGame->dInfo.clientname[i]);
for(auto chit = chains.begin(); chit != chains.end(); ++chit) { for(auto chit = chains.begin(); chit != chains.end(); ++chit) {
chit->controler = 1 - chit->controler; chit->controler = 1 - chit->controler;
GetChainLocation(chit->controler, chit->location, chit->sequence, &chit->chain_pos); GetChainLocation(chit->controler, chit->location, chit->sequence, &chit->chain_pos);
......
...@@ -1224,6 +1224,7 @@ int DuelClient::ClientAnalyze(char * msg, unsigned int len) { ...@@ -1224,6 +1224,7 @@ int DuelClient::ClientAnalyze(char * msg, unsigned int len) {
ClientCard* pcard; ClientCard* pcard;
mainGame->dField.activatable_cards.clear(); mainGame->dField.activatable_cards.clear();
mainGame->dField.activatable_descs.clear(); mainGame->dField.activatable_descs.clear();
mainGame->dField.conti_cards.clear();
count = BufferIO::ReadInt8(pbuf); count = BufferIO::ReadInt8(pbuf);
for (int i = 0; i < count; ++i) { for (int i = 0; i < count; ++i) {
code = BufferIO::ReadInt32(pbuf); code = BufferIO::ReadInt32(pbuf);
...@@ -1354,6 +1355,7 @@ int DuelClient::ClientAnalyze(char * msg, unsigned int len) { ...@@ -1354,6 +1355,7 @@ int DuelClient::ClientAnalyze(char * msg, unsigned int len) {
} }
mainGame->dField.activatable_cards.clear(); mainGame->dField.activatable_cards.clear();
mainGame->dField.activatable_descs.clear(); mainGame->dField.activatable_descs.clear();
mainGame->dField.conti_cards.clear();
count = BufferIO::ReadInt8(pbuf); count = BufferIO::ReadInt8(pbuf);
for (int i = 0; i < count; ++i) { for (int i = 0; i < count; ++i) {
code = BufferIO::ReadInt32(pbuf); code = BufferIO::ReadInt32(pbuf);
......
...@@ -52,12 +52,12 @@ struct DuelInfo { ...@@ -52,12 +52,12 @@ struct DuelInfo {
bool isFirst; bool isFirst;
bool isTag; bool isTag;
bool isSingleMode; bool isSingleMode;
bool lua64;
bool is_shuffling; bool is_shuffling;
bool tag_player[2]; bool tag_player[2];
int lp[2]; int lp[2];
int startlp; int startlp;
int duel_field; int duel_field;
int lua64;
int extraval; int extraval;
int turn; int turn;
short curMsg; short curMsg;
......
...@@ -60,39 +60,106 @@ bool ReplayMode::ReadReplayResponse() { ...@@ -60,39 +60,106 @@ bool ReplayMode::ReadReplayResponse() {
int ReplayMode::ReplayThread(void* param) { int ReplayMode::ReplayThread(void* param) {
const ReplayHeader& rh = cur_replay.pheader; const ReplayHeader& rh = cur_replay.pheader;
mainGame->dInfo.isFirst = true; mainGame->dInfo.isFirst = true;
mainGame->dInfo.isTag = !!(rh.flag & REPLAY_TAG);
mainGame->dInfo.isSingleMode = !!(rh.flag & REPLAY_SINGLE_MODE);
mainGame->dInfo.lua64 = !!(rh.flag & REPLAY_LUA64);
mainGame->dInfo.tag_player[0] = false;
mainGame->dInfo.tag_player[1] = false;
if(mainGame->dInfo.isSingleMode) {
set_script_reader((script_reader)SingleMode::ScriptReader);
set_card_reader((card_reader)DataManager::CardReader);
set_message_handler((message_handler)MessageHandler);
} else {
set_script_reader(default_script_reader);
set_card_reader((card_reader)DataManager::CardReader);
set_message_handler((message_handler)MessageHandler);
}
if(!StartDuel()) {
EndDuel();
return 0;
}
mainGame->dInfo.isStarted = true;
mainGame->dInfo.isReplay = true;
mainGame->dInfo.isReplaySkiping = (skip_turn > 0);
char engineBuffer[0x1000];
is_continuing = true;
skip_step = 0;
if(mainGame->dInfo.isSingleMode) {
int len = get_message(pduel, (byte*)engineBuffer);
if (len > 0)
is_continuing = ReplayAnalyze(engineBuffer, len);
} else {
ReplayRefreshDeck(0);
ReplayRefreshDeck(1);
ReplayRefreshExtra(0);
ReplayRefreshExtra(1);
}
exit_pending = false;
current_step = 0;
if(mainGame->dInfo.isReplaySkiping)
mainGame->gMutex.Lock();
while (is_continuing && !exit_pending) {
int result = process(pduel);
int len = result & 0xffff;
/*int flag = result >> 16;*/
if (len > 0) {
get_message(pduel, (byte*)engineBuffer);
is_continuing = ReplayAnalyze(engineBuffer, len);
if(is_restarting) {
is_restarting = false;
int step = current_step - 1;
if(step < 0)
step = 0;
if(mainGame->dInfo.isSingleMode) {
is_continuing = true;
skip_step = 0;
int len = get_message(pduel, (byte*)engineBuffer);
if (len > 0) {
mainGame->gMutex.Unlock();
is_continuing = ReplayAnalyze(engineBuffer, len);
mainGame->gMutex.Lock();
}
}
if(step == 0) {
Pause(true, false);
mainGame->dInfo.isStarted = true;
mainGame->dInfo.isReplaySkiping = false;
mainGame->dField.RefreshAllCards();
mainGame->gMutex.Unlock();
}
skip_step = step;
current_step = 0;
}
}
}
if(mainGame->dInfo.isReplaySkiping) {
mainGame->dInfo.isReplaySkiping = false;
mainGame->dField.RefreshAllCards();
mainGame->gMutex.Unlock();
}
EndDuel();
return 0;
}
bool ReplayMode::StartDuel() {
const ReplayHeader& rh = cur_replay.pheader;
mtrandom rnd; mtrandom rnd;
int seed = rh.seed; int seed = rh.seed;
rnd.reset(seed); rnd.reset(seed);
if(rh.flag & REPLAY_TAG) { if(mainGame->dInfo.isTag) {
cur_replay.ReadName(mainGame->dInfo.hostname); cur_replay.ReadName(mainGame->dInfo.hostname);
cur_replay.ReadName(mainGame->dInfo.hostname_tag); cur_replay.ReadName(mainGame->dInfo.hostname_tag);
cur_replay.ReadName(mainGame->dInfo.clientname_tag); cur_replay.ReadName(mainGame->dInfo.clientname_tag);
cur_replay.ReadName(mainGame->dInfo.clientname); cur_replay.ReadName(mainGame->dInfo.clientname);
mainGame->dInfo.isTag = true;
mainGame->dInfo.tag_player[0] = false;
mainGame->dInfo.tag_player[1] = false;
} else { } else {
cur_replay.ReadName(mainGame->dInfo.hostname); cur_replay.ReadName(mainGame->dInfo.hostname);
cur_replay.ReadName(mainGame->dInfo.clientname); cur_replay.ReadName(mainGame->dInfo.clientname);
mainGame->dInfo.isTag = false;
} }
if(rh.flag & REPLAY_SINGLE_MODE) {
set_script_reader((script_reader)SingleMode::ScriptReader);
set_card_reader((card_reader)DataManager::CardReader);
set_message_handler((message_handler)MessageHandler);
mainGame->dInfo.isSingleMode = true;
} else {
set_script_reader(default_script_reader);
set_card_reader((card_reader)DataManager::CardReader);
set_message_handler((message_handler)MessageHandler);
mainGame->dInfo.isSingleMode = false;
}
mainGame->dInfo.lua64 = (rh.flag & REPLAY_LUA64) ? 1 : 0;
pduel = create_duel(rnd.rand()); pduel = create_duel(rnd.rand());
int start_lp = cur_replay.ReadInt32(); int start_lp = cur_replay.ReadInt32();
int start_hand = cur_replay.ReadInt32(); int start_hand = cur_replay.ReadInt32();
int draw_count = cur_replay.ReadInt32(); int draw_count = cur_replay.ReadInt32();
int opt = cur_replay.ReadInt32(); int opt = cur_replay.ReadInt32();
int duel_rule = opt >> 16;
int rule = opt >> 16; //backwards compatibility with master rule replays int rule = opt >> 16; //backwards compatibility with master rule replays
if(rule) if(rule)
switch (rule) { switch (rule) {
...@@ -126,11 +193,7 @@ int ReplayMode::ReplayThread(void* param) { ...@@ -126,11 +193,7 @@ int ReplayMode::ReplayThread(void* param) {
mainGame->dInfo.duel_field = 4; mainGame->dInfo.duel_field = 4;
else if(opt & DUEL_PZONE) else if(opt & DUEL_PZONE)
mainGame->dInfo.duel_field = 3; mainGame->dInfo.duel_field = 3;
mainGame->dInfo.extraval = (opt & SPEED_DUEL) ? 1 : 0; mainGame->dInfo.extraval = !!(opt & SPEED_DUEL);
set_player_info(pduel, 0, start_lp, start_hand, draw_count);
set_player_info(pduel, 1, start_lp, start_hand, draw_count);
mainGame->dInfo.lp[0] = start_lp;
mainGame->dInfo.lp[1] = start_lp;
// reset master rule 4 phase button position // reset master rule 4 phase button position
mainGame->wPhase->setRelativePosition(mainGame->Resize(480, 310, 855, 330)); mainGame->wPhase->setRelativePosition(mainGame->Resize(480, 310, 855, 330));
if(mainGame->dInfo.extraval) { if(mainGame->dInfo.extraval) {
...@@ -168,6 +231,14 @@ int ReplayMode::ReplayThread(void* param) { ...@@ -168,6 +231,14 @@ int ReplayMode::ReplayThread(void* param) {
mainGame->btnEP->setRelativePosition(mainGame->Resize(320, 0, 370, 20)); mainGame->btnEP->setRelativePosition(mainGame->Resize(320, 0, 370, 20));
mainGame->btnShuffle->setRelativePosition(mainGame->Resize(0, 0, 50, 20)); mainGame->btnShuffle->setRelativePosition(mainGame->Resize(0, 0, 50, 20));
} }
set_player_info(pduel, 0, start_lp, start_hand, draw_count);
set_player_info(pduel, 1, start_lp, start_hand, draw_count);
mainGame->dInfo.lp[0] = start_lp;
mainGame->dInfo.lp[1] = start_lp;
mainGame->dInfo.startlp = start_lp;
myswprintf(mainGame->dInfo.strLP[0], L"%d", mainGame->dInfo.lp[0]);
myswprintf(mainGame->dInfo.strLP[1], L"%d", mainGame->dInfo.lp[1]);
mainGame->dInfo.turn = 0;
if(!mainGame->dInfo.isSingleMode) { if(!mainGame->dInfo.isSingleMode) {
if(!(opt & DUEL_TAG_MODE)) { if(!(opt & DUEL_TAG_MODE)) {
int main = cur_replay.ReadInt32(); int main = cur_replay.ReadInt32();
...@@ -218,48 +289,13 @@ int ReplayMode::ReplayThread(void* param) { ...@@ -218,48 +289,13 @@ int ReplayMode::ReplayThread(void* param) {
cur_replay.ReadData(filename, slen); cur_replay.ReadData(filename, slen);
filename[slen] = 0; filename[slen] = 0;
if(!preload_script(pduel, filename, slen)) { if(!preload_script(pduel, filename, slen)) {
end_duel(pduel); return false;
return 0;
} }
} }
myswprintf(mainGame->dInfo.strLP[0], L"%d", mainGame->dInfo.lp[0]);
myswprintf(mainGame->dInfo.strLP[1], L"%d", mainGame->dInfo.lp[1]);
mainGame->dInfo.turn = 0;
start_duel(pduel, opt); start_duel(pduel, opt);
mainGame->dInfo.isStarted = true; return true;
mainGame->dInfo.isReplay = true; }
mainGame->dInfo.isReplaySkiping = (skip_turn > 0); void ReplayMode::EndDuel() {
char engineBuffer[0x1000];
is_continuing = true;
if(mainGame->dInfo.isSingleMode) {
int len = get_message(pduel, (byte*)engineBuffer);
if (len > 0)
is_continuing = ReplayAnalyze(engineBuffer, len);
} else {
ReplayRefreshDeck(0);
ReplayRefreshDeck(1);
ReplayRefreshExtra(0);
ReplayRefreshExtra(1);
}
exit_pending = false;
current_step = 0;
skip_step = 0;
if(mainGame->dInfo.isReplaySkiping)
mainGame->gMutex.Lock();
while (is_continuing && !exit_pending) {
int result = process(pduel);
int len = result & 0xffff;
/*int flag = result >> 16;*/
if (len > 0) {
get_message(pduel, (byte*)engineBuffer);
is_continuing = ReplayAnalyze(engineBuffer, len);
}
}
if(mainGame->dInfo.isReplaySkiping) {
mainGame->dInfo.isReplaySkiping = false;
mainGame->dField.RefreshAllCards();
mainGame->gMutex.Unlock();
}
end_duel(pduel); end_duel(pduel);
if(!is_closing) { if(!is_closing) {
mainGame->actionSignal.Reset(); mainGame->actionSignal.Reset();
...@@ -285,7 +321,6 @@ int ReplayMode::ReplayThread(void* param) { ...@@ -285,7 +321,6 @@ int ReplayMode::ReplayThread(void* param) {
if(exit_on_return) if(exit_on_return)
mainGame->device->closeDevice(); mainGame->device->closeDevice();
} }
return 0;
} }
void ReplayMode::Restart(bool refresh) { void ReplayMode::Restart(bool refresh) {
end_duel(pduel); end_duel(pduel);
...@@ -293,93 +328,12 @@ void ReplayMode::Restart(bool refresh) { ...@@ -293,93 +328,12 @@ void ReplayMode::Restart(bool refresh) {
mainGame->dField.Clear(); mainGame->dField.Clear();
//mainGame->device->setEventReceiver(&mainGame->dField); //mainGame->device->setEventReceiver(&mainGame->dField);
cur_replay.Rewind(); cur_replay.Rewind();
const ReplayHeader& rh = cur_replay.pheader;
//mainGame->dInfo.isFirst = true; //mainGame->dInfo.isFirst = true;
mtrandom rnd; mainGame->dInfo.tag_player[0] = false;
int seed = rh.seed; mainGame->dInfo.tag_player[1] = false;
rnd.reset(seed); if(!StartDuel()) {
if(rh.flag & REPLAY_TAG) { EndDuel();
cur_replay.ReadName(mainGame->dInfo.hostname);
cur_replay.ReadName(mainGame->dInfo.hostname_tag);
cur_replay.ReadName(mainGame->dInfo.clientname_tag);
cur_replay.ReadName(mainGame->dInfo.clientname);
mainGame->dInfo.isTag = true;
mainGame->dInfo.tag_player[0] = false;
mainGame->dInfo.tag_player[1] = false;
} else {
cur_replay.ReadName(mainGame->dInfo.hostname);
cur_replay.ReadName(mainGame->dInfo.clientname);
} }
//set_card_reader((card_reader)DataManager::CardReader);
//set_message_handler((message_handler)MessageHandler);
pduel = create_duel(rnd.rand());
int start_lp = cur_replay.ReadInt32();
int start_hand = cur_replay.ReadInt32();
int draw_count = cur_replay.ReadInt32();
int opt = cur_replay.ReadInt32();
set_player_info(pduel, 0, start_lp, start_hand, draw_count);
set_player_info(pduel, 1, start_lp, start_hand, draw_count);
mainGame->dInfo.lp[0] = start_lp;
mainGame->dInfo.lp[1] = start_lp;
mainGame->dInfo.startlp = start_lp;
myswprintf(mainGame->dInfo.strLP[0], L"%d", mainGame->dInfo.lp[0]);
myswprintf(mainGame->dInfo.strLP[1], L"%d", mainGame->dInfo.lp[1]);
mainGame->dInfo.turn = 0;
if(!mainGame->dInfo.isSingleMode) {
if(!(opt & DUEL_TAG_MODE)) {
int main = cur_replay.ReadInt32();
for(int i = 0; i < main; ++i)
new_card(pduel, cur_replay.ReadInt32(), 0, 0, LOCATION_DECK, 0, POS_FACEDOWN_DEFENSE);
int extra = cur_replay.ReadInt32();
for(int i = 0; i < extra; ++i)
new_card(pduel, cur_replay.ReadInt32(), 0, 0, LOCATION_EXTRA, 0, POS_FACEDOWN_DEFENSE);
mainGame->dField.Initial(0, main, extra);
main = cur_replay.ReadInt32();
for(int i = 0; i < main; ++i)
new_card(pduel, cur_replay.ReadInt32(), 1, 1, LOCATION_DECK, 0, POS_FACEDOWN_DEFENSE);
extra = cur_replay.ReadInt32();
for(int i = 0; i < extra; ++i)
new_card(pduel, cur_replay.ReadInt32(), 1, 1, LOCATION_EXTRA, 0, POS_FACEDOWN_DEFENSE);
mainGame->dField.Initial(1, main, extra);
} else {
int main = cur_replay.ReadInt32();
for(int i = 0; i < main; ++i)
new_card(pduel, cur_replay.ReadInt32(), 0, 0, LOCATION_DECK, 0, POS_FACEDOWN_DEFENSE);
int extra = cur_replay.ReadInt32();
for(int i = 0; i < extra; ++i)
new_card(pduel, cur_replay.ReadInt32(), 0, 0, LOCATION_EXTRA, 0, POS_FACEDOWN_DEFENSE);
mainGame->dField.Initial(0, main, extra);
main = cur_replay.ReadInt32();
for(int i = 0; i < main; ++i)
new_tag_card(pduel, cur_replay.ReadInt32(), 0, LOCATION_DECK);
extra = cur_replay.ReadInt32();
for(int i = 0; i < extra; ++i)
new_tag_card(pduel, cur_replay.ReadInt32(), 0, LOCATION_EXTRA);
main = cur_replay.ReadInt32();
for(int i = 0; i < main; ++i)
new_card(pduel, cur_replay.ReadInt32(), 1, 1, LOCATION_DECK, 0, POS_FACEDOWN_DEFENSE);
extra = cur_replay.ReadInt32();
for(int i = 0; i < extra; ++i)
new_card(pduel, cur_replay.ReadInt32(), 1, 1, LOCATION_EXTRA, 0, POS_FACEDOWN_DEFENSE);
mainGame->dField.Initial(1, main, extra);
main = cur_replay.ReadInt32();
for(int i = 0; i < main; ++i)
new_tag_card(pduel, cur_replay.ReadInt32(), 1, LOCATION_DECK);
extra = cur_replay.ReadInt32();
for(int i = 0; i < extra; ++i)
new_tag_card(pduel, cur_replay.ReadInt32(), 1, LOCATION_EXTRA);
}
} else {
char filename[256];
size_t slen = cur_replay.ReadInt16();
cur_replay.ReadData(filename, slen);
filename[slen] = 0;
if(!preload_script(pduel, filename, slen)) {
end_duel(pduel);
return;
}
}
start_duel(pduel, opt);
if(refresh) { if(refresh) {
mainGame->dField.RefreshAllCards(); mainGame->dField.RefreshAllCards();
mainGame->dInfo.isStarted = true; mainGame->dInfo.isStarted = true;
...@@ -389,17 +343,12 @@ void ReplayMode::Restart(bool refresh) { ...@@ -389,17 +343,12 @@ void ReplayMode::Restart(bool refresh) {
is_restarting = true; is_restarting = true;
} }
void ReplayMode::Undo() { void ReplayMode::Undo() {
if(skip_step > 0 || current_step == 0)
return;
mainGame->dInfo.isReplaySkiping = true;
Restart(false); Restart(false);
skip_step = current_step - 1; mainGame->gMutex.Lock();
if(skip_step < 0) Pause(false, false);
skip_step = 0;
current_step = 0;
if(skip_step) {
mainGame->dInfo.isReplaySkiping = true;
mainGame->gMutex.Lock();
Pause(false, false);
} else
mainGame->dInfo.isReplaySkiping = false;
} }
bool ReplayMode::ReplayAnalyze(char* msg, unsigned int len) { bool ReplayMode::ReplayAnalyze(char* msg, unsigned int len) {
char* pbuf = msg; char* pbuf = msg;
...@@ -409,7 +358,7 @@ bool ReplayMode::ReplayAnalyze(char* msg, unsigned int len) { ...@@ -409,7 +358,7 @@ bool ReplayMode::ReplayAnalyze(char* msg, unsigned int len) {
if(is_closing) if(is_closing)
return false; return false;
if(is_restarting) { if(is_restarting) {
is_restarting = false; //is_restarting = false;
return true; return true;
} }
if(is_swaping) { if(is_swaping) {
...@@ -959,6 +908,7 @@ bool ReplayMode::ReplayAnalyze(char* msg, unsigned int len) { ...@@ -959,6 +908,7 @@ bool ReplayMode::ReplayAnalyze(char* msg, unsigned int len) {
} }
} }
if(pauseable) { if(pauseable) {
current_step++;
if(skip_step) { if(skip_step) {
skip_step--; skip_step--;
if(skip_step == 0) { if(skip_step == 0) {
...@@ -975,7 +925,6 @@ bool ReplayMode::ReplayAnalyze(char* msg, unsigned int len) { ...@@ -975,7 +925,6 @@ bool ReplayMode::ReplayAnalyze(char* msg, unsigned int len) {
mainGame->actionSignal.Wait(); mainGame->actionSignal.Wait();
is_paused = false; is_paused = false;
} }
current_step++;
} }
} }
return true; return true;
...@@ -995,7 +944,7 @@ void ReplayMode::ReplayRefresh(int flag) { ...@@ -995,7 +944,7 @@ void ReplayMode::ReplayRefresh(int flag) {
/*len = */query_field_card(pduel, 1, LOCATION_HAND, flag, queryBuffer, 0); /*len = */query_field_card(pduel, 1, LOCATION_HAND, flag, queryBuffer, 0);
mainGame->dField.UpdateFieldCard(mainGame->LocalPlayer(1), LOCATION_HAND, (char*)queryBuffer); mainGame->dField.UpdateFieldCard(mainGame->LocalPlayer(1), LOCATION_HAND, (char*)queryBuffer);
} }
void ReplayMode::ReplayRefreshHand(int player, int flag) { void ReplayMode::ReplayRefreshHand(int player, int flag) {
unsigned char queryBuffer[0x2000]; unsigned char queryBuffer[0x2000];
/*int len = */query_field_card(pduel, player, LOCATION_HAND, flag, queryBuffer, 0); /*int len = */query_field_card(pduel, player, LOCATION_HAND, flag, queryBuffer, 0);
mainGame->dField.UpdateFieldCard(mainGame->LocalPlayer(player), LOCATION_HAND, (char*)queryBuffer); mainGame->dField.UpdateFieldCard(mainGame->LocalPlayer(player), LOCATION_HAND, (char*)queryBuffer);
......
...@@ -33,6 +33,8 @@ public: ...@@ -33,6 +33,8 @@ public:
static void Pause(bool is_pause, bool is_step); static void Pause(bool is_pause, bool is_step);
static bool ReadReplayResponse(); static bool ReadReplayResponse();
static int ReplayThread(void* param); static int ReplayThread(void* param);
static bool StartDuel();
static void EndDuel();
static void Restart(bool refresh); static void Restart(bool refresh);
static void Undo(); static void Undo();
static bool ReplayAnalyze(char* msg, unsigned int len); static bool ReplayAnalyze(char* msg, unsigned int len);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment