Entity.java 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666
  1. package me.hammerle.supersnuvi.entity;
  2. import java.util.ArrayList;
  3. import java.util.List;
  4. import me.hammerle.supersnuvi.entity.components.ai.Controller;
  5. import me.hammerle.supersnuvi.entity.components.Energy;
  6. import me.hammerle.supersnuvi.entity.components.Health;
  7. import me.hammerle.supersnuvi.entity.components.ItemCollector;
  8. import me.hammerle.supersnuvi.entity.components.Movement;
  9. import me.hammerle.supersnuvi.tiles.Tile;
  10. import me.hammerle.supersnuvi.util.Face;
  11. import me.hammerle.supersnuvi.util.Utils;
  12. import me.hammerle.supersnuvi.gamelogic.Level;
  13. import me.hammerle.supersnuvi.math.Vector;
  14. import me.hammerle.supersnuvi.entity.components.IDespawn;
  15. public final class Entity
  16. {
  17. public static final float GRAVITY = 8.0f * Tile.SIZE_SCALE;
  18. public static final float STEP = 0.0625f;
  19. // this one is a little bit bigger to prevent wrong calculation
  20. // while going upwards
  21. public static final float UP_STEP = STEP + 0.00390625f;
  22. // the last position is used for interpolation during rendering
  23. private final Vector lastPos = new Vector();
  24. // the current position of the entity
  25. private final Vector pos = new Vector();
  26. // the width of the entity
  27. private final float width;
  28. // the height of the entity
  29. private final float height;
  30. // own force used by the controller component
  31. private final Vector ownForce = new Vector();
  32. // the final motion of the last tick
  33. private final Vector lastMotion = new Vector();
  34. // the motion, reduced by the movement collision check
  35. private final Vector motion = new Vector();
  36. // the friction reducing motion each tick
  37. private final Vector friction = new Vector(0.0f, 1.0f);
  38. // a flag indicating that the entity is on the ground
  39. private boolean onGround = true;
  40. // entity components
  41. private final Controller controller;
  42. private final Health health;
  43. private final Energy energy;
  44. private final Movement move;
  45. private final ItemCollector itemCollector;
  46. private final IDespawn onDespawn;
  47. // the type of the entity, used by snuvi script
  48. private final String type;
  49. protected Entity(float width, float height, Controller c, Health h, Energy e, Movement m, ItemCollector ic, IDespawn d, String type)
  50. {
  51. this.width = width;
  52. this.height = height;
  53. this.controller = c;
  54. this.health = h;
  55. this.energy = e;
  56. this.move = m;
  57. this.itemCollector = ic;
  58. this.onDespawn = d;
  59. this.type = type;
  60. }
  61. public String getType()
  62. {
  63. return type;
  64. }
  65. //--------------------------------------------------------------------------
  66. // components
  67. //--------------------------------------------------------------------------
  68. public boolean isAnimated()
  69. {
  70. return controller.isAnimated();
  71. }
  72. public Controller getController()
  73. {
  74. return controller;
  75. }
  76. public Health getHealth()
  77. {
  78. return health;
  79. }
  80. public Energy getEnergy()
  81. {
  82. return energy;
  83. }
  84. public Movement getMovement()
  85. {
  86. return move;
  87. }
  88. public ItemCollector getItemCollector()
  89. {
  90. return itemCollector;
  91. }
  92. public void onDespawn()
  93. {
  94. onDespawn.onDespawn(this);
  95. }
  96. //--------------------------------------------------------------------------
  97. // basic stuff
  98. //--------------------------------------------------------------------------
  99. public float getSquaredDistance(Entity e)
  100. {
  101. return Utils.getSquaredDistance(getCenterX(), getCenterY(), e.getCenterX(), e.getCenterY());
  102. }
  103. public float getX()
  104. {
  105. return pos.getX();
  106. }
  107. public float getY()
  108. {
  109. return pos.getY();
  110. }
  111. public void setPosition(float x, float y)
  112. {
  113. lastPos.set(x, y);
  114. pos.set(x, y);
  115. }
  116. public float getLastX()
  117. {
  118. return lastPos.getX();
  119. }
  120. public float getLastY()
  121. {
  122. return lastPos.getY();
  123. }
  124. public float getCenterX()
  125. {
  126. return pos.getX() + width * 0.5f;
  127. }
  128. public float getCenterY()
  129. {
  130. return pos.getY() + height * 0.5f;
  131. }
  132. public float getWidth()
  133. {
  134. return width;
  135. }
  136. public float getHeight()
  137. {
  138. return height;
  139. }
  140. public float getMotionX()
  141. {
  142. return motion.getX();
  143. }
  144. public float getMotionY()
  145. {
  146. return motion.getY();
  147. }
  148. public float getOwnForceX()
  149. {
  150. return ownForce.getX();
  151. }
  152. public float getOwnForceY()
  153. {
  154. return ownForce.getY();
  155. }
  156. public void applyOwnForce(float x, float y)
  157. {
  158. ownForce.add(x, y);
  159. }
  160. public void applyForce(float x, float y)
  161. {
  162. motion.add(x, y);
  163. }
  164. public void setFriction(float fx, float fy)
  165. {
  166. friction.set(fx, fy);
  167. }
  168. public boolean isAt(float x, float y)
  169. {
  170. return Math.abs(x - pos.getX()) < STEP && Math.abs(y - pos.getY()) < STEP;
  171. }
  172. public boolean isColliding(float minX, float minY, float maxX, float maxY)
  173. {
  174. return maxX > getX() && getX() + width > minX && maxY > getY() && getY() + height > minY;
  175. }
  176. public boolean isColliding(Entity ent)
  177. {
  178. return isColliding(ent.getX(), ent.getY(), ent.getX() + ent.width, ent.getY() + ent.height);
  179. }
  180. private Face getCollidingFace(Entity o)
  181. {
  182. return Utils.getCollidingFace(
  183. o.getX(), o.getY(), o.getX() + o.getWidth(), o.getY() + o.getHeight(),
  184. getX(), getY(), getX() + getWidth(), getY() + getHeight());
  185. }
  186. public boolean jump()
  187. {
  188. if(onGround)
  189. {
  190. ownForce.addY(-move.getJumpPower());
  191. return true;
  192. }
  193. return false;
  194. }
  195. //--------------------------------------------------------------------------
  196. // ticking
  197. //--------------------------------------------------------------------------
  198. public void tick(Level level)
  199. {
  200. // last motion is current motion, last motion will be set in move
  201. motion.set(lastMotion);
  202. lastMotion.set(0.0f, 0.0f);
  203. // reset own force
  204. ownForce.set(0.0f, 0.0f);
  205. // remeber last position for rendering
  206. lastPos.set(pos);
  207. // update onGround before controller tick
  208. onGround = isOnTileGround(level);
  209. if(onGround)
  210. {
  211. motion.setY(0.0f);
  212. }
  213. // tick components
  214. controller.tick(this, level);
  215. energy.tick(this);
  216. health.tick();
  217. // apply gravity if needed
  218. if(move.hasGravity(this))
  219. {
  220. motion.addY(GRAVITY * move.getGravityFactor());
  221. }
  222. // apply friction
  223. motion.mul(friction);
  224. // modify current motion by own force
  225. motion.add(ownForce);
  226. // (entity <-> tile) collision
  227. doTileCollision(level);
  228. // (entity <-> entity) collision
  229. level.forEachCollidingEntity(this, (ent) ->
  230. {
  231. controller.onCollideWithEntity(this, ent, getCollidingFace(ent));
  232. });
  233. }
  234. public int move(Level level)
  235. {
  236. if(motion.getX() == 0.0f && motion.getY() == 0.0f)
  237. {
  238. return 0;
  239. }
  240. Vector maxMotion = new Vector();
  241. maxMotion.set(motion);
  242. reduceMotionByTiles(level);
  243. reduceMotionByEntities(level);
  244. pos.add(motion);
  245. int r = (motion.getX() == 0.0f && motion.getY() == 0.0f) ? 0 : 1;
  246. lastMotion.add(motion);
  247. maxMotion.sub(motion);
  248. motion.set(maxMotion);
  249. return r;
  250. }
  251. private boolean isOnTileGround(Level level)
  252. {
  253. float minX = pos.getX();
  254. float minY = pos.getY() + height;
  255. float maxX = pos.getX() + width;
  256. float maxY = pos.getY() + height + STEP * 2;
  257. int startX = Utils.toBlock(minX);
  258. int startY = Utils.toBlock(minY);
  259. int endX = Utils.toBlock(maxX);
  260. int endY = Utils.toBlock(maxY);
  261. for(int x = startX; x <= endX; x++)
  262. {
  263. for(int y = startY; y <= endY; y++)
  264. {
  265. Tile t = level.getInteractionTile(x, y);
  266. if(t.isMoveColliding(minX, minY, maxX, maxY, x, y, level))
  267. {
  268. return true;
  269. }
  270. }
  271. }
  272. for(Entity ent : level.getEntities())
  273. {
  274. if(ent == this)
  275. {
  276. continue;
  277. }
  278. if(ent.getMovement().isSolid() && ent.isColliding(minX, minY, maxX, maxY))
  279. {
  280. return true;
  281. }
  282. }
  283. return false;
  284. }
  285. private void doTileCollision(Level level)
  286. {
  287. float minX = pos.getX() - STEP;
  288. float minY = pos.getY() - STEP;
  289. float maxX = pos.getX() + width + STEP;
  290. float maxY = pos.getY() + height + STEP;
  291. int startX = Utils.toBlock(minX);
  292. int startY = Utils.toBlock(minY);
  293. int endX = Utils.toBlock(maxX);
  294. int endY = Utils.toBlock(maxY);
  295. for(int x = startX; x <= endX; x++)
  296. {
  297. for(int y = startY; y <= endY; y++)
  298. {
  299. Tile t = level.getInteractionTile(x, y);
  300. if(t.isColliding(minX, minY, maxX, maxY, x, y, level))
  301. {
  302. Face f = t.getCollidingFace(minX, minY, maxX, maxY, x, y, level);
  303. controller.onCollideWithTile(this, x, y, level, t, f);
  304. t.onEntityCollide(this, x, y, f.getOpposite(), level);
  305. }
  306. }
  307. }
  308. }
  309. private void reduceMotionByEntities(Level level)
  310. {
  311. if(motion.getX() == 0.0f && motion.getY() == 0.0f)
  312. {
  313. return;
  314. }
  315. float mx = motion.getX();
  316. float my = motion.getY();
  317. float oldPosX = pos.getX();
  318. float oldPosY = pos.getY();
  319. while(mx != 0.0 || my != 0.0)
  320. {
  321. if(mx != 0.0f)
  322. {
  323. float oldX = pos.getX();
  324. if(mx < 0.0)
  325. {
  326. if(mx > -STEP)
  327. {
  328. pos.addX(mx);
  329. mx = 0.0f;
  330. }
  331. else
  332. {
  333. pos.addX(-STEP);
  334. mx += STEP;
  335. }
  336. }
  337. else if(mx > 0.0)
  338. {
  339. if(mx < STEP)
  340. {
  341. pos.addX(mx);
  342. mx = 0.0f;
  343. }
  344. else
  345. {
  346. pos.addX(STEP);
  347. mx -= STEP;
  348. }
  349. }
  350. List<Entity> list = isCollidingWithEntities(level);
  351. for(Entity ent : list)
  352. {
  353. if(move.isSolid() || ent.move.isSolid())
  354. {
  355. //controller.onCollideWithEntity(this, ent, getCollidingFace(ent));
  356. pos.setX(oldX);
  357. mx = 0.0f;
  358. }
  359. }
  360. }
  361. if(my != 0.0f)
  362. {
  363. float oldY = pos.getY();
  364. if(my < 0.0)
  365. {
  366. if(my > -STEP)
  367. {
  368. pos.addY(my);
  369. my = 0.0f;
  370. }
  371. else
  372. {
  373. pos.addY(-STEP);
  374. my += STEP;
  375. }
  376. }
  377. else if(my > 0.0)
  378. {
  379. if(my < STEP)
  380. {
  381. pos.addY(my);
  382. my = 0.0f;
  383. }
  384. else
  385. {
  386. pos.addY(STEP);
  387. my -= STEP;
  388. }
  389. }
  390. List<Entity> list = isCollidingWithEntities(level);
  391. for(Entity ent : list)
  392. {
  393. if(move.isSolid() || ent.move.isSolid())
  394. {
  395. //controller.onCollideWithEntity(this, ent, getCollidingFace(ent));
  396. pos.setY(oldY);
  397. my = 0.0f;
  398. }
  399. }
  400. }
  401. }
  402. motion.set(pos.getX() - oldPosX, pos.getY() - oldPosY);
  403. pos.set(oldPosX, oldPosY);
  404. }
  405. private boolean isCollidingWithTiles(int startX, int startY, int endX, int endY, Level level)
  406. {
  407. float minX = getX();
  408. float minY = getY();
  409. float maxX = minX + width;
  410. float maxY = minY + height;
  411. for(int x = startX; x <= endX; x++)
  412. {
  413. for(int y = startY; y <= endY; y++)
  414. {
  415. if(level.getInteractionTile(x, y).isMoveColliding(minX, minY, maxX, maxY, x, y, level))
  416. {
  417. return true;
  418. }
  419. }
  420. }
  421. return false;
  422. }
  423. private List<Entity> isCollidingWithEntities(Level level)
  424. {
  425. float minX = pos.getX();
  426. float minY = pos.getY();
  427. float maxX = pos.getX() + width;
  428. float maxY = pos.getY() + height;
  429. ArrayList<Entity> list = new ArrayList<>();
  430. for(Entity ent : level.getEntities())
  431. {
  432. if(ent == this)
  433. {
  434. continue;
  435. }
  436. if(ent.isColliding(minX, minY, maxX, maxY))
  437. {
  438. list.add(ent);
  439. }
  440. }
  441. for(Entity ent : level.getEntitiesInQueue())
  442. {
  443. if(ent == this)
  444. {
  445. continue;
  446. }
  447. if(ent.isColliding(minX, minY, maxX, maxY))
  448. {
  449. list.add(ent);
  450. }
  451. }
  452. return list;
  453. }
  454. public void reduceMotionByTiles(Level level)
  455. {
  456. if(move.canMoveEverywhere() || (motion.getX() == 0.0f && motion.getY() == 0.0f))
  457. {
  458. return;
  459. }
  460. float mx = motion.getX();
  461. float my = motion.getY();
  462. int startX;
  463. int endX;
  464. if(mx < 0.0f)
  465. {
  466. startX = Utils.toBlock(pos.getX() + mx);
  467. endX = Utils.toBlock(pos.getX() + width);
  468. }
  469. else
  470. {
  471. startX = Utils.toBlock(pos.getX());
  472. endX = Utils.toBlock(pos.getX() + width + mx);
  473. }
  474. int startY;
  475. int endY;
  476. if(my < 0.0f)
  477. {
  478. startY = Utils.toBlock(pos.getY() + my);
  479. endY = Utils.toBlock(pos.getY() + height);
  480. }
  481. else
  482. {
  483. startY = Utils.toBlock(pos.getY());
  484. endY = Utils.toBlock(pos.getY() + height + my);
  485. }
  486. float oldPosX = pos.getX();
  487. float oldPosY = pos.getY();
  488. while(mx != 0.0 || my != 0.0)
  489. {
  490. if(mx != 0.0f)
  491. {
  492. float oldX = pos.getX();
  493. if(mx < 0.0)
  494. {
  495. if(mx > -STEP)
  496. {
  497. pos.addX(mx);
  498. mx = 0.0f;
  499. }
  500. else
  501. {
  502. pos.addX(-STEP);
  503. mx += STEP;
  504. }
  505. }
  506. else if(mx > 0.0)
  507. {
  508. if(mx < STEP)
  509. {
  510. pos.addX(mx);
  511. mx = 0.0f;
  512. }
  513. else
  514. {
  515. pos.addX(STEP);
  516. mx -= STEP;
  517. }
  518. }
  519. if(isCollidingWithTiles(startX, startY, endX, endY, level))
  520. {
  521. pos.addY(-UP_STEP);
  522. if(isCollidingWithTiles(startX, startY, endX, endY, level))
  523. {
  524. pos.addY(UP_STEP);
  525. pos.setX(oldX);
  526. mx = 0.0f;
  527. }
  528. }
  529. }
  530. if(my != 0.0f)
  531. {
  532. float oldY = pos.getY();
  533. if(my < 0.0)
  534. {
  535. if(my > -STEP)
  536. {
  537. pos.addY(my);
  538. my = 0.0f;
  539. }
  540. else
  541. {
  542. pos.addY(-STEP);
  543. my += STEP;
  544. }
  545. }
  546. else if(my > 0.0)
  547. {
  548. if(my < STEP)
  549. {
  550. pos.addY(my);
  551. my = 0.0f;
  552. }
  553. else
  554. {
  555. pos.addY(STEP);
  556. my -= STEP;
  557. }
  558. }
  559. if(isCollidingWithTiles(startX, startY, endX, endY, level))
  560. {
  561. my = 0.0f;
  562. pos.setY(oldY);
  563. }
  564. }
  565. }
  566. motion.set(pos.getX() - oldPosX, pos.getY() - oldPosY);
  567. pos.set(oldPosX, oldPosY);
  568. }
  569. public void renderTick(float lag)
  570. {
  571. controller.renderTick(this, lag);
  572. }
  573. //--------------------------------------------------------------------------
  574. // gravity, friction
  575. //--------------------------------------------------------------------------
  576. public boolean isOnGround()
  577. {
  578. return onGround;
  579. }
  580. @Override
  581. public String toString()
  582. {
  583. return String.format("(%f, %f, %f, %f)", pos.getX(), pos.getY(), pos.getX() + width, pos.getY() + height);
  584. }
  585. }