aboutsummaryrefslogtreecommitdiff
path: root/src/receiving.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/receiving.cc')
-rw-r--r--src/receiving.cc1818
1 files changed, 1818 insertions, 0 deletions
diff --git a/src/receiving.cc b/src/receiving.cc
new file mode 100644
index 0000000..1c6ee00
--- /dev/null
+++ b/src/receiving.cc
@@ -0,0 +1,1818 @@
+#include "connections.hh"
+#include "info.hh"
+
+#include "stubs.hh"
+
+#include <signal.h>
+
+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` usage in this context will only be used inside
+ // `GetObject` for indexing 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 = 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 = 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<TReportedStatement> *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;
+ // TODO(fusion): It could have been disconnected inside `ReceiveData`?
+ if(Connection->Live()){
+ kill(Connection->GetPID(), SIGUSR1);
+ }
+ }
+ Connection = GetNextConnection();
+ }
+}
+
+void InitReceiving(void){
+ // no-op
+}
+
+void ExitReceiving(void){
+ // no-op
+}