#include "connections.hh" #include "houses.hh" #include "info.hh" #include "writer.hh" #include bool CommandAllowed(TConnection *Connection, int Command){ if(Connection == NULL){ error("CommandAllowed: Connection ist NULL.\n"); return false; } if(Connection->State == CONNECTION_GAME){ return Command != CL_CMD_LOGIN_REQUEST && Command != CL_CMD_LOGIN; }else if(Connection->State == CONNECTION_DEAD || Connection->State == CONNECTION_LOGOUT){ return Command == CL_CMD_LOGOUT || Command == CL_CMD_PING || Command == CL_CMD_BUG_REPORT || Command == CL_CMD_ERROR_FILE_ENTRY; }else{ error("CommandAllowed: Ungültiger Verbindungsstatus %d bei Kommando %d.\n", Connection->State, Command); return false; } } bool CheckSpecialCoordinates(int Command, int x, int y, int z, bool AllowInventoryAny){ if(x == 0xFFFF){ // SPECIAL_COORDINATE ? if(y == INVENTORY_ANY){ return AllowInventoryAny; }else if((y >= INVENTORY_FIRST && y <= INVENTORY_LAST) || (y >= CONTAINER_FIRST && y <= CONTAINER_LAST)){ return true; }else{ print(3, "CheckSpecialCoordinates: Ungültige Koordinaten" " [%d,%d,%d] bei Kommando %d.\n", x, y, z, Command); return false; } }else{ return IsOnMap(x, y, z); } } bool CheckVisibility(int Command, TConnection *Connection, int x, int y, int z){ if(Connection == NULL){ error("CheckVisibility: Connection ist NULL.\n"); return false; } bool Result = Connection->IsVisible(x, y, z); if(!Result){ int PlayerX, PlayerY, PlayerZ; Connection->GetPosition(&PlayerX, &PlayerY, &PlayerZ); print(3, "CheckVisibility: Feld [%d,%d,%d] ist von [%d,%d,%d]" " aus nicht sichtbar bei Kommando %d.\n", x, y, z, PlayerX, PlayerY, PlayerZ, Command); } return Result; } bool CheckObjectType(int Command, int TypeID){ bool Result = ObjectTypeExists(TypeID); if(!Result){ print(3, "CheckObjectType: Ungültiger Objekttyp %d bei Kommando %d.\n", TypeID, Command); } return Result; } ObjectType GetObjectType(int Command, int TypeID){ // TODO(fusion): Isn't this already done by `ObjectType::setTypeID` ? ObjectType ObjType(0); if(CheckObjectType(Command, TypeID)){ ObjType.setTypeID(TypeID); } return ObjType; } void CQuitGame(TConnection *Connection, TReadBuffer *Buffer){ TPlayer *Player = Connection->GetPlayer(); if(Player == NULL){ Connection->Logout(0, true); return; } switch(Player->LogoutPossible()){ case 0:{ Player->ToDoClear(); Connection->Logout(0, true); break; } case 1:{ SendMessage(Connection, TALK_FAILURE_MESSAGE, "You may not logout during or immediately after a fight!"); break; } case 2:{ SendMessage(Connection, TALK_FAILURE_MESSAGE, "You may not logout here!"); break; } default:{ // TODO(fusion): The original function would also logout the player // on this case which feels weird, specially when you can't have any // other result from `LogoutPossible`. error("CQuitGame: Ungültiger Rückgabewert von LogoutPossible.\n"); break; } } } void CPing(TConnection *Connection, TReadBuffer *Buffer){ // NOTE(fusion): Its a no-op because it's already encoded in `Connection::TimeStamp`. } void CGoPath(TConnection *Connection, TReadBuffer *Buffer){ // TODO(fusion): Sometimes we check, sometimes we don't. It should always be // valid since these functions are all called from within `ReceiveData` which // also checks it. if(Connection == NULL){ error("CGoPath: Connection ist NULL.\n"); return; } TPlayer *Player = Connection->GetPlayer(); if(Player == NULL){ return; } if(Player->ToDoClear()){ SendSnapback(Connection); } int Steps = (int)Buffer->readByte(); TToDoEntry TD = {}; TD.Code = TDGo; TD.Go.x = Player->posx; TD.Go.y = Player->posy; TD.Go.z = Player->posz; try{ for(int i = 0; i < Steps; i += 1){ // NOTE(fusion): For whatever reason these directions are not the same // we use internally. switch(Buffer->readByte()){ case 1: TD.Go.x += 1; break; // EAST case 2: TD.Go.x += 1; TD.Go.y -= 1; break; // NORTHEAST case 3: TD.Go.y -= 1; break; // NORTH case 4: TD.Go.x -= 1; TD.Go.y -= 1; break; // NORTHWEST case 5: TD.Go.x -= 1; break; // WEST case 6: TD.Go.x -= 1; TD.Go.y += 1; break; // SOUTHWEST case 7: TD.Go.y += 1; break; // SOUTH case 8: TD.Go.x += 1; TD.Go.y += 1; break; // SOUTHEAST default:{ continue; } } Player->ToDoAdd(TD); } Player->ToDoStart(); }catch(RESULT r){ SendResult(Connection, r); Player->ToDoYield(); } } void CGoDirection(TConnection *Connection, int OffsetX, int OffsetY){ if(Connection == NULL){ error("CGoDirection: Connection ist NULL.\n"); return; } TPlayer *Player = Connection->GetPlayer(); if(Player == NULL){ return; } try{ if(Player->ToDoClear()){ SendSnapback(Connection); } TToDoEntry TD = {}; TD.Code = TDGo; TD.Go.x = Player->posx + OffsetX; TD.Go.y = Player->posy + OffsetY; TD.Go.z = Player->posz; Player->ToDoAdd(TD); Player->ToDoStart(); }catch(RESULT r){ SendResult(Connection, r); Player->ToDoYield(); } } void CGoStop(TConnection *Connection, TReadBuffer *Buffer){ if(Connection == NULL){ error("CGoStop: Connection ist NULL.\n"); return; } TPlayer *Player = Connection->GetPlayer(); if(Player != NULL){ Player->ToDoStop(); } } void CRotate(TConnection *Connection, int Direction){ if(Connection == NULL){ error("CRotate: Connection ist NULL.\n"); return; } TPlayer *Player = Connection->GetPlayer(); if(Player == NULL){ return; } try{ Player->ToDoRotate(Direction); Player->ToDoStart(); }catch(RESULT r){ SendResult(Connection, r); Player->ToDoYield(); } } void CMoveObject(TConnection *Connection, TReadBuffer *Buffer){ if(Connection == NULL){ error("CMoveObject: Connection ist NULL.\n"); return; } // TODO(fusion): I'm just following the convention being used. There is a // comment in `ReceiveData` but the fact that there is only one command in // a packet makes it unnecessary to "consume" all command's parameters. TPlayer *Player = Connection->GetPlayer(); if(Player == NULL){ return; } int OrigX = Buffer->readWord(); int OrigY = Buffer->readWord(); int OrigZ = Buffer->readByte(); int TypeID = Buffer->readWord(); uint8 RNum = Buffer->readByte(); int DestX = Buffer->readWord(); int DestY = Buffer->readWord(); int DestZ = Buffer->readByte(); uint8 Count = Buffer->readByte(); ObjectType Type = GetObjectType(CL_CMD_MOVE_OBJECT, TypeID); if(Type.isMapContainer() || (Type.getFlag(CUMULATIVE) && Count == 0)){ return; } if(!CheckSpecialCoordinates(CL_CMD_MOVE_OBJECT, OrigX, OrigY, OrigZ, false) || !CheckSpecialCoordinates(CL_CMD_MOVE_OBJECT, DestX, DestY, DestZ, true)){ return; } if((OrigX != 0xFFFF && !CheckVisibility(CL_CMD_MOVE_OBJECT, Connection, OrigX, OrigY, OrigZ)) || (DestX != 0xFFFF && !CheckVisibility(CL_CMD_MOVE_OBJECT, Connection, DestX, DestY, DestZ))){ return; } // TODO(fusion): `RNum` is only used inside `ToDoMove` > `GetObject` to index // into containers. For some reason I thought the Z coordinate was also used // for that. Perhaps they're set to the same value. if(OrigX != 0xFFFF){ RNum = 1; } try{ Player->ToDoMove(OrigX, OrigY, OrigZ, Type, RNum, DestX, DestY, DestZ, Count); Player->ToDoStart(); }catch(RESULT r){ if(r != NOERROR){ SendResult(Connection, r); Player->ToDoYield(); } } } void CTradeObject(TConnection *Connection, TReadBuffer *Buffer){ if(Connection == NULL){ error("CTradeObject: Connection ist NULL.\n"); return; } TPlayer *Player = Connection->GetPlayer(); if(Player == NULL){ return; } int ObjX = Buffer->readWord(); int ObjY = Buffer->readWord(); int ObjZ = Buffer->readByte(); int TypeID = Buffer->readWord(); uint8 RNum = Buffer->readByte(); uint32 TradePartner = Buffer->readQuad(); ObjectType Type = GetObjectType(CL_CMD_TRADE_OBJECT, TypeID); if(Type.isMapContainer()){ return; } if(!CheckSpecialCoordinates(CL_CMD_TRADE_OBJECT, ObjX, ObjY, ObjZ, false)){ return; } if(ObjX != 0xFFFF && !CheckVisibility(CL_CMD_TRADE_OBJECT, Connection, ObjX, ObjY, ObjZ)){ return; } // TODO(fusion): Same as `CMoveObject`. if(ObjX != 0xFFFF){ RNum = 1; } try{ Player->ToDoTrade(ObjX, ObjY, ObjZ, Type, RNum, TradePartner); Player->ToDoStart(); }catch(RESULT r){ if(r != NOERROR){ SendResult(Connection, r); Player->ToDoYield(); } } } void CInspectTrade(TConnection *Connection, TReadBuffer *Buffer){ if(Connection == NULL){ error("CInspectTrade: Connection ist NULL.\n"); return; } TPlayer *Player = Connection->GetPlayer(); if(Player == NULL){ return; } bool OwnOffer = (Buffer->readByte() == 0); int Position = Buffer->readByte(); Object Obj = Player->InspectTrade(OwnOffer, Position); if(Obj.exists()){ try{ Look(Player->ID, Obj); }catch(RESULT r){ SendResult(Connection, r); } } } void CAcceptTrade(TConnection *Connection, TReadBuffer *Buffer){ if(Connection == NULL){ error("CAcceptTrade: Connection ist NULL.\n"); return; } TPlayer *Player = Connection->GetPlayer(); if(Player != NULL){ Player->AcceptTrade(); } } void CRejectTrade(TConnection *Connection, TReadBuffer *Buffer){ if(Connection == NULL){ error("CRejectTrade: Connection ist NULL.\n"); return; } TPlayer *Player = Connection->GetPlayer(); if(Player != NULL){ Player->RejectTrade(); } } void CUseObject(TConnection *Connection, TReadBuffer *Buffer){ if(Connection == NULL){ error("CUseObject: Connection ist NULL.\n"); return; } TPlayer *Player = Connection->GetPlayer(); if(Player == NULL){ return; } int ObjX = Buffer->readWord(); int ObjY = Buffer->readWord(); int ObjZ = Buffer->readByte(); int TypeID = Buffer->readWord(); uint8 RNum = Buffer->readByte(); uint8 Dummy = Buffer->readByte(); ObjectType Type = GetObjectType(CL_CMD_USE_OBJECT, TypeID); if(Type.isMapContainer() || Type.getFlag(MULTIUSE)){ return; } if(Type.getFlag(CONTAINER) && Dummy >= NARRAY(TPlayer::OpenContainer)){ return; } if(!CheckSpecialCoordinates(CL_CMD_USE_OBJECT, ObjX, ObjY, ObjZ, false)){ return; } if(ObjX != 0xFFFF && !CheckVisibility(CL_CMD_USE_OBJECT, Connection, ObjX, ObjY, ObjZ)){ return; } try{ Player->ToDoWait(100); Player->ToDoUse(1, ObjX, ObjY, ObjZ, Type, RNum, Dummy, 0, 0, 0, 0, 0); Player->ToDoStart(); }catch(RESULT r){ SendResult(Connection, r); Player->ToDoClear(); Player->ToDoYield(); } } void CUseTwoObjects(TConnection *Connection, TReadBuffer *Buffer){ if(Connection == NULL){ error("CUseTwoObjects: Connection ist NULL.\n"); return; } TPlayer *Player = Connection->GetPlayer(); if(Player == NULL){ return; } int ObjX1 = Buffer->readWord(); int ObjY1 = Buffer->readWord(); int ObjZ1 = Buffer->readByte(); int TypeID1 = Buffer->readWord(); uint8 RNum1 = Buffer->readByte(); int ObjX2 = Buffer->readWord(); int ObjY2 = Buffer->readWord(); int ObjZ2 = Buffer->readByte(); int TypeID2 = Buffer->readWord(); uint8 RNum2 = Buffer->readByte(); ObjectType Type1 = GetObjectType(CL_CMD_USE_TWO_OBJECTS, TypeID1); ObjectType Type2 = GetObjectType(CL_CMD_USE_TWO_OBJECTS, TypeID2); if(Type1.isMapContainer() || Type2.isMapContainer() || !Type1.getFlag(MULTIUSE)){ return; } if(!CheckSpecialCoordinates(CL_CMD_USE_TWO_OBJECTS, ObjX1, ObjY1, ObjZ1, false) || !CheckSpecialCoordinates(CL_CMD_USE_TWO_OBJECTS, ObjX2, ObjY2, ObjZ2, false)){ return; } if((ObjX1 != 0xFFFF && !CheckVisibility(CL_CMD_USE_TWO_OBJECTS, Connection, ObjX1, ObjY1, ObjZ1)) || (ObjX2 != 0xFFFF && !CheckVisibility(CL_CMD_USE_TWO_OBJECTS, Connection, ObjX2, ObjY2, ObjZ2))){ return; } try{ Player->ToDoWait(100); Player->ToDoUse(2, ObjX1, ObjY1, ObjZ1, Type1, RNum1, 0, ObjX2, ObjY2, ObjZ2, Type2, RNum2); Player->ToDoStart(); }catch(RESULT r){ SendResult(Connection, r); Player->ToDoClear(); Player->ToDoYield(); } } void CUseOnCreature(TConnection *Connection, TReadBuffer *Buffer){ if(Connection == NULL){ error("CUseOnCreature: Connection ist NULL.\n"); return; } TPlayer *Player = Connection->GetPlayer(); if(Player == NULL){ return; } int ObjX = Buffer->readWord(); int ObjY = Buffer->readWord(); int ObjZ = Buffer->readByte(); int TypeID = Buffer->readWord(); uint8 RNum = Buffer->readByte(); uint32 TargetID = Buffer->readQuad(); // TODO(fusion): Check `Type.getFlag(MULTIUSE)`? ObjectType Type = GetObjectType(CL_CMD_USE_ON_CREATURE, TypeID); if(Type.isMapContainer()){ return; } if(!CheckSpecialCoordinates(CL_CMD_USE_ON_CREATURE, ObjX, ObjY, ObjZ, false)){ return; } if(ObjX != 0xFFFF && !CheckVisibility(CL_CMD_USE_ON_CREATURE, Connection, ObjX, ObjY, ObjZ)){ return; } TCreature *Target = GetCreature(TargetID); if(Target == NULL || Target->Type == PLAYER){ return; } if(!CheckVisibility(CL_CMD_USE_ON_CREATURE, Connection, Target->posx, Target->posy, Target->posz)){ return; } // TODO(fusion): Isn't this already checked with `CheckVisibility`? It also // checks against the terminal offset which is not a distance semantically. int DistanceX = std::abs(Target->posx - Player->posx); int DistanceY = std::abs(Target->posy - Player->posy); if(DistanceX >= Connection->TerminalOffsetX || DistanceY >= Connection->TerminalOffsetY){ SendResult(Connection, OUTOFRANGE); return; } Object Obj = GetObject(Player->ID, ObjX, ObjY, ObjZ, RNum, Type); if(!Obj.exists() || !Target->CrObject.exists()){ SendResult(Connection, NOTACCESSIBLE); return; } try{ Player->ToDoWait(100); Player->ToDoUse(2, Obj, Target->CrObject); Player->ToDoStart(); }catch(RESULT r){ SendResult(Connection, r); Player->ToDoClear(); Player->ToDoYield(); } } void CTurnObject(TConnection *Connection, TReadBuffer *Buffer){ if(Connection == NULL){ error("CTurnObject: Connection ist NULL.\n"); return; } TPlayer *Player = Connection->GetPlayer(); if(Player == NULL){ return; } int ObjX = Buffer->readWord(); int ObjY = Buffer->readWord(); int ObjZ = Buffer->readByte(); int TypeID = Buffer->readWord(); uint8 RNum = Buffer->readByte(); // TODO(fusion): Check `Type.getFlag(ROTATE)`? ObjectType Type = GetObjectType(CL_CMD_TURN_OBJECT, TypeID); if(Type.isMapContainer()){ return; } if(!CheckSpecialCoordinates(CL_CMD_TURN_OBJECT, ObjX, ObjY, ObjZ, false)){ return; } if(ObjX != 0xFFFF && !CheckVisibility(CL_CMD_TURN_OBJECT, Connection, ObjX, ObjY, ObjZ)){ return; } try{ Player->ToDoWait(100); Player->ToDoTurn(ObjX, ObjY, ObjZ, Type, RNum); Player->ToDoStart(); }catch(RESULT r){ SendResult(Connection, r); Player->ToDoClear(); Player->ToDoYield(); } } void CCloseContainer(TConnection *Connection, TReadBuffer *Buffer){ if(Connection == NULL){ error("CCloseContainer: Connection ist NULL.\n"); return; } TPlayer *Player = Connection->GetPlayer(); if(Player == NULL){ return; } int ContainerNr = Buffer->readByte(); if(ContainerNr < NARRAY(TPlayer::OpenContainer)){ Player->SetOpenContainer(ContainerNr, NONE); SendCloseContainer(Connection, ContainerNr); } } void CUpContainer(TConnection *Connection, TReadBuffer *Buffer){ if(Connection == NULL){ error("CUpContainer: Connection ist NULL.\n"); return; } TPlayer *Player = Connection->GetPlayer(); if(Player == NULL){ return; } int ContainerNr = Buffer->readByte(); if(ContainerNr < NARRAY(TPlayer::OpenContainer)){ Object Con = Player->GetOpenContainer(ContainerNr); if(Con != NONE){ Object Up = Con.getContainer(); ObjectType UpType = Up.getObjectType(); if(!UpType.isMapContainer() && !UpType.isBodyContainer()){ Player->SetOpenContainer(ContainerNr, Up); SendContainer(Connection, ContainerNr); }else{ Player->SetOpenContainer(ContainerNr, NONE); SendCloseContainer(Connection, ContainerNr); } } } } void CEditText(TConnection *Connection, TReadBuffer *Buffer){ if(Connection == NULL){ error("CEditText: Connection ist NULL.\n"); return; } TPlayer *Player = Connection->GetPlayer(); if(Player == NULL){ return; } Object Obj = Object(Buffer->readQuad()); if(!Obj.exists()){ SendResult(Connection, NOTACCESSIBLE); return; } ObjectType ObjType = Obj.getObjectType(); if(!ObjType.getFlag(WRITE) && (!ObjType.getFlag(WRITEONCE) || Obj.getAttribute(TEXTSTRING) != 0)){ SendResult(Connection, NOTACCESSIBLE); return; } char Text[4096]; Buffer->readString(Text, sizeof(Text)); int TextLength = (int)strlen(Text); int MaxLength = (ObjType.getFlag(WRITE) ? (int)ObjType.getAttribute(MAXLENGTH) : (int)ObjType.getAttribute(MAXLENGTHONCE)); if(TextLength >= MaxLength){ SendResult(Connection, NOROOM); return; } try{ EditText(Player->ID, Obj, Text); }catch(RESULT r){ SendResult(Connection, r); } } void CEditList(TConnection *Connection, TReadBuffer *Buffer){ if(Connection == NULL){ error("CEditList: Connection ist NULL.\n"); return; } TPlayer *Player = Connection->GetPlayer(); if(Player == NULL){ return; } char Text[4096]; int Type = Buffer->readByte(); uint32 ID = Buffer->readQuad(); Buffer->readString(Text, sizeof(Text)); if(Type == GUESTLIST){ ChangeGuests((uint16)ID, Player, Text); }else if(Type == SUBOWNERLIST){ ChangeSubowners((uint16)ID, Player, Text); }else if(Type == DOORLIST){ Object Door = Object(ID); if(!Door.exists()){ SendResult(Connection, NOTACCESSIBLE); return; } ObjectType DoorType = Door.getObjectType(); if(!DoorType.getFlag(NAMEDOOR) || !DoorType.getFlag(TEXT)){ SendResult(Connection, NOTACCESSIBLE); return; } ChangeNameDoor(Door, Player, Text); }else{ error("CEditList: Unbekannter Typ %d.\n", Type); } } void CLookAtPoint(TConnection *Connection, TReadBuffer *Buffer){ if(Connection == NULL){ error("CLookAtPoint: Connection ist NULL.\n"); return; } TPlayer *Player = Connection->GetPlayer(); if(Player == NULL){ return; } int ObjX = Buffer->readWord(); int ObjY = Buffer->readWord(); int ObjZ = Buffer->readByte(); if(!CheckSpecialCoordinates(CL_CMD_LOOK_AT_POINT, ObjX, ObjY, ObjZ, false)){ return; } if(ObjX != 0xFFFF && !CheckVisibility(CL_CMD_LOOK_AT_POINT, Connection, ObjX, ObjY, ObjZ)){ return; } int RNum = (ObjX == 0xFFFF ? ObjZ : -1); Object Obj = GetObject(Player->ID, ObjX, ObjY, ObjZ, RNum, 0); if(Obj.exists()){ try{ Look(Player->ID, Obj); }catch(RESULT r){ SendResult(Connection, r); } } } void CTalk(TConnection *Connection, TReadBuffer *Buffer){ if(Connection == NULL){ error("CTalk: Connection ist NULL.\n"); return; } TPlayer *Player = Connection->GetPlayer(); if(Player == NULL){ print(3, "CTalk: Der Verbindung ist kein Spieler zugeordnet.\n"); return; } int Mode = Buffer->readByte(); if(Mode != TALK_SAY && Mode != TALK_WHISPER && Mode != TALK_YELL && Mode != TALK_PRIVATE_MESSAGE && Mode != TALK_CHANNEL_CALL && Mode != TALK_GAMEMASTER_REQUEST && Mode != TALK_GAMEMASTER_ANSWER && Mode != TALK_PLAYER_ANSWER && Mode != TALK_GAMEMASTER_BROADCAST && Mode != TALK_GAMEMASTER_CHANNELCALL && Mode != TALK_GAMEMASTER_MESSAGE && Mode != TALK_ANONYMOUS_BROADCAST && Mode != TALK_ANONYMOUS_CHANNELCALL && Mode != TALK_ANONYMOUS_MESSAGE){ print(3, "CTalk: Unbekannter Sprechmodus %d.\n", Mode); return; } char Addressee[30] = {}; if(Mode == TALK_PRIVATE_MESSAGE || Mode == TALK_GAMEMASTER_ANSWER || Mode == TALK_GAMEMASTER_MESSAGE || Mode == TALK_ANONYMOUS_MESSAGE){ Buffer->readString(Addressee, sizeof(Addressee)); if(Addressee[0] == 0){ print(3, "CTalk: Adressat nicht angegeben.\n"); return; } } int Channel = 0; if(Mode == TALK_CHANNEL_CALL || Mode == TALK_GAMEMASTER_CHANNELCALL || Mode == TALK_ANONYMOUS_CHANNELCALL){ Channel = Buffer->readWord(); if(Channel >= GetNumberOfChannels()){ print(3, "CTalk: Ungültiger Kanal %d.\n", Channel); return; } snprintf(Addressee, sizeof(Addressee), "%d", Channel); } char Text[256]; Buffer->readString(Text, sizeof(Text)); if(Text[0] == 0){ print(3, "CTalk: Kein Text.\n"); return; } if(findFirst(Text, '\n') != NULL){ error("CTalk: %s verwendet Newlines (%d,%s)\n", Player->Name, Mode, Text); return; } if(Mode == TALK_GAMEMASTER_ANSWER && !CheckRight(Player->ID, READ_GAMEMASTER_CHANNEL)){ Mode = TALK_PRIVATE_MESSAGE; }else if(Mode == TALK_GAMEMASTER_BROADCAST && !CheckRight(Player->ID, GAMEMASTER_BROADCAST)){ Mode = TALK_SAY; }else if(Mode == TALK_GAMEMASTER_CHANNELCALL && !CheckRight(Player->ID, GAMEMASTER_BROADCAST)){ Mode = TALK_CHANNEL_CALL; }else if(Mode == TALK_GAMEMASTER_MESSAGE && !CheckRight(Player->ID, GAMEMASTER_BROADCAST)){ Mode = TALK_PRIVATE_MESSAGE; }else if(Mode == TALK_ANONYMOUS_BROADCAST && !CheckRight(Player->ID, ANONYMOUS_BROADCAST)){ Mode = TALK_SAY; }else if(Mode == TALK_ANONYMOUS_CHANNELCALL && !CheckRight(Player->ID, ANONYMOUS_BROADCAST)){ Mode = TALK_CHANNEL_CALL; }else if(Mode == TALK_ANONYMOUS_MESSAGE && !CheckRight(Player->ID, ANONYMOUS_BROADCAST)){ Mode = TALK_PRIVATE_MESSAGE; } if(Mode == TALK_YELL && Player->Skills[SKILL_LEVEL]->Get() <= 1){ SendMessage(Connection, TALK_FAILURE_MESSAGE, "You may not yell as long as you are on level 1."); return; } if(Mode == TALK_CHANNEL_CALL && Player->Skills[SKILL_LEVEL]->Get() <= 1){ SendMessage(Connection, TALK_FAILURE_MESSAGE, "You may not speak into channels as long as you are on level 1."); return; } if(Mode == TALK_CHANNEL_CALL || Mode == TALK_GAMEMASTER_CHANNELCALL || Mode == TALK_ANONYMOUS_CHANNELCALL){ if(Channel == CHANNEL_RULEVIOLATIONS){ print(3, "ChannelCall für GM-Request-Queue.\n"); return; } if(!ChannelSubscribed(Channel, Player->ID)){ Addressee[0] = 0; Mode = TALK_SAY; } } if(Mode == TALK_CHANNEL_CALL && Channel == CHANNEL_HELP && CheckRight(Player->ID, HIGHLIGHT_HELP_CHANNEL)){ Mode = TALK_HIGHLIGHT_CHANNELCALL; Player->TutorActivities += 1; print(3, "Aktvitätspunkt für %s.\n", Player->Name); } if(Mode == TALK_PLAYER_ANSWER){ if(Player->Request == 0){ return; } if(Player->RequestProcessingGamemaster == 0){ SendMessage(Connection, TALK_FAILURE_MESSAGE, "Please wait until your request is answered."); return; } TPlayer *Gamemaster = GetPlayer(Player->RequestProcessingGamemaster); if(Gamemaster == NULL){ SendCloseRequest(Connection); Player->Request = 0; return; } strcpy(Addressee, Gamemaster->Name); } if(Mode == TALK_GAMEMASTER_REQUEST){ if(Player->Request != 0){ SendMessage(Connection, TALK_FAILURE_MESSAGE, "You have already submitted a request." " Please wait until it is answered."); return; } Player->Request = AddDynamicString(Text); Player->RequestTimestamp = RoundNr; Player->RequestProcessingGamemaster = 0; CreateGamemasterRequest(Player->Name, Text); }else{ try{ Player->ToDoTalk(Mode, Addressee, Text, true); Player->ToDoStart(); }catch(RESULT r){ SendResult(Connection, r); Player->ToDoYield(); } } } void CGetChannels(TConnection *Connection, TReadBuffer *Buffer){ if(Connection == NULL){ error("CGetChannels: Connection ist NULL.\n"); return; } SendChannels(Connection); } void CJoinChannel(TConnection *Connection, TReadBuffer *Buffer){ if(Connection == NULL){ error("CJoinChannel: Connection ist NULL.\n"); return; } TPlayer *Player = Connection->GetPlayer(); if(Player == NULL){ return; } int Channel = Buffer->readWord(); if(Channel < GetNumberOfChannels()){ try{ bool OwnChannel = JoinChannel(Channel, Player->ID); if(Channel == CHANNEL_RULEVIOLATIONS){ SendOpenRequestQueue(Connection); SendExistingRequests(Connection); }else if(OwnChannel){ SendOpenOwnChannel(Connection, Channel); }else{ SendOpenChannel(Connection, Channel); } }catch(RESULT r){ print(3, "Spieler darf Kanal nicht hören.\n"); } } } void CLeaveChannel(TConnection *Connection, TReadBuffer *Buffer){ if(Connection == NULL){ error("CLeaveChannel: Connection ist NULL.\n"); return; } TPlayer *Player = Connection->GetPlayer(); if(Player == NULL){ return; } int Channel = Buffer->readWord(); if(Channel < GetNumberOfChannels()){ LeaveChannel(Channel, Player->ID, true); } } void CPrivateChannel(TConnection *Connection, TReadBuffer *Buffer){ if(Connection == NULL){ error("CPrivateChannel: Connection ist NULL.\n"); return; } TPlayer *Player = Connection->GetPlayer(); if(Player == NULL){ return; } char Name[30]; Buffer->readString(Name, sizeof(Name)); if(Name[0] == 0){ return; } uint32 CharacterID = GetCharacterID(Name); if(CharacterID == 0){ SendResult(Connection, PLAYERNOTEXISTING); return; } if(CharacterID == Player->ID){ SendMessage(Connection, TALK_FAILURE_MESSAGE, "You cannot set up a private message channel with yourself."); return; } SendPrivateChannel(Connection, GetCharacterName(Name)); } void CProcessRequest(TConnection *Connection, TReadBuffer *Buffer){ if(Connection == NULL){ error("CProcessRequest: Connection ist NULL.\n"); return; } TPlayer *Player = Connection->GetPlayer(); if(Player == NULL || !ChannelAvailable(CHANNEL_RULEVIOLATIONS, Player->ID)){ return; } char Name[30]; Buffer->readString(Name, sizeof(Name)); if(Name[0] == 0){ return; } TPlayer *Other; if(IdentifyPlayer(Name, true, true, &Other) != 0){ SendMessage(Connection, TALK_FAILURE_MESSAGE, "Player is not online any more."); SendFinishRequest(Connection, Name); return; } if(Other->Request == 0 || Other->RequestProcessingGamemaster != 0){ SendMessage(Connection, TALK_FAILURE_MESSAGE, "Request has already been processed."); SendFinishRequest(Connection, Name); return; } Other->RequestProcessingGamemaster = Player->ID; DeleteGamemasterRequest(Other->Name); } void CRemoveRequest(TConnection *Connection, TReadBuffer *Buffer){ if(Connection == NULL){ error("CRemoveRequest: Connection ist NULL.\n"); return; } TPlayer *Player = Connection->GetPlayer(); if(Player == NULL || !ChannelAvailable(CHANNEL_RULEVIOLATIONS, Player->ID)){ return; } char Name[30]; Buffer->readString(Name, sizeof(Name)); if(Name[0] == 0){ return; } TPlayer *Other; if(IdentifyPlayer(Name, true, true, &Other) != 0){ SendMessage(Connection, TALK_FAILURE_MESSAGE, "Player is not online any more."); SendFinishRequest(Connection, Name); return; } if(Other->Request != 0){ if(Other->RequestProcessingGamemaster == Player->ID){ Other->Request = 0; SendCloseRequest(Other->Connection); }else if(Other->RequestProcessingGamemaster == 0){ DeleteGamemasterRequest(Other->Name); } } } void CCancelRequest(TConnection *Connection, TReadBuffer *Buffer){ if(Connection == NULL){ error("CCancelRequest: Connection ist NULL.\n"); return; } // TODO(fusion): Does this mean regular players can't cancel their requests? TPlayer *Player = Connection->GetPlayer(); if(Player == NULL || !ChannelAvailable(CHANNEL_RULEVIOLATIONS, Player->ID)){ return; } if(Player->Request != 0){ Player->Request = 0; if(Player->RequestProcessingGamemaster != 0){ TPlayer *Gamemaster = GetPlayer(Player->RequestProcessingGamemaster); if(Gamemaster != NULL){ SendFinishRequest(Gamemaster->Connection, Player->Name); } }else{ DeleteGamemasterRequest(Player->Name); } } } void CSetTactics(TConnection *Connection, TReadBuffer *Buffer){ if(Connection == NULL){ error("CSetTactics: Connection ist NULL.\n"); return; } TPlayer *Player = Connection->GetPlayer(); if(Player == NULL){ return; } int AttackMode = Buffer->readByte(); int ChaseMode = Buffer->readByte(); int SecureMode = Buffer->readByte(); if(AttackMode != ATTACK_MODE_OFFENSIVE && AttackMode != ATTACK_MODE_BALANCED && AttackMode != ATTACK_MODE_DEFENSIVE){ print(3, "CSetTactics: Ungültiger Angriffsmodus %d.\n", AttackMode); return; } if(ChaseMode != CHASE_MODE_NONE && ChaseMode != CHASE_MODE_CLOSE){ print(3, "CSetTactics: Ungültiger Verfolgungsmodus %d.\n", ChaseMode); return; } if(SecureMode != SECURE_MODE_DISABLED && SecureMode != SECURE_MODE_ENABLED){ print(3, "CSetTactics: Ungültiger Sicherheitsmodus %d.\n", SecureMode); return; } Player->Combat.SetAttackMode((uint8)AttackMode); Player->Combat.SetChaseMode((uint8)ChaseMode); Player->Combat.SetSecureMode((uint8)SecureMode); } void CAttack(TConnection *Connection, TReadBuffer *Buffer, bool Follow){ if(Connection == NULL){ error("CAttack: Connection ist NULL.\n"); return; } TPlayer *Player = Connection->GetPlayer(); if(Player == NULL){ return; } uint32 TargetID = Buffer->readQuad(); try{ Player->Combat.SetAttackDest(TargetID, Follow); Player->ToDoAttack(); Player->ToDoStart(); }catch(RESULT r){ if(r != NOERROR){ SendResult(Connection, r); Player->ToDoYield(); } } } void CInviteToParty(TConnection *Connection, TReadBuffer *Buffer){ if(Connection == NULL){ error("CInviteToParty: Connection ist NULL.\n"); return; } TPlayer *Player = Connection->GetPlayer(); if(Player == NULL){ return; } uint32 GuestID = Buffer->readQuad(); TCreature *Guest = GetCreature(GuestID); if(Guest == NULL){ return; } if(!CheckVisibility(CL_CMD_INVITE_TO_PARTY, Connection, Guest->posx, Guest->posy, Guest->posz)){ return; } InviteToParty(Player->ID, GuestID); } void CJoinParty(TConnection *Connection, TReadBuffer *Buffer){ if(Connection == NULL){ error("CJoinParty: Connection ist NULL.\n"); return; } TPlayer *Player = Connection->GetPlayer(); if(Player == NULL){ return; } uint32 HostID = Buffer->readQuad(); JoinParty(Player->ID, HostID); } void CRevokeInvitation(TConnection *Connection, TReadBuffer *Buffer){ if(Connection == NULL){ error("CRevokeInvitation: Connection ist NULL.\n"); return; } TPlayer *Player = Connection->GetPlayer(); if(Player == NULL){ return; } if(Player->GetPartyLeader(false) == 0){ print(3, "Spieler ist kein Mitglied einer Jagdgruppe.\n"); return; } uint32 GuestID = Buffer->readQuad(); RevokeInvitation(Player->ID, GuestID); } void CPassLeadership(TConnection *Connection, TReadBuffer *Buffer){ if(Connection == NULL){ error("CPassLeadership: Connection ist NULL.\n"); return; } TPlayer *Player = Connection->GetPlayer(); if(Player == NULL){ return; } if(Player->GetPartyLeader(false) == 0){ print(3, "Spieler ist kein Mitglied einer Jagdgruppe.\n"); return; } uint32 NewLeaderID = Buffer->readQuad(); PassLeadership(Player->ID, NewLeaderID); } void CLeaveParty(TConnection *Connection, TReadBuffer *Buffer){ if(Connection == NULL){ error("CLeaveParty: Connection ist NULL.\n"); return; } TPlayer *Player = Connection->GetPlayer(); if(Player == NULL){ return; } if(Player->GetPartyLeader(false) == 0){ print(3, "Spieler ist kein Mitglied einer Jagdgruppe.\n"); return; } LeaveParty(Player->ID, false); } void COpenChannel(TConnection *Connection, TReadBuffer *Buffer){ if(Connection == NULL){ error("COpenChannel: Connection ist NULL.\n"); return; } TPlayer *Player = Connection->GetPlayer(); if(Player == NULL){ return; } try{ OpenChannel(Player->ID); }catch(RESULT r){ SendResult(Connection, r); } } void CInviteToChannel(TConnection *Connection, TReadBuffer *Buffer){ if(Connection == NULL){ error("CInviteToChannel: Connection ist NULL.\n"); return; } TPlayer *Player = Connection->GetPlayer(); if(Player == NULL){ return; } char Name[30]; Buffer->readString(Name, sizeof(Name)); if(Name[0] == 0){ print(2, "CInviteToChannel: Kein Name angegeben.\n"); return; } try{ InviteToChannel(Player->ID, Name); }catch(RESULT r){ SendResult(Connection, r); } } void CExcludeFromChannel(TConnection *Connection, TReadBuffer *Buffer){ if(Connection == NULL){ error("CExcludeFromChannel: Connection ist NULL.\n"); return; } TPlayer *Player = Connection->GetPlayer(); if(Player == NULL){ return; } // TODO(fusion): The original function didn check if name was empty but it // is probably a good idea. char Name[30]; Buffer->readString(Name, sizeof(Name)); if(Name[0] == 0){ print(2, "CInviteToChannel: Kein Name angegeben.\n"); return; } try{ ExcludeFromChannel(Player->ID, Name); }catch(RESULT r){ SendResult(Connection, r); } } void CCancel(TConnection *Connection, TReadBuffer *Buffer){ if(Connection == NULL){ error("CCancel: Connection ist NULL.\n"); return; } TPlayer *Player = Connection->GetPlayer(); if(Player == NULL){ return; } try{ Player->Combat.StopAttack(0); if(Player->ToDoClear()){ SendSnapback(Connection); } }catch(RESULT r){ // no-op } Player->ToDoYield(); } void CRefreshField(TConnection *Connection, TReadBuffer *Buffer){ if(Connection == NULL){ error("CRefreshField: Connection ist NULL.\n"); return; } int FieldX = Buffer->readWord(); int FieldY = Buffer->readWord(); int FieldZ = Buffer->readByte(); if(!IsOnMap(FieldX, FieldY, FieldZ)){ return; } if(!CheckVisibility(CL_CMD_REFRESH_FIELD, Connection, FieldX, FieldY, FieldZ)){ return; } SendFieldData(Connection, FieldX, FieldY, FieldZ); } void CRefreshContainer(TConnection *Connection, TReadBuffer *Buffer){ if(Connection == NULL){ error("CRefreshContainer: Connection ist NULL.\n"); return; } TPlayer *Player = Connection->GetPlayer(); if(Player == NULL){ return; } int ContainerNr = Buffer->readByte(); if(ContainerNr < NARRAY(TPlayer::OpenContainer)){ Object Con = Player->GetOpenContainer(ContainerNr); if(Con != NONE){ SendContainer(Connection, ContainerNr); } } } void CGetOutfit(TConnection *Connection, TReadBuffer *Buffer){ if(Connection == NULL){ error("CGetOutfit: Connection ist NULL.\n"); return; } TPlayer *Player = Connection->GetPlayer(); if(Player == NULL){ return; } if(CheckRight(Player->ID, GAMEMASTER_OUTFIT)){ SendMessage(Connection, TALK_FAILURE_MESSAGE, "You may not change your outfit."); return; } SendOutfit(Connection); } void CSetOutfit(TConnection *Connection, TReadBuffer *Buffer){ if(Connection == NULL){ error("CSetOutfit: Connection ist NULL.\n"); return; } TPlayer *Player = Connection->GetPlayer(); if(Player == NULL){ return; } TOutfit NewOutfit = {}; NewOutfit.OutfitID = Buffer->readWord(); Buffer->readBytes(NewOutfit.Colors, sizeof(NewOutfit.Colors)); int FirstOutfit = (Player->Sex == 1) ? 128 : 136; int LastOutfit = (Player->Sex == 1) ? 131 : 139; if(CheckRight(Player->ID, PREMIUM_ACCOUNT)){ LastOutfit += 3; } if(NewOutfit.OutfitID < FirstOutfit || NewOutfit.OutfitID > LastOutfit){ if(Player->Sex == 1){ print(3, "CSetOutfit: Ungültiges Outfit %d für Männer.\n", NewOutfit.OutfitID); }else{ print(3, "CSetOutfit: Ungültiges Outfit %d für Frauen.\n", NewOutfit.OutfitID); } return; } if(NewOutfit.Colors[0] > 132 || NewOutfit.Colors[1] > 132 || NewOutfit.Colors[2] > 132 || NewOutfit.Colors[3] > 132){ print(3, "CSetOutfit: Ungültige Farbe für Outfit.\n"); return; } if(Player->Outfit == Player->OrgOutfit){ Player->Outfit = NewOutfit; AnnounceChangedCreature(Player->ID, CREATURE_OUTFIT_CHANGED); } Player->OrgOutfit = NewOutfit; } void CAddBuddy(TConnection *Connection, TReadBuffer *Buffer){ if(Connection == NULL){ error("CAddBuddy: Connection ist NULL.\n"); return; } TPlayer *Player = Connection->GetPlayer(); if(Player == NULL){ return; } char Name[30]; Buffer->readString(Name, sizeof(Name)); if(Name[0] != 0){ Player->AddBuddy(Name); } } void CRemoveBuddy(TConnection *Connection, TReadBuffer *Buffer){ if(Connection == NULL){ error("CRemoveBuddy: Connection ist NULL.\n"); return; } TPlayer *Player = Connection->GetPlayer(); if(Player == NULL){ return; } uint32 CharacterID = Buffer->readQuad(); Player->RemoveBuddy(CharacterID); } // NOTE(fusion): These logging functions were out of control. It's not necessarily // better now but it is way simpler and do we even need any special formatting? void CBugReport(TConnection *Connection, TReadBuffer *Buffer){ if(Connection == NULL){ error("CBugReport: Connection ist NULL.\n"); return; } char Text[1024]; Buffer->readString(Text, sizeof(Text)); bool Empty = true; for(int i = 0; Text[i] != 0; i += 1){ if(!isprint(Text[i])){ Text[i] = 0; break; }else if(!isSpace(Text[i])){ Empty = false; } } if(Empty){ return; } // NOTE(fusion): `ctime` will already add a new line character which is why // there is none in this first `Log` call. int PlayerX, PlayerY, PlayerZ; time_t Time = time(NULL); Connection->GetPosition(&PlayerX, &PlayerY, &PlayerZ); Log("bugreport", "%s - %d/%d - [%d,%d,%d] - %s", Connection->GetName(), Connection->TerminalType, Connection->TerminalVersion, PlayerX, PlayerY, PlayerZ, ctime(&Time)); Log("bugreport", "%s\n", Text); Log("bugreport", "---------------------------------------------------------------------------\n"); SendMessage(Connection, TALK_STATUS_MESSAGE, "Comment sent."); } void CRuleViolation(TConnection *Connection, TReadBuffer *Buffer){ if(Connection == NULL){ error("CRuleViolation: Connection ist NULL.\n"); return; } TPlayer *Player = Connection->GetPlayer(); if(Player == NULL){ return; } char Name[30]; char Comment[200]; Buffer->readString(Name, sizeof(Name)); int Reason = Buffer->readByte(); int Action = Buffer->readByte(); Buffer->readString(Comment, sizeof(Comment)); uint32 StatementID = Buffer->readQuad(); bool IPBanishment = Buffer->readByte() != 0; if(!CheckBanishmentRight(Player->ID, Reason, Action) || (IPBanishment && !CheckRight(Player->ID, IP_BANISHMENT))){ SendMessage(Connection, TALK_FAILURE_MESSAGE, "You have no authorization for this action."); return; } if(Comment[0] == 0){ SendMessage(Connection, TALK_FAILURE_MESSAGE, "Please provide a comment for this report."); return; } if(Action == 6 && StatementID == 0){ SendMessage(Connection, TALK_FAILURE_MESSAGE, "Please provide a statement for this rule violation."); return; } TPlayer *Criminal; const char *IPAddress = NULL; bool IgnoreGamemasters = !CheckRight(Player->ID, READ_GAMEMASTER_CHANNEL); if(IdentifyPlayer(Name, false, IgnoreGamemasters, &Criminal) == 0){ if(CheckRight(Criminal->ID, NO_BANISHMENT)){ if(Action == 1 || Action == 3 || Action == 5){ SendMessage(Connection, TALK_FAILURE_MESSAGE, "This name has already been approved."); return; } if(Action != 6){ SendMessage(Connection, TALK_FAILURE_MESSAGE, "You may not report a god or gamemaster."); return; } } strcpy(Name, Criminal->Name); IPAddress = Criminal->IPAddress; }else{ if(GetCharacterID(Name) == 0){ SendResult(Connection, PLAYERNOTEXISTING); return; } strcpy(Name, GetCharacterName(Name)); if(IPBanishment){ SendMessage(Connection, TALK_FAILURE_MESSAGE, "Player is not online. No IP address was banished."); IPBanishment = false; } } int NumberOfStatements = 0; vector *ReportedStatements = NULL; bool HasStatement = ((Reason >= 9 && Reason <= 27) || Reason == 29); if(HasStatement && StatementID != 0){ uint32 ListenerID = (Action != 6 ? Player->ID : 0); int Context = GetCommunicationContext(ListenerID, StatementID, &NumberOfStatements, &ReportedStatements); if(Context == 1){ SendMessage(Connection, TALK_FAILURE_MESSAGE, "Statement is unknown. Perhaps it is too old?"); return; }else if(Context == 2){ SendMessage(Connection, TALK_FAILURE_MESSAGE, "Statement has already been reported."); return; } } PunishmentOrder(Player, Name,IPAddress, Reason, Action, Comment, NumberOfStatements, ReportedStatements, StatementID, IPBanishment); } void CErrorFileEntry(TConnection *Connection, TReadBuffer *Buffer){ if(Connection == NULL){ error("CErrorFileEntry: Connection ist NULL.\n"); return; } char Title[64]; char Date[32]; char Stack[2048]; char Comment[512]; Buffer->readString(Title, sizeof(Title)); Buffer->readString(Date, sizeof(Date)); Buffer->readString(Stack, sizeof(Stack)); Buffer->readString(Comment, sizeof(Comment)); if(Stack[0] == 0 && Comment[0] == 0){ return; } // NOTE(fusion): `ctime` will already add a new line character which is why // there is none in this first `Log` call. int PlayerX, PlayerY, PlayerZ; time_t Time = time(NULL); Connection->GetPosition(&PlayerX, &PlayerY, &PlayerZ); Log("client-error", "%s - %d/%d - [%d,%d,%d] - %s", Connection->GetName(), Connection->TerminalType, Connection->TerminalVersion, PlayerX, PlayerY, PlayerZ, ctime(&Time)); Log("client-error", "%s\n", Title); Log("client-error", "%s\n", Date); if(Stack[0] != 0){ Log("client-error", "\n"); Log("client-error", "%s\n", Stack); } if(Comment[0] != 0){ Log("client-error", "\n"); Log("client-error", "%s\n", Comment); } Log("client-error","---------------------------------------------------------------------------\n"); } void ReceiveData(TConnection *Connection){ if(Connection == NULL){ error("ReceiveData: Connection ist NULL.\n"); return; } if(Connection->InDataSize <= 0){ error("ReceiveData: Keine Daten vorhanden.\n"); return; } if((Connection->InDataSize + 2) > (int)sizeof(Connection->InData)){ error("ReceiveData: Zu große Paketlänge %d.\n", Connection->InDataSize); return; } // TODO(fusion): I thought the client would actually pack multiple commands // in the same packet. Perhaps not until some later version? // IMPORTANT(fusion): The actual payload size is in the buffer's first two // bytes which is why we start reading from offset two. TReadBuffer Buffer(Connection->InData + 2, Connection->InDataSize); uint8 Command; try{ Command = Buffer.readByte(); }catch(const char *str){ error("ReceiveData: Fehler beim Auslesen des Kommandos (%s).\n", str); return; } if(Connection->State == CONNECTION_LOGIN){ if(Command != CL_CMD_LOGIN){ error("ReceiveData: Falsches Login-Kommando %d.\n", Command); }else if(!Connection->JoinGame(&Buffer)){ Log("game", "Einloggen gescheitert.\n"); SendResult(Connection, LOGINERROR); TPlayer *Player = Connection->GetPlayer(); if(Player != NULL){ DecrementIsOnlineOrder(Player->ID); }else{ error("ReceiveData: Einloggen gescheitert und keine Kreatur bekannt.\n"); } Connection->Disconnect(); } return; } if(!CommandAllowed(Connection, Command)){ return; } Connection->ResetTimer(Command); try{ switch(Command){ case CL_CMD_LOGOUT: CQuitGame(Connection, &Buffer); break; case CL_CMD_PING: CPing(Connection, &Buffer); break; case CL_CMD_GO_PATH: CGoPath(Connection, &Buffer); break; case CL_CMD_GO_NORTH: CGoDirection(Connection, 0, -1); break; case CL_CMD_GO_EAST: CGoDirection(Connection, 1, 0); break; case CL_CMD_GO_SOUTH: CGoDirection(Connection, 0, 1); break; case CL_CMD_GO_WEST: CGoDirection(Connection, -1, 0); break; case CL_CMD_GO_STOP: CGoStop(Connection, &Buffer); break; case CL_CMD_GO_NORTHEAST: CGoDirection(Connection, 1, -1); break; case CL_CMD_GO_SOUTHEAST: CGoDirection(Connection, 1, 1); break; case CL_CMD_GO_SOUTHWEST: CGoDirection(Connection, -1, 1); break; case CL_CMD_GO_NORTHWEST: CGoDirection(Connection, -1, -1); break; case CL_CMD_ROTATE_NORTH: CRotate(Connection, DIRECTION_NORTH); break; case CL_CMD_ROTATE_EAST: CRotate(Connection, DIRECTION_EAST); break; case CL_CMD_ROTATE_SOUTH: CRotate(Connection, DIRECTION_SOUTH); break; case CL_CMD_ROTATE_WEST: CRotate(Connection, DIRECTION_WEST); break; case CL_CMD_MOVE_OBJECT: CMoveObject(Connection, &Buffer); break; case CL_CMD_TRADE_OBJECT: CTradeObject(Connection, &Buffer); break; case CL_CMD_INSPECT_TRADE: CInspectTrade(Connection, &Buffer); break; case CL_CMD_ACCEPT_TRADE: CAcceptTrade(Connection, &Buffer); break; case CL_CMD_REJECT_TRADE: CRejectTrade(Connection, &Buffer); break; case CL_CMD_USE_OBJECT: CUseObject(Connection, &Buffer); break; case CL_CMD_USE_TWO_OBJECTS: CUseTwoObjects(Connection, &Buffer); break; case CL_CMD_USE_ON_CREATURE: CUseOnCreature(Connection, &Buffer); break; case CL_CMD_TURN_OBJECT: CTurnObject(Connection, &Buffer); break; case CL_CMD_CLOSE_CONTAINER: CCloseContainer(Connection, &Buffer); break; case CL_CMD_UP_CONTAINER: CUpContainer(Connection, &Buffer); break; case CL_CMD_EDIT_TEXT: CEditText(Connection, &Buffer); break; case CL_CMD_EDIT_LIST: CEditList(Connection, &Buffer); break; case CL_CMD_LOOK_AT_POINT: CLookAtPoint(Connection, &Buffer); break; case CL_CMD_TALK: CTalk(Connection, &Buffer); break; case CL_CMD_GET_CHANNELS: CGetChannels(Connection, &Buffer); break; case CL_CMD_JOIN_CHANNEL: CJoinChannel(Connection, &Buffer); break; case CL_CMD_LEAVE_CHANNEL: CLeaveChannel(Connection, &Buffer); break; case CL_CMD_PRIVATE_CHANNEL: CPrivateChannel(Connection, &Buffer); break; case CL_CMD_PROCESS_REQUEST: CProcessRequest(Connection, &Buffer); break; case CL_CMD_REMOVE_REQUEST: CRemoveRequest(Connection, &Buffer); break; case CL_CMD_CANCEL_REQUEST: CCancelRequest(Connection, &Buffer); break; case CL_CMD_SET_TACTICS: CSetTactics(Connection, &Buffer); break; case CL_CMD_ATTACK: CAttack(Connection, &Buffer, false); break; case CL_CMD_FOLLOW: CAttack(Connection, &Buffer, true); break; case CL_CMD_INVITE_TO_PARTY: CInviteToParty(Connection, &Buffer); break; case CL_CMD_JOIN_PARTY: CJoinParty(Connection, &Buffer); break; case CL_CMD_REVOKE_INVITATION: CRevokeInvitation(Connection, &Buffer); break; case CL_CMD_PASS_LEADERSHIP: CPassLeadership(Connection, &Buffer); break; case CL_CMD_LEAVE_PARTY: CLeaveParty(Connection, &Buffer); break; case CL_CMD_OPEN_CHANNEL: COpenChannel(Connection, &Buffer); break; case CL_CMD_INVITE_TO_CHANNEL: CInviteToChannel(Connection, &Buffer); break; case CL_CMD_EXCLUDE_FROM_CHANNEL: CExcludeFromChannel(Connection, &Buffer); break; case CL_CMD_CANCEL: CCancel(Connection, &Buffer); break; case CL_CMD_REFRESH_FIELD: CRefreshField(Connection, &Buffer); break; case CL_CMD_REFRESH_CONTAINER: CRefreshContainer(Connection, &Buffer); break; case CL_CMD_GET_OUTFIT: CGetOutfit(Connection, &Buffer); break; case CL_CMD_SET_OUTFIT: CSetOutfit(Connection, &Buffer); break; case CL_CMD_ADD_BUDDY: CAddBuddy(Connection, &Buffer); break; case CL_CMD_REMOVE_BUDDY: CRemoveBuddy(Connection, &Buffer); break; case CL_CMD_BUG_REPORT: CBugReport(Connection, &Buffer); break; case CL_CMD_RULE_VIOLATION: CRuleViolation(Connection, &Buffer); break; case CL_CMD_ERROR_FILE_ENTRY: CErrorFileEntry(Connection, &Buffer); break; default:{ print(3, "Unbekanntes Kommando %d.\n", Command); break; } } }catch(const char *str){ error("Fehler beim Auslesen der Daten für Kommando %d (%s).\n", Command, str); } } void ReceiveData(void){ TConnection *Connection = GetFirstConnection(); while(Connection != NULL){ if(Connection->Live() && Connection->WaitingForACK){ ReceiveData(Connection); Connection->WaitingForACK = false; // NOTE(fusion): `SIGUSR1` is used to signal the connection thread // that we parsed all received data and that it may resume reading. // We check if the connection is still live because it may have been // disconnected inside `ReceiveData`. if(Connection->Live()){ tgkill(GetGameProcessID(), Connection->GetThreadID(), SIGUSR1); } } Connection = GetNextConnection(); } } void InitReceiving(void){ // no-op } void ExitReceiving(void){ // no-op }