ai.js 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206
  1. /*
  2. * ===========================================================================
  3. *
  4. * Wolf3D Browser Version GPL Source Code
  5. * Copyright (C) 2012 id Software LLC, a ZeniMax Media company.
  6. *
  7. * This file is part of the Wolf3D Browser Version GPL Source Code ("Wolf3D Browser Source Code").
  8. *
  9. * Wolf3D Browser Source Code is free software: you can redistribute it and/or modify
  10. * it under the terms of the GNU General Public License as published by
  11. * the Free Software Foundation, either version 2 of the License, or
  12. * (at your option) any later version.
  13. *
  14. * Wolf3D Browser Source Code is distributed in the hope that it will be useful,
  15. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  17. * GNU General Public License for more details.
  18. *
  19. * You should have received a copy of the GNU General Public License version 2
  20. * along with Wolf3D Browser Source Code. If not, see <http://www.gnu.org/licenses/>.
  21. *
  22. * If you have questions concerning this license, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
  23. *
  24. * ===========================================================================
  25. */
  26. /**
  27. * @namespace
  28. * @description Enemy AI
  29. */
  30. Wolf.AI = (function() {
  31. Wolf.setConsts({
  32. RUNSPEED : 6000,
  33. MINSIGHT : 0x18000
  34. });
  35. function checkSight(self, game) {
  36. var level = game.level,
  37. player = game.player,
  38. deltax, deltay;
  39. // don't bother tracing a line if the area isn't connected to the player's
  40. if (!(self.flags & Wolf.FL_AMBUSH)) {
  41. if (!level.state.areabyplayer[self.areanumber]) {
  42. return false;
  43. }
  44. }
  45. // if the player is real close, sight is automatic
  46. deltax = player.position.x - self.x;
  47. deltay = player.position.y - self.y;
  48. if (Math.abs(deltax) < Wolf.MINSIGHT && Math.abs(deltay) < Wolf.MINSIGHT) {
  49. return true;
  50. }
  51. // see if they are looking in the right direction
  52. switch (self.dir) {
  53. case Wolf.Math.dir8_north:
  54. if (deltay < 0) {
  55. return false;
  56. }
  57. break;
  58. case Wolf.Math.dir8_east:
  59. if (deltax < 0) {
  60. return false;
  61. }
  62. break;
  63. case Wolf.Math.dir8_south:
  64. if (deltay > 0) {
  65. return false;
  66. }
  67. break;
  68. case Wolf.Math.dir8_west:
  69. if (deltax > 0) {
  70. return false;
  71. }
  72. break;
  73. default:
  74. break;
  75. }
  76. // trace a line to check for blocking tiles (corners)
  77. return Wolf.Level.checkLine(self.x, self.y, player.position.x, player.position.y, level);
  78. }
  79. /**
  80. * @description Entity is going to move in a new direction.
  81. * Called, when actor finished previous moving & located in
  82. * the 'center' of the tile. Entity will try walking in direction.
  83. * @private
  84. * @returns {boolean} true if direction is OK, otherwise false.
  85. */
  86. function changeDir(self, new_dir, level) {
  87. var oldx,
  88. oldy,
  89. newx,
  90. newy, // all it tiles
  91. n,
  92. moveok = false;
  93. oldx = Wolf.POS2TILE(self.x);
  94. oldy = Wolf.POS2TILE(self.y);
  95. //assert( new_dir >= 0 && new_dir <= 8 );
  96. newx = oldx + Wolf.Math.dx8dir[new_dir];
  97. newy = oldy + Wolf.Math.dy8dir[new_dir];
  98. if (new_dir & 0x01) { // same as %2 (diagonal dir)
  99. if (level.tileMap[newx][oldy] & Wolf.SOLID_TILE ||
  100. level.tileMap[oldx][newy] & Wolf.SOLID_TILE ||
  101. level.tileMap[newx][newy] & Wolf.SOLID_TILE) {
  102. return false;
  103. }
  104. for (n=0; n < level.state.numGuards; ++n) {
  105. if (level.state.guards[n].state >= Wolf.st_die1) {
  106. continue;
  107. }
  108. if (level.state.guards[n].tile.x == newx && level.state.guards[n].tile.y == newy) {
  109. return false; // another guard in path
  110. }
  111. if (level.state.guards[n].tile.x == oldx && level.state.guards[n].tile.y == newy) {
  112. return false; // another guard in path
  113. }
  114. if (level.state.guards[n].tile.x == newx && level.state.guards[n].tile.y == oldy) {
  115. return false; // another guard in path
  116. }
  117. }
  118. } else { // linear dir (E, N, W, S)
  119. if (level.tileMap[newx][newy] & Wolf.SOLID_TILE) {
  120. return false;
  121. }
  122. if (level.tileMap[newx][newy] & Wolf.DOOR_TILE) {
  123. if (self.type == Wolf.en_fake || self.type == Wolf.en_dog) { // they can't open doors
  124. if (level.state.doorMap[newx][newy].action != Wolf.dr_open) { // path is blocked by a closed opened door
  125. return false;
  126. }
  127. } else {
  128. self.waitfordoorx = newx;
  129. self.waitfordoory = newy;
  130. moveok = true;
  131. }
  132. }
  133. if (!moveok) {
  134. for (n = 0; n < level.state.numGuards; ++n) {
  135. if (level.state.guards[n].state >= Wolf.st_die1) {
  136. continue;
  137. }
  138. if (level.state.guards[n].tile.x == newx && level.state.guards[n].tile.y == newy) {
  139. return false; // another guard in path
  140. }
  141. }
  142. }
  143. }
  144. //moveok:
  145. self.tile.x = newx;
  146. self.tile.y = newy;
  147. level.tileMap[oldx][oldy] &= ~Wolf.ACTOR_TILE; // update map status
  148. level.tileMap[newx][newy] |= Wolf.ACTOR_TILE;
  149. if (level.areas[newx][newy] > 0) {
  150. // ambush tiles don't have valid area numbers (-3), so don't change the area if walking over them
  151. self.areanumber = level.areas[newx][newy];
  152. // assert( self.areanumber >= 0 && self.areanumber < NUMAREAS );
  153. }
  154. self.distance = Wolf.TILEGLOBAL;
  155. self.dir = new_dir;
  156. return true;
  157. }
  158. /**
  159. * @description Entity is going to turn on a way point.
  160. * @private
  161. */
  162. function path(self, game) {
  163. var level = game.level;
  164. if (level.tileMap[self.x >> Wolf.TILESHIFT][self.y >> Wolf.TILESHIFT] & Wolf.WAYPOINT_TILE) {
  165. var tileinfo = level.tileMap[self.x >> Wolf.TILESHIFT][self.y >> Wolf.TILESHIFT];
  166. if (tileinfo & Wolf.TILE_IS_E_TURN) {
  167. self.dir = Wolf.Math.dir8_east;
  168. } else if (tileinfo & Wolf.TILE_IS_NE_TURN) {
  169. self.dir = Wolf.Math.dir8_northeast;
  170. } else if (tileinfo & Wolf.TILE_IS_N_TURN) {
  171. self.dir = Wolf.Math.dir8_north;
  172. } else if (tileinfo & Wolf.TILE_IS_NW_TURN) {
  173. self.dir = Wolf.Math.dir8_northwest;
  174. } else if (tileinfo & Wolf.TILE_IS_W_TURN) {
  175. self.dir = Wolf.Math.dir8_west;
  176. } else if (tileinfo & Wolf.TILE_IS_SW_TURN) {
  177. self.dir = Wolf.Math.dir8_southwest;
  178. } else if (tileinfo & Wolf.TILE_IS_S_TURN) {
  179. self.dir = Wolf.Math.dir8_south;
  180. } else if (tileinfo & Wolf.TILE_IS_SE_TURN) {
  181. self.dir = Wolf.Math.dir8_southeast;
  182. }
  183. }
  184. if (!changeDir(self, self.dir, level)) {
  185. self.dir = Wolf.Math.dir8_nodir;
  186. }
  187. }
  188. /**
  189. * @description Called by entities that ARE NOT chasing the player.
  190. * @private
  191. */
  192. function findTarget(self, game, tics) {
  193. var level = game.level,
  194. player = game.player;
  195. if (self.temp2) { // count down reaction time
  196. self.temp2 -= tics;
  197. if (self.temp2 > 0) {
  198. return false;
  199. }
  200. self.temp2 = 0; // time to react
  201. } else {
  202. // check if we can/want to see/hear player
  203. if (player.flags & Wolf.FL_NOTARGET) {
  204. return false; // notarget cheat
  205. }
  206. // assert( self.areanumber >= 0 && self.areanumber < NUMAREAS );
  207. if (!(self.flags & Wolf.FL_AMBUSH) && ! level.state.areabyplayer[self.areanumber]) {
  208. return false;
  209. }
  210. if (!checkSight(self, game)) { // Player is visible - normal behavior
  211. if (self.flags & Wolf.FL_AMBUSH || !player.madenoise) {
  212. return false;
  213. }
  214. }
  215. self.flags &= ~Wolf.FL_AMBUSH;
  216. // if we are here we see/hear player!!!
  217. switch (self.type) {
  218. case Wolf.en_guard:
  219. self.temp2 = 1 + Wolf.Random.rnd() / 4;
  220. break;
  221. case Wolf.en_officer:
  222. self.temp2 = 2;
  223. break;
  224. case Wolf.en_mutant:
  225. self.temp2 = 1 + Wolf.Random.rnd() / 6;
  226. break;
  227. case Wolf.en_ss:
  228. self.temp2 = 1 + Wolf.Random.rnd() / 6;
  229. break;
  230. case Wolf.en_dog:
  231. self.temp2 = 1 + Wolf.Random.rnd() / 8;
  232. break;
  233. case Wolf.en_boss:
  234. case Wolf.en_schabbs:
  235. case Wolf.en_fake:
  236. case Wolf.en_mecha:
  237. case Wolf.en_hitler:
  238. case Wolf.en_gretel:
  239. case Wolf.en_gift:
  240. case Wolf.en_fat:
  241. case Wolf.en_spectre:
  242. case Wolf.en_angel:
  243. case Wolf.en_trans:
  244. case Wolf.en_uber:
  245. case Wolf.en_will:
  246. case Wolf.en_death:
  247. self.temp2 = 1;
  248. break;
  249. }
  250. return false; // we are amazed & waiting to understand what to do!
  251. }
  252. Wolf.ActorAI.firstSighting(self, game);
  253. return true;
  254. }
  255. /**
  256. * @description As dodge(), but doesn't try to dodge.
  257. * @private
  258. */
  259. function chase(self, game) {
  260. var level = game.level,
  261. player = game.player,
  262. deltax,
  263. deltay,
  264. d = [],
  265. tdir, olddir, turnaround;
  266. if (game.player.playstate == Wolf.ex_victory) {
  267. return;
  268. }
  269. olddir = self.dir;
  270. turnaround = Wolf.Math.opposite8[olddir];
  271. d[0] = d[1] = Wolf.Math.dir8_nodir;
  272. deltax = Wolf.POS2TILE(player.position.x) - Wolf.POS2TILE(self.x);
  273. deltay = Wolf.POS2TILE(player.position.y) - Wolf.POS2TILE(self.y);
  274. if (deltax > 0) {
  275. d[0] = Wolf.Math.dir8_east;
  276. } else if (deltax < 0) {
  277. d[0] = Wolf.Math.dir8_west;
  278. }
  279. if (deltay > 0) {
  280. d[1] = Wolf.Math.dir8_north;
  281. } else if (deltay < 0) {
  282. d[1] = Wolf.Math.dir8_south;
  283. }
  284. if (Math.abs(deltay) > Math.abs(deltax)) {
  285. tdir = d[0];
  286. d[0] = d[1];
  287. d[1] = tdir;
  288. } // swap d[0] & d[1]
  289. if (d[0] == turnaround) {
  290. d[0] = Wolf.Math.dir8_nodir;
  291. }
  292. if (d[1] == turnaround) {
  293. d[1] = Wolf.Math.dir8_nodir;
  294. }
  295. if (d[0] != Wolf.Math.dir8_nodir) {
  296. if (changeDir(self, d[0], level)) {
  297. return;
  298. }
  299. }
  300. if (d[1] != Wolf.Math.dir8_nodir) {
  301. if (changeDir(self, d[1], level)) {
  302. return;
  303. }
  304. }
  305. // there is no direct path to the player, so pick another direction
  306. if (olddir != Wolf.Math.dir8_nodir) {
  307. if (changeDir(self, olddir, level)) {
  308. return;
  309. }
  310. }
  311. if (Wolf.Random.rnd() > 128) { // randomly determine direction of search
  312. for (tdir = Wolf.Math.dir8_east; tdir <= Wolf.Math.dir8_south; tdir += 2) { // * Revision
  313. if (tdir != turnaround) {
  314. if (changeDir(self, tdir, level)) {
  315. return;
  316. }
  317. }
  318. }
  319. } else {
  320. for (tdir = Wolf.Math.dir8_south; tdir >= Wolf.Math.dir8_east; tdir -= 2) { // * Revision (JDC fix for unsigned enums)
  321. if (tdir != turnaround) {
  322. if (changeDir(self, tdir, level)) {
  323. return;
  324. }
  325. }
  326. }
  327. }
  328. if (turnaround != Wolf.Math.dir8_nodir) {
  329. if (changeDir(self, turnaround, level)) {
  330. return;
  331. }
  332. }
  333. self.dir = Wolf.Math.dir8_nodir; // can't move
  334. }
  335. /**
  336. * @description Run Away from player.
  337. * @private
  338. */
  339. function retreat(self, game) {
  340. var level = game.level,
  341. player = game.player,
  342. deltax,
  343. deltay,
  344. d = [],
  345. tdir;
  346. deltax = Wolf.POS2TILE(player.position.x) - Wolf.POS2TILE(self.x);
  347. deltay = Wolf.POS2TILE(player.position.y) - Wolf.POS2TILE(self.y);
  348. d[0] = deltax < 0 ? Wolf.Math.dir8_east : Wolf.Math.dir8_west;
  349. d[1] = deltay < 0 ? Wolf.Math.dir8_north : Wolf.Math.dir8_south;
  350. if (Math.abs(deltay) > Math.abs(deltax)) {
  351. tdir = d[0];
  352. d[0] = d[1];
  353. d[1] = tdir;
  354. } // swap d[0] & d[1]
  355. if (changeDir(self, d[0], level)) {
  356. return;
  357. }
  358. if (changeDir(self, d[1], level)) {
  359. return;
  360. }
  361. // there is no direct path to the player, so pick another direction
  362. if (Wolf.Random.rnd() > 128) { // randomly determine direction of search
  363. for(tdir = Wolf.Math.dir8_east; tdir <= Wolf.Math.dir8_south; tdir += 2 ) { // * Revision
  364. if (changeDir(self, tdir, level)) {
  365. return;
  366. }
  367. }
  368. } else {
  369. for (tdir = Wolf.Math.dir8_south; tdir >= Wolf.Math.dir8_east; tdir -= 2) { // * Revision (JDC fix for unsigned enums)
  370. if (changeDir(self, tdir, level)) {
  371. return;
  372. }
  373. }
  374. }
  375. self.dir = Wolf.Math.dir8_nodir; // can't move
  376. }
  377. /**
  378. * @description Attempts to choose and initiate a movement for entity
  379. * that sends it towards the player while dodging.
  380. * @private
  381. */
  382. function dodge(self, game) {
  383. var level = game.level,
  384. player = game.player,
  385. deltax,
  386. deltay,
  387. i,
  388. dirtry = [],
  389. turnaround,
  390. tdir;
  391. if (game.player.playstate == Wolf.ex_victory) {
  392. return;
  393. }
  394. if (self.flags & Wolf.FL_FIRSTATTACK) {
  395. // turning around is only ok the very first time after noticing the player
  396. turnaround = Wolf.Math.dir8_nodir;
  397. self.flags &= ~Wolf.FL_FIRSTATTACK;
  398. } else {
  399. turnaround = Wolf.Math.opposite8[self.dir];
  400. }
  401. deltax = Wolf.POS2TILE(player.position.x) - Wolf.POS2TILE(self.x);
  402. deltay = Wolf.POS2TILE(player.position.y) - Wolf.POS2TILE(self.y);
  403. //
  404. // arange 5 direction choices in order of preference
  405. // the four cardinal directions plus the diagonal straight towards
  406. // the player
  407. //
  408. if (deltax > 0) {
  409. dirtry[1] = Wolf.Math.dir8_east;
  410. dirtry[3] = Wolf.Math.dir8_west;
  411. } else {
  412. dirtry[1] = Wolf.Math.dir8_west;
  413. dirtry[3] = Wolf.Math.dir8_east;
  414. }
  415. if( deltay > 0 ) {
  416. dirtry[2] = Wolf.Math.dir8_north;
  417. dirtry[4] = Wolf.Math.dir8_south;
  418. } else {
  419. dirtry[2] = Wolf.Math.dir8_south;
  420. dirtry[4] = Wolf.Math.dir8_north;
  421. }
  422. // randomize a bit for dodging
  423. if (Math.abs(deltax) > Math.abs(deltay)) {
  424. tdir = dirtry[1]; dirtry[1]=dirtry[2]; dirtry[2]=tdir; // => swap dirtry[1] & dirtry[2]
  425. tdir = dirtry[3]; dirtry[3]=dirtry[4]; dirtry[4]=tdir; // => swap dirtry[3] & dirtry[4]
  426. }
  427. if (Wolf.Random.rnd() < 128) {
  428. tdir = dirtry[1]; dirtry[1]=dirtry[2]; dirtry[2]=tdir;
  429. tdir = dirtry[3]; dirtry[3]=dirtry[4]; dirtry[4]=tdir;
  430. }
  431. dirtry[0] = Wolf.Math.diagonal[dirtry[1]][dirtry[2]];
  432. // try the directions util one works
  433. for (i=0; i < 5; ++i) {
  434. if (dirtry[i] == Wolf.Math.dir8_nodir || dirtry[i] == turnaround) {
  435. continue;
  436. }
  437. if (changeDir(self, dirtry[i], level)) {
  438. return;
  439. }
  440. }
  441. // turn around only as a last resort
  442. if (turnaround != Wolf.Math.dir8_nodir) {
  443. if (changeDir(self, turnaround, level)) {
  444. return;
  445. }
  446. }
  447. self.dir = Wolf.Math.dir8_nodir;
  448. }
  449. /**
  450. * @memberOf Wolf.AI
  451. */
  452. function T_Stand(self, game, tics) {
  453. findTarget(self, game, tics);
  454. }
  455. /**
  456. * @memberOf Wolf.AI
  457. */
  458. function T_Path(self, game, tics) {
  459. var level = game.level;
  460. if (findTarget(self, game, tics)) {
  461. return;
  462. }
  463. if (!self.speed) {
  464. return; // if patroling with a speed of 0
  465. }
  466. if (self.dir == Wolf.Math.dir8_nodir) {
  467. path(self, game);
  468. if (self.dir == Wolf.Math.dir8_nodir) {
  469. return; // all movement is blocked
  470. }
  471. }
  472. T_Advance(self, game, path, tics);
  473. }
  474. /**
  475. * @description Try to damage the player.
  476. * @memberOf Wolf.AI
  477. */
  478. function T_Shoot(self, game, tics) {
  479. var level = game.level,
  480. player = game.player,
  481. dx, dy, dist,
  482. hitchance,
  483. damage;
  484. if (!level.state.areabyplayer[self.areanumber]) {
  485. return;
  486. }
  487. if (!Wolf.Level.checkLine(self.x, self.y, player.position.x, player.position.y, level)) {
  488. return; // player is behind a wall
  489. }
  490. dx = Math.abs(Wolf.POS2TILE(self.x ) - Wolf.POS2TILE(player.position.x));
  491. dy = Math.abs(Wolf.POS2TILE(self.y ) - Wolf.POS2TILE(player.position.y));
  492. dist = Math.max(dx, dy);
  493. if (self.type == Wolf.en_ss || self.type == Wolf.en_boss )
  494. {
  495. dist = dist * 2 / 3; // ss are better shots
  496. }
  497. if (player.speed >= Wolf.RUNSPEED) {
  498. hitchance = 160;
  499. } else {
  500. hitchance = 256;
  501. }
  502. // if guard is visible by player
  503. // player can see to dodge
  504. // (if CheckLine both player & enemy see each other)
  505. // So left only check if guard is in player's fov: FIXME: not fixed fov!
  506. var trans = Wolf.Math.transformPoint(self.x, self.y, player.position.x, player.position.y);
  507. if (Wolf.Angle.diff(trans, Wolf.FINE2DEG(player.angle)) < (Math.PI/3)) {
  508. hitchance -= dist * 16;
  509. } else {
  510. hitchance -= dist * 8;
  511. }
  512. // see if the shot was a hit
  513. if (Wolf.Random.rnd() < hitchance) {
  514. if (dist < 2) {
  515. damage = Wolf.Random.rnd() >> 2;
  516. } else if (dist < 4) {
  517. damage = Wolf.Random.rnd() >> 3;
  518. } else {
  519. damage = Wolf.Random.rnd() >> 4;
  520. }
  521. Wolf.Player.damage(player, self, damage);
  522. }
  523. switch (self.type) {
  524. case Wolf.en_ss:
  525. Wolf.Sound.startSound(player.position, self, 1, Wolf.CHAN_WEAPON, "sfx/024.wav", 1, Wolf.ATTN_NORM, 0);
  526. break;
  527. case Wolf.en_gift:
  528. case Wolf.en_fat:
  529. case Wolf.en_mecha:
  530. case Wolf.en_hitler:
  531. case Wolf.en_boss:
  532. Wolf.Sound.startSound(player.position, self, 1, Wolf.CHAN_WEAPON, "sfx/022.wav", 1, Wolf.ATTN_NORM, 0);
  533. break;
  534. default:
  535. Wolf.Sound.startSound(player.position, self, 1, Wolf.CHAN_WEAPON, "sfx/049.wav", 1, Wolf.ATTN_NORM, 0);
  536. break;
  537. }
  538. }
  539. /**
  540. * @description
  541. * @memberOf Wolf.AI
  542. */
  543. function T_Chase(self, game, tics) {
  544. var level = game.level,
  545. player = game.player,
  546. dx, dy,
  547. dist,
  548. chance,
  549. shouldDodge = false;
  550. // if (gamestate.victoryflag) return;
  551. if (Wolf.Level.checkLine(self.x, self.y, player.position.x, player.position.y, level)) { // got a shot at player?
  552. dx = Math.abs(Wolf.POS2TILE(self.x) - Wolf.POS2TILE(player.position.x));
  553. dy = Math.abs(Wolf.POS2TILE(self.y) - Wolf.POS2TILE(player.position.y));
  554. dist = Math.max(dx, dy);
  555. if (!dist || (dist == 1 && self.distance < 16)) {
  556. chance = 300;
  557. } else {
  558. chance = (tics << 4) / dist; // 100/dist;
  559. }
  560. if (Wolf.Random.rnd() < chance) {
  561. // go into attack frame
  562. Wolf.Actors.stateChange(self, Wolf.st_shoot1);
  563. return;
  564. }
  565. shouldDodge = true;
  566. }
  567. if (self.dir == Wolf.Math.dir8_nodir) {
  568. if (shouldDodge) {
  569. dodge(self, game);
  570. } else {
  571. chase(self, game);
  572. }
  573. if (self.dir == Wolf.Math.dir8_nodir) {
  574. return; // object is blocked in
  575. }
  576. self.angle = Wolf.Math.dir8angle[self.dir];
  577. }
  578. T_Advance(self, game, shouldDodge ? dodge : chase, tics);
  579. }
  580. /**
  581. * @description
  582. * @memberOf Wolf.AI
  583. */
  584. function T_DogChase(self, game, tics) {
  585. var level = game.level,
  586. player = game.player,
  587. dx, dy;
  588. if (self.dir == Wolf.Math.dir8_nodir) {
  589. dodge(self, game);
  590. self.angle = Wolf.Math.dir8angle[ self.dir ];
  591. if (self.dir == Wolf.Math.dir8_nodir) {
  592. return; // object is blocked in
  593. }
  594. }
  595. //
  596. // check for bite range
  597. //
  598. dx = Math.abs(player.position.x - self.x) - Wolf.TILEGLOBAL / 2;
  599. if (dx <= Wolf.MINACTORDIST) {
  600. dy = Math.abs(player.position.y - self.y) - Wolf.TILEGLOBAL / 2;
  601. if (dy <= Wolf.MINACTORDIST) {
  602. Wolf.Actors.stateChange(self, Wolf.st_shoot1);
  603. return; // bite player!
  604. }
  605. }
  606. T_Advance(self, game, dodge, tics);
  607. }
  608. /**
  609. * @description Try to damage the player.
  610. * @memberOf Wolf.AI
  611. */
  612. function T_BossChase(self, game, tics) {
  613. var level = game.level,
  614. player = game.player,
  615. dx, dy, dist,
  616. think,
  617. shouldDodge = false;
  618. dx = Math.abs(self.tile.x - Wolf.POS2TILE(player.position.x));
  619. dy = Math.abs(self.tile.y - Wolf.POS2TILE(player.position.y));
  620. dist = Math.max(dx, dy);
  621. if (Wolf.Level.checkLine(self.x, self.y, player.position.x, player.position.y, level)) {
  622. // got a shot at player?
  623. if (Wolf.Random.rnd() < tics << 3) {
  624. // go into attack frame
  625. Wolf.Actors.stateChange(self, Wolf.st_shoot1);
  626. return;
  627. }
  628. shouldDodge = true;
  629. }
  630. if( self.dir == Wolf.Math.dir8_nodir ) {
  631. if (shouldDodge) {
  632. dodge(self, game);
  633. } else {
  634. chase(self, game);
  635. }
  636. if( self.dir == Wolf.Math.dir8_nodir ) {
  637. // object is blocked in
  638. return;
  639. }
  640. }
  641. think = dist < 4 ? retreat : (shouldDodge ? dodge : chase);
  642. T_Advance(self, game, think, tics);
  643. }
  644. /**
  645. * @description
  646. * @memberOf Wolf.AI
  647. */
  648. function T_Fake(self, game, tics) {
  649. var level = game.level,
  650. player = game.player;
  651. if (Wolf.Level.checkLine(self.x, self.y, player.position.x, player.position.y, level)) {
  652. if (Wolf.Random.rnd() < tics << 1) {
  653. // go into attack frame
  654. Wolf.Actors.stateChange(self, Wolf.st_shoot1);
  655. return;
  656. }
  657. }
  658. if (self.dir == Wolf.Math.dir8_nodir) {
  659. dodge(self, game);
  660. if (self.dir == Wolf.Math.dir8_nodir ) {
  661. // object is blocked in
  662. return;
  663. }
  664. }
  665. T_Advance(self, game, dodge, tics);
  666. }
  667. /**
  668. * @description
  669. * @private
  670. */
  671. function T_Advance(self, game, think, tics) {
  672. var level = game.level,
  673. move, door;
  674. if (!think) {
  675. Wolf.log("Warning: Advance without <think> proc\n");
  676. return;
  677. }
  678. move = self.speed * tics;
  679. while (move > 0) {
  680. // waiting for a door to open
  681. if (self.waitfordoorx) {
  682. door = level.state.doorMap[self.waitfordoorx][self.waitfordoory];
  683. Wolf.Doors.open(door);
  684. if (door.action != Wolf.dr_open) {
  685. return; // not opened yet...
  686. }
  687. self.waitfordoorx = self.waitfordoory = 0; // go ahead, the door is now open
  688. }
  689. if (move < self.distance ) {
  690. T_Move(self, game, move);
  691. break;
  692. }
  693. // fix position to account for round off during moving
  694. self.x = Wolf.TILE2POS(self.tile.x);
  695. self.y = Wolf.TILE2POS(self.tile.y);
  696. move -= self.distance;
  697. // think: Where to go now?
  698. think(self, game, tics);
  699. self.angle = Wolf.Math.dir8angle[self.dir];
  700. if (self.dir == Wolf.Math.dir8_nodir) {
  701. return; // all movement is blocked
  702. }
  703. }
  704. }
  705. /**
  706. * @description Moves object for distance in global units, in self.dir direction.
  707. * @memberOf Wolf.AI
  708. */
  709. function T_Move(self, game, dist) {
  710. var level = game.level,
  711. player = game.player;
  712. if (self.dir == Wolf.Math.dir8_nodir || !dist) {
  713. return;
  714. }
  715. self.x += dist * Wolf.Math.dx8dir[self.dir];
  716. self.y += dist * Wolf.Math.dy8dir[self.dir];
  717. // check to make sure it's not on top of player
  718. if (Math.abs(self.x - player.position.x) <= Wolf.MINACTORDIST) {
  719. if (Math.abs(self.y - player.position.y) <= Wolf.MINACTORDIST) {
  720. var t = self.type;
  721. if (t == Wolf.en_blinky || t == Wolf.en_clyde || t == Wolf.en_pinky || t == Wolf.en_inky || t == Wolf.en_spectre) {
  722. Wolf.Player.damage(player, self, 2); // ghosts hurt player!
  723. }
  724. //
  725. // back up
  726. //
  727. self.x -= dist * Wolf.Math.dx8dir[self.dir];
  728. self.y -= dist * Wolf.Math.dy8dir[self.dir];
  729. return;
  730. }
  731. }
  732. self.distance -= dist;
  733. if (self.distance < 0) {
  734. self.distance = 0;
  735. }
  736. }
  737. /**
  738. * @description
  739. * @memberOf Wolf.AI
  740. */
  741. function T_Ghosts(self, game, tics) {
  742. var level = game.level,
  743. player = game.player;
  744. if (self.dir == Wolf.Math.dir8_nodir) {
  745. chase(self, game);
  746. if (self.dir == Wolf.Math.dir8_nodir ) {
  747. return; // object is blocked in
  748. }
  749. self.angle = Wolf.Math.dir8angle[self.dir];
  750. }
  751. T_Advance(self, game, chase, tics);
  752. }
  753. /**
  754. * @description
  755. * @memberOf Wolf.AI
  756. */
  757. function T_Bite(self, game, tics) {
  758. var level = game.level,
  759. player = game.player,
  760. dx, dy;
  761. Wolf.Sound.startSound(player.position, self, 1, Wolf.CHAN_VOICE, "sfx/002.wav", 1, Wolf.ATTN_NORM, 0);
  762. dx = Math.abs(player.position.x - self.x) - Wolf.TILEGLOBAL;
  763. if (dx <= Wolf.MINACTORDIST) {
  764. dy = Math.abs(player.position.y - self.y) - Wolf.TILEGLOBAL;
  765. if (dy <= Wolf.MINACTORDIST) {
  766. if (Wolf.Random.rnd() < 180) {
  767. Wolf.Player.damage(player, self, Wolf.Random.rnd() >> 4);
  768. return;
  769. }
  770. }
  771. }
  772. }
  773. /**
  774. * @description
  775. * @memberOf Wolf.AI
  776. */
  777. function T_UShoot(self, game, tics) {
  778. var level = game.level,
  779. player = game.player,
  780. dx, dy,
  781. dist;
  782. T_Shoot(self, game, tics);
  783. dx = Math.abs(self.tile.x - Wolf.POS2TILE(player.position.x));
  784. dy = Math.abs(self.tile.y - Wolf.POS2TILE(player.position.y));
  785. dist = Math.max(dx, dy);
  786. if (dist <= 1) {
  787. Wolf.Player.damage(player, self, 10);
  788. }
  789. }
  790. /**
  791. * @description
  792. * @memberOf Wolf.AI
  793. */
  794. function T_Launch(self, game, tics) {
  795. var level = game.level,
  796. player = game.player,
  797. proj, iangle;
  798. iangle = Wolf.Math.transformPoint(self.x, self.y, player.position.x, player.position.y) + Math.PI;
  799. if (iangle > 2 * Math.PI) {
  800. iangle -= 2 * Math.PI;
  801. }
  802. if (self.type == Wolf.en_death) {
  803. // death knight launches 2 rockets with 4 degree shift each.
  804. T_Shoot(self, game, tics);
  805. if (self.state == Wolf.st_shoot2) {
  806. iangle = Wolf.Math.normalizeAngle(iangle - Wolf.DEG2RAD(4));
  807. } else {
  808. iangle = Wolf.Math.normalizeAngle(iangle + Wolf.DEG2RAD(4));
  809. }
  810. }
  811. proj = Wolf.Actors.getNewActor(level);
  812. if (proj == null) {
  813. return;
  814. }
  815. proj.x = self.x;
  816. proj.y = self.y;
  817. proj.tile.x = self.tile.x;
  818. proj.tile.y = self.tile.y;
  819. proj.state = Wolf.st_stand;
  820. proj.ticcount = 1;
  821. proj.dir = Wolf.Math.dir8_nodir;
  822. proj.angle = Wolf.RAD2FINE(iangle)>>0;
  823. proj.speed = 0x2000;
  824. proj.flags = Wolf.FL_NONMARK; // FL_NEVERMARK;
  825. proj.sprite = Wolf.Sprites.getNewSprite(level);
  826. switch(self.type) {
  827. case Wolf.en_death:
  828. proj.type = Wolf.en_hrocket;
  829. Wolf.Sound.startSound(player.position, self, 1, Wolf.CHAN_WEAPON, "lsfx/078.wav", 1, Wolf.ATTN_NORM, 0);
  830. break;
  831. case Wolf.en_angel:
  832. proj.type = Wolf.en_spark;
  833. proj.state = Wolf.st_path1;
  834. Wolf.Sound.startSound(player.position, self, 1, Wolf.CHAN_WEAPON, "lsfx/069.wav", 1, Wolf.ATTN_NORM, 0);
  835. break;
  836. case Wolf.en_fake:
  837. proj.type = Wolf.en_fire;
  838. proj.state = Wolf.st_path1;
  839. proj.flags = Wolf.FL_NEVERMARK;
  840. proj.speed = 0x1200;
  841. Wolf.Sound.startSound(player.position, self, 1, Wolf.CHAN_WEAPON, "lsfx/069.wav", 1, Wolf.ATTN_NORM, 0);
  842. break;
  843. case Wolf.en_schabbs:
  844. proj.type = Wolf.en_needle;
  845. proj.state = Wolf.st_path1;
  846. Wolf.Sound.startSound(player.position, self, 1, Wolf.CHAN_WEAPON, "lsfx/008.wav", 1, Wolf.ATTN_NORM, 0);
  847. break;
  848. default:
  849. proj.type = Wolf.en_rocket;
  850. Wolf.Sound.startSound(player.position, self, 1, Wolf.CHAN_WEAPON, "lsfx/085.wav", 1, Wolf.ATTN_NORM, 0);
  851. }
  852. }
  853. /**
  854. * @description Called when projectile is airborne.
  855. * @private
  856. * @param {object} self The projectile actor object.
  857. * @param {object} level The level object.
  858. * @returns {boolean} True if move ok, otherwise false.
  859. */
  860. function projectileTryMove(self, level) {
  861. var PROJSIZE = 0x2000,
  862. xl, yl, xh, yh, x, y;
  863. xl = (self.x - PROJSIZE) >> Wolf.TILESHIFT;
  864. yl = (self.y - PROJSIZE) >> Wolf.TILESHIFT;
  865. xh = (self.x + PROJSIZE) >> Wolf.TILESHIFT;
  866. yh = (self.y + PROJSIZE) >> Wolf.TILESHIFT;
  867. // Checking for solid walls:
  868. for (y = yl; y <= yh; ++y) {
  869. for (x = xl; x <= xh; ++x) {
  870. // FIXME: decide what to do with statics & Doors!
  871. if (level.tileMap[x][y] & (Wolf.WALL_TILE | Wolf.BLOCK_TILE)) {
  872. return false;
  873. }
  874. if (level.tileMap[x][y] & Wolf.DOOR_TILE) {
  875. if (Wolf.Doors.opened(level.state.doorMap[x][y]) != Wolf.DOOR_FULLOPEN) {
  876. return false;
  877. }
  878. }
  879. }
  880. }
  881. // FIXME: Projectile will fly through objects (even guards & columns) - must fix to create rocket launcher!
  882. return true;
  883. }
  884. /**
  885. * @description Called when projectile is airborne.
  886. * @memberOf Wolf.AI
  887. * @param {object} self The enemy actor object.
  888. * @param {object} level The level object.
  889. * @param {object} player The player object.
  890. * @param {number} tics The number of tics.
  891. * @returns {boolean} True if move ok, otherwise false.
  892. */
  893. function T_Projectile(self, game, tics) {
  894. var level = game.level,
  895. player = game.player,
  896. PROJECTILESIZE = 0xC000,
  897. deltax, deltay,
  898. speed, damage;
  899. speed = self.speed * tics;
  900. deltax = (speed * Wolf.Math.CosTable[self.angle])>>0;
  901. deltay = (speed * Wolf.Math.SinTable[self.angle])>>0;
  902. if (deltax > Wolf.TILEGLOBAL) {
  903. deltax = Wolf.TILEGLOBAL;
  904. }
  905. if (deltax < -Wolf.TILEGLOBAL) {
  906. deltax = -Wolf.TILEGLOBAL; // my
  907. }
  908. if (deltay > Wolf.TILEGLOBAL) {
  909. deltay = Wolf.TILEGLOBAL;
  910. }
  911. if (deltay < -Wolf.TILEGLOBAL) {
  912. deltay = -Wolf.TILEGLOBAL; // my
  913. }
  914. self.x += deltax;
  915. self.y += deltay;
  916. deltax = Math.abs(self.x - player.position.x);
  917. deltay = Math.abs(self.y - player.position.y);
  918. if (!projectileTryMove(self, level)) {
  919. if (self.type == Wolf.en_rocket || self.type == Wolf.en_hrocket ) {
  920. // rocket ran into obstacle, draw explosion!
  921. Wolf.Sound.startSound(player.position, self, 1, Wolf.CHAN_WEAPON, "lsfx/086.wav", 1, Wolf.ATTN_NORM, 0);
  922. Wolf.Actors.stateChange(self, Wolf.st_die1);
  923. } else {
  924. Wolf.Actors.stateChange(self, Wolf.st_remove); // mark for removal
  925. }
  926. return;
  927. }
  928. if (deltax < PROJECTILESIZE && deltay < PROJECTILESIZE) {
  929. // hit the player
  930. switch (self.type) {
  931. case Wolf.en_needle:
  932. damage = (Wolf.Random.rnd() >> 3) + 20;
  933. break;
  934. case Wolf.en_rocket:
  935. case Wolf.en_hrocket:
  936. case Wolf.en_spark:
  937. damage = (Wolf.Random.rnd()>>3) + 30;
  938. break;
  939. case Wolf.en_fire:
  940. damage = (Wolf.Random.rnd() >> 3);
  941. break;
  942. default:
  943. damage = 0;
  944. break;
  945. }
  946. Wolf.Player.damage(player, self, damage);
  947. Wolf.Actors.stateChange(self, Wolf.st_remove); // mark for removal
  948. return;
  949. }
  950. self.tile.x = self.x >> Wolf.TILESHIFT;
  951. self.tile.y = self.y >> Wolf.TILESHIFT;
  952. }
  953. /**
  954. * @description
  955. * @memberOf Wolf.AI
  956. */
  957. function T_BJRun(self, game, tics) {
  958. var move = Wolf.BJRUNSPEED * tics;
  959. T_Move(self, game, move);
  960. if (!self.distance) {
  961. self.distance = Wolf.TILEGLOBAL;
  962. if (!(--self.temp2)) {
  963. Wolf.Actors.stateChange(self, Wolf.st_shoot1);
  964. self.speed = Wolf.BJJUMPSPEED;
  965. return;
  966. }
  967. }
  968. }
  969. /**
  970. * @description
  971. * @memberOf Wolf.AI
  972. */
  973. function T_BJJump(self, game, tics) {
  974. //var move = Wolf.BJRUNSPEED * tics;
  975. //T_Move(self, game, move);
  976. }
  977. /**
  978. * @description
  979. * @memberOf Wolf.AI
  980. */
  981. function T_BJYell(self, game, tics) {
  982. Wolf.Sound.startSound(null, null, 0, Wolf.CHAN_VOICE, "sfx/082.wav", 1, Wolf.ATTN_NORM, 0);
  983. }
  984. /**
  985. * @description
  986. * @memberOf Wolf.AI
  987. */
  988. function T_BJDone(self, game, tics) {
  989. Wolf.Player.playstate = Wolf.ex_victory; // exit castle tile
  990. //Wolf.Player.playstate = Wolf.ex_complete;
  991. Wolf.Game.endEpisode(game);
  992. }
  993. return {
  994. T_Stand : T_Stand,
  995. T_Path : T_Path,
  996. T_Ghosts : T_Ghosts,
  997. T_Bite : T_Bite,
  998. T_Shoot : T_Shoot,
  999. T_UShoot : T_UShoot,
  1000. T_Launch : T_Launch,
  1001. T_Chase : T_Chase,
  1002. T_DogChase : T_DogChase,
  1003. T_BossChase : T_BossChase,
  1004. T_Fake : T_Fake,
  1005. T_Projectile : T_Projectile,
  1006. T_BJRun : T_BJRun,
  1007. T_BJJump : T_BJJump,
  1008. T_BJYell : T_BJYell,
  1009. T_BJDone : T_BJDone
  1010. };
  1011. })();