From fbb392953c5af29b6a0924590c24a05befb72e56 Mon Sep 17 00:00:00 2001 From: fusion32 Date: Mon, 26 May 2025 01:44:13 -0300 Subject: more object functions --- src/map.cc | 394 +++++++++++++++++++++++++++++++++++++++++++++------------ src/map.hh | 78 +++++++++--- src/objects.hh | 8 ++ src/stubs.hh | 2 + 4 files changed, 387 insertions(+), 95 deletions(-) (limited to 'src') diff --git a/src/map.cc b/src/map.cc index 20dae79..0930083 100644 --- a/src/map.cc +++ b/src/map.cc @@ -8,30 +8,6 @@ #include -// NOTE(fusion): This is used by hash table entries and sectors to tell whether -// they're currently loaded or swapped out to disk. -enum : uint8 { - STATUS_FREE = 0, - STATUS_LOADED = 1, - STATUS_SWAPPED = 2, - - // TODO(fusion): It seems this is only used with the `NONE` entry in the - // hash table. I haven't seen it used **yet** but It may have a purpose - // aside from preventing swap outs. - STATUS_PERMANENT = 255, -}; - -// NOTE(fusion): This is used to determine precedence order of different objects -// in a tile. -enum : int { - PRIORITY_BANK = 0, - PRIORITY_CLIP = 1, - PRIORITY_BOTTOM = 2, - PRIORITY_TOP = 3, - PRIORITY_CREATURE = 4, - PRIORITY_OTHER = 5, -}; - static int OBCount; static int SectorXMin; static int SectorXMax; @@ -1207,6 +1183,24 @@ void ExitMap(bool Save){ // Object Functions // ============================================================================= +TObject *AccessObject(Object Obj){ + if(Obj == NONE){ + error("AccessObject: Ungültige Objektnummer Null.\n"); + return HashTableData[0]; + } + + uint32 EntryIndex = Obj.ObjectID & HashTableMask; + if(HashTableType[EntryIndex] == STATUS_SWAPPED){ + UnswapSector((uint32)((uintptr)HashTableData[EntryIndex])); + } + + if(HashTableType[EntryIndex] == STATUS_LOADED && HashTableData[EntryIndex]->ObjectID == Obj.ObjectID){ + return HashTableData[EntryIndex]; + }else{ + return HashTableData[0]; + } +} + Object CreateObject(void){ static uint32 NextObjectID = 1; @@ -1240,25 +1234,58 @@ Object CreateObject(void){ return Object(NextObjectID); } -// DestroyObject -// DeleteObject +void DestroyObject(Object Obj){ + if(!Obj.exists()){ + error("DestroyObject: Übergebenes Objekt existiert nicht.\n"); + return; + } + + ObjectType ObjType = Obj.getObjectType(); + if(ObjType.getFlag(TEXT)){ + DeleteDynamicString(Obj.getAttribute(TEXTSTRING)); + DeleteDynamicString(Obj.getAttribute(EDITOR)); + } -TObject *AccessObject(Object Obj){ + if(ObjType.getFlag(CONTAINER) || ObjType.getFlag(CHEST)){ + while(true){ + Object Inner = Obj.getAttribute(CONTENT); + if(Inner == NONE){ + break; + } + DeleteObject(Inner); + } + } + + if(ObjType.getFlag(EXPIRE)){ + CronStop(Obj); + } + + // TODO(fusion): I feel this should be checked up front? if(Obj == NONE){ - error("AccessObject: Ungültige Objektnummer Null.\n"); - return HashTableData[0]; + error("DestroyObject: Ungültige Objektnummer %d.\n", NONE.ObjectID); + return; } uint32 EntryIndex = Obj.ObjectID & HashTableMask; - if(HashTableType[EntryIndex] == STATUS_SWAPPED){ - UnswapSector((uint32)((uintptr)HashTableData[EntryIndex])); + if(HashTableType[EntryIndex] != STATUS_LOADED){ + error("DestroyObject: Objekt steht nicht im Speicher.\n"); + return; } - if(HashTableType[EntryIndex] == STATUS_LOADED && HashTableData[EntryIndex]->ObjectID == Obj.ObjectID){ - return HashTableData[EntryIndex]; - }else{ - return HashTableData[0]; + PutFreeObjectSlot(HashTableData[EntryIndex]); + HashTableType[EntryIndex] = STATUS_FREE; + HashTableFree += 1; + DecrementObjectCounter(); +} + +void DeleteObject(Object Obj){ + if(!Obj.exists()){ + error("DeleteObject: Übergebenes Objekt existiert nicht.\n"); + return; } + + CutObject(Obj); + DestroyObject(Obj); } void ChangeObject(Object Obj, ObjectType NewType){ @@ -1338,6 +1365,41 @@ void ChangeObject(Object Obj, ObjectType NewType){ CronExpire(Obj, Delay); } +void ChangeObject(Object Obj, INSTANCEATTRIBUTE Attribute, uint32 Value){ + if(!Obj.exists()){ + error("ChangeObject: Übergebenes Objekt existiert nicht.\n"); + return; + } + + Obj.setAttribute(Attribute, Value); +} + +void ChangeObject(Object Obj, ObjectType NewType, uint32 Value){ + if(!Obj.exists()){ + error("ChangeObject: Übergebenes Objekt existiert nicht (1, NewType=%d).\n", NewType.TypeID); + return; + } + + // TODO(fusion): Why are we checking if `Obj` exists a second time? There is + // no reason to assume `Obj.getContainer()` would change anything since the + // first call. + Object Con = Obj.getContainer(); + if(!Obj.exists()){ + error("ChangeObject: Übergebenes Objekt existiert nicht (2, NewType=%d).\n", NewType.TypeID); + return; + } + + ObjectType ObjType = Obj.getObjectType(); + if(ObjType.getFlag(CUMULATIVE)){ + uint32 Amount = Obj.getAttribute(AMOUNT); + if(Amount > 1){ + Move(0, Obj, Con, Amount - 1, true, NONE); + } + } + + Change(Obj, NewType, Value); +} + int GetObjectPriority(Object Obj){ if(!Obj.exists()){ error("GetObjectPriority: Übergebenes Objekt existiert nicht.\n"); @@ -1439,6 +1501,46 @@ void PlaceObject(Object Obj, Object Con, bool Append){ Obj.setContainer(Con); } +// NOTE(fusion): This is the opposite of `PlaceObject`. +void CutObject(Object Obj){ + if(!Obj.exists()){ + error("CutObject: Übergebenes Objekt existiert nicht.\n"); + return; + } + + Object Con = Obj.getContainer(); + Object Cur = GetFirstContainerObject(Con); + if(Cur == Obj){ + Object Next = Obj.getNextObject(); + Con.setAttribute(CONTENT, Next.ObjectID); + }else{ + Object Prev; + do{ + Prev = Cur; + Cur = Cur.getNextObject(); + }while(Cur != Obj); + Prev.setNextObject(Cur.getNextObject()); + } + + Obj.setNextObject(NONE); + Obj.setContainer(NONE); +} + +void MoveObject(Object Obj, Object Con){ + if(!Obj.exists()){ + error("MoveObject: Übergebenes Objekt existiert nicht.\n"); + return; + } + + if(!Con.exists()){ + error("MoveObject: Übergebener Zielcontainer existiert nicht.\n"); + return; + } + + CutObject(Obj); + PlaceObject(Obj, Con, false); +} + Object AppendObject(Object Con, ObjectType Type){ if(!Con.exists()){ error("AppendObject: Übergebener Container existiert nicht.\n"); @@ -1451,6 +1553,143 @@ Object AppendObject(Object Con, ObjectType Type){ return Obj; } +Object SetObject(Object Con, ObjectType Type, uint32 CreatureID){ + if(!Con.exists()){ + error("SetObject: Übergebener Container existiert nicht.\n"); + return NONE; + } + + Object Obj = CreateObject(); + ChangeObject(Obj, Type); + PlaceObject(Obj, Con, false); + if(Type.isCreatureContainer()){ + if(CreatureID == 0){ + error("SetObject: Ungültige Kreatur-ID.\n"); + } + AccessObject(Obj)->Attributes[1] = CreatureID; + } + return Obj; +} + +Object CopyObject(Object Con, Object Source){ + if(!Source.exists()){ + error("CopyObject: Vorlage existiert nicht.\n"); + return NONE; + } + + if(!Con.exists()){ + error("CopyObject: Zielcontainer existiert nicht.\n"); + return NONE; + } + + ObjectType SourceType = Source.getObjectType(); + if(SourceType.isCreatureContainer()){ + error("CopyObject: Kreaturen dürfen nicht kopiert werden.\n"); + return NONE; + } + + Object NewObj = SetObject(Con, SourceType, 0); + for(int i = 0; i < NARRAY(TObject::Attributes); i += 1){ + AccessObject(NewObj)->Attributes[i] = AccessObject(Source)->Attributes[i]; + } + + if(SourceType.getFlag(CONTAINER) || SourceType.getFlag(CHEST)){ + NewObj.setAttribute(CONTENT, NONE.ObjectID); + } + + if(SourceType.getFlag(TEXT)){ + // NOTE(fusion): Both `NewObj` and `Source` share the same strings. We + // need to duplicate them so both objects can "own" and manage their own + // strings separately. + uint32 TextString = NewObj.getAttribute(TEXTSTRING); + if(TextString != 0){ + TextString = AddDynamicString(GetDynamicString(TextString)); + NewObj.setAttribute(TEXTSTRING, TextString); + } + + uint32 Editor = NewObj.getAttribute(EDITOR); + if(Editor != 0){ + Editor = AddDynamicString(GetDynamicString(TextString)); + NewObj.setAttribute(EDITOR, Editor); + } + } + + return NewObj; +} + +Object SplitObject(Object Obj, int Count){ + if(!Obj.exists()){ + error("SplitObject: Übergebenes Objekt existiert nicht.\n"); + return NONE; + } + + ObjectType ObjType = Obj.getObjectType(); + if(!ObjType.getFlag(CUMULATIVE)){ + error("SplitObject: Objekt ist nicht kumulierbar.\n"); + return Obj; // TODO(fusion): Probably return NONE? + } + + uint32 Amount = Obj.getAttribute(AMOUNT); + if(Count <= 0 || (uint32)Count > Amount){ + error("SplitObject: Ungültiger Zähler %d.\n", Count); + return Obj; // TODO(fusion): Probably return NONE? + } + + Object Res = Obj; + if((uint32)Count != Amount){ + Res = CopyObject(Obj.getContainer(), Obj); + Res.setAttribute(AMOUNT, (uint32)Count); + Obj.setAttribute(AMOUNT, Amount - (uint32)Count); + } + return Res; +} + +void MergeObjects(Object Obj, Object Dest){ + if(!Obj.exists()){ + error("MergeObjects: Übergebenes Objekt existiert nicht.\n"); + return; + } + + if(!Dest.exists()){ + error("MergeObjects: Übergebenes Ziel existiert nicht.\n"); + return; + } + + ObjectType ObjType = Obj.getObjectType(); + ObjectType DestType = Dest.getObjectType(); + if(ObjType != DestType){ + error("MergeObjects: Objekttypen %d und %d sind nicht identisch.\n", + ObjType.TypeID, DestType.TypeID); + return; + } + + if(!ObjType.getFlag(CUMULATIVE)){ + error("MergeObjects: Objekttyp %d ist nicht kumulierbar.\n", ObjType.TypeID); + return; + } + + uint32 ObjAmount = Obj.getAttribute(AMOUNT); + if(ObjAmount == 0){ + error("MergeObjects: Objekt enthält 0 Teile.\n"); + ObjAmount = 1; + } + + uint32 DestAmount = Dest.getAttribute(AMOUNT); + if(DestAmount == 0){ + error("MergeObjects: Zielobjekt enthält 0 Teile.\n"); + DestAmount = 1; + } + + DestAmount += ObjAmount; + if(DestAmount > 100){ + error("MergeObjects: Neues Objekt enthält mehr als 100 Teile.\n"); + DestAmount = 100; + } + + Dest.setAttribute(AMOUNT, DestAmount); + DeleteObject(Obj); +} + Object GetFirstContainerObject(Object Con){ if(Con == NONE){ error("GetFirstContainerObject: Übergebener Container existiert nicht.\n"); @@ -1478,53 +1717,14 @@ Object GetContainerObject(Object Con, int Index){ } Object Obj = GetFirstContainerObject(Con); - while(Obj != NONE && Index > 0){ + while(Index > 0 && Obj != NONE){ + Index -= 1; Obj = Obj.getNextObject(); } return Obj; } -void GetObjectCoordinates(Object Obj, int *x, int *y, int *z){ - if(!Obj.exists()){ - error("GetObjectCoordinates: Übergebenes Objekt existiert nicht.\n"); - *x = 0; - *y = 0; - *z = 0; - return; - } - - // TODO(fusion): I'm not sure I like this approach with calling `AccessObject` - // multiple times for the same object. Furthermore, using `Object::getObjectType` - // (at least until now) is very weird when we could just get the same information - // from the `TObject` which we'll access ANYWAYS. - - while(true){ - if(Obj.getObjectType().isMapContainer()) - break; - Obj = Obj.getContainer(); - } - - *x = AccessObject(Obj)->Attributes[1]; - *y = AccessObject(Obj)->Attributes[2]; - - // NOTE(fusion): The first 8 bits of `Attributes[3]` holds the Z coordinate - // of a map container. The next 8 bits holds the flags of a map container. - *z = AccessObject(Obj)->Attributes[3] & 0xFF; - - return; -} - -uint8 GetMapContainerFlags(Object Obj){ - if(!Obj.exists() || !Obj.getObjectType().isMapContainer()){ - error("GetMapContainerFlags: Objekt ist kein MapContainer.\n"); - return 0; - } - - // NOTE(fusion): See note in `GetObjectCoordinates` just above. - return (uint8)(AccessObject(Obj)->Attributes[3] >> 8); -} - Object GetMapContainer(int x, int y, int z){ int SectorX = x / 32; int SectorY = y / 32; @@ -1542,7 +1742,6 @@ Object GetMapContainer(int x, int y, int z){ return NONE; } - int OffsetX = x % 32; int OffsetY = y % 32; ConSector->TimeStamp = RoundNr; @@ -1555,7 +1754,7 @@ Object GetMapContainer(Object Obj){ return NONE; } - while(true){ + while(Obj != NONE){ if(Obj.getObjectType().isMapContainer()) break; Obj = Obj.getContainer(); @@ -1572,3 +1771,42 @@ Object GetFirstObject(int x, int y, int z){ return NONE; } } + +uint8 GetMapContainerFlags(Object Obj){ + if(!Obj.exists() || !Obj.getObjectType().isMapContainer()){ + error("GetMapContainerFlags: Objekt ist kein MapContainer.\n"); + return 0; + } + + // NOTE(fusion): See note in `GetObjectCoordinates`. + return (uint8)(AccessObject(Obj)->Attributes[3] >> 8); +} + +void GetObjectCoordinates(Object Obj, int *x, int *y, int *z){ + if(!Obj.exists()){ + error("GetObjectCoordinates: Übergebenes Objekt existiert nicht.\n"); + *x = 0; + *y = 0; + *z = 0; + return; + } + + // TODO(fusion): I'm not sure I like this approach with calling `AccessObject` + // multiple times for the same object. Furthermore, using `Object::getObjectType` + // (at least until now) is very weird when we could just get the same information + // from the `TObject` which we'll access ANYWAYS. + + while(true){ + if(Obj.getObjectType().isMapContainer()) + break; + Obj = Obj.getContainer(); + } + + *x = AccessObject(Obj)->Attributes[1]; + *y = AccessObject(Obj)->Attributes[2]; + + // NOTE(fusion): The first 8 bits of `Attributes[3]` holds the Z coordinate + // of a map container. The next 8 bits holds its flags and the last 16 bits + // holds its house id. + *z = AccessObject(Obj)->Attributes[3] & 0xFF; +} diff --git a/src/map.hh b/src/map.hh index 3c92fff..ac58b98 100644 --- a/src/map.hh +++ b/src/map.hh @@ -5,37 +5,55 @@ #include "objects.hh" #include "script.hh" +// NOTE(fusion): This is used by hash table entries and sectors to tell whether +// they're currently loaded or swapped out to disk. +enum : uint8 { + STATUS_FREE = 0, + STATUS_LOADED = 1, + STATUS_SWAPPED = 2, + + // TODO(fusion): It seems this is only used with the `NONE` entry in the + // hash table. I haven't seen it used **yet** but It may have a purpose + // aside from preventing swap outs. + STATUS_PERMANENT = 255, +}; + +// NOTE(fusion): This is used to determine precedence order of different objects +// in a map container. +enum : int { + PRIORITY_BANK = 0, + PRIORITY_CLIP = 1, + PRIORITY_BOTTOM = 2, + PRIORITY_TOP = 3, + PRIORITY_CREATURE = 4, + PRIORITY_OTHER = 5, +}; + struct Object { // REGULAR FUNCTIONS // ========================================================================= 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); void setObjectType(ObjectType Type); - Object getNextObject(void); void setNextObject(Object NextObject); - Object getContainer(void); void setContainer(Object Con); - uint32 getCreatureID(void); - //void setCreatureID(uint32 CreatureID); //?? - uint32 getAttribute(INSTANCEATTRIBUTE Attribute); void setAttribute(INSTANCEATTRIBUTE Attribute, uint32 Value); + constexpr bool operator==(const Object &Other) const { + return this->ObjectID == Other.ObjectID; + } + + constexpr bool operator!=(const Object &Other) const { + return this->ObjectID != Other.ObjectID; + } + // DATA // ========================================================================= uint32 ObjectID; @@ -95,22 +113,48 @@ 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); +//RefreshSector +//PatchSector void InitMap(void); void ExitMap(bool Save); // NOTE(fusion): Object related functions. -Object CreateObject(void); TObject *AccessObject(Object Obj); +Object CreateObject(void); +void DestroyObject(Object Obj); +void DeleteObject(Object Obj); void ChangeObject(Object Obj, ObjectType NewType); +void ChangeObject(Object Obj, INSTANCEATTRIBUTE Attribute, uint32 Value); +void ChangeObject(Object Obj, ObjectType NewType, uint32 Value); int GetObjectPriority(Object Obj); void PlaceObject(Object Obj, Object Con, bool Append); +void CutObject(Object Obj); +void MoveObject(Object Obj, Object Con); Object AppendObject(Object Con, ObjectType Type); +Object SetObject(Object Con, ObjectType Type, uint32 CreatureID); +Object CopyObject(Object Con, Object Source); +Object SplitObject(Object Obj, int Count); +void MergeObjects(Object Obj, Object Dest); 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); +//GetFirstSpecObject +uint8 GetMapContainerFlags(Object Obj); +//CoordinateFlag +void GetObjectCoordinates(Object Obj, int *x, int *y, int *z); +//IsOnMap +//IsPremiumArea +//IsProtectionZone +//IsNoLogoutField +//IsHouse +//GetHouseID +//SetHouseID +//GetDepotNumber +//GetDepotName +//GetDepotSize +//GetStartPosition +//GetMarkPosition #endif //TIBIA_MAP_HH_ diff --git a/src/objects.hh b/src/objects.hh index 61a0185..eec0565 100644 --- a/src/objects.hh +++ b/src/objects.hh @@ -52,6 +52,14 @@ struct ObjectType { return this->TypeID == TYPEID_CREATURE_CONTAINER; } + bool operator==(const ObjectType &Other) const { + return this->TypeID == Other.TypeID; + } + + bool operator!=(const ObjectType &Other) const { + return this->TypeID != Other.TypeID; + } + // DATA // ========================================================================= int TypeID; diff --git a/src/stubs.hh b/src/stubs.hh index bc89d12..0926a52 100644 --- a/src/stubs.hh +++ b/src/stubs.hh @@ -18,6 +18,7 @@ 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 void Change(Object Obj, ObjectType NewType, uint32 Value); extern bool CheckRight(uint32 CreatureID, RIGHT Right); extern void CleanupDynamicStrings(void); extern void CreatePlayerList(bool Online); @@ -33,6 +34,7 @@ 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 Move(uint32 CreatureID, Object Obj, Object Con, int Count, bool NoMerge, Object Ignore); extern void MoveCreatures(int Delay); extern void NetLoadCheck(void); extern void NetLoadSummary(void); -- cgit v1.2.3