diff options
| author | fusion32 <marcopuzziello@gmail.com> | 2025-05-25 22:37:57 -0300 |
|---|---|---|
| committer | fusion32 <marcopuzziello@gmail.com> | 2025-05-25 22:37:57 -0300 |
| commit | ad8213f35523cbb07f418ae275af448a47cc0288 (patch) | |
| tree | 4d77b8ff2b44461734b5a2cbaad5402b4a94736f /src/map.cc | |
| parent | 5f883a80175a4cc9abda9d647a6a0d73bda84878 (diff) | |
| download | game-ad8213f35523cbb07f418ae275af448a47cc0288.tar.gz game-ad8213f35523cbb07f418ae275af448a47cc0288.zip | |
linux Makefile + fix most compilation problems
I wanted to see if the compiler had any problems with the code
so far and added a few stub definitions so that each file would
properly compile. We still fail when linking but we're able to to
find and fix compile time errors.
Diffstat (limited to 'src/map.cc')
| -rw-r--r-- | src/map.cc | 999 |
1 files changed, 577 insertions, 422 deletions
@@ -1,5 +1,12 @@ #include "map.hh" #include "containers.hh" +#include "config.hh" +#include "enums.hh" +#include "script.hh" + +#include "stubs.hh" + +#include <dirent.h> // NOTE(fusion): This is used by hash table entries and sectors to tell whether // they're currently loaded or swapped out to disk. @@ -57,25 +64,387 @@ static int CronEntries; static vector<TDepotInfo> DepotInfo(0, 4, 5); static int Depots; -static vector<TMask> Mark(0, 4, 5); +static vector<TMark> Mark(0, 4, 5); static int Marks; static TDynamicWriteBuffer HelpBuffer(0x10000); -// Map Init/Exit and Helpers +// Object // ============================================================================= -static void SwapObject(TWriteBinaryFile *File, Object Obj, uint32 FileNumber){ +bool Object::exists(void){ + if(*this == NONE){ + return false; + } + + uint32 EntryIndex = this->ObjectID & HashTableMask; + if(HashTableType[EntryIndex] == STATUS_SWAPPED){ + UnswapSector((uint32)((uintptr)HashTableData[EntryIndex])); + } + + return HashTableType[EntryIndex] == STATUS_LOADED + && HashTableData[EntryIndex]->ObjectID == this->ObjectID; +} + +ObjectType Object::getObjectType(void){ + return AccessObject(*this)->Type; +} + +void Object::setObjectType(ObjectType Type){ + AccessObject(*this)->Type = Type; +} + +Object Object::getNextObject(void){ + return AccessObject(*this)->NextObject; +} + +void Object::setNextObject(Object NextObject){ + AccessObject(*this)->NextObject = NextObject; +} + +Object Object::getContainer(void){ + return AccessObject(*this)->Container; +} + +void Object::setContainer(Object Container){ + AccessObject(*this)->Container = Container; +} + +uint32 Object::getCreatureID(void){ + // TODO(fusion): We call `AccessObject` once in `getObjectType` then again + // after checking the TypeID, when we could call it once to check both type + // and access `Attributes[1]`. + + if(!this->getObjectType().isCreatureContainer()){ + error("Object::getCreatureID: Objekt ist keine Kreatur.\n"); + return 0; + } + + return AccessObject(*this)->Attributes[1]; +} + +uint32 Object::getAttribute(INSTANCEATTRIBUTE Attribute){ + ObjectType ObjType = this->getObjectType(); + int AttributeOffset = ObjType.getAttributeOffset(Attribute); + if(AttributeOffset == -1){ + error("Object::getAttribute: Flag für Attribut %d bei Objekttyp %d nicht gesetzt.\n", + Attribute, ObjType.TypeID); + return 0; + } + + if(AttributeOffset < 0 || AttributeOffset >= NARRAY(TObject::Attributes)){ + error("Object::getAttribute: Ungültiger Offset %d für Attribut %d bei Objekttyp %d.\n", + AttributeOffset, Attribute, ObjType.TypeID); + return 0; + } + + return AccessObject(*this)->Attributes[AttributeOffset]; +} + +void Object::setAttribute(INSTANCEATTRIBUTE Attribute, uint32 Value){ + ObjectType ObjType = this->getObjectType(); + int AttributeOffset = ObjType.getAttributeOffset(Attribute); + if(AttributeOffset == -1){ + error("Object::setAttribute: Flag für Attribut %d bei Objekttyp %d nicht gesetzt.\n", + Attribute, ObjType.TypeID); + return; + } + + if(AttributeOffset < 0 || AttributeOffset >= NARRAY(TObject::Attributes)){ + error("Object::setAttribute: Ungültiger Offset %d für Attribut %d bei Objekttyp %d.\n", + AttributeOffset, Attribute, ObjType.TypeID); + return; + } + + if(Value == 0){ + if(Attribute == AMOUNT || Attribute == POOLLIQUIDTYPE || Attribute == CHARGES){ + Value = 1; + }else if(Attribute == REMAININGUSES){ + Value = ObjType.getAttribute(TOTALUSES); + } + } + + AccessObject(*this)->Attributes[AttributeOffset] = Value; +} + +// Map Management +// ============================================================================= +static void ReadMapConfig(void){ + OBCount = 0xA0000; + SectorXMin = 1000; + SectorXMax = 1015; + SectorYMin = 1000; + SectorYMax = 1015; + SectorZMin = 0; + SectorZMax = 15; + RefreshedCylinders = 1; + NewbieStartPositionX = 0; + NewbieStartPositionY = 0; + NewbieStartPositionZ = 0; + VeteranStartPositionX = 0; + VeteranStartPositionY = 0; + VeteranStartPositionZ = 0; + HashTableSize = 0x100000; + Marks = 0; + Depots = 0; + + char FileName[4096]; + snprintf(FileName, sizeof(FileName), "%s/map.dat", DATAPATH); + + TReadScriptFile Script; + Script.open(FileName); + while(true){ + Script.nextToken(); + if(Script.Token == ENDOFFILE){ + Script.close(); + break; + } + + char Identifier[MAX_IDENT_LENGTH]; + strcpy(Identifier, Script.readIdentifier()); + Script.readSymbol('='); + + if(strcmp(Identifier, "sectorxmin") == 0){ + SectorXMin = Script.readNumber(); + }else if(strcmp(Identifier, "sectorxmax") == 0){ + SectorXMax = Script.readNumber(); + }else if(strcmp(Identifier, "sectorymin") == 0){ + SectorYMin = Script.readNumber(); + }else if(strcmp(Identifier, "sectorymax") == 0){ + SectorYMax = Script.readNumber(); + }else if(strcmp(Identifier, "sectorzmin") == 0){ + SectorZMin = Script.readNumber(); + }else if(strcmp(Identifier, "sectorzmax") == 0){ + SectorZMax = Script.readNumber(); + }else if(strcmp(Identifier, "refreshedcylinders") == 0){ + RefreshedCylinders = Script.readNumber(); + }else if(strcmp(Identifier, "objects") == 0){ + HashTableSize = (uint32)Script.readNumber(); + }else if(strcmp(Identifier, "cachesize") == 0){ + OBCount = Script.readNumber(); + }else if(strcmp(Identifier, "depot") == 0){ + int DepotIndex = 0; + TDepotInfo TempInfo = {}; + Script.readSymbol('('); + DepotIndex = Script.readNumber(); + Script.readSymbol(','); + const char *Town = Script.readString(); + if(strlen(Town) >= NARRAY(TempInfo.Town)){ + Script.error("town name too long"); + } + strcpy(TempInfo.Town, Town); + Script.readSymbol(','); + TempInfo.Size = Script.readNumber(); + Script.readSymbol(')'); + *DepotInfo.at(DepotIndex) = TempInfo; + }else if(strcmp(Identifier, "mark") == 0){ + TMark TempMark = {}; + Script.readSymbol('('); + const char *Name = Script.readString(); + if(strlen(Name) >= NARRAY(TempMark.Name)){ + Script.error("mark name too long"); + } + strcpy(TempMark.Name, Name); + Script.readSymbol(','); + Script.readCoordinate(&TempMark.x, &TempMark.y, &TempMark.z); + Script.readSymbol(')'); + *Mark.at(Marks) = TempMark; + Marks += 1; + }else if(strcmp(Identifier, "newbiestart") == 0){ + Script.readCoordinate( + &NewbieStartPositionX, + &NewbieStartPositionY, + &NewbieStartPositionZ); + }else if(strcmp(Identifier, "veteranstart") == 0){ + Script.readCoordinate( + &VeteranStartPositionX, + &VeteranStartPositionY, + &VeteranStartPositionZ); + }else{ + // TODO(fusion): + //error("Unknown map configuration key \"%s\"", Identifier); + } + } + + // NOTE(fusion): If each sector is 32 x 32 tiles and the whole world is + // 65535 x 65535 tiles, then the maximum number of sectors is 65535 / 32 + // which is the 2047 used below. We should probably define these contants. + // Notice that sectors at the edge of the XY-plane are also considered + // invalid, probably to add some buffer to avoid wrapping or other types + // of problems. + + if(SectorXMin <= 0){ + throw "illegal value for SectorXMin"; + } + + if(SectorXMax >= 2047){ + throw "illegal value for SectorXMax"; + } + + if(SectorYMin <= 0){ + throw "illegal value for SectorYMin"; + } + + if(SectorYMax >= 2047){ + throw "illegal value for SectorYMax"; + } + + if(SectorZMin < 0){ + throw "illegal value for SectorZMin"; + } + + if(SectorZMax > 15){ + throw "illegal value for SectorZMax"; + } + + if(SectorXMin > SectorXMax){ + throw "SectorXMin is greater than SectorXMax"; + } + + if(SectorYMin > SectorYMax){ + throw "SectorYMin is greater than SectorYMax"; + } + + if(SectorZMin > SectorZMax){ + throw "SectorZMin is greater than SectorZMax"; + } + + // TODO(fusion): Just align up from whatever value we got? And use `ObjectsPerBlock`? + if(OBCount % 32768 != 0){ + throw "CacheSize must be a multiple of 32768"; + } + + if(OBCount <= 0){ + throw "illegal value for CacheSize"; + } + + OBCount /= 32768; + + if(HashTableSize <= 0){ + throw "illegal value for Objects"; + } + + if(!ISPOW2(HashTableSize)){ + throw "Objects must be a power of 2"; + } + + if(NewbieStartPositionX == 0){ + throw "no start position for newbies specified"; + } + + if(VeteranStartPositionX == 0){ + throw "no start position for veterans specified"; + } +} + +static void ResizeHashTable(void){ + uint32 OldSize = HashTableSize; + uint32 NewSize = OldSize * 2; + ASSERT(ISPOW2(OldSize)); + ASSERT(NewSize > OldSize); + + // TODO(fusion): See note below. + error("FATAL ERROR in ResizeHashTable: Resizing the object hash table is" + " currently disabled. You may increase `Objects` in the map config" + " from %d to %d, to achieve the same effect.", OldSize, NewSize); + abort(); + + error("INFO: HashTabelle zu klein. Größe wird verdoppelt auf %d.\n", NewSize); + + uint32 NewMask = NewSize - 1; + TObject **NewData = (TObject**)malloc(NewSize * sizeof(TObject*)); + uint8 *NewType = (uint8*)malloc(NewSize * sizeof(uint8)); + memset(NewType, 0, NewSize * sizeof(uint8)); + + // TODO(fusion): This rehash loop doesn't make a lot of sense. It wants to + // access all existing objects but doing so would cause all sectors to be + // swapped in at some time or another. This may be bad for performance but + // the real problem is that `UnswapSector` may swap out some other sector + // whose objects were already put into `NewType` and `NewData`, causing + // multiple object ids to reference the same `TObject`. + // Looking at some of the logs included with this executable, it doesn't + // look like this function was ever called which explains why it wasn't fixed + // earlier. + // It would be possible to do this rehashing without swapping anything out, + // if we stored ObjectID somewhere (maybe `HashTableData`). Doubling the size + // of the table means there are two possible indices for each previous entry, + // and we can only determine which one to actually move it with the ObjectID. + + NewType[0] = STATUS_PERMANENT; + NewData[0] = HashTableData[0]; + for(uint32 i = 1; i < NewSize; i += 1){ + if(HashTableType[i] != STATUS_FREE){ + if(HashTableType[i] == STATUS_SWAPPED){ + UnswapSector((uint32)((uintptr)HashTableData[i])); + } + + if(HashTableType[i] == STATUS_LOADED){ + TObject *Entry = HashTableData[i]; + NewType[Entry->ObjectID & NewMask] = STATUS_LOADED; + NewData[Entry->ObjectID & NewMask] = Entry; + }else{ + error("ResizeHashTable: Fehler beim Reorganisieren der HashTabelle.\n"); + } + } + } + + free(HashTableData); + free(HashTableType); + + HashTableData = NewData; + HashTableType = NewType; + HashTableSize = NewSize; + HashTableMask = NewMask; + HashTableFree += (NewSize - OldSize); +} + +static TObject *GetFreeObjectSlot(void){ + if(FirstFreeObject == NULL){ + SwapSector(); + } + + TObject *Entry = FirstFreeObject; + if(Entry == NULL){ + error("GetFreeObjectSlot: Kein freier Platz mehr.\n"); + return NULL; + } + + // NOTE(fusion): The next object pointer was originally stored in `Entry->NextObject.ObjectID` + // which is a problem when compiling in 64 bits mode. For this reason, I've changed it to be + // stored at the beggining of `TObject`. + FirstFreeObject = *((TObject**)Entry); + + // TODO(fusion): Using `memset` here will trigger a compiler warning because `TObject` contains + // a few `Object`s and I've made them non PODs by adding a few constructors. + //memset(Entry, 0, sizeof(TObject)); + + *Entry = TObject{}; + return Entry; +} + +static void PutFreeObjectSlot(TObject *Entry){ + if(Entry == NULL){ + error("PutFreeObjectSlot: Entry ist NULL.\n"); + return; + } + + // NOTE(fusion): See note in `GetFreeObjectSlot`, just above. + *((TObject**)Entry) = FirstFreeObject; + FirstFreeObject = Entry; +} + +void SwapObject(TWriteBinaryFile *File, Object Obj, uint32 FileNumber){ ASSERT(Obj != NONE); // NOTE(fusion): Does it make sense to swap an object that isn't loaded? We // were originally calling `Object::exists` that would swap in the object's // sector if it was swapped out. We should probably have an assertion here. - if(HashTableType[Obj.ObjectID & HashTableMask] != STATUS_LOADED){ + uint32 EntryIndex = Obj.ObjectID & HashTableMask; + if(HashTableType[EntryIndex] != STATUS_LOADED){ error("SwapObject: Object doesn't exists or is not currently loaded.\n"); return; } - TObject *Entry = HashTableData[Obj.ObjectID & HashTableMask]; + TObject *Entry = HashTableData[EntryIndex]; if(Entry->ObjectID != Obj.ObjectID){ error("SwapObject: Übergebenes Objekt existiert nicht.\n"); return; @@ -93,10 +462,10 @@ static void SwapObject(TWriteBinaryFile *File, Object Obj, uint32 FileNumber){ PutFreeObjectSlot(Entry); HashTableType[EntryIndex] = STATUS_SWAPPED; - HashTableData[EntryIndex] = (TObject*)FileNumber; + HashTableData[EntryIndex] = (TObject*)((uintptr)FileNumber); } -static void SwapSector(void){ +void SwapSector(void){ static uint32 FileNumber = 0; TSector *Oldest = NULL; @@ -128,8 +497,11 @@ static void SwapSector(void){ char FileName[4096]; do{ - FileNumber += 1; // NOTE(fusion): Let it wrap naturally. - snprintf(FileName, sizeof(FileName), "%s/%010u.swp", SAVEPATH, FileNumber); + FileNumber += 1; + if(FileNumber > 99999999){ + FileNumber = 1; + } + snprintf(FileName, sizeof(FileName), "%s/%08u.swp", SAVEPATH, FileNumber); }while(FileExists(FileName)); TWriteBinaryFile File; @@ -155,9 +527,9 @@ static void SwapSector(void){ } } -static void UnswapSector(uint32 FileNumber){ +void UnswapSector(uint32 FileNumber){ char FileName[4096]; - snprintf(FileName, sizeof(FileName), "%s/%010u.swp", SAVEPATH, FileNumber); + snprintf(FileName, sizeof(FileName), "%s/%08u.swp", SAVEPATH, FileNumber); TReadBinaryFile File; try{ @@ -210,7 +582,7 @@ static void UnswapSector(uint32 FileNumber){ } } -static void DeleteSwappedSectors(void){ +void DeleteSwappedSectors(void){ DIR *SwapDir = opendir(SAVEPATH); if(SwapDir == NULL){ error("DeleteSwappedSectors: Unterverzeichnis %s nicht gefunden\n", SAVEPATH); @@ -235,13 +607,13 @@ static void DeleteSwappedSectors(void){ closedir(SwapDir); } -static void LoadObjects(TReadScriptFile *Script, TWriteStream *Stream, bool Skip){ +void LoadObjects(TReadScriptFile *Script, TWriteStream *Stream, bool Skip){ int Depth = 1; - bool ParseAttribute = false; + bool ProcessObjects = true; Script->readSymbol('{'); Script->nextToken(); while(true){ - if(!ParseAttribute){ + if(ProcessObjects){ if(Script->Token != SPECIAL){ int TypeID = Script->getNumber(); if(!ObjectTypeExists(TypeID)){ @@ -252,7 +624,7 @@ static void LoadObjects(TReadScriptFile *Script, TWriteStream *Stream, bool Skip Stream->writeWord((uint16)TypeID); } - ParseAttribute = true; + ProcessObjects = false; }else{ char Special = Script->getSpecial(); if(Special == '}'){ @@ -284,7 +656,7 @@ static void LoadObjects(TReadScriptFile *Script, TWriteStream *Stream, bool Skip if(Attribute == CONTENT){ Script->readSymbol('{'); Depth += 1; - ParseAttribute = false; + ProcessObjects = true; }else if(Attribute == TEXTSTRING || Attribute == EDITOR){ const char *String = Script->readString(); if(!Skip){ @@ -305,13 +677,13 @@ static void LoadObjects(TReadScriptFile *Script, TWriteStream *Stream, bool Skip if(!Skip){ Stream->writeByte(0xFF); } - ParseAttribute = false; + ProcessObjects = true; } } } } -static void LoadObjects(TReadStream *Stream, Object Con){ +void LoadObjects(TReadStream *Stream, Object Con){ int Depth = 1; Object Obj = NONE; while(true){ @@ -322,6 +694,10 @@ static void LoadObjects(TReadStream *Stream, Object Con){ ObjectType ObjType(TypeID); if(ObjType.isBodyContainer()){ + // TODO(fusion): I couldn't find if a creature's body containers + // are properly initialized or if we need to create them here. + // Either way we should come back to this function to check it + // is working properly. Obj = GetContainerObject(Con, (TypeID - 1)); }else{ Obj = AppendObject(Con, ObjType); @@ -342,11 +718,12 @@ static void LoadObjects(TReadStream *Stream, Object Con){ Obj = NONE; Depth += 1; }else if(Attribute == TEXTSTRING || Attribute == EDITOR){ - uint32 Value = AddDynamicString(Stream->readString()); - Obj.setAttribute(Attribute, Value); + char String[4096]; + Stream->readString(String, sizeof(String)); + Obj.setAttribute((INSTANCEATTRIBUTE)Attribute, AddDynamicString(String)); }else if(Attribute != REMAININGEXPIRETIME){ uint32 Value = Stream->readQuad(); - Obj.setAttribute(Attribute, Value); + Obj.setAttribute((INSTANCEATTRIBUTE)Attribute, Value); }else{ uint32 Value = Stream->readQuad(); if(Value != 0){ @@ -360,7 +737,7 @@ static void LoadObjects(TReadStream *Stream, Object Con){ } } -static void InitSector(int SectorX, int SectorY, int SectorZ){ +void InitSector(int SectorX, int SectorY, int SectorZ){ ASSERT(Sector); if(*Sector->at(SectorX, SectorY, SectorZ) != NULL){ error("InitSector: Sektor %d/%d/%d existiert schon.\n", SectorX, SectorY, SectorZ); @@ -373,9 +750,9 @@ static void InitSector(int SectorX, int SectorY, int SectorZ){ Object MapCon = CreateObject(); // NOTE(fusion): `Attributes[0]` is probably the object id of the // first object in the container. - Access(MapCon)->Attributes[1] = SectorX * 32 + X; - Access(MapCon)->Attributes[2] = SectorY * 32 + Y; - Access(MapCon)->Attributes[3] = SectorZ; + AccessObject(MapCon)->Attributes[1] = SectorX * 32 + X; + AccessObject(MapCon)->Attributes[2] = SectorY * 32 + Y; + AccessObject(MapCon)->Attributes[3] = SectorZ; NewSector->MapCon[X][Y] = MapCon; } } @@ -386,7 +763,7 @@ static void InitSector(int SectorX, int SectorY, int SectorZ){ *Sector->at(SectorX, SectorY, SectorZ) = NewSector; } -static void LoadSector(const char *FileName, int SectorX, int SectorY, int SectorZ){ +void LoadSector(const char *FileName, int SectorX, int SectorY, int SectorZ){ if(SectorX < SectorXMin || SectorXMax < SectorX || SectorY < SectorYMin || SectorYMax < SectorY || SectorZ < SectorZMin || SectorZMax < SectorZ){ @@ -447,7 +824,7 @@ static void LoadSector(const char *FileName, int SectorX, int SectorY, int Secto Script.readSymbol('='); HelpBuffer.reset(); LoadObjects(&Script, &HelpBuffer, false); - TReadBuffer ReadBuffer(HelpBuffer); + TReadBuffer ReadBuffer(HelpBuffer.Data, HelpBuffer.Position); LoadObjects(&ReadBuffer, LoadingSector->MapCon[OffsetX][OffsetY]); }else{ Script.error("unknown map flag"); @@ -460,7 +837,7 @@ static void LoadSector(const char *FileName, int SectorX, int SectorY, int Secto } } -static void LoadMap(void){ +void LoadMap(void){ DIR *MapDir = opendir(MAPPATH); if(MapDir == NULL){ error("LoadMap: Unterverzeichnis %s nicht gefunden\n", MAPPATH); @@ -470,6 +847,7 @@ static void LoadMap(void){ print(1, "Lade Karte ...\n"); ObjectCounter = 0; + int SectorCounter = 0; char FilePath[4096]; while(dirent *DirEntry = readdir(MapDir)){ if(DirEntry->d_type == DT_REG){ @@ -477,7 +855,7 @@ static void LoadMap(void){ const char *FileExt = findLast(DirEntry->d_name, '.'); if(FileExt != NULL && strcmp(FileExt, ".sec") == 0){ int SectorX, SectorY, SectorZ; - if(sscanf("%d-%d-%d.sec", &SectorX, &SectorY, &SectorZ) == 3){ + if(sscanf(DirEntry->d_name, "%d-%d-%d.sec", &SectorX, &SectorY, &SectorZ) == 3){ snprintf(FilePath, sizeof(FilePath), "%s/%s", SAVEPATH, DirEntry->d_name); LoadSector(FilePath, SectorX, SectorY, SectorZ); SectorCounter += 1; @@ -491,8 +869,145 @@ static void LoadMap(void){ print(1, "%d Objekte geladen.\n", ObjectCounter); } -//SaveObjects -//SaveObjects +void SaveObjects(Object Obj, TWriteStream *Stream, bool Stop){ + // TODO(fusion): Just use a recursive algorithm for both `SaveObjects` and + // `LoadObjects`. Regardless of performance differences, this iterative version + // is just unreadable and mostly disconnected. + int Depth = 1; + bool ProcessObjects = true; + Object Prev = NONE; + while(true){ + if(ProcessObjects){ + if(Obj != NONE){ + ObjectType ObjType = Obj.getObjectType(); + if(!ObjType.isCreatureContainer()){ + Stream->writeWord((uint16)ObjType.TypeID); + ProcessObjects = false; + }else{ + if(Stop && Depth == 1){ + break; + } + Prev = Obj; + Obj = Obj.getNextObject(); + } + }else{ + Stream->writeWord(0xFFFF); + Depth -= 1; + if(Depth <= 0){ + break; + } + + Stream->writeByte(0xFF); + ObjectCounter += 1; + if(Stop && Depth == 1){ + break; + } + + if(Prev == NONE){ + error("LastObj ist NONE (1)\n"); + } + + Prev = Prev.getContainer(); + if(Prev == NONE){ + error("LastObj ist NONE (2)\n"); + } + + Obj = Prev.getNextObject(); + } + }else{ + ASSERT(Obj != NONE); + ObjectType ObjType = Obj.getObjectType(); + for(int Attribute = 1; Attribute <= 17; Attribute += 1){ + if(ObjType.getAttributeOffset((INSTANCEATTRIBUTE)Attribute) != -1){ + uint32 Value = 0; + if(Attribute == REMAININGEXPIRETIME){ + Value = CronInfo(Obj, false); + }else{ + Value = Obj.getAttribute((INSTANCEATTRIBUTE)Attribute); + } + + if(Value != 0){ + Stream->writeByte((uint8)Attribute); + if(Attribute == TEXTSTRING || Attribute == EDITOR){ + Stream->writeString(GetDynamicString(Value)); + }else{ + Stream->writeQuad(Value); + } + } + } + } + + Object First = NONE; + if(ObjType.getAttributeOffset(CONTENT) != -1){ + First = Obj.getAttribute(CONTENT); + } + + if(First != NONE){ + Depth += 1; + Prev = NONE; + Obj = First; + Stream->writeByte((uint8)CONTENT); + }else{ + Stream->writeByte(0xFF); + ObjectCounter += 1; + if(Stop && Depth == 1){ + break; + } + + Prev = Obj; + Obj = Obj.getNextObject(); + } + + ProcessObjects = true; + } + } +} + +void SaveObjects(TReadStream *Stream, TWriteScriptFile *Script){ + int Depth = 1; + bool ProcessObjects = true; + bool FirstObject = true; + Script->writeText("{"); + while(true){ + if(ProcessObjects){ + int TypeID = (int)Stream->readWord(); + if(TypeID != 0xFFFF){ + if(!FirstObject){ + Script->writeText(", "); + } + + Script->writeNumber(TypeID); + }else{ + Depth -= 1; + Script->writeText("}"); + } + + ProcessObjects = false; + }else{ + int Attribute = (int)Stream->readByte(); + if(Attribute != 0xFF){ + Script->writeText(" "); + Script->writeText(GetInstanceAttributeName(Attribute)); + Script->writeText("="); + if(Attribute == CONTENT){ + Depth += 1; + ProcessObjects = true; + FirstObject = true; + Script->writeText("{"); + }else if(Attribute == TEXTSTRING || Attribute == EDITOR){ + char String[4096]; + Stream->readString(String, sizeof(String)); + Script->writeString(String); + }else{ + Script->writeNumber((int)Stream->readQuad()); + } + }else{ + ProcessObjects = true; + FirstObject = false; + } + } + } +} void SaveSector(char *FileName, int SectorX, int SectorY, int SectorZ){ ASSERT(Sector); @@ -561,7 +1076,7 @@ void SaveSector(char *FileName, int SectorX, int SectorY, int SectorZ){ Script.writeText("Content="); HelpBuffer.reset(); SaveObjects(First, &HelpBuffer, false); - TReadBuffer ReadBuffer(HelpBuffer); + TReadBuffer ReadBuffer(HelpBuffer.Data, HelpBuffer.Position); SaveObjects(&ReadBuffer, &Script); AttrCount += 1; } @@ -583,7 +1098,7 @@ void SaveSector(char *FileName, int SectorX, int SectorY, int SectorZ){ } } -static void SaveMap(void){ +void SaveMap(void){ // NOTE(fusion): I guess this could happen if we're already saving the map // and a signal causes `exit` to execute cleanup functions registered with // `atexit`, among which is `ExitAll` which may call `SaveMap` throught @@ -611,267 +1126,6 @@ static void SaveMap(void){ SavingMap = false; } -static void ResizeHashTable(void){ - uint32 OldSize = HashTableSize; - uint32 NewSize = OldSize * 2; - ASSERT(ISPOW2(OldSize)); - ASSERT(NewSize > OldSize); - - // TODO(fusion): See note below. - error("FATAL ERROR in ResizeHashTable: Resizing the object hash table is" - " currently disabled. You may increase `Objects` in the map config" - " from %d to %d, to achieve the same effect.", OldSize, NewSize); - abort(); - - error("INFO: HashTabelle zu klein. Größe wird verdoppelt auf %d.\n", NewSize); - - uint32 NewMask = NewSize - 1; - TObject **NewData = (TObject**)malloc(NewSize * sizeof(TObject*)); - uint8 *NewType = (uint8*)malloc(NewSize * sizeof(uint8)); - memset(NewType, 0, NewSize * sizeof(uint8)); - - // TODO(fusion): This rehash loop doesn't make a lot of sense. It wants to - // access all existing objects but doing so would cause all sectors to be - // swapped in at some time or another. This may be bad for performance but - // the real problem is that `UnswapSector` may swap out some other sector - // whose objects were already put into `NewType` and `NewData`, causing - // multiple object ids to reference the same `TObject`. - // Looking at some of the logs included with this executable, it doesn't - // look like this function was ever called which explains why it wasn't fixed - // earlier. - // It would be possible to do this rehashing without swapping anything out, - // if we stored ObjectID somewhere (maybe `HashTableData`). Doubling the size - // of the table means there are two possible indices for each previous entry, - // and we can only determine which one to actually move it with the ObjectID. - - NewType[0] = STATUS_PERMANENT; - NewData[0] = HashTableData[0]; - for(uint32 i = 1; i < NewSize; i += 1){ - if(HashTableType[i] != STATUS_FREE){ - if(HashTableType[i] == STATUS_SWAPPED){ - UnswapSector((uintptr)HashTableData[i]); - } - - if(HashTableType[i] == STATUS_LOADED){ - TObject *Entry = HashTableData[i]; - NewType[Entry->ObjectID & NewMask] = STATUS_LOADED; - NewData[Entry->ObjectID & NewMask] = Entry; - }else{ - error("ResizeHashTable: Fehler beim Reorganisieren der HashTabelle.\n"); - } - } - } - - free(HashTableData); - free(HashTableType); - - HashTableData = NewData; - HashTableType = NewType; - HashTableSize = NewSize; - HashTableMask = NewMask; - HashTableFree += (NewSize - OldSize); -} - -static TObject *GetFreeObjectSlot(void){ - if(FirstFreeObject == NULL){ - SwapSector(); - } - - TObject *Entry = FirstFreeObject; - if(Entry == NULL){ - error("GetFreeObjectSlot: Kein freier Platz mehr.\n"); - return NULL; - } - - // NOTE(fusion): The next object pointer was originally stored in `Entry->NextObject.ObjectID` - // which is a problem when compiling in 64 bits mode. For this reason, I've changed it to be - // stored at the beggining of `TObject`. - FirstFreeObject = *((TObject**)Entry); - - memset(Entry, 0, sizeof(TObject)); - - return Entry; -} - -static void PutFreeObjectSlot(TObject *Entry){ - if(Entry == NULL){ - error("PutFreeObjectSlot: Entry ist NULL.\n"); - return; - } - - // NOTE(fusion): See note in `GetFreeObjectSlot`, just above. - *((TObject**)Entry) = FirstFreeObject; - FirstFreeObject = Entry; -} - -static void ReadMapConfig(void){ - OBCount = 0xA0000; - SectorXMin = 1000; - SectorXMax = 1015; - SectorYMin = 1000; - SectorYMax = 1015; - SectorZMin = 0; - SectorZMax = 15; - RefreshedCylinders = 1; - NewbieStartPositionX = 0; - NewbieStartPositionY = 0; - NewbieStartPositionZ = 0; - VeteranStartPositionX = 0; - VeteranStartPositionY = 0; - VeteranStartPositionZ = 0; - HashTableSize = 0x100000; - Marks = 0; - Depots = 0; - - char FileName[4096]; - snprintf(FileName, sizeof(FileName), "%s/map.dat", DATAPATH); - - TReadScriptFile Script; - Script.open(FileName); - while(true){ - Script.nextToken(); - if(Script.Token == ENDOFFILE){ - Script.close(): - break; - } - - char Identifier[MAX_IDENT_LENGTH]; - strcpy(Identifier, Script.readIdentifier()); - Script.readSymbol('='); - - if(strcmp(Identifier, "sectorxmin") == 0){ - SectorXMin = Script.readNumber(); - }else if(strcmp(Identifier, "sectorxmax") == 0){ - SectorXMax = Script.readNumber(); - }else if(strcmp(Identifier, "sectorymin") == 0){ - SectorYMin = Script.readNumber(); - }else if(strcmp(Identifier, "sectorymax") == 0){ - SectorYMax = Script.readNumber(); - }else if(strcmp(Identifier, "sectorzmin") == 0){ - SectorZMin = Script.readNumber(); - }else if(strcmp(Identifier, "sectorzmax") == 0){ - SectorZMax = Script.readNumber(); - }else if(strcmp(Identifier, "refreshedcylinders") == 0){ - RefreshedCylinders = Script.readNumber(); - }else if(strcmp(Identifier, "objects") == 0){ - HashTableSize = (uint32)Script.readNumber(); - }else if(strcmp(Identifier, "cachesize") == 0){ - OBCount = Script.readNumber(); - }else if(strcmp(Identifier, "depot") == 0){ - int DepotIndex = 0; - TDepotInfo TempInfo = {}; - Script.readSymbol('('); - DepotIndex = Script.readNumber(); - Script.readSymbol(','); - const char *Town = Script.readString(); - if(strlen(Town) >= NARRAY(TempInfo.Town)){ - Script.error("town name too long"); - } - strcpy(TempInfo.Town, Town); - Script.readSymbol(','); - TempInfo.Size = Script.readNumber(); - Script.readSymbol(')'); - *DepotInfo.at(DepotIndex) = TempInfo; - }else if(strcmp(Identifier, "mark") == 0){ - TMark TempMark = {}; - Script.readSymbol('('); - const char *Name = Script.readString(); - if(strlen(Name) >= NARRAY(TempMark.Name)){ - Script.error("mark name too long"); - } - strcpy(TempMark.Name, Name); - Script.readSymbol(','); - Script.readCoordinate(&TempMark.x, &TempMark.y, &TempMark.z); - Script.readSymbol(')'); - *Mark.at(Marks) = TempMark; - Marks += 1; - }else if(strcmp(Identifier, "newbiestart") == 0){ - Script.readCoordinate( - &NewbieStartPositionX, - &NewbieStartPositionY, - &NewbieStartPositionZ); - }else if(strcmp(Identifier, "veteranstart") == 0){ - Script.readCoordinate( - &VeteranStartPositionX, - &VeteranStartPositionY, - &VeteranStartPositionZ); - }else{ - // TODO(fusion): - //error("Unknown map configuration key \"%s\"", Identifier); - } - } - - // NOTE(fusion): If each sector is 32 x 32 tiles and the whole world is - // 65535 x 65535 tiles, then the maximum number of sectors is 65535 / 32 - // which is the 2047 used below. We should probably define these contants. - // Notice that sectors at the edge of the XY-plane are also considered - // invalid, probably to add some buffer to avoid wrapping or other types - // of problems. - - if(SectorXMin <= 0){ - throw "illegal value for SectorXMin"; - } - - if(SectorXMax >= 2047){ - throw "illegal value for SectorXMax"; - } - - if(SectorYMin <= 0){ - throw "illegal value for SectorYMin"; - } - - if(SectorYMax >= 2047){ - throw "illegal value for SectorYMax"; - } - - if(SectorZMin < 0){ - throw "illegal value for SectorZMin"; - } - - if(SectorZMax > 15){ - throw "illegal value for SectorZMax"; - } - - if(SectorXMin > SectorXMax){ - throw "SectorXMin is greater than SectorXMax"; - } - - if(SectorYMin > SectorYMax){ - throw "SectorYMin is greater than SectorYMax"; - } - - if(SectorZMin > SectorZMax){ - throw "SectorZMin is greater than SectorZMax"; - } - - // TODO(fusion): Just align up from whatever value we got? And use `ObjectsPerBlock`? - if(OBCount % 32768 != 0){ - throw "CacheSize must be a multiple of 32768"; - } - - if(OBCount <= 0){ - throw "illegal value for CacheSize"; - } - - OBCount /= 32768; - - if(HashTableSize <= 0){ - throw "illegal value for Objects"; - } - - if(!ISPOW2(HashTableSize)){ - throw "Objects must be a power of 2"; - } - - if(NewbieStartPositionX == 0){ - throw "no start position for newbies specified"; - } - - if(VeteranStartPositionX == 0){ - throw "no start position for veterans specified"; - } -} - void InitMap(void){ ReadMapConfig(); @@ -951,26 +1205,9 @@ void ExitMap(bool Save){ DeleteSwappedSectors(); } -// TODO(fusion): For some reason this is not a member of `Object`? -static TObject *AccessObject(Object Obj){ - if(Obj == NONE){ - error("AccessObject: Ungültige Objektnummer Null.\n"); - return HashTableData[0]; - } - - uint32 Index = Obj.ObjectID - if(HashTableType[Index] == STATUS_SWAPPED){ - UnswapSector((uintptr)HashTableData[Index]); - } - - if(HashTableType[Index] == STATUS_LOADED && HashTableData[Index]->ObjectID == Obj.ObjectID){ - return HashTableData[Index]; - }else{ - return HashTableData[0]; - } -} - -static Object CreateObject(void){ +// Object Functions +// ============================================================================= +Object CreateObject(void){ static uint32 NextObjectID = 1; // NOTE(fusion): Load factor of 1/16. @@ -1006,105 +1243,24 @@ static Object CreateObject(void){ // DestroyObject // DeleteObject -// Object -// ============================================================================= -bool Object::exists(void){ - if(*this == NONE){ - return false; - } - - uint32 Index = this->ObjectID & HashTableMask; - if(HashTableType[Index] == STATUS_SWAPPED){ - UnswapSector(HashTableData[Index]); - } - - return HashTableType[Index] == STATUS_LOADED - && HashTableData[Index]->ObjectID == this->ObjectID; -} - -ObjectType Object::getObjectType(void){ - return AccessObject(*this)->Type; -} - -void Object::setObjectType(ObjectType Type){ - AccessObject(*this)->Type = Type; -} - -Object Object::getNextObject(void){ - return AccessObject(*this)->NextObject; -} - -void Object::setNextObject(Object NextObject){ - AccessObject(*this)->NextObject = NextObject; -} - -Object Object::getContainer(void){ - return AccessObject(*this)->Container; -} - -void Object::setContainer(Object Container){ - AccessObject(*this)->Container = Container; -} - -uint32 Object::getCreatureID(void){ - // TODO(fusion): We call `AccessObject` once in `getObjectType` then again - // after checking the TypeID, when we could call it once to check both type - // and access `Attributes[1]`. - - if(!this->getObjectType().isCreatureContainer()){ - error("Object::getCreatureID: Objekt ist keine Kreatur.\n"); - return 0; - } - - return AccessObject(*this)->Attributes[1]; -} - -uint32 Object::getAttribute(INSTANCEATTRIBUTE Attribute){ - ObjectType ObjType = this->getObjectType(); - int AttributeOffset = ObjType.getAttributeOffset(Attribute); - if(AttributeOffset == -1){ - error("Object::getAttribute: Flag für Attribut %d bei Objekttyp %d nicht gesetzt.\n", - Attribute, ObjType.TypeID); - return 0; - } - - if(AttributeOffset < 0 || AttributeOffset >= NARRAY(TObject::Attributes)){ - error("Object::getAttribute: Ungültiger Offset %d für Attribut %d bei Objekttyp %d.\n", - AttributeOffset, Attribute, ObjType.TypeID); - return 0; - } - - return AccessObject(*this)->Attributes[AttributeOffset]; -} - -void Object::setAttribute(INSTANCEATTRIBUTE Attribute, uint32 Value){ - ObjectType ObjType = this->getObjectType(); - int AttributeOffset = ObjType.getAttributeOffset(Attribute); - if(AttributeOffset == -1){ - error("Object::setAttribute: Flag für Attribut %d bei Objekttyp %d nicht gesetzt.\n", - Attribute, ObjType.TypeID); - return; +TObject *AccessObject(Object Obj){ + if(Obj == NONE){ + error("AccessObject: Ungültige Objektnummer Null.\n"); + return HashTableData[0]; } - if(AttributeOffset < 0 || AttributeOffset >= NARRAY(TObject::Attributes)){ - error("Object::setAttribute: Ungültiger Offset %d für Attribut %d bei Objekttyp %d.\n", - AttributeOffset, Attribute, ObjType.TypeID); - return; + uint32 EntryIndex = Obj.ObjectID & HashTableMask; + if(HashTableType[EntryIndex] == STATUS_SWAPPED){ + UnswapSector((uint32)((uintptr)HashTableData[EntryIndex])); } - if(Value == 0){ - if(Attribute == AMOUNT || Attribute == POOLLIQUIDTYPE || Attribute == CHARGES){ - Value = 1; - }else if(Attribute == REMAININGUSES){ - Value = ObjType.getAttribute(TOTALUSES); - } + if(HashTableType[EntryIndex] == STATUS_LOADED && HashTableData[EntryIndex]->ObjectID == Obj.ObjectID){ + return HashTableData[EntryIndex]; + }else{ + return HashTableData[0]; } - - AccessObject(*this)->Attributes[AttributeOffset] = Value; } -// Object Functions -// ============================================================================= void ChangeObject(Object Obj, ObjectType NewType){ if(!Obj.exists()){ error("ChangeObject: Übergebenes Objekt existiert nicht.\n"); @@ -1227,12 +1383,11 @@ void PlaceObject(Object Obj, Object Con, bool Append){ Object Cur(Con.getAttribute(CONTENT)); if(ConType.isMapContainer()){ // TODO(fusion): Review. The loop below was a bit rough but it seems that - // append is forced for PRIORITY_CREATURE and PRIORITY_OTHER. We should - // confirm this whenever the server is up and running. It'll show. + // append is forced for non PRIORITY_CREATURE and PRIORITY_OTHER. int ObjPriority = GetObjectPriority(Obj); Append = Append - || ObjPriority == PRIORITY_CREATURE - || ObjPriority == PRIORITY_OTHER; + || (ObjPriority != PRIORITY_CREATURE + && ObjPriority != PRIORITY_OTHER); while(Cur != NONE){ int CurPriority = GetObjectPriority(Cur); @@ -1243,16 +1398,16 @@ void PlaceObject(Object Obj, Object Con, bool Append){ // TODO(fusion): Replace item? I think the client might assert // if there are two of these objects on a single tile. const char *PriorityString = ""; - if(ObjPriority == PRIORITY_BACK){ - PriorityString == "BANK"; + if(ObjPriority == PRIORITY_BANK){ + PriorityString = "BANK"; }else if(ObjPriority == PRIORITY_BOTTOM){ - PriorityString == "BOTTOM"; + PriorityString = "BOTTOM"; }else if(ObjPriority == PRIORITY_TOP){ - PriorityString == "TOP"; + PriorityString = "TOP"; } int CoordX, CoordY, CoordZ; - GetObjectCoordinates(Con, CoordX, CoordY, CoordZ); + GetObjectCoordinates(Con, &CoordX, &CoordY, &CoordZ); ObjectType ObjType = Obj.getObjectType(); ObjectType CurType = Cur.getObjectType(); @@ -1302,7 +1457,7 @@ Object GetFirstContainerObject(Object Con){ return NONE; } - ObjectType ConType = Con.getObjectType(Con); + ObjectType ConType = Con.getObjectType(); if(!ConType.getFlag(CONTAINER) && !ConType.getFlag(CHEST)){ error("GetFirstContainerObject: Con (%d) ist kein Container.\n", ConType.TypeID); return NONE; @@ -1322,12 +1477,12 @@ Object GetContainerObject(Object Con, int Index){ return NONE; } - Object Current = GetFirstContainerObject(Con); - while(Current != NONE && Index > 0){ - Current = CurrentObject.getNextObject(); + Object Obj = GetFirstContainerObject(Con); + while(Obj != NONE && Index > 0){ + Obj = Obj.getNextObject(); } - return Current; + return Obj; } void GetObjectCoordinates(Object Obj, int *x, int *y, int *z){ @@ -1396,7 +1551,7 @@ Object GetMapContainer(int x, int y, int z){ Object GetMapContainer(Object Obj){ if(!Obj.exists()){ - error("GetMapContainer: Übergebenes Objekt existiert nicht\n") + error("GetMapContainer: Übergebenes Objekt existiert nicht\n"); return NONE; } |
