23 package com.realtime.crossfire.jxclient.server.crossfire;
44 import java.io.BufferedReader;
45 import java.io.ByteArrayInputStream;
46 import java.io.IOException;
47 import java.io.InputStreamReader;
48 import java.nio.Buffer;
49 import java.nio.BufferUnderflowException;
50 import java.nio.ByteBuffer;
51 import java.nio.ByteOrder;
52 import java.nio.charset.StandardCharsets;
53 import java.util.ArrayDeque;
54 import java.util.ArrayList;
55 import java.util.Arrays;
56 import java.util.Collection;
57 import java.util.Deque;
58 import java.util.LinkedHashMap;
59 import java.util.List;
61 import java.util.regex.Pattern;
62 import org.jetbrains.annotations.NotNull;
63 import org.jetbrains.annotations.Nullable;
91 private static final Pattern
PATTERN_DOT = Pattern.compile(
":");
97 private static final Pattern
PATTERN_BAR = Pattern.compile(
"\\|+");
242 private static final byte @NotNull []
ACCOUNT_LOGIN_PREFIX =
"accountlogin ".getBytes(StandardCharsets.US_ASCII);
247 private static final byte @NotNull []
ACCOUNT_PLAY_PREFIX =
"accountplay ".getBytes(StandardCharsets.US_ASCII);
257 private static final byte @NotNull []
ACCOUNT_NEW_PREFIX =
"accountnew ".getBytes(StandardCharsets.US_ASCII);
267 private static final byte @NotNull []
CREATE_PLAYER_PREFIX =
"createplayer ".getBytes(StandardCharsets.US_ASCII);
272 private static final byte @NotNull []
ADDME_PREFIX =
"addme ".getBytes(StandardCharsets.US_ASCII);
277 private static final byte @NotNull []
APPLY_PREFIX =
"apply ".getBytes(StandardCharsets.US_ASCII);
282 private static final byte @NotNull []
ASKFACE_PREFIX =
"askface ".getBytes(StandardCharsets.US_ASCII);
287 private static final byte @NotNull []
EXAMINE_PREFIX =
"examine ".getBytes(StandardCharsets.US_ASCII);
292 private static final byte @NotNull []
LOCK_PREFIX =
"lock ".getBytes(StandardCharsets.US_ASCII);
297 private static final byte @NotNull []
LOOKAT_PREFIX =
"lookat ".getBytes(StandardCharsets.US_ASCII);
302 private static final byte @NotNull []
MARK_PREFIX =
"mark ".getBytes(StandardCharsets.US_ASCII);
307 private static final byte @NotNull []
MOVE_PREFIX =
"move ".getBytes(StandardCharsets.US_ASCII);
312 private static final byte @NotNull []
NCOM_PREFIX =
"ncom ".getBytes(StandardCharsets.US_ASCII);
317 private static final byte @NotNull []
REPLY_PREFIX =
"reply ".getBytes(StandardCharsets.US_ASCII);
322 private static final byte @NotNull []
REQUESTINFO_PREFIX =
"requestinfo ".getBytes(StandardCharsets.US_ASCII);
327 private static final byte @NotNull []
SETUP_PREFIX =
"setup".getBytes(StandardCharsets.US_ASCII);
337 private static final byte @NotNull []
VERSION_PREFIX =
"version ".getBytes(StandardCharsets.US_ASCII);
376 private final Object sync =
new Object();
379 public void newMap(
final int mapWidth,
final int mapHeight) {
384 public Object mapBegin() {
389 public void mapClear(
final int x,
final int y) {
393 public void mapDarkness(
final int x,
final int y,
final int darkness) {
397 public void mapFace(@NotNull
final Location location,
final int faceNum) {
401 public void mapAnimation(@NotNull
final Location location,
final int animationNum,
final int animationType) {
405 public void mapAnimationSpeed(@NotNull
final Location location,
final int animationSpeed) {
409 public void mapSmooth(@NotNull
final Location location,
final int smooth) {
413 public void mapScroll(
final int dx,
final int dy) {
417 public void magicMap(
final int x,
final int y,
final byte @NotNull [] @NotNull [] data) {
421 public void mapEnd() {
458 @SuppressWarnings(
"FieldCanBeLocal")
462 public void connecting() {
482 public void disconnected(@NotNull
final String reason) {
520 public void stop() throws InterruptedException {
560 if (
packet.get() !=
'c') {
563 if (
packet.get() !=
'o') {
566 if (
packet.get() !=
'u') {
569 if (
packet.get() !=
'n') {
572 if (
packet.get() !=
't') {
575 if (
packet.get() !=
'p') {
578 if (
packet.get() !=
'l') {
581 if (
packet.get() !=
'a') {
584 if (
packet.get() !=
'y') {
587 if (
packet.get() !=
'e') {
590 if (
packet.get() !=
'r') {
593 if (
packet.get() !=
's') {
596 if (
packet.get() !=
' ') {
603 if (
packet.get() !=
'd') {
608 if (
packet.get() !=
'n') {
611 if (
packet.get() !=
'o') {
614 if (
packet.get() !=
'w') {
617 if (
packet.get() !=
'l') {
620 if (
packet.get() !=
'e') {
623 if (
packet.get() !=
'd') {
626 if (
packet.get() !=
'g') {
629 if (
packet.get() !=
'e') {
632 if (
packet.get() !=
' ') {
639 if (
packet.get() !=
'e') {
642 if (
packet.get() !=
'_') {
647 if (
packet.get() !=
'a') {
650 if (
packet.get() !=
'i') {
653 if (
packet.get() !=
'l') {
656 if (
packet.get() !=
'e') {
659 if (
packet.get() !=
'd') {
662 if (
packet.hasRemaining()) {
669 if (
packet.get() !=
'u') {
672 if (
packet.get() !=
'c') {
675 if (
packet.get() !=
'c') {
678 if (
packet.get() !=
'e') {
681 if (
packet.get() !=
's') {
684 if (
packet.get() !=
's') {
687 if (
packet.hasRemaining()) {
696 if (
packet.get() !=
'u') {
699 if (
packet.get() !=
'e') {
702 if (
packet.get() !=
's') {
705 if (
packet.get() !=
't') {
708 if (
packet.get() !=
' ') {
715 if (
packet.get() !=
'p') {
718 if (
packet.get() !=
'e') {
721 if (
packet.get() !=
'l') {
724 if (
packet.get() !=
'l') {
727 if (
packet.get() !=
' ') {
736 if (
packet.get() !=
'i') {
739 if (
packet.get() !=
'm') {
742 if (
packet.get() !=
' ') {
751 if (
packet.get() !=
'o') {
754 if (
packet.get() !=
'm') {
757 if (
packet.get() !=
'c') {
760 if (
packet.get() !=
' ') {
769 if (
packet.get() !=
'l') {
776 if (
packet.get() !=
'v') {
779 if (
packet.get() !=
' ') {
786 if (
packet.get() !=
'e') {
789 if (
packet.get() !=
'm') {
792 if (
packet.get() !=
' ') {
801 if (
packet.get() !=
'p') {
804 if (
packet.get() !=
'e') {
807 if (
packet.get() !=
'l') {
810 if (
packet.get() !=
'l') {
813 if (
packet.get() !=
' ') {
822 if (
packet.get() !=
'a') {
825 if (
packet.get() !=
'w') {
830 if (
packet.get() !=
'x') {
833 if (
packet.get() !=
't') {
836 if (
packet.get() !=
'i') {
839 if (
packet.get() !=
'n') {
842 if (
packet.get() !=
'f') {
845 if (
packet.get() !=
'o') {
848 if (
packet.get() !=
' ') {
855 if (
packet.get() !=
'n') {
858 if (
packet.get() !=
'f') {
861 if (
packet.get() !=
'o') {
864 if (
packet.get() !=
' ') {
875 if (
packet.get() !=
'x') {
878 if (
packet.get() !=
't') {
881 if (
packet.get() !=
'e') {
884 if (
packet.get() !=
'n') {
887 if (
packet.get() !=
'd') {
890 if (
packet.get() !=
'e') {
893 if (
packet.get() !=
'd') {
898 if (
packet.get() !=
'n') {
901 if (
packet.get() !=
'f') {
904 if (
packet.get() !=
'o') {
907 if (
packet.get() !=
'S') {
910 if (
packet.get() !=
'e') {
913 if (
packet.get() !=
't') {
916 if (
packet.get() !=
' ') {
923 if (
packet.get() !=
'e') {
926 if (
packet.get() !=
'x') {
929 if (
packet.get() !=
't') {
932 if (
packet.get() !=
'S') {
935 if (
packet.get() !=
'e') {
938 if (
packet.get() !=
't') {
941 if (
packet.get() !=
' ') {
950 if (
packet.get() !=
'a') {
955 if (
packet.get() !=
'e') {
958 if (
packet.get() !=
'2') {
961 if (
packet.get() !=
' ') {
968 if (
packet.get() !=
'l') {
971 if (
packet.get() !=
'u') {
974 if (
packet.get() !=
'r') {
977 if (
packet.get() !=
'e') {
980 if (
packet.get() !=
' ') {
989 if (
packet.get() !=
'o') {
992 if (
packet.get() !=
'o') {
995 if (
packet.get() !=
'd') {
998 if (
packet.get() !=
'b') {
1001 if (
packet.get() !=
'y') {
1004 if (
packet.get() !=
'e') {
1007 if (
packet.get() !=
' ') {
1016 if (
packet.get() !=
'a') {
1019 if (
packet.get() !=
'g') {
1022 if (
packet.get() !=
'e') {
1025 if (
packet.get() !=
'2') {
1028 if (
packet.get() !=
' ') {
1035 if (
packet.get() !=
'e') {
1038 if (
packet.get() !=
'm') {
1041 if (
packet.get() !=
'2') {
1044 if (
packet.get() !=
' ') {
1057 if (
packet.get() !=
'i') {
1060 if (
packet.get() !=
'c') {
1063 if (
packet.get() !=
'm') {
1066 if (
packet.get() !=
'a') {
1069 if (
packet.get() !=
'p') {
1072 if (
packet.get() !=
' ') {
1081 if (
packet.get() !=
' ') {
1088 if (
packet.get() !=
'x') {
1091 if (
packet.get() !=
't') {
1094 if (
packet.get() !=
'e') {
1097 if (
packet.get() !=
'n') {
1100 if (
packet.get() !=
'd') {
1103 if (
packet.get() !=
'e') {
1106 if (
packet.get() !=
'd') {
1109 if (
packet.get() !=
' ') {
1120 if (
packet.get() !=
's') {
1123 if (
packet.get() !=
'i') {
1126 if (
packet.get() !=
'c') {
1129 if (
packet.get() !=
' ') {
1138 if (
packet.get() !=
'e') {
1141 if (
packet.get() !=
'w') {
1144 if (
packet.get() !=
'm') {
1147 if (
packet.get() !=
'a') {
1150 if (
packet.get() !=
'p') {
1159 if (
packet.get() !=
'c') {
1162 if (
packet.get() !=
'k') {
1165 if (
packet.get() !=
'u') {
1168 if (
packet.get() !=
'p') {
1171 if (
packet.get() !=
' ') {
1178 if (
packet.get() !=
'a') {
1181 if (
packet.get() !=
'y') {
1184 if (
packet.get() !=
'e') {
1187 if (
packet.get() !=
'r') {
1190 if (
packet.get() !=
' ') {
1199 if (
packet.get() !=
'u') {
1202 if (
packet.get() !=
'e') {
1205 if (
packet.get() !=
'r') {
1208 if (
packet.get() !=
'y') {
1211 if (
packet.get() !=
' ') {
1218 if (
packet.get() !=
'e') {
1221 if (
packet.get() !=
'p') {
1224 if (
packet.get() !=
'l') {
1227 if (
packet.get() !=
'y') {
1230 if (
packet.get() !=
'i') {
1233 if (
packet.get() !=
'n') {
1236 if (
packet.get() !=
'f') {
1239 if (
packet.get() !=
'o') {
1242 if (
packet.get() !=
' ') {
1251 if (
packet.get() !=
't') {
1254 if (
packet.get() !=
'u') {
1257 if (
packet.get() !=
'p') {
1260 if (
packet.get() !=
' ') {
1267 if (
packet.get() !=
'o') {
1270 if (
packet.get() !=
'o') {
1273 if (
packet.get() !=
't') {
1276 if (
packet.get() !=
'h') {
1279 if (
packet.get() !=
' ') {
1286 if (
packet.get() !=
'u') {
1289 if (
packet.get() !=
'n') {
1292 if (
packet.get() !=
'd') {
1301 if (
packet.get() !=
' ') {
1310 if (
packet.get() !=
'a') {
1313 if (
packet.get() !=
't') {
1316 if (
packet.get() !=
's') {
1319 if (
packet.get() !=
' ') {
1328 if (
packet.get() !=
'i') {
1331 if (
packet.get() !=
'c') {
1334 if (
packet.get() !=
'k') {
1337 if (
packet.get() !=
' ') {
1344 if (
packet.get() !=
'p') {
1347 if (
packet.get() !=
'd') {
1352 if (
packet.get() !=
't') {
1355 if (
packet.get() !=
'e') {
1358 if (
packet.get() !=
'm') {
1361 if (
packet.get() !=
' ') {
1368 if (
packet.get() !=
'u') {
1371 if (
packet.get() !=
'e') {
1374 if (
packet.get() !=
's') {
1377 if (
packet.get() !=
't') {
1380 if (
packet.get() !=
' ') {
1387 if (
packet.get() !=
'p') {
1390 if (
packet.get() !=
'e') {
1393 if (
packet.get() !=
'l') {
1396 if (
packet.get() !=
'l') {
1399 if (
packet.get() !=
' ') {
1408 if (
packet.get() !=
'e') {
1411 if (
packet.get() !=
'r') {
1414 if (
packet.get() !=
's') {
1417 if (
packet.get() !=
'i') {
1420 if (
packet.get() !=
'o') {
1423 if (
packet.get() !=
'n') {
1426 if (
packet.get() !=
' ') {
1432 }
catch (
final IllegalArgumentException ex) {
1436 }
catch (
final BufferUnderflowException ex) {
1440 }
catch (
final ArrayIndexOutOfBoundsException ex) {
1444 }
catch (
final StringIndexOutOfBoundsException ex) {
1455 ((Buffer)
packet).position(0);
1457 for (cmdLen = 0; cmdLen <
packet.limit(); cmdLen++) {
1458 final byte ch =
packet.get(cmdLen);
1459 if ((ch&0xFF) <= 0x20 || (ch&0xFF) >= 0x80) {
1480 if (lenType == 0xFF) {
1484 final int len = (lenType>>5)&7;
1485 final int type = lenType&31;
1580 }
else if (len == 4) {
1582 }
else if (len != 2) {
1688 System.err.println(
"Server responded with replyinfo "+infoType+
" when expecting no replyinfo");
1690 System.err.println(
"Server responded with replyinfo "+infoType+
" when expecting replyinfo "+
sendingRequestInfo.getFirst());
1723 case "knowledge_info":
1752 System.err.println(
"Ignoring unexpected replyinfo type '"+infoType+
"'.");
1773 final byte[] data =
new byte[
packet.remaining()];
1775 try (ByteArrayInputStream is =
new ByteArrayInputStream(data)) {
1776 try (InputStreamReader isr =
new InputStreamReader(is,
UTF8)) {
1777 try (BufferedReader d =
new BufferedReader(isr)) {
1778 final String info = d.readLine();
1780 throw new IOException(
"Truncated parameter in image_info");
1782 final int nrPics = Integer.parseInt(info);
1799 final byte[] data =
new byte[
packet.remaining()];
1801 try (ByteArrayInputStream is =
new ByteArrayInputStream(data)) {
1802 try (InputStreamReader isr =
new InputStreamReader(is,
UTF8)) {
1803 try (BufferedReader d =
new BufferedReader(isr)) {
1805 final CharSequence r = d.readLine();
1811 if (sk.length < 2 || sk.length > 3) {
1812 System.err.println(
"Ignoring skill definition for invalid skill: "+r+
".");
1818 skillId = Integer.parseInt(sk[0]);
1819 }
catch (
final NumberFormatException ignored) {
1820 System.err.println(
"Ignoring skill definition for invalid skill: "+r+
".");
1825 System.err.println(
"Ignoring skill definition for invalid skill id "+skillId+
": "+r+
".");
1830 if (sk.length > 2) {
1832 face = Integer.parseInt(sk[2]);
1833 }
catch (
final NumberFormatException ignored) {
1834 System.err.println(
"Ignoring skill definition for invalid face: "+r+
".");
1853 while (
packet.remaining() >= 2) {
1854 final short skillIndex =
packet.getShort();
1855 if (skillIndex == 0) {
1856 if (
packet.hasRemaining()) {
1857 System.err.println(
"Ignoring excess data at the of skill_extra");
1861 final short len =
packet.getShort();
1876 final long[] expTable =
new long[numLevels];
1877 for (
int level = 1; level < numLevels; level++) {
1880 if (
packet.hasRemaining()) {
1881 System.err.println(
"Ignoring excess data at end of exp_table");
1897 final byte[] data =
new byte[
packet.remaining()];
1899 try (ByteArrayInputStream is =
new ByteArrayInputStream(data)) {
1900 try (InputStreamReader isr =
new InputStreamReader(is,
UTF8)) {
1901 try (BufferedReader d =
new BufferedReader(isr)) {
1903 final CharSequence r = d.readLine();
1909 if (sk.length != 4) {
1910 System.err.println(
"Ignoring knowledge definition for invalid knowledge: "+r+
".");
1916 face = Integer.parseInt(sk[2]);
1917 }
catch (
final NumberFormatException ignored) {
1918 System.err.println(
"Ignoring knowledge definition for invalid face: "+r+
".");
1938 while (
packet.hasRemaining()) {
1970 System.err.println(
"Ignoring startingmap type "+type);
1993 for (String race : races) {
2015 for (String class_ : classes) {
2031 while (
packet.hasRemaining()) {
2051 System.err.println(
"Ignoring race_info type "+type);
2073 while (
packet.hasRemaining()) {
2079 if (tokens.length != 3) {
2082 final String typeString = tokens[0];
2083 final String variableName = tokens[1];
2084 final String values = tokens[2];
2085 switch (typeString) {
2122 if (variableName.equals(
"race")) {
2123 if (!values.equals(
"requestinfo")) {
2126 newCharInfoBuilder.setRaceChoice();
2127 }
else if (variableName.equals(
"class")) {
2128 if (!values.equals(
"requestinfo")) {
2131 newCharInfoBuilder.setClassChoice();
2132 }
else if (variableName.equals(
"startingmap")) {
2133 if (!values.equals(
"requestinfo")) {
2136 newCharInfoBuilder.setStartingMapChoice();
2137 }
else if (!required) {
2138 System.err.println(
"unknown variable name '"+variableName+
"' in replyinfo newcharinfo");
2152 switch (variableName) {
2156 points = Integer.parseInt(values);
2157 }
catch (
final NumberFormatException ignored) {
2158 throw new UnknownCommandException(
"'"+variableName+
"' variable in replyinfo newcharinfo has invalid value '"+values+
"'.");
2160 newCharInfoBuilder.setPoints(points);
2165 if (tmp.length != 2) {
2166 throw new UnknownCommandException(
"'"+variableName+
"' variable in replyinfo newcharinfo has invalid value '"+values+
"'.");
2171 minValue = Integer.parseInt(tmp[0]);
2172 maxValue = Integer.parseInt(tmp[1]);
2173 }
catch (
final NumberFormatException ignored) {
2174 throw new UnknownCommandException(
"'"+variableName+
"' variable in replyinfo newcharinfo has invalid value '"+values+
"'.");
2176 newCharInfoBuilder.setStatRange(minValue, maxValue);
2180 newCharInfoBuilder.setStatNames(
PATTERN_SPACE.split(values));
2194 System.err.println(
"ignoring informational "+variableName+
"="+values+
" in replyinfo newcharinfo");
2205 while (
packet.hasRemaining()) {
2256 rb.setStatAdjustment(stat, int2Param);
2269 rb.setStatAdjustment(stat, int4Param);
2274 rb.setStatAdjustment(stat, int8Param);
2281 System.err.println(
"replyinfo race/class_info: string stat "+stat+
" not implemented");
2287 rb.setStatAdjustment(stat, int2Param2);
2291 System.err.println(
"replyinfo race/class_info: skill stat "+stat+
" not implemented");
2313 final Map<String, String> choices =
new LinkedHashMap<>();
2314 choices.put(archName, archDesc);
2317 if (archNameLength == 0) {
2322 choices.put(archName2, archDesc2);
2324 rb.addChoice(
new Choice(choiceName, choiceDescription, choices));
2341 for (
int count = 0; count < total; count++) {
2348 if (
packet.hasRemaining()) {
2367 if (!
packet.hasRemaining()) {
2420 return accountPlayerBuilder.finish();
2460 while (
packet.hasRemaining()) {
2469 final String step = stepLength > 0 ?
getString(
packet, stepLength) :
"";
2483 while (
packet.hasRemaining()) {
2503 while (
packet.hasRemaining()) {
2518 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);
2520 model.
getSpellsManager().
addSpell(tag, level, castingTime, mana, grace, damage, skill, path, face, name, message);
2533 final int[] faces =
new int[
packet.remaining()/2];
2534 if (faces.length <= 0) {
2538 if (
packet.hasRemaining()) {
2544 if ((num&~0x1FFF) != 0) {
2549 final StringBuilder sb =
new StringBuilder();
2553 for (
final int face : faces) {
2557 return sb.toString();
2569 if (
packet.hasRemaining()) {
2588 }
while (
packet.hasRemaining());
2589 if (
packet.hasRemaining()) {
2596 final int tag0 = tag;
2606 final int[] tags =
new int[
packet.remaining()/4];
2608 if (
packet.hasRemaining()) {
2616 final StringBuilder sb =
new StringBuilder();
2617 for (
final int tag : tags) {
2621 return sb.length() == 0 ?
"" : sb.substring(1);
2632 if (
packet.hasRemaining()) {
2671 final int color0 = color;
2672 final int type0 = type;
2673 final int subtype0 = subtype;
2694 final int color0 = color;
2704 final int startPos =
packet.position();
2714 }
while (
packet.hasRemaining());
2724 final int startPos =
packet.position();
2733 if (!
packet.hasRemaining()) {
2763 final String command;
2764 final String message;
2765 final int idx = full.indexOf(
' ');
2770 command = full.substring(0, idx);
2771 message = full.substring(idx+1);
2785 if (
packet.hasRemaining()) {
2804 if (
packet.remaining() != len) {
2807 final int faceDataPosition =
packet.position();
2811 ((Buffer)
packet).position(faceDataPosition);
2812 final byte[] data =
new byte[
packet.remaining()];
2815 listener.faceReceived(faceNum, faceSetNum, data);
2827 while (
packet.hasRemaining()) {
2834 final String name = names[0].intern();
2835 final String namePl = names.length < 2 ? name : names[1].intern();
2841 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);
2843 fireAddItemReceived(location, tag, flags, weight, faceNum, name, namePl, anim, animSpeed, nrof, type);
2845 if (
packet.hasRemaining()) {
2857 final boolean widthSign =
packet.get(
packet.position()) ==
'-';
2870 final boolean heightSign =
packet.get(
packet.position()) ==
'-';
2883 final boolean pxSign =
packet.get(
packet.position()) ==
'-';
2896 final boolean pySign =
packet.get(
packet.position()) ==
'-';
2912 if (
packet.remaining() != width*height) {
2916 final byte[][] data =
new byte[height][width];
2917 for (
int y = 0; y < height; y++) {
2939 while (
packet.hasRemaining()) {
2943 final int coordType = coord&0xF;
2945 switch (coordType) {
2977 listener.mapScroll(dx, dy);
3013 if (
packet.hasRemaining()) {
3031 if (
packet.hasRemaining()) {
3052 if (
packet.hasRemaining()) {
3084 final int flags0 = flags;
3094 final int startPos =
packet.position();
3099 if (
packet.hasRemaining()) {
3107 }
catch (
final IOException ex) {
3119 final List<String> options =
new ArrayList<>();
3120 while (
packet.hasRemaining()) {
3124 final int startPos =
packet.position();
3129 if (
packet.hasRemaining()) {
3136 if (options.size()%2 != 0) {
3139 for (
int i = 0; i+1 < options.size(); i += 2) {
3140 final String option = options.get(i);
3141 final String value = options.get(i+1);
3144 if (!value.equals(
"1")) {
3145 throw new UnknownCommandException(
"Error: the server is too old for this client since it does not support the spellmon=1 setup option.");
3159 if (!value.equals(
"1")) {
3160 throw new UnknownCommandException(
"Error: the server is too old for this client since it does not support the newmapcmd=1 setup option.");
3165 if (!value.equals(
"1")) {
3166 throw new UnknownCommandException(
"the server is too old for this client since it does not support the facecache=1 setup option.");
3170 case "extendedTextInfos":
3171 if (!value.equals(
"1")) {
3172 throw new UnknownCommandException(
"the server is too old for this client since it does not support the extendedTextInfos=1 setup option.");
3177 if (!value.equals(
"2")) {
3178 throw new UnknownCommandException(
"the server is too old for this client since it does not support the itemcmd=2 setup option.");
3183 final String[] tmp = value.split(
"x", 2);
3184 if (tmp.length != 2) {
3187 final int thisMapWidth;
3188 final int thisMapHeight;
3190 thisMapWidth = Integer.parseInt(tmp[0]);
3191 thisMapHeight = Integer.parseInt(tmp[1]);
3192 }
catch (
final NumberFormatException ignored) {
3196 System.err.println(
"the server sent an unexpected 'setup mapsize "+value+
"'.");
3244 if (!value.equals(
"1")) {
3245 throw new UnknownCommandException(
"the server is too old for this client since it does not support the map2cmd=1 setup option.");
3254 if (!value.equals(
"1")) {
3255 throw new UnknownCommandException(
"the server is too old for this client since it does not support the tick=1 setup option.");
3259 case "num_look_objects":
3271 case "extended_stats":
3276 if (value.equals(
"FALSE")) {
3283 method = Integer.parseInt(value);
3284 }
catch (
final NumberFormatException ignored) {
3287 if (method < 0 || method > 2) {
3293 case "notifications":
3298 System.err.println(
"Warning: ignoring unknown setup option from server: "+option+
"="+value);
3303 if (options.size() != 2 || !options.get(0).equals(
"mapsize") && !options.get(0).equals(
"num_look_objects")) {
3317 final StringBuilder sb =
new StringBuilder();
3318 for (
final String option : options) {
3322 return sb.length() == 0 ?
"" : sb.substring(1);
3334 if (
packet.hasRemaining()) {
3350 final int x =
packet.get();
3351 final int y =
packet.get();
3354 if (
packet.hasRemaining()) {
3370 final int x =
packet.get();
3371 final int y =
packet.get();
3372 final int dir =
packet.get();
3379 if (
packet.hasRemaining()) {
3395 while (
packet.hasRemaining()) {
3522 if (
packet.hasRemaining()) {
3544 final String valName;
3545 final String valNamePl;
3551 int namePlIndex = 0;
3552 while (namePlIndex < nameLength &&
packet.get(
packet.position()+namePlIndex) != 0) {
3556 valNamePl = namePlIndex+1 < nameLength ?
newString(
packet,
packet.position()+namePlIndex+1, nameLength-(namePlIndex+1)) : valName;
3557 ((Buffer)
packet).position(
packet.position()+nameLength);
3562 if (
packet.hasRemaining()) {
3566 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);
3568 fireUpditemReceived(flags, tag, valLocation, valFlags, valWeight, valFaceNum, valName, valNamePl, valAnim, valAnimSpeed, valNrof);
3569 notifyPacketWatcherListeners(
"upditem", () -> flags+
" "+tag+
" "+valLocation+
" "+valFlags+
" "+valWeight+
" "+valFaceNum+
" "+valName+
" "+valNamePl+
" "+valAnim+
" "+valAnimSpeed+
" "+valNrof);
3580 final String step = stepLength > 0 ?
getString(
packet, stepLength) :
"";
3600 if (
packet.hasRemaining()) {
3633 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 2",
"notifications 3");
3636 final int csval0 = csval;
3637 final int scval0 = scval;
3652 final byte[] loginBytes = login.getBytes(
UTF8);
3655 final byte[] passwordBytes = password.getBytes(
UTF8);
3719 public void sendLock(
final boolean val,
final int tag) {
3765 public void sendMove(
final int to,
final int tag,
final int nrof) {
3783 public int sendNcom(
final int repeat, @NotNull
final String command) {
3787 final int thisPacket;
3789 thisPacket =
packet++&0x00FF;
3853 if (options.length <= 0) {
3856 for (String option : options) {
3862 final StringBuilder sb =
new StringBuilder(
"setup");
3863 for (String option : options) {
3864 sb.append(
" ").append(option);
3866 return sb.toString();
3873 if (types.length <= 0) {
3884 for (
int type : types) {
3889 final StringBuilder sb = new StringBuilder(
"toggleextendedtext");
3890 for (int type : types) {
3891 sb.append(
" ").append(type);
3893 return sb.toString();
3899 public void sendVersion(
final int csval,
final int scval, @NotNull
final String vinfo) {
3900 if (debugProtocol !=
null) {
3901 debugProtocol.debugProtocolWrite(
"send version cs="+csval+
" sc="+scval+
" info="+vinfo);
3903 synchronized (writeBuffer) {
3904 ((Buffer)byteBuffer).clear();
3906 byteBuffer.put(VERSION_PREFIX);
3908 byteBuffer.put((
byte)
' ');
3910 byteBuffer.put((
byte)
' ');
3911 byteBuffer.put(vinfo.getBytes(UTF8));
3912 defaultServerConnection.
writePacket(writeBuffer, byteBuffer.position(), () ->
"version "+csval+
" "+scval+
" "+vinfo);
3923 byteBuffer.put((
byte)
'0');
3925 byteBuffer.put(Integer.toString(value).getBytes(StandardCharsets.ISO_8859_1));
3936 final int digit = ch-
'0';
3937 if (digit < 0 || digit > 9) {
3945 final int preferredMapWidth2 = Math.max(1, preferredMapWidth|1);
3946 final int preferredMapHeight2 = Math.max(1, preferredMapHeight|1);
3947 if (this.preferredMapWidth == preferredMapWidth2 && this.preferredMapHeight == preferredMapHeight2) {
3951 this.preferredMapWidth = preferredMapWidth2;
3952 this.preferredMapHeight = preferredMapHeight2;
3954 negotiateMapSize(this.preferredMapWidth, this.preferredMapHeight);
3963 if (this.currentMapWidth == currentMapWidth && this.currentMapHeight == currentMapHeight) {
3967 this.currentMapWidth = currentMapWidth;
3968 this.currentMapHeight = currentMapHeight;
3976 crossfireUpdateMapListener.newMap(currentMapWidth, currentMapHeight);
3991 public void connect(@NotNull
final String hostname,
final int port) {
3996 defaultServerConnection.
connect(hostname, port);
4020 if (debugProtocol !=
null) {
4021 debugProtocol.debugProtocolWrite(
"connection state: "+nextState);
4023 if (clientSocketState != prevState) {
4024 System.err.println(
"Warning: connection state is "+clientSocketState+
" when switching to state "+nextState+
", expecting state "+prevState);
4026 clientSocketState = nextState;
4029 synchronized (writeBuffer) {
4030 param = sendingRequestInfo.isEmpty() ?
"" : sendingRequestInfo.getFirst();
4042 if (debugProtocol !=
null) {
4043 debugProtocol.debugProtocolWrite(
"send accountplay "+name);
4045 synchronized (writeBuffer) {
4046 ((Buffer)byteBuffer).clear();
4048 byteBuffer.put(ACCOUNT_PLAY_PREFIX);
4049 byteBuffer.put(name.getBytes(UTF8));
4050 defaultServerConnection.
writePacket(writeBuffer, byteBuffer.position(), () ->
"accountplay "+name);
4053 final String tmpAccountName = accountName;
4054 if (tmpAccountName !=
null) {
4055 fireSelectCharacter(tmpAccountName, name);
4060 public void sendAccountLink(
final int force, @NotNull
final String login, @NotNull
final String password) {
4062 if (debugProtocol !=
null) {
4063 debugProtocol.debugProtocolWrite(
"send accountaddplayer "+login);
4065 synchronized (writeBuffer) {
4066 ((Buffer)byteBuffer).clear();
4068 byteBuffer.put(ACCOUNT_ADD_PLAYER_PREFIX);
4069 byteBuffer.put((
byte)force);
4070 final byte[] loginBytes = login.getBytes(UTF8);
4071 byteBuffer.put((
byte)loginBytes.length);
4072 byteBuffer.put(loginBytes);
4073 final byte[] passwordBytes = password.getBytes(UTF8);
4074 byteBuffer.put((
byte)passwordBytes.length);
4075 byteBuffer.put(passwordBytes);
4076 defaultServerConnection.
writePacket(writeBuffer, byteBuffer.position(), () ->
"accountaddplayer "+login);
4083 if (debugProtocol !=
null) {
4084 debugProtocol.debugProtocolWrite(
"send accountnew "+login);
4086 accountName = login;
4087 synchronized (writeBuffer) {
4088 ((Buffer)byteBuffer).clear();
4090 byteBuffer.put(ACCOUNT_NEW_PREFIX);
4091 final byte[] loginBytes = login.getBytes(UTF8);
4092 byteBuffer.put((
byte)loginBytes.length);
4093 byteBuffer.put(loginBytes);
4094 final byte[] passwordBytes = password.getBytes(UTF8);
4095 byteBuffer.put((
byte)passwordBytes.length);
4096 byteBuffer.put(passwordBytes);
4097 defaultServerConnection.
writePacket(writeBuffer, byteBuffer.position(), () ->
"accountnew "+login);
4102 public void sendAccountCharacterCreate(@NotNull
final String login, @NotNull
final String password, @NotNull
final Collection<String> attributes) {
4104 if (debugProtocol !=
null) {
4105 debugProtocol.debugProtocolWrite(
"send createplayer "+login+
" "+attributes);
4107 synchronized (writeBuffer) {
4108 ((Buffer)byteBuffer).clear();
4110 byteBuffer.put(CREATE_PLAYER_PREFIX);
4111 final byte[] loginBytes = login.getBytes(UTF8);
4112 byteBuffer.put((
byte)loginBytes.length);
4113 byteBuffer.put(loginBytes);
4114 final byte[] passwordBytes = password.getBytes(UTF8);
4115 byteBuffer.put((
byte)passwordBytes.length);
4116 byteBuffer.put(passwordBytes);
4117 if (loginMethod >= 2) {
4118 for (
final String attribute : attributes) {
4119 final byte[] attributeBytes = attribute.getBytes(UTF8);
4120 byteBuffer.put((
byte)(attributeBytes.length+1));
4121 byteBuffer.put(attributeBytes);
4122 byteBuffer.put((
byte)0);
4125 defaultServerConnection.
writePacket(writeBuffer, byteBuffer.position(), () -> {
4126 final StringBuilder sb =
new StringBuilder(
"createplayer ");
4128 if (loginMethod >= 2) {
4129 for (
final String attribute : attributes) {
4130 sb.append(
" ").append(attribute);
4133 return sb.toString();
4141 if (debugProtocol !=
null) {
4142 debugProtocol.debugProtocolWrite(
"send accountpw");
4144 synchronized (writeBuffer) {
4145 ((Buffer)byteBuffer).clear();
4147 byteBuffer.put(ACCOUNT_PASSWORD_PREFIX);
4148 final byte[] currentPasswordBytes = currentPassword.getBytes(UTF8);
4149 byteBuffer.put((
byte)currentPasswordBytes.length);
4150 byteBuffer.put(currentPasswordBytes);
4151 final byte[] newPasswordBytes = newPassword.getBytes(UTF8);
4152 byteBuffer.put((
byte)newPasswordBytes.length);
4153 byteBuffer.put(newPasswordBytes);
4154 defaultServerConnection.
writePacket(writeBuffer, byteBuffer.position(), () ->
"accountpw");
4164 private static int getInt1(@NotNull
final ByteBuffer byteBuffer) {
4165 return byteBuffer.get()&0xFF;
4174 private static int getInt2(@NotNull
final ByteBuffer byteBuffer) {
4175 return byteBuffer.getShort()&0xFFFF;
4184 private static int getInt4(@NotNull
final ByteBuffer byteBuffer) {
4185 return byteBuffer.getInt();
4194 private static long getInt8(@NotNull
final ByteBuffer byteBuffer) {
4195 return byteBuffer.getLong();
4206 private static String
getString(@NotNull
final ByteBuffer byteBuffer,
final int len) {
4207 final byte[] tmp =
new byte[len];
4208 byteBuffer.get(tmp);
4209 return new String(tmp, UTF8);
4221 final int position = byteBuffer.position();
4222 final int remaining = byteBuffer.remaining();
4224 for (len = 0; len < remaining; len++) {
4225 if (byteBuffer.get(position+len) == delimiter) {
4229 final byte[] tmp =
new byte[len];
4230 byteBuffer.get(tmp);
4231 if (len < remaining) {
4234 return new String(tmp, UTF8);
4243 private static String
hexDump(@NotNull
final ByteBuffer byteBuffer) {
4244 final int len = byteBuffer.limit();
4245 final byte[] data =
new byte[len];
4246 for (
int i = 0; i < len; i++) {
4247 data[i] = byteBuffer.get(i);
4271 crossfireUpdateMapListener.mapClear(x, y);
4275 crossfireUpdateMapListener.mapDarkness(x, y, darkness);
4279 crossfireUpdateMapListener.mapFace(location, face);
4283 crossfireUpdateMapListener.mapAnimation(location, animationNum, animationType);
4287 crossfireUpdateMapListener.mapSmooth(location, smooth);
4291 crossfireUpdateMapListener.mapAnimationSpeed(location, animSpeed);
4294 private void fireMagicMap(
final int x,
final int y,
final byte @NotNull [] @NotNull [] data) {
4295 synchronized (crossfireUpdateMapListener.mapBegin()) {
4296 assert crossfireUpdateMapListener !=
null;
4297 crossfireUpdateMapListener.magicMap(x, y, data);
4298 assert crossfireUpdateMapListener !=
null;
4299 crossfireUpdateMapListener.mapEnd();
4309 synchronized (writeBuffer) {
4310 pendingRequestInfos.add(infoType);
4312 sendPendingRequestInfo();
4320 final String infoType;
4321 synchronized (writeBuffer) {
4322 if (sendingRequestInfo.size() >= CONCURRENT_REQUESTINFO_MESSAGES || pendingRequestInfos.isEmpty()) {
4325 infoType = pendingRequestInfos.remove(0);
4327 sendRequestinfo(infoType);
4336 synchronized (writeBuffer) {
4337 return !sendingRequestInfo.isEmpty() || !pendingRequestInfos.isEmpty();