diff --git a/src/main/kotlin/org/openbase/planetsudo/game/strategy/AbstractStrategy.kt b/src/main/kotlin/org/openbase/planetsudo/game/strategy/AbstractStrategy.kt index 2b5d4d86..36aa68b5 100644 --- a/src/main/kotlin/org/openbase/planetsudo/game/strategy/AbstractStrategy.kt +++ b/src/main/kotlin/org/openbase/planetsudo/game/strategy/AbstractStrategy.kt @@ -29,6 +29,8 @@ abstract class AbstractStrategy>(val agent: Agen private val strategyOwner: Agent by lazy { agent as Agent } val mothership: MothershipInterface by lazy { strategyOwner.mothership } + + val tower: TowerInterface by lazy { strategyOwner.mothership.tower } val enemyAgent get() = strategyOwner.enemyAgent val teamAgent get() = strategyOwner.teamAgent private val mothershipInternal: Mothership by lazy { strategyOwner.mothership } diff --git a/src/main/kotlin/org/openbase/planetsudo/game/strategy/DivineStrategy.kt b/src/main/kotlin/org/openbase/planetsudo/game/strategy/DivineStrategy.kt index 1c1ade6e..327c2a92 100644 --- a/src/main/kotlin/org/openbase/planetsudo/game/strategy/DivineStrategy.kt +++ b/src/main/kotlin/org/openbase/planetsudo/game/strategy/DivineStrategy.kt @@ -1,6 +1,7 @@ package org.openbase.planetsudo.game.strategy import org.openbase.planetsudo.game.SwatTeam +import org.openbase.planetsudo.level.LevelSize import org.openbase.planetsudo.level.levelobjects.AgentInterface import org.openbase.planetsudo.level.levelobjects.Resource.ResourceType import org.openbase.planetsudo.level.levelobjects.Tower @@ -37,18 +38,17 @@ class DivineStrategy(agent: AgentInterface) : StrategyLevelLegacy(agent) { } }, ) - // --------------------------------------------> - createRule( - object : Rule("Discover", SwatTeam.COMMANDER) { - override fun constraint(): Boolean { - return true - } - override fun action() { - agent.goRight(4) - } - }, - ) + "Discover" commander inCase { true } then { agent.goRight(4) } + + "Scan" commander inCase { tower.type == Tower.TowerType.ObservationTower && tower.levelSize == LevelSize.UNKNOWN } then { + tower.scanLevelSize() + } + + "Place Observation Tower" commander inCase { agent.hasTower } then { + agent.constructTower(Tower.TowerType.ObservationTower) + } + // --------------------------------------------> // createRule( // object : Rule("Avoid Agents", SwatTeam.COMMANDER) { @@ -498,19 +498,6 @@ class DivineStrategy(agent: AgentInterface) : StrategyLevelLegacy(agent) { } }, ) - // --------------------------------------------> - createRule( - object : Rule("Construct Tower", SwatTeam.COMMANDER) { - override fun constraint(): Boolean { - return agent.hasTower && agent.seeResource - } - - override fun action() { - agent.constructTower(Tower.TowerType.DefenceTower) - } - }, - ) - // --------------------------------------------> createRule( object : Rule("Follow Wall", SwatTeam.ALPHA) { override fun constraint(): Boolean { diff --git a/src/main/kotlin/org/openbase/planetsudo/level/AbstractLevel.kt b/src/main/kotlin/org/openbase/planetsudo/level/AbstractLevel.kt index 90ba225b..dc710792 100644 --- a/src/main/kotlin/org/openbase/planetsudo/level/AbstractLevel.kt +++ b/src/main/kotlin/org/openbase/planetsudo/level/AbstractLevel.kt @@ -33,6 +33,7 @@ abstract class AbstractLevel : AbstractGameObject, Runnable { private val RESOURCES_LOCK: Any = SyncObject("ResourcesLock") private val TOWER_LOCK: Any = SyncObject("TowerLock") private val base: Point2D + val size: LevelSize by lazy { LevelSize.fromLevel(this) } @JvmField val levelBorderPolygon: Polygon @@ -102,11 +103,11 @@ abstract class AbstractLevel : AbstractGameObject, Runnable { this.base = calculateBasePosition(levelBorderPolygon) levelBorderPolygon.translate(-base.x.toInt(), -base.y.toInt()) - if (levelWallPolygons != null) { - for (p in levelWallPolygons) { - p.translate(-base.x.toInt(), -base.y.toInt()) - } + + for (p in levelWallPolygons) { + p.translate(-base.x.toInt(), -base.y.toInt()) } + for (homepos in homePositions) { homepos.translateIntoBase(base) } diff --git a/src/main/kotlin/org/openbase/planetsudo/level/LevelSize.kt b/src/main/kotlin/org/openbase/planetsudo/level/LevelSize.kt new file mode 100644 index 00000000..3c01be3e --- /dev/null +++ b/src/main/kotlin/org/openbase/planetsudo/level/LevelSize.kt @@ -0,0 +1,22 @@ +package org.openbase.planetsudo.level + +import kotlin.math.max + +enum class LevelSize(val label: String) { + SMALL("Klein"), + MEDIUM("Mittel"), + LARGE("Groß"), + UNKNOWN("?"), + ; + + companion object { + fun fromLevel(level: AbstractLevel): LevelSize { + val size = max(level.levelBorderPolygon.bounds2D.width, level.levelBorderPolygon.bounds2D.height) + return when { + size <= 1000 -> SMALL + size <= 1500 -> MEDIUM + else -> LARGE + } + } + } +} diff --git a/src/main/kotlin/org/openbase/planetsudo/level/levelobjects/Agent.kt b/src/main/kotlin/org/openbase/planetsudo/level/levelobjects/Agent.kt index ad0b6341..4b82b779 100644 --- a/src/main/kotlin/org/openbase/planetsudo/level/levelobjects/Agent.kt +++ b/src/main/kotlin/org/openbase/planetsudo/level/levelobjects/Agent.kt @@ -814,7 +814,6 @@ class Agent( override fun seeMarker(): Boolean = mothership.teamMarker.seeMarker(this) - @Suppress("OVERRIDE_DEPRECATION") override fun seeTower(): Boolean = mothership.tower.seeTower(this) @Suppress("OVERRIDE_DEPRECATION") diff --git a/src/main/kotlin/org/openbase/planetsudo/level/levelobjects/AgentLegacyInterface.kt b/src/main/kotlin/org/openbase/planetsudo/level/levelobjects/AgentLegacyInterface.kt index 52d00547..2c40c37b 100644 --- a/src/main/kotlin/org/openbase/planetsudo/level/levelobjects/AgentLegacyInterface.kt +++ b/src/main/kotlin/org/openbase/planetsudo/level/levelobjects/AgentLegacyInterface.kt @@ -9,7 +9,6 @@ interface AgentLegacyInterface : AgentSpecialInterface 0 } @@ -162,7 +174,7 @@ class Tower(id: Int, level: AbstractLevel, @JvmField val mothership: Mothership) } @Synchronized - fun attack() { + override fun attack() { logger.debug("Attack Mothership") if (shieldForce > 0) { shieldForce-- @@ -177,7 +189,7 @@ class Tower(id: Int, level: AbstractLevel, @JvmField val mothership: Mothership) } @Synchronized - fun repair() { + override fun repair() { if (shieldForce < 100) { shieldForce++ if (shieldForce > Mothership.Companion.BURNING_TOWER && timer.isRunning) { @@ -187,16 +199,24 @@ class Tower(id: Int, level: AbstractLevel, @JvmField val mothership: Mothership) } } - val isBurning: Boolean + override fun scanLevelSize() { + if (!isConstructed || levelSize != LevelSize.UNKNOWN) { + return + } + levelSize = level.size + changes.firePropertyChange(TOWER_SCAN_LEVEL, null, null) + } + + override val isBurning: Boolean get() = shieldForce < Mothership.BURNING_TOWER && hasFuel() - val shieldPoints: Int + override val shieldPoints: Int get() = shieldForce / 2 - val isMaxDamaged: Boolean + override val isMaxDamaged: Boolean get() = shieldForce == 0 - val isDamaged: Boolean + override val isDamaged: Boolean get() = shieldForce < 100 override fun actionPerformed(ex: ActionEvent) { @@ -210,6 +230,7 @@ class Tower(id: Int, level: AbstractLevel, @JvmField val mothership: Mothership) const val TOWER_FUEL_VOLUME: Int = 500 const val TOWER_FUEL_STATE_CHANGE: String = "FuelStateChange" const val TOWER_SHIELD_STATE_CHANGE: String = "ShieldStateChange" + const val TOWER_SCAN_LEVEL: String = "ScanLevel" const val TOWER_CONSTRUCT: String = "construct tower" const val TOWER_DECONSTRUCT: String = "deconstruct tower" diff --git a/src/main/kotlin/org/openbase/planetsudo/level/levelobjects/TowerInterface.kt b/src/main/kotlin/org/openbase/planetsudo/level/levelobjects/TowerInterface.kt new file mode 100644 index 00000000..9c1f64a7 --- /dev/null +++ b/src/main/kotlin/org/openbase/planetsudo/level/levelobjects/TowerInterface.kt @@ -0,0 +1,98 @@ +package org.openbase.planetsudo.level.levelobjects + +import org.openbase.planetsudo.level.LevelSize + +/*- + * #%L + * PlanetSudo GameEngine + * %% + * Copyright (C) 2009 - 2024 openbase.org + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * . + * #L% + */ +/** + * + * @author Divine Threepwood + */ +interface TowerInterface { + + val de get() = TowerInterfaceGermanWrapper(this) + + /** + * Returns whether the tower has any fuel left. + */ + fun hasFuel(): Boolean + + /** + * Current fuel amount of the tower. + */ + val fuel: Int + + /** + * Maximum fuel capacity of the tower. + */ + val fuelVolume: Int + + /** + * Current fuel level as percentage. + */ + val fuelInPercent: Int + + /** + * Makes the tower perform an attack. + */ + fun attack() + + /** + * Repairs the tower (increases shield strength). + */ + fun repair() + + /** + * Returns whether the tower is burning (losing fuel). + */ + val isBurning: Boolean + + /** + * Shield points of the tower. + */ + val shieldPoints: Int + + /** + * Returns whether the tower is completely destroyed (no shield left). + */ + val isMaxDamaged: Boolean + + /** + * Returns whether the tower is damaged (shield not full). + */ + val isDamaged: Boolean + + /** + * Current tower type (DefenceTower or ObservationTower). Unknown if no tower is constructed. + */ + val type: Tower.TowerType + + /** + * Scans and updates the currently detected level size. + */ + fun scanLevelSize() + + /** + * The last scanned level size. + */ + val levelSize: LevelSize +} diff --git a/src/main/kotlin/org/openbase/planetsudo/level/levelobjects/TowerInterfaceGermanWrapper.kt b/src/main/kotlin/org/openbase/planetsudo/level/levelobjects/TowerInterfaceGermanWrapper.kt new file mode 100644 index 00000000..62bfaf85 --- /dev/null +++ b/src/main/kotlin/org/openbase/planetsudo/level/levelobjects/TowerInterfaceGermanWrapper.kt @@ -0,0 +1,94 @@ +package org.openbase.planetsudo.level.levelobjects + +/*- + * #%L + * PlanetSudo GameEngine + * %% + * Copyright (C) 2009 - 2024 openbase.org + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * . + * #L% + */ +/** + * + * @author Divine Threepwood + */ +class TowerInterfaceGermanWrapper(private val tower: TowerInterface) { + + /** + * Gibt zurück, ob der Turm Treibstoff hat. + */ + fun hatTreibstoff() = tower.hasFuel() + + /** + * Aktueller Treibstoff des Turms. + */ + val treibstoff get() = tower.fuel + + /** + * Maximales Treibstoffvolumen des Turms. + */ + val treibstoffVolumen get() = tower.fuelVolume + + /** + * Treibstoff in Prozent. + */ + val treibstoffInProzent get() = tower.fuelInPercent + + /** + * Lässt den Turm angreifen. + */ + fun greifeAn() = tower.attack() + + /** + * Repariert den Turm. + */ + fun repariere() = tower.repair() + + /** + * Gibt zurück, ob der Turm brennt. + */ + val istAmBrennen get() = tower.isBurning + + /** + * Gibt die Schildpunkte des Turms zurück. + */ + val schildPunkte get() = tower.shieldPoints + + /** + * Gibt zurück, ob der Turm maximal beschädigt ist. + */ + val istMaximalBeschaedigt get() = tower.isMaxDamaged + + /** + * Gibt zurück, ob der Turm beschädigt ist. + */ + val istBeschaedigt get() = tower.isDamaged + + /** + * Scannt die Level-Größe und aktualisiert die interne Größe. + */ + fun scanneLevelGroesse() = tower.scanLevelSize() + + /** + * Zuletzt gescannte Level-Größe. + */ + val levelGroesse get() = tower.levelSize + + /** + * Typ des Turms (DefenceTower oder ObservationTower). Null wenn nicht konstruiert. + */ + val turmTyp get() = tower.type +} diff --git a/src/main/kotlin/org/openbase/planetsudo/view/configuration/ConfigurationPanel.kt b/src/main/kotlin/org/openbase/planetsudo/view/configuration/ConfigurationPanel.kt index a3de66ec..e99ee49e 100644 --- a/src/main/kotlin/org/openbase/planetsudo/view/configuration/ConfigurationPanel.kt +++ b/src/main/kotlin/org/openbase/planetsudo/view/configuration/ConfigurationPanel.kt @@ -695,6 +695,8 @@ class ConfigurationPanel : JPanel() { } val level = getInstance()!!.loadLevel(levelName) gameManager.setLevel(level!!) + levelPreviewPanel!!.border = + BorderFactory.createTitledBorder("Nächstes Level: ${level.name} \tGröße: ${level.size.label}") levelPreviewDisplayPanel!!.setLevel(level) levelPreviewDisplayPanel!!.isOpaque = true levelPreviewDisplayPanel!!.background = level.color diff --git a/src/main/kotlin/org/openbase/planetsudo/view/level/LevelDisplayPanel.kt b/src/main/kotlin/org/openbase/planetsudo/view/level/LevelDisplayPanel.kt index be324d15..bfa5680e 100644 --- a/src/main/kotlin/org/openbase/planetsudo/view/level/LevelDisplayPanel.kt +++ b/src/main/kotlin/org/openbase/planetsudo/view/level/LevelDisplayPanel.kt @@ -14,6 +14,7 @@ import org.openbase.planetsudo.level.AbstractLevel import org.slf4j.Logger import org.slf4j.LoggerFactory import javax.swing.GroupLayout +import javax.swing.JLabel /** * @@ -41,6 +42,8 @@ class LevelDisplayPanel : ResourceDisplayPanel(), Runnable { setVisibleResourcePanel(LevelPanel(level, this)) } + private val level get() = visibleResourcePanel?.resource + fun displayLevelObjects() { if (visibleResourcePanel != null) { visibleResourcePanel?.loadLevelObjects() @@ -89,6 +92,9 @@ class LevelDisplayPanel : ResourceDisplayPanel(), Runnable { layout.createParallelGroup(GroupLayout.Alignment.LEADING) .addGap(0, 300, Short.MAX_VALUE.toInt()), ) + level?.let { + parent.add("Label", JLabel("Name: ${level?.name} (${level?.size}) }")) + } } // //GEN-END:initComponents override fun run() { diff --git a/src/main/kotlin/org/openbase/planetsudo/view/level/levelobjects/TowerTopPanel.kt b/src/main/kotlin/org/openbase/planetsudo/view/level/levelobjects/TowerTopPanel.kt index 397cce40..6ed546dc 100644 --- a/src/main/kotlin/org/openbase/planetsudo/view/level/levelobjects/TowerTopPanel.kt +++ b/src/main/kotlin/org/openbase/planetsudo/view/level/levelobjects/TowerTopPanel.kt @@ -6,10 +6,14 @@ import org.openbase.planetsudo.level.levelobjects.Tower import org.openbase.planetsudo.level.levelobjects.Tower.TowerType import org.slf4j.Logger import org.slf4j.LoggerFactory +import java.awt.BasicStroke +import java.awt.Color import java.awt.Graphics2D import java.awt.event.MouseEvent +import java.awt.geom.Arc2D import java.beans.PropertyChangeEvent import java.beans.PropertyChangeListener +import javax.swing.Timer /** * @@ -29,10 +33,40 @@ class TowerTopPanel(tower: Tower, parentPanel: TowerPanel) : private var gg2: Graphics2D? = null private var direction: Direction2D? = null + // scanning animation state: expanding wave rings + private var scanning: Boolean = false + private val scanRings: MutableList = ArrayList() + private var scanTimer: Timer? = null + private var scanMaxRadius: Double = 0.0 + private val ringSpeed: Double = 12.0 // pixels per tick + private val ringSpacing: Double = 40.0 // spacing between consecutive rings + init { LOGGER.info("Create $this") this.tower = tower tower.addPropertyChangeListener(this) + + // Timer advances rings outward + scanTimer = Timer(40) { + if (!scanning) return@Timer + val it = scanRings.listIterator() + while (it.hasNext()) { + val idx = it.nextIndex() + val v = it.next() + val nv = v + ringSpeed + it.set(nv) + // remove ring if beyond max radius + spacing + if (nv > scanMaxRadius + ringSpacing * 2) { + it.remove() + } + } + // stop when no rings left + if (scanRings.isEmpty()) { + scanning = false + scanTimer?.stop() + } + parentPanel.repaint() + } } override fun paintComponent(g2: Graphics2D, gl: Graphics2D) { @@ -43,17 +77,78 @@ class TowerTopPanel(tower: Tower, parentPanel: TowerPanel) : boundingBox = resource.bounds direction = resource.direction + gg2 = g2.create() as Graphics2D - paintImageRotated(tower.direction, gg2!!) - gg2!!.dispose() + try { + paintImageRotated(tower.direction, gg2!!) + + // draw scanning waves if active + if (scanning) { + val overlay = g2.create() as Graphics2D + try { + overlay.composite = java.awt.AlphaComposite.getInstance(java.awt.AlphaComposite.SRC_OVER, 1.0f) + overlay.stroke = BasicStroke(3.0f) + + val cx = boundingBox.centerX + val cy = boundingBox.centerY + + for (r in scanRings) { + if (r <= 0) continue + val progress = (r / scanMaxRadius).coerceIn(0.0, 1.0) + val alpha = (1.0 - progress) * 0.7 + val color = Color(0, 220, 255, (alpha * 255).toInt().coerceIn(0, 255)) + overlay.color = color + val ir = r + overlay.draw(Arc2D.Double(cx - ir, cy - ir, ir * 2, ir * 2, 0.0, 360.0, Arc2D.OPEN)) + } + } catch (ex: Exception) { + // ignore painting errors + } finally { + overlay.dispose() + } + } + } finally { + gg2?.dispose() + } } override fun propertyChange(evt: PropertyChangeEvent) { -// if (evt.getPropertyName().equals(Tower.REMOVE_TOWER)) { -// if (((Tower) evt.getNewValue()).equals(resource)) { -// parentResourcePanel.removeChild(this); -// } -// } + // if (evt.getPropertyName().equals(Tower.REMOVE_TOWER)) { + // if (((Tower) evt.getNewValue()).equals(resource)) { + // parentResourcePanel.removeChild(this); + // } + // } + if (evt.propertyName == Tower.TOWER_SCAN_LEVEL) { + // start wave animation: compute max radius to level borders + val levelBounds = tower.level.levelBorderPolygon.bounds2D + val cx = tower.position.x + val cy = tower.position.y + // compute distance to all 4 corners and take max + val corners = arrayOf( + doubleArrayOf(levelBounds.minX, levelBounds.minY), + doubleArrayOf(levelBounds.minX + levelBounds.width, levelBounds.minY), + doubleArrayOf(levelBounds.minX, levelBounds.minY + levelBounds.height), + doubleArrayOf(levelBounds.minX + levelBounds.width, levelBounds.minY + levelBounds.height), + ) + var maxd = 0.0 + for (c in corners) { + val dx = c[0] - cx + val dy = c[1] - cy + val d = Math.hypot(dx, dy) + if (d > maxd) maxd = d + } + scanMaxRadius = maxd + + // prime a few rings so the wave looks continuous + scanRings.clear() + scanRings.add(0.0) + scanRings.add(-ringSpacing) + scanRings.add(-ringSpacing * 2) + scanning = true + scanTimer?.restart() + return + } + // other property changes can be handled here } override val isFocusable: Boolean