Add scalafmt

This commit is contained in:
LeshaInc 2025-01-30 20:29:55 +00:00
parent edd9916905
commit ee4f2dcc3b
194 changed files with 2153 additions and 1888 deletions

View File

@ -29,6 +29,13 @@ test:
script: script:
- sbt test - sbt test
scalafmt:
stage: test
before_script:
- sbt -v sbtVersion
script:
- sbt scalafmtCheckAll
build: build:
stage: build stage: build
before_script: before_script:

35
.scalafmt.conf Normal file
View File

@ -0,0 +1,35 @@
version = 3.8.6
runner.dialect = scala213
preset = default
maxColumn = 120
indent.defnSite = 2
align = {
preset = none
openParenDefnSite = true
}
newlines = {
source = keep
topLevelStatementBlankLines = [
{ blanks { before = 1, after = 1, beforeAll = -1, afterAll = -1 } }
]
}
binPack = {
preset = Oneline
literalsExclude = []
}
rewrite = {
rules = [SortModifiers, Imports]
sortModifiers.preset = styleGuide
imports.sort = scalastyle
trailingCommas.style = multiple
insertBraces.minLines = 3
}
docstrings = {
oneline = fold
wrap = keep
}

View File

@ -1 +0,0 @@
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "2.1.1")

View File

@ -1,2 +1,2 @@
# suppress inspection "UnusedProperty" for whole file # suppress inspection "UnusedProperty" for whole file
sbt.version = 1.8.3 sbt.version = 1.10.7

View File

@ -1 +0,0 @@
addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.11.0")

3
project/plugins.sbt Normal file
View File

@ -0,0 +1,3 @@
addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.13.1")
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "2.3.1")
addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.5.4")

View File

@ -28,7 +28,7 @@ class ColorScheme extends Logging {
val bytes = java.lang.Long.parseLong(value.substring(1), 16) val bytes = java.lang.Long.parseLong(value.substring(1), 16)
val color = if (value.length == 9) { val color = if (value.length == 9) {
val rgb = bytes >> 8 val rgb = bytes >> 8
IntColor(rgb.toInt).toRGBANorm.withAlpha((bytes & 0xFF).toFloat / 255f) IntColor(rgb.toInt).toRGBANorm.withAlpha((bytes & 0xff).toFloat / 255f)
} else IntColor(bytes.toInt).toRGBANorm } else IntColor(bytes.toInt).toRGBANorm
entries.addOne((key, color)) entries.addOne((key, color))
} }

View File

@ -30,11 +30,8 @@ import scala.io.Source
import scala.jdk.CollectionConverters._ import scala.jdk.CollectionConverters._
import scala.util.{Failure, Success, Try, Using} import scala.util.{Failure, Success, Try, Using}
object OcelotDesktop // LoggingConfiguration configures Log4j appenders & loggers, it should come before Logging in inheritance hierarchy
// This configures Log4j appenders & loggers, it should come before Logging in inheritance hierarchy object OcelotDesktop extends LoggingConfiguration with Logging {
extends LoggingConfiguration
with Logging
{
System.setProperty("awt.useSystemAAFontSettings", "on") System.setProperty("awt.useSystemAAFontSettings", "on")
System.setProperty("swing.aatext", "true") System.setProperty("swing.aatext", "true")
System.setProperty("LWJGL_WM_CLASS", "Ocelot Desktop") System.setProperty("LWJGL_WM_CLASS", "Ocelot Desktop")
@ -70,11 +67,10 @@ object OcelotDesktop
splashScreen.setStatus("Loading configuration...", 0.10f) splashScreen.setStatus("Loading configuration...", 0.10f)
val customConfigPath = args.get(CommandLine.ConfigPath).flatten val customConfigPath = args.get(CommandLine.ConfigPath).flatten
val desktopConfigPath: Path = val desktopConfigPath: Path = {
if (customConfigPath.isDefined) { if (customConfigPath.isDefined) {
Paths.get(customConfigPath.get) Paths.get(customConfigPath.get)
} } else {
else {
// TODO: migration for old locations of ocelot.conf, can be safely removed later // TODO: migration for old locations of ocelot.conf, can be safely removed later
// TODO: uncomment this line and delete everything below it when you're ready! // TODO: uncomment this line and delete everything below it when you're ready!
// OcelotPaths.desktopConfig // OcelotPaths.desktopConfig
@ -83,23 +79,24 @@ object OcelotDesktop
try { try {
if (!Files.exists(newConfigPath)) { if (!Files.exists(newConfigPath)) {
val oldConfigPath = val oldConfigPath = {
if (SystemUtils.IS_OS_WINDOWS) if (SystemUtils.IS_OS_WINDOWS)
Paths.get(OcelotPaths.windowsAppDataDirectoryName, "Ocelot", "ocelot.conf") Paths.get(OcelotPaths.windowsAppDataDirectoryName, "Ocelot", "ocelot.conf")
else else
Paths.get(OcelotPaths.linuxHomeDirectoryName, ".config", "ocelot", "ocelot.conf") Paths.get(OcelotPaths.linuxHomeDirectoryName, ".config", "ocelot", "ocelot.conf")
}
if (Files.exists(oldConfigPath)) if (Files.exists(oldConfigPath))
Files.move(oldConfigPath, newConfigPath) Files.move(oldConfigPath, newConfigPath)
} }
} } catch {
catch {
case _: Throwable => case _: Throwable =>
} }
// TODO: end of upper todo <3 // TODO: end of upper todo <3
newConfigPath newConfigPath
} }
}
Settings.load(desktopConfigPath) Settings.load(desktopConfigPath)
@ -157,7 +154,9 @@ object OcelotDesktop
} }
} }
val updateThread = new Thread(() => try { val updateThread = new Thread(
() =>
try {
val currentThread = Thread.currentThread() val currentThread = Thread.currentThread()
while (!currentThread.isInterrupted) { while (!currentThread.isInterrupted) {
@ -174,7 +173,9 @@ object OcelotDesktop
} }
} catch { } catch {
case _: InterruptedException => // ignore case _: InterruptedException => // ignore
}, "update-thread") },
"update-thread",
)
updateThread.start() updateThread.start()
splashScreen.dispose() splashScreen.dispose()
@ -184,7 +185,8 @@ object OcelotDesktop
logger.info("Cleaning up") logger.info("Cleaning up")
updateThread.interrupt() updateThread.interrupt()
try updateThread.join() catch { try updateThread.join()
catch {
case _: InterruptedException => case _: InterruptedException =>
} }
@ -234,7 +236,9 @@ object OcelotDesktop
root.workspaceView.load(frontendNBT) root.workspaceView.load(frontendNBT)
if (frontendNBT.hasKey("players")) { if (frontendNBT.hasKey("players")) {
players.clear() players.clear()
players.addAll(frontendNBT.getTagList("players", NBT.TAG_STRING).map((player: NBTTagString) => User(player.getString))) players.addAll(
frontendNBT.getTagList("players", NBT.TAG_STRING).map((player: NBTTagString) => User(player.getString))
)
} }
} }
@ -288,12 +292,13 @@ object OcelotDesktop
val oldPath = workspace.path val oldPath = workspace.path
if (oldPath != outputPath) { if (oldPath != outputPath) {
val (oldFiles, newFiles) = val (oldFiles, newFiles) = {
Using.resources(Files.list(oldPath), Files.list(outputPath)) { (oldDirStream, newDirStream) => Using.resources(Files.list(oldPath), Files.list(outputPath)) { (oldDirStream, newDirStream) =>
val oldFiles = oldDirStream.iterator.asScala.toArray val oldFiles = oldDirStream.iterator.asScala.toArray
val newFiles = newDirStream.iterator.asScala.toArray val newFiles = newDirStream.iterator.asScala.toArray
(oldFiles, newFiles) (oldFiles, newFiles)
} }
}
val toRemove = newFiles.intersect(oldFiles) val toRemove = newFiles.intersect(oldFiles)
@ -311,8 +316,7 @@ object OcelotDesktop
if (Files.isDirectory(path)) { if (Files.isDirectory(path)) {
FileUtils.copyDirectory(oldFile, newFile) FileUtils.copyDirectory(oldFile, newFile)
} } else {
else {
FileUtils.copyFile(oldFile, newFile) FileUtils.copyFile(oldFile, newFile)
} }
} }
@ -355,11 +359,12 @@ object OcelotDesktop
def saveAs(): Unit = showSaveDialog() def saveAs(): Unit = showSaveDialog()
def showOpenDialog(): Unit = def showOpenDialog(): Unit = {
showFileChooserDialog(JFileChooser.OPEN_DIALOG, JFileChooser.DIRECTORIES_ONLY) { showFileChooserDialog(JFileChooser.OPEN_DIALOG, JFileChooser.DIRECTORIES_ONLY) {
case Some(dir) => load(dir) case Some(dir) => load(dir)
case None => Success(()) case None => Success(())
} }
}
def load(dir: File): Try[Unit] = { def load(dir: File): Try[Unit] = {
val path = Paths.get(dir.getCanonicalPath, "workspace.nbt") val path = Paths.get(dir.getCanonicalPath, "workspace.nbt")
@ -410,7 +415,7 @@ object OcelotDesktop
val result = f(selectedFile) val result = f(selectedFile)
result match { result match {
case f@Failure(_) => showFailureMessage(f) case f @ Failure(_) => showFailureMessage(f)
case Success(_) => case Success(_) =>
} }
}) })
@ -424,13 +429,13 @@ object OcelotDesktop
new NotificationDialog( new NotificationDialog(
s"Something went wrong!\n($exception)\nCheck the log file for a full stacktrace.", s"Something went wrong!\n($exception)\nCheck the log file for a full stacktrace.",
NotificationType.Error NotificationType.Error,
).addCloseButton().show() ).addCloseButton().show()
} }
def showAddPlayerDialog(): Unit = new InputDialog( def showAddPlayerDialog(): Unit = new InputDialog(
"Add new player", "Add new player",
text => OcelotDesktop.selectPlayer(text) text => OcelotDesktop.selectPlayer(text),
).show() ).show()
def player: User = if (players.nonEmpty) players.head else User("myself") def player: User = if (players.nonEmpty) players.head else User("myself")
@ -464,13 +469,16 @@ object OcelotDesktop
} }
private def prepareSavePath(path: Path)(continuation: => Unit): Unit = { private def prepareSavePath(path: Path)(continuation: => Unit): Unit = {
val nonEmpty = try Using.resource(Files.list(path))(_.iterator.asScala.nonEmpty) catch { val nonEmpty = {
try Using.resource(Files.list(path))(_.iterator.asScala.nonEmpty)
catch {
case _: FileNotFoundException | _: NoSuchFileException => case _: FileNotFoundException | _: NoSuchFileException =>
logger.info(s"Save path $path does not exist: creating a new directory") logger.info(s"Save path $path does not exist: creating a new directory")
Files.createDirectory(path) Files.createDirectory(path)
false false
} }
}
if (nonEmpty) { if (nonEmpty) {
new NotificationDialog( new NotificationDialog(
@ -478,7 +486,7 @@ object OcelotDesktop
|Files in the save directory will be included in the workspace. |Files in the save directory will be included in the workspace.
|They may be overwritten, causing loss of data. |They may be overwritten, causing loss of data.
|Proceed with saving anyway?""".stripMargin, |Proceed with saving anyway?""".stripMargin,
NotificationType.Warning NotificationType.Warning,
) { ) {
addButton("Cancel") { addButton("Cancel") {
close() close()
@ -491,7 +499,7 @@ object OcelotDesktop
} else continuation } else continuation
} }
private def showSaveDialog(continuation: => Unit): Unit = private def showSaveDialog(continuation: => Unit): Unit = {
showFileChooserDialog(JFileChooser.SAVE_DIALOG, JFileChooser.DIRECTORIES_ONLY) { dir => showFileChooserDialog(JFileChooser.SAVE_DIALOG, JFileChooser.DIRECTORIES_ONLY) { dir =>
Try { Try {
if (dir.nonEmpty) { if (dir.nonEmpty) {
@ -512,6 +520,7 @@ object OcelotDesktop
} }
} }
} }
}
private def showCloseConfirmationDialog(prompt: Option[String])(continuation: => Unit): Unit = { private def showCloseConfirmationDialog(prompt: Option[String])(continuation: => Unit): Unit = {
if (UiHandler.root.modalDialogPool.children.exists(_.isInstanceOf[CloseConfirmationDialog])) { if (UiHandler.root.modalDialogPool.children.exists(_.isInstanceOf[CloseConfirmationDialog])) {

View File

@ -81,19 +81,23 @@ object Settings extends Logging {
if (config.hasPath(path)) config.getDouble(path) if (config.hasPath(path)) config.getDouble(path)
else default else default
def withValuePreserveOrigin(path: String, value: Any): Config = def withValuePreserveOrigin(path: String, value: Any): Config = {
config.withValue(path, config.withValue(
path,
if (config.hasPath(path)) if (config.hasPath(path))
ConfigValueFactory.fromAnyRef(value).withOrigin(config.getValue(path).origin()) ConfigValueFactory.fromAnyRef(value).withOrigin(config.getValue(path).origin())
else ConfigValueFactory.fromAnyRef(value) else ConfigValueFactory.fromAnyRef(value),
) )
}
def withValuePreserveOrigin(path: String, value: Option[Any]): Config = def withValuePreserveOrigin(path: String, value: Option[Any]): Config = {
config.withValue(path, config.withValue(
path,
if (config.hasPath(path)) if (config.hasPath(path))
ConfigValueFactory.fromAnyRef(value.orNull).withOrigin(config.getValue(path).origin()) ConfigValueFactory.fromAnyRef(value.orNull).withOrigin(config.getValue(path).origin())
else ConfigValueFactory.fromAnyRef(value.orNull) else ConfigValueFactory.fromAnyRef(value.orNull),
) )
}
def withValue(path: String, value: Int2D): Config = { def withValue(path: String, value: Int2D): Config = {
config.withValue(path, ConfigValueFactory.fromIterable(util.Arrays.asList(value.x, value.y))) config.withValue(path, ConfigValueFactory.fromIterable(util.Arrays.asList(value.x, value.y)))
@ -107,6 +111,7 @@ object Settings extends Logging {
var isSet: Boolean = false var isSet: Boolean = false
def this() = this(0, 0) def this() = this(0, 0)
def this(list: util.List[Integer]) = { def this(list: util.List[Integer]) = {
this() this()
if (list.size() == 2) { if (list.size() == 2) {
@ -143,12 +148,10 @@ object Settings extends Logging {
logger.info(s"Loaded Ocelot Desktop configuration from: $path") logger.info(s"Loaded Ocelot Desktop configuration from: $path")
return return
} } catch {
catch {
case _: Throwable => case _: Throwable =>
logger.info(s"Failed to parse $path, using default Ocelot Desktop configuration.") logger.info(s"Failed to parse $path, using default Ocelot Desktop configuration.")
} } finally {
finally {
if (stream != null) if (stream != null)
stream.close() stream.close()
} }

View File

@ -15,9 +15,7 @@ object Audio extends Logging {
private val sources = new mutable.HashMap[SoundSource, Int] private val sources = new mutable.HashMap[SoundSource, Int]
private var _disabled = true private var _disabled = true
/** /** Should be called _before_ initializing any sound-related resources */
* Should be called _before_ initializing any sound-related resources
*/
def init(): Unit = { def init(): Unit = {
try { try {
AL.create() AL.create()
@ -39,7 +37,7 @@ object Audio extends Logging {
def newStream( def newStream(
soundCategory: SoundCategory.Value, soundCategory: SoundCategory.Value,
pitch: Float = 1f, pitch: Float = 1f,
volume: Float = 1f volume: Float = 1f,
): (SoundStream, SoundSource) = { ): (SoundStream, SoundSource) = {
var source: SoundSource = null var source: SoundSource = null
@ -188,7 +186,7 @@ object Audio extends Logging {
AL10W.alSourcef( AL10W.alSourcef(
sourceId, sourceId,
AL10.AL_GAIN, AL10.AL_GAIN,
source.volume * SoundCategory.getSettingsValue(source.soundCategory) * Settings.get.volumeMaster source.volume * SoundCategory.getSettingsValue(source.soundCategory) * Settings.get.volumeMaster,
) )
} }

View File

@ -19,8 +19,8 @@ object BeepGenerator {
val value = (math.signum(math.sin(angle)) * 8192).toShort val value = (math.signum(math.sin(angle)) * 8192).toShort
offset += step offset += step
if (offset > 1) offset -= 1 if (offset > 1) offset -= 1
data.put((value & 0xFF).toByte) data.put((value & 0xff).toByte)
data.put(((value >> 8) & 0xFF).toByte) data.put(((value >> 8) & 0xff).toByte)
} }
if (data.hasRemaining) { if (data.hasRemaining) {
for (_ <- 0 until pauseSampleCount) { for (_ <- 0 until pauseSampleCount) {

View File

@ -8,7 +8,8 @@ import java.nio.ByteBuffer
object OggDecoder { object OggDecoder {
def decode(input: InputStream): SoundSamples = { def decode(input: InputStream): SoundSamples = {
val stream = new VorbisStream(new BasicStream(input).getLogicalStreams.iterator().next().asInstanceOf[LogicalOggStream]) val stream =
new VorbisStream(new BasicStream(input).getLogicalStreams.iterator().next().asInstanceOf[LogicalOggStream])
val rate = stream.getIdentificationHeader.getSampleRate val rate = stream.getIdentificationHeader.getSampleRate
val channels = stream.getIdentificationHeader.getChannels val channels = stream.getIdentificationHeader.getChannels

View File

@ -3,12 +3,10 @@ package ocelot.desktop.audio
import scala.util.control import scala.util.control
import scala.util.control.Exception.Catch import scala.util.control.Exception.Catch
case class OpenAlException(func: String, errName: String, code: Int) case class OpenAlException(func: String, errName: String, code: Int) extends Exception(s"OpenAL error: $func: $errName")
extends Exception(s"OpenAL error: $func: $errName")
object OpenAlException { object OpenAlException {
def defaulting[T](default: => T): Catch[T] = control.Exception.failAsValue(classOf[OpenAlException])(default) def defaulting[T](default: => T): Catch[T] = control.Exception.failAsValue(classOf[OpenAlException])(default)
def ignoring: Catch[Unit] = defaulting(()) def ignoring: Catch[Unit] = defaulting(())
} }

View File

@ -6,6 +6,7 @@ import scala.collection.mutable.ArrayBuffer
object SoundBuffers extends Resource { object SoundBuffers extends Resource {
lazy val MachineComputerRunning: SoundBuffer = load("/ocelot/desktop/sounds/machine/computer_running.ogg") lazy val MachineComputerRunning: SoundBuffer = load("/ocelot/desktop/sounds/machine/computer_running.ogg")
lazy val MachineFloppyAccess: Array[SoundBuffer] = Array( lazy val MachineFloppyAccess: Array[SoundBuffer] = Array(
load("/ocelot/desktop/sounds/machine/floppy_access1.ogg"), load("/ocelot/desktop/sounds/machine/floppy_access1.ogg"),
load("/ocelot/desktop/sounds/machine/floppy_access2.ogg"), load("/ocelot/desktop/sounds/machine/floppy_access2.ogg"),
@ -14,8 +15,10 @@ object SoundBuffers extends Resource {
load("/ocelot/desktop/sounds/machine/floppy_access5.ogg"), load("/ocelot/desktop/sounds/machine/floppy_access5.ogg"),
load("/ocelot/desktop/sounds/machine/floppy_access6.ogg"), load("/ocelot/desktop/sounds/machine/floppy_access6.ogg"),
) )
lazy val MachineFloppyEject: SoundBuffer = load("/ocelot/desktop/sounds/machine/floppy_eject.ogg") lazy val MachineFloppyEject: SoundBuffer = load("/ocelot/desktop/sounds/machine/floppy_eject.ogg")
lazy val MachineFloppyInsert: SoundBuffer = load("/ocelot/desktop/sounds/machine/floppy_insert.ogg") lazy val MachineFloppyInsert: SoundBuffer = load("/ocelot/desktop/sounds/machine/floppy_insert.ogg")
lazy val MachineHDDAccess: Array[SoundBuffer] = Array( lazy val MachineHDDAccess: Array[SoundBuffer] = Array(
load("/ocelot/desktop/sounds/machine/hdd_access1.ogg"), load("/ocelot/desktop/sounds/machine/hdd_access1.ogg"),
load("/ocelot/desktop/sounds/machine/hdd_access2.ogg"), load("/ocelot/desktop/sounds/machine/hdd_access2.ogg"),
@ -43,7 +46,7 @@ object SoundBuffers extends Resource {
lazy val NoteBlock: Map[String, SoundBuffer] = List( lazy val NoteBlock: Map[String, SoundBuffer] = List(
"banjo", "basedrum", "bass", "bell", "bit", "chime", "cow_bell", "didgeridoo", "flute", "guitar", "banjo", "basedrum", "bass", "bell", "bit", "chime", "cow_bell", "didgeridoo", "flute", "guitar",
"harp", "hat", "iron_xylophone", "pling", "snare", "xylophone" "harp", "hat", "iron_xylophone", "pling", "snare", "xylophone",
).map(name => { ).map(name => {
(name, load(s"/ocelot/desktop/sounds/minecraft/note_block/$name.ogg")) (name, load(s"/ocelot/desktop/sounds/minecraft/note_block/$name.ogg"))
}).toMap }).toMap

View File

@ -11,7 +11,7 @@ class SoundSource(
val looping: Boolean, val looping: Boolean,
val pitch: Float, val pitch: Float,
var volume: Float, var volume: Float,
var position: Vector3D = Vector3D(0, 0, 0) var position: Vector3D = Vector3D(0, 0, 0),
) { ) {
def duration: Option[Duration] = kind match { def duration: Option[Duration] = kind match {
case SoundSource.KindSoundBuffer(buffer) => case SoundSource.KindSoundBuffer(buffer) =>
@ -124,8 +124,12 @@ object SoundSource {
SoundSource.fromBuffer(SoundBuffers.MinecraftClickRelease, SoundCategory.Interface) SoundSource.fromBuffer(SoundBuffers.MinecraftClickRelease, SoundCategory.Interface)
} }
lazy val MinecraftExplosion: SoundSource = SoundSource.fromBuffer(SoundBuffers.MinecraftExplosion, SoundCategory.Environment) lazy val MinecraftExplosion: SoundSource =
SoundSource.fromBuffer(SoundBuffers.MinecraftExplosion, SoundCategory.Environment)
lazy val MachineFloppyInsert: SoundSource = SoundSource.fromBuffer(SoundBuffers.MachineFloppyInsert, SoundCategory.Environment) lazy val MachineFloppyInsert: SoundSource =
lazy val MachineFloppyEject: SoundSource = SoundSource.fromBuffer(SoundBuffers.MachineFloppyEject, SoundCategory.Environment) SoundSource.fromBuffer(SoundBuffers.MachineFloppyInsert, SoundCategory.Environment)
lazy val MachineFloppyEject: SoundSource =
SoundSource.fromBuffer(SoundBuffers.MachineFloppyEject, SoundCategory.Environment)
} }

View File

@ -6,8 +6,8 @@ case class IntColor(color: Int) extends Color {
override def toRGBA: RGBAColor = { override def toRGBA: RGBAColor = {
RGBAColor( RGBAColor(
(color >> 16).toShort, (color >> 16).toShort,
((color >> 8) & 0xFF).toShort, ((color >> 8) & 0xff).toShort,
(color & 0xFF).toShort, (color & 0xff).toShort,
) )
} }

View File

@ -14,7 +14,7 @@ case class RGBAColor(r: Short, g: Short, b: Short, a: Short = 255) extends Color
r.toFloat / 255f, r.toFloat / 255f,
g.toFloat / 255f, g.toFloat / 255f,
b.toFloat / 255f, b.toFloat / 255f,
a.toFloat / 255f a.toFloat / 255f,
) )
override def toHSVA: HSVAColor = toRGBANorm.toHSVA override def toHSVA: HSVAColor = toRGBANorm.toHSVA

View File

@ -18,7 +18,7 @@ case class RGBAColorNorm(r: Float, g: Float, b: Float, a: Float = 1f) extends Co
def mapA(f: Float => Float): RGBAColorNorm = copy(a = f(a)) def mapA(f: Float => Float): RGBAColorNorm = copy(a = f(a))
final private def componentToLinear(x: Float): Float = { private final def componentToLinear(x: Float): Float = {
if (x <= 0.0404482362771082) if (x <= 0.0404482362771082)
x / 12.92f x / 12.92f
else else

View File

@ -43,7 +43,7 @@ class Camera extends Entity with GenericCamera with DeviceInfo {
DeviceAttribute.Class -> DeviceClass.Multimedia, DeviceAttribute.Class -> DeviceClass.Multimedia,
DeviceAttribute.Description -> "Dungeon Scanner 2.5D", DeviceAttribute.Description -> "Dungeon Scanner 2.5D",
DeviceAttribute.Vendor -> Constants.DeviceInfo.DefaultVendor, DeviceAttribute.Vendor -> Constants.DeviceInfo.DefaultVendor,
DeviceAttribute.Product -> webcamCapture.map(_.name).getOrElse("Blind Pirate") DeviceAttribute.Product -> webcamCapture.map(_.name).getOrElse("Blind Pirate"),
) )
override def load(nbt: NBTTagCompound, workspace: Workspace): Unit = { override def load(nbt: NBTTagCompound, workspace: Workspace): Unit = {

View File

@ -19,11 +19,12 @@ import javax.sound.sampled.AudioFormat.Encoding
import javax.sound.sampled.{AudioFormat, AudioSystem} import javax.sound.sampled.{AudioFormat, AudioSystem}
class OpenFMRadio extends Entity with Environment with DeviceInfo with Logging { class OpenFMRadio extends Entity with Environment with DeviceInfo with Logging {
override val node: Component = override val node: Component = {
Network Network
.newNode(this, Visibility.Network) .newNode(this, Visibility.Network)
.withComponent("openfm_radio", Visibility.Network) .withComponent("openfm_radio", Visibility.Network)
.create() .create()
}
// --------------------------- URL --------------------------- // --------------------------- URL ---------------------------
@ -44,14 +45,18 @@ class OpenFMRadio extends Entity with Environment with DeviceInfo with Logging {
private def playSynchronously(): Unit = { private def playSynchronously(): Unit = {
try { try {
// Trying to parse URL and sending request to host // Trying to parse URL and sending request to host
val connection = val connection = {
new URI(url.get) new URI(url.get)
.toURL .toURL
.openConnection .openConnection
.asInstanceOf[HttpURLConnection] .asInstanceOf[HttpURLConnection]
}
connection.setRequestMethod("GET") connection.setRequestMethod("GET")
connection.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36") connection.setRequestProperty(
"User-Agent",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36",
)
connection.setRequestProperty("Content-Language", "en-US") connection.setRequestProperty("Content-Language", "en-US")
connection.setDoInput(true) connection.setDoInput(true)
connection.setDoOutput(true) connection.setDoOutput(true)
@ -65,21 +70,22 @@ class OpenFMRadio extends Entity with Environment with DeviceInfo with Logging {
2, 2,
4, 4,
44100, 44100,
false false,
), ),
// Obtaining audio input stream from HTTP connection // Obtaining audio input stream from HTTP connection
AudioSystem.getAudioInputStream(new BufferedInputStream(connection.getInputStream)) AudioSystem.getAudioInputStream(new BufferedInputStream(connection.getInputStream)),
) )
// Keeping input stream format parameters here to offload the reading loop // Keeping input stream format parameters here to offload the reading loop
val inputStreamFormat = inputStream.getFormat val inputStreamFormat = inputStream.getFormat
val inputStreamSampleRate = inputStreamFormat.getSampleRate.toInt val inputStreamSampleRate = inputStreamFormat.getSampleRate.toInt
val inputStreamSoundSampleFormat = val inputStreamSoundSampleFormat = {
if (inputStreamFormat.getChannels > 1) if (inputStreamFormat.getChannels > 1)
Format.Stereo16 Format.Stereo16
else else
Format.Mono16 Format.Mono16
}
// Creating Ocelot output sound stream // Creating Ocelot output sound stream
val (outputStream, outputSource) = Audio.newStream(SoundCategory.Records, volume = volume) val (outputStream, outputSource) = Audio.newStream(SoundCategory.Records, volume = volume)
@ -99,7 +105,7 @@ class OpenFMRadio extends Entity with Environment with DeviceInfo with Logging {
.flip .flip
.asInstanceOf[ByteBuffer], .asInstanceOf[ByteBuffer],
inputStreamSampleRate, inputStreamSampleRate,
inputStreamSoundSampleFormat inputStreamSoundSampleFormat,
)) ))
} }
@ -107,8 +113,7 @@ class OpenFMRadio extends Entity with Environment with DeviceInfo with Logging {
} }
logger.info("OpenFM input audio stream has reached EOF, closing thread") logger.info("OpenFM input audio stream has reached EOF, closing thread")
} } catch {
catch {
case _: InterruptedException => case _: InterruptedException =>
case e: Exception => logger.error("OpenFM playback exception", e) case e: Exception => logger.error("OpenFM playback exception", e)
} }
@ -168,6 +173,7 @@ class OpenFMRadio extends Entity with Environment with DeviceInfo with Logging {
private var _volume: Float = 1 private var _volume: Float = 1
def volume: Float = _volume def volume: Float = _volume
def volume_=(value: Float): Unit = { def volume_=(value: Float): Unit = {
_volume = value _volume = value
@ -211,7 +217,7 @@ class OpenFMRadio extends Entity with Environment with DeviceInfo with Logging {
// --------------------------- Screen color/text --------------------------- // --------------------------- Screen color/text ---------------------------
private val defaultScreenText = "OpenFM" private val defaultScreenText = "OpenFM"
private val defaultScreenColor = IntColor(0x0AFF0A) private val defaultScreenColor = IntColor(0x0aff0a)
var screenColor: IntColor = defaultScreenColor var screenColor: IntColor = defaultScreenColor
private var _screenText: String = defaultScreenText private var _screenText: String = defaultScreenText
@ -272,23 +278,26 @@ class OpenFMRadio extends Entity with Environment with DeviceInfo with Logging {
if (nbt.hasKey("url")) if (nbt.hasKey("url"))
url = Option(nbt.getString("url")) url = Option(nbt.getString("url"))
screenColor = screenColor = {
if (nbt.hasKey("screenColor")) if (nbt.hasKey("screenColor"))
IntColor(nbt.getInteger("screenColor")) IntColor(nbt.getInteger("screenColor"))
else else
defaultScreenColor defaultScreenColor
}
screenText = screenText = {
if (nbt.hasKey("screenText")) if (nbt.hasKey("screenText"))
nbt.getString("screenText") nbt.getString("screenText")
else else
defaultScreenText defaultScreenText
}
volume = volume = {
if (nbt.hasKey("volume")) if (nbt.hasKey("volume"))
nbt.getDouble("volume").toFloat nbt.getDouble("volume").toFloat
else else
1 1
}
isListenRedstone = nbt.hasKey("isListenRedstone") && nbt.getBoolean("isListenRedstone") isListenRedstone = nbt.hasKey("isListenRedstone") && nbt.getBoolean("isListenRedstone")

View File

@ -2,7 +2,7 @@ package ocelot.desktop.entity.traits
import ocelot.desktop.entity.traits.OcelotInterface.LogEvent import ocelot.desktop.entity.traits.OcelotInterface.LogEvent
import totoro.ocelot.brain.entity.machine.{Arguments, Callback, Context} import totoro.ocelot.brain.entity.machine.{Arguments, Callback, Context}
import totoro.ocelot.brain.entity.traits.{Entity, Environment, result} import totoro.ocelot.brain.entity.traits.{result, Entity, Environment}
import totoro.ocelot.brain.event.{EventBus, NodeEvent} import totoro.ocelot.brain.event.{EventBus, NodeEvent}
import java.time.Instant import java.time.Instant
@ -21,7 +21,8 @@ trait OcelotInterface extends Entity with Environment {
result() result()
} }
@Callback(direct = true, doc = """function(): integer -- Returns the current Unix timestamp (UTC, in milliseconds).""") @Callback(direct = true,
doc = """function(): integer -- Returns the current Unix timestamp (UTC, in milliseconds).""")
def getTimestamp(context: Context, args: Arguments): Array[AnyRef] = { def getTimestamp(context: Context, args: Arguments): Array[AnyRef] = {
result(Instant.now().toEpochMilli) result(Instant.now().toEpochMilli)
} }

View File

@ -27,25 +27,25 @@ case class Basis3D(x: Vector3D, y: Vector3D, z: Vector3D) {
def *(rhs: Vector3D): Vector3D = Vector3D( def *(rhs: Vector3D): Vector3D = Vector3D(
x.x * rhs.x + y.x * rhs.y + z.x * rhs.z, x.x * rhs.x + y.x * rhs.y + z.x * rhs.z,
x.y * rhs.x + y.y * rhs.y + z.y * rhs.z, x.y * rhs.x + y.y * rhs.y + z.y * rhs.z,
x.z * rhs.x + y.z * rhs.y + z.z * rhs.z x.z * rhs.x + y.z * rhs.y + z.z * rhs.z,
) )
def *(rhs: Basis3D): Basis3D = Basis3D( def *(rhs: Basis3D): Basis3D = Basis3D(
Vector3D( Vector3D(
x.x * rhs.x.x + y.x * rhs.x.y + z.x * rhs.x.z, x.x * rhs.x.x + y.x * rhs.x.y + z.x * rhs.x.z,
x.y * rhs.x.x + y.y * rhs.x.y + z.y * rhs.x.z, x.y * rhs.x.x + y.y * rhs.x.y + z.y * rhs.x.z,
x.z * rhs.x.x + y.z * rhs.x.y + z.z * rhs.x.z x.z * rhs.x.x + y.z * rhs.x.y + z.z * rhs.x.z,
), ),
Vector3D( Vector3D(
x.x * rhs.y.x + y.x * rhs.y.y + z.x * rhs.y.z, x.x * rhs.y.x + y.x * rhs.y.y + z.x * rhs.y.z,
x.y * rhs.y.x + y.y * rhs.y.y + z.y * rhs.y.z, x.y * rhs.y.x + y.y * rhs.y.y + z.y * rhs.y.z,
x.z * rhs.y.x + y.z * rhs.y.y + z.z * rhs.y.z x.z * rhs.y.x + y.z * rhs.y.y + z.z * rhs.y.z,
), ),
Vector3D( Vector3D(
x.x * rhs.z.x + y.x * rhs.z.y + z.x * rhs.z.z, x.x * rhs.z.x + y.x * rhs.z.y + z.x * rhs.z.z,
x.y * rhs.z.x + y.y * rhs.z.y + z.y * rhs.z.z, x.y * rhs.z.x + y.y * rhs.z.y + z.y * rhs.z.z,
x.z * rhs.z.x + y.z * rhs.z.y + z.z * rhs.z.z x.z * rhs.z.x + y.z * rhs.z.y + z.z * rhs.z.z,
) ),
) )
def det: Float = x.x * (y.y * z.z - z.y * y.z) - def det: Float = x.x * (y.y * z.z - z.y * y.z) -
@ -59,18 +59,18 @@ case class Basis3D(x: Vector3D, y: Vector3D, z: Vector3D) {
Vector3D( Vector3D(
(y.y * z.z - z.y * y.z) * s, (y.y * z.z - z.y * y.z) * s,
(x.z * z.y - x.y * z.z) * s, (x.z * z.y - x.y * z.z) * s,
(x.y * y.z - x.z * y.y) * s (x.y * y.z - x.z * y.y) * s,
), ),
Vector3D( Vector3D(
(y.z * z.x - y.x * z.z) * s, (y.z * z.x - y.x * z.z) * s,
(x.x * z.z - x.z * z.x) * s, (x.x * z.z - x.z * z.x) * s,
(y.x * x.z - x.x * y.z) * s (y.x * x.z - x.x * y.z) * s,
), ),
Vector3D( Vector3D(
(y.x * z.y - z.x * y.y) * s, (y.x * z.y - z.x * y.y) * s,
(z.x * x.y - x.x * z.y) * s, (z.x * x.y - x.x * z.y) * s,
(x.x * y.y - y.x * x.y) * s (x.x * y.y - y.x * x.y) * s,
) ),
) )
} }

View File

@ -3,11 +3,12 @@ package ocelot.desktop.geometry
object ProjectionMatrix3D { object ProjectionMatrix3D {
def perspective(aspect: Float, fovY: Float, zNear: Float, zFar: Float): ProjectionMatrix3D = { def perspective(aspect: Float, fovY: Float, zNear: Float, zFar: Float): ProjectionMatrix3D = {
val f = (1.0 / math.tan(math.toRadians(fovY) / 2.0)).toFloat val f = (1.0 / math.tan(math.toRadians(fovY) / 2.0)).toFloat
// format: off
ProjectionMatrix3D( ProjectionMatrix3D(
f / aspect, 0, 0, 0, f / aspect, 0, 0, 0,
0, f, 0, 0, 0, f, 0, 0,
0, 0, (zFar + zNear) / (zNear - zFar), (2 * zFar * zNear) / (zNear - zFar), 0, 0, (zFar + zNear) / (zNear - zFar), (2 * zFar * zNear) / (zNear - zFar),
0, 0, -1, 0 0, 0, -1, 0,
) )
} }
} }
@ -15,7 +16,6 @@ object ProjectionMatrix3D {
case class ProjectionMatrix3D(m11: Float, m12: Float, m13: Float, m14: Float, case class ProjectionMatrix3D(m11: Float, m12: Float, m13: Float, m14: Float,
m21: Float, m22: Float, m23: Float, m24: Float, m21: Float, m22: Float, m23: Float, m24: Float,
m31: Float, m32: Float, m33: Float, m34: Float, m31: Float, m32: Float, m33: Float, m34: Float,
m41: Float, m42: Float, m43: Float, m44: Float) m41: Float, m42: Float, m43: Float, m44: Float) {
{
def array: Array[Float] = Array(m11, m12, m13, m14, m21, m22, m23, m24, m31, m32, m33, m34, m41, m42, m43, m44) def array: Array[Float] = Array(m11, m12, m13, m14, m21, m22, m23, m24, m31, m32, m33, m34, m41, m42, m43, m44)
} }

View File

@ -55,7 +55,7 @@ case class Quaternion(x: Float, y: Float, z: Float, w: Float) {
y * rhs.z - z * rhs.y + x * rhs.w + w * rhs.x, y * rhs.z - z * rhs.y + x * rhs.w + w * rhs.x,
z * rhs.x - x * rhs.z + y * rhs.w + w * rhs.y, z * rhs.x - x * rhs.z + y * rhs.w + w * rhs.y,
x * rhs.y - y * rhs.x + z * rhs.w + w * rhs.z, x * rhs.y - y * rhs.x + z * rhs.w + w * rhs.z,
w * rhs.w - x * rhs.x - y * rhs.y - z * rhs.z w * rhs.w - x * rhs.x - y * rhs.y - z * rhs.z,
) )
def conj: Quaternion = Quaternion(-x, -y, -z, w) def conj: Quaternion = Quaternion(-x, -y, -z, w)
@ -72,7 +72,7 @@ case class Quaternion(x: Float, y: Float, z: Float, w: Float) {
def basis: Basis3D = Basis3D( def basis: Basis3D = Basis3D(
Vector3D(1 - 2 * y * y - 2 * z * z, 2 * x * y + 2 * z * w, 2 * x * z - 2 * y * w), Vector3D(1 - 2 * y * y - 2 * z * z, 2 * x * y + 2 * z * w, 2 * x * z - 2 * y * w),
Vector3D(2 * x * y - 2 * z * w, 1 - 2 * x * x - 2 * z * z, 2 * y * z + 2 * x * w), Vector3D(2 * x * y - 2 * z * w, 1 - 2 * x * x - 2 * z * z, 2 * y * z + 2 * x * w),
Vector3D(2 * x * z + 2 * y * w, 2 * y * z - 2 * x * w, 1 - 2 * x * x - 2 * y * y) Vector3D(2 * x * z + 2 * y * w, 2 * y * z - 2 * x * w, 1 - 2 * x * x - 2 * y * y),
) )
def dot(that: Quaternion): Float = x * that.x + y * that.y + z * that.z + w * that.w def dot(that: Quaternion): Float = x * that.x + y * that.y + z * that.z + w * that.w

View File

@ -86,7 +86,8 @@ case class Rect2D(x: Float, y: Float, w: Float, h: Float) {
Vector2D(x + w / 2f, y), Vector2D(x + w / 2f, y),
Vector2D(x + w, y + h / 2f), Vector2D(x + w, y + h / 2f),
Vector2D(x + w / 2f, y + h), Vector2D(x + w / 2f, y + h),
Vector2D(x, y + h / 2f)) Vector2D(x, y + h / 2f),
)
def distanceTo(that: Rect2D): Float = { def distanceTo(that: Rect2D): Float = {
((center - that.center).abs - (extent + that.extent)).max(Vector2D(0, 0)).length ((center - that.center).abs - (extent + that.extent)).max(Vector2D(0, 0)).length

View File

@ -32,7 +32,7 @@ case class Size2D(width: Float, height: Float) {
def clamped(min: Size2D, max: Size2D): Size2D = { def clamped(min: Size2D, max: Size2D): Size2D = {
Size2D( Size2D(
math.min(max.width, math.max(min.width, width)), math.min(max.width, math.max(min.width, width)),
math.min(max.height, math.max(min.height, height)) math.min(max.height, math.max(min.height, height)),
) )
} }

View File

@ -5,19 +5,19 @@ import java.nio.ByteBuffer
object Transform2D { object Transform2D {
def identity: Transform2D = Transform2D( def identity: Transform2D = Transform2D(
1, 0, 0, 1, 0, 0,
0, 1, 0 0, 1, 0,
) )
def scale(x: Float, y: Float): Transform2D = Transform2D( def scale(x: Float, y: Float): Transform2D = Transform2D(
x, 0, 0, x, 0, 0,
0, y, 0 0, y, 0,
) )
def scale(a: Float): Transform2D = Transform2D.scale(a, a) def scale(a: Float): Transform2D = Transform2D.scale(a, a)
def translate(x: Float, y: Float): Transform2D = Transform2D( def translate(x: Float, y: Float): Transform2D = Transform2D(
1, 0, x, 1, 0, x,
0, 1, y 0, 1, y,
) )
def viewport(width: Float, height: Float): Transform2D = def viewport(width: Float, height: Float): Transform2D =
@ -26,6 +26,7 @@ object Transform2D {
def rotate(angle: Float): Transform2D = { def rotate(angle: Float): Transform2D = {
val (s, c) = (math.sin(angle).asInstanceOf[Float], math.cos(angle).asInstanceOf[Float]) val (s, c) = (math.sin(angle).asInstanceOf[Float], math.cos(angle).asInstanceOf[Float])
// format: off
Transform2D( Transform2D(
c, -s, 0, c, -s, 0,
s, c, 0 s, c, 0
@ -36,13 +37,13 @@ object Transform2D {
case class Transform2D(m11: Float, m12: Float, m13: Float, m21: Float, m22: Float, m23: Float) { case class Transform2D(m11: Float, m12: Float, m13: Float, m21: Float, m22: Float, m23: Float) {
def array: Array[Float] = Array(m11, m12, m13, m21, m22, m23) def array: Array[Float] = Array(m11, m12, m13, m21, m22, m23)
// :| // format: off
def >>(that: Transform2D): Transform2D = Transform2D( def >>(that: Transform2D): Transform2D = Transform2D(
m11 * that.m11 + m12 * that.m21, m11 * that.m12 + m12 * that.m22, m11 * that.m13 + m12 * that.m23 + m13, m11 * that.m11 + m12 * that.m21, m11 * that.m12 + m12 * that.m22, m11 * that.m13 + m12 * that.m23 + m13,
m21 * that.m11 + m22 * that.m21, m21 * that.m12 + m22 * that.m22, m21 * that.m13 + m22 * that.m23 + m23, m21 * that.m11 + m22 * that.m21, m21 * that.m12 + m22 * that.m22, m21 * that.m13 + m22 * that.m23 + m23,
) )
// (__) // format: off
def <<(that: Transform2D): Transform2D = Transform2D( def <<(that: Transform2D): Transform2D = Transform2D(
m11 * that.m11 + m21 * that.m12, m12 * that.m11 + m22 * that.m12, m13 * that.m11 + m23 * that.m12 + that.m13, m11 * that.m11 + m21 * that.m12, m12 * that.m11 + m22 * that.m12, m13 * that.m11 + m23 * that.m12 + that.m13,
m11 * that.m21 + m21 * that.m22, m12 * that.m21 + m22 * that.m22, m13 * that.m21 + m23 * that.m22 + that.m23 m11 * that.m21 + m21 * that.m22, m12 * that.m21 + m22 * that.m22, m13 * that.m21 + m23 * that.m22 + that.m23
@ -50,13 +51,14 @@ case class Transform2D(m11: Float, m12: Float, m13: Float, m21: Float, m22: Floa
def *(that: Vector2D): Vector2D = Vector2D( def *(that: Vector2D): Vector2D = Vector2D(
m11 * that.x + m12 * that.y + m13, m11 * that.x + m12 * that.y + m13,
m21 * that.x + m22 * that.y + m23 m21 * that.x + m22 * that.y + m23,
) )
override def toString: String = override def toString: String = {
f"""Transform2D [$m11%6.3f $m12%6.3f $m13%6.3f] f"""Transform2D [$m11%6.3f $m12%6.3f $m13%6.3f]
| [$m21%6.3f $m22%6.3f $m23%6.3f] | [$m21%6.3f $m22%6.3f $m23%6.3f]
""".stripMargin """.stripMargin
}
// (°°) ┻━┻ // (°°) ┻━┻
def put(buffer: ByteBuffer): Unit = { def put(buffer: ByteBuffer): Unit = {

View File

@ -26,6 +26,7 @@ object Transform3D {
} }
case class Transform3D(basis: Basis3D, origin: Vector3D) { case class Transform3D(basis: Basis3D, origin: Vector3D) {
// format: off
def array: Array[Float] = Array( def array: Array[Float] = Array(
basis.x.x, basis.y.x, basis.z.x, origin.x, basis.x.x, basis.y.x, basis.z.x, origin.x,
basis.x.y, basis.y.y, basis.z.y, origin.y, basis.x.y, basis.y.y, basis.z.y, origin.y,
@ -44,8 +45,8 @@ case class Transform3D(basis: Basis3D, origin: Vector3D) {
override def toString: String = s"Transform3D [${basis.x}, ${basis.y}, ${basis.z}, $origin]" override def toString: String = s"Transform3D [${basis.x}, ${basis.y}, ${basis.z}, $origin]"
def put(buffer: ByteBuffer): Unit = { def put(buffer: ByteBuffer): Unit = {
buffer.putFloat(basis.x.x); buffer.putFloat(basis.y.x); buffer.putFloat(basis.z.x); buffer.putFloat(origin.x); buffer.putFloat(basis.x.x); buffer.putFloat(basis.y.x); buffer.putFloat(basis.z.x); buffer.putFloat(origin.x)
buffer.putFloat(basis.x.y); buffer.putFloat(basis.y.y); buffer.putFloat(basis.z.y); buffer.putFloat(origin.y); buffer.putFloat(basis.x.y); buffer.putFloat(basis.y.y); buffer.putFloat(basis.z.y); buffer.putFloat(origin.y)
buffer.putFloat(basis.x.z); buffer.putFloat(basis.y.z); buffer.putFloat(basis.z.z); buffer.putFloat(origin.z); buffer.putFloat(basis.x.z); buffer.putFloat(basis.y.z); buffer.putFloat(basis.z.z); buffer.putFloat(origin.z)
} }
} }

View File

@ -41,7 +41,7 @@ case class Vector3D(x: Float, y: Float, z: Float) {
def cross(that: Vector3D): Vector3D = Vector3D( def cross(that: Vector3D): Vector3D = Vector3D(
y * that.z - z * that.y, y * that.z - z * that.y,
z * that.x - x * that.z, z * that.x - x * that.z,
x * that.y - y * that.x x * that.y - y * that.x,
) )
def angle(that: Vector3D): Float = { def angle(that: Vector3D): Float = {

View File

@ -17,7 +17,8 @@ import scala.collection.mutable
import scala.util.control.Breaks._ import scala.util.control.Breaks._
//noinspection ScalaWeakerAccess,ScalaUnusedSymbol //noinspection ScalaWeakerAccess,ScalaUnusedSymbol
class Graphics(private var width: Int, private var height: Int, private var scalingFactor: Float) extends Logging with Resource { class Graphics(private var width: Int, private var height: Int, private var scalingFactor: Float)
extends Logging with Resource {
private var time = 0f private var time = 0f
private var projection = Transform2D.viewport(width, height) private var projection = Transform2D.viewport(width, height)
@ -33,12 +34,15 @@ class Graphics(private var width: Int, private var height: Int, private var scal
private val stack = mutable.Stack[GraphicsState](GraphicsState()) private val stack = mutable.Stack[GraphicsState](GraphicsState())
private var spriteRect = Spritesheet.sprites("Empty") private var spriteRect = Spritesheet.sprites("Empty")
private val emptySpriteTrans = Transform2D.translate(spriteRect.x, spriteRect.y) >> Transform2D.scale(spriteRect.w, spriteRect.h)
private val emptySpriteTrans =
Transform2D.translate(spriteRect.x, spriteRect.y) >> Transform2D.scale(spriteRect.w, spriteRect.h)
private val offscreenTexture = new Texture(width, height, GL21.GL_SRGB8_ALPHA8, GL11.GL_UNSIGNED_BYTE, GL11.GL_RGBA) private val offscreenTexture = new Texture(width, height, GL21.GL_SRGB8_ALPHA8, GL11.GL_UNSIGNED_BYTE, GL11.GL_RGBA)
private val offscreenFramebuffer = ARBFramebufferObject.glGenFramebuffers() private val offscreenFramebuffer = ARBFramebufferObject.glGenFramebuffers()
GL30.glBindFramebuffer(GL30.GL_FRAMEBUFFER, offscreenFramebuffer) GL30.glBindFramebuffer(GL30.GL_FRAMEBUFFER, offscreenFramebuffer)
GL30.glFramebufferTexture2D(GL30.GL_FRAMEBUFFER, GL30.GL_COLOR_ATTACHMENT0, GL11.GL_TEXTURE_2D, GL30.glFramebufferTexture2D(GL30.GL_FRAMEBUFFER, GL30.GL_COLOR_ATTACHMENT0, GL11.GL_TEXTURE_2D,
offscreenTexture.texture, 0) offscreenTexture.texture, 0)
@ -152,7 +156,7 @@ class Graphics(private var width: Int, private var height: Int, private var scal
viewport: ScreenViewport, viewport: ScreenViewport,
bounds: Rect2D, bounds: Rect2D,
filteringMode: MinFilteringMode = MinFilteringMode.Nearest, filteringMode: MinFilteringMode = MinFilteringMode.Nearest,
alpha: Float = 1.0f alpha: Float = 1.0f,
): Unit = { ): Unit = {
flush() flush()
foreground = RGBAColorNorm(1, 1, 1, alpha) foreground = RGBAColorNorm(1, 1, 1, alpha)
@ -255,16 +259,17 @@ class Graphics(private var width: Int, private var height: Int, private var scal
val uvTransform = Transform2D.translate( val uvTransform = Transform2D.translate(
rect.x, rect.x,
rect.y rect.y,
) >> Transform2D.scale( ) >> Transform2D.scale(
rect.w - 0.25f / _font.AtlasWidth, rect.w - 0.25f / _font.AtlasWidth,
rect.h - 0.25f / _font.AtlasHeight rect.h - 0.25f / _font.AtlasHeight,
) )
val transform = val transform = {
stack.head.transform >> stack.head.transform >>
Transform2D.translate(x.round, y.round) >> Transform2D.translate(x.round, y.round) >>
Transform2D.scale(_font.charWidth(c), fontSize) Transform2D.scale(_font.charWidth(c), fontSize)
}
val foreground = stack.head.foreground.toRGBANorm.mapA(_ * alphaMultiplier) val foreground = stack.head.foreground.toRGBANorm.mapA(_ * alphaMultiplier)
val background = stack.head.background.toRGBANorm.mapA(_ * alphaMultiplier) val background = stack.head.background.toRGBANorm.mapA(_ * alphaMultiplier)
@ -333,8 +338,7 @@ class Graphics(private var width: Int, private var height: Int, private var scal
def sprite(name: String, x: Float, y: Float, width: Float, height: Float, def sprite(name: String, x: Float, y: Float, width: Float, height: Float,
color: Color = Color.White, color: Color = Color.White,
animation: Option[Animation] = None): Unit = animation: Option[Animation] = None): Unit = {
{
sprite = name sprite = name
foreground = color foreground = color
_rect(x, y, width, height, fixUV = true, animation) _rect(x, y, width, height, fixUV = true, animation)
@ -389,10 +393,11 @@ class Graphics(private var width: Int, private var height: Int, private var scal
else else
Transform2D.scale(spriteRect.w, spriteRect.h)) Transform2D.scale(spriteRect.w, spriteRect.h))
val transform = val transform = {
stack.head.transform >> stack.head.transform >>
Transform2D.translate(x, y) >> Transform2D.translate(x, y) >>
Transform2D.scale(width, height) Transform2D.scale(width, height)
}
val color = stack.head.foreground.toRGBANorm.mapA(_ * alphaMultiplier) val color = stack.head.foreground.toRGBANorm.mapA(_ * alphaMultiplier)
@ -426,9 +431,9 @@ class Graphics(private var width: Int, private var height: Int, private var scal
for (y <- 0 until height) { for (y <- 0 until height) {
for (x <- 0 until width) { for (x <- 0 until width) {
val i = (x + (height - y - 1) * width) * 4 val i = (x + (height - y - 1) * width) * 4
val r = buffer.get(i) & 0xFF val r = buffer.get(i) & 0xff
val g = buffer.get(i + 1) & 0xFF val g = buffer.get(i + 1) & 0xff
val b = buffer.get(i + 2) & 0xFF val b = buffer.get(i + 2) & 0xff
val rgb = (r << 16) | (g << 8) | b val rgb = (r << 16) | (g << 8) | b
image.setRGB(x, y, rgb) image.setRGB(x, y, rgb)
} }
@ -456,7 +461,7 @@ class Graphics(private var width: Int, private var height: Int, private var scal
Math.round(x * scalingFactor), Math.round(x * scalingFactor),
Math.round(height - h * scalingFactor - y * scalingFactor), Math.round(height - h * scalingFactor - y * scalingFactor),
Math.round(w * scalingFactor), Math.round(w * scalingFactor),
Math.round(h * scalingFactor) Math.round(h * scalingFactor),
) )
case _ => case _ =>
GL11.glDisable(GL11.GL_SCISSOR_TEST) GL11.glDisable(GL11.GL_SCISSOR_TEST)

View File

@ -10,5 +10,5 @@ case class GraphicsState(
var alphaMultiplier: Float = 1f, var alphaMultiplier: Float = 1f,
var sprite: String = "Empty", var sprite: String = "Empty",
var scissor: Option[(Float, Float, Float, Float)] = None, var scissor: Option[(Float, Float, Float, Float)] = None,
var transform: Transform2D = Transform2D.identity var transform: Transform2D = Transform2D.identity,
) )

View File

@ -94,7 +94,7 @@ object IconSource {
val DiskDriveMountable: IconSource = IconSource("items/DiskDriveMountable") val DiskDriveMountable: IconSource = IconSource("items/DiskDriveMountable")
//noinspection ScalaWeakerAccess // noinspection ScalaWeakerAccess
object Animations { object Animations {
val Apu: Animation = val Apu: Animation =
Animation((0, 3f), (1, 3f), (2, 3f), (3, 3f), (4, 3f), (5, 3f), (4, 3f), (3, 3f), (2, 3f), (1, 3f), (0, 3f)) Animation((0, 3f), (1, 3f), (2, 3f), (3, 3f), (4, 3f), (5, 3f), (4, 3f), (3, 3f), (2, 3f), (1, 3f), (0, 3f))
@ -115,6 +115,7 @@ object IconSource {
} }
case class Animation(frames: Array[(Int, Float)], frameSize: Option[Size2D]) case class Animation(frames: Array[(Int, Float)], frameSize: Option[Size2D])
object Animation { object Animation {
def apply(frames: (Int, Float)*) = new Animation(frames.toArray, None) def apply(frames: (Int, Float)*) = new Animation(frames.toArray, None)
def apply(size: Size2D, frames: (Int, Float)*) = new Animation(frames.toArray, Some(size)) def apply(size: Size2D, frames: (Int, Float)*) = new Animation(frames.toArray, Some(size))
@ -127,7 +128,9 @@ object IconSource {
IconSource(s"icons/Notification$notificationType") IconSource(s"icons/Notification$notificationType")
} }
val Loading: IconSource = IconSource("Loading", animation = Some(Animation( val Loading: IconSource = IconSource(
"Loading",
animation = Some(Animation(
Size2D(48, 32), Size2D(48, 32),
(0, 0.7f), (0, 0.7f),
(1, 0.7f), (1, 0.7f),
@ -143,7 +146,8 @@ object IconSource {
(11, 0.7f), (11, 0.7f),
(12, 0.7f), (12, 0.7f),
(13, 0.7f), (13, 0.7f),
))) )),
)
val SettingsSystem: IconSource = IconSource("icons/SettingsSystem") val SettingsSystem: IconSource = IconSource("icons/SettingsSystem")
val SettingsSound: IconSource = IconSource("icons/SettingsSound") val SettingsSound: IconSource = IconSource("icons/SettingsSound")

View File

@ -21,6 +21,7 @@ class ScreenViewport(graphics: Graphics, private var _width: Int, private var _h
private val _font = graphics.normalFont private val _font = graphics.normalFont
private val spriteRect = Spritesheet.sprites("Empty") private val spriteRect = Spritesheet.sprites("Empty")
private val emptySpriteTrans = private val emptySpriteTrans =
Transform2D.translate(spriteRect.x, spriteRect.y) >> Transform2D.scale(spriteRect.w, spriteRect.h) Transform2D.translate(spriteRect.x, spriteRect.y) >> Transform2D.scale(spriteRect.w, spriteRect.h)

View File

@ -12,10 +12,15 @@ class ShaderProgram(name: String) extends Logging with Resource {
private val fragmentShader: Int = createShader( private val fragmentShader: Int = createShader(
Source.fromResource(s"ocelot/desktop/shader/$name.frag", getClass.getClassLoader), Source.fromResource(s"ocelot/desktop/shader/$name.frag", getClass.getClassLoader),
GL20.GL_FRAGMENT_SHADER, "fragment") GL20.GL_FRAGMENT_SHADER,
"fragment",
)
private val vertexShader: Int = createShader( private val vertexShader: Int = createShader(
Source.fromResource(s"ocelot/desktop/shader/$name.vert", getClass.getClassLoader), Source.fromResource(s"ocelot/desktop/shader/$name.vert", getClass.getClassLoader),
GL20.GL_VERTEX_SHADER, "vertex") GL20.GL_VERTEX_SHADER,
"vertex",
)
val shaderProgram: Int = GL20.glCreateProgram() val shaderProgram: Int = GL20.glCreateProgram()

View File

@ -28,23 +28,25 @@ class Texture() extends Logging with Resource {
for (y <- 0 until image.getHeight) { for (y <- 0 until image.getHeight) {
for (x <- 0 until image.getWidth) { for (x <- 0 until image.getWidth) {
val pixel = pixels(y * image.getWidth + x) val pixel = pixels(y * image.getWidth + x)
buf.put(((pixel >> 16) & 0xFF).toByte) buf.put(((pixel >> 16) & 0xff).toByte)
buf.put(((pixel >> 8) & 0xFF).toByte) buf.put(((pixel >> 8) & 0xff).toByte)
buf.put((pixel & 0xFF).toByte) buf.put((pixel & 0xff).toByte)
buf.put(((pixel >> 24) & 0xFF).toByte) buf.put(((pixel >> 24) & 0xff).toByte)
} }
} }
buf.flip() buf.flip()
bind() bind()
GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL21.GL_SRGB8_ALPHA8, image.getWidth, image.getHeight, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, buf) GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL21.GL_SRGB8_ALPHA8, image.getWidth, image.getHeight, 0, GL11.GL_RGBA,
GL11.GL_UNSIGNED_BYTE, buf)
} }
def this(width: Int, height: Int, format: Int, dataType: Int, internalFormat: Int) = { def this(width: Int, height: Int, format: Int, dataType: Int, internalFormat: Int) = {
this() this()
bind() bind()
GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, format, width, height, 0, internalFormat, dataType, null.asInstanceOf[ByteBuffer]) GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, format, width, height, 0, internalFormat, dataType,
null.asInstanceOf[ByteBuffer])
} }
def this(width: Int, height: Int, format: Int, dataType: Int) = { def this(width: Int, height: Int, format: Int, dataType: Int) = {
@ -88,7 +90,7 @@ class Texture() extends Logging with Resource {
} }
object Texture { object Texture {
class MinFilteringMode private(private[Texture] val glValue: Int, val needsMipmap: Boolean) class MinFilteringMode private (private[Texture] val glValue: Int, val needsMipmap: Boolean)
object MinFilteringMode { object MinFilteringMode {
val Nearest = new MinFilteringMode(GL11.GL_NEAREST, false) val Nearest = new MinFilteringMode(GL11.GL_NEAREST, false)

View File

@ -14,8 +14,13 @@ class Viewport3D(width: Int, height: Int) extends Resource with Logging {
private val framebuffer = ARBFramebufferObject.glGenFramebuffers() private val framebuffer = ARBFramebufferObject.glGenFramebuffers()
GL30.glBindFramebuffer(GL30.GL_FRAMEBUFFER, framebuffer) GL30.glBindFramebuffer(GL30.GL_FRAMEBUFFER, framebuffer)
GL30.glFramebufferTexture2D(GL30.GL_FRAMEBUFFER, GL30.GL_COLOR_ATTACHMENT0, GL11.GL_TEXTURE_2D, textureColor.texture, 0)
GL30.glFramebufferTexture2D(GL30.GL_FRAMEBUFFER, GL30.GL_DEPTH_ATTACHMENT, GL11.GL_TEXTURE_2D, textureDepth.texture, 0) GL30.glFramebufferTexture2D(GL30.GL_FRAMEBUFFER, GL30.GL_COLOR_ATTACHMENT0, GL11.GL_TEXTURE_2D, textureColor.texture,
0)
GL30.glFramebufferTexture2D(GL30.GL_FRAMEBUFFER, GL30.GL_DEPTH_ATTACHMENT, GL11.GL_TEXTURE_2D, textureDepth.texture,
0)
GL30.glBindFramebuffer(GL30.GL_FRAMEBUFFER, 0) GL30.glBindFramebuffer(GL30.GL_FRAMEBUFFER, 0)
private type Instancer = InstanceRenderer[MeshVertex3D, MeshInstance3D] private type Instancer = InstanceRenderer[MeshVertex3D, MeshInstance3D]
@ -84,7 +89,8 @@ class Viewport3D(width: Int, height: Int) extends Resource with Logging {
private def setupLight(scene: Scene3D): Unit = { private def setupLight(scene: Scene3D): Unit = {
shaderProgram.set("uLightDir", scene.lightSource.basis.up) shaderProgram.set("uLightDir", scene.lightSource.basis.up)
shaderProgram.set("uLightColor", scene.lightSource.color.toRGBANorm.toLinear.rgbVector * scene.lightSource.energy) shaderProgram.set("uLightColor", scene.lightSource.color.toRGBANorm.toLinear.rgbVector * scene.lightSource.energy)
shaderProgram.set("uAmbientLightColor", scene.ambientLightColor.toRGBANorm.toLinear.rgbVector * scene.ambientLightEnergy) shaderProgram.set("uAmbientLightColor",
scene.ambientLightColor.toRGBANorm.toLinear.rgbVector * scene.ambientLightEnergy)
} }
private def collectOpaque(scene: Scene3D): Unit = { private def collectOpaque(scene: Scene3D): Unit = {

View File

@ -6,8 +6,9 @@ import ocelot.desktop.graphics.buffer.Index
object Mesh2D { object Mesh2D {
val quad: Mesh2D = new Mesh2D( val quad: Mesh2D = new Mesh2D(
Array(Vector2D(0f, 0f), Vector2D(1f, 0f), Vector2D(1f, 1f), Array(Vector2D(0f, 0f), Vector2D(1f, 0f), Vector2D(1f, 1f),
Vector2D(1f, 1f), Vector2D(0f, 1f), Vector2D(0f, 0f)).map(v => MeshVertex2D(v, v)), Vector2D(1f, 1f), Vector2D(0f, 1f), Vector2D(0f, 0f)).map(v => MeshVertex2D(v, v))
) )
} }
class Mesh2D(override val vertices: Seq[MeshVertex2D], override val indices: Option[Seq[Index]] = None) extends Mesh[MeshVertex2D] class Mesh2D(override val vertices: Seq[MeshVertex2D], override val indices: Option[Seq[Index]] = None)
extends Mesh[MeshVertex2D]

View File

@ -50,8 +50,8 @@ object Mesh3D {
} }
class Mesh3D(override val vertices: Seq[MeshVertex3D], override val indices: Option[Seq[Index]] = None, class Mesh3D(override val vertices: Seq[MeshVertex3D], override val indices: Option[Seq[Index]] = None,
override val primitiveType: PrimitiveType = PrimitiveTriangles) extends Mesh[MeshVertex3D] override val primitiveType: PrimitiveType = PrimitiveTriangles)
{ extends Mesh[MeshVertex3D] {
val boundingBox: Box3D = { val boundingBox: Box3D = {
Box3D.fromVertices(vertices.iterator.map(_.pos)) Box3D.fromVertices(vertices.iterator.map(_.pos))
} }

View File

@ -43,8 +43,7 @@ class MeshBuilder3D {
def triangle(a: Vector3D, aUV: Vector2D, def triangle(a: Vector3D, aUV: Vector2D,
b: Vector3D, bUV: Vector2D, b: Vector3D, bUV: Vector2D,
c: Vector3D, cUV: Vector2D): Unit = c: Vector3D, cUV: Vector2D): Unit = {
{
val normal = (b - a).cross(c - a).normalize val normal = (b - a).cross(c - a).normalize
val aIdx = vertex(a, normal, aUV) val aIdx = vertex(a, normal, aUV)
val bIdx = vertex(b, normal, bUV) val bIdx = vertex(b, normal, bUV)
@ -56,8 +55,7 @@ class MeshBuilder3D {
b: Vector3D, bUV: Vector2D, b: Vector3D, bUV: Vector2D,
c: Vector3D, cUV: Vector2D, c: Vector3D, cUV: Vector2D,
d: Vector3D, dUV: Vector2D, d: Vector3D, dUV: Vector2D,
color: Color): Unit = color: Color): Unit = {
{
val normal = (b - a).cross(c - a).normalize val normal = (b - a).cross(c - a).normalize
val aIdx = vertex(a, normal, aUV, color) val aIdx = vertex(a, normal, aUV, color)
val bIdx = vertex(b, normal, bUV, color) val bIdx = vertex(b, normal, bUV, color)
@ -74,15 +72,19 @@ class MeshBuilder3D {
spriteRect.x + cutRect.x * spriteUVScale * spriteRect.w, spriteRect.x + cutRect.x * spriteUVScale * spriteRect.w,
spriteRect.y + cutRect.y * spriteUVScale * spriteRect.w, spriteRect.y + cutRect.y * spriteUVScale * spriteRect.w,
cutRect.w * spriteUVScale * spriteRect.w, cutRect.w * spriteUVScale * spriteRect.w,
cutRect.h * spriteUVScale * spriteRect.h cutRect.h * spriteUVScale * spriteRect.h,
) )
quad( quad(
a, Vector2D(rect.x + rect.w, rect.y), a,
b, Vector2D(rect.x, rect.y), Vector2D(rect.x + rect.w, rect.y),
c, Vector2D(rect.x, rect.y + rect.h), b,
d, Vector2D(rect.x + rect.w, rect.y + rect.h), Vector2D(rect.x, rect.y),
color c,
Vector2D(rect.x, rect.y + rect.h),
d,
Vector2D(rect.x + rect.w, rect.y + rect.h),
color,
) )
} }
@ -93,55 +95,72 @@ class MeshBuilder3D {
back: Option[(String, Rect2D)] = Some(("Empty", Rect2D.Unit)), back: Option[(String, Rect2D)] = Some(("Empty", Rect2D.Unit)),
top: Option[(String, Rect2D)] = Some(("Empty", Rect2D.Unit)), top: Option[(String, Rect2D)] = Some(("Empty", Rect2D.Unit)),
bottom: Option[(String, Rect2D)] = Some(("Empty", Rect2D.Unit)), bottom: Option[(String, Rect2D)] = Some(("Empty", Rect2D.Unit)),
color: Color = Color.White): Unit = color: Color = Color.White): Unit = {
{ left.foreach(sprite => {
left.foreach(sprite => quad( quad(
Vector3D(min.x, max.y, max.z), Vector3D(min.x, max.y, max.z),
Vector3D(min.x, max.y, min.z), Vector3D(min.x, max.y, min.z),
Vector3D(min.x, min.y, min.z), Vector3D(min.x, min.y, min.z),
Vector3D(min.x, min.y, max.z), Vector3D(min.x, min.y, max.z),
sprite, color, sprite,
)) color,
)
})
right.foreach(sprite => quad( right.foreach(sprite => {
quad(
Vector3D(max.x, max.y, min.z), Vector3D(max.x, max.y, min.z),
Vector3D(max.x, max.y, max.z), Vector3D(max.x, max.y, max.z),
Vector3D(max.x, min.y, max.z), Vector3D(max.x, min.y, max.z),
Vector3D(max.x, min.y, min.z), Vector3D(max.x, min.y, min.z),
sprite, color, sprite,
)) color,
)
})
front.foreach(sprite => quad( front.foreach(sprite => {
quad(
Vector3D(min.x, max.y, min.z), Vector3D(min.x, max.y, min.z),
Vector3D(max.x, max.y, min.z), Vector3D(max.x, max.y, min.z),
Vector3D(max.x, min.y, min.z), Vector3D(max.x, min.y, min.z),
Vector3D(min.x, min.y, min.z), Vector3D(min.x, min.y, min.z),
sprite, color, sprite,
)) color,
)
})
back.foreach(sprite => quad( back.foreach(sprite => {
quad(
Vector3D(max.x, max.y, max.z), Vector3D(max.x, max.y, max.z),
Vector3D(min.x, max.y, max.z), Vector3D(min.x, max.y, max.z),
Vector3D(min.x, min.y, max.z), Vector3D(min.x, min.y, max.z),
Vector3D(max.x, min.y, max.z), Vector3D(max.x, min.y, max.z),
sprite, color, sprite,
)) color,
)
})
top.foreach(sprite => quad( top.foreach(sprite => {
quad(
Vector3D(max.x, max.y, min.z), Vector3D(max.x, max.y, min.z),
Vector3D(min.x, max.y, min.z), Vector3D(min.x, max.y, min.z),
Vector3D(min.x, max.y, max.z), Vector3D(min.x, max.y, max.z),
Vector3D(max.x, max.y, max.z), Vector3D(max.x, max.y, max.z),
sprite, color, sprite,
)) color,
)
})
bottom.foreach(sprite => quad( bottom.foreach(sprite => {
quad(
Vector3D(max.x, min.y, max.z), Vector3D(max.x, min.y, max.z),
Vector3D(min.x, min.y, max.z), Vector3D(min.x, min.y, max.z),
Vector3D(min.x, min.y, min.z), Vector3D(min.x, min.y, min.z),
Vector3D(max.x, min.y, min.z), Vector3D(max.x, min.y, min.z),
sprite, color, sprite,
)) color,
)
})
} }
def line(a: Vector3D, b: Vector3D, aColor: Color = Color.White, bColor: Color = Color.White): Unit = { def line(a: Vector3D, b: Vector3D, aColor: Color = Color.White, bColor: Color = Color.White): Unit = {

View File

@ -22,8 +22,8 @@ object MeshInstance2D extends VertexType {
} }
case class MeshInstance2D(color: Color, textColor: Color, transform: Transform2D, uvTransform: Transform2D, case class MeshInstance2D(color: Color, textColor: Color, transform: Transform2D, uvTransform: Transform2D,
textUvTransform: Transform2D) extends Vertex textUvTransform: Transform2D)
{ extends Vertex {
override def stride: Int = MeshInstance2D.stride override def stride: Int = MeshInstance2D.stride
override def vertexType: VertexType = MeshInstance2D override def vertexType: VertexType = MeshInstance2D

View File

@ -17,13 +17,13 @@ object MeshInstance3D extends VertexType {
Attribute("inUVTransform0", 3, GL11.GL_FLOAT, normalized = false, stride, 64), Attribute("inUVTransform0", 3, GL11.GL_FLOAT, normalized = false, stride, 64),
Attribute("inUVTransform1", 3, GL11.GL_FLOAT, normalized = false, stride, 76), Attribute("inUVTransform1", 3, GL11.GL_FLOAT, normalized = false, stride, 76),
Attribute("inLightingFactor", 1, GL11.GL_FLOAT, normalized = false, stride, 88), Attribute("inLightingFactor", 1, GL11.GL_FLOAT, normalized = false, stride, 88),
Attribute("inTextureFactor", 1, GL11.GL_FLOAT, normalized = false, stride, 92) Attribute("inTextureFactor", 1, GL11.GL_FLOAT, normalized = false, stride, 92),
) )
} }
case class MeshInstance3D(color: Color, transform: Transform3D, uvTransform: Transform2D, case class MeshInstance3D(color: Color, transform: Transform3D, uvTransform: Transform2D,
lightingFactor: Float, textureFactor: Float) extends Vertex lightingFactor: Float, textureFactor: Float)
{ extends Vertex {
override def vertexType: VertexType = MeshInstance3D override def vertexType: VertexType = MeshInstance3D
override def stride: Int = MeshInstance3D.stride override def stride: Int = MeshInstance3D.stride

View File

@ -10,7 +10,7 @@ object MeshVertex2D extends VertexType {
override val attributes: Seq[Attribute] = Array( override val attributes: Seq[Attribute] = Array(
Attribute("inPos", 2, GL11.GL_FLOAT, normalized = false, stride, 0), Attribute("inPos", 2, GL11.GL_FLOAT, normalized = false, stride, 0),
Attribute("inUV", 2, GL11.GL_FLOAT, normalized = false, stride, 8) Attribute("inUV", 2, GL11.GL_FLOAT, normalized = false, stride, 8),
) )
} }

View File

@ -10,7 +10,8 @@ import org.lwjgl.opengl._
import java.nio.ByteBuffer import java.nio.ByteBuffer
import scala.collection.mutable.ArrayBuffer import scala.collection.mutable.ArrayBuffer
class InstanceRenderer[V <: Vertex, I <: Vertex](mesh: Mesh[V], instanceType: VertexType, shader: ShaderProgram) extends Resource with Logging { class InstanceRenderer[V <: Vertex, I <: Vertex](mesh: Mesh[V], instanceType: VertexType, shader: ShaderProgram)
extends Resource with Logging {
private val InitialCapacity: Int = 1 private val InitialCapacity: Int = 1
private val vertexBuffer = new VertexBuffer[V](mesh.vertices) private val vertexBuffer = new VertexBuffer[V](mesh.vertices)
@ -46,18 +47,20 @@ class InstanceRenderer[V <: Vertex, I <: Vertex](mesh: Mesh[V], instanceType: Ve
( (
method.invoke(null, "glDrawArraysInstancedARB"), method.invoke(null, "glDrawArraysInstancedARB"),
method.invoke(null, "glDrawElementsInstancedARB") method.invoke(null, "glDrawElementsInstancedARB"),
) )
} }
private lazy val nglDrawElementsInstancedARB = { private lazy val nglDrawElementsInstancedARB = {
val method = classOf[ARBDrawInstanced].getDeclaredMethod("nglDrawElementsInstancedARB", Integer.TYPE, Integer.TYPE, Integer.TYPE, java.lang.Long.TYPE, Integer.TYPE, java.lang.Long.TYPE) val method = classOf[ARBDrawInstanced].getDeclaredMethod("nglDrawElementsInstancedARB", Integer.TYPE, Integer.TYPE,
Integer.TYPE, java.lang.Long.TYPE, Integer.TYPE, java.lang.Long.TYPE)
method.setAccessible(true) method.setAccessible(true)
method method
} }
private lazy val nglDrawArraysInstancedARB = { private lazy val nglDrawArraysInstancedARB = {
val method = classOf[ARBDrawInstanced].getDeclaredMethod("nglDrawArraysInstancedARB", Integer.TYPE, Integer.TYPE, Integer.TYPE, Integer.TYPE, java.lang.Long.TYPE) val method = classOf[ARBDrawInstanced].getDeclaredMethod("nglDrawArraysInstancedARB", Integer.TYPE, Integer.TYPE,
Integer.TYPE, Integer.TYPE, java.lang.Long.TYPE)
method.setAccessible(true) method.setAccessible(true)
method method
} }
@ -70,10 +73,12 @@ class InstanceRenderer[V <: Vertex, I <: Vertex](mesh: Mesh[V], instanceType: Ve
indexBuffer match { indexBuffer match {
case Some(ib) => case Some(ib) =>
nglDrawElementsInstancedARB.invoke(null, mesh.primitiveType.toGL, ib.capacity, GL11.GL_UNSIGNED_INT, 0L, instances.length, glDrawElementsInstancedARBptr) nglDrawElementsInstancedARB.invoke(null, mesh.primitiveType.toGL, ib.capacity, GL11.GL_UNSIGNED_INT, 0L,
instances.length, glDrawElementsInstancedARBptr)
case None => case None =>
nglDrawArraysInstancedARB.invoke(null, mesh.primitiveType.toGL, 0, vertexBuffer.capacity, instances.length, glDrawArraysInstancedARBptr) nglDrawArraysInstancedARB.invoke(null, mesh.primitiveType.toGL, 0, vertexBuffer.capacity, instances.length,
glDrawArraysInstancedARBptr)
} }
instances.clear() instances.clear()

View File

@ -4,8 +4,8 @@ import ocelot.desktop.geometry.{ProjectionMatrix3D, Vector3D}
class Camera3D(var zNear: Float = 0.1f, class Camera3D(var zNear: Float = 0.1f,
var zFar: Float = 100.0f, var zFar: Float = 100.0f,
var fovY: Float = 80.0f) extends SceneNode3D var fovY: Float = 80.0f)
{ extends SceneNode3D {
def distanceTo(point: Vector3D): Float = { def distanceTo(point: Vector3D): Float = {
(origin - point).length (origin - point).length
} }

View File

@ -3,4 +3,5 @@ package ocelot.desktop.graphics.scene
import ocelot.desktop.color.Color import ocelot.desktop.color.Color
class DirectionalLight3D(var color: Color = Color.White, class DirectionalLight3D(var color: Color = Color.White,
var energy: Float = 1) extends SceneNode3D var energy: Float = 1)
extends SceneNode3D

View File

@ -10,4 +10,5 @@ class SceneMesh3D(var mesh: Mesh3D,
var lightingFactor: Float = 1, var lightingFactor: Float = 1,
var textureFactor: Float = 1, var textureFactor: Float = 1,
var isOpaque: Boolean = true, var isOpaque: Boolean = true,
var priority: Int = 0) extends SceneNode3D var priority: Int = 0)
extends SceneNode3D

View File

@ -6,16 +6,12 @@ import totoro.ocelot.brain.event.NodeEvent
import scala.collection.mutable import scala.collection.mutable
/** /** Provides an inventory — a collection of [[Item]]s indexed by slots. */
* Provides an inventory a collection of [[Item]]s indexed by slots.
*/
trait Inventory extends EventAware { trait Inventory extends EventAware {
// parallels totoro.ocelot.brain.entity.traits.Inventory // parallels totoro.ocelot.brain.entity.traits.Inventory
// this is intentional // this is intentional
/** /** The type of items stored in this inventory. */
* The type of items stored in this inventory.
*/
type I <: Item type I <: Item
private type WeakHashSet[A] = mutable.WeakHashMap[A, Unit] private type WeakHashSet[A] = mutable.WeakHashMap[A, Unit]
@ -24,15 +20,13 @@ trait Inventory extends EventAware {
private val itemSlots = mutable.HashMap.empty[I, Int] private val itemSlots = mutable.HashMap.empty[I, Int]
private val observers = mutable.HashMap.empty[Int, WeakHashSet[SlotObserver]] private val observers = mutable.HashMap.empty[Int, WeakHashSet[SlotObserver]]
/** /** Called after a new item is added to the inventory.
* Called after a new item is added to the inventory.
* *
* @param slot the slot the item was added to * @param slot the slot the item was added to
*/ */
def onItemAdded(slot: Slot): Unit def onItemAdded(slot: Slot): Unit
/** /** Called after an item is removed from the inventory.
* Called after an item is removed from the inventory.
* *
* When the item is replaced by another one, the event are sequenced in the following order: * When the item is replaced by another one, the event are sequenced in the following order:
* *
@ -47,9 +41,7 @@ trait Inventory extends EventAware {
*/ */
def onItemRemoved(slot: Slot, removedItem: I, replacedBy: Option[I]): Unit def onItemRemoved(slot: Slot, removedItem: I, replacedBy: Option[I]): Unit
/** /** An iterator over all slots occupied in this inventory. */
* An iterator over all slots occupied in this inventory.
*/
def inventoryIterator: Iterator[Slot] = slotItems.keysIterator.map(Slot(_)) def inventoryIterator: Iterator[Slot] = slotItems.keysIterator.map(Slot(_))
def clearInventory(): Unit = { def clearInventory(): Unit = {
@ -86,46 +78,35 @@ trait Inventory extends EventAware {
} }
} }
/** /** A proxy to access a slot of the inventory. */
* A proxy to access a slot of the inventory. final class Slot private[Inventory] (val index: Int) {
*/
final class Slot private[Inventory](val index: Int) {
require(index >= 0) require(index >= 0)
def isEmpty: Boolean = get.isEmpty def isEmpty: Boolean = get.isEmpty
def nonEmpty: Boolean = !isEmpty def nonEmpty: Boolean = !isEmpty
/** /** Inserts the `item` into this slot (replacing the previous item if any). */
* Inserts the `item` into this slot (replacing the previous item if any).
*/
def put(item: inventory.I): Unit = { def put(item: inventory.I): Unit = {
setSlot(index, Some(item)) setSlot(index, Some(item))
} }
/** /** Allows inserting/removing the item in this slot. */
* Allows inserting/removing the item in this slot.
*/
def set(item: Option[inventory.I]): Unit = { def set(item: Option[inventory.I]): Unit = {
setSlot(index, item) setSlot(index, item)
} }
/** /** Removes the item contained in this slot if there is one. */
* Removes the item contained in this slot if there is one.
*/
def remove(): Unit = { def remove(): Unit = {
setSlot(index, None) setSlot(index, None)
} }
/** /** The [[Item]] contained in this slot. */
* The [[Item]] contained in this slot.
*/
def get: Option[inventory.I] = slotItems.get(index) def get: Option[inventory.I] = slotItems.get(index)
val inventory: Inventory.this.type = Inventory.this val inventory: Inventory.this.type = Inventory.this
/** /** Registers an observer to receive item added/removed events.
* Registers an observer to receive item added/removed events.
* *
* @note The inventory keeps a '''weak''' reference to the `observer`. * @note The inventory keeps a '''weak''' reference to the `observer`.
*/ */
@ -167,9 +148,8 @@ trait Inventory extends EventAware {
} }
final object Slot { final object Slot {
/**
* Creates a proxy to an inventory slot. /** Creates a proxy to an inventory slot. */
*/
def apply(index: Int) = new Slot(index) def apply(index: Int) = new Slot(index)
} }
@ -225,15 +205,14 @@ trait Inventory extends EventAware {
object Inventory { object Inventory {
trait SlotObserver { trait SlotObserver {
/**
* Called after an item was inserted into this slot. /** Called after an item was inserted into this slot.
* *
* @note [[Inventory.onItemAdded]] is called before this method. * @note [[Inventory.onItemAdded]] is called before this method.
*/ */
def onItemAdded(): Unit def onItemAdded(): Unit
/** /** Called after an item was removed from this slot.
* Called after an item was removed from this slot.
* *
* In particular, the slot no longer contains the removed item. * In particular, the slot no longer contains the removed item.
* *
@ -241,9 +220,7 @@ object Inventory {
*/ */
def onItemRemoved(removedItem: Item, replacedBy: Option[Item]): Unit def onItemRemoved(removedItem: Item, replacedBy: Option[Item]): Unit
/** /** Called when an item contained in this slot sends a notification via [[Item.notifySlot]]. */
* Called when an item contained in this slot sends a notification via [[Item.notifySlot]].
*/
def onItemNotification(notification: Item.Notification): Unit def onItemNotification(notification: Item.Notification): Unit
} }
} }

View File

@ -11,15 +11,11 @@ import ocelot.desktop.util.Disposable
import totoro.ocelot.brain.util.Tier import totoro.ocelot.brain.util.Tier
import totoro.ocelot.brain.util.Tier.Tier import totoro.ocelot.brain.util.Tier.Tier
/** /** Something that can be stored in an [[Inventory]]. */
* Something that can be stored in an [[Inventory]].
*/
trait Item extends EventAware with Updatable with Disposable { trait Item extends EventAware with Updatable with Disposable {
private var _slot: Option[Inventory#Slot] = None private var _slot: Option[Inventory#Slot] = None
/** /** The slot this item is stored in. */
* The slot this item is stored in.
*/
def slot: Option[Inventory#Slot] = _slot def slot: Option[Inventory#Slot] = _slot
private[inventory] def slot_=(slot: Option[Inventory#Slot]): Unit = { private[inventory] def slot_=(slot: Option[Inventory#Slot]): Unit = {
@ -32,9 +28,7 @@ trait Item extends EventAware with Updatable with Disposable {
} }
} }
/** /** Remove the item from its slot, apply `f`, and put it back into the slot. */
* Remove the item from its slot, apply `f`, and put it back into the slot.
*/
def reinserting(f: => Unit): Unit = { def reinserting(f: => Unit): Unit = {
val prevSlot = slot val prevSlot = slot
slot.foreach(_.remove()) slot.foreach(_.remove())
@ -42,8 +36,7 @@ trait Item extends EventAware with Updatable with Disposable {
prevSlot.foreach(slot => slot.put(this.asInstanceOf[slot.inventory.I])) prevSlot.foreach(slot => slot.put(this.asInstanceOf[slot.inventory.I]))
} }
/** /** Sends a `notification` to observers of the `slot`.
* Sends a `notification` to observers of the `slot`.
* *
* (Which means, if the item is not put into any slot, nobody will receive the message.) * (Which means, if the item is not put into any slot, nobody will receive the message.)
*/ */
@ -55,18 +48,13 @@ trait Item extends EventAware with Updatable with Disposable {
protected def onRemoved(): Unit = {} protected def onRemoved(): Unit = {}
/** /** The name of the item (as shown in the tooltip). */
* The name of the item (as shown in the tooltip).
*/
def name: String = factory.name def name: String = factory.name
/** /** The icon used to draw the item in a slot. */
* The icon used to draw the item in a slot.
*/
def icon: IconSource = factory.icon def icon: IconSource = factory.icon
/** /** The tier of the item (if it has one).
* The tier of the item (if it has one).
* *
* This affects the color of the item name in the tooltip. * This affects the color of the item name in the tooltip.
*/ */
@ -74,8 +62,7 @@ trait Item extends EventAware with Updatable with Disposable {
protected def tooltipNameColor: Color = ColorScheme(s"Tier${tier.getOrElse(Tier.One).id}") protected def tooltipNameColor: Color = ColorScheme(s"Tier${tier.getOrElse(Tier.One).id}")
/** /** Override this in subclasses to customize the contents of the tooltip.
* Override this in subclasses to customize the contents of the tooltip.
* *
* @example {{{ * @example {{{
* override protected def fillTooltipBody(body: Widget): Unit = { * override protected def fillTooltipBody(body: Widget): Unit = {
@ -91,31 +78,27 @@ trait Item extends EventAware with Updatable with Disposable {
tooltip.addLine(name, tooltipNameColor) tooltip.addLine(name, tooltipNameColor)
} }
/** /** Override this in subclasses to add new entries to the context menu.
* Override this in subclasses to add new entries to the context menu.
* *
* It usually makes sense to call `super.fillRmbMenu` ''after'' you've added your own entries. * It usually makes sense to call `super.fillRmbMenu` ''after'' you've added your own entries.
*/ */
def fillRmbMenu(menu: ContextMenu): Unit = {} def fillRmbMenu(menu: ContextMenu): Unit = {}
/** /** The factory that can be used to build an independent instance of this [[Item]]
* The factory that can be used to build an independent instance of this [[Item]]
* in a way equivalent to this one (e.g. the same tier, label, EEPROM code). * in a way equivalent to this one (e.g. the same tier, label, EEPROM code).
* *
* Keep this rather cheap. * Keep this rather cheap.
*/ */
def factory: ItemFactory def factory: ItemFactory
/** /** Override this in subclasses to implement some specific item behavior
* Override this in subclasses to implement some specific item behavior
* during UI update * during UI update
*/ */
override def update(): Unit = {} override def update(): Unit = {}
} }
object Item { object Item {
/**
* A notification that can be sent with [[Item.notifySlot]]. /** A notification that can be sent with [[Item.notifySlot]]. */
*/
trait Notification trait Notification
} }

View File

@ -3,8 +3,7 @@ package ocelot.desktop.inventory
import ocelot.desktop.graphics.IconSource import ocelot.desktop.graphics.IconSource
import totoro.ocelot.brain.util.Tier.Tier import totoro.ocelot.brain.util.Tier.Tier
/** /** Provides information about a class of [[Item]]s and allows to build an item instance.
* Provides information about a class of [[Item]]s and allows to build an item instance.
* *
* Used by [[ocelot.desktop.ui.widget.slot.SlotWidget SlotWidgets]] * Used by [[ocelot.desktop.ui.widget.slot.SlotWidget SlotWidgets]]
* (and [[ocelot.desktop.ui.widget.slot.ItemChooser]]) to tell if you can insert an item into the slot * (and [[ocelot.desktop.ui.widget.slot.ItemChooser]]) to tell if you can insert an item into the slot
@ -12,20 +11,17 @@ import totoro.ocelot.brain.util.Tier.Tier
* In particular, make sure the [[tier]] and [[itemClass]] provided by the factory are accurate. * In particular, make sure the [[tier]] and [[itemClass]] provided by the factory are accurate.
*/ */
trait ItemFactory { trait ItemFactory {
/**
* The concrete type of the [[Item]] built by the factory. /** The concrete type of the [[Item]] built by the factory. */
*/
type I <: Item type I <: Item
/** /** The runtime class of the [[Item]] built by the factory.
* The runtime class of the [[Item]] built by the factory.
* *
* @note It's expected that `build().getClass == itemClass`. * @note It's expected that `build().getClass == itemClass`.
*/ */
def itemClass: Class[I] def itemClass: Class[I]
/** /** A name that represents what will be built by the factory.
* A name that represents what will be built by the factory.
* *
* Usually [[name]] and [[Item.name]] are the same (in fact, the latter defaults to this unless overridden). * Usually [[name]] and [[Item.name]] are the same (in fact, the latter defaults to this unless overridden).
* *
@ -33,27 +29,21 @@ trait ItemFactory {
*/ */
def name: String def name: String
/** /** The tier of an item this factory will construct in its [[build]] method.
* The tier of an item this factory will construct in its [[build]] method.
* *
* @note It's expected that `build().tier == tier`. * @note It's expected that `build().tier == tier`.
*/ */
def tier: Option[Tier] def tier: Option[Tier]
/** /** The icon of an item this factory will construct in its [[build]] method.
* The icon of an item this factory will construct in its [[build]] method.
* *
* Used by the [[ocelot.desktop.ui.widget.slot.ItemChooser ItemChooser]] in its entries. * Used by the [[ocelot.desktop.ui.widget.slot.ItemChooser ItemChooser]] in its entries.
*/ */
def icon: IconSource def icon: IconSource
/** /** Constructs a new item instance. */
* Constructs a new item instance.
*/
def build(): I def build(): I
/** /** Returns a list of recoverers provided by the factory, */
* Returns a list of recoverers provided by the factory,
*/
def recoverers: Iterable[ItemRecoverer[_, _]] def recoverers: Iterable[ItemRecoverer[_, _]]
} }

View File

@ -1,9 +1,8 @@
package ocelot.desktop.inventory package ocelot.desktop.inventory
import scala.reflect.{ClassTag, classTag} import scala.reflect.{classTag, ClassTag}
/** /** Allows recovering an [[Item]] from [[S]] (typically an [[totoro.ocelot.brain.entity.traits.Entity Entity]]).
* Allows recovering an [[Item]] from [[S]] (typically an [[totoro.ocelot.brain.entity.traits.Entity Entity]]).
* *
* This is used for migration from old saves before the inventory system was introduced to Ocelot Desktop. * This is used for migration from old saves before the inventory system was introduced to Ocelot Desktop.
*/ */

View File

@ -20,9 +20,7 @@ object Items extends Logging {
// this is just to force load the class during initialization // this is just to force load the class during initialization
def init(): Unit = {} def init(): Unit = {}
/** /** Registers a recoverer for [[ItemRecoverer.sourceClass]]. */
* Registers a recoverer for [[ItemRecoverer.sourceClass]].
*/
def registerRecoverer(recoverer: ItemRecoverer[_, _]): Unit = { def registerRecoverer(recoverer: ItemRecoverer[_, _]): Unit = {
if (!_recoverers.contains(recoverer.sourceClass)) { if (!_recoverers.contains(recoverer.sourceClass)) {
_recoverers(recoverer.sourceClass) = recoverer _recoverers(recoverer.sourceClass) = recoverer
@ -72,8 +70,7 @@ object Items extends Logging {
def groups: Iterable[ItemGroup] = _groups def groups: Iterable[ItemGroup] = _groups
/** /** Attempts to recover an [[Item]] from `source`.
* Attempts to recover an [[Item]] from `source`.
* *
* Checks superclasses and traits while looking for a recoverer. * Checks superclasses and traits while looking for a recoverer.
*/ */
@ -111,7 +108,7 @@ object Items extends Logging {
case ExtendedTier.Creative => new MagicalMemoryItem.Factory() case ExtendedTier.Creative => new MagicalMemoryItem.Factory()
case tier => new MemoryItem.Factory(tier) case tier => new MemoryItem.Factory(tier)
} }
.map(factory => (factory.name, factory)) .map(factory => (factory.name, factory)),
) )
registerTiered("HDD", Tier.One to Tier.Three)(new HddItem.Factory(managed = true, _)) registerTiered("HDD", Tier.One to Tier.Three)(new HddItem.Factory(managed = true, _))
@ -121,7 +118,7 @@ object Items extends Logging {
FloppyItem.Factory.Empty.icon, FloppyItem.Factory.Empty.icon,
Loot.Floppies.iterator Loot.Floppies.iterator
.map(new FloppyItem.Factory.Loot(_)) .map(new FloppyItem.Factory.Loot(_))
.map(factory => (factory.name, factory)) ++ Some(("Empty", FloppyItem.Factory.Empty)) .map(factory => (factory.name, factory)) ++ Some(("Empty", FloppyItem.Factory.Empty)),
) )
registerArbitrary( registerArbitrary(
@ -129,26 +126,31 @@ object Items extends Logging {
EepromItem.Factory.Empty.icon, EepromItem.Factory.Empty.icon,
Loot.Eeproms.iterator Loot.Eeproms.iterator
.map(new EepromItem.Factory.Loot(_)) .map(new EepromItem.Factory.Loot(_))
.map(factory => (factory.name, factory)) ++ Some(("Empty", EepromItem.Factory.Empty)) .map(factory => (factory.name, factory)) ++ Some(("Empty", EepromItem.Factory.Empty)),
) )
registerTiered("Graphics Card", Tier.One to Tier.Three)(new GraphicsCardItem.Factory(_)) registerTiered("Graphics Card", Tier.One to Tier.Three)(new GraphicsCardItem.Factory(_))
registerSingleton(NetworkCardItem.Factory) registerSingleton(NetworkCardItem.Factory)
registerTiered("Wireless Net. Card", Tier.One to Tier.Two) { registerTiered("Wireless Net. Card", Tier.One to Tier.Two) {
case Tier.One => WirelessNetworkCardItem.Tier1.Factory case Tier.One => WirelessNetworkCardItem.Tier1.Factory
case Tier.Two => WirelessNetworkCardItem.Tier2.Factory case Tier.Two => WirelessNetworkCardItem.Tier2.Factory
} }
registerSingleton(LinkedCardItem.Factory) registerSingleton(LinkedCardItem.Factory)
registerSingleton(InternetCardItem.Factory) registerSingleton(InternetCardItem.Factory)
registerTiered("Redstone Card", Tier.One to Tier.Two) { registerTiered("Redstone Card", Tier.One to Tier.Two) {
case Tier.One => RedstoneCardItem.Tier1.Factory case Tier.One => RedstoneCardItem.Tier1.Factory
case Tier.Two => RedstoneCardItem.Tier2.Factory case Tier.Two => RedstoneCardItem.Tier2.Factory
} }
registerTiered("Data Card", Tier.One to Tier.Three) { registerTiered("Data Card", Tier.One to Tier.Three) {
case Tier.One => DataCardItem.Tier1.Factory case Tier.One => DataCardItem.Tier1.Factory
case Tier.Two => DataCardItem.Tier2.Factory case Tier.Two => DataCardItem.Tier2.Factory
case Tier.Three => DataCardItem.Tier3.Factory case Tier.Three => DataCardItem.Tier3.Factory
} }
registerSingleton(SoundCardItem.Factory) registerSingleton(SoundCardItem.Factory)
registerSingleton(SelfDestructingCardItem.Factory) registerSingleton(SelfDestructingCardItem.Factory)
registerSingleton(OcelotCardItem.Factory) registerSingleton(OcelotCardItem.Factory)
@ -162,6 +164,6 @@ object Items extends Logging {
new TapeItem.Factory(Tape.Kind.Iron).icon, new TapeItem.Factory(Tape.Kind.Iron).icon,
Tape.Kind.values.iterator Tape.Kind.values.iterator
.map(new TapeItem.Factory(_)) .map(new TapeItem.Factory(_))
.map(factory => (f"${factory.name}%s (${Tape.lengthMinutes(factory.kind)}%.0f min)", factory)) .map(factory => (f"${factory.name}%s (${Tape.lengthMinutes(factory.kind)}%.0f min)", factory)),
) )
} }

View File

@ -11,8 +11,7 @@ import totoro.ocelot.brain.nbt.{NBT, NBTTagCompound}
import scala.collection.mutable import scala.collection.mutable
/** /** Provides persistence for an [[Inventory]].
* Provides persistence for an [[Inventory]].
* *
* [[ComponentItem]]s are treated specially: their [[ComponentItem.component component]] is persisted * [[ComponentItem]]s are treated specially: their [[ComponentItem.component component]] is persisted
* along with the item when saving. When loading the item, it first loads the component and uses it to instantiate * along with the item when saving. When loading the item, it first loads the component and uses it to instantiate
@ -45,7 +44,7 @@ trait PersistedInventory extends Inventory with Logging with Persistable {
case ItemLoadException(message) => case ItemLoadException(message) =>
logger.error( logger.error(
s"Could not restore an item in the slot $slotIndex of " + s"Could not restore an item in the slot $slotIndex of " +
s"the inventory $this (class ${this.getClass.getName}): $message", s"the inventory $this (class ${this.getClass.getName}): $message"
) )
onSlotLoadFailed(slotIndex) onSlotLoadFailed(slotIndex)
} }
@ -60,7 +59,8 @@ trait PersistedInventory extends Inventory with Logging with Persistable {
super.save(nbt) super.save(nbt)
nbt.setNewTagList( nbt.setNewTagList(
InventoryTag, inventoryIterator.map { slot => InventoryTag,
inventoryIterator.map { slot =>
val item = slot.get.get val item = slot.get.get
val slotNbt = new NBTTagCompound val slotNbt = new NBTTagCompound
@ -77,7 +77,7 @@ trait PersistedInventory extends Inventory with Logging with Persistable {
} }
slotNbt slotNbt
} },
) )
} }
@ -97,7 +97,7 @@ trait PersistedInventory extends Inventory with Logging with Persistable {
constructor.newInstance(entity).asInstanceOf[I] constructor.newInstance(entity).asInstanceOf[I]
} }
//noinspection ScalaWeakerAccess // noinspection ScalaWeakerAccess
@throws[ItemLoadException]("if the item could not be loaded") @throws[ItemLoadException]("if the item could not be loaded")
protected def loadPlainItem(itemClass: Class[_]): I = { protected def loadPlainItem(itemClass: Class[_]): I = {
itemClass.getConstructor().newInstance().asInstanceOf[I] itemClass.getConstructor().newInstance().asInstanceOf[I]

View File

@ -16,8 +16,7 @@ import totoro.ocelot.brain.network.Network
import scala.annotation.tailrec import scala.annotation.tailrec
/** /** A [[PersistedInventory]] backed by a [[BrainInventory brain Inventory]].
* A [[PersistedInventory]] backed by a [[BrainInventory brain Inventory]].
* *
* Synchronizes the contents of the two inventories, propagating changes from one to the other. * Synchronizes the contents of the two inventories, propagating changes from one to the other.
* *
@ -36,11 +35,10 @@ trait SyncedInventory extends PersistedInventory with EventAware with Logging {
@volatile @volatile
private var syncFuel: Int = _ private var syncFuel: Int = _
refuel() refuel()
/** /** The backing [[BrainInventory brain Inventory]]. */
* The backing [[BrainInventory brain Inventory]].
*/
def brainInventory: BrainInventory def brainInventory: BrainInventory
override def load(nbt: NBTTagCompound): Unit = { override def load(nbt: NBTTagCompound): Unit = {
@ -210,7 +208,6 @@ trait SyncedInventory extends PersistedInventory with EventAware with Logging {
case SyncDirection.Reconcile => case SyncDirection.Reconcile =>
checkSlotStatus(slotIndex) match { checkSlotStatus(slotIndex) match {
case SlotStatus.Synchronized => // no-op case SlotStatus.Synchronized => // no-op
// let's just grab whatever we have // let's just grab whatever we have
case SlotStatus.DesktopNonEmpty => doSync(slotIndex, SyncDirection.DesktopToBrain) case SlotStatus.DesktopNonEmpty => doSync(slotIndex, SyncDirection.DesktopToBrain)
case SlotStatus.BrainNonEmpty => doSync(slotIndex, SyncDirection.BrainToDesktop) case SlotStatus.BrainNonEmpty => doSync(slotIndex, SyncDirection.BrainToDesktop)
@ -249,7 +246,7 @@ trait SyncedInventory extends PersistedInventory with EventAware with Logging {
) )
} }
private def checkSlotStatus(slotIndex: Int): SlotStatus = private def checkSlotStatus(slotIndex: Int): SlotStatus = {
(Slot(slotIndex).get, brainInventory.inventory(slotIndex).get) match { (Slot(slotIndex).get, brainInventory.inventory(slotIndex).get) match {
case (Some(item), Some(entity)) if item.component eq entity => SlotStatus.Synchronized case (Some(item), Some(entity)) if item.component eq entity => SlotStatus.Synchronized
case (Some(_), Some(_)) => SlotStatus.Conflict case (Some(_), Some(_)) => SlotStatus.Conflict
@ -257,6 +254,7 @@ trait SyncedInventory extends PersistedInventory with EventAware with Logging {
case (None, Some(_)) => SlotStatus.BrainNonEmpty case (None, Some(_)) => SlotStatus.BrainNonEmpty
case (None, None) => SlotStatus.Synchronized case (None, None) => SlotStatus.Synchronized
} }
}
} }
object SyncedInventory { object SyncedInventory {
@ -267,42 +265,29 @@ object SyncedInventory {
object SyncDirection extends Enumeration { object SyncDirection extends Enumeration {
type SyncDirection = Value type SyncDirection = Value
/** /** Apply a change from the [[SyncedInventory]] to [[SyncedInventory.brainInventory]]. */
* Apply a change from the [[SyncedInventory]] to [[SyncedInventory.brainInventory]].
*/
val DesktopToBrain: SyncDirection = Value val DesktopToBrain: SyncDirection = Value
/** /** Apply a change from the [[SyncedInventory.brainInventory]] to [[SyncedInventory]]. */
* Apply a change from the [[SyncedInventory.brainInventory]] to [[SyncedInventory]].
*/
val BrainToDesktop: SyncDirection = Value val BrainToDesktop: SyncDirection = Value
/** /** Try to reconcile conflicts between [[SyncedInventory]] and its [[SyncedInventory.brainInventory]]. */
* Try to reconcile conflicts between [[SyncedInventory]] and its [[SyncedInventory.brainInventory]].
*/
val Reconcile: SyncDirection = Value val Reconcile: SyncDirection = Value
} }
object SlotStatus extends Enumeration { object SlotStatus extends Enumeration {
type SlotStatus = Value type SlotStatus = Value
/** /** The slots are in sync (both are empty or they contain the same entity). */
* The slots are in sync (both are empty or they contain the same entity).
*/
val Synchronized: SlotStatus = Value val Synchronized: SlotStatus = Value
/** /** [[SyncedInventory]]'s slot is non-empty; [[SyncedInventory.brainInventory]]'s is empty. */
* [[SyncedInventory]]'s slot is non-empty; [[SyncedInventory.brainInventory]]'s is empty.
*/
val DesktopNonEmpty: SlotStatus = Value val DesktopNonEmpty: SlotStatus = Value
/** /** [[SyncedInventory]]'s slot is empty; [[SyncedInventory.brainInventory]]'s isn't. */
* [[SyncedInventory]]'s slot is empty; [[SyncedInventory.brainInventory]]'s isn't.
*/
val BrainNonEmpty: SlotStatus = Value val BrainNonEmpty: SlotStatus = Value
/** /** The [[SyncedInventory]] and the [[SyncedInventory.brainInventory]] are both non-empty
* The [[SyncedInventory]] and the [[SyncedInventory.brainInventory]] are both non-empty
* and contain different items. * and contain different items.
*/ */
val Conflict: SlotStatus = Value val Conflict: SlotStatus = Value

View File

@ -8,10 +8,7 @@ import totoro.ocelot.brain.entity.ComponentBus
import totoro.ocelot.brain.entity.traits.{Entity, Environment} import totoro.ocelot.brain.entity.traits.{Entity, Environment}
import totoro.ocelot.brain.util.Tier.Tier import totoro.ocelot.brain.util.Tier.Tier
class ComponentBusItem(val componentBus: ComponentBus) class ComponentBusItem(val componentBus: ComponentBus) extends Item with ComponentItem {
extends Item
with ComponentItem
{
override def component: Entity with Environment = componentBus override def component: Entity with Environment = componentBus
override def showAddress: Boolean = false override def showAddress: Boolean = false
override def factory: ItemFactory = new ComponentBusItem.Factory(componentBus.tier) override def factory: ItemFactory = new ComponentBusItem.Factory(componentBus.tier)

View File

@ -13,7 +13,9 @@ import totoro.ocelot.brain.util.Tier.Tier
abstract class DataCardItem extends Item with ComponentItem with PersistableItem with CardItem { abstract class DataCardItem extends Item with ComponentItem with PersistableItem with CardItem {
override def fillTooltip(tooltip: ItemTooltip): Unit = { override def fillTooltip(tooltip: ItemTooltip): Unit = {
super.fillTooltip(tooltip) super.fillTooltip(tooltip)
tooltip.addLine(s"Soft limit (${Settings.get.dataCardTimeout} sec slowdown): ${Settings.get.dataCardSoftLimit} bytes") tooltip.addLine(
s"Soft limit (${Settings.get.dataCardTimeout} sec slowdown): ${Settings.get.dataCardSoftLimit} bytes"
)
tooltip.addLine(s"Hard limit (fail): ${Settings.get.dataCardHardLimit} bytes") tooltip.addLine(s"Hard limit (fail): ${Settings.get.dataCardHardLimit} bytes")
} }
} }

View File

@ -9,10 +9,7 @@ import totoro.ocelot.brain.entity.traits.Floppy
import totoro.ocelot.brain.entity.{DiskDriveMountable, FloppyDiskDrive} import totoro.ocelot.brain.entity.{DiskDriveMountable, FloppyDiskDrive}
import totoro.ocelot.brain.util.Tier.Tier import totoro.ocelot.brain.util.Tier.Tier
class DiskDriveMountableItem(val diskDriveMountable: DiskDriveMountable) class DiskDriveMountableItem(val diskDriveMountable: DiskDriveMountable) extends RackMountableItem with DiskDriveAware {
extends RackMountableItem
with DiskDriveAware
{
override def floppyDiskDrive: FloppyDiskDrive = diskDriveMountable override def floppyDiskDrive: FloppyDiskDrive = diskDriveMountable
override def component: DiskDriveMountable = diskDriveMountable override def component: DiskDriveMountable = diskDriveMountable

View File

@ -32,8 +32,11 @@ class EepromItem(val eeprom: EEPROM) extends Item with ComponentItem with Persis
} }
if (source.isEmpty && eeprom != null) { if (source.isEmpty && eeprom != null) {
if (eeprom.codeBytes != null) if (eeprom.codeBytes != null) {
tooltip.addLine(s"Code: ${eeprom.codeBytes.map(_.length).getOrElse(0)} bytes / ${Settings.get.eepromSize} bytes") tooltip.addLine(
s"Code: ${eeprom.codeBytes.map(_.length).getOrElse(0)} bytes / ${Settings.get.eepromSize} bytes"
)
}
if (eeprom.volatileData != null) if (eeprom.volatileData != null)
tooltip.addLine(s"Data: ${eeprom.volatileData.length} bytes / ${Settings.get.eepromDataSize} bytes") tooltip.addLine(s"Data: ${eeprom.volatileData.length} bytes / ${Settings.get.eepromDataSize} bytes")
if (eeprom.readonly) if (eeprom.readonly)
@ -59,12 +62,13 @@ class EepromItem(val eeprom: EEPROM) extends Item with ComponentItem with Persis
onConfirmed = { text => onConfirmed = { text =>
eeprom.codeURL = Some(new URL(text)) eeprom.codeURL = Some(new URL(text))
}, },
inputValidator = text => inputValidator = text => {
try { try {
new URL(text) new URL(text)
true true
} catch { } catch {
case _: MalformedURLException => false case _: MalformedURLException => false
}
}, },
).show() ).show()
}) })

View File

@ -7,7 +7,7 @@ import ocelot.desktop.ui.widget.tooltip.ItemTooltip
import totoro.ocelot.brain.entity.fs.ReadWriteLabel import totoro.ocelot.brain.entity.fs.ReadWriteLabel
import totoro.ocelot.brain.entity.traits.{Disk, Entity, Floppy} import totoro.ocelot.brain.entity.traits.{Disk, Entity, Floppy}
import totoro.ocelot.brain.entity.{FloppyManaged, FloppyUnmanaged} import totoro.ocelot.brain.entity.{FloppyManaged, FloppyUnmanaged}
import totoro.ocelot.brain.loot.Loot.{LootFloppy, FloppyFactory => LootFloppyFactory} import totoro.ocelot.brain.loot.Loot.{FloppyFactory => LootFloppyFactory, LootFloppy}
import totoro.ocelot.brain.util.DyeColor import totoro.ocelot.brain.util.DyeColor
import totoro.ocelot.brain.util.Tier.Tier import totoro.ocelot.brain.util.Tier.Tier

View File

@ -7,7 +7,8 @@ import totoro.ocelot.brain.entity.GraphicsCard
import totoro.ocelot.brain.entity.traits.{Entity, GenericGPU} import totoro.ocelot.brain.entity.traits.{Entity, GenericGPU}
import totoro.ocelot.brain.util.Tier.Tier import totoro.ocelot.brain.util.Tier.Tier
class GraphicsCardItem(val gpu: GraphicsCard) extends Item with ComponentItem with PersistableItem with CardItem with GpuLikeItem { class GraphicsCardItem(val gpu: GraphicsCard)
extends Item with ComponentItem with PersistableItem with CardItem with GpuLikeItem {
override def component: Entity with GenericGPU = gpu override def component: Entity with GenericGPU = gpu
override def factory: GraphicsCardItem.Factory = new GraphicsCardItem.Factory(gpu.tier) override def factory: GraphicsCardItem.Factory = new GraphicsCardItem.Factory(gpu.tier)

View File

@ -14,12 +14,7 @@ import totoro.ocelot.brain.util.Tier
import totoro.ocelot.brain.util.Tier.Tier import totoro.ocelot.brain.util.Tier.Tier
class OcelotCardItem(val ocelotCard: OcelotCard) class OcelotCardItem(val ocelotCard: OcelotCard)
extends Item extends Item with ComponentItem with OcelotInterfaceLogStorage with PersistableItem with CardItem with Logging {
with ComponentItem
with OcelotInterfaceLogStorage
with PersistableItem
with CardItem
with Logging {
override def component: OcelotCard = ocelotCard override def component: OcelotCard = ocelotCard
@ -40,6 +35,7 @@ class OcelotCardItem(val ocelotCard: OcelotCard)
override def factory: ItemFactory = OcelotCardItem.Factory override def factory: ItemFactory = OcelotCardItem.Factory
private val OcelotSays = Array("meow", ":3", "♥", "meooow", "~(=^^)", "/ᐠ。ꞈ。ᐟ\\", "=^._.^=", "=’①。①’=") private val OcelotSays = Array("meow", ":3", "♥", "meooow", "~(=^^)", "/ᐠ。ꞈ。ᐟ\\", "=^._.^=", "=’①。①’=")
override def fillTooltip(tooltip: ItemTooltip): Unit = { override def fillTooltip(tooltip: ItemTooltip): Unit = {
super.fillTooltip(tooltip) super.fillTooltip(tooltip)
tooltip.addLine(OcelotSays(((System.currentTimeMillis() / 5000) % OcelotSays.length).toInt)) tooltip.addLine(OcelotSays(((System.currentTimeMillis() / 5000) % OcelotSays.length).toInt))

View File

@ -13,9 +13,7 @@ import totoro.ocelot.brain.nbt.NBTTagCompound
import totoro.ocelot.brain.util.Tier import totoro.ocelot.brain.util.Tier
import totoro.ocelot.brain.util.Tier.Tier import totoro.ocelot.brain.util.Tier.Tier
abstract class RedstoneCardItem extends Item with ComponentItem with PersistableItem with CardItem { abstract class RedstoneCardItem extends Item with ComponentItem with PersistableItem with CardItem {}
}
object RedstoneCardItem { object RedstoneCardItem {
abstract class Factory extends ItemFactory { abstract class Factory extends ItemFactory {

View File

@ -10,10 +10,7 @@ import totoro.ocelot.brain.util.Tier
import totoro.ocelot.brain.util.Tier.Tier import totoro.ocelot.brain.util.Tier.Tier
class SelfDestructingCardItem(val card: SelfDestructingCard) class SelfDestructingCardItem(val card: SelfDestructingCard)
extends Item extends Item with ComponentItem with PersistableItem with CardItem {
with ComponentItem
with PersistableItem
with CardItem {
override def component: Entity with Environment = card override def component: Entity with Environment = card

View File

@ -11,10 +11,7 @@ import totoro.ocelot.brain.entity.traits.Inventory
import totoro.ocelot.brain.util.Tier import totoro.ocelot.brain.util.Tier
import totoro.ocelot.brain.util.Tier.Tier import totoro.ocelot.brain.util.Tier.Tier
class ServerItem(val server: Server) class ServerItem(val server: Server) extends RackMountableItem with AudibleComputerAware {
extends RackMountableItem
with AudibleComputerAware
{
override def component: Server = server override def component: Server = server
override def factory: ItemFactory = new ServerItem.Factory(server.tier) override def factory: ItemFactory = new ServerItem.Factory(server.tier)
@ -44,24 +41,26 @@ class ServerItem(val server: Server)
eepromSlot = addSlotWidget(new EepromSlotWidget(_)) eepromSlot = addSlotWidget(new EepromSlotWidget(_))
case Tier.Two => case Tier.Two =>
cardSlots = addSlotWidgets(new CardSlotWidget(_, Tier.Three), new CardSlotWidget(_, Tier.Two), new CardSlotWidget(_, Tier.Two)) cardSlots = addSlotWidgets(new CardSlotWidget(_, Tier.Three), new CardSlotWidget(_, Tier.Two),
new CardSlotWidget(_, Tier.Two))
cpuSlot = addSlotWidget(new ComputerCpuSlotWidget(_, this, Tier.Three)) cpuSlot = addSlotWidget(new ComputerCpuSlotWidget(_, this, Tier.Three))
componentBusSlots = addSlotWidgets(new ComponentBusSlotWidget(_, Tier.Three), new ComponentBusSlotWidget(_, Tier.Three)) componentBusSlots =
memorySlots = addSlotWidgets(new MemorySlotWidget(_, Tier.Three), new MemorySlotWidget(_, Tier.Three), new MemorySlotWidget(_, Tier.Three)) addSlotWidgets(new ComponentBusSlotWidget(_, Tier.Three), new ComponentBusSlotWidget(_, Tier.Three))
memorySlots = addSlotWidgets(new MemorySlotWidget(_, Tier.Three), new MemorySlotWidget(_, Tier.Three),
new MemorySlotWidget(_, Tier.Three))
diskSlots = addSlotWidgets(new HddSlotWidget(_, Tier.Three), new HddSlotWidget(_, Tier.Three)) diskSlots = addSlotWidgets(new HddSlotWidget(_, Tier.Three), new HddSlotWidget(_, Tier.Three))
eepromSlot = addSlotWidget(new EepromSlotWidget(_)) eepromSlot = addSlotWidget(new EepromSlotWidget(_))
case _ => case _ =>
cardSlots = cardSlots = {
if (server.tier == Tier.Three) { if (server.tier == Tier.Three) {
addSlotWidgets( addSlotWidgets(
new CardSlotWidget(_, Tier.Three), new CardSlotWidget(_, Tier.Three),
new CardSlotWidget(_, Tier.Three), new CardSlotWidget(_, Tier.Three),
new CardSlotWidget(_, Tier.Two), new CardSlotWidget(_, Tier.Two),
new CardSlotWidget(_, Tier.Two) new CardSlotWidget(_, Tier.Two),
) )
} } else {
else {
addSlotWidgets( addSlotWidgets(
new CardSlotWidget(_, Tier.Three), new CardSlotWidget(_, Tier.Three),
new CardSlotWidget(_, Tier.Three), new CardSlotWidget(_, Tier.Three),
@ -69,6 +68,7 @@ class ServerItem(val server: Server)
new CardSlotWidget(_, Tier.Three), new CardSlotWidget(_, Tier.Three),
) )
} }
}
cpuSlot = addSlotWidget(new ComputerCpuSlotWidget(_, this, Tier.Three)) cpuSlot = addSlotWidget(new ComputerCpuSlotWidget(_, this, Tier.Three))
@ -82,7 +82,7 @@ class ServerItem(val server: Server)
new MemorySlotWidget(_, Tier.Three), new MemorySlotWidget(_, Tier.Three),
new MemorySlotWidget(_, Tier.Three), new MemorySlotWidget(_, Tier.Three),
new MemorySlotWidget(_, Tier.Three), new MemorySlotWidget(_, Tier.Three),
new MemorySlotWidget(_, Tier.Three) new MemorySlotWidget(_, Tier.Three),
) )
diskSlots = addSlotWidgets( diskSlots = addSlotWidgets(

View File

@ -12,11 +12,7 @@ import totoro.ocelot.brain.util.Tier
import totoro.ocelot.brain.util.Tier.Tier import totoro.ocelot.brain.util.Tier.Tier
class SoundCardItem(val soundCard: SoundCard) class SoundCardItem(val soundCard: SoundCard)
extends Item extends Item with ComponentItem with PersistableItem with CardItem with Windowed[SoundCardWindow] {
with ComponentItem
with PersistableItem
with CardItem
with Windowed[SoundCardWindow] {
override def createWindow(): SoundCardWindow = new SoundCardWindow(soundCard) override def createWindow(): SoundCardWindow = new SoundCardWindow(soundCard)

View File

@ -6,9 +6,7 @@ import totoro.ocelot.brain.entity.WirelessNetworkCard
import totoro.ocelot.brain.util.Tier import totoro.ocelot.brain.util.Tier
import totoro.ocelot.brain.util.Tier.Tier import totoro.ocelot.brain.util.Tier.Tier
abstract class WirelessNetworkCardItem(override val card: WirelessNetworkCard) abstract class WirelessNetworkCardItem(override val card: WirelessNetworkCard) extends NetworkCardItem(card) {}
extends NetworkCardItem(card) {
}
object WirelessNetworkCardItem { object WirelessNetworkCardItem {
abstract class Factory extends ItemFactory { abstract class Factory extends ItemFactory {

View File

@ -2,7 +2,5 @@ package ocelot.desktop.inventory.traits
import ocelot.desktop.inventory.Item import ocelot.desktop.inventory.Item
/** /** A marker trait implemented by [[Item]]s that can be put into card slots. */
* A marker trait implemented by [[Item]]s that can be put into card slots.
*/
trait CardItem extends ComponentItem trait CardItem extends ComponentItem

View File

@ -8,14 +8,13 @@ import ocelot.desktop.ui.widget.contextmenu.{ContextMenu, ContextMenuEntry}
import ocelot.desktop.ui.widget.tooltip.ItemTooltip import ocelot.desktop.ui.widget.tooltip.ItemTooltip
import totoro.ocelot.brain.entity.traits.{Entity, Environment, WorkspaceAware} import totoro.ocelot.brain.entity.traits.{Entity, Environment, WorkspaceAware}
/** /** Implemented by [[Item]]s that wrap a component.
* Implemented by [[Item]]s that wrap a component.
* *
* @note Subclasses must provide a unary constructor that accepts the [[component]]. * @note Subclasses must provide a unary constructor that accepts the [[component]].
*/ */
trait ComponentItem extends Item with PersistableItem { trait ComponentItem extends Item with PersistableItem {
/**
* The component wrapped by this item. /** The component wrapped by this item.
* *
* @note The value must already be available during the initialization. * @note The value must already be available during the initialization.
*/ */

View File

@ -8,9 +8,7 @@ import ocelot.desktop.ui.widget.tooltip.ItemTooltip
import totoro.ocelot.brain.entity.machine.MachineAPI import totoro.ocelot.brain.entity.machine.MachineAPI
import totoro.ocelot.brain.entity.traits.{Entity, GenericCPU} import totoro.ocelot.brain.entity.traits.{Entity, GenericCPU}
/** /** An [[Item]] that acts as a CPU and can therefore be inserted into a CPU slot. */
* An [[Item]] that acts as a CPU and can therefore be inserted into a CPU slot.
*/
trait CpuLikeItem extends ComponentItem { trait CpuLikeItem extends ComponentItem {
override def component: Entity with GenericCPU override def component: Entity with GenericCPU

View File

@ -13,9 +13,7 @@ import totoro.ocelot.brain.util.DyeColor
import javax.swing.JFileChooser import javax.swing.JFileChooser
import scala.util.Try import scala.util.Try
/** /** A utility mixin for HDDs and floppies. */
* A utility mixin for HDDs and floppies.
*/
trait DiskItem extends ComponentItem with Windowed[DiskEditWindow] { trait DiskItem extends ComponentItem with Windowed[DiskEditWindow] {
override def component: Entity with Disk override def component: Entity with Disk
@ -51,7 +49,8 @@ trait DiskItem extends ComponentItem with Windowed[DiskEditWindow] {
// Real path // Real path
component match { component match {
case diskManaged: DiskManaged => case diskManaged: DiskManaged =>
DiskItem.addRealPathContextMenuEntries(menu, diskManaged, realPathSetter => { DiskItem.addRealPathContextMenuEntries(menu, diskManaged,
realPathSetter => {
reinserting { reinserting {
realPathSetter() realPathSetter()
} }
@ -94,10 +93,11 @@ trait DiskItem extends ComponentItem with Windowed[DiskEditWindow] {
} }
object DiskItem { object DiskItem {
def addRealPathContextMenuEntries(menu: ContextMenu, diskRealPathAware: DiskRealPathAware, realPathSetter: (() => Unit) => Unit): Unit = { def addRealPathContextMenuEntries(menu: ContextMenu, diskRealPathAware: DiskRealPathAware,
realPathSetter: (() => Unit) => Unit): Unit = {
menu.addEntry(ContextMenuEntry( menu.addEntry(ContextMenuEntry(
if (diskRealPathAware.customRealPath.isDefined) "Change directory" else "Set directory", if (diskRealPathAware.customRealPath.isDefined) "Change directory" else "Set directory",
IconSource.Folder IconSource.Folder,
) { ) {
OcelotDesktop.showFileChooserDialog(JFileChooser.OPEN_DIALOG, JFileChooser.DIRECTORIES_ONLY) { dir => OcelotDesktop.showFileChooserDialog(JFileChooser.OPEN_DIALOG, JFileChooser.DIRECTORIES_ONLY) { dir =>
Try { Try {

View File

@ -3,7 +3,5 @@ package ocelot.desktop.inventory.traits
import ocelot.desktop.inventory.Item import ocelot.desktop.inventory.Item
import ocelot.desktop.util.Persistable import ocelot.desktop.util.Persistable
/** /** Provides serialization and deserialization for an [[Item]]. */
* Provides serialization and deserialization for an [[Item]].
*/
trait PersistableItem extends Item with Persistable trait PersistableItem extends Item with Persistable

View File

@ -6,10 +6,7 @@ import ocelot.desktop.ui.widget.contextmenu.ContextMenu
import totoro.ocelot.brain.entity.result import totoro.ocelot.brain.entity.result
import totoro.ocelot.brain.entity.traits.{Entity, RackMountable} import totoro.ocelot.brain.entity.traits.{Entity, RackMountable}
trait RackMountableItem trait RackMountableItem extends Item with ComponentItem {
extends Item
with ComponentItem
{
override def component: Entity with RackMountable override def component: Entity with RackMountable
def isInRack: Boolean = slot.exists(_.inventory.isInstanceOf[RackNode]) def isInRack: Boolean = slot.exists(_.inventory.isInstanceOf[RackNode])

View File

@ -220,8 +220,8 @@ abstract class Node extends Widget with MouseHandler with HoverHandler with Pers
private def move(pos: Vector2D): Unit = { private def move(pos: Vector2D): Unit = {
val oldPos = position val oldPos = position
val desiredPos = val desiredPos = {
if (KeyEvents.isControlDown) if (KeyEvents.isControlDown) {
(pos - workspaceView.cameraOffset).snap(Size) + // snap the position to the grid relative to the origin (pos - workspaceView.cameraOffset).snap(Size) + // snap the position to the grid relative to the origin
workspaceView.cameraOffset - // restore the camera offset workspaceView.cameraOffset - // restore the camera offset
grabPoint.snap(Size) + // accounts for multi-block screens grabPoint.snap(Size) + // accounts for multi-block screens
@ -229,8 +229,9 @@ abstract class Node extends Widget with MouseHandler with HoverHandler with Pers
((Size - width) % Size) / 2, ((Size - width) % Size) / 2,
((Size - height) % Size) / 2, ((Size - height) % Size) / 2,
) // if a node is not full-size, moves it to the center of the grid cell ) // if a node is not full-size, moves it to the center of the grid cell
else } else
pos - grabPoint pos - grabPoint
}
position = desiredPos position = desiredPos
workspaceView.resolveCollision(this) workspaceView.resolveCollision(this)
@ -260,7 +261,7 @@ abstract class Node extends Widget with MouseHandler with HoverHandler with Pers
g.sprite( g.sprite(
iconSource.path, iconSource.path,
position. x + HighlightThickness, position.x + HighlightThickness,
position.y + HighlightThickness, position.y + HighlightThickness,
size.width - HighlightThickness * 2, size.width - HighlightThickness * 2,
size.height - HighlightThickness * 2, size.height - HighlightThickness * 2,

View File

@ -3,7 +3,10 @@ package ocelot.desktop.node
import ocelot.desktop.entity.{Camera, OcelotBlock, OpenFMRadio} import ocelot.desktop.entity.{Camera, OcelotBlock, OpenFMRadio}
import ocelot.desktop.graphics.IconSource import ocelot.desktop.graphics.IconSource
import ocelot.desktop.node.nodes._ import ocelot.desktop.node.nodes._
import totoro.ocelot.brain.entity.{Cable, Case, ColorfulLamp, FloppyDiskDrive, HologramProjector, IronNoteBlock, Microcontroller, NoteBlock, Rack, Raid, Relay, Screen, TapeDrive} import totoro.ocelot.brain.entity.{
Cable, Case, ColorfulLamp, FloppyDiskDrive, HologramProjector, IronNoteBlock, Microcontroller, NoteBlock, Rack, Raid,
Relay, Screen, TapeDrive,
}
import totoro.ocelot.brain.util.Tier import totoro.ocelot.brain.util.Tier
import scala.collection.mutable import scala.collection.mutable

View File

@ -15,7 +15,7 @@ import scala.util.Random
trait OcelotLogParticleNode extends Node { trait OcelotLogParticleNode extends Node {
private case class LogParticle( private case class LogParticle(
var time: Float = -LogParticleGrow, var time: Float = -LogParticleGrow,
angle: Float = Random.between(0f, 2 * math.Pi.toFloat * LogParticleMaxAngle) angle: Float = Random.between(0f, 2 * math.Pi.toFloat * LogParticleMaxAngle),
) )
// access should be synchronized because log particles are added in the update thread // access should be synchronized because log particles are added in the update thread
@ -76,5 +76,3 @@ object OcelotLogParticleNode {
private val LogParticleMoveSpeed: Float = 1f private val LogParticleMoveSpeed: Float = 1f
private val LogParticleMoveDistance: Float = 20f private val LogParticleMoveDistance: Float = 20f
} }

View File

@ -29,10 +29,9 @@ trait PositionalSoundSourcesNode extends Node {
soundPosition = Vector3D( soundPosition = Vector3D(
(nodeCenterX - rootWidthHalf) / rootWidthHalf * limit, (nodeCenterX - rootWidthHalf) / rootWidthHalf * limit,
(nodeCenterY - rootHeightHalf) / rootHeightHalf * limit, (nodeCenterY - rootHeightHalf) / rootHeightHalf * limit,
0 0,
) )
} } else {
else {
soundPosition = Vector3D.Zero soundPosition = Vector3D.Zero
} }

View File

@ -16,9 +16,9 @@ class ColorfulLampNode(val lamp: ColorfulLamp) extends EntityNode(lamp) with Lab
super.draw(g) super.draw(g)
lastColor = RGBAColor( lastColor = RGBAColor(
(((lamp.color >>> 10) & 0x1F) << 3).toShort, (((lamp.color >>> 10) & 0x1f) << 3).toShort,
(((lamp.color >>> 5) & 0x1F) << 3).toShort, (((lamp.color >>> 5) & 0x1f) << 3).toShort,
(((lamp.color >>> 0) & 0x1F) << 3).toShort (((lamp.color >>> 0) & 0x1f) << 3).toShort,
) )
g.rect(position.x + 2, position.y + 2, size.width - 4, size.height - 4, lastColor) g.rect(position.x + 2, position.y + 2, size.width - 4, size.height - 4, lastColor)

View File

@ -15,10 +15,7 @@ import totoro.ocelot.brain.entity.traits.Inventory
import totoro.ocelot.brain.util.Tier import totoro.ocelot.brain.util.Tier
class ComputerNode(val computerCase: Case) class ComputerNode(val computerCase: Case)
extends ComputerAwareNode(computerCase) extends ComputerAwareNode(computerCase) with AudibleComputerAware with WindowedNode[ComputerWindow] {
with AudibleComputerAware
with WindowedNode[ComputerWindow]
{
override val iconSource: IconSource = IconSource.Nodes.Computer.Default override val iconSource: IconSource = IconSource.Nodes.Computer.Default
override def iconColor: Color = TierColor.get(computerCase.tier) override def iconColor: Color = TierColor.get(computerCase.tier)
@ -80,31 +77,31 @@ class ComputerNode(val computerCase: Case)
eepromSlot = addSlotWidget(new EepromSlotWidget(_)) eepromSlot = addSlotWidget(new EepromSlotWidget(_))
case _ => case _ =>
cardSlots = cardSlots = {
if (computerCase.tier == Tier.Three) { if (computerCase.tier == Tier.Three) {
addSlotWidgets( addSlotWidgets(
new CardSlotWidget(_, Tier.Three), new CardSlotWidget(_, Tier.Three),
new CardSlotWidget(_, Tier.Two), new CardSlotWidget(_, Tier.Two),
new CardSlotWidget(_, Tier.Two) new CardSlotWidget(_, Tier.Two),
) )
} } else {
else {
addSlotWidgets( addSlotWidgets(
new CardSlotWidget(_, Tier.Three), new CardSlotWidget(_, Tier.Three),
new CardSlotWidget(_, Tier.Three), new CardSlotWidget(_, Tier.Three),
new CardSlotWidget(_, Tier.Three), new CardSlotWidget(_, Tier.Three),
) )
} }
}
memorySlots = addSlotWidgets(new MemorySlotWidget(_, Tier.Three), new MemorySlotWidget(_, Tier.Three)) memorySlots = addSlotWidgets(new MemorySlotWidget(_, Tier.Three), new MemorySlotWidget(_, Tier.Three))
diskSlots = diskSlots = {
if (computerCase.tier == Tier.Three) { if (computerCase.tier == Tier.Three) {
addSlotWidgets(new HddSlotWidget(_, Tier.Three), new HddSlotWidget(_, Tier.Two)) addSlotWidgets(new HddSlotWidget(_, Tier.Three), new HddSlotWidget(_, Tier.Two))
} } else {
else {
addSlotWidgets(new HddSlotWidget(_, Tier.Three), new HddSlotWidget(_, Tier.Three)) addSlotWidgets(new HddSlotWidget(_, Tier.Three), new HddSlotWidget(_, Tier.Three))
} }
}
floppySlot = Some(addSlotWidget(new FloppySlotWidget(_))) floppySlot = Some(addSlotWidget(new FloppySlotWidget(_)))
cpuSlot = addSlotWidget(new ComputerCpuSlotWidget(_, this, Tier.Three)) cpuSlot = addSlotWidget(new ComputerCpuSlotWidget(_, this, Tier.Three))

View File

@ -6,12 +6,9 @@ import ocelot.desktop.windows.HologramProjectorWindow
import totoro.ocelot.brain.entity.HologramProjector import totoro.ocelot.brain.entity.HologramProjector
class HologramProjectorNode(val hologramProjector: HologramProjector) class HologramProjectorNode(val hologramProjector: HologramProjector)
extends EntityNode(hologramProjector) extends EntityNode(hologramProjector) with LabeledEntityNode with WindowedNode[HologramProjectorWindow] {
with LabeledEntityNode
with WindowedNode[HologramProjectorWindow] {
override def iconSource: IconSource = IconSource.Nodes.HologramProjector(hologramProjector.tier) override def iconSource: IconSource = IconSource.Nodes.HologramProjector(hologramProjector.tier)
override def createWindow(): HologramProjectorWindow = new HologramProjectorWindow(this) override def createWindow(): HologramProjectorWindow = new HologramProjectorWindow(this)
} }

View File

@ -18,7 +18,7 @@ class IronNoteBlockNode(val ironNoteBlock: IronNoteBlock) extends NoteBlockNodeB
EventBus.send(NoteBlockTriggerEvent( EventBus.send(NoteBlockTriggerEvent(
ironNoteBlock.node.address, ironNoteBlock.node.address,
Instruments(Random.nextInt(Instruments.length))._1, Instruments(Random.nextInt(Instruments.length))._1,
Random.nextInt(NumberOfPitches) Random.nextInt(NumberOfPitches),
)) ))
} }
case _ => case _ =>

View File

@ -19,8 +19,7 @@ class MicrocontrollerNode(val microcontroller: Microcontroller)
extends ComputerAwareNode(microcontroller) extends ComputerAwareNode(microcontroller)
with ComputerAware with ComputerAware
with DefaultSlotItemsFillable with DefaultSlotItemsFillable
with WindowedNode[ComputerWindow] with WindowedNode[ComputerWindow] {
{
override val iconSource: IconSource = IconSource.Nodes.Microcontroller.Default override val iconSource: IconSource = IconSource.Nodes.Microcontroller.Default
override def setupContextMenu(menu: ContextMenu, event: ClickEvent): Unit = { override def setupContextMenu(menu: ContextMenu, event: ClickEvent): Unit = {
@ -49,7 +48,7 @@ class MicrocontrollerNode(val microcontroller: Microcontroller)
NodePort(Some(Direction.Top)), NodePort(Some(Direction.Top)),
NodePort(Some(Direction.Back)), NodePort(Some(Direction.Back)),
NodePort(Some(Direction.Right)), NodePort(Some(Direction.Right)),
NodePort(Some(Direction.Left)) NodePort(Some(Direction.Left)),
) )
override def getNodeByPort(port: NodePort): network.Node = override def getNodeByPort(port: NodePort): network.Node =
@ -71,14 +70,14 @@ class MicrocontrollerNode(val microcontroller: Microcontroller)
case Tier.One => case Tier.One =>
cardSlots = addSlotWidgets( cardSlots = addSlotWidgets(
new CardSlotWidget(_, Tier.One), new CardSlotWidget(_, Tier.One),
new CardSlotWidget(_, Tier.One) new CardSlotWidget(_, Tier.One),
) )
cpuSlot = addSlotWidget(new ComputerCpuSlotWidget(_, this, Tier.One)) cpuSlot = addSlotWidget(new ComputerCpuSlotWidget(_, this, Tier.One))
memorySlots = addSlotWidgets( memorySlots = addSlotWidgets(
new MemorySlotWidget(_, Tier.One), new MemorySlotWidget(_, Tier.One),
new MemorySlotWidget(_, Tier.One) new MemorySlotWidget(_, Tier.One),
) )
eepromSlot = addSlotWidget(new EepromSlotWidget(_)) eepromSlot = addSlotWidget(new EepromSlotWidget(_))
@ -86,14 +85,14 @@ class MicrocontrollerNode(val microcontroller: Microcontroller)
case Tier.Two => case Tier.Two =>
cardSlots = addSlotWidgets( cardSlots = addSlotWidgets(
new CardSlotWidget(_, Tier.Two), new CardSlotWidget(_, Tier.Two),
new CardSlotWidget(_, Tier.One) new CardSlotWidget(_, Tier.One),
) )
cpuSlot = addSlotWidget(new ComputerCpuSlotWidget(_, this, Tier.One)) cpuSlot = addSlotWidget(new ComputerCpuSlotWidget(_, this, Tier.One))
memorySlots = addSlotWidgets( memorySlots = addSlotWidgets(
new MemorySlotWidget(_, Tier.One), new MemorySlotWidget(_, Tier.One),
new MemorySlotWidget(_, Tier.One) new MemorySlotWidget(_, Tier.One),
) )
eepromSlot = addSlotWidget(new EepromSlotWidget(_)) eepromSlot = addSlotWidget(new EepromSlotWidget(_))
@ -102,14 +101,14 @@ class MicrocontrollerNode(val microcontroller: Microcontroller)
case _ => case _ =>
cardSlots = addSlotWidgets( cardSlots = addSlotWidgets(
new CardSlotWidget(_, Tier.Three), new CardSlotWidget(_, Tier.Three),
new CardSlotWidget(_, Tier.Three) new CardSlotWidget(_, Tier.Three),
) )
cpuSlot = addSlotWidget(new ComputerCpuSlotWidget(_, this, Tier.Three)) cpuSlot = addSlotWidget(new ComputerCpuSlotWidget(_, this, Tier.Three))
memorySlots = addSlotWidgets( memorySlots = addSlotWidgets(
new MemorySlotWidget(_, Tier.Three), new MemorySlotWidget(_, Tier.Three),
new MemorySlotWidget(_, Tier.Three) new MemorySlotWidget(_, Tier.Three),
) )
eepromSlot = addSlotWidget(new EepromSlotWidget(_)) eepromSlot = addSlotWidget(new EepromSlotWidget(_))
@ -120,17 +119,19 @@ class MicrocontrollerNode(val microcontroller: Microcontroller)
// ---------------------------- DefaultSlotItemsFillable ---------------------------- // ---------------------------- DefaultSlotItemsFillable ----------------------------
override def fillSlotsWithDefaultItems(): Unit = { override def fillSlotsWithDefaultItems(): Unit = {
cardSlots(0).item = cardSlots(0).item = {
if (microcontroller.tier == Tier.Two) if (microcontroller.tier == Tier.Two)
WirelessNetworkCardItem.Tier2.Factory.build() WirelessNetworkCardItem.Tier2.Factory.build()
else else
WirelessNetworkCardItem.Tier1.Factory.build() WirelessNetworkCardItem.Tier1.Factory.build()
}
cardSlots(1).item = cardSlots(1).item = {
if (microcontroller.tier == Tier.Two) if (microcontroller.tier == Tier.Two)
RedstoneCardItem.Tier2.Factory.build() RedstoneCardItem.Tier2.Factory.build()
else else
RedstoneCardItem.Tier1.Factory.build() RedstoneCardItem.Tier1.Factory.build()
}
cpuSlot.item = new CpuItem.Factory(Tier.One).build() cpuSlot.item = new CpuItem.Factory(Tier.One).build()

View File

@ -19,7 +19,7 @@ abstract class NoteBlockNodeBase(entity: Entity with Environment) extends Entity
SoundBuffers.NoteBlock(event.instrument), SoundBuffers.NoteBlock(event.instrument),
SoundCategory.Records, SoundCategory.Records,
pitch = math.pow(2f, (event.pitch - 12).toFloat / 12f).toFloat, pitch = math.pow(2f, (event.pitch - 12).toFloat / 12f).toFloat,
volume = event.volume.toFloat.min(1f).max(0f) volume = event.volume.toFloat.min(1f).max(0f),
).play() ).play()
addParticle(event.pitch) addParticle(event.pitch)
@ -36,7 +36,8 @@ abstract class NoteBlockNodeBase(entity: Entity with Environment) extends Entity
override def drawParticles(g: Graphics): Unit = synchronized { override def drawParticles(g: Graphics): Unit = synchronized {
for ((time, pitch) <- particles.reverseIterator) { for ((time, pitch) <- particles.reverseIterator) {
val col = ColorScheme("Note" + pitch.min(24).max(0)).withAlpha(1 - (2 * time - 1).min(1).max(0)) val col = ColorScheme("Note" + pitch.min(24).max(0)).withAlpha(1 - (2 * time - 1).min(1).max(0))
g.sprite("particles/Note", position + Vector2D(pitch / 24f * 40f + 5, height / 2 - 10 - 100 * time), Size2D(14, 20), col) g.sprite("particles/Note", position + Vector2D(pitch / 24f * 40f + 5, height / 2 - 10 - 100 * time),
Size2D(14, 20), col)
} }
particles.mapInPlace { case (t, p) => (t + 1.2f * UiHandler.dt, p) } particles.mapInPlace { case (t, p) => (t + 1.2f * UiHandler.dt, p) }
particles.filterInPlace(_._1 <= 1f) particles.filterInPlace(_._1 <= 1f)

View File

@ -7,7 +7,8 @@ import ocelot.desktop.graphics.{Graphics, IconSource}
import ocelot.desktop.node.{EntityNode, LabeledEntityNode, WindowedNode} import ocelot.desktop.node.{EntityNode, LabeledEntityNode, WindowedNode}
import ocelot.desktop.windows.OpenFMRadioWindow import ocelot.desktop.windows.OpenFMRadioWindow
class OpenFMRadioNode(val openFMRadio: OpenFMRadio) extends EntityNode(openFMRadio) with LabeledEntityNode with WindowedNode[OpenFMRadioWindow] { class OpenFMRadioNode(val openFMRadio: OpenFMRadio)
extends EntityNode(openFMRadio) with LabeledEntityNode with WindowedNode[OpenFMRadioWindow] {
override def iconSource: IconSource = IconSource.Nodes.OpenFMRadio override def iconSource: IconSource = IconSource.Nodes.OpenFMRadio
private var lastTick = OcelotDesktop.ticker.tick private var lastTick = OcelotDesktop.ticker.tick
@ -32,8 +33,7 @@ class OpenFMRadioNode(val openFMRadio: OpenFMRadio) extends EntityNode(openFMRad
text = rest + first text = rest + first
animationIndex += 1 animationIndex += 1
} } else {
else {
animationIndex = 0 animationIndex = 0
} }
@ -62,7 +62,7 @@ class OpenFMRadioNode(val openFMRadio: OpenFMRadio) extends EntityNode(openFMRad
g.text( g.text(
(position.x + size.width / 2) / scale - textWidth / 2, (position.x + size.width / 2) / scale - textWidth / 2,
(position.y + 18) / scale, (position.y + 18) / scale,
text text,
) )
g.restore() g.restore()

View File

@ -17,10 +17,7 @@ import totoro.ocelot.brain.event.NetworkActivityEvent
import totoro.ocelot.brain.network import totoro.ocelot.brain.network
import totoro.ocelot.brain.util.Direction import totoro.ocelot.brain.util.Direction
class RackNode(val rack: Rack) class RackNode(val rack: Rack) extends ComputerAwareNode(rack) with WindowedNode[RackWindow] {
extends ComputerAwareNode(rack)
with WindowedNode[RackWindow]
{
override val iconSource: IconSource = IconSource.Nodes.Rack.Empty override val iconSource: IconSource = IconSource.Nodes.Rack.Empty
override def exposeAddress = false override def exposeAddress = false
@ -29,12 +26,12 @@ class RackNode(val rack: Rack)
NodePort(Some(Direction.Top)), NodePort(Some(Direction.Top)),
NodePort(Some(Direction.Back)), NodePort(Some(Direction.Back)),
NodePort(Some(Direction.Right)), NodePort(Some(Direction.Right)),
NodePort(Some(Direction.Left)) NodePort(Some(Direction.Left)),
) )
override def getNodeByPort(port: NodePort): network.Node = rack.sidedNode(port.direction.get) override def getNodeByPort(port: NodePort): network.Node = rack.sidedNode(port.direction.get)
override def shouldReceiveEventsFor(address: String): Boolean = override def shouldReceiveEventsFor(address: String): Boolean = {
super.shouldReceiveEventsFor(address) || super.shouldReceiveEventsFor(address) ||
rack.inventory.entities.exists { rack.inventory.entities.exists {
case mountable: RackMountable if mountable.node.address == address => true case mountable: RackMountable if mountable.node.address == address => true
@ -43,6 +40,7 @@ class RackNode(val rack: Rack)
} }
case _ => false case _ => false
} }
}
override def setupContextMenu(menu: ContextMenu, event: ClickEvent): Unit = { override def setupContextMenu(menu: ContextMenu, event: ClickEvent): Unit = {
RackNode.addContextMenuEntriesOfMountable(menu, getMountableByClick(event)) RackNode.addContextMenuEntriesOfMountable(menu, getMountableByClick(event))
@ -91,9 +89,9 @@ class RackNode(val rack: Rack)
x, x,
y, y,
NoHighlightSize, NoHighlightSize,
NoHighlightSize NoHighlightSize,
), ),
driveIconSource driveIconSource,
) )
case _ => case _ =>
@ -131,12 +129,12 @@ class RackNode(val rack: Rack)
val localPosition = Vector2D( val localPosition = Vector2D(
event.mousePos.x - position.x - horizontalMargin, event.mousePos.x - position.x - horizontalMargin,
event.mousePos.y - position.y - verticalMargin event.mousePos.y - position.y - verticalMargin,
) )
val m = Size2D( val m = Size2D(
this.width - horizontalMargin * 2, this.width - horizontalMargin * 2,
this.height - verticalMargin * 2 this.height - verticalMargin * 2,
) )
// Checking if click was inside mountables area // Checking if click was inside mountables area
@ -207,7 +205,7 @@ object RackNode {
"Change floppy" "Change floppy"
else else
"Set floppy", "Set floppy",
IconSource.Save IconSource.Save,
) { ) {
diskDriveMountableItem.window.open() diskDriveMountableItem.window.open()
}) })

View File

@ -18,14 +18,13 @@ import totoro.ocelot.brain.util.Tier
import scala.util.Random import scala.util.Random
class RaidNode(val raid: Raid) extends class RaidNode(val raid: Raid)
EntityNode(raid) extends EntityNode(raid)
with SyncedInventory with SyncedInventory
with LabeledEntityNode with LabeledEntityNode
with DiskActivityHandler with DiskActivityHandler
with DefaultSlotItemsFillable with DefaultSlotItemsFillable
with WindowedNode[RaidWindow] with WindowedNode[RaidWindow] {
{
var diskSlots: Array[HddSlotWidget] = Array.tabulate(3)(index => new HddSlotWidget(Slot(index), Tier.Three)) var diskSlots: Array[HddSlotWidget] = Array.tabulate(3)(index => new HddSlotWidget(Slot(index), Tier.Three))
override val iconSource: IconSource = IconSource.Nodes.Raid.Default override val iconSource: IconSource = IconSource.Nodes.Raid.Default
@ -45,7 +44,9 @@ class RaidNode(val raid: Raid) extends
} }
// Disk activity overlay // Disk activity overlay
if (raid.shouldVisualizeDiskActivity && Random.nextDouble() > 0.1 && i == raid.lastDiskAccess % raid.getSizeInventory) { if (
raid.shouldVisualizeDiskActivity && Random.nextDouble() > 0.1 && i == raid.lastDiskAccess % raid.getSizeInventory
) {
DrawUtils.drawFilesystemActivity(g, x, y, driveIconSource) DrawUtils.drawFilesystemActivity(g, x, y, driveIconSource)
} }
} }
@ -56,7 +57,8 @@ class RaidNode(val raid: Raid) extends
super.shouldReceiveEventsFor(address) || raid.filesystem.exists(_.node.address == address) super.shouldReceiveEventsFor(address) || raid.filesystem.exists(_.node.address == address)
override def setupContextMenu(menu: ContextMenu, event: ClickEvent): Unit = { override def setupContextMenu(menu: ContextMenu, event: ClickEvent): Unit = {
DiskItem.addRealPathContextMenuEntries(menu, raid, realPathSetter => { DiskItem.addRealPathContextMenuEntries(menu, raid,
realPathSetter => {
realPathSetter() realPathSetter()
}) })

View File

@ -9,10 +9,7 @@ import totoro.ocelot.brain.network
import totoro.ocelot.brain.util.Direction import totoro.ocelot.brain.util.Direction
class RelayNode(val relay: Relay) class RelayNode(val relay: Relay)
extends EntityNode(relay) extends EntityNode(relay) with SyncedInventory with LabeledNode with WindowedNode[RelayWindow] {
with SyncedInventory
with LabeledNode
with WindowedNode[RelayWindow] {
override val iconSource: IconSource = IconSource.Nodes.Relay override val iconSource: IconSource = IconSource.Nodes.Relay
@ -22,7 +19,8 @@ class RelayNode(val relay: Relay)
NodePort(Some(Direction.East)), NodePort(Some(Direction.East)),
NodePort(Some(Direction.West)), NodePort(Some(Direction.West)),
NodePort(Some(Direction.Up)), NodePort(Some(Direction.Up)),
NodePort(Some(Direction.Down))) NodePort(Some(Direction.Down)),
)
override def getNodeByPort(port: NodePort): network.Node = relay.sidedNode(port.direction.get) override def getNodeByPort(port: NodePort): network.Node = relay.sidedNode(port.direction.get)

View File

@ -19,18 +19,14 @@ import totoro.ocelot.brain.nbt.NBTTagCompound
import totoro.ocelot.brain.util.PackedColor import totoro.ocelot.brain.util.PackedColor
class ScreenNode(val screen: Screen) class ScreenNode(val screen: Screen)
extends EntityNode(screen) extends EntityNode(screen) with LabeledEntityNode with WindowedNode[ScreenWindow] with TickUpdatable {
with LabeledEntityNode
with WindowedNode[ScreenWindow]
with TickUpdatable {
// NOTE: whenever you access screen's TextBuffer methods, make sure to synchronize on `screen`: // NOTE: whenever you access screen's TextBuffer methods, make sure to synchronize on `screen`:
// computers may update the screen data concurrently via direct methods! // computers may update the screen data concurrently via direct methods!
// conversely, if a property is only modified via indirect methods, no additional synchronization is necessary. // conversely, if a property is only modified via indirect methods, no additional synchronization is necessary.
override def minimumSize: Size2D = Size2D( override def minimumSize: Size2D = Size2D(
Size * screen.aspectRatio._1, Size * screen.aspectRatio._1,
Size * screen.aspectRatio._2 Size * screen.aspectRatio._2,
) )
override def maximumSize: Size2D = minimumSize override def maximumSize: Size2D = minimumSize
@ -170,7 +166,7 @@ class ScreenNode(val screen: Screen)
startY: Float, startY: Float,
scaleX: Float, scaleX: Float,
scaleY: Float, scaleY: Float,
filteringMode: MinFilteringMode filteringMode: MinFilteringMode,
): Unit = { ): Unit = {
g.save() g.save()
g.scale(scaleX, scaleY) g.scale(scaleX, scaleY)
@ -196,7 +192,7 @@ class ScreenNode(val screen: Screen)
y, y,
width, width,
height, height,
iconColor iconColor,
) )
} }
@ -233,7 +229,7 @@ class ScreenNode(val screen: Screen)
position.x + HighlightThickness, position.x + HighlightThickness,
position.y + y * Size, position.y + y * Size,
NoHighlightSize, NoHighlightSize,
Size Size,
) )
} }
@ -261,14 +257,15 @@ class ScreenNode(val screen: Screen)
) )
// Middle // Middle
for (x <- 1 until aspectRatioWidth - 1) for (x <- 1 until aspectRatioWidth - 1) {
drawScreenPart( drawScreenPart(
IconSource.Nodes.Screen.RowMiddle, IconSource.Nodes.Screen.RowMiddle,
position.x + x * Size, position.x + x * Size,
position.y + HighlightThickness, position.y + HighlightThickness,
Size, Size,
NoHighlightSize NoHighlightSize,
) )
}
// Right // Right
drawScreenPart( drawScreenPart(
@ -276,7 +273,7 @@ class ScreenNode(val screen: Screen)
position.x + (aspectRatioWidth - 1) * Size, position.x + (aspectRatioWidth - 1) * Size,
position.y + HighlightThickness, position.y + HighlightThickness,
Size - HighlightThickness, Size - HighlightThickness,
NoHighlightSize NoHighlightSize,
) )
} }
// n x n // n x n
@ -286,7 +283,7 @@ class ScreenNode(val screen: Screen)
height: Float, height: Float,
leftIcon: IconSource, leftIcon: IconSource,
middleIcon: IconSource, middleIcon: IconSource,
rightIcon: IconSource rightIcon: IconSource,
): Unit = { ): Unit = {
// Left // Left
drawScreenPart( drawScreenPart(
@ -294,18 +291,19 @@ class ScreenNode(val screen: Screen)
position.x + HighlightThickness, position.x + HighlightThickness,
y, y,
Size, Size,
height height,
) )
// Middle // Middle
for (x <- 1 until aspectRatioWidth - 1) for (x <- 1 until aspectRatioWidth - 1) {
drawScreenPart( drawScreenPart(
middleIcon, middleIcon,
position.x + x * Size, position.x + x * Size,
y, y,
Size, Size,
height height,
) )
}
// Right // Right
drawScreenPart( drawScreenPart(
@ -313,7 +311,7 @@ class ScreenNode(val screen: Screen)
position.x + (aspectRatioWidth - 1) * Size - HighlightThickness, position.x + (aspectRatioWidth - 1) * Size - HighlightThickness,
y, y,
Size, Size,
height height,
) )
} }
@ -323,18 +321,19 @@ class ScreenNode(val screen: Screen)
Size - HighlightThickness, Size - HighlightThickness,
IconSource.Nodes.Screen.TopLeft, IconSource.Nodes.Screen.TopLeft,
IconSource.Nodes.Screen.TopMiddle, IconSource.Nodes.Screen.TopMiddle,
IconSource.Nodes.Screen.TopRight IconSource.Nodes.Screen.TopRight,
) )
// Middle // Middle
for (y <- 1 until aspectRatioHeight - 1) for (y <- 1 until aspectRatioHeight - 1) {
drawLine( drawLine(
position.y + (y * Size), position.y + (y * Size),
Size, Size,
IconSource.Nodes.Screen.MiddleLeft, IconSource.Nodes.Screen.MiddleLeft,
IconSource.Nodes.Screen.Middle, IconSource.Nodes.Screen.Middle,
IconSource.Nodes.Screen.MiddleRight IconSource.Nodes.Screen.MiddleRight,
) )
}
// Bottom // Bottom
drawLine( drawLine(
@ -342,7 +341,7 @@ class ScreenNode(val screen: Screen)
Size - HighlightThickness, Size - HighlightThickness,
IconSource.Nodes.Screen.BottomLeft, IconSource.Nodes.Screen.BottomLeft,
IconSource.Nodes.Screen.BottomMiddle, IconSource.Nodes.Screen.BottomMiddle,
IconSource.Nodes.Screen.BottomRight IconSource.Nodes.Screen.BottomRight,
) )
} }
} }
@ -365,18 +364,18 @@ class ScreenNode(val screen: Screen)
virtualScreenBounds.y, virtualScreenBounds.y,
virtualScreenBounds.w, virtualScreenBounds.w,
virtualScreenBounds.h, virtualScreenBounds.h,
Color.Black Color.Black,
) )
// Calculating pixel data bounds, so that they fit perfectly into the virtual screen // Calculating pixel data bounds, so that they fit perfectly into the virtual screen
val pixelDataSize = Size2D( val pixelDataSize = Size2D(
screenWidth * FontWidth, screenWidth * FontWidth,
screenHeight * FontHeight screenHeight * FontHeight,
) )
val scale = Math.min( val scale = Math.min(
virtualScreenBounds.w / pixelDataSize.width, virtualScreenBounds.w / pixelDataSize.width,
virtualScreenBounds.h / pixelDataSize.height virtualScreenBounds.h / pixelDataSize.height,
) )
// Drawing pixel data // Drawing pixel data
@ -386,7 +385,7 @@ class ScreenNode(val screen: Screen)
virtualScreenBounds.y + virtualScreenBounds.h / 2 - (pixelDataSize.height * scale) / 2, virtualScreenBounds.y + virtualScreenBounds.h / 2 - (pixelDataSize.height * scale) / 2,
scale, scale,
scale, scale,
MinFilteringMode.NearestMipmapNearest MinFilteringMode.NearestMipmapNearest,
) )
} }
// Drawing simple overlay otherwise // Drawing simple overlay otherwise
@ -396,7 +395,7 @@ class ScreenNode(val screen: Screen)
position.x + HighlightThickness, position.x + HighlightThickness,
position.y + HighlightThickness, position.y + HighlightThickness,
NoHighlightSize, NoHighlightSize,
NoHighlightSize NoHighlightSize,
) )
} }
} }

View File

@ -16,15 +16,14 @@ class TapeDriveNode(val tapeDrive: TapeDrive)
with SyncedInventory with SyncedInventory
with LabeledEntityNode with LabeledEntityNode
with PositionalSoundSourcesNode with PositionalSoundSourcesNode
with WindowedNode[TapeDriveWindow] with WindowedNode[TapeDriveWindow] {
{
override def iconSource: IconSource = IconSource.Nodes.TapeDrive override def iconSource: IconSource = IconSource.Nodes.TapeDrive
private lazy val soundTapeRewind: SoundSource = SoundSource.fromBuffer( private lazy val soundTapeRewind: SoundSource = SoundSource.fromBuffer(
SoundBuffers.MachineTapeRewind, SoundBuffers.MachineTapeRewind,
SoundCategory.Records, SoundCategory.Records,
looping = true looping = true,
) )
private lazy val streams: (SoundStream, SoundSource) = Audio.newStream(SoundCategory.Records) private lazy val streams: (SoundStream, SoundSource) = Audio.newStream(SoundCategory.Records)
@ -53,7 +52,8 @@ class TapeDriveNode(val tapeDrive: TapeDrive)
override def update(): Unit = { override def update(): Unit = {
super.update() super.update()
val isRewinding = tapeDrive.state.state == TapeDriveState.State.Rewinding || tapeDrive.state.state == TapeDriveState.State.Forwarding val isRewinding =
tapeDrive.state.state == TapeDriveState.State.Rewinding || tapeDrive.state.state == TapeDriveState.State.Forwarding
if (!isRewinding && soundTapeRewind.isPlaying) { if (!isRewinding && soundTapeRewind.isPlaying) {
soundTapeRewind.stop() soundTapeRewind.stop()
@ -78,7 +78,6 @@ class TapeDriveNode(val tapeDrive: TapeDrive)
override def brainInventory: TapeDrive = tapeDrive override def brainInventory: TapeDrive = tapeDrive
// -------------------------------- Windowed -------------------------------- // -------------------------------- Windowed --------------------------------
override protected def createWindow(): TapeDriveWindow = new TapeDriveWindow(this) override protected def createWindow(): TapeDriveWindow = new TapeDriveWindow(this)

View File

@ -76,13 +76,15 @@ object UiHandler extends Logging {
private val _clipboard = Toolkit.getDefaultToolkit.getSystemClipboard private val _clipboard = Toolkit.getDefaultToolkit.getSystemClipboard
def clipboard: String = try { def clipboard: String = {
try {
_clipboard.getData(DataFlavor.stringFlavor).toString _clipboard.getData(DataFlavor.stringFlavor).toString
} catch { } catch {
case _: UnsupportedFlavorException => case _: UnsupportedFlavorException =>
logger.debug("Trying to paste non-Unicode content from clipboard! Replaced with empty string.") logger.debug("Trying to paste non-Unicode content from clipboard! Replaced with empty string.")
"" ""
} }
}
def clipboard_=(value: String): Unit = { def clipboard_=(value: String): Unit = {
val data = new StringSelection(value) val data = new StringSelection(value)
@ -155,11 +157,12 @@ object UiHandler extends Logging {
private def windowGeometry: Rect2D = new Rect2D(Display.getX, Display.getY, Display.getWidth, Display.getHeight) private def windowGeometry: Rect2D = new Rect2D(Display.getX, Display.getY, Display.getWidth, Display.getHeight)
private def sanitizeWindowSize(size: Size2D): Size2D = private def sanitizeWindowSize(size: Size2D): Size2D = {
Size2D( Size2D(
if (size.width < 10) 800 else size.width, if (size.width < 10) 800 else size.width,
if (size.height < 10) 600 else size.height, if (size.height < 10) 600 else size.height,
) )
}
private def sanitizeWindowGeometry(currentGeometry: Rect2D): Rect2D = { private def sanitizeWindowGeometry(currentGeometry: Rect2D): Rect2D = {
val Rect2D(x, y, w, h) = currentGeometry val Rect2D(x, y, w, h) = currentGeometry
@ -243,21 +246,22 @@ object UiHandler extends Logging {
val arch = System.getProperty("os.arch") val arch = System.getProperty("os.arch")
val is64bit = arch.startsWith("amd64") val is64bit = arch.startsWith("amd64")
val libs = val libs = {
if (SystemUtils.IS_OS_WINDOWS) if (SystemUtils.IS_OS_WINDOWS) {
if (is64bit) if (is64bit)
Array("jinput-dx8_64.dll", "jinput-raw_64.dll", "jinput-wintab.dll", "lwjgl64.dll", "OpenAL64.dll") Array("jinput-dx8_64.dll", "jinput-raw_64.dll", "jinput-wintab.dll", "lwjgl64.dll", "OpenAL64.dll")
else else
Array("jinput-dx8.dll", "jinput-raw.dll", "jinput-wintab.dll", "lwjgl.dll", "OpenAL32.dll") Array("jinput-dx8.dll", "jinput-raw.dll", "jinput-wintab.dll", "lwjgl.dll", "OpenAL32.dll")
else if (SystemUtils.IS_OS_MAC_OSX) } else if (SystemUtils.IS_OS_MAC_OSX)
Array("liblwjgl.dylib") Array("liblwjgl.dylib")
else if (SystemUtils.IS_OS_LINUX) else if (SystemUtils.IS_OS_LINUX) {
if (is64bit) if (is64bit)
Array("libjinput-linux64.so", "liblwjgl64.so", "libopenal64.so") Array("libjinput-linux64.so", "liblwjgl64.so", "libopenal64.so")
else else
Array("libjinput-linux.so", "liblwjgl.so", "libopenal.so") Array("libjinput-linux.so", "liblwjgl.so", "libopenal.so")
else } else
throw new Exception("Unsupported OS") throw new Exception("Unsupported OS")
}
logger.debug(s"Unpacking native libraries to: $librariesPath") logger.debug(s"Unpacking native libraries to: $librariesPath")
@ -329,13 +333,14 @@ object UiHandler extends Logging {
UiThreadTasks.add(updateWindowTitle) UiThreadTasks.add(updateWindowTitle)
} }
private def updateWindowTitle(): Unit = private def updateWindowTitle(): Unit = {
Display.setTitle( Display.setTitle(
if (_windowTitleSuffix.isEmpty) if (_windowTitleSuffix.isEmpty)
windowTitleBase windowTitleBase
else else
s"$windowTitleBase - ${_windowTitleSuffix.get}" s"$windowTitleBase - ${_windowTitleSuffix.get}"
) )
}
private var exitRequested = false private var exitRequested = false
@ -441,7 +446,7 @@ object UiHandler extends Logging {
ScrollEvents.events.foreach(dispatchEvent(scrollTarget)) ScrollEvents.events.foreach(dispatchEvent(scrollTarget))
for (event <- MouseEvents.events) for (event <- MouseEvents.events) {
if (event.state == MouseEvent.State.Press) { if (event.state == MouseEvent.State.Press) {
dispatchEvent(mouseTarget)(event) dispatchEvent(mouseTarget)(event)
@ -452,6 +457,7 @@ object UiHandler extends Logging {
} else { } else {
dispatchEvent(hierarchy.reverseIterator)(event) dispatchEvent(hierarchy.reverseIterator)(event)
} }
}
hierarchy.reverseIterator.foreach { hierarchy.reverseIterator.foreach {
case handler: HoverHandler if !mouseTarget.contains(handler) => handler.setHovered(false) case handler: HoverHandler if !mouseTarget.contains(handler) => handler.setHovered(false)

View File

@ -3,14 +3,12 @@ package ocelot.desktop.ui.event
import ocelot.desktop.geometry.Vector2D import ocelot.desktop.geometry.Vector2D
object DragEvent { object DragEvent {
object State extends Enumeration { object State extends Enumeration {
val Start, Drag, Stop = Value val Start, Drag, Stop = Value
} }
def apply(state: DragEvent.State.Value, button: MouseEvent.Button.Value, mousePos: Vector2D, def apply(state: DragEvent.State.Value, button: MouseEvent.Button.Value, mousePos: Vector2D,
start: Vector2D, delta: Vector2D): DragEvent = start: Vector2D, delta: Vector2D): DragEvent = {
{
val ev = DragEvent(state, button, mousePos) val ev = DragEvent(state, button, mousePos)
ev._start = start ev._start = start
ev._delta = delta ev._delta = delta

View File

@ -1,11 +1,9 @@
package ocelot.desktop.ui.event package ocelot.desktop.ui.event
object HoverEvent { object HoverEvent {
object State extends Enumeration { object State extends Enumeration {
val Enter, Leave = Value val Enter, Leave = Value
} }
} }
case class HoverEvent(state: HoverEvent.State.Value) extends Event case class HoverEvent(state: HoverEvent.State.Value) extends Event

View File

@ -1,8 +1,6 @@
package ocelot.desktop.ui.event package ocelot.desktop.ui.event
object MouseEvent { object MouseEvent {
object State extends Enumeration { object State extends Enumeration {
val Press, Release = Value val Press, Release = Value
} }
@ -12,7 +10,6 @@ object MouseEvent {
val Right: Button.Value = Value(1) val Right: Button.Value = Value(1)
val Middle: Button.Value = Value(2) val Middle: Button.Value = Value(2)
} }
} }
case class MouseEvent(state: MouseEvent.State.Value, button: MouseEvent.Button.Value) extends Event case class MouseEvent(state: MouseEvent.State.Value, button: MouseEvent.Button.Value) extends Event

View File

@ -10,13 +10,14 @@ import scala.util.Random
trait DiskActivityHandler extends EventAware { trait DiskActivityHandler extends EventAware {
eventHandlers += { eventHandlers += {
case BrainEvent(event: FileSystemActivityEvent) if !Audio.isDisabled => case BrainEvent(event: FileSystemActivityEvent) if !Audio.isDisabled =>
val sound = val sound = {
if (event.activityType == Floppy) if (event.activityType == Floppy)
SoundBuffers.MachineFloppyAccess SoundBuffers.MachineFloppyAccess
.map(buffer => SoundSource.fromBuffer(buffer, SoundCategory.Environment)) .map(buffer => SoundSource.fromBuffer(buffer, SoundCategory.Environment))
else else
SoundBuffers.MachineHDDAccess SoundBuffers.MachineHDDAccess
.map(buffer => SoundSource.fromBuffer(buffer, SoundCategory.Environment)) .map(buffer => SoundSource.fromBuffer(buffer, SoundCategory.Environment))
}
sound(Random.between(0, sound.length)).play() sound(Random.between(0, sound.length)).play()
} }

View File

@ -19,8 +19,7 @@ trait MouseHandler extends Widget {
protected def receiveDragEvents: Boolean = false protected def receiveDragEvents: Boolean = false
/** /** If `true`, a [[ClickEvent]] will be registered even if the mouse button is released
* If `true`, a [[ClickEvent]] will be registered even if the mouse button is released
* outside the tolerance threshold, as long as it stays within the widget's bounds. * outside the tolerance threshold, as long as it stays within the widget's bounds.
*/ */
protected def allowClickReleaseOutsideThreshold: Boolean = !receiveDragEvents protected def allowClickReleaseOutsideThreshold: Boolean = !receiveDragEvents
@ -73,12 +72,14 @@ trait MouseHandler extends Widget {
} }
} }
dragButtons.foreach( dragButtons.foreach(button => {
button => {
handleEvent( handleEvent(
DragEvent( DragEvent(
DragEvent.State.Drag, button, mousePos, startPositions(button), DragEvent.State.Drag,
mousePos - prevPositions(button) button,
mousePos,
startPositions(button),
mousePos - prevPositions(button),
) )
) )
prevPositions(button) = mousePos prevPositions(button) = mousePos

View File

@ -8,8 +8,8 @@ class LinearLayout(widget: Widget,
orientation: Orientation.Value = Orientation.Horizontal, orientation: Orientation.Value = Orientation.Horizontal,
justifyContent: JustifyContent.Value = JustifyContent.Start, justifyContent: JustifyContent.Value = JustifyContent.Start,
alignItems: AlignItems.Value = AlignItems.Stretch, alignItems: AlignItems.Value = AlignItems.Stretch,
gap: Float = 0f) extends Layout(widget) gap: Float = 0f)
{ extends Layout(widget) {
override def recalculateBounds(): Unit = { override def recalculateBounds(): Unit = {
super.recalculateBounds() super.recalculateBounds()

View File

@ -35,7 +35,8 @@ class SplashScreen extends JDialog {
panel.setBackground(new Color(0, 0, 0, 0)) panel.setBackground(new Color(0, 0, 0, 0))
panel.setLayout(null) panel.setLayout(null)
private def addLabel(rectangle: Rectangle, text: String, horizontalAlignment: Int = SwingConstants.LEFT, verticalAlignment: Int = SwingConstants.TOP): JLabel = { private def addLabel(rectangle: Rectangle, text: String, horizontalAlignment: Int = SwingConstants.LEFT,
verticalAlignment: Int = SwingConstants.TOP): JLabel = {
val label = new JLabel(text) val label = new JLabel(text)
label.setForeground(Color.white) label.setForeground(Color.white)
label.setBounds(rectangle) label.setBounds(rectangle)
@ -57,7 +58,7 @@ class SplashScreen extends JDialog {
new Rectangle(0, 0, imageWidth, imageHeight), new Rectangle(0, 0, imageWidth, imageHeight),
s"Version: ${BuildInfo.version} (${BuildInfo.commit.take(7)})", s"Version: ${BuildInfo.version} (${BuildInfo.commit.take(7)})",
SwingConstants.LEFT, SwingConstants.LEFT,
SwingConstants.BOTTOM SwingConstants.BOTTOM,
) )
setLabelMargin(applicationVersion, new Insets(0, 20, 20, 0)) setLabelMargin(applicationVersion, new Insets(0, 20, 20, 0))
@ -70,7 +71,7 @@ class SplashScreen extends JDialog {
new Rectangle(0, 0, imageWidth, imageHeight), new Rectangle(0, 0, imageWidth, imageHeight),
s"© 2018 - ${Year.now.getValue} Ocelot Dev Team", s"© 2018 - ${Year.now.getValue} Ocelot Dev Team",
SwingConstants.RIGHT, SwingConstants.RIGHT,
SwingConstants.BOTTOM SwingConstants.BOTTOM,
) )
setLabelMargin(copyright, new Insets(0, 0, 20, 20)) setLabelMargin(copyright, new Insets(0, 0, 20, 20))

View File

@ -11,11 +11,7 @@ import ocelot.desktop.ui.widget.tooltip.Tooltip
import ocelot.desktop.ui.widget.traits.HoverAnimation import ocelot.desktop.ui.widget.traits.HoverAnimation
import ocelot.desktop.util.DrawUtils import ocelot.desktop.util.DrawUtils
class Button(tooltip: Option[Tooltip] = None) class Button(tooltip: Option[Tooltip] = None) extends Widget with MouseHandler with HoverHandler with HoverAnimation {
extends Widget
with MouseHandler
with HoverHandler
with HoverAnimation {
def this(tooltip: Tooltip) = this(Some(tooltip)) def this(tooltip: Tooltip) = this(Some(tooltip))
@ -53,11 +49,12 @@ class Button(tooltip: Option[Tooltip] = None)
val (background, border, foreground) = if (enabled) ( val (background, border, foreground) = if (enabled) (
hoverAnimation.color, hoverAnimation.color,
colorScheme("ButtonBorder"), colorScheme("ButtonBorder"),
colorScheme("ButtonForeground") colorScheme("ButtonForeground"),
) else ( )
else (
colorScheme("ButtonBackgroundDisabled"), colorScheme("ButtonBackgroundDisabled"),
colorScheme("ButtonBorderDisabled"), colorScheme("ButtonBorderDisabled"),
colorScheme("ButtonForegroundDisabled") colorScheme("ButtonForegroundDisabled"),
) )
g.rect(bounds, background) g.rect(bounds, background)

View File

@ -21,7 +21,8 @@ class ChangeSimulationSpeedDialog extends ModalDialog {
close() close()
} }
children :+= new PaddingBox(new Widget { children :+= new PaddingBox(
new Widget {
override val layout = new LinearLayout(this, orientation = Orientation.Vertical) override val layout = new LinearLayout(this, orientation = Orientation.Vertical)
children :+= new PaddingBox(new Label("Change simulation speed"), Padding2D(bottom = 16)) children :+= new PaddingBox(new Label("Change simulation speed"), Padding2D(bottom = 16))
@ -34,11 +35,13 @@ class ChangeSimulationSpeedDialog extends ModalDialog {
inputMSPT = new TextInput(formatMSPT(OcelotDesktop.ticker.tickInterval)) { inputMSPT = new TextInput(formatMSPT(OcelotDesktop.ticker.tickInterval)) {
focus() focus()
private def parseInput(text: String): Option[Duration] = try { private def parseInput(text: String): Option[Duration] = {
try {
validateIntervalUs((text.toFloat * 1000).toLong) validateIntervalUs((text.toFloat * 1000).toLong)
} catch { } catch {
case _: NumberFormatException => None case _: NumberFormatException => None
} }
}
override def onInput(text: String): Unit = { override def onInput(text: String): Unit = {
tickInterval = parseInput(text).map { interval => tickInterval = parseInput(text).map { interval =>
@ -53,11 +56,13 @@ class ChangeSimulationSpeedDialog extends ModalDialog {
} }
inputTPS = new TextInput(formatTPS(OcelotDesktop.ticker.tickInterval)) { inputTPS = new TextInput(formatTPS(OcelotDesktop.ticker.tickInterval)) {
private def parseInput(text: String): Option[Duration] = try { private def parseInput(text: String): Option[Duration] = {
try {
validateIntervalUs((1_000_000 / text.toFloat).toLong) validateIntervalUs((1_000_000 / text.toFloat).toLong)
} catch { } catch {
case _: NumberFormatException => None case _: NumberFormatException => None
} }
}
override def onInput(text: String): Unit = { override def onInput(text: String): Unit = {
tickInterval = parseInput(text).map { interval => tickInterval = parseInput(text).map { interval =>
@ -79,11 +84,14 @@ class ChangeSimulationSpeedDialog extends ModalDialog {
children :+= new Widget { children :+= new Widget {
children :+= new Filler children :+= new Filler
children :+= new PaddingBox(new Button { children :+= new PaddingBox(
new Button {
override def text: String = "Cancel" override def text: String = "Cancel"
override protected def clickSoundSource: ClickSoundSource = SoundSource.InterfaceClickLow override protected def clickSoundSource: ClickSoundSource = SoundSource.InterfaceClickLow
override def onClick(): Unit = close() override def onClick(): Unit = close()
}, Padding2D(right = 8)) },
Padding2D(right = 8),
)
children :+= new Button { children :+= new Button {
override def text: String = "Apply" override def text: String = "Apply"
@ -91,18 +99,22 @@ class ChangeSimulationSpeedDialog extends ModalDialog {
override def enabled: Boolean = tickInterval.nonEmpty override def enabled: Boolean = tickInterval.nonEmpty
} }
} }
}, Padding2D.equal(16)) },
Padding2D.equal(16),
)
} }
object ChangeSimulationSpeedDialog { object ChangeSimulationSpeedDialog {
private val MaxUpdateInterval = 1.minute private val MaxUpdateInterval = 1.minute
private val MinUpdateInterval = 1.micro private val MinUpdateInterval = 1.micro
private def validateIntervalUs(us: Long): Option[Duration] = try { private def validateIntervalUs(us: Long): Option[Duration] = {
try {
val interval = us.micros val interval = us.micros
Option.when(interval >= MinUpdateInterval && interval <= MaxUpdateInterval)(interval) Option.when(interval >= MinUpdateInterval && interval <= MaxUpdateInterval)(interval)
} catch { } catch {
case _: IllegalArgumentException => None case _: IllegalArgumentException => None
} }
}
} }

View File

@ -10,9 +10,7 @@ import ocelot.desktop.ui.widget.traits.HoverAnimation
import ocelot.desktop.util.DrawUtils import ocelot.desktop.util.DrawUtils
class Checkbox(val label: String, val initialValue: Boolean = false, val isSmall: Boolean = false) class Checkbox(val label: String, val initialValue: Boolean = false, val isSmall: Boolean = false)
extends Widget extends Widget with MouseHandler with HoverAnimation {
with MouseHandler
with HoverAnimation {
override protected val hoverAnimationColorDefault: Color = ColorScheme("CheckboxBackground") override protected val hoverAnimationColorDefault: Color = ColorScheme("CheckboxBackground")
override protected val hoverAnimationColorActive: Color = ColorScheme("CheckboxBackgroundActive") override protected val hoverAnimationColorActive: Color = ColorScheme("CheckboxBackgroundActive")
@ -20,6 +18,7 @@ class Checkbox(val label: String, val initialValue: Boolean = false, val isSmall
private var _checked: Boolean = initialValue private var _checked: Boolean = initialValue
def checked: Boolean = _checked def checked: Boolean = _checked
def checked_=(value: Boolean): Unit = { def checked_=(value: Boolean): Unit = {
if (_checked != value) { if (_checked != value) {
_checked = value _checked = value

View File

@ -9,33 +9,48 @@ import ocelot.desktop.util.Orientation
class CloseConfirmationDialog extends ModalDialog { class CloseConfirmationDialog extends ModalDialog {
protected def prompt: String = "Save workspace before exiting?" protected def prompt: String = "Save workspace before exiting?"
children :+= new PaddingBox(new Widget { children :+= new PaddingBox(
new Widget {
override val layout = new LinearLayout(this, orientation = Orientation.Vertical) override val layout = new LinearLayout(this, orientation = Orientation.Vertical)
children :+= new PaddingBox(new Label { children :+= new PaddingBox(
new Label {
override def text: String = prompt override def text: String = prompt
}, Padding2D(bottom = 16)) },
Padding2D(bottom = 16),
)
children :+= new Widget { children :+= new Widget {
children :+= new PaddingBox(new Button { children :+= new PaddingBox(
new Button {
override def text: String = "Cancel" override def text: String = "Cancel"
override protected def clickSoundSource: ClickSoundSource = SoundSource.InterfaceClickLow override protected def clickSoundSource: ClickSoundSource = SoundSource.InterfaceClickLow
override def onClick(): Unit = close() override def onClick(): Unit = close()
}, Padding2D(left = 8)) },
Padding2D(left = 8),
)
children :+= new Filler children :+= new Filler
children :+= new PaddingBox(new Button { children :+= new PaddingBox(
new Button {
override def text: String = "No" override def text: String = "No"
override def onClick(): Unit = onNoSaveSelected() override def onClick(): Unit = onNoSaveSelected()
}, Padding2D(right = 8)) },
Padding2D(right = 8),
)
children :+= new PaddingBox(new Button { children :+= new PaddingBox(
new Button {
override def text: String = "Yes" override def text: String = "Yes"
override def onClick(): Unit = onSaveSelected() override def onClick(): Unit = onSaveSelected()
}, Padding2D(right = 8)) },
Padding2D(right = 8),
)
} }
}, Padding2D.equal(16)) },
Padding2D.equal(16),
)
def onSaveSelected(): Unit = {} def onSaveSelected(): Unit = {}

View File

@ -41,17 +41,21 @@ class ComponentUsageBar extends Widget with TickUpdatable {
// Vertical lines // Vertical lines
for (i <- 0 until cellCount) { for (i <- 0 until cellCount) {
g.rect( g.rect(
x + i * cellWidth, position.y, x + i * cellWidth,
cellThickness, gridHeight, position.y,
ColorScheme("HistogramGrid") cellThickness,
gridHeight,
ColorScheme("HistogramGrid"),
) )
} }
// Closing vertical line // Closing vertical line
g.rect( g.rect(
x + gridWidth, position.y, x + gridWidth,
cellThickness, gridHeight + cellThickness, position.y,
ColorScheme("HistogramGrid") cellThickness,
gridHeight + cellThickness,
ColorScheme("HistogramGrid"),
) )
val fillWidth = Math.min( val fillWidth = Math.min(
@ -59,7 +63,7 @@ class ComponentUsageBar extends Widget with TickUpdatable {
cellWidth * currentValue - cellThickness cellWidth * currentValue - cellThickness
else else
gridWidth * (currentValue.toFloat / maxValue), gridWidth * (currentValue.toFloat / maxValue),
gridWidth gridWidth,
) )
// Value fill // Value fill
@ -70,9 +74,11 @@ class ComponentUsageBar extends Widget with TickUpdatable {
} }
g.rect( g.rect(
x + cellThickness, position.y + cellThickness, x + cellThickness,
fillWidth, cellHeight - cellThickness, position.y + cellThickness,
fillColor fillWidth,
cellHeight - cellThickness,
fillColor,
) )
// Value line // Value line
@ -83,9 +89,11 @@ class ComponentUsageBar extends Widget with TickUpdatable {
} }
g.rect( g.rect(
x + fillWidth + cellThickness, position.y, x + fillWidth + cellThickness,
cellThickness, cellHeight + cellThickness, position.y,
edgeColor cellThickness,
cellHeight + cellThickness,
edgeColor,
) )
} }
} }

Some files were not shown because too many files have changed in this diff Show More