ocelot-desktop/src/main/scala/ocelot/desktop/node/ComputerAwareNode.scala
2025-08-03 17:55:58 +03:00

169 lines
5.4 KiB
Scala

package ocelot.desktop.node
import ocelot.desktop.{ColorScheme, OcelotDesktop, Settings => DesktopSettings}
import ocelot.desktop.audio._
import ocelot.desktop.color.Color
import ocelot.desktop.graphics.{Graphics, IconSource}
import ocelot.desktop.inventory.SyncedInventory
import ocelot.desktop.node.ComputerAwareNode._
import ocelot.desktop.node.Node.Size
import ocelot.desktop.ui.UiHandler
import ocelot.desktop.ui.event.BrainEvent
import ocelot.desktop.ui.event.handlers.DiskActivityHandler
import ocelot.desktop.ui.particle.Particle
import ocelot.desktop.util.Messages
import totoro.ocelot.brain.Settings
import totoro.ocelot.brain.entity.traits.{Entity, Environment, WorkspaceAware}
import totoro.ocelot.brain.entity.SelfDestructingCard
import totoro.ocelot.brain.event._
import java.util.Calendar
abstract class ComputerAwareNode(entity: Entity with Environment with WorkspaceAware)
extends EntityNode(entity)
with SyncedInventory
with DiskActivityHandler
with OcelotLogParticleNode
with SmokeParticleNode
with ShiftClickNode
with PositionalSoundSourcesNode {
private lazy val soundCardSounds: (SoundStream, SoundSource) = Audio.newStream(SoundCategory.Records)
private def soundCardStream: SoundStream = soundCardSounds._1
private def soundCardSource: SoundSource = soundCardSounds._2
// PositionalSoundSourcesNode
override def soundSources: Seq[SoundSource] = Seq(
SoundSource.MinecraftExplosion,
SoundSource.SelfDestructingCardCountdownBeep,
)
private var boomGlowIntensity: Float = -1
eventHandlers += {
case BrainEvent(event: MachineCrashEvent) =>
val message = Messages.lift(event.message) match {
case Some(message) =>
logger.info(s"[EVENT] Machine crash (address = ${event.address})! Message code ${event.message}: $message")
message
case None =>
logger.info(s"[EVENT] Machine crash (address = ${event.address})! Message: ${event.message}")
event.message
}
UiHandler.root.workspaceView.particleSystem.add(new ErrorMessageParticle(message))
case BrainEvent(event: BeepEvent) if !Audio.isDisabled =>
BeepGenerator.newBeep(".", event.frequency, event.duration).play()
case BrainEvent(event: BeepPatternEvent) if !Audio.isDisabled =>
BeepGenerator.newBeep(event.pattern, 1000, 200).play()
case BrainEvent(event: SoundCardAudioEvent) if !Audio.isDisabled =>
val samples = SoundSamples(event.data, Settings.get.soundCardSampleRate, SoundSamples.Format.Mono8)
soundCardStream.enqueue(samples)
soundCardSource.volume = event.volume
case BrainEvent(_: SelfDestructingCardBoomEvent) =>
OcelotDesktop.updateThreadTasks.add(() => {
SoundSource.MinecraftExplosion.play()
emitSmoke()
destroy()
})
}
override def update(): Unit = {
super.update()
updateBoomCardState()
}
private def updateBoomCardState(): Unit = {
boomGlowIntensity = -1
for (slot <- brainInventory.inventory) {
slot.get match {
case Some(card: SelfDestructingCard) =>
if (card.time > 0) {
// If multiple SDCs are ticking, let the most soon exploding one to define the glow
boomGlowIntensity = boomGlowIntensity.max(1 - card.time.toFloat / card.initialTime)
if (card.lastBeepTime < 0 || card.lastBeepTime - card.time >= 20) {
SoundSource.SelfDestructingCardCountdownBeep.play()
card.lastBeepTime = card.time
}
}
case _ =>
}
}
}
protected def drawOverlay(g: Graphics): Unit = HolidayIcon match {
case Some(icon) if DesktopSettings.get.enableFestiveDecorations =>
val holidayOverlaySize = Size * 1.6f
val offset = (holidayOverlaySize - Size) / 2
g.sprite(
icon,
position.x - offset,
position.y - offset,
holidayOverlaySize,
holidayOverlaySize,
)
case _ =>
}
override def drawLight(g: Graphics): Unit = {
super.drawLight(g)
if (boomGlowIntensity > 0) {
g.sprite(
IconSource.Nodes.Lamp.Glow,
position - size / 2,
size * 2,
Color.Transparent.lerp(ColorScheme("BoomCardGlow"), boomGlowIntensity),
)
}
}
override def draw(g: Graphics): Unit = {
super.draw(g)
drawOverlay(g)
}
private class ErrorMessageParticle(message: String) extends Particle(speed = ErrorMessageMoveSpeed) {
private val offsetX = size.width / 2 - message.length * 4
private val offsetY = -8
override def draw(g: Graphics): Unit = {
g.setSmallFont()
g.foreground = ColorScheme("ErrorMessage").withAlpha(1 - (2 * time - 1).min(1).max(0))
g.text(position.x + offsetX, position.y + offsetY - MaxErrorMessageDistance * time, message)
g.setNormalFont()
}
}
}
object ComputerAwareNode {
private val MaxErrorMessageDistance: Float = 50f
private val ErrorMessageMoveSpeed: Float = 0.5f
private val HolidayIcon: Option[IconSource] = {
var dayOfYear = Calendar.getInstance().get(Calendar.DAY_OF_YEAR)
val maxDuration = 5
if (dayOfYear >= 365 - maxDuration) {
dayOfYear -= 365
}
val holidays: Array[(Int, IconSource)] = Array(
(1, IconSource.Nodes.Holidays.Christmas),
(45, IconSource.Nodes.Holidays.Valentines),
(305, IconSource.Nodes.Holidays.Halloween),
)
holidays
.find(holiday => dayOfYear >= holiday._1 - maxDuration && dayOfYear <= holiday._1 + maxDuration)
.map(holiday => holiday._2)
}
}