From ad8213f35523cbb07f418ae275af448a47cc0288 Mon Sep 17 00:00:00 2001 From: fusion32 Date: Sun, 25 May 2025 22:37:57 -0300 Subject: 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. --- Makefile | 75 +++ build.bat | 4 +- reference/enums.hh | 8 - src/common.hh | 22 +- src/config.cc | 13 +- src/containers.hh | 8 +- src/crcombat.cc | 2 +- src/creature.cc | 5 +- src/creature.hh | 5 +- src/enums.hh | 8 + src/main.cc | 21 +- src/map.cc | 1523 +++++++++++++++++++++++++++++----------------------- src/map.hh | 43 +- src/monster.hh | 2 + src/objects.cc | 42 +- src/objects.hh | 1 + src/player.cc | 2 + src/player.hh | 1 + src/script.cc | 29 +- src/script.hh | 26 +- src/shm.cc | 18 +- src/skill.cc | 10 +- src/stubs.hh | 64 +++ src/thread.cc | 2 +- src/thread.hh | 2 +- src/time.cc | 8 +- src/util.cc | 17 +- 27 files changed, 1164 insertions(+), 797 deletions(-) create mode 100644 Makefile create mode 100644 src/stubs.hh diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..18037c1 --- /dev/null +++ b/Makefile @@ -0,0 +1,75 @@ +SRCDIR = src +BUILDDIR = build +OUTPUTEXE = game + +CC = g++ +CFLAGS = -m64 -fno-strict-aliasing -Wall -Wextra -Wno-unused-parameter -Wno-format-truncation -std=c++11 -DOS_LINUX=1 -DARCH_X64=1 -D_CRT_SECURE_NO_WARNINGS=1 +LFLAGS = -Wl,-t + +DEBUG ?= 0 +ifneq ($(DEBUG), 0) + CFLAGS += -g -O0 +else + CFLAGS += -O2 +endif + +HEADERS = $(SRCDIR)/common.hh $(SRCDIR)/config.hh $(SRCDIR)/connection.hh $(SRCDIR)/containers.hh $(SRCDIR)/creature.hh $(SRCDIR)/enums.hh $(SRCDIR)/map.hh $(SRCDIR)/monster.hh $(SRCDIR)/objects.hh $(SRCDIR)/player.hh $(SRCDIR)/script.hh $(SRCDIR)/skill.hh $(SRCDIR)/thread.hh + +$(BUILDDIR)/$(OUTPUTEXE): $(BUILDDIR)/config.obj $(BUILDDIR)/crcombat.obj $(BUILDDIR)/creature.obj $(BUILDDIR)/main.obj $(BUILDDIR)/map.obj $(BUILDDIR)/objects.obj $(BUILDDIR)/player.obj $(BUILDDIR)/script.obj $(BUILDDIR)/shm.obj $(BUILDDIR)/skill.obj $(BUILDDIR)/thread.obj $(BUILDDIR)/time.obj $(BUILDDIR)/util.obj + $(CC) $(CFLAGS) $(LFLAGS) -o $@ $^ + +$(BUILDDIR)/config.obj: $(SRCDIR)/config.cc $(HEADERS) + @mkdir -p $(@D) + $(CC) -c $(CFLAGS) -o $@ $< + +$(BUILDDIR)/crcombat.obj: $(SRCDIR)/crcombat.cc $(HEADERS) + @mkdir -p $(@D) + $(CC) -c $(CFLAGS) -o $@ $< + +$(BUILDDIR)/creature.obj: $(SRCDIR)/creature.cc $(HEADERS) + @mkdir -p $(@D) + $(CC) -c $(CFLAGS) -o $@ $< + +$(BUILDDIR)/main.obj: $(SRCDIR)/main.cc $(HEADERS) + @mkdir -p $(@D) + $(CC) -c $(CFLAGS) -o $@ $< + +$(BUILDDIR)/map.obj: $(SRCDIR)/map.cc $(HEADERS) + @mkdir -p $(@D) + $(CC) -c $(CFLAGS) -o $@ $< + +$(BUILDDIR)/objects.obj: $(SRCDIR)/objects.cc $(HEADERS) + @mkdir -p $(@D) + $(CC) -c $(CFLAGS) -o $@ $< + +$(BUILDDIR)/player.obj: $(SRCDIR)/player.cc $(HEADERS) + @mkdir -p $(@D) + $(CC) -c $(CFLAGS) -o $@ $< + +$(BUILDDIR)/script.obj: $(SRCDIR)/script.cc $(HEADERS) + @mkdir -p $(@D) + $(CC) -c $(CFLAGS) -o $@ $< + +$(BUILDDIR)/shm.obj: $(SRCDIR)/shm.cc $(HEADERS) + @mkdir -p $(@D) + $(CC) -c $(CFLAGS) -o $@ $< + +$(BUILDDIR)/skill.obj: $(SRCDIR)/skill.cc $(HEADERS) + @mkdir -p $(@D) + $(CC) -c $(CFLAGS) -o $@ $< + +$(BUILDDIR)/thread.obj: $(SRCDIR)/thread.cc $(HEADERS) + @mkdir -p $(@D) + $(CC) -c $(CFLAGS) -o $@ $< + +$(BUILDDIR)/time.obj: $(SRCDIR)/time.cc $(HEADERS) + @mkdir -p $(@D) + $(CC) -c $(CFLAGS) -o $@ $< + +$(BUILDDIR)/util.obj: $(SRCDIR)/util.cc $(HEADERS) + @mkdir -p $(@D) + $(CC) -c $(CFLAGS) -o $@ $< + +.PHONY: clean +clean: + @rm -r $(BUILDDIR) diff --git a/build.bat b/build.bat index ccee552..a0e501f 100644 --- a/build.bat +++ b/build.bat @@ -11,8 +11,8 @@ set OUTPUTPDB=out.pdb set INSTALLDIR=..\bin @REM set SRC="../src/creature.cc" "../src/main.cc" "../src/player.cc" "../src/skill.cc" -set SRC="../src/script.cc" "../src/util.cc" -cl -W3 -WX -Zi -we4244 -we4456 -we4457 -wd4996 -std:c++14 -utf-8 -MP8 -DBUILD_DEBUG=1 -DARCH_X64=1 -D_CRT_SECURE_NO_WARNINGS=1 -Fe:"%OUTPUTEXE%" -Fd:"%OUTPUTPDB%" %* %SRC% /link -subsystem:console -incremental:no -opt:ref -dynamicbase "ws2_32.lib" +set SRC="../src/map.cc" "../src/script.cc" "../src/util.cc" +cl -W3 -WX -Zi -we4244 -we4456 -we4457 -wd4996 -std:c++14 -EHsc -utf-8 -MP8 -DBUILD_DEBUG=1 -DARCH_X64=1 -D_CRT_SECURE_NO_WARNINGS=1 -Fe:"%OUTPUTEXE%" -Fd:"%OUTPUTPDB%" %* %SRC% /link -subsystem:console -incremental:no -opt:ref -dynamicbase "ws2_32.lib" if errorlevel 1 exit /b errorlevel diff --git a/reference/enums.hh b/reference/enums.hh index 5e045f3..6a84666 100644 --- a/reference/enums.hh +++ b/reference/enums.hh @@ -186,11 +186,3 @@ enum SITUATION: int { BUSY=3, VANISH=4 }; - -enum BloodType: int { - BT_BLOOD=0, - BT_SLIME=1, - BT_BONES=2, - BT_FIRE=3, - BT_ENERGY=4 -}; diff --git a/src/common.hh b/src/common.hh index 219ff22..e113e41 100644 --- a/src/common.hh +++ b/src/common.hh @@ -19,7 +19,7 @@ typedef uintptr_t uintptr; typedef size_t usize; #define STATIC_ASSERT(expr) static_assert((expr), "static assertion failed: " #expr) -#define NARRAY(arr) (sizeof(arr) / sizeof(arr[0])) +#define NARRAY(arr) (int)(sizeof(arr) / sizeof(arr[0])) #define ISPOW2(x) ((x) != 0 && ((x) & ((x) - 1)) == 0) #define KB(x) ((usize)(x) << 10) #define MB(x) ((usize)(x) << 20) @@ -80,10 +80,9 @@ STATIC_ASSERT(sizeof(int) == 4); // be too difficult either. Overall this design is outdated and should be reviewed. // Nevertheless, we should focus on getting it working as intended, on the target // platform (which is Linux 32-bits) before attempting to refine it. -//STATIC_ASSERT(OS_LINUX); -//#include -typedef int pid_t; -// +STATIC_ASSERT(OS_LINUX); +#include +#include // shm.cc // ============================================================================= @@ -120,7 +119,6 @@ void ExitSHMExtern(void); // ============================================================================= extern uint32 RoundNr; extern uint32 ServerMilliseconds; - void GetRealTime(int *Hour, int *Minute); void GetTime(int *Hour, int *Minute); void GetDate(int *Year, int *Cycle, int *Day); @@ -134,8 +132,8 @@ typedef void TErrorFunction(const char *Text); typedef void TPrintFunction(int Level, const char *Text); void SetErrorFunction(TErrorFunction *Function); void SetPrintFunction(TPrintFunction *Function); -void error(char *Text, ...) ATTR_PRINTF(1, 2); -void print(int Level, char *Text, ...) ATTR_PRINTF(1, 2); +void error(const char *Text, ...) ATTR_PRINTF(1, 2); +void print(int Level, const char *Text, ...) ATTR_PRINTF(2, 3); bool isSpace(int c); bool isAlpha(int c); @@ -145,7 +143,7 @@ int toLower(int c); int toUpper(int c); char *strLower(char *s); char *strUpper(char *s); -int stricmp(const char *s1, const char *s2, int Pos); +int stricmp(const char *s1, const char *s2, int Max = INT_MAX); char *findFirst(char *s, char c); char *findLast(char *s, char c); @@ -166,8 +164,6 @@ struct TReadBuffer: TReadStream { // REGULAR FUNCTIONS // ========================================================================= TReadBuffer(uint8 *Data, int Size); - TReadBuffer(const TWriteBuffer &WriteBuffer) - : TReadBuffer(WriteBuffer.Data, WriteBuffer.Size) {} // VIRTUAL FUNCTIONS // ========================================================================= @@ -207,7 +203,7 @@ struct TWriteBuffer: TWriteStream { void writeByte(uint8 Byte) override; void writeWord(uint16 Word) override; void writeQuad(uint32 Quad) override; - void writeBytes(uint8 *Buffer, int Count) override; + void writeBytes(const uint8 *Buffer, int Count) override; // DATA // ========================================================================= @@ -227,7 +223,7 @@ struct TDynamicWriteBuffer: TWriteBuffer { void writeByte(uint8 Byte) override; void writeWord(uint16 Word) override; void writeQuad(uint32 Quad) override; - void writeBytes(uint8 *Buffer, int Count) override; + void writeBytes(const uint8 *Buffer, int Count) override; // TODO(fusion): Appended virtual functions. These are not in the base class // VTABLE which can be problematic if we intend to use polymorphism, although diff --git a/src/config.cc b/src/config.cc index e3e3c85..d08b791 100644 --- a/src/config.cc +++ b/src/config.cc @@ -1,4 +1,7 @@ #include "config.hh" +#include "script.hh" + +#include "stubs.hh" char BINPATH[4096]; char DATAPATH[4096]; @@ -254,16 +257,16 @@ void ReadConfig(void){ }else if(strcmp(Identifier, "querymanager") == 0){ Script.readSymbol('{'); do{ - if(NumberOfQueryManager >= NARRAY(QUERY_MANAGER)){ + if(NumberOfQueryManagers >= NARRAY(QUERY_MANAGER)){ Script.error("Cannot handle more query managers"); } Script.readSymbol('('); - strcpy(QUERY_MANAGER[NumberOfQueryManager].Host, Script.readString()); + strcpy(QUERY_MANAGER[NumberOfQueryManagers].Host, Script.readString()); Script.readSymbol(','); - QUERY_MANAGER[NumberOfQueryManager].Port = Script.readNumber(); + QUERY_MANAGER[NumberOfQueryManagers].Port = Script.readNumber(); Script.readSymbol(','); - strcpy(QUERY_MANAGER[NumberOfQueryManager].Password, Script.readString()); - DisguisePassword(QUERY_MANAGER[NumberOfQueryManager].Password, PasswordKey); + strcpy(QUERY_MANAGER[NumberOfQueryManagers].Password, Script.readString()); + DisguisePassword(QUERY_MANAGER[NumberOfQueryManagers].Password, PasswordKey); Script.readSymbol(')'); NumberOfQueryManagers += 1; }while(Script.readSpecial() != '}'); diff --git a/src/containers.hh b/src/containers.hh index 8000d3c..d79946c 100644 --- a/src/containers.hh +++ b/src/containers.hh @@ -75,7 +75,7 @@ struct vector{ increment = this->space; } - T *entry = new[this->space + increment]; + T *entry = new T[this->space + increment]; for(int i = this->min; i <= this->max; i += 1){ int old_index = i - this->start; int new_index = old_index; @@ -207,7 +207,7 @@ struct priority_queue{ // priority_queue ToDoQueue(5000, 1000); // priority_queue AttackWaveQueue(100, 100); -typedef +template struct matrix{ // REGULAR FUNCTIONS // ========================================================================= @@ -264,7 +264,7 @@ struct matrix{ T *entry; }; -typedef +template struct matrix3d{ // REGULAR FUNCTIONS // ========================================================================= @@ -298,7 +298,7 @@ struct matrix3d{ } matrix3d(int xmin, int xmax, int ymin, int ymax, int zmin, int zmax, T init) - : matrix(xmin, xmax, ymin, ymax, zmin, zmax) { + : matrix3d(xmin, xmax, ymin, ymax, zmin, zmax) { int count = this->dx * this->dy * this->dz; for(int i = 0; i < count; i += 1){ this->entry[i] = init; diff --git a/src/crcombat.cc b/src/crcombat.cc index 6b0df86..854ffe2 100644 --- a/src/crcombat.cc +++ b/src/crcombat.cc @@ -10,7 +10,7 @@ TCombat::TCombat(void){ this->ChaseMode = 0; this->SecureMode = 1; this->AttackDest = 0; - this->Following; + this->Following = false; this->Shield = NONE; this->Close = NONE; this->Missile = NONE; diff --git a/src/creature.cc b/src/creature.cc index e2cb5ce..549543e 100644 --- a/src/creature.cc +++ b/src/creature.cc @@ -1,13 +1,14 @@ #include "creature.hh" - #include "enums.hh" +#include "stubs.hh" + TCreature::TCreature(void) : TSkillBase(), Combat(), ToDoList(0, 20, 10) { - this->Master = this; + this->Combat.Master = this; this->ID = 0; this->NextHashEntry = NULL; this->NextChainCreature = 0; diff --git a/src/creature.hh b/src/creature.hh index a6f7678..423ad8c 100644 --- a/src/creature.hh +++ b/src/creature.hh @@ -4,6 +4,7 @@ #include "common.hh" #include "connection.hh" #include "containers.hh" +#include "map.hh" #include "skill.hh" struct TCreature; @@ -18,6 +19,7 @@ struct TCombat{ // REGULAR FUNCTIONS // ========================================================================= TCombat(void); + void CheckCombatValues(void); // DATA // ========================================================================= @@ -108,7 +110,8 @@ struct TOutfit{ struct TCreature: TSkillBase { // REGULAR FUNCTIONS // ========================================================================= - // TODO + TCreature(void); + int Damage(TCreature *Attacker, int Damage, int DamageType); // VIRTUAL FUNCTIONS // ========================================================================= diff --git a/src/enums.hh b/src/enums.hh index 5124936..ea0d87a 100644 --- a/src/enums.hh +++ b/src/enums.hh @@ -6,6 +6,14 @@ // TODO(fusion): Probably cleanup these names? Prefix values? Its crazy that // there are no collision problems (possibly yet). +enum BloodType: int { + BT_BLOOD = 0, + BT_SLIME = 1, + BT_BONES = 2, + BT_FIRE = 3, + BT_ENERGY = 4, +}; + enum CreatureType: int { PLAYER = 0, MONSTER = 1, diff --git a/src/main.cc b/src/main.cc index 736f2f2..e0cf156 100644 --- a/src/main.cc +++ b/src/main.cc @@ -2,8 +2,12 @@ #include "config.hh" #include "map.hh" +#include "stubs.hh" + #include +#include #include +#include static bool BeADaemon = false; static bool Reboot = false; @@ -62,6 +66,7 @@ static void DefaultHandler(int signr){ SaveMapOn = (signr == SIGQUIT) || (signr == SIGTERM) || (signr == SIGPWR); if(signr == SIGTERM){ + int Hour, Minute; GetRealTime(&Hour, &Minute); RebootTime = (Hour * 60 + Minute + 6) % 1440; CloseGame(); @@ -114,7 +119,7 @@ static void SigAlarmHandler(int signr){ SigAlarmCounter += 1; struct itimerval new_timer = {}; - strict itimerval old_timer = {}; + struct itimerval old_timer = {}; new_timer.it_value.tv_usec = Beat * 1000; setitimer(ITIMER_REAL, &new_timer, &old_timer); } @@ -122,12 +127,12 @@ static void SigAlarmHandler(int signr){ static void InitTime(void){ SigAlarmCounter = 0; handler(SIGALRM, SigAlarmHandler); - SigAlarmHandler(SIGALARM); + SigAlarmHandler(SIGALRM); } static void ExitTime(void){ struct itimerval new_timer = {}; - strict itimerval old_timer = {}; + struct itimerval old_timer = {}; setitimer(ITIMER_REAL, &new_timer, &old_timer); handler(SIGALRM, SIG_IGN); } @@ -138,7 +143,7 @@ static void UnlockGame(void){ strcpy(FileName, SAVEPATH); strcat(FileName, "/game.pid"); - std::ifstream InputFile(Filename, std::ios_base::in); + std::ifstream InputFile(FileName, std::ios_base::in); if(!InputFile.fail()){ int Pid; InputFile >> Pid; @@ -156,7 +161,7 @@ static void LockGame(void){ strcat(FileName, "/game.pid"); { - std::ifstream InputFile(Filename, std::ios_base::in); + std::ifstream InputFile(FileName, std::ios_base::in); if(!InputFile.fail()){ int Pid; InputFile >> Pid; @@ -200,7 +205,7 @@ void LoadWorldConfig(void){ WorldType = NORMAL; RebootTime = 6 * 60; // minutes strcpy(GameAddress, "127.0.0.1"); // I KNOW - Port = 7171; + GamePort = 7171; MaxPlayers = 1000; PremiumPlayerBuffer = 100; MaxNewbies = 200; @@ -292,7 +297,7 @@ static void AdvanceGame(int Delay){ if(CreatureTimeCounter >= 1750){ CreatureTimeCounter -= 1000; - ProcessCreature(); + ProcessCreatures(); } if(CronTimeCounter >= 1500){ @@ -315,7 +320,7 @@ static void AdvanceGame(int Delay){ ProcessMonsterhomes(); ProcessMonsterRaids(); ProcessCommunicationControl(); - ProcessReaderThreadReplies(RefreshSector,SendMails); + ProcessReaderThreadReplies(RefreshSector, SendMails); ProcessWriterThreadReplies(); ProcessCommand(); 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 // NOTE(fusion): This is used by hash table entries and sectors to tell whether // they're currently loaded or swapped out to disk. @@ -57,221 +64,586 @@ static int CronEntries; static vector DepotInfo(0, 4, 5); static int Depots; -static vector Mark(0, 4, 5); +static vector 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){ - 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){ - error("SwapObject: Object doesn't exists or is not currently loaded.\n"); - return; - } - - TObject *Entry = HashTableData[Obj.ObjectID & HashTableMask]; - if(Entry->ObjectID != Obj.ObjectID){ - error("SwapObject: Übergebenes Objekt existiert nicht.\n"); - return; +bool Object::exists(void){ + if(*this == NONE){ + return false; } - File->writeBytes((const uint8*)Entry, sizeof(TObject)); - if(Entry->Type.getFlag(CONTAINER) || Entry->Type.getFlag(CHEST)){ - Object Current = Obj.getAttribute(CONTENT); - while(Current != NONE){ - Object Next = Current.getNextObject(); - SwapObject(File, Current, FileNumber); - Current = Next; - } + uint32 EntryIndex = this->ObjectID & HashTableMask; + if(HashTableType[EntryIndex] == STATUS_SWAPPED){ + UnswapSector((uint32)((uintptr)HashTableData[EntryIndex])); } - PutFreeObjectSlot(Entry); - HashTableType[EntryIndex] = STATUS_SWAPPED; - HashTableData[EntryIndex] = (TObject*)FileNumber; + return HashTableType[EntryIndex] == STATUS_LOADED + && HashTableData[EntryIndex]->ObjectID == this->ObjectID; } -static void SwapSector(void){ - static uint32 FileNumber = 0; - - TSector *Oldest = NULL; - int OldestSectorX = 0; - int OldestSectorY = 0; - int OldestSectorZ = 0; - uint32 OldestTimeStamp = RoundNr + 1; +ObjectType Object::getObjectType(void){ + return AccessObject(*this)->Type; +} - ASSERT(Sector != NULL); - for(int SectorZ = SectorZMin; SectorZ <= SectorZMax; SectorZ += 1) - for(int SectorY = SectorYMin; SectorY <= SectorYMax; SectorY += 1) - for(int SectorX = SectorXMin; SectorX <= SectorXMax; SectorX += 1){ - TSector *CurrentSector = *Sector->at(SectorX, SectorY, SectorZ); - if(CurrentSector != NULL - && CurrentSector->Status == STATUS_LOADED - && CurrentSector->TimeStamp < OldestTimeStamp){ - Oldest = CurrentSector; - OldestSectorX = SectorX; - OldestSectorY = SectorY; - OldestSectorZ = SectorZ; - OldestTimeStamp = CurrentSector->TimeStamp; - } - } +void Object::setObjectType(ObjectType Type){ + AccessObject(*this)->Type = Type; +} - if(Oldest == NULL){ - error("FATAL ERROR in SwapSector: Es kann kein Sektor ausgelagert werden.\n"); - abort(); - } +Object Object::getNextObject(void){ + return AccessObject(*this)->NextObject; +} - char FileName[4096]; - do{ - FileNumber += 1; // NOTE(fusion): Let it wrap naturally. - snprintf(FileName, sizeof(FileName), "%s/%010u.swp", SAVEPATH, FileNumber); - }while(FileExists(FileName)); +void Object::setNextObject(Object NextObject){ + AccessObject(*this)->NextObject = NextObject; +} - TWriteBinaryFile File; - try{ - File.open(FileName); - print(2, "Lagere Sektor %d/%d/%d aus...\n", OldestSectorX, OldestSectorY, OldestSectorZ); - File.writeQuad((uint32)OldestSectorX); - File.writeQuad((uint32)OldestSectorY); - File.writeQuad((uint32)OldestSectorZ); - // TODO(fusion): I think tiles are stored in column major order but it doesn't - // really matter as long as optimize for sequential access. - for(int X = 0; X < 32; X += 1){ - for(int Y = 0; Y < 32; Y += 1){ - SwapObject(&File, Oldest->MapCon[X][Y], FileNumber); - } - } - Oldest->Status = STATUS_SWAPPED; - File.close(); - }catch(const char *str){ - error("FATAL ERROR in SwapSector: Kann Datei \"%s\" nicht anlegen.\n", FileName); - error("# Fehler: %s\n", str); - abort(); - } +Object Object::getContainer(void){ + return AccessObject(*this)->Container; } -static void UnswapSector(uint32 FileNumber){ - char FileName[4096]; - snprintf(FileName, sizeof(FileName), "%s/%010u.swp", SAVEPATH, FileNumber); +void Object::setContainer(Object Container){ + AccessObject(*this)->Container = Container; +} - TReadBinaryFile File; - try{ - File.open(FileName); - int SectorX = (int)File.readQuad(); - int SectorY = (int)File.readQuad(); - int SectorZ = (int)File.readQuad(); - print(2, "Lagere Sector %d/%d/%d ein...\n", SectorX, SectorY, SectorZ); +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]`. - ASSERT(Sector != NULL); - TSector *LoadingSector = *Sector->at(SectorX, SectorY, SectorZ); - if(LoadingSector == NULL){ - error("UnswapSector: Sektor %d/%d/%d existiert nicht.\n", SectorX, SectorY, SectorZ); - File.close(); - return; - } + if(!this->getObjectType().isCreatureContainer()){ + error("Object::getCreatureID: Objekt ist keine Kreatur.\n"); + return 0; + } - if(LoadingSector->Status != STATUS_SWAPPED){ - error("UnswapSector: Sektor %d/%d/%d ist nicht ausgelagert.\n", SectorX, SectorY, SectorZ); - File.close(); - return; - } + return AccessObject(*this)->Attributes[1]; +} - int Size = File.getSize(); - while(File.getPosition() < Size){ - TObject Entry; - File.readBytes((uint8*)&Entry, sizeof(TObject)); +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; + } - uint32 EntryIndex = Entry.ObjectID & HashTableMask; - if(HashTableType[EntryIndex] == STATUS_SWAPPED){ - // NOTE(fusion): Make sure we only allocate the object if we confirm - // its status. The original code would call `readBytes` on the result - // from `GetFreeObjectSlot()` directly and would then leak it if the - // entry status was not `STATUS_SWAPPED`. - TObject *EntryPointer = GetFreeObjectSlot(); - *EntryPointer = Entry; - HashTableData[EntryIndex] = EntryPointer; - HashTableType[EntryIndex] = STATUS_LOADED; - }else{ - error("UnswapSector: Objekt %u existiert schon.\n", Entry.ObjectID); - } - } - LoadingSector->Status = STATUS_LOADED; - File.close(); - unlink(FileName); - }catch(const char *str){ - error("FATAL ERROR in UnswapSector: Kann Datei \"%s\" nicht lesen.\n", FileName); - error("# Fehler: %s\n", str); - abort(); + 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]; } -static void DeleteSwappedSectors(void){ - DIR *SwapDir = opendir(SAVEPATH); - if(SwapDir == NULL){ - error("DeleteSwappedSectors: Unterverzeichnis %s nicht gefunden\n", SAVEPATH); +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; } - char FilePath[4096]; - while(dirent *DirEntry = readdir(SwapDir)){ - if(DirEntry->d_type == DT_REG){ - // NOTE(fusion): `DirEntry->d_name` will only contain the filename - // so we need to assemble the actual file path (relative in this - // case) to properly unlink it. Windows has the same behavior with - // its `FindFirstFile`/`FindNextFile` API. - const char *FileExt = findLast(DirEntry->d_name, '.'); - if(FileExt != NULL && strcmp(FileExt, ".swp") == 0){ - snprintf(FilePath, sizeof(FilePath), "%s/%s", SAVEPATH, DirEntry->d_name); - unlink(FilePath); - } + 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); } } - closedir(SwapDir); + AccessObject(*this)->Attributes[AttributeOffset] = Value; } -static void LoadObjects(TReadScriptFile *Script, TWriteStream *Stream, bool Skip){ - int Depth = 1; - bool ParseAttribute = false; - Script->readSymbol('{'); - Script->nextToken(); - while(true){ - if(!ParseAttribute){ - if(Script->Token != SPECIAL){ - int TypeID = Script->getNumber(); - if(!ObjectTypeExists(TypeID)){ - Script->error("unknown object type"); - } +// 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; - if(!Skip){ - Stream->writeWord((uint16)TypeID); - } + char FileName[4096]; + snprintf(FileName, sizeof(FileName), "%s/map.dat", DATAPATH); - ParseAttribute = true; - }else{ - char Special = Script->getSpecial(); - if(Special == '}'){ - if(!Skip){ - Stream->writeWord(0xFFFF); - } + TReadScriptFile Script; + Script.open(FileName); + while(true){ + Script.nextToken(); + if(Script.Token == ENDOFFILE){ + Script.close(); + break; + } - Depth -= 1; - if(Depth <= 0){ - break; - } - }else if(Special != ','){ - Script->error("expected comma"); - } - } - Script->nextToken(); - }else{ - if(Script->Token != SPECIAL){ - int Attribute = GetInstanceAttributeByName(Script->getIdentifier()); + 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. + 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[EntryIndex]; + if(Entry->ObjectID != Obj.ObjectID){ + error("SwapObject: Übergebenes Objekt existiert nicht.\n"); + return; + } + + File->writeBytes((const uint8*)Entry, sizeof(TObject)); + if(Entry->Type.getFlag(CONTAINER) || Entry->Type.getFlag(CHEST)){ + Object Current = Obj.getAttribute(CONTENT); + while(Current != NONE){ + Object Next = Current.getNextObject(); + SwapObject(File, Current, FileNumber); + Current = Next; + } + } + + PutFreeObjectSlot(Entry); + HashTableType[EntryIndex] = STATUS_SWAPPED; + HashTableData[EntryIndex] = (TObject*)((uintptr)FileNumber); +} + +void SwapSector(void){ + static uint32 FileNumber = 0; + + TSector *Oldest = NULL; + int OldestSectorX = 0; + int OldestSectorY = 0; + int OldestSectorZ = 0; + uint32 OldestTimeStamp = RoundNr + 1; + + ASSERT(Sector != NULL); + for(int SectorZ = SectorZMin; SectorZ <= SectorZMax; SectorZ += 1) + for(int SectorY = SectorYMin; SectorY <= SectorYMax; SectorY += 1) + for(int SectorX = SectorXMin; SectorX <= SectorXMax; SectorX += 1){ + TSector *CurrentSector = *Sector->at(SectorX, SectorY, SectorZ); + if(CurrentSector != NULL + && CurrentSector->Status == STATUS_LOADED + && CurrentSector->TimeStamp < OldestTimeStamp){ + Oldest = CurrentSector; + OldestSectorX = SectorX; + OldestSectorY = SectorY; + OldestSectorZ = SectorZ; + OldestTimeStamp = CurrentSector->TimeStamp; + } + } + + if(Oldest == NULL){ + error("FATAL ERROR in SwapSector: Es kann kein Sektor ausgelagert werden.\n"); + abort(); + } + + char FileName[4096]; + do{ + FileNumber += 1; + if(FileNumber > 99999999){ + FileNumber = 1; + } + snprintf(FileName, sizeof(FileName), "%s/%08u.swp", SAVEPATH, FileNumber); + }while(FileExists(FileName)); + + TWriteBinaryFile File; + try{ + File.open(FileName); + print(2, "Lagere Sektor %d/%d/%d aus...\n", OldestSectorX, OldestSectorY, OldestSectorZ); + File.writeQuad((uint32)OldestSectorX); + File.writeQuad((uint32)OldestSectorY); + File.writeQuad((uint32)OldestSectorZ); + // TODO(fusion): I think tiles are stored in column major order but it doesn't + // really matter as long as optimize for sequential access. + for(int X = 0; X < 32; X += 1){ + for(int Y = 0; Y < 32; Y += 1){ + SwapObject(&File, Oldest->MapCon[X][Y], FileNumber); + } + } + Oldest->Status = STATUS_SWAPPED; + File.close(); + }catch(const char *str){ + error("FATAL ERROR in SwapSector: Kann Datei \"%s\" nicht anlegen.\n", FileName); + error("# Fehler: %s\n", str); + abort(); + } +} + +void UnswapSector(uint32 FileNumber){ + char FileName[4096]; + snprintf(FileName, sizeof(FileName), "%s/%08u.swp", SAVEPATH, FileNumber); + + TReadBinaryFile File; + try{ + File.open(FileName); + int SectorX = (int)File.readQuad(); + int SectorY = (int)File.readQuad(); + int SectorZ = (int)File.readQuad(); + print(2, "Lagere Sector %d/%d/%d ein...\n", SectorX, SectorY, SectorZ); + + ASSERT(Sector != NULL); + TSector *LoadingSector = *Sector->at(SectorX, SectorY, SectorZ); + if(LoadingSector == NULL){ + error("UnswapSector: Sektor %d/%d/%d existiert nicht.\n", SectorX, SectorY, SectorZ); + File.close(); + return; + } + + if(LoadingSector->Status != STATUS_SWAPPED){ + error("UnswapSector: Sektor %d/%d/%d ist nicht ausgelagert.\n", SectorX, SectorY, SectorZ); + File.close(); + return; + } + + int Size = File.getSize(); + while(File.getPosition() < Size){ + TObject Entry; + File.readBytes((uint8*)&Entry, sizeof(TObject)); + + uint32 EntryIndex = Entry.ObjectID & HashTableMask; + if(HashTableType[EntryIndex] == STATUS_SWAPPED){ + // NOTE(fusion): Make sure we only allocate the object if we confirm + // its status. The original code would call `readBytes` on the result + // from `GetFreeObjectSlot()` directly and would then leak it if the + // entry status was not `STATUS_SWAPPED`. + TObject *EntryPointer = GetFreeObjectSlot(); + *EntryPointer = Entry; + HashTableData[EntryIndex] = EntryPointer; + HashTableType[EntryIndex] = STATUS_LOADED; + }else{ + error("UnswapSector: Objekt %u existiert schon.\n", Entry.ObjectID); + } + } + LoadingSector->Status = STATUS_LOADED; + File.close(); + unlink(FileName); + }catch(const char *str){ + error("FATAL ERROR in UnswapSector: Kann Datei \"%s\" nicht lesen.\n", FileName); + error("# Fehler: %s\n", str); + abort(); + } +} + +void DeleteSwappedSectors(void){ + DIR *SwapDir = opendir(SAVEPATH); + if(SwapDir == NULL){ + error("DeleteSwappedSectors: Unterverzeichnis %s nicht gefunden\n", SAVEPATH); + return; + } + + char FilePath[4096]; + while(dirent *DirEntry = readdir(SwapDir)){ + if(DirEntry->d_type == DT_REG){ + // NOTE(fusion): `DirEntry->d_name` will only contain the filename + // so we need to assemble the actual file path (relative in this + // case) to properly unlink it. Windows has the same behavior with + // its `FindFirstFile`/`FindNextFile` API. + const char *FileExt = findLast(DirEntry->d_name, '.'); + if(FileExt != NULL && strcmp(FileExt, ".swp") == 0){ + snprintf(FilePath, sizeof(FilePath), "%s/%s", SAVEPATH, DirEntry->d_name); + unlink(FilePath); + } + } + } + + closedir(SwapDir); +} + +void LoadObjects(TReadScriptFile *Script, TWriteStream *Stream, bool Skip){ + int Depth = 1; + bool ProcessObjects = true; + Script->readSymbol('{'); + Script->nextToken(); + while(true){ + if(ProcessObjects){ + if(Script->Token != SPECIAL){ + int TypeID = Script->getNumber(); + if(!ObjectTypeExists(TypeID)){ + Script->error("unknown object type"); + } + + if(!Skip){ + Stream->writeWord((uint16)TypeID); + } + + ProcessObjects = false; + }else{ + char Special = Script->getSpecial(); + if(Special == '}'){ + if(!Skip){ + Stream->writeWord(0xFFFF); + } + + Depth -= 1; + if(Depth <= 0){ + break; + } + }else if(Special != ','){ + Script->error("expected comma"); + } + } + Script->nextToken(); + }else{ + if(Script->Token != SPECIAL){ + int Attribute = GetInstanceAttributeByName(Script->getIdentifier()); if(Attribute == -1){ Script->error("unknown attribute"); } @@ -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,429 +824,306 @@ 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"); - } - } - }catch(const char *str){ - error("LoadSector: Kann Datei \"%s\" nicht lesen.\n", FileName); - error("# Fehler: %s\n", str); - throw "Cannot load sector"; - } -} - -static void LoadMap(void){ - DIR *MapDir = opendir(MAPPATH); - if(MapDir == NULL){ - error("LoadMap: Unterverzeichnis %s nicht gefunden\n", MAPPATH); - throw "Cannot load map"; - } - - print(1, "Lade Karte ...\n"); - ObjectCounter = 0; - - char FilePath[4096]; - while(dirent *DirEntry = readdir(MapDir)){ - if(DirEntry->d_type == DT_REG){ - // NOTE(fusion): See note in `DeleteSwappedSectors`. - 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){ - snprintf(FilePath, sizeof(FilePath), "%s/%s", SAVEPATH, DirEntry->d_name); - LoadSector(FilePath, SectorX, SectorY, SectorZ); - SectorCounter += 1; - } - } - } - } - - closedir(MapDir); - print(1, "%d Sektoren geladen.\n", SectorCounter); - print(1, "%d Objekte geladen.\n", ObjectCounter); -} - -//SaveObjects -//SaveObjects - -void SaveSector(char *FileName, int SectorX, int SectorY, int SectorZ){ - ASSERT(Sector); - TSector *SavingSector = *Sector->at(SectorX, SectorY, SectorZ); - if(!SavingSector){ - return; - } - - bool Empty = true; - TWriteScriptFile Script; - try{ - Script.open(FileName); - print(1, "Speichere Sektor %d/%d/%d ...\n", SectorX, SectorY, SectorZ); - - Script.writeText("# Tibia - graphical Multi-User-Dungeon"); - Script.writeLn(); - Script.writeText("# Data for sector "); - Script.writeNumber(SectorX); - Script.writeText("/"); - Script.writeNumber(SectorY); - Script.writeText("/"); - Script.writeNumber(SectorZ); - Script.writeLn(); - Script.writeLn(); - - for(int X = 0; X < 32; X += 1){ - for(int Y = 0; Y < 32; Y += 1){ - Object First = Object(SavingSector->MapCon[X][Y].getAttribute(CONTENT)); - uint8 Flags = GetMapContainerFlags(SavingSector->MapCon[X][Y]); - if(First != NONE || Flags != 0){ - Script.writeNumber(X); - Script.writeText("-"); - Script.writeNumber(Y); - Script.writeText(": "); - - int AttrCount = 0; - - if(Flags & 1){ - if(AttrCount > 0){ - Script.writeText(", "); - } - Script.writeText("Refresh"); - AttrCount += 1; - } - - if(Flags & 2){ - if(AttrCount > 0){ - Script.writeText(", "); - } - Script.writeText("NoLogout"); - AttrCount += 1; - } - - if(Flags & 4){ - if(AttrCount > 0){ - Script.writeText(", "); - } - Script.writeText("ProtectionZone"); - AttrCount += 1; - } - - if(First != NONE){ - if(AttrCount > 0){ - Script.writeText(", "); - } - Script.writeText("Content="); - HelpBuffer.reset(); - SaveObjects(First, &HelpBuffer, false); - TReadBuffer ReadBuffer(HelpBuffer); - SaveObjects(&ReadBuffer, &Script); - AttrCount += 1; - } - - Script.writeLn(); - Empty = false; - } - } - } - - Script.close(); - if(Empty){ - error("SaveSector: Sektor %d/%d/%d ist leer.\n", SectorX, SectorY, SectorZ); - unlink(FileName); + } } }catch(const char *str){ - error("SaveSector: Kann Datei %s nicht schreiben.\n", FileName); + error("LoadSector: Kann Datei \"%s\" nicht lesen.\n", FileName); error("# Fehler: %s\n", str); + throw "Cannot load sector"; } } -static 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 - // `ExitMap`. - static bool SavingMap = false; - if(SavingMap){ - error("SaveMap: Karte wird schon gespeichert.\n"); - return; +void LoadMap(void){ + DIR *MapDir = opendir(MAPPATH); + if(MapDir == NULL){ + error("LoadMap: Unterverzeichnis %s nicht gefunden\n", MAPPATH); + throw "Cannot load map"; } - SavingMap = true; - print(1, "Speichere Karte ...\n"); + print(1, "Lade Karte ...\n"); ObjectCounter = 0; - char FileName[4096]; - for(int SectorZ = SectorZMin; SectorZ <= SectorZMax; SectorZ += 1) - for(int SectorY = SectorYMin; SectorY <= SectorYMax; SectorY += 1) - for(int SectorX = SectorXMin; SectorX <= SectorXMax; SectorX += 1){ - snprintf(FileName, sizeof(FileName), "%s/%04d-%04d-%02d.sec", - MAPPATH, SectorX, SectorY, SectorZ); - SaveSector(FileName, SectorX, SectorY, SectorZ); + int SectorCounter = 0; + char FilePath[4096]; + while(dirent *DirEntry = readdir(MapDir)){ + if(DirEntry->d_type == DT_REG){ + // NOTE(fusion): See note in `DeleteSwappedSectors`. + const char *FileExt = findLast(DirEntry->d_name, '.'); + if(FileExt != NULL && strcmp(FileExt, ".sec") == 0){ + int SectorX, SectorY, SectorZ; + 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; + } + } + } } - print(1, "%d Objekte gespeichert.\n", ObjectCounter); - SavingMap = false; + closedir(MapDir); + print(1, "%d Sektoren geladen.\n", SectorCounter); + print(1, "%d Objekte geladen.\n", ObjectCounter); } -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(); +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; + } - error("INFO: HashTabelle zu klein. Größe wird verdoppelt auf %d.\n", NewSize); + Stream->writeByte(0xFF); + ObjectCounter += 1; + if(Stop && Depth == 1){ + break; + } - uint32 NewMask = NewSize - 1; - TObject **NewData = (TObject**)malloc(NewSize * sizeof(TObject*)); - uint8 *NewType = (uint8*)malloc(NewSize * sizeof(uint8)); - memset(NewType, 0, NewSize * sizeof(uint8)); + if(Prev == NONE){ + error("LastObj ist NONE (1)\n"); + } - // 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. + Prev = Prev.getContainer(); + if(Prev == NONE){ + error("LastObj ist NONE (2)\n"); + } - 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]); + 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(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"); + if(Value != 0){ + Stream->writeByte((uint8)Attribute); + if(Attribute == TEXTSTRING || Attribute == EDITOR){ + Stream->writeString(GetDynamicString(Value)); + }else{ + Stream->writeQuad(Value); + } + } + } } - } - } - - 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); + Object First = NONE; + if(ObjType.getAttributeOffset(CONTENT) != -1){ + First = Obj.getAttribute(CONTENT); + } - memset(Entry, 0, sizeof(TObject)); + if(First != NONE){ + Depth += 1; + Prev = NONE; + Obj = First; + Stream->writeByte((uint8)CONTENT); + }else{ + Stream->writeByte(0xFF); + ObjectCounter += 1; + if(Stop && Depth == 1){ + break; + } - return Entry; -} + Prev = Obj; + Obj = Obj.getNextObject(); + } -static void PutFreeObjectSlot(TObject *Entry){ - if(Entry == NULL){ - error("PutFreeObjectSlot: Entry ist NULL.\n"); - return; + ProcessObjects = true; + } } - - // 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); +void SaveObjects(TReadStream *Stream, TWriteScriptFile *Script){ + int Depth = 1; + bool ProcessObjects = true; + bool FirstObject = true; + Script->writeText("{"); while(true){ - Script.nextToken(); - if(Script.Token == ENDOFFILE){ - Script.close(): - break; - } - - char Identifier[MAX_IDENT_LENGTH]; - strcpy(Identifier, Script.readIdentifier()); - Script.readSymbol('='); + if(ProcessObjects){ + int TypeID = (int)Stream->readWord(); + if(TypeID != 0xFFFF){ + if(!FirstObject){ + Script->writeText(", "); + } - 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"); + Script->writeNumber(TypeID); + }else{ + Depth -= 1; + Script->writeText("}"); } - 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); + + ProcessObjects = false; }else{ - // TODO(fusion): - //error("Unknown map configuration key \"%s\"", Identifier); + 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; + } } } +} - // 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"; +void SaveSector(char *FileName, int SectorX, int SectorY, int SectorZ){ + ASSERT(Sector); + TSector *SavingSector = *Sector->at(SectorX, SectorY, SectorZ); + if(!SavingSector){ + return; } - if(SectorXMax >= 2047){ - throw "illegal value for SectorXMax"; - } + bool Empty = true; + TWriteScriptFile Script; + try{ + Script.open(FileName); + print(1, "Speichere Sektor %d/%d/%d ...\n", SectorX, SectorY, SectorZ); - if(SectorYMin <= 0){ - throw "illegal value for SectorYMin"; - } + Script.writeText("# Tibia - graphical Multi-User-Dungeon"); + Script.writeLn(); + Script.writeText("# Data for sector "); + Script.writeNumber(SectorX); + Script.writeText("/"); + Script.writeNumber(SectorY); + Script.writeText("/"); + Script.writeNumber(SectorZ); + Script.writeLn(); + Script.writeLn(); - if(SectorYMax >= 2047){ - throw "illegal value for SectorYMax"; - } + for(int X = 0; X < 32; X += 1){ + for(int Y = 0; Y < 32; Y += 1){ + Object First = Object(SavingSector->MapCon[X][Y].getAttribute(CONTENT)); + uint8 Flags = GetMapContainerFlags(SavingSector->MapCon[X][Y]); + if(First != NONE || Flags != 0){ + Script.writeNumber(X); + Script.writeText("-"); + Script.writeNumber(Y); + Script.writeText(": "); - if(SectorZMin < 0){ - throw "illegal value for SectorZMin"; - } + int AttrCount = 0; - if(SectorZMax > 15){ - throw "illegal value for SectorZMax"; - } + if(Flags & 1){ + if(AttrCount > 0){ + Script.writeText(", "); + } + Script.writeText("Refresh"); + AttrCount += 1; + } - if(SectorXMin > SectorXMax){ - throw "SectorXMin is greater than SectorXMax"; - } + if(Flags & 2){ + if(AttrCount > 0){ + Script.writeText(", "); + } + Script.writeText("NoLogout"); + AttrCount += 1; + } - if(SectorYMin > SectorYMax){ - throw "SectorYMin is greater than SectorYMax"; - } + if(Flags & 4){ + if(AttrCount > 0){ + Script.writeText(", "); + } + Script.writeText("ProtectionZone"); + AttrCount += 1; + } - if(SectorZMin > SectorZMax){ - throw "SectorZMin is greater than SectorZMax"; - } + if(First != NONE){ + if(AttrCount > 0){ + Script.writeText(", "); + } + Script.writeText("Content="); + HelpBuffer.reset(); + SaveObjects(First, &HelpBuffer, false); + TReadBuffer ReadBuffer(HelpBuffer.Data, HelpBuffer.Position); + SaveObjects(&ReadBuffer, &Script); + AttrCount += 1; + } - // 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"; - } + Script.writeLn(); + Empty = false; + } + } + } - if(OBCount <= 0){ - throw "illegal value for CacheSize"; + Script.close(); + if(Empty){ + error("SaveSector: Sektor %d/%d/%d ist leer.\n", SectorX, SectorY, SectorZ); + unlink(FileName); + } + }catch(const char *str){ + error("SaveSector: Kann Datei %s nicht schreiben.\n", FileName); + error("# Fehler: %s\n", str); } +} - OBCount /= 32768; - - if(HashTableSize <= 0){ - throw "illegal value for Objects"; +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 + // `ExitMap`. + static bool SavingMap = false; + if(SavingMap){ + error("SaveMap: Karte wird schon gespeichert.\n"); + return; } - if(!ISPOW2(HashTableSize)){ - throw "Objects must be a power of 2"; - } + SavingMap = true; + print(1, "Speichere Karte ...\n"); + ObjectCounter = 0; - if(NewbieStartPositionX == 0){ - throw "no start position for newbies specified"; + char FileName[4096]; + for(int SectorZ = SectorZMin; SectorZ <= SectorZMax; SectorZ += 1) + for(int SectorY = SectorYMin; SectorY <= SectorYMax; SectorY += 1) + for(int SectorX = SectorXMin; SectorX <= SectorXMax; SectorX += 1){ + snprintf(FileName, sizeof(FileName), "%s/%04d-%04d-%02d.sec", + MAPPATH, SectorX, SectorY, SectorZ); + SaveSector(FileName, SectorX, SectorY, SectorZ); } - if(VeteranStartPositionX == 0){ - throw "no start position for veterans specified"; - } + print(1, "%d Objekte gespeichert.\n", ObjectCounter); + SavingMap = false; } void InitMap(void){ @@ -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; } diff --git a/src/map.hh b/src/map.hh index 590fb41..3c92fff 100644 --- a/src/map.hh +++ b/src/map.hh @@ -3,12 +3,22 @@ #include "common.hh" #include "objects.hh" +#include "script.hh" struct Object { // REGULAR FUNCTIONS // ========================================================================= - Object(void) { this->ObjectID = 0; } - Object(uint32 ObjectID) { this->ObjectID = ObjectID; } + constexpr Object(void) : ObjectID(0) {} + constexpr Object(uint32 ObjectID): ObjectID(ObjectID) {} + + constexpr bool operator==(const Object &other) const { + return this->ObjectID == other.ObjectID; + } + + constexpr bool operator!=(const Object &other) const { + return this->ObjectID != other.ObjectID; + } + bool exists(void); ObjectType getObjectType(void); @@ -71,7 +81,36 @@ struct TCronEntry { int Next; }; +// NOTE(fusion): Map management functions. Most for internal use. +void SwapObject(TWriteBinaryFile *File, Object Obj, uint32 FileNumber); +void SwapSector(void); +void UnswapSector(uint32 FileNumber); +void DeleteSwappedSectors(void); +void LoadObjects(TReadScriptFile *Script, TWriteStream *Stream, bool Skip); +void LoadObjects(TReadStream *Stream, Object Con); +void InitSector(int SectorX, int SectorY, int SectorZ); +void LoadSector(const char *FileName, int SectorX, int SectorY, int SectorZ); +void LoadMap(void); +void SaveObjects(Object Obj, TWriteStream *Stream, bool Stop); +void SaveObjects(TReadStream *Stream, TWriteScriptFile *Script); +void SaveSector(char *FileName, int SectorX, int SectorY, int SectorZ); +void SaveMap(void); void InitMap(void); void ExitMap(bool Save); +// NOTE(fusion): Object related functions. +Object CreateObject(void); +TObject *AccessObject(Object Obj); +void ChangeObject(Object Obj, ObjectType NewType); +int GetObjectPriority(Object Obj); +void PlaceObject(Object Obj, Object Con, bool Append); +Object AppendObject(Object Con, ObjectType Type); +Object GetFirstContainerObject(Object Con); +Object GetContainerObject(Object Con, int Index); +void GetObjectCoordinates(Object Obj, int *x, int *y, int *z); +uint8 GetMapContainerFlags(Object Obj); +Object GetMapContainer(int x, int y, int z); +Object GetMapContainer(Object Obj); +Object GetFirstObject(int x, int y, int z); + #endif //TIBIA_MAP_HH_ diff --git a/src/monster.hh b/src/monster.hh index b9bd27e..dd246a1 100644 --- a/src/monster.hh +++ b/src/monster.hh @@ -85,4 +85,6 @@ struct TMonster: TNonplayer { }; #endif +extern TRaceData RaceData[512]; + #endif //TIBIA_MONSTER_HH_ diff --git a/src/objects.cc b/src/objects.cc index 0119088..de62aca 100644 --- a/src/objects.cc +++ b/src/objects.cc @@ -1,4 +1,5 @@ #include "objects.hh" +#include "containers.hh" static vector ObjectTypes(0, 5000, 1000); static ObjectType SpecialObjects[49]; @@ -69,24 +70,24 @@ static FLAG TypeAttributeFlags[62] = { }; static FLAG InstanceAttributeFlags[18] = { - CONTAINER // CONTENT - CHEST // CHESTQUESTNUMBER - CUMULATIVE // AMOUNT - KEY // KEYNUMBER - KEYDOOR // KEYHOLENUMBER - LEVELDOOR // DOORLEVEL - QUESTDOOR // DOORQUESTNUMBER - QUESTDOOR // DOORQUESTVALUE - RUNE // CHARGES - TEXT // TEXTSTRING - TEXT // EDITOR - LIQUIDCONTAINER // CONTAINERLIQUIDTYPE - LIQUIDPOOL // POOLLIQUIDTYPE - TELEPORTABSOLUTE // ABSTELEPORTDESTINATION - MAGICFIELD // RESPONSIBLE - EXPIRE // REMAININGEXPIRETIME - EXPIRESTOP // SAVEDEXPIRETIME - WEAROUT // REMAININGUSES + CONTAINER, // CONTENT + CHEST, // CHESTQUESTNUMBER + CUMULATIVE, // AMOUNT + KEY, // KEYNUMBER + KEYDOOR, // KEYHOLENUMBER + LEVELDOOR, // DOORLEVEL + QUESTDOOR, // DOORQUESTNUMBER + QUESTDOOR, // DOORQUESTVALUE + RUNE, // CHARGES + TEXT, // TEXTSTRING + TEXT, // EDITOR + LIQUIDCONTAINER, // CONTAINERLIQUIDTYPE + LIQUIDPOOL, // POOLLIQUIDTYPE + TELEPORTABSOLUTE, // ABSTELEPORTDESTINATION + MAGICFIELD, // RESPONSIBLE + EXPIRE, // REMAININGEXPIRETIME + EXPIRESTOP, // SAVEDEXPIRETIME + WEAROUT, // REMAININGUSES }; static const char InstanceAttributeNames[18][30] = { @@ -180,6 +181,11 @@ const char *ObjectType::getDescription(void){ // Object Type Related Functions // ============================================================================= +const char *GetInstanceAttributeName(int Attribute){ + ASSERT(Attribute >= 0 && Attribute <= 17); + return InstanceAttributeNames[Attribute]; +} + int GetInstanceAttributeByName(const char *Name){ int Result = -1; for(int i = 0; i < NARRAY(InstanceAttributeNames); i += 1){ diff --git a/src/objects.hh b/src/objects.hh index 3cbc401..61a0185 100644 --- a/src/objects.hh +++ b/src/objects.hh @@ -65,6 +65,7 @@ struct TObjectType { int AttributeOffsets[18]; }; +const char *GetInstanceAttributeName(int Attribute); int GetInstanceAttributeByName(const char *Name); bool ObjectTypeExists(int TypeID); //void InitObjects(void); diff --git a/src/player.cc b/src/player.cc index a6cd4a5..0ee78f3 100644 --- a/src/player.cc +++ b/src/player.cc @@ -1,6 +1,8 @@ #include "player.hh" #include "enums.hh" +#include "stubs.hh" + void TPlayer::CheckState(void){ if(this->Connection != NULL){ uint8 State = 0; diff --git a/src/player.hh b/src/player.hh index f0c4a82..c985408 100644 --- a/src/player.hh +++ b/src/player.hh @@ -69,6 +69,7 @@ struct TPlayer: TCreature { // REGULAR FUNCTIONS // ========================================================================= void CheckState(void); + uint8 GetActiveProfession(void); // VIRTUAL FUNCTIONS // ========================================================================= diff --git a/src/script.cc b/src/script.cc index 39141f7..5606107 100644 --- a/src/script.cc +++ b/src/script.cc @@ -595,7 +595,7 @@ TWriteScriptFile::~TWriteScriptFile(void){ } } -void TWriteScriptFile::open(char *FileName){ +void TWriteScriptFile::open(const char *FileName){ if(this->File != NULL){ ::error("TWriteScriptFile::open: Altes Skript ist noch offen.\n"); if(fclose(this->File) != 0){ @@ -628,7 +628,7 @@ void TWriteScriptFile::close(void){ this->File = NULL; } -void TWriteScriptFile::error(char *Text){ +void TWriteScriptFile::error(const char *Text){ if(this->File != NULL){ if(fclose(this->File) != 0){ ::error("TWriteScriptFile::error: Fehler %d beim Schließen der Datei.\n", errno); @@ -638,7 +638,7 @@ void TWriteScriptFile::error(char *Text){ snprintf(ErrorString, sizeof(ErrorString), "error in script-file \"%s\", line %d: %s", - this->Filename, this->Line); + this->Filename, this->Line, Text); throw ErrorString; } @@ -652,7 +652,7 @@ void TWriteScriptFile::writeLn(void){ putc('\n', this->File); } -void TWriteScriptFile::writeText(char *Text){ +void TWriteScriptFile::writeText(const char *Text){ if(this->File == NULL){ ::error("TWriteScriptFile::writeText: Kein Skript zum Schreiben geöffnet.\n"); throw "Cannot write text"; @@ -679,9 +679,9 @@ void TWriteScriptFile::writeNumber(int Number){ this->writeText(s); } -void TWriteScriptFile::writeString(char *Text){ +void TWriteScriptFile::writeString(const char *Text){ if(this->File == NULL){ - ::error(); + ::error("TWriteScriptFile::writeString: Kein Skript zum Schreiben geöffnet.\n"); throw "Cannot write string"; } @@ -722,7 +722,7 @@ void TWriteScriptFile::writeCoordinate(int x ,int y ,int z){ this->writeText(s); } -void TWriteScriptFile::writeBytesequence(uint8 *Sequence, int Length){ +void TWriteScriptFile::writeBytesequence(const uint8 *Sequence, int Length){ if(this->File == NULL){ ::error("TWriteScriptFile::writeBytesequence: Kein Skript zum Schreiben geöffnet.\n"); throw "Cannot write bytesequence"; @@ -743,6 +743,7 @@ void TWriteScriptFile::writeBytesequence(uint8 *Sequence, int Length){ putc('-', this->File); } + char s[32]; snprintf(s, sizeof(s), "%u", Sequence[i]); this->writeText(s); } @@ -754,7 +755,7 @@ TReadBinaryFile::TReadBinaryFile(void){ this->File = NULL; } -void TReadBinaryFile::open(char *FileName){ +void TReadBinaryFile::open(const char *FileName){ if(this->File != NULL){ this->error("File still open"); } @@ -770,7 +771,7 @@ void TReadBinaryFile::open(char *FileName){ this->FileSize = -1; } -int TReadBinaryFile::close(void){ +void TReadBinaryFile::close(void){ // TODO(fusion): Check if file is NULL? if(fclose(this->File) != 0){ int ErrCode = errno; @@ -781,7 +782,7 @@ int TReadBinaryFile::close(void){ this->File = NULL; } -void TReadBinaryFile::error(char *Text){ +void TReadBinaryFile::error(const char *Text){ if(this->File != NULL){ if(fclose(this->File) != 0){ ::error("TReadBinaryFile::error: Fehler %d beim Schließen der Datei.\n", errno); @@ -870,7 +871,7 @@ void TReadBinaryFile::readBytes(uint8 *Buffer, int Count){ } } -bool TReadBinaryFile::eof(TReadBinaryFile *this){ +bool TReadBinaryFile::eof(void){ if(this->File == NULL){ this->error("File not open for eof check"); } @@ -901,7 +902,7 @@ TWriteBinaryFile::TWriteBinaryFile(void){ this->File = NULL; } -void TWriteBinaryFile::open(char *FileName){ +void TWriteBinaryFile::open(const char *FileName){ if(this->File != NULL){ this->error("File still open"); } @@ -932,7 +933,7 @@ void TWriteBinaryFile::close(void){ this->File = NULL; } -void TWriteBinaryFile::error(char *Text){ +void TWriteBinaryFile::error(const char *Text){ if(this->File != NULL){ if(fclose(this->File) != 0){ ::error("TWriteBinaryFile::error: Fehler %d beim Schließen der Datei.\n", errno); @@ -966,7 +967,7 @@ void TWriteBinaryFile::writeByte(uint8 Byte){ } } -void TWriteBinaryFile::writeBytes(uint8 *Buffer, int Count){ +void TWriteBinaryFile::writeBytes(const uint8 *Buffer, int Count){ int Result = (int)fwrite(Buffer, 1, Count, this->File); if(Result != Count){ int ErrCode = errno; diff --git a/src/script.hh b/src/script.hh index 0aa6a78..0361190 100644 --- a/src/script.hh +++ b/src/script.hh @@ -86,15 +86,15 @@ struct TWriteScriptFile { // ========================================================================= TWriteScriptFile(void); ~TWriteScriptFile(void); - void open(char *FileName); + void open(const char *FileName); void close(void); - void error(char *Text); + void error(const char *Text); void writeLn(void); - void writeText(char *Text); + void writeText(const char *Text); void writeNumber(int Number); - void writeString(char *Text); + void writeString(const char *Text); void writeCoordinate(int x ,int y ,int z); - void writeBytesequence(uint8 *Sequence, int Length); + void writeBytesequence(const uint8 *Sequence, int Length); // DATA // ========================================================================= @@ -107,9 +107,9 @@ struct TReadBinaryFile: TReadStream { // REGULAR FUNCTIONS // ========================================================================= TReadBinaryFile(void); - void open(char *FileName); - int close(void); - void error(char *Text); + void open(const char *FileName); + void close(void); + void error(const char *Text); int getPosition(void); int getSize(void); void seek(int Offset); @@ -118,13 +118,13 @@ struct TReadBinaryFile: TReadStream { // ========================================================================= uint8 readByte(void) override; void readBytes(uint8 *Buffer, int Count) override; - bool eof(TReadBinaryFile *this) override; + bool eof(void) override; void skip(int Count) override; // TODO(fusion): Appended virtual functions. These are not in the base class // VTABLE which can be problematic if we intend to use polymorphism, although // that doesn't seem to be case. - virtual ~TReadBinaryFile(void) override; // VTABLE[8] + virtual ~TReadBinaryFile(void); // VTABLE[8] // Duplicate destructor that also calls operator delete. // VTABLE[9] // DATA @@ -138,14 +138,14 @@ struct TWriteBinaryFile: TWriteStream { // REGULAR FUNCTIONS // ========================================================================= TWriteBinaryFile(void); - void open(char *FileName); + void open(const char *FileName); void close(void); - void error(char *Text); + void error(const char *Text); // VIRTUAL FUNCTIONS // ========================================================================= void writeByte(uint8 Byte) override; - void writeBytes(uint8 *Buffer, int Count) override; + void writeBytes(const uint8 *Buffer, int Count) override; // TODO(fusion): Appended virtual functions. These are not in the base class // VTABLE which can be problematic if we intend to use polymorphism, although diff --git a/src/shm.cc b/src/shm.cc index c26638f..c68ce1f 100644 --- a/src/shm.cc +++ b/src/shm.cc @@ -1,4 +1,11 @@ #include "common.hh" +#include "config.hh" +#include "enums.hh" +#include "thread.hh" + +#include "stubs.hh" + +#include // NOTE(fusion): This looks like an interface to external tools. Looking at the // `bin` directory this program was in, there are other programs that probably @@ -103,7 +110,7 @@ pid_t GetGameThreadPID(void){ return Pid; } -static void ErrorHandler(char *Text){ +static void ErrorHandler(const char *Text){ if(VerboseOutput){ printf("%s", Text); } @@ -119,7 +126,7 @@ static void ErrorHandler(char *Text){ } } -static void PrintHandler(int Level, char *Text){ +static void PrintHandler(int Level, const char *Text){ static Semaphore LogfileMutex(1); if(Level > DebugLevel){ @@ -249,7 +256,7 @@ void SetCommand(int Command, char *Text){ if(Text == NULL){ SHM->CommandBuffer[0] = 0; }else{ - strncpy(SHM->CommandBuffer, sizeof(SHM->CommandBuffer), Text); + strncpy(SHM->CommandBuffer, Text, sizeof(SHM->CommandBuffer)); SHM->CommandBuffer[sizeof(SHM->CommandBuffer) - 1] = 0; } } @@ -359,8 +366,9 @@ void InitSHM(bool Verbose){ // the decompiled version was also clearing SHM, probably just in case. memset(SHM, 0, sizeof(TSharedMemory)); - strncpy(SHM->PrintBuffer[0], sizeof(SHM->PrintBuffer[0]), - "SHM initialized. System printing is working!\n"); + strncpy(SHM->PrintBuffer[0], + "SHM initialized. System printing is working!\n", + sizeof(SHM->PrintBuffer[0])); SHM->PrintBuffer[0][sizeof(SHM->PrintBuffer[0]) - 1] = 0; SHM->PrintBufferPosition = 1; diff --git a/src/skill.cc b/src/skill.cc index 6ca04ef..aa69658 100644 --- a/src/skill.cc +++ b/src/skill.cc @@ -1,8 +1,11 @@ #include "skill.hh" - #include "enums.hh" #include "creature.hh" +#include "monster.hh" #include "player.hh" + +#include "stubs.hh" + #include // TSkill REGULAR FUNCTIONS @@ -952,8 +955,7 @@ void TSkillIllusion::Event(int Range){ if(this->Get() <= 0){ Master->Outfit = Master->OrgOutfit; AnnounceChangedCreature(Master->ID, 3); - // TODO(fusion): I'm not sure this is correct. - NotifyAllCreatures(&Master->CrObject, 2, &NONE); + NotifyAllCreatures(Master->CrObject, 2, NONE); } } } @@ -1101,7 +1103,7 @@ void TSkillEnergy::Event(int Range){ // TSkillBase //============================================================================== TSkillBase::TSkillBase(void){ - STATIC_ASSERT(NARRAY(this>Skills) == NARRAY(this->TimerList)); + STATIC_ASSERT(NARRAY(this->Skills) == NARRAY(this->TimerList)); for(int i = 0; i < NARRAY(this->Skills); i += 1){ this->Skills[i] = NULL; this->TimerList[i] = NULL; diff --git a/src/stubs.hh b/src/stubs.hh new file mode 100644 index 0000000..72c898d --- /dev/null +++ b/src/stubs.hh @@ -0,0 +1,64 @@ +#ifndef TIBIA_STUBS_HH_ +#define TIBIA_STUBS_HH_ 1 + +#include "common.hh" +#include "enums.hh" +#include "connection.hh" +#include "creature.hh" +#include "map.hh" +#include "player.hh" + +// IMPORTANT(fusion): These function definitions exist to test compilation. They're +// not yet implemented and will cause the linker to fail with unresolved symbols. + +typedef void TRefreshSectorFunction(int SectorX, int SectorY, int SectorZ, TReadStream *Stream); +typedef void TSendMailsFunction(TPlayerData *PlayerData); + +extern void AbortWriter(void); +extern uint32 AddDynamicString(const char *Text); +extern void AnnounceChangedCreature(uint32 CreatureID, int Type); +extern void BroadcastMessage(int Mode, const char *Text, ...) ATTR_PRINTF(2, 3); +extern bool CheckRight(uint32 CreatureID, RIGHT Right); +extern void CleanupDynamicStrings(void); +extern void CreatePlayerList(bool Online); +extern void CronChange(Object Obj, int NewDelay); +extern void CronExpire(Object Obj, int Delay); +extern uint32 CronInfo(Object Obj, bool Delete); +extern uint32 CronStop(Object Obj); +extern void DeleteDynamicString(uint32 Number); +extern bool FileExists(const char *FileName); +extern TCreature *GetCreature(uint32 CreatureID); +extern const char *GetDynamicString(uint32 Number); +extern TConnection *GetFirstConnection(void); +extern TConnection *GetNextConnection(void); +extern bool IsProtectionZone(int x, int y, int z); +extern void Log(const char *ProtocolName, const char *Text, ...) ATTR_PRINTF(2, 3); +extern void LogoutAllPlayers(void); +extern void MoveCreatures(int Delay); +extern void NetLoadCheck(void); +extern void NetLoadSummary(void); +extern void NotifyAllCreatures(Object Obj, int Type, Object OldCon); +extern void ProcessCommunicationControl(void); +extern void ProcessConnections(void); +extern void ProcessCreatures(void); +extern void ProcessCronSystem(void); +extern void ProcessMonsterhomes(void); +extern void ProcessMonsterRaids(void); +extern void ProcessReaderThreadReplies(TRefreshSectorFunction *RefreshSector, TSendMailsFunction *SendMails); +extern void ProcessSkills(void); +extern void ProcessWriterThreadReplies(void); +extern void ReceiveData(void); +extern void RefreshCylinders(void); +extern void RefreshMap(void); +extern void RefreshSector(int SectorX, int SectorY, int SectorZ, TReadStream *Stream); +extern void SavePlayerDataOrder(void); +extern void SendAll(void); +extern void SendAmbiente(TConnection *Connection); +extern void SendMails(TPlayerData *PlayerData); +extern void SendMessage(TConnection *Connection, int Mode, const char *Text, ...) ATTR_PRINTF(3, 4); +extern void SendPlayerData(TConnection *Connection); +extern void SendPlayerSkills(TConnection *Connection); +extern void SendPlayerState(TConnection *Connection, uint8 State); +extern void WriteKillStatistics(void); + +#endif //TIBIA_STUBS_HH_ diff --git a/src/thread.cc b/src/thread.cc index 2d2d427..89dfc92 100644 --- a/src/thread.cc +++ b/src/thread.cc @@ -50,7 +50,7 @@ int JoinThread(ThreadHandle Handle){ int Result = 0; int *ResultPointer; - pthread_join((pthread_t)Handle, &ResultPointer); + pthread_join((pthread_t)Handle, (void**)&ResultPointer); if(ResultPointer != NULL){ Result = *ResultPointer; delete ResultPointer; diff --git a/src/thread.hh b/src/thread.hh index 825b330..20cfa0b 100644 --- a/src/thread.hh +++ b/src/thread.hh @@ -18,7 +18,7 @@ void DelayThread(int Seconds, int MicroSeconds); struct Semaphore { // REGULAR FUNCTIONS // ========================================================================= - Semaphore(void); + Semaphore(int Value); ~Semaphore(void); void up(void); void down(void); diff --git a/src/time.cc b/src/time.cc index 4e743ed..0003c39 100644 --- a/src/time.cc +++ b/src/time.cc @@ -1,5 +1,7 @@ #include "common.hh" +#include + // IMPORTANT(fusion): `RoundNr` is just the number of seconds since startup which // is why `GetRoundAtTime` and `GetRoundForNextMinute` straight up uses it as // seconds. It is incremented every 1 second inside `AdvanceGame`. @@ -38,7 +40,7 @@ void GetDate(int *Year, int *Cycle, int *Day){ // NOTE(fusion): This maps each real time week to a game time year. time_t RealTime = time(NULL); struct tm LocalTime = GetLocalTimeTM(RealTime); - *Year = ((T / 86400) + 4) / 7 + *Year = (int)(((RealTime / 86400) + 4) / 7); *Cycle = LocalTime.tm_wday; *Day = LocalTime.tm_hour; } @@ -79,7 +81,7 @@ void GetAmbiente(int *Brightness, int *Color){ } uint32 GetRoundAtTime(int Hour, int Minute){ - struct tm LocalTime = GetLocalTimeTM(RealTime); + struct tm LocalTime = GetLocalTimeTM(time(NULL)); int SecondsToTime = (Hour - LocalTime.tm_hour) * 3600 + (Minute - LocalTime.tm_min) * 60 + (0 - LocalTime.tm_sec); @@ -90,7 +92,7 @@ uint32 GetRoundAtTime(int Hour, int Minute){ } uint32 GetRoundForNextMinute(void){ - struct tm LocalTime = GetLocalTimeTM(RealTime); + struct tm LocalTime = GetLocalTimeTM(time(NULL)); int SecondsToNextMinute = 60 - LocalTime.tm_sec; return SecondsToNextMinute + RoundNr + 30; } diff --git a/src/util.cc b/src/util.cc index 017e98b..418fb98 100644 --- a/src/util.cc +++ b/src/util.cc @@ -11,7 +11,7 @@ void SetPrintFunction(TPrintFunction *Function){ PrintFunction = Function; } -void error(char *Text, ...){ +void error(const char *Text, ...){ char s[1024]; va_list ap; @@ -26,7 +26,7 @@ void error(char *Text, ...){ } } -void print(int Level, char *Text, ...){ +void print(int Level, const char *Text, ...){ char s[1024]; va_list ap; @@ -109,8 +109,8 @@ char *strUpper(char *s){ return s; } -int stricmp(const char *s1, const char *s2, int Pos){ - for(int i = 0; i < Pos; i += 1){ +int stricmp(const char *s1, const char *s2, int Max /*= INT_MAX*/){ + for(int i = 0; i < Max; i += 1){ int c1 = toLower(s1[i]); int c2 = toLower(s2[i]); if(c1 > c2){ @@ -120,10 +120,11 @@ int stricmp(const char *s1, const char *s2, int Pos){ }else{ ASSERT(c1 == c2); if(c1 == 0){ - return 0; + break; } } } + return 0; } char *findFirst(char *s, char c){ @@ -179,7 +180,7 @@ void TReadStream::readString(char *Buffer, int MaxLength){ if(Length > 0){ if(MaxLength < 0 || MaxLength > Length){ - this->readBytes(Buffer, Length); + this->readBytes((uint8*)Buffer, Length); Buffer[Length] = 0; }else{ this->readBytes((uint8*)Buffer, MaxLength - 1); @@ -375,7 +376,7 @@ void TWriteBuffer::writeQuad(uint32 Quad){ this->Position += 4; } -void TWriteBuffer::writeBytes(uint8 *Buffer, int Count){ +void TWriteBuffer::writeBytes(const uint8 *Buffer, int Count){ if((this->Size - this->Position) < Count){ throw "buffer full"; } @@ -436,7 +437,7 @@ void TDynamicWriteBuffer::writeQuad(uint32 Quad){ this->Position += 4; } -void TDynamicWriteBuffer::writeBytes(uint8 *Buffer, int Count){ +void TDynamicWriteBuffer::writeBytes(const uint8 *Buffer, int Count){ while((this->Size - this->Position) < Count){ this->resizeBuffer(); } -- cgit v1.2.3