mirror of
https://github.com/OpenFusionProject/OpenFusion.git
synced 2024-11-04 22:40:05 +00:00
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:
parent
321dca3f79
commit
5e0948ea93
@ -14,6 +14,8 @@ acceptallcustomnames=true
|
||||
# how often should everything be flushed to the database?
|
||||
# the default is 4 minutes
|
||||
dbsaveinterval=240
|
||||
# use the web API
|
||||
usewebapi=false
|
||||
|
||||
# Shard Server configuration
|
||||
[shard]
|
||||
|
@ -53,17 +53,27 @@ void CNLoginServer::handlePacket(CNSocket* sock, CNPacketData* data) {
|
||||
else
|
||||
{
|
||||
std::unique_ptr<Database::Account> findUser = Database::findAccount(userLogin);
|
||||
// if account not found, create it
|
||||
// if account not found
|
||||
if (findUser == nullptr)
|
||||
{
|
||||
loginSessions[sock] = CNLoginData();
|
||||
loginSessions[sock].userID = Database::addAccount(userLogin, userPassword);
|
||||
loginSessions[sock].slot = 1;
|
||||
success = true;
|
||||
//web api takes care of registration
|
||||
if (settings::USEWEBAPI)
|
||||
errorCode = (int)LoginError::ID_DOESNT_EXIST;
|
||||
//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
|
||||
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
|
||||
if (CNLoginServer::isAccountInUse(findUser->AccountID) ||
|
||||
PlayerManager::isAccountInUse(findUser->AccountID))
|
||||
@ -144,10 +154,8 @@ void CNLoginServer::handlePacket(CNSocket* sock, CNPacketData* data) {
|
||||
// Failure
|
||||
else {
|
||||
INITSTRUCT(sP_LS2CL_REP_LOGIN_FAIL, resp);
|
||||
|
||||
memcpy(resp.szID, login->szID, sizeof(char16_t) * 33);
|
||||
U8toU16(userLogin, resp.szID);
|
||||
resp.iErrorCode = errorCode;
|
||||
|
||||
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;
|
||||
auto account = Database::findAccount(U16toU8(exit->szID));
|
||||
//sanity check
|
||||
if (account == nullptr)
|
||||
{
|
||||
std::cout << "[WARN] P_CL2LS_REQ_PC_EXIT_DUPLICATE submitted unknown username: " << exit->szID << std::endl;
|
||||
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;
|
||||
if (!exitDuplicate(accountId))
|
||||
@ -407,11 +429,19 @@ bool CNLoginServer::exitDuplicate(int accountId) {
|
||||
|
||||
bool CNLoginServer::isLoginDataGood(std::string login, std::string password) {
|
||||
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}");
|
||||
return (std::regex_match(login, loginRegex) && std::regex_match(password, passwordRegex));
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
200
src/Database.cpp
200
src/Database.cpp
@ -18,7 +18,9 @@ auto db = make_storage("database.db",
|
||||
make_column("AccountID", &Database::Account::AccountID, autoincrement(), primary_key()),
|
||||
make_column("Login", &Database::Account::Login),
|
||||
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_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("Firstname", &Database::DbPlayer::FirstName, 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("Nano1", &Database::DbPlayer::Nano1),
|
||||
make_column("Nano2", &Database::DbPlayer::Nano2),
|
||||
@ -51,7 +55,14 @@ auto db = make_storage("database.db",
|
||||
make_column("isGM", &Database::DbPlayer::isGM),
|
||||
make_column("FusionMatter", &Database::DbPlayer::FusionMatter),
|
||||
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_column("PlayerId", &Database::Inventory::playerId),
|
||||
@ -66,6 +77,13 @@ auto db = make_storage("database.db",
|
||||
make_column("Id", &Database::Nano::iID),
|
||||
make_column("Skill", &Database::Nano::iSkillID),
|
||||
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
|
||||
bool preserve = true;
|
||||
db.sync_schema(preserve);
|
||||
DEBUGLOG(
|
||||
std::cout << "[DB] Database in operation" << std::endl;
|
||||
)
|
||||
std::cout << "[INFO] Database in operation ";
|
||||
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)
|
||||
{
|
||||
password = BCrypt::generateHash(password);
|
||||
Account x = {};
|
||||
x.Login = login;
|
||||
x.Password = password;
|
||||
x.Selected = 1;
|
||||
return db.insert(x);
|
||||
Account account = {};
|
||||
account.Login = login;
|
||||
account.Password = password;
|
||||
account.Selected = 1;
|
||||
account.Created = getTime();
|
||||
return db.insert(account);
|
||||
}
|
||||
|
||||
void Database::updateSelected(int accountId, int slot)
|
||||
{
|
||||
Account acc = db.get<Account>(accountId);
|
||||
acc.Selected = slot;
|
||||
//timestamp
|
||||
acc.LastLogin = getTime();
|
||||
db.update(acc);
|
||||
}
|
||||
|
||||
@ -130,6 +171,8 @@ int Database::createCharacter(sP_CL2LS_REQ_SAVE_CHAR_NAME* save, int AccountID)
|
||||
|
||||
DbPlayer create = {};
|
||||
|
||||
//set timestamp
|
||||
create.Created = getTime();
|
||||
// save packet data
|
||||
create.FirstName = U16toU8(save->szFirstName);
|
||||
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.angle = settings::SPAWN_ANGLE;
|
||||
create.QuestFlag = std::vector<char>();
|
||||
//set mentor to computress
|
||||
create.Mentor = 5;
|
||||
|
||||
return db.insert(create);
|
||||
}
|
||||
@ -285,7 +330,8 @@ void Database::changeName(sP_CL2LS_REQ_CHANGE_CHAR_NAME* save) {
|
||||
|
||||
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.AccountID = player->accountId;
|
||||
@ -318,22 +364,24 @@ Database::DbPlayer Database::playerToDb(Player *player)
|
||||
result.Nano1 = player->equippedNanos[0];
|
||||
result.Nano2 = player->equippedNanos[1];
|
||||
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>();
|
||||
// parsing long array to char vector
|
||||
for (int i=0; i<16; i++)
|
||||
{
|
||||
int64_t temp = player->aQuestFlag[i];
|
||||
for (int j = 0; j < 8; j++) {
|
||||
int64_t check2 = (temp >> (8 * (7 - j)));
|
||||
char toadd = check2;
|
||||
result.QuestFlag.push_back(
|
||||
toadd
|
||||
);
|
||||
}
|
||||
int64_t flag = player->aQuestFlag[i];
|
||||
appendBlob(&result.QuestFlag, flag);
|
||||
}
|
||||
|
||||
//timestamp
|
||||
result.LastLogin = getTime();
|
||||
result.Created = getDbPlayerById(player->iID).Created;
|
||||
|
||||
return result;
|
||||
}
|
||||
@ -370,28 +418,31 @@ Player Database::DbToPlayer(DbPlayer player) {
|
||||
result.angle = player.angle;
|
||||
result.money = player.Taros;
|
||||
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[1] = player.Nano2;
|
||||
result.equippedNanos[2] = player.Nano3;
|
||||
|
||||
result.iWarpLocationFlag = player.WarpLocationFlag;
|
||||
result.aSkywayLocationFlag[0] = player.SkywayLocationFlag1;
|
||||
result.aSkywayLocationFlag[1] = player.SkywayLocationFlag2;
|
||||
|
||||
Database::getInventory(&result);
|
||||
Database::getNanos(&result);
|
||||
Database::getQuests(&result);
|
||||
|
||||
std::vector<char>::iterator it = player.QuestFlag.begin();
|
||||
for (int i = 0; i < 16; i++)
|
||||
{
|
||||
if (it == player.QuestFlag.end())
|
||||
break;
|
||||
|
||||
int64_t toAdd = 0;
|
||||
for (int j = 0; j < 8; j++) {
|
||||
int64_t temp = *it;
|
||||
int64_t check2 = (temp << (8 * (7 - j)));
|
||||
toAdd += check2;
|
||||
it++;
|
||||
}
|
||||
result.aQuestFlag[i] = toAdd;
|
||||
result.aQuestFlag[i] = blobToInt64(it);
|
||||
//move iterator to the next flag
|
||||
it += 8;
|
||||
}
|
||||
|
||||
return result;
|
||||
@ -417,6 +468,7 @@ void Database::updatePlayer(Player *player) {
|
||||
db.update(toUpdate);
|
||||
updateInventory(player);
|
||||
updateNanos(player);
|
||||
updateQuests(player);
|
||||
}
|
||||
|
||||
void Database::updateInventory(Player *player){
|
||||
@ -468,8 +520,23 @@ void Database::updateInventory(Player *player){
|
||||
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();
|
||||
}
|
||||
|
||||
void Database::updateNanos(Player *player) {
|
||||
// start transaction
|
||||
db.begin_transaction();
|
||||
@ -492,6 +559,30 @@ void Database::updateNanos(Player *player) {
|
||||
}
|
||||
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) {
|
||||
// get items from DB
|
||||
auto items = db.get_all<Inventory>(
|
||||
@ -505,15 +596,18 @@ void Database::getInventory(Player* player) {
|
||||
toSet.iOpt = current.Opt;
|
||||
toSet.iTimeLimit = current.TimeLimit;
|
||||
// assign to proper arrays
|
||||
if (current.slot <= AEQUIP_COUNT)
|
||||
if (current.slot < AEQUIP_COUNT)
|
||||
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;
|
||||
else
|
||||
else if (current.slot < (AEQUIP_COUNT + AINVEN_COUNT + ABANK_COUNT))
|
||||
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) {
|
||||
// get from DB
|
||||
auto nanos = db.get_all<Nano>(
|
||||
@ -527,4 +621,42 @@ void Database::getNanos(Player* player) {
|
||||
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 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
|
||||
|
@ -13,6 +13,8 @@ namespace Database {
|
||||
std::string Login;
|
||||
std::string Password;
|
||||
int Selected;
|
||||
uint64_t Created;
|
||||
uint64_t LastLogin;
|
||||
};
|
||||
struct Inventory
|
||||
{
|
||||
@ -36,6 +38,8 @@ namespace Database {
|
||||
short int slot;
|
||||
std::string FirstName;
|
||||
std::string LastName;
|
||||
uint64_t Created;
|
||||
uint64_t LastLogin;
|
||||
short int Level;
|
||||
int Nano1;
|
||||
int Nano2;
|
||||
@ -62,15 +66,30 @@ namespace Database {
|
||||
int z_coordinates;
|
||||
int angle;
|
||||
short int PCState;
|
||||
int BatteryW;
|
||||
int BatteryN;
|
||||
int16_t Mentor;
|
||||
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
|
||||
|
||||
// handles migrations
|
||||
void open();
|
||||
int getAccountsCount();
|
||||
int getPlayersCount();
|
||||
// returns ID
|
||||
int addAccount(std::string login, std::string password);
|
||||
void updateSelected(int accountId, int playerId);
|
||||
@ -104,7 +123,13 @@ namespace Database {
|
||||
void updatePlayer(Player *player);
|
||||
void updateInventory(Player *player);
|
||||
void updateNanos(Player *player);
|
||||
void updateQuests(Player* player);
|
||||
|
||||
void getInventory(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);
|
||||
}
|
||||
|
@ -69,6 +69,9 @@ void ItemManager::itemMoveHandler(CNSocket* sock, CNPacketData* data) {
|
||||
case SlotType::BANK:
|
||||
fromItem = &plr.plr->Bank[itemmove->iFromSlotNum];
|
||||
break;
|
||||
default:
|
||||
std::cout << "[WARN] MoveItem submitted unknown Item Type?! " << itemmove->eFrom << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
// get the toItem
|
||||
@ -83,6 +86,9 @@ void ItemManager::itemMoveHandler(CNSocket* sock, CNPacketData* data) {
|
||||
case SlotType::BANK:
|
||||
toItem = &plr.plr->Bank[itemmove->iToSlotNum];
|
||||
break;
|
||||
default:
|
||||
std::cout << "[WARN] MoveItem submitted unknown Item Type?! " << itemmove->eTo << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
// save items to response
|
||||
|
@ -32,13 +32,18 @@ void MissionManager::taskStart(CNSocket* sock, CNPacketData* data) {
|
||||
return;
|
||||
}
|
||||
|
||||
TaskData& task = *Tasks[missionData->iTaskNum];
|
||||
int i;
|
||||
for (i = 0; i < ACTIVE_MISSION_COUNT; i++) {
|
||||
if (plr->tasks[i] == 0) {
|
||||
plr->tasks[i] = missionData->iTaskNum;
|
||||
for (int j = 0; j < 3; j++) {
|
||||
plr->RemainingNPCCount[i][j] = (int)task["m_iCSUNumToKill"][j];
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (i == ACTIVE_MISSION_COUNT - 1 && plr->tasks[i] != missionData->iTaskNum) {
|
||||
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
|
||||
* a mission, to clean up its quest items.
|
||||
*/
|
||||
|
||||
for (int i = 0; i < 3; i++)
|
||||
if (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;
|
||||
for (i = 0; i < ACTIVE_MISSION_COUNT; i++) {
|
||||
if (plr->tasks[i] == missionData->iTaskNum)
|
||||
{
|
||||
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) {
|
||||
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
|
||||
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));
|
||||
@ -119,6 +132,8 @@ void MissionManager::setMission(CNSocket* sock, CNPacketData* data) {
|
||||
|
||||
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));
|
||||
Player* plr = PlayerManager::getPlayer(sock);
|
||||
plr->CurrentMissionID = missionData->iCurrentMissionID;
|
||||
}
|
||||
|
||||
void MissionManager::quitMission(CNSocket* sock, CNPacketData* data) {
|
||||
@ -133,17 +148,24 @@ void MissionManager::quitMission(CNSocket* sock, CNPacketData* data) {
|
||||
int i;
|
||||
for (i = 0; i < ACTIVE_MISSION_COUNT; i++) {
|
||||
if (plr->tasks[i] == missionData->iTaskNum)
|
||||
{
|
||||
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) {
|
||||
std::cout << "[WARN] Player quit non-active mission!?" << std::endl;
|
||||
}
|
||||
// remove current mission
|
||||
plr->CurrentMissionID = 0;
|
||||
|
||||
TaskData& task = *Tasks[missionData->iTaskNum];
|
||||
|
||||
// clean up quest items
|
||||
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;
|
||||
|
||||
/*
|
||||
@ -151,7 +173,7 @@ void MissionManager::quitMission(CNSocket* sock, CNPacketData* data) {
|
||||
* slot later items will be placed in.
|
||||
*/
|
||||
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));
|
||||
}
|
||||
|
||||
@ -304,10 +326,12 @@ void MissionManager::mobKilled(CNSocket *sock, int mobid) {
|
||||
|
||||
// acknowledge killing of mission mob...
|
||||
if (task["m_iCSUNumToKill"][j] != 0)
|
||||
{
|
||||
missionmob = true;
|
||||
|
||||
plr->RemainingNPCCount[i][j]--;
|
||||
}
|
||||
// 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];
|
||||
if (drop) {
|
||||
// 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) {
|
||||
//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
|
||||
int row = missionId / 64;
|
||||
int column = missionId % 64;
|
||||
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);
|
||||
}
|
||||
|
@ -47,6 +47,8 @@ namespace MissionManager {
|
||||
|
||||
int findQSlot(Player *plr, int id);
|
||||
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);
|
||||
|
||||
void mobKilled(CNSocket *sock, int mobid);
|
||||
|
@ -19,6 +19,7 @@ struct Player {
|
||||
int level;
|
||||
int HP;
|
||||
int slot; // player slot, not nano slot
|
||||
int16_t mentor;
|
||||
int32_t money;
|
||||
int32_t fusionmatter;
|
||||
int32_t batteryW;
|
||||
@ -45,5 +46,7 @@ struct Player {
|
||||
|
||||
int64_t aQuestFlag[16];
|
||||
int tasks[ACTIVE_MISSION_COUNT];
|
||||
int RemainingNPCCount[ACTIVE_MISSION_COUNT][3];
|
||||
sItemBase QInven[AQINVEN_COUNT];
|
||||
int32_t CurrentMissionID;
|
||||
};
|
||||
|
@ -3,6 +3,7 @@
|
||||
#include "NPCManager.hpp"
|
||||
#include "CNShardServer.hpp"
|
||||
#include "CNShared.hpp"
|
||||
#include "MissionManager.hpp"
|
||||
|
||||
#include "settings.hpp"
|
||||
|
||||
@ -207,24 +208,30 @@ void PlayerManager::enterPlayer(CNSocket* sock, CNPacketData* data) {
|
||||
response.PCLoadData2CL.iLevel = plr.level;
|
||||
response.PCLoadData2CL.iCandy = plr.money;
|
||||
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.iMapNum = 0;
|
||||
response.PCLoadData2CL.iX = plr.x;
|
||||
response.PCLoadData2CL.iY = plr.y;
|
||||
response.PCLoadData2CL.iZ = plr.z;
|
||||
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.iFatigue = 50;
|
||||
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
|
||||
for (int i = 0; i < AEQUIP_COUNT; i++)
|
||||
response.PCLoadData2CL.aEquip[i] = plr.Equip[i];
|
||||
|
||||
for (int i = 0; i < AINVEN_COUNT; 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
|
||||
for (int i = 1; i < SIZEOF_NANO_BANK_SLOT; 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++) {
|
||||
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++) {
|
||||
response.PCLoadData2CL.aQuestFlag[i] = plr.aQuestFlag[i];
|
||||
}
|
||||
@ -721,6 +748,8 @@ void PlayerManager::changePlayerGuide(CNSocket *sock, CNPacketData *data) {
|
||||
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));
|
||||
//save it on player
|
||||
plr->mentor = pkt->iMentor;
|
||||
}
|
||||
|
||||
#pragma region Helper methods
|
||||
|
@ -7,6 +7,7 @@ int settings::VERBOSITY = 1;
|
||||
|
||||
int settings::LOGINPORT = 8001;
|
||||
bool settings::APPROVEALLNAMES = true;
|
||||
bool settings::USEWEBAPI = false;
|
||||
int settings::DBSAVEINTERVAL = 240;
|
||||
|
||||
int settings::SHARDPORT = 8002;
|
||||
@ -41,9 +42,10 @@ void settings::init() {
|
||||
APPROVEALLNAMES = reader.GetBoolean("", "acceptallcustomnames", APPROVEALLNAMES);
|
||||
VERBOSITY = reader.GetInteger("", "verbosity", VERBOSITY);
|
||||
LOGINPORT = reader.GetInteger("login", "port", LOGINPORT);
|
||||
USEWEBAPI = reader.GetBoolean("login", "usewebapi", USEWEBAPI);
|
||||
SHARDPORT = reader.GetInteger("shard", "port", SHARDPORT);
|
||||
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);
|
||||
PLAYERDISTANCE = reader.GetInteger("shard", "playerdistance", PLAYERDISTANCE);
|
||||
NPCDISTANCE = reader.GetInteger("shard", "npcdistance", NPCDISTANCE);
|
||||
|
@ -4,6 +4,7 @@ namespace settings {
|
||||
extern int VERBOSITY;
|
||||
extern int LOGINPORT;
|
||||
extern bool APPROVEALLNAMES;
|
||||
extern bool USEWEBAPI;
|
||||
extern int DBSAVEINTERVAL;
|
||||
extern int SHARDPORT;
|
||||
extern std::string SHARDSERVERIP;
|
||||
|
Loading…
Reference in New Issue
Block a user