aboutsummaryrefslogtreecommitdiff
path: root/src/map.cc
diff options
context:
space:
mode:
authorfusion32 <marcopuzziello@gmail.com>2025-05-25 22:37:57 -0300
committerfusion32 <marcopuzziello@gmail.com>2025-05-25 22:37:57 -0300
commitad8213f35523cbb07f418ae275af448a47cc0288 (patch)
tree4d77b8ff2b44461734b5a2cbaad5402b4a94736f /src/map.cc
parent5f883a80175a4cc9abda9d647a6a0d73bda84878 (diff)
downloadgame-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.cc999
1 files changed, 577 insertions, 422 deletions
diff --git a/src/map.cc b/src/map.cc
index 0d3f69b..20dae79 100644
--- a/src/map.cc
+++ b/src/map.cc
@@ -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;
}