Database saving update (#104)

* implemented saving BatteryN and BatteryW

* implemented saving mentor

* moved int64->blob parsing to a separate function

* moved parsing blob->int64 to a separate function

* added functions for parsing int32->blob and vice versa

* added functions for parsing int16->blob and vice versa

* WIP saving quest items and active tasks

* Quest items are stored in inventory table instead of blob

* added sanity check for missionId

* saving active missions works

* removed unneccesary include

* implemented saving warplocationflag, skywaylocationflag and currentmissionid in database

* INFO DB message now shows how many accounts and player characters are in the database

* fixed dbsaveinterval being in [login] instead of [shard]

* fixed mission quit:
- fixed wrong json name, causing qitems not deleting properly
- quitting mission now resets npc kill count

* adjusted saving active missions

* removed blob parsing functions that ended up being unused

* removed accidentaly added include

* removed sending PCStyle2 on Player Enter

* added a sanity check in itemMoveHandler

* removed MapNum from PCLoad, as client doesn't even read it

* set BuddyWarpCooldown to 60s on PCLoad

* fixed a bug causing EXIT DUPLICATE not working

* added creation and last login timestamps to accounts and players

* added a sanity check for P_CL2LS_REQ_PC_EXIT_DUPLICATE

* implemented web api support, toggled by new setting (off by default)

* add usewebapi to config

Co-authored-by: Gent <gentsemaj@live.com>
This commit is contained in:
kamilprzyb 2020-09-21 21:43:53 +02:00 committed by GitHub
parent 321dca3f79
commit 5e0948ea93
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 332 additions and 58 deletions

View File

@ -14,6 +14,8 @@ acceptallcustomnames=true
# how often should everything be flushed to the database? # how often should everything be flushed to the database?
# the default is 4 minutes # the default is 4 minutes
dbsaveinterval=240 dbsaveinterval=240
# use the web API
usewebapi=false
# Shard Server configuration # Shard Server configuration
[shard] [shard]

View File

@ -53,17 +53,27 @@ void CNLoginServer::handlePacket(CNSocket* sock, CNPacketData* data) {
else else
{ {
std::unique_ptr<Database::Account> findUser = Database::findAccount(userLogin); std::unique_ptr<Database::Account> findUser = Database::findAccount(userLogin);
// if account not found, create it // if account not found
if (findUser == nullptr) if (findUser == nullptr)
{ {
loginSessions[sock] = CNLoginData(); //web api takes care of registration
loginSessions[sock].userID = Database::addAccount(userLogin, userPassword); if (settings::USEWEBAPI)
loginSessions[sock].slot = 1; errorCode = (int)LoginError::ID_DOESNT_EXIST;
success = true; //without web api, create new account
else
{
loginSessions[sock] = CNLoginData();
loginSessions[sock].userID = Database::addAccount(userLogin, userPassword);
loginSessions[sock].slot = 1;
success = true;
}
} }
// if user exists, check if password is correct // if user exists, check if password is correct
else if (CNLoginServer::isPasswordCorrect(findUser->Password, userPassword)) else if (CNLoginServer::isPasswordCorrect(findUser->Password, userPassword))
{ {
/*calling this here to timestamp login attempt,
* in order to make duplicate exit sanity check work*/
Database::updateSelected(findUser->AccountID, findUser->Selected);
// check if account isn't currently in use // check if account isn't currently in use
if (CNLoginServer::isAccountInUse(findUser->AccountID) || if (CNLoginServer::isAccountInUse(findUser->AccountID) ||
PlayerManager::isAccountInUse(findUser->AccountID)) PlayerManager::isAccountInUse(findUser->AccountID))
@ -100,7 +110,7 @@ void CNLoginServer::handlePacket(CNSocket* sock, CNPacketData* data) {
resp.iPaymentFlag = 1; resp.iPaymentFlag = 1;
resp.iOpenBetaFlag = 0; resp.iOpenBetaFlag = 0;
resp.uiSvrTime = getTime(); resp.uiSvrTime = getTime();
// send the resp in with original key // send the resp in with original key
sock->sendPacket((void*)&resp, P_LS2CL_REP_LOGIN_SUCC, sizeof(sP_LS2CL_REP_LOGIN_SUCC)); sock->sendPacket((void*)&resp, P_LS2CL_REP_LOGIN_SUCC, sizeof(sP_LS2CL_REP_LOGIN_SUCC));
@ -144,10 +154,8 @@ void CNLoginServer::handlePacket(CNSocket* sock, CNPacketData* data) {
// Failure // Failure
else { else {
INITSTRUCT(sP_LS2CL_REP_LOGIN_FAIL, resp); INITSTRUCT(sP_LS2CL_REP_LOGIN_FAIL, resp);
U8toU16(userLogin, resp.szID);
memcpy(resp.szID, login->szID, sizeof(char16_t) * 33);
resp.iErrorCode = errorCode; resp.iErrorCode = errorCode;
sock->sendPacket((void*)&resp, P_LS2CL_REP_LOGIN_FAIL, sizeof(sP_LS2CL_REP_LOGIN_FAIL)); sock->sendPacket((void*)&resp, P_LS2CL_REP_LOGIN_FAIL, sizeof(sP_LS2CL_REP_LOGIN_FAIL));
} }
@ -348,8 +356,22 @@ void CNLoginServer::handlePacket(CNSocket* sock, CNPacketData* data) {
sP_CL2LS_REQ_PC_EXIT_DUPLICATE* exit = (sP_CL2LS_REQ_PC_EXIT_DUPLICATE*)data->buf; sP_CL2LS_REQ_PC_EXIT_DUPLICATE* exit = (sP_CL2LS_REQ_PC_EXIT_DUPLICATE*)data->buf;
auto account = Database::findAccount(U16toU8(exit->szID)); auto account = Database::findAccount(U16toU8(exit->szID));
//sanity check
if (account == nullptr) if (account == nullptr)
{
std::cout << "[WARN] P_CL2LS_REQ_PC_EXIT_DUPLICATE submitted unknown username: " << exit->szID << std::endl;
break; break;
}
/* sanity check
* client is supposed to send us user password for verification,
* however it never sends it (>_<)
* therefore, we check if the account made a login attempt within last 30s
*/
if (account->LastLogin + 30000 < getTime())
{
std::cout << "[WARN] P_CL2LS_REQ_PC_EXIT_DUPLICATE submitted without a login attempt on: " << exit->szID << std::endl;
break;
}
int accountId = account->AccountID; int accountId = account->AccountID;
if (!exitDuplicate(accountId)) if (!exitDuplicate(accountId))
@ -407,11 +429,19 @@ bool CNLoginServer::exitDuplicate(int accountId) {
bool CNLoginServer::isLoginDataGood(std::string login, std::string password) { bool CNLoginServer::isLoginDataGood(std::string login, std::string password) {
std::regex loginRegex("[a-zA-Z0-9_-]{4,32}"); std::regex loginRegex("[a-zA-Z0-9_-]{4,32}");
//web api sends password hashed, so we don't check it
if (settings::USEWEBAPI)
return (std::regex_match(login, loginRegex));
//without web api
std::regex passwordRegex("[a-zA-Z0-9!@#$%^&*()_+]{8,32}"); std::regex passwordRegex("[a-zA-Z0-9!@#$%^&*()_+]{8,32}");
return (std::regex_match(login, loginRegex) && std::regex_match(password, passwordRegex)); return (std::regex_match(login, loginRegex) && std::regex_match(password, passwordRegex));
} }
bool CNLoginServer::isPasswordCorrect(std::string actualPassword, std::string tryPassword) { bool CNLoginServer::isPasswordCorrect(std::string actualPassword, std::string tryPassword) {
//web api sends password already hashed
if (settings::USEWEBAPI)
return actualPassword == tryPassword;
//without web api
return BCrypt::validatePassword(tryPassword, actualPassword); return BCrypt::validatePassword(tryPassword, actualPassword);
} }

View File

@ -18,7 +18,9 @@ auto db = make_storage("database.db",
make_column("AccountID", &Database::Account::AccountID, autoincrement(), primary_key()), make_column("AccountID", &Database::Account::AccountID, autoincrement(), primary_key()),
make_column("Login", &Database::Account::Login), make_column("Login", &Database::Account::Login),
make_column("Password", &Database::Account::Password), make_column("Password", &Database::Account::Password),
make_column("Selected", &Database::Account::Selected) make_column("Selected", &Database::Account::Selected),
make_column("Created", &Database::Account::Created),
make_column("LastLogin", &Database::Account::LastLogin)
), ),
make_table("Players", make_table("Players",
make_column("PlayerID", &Database::DbPlayer::PlayerID, autoincrement(), primary_key()), make_column("PlayerID", &Database::DbPlayer::PlayerID, autoincrement(), primary_key()),
@ -26,6 +28,8 @@ auto db = make_storage("database.db",
make_column("Slot", &Database::DbPlayer::slot), make_column("Slot", &Database::DbPlayer::slot),
make_column("Firstname", &Database::DbPlayer::FirstName, collate_nocase()), make_column("Firstname", &Database::DbPlayer::FirstName, collate_nocase()),
make_column("LastName", &Database::DbPlayer::LastName, collate_nocase()), make_column("LastName", &Database::DbPlayer::LastName, collate_nocase()),
make_column("Created", &Database::DbPlayer::Created),
make_column("LastLogin", &Database::DbPlayer::LastLogin),
make_column("Level", &Database::DbPlayer::Level), make_column("Level", &Database::DbPlayer::Level),
make_column("Nano1", &Database::DbPlayer::Nano1), make_column("Nano1", &Database::DbPlayer::Nano1),
make_column("Nano2", &Database::DbPlayer::Nano2), make_column("Nano2", &Database::DbPlayer::Nano2),
@ -51,7 +55,14 @@ auto db = make_storage("database.db",
make_column("isGM", &Database::DbPlayer::isGM), make_column("isGM", &Database::DbPlayer::isGM),
make_column("FusionMatter", &Database::DbPlayer::FusionMatter), make_column("FusionMatter", &Database::DbPlayer::FusionMatter),
make_column("Taros", &Database::DbPlayer::Taros), make_column("Taros", &Database::DbPlayer::Taros),
make_column("Quests", &Database::DbPlayer::QuestFlag) make_column("Quests", &Database::DbPlayer::QuestFlag),
make_column("BatteryW", &Database::DbPlayer::BatteryW),
make_column("BatteryN", &Database::DbPlayer::BatteryN),
make_column("Mentor", &Database::DbPlayer::Mentor),
make_column("WarpLocationFlag", &Database::DbPlayer::WarpLocationFlag),
make_column("SkywayLocationFlag1", &Database::DbPlayer::SkywayLocationFlag1),
make_column("SkywayLocationFlag2", &Database::DbPlayer::SkywayLocationFlag2),
make_column("CurrentMissionID", &Database::DbPlayer::CurrentMissionID)
), ),
make_table("Inventory", make_table("Inventory",
make_column("PlayerId", &Database::Inventory::playerId), make_column("PlayerId", &Database::Inventory::playerId),
@ -66,6 +77,13 @@ auto db = make_storage("database.db",
make_column("Id", &Database::Nano::iID), make_column("Id", &Database::Nano::iID),
make_column("Skill", &Database::Nano::iSkillID), make_column("Skill", &Database::Nano::iSkillID),
make_column("Stamina", &Database::Nano::iStamina) make_column("Stamina", &Database::Nano::iStamina)
),
make_table("RunningQuests",
make_column("PlayerId", &Database::DbQuest::PlayerId),
make_column("TaskId", &Database::DbQuest::TaskId),
make_column("RemainingNPCCount1", &Database::DbQuest::RemainingNPCCount1),
make_column("RemainingNPCCount2", &Database::DbQuest::RemainingNPCCount2),
make_column("RemainingNPCCount3", &Database::DbQuest::RemainingNPCCount3)
) )
); );
@ -78,25 +96,48 @@ void Database::open()
// this parameter means it will try to preserve data during migration // this parameter means it will try to preserve data during migration
bool preserve = true; bool preserve = true;
db.sync_schema(preserve); db.sync_schema(preserve);
DEBUGLOG( std::cout << "[INFO] Database in operation ";
std::cout << "[DB] Database in operation" << std::endl; int accounts = getAccountsCount();
) int players = getPlayersCount();
std::string message = "";
if (accounts > 0) {
message += ": Found " + std::to_string(accounts) + " Account";
if (accounts > 1)
message += "s";
}
if (players > 0) {
message += " and " + std::to_string(players) + " Player Character";
if (players > 1)
message += "s";
}
std::cout << message << std::endl;
}
int Database::getAccountsCount() {
return db.count<Account>();
}
int Database::getPlayersCount() {
return db.count<DbPlayer>();
} }
int Database::addAccount(std::string login, std::string password) int Database::addAccount(std::string login, std::string password)
{ {
password = BCrypt::generateHash(password); password = BCrypt::generateHash(password);
Account x = {}; Account account = {};
x.Login = login; account.Login = login;
x.Password = password; account.Password = password;
x.Selected = 1; account.Selected = 1;
return db.insert(x); account.Created = getTime();
return db.insert(account);
} }
void Database::updateSelected(int accountId, int slot) void Database::updateSelected(int accountId, int slot)
{ {
Account acc = db.get<Account>(accountId); Account acc = db.get<Account>(accountId);
acc.Selected = slot; acc.Selected = slot;
//timestamp
acc.LastLogin = getTime();
db.update(acc); db.update(acc);
} }
@ -129,7 +170,9 @@ int Database::createCharacter(sP_CL2LS_REQ_SAVE_CHAR_NAME* save, int AccountID)
return -1; return -1;
DbPlayer create = {}; DbPlayer create = {};
//set timestamp
create.Created = getTime();
// save packet data // save packet data
create.FirstName = U16toU8(save->szFirstName); create.FirstName = U16toU8(save->szFirstName);
create.LastName = U16toU8(save->szLastName); create.LastName = U16toU8(save->szLastName);
@ -165,6 +208,8 @@ int Database::createCharacter(sP_CL2LS_REQ_SAVE_CHAR_NAME* save, int AccountID)
create.z_coordinates = settings::SPAWN_Z; create.z_coordinates = settings::SPAWN_Z;
create.angle = settings::SPAWN_ANGLE; create.angle = settings::SPAWN_ANGLE;
create.QuestFlag = std::vector<char>(); create.QuestFlag = std::vector<char>();
//set mentor to computress
create.Mentor = 5;
return db.insert(create); return db.insert(create);
} }
@ -285,7 +330,8 @@ void Database::changeName(sP_CL2LS_REQ_CHANGE_CHAR_NAME* save) {
Database::DbPlayer Database::playerToDb(Player *player) Database::DbPlayer Database::playerToDb(Player *player)
{ {
DbPlayer result = {}; // fixes some weird memory errors, this zeros out the members (not the padding inbetween though) //TODO: move stuff that is never updated to separate table so it doesn't try to update it every time
DbPlayer result = {};
result.PlayerID = player->iID; result.PlayerID = player->iID;
result.AccountID = player->accountId; result.AccountID = player->accountId;
@ -318,22 +364,24 @@ Database::DbPlayer Database::playerToDb(Player *player)
result.Nano1 = player->equippedNanos[0]; result.Nano1 = player->equippedNanos[0];
result.Nano2 = player->equippedNanos[1]; result.Nano2 = player->equippedNanos[1];
result.Nano3 = player->equippedNanos[2]; result.Nano3 = player->equippedNanos[2];
result.BatteryN = player->batteryN;
result.BatteryW = player->batteryW;
result.Mentor = player->mentor;
result.WarpLocationFlag = player->iWarpLocationFlag;
result.SkywayLocationFlag1 = player->aSkywayLocationFlag[0];
result.SkywayLocationFlag2 = player->aSkywayLocationFlag[1];
result.CurrentMissionID = player->CurrentMissionID;
// quests // finished quests: parsing to blob
result.QuestFlag = std::vector<char>(); result.QuestFlag = std::vector<char>();
// parsing long array to char vector
for (int i=0; i<16; i++) for (int i=0; i<16; i++)
{ {
int64_t temp = player->aQuestFlag[i]; int64_t flag = player->aQuestFlag[i];
for (int j = 0; j < 8; j++) { appendBlob(&result.QuestFlag, flag);
int64_t check2 = (temp >> (8 * (7 - j)));
char toadd = check2;
result.QuestFlag.push_back(
toadd
);
}
} }
//timestamp
result.LastLogin = getTime();
result.Created = getDbPlayerById(player->iID).Created;
return result; return result;
} }
@ -370,28 +418,31 @@ Player Database::DbToPlayer(DbPlayer player) {
result.angle = player.angle; result.angle = player.angle;
result.money = player.Taros; result.money = player.Taros;
result.fusionmatter = player.FusionMatter; result.fusionmatter = player.FusionMatter;
result.batteryN = player.BatteryN;
result.batteryW = player.BatteryW;
result.mentor = player.Mentor;
result.CurrentMissionID = player.CurrentMissionID;
result.equippedNanos[0] = player.Nano1; result.equippedNanos[0] = player.Nano1;
result.equippedNanos[1] = player.Nano2; result.equippedNanos[1] = player.Nano2;
result.equippedNanos[2] = player.Nano3; result.equippedNanos[2] = player.Nano3;
result.iWarpLocationFlag = player.WarpLocationFlag;
result.aSkywayLocationFlag[0] = player.SkywayLocationFlag1;
result.aSkywayLocationFlag[1] = player.SkywayLocationFlag2;
Database::getInventory(&result); Database::getInventory(&result);
Database::getNanos(&result); Database::getNanos(&result);
Database::getQuests(&result);
std::vector<char>::iterator it = player.QuestFlag.begin(); std::vector<char>::iterator it = player.QuestFlag.begin();
for (int i = 0; i < 16; i++) for (int i = 0; i < 16; i++)
{ {
if (it == player.QuestFlag.end()) if (it == player.QuestFlag.end())
break; break;
result.aQuestFlag[i] = blobToInt64(it);
int64_t toAdd = 0; //move iterator to the next flag
for (int j = 0; j < 8; j++) { it += 8;
int64_t temp = *it;
int64_t check2 = (temp << (8 * (7 - j)));
toAdd += check2;
it++;
}
result.aQuestFlag[i] = toAdd;
} }
return result; return result;
@ -417,6 +468,7 @@ void Database::updatePlayer(Player *player) {
db.update(toUpdate); db.update(toUpdate);
updateInventory(player); updateInventory(player);
updateNanos(player); updateNanos(player);
updateQuests(player);
} }
void Database::updateInventory(Player *player){ void Database::updateInventory(Player *player){
@ -468,8 +520,23 @@ void Database::updateInventory(Player *player){
db.insert(toAdd); db.insert(toAdd);
} }
} }
// insert quest items
for (int i = 0; i < AQINVEN_COUNT; i++) {
if (player->QInven[i].iID != 0) {
sItemBase* next = &player->QInven[i];
Inventory toAdd = {};
toAdd.playerId = player->iID;
toAdd.slot = i + AEQUIP_COUNT + AINVEN_COUNT + ABANK_COUNT;
toAdd.id = next->iID;
toAdd.Opt = next->iOpt;
toAdd.Type = next->iType;
toAdd.TimeLimit = next->iTimeLimit;
db.insert(toAdd);
}
}
db.commit(); db.commit();
} }
void Database::updateNanos(Player *player) { void Database::updateNanos(Player *player) {
// start transaction // start transaction
db.begin_transaction(); db.begin_transaction();
@ -492,6 +559,30 @@ void Database::updateNanos(Player *player) {
} }
db.commit(); db.commit();
} }
void Database::updateQuests(Player* player) {
// start transaction
db.begin_transaction();
// remove all
db.remove_all<DbQuest>(
where(c(&DbQuest::PlayerId) == player->iID)
);
// insert
for (int i = 0; i < ACTIVE_MISSION_COUNT; i++)
{
if (player->tasks[i] == 0)
continue;
DbQuest toAdd = {};
toAdd.PlayerId = player->iID;
toAdd.TaskId = player->tasks[i];
toAdd.RemainingNPCCount1 = player->RemainingNPCCount[i][0];
toAdd.RemainingNPCCount2 = player->RemainingNPCCount[i][1];
toAdd.RemainingNPCCount3 = player->RemainingNPCCount[i][2];
db.insert(toAdd);
}
db.commit();
}
void Database::getInventory(Player* player) { void Database::getInventory(Player* player) {
// get items from DB // get items from DB
auto items = db.get_all<Inventory>( auto items = db.get_all<Inventory>(
@ -505,15 +596,18 @@ void Database::getInventory(Player* player) {
toSet.iOpt = current.Opt; toSet.iOpt = current.Opt;
toSet.iTimeLimit = current.TimeLimit; toSet.iTimeLimit = current.TimeLimit;
// assign to proper arrays // assign to proper arrays
if (current.slot <= AEQUIP_COUNT) if (current.slot < AEQUIP_COUNT)
player->Equip[current.slot] = toSet; player->Equip[current.slot] = toSet;
else if (current.slot <= (AEQUIP_COUNT + AINVEN_COUNT)) else if (current.slot < (AEQUIP_COUNT + AINVEN_COUNT))
player->Inven[current.slot - AEQUIP_COUNT] = toSet; player->Inven[current.slot - AEQUIP_COUNT] = toSet;
else else if (current.slot < (AEQUIP_COUNT + AINVEN_COUNT + ABANK_COUNT))
player->Bank[current.slot - AEQUIP_COUNT - AINVEN_COUNT] = toSet; player->Bank[current.slot - AEQUIP_COUNT - AINVEN_COUNT] = toSet;
else
player->QInven[current.slot - AEQUIP_COUNT - AINVEN_COUNT - ABANK_COUNT] = toSet;
} }
} }
void Database::getNanos(Player* player) { void Database::getNanos(Player* player) {
// get from DB // get from DB
auto nanos = db.get_all<Nano>( auto nanos = db.get_all<Nano>(
@ -527,4 +621,42 @@ void Database::getNanos(Player* player) {
toSet->iStamina = current.iStamina; toSet->iStamina = current.iStamina;
} }
} }
void Database::getQuests(Player* player) {
// get from DB
auto quests = db.get_all<DbQuest>(
where(c(&DbQuest::PlayerId) == player->iID)
);
// set
int i = 0;
for (const DbQuest& current : quests) {
player->tasks[i] = current.TaskId;
player->RemainingNPCCount[i][0] = current.RemainingNPCCount1;
player->RemainingNPCCount[i][1] = current.RemainingNPCCount2;
player->RemainingNPCCount[i][2] = current.RemainingNPCCount3;
i++;
}
}
#pragma endregion ShardServer #pragma endregion ShardServer
#pragma region parsingBlobs
void Database::appendBlob(std::vector<char> *blob, int64_t input) {
for (int i = 0; i < 8; i++) {
char toadd = (input >> (8 * (7 - i)));
blob->push_back(toadd);
}
}
int64_t Database::blobToInt64(std::vector<char>::iterator it) {
int64_t result = 0;
for (int i = 0; i < 8; i++) {
int64_t toAdd = ((int64_t)*it << (8 * (7 - i)));
result += toAdd;
it++;
}
return result;
}
#pragma endregion parsingBlobs

View File

@ -13,6 +13,8 @@ namespace Database {
std::string Login; std::string Login;
std::string Password; std::string Password;
int Selected; int Selected;
uint64_t Created;
uint64_t LastLogin;
}; };
struct Inventory struct Inventory
{ {
@ -36,6 +38,8 @@ namespace Database {
short int slot; short int slot;
std::string FirstName; std::string FirstName;
std::string LastName; std::string LastName;
uint64_t Created;
uint64_t LastLogin;
short int Level; short int Level;
int Nano1; int Nano1;
int Nano2; int Nano2;
@ -62,15 +66,30 @@ namespace Database {
int z_coordinates; int z_coordinates;
int angle; int angle;
short int PCState; short int PCState;
int BatteryW;
int BatteryN;
int16_t Mentor;
std::vector<char> QuestFlag; std::vector<char> QuestFlag;
int32_t CurrentMissionID;
int32_t WarpLocationFlag;
int64_t SkywayLocationFlag1;
int64_t SkywayLocationFlag2;
};
struct DbQuest {
int PlayerId;
int32_t TaskId;
int RemainingNPCCount1;
int RemainingNPCCount2;
int RemainingNPCCount3;
}; };
#pragma endregion DatabaseStructs #pragma endregion DatabaseStructs
// handles migrations // handles migrations
void open(); void open();
int getAccountsCount();
int getPlayersCount();
// returns ID // returns ID
int addAccount(std::string login, std::string password); int addAccount(std::string login, std::string password);
void updateSelected(int accountId, int playerId); void updateSelected(int accountId, int playerId);
@ -104,7 +123,13 @@ namespace Database {
void updatePlayer(Player *player); void updatePlayer(Player *player);
void updateInventory(Player *player); void updateInventory(Player *player);
void updateNanos(Player *player); void updateNanos(Player *player);
void updateQuests(Player* player);
void getInventory(Player* player); void getInventory(Player* player);
void getNanos(Player* player); void getNanos(Player* player);
void getQuests(Player* player);
//parsing blobs
void appendBlob(std::vector<char>*blob, int64_t input);
int64_t blobToInt64(std::vector<char>::iterator it);
} }

View File

@ -69,6 +69,9 @@ void ItemManager::itemMoveHandler(CNSocket* sock, CNPacketData* data) {
case SlotType::BANK: case SlotType::BANK:
fromItem = &plr.plr->Bank[itemmove->iFromSlotNum]; fromItem = &plr.plr->Bank[itemmove->iFromSlotNum];
break; break;
default:
std::cout << "[WARN] MoveItem submitted unknown Item Type?! " << itemmove->eFrom << std::endl;
return;
} }
// get the toItem // get the toItem
@ -83,6 +86,9 @@ void ItemManager::itemMoveHandler(CNSocket* sock, CNPacketData* data) {
case SlotType::BANK: case SlotType::BANK:
toItem = &plr.plr->Bank[itemmove->iToSlotNum]; toItem = &plr.plr->Bank[itemmove->iToSlotNum];
break; break;
default:
std::cout << "[WARN] MoveItem submitted unknown Item Type?! " << itemmove->eTo << std::endl;
return;
} }
// save items to response // save items to response

View File

@ -31,14 +31,19 @@ void MissionManager::taskStart(CNSocket* sock, CNPacketData* data) {
sock->sendPacket((void*)&response, P_FE2CL_REP_PC_TASK_START_SUCC, sizeof(sP_FE2CL_REP_PC_TASK_START_SUCC)); sock->sendPacket((void*)&response, P_FE2CL_REP_PC_TASK_START_SUCC, sizeof(sP_FE2CL_REP_PC_TASK_START_SUCC));
return; return;
} }
TaskData& task = *Tasks[missionData->iTaskNum];
int i; int i;
for (i = 0; i < ACTIVE_MISSION_COUNT; i++) { for (i = 0; i < ACTIVE_MISSION_COUNT; i++) {
if (plr->tasks[i] == 0) { if (plr->tasks[i] == 0) {
plr->tasks[i] = missionData->iTaskNum; plr->tasks[i] = missionData->iTaskNum;
for (int j = 0; j < 3; j++) {
plr->RemainingNPCCount[i][j] = (int)task["m_iCSUNumToKill"][j];
}
break; break;
} }
} }
if (i == ACTIVE_MISSION_COUNT - 1 && plr->tasks[i] != missionData->iTaskNum) { if (i == ACTIVE_MISSION_COUNT - 1 && plr->tasks[i] != missionData->iTaskNum) {
std::cout << "[WARN] Player has more than 6 active missions!?" << std::endl; std::cout << "[WARN] Player has more than 6 active missions!?" << std::endl;
} }
@ -80,6 +85,7 @@ void MissionManager::taskEnd(CNSocket* sock, CNPacketData* data) {
* iSUInstancename is the number of items to give. It is usually negative at the end of * iSUInstancename is the number of items to give. It is usually negative at the end of
* a mission, to clean up its quest items. * a mission, to clean up its quest items.
*/ */
for (int i = 0; i < 3; i++) for (int i = 0; i < 3; i++)
if (task["m_iSUItem"][i] != 0) if (task["m_iSUItem"][i] != 0)
dropQuestItem(sock, missionData->iTaskNum, task["m_iSUInstancename"][i], task["m_iSUItem"][i], 0); dropQuestItem(sock, missionData->iTaskNum, task["m_iSUInstancename"][i], task["m_iSUItem"][i], 0);
@ -94,7 +100,12 @@ void MissionManager::taskEnd(CNSocket* sock, CNPacketData* data) {
int i; int i;
for (i = 0; i < ACTIVE_MISSION_COUNT; i++) { for (i = 0; i < ACTIVE_MISSION_COUNT; i++) {
if (plr->tasks[i] == missionData->iTaskNum) if (plr->tasks[i] == missionData->iTaskNum)
{
plr->tasks[i] = 0; plr->tasks[i] = 0;
for (int j = 0; j < 3; j++) {
plr->RemainingNPCCount[i][j] = 0;
}
}
} }
if (i == ACTIVE_MISSION_COUNT - 1 && plr->tasks[i] != 0) { if (i == ACTIVE_MISSION_COUNT - 1 && plr->tasks[i] != 0) {
std::cout << "[WARN] Player completed non-active mission!?" << std::endl; std::cout << "[WARN] Player completed non-active mission!?" << std::endl;
@ -105,6 +116,8 @@ void MissionManager::taskEnd(CNSocket* sock, CNPacketData* data) {
{ {
// save completed mission on player // save completed mission on player
saveMission(plr, (int)(task["m_iHMissionID"])-1); saveMission(plr, (int)(task["m_iHMissionID"])-1);
// remove current mission
plr->CurrentMissionID = 0;
} }
sock->sendPacket((void*)&response, P_FE2CL_REP_PC_TASK_END_SUCC, sizeof(sP_FE2CL_REP_PC_TASK_END_SUCC)); sock->sendPacket((void*)&response, P_FE2CL_REP_PC_TASK_END_SUCC, sizeof(sP_FE2CL_REP_PC_TASK_END_SUCC));
@ -116,9 +129,11 @@ void MissionManager::setMission(CNSocket* sock, CNPacketData* data) {
sP_CL2FE_REQ_PC_SET_CURRENT_MISSION_ID* missionData = (sP_CL2FE_REQ_PC_SET_CURRENT_MISSION_ID*)data->buf; sP_CL2FE_REQ_PC_SET_CURRENT_MISSION_ID* missionData = (sP_CL2FE_REQ_PC_SET_CURRENT_MISSION_ID*)data->buf;
INITSTRUCT(sP_FE2CL_REP_PC_SET_CURRENT_MISSION_ID, response); INITSTRUCT(sP_FE2CL_REP_PC_SET_CURRENT_MISSION_ID, response);
response.iCurrentMissionID = missionData->iCurrentMissionID; response.iCurrentMissionID = missionData->iCurrentMissionID;
sock->sendPacket((void*)&response, P_FE2CL_REP_PC_SET_CURRENT_MISSION_ID, sizeof(sP_FE2CL_REP_PC_SET_CURRENT_MISSION_ID)); sock->sendPacket((void*)&response, P_FE2CL_REP_PC_SET_CURRENT_MISSION_ID, sizeof(sP_FE2CL_REP_PC_SET_CURRENT_MISSION_ID));
Player* plr = PlayerManager::getPlayer(sock);
plr->CurrentMissionID = missionData->iCurrentMissionID;
} }
void MissionManager::quitMission(CNSocket* sock, CNPacketData* data) { void MissionManager::quitMission(CNSocket* sock, CNPacketData* data) {
@ -133,17 +148,24 @@ void MissionManager::quitMission(CNSocket* sock, CNPacketData* data) {
int i; int i;
for (i = 0; i < ACTIVE_MISSION_COUNT; i++) { for (i = 0; i < ACTIVE_MISSION_COUNT; i++) {
if (plr->tasks[i] == missionData->iTaskNum) if (plr->tasks[i] == missionData->iTaskNum)
{
plr->tasks[i] = 0; plr->tasks[i] = 0;
for (int j = 0; j < 3; j++) {
plr->RemainingNPCCount[i][j] = 0;
}
}
} }
if (i == ACTIVE_MISSION_COUNT - 1 && plr->tasks[i] != 0) { if (i == ACTIVE_MISSION_COUNT - 1 && plr->tasks[i] != 0) {
std::cout << "[WARN] Player quit non-active mission!?" << std::endl; std::cout << "[WARN] Player quit non-active mission!?" << std::endl;
} }
// remove current mission
plr->CurrentMissionID = 0;
TaskData& task = *Tasks[missionData->iTaskNum]; TaskData& task = *Tasks[missionData->iTaskNum];
// clean up quest items // clean up quest items
for (i = 0; i < 3; i++) { for (i = 0; i < 3; i++) {
if (task["m_iSUItem"][i] == 0 && task["m_iCSUItem"][i] == 0) if (task["m_iSUItem"][i] == 0 && task["m_iCSUItemID"][i] == 0)
continue; continue;
/* /*
@ -151,7 +173,7 @@ void MissionManager::quitMission(CNSocket* sock, CNPacketData* data) {
* slot later items will be placed in. * slot later items will be placed in.
*/ */
for (int j = 0; j < AQINVEN_COUNT; j++) for (int j = 0; j < AQINVEN_COUNT; j++)
if (plr->QInven[j].iID == task["m_iSUItem"][i] || plr->QInven[j].iID == task["m_iCSUItem"][i]) if (plr->QInven[j].iID == task["m_iSUItem"][i] || plr->QInven[j].iID == task["m_iCSUItemID"][i])
memset(&plr->QInven[j], 0, sizeof(sItemBase)); memset(&plr->QInven[j], 0, sizeof(sItemBase));
} }
@ -304,10 +326,12 @@ void MissionManager::mobKilled(CNSocket *sock, int mobid) {
// acknowledge killing of mission mob... // acknowledge killing of mission mob...
if (task["m_iCSUNumToKill"][j] != 0) if (task["m_iCSUNumToKill"][j] != 0)
{
missionmob = true; missionmob = true;
plr->RemainingNPCCount[i][j]--;
}
// drop quest item // drop quest item
if (task["m_iCSUItemNumNeeded"][j] != 0) { if (task["m_iCSUItemNumNeeded"][j] != 0 && !isQuestItemFull(sock, task["m_iCSUItemID"][j], task["m_iCSUItemNumNeeded"][j]) ) {
bool drop = rand() % 100 < task["m_iSTItemDropRate"][j]; bool drop = rand() % 100 < task["m_iSTItemDropRate"][j];
if (drop) { if (drop) {
// XXX: are CSUItemID and CSTItemID the same? // XXX: are CSUItemID and CSTItemID the same?
@ -332,8 +356,26 @@ void MissionManager::mobKilled(CNSocket *sock, int mobid) {
} }
void MissionManager::saveMission(Player* player, int missionId) { void MissionManager::saveMission(Player* player, int missionId) {
//sanity check missionID so we don't get exceptions
if (missionId < 0 || missionId>1023) {
std::cout << "[WARN] Client submitted invalid missionId: " <<missionId<< std::endl;
return;
}
// Missions are stored in int_64t array // Missions are stored in int_64t array
int row = missionId / 64; int row = missionId / 64;
int column = missionId % 64; int column = missionId % 64;
player->aQuestFlag[row] |= (1ULL << column); player->aQuestFlag[row] |= (1ULL << column);
} }
bool MissionManager::isQuestItemFull(CNSocket* sock, int itemId, int itemCount) {
Player* plr = PlayerManager::getPlayer(sock);
int slot = findQSlot(plr, itemId);
if (slot == -1) {
// this should never happen
std::cout << "[WARN] Player has no room for quest item!?" << std::endl;
return true;
}
return (itemCount == plr->QInven[slot].iOpt);
}

View File

@ -47,6 +47,8 @@ namespace MissionManager {
int findQSlot(Player *plr, int id); int findQSlot(Player *plr, int id);
void dropQuestItem(CNSocket *sock, int task, int count, int id, int mobid); void dropQuestItem(CNSocket *sock, int task, int count, int id, int mobid);
//checks if player doesn't have n/n quest items
bool isQuestItemFull(CNSocket* sock, int itemId, int itemCount);
int giveMissionReward(CNSocket *sock, int task); int giveMissionReward(CNSocket *sock, int task);
void mobKilled(CNSocket *sock, int mobid); void mobKilled(CNSocket *sock, int mobid);

View File

@ -19,6 +19,7 @@ struct Player {
int level; int level;
int HP; int HP;
int slot; // player slot, not nano slot int slot; // player slot, not nano slot
int16_t mentor;
int32_t money; int32_t money;
int32_t fusionmatter; int32_t fusionmatter;
int32_t batteryW; int32_t batteryW;
@ -45,5 +46,7 @@ struct Player {
int64_t aQuestFlag[16]; int64_t aQuestFlag[16];
int tasks[ACTIVE_MISSION_COUNT]; int tasks[ACTIVE_MISSION_COUNT];
int RemainingNPCCount[ACTIVE_MISSION_COUNT][3];
sItemBase QInven[AQINVEN_COUNT]; sItemBase QInven[AQINVEN_COUNT];
int32_t CurrentMissionID;
}; };

View File

@ -3,6 +3,7 @@
#include "NPCManager.hpp" #include "NPCManager.hpp"
#include "CNShardServer.hpp" #include "CNShardServer.hpp"
#include "CNShared.hpp" #include "CNShared.hpp"
#include "MissionManager.hpp"
#include "settings.hpp" #include "settings.hpp"
@ -207,24 +208,30 @@ void PlayerManager::enterPlayer(CNSocket* sock, CNPacketData* data) {
response.PCLoadData2CL.iLevel = plr.level; response.PCLoadData2CL.iLevel = plr.level;
response.PCLoadData2CL.iCandy = plr.money; response.PCLoadData2CL.iCandy = plr.money;
response.PCLoadData2CL.iFusionMatter = plr.fusionmatter; response.PCLoadData2CL.iFusionMatter = plr.fusionmatter;
response.PCLoadData2CL.iMentor = 5; // Computress response.PCLoadData2CL.iMentor = plr.mentor;
response.PCLoadData2CL.iMentorCount = 1; // how many guides the player has had response.PCLoadData2CL.iMentorCount = 1; // how many guides the player has had
response.PCLoadData2CL.iMapNum = 0;
response.PCLoadData2CL.iX = plr.x; response.PCLoadData2CL.iX = plr.x;
response.PCLoadData2CL.iY = plr.y; response.PCLoadData2CL.iY = plr.y;
response.PCLoadData2CL.iZ = plr.z; response.PCLoadData2CL.iZ = plr.z;
response.PCLoadData2CL.iAngle = plr.angle; response.PCLoadData2CL.iAngle = plr.angle;
response.PCLoadData2CL.iBatteryN = plr.batteryN;
response.PCLoadData2CL.iBatteryW = plr.batteryW;
response.PCLoadData2CL.iBuddyWarpTime = 60; //sets 60s warp cooldown on login
response.PCLoadData2CL.iActiveNanoSlotNum = -1; response.PCLoadData2CL.iActiveNanoSlotNum = -1;
response.PCLoadData2CL.iFatigue = 50; response.PCLoadData2CL.iFatigue = 50;
response.PCLoadData2CL.PCStyle = plr.PCStyle; response.PCLoadData2CL.PCStyle = plr.PCStyle;
response.PCLoadData2CL.PCStyle2 = plr.PCStyle2;
//client doesnt read this, it gets it from charinfo
//response.PCLoadData2CL.PCStyle2 = plr.PCStyle2;
// inventory // inventory
for (int i = 0; i < AEQUIP_COUNT; i++) for (int i = 0; i < AEQUIP_COUNT; i++)
response.PCLoadData2CL.aEquip[i] = plr.Equip[i]; response.PCLoadData2CL.aEquip[i] = plr.Equip[i];
for (int i = 0; i < AINVEN_COUNT; i++) for (int i = 0; i < AINVEN_COUNT; i++)
response.PCLoadData2CL.aInven[i] = plr.Inven[i]; response.PCLoadData2CL.aInven[i] = plr.Inven[i];
// quest inventory
for (int i = 0; i < AQINVEN_COUNT; i++)
response.PCLoadData2CL.aQInven[i] = plr.QInven[i];
// nanos // nanos
for (int i = 1; i < SIZEOF_NANO_BANK_SLOT; i++) { for (int i = 1; i < SIZEOF_NANO_BANK_SLOT; i++) {
response.PCLoadData2CL.aNanoBank[i] = plr.Nanos[i]; response.PCLoadData2CL.aNanoBank[i] = plr.Nanos[i];
@ -232,8 +239,28 @@ void PlayerManager::enterPlayer(CNSocket* sock, CNPacketData* data) {
for (int i = 0; i < 3; i++) { for (int i = 0; i < 3; i++) {
response.PCLoadData2CL.aNanoSlots[i] = plr.equippedNanos[i]; response.PCLoadData2CL.aNanoSlots[i] = plr.equippedNanos[i];
} }
//missions in progress
for (int i = 0; i < ACTIVE_MISSION_COUNT; i++) {
if (plr.tasks[i] == 0)
break;
response.PCLoadData2CL.aRunningQuest[i].m_aCurrTaskID = plr.tasks[i];
TaskData &task = *MissionManager::Tasks[plr.tasks[i]];
for (int j = 0; j < 3; j++) {
response.PCLoadData2CL.aRunningQuest[i].m_aKillNPCID[j] = (int)task["m_iCSUEnemyID"][j];
response.PCLoadData2CL.aRunningQuest[i].m_aKillNPCCount[j] = plr.RemainingNPCCount[i][j];
/*
* client doesn't care about NeededItem ID and Count,
* it gets Count from Quest Inventory
*
* KillNPCCount sets RemainEnemyNum in the client
* Yes, this is extraordinary stupid, even for Grigon
*/
}
}
response.PCLoadData2CL.iCurrentMissionID = plr.CurrentMissionID;
// missions // completed missions
// the packet requires 32 items, but the client only checks the first 16 (shrug)
for (int i = 0; i < 16; i++) { for (int i = 0; i < 16; i++) {
response.PCLoadData2CL.aQuestFlag[i] = plr.aQuestFlag[i]; response.PCLoadData2CL.aQuestFlag[i] = plr.aQuestFlag[i];
} }
@ -719,8 +746,10 @@ void PlayerManager::changePlayerGuide(CNSocket *sock, CNPacketData *data) {
resp.iMentor = pkt->iMentor; resp.iMentor = pkt->iMentor;
resp.iMentorCnt = 1; resp.iMentorCnt = 1;
resp.iFusionMatter = plr->fusionmatter; // no cost resp.iFusionMatter = plr->fusionmatter; // no cost
sock->sendPacket((void*)&resp, P_FE2CL_REP_PC_CHANGE_MENTOR_SUCC, sizeof(sP_FE2CL_REP_PC_CHANGE_MENTOR_SUCC)); sock->sendPacket((void*)&resp, P_FE2CL_REP_PC_CHANGE_MENTOR_SUCC, sizeof(sP_FE2CL_REP_PC_CHANGE_MENTOR_SUCC));
//save it on player
plr->mentor = pkt->iMentor;
} }
#pragma region Helper methods #pragma region Helper methods

View File

@ -7,6 +7,7 @@ int settings::VERBOSITY = 1;
int settings::LOGINPORT = 8001; int settings::LOGINPORT = 8001;
bool settings::APPROVEALLNAMES = true; bool settings::APPROVEALLNAMES = true;
bool settings::USEWEBAPI = false;
int settings::DBSAVEINTERVAL = 240; int settings::DBSAVEINTERVAL = 240;
int settings::SHARDPORT = 8002; int settings::SHARDPORT = 8002;
@ -41,9 +42,10 @@ void settings::init() {
APPROVEALLNAMES = reader.GetBoolean("", "acceptallcustomnames", APPROVEALLNAMES); APPROVEALLNAMES = reader.GetBoolean("", "acceptallcustomnames", APPROVEALLNAMES);
VERBOSITY = reader.GetInteger("", "verbosity", VERBOSITY); VERBOSITY = reader.GetInteger("", "verbosity", VERBOSITY);
LOGINPORT = reader.GetInteger("login", "port", LOGINPORT); LOGINPORT = reader.GetInteger("login", "port", LOGINPORT);
USEWEBAPI = reader.GetBoolean("login", "usewebapi", USEWEBAPI);
SHARDPORT = reader.GetInteger("shard", "port", SHARDPORT); SHARDPORT = reader.GetInteger("shard", "port", SHARDPORT);
SHARDSERVERIP = reader.Get("shard", "ip", "127.0.0.1"); SHARDSERVERIP = reader.Get("shard", "ip", "127.0.0.1");
DBSAVEINTERVAL = reader.GetInteger("login", "dbsaveinterval", DBSAVEINTERVAL); DBSAVEINTERVAL = reader.GetInteger("shard", "dbsaveinterval", DBSAVEINTERVAL);
TIMEOUT = reader.GetInteger("shard", "timeout", TIMEOUT); TIMEOUT = reader.GetInteger("shard", "timeout", TIMEOUT);
PLAYERDISTANCE = reader.GetInteger("shard", "playerdistance", PLAYERDISTANCE); PLAYERDISTANCE = reader.GetInteger("shard", "playerdistance", PLAYERDISTANCE);
NPCDISTANCE = reader.GetInteger("shard", "npcdistance", NPCDISTANCE); NPCDISTANCE = reader.GetInteger("shard", "npcdistance", NPCDISTANCE);

View File

@ -4,6 +4,7 @@ namespace settings {
extern int VERBOSITY; extern int VERBOSITY;
extern int LOGINPORT; extern int LOGINPORT;
extern bool APPROVEALLNAMES; extern bool APPROVEALLNAMES;
extern bool USEWEBAPI;
extern int DBSAVEINTERVAL; extern int DBSAVEINTERVAL;
extern int SHARDPORT; extern int SHARDPORT;
extern std::string SHARDSERVERIP; extern std::string SHARDSERVERIP;