GeoEngine.java 40 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218
  1. /*
  2. * This program is free software: you can redistribute it and/or modify it under
  3. * the terms of the GNU General Public License as published by the Free Software
  4. * Foundation, either version 3 of the License, or (at your option) any later
  5. * version.
  6. *
  7. * This program is distributed in the hope that it will be useful, but WITHOUT
  8. * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
  9. * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
  10. * details.
  11. *
  12. * You should have received a copy of the GNU General Public License along with
  13. * this program. If not, see <http://www.gnu.org/licenses/>.
  14. */
  15. package net.sf.l2j.gameserver;
  16. import java.io.BufferedOutputStream;
  17. import java.io.BufferedReader;
  18. import java.io.File;
  19. import java.io.FileOutputStream;
  20. import java.io.FileReader;
  21. import java.io.LineNumberReader;
  22. import java.io.RandomAccessFile;
  23. import java.nio.ByteBuffer;
  24. import java.nio.ByteOrder;
  25. import java.nio.IntBuffer;
  26. import java.nio.MappedByteBuffer;
  27. import java.nio.channels.FileChannel;
  28. import java.util.Map;
  29. import java.util.StringTokenizer;
  30. import java.util.logging.Logger;
  31. import javolution.util.FastMap;
  32. import net.sf.l2j.Config;
  33. import net.sf.l2j.gameserver.datatables.DoorTable;
  34. import net.sf.l2j.gameserver.model.L2Object;
  35. import net.sf.l2j.gameserver.model.L2World;
  36. import net.sf.l2j.gameserver.model.Location;
  37. import net.sf.l2j.gameserver.model.actor.instance.L2DoorInstance;
  38. import net.sf.l2j.gameserver.model.actor.instance.L2PcInstance;
  39. import net.sf.l2j.gameserver.model.actor.instance.L2SiegeGuardInstance;
  40. /**
  41. *
  42. * @author -Nemesiss-
  43. */
  44. public class GeoEngine extends GeoData
  45. {
  46. private static Logger _log = Logger.getLogger(GeoData.class.getName());
  47. private static GeoEngine _instance;
  48. private final static byte _e = 1;
  49. private final static byte _w = 2;
  50. private final static byte _s = 4;
  51. private final static byte _n = 8;
  52. private static Map<Short, MappedByteBuffer> _geodata = new FastMap<Short, MappedByteBuffer>();
  53. private static Map<Short, IntBuffer> _geodataIndex = new FastMap<Short, IntBuffer>();
  54. private static BufferedOutputStream _geoBugsOut;
  55. public static GeoEngine getInstance()
  56. {
  57. if(_instance == null)
  58. _instance = new GeoEngine();
  59. return _instance;
  60. }
  61. public GeoEngine()
  62. {
  63. nInitGeodata();
  64. }
  65. //Public Methods
  66. /**
  67. * @see net.sf.l2j.gameserver.GeoData#getType(int, int)
  68. */
  69. @Override
  70. public short getType(int x, int y)
  71. {
  72. return nGetType((x - L2World.MAP_MIN_X) >> 4, (y - L2World.MAP_MIN_Y) >> 4);
  73. }
  74. /**
  75. * @see net.sf.l2j.gameserver.GeoData#getHeight(int, int, int)
  76. */
  77. @Override
  78. public short getHeight(int x, int y, int z)
  79. {
  80. return nGetHeight((x - L2World.MAP_MIN_X) >> 4,(y - L2World.MAP_MIN_Y) >> 4,z);
  81. }
  82. /**
  83. * @see net.sf.l2j.gameserver.GeoData#getSpawnHeight(int, int, int, int, int)
  84. */
  85. @Override
  86. public short getSpawnHeight(int x, int y, int zmin, int zmax, int spawnid)
  87. {
  88. return nGetSpawnHeight((x - L2World.MAP_MIN_X) >> 4,(y - L2World.MAP_MIN_Y) >> 4,zmin,zmax,spawnid);
  89. }
  90. /**
  91. * @see net.sf.l2j.gameserver.GeoData#geoPosition(int, int)
  92. */
  93. @Override
  94. public String geoPosition(int x, int y)
  95. {
  96. int gx = (x - L2World.MAP_MIN_X) >> 4;
  97. int gy = (y - L2World.MAP_MIN_Y) >> 4;
  98. return "bx: "+getBlock(gx)+" by: "+getBlock(gy)+" cx: "+getCell(gx)+" cy: "+getCell(gy)+" region offset: "+getRegionOffset(gx,gy);
  99. }
  100. /**
  101. * @see net.sf.l2j.gameserver.GeoData#canSeeTarget(net.sf.l2j.gameserver.model.L2Object, net.sf.l2j.gameserver.model.L2Object)
  102. */
  103. @Override
  104. public boolean canSeeTarget(L2Object cha, L2Object target)
  105. {
  106. // To be able to see over fences and give the player the viewpoint
  107. // game client has, all coordinates are lifted 45 from ground.
  108. // Because of layer selection in LOS algorithm (it selects -45 there
  109. // and some layers can be very close...) do not change this without
  110. // changing the LOS code.
  111. // Basically the +45 is character height. Raid bosses are naturally higher,
  112. // dwarves shorter, but this should work relatively well.
  113. // If this is going to be improved, use e.g.
  114. // ((L2Character)cha).getTemplate().collisionHeight
  115. int z = cha.getZ()+45;
  116. if(cha instanceof L2SiegeGuardInstance) z += 30; // well they don't move closer to balcony fence at the moment :(
  117. int z2 = target.getZ()+45;
  118. if (!(target instanceof L2DoorInstance)
  119. && DoorTable.getInstance().checkIfDoorsBetween(cha.getX(),cha.getY(),z,target.getX(),target.getY(),z2))
  120. return false;
  121. if(target instanceof L2DoorInstance) return true; // door coordinates are hinge coords..
  122. if(target instanceof L2SiegeGuardInstance) z2 += 30; // well they don't move closer to balcony fence at the moment :(
  123. if(cha.getZ() >= target.getZ())
  124. return canSeeTarget(cha.getX(),cha.getY(),z,target.getX(),target.getY(),z2);
  125. else
  126. return canSeeTarget(target.getX(),target.getY(),z2, cha.getX(),cha.getY(),z);
  127. }
  128. /**
  129. * @see net.sf.l2j.gameserver.GeoData#canSeeTargetDebug(net.sf.l2j.gameserver.model.actor.instance.L2PcInstance, net.sf.l2j.gameserver.model.L2Object)
  130. */
  131. @Override
  132. public boolean canSeeTargetDebug(L2PcInstance gm, L2Object target)
  133. {
  134. // comments: see above
  135. int z = gm.getZ()+45;
  136. int z2 = target.getZ()+45;
  137. if(target instanceof L2DoorInstance)
  138. {
  139. gm.sendMessage("door always true");
  140. return true; // door coordinates are hinge coords..
  141. }
  142. if(gm.getZ() >= target.getZ())
  143. return canSeeDebug(gm,(gm.getX() - L2World.MAP_MIN_X) >> 4,(gm.getY() - L2World.MAP_MIN_Y) >> 4,z,(target.getX() - L2World.MAP_MIN_X) >> 4,(target.getY() - L2World.MAP_MIN_Y) >> 4,z2);
  144. else
  145. return canSeeDebug(gm,(target.getX() - L2World.MAP_MIN_X) >> 4,(target.getY() - L2World.MAP_MIN_Y) >> 4,z2,(gm.getX() - L2World.MAP_MIN_X) >> 4,(gm.getY() - L2World.MAP_MIN_Y) >> 4,z);
  146. }
  147. /**
  148. * @see net.sf.l2j.gameserver.GeoData#getNSWE(int, int, int)
  149. */
  150. @Override
  151. public short getNSWE(int x, int y, int z)
  152. {
  153. return nGetNSWE((x - L2World.MAP_MIN_X) >> 4,(y - L2World.MAP_MIN_Y) >> 4,z);
  154. }
  155. /**
  156. * @see net.sf.l2j.gameserver.GeoData#moveCheck(int, int, int, int, int, int)
  157. */
  158. @Override
  159. public Location moveCheck(int x, int y, int z, int tx, int ty, int tz)
  160. {
  161. Location startpoint = new Location(x,y,z);
  162. if (DoorTable.getInstance().checkIfDoorsBetween(x,y,z,tx,ty,tz))
  163. return startpoint;
  164. Location destiny = new Location(tx,ty,tz);
  165. return moveCheck(startpoint, destiny,(x - L2World.MAP_MIN_X) >> 4,(y - L2World.MAP_MIN_Y) >> 4,z,(tx - L2World.MAP_MIN_X) >> 4,(ty - L2World.MAP_MIN_Y) >> 4,tz);
  166. }
  167. /**
  168. * @see net.sf.l2j.gameserver.GeoData#addGeoDataBug(net.sf.l2j.gameserver.model.actor.instance.L2PcInstance, java.lang.String)
  169. */
  170. @Override
  171. public void addGeoDataBug(L2PcInstance gm, String comment)
  172. {
  173. int gx = (gm.getX() - L2World.MAP_MIN_X) >> 4;
  174. int gy = (gm.getY() - L2World.MAP_MIN_Y) >> 4;
  175. int bx = getBlock(gx);
  176. int by = getBlock(gy);
  177. int cx = getCell(gx);
  178. int cy = getCell(gy);
  179. int rx = (gx >> 11) + 16;
  180. int ry = (gy >> 11) + 10;
  181. String out = rx+";"+ry+";"+bx+";"+by+";"+cx+";"+cy+";"+gm.getZ()+";"+comment+"\n";
  182. try
  183. {
  184. _geoBugsOut.write(out.getBytes());
  185. _geoBugsOut.flush();
  186. gm.sendMessage("GeoData bug saved!");
  187. } catch (Exception e) {
  188. e.printStackTrace();
  189. gm.sendMessage("GeoData bug save Failed!");
  190. }
  191. }
  192. // Private Methods
  193. private boolean canSeeTarget(int x, int y, int z, int tx, int ty, int tz)
  194. {
  195. return canSee((x - L2World.MAP_MIN_X) >> 4,(y - L2World.MAP_MIN_Y) >> 4,z,(tx - L2World.MAP_MIN_X) >> 4,(ty - L2World.MAP_MIN_Y) >> 4,tz);
  196. }
  197. private static boolean canSee(int x, int y, double z, int tx, int ty, int tz)
  198. {
  199. int dx = (tx - x);
  200. int dy = (ty - y);
  201. final double dz = (tz - z);
  202. final int distance2 = dx*dx+dy*dy;
  203. if (distance2 > 90000) // (300*300) 300*16 = 4800 in world coord
  204. {
  205. //Avoid too long check
  206. return false;
  207. }
  208. // very short checks: 9 => 144 world distance
  209. // this ensures NLOS function has enough points to calculate,
  210. // it might not work when distance is small and path vertical
  211. else if (distance2 < 82)
  212. {
  213. // 200 too deep/high. This value should be in sync with NLOS
  214. if(dz*dz > 40000)
  215. {
  216. short region = getRegionOffset(x,y);
  217. // geodata is loaded for region and mobs should have correct Z coordinate...
  218. // so there would likely be a floor in between the two
  219. if (_geodata.get(region) != null)
  220. return false;
  221. }
  222. return true;
  223. }
  224. // Increment in Z coordinate when moving along X or Y axis
  225. // and not straight to the target. This is done because
  226. // calculation moves either in X or Y direction.
  227. final int inc_x = sign(dx);
  228. final int inc_y = sign(dy);
  229. dx = Math.abs(dx);
  230. dy = Math.abs(dy);
  231. final double inc_z_directionx = dz*dx / (distance2);
  232. final double inc_z_directiony = dz*dy / (distance2);
  233. // next_* are used in NLOS check from x,y
  234. int next_x = x;
  235. int next_y = y;
  236. // creates path to the target
  237. // calculation stops when next_* == target
  238. if (dx >= dy)// dy/dx <= 1
  239. {
  240. int delta_A = 2*dy;
  241. int d = delta_A - dx;
  242. int delta_B = delta_A - 2*dx;
  243. for (int i = 0; i < dx; i++)
  244. {
  245. x = next_x;
  246. y = next_y;
  247. if (d > 0)
  248. {
  249. d += delta_B;
  250. next_x += inc_x;
  251. z += inc_z_directionx;
  252. next_y += inc_y;
  253. z += inc_z_directiony;
  254. //_log.warning("1: next_x:"+next_x+" next_y"+next_y);
  255. if (!nLOS(x,y,(int)z,inc_x,inc_y,tz,false))
  256. return false;
  257. }
  258. else
  259. {
  260. d += delta_A;
  261. next_x += inc_x;
  262. //_log.warning("2: next_x:"+next_x+" next_y"+next_y);
  263. z += inc_z_directionx;
  264. if (!nLOS(x,y,(int)z,inc_x,0,tz,false))
  265. return false;
  266. }
  267. }
  268. }
  269. else
  270. {
  271. int delta_A = 2*dx;
  272. int d = delta_A - dy;
  273. int delta_B = delta_A - 2*dy;
  274. for (int i = 0; i < dy; i++)
  275. {
  276. x = next_x;
  277. y = next_y;
  278. if (d > 0)
  279. {
  280. d += delta_B;
  281. next_y += inc_y;
  282. z += inc_z_directiony;
  283. next_x += inc_x;
  284. z += inc_z_directionx;
  285. //_log.warning("3: next_x:"+next_x+" next_y"+next_y);
  286. if (!nLOS(x,y,(int)z,inc_x,inc_y,tz,false))
  287. return false;
  288. }
  289. else
  290. {
  291. d += delta_A;
  292. next_y += inc_y;
  293. //_log.warning("4: next_x:"+next_x+" next_y"+next_y);
  294. z += inc_z_directiony;
  295. if (!nLOS(x,y,(int)z,0,inc_y,tz,false))
  296. return false;
  297. }
  298. }
  299. }
  300. return true;
  301. }
  302. /*
  303. * Debug function for checking if there's a line of sight between
  304. * two coordinates.
  305. *
  306. * Creates points for line of sight check (x,y,z towards target) and
  307. * in each point, layer and movement checks are made with NLOS function.
  308. *
  309. * Coordinates here are geodata x,y but z coordinate is world coordinate
  310. */
  311. private static boolean canSeeDebug(L2PcInstance gm, int x, int y, double z, int tx, int ty, int tz)
  312. {
  313. int dx = (tx - x);
  314. int dy = (ty - y);
  315. final double dz = (tz - z);
  316. final int distance2 = dx*dx+dy*dy;
  317. if (distance2 > 90000) // (300*300) 300*16 = 4800 in world coord
  318. {
  319. //Avoid too long check
  320. gm.sendMessage("dist > 300");
  321. return false;
  322. }
  323. // very short checks: 9 => 144 world distance
  324. // this ensures NLOS function has enough points to calculate,
  325. // it might not work when distance is small and path vertical
  326. else if (distance2 < 82)
  327. {
  328. // 200 too deep/high. This value should be in sync with NLOS
  329. if(dz*dz > 40000)
  330. {
  331. short region = getRegionOffset(x,y);
  332. // geodata is loaded for region and mobs should have correct Z coordinate...
  333. // so there would likely be a floor in between the two
  334. if (_geodata.get(region) != null)
  335. return false;
  336. }
  337. return true;
  338. }
  339. // Increment in Z coordinate when moving along X or Y axis
  340. // and not straight to the target. This is done because
  341. // calculation moves either in X or Y direction.
  342. final int inc_x = sign(dx);
  343. final int inc_y = sign(dy);
  344. dx = Math.abs(dx);
  345. dy = Math.abs(dy);
  346. final double inc_z_directionx = dz*dx / (distance2);
  347. final double inc_z_directiony = dz*dy / (distance2);
  348. gm.sendMessage("Los: from X: "+x+ "Y: "+y+ "--->> X: "+tx+" Y: "+ty);
  349. // next_* are used in NLOS check from x,y
  350. int next_x = x;
  351. int next_y = y;
  352. // creates path to the target
  353. // calculation stops when next_* == target
  354. if (dx >= dy)// dy/dx <= 1
  355. {
  356. int delta_A = 2*dy;
  357. int d = delta_A - dx;
  358. int delta_B = delta_A - 2*dx;
  359. for (int i = 0; i < dx; i++)
  360. {
  361. x = next_x;
  362. y = next_y;
  363. if (d > 0)
  364. {
  365. d += delta_B;
  366. next_x += inc_x;
  367. z += inc_z_directionx;
  368. next_y += inc_y;
  369. z += inc_z_directiony;
  370. //_log.warning("1: next_x:"+next_x+" next_y"+next_y);
  371. if (!nLOS(x,y,(int)z,inc_x,inc_y,tz,true))
  372. return false;
  373. }
  374. else
  375. {
  376. d += delta_A;
  377. next_x += inc_x;
  378. //_log.warning("2: next_x:"+next_x+" next_y"+next_y);
  379. z += inc_z_directionx;
  380. if (!nLOS(x,y,(int)z,inc_x,0,tz,true))
  381. return false;
  382. }
  383. }
  384. }
  385. else
  386. {
  387. int delta_A = 2*dx;
  388. int d = delta_A - dy;
  389. int delta_B = delta_A - 2*dy;
  390. for (int i = 0; i < dy; i++)
  391. {
  392. x = next_x;
  393. y = next_y;
  394. if (d > 0)
  395. {
  396. d += delta_B;
  397. next_y += inc_y;
  398. z += inc_z_directiony;
  399. next_x += inc_x;
  400. z += inc_z_directionx;
  401. //_log.warning("3: next_x:"+next_x+" next_y"+next_y);
  402. if (!nLOS(x,y,(int)z,inc_x,inc_y,tz,true))
  403. return false;
  404. }
  405. else
  406. {
  407. d += delta_A;
  408. next_y += inc_y;
  409. //_log.warning("4: next_x:"+next_x+" next_y"+next_y);
  410. z += inc_z_directiony;
  411. if (!nLOS(x,y,(int)z,0,inc_y,tz,true))
  412. return false;
  413. }
  414. }
  415. }
  416. return true;
  417. }
  418. /*
  419. * MoveCheck
  420. */
  421. private static Location moveCheck(Location startpoint, Location destiny, int x, int y, double z, int tx, int ty, int tz)
  422. {
  423. int dx = (tx - x);
  424. int dy = (ty - y);
  425. final int distance2 = dx*dx+dy*dy;
  426. if (distance2 == 0)
  427. return destiny;
  428. if (distance2 > 36100) // 190*190*16 = 3040 world coord
  429. {
  430. // Avoid too long check
  431. // Currently we calculate a middle point
  432. // for wyvern users and otherwise for comfort
  433. double divider = Math.sqrt((double)30000/distance2);
  434. tx = x + (int)(divider * dx);
  435. ty = y + (int)(divider * dy);
  436. int dz = (tz - startpoint.getZ());
  437. tz = startpoint.getZ() + (int)(divider * dz);
  438. dx = (tx - x);
  439. dy = (ty - y);
  440. //return startpoint;
  441. }
  442. // Increment in Z coordinate when moving along X or Y axis
  443. // and not straight to the target. This is done because
  444. // calculation moves either in X or Y direction.
  445. final int inc_x = sign(dx);
  446. final int inc_y = sign(dy);
  447. dx = Math.abs(dx);
  448. dy = Math.abs(dy);
  449. //gm.sendMessage("MoveCheck: from X: "+x+ "Y: "+y+ "--->> X: "+tx+" Y: "+ty);
  450. // next_* are used in NcanMoveNext check from x,y
  451. int next_x = x;
  452. int next_y = y;
  453. double tempz = z;
  454. // creates path to the target, using only x or y direction
  455. // calculation stops when next_* == target
  456. if (dx >= dy)// dy/dx <= 1
  457. {
  458. int delta_A = 2*dy;
  459. int d = delta_A - dx;
  460. int delta_B = delta_A - 2*dx;
  461. for (int i = 0; i < dx; i++)
  462. {
  463. x = next_x;
  464. y = next_y;
  465. if (d > 0)
  466. {
  467. d += delta_B;
  468. next_x += inc_x;
  469. next_y += inc_y;
  470. //_log.warning("2: next_x:"+next_x+" next_y"+next_y);
  471. tempz = nCanMoveNext(x,y,(int)z,next_x,next_y,tz);
  472. if (tempz == Double.MIN_VALUE)
  473. return new Location((x << 4) + L2World.MAP_MIN_X,(y << 4) + L2World.MAP_MIN_Y,(int)z);
  474. else z = tempz;
  475. }
  476. else
  477. {
  478. d += delta_A;
  479. next_x += inc_x;
  480. //_log.warning("3: next_x:"+next_x+" next_y"+next_y);
  481. tempz = nCanMoveNext(x,y,(int)z,next_x,next_y,tz);
  482. if (tempz == Double.MIN_VALUE)
  483. return new Location((x << 4) + L2World.MAP_MIN_X,(y << 4) + L2World.MAP_MIN_Y,(int)z);
  484. else z = tempz;
  485. }
  486. }
  487. }
  488. else
  489. {
  490. int delta_A = 2*dx;
  491. int d = delta_A - dy;
  492. int delta_B = delta_A - 2*dy;
  493. for (int i = 0; i < dy; i++)
  494. {
  495. x = next_x;
  496. y = next_y;
  497. if (d > 0)
  498. {
  499. d += delta_B;
  500. next_y += inc_y;
  501. next_x += inc_x;
  502. //_log.warning("5: next_x:"+next_x+" next_y"+next_y);
  503. tempz = nCanMoveNext(x,y,(int)z,next_x,next_y,tz);
  504. if (tempz == Double.MIN_VALUE)
  505. return new Location((x << 4) + L2World.MAP_MIN_X,(y << 4) + L2World.MAP_MIN_Y,(int)z);
  506. else z = tempz;
  507. }
  508. else
  509. {
  510. d += delta_A;
  511. next_y += inc_y;
  512. //_log.warning("6: next_x:"+next_x+" next_y"+next_y);
  513. tempz = nCanMoveNext(x,y,(int)z,next_x,next_y,tz);
  514. if (tempz == Double.MIN_VALUE)
  515. return new Location((x << 4) + L2World.MAP_MIN_X,(y << 4) + L2World.MAP_MIN_Y,(int)z);
  516. else z = tempz;
  517. }
  518. }
  519. }
  520. return destiny; // should actually return correct z here instead of tz
  521. }
  522. private static byte sign(int x)
  523. {
  524. if (x >= 0)
  525. return +1;
  526. else
  527. return -1;
  528. }
  529. //GeoEngine
  530. private static void nInitGeodata()
  531. {
  532. LineNumberReader lnr = null;
  533. try
  534. {
  535. _log.info("Geo Engine: - Loading Geodata...");
  536. File Data = new File("./data/geodata/geo_index.txt");
  537. if (!Data.exists())
  538. return;
  539. lnr = new LineNumberReader(new BufferedReader(new FileReader(Data)));
  540. } catch (Exception e) {
  541. e.printStackTrace();
  542. throw new Error("Failed to Load geo_index File.");
  543. }
  544. String line;
  545. try
  546. {
  547. while ((line = lnr.readLine()) != null) {
  548. if (line.trim().length() == 0)
  549. continue;
  550. StringTokenizer st = new StringTokenizer(line, "_");
  551. byte rx = Byte.parseByte(st.nextToken());
  552. byte ry = Byte.parseByte(st.nextToken());
  553. loadGeodataFile(rx,ry);
  554. }
  555. } catch (Exception e) {
  556. e.printStackTrace();
  557. throw new Error("Failed to Read geo_index File.");
  558. }
  559. try
  560. {
  561. File geo_bugs = new File("./data/geodata/geo_bugs.txt");
  562. _geoBugsOut = new BufferedOutputStream(new FileOutputStream(geo_bugs,true));
  563. } catch (Exception e) {
  564. e.printStackTrace();
  565. throw new Error("Failed to Load geo_bugs.txt File.");
  566. }
  567. }
  568. public static void unloadGeodata(byte rx, byte ry)
  569. {
  570. short regionoffset = (short)((rx << 5) + ry);
  571. _geodataIndex.remove(regionoffset);
  572. _geodata.remove(regionoffset);
  573. }
  574. public static boolean loadGeodataFile(byte rx, byte ry)
  575. {
  576. String fname = "./data/geodata/"+rx+"_"+ry+".l2j";
  577. short regionoffset = (short)((rx << 5) + ry);
  578. _log.info("Geo Engine: - Loading: "+fname+" -> region offset: "+regionoffset+"X: "+rx+" Y: "+ry);
  579. File Geo = new File(fname);
  580. int size, index = 0, block = 0, flor = 0;
  581. try {
  582. // Create a read-only memory-mapped file
  583. FileChannel roChannel = new RandomAccessFile(Geo, "r").getChannel();
  584. size = (int)roChannel.size();
  585. MappedByteBuffer geo;
  586. if (Config.FORCE_GEODATA) //Force O/S to Loads this buffer's content into physical memory.
  587. //it is not guarantee, because the underlying operating system may have paged out some of the buffer's data
  588. geo = roChannel.map(FileChannel.MapMode.READ_ONLY, 0, size).load();
  589. else
  590. geo = roChannel.map(FileChannel.MapMode.READ_ONLY, 0, size);
  591. geo.order(ByteOrder.LITTLE_ENDIAN);
  592. if (size > 196608)
  593. {
  594. // Indexing geo files, so we will know where each block starts
  595. IntBuffer indexs = IntBuffer.allocate(65536);
  596. while(block < 65536)
  597. {
  598. byte type = geo.get(index);
  599. indexs.put(block,index);
  600. block++;
  601. index++;
  602. if(type == 0)
  603. index += 2; // 1x short
  604. else if(type == 1)
  605. index += 128; // 64 x short
  606. else
  607. {
  608. int b;
  609. for(b=0;b<64;b++)
  610. {
  611. byte layers = geo.get(index);
  612. index += (layers << 1) + 1;
  613. if (layers > flor)
  614. flor = layers;
  615. }
  616. }
  617. }
  618. _geodataIndex.put(regionoffset, indexs);
  619. }
  620. _geodata.put(regionoffset,geo);
  621. _log.info("Geo Engine: - Max Layers: "+flor+" Size: "+size+" Loaded: "+index);
  622. } catch (Exception e)
  623. {
  624. e.printStackTrace();
  625. _log.warning("Failed to Load GeoFile at block: "+block+"\n");
  626. return false;
  627. }
  628. return true;
  629. }
  630. //Geodata Methods
  631. /**
  632. * @param x
  633. * @param y
  634. * @return Region Offset
  635. */
  636. private static short getRegionOffset(int x, int y)
  637. {
  638. int rx = x >> 11; // =/(256 * 8)
  639. int ry = y >> 11;
  640. return (short)(((rx+16) << 5) + (ry+10));
  641. }
  642. /**
  643. * @param pos
  644. * @return Block Index: 0-255
  645. */
  646. private static int getBlock(int geo_pos)
  647. {
  648. return (geo_pos >> 3) % 256;
  649. }
  650. /**
  651. * @param pos
  652. * @return Cell Index: 0-7
  653. */
  654. private static int getCell(int geo_pos)
  655. {
  656. return geo_pos % 8;
  657. }
  658. //Geodata Functions
  659. /**
  660. * @param x
  661. * @param y
  662. * @return Type of geo_block: 0-2
  663. */
  664. private static short nGetType(int x, int y)
  665. {
  666. short region = getRegionOffset(x,y);
  667. int blockX = getBlock(x);
  668. int blockY = getBlock(y);
  669. int index = 0;
  670. //Geodata without index - it is just empty so index can be calculated on the fly
  671. if(_geodataIndex.get(region) == null) index = ((blockX << 8) + blockY)*3;
  672. //Get Index for current block of current geodata region
  673. else index = _geodataIndex.get(region).get((blockX << 8) + blockY);
  674. //Buffer that Contains current Region GeoData
  675. ByteBuffer geo = _geodata.get(region);
  676. if(geo == null)
  677. {
  678. if(Config.DEBUG)
  679. _log.warning("Geo Region - Region Offset: "+region+" dosnt exist!!");
  680. return 0;
  681. }
  682. return geo.get(index);
  683. }
  684. /**
  685. * @param x
  686. * @param y
  687. * @param z
  688. * @return Nearlest Z
  689. */
  690. private static short nGetHeight(int geox, int geoy, int z)
  691. {
  692. short region = getRegionOffset(geox,geoy);
  693. int blockX = getBlock(geox);
  694. int blockY = getBlock(geoy);
  695. int cellX, cellY, index;
  696. //Geodata without index - it is just empty so index can be calculated on the fly
  697. if(_geodataIndex.get(region) == null) index = ((blockX << 8) + blockY)*3;
  698. //Get Index for current block of current region geodata
  699. else index = _geodataIndex.get(region).get(((blockX << 8))+(blockY));
  700. //Buffer that Contains current Region GeoData
  701. ByteBuffer geo = _geodata.get(region);
  702. if(geo == null)
  703. {
  704. if(Config.DEBUG)
  705. _log.warning("Geo Region - Region Offset: "+region+" dosnt exist!!");
  706. return (short)z;
  707. }
  708. //Read current block type: 0-flat,1-complex,2-multilevel
  709. byte type = geo.get(index);
  710. index++;
  711. if(type == 0)//flat
  712. return geo.getShort(index);
  713. else if(type == 1)//complex
  714. {
  715. cellX = getCell(geox);
  716. cellY = getCell(geoy);
  717. index += ((cellX << 3) + cellY) << 1;
  718. short height = geo.getShort(index);
  719. height = (short)(height&0x0fff0);
  720. height = (short)(height >> 1); //height / 2
  721. return height;
  722. }
  723. else //multilevel
  724. {
  725. cellX = getCell(geox);
  726. cellY = getCell(geoy);
  727. int offset = (cellX << 3) + cellY;
  728. while(offset > 0)
  729. {
  730. byte lc = geo.get(index);
  731. index += (lc << 1) + 1;
  732. offset--;
  733. }
  734. byte layers = geo.get(index);
  735. index++;
  736. short height=-1;
  737. if(layers <= 0 || layers > 125)
  738. {
  739. _log.warning("Broken geofile (case1), region: "+region+" - invalid layer count: "+layers+" at: "+geox+" "+geoy);
  740. return (short)z;
  741. }
  742. short temph = Short.MIN_VALUE;
  743. while(layers > 0)
  744. {
  745. height = geo.getShort(index);
  746. height = (short)(height&0x0fff0);
  747. height = (short)(height >> 1); //height / 2
  748. if ((z-temph)*(z-temph) > (z-height)*(z-height))
  749. temph = height;
  750. layers--;
  751. index += 2;
  752. }
  753. return temph;
  754. }
  755. }
  756. /**
  757. * @param x
  758. * @param y
  759. * @param zmin
  760. * @param zmax
  761. * @return Z betwen zmin and zmax
  762. */
  763. private static short nGetSpawnHeight(int geox, int geoy, int zmin, int zmax, int spawnid)
  764. {
  765. short region = getRegionOffset(geox,geoy);
  766. int blockX = getBlock(geox);
  767. int blockY = getBlock(geoy);
  768. int cellX, cellY, index;
  769. short temph = Short.MIN_VALUE;
  770. //Geodata without index - it is just empty so index can be calculated on the fly
  771. if(_geodataIndex.get(region) == null) index = ((blockX << 8) + blockY)*3;
  772. //Get Index for current block of current region geodata
  773. else index = _geodataIndex.get(region).get(((blockX << 8))+(blockY));
  774. //Buffer that Contains current Region GeoData
  775. ByteBuffer geo = _geodata.get(region);
  776. if(geo == null)
  777. {
  778. if(Config.DEBUG)
  779. _log.warning("Geo Region - Region Offset: "+region+" dosnt exist!!");
  780. return (short)zmin;
  781. }
  782. //Read current block type: 0-flat,1-complex,2-multilevel
  783. byte type = geo.get(index);
  784. index++;
  785. if(type == 0)//flat
  786. temph = geo.getShort(index);
  787. else if(type == 1)//complex
  788. {
  789. cellX = getCell(geox);
  790. cellY = getCell(geoy);
  791. index += ((cellX << 3) + cellY) << 1;
  792. short height = geo.getShort(index);
  793. height = (short)(height&0x0fff0);
  794. height = (short)(height >> 1); //height / 2
  795. temph = height;
  796. }
  797. else//multilevel
  798. {
  799. cellX = getCell(geox);
  800. cellY = getCell(geoy);
  801. short height;
  802. int offset = (cellX << 3) + cellY;
  803. while(offset > 0)
  804. {
  805. byte lc = geo.get(index);
  806. index += (lc << 1) + 1;
  807. offset--;
  808. }
  809. //Read current block type: 0-flat,1-complex,2-multilevel
  810. byte layers = geo.get(index);
  811. index++;
  812. if(layers <= 0 || layers > 125)
  813. {
  814. _log.warning("Broken geofile (case2), region: "+region+" - invalid layer count: "+layers+" at: "+geox+" "+geoy);
  815. return (short)zmin;
  816. }
  817. while(layers > 0)
  818. {
  819. height = geo.getShort(index);
  820. height = (short)(height&0x0fff0);
  821. height = (short)(height >> 1); //height / 2
  822. if ((zmin-temph)*(zmin-temph) > (zmin-height)*(zmin-height))
  823. temph = height;
  824. layers--;
  825. index += 2;
  826. }
  827. if (temph > zmax + 200 || temph < zmin - 200)
  828. {
  829. if(Config.DEBUG)
  830. _log.warning("SpawnHeight Error - Couldnt find correct layer to spawn NPC - GeoData or Spawnlist Bug!: zmin: "+zmin+" zmax: "+zmax+" value: "+temph+" SpawnId: "+spawnid+" at: "+geox+" : "+geoy);
  831. return (short)zmin;
  832. }
  833. }
  834. if (temph > zmax + 1000 || temph < zmin - 1000)
  835. {
  836. if(Config.DEBUG)
  837. _log.warning("SpawnHeight Error - Spawnlist z value is wrong or GeoData error: zmin: "+zmin+" zmax: "+zmax+" value: "+temph+" SpawnId: "+spawnid+" at: "+geox+" : "+geoy);
  838. return (short)zmin;
  839. }
  840. return temph;
  841. }
  842. /**
  843. * @param x
  844. * @param y
  845. * @param z
  846. * @param tx
  847. * @param ty
  848. * @param tz
  849. * @return True if char can move to (tx,ty,tz)
  850. */
  851. private static double nCanMoveNext(int x, int y, int z, int tx, int ty, int tz)
  852. {
  853. short region = getRegionOffset(x,y);
  854. int blockX = getBlock(x);
  855. int blockY = getBlock(y);
  856. int cellX, cellY;
  857. short NSWE = 0;
  858. int index = 0;
  859. //Geodata without index - it is just empty so index can be calculated on the fly
  860. if(_geodataIndex.get(region) == null) index = ((blockX << 8) + blockY)*3;
  861. //Get Index for current block of current region geodata
  862. else index = _geodataIndex.get(region).get(((blockX << 8))+(blockY));
  863. //Buffer that Contains current Region GeoData
  864. ByteBuffer geo = _geodata.get(region);
  865. if(geo == null)
  866. {
  867. if(Config.DEBUG)
  868. _log.warning("Geo Region - Region Offset: "+region+" dosnt exist!!");
  869. return z;
  870. }
  871. //Read current block type: 0-flat,1-complex,2-multilevel
  872. byte type = geo.get(index);
  873. index++;
  874. if(type == 0) //flat
  875. return z;
  876. else if(type == 1) //complex
  877. {
  878. cellX = getCell(x);
  879. cellY = getCell(y);
  880. index += ((cellX << 3) + cellY) << 1;
  881. short height = geo.getShort(index);
  882. NSWE = (short)(height&0x0F);
  883. height = (short)(height&0x0fff0);
  884. height = (short)(height >> 1); //height / 2
  885. if(checkNSWE(NSWE,x,y,tx,ty)) return height;
  886. else return Double.MIN_VALUE;
  887. }
  888. else //multilevel, type == 2
  889. {
  890. cellX = getCell(x);
  891. cellY = getCell(y);
  892. int offset = (cellX << 3) + cellY;
  893. while(offset > 0) // iterates (too many times?) to get to layer count
  894. {
  895. byte lc = geo.get(index);
  896. index += (lc << 1) + 1;
  897. offset--;
  898. }
  899. byte layers = geo.get(index);
  900. //_log.warning("layers"+layers);
  901. index++;
  902. short height=-1;
  903. if(layers <= 0 || layers > 125)
  904. {
  905. _log.warning("Broken geofile (case3), region: "+region+" - invalid layer count: "+layers+" at: "+x+" "+y);
  906. return z;
  907. }
  908. short tempz = Short.MIN_VALUE;
  909. while(layers > 0)
  910. {
  911. height = geo.getShort(index);
  912. height = (short)(height&0x0fff0);
  913. height = (short)(height >> 1); //height / 2
  914. // searches the closest layer to current z coordinate
  915. if ((z-tempz)*(z-tempz) > (z-height)*(z-height))
  916. {
  917. //layercurr = layers;
  918. tempz = height;
  919. NSWE = geo.getShort(index);
  920. NSWE = (short)(NSWE&0x0F);
  921. }
  922. layers--;
  923. index += 2;
  924. }
  925. if(checkNSWE(NSWE,x,y,tx,ty)) return tempz;
  926. else return Double.MIN_VALUE;
  927. }
  928. }
  929. /**
  930. * @param x
  931. * @param y
  932. * @param z
  933. * @param inc_x
  934. * @param inc_y
  935. * @param tz
  936. * @return True if Char can see target
  937. */
  938. private static boolean nLOS(int x, int y, int z, int inc_x, int inc_y, int tz, boolean debug)
  939. {
  940. short region = getRegionOffset(x,y);
  941. int blockX = getBlock(x);
  942. int blockY = getBlock(y);
  943. int cellX, cellY;
  944. short NSWE = 0;
  945. int index;
  946. //Geodata without index - it is just empty so index can be calculated on the fly
  947. if(_geodataIndex.get(region) == null) index = ((blockX << 8) + blockY)*3;
  948. //Get Index for current block of current region geodata
  949. else index = _geodataIndex.get(region).get(((blockX << 8))+(blockY));
  950. //Buffer that Contains current Region GeoData
  951. ByteBuffer geo = _geodata.get(region);
  952. if(geo == null)
  953. {
  954. if(Config.DEBUG)
  955. _log.warning("Geo Region - Region Offset: "+region+" dosnt exist!!");
  956. return true;
  957. }
  958. //Read current block type: 0-flat,1-complex,2-multilevel
  959. byte type = geo.get(index);
  960. index++;
  961. if(type == 0) //flat, movement and sight always possible
  962. {
  963. if(debug) _log.warning("flatheight:"+geo.getShort(index));
  964. return true;
  965. }
  966. else if(type == 1) //complex
  967. {
  968. cellX = getCell(x);
  969. cellY = getCell(y);
  970. index += ((cellX << 3) + cellY) << 1;
  971. short height = geo.getShort(index);
  972. NSWE = (short)(height&0x0F);
  973. height = (short)(height&0x0fff0);
  974. height = (short)(height >> 1); //height / 2
  975. if(debug) {
  976. _log.warning("height:"+height+" z"+z);
  977. if(!checkNSWE(NSWE,x,y,x+inc_x,y+inc_y)) _log.warning("would block");
  978. }
  979. if(z - height > 50) return true; // this value is just an approximate
  980. }
  981. else//multilevel, type == 2
  982. {
  983. cellX = getCell(x);
  984. cellY = getCell(y);
  985. int offset = (cellX << 3) + cellY;
  986. while(offset > 0) // iterates (too many times?) to get to layer count
  987. {
  988. byte lc = geo.get(index);
  989. index += (lc << 1) + 1;
  990. offset--;
  991. }
  992. byte layers = geo.get(index);
  993. if (debug) _log.warning("layers"+layers);
  994. index++;
  995. short height=-1;
  996. if(layers <= 0 || layers > 125)
  997. {
  998. _log.warning("Broken geofile (case4), region: "+region+" - invalid layer count: "+layers+" at: "+x+" "+y);
  999. return false;
  1000. }
  1001. short tempz = Short.MIN_VALUE; // big negative value
  1002. byte temp_layers = layers;
  1003. boolean highestlayer = true;
  1004. z -= 25; // lowering level temporarily to avoid selecting ceiling
  1005. while(temp_layers > 0)
  1006. {
  1007. // reads height for current layer, result in world z coordinate
  1008. height = geo.getShort(index);
  1009. height = (short)(height&0x0fff0);
  1010. height = (short)(height >> 1); //height / 2
  1011. //height -= 8; // old geo files had -8 around giran, new data seems better
  1012. // searches the closest layer to current z coordinate
  1013. if ((z-tempz)*(z-tempz) > (z-height)*(z-height))
  1014. {
  1015. if(tempz > Short.MIN_VALUE) highestlayer = false;
  1016. tempz = height;
  1017. if (debug) _log.warning("z"+(z+45)+" tempz"+tempz+" dz"+(z-tempz));
  1018. NSWE = geo.getShort(index);
  1019. NSWE = (short)(NSWE&0x0F);
  1020. }
  1021. temp_layers--;
  1022. index += 2;
  1023. }
  1024. z += 25; // level rises back
  1025. // Check if LOS goes under a layer/floor
  1026. if((z-tempz) < -20) return false; // -20 => clearly under, approximates also fence width
  1027. // this helps in some cases (occasional under-highest-layer block which isn't wall)
  1028. // but might also create problems in others (passes walls when you're standing high)
  1029. if((z-tempz) > 250) return true;
  1030. // or there's a fence/wall ahead when we're not on highest layer
  1031. // this part of the check is problematic
  1032. if(!highestlayer)
  1033. {
  1034. //a probable wall, there's movement block and layers above you
  1035. if(!checkNSWE(NSWE,x,y,x+inc_x,y+inc_y)) // cannot move
  1036. {
  1037. // the height after 2 inc_x,inc_y
  1038. short nextheight = nGetHeight(x+2*inc_x,y+2*inc_y,z-50);
  1039. if(debug)
  1040. {
  1041. _log.warning("0: z:"+z+" tz"+nGetHeight(x,y,z-60));
  1042. _log.warning("1: z:"+z+" tz"+nGetHeight(x+inc_x,y+inc_y,z-60));
  1043. _log.warning("2: z:"+z+" tz"+nGetHeight(x+2*inc_x,y+2*inc_y,z-60));
  1044. _log.warning("3: z:"+z+" tz"+nGetHeight(x+3*inc_x,y+3*inc_y,z-60));
  1045. }
  1046. // Probably a very thin fence (e.g. castle fences above artefact),
  1047. // where height instantly drops after 1-2 cells and layer ends.
  1048. if(z-nextheight>100) return true;
  1049. // layer continues so close we can see over it
  1050. if(nextheight-tempz>5 && nextheight-tempz<20) return true;
  1051. return false;
  1052. }
  1053. else return true;
  1054. }
  1055. else return true;
  1056. }
  1057. return checkNSWE(NSWE,x,y,x+inc_x,y+inc_y);
  1058. }
  1059. /**
  1060. * @param x
  1061. * @param y
  1062. * @param z
  1063. * @return NSWE: 0-15
  1064. */
  1065. private short nGetNSWE(int x, int y, int z)
  1066. {
  1067. short region = getRegionOffset(x,y);
  1068. int blockX = getBlock(x);
  1069. int blockY = getBlock(y);
  1070. int cellX, cellY;
  1071. short NSWE = 0;
  1072. int index = 0;
  1073. //Geodata without index - it is just empty so index can be calculated on the fly
  1074. if(_geodataIndex.get(region) == null) index = ((blockX << 8) + blockY)*3;
  1075. //Get Index for current block of current region geodata
  1076. else index = _geodataIndex.get(region).get(((blockX << 8))+(blockY));
  1077. //Buffer that Contains current Region GeoData
  1078. ByteBuffer geo = _geodata.get(region);
  1079. if(geo == null)
  1080. {
  1081. if(Config.DEBUG)
  1082. _log.warning("Geo Region - Region Offset: "+region+" dosnt exist!!");
  1083. return 15;
  1084. }
  1085. //Read current block type: 0-flat,1-complex,2-multilevel
  1086. byte type = geo.get(index);
  1087. index++;
  1088. if(type == 0)//flat
  1089. return 15;
  1090. else if(type == 1)//complex
  1091. {
  1092. cellX = getCell(x);
  1093. cellY = getCell(y);
  1094. index += ((cellX << 3) + cellY) << 1;
  1095. short height = geo.getShort(index);
  1096. NSWE = (short)(height&0x0F);
  1097. }
  1098. else//multilevel
  1099. {
  1100. cellX = getCell(x);
  1101. cellY = getCell(y);
  1102. int offset = (cellX << 3) + cellY;
  1103. while(offset > 0)
  1104. {
  1105. byte lc = geo.get(index);
  1106. index += (lc << 1) + 1;
  1107. offset--;
  1108. }
  1109. byte layers = geo.get(index);
  1110. index++;
  1111. short height=-1;
  1112. if(layers <= 0 || layers > 125)
  1113. {
  1114. _log.warning("Broken geofile (case5), region: "+region+" - invalid layer count: "+layers+" at: "+x+" "+y);
  1115. return 15;
  1116. }
  1117. short tempz = Short.MIN_VALUE;
  1118. while(layers > 0)
  1119. {
  1120. height = geo.getShort(index);
  1121. height = (short)(height&0x0fff0);
  1122. height = (short)(height >> 1); //height / 2
  1123. if ((z-tempz)*(z-tempz) > (z-height)*(z-height))
  1124. {
  1125. tempz = height;
  1126. NSWE = geo.get(index);
  1127. NSWE = (short)(NSWE&0x0F);
  1128. }
  1129. layers--;
  1130. index += 2;
  1131. }
  1132. }
  1133. return NSWE;
  1134. }
  1135. /**
  1136. * @param NSWE
  1137. * @param x
  1138. * @param y
  1139. * @param tx
  1140. * @param ty
  1141. * @return True if NSWE dont block given direction
  1142. */
  1143. private static boolean checkNSWE(short NSWE, int x, int y, int tx, int ty)
  1144. {
  1145. //Check NSWE
  1146. if(NSWE == 15)
  1147. return true;
  1148. if(tx > x)//E
  1149. {
  1150. if ((NSWE & _e) == 0)
  1151. return false;
  1152. }
  1153. else if (tx < x)//W
  1154. {
  1155. if ((NSWE & _w) == 0)
  1156. return false;
  1157. }
  1158. if (ty > y)//S
  1159. {
  1160. if ((NSWE & _s) == 0)
  1161. return false;
  1162. }
  1163. else if (ty < y)//N
  1164. {
  1165. if ((NSWE & _n) == 0)
  1166. return false;
  1167. }
  1168. return true;
  1169. }
  1170. }