From 73a1df40f700df460d5712022494f410d51a64ea Mon Sep 17 00:00:00 2001 From: Sivert Underdal Date: Wed, 16 Apr 2025 22:49:23 +0200 Subject: [PATCH 01/11] feature: show GameUIView over GameView --- .../chessevolved/presenters/GamePresenter.kt | 17 ++++++-- .../github/chessevolved/views/GameUIView.kt | 43 ++++--------------- 2 files changed, 21 insertions(+), 39 deletions(-) diff --git a/chessevolved_presenter/src/main/kotlin/io/github/chessevolved/presenters/GamePresenter.kt b/chessevolved_presenter/src/main/kotlin/io/github/chessevolved/presenters/GamePresenter.kt index b5f02e7c..9bd6c187 100644 --- a/chessevolved_presenter/src/main/kotlin/io/github/chessevolved/presenters/GamePresenter.kt +++ b/chessevolved_presenter/src/main/kotlin/io/github/chessevolved/presenters/GamePresenter.kt @@ -9,6 +9,7 @@ import com.badlogic.gdx.graphics.Texture import com.badlogic.gdx.graphics.g2d.SpriteBatch import com.badlogic.gdx.scenes.scene2d.Stage import com.badlogic.gdx.utils.viewport.FitViewport +import com.badlogic.gdx.utils.viewport.ScreenViewport import com.badlogic.gdx.utils.viewport.Viewport import io.github.chessevolved.Navigator import io.github.chessevolved.components.PieceType @@ -38,10 +39,13 @@ class GamePresenter( private val boardSquareFactory = BoardSquareFactory(engine, assetManager) private val gameCamera = OrthographicCamera() + private val gameUICamera = OrthographicCamera() private val boardWorldSize = 8 private val gameViewport: Viewport = FitViewport(boardWorldSize.toFloat(), boardWorldSize.toFloat(), gameCamera) + private val gameUIViewport: Viewport = + ScreenViewport() private lateinit var gameUIView: GameUIView private lateinit var gameBoardView: GameView private val gameBatch: SpriteBatch = SpriteBatch() @@ -96,7 +100,7 @@ class GamePresenter( } private fun setupGameView() { - gameUIView = GameUIView(gameViewport, gameCamera) + gameUIView = GameUIView(gameUIViewport) gameUIView.init() gameBoardView = GameView(gameUIView.getStage(), gameViewport) @@ -104,8 +108,10 @@ class GamePresenter( gameStage = gameBoardView.getStage() - gameCamera.position.set(boardWorldSize / 2f, boardWorldSize / 2f, 0f) + gameCamera.position.set(boardWorldSize / 2f, boardWorldSize / 2f, 1f) + gameUICamera.position.set(boardWorldSize / 2f, boardWorldSize / 2f, 0f) gameCamera.update() + gameUICamera.update() } private fun setupBoard() { @@ -220,13 +226,16 @@ class GamePresenter( Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT) gameViewport.apply() + gameCamera.update() gameBatch.projectionMatrix = gameCamera.combined gameBatch.begin() engine.update(Gdx.graphics.deltaTime) gameBatch.end() - gameBoardView.render() + // gameBoardView.render() + + gameUIViewport.apply() gameUIView.render() } @@ -236,7 +245,7 @@ class GamePresenter( ) { gameViewport.update(width, height, false) gameBoardView.resize(width, height) - // TODO: Do some resize logic for GameUIView as well. + gameUIView.resize(width, height) } override fun dispose() { diff --git a/chessevolved_view/src/main/kotlin/io/github/chessevolved/views/GameUIView.kt b/chessevolved_view/src/main/kotlin/io/github/chessevolved/views/GameUIView.kt index b94f8884..48eaf9a4 100644 --- a/chessevolved_view/src/main/kotlin/io/github/chessevolved/views/GameUIView.kt +++ b/chessevolved_view/src/main/kotlin/io/github/chessevolved/views/GameUIView.kt @@ -1,9 +1,7 @@ package io.github.chessevolved.views import com.badlogic.gdx.Gdx -import com.badlogic.gdx.graphics.OrthographicCamera import com.badlogic.gdx.scenes.scene2d.Stage -import com.badlogic.gdx.utils.viewport.FitViewport import com.badlogic.gdx.utils.viewport.Viewport import ktx.scene2d.label import ktx.scene2d.scene2d @@ -14,20 +12,11 @@ class GameUIView( * This variable can be used to make sure the ui doesn't overlap the game-board. */ private val gameViewport: Viewport, - private val gameCamera: OrthographicCamera, ) : IView { private lateinit var stage: Stage - private lateinit var viewport: Viewport override fun init() { - viewport = - FitViewport( - Gdx.graphics.width.toFloat(), - Gdx.graphics.height.toFloat(), - ) - - stage = - Stage() + stage = Stage(gameViewport) val root = scene2d.table { @@ -38,29 +27,7 @@ class GameUIView( setColor(1f, 0f, 0f, 0.5f) } row() - label("Settings 2") { it.colspan(2).padBottom(50f).center() } - row() - label("Settings 3") { it.colspan(2).padBottom(50f).center() } - row() - label("Settings 4") { - it.colspan(2).padBottom(50f).center() - } - row() - label("Settings 5") { it.colspan(2).padBottom(50f).center() } - row() - label("Settings 6") { it.colspan(2).padBottom(50f).center() } - row() - label("Settings") { it.colspan(2).padBottom(50f).center() } - row() - label("Settings") { it.colspan(2).padBottom(50f).center() } - row() - label("Settings") { it.colspan(2).padBottom(50f).center() } - row() - label("Settings") { it.colspan(2).padBottom(50f).center() } - row() - label("Settings") { it.colspan(2).padBottom(50f).center() } - row() - label("Settings") { it.colspan(2).padBottom(50f).center() } + label("Test 2") { it.colspan(2).padBottom(50f).center() } row() label("Fog of War:") { it.right() } @@ -68,6 +35,12 @@ class GameUIView( label("Board Size (8-16):") { it.right() } row() + label("Board Size (8-16):") { it.right() } + row() + label("Board Size (8-16):") { it.right() } + row() + label("Board Size (8-16):") { it.right() } + row() } stage.addActor(root) } From dd40fee77aa05202c623ca4263e7e0ee5ee88606 Mon Sep 17 00:00:00 2001 From: Sivert Underdal Date: Sat, 19 Apr 2025 01:30:01 +0200 Subject: [PATCH 02/11] feature: add styling to GameUIView and necessary functions for displaying abilityCardPrompt and abilityCardInventory --- .../chessevolved/presenters/GamePresenter.kt | 18 +- .../github/chessevolved/views/GameUIView.kt | 164 ++++++++++++++++-- .../io/github/chessevolved/views/GameView.kt | 2 +- 3 files changed, 162 insertions(+), 22 deletions(-) diff --git a/chessevolved_presenter/src/main/kotlin/io/github/chessevolved/presenters/GamePresenter.kt b/chessevolved_presenter/src/main/kotlin/io/github/chessevolved/presenters/GamePresenter.kt index 9bd6c187..d0248ad6 100644 --- a/chessevolved_presenter/src/main/kotlin/io/github/chessevolved/presenters/GamePresenter.kt +++ b/chessevolved_presenter/src/main/kotlin/io/github/chessevolved/presenters/GamePresenter.kt @@ -9,7 +9,6 @@ import com.badlogic.gdx.graphics.Texture import com.badlogic.gdx.graphics.g2d.SpriteBatch import com.badlogic.gdx.scenes.scene2d.Stage import com.badlogic.gdx.utils.viewport.FitViewport -import com.badlogic.gdx.utils.viewport.ScreenViewport import com.badlogic.gdx.utils.viewport.Viewport import io.github.chessevolved.Navigator import io.github.chessevolved.components.PieceType @@ -45,7 +44,7 @@ class GamePresenter( private val gameViewport: Viewport = FitViewport(boardWorldSize.toFloat(), boardWorldSize.toFloat(), gameCamera) private val gameUIViewport: Viewport = - ScreenViewport() + FitViewport(Gdx.graphics.width.toFloat(), Gdx.graphics.height.toFloat(), gameUICamera) private lateinit var gameUIView: GameUIView private lateinit var gameBoardView: GameView private val gameBatch: SpriteBatch = SpriteBatch() @@ -83,6 +82,10 @@ class GamePresenter( // If we do not call this the board will not be displayed resize(Gdx.graphics.width, Gdx.graphics.height) + + // TODO: Remove after testing ability card UI + val copyIconTexture = Texture(Gdx.files.internal("icons/copy-icon.png")) + gameUIView.addAbilityCardToInventory(copyIconTexture, ::onAbilityCardUsed, 1) } private fun loadRequiredAssets() { @@ -100,7 +103,8 @@ class GamePresenter( } private fun setupGameView() { - gameUIView = GameUIView(gameUIViewport) + // TODO: Pass in if the player is the white player or not. + gameUIView = GameUIView(gameUIViewport, true) gameUIView.init() gameBoardView = GameView(gameUIView.getStage(), gameViewport) @@ -243,7 +247,8 @@ class GamePresenter( width: Int, height: Int, ) { - gameViewport.update(width, height, false) + gameViewport.update(width, height, true) + gameUIViewport.update(width, height, false) gameBoardView.resize(width, height) gameUIView.resize(width, height) } @@ -276,6 +281,11 @@ class GamePresenter( } } + fun onAbilityCardUsed(abilityId: Int) { + // TODO: Fetch which ability to use. + println("Abilitycard used!") + } + override fun setInputProcessor() { gameBoardView.setInputProcessor() } diff --git a/chessevolved_view/src/main/kotlin/io/github/chessevolved/views/GameUIView.kt b/chessevolved_view/src/main/kotlin/io/github/chessevolved/views/GameUIView.kt index 48eaf9a4..873398f5 100644 --- a/chessevolved_view/src/main/kotlin/io/github/chessevolved/views/GameUIView.kt +++ b/chessevolved_view/src/main/kotlin/io/github/chessevolved/views/GameUIView.kt @@ -1,50 +1,180 @@ package io.github.chessevolved.views import com.badlogic.gdx.Gdx +import com.badlogic.gdx.graphics.Color +import com.badlogic.gdx.graphics.Texture +import com.badlogic.gdx.graphics.g2d.TextureRegion import com.badlogic.gdx.scenes.scene2d.Stage +import com.badlogic.gdx.scenes.scene2d.ui.ImageButton +import com.badlogic.gdx.scenes.scene2d.ui.Table +import com.badlogic.gdx.scenes.scene2d.ui.TextField +import com.badlogic.gdx.scenes.scene2d.utils.TextureRegionDrawable import com.badlogic.gdx.utils.viewport.Viewport +import ktx.actors.onClick +import ktx.scene2d.image +import ktx.scene2d.imageButton import ktx.scene2d.label import ktx.scene2d.scene2d import ktx.scene2d.table +import ktx.scene2d.textField +import kotlin.math.min class GameUIView( /** * This variable can be used to make sure the ui doesn't overlap the game-board. */ private val gameViewport: Viewport, + private val isWhitePlayer: Boolean, ) : IView { private lateinit var stage: Stage + private val blackColor = Color(0.37f, 0.5f, 0.6f, 0.8f) + private val whiteColor = Color(1f, 1f, 1f, 0.8f) + private var amountOfAbilityCards = 0 + private var sizeOfAbilityCards = min(100, (Gdx.graphics.width - 10) / (amountOfAbilityCards + 1)) + private val abilityCards = HashMap() + private lateinit var abilityCardInventory: Table + private lateinit var abilityPickerWindow: Table + private lateinit var blackTimer: TextField + private lateinit var whiteTimer: TextField override fun init() { stage = Stage(gameViewport) - val root = + val blackInfoBox = scene2d.table { - setFillParent(true) - defaults().pad(10f) - label("Settings 1") { - it.colspan(2).padBottom(50f).center() - setColor(1f, 0f, 0f, 0.5f) - } - row() - label("Test 2") { it.colspan(2).padBottom(50f).center() } - row() + textField(if (isWhitePlayer) "Black: Opponent" else "Black: You") { + color = blackColor + isDisabled = true + }.cell(growX = true) - label("Fog of War:") { it.right() } - row() + blackTimer = + textField("Time: 10:00") { + color = blackColor + isDisabled = true + } + } - label("Board Size (8-16):") { it.right() } - row() - label("Board Size (8-16):") { it.right() } + abilityPickerWindow = + scene2d.table { + defaults().width(100f).height(100f).pad(4f) + imageButton() + imageButton() + imageButton() + } + + abilityCardInventory = + scene2d.table { + // This label is here to always keep the height of the abilityCardInventory the same. + label("").cell(expandY = true, height = 100f, width = 0f) + defaults().width(sizeOfAbilityCards.toFloat()).height(sizeOfAbilityCards.toFloat()) + } + + val whiteInfoBox = + scene2d.table { + textField(if (isWhitePlayer) "White: You" else "White: Opponent") { + color = whiteColor + isDisabled = true + }.cell(growX = true) + + whiteTimer = + textField("Time: 10:00") { + color = whiteColor + isDisabled = true + } + } + + val root = + scene2d.table { + // setDebug(true, true) + setFillParent(true) + add(blackInfoBox).growX().expandY().top() row() - label("Board Size (8-16):") { it.right() } + add(abilityPickerWindow.bottom().padBottom(150f)).expandY().height(500f) row() - label("Board Size (8-16):") { it.right() } + add(abilityCardInventory) + .growX() + .growY() + .expandY() + .bottom() row() + add(whiteInfoBox).growX() } + stage.addActor(root) } + data class ThreeAbilities( + val ability1Texture: Texture, + val ability2Texture: Texture, + val ability3Texture: Texture, + val onAbilityPickedListener: (abilityPicked: Int) -> Unit, + ) + + fun promptPickAbility(abilities: ThreeAbilities) { + abilityPickerWindow.reset() + abilityPickerWindow.isVisible = true + abilityPickerWindow.add( + scene2d.imageButton { onClick { onAbilityPicked(abilities.onAbilityPickedListener, 1) } }, + ) + abilityPickerWindow.add( + scene2d.imageButton { onClick { onAbilityPicked(abilities.onAbilityPickedListener, 2) } }, + ) + abilityPickerWindow.add( + scene2d.imageButton { onClick { onAbilityPicked(abilities.onAbilityPickedListener, 3) } }, + ) + } + + private fun onAbilityPicked( + onAbilityPickedListener: (abilityPicked: Int) -> Unit, + abilityPicked: Int, + ) { + abilityPickerWindow.isVisible = false + onAbilityPickedListener.invoke(abilityPicked) + } + + /** + * @param abilityId corresponds to the id of the ability, not the ability-type. abilityId will be used + * to remove the ability from the inventory as well. + */ + fun addAbilityCardToInventory( + abilityTexture: Texture, + onAbilityUsed: (abilityId: Int) -> Unit, + abilityId: Int, + ) { + amountOfAbilityCards++ + sizeOfAbilityCards = min(100, (Gdx.graphics.width - 10) / amountOfAbilityCards + 1) + + var abilityCard = + scene2d.imageButton { + image(TextureRegionDrawable(TextureRegion(abilityTexture))) + onClick { + onAbilityUsed(abilityId) + } + } + abilityCardInventory.add(abilityCard) + + abilityCards[abilityId] = abilityCard + + abilityCardInventory.cells.forEach { + if (it.expandY != 1) { + it.width(sizeOfAbilityCards.toFloat()).height(sizeOfAbilityCards.toFloat()) + } + } + // abilityCardInventory.defaults().width(sizeOfAbilityCards.toFloat()).height(sizeOfAbilityCards.toFloat()) + } + + fun removeAbilityCardFromInventory(abilityId: Int) { + abilityCards[abilityId]?.remove() + } + + fun updateWhiteTimer(time: Int) { + whiteTimer.text = "Time: $time" + } + + fun updateBlackTimer(time: Int) { + blackTimer.text = "Time: $time" + } + override fun render() { stage.act(Gdx.graphics.deltaTime) stage.draw() diff --git a/chessevolved_view/src/main/kotlin/io/github/chessevolved/views/GameView.kt b/chessevolved_view/src/main/kotlin/io/github/chessevolved/views/GameView.kt index 6da1d056..f54450eb 100644 --- a/chessevolved_view/src/main/kotlin/io/github/chessevolved/views/GameView.kt +++ b/chessevolved_view/src/main/kotlin/io/github/chessevolved/views/GameView.kt @@ -15,8 +15,8 @@ class GameView( override fun init() { gameStage = Stage(gameViewport) - inputMultiplexer.addProcessor(gameStage) inputMultiplexer.addProcessor(uiStage) + inputMultiplexer.addProcessor(gameStage) } fun getStage(): Stage = gameStage From 1ecba180306a026561056a824cc5ed7fb5d86669 Mon Sep 17 00:00:00 2001 From: Sivert Underdal Date: Sat, 19 Apr 2025 01:31:27 +0200 Subject: [PATCH 03/11] feature: add AbilityItemFactory and add method to InputSystem for handling clicking on AbilityCards --- .../components/AbilityCardComponent.kt | 17 +++++++++ .../entities/AbilityItemFactory.kt | 35 +++++++++++++++++ .../chessevolved/systems/InputSystem.kt | 38 +++++++++++++++---- 3 files changed, 82 insertions(+), 8 deletions(-) create mode 100644 chessevolved_model/src/main/kotlin/io/github/chessevolved/components/AbilityCardComponent.kt create mode 100644 chessevolved_model/src/main/kotlin/io/github/chessevolved/entities/AbilityItemFactory.kt diff --git a/chessevolved_model/src/main/kotlin/io/github/chessevolved/components/AbilityCardComponent.kt b/chessevolved_model/src/main/kotlin/io/github/chessevolved/components/AbilityCardComponent.kt new file mode 100644 index 00000000..f4fe6457 --- /dev/null +++ b/chessevolved_model/src/main/kotlin/io/github/chessevolved/components/AbilityCardComponent.kt @@ -0,0 +1,17 @@ +package io.github.chessevolved.components +import com.badlogic.ashley.core.Component +import com.badlogic.ashley.core.ComponentMapper + +private object CurrentCardIdCounter { + var count = 0 +} + +class AbilityCardComponent : Component { + companion object { + val mapper: ComponentMapper = + ComponentMapper.getFor(AbilityCardComponent::class.java) + } + + val id = CurrentCardIdCounter.count++ + var selected = false +} diff --git a/chessevolved_model/src/main/kotlin/io/github/chessevolved/entities/AbilityItemFactory.kt b/chessevolved_model/src/main/kotlin/io/github/chessevolved/entities/AbilityItemFactory.kt new file mode 100644 index 00000000..800cd13d --- /dev/null +++ b/chessevolved_model/src/main/kotlin/io/github/chessevolved/entities/AbilityItemFactory.kt @@ -0,0 +1,35 @@ +package io.github.chessevolved.entities + +import com.badlogic.ashley.core.Engine +import com.badlogic.ashley.core.Entity +import com.badlogic.gdx.assets.AssetManager +import com.badlogic.gdx.graphics.Color +import com.badlogic.gdx.graphics.Texture +import com.badlogic.gdx.graphics.g2d.TextureRegion +import io.github.chessevolved.components.AbilityComponent +import io.github.chessevolved.components.AbilityType +import io.github.chessevolved.components.HighlightComponent +import io.github.chessevolved.components.TextureRegionComponent + +class AbilityItemFactory( + private val engine: Engine, + private val assetManager: AssetManager, +) { + fun getAbilityItemTexture(abilityType: AbilityType): TextureRegion = + when (abilityType) { + AbilityType.SHIELD -> TextureRegion(assetManager.get("icons/copy-icon.png", Texture::class.java)) + AbilityType.EXPLOSION -> TextureRegion(assetManager.get("icons/copy-icon.png", Texture::class.java)) + AbilityType.SWAP -> TextureRegion(assetManager.get("icons/copy-icon.png", Texture::class.java)) + AbilityType.MIRROR -> TextureRegion(assetManager.get("icons/copy-icon.png", Texture::class.java)) + AbilityType.NEW_MOVEMENT -> TextureRegion(assetManager.get("icons/copy-icon.png", Texture::class.java)) + } + + fun createAbilityItem(abilityType: AbilityType): Entity = + Entity().apply { + add(AbilityComponent(listOf(abilityType))) + add(TextureRegionComponent(getAbilityItemTexture(abilityType))) + add(HighlightComponent(Color.BLACK)) + + engine.addEntity(this) + } +} diff --git a/chessevolved_model/src/main/kotlin/io/github/chessevolved/systems/InputSystem.kt b/chessevolved_model/src/main/kotlin/io/github/chessevolved/systems/InputSystem.kt index d5553182..54e333bf 100644 --- a/chessevolved_model/src/main/kotlin/io/github/chessevolved/systems/InputSystem.kt +++ b/chessevolved_model/src/main/kotlin/io/github/chessevolved/systems/InputSystem.kt @@ -3,6 +3,7 @@ package io.github.chessevolved.systems import com.badlogic.ashley.core.Entity import com.badlogic.ashley.core.Family import com.badlogic.ashley.systems.IteratingSystem +import io.github.chessevolved.components.AbilityCardComponent import io.github.chessevolved.components.CanBeCapturedComponent import io.github.chessevolved.components.CapturedComponent import io.github.chessevolved.components.ClickEventComponent @@ -14,9 +15,10 @@ import io.github.chessevolved.components.SelectionComponent import io.github.chessevolved.components.WeatherEventComponent import io.github.chessevolved.singletons.ECSEngine -class InputSystem : IteratingSystem( - Family.all(ClickEventComponent::class.java).get(), -) { +class InputSystem : + IteratingSystem( + Family.all(ClickEventComponent::class.java).get(), + ) { override fun processEntity( entity: Entity?, deltaTime: Float, @@ -25,6 +27,8 @@ class InputSystem : IteratingSystem( handlePieceClicked(entity) } else if (entity?.getComponent(WeatherEventComponent::class.java) != null) { handleBoardSquareClicked(entity) + } else if (entity?.getComponent(AbilityCardComponent::class.java) != null) { + handleAbilityCardClicked(entity) } entity?.remove(ClickEventComponent::class.java) @@ -48,15 +52,23 @@ class InputSystem : IteratingSystem( private fun handleBoardSquareClicked(boardSquare: Entity) { val position = PositionComponent.mapper.get(boardSquare).position - ECSEngine.getEntitiesFor(Family.all(PieceTypeComponent::class.java, SelectionComponent::class.java).get()) - .firstOrNull()?.add(MovementIntentComponent(position)) + ECSEngine + .getEntitiesFor(Family.all(PieceTypeComponent::class.java, SelectionComponent::class.java).get()) + .firstOrNull() + ?.add(MovementIntentComponent(position)) + } + + private fun handleAbilityCardClicked(abilityCard: Entity) { + println("Ability card clicked") + // TODO: Give selected component, de-select all other ability-cards, set highlightComponent to white (selected color) } } -class InputService() { +class InputService { fun clickPieceAtPosition(position: Position) { val entity = - ECSEngine.getEntitiesFor(Family.all(PieceTypeComponent::class.java).get()) + ECSEngine + .getEntitiesFor(Family.all(PieceTypeComponent::class.java).get()) .find { PositionComponent.mapper.get(it).position == position } entity?.add(ClickEventComponent()) @@ -64,9 +76,19 @@ class InputService() { fun clickBoardSquareAtPosition(position: Position) { val entity = - ECSEngine.getEntitiesFor(Family.all(WeatherEventComponent::class.java).get()) + ECSEngine + .getEntitiesFor(Family.all(WeatherEventComponent::class.java).get()) .find { PositionComponent.mapper.get(it).position == position } entity?.add(ClickEventComponent()) } + + fun clickAbilityCardWithId(abilityCardId: Int) { + val entity = + ECSEngine + .getEntitiesFor(Family.all(AbilityCardComponent::class.java).get()) + .find { AbilityCardComponent.mapper.get(it).id == abilityCardId } + + entity?.add(ClickEventComponent()) + } } From 925360d6fcce28b71f19d7f77a7db32f06cb0fea Mon Sep 17 00:00:00 2001 From: Sivert Underdal Date: Sun, 20 Apr 2025 03:28:10 +0200 Subject: [PATCH 04/11] feature: create system for abilityCards in inventory within ECS and create helper-functions in view + translation in presenter --- assets/abilities/explosion-card-temp.png | Bin 0 -> 1395 bytes assets/abilities/explosion-card.png | Bin 0 -> 1674 bytes assets/abilities/mirror-card.png | Bin 0 -> 6779 bytes assets/abilities/new_movement-card.png | Bin 0 -> 6779 bytes assets/abilities/shield-card.png | Bin 0 -> 6779 bytes assets/abilities/swap-card.png | Bin 0 -> 6779 bytes .../components/AbilityCardComponent.kt | 2 +- .../components/AbilityComponent.kt | 22 ++- .../entities/AbilityItemFactory.kt | 18 +- .../chessevolved/systems/InputSystem.kt | 48 +++++- .../systems/SelectionEntityListener.kt | 7 +- .../chessevolved/presenters/GamePresenter.kt | 96 ++++++++++- .../github/chessevolved/views/GameUIView.kt | 160 +++++++++++++----- 13 files changed, 277 insertions(+), 76 deletions(-) create mode 100644 assets/abilities/explosion-card-temp.png create mode 100644 assets/abilities/explosion-card.png create mode 100644 assets/abilities/mirror-card.png create mode 100644 assets/abilities/new_movement-card.png create mode 100644 assets/abilities/shield-card.png create mode 100644 assets/abilities/swap-card.png diff --git a/assets/abilities/explosion-card-temp.png b/assets/abilities/explosion-card-temp.png new file mode 100644 index 0000000000000000000000000000000000000000..b1befe02db5ab986467fd6bd0020e6f2470f7e23 GIT binary patch literal 1395 zcmV-(1&sQMP)Px)6G=otRCt{2TTf^lRT%$m$)P7NQVO$Pqz#d}3n{p4C`iGp$)zepSPxT>=HQNY za|;N9u;69W&Rzst=3ozs?J2ECiwH}W2&wG0bR|#}GoT{krHGgHF#ElE^X9$zx0yei z%@0D}yqP!e`+nb__svUQ3BVY0j0FJr{LWW-%aIE<8tXX4Us&DkJmtv*R(Bg4jdgat zK#h96j()!ny$QVf%2F=ErH#i=N9gzasMqVPj}f3ZuS0LT55NA}#Y}}u8$bNG55OPp z3;cy>`ENdcFJ-Z^!8@OR3Bc9w@zW8zw;(<&TK>{owKQc*9ld*d09?Ju69_&6fa7C% z0_j7Zz(Yy^F#DD#kRId-B2Gzr=LjbRfi0jr?Eb-QjhBOdzox72>mD zVPx*DoI2^RCt{2olj^SRUF5^D?NDfB2w6Tks1`;1q+4^1rfZOT>JwO*24;74(@7` zTR{+nfS0u0TokQ)um{EJCF;>4LdXyyO19EUK@?r6MZ`-HFYz$@W`6I@dpmD-XY$*f z{e3Q(c{}^&<@e|N{>)3J0^kS&8~1QGVBR^Q4&J-+31>UzlC|b)>;P2&0F6cit@b&V zMwOM`_5}by5CmGViv%&FWk93R01$eq)eg%ZfAo=KZ&ca1e&Z&BAOH~CSfM#U$&NC< z;;ARk6xh7-^4<3b(Xp$K6$xVIgsspeJDGM68Lx?0**Wz@M?3%Hhwic6TQY!|xy}F;`R&aueDTryiSpyf z1jztq!~x9AwFD`N;p~Lx+5x8F&2s4j<^XX3Gjk1=9Ud1XT>9Z>DrM>o@mNDwpZ8BpzYFlddJ&dx}$0hL$7 zLm%1s@=8xTUv9Pl=u&jBw~JE`$6E~Znk(ey0IM--0Q$!HkilZuaBBR_(ou7i*Rxf_ ziAWGLnKD3&&>Esh8{FG<%19OAcT~h%>r|0ki?6Hh8`+?u`Vcb};b~-YfR9#FCkHnkrJ_21Nc8@6kfo2_Vq1|aGWCLNlJ1TmBQ zC`jv6_d9WCJ3@lgcQlD3QLkkQWeh1&BOHor6m%REIt+CgUAu-j%-N4P97FF>{daaI zUlfrbX4ofUK8(tYYCrD4XOWq3fQyMHaZ`dw5HqE5z_{y4+|0BDnb3(4-E^gR=rMiV zsut#4!%jN!O5E$#&BQ&&p)?sVtCT7O6tqL$KEECvo68d_BV5mfO;pt9$<#YQ8PjW$ zrkJ52sYnnrQgiX4I&id?EUqlACkr!B#I}^FaX{KI()M|^*U{I8UK;)+ z&kRN_QM-0UD9pZ~h+PpPLCj1|28@H56|=Y;G1Cq(nZ>msksxMr&wxqx`Q%B|hD-KI z*|d;*2B^n}vmyc9hy*cHNGD7uPesbnsQPWIp(a`90k-<5W^1CATKZbfB+%UwuKK&T z*Q42T=T7X5G~)xwy`j&&@>+DBSAQZB#Ee%4WQk%b3xmaOv^6nnJ-$S8w;wM{(E2+k zw}3n0arc>ZbiN6*zzPlwwRyI7A`SZl!VNJf6;lIyXQ;Rbab!KqR(FaIuVFYx;nXG&pKbTj}KppzFcvb%!?s^8kjT|DQAZGj|NJVbn z9;IH-YsnvU0LZ+v*>+zV(OT5^p+6}bmYb2g@>dx3=I-bfgylQ^Ui^^9r@f|q?o0o_ z+kOtHlb>|{;vpSQ*|F8{;tr#n^ykm%tB(_rLmNjiR{IxDr(Pl%z>GM68F2tJ z;s9pE0nCU4nDH8hUfsMH9lvn?&B7W}2Ht%(edtM%3}7ZT1L$k`KB**(X05pj0BAHCXtmEpmk}a}kiTjG000hUSV?A0O#mtY000O800000 U007cclK=n!07*qoM6N<$f=a3qg8%>k literal 0 HcmV?d00001 diff --git a/assets/abilities/mirror-card.png b/assets/abilities/mirror-card.png new file mode 100644 index 0000000000000000000000000000000000000000..6379f6eb5cf7bc3429d16f72902460aea7acd63e GIT binary patch literal 6779 zcmeHLXEdDO)<0t~I*ArUiRc6oB1#yYgfTjQqC}MFy+oNwM2Zsrh%Q=)E<*GgMDIjz zi8}fqqRqVAyVkv*-uv->c-Q-^^{jo)K6{_@?ETxn^E~Hyi$thVk};D304O!om309i zxM&gp66l3!&E@L65KvD=4Sf<4lIcb5g^Mn!o4Sc70A!q(H>5;g_{N2j$xFr9OV8ET z%h$@o2Kf5=3O{vm^t85evk`XnuuIvNWxkko1F5O6g2&@8?EnAz{|x+}&wvuyjmnE4 z|9I&l^ZqPso5h7ORLA$ zPi$=M?4LR~Iyt+zy19FJdi(hL`9BK?3__uwzX*95`s($Yw_)KCk?;PFiH&<7pOBc8 zoRXUMAw45AEBj+k?x(!`g2JNWlG4v*U&<@KR#sKl)YjEEG&VK2d~0oM|K8Er)zjP8 zKQK5nJTf}=V|-$AYInLe;z`6)#DD?vZix=~}J3aJaz z5Sd`G#64(L;wyY|LT4bxp36g&^Im=QqgHWmy3`NKW%?Y)J*JiUdiroO&k_IN`}^KM zUS@V~@iu;bcYHrwXT|5vIYl|gyjrGf+EQ_ESjCgfA64p4tf1vw39!r=Np5TTa^A5Z zon02O!snB5tyP&z<2*i6-d!>HuR zTRuP1C88AeO1aWaQXU>)+P*cs6MSO6vU*uKYd$E{m^g!_5RnV zcgIj!*%)?yj9q87@=c~c?xz;yvQFCPzYyIZ1_A;2^7YKJCX$&9~jpAGQWkqfaarwBXgKjdULORgvds)adWK=5Np6j~PMK zVkp6yTD@!U;Vrb7)%kcVE3ju;WijPFuE3Rj^Lot_$KyasFR{zmO{zkk(YC9?Q2uJoXL zQI6_YQ|MBR@*S@?n*{KOe)Bl;dB`>HPU$eV>=Vche1(0h2q)Jz009*O(sw`c>2 zEt8rCfD1s1xrWC@u^%v^DPNHO_4EP;0oTk?Orv2CnNZ~w(6`dauw3Z9tg`cRpzJEZ zV~zbIqQG@V&B1Fjke7Jso!91jL(5>uI|JfFknvJ}KNw?&gA8&dW+*>|AlbKl{O2W{ zn4eLw$k-rozqI8~zddXU+VVB^*__$m9->B!r@Gp^-&Y8R!Ik_`EVrz+lF-$q!Kn3j z9F@2VMxgcUd?p7aYIShF;_O19#0J-G!2vb_D4fZme>- zoGvOz2y#zSprzeQW1L2OA05Li^efk@#y}pUD<%K$tXylveDi3#3lM*Ens8A4?q=iB zvLP`j0E6=GlE|c-SwB~cT)z(LX~A*L27SvCQlU!vY*RZg9RecUES+wRnE52Jzo>O}Dm8aALXa^foJy>vOG+l${#N?!B0M1`;j%T&<}q=y|Yy2Nian z2~-ntobqo|%`xk;)|b?Onr=8{MISh_LH!)n%erM`T;7CV*uX?}YIEFYI#z%COw?lW zEr5drNEP5lPJ&>?g7RM}!3)U3QXelar*c*LNub5k$AE?c@EW;i;hLQbK0{v}wO_=c z0O9i;p4h6F_-H&m8XYCPcb^!vK;V;{!l{vqHTz4iN5=so5cn?ex#zSiUsJrbO;?+#($ESx{D2nKI4*pm*LnMJ zuvidJ2n9soI*yjZSShTG`-Tie9D=?I-We%sEuwz+lMi*?6$40u=3{C5oAQfa$EBsX zpe(D^aaz1SMb8S>I%=fuls8HEjDCil@E&0p*cW}lRo5=rrRt#_w`s@g%rD$kWa
5ec!X?SgpzpNBZY{ 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
5ec!X?SgpzpNBZY{ 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
5ec!X?SgpzpNBZY{ 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
5ec!X?SgpzpNBZY{ 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 aICgHbY%*ai^{dM_r-lkbxkS, -) : Component +) : Component { + companion object { + val mapper: ComponentMapper = + ComponentMapper.getFor(AbilityComponent::class.java) + } +} diff --git a/chessevolved_model/src/main/kotlin/io/github/chessevolved/entities/AbilityItemFactory.kt b/chessevolved_model/src/main/kotlin/io/github/chessevolved/entities/AbilityItemFactory.kt index 800cd13d..6b55b608 100644 --- a/chessevolved_model/src/main/kotlin/io/github/chessevolved/entities/AbilityItemFactory.kt +++ b/chessevolved_model/src/main/kotlin/io/github/chessevolved/entities/AbilityItemFactory.kt @@ -3,33 +3,31 @@ package io.github.chessevolved.entities import com.badlogic.ashley.core.Engine import com.badlogic.ashley.core.Entity import com.badlogic.gdx.assets.AssetManager -import com.badlogic.gdx.graphics.Color import com.badlogic.gdx.graphics.Texture import com.badlogic.gdx.graphics.g2d.TextureRegion +import io.github.chessevolved.components.AbilityCardComponent import io.github.chessevolved.components.AbilityComponent import io.github.chessevolved.components.AbilityType -import io.github.chessevolved.components.HighlightComponent import io.github.chessevolved.components.TextureRegionComponent class AbilityItemFactory( private val engine: Engine, private val assetManager: AssetManager, ) { - fun getAbilityItemTexture(abilityType: AbilityType): TextureRegion = + private fun getAbilityItemTexture(abilityType: AbilityType): TextureRegion = when (abilityType) { - AbilityType.SHIELD -> TextureRegion(assetManager.get("icons/copy-icon.png", Texture::class.java)) - AbilityType.EXPLOSION -> TextureRegion(assetManager.get("icons/copy-icon.png", Texture::class.java)) - AbilityType.SWAP -> TextureRegion(assetManager.get("icons/copy-icon.png", Texture::class.java)) - AbilityType.MIRROR -> TextureRegion(assetManager.get("icons/copy-icon.png", Texture::class.java)) - AbilityType.NEW_MOVEMENT -> TextureRegion(assetManager.get("icons/copy-icon.png", Texture::class.java)) + AbilityType.SHIELD -> TextureRegion(assetManager.get("abilities/shield-card.png", Texture::class.java)) + AbilityType.EXPLOSION -> TextureRegion(assetManager.get("abilities/explosion-card.png", Texture::class.java)) + AbilityType.SWAP -> TextureRegion(assetManager.get("abilities/swap-card.png", Texture::class.java)) + AbilityType.MIRROR -> TextureRegion(assetManager.get("abilities/mirror-card.png", Texture::class.java)) + AbilityType.NEW_MOVEMENT -> TextureRegion(assetManager.get("abilities/new_movement-card.png", Texture::class.java)) } fun createAbilityItem(abilityType: AbilityType): Entity = Entity().apply { add(AbilityComponent(listOf(abilityType))) + add(AbilityCardComponent()) add(TextureRegionComponent(getAbilityItemTexture(abilityType))) - add(HighlightComponent(Color.BLACK)) - engine.addEntity(this) } } diff --git a/chessevolved_model/src/main/kotlin/io/github/chessevolved/systems/InputSystem.kt b/chessevolved_model/src/main/kotlin/io/github/chessevolved/systems/InputSystem.kt index 54e333bf..5dd40e54 100644 --- a/chessevolved_model/src/main/kotlin/io/github/chessevolved/systems/InputSystem.kt +++ b/chessevolved_model/src/main/kotlin/io/github/chessevolved/systems/InputSystem.kt @@ -36,16 +36,25 @@ class InputSystem : private fun handlePieceClicked(piece: Entity) { val selectionComponent = piece.getComponent(SelectionComponent::class.java) - val selectedPiece = engine.getEntitiesFor(Family.all(SelectionComponent::class.java).get()).firstOrNull() + val selectedEntity = engine.getEntitiesFor(Family.all(SelectionComponent::class.java).get()).firstOrNull() val canBeCapturedComponent = piece.getComponent(CanBeCapturedComponent::class.java) if (selectionComponent != null) { piece.remove(SelectionComponent::class.java) - } else if (selectedPiece != null && canBeCapturedComponent != null) { + } else if (selectedEntity != null && canBeCapturedComponent != null) { piece.add(CapturedComponent()) } else { - selectedPiece?.remove(SelectionComponent::class.java) - piece.add(SelectionComponent()) + if (selectedEntity != null && + AbilityCardComponent.mapper.get(selectedEntity) != null && + AbilityCardComponent.mapper.get(selectedEntity).isInInventory + ) { + // TODO: Add logic for applying ability to piece + println("Ability got applied to piece!") + selectedEntity.removeAll() // Remove abilityCard-entity from the game. + } else { + selectedEntity?.remove(SelectionComponent::class.java) + piece.add(SelectionComponent()) + } } } @@ -59,8 +68,16 @@ class InputSystem : } private fun handleAbilityCardClicked(abilityCard: Entity) { - println("Ability card clicked") - // TODO: Give selected component, de-select all other ability-cards, set highlightComponent to white (selected color) + val selectionComponent = SelectionComponent.mapper.get(abilityCard) + val alreadySelectedEntity = engine.getEntitiesFor(Family.all(SelectionComponent::class.java).get()).firstOrNull() + // val abilityCardComponent = AbilityCardComponent.mapper.get(abilityCard) + + if (selectionComponent != null) { + abilityCard.remove(SelectionComponent::class.java) + } else { + alreadySelectedEntity?.remove(SelectionComponent::class.java) + abilityCard.add(SelectionComponent()) + } } } @@ -91,4 +108,23 @@ class InputService { entity?.add(ClickEventComponent()) } + + fun confirmAbilityChoice() { + val entityWithSelectionComponent = + ECSEngine.getEntitiesFor(Family.all(SelectionComponent::class.java).get()).firstOrNull() ?: return + val abilityCardComponent = AbilityCardComponent.mapper.get(entityWithSelectionComponent) ?: return + if (abilityCardComponent.isInInventory) return + + abilityCardComponent.isInInventory = true + entityWithSelectionComponent.remove(SelectionComponent::class.java) + + // Remove all other not-in-inventory cards + ECSEngine + .getEntitiesFor(Family.all(AbilityCardComponent::class.java).get()) + .filter { + !AbilityCardComponent.mapper.get(it).isInInventory + }.forEach { + it.removeAll() + } + } } diff --git a/chessevolved_model/src/main/kotlin/io/github/chessevolved/systems/SelectionEntityListener.kt b/chessevolved_model/src/main/kotlin/io/github/chessevolved/systems/SelectionEntityListener.kt index 911faeab..352781b6 100644 --- a/chessevolved_model/src/main/kotlin/io/github/chessevolved/systems/SelectionEntityListener.kt +++ b/chessevolved_model/src/main/kotlin/io/github/chessevolved/systems/SelectionEntityListener.kt @@ -4,6 +4,7 @@ import com.badlogic.ashley.core.Entity import com.badlogic.ashley.core.EntityListener import com.badlogic.ashley.core.Family import com.badlogic.gdx.graphics.Color +import io.github.chessevolved.components.AbilityCardComponent import io.github.chessevolved.components.CanBeCapturedComponent import io.github.chessevolved.components.HighlightComponent import io.github.chessevolved.components.MovementRuleComponent @@ -14,7 +15,9 @@ import io.github.chessevolved.components.ValidMovesComponent import io.github.chessevolved.components.WeatherEventComponent import io.github.chessevolved.singletons.ECSEngine -class SelectionEntityListener(private val boardSize: Int) : EntityListener { +class SelectionEntityListener( + private val boardSize: Int, +) : EntityListener { private val moveValidator = MoveValidator() private val capturableFamily = Family.all(CanBeCapturedComponent::class.java).get() @@ -27,6 +30,7 @@ class SelectionEntityListener(private val boardSize: Int) : EntityListener { .get() override fun entityAdded(entity: Entity?) { + if (AbilityCardComponent.mapper.get(entity) != null) return for (piece in ECSEngine.getEntitiesFor(capturableFamily)) { piece.remove(CanBeCapturedComponent::class.java) } @@ -51,6 +55,7 @@ class SelectionEntityListener(private val boardSize: Int) : EntityListener { } override fun entityRemoved(entity: Entity?) { + if (AbilityCardComponent.mapper.get(entity) != null) return entity?.remove(ValidMovesComponent::class.java) for (piece in ECSEngine.getEntitiesFor(capturableFamily)) { diff --git a/chessevolved_presenter/src/main/kotlin/io/github/chessevolved/presenters/GamePresenter.kt b/chessevolved_presenter/src/main/kotlin/io/github/chessevolved/presenters/GamePresenter.kt index d0248ad6..0708fccf 100644 --- a/chessevolved_presenter/src/main/kotlin/io/github/chessevolved/presenters/GamePresenter.kt +++ b/chessevolved_presenter/src/main/kotlin/io/github/chessevolved/presenters/GamePresenter.kt @@ -11,11 +11,16 @@ import com.badlogic.gdx.scenes.scene2d.Stage import com.badlogic.gdx.utils.viewport.FitViewport import com.badlogic.gdx.utils.viewport.Viewport import io.github.chessevolved.Navigator +import io.github.chessevolved.components.AbilityCardComponent +import io.github.chessevolved.components.AbilityComponent +import io.github.chessevolved.components.AbilityType import io.github.chessevolved.components.PieceType import io.github.chessevolved.components.PlayerColor import io.github.chessevolved.components.Position import io.github.chessevolved.components.SelectionComponent +import io.github.chessevolved.components.TextureRegionComponent import io.github.chessevolved.components.WeatherEvent +import io.github.chessevolved.entities.AbilityItemFactory import io.github.chessevolved.entities.BoardSquareFactory import io.github.chessevolved.entities.PieceFactory import io.github.chessevolved.singletons.ECSEngine @@ -36,6 +41,7 @@ class GamePresenter( private val pieceFactory = PieceFactory(engine, assetManager) private val boardSquareFactory = BoardSquareFactory(engine, assetManager) + private val abilityItemFactory = AbilityItemFactory(engine, assetManager) private val gameCamera = OrthographicCamera() private val gameUICamera = OrthographicCamera() @@ -84,8 +90,11 @@ class GamePresenter( resize(Gdx.graphics.width, Gdx.graphics.height) // TODO: Remove after testing ability card UI - val copyIconTexture = Texture(Gdx.files.internal("icons/copy-icon.png")) - gameUIView.addAbilityCardToInventory(copyIconTexture, ::onAbilityCardUsed, 1) + val testAbilityCard = abilityItemFactory.createAbilityItem(AbilityType.EXPLOSION) + AbilityCardComponent.mapper.get(testAbilityCard).isInInventory = true + val testAbilityCard2 = abilityItemFactory.createAbilityItem(AbilityType.EXPLOSION) + val testAbilityCard3 = abilityItemFactory.createAbilityItem(AbilityType.SWAP) + val testAbilityCard4 = abilityItemFactory.createAbilityItem(AbilityType.NEW_MOVEMENT) } private fun loadRequiredAssets() { @@ -100,11 +109,16 @@ class GamePresenter( assetManager.load(filename, Texture::class.java) } } + + AbilityType.entries.forEach { ability -> + val abilityName = ability.name.lowercase() + assetManager.load("abilities/$abilityName-card.png", Texture::class.java) + } } private fun setupGameView() { // TODO: Pass in if the player is the white player or not. - gameUIView = GameUIView(gameUIViewport, true) + gameUIView = GameUIView(gameUIViewport, true, ::onSelectAbilityCardButtonClicked) gameUIView.init() gameBoardView = GameView(gameUIView.getStage(), gameViewport) @@ -239,6 +253,7 @@ class GamePresenter( // gameBoardView.render() + updateAbilityCardInventoryView() gameUIViewport.apply() gameUIView.render() } @@ -279,11 +294,80 @@ class GamePresenter( } } } + AbilityType.entries.forEach { ability -> + val abilityName = ability.name.lowercase() + val filename = "abilities/$abilityName-card.png" + if (assetManager.isLoaded(filename)) { + assetManager.unload(filename) + } + } + } + + private fun updateAbilityCardInventoryView() { + // Update if abilityPickPrompt should be showing or not. + val allAbilityCards = ECSEngine.getEntitiesFor(Family.all(AbilityCardComponent::class.java).get()) + val abilityCardsNotInInventory = + allAbilityCards.filter { + !AbilityCardComponent.mapper.get(it).isInInventory + } + + if (abilityCardsNotInInventory.isNotEmpty()) { + val abilityCards = + abilityCardsNotInInventory.associateBy { + GameUIView.AbilityCardInformation( + TextureRegionComponent.mapper + .get(it) + .region + ?.texture, + AbilityCardComponent.mapper.get(it).id, + ) + } + gameUIView.promptPickAbility(abilityCards.keys, ::onAbilityCardClicked) + } else { + gameUIView.hidePromptPickAbility() + } + + // Update if any new cards have appeared/disappeared from inventory. + val abilityCardsInInventory = + allAbilityCards.filter { + AbilityCardComponent.mapper.get(it).isInInventory + } + + val abilityCards = + abilityCardsInInventory.associateBy { + GameUIView.AbilityCardInformation( + TextureRegionComponent.mapper + .get(it) + .region + ?.texture, + AbilityCardComponent.mapper.get(it).id, + ) + } + + gameUIView.updateCardsInInventory(abilityCards.keys, ::onAbilityCardClicked) + + // Update if a card has been selected or deselected. + val selectedAbilityCardEntity = + ECSEngine.getEntitiesFor(Family.all(AbilityCardComponent::class.java, SelectionComponent::class.java).get()).firstOrNull() + if (selectedAbilityCardEntity != null) { + gameUIView.selectCardFromInventory( + AbilityComponent.mapper + .get(selectedAbilityCardEntity) + .abilities[0] + .abilityDescription, + AbilityCardComponent.mapper.get(selectedAbilityCardEntity).id, + ) + } else { + gameUIView.selectCardFromInventory("", -1) + } + } + + fun onAbilityCardClicked(idOfAbilityClicked: Int) { + inputService.clickAbilityCardWithId(idOfAbilityClicked) } - fun onAbilityCardUsed(abilityId: Int) { - // TODO: Fetch which ability to use. - println("Abilitycard used!") + fun onSelectAbilityCardButtonClicked() { + inputService.confirmAbilityChoice() } override fun setInputProcessor() { diff --git a/chessevolved_view/src/main/kotlin/io/github/chessevolved/views/GameUIView.kt b/chessevolved_view/src/main/kotlin/io/github/chessevolved/views/GameUIView.kt index 873398f5..5aa15140 100644 --- a/chessevolved_view/src/main/kotlin/io/github/chessevolved/views/GameUIView.kt +++ b/chessevolved_view/src/main/kotlin/io/github/chessevolved/views/GameUIView.kt @@ -2,20 +2,23 @@ package io.github.chessevolved.views import com.badlogic.gdx.Gdx import com.badlogic.gdx.graphics.Color +import com.badlogic.gdx.graphics.Pixmap import com.badlogic.gdx.graphics.Texture import com.badlogic.gdx.graphics.g2d.TextureRegion import com.badlogic.gdx.scenes.scene2d.Stage import com.badlogic.gdx.scenes.scene2d.ui.ImageButton +import com.badlogic.gdx.scenes.scene2d.ui.Label import com.badlogic.gdx.scenes.scene2d.ui.Table +import com.badlogic.gdx.scenes.scene2d.ui.TextButton import com.badlogic.gdx.scenes.scene2d.ui.TextField import com.badlogic.gdx.scenes.scene2d.utils.TextureRegionDrawable import com.badlogic.gdx.utils.viewport.Viewport import ktx.actors.onClick -import ktx.scene2d.image import ktx.scene2d.imageButton import ktx.scene2d.label import ktx.scene2d.scene2d import ktx.scene2d.table +import ktx.scene2d.textButton import ktx.scene2d.textField import kotlin.math.min @@ -25,17 +28,26 @@ class GameUIView( */ private val gameViewport: Viewport, private val isWhitePlayer: Boolean, + private val onPickAbilityCardButtonClicked: () -> Unit, ) : IView { private lateinit var stage: Stage private val blackColor = Color(0.37f, 0.5f, 0.6f, 0.8f) private val whiteColor = Color(1f, 1f, 1f, 0.8f) - private var amountOfAbilityCards = 0 - private var sizeOfAbilityCards = min(100, (Gdx.graphics.width - 10) / (amountOfAbilityCards + 1)) private val abilityCards = HashMap() + private var sizeOfAbilityCards = min(100, (Gdx.graphics.width - 10) / (abilityCards.size + 1)) + private var promptedAmountOfPickableAbilities = 0 private lateinit var abilityCardInventory: Table private lateinit var abilityPickerWindow: Table private lateinit var blackTimer: TextField private lateinit var whiteTimer: TextField + private lateinit var abilityDescriptionLabel: Label + private lateinit var abilityInfoTable: Table + private lateinit var pickAbilityButton: TextButton + + data class AbilityCardInformation( + val texture: Texture? = Texture(Gdx.files.internal("pieces/pawn-white.png")), + val id: Int, + ) override fun init() { stage = Stage(gameViewport) @@ -52,14 +64,35 @@ class GameUIView( color = blackColor isDisabled = true } + row() + } + + val bgPixmap = Pixmap(1, 1, Pixmap.Format.RGB565) + bgPixmap.setColor(Color.WHITE) + bgPixmap.fill() + val textureRegionDrawableBg = TextureRegionDrawable(TextureRegion(Texture(bgPixmap))) + + abilityInfoTable = + scene2d.table { + color = blackColor + label("Ability Information:") { + setAlignment(1) + }.cell(growX = true, colspan = 2) + row() + abilityDescriptionLabel = + label("") { + wrap = true + setAlignment(1) + }.cell(growX = true, colspan = 2) + setBackground(textureRegionDrawableBg) } + bgPixmap.dispose() + abilityPickerWindow = scene2d.table { defaults().width(100f).height(100f).pad(4f) - imageButton() - imageButton() - imageButton() + isVisible = false } abilityCardInventory = @@ -87,15 +120,30 @@ class GameUIView( scene2d.table { // setDebug(true, true) setFillParent(true) - add(blackInfoBox).growX().expandY().top() + add(blackInfoBox) + .growX() + .top() + .padBottom(10f) row() - add(abilityPickerWindow.bottom().padBottom(150f)).expandY().height(500f) + add(abilityInfoTable.top()) + .growX() + .expandY() + .top() + .height(100f) + row() + add(abilityPickerWindow).height(200f) + row() + pickAbilityButton = + textButton("Select") { + onClick { onPickAbilityCardButtonClicked() } + }.cell(padTop = -10f) row() add(abilityCardInventory) .growX() .growY() .expandY() .bottom() + .height(120f) row() add(whiteInfoBox).growX() } @@ -103,57 +151,67 @@ class GameUIView( stage.addActor(root) } - data class ThreeAbilities( - val ability1Texture: Texture, - val ability2Texture: Texture, - val ability3Texture: Texture, - val onAbilityPickedListener: (abilityPicked: Int) -> Unit, - ) - - fun promptPickAbility(abilities: ThreeAbilities) { + fun promptPickAbility( + abilityCards: Set, + onAbilityPickedListener: (idOfAbilityClicked: Int) -> Unit, + ) { + if (promptedAmountOfPickableAbilities == abilityCards.size) return + promptedAmountOfPickableAbilities = abilityCards.size abilityPickerWindow.reset() abilityPickerWindow.isVisible = true - abilityPickerWindow.add( - scene2d.imageButton { onClick { onAbilityPicked(abilities.onAbilityPickedListener, 1) } }, - ) - abilityPickerWindow.add( - scene2d.imageButton { onClick { onAbilityPicked(abilities.onAbilityPickedListener, 2) } }, - ) - abilityPickerWindow.add( - scene2d.imageButton { onClick { onAbilityPicked(abilities.onAbilityPickedListener, 3) } }, - ) + pickAbilityButton.isVisible = true + abilityCards.forEach { + val id = it.id + val texture = it.texture + abilityPickerWindow.add( + scene2d.imageButton { + onClick { onAbilityPickedListener(id) } + style.imageUp = TextureRegionDrawable(TextureRegion(texture)) + style.imageDown = TextureRegionDrawable(TextureRegion(texture)) + }, + ) + } } - private fun onAbilityPicked( - onAbilityPickedListener: (abilityPicked: Int) -> Unit, - abilityPicked: Int, - ) { + fun hidePromptPickAbility() { + if (!abilityPickerWindow.isVisible) return + promptedAmountOfPickableAbilities = 0 abilityPickerWindow.isVisible = false - onAbilityPickedListener.invoke(abilityPicked) + pickAbilityButton.isVisible = false } - /** - * @param abilityId corresponds to the id of the ability, not the ability-type. abilityId will be used - * to remove the ability from the inventory as well. - */ - fun addAbilityCardToInventory( - abilityTexture: Texture, + fun updateCardsInInventory( + abilityCards: Set, onAbilityUsed: (abilityId: Int) -> Unit, - abilityId: Int, ) { - amountOfAbilityCards++ - sizeOfAbilityCards = min(100, (Gdx.graphics.width - 10) / amountOfAbilityCards + 1) + // Avoid updating inventory if inventory haven't lost/gained cards. + if (abilityCards.size == this.abilityCards.size) return - var abilityCard = + abilityCardInventory.clear() + this.abilityCards.clear() + abilityCards.forEach { + addAbilityCardToInventory(it, onAbilityUsed) + } + } + + private fun addAbilityCardToInventory( + abilityCardInformation: AbilityCardInformation, + onAbilityUsed: (idOfAbilityClicked: Int) -> Unit, + ) { + sizeOfAbilityCards = min(100, (Gdx.graphics.width - 10) / (abilityCards.size + 1)) + + val abilityCard = scene2d.imageButton { - image(TextureRegionDrawable(TextureRegion(abilityTexture))) + // TODO: Make image fill imageButton + style.imageUp = TextureRegionDrawable(TextureRegion(abilityCardInformation.texture)) + style.imageDown = TextureRegionDrawable(TextureRegion(abilityCardInformation.texture)) onClick { - onAbilityUsed(abilityId) + onAbilityUsed(abilityCardInformation.id) } } abilityCardInventory.add(abilityCard) - abilityCards[abilityId] = abilityCard + abilityCards[abilityCardInformation.id] = abilityCard abilityCardInventory.cells.forEach { if (it.expandY != 1) { @@ -163,8 +221,20 @@ class GameUIView( // abilityCardInventory.defaults().width(sizeOfAbilityCards.toFloat()).height(sizeOfAbilityCards.toFloat()) } - fun removeAbilityCardFromInventory(abilityId: Int) { - abilityCards[abilityId]?.remove() + fun selectCardFromInventory( + abilityInformation: String, + abilityCardId: Int, + ) { + abilityDescriptionLabel.setText(abilityInformation) + abilityInfoTable.isVisible = abilityInformation != "" + + abilityCards.forEach { + it.value.width = sizeOfAbilityCards.toFloat() + it.value.height = sizeOfAbilityCards.toFloat() + } + + abilityCards[abilityCardId]?.width = sizeOfAbilityCards.toFloat() + 20f + abilityCards[abilityCardId]?.height = sizeOfAbilityCards.toFloat() + 20f } fun updateWhiteTimer(time: Int) { From 8ce34a377b6fa6bdd5ae5dc4cb34fc71e119563c Mon Sep 17 00:00:00 2001 From: Sivert Underdal Date: Sun, 20 Apr 2025 14:38:25 +0200 Subject: [PATCH 05/11] fix: fix ability-cards showing same image --- assets/skin/plain-james-ui.json | 5 ++- .../github/chessevolved/views/GameUIView.kt | 37 +++++++++++-------- 2 files changed, 24 insertions(+), 18 deletions(-) diff --git a/assets/skin/plain-james-ui.json b/assets/skin/plain-james-ui.json index b4d7349f..df70bf7b 100644 --- a/assets/skin/plain-james-ui.json +++ b/assets/skin/plain-james-ui.json @@ -108,7 +108,8 @@ com.badlogic.gdx.scenes.scene2d.ui.ImageButton$ImageButtonStyle: { over: round-light-gray checked: round-dark-gray checkedOver: round-light-gray - } + }, + } com.badlogic.gdx.scenes.scene2d.ui.ImageTextButton$ImageTextButtonStyle: { default: { @@ -255,4 +256,4 @@ com.badlogic.gdx.scenes.scene2d.ui.Window$WindowStyle: { stageBackground: white } } -} \ No newline at end of file +} diff --git a/chessevolved_view/src/main/kotlin/io/github/chessevolved/views/GameUIView.kt b/chessevolved_view/src/main/kotlin/io/github/chessevolved/views/GameUIView.kt index 5aa15140..16f0c0ad 100644 --- a/chessevolved_view/src/main/kotlin/io/github/chessevolved/views/GameUIView.kt +++ b/chessevolved_view/src/main/kotlin/io/github/chessevolved/views/GameUIView.kt @@ -7,6 +7,7 @@ import com.badlogic.gdx.graphics.Texture import com.badlogic.gdx.graphics.g2d.TextureRegion import com.badlogic.gdx.scenes.scene2d.Stage import com.badlogic.gdx.scenes.scene2d.ui.ImageButton +import com.badlogic.gdx.scenes.scene2d.ui.ImageButton.ImageButtonStyle import com.badlogic.gdx.scenes.scene2d.ui.Label import com.badlogic.gdx.scenes.scene2d.ui.Table import com.badlogic.gdx.scenes.scene2d.ui.TextButton @@ -97,8 +98,6 @@ class GameUIView( abilityCardInventory = scene2d.table { - // This label is here to always keep the height of the abilityCardInventory the same. - label("").cell(expandY = true, height = 100f, width = 0f) defaults().width(sizeOfAbilityCards.toFloat()).height(sizeOfAbilityCards.toFloat()) } @@ -134,7 +133,7 @@ class GameUIView( add(abilityPickerWindow).height(200f) row() pickAbilityButton = - textButton("Select") { + textButton("Select Ability") { onClick { onPickAbilityCardButtonClicked() } }.cell(padTop = -10f) row() @@ -160,16 +159,22 @@ class GameUIView( abilityPickerWindow.reset() abilityPickerWindow.isVisible = true pickAbilityButton.isVisible = true + abilityCards.forEach { + val imgButtonStyle = ImageButtonStyle() val id = it.id val texture = it.texture - abilityPickerWindow.add( - scene2d.imageButton { - onClick { onAbilityPickedListener(id) } - style.imageUp = TextureRegionDrawable(TextureRegion(texture)) - style.imageDown = TextureRegionDrawable(TextureRegion(texture)) - }, - ) + abilityPickerWindow + .add( + scene2d.imageButton { + style = imgButtonStyle + onClick { onAbilityPickedListener(id) } + style.up = TextureRegionDrawable(TextureRegion(texture)) + style.down = TextureRegionDrawable(TextureRegion(texture)) + style.over = TextureRegionDrawable(TextureRegion(texture)).tint(Color(0.5f, 0.5f, 0.5f, 1f)) + }, + ).width(100f) + .height(100f) } } @@ -200,11 +205,13 @@ class GameUIView( ) { sizeOfAbilityCards = min(100, (Gdx.graphics.width - 10) / (abilityCards.size + 1)) + val imgButtonStyle = ImageButtonStyle() val abilityCard = scene2d.imageButton { - // TODO: Make image fill imageButton - style.imageUp = TextureRegionDrawable(TextureRegion(abilityCardInformation.texture)) - style.imageDown = TextureRegionDrawable(TextureRegion(abilityCardInformation.texture)) + style = imgButtonStyle + style.up = TextureRegionDrawable(TextureRegion(abilityCardInformation.texture)) + style.down = TextureRegionDrawable(TextureRegion(abilityCardInformation.texture)) + style.over = TextureRegionDrawable(TextureRegion(abilityCardInformation.texture)).tint(Color(0.5f, 0.5f, 0.5f, 1f)) onClick { onAbilityUsed(abilityCardInformation.id) } @@ -214,9 +221,7 @@ class GameUIView( abilityCards[abilityCardInformation.id] = abilityCard abilityCardInventory.cells.forEach { - if (it.expandY != 1) { - it.width(sizeOfAbilityCards.toFloat()).height(sizeOfAbilityCards.toFloat()) - } + it.width(sizeOfAbilityCards.toFloat()).height(sizeOfAbilityCards.toFloat()) } // abilityCardInventory.defaults().width(sizeOfAbilityCards.toFloat()).height(sizeOfAbilityCards.toFloat()) } From bbcbd7d0f8af59e5c8f1f7df82a562bc1ff2b92f Mon Sep 17 00:00:00 2001 From: chrisjk Date: Sun, 20 Apr 2025 14:56:56 +0200 Subject: [PATCH 06/11] feat: :sparkles: add abilit cards --- assets/abilities/cards/explotionCard.png | Bin 0 -> 1691 bytes assets/abilities/cards/firetrailCard.png | Bin 0 -> 1369 bytes assets/abilities/cards/guardCard.png | Bin 0 -> 1195 bytes assets/abilities/cards/mirrorCard.png | Bin 0 -> 1300 bytes assets/abilities/cards/shieldCard.png | Bin 0 -> 1728 bytes 5 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 assets/abilities/cards/explotionCard.png create mode 100644 assets/abilities/cards/firetrailCard.png create mode 100644 assets/abilities/cards/guardCard.png create mode 100644 assets/abilities/cards/mirrorCard.png create mode 100644 assets/abilities/cards/shieldCard.png diff --git a/assets/abilities/cards/explotionCard.png b/assets/abilities/cards/explotionCard.png new file mode 100644 index 0000000000000000000000000000000000000000..ef33353eb161d483f0228368e1fffc3d8cf59a5e GIT binary patch literal 1691 zcmV;M24wk(P)Px*RY^oaRCt{2TS03ZM-=`x7<}2AOu))1*rp}L;6RH?aIx<-J|rzT;8Hf1+EAIs zMi=8kp~5s6;u0U6LqUfIY-oBh`2jXnO+{N02o6zEDLL2ylcg8al3vt<_RYSXozc#& zb|qr_yJ&ZIX6NlU@4b2RMq>eJ;tZn?0Pa87?zJ2}!QAW&F7TVm#(FR1=?W?v>zJFJ z5%+y`Q79BptJUBXfp^}1t0%%`jf3ZP)M_;p3I$On2ylwG;S~MKAG~|DtHNcCpMHA` zAZ+e^{6-%6AHMt~W3jq}kH7vNfd735&+Fn@pIliU`D-`c>!|bm@Ugf)YFzB-T}iP2 zCo4E=bS87E1d!ma zy#TC@x5~U<4!2Ydx8?OVcL4zT`AJ_x*mG*{+GX^6Wl@@-!-eQ7ryHqifNHc<$gPdT zlCmd;VDrW?=SNGh>icsxH%uo8)tc`3sEg#U?)S9XWn1Dw9 z(y$+NUBC4i29_6rQWedJ5lXZ zY4>(HBruJXB@siQ@&FN=yTTth*bVnns)qWB8ylQYgK;Vcygm2S5^?~r_Ywf$Obo&* zRdLH}OV01=`_}?(E?*f109f{-C~N9`i`RD!z_e#dqzSHF4usrMFd{&Nqu#w+stRWo zAcItQep0BcRjLL&zWm{Yzn(n>02Ej6iZaVy^k)U;4^&Qa`q7`_TKFFWUl2(^e)w3t zklPz|=$5Lj5ho!@2orB_0`Hx!UH1KrOt?n2{;`F~5IM6rV8Hv;jsBwtl|cQeSqDH% z6UZEjt9OM-yvj&UEdjK$!`isdyl2;dG%b<6&wKZ#5nl}C-z!_^idto8 z2@t`JW-6*MGDPRh2N(rBF%U2TDl1jtD9nQ7;WFxwx(QgGgH)-$to9uI8JWZ7E2F|G zW)&(&5} zv~7o`7aChE_J91#zIaYEg3jhC2qJ7Ow*sN2Cr2m@_aLS`DIye+U>a$0)tcPdz8Q9k z83aOFNHGeZ`%RX{nWw}K8}&d;K-!>N4RX-vXNgDxjv5z5?p0;8@7$Yi*sDx9=4LNt|7W@wrb5{L)9#Ahb6 z0M7_1Hct2kOzLTI$_-FTOqcNu2*^m__HJVP9OA=xVTuHwhr%Y6{B7@+>PJn7u3c89 z#DI8C5b*K`V;-Y?dHI zjXn@eipo&_u|o3rakt645(;yem_Ro|U_vYcWKGbOPsdn2nu3SFNq2%(NTdO}PIVif zgu_N%XoS2RZkI5X-+kygxlaEe1*BjCmuUY0VUJHxsl7=~Q0EJHpN9$m97Q;f1Clo6 zqd?F_c_z_clnF%1kyI131ms6Qjc6JspqIGu%|YAeOC#!Xy$1Z(-orHi{T=&1?_ue? zI*$K%8Y!R(FB{MB&)+X1`_h5;A1T9#`Y$$T-Upl$&}#vDjWbVx)(Jjb{T2Z5(ef8v z6DAAX`B3fr2ucFzWW{MYIifl#LI@E+XKLt-4QYZ7OC!)uG0_;p!>>M1*fw7O@>wB5 zfB-s8MW?IUG502dpB5ts0xBEpsMTuXT%C@wHyv}cGbj`aaEiA9Far3AN)=P51^k38 l09XK;XZi57g)@G9{(pTi&y>XHBtQTF002ovPDHLkV1fjC2F3sY literal 0 HcmV?d00001 diff --git a/assets/abilities/cards/firetrailCard.png b/assets/abilities/cards/firetrailCard.png new file mode 100644 index 0000000000000000000000000000000000000000..e416768c4bef8c12dc439d601a0f4a985336f4fe GIT binary patch literal 1369 zcmV-f1*ZCmP)Px)6G=otRCt{2TTf^lRT%$m$)P7NQVO$Pqz#d}3n{p4C`iGp$)zepSPxT>=HQNY za|;N9u;69W&Rzst=3ozs?J2ECiwH}W2&wG0bR|#}GoT{krHGgHF#ElE^X9$zx0yei z%@0D}yqP!e`+nb__svUQ3BVY0j0FJr{LWW-%aIE<8tXX4Us&DkJmtv*R(Bg4jdgat zK#h96j()!ny$QVf%2F=ErH#i=N9gzasMqVPj}f3ZuS0LT55NA}#Y}}u8$bNG55OPp z3;cy>`ENdcFJ-Z^!8@OR3Bc9w@zW8zw;(<&TK>{owKQc*9ld*d09?Ju69_&6fa7C% z0_j7Zz(Yy^F#DD#kRId-B2Gzr=LjbRfi0jr?Eb-QjhBOdzox72>mD zVPx(Wl2OqRCt{2Tg^+9K^XrT`xkUvar&}D?1I z%Nn~79fCb1VWdP5-XjuUN;E`K5v0Rj>=1M)u!9}snVp@TdH4O8nKgHRr+s(ko#&a~ z^D)oN%X9!ZLJK1X059g|o2^GvFg`YlGyFrjG}}ylte{+)#rW7Ldydf$$8q4gF47sG z??O*glrxRZ?H#zT3&(NTe~f^1W(w&{we!_W{Z^GTjZa_K0Mv(jjDHX<|MvcE!)n>V ztw*l_ROh$3y~EmKVrS9vgCm!XC>2j-Yo06XMI&;S7ry|g&Wk*OP)7h@&+<3M(g1)D zb7iA)Xg=f#Jfs8wW-KpSUDc-38)zQn2_lpPm;zayMJY8+HH5 zCL+4MtdDH0&fa7M?dlPk0J6SJS%M!@yxy1fBd>|-1xAwK+R8Ejpt5&da>f(Y61#t3 z5P*8Z^selEdRHgf%b@N7*H)I9<;Xo{2~M=8=ryYMb$fx8IzYAj(PU(#*@AWgcnj%B zQt?#QXbv#q0lG+_$(xusK??z;+%#L(`0P%KliT|xg8IuQPj!H<^0~bVL>-{AUPL^V zWfl>OA~75q!7JuFdy_SmKQW~+kpRfUfhMAg<^F*|&p4O4{fXKP0kvzk${WJKgw=Uy z*$eQHc|%wdWRur@m>QY@S*GsnP1a<5@&?%o;RVsZJ@I^iA=u0EEsjo_A4(83C8yVvaN6<@P{6z&kF+;LVoLV{?LNo z-33J{Hyv1B(9pB98>F3H1Tsv)`gS-7bS6?2T7ECkCLl=X%6gH-F@Ja|f-hvERBATl z1wlWfEM-KLY^V%-yx7j0rrwE>C_0DFJk|;G}U zY{(N-nCXUciD`^3EIgPD`ED@z;$0y^4FPnUif&hVG50@$zb!^!1e8m&a9x+(t5Y%d zzhiuC6prH{otXl_5y0Q5lreQ$z~7JsfDQmht$ca911)}i{vR}lZr%~3sc!%P002ov JPDHLkV1jGuD`o%y literal 0 HcmV?d00001 diff --git a/assets/abilities/cards/mirrorCard.png b/assets/abilities/cards/mirrorCard.png new file mode 100644 index 0000000000000000000000000000000000000000..7443de627efb4ee7eaafad947062d56ff0d6bb58 GIT binary patch literal 1300 zcmV+v1?&2WP)Px(&PhZ;RCt{2n@?yPM-;|C2kYQNbm_%ZzJyq~q!?`6ctb*<*U+BS7JP6CDuMbC znZ&*s3Wb6pmpE9L9vpV6?$w-iRroxF@%ID5tKqjOrR7Lh@m>T2lvgs9nI|j zol&g*PSR*3z4yKMW@l!1MgSaQh#3WdFSoz8ZHE<>78h`WziHGrY}#XlMtuWIiwolY zD7|nT2d?X)S_9sGYs!jodGqK=3$E+JaU9Vm1gO@oqgwMiUwmiQsBwAo^KTCT^q2c6 zeIJpUd&X^GE9<<4^C&wp?>Pup^5=i6Tacz#hP5Pbpw$0zax z`9qn&Q3?RO{*foh56T1?tpFw|%`A)8oxM$?|7}$Oa=h1_7j1>&QJX0ttO-z>Sr(38 zd3IN{_qr7TM?#noArk?33IpNMBj>r}1hw_L6(b^?2=9_DLC$loJiCitcfL>DtVxvt zWL#;|n?O}Q`BbzgiC%Sl42}JRG-k!ZXa);5U2jysyuxGt+&2 zRp^|5xAX=8Z{d>X(ma*`rI}^lh%Y-%{QmeM0HC&hQ?%9BR(yR_&Nt^K&_w@=jsJfV zf=bcKOi*^dIX3}7@3^i#3Srq3NdVRLo=5+??S`-a`-@coN11Ty%rxq2E292Yk^iyt z7aIEqxTq0gU`ik@K?5EDT)zJ&04Rm%b>~qi?q)tUHYz}n^Bh@-JP8yaDD~yE=kU*{ z9Au(Qc}a*PDuI2IVtg?~#F_8sIU`J50z9(1K`<^hW`x!@ z>wMY`)j$E{!9^O5+Py<&}p}Pa{|f+-D;4it=~kUxPo4{B9_B)%0wuu$Y(+94B+*iwDt$ZdBAW9boMq;C@!Pdt>E6v?*RlmAC~@E zka$ZBh&KrG-yv*9p0F|o6pFjJaPm@M9~03BQj|bg^Z{>EREC!V;D0_~t4z-a$Kj4V~cn?JY5O;!({SzX9=OhWi+f1PqAnYzor`-}6Auor6HcaK;?(A(E zcbcOYU{d|Ouqy4N>m^`@go_g>jlmgJnisN!}M0HYx=o3JG zG>tMr$7&hcOH4SYap&`^3BMcf|8+NBd;-vEDmq;iq}=NWKP^TG0UGrUxUMVC)n$3B zYLHl3T!7;^sMf9nU;^+Hl`5r<3;cvE0E_@Q9OlE*BN+1I^Zx=^3P1-ihp-R;0000< KMNUMnLSTZiu3h^8 literal 0 HcmV?d00001 diff --git a/assets/abilities/cards/shieldCard.png b/assets/abilities/cards/shieldCard.png new file mode 100644 index 0000000000000000000000000000000000000000..9ed26f4a8f5390b8e40b407fb76f57daada9e146 GIT binary patch literal 1728 zcmV;x20!_UP)Px*dPzhAA@+d)5=kFSc+f#1}zax>15vXuz@wyJVqR3rqRwW($Z9W>Y_yFkpsBSDO|4<#*I%(F zRXDBj%kL8a3|9stSl_;+?%WME4yR&nwch{H*2{Rh5u( zf!#7-16?dD-_t(_04OlnX_P)=ch4TD?UsraEX`_N!#7C$(2Vl1+c5muB0^h zN!!_AsKi4}B&2|7(cV3quZqAd526eyUp6Kr;tEM|0e02%3IyLTF_(*I45lI(ZKSPH zEyS&WNP&;HN}ato=%fK*;vxSrg#6;Z_x(VoX%Z*S)QS2MB@muG>mu?b5)f6hfM{l~ z-#!HZ2>El6X`1BHg#5=4SknUl43>E=jiq8mH5UWoP(ZxtEivW1f+NsxpF%KwSY-Co z6XPQKDr*f@jKNfdfV&JQ&eU;(qY@H{2dPYOw+xh`GXNO%4(PTs5lkPBo3Ft#kAS-j zM!f^e!9me8;s#3N#sk%N0b;UG=hq{cK8!7dBR(M)Ra5tl~_bLGOcZQS;#6qOB^~D>FtE@HH@SH?;`3N@%W_a3=T|N>MFQsiU-Yrp+ zM98Rj$ozi8a}od|PB6QCgiB|z%){t&Xdr3I1yTx7-32->o`-X^6hDtY03akn3tkU$ zwzfocB+^Tfo*}FWc{dmvx(NVyqo(~4@CD1UJn+$9vPj%R76*u zfULfj&}0{|*ZT11U2z7tuKOMUkhA$Vy_RBeq6)>h>jS!+aYz}5q_f@TV^*ZSZbErn6U~jiN|WYJ!`g5LSc2ZXn$ilwj0rlln>#vjRv^ zv6^H17{X#XI4GKs^!=V77Me(~n$PZCO;EZcuq817G!B%Q%kh-|T`rGI(vMlehZu?)@wha)D^^z*4b-EL}AK zU~4t}K@^aG5Rt-QDxxWHmadxeQ8N2P{GNqoy8t_BG6~(kxd1{D`)&MVp!6&c;Hb(H z3Q$!6N*_K6g6nITo)9J_eH|txvMk->sJzw84tJ#hHXZ;#UcnfbuEcye+-FS?-a9!b zqOnv6?Yi6r+Ga4-fd}IAM26j9*A`$Z2($1n2}O_hIzLmSCleH#H=xTs%p;fKdMJOtuq?(_sgdtY|Ss7vp7ur8a zNGK_;b$u-xLyGz@ry}xbujIEYTfpPU|5e!uj>GxuFxtPFf&Xu@IluO%P};uzll-mk z{jMwYdUeTwT3|}#; zoeU)XUt+9M7H99`QNR<`-Lyo?1(Y6K@9Y5pyw!0jX~JlMD|P(fN5EA8Ia#q^PLA+S ziV(;YK+e>VGd9E$l+a!Y#VsZ}EVzF8!vy=rH@@_&5P?Vm Date: Sun, 20 Apr 2025 16:25:03 +0200 Subject: [PATCH 07/11] fix: merge conflicts --- .../entities/AbilityItemFactory.kt | 19 +++++++++--- .../chessevolved/systems/InputSystem.kt | 31 ++++++++++++++----- .../chessevolved/systems/MovementSystem.kt | 3 ++ .../chessevolved/presenters/GamePresenter.kt | 22 ++++++------- .../github/chessevolved/enums/AbilityType.kt | 14 +++++---- 5 files changed, 58 insertions(+), 31 deletions(-) diff --git a/chessevolved_model/src/main/kotlin/io/github/chessevolved/entities/AbilityItemFactory.kt b/chessevolved_model/src/main/kotlin/io/github/chessevolved/entities/AbilityItemFactory.kt index 6b55b608..7f171292 100644 --- a/chessevolved_model/src/main/kotlin/io/github/chessevolved/entities/AbilityItemFactory.kt +++ b/chessevolved_model/src/main/kotlin/io/github/chessevolved/entities/AbilityItemFactory.kt @@ -7,8 +7,8 @@ import com.badlogic.gdx.graphics.Texture import com.badlogic.gdx.graphics.g2d.TextureRegion import io.github.chessevolved.components.AbilityCardComponent import io.github.chessevolved.components.AbilityComponent -import io.github.chessevolved.components.AbilityType import io.github.chessevolved.components.TextureRegionComponent +import io.github.chessevolved.enums.AbilityType class AbilityItemFactory( private val engine: Engine, @@ -23,11 +23,22 @@ class AbilityItemFactory( AbilityType.NEW_MOVEMENT -> TextureRegion(assetManager.get("abilities/new_movement-card.png", Texture::class.java)) } - fun createAbilityItem(abilityType: AbilityType): Entity = - Entity().apply { - add(AbilityComponent(listOf(abilityType))) + fun createAbilityItem(abilityType: AbilityType): Entity { + var cooldown: Int = 3 + + when (abilityType) { + AbilityType.SHIELD -> {cooldown = 3} + AbilityType.EXPLOSION -> {cooldown = 2} + AbilityType.SWAP -> {cooldown = 2} + AbilityType.MIRROR -> {cooldown = 3} + AbilityType.NEW_MOVEMENT -> {cooldown = 0} + } + + return Entity().apply { + add(AbilityComponent(abilityType, cooldown, 0)) add(AbilityCardComponent()) add(TextureRegionComponent(getAbilityItemTexture(abilityType))) engine.addEntity(this) } + } } diff --git a/chessevolved_model/src/main/kotlin/io/github/chessevolved/systems/InputSystem.kt b/chessevolved_model/src/main/kotlin/io/github/chessevolved/systems/InputSystem.kt index 6812b4d9..51308a1e 100644 --- a/chessevolved_model/src/main/kotlin/io/github/chessevolved/systems/InputSystem.kt +++ b/chessevolved_model/src/main/kotlin/io/github/chessevolved/systems/InputSystem.kt @@ -4,6 +4,7 @@ import com.badlogic.ashley.core.Entity import com.badlogic.ashley.core.Family import com.badlogic.ashley.systems.IteratingSystem import io.github.chessevolved.components.AbilityCardComponent +import io.github.chessevolved.components.AbilityComponent import io.github.chessevolved.components.CanBeCapturedComponent import io.github.chessevolved.components.CapturedComponent import io.github.chessevolved.components.ClickEventComponent @@ -49,8 +50,22 @@ class InputSystem : AbilityCardComponent.mapper.get(selectedEntity).isInInventory ) { // TODO: Add logic for applying ability to piece - println("Ability got applied to piece!") - selectedEntity.removeAll() // Remove abilityCard-entity from the game. + if (AbilityComponent.mapper.get(piece) != null) { + println("Already has ability") + } else { + val abilityComponent = AbilityComponent.mapper.get(selectedEntity) + + if (abilityComponent != null) { + piece.add(AbilityComponent( + abilityComponent.ability, + abilityComponent.abilityCooldownTime, + abilityComponent.currentAbilityCDTime + )) + } + + println("Ability got applied to piece!") + selectedEntity.removeAll() // Remove abilityCard-entity from the game. + } } else { selectedEntity?.remove(SelectionComponent::class.java) piece.add(SelectionComponent()) @@ -60,8 +75,8 @@ class InputSystem : private fun handleBoardSquareClicked(boardSquare: Entity) { val position = PositionComponent.mapper.get(boardSquare).position - - ECSEngine + + EcsEngine .getEntitiesFor(Family.all(PieceTypeComponent::class.java, SelectionComponent::class.java).get()) .firstOrNull() ?.add(MovementIntentComponent(position)) @@ -84,7 +99,7 @@ class InputSystem : class InputService { fun clickPieceAtPosition(position: Position) { val entity = - ECSEngine + EcsEngine .getEntitiesFor(Family.all(PieceTypeComponent::class.java).get()) .find { PositionComponent.mapper.get(it).position == position } @@ -102,7 +117,7 @@ class InputService { fun clickAbilityCardWithId(abilityCardId: Int) { val entity = - ECSEngine + EcsEngine .getEntitiesFor(Family.all(AbilityCardComponent::class.java).get()) .find { AbilityCardComponent.mapper.get(it).id == abilityCardId } @@ -111,7 +126,7 @@ class InputService { fun confirmAbilityChoice() { val entityWithSelectionComponent = - ECSEngine.getEntitiesFor(Family.all(SelectionComponent::class.java).get()).firstOrNull() ?: return + EcsEngine.getEntitiesFor(Family.all(SelectionComponent::class.java).get()).firstOrNull() ?: return val abilityCardComponent = AbilityCardComponent.mapper.get(entityWithSelectionComponent) ?: return if (abilityCardComponent.isInInventory) return @@ -119,7 +134,7 @@ class InputService { entityWithSelectionComponent.remove(SelectionComponent::class.java) // Remove all other not-in-inventory cards - ECSEngine + EcsEngine .getEntitiesFor(Family.all(AbilityCardComponent::class.java).get()) .filter { !AbilityCardComponent.mapper.get(it).isInInventory diff --git a/chessevolved_model/src/main/kotlin/io/github/chessevolved/systems/MovementSystem.kt b/chessevolved_model/src/main/kotlin/io/github/chessevolved/systems/MovementSystem.kt index 1613a1e8..143dab13 100644 --- a/chessevolved_model/src/main/kotlin/io/github/chessevolved/systems/MovementSystem.kt +++ b/chessevolved_model/src/main/kotlin/io/github/chessevolved/systems/MovementSystem.kt @@ -3,6 +3,7 @@ package io.github.chessevolved.systems import com.badlogic.ashley.core.Entity import com.badlogic.ashley.core.Family import com.badlogic.ashley.systems.IteratingSystem +import io.github.chessevolved.components.AbilityTriggerComponent import io.github.chessevolved.components.ActorComponent import io.github.chessevolved.components.FowComponent import io.github.chessevolved.components.MovementIntentComponent @@ -45,6 +46,8 @@ class MovementSystem( val pieceActorComponent = ActorComponent.mapper.get(entity) val pieceMovementRuleComponent = MovementRuleComponent.mapper.get(entity) + entity?.add(AbilityTriggerComponent(targetPosition, piecePositionComponent.position)) + piecePositionComponent.position = targetPosition pieceActorComponent.actor.setPosition(targetPosition.x.toFloat(), targetPosition.y.toFloat()) diff --git a/chessevolved_presenter/src/main/kotlin/io/github/chessevolved/presenters/GamePresenter.kt b/chessevolved_presenter/src/main/kotlin/io/github/chessevolved/presenters/GamePresenter.kt index 01d7729a..18b4e08b 100644 --- a/chessevolved_presenter/src/main/kotlin/io/github/chessevolved/presenters/GamePresenter.kt +++ b/chessevolved_presenter/src/main/kotlin/io/github/chessevolved/presenters/GamePresenter.kt @@ -13,13 +13,9 @@ import com.badlogic.gdx.utils.viewport.Viewport import io.github.chessevolved.Navigator import io.github.chessevolved.components.AbilityCardComponent import io.github.chessevolved.components.AbilityComponent -import io.github.chessevolved.components.AbilityType -import io.github.chessevolved.components.PieceType -import io.github.chessevolved.components.PlayerColor -import io.github.chessevolved.components.Position +import io.github.chessevolved.enums.AbilityType import io.github.chessevolved.components.SelectionComponent import io.github.chessevolved.components.TextureRegionComponent -import io.github.chessevolved.components.WeatherEvent import io.github.chessevolved.entities.AbilityItemFactory import io.github.chessevolved.data.Position import io.github.chessevolved.entities.BoardSquareFactory @@ -37,6 +33,7 @@ import io.github.chessevolved.singletons.Lobby import io.github.chessevolved.systems.AbilitySystem import io.github.chessevolved.systems.CaptureSystem import io.github.chessevolved.systems.FowRenderingSystem +import io.github.chessevolved.systems.InputService import io.github.chessevolved.systems.InputSystem import io.github.chessevolved.systems.MovementSystem import io.github.chessevolved.systems.RenderingSystem @@ -76,11 +73,11 @@ class GamePresenter( private val selectionListener: SelectionEntityListener private val captureSystem: CaptureSystem private val inputSystem: InputSystem + private val inputService = InputService() private val abilitySystem: AbilitySystem private val visualEffectSystem: VisualEffectSystem private var navigatingToEndGame = false - private val isFowEnabled = GameSettings.isFOWEnabled() init { setupGameView() @@ -124,7 +121,7 @@ class GamePresenter( val testAbilityCard2 = abilityItemFactory.createAbilityItem(AbilityType.EXPLOSION) val testAbilityCard3 = abilityItemFactory.createAbilityItem(AbilityType.SWAP) val testAbilityCard4 = abilityItemFactory.createAbilityItem(AbilityType.NEW_MOVEMENT) - + subscribeToGameUpdates(this.toString(), this::onGameStateUpdate) } @@ -149,15 +146,14 @@ class GamePresenter( } private fun setupGameView() { - // TODO: Pass in if the player is the white player or not. - gameUIView = GameUIView(gameUIViewport, true, ::onSelectAbilityCardButtonClicked) runBlocking { launch { Game.joinGame(Lobby.getLobbyId() ?: throw IllegalStateException("Can't join a game if not in a lobby first!")) } } - gameUIView = GameUIView(gameViewport, gameCamera) + // TODO: Pass in if the player is the white player or not. + gameUIView = GameUIView(gameUIViewport, true, ::onSelectAbilityCardButtonClicked) gameUIView.init() gameBoardView = GameView(gameUIView.getStage(), gameViewport) @@ -367,7 +363,7 @@ class GamePresenter( private fun updateAbilityCardInventoryView() { // Update if abilityPickPrompt should be showing or not. - val allAbilityCards = ECSEngine.getEntitiesFor(Family.all(AbilityCardComponent::class.java).get()) + val allAbilityCards = EcsEngine.getEntitiesFor(Family.all(AbilityCardComponent::class.java).get()) val abilityCardsNotInInventory = allAbilityCards.filter { !AbilityCardComponent.mapper.get(it).isInInventory @@ -410,12 +406,12 @@ class GamePresenter( // Update if a card has been selected or deselected. val selectedAbilityCardEntity = - ECSEngine.getEntitiesFor(Family.all(AbilityCardComponent::class.java, SelectionComponent::class.java).get()).firstOrNull() + EcsEngine.getEntitiesFor(Family.all(AbilityCardComponent::class.java, SelectionComponent::class.java).get()).firstOrNull() if (selectedAbilityCardEntity != null) { gameUIView.selectCardFromInventory( AbilityComponent.mapper .get(selectedAbilityCardEntity) - .abilities[0] + .ability .abilityDescription, AbilityCardComponent.mapper.get(selectedAbilityCardEntity).id, ) diff --git a/chessevolved_shared/src/main/kotlin/io/github/chessevolved/enums/AbilityType.kt b/chessevolved_shared/src/main/kotlin/io/github/chessevolved/enums/AbilityType.kt index 4f55941e..dff8b621 100644 --- a/chessevolved_shared/src/main/kotlin/io/github/chessevolved/enums/AbilityType.kt +++ b/chessevolved_shared/src/main/kotlin/io/github/chessevolved/enums/AbilityType.kt @@ -1,9 +1,11 @@ package io.github.chessevolved.enums -enum class AbilityType { - SHIELD, - EXPLOSION, - SWAP, - MIRROR, - NEW_MOVEMENT, +enum class AbilityType( + val abilityDescription: String +) { + SHIELD("Blocks attacks from opponent pieces"), + EXPLOSION("Causes an explosion around the tile that your piece moves to"), + SWAP("Swaps position with a non royal piece"), + MIRROR("Mirrors an ability used against this piece"), + NEW_MOVEMENT("No cluerino"), } From b7ff7922f58cbb8d66a93c305e802c5db2af6104 Mon Sep 17 00:00:00 2001 From: CraZyB1336 Date: Sun, 20 Apr 2025 16:29:54 +0200 Subject: [PATCH 08/11] fix: ktlint --- .../entities/AbilityItemFactory.kt | 20 ++++++++++++++----- .../chessevolved/systems/InputSystem.kt | 12 ++++++----- .../chessevolved/presenters/GamePresenter.kt | 5 ++--- .../github/chessevolved/enums/AbilityType.kt | 2 +- 4 files changed, 25 insertions(+), 14 deletions(-) diff --git a/chessevolved_model/src/main/kotlin/io/github/chessevolved/entities/AbilityItemFactory.kt b/chessevolved_model/src/main/kotlin/io/github/chessevolved/entities/AbilityItemFactory.kt index 7f171292..9b7ea709 100644 --- a/chessevolved_model/src/main/kotlin/io/github/chessevolved/entities/AbilityItemFactory.kt +++ b/chessevolved_model/src/main/kotlin/io/github/chessevolved/entities/AbilityItemFactory.kt @@ -27,11 +27,21 @@ class AbilityItemFactory( var cooldown: Int = 3 when (abilityType) { - AbilityType.SHIELD -> {cooldown = 3} - AbilityType.EXPLOSION -> {cooldown = 2} - AbilityType.SWAP -> {cooldown = 2} - AbilityType.MIRROR -> {cooldown = 3} - AbilityType.NEW_MOVEMENT -> {cooldown = 0} + AbilityType.SHIELD -> { + cooldown = 3 + } + AbilityType.EXPLOSION -> { + cooldown = 2 + } + AbilityType.SWAP -> { + cooldown = 2 + } + AbilityType.MIRROR -> { + cooldown = 3 + } + AbilityType.NEW_MOVEMENT -> { + cooldown = 0 + } } return Entity().apply { diff --git a/chessevolved_model/src/main/kotlin/io/github/chessevolved/systems/InputSystem.kt b/chessevolved_model/src/main/kotlin/io/github/chessevolved/systems/InputSystem.kt index 51308a1e..d0bb9c96 100644 --- a/chessevolved_model/src/main/kotlin/io/github/chessevolved/systems/InputSystem.kt +++ b/chessevolved_model/src/main/kotlin/io/github/chessevolved/systems/InputSystem.kt @@ -56,11 +56,13 @@ class InputSystem : val abilityComponent = AbilityComponent.mapper.get(selectedEntity) if (abilityComponent != null) { - piece.add(AbilityComponent( - abilityComponent.ability, - abilityComponent.abilityCooldownTime, - abilityComponent.currentAbilityCDTime - )) + piece.add( + AbilityComponent( + abilityComponent.ability, + abilityComponent.abilityCooldownTime, + abilityComponent.currentAbilityCDTime, + ), + ) } println("Ability got applied to piece!") diff --git a/chessevolved_presenter/src/main/kotlin/io/github/chessevolved/presenters/GamePresenter.kt b/chessevolved_presenter/src/main/kotlin/io/github/chessevolved/presenters/GamePresenter.kt index 18b4e08b..7eeebc6b 100644 --- a/chessevolved_presenter/src/main/kotlin/io/github/chessevolved/presenters/GamePresenter.kt +++ b/chessevolved_presenter/src/main/kotlin/io/github/chessevolved/presenters/GamePresenter.kt @@ -13,13 +13,13 @@ import com.badlogic.gdx.utils.viewport.Viewport import io.github.chessevolved.Navigator import io.github.chessevolved.components.AbilityCardComponent import io.github.chessevolved.components.AbilityComponent -import io.github.chessevolved.enums.AbilityType import io.github.chessevolved.components.SelectionComponent import io.github.chessevolved.components.TextureRegionComponent -import io.github.chessevolved.entities.AbilityItemFactory import io.github.chessevolved.data.Position +import io.github.chessevolved.entities.AbilityItemFactory import io.github.chessevolved.entities.BoardSquareFactory import io.github.chessevolved.entities.PieceFactory +import io.github.chessevolved.enums.AbilityType import io.github.chessevolved.enums.PieceType import io.github.chessevolved.enums.PlayerColor import io.github.chessevolved.enums.WeatherEvent @@ -28,7 +28,6 @@ import io.github.chessevolved.singletons.EcsEntityMapper import io.github.chessevolved.singletons.Game import io.github.chessevolved.singletons.Game.subscribeToGameUpdates import io.github.chessevolved.singletons.Game.unsubscribeFromGameUpdates -import io.github.chessevolved.singletons.GameSettings import io.github.chessevolved.singletons.Lobby import io.github.chessevolved.systems.AbilitySystem import io.github.chessevolved.systems.CaptureSystem diff --git a/chessevolved_shared/src/main/kotlin/io/github/chessevolved/enums/AbilityType.kt b/chessevolved_shared/src/main/kotlin/io/github/chessevolved/enums/AbilityType.kt index dff8b621..adb824b9 100644 --- a/chessevolved_shared/src/main/kotlin/io/github/chessevolved/enums/AbilityType.kt +++ b/chessevolved_shared/src/main/kotlin/io/github/chessevolved/enums/AbilityType.kt @@ -1,7 +1,7 @@ package io.github.chessevolved.enums enum class AbilityType( - val abilityDescription: String + val abilityDescription: String, ) { SHIELD("Blocks attacks from opponent pieces"), EXPLOSION("Causes an explosion around the tile that your piece moves to"), From d9fdbf3e3148f6f41114b1e8b0044f14661ec29d Mon Sep 17 00:00:00 2001 From: Christer Justad Krane <114220928+ChrisKrane@users.noreply.github.com> Date: Sun, 20 Apr 2025 16:55:13 +0200 Subject: [PATCH 09/11] Update AbilityItemFactory.kt Updated abilitycard paths --- .../io/github/chessevolved/entities/AbilityItemFactory.kt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/chessevolved_model/src/main/kotlin/io/github/chessevolved/entities/AbilityItemFactory.kt b/chessevolved_model/src/main/kotlin/io/github/chessevolved/entities/AbilityItemFactory.kt index 9b7ea709..0c9b5bfa 100644 --- a/chessevolved_model/src/main/kotlin/io/github/chessevolved/entities/AbilityItemFactory.kt +++ b/chessevolved_model/src/main/kotlin/io/github/chessevolved/entities/AbilityItemFactory.kt @@ -16,10 +16,11 @@ class AbilityItemFactory( ) { private fun getAbilityItemTexture(abilityType: AbilityType): TextureRegion = when (abilityType) { - AbilityType.SHIELD -> TextureRegion(assetManager.get("abilities/shield-card.png", Texture::class.java)) - AbilityType.EXPLOSION -> TextureRegion(assetManager.get("abilities/explosion-card.png", Texture::class.java)) + AbilityType.SHIELD -> TextureRegion(assetManager.get("abilities/cards/shieldCard.png", Texture::class.java)) + AbilityType.EXPLOSION -> TextureRegion(assetManager.get("abilities/cards/explosionCard.png", Texture::class.java)) + // TODO: update all cards AbilityType.SWAP -> TextureRegion(assetManager.get("abilities/swap-card.png", Texture::class.java)) - AbilityType.MIRROR -> TextureRegion(assetManager.get("abilities/mirror-card.png", Texture::class.java)) + AbilityType.MIRROR -> TextureRegion(assetManager.get("abilities/cards/mirrorCard.png", Texture::class.java)) AbilityType.NEW_MOVEMENT -> TextureRegion(assetManager.get("abilities/new_movement-card.png", Texture::class.java)) } From c0371a317d8fa0b536c70df560b5a5e1dd936ce6 Mon Sep 17 00:00:00 2001 From: CraZyB1336 Date: Sun, 20 Apr 2025 17:48:58 +0200 Subject: [PATCH 10/11] refactor: review requests --- .../kotlin/io/github/chessevolved/presenters/GamePresenter.kt | 4 ---- .../main/kotlin/io/github/chessevolved/views/GameUIView.kt | 2 -- 2 files changed, 6 deletions(-) diff --git a/chessevolved_presenter/src/main/kotlin/io/github/chessevolved/presenters/GamePresenter.kt b/chessevolved_presenter/src/main/kotlin/io/github/chessevolved/presenters/GamePresenter.kt index 7eeebc6b..5238a303 100644 --- a/chessevolved_presenter/src/main/kotlin/io/github/chessevolved/presenters/GamePresenter.kt +++ b/chessevolved_presenter/src/main/kotlin/io/github/chessevolved/presenters/GamePresenter.kt @@ -114,12 +114,8 @@ class GamePresenter( resize(Gdx.graphics.width, Gdx.graphics.height) - // TODO: Remove after testing ability card UI val testAbilityCard = abilityItemFactory.createAbilityItem(AbilityType.EXPLOSION) AbilityCardComponent.mapper.get(testAbilityCard).isInInventory = true - val testAbilityCard2 = abilityItemFactory.createAbilityItem(AbilityType.EXPLOSION) - val testAbilityCard3 = abilityItemFactory.createAbilityItem(AbilityType.SWAP) - val testAbilityCard4 = abilityItemFactory.createAbilityItem(AbilityType.NEW_MOVEMENT) subscribeToGameUpdates(this.toString(), this::onGameStateUpdate) } diff --git a/chessevolved_view/src/main/kotlin/io/github/chessevolved/views/GameUIView.kt b/chessevolved_view/src/main/kotlin/io/github/chessevolved/views/GameUIView.kt index 16f0c0ad..8bbdf2dc 100644 --- a/chessevolved_view/src/main/kotlin/io/github/chessevolved/views/GameUIView.kt +++ b/chessevolved_view/src/main/kotlin/io/github/chessevolved/views/GameUIView.kt @@ -117,7 +117,6 @@ class GameUIView( val root = scene2d.table { - // setDebug(true, true) setFillParent(true) add(blackInfoBox) .growX() @@ -223,7 +222,6 @@ class GameUIView( abilityCardInventory.cells.forEach { it.width(sizeOfAbilityCards.toFloat()).height(sizeOfAbilityCards.toFloat()) } - // abilityCardInventory.defaults().width(sizeOfAbilityCards.toFloat()).height(sizeOfAbilityCards.toFloat()) } fun selectCardFromInventory( From 1438f1c4ec15acf4035caa67e0136e63e6b2c1f7 Mon Sep 17 00:00:00 2001 From: CraZyB1336 Date: Sun, 20 Apr 2025 17:52:29 +0200 Subject: [PATCH 11/11] refactor: review requests --- .../main/kotlin/io/github/chessevolved/systems/InputSystem.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/chessevolved_model/src/main/kotlin/io/github/chessevolved/systems/InputSystem.kt b/chessevolved_model/src/main/kotlin/io/github/chessevolved/systems/InputSystem.kt index d0bb9c96..598e5b37 100644 --- a/chessevolved_model/src/main/kotlin/io/github/chessevolved/systems/InputSystem.kt +++ b/chessevolved_model/src/main/kotlin/io/github/chessevolved/systems/InputSystem.kt @@ -49,7 +49,6 @@ class InputSystem : AbilityCardComponent.mapper.get(selectedEntity) != null && AbilityCardComponent.mapper.get(selectedEntity).isInInventory ) { - // TODO: Add logic for applying ability to piece if (AbilityComponent.mapper.get(piece) != null) { println("Already has ability") } else { @@ -87,7 +86,6 @@ class InputSystem : private fun handleAbilityCardClicked(abilityCard: Entity) { val selectionComponent = SelectionComponent.mapper.get(abilityCard) val alreadySelectedEntity = engine.getEntitiesFor(Family.all(SelectionComponent::class.java).get()).firstOrNull() - // val abilityCardComponent = AbilityCardComponent.mapper.get(abilityCard) if (selectionComponent != null) { abilityCard.remove(SelectionComponent::class.java)