Code Sketch
bhavesh
Category: Programming
import scala.collection.mutable.ArrayBuffer
cleari()
originBottomLeft()
// ============================================================
// OPERATION SINDOOR
// ULTRA SAINIK COMMAND EDITION
// KOJO 2.9.x CANVASDRAW
// ============================================================
// ============================================================
// GAME STATES
// ============================================================
//
// -1 = PASSWORD
// -2 = AVATAR SELECT
// 0 = BRIEFING
// 1 = SAINIK DEPLOYMENT
// 2 = ACTIVE DEFENSE
// 3 = BOSS BATTLE
// 4 = FINAL STRIKE
// 5 = VICTORY
// ============================================================
var currentGameState = -1
// ============================================================
// ENGINE SETTINGS
// ============================================================
val ENGINE_VERSION = "V13.0-SA INIK COMMAND"
val MAX_STARS = 420
val MAX_PARTICLES = 1000
val MAX_ENEMIES = 70
val MAX_SHOCKWAVES = 60
val MAX_SPARKS = 450
// ============================================================
// PASSWORD
// ============================================================
val missionPassword = "bhavesh"
var passwordUnlocked = false
var passwordInput = ""
var passwordErrorTimer = 0.0
var keyBLast = false
var keyHLast = false
var keyALast = false
var keyVLast = false
var keyELast = false
var keySLast = false
// ============================================================
// AVATAR SELECTION
// ============================================================
var selectedAvatar = 1
var avatarHover = 0
val avatarNames = Array(
"NEON COMMANDER",
"IRON DEFENDER",
"SHADOW SCOUT",
"QUANTUM SPECIALIST"
)
val avatarRoles = Array(
"TACTICAL COMMAND",
"HEAVY DEFENSE",
"RAPID RESPONSE",
"ENERGY SYSTEMS"
)
var avatarAnimClock = 0.0
var avatarButtonClock = 0.0
// ============================================================
// GLOBAL CLOCKS
// ============================================================
var universalClock = 0.0
var globalElapsedTime = 0.0
var briefingIndex = 0
var briefingTimer = 0.0
var combatTimer = 100.0
var scoreDestroyedCount = 0
val quotaRequirement = 35
// ============================================================
// COMMANDER
// ============================================================
var commanderHP = 100.0
var commanderX = cwidth / 2.0
var commanderY = 145.0
var commanderMarch = 0.0
var commanderPulse = 0.0
// ============================================================
// TANK
// ============================================================
var tankX = cwidth / 2.0 + 90.0
val tankY = 32.0
var turretAngle = math.Pi / 2.0
var tankRecoil = 0.0
var tankHeat = 0.0
var tankEngine = 0.0
var cannonFlash = 0
var cannonCooldown = 0
// ============================================================
// SILO
// ============================================================
var siloX = cwidth / 2.0 - 260.0
var siloY = 145.0
var siloAngle = math.Pi / 2.0
var missileType = 1
var missileCooldown = 0
// ============================================================
// AIR DEFENSE
// ============================================================
var akashX = cwidth / 2.0 - 90.0
var akashY = 110.0
var pinakaX = cwidth / 2.0 + 200.0
var pinakaY = 120.0
var akashAngle = 0.0
var pinakaPulse = 0.0
var pinakaCooldown = 0
// ============================================================
// BOSS
// ============================================================
var bossActive = false
var bossX = cwidth / 2.0
var bossY = cheight - 145.0
var bossHP = 800.0
val bossMaxHP = 800.0
var bossShield = 100.0
var bossPhase = 1
var bossMovement = 0.0
var bossWarning = 0.0
var bossCore = 0.0
// ============================================================
// FINAL STRIKE
// ============================================================
var strikeX = 0.0
var strikeY = 0.0
var strikeAngle = math.Pi / 2.0
var strikeSpeed = 9.0
// ============================================================
// CAMERA FX
// ============================================================
var screenShake = 0.0
var colorFlash = 0.0
var redAlert = 0.0
var cinematicBars = 0.0
// ============================================================
// SAINIK SQUAD
// ============================================================
val squadCount = 4
var squadDeploy = 0.0
var squadMarch = 0.0
var squadBreath = 0.0
var squadSalute = 0.0
var squadScan = 0.0
var squadDrone = 0.0
// ============================================================
// BRIEFING
// ============================================================
val briefingText =
"TOP SECRET DIRECTIVE: OPERATION SINDOOR\n" +
"DEFENSE STRATEGIC COMMAND NETWORK\n\n" +
"MULTIPLE HOSTILE SIGNALS DETECTED.\n" +
"PERIMETER STATUS: CRITICAL.\n\n" +
"SAINIK COMMAND SQUAD: READY\n" +
"MAIN BATTLE TANK: READY\n" +
"AKASH AIR DEFENSE: ONLINE\n" +
"PINAKA ROCKET GRID: ONLINE\n" +
"COMMAND DRONE: ONLINE\n" +
"QUANTUM LINK: STABLE\n\n" +
"OBJECTIVES:\n" +
"DEFEND THE SECTOR.\n" +
"NEUTRALIZE HOSTILE WAVES.\n" +
"DEFEAT THE FLAGSHIP.\n\n" +
"INITIALIZE HYPER DEFENSE."
// ============================================================
// AUDIO
// ============================================================
var audioEnabled = false
try {
setNoteInstrument(
Instrument.SYNTH_STRINGS
)
audioEnabled = true
} catch {
case _: Exception =>
audioEnabled = false
}
def playFX(
note: Int,
duration: Int
): Unit = {
if (audioEnabled) {
try {
playNote(
note,
duration
)
} catch {
case _: Exception =>
}
}
}
// ============================================================
// STARS
// ============================================================
val starX =
Array.fill(
MAX_STARS
)(
randomDouble(
0.0,
cwidth
)
)
val starY =
Array.fill(
MAX_STARS
)(
randomDouble(
60.0,
cheight
)
)
val starSize =
Array.fill(
MAX_STARS
)(
randomDouble(
0.5,
3.5
)
)
val starSpeed =
Array.fill(
MAX_STARS
)(
randomDouble(
0.5,
4.0
)
)
// ============================================================
// PARTICLES
// ============================================================
case class Particle(
var x: Double,
var y: Double,
var vx: Double,
var vy: Double,
var life: Double,
val r: Int,
val g: Int,
val b: Int,
var size: Double
)
val particles =
ArrayBuffer.empty[Particle]
def spawnParticles(
x: Double,
y: Double,
r: Int,
g: Int,
b: Int,
count: Int
): Unit = {
var i = 0
while (
i < count &&
particles.length <
MAX_PARTICLES
) {
particles.append(
Particle(
x,
y,
randomDouble(
-7.0,
7.0
),
randomDouble(
-7.0,
7.0
),
1.0,
r,
g,
b,
randomDouble(
2.0,
8.0
)
)
)
i += 1
}
}
def updateParticles(): Unit = {
particles.filterInPlace(
_.life > 0.0
)
particles.foreach { p =>
p.x += p.vx
p.y += p.vy
p.vx *= 0.96
p.vy *= 0.96
p.life -= 0.025
p.size *= 0.985
}
}
def drawParticles(
c: CanvasDraw
): Unit = {
particles.foreach { p =>
val alpha =
math.max(
0,
math.min(
255,
(
p.life *
255.0
).toInt
)
)
c.fill(
p.r,
p.g,
p.b,
alpha
)
c.noStroke()
c.ellipse(
p.x,
p.y,
p.size,
p.size
)
}
}
// ============================================================
// SHOCKWAVES
// ============================================================
class Shockwave(
val x: Double,
val y: Double,
val kind: Int
) {
var radius = 5.0
var life = 255.0
def update(): Unit = {
radius += 11.0
life -= 14.0
}
def draw(
c: CanvasDraw
): Unit = {
if (life > 0.0) {
c.noFill()
if (kind == 0) {
c.stroke(
0,
255,
255,
life.toInt
)
} else if (kind == 1) {
c.stroke(
255,
180,
50,
life.toInt
)
} else {
c.stroke(
255,
50,
50,
life.toInt
)
}
c.strokeWeight(
3.0
)
c.ellipse(
x,
y,
radius * 2.0,
radius * 2.0
)
}
}
}
val shockwaves =
ArrayBuffer.empty[Shockwave]
// ============================================================
// SHELL
// ============================================================
class Shell(
var x: Double,
var y: Double,
val vx: Double,
val vy: Double
) {
var active = true
val trail =
ArrayBuffer.empty[
(Double, Double)
]
def update(): Unit = {
x += vx
y += vy
trail.append(
(x, y)
)
if (
trail.length > 12
) {
trail.remove(0)
}
if (
x < -100.0 ||
x > cwidth + 100.0 ||
y < -100.0 ||
y > cheight + 100.0
) {
active = false
}
}
def draw(
c: CanvasDraw
): Unit = {
var i = 0
while (
i < trail.length
) {
val p = trail(i)
c.fill(
255,
150,
30,
(
i.toDouble /
trail.length *
180.0
).toInt
)
c.noStroke()
c.ellipse(
p._1,
p._2,
4.0,
4.0
)
i += 1
}
c.fill(
255,
245,
170
)
c.ellipse(
x,
y,
12.0,
12.0
)
}
}
val shells =
ArrayBuffer.empty[Shell]
// ============================================================
// ENEMY
// ============================================================
class Enemy(
var x: Double,
var y: Double,
val speed: Double,
val pattern: Int
) {
var active = true
var destroyed = false
var angle = 0.0
val trail =
ArrayBuffer.empty[
(Double, Double)
]
def update(): Unit = {
if (!destroyed) {
y += speed
angle += 0.07
if (pattern == 1) {
x +=
math.sin(
universalClock * 4.0 +
y * 0.02
) * 3.0
}
if (pattern == 2) {
x +=
math.sin(
universalClock * 6.0 +
y * 0.02
) * 5.0
}
if (pattern == 3) {
x +=
math.cos(
universalClock * 7.0 +
y * 0.025
) * 7.0
}
if (pattern == 4) {
x +=
math.sin(
universalClock * 10.0 +
y * 0.04
) * 9.0
}
trail.append(
(x, y)
)
if (
trail.length >
22
) {
trail.remove(0)
}
if (
y < -100.0
) {
active = false
}
}
}
def draw(
c: CanvasDraw
): Unit = {
if (!destroyed) {
var i = 0
while (
i < trail.length
) {
val p = trail(i)
c.fill(
255,
40,
60,
(
i.toDouble /
trail.length *
170.0
).toInt
)
c.noStroke()
c.ellipse(
p._1,
p._2,
4.0,
4.0
)
i += 1
}
c.pushMatrix()
c.translate(
x,
y
)
c.rotate(
math.sin(angle) *
0.18
)
c.fill(
70,
15,
20
)
c.stroke(
170,
25,
35
)
c.strokeWeight(
2.0
)
c.rect(
-28.0,
-8.0,
56.0,
16.0
)
c.fill(
210,
40,
45
)
c.rect(
-17.0,
-14.0,
34.0,
28.0
)
c.fill(
255,
170,
40
)
c.noStroke()
c.ellipse(
22.0,
0.0,
14.0,
14.0
)
c.popMatrix()
}
}
}
val enemies =
ArrayBuffer.empty[Enemy]
// ============================================================
// MISSILE
// ============================================================
class Missile(
var x: Double,
var y: Double,
val speed: Double,
var angle: Double,
val kind: Int
) {
var active = true
val trail =
ArrayBuffer.empty[
(Double, Double)
]
def update(): Unit = {
if (kind == 2) {
angle += 0.025
}
if (kind == 3) {
angle +=
math.sin(
universalClock * 8.0
) * 0.05
}
x +=
math.cos(angle) *
speed
y +=
math.sin(angle) *
speed
trail.append(
(x, y)
)
if (
trail.length > 24
) {
trail.remove(0)
}
if (
x < -150.0 ||
x > cwidth + 150.0 ||
y < -150.0 ||
y > cheight + 150.0
) {
active = false
}
spawnParticles(
x -
math.cos(angle) *
18.0,
y -
math.sin(angle) *
18.0,
50,
210,
255,
1
)
}
def draw(
c: CanvasDraw
): Unit = {
var i = 0
while (
i < trail.length
) {
val p = trail(i)
if (kind == 1) {
c.fill(
0,
255,
230,
(
i.toDouble /
trail.length *
210.0
).toInt
)
} else if (kind == 2) {
c.fill(
255,
170,
40,
(
i.toDouble /
trail.length *
210.0
).toInt
)
} else {
c.fill(
255,
70,
40,
(
i.toDouble /
trail.length *
210.0
).toInt
)
}
c.noStroke()
c.ellipse(
p._1,
p._2,
5.0,
5.0
)
i += 1
}
c.pushMatrix()
c.translate(
x,
y
)
c.rotate(
angle
)
c.fill(
235,
250,
255
)
c.stroke(
0,
110,
150
)
c.strokeWeight(
1.2
)
c.rect(
-25.0,
-7.0,
50.0,
14.0
)
c.fill(
0,
245,
255
)
c.noStroke()
c.ellipse(
25.0,
0.0,
15.0,
15.0
)
c.popMatrix()
}
}
val missiles =
ArrayBuffer.empty[Missile]
// ============================================================
// EXPLOSION
// ============================================================
def explosion(
x: Double,
y: Double,
power: Int
): Unit = {
shockwaves.append(
new Shockwave(
x,
y,
1
)
)
if (
shockwaves.length >
MAX_SHOCKWAVES
) {
shockwaves.remove(0)
}
spawnParticles(
x,
y,
255,
190,
50,
power
)
spawnParticles(
x,
y,
255,
60,
30,
power / 2
)
screenShake =
math.max(
screenShake,
7.0 +
power *
0.15
)
colorFlash =
math.max(
colorFlash,
0.25
)
}
def megaExplosion(
x: Double,
y: Double
): Unit = {
explosion(
x,
y,
40
)
shockwaves.append(
new Shockwave(
x,
y,
0
)
)
shockwaves.append(
new Shockwave(
x,
y,
2
)
)
spawnParticles(
x,
y,
255,
255,
220,
30
)
screenShake =
18.0
colorFlash =
0.65
playFX(
42,
100
)
}
// ============================================================
// FIRE GUN
// ============================================================
def fireGun(): Unit = {
if (
cannonCooldown <=
0
) {
val mx =
tankX +
math.cos(
turretAngle
) *
94.0
val my =
tankY +
59.0 +
math.sin(
turretAngle
) *
94.0
shells.append(
new Shell(
mx,
my,
math.cos(
turretAngle
) * 14.0,
math.sin(
turretAngle
) * 14.0
)
)
cannonCooldown = 8
cannonFlash = 6
tankRecoil = 3.5
tankHeat += 10.0
spawnParticles(
mx,
my,
255,
180,
60,
8
)
screenShake = 4.0
playFX(
72,
40
)
}
}
// ============================================================
// MISSILE LAUNCH
// ============================================================
def fireMissile(): Unit = {
if (
missileCooldown <=
0
) {
var speed =
9.0
if (
missileType ==
2
) {
speed =
8.0
}
if (
missileType ==
3
) {
speed =
11.0
}
val mx =
siloX +
math.cos(
siloAngle
) *
65.0
val my =
siloY +
math.sin(
siloAngle
) *
65.0
missiles.append(
new Missile(
mx,
my,
speed,
siloAngle,
missileType
)
)
missileCooldown = 12
spawnParticles(
mx,
my,
255,
140,
40,
12
)
screenShake = 3.0
playFX(
68,
50
)
}
}
// ============================================================
// PASSWORD INPUT
// ============================================================
def passwordChar(
ch: Char
): Unit = {
if (
passwordUnlocked
) {
return
}
if (
passwordInput.length <
missionPassword.length
) {
passwordInput +=
ch
}
if (
passwordInput ==
missionPassword
) {
passwordUnlocked =
true
passwordInput =
""
currentGameState =
-2
playFX(
84,
180
)
}
if (
passwordInput.length >=
missionPassword.length &&
passwordInput !=
missionPassword
) {
passwordInput =
""
passwordErrorTimer =
1.8
playFX(
40,
120
)
}
}
def updatePassword(): Unit = {
if (
passwordUnlocked
) {
return
}
if (
passwordErrorTimer >
0.0
) {
passwordErrorTimer -=
0.04
}
val b =
isKeyPressed(
Kc.VK_B
)
val h =
isKeyPressed(
Kc.VK_H
)
val a =
isKeyPressed(
Kc.VK_A
)
val v =
isKeyPressed(
Kc.VK_V
)
val e =
isKeyPressed(
Kc.VK_E
)
val s =
isKeyPressed(
Kc.VK_S
)
if (
b &&
!keyBLast
) {
passwordChar('b')
}
if (
h &&
!keyHLast
) {
passwordChar('h')
}
if (
a &&
!keyALast
) {
passwordChar('a')
}
if (
v &&
!keyVLast
) {
passwordChar('v')
}
if (
e &&
!keyELast
) {
passwordChar('e')
}
if (
s &&
!keySLast
) {
passwordChar('s')
}
keyBLast = b
keyHLast = h
keyALast = a
keyVLast = v
keyELast = e
keySLast = s
}
// ============================================================
// MOUSE CONTROLS
// ============================================================
def handleMouseControls(): Unit = {
if (
currentGameState !=
2 &&
currentGameState !=
3
) {
return
}
if (
!isMousePressed
) {
return
}
val mx =
mouseX
val my =
mouseY
if (
mx >= 20.0 &&
mx <= 70.0 &&
my >= 90.0 &&
my <= 125.0
) {
tankX -=
6.0
} else if (
mx >= 75.0 &&
mx <= 125.0 &&
my >= 90.0 &&
my <= 125.0
) {
tankX +=
6.0
} else if (
mx >= 130.0 &&
mx <= 180.0 &&
my >= 90.0 &&
my <= 125.0
) {
turretAngle -=
0.06
} else if (
mx >= 185.0 &&
mx <= 235.0 &&
my >= 90.0 &&
my <= 125.0
) {
turretAngle +=
0.06
} else if (
mx >= 240.0 &&
mx <= 315.0 &&
my >= 90.0 &&
my <= 125.0
) {
fireGun()
} else if (
mx >= 320.0 &&
mx <= 365.0 &&
my >= 90.0 &&
my <= 125.0
) {
siloAngle -=
0.06
} else if (
mx >= 370.0 &&
mx <= 415.0 &&
my >= 90.0 &&
my <= 125.0
) {
siloAngle +=
0.06
} else if (
mx >= 420.0 &&
mx <= 465.0 &&
my >= 90.0 &&
my <= 125.0
) {
missileType = 1
} else if (
mx >= 470.0 &&
mx <= 515.0 &&
my >= 90.0 &&
my <= 125.0
) {
missileType = 2
} else if (
mx >= 520.0 &&
mx <= 565.0 &&
my >= 90.0 &&
my <= 125.0
) {
missileType = 3
} else if (
mx >= 570.0 &&
mx <= 700.0 &&
my >= 90.0 &&
my <= 125.0
) {
fireMissile()
}
}
// ============================================================
// KEYBOARD CONTROLS
// ============================================================
def handleKeyboard(): Unit = {
if (
currentGameState !=
2 &&
currentGameState !=
3
) {
return
}
if (
isKeyPressed(
Kc.VK_LEFT
)
) {
tankX -=
3.5
}
if (
isKeyPressed(
Kc.VK_RIGHT
)
) {
tankX +=
3.5
}
if (
isKeyPressed(
Kc.VK_UP
)
) {
turretAngle +=
0.045
}
if (
isKeyPressed(
Kc.VK_DOWN
)
) {
turretAngle -=
0.045
}
if (
isKeyPressed(
Kc.VK_SPACE
)
) {
fireGun()
}
if (
isKeyPressed(
Kc.VK_1
)
) {
missileType = 1
}
if (
isKeyPressed(
Kc.VK_2
)
) {
missileType = 2
}
if (
isKeyPressed(
Kc.VK_3
)
) {
missileType = 3
}
if (
isKeyPressed(
Kc.VK_ENTER
)
) {
fireMissile()
}
}
// ============================================================
// COLLISIONS
// ============================================================
def checkCollisions(): Unit = {
repeatFor(
enemies
) { enemy =>
if (
!enemy.destroyed
) {
repeatFor(
shells
) { shell =>
if (
shell.active &&
math.abs(
shell.x -
enemy.x
) < 40.0 &&
math.abs(
shell.y -
enemy.y
) < 40.0
) {
shell.active =
false
enemy.destroyed =
true
scoreDestroyedCount +=
1
explosion(
enemy.x,
enemy.y,
25
)
}
}
repeatFor(
missiles
) { missile =>
if (
missile.active &&
math.abs(
missile.x -
enemy.x
) < 46.0 &&
math.abs(
missile.y -
enemy.y
) < 46.0
) {
missile.active =
false
enemy.destroyed =
true
scoreDestroyedCount +=
1
explosion(
enemy.x,
enemy.y,
30
)
}
}
}
}
if (
bossActive
) {
repeatFor(
shells
) { shell =>
if (
shell.active &&
math.abs(
shell.x -
bossX
) < 75.0 &&
math.abs(
shell.y -
bossY
) < 65.0
) {
shell.active =
false
if (
bossShield >
0.0
) {
bossShield -=
8.0
} else {
bossHP -=
18.0
}
explosion(
shell.x,
shell.y,
28
)
}
}
repeatFor(
missiles
) { missile =>
if (
missile.active &&
math.abs(
missile.x -
bossX
) < 78.0 &&
math.abs(
missile.y -
bossY
) < 68.0
) {
missile.active =
false
if (
bossShield >
0.0
) {
bossShield -=
18.0
} else {
bossHP -=
38.0
}
explosion(
missile.x,
missile.y,
32
)
}
}
if (
bossShield <
0.0
) {
bossShield =
0.0
}
}
}
// ============================================================
// UPDATE OBJECTS
// ============================================================
def updateObjects(): Unit = {
shells.filterInPlace(
_.active
)
repeatFor(
shells
)(
_.update()
)
missiles.filterInPlace(
_.active
)
repeatFor(
missiles
)(
_.update()
)
enemies.filterInPlace(
_.active
)
repeatFor(
enemies
)(
_.update()
)
shockwaves.filterInPlace(
_.life > 0.0
)
repeatFor(
shockwaves
)(
_.update()
)
}
// ============================================================
// SPAWN ENEMY
// ============================================================
def spawnEnemy(): Unit = {
if (
enemies.length >=
MAX_ENEMIES
) {
return
}
val x =
randomDouble(
80.0,
cwidth - 80.0
)
val pattern =
random(
1,
5
)
val speed =
-(
0.7 +
randomDouble(
0.0,
0.65
)
)
enemies.append(
new Enemy(
x,
cheight + 40.0,
speed,
pattern
)
)
spawnParticles(
x,
cheight -
10.0,
255,
50,
50,
7
)
}
// ============================================================
// START BOSS
// ============================================================
def startBoss(): Unit = {
bossActive =
true
bossHP =
bossMaxHP
bossShield =
100.0
bossPhase =
1
bossWarning =
4.0
bossMovement =
0.0
currentGameState =
3
redAlert =
1.0
screenShake =
10.0
cinematicBars =
8.0
playFX(
42,
220
)
}
// ============================================================
// SAINIK AVATAR
// ============================================================
def drawAvatar(
c: CanvasDraw,
x: Double,
y: Double,
avatarType: Int,
scale: Double,
selected: Boolean,
salute: Boolean
): Unit = {
c.pushMatrix()
c.translate(
x,
y
)
c.scale(
scale,
scale
)
val breath =
math.sin(
avatarAnimClock *
2.0
) * 1.5
val walk =
math.sin(
squadMarch
) * 4.0
if (
selected
) {
c.noFill()
c.stroke(
0,
255,
255,
160
)
c.strokeWeight(
2.5
)
c.ellipse(
0.0,
30.0,
105.0 +
math.sin(
avatarAnimClock *
3.0
) * 8.0,
145.0
)
}
c.fill(
0,
0,
0,
110
)
c.noStroke()
c.ellipse(
0.0,
-35.0,
60.0,
12.0
)
// Legs
c.stroke(
35,
45,
52
)
c.strokeWeight(
7.0
)
c.line(
-9.0,
0.0,
-14.0 + walk,
-31.0
)
c.line(
9.0,
0.0,
14.0 - walk,
-31.0
)
// Boots
c.stroke(
15,
20,
25
)
c.strokeWeight(
8.0
)
c.line(
-14.0 + walk,
-31.0,
-22.0 + walk,
-36.0
)
c.line(
14.0 - walk,
-31.0,
22.0 - walk,
-36.0
)
// Body
if (
avatarType ==
1
) {
c.fill(
45,
75,
85
)
c.stroke(
0,
220,
255
)
} else if (
avatarType ==
2
) {
c.fill(
72,
67,
58
)
c.stroke(
255,
165,
50
)
} else if (
avatarType ==
3
) {
c.fill(
35,
40,
60
)
c.stroke(
175,
80,
255
)
} else {
c.fill(
35,
68,
58
)
c.stroke(
0,
255,
180
)
}
c.strokeWeight(
2.0
)
c.rect(
-20.0,
0.0 + breath,
40.0,
43.0
)
// Chest core
if (
avatarType ==
1
) {
c.fill(
0,
220,
255
)
} else if (
avatarType ==
2
) {
c.fill(
255,
100,
30
)
} else if (
avatarType ==
3
) {
c.fill(
185,
75,
255
)
} else {
c.fill(
0,
255,
180
)
}
c.noStroke()
c.ellipse(
0.0,
21.0 + breath,
10.0,
10.0
)
// Arms
c.stroke(
55,
75,
82
)
c.strokeWeight(
6.0
)
if (
salute
) {
c.line(
-15.0,
28.0,
-22.0,
46.0
)
c.line(
15.0,
28.0,
21.0,
46.0
)
} else {
c.line(
-15.0,
28.0,
-26.0,
7.0
)
c.line(
15.0,
28.0,
26.0,
7.0
)
}
// Neck
c.fill(
170,
125,
85
)
c.noStroke()
c.rect(
-7.0,
38.0,
14.0,
10.0
)
// Helmet
c.fill(
52,
70,
78
)
c.stroke(
10,
22,
28
)
c.strokeWeight(
2.0
)
c.ellipse(
0.0,
68.0 + breath,
50.0,
45.0
)
// Visor
if (
avatarType ==
1
) {
c.fill(
5,
35,
48
)
c.stroke(
0,
235,
255
)
} else if (
avatarType ==
2
) {
c.fill(
75,
35,
25
)
c.stroke(
255,
170,
50
)
} else if (
avatarType ==
3
) {
c.fill(
30,
15,
60
)
c.stroke(
190,
90,
255
)
} else {
c.fill(
5,
45,
35
)
c.stroke(
0,
255,
180
)
}
c.strokeWeight(
2.5
)
c.ellipse(
0.0,
66.0 + breath,
34.0,
20.0
)
// Helmet light
val lightPulse =
120 +
(
math.abs(
math.sin(
avatarAnimClock *
3.0
)
) *
120.0
).toInt
if (
avatarType ==
3
) {
c.fill(
190,
80,
lightPulse
)
} else if (
avatarType ==
4
) {
c.fill(
0,
lightPulse,
180
)
} else {
c.fill(
0,
lightPulse,
255
)
}
c.noStroke()
c.ellipse(
0.0,
91.0 + breath,
6.0,
6.0
)
// Tricolor badge
c.fill(
255,
153,
51
)
c.rect(
-27.0,
12.0,
6.0,
5.0
)
c.fill(
255,
255,
255
)
c.rect(
-27.0,
17.0,
6.0,
5.0
)
c.fill(
18,
136,
7
)
c.rect(
-27.0,
22.0,
6.0,
5.0
)
c.popMatrix()
}
// ============================================================
// AVATAR SELECTION SCREEN
// ============================================================
def drawAvatarSelectionScreen(
c: CanvasDraw
): Unit = {
avatarAnimClock +=
0.04
avatarButtonClock +=
0.05
c.background(
2,
6,
20
)
drawStarfield(
c
)
// Grid
c.stroke(
0,
170,
220,
28
)
c.strokeWeight(
1.0
)
var gx =
0.0
while (
gx <
cwidth
) {
c.line(
gx,
0.0,
gx,
cheight
)
gx +=
45.0
}
var gy =
0.0
while (
gy <
cheight
) {
c.line(
0.0,
gy,
cwidth,
gy
)
gy +=
45.0
}
// Header
c.fill(
0,
255,
230
)
c.text(
"DEFENSE COMMAND // AVATAR SELECTION",
cwidth / 2.0 -
175.0,
cheight -
30.0
)
c.fill(
255,
215,
60
)
c.text(
"SELECT YOUR SAINIK AVATAR",
cwidth / 2.0 -
125.0,
cheight -
58.0
)
c.fill(
140,
190,
210
)
c.text(
"Choose your specialist before starting the mission.",
cwidth / 2.0 -
170.0,
cheight -
84.0
)
val cardW =
170.0
val cardH =
275.0
val firstX =
cwidth / 2.0 -
365.0
val cardY =
cheight / 2.0 -
145.0
var i =
0
while (
i < 4
) {
val x =
firstX +
i *
185.0
val hovered =
mouseX >= x &&
mouseX <=
x + cardW &&
mouseY >= cardY &&
mouseY <=
cardY + cardH
if (
hovered
) {
avatarHover =
i + 1
}
val selected =
selectedAvatar ==
i + 1
if (
selected
) {
c.fill(
0,
65,
85,
240
)
c.stroke(
0,
255,
255
)
c.strokeWeight(
3.5
)
} else if (
hovered
) {
c.fill(
20,
45,
70,
240
)
c.stroke(
255,
215,
70
)
c.strokeWeight(
2.5
)
} else {
c.fill(
7,
20,
40,
235
)
c.stroke(
55,
90,
120
)
c.strokeWeight(
1.5
)
}
c.rect(
x,
cardY,
cardW,
cardH
)
drawAvatar(
c,
x + cardW / 2.0,
cardY + 105.0,
i + 1,
0.9,
selected,
false
)
c.fill(
255,
255,
255
)
c.text(
avatarNames(i),
x + 19.0,
cardY + 178.0
)
c.fill(
0,
245,
220
)
c.text(
avatarRoles(i),
x + 26.0,
cardY + 200.0
)
c.fill(
160,
190,
210
)
if (
i == 0
) {
c.text(
"Balanced Combat",
x + 24.0,
cardY + 228.0
)
c.text(
"Tactical AI",
x + 43.0,
cardY + 248.0
)
} else if (
i == 1
) {
c.text(
"Heavy Armour",
x + 31.0,
cardY + 228.0
)
c.text(
"High Defense",
x + 42.0,
cardY + 248.0
)
} else if (
i == 2
) {
c.text(
"Rapid Movement",
x + 28.0,
cardY + 228.0
)
c.text(
"Recon Expert",
x + 38.0,
cardY + 248.0
)
} else {
c.text(
"Energy Control",
x + 28.0,
cardY + 228.0
)
c.text(
"Quantum Systems",
x + 20.0,
cardY + 248.0
)
}
if (
isMousePressed &&
hovered
) {
selectedAvatar =
i + 1
playFX(
72 +
i * 4,
90
)
}
i +=
1
}
// Selected
c.fill(
100,
255,
225
)
c.text(
"SELECTED: " +
avatarNames(
selectedAvatar - 1
),
cwidth / 2.0 -
112.0,
104.0
)
// Start Mission
val bx =
cwidth / 2.0 -
140.0
val by =
42.0
val pulse =
3.0 +
math.abs(
math.sin(
avatarButtonClock
)
) *
3.0
c.fill(
0,
150,
95
)
c.stroke(
0,
255,
180
)
c.strokeWeight(
2.0 + pulse * 0.2
)
c.rect(
bx,
by,
280.0,
48.0
)
c.fill(
255,
255,
255
)
c.text(
"START MISSION",
bx + 83.0,
by + 30.0
)
if (
isMousePressed &&
mouseX >= bx &&
mouseX <= bx + 280.0 &&
mouseY >= by &&
mouseY <= by + 48.0
) {
currentGameState =
0
briefingIndex =
0
briefingTimer =
0.0
squadDeploy =
0.0
playFX(
84,
180
)
}
}
// ============================================================
// STAR DRAW
// ============================================================
def drawStarfield(
c: CanvasDraw
): Unit = {
var i =
0
while (
i < MAX_STARS
) {
val alpha =
math.max(
60,
math.min(
255,
(
80.0 +
math.abs(
math.sin(
universalClock *
starSpeed(i) +
i
)
) *
175.0
).toInt
)
)
var yy =
starY(i) -
universalClock *
starSpeed(i) *
5.0
while (
yy < 50.0
) {
yy +=
cheight -
50.0
}
c.fill(
225,
240,
255,
alpha
)
c.noStroke()
c.ellipse(
starX(i),
yy,
starSize(i),
starSize(i)
)
i +=
1
}
}
// ============================================================
// BRIEFING
// ============================================================
def drawBriefing(
c: CanvasDraw
): Unit = {
c.background(
1,
4,
16
)
drawStarfield(
c
)
c.fill(
8,
16,
40,
250
)
c.stroke(
0,
255,
255
)
c.strokeWeight(
3.0
)
val bx =
cwidth / 2.0 -
315.0
val by =
cheight / 2.0 -
190.0
c.rect(
bx,
by,
630.0,
400.0
)
c.fill(
255,
215,
60
)
c.text(
"OPERATION SINDOOR // COMMAND BRIEFING",
bx + 30.0,
by + 32.0
)
c.fill(
80,
255,
225
)
c.text(
ENGINE_VERSION,
bx + 460.0,
by + 32.0
)
val shown =
briefingText.substring(
0,
briefingIndex
)
val lines =
shown.split(
"\n"
)
var ty =
by + 68.0
for (
line <- lines
) {
c.fill(
230,
240,
255
)
c.text(
line,
bx + 25.0,
ty
)
ty +=
19.0
}
if (
briefingIndex >=
briefingText.length
) {
val sx =
bx + 170.0
val sy =
by - 58.0
c.fill(
0,
220,
150
)
c.stroke(
255,
255,
255
)
c.strokeWeight(
2.0
)
c.rect(
sx,
sy,
290.0,
44.0
)
c.fill(
255,
255,
255
)
c.text(
"INITIALIZE MISSION",
sx + 63.0,
sy + 28.0
)
if (
isMousePressed &&
mouseX >= sx &&
mouseX <=
sx + 290.0 &&
mouseY >= sy &&
mouseY <=
sy + 44.0
) {
currentGameState =
1
squadDeploy =
0.0
playFX(
80,
150
)
}
}
}
// ============================================================
// PASSWORD SCREEN
// ============================================================
def drawPasswordScreen(
c: CanvasDraw
): Unit = {
c.background(
1,
3,
14
)
drawStarfield(
c
)
val cx =
cwidth / 2.0
val cy =
cheight / 2.0
c.fill(
6,
18,
42,
250
)
c.stroke(
0,
255,
255
)
c.strokeWeight(
3.0
)
c.rect(
cx - 275.0,
cy - 165.0,
550.0,
330.0
)
c.fill(
0,
255,
230
)
c.text(
"DEFENSE COMMAND SECURITY",
cx - 132.0,
cy + 120.0
)
c.fill(
255,
215,
60
)
c.text(
"OPERATION SINDOOR",
cx - 82.0,
cy + 88.0
)
// Lock body
c.fill(
25,
35,
60
)
c.stroke(
0,
220,
255
)
c.strokeWeight(
3.0
)
c.rect(
cx - 42.0,
cy - 28.0,
84.0,
65.0
)
c.noFill()
c.stroke(
0,
220,
255
)
c.strokeWeight(
5.0
)
c.line(
cx - 28.0,
cy - 28.0,
cx - 28.0,
cy - 52.0
)
c.line(
cx - 28.0,
cy - 52.0,
cx,
cy - 68.0
)
c.line(
cx,
cy - 68.0,
cx + 28.0,
cy - 52.0
)
c.line(
cx + 28.0,
cy - 52.0,
cx + 28.0,
cy - 28.0
)
c.fill(
255,
215,
60
)
c.noStroke()
c.ellipse(
cx,
cy,
10.0,
10.0
)
c.fill(
170,
210,
230
)
c.text(
"ENTER ACCESS PASSWORD",
cx - 105.0,
cy - 88.0
)
c.fill(
4,
22,
40
)
c.stroke(
0,
210,
255
)
c.strokeWeight(
2.0
)
c.rect(
cx - 135.0,
cy - 130.0,
270.0,
38.0
)
c.fill(
0,
255,
180
)
c.noStroke()
val masked =
passwordInput.map(
_ =>
"*"
).mkString
c.text(
masked,
cx - 110.0,
cy - 105.0
)
if (
passwordErrorTimer >
0.0
) {
c.fill(
255,
70,
70
)
c.text(
"ACCESS DENIED",
cx - 60.0,
cy - 145.0
)
} else {
c.fill(
0,
220,
180
)
c.text(
"WAITING FOR AUTHORIZATION",
cx - 115.0,
cy - 145.0
)
}
c.fill(
100,
150,
190
)
c.text(
"TYPE PASSWORD ON KEYBOARD",
cx - 120.0,
cy + 145.0
)
}
// ============================================================
// DEPLOYMENT
// ============================================================
def drawDeployment(
c: CanvasDraw
): Unit = {
drawCombatBackground(
c
)
val p =
math.max(
0.2,
math.min(
1.0,
squadDeploy
)
)
var i =
0
while (
i < squadCount
) {
val offset =
(
i -
1.5
) *
75.0
val sx =
cwidth / 2.0 +
offset
val sy =
140.0 +
math.sin(
squadMarch +
i *
0.8
) *
3.0
drawAvatar(
c,
sx,
sy,
selectedAvatar,
p,
false,
true
)
i +=
1
}
drawTank(
c
)
drawSilo(
c
)
drawAirDefense(
c
)
c.fill(
255,
215,
60
)
c.text(
"SAINIK SQUAD DEPLOYMENT",
cwidth / 2.0 -
115.0,
cheight - 35.0
)
c.fill(
100,
255,
225
)
c.text(
"FORMATION ALPHA // ALL UNITS READY",
cwidth / 2.0 -
135.0,
cheight - 60.0
)
}
// ============================================================
// COMBAT BACKGROUND
// ============================================================
def drawCombatBackground(
c: CanvasDraw
): Unit = {
c.background(
2,
5,
20
)
drawStarfield(
c
)
c.fill(
15,
50,
150,
25
)
c.noStroke()
c.ellipse(
cwidth / 2.0,
100.0,
cwidth + 100.0,
160.0
)
c.stroke(
0,
100,
140,
35
)
c.strokeWeight(
1.0
)
var gy =
80.0
while (
gy <
260.0
) {
c.line(
0.0,
gy,
cwidth,
gy
)
gy +=
25.0
}
var gx =
0.0
while (
gx <
cwidth
) {
c.line(
gx,
120.0,
cwidth / 2.0,
80.0
)
gx +=
50.0
}
}
// ============================================================
// TANK DRAW
// ============================================================
def drawTank(
c: CanvasDraw
): Unit = {
c.fill(
0,
0,
0,
120
)
c.noStroke()
c.ellipse(
tankX,
tankY -
4.0,
175.0,
24.0
)
c.fill(
34,
39,
44
)
c.stroke(
12,
17,
22
)
c.strokeWeight(
1.5
)
c.rect(
tankX -
70.0,
tankY,
140.0,
32.0
)
c.fill(
100,
105,
101
)
for (
i <- -5 to 5
) {
c.ellipse(
tankX +
i *
13.5,
tankY +
16.0,
11.0,
11.0
)
}
c.fill(
68,
110,
60
)
c.rect(
tankX -
55.0,
tankY +
30.0,
110.0,
30.0
)
c.fill(
58,
96,
52
)
c.ellipse(
tankX,
tankY +
59.0,
60.0,
34.0
)
val barrel =
90.0 +
tankRecoil *
4.0
val tx =
tankX +
math.cos(
turretAngle
) *
barrel
val ty =
tankY +
59.0 +
math.sin(
turretAngle
) *
barrel
c.stroke(
42,
72,
37
)
c.strokeWeight(
10.0 +
tankRecoil *
2.0
)
c.line(
tankX,
tankY +
59.0,
tx,
ty
)
c.fill(
255,
80,
20,
150
)
c.noStroke()
val enginePulse =
70 +
(
math.abs(
math.sin(
tankEngine
)
) *
150.0
).toInt
c.fill(
255,
80,
20,
enginePulse
)
c.ellipse(
tankX -
50.0,
tankY +
4.0,
8.0,
5.0
)
c.ellipse(
tankX +
50.0,
tankY +
4.0,
8.0,
5.0
)
if (
cannonFlash >
0
) {
c.fill(
255,
150,
40,
220
)
c.ellipse(
tx,
ty,
50.0,
50.0
)
c.fill(
255,
250,
180,
240
)
c.ellipse(
tx,
ty,
22.0,
22.0
)
}
}
// ============================================================
// SILO DRAW
// ============================================================
def drawSilo(
c: CanvasDraw
): Unit = {
c.fill(
58,
63,
73
)
c.stroke(
25,
30,
38
)
c.strokeWeight(
2.0
)
c.ellipse(
siloX,
siloY,
76.0,
76.0
)
c.pushMatrix()
c.translate(
siloX,
siloY
)
c.rotate(
siloAngle
)
c.fill(
82,
108,
94
)
c.rect(
-18.0,
-21.0,
80.0,
42.0
)
c.popMatrix()
}
// ============================================================
// AIR DEFENSE DRAW
// ============================================================
def drawAirDefense(
c: CanvasDraw
): Unit = {
c.fill(
45,
85,
60
)
c.stroke(
20,
45,
30
)
c.strokeWeight(
2.0
)
c.rect(
akashX -
36.0,
akashY -
16.0,
72.0,
32.0
)
c.pushMatrix()
c.translate(
akashX,
akashY
)
c.rotate(
akashAngle
)
c.stroke(
0,
255,
180,
170
)
c.strokeWeight(
2.0
)
c.line(
0.0,
0.0,
25.0,
0.0
)
c.popMatrix()
c.fill(
0,
255,
180
)
c.noStroke()
c.ellipse(
akashX,
akashY,
15.0,
15.0
)
c.fill(
255,
255,
255
)
c.text(
"AKASH",
akashX -
22.0,
akashY -
24.0
)
c.fill(
85,
65,
43
)
c.stroke(
45,
30,
18
)
c.strokeWeight(
2.0
)
c.rect(
pinakaX -
42.0,
pinakaY -
20.0,
84.0,
40.0
)
c.fill(
255,
153,
51,
(
70 +
(
math.abs(
math.sin(
pinakaPulse
)
) *
160.0
)
).toInt
)
c.noStroke()
c.ellipse(
pinakaX -
17.0,
pinakaY,
9.0,
9.0
)
c.ellipse(
pinakaX,
pinakaY,
9.0,
9.0
)
c.ellipse(
pinakaX +
17.0,
pinakaY,
9.0,
9.0
)
c.fill(
255,
255,
255
)
c.text(
"PINAKA",
pinakaX -
26.0,
pinakaY -
28.0
)
}
// ============================================================
// SQUAD DRAW
// ============================================================
def drawSquad(
c: CanvasDraw
): Unit = {
var i =
0
while (
i < squadCount
) {
val offset =
(
i -
1.5
) *
75.0
val x =
cwidth / 2.0 +
offset
val y =
140.0 +
math.sin(
squadMarch +
i.toDouble
) *
2.5
drawAvatar(
c,
x,
y,
selectedAvatar,
0.9 *
math.max(
0.2,
squadDeploy
),
false,
squadSalute > 0.5
)
i +=
1
}
}
// ============================================================
// RADAR
// ============================================================
def drawRadar(
c: CanvasDraw
): Unit = {
val x =
80.0
val y =
cheight -
120.0
c.fill(
5,
20,
30,
230
)
c.stroke(
0,
255,
160,
200
)
c.strokeWeight(
2.0
)
c.ellipse(
x,
y,
120.0,
120.0
)
c.ellipse(
x,
y,
82.0,
82.0
)
c.ellipse(
x,
y,
42.0,
42.0
)
c.line(
x - 60.0,
y,
x + 60.0,
y
)
c.line(
x,
y - 60.0,
x,
y + 60.0
)
c.stroke(
0,
255,
180
)
c.strokeWeight(
2.5
)
c.line(
x,
y,
x +
math.cos(
universalClock
) *
56.0,
y +
math.sin(
universalClock
) *
56.0
)
repeatFor(
enemies
) { e =>
if (
!e.destroyed
) {
val dx =
math.max(
-50.0,
math.min(
50.0,
(
e.x -
cwidth / 2.0
) /
5.0
)
)
val dy =
math.max(
-50.0,
math.min(
50.0,
(
e.y -
cheight / 2.0
) /
5.0
)
)
c.fill(
255,
50,
60
)
c.noStroke()
c.ellipse(
x + dx,
y + dy,
5.0,
5.0
)
}
}
}
// ============================================================
// COMMAND HUD
// ============================================================
def drawHUD(
c: CanvasDraw
): Unit = {
c.fill(
5,
10,
30,
245
)
c.stroke(
0,
255,
255,
190
)
c.strokeWeight(
2.0
)
c.rect(
15.0,
cheight - 66.0,
cwidth - 30.0,
50.0
)
c.fill(
255,
220,
70
)
c.text(
"OPERATION SINDOOR",
28.0,
cheight - 38.0
)
c.fill(
100,
255,
225
)
c.text(
s"TARGETS: $scoreDestroyedCount / $quotaRequirement",
210.0,
cheight - 38.0
)
c.fill(
255,
120,
110
)
c.text(
s"TIME: ${combatTimer.toInt}s",
390.0,
cheight - 38.0
)
c.fill(
30,
35,
48
)
c.rect(
500.0,
cheight - 53.0,
170.0,
10.0
)
c.fill(
40,
220,
110
)
c.rect(
500.0,
cheight - 53.0,
170.0 *
math.max(
0.0,
math.min(
1.0,
commanderHP /
100.0
)
),
10.0
)
c.fill(
255,
255,
255
)
c.text(
"COMMANDER HP",
545.0,
cheight - 25.0
)
drawRadar(
c
)
}
// ============================================================
// CONTROL PANEL
// ============================================================
def drawControls(
c: CanvasDraw
): Unit = {
c.fill(
14,
20,
45,
255
)
c.stroke(
0,
255,
255
)
c.strokeWeight(
1.8
)
c.rect(
15.0,
88.0,
710.0,
52.0
)
c.fill(
70,
75,
110
)
c.rect(
20.0,
96.0,
52.0,
32.0
)
c.rect(
78.0,
96.0,
52.0,
32.0
)
c.fill(
255,
255,
255
)
c.text(
"T-",
38.0,
116.0
)
c.text(
"T+",
96.0,
116.0
)
c.fill(
100,
50,
50
)
c.rect(
136.0,
96.0,
52.0,
32.0
)
c.rect(
194.0,
96.0,
52.0,
32.0
)
c.fill(
255,
255,
255
)
c.text(
"C-",
154.0,
116.0
)
c.text(
"C+",
212.0,
116.0
)
c.fill(
240,
100,
0
)
c.rect(
252.0,
96.0,
62.0,
32.0
)
c.fill(
255,
255,
255
)
c.text(
"FIRE",
267.0,
116.0
)
c.fill(
50,
120,
120
)
c.rect(
320.0,
96.0,
48.0,
32.0
)
c.rect(
374.0,
96.0,
48.0,
32.0
)
c.fill(
255,
255,
255
)
c.text(
"M-",
334.0,
116.0
)
c.text(
"M+",
388.0,
116.0
)
c.fill(
if (
missileType ==
1
)
240
else
80,
35,
150
)
c.rect(
426.0,
96.0,
48.0,
32.0
)
c.fill(
if (
missileType ==
2
)
240
else
80,
35,
150
)
c.rect(
480.0,
96.0,
48.0,
32.0
)
c.fill(
if (
missileType ==
3
)
240
else
80,
35,
150
)
c.rect(
534.0,
96.0,
48.0,
32.0
)
c.fill(
255,
255,
255
)
c.text(
"A",
444.0,
116.0
)
c.text(
"B",
498.0,
116.0
)
c.text(
"C",
552.0,
116.0
)
c.fill(
200,
0,
240
)
c.rect(
592.0,
96.0,
120.0,
32.0
)
c.fill(
255,
255,
255
)
c.text(
"LAUNCH",
625.0,
116.0
)
}
// ============================================================
// BOSS DRAW
// ============================================================
def drawBoss(
c: CanvasDraw
): Unit = {
if (
!bossActive
) {
return
}
if (
bossShield >
0.0
) {
c.noFill()
c.stroke(
0,
220,
255,
140
)
c.strokeWeight(
5.0
)
c.ellipse(
bossX,
bossY,
165.0 +
math.sin(
bossCore
) * 10.0,
100.0 +
math.sin(
bossCore
) * 8.0
)
}
c.fill(
125,
20,
30
)
c.stroke(
255,
190,
50
)
c.strokeWeight(
3.0
)
c.ellipse(
bossX,
bossY,
125.0,
64.0
)
c.fill(
60,
65,
80
)
c.rect(
bossX - 42.0,
bossY - 40.0,
84.0,
24.0
)
val pulse =
20.0 +
math.abs(
math.sin(
bossCore
)
) * 7.0
c.fill(
255,
50,
40,
150
)
c.noStroke()
c.ellipse(
bossX,
bossY,
pulse + 12.0,
pulse + 12.0
)
c.fill(
255,
50,
40
)
c.ellipse(
bossX,
bossY,
pulse,
pulse
)
// HP
c.fill(
20,
20,
28
)
c.rect(
bossX - 85.0,
bossY + 58.0,
170.0,
10.0
)
val hpRatio =
math.max(
0.0,
math.min(
1.0,
bossHP /
bossMaxHP
)
)
c.fill(
255,
45,
45
)
c.rect(
bossX - 85.0,
bossY + 58.0,
170.0 *
hpRatio,
10.0
)
// Shield
if (
bossShield >
0.0
) {
val shieldRatio =
math.max(
0.0,
math.min(
1.0,
bossShield /
100.0
)
)
c.fill(
0,
210,
255
)
c.rect(
bossX - 85.0,
bossY + 72.0,
170.0 *
shieldRatio,
5.0
)
}
c.fill(
255,
220,
80
)
c.text(
s"PHASE $bossPhase",
bossX - 32.0,
bossY + 95.0
)
}
// ============================================================
// TARGET LOCK
// ============================================================
def drawTargetLock(
c: CanvasDraw
): Unit = {
if (
enemies.nonEmpty
) {
val target =
enemies.head
if (
!target.destroyed
) {
val px =
target.x
val py =
target.y
c.noFill()
c.stroke(
0,
255,
220,
200
)
c.strokeWeight(
2.0
)
c.line(
px - 32.0,
py - 24.0,
px - 10.0,
py - 24.0
)
c.line(
px + 10.0,
py - 24.0,
px + 32.0,
py - 24.0
)
c.line(
px - 32.0,
py + 24.0,
px - 10.0,
py + 24.0
)
c.line(
px + 10.0,
py + 24.0,
px + 32.0,
py + 24.0
)
c.fill(
0,
255,
220
)
c.noStroke()
c.text(
"TARGET",
px - 22.0,
py + 42.0
)
}
}
}
// ============================================================
// COMMAND DRONE
// ============================================================
def drawCommandDrone(
c: CanvasDraw
): Unit = {
val x =
cwidth / 2.0 +
math.cos(
squadDrone
) *
220.0
val y =
250.0 +
math.sin(
squadDrone * 1.7
) *
30.0
c.fill(
40,
50,
60
)
c.stroke(
0,
220,
255
)
c.strokeWeight(
2.0
)
c.ellipse(
x,
y,
38.0,
18.0
)
c.fill(
0,
230,
255
)
c.noStroke()
c.ellipse(
x,
y,
10.0,
10.0
)
c.stroke(
180,
240,
255,
150
)
c.strokeWeight(
2.0
)
c.line(
x - 20.0,
y,
x - 42.0,
y
)
c.line(
x + 20.0,
y,
x + 42.0,
y
)
}
// ============================================================
// SAINIK SCAN
// ============================================================
def drawSainikScan(
c: CanvasDraw
): Unit = {
val cx =
cwidth / 2.0
val cy =
185.0
c.noFill()
c.stroke(
0,
255,
220,
70
)
c.strokeWeight(
2.0
)
c.ellipse(
cx,
cy,
350.0 +
math.sin(
squadScan
) * 20.0,
170.0
)
c.stroke(
0,
230,
255,
100
)
c.line(
cx,
cy,
cx +
math.cos(
squadScan
) *
290.0,
cy +
math.sin(
squadScan
) *
110.0
)
}
// ============================================================
// FINAL STRIKE
// ============================================================
def drawFinalStrike(
c: CanvasDraw
): Unit = {
if (
currentGameState !=
4
) {
return
}
c.pushMatrix()
c.translate(
strikeX,
strikeY
)
c.rotate(
strikeAngle
)
c.fill(
255,
80,
20,
60
)
c.noStroke()
c.ellipse(
-25.0,
0.0,
80.0,
40.0
)
c.fill(
245,
245,
245
)
c.stroke(
150,
30,
30
)
c.strokeWeight(
2.0
)
c.rect(
-38.0,
-12.0,
76.0,
24.0
)
c.fill(
255,
235,
100
)
c.noStroke()
c.ellipse(
40.0,
0.0,
28.0,
28.0
)
c.popMatrix()
}
// ============================================================
// VICTORY
// ============================================================
def drawVictory(
c: CanvasDraw
): Unit = {
c.background(
0,
0,
0
)
drawStarfield(
c
)
val pulse =
1.0 +
math.abs(
math.sin(
universalClock
)
) *
0.15
c.fill(
255,
153,
51
)
c.rect(
cwidth / 2.0 -
220.0,
70.0,
440.0,
5.0
)
c.fill(
255,
255,
255
)
c.rect(
cwidth / 2.0 -
220.0,
61.0,
440.0,
5.0
)
c.fill(
18,
136,
7
)
c.rect(
cwidth / 2.0 -
220.0,
52.0,
440.0,
5.0
)
c.fill(
255,
220,
100,
(
230.0 *
pulse
).toInt
)
c.text(
"JAI HIND",
cwidth / 2.0 -
40.0,
cheight / 2.0 +
110.0
)
c.fill(
255,
255,
255
)
c.text(
"MISSION ACCOMPLISHED",
cwidth / 2.0 -
135.0,
cheight / 2.0 +
55.0
)
c.fill(
255,
235,
190
)
c.text(
"SAINIK COMMAND SUCCEEDED",
cwidth / 2.0 -
125.0,
cheight / 2.0 +
20.0
)
c.fill(
100,
240,
255
)
c.text(
"QUANTUM DEFENSE STATUS: STABLE",
cwidth / 2.0 -
135.0,
cheight / 2.0 -
15.0
)
c.fill(
255,
255,
255
)
c.text(
"SELECTED AVATAR:",
cwidth / 2.0 -
110.0,
cheight / 2.0 -
55.0
)
c.fill(
0,
255,
190
)
c.text(
avatarNames(
selectedAvatar - 1
),
cwidth / 2.0 -
95.0,
cheight / 2.0 -
78.0
)
c.fill(
255,
220,
80
)
c.text(
s"DEFENSE SCORE: $scoreDestroyedCount",
cwidth / 2.0 -
85.0,
cheight / 2.0 -
110.0
)
drawAvatar(
c,
cwidth / 2.0,
cheight / 2.0 -
180.0,
selectedAvatar,
1.35,
false,
true
)
}
// ============================================================
// MAIN COMBAT DRAW
// ============================================================
def drawCombat(
c: CanvasDraw
): Unit = {
drawCombatBackground(
c
)
drawSainikScan(
c
)
drawSquad(
c
)
drawCommandDrone(
c
)
drawTank(
c
)
drawSilo(
c
)
drawAirDefense(
c
)
drawParticles(
c
)
repeatFor(
shells
)(
_.draw(c)
)
repeatFor(
missiles
)(
_.draw(c)
)
repeatFor(
enemies
)(
_.draw(c)
)
repeatFor(
shockwaves
)(
_.draw(c)
)
drawTargetLock(
c
)
if (
bossActive
) {
drawBoss(
c
)
}
drawControls(
c
)
drawHUD(
c
)
// Help
c.fill(
150,
195,
220
)
c.text(
"ARROWS: MOVE / AIM",
270.0,
cheight - 90.0
)
c.text(
"SPACE: FIRE",
270.0,
cheight - 108.0
)
c.text(
"ENTER: LAUNCH MISSILE",
270.0,
cheight - 126.0
)
if (
bossActive
) {
c.fill(
255,
60,
60
)
c.text(
"FLAGSHIP BATTLE ACTIVE",
cwidth / 2.0 -
95.0,
cheight -
35.0
)
}
}
// ============================================================
// WORLD UPDATE
// ============================================================
def updateWorld(): Unit = {
universalClock +=
0.025
globalElapsedTime +=
0.025
avatarAnimClock +=
0.05
avatarButtonClock +=
0.05
commanderMarch +=
0.14
commanderPulse +=
0.06
tankEngine +=
0.08
squadMarch +=
0.14
squadBreath +=
0.05
squadScan +=
0.04
squadDrone +=
0.025
akashAngle +=
0.035
pinakaPulse +=
0.07
bossCore +=
0.09
screenShake *=
0.86
colorFlash *=
0.84
redAlert *=
0.95
cinematicBars *=
0.97
tankRecoil *=
0.85
tankHeat *=
0.99
if (
cannonCooldown >
0
) {
cannonCooldown -=
1
}
if (
missileCooldown >
0
) {
missileCooldown -=
1
}
updateParticles()
if (
!passwordUnlocked
) {
updatePassword()
return
}
if (
currentGameState ==
0
) {
briefingTimer +=
0.04
if (
briefingTimer >=
0.025 &&
briefingIndex <
briefingText.length
) {
briefingIndex +=
1
briefingTimer =
0.0
}
return
}
if (
currentGameState ==
1
) {
squadDeploy =
math.min(
1.0,
squadDeploy +
0.018
)
squadSalute =
0.5 +
math.sin(
universalClock
) *
0.5
if (
squadDeploy >=
1.0
) {
currentGameState =
2
playFX(
82,
140
)
}
return
}
if (
currentGameState ==
2
) {
handleMouseControls()
handleKeyboard()
combatTimer -=
0.033
updateObjects()
checkCollisions()
if (
tankX <
60.0
) {
tankX =
60.0
}
if (
tankX >
cwidth - 60.0
) {
tankX =
cwidth - 60.0
}
if (
randomDouble(
0.0,
1.0
) <
0.055
) {
spawnEnemy()
}
if (
pinakaCooldown <=
0 &&
enemies.nonEmpty
) {
val target =
enemies.head
val dx =
target.x -
pinakaX
val dy =
target.y -
pinakaY
val angle =
math.atan2(
dy,
dx
)
missiles.append(
new Missile(
pinakaX,
pinakaY,
10.5,
angle,
3
)
)
pinakaCooldown =
38
playFX(
68,
30
)
} else {
pinakaCooldown -=
1
}
if (
combatTimer <=
45.0 &&
!bossActive
) {
startBoss()
return
}
return
}
if (
currentGameState ==
3
) {
handleMouseControls()
handleKeyboard()
updateObjects()
checkCollisions()
bossWarning -=
0.033
bossMovement +=
0.035
bossX =
cwidth / 2.0 +
math.sin(
bossMovement *
1.8
) *
225.0
if (
bossPhase ==
1 &&
bossHP <
500.0
) {
bossPhase =
2
redAlert =
1.0
screenShake =
8.0
playFX(
48,
140
)
}
if (
bossPhase ==
2 &&
bossHP <
230.0
) {
bossPhase =
3
redAlert =
1.0
screenShake =
11.0
playFX(
38,
160
)
}
if (
randomDouble(
0.0,
1.0
) <
0.20
) {
spawnParticles(
bossX +
randomDouble(
-50.0,
50.0
),
bossY +
randomDouble(
-30.0,
30.0
),
255,
50,
40,
2
)
}
if (
bossHP <=
0.0
) {
bossActive =
false
currentGameState =
4
strikeX =
tankX +
260.0
strikeY =
cheight +
160.0
strikeSpeed =
8.5
cinematicBars =
12.0
megaExplosion(
bossX,
bossY
)
playFX(
36,
250
)
}
return
}
if (
currentGameState ==
4
) {
val dx =
tankX -
strikeX
val dy =
(
tankY +
50.0
) -
strikeY
val targetAngle =
math.atan2(
dy,
dx
)
strikeAngle +=
(
targetAngle -
strikeAngle
) *
0.08
strikeX +=
math.cos(
strikeAngle
) *
strikeSpeed
strikeY +=
math.sin(
strikeAngle
) *
strikeSpeed
strikeSpeed +=
0.06
if (
math.abs(
strikeX -
tankX
) <
30.0 &&
math.abs(
strikeY -
(
tankY +
50.0
)
) <
30.0
) {
megaExplosion(
tankX,
tankY +
50.0
)
currentGameState =
5
playFX(
36,
300
)
}
updateObjects()
return
}
if (
currentGameState ==
5
) {
updateObjects()
return
}
}
// ============================================================
// COMPLETE PIPELINE
// ============================================================
def renderGame(
c: CanvasDraw
): Unit = {
if (
currentGameState ==
-1
) {
drawPasswordScreen(
c
)
return
}
if (
currentGameState ==
-2
) {
drawAvatarSelectionScreen(
c
)
return
}
if (
currentGameState ==
0
) {
drawBriefing(
c
)
return
}
if (
currentGameState ==
1
) {
drawDeployment(
c
)
return
}
if (
currentGameState ==
5
) {
drawVictory(
c
)
return
}
if (
screenShake >
0.5
) {
c.pushMatrix()
c.translate(
randomDouble(
-screenShake,
screenShake
),
randomDouble(
-screenShake,
screenShake
)
)
}
drawCombat(
c
)
if (
currentGameState ==
4
) {
drawFinalStrike(
c
)
}
if (
screenShake >
0.5
) {
c.popMatrix()
}
if (
colorFlash >
0.01
) {
c.fill(
255,
245,
210,
math.max(
0,
math.min(
255,
(
colorFlash *
255.0
).toInt
)
)
)
c.noStroke()
c.rect(
0.0,
0.0,
cwidth,
cheight
)
}
if (
redAlert >
0.02
) {
c.noFill()
c.stroke(
255,
20,
20,
math.min(
190,
(
redAlert *
190.0
).toInt
)
)
c.strokeWeight(
6.0
)
c.rect(
4.0,
4.0,
cwidth - 8.0,
cheight - 8.0
)
}
val bars =
math.max(
0,
math.min(
55,
cinematicBars.toInt
)
)
if (
bars >
0
) {
c.fill(
0,
0,
0
)
c.noStroke()
c.rect(
0.0,
0.0,
cwidth,
bars
)
c.rect(
0.0,
cheight -
bars,
cwidth,
bars
)
}
}
// ============================================================
// MAIN LOOP
// ============================================================
animateWithCanvasDraw { c =>
updateWorld()
renderGame(
c
)
}