GeoEngine.java 40 KB

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