"drop": "iron_block",
"texture": "iron_block"
},
- "stone_slab": {
+ "stone_slab_bottom": {
"id": 51,
"top": 8,
"sprite_top": 8,
"drop": "stone_slab",
"meta": "slab",
"texture": "stone_slab",
- "full_block": "double_stone_slab"
+ "full_block": "double_stone_slab",
+ "other_part": "stone_slab_top"
+ },
+ "stone_slab_top": {
+ "id": 51,
+ "hp": 600,
+ "bottom": 8,
+ "sprite_bottom": 8,
+ "transparent": true,
+ "drop": "stone_slab",
+ "meta": "slab",
+ "texture": "stone_slab",
+ "full_block": "double_stone_slab",
+ "other_part": "stone_slab_bottom"
},
"double_stone_slab": {
"id": 52,
"hp": 600,
"drop": "stone_slab",
+ "drop_count": 2,
"texture": "stone_slab"
},
- "sandstone_slab": {
+ "sandstone_slab_bottom": {
"id": 53,
"top": 8,
"sprite_top": 8,
"drop": "sandstone_slab",
"meta": "slab",
"texture": "sandstone",
- "full_block": "sandstone"
+ "full_block": "sandstone",
+ "other_part": "sandstone_slab_top"
},
- "oak_slab": {
+ "sandstone_slab_top": {
+ "id": 53,
+ "bottom": 8,
+ "sprite_bottom": 8,
+ "hp": 600,
+ "transparent": true,
+ "drop": "sandstone_slab",
+ "meta": "slab",
+ "texture": "sandstone",
+ "full_block": "sandstone",
+ "other_part": "sandstone_slab_bottom"
+ },
+ "oak_slab_bottom": {
"id": 54,
"top": 8,
"sprite_top": 8,
"drop": "oak_slab",
"meta": "slab",
"texture": "planks_oak",
- "full_block": "planks_oak"
+ "full_block": "planks_oak",
+ "other_part": "oak_slab_top"
},
- "cobblestone_slab": {
+ "oak_slab_top": {
+ "id": 54,
+ "bottom": 8,
+ "sprite_bottom": 8,
+ "hp": 180,
+ "transparent": true,
+ "drop": "oak_slab",
+ "meta": "slab",
+ "texture": "planks_oak",
+ "full_block": "planks_oak",
+ "other_part": "oak_slab_bottom"
+ },
+ "cobblestone_slab_bottom": {
"id": 55,
"top": 8,
+ "sprite_top": 8,
"hp": 600,
"transparent": true,
+ "meta": "slab",
"drop": "cobblestone_slab",
- "texture": "cobblestone"
+ "texture": "cobblestone",
+ "full_block": "cobblestone",
+ "other_part": "cobblestone_slab_top"
},
- "brick_slab": {
+ "cobblestone_slab_top": {
+ "id": 55,
+ "bottom": 8,
+ "sprite_bottom": 8,
+ "hp": 600,
+ "transparent": true,
+ "meta": "slab",
+ "drop": "cobblestone_slab",
+ "texture": "cobblestone",
+ "full_block": "cobblestone",
+ "other_part": "cobblestone_slab_bottom"
+ },
+ "brick_slab_bottom": {
"id": 56,
"top": 8,
"sprite_top": 8,
"drop": "brick_slab",
"meta": "slab",
"texture": "bricks",
- "full_block": "bricks"
+ "full_block": "bricks",
+ "other_part": "brick_slab_top"
+ },
+ "brick_slab_top": {
+ "id": 56,
+ "bottom": 8,
+ "sprite_bottom": 8,
+ "hp": 600,
+ "transparent": true,
+ "drop": "brick_slab",
+ "meta": "slab",
+ "texture": "bricks",
+ "full_block": "bricks",
+ "other_part": "brick_slab_bottom"
},
"stonebrick": {
"id": 57,
"drop": "stonebrick",
"texture": "stonebrick"
},
- "stonebrick_slab": {
+ "stonebrick_slab_bottom": {
"id": 58,
"top": 8,
"sprite_top": 8,
"drop": "stonebrick_slab",
"meta": "slab",
"texture": "stonebrick",
- "full_block": "stonebrick"
+ "full_block": "stonebrick",
+ "other_part": "stonebrick_slab_top"
+ },
+ "stonebrick_slab_top": {
+ "id": 58,
+ "bottom": 8,
+ "sprite_bottom": 8,
+ "hp": 450,
+ "transparent": true,
+ "drop": "stonebrick_slab",
+ "meta": "slab",
+ "texture": "stonebrick",
+ "full_block": "stonebrick",
+ "other_part": "brick_slab_bottom"
},
"cactus": {
"id": 59,
"stone_slab": {
"id": 47,
"name": "Stone Slab",
- "type": "block",
- "texture": "stone_slab"
+ "type": "slab",
+ "texture": "stone_slab",
+ "top_slab_block": "stone_slab_top",
+ "bottom_slab_block": "stone_slab_bottom"
},
"sandstone_slab": {
"id": 48,
"name": "Sandstone Slab",
- "type": "block",
- "texture": "sandstone_slab"
+ "type": "slab",
+ "texture": "sandstone_slab",
+ "top_slab_block": "sandstone_slab_top",
+ "bottom_slab_block": "sandstone_slab_bottom"
},
"oak_slab": {
"id": 49,
"name": "Oak Slab",
- "type": "block",
- "texture": "oak_slab"
+ "type": "slab",
+ "texture": "oak_slab",
+ "top_slab_block": "oak_slab_top",
+ "bottom_slab_block": "oak_slab_bottom"
},
"cobblestone_slab": {
"id": 50,
"name": "Cobblestone Slab",
- "type": "block",
- "texture": "cobblestone_slab"
+ "type": "slab",
+ "texture": "cobblestone_slab",
+ "top_slab_block": "cobblestone_slab_top",
+ "bottom_slab_block": "cobblestone_slab_bottom"
},
"brick_slab": {
"id": 51,
"name": "Brick Slab",
- "type": "block",
- "texture": "brick_slab"
+ "type": "slab",
+ "texture": "brick_slab",
+ "top_slab_block": "brick_slab_top",
+ "bottom_slab_block": "brick_slab_bottom"
},
"stonebrick": {
"id": 52,
"stonebrick_slab": {
"id": 53,
"name": "Stone Brick Slab",
- "type": "block",
- "texture": "stonebrick_slab"
+ "type": "slab",
+ "texture": "stonebrick_slab",
+ "top_slab_block": "stonebrick_slab_top",
+ "bottom_slab_block": "stonebrick_slab_bottom"
},
"cactus": {
"id": 54,
"h": 26
}
},
- "break": {
- "break_0": {
- "w": 16,
- "h": 16
- },
- "break_1": {
- "x": 16,
- "w": 16,
- "h": 16
- },
- "break_2": {
- "x": 32,
- "w": 16,
- "h": 16
- },
- "break_3": {
- "x": 48,
- "w": 16,
- "h": 16
- },
- "break_4": {
- "x": 64,
- "w": 16,
- "h": 16
- },
- "break_5": {
- "x": 80,
- "w": 16,
- "h": 16
- },
- "break_6": {
- "x": 96,
- "w": 16,
- "h": 16
- },
- "break_7": {
- "x": 112,
- "w": 16,
- "h": 16
- },
- "break_8": {
- "x": 128,
- "w": 16,
- "h": 16
- },
- "break_9": {
- "x": 144,
- "w": 16,
- "h": 16
- }
- },
"allitems": {
"creative": {
"w": 176,
}
dtoMap.forEach { (key, dto) ->
- itemsMap[key] = itemMapper.map(key, dto, blocksMap[key])
+ try {
+ itemsMap[key] = itemMapper.map(
+ key = key,
+ dto = dto,
+ block = blocksMap[key],
+ slabTopBlock = blocksMap[dto.topSlabBlock] as? Block.Slab,
+ slabBottomBlock = blocksMap[dto.bottomSlabBlock] as? Block.Slab
+ )
+ } catch (e: Exception) {
+ Gdx.app.error(TAG, "Failed to map item $key. Reason: ${e.message}")
+ e.printStackTrace()
+ }
}
fallbackItem = itemsMap[FALLBACK_ITEM_KEY]
import ru.deadsoftware.cavedroid.game.actions.placeblock.IPlaceBlockAction
import ru.deadsoftware.cavedroid.game.actions.placeblock.PlaceBlockItemToBackgroundAction
import ru.deadsoftware.cavedroid.game.actions.placeblock.PlaceBlockItemToForegroundAction
+import ru.deadsoftware.cavedroid.game.actions.placeblock.PlaceSlabAction
@Module
class PlaceBlockActionsModule {
return action
}
+ @Binds
+ @IntoMap
+ @StringKey(PlaceSlabAction.ACTION_KEY)
+ @GameScope
+ fun bindPlaceSlabAction(action: PlaceSlabAction): IPlaceBlockAction {
+ return action
+ }
+
}
\ No newline at end of file
@GameScope
class PlaceBlockItemToForegroundAction @Inject constructor(
private val gameWorld: GameWorld,
+ private val placeSlabAction: PlaceSlabAction,
) : IPlaceBlockAction {
override fun place(placeable: Item.Placeable, x: Int, y: Int) {
- gameWorld.placeToForeground(x, y, placeable.block)
+ if (placeable.isSlab()) {
+ placeSlabAction.place(placeable, x, y)
+ } else {
+ gameWorld.placeToForeground(x, y, placeable.block)
+ }
}
companion object {
--- /dev/null
+package ru.deadsoftware.cavedroid.game.actions.placeblock
+
+import com.badlogic.gdx.Gdx
+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.Item
+import ru.deadsoftware.cavedroid.game.world.GameWorld
+import javax.inject.Inject
+import kotlin.random.Random
+
+@GameScope
+class PlaceSlabAction @Inject constructor(
+ private val gameWorld: GameWorld,
+ private val mobsController: MobsController,
+ private val gameItemsHolder: GameItemsHolder,
+) : IPlaceBlockAction {
+
+ override fun place(placeable: Item.Placeable, x: Int, y: Int) {
+ if (placeable !is Item.Slab) {
+ Gdx.app.debug(TAG, "Place slab action called on ${placeable.params.key} which is not a slab")
+ return
+ }
+
+ val slabPart = if (Random.nextBoolean()) placeable.topPartBlock else placeable.bottomPartBlock
+ gameWorld.placeToForeground(x, y, slabPart)
+ }
+
+ companion object {
+ private const val TAG = "PlaceSlabAction"
+ const val ACTION_KEY = "place_slab"
+ }
+}
\ No newline at end of file
val width: Float get() = 16f - params.collisionMargins.left - params.collisionMargins.right
val height: Float get() = 16f - params.collisionMargins.top - params.collisionMargins.bottom
- private val spriteWidth: Float get() = 16f - params.spriteMargins.left - params.spriteMargins.right
- private val spriteHeight: Float get() = 16f - params.spriteMargins.top - params.spriteMargins.bottom
+ val spriteWidth: Float get() = 16f - params.spriteMargins.left - params.spriteMargins.right
+ val spriteHeight: Float get() = 16f - params.spriteMargins.top - params.spriteMargins.bottom
private var animation: Array<Sprite>? = null
data class Slab(
override val params: CommonBlockParams,
val fullBlockKey: String,
+ val otherPartBlockKey: String,
): Block()
sealed class Fluid: Block() {
@SerialName("drop_count") val dropCount: Int = 1,
@SerialName("full_block") val fullBlock: String? = null,
@SerialName("state") val state: Int? = null,
+ @SerialName("other_part") val otherPart: String? = null,
)
@SerialName("action_key") val actionKey: String? = null,
@SerialName("mob_damage_multiplier") val mobDamageMultiplier: Float = 1f,
@SerialName("block_damage_multiplier") val blockDamageMultiplier: Float = 1f,
+ @SerialName("top_slab_block") val topSlabBlock: String? = null,
+ @SerialName("bottom_slab_block") val bottomSlabBlock: String? = null,
)
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 ru.deadsoftware.cavedroid.game.model.block.Block as BlockModel
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.contract
return this is Placeable
}
+ fun isSlab(): Boolean {
+ contract { returns(true) implies (this@Item is Slab) }
+ return this is Slab
+ }
+
+
fun isTool(): Boolean {
contract { returns(true) implies (this@Item is Tool) }
return this is Tool
abstract val useActionKey: String
}
+ sealed class Placeable : Item() {
+ abstract val block: BlockModel
+ override val sprite: Sprite get() = block.sprite
+ }
+
data class None(
override val params: CommonItemParams,
): Item() {
get() = throw IllegalAccessException("Trying to get sprite of None")
}
- data class Placeable(
+ data class Block(
override val params: CommonItemParams,
- val block: Block
- ) : Item() {
- override val sprite: Sprite get() = block.sprite
+ override val block: BlockModel
+ ) : Placeable()
+
+ data class Slab(
+ override val params: CommonItemParams,
+ val topPartBlock: BlockModel.Slab,
+ val bottomPartBlock: BlockModel.Slab
+ ) : Placeable() {
+ override val block get() = bottomPartBlock
}
data class Sword(
return when (dto.meta) {
"water" -> Water(commonBlockParams, requireNotNull(dto.state))
"lava" -> Lava(commonBlockParams, requireNotNull(dto.state))
- "slab" -> Slab(commonBlockParams, requireNotNull(dto.fullBlock))
+ "slab" -> Slab(commonBlockParams, requireNotNull(dto.fullBlock), requireNotNull(dto.otherPart))
"none" -> None(commonBlockParams)
else -> Normal(commonBlockParams)
}
@Reusable
class ItemMapper @Inject constructor() {
- fun map(key: String, dto: ItemDto, block: Block?): Item {
+ fun map(key: String, dto: ItemDto, block: Block?, slabTopBlock: Block.Slab?, slabBottomBlock: Block.Slab?): Item {
val params = mapCommonParams(key, dto)
return when (dto.type) {
"bucket" -> Bucket(params, requireNotNull(loadSprite(dto)), requireNotNull(dto.actionKey))
"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))
+ "block" -> Block(params, requireNotNull(block))
+ "slab" -> Slab(params, requireNotNull(slabTopBlock), requireNotNull(slabBottomBlock))
"none" -> None(params)
else -> throw IllegalArgumentException("Unknown item type ${dto.type}")
}
package ru.deadsoftware.cavedroid.game.render
+import com.badlogic.gdx.graphics.g2d.Sprite
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.MathUtils
import com.badlogic.gdx.math.Rectangle
-import ru.deadsoftware.cavedroid.game.GameInput
import ru.deadsoftware.cavedroid.game.mobs.MobsController
import ru.deadsoftware.cavedroid.game.model.block.Block
import ru.deadsoftware.cavedroid.game.world.GameWorld
private val Block.canSeeThrough
get() = isNone() || params.isTransparent
- private fun blockDamageTexture(index: Int): TextureRegion? {
+ private fun blockDamageSprite(index: Int): Sprite? {
if (index !in 0..MAX_BLOCK_DAMAGE_INDEX) {
return null
}
- val textureKey = "$BLOCK_DAMAGE_TEXTURE_PREFIX$index"
- return Assets.textureRegions[textureKey]
+ return Assets.blockDamageSprites[index]
}
protected fun drawBlockDamage(spriteBatch: SpriteBatch, viewport: Rectangle) {
val index = (MAX_BLOCK_DAMAGE_INDEX.toFloat() * (blockDamage.toFloat() / block.params.hitPoints.toFloat()))
.let(MathUtils::floor)
- val texture = blockDamageTexture(index) ?: return
+ val sprite = blockDamageSprite(index) ?: return
if (gameWorld.hasForeAt(cursorX, cursorY) != background) {
- spriteBatch.draw(texture, cursorX.px - viewport.x, cursorY.px - viewport.y)
+ sprite.setBounds(
+ /* x = */ cursorX.px - viewport.x + block.params.spriteMargins.left,
+ /* y = */ cursorY.px - viewport.y + block.params.spriteMargins.top,
+ /* width = */ block.spriteWidth,
+ /* height = */ block.spriteHeight
+ )
+ sprite.draw(spriteBatch)
}
}
}
companion object {
- private const val BLOCK_DAMAGE_TEXTURE_PREFIX = "break_"
private const val MAX_BLOCK_DAMAGE_INDEX = 10
}
}
}
+ private boolean isSameSlab(Block slab1, Block slab2) {
+ if (!(slab1 instanceof Block.Slab) || !(slab2 instanceof Block.Slab)) {
+ return false;
+ }
+
+ return slab1.getParams().getKey().equals(((Block.Slab) slab2).getOtherPartBlockKey())
+ || slab1.getParams().getKey().equals(slab2.getParams().getKey());
+ }
+
public boolean hasForeAt(int x, int y) {
return getMap(x, y, 0) != mGameItemsHolder.getFallbackBlock();
}
public void placeToForeground(int x, int y, Block value) {
if (!hasForeAt(x, y) || value == mGameItemsHolder.getFallbackBlock() || !getForeMap(x, y).hasCollision()) {
setForeMap(x, y, value);
- } else if (value instanceof Block.Slab && getForeMap(x, y) == value) {
+ } else if (value instanceof Block.Slab && isSameSlab(value, getForeMap(x, y))) {
setForeMap(x, y, mGameItemsHolder.getBlock(((Block.Slab) value).getFullBlockKey()));
}
}
public class Assets {
+ private static final int BLOCK_DAMAGE_STAGES = 10;
+
public static final JsonReader jsonReader = new JsonReader();
private static final List<Texture> loadedTextures = new LinkedList<>();
public static final Sprite[][] playerSprite = new Sprite[2][4];
public static final Sprite[][] pigSprite = new Sprite[2][2];
+
+ public static final Sprite[] blockDamageSprites = new Sprite[10];
+
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();
}
}
+ private static void loadBlockDamage(AssetLoader assetLoader) {
+ final Texture blockDamageTexture = loadTexture(assetLoader.getAssetHandle("break.png"));
+ for (int i = 0; i < BLOCK_DAMAGE_STAGES; i++) {
+ blockDamageSprites[i] = new Sprite(flippedRegion(blockDamageTexture, i * 16, 0, 16, 16));
+ }
+ }
+
private static void setPlayerHeadOrigin() {
for (Sprite[] sprites : playerSprite) {
sprites[0].setOrigin(sprites[0].getWidth() / 2, sprites[0].getHeight());
public static void load(final AssetLoader assetLoader) {
loadMob(assetLoader, playerSprite, "char");
loadMob(assetLoader, pigSprite, "pig");
+ loadBlockDamage(assetLoader);
loadJSON(assetLoader);
loadBlocks(assetLoader);
loadItems(assetLoader);