diff options
| author | fusion32 <marcopuzziello@gmail.com> | 2025-07-01 15:39:01 -0300 |
|---|---|---|
| committer | fusion32 <marcopuzziello@gmail.com> | 2025-07-01 15:39:01 -0300 |
| commit | 11ae3d0e42293d01b4d729ed5b6fceb8f0ec3fa5 (patch) | |
| tree | 00e4efd093990dc1afcd783ba919b395ffb0d7a9 /src/crplayer.cc | |
| parent | d5a143875678c9fbe3a76f23a7a810bc13b2edba (diff) | |
| download | game-11ae3d0e42293d01b4d729ed5b6fceb8f0ec3fa5.tar.gz game-11ae3d0e42293d01b4d729ed5b6fceb8f0ec3fa5.zip | |
merged `dbfuncs.cc` into `crplayer.cc`
Diffstat (limited to 'src/crplayer.cc')
| -rw-r--r-- | src/crplayer.cc | 506 |
1 files changed, 506 insertions, 0 deletions
diff --git a/src/crplayer.cc b/src/crplayer.cc index b8e1926..a53e676 100644 --- a/src/crplayer.cc +++ b/src/crplayer.cc @@ -2104,6 +2104,512 @@ void SendExistingRequests(TConnection *Connection){ delete []Players; } +// Player Loader +// ============================================================================= +void PlayerDataPath(char *Buffer, int BufferSize, uint32 CharacterID){ + snprintf(Buffer, BufferSize, "%s/%02u/%u.usr", + USERPATH, (CharacterID % 100), CharacterID); +} + +bool PlayerDataExists(uint32 CharacterID){ + char FileName[4096]; + PlayerDataPath(FileName, sizeof(FileName), CharacterID); + return FileExists(FileName); +} + +bool LoadPlayerData(TPlayerData *Slot){ + if(Slot == NULL){ + error("LoadPlayerData: Slot ist NULL.\n"); + return false; + } + + if(Slot->CharacterID == 0){ + error("LoadPlayerData: Slot enthält keinen Charakter.\n"); + return false; + } + + // IMPORTANT(fusion): This function is only called from `AssignPlayerPoolSlot` + // which zero initializes it before hand. Note that it would be a problem + // otherwise, since we shouldn't write to `CharacterID`, `Locked`, or `Sticky` + // outside a critical section, making `memset` not viable and turning this + // into an assignment fiesta for no good reason. + Slot->Race = 1; + Slot->Profession = PROFESSION_NONE; + for(int SkillNr = 0; + SkillNr < NARRAY(Slot->Minimum); + SkillNr += 1){ + // NOTE(fusion): See `TPlayer::LoadData`. + Slot->Minimum[SkillNr] = INT_MIN; + } + + // NOTE(fusion): First login. Use defaults. + char FileName[4096]; + PlayerDataPath(FileName, sizeof(FileName), Slot->CharacterID); + if(!FileExists(FileName)){ + return true; + } + + bool Result = false; + try{ + // TODO(fusion): Same thing as house loaders. Data is expected to be in + // an exact order and we don't check identifiers + TDynamicWriteBuffer HelpBuffer(KB(16)); + TReadScriptFile Script; + Script.open(FileName); + + Script.readIdentifier(); // "id" + Script.readSymbol('='); + Script.readNumber(); + + Script.readIdentifier(); // "name" + Script.readSymbol('='); + strcpy(Slot->Name, Script.readString()); + + Script.readIdentifier(); // "race" + Script.readSymbol('='); + Slot->Race = Script.readNumber(); + + Script.readIdentifier(); // "profession" + Script.readSymbol('='); + Slot->Profession = (uint8)Script.readNumber(); + + Script.readIdentifier(); // "originaloutfit" + Script.readSymbol('='); + Slot->OriginalOutfit = ReadOutfit(&Script); + + Script.readIdentifier(); // "currentoutfit" + Script.readSymbol('='); + Slot->CurrentOutfit = ReadOutfit(&Script); + + Script.readIdentifier(); // "lastlogin" + Script.readSymbol('='); + Slot->LastLoginTime = (time_t)Script.readNumber(); + + Script.readIdentifier(); // "lastlogout" + Script.readSymbol('='); + Slot->LastLogoutTime = (time_t)Script.readNumber(); + + Script.readIdentifier(); // "startposition" + Script.readSymbol('='); + Script.readCoordinate(&Slot->startx, &Slot->starty, &Slot->startz); + + Script.readIdentifier(); // "currentposition" + Script.readSymbol('='); + Script.readCoordinate(&Slot->posx, &Slot->posy, &Slot->posz); + + Script.readIdentifier(); // "playerkillerend" + Script.readSymbol('='); + Slot->PlayerkillerEnd = Script.readNumber(); + + while(strcmp(Script.readIdentifier(), "skill") == 0){ + Script.readSymbol('='); + Script.readSymbol('('); + int SkillNr = Script.readNumber(); + if(SkillNr < 0 || SkillNr >= NARRAY(Slot->Minimum)){ + Script.error("illegal skill number"); + } + Script.readSymbol(','); + Slot->Actual[SkillNr] = Script.readNumber(); + Script.readSymbol(','); + Slot->Maximum[SkillNr] = Script.readNumber(); + Script.readSymbol(','); + Slot->Minimum[SkillNr] = Script.readNumber(); + Script.readSymbol(','); + Slot->DeltaAct[SkillNr] = Script.readNumber(); + Script.readSymbol(','); + Slot->MagicDeltaAct[SkillNr] = Script.readNumber(); + Script.readSymbol(','); + Slot->Cycle[SkillNr] = Script.readNumber(); + Script.readSymbol(','); + Slot->MaxCycle[SkillNr] = Script.readNumber(); + Script.readSymbol(','); + Slot->Count[SkillNr] = Script.readNumber(); + Script.readSymbol(','); + Slot->MaxCount[SkillNr] = Script.readNumber(); + Script.readSymbol(','); + Slot->AddLevel[SkillNr] = Script.readNumber(); + Script.readSymbol(','); + Slot->Experience[SkillNr] = Script.readNumber(); + Script.readSymbol(','); + Slot->FactorPercent[SkillNr] = Script.readNumber(); + Script.readSymbol(','); + Slot->NextLevel[SkillNr] = Script.readNumber(); + Script.readSymbol(','); + Slot->Delta[SkillNr] = Script.readNumber(); + Script.readSymbol(')'); + } + + // "spells", already primed in the loop condition above + Script.readSymbol('='); + Script.readSymbol('{'); + while(true){ + Script.nextToken(); + if(Script.Token == SPECIAL){ + if(Script.getSpecial() == '}'){ + break; + }else if(Script.getSpecial() == ','){ + continue; + } + } + + int SpellNr = Script.getNumber(); + if(SpellNr < 0 || SpellNr >= NARRAY(Slot->SpellList)){ + Script.error("illegal spell number"); + } + + Slot->SpellList[SpellNr] = 1; + } + + Script.readIdentifier(); // "questvalues" + Script.readSymbol('='); + Script.readSymbol('{'); + while(true){ + char Special = Script.readSpecial(); + if(Special == '}'){ + break; + }else if(Special == ','){ + continue; + }else if(Special != '('){ + Script.error("'(' expected"); + } + + int QuestNr = Script.readNumber(); + if(QuestNr < 0 || QuestNr >= NARRAY(Slot->QuestValues)){ + Script.error("illegal quest number"); + } + Script.readSymbol(','); + Slot->QuestValues[QuestNr] = Script.readNumber(); + Script.readSymbol(')'); + } + + Script.readIdentifier(); // "murders" + Script.readSymbol('='); + Script.readSymbol('{'); + while(true){ + Script.nextToken(); + if(Script.Token == SPECIAL){ + if(Script.getSpecial() == '}'){ + break; + }else if(Script.getSpecial() == ','){ + continue; + } + } + + for(int i = 1; i < NARRAY(Slot->MurderTimestamps); i += 1){ + Slot->MurderTimestamps[i - 1] = Slot->MurderTimestamps[i]; + } + + Slot->MurderTimestamps[NARRAY(Slot->MurderTimestamps) - 1] = Script.getNumber(); + } + + HelpBuffer.Position = 0; + Script.readIdentifier(); // "inventory" + Script.readSymbol('='); + Script.readSymbol('{'); + while(true){ + Script.nextToken(); + if(Script.Token == SPECIAL){ + if(Script.getSpecial() == '}'){ + break; + }else if(Script.getSpecial() == ','){ + continue; + } + } + + int Position = Script.readNumber(); + if(Position < INVENTORY_FIRST || Position > INVENTORY_LAST){ + Script.error("illegal inventory position"); + } + + Script.readIdentifier(); // "content" + Script.readSymbol('='); + HelpBuffer.writeByte((uint8)Position); + LoadObjects(&Script, &HelpBuffer, false); + } + HelpBuffer.writeByte(0xFF); + Slot->Inventory = new uint8[HelpBuffer.Position]; + Slot->InventorySize = HelpBuffer.Position; + memcpy(Slot->Inventory, HelpBuffer.Data, HelpBuffer.Position); + + Script.readIdentifier(); // "depots" + Script.readSymbol('='); + Script.readSymbol('{'); + while(true){ + Script.nextToken(); + if(Script.Token == SPECIAL){ + if(Script.getSpecial() == '}'){ + break; + }else if(Script.getSpecial() == ','){ + continue; + } + } + + int DepotNr = Script.readNumber(); + if(DepotNr < 0 || DepotNr >= NARRAY(Slot->Depot)){ + Script.error("illegal depot number"); + } + + Script.readIdentifier(); // "content" + Script.readSymbol('='); + HelpBuffer.Position = 0; + LoadObjects(&Script, &HelpBuffer, false); + Slot->Depot[DepotNr] = new uint8[HelpBuffer.Position]; + Slot->DepotSize[DepotNr] = HelpBuffer.Position; + memcpy(Slot->Depot[DepotNr], HelpBuffer.Data, HelpBuffer.Position); + } + + Script.nextToken(); + if(Script.Token != ENDOFFILE){ + Script.error("end of file expected"); + } + + Result = true; + }catch(const char *str){ + error("LoadPlayerData: Kann Gegenstände des Spielers %u nicht laden.\n", + Slot->CharacterID); + error("# Fehler: %s\n", str); + }catch(const std::bad_alloc &e){ + error("LoadPlayerData: Kein Speicher frei beim Laden von Spieler %u.\n", + Slot->CharacterID); + } + + if(!Result){ + delete[] Slot->Inventory; + Slot->Inventory = NULL; + Slot->InventorySize = 0; + + for(int DepotNr = 0; + DepotNr < NARRAY(Slot->Depot); + DepotNr += 1){ + delete[] Slot->Depot[DepotNr]; + Slot->Depot[DepotNr] = NULL; + Slot->DepotSize[DepotNr] = 0; + } + } + + return Result; +} + +void SavePlayerData(TPlayerData *Slot){ + if(Slot == NULL){ + error("SavePlayerData: Slot ist NULL.\n"); + return; + } + + if(Slot->CharacterID == 0){ + error("SavePlayerData: Slot enthält keinen Charakter.\n"); + return; + } + + // TODO(fusion): This is prone to problems if we don't backup user files. + // Even if we did automatic backups, would only this user get rolled back? + // This is probably one of the sources of whole day rollbacks. + char FileName[4096]; + PlayerDataPath(FileName, sizeof(FileName), Slot->CharacterID); + try{ + TWriteScriptFile Script; + Script.open(FileName); + + Script.writeText("ID = "); + Script.writeNumber(Slot->CharacterID); + Script.writeLn(); + + Script.writeText("Name = "); + Script.writeString(Slot->Name); + Script.writeLn(); + + Script.writeText("Race = "); + Script.writeNumber(Slot->Race); + Script.writeLn(); + + Script.writeText("Profession = "); + Script.writeNumber(Slot->Profession); + Script.writeLn(); + + Script.writeText("OriginalOutfit = "); + WriteOutfit(&Script, Slot->OriginalOutfit); + Script.writeLn(); + + Script.writeText("CurrentOutfit = "); + WriteOutfit(&Script, Slot->CurrentOutfit); + Script.writeLn(); + + Script.writeText("LastLogin = "); + Script.writeNumber((int)Slot->LastLoginTime); + Script.writeLn(); + + Script.writeText("LastLogout = "); + Script.writeNumber((int)Slot->LastLogoutTime); + Script.writeLn(); + + Script.writeText("StartPosition = "); + Script.writeCoordinate(Slot->startx, Slot->starty, Slot->startz); + Script.writeLn(); + + Script.writeText("CurrentPosition = "); + Script.writeCoordinate(Slot->posx, Slot->posy, Slot->posz); + Script.writeLn(); + + Script.writeText("PlayerkillerEnd = "); + Script.writeNumber(Slot->PlayerkillerEnd); + Script.writeLn(); + Script.writeLn(); + + for(int SkillNr = 0; + SkillNr < NARRAY(Slot->Minimum); + SkillNr += 1){ + if(Slot->Minimum[SkillNr] == INT_MIN){ + continue; + } + + Script.writeText("Skill = ("); + Script.writeNumber(SkillNr); + Script.writeText(","); + Script.writeNumber(Slot->Actual[SkillNr]); + Script.writeText(","); + Script.writeNumber(Slot->Maximum[SkillNr]); + Script.writeText(","); + Script.writeNumber(Slot->Minimum[SkillNr]); + Script.writeText(","); + Script.writeNumber(Slot->DeltaAct[SkillNr]); + Script.writeText(","); + Script.writeNumber(Slot->MagicDeltaAct[SkillNr]); + Script.writeText(","); + Script.writeNumber(Slot->Cycle[SkillNr]); + Script.writeText(","); + Script.writeNumber(Slot->MaxCycle[SkillNr]); + Script.writeText(","); + Script.writeNumber(Slot->Count[SkillNr]); + Script.writeText(","); + Script.writeNumber(Slot->MaxCount[SkillNr]); + Script.writeText(","); + Script.writeNumber(Slot->AddLevel[SkillNr]); + Script.writeText(","); + Script.writeNumber(Slot->Experience[SkillNr]); + Script.writeText(","); + Script.writeNumber(Slot->FactorPercent[SkillNr]); + Script.writeText(","); + Script.writeNumber(Slot->NextLevel[SkillNr]); + Script.writeText(","); + Script.writeNumber(Slot->Delta[SkillNr]); + Script.writeText(")"); + Script.writeLn(); + } + Script.writeLn(); + + bool FirstSpell = true; + Script.writeText("Spells = {"); + for(int SpellNr = 0; + SpellNr < NARRAY(Slot->SpellList); + SpellNr += 1){ + if(Slot->SpellList[SpellNr] != 0){ + if(!FirstSpell){ + Script.writeText(","); + } + Script.writeNumber(SpellNr); + FirstSpell = false; + } + } + Script.writeText("}"); + Script.writeLn(); + + bool FirstQuest = true; + Script.writeText("QuestValues = {"); + for(int QuestNr = 0; + QuestNr < NARRAY(Slot->QuestValues); + QuestNr += 1){ + if(Slot->QuestValues[QuestNr] != 0){ + if(!FirstQuest){ + Script.writeText(","); + } + Script.writeText("("); + Script.writeNumber(QuestNr); + Script.writeText(","); + Script.writeNumber(Slot->QuestValues[QuestNr]); + Script.writeText(")"); + FirstQuest = false; + } + } + Script.writeText("}"); + Script.writeLn(); + + bool FirstMurder = true; + int Now = (int)time(NULL); + Script.writeText("Murders = {"); + for(int i = 0; i < NARRAY(Slot->MurderTimestamps); i += 1){ + // NOTE(fusion): Save murder timestamps for up to a month. + if((Now - Slot->MurderTimestamps[i]) < (30 * 24 * 60 * 60)){ + if(FirstMurder){ + Script.writeText(","); + } + Script.writeNumber(Slot->MurderTimestamps[i]); + FirstMurder = false; + } + } + Script.writeText("}"); + Script.writeLn(); + Script.writeLn(); + + bool FirstPosition = true; + Script.writeText("Inventory = {"); + if(Slot->Inventory != NULL){ + TReadBuffer Buffer(Slot->Inventory, Slot->InventorySize); + while(true){ + int Position = (int)Buffer.readByte(); + if(Position == 0xFF){ + break; + } + + if(FirstPosition){ + Script.writeText(","); + Script.writeLn(); + Script.writeText(" "); + } + Script.writeNumber(Position); + Script.writeText(" Content="); + SaveObjects(&Buffer, &Script); + FirstPosition = false; + } + } + Script.writeText("}"); + Script.writeLn(); + Script.writeLn(); + + bool FirstDepot = true; + Script.writeText("Depots = {"); + for(int DepotNr = 0; + DepotNr < NARRAY(Slot->Depot); + DepotNr += 1){ + if(Slot->Depot[DepotNr] != NULL){ + TReadBuffer Buffer(Slot->Depot[DepotNr], Slot->DepotSize[DepotNr]); + if(FirstDepot){ + Script.writeText(","); + Script.writeLn(); + Script.writeText(" "); + } + Script.writeNumber(DepotNr); + Script.writeText(" Content="); + SaveObjects(&Buffer, &Script); + FirstDepot = false; + } + } + Script.writeText("}"); + Script.writeLn(); + Script.close(); + + }catch(const char *str){ + error("SavePlayerData: Kann Gegenstände des Spielers %u nicht schreiben.\n", Slot->CharacterID); + error("# Fehler: %s\n", str); + unlink(FileName); + } +} + +void UnlinkPlayerData(uint32 CharacterID){ + char FileName[4096]; + PlayerDataPath(FileName, sizeof(FileName), CharacterID); + unlink(FileName); +} + // Player Pool // ============================================================================= void SavePlayerPoolSlot(TPlayerData *Slot){ |
