diff --git a/src/main/java/minicraft/core/Renderer.java b/src/main/java/minicraft/core/Renderer.java index ec6ed5c01..f1b86a374 100644 --- a/src/main/java/minicraft/core/Renderer.java +++ b/src/main/java/minicraft/core/Renderer.java @@ -29,10 +29,10 @@ import minicraft.gfx.Ellipsis; import minicraft.gfx.Font; import minicraft.gfx.FontStyle; +import minicraft.gfx.MinicraftImage; import minicraft.gfx.Point; import minicraft.gfx.Screen; import minicraft.gfx.SpriteLinker; -import minicraft.gfx.SpriteSheet; import minicraft.item.Items; import minicraft.item.PotionType; import minicraft.item.ToolItem; @@ -76,11 +76,11 @@ private Renderer() {} private static LinkedSprite hudSheet; - public static SpriteSheet loadDefaultSkinSheet() { - SpriteSheet skinsSheet; + public static MinicraftImage loadDefaultSkinSheet() { + MinicraftImage skinsSheet; try { // These set the sprites to be used. - skinsSheet = new SpriteSheet(ImageIO.read(Objects.requireNonNull(Game.class.getResourceAsStream("/resources/textures/skins.png")))); + skinsSheet = new MinicraftImage(ImageIO.read(Objects.requireNonNull(Game.class.getResourceAsStream("/resources/textures/skins.png")))); } catch (NullPointerException e) { // If a provided InputStream has no name. (in practice meaning it cannot be found.) CrashHandler.crashHandle(e, new ErrorInfo("Sprite Sheet Not Found", ErrorInfo.ErrorType.UNEXPECTED, true, "A sprite sheet was not found.")); @@ -97,9 +97,8 @@ public static SpriteSheet loadDefaultSkinSheet() { public static void initScreen() { ResourcePackDisplay.initPacks(); ResourcePackDisplay.reloadResources(); - SpriteSheet sheet = loadDefaultSkinSheet(); - screen = new Screen(sheet); - lightScreen = new Screen(sheet); + screen = new Screen(); + lightScreen = new Screen(); image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB); screen.pixels = ((DataBufferInt) image.getRaster().getDataBuffer()).getData(); @@ -194,11 +193,11 @@ private static void renderLevel() { if (xScroll > level.w * 16 - Screen.w) xScroll = level.w * 16 - Screen.w; // ...Right border. if (yScroll > level.h * 16 - Screen.h) yScroll = level.h * 16 - Screen.h; // ...Bottom border. if (currentLevel > 3) { // If the current level is higher than 3 (which only the sky level (and dungeon) is) - SpriteSheet cloud = spriteLinker.getSpriteSheet(SpriteType.Tile, "cloud"); + MinicraftImage cloud = spriteLinker.getSheet(SpriteType.Tile, "cloud_background"); for (int y = 0; y < 28; y++) for (int x = 0; x < 48; x++) { // Creates the background for the sky (and dungeon) level: - screen.render(x * 8 - ((xScroll / 4) & 7), y * 8 - ((yScroll / 4) & 7), 2, 3, 0, cloud); + screen.render(x * 8 - ((xScroll / 4) & 7), y * 8 - ((yScroll / 4) & 7), 0, 0, 0, cloud); } } @@ -351,7 +350,7 @@ private static void renderGui() { // Renders armor int armor = player.armor * Player.maxStat / Player.maxArmor; if (i <= armor && player.curArmor != null) { - player.curArmor.sprite.getSprite().render(screen, i * 8, Screen.h - 24); + screen.render(i * 8, Screen.h - 24, player.curArmor.sprite); } // Renders your current red hearts, or black hearts for damaged health. diff --git a/src/main/java/minicraft/core/io/Localization.java b/src/main/java/minicraft/core/io/Localization.java index e62a12009..8f2bbef60 100644 --- a/src/main/java/minicraft/core/io/Localization.java +++ b/src/main/java/minicraft/core/io/Localization.java @@ -7,11 +7,9 @@ import org.jetbrains.annotations.NotNull; -import minicraft.core.CrashHandler; import minicraft.core.Game; import minicraft.util.Logging; -import org.json.JSONException; import org.json.JSONObject; import org.tinylog.Logger; diff --git a/src/main/java/minicraft/entity/Arrow.java b/src/main/java/minicraft/entity/Arrow.java index bfd8036eb..67c62c4d6 100644 --- a/src/main/java/minicraft/entity/Arrow.java +++ b/src/main/java/minicraft/entity/Arrow.java @@ -2,19 +2,23 @@ import java.util.List; -import minicraft.core.Renderer; +import javax.security.auth.DestroyFailedException; + import minicraft.entity.mob.Mob; import minicraft.entity.mob.Player; import minicraft.gfx.Color; import minicraft.gfx.Rectangle; import minicraft.gfx.Screen; +import minicraft.gfx.SpriteLinker.LinkedSprite; import minicraft.gfx.SpriteLinker.SpriteType; +import minicraft.util.Logging; public class Arrow extends Entity implements ClientTickable { private Direction dir; private int damage; public Mob owner; private int speed; + private LinkedSprite sprite = new LinkedSprite(SpriteType.Entity, "arrow").setSpriteSize(1, 1); public Arrow(Mob owner, Direction dir, int dmg) { this(owner, owner.x, owner.y, dir, dmg); @@ -29,6 +33,12 @@ public Arrow(Mob owner, int x, int y, Direction dir, int dmg) { damage = dmg; col = Color.get(-1, 111, 222, 430); + int xt = 0; + if(dir == Direction.LEFT) xt = 1; + if(dir == Direction.UP) xt = 2; + if(dir == Direction.DOWN) xt = 3; + sprite.setSpritePos(xt, 0); + if (damage > 3) speed = 8; else if (damage >= 0) speed = 7; else speed = 6; @@ -67,6 +77,11 @@ public void tick() { && !level.getTile(x / 16, y / 16).connectsToFluid && level.getTile(x / 16, y / 16).id != 16) { this.remove(); + try { + sprite.destroy(); + } catch (DestroyFailedException e) { + Logging.SPRITE.trace(e); + } } } } @@ -77,12 +92,6 @@ public boolean isSolid() { @Override public void render(Screen screen) { - int xt = 0; - - if(dir == Direction.LEFT) xt = 1; - if(dir == Direction.UP) xt = 2; - if(dir == Direction.DOWN) xt = 3; - - screen.render(x - 4, y - 4, xt, 0, 0, Renderer.spriteLinker.getSpriteSheet(SpriteType.Entity, "arrow")); + screen.render(x - 4, y - 4, sprite); } } diff --git a/src/main/java/minicraft/entity/ItemEntity.java b/src/main/java/minicraft/entity/ItemEntity.java index 9c17f31f8..88b700d31 100644 --- a/src/main/java/minicraft/entity/ItemEntity.java +++ b/src/main/java/minicraft/entity/ItemEntity.java @@ -17,7 +17,7 @@ public class ItemEntity extends Entity implements ClientTickable { // Solely for multiplayer use. private boolean pickedUp = false; private long pickupTimestamp; - + /** * Creates an item entity of the item item at position (x,y) with size 2*2. * @param item Item to add as item entity @@ -127,8 +127,9 @@ public void render(Screen screen) { if (time >= lifeTime - 6 * 20) { if (time / 6 % 2 == 0) return; } - item.sprite.getSprite().render(screen, x-4, y - 4, 4, -1, Color.get(0, 31)); // Item shadow - item.sprite.getSprite().render(screen, x - 4, y - 4 - (int)(zz)); // Item + + screen.render(x-4, y - 4, item.sprite.getSprite(), 0, false, Color.get(0, 31)); // Item shadow + screen.render(x - 4, y - 4 - (int) zz, item.sprite); // Item } @Override diff --git a/src/main/java/minicraft/entity/Spark.java b/src/main/java/minicraft/entity/Spark.java index 1f5ebe731..6b0df57fd 100644 --- a/src/main/java/minicraft/entity/Spark.java +++ b/src/main/java/minicraft/entity/Spark.java @@ -3,12 +3,12 @@ import java.util.List; import minicraft.core.Game; -import minicraft.core.Renderer; import minicraft.entity.mob.AirWizard; import minicraft.entity.mob.Mob; import minicraft.gfx.Color; import minicraft.gfx.Rectangle; import minicraft.gfx.Screen; +import minicraft.gfx.SpriteLinker.LinkedSprite; import minicraft.gfx.SpriteLinker.SpriteType; public class Spark extends Entity { @@ -17,6 +17,7 @@ public class Spark extends Entity { private double xx, yy; // The x and y positions private int time; // The amount of time that has passed private final AirWizard owner; // The AirWizard that created this spark + private LinkedSprite sprite = new LinkedSprite(SpriteType.Entity, "spark"); /** * Creates a new spark. Owner is the AirWizard which is spawning this spark. @@ -75,9 +76,10 @@ public void render(Screen screen) { randmirror = random.nextInt(4); } - - screen.render(x - 4, y - 4 + 2, 0, 0, randmirror, Renderer.spriteLinker.getSpriteSheet(SpriteType.Entity, "spark"), -1, false, Color.BLACK); // renders the shadow on the ground - screen.render(x - 4, y - 4 - 2, 0, 0, randmirror, Renderer.spriteLinker.getSpriteSheet(SpriteType.Entity, "spark")); // Renders the spark + + sprite.setMirror(randmirror); + screen.render(x - 4, y - 4 + 2, sprite.getSprite(), 0, false, Color.BLACK); // renders the shadow on the ground + screen.render(x - 4, y - 4 - 2, sprite); // Renders the spark } /** diff --git a/src/main/java/minicraft/entity/furniture/Furniture.java b/src/main/java/minicraft/entity/furniture/Furniture.java index 55e6b303d..3b958e243 100644 --- a/src/main/java/minicraft/entity/furniture/Furniture.java +++ b/src/main/java/minicraft/entity/furniture/Furniture.java @@ -68,7 +68,7 @@ public void tick() { } /** Draws the furniture on the screen. */ - public void render(Screen screen) { sprite.getSprite().render(screen, x-8, y-8); } + public void render(Screen screen) { screen.render(x-8, y-8, sprite); } /** Called when the player presses the MENU key in front of this. */ public boolean use(Player player) { return false; } diff --git a/src/main/java/minicraft/entity/mob/AirWizard.java b/src/main/java/minicraft/entity/mob/AirWizard.java index c18aa0139..e66148d2d 100644 --- a/src/main/java/minicraft/entity/mob/AirWizard.java +++ b/src/main/java/minicraft/entity/mob/AirWizard.java @@ -11,14 +11,13 @@ import minicraft.gfx.Font; import minicraft.gfx.Screen; import minicraft.gfx.SpriteLinker.LinkedSprite; -import minicraft.gfx.SpriteLinker.SpriteType; import minicraft.network.Analytics; import minicraft.screen.AchievementsDisplay; public class AirWizard extends EnemyMob { - private static final LinkedSprite[] sprites = new LinkedSprite[] { - new LinkedSprite(SpriteType.Entity, "air_wizard").setSpritePos(0, 0), - new LinkedSprite(SpriteType.Entity, "air_wizard").setSpritePos(0, 2) + private static final LinkedSprite[][][] sprites = new LinkedSprite[][][] { + Mob.compileMobSpriteAnimations(0, 0, "air_wizard"), + Mob.compileMobSpriteAnimations(0, 2, "air_wizard") }; public static boolean beaten = false; diff --git a/src/main/java/minicraft/entity/mob/Cow.java b/src/main/java/minicraft/entity/mob/Cow.java index c600636d1..1b625a6e3 100644 --- a/src/main/java/minicraft/entity/mob/Cow.java +++ b/src/main/java/minicraft/entity/mob/Cow.java @@ -2,11 +2,10 @@ import minicraft.core.io.Settings; import minicraft.gfx.SpriteLinker.LinkedSprite; -import minicraft.gfx.SpriteLinker.SpriteType; import minicraft.item.Items; public class Cow extends PassiveMob { - private static LinkedSprite sprites = new LinkedSprite(SpriteType.Entity, "cow"); + private static LinkedSprite[][] sprites = Mob.compileMobSpriteAnimations(0, 0, "cow"); /** * Creates the cow with the right sprites and color. diff --git a/src/main/java/minicraft/entity/mob/Creeper.java b/src/main/java/minicraft/entity/mob/Creeper.java index d19276330..acaa6810d 100644 --- a/src/main/java/minicraft/entity/mob/Creeper.java +++ b/src/main/java/minicraft/entity/mob/Creeper.java @@ -9,7 +9,6 @@ import minicraft.gfx.Point; import minicraft.gfx.Screen; import minicraft.gfx.SpriteLinker.LinkedSprite; -import minicraft.gfx.SpriteLinker.SpriteType; import minicraft.item.Items; import minicraft.level.tile.Tiles; @@ -17,11 +16,11 @@ import java.util.List; public class Creeper extends EnemyMob { - private static LinkedSprite[] sprites = new LinkedSprite[] { - new LinkedSprite(SpriteType.Entity, "creeper").setSpriteDim(0, 0, 2, 2).setSpriteList(2), - new LinkedSprite(SpriteType.Entity, "creeper").setSpriteDim(0, 2, 2, 2).setSpriteList(2), - new LinkedSprite(SpriteType.Entity, "creeper").setSpriteDim(0, 4, 2, 2).setSpriteList(2), - new LinkedSprite(SpriteType.Entity, "creeper").setSpriteDim(0, 6, 2, 2).setSpriteList(2) + private static LinkedSprite[][][] sprites = new LinkedSprite[][][] { + new LinkedSprite[][] {Mob.compileSpriteList(0, 0, 2, 2, 0, 2, "creeper")}, + new LinkedSprite[][] {Mob.compileSpriteList(0, 2, 2, 2, 0, 2, "creeper")}, + new LinkedSprite[][] {Mob.compileSpriteList(0, 4, 2, 2, 0, 2, "creeper")}, + new LinkedSprite[][] {Mob.compileSpriteList(0, 6, 2, 2, 0, 2, "creeper")} }; private static final int MAX_FUSE_TIME = 60; diff --git a/src/main/java/minicraft/entity/mob/EnemyMob.java b/src/main/java/minicraft/entity/mob/EnemyMob.java index 646d5e1cb..70d8d6cc8 100644 --- a/src/main/java/minicraft/entity/mob/EnemyMob.java +++ b/src/main/java/minicraft/entity/mob/EnemyMob.java @@ -14,7 +14,7 @@ public class EnemyMob extends MobAi { public int lvl; - protected LinkedSprite[] lvlSprites; + protected LinkedSprite[][][] lvlSprites; public int detectDist; /** @@ -30,7 +30,7 @@ public class EnemyMob extends MobAi { * @param rwTime How long the mob will walk in a random direction. (random walk duration) * @param rwChance The chance of this mob will walk in a random direction (random walk chance) */ - public EnemyMob(int lvl, LinkedSprite[] lvlSprites, int health, boolean isFactor, int detectDist, int lifetime, int rwTime, int rwChance) { + public EnemyMob(int lvl, LinkedSprite[][][] lvlSprites, int health, boolean isFactor, int detectDist, int lifetime, int rwTime, int rwChance) { super(lvlSprites[0], isFactor ? (lvl == 0 ? 1 : lvl * lvl) * health * ((Double)(Math.pow(2, Settings.getIdx("diff")))).intValue() : health, lifetime, rwTime, rwChance); this.lvl = lvl == 0 ? 1 : lvl; this.lvlSprites = java.util.Arrays.copyOf(lvlSprites, lvlSprites.length); @@ -48,7 +48,7 @@ public EnemyMob(int lvl, LinkedSprite[] lvlSprites, int health, boolean isFactor * @param rwTime How long the mob will walk in a random direction. (random walk duration) * @param rwChance The chance of this mob will walk in a random direction (random walk chance) */ - public EnemyMob(int lvl, LinkedSprite[] lvlSprites, int health, boolean isFactor, int detectDist, int rwTime, int rwChance) { + public EnemyMob(int lvl, LinkedSprite[][][] lvlSprites, int health, boolean isFactor, int detectDist, int rwTime, int rwChance) { this(lvl, lvlSprites, health, isFactor, detectDist, 60 * Updater.normSpeed, rwTime, rwChance); } @@ -63,7 +63,7 @@ public EnemyMob(int lvl, LinkedSprite[] lvlSprites, int health, boolean isFactor * @param health How much health the mob has. * @param detectDist The distance where the mob will detect the player and start moving towards him/her. */ - public EnemyMob(int lvl, LinkedSprite[] lvlSprites, int health, int detectDist) { + public EnemyMob(int lvl, LinkedSprite[][][] lvlSprites, int health, int detectDist) { this(lvl, lvlSprites, health, true, detectDist, 60, 200); } diff --git a/src/main/java/minicraft/entity/mob/Knight.java b/src/main/java/minicraft/entity/mob/Knight.java index b68bbef94..91b74df0f 100644 --- a/src/main/java/minicraft/entity/mob/Knight.java +++ b/src/main/java/minicraft/entity/mob/Knight.java @@ -2,15 +2,14 @@ import minicraft.core.io.Settings; import minicraft.gfx.SpriteLinker.LinkedSprite; -import minicraft.gfx.SpriteLinker.SpriteType; import minicraft.item.Items; public class Knight extends EnemyMob { - private static LinkedSprite[] sprites = new LinkedSprite[] { - new LinkedSprite(SpriteType.Entity, "creeper").setSpritePos(0, 0), - new LinkedSprite(SpriteType.Entity, "creeper").setSpritePos(0, 2), - new LinkedSprite(SpriteType.Entity, "creeper").setSpritePos(0, 4), - new LinkedSprite(SpriteType.Entity, "creeper").setSpritePos(0, 6) + private static LinkedSprite[][][] sprites = new LinkedSprite[][][] { + Mob.compileMobSpriteAnimations(0, 0, "knight"), + Mob.compileMobSpriteAnimations(0, 2, "knight"), + Mob.compileMobSpriteAnimations(0, 4, "knight"), + Mob.compileMobSpriteAnimations(0, 6, "knight") }; /** diff --git a/src/main/java/minicraft/entity/mob/Mob.java b/src/main/java/minicraft/entity/mob/Mob.java index 077446dae..a792f4024 100644 --- a/src/main/java/minicraft/entity/mob/Mob.java +++ b/src/main/java/minicraft/entity/mob/Mob.java @@ -7,13 +7,14 @@ import minicraft.entity.particle.TextParticle; import minicraft.gfx.Color; import minicraft.gfx.SpriteLinker.LinkedSprite; +import minicraft.gfx.SpriteLinker.SpriteType; import minicraft.item.PotionType; import minicraft.level.tile.Tile; import minicraft.level.tile.Tiles; public abstract class Mob extends Entity { - protected LinkedSprite sprites; // This contains all the mob's sprites, sorted first by direction (index corresponding to the dir variable), and then by walk animation state. + protected LinkedSprite[][] sprites; // This contains all the mob's sprites, sorted first by direction (index corresponding to the dir variable), and then by walk animation state. public int walkDist = 0; // How far we've walked currently, incremented after each movement. This is used to change the sprite; "(walkDist >> 3) & 1" switches between a value of 0 and 1 every 8 increments of walkDist. public Direction dir = Direction.DOWN; // The direction the mob is facing, used in attacking and rendering. 0 is down, 1 is up, 2 is left, 3 is right @@ -31,7 +32,7 @@ public abstract class Mob extends Entity { * @param sprites All of this mob's sprites. * @param health The mob's max health. */ - public Mob(LinkedSprite sprites, int health) { + public Mob(LinkedSprite[][] sprites, int health) { super(4, 3); this.sprites = sprites; this.health = this.maxHealth = health; @@ -73,7 +74,9 @@ public void tick() { private boolean move(int xd, int yd, boolean changeDir) { // Knockback shouldn't change mob direction if (level == null) return false; // Stopped b/c there's no level to move in! + @SuppressWarnings("unused") int oldxt = x >> 4; + @SuppressWarnings("unused") int oldyt = y >> 4; // These should return true b/c the mob is still technically moving; these are just to make it move *slower*. @@ -103,6 +106,46 @@ private boolean move(int xd, int yd, boolean changeDir) { // Knockback shouldn't return moved; } + /** This is an easy way to make a list of sprites that are all part of the same "Sprite", so they have similar parameters, but they're just at different locations on the spreadsheet. */ + public static LinkedSprite[] compileSpriteList(int sheetX, int sheetY, int width, int height, int mirror, int number, String key) { + LinkedSprite[] sprites = new LinkedSprite[number]; + for (int i = 0; i < sprites.length; i++) + sprites[i] = new LinkedSprite(SpriteType.Entity, key).setSpriteDim(sheetX + width * i, sheetY, width, height) + .setMirror(mirror).setFlip(mirror); + + return sprites; + } + + public static LinkedSprite[][] compileMobSpriteAnimations(int sheetX, int sheetY, String key) { + LinkedSprite[][] sprites = new LinkedSprite[4][2]; + // dir numbers: 0=down, 1=up, 2=left, 3=right. + /// On the spritesheet, most mobs have 4 sprites there, first facing down, then up, then right 1, then right 2. The first two get flipped to animate them, but the last two get flipped to change direction. + + // Contents: down 1, up 1, right 1, right 2 + LinkedSprite[] set1 = compileSpriteList(sheetX, sheetY, 2, 2, 0, 4, key); + + // Contents: down 2, up 2, left 1, left 2 + LinkedSprite[] set2 = compileSpriteList(sheetX, sheetY, 2, 2, 1, 4, key); + + // Down + sprites[0][0] = set1[0]; + sprites[0][1] = set2[0]; + + // Up + sprites[1][0] = set1[1]; + sprites[1][1] = set2[1]; + + // Left + sprites[2][0] = set2[2]; + sprites[2][1] = set2[3]; + + // Right + sprites[3][0] = set1[2]; + sprites[3][1] = set1[3]; + + return sprites; + } + private boolean isWooling() { // supposed to walk at half speed on wool if (level == null) return false; Tile tile = level.getTile(x >> 4, y >> 4); diff --git a/src/main/java/minicraft/entity/mob/MobAi.java b/src/main/java/minicraft/entity/mob/MobAi.java index 8d90f1f0e..92b4c0e68 100644 --- a/src/main/java/minicraft/entity/mob/MobAi.java +++ b/src/main/java/minicraft/entity/mob/MobAi.java @@ -5,7 +5,6 @@ import minicraft.entity.Entity; import minicraft.entity.particle.TextParticle; import minicraft.gfx.Color; -import minicraft.gfx.MobSprite; import minicraft.gfx.Rectangle; import minicraft.gfx.Screen; import minicraft.gfx.SpriteLinker.LinkedSprite; @@ -30,7 +29,7 @@ public abstract class MobAi extends Mob { * @param rwTime How long the mob will walk in a random direction. (random walk duration) * @param rwChance The chance of this mob will walk in a random direction (random walk chance) */ - protected MobAi(LinkedSprite sprites, int maxHealth, int lifetime, int rwTime, int rwChance) { + protected MobAi(LinkedSprite[][] sprites, int maxHealth, int lifetime, int rwTime, int rwChance) { super(sprites, maxHealth); this.lifetime = lifetime; randomWalkTime = 0; @@ -96,12 +95,11 @@ public void render(Screen screen) { int xo = x - 8; int yo = y - 11; - MobSprite[][] sprites = this.sprites.getMobSprites(); - MobSprite curSprite = sprites[dir.getDir()][(walkDist >> 3) % sprites[dir.getDir()].length]; + LinkedSprite curSprite = sprites[dir.getDir()][(walkDist >> 3) % sprites[dir.getDir()].length]; if (hurtTime > 0) { - curSprite.render(screen, xo, yo, true); + screen.render(xo, yo, curSprite.getSprite(), true); } else { - curSprite.render(screen, xo, yo); + screen.render(xo, yo, curSprite.getSprite()); } } diff --git a/src/main/java/minicraft/entity/mob/PassiveMob.java b/src/main/java/minicraft/entity/mob/PassiveMob.java index 056d64ecd..b05ae5a12 100644 --- a/src/main/java/minicraft/entity/mob/PassiveMob.java +++ b/src/main/java/minicraft/entity/mob/PassiveMob.java @@ -17,7 +17,7 @@ public class PassiveMob extends MobAi { * healthFactor = 3. * @param sprites The mob's sprites. */ - public PassiveMob(LinkedSprite sprites) { + public PassiveMob(LinkedSprite[][] sprites) { this(sprites, 3); } @@ -27,7 +27,7 @@ public PassiveMob(LinkedSprite sprites) { * @param healthFactor Determines the mobs health. Will be multiplied by the difficulty * and then added with 5. */ - public PassiveMob(LinkedSprite sprites, int healthFactor) { + public PassiveMob(LinkedSprite[][] sprites, int healthFactor) { super(sprites, 5 + healthFactor * Settings.getIdx("diff"), 5*60*Updater.normSpeed, 45, 40); } diff --git a/src/main/java/minicraft/entity/mob/Pig.java b/src/main/java/minicraft/entity/mob/Pig.java index ce4370c72..1cc2ee567 100644 --- a/src/main/java/minicraft/entity/mob/Pig.java +++ b/src/main/java/minicraft/entity/mob/Pig.java @@ -2,11 +2,10 @@ import minicraft.core.io.Settings; import minicraft.gfx.SpriteLinker.LinkedSprite; -import minicraft.gfx.SpriteLinker.SpriteType; import minicraft.item.Items; public class Pig extends PassiveMob { - private static LinkedSprite sprites = new LinkedSprite(SpriteType.Entity, "pig"); + private static LinkedSprite[][] sprites = Mob.compileMobSpriteAnimations(0, 0, "pig"); /** * Creates a pig. diff --git a/src/main/java/minicraft/entity/mob/Player.java b/src/main/java/minicraft/entity/mob/Player.java index 3a11d574e..89fb5e713 100644 --- a/src/main/java/minicraft/entity/mob/Player.java +++ b/src/main/java/minicraft/entity/mob/Player.java @@ -53,8 +53,8 @@ public class Player extends Mob implements ItemHolder, ClientTickable { public static final int maxHealth = maxStat, maxStamina = maxStat, maxHunger = maxStat; public static final int maxArmor = 100; - public static MobSprite[][] sprites; - public static MobSprite[][] carrySprites; + public static LinkedSprite[][] sprites; + public static LinkedSprite[][] carrySprites; private Inventory inventory; @@ -106,7 +106,7 @@ public class Player extends Mob implements ItemHolder, ClientTickable { // Note: the player's health & max health are inherited from Mob.java public Player(@Nullable Player previousInstance, InputHandler input) { - super(Sprite.missingTexture(SpriteType.Entity), Player.maxHealth); + super(null, Player.maxHealth); x = 24; y = 24; @@ -729,7 +729,7 @@ private int getAttackDamage(Entity e) { */ public void updateSprites() { // Get the current skin we are using as a MobSprite array. - MobSprite[][][] selectedSkin = SkinDisplay.getSkinAsMobSprite(); + LinkedSprite[][][] selectedSkin = SkinDisplay.getSkinAsMobSprite(); // Assign the skin to the states. sprites = selectedSkin[0]; @@ -780,10 +780,10 @@ public void render(Screen screen) { col = Color.WHITE; // Make the sprite white. } - MobSprite[][] spriteSet = activeItem instanceof FurnitureItem ? carrySprites : sprites; + LinkedSprite[][] spriteSet = activeItem instanceof FurnitureItem ? carrySprites : sprites; // Renders falling - MobSprite curSprite; + LinkedSprite curSprite; if (onFallDelay > 0) { // This makes falling look really cool. float spriteToUse = onFallDelay / 2f; @@ -795,11 +795,14 @@ public void render(Screen screen) { curSprite = spriteSet[dir.getDir()][(walkDist >> 3) & 1]; // Gets the correct sprite to render. } + curSprite.setColor(shirtColor); // Render each corner of the sprite if (isSwimming()) { - curSprite.renderRow(0, screen, xo, yo, -1, shirtColor); + Sprite sprite = curSprite.getSprite(); + screen.render(xo, yo, sprite.spritePixels[0][0]); + screen.render(xo, yo, sprite.spritePixels[0][1]); } else { // Don't render the bottom half if swimming. - curSprite.render(screen, xo, yo - 4 * onFallDelay, -1, shirtColor); + screen.render(xo, yo - 4 * onFallDelay, curSprite); } // Renders slashes: @@ -809,28 +812,28 @@ public void render(Screen screen) { screen.render(xo + 0, yo - 4, 3, 0, 0, hudSheet.getSheet()); // Render left half-slash screen.render(xo + 8, yo - 4, 3, 0, 1, hudSheet.getSheet()); // Render right half-slash (mirror of left). if (attackItem != null && !(attackItem instanceof PowerGloveItem)) { // If the player had an item when they last attacked... - attackItem.sprite.getSprite().render(screen, xo + 4, yo - 4, 1); // Then render the icon of the item, mirrored + screen.render(xo + 4, yo - 4, attackItem.sprite.getSprite(), 1, false); // Then render the icon of the item, mirrored } break; case LEFT: // Attacking to the left... (Same as above) screen.render(xo - 4, yo, 4, 0, 1, hudSheet.getSheet()); screen.render(xo - 4, yo + 8, 4, 0, 3, hudSheet.getSheet()); if (attackItem != null && !(attackItem instanceof PowerGloveItem)) { - attackItem.sprite.getSprite().render(screen, xo - 4, yo + 4, 1); + screen.render(xo - 4, yo + 4, attackItem.sprite.getSprite(), 1, false); } break; case RIGHT: // Attacking to the right (Same as above) screen.render(xo + 8 + 4, yo, 4, 0, 0, hudSheet.getSheet()); screen.render(xo + 8 + 4, yo + 8, 4, 0, 2, hudSheet.getSheet()); if (attackItem != null && !(attackItem instanceof PowerGloveItem)) { - attackItem.sprite.getSprite().render(screen, xo + 8 + 4, yo + 4); + screen.render(xo + 8 + 4, yo + 4, attackItem.sprite.getSprite()); } break; case DOWN: // Attacking downwards (Same as above) screen.render(xo + 0, yo + 8 + 4, 3, 0, 2, hudSheet.getSheet()); screen.render(xo + 8, yo + 8 + 4, 3, 0, 3, hudSheet.getSheet()); if (attackItem != null && !(attackItem instanceof PowerGloveItem)) { - attackItem.sprite.getSprite().render(screen, xo + 4, yo + 8 + 4); + screen.render(xo + 4, yo + 8 + 4, attackItem.sprite.getSprite()); } break; case NONE: @@ -842,16 +845,16 @@ public void render(Screen screen) { if (isFishing) { switch (dir) { case UP: - activeItem.sprite.getSprite().render(screen, xo + 4, yo - 4, 1); + screen.render(xo + 4, yo - 4, activeItem.sprite.getSprite(), 1, false); break; case LEFT: - activeItem.sprite.getSprite().render(screen, xo - 4, yo + 4, 1); + screen.render(xo - 4, yo + 4, activeItem.sprite.getSprite(), 1, false); break; case RIGHT: - activeItem.sprite.getSprite().render(screen, xo + 8 + 4, yo + 4, 0); + screen.render(xo + 8 + 4, yo + 4, activeItem.sprite.getSprite()); break; case DOWN: - activeItem.sprite.getSprite().render(screen, xo + 4, yo + 8 + 4, 0); + screen.render(xo + 4, yo + 8 + 4, activeItem.sprite.getSprite()); break; case NONE: break; diff --git a/src/main/java/minicraft/entity/mob/Sheep.java b/src/main/java/minicraft/entity/mob/Sheep.java index 4cb7a85b1..5b201888a 100644 --- a/src/main/java/minicraft/entity/mob/Sheep.java +++ b/src/main/java/minicraft/entity/mob/Sheep.java @@ -5,18 +5,16 @@ import minicraft.core.Updater; import minicraft.core.io.Settings; import minicraft.entity.Direction; -import minicraft.gfx.MobSprite; import minicraft.gfx.Screen; import minicraft.gfx.SpriteLinker.LinkedSprite; -import minicraft.gfx.SpriteLinker.SpriteType; import minicraft.item.Item; import minicraft.item.Items; import minicraft.item.ToolItem; import minicraft.item.ToolType; public class Sheep extends PassiveMob { - private static final LinkedSprite sprites = new LinkedSprite(SpriteType.Entity, "sheep"); - private static final LinkedSprite cutSprites = new LinkedSprite(SpriteType.Entity, "sheep").setSpritePos(0, 2); + private static final LinkedSprite[][] sprites = Mob.compileMobSpriteAnimations(0, 0, "sheep"); + private static final LinkedSprite[][] cutSprites = Mob.compileMobSpriteAnimations(0, 2, "sheep"); private static final int WOOL_GROW_TIME = 3 * 60 * Updater.normSpeed; // Three minutes @@ -35,13 +33,13 @@ public void render(Screen screen) { int xo = x - 8; int yo = y - 11; - MobSprite[][] curAnim = cut ? cutSprites.getMobSprites() : sprites.getMobSprites(); + LinkedSprite[][] curAnim = cut ? cutSprites : sprites; - MobSprite curSprite = curAnim[dir.getDir()][(walkDist >> 3) % curAnim[dir.getDir()].length]; + LinkedSprite curSprite = curAnim[dir.getDir()][(walkDist >> 3) % curAnim[dir.getDir()].length]; if (hurtTime > 0) { - curSprite.render(screen, xo, yo, true); + screen.render(xo, yo, curSprite.getSprite(), true); } else { - curSprite.render(screen, xo, yo); + screen.render(xo, yo, curSprite); } } diff --git a/src/main/java/minicraft/entity/mob/Skeleton.java b/src/main/java/minicraft/entity/mob/Skeleton.java index 021d10d49..09fc27c56 100644 --- a/src/main/java/minicraft/entity/mob/Skeleton.java +++ b/src/main/java/minicraft/entity/mob/Skeleton.java @@ -4,15 +4,14 @@ import minicraft.core.io.Settings; import minicraft.entity.Arrow; import minicraft.gfx.SpriteLinker.LinkedSprite; -import minicraft.gfx.SpriteLinker.SpriteType; import minicraft.item.Items; public class Skeleton extends EnemyMob { - private static LinkedSprite[] sprites = new LinkedSprite[] { - new LinkedSprite(SpriteType.Entity, "skeleton"), - new LinkedSprite(SpriteType.Entity, "skeleton").setSpritePos(0, 2), - new LinkedSprite(SpriteType.Entity, "skeleton").setSpritePos(0, 4), - new LinkedSprite(SpriteType.Entity, "skeleton").setSpritePos(0, 6) + private static LinkedSprite[][][] sprites = new LinkedSprite[][][] { + Mob.compileMobSpriteAnimations(0, 0, "skeleton"), + Mob.compileMobSpriteAnimations(0, 2, "skeleton"), + Mob.compileMobSpriteAnimations(0, 4, "skeleton"), + Mob.compileMobSpriteAnimations(0, 6, "skeleton") }; private int arrowtime; diff --git a/src/main/java/minicraft/entity/mob/Slime.java b/src/main/java/minicraft/entity/mob/Slime.java index 1d648662c..e6a416ba2 100644 --- a/src/main/java/minicraft/entity/mob/Slime.java +++ b/src/main/java/minicraft/entity/mob/Slime.java @@ -5,15 +5,14 @@ import minicraft.entity.Direction; import minicraft.gfx.Screen; import minicraft.gfx.SpriteLinker.LinkedSprite; -import minicraft.gfx.SpriteLinker.SpriteType; import minicraft.item.Items; public class Slime extends EnemyMob { - private static LinkedSprite[] sprites = new LinkedSprite[] { - new LinkedSprite(SpriteType.Entity, "slime").setSpriteList(2).setSpriteDim(0, 0, 2, 2), - new LinkedSprite(SpriteType.Entity, "slime").setSpriteList(2).setSpriteDim(0, 2, 2, 2), - new LinkedSprite(SpriteType.Entity, "slime").setSpriteList(2).setSpriteDim(0, 4, 2, 2), - new LinkedSprite(SpriteType.Entity, "slime").setSpriteList(2).setSpriteDim(0, 6, 2, 2) + private static LinkedSprite[][][] sprites = new LinkedSprite[][][] { + new LinkedSprite[][] {Mob.compileSpriteList(0, 0, 2, 2, 0, 2, "slime")}, + new LinkedSprite[][] {Mob.compileSpriteList(0, 2, 2, 2, 0, 2, "slime")}, + new LinkedSprite[][] {Mob.compileSpriteList(0, 4, 2, 2, 0, 2, "slime")}, + new LinkedSprite[][] {Mob.compileSpriteList(0, 6, 2, 2, 0, 2, "slime")} }; private int jumpTime = 0; // jumpTimer, also acts as a rest timer before the next jump diff --git a/src/main/java/minicraft/entity/mob/Snake.java b/src/main/java/minicraft/entity/mob/Snake.java index f2c3d5b0d..fdcc43eaa 100644 --- a/src/main/java/minicraft/entity/mob/Snake.java +++ b/src/main/java/minicraft/entity/mob/Snake.java @@ -3,15 +3,14 @@ import minicraft.core.io.Settings; import minicraft.entity.Entity; import minicraft.gfx.SpriteLinker.LinkedSprite; -import minicraft.gfx.SpriteLinker.SpriteType; import minicraft.item.Items; public class Snake extends EnemyMob { - private static LinkedSprite[] sprites = new LinkedSprite[] { - new LinkedSprite(SpriteType.Entity, "snake").setSpritePos(0, 0), - new LinkedSprite(SpriteType.Entity, "snake").setSpritePos(0, 2), - new LinkedSprite(SpriteType.Entity, "snake").setSpritePos(0, 4), - new LinkedSprite(SpriteType.Entity, "snake").setSpritePos(0, 6) + private static LinkedSprite[][][] sprites = new LinkedSprite[][][] { + Mob.compileMobSpriteAnimations(0, 0, "snake"), + Mob.compileMobSpriteAnimations(0, 2, "snake"), + Mob.compileMobSpriteAnimations(0, 4, "snake"), + Mob.compileMobSpriteAnimations(0, 6, "snake") }; public Snake(int lvl) { diff --git a/src/main/java/minicraft/entity/mob/Zombie.java b/src/main/java/minicraft/entity/mob/Zombie.java index 4927b4c9f..987b26072 100644 --- a/src/main/java/minicraft/entity/mob/Zombie.java +++ b/src/main/java/minicraft/entity/mob/Zombie.java @@ -2,15 +2,14 @@ import minicraft.core.io.Settings; import minicraft.gfx.SpriteLinker.LinkedSprite; -import minicraft.gfx.SpriteLinker.SpriteType; import minicraft.item.Items; public class Zombie extends EnemyMob { - private static LinkedSprite[] sprites = new LinkedSprite[] { - new LinkedSprite(SpriteType.Entity, "zombie"), - new LinkedSprite(SpriteType.Entity, "zombie").setSpritePos(0, 2), - new LinkedSprite(SpriteType.Entity, "zombie").setSpritePos(0, 4), - new LinkedSprite(SpriteType.Entity, "zombie").setSpritePos(0, 6) + private static LinkedSprite[][][] sprites = new LinkedSprite[][][] { + Mob.compileMobSpriteAnimations(0, 0, "zombie"), + Mob.compileMobSpriteAnimations(0, 2, "zombie"), + Mob.compileMobSpriteAnimations(0, 4, "zombie"), + Mob.compileMobSpriteAnimations(0, 6, "zombie") }; /** diff --git a/src/main/java/minicraft/entity/particle/Particle.java b/src/main/java/minicraft/entity/particle/Particle.java index 0d06db24a..93739ca41 100644 --- a/src/main/java/minicraft/entity/particle/Particle.java +++ b/src/main/java/minicraft/entity/particle/Particle.java @@ -1,9 +1,12 @@ package minicraft.entity.particle; +import javax.security.auth.DestroyFailedException; + import minicraft.entity.ClientTickable; import minicraft.entity.Entity; import minicraft.gfx.Screen; import minicraft.gfx.SpriteLinker.LinkedSprite; +import minicraft.util.Logging; public class Particle extends Entity implements ClientTickable { private int time; // lifetime elapsed. @@ -37,11 +40,16 @@ public void tick() { time++; if (time > lifetime) { remove(); + if (sprite != null) try { + sprite.destroy(); + } catch (DestroyFailedException e) { + Logging.SPRITE.trace(e); + } } } @Override - public void render(Screen screen) { sprite.getSprite().render(screen, x, y); } + public void render(Screen screen) { screen.render(x, y, sprite); } @Override public boolean isSolid() { return false; } diff --git a/src/main/java/minicraft/entity/particle/SandParticle.java b/src/main/java/minicraft/entity/particle/SandParticle.java new file mode 100644 index 000000000..c8f62a02d --- /dev/null +++ b/src/main/java/minicraft/entity/particle/SandParticle.java @@ -0,0 +1,15 @@ +package minicraft.entity.particle; + +import minicraft.gfx.SpriteLinker.LinkedSprite; +import minicraft.gfx.SpriteLinker.SpriteType; + +public class SandParticle extends Particle { + /** + * Creating a sand particle. + * @param x X map position + * @param y Y map position + */ + public SandParticle(int x, int y) { + super(x, y, 180, new LinkedSprite(SpriteType.Entity, "sand_dust")); + } +} diff --git a/src/main/java/minicraft/entity/particle/SmashParticle.java b/src/main/java/minicraft/entity/particle/SmashParticle.java index 88c09f867..3873604c2 100644 --- a/src/main/java/minicraft/entity/particle/SmashParticle.java +++ b/src/main/java/minicraft/entity/particle/SmashParticle.java @@ -4,8 +4,6 @@ import minicraft.gfx.SpriteLinker.SpriteType; public class SmashParticle extends Particle { - static int[][] mirrors = {{2, 3}, {0, 1}}; - /** * Creates a smash particle at the given position. Has a lifetime of 10 ticks. * Will also play a monsterhurt sound when created. @@ -14,6 +12,6 @@ public class SmashParticle extends Particle { * @param y Y map position */ public SmashParticle(int x, int y) { - super(x, y, 10, new LinkedSprite(SpriteType.Gui, "hud").setMirrors(mirrors).setOnePixel(true).setSpriteDim(3, 1, 2, 2)); + super(x, y, 10, new LinkedSprite(SpriteType.Entity, "smash")); } } diff --git a/src/main/java/minicraft/gfx/ConnectorSprite.java b/src/main/java/minicraft/gfx/ConnectorSprite.java deleted file mode 100644 index 119538631..000000000 --- a/src/main/java/minicraft/gfx/ConnectorSprite.java +++ /dev/null @@ -1,121 +0,0 @@ -package minicraft.gfx; - -import minicraft.gfx.SpriteLinker.LinkedSprite; -import minicraft.level.Level; -import minicraft.level.tile.ConnectTile; -import minicraft.level.tile.Tile; - -/** @deprecated because of standardization. */ -@Deprecated -public class ConnectorSprite { - /** - This class is meant for those tiles that look different when they are touching other tiles of their type; aka, they "connect" to them. - - Since I think connecting tile sprites tend to have three color categories, maybe this should have two extra colors..? - - This class will need to keep rack of the following sprites: - -a sprite for each kind of intersection; aka a 3x3 grid of sprite pixels, that show the sprite for each position, totally surrounded, nothing of left, etc. - - */ - - public LinkedSprite sparse, sides, full; - private Class owner; - private boolean checkCorners; - - public ConnectorSprite(Class owner, LinkedSprite sparse, LinkedSprite sides, LinkedSprite full) { - this(owner, sparse, sides, full, true); - } - public ConnectorSprite(Class owner, LinkedSprite sparse, LinkedSprite sides, LinkedSprite full, boolean cornersMatter) { - this.owner = owner; - this.sparse = sparse; - this.sides = sides; - this.full = full; - this.checkCorners = cornersMatter; - } - public ConnectorSprite(Class owner, LinkedSprite sparse, LinkedSprite full) { - this(owner, sparse, sparse, full, false); - } - - public void render(Screen screen, Level level, int x, int y) { render(screen, level, x, y, -1); } - - public void render(Screen screen, Level level, int x, int y, int whiteTint) { - //System.out.println("rendering sprite for tile " + owner); - - Tile ut = level.getTile(x, y - 1); - Tile dt = level.getTile(x, y + 1); - Tile lt = level.getTile(x - 1, y); - Tile rt = level.getTile(x + 1, y); - - boolean u = connectsToDoEdgeCheck(ut, true); - boolean d = connectsToDoEdgeCheck(dt, true); - boolean l = connectsToDoEdgeCheck(lt, true); - boolean r = connectsToDoEdgeCheck(rt, true); - - boolean ul = connectsToDoEdgeCheck(level.getTile(x - 1, y - 1), false); - boolean dl = connectsToDoEdgeCheck(level.getTile(x - 1, y + 1), false); - boolean ur = connectsToDoEdgeCheck(level.getTile(x + 1, y - 1), false); - boolean dr = connectsToDoEdgeCheck(level.getTile(x + 1, y + 1), false); - - x = x << 4; - y = y << 4; - - - if (u && l) { - if (ul || !checkCorners) full.getSprite().renderPixel(1, 1, screen, x, y); - else sides.getSprite().renderPixel(0, 0, screen, x, y); - } else - sparse.getSprite().renderPixel(l ? 1 : 2, u ? 1 : 2, screen, x, y); - - - if (u && r) { - if (ur || !checkCorners) full.getSprite().renderPixel(0, 1, screen, x + 8, y); - else sides.getSprite().renderPixel(1, 0, screen, x + 8, y); - } else - sparse.getSprite().renderPixel(r ? 1 : 0, u ? 1 : 2, screen, x + 8, y); - - - if (d && l) { - if (dl || !checkCorners) full.getSprite().renderPixel(1, 0, screen, x, y + 8); - else sides.getSprite().renderPixel(0, 1, screen, x, y + 8); - } else - sparse.getSprite().renderPixel(l ? 1 : 2, d ? 1 : 0, screen, x, y + 8); - - - if (d && r) { - if (dr || !checkCorners) full.getSprite().renderPixel(0, 0, screen, x + 8, y + 8); - else sides.getSprite().renderPixel(1, 1, screen, x + 8, y + 8); - } else - sparse.getSprite().renderPixel(r ? 1 : 0, d ? 1 : 0, screen, x + 8, y + 8); - - } - - // It is expected that some tile classes will override this on class instantiation. - public boolean connectsTo(Tile tile, boolean isSide) { - //System.out.println("original connection check"); - return tile.getClass() == owner; - } - - public boolean connectsToDoEdgeCheck(Tile tile, boolean isSide) { - if (tile.getClass() == ConnectTile.class) { - return true; - } - else { - return connectsTo(tile, isSide); - } - } - - public static Sprite makeSprite(int w, int h, int mirror, SpriteSheet sheet, boolean repeat, Point... coords) { - Sprite.Px[][] pixels = new Sprite.Px[h][w]; - int i = 0; - for (int r = 0; r < h && i < coords.length; r++) { - for (int c = 0; c < w && i < coords.length; c++) { - Point pos = coords[i]; - pixels[r][c] = new Sprite.Px(pos.x, pos.y, mirror, sheet); - i++; - if (i == coords.length && repeat) i = 0; - } - } - - return new Sprite(pixels); - } -} diff --git a/src/main/java/minicraft/gfx/Font.java b/src/main/java/minicraft/gfx/Font.java index 9b75f1b92..8c47ee146 100644 --- a/src/main/java/minicraft/gfx/Font.java +++ b/src/main/java/minicraft/gfx/Font.java @@ -6,7 +6,6 @@ import minicraft.core.Renderer; import minicraft.core.io.Localization; -import minicraft.gfx.SpriteLinker.SpriteSheet; import minicraft.gfx.SpriteLinker.SpriteType; public class Font { @@ -89,7 +88,7 @@ public static int textWidth(String[] para) { } public static int textHeight() {//noinspection SuspiciousNameCombination - return SpriteSheet.boxWidth; + return MinicraftImage.boxWidth; } public static void drawCentered(String msg, Screen screen, int y, int color) { diff --git a/src/main/java/minicraft/gfx/MinicraftImage.java b/src/main/java/minicraft/gfx/MinicraftImage.java new file mode 100644 index 000000000..e9e0b1344 --- /dev/null +++ b/src/main/java/minicraft/gfx/MinicraftImage.java @@ -0,0 +1,87 @@ +package minicraft.gfx; + +import java.awt.image.BufferedImage; +import java.io.IOException; + +import minicraft.core.CrashHandler; +import minicraft.gfx.SpriteLinker.LinkedSprite; + +/** + * Although we have SpriteLinker, we still need SpriteSheet for buffering. + * As BufferedImage is heavy. Our current rendering system still depends on this array. + */ +public class MinicraftImage { + /** Each sprite tile size. */ + public static final int boxWidth = 8; + + public final int width, height; // Width and height of the sprite sheet + public final int[] pixels; // Integer array of the image's pixels + + /** + * Default with maximum size of image. + * @param image The image to be added. + * @throws IOException if I/O exception occurs. + */ + public MinicraftImage(BufferedImage image) throws IOException { + this(image, image.getWidth(), image.getHeight()); + } + /** + * Custom size. + * @param image The image to be added. + * @param width The width of the {@link MinicraftImage} to be applied to the {@link LinkedSprite}. + * @param height The height of the {@link MinicraftImage} to be applied to the {@link LinkedSprite}. + * @throws IOException + */ + public MinicraftImage(BufferedImage image, int width, int height) throws IOException { + if (width % 8 != 0) + CrashHandler.errorHandle(new IllegalArgumentException("Invalid width of SpriteSheet."), new CrashHandler.ErrorInfo( + "Invalid SpriteSheet argument.", CrashHandler.ErrorInfo.ErrorType.HANDLED, + String.format("Invalid width: {}, SpriteSheet width should be a multiple of 8.") + )); + if (height % 8 != 0) + CrashHandler.errorHandle(new IllegalArgumentException("Invalid height of SpriteSheet."), new CrashHandler.ErrorInfo( + "Invalid SpriteSheet argument.", CrashHandler.ErrorInfo.ErrorType.HANDLED, + String.format("Invalid height: {}, SpriteSheet height should be a multiple of 8.") + )); + + // Sets width and height to that of the image + this.width = width - width % 8; + this.height = height - height % 8; + + // If size is bigger than image source, throw error. + if (this.width > image.getWidth() || this.height > image.getHeight()) { + throw new IOException(new IndexOutOfBoundsException(String.format("Requested size %s*%s out of source size %s*%s", + this.width, this.height, image.getWidth(), image.getHeight()))); + } + + pixels = image.getRGB(0, 0, width, height, null, 0, width); // Gets the color array of the image pixels + + // Applying the RGB array into Minicraft rendering system 25 bits RBG array. + for (int i = 0; i < pixels.length; i++) { // Loops through all the pixels + int red; + int green; + int blue; + + // This should be a number from 0-255 that is the red of the pixel + red = (pixels[i] & 0xff0000); + + // Same, but green + green = (pixels[i] & 0xff00); + + // Same, but blue + blue = (pixels[i] & 0xff); + + // This stuff is to figure out if the pixel is transparent or not + int transparent = 1; + + // A value of 0 means transparent, a value of 1 means opaque + if (pixels[i] >> 24 == 0x00) { + transparent = 0; + } + + // Actually put the data in the array + // Uses 25 bits to store everything (8 for red, 8 for green, 8 for blue, and 1 for alpha) + pixels[i] = (transparent << 24) + red + green + blue; + } + } +} diff --git a/src/main/java/minicraft/gfx/MobSprite.java b/src/main/java/minicraft/gfx/MobSprite.java deleted file mode 100644 index dc473a858..000000000 --- a/src/main/java/minicraft/gfx/MobSprite.java +++ /dev/null @@ -1,115 +0,0 @@ -package minicraft.gfx; - -/** @deprecated because of standardization. */ -@Deprecated -public class MobSprite extends Sprite { - /** - This class is meant specifically for mobs, becuase they have a special way of flipping and such. It's not only the pixels, as much as the whole sprite flips. - */ - - public MobSprite(int sx, int sy, int w, int h, int mirror, SpriteSheet sheet) { - /// This assumes the pixels are all neatly laid out on the spreadsheet, and should be flipped in position according to their mirroring. - super(new Px[h][w]); - - boolean flipX = (0x01 & mirror) > 0, flipY = (0x02 & mirror) > 0; - - for (int r = 0; r < spritePixels.length; r++) { // Loop down through each row - for (int c = 0; c < spritePixels[r].length; c++) { // Loop across through each column - // The offsets are there to determine the pixel that will be there: the one in order, or on the opposite side. - int xOffset = flipX ? spritePixels[r].length-1 - c : c; - int yOffset = flipY ? spritePixels.length-1 - r : r; - spritePixels[r][c] = new Px(sx+xOffset, sy+yOffset, mirror, sheet); - } - } - } - - /** This is an easy way to make a list of sprites that are all part of the same "Sprite", so they have similar parameters, but they're just at different locations on the spreadsheet. */ - public static MobSprite[] compileSpriteList(int sheetX, int sheetY, int width, int height, int mirror, int number, SpriteSheet sheet) { - MobSprite[] sprites = new MobSprite[number]; - for (int i = 0; i < sprites.length; i++) - sprites[i] = new MobSprite(sheetX + width * i, sheetY, width, height, mirror, sheet); - - return sprites; - } - - public static MobSprite[] compilePlayerSpriteList(int sheetX, int sheetY, int width, int height, int mirror, int number, SpriteSheet sheet) { - MobSprite[] sprites = new MobSprite[number]; - for (int i = 0; i < sprites.length; i++) - sprites[i] = new MobSprite(sheetX + width * i, sheetY, width, height, mirror, sheet); - - return sprites; - } - - public static MobSprite[][] compileMobSpriteAnimations(int sheetX, int sheetY, SpriteSheet sheet) { - MobSprite[][] sprites = new MobSprite[4][2]; - // dir numbers: 0=down, 1=up, 2=left, 3=right. - /// On the spritesheet, most mobs have 4 sprites there, first facing down, then up, then right 1, then right 2. The first two get flipped to animate them, but the last two get flipped to change direction. - - // Contents: down 1, up 1, right 1, right 2 - MobSprite[] set1 = MobSprite.compileSpriteList(sheetX, sheetY, 2, 2, 0, 4, sheet); - - // Contents: down 2, up 2, left 1, left 2 - MobSprite[] set2 = MobSprite.compileSpriteList(sheetX, sheetY, 2, 2, 1, 4, sheet); - - // Down - sprites[0][0] = set1[0]; - sprites[0][1] = set2[0]; - - // Up - sprites[1][0] = set1[1]; - sprites[1][1] = set2[1]; - - // Left - sprites[2][0] = set2[2]; - sprites[2][1] = set2[3]; - - // Right - sprites[3][0] = set1[2]; - sprites[3][1] = set1[3]; - - return sprites; - } - - public static MobSprite[][] compilePlayerSpriteAnimations(int sheetX, int sheetY, SpriteSheet sheet) { - MobSprite[][] sprites = new MobSprite[4][2]; - // dir numbers: 0=down, 1=up, 2=left, 3=right. - /// On the spritesheet, most mobs have 4 sprites there, first facing down, then up, then right 1, then right 2. The first two get flipped to animate them, but the last two get flipped to change direction. - - // Contents: down 1, up 1, right 1, right 2 - MobSprite[] set1 = MobSprite.compilePlayerSpriteList(sheetX, sheetY, 2, 2, 0, 4, sheet); - - // Contents: down 2, up 2, left 1, left 2 - MobSprite[] set2 = MobSprite.compilePlayerSpriteList(sheetX, sheetY, 2, 2, 1, 4, sheet); - - // Down - sprites[0][0] = set1[0]; - sprites[0][1] = set2[0]; - - // Up - sprites[1][0] = set1[1]; - sprites[1][1] = set2[1]; - - // Left - sprites[2][0] = set2[2]; - sprites[2][1] = set2[3]; - - // Right - sprites[3][0] = set1[2]; - sprites[3][1] = set1[3]; - - return sprites; - } - - public void render(Screen screen, int x, int y, boolean fullbright) { - for (int row = 0; row < spritePixels.length; row++) { // Loop down through each row - renderRow(row, screen, x, y + row * 8, fullbright); - } - } - - public void renderRow(int r, Screen screen, int x, int y, boolean fullbright) { - Px[] row = spritePixels[r]; - for (int c = 0; c < row.length; c++) { // Loop across through each column - screen.render(x + c * 8, y, row[c].x, row[c].y, row[c].mirror, row[c].sheet, -1, fullbright); // Render the sprite pixel. - } - } -} diff --git a/src/main/java/minicraft/gfx/Screen.java b/src/main/java/minicraft/gfx/Screen.java index ef4ba188d..7440edfb6 100644 --- a/src/main/java/minicraft/gfx/Screen.java +++ b/src/main/java/minicraft/gfx/Screen.java @@ -2,12 +2,9 @@ import java.util.Arrays; -import org.jetbrains.annotations.NotNull; - import minicraft.core.Renderer; import minicraft.core.Updater; import minicraft.gfx.SpriteLinker.LinkedSprite; -import minicraft.gfx.SpriteLinker.SpriteSheet; import minicraft.gfx.SpriteLinker.SpriteType; public class Screen { @@ -32,25 +29,10 @@ public class Screen { // So 0 is the start of the item sheet 1024 the start of the tile sheet, 2048 the start of the entity sheet, // And 3072 the start of the gui sheet - private SpriteSheet skinSheet; - - public Screen(SpriteSheet skinsSheet) { - skinSheet = skinsSheet; - + public Screen() { /// Screen width and height are determined by the actual game window size, meaning the screen is only as big as the window. pixels = new int[Screen.w * Screen.h]; // Makes new integer array for all the pixels on the screen. } - public Screen(Screen model) { - this(model.skinSheet); - } - - @NotNull - public void setSkinSheet(SpriteSheet skinSheet) { - this.skinSheet = skinSheet; - } - public SpriteSheet getSkinSheet() { - return skinSheet; - } /** Clears all the colors on the screen */ public void clear(int color) { @@ -58,26 +40,36 @@ public void clear(int color) { Arrays.fill(pixels, color); } - public void render(int xp, int yp, int xt, int yt, int bits, SpriteSheet sheet) { render(xp, yp, xt, yt, bits, sheet, -1); } - public void render(int xp, int yp, int xt, int yt, int bits, SpriteSheet sheet, int whiteTint) { render(xp, yp, xt, yt, bits, sheet, whiteTint, false); } + public void render(int xp, int yp, int xt, int yt, int bits, MinicraftImage sheet) { render(xp, yp, xt, yt, bits, sheet, -1); } + public void render(int xp, int yp, int xt, int yt, int bits, MinicraftImage sheet, int whiteTint) { render(xp, yp, xt, yt, bits, sheet, whiteTint, false); } /** This method takes care of assigning the correct spritesheet to assign to the sheet variable **/ - public void render(int xp, int yp, int xt, int yt, int bits, SpriteSheet sheet, int whiteTint, boolean fullbright) { + public void render(int xp, int yp, int xt, int yt, int bits, MinicraftImage sheet, int whiteTint, boolean fullbright) { render(xp, yp, xt, yt, bits, sheet, whiteTint, fullbright, 0); } public void render(int xp, int yp, LinkedSprite sprite) { render(xp, yp, sprite.getSprite()); } public void render(int xp, int yp, Sprite sprite) { render(xp, yp, sprite, false); } - public void render(int xp, int yp, Sprite sprite, boolean fullbright) { + public void render(int xp, int yp, Sprite sprite, boolean fullbright) { render(xp, yp, sprite, 0, fullbright, 0); } + public void render(int xp, int yp, Sprite sprite, int mirror, boolean fullbright) { render(xp, yp, sprite, mirror, fullbright, 0); } + public void render(int xp, int yp, Sprite sprite, int mirror, boolean fullbright, int color) { for (int r = 0; r < sprite.spritePixels.length; r++) { for (int c = 0; c < sprite.spritePixels[r].length; c++) { Sprite.Px px = sprite.spritePixels[r][c]; - render(xp + c, yp + r, px.x, px.y, px.mirror, px.sheet, sprite.color, fullbright); + render(xp + c * 8, yp + r * 8, px, mirror, sprite.color, fullbright, color); } } } + public void render(int xp, int yp, Sprite.Px pixel) { render(xp, yp, pixel, -1); } + public void render(int xp, int yp, Sprite.Px pixel, int whiteTint) { render(xp, yp, pixel, 0, whiteTint); } + public void render(int xp, int yp, Sprite.Px pixel, int mirror, int whiteTint) { render(xp, yp, pixel, mirror, whiteTint, false); } + public void render(int xp, int yp, Sprite.Px pixel, int mirror, int whiteTint, boolean fullbright) { render(xp, yp, pixel, mirror, whiteTint, fullbright, 0); } + public void render(int xp, int yp, Sprite.Px pixel, int mirror, int whiteTint, boolean fullbright, int color) { + render(xp, yp, pixel.x, pixel.y, pixel.mirror ^ mirror, pixel.sheet, whiteTint, fullbright, color); + } + /** Renders an object from the sprite sheet based on screen coordinates, tile (SpriteSheet location), colors, and bits (for mirroring). I believe that xp and yp refer to the desired position of the upper-left-most pixel. */ - public void render(int xp, int yp, int xt, int yt, int bits, SpriteSheet sheet, int whiteTint, boolean fullbright, int color) { + public void render(int xp, int yp, int xt, int yt, int bits, MinicraftImage sheet, int whiteTint, boolean fullbright, int color) { if (sheet == null) return; // Verifying that sheet is not null. // xp and yp are originally in level coordinates, but offset turns them to screen coordinates. @@ -89,7 +81,7 @@ public void render(int xp, int yp, int xt, int yt, int bits, SpriteSheet sheet, boolean mirrorY = (bits & BIT_MIRROR_Y) > 0; // Vertically. // Validation check - if (sheet == null || (xt + 1) * 8 + (yt + 1) * 8 * sheet.width > sheet.pixels.length) { + if (sheet == null || xt * 8 + yt * 8 * sheet.width + 7 + 7 * sheet.width >= sheet.pixels.length) { sheet = Renderer.spriteLinker.missingSheet(SpriteType.Item); xt = 0; yt = 0; diff --git a/src/main/java/minicraft/gfx/Sprite.java b/src/main/java/minicraft/gfx/Sprite.java index 0164e544b..c913a82f3 100644 --- a/src/main/java/minicraft/gfx/Sprite.java +++ b/src/main/java/minicraft/gfx/Sprite.java @@ -1,7 +1,5 @@ package minicraft.gfx; -import minicraft.gfx.SpriteLinker.SpriteSheet; - /** This class represents a group of pixels on their sprite sheet(s). */ public class Sprite { /** @@ -13,7 +11,7 @@ public class Sprite { The screen's render method only draws one 8x8 pixel of the spritesheet at a time, so the "sprite size" will be determined by how many repetitions of the above group there are. */ - protected Px[][] spritePixels; + public Px[][] spritePixels; public int color = -1; // spritePixels is arranged so that the pixels are in their correct positions relative to the top left of the full sprite. This means that their render positions are built-in to the array. @@ -34,9 +32,9 @@ public String toString() { /** This class represents a pixel on the sprite sheet. */ public static class Px { protected int x, y, mirror; - protected SpriteSheet sheet; + protected MinicraftImage sheet; - public Px(int sheetX, int sheetY, int mirroring, SpriteSheet sheet) { + public Px(int sheetX, int sheetY, int mirroring, MinicraftImage sheet) { // pixelX and pixelY are the relative positions each pixel should have relative to the top-left-most pixel of the sprite. x = sheetX; y = sheetY; diff --git a/src/main/java/minicraft/gfx/SpriteAnimation.java b/src/main/java/minicraft/gfx/SpriteAnimation.java index 2d3fca85b..d5fb378b9 100644 --- a/src/main/java/minicraft/gfx/SpriteAnimation.java +++ b/src/main/java/minicraft/gfx/SpriteAnimation.java @@ -1,7 +1,8 @@ package minicraft.gfx; import java.util.ArrayList; -import java.util.Objects; +import java.util.HashMap; +import java.util.function.BiFunction; import javax.security.auth.DestroyFailedException; import javax.security.auth.Destroyable; @@ -9,15 +10,31 @@ import minicraft.core.Renderer; import minicraft.gfx.SpriteLinker.LinkedSprite; import minicraft.gfx.SpriteLinker.SpriteMeta; -import minicraft.gfx.SpriteLinker.SpriteSheet; import minicraft.gfx.SpriteLinker.SpriteType; +import minicraft.level.Level; +import minicraft.level.tile.Tile; +import minicraft.util.Logging; /** This is not applicable for mob sprite animations. Only for generic sprite animations. */ public class SpriteAnimation implements Destroyable { private static final ArrayList spriteAnimations = new ArrayList<>(); + private static final HashMap metas = new HashMap<>(); + public static void setMetadata(String key, SpriteMeta meta) { + metas.put(key, meta); + } + + public static void resetMetadata() { + metas.clear(); + } + + public static SpriteMeta getMetadata(String key) { + return metas.get(key); + } + + /** Refreshing all currently registered animations. */ public static void refreshAnimations() { - spriteAnimations.forEach(a -> a.refreshAnimation()); + spriteAnimations.forEach(a -> a.refreshAnimation(metas.get(a.key))); } private LinkedSprite[] animations; @@ -26,6 +43,21 @@ public static void refreshAnimations() { private int frametick = 0; // The current tick of the current frame. It would be always 0 if no animation. private boolean destoryed = false; // Whether this instance is still registered. + // Border settings. + private LinkedSprite border = null; + private LinkedSprite corner = null; + private BiFunction connectChecker; + + // Refreshing only data. + private SpriteType type; + private String key; + + /** + * Constructing animations with the provided key. The meta is given by default. + * @param type The sprite category. + * @param key The sprite resource key. + */ + public SpriteAnimation(SpriteType type, String key) { this(metas.get(key), type, key); } /** * Constructing animations with the provided metadata and key. It should already be validated. * @param meta The metadata of the sprite sheet. @@ -33,18 +65,23 @@ public static void refreshAnimations() { * @param key The sprite resource key. */ public SpriteAnimation(SpriteMeta meta, SpriteType type, String key) { - this.metadata = meta; - SpriteSheet sheet = Objects.requireNonNull(Renderer.spriteLinker.getSheet(type, key), "sprite " + type + ": " + key); - int width = sheet.width; - if (meta.frames < 1) meta.frames = 1; - animations = new LinkedSprite[meta.frames]; - for (int f = 0; f < animations.length; f++) { - animations[f] = new LinkedSprite(type, key).setSpriteDim(0, f * (width / 8), width, width); - } + this.type = type; + this.key = key; + refreshAnimation(meta); spriteAnimations.add(this); } + /** + * Setting the tile class of this animation used for tile connector rendering. + * @param clazz The tile class. + * @return The instance itself. + */ + public SpriteAnimation setConnectChecker(BiFunction connectChecker) { + this.connectChecker = connectChecker; + return this; + } + /** * Setting the color of all animation frames. * @param color The color of sprite. @@ -99,6 +136,16 @@ public SpriteAnimation setMirror(int frame, int mirror) { return this; } + /** + * Setting the sprite sheet mirror of all frames. + * @param mirror The mirror of sprite sheet. + * @return The instance itself. + */ + public SpriteAnimation setSpriteMirror(int mirror) { + for (LinkedSprite sprite : animations) sprite.setFlip(mirror); + return this; + } + /** * Getting the current frame of animation. * @return The current frame sprite. @@ -119,30 +166,119 @@ public LinkedSprite getFrame(int frame) { /** * Rendering the animation on the screen. * @param screen The screen instance. - * @param xp The x pixel. - * @param yp The y pixel. + * @param level The level for rendering. + * @param x The x coordinate level tile. + * @param y The y coordinate level tile. */ - public void render(Screen screen, int xp, int yp) { - screen.render(xp, yp, animations[frame]); - frametick++; - - // Checking frame increment. - if (frametick == metadata.frametime) { - frametick = 0; - if (frame == animations.length - 1) - frame = 0; - else - frame++; + public void render(Screen screen, Level level, int x, int y) { + // If border and the tile class is set. + if (connectChecker != null && (border != null || corner != null)) { + boolean u = connectChecker.apply(level.getTile(x, y - 1), true); + boolean d = connectChecker.apply(level.getTile(x, y + 1), true); + boolean l = connectChecker.apply(level.getTile(x - 1, y), true); + boolean r = connectChecker.apply(level.getTile(x + 1, y), true); + + boolean ul = connectChecker.apply(level.getTile(x - 1, y - 1), false); + boolean dl = connectChecker.apply(level.getTile(x - 1, y + 1), false); + boolean ur = connectChecker.apply(level.getTile(x + 1, y - 1), false); + boolean dr = connectChecker.apply(level.getTile(x + 1, y + 1), false); + + x = x << 4; + y = y << 4; + + Sprite full = animations[frame].getSprite(); // Must be 2*2. + Sprite sparse = border != null ? border.getSprite() : null; // Must be 3*3. + Sprite sides = corner != null ? corner.getSprite() : null; // Must be 2*2. + + if (u && l) { + if (ul || sides == null) screen.render(x, y, full.spritePixels[1][1], full.color); + else screen.render(x, y, sides.spritePixels[0][0], sides.color); + } else + screen.render(x, y, sparse.spritePixels[u ? 1 : 2][l ? 1 : 2], sparse.color); + + if (u && r) { + if (ur || sides == null) screen.render(x + 8, y, full.spritePixels[1][0], full.color); + else screen.render(x + 8, y, sides.spritePixels[0][1], sides.color); + } else + screen.render(x + 8, y, sparse.spritePixels[u ? 1 : 2][r ? 1 : 0], sparse.color); + + if (d && l) { + if (dl || sides == null) screen.render(x, y + 8, full.spritePixels[0][1], full.color); + else screen.render(x, y + 8, sides.spritePixels[1][0], sides.color); + } else + screen.render(x, y + 8, sparse.spritePixels[d ? 1 : 0][l ? 1 : 2], sparse.color); + + if (d && r) { + if (dr || sides == null) screen.render(x + 8, y + 8, full.spritePixels[0][0], full.color); + else screen.render(x + 8, y + 8, sides.spritePixels[1][1], sides.color); + } else + screen.render(x + 8, y + 8, sparse.spritePixels[d ? 1 : 0][r ? 1 : 0], sparse.color); + + } else + screen.render(x << 4, y << 4, animations[frame]); + + // If there is animation. + if (animations.length > 1) { + frametick++; + + // Checking frame increment. + if (frametick == metadata.frametime) { + frametick = 0; + if (frame == animations.length - 1) + frame = 0; + else + frame++; + } } } - public void refreshAnimation() { - // TODO + /** Refreshing the animation data for this instance. */ + public void refreshAnimation(SpriteMeta metadata) { + frame = 0; + frametick = 0; + this.metadata = metadata; + MinicraftImage sheet = Renderer.spriteLinker.getSheet(type, key); + if (sheet == null) { + animations = new LinkedSprite[] {SpriteLinker.missingTexture(type)}; + border = null; + corner = null; + return; + } + + int width = sheet.width / 8; + + // Destroying all previous LinkedSprite. + try { + if (animations != null) for (LinkedSprite sprite : animations) sprite.destroy(); + if (border != null) border.destroy(); + if (corner != null) corner.destroy(); + } catch (DestroyFailedException e) { + Logging.SPRITE.trace(e); + } + + if (metadata != null) { + if (metadata.frames < 1) metadata.frames = 1; + animations = new LinkedSprite[metadata.frames]; + for (int f = 0; f < animations.length; f++) { + animations[f] = new LinkedSprite(type, key).setSpriteDim(0, f * width, width, width); + } + + // Tile sprite only. + if (metadata.border != null) border = new LinkedSprite(type, metadata.border).setMirror(3); + if (metadata.corner != null) corner = new LinkedSprite(type, metadata.corner).setMirror(3); + } else { + animations = new LinkedSprite[] {new LinkedSprite(type, key).setSpriteSize(width, width)}; + border = null; + corner = null; + } } @Override public void destroy() throws DestroyFailedException { spriteAnimations.remove(this); + if (animations != null) for (LinkedSprite sprite : animations) sprite.destroy(); + if (border != null) border.destroy(); + if (corner != null) corner.destroy(); destoryed = true; } diff --git a/src/main/java/minicraft/gfx/SpriteLinker.java b/src/main/java/minicraft/gfx/SpriteLinker.java index 0b2075e78..87cb3c749 100644 --- a/src/main/java/minicraft/gfx/SpriteLinker.java +++ b/src/main/java/minicraft/gfx/SpriteLinker.java @@ -2,26 +2,21 @@ import java.util.ArrayList; import java.util.HashMap; -import java.util.Random; -import java.awt.image.BufferedImage; import javax.security.auth.DestroyFailedException; import javax.security.auth.Destroyable; -import minicraft.core.CrashHandler; import minicraft.core.Renderer; import minicraft.util.Logging; public class SpriteLinker { /** Buffering SpriteSheet for caching. */ - private final HashMap entitySheets = new HashMap<>(), + private final HashMap entitySheets = new HashMap<>(), guiSheets = new HashMap<>(), itemSheets = new HashMap<>(), tileSheets = new HashMap<>(); /** Storing all exist in-used LinkedSprite. */ private final ArrayList linkedSheets = new ArrayList<>(); - static Random ran = new Random(); - /** Clearing all Sprite buffers for the upcoming resource pack application. */ public void resetSprites() { entitySheets.clear(); @@ -37,7 +32,7 @@ public void resetSprites() { * @param key The sheet key. * @param spriteSheet The sheet. */ - public void setSprite(SpriteType t, String key, SpriteSheet spriteSheet) { + public void setSprite(SpriteType t, String key, MinicraftImage spriteSheet) { switch (t) { case Entity: entitySheets.put(key, spriteSheet); break; case Gui: guiSheets.put(key, spriteSheet); break; @@ -46,7 +41,13 @@ public void setSprite(SpriteType t, String key, SpriteSheet spriteSheet) { } } - public SpriteSheet getSheet(SpriteType t, String key) { + /** + * Getting the sprite sheet with the category and key. + * @param t The sprite category + * @param key The resource key. + * @return The sprite sheet. null if not found. + */ + public MinicraftImage getSheet(SpriteType t, String key) { switch (t) { case Entity: return entitySheets.get(key); case Gui: return guiSheets.get(key); @@ -57,6 +58,22 @@ public SpriteSheet getSheet(SpriteType t, String key) { return null; } + /** Cleaing all skin sheets in entity sheets. */ + public void clearSkins() { + for (String k : new ArrayList<>(entitySheets.keySet())) { + if (k.startsWith("skin.")) entitySheets.remove(k); + } + } + + /** + * Setting the skin in entity sheet. + * @param key The key of the sheet. + * @param spriteSheet The sheet to be added. + */ + public void setSkin(String key, MinicraftImage spriteSheet) { + setSprite(SpriteType.Entity, key, spriteSheet); + } + /** * Getting the missing texture texture with the specific sprite type. * @param type The sprite category. @@ -76,7 +93,7 @@ public static LinkedSprite missingTexture(SpriteType type) { * @param type The sprite category. * @return Ths missing texture sprite sheet or null if invalid sprite type. */ - public SpriteSheet missingSheet(SpriteType type) { + public MinicraftImage missingSheet(SpriteType type) { switch (type) { // The sheets should be found. case Entity: return entitySheets.get("missing_entity"); case Item: return itemSheets.get("missing_item"); @@ -87,78 +104,10 @@ public SpriteSheet missingSheet(SpriteType type) { /** Updating all existing LinkedSheet for resource pack application. */ public void updateLinkedSheets() { + Logging.SPRITE.debug("Updating all LinkedSprite."); linkedSheets.forEach(s -> s.reload()); } - /** - * Although we have SpriteLinker, we still need SpriteSheet for buffering. - * As BufferedImage is heavy. Our current rendering system still depends on this array. - */ - public static class SpriteSheet { - /** Each sprite tile size. */ - public static final int boxWidth = 8; - - public final int width, height; // Width and height of the sprite sheet - public final int[] pixels; // Integer array of the image's pixels - - /** - * Default with maximum size of image. - * @param image The image to be added. - */ - public SpriteSheet(BufferedImage image) { this(image, image.getWidth(), image.getHeight()); } - /** - * Custom size. - * @param image The image to be added. - * @param width The width of the {@link SpriteSheet} to be applied to the {@link LinkedSprite}. - * @param height The height of the {@link SpriteSheet} to be applied to the {@link LinkedSprite}. - */ - public SpriteSheet(BufferedImage image, int width, int height) { - if (width % 8 != 0) - CrashHandler.errorHandle(new IllegalArgumentException("Invalid width of SpriteSheet."), new CrashHandler.ErrorInfo( - "Invalid SpriteSheet argument.", CrashHandler.ErrorInfo.ErrorType.HANDLED, - String.format("Invalid width: {}, SpriteSheet width should be a multiple of 8.") - )); - if (height % 8 != 0) - CrashHandler.errorHandle(new IllegalArgumentException("Invalid height of SpriteSheet."), new CrashHandler.ErrorInfo( - "Invalid SpriteSheet argument.", CrashHandler.ErrorInfo.ErrorType.HANDLED, - String.format("Invalid height: {}, SpriteSheet height should be a multiple of 8.") - )); - - // Sets width and height to that of the image - this.width = width - width % 8; - this.height = height - height % 8; - pixels = image.getRGB(0, 0, width, height, null, 0, width); // Gets the color array of the image pixels - - // Applying the RGB array into Minicraft rendering system 25 bits RBG array. - for (int i = 0; i < pixels.length; i++) { // Loops through all the pixels - int red; - int green; - int blue; - - // This should be a number from 0-255 that is the red of the pixel - red = (pixels[i] & 0xff0000); - - // Same, but green - green = (pixels[i] & 0xff00); - - // Same, but blue - blue = (pixels[i] & 0xff); - - // This stuff is to figure out if the pixel is transparent or not - int transparent = 1; - - // A value of 0 means transparent, a value of 1 means opaque - if (pixels[i] >> 24 == 0x00) { - transparent = 0; - } - - // Actually put the data in the array - // Uses 25 bits to store everything (8 for red, 8 for green, 8 for blue, and 1 for alpha) - pixels[i] = (transparent << 24) + red + green + blue; - } - } - } - /** The metadata of the sprite sheet. */ public static class SpriteMeta { /** The sprite animation configuration. */ @@ -193,10 +142,10 @@ public static class LinkedSprite implements Destroyable { private final String key; // The resource key. /** The sprite configuration. */ - private int x, y, w, h, color = -1, mirror = 0; + private int x, y, w, h, color = -1, mirror = 0, flip = 0; // Sprite data. - private HashMap linkedMap; + private HashMap linkedMap; private SpriteType spriteType; private Sprite sprite; private boolean destoryed; // It is not linked when destoryed. @@ -218,7 +167,7 @@ public LinkedSprite(SpriteType t, String key) { * Getting the sprite sheet of the linked sprite. * @return The current linked sprite. */ - public SpriteSheet getSheet() { + public MinicraftImage getSheet() { return linkedMap.get(key); } @@ -226,7 +175,7 @@ public SpriteSheet getSheet() { * Setting the sprite size. * @param w The sprite width. * @param h The sprite height - * @return The same instance. + * @return The instance itself. */ public LinkedSprite setSpriteSize(int w, int h) { this.w = w; @@ -238,7 +187,7 @@ public LinkedSprite setSpriteSize(int w, int h) { * Setting the sprite position. * @param x The x position of the sprite. * @param y The y position of the sprite. - * @return The same instance. + * @return The instance itself. */ public LinkedSprite setSpritePos(int x, int y) { this.x = x; @@ -252,7 +201,7 @@ public LinkedSprite setSpritePos(int x, int y) { * @param y The y position of the sprite. * @param w The sprite width. * @param h The sprite height - * @return The same instance. + * @return The instance itself. */ public LinkedSprite setSpriteDim(int x, int y, int w, int h) { setSpriteSize(w, h); @@ -263,7 +212,7 @@ public LinkedSprite setSpriteDim(int x, int y, int w, int h) { /** * Setting the white tint. * @param color The color of the white tint. - * @return The same instance. + * @return The instance itself. */ public LinkedSprite setColor(int color) { this.color = color; @@ -273,13 +222,23 @@ public LinkedSprite setColor(int color) { /** * Setting the mirror of the sprite. * @param mirror The mirror of the sprite. - * @return The same instance. + * @return The instance itself. */ public LinkedSprite setMirror(int mirror) { this.mirror = mirror; reloaded = false; // Reload this. return this; } + /** + * Setting the flip of the sprite sheet. + * @param flip The mirror of the sprite sheet. + * @return The instance itself. + */ + public LinkedSprite setFlip(int flip) { + this.flip = flip; + reloaded = false; + return this; + } /** * Getting the sprite with the configuration. @@ -297,22 +256,27 @@ public Sprite getSprite() { public void reload() { reloaded = false; } /** Reloading the sprite with the configuration. */ private void reloadSprite() { - SpriteSheet sheet = linkedMap.get(key); + MinicraftImage sheet = linkedMap.get(key); if (sheet != null) { if (w <= 0) w = sheet.width / 8; // Set the size as the maximum size of the sheet. if (h <= 0) h = sheet.height / 8; // Set the size as the maximum size of the sheet. + boolean flipX = (0x01 & flip) > 0, flipY = (0x02 & flip) > 0; + Sprite.Px[][] pixels = new Sprite.Px[h][w]; for (int r = 0; r < h; r++) { for (int c = 0; c < w; c++) { - pixels[r][c] = new Sprite.Px(x + c, y + r, mirror, sheet); + // The offsets are there to determine the pixel that will be there: the one in order, or on the opposite side. + int xOffset = flipX ? w-1 - c : c; + int yOffset = flipY ? h-1 - r : r; + pixels[r][c] = new Sprite.Px(x + xOffset, y + yOffset, mirror, sheet); } } sprite = new Sprite(pixels); sprite.color = color; } else { - Logging.SPRITE.warn("Sprite with resource ID not found: {}", key); + Logging.SPRITE.warn("SpriteSheet with resource ID not found: {}", key); sprite = missingTexture(spriteType).getSprite(); } diff --git a/src/main/java/minicraft/item/Item.java b/src/main/java/minicraft/item/Item.java index bb59d3026..aef585924 100644 --- a/src/main/java/minicraft/item/Item.java +++ b/src/main/java/minicraft/item/Item.java @@ -5,7 +5,7 @@ import minicraft.entity.mob.Player; import minicraft.gfx.Font; import minicraft.gfx.Screen; -import minicraft.gfx.Sprite; +import minicraft.gfx.SpriteLinker; import minicraft.gfx.SpriteLinker.LinkedSprite; import minicraft.gfx.SpriteLinker.SpriteType; import minicraft.level.Level; @@ -21,7 +21,7 @@ public abstract class Item { public boolean used_pending = false; // This is for multiplayer, when an item has been used, and is pending server response as to the outcome, this is set to true so it cannot be used again unless the server responds that the item wasn't used. Which should basically replace the item anyway, soo... yeah. this never gets set back. protected Item(String name) { - sprite = Sprite.missingTexture(SpriteType.Item); + sprite = SpriteLinker.missingTexture(SpriteType.Item); this.name = name; } protected Item(String name, LinkedSprite sprite) { @@ -32,7 +32,7 @@ protected Item(String name, LinkedSprite sprite) { /** Renders an item on the HUD */ public void renderHUD(Screen screen, int x, int y, int fontColor) { String dispName = getDisplayName(); - sprite.getSprite().render(screen, x, y); + screen.render(x, y, sprite); Font.drawBackground(dispName, screen, x + 8, y, fontColor); } diff --git a/src/main/java/minicraft/item/TileItem.java b/src/main/java/minicraft/item/TileItem.java index 53a9edd5b..cbe6a29cc 100644 --- a/src/main/java/minicraft/item/TileItem.java +++ b/src/main/java/minicraft/item/TileItem.java @@ -9,7 +9,7 @@ import minicraft.core.io.Sound; import minicraft.entity.Direction; import minicraft.entity.mob.Player; -import minicraft.gfx.Sprite; +import minicraft.gfx.SpriteLinker; import minicraft.gfx.SpriteLinker.LinkedSprite; import minicraft.gfx.SpriteLinker.SpriteType; import minicraft.level.Level; @@ -59,12 +59,12 @@ protected static ArrayList getAllInstances() { items.add(new TileItem("Grass Seeds", new LinkedSprite(SpriteType.Item, "seed"), "grass", "dirt")); // Creative mode available tiles: - items.add(new TileItem("Farmland", Sprite.missingTexture(SpriteType.Item), "farmland", "dirt", "grass", "hole")); - items.add(new TileItem("Exploded", Sprite.missingTexture(SpriteType.Item), "explode", "dirt", "grass")); - items.add(new TileItem("hole", Sprite.missingTexture(SpriteType.Item), "hole", "dirt", "grass")); - items.add(new TileItem("lava", Sprite.missingTexture(SpriteType.Item), "lava", "dirt", "grass", "hole")); - items.add(new TileItem("path", Sprite.missingTexture(SpriteType.Item), "path", "dirt", "grass", "hole")); - items.add(new TileItem("water", Sprite.missingTexture(SpriteType.Item), "water", "dirt", "grass", "hole")); + items.add(new TileItem("Farmland", SpriteLinker.missingTexture(SpriteType.Item), "farmland", "dirt", "grass", "hole")); + items.add(new TileItem("Exploded", SpriteLinker.missingTexture(SpriteType.Item), "explode", "dirt", "grass")); + items.add(new TileItem("hole", SpriteLinker.missingTexture(SpriteType.Item), "hole", "dirt", "grass")); + items.add(new TileItem("lava", SpriteLinker.missingTexture(SpriteType.Item), "lava", "dirt", "grass", "hole")); + items.add(new TileItem("path", SpriteLinker.missingTexture(SpriteType.Item), "path", "dirt", "grass", "hole")); + items.add(new TileItem("water", SpriteLinker.missingTexture(SpriteType.Item), "water", "dirt", "grass", "hole")); return items; } diff --git a/src/main/java/minicraft/item/UnknownItem.java b/src/main/java/minicraft/item/UnknownItem.java index 536f7e23a..4df54ea5d 100644 --- a/src/main/java/minicraft/item/UnknownItem.java +++ b/src/main/java/minicraft/item/UnknownItem.java @@ -1,12 +1,12 @@ package minicraft.item; -import minicraft.gfx.Sprite; +import minicraft.gfx.SpriteLinker; import minicraft.gfx.SpriteLinker.SpriteType; public class UnknownItem extends StackableItem { protected UnknownItem(String reqName) { - super(reqName, Sprite.missingTexture(SpriteType.Item)); + super(reqName, SpriteLinker.missingTexture(SpriteType.Item)); } public UnknownItem clone() { diff --git a/src/main/java/minicraft/level/tile/CactusTile.java b/src/main/java/minicraft/level/tile/CactusTile.java index 59f51868b..3fa2c59f7 100644 --- a/src/main/java/minicraft/level/tile/CactusTile.java +++ b/src/main/java/minicraft/level/tile/CactusTile.java @@ -10,13 +10,13 @@ import minicraft.entity.particle.TextParticle; import minicraft.gfx.Color; import minicraft.gfx.Screen; -import minicraft.gfx.SpriteLinker.LinkedSprite; +import minicraft.gfx.SpriteAnimation; import minicraft.gfx.SpriteLinker.SpriteType; import minicraft.item.Items; import minicraft.level.Level; public class CactusTile extends Tile { - private static LinkedSprite sprite = new LinkedSprite(SpriteType.Tile, "cactus"); + private static SpriteAnimation sprite = new SpriteAnimation(SpriteType.Tile, "cactus"); protected CactusTile(String name) { super(name, sprite); @@ -48,8 +48,7 @@ public boolean hurt(Level level, int x, int y, Mob source, int dmg, Direction at @Override public void render(Screen screen, Level level, int x, int y) { Tiles.get("Sand").render(screen, level, x, y); - - sprite.getSprite().render(screen, x << 4, y << 4); + sprite.render(screen, level, x, y); } public void bumpedInto(Level level, int x, int y, Entity entity) { diff --git a/src/main/java/minicraft/level/tile/CloudTile.java b/src/main/java/minicraft/level/tile/CloudTile.java index 12880cc53..be6d9bec7 100644 --- a/src/main/java/minicraft/level/tile/CloudTile.java +++ b/src/main/java/minicraft/level/tile/CloudTile.java @@ -4,8 +4,7 @@ import minicraft.entity.Direction; import minicraft.entity.Entity; import minicraft.entity.mob.Player; -import minicraft.gfx.ConnectorSprite; -import minicraft.gfx.SpriteLinker.LinkedSprite; +import minicraft.gfx.SpriteAnimation; import minicraft.gfx.SpriteLinker.SpriteType; import minicraft.item.Item; import minicraft.item.Items; @@ -14,13 +13,8 @@ import minicraft.level.Level; public class CloudTile extends Tile { - private static ConnectorSprite sprite = new ConnectorSprite(CloudTile.class, new LinkedSprite(SpriteType.Tile, "cloud").setSpriteSize(3, 3).setMirror(3), - new LinkedSprite(SpriteType.Tile, "cloud").setSpriteDim(3, 2, 2, 2).setMirror(3), new LinkedSprite(SpriteType.Tile, "cloud").setSpriteDim(3, 0, 2, 2)) - { - public boolean connectsTo(Tile tile, boolean isSide) { - return tile != Tiles.get("Infinite Fall"); - } - }; + private static SpriteAnimation sprite = new SpriteAnimation(SpriteType.Tile, "cloud") + .setConnectChecker((tile, side) -> tile.getClass() != InfiniteFallTile.class); protected CloudTile(String name) { super(name, sprite); diff --git a/src/main/java/minicraft/level/tile/ConnectTile.java b/src/main/java/minicraft/level/tile/ConnectTile.java index cb76e42ea..c6db32812 100644 --- a/src/main/java/minicraft/level/tile/ConnectTile.java +++ b/src/main/java/minicraft/level/tile/ConnectTile.java @@ -2,14 +2,15 @@ import minicraft.entity.Entity; -import minicraft.gfx.Sprite; +import minicraft.gfx.SpriteAnimation; import minicraft.gfx.SpriteLinker.SpriteType; import minicraft.level.Level; +// TODO Remove this. // IMPORTANT: This tile should never be used for anything, it only exists to allow tiles right next to the edge of the world to connect to it public class ConnectTile extends Tile { public ConnectTile() { - super("connector tile", Sprite.missingTexture(SpriteType.Tile)); + super("connector tile", new SpriteAnimation(SpriteType.Tile, "missing_tile")); } @Override diff --git a/src/main/java/minicraft/level/tile/DecorTile.java b/src/main/java/minicraft/level/tile/DecorTile.java index 624f1077e..4b86fd66e 100644 --- a/src/main/java/minicraft/level/tile/DecorTile.java +++ b/src/main/java/minicraft/level/tile/DecorTile.java @@ -4,7 +4,7 @@ import minicraft.entity.Direction; import minicraft.entity.Entity; import minicraft.entity.mob.Player; -import minicraft.gfx.SpriteLinker.LinkedSprite; +import minicraft.gfx.SpriteAnimation; import minicraft.gfx.SpriteLinker.SpriteType; import minicraft.item.Item; import minicraft.item.Items; @@ -12,17 +12,16 @@ import minicraft.level.Level; public class DecorTile extends Tile { + private static SpriteAnimation stoneSprite = new SpriteAnimation(SpriteType.Tile, "ornate_stone"); + private static SpriteAnimation obsidianSprite = new SpriteAnimation(SpriteType.Tile, "ornate_obsidian"); + protected Material type; protected DecorTile(Material type) { - super((type == Material.Obsidian ? "Ornate Obsidian" : type == Material.Stone ? "Ornate Stone" : "Decorated " + type.name()), (LinkedSprite) null); + super((type == Material.Obsidian ? "Ornate Obsidian" : type == Material.Stone ? "Ornate Stone" : "Decorated " + type.name()), + type == Material.Stone ? stoneSprite : obsidianSprite); this.type = type; maySpawn = true; - switch (type) { - case Stone: sprite = new LinkedSprite(SpriteType.Tile, "ornate_stone"); break; - case Obsidian: sprite = new LinkedSprite(SpriteType.Tile, "ornate_obsidian"); break; - default: - } } public boolean interact(Level level, int xt, int yt, Player player, Item item, Direction attackDir) { diff --git a/src/main/java/minicraft/level/tile/DirtTile.java b/src/main/java/minicraft/level/tile/DirtTile.java index 6ff405ba9..7293bc937 100644 --- a/src/main/java/minicraft/level/tile/DirtTile.java +++ b/src/main/java/minicraft/level/tile/DirtTile.java @@ -5,7 +5,7 @@ import minicraft.entity.mob.Player; import minicraft.gfx.Color; import minicraft.gfx.Screen; -import minicraft.gfx.SpriteLinker.LinkedSprite; +import minicraft.gfx.SpriteAnimation; import minicraft.gfx.SpriteLinker.SpriteType; import minicraft.item.Item; import minicraft.item.Items; @@ -14,10 +14,10 @@ import minicraft.level.Level; public class DirtTile extends Tile { - private static LinkedSprite[] levelSprite = new LinkedSprite[] { - new LinkedSprite(SpriteType.Tile, "dirt"), - new LinkedSprite(SpriteType.Tile, "gray_dirt"), - new LinkedSprite(SpriteType.Tile, "purple_dirt") + private static SpriteAnimation[] levelSprite = new SpriteAnimation[] { + new SpriteAnimation(SpriteType.Tile, "dirt"), + new SpriteAnimation(SpriteType.Tile, "gray_dirt"), + new SpriteAnimation(SpriteType.Tile, "purple_dirt") }; protected DirtTile(String name) { @@ -43,7 +43,7 @@ protected static int dIdx(int depth) { } public void render(Screen screen, Level level, int x, int y) { - levelSprite[dIdx(level.depth)].getSprite().render(screen, x * 16, y * 16, 0); + levelSprite[dIdx(level.depth)].render(screen, level, x, y); } public boolean interact(Level level, int xt, int yt, Player player, Item item, Direction attackDir) { diff --git a/src/main/java/minicraft/level/tile/DoorTile.java b/src/main/java/minicraft/level/tile/DoorTile.java index dd3883cfb..ae5ad8fbb 100644 --- a/src/main/java/minicraft/level/tile/DoorTile.java +++ b/src/main/java/minicraft/level/tile/DoorTile.java @@ -6,8 +6,7 @@ import minicraft.entity.mob.Mob; import minicraft.entity.mob.Player; import minicraft.gfx.Screen; -import minicraft.gfx.Sprite; -import minicraft.gfx.SpriteLinker.LinkedSprite; +import minicraft.gfx.SpriteAnimation; import minicraft.gfx.SpriteLinker.SpriteType; import minicraft.item.Item; import minicraft.item.Items; @@ -16,24 +15,24 @@ public class DoorTile extends Tile { protected Material type; - private LinkedSprite closedSprite; - private LinkedSprite openSprite; + private SpriteAnimation closedSprite; + private SpriteAnimation openSprite; protected DoorTile(Material type) { - super(type.name() + " Door", (LinkedSprite) null); + super(type.name() + " Door", (SpriteAnimation) null); this.type = type; switch (type) { case Wood: - closedSprite = new LinkedSprite(SpriteType.Tile, "wood_door").setSpriteDim(2, 0, 2, 2); - openSprite = new LinkedSprite(SpriteType.Tile, "wood_door").setSpriteSize(2, 2); + closedSprite = new SpriteAnimation(SpriteType.Tile, "wood_door"); + openSprite = new SpriteAnimation(SpriteType.Tile, "wood_door_opened"); break; case Stone: - closedSprite = new LinkedSprite(SpriteType.Tile, "stone_door").setSpriteDim(2, 0, 2, 2); - openSprite = new LinkedSprite(SpriteType.Tile, "stone_door").setSpriteSize(2, 2); + closedSprite = new SpriteAnimation(SpriteType.Tile, "stone_door"); + openSprite = new SpriteAnimation(SpriteType.Tile, "stone_door_opened"); break; case Obsidian: - closedSprite = new LinkedSprite(SpriteType.Tile, "obsidian_door").setSpriteDim(2, 0, 2, 2); - openSprite = new LinkedSprite(SpriteType.Tile, "obsidian_door").setSpriteSize(2, 2); + closedSprite = new SpriteAnimation(SpriteType.Tile, "obsidian_door"); + openSprite = new SpriteAnimation(SpriteType.Tile, "obsidian_door_opened"); break; } sprite = closedSprite; @@ -41,8 +40,8 @@ protected DoorTile(Material type) { public void render(Screen screen, Level level, int x, int y) { boolean closed = level.getData(x, y) == 0; - Sprite curSprite = (closed ? closedSprite : openSprite).getSprite(); - curSprite.render(screen, x * 16, y * 16); + SpriteAnimation curSprite = closed ? closedSprite : openSprite; + curSprite.render(screen, level, x, y); } public boolean interact(Level level, int xt, int yt, Player player, Item item, Direction attackDir) { diff --git a/src/main/java/minicraft/level/tile/ExplodedTile.java b/src/main/java/minicraft/level/tile/ExplodedTile.java index 435255569..af333f481 100644 --- a/src/main/java/minicraft/level/tile/ExplodedTile.java +++ b/src/main/java/minicraft/level/tile/ExplodedTile.java @@ -1,19 +1,14 @@ package minicraft.level.tile; import minicraft.entity.Entity; -import minicraft.gfx.ConnectorSprite; -import minicraft.gfx.SpriteLinker.LinkedSprite; +import minicraft.gfx.SpriteAnimation; import minicraft.gfx.SpriteLinker.SpriteType; import minicraft.level.Level; /// This class is for tiles WHILE THEY ARE EXPLODING public class ExplodedTile extends Tile { - private static ConnectorSprite sprite = new ConnectorSprite(ExplodedTile.class, new LinkedSprite(SpriteType.Tile, "exploded").setSpriteSize(3, 3), new LinkedSprite(SpriteType.Tile, "exploded").setSpriteDim(3, 0, 2, 2)) - { - public boolean connectsTo(Tile tile, boolean isSide) { - return !isSide || tile.connectsToLiquid(); - } - }; + private static SpriteAnimation sprite = new SpriteAnimation(SpriteType.Tile, "exploded") + .setConnectChecker((tile, side) -> tile.getClass() == ExplodedTile.class); protected ExplodedTile(String name) { super(name, sprite); diff --git a/src/main/java/minicraft/level/tile/FloorTile.java b/src/main/java/minicraft/level/tile/FloorTile.java index 3a330b9a7..dee708009 100644 --- a/src/main/java/minicraft/level/tile/FloorTile.java +++ b/src/main/java/minicraft/level/tile/FloorTile.java @@ -4,7 +4,7 @@ import minicraft.entity.Direction; import minicraft.entity.Entity; import minicraft.entity.mob.Player; -import minicraft.gfx.SpriteLinker.LinkedSprite; +import minicraft.gfx.SpriteAnimation; import minicraft.gfx.SpriteLinker.SpriteType; import minicraft.item.Item; import minicraft.item.Items; @@ -15,13 +15,13 @@ public class FloorTile extends Tile { protected Material type; protected FloorTile(Material type) { - super((type == Material.Wood ? "Wood Planks" : type == Material.Obsidian ? "Obsidian" : type.name() + " Bricks"), (LinkedSprite) null); + super((type == Material.Wood ? "Wood Planks" : type == Material.Obsidian ? "Obsidian" : type.name() + " Bricks"), (SpriteAnimation) null); this.type = type; maySpawn = true; switch (type) { - case Wood: sprite = new LinkedSprite(SpriteType.Tile, "wood_floor"); break; - case Stone: sprite = new LinkedSprite(SpriteType.Tile, "stone_floor"); break; - case Obsidian: sprite = new LinkedSprite(SpriteType.Tile, "obsidian_floor"); break; + case Wood: sprite = new SpriteAnimation(SpriteType.Tile, "wood_floor"); break; + case Stone: sprite = new SpriteAnimation(SpriteType.Tile, "stone_floor"); break; + case Obsidian: sprite = new SpriteAnimation(SpriteType.Tile, "obsidian_floor"); break; } } diff --git a/src/main/java/minicraft/level/tile/FlowerTile.java b/src/main/java/minicraft/level/tile/FlowerTile.java index 1325ca214..8c0bf8176 100644 --- a/src/main/java/minicraft/level/tile/FlowerTile.java +++ b/src/main/java/minicraft/level/tile/FlowerTile.java @@ -4,9 +4,8 @@ import minicraft.entity.Direction; import minicraft.entity.mob.Mob; import minicraft.entity.mob.Player; -import minicraft.gfx.ConnectorSprite; import minicraft.gfx.Screen; -import minicraft.gfx.SpriteLinker.LinkedSprite; +import minicraft.gfx.SpriteAnimation; import minicraft.gfx.SpriteLinker.SpriteType; import minicraft.item.Item; import minicraft.item.Items; @@ -15,10 +14,11 @@ import minicraft.level.Level; public class FlowerTile extends Tile { - private static final LinkedSprite flowerSprite = new LinkedSprite(SpriteType.Tile, "grass").setSpriteDim(3, 2, 1, 1); + private static final SpriteAnimation flowerSprite0 = new SpriteAnimation(SpriteType.Tile, "flower_shape0"); + private static final SpriteAnimation flowerSprite1 = new SpriteAnimation(SpriteType.Tile, "flower_shape1"); protected FlowerTile(String name) { - super(name, (ConnectorSprite)null); + super(name, (SpriteAnimation) null); connectsToGrass = true; maySpawn = true; } @@ -41,15 +41,9 @@ public boolean tick(Level level, int xt, int yt) { public void render(Screen screen, Level level, int x, int y) { Tiles.get("Grass").render(screen, level, x, y); - int data = level.getData(x, y); int shape = (data / 16) % 2; - - x = x << 4; - y = y << 4; - - flowerSprite.getSprite().render(screen, x + 8 * shape, y); - flowerSprite.getSprite().render(screen, x + 8 * (shape == 0 ? 1 : 0), y + 8); + (shape == 0 ? flowerSprite0 : flowerSprite1).render(screen, level, x, y); } public boolean interact(Level level, int x, int y, Player player, Item item, Direction attackDir) { diff --git a/src/main/java/minicraft/level/tile/GrassTile.java b/src/main/java/minicraft/level/tile/GrassTile.java index 4cc41cee1..8af8533c8 100644 --- a/src/main/java/minicraft/level/tile/GrassTile.java +++ b/src/main/java/minicraft/level/tile/GrassTile.java @@ -3,9 +3,8 @@ import minicraft.core.io.Sound; import minicraft.entity.Direction; import minicraft.entity.mob.Player; -import minicraft.gfx.ConnectorSprite; import minicraft.gfx.Screen; -import minicraft.gfx.SpriteLinker.LinkedSprite; +import minicraft.gfx.SpriteAnimation; import minicraft.gfx.SpriteLinker.SpriteType; import minicraft.item.Item; import minicraft.item.Items; @@ -14,18 +13,11 @@ import minicraft.level.Level; public class GrassTile extends Tile { - private static ConnectorSprite sprite = new ConnectorSprite(GrassTile.class, - new LinkedSprite(SpriteType.Tile, "grass").setSpriteSize(3, 3).setMirror(3), new LinkedSprite(SpriteType.Tile, "grass").setSpriteDim(3, 0, 2, 2)) - { - public boolean connectsTo(Tile tile, boolean isSide) { - if(!isSide) return true; - return tile.connectsToGrass; - } - }; + private static SpriteAnimation sprite = new SpriteAnimation(SpriteType.Tile, "grass") + .setConnectChecker((tile, side) -> !side || tile.connectsToGrass); protected GrassTile(String name) { super(name, sprite); - csprite.sides = csprite.sparse; connectsToGrass = true; maySpawn = true; } @@ -48,7 +40,7 @@ public boolean tick(Level level, int xt, int yt) { @Override public void render(Screen screen, Level level, int x, int y) { - sprite.sparse.setColor(DirtTile.dCol(level.depth)); + Tiles.get("dirt").render(screen, level, x, y); sprite.render(screen, level, x, y); } diff --git a/src/main/java/minicraft/level/tile/HardRockTile.java b/src/main/java/minicraft/level/tile/HardRockTile.java index 127de90a7..3d31dee87 100644 --- a/src/main/java/minicraft/level/tile/HardRockTile.java +++ b/src/main/java/minicraft/level/tile/HardRockTile.java @@ -9,10 +9,8 @@ import minicraft.entity.particle.SmashParticle; import minicraft.entity.particle.TextParticle; import minicraft.gfx.Color; -import minicraft.gfx.ConnectorSprite; import minicraft.gfx.Screen; -import minicraft.gfx.Sprite; -import minicraft.gfx.SpriteLinker.LinkedSprite; +import minicraft.gfx.SpriteAnimation; import minicraft.gfx.SpriteLinker.SpriteType; import minicraft.item.Item; import minicraft.item.Items; @@ -22,8 +20,8 @@ public class HardRockTile extends Tile { // Theoretically the full sprite should never be used, so we can use a placeholder - private static ConnectorSprite sprite = new ConnectorSprite(HardRockTile.class, new LinkedSprite(SpriteType.Tile, "hardrock") - .setSpriteSize(3, 3).setMirror(3), new LinkedSprite(SpriteType.Tile, "hardrock").setSpriteDim(3, 0, 2, 2).setMirror(3), Sprite.missingTexture(SpriteType.Tile)); + private static SpriteAnimation sprite = new SpriteAnimation(SpriteType.Tile, "hardrock") + .setConnectChecker((tile, side) -> tile.getClass() == HardRockTile.class); protected HardRockTile(String name) { super(name, sprite); @@ -76,7 +74,7 @@ public void hurt(Level level, int x, int y, int dmg) { @Override public void render(Screen screen, Level level, int x, int y) { - sprite.sparse.setColor(DirtTile.dCol(level.depth)); + Tiles.get("dirt").render(screen, level, x, y); super.render(screen, level, x, y); } diff --git a/src/main/java/minicraft/level/tile/HoleTile.java b/src/main/java/minicraft/level/tile/HoleTile.java index a95f90505..bbf2fa5fb 100644 --- a/src/main/java/minicraft/level/tile/HoleTile.java +++ b/src/main/java/minicraft/level/tile/HoleTile.java @@ -1,20 +1,14 @@ package minicraft.level.tile; import minicraft.entity.Entity; -import minicraft.gfx.ConnectorSprite; import minicraft.gfx.Screen; -import minicraft.gfx.SpriteLinker.LinkedSprite; +import minicraft.gfx.SpriteAnimation; import minicraft.gfx.SpriteLinker.SpriteType; import minicraft.level.Level; public class HoleTile extends Tile { - private static ConnectorSprite sprite = new ConnectorSprite(HoleTile.class, new LinkedSprite(SpriteType.Tile, "hole").setSpriteSize(3, 3) - .setMirror(3), new LinkedSprite(SpriteType.Tile, "hole").setSpriteDim(3, 0, 2, 2)) - { - public boolean connectsTo(Tile tile, boolean isSide) { - return tile.connectsToLiquid(); - } - }; + private static SpriteAnimation sprite = new SpriteAnimation(SpriteType.Tile, "hole") + .setConnectChecker((tile, side) -> tile.connectsToLiquid()); protected HoleTile(String name) { super(name, sprite); @@ -24,7 +18,7 @@ protected HoleTile(String name) { @Override public void render(Screen screen, Level level, int x, int y) { - sprite.sparse.setColor(DirtTile.dCol(level.depth)); + Tiles.get("dirt").render(screen, level, x, y); sprite.render(screen, level, x, y); } diff --git a/src/main/java/minicraft/level/tile/InfiniteFallTile.java b/src/main/java/minicraft/level/tile/InfiniteFallTile.java index 5610eb4b0..19c798890 100644 --- a/src/main/java/minicraft/level/tile/InfiniteFallTile.java +++ b/src/main/java/minicraft/level/tile/InfiniteFallTile.java @@ -6,13 +6,13 @@ import minicraft.entity.mob.AirWizard; import minicraft.entity.mob.Player; import minicraft.gfx.Screen; -import minicraft.gfx.SpriteLinker.LinkedSprite; +import minicraft.gfx.SpriteAnimation; import minicraft.level.Level; public class InfiniteFallTile extends Tile { protected InfiniteFallTile(String name) { - super(name, (LinkedSprite)null); + super(name, (SpriteAnimation)null); } @Override diff --git a/src/main/java/minicraft/level/tile/LavaBrickTile.java b/src/main/java/minicraft/level/tile/LavaBrickTile.java index c4896dae4..24c9f04b9 100644 --- a/src/main/java/minicraft/level/tile/LavaBrickTile.java +++ b/src/main/java/minicraft/level/tile/LavaBrickTile.java @@ -5,7 +5,7 @@ import minicraft.entity.Entity; import minicraft.entity.mob.Mob; import minicraft.entity.mob.Player; -import minicraft.gfx.Sprite; +import minicraft.gfx.SpriteAnimation; import minicraft.gfx.SpriteLinker.SpriteType; import minicraft.item.Item; import minicraft.item.ToolItem; @@ -14,7 +14,7 @@ public class LavaBrickTile extends Tile { protected LavaBrickTile(String name) { - super(name, Sprite.missingTexture(SpriteType.Tile)); + super(name, new SpriteAnimation(SpriteType.Tile, "missing_tile")); } public boolean interact(Level level, int xt, int yt, Player player, Item item, Direction attackDir) { diff --git a/src/main/java/minicraft/level/tile/LavaTile.java b/src/main/java/minicraft/level/tile/LavaTile.java index b78c3f6c6..20dd64f6e 100644 --- a/src/main/java/minicraft/level/tile/LavaTile.java +++ b/src/main/java/minicraft/level/tile/LavaTile.java @@ -1,36 +1,20 @@ package minicraft.level.tile; import minicraft.entity.Entity; -import minicraft.gfx.ConnectorSprite; -import minicraft.gfx.Screen; -import minicraft.gfx.Sprite; -import minicraft.gfx.SpriteLinker.LinkedSprite; +import minicraft.gfx.SpriteAnimation; import minicraft.gfx.SpriteLinker.SpriteType; import minicraft.level.Level; public class LavaTile extends Tile { - private ConnectorSprite sprite = new ConnectorSprite(LavaTile.class, new LinkedSprite(SpriteType.Tile, "lava").setSpriteSize(3, 3).setMirror(3), null) - { - public boolean connectsTo(Tile tile, boolean isSide) { - return tile.connectsToFluid; - } - }; + private static SpriteAnimation sprite = new SpriteAnimation(SpriteType.Tile, "lava") + .setConnectChecker((tile, side) -> tile.connectsToFluid); protected LavaTile(String name) { - super(name, (ConnectorSprite)null); - super.csprite = sprite; + super(name, sprite); connectsToSand = true; connectsToFluid = true; } - @Override - public void render(Screen screen, Level level, int x, int y) { - long seed = (tickCount + (x / 2 - y) * 4311) / 10 * 54687121l + x * 3271612l + y * 3412987161l; - sprite.full = Sprite.randomTiles(seed, "lava"); - sprite.sparse.setColor(DirtTile.dCol(level.depth)); - sprite.render(screen, level, x, y); - } - @Override public boolean mayPass(Level level, int x, int y, Entity e) { return e.canSwim(); diff --git a/src/main/java/minicraft/level/tile/MaterialTile.java b/src/main/java/minicraft/level/tile/MaterialTile.java index ee8c568de..45a97bfce 100644 --- a/src/main/java/minicraft/level/tile/MaterialTile.java +++ b/src/main/java/minicraft/level/tile/MaterialTile.java @@ -4,7 +4,7 @@ import minicraft.entity.Direction; import minicraft.entity.Entity; import minicraft.entity.mob.Player; -import minicraft.gfx.SpriteLinker.LinkedSprite; +import minicraft.gfx.SpriteAnimation; import minicraft.gfx.SpriteLinker.SpriteType; import minicraft.item.Item; import minicraft.item.Items; @@ -15,12 +15,12 @@ public class MaterialTile extends Tile { protected Material type; protected MaterialTile(Material type) { - super((type == Material.Stone ? "Stone" : type == Material.Obsidian ? "Raw Obsidian" :type.name()), (LinkedSprite) null); + super((type == Material.Stone ? "Stone" : type == Material.Obsidian ? "Raw Obsidian" :type.name()), (SpriteAnimation) null); this.type = type; maySpawn = true; switch (type) { - case Stone: sprite = new LinkedSprite(SpriteType.Tile, "stone"); break; - case Obsidian: sprite = new LinkedSprite(SpriteType.Tile, "obsidian"); break; + case Stone: sprite = new SpriteAnimation(SpriteType.Tile, "stone"); break; + case Obsidian: sprite = new SpriteAnimation(SpriteType.Tile, "obsidian"); break; default: } } diff --git a/src/main/java/minicraft/level/tile/OreTile.java b/src/main/java/minicraft/level/tile/OreTile.java index 6c5d64067..38e07b0ea 100644 --- a/src/main/java/minicraft/level/tile/OreTile.java +++ b/src/main/java/minicraft/level/tile/OreTile.java @@ -10,7 +10,7 @@ import minicraft.entity.particle.TextParticle; import minicraft.gfx.Color; import minicraft.gfx.Screen; -import minicraft.gfx.SpriteLinker.LinkedSprite; +import minicraft.gfx.SpriteAnimation; import minicraft.gfx.SpriteLinker.SpriteType; import minicraft.item.Item; import minicraft.item.Items; @@ -24,16 +24,16 @@ public class OreTile extends Tile { private final OreType type; public enum OreType { - Iron (Items.get("Iron Ore"), new LinkedSprite(SpriteType.Tile, "iron_ore")), - Lapis (Items.get("Lapis"), new LinkedSprite(SpriteType.Tile, "lapis_ore")), - Gold (Items.get("Gold Ore"), new LinkedSprite(SpriteType.Tile, "gold_ore")), - Gem (Items.get("Gem"), new LinkedSprite(SpriteType.Tile, "gem_ore")), - Cloud (Items.get("Cloud Ore"), new LinkedSprite(SpriteType.Tile, "cloud_ore")); + Iron (Items.get("Iron Ore"), new SpriteAnimation(SpriteType.Tile, "iron_ore")), + Lapis (Items.get("Lapis"), new SpriteAnimation(SpriteType.Tile, "lapis_ore")), + Gold (Items.get("Gold Ore"), new SpriteAnimation(SpriteType.Tile, "gold_ore")), + Gem (Items.get("Gem"), new SpriteAnimation(SpriteType.Tile, "gem_ore")), + Cloud (Items.get("Cloud Ore"), new SpriteAnimation(SpriteType.Tile, "cloud_ore")); private final Item drop; - public final LinkedSprite sheet; + public final SpriteAnimation sheet; - OreType(Item drop, LinkedSprite sheet) { + OreType(Item drop, SpriteAnimation sheet) { this.drop = drop; this.sheet = sheet; } @@ -49,8 +49,11 @@ protected OreTile(OreType o) { } public void render(Screen screen, Level level, int x, int y) { - sprite.setColor(DirtTile.dCol(level.depth)); - sprite.getSprite().render(screen, x * 16, y * 16); + if (type == OreType.Cloud) + Tiles.get("cloud").render(screen, level, x, y); + else + Tiles.get("dirt").render(screen, level, x, y); + sprite.render(screen, level, x, y); } public boolean mayPass(Level level, int x, int y, Entity e) { diff --git a/src/main/java/minicraft/level/tile/PathTile.java b/src/main/java/minicraft/level/tile/PathTile.java index c5db03774..ff316417f 100644 --- a/src/main/java/minicraft/level/tile/PathTile.java +++ b/src/main/java/minicraft/level/tile/PathTile.java @@ -3,7 +3,7 @@ import minicraft.core.io.Sound; import minicraft.entity.Direction; import minicraft.entity.mob.Player; -import minicraft.gfx.SpriteLinker.LinkedSprite; +import minicraft.gfx.SpriteAnimation; import minicraft.gfx.SpriteLinker.SpriteType; import minicraft.item.Item; import minicraft.item.Items; @@ -12,7 +12,7 @@ import minicraft.level.Level; public class PathTile extends Tile { - private static LinkedSprite sprite = new LinkedSprite(SpriteType.Tile, "path"); + private static SpriteAnimation sprite = new SpriteAnimation(SpriteType.Tile, "path"); public PathTile(String name) { super(name, sprite); diff --git a/src/main/java/minicraft/level/tile/RockTile.java b/src/main/java/minicraft/level/tile/RockTile.java index 1bcc6e50d..6533263fc 100644 --- a/src/main/java/minicraft/level/tile/RockTile.java +++ b/src/main/java/minicraft/level/tile/RockTile.java @@ -10,9 +10,8 @@ import minicraft.entity.particle.SmashParticle; import minicraft.entity.particle.TextParticle; import minicraft.gfx.Color; -import minicraft.gfx.ConnectorSprite; import minicraft.gfx.Screen; -import minicraft.gfx.SpriteLinker.LinkedSprite; +import minicraft.gfx.SpriteAnimation; import minicraft.gfx.SpriteLinker.SpriteType; import minicraft.item.Item; import minicraft.item.Items; @@ -23,8 +22,8 @@ // This is the normal stone you see underground and on the surface, that drops coal and stone. public class RockTile extends Tile { - private ConnectorSprite sprite = new ConnectorSprite(RockTile.class, new LinkedSprite(SpriteType.Tile, "rock").setSpriteSize(3, 3).setMirror(3), - new LinkedSprite(SpriteType.Tile, "rock").setSpriteDim(3, 2, 2, 2).setMirror(3), new LinkedSprite(SpriteType.Tile, "rock").setSpriteDim(3, 0, 2, 2).setMirror(3)); + private static SpriteAnimation sprite = new SpriteAnimation(SpriteType.Tile, "rock") + .setConnectChecker((tile, side) -> tile.getClass() == RockTile.class); private boolean dropCoal = false; private int maxHealth = 50; @@ -32,12 +31,11 @@ public class RockTile extends Tile { private int damage; protected RockTile(String name) { - super(name, (ConnectorSprite)null); - csprite = sprite; + super(name, sprite); } public void render(Screen screen, Level level, int x, int y) { - sprite.sparse.setColor(DirtTile.dCol(level.depth)); + Tiles.get("dirt").render(screen, level, x, y); sprite.render(screen, level, x, y); } diff --git a/src/main/java/minicraft/level/tile/SandTile.java b/src/main/java/minicraft/level/tile/SandTile.java index 0ac472594..70bd9ec5b 100644 --- a/src/main/java/minicraft/level/tile/SandTile.java +++ b/src/main/java/minicraft/level/tile/SandTile.java @@ -5,9 +5,9 @@ import minicraft.entity.Entity; import minicraft.entity.mob.Mob; import minicraft.entity.mob.Player; -import minicraft.gfx.ConnectorSprite; +import minicraft.entity.particle.SandParticle; import minicraft.gfx.Screen; -import minicraft.gfx.SpriteLinker.LinkedSprite; +import minicraft.gfx.SpriteAnimation; import minicraft.gfx.SpriteLinker.SpriteType; import minicraft.item.Item; import minicraft.item.Items; @@ -16,47 +16,27 @@ import minicraft.level.Level; public class SandTile extends Tile { - static LinkedSprite steppedOn = new LinkedSprite(SpriteType.Tile, "sand_stepped"), - normal = new LinkedSprite(SpriteType.Tile, "sand").setSpriteDim(3, 0, 2, 2); - - private ConnectorSprite sprite = new ConnectorSprite(SandTile.class, new LinkedSprite(SpriteType.Tile, "sand").setSpriteSize(3, 3).setMirror(3), normal) - { - public boolean connectsTo(Tile tile, boolean isSide) { - if(!isSide) return true; - return tile.connectsToSand; - } - }; + private static SpriteAnimation sprite = new SpriteAnimation(SpriteType.Tile, "sand") + .setConnectChecker((tile, side) -> !side || tile.connectsToSand); protected SandTile(String name) { - super(name, (ConnectorSprite)null); - csprite = sprite; + super(name, sprite); connectsToSand = true; maySpawn = true; } public void render(Screen screen, Level level, int x, int y) { - boolean steppedOn = level.getData(x, y) > 0; - - if(steppedOn) csprite.full = SandTile.steppedOn; - else csprite.full = SandTile.normal; - - csprite.sparse.setColor(DirtTile.dCol(level.depth)); - - csprite.render(screen, level, x, y); + Tiles.get("dirt").render(screen, level, x, y); + sprite.render(screen, level, x, y); } public boolean tick(Level level, int x, int y) { - int damage = level.getData(x, y); - if (damage > 0) { - level.setData(x, y, damage - 1); - return true; - } return false; } public void steppedOn(Level level, int x, int y, Entity entity) { if (entity instanceof Mob) { - level.setData(x, y, 10); + level.add(new SandParticle(x, y)); } } diff --git a/src/main/java/minicraft/level/tile/SaplingTile.java b/src/main/java/minicraft/level/tile/SaplingTile.java index 1b272bf8f..f000acf9e 100644 --- a/src/main/java/minicraft/level/tile/SaplingTile.java +++ b/src/main/java/minicraft/level/tile/SaplingTile.java @@ -4,12 +4,12 @@ import minicraft.entity.Direction; import minicraft.entity.mob.Mob; import minicraft.gfx.Screen; -import minicraft.gfx.SpriteLinker.LinkedSprite; +import minicraft.gfx.SpriteAnimation; import minicraft.gfx.SpriteLinker.SpriteType; import minicraft.level.Level; public class SaplingTile extends Tile { - private static LinkedSprite sprite = new LinkedSprite(SpriteType.Tile, "sapling"); + private static SpriteAnimation sprite = new SpriteAnimation(SpriteType.Tile, "sapling"); private Tile onType; private Tile growsTo; @@ -26,8 +26,7 @@ protected SaplingTile(String name, Tile onType, Tile growsTo) { public void render(Screen screen, Level level, int x, int y) { onType.render(screen, level, x, y); - - sprite.getSprite().render(screen, x * 16, y * 16); + sprite.render(screen, level, x, y); } public boolean tick(Level level, int x, int y) { diff --git a/src/main/java/minicraft/level/tile/StairsTile.java b/src/main/java/minicraft/level/tile/StairsTile.java index 6dca99e50..3624971ea 100644 --- a/src/main/java/minicraft/level/tile/StairsTile.java +++ b/src/main/java/minicraft/level/tile/StairsTile.java @@ -7,15 +7,15 @@ import minicraft.entity.furniture.Furniture; import minicraft.entity.mob.Player; import minicraft.gfx.Screen; -import minicraft.gfx.SpriteLinker.LinkedSprite; +import minicraft.gfx.SpriteAnimation; import minicraft.gfx.SpriteLinker.SpriteType; import minicraft.item.Item; import minicraft.item.PowerGloveItem; import minicraft.level.Level; public class StairsTile extends Tile { - private static LinkedSprite down = new LinkedSprite(SpriteType.Tile, "stairs_down"); - private static LinkedSprite up = new LinkedSprite(SpriteType.Tile, "stairs_up"); + private static SpriteAnimation down = new SpriteAnimation(SpriteType.Tile, "stairs_down"); + private static SpriteAnimation up = new SpriteAnimation(SpriteType.Tile, "stairs_up"); protected StairsTile(String name, boolean leadsUp) { super(name, leadsUp ? up : down); @@ -24,7 +24,11 @@ protected StairsTile(String name, boolean leadsUp) { @Override public void render(Screen screen, Level level, int x, int y) { - sprite.getSprite().render(screen, x * 16, y * 16, 0, DirtTile.dCol(level.depth)); + if (level.depth == 1) + Tiles.get("cloud").render(screen, level, x, y); + else + Tiles.get("dirt").render(screen, level, x, y); + sprite.render(screen, level, x, y); } public boolean mayPass(Level level, int x, int y, Entity e) { diff --git a/src/main/java/minicraft/level/tile/Tile.java b/src/main/java/minicraft/level/tile/Tile.java index b81478bf1..78534e5b7 100644 --- a/src/main/java/minicraft/level/tile/Tile.java +++ b/src/main/java/minicraft/level/tile/Tile.java @@ -7,9 +7,8 @@ import minicraft.entity.Entity; import minicraft.entity.mob.Mob; import minicraft.entity.mob.Player; -import minicraft.gfx.ConnectorSprite; import minicraft.gfx.Screen; -import minicraft.gfx.SpriteLinker.LinkedSprite; +import minicraft.gfx.SpriteAnimation; import minicraft.item.Item; import minicraft.item.ToolType; import minicraft.level.Level; @@ -45,27 +44,15 @@ public ToolType getRequiredTool() { public boolean connectsToGrass = false; public boolean connectsToSand = false; public boolean connectsToFluid = false; - public int light; - protected boolean maySpawn; + public int light = 1; + protected boolean maySpawn = false; - protected LinkedSprite sprite; - protected ConnectorSprite csprite; + protected SpriteAnimation sprite = null; - { - light = 1; - maySpawn = false; - sprite = null; - csprite = null; - } - - protected Tile(String name, LinkedSprite sprite) { + protected Tile(String name, SpriteAnimation sprite) { this.name = name.toUpperCase(); this.sprite = sprite; } - protected Tile(String name, ConnectorSprite sprite) { - this.name = name.toUpperCase(); - csprite = sprite; - } /** This method is used by tiles to specify the default "data" they have in a level's data array. @@ -77,10 +64,7 @@ public int getDefaultData() { /** Render method, used in sub-classes */ public void render(Screen screen, Level level, int x, int y) { - if (sprite != null) - sprite.getSprite().render(screen, x << 4, y << 4); - if (csprite != null) - csprite.render(screen, level, x, y); + sprite.render(screen, level, x, y); } public boolean maySpawn() { return maySpawn; } diff --git a/src/main/java/minicraft/level/tile/Tiles.java b/src/main/java/minicraft/level/tile/Tiles.java index e0c9d15e6..4d9b5d48e 100644 --- a/src/main/java/minicraft/level/tile/Tiles.java +++ b/src/main/java/minicraft/level/tile/Tiles.java @@ -187,7 +187,8 @@ public static Tile get(String name) { overflowCheck++; if(overflowCheck > 50) { - CrashHandler.crashHandle(new StackOverflowError("Tiles#get: " + name), new CrashHandler.ErrorInfo("Tile fetching Stacking", CrashHandler.ErrorInfo.ErrorType.SERIOUS, "STACKOVERFLOW prevented in Tiles.get(), on: " + name)); + CrashHandler.crashHandle(new StackOverflowError("Tiles#get: " + name), new CrashHandler.ErrorInfo("Tile fetching Stacking", + CrashHandler.ErrorInfo.ErrorType.SERIOUS, "STACKOVERFLOW prevented in Tiles.get(), on: " + name)); } //System.out.println("Fetching tile " + name); diff --git a/src/main/java/minicraft/level/tile/TorchTile.java b/src/main/java/minicraft/level/tile/TorchTile.java index 44f8d27be..c3dd15905 100644 --- a/src/main/java/minicraft/level/tile/TorchTile.java +++ b/src/main/java/minicraft/level/tile/TorchTile.java @@ -6,7 +6,7 @@ import minicraft.entity.Direction; import minicraft.entity.mob.Player; import minicraft.gfx.Screen; -import minicraft.gfx.SpriteLinker.LinkedSprite; +import minicraft.gfx.SpriteAnimation; import minicraft.gfx.SpriteLinker.SpriteType; import minicraft.item.Item; import minicraft.item.Items; @@ -31,7 +31,7 @@ public static TorchTile getTorchTile(Tile onTile) { } private TorchTile(Tile onType) { - super("Torch "+ onType.name, new LinkedSprite(SpriteType.Item, "torch")); + super("Torch "+ onType.name, new SpriteAnimation(SpriteType.Tile, "torch")); this.onType = onType; this.connectsToSand = onType.connectsToSand; this.connectsToGrass = onType.connectsToGrass; @@ -40,7 +40,7 @@ private TorchTile(Tile onType) { public void render(Screen screen, Level level, int x, int y) { onType.render(screen, level, x, y); - sprite.getSprite().render(screen, x * 16 + 4, y * 16 + 4); + sprite.render(screen, level, x, y); } public int getLightRadius(Level level, int x, int y) { diff --git a/src/main/java/minicraft/level/tile/TreeTile.java b/src/main/java/minicraft/level/tile/TreeTile.java index ff75812b1..aed54b1ae 100644 --- a/src/main/java/minicraft/level/tile/TreeTile.java +++ b/src/main/java/minicraft/level/tile/TreeTile.java @@ -1,7 +1,6 @@ package minicraft.level.tile; import minicraft.core.Game; -import minicraft.core.Renderer; import minicraft.core.io.Sound; import minicraft.entity.Direction; import minicraft.entity.Entity; @@ -10,9 +9,10 @@ import minicraft.entity.particle.SmashParticle; import minicraft.entity.particle.TextParticle; import minicraft.gfx.Color; -import minicraft.gfx.ConnectorSprite; import minicraft.gfx.Screen; -import minicraft.gfx.SpriteSheet; +import minicraft.gfx.Sprite; +import minicraft.gfx.SpriteAnimation; +import minicraft.gfx.SpriteLinker.LinkedSprite; import minicraft.gfx.SpriteLinker.SpriteType; import minicraft.item.Item; import minicraft.item.Items; @@ -23,9 +23,11 @@ import minicraft.screen.QuestsDisplay; public class TreeTile extends Tile { + private static LinkedSprite treeSprite = new LinkedSprite(SpriteType.Tile, "tree"); + private static LinkedSprite treeSpriteFull = new LinkedSprite(SpriteType.Tile, "tree_full"); protected TreeTile(String name) { - super(name, (ConnectorSprite)null); + super(name, (SpriteAnimation)null); connectsToGrass = true; } @@ -41,30 +43,31 @@ public void render(Screen screen, Level level, int x, int y) { boolean dl = level.getTile(x - 1, y + 1) == this; boolean dr = level.getTile(x + 1, y + 1) == this; - SpriteSheet sprite = Renderer.spriteLinker.getSpriteSheet(SpriteType.Tile, "tree"); + Sprite sprite = treeSprite.getSprite(); + Sprite spriteFull = treeSpriteFull.getSprite(); if (u && ul && l) { - screen.render(x * 16 + 0, y * 16 + 0, 1, 1, 0, sprite); + screen.render(x * 16 + 0, y * 16 + 0, spriteFull.spritePixels[0][1]); } else { - screen.render(x * 16 + 0, y * 16 + 0, 0, 0, 0, sprite); + screen.render(x * 16 + 0, y * 16 + 0, sprite.spritePixels[0][0]); } if (u && ur && r) { - screen.render(x * 16 + 8, y * 16 + 0, 1, 2, 0, sprite); + screen.render(x * 16 + 8, y * 16 + 0, spriteFull.spritePixels[0][0]); } else { - screen.render(x * 16 + 8, y * 16 + 0, 1, 0, 0, sprite); + screen.render(x * 16 + 8, y * 16 + 0, sprite.spritePixels[0][1]); } if (d && dl && l) { - screen.render(x * 16 + 0, y * 16 + 8, 1, 2, 0, sprite); + screen.render(x * 16 + 0, y * 16 + 8, spriteFull.spritePixels[1][1]); } else { - screen.render(x * 16 + 0, y * 16 + 8, 0, 1, 0, sprite); + screen.render(x * 16 + 0, y * 16 + 8, sprite.spritePixels[1][0]); } if (d && dr && r) { - screen.render(x * 16 + 8, y * 16 + 8, 1, 1, 0, sprite); + screen.render(x * 16 + 8, y * 16 + 8, spriteFull.spritePixels[1][0]); } else { - screen.render(x * 16 + 8, y * 16 + 8, 1, 3, 0, sprite); + screen.render(x * 16 + 8, y * 16 + 8, sprite.spritePixels[1][1]); } } diff --git a/src/main/java/minicraft/level/tile/WallTile.java b/src/main/java/minicraft/level/tile/WallTile.java index 2fce5852e..2c6027f48 100644 --- a/src/main/java/minicraft/level/tile/WallTile.java +++ b/src/main/java/minicraft/level/tile/WallTile.java @@ -10,8 +10,7 @@ import minicraft.entity.particle.SmashParticle; import minicraft.entity.particle.TextParticle; import minicraft.gfx.Color; -import minicraft.gfx.ConnectorSprite; -import minicraft.gfx.SpriteLinker.LinkedSprite; +import minicraft.gfx.SpriteAnimation; import minicraft.gfx.SpriteLinker.SpriteType; import minicraft.item.Item; import minicraft.item.Items; @@ -19,20 +18,23 @@ import minicraft.level.Level; public class WallTile extends Tile { + private static SpriteAnimation wood = new SpriteAnimation(SpriteType.Tile, "wood_wall") + .setConnectChecker((tile, side) -> tile.getClass() == WallTile.class); + private static SpriteAnimation stone = new SpriteAnimation(SpriteType.Tile, "stone_wall") + .setConnectChecker((tile, side) -> tile.getClass() == WallTile.class); + private static SpriteAnimation obsidian = new SpriteAnimation(SpriteType.Tile, "obsidian_wall") + .setConnectChecker((tile, side) -> tile.getClass() == WallTile.class); private static final String obrickMsg = "minicraft.notification.defeat_air_wizard_first"; protected Material type; protected WallTile(Material type) { - super(type.name() + " Wall", (ConnectorSprite) null); + super(type.name() + " Wall", (SpriteAnimation) null); this.type = type; switch (type) { - case Wood: csprite = new ConnectorSprite(WallTile.class, new LinkedSprite(SpriteType.Tile , "wood_wall").setSpriteSize(3, 3).setMirror(3), - new LinkedSprite(SpriteType.Tile , "wood_wall").setSpriteDim(3, 0, 2, 2).setMirror(3), new LinkedSprite(SpriteType.Tile , "wood_wall").setSpriteDim(1, 1, 2, 2).setOnePixel(true)); break; - case Stone: csprite = new ConnectorSprite(WallTile.class, new LinkedSprite(SpriteType.Tile , "stone_wall").setSpriteSize(3, 3).setMirror(3), - new LinkedSprite(SpriteType.Tile , "stone_wall").setSpriteDim(3, 0, 2, 2).setMirror(3), new LinkedSprite(SpriteType.Tile , "stone_wall").setSpriteDim(1, 1, 2, 2).setOnePixel(true)); break; - case Obsidian: csprite = new ConnectorSprite(WallTile.class, new LinkedSprite(SpriteType.Tile , "obsidian_wall").setSpriteSize(3, 3).setMirror(3), - new LinkedSprite(SpriteType.Tile , "obsidian_wall").setSpriteDim(3, 0, 2, 2).setMirror(3), new LinkedSprite(SpriteType.Tile , "obsidian_wall").setSpriteDim(1, 1, 2, 2).setOnePixel(true)); break; + case Wood: sprite = wood; break; + case Stone: sprite = stone; break; + case Obsidian: sprite = obsidian; break; } } diff --git a/src/main/java/minicraft/level/tile/WaterTile.java b/src/main/java/minicraft/level/tile/WaterTile.java index df978f9d5..060808254 100644 --- a/src/main/java/minicraft/level/tile/WaterTile.java +++ b/src/main/java/minicraft/level/tile/WaterTile.java @@ -1,32 +1,23 @@ package minicraft.level.tile; import minicraft.entity.Entity; -import minicraft.gfx.ConnectorSprite; import minicraft.gfx.Screen; -import minicraft.gfx.Sprite; -import minicraft.gfx.SpriteLinker.LinkedSprite; +import minicraft.gfx.SpriteAnimation; import minicraft.gfx.SpriteLinker.SpriteType; import minicraft.level.Level; public class WaterTile extends Tile { - private ConnectorSprite sprite = new ConnectorSprite(WaterTile.class, new LinkedSprite(SpriteType.Tile, "water").setSpriteSize(3, 3).setMirror(3), null) - { - public boolean connectsTo(Tile tile, boolean isSide) { - return tile.connectsToFluid; - } - }; + private static SpriteAnimation sprite = new SpriteAnimation(SpriteType.Tile, "water") + .setConnectChecker((tile, side) -> tile.connectsToFluid); protected WaterTile(String name) { - super(name, (ConnectorSprite)null); - csprite = sprite; + super(name, sprite); connectsToFluid = true; } @Override public void render(Screen screen, Level level, int x, int y) { - long seed = (tickCount + (x / 2 - y) * 4311) / 10 * 54687121l + x * 3271612l + y * 3412987161l; - sprite.full = Sprite.randomTiles(seed, "water"); - sprite.sparse.setColor(DirtTile.dCol(level.depth)); + Tiles.get("dirt").render(screen, level, x, y); sprite.render(screen, level, x, y); } diff --git a/src/main/java/minicraft/level/tile/WoolTile.java b/src/main/java/minicraft/level/tile/WoolTile.java index d94648844..355d9e92c 100644 --- a/src/main/java/minicraft/level/tile/WoolTile.java +++ b/src/main/java/minicraft/level/tile/WoolTile.java @@ -4,7 +4,7 @@ import minicraft.entity.Direction; import minicraft.entity.Entity; import minicraft.entity.mob.Player; -import minicraft.gfx.SpriteLinker.LinkedSprite; +import minicraft.gfx.SpriteAnimation; import minicraft.gfx.SpriteLinker.SpriteType; import minicraft.item.Item; import minicraft.item.Items; @@ -38,14 +38,14 @@ public boolean mayPass(Level level, int x, int y, Entity e) { } public enum WoolType { - BLACK("Black Wool", new LinkedSprite(SpriteType.Tile, "black_wool")), - BLUE("Blue Wool", new LinkedSprite(SpriteType.Tile, "blue_wool")), - GREEN("Green Wool", new LinkedSprite(SpriteType.Tile, "green_wool")), - NORMAL("Wool", new LinkedSprite(SpriteType.Tile, "white_wool")), - RED("Red Wool", new LinkedSprite(SpriteType.Tile, "red_wool")), - YELLOW("Yellow Wool", new LinkedSprite(SpriteType.Tile, "yellow_wool")); - - public final LinkedSprite sprite; + BLACK("Black Wool", new SpriteAnimation(SpriteType.Tile, "black_wool")), + BLUE("Blue Wool", new SpriteAnimation(SpriteType.Tile, "blue_wool")), + GREEN("Green Wool", new SpriteAnimation(SpriteType.Tile, "green_wool")), + NORMAL("Wool", new SpriteAnimation(SpriteType.Tile, "white_wool")), + RED("Red Wool", new SpriteAnimation(SpriteType.Tile, "red_wool")), + YELLOW("Yellow Wool", new SpriteAnimation(SpriteType.Tile, "yellow_wool")); + + public final SpriteAnimation sprite; public final String name; /** @@ -53,7 +53,7 @@ public enum WoolType { * * @param sprite The sprite for the type of wool. */ - WoolType(String name, LinkedSprite sprite) { + WoolType(String name, SpriteAnimation sprite) { this.sprite = sprite; this.name = name; } diff --git a/src/main/java/minicraft/level/tile/farming/FarmTile.java b/src/main/java/minicraft/level/tile/farming/FarmTile.java index d521c6c70..ed1952355 100644 --- a/src/main/java/minicraft/level/tile/farming/FarmTile.java +++ b/src/main/java/minicraft/level/tile/farming/FarmTile.java @@ -5,7 +5,7 @@ import minicraft.entity.Entity; import minicraft.entity.ItemEntity; import minicraft.entity.mob.Player; -import minicraft.gfx.SpriteLinker.LinkedSprite; +import minicraft.gfx.SpriteAnimation; import minicraft.gfx.SpriteLinker.SpriteType; import minicraft.item.Item; import minicraft.item.ToolItem; @@ -15,12 +15,12 @@ import minicraft.level.tile.Tiles; public class FarmTile extends Tile { - private static LinkedSprite sprite = new LinkedSprite(SpriteType.Tile, "farmland"); + private static SpriteAnimation sprite = new SpriteAnimation(SpriteType.Tile, "farmland"); public FarmTile(String name) { super(name, sprite); } - protected FarmTile(String name, LinkedSprite sprite) { + protected FarmTile(String name, SpriteAnimation sprite) { super(name, sprite); } diff --git a/src/main/java/minicraft/level/tile/farming/PotatoTile.java b/src/main/java/minicraft/level/tile/farming/PotatoTile.java index 5a918dda5..a4c96fd49 100644 --- a/src/main/java/minicraft/level/tile/farming/PotatoTile.java +++ b/src/main/java/minicraft/level/tile/farming/PotatoTile.java @@ -1,16 +1,25 @@ package minicraft.level.tile.farming; -import minicraft.core.Renderer; import minicraft.core.io.Sound; import minicraft.entity.Entity; import minicraft.entity.mob.Player; import minicraft.gfx.Screen; +import minicraft.gfx.SpriteLinker.LinkedSprite; import minicraft.gfx.SpriteLinker.SpriteType; import minicraft.item.Items; import minicraft.level.Level; import minicraft.level.tile.Tiles; public class PotatoTile extends PlantTile { + private LinkedSprite[] spritStages = new LinkedSprite[] { + new LinkedSprite(SpriteType.Tile, "potato_stage0"), + new LinkedSprite(SpriteType.Tile, "potato_stage1"), + new LinkedSprite(SpriteType.Tile, "potato_stage2"), + new LinkedSprite(SpriteType.Tile, "potato_stage3"), + new LinkedSprite(SpriteType.Tile, "potato_stage4"), + new LinkedSprite(SpriteType.Tile, "potato_stage5") + }; + public PotatoTile(String name) { super(name); } @@ -25,7 +34,7 @@ public void render(Screen screen, Level level, int x, int y) { int icon = age / (maxAge / 5); Tiles.get("Farmland").render(screen, level, x, y); - screen.render(x * 16, y * 16, 13 + icon * 2, 0, 0, Renderer.spriteLinker.getSpriteSheet(SpriteType.Tile, "potato")); + screen.render(x * 16, y * 16, spritStages[icon]); } @Override diff --git a/src/main/java/minicraft/level/tile/farming/WheatTile.java b/src/main/java/minicraft/level/tile/farming/WheatTile.java index f6cbf9b55..f5f3c6cfb 100644 --- a/src/main/java/minicraft/level/tile/farming/WheatTile.java +++ b/src/main/java/minicraft/level/tile/farming/WheatTile.java @@ -1,12 +1,20 @@ package minicraft.level.tile.farming; -import minicraft.core.Renderer; import minicraft.gfx.Screen; +import minicraft.gfx.SpriteLinker.LinkedSprite; import minicraft.gfx.SpriteLinker.SpriteType; import minicraft.level.Level; import minicraft.level.tile.Tiles; public class WheatTile extends PlantTile { + private LinkedSprite[] spritStages = new LinkedSprite[] { + new LinkedSprite(SpriteType.Tile, "wheat_stage0"), + new LinkedSprite(SpriteType.Tile, "wheat_stage1"), + new LinkedSprite(SpriteType.Tile, "wheat_stage2"), + new LinkedSprite(SpriteType.Tile, "wheat_stage3"), + new LinkedSprite(SpriteType.Tile, "wheat_stage4"), + new LinkedSprite(SpriteType.Tile, "wheat_stage5") + }; public WheatTile(String name) { super(name); @@ -18,10 +26,6 @@ public void render(Screen screen, Level level, int x, int y) { int icon = age / (maxAge / 5); Tiles.get("Farmland").render(screen, level, x, y); - - screen.render(x * 16 + 0, y * 16 + 0, 13 + icon, 0, 0, Renderer.spriteLinker.getSpriteSheet(SpriteType.Tile, "wheat")); - screen.render(x * 16 + 8, y * 16 + 0, 13 + icon, 0, 0, Renderer.spriteLinker.getSpriteSheet(SpriteType.Tile, "wheat")); - screen.render(x * 16 + 0, y * 16 + 8, 13 + icon, 0, 1, Renderer.spriteLinker.getSpriteSheet(SpriteType.Tile, "wheat")); - screen.render(x * 16 + 8, y * 16 + 8, 13 + icon, 0, 1, Renderer.spriteLinker.getSpriteSheet(SpriteType.Tile, "wheat")); + screen.render(x * 16, y * 16, spritStages[icon]); } } diff --git a/src/main/java/minicraft/saveload/Load.java b/src/main/java/minicraft/saveload/Load.java index eaadb29b3..74e80b3cd 100644 --- a/src/main/java/minicraft/saveload/Load.java +++ b/src/main/java/minicraft/saveload/Load.java @@ -274,8 +274,7 @@ private void loadPrefsOld(String filename) { if (prefVer.compareTo(new Version("2.0.4-dev2")) >= 0) Settings.set("fps", Integer.parseInt(data.remove(0))); - if (prefVer.compareTo(new Version("2.0.7-dev5")) >= 0) - SkinDisplay.setSelectedSkinIndex(Integer.parseInt(data.remove(0))); + SkinDisplay.releaseSkins(); // Get legacy language and convert it into the current format. if (prefVer.compareTo(new Version("2.0.3-dev1")) >= 0) { @@ -364,7 +363,8 @@ private void loadPrefs(String filename) { Localization.changeLanguage(lang); } - SkinDisplay.setSelectedSkinIndex(json.getInt("skinIdx")); + SkinDisplay.setSelectedSkin(json.optString("skin", "minicraft.skin.paul")); + SkinDisplay.releaseSkins(); // Load keymap JSONArray keyData = json.getJSONArray("keymap"); diff --git a/src/main/java/minicraft/saveload/Save.java b/src/main/java/minicraft/saveload/Save.java index 435a98432..0ded82574 100644 --- a/src/main/java/minicraft/saveload/Save.java +++ b/src/main/java/minicraft/saveload/Save.java @@ -174,7 +174,7 @@ private void writePrefs() { json.put("autosave", String.valueOf(Settings.get("autosave"))); json.put("fps", String.valueOf(Settings.get("fps"))); json.put("lang", Localization.getSelectedLocale().toLanguageTag()); - json.put("skinIdx", String.valueOf(SkinDisplay.getSelectedSkinIndex())); + json.put("skin", String.valueOf(SkinDisplay.getSelectedSkin())); json.put("savedIP", MultiplayerDisplay.savedIP); json.put("savedUUID", MultiplayerDisplay.savedUUID); json.put("savedUsername", MultiplayerDisplay.savedUsername); diff --git a/src/main/java/minicraft/screen/BookDisplay.java b/src/main/java/minicraft/screen/BookDisplay.java index 8639a10d1..9a70d0306 100644 --- a/src/main/java/minicraft/screen/BookDisplay.java +++ b/src/main/java/minicraft/screen/BookDisplay.java @@ -61,7 +61,7 @@ public class BookDisplay extends Display { builder .setPositioning(new Point(Screen.w/2, pageCount.getBounds().getBottom() + spacing), RelPos.BOTTOM) - .setSize(maxX-minX + SpriteSheet.boxWidth*2, maxY-minY + SpriteSheet.boxWidth*2) + .setSize(maxX-minX + MinicraftImage.boxWidth*2, maxY-minY + MinicraftImage.boxWidth*2) .setShouldRender(false); menus = new Menu[lines.length + pageOffset]; diff --git a/src/main/java/minicraft/screen/CraftingDisplay.java b/src/main/java/minicraft/screen/CraftingDisplay.java index b30b2c379..5c47a112c 100644 --- a/src/main/java/minicraft/screen/CraftingDisplay.java +++ b/src/main/java/minicraft/screen/CraftingDisplay.java @@ -5,8 +5,8 @@ import minicraft.core.io.Settings; import minicraft.core.io.Sound; import minicraft.entity.mob.Player; +import minicraft.gfx.MinicraftImage; import minicraft.gfx.Point; -import minicraft.gfx.SpriteSheet; import minicraft.item.Item; import minicraft.item.Items; import minicraft.item.Recipe; @@ -51,7 +51,7 @@ public CraftingDisplay(List recipes, String title, Player player, boolea itemCountMenu = new Menu.Builder(true, 0, RelPos.LEFT) .setTitle("minicraft.displays.crafting.container_title.have") .setTitlePos(RelPos.TOP_LEFT) - .setPositioning(new Point(recipeMenu.getBounds().getRight()+SpriteSheet.boxWidth, recipeMenu.getBounds().getTop()), RelPos.BOTTOM_RIGHT); + .setPositioning(new Point(recipeMenu.getBounds().getRight()+MinicraftImage.boxWidth, recipeMenu.getBounds().getTop()), RelPos.BOTTOM_RIGHT); costsMenu = new Menu.Builder(true, 0, RelPos.LEFT) .setTitle("minicraft.displays.crafting.container_title.cost") diff --git a/src/main/java/minicraft/screen/InfoDisplay.java b/src/main/java/minicraft/screen/InfoDisplay.java index 36bca797a..cc0b7e432 100644 --- a/src/main/java/minicraft/screen/InfoDisplay.java +++ b/src/main/java/minicraft/screen/InfoDisplay.java @@ -4,8 +4,8 @@ import minicraft.core.Updater; import minicraft.core.io.InputHandler; import minicraft.core.io.Localization; +import minicraft.gfx.MinicraftImage; import minicraft.gfx.Point; -import minicraft.gfx.SpriteSheet; import minicraft.screen.entry.StringEntry; public class InfoDisplay extends Display { @@ -21,7 +21,7 @@ public InfoDisplay() { )) .setTitle("minicraft.displays.info.title") .setTitlePos(RelPos.TOP_LEFT) - .setPositioning(new Point(SpriteSheet.boxWidth, SpriteSheet.boxWidth), RelPos.BOTTOM_RIGHT) + .setPositioning(new Point(MinicraftImage.boxWidth, MinicraftImage.boxWidth), RelPos.BOTTOM_RIGHT) .createMenu() ); } diff --git a/src/main/java/minicraft/screen/Menu.java b/src/main/java/minicraft/screen/Menu.java index d7a2b4fcc..932a1a3e6 100644 --- a/src/main/java/minicraft/screen/Menu.java +++ b/src/main/java/minicraft/screen/Menu.java @@ -372,11 +372,11 @@ public void setColors(Menu model) { private void renderFrame(Screen screen) { if(!hasFrame) return; - int bottom = bounds.getBottom()-SpriteSheet.boxWidth; - int right = bounds.getRight()-SpriteSheet.boxWidth; + int bottom = bounds.getBottom()-MinicraftImage.boxWidth; + int right = bounds.getRight()-MinicraftImage.boxWidth; - for (int y = bounds.getTop(); y <= bottom; y += SpriteSheet.boxWidth) { // loop through the height of the bounds - for (int x = bounds.getLeft(); x <= right; x += SpriteSheet.boxWidth) { // loop through the width of the bounds + for (int y = bounds.getTop(); y <= bottom; y += MinicraftImage.boxWidth) { // loop through the height of the bounds + for (int x = bounds.getLeft(); x <= right; x += MinicraftImage.boxWidth) { // loop through the width of the bounds boolean xend = x == bounds.getLeft() || x == right; boolean yend = y == bounds.getTop() || y == bottom; @@ -385,12 +385,12 @@ private void renderFrame(Screen screen) { screen.render(x, y, spriteoffset, 3, mirrors, hudSheet.getSheet()); - if(x < right && x + SpriteSheet.boxWidth > right) - x = right - SpriteSheet.boxWidth; + if(x < right && x + MinicraftImage.boxWidth > right) + x = right - MinicraftImage.boxWidth; } - if(y < bottom && y + SpriteSheet.boxWidth > bottom) - y = bottom - SpriteSheet.boxWidth; + if(y < bottom && y + MinicraftImage.boxWidth > bottom) + y = bottom - MinicraftImage.boxWidth; } } @@ -541,14 +541,14 @@ private Menu createMenu(Builder b) { Insets border; if(menu.hasFrame) - border = new Insets(SpriteSheet.boxWidth); // add frame insets + border = new Insets(MinicraftImage.boxWidth); // add frame insets else { border = new Insets(); // add title insets if (menu.title.length() > 0 && titlePos != RelPos.CENTER) { RelPos c = titlePos; - int space = SpriteSheet.boxWidth * 2; + int space = MinicraftImage.boxWidth * 2; if (c.yIndex == 0) border.top = space; else if (c.yIndex == 2) @@ -562,8 +562,8 @@ else if (c.xIndex == 2) // must be center right if(menu.isSelectable()) { // add spacing for selection cursors - border.left += SpriteSheet.boxWidth * 2; - border.right += SpriteSheet.boxWidth * 2; + border.left += MinicraftImage.boxWidth * 2; + border.right += MinicraftImage.boxWidth * 2; } if(menu.wrap && menu.displayLength > 0) @@ -577,7 +577,7 @@ else if (c.xIndex == 2) // must be center right for(ListEntry entry: menu.entries) { int entryWidth = entry.getWidth(); if(menu.isSelectable() && !entry.isSelectable()) - entryWidth = Math.max(0, entryWidth - SpriteSheet.boxWidth * 4); + entryWidth = Math.max(0, entryWidth - MinicraftImage.boxWidth * 4); width = Math.max(width, entryWidth); } @@ -624,9 +624,9 @@ else if(menuPos.yIndex == 2) menu.titleLoc = titlePos.positionRect(titleDim, menu.bounds); if(titlePos.xIndex == 0 && titlePos.yIndex != 1) - menu.titleLoc.x += SpriteSheet.boxWidth; + menu.titleLoc.x += MinicraftImage.boxWidth; if(titlePos.xIndex == 2 && titlePos.yIndex != 1) - menu.titleLoc.x -= SpriteSheet.boxWidth; + menu.titleLoc.x -= MinicraftImage.boxWidth; // set the menu title color if(menu.title.length() > 0) { diff --git a/src/main/java/minicraft/screen/PlayerDeathDisplay.java b/src/main/java/minicraft/screen/PlayerDeathDisplay.java index c30403b98..8beedeff4 100644 --- a/src/main/java/minicraft/screen/PlayerDeathDisplay.java +++ b/src/main/java/minicraft/screen/PlayerDeathDisplay.java @@ -6,8 +6,8 @@ import minicraft.core.Game; import minicraft.core.World; import minicraft.core.io.Localization; +import minicraft.gfx.MinicraftImage; import minicraft.gfx.Point; -import minicraft.gfx.SpriteSheet; import minicraft.saveload.Save; import minicraft.screen.entry.BlankEntry; import minicraft.screen.entry.ListEntry; @@ -42,7 +42,7 @@ public PlayerDeathDisplay() { menus = new Menu[]{ new Menu.Builder(true, 0, RelPos.LEFT, entries) - .setPositioning(new Point(SpriteSheet.boxWidth, SpriteSheet.boxWidth * 3), RelPos.BOTTOM_RIGHT) + .setPositioning(new Point(MinicraftImage.boxWidth, MinicraftImage.boxWidth * 3), RelPos.BOTTOM_RIGHT) .setTitle("minicraft.displays.player_death.title") .setTitlePos(RelPos.TOP_LEFT) .createMenu() diff --git a/src/main/java/minicraft/screen/ResourcePackDisplay.java b/src/main/java/minicraft/screen/ResourcePackDisplay.java index 662b60e0c..ddb7f49b4 100644 --- a/src/main/java/minicraft/screen/ResourcePackDisplay.java +++ b/src/main/java/minicraft/screen/ResourcePackDisplay.java @@ -1,5 +1,6 @@ package minicraft.screen; +import java.awt.image.BufferedImage; import java.io.BufferedInputStream; import java.io.BufferedReader; import java.io.Closeable; @@ -39,9 +40,11 @@ import minicraft.core.io.Sound; import minicraft.gfx.Color; import minicraft.gfx.Font; +import minicraft.gfx.MinicraftImage; import minicraft.gfx.Point; import minicraft.gfx.Screen; -import minicraft.gfx.SpriteLinker.SpriteSheet; +import minicraft.gfx.SpriteAnimation; +import minicraft.gfx.SpriteLinker; import minicraft.gfx.SpriteLinker.SpriteType; import minicraft.saveload.Save; import minicraft.screen.entry.ArrayEntry; @@ -97,7 +100,7 @@ public class ResourcePackDisplay extends Display { private static final int VERSION = 1; private static final ResourcePack defaultPack; // Used to check if the resource pack default. - private static final SpriteSheet defaultLogo; + private static final MinicraftImage defaultLogo; private static ArrayList loadedPacks = new ArrayList<>(); private static ArrayList loadQuery = new ArrayList<>(); @@ -115,7 +118,7 @@ public class ResourcePackDisplay extends Display { defaultPack = Objects.requireNonNull(loadPackMetadata(Game.class.getProtectionDomain().getCodeSource().getLocation())); loadedPacks.add(defaultPack); try { - defaultLogo = new SpriteSheet(ImageIO.read(ResourcePackDisplay.class.getResourceAsStream("/resources/default_pack.png"))); + defaultLogo = new MinicraftImage(ImageIO.read(ResourcePackDisplay.class.getResourceAsStream("/resources/default_pack.png"))); } catch (IOException e) { CrashHandler.crashHandle(e); throw new RuntimeException(); @@ -209,7 +212,7 @@ private void refreshEntries() { menus[selection ^ 1].translate(menus[selection].getBounds().getWidth() + padding, 0); } - /** Watching the directory changes. */ + /** Watching the directory changes. Allowing hot-loading. */ private class WatcherThread extends Thread implements Closeable { private WatchService watcher; private volatile boolean running = true; @@ -221,7 +224,7 @@ private class WatcherThread extends Thread implements Closeable { FOLDER_LOCATION.toPath().register(watcher, StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.ENTRY_DELETE, StandardWatchEventKinds.ENTRY_MODIFY); } catch (IOException e) { - CrashHandler.crashHandle(e, new CrashHandler.ErrorInfo("Unable to Watch File", CrashHandler.ErrorInfo.ErrorType.UNHANDLEABLE, "Unable to create file water service.")); + CrashHandler.errorHandle(e, new CrashHandler.ErrorInfo("Unable to Watch File", CrashHandler.ErrorInfo.ErrorType.UNHANDLEABLE, "Unable to create file water service.")); } start(); @@ -358,7 +361,7 @@ public void render(Screen screen) { ArrayList packs = selection == 0 ? resourcePacks : loadedPacks; if (packs.size() > 0) { @SuppressWarnings("resource") - SpriteSheet logo = packs.get(menus[selection].getSelection()).logo; + MinicraftImage logo = packs.get(menus[selection].getSelection()).logo; int h = logo.height / 8; int w = logo.width / 8; int xo = (Screen.w - logo.width) / 2; @@ -382,7 +385,7 @@ private static class ResourcePack implements Closeable { private final int packFormat; // The pack format of the pack. private final String name; // The name of the pack. private final String description; // The description of the pack. - private SpriteSheet logo; // The logo of the pack. + private MinicraftImage logo; // The logo of the pack. private boolean opened = false; // If the zip file stream is opened. private ZipFile zipFile = null; // The zip file stream. @@ -402,7 +405,7 @@ public void refreshPack() { openStream(); InputStream in = getResourceAsStream("pack.png"); if (in != null) { - logo = new SpriteSheet(ImageIO.read(in)); + logo = new MinicraftImage(ImageIO.read(in)); // Logo size verification. int h = logo.height; @@ -418,11 +421,10 @@ public void refreshPack() { close(); } catch (IOException | NullPointerException e) { - e.printStackTrace(); - Logging.RESOURCEHANDLER_RESOURCEPACK.warn("Unable to load logo in pack: {}, loading default logo instead.", name); + Logging.RESOURCEHANDLER_RESOURCEPACK.warn(e, "Unable to load logo in pack: {}, loading default logo instead.", name); if (this == defaultPack) { try { - logo = new SpriteSheet(ImageIO.read(getClass().getResourceAsStream("/resources/logo.png"))); + logo = new MinicraftImage(ImageIO.read(getClass().getResourceAsStream("/resources/logo.png"))); } catch (IOException e1) { CrashHandler.crashHandle(e1); } @@ -461,7 +463,11 @@ public void close() throws IOException { * @throws IOException if an I/O error has occurred. */ private InputStream getResourceAsStream(String path) throws IOException { - return zipFile.getInputStream(zipFile.getEntry(path)); + try { + return zipFile.getInputStream(zipFile.getEntry(path)); + } catch (NullPointerException e) { + throw new IOException(e); + } } @FunctionalInterface @@ -601,7 +607,6 @@ private static void refreshResourcePacks(List urls) { } } else { // Add new pack as it should be exist. pack = loadPackMetadata(url); - pack.refreshPack(); if (pack != null) { resourcePacks.add(pack); } @@ -667,6 +672,7 @@ public static void reloadResources() { Localization.resetLocalizations(); BookData.resetBooks(); Sound.resetSounds(); + SpriteAnimation.resetMetadata(); for (ResourcePack pack : loadQuery) { if (pack.openStream()) { try { @@ -681,6 +687,7 @@ public static void reloadResources() { } } + SpriteAnimation.refreshAnimations(); Renderer.spriteLinker.updateLinkedSheets(); Localization.loadLanguage(); ArrayList options = new ArrayList<>(Arrays.asList(Localization.getLocales())); @@ -719,8 +726,83 @@ private static void loadTextures(ResourcePack pack, SpriteType type) throws IOEx case Tile: path += "tile/"; break; } - for (String p : pack.getFiles(path, (p, isDir) -> p.toString().endsWith(".png") && !isDir)) { - Renderer.spriteLinker.setSprite(type, p.substring(path.length(), p.length() - 4), new SpriteSheet(ImageIO.read(pack.getResourceAsStream(p)))); + ArrayList pngs = pack.getFiles(path, (p, isDir) -> p.toString().endsWith(".png") && !isDir); + if (type == SpriteType.Tile) { + // Loading sprite sheet metadata. + for (String m : pack.getFiles(path, (p, isDir) -> p.toString().endsWith(".png.json") && !isDir)) { + try { + JSONObject obj = new JSONObject(readStringFromInputStream(pack.getResourceAsStream(m))); + SpriteLinker.SpriteMeta meta = new SpriteLinker.SpriteMeta(); + pngs.remove(m.substring(0, m.length() - 5)); + BufferedImage image = ImageIO.read(pack.getResourceAsStream(m.substring(0, m.length() - 5))); + + // Applying animations. + MinicraftImage sheet; + JSONObject animation = obj.optJSONObject("animation"); + if (animation != null) { + meta.frametime = animation.getInt("frametime"); + meta.frames = image.getHeight() / 16; + if (meta.frames == 0) throw new IOException(new IllegalArgumentException(String.format( + "Invalid frames 0 detected with {} in pack: {}", m, pack.name))); + sheet = new MinicraftImage(image, 16, 16 * meta.frames); + } else + sheet = new MinicraftImage(image, 16, 16); + Renderer.spriteLinker.setSprite(type, m.substring(path.length(), m.length() - 9), sheet); + + JSONObject borderObj = obj.optJSONObject("border"); + if (borderObj != null) { + meta.border = borderObj.optString("key"); + if (meta.border.isEmpty()) meta.border = null; + if (meta.border != null) { + String borderK = path + meta.border + ".png"; + pngs.remove(borderK); + try { + Renderer.spriteLinker.setSprite(type, meta.border, new MinicraftImage(ImageIO.read(pack.getResourceAsStream(borderK)), 24, 24)); + } catch (IOException e) { + Logging.RESOURCEHANDLER_RESOURCEPACK.warn(e, "Unable to read {} with {} in pack: {}", borderK, m, pack.name); + meta.border = null; + } + } + + meta.corner = borderObj.optString("corner"); + if (meta.corner.isEmpty()) meta.corner = null; + if (meta.corner != null) { + String cornerK = path + meta.corner + ".png"; + pngs.remove(cornerK); + try { + Renderer.spriteLinker.setSprite(type, meta.corner, new MinicraftImage(ImageIO.read(pack.getResourceAsStream(cornerK)), 16, 16)); + } catch (IOException e) { + Logging.RESOURCEHANDLER_RESOURCEPACK.warn(e, "Unable to read {} with {} in pack: {}", cornerK, m, pack.name); + meta.corner = null; + } + } + } + + SpriteAnimation.setMetadata(m.substring(path.length(), m.length() - 9), meta); + } catch (JSONException | IOException e) { + Logging.RESOURCEHANDLER_RESOURCEPACK.warn(e, "Unable to read {} in pack: {}", m, pack.name); + } + } + + } + + // Loading the left pngs. + for (String p : pngs) { + try { + BufferedImage image = ImageIO.read(pack.getResourceAsStream(p)); + MinicraftImage sheet; + if (type == SpriteType.Item) { + sheet = new MinicraftImage(image, 8, 8); // Set the minimum tile sprite size. + } else if (type == SpriteType.Tile) { + sheet = new MinicraftImage(image, 16, 16); // Set the minimum item sprite size. + } else { + sheet = new MinicraftImage(image); + } + + Renderer.spriteLinker.setSprite(type, p.substring(path.length(), p.length() - 4), sheet); + } catch (IOException e) { + Logging.RESOURCEHANDLER_RESOURCEPACK.warn("Unable to load {} in pack : {}", p, pack.name); + } } } diff --git a/src/main/java/minicraft/screen/SkinDisplay.java b/src/main/java/minicraft/screen/SkinDisplay.java index 5cb8bef2e..85d17d584 100644 --- a/src/main/java/minicraft/screen/SkinDisplay.java +++ b/src/main/java/minicraft/screen/SkinDisplay.java @@ -1,24 +1,33 @@ package minicraft.screen; +import minicraft.core.CrashHandler; import minicraft.core.Game; import minicraft.core.Renderer; import minicraft.core.io.FileHandler; import minicraft.core.io.InputHandler; import minicraft.core.io.Localization; +import minicraft.entity.mob.Mob; import minicraft.gfx.*; +import minicraft.gfx.SpriteLinker.LinkedSprite; import minicraft.saveload.Save; import minicraft.screen.entry.ListEntry; import minicraft.screen.entry.SelectEntry; import minicraft.util.Logging; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import javax.imageio.ImageIO; -import java.awt.image.BufferedImage; +import javax.security.auth.DestroyFailedException; + import java.io.File; import java.io.FileInputStream; import java.io.IOException; +import java.nio.file.FileSystems; +import java.nio.file.Path; +import java.nio.file.StandardWatchEventKinds; +import java.nio.file.WatchEvent; +import java.nio.file.WatchService; import java.util.ArrayList; +import java.util.LinkedHashMap; import java.util.List; import java.util.Objects; @@ -27,110 +36,187 @@ * Many skins can be put according to the number of files. */ public class SkinDisplay extends Display { - private static final List skinNames = new ArrayList<>(); - private static final int defaultSkins; - private static final SpriteSheet defaultSheet; - private static final List customSkins = new ArrayList<>(); - private static int selectedSkinIndex = 0; - private static SpriteSheet selectedSkinSheet; - private static int tempSelection = 0; + private static final LinkedHashMap skins = new LinkedHashMap<>(); + private static final ArrayList defaultSkins = new ArrayList<>(); + private static final MinicraftImage defaultSheet; + private static final File FOLDER_LOCATION = new File(FileHandler.getSystemGameDir() + "/" + FileHandler.getLocalGameDir() + "/skins"); + private static String selectedSkin; private int step; + private WatcherThread thread; static { // Load the default sprite sheet. defaultSheet = Renderer.loadDefaultSkinSheet(); // These are all the generic skins. To add one, just add an entry in this list. - skinNames.add("minicraft.skin.paul"); - skinNames.add("minicraft.skin.paul_cape"); - skinNames.add("minicraft.skin.minecraft_steve"); - skinNames.add("minicraft.skin.minecraft_alex"); + defaultSkins.add("minicraft.skin.paul"); + defaultSkins.add("minicraft.skin.paul_cape"); + defaultSkins.add("minicraft.skin.minecraft_steve"); + defaultSkins.add("minicraft.skin.minecraft_alex"); + selectedSkin = defaultSkins.get(0); + + Logging.RESOURCEHANDLER_SKIN.debug("Refreshing skins."); + refreshSkins(); + } - // Never remove this - defaultSkins = skinNames.size(); + public SkinDisplay() { + super(true, true, + new Menu.Builder(false, 2, RelPos.CENTER) + .setDisplayLength(8) + .setSelectable(true) + .setPositioning(new Point(Screen.w/2, Screen.h*3/5), RelPos.CENTER) + .createMenu() + ); - // Get the folder containing the skins. - File skinsFolder = new File(FileHandler.getSystemGameDir() + "/" + FileHandler.getLocalGameDir() + "/skins"); + thread = new WatcherThread(); + refreshSkins(); + refreshEntries(); + menus[0].setSelection(new ArrayList<>(skins.keySet()).indexOf(selectedSkin)); + } + + private static void refreshSkins() { + Renderer.spriteLinker.clearSkins(); + skins.clear(); + + // Pointing the keys to the default sheet, + Renderer.spriteLinker.setSkin("skin.minicraft.skin.paul", defaultSheet); + Renderer.spriteLinker.setSkin("skin.minicraft.skin.paul_cape", defaultSheet); + Renderer.spriteLinker.setSkin("skin.minicraft.skin.minecraft_steve", defaultSheet); + Renderer.spriteLinker.setSkin("skin.minicraft.skin.minecraft_alex", defaultSheet); + + skins.put("minicraft.skin.paul", Mob.compileMobSpriteAnimations(0, 0, "skin.minicraft.skin.paul")); + skins.put("minicraft.skin.paul_cape", Mob.compileMobSpriteAnimations(0, 4, "skin.minicraft.skin.paul_cape")); + skins.put("minicraft.skin.minecraft_steve", Mob.compileMobSpriteAnimations(0, 8, "skin.minicraft.skin.minecraft_steve")); + skins.put("minicraft.skin.minecraft_alex", Mob.compileMobSpriteAnimations(0, 12, "skin.minicraft.skin.minecraft_alex")); // Create folder, and see if it was successful. - if (skinsFolder.mkdirs()) { - Logging.RESOURCEHANDLER_SKIN.info("Created skin folder at {}.", skinsFolder); + if (FOLDER_LOCATION.mkdirs()) { + Logging.RESOURCEHANDLER_SKIN.info("Created skin folder at {}.", FOLDER_LOCATION); } // Read and add the .png file to the skins list. - for (String skinPath : Objects.requireNonNull(skinsFolder.list())) { + ArrayList files = new ArrayList<>(); + for (String skinPath : Objects.requireNonNull(FOLDER_LOCATION.list())) { if (skinPath.endsWith(".png")) { - if (getSkinAsSheet(skinsFolder + "/" + skinPath) != null) { - // Add the sprite sheet to the custom skins list. - customSkins.add(skinsFolder + "/" + skinPath); - - // Remove the png file extension and to the . - skinNames.add(skinPath.substring(0, skinPath.length() - 4)); - } + files.add(new File(FOLDER_LOCATION, skinPath)); } } - } - public SkinDisplay() { - super(true, true, - new Menu.Builder(false, 2, RelPos.CENTER, getSkinsAsEntries()) - .setSize(48, 64) - .setPositioning(new Point(Screen.w/2, Screen.h*3/5), RelPos.CENTER) - .createMenu() - ); + refreshSkinFiles(files); } - @Override - public void init(@Nullable Display parent) { - super.init(parent); + private void refreshEntries() { + List l = new ArrayList<>(); + for (String s : skins.keySet()) { + l.add(new SelectEntry(s, this::confirmExit)); + } - menus[0].setSelection(selectedSkinIndex); + menus[0].setEntries(l); + menus[0].setSelection(menus[0].getSelection()); } - public static List getSkinsAsEntries() { - List l = new ArrayList<>(); - for (String s : skinNames) { - l.add(new SelectEntry(s, SkinDisplay::confirmExit)); + public static void releaseSkins() { + for (String skin : skins.keySet()) { + if (!defaultSkins.contains(skin) && !skin.equals(selectedSkin)) { + Renderer.spriteLinker.setSkin("skin." + skin, null); + if (skins.containsKey(skin)) for (LinkedSprite[] a : skins.remove(skin)) { + for (LinkedSprite b : a) { + try { + b.destroy(); + } catch (DestroyFailedException e) { + Logging.RESOURCEHANDLER_SKIN.trace(e); + } + } + } + } } - - return l; } - private static SpriteSheet getSkinAsSheet(String path) { - BufferedImage image; - try { - image = ImageIO.read(new FileInputStream(path)); - } catch (IOException e) { - Logging.RESOURCEHANDLER_SKIN.error("Could not read image at path {}. The file is probably missing or formatted wrong.", path); - return null; - } catch (SecurityException e) { - Logging.RESOURCEHANDLER_SKIN.error("Access to file located at {} was denied. Check if game is given permission.", path); - return null; + /** Watching the directory changes. Allowing hot-loading. */ + private class WatcherThread extends Thread { + private WatchService watcher; + + WatcherThread() { + super("Skin File Watcher"); + try { + watcher = FileSystems.getDefault().newWatchService(); + FOLDER_LOCATION.toPath().register(watcher, StandardWatchEventKinds.ENTRY_CREATE, + StandardWatchEventKinds.ENTRY_DELETE, StandardWatchEventKinds.ENTRY_MODIFY); + } catch (IOException e) { + CrashHandler.errorHandle(e, new CrashHandler.ErrorInfo("Unable to Watch File", CrashHandler.ErrorInfo.ErrorType.UNHANDLEABLE, "Unable to create file water service.")); + } + + start(); + Logging.RESOURCEHANDLER_SKIN.debug("WatcherThread started."); } - // If we found an image. - if (image != null) { - SpriteSheet spriteSheet = new SpriteSheet(image); + @Override + public void run() { + while (true) { + try { + ArrayList files = new ArrayList<>(); + for (WatchEvent event : watcher.take().pollEvents()) { + if (event.kind() == StandardWatchEventKinds.OVERFLOW) + continue; + + @SuppressWarnings("unchecked") + WatchEvent ev = (WatchEvent) event; + Path filename = ev.context(); + files.add(FOLDER_LOCATION.toPath().resolve(filename).toFile()); + } + + if (files.size() > 0) { + Logging.RESOURCEHANDLER_SKIN.debug("Refreshing resource packs."); + refreshSkinFiles(files); + refreshEntries(); + } + } catch (InterruptedException e) { + Logging.RESOURCEHANDLER_SKIN.trace("File watcher terminated."); + return; + } - // Check if sheet is a multiple of 8. - if (spriteSheet.width % 8 == 0 && spriteSheet.height % 8 == 0) { - return spriteSheet; - } else { - // Go here if image has wrong dimensions. - Logging.RESOURCEHANDLER_SKIN.error("Custom skin at '{}' has incorrect width or height. Should be a multiple of 8.", path); + if (Thread.interrupted()) { + Logging.RESOURCEHANDLER_SKIN.trace("File watcher terminated."); + return; + } } } + } - return null; + private synchronized static void refreshSkinFiles(List files) { + for (File file : files) { + String skinPath = file.getName(); + String name = skinPath.substring(0, skinPath.length() - 4); + if (file.exists()) try { + MinicraftImage sheet = new MinicraftImage(ImageIO.read(new FileInputStream(file)), 64, 32); + Renderer.spriteLinker.setSkin("skin." + name, sheet); + skins.put(name, Mob.compileMobSpriteAnimations(0, 0, "skin." + name)); + } catch (IOException e) { + Logging.RESOURCEHANDLER_SKIN.error("Could not read image at path {}. The file is probably missing or formatted wrong.", skinPath); + } catch (SecurityException e) { + Logging.RESOURCEHANDLER_SKIN.error("Access to file located at {} was denied. Check if game is given permission.", skinPath); + } else { + Renderer.spriteLinker.setSkin("skin." + name, null); + if (skins.containsKey(name)) for (LinkedSprite[] a : skins.remove(name)) { + for (LinkedSprite b : a) { + try { + b.destroy(); + } catch (DestroyFailedException e) { + Logging.RESOURCEHANDLER_SKIN.trace(e); + } + } + } + } + } } /** * If we exited by selecting a skin. */ - private static void confirmExit() { + private void confirmExit() { Game.exitDisplay(); - selectedSkinIndex = tempSelection; + selectedSkin = new ArrayList<>(skins.keySet()).get(menus[0].getSelection()); // Achieve Fashion Show: AchievementsDisplay.setAchievement(true, "minicraft.achievement.skin", true); @@ -144,46 +230,15 @@ private static void confirmExit() { new Save(); } + @Override + public void onExit() { + thread.interrupt(); + releaseSkins(); + } @Override public void tick(InputHandler input) { super.tick(input); - - int prevSel = tempSelection; - tempSelection = menus[0].getSelection(); - - // Executes every time the selection is updated. - if (tempSelection != prevSel) { - if (tempSelection >= defaultSkins) { - selectedSkinSheet = getSkinAsSheet(customSkins.get(tempSelection - defaultSkins)); - - // Something failed when getting the sheet, so remove it from the list and set the skin back to default. - if (selectedSkinSheet == null) { - // Set selected skin back to default. - selectedSkinIndex = 0; - Renderer.screen.setSkinSheet(defaultSheet); - - // Remove references to the skin. - customSkins.remove(tempSelection - defaultSkins); - skinNames.remove(tempSelection); - - // Refresh menu and save. - menus[0].setEntries(getSkinsAsEntries().toArray(new ListEntry[0])); - tempSelection = 0; - init(getParent()); - new Save(); - - Logging.RESOURCEHANDLER_SKIN.error("Error setting skin. Removed skin from list and set skin back to default."); - return; - } - - Renderer.screen.setSkinSheet(selectedSkinSheet); - Logging.RESOURCEHANDLER_SKIN.debug("Skin sheet set to {}.png.", skinNames.get(tempSelection)); - } else { - Renderer.screen.setSkinSheet(defaultSheet); - Logging.RESOURCEHANDLER_SKIN.debug("Skin sheet changed to default sheet."); - } - } } @Override @@ -194,46 +249,40 @@ public void render(Screen screen) { // Title. Font.drawCentered(Localization.getLocalized("minicraft.displays.skin"), screen, 16, Color.WHITE); - int h = 2; - int w = 2; - int xOffset = Screen.w / 2 - w * 4; // Put this in the center of the screen + int xOffset = Screen.w / 2 - 8; // Put this in the center of the screen int yOffset = 40; // Player sprite Y position - int spriteIndex = (step / 40) % 4; // 9 = 8 Frames for sprite + int spriteIndex = (step / 40) % 8; // 9 = 8 Frames for sprite // Render preview of skin. - for (int y = 0; y < h; y++) - for (int x = 0; x < w; x++) - if (menus[0].getSelection() < defaultSkins) { - screen.render(xOffset + x * 8, yOffset + y * 8, spriteIndex * 2 + x, y + menus[0].getSelection() * 4, 0, Renderer.screen.getSkinSheet()); - } else { - screen.render(xOffset + x * 8, yOffset + y * 8, spriteIndex * 2 + x, y, 0, selectedSkinSheet, - 1, false, 0); - } + LinkedSprite sprite = new ArrayList<>(skins.values()).get(menus[0].getSelection())[spriteIndex / 2][spriteIndex % 2]; + screen.render(xOffset, yOffset, sprite); // Help text. Font.drawCentered(Localization.getLocalized("minicraft.displays.resource_packs.display.help.move", Game.input.getMapping("cursor-down"), Game.input.getMapping("cursor-up")), screen, Screen.h - 17, Color.DARK_GRAY); Font.drawCentered(Localization.getLocalized("minicraft.displays.skin.display.help.select", Game.input.getMapping("SELECT"), Game.input.getMapping("EXIT")), screen, Screen.h - 9, Color.DARK_GRAY); } - public static int getSelectedSkinIndex() { - return selectedSkinIndex; + public static String getSelectedSkin() { + return selectedSkin; } - public static void setSelectedSkinIndex(int selectedSkinIndex) { - SkinDisplay.selectedSkinIndex = selectedSkinIndex; + public static void setSelectedSkin(String selectedSkin) { + SkinDisplay.selectedSkin = selectedSkin; } // First array is one of the four animations. @NotNull - public static MobSprite[][][] getSkinAsMobSprite() { - MobSprite[][][] mobSprites = new MobSprite[2][][]; + public static LinkedSprite[][][] getSkinAsMobSprite() { + LinkedSprite[][][] mobSprites = new LinkedSprite[2][][]; - if (selectedSkinIndex < defaultSkins) { - mobSprites[0] = MobSprite.compilePlayerSpriteAnimations(0, SkinDisplay.getSelectedSkinIndex() * 4, Renderer.screen.getSkinSheet()); - mobSprites[1] = MobSprite.compilePlayerSpriteAnimations(0, SkinDisplay.getSelectedSkinIndex() * 4 + 2, Renderer.screen.getSkinSheet()); + if (!skins.keySet().contains(selectedSkin)) selectedSkin = defaultSkins.get(0); + if (defaultSkins.contains(selectedSkin)) { + mobSprites[0] = Mob.compileMobSpriteAnimations(0, defaultSkins.indexOf(selectedSkin) * 4, "skin." + selectedSkin); + mobSprites[1] = Mob.compileMobSpriteAnimations(0, defaultSkins.indexOf(selectedSkin) * 4 + 2, "skin." + selectedSkin); } else { - mobSprites[0] = MobSprite.compilePlayerSpriteAnimations(0, 0, Renderer.screen.getSkinSheet()); - mobSprites[1] = MobSprite.compilePlayerSpriteAnimations(0, 2, Renderer.screen.getSkinSheet()); + mobSprites[0] = Mob.compileMobSpriteAnimations(0, 0, "skin." + selectedSkin); + mobSprites[1] = Mob.compileMobSpriteAnimations(0, 2, "skin." + selectedSkin); } return mobSprites; diff --git a/src/main/java/minicraft/screen/TitleDisplay.java b/src/main/java/minicraft/screen/TitleDisplay.java index f9b6be0cb..ce0ea7c75 100644 --- a/src/main/java/minicraft/screen/TitleDisplay.java +++ b/src/main/java/minicraft/screen/TitleDisplay.java @@ -8,9 +8,9 @@ import minicraft.core.io.Localization; import minicraft.gfx.Color; import minicraft.gfx.Font; +import minicraft.gfx.MinicraftImage; import minicraft.gfx.Point; import minicraft.gfx.Screen; -import minicraft.gfx.SpriteSheet; import minicraft.gfx.SpriteLinker.SpriteType; import minicraft.level.Level; import minicraft.network.Network; @@ -115,7 +115,7 @@ public void tick(InputHandler input) { public void render(Screen screen) { super.render(screen); - SpriteSheet sheet = Renderer.spriteLinker.getSpriteSheet(SpriteType.Gui, "title"); + MinicraftImage sheet = Renderer.spriteLinker.getSheet(SpriteType.Gui, "title"); int h = sheet.height / 8; // Height of squares (on the spritesheet) int w = sheet.width / 8; // Width of squares (on the spritesheet) int xo = (Screen.w - sheet.width) / 2; // X location of the title diff --git a/src/main/java/minicraft/screen/entry/BlankEntry.java b/src/main/java/minicraft/screen/entry/BlankEntry.java index 5b9c2d381..9f42b58ef 100644 --- a/src/main/java/minicraft/screen/entry/BlankEntry.java +++ b/src/main/java/minicraft/screen/entry/BlankEntry.java @@ -1,26 +1,26 @@ package minicraft.screen.entry; import minicraft.core.io.InputHandler; +import minicraft.gfx.MinicraftImage; import minicraft.gfx.Screen; -import minicraft.gfx.SpriteSheet; public class BlankEntry extends ListEntry { - + public BlankEntry() { setSelectable(false); } - + @Override public void tick(InputHandler input) {} - + @Override public void render(Screen screen, int x, int y, boolean isSelected) {} - + @Override public int getWidth() { - return SpriteSheet.boxWidth; + return MinicraftImage.boxWidth; } - + @Override public String toString() { return " "; } } diff --git a/src/main/java/minicraft/screen/entry/ItemEntry.java b/src/main/java/minicraft/screen/entry/ItemEntry.java index 193a19555..780e3e543 100644 --- a/src/main/java/minicraft/screen/entry/ItemEntry.java +++ b/src/main/java/minicraft/screen/entry/ItemEntry.java @@ -27,7 +27,7 @@ public void tick(InputHandler input) {} @Override public void render(Screen screen, int x, int y, boolean isSelected) { super.render(screen, x, y, true); - item.sprite.getSprite().render(screen, x, y); + screen.render(x, y, item.sprite); } // If you add to the length of the string, and therefore the width of the entry, then it will actually move the entry RIGHT in the inventory, instead of the intended left, because it is auto-positioned to the left side. diff --git a/src/main/java/minicraft/screen/entry/RecipeEntry.java b/src/main/java/minicraft/screen/entry/RecipeEntry.java index 6673a47f7..e176ea620 100644 --- a/src/main/java/minicraft/screen/entry/RecipeEntry.java +++ b/src/main/java/minicraft/screen/entry/RecipeEntry.java @@ -30,7 +30,7 @@ public void tick(InputHandler input) {} public void render(Screen screen, int x, int y, boolean isSelected) { if (isVisible()) { Font.draw(toString(), screen, x, y, recipe.getCanCraft() ? COL_SLCT : COL_UNSLCT); - getItem().sprite.getSprite().render(screen, x, y); + screen.render(x, y, getItem().sprite); } } diff --git a/src/main/java/minicraft/util/TinylogLoggingConfiguration.java b/src/main/java/minicraft/util/TinylogLoggingConfiguration.java index 7ede57660..19b6c86b0 100644 --- a/src/main/java/minicraft/util/TinylogLoggingConfiguration.java +++ b/src/main/java/minicraft/util/TinylogLoggingConfiguration.java @@ -1,6 +1,5 @@ package minicraft.util; -import minicraft.core.Game; import minicraft.util.TinylogLoggingConfiguration.WriterConfig.TagList; import org.tinylog.Level; import org.tinylog.Supplier; @@ -134,7 +133,7 @@ public Map createConsoleWriter(boolean logTime, boo HashMap properties = new HashMap<>(); String ID = String.format("writer1%s%s%s", logTime ? "T" : "F", logThread ? "T" : "F", logTrace ? "T" : "F"); - properties.put("level", Game.debug && logTrace ? "trace" : Game.debug ? "debug" : "info"); + properties.put("level", logTrace ? "trace" : "debug"); Level level = ConfigurationParser.parse(properties.get("level"), Level.TRACE); properties.put("format", String.format("%s%s[{tag}] {level}: {message}", logTime ? "{date: HH:mm:ss.SSS} " : "", logThread ? "[{thread-id}/{thread}] " : "")); diff --git a/src/main/java/minicraft/util/TinylogLoggingProvider.java b/src/main/java/minicraft/util/TinylogLoggingProvider.java index 4cf416ed5..880ff51ba 100644 --- a/src/main/java/minicraft/util/TinylogLoggingProvider.java +++ b/src/main/java/minicraft/util/TinylogLoggingProvider.java @@ -22,6 +22,7 @@ import org.tinylog.writers.FileWriter; import org.tinylog.writers.Writer; +import minicraft.core.Game; import minicraft.util.TinylogLoggingConfiguration.WriterConfig; /** Originally copied from {@link org.tinylog.core.TinylogLoggingProvider} */ @@ -161,7 +162,8 @@ private void output(final StackTraceElement stackTraceElement, final String tag, Consumer addToThread = writer -> { WriterConfig cfg = writers.get(writer); - if (cfg.levels.contains(level) && cfg.tags.contains(tag)) + if ((level.ordinal() <= Level.DEBUG.ordinal() && Game.debug || level.ordinal() > Level.DEBUG.ordinal()) && + cfg.levels.contains(level) && cfg.tags.contains(tag)) writingThread.add(writer, logEntry); }; diff --git a/src/main/resources/assets/textures/entity/smash.png b/src/main/resources/assets/textures/entity/smash.png new file mode 100644 index 000000000..93cf47814 Binary files /dev/null and b/src/main/resources/assets/textures/entity/smash.png differ diff --git a/src/main/resources/assets/textures/tile/rock.png.json b/src/main/resources/assets/textures/tile/rock.png.json index 562013053..362d6c85f 100644 --- a/src/main/resources/assets/textures/tile/rock.png.json +++ b/src/main/resources/assets/textures/tile/rock.png.json @@ -1,5 +1,6 @@ { "border": { - "key": "rock_border" + "key": "rock_border", + "corner": "rock_corner" } } diff --git a/src/main/resources/assets/textures/tile/torch.png b/src/main/resources/assets/textures/tile/torch.png new file mode 100644 index 000000000..e60eb09c1 Binary files /dev/null and b/src/main/resources/assets/textures/tile/torch.png differ diff --git a/src/main/resources/assets/textures/tile/tree_full.png b/src/main/resources/assets/textures/tile/tree_full.png index 72d995dbd..8dad3ec38 100644 Binary files a/src/main/resources/assets/textures/tile/tree_full.png and b/src/main/resources/assets/textures/tile/tree_full.png differ diff --git a/src/main/resources/assets/textures/tile/wood_wall.png.json b/src/main/resources/assets/textures/tile/wood_wall.png.json index 3b65c8896..bec407fa8 100644 --- a/src/main/resources/assets/textures/tile/wood_wall.png.json +++ b/src/main/resources/assets/textures/tile/wood_wall.png.json @@ -1,5 +1,5 @@ { "border": { - "key": " wood_wall_border" + "key": "wood_wall_border" } }