123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741 |
- package me.hammerle.supersnuvi.gamelogic;
- import java.io.File;
- import java.util.HashMap;
- import java.util.LinkedList;
- import java.util.List;
- import java.util.TreeSet;
- import java.util.stream.Collectors;
- import me.hammerle.snuviengine.api.Shader;
- import me.hammerle.snuviengine.api.Texture;
- import me.hammerle.snuviengine.api.TextureRenderer;
- import me.hammerle.supersnuvi.Game;
- import me.hammerle.supersnuvi.entity.Entity;
- import me.hammerle.supersnuvi.entity.EntityBuilder;
- import me.hammerle.supersnuvi.tiles.Location;
- import me.hammerle.supersnuvi.tiles.StartTile;
- import me.hammerle.supersnuvi.tiles.Tile;
- import me.hammerle.supersnuvi.util.CollisionLine;
- import me.hammerle.supersnuvi.util.CollisionObject;
- import me.hammerle.supersnuvi.util.Utils;
- public final class Level
- {
- private final static float ERROR = 1f / 65536f;
-
- public final static Texture TILES = new Texture("resources/tiles.png");
- private final static Texture GUI = new Texture("resources/gui.png");
- private final static TextureRenderer GUI_RENDERER = new TextureRenderer(60);
-
- private final boolean worldLoaded;
- private final LevelData data;
-
- private final String fileName;
- private final String name;
-
- private final HashMap<Integer, Entity> entities = new HashMap<>();
- private final LinkedList<Entity> spawnQueue = new LinkedList<>();
- private Entity hero;
- private int entityCounter = 0;
-
- private boolean shouldReset = false;
- private boolean done = true; // this will be reseted in resetLevel()
-
- private int souls;
- private int maxSouls;
-
- private float time = 0.0f;
-
- private float cameraX = 0.0f;
- private float cameraY = 0.0f;
- private float oldCameraX = 0.0f;
- private float oldCameraY = 0.0f;
-
- private final int meshSize = 16;
- private final int meshLayers;
- private final int meshWidth;
- private final int meshHeight;
- private final TextureRenderer[][][] meshes;
-
- //private int tries = 7;
-
- private TreeSet<Point> spawns = new TreeSet<>();
-
- public Level(File f)
- {
- this.data = new LevelData(f);
- this.worldLoaded = data.load();
-
- if(worldLoaded)
- {
- this.name = data.getString("name", "error");
-
- meshLayers = data.getLayers() - 1;
- meshWidth = (int) Math.ceil((double) data.getWidth() / meshSize);
- meshHeight = (int) Math.ceil((double) data.getHeight() / meshSize);
- meshes = new TextureRenderer[meshLayers][meshWidth][meshHeight];
- for(int l = 0; l < meshLayers; l++)
- {
- for(int mx = 0; mx < meshWidth; mx++)
- {
- for(int my = 0; my < meshHeight; my++)
- {
- meshes[l][mx][my] = new TextureRenderer(meshSize * meshSize * 2);
- }
- }
- }
-
- // debug stuff
- if(name.equals("00-Tech Demo"))
- {
- int index = data.getBackgroundIndex();
- for(int i = 0; i < 5; i++)
- {
- data.setTile(index, 3 + i, 7 - i, 300);
- }
- data.setTile(index, 8, 3, 2);
- data.setTile(index, 9, 4, 2);
-
- /*for(int layer = 0; layer <= data.getBackgroundIndex(); layer++)
- {
- for(int x = 0; x < data.getWidth(); x++)
- {
- for(int y = 0; y < data.getHeight(); y++)
- {
- int t = data.getTile(layer, x, y);
- if(t < 16)
- {
- data.setTile(layer, x, y, t + 240);
- }
- else if(t < 32)
- {
- data.setTile(layer, x, y, t + 224);
- }
- }
- }
- }
-
- index--;
- for(int x = 0; x < data.getWidth(); x++)
- {
- for(int y = 0; y < data.getHeight(); y++)
- {
- if(data.getTile(index, x, y) == 128)
- {
- data.setTile(index, x, y, -1);
- }
- }
- }
- for(int x = 0; x < 5; x++)
- {
- for(int y = 0; y < 5; y++)
- {
- data.setTile(index, x + 6, y + 1, 256 + x + (4 - y) * 5);
- }
- }*/
- }
- // end debug stuff
-
- maxSouls = 0;
- data.forEachInteractTile((x, y, tile) ->
- {
- Tile t = Game.get().getTile(tile);
- if(t != null)
- {
- maxSouls += t.getBottleScore();
- }
-
- if(tile == StartTile.ID)
- {
- spawns.add(new Point(x, y - 1));
- }
- });
- data.forEachEntity((x, y, tile) ->
- {
- if(tile == 1)
- {
- maxSouls++;
- }
- }, 0, data.getWidth(), 0, data.getHeight());
- }
- else
- {
- this.name = f.getName();
- maxSouls = 0;
-
- meshLayers = 0;
- meshWidth = 0;
- meshHeight = 0;
- meshes = new TextureRenderer[0][0][0];
- }
- fileName = f.getName();
-
- // there must be at least one spawn
- if(spawns.isEmpty())
- {
- spawns.add(new Point(5, 5));
- }
- resetLevel();
- }
-
- // -------------------------------------------------------------------------
- // basic stuff
- // -------------------------------------------------------------------------
- public Entity getHero()
- {
- return hero;
- }
-
- public String getFileName()
- {
- return fileName;
- }
-
- public String getName()
- {
- return name;
- }
-
- public void finishLevel()
- {
- shouldReset = true;
- done = true;
- }
-
- public boolean shouldFinish()
- {
- return done;
- }
-
- public boolean shouldReset()
- {
- return shouldReset;
- }
-
- public void scheduleReset()
- {
- shouldReset = true;
- }
-
- public boolean resetLevel()
- {
- /*boolean dead = false;
- if(!done) // hero just died
- {
- tries--;
- if(tries <= 0)
- {
- tries = 7;
- dead = true;
- }
- }*/
- Game.get().resetTiles();
- data.activateEntities();
- souls = 0;
- time = 0.0f;
- shouldReset = false;
- done = false;
- Entity h = spawnHero(true);
- hero = h;
- entities.clear();
- entities.put(entityCounter++, h);
-
- for(int l = 0; l < meshLayers; l++)
- {
- for(int x = 0; x < meshWidth; x++)
- {
- for(int y = 0; y < meshHeight; y++)
- {
- meshes[l][x][y].clear();
- }
- }
- }
- return false;
- }
-
- public void spawnEntity(Entity ent)
- {
- spawnQueue.add(ent);
- }
-
- public Entity spawnHero(boolean first)
- {
- Entity h = hero;
- Point p;
- if(h == null || hero.getX() < 0 || first)
- {
- // first spawn or out of map, use first spawn
- p = spawns.first();
- }
- else
- {
- // hero is somewhere in the map, getting last spawn
- p = spawns.floor(new Point(Utils.toBlock(hero.getX()), Utils.toBlock(hero.getY())));
- if(p == null)
- {
- p = spawns.first();
- }
- }
-
- Entity newHero = EntityBuilder.buildHero(this, Utils.toCoord(p.getX()), Utils.toCoord(p.getY()));
-
- // reset the camera
- oldCameraX = -getViewX(newHero.getCenterX());
- oldCameraY = -getViewY(newHero.getCenterY());
- cameraX = oldCameraX;
- cameraY = oldCameraY;
-
- return newHero;
- }
-
- public void increaseSouls(int score)
- {
- souls += score;
- }
-
- public int getCurrentBottles()
- {
- return souls;
- }
-
- public int getMaxBottles()
- {
- return maxSouls;
- }
-
- public LevelData getData()
- {
- return data;
- }
-
- public float getTime()
- {
- return time;
- }
-
- public int getWidth()
- {
- return data.getWidth();
- }
-
- public int getHeight()
- {
- return data.getHeight();
- }
- // -------------------------------------------------------------------------
- // tick
- // -------------------------------------------------------------------------
-
- public void tick()
- {
- if(worldLoaded)
- {
- time += Game.SECS_PER_TICK;
- Game.get().tickTiles();
-
- // entity spawn layer
- /*int startX = renderer.getFirstVisibleBlockX();
- int startY = renderer.getFirstVisibleBlockY();
- int endX = Math.min(renderer.getLastVisibleBlockX() + 1, data.getWidth());
- int endY = Math.min(renderer.getLastVisibleBlockY() + 1, data.getHeight());
- data.forEachEntity((x, y, tile) ->
- {
- if(tile > 0)
- {
- data.deactivateEntity(x, y);
-
- Entity ent = EntityBuilder.fromId(tile, this, Utils.toCoord(x), Utils.toCoord(y));
- if(ent != null)
- {
- entities.put(entityCounter++, ent);
- }
- }
- }, startX, endX, startY, endY);*/
-
- // doing entity logic first
- entities.values().removeIf(entity ->
- {
- entity.tick();
- return entity.getHealth().shouldDespawn();
- });
-
- if(!spawnQueue.isEmpty())
- {
- spawnQueue.forEach(ent -> entities.put(entityCounter++, ent));
- spawnQueue.clear();
- }
-
- // calculate new camera position
- oldCameraX = cameraX;
- oldCameraY = cameraY;
- cameraX = -getViewX(hero.getCenterX());
- cameraY = -getViewY(hero.getCenterY());
- }
- }
-
- public String formatBottles(int bottles)
- {
- char[] c = new char[5];
- if(bottles <= 9)
- {
- c[0] = '0';
- c[1] = (char) (bottles + '0');
- }
- else if(bottles > 99)
- {
- c[0] = 'X';
- c[1] = 'X';
- }
- else
- {
- c[0] = (char) ((bottles / 10) + '0');
- c[1] = (char) ((bottles % 10) + '0');
- }
- c[2] = '/';
- if(maxSouls <= 9)
- {
- c[3] = '0';
- c[4] = (char) (maxSouls + '0');
- }
- else if(maxSouls > 99)
- {
- c[3] = 'X';
- c[4] = 'X';
- }
- else
- {
- c[3] = (char) ((maxSouls / 10) + '0');
- c[4] = (char) ((maxSouls % 10) + '0');
- }
- return new String(c);
- }
-
- public String formatTime(float time)
- {
- if(time == -1.0f)
- {
- return "-----";
- }
- else if(time >= 999.9f)
- {
- return "999.9";
- }
- return String.format("%05.1f", time);
- }
-
- private float getViewX(float x)
- {
- x -= Shader.getViewWidth() >> 1;
- if(x < 0)
- {
- return 0;
- }
- float max = data.getWidth() * Tile.SIZE - Shader.getViewWidth();
- if(x > max)
- {
- return max;
- }
- return x;
- }
-
- private float getViewY(float y)
- {
- y -= Shader.getViewHeight() >> 1;
- if(y < 0)
- {
- return 0;
- }
- float max = data.getHeight() * Tile.SIZE - Shader.getViewHeight();
- if(y > max)
- {
- return max;
- }
- return y;
- }
-
- public void updateTile(int layer, int x, int y)
- {
- if(layer > data.getBackgroundIndex())
- {
- layer--;
- }
- meshes[layer][x / meshSize][y / meshSize].clear();
- }
-
- public void updateTile(int x, int y)
- {
- updateTile(data.getBackgroundIndex(), x, y);
- }
-
- private void drawMesh(int l, int tl, int mx, int my)
- {
- TextureRenderer tr = meshes[l][mx][my];
- if(!tr.isBuilt())
- {
- int tsx = mx * meshSize;
- int tsy = my * meshSize;
- int tex = Math.min(tsx + meshSize, data.getWidth());
- int tey = Math.min(tsy + meshSize, data.getHeight());
- for(int x = tsx; x < tex; x++)
- {
- for(int y = tsy; y < tey; y++)
- {
- Tile t = Game.get().getTile(data.getTile(tl, x, y));
- if(t.shouldRender(x, y))
- {
- float minX = x * Tile.SIZE + t.getOffsetX();
- float minY = y * Tile.SIZE + t.getOffsetY();
- tr.addRectangle(minX, minY,
- minX + t.getWidth(), minY + t.getHeight(),
- t.getTextureMinX() + ERROR, t.getTextureMinY() + ERROR,
- t.getTextureMaxX() - ERROR, t.getTextureMaxY() - ERROR);
- }
- }
- }
- tr.build();
- }
- meshes[l][mx][my].draw();
- }
-
- public void renderTick(float lag)
- {
- if(worldLoaded)
- {
- float camX = Utils.interpolate(oldCameraX, cameraX, lag);
- float camY = Utils.interpolate(oldCameraY, cameraY, lag);
-
- Shader.translateTo(camX, camY);
- Shader.updateMatrix();
-
- int startX = (int) (-camX / (meshSize * Tile.SIZE));
- int startY = (int) (-camY / (meshSize * Tile.SIZE));
- int endX = (int) Math.ceil((-camX + Shader.getViewWidth()) / (meshSize * Tile.SIZE));
- int endY = (int) Math.ceil((-camY + Shader.getViewHeight()) / (meshSize * Tile.SIZE));
-
- startX = Math.min(Math.max(startX, 0), meshWidth);
- startY = Math.min(Math.max(startY, 0), meshHeight);
- endX = Math.min(Math.max(endX, 0), meshWidth);
- endY = Math.min(Math.max(endY, 0), meshHeight);
-
- // background
- Shader.setColorEnabled(false);
- Shader.setTextureEnabled(true);
- Shader.setBlendingEnabled(true);
- TILES.bind();
-
- int fromLayer = 0;
- int toLayer = data.getBackgroundIndex() + 1;
-
- for(int l = fromLayer; l < toLayer; l++)
- {
- for(int mx = startX; mx < endX; mx++)
- {
- for(int my = startY; my < endY; my++)
- {
- drawMesh(l, l, mx, my);
- }
- }
- }
-
- // entities
-
- entities.values().forEach(entity ->
- {
- entity.renderTick(lag);
- });
-
- // foreground
- Shader.setColorEnabled(false);
- Shader.setTextureEnabled(true);
- Shader.setBlendingEnabled(true);
- TILES.bind();
-
- fromLayer = toLayer + 1;
- toLayer = data.getLayers();
-
- for(int l = fromLayer; l < toLayer; l++)
- {
- for(int mx = startX; mx < endX; mx++)
- {
- for(int my = startY; my < endY; my++)
- {
- drawMesh(l - 1, l, mx, my);
- }
- }
- }
-
- // menu rendering
- Shader.translateTo(0.0f, 0.0f);
- Shader.updateMatrix();
-
- // grey background of clock and bottles
- float lineHeight = Shader.getFontRenderer().getHeight();
- float lineWidth = Shader.getFontRenderer().getWidth();
- Shader.setColorEnabled(true);
- Shader.setTextureEnabled(false);
- Shader.getColorRenderer().drawRectangle(0.0f, 0.0f, (lineWidth * 6.0f) + 10.0f, (lineHeight * 2.0f + 10.0f), 0x77000000);
- Shader.setTextureEnabled(true);
- float y = 5.0f;
- y = Shader.getFontRenderer().drawString(13.0f, y, formatBottles(souls));
- Shader.getFontRenderer().drawString(13.0f, y, formatTime(time));
- Shader.setColorEnabled(false);
-
- float w = Shader.getViewWidth();
- GUI.bind();
- GUI_RENDERER.clear();
-
- int scale = Shader.getViewScale();
-
- // bottles
- switch(scale)
- {
- case 1: GUI_RENDERER.addRectangle(6.0f, 4.0f, 12.0f, 14.0f, 0.0f, 0.046875f, 0.01171875f, 0.068359375f); break;
- case 2: GUI_RENDERER.addRectangle(6.0f, 4.0f, 12.0f, 14.0f, 0.01171875f, 0.046875f, 0.037109375f, 0.0859375f); break;
- default: GUI_RENDERER.addRectangle(6.0f, 4.0f, 12.0f, 14.0f, 0.037109375f, 0.046875f, 0.06640625f, 0.10546875f); break;
- }
-
- // clock
- switch(scale)
- {
- case 1: GUI_RENDERER.addRectangle(4.0f, y, 13.0f, y + 9.0f, 0.0f, 0.265625f, 0.017578125f, 0.283203125f); break;
- case 2: GUI_RENDERER.addRectangle(4.5f, y, 13.0f, y + 8.5f, 0.017578125f, 0.265625f, 0.05078125f, 0.298828125f); break;
- default: GUI_RENDERER.addRectangle(4.666666666f, y, 13.0f, y + 8.333333333f, 0.05078125f, 0.265625f, 0.099609375f, 0.314453125f); break;
- }
-
- // gui background
- GUI_RENDERER.addRectangle(w - 111.0f, 0.0f, w - 44.0f, 24.0f, 0.0f, 0.0f, 0.130859375f, 0.046875f);
- GUI_RENDERER.addRectangle(w - 44.0f, 0.0f, w - 18.0f, 16.0f, 0.130859375f, 0.0f, 0.181640625f, 0.03125f);
- GUI_RENDERER.addRectangle(w - 76.0f, 24.0f, w - 45.0f, 57.0f, 0.068359375f, 0.046875f, 0.12890625f, 0.111328125f);
-
- // health mirror
- int healthFrame = (int) (hero.getHealth().getHealthPercent() * 7);
- float leftMirror = (7 - healthFrame) * 0.0625f;
- GUI_RENDERER.addRectangle(w - 39.0f, 8.0f, w - 7.0f, 46.0f, leftMirror, 0.15625f, leftMirror + 0.0625f, 0.23046875f);
-
- // energy
- float energy = hero.getEnergy().getEnergyPercent();
- float fullEnd = w - 109.0f + 64.0f * energy;
- GUI_RENDERER.addRectangle(w - 109.0f, 13.0f, fullEnd, 21.0f, 0.0f, 0.140625f, 0.125f * energy, 0.15625f);
- GUI_RENDERER.addRectangle(fullEnd, 13.0f, w - 45.0f, 21.0f, 0.125f * energy, 0.125f, 0.125f, 0.140625f);
-
- // gui foreground
- GUI_RENDERER.addRectangle(w - 49.0f, 0.0f, w, 64.0f, 0.201171875f, 0.0f, 0.296875f, 0.125f);
- GUI_RENDERER.addRectangle(w - 109.0f, 15.0f, w - 106.0f, 18.0f, 0.15625f, 0.03125f, 0.162109375f, 0.037109375f);
- GUI_RENDERER.addRectangle(w - 97.0f, 15.0f, w - 92.0f, 20.0f, 0.1796875f, 0.03125f, 0.189453125f, 0.041015625f);
-
- // health number overlay
- GUI_RENDERER.addRectangle(w - 30.0f, 53.0f, w - 12.0f, 62.0f, leftMirror, 0.23828125f, leftMirror + 0.03515625f, 0.255859375f);
-
- GUI_RENDERER.build();
- GUI_RENDERER.draw();
-
- // dynamic clock hand
- Shader.setColorEnabled(true);
- Shader.setTextureEnabled(false);
- switch(scale)
- {
- case 1: Shader.translateTo(8.5f, y + 4.5f); break;
- case 2: Shader.translateTo(8.75f, y + 4.25f); break;
- default: Shader.translateTo(8.8333333333f, y + 4.16666667f); break;
- }
- Shader.rotate(-time * 72.0f);
- Shader.updateMatrix();
- Shader.getColorRenderer().drawRectangle(-0.5f / scale, -0.5f / scale, 0.5f / scale, 4.0f - 0.5f * scale, 0xFF000000);
- }
- }
-
- // -------------------------------------------------------------------------
- // collision box, interaction layer
- // -------------------------------------------------------------------------
-
- private Tile getInteractionTile(int x, int y)
- {
- int i = data.getInteractionTile(x, y);
- if(i == -1)
- {
- return Game.FALLBACK_TILE;
- }
- return Game.get().getTile(i);
- }
-
- private CollisionObject getMovementBox(int x, int y)
- {
- int i = data.getInteractionTile(x, y);
- if(i == -1)
- {
- return CollisionObject.NULL_BOX;
- }
- return Game.get().getTile(i).getMovementBox(x, y).reset().offset(Utils.toCoord(x), Utils.toCoord(y));
- }
-
- public List<CollisionObject> getMovementBoxesAt(CollisionObject box, Entity not)
- {
- List<CollisionObject> boxes;
- if(not != null)
- {
- boxes = getEntitiesCollidingWith(not, box).stream().map(ent -> ent.getBox()).collect(Collectors.toList());
- }
- else
- {
- boxes = new LinkedList<>();
- }
- int startX = Utils.toBlock(box.getMinX());
- int endX = Utils.toBlock(box.getMaxX());
- int startY = Utils.toBlock(box.getMinY());
- int endY = Utils.toBlock(box.getMaxY());
-
- for(int x = startX; x <= endX; x++)
- {
- for(int y = startY; y <= endY; y++)
- {
- CollisionObject cb = getMovementBox(x, y);
- if(cb.mayCollide(box) && cb != CollisionObject.NULL_BOX)
- {
- boxes.add(cb.copy());
- }
- }
- }
- return boxes;
- }
-
- private CollisionObject getCollisionBox(int x, int y)
- {
- int i = data.getInteractionTile(x, y);
- if(i == -1)
- {
- return CollisionObject.NULL_BOX;
- }
- Tile tile = Game.get().getTile(i);
- return tile.getCollisionBox(x, y).reset().offset(Utils.toCoord(x), Utils.toCoord(y));
- }
-
- public List<Location> getCollisionBoxesAt(CollisionObject cb)
- {
- LinkedList<Location> boxes = new LinkedList<>();
- int startX = Utils.toBlock(cb.getMinX());
- int endX = Utils.toBlock(cb.getMaxX());
- int startY = Utils.toBlock(cb.getMinY());
- int endY = Utils.toBlock(cb.getMaxY());
-
- for(int x = startX; x <= endX; x++)
- {
- for(int y = startY; y <= endY; y++)
- {
- if(getCollisionBox(x, y).isColliding(cb))
- {
- boxes.add(new Location(getInteractionTile(x, y), x, y));
- }
- }
- }
- return boxes;
- }
-
- public List<Entity> getEntitiesCollidingWith(Entity not, CollisionObject cb)
- {
- return entities.values().stream().filter(ent -> ent != not && ent.getBox().isColliding(cb)).collect(Collectors.toList());
- }
- }
|