aboutsummaryrefslogtreecommitdiff
path: root/src/sending.cc
diff options
context:
space:
mode:
authorfusion32 <marcopuzziello@gmail.com>2025-06-22 00:52:27 -0300
committerfusion32 <marcopuzziello@gmail.com>2025-06-22 00:52:27 -0300
commitc4dfe97e95846b43c578b3be35d0ef69f9b50e94 (patch)
tree104189628cc30e172cd291fd56dda2382fb38ed9 /src/sending.cc
parentefeb10adcc606ad261782a260c8ef86e77ea0608 (diff)
downloadgame-c4dfe97e95846b43c578b3be35d0ef69f9b50e94.tar.gz
game-c4dfe97e95846b43c578b3be35d0ef69f9b50e94.zip
`sending.cc`
Diffstat (limited to 'src/sending.cc')
-rw-r--r--src/sending.cc1751
1 files changed, 1751 insertions, 0 deletions
diff --git a/src/sending.cc b/src/sending.cc
new file mode 100644
index 0000000..52c3e28
--- /dev/null
+++ b/src/sending.cc
@@ -0,0 +1,1751 @@
+#include "connections.hh"
+#include "config.hh"
+#include "cr.hh"
+#include "info.hh"
+#include "map.hh"
+
+#include "stubs.hh"
+
+#include <signal.h>
+
+#define MAX_OBJECTS_PER_POINT 10
+#define MAX_OBJECTS_PER_CONTAINER 36
+
+static int Skip = -1;
+static TConnection *FirstSendingConnection;
+
+void SendAll(void){
+ TConnection *Connection = FirstSendingConnection;
+ FirstSendingConnection = NULL;
+ while(Connection != NULL){
+ if(Connection->WillingToSend){
+ Connection->WillingToSend = false;
+ if(Connection->Live() && Connection->NextToCommit > Connection->NextToSend){
+ kill(Connection->GetPID(), SIGUSR2);
+ }
+ }else{
+ error("SendAll: Verbindung ist nicht sendewillig.\n");
+ }
+ Connection = Connection->NextSendingConnection;
+ }
+}
+
+bool BeginSendData(TConnection *Connection){
+ bool Result = false;
+ if(Connection != NULL && Connection->Live() && Connection->State != CONNECTION_LOGIN){
+ int OutDataCommitted = (Connection->NextToCommit - Connection->NextToSend);
+ int OutDataCapacity = (int)sizeof(Connection->OutData);
+ if(OutDataCommitted < OutDataCapacity){
+ Connection->NextToWrite = Connection->NextToCommit;
+ Connection->Overflow = false;
+ Result = true;
+ }else{
+ print(2, "BeginSendData: Puffer von Verbindung %d ist voll.\n", Connection->GetSocket());
+ }
+ }
+ return Result;
+}
+
+void FinishSendData(TConnection *Connection){
+ if(Connection == NULL){
+ error("FinishSendData: Verbindung ist NULL.\n");
+ return;
+ }
+
+ if(!Connection->Live()){
+ error("FinishSendData: Verbindung ist nicht online.\n");
+ return;
+ }
+
+ if(Connection->Overflow){
+ print(2, "FinishSendData: Puffer ist voll. Paket wird nicht versandt.\n");
+ return;
+ }
+
+ Connection->NextToCommit = Connection->NextToWrite;
+ if(!Connection->WillingToSend){
+ Connection->WillingToSend = true;
+ Connection->NextSendingConnection = FirstSendingConnection;
+ FirstSendingConnection = Connection;
+ }
+}
+
+static void SendByte(TConnection *Connection, uint8 Value){
+ int OutDataWritten = (Connection->NextToWrite - Connection->NextToSend);
+ int OutDataCapacity = (int)sizeof(Connection->OutData);
+ if((OutDataWritten + 1) > OutDataCapacity){
+ Connection->Overflow = true;
+ return;
+ }
+
+ Connection->OutData[Connection->NextToWrite % OutDataCapacity] = Value;
+ Connection->NextToWrite += 1;
+}
+
+static void SendWord(TConnection *Connection, uint16 Value){
+ int OutDataWritten = (Connection->NextToWrite - Connection->NextToSend);
+ int OutDataCapacity = (int)sizeof(Connection->OutData);
+ if((OutDataWritten + 2) > OutDataCapacity){
+ Connection->Overflow = true;
+ return;
+ }
+
+ Connection->OutData[(Connection->NextToWrite + 0) % OutDataCapacity] = (uint8)(Value >> 0);
+ Connection->OutData[(Connection->NextToWrite + 1) % OutDataCapacity] = (uint8)(Value >> 8);
+ Connection->NextToWrite += 2;
+}
+
+static void SendQuad(TConnection *Connection, uint32 Value){
+ int OutDataWritten = (Connection->NextToWrite - Connection->NextToSend);
+ int OutDataCapacity = (int)sizeof(Connection->OutData);
+ if((OutDataWritten + 4) > OutDataCapacity){
+ Connection->Overflow = true;
+ return;
+ }
+
+ Connection->OutData[(Connection->NextToWrite + 0) % OutDataCapacity] = (uint8)(Value >> 0);
+ Connection->OutData[(Connection->NextToWrite + 1) % OutDataCapacity] = (uint8)(Value >> 8);
+ Connection->OutData[(Connection->NextToWrite + 2) % OutDataCapacity] = (uint8)(Value >> 16);
+ Connection->OutData[(Connection->NextToWrite + 3) % OutDataCapacity] = (uint8)(Value >> 24);
+ Connection->NextToWrite += 4;
+}
+
+// NOTE(fusion): This was called `SendText` but since most these sending helpers
+// were inlined, I just renamed it to something that made more sense.
+static void SendBytes(TConnection *Connection, const uint8 *Buffer, int Count){
+ if(Buffer == NULL){
+ error("SendText: Text ist NULL.\n");
+ return;
+ }
+
+ if(Count <= 0){
+ error("SendText: ungültige Textlänge %d.\n", Count);
+ return;
+ }
+
+ int OutDataWritten = (Connection->NextToWrite - Connection->NextToSend);
+ int OutDataCapacity = (int)sizeof(Connection->OutData);
+ if((OutDataWritten + Count) > OutDataCapacity){
+ Connection->Overflow = true;
+ return;
+ }
+
+ int BufferStart = Connection->NextToWrite % OutDataCapacity;
+ int BufferEnd = BufferStart + Count;
+ if(BufferEnd < OutDataCapacity){
+ memcpy(&Connection->OutData[BufferStart], Buffer, Count);
+ }else{
+ int Size1 = OutDataCapacity - BufferStart;
+ int Size2 = BufferEnd - OutDataCapacity;
+ memcpy(&Connection->OutData[BufferStart], &Buffer[0], Size1);
+ memcpy(&Connection->OutData[0], &Buffer[Size1], Size2);
+ }
+
+ Connection->NextToWrite += Count;
+}
+
+static void SendString(TConnection *Connection, const char *String){
+ if(String == NULL){
+ error("SendString: String ist NULL.\n");
+ return;
+ }
+
+ int StringLength = (int)strlen(String);
+ SendWord(Connection, (uint16)StringLength);
+ if(StringLength > 0){
+ SendBytes(Connection, (const uint8*)String, StringLength);
+ }
+}
+
+static void SendOutfit(TConnection *Connection, TOutfit Outfit){
+ SendWord(Connection, (uint16)Outfit.OutfitID);
+ if(Outfit.OutfitID == 0){
+ SendWord(Connection, Outfit.ObjectType);
+ }else{
+ SendBytes(Connection, Outfit.Colors, sizeof(Outfit.Colors));
+ }
+}
+
+static void SendItem(TConnection *Connection, Object Obj){
+ ObjectType ObjType = Obj.getObjectType();
+ SendWord(Connection, (uint16)ObjType.getDisguise().TypeID);
+
+ if(ObjType.getFlag(LIQUIDCONTAINER)){
+ int LiquidType = (int)Obj.getAttribute(CONTAINERLIQUIDTYPE);
+ SendByte(Connection, GetLiquidColor(LiquidType));
+ }
+
+ if(ObjType.getFlag(LIQUIDPOOL)){
+ int LiquidType = (int)Obj.getAttribute(POOLLIQUIDTYPE);
+ SendByte(Connection, GetLiquidColor(LiquidType));
+ }
+
+ if(ObjType.getFlag(CUMULATIVE)){
+ SendByte(Connection, (uint8)Obj.getAttribute(AMOUNT));
+ }
+}
+
+void SkipFlush(TConnection *Connection){
+ while(Skip >= 0){
+ int Count = std::min<int>(Skip, UINT8_MAX);
+ SendByte(Connection, (uint8)Count);
+ SendByte(Connection, UINT8_MAX);
+ Skip -= (Count + 1);
+ }
+}
+
+void SendMapObject(TConnection *Connection, Object Obj){
+ if(!Obj.exists()){
+ error("SendMapObject: Übergebenes Objekt existiert nicht.\n");
+ return;
+ }
+
+ if(!Obj.getObjectType().isCreatureContainer()){
+ SendItem(Connection, Obj);
+ return;
+ }
+
+ // TODO(fusion): This might also be contained in its own `SendCreature` function?
+ TCreature *Creature = GetCreature(Obj);
+ if(Creature == NULL){
+ error("SendMapObject: Kreatur hat kein Kreatur-Objekt\n");
+ return;
+ }
+
+ KNOWNCREATURESTATE KnownState = Connection->KnownCreature(Creature->ID, true);
+ if(KnownState == KNOWNCREATURE_UPTODATE){
+ SendWord(Connection, 99);
+ SendQuad(Connection, Creature->ID);
+ SendByte(Connection, (uint8)Creature->Direction);
+ }else if(KnownState == KNOWNCREATURE_OUTDATED
+ || KnownState == KNOWNCREATURE_FREE){
+ if(KnownState == KNOWNCREATURE_FREE){
+ SendWord(Connection, 97);
+ SendQuad(Connection, Connection->NewKnownCreature(Creature->ID));
+ SendQuad(Connection, Creature->ID);
+ if(Creature->Type == MONSTER){
+ // TODO(fusion): Apparently the monster name contains its
+ // article. I'm not sure it is a good idea and this probably
+ // lead to other bugs such as a monster having a two part name
+ // with no article.
+ const char *Name = findFirst(Creature->Name, ' ');
+ if(Name != NULL){
+ Name += 1;
+ }else{
+ Name = Creature->Name;
+ }
+ SendString(Connection, Name);
+ }else{
+ SendString(Connection, Creature->Name);
+ }
+ }else{
+ SendWord(Connection, 98);
+ SendQuad(Connection, Creature->ID);
+ }
+
+ int Brightness, Color;
+ GetCreatureLight(Creature->ID, &Brightness, &Color);
+
+ int PlayerkillingMark = SKULL_NONE;
+ int PartyMark = PARTY_SHIELD_NONE;
+ if(Creature->Type == PLAYER){
+ TPlayer *Player = (TPlayer*)Creature;
+ TPlayer *Observer = Connection->GetPlayer();
+ PlayerkillingMark = Player->GetPlayerkillingMark(Observer);
+ PartyMark = Player->GetPartyMark(Observer);
+ }
+
+ SendByte(Connection, (uint8)Creature->GetHealth());
+ SendByte(Connection, (uint8)Creature->Direction);
+ SendOutfit(Connection, Creature->Outfit);
+ SendByte(Connection, (uint8)Brightness);
+ SendByte(Connection, (uint8)Color);
+ SendWord(Connection, (uint16)Creature->GetSpeed());
+ SendByte(Connection, (uint8)PlayerkillingMark);
+ SendByte(Connection, (uint8)PartyMark);
+ }
+}
+
+void SendMapPoint(TConnection *Connection, int x, int y, int z){
+ Object Obj = GetFirstObject(x, y, z);
+ if(Obj != NONE){
+ SkipFlush(Connection);
+ int ObjCount = 0;
+ while(Obj != NONE && ObjCount < MAX_OBJECTS_PER_POINT){
+ SendMapObject(Connection, Obj);
+ Obj = Obj.getNextObject();
+ }
+ }
+ Skip += 1;
+}
+
+void SendResult(TConnection *Connection, RESULT r){
+ if(Connection == NULL){
+ return;
+ }
+
+ const char *Message = NULL;
+ switch(r) {
+ case NOERROR: error("SendResult: NOERROR\n"); break;
+ case NOTACCESSIBLE: Message = "Sorry, not possible."; break;
+ case NOTMOVABLE: Message = "You cannot move this object."; break;
+ case NOTTAKABLE: Message = "You cannot take this object."; break;
+ case NOROOM: Message = "There is not enough room."; break;
+ case OUTOFRANGE: Message = "Destination is out of range."; break;
+ case OUTOFHOME: error("SendResult: OUTOFHOME\n"); break;
+ case CANNOTTHROW: Message = "You cannot throw there."; break;
+ case TOOHEAVY: Message = "This object is too heavy."; break;
+ case CROSSREFERENCE: Message = "This is impossible."; break;
+ case CONTAINERFULL: Message = "You cannot put more objects in this container."; break;
+ case WRONGPOSITION: Message = "Put this object in your hand."; break;
+ case WRONGPOSITION2: Message = "Put this object in both hands."; break;
+ case WRONGCLOTHES: Message = "You cannot dress this object there."; break;
+ case HANDSNOTFREE: Message = "Both hands have to be free."; break;
+ case HANDBLOCKED: Message = "Drop the double-handed object first."; break;
+ case ONEWEAPONONLY: Message = "You may only use one weapon."; break;
+ case NOMATCH: error("SendResult: NOMATCH\n"); break;
+ case NOTCUMULABLE: error("SendResult: NOTCUMULABLE\n"); break;
+ case TOOMANYPARTS: error("SendResult: TOOMANYPARTS\n"); break;
+ case EMPTYCONTAINER: error("SendResult: EMPTYCONTAINER\n"); break;
+ case SPLITOBJECT: error("SendResult: SPLITOBJECT\n"); break;
+ case NOKEYMATCH: Message = "The key does not match."; break;
+ case UPSTAIRS: Message = "First go upstairs."; break;
+ case DOWNSTAIRS: Message = "First go downstairs."; break;
+ case CREATURENOTEXISTING: Message = "A creature with this name does not exist."; break;
+ case PLAYERNOTEXISTING: Message = "A player with this name does not exist."; break;
+ case PLAYERNOTONLINE: Message = "A player with this name is not online."; break;
+ case NAMEAMBIGUOUS: Message = "Playername is ambiguous."; break;
+ case NOTUSABLE: Message = "You cannot use this object."; break;
+ case FEDUP: Message = "You are full."; break;
+ case SPELLUNKNOWN: Message = "You must learn this spell first."; break;
+ case LOWMAGICLEVEL: Message = "Your magic level is too low."; break;
+ case MAGICITEM: Message = "A magic item is necessary to cast this spell."; break;
+ case NOTENOUGHMANA: Message = "You do not have enough mana."; break;
+ case NOSKILL: error("SendResult: NOSKILL\n"); break;
+ case TARGETLOST: Message = "Target lost."; break;
+ case NOCREATURE: Message = "You can only use this rune on creatures."; break;
+ case TOOLONG: error("SendResult: TOOLONG\n"); break;
+ case ATTACKNOTALLOWED: Message = "You may not attack this person."; break;
+ case NOWAY: Message = "There is no way."; break;
+ case LOGINERROR: Message = "An error occured while logging in."; break;
+ case PROTECTIONZONE: Message = "This action is not permitted in a protection zone."; break;
+ case ENTERPROTECTIONZONE: Message = "Characters who attacked other players may not enter a protection zone."; break;
+ case EXHAUSTED: Message = "You are exhausted."; break;
+ case NOTINVITED: Message = "You are not invited."; break;
+ case NOPREMIUMACCOUNT: Message = "You need a premium account."; break;
+ case MOVENOTPOSSIBLE: Message = "Sorry, not possible."; break;
+ case ALREADYTRADING: Message = "You are already trading. Finish this trade first."; break;
+ case PARTNERTRADING: Message = "This person is already trading."; break;
+ case TOOMANYOBJECTS: Message = "You can only trade up to 100 objects at one time."; break;
+ case TOOMANYSLAVES: Message = "You cannot control more creatures."; break;
+ case NOTTURNABLE: Message = "You cannot turn this object."; break;
+ case SECUREMODE: Message = "Turn secure mode off if you really want to attack unmarked players."; break;
+ case NOTENOUGHSOULPOINTS: Message = "You do not have enough soulpoints."; break;
+ case LOWLEVEL: Message = "Your level is too low."; break;
+ default: break;
+ }
+
+ if(Message != NULL){
+ if(r == ENTERPROTECTIONZONE || r == NOTINVITED || r == MOVENOTPOSSIBLE){
+ SendSnapback(Connection);
+ }
+ SendMessage(Connection, TALK_FAILURE_MESSAGE, "Sorry, not possible.");
+ }
+}
+
+void SendRefresh(TConnection *Connection){
+ SendFullScreen(Connection);
+ SendAmbiente(Connection);
+ SendPlayerData(Connection);
+ SendPlayerSkills(Connection);
+}
+
+void SendInitGame(TConnection *Connection, uint32 CreatureID){
+ if(!BeginSendData(Connection)){
+ return;
+ }
+
+ SendByte(Connection, SV_CMD_INIT_GAME);
+ SendQuad(Connection, CreatureID);
+ SendWord(Connection, (uint16)Beat);
+ SendByte(Connection, CheckRight(CreatureID, SEND_BUGREPORTS) ? 1 : 0);
+ FinishSendData(Connection);
+}
+
+void SendRights(TConnection *Connection){
+ if(!BeginSendData(Connection)){
+ return;
+ }
+
+ SendByte(Connection, SV_CMD_RIGHTS);
+
+ // NOTE(fusion): See `CheckBanishmentRight`.
+ bool Count = 0;
+ for(int Reason = 0; Reason <= 31; Reason += 1){
+ uint8 Actions = 0;
+ if(CheckRight(Connection->CharacterID, (RIGHT)(Reason + 18))){
+ for(int Action = 0; Action <= 6; Action += 1){
+ if(CheckBanishmentRight(Connection->CharacterID, Reason, Action)){
+ Actions |= (1 << Action);
+ }
+ }
+
+ if(Actions != 0){
+ if(CheckRight(Connection->CharacterID, IP_BANISHMENT)){
+ Actions |= 0x80;
+ }
+ Count += 1;
+ }
+ }
+
+ SendByte(Connection, Actions);
+ }
+
+ if(Count > 0){
+ FinishSendData(Connection);
+ }
+}
+
+void SendPing(TConnection *Connection){
+ if(!BeginSendData(Connection)){
+ return;
+ }
+
+ SendByte(Connection, SV_CMD_PING);
+ FinishSendData(Connection);
+}
+
+void SendFullScreen(TConnection *Connection){
+ if(!BeginSendData(Connection)){
+ return;
+ }
+
+ int PlayerX, PlayerY, PlayerZ;
+ Connection->GetPosition(&PlayerX, &PlayerY, &PlayerZ);
+ int MinX = PlayerX - Connection->TerminalOffsetX;
+ int MinY = PlayerY - Connection->TerminalOffsetY;
+ int MaxX = MinX + Connection->TerminalWidth - 1;
+ int MaxY = MinY + Connection->TerminalHeight - 1;
+
+ // NOTE(fusion): `EndZ` is exclusive.
+ int StartZ, EndZ, StepZ;
+ if(PlayerZ <= 7){
+ StepZ = -1;
+ StartZ = 7;
+ EndZ = 0 + StepZ;
+ }else{
+ StepZ = 1;
+ StartZ = PlayerZ - 2;
+ EndZ = std::min<int>(PlayerZ + 2, 15) + StepZ;
+ }
+
+ SendByte(Connection, SV_CMD_FULLSCREEN);
+ SendWord(Connection, (uint16)PlayerX);
+ SendWord(Connection, (uint16)PlayerY);
+ SendByte(Connection, (uint8)PlayerZ);
+
+ Skip = -1;
+ for(int PointZ = StartZ; PointZ != EndZ; EndZ += StepZ){
+ int ZOffset = (PlayerZ - PointZ);
+ for(int PointX = MinX; PointX <= MaxX; PointX += 1)
+ for(int PointY = MinY; PointY <= MaxY; PointY += 1){
+ SendMapPoint(Connection, PointX + ZOffset, PointY + ZOffset, PlayerZ);
+ }
+ }
+ SkipFlush(Connection);
+
+ FinishSendData(Connection);
+}
+
+void SendRow(TConnection *Connection, int Direction){
+ if(!BeginSendData(Connection)){
+ return;
+ }
+
+ int PlayerX, PlayerY, PlayerZ;
+ Connection->GetPosition(&PlayerX, &PlayerY, &PlayerZ);
+ int MinX = PlayerX - Connection->TerminalOffsetX;
+ int MinY = PlayerY - Connection->TerminalOffsetY;
+ int MaxX = MinX + Connection->TerminalWidth - 1;
+ int MaxY = MinY + Connection->TerminalHeight - 1;
+
+ // NOTE(fusion): `EndZ` is exclusive.
+ int StartZ, EndZ, StepZ;
+ if(PlayerZ <= 7){
+ StepZ = -1;
+ StartZ = 7;
+ EndZ = 0 + StepZ;
+ }else{
+ StepZ = 1;
+ StartZ = PlayerZ - 2;
+ EndZ = std::min<int>(PlayerZ + 2, 15) + StepZ;
+ }
+
+ if(Direction == DIRECTION_NORTH){
+ SendByte(Connection, SV_CMD_ROW_NORTH);
+ MaxY = MinY;
+ }else if(Direction == DIRECTION_EAST){
+ SendByte(Connection, SV_CMD_ROW_EAST);
+ MinX = MaxX;
+ }else if(Direction == DIRECTION_SOUTH){
+ SendByte(Connection, SV_CMD_ROW_SOUTH);
+ MinY = MaxY;
+ }else if(Direction == DIRECTION_WEST){
+ SendByte(Connection, SV_CMD_ROW_WEST);
+ MaxX = MinX;
+ }else{
+ error("SendRow: Ungültige Richtungsangabe %d.\n", Direction);
+ return;
+ }
+
+ Skip = -1;
+ for(int PointZ = StartZ; PointZ != EndZ; EndZ += StepZ){
+ int ZOffset = (PlayerZ - PointZ);
+ for(int PointX = MinX; PointX <= MaxX; PointX += 1)
+ for(int PointY = MinY; PointY <= MaxY; PointY += 1){
+ SendMapPoint(Connection, PointX + ZOffset, PointY + ZOffset, PlayerZ);
+ }
+ }
+ SkipFlush(Connection);
+
+ FinishSendData(Connection);
+}
+
+void SendFloors(TConnection *Connection, bool Up){
+ if(!BeginSendData(Connection)){
+ return;
+ }
+
+ int PlayerX, PlayerY, PlayerZ;
+ Connection->GetPosition(&PlayerX, &PlayerY, &PlayerZ);
+
+ // NOTE(fusion): `EndZ` is exclusive.
+ int StartZ = -1;
+ int EndZ = -1;
+ int StepZ = 0;
+ if(Up){
+ SendByte(Connection, SV_CMD_FLOOR_UP);
+ if(PlayerZ == 7){
+ // NOTE(fusion): Going to surface (8 -> 7). The client currently has
+ // floors [10, 6] so we only need to send floors [5, 0].
+ StepZ = -1;
+ StartZ = 5;
+ EndZ = 0 + StepZ;
+ }else if(PlayerZ > 7){
+ // NOTE(fusion): Going up but still underground. Send next floor up.
+ StepZ = -1;
+ StartZ = PlayerZ - 2;
+ EndZ = StartZ + StepZ;
+ }
+ }else{
+ SendByte(Connection, SV_CMD_FLOOR_DOWN);
+ if(PlayerZ == 8){
+ // NOTE(fusion): Going underground (7 -> 8). The client currently has
+ // floors [7, 0] so we only need to send floors [10, 8].
+ StepZ = 1;
+ StartZ = 8;
+ EndZ = 10 + StepZ;
+ }else if(PlayerZ > 8 && (PlayerZ + 2) <= 15){
+ // NOTE(fusion): Going down while underground. Send next floor down
+ // if it exists.
+ StepZ = 1;
+ StartZ = PlayerZ + 2;
+ EndZ = StartZ + StepZ;
+ }
+ }
+
+ if(StartZ != EndZ){
+ int MinX = PlayerX - Connection->TerminalOffsetX;
+ int MinY = PlayerY - Connection->TerminalOffsetY;
+ int MaxX = MinX + Connection->TerminalWidth - 1;
+ int MaxY = MinY + Connection->TerminalHeight - 1;
+
+ Skip = -1;
+ for(int PointZ = StartZ; PointZ != EndZ; EndZ += StepZ){
+ int ZOffset = (PlayerZ - PointZ);
+ for(int PointX = MinX; PointX <= MaxX; PointX += 1)
+ for(int PointY = MinY; PointY <= MaxY; PointY += 1){
+ SendMapPoint(Connection, PointX + ZOffset, PointY + ZOffset, PlayerZ);
+ }
+ }
+ SkipFlush(Connection);
+ }
+
+ FinishSendData(Connection);
+}
+
+void SendFieldData(TConnection *Connection, int x, int y, int z){
+ if(!BeginSendData(Connection)){
+ return;
+ }
+
+ SendByte(Connection, SV_CMD_FIELD_DATA);
+ SendWord(Connection, (uint16)x);
+ SendWord(Connection, (uint16)y);
+ SendByte(Connection, (uint8)z);
+
+ Skip = -1;
+ SendMapPoint(Connection, x, y, z);
+ SkipFlush(Connection);
+
+ FinishSendData(Connection);
+}
+
+void SendAddField(TConnection *Connection, int x, int y, int z, Object Obj){
+ if(!BeginSendData(Connection)){
+ return;
+ }
+
+ if(!Obj.exists()){
+ error("SendAddField: Übergebenes Objekt existiert nicht.\n");
+ return;
+ }
+
+ SendByte(Connection, SV_CMD_ADD_FIELD);
+ SendWord(Connection, (uint16)x);
+ SendWord(Connection, (uint16)y);
+ SendByte(Connection, (uint8)z);
+ SendMapObject(Connection, Obj);
+ FinishSendData(Connection);
+}
+
+void SendChangeField(TConnection *Connection, int x, int y, int z, Object Obj){
+ if(!BeginSendData(Connection)){
+ return;
+ }
+
+ if(!Obj.exists()){
+ error("SendChangeField: Übergebenes Objekt existiert nicht.\n");
+ return;
+ }
+
+ int ObjIndex = GetObjectRNum(Obj);
+ if(ObjIndex < MAX_OBJECTS_PER_POINT){
+ SendByte(Connection, SV_CMD_CHANGE_FIELD);
+ SendWord(Connection, (uint16)x);
+ SendWord(Connection, (uint16)y);
+ SendByte(Connection, (uint8)z);
+ SendByte(Connection, (uint8)ObjIndex);
+ SendMapObject(Connection, Obj);
+ FinishSendData(Connection);
+ }
+}
+
+void SendDeleteField(TConnection *Connection, int x, int y, int z, Object Obj){
+ if(!BeginSendData(Connection)){
+ return;
+ }
+
+ if(!Obj.exists()){
+ error("SendDeleteField: Übergebenes Objekt existiert nicht.\n");
+ return;
+ }
+
+ int ObjIndex = GetObjectRNum(Obj);
+ if(ObjIndex < MAX_OBJECTS_PER_POINT){
+ SendByte(Connection, SV_CMD_DELETE_FIELD);
+ SendWord(Connection, (uint16)x);
+ SendWord(Connection, (uint16)y);
+ SendByte(Connection, (uint8)z);
+ SendByte(Connection, (uint8)ObjIndex);
+ FinishSendData(Connection);
+ }
+}
+
+void SendMoveCreature(TConnection *Connection,
+ uint32 CreatureID, int DestX, int DestY, int DestZ){
+ if(Connection == NULL){
+ return;
+ }
+
+ TCreature *Creature = GetCreature(CreatureID);
+ if(Creature == NULL){
+ error("SendMoveCreature: Kreatur existiert nicht.\n");
+ return;
+ }
+
+ int OrigX = Creature->posx;
+ int OrigY = Creature->posy;
+ int OrigZ = Creature->posz;
+ int OrigIndex = GetObjectRNum(Creature->CrObject);
+ bool IsVisible = Connection->IsVisible(DestX, DestY, DestZ);
+ bool WasVisible = OrigIndex < MAX_OBJECTS_PER_POINT
+ && Connection->IsVisible(OrigX, OrigY, OrigZ);
+ if(IsVisible && WasVisible){
+ if(BeginSendData(Connection)){
+ SendByte(Connection, SV_CMD_MOVE_CREATURE);
+ SendWord(Connection, (uint16)OrigX);
+ SendWord(Connection, (uint16)OrigY);
+ SendByte(Connection, (uint8)OrigZ);
+ SendByte(Connection, (uint8)OrigIndex);
+ SendWord(Connection, (uint16)DestX);
+ SendWord(Connection, (uint16)DestY);
+ SendByte(Connection, (uint8)DestZ);
+ FinishSendData(Connection);
+ }
+ }else if(IsVisible){
+ SendAddField(Connection, DestX, DestY, DestZ, Creature->CrObject);
+ }else if(WasVisible){
+ SendDeleteField(Connection, OrigX, OrigY, OrigZ, Creature->CrObject);
+ }
+}
+
+void SendContainer(TConnection *Connection, int ContainerNr){
+ if(!BeginSendData(Connection)){
+ return;
+ }
+
+ TPlayer *Player = Connection->GetPlayer();
+ if(Player == NULL){
+ error("SendContainer: Zu dieser Verbindung gehört kein Spieler.\n");
+ return;
+ }
+
+ Object Con = Player->GetOpenContainer(ContainerNr);
+ if(!Con.exists()){
+ error("SendContainer: Container %d existiert nicht.\n", ContainerNr);
+ return;
+ }
+
+ ObjectType ConType = Con.getObjectType();
+ bool HasUpContainer = (Con.getContainer() != NONE
+ && !Con.getContainer().getObjectType().isBodyContainer());
+
+ int ConObjects = CountObjectsInContainer(Con);
+ if(ConObjects > MAX_OBJECTS_PER_CONTAINER){
+ ConObjects = MAX_OBJECTS_PER_CONTAINER;
+ }
+
+ SendByte(Connection, SV_CMD_CONTAINER);
+ SendByte(Connection, (uint8)ContainerNr);
+ SendWord(Connection, (uint16)ConType.getDisguise().TypeID);
+ SendString(Connection, ConType.getName(-1));
+ SendByte(Connection, (uint8)ConType.getAttribute(CAPACITY));
+ SendByte(Connection, HasUpContainer ? 1 : 0);
+ SendByte(Connection, (uint8)ConObjects);
+
+ Object Obj = GetFirstContainerObject(Con);
+ while(Obj != NONE && ConObjects > 0){
+ SendItem(Connection, Obj);
+ Obj = Obj.getNextObject();
+ ConObjects -= 1;
+ }
+
+ FinishSendData(Connection);
+}
+
+void SendCloseContainer(TConnection *Connection, int ContainerNr){
+ if(!BeginSendData(Connection)){
+ return;
+ }
+
+ SendByte(Connection, SV_CMD_CLOSE_CONTAINER);
+ SendByte(Connection, (uint8)ContainerNr);
+ FinishSendData(Connection);
+}
+
+void SendCreateInContainer(TConnection *Connection, int ContainerNr, Object Obj){
+ if(!BeginSendData(Connection)){
+ return;
+ }
+
+ SendByte(Connection, SV_CMD_CREATE_IN_CONTAINER);
+ SendByte(Connection, (uint8)ContainerNr);
+ SendItem(Connection, Obj);
+ FinishSendData(Connection);
+}
+
+void SendChangeInContainer(TConnection *Connection, int ContainerNr, Object Obj){
+ if(!BeginSendData(Connection)){
+ return;
+ }
+
+ if(!Obj.exists()){
+ error("SendChangeInContainer: Übergebenes Objekt existiert nicht.\n");
+ return;
+ }
+
+ int ObjIndex = GetObjectRNum(Obj);
+ if(ObjIndex < MAX_OBJECTS_PER_CONTAINER){
+ SendByte(Connection, SV_CMD_CHANGE_IN_CONTAINER);
+ SendByte(Connection, (uint8)ContainerNr);
+ SendByte(Connection, (uint8)ObjIndex);
+ SendItem(Connection, Obj);
+ FinishSendData(Connection);
+ }
+}
+
+void SendDeleteInContainer(TConnection *Connection, int ContainerNr, Object Obj){
+ if(!BeginSendData(Connection)){
+ return;
+ }
+
+ if(!Obj.exists()){
+ error("SendDeleteInContainer: Übergebenes Objekt existiert nicht.\n");
+ return;
+ }
+
+ int ObjIndex = GetObjectRNum(Obj);
+ if(ObjIndex < MAX_OBJECTS_PER_CONTAINER){
+ SendByte(Connection, SV_CMD_DELETE_IN_CONTAINER);
+ SendByte(Connection, (uint8)ContainerNr);
+ SendByte(Connection, (uint8)ObjIndex);
+ FinishSendData(Connection);
+ }
+}
+
+void SendBodyInventory(TConnection *Connection, uint32 CreatureID){
+ for(int Position = INVENTORY_FIRST;
+ Position <= INVENTORY_LAST;
+ Position += 1){
+ Object Obj = GetBodyObject(CreatureID, Position);
+ if(Obj != NONE){
+ SendSetInventory(Connection, Position, Obj);
+ }
+ }
+}
+
+void SendSetInventory(TConnection *Connection, int Position, Object Obj){
+ if(!BeginSendData(Connection)){
+ return;
+ }
+
+ if(!Obj.exists()){
+ error("SendSetInventory: Objekt existiert nicht.\n");
+ return;
+ }
+
+ SendByte(Connection, SV_CMD_SET_INVENTORY);
+ SendByte(Connection, (uint8)Position);
+ SendItem(Connection, Obj);
+ FinishSendData(Connection);
+}
+
+void SendDeleteInventory(TConnection *Connection, int Position){
+ if(!BeginSendData(Connection)){
+ return;
+ }
+
+ SendByte(Connection, SV_CMD_DELETE_INVENTORY);
+ SendByte(Connection, (uint8)Position);
+ FinishSendData(Connection);
+}
+
+static void SendTradeObjects(TConnection *Connection, Object Obj){
+ if(!Obj.exists()){
+ return;
+ }
+
+ SendItem(Connection, Obj);
+ if(Obj.getObjectType().getFlag(CONTAINER)){
+ Object Help = GetFirstContainerObject(Obj);
+ while(Help != NONE){
+ SendTradeObjects(Connection, Help);
+ Help = Help.getNextObject();
+ }
+ }
+}
+
+void SendTradeOffer(TConnection *Connection, const char *Name, bool OwnOffer, Object Obj){
+ if(!BeginSendData(Connection)){
+ return;
+ }
+
+ if(Name == NULL){
+ error("SendTradeOffer: Name ist NULL.\n");
+ return;
+ }
+
+ if(!Obj.exists()){
+ error("SendTradeOffer: Objekt existiert nicht.\n");
+ return;
+ }
+
+ int ObjCount = CountObjects(Obj);
+ if(ObjCount > UINT8_MAX){
+ error("SendTradeOffer: Too many objects.\n");
+ return;
+ }
+
+ if(OwnOffer){
+ SendByte(Connection, SV_CMD_TRADE_OFFER_OWN);
+ }else{
+ SendByte(Connection, SV_CMD_TRADE_OFFER_PARTNER);
+ }
+
+ SendString(Connection, Name);
+ SendByte(Connection, (uint8)ObjCount);
+ SendTradeObjects(Connection, Obj);
+ FinishSendData(Connection);
+}
+
+void SendCloseTrade(TConnection *Connection){
+ if(!BeginSendData(Connection)){
+ return;
+ }
+
+ SendByte(Connection, SV_CMD_CLOSE_TRADE);
+ FinishSendData(Connection);
+}
+
+void SendAmbiente(TConnection *Connection){
+ if(!BeginSendData(Connection)){
+ return;
+ }
+
+ int Brightness, Color;
+ GetAmbiente(&Brightness, &Color);
+
+ SendByte(Connection, SV_CMD_AMBIENTE);
+ SendByte(Connection, (uint8)Brightness);
+ SendByte(Connection, (uint8)Color);
+ FinishSendData(Connection);
+}
+
+void SendGraphicalEffect(TConnection *Connection, int x, int y, int z, int Type){
+ if(!BeginSendData(Connection)){
+ return;
+ }
+
+ SendByte(Connection, SV_CMD_GRAPHICAL_EFFECT);
+ SendWord(Connection, (uint16)x);
+ SendWord(Connection, (uint16)y);
+ SendByte(Connection, (uint8)z);
+ SendByte(Connection, (uint8)Type);
+ FinishSendData(Connection);
+}
+
+void SendTextualEffect(TConnection *Connection, int x, int y, int z, int Color, const char *Text){
+ if(Text == NULL){
+ error("SendTextualEffect: Text ist NULL.\n");
+ return;
+ }
+
+ if(Text[0] == 0){
+ error("SendTextualEffect: Text ist leer.\n");
+ return;
+ }
+
+ if(!BeginSendData(Connection)){
+ return;
+ }
+
+ SendByte(Connection, SV_CMD_TEXTUAL_EFFECT);
+ SendWord(Connection, (uint16)x);
+ SendWord(Connection, (uint16)y);
+ SendByte(Connection, (uint8)z);
+ SendByte(Connection, (uint8)Color);
+ SendString(Connection, Text);
+ FinishSendData(Connection);
+}
+
+void SendMissileEffect(TConnection *Connection, int OrigX, int OrigY, int OrigZ,
+ int DestX, int DestY, int DestZ, int Type){
+ if(!BeginSendData(Connection)){
+ return;
+ }
+
+ SendByte(Connection, SV_CMD_MISSILE_EFFECT);
+ SendWord(Connection, (uint16)OrigX);
+ SendWord(Connection, (uint16)OrigY);
+ SendByte(Connection, (uint8)OrigZ);
+ SendWord(Connection, (uint16)DestX);
+ SendWord(Connection, (uint16)DestY);
+ SendByte(Connection, (uint8)DestZ);
+ SendByte(Connection, (uint8)Type);
+ FinishSendData(Connection);
+}
+
+void SendMarkCreature(TConnection *Connection, uint32 CreatureID, int Color){
+ if(!BeginSendData(Connection)){
+ return;
+ }
+
+ SendByte(Connection, SV_CMD_MARK_CREATURE);
+ SendQuad(Connection, CreatureID);
+ SendByte(Connection, (uint8)Color);
+ FinishSendData(Connection);
+}
+
+void SendCreatureHealth(TConnection *Connection, uint32 CreatureID){
+ if(!BeginSendData(Connection)){
+ return;
+ }
+
+ TCreature *Creature = GetCreature(CreatureID);
+ if(Creature == NULL){
+ error("SendCreatureHealth: Kreatur %u existiert nicht.\n", CreatureID);
+ return;
+ }
+
+ SendByte(Connection, SV_CMD_CREATURE_HEALTH);
+ SendQuad(Connection, CreatureID);
+ SendByte(Connection, (uint8)Creature->GetHealth());
+ FinishSendData(Connection);
+}
+
+void SendCreatureLight(TConnection *Connection, uint32 CreatureID){
+ if(!BeginSendData(Connection)){
+ return;
+ }
+
+ TCreature *Creature = GetCreature(CreatureID);
+ if(Creature == NULL){
+ error("SendCreatureLight: Kreatur %u existiert nicht.\n", CreatureID);
+ return;
+ }
+
+ int Brightness, Color;
+ GetCreatureLight(CreatureID, &Brightness, &Color);
+
+ SendByte(Connection, SV_CMD_CREATURE_LIGHT);
+ SendQuad(Connection, CreatureID);
+ SendByte(Connection, (uint8)Brightness);
+ SendByte(Connection, (uint8)Color);
+ FinishSendData(Connection);
+}
+
+void SendCreatureOutfit(TConnection *Connection, uint32 CreatureID){
+ if(!BeginSendData(Connection)){
+ return;
+ }
+
+ TCreature *Creature = GetCreature(CreatureID);
+ if(Creature == NULL){
+ error("SendCreatureOutfit: Kreatur %u existiert nicht.\n", CreatureID);
+ return;
+ }
+
+ SendByte(Connection, SV_CMD_CREATURE_OUTFIT);
+ SendQuad(Connection, CreatureID);
+ SendOutfit(Connection, Creature->Outfit);
+ FinishSendData(Connection);
+}
+
+void SendCreatureSpeed(TConnection *Connection, uint32 CreatureID){
+ if(!BeginSendData(Connection)){
+ return;
+ }
+
+ TCreature *Creature = GetCreature(CreatureID);
+ if(Creature == NULL){
+ error("SendCreatureSpeed: Kreatur %u existiert nicht.\n", CreatureID);
+ return;
+ }
+
+ SendByte(Connection, SV_CMD_CREATURE_SPEED);
+ SendQuad(Connection, CreatureID);
+ SendWord(Connection, (uint16)Creature->GetSpeed());
+ FinishSendData(Connection);
+}
+
+void SendCreatureSkull(TConnection *Connection, uint32 CreatureID){
+ if(!BeginSendData(Connection)){
+ return;
+ }
+
+ TPlayer *Player = GetPlayer(CreatureID);
+ if(Player == NULL){
+ error("SendCreatureSkull: Kreatur %u existiert nicht.\n", CreatureID);
+ return;
+ }
+
+ SendByte(Connection, SV_CMD_CREATURE_SKULL);
+ SendQuad(Connection, CreatureID);
+ SendByte(Connection, Player->GetPlayerkillingMark(Connection->GetPlayer()));
+ FinishSendData(Connection);
+}
+
+void SendCreatureParty(TConnection *Connection, uint32 CreatureID){
+ if(!BeginSendData(Connection)){
+ return;
+ }
+
+ TPlayer *Player = GetPlayer(CreatureID);
+ if(Player == NULL){
+ error("SendCreatureParty: Kreatur %u existiert nicht.\n", CreatureID);
+ return;
+ }
+
+ SendByte(Connection, SV_CMD_CREATURE_PARTY);
+ SendQuad(Connection, CreatureID);
+ SendByte(Connection, Player->GetPartyMark(Connection->GetPlayer()));
+ FinishSendData(Connection);
+}
+
+void SendEditText(TConnection *Connection, Object Obj){
+ if(!BeginSendData(Connection)){
+ return;
+ }
+
+ if(!Obj.exists()){
+ error("SendEditText: Übergebenes Objekt existiert nicht.\n");
+ return;
+ }
+
+ ObjectType ObjType = Obj.getObjectType();
+ int TypeID = ObjType.getDisguise().TypeID;
+ int MaxLength = 0;
+ const char *Text = NULL;
+ const char *Editor = NULL;
+ char SpellbookBuffer[4096] = {};
+ if(ObjType.getFlag(WRITE) || ObjType.getFlag(WRITEONCE)){
+ MaxLength = (ObjType.getFlag(WRITE)
+ ? ObjType.getAttribute(MAXLENGTH)
+ : ObjType.getAttribute(MAXLENGTHONCE));
+ MaxLength -= 1;
+ Text = GetDynamicString(Obj.getAttribute(TEXTSTRING));
+ Editor = GetDynamicString(Obj.getAttribute(EDITOR));
+ }else if(ObjType.getFlag(INFORMATION)
+ && ObjType.getAttribute(INFORMATIONTYPE) == 4){ // INFORMATION_SPELLBOOK ?
+ TPlayer *Player = Connection->GetPlayer();
+ if(Player == NULL){
+ error("SendEditText: Zu dieser Verbindung gehört kein Spieler.\n");
+ return;
+ }
+
+ GetSpellbook(Player->ID, SpellbookBuffer);
+ Text = SpellbookBuffer;
+ MaxLength = strlen(Text);
+ }else{
+ Text = GetDynamicString(Obj.getAttribute(TEXTSTRING));
+ Editor = GetDynamicString(Obj.getAttribute(EDITOR));
+ if(Text != NULL){
+ MaxLength = strlen(Text);
+ }
+ }
+
+ SendByte(Connection, SV_CMD_EDIT_TEXT);
+ SendQuad(Connection, Obj.ObjectID);
+ SendWord(Connection, (uint16)TypeID);
+ SendWord(Connection, (uint16)MaxLength);
+ if(Text != NULL){
+ SendString(Connection, Text);
+ SendString(Connection, (Editor != NULL ? Editor : ""));
+ }else{
+ SendString(Connection, "");
+ SendString(Connection, "");
+ }
+
+ FinishSendData(Connection);
+}
+
+void SendEditList(TConnection *Connection, uint8 Type, uint32 ID, const char *Text){
+ if(!BeginSendData(Connection)){
+ return;
+ }
+
+ if(Text == NULL){
+ error("SendEditList: Text ist NULL.\n");
+ return;
+ }
+
+ if(strlen(Text) >= 4000){
+ error("SendEditList: Text ist zu lang.\n");
+ return;
+ }
+
+ SendByte(Connection, SV_CMD_EDIT_LIST);
+ SendByte(Connection, Type);
+ SendQuad(Connection, ID);
+ SendString(Connection, Text);
+ FinishSendData(Connection);
+}
+
+void SendPlayerData(TConnection *Connection){
+ if(!BeginSendData(Connection)){
+ return;
+ }
+
+ TPlayer *Player = Connection->GetPlayer();
+ if(Player == NULL){
+ error("SendStatus: Zu dieser Verbindung gehört kein Spieler.\n");
+ return;
+ }
+
+ // TODO(fusion): I don't even think skills can even be NULL.
+ int Level = 0;
+ int LevelPercent = 0;
+ int Experience = 0;
+ int MagicLevel = 0;
+ int MagicLevelPercent = 0;
+ int HitPoints = 0;
+ int MaxHitPoints = 0;
+ int ManaPoints = 0;
+ int MaxManaPoints = 0;
+ int Capacity = 0;
+ int SoulPoints = 0;
+
+ if(Player->Skills[SKILL_LEVEL] != NULL){
+ Level = Player->Skills[SKILL_LEVEL]->Get();
+ LevelPercent = Player->Skills[SKILL_LEVEL]->GetProgress();
+ Experience = Player->Skills[SKILL_LEVEL]->Exp;
+ }
+
+ if(Player->Skills[SKILL_MAGIC_LEVEL] != NULL){
+ MagicLevel = Player->Skills[SKILL_MAGIC_LEVEL]->Get();
+ MagicLevelPercent = Player->Skills[SKILL_MAGIC_LEVEL]->GetProgress();
+ }
+
+ if(Player->Skills[SKILL_HITPOINTS] != NULL){
+ HitPoints = Player->Skills[SKILL_HITPOINTS]->Get();
+ MaxHitPoints = Player->Skills[SKILL_HITPOINTS]->Max;
+ }
+
+ if(Player->Skills[SKILL_MANA] != NULL){
+ ManaPoints = Player->Skills[SKILL_MANA]->Get();
+ MaxManaPoints = Player->Skills[SKILL_MANA]->Max;
+ }
+
+ if(Player->Skills[SKILL_CARRY_STRENGTH] != NULL
+ && !CheckRight(Player->ID, ZERO_CAPACITY)){
+ int InventoryWeight = GetInventoryWeight(Player->ID);
+ int MaxWeight = Player->Skills[SKILL_CARRY_STRENGTH]->Get() * 100;
+ Capacity = (MaxWeight - InventoryWeight) / 100;
+ }
+
+ if(Player->Skills[SKILL_SOUL] != NULL){
+ SoulPoints = Player->Skills[SKILL_SOUL]->Get();
+ }
+
+ SendByte(Connection, SV_CMD_PLAYER_DATA);
+ SendWord(Connection, (uint16)HitPoints);
+ SendWord(Connection, (uint16)MaxHitPoints);
+ SendWord(Connection, (uint16)Capacity);
+ SendQuad(Connection, (uint32)Experience);
+ SendWord(Connection, (uint16)Level);
+ SendByte(Connection, (uint8)LevelPercent);
+ SendWord(Connection, (uint16)ManaPoints);
+ SendWord(Connection, (uint16)MaxManaPoints);
+ SendByte(Connection, (uint8)MagicLevel);
+ SendByte(Connection, (uint8)MagicLevelPercent);
+ SendByte(Connection, (uint8)SoulPoints);
+ FinishSendData(Connection);
+}
+
+void SendPlayerSkills(TConnection *Connection){
+ if(!BeginSendData(Connection)){
+ return;
+ }
+
+ TPlayer *Player = Connection->GetPlayer();
+ if(Player == NULL){
+ error("SendPlayerSkills: Zu dieser Verbindung gehört kein Spieler.\n");
+ return;
+ }
+
+ // TODO(fusion): I don't even think skills can even be NULL.
+ int FistLevel = 0;
+ int FistPercent = 0;
+ int ClubLevel = 0;
+ int ClubPercent = 0;
+ int SwordLevel = 0;
+ int SwordPercent = 0;
+ int AxeLevel = 0;
+ int AxePercent = 0;
+ int DistanceLevel = 0;
+ int DistancePercent = 0;
+ int ShieldingLevel = 0;
+ int ShieldingPercent = 0;
+ int FishingLevel = 0;
+ int FishingPercent = 0;
+
+ if(Player->Skills[SKILL_FIST] != NULL){
+ FistLevel = Player->Skills[SKILL_FIST]->Get();
+ FistPercent = Player->Skills[SKILL_FIST]->GetProgress();
+ }
+
+ if(Player->Skills[SKILL_CLUB] != NULL){
+ ClubLevel = Player->Skills[SKILL_CLUB]->Get();
+ ClubPercent = Player->Skills[SKILL_CLUB]->GetProgress();
+ }
+
+ if(Player->Skills[SKILL_SWORD] != NULL){
+ SwordLevel = Player->Skills[SKILL_SWORD]->Get();
+ SwordPercent = Player->Skills[SKILL_SWORD]->GetProgress();
+ }
+
+ if(Player->Skills[SKILL_AXE] != NULL){
+ AxeLevel = Player->Skills[SKILL_AXE]->Get();
+ AxePercent = Player->Skills[SKILL_AXE]->GetProgress();
+ }
+
+ if(Player->Skills[SKILL_DISTANCE] != NULL){
+ DistanceLevel = Player->Skills[SKILL_DISTANCE]->Get();
+ DistancePercent = Player->Skills[SKILL_DISTANCE]->GetProgress();
+ }
+
+ if(Player->Skills[SKILL_SHIELDING] != NULL){
+ ShieldingLevel = Player->Skills[SKILL_SHIELDING]->Get();
+ ShieldingPercent = Player->Skills[SKILL_SHIELDING]->GetProgress();
+ }
+
+ if(Player->Skills[SKILL_FISHING] != NULL){
+ FishingLevel = Player->Skills[SKILL_FISHING]->Get();
+ FishingPercent = Player->Skills[SKILL_FISHING]->GetProgress();
+ }
+
+ SendByte(Connection, SV_CMD_PLAYER_SKILLS);
+ SendByte(Connection, (uint8)FistLevel);
+ SendByte(Connection, (uint8)FistPercent);
+ SendByte(Connection, (uint8)ClubLevel);
+ SendByte(Connection, (uint8)ClubPercent);
+ SendByte(Connection, (uint8)SwordLevel);
+ SendByte(Connection, (uint8)SwordPercent);
+ SendByte(Connection, (uint8)AxeLevel);
+ SendByte(Connection, (uint8)AxePercent);
+ SendByte(Connection, (uint8)DistanceLevel);
+ SendByte(Connection, (uint8)DistancePercent);
+ SendByte(Connection, (uint8)ShieldingLevel);
+ SendByte(Connection, (uint8)ShieldingPercent);
+ SendByte(Connection, (uint8)FishingLevel);
+ SendByte(Connection, (uint8)FishingPercent);
+ FinishSendData(Connection);
+}
+
+void SendPlayerState(TConnection *Connection, uint8 State){
+ if(!BeginSendData(Connection)){
+ return;
+ }
+
+ SendByte(Connection, SV_CMD_PLAYER_STATE);
+ SendByte(Connection, State);
+ FinishSendData(Connection);
+}
+
+void SendClearTarget(TConnection *Connection){
+ if(!BeginSendData(Connection)){
+ return;
+ }
+
+ SendByte(Connection, SV_CMD_CLEAR_TARGET);
+ FinishSendData(Connection);
+}
+
+void SendTalk(TConnection *Connection, uint32 StatementID,
+ const char *Sender, int Mode, const char *Text, int Data){
+ if(Mode != TALK_PRIVATE_MESSAGE
+ && Mode != TALK_GAMEMASTER_REQUEST
+ && Mode != TALK_GAMEMASTER_ANSWER
+ && Mode != TALK_PLAYER_ANSWER
+ && Mode != TALK_GAMEMASTER_BROADCAST
+ && Mode != TALK_GAMEMASTER_MESSAGE){
+ error("SendTalk (-): Ungültiger Modus %d.\n", Mode);
+ return;
+ }
+
+ if(Sender == NULL){
+ error("SendTalk (-): Sender ist NULL.\n");
+ return;
+ }
+
+ if(Text == NULL){
+ error("SendTalk (-): Text ist NULL.\n");
+ return;
+ }
+
+ if(!BeginSendData(Connection)){
+ return;
+ }
+
+ SendByte(Connection, SV_CMD_TALK);
+ SendQuad(Connection, StatementID);
+ SendString(Connection, Sender);
+ SendByte(Connection, (uint8)Mode);
+ if(Mode == TALK_GAMEMASTER_REQUEST){
+ SendQuad(Connection, (uint32)Data);
+ }
+ SendString(Connection, Text);
+ FinishSendData(Connection);
+}
+
+void SendTalk(TConnection *Connection, uint32 StatementID,
+ const char *Sender, int Mode, int Channel, const char *Text){
+ if(Mode != TALK_CHANNEL_CALL
+ && Mode != TALK_GAMEMASTER_CHANNELCALL
+ && Mode != TALK_HIGHLIGHT_CHANNELCALL
+ && Mode != TALK_ANONYMOUS_CHANNELCALL){
+ error("SendTalk (C): Ungültiger Modus %d.\n", Mode);
+ return;
+ }
+
+ if(Sender == NULL){
+ error("SendTalk (C): Sender ist NULL.\n");
+ return;
+ }
+
+ if(Text == NULL){
+ error("SendTalk (C): Text ist NULL.\n");
+ return;
+ }
+
+ if(!BeginSendData(Connection)){
+ return;
+ }
+
+ SendByte(Connection, SV_CMD_TALK);
+ SendQuad(Connection, StatementID);
+ if(Mode != TALK_ANONYMOUS_CHANNELCALL){
+ SendString(Connection, Sender);
+ }else{
+ SendString(Connection, "");
+ }
+ SendByte(Connection, (uint8)Mode);
+ SendWord(Connection, (uint16)Channel);
+ SendString(Connection, Text);
+ FinishSendData(Connection);
+}
+
+void SendTalk(TConnection *Connection, uint32 StatementID,
+ const char *Sender, int Mode, int x, int y, int z, const char *Text){
+ if(Mode != TALK_SAY
+ && Mode != TALK_WHISPER
+ && Mode != TALK_YELL
+ && Mode != TALK_ANIMAL_LOW
+ && Mode != TALK_ANIMAL_LOUD){
+ error("SendTalk: Ungültiger Modus %d.\n", Mode);
+ return;
+ }
+
+ if(Sender == NULL){
+ error("SendTalk (K): Sender ist NULL.\n");
+ return;
+ }
+
+ if(Text == NULL){
+ error("SendTalk (K): Text ist NULL.\n");
+ return;
+ }
+
+ if(!BeginSendData(Connection)){
+ return;
+ }
+
+ SendByte(Connection, SV_CMD_TALK);
+ SendQuad(Connection, StatementID);
+ SendString(Connection, Sender);
+ SendByte(Connection, (uint8)Mode);
+ SendWord(Connection, (uint16)x);
+ SendWord(Connection, (uint16)y);
+ SendByte(Connection, (uint8)z);
+ SendString(Connection, Text);
+ FinishSendData(Connection);
+}
+
+void SendChannels(TConnection *Connection){
+ if(!BeginSendData(Connection)){
+ return;
+ }
+
+ TPlayer *Player = Connection->GetPlayer();
+ if(Player == NULL){
+ return;
+ }
+
+ int Channels = GetNumberOfChannels();
+ bool OwnChannel = MayOpenChannel(Player->ID);
+ int NumAvailable = 0;
+ bool *Available = (bool*)alloca(Channels * sizeof(bool));
+ for(int Channel = 0; Channel < Channels; Channel += 1){
+ Available[Channel] = ChannelAvailable(Channel, Player->ID);
+ if(Available[Channel]){
+ NumAvailable += 1;
+ }
+ }
+
+ if(OwnChannel){
+ NumAvailable += 1;
+ }
+
+ // TODO(fusion): The original function wouldn't limit the maximum number
+ // of available channels. It is unlikely any player would be invited to
+ // more channels than an uint8 can hold but there is no reason not to
+ // maintain packet coherency. If this value wraps and we still add all
+ // available channels, the whole packet will get dropped and I'm not sure
+ // but the client may also assert.
+ if(NumAvailable > UINT8_MAX){
+ NumAvailable = UINT8_MAX;
+ }
+
+ SendByte(Connection, SV_CMD_CHANNELS);
+ SendByte(Connection, (uint8)NumAvailable);
+
+ if(OwnChannel){
+ SendWord(Connection, 0xFFFF);
+ SendString(Connection, "Private Chat Channel");
+ NumAvailable -= 1;
+ }
+
+ for(int Channel = 0; Channel < Channels; Channel += 1){
+ if(NumAvailable <= 0){
+ break;
+ }
+
+ if(Available[Channel]){
+ SendWord(Connection, (uint16)Channel);
+ SendString(Connection, GetChannelName(Channel, Player->ID));
+ NumAvailable -= 1;
+ }
+ }
+
+ FinishSendData(Connection);
+}
+
+void SendOpenChannel(TConnection *Connection, int Channel){
+ if(!BeginSendData(Connection)){
+ return;
+ }
+
+ if(Channel == CHANNEL_RULEVIOLATIONS){
+ error("SendOpenChannel: Kanal ist GM-Request-Queue.\n");
+ return;
+ }
+
+ if(Channel < 0 || Channel >= GetNumberOfChannels()){
+ error("SendOpenChannel: Ungültiger Kanal %d.\n", Channel);
+ return;
+ }
+
+ TPlayer *Player = Connection->GetPlayer();
+ if(Player == NULL){
+ return;
+ }
+
+ SendByte(Connection, SV_CMD_OPEN_CHANNEL);
+ SendWord(Connection, (uint16)Channel);
+ SendString(Connection, GetChannelName(Channel, Player->ID));
+ FinishSendData(Connection);
+}
+
+void SendPrivateChannel(TConnection *Connection, const char *Name){
+ if(!BeginSendData(Connection)){
+ return;
+ }
+
+ if(Name == NULL){
+ error("SendPrivateChannel: Name ist NULL.\n");
+ return;
+ }
+
+ SendByte(Connection, SV_CMD_PRIVATE_CHANNEL);
+ SendString(Connection, Name);
+ FinishSendData(Connection);
+}
+
+void SendOpenRequest(TConnection *Connection){
+ if(!BeginSendData(Connection)){
+ return;
+ }
+
+ SendByte(Connection, SV_CMD_OPEN_REQUEST);
+ SendWord(Connection, (uint16)CHANNEL_RULEVIOLATIONS);
+ FinishSendData(Connection);
+}
+
+void SendDeleteRequest(TConnection *Connection, const char *Name){
+ if(!BeginSendData(Connection)){
+ return;
+ }
+
+ if(Name == NULL){
+ error("SendDeleteRequest: Name ist NULL.\n");
+ return;
+ }
+
+ SendByte(Connection, SV_CMD_DELETE_REQUEST);
+ SendString(Connection, Name);
+ FinishSendData(Connection);
+}
+
+void SendFinishRequest(TConnection *Connection, const char *Name){
+ if(!BeginSendData(Connection)){
+ return;
+ }
+
+ if(Name == NULL){
+ error("SendFinishRequest: Name ist NULL.\n");
+ return;
+ }
+
+ SendByte(Connection, SV_CMD_FINISH_REQUEST);
+ SendString(Connection, Name);
+ FinishSendData(Connection);
+}
+
+void SendCloseRequest(TConnection *Connection){
+ if(!BeginSendData(Connection)){
+ return;
+ }
+
+ SendByte(Connection, SV_CMD_CLOSE_REQUEST);
+ FinishSendData(Connection);
+}
+
+void SendOpenOwnChannel(TConnection *Connection, int Channel){
+ if(!BeginSendData(Connection)){
+ return;
+ }
+
+ if(Channel < 0 || Channel >= GetNumberOfChannels()){
+ error("SendOpenOwnChannel: Ungültiger Kanal %d.\n", Channel);
+ return;
+ }
+
+ TPlayer *Player = Connection->GetPlayer();
+ if(Player == NULL){
+ return;
+ }
+
+ SendByte(Connection, SV_CMD_OPEN_OWN_CHANNEL);
+ SendWord(Connection, (uint16)Channel);
+ SendString(Connection, GetChannelName(Channel, Player->ID));
+ FinishSendData(Connection);
+}
+
+void SendCloseChannel(TConnection *Connection, int Channel){
+ if(!BeginSendData(Connection)){
+ return;
+ }
+
+ SendByte(Connection, SV_CMD_CLOSE_CHANNEL);
+ SendWord(Connection, (uint16)Channel);
+ FinishSendData(Connection);
+}
+
+void SendMessage(TConnection *Connection, int Mode, const char *Text, ...){
+ if(Mode != TALK_ADMIN_MESSAGE
+ && Mode != TALK_EVENT_MESSAGE
+ && Mode != TALK_LOGIN_MESSAGE
+ && Mode != TALK_STATUS_MESSAGE
+ && Mode != TALK_INFO_MESSAGE
+ && Mode != TALK_FAILURE_MESSAGE){
+ error("SendMessage: Ungültiger Modus %d.\n", Mode);
+ return;
+ }
+
+ if(!BeginSendData(Connection)){
+ return;
+ }
+
+ char Buffer[4096];
+ va_list ap;
+ va_start(ap, Text);
+ vsnprintf(Buffer, sizeof(Buffer), Text, ap);
+ va_end(ap);
+
+ SendByte(Connection, SV_CMD_MESSAGE);
+ SendByte(Connection, (uint8)Mode);
+ SendString(Connection, Buffer);
+ FinishSendData(Connection);
+}
+
+void SendSnapback(TConnection *Connection){
+ if(!BeginSendData(Connection)){
+ return;
+ }
+
+ // TODO(fusion): Sometimes we check, sometimes we don't.
+ TPlayer *Player = Connection->GetPlayer();
+ if(Player == NULL){
+ return;
+ }
+
+ SendByte(Connection, SV_CMD_SNAPBACK);
+ SendByte(Connection, (uint8)Player->Direction);
+ FinishSendData(Connection);
+}
+
+void SendOutfit(TConnection *Connection){
+ if(!BeginSendData(Connection)){
+ return;
+ }
+
+ TPlayer *Player = Connection->GetPlayer();
+ if(Player == NULL){
+ return;
+ }
+
+ uint16 FirstOutfit = (Player->Sex == 1) ? 128 : 136;
+ uint16 LastOutfit = (Player->Sex == 1) ? 131 : 139;
+ if(CheckRight(Player->ID, PREMIUM_ACCOUNT)){
+ LastOutfit += 3;
+ }
+
+ SendByte(Connection, SV_CMD_OUTFIT);
+ SendOutfit(Connection, Player->OrgOutfit);
+ SendWord(Connection, FirstOutfit);
+ SendWord(Connection, LastOutfit);
+ FinishSendData(Connection);
+}
+
+void SendBuddyData(TConnection *Connection, uint32 CharacterID, const char *Name, bool Online){
+ if(!BeginSendData(Connection)){
+ return;
+ }
+
+ if(Name == NULL){
+ return;
+ }
+
+ SendByte(Connection, SV_CMD_BUDDY_DATA);
+ SendWord(Connection, CharacterID);
+ SendString(Connection, Name);
+ SendByte(Connection, (Online ? 1 : 0));
+ FinishSendData(Connection);
+}
+
+void SendBuddyStatus(TConnection *Connection, uint32 CharacterID, bool Online){
+ if(!BeginSendData(Connection)){
+ return;
+ }
+
+ if(Online){
+ SendByte(Connection, SV_CMD_BUDDY_ONLINE);
+ }else{
+ SendByte(Connection, SV_CMD_BUDDY_OFFLINE);
+ }
+ SendWord(Connection, CharacterID);
+ FinishSendData(Connection);
+}
+
+void BroadcastMessage(int Mode, const char *Text, ...){
+ char Message[1024];
+ va_list ap;
+ va_start(ap, Text);
+ vsnprintf(Message, sizeof(Message), Text, ap);
+ va_end(ap);
+
+ TConnection *Connection = GetFirstConnection();
+ while(Connection != NULL){
+ if(Connection->Live()){
+ SendMessage(Connection, Mode, Message);
+ }
+ Connection = GetNextConnection();
+ }
+}
+
+void DeleteGamemasterRequest(const char *Name){
+ if(Name == NULL){
+ error("DeleteGamemasterRequest: Name ist NULL.\n");
+ return;
+ }
+
+ TConnection *Connection = GetFirstConnection();
+ while(Connection != NULL){
+ if(Connection->Live()){
+ TPlayer *Player = Connection->GetPlayer();
+ if(Player != NULL && ChannelSubscribed(CHANNEL_RULEVIOLATIONS, Player->ID)){
+ SendDeleteRequest(Connection, Name);
+ }
+ }
+ Connection = GetNextConnection();
+ }
+}
+
+void InitSending(void){
+ FirstSendingConnection = NULL;
+}
+
+void ExitSending(void){
+ // no-op
+}