diff --git a/res/img/dudes.png b/res/img/dudes.png new file mode 100644 index 0000000..46fc775 Binary files /dev/null and b/res/img/dudes.png differ diff --git a/res/img/dudes.xcf b/res/img/dudes.xcf new file mode 100644 index 0000000..192a433 Binary files /dev/null and b/res/img/dudes.xcf differ diff --git a/res/img/tiles16.png b/res/img/tiles16.png new file mode 100644 index 0000000..c972b97 Binary files /dev/null and b/res/img/tiles16.png differ diff --git a/res/img/tiles16.xcf b/res/img/tiles16.xcf new file mode 100644 index 0000000..74e1b6b Binary files /dev/null and b/res/img/tiles16.xcf differ diff --git a/src/mightypork/gamecore/render/Render.java b/src/mightypork/gamecore/render/Render.java index 1ab374f..7ef82c5 100644 --- a/src/mightypork/gamecore/render/Render.java +++ b/src/mightypork/gamecore/render/Render.java @@ -5,11 +5,9 @@ import static org.lwjgl.opengl.GL11.*; import java.io.IOException; -import mightypork.gamecore.audio.players.EffectPlayer; import mightypork.gamecore.render.textures.FilterMode; import mightypork.gamecore.render.textures.GLTexture; import mightypork.gamecore.render.textures.TxQuad; -import mightypork.rogue.Res; import mightypork.util.constraints.rect.Rect; import mightypork.util.constraints.rect.caching.RectDigest; import mightypork.util.constraints.vect.Vect; @@ -430,93 +428,69 @@ public class Render { * Render textured rect * * @param quad rectangle (px) - * @param uvs texture coords rectangle (0-1) - * @param texture texture instance + * @param txquad texture quad + */ + public static void quadTextured(Rect quad, TxQuad txquad) + { + quadTextured(quad, txquad, Color.WHITE); + } + + + /** + * Render textured rect + * + * @param quad rectangle (px) + * @param txquad texture instance * @param tint color tint */ - public static void quadTextured(Rect quad, Rect uvs, GLTexture texture, Color tint) + public static void quadTextured(Rect quad, TxQuad txquad, Color tint) { if (!batchTexturedQuadMode) { glEnable(GL_TEXTURE_2D); - texture.bind(); + txquad.tx.bind(); glBegin(GL_QUADS); } setColor(tint); final RectDigest q = quad.digest(); - final RectDigest u = uvs.digest(); + final RectDigest u = txquad.uvs.digest(); - final double w = texture.getWidth01(); - final double h = texture.getHeight01(); + double tL = u.left, tR = u.right, tT = u.top, tB = u.bottom; + + // handle flip + if (txquad.isFlippedY()) { + final double swap = tT; + tT = tB; + tB = swap; + } + + if (txquad.isFlippedX()) { + final double swap = tL; + tL = tR; + tR = swap; + } + + final double w = txquad.tx.getWidth01(); + final double h = txquad.tx.getHeight01(); // quad with texture - glTexCoord2d(u.x * w, u.bottom * h); + glTexCoord2d(tL * w, tB * h); glVertex2d(q.left, q.bottom); - glTexCoord2d(u.right * w, u.bottom * h); + glTexCoord2d(tR * w, tB * h); glVertex2d(q.right, q.bottom); - glTexCoord2d(u.right * w, u.top * h); + glTexCoord2d(tR * w, tT * h); glVertex2d(q.right, q.top); - glTexCoord2d(u.left * w, u.top * h); + glTexCoord2d(tL * w, tT * h); glVertex2d(q.left, q.top); if (!batchTexturedQuadMode) glEnd(); } - /** - * Render textured rect - * - * @param quad rectangle (px) - * @param uvs texture coords rectangle (px) - * @param texture texture instance - */ - public static void quadTextured(Rect quad, Rect uvs, GLTexture texture) - { - quadTextured(quad, uvs, texture, Color.WHITE); - } - - - /** - * Render textured rect - * - * @param quad rectangle (px) - * @param texture texture instance - */ - public static void quadTextured(Rect quad, GLTexture texture) - { - quadTextured(quad, Rect.ONE, texture, Color.WHITE); - } - - - /** - * Render textured rect - * - * @param quad rectangle (px) - * @param txquad texture quad - */ - public static void quadTextured(Rect quad, TxQuad txquad) - { - quadTextured(quad, txquad, Color.WHITE); - } - - - /** - * Render textured rect - * - * @param quad rectangle (px) - * @param txquad texture instance - * @param tint color tint - */ - public static void quadTextured(Rect quad, TxQuad txquad, Color tint) - { - quadTextured(quad, txquad.uvs, txquad.tx, tint); - } - - /** * Setup Ortho projection for 2D graphics * diff --git a/src/mightypork/gamecore/render/Screenshot.java b/src/mightypork/gamecore/render/Screenshot.java index eb9bf2a..21bbee8 100644 --- a/src/mightypork/gamecore/render/Screenshot.java +++ b/src/mightypork/gamecore/render/Screenshot.java @@ -75,6 +75,7 @@ public class Screenshot { */ public void save(File file) throws IOException { + file.getParentFile().mkdirs(); ImageIO.write(getImage(), "PNG", file); } } diff --git a/src/mightypork/gamecore/render/textures/TxQuad.java b/src/mightypork/gamecore/render/textures/TxQuad.java index d588505..ade538e 100644 --- a/src/mightypork/gamecore/render/textures/TxQuad.java +++ b/src/mightypork/gamecore/render/textures/TxQuad.java @@ -17,6 +17,9 @@ public class TxQuad { /** Coords in texture (0-1) */ public final RectConst uvs; + private boolean flipX; + private boolean flipY; + /** * TxQuad from origin and size in pixels @@ -88,6 +91,8 @@ public class TxQuad { { this.tx = txQuad.tx; this.uvs = txQuad.uvs; + this.flipX = txQuad.flipX; + this.flipY = txQuad.flipY; } @@ -113,4 +118,38 @@ public class TxQuad { { return new TxSheet(this, width, height); } + + + /** + * @return copy flipped X + */ + public TxQuad flipX() + { + final TxQuad copy = new TxQuad(this); + copy.flipX ^= true; + return copy; + } + + + /** + * @return copy flipped Y + */ + public TxQuad flipY() + { + final TxQuad copy = new TxQuad(this); + copy.flipY ^= true; + return copy; + } + + + public boolean isFlippedY() + { + return flipY; + } + + + public boolean isFlippedX() + { + return flipX; + } } diff --git a/src/mightypork/gamecore/render/textures/TxSheet.java b/src/mightypork/gamecore/render/textures/TxSheet.java index 28485b2..0145714 100644 --- a/src/mightypork/gamecore/render/textures/TxSheet.java +++ b/src/mightypork/gamecore/render/textures/TxSheet.java @@ -3,6 +3,8 @@ package mightypork.gamecore.render.textures; import java.util.Random; +import mightypork.util.logging.Log; + /** * Basic sprite sheet @@ -39,6 +41,18 @@ public class TxSheet { } + /** + * Get a quad based on ratio 0-1 (0: first, 1: last) + * + * @param ratio ratio + * @return quad + */ + public TxQuad getQuad(double ratio) + { + return getQuad((int) Math.round((count - 1) * ratio)); + } + + /** * Get quad of index * @@ -47,8 +61,9 @@ public class TxSheet { */ public TxQuad getQuad(int index) { - if (index < 0 || index > count) { - throw new IndexOutOfBoundsException("Index out of bounds: " + index + ", allowed: 0.." + count); + if (index < 0 || index >= count) { + Log.w("Index out of bounds: " + index + ", allowed: 0.." + count); + index = index % count; } // lazy - init only when needed diff --git a/src/mightypork/rogue/Res.java b/src/mightypork/rogue/Res.java index 475d674..65cce3d 100644 --- a/src/mightypork/rogue/Res.java +++ b/src/mightypork/rogue/Res.java @@ -61,10 +61,13 @@ public final class Res { private static void loadTextures() { GLTexture texture; + QuadGrid tiles; + // tests texture = textures.loadTexture("test.kitten", "/res/img/kitten.png", FilterMode.LINEAR, WrapMode.CLAMP); texture = textures.loadTexture("test.kitten2", "/res/img/kitten_npot.png", FilterMode.LINEAR, WrapMode.CLAMP); + // gui texture = textures.loadTexture("gui1", "/res/img/gui1.png", FilterMode.NEAREST, WrapMode.CLAMP); final QuadGrid gui = texture.grid(4, 4); textures.addQuad("item_frame", gui.makeQuad(0, 0)); @@ -76,19 +79,41 @@ public final class Res { textures.addQuad("xp_off", gui.makeQuad(.5, 1.5, .5, .5)); textures.addQuad("panel", gui.makeQuad(0, 3.75, 4, .25)); - texture = textures.loadTexture("tiles", "/res/img/tiles.png", FilterMode.NEAREST, WrapMode.CLAMP); - final QuadGrid tiles = texture.grid(32, 32); + // huge sheet +// texture = textures.loadTexture("tiles", "/res/img/tiles.png", FilterMode.NEAREST, WrapMode.CLAMP); +// tiles = texture.grid(32, 32); +// textures.addSheet("tile.wall.mossy_bricks", tiles.makeSheet(4, 0, 7, 1)); +// textures.addSheet("tile.wall.small_bricks", tiles.makeSheet(0, 0, 4, 1)); +// textures.addSheet("tile.floor.mossy_bricks", tiles.makeSheet(16, 5, 7, 1)); +// textures.addSheet("tile.floor.rect_bricks", tiles.makeSheet(23, 5, 4, 1)); +// textures.addSheet("tile.wall.sandstone", tiles.makeSheet(0, 3, 10, 1)); +// textures.addSheet("tile.floor.sandstone", tiles.makeSheet(0, 6, 10, 1)); +// textures.addSheet("tile.wall.brown_cobble", tiles.makeSheet(0, 8, 8, 1)); +// textures.addSheet("tile.floor.brown_cobble", tiles.makeSheet(0, 11, 9, 1)); +// textures.addSheet("tile.floor.crystal", tiles.makeSheet(4, 5, 6, 1)); +// textures.addSheet("tile.wall.crystal", tiles.makeSheet(12, 2, 14, 1)); - textures.addSheet("tile.wall.mossy_bricks", tiles.makeSheet(4, 0, 7, 1)); - textures.addSheet("tile.wall.small_bricks", tiles.makeSheet(0, 0, 4, 1)); - textures.addSheet("tile.floor.mossy_bricks", tiles.makeSheet(16, 5, 7, 1)); - textures.addSheet("tile.floor.rect_bricks", tiles.makeSheet(23, 5, 4, 1)); - textures.addSheet("tile.wall.sandstone", tiles.makeSheet(0, 3, 10, 1)); - textures.addSheet("tile.floor.sandstone", tiles.makeSheet(0, 6, 10, 1)); - textures.addSheet("tile.wall.brown_cobble", tiles.makeSheet(0, 8, 8, 1)); - textures.addSheet("tile.floor.brown_cobble", tiles.makeSheet(0, 11, 9, 1)); - textures.addSheet("tile.floor.crystal", tiles.makeSheet(4, 5, 6, 1)); - textures.addSheet("tile.wall.crystal", tiles.makeSheet(12, 2, 14, 1)); + // sprites + texture = textures.loadTexture("mob", "/res/img/dudes.png", FilterMode.NEAREST, WrapMode.CLAMP); + tiles = texture.grid(8, 8); + textures.addSheet("player", tiles.makeSheet(0, 0, 4, 1)); + + // small sheet + texture = textures.loadTexture("tiles16", "/res/img/tiles16.png", FilterMode.NEAREST, WrapMode.CLAMP); + tiles = texture.grid(8, 8); + + textures.addSheet("tile16.floor.dark", tiles.makeSheet(0, 1, 5, 1)); + textures.addSheet("tile16.wall.brick", tiles.makeSheet(0, 0, 5, 1)); + + textures.addQuad("tile16.shadow.n", tiles.makeQuad(0, 7)); + textures.addQuad("tile16.shadow.s", tiles.makeQuad(0, 7).flipY()); + textures.addQuad("tile16.shadow.w", tiles.makeQuad(2, 7)); + textures.addQuad("tile16.shadow.e", tiles.makeQuad(2, 7).flipX()); + + textures.addQuad("tile16.shadow.nw", tiles.makeQuad(1, 7)); + textures.addQuad("tile16.shadow.ne", tiles.makeQuad(1, 7).flipX()); + textures.addQuad("tile16.shadow.sw", tiles.makeQuad(1, 7).flipY()); + textures.addQuad("tile16.shadow.se", tiles.makeQuad(1, 7).flipY().flipX()); } diff --git a/src/mightypork/rogue/screens/ingame/WorldLayer.java b/src/mightypork/rogue/screens/ingame/WorldLayer.java index 6d0be6d..e3722ff 100644 --- a/src/mightypork/rogue/screens/ingame/WorldLayer.java +++ b/src/mightypork/rogue/screens/ingame/WorldLayer.java @@ -7,9 +7,16 @@ import java.util.Random; import mightypork.gamecore.gui.screens.Screen; import mightypork.gamecore.gui.screens.ScreenLayer; +import mightypork.gamecore.input.InputSystem; +import mightypork.gamecore.input.KeyStroke; +import mightypork.gamecore.input.Keys; import mightypork.rogue.Paths; import mightypork.rogue.world.MapGenerator; +import mightypork.rogue.world.PlayerControl; import mightypork.rogue.world.World; +import mightypork.rogue.world.entity.Entity; +import mightypork.rogue.world.entity.models.EntityMoveListener; +import mightypork.rogue.world.level.Level; import mightypork.util.ion.Ion; @@ -46,63 +53,81 @@ public class WorldLayer extends ScreenLayer { wr.setRect(root); root.add(wr); -// bindKey(new KeyStroke(true, Keys.LEFT), new Runnable() { -// -// @Override -// public void run() -// { -// w.getPlayer().walk(-1, 0); -// } -// }); -// bindKey(new KeyStroke(true, Keys.RIGHT), new Runnable() { -// -// @Override -// public void run() -// { -// w.getPlayer().walk(1, 0); -// } -// }); -// bindKey(new KeyStroke(true, Keys.UP), new Runnable() { -// -// @Override -// public void run() -// { -// w.getPlayer().walk(0, -1); -// } -// }); -// bindKey(new KeyStroke(true, Keys.DOWN), new Runnable() { -// -// @Override -// public void run() -// { -// w.getPlayer().walk(0, 1); -// } -// }); -// bindKey(new KeyStroke(true, Keys.SPACE), new Runnable() { -// -// @Override -// public void run() -// { -// w.getPlayer().walk(5, 5); -// } -// }); -// -// w.getPlayer().setMoveListener(new Runnable() { -// -// @Override -// public void run() -// { -// if (InputSystem.isKeyDown(Keys.LEFT)) { -// w.getPlayer().walk(-1, 0); -// } else if (InputSystem.isKeyDown(Keys.RIGHT)) { -// w.getPlayer().walk(1, 0); -// } else if (InputSystem.isKeyDown(Keys.UP)) { -// w.getPlayer().walk(0, -1); -// } else if (InputSystem.isKeyDown(Keys.DOWN)) { -// w.getPlayer().walk(0, 1); -// } -// } -// }); + final PlayerControl c = w.getPlayerControl(); + + bindKey(new KeyStroke(true, Keys.LEFT), new Runnable() { + + @Override + public void run() + { + c.walkWest(); + } + }); + + bindKey(new KeyStroke(true, Keys.RIGHT), new Runnable() { + + @Override + public void run() + { + c.walkEast(); + } + }); + + bindKey(new KeyStroke(true, Keys.UP), new Runnable() { + + @Override + public void run() + { + c.walkNorth(); + } + }); + + bindKey(new KeyStroke(true, Keys.DOWN), new Runnable() { + + @Override + public void run() + { + c.walkSouth(); + } + }); + + c.addMoveListener(new EntityMoveListener() { + + private void tryGo(Entity entity) + { + if (InputSystem.isKeyDown(Keys.LEFT)) { + c.walkWest(); + } else if (InputSystem.isKeyDown(Keys.RIGHT)) { + c.walkEast(); + } else if (InputSystem.isKeyDown(Keys.UP)) { + c.walkNorth(); + } else if (InputSystem.isKeyDown(Keys.DOWN)) { + c.walkSouth(); + } + } + + + @Override + public void onStepFinished(Entity entity, World world, Level level) + { + entity.cancelPath(); // halt + tryGo(entity); + } + + + @Override + public void onPathFinished(Entity entity, World world, Level level) + { + entity.cancelPath(); // halt + tryGo(entity); + } + + + @Override + public void onPathAborted(Entity entity, World world, Level level) + { + } + }); } diff --git a/src/mightypork/rogue/screens/ingame/WorldRenderer.java b/src/mightypork/rogue/screens/ingame/WorldRenderer.java index 524c84f..868618e 100644 --- a/src/mightypork/rogue/screens/ingame/WorldRenderer.java +++ b/src/mightypork/rogue/screens/ingame/WorldRenderer.java @@ -48,7 +48,7 @@ public class WorldRenderer extends InputComponent implements Updateable { @Override protected void renderComponent() { - world.render(this, 8, 6, 64); + world.render(this, 8, 6, 110); Render.quadGradH(leftShadow, RGB.BLACK, RGB.NONE); Render.quadGradH(rightShadow, RGB.NONE, RGB.BLACK); diff --git a/src/mightypork/rogue/world/MapGenerator.java b/src/mightypork/rogue/world/MapGenerator.java index 65797bd..6e9fc48 100644 --- a/src/mightypork/rogue/world/MapGenerator.java +++ b/src/mightypork/rogue/world/MapGenerator.java @@ -21,8 +21,8 @@ public class MapGenerator { final World w = new World(); w.setSeed(seed); - w.addLevel(createLevel(rand.nextLong(), Tiles.CRYSTAL_FLOOR, Tiles.CRYSTAL_WALL)); - w.addLevel(createLevel(rand.nextLong(), Tiles.BRCOBBLE_FLOOR, Tiles.BRCOBBLE_WALL)); + w.addLevel(createLevel(rand.nextLong(), Tiles.FLOOR_DARK, Tiles.WALL_BRICK)); + //w.addLevel(createLevel(rand.nextLong(), Tiles.BRCOBBLE_FLOOR, Tiles.BRCOBBLE_WALL)); // TODO place on start position w.createPlayer(10, 10, 0); diff --git a/src/mightypork/rogue/world/PathStep.java b/src/mightypork/rogue/world/PathStep.java index ada46a2..b5b7174 100644 --- a/src/mightypork/rogue/world/PathStep.java +++ b/src/mightypork/rogue/world/PathStep.java @@ -10,6 +10,37 @@ import mightypork.util.ion.IonOutput; public class PathStep implements IonBinary { + public static final PathStep NORTH = new PathStep(0, -1); + public static final PathStep SOUTH = new PathStep(0, 1); + public static final PathStep EAST = new PathStep(1, 0); + public static final PathStep WEST = new PathStep(-1, 0); + public static final PathStep NORTH_EAST = new PathStep(1, -1); + public static final PathStep NORTH_WEST = new PathStep(-1, -1); + public static final PathStep SOUTH_EAST = new PathStep(1, 1); + public static final PathStep SOUTH_WEST = new PathStep(-1, 1); + public static final PathStep NONE = new PathStep(0, 0); + + + public static PathStep make(int x, int y) + { + x = x < 0 ? -1 : x > 0 ? 1 : 0; + y = y < 0 ? -1 : y > 0 ? 1 : 0; + + if (x == 0 && y == -1) return NORTH; + if (x == 0 && y == 1) return SOUTH; + if (x == -1 && y == 0) return WEST; + if (x == 1 && y == 0) return EAST; + + if (x == -1 && y == -1) return NORTH_WEST; + if (x == 1 && y == -1) return NORTH_EAST; + if (x == -1 && y == 1) return SOUTH_WEST; + if (x == 1 && y == 1) return SOUTH_EAST; + + if (x == 0 && y == 0) return NONE; + + return new PathStep(x, y); + } + public static final int ION_MARK = 0; public int x; @@ -18,10 +49,8 @@ public class PathStep implements IonBinary { public PathStep(int x, int y) { - this.x = x < 1 ? -1 : x > 0 ? 1 : 0; - this.y = y < 1 ? -1 : y > 0 ? 1 : 0; - - y = (int) Math.signum(x); + this.x = x < 0 ? -1 : x > 0 ? 1 : 0; + this.y = y < 0 ? -1 : y > 0 ? 1 : 0; } diff --git a/src/mightypork/rogue/world/PlayerControl.java b/src/mightypork/rogue/world/PlayerControl.java new file mode 100644 index 0000000..14982ca --- /dev/null +++ b/src/mightypork/rogue/world/PlayerControl.java @@ -0,0 +1,46 @@ +package mightypork.rogue.world; + + +import mightypork.rogue.world.entity.models.EntityMoveListener; + + +public class PlayerControl { + + private final World world; + + + public PlayerControl(World w) + { + this.world = w; + } + + + public void walkNorth() + { + world.playerEntity.addStep(PathStep.NORTH); + } + + + public void walkSouth() + { + world.playerEntity.addStep(PathStep.SOUTH); + } + + + public void walkEast() + { + world.playerEntity.addStep(PathStep.EAST); + } + + + public void walkWest() + { + world.playerEntity.addStep(PathStep.WEST); + } + + + public void addMoveListener(EntityMoveListener eml) + { + world.playerEntity.addMoveListener(eml); + } +} diff --git a/src/mightypork/rogue/world/World.java b/src/mightypork/rogue/world/World.java index 25553aa..a0ca94e 100644 --- a/src/mightypork/rogue/world/World.java +++ b/src/mightypork/rogue/world/World.java @@ -22,7 +22,10 @@ public class World implements IonBundled, Updateable { private final ArrayList levels = new ArrayList<>(); - private final PlayerInfo player = new PlayerInfo(); + final PlayerInfo player = new PlayerInfo(); + Entity playerEntity; + + private final PlayerControl control = new PlayerControl(this); private long seed; // world seed private int eid; // next entity ID @@ -57,7 +60,7 @@ public class World implements IonBundled, Updateable { @Override public void update(double delta) { - getCurrentLevel().update(delta); + getCurrentLevel().update(this, delta); } @@ -89,14 +92,14 @@ public class World implements IonBundled, Updateable { } // make entity - int playerEid = getNewEID(); + final int playerEid = getNewEID(); - final Entity entity = Entities.PLAYER.createEntity(playerEid, new WorldPos(x, y)); + playerEntity = Entities.PLAYER.createEntity(playerEid, new WorldPos(x, y)); player.setLevel(level); player.setEID(playerEid); - levels.get(level).addEntity(entity); + levels.get(level).addEntity(playerEntity); } @@ -108,9 +111,9 @@ public class World implements IonBundled, Updateable { * @param yTiles Desired nr of tiles vertically * @param minSize minimum tile size */ - public void render(RectBound viewport, final int yTiles, final int xTiles, final int minSize) + public void render(RectBound viewport, final int xTiles, final int yTiles, final int minSize) { - getCurrentLevel().render(player, viewport, yTiles, xTiles, minSize); + getCurrentLevel().render(playerEntity.getPosition(), viewport, xTiles, yTiles, minSize); } @@ -118,4 +121,10 @@ public class World implements IonBundled, Updateable { { return levels.get(player.getLevel()); } + + + public PlayerControl getPlayerControl() + { + return control; + } } diff --git a/src/mightypork/rogue/world/WorldPos.java b/src/mightypork/rogue/world/WorldPos.java index bdecdda..d6703ff 100644 --- a/src/mightypork/rogue/world/WorldPos.java +++ b/src/mightypork/rogue/world/WorldPos.java @@ -82,6 +82,18 @@ public class WorldPos implements IonBundled, Updateable { } + public double getVisualXOffset() + { + return walkOffset.x(); + } + + + public double getVisualYOffset() + { + return walkOffset.y(); + } + + public void setTo(int x, int y) { this.x = x; diff --git a/src/mightypork/rogue/world/entity/Entity.java b/src/mightypork/rogue/world/entity/Entity.java index 82ca2b1..abd0f30 100644 --- a/src/mightypork/rogue/world/entity/Entity.java +++ b/src/mightypork/rogue/world/entity/Entity.java @@ -2,14 +2,18 @@ package mightypork.rogue.world.entity; import java.io.IOException; +import java.util.ArrayList; import java.util.LinkedList; +import java.util.List; import java.util.Queue; import mightypork.rogue.world.PathStep; import mightypork.rogue.world.World; import mightypork.rogue.world.WorldPos; import mightypork.rogue.world.entity.models.EntityModel; +import mightypork.rogue.world.entity.models.EntityMoveListener; import mightypork.rogue.world.level.Level; +import mightypork.rogue.world.level.render.EntityRenderContext; import mightypork.util.ion.IonBinary; import mightypork.util.ion.IonBundle; import mightypork.util.ion.IonBundled; @@ -22,23 +26,32 @@ import mightypork.util.ion.IonOutput; * * @author MightyPork */ -public final class Entity implements IonBinary, IonBundled { +public final class Entity implements IonBinary, IonBundled, EntityMoveListener { // binary & bundled - binary stores via a bundle public static final int ION_MARK = 52; - private final WorldPos position = new WorldPos(); + private final WorldPos position = new WorldPos(); // saved /** Entity ID */ - private int eid = 0; + private int eid = 0; // saved /** Model ID */ - private int id; + private int id; // saved - private final Queue path = new LinkedList<>(); + private final Queue path = new LinkedList<>(); // saved private EntityModel model; - private final IonBundle metadata = new IonBundle(); + public final IonBundle metadata = new IonBundle(); // saved + public final IonBundle tmpdata = new IonBundle(); // NOT saved + + // used for rendering "facing" sprite + public int lastXMove = 1; + public int lastYMove = 1; + + private final List moveListeners = new ArrayList<>(); + + private boolean walking = false; public Entity(int eid, WorldPos pos, EntityModel entityModel) @@ -52,6 +65,10 @@ public final class Entity implements IonBinary, IonBundled { private void setModel(EntityModel entityModel) { + // replace listener + if (model != null) moveListeners.remove(model); + moveListeners.add(entityModel); + this.id = entityModel.id; this.model = entityModel; } @@ -93,7 +110,10 @@ public final class Entity implements IonBinary, IonBundled { bundle.putBundled("pos", position); bundle.putSequence("steps", path); bundle.put("eid", eid); - bundle.put("metadata", metadata); + + if (model.hasMetadata()) { + bundle.put("metadata", metadata); + } } @@ -110,11 +130,16 @@ public final class Entity implements IonBinary, IonBundled { bundle.loadSequence("path", path); eid = bundle.get("eid", eid); - metadata.clear(); - bundle.loadBundle("metadata", metadata); + if (model.hasMetadata()) { + metadata.clear(); + bundle.loadBundle("metadata", metadata); + } } + /** + * @return unique entity id + */ public int getEID() { return eid; @@ -150,19 +175,49 @@ public final class Entity implements IonBinary, IonBundled { position.update(delta); } - if (position.isFinished()) { + if (walking && position.isFinished()) { + walking = false; - model.onStepFinished(this, world, level); + onStepFinished(this, world, level); - if (!path.isEmpty()) { - // get next step to walk - final PathStep step = path.poll(); - position.walk(step.x, step.y, getStepTime()); + if (path.isEmpty()) { + onPathFinished(this, world, level); + } + } + + if (!walking && !path.isEmpty()) { + + walking = true; + + final PathStep step = path.poll(); + + final int projX = position.x + step.x, projY = position.y + step.y; + + if (!level.canWalkInto(projX, projY)) { + cancelPath(); + onPathAborted(this, world, level); + walking = false; } else { - // notify AI or whatever - model.onPathFinished(this, world, level); + + // tmp for renderer + if (step.x != 0) lastXMove = step.x; + if (step.y != 0) lastYMove = step.y; + + position.walk(step.x, step.y, getStepTime()); + level.occupyTile(projX, projY); + level.freeTile(position.x, position.y); } } + + if (!walking) { + model.update(this, level, delta); + } + } + + + public void render(EntityRenderContext context) + { + model.renderer.render(this, context); } @@ -189,4 +244,36 @@ public final class Entity implements IonBinary, IonBundled { return model.getStepTime(this); } + + @Override + public void onStepFinished(Entity entity, World world, Level level) + { + for (final EntityMoveListener l : moveListeners) { + l.onStepFinished(entity, world, level); + } + } + + + @Override + public void onPathFinished(Entity entity, World world, Level level) + { + for (final EntityMoveListener l : moveListeners) { + l.onStepFinished(entity, world, level); + } + } + + + @Override + public void onPathAborted(Entity entity, World world, Level level) + { + for (final EntityMoveListener l : moveListeners) { + l.onPathAborted(entity, world, level); + } + } + + + public void addMoveListener(EntityMoveListener listener) + { + moveListeners.add(listener); + } } diff --git a/src/mightypork/rogue/world/entity/models/EntityModel.java b/src/mightypork/rogue/world/entity/models/EntityModel.java index ea0de63..c9c126a 100644 --- a/src/mightypork/rogue/world/entity/models/EntityModel.java +++ b/src/mightypork/rogue/world/entity/models/EntityModel.java @@ -14,7 +14,7 @@ import mightypork.rogue.world.level.Level; * * @author MightyPork */ -public abstract class EntityModel { +public abstract class EntityModel implements EntityMoveListener { /** Model ID */ public final int id; @@ -45,7 +45,7 @@ public abstract class EntityModel { /** - * Update entity + * Entity is idle, waiting for action. */ public abstract void update(Entity entity, Level level, double delta); @@ -57,15 +57,20 @@ public abstract class EntityModel { /** - * @param entity the value is valid for - * @return step time (seconds) + * Get one path step duration (in seconds) */ public abstract double getStepTime(Entity entity); + @Override public abstract void onStepFinished(Entity entity, World world, Level level); + @Override public abstract void onPathFinished(Entity entity, World world, Level level); + + @Override + public abstract void onPathAborted(Entity entity, World world, Level level); + } diff --git a/src/mightypork/rogue/world/entity/models/EntityMoveListener.java b/src/mightypork/rogue/world/entity/models/EntityMoveListener.java new file mode 100644 index 0000000..110e1df --- /dev/null +++ b/src/mightypork/rogue/world/entity/models/EntityMoveListener.java @@ -0,0 +1,28 @@ +package mightypork.rogue.world.entity.models; + + +import mightypork.rogue.world.World; +import mightypork.rogue.world.entity.Entity; +import mightypork.rogue.world.level.Level; + + +public interface EntityMoveListener { + + /** + * One step of a path finished + */ + void onStepFinished(Entity entity, World world, Level level); + + + /** + * Scheduled path finished + */ + void onPathFinished(Entity entity, World world, Level level); + + + /** + * Path was aborted (bumped into a wall or entity) + */ + void onPathAborted(Entity entity, World world, Level level); + +} diff --git a/src/mightypork/rogue/world/entity/models/PlayerModel.java b/src/mightypork/rogue/world/entity/models/PlayerModel.java index 1cf2ca2..937c7fb 100644 --- a/src/mightypork/rogue/world/entity/models/PlayerModel.java +++ b/src/mightypork/rogue/world/entity/models/PlayerModel.java @@ -4,6 +4,7 @@ package mightypork.rogue.world.entity.models; import mightypork.rogue.world.World; import mightypork.rogue.world.WorldPos; import mightypork.rogue.world.entity.Entity; +import mightypork.rogue.world.entity.renderers.PlayerRenderer; import mightypork.rogue.world.level.Level; @@ -20,6 +21,7 @@ public class PlayerModel extends EntityModel { public PlayerModel(int id) { super(id); + setRenderer(new PlayerRenderer("player")); } @@ -64,4 +66,10 @@ public class PlayerModel extends EntityModel { public void onPathFinished(Entity entity, World world, Level level) { } + + + @Override + public void onPathAborted(Entity entity, World world, Level level) + { + } } diff --git a/src/mightypork/rogue/world/entity/renderers/EntityRenderer.java b/src/mightypork/rogue/world/entity/renderers/EntityRenderer.java index 7858a73..3251867 100644 --- a/src/mightypork/rogue/world/entity/renderers/EntityRenderer.java +++ b/src/mightypork/rogue/world/entity/renderers/EntityRenderer.java @@ -1,8 +1,15 @@ package mightypork.rogue.world.entity.renderers; -public class EntityRenderer { +import mightypork.rogue.world.entity.Entity; +import mightypork.rogue.world.level.render.EntityRenderContext; + + +public abstract class EntityRenderer { public static final EntityRenderer NONE = new NullEntityRenderer(); + + public abstract void render(Entity entity, EntityRenderContext context); + } diff --git a/src/mightypork/rogue/world/entity/renderers/NullEntityRenderer.java b/src/mightypork/rogue/world/entity/renderers/NullEntityRenderer.java index 37e9bd2..012d658 100644 --- a/src/mightypork/rogue/world/entity/renderers/NullEntityRenderer.java +++ b/src/mightypork/rogue/world/entity/renderers/NullEntityRenderer.java @@ -1,6 +1,16 @@ package mightypork.rogue.world.entity.renderers; +import mightypork.rogue.world.entity.Entity; +import mightypork.rogue.world.level.render.EntityRenderContext; + + public class NullEntityRenderer extends EntityRenderer { + @Override + public void render(Entity entity, EntityRenderContext context) + { + // hell no + } + } diff --git a/src/mightypork/rogue/world/entity/renderers/PlayerRenderer.java b/src/mightypork/rogue/world/entity/renderers/PlayerRenderer.java new file mode 100644 index 0000000..4d55216 --- /dev/null +++ b/src/mightypork/rogue/world/entity/renderers/PlayerRenderer.java @@ -0,0 +1,43 @@ +package mightypork.rogue.world.entity.renderers; + + +import mightypork.gamecore.render.Render; +import mightypork.gamecore.render.textures.TxQuad; +import mightypork.gamecore.render.textures.TxSheet; +import mightypork.rogue.Res; +import mightypork.rogue.world.WorldPos; +import mightypork.rogue.world.entity.Entity; +import mightypork.rogue.world.level.render.EntityRenderContext; +import mightypork.util.constraints.rect.Rect; +import mightypork.util.math.Calc; + + +public class PlayerRenderer extends EntityRenderer { + + TxSheet sheet; + + + public PlayerRenderer(String sheetKey) + { + this.sheet = Res.getTxSheet(sheetKey); // expects 1x4 + } + + + @Override + public void render(Entity entity, EntityRenderContext context) + { + TxQuad q = sheet.getQuad(Calc.frag(entity.getPosition().getProgress())); + + if (entity.lastXMove == -1) q = q.flipX(); + + final WorldPos pos = entity.getPosition(); + + final Rect tileRect = context.getRectForTile(pos.x, pos.y); + final double w = tileRect.width().value(); + + Rect spriteRect = tileRect.move(pos.getVisualXOffset() * w, pos.getVisualYOffset() * w + w * 0.1); + spriteRect = spriteRect.shrink(w * 0.1); + + Render.quadTextured(spriteRect, q); + } +} diff --git a/src/mightypork/rogue/world/level/Level.java b/src/mightypork/rogue/world/level/Level.java index 0c050d7..83c6367 100644 --- a/src/mightypork/rogue/world/level/Level.java +++ b/src/mightypork/rogue/world/level/Level.java @@ -7,13 +7,12 @@ import java.util.HashSet; import java.util.Map; import java.util.Set; -import org.lwjgl.opengl.GL11; - import mightypork.gamecore.render.Render; import mightypork.rogue.Res; -import mightypork.rogue.world.PlayerInfo; +import mightypork.rogue.world.World; import mightypork.rogue.world.WorldPos; import mightypork.rogue.world.entity.Entity; +import mightypork.rogue.world.level.render.EntityRenderContext; import mightypork.rogue.world.level.render.TileRenderContext; import mightypork.rogue.world.tile.Tile; import mightypork.rogue.world.tile.Tiles; @@ -170,6 +169,11 @@ public class Level implements MapAccess, IonBinary { tiles[y][x].load(in); } } + + // mark tiles as occupied + for (final Entity e : entity_set) { + occupyTile(e.getPosition().x, e.getPosition().y); + } } @@ -201,7 +205,7 @@ public class Level implements MapAccess, IonBinary { } - public void update(double delta) + public void update(World w, double delta) { // just update them all for (int y = 0; y < height; y++) { @@ -209,6 +213,10 @@ public class Level implements MapAccess, IonBinary { getTile(x, y).update(this, delta); } } + + for (final Entity e : entity_set) { + e.update(w, this, delta); + } } @@ -232,16 +240,8 @@ public class Level implements MapAccess, IonBinary { * @param yTiles Desired nr of tiles vertically * @param minSize minimum tile size */ - public void render(PlayerInfo playerInfo, RectBound viewport, final int yTiles, final int xTiles, final int minSize) + public void render(WorldPos pos, RectBound viewport, final int xTiles, final int yTiles, final int minSize) { - final WorldPos pos; - - try { - pos = getEntity(playerInfo.getEID()).getPosition(); - } catch (NullPointerException e) { - throw new RuntimeException("Player entity not found in level.", e); - } - final Rect r = viewport.getRect(); final double vpH = r.height().value(); final double vpW = r.width().value(); @@ -250,9 +250,9 @@ public class Level implements MapAccess, IonBinary { final double allowedSizeW = vpW / xTiles; final double allowedSizeH = vpH / yTiles; - int tileSize = (int) Math.round(Math.max(Math.min(allowedSizeH, allowedSizeW), minSize)); + final int tileSize = (int) Math.round(Math.max(Math.min(allowedSizeH, allowedSizeW), minSize)); - tileSize -= tileSize % 16; + //tileSize -= tileSize % 8; final VectConst vpCenter = r.center().sub(tileSize * 0.5, tileSize).freeze(); // 0.5 to center, 1 to move up (down is teh navbar) @@ -280,7 +280,7 @@ public class Level implements MapAccess, IonBinary { // batch rendering of the tiles if (USE_BATCH_RENDERING) { - Render.enterBatchTexturedQuadMode(Res.getTexture("tiles")); + Render.enterBatchTexturedQuadMode(Res.getTexture("tiles16")); } for (trc.y = y1; trc.y <= y2; trc.y++) { @@ -299,6 +299,12 @@ public class Level implements MapAccess, IonBinary { trc.renderItems(); } } + + // render entities + final EntityRenderContext erc = new EntityRenderContext(this, mapRect); + for (final Entity e : entity_set) { + e.render(erc); + } } @@ -327,4 +333,30 @@ public class Level implements MapAccess, IonBinary { final Entity removed = entity_map.remove(eid); entity_set.remove(removed); } + + + public boolean canWalkInto(int x, int y) + { + final Tile t = getTile(x, y); + + return t.isWalkable() && !t.isOccupied(); + } + + + /** + * Mark tile as occupied by an entity + */ + public void occupyTile(int x, int y) + { + getTile(x, y).setOccupied(true); + } + + + /** + * Mark tile as free (no longet occupied) + */ + public void freeTile(int x, int y) + { + getTile(x, y).setOccupied(false); + } } diff --git a/src/mightypork/rogue/world/level/render/TileRenderContext.java b/src/mightypork/rogue/world/level/render/TileRenderContext.java index f5e79b8..8f9aa42 100644 --- a/src/mightypork/rogue/world/level/render/TileRenderContext.java +++ b/src/mightypork/rogue/world/level/render/TileRenderContext.java @@ -65,12 +65,14 @@ public final class TileRenderContext extends MapRenderContext implements RectBou { map.getTile(x, y).renderTile(this); } - + + public void renderItems() { map.getTile(x, y).renderItems(this); } + /** * Rect of the current tile to draw */ diff --git a/src/mightypork/rogue/world/tile/Tile.java b/src/mightypork/rogue/world/tile/Tile.java index 42b962b..169b6bb 100644 --- a/src/mightypork/rogue/world/tile/Tile.java +++ b/src/mightypork/rogue/world/tile/Tile.java @@ -34,6 +34,13 @@ public final class Tile implements IonBinary { /** persistent field for model, reflected by renderer */ public final IonBundle metadata = new IonBundle(); + // temporary flag for map. + private boolean occupied; + + // for renderer of AO shadows + public byte shadows; + public boolean shadowsComputed; + public Tile(int id) { @@ -75,6 +82,7 @@ public final class Tile implements IonBinary { /** * Render items + * * @param context */ public void renderItems(TileRenderContext context) @@ -85,7 +93,6 @@ public final class Tile implements IonBinary { } - @Override public void save(IonOutput out) throws IOException { @@ -156,4 +163,16 @@ public final class Tile implements IonBinary { { return ION_MARK; } + + + public boolean isOccupied() + { + return occupied; + } + + + public void setOccupied(boolean occupied) + { + this.occupied = occupied; + } } diff --git a/src/mightypork/rogue/world/tile/Tiles.java b/src/mightypork/rogue/world/tile/Tiles.java index 65597af..d3bcb06 100644 --- a/src/mightypork/rogue/world/tile/Tiles.java +++ b/src/mightypork/rogue/world/tile/Tiles.java @@ -20,21 +20,24 @@ public final class Tiles { public static final TileModel NULL_SOLID = new NullWall(0); public static final TileModel NULL_EMPTY = new NullFloor(1); - public static final TileModel BRICK_FLOOR_VINES = new Floor(2).setTexture("tile.floor.mossy_bricks"); - public static final TileModel BRICK_WALL_VINES = new Wall(3).setTexture("tile.wall.mossy_bricks"); + public static final TileModel FLOOR_DARK = new Floor(2).setTexture("tile16.floor.dark"); + public static final TileModel WALL_BRICK = new Wall(3).setTexture("tile16.wall.brick"); - public static final TileModel BRICK_FLOOR_RECT = new Floor(4).setTexture("tile.floor.rect_bricks"); - public static final TileModel BRICK_WALL_SMALL = new Wall(5).setTexture("tile.wall.small_bricks"); - - public static final TileModel SANDSTONE_FLOOR = new Floor(6).setTexture("tile.floor.sandstone"); - public static final TileModel SANDSTONE_WALL = new Wall(7).setTexture("tile.wall.sandstone"); - - public static final TileModel BRCOBBLE_FLOOR = new Floor(8).setTexture("tile.floor.brown_cobble"); - public static final TileModel BRCOBBLE_WALL = new Wall(9).setTexture("tile.wall.brown_cobble"); - - public static final TileModel CRYSTAL_FLOOR = new Floor(10).setTexture("tile.floor.crystal"); - public static final TileModel CRYSTAL_WALL = new Wall(11).setTexture("tile.wall.crystal"); +// public static final TileModel BRICK_FLOOR_VINES = new Floor(2).setTexture("tile.floor.mossy_bricks"); +// public static final TileModel BRICK_WALL_VINES = new Wall(3).setTexture("tile.wall.mossy_bricks"); +// +// public static final TileModel BRICK_FLOOR_RECT = new Floor(4).setTexture("tile.floor.rect_bricks"); +// public static final TileModel BRICK_WALL_SMALL = new Wall(5).setTexture("tile.wall.small_bricks"); +// +// public static final TileModel SANDSTONE_FLOOR = new Floor(6).setTexture("tile.floor.sandstone"); +// public static final TileModel SANDSTONE_WALL = new Wall(7).setTexture("tile.wall.sandstone"); +// +// public static final TileModel BRCOBBLE_FLOOR = new Floor(8).setTexture("tile.floor.brown_cobble"); +// public static final TileModel BRCOBBLE_WALL = new Wall(9).setTexture("tile.wall.brown_cobble"); +// +// public static final TileModel CRYSTAL_FLOOR = new Floor(10).setTexture("tile.floor.crystal"); +// public static final TileModel CRYSTAL_WALL = new Wall(11).setTexture("tile.wall.crystal"); public static void register(int id, TileModel model) { diff --git a/src/mightypork/rogue/world/tile/models/AbstractNullTile.java b/src/mightypork/rogue/world/tile/models/AbstractNullTile.java index 0e26c70..d9cc8a9 100644 --- a/src/mightypork/rogue/world/tile/models/AbstractNullTile.java +++ b/src/mightypork/rogue/world/tile/models/AbstractNullTile.java @@ -55,4 +55,10 @@ public abstract class AbstractNullTile extends SimpleTile { @Override public abstract boolean isWalkable(); + + @Override + public boolean doesCastShadow() + { + return false; + } } diff --git a/src/mightypork/rogue/world/tile/models/Floor.java b/src/mightypork/rogue/world/tile/models/Floor.java index 6baf9bd..636bc45 100644 --- a/src/mightypork/rogue/world/tile/models/Floor.java +++ b/src/mightypork/rogue/world/tile/models/Floor.java @@ -27,4 +27,10 @@ public class Floor extends SimpleTile { return true; } + + @Override + public boolean doesCastShadow() + { + return false; + } } diff --git a/src/mightypork/rogue/world/tile/models/TileModel.java b/src/mightypork/rogue/world/tile/models/TileModel.java index 9d3147b..f3bfef2 100644 --- a/src/mightypork/rogue/world/tile/models/TileModel.java +++ b/src/mightypork/rogue/world/tile/models/TileModel.java @@ -63,6 +63,9 @@ public abstract class TileModel { public abstract boolean isWalkable(); + public abstract boolean doesCastShadow(); + + public boolean isNullTile() { return false; diff --git a/src/mightypork/rogue/world/tile/models/Wall.java b/src/mightypork/rogue/world/tile/models/Wall.java index c18d431..36ce59e 100644 --- a/src/mightypork/rogue/world/tile/models/Wall.java +++ b/src/mightypork/rogue/world/tile/models/Wall.java @@ -27,4 +27,11 @@ public class Wall extends SimpleTile { return false; } + + @Override + public boolean doesCastShadow() + { + return true; + } + } diff --git a/src/mightypork/rogue/world/tile/renderers/BasicTileRenderer.java b/src/mightypork/rogue/world/tile/renderers/BasicTileRenderer.java index 7ca2e71..bc0e9d1 100644 --- a/src/mightypork/rogue/world/tile/renderers/BasicTileRenderer.java +++ b/src/mightypork/rogue/world/tile/renderers/BasicTileRenderer.java @@ -2,25 +2,85 @@ package mightypork.rogue.world.tile.renderers; import mightypork.gamecore.render.Render; +import mightypork.gamecore.render.textures.TxQuad; import mightypork.gamecore.render.textures.TxSheet; import mightypork.rogue.Res; import mightypork.rogue.world.level.render.TileRenderContext; +import mightypork.rogue.world.tile.Tile; +import mightypork.util.constraints.rect.Rect; public class BasicTileRenderer extends TileRenderer { private final TxSheet sheet; + private static boolean inited; + private static TxQuad SH_N, SH_S, SH_E, SH_W, SH_NW, SH_NE, SH_SW, SH_SE; + public BasicTileRenderer(String sheetKey) { this.sheet = Res.getTxSheet(sheetKey); + + if (!inited) { + SH_N = Res.getTxQuad("tile16.shadow.n"); + SH_S = Res.getTxQuad("tile16.shadow.s"); + SH_E = Res.getTxQuad("tile16.shadow.e"); + SH_W = Res.getTxQuad("tile16.shadow.w"); + SH_NW = Res.getTxQuad("tile16.shadow.nw"); + SH_NE = Res.getTxQuad("tile16.shadow.ne"); + SH_SW = Res.getTxQuad("tile16.shadow.sw"); + SH_SE = Res.getTxQuad("tile16.shadow.se"); + } } @Override public void render(TileRenderContext context) { - Render.quadTextured(context.getRect(), sheet.getRandomQuad(context.getTileNoise())); + final Rect rect = context.getRect(); + Render.quadTextured(rect, sheet.getRandomQuad(context.getTileNoise())); + + final Tile t = context.getTile(); + + if (t.getModel().doesCastShadow()) return; // no shadows for wall + + Tile t2; + + if (!t.shadowsComputed) { + // no shadows computed yet + + t.shadows = 0; // reset the mask + + int move = 0; + for (int y = -1; y <= 1; y++) { + for (int x = -1; x <= 1; x++) { + if (x == 0 && y == 0) continue; + + t2 = context.getAdjacentTile(x, y); + + if (t2.getModel().doesCastShadow()) { + t.shadows |= 1 << move; + } + + move++; + } + } + + t.shadowsComputed = true; + } + + if (t.shadows == 0) return; + + if ((t.shadows & (1 << 0)) != 0) Render.quadTextured(rect, SH_NW); + if ((t.shadows & (1 << 1)) != 0) Render.quadTextured(rect, SH_N); + if ((t.shadows & (1 << 2)) != 0) Render.quadTextured(rect, SH_NE); + + if ((t.shadows & (1 << 3)) != 0) Render.quadTextured(rect, SH_W); + if ((t.shadows & (1 << 4)) != 0) Render.quadTextured(rect, SH_E); + + if ((t.shadows & (1 << 5)) != 0) Render.quadTextured(rect, SH_SW); + if ((t.shadows & (1 << 6)) != 0) Render.quadTextured(rect, SH_S); + if ((t.shadows & (1 << 7)) != 0) Render.quadTextured(rect, SH_SE); } }