diff --git a/lib/ocelot-brain b/lib/ocelot-brain index a0eea2a..e7b477c 160000 --- a/lib/ocelot-brain +++ b/lib/ocelot-brain @@ -1 +1 @@ -Subproject commit a0eea2abcae926908a232fee827ebbd28782a326 +Subproject commit e7b477cf4d369d91132275237e72c80ad5440e40 diff --git a/src/main/scala/ocelot/desktop/OcelotDesktop.scala b/src/main/scala/ocelot/desktop/OcelotDesktop.scala index b3c4100..03acdfb 100644 --- a/src/main/scala/ocelot/desktop/OcelotDesktop.scala +++ b/src/main/scala/ocelot/desktop/OcelotDesktop.scala @@ -2,8 +2,6 @@ package ocelot.desktop import buildinfo.BuildInfo import li.flor.nativejfilechooser.NativeJFileChooser -import ocelot.desktop.audio._ -import ocelot.desktop.node.nodes.{IronNoteBlockNode, NoteBlockNode} import ocelot.desktop.ui.UiHandler import ocelot.desktop.ui.swing.SplashScreen import ocelot.desktop.ui.widget._ @@ -15,8 +13,6 @@ import ocelot.desktop.util._ import org.apache.commons.io.FileUtils import org.apache.logging.log4j.LogManager import totoro.ocelot.brain.Ocelot -import totoro.ocelot.brain.event.FileSystemActivityType.Floppy -import totoro.ocelot.brain.event._ import totoro.ocelot.brain.nbt.ExtendedNBT.{extendNBTTagCompound, extendNBTTagList} import totoro.ocelot.brain.nbt.{CompressedStreamTools, NBT, NBTTagCompound, NBTTagString} import totoro.ocelot.brain.user.User @@ -30,7 +26,7 @@ import scala.collection.mutable import scala.collection.mutable.ArrayBuffer import scala.io.Source import scala.jdk.CollectionConverters._ -import scala.util.{Failure, Random, Success, Try} +import scala.util.{Failure, Success, Try} object OcelotDesktop extends Logging { private val splashScreen = new SplashScreen() @@ -49,8 +45,6 @@ object OcelotDesktop extends Logging { private val tickLock: Lock = new ReentrantLock() - private val random: Random = new Random() - def withTickLockAcquired(f: => Unit): Unit = withLockAcquired(tickLock, f) private def mainInner(args: mutable.HashMap[Argument, Option[String]]): Unit = { @@ -80,8 +74,6 @@ object OcelotDesktop extends Logging { // loading resources _after_ the UiHandler was initialized, because the native libraries are not available before ResourceManager.initResources() - setupEventHandlers() - splashScreen.setStatus("Loading workspace...", 0.90f) val cmdLineWorkspaceArgument = args.get(CommandLine.WorkspacePath).flatten if (loadRecentWorkspace || cmdLineWorkspaceArgument.isDefined) { @@ -368,38 +360,4 @@ object OcelotDesktop extends Logging { private def createWorkspace(): Unit = { workspace = new Workspace(tmpPath) } - - private def setupEventHandlers(): Unit = { - EventBus.subscribe[MachineCrashEvent] { event => - logger.info(s"[EVENT] Machine crash! (address = ${event.address}, ${event.message})") - } - - if (!Audio.isDisabled) { - EventBus.subscribe[BeepEvent] { event => - BeepGenerator.newBeep(".", event.frequency, event.duration).play() - } - - EventBus.subscribe[BeepPatternEvent] { event => - BeepGenerator.newBeep(event.pattern, 1000, 200).play() - } - - EventBus.subscribe[NoteBlockTriggerEvent] { event => - SoundSource.fromBuffer(SoundBuffers.NoteBlock(event.instrument), SoundCategory.Beep, - pitch = NoteBlockNode.Pitches(event.pitch), volume = event.volume.toFloat.min(1f).max(0f)).play() - val node = UiHandler.root.workspaceView.nodes.find(_.entity.node.address == event.address).get - node match { - case nb: NoteBlockNode => nb.addParticle(event.pitch) - case nb: IronNoteBlockNode => nb.addParticle(event.pitch) - case _ => - } - } - - val soundFloppyAccess = SoundBuffers.MachineFloppyAccess.map(buffer => SoundSource.fromBuffer(buffer, SoundCategory.Environment)) - val soundHDDAccess = SoundBuffers.MachineHDDAccess.map(buffer => SoundSource.fromBuffer(buffer, SoundCategory.Environment)) - EventBus.subscribe[FileSystemActivityEvent] { event => - val sound = if (event.activityType == Floppy) soundFloppyAccess else soundHDDAccess - sound(random.nextInt(sound.length)).play() - } - } - } } diff --git a/src/main/scala/ocelot/desktop/node/Node.scala b/src/main/scala/ocelot/desktop/node/Node.scala index f32728a..890e356 100644 --- a/src/main/scala/ocelot/desktop/node/Node.scala +++ b/src/main/scala/ocelot/desktop/node/Node.scala @@ -132,7 +132,7 @@ abstract class Node(val entity: Entity with Environment) extends Widget with Dra menu.addEntry(new ContextMenuEntry("Delete", () => { dispose() - workspaceView.nodes -= this + workspaceView.nodes = workspaceView.nodes.filter(_ != this) })) } @@ -155,6 +155,8 @@ abstract class Node(val entity: Entity with Environment) extends Widget with Dra def getNodeByPort(port: NodePort): network.Node = entity.node + def shouldReceiveEventsFor(address: String): Boolean = address == entity.node.address + def connections: Iterator[(NodePort, Node, NodePort)] = _connections.iterator def connect(portA: NodePort, node: Node, portB: NodePort): Unit = { diff --git a/src/main/scala/ocelot/desktop/node/nodes/ComputerNode.scala b/src/main/scala/ocelot/desktop/node/nodes/ComputerNode.scala index 50f74ab..c0ba85a 100644 --- a/src/main/scala/ocelot/desktop/node/nodes/ComputerNode.scala +++ b/src/main/scala/ocelot/desktop/node/nodes/ComputerNode.scala @@ -1,23 +1,26 @@ package ocelot.desktop.node.nodes -import ocelot.desktop.audio.{SoundBuffers, SoundCategory, SoundSource} +import ocelot.desktop.audio._ import ocelot.desktop.color.Color import ocelot.desktop.graphics.Graphics import ocelot.desktop.node.Node import ocelot.desktop.ui.event.sources.KeyEvents -import ocelot.desktop.ui.event.{ClickEvent, MouseEvent} +import ocelot.desktop.ui.event.{BrainEvent, ClickEvent, MouseEvent} import ocelot.desktop.ui.widget.contextmenu.{ContextMenu, ContextMenuEntry, ContextMenuSubmenu} import ocelot.desktop.ui.widget.slot._ -import ocelot.desktop.util.TierColor +import ocelot.desktop.util.{Logging, TierColor} import org.lwjgl.input.Keyboard -import totoro.ocelot.brain.entity.traits.{Entity, Floppy, GenericCPU, Inventory} +import totoro.ocelot.brain.entity.traits.{Entity, Environment, Floppy, GenericCPU, Inventory} import totoro.ocelot.brain.entity.{CPU, Case, EEPROM, GraphicsCard, HDDManaged, HDDUnmanaged, Memory} +import totoro.ocelot.brain.event.FileSystemActivityType.Floppy +import totoro.ocelot.brain.event.{BeepEvent, BeepPatternEvent, FileSystemActivityEvent, MachineCrashEvent} import totoro.ocelot.brain.loot.Loot import totoro.ocelot.brain.util.Tier import scala.reflect.ClassTag +import scala.util.Random -class ComputerNode(val computer: Case) extends Node(computer) { +class ComputerNode(val computer: Case) extends Node(computer) with Logging { var eepromSlot: EEPROMSlot = _ var cpuSlot: CPUSlot = _ var memorySlots: Array[MemorySlot] = _ @@ -30,6 +33,26 @@ class ComputerNode(val computer: Case) extends Node(computer) { setupSlots() refitSlots() + eventHandlers += { + case BrainEvent(event: MachineCrashEvent) => + logger.info(s"[EVENT] Machine crash! (address = ${event.address}, ${event.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: FileSystemActivityEvent) if !Audio.isDisabled => + val soundFloppyAccess = SoundBuffers.MachineFloppyAccess.map(buffer => SoundSource.fromBuffer(buffer, SoundCategory.Environment)) + val soundHDDAccess = SoundBuffers.MachineHDDAccess.map(buffer => SoundSource.fromBuffer(buffer, SoundCategory.Environment)) + val sound = if (event.activityType == Floppy) soundFloppyAccess else soundHDDAccess + sound(Random.between(0, sound.length)).play() + } + + override def shouldReceiveEventsFor(address: String): Boolean = super.shouldReceiveEventsFor(address) || + computer.inventory.entities.exists { case env: Environment => env.node.address == address } + def setup(): ComputerNode = { cpuSlot.owner.put(new CPU(computer.tier.min(2))) memorySlots(0).owner.put(new Memory(computer.tier.min(2) * 2 + 1)) @@ -42,6 +65,7 @@ class ComputerNode(val computer: Case) extends Node(computer) { } override val icon: String = "nodes/Computer" + override def iconColor: Color = TierColor.get(computer.tier) override protected val canOpen = true diff --git a/src/main/scala/ocelot/desktop/node/nodes/NoteBlockNodeBase.scala b/src/main/scala/ocelot/desktop/node/nodes/NoteBlockNodeBase.scala index 9b67318..5edfb77 100644 --- a/src/main/scala/ocelot/desktop/node/nodes/NoteBlockNodeBase.scala +++ b/src/main/scala/ocelot/desktop/node/nodes/NoteBlockNodeBase.scala @@ -1,15 +1,25 @@ package ocelot.desktop.node.nodes import ocelot.desktop.ColorScheme +import ocelot.desktop.audio.{SoundBuffers, SoundCategory, SoundSource} import ocelot.desktop.geometry.{Size2D, Vector2D} import ocelot.desktop.graphics.Graphics import ocelot.desktop.node.Node import ocelot.desktop.ui.UiHandler +import ocelot.desktop.ui.event.BrainEvent import totoro.ocelot.brain.entity.traits.{Entity, Environment} +import totoro.ocelot.brain.event.NoteBlockTriggerEvent import scala.collection.mutable abstract class NoteBlockNodeBase(entity: Entity with Environment) extends Node(entity) { + eventHandlers += { + case BrainEvent(event: NoteBlockTriggerEvent) => + SoundSource.fromBuffer(SoundBuffers.NoteBlock(event.instrument), SoundCategory.Beep, + pitch = NoteBlockNode.Pitches(event.pitch), volume = event.volume.toFloat.min(1f).max(0f)).play() + addParticle(event.pitch) + } + private val particles = mutable.ArrayBuffer[(Float, Int)]() def addParticle(pitch: Int): Unit = { diff --git a/src/main/scala/ocelot/desktop/ui/UiHandler.scala b/src/main/scala/ocelot/desktop/ui/UiHandler.scala index ad243db..0dd2ecc 100644 --- a/src/main/scala/ocelot/desktop/ui/UiHandler.scala +++ b/src/main/scala/ocelot/desktop/ui/UiHandler.scala @@ -275,6 +275,7 @@ object UiHandler extends Logging { } def terminate(): Unit = { + root.workspaceView.dispose() KeyEvents.destroy() MouseEvents.destroy() Display.destroy() diff --git a/src/main/scala/ocelot/desktop/ui/event/BrainEvent.scala b/src/main/scala/ocelot/desktop/ui/event/BrainEvent.scala new file mode 100644 index 0000000..d23e6d2 --- /dev/null +++ b/src/main/scala/ocelot/desktop/ui/event/BrainEvent.scala @@ -0,0 +1,3 @@ +package ocelot.desktop.ui.event + +case class BrainEvent(event: totoro.ocelot.brain.event.Event) extends Event diff --git a/src/main/scala/ocelot/desktop/ui/widget/WorkspaceView.scala b/src/main/scala/ocelot/desktop/ui/widget/WorkspaceView.scala index bf6ae4e..3436203 100644 --- a/src/main/scala/ocelot/desktop/ui/widget/WorkspaceView.scala +++ b/src/main/scala/ocelot/desktop/ui/widget/WorkspaceView.scala @@ -15,16 +15,17 @@ import ocelot.desktop.{ColorScheme, OcelotDesktop, Settings} import org.lwjgl.input.Keyboard import totoro.ocelot.brain.entity.traits.{Entity, Environment, SidedEnvironment} import totoro.ocelot.brain.entity.{Case, Screen} +import totoro.ocelot.brain.event.{EventBus, NodeEvent} import totoro.ocelot.brain.nbt.ExtendedNBT._ import totoro.ocelot.brain.nbt.{NBT, NBTBase, NBTTagCompound} import totoro.ocelot.brain.util.{Direction, Tier} -import scala.collection.mutable -import scala.collection.mutable.ArrayBuffer +import scala.collection.{immutable, mutable} import scala.jdk.CollectionConverters._ class WorkspaceView extends Widget with DragHandler with ClickHandler with HoverHandler { - val nodes: ArrayBuffer[Node] = ArrayBuffer[Node]() + @volatile + var nodes: immutable.Seq[Node] = immutable.ArraySeq[Node]() var windowPool = new WindowPool var nodeSelector = new NodeSelector var profilerWindow = new ProfilerWindow @@ -42,15 +43,26 @@ class WorkspaceView extends Widget with DragHandler with ClickHandler with Hover override def hierarchy: Array[Widget] = nodes.toArray } + private val eventSubscription = EventBus.subscribe { case event: NodeEvent => + nodes + .filter(_.shouldReceiveEventsFor(event.address)) + .foreach(_.handleEvent(BrainEvent(event))) + } + def reset(): Unit = { nodes.foreach(_.dispose()) - nodes.clear() + nodes = nodes.empty windowPool.closeAll() nodeSelector = new NodeSelector profilerWindow = new ProfilerWindow cameraOffset = Vector2D(0, 0) } + def dispose(): Unit = { + nodes.foreach(_.dispose()) + eventSubscription.cancel() + } + def load(nbt: NBTTagCompound): Unit = { reset() cameraOffset = new Vector2D(nbt.getCompoundTag("cameraOffset")) @@ -70,7 +82,7 @@ class WorkspaceView extends Widget with DragHandler with ClickHandler with Hover node.workspaceView = this node.root = _root node.load(nbt) - nodes += node + nodes = nodes.appended(node) }) nbt.getTagList("connections", NBT.TAG_COMPOUND).foreach((nbt: NBTTagCompound) => { @@ -97,7 +109,7 @@ class WorkspaceView extends Widget with DragHandler with ClickHandler with Hover cameraOffset.save(cameraOffsetTag) nbt.setTag("cameraOffset", cameraOffsetTag) - nbt.setTagList("nodes", nodes.map(node => { + nbt.setTagList("nodes", nodes.toList.map(node => { val nbt = new NBTTagCompound nbt.setString("class", node.getClass.getName) nbt.setString("entityClass", node.entity.getClass.getName) @@ -146,7 +158,7 @@ class WorkspaceView extends Widget with DragHandler with ClickHandler with Hover node.workspaceView = this node.root = _root node.parent = Some(this) - nodes += node + nodes = nodes.appended(node) } def buildNewConnection(): Unit = { @@ -259,8 +271,7 @@ class WorkspaceView extends Widget with DragHandler with ClickHandler with Hover b: Array[(Vector2D, Vector2D)], checkCollision: Boolean, forceParallel: Boolean, - clampMin: Boolean): Array[Array[Vector2D]] = - { + clampMin: Boolean): Array[Array[Vector2D]] = { val product = for (x <- a; y <- b) yield (x, y) var iter = product.map { case ((aSide, aCenter), (bSide, bCenter)) => val (aLen, bLen) = connectorLen(aSide, aCenter, bSide, bCenter, clampMin) @@ -289,8 +300,7 @@ class WorkspaceView extends Widget with DragHandler with ClickHandler with Hover private def connectorLen(aSide: Vector2D, aCenter: Vector2D, bSide: Vector2D, bCenter: Vector2D, - clampMin: Boolean): (Float, Float) = - { + clampMin: Boolean): (Float, Float) = { val aDir = aSide - aCenter val bDir = bSide - bCenter val xDist = (aSide - bSide).x.abs @@ -395,8 +405,7 @@ class WorkspaceView extends Widget with DragHandler with ClickHandler with Hover private def drawSelectorConnection(g: Graphics, aRect: Rect2D, bRect: Rect2D, thickness: Float = 4, - color: Color = RGBAColor(150, 150, 150)): Unit = - { + color: Color = RGBAColor(150, 150, 150)): Unit = { if (aRect.collides(bRect)) return val (a, b) = if (aRect.x > bRect.x) (aRect, bRect) else (bRect, aRect)