Code Sketch
Bixuu spring season event 6
Category: Programming
import java.awt.{BasicStroke, Color, Dimension, Font, GradientPaint, Graphics, Graphics2D, Polygon, RenderingHints, Rectangle}
import java.awt.event.{ActionEvent, ActionListener, KeyAdapter, KeyEvent, MouseAdapter, MouseEvent}
import java.awt.geom.{Ellipse2D, RoundRectangle2D}
import java.io.{BufferedReader, InputStreamReader, PrintWriter}
import java.net.{InetAddress, InetSocketAddress, ServerSocket, Socket}
import java.security.MessageDigest
import java.util.Locale
import java.util.prefs.Preferences
import javax.swing.{JFrame, JOptionPane, JPanel, JPasswordField, SwingUtilities, Timer, WindowConstants}
import scala.collection.mutable
import scala.collection.mutable.ArrayBuffer
import scala.util.Try
/**
* SPRING SEASON EVENT 6 - FIGHTING BLOOM
* Original, stylized arcade battle game for Scala / Java Swing.
* Uses Java Swing only; no external game-engine dependency.
*
* This is a 2D Swing game with perspective-style art, not a full 3D engine.
* Profiles and game progress are saved locally on this computer.
* LAN multiplayer is a small one-peer demo over the same Wi-Fi network.
* It is not an internet account service or a secure production game server.
*/
object BixuuSpringSeasonEvent6 {
private val AppTitle = "SPRING SEASON EVENT 6 - FIGHTING BLOOM"
private val AppRoot = Preferences.userRoot().node("BixuuGames/FightingBloom")
private val Profiles = AppRoot.node("profiles")
// Canonical administrator ID requested for Event 6.
private val AdminId = "BixubuxShah25"
// SHA-256 digests for supported admin ID spellings.
// These protect a LOCAL DEMO profile only; do not use this as production authentication.
private val AdminPasswordHashes = Map(
"bixubuxshah25" -> "cca2f82edaacfa7bef39e4a7128804e6437605037d4886fc8d04e8184e701d6d",
"bixubuxshahare25" -> "266e7000a41697c2908a9b920baaed085cf69c4a27433b2075c9d66df5459d83",
"bixubux" -> "b621541d5dc8439962c0ea4ea2aec5dd332add4af2d5d0e5855e29948a70f182"
)
private val Bg = new Color(8, 22, 32)
private val PanelColor = new Color(13, 39, 49, 235)
private val Mint = new Color(126, 255, 197)
private val Gold = new Color(255, 216, 105)
private val Pink = new Color(255, 125, 174)
private val White = new Color(240, 250, 248)
private val Muted = new Color(163, 193, 190)
private class PlayerData(val id: String, var passwordHash: String, var coins: Int,
var diamonds: Int, var level: Int, var xp: Int,
var avatar: Int, var bundle: String, var powerStyle: String,
var tags: Int, var wins: Int) {
def xpTarget: Int = 100 + (level - 1) * 30
}
private case class UiButton(rect: Rectangle, label: String, action: () => Unit, enabled: Boolean = true)
private class Bot(var x: Double, var y: Double, var tagsLeft: Int, val maxTags: Int,
val name: String, val trainingDummy: Boolean) {
var alive = true
}
private class Loot(var x: Double, var y: Double, val kind: String, val amount: Int) {
var taken = false
}
// Magical, non-realistic Bloom energy projectile used by the arcade light-tag game.
private class BloomBolt(var x: Double, var y: Double, val vx: Double, val vy: Double, var life: Double) {
var active = true
}
private case class RemotePlayer(id: String, x: Double, y: Double, avatar: Int, energy: Int)
private val avatarNames = Array("Bloom Runner", "Moss Mage", "Crystal Guard", "Sun Scout")
private val bundles = Array("Starter Petals", "Leaf Ranger", "Crystal Bloom", "Solar Champion")
private val bundleCoins = Array(0, 150, 260, 420)
private val bundleDiamonds = Array(0, 0, 25, 50)
private val powerCoins = Array(0, 100, 220, 350)
private val powerDiamonds = Array(0, 0, 0, 20)
private val powerNames = Array("Bloom Dash", "Petal Shield", "Healing Bloom", "Nature Nova")
private val costumeNames = Array("Mint Ranger", "Cherry Blossom", "Crystal Bloom", "Golden Guardian")
private val blasterNames = Array("Petal Pulse", "Crystal Beam", "Sun Orb")
private var current: PlayerData = null
private var adminMode = false
private var screen = "login"
private var statusMessage = "Welcome to Fighting Bloom!"
private var selectedAvatar = 0
private var selectedBundle = 0
private var selectedPowerStyle = 0
private var selectedCostume = 0
private var selectedBlaster = 0
private var gameMode = "BR"
private var gameRunning = false
private var matchFinished = false
private var matchWon = false
private var dropPhase = false
private var aircraftX = -80.0
private var aircraftY = 125.0
private var landingX = 500.0
private var landingY = 340.0
private var landingFlash = 0.0
private var playerX = 500.0
private var playerY = 385.0
private var aimX = 650.0
private var aimY = 350.0
private var playerEnergy = 100
private var playerMaxEnergy = 100
private var matchSeconds = 0.0
private var zoneRadius = 340.0
private var tagCooldown = 0.0
private var shotCooldown = 0.0
private var powerCooldown = 0.0
private var zoneDrainCooldown = 0.0
private var airdropMade = false
private var rewardGiven = false
private var matchTags = 0
private var matchCoins = 0
@volatile private var remotePlayer: RemotePlayer = null
@volatile private var lanChannel: LanChannel = null
@volatile private var lanServer: ServerSocket = null
@volatile private var lanStatus = "LAN: not connected"
private var hostAcceptThread: Thread = null
private val bots = ArrayBuffer[Bot]()
private val loots = ArrayBuffer[Loot]()
private val bloomBolts = ArrayBuffer[BloomBolt]()
private val heldKeys = mutable.Set[Int]()
private val buttons = ArrayBuffer[UiButton]()
private var frame: JFrame = null
private var canvas: GamePanel = null
private var lastLanSend = 0.0
private def sha256(text: String): String = {
val bytes = MessageDigest.getInstance("SHA-256").digest(text.getBytes("UTF-8"))
bytes.map(b => "%02x".format(b & 0xff)).mkString
}
private def safeId(id: String): String = id.trim.toLowerCase(Locale.ROOT)
private def validId(id: String): Boolean = id != null && id.matches("[A-Za-z0-9_-]{3,18}")
private def profileNode(id: String) = Profiles.node(safeId(id))
private def savePlayer(p: PlayerData): Unit = {
if (p != null) {
val n = profileNode(p.id)
n.put("exists", "1")
n.put("id", p.id)
n.put("passwordHash", p.passwordHash)
n.putInt("coins", math.max(0, p.coins))
n.putInt("diamonds", math.max(0, p.diamonds))
n.putInt("level", math.max(1, math.min(20, p.level)))
n.putInt("xp", math.max(0, p.xp))
n.putInt("avatar", math.max(0, math.min(avatarNames.length - 1, p.avatar)))
n.put("bundle", p.bundle)
n.put("powerStyle", p.powerStyle)
n.putInt("tags", math.max(0, p.tags))
n.putInt("wins", math.max(0, p.wins))
try { n.flush(); AppRoot.flush() } catch { case _: Throwable => () }
}
}
private def loadPlayer(id: String): PlayerData = {
val n = profileNode(id)
new PlayerData(n.get("id", id), n.get("passwordHash", ""),
math.max(0, n.getInt("coins", 500)), math.max(0, n.getInt("diamonds", 30)),
math.max(1, math.min(20, n.getInt("level", 1))), math.max(0, n.getInt("xp", 0)),
math.max(0, math.min(avatarNames.length - 1, n.getInt("avatar", 0))),
n.get("bundle", bundles(0)), n.get("powerStyle", powerNames(0)),
math.max(0, n.getInt("tags", 0)), math.max(0, n.getInt("wins", 0)))
}
private def showPasswordDialog(title: String): Option[String] = {
val field = new JPasswordField(18)
val result = JOptionPane.showConfirmDialog(frame, field, title, JOptionPane.OK_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE)
if (result == JOptionPane.OK_OPTION) Some(new String(field.getPassword)) else None
}
private def loginDialog(): Unit = {
val id = JOptionPane.showInputDialog(frame, "Enter your Player ID:", "Player Login", JOptionPane.QUESTION_MESSAGE)
if (id == null) return
val clean = id.trim
if (!validId(clean)) { message("Use 3-18 letters, numbers, _ or - for your ID."); return }
showPasswordDialog("Enter password for " + clean).foreach { pass =>
val supplied = sha256("FIGHTINGBLOOM|" + safeId(clean) + "|" + pass)
val isAdminLogin = AdminPasswordHashes.get(safeId(clean)).contains(supplied)
val node = profileNode(if (isAdminLogin) AdminId else clean)
val stored = node.get("passwordHash", "")
if (isAdminLogin || (node.get("exists", "") == "1" && stored == supplied)) {
if (isAdminLogin && node.get("exists", "") != "1") {
node.put("exists", "1"); node.put("id", AdminId); node.put("passwordHash", AdminPasswordHashes("bixubuxshah25"))
node.putInt("coins", 1000); node.putInt("diamonds", 500); node.putInt("level", 1)
node.putInt("xp", 0); node.putInt("avatar", 0); node.put("bundle", bundles(0))
node.put("powerStyle", powerNames(0)); node.putInt("tags", 0); node.putInt("wins", 0)
node.putInt("costume", 0); node.putInt("blaster", 0)
}
current = loadPlayer(if (isAdminLogin) AdminId else clean)
adminMode = isAdminLogin
selectedCostume = math.max(0, math.min(costumeNames.length - 1, profileNode(current.id).getInt("costume", 0)))
selectedBlaster = math.max(0, math.min(blasterNames.length - 1, profileNode(current.id).getInt("blaster", 0)))
AppRoot.put("lastId", current.id)
try { AppRoot.flush() } catch { case _: Throwable => () }
screen = "menu"
statusMessage = "Welcome, " + current.id + "!"
if (canvas != null) canvas.requestFocusInWindow()
} else {
message("Login failed. Check your ID and password, or create an account first.")
}
}
}
private def createAccountDialog(): Unit = {
val id = JOptionPane.showInputDialog(frame, "Choose a Player ID (3-18 letters/numbers):", "Create Account", JOptionPane.QUESTION_MESSAGE)
if (id == null) return
val clean = id.trim
if (!validId(clean)) { message("Use 3-18 letters, numbers, _ or -."); return }
if (AdminPasswordHashes.contains(safeId(clean))) { message("That ID is reserved for the game administrator."); return }
val node = profileNode(clean)
if (node.get("exists", "") == "1") { message("That Player ID already exists on this computer."); return }
showPasswordDialog("Create a password (at least 4 characters)").foreach { pass =>
if (pass.length < 4) message("Password must have at least 4 characters.")
else {
val p = new PlayerData(clean, sha256("FIGHTINGBLOOM|" + safeId(clean) + "|" + pass),
500, 30, 1, 0, 0, bundles(0), powerNames(0), 0, 0)
savePlayer(p)
current = p
adminMode = false
selectedCostume = 0
selectedBlaster = 0
profileNode(clean).putInt("costume", 0)
profileNode(clean).putInt("blaster", 0)
AppRoot.put("lastId", clean)
screen = "menu"
statusMessage = "Account created! Your profile is saved on this computer."
}
}
}
private def message(s: String): Unit = JOptionPane.showMessageDialog(frame, s, AppTitle, JOptionPane.INFORMATION_MESSAGE)
private def logout(): Unit = {
savePlayer(current)
current = null
adminMode = false
screen = "login"
statusMessage = "Logged out. Your local profile is saved."
closeLan()
}
private def addXp(amount: Int): Unit = {
if (current == null) return
current.xp += amount
while (current.level < 20 && current.xp >= current.xpTarget) {
current.xp -= current.xpTarget
current.level += 1
current.coins += 75
statusMessage = "LEVEL UP! You reached level " + current.level + " (+75 coins)."
}
if (current.level >= 20) current.xp = math.min(current.xp, current.xpTarget)
savePlayer(current)
}
private def startMatch(mode: String): Unit = {
if (current == null) { screen = "login"; return }
gameMode = mode
gameRunning = true
matchFinished = false
matchWon = false
dropPhase = mode == "BR"
aircraftX = -85.0; aircraftY = 145.0
landingX = 500.0; landingY = 340.0; landingFlash = 0.0
playerX = 490.0; playerY = if (dropPhase) 145.0 else 365.0
aimX = 640.0; aimY = 350.0
playerEnergy = 100; playerMaxEnergy = 100
matchSeconds = 0.0; zoneRadius = 340.0
tagCooldown = 0.0; shotCooldown = 0.0; powerCooldown = 0.0; zoneDrainCooldown = 0.0
airdropMade = false; rewardGiven = false; matchTags = 0; matchCoins = 0
bots.clear(); loots.clear(); bloomBolts.clear()
val count = if (mode == "BR") 8 else if (mode == "CS") 3 else 4
var i = 0
while (i < count) {
val bx = 145.0 + ((i * 137) % 700)
val by = 180.0 + ((i * 91) % 360)
val tagsNeeded = if (mode == "TRAINING") 1 else 2
bots += new Bot(bx, by, tagsNeeded, tagsNeeded,
"Bloom Bot " + (i + 1), mode == "TRAINING")
i += 1
}
loots += new Loot(250, 275, "COINS", 35)
loots += new Loot(740, 250, "STAR", 18)
loots += new Loot(620, 510, "ENERGY", 25)
loots += new Loot(350, 490, "COINS", 25)
loots += new Loot(805, 430, "DIAMOND", 2)
screen = "battle"
statusMessage = if (dropPhase) "You are aboard the Bloom Airship. Choose a map point, then press ENTER to parachute down." else "Training ground ready. Move with arrows / W-A-D; press S to fire a Bloom energy orb."
if (canvas != null) canvas.requestFocusInWindow()
}
private def finishMatch(won: Boolean): Unit = {
if (matchFinished) return
matchFinished = true
gameRunning = false
matchWon = won
if (!rewardGiven && current != null) {
val reward = if (won) 180 else 35
current.coins += reward + matchCoins
current.tags += matchTags
if (won) current.wins += 1
addXp((if (won) 120 else 45) + matchTags * 18)
rewardGiven = true
savePlayer(current)
}
statusMessage = if (won) "VICTORY! The Bloom Arena is yours." else "MATCH OVER. Train and return stronger."
}
private def usePower(): Unit = {
if (screen != "battle" || matchFinished || dropPhase || powerCooldown > 0.0) return
val power = if (current == null) powerNames(0) else current.powerStyle match {
case "Starter Petals" => powerNames(0)
case "Crystal Bloom" => powerNames(1)
case "Solar Champion" => powerNames(3)
case _ => powerNames(2)
}
power match {
case "Bloom Dash" => playerX = clamp(playerX + (aimX - playerX) * 0.45, 35, 965); playerY = clamp(playerY + (aimY - playerY) * 0.45, 70, 615); statusMessage = "BLOOM DASH!"
case "Petal Shield" => playerEnergy = math.min(playerMaxEnergy, playerEnergy + 20); statusMessage = "PETAL SHIELD restored your energy."
case "Healing Bloom" => playerEnergy = math.min(playerMaxEnergy, playerEnergy + 32); statusMessage = "HEALING BLOOM restored energy."
case _ =>
bots.filter(_.alive).foreach(b => if (distance(playerX, playerY, b.x, b.y) < 260) tagBot(b, 1))
statusMessage = "NATURE NOVA! Nearby Bloom Bots received a light-tag pulse."
}
powerCooldown = 8.0
}
private def tagBot(bot: Bot, tags: Int): Unit = {
if (bot != null && bot.alive) {
bot.tagsLeft -= tags
if (bot.tagsLeft <= 0) {
bot.alive = false
matchTags += 1
matchCoins += 12
addXp(18)
statusMessage = "BLOOM BOT TAGGED! +12 coins"
} else {
statusMessage = "One light-tag recorded. Tag again to score!"
}
}
}
private def tagBotAt(x: Double, y: Double): Unit = {
if (screen != "battle" || matchFinished || dropPhase || tagCooldown > 0.0) return
val candidate = bots.filter(_.alive).sortBy(b => distance(x, y, b.x, b.y)).headOption
candidate match {
case Some(bot) if distance(x, y, bot.x, bot.y) < 48 && distance(playerX, playerY, bot.x, bot.y) < 210 => tagBot(bot, 1)
case _ => statusMessage = "Move closer, then click a Bloom Bot to light-tag it."
}
tagCooldown = 0.45
aimX = x; aimY = y
}
private def shootBloomBolt(): Unit = {
if (screen != "battle" || matchFinished || dropPhase || shotCooldown > 0.0) return
var dx = aimX - playerX
var dy = aimY - playerY
var length = math.sqrt(dx * dx + dy * dy)
if (length < 1.0) { dx = 1.0; dy = 0.0; length = 1.0 }
val speed = 12.0
bloomBolts += new BloomBolt(playerX, playerY - 8.0, dx / length * speed, dy / length * speed, 1.4)
shotCooldown = 0.28
statusMessage = "BLOOM PULSE! A magical energy orb is on its way."
}
private def deployFromAirship(): Unit = {
if (screen == "battle" && dropPhase) {
dropPhase = false
playerX = clamp(landingX, 35, 965)
playerY = clamp(landingY, 80, 615)
landingFlash = 1.6
statusMessage = "Parachute landing complete! Collect glowing loot with E."
}
}
private def collectNearby(): Unit = {
val item = loots.find(l => !l.taken && distance(playerX, playerY, l.x, l.y) < 48)
item.foreach { l =>
l.taken = true
l.kind match {
case "COINS" => matchCoins += l.amount; statusMessage = "+" + l.amount + " COINS (added after match)"
case "DIAMOND" => current.diamonds += l.amount; statusMessage = "+" + l.amount + " DIAMONDS"
case "ENERGY" => playerEnergy = math.min(playerMaxEnergy, playerEnergy + l.amount); statusMessage = "ENERGY RESTORED"
case "STAR" => matchCoins += l.amount; statusMessage = "STAR SHARDS COLLECTED"
case _ => ()
}
savePlayer(current)
}
}
private def updateGame(): Unit = {
if (screen != "battle" || !gameRunning || matchFinished) return
val dt = 0.033
matchSeconds += dt
if (tagCooldown > 0) tagCooldown = math.max(0.0, tagCooldown - dt)
if (shotCooldown > 0) shotCooldown = math.max(0.0, shotCooldown - dt)
if (powerCooldown > 0) powerCooldown = math.max(0.0, powerCooldown - dt)
if (zoneDrainCooldown > 0) zoneDrainCooldown = math.max(0.0, zoneDrainCooldown - dt)
if (landingFlash > 0) landingFlash = math.max(0.0, landingFlash - dt)
if (dropPhase) {
aircraftX += 5.0
if (aircraftX > 1080.0) aircraftX = -100.0
} else {
var dx = 0.0; var dy = 0.0
// S is reserved for shooting; use the arrow keys for full movement and W/A/D as alternatives.
if (heldKeys.contains(KeyEvent.VK_W) || heldKeys.contains(KeyEvent.VK_UP)) dy -= 1
if (heldKeys.contains(KeyEvent.VK_DOWN)) dy += 1
if (heldKeys.contains(KeyEvent.VK_A) || heldKeys.contains(KeyEvent.VK_LEFT)) dx -= 1
if (heldKeys.contains(KeyEvent.VK_D) || heldKeys.contains(KeyEvent.VK_RIGHT)) dx += 1
val mag = math.max(1.0, math.sqrt(dx * dx + dy * dy))
if (dx != 0 || dy != 0) { playerX = clamp(playerX + dx / mag * 4.0, 32, 968); playerY = clamp(playerY + dy / mag * 4.0, 72, 618) }
// Move magical Bloom energy orbs and register non-realistic light-tag hits.
bloomBolts.foreach { bolt =>
if (bolt.active) {
bolt.x += bolt.vx
bolt.y += bolt.vy
bolt.life -= dt
val hit = bots.find(b => b.alive && distance(bolt.x, bolt.y, b.x, b.y) < 27.0)
hit match {
case Some(bot) => tagBot(bot, 1); bolt.active = false
case None => if (bolt.life <= 0.0 || bolt.x < 0 || bolt.x > 1000 || bolt.y < 65 || bolt.y > 665) bolt.active = false
}
}
}
val liveBolts = bloomBolts.filter(_.active)
bloomBolts.clear()
bloomBolts ++= liveBolts
if (gameMode == "BR") zoneRadius = math.max(115.0, 340.0 - matchSeconds * 1.2)
if (gameMode == "BR" && matchSeconds > 24.0 && !airdropMade) {
loots += new Loot(500, 330, "DIAMOND", 5)
airdropMade = true
statusMessage = "A BLOOM DROP has arrived near the arena center!"
}
// Bloom Bots roam gently around the arena in a non-contact tag challenge.
bots.filter(b => b.alive && !b.trainingDummy).zipWithIndex.foreach { pair =>
val bot = pair._1
val phase = matchSeconds * 0.55 + pair._2 * 1.7
bot.x = clamp(bot.x + math.sin(phase) * 0.55, 55, 945)
bot.y = clamp(bot.y + math.cos(phase * 0.8) * 0.45, 85, 615)
}
if (gameMode == "BR" && distance(playerX, playerY, 500, 340) > zoneRadius && zoneDrainCooldown <= 0.0) {
playerEnergy -= 1
zoneDrainCooldown = 1.0
}
if (gameMode == "TRAINING") playerEnergy = math.min(playerMaxEnergy, playerEnergy + 1)
if (playerEnergy <= 0) finishMatch(false)
if (bots.forall(b => !b.alive)) finishMatch(true)
// A decorative transport glides overhead until the player chooses to land.
if (matchSeconds > 40) matchSeconds = 0
}
lastLanSend += dt
if (lastLanSend >= 0.12) { sendLanState(); lastLanSend = 0.0 }
}
private def clamp(v: Double, lo: Double, hi: Double): Double = math.max(lo, math.min(hi, v))
private def distance(x1: Double, y1: Double, x2: Double, y2: Double): Double = {
val dx = x1 - x2; val dy = y1 - y2; math.sqrt(dx * dx + dy * dy)
}
private class LanChannel(sock: Socket) {
private val reader = new BufferedReader(new InputStreamReader(sock.getInputStream, "UTF-8"))
private val writer = new PrintWriter(sock.getOutputStream, true)
@volatile private var remote: RemotePlayer = null
@volatile private var closed = false
private val readThread = new Thread(new Runnable {
override def run(): Unit = {
try {
var line: String = null
while (!closed && { line = reader.readLine(); line != null }) {
val p = line.split("\\|", 6)
if (p.length == 6 && p(0) == "P") {
val x = Try(p(2).toDouble).getOrElse(500.0)
val y = Try(p(3).toDouble).getOrElse(350.0)
val av = Try(p(4).toInt).getOrElse(0)
val energy = Try(p(5).toInt).getOrElse(100)
remote = RemotePlayer(p(1).take(18), x, y, math.max(0, math.min(3, av)), math.max(0, math.min(100, energy)))
}
}
} catch { case _: Throwable => () }
finally { close() }
}
}, "FightingBloom-LAN-Reader")
readThread.setDaemon(true)
readThread.start()
def send(line: String): Unit = synchronized { if (!closed) { writer.println(line); if (writer.checkError()) close() } }
def latest: Option[RemotePlayer] = Option(remote)
def close(): Unit = { if (!closed) { closed = true; try { sock.close() } catch { case _: Throwable => () } } }
}
private def hostLan(): Unit = {
try {
val input = JOptionPane.showInputDialog(frame, "Port for your Wi-Fi room:", "5055")
if (input == null) return
val port = Try(input.trim.toInt).getOrElse(5055)
if (port < 1024 || port > 65535) { message("Choose a port from 1024 to 65535."); return }
closeLan()
val server = new ServerSocket(port)
lanServer = server
lanStatus = "HOSTING on " + InetAddress.getLocalHost.getHostAddress + ":" + port + " (waiting for 1 player)"
hostAcceptThread = new Thread(new Runnable {
override def run(): Unit = {
try {
val socket = server.accept()
lanChannel = new LanChannel(socket)
lanStatus = "LAN connected: 1 player"
} catch { case _: Throwable => lanStatus = "LAN host closed" }
}
}, "FightingBloom-LAN-Host")
hostAcceptThread.setDaemon(true); hostAcceptThread.start()
statusMessage = lanStatus
message("Room created! Share this IP and port with the other player:\n" + lanStatus + "\nBoth computers must be on the same Wi-Fi. Allow the app through Windows Firewall if asked.")
} catch { case e: Throwable => message("Could not host LAN room: " + e.getMessage) }
}
private def joinLan(): Unit = {
val ip = JOptionPane.showInputDialog(frame, "Enter the host computer's Wi-Fi IP address:", "192.168.1.10")
if (ip == null || ip.trim.isEmpty) return
val portText = JOptionPane.showInputDialog(frame, "Enter room port:", "5055")
if (portText == null) return
val port = Try(portText.trim.toInt).getOrElse(5055)
try {
closeLan()
val socket = new Socket()
socket.connect(new InetSocketAddress(ip.trim, port), 4000)
lanChannel = new LanChannel(socket)
lanStatus = "CONNECTED to " + ip.trim + ":" + port
statusMessage = lanStatus
message("Connected! Start a match on both computers to see each other's player marker.")
} catch { case e: Throwable => message("Could not connect. Check the IP, port, Wi-Fi and Firewall.\n" + e.getMessage) }
}
private def sendLanState(): Unit = {
if (lanChannel != null && current != null && screen == "battle") {
val id = current.id.replace("|", "_")
lanChannel.send("P|" + id + "|" + playerX + "|" + playerY + "|" + current.avatar + "|" + playerEnergy)
remotePlayer = lanChannel.latest.orNull
if (remotePlayer != null) lanStatus = "LAN player: " + remotePlayer.id
}
}
private def closeLan(): Unit = {
if (lanChannel != null) { lanChannel.close(); lanChannel = null }
if (lanServer != null) { try { lanServer.close() } catch { case _: Throwable => () }; lanServer = null }
remotePlayer = null
lanStatus = "LAN: not connected"
}
private def adminSearch(): Unit = {
if (!adminMode) { message("Admin access required."); return }
val query = JOptionPane.showInputDialog(frame, "Search a Player ID:", "Admin Player Search", JOptionPane.QUESTION_MESSAGE)
if (query == null || query.trim.isEmpty) return
try {
val children = Profiles.childrenNames()
val found = children.toSeq.map(n => Profiles.node(n)).find { n =>
n.get("id", "").equalsIgnoreCase(query.trim) || n.get("id", "").toLowerCase(Locale.ROOT).contains(query.trim.toLowerCase(Locale.ROOT))
}
found match {
case Some(n) =>
val playerId = n.get("id", "Unknown")
val amountText = JOptionPane.showInputDialog(frame,
"Player: " + playerId + "\nCoins: " + n.getInt("coins", 0) + " Diamonds: " + n.getInt("diamonds", 0) +
"\nEnter diamonds to add (0-100000):", "100")
if (amountText != null) {
val amount = Try(amountText.trim.toInt).getOrElse(-1)
if (amount < 0 || amount > 100000) message("Enter a number from 0 to 100000.")
else {
n.putInt("diamonds", math.min(2000000000L, n.getInt("diamonds", 0).toLong + amount.toLong).toInt)
try { n.flush() } catch { case _: Throwable => () }
message("Added " + amount + " diamonds to " + playerId + ".")
if (current != null && current.id.equalsIgnoreCase(playerId)) current.diamonds = n.getInt("diamonds", current.diamonds)
}
}
case None => message("No matching local player ID found. Player profiles are saved on this computer only.")
}
} catch { case e: Throwable => message("Admin search failed: " + e.getMessage) }
}
private def buyBundle(index: Int): Unit = {
if (current == null || index < 0 || index >= bundles.length) return
if (current.bundle == bundles(index)) { message("This bundle is already equipped."); return }
if (current.coins >= bundleCoins(index) && current.diamonds >= bundleDiamonds(index)) {
current.coins -= bundleCoins(index); current.diamonds -= bundleDiamonds(index)
current.bundle = bundles(index); selectedBundle = index; savePlayer(current)
statusMessage = bundles(index) + " equipped!"
} else message("Not enough coins or diamonds for this bundle.")
}
private def buyPowerStyle(index: Int): Unit = {
if (current == null || index < 0 || index >= powerNames.length) return
val ownedKey = "powerOwned_" + index
val node = profileNode(current.id)
val owned = index == 0 || node.getBoolean(ownedKey, false)
if (owned) { current.powerStyle = powerNames(index); savePlayer(current); statusMessage = powerNames(index) + " equipped." }
else if (current.coins >= powerCoins(index) && current.diamonds >= powerDiamonds(index)) {
current.coins -= powerCoins(index); current.diamonds -= powerDiamonds(index)
node.putBoolean(ownedKey, true); current.powerStyle = powerNames(index); savePlayer(current)
statusMessage = powerNames(index) + " unlocked and equipped!"
} else message("Not enough coins or diamonds.")
}
private def drawBackground(g: Graphics2D, w: Int, h: Int): Unit = {
g.setPaint(new GradientPaint(0, 0, new Color(8, 24, 37), 0, h, new Color(14, 73, 64)))
g.fillRect(0, 0, w, h)
var i = 0
while (i < 18) {
val x = (i * 67 + 23) % w
val y = (i * 43 + 31) % h
g.setColor(new Color(91, 255, 183, 18))
g.fill(new Ellipse2D.Double(x, y, 40 + (i % 4) * 14, 40 + (i % 4) * 14))
i += 1
}
g.setColor(new Color(255, 255, 255, 12))
for (x <- 0 until w by 70) g.drawLine(x, 0, x - 90, h)
}
private def drawTitle(g: Graphics2D, w: Int, y: Int): Unit = {
g.setFont(new Font("SansSerif", Font.BOLD, 34)); g.setColor(Mint)
val a = "SPRING SEASON EVENT 6"
g.drawString(a, (w - g.getFontMetrics.stringWidth(a)) / 2, y)
g.setFont(new Font("SansSerif", Font.BOLD, 45)); g.setColor(Gold)
val b = "FIGHTING BLOOM"
g.drawString(b, (w - g.getFontMetrics.stringWidth(b)) / 2, y + 48)
}
private def drawText(g: Graphics2D, s: String, x: Int, y: Int, size: Int, color: Color, bold: Boolean = false): Unit = {
g.setFont(new Font("SansSerif", if (bold) Font.BOLD else Font.PLAIN, size)); g.setColor(color); g.drawString(s, x, y)
}
private def drawCentered(g: Graphics2D, s: String, x: Int, y: Int, size: Int, color: Color, bold: Boolean = false): Unit = {
g.setFont(new Font("SansSerif", if (bold) Font.BOLD else Font.PLAIN, size)); g.setColor(color)
g.drawString(s, x - g.getFontMetrics.stringWidth(s) / 2, y)
}
private def drawButton(g: Graphics2D, text: String, x: Int, y: Int, w: Int, h: Int,
action: () => Unit, enabled: Boolean = true): Unit = {
val rect = new Rectangle(x, y, w, h)
buttons += UiButton(rect, text, action, enabled)
g.setColor(if (enabled) new Color(26, 104, 85, 235) else new Color(62, 76, 75, 200))
g.fill(new RoundRectangle2D.Double(x, y, w, h, 18, 18))
g.setStroke(new BasicStroke(2f)); g.setColor(if (enabled) Mint else Muted)
g.draw(new RoundRectangle2D.Double(x, y, w, h, 18, 18))
g.setFont(new Font("SansSerif", Font.BOLD, 15)); g.setColor(if (enabled) White else Muted)
g.drawString(text, x + (w - g.getFontMetrics.stringWidth(text)) / 2, y + h / 2 + 5)
}
private def drawCard(g: Graphics2D, x: Int, y: Int, w: Int, h: Int, title: String, detail: String): Unit = {
g.setColor(PanelColor); g.fill(new RoundRectangle2D.Double(x, y, w, h, 20, 20))
g.setColor(new Color(104, 255, 198, 130)); g.draw(new RoundRectangle2D.Double(x, y, w, h, 20, 20))
drawText(g, title, x + 18, y + 31, 17, Gold, true)
detail.split("\\n").zipWithIndex.foreach { pair => drawText(g, pair._1, x + 18, y + 59 + pair._2 * 17, 12, White) }
}
private def drawProfileBar(g: Graphics2D): Unit = {
if (current == null) return
g.setColor(new Color(4, 17, 26, 220)); g.fillRoundRect(18, 16, 964, 44, 16, 16)
drawText(g, current.id + " ? LV " + current.level, 32, 44, 15, White, true)
drawText(g, "COINS " + current.coins, 400, 44, 14, Gold, true)
drawText(g, "DIAMONDS " + current.diamonds, 560, 44, 14, new Color(137, 225, 255), true)
drawText(g, "WINS " + current.wins + " TAGS " + current.tags, 760, 44, 13, Mint, true)
}
private def drawLogin(g: Graphics2D, w: Int): Unit = {
drawTitle(g, w, 155)
drawCentered(g, "A NEW ARCADE BATTLE BEGINS", w / 2, 265, 17, White, true)
drawCentered(g, "Create a local profile or log in to continue.", w / 2, 294, 14, Muted)
val last = AppRoot.get("lastId", "")
if (last.nonEmpty) drawCentered(g, "Last Player ID on this computer: " + last, w / 2, 330, 13, Mint)
drawButton(g, "LOGIN", 350, 365, 300, 52, () => loginDialog())
drawButton(g, "CREATE ACCOUNT", 350, 430, 300, 52, () => createAccountDialog())
drawCentered(g, "Local profiles are saved on this computer.", w / 2, 520, 12, Muted)
drawCentered(g, "Admin access is managed by the local game owner.", w / 2, 548, 12, new Color(255, 216, 105))
}
private def drawMenu(g: Graphics2D, w: Int): Unit = {
drawProfileBar(g); drawTitle(g, w, 132)
drawCentered(g, "Choose your next move, " + current.id + "!", w / 2, 235, 16, White, true)
drawButton(g, "SINGLE PLAYER", 95, 280, 250, 55, () => screen = "modes")
drawButton(g, "MULTIPLAYER", 375, 280, 250, 55, () => screen = "multiplayer")
drawButton(g, "AVATAR + BUNDLE", 655, 280, 250, 55, () => screen = "avatar")
drawButton(g, "POWER STYLES", 95, 350, 250, 55, () => screen = "powers")
drawButton(g, "BUNDLE SHOP", 375, 350, 250, 55, () => screen = "shop")
drawButton(g, "LEVEL REWARDS", 655, 350, 250, 55, () => screen = "levels")
drawButton(g, "COSTUME + BLASTER CUSTOM", 65, 430, 270, 50, () => screen = "customize")
if (adminMode) drawButton(g, "ADMIN PANEL", 365, 430, 220, 50, () => screen = "admin")
drawButton(g, "LOG OUT", 635, 430, 220, 50, () => logout())
drawCentered(g, lanStatus, w / 2, 535, 12, Mint)
drawCentered(g, statusMessage, w / 2, 570, 13, Gold)
}
private def drawModes(g: Graphics2D, w: Int): Unit = {
drawProfileBar(g); drawCentered(g, "SELECT YOUR BATTLE MODE", w / 2, 145, 30, Gold, true)
drawCard(g, 90, 195, 250, 185, "BLOOM ROYALE", "Drop into Bloom Island.\nCollect star shards and tag Bloom Bots.")
drawCard(g, 375, 195, 250, 185, "TAG SQUAD", "A quick team-tag\nround against Bloom Bots.")
drawCard(g, 660, 195, 250, 185, "TRAINING GROUND", "Practice movement and\nBloom Pulse timing in a safe arena.")
drawButton(g, "START BLOOM ROYALE", 90, 405, 250, 54, () => startMatch("BR"))
drawButton(g, "START TAG SQUAD", 375, 405, 250, 54, () => startMatch("CS"))
drawButton(g, "START TRAINING", 660, 405, 250, 54, () => startMatch("TRAINING"))
drawButton(g, "BACK (B)", 375, 510, 250, 48, () => screen = "menu")
drawCentered(g, "ARROWS / W-A-D move ? S shoot Bloom orbs ? E loot ? Q power ? B back", w / 2, 590, 13, Muted)
}
private def drawMultiplayer(g: Graphics2D, w: Int): Unit = {
drawProfileBar(g)
drawCentered(g, "WI-FI MULTIPLAYER", w / 2, 150, 30, Gold, true)
drawCard(g, 180, 205, 640, 115, "PLAY OVER YOUR LOCAL WI-FI", "One computer hosts a room. One other computer joins using the host IP and port.\nBoth computers must be on the same Wi-Fi network.")
drawButton(g, "HOST A ROOM", 220, 370, 250, 58, () => hostLan())
drawButton(g, "JOIN A ROOM", 530, 370, 250, 58, () => joinLan())
drawCentered(g, lanStatus, w / 2, 475, 14, Mint, true)
drawCentered(g, "This demo shares player markers; match progress stays local on each PC.", w / 2, 510, 12, Muted)
drawButton(g, "BACK TO MENU", 385, 565, 230, 45, () => screen = "menu")
}
private def drawAvatar(g: Graphics2D, x: Int, y: Int, index: Int, scale: Int): Unit = {
val c = index match { case 0 => new Color(111, 247, 173); case 1 => new Color(113, 199, 255); case 2 => new Color(206, 193, 255); case _ => new Color(255, 202, 103) }
g.setColor(new Color(0, 0, 0, 70)); g.fillOval(x - scale, y + scale, scale * 2, scale / 2)
g.setColor(c); g.fillOval(x - scale / 2, y - scale, scale, scale)
g.setColor(new Color(29, 56, 67)); g.fillRoundRect(x - scale / 2, y, scale, scale, scale / 3, scale / 3)
g.setColor(new Color(255, 255, 255, 200)); g.drawOval(x - scale / 2, y - scale, scale, scale)
}
private def drawPlayerAvatar(g: Graphics2D, x: Int, y: Int, index: Int, scale: Int): Unit = {
drawAvatar(g, x, y, index, scale)
val costumeColor = selectedCostume match {
case 0 => new Color(95, 245, 170)
case 1 => new Color(255, 135, 185)
case 2 => new Color(140, 200, 255)
case _ => new Color(255, 205, 95)
}
g.setColor(costumeColor)
g.fillRoundRect(x - scale / 4, y + scale / 3, scale / 2, scale / 3, 8, 8)
}
private def drawAvatarScreen(g: Graphics2D, w: Int): Unit = {
drawProfileBar(g); drawCentered(g, "CHOOSE YOUR AVATAR", w / 2, 140, 29, Gold, true)
var i = 0
while (i < avatarNames.length) {
val x = 145 + i * 235
drawCard(g, x - 82, 190, 164, 220, avatarNames(i), if (current.avatar == i) "CURRENT AVATAR" else "Select this hero")
drawAvatar(g, x, 300, i, 46)
val avatarIndex = i
if (current.avatar == avatarIndex) drawCentered(g, "EQUIPPED", x, 385, 12, Mint, true)
else drawButton(g, "SELECT", x - 65, 425, 130, 42, () => { current.avatar = avatarIndex; savePlayer(current); statusMessage = avatarNames(avatarIndex) + " selected." })
i += 1
}
drawCard(g, 205, 490, 590, 68, "CURRENT BUNDLE: " + current.bundle, "Change bundles in the Bundle Shop.")
drawButton(g, "BACK (B)", 385, 585, 230, 44, () => screen = "menu")
}
private def drawShop(g: Graphics2D, w: Int): Unit = {
drawProfileBar(g); drawCentered(g, "BUNDLE SHOP", w / 2, 145, 30, Gold, true)
drawCard(g, 80, 200, 260, 180, "STARTER PETALS", "Free starter style\nIncluded with every account.")
drawCard(g, 370, 200, 260, 180, "LEAF RANGER", "Green forest outfit\n150 coins")
drawCard(g, 660, 200, 260, 180, "CRYSTAL BLOOM", "Crystal aura outfit\n260 coins + 25 diamonds")
drawButton(g, "EQUIP STARTER", 80, 405, 260, 50, () => { current.bundle = bundles(0); savePlayer(current); statusMessage = "Starter Petals equipped." })
drawButton(g, "BUY LEAF RANGER", 370, 405, 260, 50, () => buyBundle(1))
drawButton(g, "BUY CRYSTAL BLOOM", 660, 405, 260, 50, () => buyBundle(2))
drawCard(g, 300, 475, 400, 72, "SOLAR CHAMPION", "420 coins + 50 diamonds")
drawButton(g, "BUY SOLAR", 385, 560, 230, 42, () => buyBundle(3))
drawButton(g, "BACK (B)", 30, 585, 180, 42, () => screen = "menu")
}
private def drawPowerStyles(g: Graphics2D, w: Int): Unit = {
drawProfileBar(g); drawCentered(g, "POWER STYLE CUSTOMIZATION", w / 2, 145, 29, Gold, true)
val n = profileNode(current.id)
var i = 0
while (i < powerNames.length) {
val x = 85 + i * 225
val owned = i == 0 || n.getBoolean("powerOwned_" + i, false)
val price = if (powerDiamonds(i) > 0) powerDiamonds(i) + " diamonds" else powerCoins(i) + " coins"
drawCard(g, x, 220, 190, 180, powerNames(i), if (owned) "OWNED" else "Cost: " + price)
val powerColor = i match {
case 0 => new Color(128, 255, 203)
case 1 => new Color(94, 217, 153)
case 2 => new Color(255, 196, 90)
case _ => new Color(159, 199, 255)
}
g.setColor(powerColor)
g.fillOval(x + 65, 280, 60, 60)
drawCentered(g, if (current.powerStyle == powerNames(i)) "EQUIPPED" else if (owned) "EQUIP" else "UNLOCK", x + 95, 370, 12, Mint, true)
val powerIndex = i
drawButton(g, if (current.powerStyle == powerNames(powerIndex)) "EQUIPPED" else if (owned) "EQUIP" else "BUY / EQUIP", x + 8, 425, 174, 44, () => buyPowerStyle(powerIndex))
i += 1
}
drawButton(g, "BACK (B)", 385, 535, 230, 44, () => screen = "menu")
}
private def drawLevels(g: Graphics2D, w: Int): Unit = {
drawProfileBar(g); drawCentered(g, "BLOOM LEVEL TRACK", w / 2, 140, 30, Gold, true)
val xpNeed = current.xpTarget
drawText(g, "LEVEL " + current.level + " / 20", 190, 220, 20, White, true)
g.setColor(new Color(40, 76, 76)); g.fillRoundRect(190, 245, 620, 26, 14, 14)
g.setColor(Mint); g.fillRoundRect(190, 245, (620.0 * current.xp / math.max(1, xpNeed)).toInt, 26, 14, 14)
drawCentered(g, current.xp + " / " + xpNeed + " XP", 500, 290, 15, White, true)
drawCard(g, 180, 330, 300, 120, "MATCH REWARDS", "Victory: 180 coins + XP\nMatch complete: 35 coins + XP")
drawCard(g, 520, 330, 300, 120, "LEVEL REWARDS", "Level up: +75 coins\nMaximum level: 20")
drawButton(g, "BACK (B)", 385, 510, 230, 44, () => screen = "menu")
}
private def drawAdmin(g: Graphics2D, w: Int): Unit = {
drawProfileBar(g); drawCentered(g, "ADMIN CONTROL PANEL", w / 2, 145, 30, Pink, true)
drawCard(g, 195, 205, 610, 120, "ADMIN: BIXUBUX SHAH 25", "Search local Player IDs and grant test diamonds.\nThe profiles shown are saved on this computer only.")
drawButton(g, "SEARCH PLAYER ID", 350, 365, 300, 55, () => adminSearch())
drawButton(g, "SHOW LOCAL PLAYER COUNT", 350, 435, 300, 48, () => {
val count = Try(Profiles.childrenNames().length).getOrElse(0)
message("Local profiles on this computer: " + count)
})
drawButton(g, "BACK TO MENU", 385, 525, 230, 46, () => screen = "menu")
drawCentered(g, "Admin can also play all game modes.", w / 2, 595, 13, Mint)
}
private def drawCustomizer(g: Graphics2D, w: Int): Unit = {
drawProfileBar(g)
drawCentered(g, "COSTUME + BLOOM BLASTER CUSTOMIZER", w / 2, 135, 27, Gold, true)
drawCentered(g, "Choose a look. These are cosmetic styles, not real-world weapon designs.", w / 2, 163, 13, Muted)
val costumeColors = Array(new Color(111, 247, 173), new Color(255, 153, 192), new Color(160, 205, 255), new Color(255, 205, 105))
var i = 0
while (i < costumeNames.length) {
val x = 135 + i * 190
drawCard(g, x - 72, 195, 145, 160, costumeNames(i), if (selectedCostume == i) "EQUIPPED LOOK" else "Free cosmetic look")
drawAvatar(g, x, 275, current.avatar, 38)
g.setColor(costumeColors(i)); g.fillOval(x - 13, 304, 26, 15)
val costumeIndex = i
drawButton(g, if (selectedCostume == costumeIndex) "EQUIPPED" else "EQUIP LOOK", x - 65, 365, 130, 40, () => {
selectedCostume = costumeIndex
profileNode(current.id).putInt("costume", selectedCostume)
try { profileNode(current.id).flush() } catch { case _: Throwable => () }
statusMessage = costumeNames(selectedCostume) + " equipped."
})
i += 1
}
drawCentered(g, "BLOOM PULSE STYLE", w / 2, 455, 20, Mint, true)
i = 0
while (i < blasterNames.length) {
val x = 290 + i * 210
val styleIndex = i
drawButton(g, (if (selectedBlaster == styleIndex) "? " else "") + blasterNames(styleIndex), x - 85, 480, 170, 44, () => {
selectedBlaster = styleIndex
profileNode(current.id).putInt("blaster", selectedBlaster)
try { profileNode(current.id).flush() } catch { case _: Throwable => () }
statusMessage = blasterNames(selectedBlaster) + " visual style equipped."
})
i += 1
}
drawButton(g, "BACK (B)", 385, 575, 230, 44, () => screen = "menu")
}
private def drawTree(g: Graphics2D, x: Int, y: Int, s: Int): Unit = {
g.setColor(new Color(21, 65, 55, 140)); g.fillOval(x - s, y + s / 2, s * 2, s / 2)
g.setColor(new Color(105, 73, 47)); g.fillRoundRect(x - 5, y, 10, s, 6, 6)
g.setColor(new Color(26, 115, 76)); g.fillOval(x - s, y - s / 2, s * 2, s * 2)
g.setColor(new Color(85, 210, 130)); g.fillOval(x - s / 2, y - s, s, s)
}
private def drawMap(g: Graphics2D): Unit = {
g.setPaint(new GradientPaint(0, 65, new Color(51, 137, 101), 0, 660, new Color(24, 91, 73)))
g.fillRect(0, 65, 1000, 595)
g.setColor(new Color(174, 211, 132, 60))
for (x <- 0 until 1000 by 60) g.drawLine(x, 65, x + 90, 660)
for (y <- 100 until 660 by 60) g.drawLine(0, y, 1000, y + 12)
// Small polygon platforms give the arena a layered, perspective-style look.
val buildings = Array((125, 170, 145, 75), (690, 145, 150, 88), (760, 475, 130, 75), (160, 490, 145, 72), (430, 210, 130, 68))
buildings.foreach { b =>
val x = b._1; val y = b._2; val ww = b._3; val hh = b._4
val p1 = new Polygon(Array(x, x + ww, x + ww + 24, x + 24), Array(y, y, y + 18, y + 18), 4)
val p2 = new Polygon(Array(x + ww, x + ww + 24, x + ww + 24, x + ww), Array(y, y + 18, y + hh + 18, y + hh), 4)
g.setColor(new Color(75, 106, 91)); g.fill(p1); g.setColor(new Color(47, 79, 73)); g.fill(p2)
g.setColor(new Color(145, 187, 151)); g.fillRoundRect(x, y - 12, ww, hh, 12, 12)
g.setColor(new Color(25, 75, 67)); g.drawRoundRect(x, y - 12, ww, hh, 12, 12)
}
drawTree(g, 65, 130, 27); drawTree(g, 900, 155, 30); drawTree(g, 60, 565, 30)
drawTree(g, 925, 575, 28); drawTree(g, 560, 105, 23); drawTree(g, 350, 570, 24)
g.setColor(new Color(114, 207, 220, 120)); g.fillOval(405, 455, 180, 48)
g.setColor(new Color(196, 246, 238, 120)); g.drawOval(405, 455, 180, 48)
}
private def drawAircraft(g: Graphics2D, x: Int, y: Int): Unit = {
// A decorative fantasy airship: no real aircraft controls or simulation.
val body = new Polygon(Array(x - 48, x + 52, x + 32, x - 28), Array(y, y, y + 11, y + 11), 4)
g.setColor(new Color(228, 245, 238)); g.fill(body)
g.setColor(new Color(45, 92, 83)); g.draw(body)
g.setColor(new Color(120, 215, 190)); g.fillPolygon(new Polygon(Array(x - 10, x + 8, x + 25, x - 27), Array(y - 2, y - 30, y - 30, y - 2), 4))
g.setColor(new Color(255, 218, 115)); g.fillOval(x + 36, y + 2, 10, 7)
g.setColor(new Color(180, 245, 226, 170)); g.drawLine(x - 34, y + 13, x - 58, y + 24)
}
private def drawBattle(g: Graphics2D, w: Int, h: Int): Unit = {
drawMap(g)
if (gameMode == "BR") {
g.setColor(new Color(120, 245, 255, 45)); g.setStroke(new BasicStroke(3f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND, 1f, Array(12f, 8f), 0f))
g.drawOval((500 - zoneRadius).toInt, (340 - zoneRadius).toInt, (zoneRadius * 2).toInt, (zoneRadius * 2).toInt)
g.setStroke(new BasicStroke(1f))
}
loots.filterNot(_.taken).foreach { l =>
val c = l.kind match { case "COINS" => Gold; case "DIAMOND" => new Color(120, 220, 255); case "ENERGY" => Pink; case _ => White }
g.setColor(new Color(0, 0, 0, 65)); g.fillOval(l.x.toInt - 13, l.y.toInt + 7, 28, 12)
g.setColor(c); g.fillRoundRect(l.x.toInt - 10, l.y.toInt - 15, 20, 20, 6, 6)
g.setColor(Color.WHITE); g.drawRect(l.x.toInt - 10, l.y.toInt - 15, 20, 20)
}
bots.filter(_.alive).foreach { b =>
drawAvatar(g, b.x.toInt, b.y.toInt, 2, 23)
g.setColor(new Color(24, 65, 58)); g.fillRoundRect(b.x.toInt - 22, b.y.toInt - 42, 44, 6, 5, 5)
g.setColor(Mint); g.fillRoundRect(b.x.toInt - 22, b.y.toInt - 42, (44.0 * (b.maxTags - b.tagsLeft) / b.maxTags).toInt, 6, 5, 5)
drawCentered(g, "TAGS", b.x.toInt, b.y.toInt - 48, 9, White, true)
}
if (remotePlayer != null) {
drawAvatar(g, remotePlayer.x.toInt, remotePlayer.y.toInt, remotePlayer.avatar, 27)
drawCentered(g, remotePlayer.id, remotePlayer.x.toInt, remotePlayer.y.toInt - 42, 11, Color.WHITE, true)
}
if (!dropPhase) {
drawPlayerAvatar(g, playerX.toInt, playerY.toInt, current.avatar, 29)
drawCentered(g, current.id, playerX.toInt, playerY.toInt - 46, 12, Color.WHITE, true)
}
bloomBolts.foreach { bolt =>
val boltColor = selectedBlaster match {
case 0 => new Color(140, 255, 201)
case 1 => new Color(145, 210, 255)
case _ => new Color(255, 207, 115)
}
g.setColor(new Color(boltColor.getRed, boltColor.getGreen, boltColor.getBlue, 80))
g.fillOval(bolt.x.toInt - 12, bolt.y.toInt - 12, 24, 24)
g.setColor(boltColor); g.fillOval(bolt.x.toInt - 5, bolt.y.toInt - 5, 10, 10)
}
if (landingFlash > 0.0) {
g.setColor(new Color(255, 255, 220, 150)); g.drawOval(playerX.toInt - 28, playerY.toInt - 28, 56, 56)
g.setColor(new Color(255, 232, 156, 210))
g.fillArc(playerX.toInt - 23, playerY.toInt - 62, 46, 34, 0, 180)
g.setColor(new Color(245, 249, 235, 220))
g.drawLine(playerX.toInt - 20, playerY.toInt - 43, playerX.toInt - 8, playerY.toInt - 4)
g.drawLine(playerX.toInt + 20, playerY.toInt - 43, playerX.toInt + 8, playerY.toInt - 4)
}
// HUD
g.setColor(new Color(3, 15, 24, 220)); g.fillRoundRect(18, 12, 965, 48, 14, 14)
drawText(g, gameMode + " ? " + current.id, 32, 42, 14, White, true)
drawText(g, "ENERGY " + playerEnergy + "/100", 260, 42, 14, Mint, true)
drawText(g, "TAGS " + matchTags, 385, 42, 14, Gold, true)
drawText(g, "TIME " + matchSeconds.toInt + "s", 500, 42, 14, White, true)
drawText(g, "Q POWER", 625, 42, 13, Mint, true)
drawText(g, "S SHOOT ? E LOOT ? ARROWS MOVE", 730, 42, 11, White, true)
if (dropPhase) {
drawAircraft(g, aircraftX.toInt, aircraftY.toInt)
g.setColor(new Color(4, 20, 29, 215)); g.fillRoundRect(220, 205, 560, 190, 22, 22)
drawCentered(g, "BLOOM AIRSHIP - BR DROP", 500, 245, 25, Gold, true)
drawCentered(g, "1) Click the map to mark your landing point.", 500, 285, 15, White)
drawCentered(g, "2) Press ENTER to jump and parachute down.", 500, 315, 15, Mint)
drawCentered(g, "Marked: " + landingX.toInt + ", " + landingY.toInt, 500, 345, 13, White)
g.setColor(new Color(255, 230, 125, 210)); g.setStroke(new BasicStroke(2f))
g.drawOval(landingX.toInt - 17, landingY.toInt - 17, 34, 34)
g.drawLine(landingX.toInt - 24, landingY.toInt, landingX.toInt + 24, landingY.toInt)
g.drawLine(landingX.toInt, landingY.toInt - 24, landingX.toInt, landingY.toInt + 24)
g.setStroke(new BasicStroke(1f))
drawButton(g, "JUMP + LAND (ENTER)", 375, 365, 250, 45, () => deployFromAirship())
}
if (matchFinished) {
g.setColor(new Color(3, 16, 27, 225)); g.fillRoundRect(240, 205, 520, 250, 24, 24)
drawCentered(g, if (matchWon) "BLOOM VICTORY!" else "MATCH OVER", 500, 265, 34, if (matchWon) Gold else Pink, true)
drawCentered(g, "Tags: " + matchTags + " Coins earned: " + (matchCoins + (if (matchWon) 180 else 35)), 500, 310, 16, White)
drawCentered(g, "Your profile progress has been saved.", 500, 345, 14, Mint)
drawButton(g, "BACK TO MENU", 375, 385, 250, 48, () => { screen = "menu"; closeLan() })
} else {
drawText(g, statusMessage, 22, 650, 12, White, true)
}
}
private def paintGame(g0: Graphics): Unit = {
val g = g0.asInstanceOf[Graphics2D]
val w = canvas.getWidth; val h = canvas.getHeight
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON)
buttons.clear()
if (screen != "battle") drawBackground(g, w, h)
screen match {
case "login" => drawLogin(g, w)
case "menu" => drawMenu(g, w)
case "modes" => drawModes(g, w)
case "multiplayer" => drawMultiplayer(g, w)
case "avatar" => drawAvatarScreen(g, w)
case "customize" => drawCustomizer(g, w)
case "shop" => drawShop(g, w)
case "powers" => drawPowerStyles(g, w)
case "levels" => drawLevels(g, w)
case "admin" => drawAdmin(g, w)
case "battle" => drawBattle(g, w, h)
case _ => screen = "menu"
}
g.dispose()
}
private class GamePanel extends JPanel {
setPreferredSize(new Dimension(1000, 680))
setFocusable(true)
override def paintComponent(g: Graphics): Unit = { super.paintComponent(g); paintGame(g) }
}
private def installInput(): Unit = {
canvas.addMouseListener(new MouseAdapter {
override def mouseClicked(e: MouseEvent): Unit = {
val found = buttons.reverse.find(_.rect.contains(e.getPoint))
found match {
case Some(b) => if (b.enabled) b.action()
case None =>
if (screen == "battle" && !matchFinished) {
if (dropPhase) {
landingX = clamp(e.getX, 30, 970)
landingY = clamp(e.getY, 75, 620)
statusMessage = "Landing point marked. Press ENTER when ready to jump."
} else {
aimX = e.getX.toDouble
aimY = e.getY.toDouble
shootBloomBolt()
}
}
}
canvas.repaint()
canvas.requestFocusInWindow()
}
})
canvas.addMouseMotionListener(new java.awt.event.MouseMotionAdapter {
override def mouseMoved(e: MouseEvent): Unit = {
if (screen == "battle" && !dropPhase) { aimX = e.getX.toDouble; aimY = e.getY.toDouble }
}
})
canvas.addKeyListener(new KeyAdapter {
override def keyPressed(e: KeyEvent): Unit = {
heldKeys += e.getKeyCode
if (e.getKeyCode == KeyEvent.VK_ENTER && screen == "battle" && dropPhase) deployFromAirship()
else if (e.getKeyCode == KeyEvent.VK_ENTER && screen == "battle" && matchFinished) { screen = "menu"; closeLan() }
else if (e.getKeyCode == KeyEvent.VK_E) collectNearby()
else if (e.getKeyCode == KeyEvent.VK_Q) usePower()
else if (e.getKeyCode == KeyEvent.VK_S) shootBloomBolt()
else if (e.getKeyCode == KeyEvent.VK_SPACE) shootBloomBolt()
else if (e.getKeyCode == KeyEvent.VK_B && screen != "login") { if (screen == "battle") gameRunning = false; screen = "menu" }
else if (e.getKeyCode == KeyEvent.VK_ESCAPE && screen == "battle") { gameRunning = false; screen = "menu" }
canvas.repaint()
}
override def keyReleased(e: KeyEvent): Unit = { heldKeys -= e.getKeyCode }
})
}
private def launch(): Unit = {
frame = new JFrame(AppTitle)
canvas = new GamePanel
installInput()
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE)
frame.setContentPane(canvas)
frame.pack()
frame.setLocationRelativeTo(null)
frame.setResizable(false)
frame.setVisible(true)
canvas.requestFocusInWindow()
val timer = new Timer(33, new ActionListener {
override def actionPerformed(e: ActionEvent): Unit = {
updateGame()
canvas.repaint()
}
})
timer.start()
}
def main(args: Array[String]): Unit = {
SwingUtilities.invokeLater(new Runnable {
override def run(): Unit = {
try { launch() }
catch {
case e: Throwable =>
e.printStackTrace()
JOptionPane.showMessageDialog(null, "Fighting Bloom startup error:\n" + e.toString, "Startup Error", JOptionPane.ERROR_MESSAGE)
}
}
})
}
def run(): Unit = main(Array[String]())
}
// Kojo: run the whole script. This final line opens the Swing window.
BixuuSpringSeasonEvent6.main(Array[String]())