Crossfire JXClient, Trunk  R20561
DefaultCrossfireServerConnection.java
Go to the documentation of this file.
1 /*
2  * This file is part of JXClient, the Fullscreen Java Crossfire Client.
3  *
4  * JXClient is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * JXClient is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with JXClient; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
17  *
18  * Copyright (C) 2005-2008 Yann Chachkoff.
19  * Copyright (C) 2006-2011 Andreas Kirschbaum.
20  */
21 
22 package com.realtime.crossfire.jxclient.server.crossfire;
23 
40 import java.io.BufferedReader;
41 import java.io.ByteArrayInputStream;
42 import java.io.IOException;
43 import java.io.InputStreamReader;
44 import java.io.UnsupportedEncodingException;
45 import java.nio.BufferUnderflowException;
46 import java.nio.ByteBuffer;
47 import java.nio.ByteOrder;
48 import java.util.ArrayList;
49 import java.util.Arrays;
50 import java.util.LinkedHashMap;
51 import java.util.List;
52 import java.util.Map;
53 import java.util.regex.Pattern;
54 import org.jetbrains.annotations.NotNull;
55 import org.jetbrains.annotations.Nullable;
56 
62 
66  private static final int DEFAULT_MAP_WIDTH = 11;
67 
71  private static final int DEFAULT_MAP_HEIGHT = 11;
72 
76  @NotNull
77  private static final Pattern PATTERN_DOT = Pattern.compile(":");
78 
82  @NotNull
83  private static final Pattern PATTERN_BAR = Pattern.compile("\\|+");
84 
88  @NotNull
89  private static final Pattern PATTERN_SPACE = Pattern.compile(" ");
90 
94  private static final int ACL_NAME = 1;
95 
99  private static final int ACL_CLASS = 2;
100 
104  private static final int ACL_RACE = 3;
105 
109  private static final int ACL_LEVEL = 4;
110 
114  private static final int ACL_FACE = 5;
115 
119  private static final int ACL_PARTY = 6;
120 
124  private static final int ACL_MAP = 7;
125 
129  private static final int ACL_FACE_NUM = 8;
130 
134  private static final int INFO_MAP_ARCH_NAME = 1;
135 
139  private static final int INFO_MAP_NAME = 2;
140 
144  private static final int INFO_MAP_DESCRIPTION = 3;
145 
149  @NotNull
150  private final Model model;
151 
155  @NotNull
157 
161  private int preferredMapWidth = 17;
162 
166  private int preferredMapHeight = 13;
167 
172  private int pendingMapWidth;
173 
178  private int pendingMapHeight;
179 
184 
189 
194  @NotNull
196 
201  @NotNull
202  private final byte[] writeBuffer = new byte[65536];
203 
207  @NotNull
208  private final ByteBuffer byteBuffer = ByteBuffer.wrap(writeBuffer);
209 
213  private int packet = 1;
214 
218  @NotNull
219  private static final byte[] ACCOUNT_LOGIN_PREFIX = {
220  'a',
221  'c',
222  'c',
223  'o',
224  'u',
225  'n',
226  't',
227  'l',
228  'o',
229  'g',
230  'i',
231  'n',
232  ' ',
233  };
234 
238  @NotNull
239  private static final byte[] ACCOUNT_PLAY_PREFIX = {
240  'a',
241  'c',
242  'c',
243  'o',
244  'u',
245  'n',
246  't',
247  'p',
248  'l',
249  'a',
250  'y',
251  ' ',
252  };
253 
257  @NotNull
258  private static final byte[] ACCOUNT_ADD_PLAYER_PREFIX = {
259  'a',
260  'c',
261  'c',
262  'o',
263  'u',
264  'n',
265  't',
266  'a',
267  'd',
268  'd',
269  'p',
270  'l',
271  'a',
272  'y',
273  'e',
274  'r',
275  ' ',
276  };
277 
281  @NotNull
282  private static final byte[] ACCOUNT_NEW_PREFIX = {
283  'a',
284  'c',
285  'c',
286  'o',
287  'u',
288  'n',
289  't',
290  'n',
291  'e',
292  'w',
293  ' ',
294  };
295 
299  @NotNull
300  private static final byte[] ACCOUNT_PASSWORD_PREFIX = {
301  'a',
302  'c',
303  'c',
304  'o',
305  'u',
306  'n',
307  't',
308  'p',
309  'w',
310  ' ',
311  };
312 
316  @NotNull
317  private static final byte[] CREATE_PLAYER_PREFIX = {
318  'c',
319  'r',
320  'e',
321  'a',
322  't',
323  'e',
324  'p',
325  'l',
326  'a',
327  'y',
328  'e',
329  'r',
330  ' ',
331  };
332 
336  @NotNull
337  private static final byte[] ADDME_PREFIX = {
338  'a',
339  'd',
340  'd',
341  'm',
342  'e',
343  ' ',
344  };
345 
349  @NotNull
350  private static final byte[] APPLY_PREFIX = {
351  'a',
352  'p',
353  'p',
354  'l',
355  'y',
356  ' ',
357  };
358 
362  @NotNull
363  private static final byte[] ASKFACE_PREFIX = {
364  'a',
365  's',
366  'k',
367  'f',
368  'a',
369  'c',
370  'e',
371  ' ',
372  };
373 
377  @NotNull
378  private static final byte[] EXAMINE_PREFIX = {
379  'e',
380  'x',
381  'a',
382  'm',
383  'i',
384  'n',
385  'e',
386  ' ',
387  };
388 
392  @NotNull
393  private static final byte[] LOCK_PREFIX = {
394  'l',
395  'o',
396  'c',
397  'k',
398  ' ',
399  };
400 
404  @NotNull
405  private static final byte[] LOOKAT_PREFIX = {
406  'l',
407  'o',
408  'o',
409  'k',
410  'a',
411  't',
412  ' ',
413  };
414 
418  @NotNull
419  private static final byte[] MARK_PREFIX = {
420  'm',
421  'a',
422  'r',
423  'k',
424  ' ',
425  };
426 
430  @NotNull
431  private static final byte[] MOVE_PREFIX = {
432  'm',
433  'o',
434  'v',
435  'e',
436  ' ',
437  };
438 
442  @NotNull
443  private static final byte[] NCOM_PREFIX = {
444  'n',
445  'c',
446  'o',
447  'm',
448  ' ',
449  };
450 
454  @NotNull
455  private static final byte[] REPLY_PREFIX = {
456  'r',
457  'e',
458  'p',
459  'l',
460  'y',
461  ' ',
462  };
463 
467  @NotNull
468  private static final byte[] REQUESTINFO_PREFIX = {
469  'r',
470  'e',
471  'q',
472  'u',
473  'e',
474  's',
475  't',
476  'i',
477  'n',
478  'f',
479  'o',
480  ' ',
481  };
482 
486  @NotNull
487  private static final byte[] SETUP_PREFIX = {
488  's',
489  'e',
490  't',
491  'u',
492  'p',
493  }; // note that this command does not have a trailing space
494 
498  @NotNull
499  private static final byte[] TOGGLEEXTENDEDTEXT_PREFIX = {
500  't',
501  'o',
502  'g',
503  'g',
504  'l',
505  'e',
506  'e',
507  'x',
508  't',
509  'e',
510  'n',
511  'd',
512  'e',
513  'd',
514  't',
515  'e',
516  'x',
517  't',
518  }; // note that this command does not have a trailing space
519 
523  @NotNull
524  private static final byte[] VERSION_PREFIX = {
525  'v',
526  'e',
527  'r',
528  's',
529  'i',
530  'o',
531  'n',
532  ' ',
533  };
534 
538  @NotNull
539  private final String version;
540 
545  @Nullable
546  private final DebugWriter debugProtocol;
547 
551  @NotNull
553 
557  @Nullable
558  private String accountName;
559 
563  private int loginMethod;
564 
569  @Nullable
571 
576  @Nullable
577  private String sendingRequestInfo;
578 
583  @NotNull
584  private final List<String> pendingRequestInfos = new ArrayList<>();
585 
589  @NotNull
590  @SuppressWarnings("FieldCanBeLocal")
592 
593  @Override
594  public void connecting() {
595  // ignore
596  }
597 
598  @Override
599  public void connected() {
601  }
602 
603  @Override
604  public void packetReceived(@NotNull final ByteBuffer packet) throws UnknownCommandException {
605  processPacket(packet);
606  }
607 
608  @Override
609  public void packetSent(@NotNull final byte[] buf, final int len) {
610  // ignore
611  }
612 
613  @Override
614  public void disconnecting(@NotNull final String reason, final boolean isError) {
615  // ignore
616  }
617 
618  @Override
619  public void disconnected(@NotNull final String reason) {
620  // ignore
621  }
622 
623  };
624 
634  public DefaultCrossfireServerConnection(@NotNull final Model model, @Nullable final DebugWriter debugProtocol, @NotNull final String version) throws IOException {
635  super(model);
636  this.model = model;
637  defaultServerConnection = new DefaultServerConnection(model, debugProtocol);
638  this.version = version;
639  byteBuffer.order(ByteOrder.BIG_ENDIAN);
640  this.debugProtocol = debugProtocol;
642  //noinspection ThisEscapedInObjectConstruction
643  numLookObjects = new NumLookObjects(this, debugProtocol);
644  }
645 
649  @Override
650  public void setCrossfireUpdateMapListener(@Nullable final CrossfireUpdateMapListener listener) {
651  if (listener != null && crossfireUpdateMapListener != null) {
652  throw new IllegalStateException("listener already set to "+crossfireUpdateMapListener.getClass().getName());
653  }
654  crossfireUpdateMapListener = listener;
655  }
656 
660  @Override
661  public void start() {
662  defaultServerConnection.start();
663  }
664 
668  @Override
669  public void stop() throws InterruptedException {
670  defaultServerConnection.stop();
671  }
672 
676  private void connected() {
677  pendingMapWidth = 0;
678  pendingMapHeight = 0;
679  numLookObjects.connected();
680  setCurrentMapSize(DEFAULT_MAP_WIDTH, DEFAULT_MAP_HEIGHT);
681 
682  fireNewMap();
683 
685  sendVersion(1023, 1027, version);
686  }
687 
695  private void processPacket(@NotNull final ByteBuffer packet) throws UnknownCommandException {
696  try {
697  packet.mark();
698  switch (packet.get()) {
699  case 'a':
700  switch (packet.get()) {
701  case 'c':
702  //accountplayers
703  if (packet.get() != 'c') {
704  break;
705  }
706  if (packet.get() != 'o') {
707  break;
708  }
709  if (packet.get() != 'u') {
710  break;
711  }
712  if (packet.get() != 'n') {
713  break;
714  }
715  if (packet.get() != 't') {
716  break;
717  }
718  if (packet.get() != 'p') {
719  break;
720  }
721  if (packet.get() != 'l') {
722  break;
723  }
724  if (packet.get() != 'a') {
725  break;
726  }
727  if (packet.get() != 'y') {
728  break;
729  }
730  if (packet.get() != 'e') {
731  break;
732  }
733  if (packet.get() != 'r') {
734  break;
735  }
736  if (packet.get() != 's') {
737  break;
738  }
739  if (packet.get() != ' ') {
740  break;
741  }
742  if (debugProtocol != null) {
743  debugProtocol.debugProtocolWrite("recv accountplayers");
744  }
745  processAccountPlayers(packet);
746  return;
747 
748  case 'd':
749  if (packet.get() != 'd') {
750  break;
751  }
752  switch (packet.get()) {
753  case 'k':
754  if (packet.get() != 'n') {
755  break;
756  }
757  if (packet.get() != 'o') {
758  break;
759  }
760  if (packet.get() != 'w') {
761  break;
762  }
763  if (packet.get() != 'l') {
764  break;
765  }
766  if (packet.get() != 'e') {
767  break;
768  }
769  if (packet.get() != 'd') {
770  break;
771  }
772  if (packet.get() != 'g') {
773  break;
774  }
775  if (packet.get() != 'e') {
776  break;
777  }
778  if (packet.get() != ' ') {
779  break;
780  }
781 
782  processAddKnowledge(packet);
783  return;
784 
785  case 'm':
786  if (packet.get() != 'e') {
787  break;
788  }
789  if (packet.get() != '_') {
790  break;
791  }
792  switch (packet.get()) {
793  case 'f':
794  if (packet.get() != 'a') {
795  break;
796  }
797  if (packet.get() != 'i') {
798  break;
799  }
800  if (packet.get() != 'l') {
801  break;
802  }
803  if (packet.get() != 'e') {
804  break;
805  }
806  if (packet.get() != 'd') {
807  break;
808  }
809  if (packet.hasRemaining()) {
810  break;
811  }
812  if (debugProtocol != null) {
813  debugProtocol.debugProtocolWrite("recv addme_failed");
814  }
815  processAddmeFailed(packet);
816  return;
817 
818  case 's':
819  if (packet.get() != 'u') {
820  break;
821  }
822  if (packet.get() != 'c') {
823  break;
824  }
825  if (packet.get() != 'c') {
826  break;
827  }
828  if (packet.get() != 'e') {
829  break;
830  }
831  if (packet.get() != 's') {
832  break;
833  }
834  if (packet.get() != 's') {
835  break;
836  }
837  if (packet.hasRemaining()) {
838  break;
839  }
840  if (debugProtocol != null) {
841  debugProtocol.debugProtocolWrite("recv addme_success");
842  }
843  processAddmeSuccess(packet);
844  return;
845  }
846  break;
847 
848  case 'q':
849  if (packet.get() != 'u') {
850  break;
851  }
852  if (packet.get() != 'e') {
853  break;
854  }
855  if (packet.get() != 's') {
856  break;
857  }
858  if (packet.get() != 't') {
859  break;
860  }
861  if (packet.get() != ' ') {
862  break;
863  }
864 
865  processAddQuest(packet);
866  return;
867 
868  case 's':
869  if (packet.get() != 'p') {
870  break;
871  }
872  if (packet.get() != 'e') {
873  break;
874  }
875  if (packet.get() != 'l') {
876  break;
877  }
878  if (packet.get() != 'l') {
879  break;
880  }
881  if (packet.get() != ' ') {
882  break;
883  }
884  processAddSpell(packet);
885  return;
886  }
887  break;
888 
889  case 'n':
890  if (packet.get() != 'i') {
891  break;
892  }
893  if (packet.get() != 'm') {
894  break;
895  }
896  if (packet.get() != ' ') {
897  break;
898  }
899  processAnim(packet);
900  return;
901  }
902  break;
903 
904  case 'c':
905  if (packet.get() != 'o') {
906  break;
907  }
908  if (packet.get() != 'm') {
909  break;
910  }
911  if (packet.get() != 'c') {
912  break;
913  }
914  if (packet.get() != ' ') {
915  break;
916  }
917  processComc(packet);
918  return;
919 
920  case 'd':
921  switch (packet.get()) {
922  case 'e':
923  if (packet.get() != 'l') {
924  break;
925  }
926  switch (packet.get()) {
927  case 'i':
928  switch (packet.get()) {
929  case 'n':
930  if (packet.get() != 'v') {
931  break;
932  }
933  if (packet.get() != ' ') {
934  break;
935  }
936  processDelInv(packet);
937  return;
938 
939  case 't':
940  if (packet.get() != 'e') {
941  break;
942  }
943  if (packet.get() != 'm') {
944  break;
945  }
946  if (packet.get() != ' ') {
947  break;
948  }
949  processDelItem(packet);
950  return;
951  }
952  break;
953 
954  case 's':
955  if (packet.get() != 'p') {
956  break;
957  }
958  if (packet.get() != 'e') {
959  break;
960  }
961  if (packet.get() != 'l') {
962  break;
963  }
964  if (packet.get() != 'l') {
965  break;
966  }
967  if (packet.get() != ' ') {
968  break;
969  }
970  processDelSpell(packet);
971  return;
972  }
973  break;
974 
975  case 'r':
976  if (packet.get() != 'a') {
977  break;
978  }
979  if (packet.get() != 'w') {
980  break;
981  }
982  switch (packet.get()) {
983  case 'e':
984  if (packet.get() != 'x') {
985  break;
986  }
987  if (packet.get() != 't') {
988  break;
989  }
990  if (packet.get() != 'i') {
991  break;
992  }
993  if (packet.get() != 'n') {
994  break;
995  }
996  if (packet.get() != 'f') {
997  break;
998  }
999  if (packet.get() != 'o') {
1000  break;
1001  }
1002  if (packet.get() != ' ') {
1003  break;
1004  }
1005  processDrawExtInfo(packet);
1006  return;
1007 
1008  case 'i':
1009  if (packet.get() != 'n') {
1010  break;
1011  }
1012  if (packet.get() != 'f') {
1013  break;
1014  }
1015  if (packet.get() != 'o') {
1016  break;
1017  }
1018  if (packet.get() != ' ') {
1019  break;
1020  }
1021  processDrawInfo(packet);
1022  return;
1023  }
1024  break;
1025  }
1026  break;
1027 
1028  case 'E':
1029  if (packet.get() != 'x') {
1030  break;
1031  }
1032  if (packet.get() != 't') {
1033  break;
1034  }
1035  if (packet.get() != 'e') {
1036  break;
1037  }
1038  if (packet.get() != 'n') {
1039  break;
1040  }
1041  if (packet.get() != 'd') {
1042  break;
1043  }
1044  if (packet.get() != 'e') {
1045  break;
1046  }
1047  if (packet.get() != 'd') {
1048  break;
1049  }
1050  switch (packet.get()) {
1051  case 'I':
1052  if (packet.get() != 'n') {
1053  break;
1054  }
1055  if (packet.get() != 'f') {
1056  break;
1057  }
1058  if (packet.get() != 'o') {
1059  break;
1060  }
1061  if (packet.get() != 'S') {
1062  break;
1063  }
1064  if (packet.get() != 'e') {
1065  break;
1066  }
1067  if (packet.get() != 't') {
1068  break;
1069  }
1070  if (packet.get() != ' ') {
1071  break;
1072  }
1073  processExtendedInfoSet(packet);
1074  return;
1075 
1076  case 'T':
1077  if (packet.get() != 'e') {
1078  break;
1079  }
1080  if (packet.get() != 'x') {
1081  break;
1082  }
1083  if (packet.get() != 't') {
1084  break;
1085  }
1086  if (packet.get() != 'S') {
1087  break;
1088  }
1089  if (packet.get() != 'e') {
1090  break;
1091  }
1092  if (packet.get() != 't') {
1093  break;
1094  }
1095  if (packet.get() != ' ') {
1096  break;
1097  }
1098  processExtendedTextSet(packet);
1099  return;
1100  }
1101  break;
1102 
1103  case 'f':
1104  if (packet.get() != 'a') {
1105  break;
1106  }
1107  switch (packet.get()) {
1108  case 'c':
1109  if (packet.get() != 'e') {
1110  break;
1111  }
1112  if (packet.get() != '2') {
1113  break;
1114  }
1115  if (packet.get() != ' ') {
1116  break;
1117  }
1118  processFace2(packet);
1119  return;
1120 
1121  case 'i':
1122  if (packet.get() != 'l') {
1123  break;
1124  }
1125  if (packet.get() != 'u') {
1126  break;
1127  }
1128  if (packet.get() != 'r') {
1129  break;
1130  }
1131  if (packet.get() != 'e') {
1132  break;
1133  }
1134  if (packet.get() != ' ') {
1135  break;
1136  }
1137  processFailure(packet);
1138  return;
1139  }
1140  break;
1141 
1142  case 'g':
1143  if (packet.get() != 'o') {
1144  break;
1145  }
1146  if (packet.get() != 'o') {
1147  break;
1148  }
1149  if (packet.get() != 'd') {
1150  break;
1151  }
1152  if (packet.get() != 'b') {
1153  break;
1154  }
1155  if (packet.get() != 'y') {
1156  break;
1157  }
1158  if (packet.get() != 'e') {
1159  break;
1160  }
1161  if (packet.get() != ' ') {
1162  break;
1163  }
1164  processGoodbye(packet);
1165  return;
1166 
1167  case 'i':
1168  switch (packet.get()) {
1169  case 'm':
1170  if (packet.get() != 'a') {
1171  break;
1172  }
1173  if (packet.get() != 'g') {
1174  break;
1175  }
1176  if (packet.get() != 'e') {
1177  break;
1178  }
1179  if (packet.get() != '2') {
1180  break;
1181  }
1182  if (packet.get() != ' ') {
1183  break;
1184  }
1185  processImage2(packet);
1186  return;
1187 
1188  case 't':
1189  if (packet.get() != 'e') {
1190  break;
1191  }
1192  if (packet.get() != 'm') {
1193  break;
1194  }
1195  if (packet.get() != '2') {
1196  break;
1197  }
1198  if (packet.get() != ' ') {
1199  break;
1200  }
1201  processItem2(packet);
1202  return;
1203  }
1204  break;
1205 
1206  case 'm':
1207  switch (packet.get()) {
1208  case 'a':
1209  switch (packet.get()) {
1210  case 'g':
1211  if (packet.get() != 'i') {
1212  break;
1213  }
1214  if (packet.get() != 'c') {
1215  break;
1216  }
1217  if (packet.get() != 'm') {
1218  break;
1219  }
1220  if (packet.get() != 'a') {
1221  break;
1222  }
1223  if (packet.get() != 'p') {
1224  break;
1225  }
1226  if (packet.get() != ' ') {
1227  break;
1228  }
1229  processMagicMap(packet);
1230  return;
1231 
1232  case 'p':
1233  switch (packet.get()) {
1234  case '2':
1235  if (packet.get() != ' ') {
1236  break;
1237  }
1238  processMap2(packet);
1239  return;
1240 
1241  case 'e':
1242  if (packet.get() != 'x') {
1243  break;
1244  }
1245  if (packet.get() != 't') {
1246  break;
1247  }
1248  if (packet.get() != 'e') {
1249  break;
1250  }
1251  if (packet.get() != 'n') {
1252  break;
1253  }
1254  if (packet.get() != 'd') {
1255  break;
1256  }
1257  if (packet.get() != 'e') {
1258  break;
1259  }
1260  if (packet.get() != 'd') {
1261  break;
1262  }
1263  if (packet.get() != ' ') {
1264  break;
1265  }
1266  processMapExtended(packet);
1267  return;
1268  }
1269  break;
1270  }
1271  break;
1272 
1273  case 'u':
1274  if (packet.get() != 's') {
1275  break;
1276  }
1277  if (packet.get() != 'i') {
1278  break;
1279  }
1280  if (packet.get() != 'c') {
1281  break;
1282  }
1283  if (packet.get() != ' ') {
1284  break;
1285  }
1286  processMusic(packet);
1287  return;
1288  }
1289  break;
1290 
1291  case 'n':
1292  if (packet.get() != 'e') {
1293  break;
1294  }
1295  if (packet.get() != 'w') {
1296  break;
1297  }
1298  if (packet.get() != 'm') {
1299  break;
1300  }
1301  if (packet.get() != 'a') {
1302  break;
1303  }
1304  if (packet.get() != 'p') {
1305  break;
1306  }
1307  processNewMap(packet);
1308  return;
1309 
1310  case 'p':
1311  switch (packet.get()) {
1312  case 'i':
1313  if (packet.get() != 'c') {
1314  break;
1315  }
1316  if (packet.get() != 'k') {
1317  break;
1318  }
1319  if (packet.get() != 'u') {
1320  break;
1321  }
1322  if (packet.get() != 'p') {
1323  break;
1324  }
1325  if (packet.get() != ' ') {
1326  break;
1327  }
1328  processPickup(packet);
1329  return;
1330 
1331  case 'l':
1332  if (packet.get() != 'a') {
1333  break;
1334  }
1335  if (packet.get() != 'y') {
1336  break;
1337  }
1338  if (packet.get() != 'e') {
1339  break;
1340  }
1341  if (packet.get() != 'r') {
1342  break;
1343  }
1344  if (packet.get() != ' ') {
1345  break;
1346  }
1347  processPlayer(packet);
1348  return;
1349  }
1350  break;
1351 
1352  case 'q':
1353  if (packet.get() != 'u') {
1354  break;
1355  }
1356  if (packet.get() != 'e') {
1357  break;
1358  }
1359  if (packet.get() != 'r') {
1360  break;
1361  }
1362  if (packet.get() != 'y') {
1363  break;
1364  }
1365  if (packet.get() != ' ') {
1366  break;
1367  }
1368  processQuery(packet);
1369  return;
1370 
1371  case 'r':
1372  if (packet.get() != 'e') {
1373  break;
1374  }
1375  if (packet.get() != 'p') {
1376  break;
1377  }
1378  if (packet.get() != 'l') {
1379  break;
1380  }
1381  if (packet.get() != 'y') {
1382  break;
1383  }
1384  if (packet.get() != 'i') {
1385  break;
1386  }
1387  if (packet.get() != 'n') {
1388  break;
1389  }
1390  if (packet.get() != 'f') {
1391  break;
1392  }
1393  if (packet.get() != 'o') {
1394  break;
1395  }
1396  if (packet.get() != ' ') {
1397  break;
1398  }
1399  processReplyInfo(packet);
1400  return;
1401 
1402  case 's':
1403  switch (packet.get()) {
1404  case 'e':
1405  if (packet.get() != 't') {
1406  break;
1407  }
1408  if (packet.get() != 'u') {
1409  break;
1410  }
1411  if (packet.get() != 'p') {
1412  break;
1413  }
1414  if (packet.get() != ' ') {
1415  break;
1416  }
1417  processSetup(packet);
1418  return;
1419 
1420  case 'm':
1421  if (packet.get() != 'o') {
1422  break;
1423  }
1424  if (packet.get() != 'o') {
1425  break;
1426  }
1427  if (packet.get() != 't') {
1428  break;
1429  }
1430  if (packet.get() != 'h') {
1431  break;
1432  }
1433  if (packet.get() != ' ') {
1434  break;
1435  }
1436  processSmooth(packet);
1437  return;
1438 
1439  case 'o':
1440  if (packet.get() != 'u') {
1441  break;
1442  }
1443  if (packet.get() != 'n') {
1444  break;
1445  }
1446  if (packet.get() != 'd') {
1447  break;
1448  }
1449  switch (packet.get()) {
1450  case ' ':
1451  processSound(packet);
1452  return;
1453 
1454  case '2':
1455  if (packet.get() != ' ') {
1456  break;
1457  }
1458  processSound2(packet);
1459  return;
1460  }
1461  break;
1462 
1463  case 't':
1464  if (packet.get() != 'a') {
1465  break;
1466  }
1467  if (packet.get() != 't') {
1468  break;
1469  }
1470  if (packet.get() != 's') {
1471  break;
1472  }
1473  if (packet.get() != ' ') {
1474  break;
1475  }
1476  processStats(packet);
1477  return;
1478  }
1479  break;
1480 
1481  case 't':
1482  if (packet.get() != 'i') {
1483  break;
1484  }
1485  if (packet.get() != 'c') {
1486  break;
1487  }
1488  if (packet.get() != 'k') {
1489  break;
1490  }
1491  if (packet.get() != ' ') {
1492  break;
1493  }
1494  processTick(packet);
1495  return;
1496 
1497  case 'u':
1498  if (packet.get() != 'p') {
1499  break;
1500  }
1501  if (packet.get() != 'd') {
1502  break;
1503  }
1504  switch (packet.get()) {
1505  case 'i':
1506  if (packet.get() != 't') {
1507  break;
1508  }
1509  if (packet.get() != 'e') {
1510  break;
1511  }
1512  if (packet.get() != 'm') {
1513  break;
1514  }
1515  if (packet.get() != ' ') {
1516  break;
1517  }
1518  processUpdItem(packet);
1519  return;
1520 
1521  case 'q':
1522  if (packet.get() != 'u') {
1523  break;
1524  }
1525  if (packet.get() != 'e') {
1526  break;
1527  }
1528  if (packet.get() != 's') {
1529  break;
1530  }
1531  if (packet.get() != 't') {
1532  break;
1533  }
1534  if (packet.get() != ' ') {
1535  break;
1536  }
1537  processUpdQuest(packet);
1538  return;
1539 
1540  case 's':
1541  if (packet.get() != 'p') {
1542  break;
1543  }
1544  if (packet.get() != 'e') {
1545  break;
1546  }
1547  if (packet.get() != 'l') {
1548  break;
1549  }
1550  if (packet.get() != 'l') {
1551  break;
1552  }
1553  if (packet.get() != ' ') {
1554  break;
1555  }
1556  processUpdSpell(packet);
1557  return;
1558  }
1559  break;
1560 
1561  case 'v':
1562  if (packet.get() != 'e') {
1563  break;
1564  }
1565  if (packet.get() != 'r') {
1566  break;
1567  }
1568  if (packet.get() != 's') {
1569  break;
1570  }
1571  if (packet.get() != 'i') {
1572  break;
1573  }
1574  if (packet.get() != 'o') {
1575  break;
1576  }
1577  if (packet.get() != 'n') {
1578  break;
1579  }
1580  if (packet.get() != ' ') {
1581  break;
1582  }
1583  processVersion(packet);
1584  return;
1585  }
1586  } catch (final IllegalArgumentException ex) {
1587  if (debugProtocol != null) {
1588  debugProtocol.debugProtocolWrite("IllegalArgumentException while command parsing: "+ex+"\n"+hexDump(packet), ex);
1589  }
1590  } catch (final BufferUnderflowException ex) {
1591  if (debugProtocol != null) {
1592  debugProtocol.debugProtocolWrite("BufferUnderflowException while command parsing: "+ex+"\n"+hexDump(packet), ex);
1593  }
1594  } catch (final ArrayIndexOutOfBoundsException ex) {
1595  if (debugProtocol != null) {
1596  debugProtocol.debugProtocolWrite("ArrayIndexOutOfBoundsException while command parsing: "+ex+"\n"+hexDump(packet), ex);
1597  }
1598  } catch (final StringIndexOutOfBoundsException ex) {
1599  if (debugProtocol != null) {
1600  debugProtocol.debugProtocolWrite("StringIndexOutOfBoundsException while command parsing: "+ex+"\n"+hexDump(packet), ex);
1601  }
1602  } catch (final UnknownCommandException ex) {
1603  if (debugProtocol != null) {
1604  debugProtocol.debugProtocolWrite("UnknownCommandException while command parsing: "+ex+"\n"+hexDump(packet), ex);
1605  }
1606  throw ex;
1607  }
1608 
1609  packet.position(0);
1610  final String command = extractCommand(packet);
1611  if (debugProtocol != null) {
1612  debugProtocol.debugProtocolWrite("recv invalid command: "+command+"\n"+hexDump(packet));
1613  }
1614  throw new UnknownCommandException("Cannot parse command: "+command);
1615  }
1616 
1624  private void cmdMap2Coordinate(@NotNull final ByteBuffer packet, final int x, final int y) throws UnknownCommandException {
1625  while (true) {
1626  final int lenType = getInt1(packet);
1627  if (lenType == 0xFF) {
1628  break;
1629  }
1630 
1631  final int len = (lenType>>5)&7;
1632  final int type = lenType&31;
1633  switch (type) {
1634  case Map2.COORD_CLEAR_SPACE:
1635  cmdMap2CoordinateClearSpace(x, y, len);
1636  break;
1637 
1638  case Map2.COORD_DARKNESS:
1639  cmdMap2CoordinateDarkness(packet, x, y, len);
1640  break;
1641 
1642  case Map2.COORD_LAYER0:
1643  case Map2.COORD_LAYER1:
1644  case Map2.COORD_LAYER2:
1645  case Map2.COORD_LAYER3:
1646  case Map2.COORD_LAYER4:
1647  case Map2.COORD_LAYER5:
1648  case Map2.COORD_LAYER6:
1649  case Map2.COORD_LAYER7:
1650  case Map2.COORD_LAYER8:
1651  case Map2.COORD_LAYER9:
1652  cmdMap2CoordinateLayer(packet, x, y, len, type-Map2.COORD_LAYER0);
1653  break;
1654  }
1655  }
1656  }
1657 
1666  private void cmdMap2CoordinateClearSpace(final int x, final int y, final int len) throws UnknownCommandException {
1667  if (len != 0) {
1668  throw new UnknownCommandException("map2 command contains clear command with length "+len);
1669  }
1670  if (debugProtocol != null) {
1671  debugProtocol.debugProtocolWrite("recv map2 "+x+"/"+y+" clear");
1672  }
1673  fireMapClear(x, y);
1674  }
1675 
1684  private void cmdMap2CoordinateDarkness(@NotNull final ByteBuffer packet, final int x, final int y, final int len) throws UnknownCommandException {
1685  if (len != 1) {
1686  throw new UnknownCommandException("map2 command contains darkness command with length "+len);
1687  }
1688  final int darkness = getInt1(packet);
1689  if (debugProtocol != null) {
1690  debugProtocol.debugProtocolWrite("recv map2 "+x+"/"+y+" darkness="+darkness);
1691  }
1692  fireMapDarkness(x, y, darkness);
1693  }
1694 
1704  private void cmdMap2CoordinateLayer(@NotNull final ByteBuffer packet, final int x, final int y, final int len, final int layer) throws UnknownCommandException {
1705  if (len < 2) {
1706  throw new UnknownCommandException("map2 command contains image command with length "+len);
1707  }
1708  final Location location = new Location(x, y, layer);
1709  final int face = getInt2(packet);
1710  if ((face&Map2.FACE_ANIMATION) == 0) {
1711  if (debugProtocol != null) {
1712  debugProtocol.debugProtocolWrite("recv map2 "+location+" face="+face);
1713  }
1714  fireMapFace(location, face);
1715  } else {
1716  if (debugProtocol != null) {
1717  debugProtocol.debugProtocolWrite("recv map2 "+location+" anim="+(face&Map2.ANIM_MASK)+" type="+((face>>Map2.ANIM_TYPE_SHIFT)&Map2.ANIM_TYPE_MASK));
1718  }
1720  }
1721  if (len == 3) {
1722  cmdMap2CoordinateLayer3(packet, location, face);
1723  } else if (len == 4) {
1724  cmdMap2CoordinateLayer4(packet, location, face);
1725  } else if (len != 2) {
1726  if (debugProtocol != null) {
1727  debugProtocol.debugProtocolWrite("recv map2 "+x+"/"+y+"/"+layer+" <invalid>");
1728  }
1729  throw new UnknownCommandException("map2 command contains image command with length "+len);
1730  }
1731  }
1732 
1741  private void cmdMap2CoordinateLayer3(@NotNull final ByteBuffer packet, @NotNull final Location location, final int face) throws UnknownCommandException {
1742  if (face == 0) {
1743  throw new UnknownCommandException("map2 command contains smoothing or animation information for empty face");
1744  }
1745 
1746  if ((face&Map2.FACE_ANIMATION) == 0) {
1747  final int smooth = getInt1(packet);
1748  if (debugProtocol != null) {
1749  debugProtocol.debugProtocolWrite("recv map2 "+location+" smooth="+smooth);
1750  }
1751  fireMapSmooth(location, smooth);
1752  } else {
1753  final int animSpeed = getInt1(packet);
1754  if (debugProtocol != null) {
1755  debugProtocol.debugProtocolWrite("recv map2 "+location+" anim_speed="+animSpeed);
1756  }
1757  fireMapAnimationSpeed(location, animSpeed);
1758  }
1759  }
1760 
1769  private void cmdMap2CoordinateLayer4(@NotNull final ByteBuffer packet, @NotNull final Location location, final int face) throws UnknownCommandException {
1770  if (face == 0) {
1771  throw new UnknownCommandException("map2 command contains smoothing or animation information for empty face");
1772  }
1773 
1774  final int animSpeed = getInt1(packet);
1775  if (debugProtocol != null) {
1776  debugProtocol.debugProtocolWrite("recv map2 "+location+" anim_speed="+animSpeed);
1777  }
1778  fireMapAnimationSpeed(location, animSpeed);
1779 
1780  final int smooth = getInt1(packet);
1781  if (debugProtocol != null) {
1782  debugProtocol.debugProtocolWrite("recv map2 "+location+" smooth="+smooth);
1783  }
1784  fireMapSmooth(location, smooth);
1785  }
1786 
1792  private void negotiateMapSize(final int mapWidth, final int mapHeight) {
1793  if (debugProtocol != null) {
1794  debugProtocol.debugProtocolWrite("negotiateMapSize: "+mapWidth+"x"+mapHeight);
1795  }
1796 
1797  if (clientSocketState == ClientSocketState.CONNECTING || clientSocketState == ClientSocketState.VERSION || clientSocketState == ClientSocketState.CONNECT_FAILED) {
1798  if (debugProtocol != null) {
1799  debugProtocol.debugProtocolWrite("negotiateMapSize: clientSocketState="+clientSocketState+", ignoring");
1800  }
1801  return;
1802  }
1803  if (pendingMapWidth != 0 || pendingMapHeight != 0) {
1804  if (debugProtocol != null) {
1805  debugProtocol.debugProtocolWrite("negotiateMapSize: already negotiating, ignoring");
1806  }
1807  return;
1808  }
1809  if (currentMapWidth == mapWidth && currentMapHeight == mapHeight) {
1810  if (debugProtocol != null) {
1811  debugProtocol.debugProtocolWrite("negotiateMapSize: same as current map size, ignoring");
1812  }
1813  return;
1814  }
1815  pendingMapWidth = mapWidth;
1816  pendingMapHeight = mapHeight;
1817  sendSetup("mapsize "+pendingMapWidth+"x"+pendingMapHeight);
1818  }
1819 
1827  private void cmdReplyinfo(@NotNull final String infoType, final ByteBuffer packet) throws IOException, UnknownCommandException {
1828  synchronized (writeBuffer) {
1829  if (sendingRequestInfo != null && sendingRequestInfo.equals(infoType)) {
1830  sendingRequestInfo = null;
1831  }
1832  }
1834 
1835  switch (infoType) {
1836  case "image_info":
1837  processImageInfoReplyinfo(packet);
1838  break;
1839 
1840  case "skill_info":
1841  processSkillInfoReplyinfo(packet);
1842  break;
1843 
1844  case "exp_table":
1845  processExpTableReplyinfo(packet);
1846  break;
1847 
1848  case "knowledge_info":
1850  break;
1851 
1852  case "startingmap":
1854  break;
1855 
1856  case "race_list":
1857  processRaceListReplyinfo(packet);
1858  break;
1859 
1860  case "class_list":
1861  processClassListReplyinfo(packet);
1862  break;
1863 
1864  case "race_info":
1865  processClassRaceInfoReplyinfo(packet, true);
1866  break;
1867 
1868  case "class_info":
1869  processClassRaceInfoReplyinfo(packet, false);
1870  break;
1871 
1872  case "newcharinfo":
1874  break;
1875 
1876  default:
1877  System.err.println("Ignoring unexpected replyinfo type '"+infoType+"'.");
1878  break;
1879  }
1880  }
1881 
1887  private static void processImageInfoReplyinfo(@NotNull final ByteBuffer packet) throws IOException {
1888  final byte[] data = new byte[packet.remaining()];
1889  packet.get(data);
1890  try (final ByteArrayInputStream is = new ByteArrayInputStream(data)) {
1891  try (final InputStreamReader isr = new InputStreamReader(is)) {
1892  try (final BufferedReader d = new BufferedReader(isr)) {
1893  final String info = d.readLine();
1894  if (info == null) {
1895  throw new IOException("Truncated parameter in image_info");
1896  }
1897  //noinspection UnusedAssignment
1898  final int nrPics = Integer.parseInt(info);
1899  // XXX: replyinfo image_info not implemented
1900  }
1901  }
1902  }
1903  }
1904 
1910  private void processSkillInfoReplyinfo(@NotNull final ByteBuffer packet) throws IOException {
1911  model.getSkillSet().clearSkills();
1912  final byte[] data = new byte[packet.remaining()];
1913  packet.get(data);
1914  try (final ByteArrayInputStream is = new ByteArrayInputStream(data)) {
1915  try (final InputStreamReader isr = new InputStreamReader(is)) {
1916  try (final BufferedReader d = new BufferedReader(isr)) {
1917  while (true) {
1918  final CharSequence r = d.readLine();
1919  if (r == null) {
1920  break;
1921  }
1922 
1923  final String[] sk = PATTERN_DOT.split(r, 3);
1924  if (sk.length < 2 || sk.length > 3) {
1925  System.err.println("Ignoring skill definition for invalid skill: "+r+".");
1926  continue;
1927  }
1928 
1929  final int skillId;
1930  try {
1931  skillId = Integer.parseInt(sk[0]);
1932  } catch (final NumberFormatException ignored) {
1933  System.err.println("Ignoring skill definition for invalid skill: "+r+".");
1934  continue;
1935  }
1936 
1937  if (skillId < Stats.CS_STAT_SKILLINFO || skillId >= Stats.CS_STAT_SKILLINFO+Stats.CS_NUM_SKILLS) {
1938  System.err.println("Ignoring skill definition for invalid skill id "+skillId+": "+r+".");
1939  continue;
1940  }
1941 
1942  int face = -1;
1943  if (sk.length > 2) {
1944  try {
1945  face = Integer.parseInt(sk[2]);
1946  } catch (final NumberFormatException ignored) {
1947  System.err.println("Ignoring skill definition for invalid face: "+r+".");
1948  continue;
1949  }
1950  }
1951  model.getSkillSet().addSkill(skillId, sk[1], face);
1952  }
1953  }
1954  }
1955  }
1956  }
1957 
1962  private void processExpTableReplyinfo(@NotNull final ByteBuffer packet) {
1963  final int numLevels = getInt2(packet);
1964  final long[] expTable = new long[numLevels];
1965  for (int level = 1; level < numLevels; level++) {
1966  expTable[level] = getInt8(packet);
1967  }
1968  if (packet.hasRemaining()) {
1969  System.err.println("Ignoring excess data at end of exp_table");
1970  }
1971 
1972  model.getExperienceTable().setExpTable(expTable);
1973 
1974  if (loginMethod == 0) {
1976  sendAddme();
1977  } else {
1980  }
1981  }
1982 
1988  private void processKnowledgeInfoReplyinfo(@NotNull final ByteBuffer packet) throws IOException {
1989  model.getKnowledgeManager().clearTypes();
1990  final byte[] data = new byte[packet.remaining()];
1991  packet.get(data);
1992  try (final ByteArrayInputStream is = new ByteArrayInputStream(data)) {
1993  try (final InputStreamReader isr = new InputStreamReader(is)) {
1994  try (final BufferedReader d = new BufferedReader(isr)) {
1995  while (true) {
1996  final CharSequence r = d.readLine();
1997  if (r == null) {
1998  break;
1999  }
2000 
2001  final String[] sk = PATTERN_DOT.split(r);
2002  if (sk.length != 4) {
2003  System.err.println("Ignoring knowledge definition for invalid knowledge: "+r+".");
2004  continue;
2005  }
2006 
2007  final int face;
2008  try {
2009  face = Integer.parseInt(sk[2]);
2010  } catch (final NumberFormatException ignored) {
2011  System.err.println("Ignoring knowledge definition for invalid face: "+r+".");
2012  continue;
2013  }
2014 
2015  model.getKnowledgeManager().addKnowledgeType(sk[0], sk[1], face, sk[3].equals("1"));
2016  }
2017  }
2018  }
2019  }
2020  }
2021 
2026  private void processStartingMapReplyinfo(@NotNull final ByteBuffer packet) {
2027  final StartingMapBuilder sb = new StartingMapBuilder();
2028  while (packet.hasRemaining()) {
2029  final int type = getInt1(packet);
2030  final int length = getInt2(packet);
2031  switch (type) {
2032  case INFO_MAP_ARCH_NAME:
2033  final byte[] archName = new byte[length];
2034  packet.get(archName);
2035  sb.setArchName(archName);
2036  break;
2037 
2038  case INFO_MAP_NAME:
2039  sb.setName(getString(packet, length));
2040  break;
2041 
2042  case INFO_MAP_DESCRIPTION:
2043  sb.setDescription(getString(packet, length));
2044  break;
2045 
2046  default:
2047  System.err.println("Ignoring startingmap type "+type);
2048  break;
2049  }
2050  }
2052  }
2053 
2058  private void processRaceListReplyinfo(@NotNull final ByteBuffer packet) {
2059  while (packet.remaining() > 0 && packet.get(packet.position()) == '|') {
2060  packet.get();
2061  }
2062  final CharSequence raceList = getString(packet, packet.remaining());
2063  final String[] races = PATTERN_BAR.split(raceList);
2064  model.getNewCharacterInformation().setRaceList(races);
2065 
2066  for (final String race : races) {
2067  sendQueuedRequestinfo("race_info "+race);
2068  }
2069  }
2070 
2075  private void processClassListReplyinfo(@NotNull final ByteBuffer packet) {
2076  while (packet.remaining() > 0 && packet.get(packet.position()) == '|') {
2077  packet.get();
2078  }
2079  final CharSequence classList = getString(packet, packet.remaining());
2080  final String[] classes = PATTERN_BAR.split(classList);
2081  model.getNewCharacterInformation().setClassList(classes);
2082 
2083  for (final String class_ : classes) {
2084  sendQueuedRequestinfo("class_info "+class_);
2085  }
2086  }
2087 
2095  private void processClassRaceInfoReplyinfo(@NotNull final ByteBuffer packet, final boolean raceInfo) throws UnknownCommandException {
2096  final String raceName = getStringDelimiter(packet, '\n');
2097  final ClassRaceInfoBuilder rb = new ClassRaceInfoBuilder(raceName);
2098  while (packet.hasRemaining()) {
2099  final String type = getStringDelimiter(packet, ' ');
2100  switch (type) {
2101  case "name":
2102  rb.setName(getString(packet, getInt1(packet)));
2103  break;
2104 
2105  case "msg":
2106  rb.setMsg(getString(packet, getInt2(packet)));
2107  break;
2108 
2109  case "stats":
2110  parseClassRaceInfoStats(packet, rb);
2111  break;
2112 
2113  case "choice":
2114  parseClassRaceInfoChoice(packet, rb);
2115  break;
2116 
2117  default:
2118  System.err.println("Ignoring race_info type "+type);
2119  break;
2120  }
2121  }
2122  final ClassRaceInfo classRaceInfo = rb.finish();
2123  if (debugProtocol != null) {
2124  debugProtocol.debugProtocolWrite("recv replyinfo "+(raceInfo ? "race_info" : "class_info")+" "+classRaceInfo);
2125  }
2126  if (raceInfo) {
2127  model.getNewCharacterInformation().addRaceInfo(classRaceInfo);
2128  } else {
2129  model.getNewCharacterInformation().addClassInfo(classRaceInfo);
2130  }
2131  }
2132 
2138  private void processNewCharInfoReplyinfo(@NotNull final ByteBuffer packet) throws UnknownCommandException {
2139  final NewCharInfoBuilder newCharInfoBuilder = new NewCharInfoBuilder();
2140  while (packet.hasRemaining()) {
2141  final int len = getInt1(packet)-1;
2142  final String line = getString(packet, len);
2143  getInt1(packet); // skip trailing \0 byte
2144 
2145  final String[] tokens = PATTERN_SPACE.split(line, 3);
2146  if (tokens.length != 3) {
2147  throw new UnknownCommandException("syntax error in replyinfo newcharinfo: "+line);
2148  }
2149  final String typeString = tokens[0];
2150  final String variableName = tokens[1];
2151  final String values = tokens[2];
2152  switch (typeString) {
2153  case "R":
2154  parseNewCharInfoValue(newCharInfoBuilder, true, variableName, values);
2155  break;
2156 
2157  case "O":
2158  parseNewCharInfoValue(newCharInfoBuilder, false, variableName, values);
2159  break;
2160 
2161  case "V":
2162  parseNewCharInfoValues(newCharInfoBuilder, variableName, values);
2163  break;
2164 
2165  case "I":
2166  parseNewCharInfoInformational(variableName, values);
2167  break;
2168 
2169  default:
2170  throw new UnknownCommandException("unknown type '"+typeString+"' in replyinfo newcharinfo: "+line);
2171  }
2172  }
2173  final NewCharInfo newCharInfo = newCharInfoBuilder.finish();
2174  if (debugProtocol != null) {
2175  debugProtocol.debugProtocolWrite("recv replyinfo newcharinfo "+newCharInfo);
2176  }
2177  model.getNewCharacterInformation().setNewCharInfo(newCharInfo);
2178  }
2179 
2188  private static void parseNewCharInfoValue(@NotNull final NewCharInfoBuilder newCharInfoBuilder, final boolean required, @NotNull final String variableName, @NotNull final String values) throws UnknownCommandException {
2189  if (variableName.equals("race")) {
2190  if (!values.equals("requestinfo")) {
2191  throw new UnknownCommandException(variableName+"="+values+" is not supported in replyinfo newcharinfo");
2192  }
2193  newCharInfoBuilder.setRaceChoice();
2194  } else if (variableName.equals("class")) {
2195  if (!values.equals("requestinfo")) {
2196  throw new UnknownCommandException(variableName+"="+values+" is not supported in replyinfo newcharinfo");
2197  }
2198  newCharInfoBuilder.setClassChoice();
2199  } else if (variableName.equals("startingmap")) {
2200  if (!values.equals("requestinfo")) {
2201  throw new UnknownCommandException(variableName+"="+values+" is not supported in replyinfo newcharinfo");
2202  }
2203  newCharInfoBuilder.setStartingMapChoice();
2204  } else if (!required) {
2205  System.err.println("unknown variable name '"+variableName+"' in replyinfo newcharinfo");
2206  } else {
2207  throw new UnknownCommandException("unknown variable name '"+variableName+"' in replyinfo newcharinfo");
2208  }
2209  }
2210 
2218  private static void parseNewCharInfoValues(@NotNull final NewCharInfoBuilder newCharInfoBuilder, @NotNull final String variableName, @NotNull final String values) throws UnknownCommandException {
2219  switch (variableName) {
2220  case "points":
2221  final int points;
2222  try {
2223  points = Integer.parseInt(values);
2224  } catch (final NumberFormatException ignored) {
2225  throw new UnknownCommandException("'"+variableName+"' variable in replyinfo newcharinfo has invalid value '"+values+"'.");
2226  }
2227  newCharInfoBuilder.setPoints(points);
2228  break;
2229 
2230  case "statrange":
2231  final String[] tmp = PATTERN_SPACE.split(values, 2);
2232  if (tmp.length != 2) {
2233  throw new UnknownCommandException("'"+variableName+"' variable in replyinfo newcharinfo has invalid value '"+values+"'.");
2234  }
2235  final int minValue;
2236  final int maxValue;
2237  try {
2238  minValue = Integer.parseInt(tmp[0]);
2239  maxValue = Integer.parseInt(tmp[1]);
2240  } catch (final NumberFormatException ignored) {
2241  throw new UnknownCommandException("'"+variableName+"' variable in replyinfo newcharinfo has invalid value '"+values+"'.");
2242  }
2243  newCharInfoBuilder.setStatRange(minValue, maxValue);
2244  break;
2245 
2246  case "statname":
2247  newCharInfoBuilder.setStatNames(PATTERN_SPACE.split(values));
2248  break;
2249 
2250  default:
2251  throw new UnknownCommandException("unknown variable name '"+variableName+"' in replyinfo newcharinfo");
2252  }
2253  }
2254 
2260  private static void parseNewCharInfoInformational(@NotNull final String variableName, @NotNull final String values) {
2261  System.err.println("ignoring informational "+variableName+"="+values+" in replyinfo newcharinfo");
2262  }
2263 
2271  private static void parseClassRaceInfoStats(@NotNull final ByteBuffer packet, @NotNull final ClassRaceInfoBuilder rb) throws UnknownCommandException {
2272  while (packet.hasRemaining()) {
2273  final int statNo = getInt1(packet);
2274  switch (statNo) {
2275  case 0:
2276  return;
2277 
2278  case Stats.CS_STAT_HP:
2279  case Stats.CS_STAT_MAXHP:
2280  case Stats.CS_STAT_SP:
2281  case Stats.CS_STAT_MAXSP:
2282  case Stats.CS_STAT_STR:
2283  case Stats.CS_STAT_INT:
2284  case Stats.CS_STAT_WIS:
2285  case Stats.CS_STAT_DEX:
2286  case Stats.CS_STAT_CON:
2287  case Stats.CS_STAT_CHA:
2288  case Stats.CS_STAT_LEVEL:
2289  case Stats.CS_STAT_WC:
2290  case Stats.CS_STAT_AC:
2291  case Stats.CS_STAT_DAM:
2292  case Stats.CS_STAT_ARMOUR:
2293  case Stats.CS_STAT_FOOD:
2294  case Stats.CS_STAT_POW:
2295  case Stats.CS_STAT_GRACE:
2296  case Stats.CS_STAT_MAXGRACE:
2297  case Stats.CS_STAT_FLAGS:
2298  case Stats.CS_STAT_RACE_STR:
2299  case Stats.CS_STAT_RACE_INT:
2300  case Stats.CS_STAT_RACE_WIS:
2301  case Stats.CS_STAT_RACE_DEX:
2302  case Stats.CS_STAT_RACE_CON:
2303  case Stats.CS_STAT_RACE_CHA:
2304  case Stats.CS_STAT_RACE_POW:
2305  case Stats.CS_STAT_BASE_STR:
2306  case Stats.CS_STAT_BASE_INT:
2307  case Stats.CS_STAT_BASE_WIS:
2308  case Stats.CS_STAT_BASE_DEX:
2309  case Stats.CS_STAT_BASE_CON:
2310  case Stats.CS_STAT_BASE_CHA:
2311  case Stats.CS_STAT_BASE_POW:
2319  case Stats.CS_STAT_GOLEM_HP:
2321  final short int2Param = (short)getInt2(packet);
2322  rb.setStatAdjustment(statNo, int2Param);
2323  break;
2324 
2325  case Stats.CS_STAT_EXP:
2326  case Stats.CS_STAT_SPEED:
2327  case Stats.CS_STAT_WEAP_SP:
2332  final int int4Param = getInt4(packet);
2333  rb.setStatAdjustment(statNo, int4Param);
2334  break;
2335 
2336  case Stats.CS_STAT_EXP64:
2337  final long int8Param = getInt8(packet);
2338  rb.setStatAdjustment(statNo, int8Param);
2339  break;
2340 
2341  case Stats.CS_STAT_RANGE:
2342  case Stats.CS_STAT_TITLE:
2343  final int length = getInt1(packet);
2344  final String strParam = getString(packet, length);
2345  System.err.println("replyinfo race/class_info: string stat "+statNo+" not implemented");
2346  break;
2347 
2348  default:
2350  final short int2Param2 = (short)getInt2(packet);
2351  rb.setStatAdjustment(statNo, int2Param2);
2352  } else if (Stats.CS_STAT_SKILLINFO <= statNo && statNo < Stats.CS_STAT_SKILLINFO+Stats.CS_NUM_SKILLS) {
2353  final int level = getInt1(packet);
2354  final long experience = getInt8(packet);
2355  System.err.println("replyinfo race/class_info: skill stat "+statNo+" not implemented");
2356  } else {
2357  throw new UnknownCommandException("unknown stat value: "+statNo);
2358  }
2359  break;
2360  }
2361  }
2362 
2363  throw new UnknownCommandException("truncated stats entry in replyinfo race/class_info");
2364  }
2365 
2372  private static void parseClassRaceInfoChoice(@NotNull final ByteBuffer packet, @NotNull final ClassRaceInfoBuilder rb) {
2373  final String choiceName = getString(packet, getInt1(packet));
2374  final String choiceDescription = getString(packet, getInt1(packet));
2375  final String archName = getString(packet, getInt1(packet));
2376  final String archDesc = getString(packet, getInt1(packet));
2377  final Map<String, String> choices = new LinkedHashMap<>();
2378  choices.put(archName, archDesc);
2379  while (true) {
2380  final int archNameLength = getInt1(packet);
2381  if (archNameLength == 0) {
2382  break;
2383  }
2384  final String archName2 = getString(packet, archNameLength);
2385  final String archDesc2 = getString(packet, getInt1(packet));
2386  choices.put(archName2, archDesc2);
2387  }
2388  rb.addChoice(new Choice(choiceName, choiceDescription, choices));
2389  }
2390 
2396  private void processAccountPlayers(@NotNull final ByteBuffer packet) throws UnknownCommandException {
2397  final int args = packet.position();
2398 
2399  if (accountName == null) {
2400  throw new UnknownCommandException("accountplayers without account");
2401  }
2402  fireStartAccountList(accountName);
2403 
2404  // number of characters
2405  final int total = getInt1(packet);
2406  final AccountPlayerBuilder accountPlayerBuilder = new AccountPlayerBuilder();
2407  for (int count = 0; count < total; count++) {
2408  final CharacterInformation characterInformation = parseAccountPlayer(packet, accountPlayerBuilder);
2409  if (debugProtocol != null) {
2410  debugProtocol.debugProtocolWrite("recv accountplayers entry: "+characterInformation);
2411  }
2412  fireAddAccount(characterInformation);
2413  }
2414  if (packet.hasRemaining()) {
2415  throw new UnknownCommandException("invalid accountplayers reply, pos="+packet.position());
2416  }
2417 
2418  fireEndAccountList(total);
2419 
2420  packet.reset();
2421  notifyPacketWatcherListenersMixed(packet, args);
2422  }
2423 
2431  @NotNull
2432  private CharacterInformation parseAccountPlayer(@NotNull final ByteBuffer packet, @NotNull final AccountPlayerBuilder accountPlayerBuilder) throws UnknownCommandException {
2433  while (true) {
2434  if (!packet.hasRemaining()) {
2435  throw new UnknownCommandException("truncated accountplayers reply");
2436  }
2437 
2438  final int len = getInt1(packet);
2439  if (len == 0) {
2440  break;
2441  }
2442 
2443  final int type = getInt1(packet);
2444  switch (type) {
2445  case ACL_NAME:
2446  accountPlayerBuilder.setName(getString(packet, len-1));
2447  break;
2448 
2449  case ACL_CLASS:
2450  accountPlayerBuilder.setClass(getString(packet, len-1));
2451  break;
2452 
2453  case ACL_RACE:
2454  accountPlayerBuilder.setRace(getString(packet, len-1));
2455  break;
2456 
2457  case ACL_LEVEL:
2458  accountPlayerBuilder.setLevel(getInt2(packet));
2459  break;
2460 
2461  case ACL_FACE:
2462  accountPlayerBuilder.setFace(getString(packet, len-1));
2463  break;
2464 
2465  case ACL_PARTY:
2466  accountPlayerBuilder.setParty(getString(packet, len-1));
2467  break;
2468 
2469  case ACL_MAP:
2470  accountPlayerBuilder.setMap(getString(packet, len-1));
2471  break;
2472 
2473  case ACL_FACE_NUM:
2474  accountPlayerBuilder.setFaceNumber(getInt2(packet));
2475  break;
2476 
2477  default:
2478  // ignore those values we don't understand
2479  if (debugProtocol != null) {
2480  debugProtocol.debugProtocolWrite("recv accountplayers unknown="+type);
2481  }
2482  packet.position(packet.position()+len-1);
2483  break;
2484  }
2485  }
2486 
2487  return accountPlayerBuilder.finish();
2488  }
2489 
2494  private void processAddmeFailed(@NotNull final ByteBuffer packet) {
2495  final int args = packet.position();
2496  // XXX: addme_failed command not implemented
2497  notifyPacketWatcherListenersNoData(packet, args);
2498  }
2499 
2504  private void processAddmeSuccess(@NotNull final ByteBuffer packet) {
2505  final int args = packet.position();
2506 
2507  if (clientSocketState != ClientSocketState.CONNECTED) {
2508  if (clientSocketState == ClientSocketState.ADDME) {
2509  // servers without account support
2511  } else if (clientSocketState == ClientSocketState.ACCOUNT_INFO) {
2512  fireStartPlaying();
2514  }
2515  negotiateMapSize(preferredMapWidth, preferredMapHeight);
2516  }
2517 
2518  notifyPacketWatcherListenersNoData(packet, args);
2519  }
2520 
2525  private void processAddQuest(@NotNull final ByteBuffer packet) {
2526  final int args = packet.position();
2527  while (packet.hasRemaining()) {
2528  final int code = getInt4(packet);
2529  final int titleLength = getInt2(packet);
2530  final String title = getString(packet, titleLength);
2531  final int face = getInt4(packet);
2532  final int replay = getInt1(packet);
2533  final int parent = getInt4(packet);
2534  final int end = getInt1(packet);
2535  final int stepLength = getInt2(packet);
2536  final String step = stepLength > 0 ? getString(packet, stepLength) : "";
2537 
2538  if (debugProtocol != null) {
2539  debugProtocol.debugProtocolWrite("recv addquest code="+code+" title="+title+" face="+face+"replay="+replay+" end="+end+" desc="+step);
2540  }
2541  model.getQuestsManager().addQuest(code, title, face, replay == 1, parent, end == 1, step);
2542  }
2543  notifyPacketWatcherListenersMixed(packet, args);
2544  }
2545 
2550  private void processAddKnowledge(@NotNull final ByteBuffer packet) {
2551  final int args = packet.position();
2552  while (packet.hasRemaining()) {
2553  final int code = getInt4(packet);
2554  final int typeLength = getInt2(packet);
2555  final String type = getString(packet, typeLength);
2556  final int titleLength = getInt2(packet);
2557  final String title = getString(packet, titleLength);
2558  final int face = getInt4(packet);
2559 
2560  if (debugProtocol != null) {
2561  debugProtocol.debugProtocolWrite("recv addknowledge code="+code+"type="+type+"title="+title+" face="+face);
2562  }
2563  model.getKnowledgeManager().addKnowledge(code, type, title, face);
2564  }
2565  notifyPacketWatcherListenersMixed(packet, args);
2566  }
2567 
2572  private void processAddSpell(@NotNull final ByteBuffer packet) {
2573  final int args = packet.position();
2574  while (packet.hasRemaining()) {
2575  final int tag = getInt4(packet);
2576  final int level = getInt2(packet);
2577  final int castingTime = getInt2(packet);
2578  final int mana = getInt2(packet);
2579  final int grace = getInt2(packet);
2580  final int damage = getInt2(packet);
2581  final int skill = getInt1(packet);
2582  final int path = getInt4(packet);
2583  final int face = getInt4(packet);
2584  final int nameLength = getInt1(packet);
2585  final String name = getString(packet, nameLength);
2586  final int messageLength = getInt2(packet);
2587  final String message = getString(packet, messageLength);
2588  if (debugProtocol != null) {
2589  debugProtocol.debugProtocolWrite("recv addspell tag="+tag+" lvl="+level+" time="+castingTime+" sp="+mana+" gr="+grace+" dam="+damage+" skill="+skill+" path="+path+" face="+face+" name="+name+" msg="+message);
2590  }
2591  model.getSpellsManager().addSpell(tag, level, castingTime, mana, grace, damage, skill, path, face, name, message);
2592  }
2593  notifyPacketWatcherListenersMixed(packet, args);
2594  }
2595 
2601  private void processAnim(@NotNull final ByteBuffer packet) throws UnknownCommandException {
2602  final int args = packet.position();
2603  final int num = getInt2(packet);
2604  final int flags = getInt2(packet);
2605  final int[] faces = new int[packet.remaining()/2];
2606  if (faces.length <= 0) {
2607  throw new UnknownCommandException("no faces in anim command");
2608  }
2609  for (int i = 0; i < faces.length; i++) {
2610  faces[i] = getInt2(packet);
2611  }
2612  if (packet.hasRemaining()) {
2613  throw new UnknownCommandException("excess data at end of anim command");
2614  }
2615  if (debugProtocol != null) {
2616  debugProtocol.debugProtocolWrite("recv anim num="+num+" flags="+flags+" faces="+Arrays.toString(faces));
2617  }
2618  if ((num&~0x1FFF) != 0) {
2619  throw new UnknownCommandException("invalid animation id "+num);
2620  }
2621  fireAddAnimation(num&0x1FFF, flags, faces);
2623  }
2624 
2630  private void processComc(@NotNull final ByteBuffer packet) throws UnknownCommandException {
2631  final int args = packet.position();
2632  final int packetNo = getInt2(packet);
2633  final int time = getInt4(packet);
2634  if (packet.hasRemaining()) {
2635  throw new UnknownCommandException("excess data at end of comc command");
2636  }
2637  if (debugProtocol != null) {
2638  debugProtocol.debugProtocolWrite("recv comc no="+packetNo+" time="+time);
2639  }
2640  fireCommandComcReceived(packetNo, time);
2642  }
2643 
2649  private void processDelInv(@NotNull final ByteBuffer packet) throws UnknownCommandException {
2650  final int args = packet.position();
2651  int tag = 0;
2652  do {
2653  tag = tag*10+parseDigit(packet.get());
2654  } while (packet.hasRemaining());
2655  if (packet.hasRemaining()) {
2656  throw new UnknownCommandException("excess data at end of delinv command");
2657  }
2658  if (debugProtocol != null) {
2659  debugProtocol.debugProtocolWrite("recv delinv tag="+tag);
2660  }
2661  fireDelinvReceived(tag);
2662  notifyPacketWatcherListenersAscii(packet, args);
2663  }
2664 
2670  private void processDelItem(@NotNull final ByteBuffer packet) throws UnknownCommandException {
2671  final int args = packet.position();
2672  final int[] tags = new int[packet.remaining()/4];
2673  for (int i = 0; i < tags.length; i++) {
2674  tags[i] = getInt4(packet);
2675  }
2676  if (packet.hasRemaining()) {
2677  throw new UnknownCommandException("excess data at end of delitem command");
2678  }
2679  if (debugProtocol != null) {
2680  debugProtocol.debugProtocolWrite("recv delitem tags="+Arrays.toString(tags));
2681  }
2682  fireDelitemReceived(tags);
2684  }
2685 
2691  private void processDelSpell(@NotNull final ByteBuffer packet) throws UnknownCommandException {
2692  final int args = packet.position();
2693  final int tag = getInt4(packet);
2694  if (packet.hasRemaining()) {
2695  throw new UnknownCommandException("excess data at end of delspell command");
2696  }
2697  if (debugProtocol != null) {
2698  debugProtocol.debugProtocolWrite("recv delspell tag="+tag);
2699  }
2700  model.getSpellsManager().deleteSpell(tag);
2702  }
2703 
2709  private void processDrawExtInfo(@NotNull final ByteBuffer packet) throws UnknownCommandException {
2710  final int args = packet.position();
2711  int color = 0;
2712  do {
2713  color = color*10+parseDigit(packet.get());
2714  } while (packet.get(packet.position()) != ' ');
2715  packet.get();
2716 
2717  int type = 0;
2718  do {
2719  type = type*10+parseDigit(packet.get());
2720  } while (packet.get(packet.position()) != ' ');
2721  packet.get();
2722 
2723  int subtype = 0;
2724  do {
2725  subtype = subtype*10+parseDigit(packet.get());
2726  } while (packet.get(packet.position()) != ' ');
2727  packet.get();
2728 
2729  final String message = getString(packet, packet.remaining());
2730 
2731  if (debugProtocol != null) {
2732  debugProtocol.debugProtocolWrite("recv drawextinfo color="+color+" type="+type+"/"+subtype+" msg="+message);
2733  }
2734 
2735  drawextinfo(color, type, subtype, message);
2736  notifyPacketWatcherListenersAscii(packet, args);
2737  }
2738 
2744  private void processDrawInfo(@NotNull final ByteBuffer packet) throws UnknownCommandException {
2745  final int args = packet.position();
2746  int color = 0;
2747  do {
2748  color = color*10+parseDigit(packet.get());
2749  } while (packet.get(packet.position()) != ' ');
2750  packet.get();
2751 
2752  final String message = getString(packet, packet.remaining());
2753 
2754  if (debugProtocol != null) {
2755  debugProtocol.debugProtocolWrite("recv drawinfo color="+color+" msg="+message);
2756  }
2757 
2758  drawInfo(message, color);
2759  notifyPacketWatcherListenersAscii(packet, args);
2760  }
2761 
2766  private void processExtendedInfoSet(@NotNull final ByteBuffer packet) {
2767  final int args = packet.position();
2768  do {
2769  final int startPos = packet.position();
2770  while (packet.hasRemaining() && packet.get(packet.position()) != ' ') {
2771  packet.get();
2772  }
2773  final String string = newString(packet, startPos, packet.position()-startPos);
2774  packet.get();
2775  if (debugProtocol != null) {
2776  debugProtocol.debugProtocolWrite("recv ExtendedInfoSet "+string);
2777  }
2778  // XXX: ExtendedInfoSet command not implemented
2779  } while (packet.hasRemaining());
2780  notifyPacketWatcherListenersNoData(packet, args);
2781  }
2782 
2787  private void processExtendedTextSet(@NotNull final ByteBuffer packet) {
2788  final int args = packet.position();
2789  while (true) {
2790  final int startPos = packet.position();
2791  while (packet.hasRemaining() && packet.get(packet.position()) != ' ') {
2792  packet.get();
2793  }
2794  final String type = newString(packet, startPos, packet.position()-startPos);
2795  if (debugProtocol != null) {
2796  debugProtocol.debugProtocolWrite("recv ExtendedTextSet "+type);
2797  }
2798  // XXX: ExtendedTextSet command not implemented
2799  if (!packet.hasRemaining()) {
2800  break;
2801  }
2802  packet.get();
2803  }
2804  notifyPacketWatcherListenersNoData(packet, args);
2805  }
2806 
2811  private void processFace2(@NotNull final ByteBuffer packet) {
2812  final int args = packet.position();
2813  final int faceNum = getInt2(packet);
2814  final int faceSetNum = getInt1(packet);
2815  final int faceChecksum = getInt4(packet);
2816  final String faceName = getString(packet, packet.remaining()).intern();
2817  if (debugProtocol != null) {
2818  debugProtocol.debugProtocolWrite("recv face2 num="+faceNum+" set="+faceSetNum+" checksum="+faceChecksum+" name="+faceName);
2819  }
2820  fireFaceReceived(faceNum, faceSetNum, faceChecksum, faceName);
2821  notifyPacketWatcherListenersMixed(packet, args);
2822  }
2823 
2828  private void processFailure(@NotNull final ByteBuffer packet) {
2829  final String full = getString(packet, packet.remaining());
2830  final String command;
2831  final String message;
2832  final int idx = full.indexOf(' ');
2833  if (idx == -1) {
2834  command = full;
2835  message = "";
2836  } else {
2837  command = full.substring(0, idx);
2838  message = full.substring(idx+1);
2839  }
2840  if (debugProtocol != null) {
2841  debugProtocol.debugProtocolWrite("recv failure command="+command+" message="+message);
2842  }
2843 
2844  fireFailure(command, message);
2845  }
2846 
2852  private void processGoodbye(@NotNull final ByteBuffer packet) throws UnknownCommandException {
2853  final int args = packet.position();
2854  if (packet.hasRemaining()) {
2855  throw new UnknownCommandException("excess data at end of goodbye command");
2856  }
2857  if (debugProtocol != null) {
2858  debugProtocol.debugProtocolWrite("recv goodbye");
2859  }
2860  // XXX: goodbye command not implemented
2861  notifyPacketWatcherListenersNoData(packet, args);
2862  }
2863 
2869  private void processImage2(@NotNull final ByteBuffer packet) throws UnknownCommandException {
2870  final int args = packet.position();
2871  final int faceNum = getInt4(packet);
2872  final int faceSetNum = getInt1(packet);
2873  final int len = getInt4(packet);
2874  if (packet.remaining() != len) {
2875  throw new UnknownCommandException("excess data at end of image2 command");
2876  }
2877  final int faceDataPosition = packet.position();
2878  if (debugProtocol != null) {
2879  debugProtocol.debugProtocolWrite("recv image2 face="+faceNum+" set="+faceSetNum+" len="+len);
2880  }
2881  packet.position(faceDataPosition);
2882  model.getAskfaceFaceQueue().faceReceived(faceNum, faceSetNum, packet);
2883  notifyPacketWatcherListenersMixed(packet, args);
2884  }
2885 
2891  private void processItem2(@NotNull final ByteBuffer packet) throws UnknownCommandException {
2892  final int args = packet.position();
2893  final int location = getInt4(packet);
2894  while (packet.hasRemaining()) {
2895  final int tag = getInt4(packet);
2896  final int flags = getInt4(packet);
2897  final int weight = getInt4(packet);
2898  final int faceNum = getInt4(packet);
2899  final int nameLength = getInt1(packet);
2900  final String[] names = getString(packet, nameLength).split("\0", 2);
2901  final String name = names[0].intern();
2902  final String namePl = names.length < 2 ? name : names[1].intern();
2903  final int anim = getInt2(packet);
2904  final int animSpeed = getInt1(packet);
2905  final int nrof = getInt4(packet);
2906  final int type = getInt2(packet);
2907  if (debugProtocol != null) {
2908  debugProtocol.debugProtocolWrite("recv item2 location="+location+" tag="+tag+" flags="+flags+" weight="+weight+" face="+faceNum+" name="+name+" name_pl="+namePl+" anim="+anim+" anim_speed="+animSpeed+" nrof="+nrof+" type="+type);
2909  }
2910  fireAddItemReceived(location, tag, flags, weight, faceNum, name, namePl, anim, animSpeed, nrof, type);
2911  }
2912  if (packet.hasRemaining()) {
2913  throw new UnknownCommandException("excess data at end of item2 command");
2914  }
2915  notifyPacketWatcherListenersMixed(packet, args);
2916  }
2917 
2923  private void processMagicMap(@NotNull final ByteBuffer packet) throws UnknownCommandException {
2924  final int args = packet.position();
2925 
2926  final boolean widthSign = packet.get(packet.position()) == '-';
2927  if (widthSign) {
2928  packet.get();
2929  }
2930  int width = 0;
2931  do {
2932  width = width*10+parseDigit(packet.get());
2933  } while (packet.get(packet.position()) != ' ');
2934  packet.get();
2935  if (widthSign) {
2936  width = -width;
2937  }
2938 
2939  final boolean heightSign = packet.get(packet.position()) == '-';
2940  if (heightSign) {
2941  packet.get();
2942  }
2943  int height = 0;
2944  do {
2945  height = height*10+parseDigit(packet.get());
2946  } while (packet.get(packet.position()) != ' ');
2947  packet.get();
2948  if (heightSign) {
2949  height = -height;
2950  }
2951 
2952  final boolean pxSign = packet.get(packet.position()) == '-';
2953  if (pxSign) {
2954  packet.get();
2955  }
2956  int px = 0;
2957  do {
2958  px = px*10+parseDigit(packet.get());
2959  } while (packet.get(packet.position()) != ' ');
2960  packet.get();
2961  if (pxSign) {
2962  px = -px;
2963  }
2964 
2965  final boolean pySign = packet.get(packet.position()) == '-';
2966  if (pySign) {
2967  packet.get();
2968  }
2969  int py = 0;
2970  do {
2971  py = py*10+parseDigit(packet.get());
2972  } while (packet.get(packet.position()) != ' ');
2973  packet.get();
2974  if (pySign) {
2975  py = -py;
2976  }
2977 
2978  if (debugProtocol != null) {
2979  debugProtocol.debugProtocolWrite("recv magicmap size="+width+"x"+height+" player="+px+"/"+py+" len="+packet.remaining());
2980  }
2981 
2982  if (packet.remaining() != width*height) {
2983  throw new UnknownCommandException("invalid magicmap command");
2984  }
2985 
2986  final byte[][] data = new byte[height][width];
2987  for (int y = 0; y < height; y++) {
2988  packet.get(data[y]);
2989  }
2990  fireMagicMap(-px+(currentMapWidth-1)/2, -py+(currentMapHeight-1)/2, data);
2991  notifyPacketWatcherListenersMixed(packet, args);
2992  }
2993 
2999  private void processMap2(@NotNull final ByteBuffer packet) throws UnknownCommandException {
3000  final int args = packet.position();
3001  if (debugProtocol != null) {
3002  debugProtocol.debugProtocolWrite("recv map2 begin");
3003  }
3004  if (crossfireUpdateMapListener != null) {
3005  synchronized (crossfireUpdateMapListener.mapBegin()) {
3006  while (packet.hasRemaining()) {
3007  final int coord = getInt2(packet);
3008  final int x = ((coord>>10)&0x3F)-Map2.COORD_OFFSET;
3009  final int y = ((coord>>4)&0x3F)-Map2.COORD_OFFSET;
3010  final int coordType = coord&0xF;
3011 
3012  switch (coordType) {
3013  case Map2.TYPE_COORDINATE:
3014  cmdMap2Coordinate(packet, x, y);
3015  break;
3016 
3017  case Map2.TYPE_SCROLL:
3018  if (debugProtocol != null) {
3019  debugProtocol.debugProtocolWrite("recv map2 "+x+"/"+y+" scroll");
3020  }
3021  assert crossfireUpdateMapListener != null;
3022  crossfireUpdateMapListener.mapScroll(x, y);
3023  break;
3024 
3025  default:
3026  if (debugProtocol != null) {
3027  debugProtocol.debugProtocolWrite("recv map2 "+x+"/"+y+" <invalid>");
3028  }
3029  throw new UnknownCommandException("map2 command contains unexpected coordinate type "+coordType);
3030  }
3031  }
3032  assert crossfireUpdateMapListener != null;
3033  crossfireUpdateMapListener.mapEnd();
3034  }
3035  }
3036  if (debugProtocol != null) {
3037  debugProtocol.debugProtocolWrite("recv map2 end");
3038  }
3040  }
3041 
3046  private void processMapExtended(@NotNull final ByteBuffer packet) {
3047  final int args = packet.position();
3048  if (debugProtocol != null) {
3049  debugProtocol.debugProtocolWrite("recv mapextended");
3050  }
3051 
3052  // XXX: "MapExtended" command not yet implemented
3053 
3054  notifyPacketWatcherListenersMixed(packet, args);
3055  }
3056 
3061  private void processMusic(@NotNull final ByteBuffer packet) {
3062  final int args = packet.position();
3063  final String music = getString(packet, packet.remaining());
3064  if (debugProtocol != null) {
3065  debugProtocol.debugProtocolWrite("recv music "+music);
3066  }
3067 
3068  fireMusicReceived(music);
3069  notifyPacketWatcherListenersAscii(packet, args);
3070  }
3071 
3077  private void processNewMap(@NotNull final ByteBuffer packet) throws UnknownCommandException {
3078  final int args = packet.position();
3079  if (packet.hasRemaining()) {
3080  throw new UnknownCommandException("excess data at end of newmap command");
3081  }
3082  if (debugProtocol != null) {
3083  debugProtocol.debugProtocolWrite("recv newmap");
3084  }
3085  fireNewMap();
3086  notifyPacketWatcherListenersNoData(packet, args);
3087  }
3088 
3094  private void processPickup(@NotNull final ByteBuffer packet) throws UnknownCommandException {
3095  final int args = packet.position();
3096  final int pickupOptions = getInt4(packet);
3097  if (packet.hasRemaining()) {
3098  throw new UnknownCommandException("excess data at end of pickup command");
3099  }
3100  if (debugProtocol != null) {
3101  debugProtocol.debugProtocolWrite("recv pickup options="+pickupOptions);
3102  }
3103  firePickupChanged(pickupOptions);
3104  notifyPacketWatcherListenersMixed(packet, args);
3105  }
3106 
3112  private void processPlayer(@NotNull final ByteBuffer packet) throws UnknownCommandException {
3113  final int args = packet.position();
3114  final int tag = getInt4(packet);
3115  final int weight = getInt4(packet);
3116  final int faceNum = getInt4(packet);
3117  final int nameLength = getInt1(packet);
3118  final String name = getString(packet, nameLength);
3119  if (packet.hasRemaining()) {
3120  throw new UnknownCommandException("excess data at end of player command");
3121  }
3122  if (debugProtocol != null) {
3123  debugProtocol.debugProtocolWrite("recv player tag="+tag+" weight="+weight+" face="+faceNum+" name="+name);
3124  }
3125  firePlayerReceived(tag, weight, faceNum, name);
3126  notifyPacketWatcherListenersMixed(packet, args);
3127  }
3128 
3134  private void processQuery(@NotNull final ByteBuffer packet) throws UnknownCommandException {
3135  final int args = packet.position();
3136  int flags = 0;
3137  do {
3138  flags = flags*10+parseDigit(packet.get());
3139  } while (packet.get(packet.position()) != ' ');
3140  packet.get();
3141 
3142  final String text = getString(packet, packet.remaining());
3143 
3144  if (debugProtocol != null) {
3145  debugProtocol.debugProtocolWrite("recv query flags="+flags+" text="+text);
3146  }
3147 
3148  // XXX: hack to process "What is your name?" prompt even before addme_success is received
3149  if (clientSocketState != ClientSocketState.CONNECTED) {
3151  negotiateMapSize(preferredMapWidth, preferredMapHeight);
3152  }
3153  fireCommandQueryReceived(text, flags);
3154  notifyPacketWatcherListenersAscii(packet, args);
3155  }
3156 
3162  private void processReplyInfo(@NotNull final ByteBuffer packet) throws UnknownCommandException {
3163  final int args = packet.position();
3164  final int startPos = packet.position();
3165  while (packet.hasRemaining() && packet.get(packet.position()) != '\n' && packet.get(packet.position()) != ' ') {
3166  packet.get();
3167  }
3168  final String infoType = newString(packet, startPos, packet.position()-startPos);
3169  if (packet.hasRemaining()) {
3170  packet.get();
3171  }
3172  if (debugProtocol != null) {
3173  debugProtocol.debugProtocolWrite("recv replyinfo type="+infoType+" len="+packet.remaining());
3174  }
3175  try {
3176  cmdReplyinfo(infoType, packet);
3177  } catch (final IOException ex) {
3178  throw new UnknownCommandException("invalid replyinfo command: "+ex.getMessage(), ex);
3179  }
3180  notifyPacketWatcherListenersAscii(packet, args);
3181  }
3182 
3188  @SuppressWarnings("IfStatementWithIdenticalBranches")
3189  private void processSetup(@NotNull final ByteBuffer packet) throws UnknownCommandException {
3190  final int args = packet.position();
3191  final List<String> options = new ArrayList<>();
3192  while (packet.hasRemaining()) {
3193  while (packet.get(packet.position()) == ' ') {
3194  packet.get();
3195  }
3196  final int startPos = packet.position();
3197  while (packet.hasRemaining() && packet.get(packet.position()) != ' ') {
3198  packet.get();
3199  }
3200  options.add(newString(packet, startPos, packet.position()-startPos));
3201  if (packet.hasRemaining()) {
3202  packet.get();
3203  }
3204  }
3205  if (debugProtocol != null) {
3206  debugProtocol.debugProtocolWrite("recv setup "+options);
3207  }
3208  if (options.size()%2 != 0) {
3209  throw new UnknownCommandException("odd number of arguments in setup command");
3210  }
3211  for (int i = 0; i+1 < options.size(); i += 2) {
3212  final String option = options.get(i);
3213  final String value = options.get(i+1);
3214  switch (option) {
3215  case "spellmon":
3216  if (!value.equals("1")) {
3217  throw new UnknownCommandException("Error: the server is too old for this client since it does not support the spellmon=1 setup option.");
3218  }
3219  break;
3220 
3221  case "sound2":
3222  // ignore: if the server sends sound info it is processed
3223  break;
3224 
3225  case "exp64":
3226  // Ignored since it only enables additional/improved stat
3227  // commands but the old version is also supported.
3228  break;
3229 
3230  case "newmapcmd":
3231  if (!value.equals("1")) {
3232  throw new UnknownCommandException("Error: the server is too old for this client since it does not support the newmapcmd=1 setup option.");
3233  }
3234  break;
3235 
3236  case "facecache":
3237  if (!value.equals("1")) {
3238  throw new UnknownCommandException("the server is too old for this client since it does not support the facecache=1 setup option.");
3239  }
3240  break;
3241 
3242  case "extendedTextInfos":
3243  if (!value.equals("1")) {
3244  throw new UnknownCommandException("the server is too old for this client since it does not support the extendedTextInfos=1 setup option.");
3245  }
3246  break;
3247 
3248  case "itemcmd":
3249  if (!value.equals("2")) {
3250  throw new UnknownCommandException("the server is too old for this client since it does not support the itemcmd=2 setup option.");
3251  }
3252  break;
3253 
3254  case "mapsize":
3255  final String[] tmp = value.split("x", 2);
3256  if (tmp.length != 2) {
3257  throw new UnknownCommandException("the server returned 'setup mapsize "+value+"'.");
3258  }
3259  final int thisMapWidth;
3260  final int thisMapHeight;
3261  try {
3262  thisMapWidth = Integer.parseInt(tmp[0]);
3263  thisMapHeight = Integer.parseInt(tmp[1]);
3264  } catch (final NumberFormatException ignored) {
3265  throw new UnknownCommandException("the server returned 'setup mapsize "+value+"'.");
3266  }
3267  if (pendingMapWidth == 0 || pendingMapHeight == 0) {
3268  System.err.println("the server sent an unexpected 'setup mapsize "+value+"'.");
3269  } else if (pendingMapWidth == thisMapWidth && pendingMapHeight == thisMapHeight) {
3270  pendingMapWidth = 0;
3271  pendingMapHeight = 0;
3272  setCurrentMapSize(thisMapWidth, thisMapHeight);
3273  if (thisMapWidth != preferredMapWidth && thisMapHeight != preferredMapHeight) {
3274  negotiateMapSize(preferredMapWidth, preferredMapHeight);
3275  }
3276  } else if (pendingMapWidth > thisMapWidth && pendingMapHeight > thisMapHeight) {
3277  pendingMapWidth = 0;
3278  pendingMapHeight = 0;
3279  negotiateMapSize(thisMapWidth, thisMapHeight);
3280  } else if (pendingMapWidth > thisMapWidth) {
3281  final int tmpMapHeight = pendingMapHeight;
3282  pendingMapWidth = 0;
3283  pendingMapHeight = 0;
3284  negotiateMapSize(thisMapWidth, tmpMapHeight);
3285  } else if (pendingMapHeight > thisMapHeight) {
3286  final int tmpMapWidth = pendingMapWidth;
3287  pendingMapWidth = 0;
3288  pendingMapHeight = 0;
3289  negotiateMapSize(tmpMapWidth, thisMapHeight);
3290  } else if (pendingMapWidth == thisMapWidth) {
3291  final int tmpMapHeight = pendingMapHeight+2;
3292  pendingMapWidth = 0;
3293  pendingMapHeight = 0;
3294  negotiateMapSize(thisMapWidth, tmpMapHeight);
3295  } else if (pendingMapHeight == thisMapHeight) {
3296  final int tmpMapWidth = pendingMapWidth+2;
3297  pendingMapWidth = 0;
3298  pendingMapHeight = 0;
3299  negotiateMapSize(tmpMapWidth, thisMapHeight);
3300  } else if (pendingMapWidth <= pendingMapHeight) {
3301  final int tmpMapWidth = pendingMapWidth+2;
3302  final int tmpMapHeight = pendingMapHeight;
3303  pendingMapWidth = 0;
3304  pendingMapHeight = 0;
3305  negotiateMapSize(tmpMapWidth, tmpMapHeight);
3306  } else {
3307  final int tmpMapWidth = pendingMapWidth;
3308  final int tmpMapHeight = pendingMapHeight+2;
3309  pendingMapWidth = 0;
3310  pendingMapHeight = 0;
3311  negotiateMapSize(tmpMapWidth, tmpMapHeight);
3312  }
3313  break;
3314 
3315  case "map2cmd":
3316  if (!value.equals("1")) {
3317  throw new UnknownCommandException("the server is too old for this client since it does not support the map2cmd=1 setup option.");
3318  }
3319  break;
3320 
3321  case "darkness":
3322  // do not care
3323  break;
3324 
3325  case "tick":
3326  if (!value.equals("1")) {
3327  throw new UnknownCommandException("the server is too old for this client since it does not support the tick=1 setup option.");
3328  }
3329  break;
3330 
3331  case "num_look_objects":
3332  numLookObjects.processSetupNumLookObjects(value);
3333  break;
3334 
3335  case "faceset":
3336  // ignore: we do not care about the face set
3337  break;
3338 
3339  case "want_pickup":
3340  // ignore: we do not care whether this option has been ignored
3341  break;
3342 
3343  case "extended_stats":
3344  // ignore: we do not care whether this option has been ignored
3345  break;
3346 
3347  case "loginmethod":
3348  if (value.equals("FALSE")) {
3349  loginMethod = 0;
3350  continue;
3351  }
3352 
3353  final int method;
3354  try {
3355  method = Integer.parseInt(value);
3356  } catch (final NumberFormatException ignored) {
3357  throw new UnknownCommandException("the server returned 'setup loginmethod "+value+"'.");
3358  }
3359  if (method < 0 || method > 2) {
3360  throw new UnknownCommandException("the server returned 'setup loginmethod "+value+"'.");
3361  }
3362  loginMethod = method;
3363  break;
3364 
3365  case "notifications":
3366  // ignore: we do not care whether this option has been ignored
3367  break;
3368 
3369  default:
3370  System.err.println("Warning: ignoring unknown setup option from server: "+option+"="+value);
3371  break;
3372  }
3373  }
3374 
3375  if (options.size() != 2 || !options.get(0).equals("mapsize") && !options.get(0).equals("num_look_objects")) {
3377  sendRequestinfo("skill_info 1");
3378  sendRequestinfo("exp_table");
3379  sendRequestinfo("knowledge_info");
3380  sendQueuedRequestinfo("image_info");
3381  sendQueuedRequestinfo("startingmap");
3382  sendQueuedRequestinfo("race_list");
3383  sendQueuedRequestinfo("class_list");
3384  sendQueuedRequestinfo("newcharinfo");
3386  }
3387  notifyPacketWatcherListenersAscii(packet, args);
3388  }
3389 
3395  private void processSmooth(@NotNull final ByteBuffer packet) throws UnknownCommandException {
3396  final int args = packet.position();
3397  final int faceNo = getInt2(packet);
3398  final int smoothPic = getInt2(packet);
3399  if (packet.hasRemaining()) {
3400  throw new UnknownCommandException("excess data at end of smooth command");
3401  }
3402  if (debugProtocol != null) {
3403  debugProtocol.debugProtocolWrite("recv smooth face="+faceNo+" smooth_pic="+smoothPic);
3404  }
3405  model.getSmoothFaces().updateSmoothFace(faceNo, smoothPic);
3407  }
3408 
3414  private void processSound(@NotNull final ByteBuffer packet) throws UnknownCommandException {
3415  final int args = packet.position();
3416  final int x = packet.get();
3417  final int y = packet.get();
3418  final int num = getInt2(packet);
3419  final int type = getInt1(packet);
3420  if (packet.hasRemaining()) {
3421  throw new UnknownCommandException("excess data at end of sound command");
3422  }
3423  if (debugProtocol != null) {
3424  debugProtocol.debugProtocolWrite("recv sound pos="+x+"/"+y+" num="+num+" type="+type);
3425  }
3426 
3427  fireCommandSoundReceived(x, y, num, type);
3429  }
3430 
3436  private void processSound2(@NotNull final ByteBuffer packet) throws UnknownCommandException {
3437  final int args = packet.position();
3438  final int x = packet.get();
3439  final int y = packet.get();
3440  final int dir = packet.get();
3441  final int volume = getInt1(packet);
3442  final int type = getInt1(packet);
3443  final int actionLength = getInt1(packet);
3444  final String action = getString(packet, actionLength);
3445  final int nameLength = getInt1(packet);
3446  final String name = getString(packet, nameLength);
3447  if (packet.hasRemaining()) {
3448  throw new UnknownCommandException("excess data at end of sound2 command");
3449  }
3450  if (debugProtocol != null) {
3451  debugProtocol.debugProtocolWrite("recv sound2 pos="+x+"/"+y+" dir="+dir+" volume="+volume+" type="+type+" action="+action+" name="+name);
3452  }
3453 
3454  fireCommandSound2Received(x, y, dir, volume, type, action, name);
3456  }
3457 
3463  private void processStats(@NotNull final ByteBuffer packet) throws UnknownCommandException {
3464  while (packet.hasRemaining()) {
3465  final int stat = getInt1(packet);
3466  switch (stat) {
3467  case Stats.CS_STAT_HP:
3468  case Stats.CS_STAT_MAXHP:
3469  case Stats.CS_STAT_SP:
3470  case Stats.CS_STAT_MAXSP:
3471  case Stats.CS_STAT_STR:
3472  case Stats.CS_STAT_INT:
3473  case Stats.CS_STAT_WIS:
3474  case Stats.CS_STAT_DEX:
3475  case Stats.CS_STAT_CON:
3476  case Stats.CS_STAT_CHA:
3477  case Stats.CS_STAT_LEVEL:
3478  case Stats.CS_STAT_WC:
3479  case Stats.CS_STAT_AC:
3480  case Stats.CS_STAT_DAM:
3481  case Stats.CS_STAT_ARMOUR:
3482  case Stats.CS_STAT_FOOD:
3483  case Stats.CS_STAT_POW:
3484  case Stats.CS_STAT_GRACE:
3485  case Stats.CS_STAT_MAXGRACE:
3486  case Stats.CS_STAT_FLAGS:
3487  case Stats.CS_STAT_RACE_STR:
3488  case Stats.CS_STAT_RACE_INT:
3489  case Stats.CS_STAT_RACE_WIS:
3490  case Stats.CS_STAT_RACE_DEX:
3491  case Stats.CS_STAT_RACE_CON:
3492  case Stats.CS_STAT_RACE_CHA:
3493  case Stats.CS_STAT_RACE_POW:
3494  case Stats.CS_STAT_BASE_STR:
3495  case Stats.CS_STAT_BASE_INT:
3496  case Stats.CS_STAT_BASE_WIS:
3497  case Stats.CS_STAT_BASE_DEX:
3498  case Stats.CS_STAT_BASE_CON:
3499  case Stats.CS_STAT_BASE_CHA:
3500  case Stats.CS_STAT_BASE_POW:
3508  case Stats.CS_STAT_GOLEM_HP:
3510  final short int2Param = (short)getInt2(packet);
3511  if (debugProtocol != null) {
3512  debugProtocol.debugProtocolWrite("recv stats stat="+stat+" int2="+int2Param+"="+(int2Param&0xFFFF));
3513  }
3514  model.getStats().setStatInt2(stat, int2Param);
3515  notifyPacketWatcherListenersStats(stat, int2Param);
3516  break;
3517 
3518  case Stats.CS_STAT_EXP:
3519  case Stats.CS_STAT_SPEED:
3520  case Stats.CS_STAT_WEAP_SP:
3525  final int int4Param = getInt4(packet);
3526  if (debugProtocol != null) {
3527  debugProtocol.debugProtocolWrite("recv stats stat="+stat+" int4="+int4Param);
3528  }
3529  model.getStats().setStatInt4(stat, int4Param);
3530  notifyPacketWatcherListenersStats(stat, int4Param);
3531  break;
3532 
3533  case Stats.CS_STAT_EXP64:
3534  final long int8Param = getInt8(packet);
3535  if (debugProtocol != null) {
3536  debugProtocol.debugProtocolWrite("recv stats stat="+stat+" int8="+int8Param);
3537  }
3538  model.getStats().setStatInt8(stat, int8Param);
3539  notifyPacketWatcherListenersStats(stat, int8Param);
3540  break;
3541 
3542  case Stats.CS_STAT_RANGE:
3543  case Stats.CS_STAT_TITLE:
3544  final int length = getInt1(packet);
3545  final String strParam = getString(packet, length);
3546  if (debugProtocol != null) {
3547  debugProtocol.debugProtocolWrite("recv stats stat="+stat+" str="+strParam);
3548  }
3549  model.getStats().setStatString(stat, strParam);
3550  notifyPacketWatcherListenersStats(stat, strParam);
3551  break;
3552 
3553  default:
3555  final short int2Param2 = (short)getInt2(packet);
3556  if (debugProtocol != null) {
3557  debugProtocol.debugProtocolWrite("recv stats stat="+stat+" int2="+int2Param2);
3558  }
3559  model.getStats().setStatInt2(stat, int2Param2);
3560  notifyPacketWatcherListenersStats(stat, int2Param2);
3561  } else if (Stats.CS_STAT_SKILLINFO <= stat && stat < Stats.CS_STAT_SKILLINFO+Stats.CS_NUM_SKILLS) {
3562  final int level = getInt1(packet);
3563  final long experience = getInt8(packet);
3564  if (debugProtocol != null) {
3565  debugProtocol.debugProtocolWrite("recv stats stat="+stat+" level="+level+" experience="+experience);
3566  }
3567  model.getStats().setStatSkill(stat, level, experience);
3568  notifyPacketWatcherListenersStats(stat, level, experience);
3569  } else {
3570  if (debugProtocol != null) {
3571  debugProtocol.debugProtocolWrite("recv stats stat="+stat+" <unknown parameter>");
3572  }
3573  throw new UnknownCommandException("unknown stat value: "+stat);
3574  }
3575  break;
3576  }
3577  }
3578  }
3579 
3585  private void processTick(@NotNull final ByteBuffer packet) throws UnknownCommandException {
3586  final int args = packet.position();
3587  final int tickNo = getInt4(packet);
3588  if (packet.hasRemaining()) {
3589  throw new UnknownCommandException("excess data at end of tick command");
3590  }
3591  if (debugProtocol != null) {
3592  debugProtocol.debugProtocolWrite("recv tick "+tickNo);
3593  }
3594  fireTick(tickNo);
3596  }
3597 
3603  private void processUpdItem(@NotNull final ByteBuffer packet) throws UnknownCommandException {
3604  final int args = packet.position();
3605  final int flags = getInt1(packet);
3606  final int tag = getInt4(packet);
3607  final int valLocation = (flags&UpdItem.UPD_LOCATION) == 0 ? 0 : getInt4(packet);
3608  final int valFlags = (flags&UpdItem.UPD_FLAGS) == 0 ? 0 : getInt4(packet);
3609  final int valWeight = (flags&UpdItem.UPD_WEIGHT) == 0 ? 0 : getInt4(packet);
3610  final int valFaceNum = (flags&UpdItem.UPD_FACE) == 0 ? 0 : getInt4(packet);
3611  final String valName;
3612  final String valNamePl;
3613  if ((flags&UpdItem.UPD_NAME) == 0) {
3614  valName = "";
3615  valNamePl = "";
3616  } else {
3617  final int nameLength = getInt1(packet);
3618  int namePlIndex = 0;
3619  while (namePlIndex < nameLength && packet.get(packet.position()+namePlIndex) != 0) {
3620  namePlIndex++;
3621  }
3622  valName = newString(packet, packet.position(), namePlIndex);
3623  valNamePl = namePlIndex+1 < nameLength ? newString(packet, packet.position()+namePlIndex+1, nameLength-(namePlIndex+1)) : valName;
3624  packet.position(packet.position()+nameLength);
3625  }
3626  final int valAnim = (flags&UpdItem.UPD_ANIM) == 0 ? 0 : getInt2(packet);
3627  final int valAnimSpeed = (flags&UpdItem.UPD_ANIMSPEED) == 0 ? 0 : getInt1(packet);
3628  final int valNrof = (flags&UpdItem.UPD_NROF) == 0 ? 0 : getInt4(packet);
3629  if (packet.hasRemaining()) {
3630  throw new UnknownCommandException("excess data at end of upditem command");
3631  }
3632  if (debugProtocol != null) {
3633  debugProtocol.debugProtocolWrite("recv upditem flags="+flags+" tag="+tag+" loc="+valLocation+" flags="+valFlags+" weight="+valWeight+" face="+valFaceNum+" name="+valName+" name_pl="+valNamePl+" anim="+valAnim+" anim_speed="+valAnimSpeed+" nrof="+valNrof);
3634  }
3635  fireUpditemReceived(flags, tag, valLocation, valFlags, valWeight, valFaceNum, valName, valNamePl, valAnim, valAnimSpeed, valNrof);
3637  }
3638 
3643  private void processUpdQuest(@NotNull final ByteBuffer packet) {
3644  final int args = packet.position();
3645  final int code = getInt4(packet);
3646  final int end = getInt1(packet);
3647  final int stepLength = getInt2(packet);
3648  final String step = stepLength > 0 ? getString(packet, stepLength) : "";
3649 
3650  if (debugProtocol != null) {
3651  debugProtocol.debugProtocolWrite("recv updquest code="+code+" end="+end+" description="+step);
3652  }
3653  model.getQuestsManager().updateQuest(code, end == 1, step);
3654  notifyPacketWatcherListenersMixed(packet, args);
3655  }
3656 
3662  private void processUpdSpell(@NotNull final ByteBuffer packet) throws UnknownCommandException {
3663  final int args = packet.position();
3664  final int flags = getInt1(packet);
3665  final int tag = getInt4(packet);
3666  final int mana = (flags&SpellsManager.UPD_SP_MANA) == 0 ? 0 : getInt2(packet);
3667  final int grace = (flags&SpellsManager.UPD_SP_GRACE) == 0 ? 0 : getInt2(packet);
3668  final int damage = (flags&SpellsManager.UPD_SP_DAMAGE) == 0 ? 0 : getInt2(packet);
3669  if (packet.hasRemaining()) {
3670  throw new UnknownCommandException("excess data at end of updspell command");
3671  }
3672  if (debugProtocol != null) {
3673  debugProtocol.debugProtocolWrite("recv updspell flags="+flags+" tag="+tag+" sp="+mana+" gr="+grace+" dam="+damage);
3674  }
3675  model.getSpellsManager().updateSpell(flags, tag, mana, grace, damage);
3677  }
3678 
3684  private void processVersion(@NotNull final ByteBuffer packet) throws UnknownCommandException {
3685  final int args = packet.position();
3686  int csval = 0;
3687  do {
3688  csval = csval*10+parseDigit(packet.get());
3689  } while (packet.get(packet.position()) != ' ');
3690  packet.get();
3691 
3692  int scval = 0;
3693  do {
3694  scval = scval*10+parseDigit(packet.get());
3695  } while (packet.get(packet.position()) != ' ');
3696  packet.get();
3697 
3698  final String vinfo = getString(packet, packet.remaining());
3699 
3700  if (debugProtocol != null) {
3701  debugProtocol.debugProtocolWrite("recv version cs="+csval+" sc="+scval+" info="+vinfo);
3702  }
3703 
3705  sendSetup("want_pickup 1", "faceset 0", "sound2 3", "exp64 1", "map2cmd 1", "darkness 1", "newmapcmd 1", "facecache 1", "extendedTextInfos 1", "itemcmd 2", "spellmon 1", "tick 1", "extended_stats 1", "loginmethod 1", "notifications 2");
3706  model.getStats().setSimpleWeaponSpeed(scval >= 1029);
3707 
3709  }
3710 
3714  @Override
3715  public void sendAccountLogin(@NotNull final String login, @NotNull final String password) {
3716  clearFailure();
3717  if (debugProtocol != null) {
3718  debugProtocol.debugProtocolWrite("send accountlogin "+login);
3719  }
3720  accountName = login;
3721  synchronized (writeBuffer) {
3722  byteBuffer.clear();
3723  //noinspection AccessToStaticFieldLockedOnInstance
3724  byteBuffer.put(ACCOUNT_LOGIN_PREFIX);
3725  final byte[] loginBytes = login.getBytes(UTF8);
3726  byteBuffer.put((byte)loginBytes.length);
3727  byteBuffer.put(loginBytes);
3728  final byte[] passwordBytes = password.getBytes(UTF8);
3729  byteBuffer.put((byte)passwordBytes.length);
3730  byteBuffer.put(passwordBytes);
3731  defaultServerConnection.writePacket(writeBuffer, byteBuffer.position());
3732  }
3733 
3734  }
3735 
3739  @Override
3740  public void sendAddme() {
3741  if (debugProtocol != null) {
3742  debugProtocol.debugProtocolWrite("send addme");
3743  }
3744  defaultServerConnection.writePacket(ADDME_PREFIX, ADDME_PREFIX.length);
3745  }
3746 
3750  @Override
3751  public void sendApply(final int tag) {
3752  if (debugProtocol != null) {
3753  debugProtocol.debugProtocolWrite("send apply tag="+tag);
3754  }
3755  synchronized (writeBuffer) {
3756  byteBuffer.clear();
3757  //noinspection AccessToStaticFieldLockedOnInstance
3758  byteBuffer.put(APPLY_PREFIX);
3759  putDecimal(tag);
3760  defaultServerConnection.writePacket(writeBuffer, byteBuffer.position());
3761  }
3762  }
3763 
3767  @Override
3768  public void sendAskface(final int faceNum) {
3769  if (debugProtocol != null) {
3770  debugProtocol.debugProtocolWrite("send askface face="+faceNum);
3771  }
3772  synchronized (writeBuffer) {
3773  byteBuffer.clear();
3774  //noinspection AccessToStaticFieldLockedOnInstance
3775  byteBuffer.put(ASKFACE_PREFIX);
3776  putDecimal(faceNum);
3777  defaultServerConnection.writePacket(writeBuffer, byteBuffer.position());
3778  }
3779  }
3780 
3784  @Override
3785  public void sendExamine(final int tag) {
3786  if (debugProtocol != null) {
3787  debugProtocol.debugProtocolWrite("send examine tag="+tag);
3788  }
3789  synchronized (writeBuffer) {
3790  byteBuffer.clear();
3791  //noinspection AccessToStaticFieldLockedOnInstance
3792  byteBuffer.put(EXAMINE_PREFIX);
3793  putDecimal(tag);
3794  defaultServerConnection.writePacket(writeBuffer, byteBuffer.position());
3795  }
3796  }
3797 
3801  @Override
3802  public void sendLock(final boolean val, final int tag) {
3803  if (debugProtocol != null) {
3804  debugProtocol.debugProtocolWrite("send lock tag="+tag+" val="+val);
3805  }
3806  synchronized (writeBuffer) {
3807  byteBuffer.clear();
3808  //noinspection AccessToStaticFieldLockedOnInstance
3809  byteBuffer.put(LOCK_PREFIX);
3810  byteBuffer.put((byte)(val ? 1 : 0));
3811  byteBuffer.putInt(tag);
3812  defaultServerConnection.writePacket(writeBuffer, byteBuffer.position());
3813  }
3814  }
3815 
3819  @Override
3820  public void sendLookat(final int dx, final int dy) {
3821  if (debugProtocol != null) {
3822  debugProtocol.debugProtocolWrite("send lookat pos="+dx+"/"+dy);
3823  }
3824  synchronized (writeBuffer) {
3825  byteBuffer.clear();
3826  //noinspection AccessToStaticFieldLockedOnInstance
3827  byteBuffer.put(LOOKAT_PREFIX);
3828  putDecimal(dx);
3829  byteBuffer.put((byte)' ');
3830  putDecimal(dy);
3831  defaultServerConnection.writePacket(writeBuffer, byteBuffer.position());
3832  }
3833  }
3834 
3838  @Override
3839  public void sendMark(final int tag) {
3840  if (debugProtocol != null) {
3841  debugProtocol.debugProtocolWrite("send mark tag="+tag);
3842  }
3843  synchronized (writeBuffer) {
3844  byteBuffer.clear();
3845  //noinspection AccessToStaticFieldLockedOnInstance
3846  byteBuffer.put(MARK_PREFIX);
3847  byteBuffer.putInt(tag);
3848  defaultServerConnection.writePacket(writeBuffer, byteBuffer.position());
3849  }
3850  }
3851 
3855  @Override
3856  public void sendMove(final int to, final int tag, final int nrof) {
3857  if (debugProtocol != null) {
3858  debugProtocol.debugProtocolWrite("send move tag="+tag+" to="+to+" nrof="+nrof);
3859  }
3860  synchronized (writeBuffer) {
3861  byteBuffer.clear();
3862  //noinspection AccessToStaticFieldLockedOnInstance
3863  byteBuffer.put(MOVE_PREFIX);
3864  putDecimal(to);
3865  byteBuffer.put((byte)' ');
3866  putDecimal(tag);
3867  byteBuffer.put((byte)' ');
3868  putDecimal(nrof);
3869  defaultServerConnection.writePacket(writeBuffer, byteBuffer.position());
3870  }
3871  }
3872 
3876  @Override
3877  public int sendNcom(final int repeat, @NotNull final String command) {
3878  if (debugProtocol != null) {
3879  debugProtocol.debugProtocolWrite("send ncom no="+packet+" repeat="+repeat+" cmd="+command);
3880  }
3881  final int thisPacket;
3882  synchronized (writeBuffer) {
3883  thisPacket = packet++&0x00FF;
3884  byteBuffer.clear();
3885  //noinspection AccessToStaticFieldLockedOnInstance
3886  byteBuffer.put(NCOM_PREFIX);
3887  byteBuffer.putShort((short)thisPacket);
3888  byteBuffer.putInt(repeat);
3889  byteBuffer.put(command.getBytes(UTF8));
3890  defaultServerConnection.writePacket(writeBuffer, byteBuffer.position());
3891  }
3892  return thisPacket;
3893  }
3894 
3898  @Override
3899  public void sendReply(@NotNull final String text) {
3900  if (debugProtocol != null) {
3901  debugProtocol.debugProtocolWrite("send reply text="+text);
3902  }
3903  synchronized (writeBuffer) {
3904  byteBuffer.clear();
3905  //noinspection AccessToStaticFieldLockedOnInstance
3906  byteBuffer.put(REPLY_PREFIX);
3907  byteBuffer.put(text.getBytes(UTF8));
3908  defaultServerConnection.writePacket(writeBuffer, byteBuffer.position());
3909  }
3910  fireReplySent();
3911  }
3912 
3916  @Override
3917  public void sendRequestinfo(@NotNull final String infoType) {
3918  if (debugProtocol != null) {
3919  debugProtocol.debugProtocolWrite("send requestinfo type="+infoType);
3920  }
3921  synchronized (writeBuffer) {
3922  byteBuffer.clear();
3923  //noinspection AccessToStaticFieldLockedOnInstance
3924  byteBuffer.put(REQUESTINFO_PREFIX);
3925  byteBuffer.put(infoType.getBytes(UTF8));
3926  defaultServerConnection.writePacket(writeBuffer, byteBuffer.position());
3927  sendingRequestInfo = PATTERN_SPACE.split(infoType, 2)[0];
3928  }
3929  }
3930 
3934  @Override
3935  public void sendSetup(@NotNull final String... options) {
3936  if (debugProtocol != null) {
3937  debugProtocol.debugProtocolWrite("send setup options="+Arrays.toString(options));
3938  }
3939  synchronized (writeBuffer) {
3940  byteBuffer.clear();
3941  //noinspection AccessToStaticFieldLockedOnInstance
3942  byteBuffer.put(SETUP_PREFIX);
3943  if (options.length <= 0) {
3944  byteBuffer.put((byte)' ');
3945  } else {
3946  for (final String option : options) {
3947  byteBuffer.put((byte)' ');
3948  byteBuffer.put(option.getBytes(UTF8));
3949  }
3950  }
3951  defaultServerConnection.writePacket(writeBuffer, byteBuffer.position());
3952  }
3953  }
3954 
3958  @Override
3959  public void sendToggleextendedtext(@NotNull final int... types) {
3960  if (types.length <= 0) {
3961  return;
3962  }
3963 
3964  if (debugProtocol != null) {
3965  debugProtocol.debugProtocolWrite("send toggleextendedtext types="+Arrays.toString(types));
3966  }
3967  synchronized (writeBuffer) {
3968  byteBuffer.clear();
3969  //noinspection AccessToStaticFieldLockedOnInstance
3970  byteBuffer.put(TOGGLEEXTENDEDTEXT_PREFIX);
3971  for (final int type : types) {
3972  byteBuffer.put((byte)' ');
3973  putDecimal(type);
3974  }
3975  defaultServerConnection.writePacket(writeBuffer, byteBuffer.position());
3976  }
3977  }
3978 
3982  @Override
3983  public void sendVersion(final int csval, final int scval, @NotNull final String vinfo) {
3984  if (debugProtocol != null) {
3985  debugProtocol.debugProtocolWrite("send version cs="+csval+" sc="+scval+" info="+vinfo);
3986  }
3987  synchronized (writeBuffer) {
3988  byteBuffer.clear();
3989  //noinspection AccessToStaticFieldLockedOnInstance
3990  byteBuffer.put(VERSION_PREFIX);
3991  putDecimal(csval);
3992  byteBuffer.put((byte)' ');
3993  putDecimal(scval);
3994  byteBuffer.put((byte)' ');
3995  byteBuffer.put(vinfo.getBytes(UTF8));
3996  defaultServerConnection.writePacket(writeBuffer, byteBuffer.position());
3997  }
3998  }
3999 
4005  private void putDecimal(final int value) {
4006  if (value == 0) {
4007  byteBuffer.put((byte)'0');
4008  } else {
4009  final String str = Integer.toString(value);
4010  try {
4011  byteBuffer.put(str.getBytes("ISO-8859-1"));
4012  } catch (final UnsupportedEncodingException ex) {
4013  throw new AssertionError(ex); // every Java implementation must support UTF-8
4014  }
4015  }
4016  }
4017 
4024  private static int parseDigit(final byte ch) throws UnknownCommandException {
4025  final int digit = ch-'0';
4026  if (digit < 0 || digit > 9) {
4027  throw new UnknownCommandException("not a digit: "+ch);
4028  }
4029  return digit;
4030  }
4031 
4035  @Override
4036  public void setPreferredMapSize(final int preferredMapWidth, final int preferredMapHeight) {
4037  final int preferredMapWidth2 = Math.max(1, preferredMapWidth|1);
4038  final int preferredMapHeight2 = Math.max(1, preferredMapHeight|1);
4039  if (this.preferredMapWidth == preferredMapWidth2 && this.preferredMapHeight == preferredMapHeight2) {
4040  return;
4041  }
4042 
4043  this.preferredMapWidth = preferredMapWidth2;
4044  this.preferredMapHeight = preferredMapHeight2;
4045 
4046  negotiateMapSize(this.preferredMapWidth, this.preferredMapHeight);
4047  }
4048 
4054  private void setCurrentMapSize(final int currentMapWidth, final int currentMapHeight) {
4055  if (this.currentMapWidth == currentMapWidth && this.currentMapHeight == currentMapHeight) {
4056  return;
4057  }
4058 
4059  this.currentMapWidth = currentMapWidth;
4060  this.currentMapHeight = currentMapHeight;
4061  fireNewMap();
4062  }
4063 
4067  private void fireNewMap() {
4068  if (crossfireUpdateMapListener != null) {
4069  crossfireUpdateMapListener.newMap(currentMapWidth, currentMapHeight);
4070  }
4071  }
4072 
4076  @Override
4077  public void setPreferredNumLookObjects(final int preferredNumLookObjects) {
4078  numLookObjects.setPreferredNumLookObjects(preferredNumLookObjects);
4079  }
4080 
4084  @Nullable
4085  @Override
4086  public String getAccountName() {
4087  return accountName;
4088  }
4089 
4093  @Override
4094  public void connect(@NotNull final String hostname, final int port) {
4095  accountName = null;
4096  clearFailure();
4099  defaultServerConnection.connect(hostname, port);
4100  }
4101 
4105  @Override
4106  public void disconnect(@NotNull final String reason) {
4107  defaultServerConnection.disconnect(reason);
4108  }
4109 
4113  @Override
4115  defaultServerConnection.addClientSocketListener(clientSocketListener);
4116  }
4117 
4121  @Override
4123  defaultServerConnection.removeClientSocketListener(clientSocketListener);
4124  }
4125 
4131  private void setClientSocketState(@NotNull final ClientSocketState prevState, @NotNull final ClientSocketState nextState) {
4132  if (debugProtocol != null) {
4133  debugProtocol.debugProtocolWrite("connection state: "+nextState);
4134  }
4135  if (clientSocketState != prevState) {
4136  System.err.println("Warning: connection state is "+clientSocketState+" when switching to state "+nextState+", expecting state "+prevState);
4137  }
4138  clientSocketState = nextState;
4139  model.getGuiStateManager().setClientSocketState(nextState);
4140  numLookObjects.setClientSocketState(clientSocketState);
4141  }
4142 
4146  @Override
4147  public void sendAccountPlay(@NotNull final String name) {
4148  clearFailure();
4149  if (debugProtocol != null) {
4150  debugProtocol.debugProtocolWrite("send accountplay "+name);
4151  }
4152  synchronized (writeBuffer) {
4153  byteBuffer.clear();
4154  //noinspection AccessToStaticFieldLockedOnInstance
4155  byteBuffer.put(ACCOUNT_PLAY_PREFIX);
4156  byteBuffer.put(name.getBytes(UTF8));
4157  defaultServerConnection.writePacket(writeBuffer, byteBuffer.position());
4158  }
4159 
4160  final String tmpAccountName = accountName;
4161  if (tmpAccountName != null) {
4162  fireSelectCharacter(tmpAccountName, name);
4163  }
4164  }
4165 
4169  @Override
4170  public void sendAccountLink(final int force, @NotNull final String login, @NotNull final String password) {
4171  clearFailure();
4172  if (debugProtocol != null) {
4173  debugProtocol.debugProtocolWrite("send accountaddplayer "+login);
4174  }
4175  synchronized (writeBuffer) {
4176  byteBuffer.clear();
4177  //noinspection AccessToStaticFieldLockedOnInstance
4178  byteBuffer.put(ACCOUNT_ADD_PLAYER_PREFIX);
4179  byteBuffer.put((byte)force);
4180  final byte[] loginBytes = login.getBytes(UTF8);
4181  byteBuffer.put((byte)loginBytes.length);
4182  byteBuffer.put(loginBytes);
4183  final byte[] passwordBytes = password.getBytes(UTF8);
4184  byteBuffer.put((byte)passwordBytes.length);
4185  byteBuffer.put(passwordBytes);
4186  defaultServerConnection.writePacket(writeBuffer, byteBuffer.position());
4187  }
4188  }
4189 
4193  @Override
4194  public void sendAccountCreate(@NotNull final String login, @NotNull final String password) {
4195  clearFailure();
4196  if (debugProtocol != null) {
4197  debugProtocol.debugProtocolWrite("send accountnew "+login);
4198  }
4199  accountName = login;
4200  synchronized (writeBuffer) {
4201  byteBuffer.clear();
4202  //noinspection AccessToStaticFieldLockedOnInstance
4203  byteBuffer.put(ACCOUNT_NEW_PREFIX);
4204  final byte[] loginBytes = login.getBytes(UTF8);
4205  byteBuffer.put((byte)loginBytes.length);
4206  byteBuffer.put(loginBytes);
4207  final byte[] passwordBytes = password.getBytes(UTF8);
4208  byteBuffer.put((byte)passwordBytes.length);
4209  byteBuffer.put(passwordBytes);
4210  defaultServerConnection.writePacket(writeBuffer, byteBuffer.position());
4211  }
4212  }
4213 
4217  @Override
4218  public void sendAccountCharacterCreate(@NotNull final String login, @NotNull final String password) {
4219  clearFailure();
4220  if (debugProtocol != null) {
4221  debugProtocol.debugProtocolWrite("send createplayer "+login);
4222  }
4223  synchronized (writeBuffer) {
4224  byteBuffer.clear();
4225  //noinspection AccessToStaticFieldLockedOnInstance
4226  byteBuffer.put(CREATE_PLAYER_PREFIX);
4227  final byte[] loginBytes = login.getBytes(UTF8);
4228  byteBuffer.put((byte)loginBytes.length);
4229  byteBuffer.put(loginBytes);
4230  final byte[] passwordBytes = password.getBytes(UTF8);
4231  byteBuffer.put((byte)passwordBytes.length);
4232  byteBuffer.put(passwordBytes);
4233  defaultServerConnection.writePacket(writeBuffer, byteBuffer.position());
4234  }
4235  }
4236 
4240  @Override
4241  public void sendAccountPassword(@NotNull final String currentPassword, @NotNull final String newPassword) {
4242  clearFailure();
4243  if (debugProtocol != null) {
4244  debugProtocol.debugProtocolWrite("send accountpw");
4245  }
4246  synchronized (writeBuffer) {
4247  byteBuffer.clear();
4248  //noinspection AccessToStaticFieldLockedOnInstance
4249  byteBuffer.put(ACCOUNT_PASSWORD_PREFIX);
4250  final byte[] currentPasswordBytes = currentPassword.getBytes(UTF8);
4251  byteBuffer.put((byte)currentPasswordBytes.length);
4252  byteBuffer.put(currentPasswordBytes);
4253  final byte[] newPasswordBytes = newPassword.getBytes(UTF8);
4254  byteBuffer.put((byte)newPasswordBytes.length);
4255  byteBuffer.put(newPasswordBytes);
4256  defaultServerConnection.writePacket(writeBuffer, byteBuffer.position());
4257  }
4258  }
4259 
4266  private static int getInt1(@NotNull final ByteBuffer byteBuffer) {
4267  return byteBuffer.get()&0xFF;
4268  }
4269 
4276  private static int getInt2(@NotNull final ByteBuffer byteBuffer) {
4277  return byteBuffer.getShort()&0xFFFF;
4278  }
4279 
4286  private static int getInt4(@NotNull final ByteBuffer byteBuffer) {
4287  return byteBuffer.getInt();
4288  }
4289 
4296  private static long getInt8(@NotNull final ByteBuffer byteBuffer) {
4297  return byteBuffer.getLong();
4298  }
4299 
4307  @NotNull
4308  private static String getString(@NotNull final ByteBuffer byteBuffer, final int len) {
4309  final byte[] tmp = new byte[len];
4310  byteBuffer.get(tmp);
4311  return new String(tmp, UTF8);
4312  }
4313 
4321  @NotNull
4322  private static String getStringDelimiter(@NotNull final ByteBuffer byteBuffer, final char delimiter) {
4323  final int position = byteBuffer.position();
4324  final int remaining = byteBuffer.remaining();
4325  int len;
4326  for (len = 0; len < remaining; len++) {
4327  if (byteBuffer.get(position+len) == delimiter) {
4328  break;
4329  }
4330  }
4331  final byte[] tmp = new byte[len];
4332  byteBuffer.get(tmp);
4333  if (len < remaining) {
4334  byteBuffer.get(); // skip delimiter
4335  }
4336  return new String(tmp, UTF8);
4337  }
4338 
4344  @NotNull
4345  private static String hexDump(@NotNull final ByteBuffer byteBuffer) {
4346  final int len = byteBuffer.limit();
4347  final byte[] data = new byte[len];
4348  for (int i = 0; i < len; i++) {
4349  data[i] = byteBuffer.get(i);
4350  }
4351  return HexCodec.hexDump(data, 0, len);
4352  }
4353 
4359  return numLookObjects.getCurrentNumLookObjects();
4360  }
4361 
4368  public void waitForCurrentNumLookObjectsValid() throws InterruptedException {
4369  numLookObjects.waitForCurrentNumLookObjectsValid();
4370  }
4371 
4372  protected void fireMapClear(final int x, final int y) {
4373  assert crossfireUpdateMapListener != null;
4375  }
4376 
4377  protected void fireMapDarkness(final int x, final int y, final int darkness) {
4378  assert crossfireUpdateMapListener != null;
4379  crossfireUpdateMapListener.mapDarkness(x, y, darkness);
4380  }
4381 
4382  protected void fireMapFace(@NotNull final Location location, final int face) {
4383  assert crossfireUpdateMapListener != null;
4384  crossfireUpdateMapListener.mapFace(location, face);
4385  }
4386 
4387  protected void fireMapAnimation(@NotNull final Location location, final int animationNum, final int animationType) {
4388  assert crossfireUpdateMapListener != null;
4389  crossfireUpdateMapListener.mapAnimation(location, animationNum, animationType);
4390  }
4391 
4392  protected void fireMapSmooth(@NotNull final Location location, final int smooth) {
4393  assert crossfireUpdateMapListener != null;
4394  crossfireUpdateMapListener.mapSmooth(location, smooth);
4395  }
4396 
4397  protected void fireMapAnimationSpeed(@NotNull final Location location, final int animSpeed) {
4398  assert crossfireUpdateMapListener != null;
4399  crossfireUpdateMapListener.mapAnimationSpeed(location, animSpeed);
4400  }
4401 
4402  protected void fireAddAnimation(final int animation, final int flags, @NotNull final int[] faces) {
4403  if (crossfireUpdateMapListener != null) {
4404  crossfireUpdateMapListener.addAnimation(animation, flags, faces);
4405  }
4406  }
4407 
4408  protected void fireMagicMap(final int x, final int y, @NotNull final byte[][] data) {
4409  if (crossfireUpdateMapListener != null) {
4410  synchronized (crossfireUpdateMapListener.mapBegin()) {
4411  assert crossfireUpdateMapListener != null;
4413  assert crossfireUpdateMapListener != null;
4415  }
4416  }
4417  fireMagicMap();
4418  }
4419 
4424  private void sendQueuedRequestinfo(@NotNull final String infoType) {
4425  synchronized (writeBuffer) {
4426  pendingRequestInfos.add(infoType);
4427  }
4429  }
4430 
4434  private void sendPendingRequestInfo() {
4435  final String infoType;
4436  synchronized (writeBuffer) {
4437  //noinspection VariableNotUsedInsideIf
4438  if (sendingRequestInfo != null) {
4439  return;
4440  }
4441  if (pendingRequestInfos.isEmpty()) {
4442  return;
4443  }
4444  infoType = pendingRequestInfos.remove(0);
4445  }
4446  sendRequestinfo(infoType);
4447  }
4448 
4449 }
static final int CS_STAT_GOLEM_HP
The golem&#39;s hitpoints, 0 if no golem.
Definition: Stats.java:319
void sendPendingRequestInfo()
Sends the next asynchronous "requestinfo" packet if possible.
void cmdMap2CoordinateClearSpace(final int x, final int y, final int len)
Processes the payload data for a map2 coordinate "clear_space" sub-command.
void sendVersion(final int csval, final int scval, @NotNull final String vinfo)
Sends a "version" command to the server.the client version number the server version number the clien...
void cmdMap2CoordinateLayer3(@NotNull final ByteBuffer packet, @NotNull final Location location, final int face)
Processes the additional payload data for a map2 coordinate "layer" sub-command having 4 bytes payloa...
void processAddmeFailed(@NotNull final ByteBuffer packet)
Processes an &#39;account_failed&#39; server command.
Builder for StartingMap instances while parsing a "replyinfo startingmap" response packet...
ClassRaceInfo finish()
Finishes parsing an entry an returns the ClassRaceInfo for the entry.
void writePacket(@NotNull final byte[] packet, final int length)
Writes a Crossfire Message on the socket, so it is sent to the server.
void setNewCharInfo(@NotNull final NewCharInfo newCharInfo)
Sets the NewCharInfo instance for character creation.
void addClassInfo(@NotNull final ClassRaceInfo classInfo)
Sets or updates a class info.
VERSION
"version" protocol commands are being exchanged.
void processExpTableReplyinfo(@NotNull final ByteBuffer packet)
Processes a "replyinfo exp_table" block.
void fireUpditemReceived(final int flags, final int tag, final int valLocation, final int valFlags, final int valWeight, final int valFaceNum, @NotNull final String valName, @NotNull final String valNamePl, final int valAnim, final int valAnimSpeed, final int valNrof)
Abstract base class for CrossfireServerConnection implementing classes.
void addSpell(final int tag, final int level, final int castingTime, final int mana, final int grace, final int damage, final int skill, final int path, final int faceNum, @NotNull final String spellName, @NotNull final String message)
Adds a new spell.
static int [] getAllTypes()
Returns all defined message types.
static final int CS_STAT_RACE_CHA
The race&#39;s maximum charisma primary stat.
Definition: Stats.java:239
static void parseClassRaceInfoStats(@NotNull final ByteBuffer packet, @NotNull final ClassRaceInfoBuilder rb)
Parses a "stats" entry of a "replyinfo race_info" or "replyinfo class_info" packet.
void processGoodbye(@NotNull final ByteBuffer packet)
Processes a &#39;goodbye&#39; server command.
static final int CS_STAT_DEX
The Dexterity Primary stat.
Definition: Stats.java:83
int COORD_LAYER3
Face information for layer 3.
Definition: Map2.java:78
static final int CS_STAT_SPELL_ATTUNE
Attuned spell paths of a spell.
Definition: Stats.java:199
final NumLookObjects numLookObjects
The NumLookObjects instance for negotiating the size of the ground view.
void processMagicMap(@NotNull final ByteBuffer packet)
Processes a &#39;magicmap&#39; server command.
int COORD_LAYER7
Face information for layer 7.
Definition: Map2.java:98
int loginMethod
The login method version supported by the server we&#39;re connected to.
void processDrawExtInfo(@NotNull final ByteBuffer packet)
Processes a &#39;drawextinfo&#39; server command.
Encapsulates the message type numbers for drawextinfo messages.
void sendExamine(final int tag)
Sends an "examine" command to the server.the item to examine
static void parseClassRaceInfoChoice(@NotNull final ByteBuffer packet, @NotNull final ClassRaceInfoBuilder rb)
Parses a "choice" entry of a "replyinfo race_info" or "replyinfo class_info" packet.
void sendLock(final boolean val, final int tag)
Sends a "lock" command to the server.whether to lock the item the item to lock
static long getInt8(@NotNull final ByteBuffer byteBuffer)
Extracts and removes an 8 byte integer from a ByteBuffer at it&#39;s current position.
int COORD_LAYER6
Face information for layer 6.
Definition: Map2.java:93
static final int RESIST_TYPES
The total number of resistances.
Definition: Stats.java:434
void processSetupNumLookObjects(@NotNull final String value)
Called when a "setup num_look_objects" response has been received from the server.
void updateSpell(final int flags, final int tag, final int mana, final int grace, final int damage)
Updates spell information.
void processRaceListReplyinfo(@NotNull final ByteBuffer packet)
Processes a "replyinfo race_list" block.
void processSmooth(@NotNull final ByteBuffer packet)
Processes a &#39;smooth&#39; server command.
void processClassListReplyinfo(@NotNull final ByteBuffer packet)
Processes a "replyinfo class_list" block.
NewCharInfo finish()
Finished parsing and returns the NewCharInfo instance for the parsed entry.
static final byte [] VERSION_PREFIX
The command prefix for the "version" command.
void fireAddAnimation(final int animation, final int flags, @NotNull final int[] faces)
static final int CS_STAT_TITLE
The Title stat.
Definition: Stats.java:159
static final int CS_STAT_SPELL_DENY
Denied spell paths of a spell.
Definition: Stats.java:209
void processSound2(@NotNull final ByteBuffer packet)
Processes a &#39;sound2&#39; server command.
static final int CS_STAT_RANGE
The Range stat - this is what is currently readied by the player to fire.
Definition: Stats.java:154
int UPD_FACE
The update flags value for face updates.
Definition: UpdItem.java:48
void updateQuest(final int code, final boolean end, @NotNull final String step)
Updates quest information.
static final Charset UTF8
The Charset used for parsing or encoding strings received from or sent to the Crossfire server...
int TYPE_COORDINATE
Normal coordinate.
Definition: Map2.java:43
void processAddKnowledge(@NotNull final ByteBuffer packet)
Processes an "addacknowledge" block.
REQUESTINFO
"requestinfo" protocol commands are being exchanged.
void sendLookat(final int dx, final int dy)
Sends a "lookat" command to the server.the x-coordinate in tiles, relative to the player the y-coordi...
static final int CS_STAT_APPLIED_CON
The constitution primary stat changes due to gear or skills.
Definition: Stats.java:304
static final int CS_STAT_RESIST_START
Beginning index of the resistances.
Definition: Stats.java:329
void processUpdItem(@NotNull final ByteBuffer packet)
Processes an &#39;upditem&#39; server command.
void cmdMap2CoordinateDarkness(@NotNull final ByteBuffer packet, final int x, final int y, final int len)
Processes the payload data for a map2 coordinate "darkness" sub-command.
static final byte [] CREATE_PLAYER_PREFIX
The command prefix for the "createplayer" command.
void removeClientSocketListener(@NotNull final ClientSocketListener clientSocketListener)
Removes a ClientSocketListener to notify.the client socket listener to remove
int UPD_ANIMSPEED
The update flags value for animation speed updates.
Definition: UpdItem.java:63
void notifyPacketWatcherListenersShortInt(@NotNull final ByteBuffer packet, final int args)
Notifies all ReceivedPacketListeners about a packet having a short and an in value as parameters...
void processNewMap(@NotNull final ByteBuffer packet)
Processes a &#39;newmap&#39; server command.
int COORD_LAYER1
Face information for layer 1.
Definition: Map2.java:68
static final int CS_STAT_BASE_POW
The power primary stat without boosts or depletions.
Definition: Stats.java:279
void processReplyInfo(@NotNull final ByteBuffer packet)
Processes a &#39;replyinfo&#39; server command.
void drawextinfo(final int color, final int type, final int subtype, final String message)
Pretends that a drawextinfo message has been received.the message type the message subtype the messag...
void fireMapAnimation(@NotNull final Location location, final int animationNum, final int animationType)
void setStatInt2(final int stat, final short param)
Updates a stat value with a two-byte int value.
Definition: Stats.java:804
void faceReceived(final int faceNum, final int faceSetNum, @NotNull final ByteBuffer packet)
Notifies the askface manager that image information have been received from the server.
void updateSmoothFace(final int face, final int smoothFace)
Updates smooth face information.
static final int CS_STAT_POW
The Power Primary stat.
Definition: Stats.java:164
SETUP
"setup" protocol commands are being exchanged.
void processExtendedInfoSet(@NotNull final ByteBuffer packet)
Processes an &#39;ExtendedInfoSet&#39; server command.
void clearFailure()
Inform the various failure listeners that they can clean the last displayed failure.
void processPlayer(@NotNull final ByteBuffer packet)
Processes a &#39;pickup&#39; server command.
static final int CS_STAT_APPLIED_INT
The integer primary stat changes due to gear or skills.
Definition: Stats.java:289
void processVersion(@NotNull final ByteBuffer packet)
Processes a &#39;version&#39; server command.
void debugProtocolWrite(@NotNull final CharSequence str)
Writes a message to the debug protocol.
final List< String > pendingRequestInfos
Pending "requestinfo" commands that will be sent as soon sendingRequestInfo is unset.
void addAnimation(int animation, int flags, @NotNull int[] faces)
An "addanim" command has been received.
void processPacket(@NotNull final ByteBuffer packet)
Processes a received packet.
void notifyPacketWatcherListenersMixed(@NotNull final ByteBuffer packet, final int args)
Notifies all ReceivedPacketListeners about a packet having mixed parameters.
int COORD_LAYER2
Face information for layer 2.
Definition: Map2.java:73
static int getInt4(@NotNull final ByteBuffer byteBuffer)
Extracts and removes a 4 byte integer from a ByteBuffer at it&#39;s current position. ...
void sendAccountLink(final int force, @NotNull final String login, @NotNull final String password)
Sends a request to add an existing character to an account.0 to allow failure, 1 to force in certain ...
static final int CS_NUM_SKILLS
CS_NUM_SKILLS does not match how many skills there really are - instead, it is used as a range of val...
Definition: Stats.java:447
void processImage2(@NotNull final ByteBuffer packet)
Processes an &#39;image2&#39; server command.
General information for creating new characters.
int FACE_ANIMATION
Bit value whether this is a face or an animation.
Definition: Map2.java:113
int COORD_LAYER4
Face information for layer 4.
Definition: Map2.java:83
Implements the map model which is shown in the map and magic map views.
Definition: CfMap.java:22
Builder for CharacterInformation instances while parsing an "accountplayers" packet.
static final byte [] TOGGLEEXTENDEDTEXT_PREFIX
The command prefix for the "toggleextendedtext" command.
void setPreferredNumLookObjects(final int preferredNumLookObjects)
Sets the maximum number of objects in the ground view.Must not be called in connected state...
int UPD_FLAGS
The update flags value for flags updates.
Definition: UpdItem.java:38
static final int CS_STAT_HP
The Hit Points stat.
Definition: Stats.java:48
static final int CS_STAT_LEVEL
The Global Level stat.
Definition: Stats.java:108
void processAddSpell(@NotNull final ByteBuffer packet)
Processes an &#39;addspell&#39; server command.
int COORD_DARKNESS
Darkness information.
Definition: Map2.java:58
void sendAccountCreate(@NotNull final String login, @NotNull final String password)
Sends a request to create a new account.the account login the account password
Writer debug information to a log file.
void setSimpleWeaponSpeed(final boolean simpleWeaponSpeed)
Sets whether the CS_STAT_WEAP_SP value contains the weapon speed directly.
Definition: Stats.java:583
void setPreferredMapSize(final int preferredMapWidth, final int preferredMapHeight)
Sets the preferred map size.the preferred map width in tiles; must be odd the preferred map height in...
void sendQueuedRequestinfo(@NotNull final String infoType)
Sends a "requestinfo" packet asynchronously.
static final int CS_STAT_MAXHP
The Maximum Hit Points stat.
Definition: Stats.java:53
static final int CS_STAT_RACE_WIS
The race&#39;s maximum wisdom primary stat.
Definition: Stats.java:224
void processMusic(@NotNull final ByteBuffer packet)
Processes a &#39;music&#39; server command.
static String getStringDelimiter(@NotNull final ByteBuffer byteBuffer, final char delimiter)
Extracts and removes a string from a ByteBuffer at it&#39;s current position.
void setName(@NotNull final String name)
Sets the human readable stat name.
int preferredMapHeight
The map height in tiles that is negotiated with the server.
static final int CS_STAT_DAM
The Damage stat.
Definition: Stats.java:123
static final int CS_STAT_STR
The Strength Primary stat.
Definition: Stats.java:68
void processAddQuest(@NotNull final ByteBuffer packet)
Processes an &#39;addquest&#39; server command.
void deleteSpell(final int tag)
Deletes a spell.
static final int CS_STAT_AC
The Armor Class stat.
Definition: Stats.java:118
void processAddmeSuccess(@NotNull final ByteBuffer packet)
Processes an &#39;account_success&#39; server command.
void mapScroll(int dx, int dy)
Part of "map2" parsing: scroll the map view.
static final int CS_STAT_FOOD
The Food stat.
Definition: Stats.java:138
static final int CS_STAT_WIS
The Wisdom Primary stat.
Definition: Stats.java:78
boolean connected
Whether the current client socket state is ClientSocketState#CONNECTED.
void setRaceList(@NotNull final String[] raceList)
Sets the races available for character creation.
void setStatSkill(final int stat, final int level, final long experience)
Updates a stat value with a skill value.
Definition: Stats.java:938
One of the two most important classes, ServerConnection performs most of the network-related work...
static final int CS_STAT_BASE_INT
The integer primary stat without boosts or depletions.
Definition: Stats.java:254
int UPD_NAME
The update flags value for name updates.
Definition: UpdItem.java:53
static String getString(@NotNull final ByteBuffer byteBuffer, final int len)
Extracts and removes a string from a ByteBuffer at it&#39;s current position.
static final int CS_STAT_SP
The Spell Points stat.
Definition: Stats.java:58
void processStats(@NotNull final ByteBuffer packet)
Processes a &#39;stats&#39; server command.
void mapAnimationSpeed(@NotNull Location location, int animationSpeed)
Part of "map2" parsing: set the animation speed.
static final int UPD_SP_GRACE
Flag for updspell command: grace is present.
static final int DEFAULT_MAP_HEIGHT
The default map height when no "setup mapsize" command has been sent.
void processSound(@NotNull final ByteBuffer packet)
Processes a &#39;sound&#39; server command.
int ANIM_TYPE_MASK
The mask for extracting the animation type.
Definition: Map2.java:145
void setPreferredNumLookObjects(final int preferredNumLookObjects)
Sets the preferred number of ground items.
static final int UPD_SP_MANA
Flag for updspell command: mana is present.
void processAnim(@NotNull final ByteBuffer packet)
Processes an &#39;anim&#39; server command.
Builder for ClassRaceInfo instances while parsing an "replyinfo race_info" packet.
static String extractCommand(@NotNull final ByteBuffer packet)
Returns the command string for a received packet.
void setArchName(@NotNull final byte[] archName)
Starts a new starting map entry.
static final int CS_STAT_RACE_CON
The race&#39;s maximum constitution primary stat.
Definition: Stats.java:234
void processNewCharInfoReplyinfo(@NotNull final ByteBuffer packet)
Processes a "replyinfo newcharinfo" block.
static final byte [] ACCOUNT_NEW_PREFIX
The command prefix for the "accountnew" command.
static final byte [] ACCOUNT_LOGIN_PREFIX
The command prefix for the "accountlogin" command.
Combines all model classes that are updated.
Definition: Model.java:44
ACCOUNT_INFO
The client is connected, and account information is being exchanged.
void setStatString(final int stat, @NotNull final String param)
Updates a stat value with a string value.
Definition: Stats.java:916
void disconnect(@NotNull final String reason)
Disconnects from the server.Does nothing if not connected. the reason for the disconnect ...
DefaultCrossfireServerConnection(@NotNull final Model model, @Nullable final DebugWriter debugProtocol, @NotNull final String version)
Creates a new instance.
void processSetup(@NotNull final ByteBuffer packet)
Processes a &#39;replyinfo&#39; server command.
static final int CS_STAT_WEIGHT_LIM
The Weight Limit stat.
Definition: Stats.java:184
void sendAccountCharacterCreate(@NotNull final String login, @NotNull final String password)
Sends a request to create a new character associated to the account.the character&#39;s name the characte...
void setMsg(@NotNull final String msg)
Sets the long description.
static final int CS_STAT_APPLIED_STR
The strength primary stat changes due to gear or skills.
Definition: Stats.java:284
void addClientSocketListener(@NotNull final ClientSocketListener clientSocketListener)
Adds a ClientSocketListener to notify.the client socket listener to add
void sendRequestinfo(@NotNull final String infoType)
Sends a "requestinfo" command to the server.the info type to request
static final int CS_STAT_BASE_STR
The strength primary stat without boosts or depletions.
Definition: Stats.java:249
static final int CS_STAT_GOLEM_MAXHP
The golem&#39;s maximum hitpoints, 0 if no golem.
Definition: Stats.java:324
void setName(@NotNull final String name)
Sets the name of the current entry.
A choice for character creation.
Definition: Choice.java:33
static int getInt1(@NotNull final ByteBuffer byteBuffer)
Extracts and removes a 1 byte integer from a ByteBuffer at it&#39;s current position. ...
void magicMap(int x, int y, byte[][] data)
Part of "magicmap" parsing: set the magic map color.
void processAccountPlayers(@NotNull final ByteBuffer packet)
Processes an &#39;accountplayers&#39; server command.
static void parseNewCharInfoValues(@NotNull final NewCharInfoBuilder newCharInfoBuilder, @NotNull final String variableName, @NotNull final String values)
Parses a &#39;V&#39; entry of a "replyinfo newcharinfo" packet.
static final int CS_STAT_SKILLINFO
CS_STAT_SKILLINFO is used as the starting index point.
Definition: Stats.java:454
void notifyPacketWatcherListenersShortArray(@NotNull final ByteBuffer packet, final int args)
Notifies all ReceivedPacketListeners about a packet having an array of short values as parameters...
Interface defining constants for the "map2" Crossfire protocol message.
Definition: Map2.java:28
static void processImageInfoReplyinfo(@NotNull final ByteBuffer packet)
Processes a "replyinfo image_info" block.
void sendAccountLogin(@NotNull final String login, @NotNull final String password)
Asks for an account login.the account login the account password
void processUpdSpell(@NotNull final ByteBuffer packet)
Processes an &#39;updspell&#39; server command.
static final int CS_STAT_CON
The Constitution Primary stat.
Definition: Stats.java:88
static final int CS_STAT_RACE_STR
The race&#39;s maximum strength primary stat.
Definition: Stats.java:214
final ClientSocketListener clientSocketListener
The ClientSocketListener attached to the server socket.
int getCurrentNumLookObjects()
Returns the current number of ground items.
void fireNewMap()
Notifies all listeners that a "newmap" command has been received.
static final int CS_STAT_APPLIED_DEX
The dexterity primary stat changes due to gear or skills.
Definition: Stats.java:299
void sendReply(@NotNull final String text)
Sends a "reply" command to the server.the text to reply
int COORD_LAYER9
Face information for layer 9.
Definition: Map2.java:108
Object mapBegin()
Parsing of a "map2" command has been started.
static String hexDump(@NotNull final byte[] data, final int start, final int end)
Returns a hex dump of a part of a byte array.
Definition: HexCodec.java:71
static String hexDump(@NotNull final ByteBuffer byteBuffer)
Returns a hex-dump of a ByteBuffer.
void removeClientSocketListener(@NotNull final ClientSocketListener clientSocketListener)
Removes a ClientSocketListener to notify.the client socket listener to remove
static final byte [] LOOKAT_PREFIX
The command prefix for the "lookat" command.
void notifyPacketWatcherListenersNoData(@NotNull final ByteBuffer packet, final int args)
Notifies all ReceivedPacketListeners about a packet having unknown parameters.
static final int INFO_MAP_ARCH_NAME
Archetype name of a "replyinfo startingmap" entry.
int COORD_OFFSET
Offset for coordinate values in map2 command.
Definition: Map2.java:38
static void parseNewCharInfoInformational(@NotNull final String variableName, @NotNull final String values)
Parses an &#39;I&#39; entry of a "replyinfo newcharinfo" packet.
void processItem2(@NotNull final ByteBuffer packet)
Processes an &#39;item2&#39; server command.
static void parseNewCharInfoValue(@NotNull final NewCharInfoBuilder newCharInfoBuilder, final boolean required, @NotNull final String variableName, @NotNull final String values)
Parses an &#39;R&#39; or &#39;O&#39; entry of a "replyinfo newcharinfo" packet.
static final byte [] REQUESTINFO_PREFIX
The command prefix for the "requestinfo" command.
void addSkill(final int id, @NotNull final String skillName, final int face)
Adds a new skill to the list of known skills.
Definition: SkillSet.java:120
An UnknownCommandException is generated whenever an unknown message packet is received from the serve...
int UPD_LOCATION
The update flags value for location updates.
Definition: UpdItem.java:33
void waitForCurrentNumLookObjectsValid()
Waits until getCurrentNumLookObjects() is stable.
void sendAccountPassword(@NotNull final String currentPassword, @NotNull final String newPassword)
Sends a request to change the account&#39;s password.current account password new account password ...
void processPickup(@NotNull final ByteBuffer packet)
Processes a &#39;pickup&#39; server command.
int getCurrentNumLookObjects()
Returns the currently negotiated setup value of "num_look_objects".
void cmdMap2CoordinateLayer4(@NotNull final ByteBuffer packet, @NotNull final Location location, final int face)
Processes the additional payload data for a map2 coordinate "layer" sub-command having 4 bytes payloa...
void fireCommandSound2Received(final int x, final int y, final int dir, final int volume, final int type, @NotNull final String action, @NotNull final String name)
void setStartingMaps(@NotNull final Collection< StartingMap > startingMaps)
Sets the StartingMap entries available for character creation.
static final int CS_STAT_RACE_INT
The race&#39;s maximum intelligence primary stat.
Definition: Stats.java:219
static final int CS_STAT_MAXGRACE
The Maximum Grace stat.
Definition: Stats.java:174
void processFailure(@NotNull final ByteBuffer packet)
Processes a &#39;failure&#39; server command.
Interface for listeners interested in ClientSocket related events.
void sendApply(final int tag)
Sends an "apply" command to the server.the item to apply
void processSkillInfoReplyinfo(@NotNull final ByteBuffer packet)
Processes a "replyinfo skill_info" block.
static final int CS_STAT_RACE_DEX
The race&#39;s maximum dexterity primary stat.
Definition: Stats.java:229
int COORD_LAYER8
Face information for layer 8.
Definition: Map2.java:103
static final int CS_STAT_BASE_CON
The constitution primary stat without boosts or depletions.
Definition: Stats.java:269
static final int CS_STAT_CHA
The Charisma Primary stat.
Definition: Stats.java:93
void negotiateMapSize(final int mapWidth, final int mapHeight)
Requests a change of the map size from the server.
void addKnowledge(final int index, @NotNull final String type, @NotNull final String title, final int face)
void sendAskface(final int faceNum)
Sends an "askface" command.the face number to request
void processFace2(@NotNull final ByteBuffer packet)
Processes a &#39;face2&#39; server command.
void fireFaceReceived(final int faceNum, final int faceSetNum, final int faceChecksum, @NotNull final String faceName)
void mapFace(@NotNull Location location, int faceNum)
Part of "map2" parsing: set the face of a cell.
static final int CS_STAT_EXP64
The Global Experience (64bit encoding) stat.
Definition: Stats.java:194
void notifyPacketWatcherListenersIntArray(@NotNull final ByteBuffer packet, final int args)
Notifies all ReceivedPacketListeners about a packet having an array of int values as parameters...
void sendAccountPlay(@NotNull final String name)
Sends a request to play a character from an account.the character&#39;s name to play
void processClassRaceInfoReplyinfo(@NotNull final ByteBuffer packet, final boolean raceInfo)
Processes a "replyinfo race_info" or "replyinfo class_info" block.
void fireFailure(@NotNull final String command, @NotNull final String arguments)
static final int CS_STAT_APPLIED_CHA
The charisma primary stat changes due to gear or skills.
Definition: Stats.java:309
void setCrossfireUpdateMapListener(@Nullable final CrossfireUpdateMapListener listener)
Sets a listener to be notified about map changes.At most one such listener may be set...
static int getInt2(@NotNull final ByteBuffer byteBuffer)
Extracts and removes a 2 byte integer from a ByteBuffer at it&#39;s current position. ...
void cmdMap2CoordinateLayer(@NotNull final ByteBuffer packet, final int x, final int y, final int len, final int layer)
Processes the payload data for a map2 coordinate "layer" sub-command.
static final int CS_STAT_SPELL_REPEL
Repelled spell paths of a spell.
Definition: Stats.java:204
static final int CS_STAT_WC
The Weapon Class stat.
Definition: Stats.java:113
void setClientSocketState(@NotNull final ClientSocketState clientSocketState)
Sets the new ClientSocketState.
void cmdMap2Coordinate(@NotNull final ByteBuffer packet, final int x, final int y)
Processes the payload data for a map2 coordinate command.
static final int UPD_SP_DAMAGE
Flag for updspell command: damage is present.
void setDescription(@NotNull final String description)
Sets the description of the current entry.
void setClientSocketState(@NotNull final ClientSocketState prevState, @NotNull final ClientSocketState nextState)
Updates the clientSocketState.
CrossfireUpdateMapListener crossfireUpdateMapListener
The CrossfireUpdateMapListener to be notified.
static final int CS_STAT_EXP
The Global Experience (32bit encoding) stat.
Definition: Stats.java:98
void setCurrentMapSize(final int currentMapWidth, final int currentMapHeight)
Sets the current map size as negotiated with the server.
void mapAnimation(@NotNull Location location, int animationNum, int animationType)
Part of "map2" parsing: set the animation of a cell.
static final int CS_STAT_BASE_WIS
The wisdom primary stat without boosts or depletions.
Definition: Stats.java:259
void processDelSpell(@NotNull final ByteBuffer packet)
Processes a &#39;delspell&#39; server command.
One possible class or race for character creation.
static final int CS_STAT_BASE_DEX
The dexterity primary stat without boosts or depletions.
Definition: Stats.java:264
static final int INFO_MAP_DESCRIPTION
Description of a "replyinfo startingmap" entry.
int COORD_LAYER5
Face information for layer 5.
Definition: Map2.java:88
void setStatInt4(final int stat, final int param)
Updates a stat value with a four-byte int value.
Definition: Stats.java:873
int ANIM_MASK
The mask for extracting the animation ID.
Definition: Map2.java:134
void addClientSocketListener(@NotNull final ClientSocketListener clientSocketListener)
Adds a ClientSocketListener to notify.the client socket listener to add
void fireAddItemReceived(final int location, final int tag, final int flags, final int weight, final int faceNum, final String name, final String namePl, final int anim, final int animSpeed, final int nrof, final int type)
Builder for NewCharInfo instances while parsing a "replyinfo newcharinfo" response packet...
Interface defining constants for the "upditem" Crossfire protocol message.
Definition: UpdItem.java:28
void disconnect(@NotNull final String reason)
Disconnects from the server.Does nothing if not connected. the reason for the disconnect ...
void firePlayerReceived(final int tag, final int weight, final int faceNum, @NotNull final String name)
CharacterInformation parseAccountPlayer(@NotNull final ByteBuffer packet, @NotNull final AccountPlayerBuilder accountPlayerBuilder)
Processes one account entry of an &#39;accountplayers&#39; server command.
void addQuest(final int code, @NotNull final String title, final int face, final boolean replay, final int parent, final boolean end, @NotNull final String description)
Adds a new quest.
ADDME
"addme" protocol commands are being exchanged.
void sendMark(final int tag)
Sends a "mark" command to the server.the item to mark
void addKnowledgeType(@NotNull final String type, @NotNull final String name, final int face, final boolean canAttempt)
static final int CS_STAT_RACE_POW
The race&#39;s maximum power primary stat.
Definition: Stats.java:244
static String newString(final ByteBuffer byteBuffer, final int start, final int len)
Extracts a string from a ByteBuffer range.
void cmdReplyinfo(@NotNull final String infoType, final ByteBuffer packet)
Handles the replyinfo server to client command.
int UPD_ANIM
The update flags value for animation updates.
Definition: UpdItem.java:58
static final byte [] ACCOUNT_ADD_PLAYER_PREFIX
The command prefix for the "accountaddplayer" command.
void fireSelectCharacter(@NotNull final String accountName, @NotNull final String characterName)
void processDelInv(@NotNull final ByteBuffer packet)
Processes a &#39;delinv&#39; server command.
int UPD_NROF
The update flags value for nrof updates.
Definition: UpdItem.java:68
static final byte [] ASKFACE_PREFIX
The command prefix for the "askface" command.
void processExtendedTextSet(@NotNull final ByteBuffer packet)
Processes an &#39;ExtendedTextSet&#39; server command.
static final int CS_STAT_INT
The Intelligence Primary stat.
Definition: Stats.java:73
int UPD_WEIGHT
The update flags value for weight updates.
Definition: UpdItem.java:43
void mapSmooth(@NotNull Location location, int smooth)
Part of "map2" parsing: set the smooth level.
void sendSetup(@NotNull final String... options)
Sends a "setup" command to the server.the option/value pairs to send
void notifyPacketWatcherListenersStats(final int stat, @NotNull final Object... args)
Notifies all ReceivedPacketListeners about a packet having stat parameters.
void processKnowledgeInfoReplyinfo(@NotNull final ByteBuffer packet)
Processes a "replyinfo knowledge_info" block.
static final int CS_STAT_FLAGS
The various flags used in stats.
Definition: Stats.java:179
void sendMove(final int to, final int tag, final int nrof)
Sends a "move" command to the server.the destination location the item to move the number of items to...
static final byte [] EXAMINE_PREFIX
The command prefix for the "examine" command.
void processMap2(@NotNull final ByteBuffer packet)
Processes a &#39;map2&#39; server command.
This is the representation of all the statistics of a player, like its speed or its experience...
Definition: Stats.java:43
void notifyPacketWatcherListenersAscii(@NotNull final ByteBuffer packet, final int args)
Notifies all ReceivedPacketListeners about a packet having ascii parameters.
static final int CS_STAT_WEAP_SP
The Weapon Speed stat.
Definition: Stats.java:148
void setClientSocketState(@NotNull final ClientSocketState clientSocketState)
Called whenever the client socket state has changed.
Negotiates the size of the ground view in items with the Crossfire server.
void putDecimal(final int value)
Appends an integer in decimal ASCII representation to byteBuffer.
void addRaceInfo(@NotNull final ClassRaceInfo classRaceInfo)
Sets or updates a ClassRaceInfo.
void processMapExtended(@NotNull final ByteBuffer packet)
Processes a &#39;mapextended&#39; server command.
static final int CS_STAT_MAXSP
The Maximum Spell Points stat.
Definition: Stats.java:63
void processUpdQuest(@NotNull final ByteBuffer packet)
Processes an &#39;updquest&#39; server command.
void processTick(@NotNull final ByteBuffer packet)
Processes a &#39;tick&#39; server command.
static final int CS_STAT_APPLIED_POW
The power primary stat changes due to gear or skills.
Definition: Stats.java:314
final ByteBuffer byteBuffer
A byte buffer using writeBuffer to store the data.
void processQuery(@NotNull final ByteBuffer packet)
Processes a &#39;pickup&#39; server command.
static final byte [] ACCOUNT_PLAY_PREFIX
The command prefix for the "accountplay" command.
void mapClear(int x, int y)
Part of "map2" parsing: clear a cell.
String getAccountName()
Returns the current account name.the current account name ornull
Connection progress states of the Crossfire server connection.
int TYPE_SCROLL
Scroll information.
Definition: Map2.java:48
void processComc(@NotNull final ByteBuffer packet)
Processes a &#39;comc&#39; server command.
void setClassList(@NotNull final String[] classList)
Sets the classes available for character creation.
static final int CS_STAT_SPEED
The Speed stat.
Definition: Stats.java:133
static final byte [] ACCOUNT_PASSWORD_PREFIX
The command prefix for the "accountpw" command.
void setExpTable(@NotNull final long[] expTable)
Updates the experience table information.
void newMap(int mapWidth, int mapHeight)
A "newmap" command has been received.
int ANIM_TYPE_SHIFT
The lowest bit of the animation type.
Definition: Map2.java:139
NewCharacterInformation getNewCharacterInformation()
Returns the NewCharacterInformation for creating new characters.
Definition: Model.java:163
void setStatInt8(final int stat, final long param)
Updates a stat value with an eight-byte int value.
Definition: Stats.java:899
static final int CS_STAT_GRACE
The Grace stat.
Definition: Stats.java:169
void drawInfo(@NotNull final String message, final int color)
Pretends that a drawinfo message has been received.the message the color
Utility class for en-/decoding hexadecimal strings.
Definition: HexCodec.java:30
void connect(@NotNull final String hostname, final int port)
Attempts to connect the client to a server.the hostname to connect to the port to connect to ...
static final int DEFAULT_MAP_WIDTH
The default map width when no "setup mapsize" command has been sent.
void processStartingMapReplyinfo(@NotNull final ByteBuffer packet)
Processes a "replyinfo startingmap" block.
void mapDarkness(int x, int y, int darkness)
Part of "map2" parsing: change the darkness of a cell.
void connect(@NotNull final String hostname, final int port)
Attempts to connect the client to a server.the hostname to connect to the port to connect to ...
static final int CS_STAT_BASE_CHA
The charisma primary stat without boosts or depletions.
Definition: Stats.java:274
int COORD_LAYER0
Face information for layer 0.
Definition: Map2.java:63
void processDelItem(@NotNull final ByteBuffer packet)
Processes a &#39;delitem&#39; server command.
void processDrawInfo(@NotNull final ByteBuffer packet)
Processes a &#39;drawinfo&#39; server command.
static final int CS_STAT_APPLIED_WIS
The wisdom primary stat changes due to gear or skills.
Definition: Stats.java:294
static final int CS_STAT_ARMOUR
The Armour stat.
Definition: Stats.java:128
final String version
The version information to send when connecting to the server.
int sendNcom(final int repeat, @NotNull final String command)
Sends a "ncom" command to the server.the repeat count the command the packet id
void sendToggleextendedtext(@NotNull final int... types)
Sends a "toggleextendedtext" command to the server.the types to request