diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/connection.hh | 2 | ||||
| -rw-r--r-- | src/containers.hh | 37 | ||||
| -rw-r--r-- | src/cr.hh | 3 | ||||
| -rw-r--r-- | src/crmain.cc | 23 | ||||
| -rw-r--r-- | src/crplayer.cc | 1 | ||||
| -rw-r--r-- | src/info.hh | 1 | ||||
| -rw-r--r-- | src/main.cc | 3 | ||||
| -rw-r--r-- | src/objects.hh | 11 | ||||
| -rw-r--r-- | src/operate.cc | 1025 | ||||
| -rw-r--r-- | src/operate.hh | 96 | ||||
| -rw-r--r-- | src/stubs.hh | 38 |
11 files changed, 1208 insertions, 32 deletions
diff --git a/src/connection.hh b/src/connection.hh index ec79a6e..44b7360 100644 --- a/src/connection.hh +++ b/src/connection.hh @@ -5,6 +5,7 @@ #include "enums.hh" struct TConnection; +struct TPlayer; struct TKnownCreature { KNOWNCREATURESTATE State; @@ -14,6 +15,7 @@ struct TKnownCreature { }; struct TConnection { + TPlayer *GetPlayer(void); void Logout(int Delay, bool StopFight); bool IsVisible(int x, int y, int z); diff --git a/src/containers.hh b/src/containers.hh index 85dcfde..f82159b 100644 --- a/src/containers.hh +++ b/src/containers.hh @@ -118,6 +118,14 @@ struct vector{ return &this->entry[index - this->start]; } + T copyAt(int index) const { + T Result = {}; + if(index >= this->start && index < (this->start + this->space)){ + Result = this->entry[index - this->start]; + } + return Result; + } + // DATA // ================= int min; @@ -466,7 +474,7 @@ struct fifo{ // TODO(fusion): We don't consider integer overflow at all. this->Head += 1; - return this->Entry[this->Head % this->Size]; + return &this->Entry[this->Head % this->Size]; } void remove(void){ @@ -478,6 +486,33 @@ struct fifo{ this->Tail += 1; } + int iterFirst(void){ + return this->Head; + } + + int iterLast(void){ + return this->Tail; + } + + T *iterNext(int *Position){ + if(*Position < this->Tail || this->Head < *Position){ + return NULL; + } + T *Result = &this->Entry[*Position % this->Size]; + *Position -= 1; + return Result; + } + + T *iterPrev(int *Position){ + if(*Position < this->Tail || this->Head < *Position){ + return NULL; + } + + T *Result = &this->Entry[*Position % this->Size]; + *Position += 1; + return Result; + } + // TODO(fusion): There is also a `fifoIterator` used a few times and it is // essentially iterating from `this->Head` towards `this->Tail`. All its // functions were inlined so I'm not sure it is needed. @@ -718,6 +718,9 @@ struct TPlayer: TCreature { void SetOpenContainer(int ContainerNr, Object Con); void RejectTrade(void); Object InspectTrade(bool OwnOffer, int Position); + int CheckForMuting(void); + int RecordTalk(void); + int RecordMessage(uint32 Addressee); // VIRTUAL FUNCTIONS // ================= diff --git a/src/crmain.cc b/src/crmain.cc index 7e0ab42..c6a8b74 100644 --- a/src/crmain.cc +++ b/src/crmain.cc @@ -719,9 +719,13 @@ int TCreature::Damage(TCreature *Attacker, int Damage, int DamageType){ GraphicalEffect(this->CrObject, HitEffect); TextualEffect(this->CrObject, TextColor, "%d", Damage); if(SplashLiquid != LIQUID_NONE){ - CreatePool(GetMapContainer(this->CrObject), - GetSpecialObject(BLOOD_SPLASH), - SplashLiquid); + try{ + CreatePool(GetMapContainer(this->CrObject), + GetSpecialObject(BLOOD_SPLASH), + SplashLiquid); + }catch(RESULT r){ + // TODO(fusion): Ignore? + } } } @@ -754,10 +758,15 @@ int TCreature::Damage(TCreature *Attacker, int Damage, int DamageType){ if(ObjType == AmuletOfLossType){ Log("game", "%s stirbt mit Amulett of Loss.\n", this->Name); this->LoseInventory = 0; - Delete(Obj, -1); - // TODO(fusion): Shouldn't we break here? We could also - // just check if there is an amulet of loss in the necklace - // container instead of iterating over all of them. + try{ + Delete(Obj, -1); + // TODO(fusion): Shouldn't we break here? We could also + // just check if there is an amulet of loss in the necklace + // container instead of iterating over all inventory. + }catch(RESULT r){ + error("TCreature::Damage: Exception %d beim Löschen von Objekt %d.\n", + r, AmuletOfLossType.TypeID); + } } } } diff --git a/src/crplayer.cc b/src/crplayer.cc index 5b87ec8..8d962fb 100644 --- a/src/crplayer.cc +++ b/src/crplayer.cc @@ -1,4 +1,5 @@ #include "cr.hh" +#include "info.hh" #include "stubs.hh" diff --git a/src/info.hh b/src/info.hh index 863c500..8972276 100644 --- a/src/info.hh +++ b/src/info.hh @@ -52,6 +52,7 @@ bool ThrowPossible(int OrigX, int OrigY, int OrigZ, int DestX, int DestY, int DestZ, int Power); void GetCreatureLight(uint32 CreatureID, int *Brightness, int *Color); int GetInventoryWeight(uint32 CreatureID); +bool CheckRight(uint32 CharacterID, RIGHT Right); bool CheckBanishmentRight(uint32 CharacterID, int Reason, int Action); const char *GetBanishmentReason(int Reason); void InitInfo(void); diff --git a/src/main.cc b/src/main.cc index bbdebf1..7a804e0 100644 --- a/src/main.cc +++ b/src/main.cc @@ -4,6 +4,7 @@ #include "map.hh" #include "magic.hh" #include "objects.hh" +#include "operate.hh" #include "stubs.hh" @@ -238,7 +239,7 @@ static void InitAll(void){ InitCr(); //InitHouses(); InitTime(); - //ApplyPatches(); + ApplyPatches(); }catch(const char *str){ error("Initialisierungsfehler: %s\n", str); exit(EXIT_FAILURE); diff --git a/src/objects.hh b/src/objects.hh index 3d312ac..05b094d 100644 --- a/src/objects.hh +++ b/src/objects.hh @@ -62,6 +62,17 @@ struct ObjectType { || this->getFlag(WAND); } + bool isCloseWeapon(void){ + if(!this->getFlag(WEAPON)){ + return false; + } + + int WeaponType = this->getAttribute(WEAPONTYPE); + return WeaponType == WEAPON_SWORD + || WeaponType == WEAPON_CLUB + || WeaponType == WEAPON_AXE; + } + ObjectType getDisguise(void){ if(this->getFlag(DISGUISE)){ return (int)this->getAttribute(DISGUISETARGET); diff --git a/src/operate.cc b/src/operate.cc index 1e1f9a6..2224ed4 100644 --- a/src/operate.cc +++ b/src/operate.cc @@ -4,6 +4,23 @@ #include "stubs.hh" +#include <dirent.h> +#include <time.h> + +static fifo<TStatement> Statements(1024); +static fifo<TListener> Listeners(1024); + +static vector<TChannel> Channel(0, 18, 10); +static int Channels; +static int CurrentChannelID; +static int CurrentSubscriberNumber; + +static vector<TParty> Party(0, 100, 50); +static int Parties; + +// World Operations +// ============================================================================= + // TODO(fusion): The radii parameters for `TFindCreatures` are commonly around // 16 and 14 for x and y respectively so there is probably some constant involved. // Also, since we're talking about radii, these values are quite large when you @@ -383,7 +400,9 @@ void CheckTopMultiuseObject(uint32 CreatureID, Object Obj){ Best = Help; } - if(HelpType.getFlag(FORCEUSE) || GetObjectPriority(Help) >= PRIORITY_CREATURE){ + if(HelpType.getFlag(FORCEUSE) + || GetObjectPriority(Help) == PRIORITY_CREATURE + || GetObjectPriority(Help) == PRIORITY_LOW){ break; } @@ -2145,6 +2164,14 @@ void Look(uint32 CreatureID, Object Obj){ } void Talk(uint32 CreatureID, int Mode, const char *Addressee, const char *Text, bool CheckSpamming){ + // TODO(fusion): `Text` was originally `char*`, but I wanted to improve + // string handling overall and modifying string parameters is a bad idea, + // specially when you don't know their capacity. + // It is only modified when a player uses `TALK_YELL` to make it upper + // case, which shouldn't be a problem, but if we plan to do any cleanup + // afterwards, we need to get this right from the beginning. + char YellBuffer[256]; + TCreature *Creature = GetCreature(CreatureID); if(Creature == NULL){ error("Talk: Übergebene Kreatur %d existiert nicht.\n", CreatureID); @@ -2217,11 +2244,9 @@ void Talk(uint32 CreatureID, int Mode, const char *Addressee, const char *Text, throw EXHAUSTED; } - // TODO(fusion): This would be valid if `Text` was `char*`, but I - // wanted to improve the quality of string handling and parameters - // across the board, and now we need some scratch buffer to actually - // modify the text. - strUpper(Text); + strcpy(YellBuffer, Text); + strUpper(YellBuffer); + Text = YellBuffer; } if(Mode == TALK_CHANNEL_CALL && Channel == 5){ @@ -2277,7 +2302,7 @@ void Talk(uint32 CreatureID, int Mode, const char *Addressee, const char *Text, } if(Mode == TALK_PRIVATE_MESSAGE){ - Muting = Player->RecordMessage(Receiver->ID); + int Muting = Player->RecordMessage(Receiver->ID); if(Muting > 0){ SendMessage(Player->Connection, TALK_FAILURE_MESSAGE, "You have addressed too many players. You are muted for %d second%s.", @@ -2320,7 +2345,7 @@ void Talk(uint32 CreatureID, int Mode, const char *Addressee, const char *Text, TFindCreatures Search(Radius, Radius, Creature->posx, Creature->posy, FIND_PLAYERS); while(true){ - uint32 SpectatorID = Search.findNext(); + uint32 SpectatorID = Search.getNext(); if(SpectatorID == 0){ break; } @@ -2413,8 +2438,8 @@ void Talk(uint32 CreatureID, int Mode, const char *Addressee, const char *Text, continue; } - // TODO(fusion): We should probably review this. What if both player - // guild names are empty? Is it checked elsewhere? + // TODO(fusion): We should probably review this. You'd assume creature + // should be a player when talking to any channel. if(Channel == 0 && (Creature->Type != PLAYER || strcmp(((TPlayer*)Creature)->Guild, Subscriber->Guild) != 0)){ continue; @@ -2449,3 +2474,983 @@ void Talk(uint32 CreatureID, int Mode, const char *Addressee, const char *Text, } } } + +void Use(uint32 CreatureID, Object Obj1, Object Obj2, uint8 Info){ + if(!Obj1.exists()){ + error("Use: Übergebenes Objekt Obj 1 existiert nicht.\n"); + throw ERROR; + } + + if(Obj2 != NONE && !Obj2.exists()){ + error("Use: Übergebenes Objekt Obj 2 existiert nicht.\n"); + throw ERROR; + } + + CheckTopUseObject(CreatureID, Obj1); + if(!ObjectAccessible(CreatureID, Obj1, 1)){ + throw NOTACCESSIBLE; + } + + ObjectType ObjType1 = Obj1.getObjectType(); + if(Obj2 != NONE){ + // TODO(fusion): Is this correct? + if(!ObjType1.getFlag(DISTUSE)){ + CheckTopMultiuseObject(CreatureID, Obj2); + } + + if(!ObjectAccessible(CreatureID, Obj2, 1)){ + if(!ObjType1.getFlag(DISTUSE)){ + throw NOTACCESSIBLE; + } + + TCreature *Creature = GetCreature(CreatureID); + if(Creature == NULL){ + error("Use: Verursachende Kreatur existiert nicht.\n"); + throw ERROR; + } + + int ObjX2, ObjY2, ObjZ2; + GetObjectCoordinates(Obj2, &ObjX2, &ObjY2, &ObjZ2); + if(Creature->posz > ObjZ2){ + throw UPSTAIRS; + }else if(Creature->posz < ObjZ2){ + throw DOWNSTAIRS; + } + + if(!ThrowPossible(Creature->posx, Creature->posy, Creature->posz, ObjX2, ObjY2, ObjZ2, 0)){ + throw CANNOTTHROW; + } + } + } + + if(ObjType1.getFlag(CONTAINER)){ + UseContainer(CreatureID, Obj1, Info); + }else if(ObjType1.getFlag(CHEST)){ + UseChest(CreatureID, Obj1); + }else if(ObjType1.getFlag(LIQUIDCONTAINER)){ + UseLiquidContainer(CreatureID, Obj1, Obj2); + }else if(ObjType1.getFlag(FOOD)){ + UseFood(CreatureID, Obj1); + }else if(ObjType1.getFlag(WRITE) || ObjType1.getFlag(WRITEONCE)){ + UseTextObject(CreatureID, Obj1); + }else if(ObjType1.getFlag(INFORMATION)){ + UseAnnouncer(CreatureID, Obj1); + }else if(ObjType1.getFlag(RUNE)){ + UseMagicItem(CreatureID, Obj1, Obj2); + }else if(ObjType1.getFlag(KEY)){ + UseKeyDoor(CreatureID, Obj1, Obj2); + }else if(ObjType1.getFlag(NAMEDOOR)){ + UseNameDoor(CreatureID, Obj1); + }else if(ObjType1.getFlag(LEVELDOOR)){ + UseLevelDoor(CreatureID, Obj1); + }else if(ObjType1.getFlag(QUESTDOOR)){ + UseQuestDoor(CreatureID, Obj1); + }else if(ObjType1.isCloseWeapon() + && Obj2 != NONE + && Obj2.getObjectType().getFlag(DESTROY)){ + UseWeapon(CreatureID, Obj1, Obj2); + }else if(ObjType1.getFlag(CHANGEUSE)){ + UseChangeObject(CreatureID, Obj1); + }else if(ObjType1.getFlag(USEEVENT)){ + if(Obj2 != NONE){ + UseObjects(CreatureID, Obj1, Obj2); + }else{ + UseObject(CreatureID, Obj1); + } + }else if(ObjType1.getFlag(TEXT) + && ObjType1.getAttribute(FONTSIZE) == 1){ + UseTextObject(CreatureID, Obj1); + }else{ + throw NOTUSABLE; + } +} + +void Turn(uint32 CreatureID, Object Obj){ + if(!Obj.exists()){ + error("Turn: Übergebenes Objekt existiert nicht.\n"); + throw ERROR; + } + + if(!ObjectAccessible(CreatureID, Obj, 1)){ + throw NOTACCESSIBLE; + } + + ObjectType ObjType = Obj.getObjectType(); + if(!ObjType.getFlag(ROTATE)){ + throw NOTTURNABLE; + } + + ObjectType RotateTarget = ObjType.getAttribute(ROTATETARGET); + if(RotateTarget == ObjType){ + error("Turn: Objekt %d wird durch Drehen zerstört.\n", ObjType.TypeID); + } + + Change(Obj, RotateTarget, 0); +} + +void CreatePool(Object Con, ObjectType Type, uint32 Value){ + if(!Con.exists()){ + error("CreatePool: Übergebener MapContainer existiert nicht.\n"); + throw ERROR; + } + + ObjectType ConType = Con.getObjectType(); + if(!ConType.isMapContainer()){ + error("CreatePool: Übergebenes Objekt ist kein MapContainer.\n"); + throw ERROR; + } + + // NOTE(fusion): There can be no liquid pools on fields with other BOTTOM + // objects, with the exception of other liquid pools, in which case the new + // liquid pool would replace the old one. + Object Help = GetFirstContainerObject(Con); + while(Help != NONE){ + Object Next = Help.getNextObject(); + ObjectType HelpType = Help.getObjectType(); + if(HelpType.getFlag(BOTTOM)){ + if(!HelpType.getFlag(LIQUIDPOOL)){ + throw NOROOM; + } + + try{ + Delete(Help, -1); + }catch(RESULT r){ + error("CreatePool: Exception %d beim Löschen des alten Pools.\n", r); + } + } + Help = Next; + } + + Create(Con, Type, Value); +} + +void EditText(uint32 CreatureID, Object Obj, const char *Text){ + if(!Obj.exists()){ + error("EditText: Übergebenes Objekt existiert nicht.\n"); + throw ERROR; + } + + ObjectType ObjType = Obj.getObjectType(); + if(!ObjType.getFlag(WRITE) && (!ObjType.getFlag(WRITEONCE) || Obj.getAttribute(TEXTSTRING) != 0)){ + error("EditText: Objekt ist nicht beschreibbar.\n"); + throw ERROR; + } + + TCreature *Creature = GetCreature(CreatureID); + if(Creature == NULL){ + error("EditText: Kreatur existiert nicht.\n"); + throw ERROR; + } + + int TextLength = (int)strlen(Text); + int MaxLength = (ObjType.getFlag(WRITE) + ? ObjType.getAttribute(MAXLENGTH) + : ObjType.getAttribute(MAXLENGTHONCE)); + if(TextLength >= MaxLength){ + throw TOOLONG; + } + + // TODO(fusion): Similar to maybe inlined function in `Look`. + // TODO(fusion): Same as in `Look`. `CreatureID` can't be zero here or we'd + // have already returned so its probably some inlined function. + if(CreatureID != 0 && !ObjectAccessible(CreatureID, Obj, 1)){ + throw NOTACCESSIBLE; + } + + uint32 ObjText = Obj.getAttribute(TEXTSTRING); + if((ObjText != 0 && strcmp(GetDynamicString(ObjText), Text) == 0) + || (ObjText == 0 && Text[0] == 0)){ + print(3, "Text hat sich nicht geändert.\n"); + return; + } + + DeleteDynamicString(ObjText); + Change(Obj, TEXTSTRING, AddDynamicString(Text)); + + DeleteDynamicString(Obj.getAttribute(EDITOR)); + Change(Obj, EDITOR, AddDynamicString(Creature->Name)); +} + +Object CreateAtCreature(uint32 CreatureID, ObjectType Type, uint32 Value){ + // TODO(fusion): Bruhh... + + TCreature *Creature = GetCreature(CreatureID); + if(Creature == NULL){ + // TODO(fusion): Different function name? + error("GiveObjectToCreature: Kann Kreatur nicht finden.\n"); + throw ERROR; + } + + try{ + CheckWeight(CreatureID, Type, Value, 0); + }catch(RESULT r){ + return Create(GetMapContainer(Creature->CrObject), Type, Value); + } + + Object Obj = NONE; + bool CheckContainers = Type.getFlag(MOVEMENTEVENT); + for(int i = 0; i < 2; i += 1){ + bool TooHeavy = false; + for(int Position = INVENTORY_FIRST; + Position <= INVENTORY_LAST; + Position += 1){ + try{ + Object BodyObj = GetBodyObject(CreatureID, Position); + if(CheckContainers){ + if(BodyObj != NONE && BodyObj.getObjectType().getFlag(CONTAINER)){ + Obj = Create(BodyObj, Type, Value); + break; + } + }else{ + if(BodyObj == NONE){ + Obj = Create(GetBodyContainer(CreatureID, Position), Type, Value); + break; + } + } + }catch(RESULT r){ + // TODO(fusion): Is this even possible if we're checking the + // weight before hand? + if(r == TOOHEAVY){ + TooHeavy = true; + break; + } + } + } + + if(Obj != NONE || TooHeavy){ + break; + } + + CheckContainers = !CheckContainers; + } + + if(Obj == NONE){ + Obj = Create(GetMapContainer(Creature->CrObject), Type, Value); + } + + return Obj; +} + +void DeleteAtCreature(uint32 CreatureID, ObjectType Type, int Amount, uint32 Value){ + while(Amount > 0){ + Object Obj = GetInventoryObject(CreatureID, Type, Value); + if(Obj == NONE){ + error("DeleteAtCreature: Kein Objekt gefunden.\n"); + throw ERROR; + } + + if(Type.getFlag(CUMULATIVE)){ + int ObjAmount = (int)Obj.getAttribute(AMOUNT); + if(ObjAmount < Amount){ + Delete(Obj, -1); + Amount -= ObjAmount; + }else{ + Change(Obj, AMOUNT, (ObjAmount - Amount)); + Amount = 0; + } + }else{ + Delete(Obj, -1); + Amount -= 1; + } + } +} + +void ProcessCronSystem(void){ + while(true){ + Object Obj = CronCheck(); + if(Obj == NONE){ + break; + } + + ObjectType ObjType = Obj.getObjectType(); + ObjectType ExpireTarget = ObjType.getAttribute(EXPIRETARGET); + if(ObjType.getFlag(CONTAINER)){ + int Remainder = 0; + if(!ExpireTarget.isMapContainer() && ExpireTarget.getFlag(CONTAINER)){ + Remainder = (int)ExpireTarget.getAttribute(CAPACITY); + } + + Empty(Obj, Remainder); + } + + Change(Obj, ExpireTarget, 0); + } +} + +bool SectorRefreshable(int SectorX, int SectorY, int SectorZ){ + // TODO(fusion): Have the sector size defined as a constant in `map.hh`? + //constexpr int SectorSize = 32; + int SearchRadiusX = 32 - 1; + int SearchRadiusY = 32 - 1; + int SearchCenterX = SectorX * 32 + (32 / 2); + int SearchCenterY = SectorY * 32 + (32 / 2); + TFindCreatures Search(SearchRadiusX, SearchRadiusY, SearchCenterX, SearchCenterY, FIND_PLAYERS); + while(true){ + uint32 CharacterID = Search.getNext(); + if(CharacterID == 0){ + break; + } + + TPlayer *Player = GetPlayer(CharacterID); + if(Player == NULL){ + error("SectorRefreshable: Kreatur existiert nicht.\n"); + continue; + } + + // TODO(fusion): Maybe an inlined function to check whether a player + // can see some floor. All floors above ground, when above ground. Or + // two floors up and down, when underground. + if(Player->posz <= 7){ + if(SectorZ <= 7){ + return false; + } + }else{ + if(std::abs(Player->posz - SectorZ) <= 2){ + return false; + } + } + } + + return true; +} + +void RefreshSector(int SectorX, int SectorY, int SectorZ, const uint8 *Data, int Count){ + if(!SectorRefreshable(SectorX, SectorY, SectorZ)){ + return; + } + + TReadBuffer Buffer(Data, Count); + RefreshSector(SectorX, SectorY, SectorZ, &Buffer); + + // TODO(fusion): This function is very similar to `ApplyPatch`. + int SearchRadiusX = 32 / 2; + int SearchRadiusY = 32 / 2; + int SearchCenterX = SectorX * 32 + (32 / 2); + int SearchCenterY = SectorY * 32 + (32 / 2); + TFindCreatures Search(SearchRadiusX, SearchRadiusY, SearchCenterX, SearchCenterY, FIND_ALL); + while(true){ + uint32 CreatureID = Search.getNext(); + if(CreatureID == 0){ + break; + } + + TCreature *Creature = GetCreature(CreatureID); + if(Creature == NULL){ + error("RefreshSector: Kreatur existiert nicht.\n"); + continue; + } + + if(Creature->posz != SectorZ){ + continue; + } + + bool FieldBlocked = false; + int FieldX = Creature->posx; + int FieldY = Creature->posy; + int FieldZ = Creature->posz; + Object Obj = GetFirstObject(FieldX, FieldY, FieldZ); + while(Obj != NONE){ + ObjectType ObjType = Obj.getObjectType(); + if(!ObjType.isCreatureContainer() && ObjType.getFlag(UNPASS)){ + FieldBlocked = true; + break; + } + Obj = Obj.getNextObject(); + } + + if(FieldBlocked){ + try{ + if(!SearchFreeField(&FieldX, &FieldY, &FieldZ, 1, 0, false)){ + if(Creature->Type == NPC){ + print(2, "NPC auf [%d,%d,%d] muß auf seinen Start zurückgesetzt werden.\n", FieldX, FieldY, FieldZ); + FieldX = Creature->startx; + FieldY = Creature->starty; + FieldZ = Creature->startz; + }else if(Creature->Type == MONSTER){ + print(2, "Monster auf [%d,%d,%d] muß gelöscht werden.\n", FieldX, FieldY, FieldZ); + delete Creature; + continue; + }else{ + error("Spieler auf [%d,%d,%d] ist von Refresh betroffen.\n", FieldX, FieldY, FieldZ); + continue; + } + } + + Object MapCon = GetMapContainer(FieldX, FieldY, FieldZ); + Move(0, Creature->CrObject, MapCon, -1, false, NONE); + }catch(RESULT r){ + error("RefreshSector: Exception %d beim Neusetzen des Monsters.\n", r); + } + } + } +} + +void RefreshMap(void){ + TDynamicWriteBuffer HelpBuffer(0x10000); + for(int SectorZ = SectorZMin; SectorZ <= SectorZMax; SectorZ += 1) + for(int SectorY = SectorYMin; SectorY <= SectorYMax; SectorY += 1) + for(int SectorX = SectorXMin; SectorX <= SectorXMax; SectorX += 1){ + if(!SectorRefreshable(SectorX, SectorY, SectorZ)){ + continue; + } + + char FileName[4096]; + snprintf(FileName, sizeof(FileName), "%s/%04u-%04u-%02u.sec", + ORIGMAPPATH, SectorX, SectorY, SectorZ); + if(!FileExists(FileName)){ + continue; + } + + bool Refreshable = false; + int OffsetX = -1; + int OffsetY = -1; + HelpBuffer.reset(); + try{ + TReadScriptFile Script; + Script.open(FileName); + while(true){ + Script.nextToken(); + if(Script.Token == ENDOFFILE){ + Script.close(); + break; + } + + if(Script.Token == SPECIAL && Script.getSpecial() == ','){ + continue; + } + + if(Script.Token == BYTES){ + uint8 *SectorOffset = Script.getBytesequence(); + OffsetX = (int)SectorOffset[0]; + OffsetY = (int)SectorOffset[1]; + Script.readSymbol(':'); + continue; + } + + // TODO(fusion): We don't enforce the token to be an identifier + // here as we do when loading any sector file in `map.cc`. + if(Script.Token == IDENTIFIER){ + if(OffsetX == -1 || OffsetY == -1){ + Script.error("coordinate expected"); + } + + const char *Identifier = Script.getIdentifier(); + if(strcmp(Identifier, "refresh") == 0){ + Refreshable = true; + }else if(strcmp(Identifier, "content") == 0){ + Script.readSymbol('='); + if(Refreshable){ + HelpBuffer.writeByte((uint8)OffsetX); + HelpBuffer.writeByte((uint8)OffsetY); + } + LoadObjects(&Script, &HelpBuffer, !Refreshable); + } + } + } + + if(HelpBuffer.Position > 0){ + TReadBuffer ReadBuffer(HelpBuffer.Data, HelpBuffer.Position); + RefreshSector(SectorX, SectorY, SectorZ, &ReadBuffer); + } + }catch(const char *str){ + error("RefreshMap: Fehler beim Bearbeiten der Datei (%s).\n", str); + } + } +} + +void RefreshCylinders(void){ + // TODO(fusion): `RefreshedCylinders` is the number of cylinders we attempt to + // refresh in this function, which is called every minute or so by `AdvanceGame`. + // We should probably rename it to something more clear. + + // TODO(fusion): This might be on purpose but `RefreshX` and `RefreshY` will be + // way below `SectorXMin` and `SectorYMin` the first N times this function is + // called and depending on the value of `RefreshedCylinders`, could take a few + // hours before we even start refreshing the map. + // If the intent is to delay refreshing cylinders until some time after startup, + // we should change how this function is called by `AdvanceGame` instead. + + static int RefreshX = -1; + static int RefreshY = -1; + for(int i = 0; i < RefreshedCylinders; i += 1){ + if(RefreshX < SectorXMax){ + RefreshX += 1; + }else{ + RefreshX = SectorXMin; + if(RefreshY < SectorYMax){ + RefreshY += 1; + }else{ + RefreshY = SectorYMin; + } + } + + for(int RefreshZ = SectorZMin; + RefreshZ <= SectorZMax; + RefreshZ += 1){ + if(SectorRefreshable(RefreshX, RefreshY, RefreshZ)){ + LoadSectorOrder(RefreshX, RefreshY, RefreshZ); + } + } + } +} + +void ApplyPatch(int SectorX, int SectorY, int SectorZ, + bool FullSector, TReadScriptFile *Script, bool SaveHouses){ + if(SectorX < SectorXMin || SectorX > SectorXMax + || SectorY < SectorYMin || SectorY > SectorYMax + || SectorZ < SectorZMin || SectorZ > SectorZMax){ + return; + } + + PrepareHouseCleanup(); + PatchSector(SectorX, SectorY, SectorZ, FullSector, Script, SaveHouses); + FinishHouseCleanup(); + + // TODO(fusion): Similar to `SectorRefreshable` but with a reduced radius. + int SearchRadiusX = 32 / 2; + int SearchRadiusY = 32 / 2; + int SearchCenterX = SectorX * 32 + (32 / 2); + int SearchCenterY = SectorY * 32 + (32 / 2); + TFindCreatures Search(SearchRadiusX, SearchRadiusY, SearchCenterX, SearchCenterY, FIND_ALL); + while(true){ + uint32 CreatureID = Search.getNext(); + if(CreatureID == 0){ + break; + } + + TCreature *Creature = GetCreature(CreatureID); + if(Creature == NULL){ + error("ApplyPatch: Kreatur existiert nicht.\n"); + continue; + } + + if(Creature->posz != SectorZ){ + continue; + } + + bool FieldBlocked = false; + int FieldX = Creature->posx; + int FieldY = Creature->posy; + int FieldZ = Creature->posz; + Object Obj = GetFirstObject(FieldX, FieldY, FieldZ); + while(Obj != NONE){ + ObjectType ObjType = Obj.getObjectType(); + if(!ObjType.isCreatureContainer() && ObjType.getFlag(UNPASS)){ + FieldBlocked = true; + break; + } + Obj = Obj.getNextObject(); + } + + if(!FieldBlocked){ + // TODO(fusion): Not sure why we're moving the creature to the same + // field, specially with `MoveObject`. Does `PatchSector` leave + // creatures on invalid indices? + Object MapCon = GetMapContainer(FieldX, FieldY, FieldZ); + MoveObject(Creature->CrObject, MapCon); + }else{ + try{ + if(!SearchFreeField(&FieldX, &FieldY, &FieldZ, 1, 0, false)){ + if(Creature->Type == NPC){ + print(2, "NPC auf [%d,%d,%d] muß auf seinen Start zurückgesetzt werden.\n", FieldX, FieldY, FieldZ); + FieldX = Creature->startx; + FieldY = Creature->starty; + FieldZ = Creature->startz; + }else if(Creature->Type == MONSTER){ + print(2, "Monster auf [%d,%d,%d] muß gelöscht werden.\n", FieldX, FieldY, FieldZ); + Delete(Creature->CrObject, -1); + Creature->StartLogout(false, false); + continue; + }else{ + error("Spieler auf [%d,%d,%d] ist von Patch betroffen.\n", FieldX, FieldY, FieldZ); + continue; + } + } + + Object MapCon = GetMapContainer(FieldX, FieldY, FieldZ); + Move(0, Creature->CrObject, MapCon, -1, false, NONE); + }catch(RESULT r){ + error("ApplyPatch: Exception %d beim Neusetzen des Monsters.\n", r); + } + } + } +} + +void ApplyPatches(void){ + // TODO(fusion): We don't handle any script exceptions in here which could + // lead to `PatchDir` being leaked. It may not be a problem overall because + // this function is only called at startup by `InitAll`. + + char FileName[4096]; + bool SaveHouses = false; + snprintf(FileName, sizeof(FileName), "%s/save-houses", SAVEPATH); + if(FileExists(FileName)){ + SaveHouses = true; + print(2, "Häuser werden nicht gepatcht.\n"); + unlink(FileName); + } + + DIR *PatchDir = opendir(SAVEPATH); + if(PatchDir == NULL){ + error("ApplyPatches: Unterverzeichnis %s nicht gefunden.\n", SAVEPATH); + return; + } + + int MaxPatch = -1; + while(dirent *DirEntry = readdir(PatchDir)){ + if(DirEntry->d_type != DT_REG){ + continue; + } + + const char *FileExt = findLast(DirEntry->d_name, '.'); + if(FileExt == NULL){ + continue; + } + + if(strcmp(FileExt, ".pat") == 0){ + int Patch; + if(sscanf(DirEntry->d_name, "%d.pat", &Patch) == 1){ + if(MaxPatch < Patch){ + MaxPatch = Patch; + } + } + }else if(strcmp(FileExt, ".sec") == 0){ + int SectorX, SectorY, SectorZ; + if(sscanf(DirEntry->d_name, "%d-%d-%d.sec", &SectorX, &SectorY, &SectorZ) == 3){ + print(2,"Patche kompletten Sektor %d/%d/%d ...\n", SectorX, SectorY, SectorZ); + snprintf(FileName, sizeof(FileName), "%s/%s", SAVEPATH, DirEntry->d_name); + + TReadScriptFile Script; + Script.open(FileName); + ApplyPatch(SectorX, SectorY, SectorZ, true, &Script, SaveHouses); + Script.close(); + unlink(FileName); + } + } + } + + closedir(PatchDir); + + for(int Patch = 0; Patch <= MaxPatch; Patch += 1){ + snprintf(FileName, sizeof(FileName), "%s/%03d.pat", SAVEPATH, Patch); + if(FileExists(FileName)){ + TReadScriptFile Script; + Script.open(FileName); + if(strcmp(Script.readIdentifier(), "sector") != 0){ + Script.error("Sector expected"); + } + + int SectorX = Script.readNumber(); + Script.readSymbol(','); + int SectorY = Script.readNumber(); + Script.readSymbol(','); + int SectorZ = Script.readNumber(); + + print(2, "Patche Teile von Sektor %d/%d/%d (Patch %d) ...\n", + SectorX, SectorY, SectorZ, Patch); + ApplyPatch(SectorX, SectorY, SectorZ, false, &Script, false); + Script.close(); + unlink(FileName); + } + } +} + +// Communication Logging +// ============================================================================= +uint32 LogCommunication(uint32 CreatureID, int Mode, int Channel, const char *Text){ + static uint32 StatementID = 0; + + if(Text == NULL){ + error("LogCommunication: Text ist NULL.\n"); + return 0; + } + + if(CreatureID == 0){ + error("LogCommunication: CharacterID ist Null.\n"); + return 0; + } + + StatementID += 1; + + TStatement *Statement = Statements.append(); + Statement->StatementID = StatementID; + Statement->TimeStamp = (uint32)time(NULL); + Statement->CharacterID = CreatureID; + Statement->Mode = Mode; + Statement->Channel = Channel; + Statement->Text = AddDynamicString(Text); + Statement->Reported = false; + return StatementID; +} + +uint32 LogListener(uint32 StatementID, TPlayer *Player){ + if(StatementID == 0 || Player == NULL + || !CheckRight(Player->ID, LOG_COMMUNICATION)){ + return 0; + } + + TListener *Listener = Listeners.append(); + Listener->StatementID = StatementID; + Listener->CharacterID = Player->ID; + return StatementID; +} + +void ProcessCommunicationControl(void){ + // NOTE(fusion): Remove statements older than 30 minutes. + int Now = (int)time(NULL); + uint32 Limit = 0; + while(true){ + TStatement *Statement = Statements.next(); + if(Statement == NULL || Now <= (Statement->TimeStamp + 1800)){ + if(Statement != NULL){ + Limit = Statement->StatementID; + } + break; + } + + DeleteDynamicString(Statement->Text); + Statements.remove(); + } + + // TODO(fusion): If there are no statements left we'll end up with `Limit` + // equal to zero which will prevent any listeners entry from being removed. + while(true){ + TListener *Listener = Listeners.next(); + if(Listener == NULL || Listener->StatementID >= Limit){ + break; + } + + Listeners.remove(); + } +} + +int GetCommunicationContext(uint32 CharacterID, uint32 StatementID, + int *NumberOfStatements, vector<TReportedStatement> **ReportedStatements){ + TStatement *Statement = NULL; + int StatementsIter = Statements.iterFirst(); + while(true){ + Statement = Statements.iterNext(&StatementsIter); + if(Statement == NULL || Statement->StatementID == StatementID){ + break; + } + } + + if(Statement == NULL){ + return 1; // STATEMENT_UNKNOWN ? + } + + if(CharacterID == 0){ + bool ChannelMode = Statement->Mode == TALK_CHANNEL_CALL + || Statement->Mode == TALK_GAMEMASTER_CHANNELCALL + || Statement->Mode == TALK_HIGHLIGHT_CHANNELCALL; + + bool ReportableChannel = Statement->Channel == 2 // CHANNEL_TUTOR + || Statement->Channel == 4 // CHANNEL_GAME + || Statement->Channel == 5 // CHANNEL_TRADE + || Statement->Channel == 6 // CHANNEL_RLCHAT + || Statement->Channel == 7; // CHANNEL_HELP + + if(!ChannelMode || !ReportableChannel){ + error("GetCommunicationContext: Äußerung dürfte nicht gemeldet werden.\n"); + return 1; // STATEMENT_UNKNOWN ? + } + } + + if(Statement->Reported){ + return 2; // STATEMENT_ALREADY_REPORTED ? + } + + // TODO(fusion): `FreeSpace` limits the amount of data that is gathered. It + // seems to grow/shrink with the size of the text plus an extra 46 bytes for + // each statement entry. This flat memory overhead of 46 bytes could be some + // compilation/decompilation artifact so I've left it out for now. + // The end point of this data is `ProcessPunishmentOrder` which may record + // statements into the database so it may be related to that. + + bool StatementContained = false; + int FreeSpace = KB(16); + *NumberOfStatements = 0; + *ReportedStatements = new vector<TReportedStatement>(0, 100, 100); + StatementsIter = Statements.iterLast(); + while(true){ + TStatement *Current = Statements.iterPrev(&StatementsIter); + if(Current == NULL){ + break; + } + + if(Current->TimeStamp < (Statement->TimeStamp - 180) + || Current->TimeStamp > (Statement->TimeStamp + 60)){ + continue; + } + + if(FreeSpace <= 0 && Current->TimeStamp > Statement->TimeStamp){ + error("GetCommunicationContext: Kontext wird zu groß. Schneide Ende ab.\n"); + break; + } + + if(CharacterID == 0){ + bool ChannelMode = Current->Mode == TALK_CHANNEL_CALL + || Current->Mode == TALK_GAMEMASTER_CHANNELCALL + || Current->Mode == TALK_HIGHLIGHT_CHANNELCALL; + if(!ChannelMode || Current->Channel != Statement->Channel){ + continue; + } + }else{ + // NOTE(fusion): Check if the character listened to the current statement. + TListener *Listener = NULL; + int ListenersIter = Listeners.iterLast(); + while(true){ + Listener = Listeners.iterPrev(&ListenersIter); + if(Listener == NULL + || Listener->StatementID > Current->StatementID + || (Listener->StatementID == Current->StatementID + && Listener->CharacterID == CharacterID)){ + break; + } + } + + if(Listener == NULL || Listener->StatementID != Current->StatementID){ + continue; + } + } + + TReportedStatement *Entry = (*ReportedStatements)->at(*NumberOfStatements); + Entry->StatementID = Current->StatementID; + Entry->TimeStamp = Current->TimeStamp; + Entry->CharacterID = Current->CharacterID; + Entry->Mode = Current->Mode; + Entry->Channel = Current->Channel; + if(Current->Text != 0){ + // TODO(fusion): OOF SIZE: Large. + strcpy(Entry->Text, GetDynamicString(Current->Text)); + }else{ + Entry->Text[0] = 0; + } + *NumberOfStatements += 1; + FreeSpace -= (int)strlen(Entry->Text); + + if(Current->StatementID == StatementID){ + StatementContained = true; + } + } + + if(FreeSpace < 0){ + error("GetCommunicationContext: Kontext wird um %d Bytes zu groß. Lösche Anfang.\n", -FreeSpace); + + for(int i = 0; i < *NumberOfStatements && FreeSpace < 0; i += 1){ + TReportedStatement *Entry = (*ReportedStatements)->at(i); + if(Entry->StatementID != StatementID){ + Entry->StatementID = 0; + FreeSpace += (int)strlen(Entry->Text); + } + } + + if(FreeSpace < 0){ + error("GetCommunicationContext: FreeSpace ist immer noch negativ (%d).\n", FreeSpace); + } + } + + if(!StatementContained){ + error("GetCommunicationContext: Statement ist nicht im Kontext enthalten.\n"); + delete *ReportedStatements; + *NumberOfStatements = 0; + *ReportedStatements = NULL; + return 1; // STATEMENT_UNKNOWN ? + } + + Statement->Reported = true; + return 0; // STATEMENT_REPORTED ? +} + +// Channel +// ============================================================================= +TChannel::TChannel(void) : Subscriber(0, 10, 10), InvitedPlayer(0, 10, 10) { + this->Moderator = 0; + this->ModeratorName[0] = 0; + this->Subscribers = 0; + this->InvitedPlayers = 0; +} + +TChannel::TChannel(const TChannel &Other) : TChannel() { + this->operator=(Other); +} + +void TChannel::operator=(const TChannel &Other){ + this->Subscribers = Other.Subscribers; + for(int i = 0; i < Other.Subscribers; i += 1){ + *this->Subscriber.at(i) = Other.Subscriber.copyAt(i); + } + + this->InvitedPlayers = Other.InvitedPlayers; + for(int i = 0; i < Other.InvitedPlayers; i += 1){ + *this->InvitedPlayer.at(i) = Other.InvitedPlayer.copyAt(i); + } +} + +bool ChannelActive(int ChannelID){ + if(ChannelID < 0 || ChannelID >= Channels){ + error("ChannelActive: Ungültige Kanalnummer %d.\n", ChannelID); + return false; + } + + return ChannelID <= 7 || Channel.at(ChannelID)->Moderator != 0; +} + +const char *GetChannelName(int ChannelID, uint32 CharacterID){ + static char ChannelName[40]; + + if(!ChannelActive(ChannelID)){ + return "Unknown"; + } + + TPlayer *Player = GetPlayer(CharacterID); + if(Player == NULL){ + error("GetChannelName: Spieler existiert nicht.\n"); + return "Unknown"; + } + + switch(ChannelID){ + case 0: return Player->Guild; + case 1: return "Gamemaster"; + case 2: return "Tutor"; + case 3: return "Rule Violations"; + case 4: return "Game-Chat"; + case 5: return "Trade"; + case 6: return "RL-Chat"; + case 7: return "Help"; + default:{ + if(ChannelID > 7){ + snprintf(ChannelName, sizeof(ChannelName), "%s\'s Channel", + Channel.at(ChannelID)->ModeratorName); + return ChannelName; + }else{ + error("GetChannelName: Unbenutzter Kanal %d.\n", ChannelID); + return "Unknown"; + } + } + } +} + +// Party +// ============================================================================= +TParty::TParty(void) : Member(0, 10, 10), InvitedPlayer(0, 10, 10) { + this->Leader = 0; + this->Members = 0; + this->InvitedPlayers = 0; +} + +TParty::TParty(const TParty &Other) : TParty() { + this->operator=(Other); +} + +void TParty::operator=(const TParty &Other){ + this->Members = Other.Members; + for(int i = 0; i < Other.Members; i += 1){ + *this->Member.at(i) = Other.Member.copyAt(i); + } + + this->InvitedPlayers = Other.InvitedPlayers; + for(int i = 0; i < Other.InvitedPlayers; i += 1){ + *this->InvitedPlayer.at(i) = Other.InvitedPlayer.copyAt(i); + } +} diff --git a/src/operate.hh b/src/operate.hh index 7d137fd..0c370a2 100644 --- a/src/operate.hh +++ b/src/operate.hh @@ -2,6 +2,8 @@ #define TIBIA_OPERATE_HH_ 1 #include "common.hh" +#include "containers.hh" +#include "cr.hh" #include "map.hh" enum : int { @@ -20,6 +22,74 @@ enum : int { OBJECT_MOVED = 3, }; +struct TChannel { + TChannel(void); + + // TODO(fusion): `TChannel` will primarily live inside the `Channel` vector, + // which needs to resize its internal array as needed. To resize, it needs + // to copy/swap elements, which would be impossible since `TChannel` itself + // owns a few vectors which were made NON-COPYABLE to prevent memory bugs. + // To fix this problem and allow `TChannel` to be copyable, we need to + // implement the copy constructor and assignment manually. They're annoying + // and expensive but should allow everything to compile again. + // This problem arises from the fact that our unconventional `vector` type + // has a weird interface but still manages its underlying memory. If we are + // to resolve this completely, we'd need to re-implement `vector` properly + // and preferably with move semantics (if we want to follow the C++ route, + // which we may not). + TChannel(const TChannel &Other); + void operator=(const TChannel &Other); + + // DATA + // ================= + uint32 Moderator; + char ModeratorName[30]; + vector<uint32> Subscriber; + int Subscribers; + vector<uint32> InvitedPlayer; + int InvitedPlayers; +}; + +struct TParty { + TParty(void); + + // TODO(fusion): Same as `TChannel`. + TParty(const TParty &Other); + void operator=(const TParty &Other); + + // DATA + // ================= + uint32 Leader; + vector<uint32> Member; + int Members; + vector<uint32> InvitedPlayer; + int InvitedPlayers; +}; + +struct TStatement { + uint32 StatementID; + int TimeStamp; + uint32 CharacterID; + int Mode; + int Channel; + uint32 Text; + bool Reported; +}; + +struct TListener { + uint32 StatementID; + uint32 CharacterID; +}; + +struct TReportedStatement { + uint32 StatementID; + int TimeStamp; + uint32 CharacterID; + int Mode; + int Channel; + char Text[256]; +}; + void AnnounceMovingCreature(uint32 CreatureID, Object Con); void AnnounceChangedCreature(uint32 CreatureID, int Type); void AnnounceChangedField(Object Obj, int Type); @@ -61,5 +131,31 @@ void GraphicalEffect(int x, int y, int z, int Type); void GraphicalEffect(Object Obj, int Type); void TextualEffect(Object Obj, int Color, const char *Format, ...) ATTR_PRINTF(3, 4); void Missile(Object Start, Object Dest, int Type); +void Look(uint32 CreatureID, Object Obj); +void Talk(uint32 CreatureID, int Mode, const char *Addressee, const char *Text, bool CheckSpamming); +void Use(uint32 CreatureID, Object Obj1, Object Obj2, uint8 Info); +void Turn(uint32 CreatureID, Object Obj); +void CreatePool(Object Con, ObjectType Type, uint32 Value); +void EditText(uint32 CreatureID, Object Obj, const char *Text); +Object CreateAtCreature(uint32 CreatureID, ObjectType Type, uint32 Value); +void DeleteAtCreature(uint32 CreatureID, ObjectType Type, int Amount, uint32 Value); + +void ProcessCronSystem(void); +bool SectorRefreshable(int SectorX, int SectorY, int SectorZ); +void RefreshSector(int SectorX, int SectorY, int SectorZ, const uint8 *Data, int Count); +void RefreshMap(void); +void RefreshCylinders(void); +void ApplyPatch(int SectorX, int SectorY, int SectorZ, + bool FullSector, TReadScriptFile *Script, bool SaveHouses); +void ApplyPatches(void); + +uint32 LogCommunication(uint32 CreatureID, int Mode, int Channel, const char *Text); +uint32 LogListener(uint32 StatementID, TPlayer *Player); +void ProcessCommunicationControl(void); +int GetCommunicationContext(uint32 CharacterID, uint32 StatementID, + int *NumberOfStatements, vector<TReportedStatement> **ReportedStatements); + +uint32 GetFirstSubscriber(int Channel); +uint32 GetNextSubscriber(void); #endif //TIBIA_OPERATE_HH_ diff --git a/src/stubs.hh b/src/stubs.hh index cb06608..df270ff 100644 --- a/src/stubs.hh +++ b/src/stubs.hh @@ -19,14 +19,11 @@ extern void BroadcastMessage(int Mode, const char *Text, ...) ATTR_PRINTF(2, 3); extern void ChangeNPCState(TCreature *Npc, int NewState, bool Stimulus); extern void CharacterDeathOrder(TCreature *Creature, int OldLevel, uint32 Offender, const char *Remark, bool Unjustified); -extern bool CheckRight(uint32 CreatureID, RIGHT Right); extern void CleanHouseField(int x, int y, int z); extern void ConvinceMonster(TCreature *Master, TCreature *Slave); extern void ChallengeMonster(TCreature *Challenger, TCreature *Monster); -extern Object CreateAtCreature(uint32 CreatureID, ObjectType Type, uint32 Value); extern TCreature *CreateMonster(int Race, int x, int y, int z, int Home, uint32 Master, bool ShowEffect); extern void CreatePlayerList(bool Online); -extern void CreatePool(Object Con, ObjectType Type, uint32 Value); extern void GetExitPosition(uint16 HouseID, int *x, int *y, int *z); extern TConnection *GetFirstConnection(void); extern TConnection *GetNextConnection(void); @@ -39,22 +36,18 @@ extern void InitLog(const char *ProtocolName); extern void KickGuest(uint16 HouseID, TPlayer *Host, TPlayer *Guest); extern void KillStatisticsOrder(int NumberOfRaces, const char *RaceNames, int *KilledPlayers, int *KilledCreatures); extern bool LagDetected(void); -extern void LoadMonsterRaid(const char *FileName, int Start, - bool *Type, int *Date, int *Interval, int *Duration); +extern void LoadSectorOrder(int SectorX, int SectorY, int SectorZ); extern void Log(const char *ProtocolName, const char *Text, ...) ATTR_PRINTF(2, 3); extern void LogoutAllPlayers(void); extern void NetLoadCheck(void); extern void NetLoadSummary(void); -extern void ProcessCommunicationControl(void); +extern void PrepareHouseCleanup(void); +extern void FinishHouseCleanup(void); extern void ProcessConnections(void); -extern void ProcessCronSystem(void); extern void ProcessMonsterhomes(void); extern void ProcessReaderThreadReplies(TRefreshSectorFunction *RefreshSector, TSendMailsFunction *SendMails); 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, const uint8 *Data, int Size); extern void SavePlayerDataOrder(void); extern void SendAll(void); extern void SendAmbiente(TConnection *Connection); @@ -92,13 +85,32 @@ extern void SendPlayerSkills(TConnection *Connection); extern void SendPlayerState(TConnection *Connection, uint8 State); extern void SendResult(TConnection *Connection, RESULT r); extern void SendSnapback(TConnection *Connection); +extern void SendTalk(TConnection *Connection, uint32 StatementID, + const char *Sender, int Mode, const char *Text, int Data); +extern void SendTalk(TConnection *Connection, uint32 StatementID, + const char *Sender, int Mode, int Channel, const char *Text); +extern void SendTalk(TConnection *Connection, uint32 StatementID, + const char *Sender, int Mode, int x,int y,int z, const char *Text); extern void SendTradeOffer(TConnection *Connection, const char *Name, bool OwnOffer, Object Obj); extern void ShowGuestList(uint16 HouseID, TPlayer *Player, char *Buffer); extern void ShowSubownerList(uint16 HouseID, TPlayer *Player, char *Buffer); extern void ShowNameDoor(Object Door, TPlayer *Player, char *Buffer); -extern void Talk(uint32 CreatureID, int Mode, const char *Addressee, const char *Text, bool CheckSpamming); -extern void Turn(uint32 CreatureID, Object Obj); -extern void Use(uint32 CreatureID, Object Obj1, Object Object2, uint8 Info); + +// moveuse.cc +extern void UseContainer(uint32 CreatureID, Object Con, uint8 ContainerNr); +extern void UseChest(uint32 CreatureID, Object Chest); +extern void UseLiquidContainer(uint32 CreatureID, Object Obj, Object Dest); +extern void UseFood(uint32 CreatureID, Object Obj); +extern void UseTextObject(uint32 CreatureID, Object Obj); +extern void UseAnnouncer(uint32 CreatureID, Object Obj); +extern void UseKeyDoor(uint32 CreatureID, Object Key, Object Door); +extern void UseNameDoor(uint32 CreatureID, Object Door); +extern void UseLevelDoor(uint32 CreatureID, Object Door); +extern void UseQuestDoor(uint32 CreatureID, Object Door); +extern void UseWeapon(uint32 CreatureID, Object Weapon, Object Target); +extern void UseChangeObject(uint32 CreatureID, Object Obj); +extern void UseObject(uint32 CreatureID, Object Obj); +extern void UseObjects(uint32 CreatureID, Object Obj1, Object Obj2); extern void MovementEvent(Object Obj, Object Start, Object Dest); extern void CollisionEvent(Object Obj, Object Dest); extern void SeparationEvent(Object Obj, Object Start); |
