DEADSOFTWARE

Refactor rendering
authorfredboy <fredboy@protonmail.com>
Sun, 21 Apr 2024 12:46:44 +0000 (19:46 +0700)
committerfredboy <fredboy@protonmail.com>
Sun, 21 Apr 2024 12:46:44 +0000 (19:46 +0700)
34 files changed:
android/assets/json/game_items.json
core/src/ru/deadsoftware/cavedroid/game/GameComponent.java
core/src/ru/deadsoftware/cavedroid/game/GameInput.java
core/src/ru/deadsoftware/cavedroid/game/GameItemsHolder.kt
core/src/ru/deadsoftware/cavedroid/game/GameProc.java
core/src/ru/deadsoftware/cavedroid/game/GameRenderer.java
core/src/ru/deadsoftware/cavedroid/game/GameSaver.java
core/src/ru/deadsoftware/cavedroid/game/debug/DebugInfoStringsProvider.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/mobs/Player.java
core/src/ru/deadsoftware/cavedroid/game/model/block/Block.kt
core/src/ru/deadsoftware/cavedroid/game/model/item/Item.kt
core/src/ru/deadsoftware/cavedroid/game/model/mapper/BlockMapper.kt
core/src/ru/deadsoftware/cavedroid/game/model/mapper/ItemMapper.kt
core/src/ru/deadsoftware/cavedroid/game/render/BackgroundBlocksRenderer.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/render/BlocksRenderer.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/render/DebugRenderer.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/render/DropsRenderer.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/render/ForegroundBlocksRenderer.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/render/HudRenderer.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/render/IGameRenderer.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/render/MobsRenderer.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/render/RenderModule.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/render/TouchControlsRenderer.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/render/WindowsRenderer.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/render/windows/CreativeWindowRenderer.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/world/GameWorld.java
core/src/ru/deadsoftware/cavedroid/game/world/GameWorldBlocksLogicControllerTask.kt
core/src/ru/deadsoftware/cavedroid/misc/Assets.java
core/src/ru/deadsoftware/cavedroid/misc/Renderer.java
core/src/ru/deadsoftware/cavedroid/misc/utils/ArrayMapExtensions.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/misc/utils/MeasureUnitsUtils.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/misc/utils/RenderingUtils.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/misc/utils/SpriteUtils.kt
desktop/build.gradle

index 01d66ff111ed5981f43a24193ceccf84cdbaadeb..5600fb258485c876e5c08d0bae897eb9873b396f 100644 (file)
@@ -5,7 +5,8 @@
       "collision": false,
       "transparent": true,
       "drop": "none",
-      "texture": "none"
+      "texture": "none",
+      "meta": "none"
     },
     "stone": {
       "id": 1,
     "none": {
       "id": 0,
       "name": "",
-      "type": "block",
+      "type": "none",
       "texture": "none"
     },
     "stone": {
index 0bf5c2d4acf38a18bc33dd99af0b105fe1925f6a..058c8b27a18863d475d0c8c04c3630d386da1ec7 100644 (file)
@@ -5,9 +5,15 @@ import ru.deadsoftware.cavedroid.MainComponent;
 import ru.deadsoftware.cavedroid.game.actions.PlaceBlockActionsModule;
 import ru.deadsoftware.cavedroid.game.actions.UpdateBlockActionsModule;
 import ru.deadsoftware.cavedroid.game.actions.UseItemActionsModule;
+import ru.deadsoftware.cavedroid.game.render.RenderModule;
 
 @GameScope
-@Component(dependencies = MainComponent.class, modules = {GameModule.class, UseItemActionsModule.class, UpdateBlockActionsModule.class, PlaceBlockActionsModule.class})
+@Component(dependencies = MainComponent.class,
+        modules = {GameModule.class,
+                UseItemActionsModule.class,
+                UpdateBlockActionsModule.class,
+                PlaceBlockActionsModule.class,
+                RenderModule.class})
 public interface GameComponent {
     GameProc getGameProc();
 
index fb20d8fb0520ff9fe125d29ce1de5a4645f1161c..e5b08381fbd10d0ad9285c4805dfe3a5bea00179 100644 (file)
@@ -484,19 +484,19 @@ public class GameInput {
         return mKeyDown;
     }
 
-    int getBlockDamage() {
+    public int getBlockDamage() {
         return mBlockDamage;
     }
 
-    int getCurX() {
+    public int getCurX() {
         return mCurX;
     }
 
-    int getCurY() {
+    public int getCurY() {
         return mCurY;
     }
 
-    int getCreativeScroll() {
+    public int getCreativeScroll() {
         return mCreativeScroll;
     }
 
index 23fe4fbe4104977b0f47e1b1523417e7fae2cfc2..075af6f4f39cf707ad9bd48f78c6f21f8fc66b06 100644 (file)
@@ -99,11 +99,11 @@ class GameItemsHolder @Inject constructor(
         return itemsMap.values
     }
 
-    fun getItemFromCreativeInventory(position: Int): Item? {
+    fun getItemFromCreativeInventory(position: Int): Item {
         return if (position in itemsMap.values.indices) {
             itemsMap.values.elementAt(position)
         } else {
-            null
+            fallbackItem
         }
     }
 
index 1a0a0448aba0889f6bf85284f6a6b9d88de36458..184e376cba5c448991c67c303e73f132c613cb3a 100644 (file)
@@ -5,7 +5,6 @@ import com.badlogic.gdx.utils.Timer;
 import ru.deadsoftware.cavedroid.game.mobs.MobsController;
 import ru.deadsoftware.cavedroid.game.world.GameWorldBlocksLogicControllerTask;
 import ru.deadsoftware.cavedroid.game.world.GameWorldFluidsLogicControllerTask;
-import ru.deadsoftware.cavedroid.misc.utils.AssetLoader;
 
 import javax.inject.Inject;
 
@@ -18,7 +17,6 @@ public class GameProc implements Disposable {
     private final MobsController mMobsController;
     private final GameWorldFluidsLogicControllerTask mGameWorldFluidsLogicControllerTask;
     private final GameWorldBlocksLogicControllerTask mGameWorldBlocksLogicControllerTask;
-    private final GameItemsHolder mGameItemsHolder;
 
     private final Timer mWorldLogicTimer = new Timer();
 
@@ -28,8 +26,7 @@ public class GameProc implements Disposable {
                     GameRenderer gameRenderer,
                     MobsController mobsController,
                     GameWorldFluidsLogicControllerTask gameWorldFluidsLogicControllerTask,
-                    GameWorldBlocksLogicControllerTask gameWorldBlocksLogicControllerTask,
-                    GameItemsHolder gameItemsHolder
+                    GameWorldBlocksLogicControllerTask gameWorldBlocksLogicControllerTask
     ) {
         mGamePhysics = gamePhysics;
         mGameInput = gameInput;
@@ -37,9 +34,8 @@ public class GameProc implements Disposable {
         mMobsController = mobsController;
         mGameWorldFluidsLogicControllerTask = gameWorldFluidsLogicControllerTask;
         mGameWorldBlocksLogicControllerTask = gameWorldBlocksLogicControllerTask;
-        mGameItemsHolder = gameItemsHolder;
 
-        mGameItemsHolder.initialize();
+
 
         mWorldLogicTimer.scheduleTask(gameWorldFluidsLogicControllerTask, 0,
                 GameWorldFluidsLogicControllerTask.FLUID_UPDATE_INTERVAL_SEC);
index a3a9f015fb92b00a2f5fb0019a44af1650e7b3da..c0af140bbf3b64dfc7bcc1bddd906087dce92e1c 100644 (file)
 package ru.deadsoftware.cavedroid.game;
 
 import com.badlogic.gdx.Gdx;
-import com.badlogic.gdx.graphics.Color;
 import com.badlogic.gdx.graphics.GL20;
-import com.badlogic.gdx.graphics.g2d.Sprite;
-import com.badlogic.gdx.graphics.g2d.TextureRegion;
-import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
-import com.badlogic.gdx.math.Intersector;
-import com.badlogic.gdx.math.MathUtils;
-import com.badlogic.gdx.math.Rectangle;
-import kotlin.collections.CollectionsKt;
 import ru.deadsoftware.cavedroid.MainConfig;
-import ru.deadsoftware.cavedroid.game.mobs.Mob;
 import ru.deadsoftware.cavedroid.game.mobs.MobsController;
 import ru.deadsoftware.cavedroid.game.mobs.Player;
-import ru.deadsoftware.cavedroid.game.model.block.Block;
-import ru.deadsoftware.cavedroid.game.model.item.Item;
-import ru.deadsoftware.cavedroid.game.objects.Drop;
-import ru.deadsoftware.cavedroid.game.objects.DropController;
-import ru.deadsoftware.cavedroid.game.world.GameWorld;
-import ru.deadsoftware.cavedroid.misc.ControlMode;
+import ru.deadsoftware.cavedroid.game.render.IGameRenderer;
 import ru.deadsoftware.cavedroid.misc.Renderer;
 
-import javax.annotation.CheckForNull;
-import javax.annotation.Nullable;
 import javax.inject.Inject;
-
-import java.util.Collection;
-
-import static ru.deadsoftware.cavedroid.misc.Assets.*;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Set;
 
 @GameScope
 public class GameRenderer extends Renderer {
 
-    private static final String TAG = "GameRenderer";
-
-    private final MainConfig mMainConfig;
     private final GameInput mGameInput;
-    private final GameWorld mGameWorld;
     private final MobsController mMobsController;
-    private final DropController mDropController;
-    private final GameItemsHolder mGameItemsHolder;
+    private final List<IGameRenderer> mRenderers;
 
     @Inject
     GameRenderer(MainConfig mainConfig,
                  GameInput gameInput,
-                 GameWorld gameWorld,
                  MobsController mobsController,
-                 DropController dropController,
-                 GameItemsHolder gameItemsHolder) {
+                 Set<IGameRenderer> renderers) {
         super(mainConfig.getWidth(), mainConfig.getHeight());
 
-        mMainConfig = mainConfig;
         mGameInput = gameInput;
-        mGameWorld = gameWorld;
         mMobsController = mobsController;
-        mDropController = dropController;
-        mGameItemsHolder = gameItemsHolder;
+        mRenderers = new ArrayList<>(renderers);
+        mRenderers.sort(Comparator.comparingInt(IGameRenderer::getRenderLayer));
 
         Gdx.gl.glClearColor(0f, .6f, .6f, 1f);
     }
 
-    private float drawX(int x) {
-        return x * 16 - getCamX();
-    }
-
-    private float drawY(int y) {
-        return y * 16 - getCamY();
-    }
-
-    private void drawWreck(Block bl) {
-        if (mGameInput.getBlockDamage() > 0) {
-            int index = 10 * mGameInput.getBlockDamage() / bl.getHp();
-            String key = "break_" + index;
-
-            if (index > 10 || index < 0) {
-                return;
-            }
-
-            spriter.draw(textureRegions.get(key), mGameInput.getCurX() * 16 - getCamX(),
-                    mGameInput.getCurY() * 16 - getCamY());
-        }
-    }
-
-    private void drawBlock(int x, int y, boolean drawBG) {
-        if (drawBG) {
-            if ((!mGameWorld.hasForeAt(x, y) || mGameWorld.getForeMap(x, y).isTransparent())
-                    && mGameWorld.hasBackAt(x, y)) {
-                mGameWorld.getBackMap(x, y).draw(spriter, drawX(x), drawY(y));
-                if (!mGameWorld.hasForeAt(x, y) && x == mGameInput.getCurX() && y == mGameInput.getCurY()) {
-                    drawWreck(mGameWorld.getBackMap(mGameInput.getCurX(), mGameInput.getCurY()));
-                }
-            }
-        }
-        if (mGameWorld.hasForeAt(x, y) && mGameWorld.getForeMap(x, y).isBackground() == drawBG) {
-            mGameWorld.getForeMap(x, y).draw(spriter, drawX(x), drawY(y));
-            if (x == mGameInput.getCurX() && y == mGameInput.getCurY()) {
-                drawWreck(mGameWorld.getForeMap(mGameInput.getCurX(), mGameInput.getCurY()));
-            }
-        }
-    }
-
-    private void drawWorld(boolean bg) {
-        int minX = (int) (getCamX() / 16) - 1;
-        int minY = (int) (getCamY() / 16) - 1;
-        int maxX = (int) ((getCamX() + getWidth()) / 16) + 1;
-        int maxY = (int) ((getCamY() + getHeight()) / 16) + 1;
-        if (minY < 0) {
-            minY = 0;
-        }
-        if (maxY > mGameWorld.getHeight()) {
-            maxY = mGameWorld.getHeight();
-        }
-        for (int y = minY; y < maxY; y++) {
-            for (int x = minX; x < maxX; x++) {
-                drawBlock(x, y, bg);
-            }
-        }
-        if (bg) {
-            spriter.end();
-            Gdx.gl.glEnable(GL20.GL_BLEND);
-            Gdx.gl.glBlendFunc(GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA);
-            shaper.begin(ShapeRenderer.ShapeType.Filled);
-            shaper.setColor(0f, 0f, 0f, .5f);
-            for (int y = minY; y < maxY; y++) {
-                for (int x = minX; x < maxX; x++) {
-                    if ((!mGameWorld.hasForeAt(x, y) || mGameWorld.getForeMap(x, y).isTransparent())
-                            && mGameWorld.hasBackAt(x, y)) {
-                        shaper.rect(drawX(x), drawY(y), 16, 16);
-                    }
-                }
-            }
-            shaper.end();
-            Gdx.gl.glDisable(GL20.GL_BLEND);
-            spriter.begin();
-        }
-    }
-
-    private Rectangle getShiftedRectRespectfulToViewPort(final Rectangle rect, final float shift) {
-        return new Rectangle(rect.x + shift - getCamX(), rect.y - getCamY(), rect.width, rect.height);
-    }
-
-    @CheckForNull
-    private Rectangle getDrawingRectIfInsideViewport(final Rectangle rectangle) {
-        final Rectangle viewportRect = new Rectangle(0, 0, getWidth(), getHeight());
-
-        final Rectangle notShifted = getShiftedRectRespectfulToViewPort(rectangle, 0);
-        if (Intersector.overlaps(viewportRect, notShifted)) {
-            return notShifted;
-        }
-
-        final Rectangle shiftedLeft = getShiftedRectRespectfulToViewPort(rectangle, -mGameWorld.getWidthPx());
-        if (Intersector.overlaps(viewportRect, shiftedLeft)) {
-            return shiftedLeft;
-        }
-
-        final Rectangle shiftedRight = getShiftedRectRespectfulToViewPort(rectangle, mGameWorld.getWidthPx());
-        if (Intersector.overlaps(viewportRect, shiftedRight)) {
-            return shiftedRight;
-        }
-
-        return null;
-    }
-
-    private void drawMob(Mob mob, float delta) {
-        float mobDrawX = mob.getX() - getCamX();
-        float mobDrawY = mob.getY() - getCamY();
-
-        if (mobDrawX + mob.getWidth() < 0 && mobDrawX + mGameWorld.getWidthPx() > 0) {
-            mobDrawX += mGameWorld.getWidthPx();
-        } else if (mobDrawX > getWidth() && mobDrawX + mob.getWidth() - mGameWorld.getWidthPx() > 0) {
-            mobDrawX -= mGameWorld.getWidthPx();
-        } else if (mobDrawX + mob.getWidth() < 0 && mobDrawX > getWidth()) {
-            return;
-        }
-
-        mob.draw(spriter, mobDrawX, mobDrawY, delta);
-    }
-
-    private void drawDrop(Drop drop) {
-        @CheckForNull final Rectangle drawingRect = getDrawingRectIfInsideViewport(drop);
-
-        if (drawingRect == null) {
-            return;
-        }
-
-        final Sprite sprite = drop.getItem().getSprite();
-
-        sprite.setPosition(drawingRect.x, drawingRect.y);
-        sprite.setSize(drawingRect.width, drawingRect.height);
-        sprite.draw(spriter);
-    }
-
-    @SuppressWarnings("IntegerDivisionInFloatingPointContext")
-    private void drawCreative() {
-        TextureRegion creative = textureRegions.get("creative");
-        float x = getWidth() / 2 - (float) creative.getRegionWidth() / 2;
-        float y = getHeight() / 2 - (float) creative.getRegionHeight() / 2;
-        spriter.draw(creative, x, y);
-        spriter.draw(textureRegions.get("handle"), x + 156,
-                y + 18 + (mGameInput.getCreativeScroll() * (72f / mGameItemsHolder.getCreativeScrollAmount())));
-        final Collection<Item> items = mGameItemsHolder.getAllItems();
-        for (int i = mGameInput.getCreativeScroll() * 8; i < mGameInput.getCreativeScroll() * 8 + 40; i++) {
-            if (i > 0 && i < items.size()) {
-                Item item = CollectionsKt.elementAt(items, i);
-                if (item != mGameItemsHolder.getFallbackItem()) {
-                    spriter.draw(CollectionsKt.elementAt(items, i).getSprite(),
-                            x + 8 + ((i - mGameInput.getCreativeScroll() * 8) % 8) * 18,
-                            y + 18 + ((i - mGameInput.getCreativeScroll() * 8) / 8) * 18);
-                }
-            }
-        }
-        for (int i = 0; i < 9; i++) {
-            if (mMobsController.getPlayer().inventory[i] != null && mMobsController.getPlayer().inventory[i].getItem() != mGameItemsHolder.getFallbackItem()) {
-                spriter.draw(mMobsController.getPlayer().inventory[i].getItem().getSprite(),
-                        x + 8 + i * 18, y + creative.getRegionHeight() - 24);
-            }
-        }
-
-    }
-
-    private void drawHealth(float x, float y) {
-        Player player = mMobsController.getPlayer();
-
-        if (player.gameMode == 1) {
-            return;
-        }
-
-        TextureRegion wholeHeart = textureRegions.get("heart_whole");
-        TextureRegion halfHeart = textureRegions.get("heart_half");
-
-        int wholeHearts = player.getHealth() / 2;
-
-        for (int i = 0; i < wholeHearts; i++) {
-            spriter.draw(wholeHeart, x + i * wholeHeart.getRegionWidth(), y);
-        }
-
-        if (player.getHealth() % 2 == 1) {
-            spriter.draw(halfHeart, x + wholeHearts * wholeHeart.getRegionWidth(), y);
-        }
-    }
-
-    private void drawGUI() {
-        TextureRegion cursor = textureRegions.get("cursor");
-        TextureRegion hotbar = textureRegions.get("hotbar");
-        TextureRegion hotbarSelector = textureRegions.get("hotbar_selector");
-
-        if (mGameWorld.hasForeAt(mGameInput.getCurX(), mGameInput.getCurY()) ||
-                mGameWorld.hasBackAt(mGameInput.getCurX(), mGameInput.getCurY()) ||
-                mGameInput.getControlMode() == ControlMode.CURSOR || mMainConfig.isTouch()) {
-            spriter.draw(cursor, mGameInput.getCurX() * 16 - getCamX(), mGameInput.getCurY() * 16 - getCamY());
-        }
-
-        float hotbarX = getWidth() / 2 - (float) hotbar.getRegionWidth() / 2;
-        spriter.draw(hotbar, hotbarX, 0);
-        drawHealth(hotbarX, hotbar.getRegionHeight());
-
-        for (int i = 0; i < 9; i++) {
-            if (mMobsController.getPlayer().inventory[i] != null && mMobsController.getPlayer().inventory[i].getItem() != mGameItemsHolder.getFallbackItem()) {
-                spriter.draw(mMobsController.getPlayer().inventory[i].getItem().getSprite(),
-                        getWidth() / 2 - (float) hotbar.getRegionWidth() / 2 + 3 + i * 20,
-                        3);
-            }
-        }
-        spriter.draw(hotbarSelector,
-                getWidth() / 2 - (float) hotbar.getRegionWidth() / 2 - 1 + 20 * mMobsController.getPlayer().slot,
-                -1);
-    }
-
-    private void drawTouchGui() {
-        for (int i = 0; i < guiMap.size; i++) {
-            Rectangle touchKey = guiMap.getValueAt(i).getRect();
-            spriter.draw(textureRegions.get(guiMap.getKeyAt(i)),
-                    touchKey.x, touchKey.y, touchKey.width, touchKey.height);
-        }
-        if (mGameInput.getControlMode() == ControlMode.CURSOR) {
-            spriter.draw(textureRegions.get("shade"), 83, getHeight() - 21);
-        }
-    }
-
-    private void drawGamePlay(float delta) {
-        Player player = mMobsController.getPlayer();
-
-        drawWorld(true);
-        player.draw(spriter, player.getX() - getCamX() - player.getWidth() / 2, player.getY() - getCamY(), delta);
-        mMobsController.getMobs().forEach((mob) -> {
-            drawMob(mob, delta);
-        });
-        mDropController.forEach(this::drawDrop);
-        drawWorld(false);
-        drawGUI();
-    }
-
     private void updateCameraPosition() {
         Player player = mMobsController.getPlayer();
         setCamPos(player.getX() + player.getWidth() / 2 - getWidth() / 2,
                 player.getY() + player.getHeight() / 2 - getHeight() / 2);
     }
 
-    @Nullable
-    private Color getMinimapColor(int x, int y) {
-        @Nullable Color result = null;
-
-        final boolean hasForeMap = mGameWorld.hasForeAt(x, y);
-        final boolean hasBackMap = mGameWorld.hasBackAt(x, y);
-
-        if (hasForeMap) {
-            final Block block = mGameWorld.getForeMap(x, y);
-
-            if (block.isWater()) {
-                result = Color.BLUE;
-            } else if (block.isLava()) {
-                result = Color.RED;
-            } else {
-                result = Color.BLACK;
-            }
-        } else if (hasBackMap) {
-            result = Color.DARK_GRAY;
-        }
-
-        return result;
-    }
-
-    private void drawMiniMap(float miniMapX, float miniMapY, float size) {
-        shaper.begin(ShapeRenderer.ShapeType.Filled);
-
-        shaper.setColor(Color.LIGHT_GRAY);
-        shaper.rect(miniMapX, miniMapY, size, size);
-
-        for (int x = 0; x < size; x++) {
-            for (int y = 0; y < size; y++) {
-
-                final int worldX = (int) (mMobsController.getPlayer().getMapX() - size / 2 + x);
-                final int worldY = (int) (mMobsController.getPlayer().getUpperMapY() - size / 2 + y);
-
-                @Nullable final Color color = getMinimapColor(worldX, worldY);
-
-                if (color != null) {
-                    shaper.setColor(color);
-                    shaper.rect(miniMapX + x, miniMapY + y, 1, 1);
-                }
-            }
-        }
-
-        shaper.setColor(Color.OLIVE);
-        shaper.rect(miniMapX + size / 2, miniMapY + size / 2, 1, 2);
-        shaper.end();
-    }
 
     @Override
     public void render(float delta) {
-        int fps = MathUtils.ceil(1 / delta);
         updateCameraPosition();
         mGameInput.moveCursor(this);
 
         Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
 
         spriter.begin();
-
-        drawGamePlay(delta);
-
-        switch (mMainConfig.getGameUiWindow()) {
-            case CREATIVE_INVENTORY:
-                drawCreative();
-                break;
-            //TODO draw other ui windows
-        }
-
-
-        if (mMainConfig.isTouch()) {
-            drawTouchGui();
-        }
-
+        mRenderers.forEach(iGameRenderer -> iGameRenderer.draw(spriter, shaper, getCameraViewport(), delta));
         spriter.end();
-
-        if (mMainConfig.isShowMap()) {
-            drawMiniMap(getWidth() - 64f - 24f, 24f, 64f);
-        }
-
-        if (mMainConfig.isShowInfo()) {
-            spriter.begin();
-            Player player = mMobsController.getPlayer();
-            drawString("FPS: " + fps, 0, 0);
-            drawString("X: " + player.getMapX(), 0, 10);
-            drawString("Y: " + player.getUpperMapY(), 0, 20);
-            drawString("CurX: " + mGameInput.getCurX(), 0, 30);
-            drawString("CurY: " + mGameInput.getCurY(), 0, 40);
-            drawString("Velocity: " + player.getVelocity(), 0, 50);
-            drawString("Swim: " + player.swim, 0, 60);
-            drawString("Mobs: " + mMobsController.getMobs().size(), 0, 70);
-            drawString("Drops: " + mDropController.getSize(), 0, 80);
-            drawString("Block: " + mGameWorld.getForeMap(mGameInput.getCurX(), mGameInput.getCurY()).getParams().getKey(), 0, 90);
-            drawString("Hand: " + mMobsController.getPlayer().inventory[mMobsController.getPlayer().slot].getItem().getParams().getKey(), 0, 100);
-            drawString("Game mode: " + player.gameMode, 0, 110);
-            spriter.end();
-        }
-
     }
 
 }
index 53d147228f96a334a31ce9cd2e575c14b154e55e..cb52b3c76b126812e355c43d9ce076c7d5749140 100644 (file)
@@ -118,7 +118,7 @@ public class GameSaver {
 
         BufferedOutputStream out = new BufferedOutputStream(file.write(false));
 
-        out.write(intToBytes(SAVE_VERSION));
+        out.write(SAVE_VERSION);
         out.write(intToBytes(width));
         out.write(intToBytes(height));
 
@@ -127,16 +127,16 @@ public class GameSaver {
             run = 0;
             for (Block[] blocks : map) {
                 int newValue = dict.get(blocks[y].getParams().getKey());
-                if (newValue != block) {
-                    out.write(intToBytes(run));
-                    out.write(intToBytes(block));
+                if (run >= 0xFF || newValue != block) {
+                    out.write(run);
+                    out.write(block);
                     run = 0;
                     block = dict.get(blocks[y].getParams().getKey());
                 }
                 run++;
             }
-            out.write(intToBytes(run));
-            out.write(intToBytes(block));
+            out.write(run);
+            out.write(block);
         }
 
         out.flush();
@@ -150,7 +150,7 @@ public class GameSaver {
 
         DataInputStream in = new DataInputStream(file.read());
 
-        version = in.readInt();
+        version = in.readByte();
 
         if (SAVE_VERSION == version) {
             width = in.readInt();
@@ -158,8 +158,8 @@ public class GameSaver {
             map = new Block[width][height];
             for (int y = 0; y < height; y++) {
                 for (int x = 0; x < width; x += run) {
-                    run = in.readInt();
-                    block = in.readInt();
+                    run = in.readUnsignedByte();
+                    block = in.readUnsignedByte();
                     for (int i = x; i < x + run; i++) {
                         map[i][y] = gameItemsHolder.getBlock(dict[block]);
                     }
diff --git a/core/src/ru/deadsoftware/cavedroid/game/debug/DebugInfoStringsProvider.kt b/core/src/ru/deadsoftware/cavedroid/game/debug/DebugInfoStringsProvider.kt
new file mode 100644 (file)
index 0000000..678a91f
--- /dev/null
@@ -0,0 +1,37 @@
+package ru.deadsoftware.cavedroid.game.debug
+
+import com.badlogic.gdx.Gdx
+import ru.deadsoftware.cavedroid.game.GameInput
+import ru.deadsoftware.cavedroid.game.GameScope
+import ru.deadsoftware.cavedroid.game.mobs.MobsController
+import ru.deadsoftware.cavedroid.game.objects.DropController
+import ru.deadsoftware.cavedroid.game.world.GameWorld
+import javax.inject.Inject
+
+@GameScope
+class DebugInfoStringsProvider @Inject constructor(
+    private val mobsController: MobsController,
+    private val gameInput: GameInput,
+    private val dropController: DropController,
+    private val gameWorld: GameWorld
+) {
+
+    fun getDebugStrings(): List<String> {
+        val player = mobsController.player
+
+        return listOf(
+            "FPS: ${Gdx.graphics.framesPerSecond}",
+            "X: ${player.mapX}",
+            "Y: ${player.upperMapY}",
+            "CurX: ${gameInput.curX}",
+            "CurY: ${gameInput.curY}",
+            "Velocity: ${player.velocity}",
+            "Swim: ${player.swim}",
+            "Mobs: ${mobsController.mobs.size}",
+            "Drops: ${dropController.size}",
+            "Block: ${gameWorld.getForeMap(gameInput.curX, gameInput.curY).params.key}",
+            "Hand: ${player.inventory[player.slot].item.params.key}",
+            "Game mode: ${player.gameMode}"
+        )
+    }
+}
\ No newline at end of file
index bce9a2c435a0da4e2d81c794af791f95f55d9a49..9972617b5364156afb13e4b35736be8c56251868 100644 (file)
@@ -132,7 +132,7 @@ public class Player extends Mob {
     private void drawItem(SpriteBatch spriteBatch, float x, float y, float anim) {
         final Item item = inventory(slot);
 
-        if (item == null || item.getParams().getKey().equals(GameItemsHolder.FALLBACK_ITEM_KEY)) {
+        if (item == null || item.isNone()) {
             return;
         }
 
index 2411a6352d963823bb921f1a8a741172150fddfb..4a0c31a4a4fa7e7038284aa5b3b9776412c99c03 100644 (file)
@@ -104,6 +104,11 @@ sealed class Block {
         return this is Slab
     }
 
+    fun isNone(): Boolean {
+        contract { returns(true) implies (this@Block is None) }
+        return this is None
+    }
+
     fun getRectangle(x: Int, y: Int): Rectangle {
         return Rectangle(
             /* x = */ x * 16f + params.collisionMargins.left,
@@ -113,7 +118,9 @@ sealed class Block {
         )
     }
 
-
+    data class None(
+        override val params: CommonBlockParams
+    ) : Block()
 
     data class Normal(
         override val params: CommonBlockParams,
@@ -158,7 +165,6 @@ sealed class Block {
     @Deprecated(LEGACY_ACCESSOR_DEPRECATION) fun isTransparent() = params.isTransparent
     @Deprecated(LEGACY_ACCESSOR_DEPRECATION) fun getTexture() = sprite
 
-
     companion object {
         private const val LEGACY_ACCESSOR_DEPRECATION = "legacy accessors will be removed"
         private const val ANIMATION_FRAME_DURATION_MS = 100L
index fe2bc46bad46030cd43eb0a74f284fa4bd8f4629..80a96251442ebd6bafb924b22044d4b24df703eb 100644 (file)
@@ -1,5 +1,6 @@
 package ru.deadsoftware.cavedroid.game.model.item
 
+import com.badlogic.gdx.Gdx
 import com.badlogic.gdx.graphics.g2d.Sprite
 import ru.deadsoftware.cavedroid.game.model.block.Block
 import kotlin.contracts.ExperimentalContracts
@@ -19,6 +20,11 @@ sealed class Item {
         return params.key == (other as Item).params.key
     }
 
+    fun isNone(): Boolean {
+        contract { returns(true) implies (this@Item is None) }
+        return this is None
+    }
+    
     fun isPlaceable(): Boolean {
         contract { returns(true) implies (this@Item is Placeable) }
         return this is Placeable
@@ -47,7 +53,14 @@ sealed class Item {
     sealed class Usable : Item() {
         abstract val useActionKey: String
     }
-    
+
+    data class None(
+        override val params: CommonItemParams,
+    ): Item() {
+        override val sprite: Sprite
+            get() = throw IllegalAccessException("Trying to get sprite of None")
+    }
+
     data class Placeable(
         override val params: CommonItemParams,
         val block: Block
index eb3080fd876233630c269753582beacca3ce9df6..cae219f00c053b679141776e2ec8a67b40395bf1 100644 (file)
@@ -19,6 +19,7 @@ class BlockMapper @Inject constructor() {
             "water" -> Water(commonBlockParams, requireNotNull(dto.state))
             "lava" -> Lava(commonBlockParams, requireNotNull(dto.state))
             "slab" -> Slab(commonBlockParams, requireNotNull(dto.fullBlock))
+            "none" -> None(commonBlockParams)
             else -> Normal(commonBlockParams)
         }
     }
index 311c10c838b3fe33b6d92d3af93cb68b9b12d138..ad79cbd824e01f6b0308f3d04e190f9952f16f29 100644 (file)
@@ -23,6 +23,7 @@ class ItemMapper @Inject constructor() {
             "shovel" -> Shovel(params, requireNotNull(loadSprite(dto)), dto.mobDamageMultiplier, dto.blockDamageMultiplier)
             "sword" -> Sword(params, requireNotNull(loadSprite(dto)), dto.mobDamageMultiplier, dto.blockDamageMultiplier)
             "block" -> Placeable(params, requireNotNull(block))
+            "none" -> None(params)
             else -> throw IllegalArgumentException("Unknown item type ${dto.type}")
         }
     }
@@ -40,7 +41,7 @@ class ItemMapper @Inject constructor() {
     }
 
     private fun loadSprite(dto: ItemDto): Sprite? {
-        if (dto.type == "block" || dto.texture == GameItemsHolder.FALLBACK_ITEM_KEY) {
+        if (dto.type == "none" || dto.type == "block" || dto.texture == GameItemsHolder.FALLBACK_ITEM_KEY) {
             return null
         }
 
diff --git a/core/src/ru/deadsoftware/cavedroid/game/render/BackgroundBlocksRenderer.kt b/core/src/ru/deadsoftware/cavedroid/game/render/BackgroundBlocksRenderer.kt
new file mode 100644 (file)
index 0000000..ae02aa4
--- /dev/null
@@ -0,0 +1,51 @@
+package ru.deadsoftware.cavedroid.game.render
+
+import com.badlogic.gdx.Gdx
+import com.badlogic.gdx.graphics.GL20
+import com.badlogic.gdx.graphics.g2d.SpriteBatch
+import com.badlogic.gdx.graphics.glutils.ShapeRenderer
+import com.badlogic.gdx.math.Rectangle
+import ru.deadsoftware.cavedroid.game.GameInput
+import ru.deadsoftware.cavedroid.game.GameScope
+import ru.deadsoftware.cavedroid.game.world.GameWorld
+import ru.deadsoftware.cavedroid.misc.utils.forEachBlockInArea
+import javax.inject.Inject
+
+@GameScope
+class BackgroundBlocksRenderer @Inject constructor(
+    gameWorld: GameWorld,
+    gameInput: GameInput
+) : BlocksRenderer(gameWorld, gameInput) {
+
+    override val renderLayer get() = RENDER_LAYER
+
+    override val background = true
+
+    override fun draw(spriteBatch: SpriteBatch, shapeRenderer: ShapeRenderer, viewport: Rectangle, delta: Float) {
+        forEachBlockInArea(viewport) { x, y ->
+            drawBackMap(spriteBatch, viewport, x, y)
+        }
+
+        spriteBatch.end()
+        Gdx.gl.glEnable(GL20.GL_BLEND)
+        Gdx.gl.glBlendFunc(GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA)
+        shapeRenderer.begin(ShapeRenderer.ShapeType.Filled)
+        shapeRenderer.setColor(0f, 0f, 0f, .5f)
+
+        forEachBlockInArea(viewport) { x, y ->
+            shadeBackMap(shapeRenderer, viewport, x, y)
+        }
+
+        shapeRenderer.end()
+        Gdx.gl.glDisable(GL20.GL_BLEND)
+        spriteBatch.begin()
+
+        forEachBlockInArea(viewport) { x, y ->
+            drawForeMap(spriteBatch, viewport, x, y)
+        }
+    }
+
+    companion object {
+        private const val RENDER_LAYER = 100000
+    }
+}
\ No newline at end of file
diff --git a/core/src/ru/deadsoftware/cavedroid/game/render/BlocksRenderer.kt b/core/src/ru/deadsoftware/cavedroid/game/render/BlocksRenderer.kt
new file mode 100644 (file)
index 0000000..3409fdf
--- /dev/null
@@ -0,0 +1,101 @@
+package ru.deadsoftware.cavedroid.game.render
+
+import com.badlogic.gdx.graphics.g2d.SpriteBatch
+import com.badlogic.gdx.graphics.g2d.TextureRegion
+import com.badlogic.gdx.graphics.glutils.ShapeRenderer
+import com.badlogic.gdx.math.Rectangle
+import ru.deadsoftware.cavedroid.game.GameInput
+import ru.deadsoftware.cavedroid.game.model.block.Block
+import ru.deadsoftware.cavedroid.game.world.GameWorld
+import ru.deadsoftware.cavedroid.misc.Assets
+import ru.deadsoftware.cavedroid.misc.utils.px
+
+abstract class BlocksRenderer(
+    protected val gameWorld: GameWorld,
+    protected val gameInput: GameInput,
+) : IGameRenderer {
+
+    protected abstract val background: Boolean
+
+    private val Block.canSeeThrough
+        get() = isNone() || params.isTransparent
+
+    private fun blockDamageTexture(index: Int): TextureRegion? {
+        if (index !in 0..MAX_BLOCK_DAMAGE_INDEX) {
+            return null
+        }
+        val textureKey = "$BLOCK_DAMAGE_TEXTURE_PREFIX$index"
+        return Assets.textureRegions[textureKey]
+    }
+
+    private fun drawBlockDamage(spriteBatch: SpriteBatch, block: Block, x: Float, y: Float) {
+        val blockDamage = gameInput.blockDamage
+        if (blockDamage <= 0) {
+            return
+        }
+
+        val index = MAX_BLOCK_DAMAGE_INDEX * (blockDamage / block.params.hitPoints)
+        val texture = blockDamageTexture(index) ?: return
+
+        spriteBatch.draw(texture, x, y)
+    }
+
+    protected fun shadeBackMap(
+        shapeRenderer: ShapeRenderer,
+        viewport: Rectangle,
+        x: Int,
+        y: Int
+    ) {
+        val foregroundBlock = gameWorld.getForeMap(x, y)
+        val backgroundBlock = gameWorld.getBackMap(x, y)
+
+        if (foregroundBlock.canSeeThrough && !backgroundBlock.isNone()) {
+            val drawX = x.px - viewport.x
+            val drawY = y.px - viewport.y
+            val marginLeft = backgroundBlock.params.spriteMargins.left
+            val marginTop = backgroundBlock.params.spriteMargins.top
+
+            shapeRenderer.rect(
+                /* x = */ drawX + marginLeft,
+                /* y = */ drawY + marginTop,
+                /* width = */ backgroundBlock.width,
+                /* height = */ backgroundBlock.height
+            )
+        }
+    }
+
+    protected fun drawBackMap(spriteBatch: SpriteBatch, viewport: Rectangle, x: Int, y: Int) {
+        val foregroundBlock = gameWorld.getForeMap(x, y)
+        val backgroundBlock = gameWorld.getBackMap(x, y)
+
+        if (foregroundBlock.canSeeThrough && !backgroundBlock.isNone()) {
+            val drawX = x.px - viewport.x
+            val drawY = y.px - viewport.y
+            backgroundBlock.draw(spriteBatch, drawX, drawY)
+
+            if (foregroundBlock.isNone()) {
+                drawBlockDamage(spriteBatch, backgroundBlock, drawX, drawY)
+            }
+        }
+    }
+
+    protected fun drawForeMap(spriteBatch: SpriteBatch, viewport: Rectangle, x: Int, y: Int) {
+        val foregroundBlock = gameWorld.getForeMap(x, y)
+
+        if (!foregroundBlock.isNone() && foregroundBlock.params.isBackground == background) {
+            val drawX = x.px - viewport.x
+            val drawY = y.px - viewport.y
+            foregroundBlock.draw(spriteBatch, drawX, drawY)
+
+            if (!foregroundBlock.isNone()) {
+                drawBlockDamage(spriteBatch, foregroundBlock, drawX, drawY)
+            }
+        }
+    }
+
+    companion object {
+        private const val BLOCK_DAMAGE_TEXTURE_PREFIX = "break_"
+        private const val MAX_BLOCK_DAMAGE_INDEX = 10
+    }
+
+}
\ No newline at end of file
diff --git a/core/src/ru/deadsoftware/cavedroid/game/render/DebugRenderer.kt b/core/src/ru/deadsoftware/cavedroid/game/render/DebugRenderer.kt
new file mode 100644 (file)
index 0000000..c8be755
--- /dev/null
@@ -0,0 +1,117 @@
+package ru.deadsoftware.cavedroid.game.render
+
+import com.badlogic.gdx.graphics.Color
+import com.badlogic.gdx.graphics.g2d.SpriteBatch
+import com.badlogic.gdx.graphics.glutils.ShapeRenderer
+import com.badlogic.gdx.math.Rectangle
+import ru.deadsoftware.cavedroid.MainConfig
+import ru.deadsoftware.cavedroid.game.GameScope
+import ru.deadsoftware.cavedroid.game.debug.DebugInfoStringsProvider
+import ru.deadsoftware.cavedroid.game.mobs.MobsController
+import ru.deadsoftware.cavedroid.game.model.block.Block
+import ru.deadsoftware.cavedroid.game.world.GameWorld
+import ru.deadsoftware.cavedroid.misc.Assets
+import ru.deadsoftware.cavedroid.misc.utils.bl
+import ru.deadsoftware.cavedroid.misc.utils.forEachBlockInArea
+import ru.deadsoftware.cavedroid.misc.utils.px
+import javax.inject.Inject
+
+@GameScope
+class DebugRenderer @Inject constructor(
+    private val mainConfig: MainConfig,
+    private val gameWorld: GameWorld,
+    private val mobsController: MobsController,
+    private val debugInfoStringsProvider: DebugInfoStringsProvider,
+) : IGameRenderer {
+
+    override val renderLayer get() = RENDER_LAYER
+
+    private fun SpriteBatch.drawString(str: String, x: Float, y: Float) {
+        Assets.minecraftFont.draw(this, str, x, y)
+    }
+
+    private fun getMinimapColor(x: Int, y: Int): Color? {
+        val foregroundBlock = gameWorld.getForeMap(x, y)
+
+        return if (!foregroundBlock.isNone()) {
+            when (foregroundBlock) {
+                is Block.Water -> Color.BLUE
+                is Block.Lava -> Color.RED
+                else -> Color.BLACK
+            }
+        } else if (gameWorld.hasBackAt(x, y)) {
+            Color.DARK_GRAY
+        } else {
+            null
+        }
+    }
+
+    private fun drawMinimap(
+        spriteBatch: SpriteBatch,
+        shapeRenderer: ShapeRenderer,
+        minimapX: Float,
+        minimapY: Float,
+        minimapSize: Float
+    ) {
+        val mapArea = Rectangle(
+            /* x = */ mobsController.player.x - (minimapSize.px / 2),
+            /* y = */ mobsController.player.y - (minimapSize.px / 2),
+            /* width = */ minimapSize.px,
+            /* height = */ minimapSize.px
+        )
+
+        spriteBatch.end()
+        shapeRenderer.begin(ShapeRenderer.ShapeType.Filled)
+        shapeRenderer.color = Color.LIGHT_GRAY
+        shapeRenderer.rect(minimapX, minimapY, minimapSize, minimapSize)
+
+        forEachBlockInArea(mapArea) { x, y ->
+            getMinimapColor(x, y)?.let { color ->
+                shapeRenderer.setColor(color)
+                shapeRenderer.rect(
+                    /* x = */ minimapX + (x - mapArea.x.bl),
+                    /* y = */ minimapY + (y - mapArea.y.bl),
+                    /* width = */ 1f,
+                    /* height = */ 1f
+                )
+            }
+        }
+
+        shapeRenderer.color = Color.OLIVE
+        shapeRenderer.rect(minimapX + minimapSize / 2, minimapY + minimapSize / 2, 1f, 2f)
+        shapeRenderer.end()
+        spriteBatch.begin()
+    }
+
+    private fun drawDebugInfo(spriteBatch: SpriteBatch) {
+        debugInfoStringsProvider.getDebugStrings().forEachIndexed { index, str ->
+            spriteBatch.drawString(str, 0f, index * 10f)
+        }
+    }
+
+    override fun draw(spriteBatch: SpriteBatch, shapeRenderer: ShapeRenderer, viewport: Rectangle, delta: Float) {
+        if (mainConfig.isShowInfo) {
+            drawDebugInfo(spriteBatch)
+        }
+
+        if (mainConfig.isShowMap) {
+            drawMinimap(
+                spriteBatch = spriteBatch,
+                shapeRenderer = shapeRenderer,
+                minimapX = viewport.width - MinimapConfig.margin - MinimapConfig.size,
+                minimapY = MinimapConfig.margin,
+                minimapSize = MinimapConfig.size
+            )
+        }
+
+    }
+
+    companion object {
+        private const val RENDER_LAYER = Int.MAX_VALUE
+
+        private data object MinimapConfig {
+            const val margin = 24f
+            const val size = 64f
+        }
+    }
+}
\ No newline at end of file
diff --git a/core/src/ru/deadsoftware/cavedroid/game/render/DropsRenderer.kt b/core/src/ru/deadsoftware/cavedroid/game/render/DropsRenderer.kt
new file mode 100644 (file)
index 0000000..efc7aa1
--- /dev/null
@@ -0,0 +1,39 @@
+package ru.deadsoftware.cavedroid.game.render
+
+import com.badlogic.gdx.graphics.g2d.SpriteBatch
+import com.badlogic.gdx.graphics.glutils.ShapeRenderer
+import com.badlogic.gdx.math.Rectangle
+import ru.deadsoftware.cavedroid.game.GameScope
+import ru.deadsoftware.cavedroid.game.objects.DropController
+import ru.deadsoftware.cavedroid.game.world.GameWorld
+import ru.deadsoftware.cavedroid.misc.utils.cycledInsideWorld
+import ru.deadsoftware.cavedroid.misc.utils.drawSprite
+import ru.deadsoftware.cavedroid.misc.utils.px
+import javax.inject.Inject
+
+@GameScope
+class DropsRenderer @Inject constructor(
+    private val dropController: DropController,
+    private val gameWorld: GameWorld,
+) : IGameRenderer {
+
+    override val renderLayer = RENDER_LAYER
+
+    override fun draw(spriteBatch: SpriteBatch, shapeRenderer: ShapeRenderer, viewport: Rectangle, delta: Float) {
+        dropController.forEach { drop ->
+            drop.cycledInsideWorld(viewport, gameWorld.width.px)?.let { dropRect ->
+                drop.item.sprite.setSize(dropRect.width, dropRect.height)
+                spriteBatch.drawSprite(
+                    sprite = drop.item.sprite,
+                    x = dropRect.x - viewport.x,
+                    y = dropRect.y - viewport.y,
+                )
+            }
+        }
+    }
+
+    companion object {
+        private const val RENDER_LAYER = 100200
+    }
+
+}
\ No newline at end of file
diff --git a/core/src/ru/deadsoftware/cavedroid/game/render/ForegroundBlocksRenderer.kt b/core/src/ru/deadsoftware/cavedroid/game/render/ForegroundBlocksRenderer.kt
new file mode 100644 (file)
index 0000000..3380490
--- /dev/null
@@ -0,0 +1,31 @@
+package ru.deadsoftware.cavedroid.game.render
+
+import com.badlogic.gdx.graphics.g2d.SpriteBatch
+import com.badlogic.gdx.graphics.glutils.ShapeRenderer
+import com.badlogic.gdx.math.Rectangle
+import ru.deadsoftware.cavedroid.game.GameInput
+import ru.deadsoftware.cavedroid.game.GameScope
+import ru.deadsoftware.cavedroid.game.world.GameWorld
+import ru.deadsoftware.cavedroid.misc.utils.forEachBlockInArea
+import javax.inject.Inject
+
+@GameScope
+class ForegroundBlocksRenderer @Inject constructor(
+    gameWorld: GameWorld,
+    gameInput: GameInput
+) : BlocksRenderer(gameWorld, gameInput) {
+
+    override val renderLayer get() = RENDER_LAYER
+
+    override val background = false
+
+    override fun draw(spriteBatch: SpriteBatch, shapeRenderer: ShapeRenderer, viewport: Rectangle, delta: Float) {
+        forEachBlockInArea(viewport) { x, y ->
+            drawForeMap(spriteBatch, viewport, x, y)
+        }
+    }
+
+    companion object {
+        private const val RENDER_LAYER = 100400
+    }
+}
\ No newline at end of file
diff --git a/core/src/ru/deadsoftware/cavedroid/game/render/HudRenderer.kt b/core/src/ru/deadsoftware/cavedroid/game/render/HudRenderer.kt
new file mode 100644 (file)
index 0000000..911df5a
--- /dev/null
@@ -0,0 +1,121 @@
+package ru.deadsoftware.cavedroid.game.render
+
+import com.badlogic.gdx.graphics.g2d.SpriteBatch
+import com.badlogic.gdx.graphics.glutils.ShapeRenderer
+import com.badlogic.gdx.math.Rectangle
+import ru.deadsoftware.cavedroid.game.GameInput
+import ru.deadsoftware.cavedroid.game.GameScope
+import ru.deadsoftware.cavedroid.game.mobs.MobsController
+import ru.deadsoftware.cavedroid.game.model.item.InventoryItem
+import ru.deadsoftware.cavedroid.game.world.GameWorld
+import ru.deadsoftware.cavedroid.misc.Assets
+import ru.deadsoftware.cavedroid.misc.ControlMode
+import ru.deadsoftware.cavedroid.misc.utils.px
+import javax.inject.Inject
+
+@GameScope
+class HudRenderer @Inject constructor(
+    private val gameInput: GameInput,
+    private val gameWorld: GameWorld,
+    private val mobsController: MobsController,
+) : IGameRenderer {
+
+    override val renderLayer = RENDER_LAYER
+
+    private val cursorTexture get() = requireNotNull(Assets.textureRegions[CURSOR_KEY])
+    private val hotbarTexture get() = requireNotNull(Assets.textureRegions[HOTBAR_KEY])
+    private val hotbarSelectorTexture get() = requireNotNull(Assets.textureRegions[HOTBAR_SELECTOR_KEY])
+    private val wholeHeartTexture get() = requireNotNull(Assets.textureRegions[WHOLE_HEART_KEY])
+    private val halfHeartTexture get() = requireNotNull(Assets.textureRegions[HALF_HEART_KEY])
+
+    private fun drawCursor(spriteBatch: SpriteBatch, viewport: Rectangle) {
+        if (gameWorld.hasForeAt(gameInput.curX, gameInput.curY) ||
+            gameWorld.hasBackAt(gameInput.curX, gameInput.curY) ||
+            gameInput.controlMode == ControlMode.CURSOR
+        ) {
+            spriteBatch.draw(cursorTexture, gameInput.curX.px - viewport.x, gameInput.curY.px - viewport.y)
+        }
+    }
+
+    private fun drawHealth(spriteBatch: SpriteBatch, x: Float, y: Float) {
+        val player = mobsController.player
+
+        if (player.gameMode == 1) {
+            return
+        }
+
+        val wholeHeart = wholeHeartTexture
+        val wholeHearts = player.health / 2
+
+        for (i in 0..<wholeHearts) {
+            spriteBatch.draw(wholeHeart, x + i * wholeHeart.regionWidth, y)
+        }
+
+        if (player.health % 2 == 1) {
+            spriteBatch.draw(halfHeartTexture, x + wholeHearts * wholeHeart.regionWidth, y)
+        }
+    }
+
+    private fun drawHotbarItems(spriteBatch: SpriteBatch, hotbarX: Float) {
+        mobsController.player.inventory.asSequence()
+            .map(InventoryItem::item)
+            .forEachIndexed { index, item ->
+                if (item.isNone()) {
+                    return@forEachIndexed
+                }
+
+                spriteBatch.draw(
+                    /* region = */ item.sprite,
+                    /* x = */ hotbarX + HotbarConfig.horizontalMargin
+                            + index * (HotbarConfig.itemSeparatorWidth + HotbarConfig.itemSlotSpace),
+                    /* y = */ HotbarConfig.verticalMargin,
+                )
+            }
+    }
+
+    private fun drawHotbarSelector(spriteBatch: SpriteBatch, hotbarX: Float) {
+        spriteBatch.draw(
+            /* region = */ hotbarSelectorTexture,
+            /* x = */ hotbarX - HotbarSelectorConfig.horizontalPadding
+                    + mobsController.player.slot * (HotbarConfig.itemSeparatorWidth + HotbarConfig.itemSlotSpace),
+            /* y = */ -HotbarSelectorConfig.verticalPadding
+        )
+    }
+
+    private fun drawHotbar(spriteBatch: SpriteBatch, viewport: Rectangle) {
+        val hotbar = hotbarTexture
+        val hotbarX = viewport.width / 2 - hotbar.regionWidth / 2
+
+        spriteBatch.draw(hotbar, hotbarX, 0f)
+        drawHealth(spriteBatch, hotbarX, hotbarTexture.regionHeight.toFloat())
+        drawHotbarItems(spriteBatch, hotbarX)
+        drawHotbarSelector(spriteBatch, hotbarX)
+    }
+
+    override fun draw(spriteBatch: SpriteBatch, shapeRenderer: ShapeRenderer, viewport: Rectangle, delta: Float) {
+        drawCursor(spriteBatch, viewport)
+        drawHotbar(spriteBatch, viewport)
+    }
+
+    companion object {
+        private const val RENDER_LAYER = 100500
+
+        private const val CURSOR_KEY = "cursor"
+        private const val HOTBAR_KEY = "hotbar"
+        private const val HOTBAR_SELECTOR_KEY = "hotbar_selector"
+        private const val WHOLE_HEART_KEY = "heart_whole"
+        private const val HALF_HEART_KEY = "heart_half"
+
+        private data object HotbarConfig {
+            const val horizontalMargin = 3f
+            const val verticalMargin = 3f
+            const val itemSeparatorWidth = 4f
+            const val itemSlotSpace = 16f
+        }
+
+        private data object HotbarSelectorConfig {
+            const val horizontalPadding = 1f
+            const val verticalPadding = 1f
+        }
+    }
+}
\ No newline at end of file
diff --git a/core/src/ru/deadsoftware/cavedroid/game/render/IGameRenderer.kt b/core/src/ru/deadsoftware/cavedroid/game/render/IGameRenderer.kt
new file mode 100644 (file)
index 0000000..88b5744
--- /dev/null
@@ -0,0 +1,20 @@
+package ru.deadsoftware.cavedroid.game.render
+
+import com.badlogic.gdx.graphics.g2d.SpriteBatch
+import com.badlogic.gdx.graphics.glutils.ShapeRenderer
+import com.badlogic.gdx.math.Rectangle
+
+interface IGameRenderer {
+
+    val renderLayer: Int
+
+    /**
+     * When called, [spriteBatch] is beginned!
+     */
+    fun draw(
+        spriteBatch: SpriteBatch,
+        shapeRenderer: ShapeRenderer,
+        viewport: Rectangle,
+        delta: Float
+    )
+}
\ No newline at end of file
diff --git a/core/src/ru/deadsoftware/cavedroid/game/render/MobsRenderer.kt b/core/src/ru/deadsoftware/cavedroid/game/render/MobsRenderer.kt
new file mode 100644 (file)
index 0000000..6d388bb
--- /dev/null
@@ -0,0 +1,45 @@
+package ru.deadsoftware.cavedroid.game.render
+
+import com.badlogic.gdx.graphics.g2d.SpriteBatch
+import com.badlogic.gdx.graphics.glutils.ShapeRenderer
+import com.badlogic.gdx.math.Rectangle
+import ru.deadsoftware.cavedroid.game.GameScope
+import ru.deadsoftware.cavedroid.game.mobs.Mob
+import ru.deadsoftware.cavedroid.game.mobs.MobsController
+import ru.deadsoftware.cavedroid.game.world.GameWorld
+import ru.deadsoftware.cavedroid.misc.utils.cycledInsideWorld
+import ru.deadsoftware.cavedroid.misc.utils.px
+import javax.inject.Inject
+
+@GameScope
+class MobsRenderer @Inject constructor(
+    private val mobsController: MobsController,
+    private val gameWorld: GameWorld,
+) : IGameRenderer {
+
+    override val renderLayer get() = RENDER_LAYER
+
+    private fun drawMob(spriteBatch: SpriteBatch, viewport: Rectangle, mob: Mob, delta: Float) {
+         mob.cycledInsideWorld(viewport, gameWorld.width.px)?.let { mobRect ->
+             mob.draw(spriteBatch, mobRect.x - viewport.x, mobRect.y - viewport.y, delta)
+         }
+    }
+
+    override fun draw(spriteBatch: SpriteBatch, shapeRenderer: ShapeRenderer, viewport: Rectangle, delta: Float) {
+        val player = mobsController.player
+        player.draw(
+            /* spriteBatch = */ spriteBatch,
+            /* x = */ player.x - viewport.x - player.width / 2,
+            /* y = */ player.y - viewport.y,
+            /* delta = */ delta
+        )
+
+        mobsController.mobs.forEach { mob ->
+            drawMob(spriteBatch, viewport, mob, delta)
+        }
+    }
+
+    companion object {
+        private const val RENDER_LAYER = 100100
+    }
+}
\ No newline at end of file
diff --git a/core/src/ru/deadsoftware/cavedroid/game/render/RenderModule.kt b/core/src/ru/deadsoftware/cavedroid/game/render/RenderModule.kt
new file mode 100644 (file)
index 0000000..a23ae32
--- /dev/null
@@ -0,0 +1,54 @@
+package ru.deadsoftware.cavedroid.game.render
+
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.IntoSet
+import ru.deadsoftware.cavedroid.game.GameScope
+
+@Module
+object RenderModule {
+
+    @Binds
+    @IntoSet
+    @GameScope
+    fun bindBackGroundBlocksRenderer(renderer: BackgroundBlocksRenderer): IGameRenderer = renderer
+
+    @Binds
+    @IntoSet
+    @GameScope
+    fun bindForegroundBlocksRenderer(renderer: ForegroundBlocksRenderer): IGameRenderer = renderer
+
+    @Binds
+    @IntoSet
+    @GameScope
+    fun bindMobsRenderer(renderer: MobsRenderer): IGameRenderer = renderer
+
+    @Binds
+    @IntoSet
+    @GameScope
+    fun bindDropsRenderer(renderer: DropsRenderer): IGameRenderer = renderer
+
+    @Binds
+    @IntoSet
+    @GameScope
+    fun bindHudRenderer(renderer: HudRenderer): IGameRenderer = renderer
+
+    @Binds
+    @IntoSet
+    @GameScope
+    fun bindWindowsRenderer(renderer: WindowsRenderer): IGameRenderer = renderer
+
+    @Binds
+    @IntoSet
+    @GameScope
+    fun bindDebugRenderer(renderer: DebugRenderer): IGameRenderer = renderer
+
+//    @Provides
+//    @GameScope
+//    fun provideGameRenderers(renderers: Set<@JvmSuppressWildcards IGameRenderer>): List<IGameRenderer> {
+//        return renderers.asSequence()
+//            .sortedWith(Comparator.comparingInt(IGameRenderer::renderLayer))
+//            .toList()
+//    }
+
+}
diff --git a/core/src/ru/deadsoftware/cavedroid/game/render/TouchControlsRenderer.kt b/core/src/ru/deadsoftware/cavedroid/game/render/TouchControlsRenderer.kt
new file mode 100644 (file)
index 0000000..e8b19d0
--- /dev/null
@@ -0,0 +1,50 @@
+package ru.deadsoftware.cavedroid.game.render
+
+import com.badlogic.gdx.graphics.g2d.SpriteBatch
+import com.badlogic.gdx.graphics.glutils.ShapeRenderer
+import com.badlogic.gdx.math.Rectangle
+import ru.deadsoftware.cavedroid.game.GameInput
+import ru.deadsoftware.cavedroid.game.GameScope
+import ru.deadsoftware.cavedroid.misc.Assets
+import ru.deadsoftware.cavedroid.misc.ControlMode
+import ru.deadsoftware.cavedroid.misc.utils.ArrayMapExtensions.component1
+import ru.deadsoftware.cavedroid.misc.utils.ArrayMapExtensions.component2
+import javax.inject.Inject
+
+@GameScope
+class TouchControlsRenderer @Inject constructor(
+    private val gameInput: GameInput
+) : IGameRenderer {
+
+    override val renderLayer get() = RENDER_LAYER
+
+    private val shadeTexture get() = Assets.textureRegions[SHADE_KEY]
+
+    override fun draw(spriteBatch: SpriteBatch, shapeRenderer: ShapeRenderer, viewport: Rectangle, delta: Float) {
+        val touchControlsMap = Assets.guiMap
+
+        touchControlsMap.forEach { (key, value) ->
+            val touchKey = value.rect
+            spriteBatch.draw(
+                /* region = */ Assets.textureRegions[key],
+                /* x = */ touchKey.x,
+                /* y = */ touchKey.y,
+                /* width = */ touchKey.width,
+                /* height = */ touchKey.height
+            )
+        }
+
+        // FIXME: Add pressed state for buttons
+        if (gameInput.controlMode == ControlMode.CURSOR) {
+            val altKeyRect = touchControlsMap.get("alt").rect
+            spriteBatch.draw(shadeTexture, altKeyRect.x, altKeyRect.y, altKeyRect.width, altKeyRect.height)
+        }
+    }
+
+    companion object {
+        private const val RENDER_LAYER = 100700
+
+        private const val SHADE_KEY = "shade"
+    }
+
+}
\ No newline at end of file
diff --git a/core/src/ru/deadsoftware/cavedroid/game/render/WindowsRenderer.kt b/core/src/ru/deadsoftware/cavedroid/game/render/WindowsRenderer.kt
new file mode 100644 (file)
index 0000000..12fcd39
--- /dev/null
@@ -0,0 +1,34 @@
+package ru.deadsoftware.cavedroid.game.render
+
+import com.badlogic.gdx.Gdx
+import com.badlogic.gdx.graphics.g2d.SpriteBatch
+import com.badlogic.gdx.graphics.glutils.ShapeRenderer
+import com.badlogic.gdx.math.Rectangle
+import ru.deadsoftware.cavedroid.MainConfig
+import ru.deadsoftware.cavedroid.game.GameScope
+import ru.deadsoftware.cavedroid.game.GameUiWindow
+import ru.deadsoftware.cavedroid.game.render.windows.CreativeWindowRenderer
+import javax.inject.Inject
+
+@GameScope
+class WindowsRenderer @Inject constructor(
+    private val mainConfig: MainConfig,
+    private val creativeWindowRenderer: CreativeWindowRenderer,
+) : IGameRenderer {
+
+    override val renderLayer get() = RENDER_LAYER
+
+    override fun draw(spriteBatch: SpriteBatch, shapeRenderer: ShapeRenderer, viewport: Rectangle, delta: Float) {
+        when (mainConfig.gameUiWindow) {
+            GameUiWindow.CREATIVE_INVENTORY -> creativeWindowRenderer.draw(spriteBatch, shapeRenderer, viewport, delta)
+            GameUiWindow.NONE -> return
+            else -> Gdx.app.error(TAG, "Cannot draw window: ${mainConfig.gameUiWindow.name}")
+        }
+    }
+
+    companion object {
+        private const val TAG = "WindowsRenderer"
+
+        const val RENDER_LAYER = 100600
+    }
+}
\ No newline at end of file
diff --git a/core/src/ru/deadsoftware/cavedroid/game/render/windows/CreativeWindowRenderer.kt b/core/src/ru/deadsoftware/cavedroid/game/render/windows/CreativeWindowRenderer.kt
new file mode 100644 (file)
index 0000000..3480e27
--- /dev/null
@@ -0,0 +1,123 @@
+package ru.deadsoftware.cavedroid.game.render.windows
+
+import com.badlogic.gdx.graphics.g2d.SpriteBatch
+import com.badlogic.gdx.graphics.glutils.ShapeRenderer
+import com.badlogic.gdx.math.Rectangle
+import ru.deadsoftware.cavedroid.MainConfig
+import ru.deadsoftware.cavedroid.game.GameInput
+import ru.deadsoftware.cavedroid.game.GameItemsHolder
+import ru.deadsoftware.cavedroid.game.GameScope
+import ru.deadsoftware.cavedroid.game.mobs.MobsController
+import ru.deadsoftware.cavedroid.game.model.item.InventoryItem
+import ru.deadsoftware.cavedroid.game.render.IGameRenderer
+import ru.deadsoftware.cavedroid.game.render.WindowsRenderer
+import ru.deadsoftware.cavedroid.misc.Assets
+import javax.inject.Inject
+
+@GameScope
+class CreativeWindowRenderer @Inject constructor(
+    private val mainConfig: MainConfig,
+    private val gameInput: GameInput,
+    private val gameItemsHolder: GameItemsHolder,
+    private val mobsController: MobsController,
+) : IGameRenderer {
+
+    override val renderLayer get() = WindowsRenderer.RENDER_LAYER
+
+    private val creativeWindowTexture get() = requireNotNull(Assets.textureRegions[CREATIVE_WINDOW_KEY])
+    private val scrollIndicatorTexture get() = requireNotNull(Assets.textureRegions[SCROLL_INDICATOR_KEY])
+
+    private fun drawItemsGrid(spriteBatch: SpriteBatch, gridX: Float, gridY: Float) {
+        val allItems = gameItemsHolder.getAllItems()
+        val startIndex = gameInput.creativeScroll * CreativeWindowConfig.itemsInRow
+        val endIndex = startIndex + CreativeWindowConfig.itemsOnPage
+
+        for (i in startIndex ..< endIndex) {
+            if (i !in allItems.indices) {
+                break
+            }
+            val item = allItems.elementAt(i)
+
+            if (item.isNone()) {
+                continue
+            }
+
+            val gridIndex = i - startIndex
+
+            val itemX = gridX + (gridIndex % CreativeWindowConfig.itemsInRow) * CreativeWindowConfig.itemsGridColWidth
+            val itemY = gridY + (gridIndex / CreativeWindowConfig.itemsInRow) * CreativeWindowConfig.itemsGridRowHeight
+
+            spriteBatch.draw(item.sprite, itemX, itemY)
+        }
+    }
+
+    private fun drawPlayerInventory(spriteBatch: SpriteBatch, inventoryX: Float, inventoryY: Float) {
+        mobsController.player.inventory.asSequence()
+            .map(InventoryItem::item)
+            .forEachIndexed { index, item ->
+                if (item.isNone()) {
+                    return@forEachIndexed
+                }
+
+                val itemX = inventoryX + index * CreativeWindowConfig.itemsGridColWidth
+
+                spriteBatch.draw(item.sprite, itemX, inventoryY)
+            }
+    }
+
+    private fun drawCreative(spriteBatch: SpriteBatch, viewport: Rectangle) {
+        val creativeWindow = creativeWindowTexture
+
+        val windowX = viewport.width / 2 - creativeWindow.regionWidth / 2
+        val windowY = viewport.height / 2 - creativeWindow.regionHeight / 2
+        val oneScrollAmount = CreativeWindowConfig.scrollIndicatorFullHeight / gameItemsHolder.getCreativeScrollAmount()
+
+        spriteBatch.draw(creativeWindow, windowX, windowY)
+        spriteBatch.draw(
+            /* region = */ scrollIndicatorTexture,
+            /* x = */ windowX + CreativeWindowConfig.scrollIndicatorMarginLeft,
+            /* y = */ windowY + CreativeWindowConfig.scrollIndicatorMarginTop
+                    + (gameInput.creativeScroll * oneScrollAmount)
+        )
+
+        drawItemsGrid(
+            spriteBatch = spriteBatch,
+            gridX = windowX + CreativeWindowConfig.itemsGridMarginLeft,
+            gridY = windowY + CreativeWindowConfig.itemsGridMarginTop
+        )
+
+        drawPlayerInventory(
+            spriteBatch = spriteBatch,
+            inventoryX = windowX + CreativeWindowConfig.itemsGridMarginLeft,
+            inventoryY = windowY + creativeWindow.regionHeight - CreativeWindowConfig.playerInventoryOffsetFromBottom
+        )
+    }
+
+    override fun draw(spriteBatch: SpriteBatch, shapeRenderer: ShapeRenderer, viewport: Rectangle, delta: Float) {
+            drawCreative(spriteBatch, viewport)
+    }
+
+    companion object {
+        private const val CREATIVE_WINDOW_KEY = "creative"
+        private const val SCROLL_INDICATOR_KEY = "handle"
+
+        private data object CreativeWindowConfig {
+            const val scrollIndicatorMarginLeft = 156f
+            const val scrollIndicatorMarginTop = 18f
+            const val scrollIndicatorFullHeight = 72f
+
+            const val itemsGridMarginLeft = 8f
+            const val itemsGridMarginTop = 18f
+
+            const val itemsGridRowHeight = 18f
+            const val itemsGridColWidth = 18f
+
+            const val itemsInRow = 8
+            const val itemsInCol = 5
+
+            const val playerInventoryOffsetFromBottom = 24f
+
+            val itemsOnPage get() = itemsInCol * itemsInRow
+        }
+    }
+}
\ No newline at end of file
index 449c958da450fa3bb68901df527b0ebf473f7221..91793c845a23406745876c1fc0cad8ffb013a615 100644 (file)
@@ -7,6 +7,7 @@ import ru.deadsoftware.cavedroid.game.mobs.MobsController;
 import ru.deadsoftware.cavedroid.game.model.block.Block;
 import ru.deadsoftware.cavedroid.game.model.world.generator.WorldGeneratorConfig;
 import ru.deadsoftware.cavedroid.game.objects.DropController;
+import ru.deadsoftware.cavedroid.misc.utils.MeasureUnitsUtilsKt;
 
 import javax.annotation.CheckForNull;
 import javax.inject.Inject;
@@ -59,12 +60,20 @@ public class GameWorld {
         return mHeight;
     }
 
+    /**
+     * @deprecated for kotlin use {@link MeasureUnitsUtilsKt#getPx } extension val
+     */
+    @Deprecated
     public float getWidthPx() {
-        return mWidth * 16f;
+        return MeasureUnitsUtilsKt.getPx(mWidth);
     }
 
+    /**
+     * @deprecated for kotlin use {@link MeasureUnitsUtilsKt#getPx } extension val
+     */
+    @Deprecated
     public float getHeightPx() {
-        return mHeight * 16f;
+        return MeasureUnitsUtilsKt.getPx(mHeight);
     }
 
     public Block[][] getFullForeMap() {
index f0f4af907499745719466741018db1abbc53f12a..387407a841e482fc85dd50a7ff8ab7af1c0f280c 100644 (file)
@@ -26,6 +26,11 @@ class GameWorldBlocksLogicControllerTask @Inject constructor(
 
     private fun updateBlock(x: Int, y: Int) {
         val block = gameWorld.getForeMap(x, y)
+
+        if (block.isNone()) {
+            return
+        }
+
         val blockKey = block.params.key
         val action = updateBlockActions[blockKey]
             ?: updateBlockActions.getRequiresBlockAction().takeIf { block.params.requiresBlock }
index d4f7a46936df1d249caa5d7b20adef9af150765b..cf87a0859cd666f3adda10c11ab57ec10c247ea1 100644 (file)
@@ -12,8 +12,6 @@ import com.badlogic.gdx.utils.JsonValue;
 import ru.deadsoftware.cavedroid.game.objects.TouchButton;
 import ru.deadsoftware.cavedroid.misc.utils.AssetLoader;
 
-import java.io.File;
-import java.io.FilenameFilter;
 import java.util.HashMap;
 import java.util.Map;
 
@@ -25,7 +23,7 @@ public class Assets {
     public static final HashMap<String, TextureRegion> textureRegions = new HashMap<>();
     public static final ArrayMap<String, TouchButton> guiMap = new ArrayMap<>();
     private static final GlyphLayout glyphLayout = new GlyphLayout();
-    static BitmapFont minecraftFont;
+    public static BitmapFont minecraftFont;
 
     public static Map<String, Texture> blockTextures = new HashMap<>();
     public static Map<String, Texture> itemTextures = new HashMap<>();
index 290f7ce266539045df9cd352d76dac273719cfc2..c2577d36e4940a996468b3dbb382369b19eb000d 100644 (file)
@@ -5,12 +5,14 @@ import com.badlogic.gdx.InputProcessor;
 import com.badlogic.gdx.graphics.OrthographicCamera;
 import com.badlogic.gdx.graphics.g2d.SpriteBatch;
 import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
+import com.badlogic.gdx.math.Rectangle;
 
 public abstract class Renderer implements InputProcessor {
 
     protected final ShapeRenderer shaper;
     protected final SpriteBatch spriter;
     private final OrthographicCamera camera;
+    private final Rectangle mCameraViewport;
 
     protected Renderer() {
         this(Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
@@ -23,6 +25,9 @@ public abstract class Renderer implements InputProcessor {
         shaper.setProjectionMatrix(camera.combined);
         spriter = new SpriteBatch();
         spriter.setProjectionMatrix(camera.combined);
+
+        mCameraViewport =
+                new Rectangle(camera.position.x, camera.position.y, camera.viewportWidth, camera.viewportHeight);
     }
 
     public float getWidth() {
@@ -43,6 +48,12 @@ public abstract class Renderer implements InputProcessor {
 
     public void setCamPos(float x, float y) {
         camera.position.set(x, y, 0);
+        mCameraViewport.x = x;
+        mCameraViewport.y = y;
+    }
+
+    public Rectangle getCameraViewport() {
+        return mCameraViewport;
     }
 
     public void setFontScale(float scale) {
diff --git a/core/src/ru/deadsoftware/cavedroid/misc/utils/ArrayMapExtensions.kt b/core/src/ru/deadsoftware/cavedroid/misc/utils/ArrayMapExtensions.kt
new file mode 100644 (file)
index 0000000..70a5dc6
--- /dev/null
@@ -0,0 +1,9 @@
+package ru.deadsoftware.cavedroid.misc.utils
+
+import com.badlogic.gdx.utils.ObjectMap
+
+object ArrayMapExtensions {
+    operator fun <K, V> ObjectMap.Entry<K, V>.component1(): K = this.key
+
+    operator fun <K, V> ObjectMap.Entry<K, V>.component2(): V = this.value
+}
diff --git a/core/src/ru/deadsoftware/cavedroid/misc/utils/MeasureUnitsUtils.kt b/core/src/ru/deadsoftware/cavedroid/misc/utils/MeasureUnitsUtils.kt
new file mode 100644 (file)
index 0000000..ecf1ce0
--- /dev/null
@@ -0,0 +1,18 @@
+package ru.deadsoftware.cavedroid.misc.utils
+
+import com.badlogic.gdx.math.MathUtils
+
+/**
+ * Converts this value in BLOCKS into pixels
+ */
+val Float.px get() = this * 16f
+
+/**
+ * Converts this value in BLOCKS into pixels
+ */
+val Int.px get() = this * 16f
+
+/**
+ * Converts this value in PIXELS into blocks
+ */
+val Float.bl get() = MathUtils.floor(this / 16)
\ No newline at end of file
diff --git a/core/src/ru/deadsoftware/cavedroid/misc/utils/RenderingUtils.kt b/core/src/ru/deadsoftware/cavedroid/misc/utils/RenderingUtils.kt
new file mode 100644 (file)
index 0000000..28c3827
--- /dev/null
@@ -0,0 +1,42 @@
+package ru.deadsoftware.cavedroid.misc.utils
+
+import com.badlogic.gdx.math.Rectangle
+
+private fun Rectangle.shifted(shift: Float) = Rectangle(x + shift, y, width, height)
+
+private fun Rectangle.getLazyShifts(worldWidthPx: Float)
+    = Triple(
+        first = lazy { shifted(0f) },
+        second = lazy { shifted(-worldWidthPx) },
+        third = lazy { shifted(worldWidthPx) }
+    )
+
+fun Rectangle.cycledInsideWorld(
+    viewport: Rectangle,
+    worldWidthPx: Float,
+): Rectangle? {
+    val (notShifted, shiftedLeft, shiftedRight) = getLazyShifts(worldWidthPx)
+
+    return when {
+        viewport.overlaps(notShifted.value) -> notShifted.value
+        viewport.overlaps(shiftedLeft.value) -> shiftedLeft.value
+        viewport.overlaps(shiftedRight.value) -> shiftedRight.value
+        else -> null
+    }
+}
+
+fun forEachBlockInArea(
+    area: Rectangle,
+    func: (x: Int, y: Int) -> Unit
+) {
+    val startMapX = area.x.bl
+    val endMapX = startMapX + area.width.bl + 1
+    val startMapY = area.y.bl
+    val endMapY = startMapY + area.height.bl + 1
+
+    for (x in startMapX..endMapX) {
+        for (y in startMapY..endMapY) {
+            func(x, y)
+        }
+    }
+}
\ No newline at end of file
index 7d7aae110b22aa7a02b2c571fd1481906012cfd3..fd186e5a400e558a338895c01ebc45f20a34e97c 100644 (file)
@@ -7,7 +7,12 @@ import com.badlogic.gdx.graphics.g2d.SpriteBatch
  * Draw sprite at given position rotated by [rotation] degrees
  */
 @JvmOverloads
-fun SpriteBatch.drawSprite(sprite: Sprite, x: Float, y: Float, rotation: Float = 0f) {
+fun SpriteBatch.drawSprite(
+    sprite: Sprite,
+    x: Float,
+    y: Float,
+    rotation: Float = 0f
+) {
     sprite.rotation = rotation
     sprite.setPosition(x, y)
     sprite.draw(this)
index 94e48c3ebf7ff14595b2d925a2770d567a44479a..3436d706d7d6b68dda0a0bdfba095c7614331abe 100644 (file)
@@ -22,7 +22,7 @@ task runTouch(dependsOn: classes, type: JavaExec) {
     standardInput = System.in
     workingDir = project.assetsDir
     ignoreExitValue = true as JavaExecSpec
-    args "--touch --debug"
+    args "--touch", "--debug"
 }
 
 task debug(dependsOn: classes, type: JavaExec) {