package me.hammerle.supersnuvi.entity; import java.util.List; import me.hammerle.supersnuvi.entity.components.ai.Controller; import me.hammerle.supersnuvi.entity.components.Energy; import me.hammerle.supersnuvi.entity.components.Health; import me.hammerle.supersnuvi.entity.components.ItemCollector; import me.hammerle.supersnuvi.entity.components.Movement; import me.hammerle.supersnuvi.gamelogic.Level; import me.hammerle.supersnuvi.util.CollisionObject; import me.hammerle.supersnuvi.util.Face; import me.hammerle.supersnuvi.util.Utils; public final class Entity { public static final float GRAVITY = 8.0f; public static final float STEP = 0.0625f; // this one is a little bit bigger to prevent wrong calculation // while joing upwars public static final float UP_STEP = STEP + 0.001953125f; // the last position is used for interpolation during rendering private float lastPosX; private float lastPosY; // the current position of the entity private float posX; private float posY; // the collision box of the entity private final CollisionObject box; // the motion before the movement collision check private float preMotionX; private float preMotionY; // the motion after the movement collision check private float motionX; private float motionY; // a flag indicating that the entity is on the ground private boolean onGround = false; // the level of the entity private final Level level; // entity components protected Controller controller; protected Health health; protected Energy energy; protected Movement move; protected ItemCollector itemCollector; // face private Face face; protected Entity(Level level, float x, float y, CollisionObject box) { lastPosX = x; lastPosY = y; posX = x; posY = y; // ensure the box cannot be modified from the outside this.box = box.copy().offset(x, y); preMotionX = 0.0f; preMotionY = 0.0f; motionX = 0.0f; motionY = 0.0f; this.level = level; // components this.controller = Controller.NULL; this.health = Health.NULL; this.energy = Energy.NULL; this.move = Movement.NULL; this.itemCollector = ItemCollector.NULL; } public Level getLevel() { return level; } //-------------------------------------------------------------------------- // components //-------------------------------------------------------------------------- public boolean isAnimated() { return controller.isAnimated(); } public Health getHealth() { return health; } public Energy getEnergy() { return energy; } public Movement getMovement() { return move; } public ItemCollector getItemCollector() { return itemCollector; } //-------------------------------------------------------------------------- // basic stuff //-------------------------------------------------------------------------- public float getSquaredDistance(Entity e) { return Utils.getSquaredDistance( posX + box.getWidth() * 0.5f, posY + box.getHeight() * 0.5f, e.posX + e.box.getWidth() * 0.5f, e.posY + e.box.getHeight() * 0.5f); } public CollisionObject getBox() { return box; } public float getX() { return posX; } public float getY() { return posY; } public float getLastX() { return lastPosX; } public float getLastY() { return lastPosY; } public float getCenterX() { return posX + box.getWidth() * 0.5f; } public float getCenterY() { return posY + box.getHeight() * 0.5f; } public Face getFace() { return face; } public float getMotionX() { return motionX; } public void setMotionX(float motionX) { this.motionX = motionX; } public float getMotionY() { return motionY; } public void setMotionY(float motionY) { this.motionY = motionY; } public float getPreMotionX() { return preMotionX; } public float getPreMotionY() { return preMotionY; } //-------------------------------------------------------------------------- // ticking //-------------------------------------------------------------------------- public void tick() { lastPosX = posX; lastPosY = posY; controller.tick(); energy.tick(); preMotionX = motionX; preMotionY = motionY; if(preMotionX != 0.0f) { face = preMotionX > 0.0f ? Face.RIGHT : Face.LEFT; } if(move.hasGravity()) { preMotionY += GRAVITY * move.getGravityFactor(); } if(move.canMoveEverywhere()) { motionX = preMotionX; motionY = preMotionY; } else { CollisionObject testBox = box.copy().expand(preMotionX, preMotionY); List boxes = level.getMovementBoxesAt(testBox, this); if(!boxes.isEmpty()) { float mx = preMotionX; float my = preMotionY; testBox.reset(); float oldX = testBox.getMinX(); float oldY = testBox.getMinY(); while(mx != 0.0 || my != 0.0) { testBox.save(); if(mx < 0.0) { if(mx > -STEP) { testBox.offsetX(mx); mx = 0.0f; } else { testBox.offsetX(-STEP); mx += STEP; } } else if(mx > 0.0) { if(mx < STEP) { testBox.offsetX(mx); mx = 0.0f; } else { testBox.offsetX(STEP); mx -= STEP; } } for(CollisionObject cb : boxes) { if(cb.isColliding(testBox)) { testBox.offsetY(-UP_STEP); for(CollisionObject cb2 : boxes) { if(cb2.isColliding(testBox)) { mx = 0.0f; testBox.reset(); break; } } break; } } testBox.save(); if(my < 0.0) { if(my > -STEP) { testBox.offsetY(my); my = 0.0f; } else { testBox.offsetY(-STEP); my += STEP; } } else if(my > 0.0) { if(my < STEP) { testBox.offsetY(my); my = 0.0f; } else { testBox.offsetY(STEP); my -= STEP; } } for(CollisionObject cb : boxes) { if(cb.isColliding(testBox)) { my = 0.0f; testBox.reset(); break; } } } motionX = testBox.getMinX() - oldX; motionY = testBox.getMinY() - oldY; /*if(motionX != 0.0f || motionY != 0.0f) { System.out.println("____________________"); System.out.println(testBox + " " + preMotionX + " " + preMotionY); System.out.println(box); boxes.forEach(b -> System.out.println(b)); System.out.println(motionX + " " + motionY); System.out.println("____________________"); }*/ } else { motionX = preMotionX; motionY = preMotionY; } } posX += motionX; posY += motionY; box.reset().offset(posX, posY); //onGround = preMotionY > 0.0f && motionY == 0; onGround = !level.getCollisionBoxesAt(box.copy().expand(0.0f, STEP * 2)).isEmpty(); move.setInWater(false); move.setFrictionFactor(0.6f); // apply collision CollisionObject cb = box.copy(); for(Face f : Face.values()) { cb.reset(); cb.expand(f.getCollisionOffsetX(), f.getCollisionOffsetY()); level.getEntitiesCollidingWith(this, box).forEach(ent -> ent.controller.onCollideWithEntity(ent, f)); level.getCollisionBoxesAt(cb).forEach(loc -> { controller.onCollideWithTile(loc, f); loc.getTile().onEntityCollide(this, loc.getX(), loc.getY(), f.getOpposite()); }); } motionX *= move.getFrictionFactor(); if(Math.abs(motionX) < 0.3) { motionX = 0.0f; } health.tick(); } public void renderTick(float lag) { controller.renderTick(lag); } //-------------------------------------------------------------------------- // gravity, friction //-------------------------------------------------------------------------- public boolean isOnGround() { return onGround; } }