Merge branch 'develop'
@ -16,8 +16,8 @@ variables:
|
||||
PACKAGE_NAME: "ocelot-desktop-${CI_COMMIT_TAG}.jar"
|
||||
|
||||
stages:
|
||||
- test
|
||||
- build
|
||||
- test
|
||||
- upload
|
||||
- deploy
|
||||
- release
|
||||
@ -29,12 +29,14 @@ test:
|
||||
script:
|
||||
- sbt test
|
||||
|
||||
scalafmt:
|
||||
stage: test
|
||||
before_script:
|
||||
- sbt -v sbtVersion
|
||||
script:
|
||||
- sbt scalafmtCheckAll
|
||||
# Rest in piece.
|
||||
#scalafmt:
|
||||
# stage: test
|
||||
# allow_failure: true
|
||||
# before_script:
|
||||
# - sbt -v sbtVersion
|
||||
# script:
|
||||
# - sbt scalafmtCheckAll
|
||||
|
||||
build:
|
||||
stage: build
|
||||
|
||||
@ -1,24 +1,15 @@
|
||||
version = 3.8.6
|
||||
version = 3.9.9
|
||||
runner.dialect = scala213
|
||||
preset = default
|
||||
maxColumn = 120
|
||||
indent.defnSite = 2
|
||||
|
||||
align = {
|
||||
preset = none
|
||||
openParenDefnSite = true
|
||||
openParenDefnSite = false
|
||||
}
|
||||
|
||||
newlines = {
|
||||
source = keep
|
||||
topLevelStatementBlankLines = [
|
||||
{ blanks { before = 1, after = 1, beforeAll = -1, afterAll = -1 } }
|
||||
]
|
||||
}
|
||||
|
||||
binPack = {
|
||||
preset = Oneline
|
||||
literalsExclude = []
|
||||
}
|
||||
|
||||
rewrite = {
|
||||
@ -30,7 +21,15 @@ rewrite = {
|
||||
}
|
||||
|
||||
docstrings = {
|
||||
oneline = fold
|
||||
style = SpaceAsterisk
|
||||
blankFirstLine = unfold
|
||||
oneline = unfold
|
||||
wrap = keep
|
||||
forceBlankLineBefore = false
|
||||
}
|
||||
|
||||
indent {
|
||||
defnSite = 2
|
||||
extendSite = 2
|
||||
withSiteRelativeToExtends = 2
|
||||
}
|
||||
|
||||
12
build.sbt
@ -1,5 +1,5 @@
|
||||
name := "ocelot-desktop"
|
||||
version := "1.13.1"
|
||||
version := "1.14.0"
|
||||
scalaVersion := "2.13.10"
|
||||
|
||||
lazy val root = project.in(file("."))
|
||||
@ -21,9 +21,9 @@ libraryDependencies += "org.scala-lang" % "scala-reflect" % scalaVersion.value
|
||||
libraryDependencies += "org.scalatest" %% "scalatest" % "3.2.19" % "test"
|
||||
libraryDependencies += "org.scalatest" %% "scalatest-funsuite" % "3.2.19" % "test"
|
||||
|
||||
libraryDependencies += "org.apache.logging.log4j" % "log4j-core" % "2.20.0"
|
||||
libraryDependencies += "org.apache.logging.log4j" % "log4j-api" % "2.20.0"
|
||||
libraryDependencies += "org.apache.logging.log4j" % "log4j-slf4j-impl" % "2.20.0"
|
||||
libraryDependencies += "org.apache.logging.log4j" % "log4j-core" % "2.25.1"
|
||||
libraryDependencies += "org.apache.logging.log4j" % "log4j-api" % "2.25.1"
|
||||
libraryDependencies += "org.apache.logging.log4j" % "log4j-slf4j-impl" % "2.25.1"
|
||||
|
||||
val lwjglVersion = "2.9.3"
|
||||
|
||||
@ -32,7 +32,9 @@ libraryDependencies += "org.lwjgl.lwjgl" % "lwjgl-platform" % lwjglVersion class
|
||||
libraryDependencies += "org.lwjgl.lwjgl" % "lwjgl-platform" % lwjglVersion classifier "natives-windows"
|
||||
libraryDependencies += "org.lwjgl.lwjgl" % "lwjgl-platform" % lwjglVersion classifier "natives-osx"
|
||||
|
||||
libraryDependencies += "com.github.stephengold" % "j-ogg-all" % "1.0.3"
|
||||
Compile / unmanagedResourceDirectories += baseDirectory.value / "lib" / "native"
|
||||
|
||||
libraryDependencies += "com.github.stephengold" % "j-ogg-all" % "1.0.6"
|
||||
libraryDependencies += "com.github.wendykierp" % "JTransforms" % "3.1"
|
||||
libraryDependencies += "com.github.sarxos" % "webcam-capture" % "0.3.12"
|
||||
|
||||
|
||||
25
doc/lwjgl-apple-silicon-compilation.md
Normal file
@ -0,0 +1,25 @@
|
||||
# How to compile LWJGL2 for Apple Silicon
|
||||
LWJGL2 does not provide official native ARM support, therefore Ocelot uses a specially modified version by **shadowfacts**
|
||||
|
||||
Article: https://shadowfacts.net/2022/lwjgl-arm64/
|
||||
|
||||
Repository: https://github.com/shadowfacts/lwjgl2-arm64
|
||||
|
||||
This procedure is completely optional, as the precompiled library is already checked into the repository at `lib/native/liblwjgl-arm64.dylib`
|
||||
|
||||
## Compilation
|
||||
This assumes that you are running macOS on **Apple Silicon** *(cross-compiling LWJGL seems to be impossible)*
|
||||
|
||||
1. Acquire a JDK8 built for ARM - for example, [Zulu 8 JDK](https://www.azul.com/downloads/?version=java-8-lts&os=macos&architecture=arm-64-bit&package=jdk#zulu), and add it to your `JAVA_HOME`
|
||||
2. Get [`maven`](https://maven.apache.org/) and [`ant`](https://ant.apache.org/) - you may install them via `brew`, but be careful, as they will try to install a JDK as a dependency
|
||||
3. Clone the LWJGL repository:
|
||||
```bash
|
||||
% git clone https://github.com/shadowfacts/lwjgl-arm64.git
|
||||
```
|
||||
4. Run the following commands in the repo:
|
||||
```bash
|
||||
% ant generate-all
|
||||
% ant jars
|
||||
% ant compile-native
|
||||
```
|
||||
5. Copy the compiled library from `libs/macosx/liblwjgl.dylib` into the `ocelot-desktop` project as `lib/native/liblwjgl-arm64.dylib`
|
||||
BIN
lib/native/liblwjgl-arm64.dylib
Normal file
@ -1 +1 @@
|
||||
Subproject commit 4b2d2fcec19f8e238dd1c4bdb09d42b11e9bb6e6
|
||||
Subproject commit da5dd8877035f4994b77047658561d316319b54b
|
||||
0
spritepack/spritepack.sh
Normal file → Executable file
BIN
sprites/icons/SettingsKeymap.png
Normal file
|
After Width: | Height: | Size: 196 B |
BIN
sprites/icons/SideAny.png
Normal file
|
After Width: | Height: | Size: 161 B |
BIN
sprites/icons/SideDown.png
Normal file
|
After Width: | Height: | Size: 158 B |
BIN
sprites/icons/SideEast.png
Normal file
|
After Width: | Height: | Size: 161 B |
BIN
sprites/icons/SideNone.png
Normal file
|
After Width: | Height: | Size: 178 B |
BIN
sprites/icons/SideNorth.png
Normal file
|
After Width: | Height: | Size: 154 B |
BIN
sprites/icons/SideSouth.png
Normal file
|
After Width: | Height: | Size: 150 B |
BIN
sprites/icons/SideUndefined.png
Normal file
|
After Width: | Height: | Size: 160 B |
BIN
sprites/icons/SideUp.png
Normal file
|
After Width: | Height: | Size: 154 B |
BIN
sprites/icons/SideWest.png
Normal file
|
After Width: | Height: | Size: 160 B |
BIN
sprites/particles/Smoke.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 120 B |
|
Before Width: | Height: | Size: 123 B |
|
Before Width: | Height: | Size: 167 B |
|
Before Width: | Height: | Size: 546 B |
|
Before Width: | Height: | Size: 157 B |
|
Before Width: | Height: | Size: 171 B |
BIN
sprites/screen/InnerBorderB.png
Normal file
|
After Width: | Height: | Size: 183 B |
BIN
sprites/screen/InnerBorderT.png
Normal file
|
After Width: | Height: | Size: 183 B |
BIN
sprites/screen/InnerCornerBL.png
Normal file
|
After Width: | Height: | Size: 201 B |
BIN
sprites/screen/InnerCornerBR.png
Normal file
|
After Width: | Height: | Size: 188 B |
BIN
sprites/screen/InnerCornerTL.png
Normal file
|
After Width: | Height: | Size: 188 B |
BIN
sprites/screen/InnerCornerTR.png
Normal file
|
After Width: | Height: | Size: 203 B |
BIN
sprites/screen/OuterBorderT.png
Normal file
|
After Width: | Height: | Size: 190 B |
BIN
sprites/screen/OuterCornerBL.png
Normal file
|
After Width: | Height: | Size: 219 B |
BIN
sprites/screen/OuterCornerBR.png
Normal file
|
After Width: | Height: | Size: 229 B |
BIN
sprites/screen/OuterCornerTL.png
Normal file
|
After Width: | Height: | Size: 210 B |
BIN
sprites/screen/OuterCornerTR.png
Normal file
|
After Width: | Height: | Size: 228 B |
@ -11,7 +11,7 @@ AboutButtonBackgroundActive = #888888
|
||||
PortDown = #8382d8
|
||||
PortUp = #75bdc1
|
||||
PortNorth = #c8ca5f
|
||||
PortSouth = #990da3
|
||||
PortSouth = #ed8ef4
|
||||
PortEast = #7ec95f
|
||||
PortWest = #db7d75
|
||||
PortAny = #9b9b9b
|
||||
@ -23,6 +23,7 @@ Tier3 = #c354cd
|
||||
|
||||
Label = #333333
|
||||
LabelError = #aa0000
|
||||
LabelDisabled = #888888
|
||||
|
||||
Scrollbar = #e5e5e526
|
||||
ScrollbarThumb = #cc3f72
|
||||
@ -46,6 +47,8 @@ ComputerAddress = #333333
|
||||
|
||||
ScreenOff = #000000
|
||||
|
||||
WindowBackground = #c6c6c6
|
||||
|
||||
StatusBar = #181818
|
||||
StatusBarActive = #ffffff04
|
||||
StatusBarBorder = #222222
|
||||
@ -190,3 +193,6 @@ Flash = #ffffff
|
||||
RelayTextLow = #009900
|
||||
RelayTextMid = #999900
|
||||
RelayTextHigh = #990000
|
||||
|
||||
BoomCardGlowStart = #ff4010
|
||||
BoomCardGlowEnd = #ff1010
|
||||
|
||||
|
Before Width: | Height: | Size: 147 KiB After Width: | Height: | Size: 149 KiB |
@ -1,22 +1,22 @@
|
||||
BackgroundPattern 0 0 304 304
|
||||
BarSegment 385 434 16 4
|
||||
Empty 134 618 16 16
|
||||
EmptySlot 237 567 18 18
|
||||
Empty 134 632 16 16
|
||||
EmptySlot 246 567 18 18
|
||||
Knob 203 434 50 50
|
||||
KnobCenter 254 434 50 50
|
||||
KnobLimits 305 434 50 50
|
||||
Loading 0 305 48 448
|
||||
Logo 305 0 168 200
|
||||
ShadowBorder 201 540 1 24
|
||||
ShadowBorder 279 305 1 24
|
||||
ShadowCorner 233 674 24 24
|
||||
TabArrow 225 600 8 14
|
||||
blocks/Generic 151 618 16 16
|
||||
blocks/HologramEffect 209 549 4 4
|
||||
blocks/HologramProjector1Top 168 618 16 16
|
||||
blocks/HologramProjector2Top 185 618 16 16
|
||||
blocks/HologramProjectorSide 202 618 16 16
|
||||
buttons/BottomDrawerClose 256 567 18 18
|
||||
buttons/BottomDrawerOpen 275 567 18 18
|
||||
TabArrow 569 567 8 14
|
||||
blocks/Generic 151 632 16 16
|
||||
blocks/HologramEffect 291 305 4 4
|
||||
blocks/HologramProjector1Top 168 632 16 16
|
||||
blocks/HologramProjector2Top 185 632 16 16
|
||||
blocks/HologramProjectorSide 202 632 16 16
|
||||
buttons/BottomDrawerClose 265 567 18 18
|
||||
buttons/BottomDrawerOpen 284 567 18 18
|
||||
buttons/OpenFMRadioCloseOff 404 445 7 8
|
||||
buttons/OpenFMRadioCloseOn 412 445 7 8
|
||||
buttons/OpenFMRadioRedstoneOff 359 445 8 8
|
||||
@ -25,303 +25,319 @@ buttons/OpenFMRadioStartOff 258 674 24 24
|
||||
buttons/OpenFMRadioStartOn 283 674 24 24
|
||||
buttons/OpenFMRadioStopOff 308 674 24 24
|
||||
buttons/OpenFMRadioStopOn 333 674 24 24
|
||||
buttons/OpenFMRadioVolumeDownOff 234 600 10 10
|
||||
buttons/OpenFMRadioVolumeDownOn 245 600 10 10
|
||||
buttons/OpenFMRadioVolumeUpOff 256 600 10 10
|
||||
buttons/OpenFMRadioVolumeUpOn 267 600 10 10
|
||||
buttons/PowerOff 294 567 18 18
|
||||
buttons/PowerOn 313 567 18 18
|
||||
buttons/OpenFMRadioVolumeDownOff 578 567 10 10
|
||||
buttons/OpenFMRadioVolumeDownOn 589 567 10 10
|
||||
buttons/OpenFMRadioVolumeUpOff 600 567 10 10
|
||||
buttons/OpenFMRadioVolumeUpOn 611 567 10 10
|
||||
buttons/PowerOff 303 567 18 18
|
||||
buttons/PowerOn 322 567 18 18
|
||||
buttons/RackRelayOff 134 655 65 18
|
||||
buttons/RackRelayOn 200 655 65 18
|
||||
icons/Antenna 219 618 16 16
|
||||
icons/ArrowRight 236 618 16 16
|
||||
icons/AspectRatio 253 618 16 16
|
||||
icons/Book 270 618 16 16
|
||||
icons/ButtonCheck 134 600 17 17
|
||||
icons/ButtonClipboard 152 600 17 17
|
||||
icons/ButtonRandomize 170 600 17 17
|
||||
icons/CPU 287 618 16 16
|
||||
icons/Card 304 618 16 16
|
||||
icons/Close 209 600 15 14
|
||||
icons/Code 321 618 16 16
|
||||
icons/ComponentBus 338 618 16 16
|
||||
icons/Copy 355 618 16 16
|
||||
icons/Cross 372 618 16 16
|
||||
icons/Delete 389 618 16 16
|
||||
icons/DragLMB 500 567 21 14
|
||||
icons/DragRMB 522 567 21 14
|
||||
icons/EEPROM 406 618 16 16
|
||||
icons/Edit 423 618 16 16
|
||||
icons/Eject 440 618 16 16
|
||||
icons/File 457 618 16 16
|
||||
icons/Floppy 474 618 16 16
|
||||
icons/Folder 491 618 16 16
|
||||
icons/FolderSlash 508 618 16 16
|
||||
icons/Grid 168 567 22 22
|
||||
icons/GridOff 191 567 22 22
|
||||
icons/Guitar 525 618 16 16
|
||||
icons/HDD 542 618 16 16
|
||||
icons/Help 559 618 16 16
|
||||
icons/Home 214 567 22 22
|
||||
icons/Keyboard 576 618 16 16
|
||||
icons/KeyboardOff 593 618 16 16
|
||||
icons/LMB 350 655 11 14
|
||||
icons/Label 610 618 16 16
|
||||
icons/LinesHorizontal 627 618 16 16
|
||||
icons/Link 644 618 16 16
|
||||
icons/LinkSlash 661 618 16 16
|
||||
icons/Memory 678 618 16 16
|
||||
icons/Microchip 695 618 16 16
|
||||
icons/NA 712 618 16 16
|
||||
icons/NotificationError 374 655 11 11
|
||||
icons/NotificationInfo 386 655 11 11
|
||||
icons/NotificationWarning 398 655 11 11
|
||||
icons/Ocelot 729 618 16 16
|
||||
icons/Pin 305 655 14 14
|
||||
icons/Plus 746 618 16 16
|
||||
icons/Power 763 618 16 16
|
||||
icons/RMB 362 655 11 14
|
||||
icons/Restart 780 618 16 16
|
||||
icons/Save 797 618 16 16
|
||||
icons/SaveAs 814 618 16 16
|
||||
icons/Server 831 618 16 16
|
||||
icons/SettingsSound 266 655 12 17
|
||||
icons/SettingsSystem 279 655 12 17
|
||||
icons/SettingsUI 292 655 12 17
|
||||
icons/Tier0 848 618 16 16
|
||||
icons/Tier1 865 618 16 16
|
||||
icons/Tier2 882 618 16 16
|
||||
icons/Tiers 899 618 16 16
|
||||
icons/Unpin 320 655 14 14
|
||||
icons/WaveLFSR 901 707 24 10
|
||||
icons/WaveNoise 926 707 24 10
|
||||
icons/WaveSawtooth 951 707 24 10
|
||||
icons/WaveSine 976 707 24 10
|
||||
icons/WaveSquare 134 724 24 10
|
||||
icons/WaveTriangle 159 724 24 10
|
||||
icons/Window 916 618 16 16
|
||||
icons/WireArrowLeft 203 540 4 8
|
||||
icons/WireArrowRight 208 540 4 8
|
||||
icons/Antenna 219 632 16 16
|
||||
icons/ArrowRight 236 632 16 16
|
||||
icons/AspectRatio 253 632 16 16
|
||||
icons/Book 270 632 16 16
|
||||
icons/ButtonCheck 143 600 17 17
|
||||
icons/ButtonClipboard 161 600 17 17
|
||||
icons/ButtonRandomize 179 600 17 17
|
||||
icons/CPU 287 632 16 16
|
||||
icons/Card 304 632 16 16
|
||||
icons/Close 553 567 15 14
|
||||
icons/Code 321 632 16 16
|
||||
icons/ComponentBus 338 632 16 16
|
||||
icons/Copy 355 632 16 16
|
||||
icons/Cross 372 632 16 16
|
||||
icons/Delete 389 632 16 16
|
||||
icons/DragLMB 509 567 21 14
|
||||
icons/DragRMB 531 567 21 14
|
||||
icons/EEPROM 406 632 16 16
|
||||
icons/Edit 423 632 16 16
|
||||
icons/Eject 440 632 16 16
|
||||
icons/File 457 632 16 16
|
||||
icons/Floppy 474 632 16 16
|
||||
icons/Folder 491 632 16 16
|
||||
icons/FolderSlash 508 632 16 16
|
||||
icons/Grid 177 567 22 22
|
||||
icons/GridOff 200 567 22 22
|
||||
icons/Guitar 525 632 16 16
|
||||
icons/HDD 542 632 16 16
|
||||
icons/Help 559 632 16 16
|
||||
icons/Home 223 567 22 22
|
||||
icons/Keyboard 576 632 16 16
|
||||
icons/KeyboardOff 593 632 16 16
|
||||
icons/LMB 298 540 11 14
|
||||
icons/Label 610 632 16 16
|
||||
icons/LinesHorizontal 627 632 16 16
|
||||
icons/Link 644 632 16 16
|
||||
icons/LinkSlash 661 632 16 16
|
||||
icons/Memory 678 632 16 16
|
||||
icons/Microchip 695 632 16 16
|
||||
icons/NA 712 632 16 16
|
||||
icons/NotificationError 322 540 11 11
|
||||
icons/NotificationInfo 334 540 11 11
|
||||
icons/NotificationWarning 346 540 11 11
|
||||
icons/Ocelot 729 632 16 16
|
||||
icons/Pin 253 540 14 14
|
||||
icons/Plus 746 632 16 16
|
||||
icons/Power 763 632 16 16
|
||||
icons/RMB 310 540 11 14
|
||||
icons/Restart 780 632 16 16
|
||||
icons/Save 797 632 16 16
|
||||
icons/SaveAs 814 632 16 16
|
||||
icons/Server 831 632 16 16
|
||||
icons/SettingsKeymap 201 540 12 17
|
||||
icons/SettingsSound 214 540 12 17
|
||||
icons/SettingsSystem 227 540 12 17
|
||||
icons/SettingsUI 240 540 12 17
|
||||
icons/SideAny 358 540 11 11
|
||||
icons/SideDown 370 540 11 11
|
||||
icons/SideEast 382 540 11 11
|
||||
icons/SideNone 394 540 11 11
|
||||
icons/SideNorth 406 540 11 11
|
||||
icons/SideSouth 418 540 11 11
|
||||
icons/SideUndefined 430 540 11 11
|
||||
icons/SideUp 442 540 11 11
|
||||
icons/SideWest 454 540 11 11
|
||||
icons/Tier0 848 632 16 16
|
||||
icons/Tier1 865 632 16 16
|
||||
icons/Tier2 882 632 16 16
|
||||
icons/Tiers 899 632 16 16
|
||||
icons/Unpin 268 540 14 14
|
||||
icons/WaveLFSR 237 600 24 10
|
||||
icons/WaveNoise 262 600 24 10
|
||||
icons/WaveSawtooth 287 600 24 10
|
||||
icons/WaveSine 312 600 24 10
|
||||
icons/WaveSquare 337 600 24 10
|
||||
icons/WaveTriangle 362 600 24 10
|
||||
icons/Window 916 632 16 16
|
||||
icons/WireArrowLeft 281 305 4 8
|
||||
icons/WireArrowRight 286 305 4 8
|
||||
items/APU0 49 655 16 96
|
||||
items/APU1 66 655 16 96
|
||||
items/APU2 83 655 16 96
|
||||
items/CPU0 933 618 16 16
|
||||
items/CPU1 950 618 16 16
|
||||
items/CPU2 967 618 16 16
|
||||
items/CardBase 984 618 16 16
|
||||
items/CircuitBoard 1001 618 16 16
|
||||
items/ComponentBus0 134 635 16 16
|
||||
items/ComponentBus1 151 635 16 16
|
||||
items/ComponentBus2 168 635 16 16
|
||||
items/ComponentBus3 185 635 16 16
|
||||
items/CPU0 933 632 16 16
|
||||
items/CPU1 950 632 16 16
|
||||
items/CPU2 967 632 16 16
|
||||
items/CardBase 984 632 16 16
|
||||
items/CircuitBoard 1001 632 16 16
|
||||
items/ComponentBus0 358 674 16 16
|
||||
items/ComponentBus1 375 674 16 16
|
||||
items/ComponentBus2 392 674 16 16
|
||||
items/ComponentBus3 409 674 16 16
|
||||
items/DataCard0 49 526 16 128
|
||||
items/DataCard1 66 526 16 128
|
||||
items/DataCard2 83 526 16 128
|
||||
items/DebugCard 202 635 16 16
|
||||
items/DiskDriveMountable 219 635 16 16
|
||||
items/EEPROM 236 635 16 16
|
||||
items/FloppyDisk_dyeBlack 253 635 16 16
|
||||
items/FloppyDisk_dyeBlue 270 635 16 16
|
||||
items/FloppyDisk_dyeBrown 287 635 16 16
|
||||
items/FloppyDisk_dyeCyan 304 635 16 16
|
||||
items/FloppyDisk_dyeGray 321 635 16 16
|
||||
items/FloppyDisk_dyeGreen 338 635 16 16
|
||||
items/FloppyDisk_dyeLightBlue 355 635 16 16
|
||||
items/FloppyDisk_dyeLightGray 372 635 16 16
|
||||
items/FloppyDisk_dyeLime 389 635 16 16
|
||||
items/FloppyDisk_dyeMagenta 406 635 16 16
|
||||
items/FloppyDisk_dyeOrange 423 635 16 16
|
||||
items/FloppyDisk_dyePink 440 635 16 16
|
||||
items/FloppyDisk_dyePurple 457 635 16 16
|
||||
items/FloppyDisk_dyeRed 474 635 16 16
|
||||
items/FloppyDisk_dyeWhite 491 635 16 16
|
||||
items/FloppyDisk_dyeYellow 508 635 16 16
|
||||
items/GraphicsCard0 525 635 16 16
|
||||
items/GraphicsCard1 542 635 16 16
|
||||
items/GraphicsCard2 559 635 16 16
|
||||
items/HardDiskDrive0 576 635 16 16
|
||||
items/HardDiskDrive1 593 635 16 16
|
||||
items/HardDiskDrive2 610 635 16 16
|
||||
items/InternetCard 134 567 16 32
|
||||
items/DebugCard 426 674 16 16
|
||||
items/DiskDriveMountable 443 674 16 16
|
||||
items/EEPROM 460 674 16 16
|
||||
items/FloppyDisk_dyeBlack 477 674 16 16
|
||||
items/FloppyDisk_dyeBlue 494 674 16 16
|
||||
items/FloppyDisk_dyeBrown 511 674 16 16
|
||||
items/FloppyDisk_dyeCyan 528 674 16 16
|
||||
items/FloppyDisk_dyeGray 545 674 16 16
|
||||
items/FloppyDisk_dyeGreen 562 674 16 16
|
||||
items/FloppyDisk_dyeLightBlue 579 674 16 16
|
||||
items/FloppyDisk_dyeLightGray 596 674 16 16
|
||||
items/FloppyDisk_dyeLime 613 674 16 16
|
||||
items/FloppyDisk_dyeMagenta 630 674 16 16
|
||||
items/FloppyDisk_dyeOrange 647 674 16 16
|
||||
items/FloppyDisk_dyePink 664 674 16 16
|
||||
items/FloppyDisk_dyePurple 681 674 16 16
|
||||
items/FloppyDisk_dyeRed 698 674 16 16
|
||||
items/FloppyDisk_dyeWhite 715 674 16 16
|
||||
items/FloppyDisk_dyeYellow 732 674 16 16
|
||||
items/GraphicsCard0 749 674 16 16
|
||||
items/GraphicsCard1 766 674 16 16
|
||||
items/GraphicsCard2 783 674 16 16
|
||||
items/HardDiskDrive0 800 674 16 16
|
||||
items/HardDiskDrive1 817 674 16 16
|
||||
items/HardDiskDrive2 834 674 16 16
|
||||
items/InternetCard 143 567 16 32
|
||||
items/LinkedCard 100 655 16 96
|
||||
items/Memory0 627 635 16 16
|
||||
items/Memory1 644 635 16 16
|
||||
items/Memory2 661 635 16 16
|
||||
items/Memory3 678 635 16 16
|
||||
items/Memory4 695 635 16 16
|
||||
items/Memory5 712 635 16 16
|
||||
items/Memory6 729 635 16 16
|
||||
items/NetworkCard 746 635 16 16
|
||||
items/Memory0 851 674 16 16
|
||||
items/Memory1 868 674 16 16
|
||||
items/Memory2 885 674 16 16
|
||||
items/Memory3 902 674 16 16
|
||||
items/Memory4 919 674 16 16
|
||||
items/Memory5 936 674 16 16
|
||||
items/Memory6 953 674 16 16
|
||||
items/NetworkCard 970 674 16 16
|
||||
items/OcelotCard 100 526 16 128
|
||||
items/RedstoneCard0 763 635 16 16
|
||||
items/RedstoneCard1 780 635 16 16
|
||||
items/SelfDestructingCard 151 567 16 32
|
||||
items/Server0 797 635 16 16
|
||||
items/Server1 814 635 16 16
|
||||
items/Server2 831 635 16 16
|
||||
items/Server3 848 635 16 16
|
||||
items/RedstoneCard0 987 674 16 16
|
||||
items/RedstoneCard1 1004 674 16 16
|
||||
items/SelfDestructingCard 160 567 16 32
|
||||
items/Server0 134 707 16 16
|
||||
items/Server1 151 707 16 16
|
||||
items/Server2 168 707 16 16
|
||||
items/Server3 185 707 16 16
|
||||
items/SoundCard 117 526 16 128
|
||||
items/TapeCopper 865 635 16 16
|
||||
items/TapeDiamond 882 635 16 16
|
||||
items/TapeGold 899 635 16 16
|
||||
items/TapeGreg 916 635 16 16
|
||||
items/TapeIg 933 635 16 16
|
||||
items/TapeIron 950 635 16 16
|
||||
items/TapeNetherStar 967 635 16 16
|
||||
items/TapeSteel 984 635 16 16
|
||||
items/WirelessNetworkCard0 1001 635 16 16
|
||||
items/WirelessNetworkCard1 358 674 16 16
|
||||
light-panel/BookmarkLeft 882 707 18 14
|
||||
light-panel/BookmarkRight 188 600 20 14
|
||||
light-panel/BorderB 214 549 4 4
|
||||
light-panel/BorderL 304 549 4 2
|
||||
light-panel/BorderR 219 549 4 4
|
||||
light-panel/BorderT 224 549 4 4
|
||||
light-panel/CornerBL 229 549 4 4
|
||||
light-panel/CornerBR 234 549 4 4
|
||||
light-panel/CornerTL 239 549 4 4
|
||||
light-panel/CornerTR 244 549 4 4
|
||||
light-panel/Fill 207 560 2 2
|
||||
items/TapeCopper 202 707 16 16
|
||||
items/TapeDiamond 219 707 16 16
|
||||
items/TapeGold 236 707 16 16
|
||||
items/TapeGreg 253 707 16 16
|
||||
items/TapeIg 270 707 16 16
|
||||
items/TapeIron 287 707 16 16
|
||||
items/TapeNetherStar 304 707 16 16
|
||||
items/TapeSteel 321 707 16 16
|
||||
items/WirelessNetworkCard0 338 707 16 16
|
||||
items/WirelessNetworkCard1 355 707 16 16
|
||||
light-panel/BookmarkLeft 218 600 18 14
|
||||
light-panel/BookmarkRight 197 600 20 14
|
||||
light-panel/BorderB 296 305 4 4
|
||||
light-panel/BorderL 284 314 4 2
|
||||
light-panel/BorderR 301 305 4 4
|
||||
light-panel/BorderT 306 305 4 4
|
||||
light-panel/CornerBL 311 305 4 4
|
||||
light-panel/CornerBR 316 305 4 4
|
||||
light-panel/CornerTL 321 305 4 4
|
||||
light-panel/CornerTR 326 305 4 4
|
||||
light-panel/Fill 410 305 2 2
|
||||
light-panel/Vent 356 434 2 38
|
||||
nodes/Cable 377 445 8 8
|
||||
nodes/Camera 375 674 16 16
|
||||
nodes/Chest 335 655 14 14
|
||||
nodes/HologramProjector0 392 674 16 16
|
||||
nodes/HologramProjector1 409 674 16 16
|
||||
nodes/IronNoteBlock 426 674 16 16
|
||||
nodes/Lamp 443 674 16 16
|
||||
nodes/LampFrame 460 674 16 16
|
||||
nodes/Camera 372 707 16 16
|
||||
nodes/Chest 283 540 14 14
|
||||
nodes/HologramProjector0 389 707 16 16
|
||||
nodes/HologramProjector1 406 707 16 16
|
||||
nodes/IronNoteBlock 423 707 16 16
|
||||
nodes/Lamp 440 707 16 16
|
||||
nodes/LampFrame 457 707 16 16
|
||||
nodes/LampGlow 49 305 128 128
|
||||
nodes/NewNode 477 674 16 16
|
||||
nodes/NoteBlock 494 674 16 16
|
||||
nodes/OpenFMRadio 511 674 16 16
|
||||
nodes/Relay 528 674 16 16
|
||||
nodes/TapeDrive 545 674 16 16
|
||||
nodes/computer/Default 562 674 16 16
|
||||
nodes/computer/DiskActivity 579 674 16 16
|
||||
nodes/computer/Error 596 674 16 16
|
||||
nodes/computer/On 613 674 16 16
|
||||
nodes/disk-drive/Default 630 674 16 16
|
||||
nodes/disk-drive/DiskActivity 647 674 16 16
|
||||
nodes/disk-drive/Floppy 664 674 16 16
|
||||
nodes/NewNode 474 707 16 16
|
||||
nodes/NoteBlock 491 707 16 16
|
||||
nodes/OpenFMRadio 508 707 16 16
|
||||
nodes/Relay 525 707 16 16
|
||||
nodes/TapeDrive 542 707 16 16
|
||||
nodes/computer/Default 559 707 16 16
|
||||
nodes/computer/DiskActivity 576 707 16 16
|
||||
nodes/computer/Error 593 707 16 16
|
||||
nodes/computer/On 610 707 16 16
|
||||
nodes/disk-drive/Default 627 707 16 16
|
||||
nodes/disk-drive/DiskActivity 644 707 16 16
|
||||
nodes/disk-drive/Floppy 661 707 16 16
|
||||
nodes/holidays/Christmas 134 674 32 32
|
||||
nodes/holidays/Halloween 167 674 32 32
|
||||
nodes/holidays/Valentines 200 674 32 32
|
||||
nodes/microcontroller/Default 681 674 16 16
|
||||
nodes/microcontroller/Error 698 674 16 16
|
||||
nodes/microcontroller/On 715 674 16 16
|
||||
nodes/microcontroller/Default 678 707 16 16
|
||||
nodes/microcontroller/Error 695 707 16 16
|
||||
nodes/microcontroller/On 712 707 16 16
|
||||
nodes/ocelot-block/Default 117 655 16 80
|
||||
nodes/ocelot-block/Rx 732 674 16 16
|
||||
nodes/ocelot-block/Tx 749 674 16 16
|
||||
nodes/rack/Default 766 674 16 16
|
||||
nodes/rack/Empty 783 674 16 16
|
||||
nodes/rack/drive/0/Default 800 674 16 16
|
||||
nodes/rack/drive/0/DiskActivity 817 674 16 16
|
||||
nodes/rack/drive/0/Floppy 834 674 16 16
|
||||
nodes/rack/drive/1/Default 851 674 16 16
|
||||
nodes/rack/drive/1/DiskActivity 868 674 16 16
|
||||
nodes/rack/drive/1/Floppy 885 674 16 16
|
||||
nodes/rack/drive/2/Default 902 674 16 16
|
||||
nodes/rack/drive/2/DiskActivity 919 674 16 16
|
||||
nodes/rack/drive/2/Floppy 936 674 16 16
|
||||
nodes/rack/drive/3/Default 953 674 16 16
|
||||
nodes/rack/drive/3/DiskActivity 970 674 16 16
|
||||
nodes/rack/drive/3/Floppy 987 674 16 16
|
||||
nodes/rack/drive/Floppy 1004 674 16 16
|
||||
nodes/rack/server/0/Default 134 707 16 16
|
||||
nodes/rack/server/0/DiskActivity 151 707 16 16
|
||||
nodes/rack/server/0/Error 168 707 16 16
|
||||
nodes/rack/server/0/NetworkActivity 185 707 16 16
|
||||
nodes/rack/server/0/On 202 707 16 16
|
||||
nodes/rack/server/1/Default 219 707 16 16
|
||||
nodes/rack/server/1/DiskActivity 236 707 16 16
|
||||
nodes/rack/server/1/Error 253 707 16 16
|
||||
nodes/rack/server/1/NetworkActivity 270 707 16 16
|
||||
nodes/rack/server/1/On 287 707 16 16
|
||||
nodes/rack/server/2/Default 304 707 16 16
|
||||
nodes/rack/server/2/DiskActivity 321 707 16 16
|
||||
nodes/rack/server/2/Error 338 707 16 16
|
||||
nodes/rack/server/2/NetworkActivity 355 707 16 16
|
||||
nodes/rack/server/2/On 372 707 16 16
|
||||
nodes/rack/server/3/Default 389 707 16 16
|
||||
nodes/rack/server/3/DiskActivity 406 707 16 16
|
||||
nodes/rack/server/3/Error 423 707 16 16
|
||||
nodes/rack/server/3/NetworkActivity 440 707 16 16
|
||||
nodes/rack/server/3/On 457 707 16 16
|
||||
nodes/raid/0/DiskActivity 474 707 16 16
|
||||
nodes/raid/0/Error 491 707 16 16
|
||||
nodes/raid/1/DiskActivity 508 707 16 16
|
||||
nodes/raid/1/Error 525 707 16 16
|
||||
nodes/raid/2/DiskActivity 542 707 16 16
|
||||
nodes/raid/2/Error 559 707 16 16
|
||||
nodes/raid/Default 576 707 16 16
|
||||
nodes/screen/BottomLeft 593 707 16 16
|
||||
nodes/screen/BottomMiddle 610 707 16 16
|
||||
nodes/screen/BottomRight 627 707 16 16
|
||||
nodes/screen/ColumnBottom 644 707 16 16
|
||||
nodes/screen/ColumnMiddle 661 707 16 16
|
||||
nodes/screen/ColumnTop 678 707 16 16
|
||||
nodes/screen/Middle 695 707 16 16
|
||||
nodes/screen/MiddleLeft 712 707 16 16
|
||||
nodes/screen/MiddleRight 729 707 16 16
|
||||
nodes/screen/PowerOnOverlay 746 707 16 16
|
||||
nodes/screen/RowLeft 763 707 16 16
|
||||
nodes/screen/RowMiddle 780 707 16 16
|
||||
nodes/screen/RowRight 797 707 16 16
|
||||
nodes/screen/Standalone 814 707 16 16
|
||||
nodes/screen/TopLeft 831 707 16 16
|
||||
nodes/screen/TopMiddle 848 707 16 16
|
||||
nodes/screen/TopRight 865 707 16 16
|
||||
panel/BorderB 249 549 4 4
|
||||
panel/BorderL 309 549 4 2
|
||||
panel/BorderR 254 549 4 4
|
||||
panel/BorderT 259 549 4 4
|
||||
panel/CornerBL 264 549 4 4
|
||||
panel/CornerBR 269 549 4 4
|
||||
panel/CornerTL 274 549 4 4
|
||||
panel/CornerTR 279 549 4 4
|
||||
panel/Fill 210 560 2 2
|
||||
nodes/ocelot-block/Rx 729 707 16 16
|
||||
nodes/ocelot-block/Tx 746 707 16 16
|
||||
nodes/rack/Default 763 707 16 16
|
||||
nodes/rack/Empty 780 707 16 16
|
||||
nodes/rack/drive/0/Default 797 707 16 16
|
||||
nodes/rack/drive/0/DiskActivity 814 707 16 16
|
||||
nodes/rack/drive/0/Floppy 831 707 16 16
|
||||
nodes/rack/drive/1/Default 848 707 16 16
|
||||
nodes/rack/drive/1/DiskActivity 865 707 16 16
|
||||
nodes/rack/drive/1/Floppy 882 707 16 16
|
||||
nodes/rack/drive/2/Default 899 707 16 16
|
||||
nodes/rack/drive/2/DiskActivity 916 707 16 16
|
||||
nodes/rack/drive/2/Floppy 933 707 16 16
|
||||
nodes/rack/drive/3/Default 950 707 16 16
|
||||
nodes/rack/drive/3/DiskActivity 967 707 16 16
|
||||
nodes/rack/drive/3/Floppy 984 707 16 16
|
||||
nodes/rack/drive/Floppy 1001 707 16 16
|
||||
nodes/rack/server/0/Default 266 655 16 16
|
||||
nodes/rack/server/0/DiskActivity 283 655 16 16
|
||||
nodes/rack/server/0/Error 300 655 16 16
|
||||
nodes/rack/server/0/NetworkActivity 317 655 16 16
|
||||
nodes/rack/server/0/On 334 655 16 16
|
||||
nodes/rack/server/1/Default 351 655 16 16
|
||||
nodes/rack/server/1/DiskActivity 368 655 16 16
|
||||
nodes/rack/server/1/Error 385 655 16 16
|
||||
nodes/rack/server/1/NetworkActivity 402 655 16 16
|
||||
nodes/rack/server/1/On 419 655 16 16
|
||||
nodes/rack/server/2/Default 436 655 16 16
|
||||
nodes/rack/server/2/DiskActivity 453 655 16 16
|
||||
nodes/rack/server/2/Error 470 655 16 16
|
||||
nodes/rack/server/2/NetworkActivity 487 655 16 16
|
||||
nodes/rack/server/2/On 504 655 16 16
|
||||
nodes/rack/server/3/Default 521 655 16 16
|
||||
nodes/rack/server/3/DiskActivity 538 655 16 16
|
||||
nodes/rack/server/3/Error 555 655 16 16
|
||||
nodes/rack/server/3/NetworkActivity 572 655 16 16
|
||||
nodes/rack/server/3/On 589 655 16 16
|
||||
nodes/raid/0/DiskActivity 606 655 16 16
|
||||
nodes/raid/0/Error 623 655 16 16
|
||||
nodes/raid/1/DiskActivity 640 655 16 16
|
||||
nodes/raid/1/Error 657 655 16 16
|
||||
nodes/raid/2/DiskActivity 674 655 16 16
|
||||
nodes/raid/2/Error 691 655 16 16
|
||||
nodes/raid/Default 708 655 16 16
|
||||
nodes/screen/BottomLeft 725 655 16 16
|
||||
nodes/screen/BottomMiddle 742 655 16 16
|
||||
nodes/screen/BottomRight 759 655 16 16
|
||||
nodes/screen/ColumnBottom 776 655 16 16
|
||||
nodes/screen/ColumnMiddle 793 655 16 16
|
||||
nodes/screen/ColumnTop 810 655 16 16
|
||||
nodes/screen/Middle 827 655 16 16
|
||||
nodes/screen/MiddleLeft 844 655 16 16
|
||||
nodes/screen/MiddleRight 861 655 16 16
|
||||
nodes/screen/PowerOnOverlay 878 655 16 16
|
||||
nodes/screen/RowLeft 895 655 16 16
|
||||
nodes/screen/RowMiddle 912 655 16 16
|
||||
nodes/screen/RowRight 929 655 16 16
|
||||
nodes/screen/Standalone 946 655 16 16
|
||||
nodes/screen/TopLeft 963 655 16 16
|
||||
nodes/screen/TopMiddle 980 655 16 16
|
||||
nodes/screen/TopRight 997 655 16 16
|
||||
panel/BorderB 331 305 4 4
|
||||
panel/BorderL 289 314 4 2
|
||||
panel/BorderR 336 305 4 4
|
||||
panel/BorderT 341 305 4 4
|
||||
panel/CornerBL 346 305 4 4
|
||||
panel/CornerBR 351 305 4 4
|
||||
panel/CornerTL 356 305 4 4
|
||||
panel/CornerTR 361 305 4 4
|
||||
panel/Fill 413 305 2 2
|
||||
particles/Note 377 434 7 10
|
||||
screen/BorderB 206 549 2 8
|
||||
screen/BorderT 203 549 2 10
|
||||
screen/CornerBL 386 445 8 8
|
||||
screen/CornerBR 395 445 8 8
|
||||
screen/CornerTL 359 434 8 10
|
||||
screen/CornerTR 368 434 8 10
|
||||
window/BorderDark 203 560 1 4
|
||||
window/BorderLight 205 560 1 4
|
||||
window/CornerBL 284 549 4 4
|
||||
window/CornerBR 289 549 4 4
|
||||
window/CornerTL 294 549 4 4
|
||||
window/CornerTR 299 549 4 4
|
||||
particles/Smoke 134 567 8 64
|
||||
screen/InnerBorderB 281 321 2 4
|
||||
screen/InnerBorderT 284 321 2 4
|
||||
screen/InnerCornerBL 366 305 4 4
|
||||
screen/InnerCornerBR 371 305 4 4
|
||||
screen/InnerCornerTL 376 305 4 4
|
||||
screen/InnerCornerTR 381 305 4 4
|
||||
screen/OuterBorderT 281 314 2 6
|
||||
screen/OuterCornerBL 386 445 8 8
|
||||
screen/OuterCornerBR 395 445 8 8
|
||||
screen/OuterCornerTL 359 434 8 10
|
||||
screen/OuterCornerTR 368 434 8 10
|
||||
window/BorderDark 406 305 1 4
|
||||
window/BorderLight 408 305 1 4
|
||||
window/CornerBL 386 305 4 4
|
||||
window/CornerBR 391 305 4 4
|
||||
window/CornerTL 396 305 4 4
|
||||
window/CornerTR 401 305 4 4
|
||||
window/OpenFMRadio 474 0 232 105
|
||||
window/case/Motherboard 123 434 79 70
|
||||
window/rack/Lines 49 434 73 91
|
||||
window/rack/Motherboard 178 305 100 78
|
||||
window/rack/NetworkBack 221 554 1 2
|
||||
window/rack/NetworkBottom 223 554 1 2
|
||||
window/rack/NetworkConnector 225 554 1 2
|
||||
window/rack/NetworkLeft 227 554 1 2
|
||||
window/rack/NetworkRight 229 554 1 2
|
||||
window/rack/NetworkTop 231 554 1 2
|
||||
window/rack/NodeBack 314 549 5 1
|
||||
window/rack/NodeBottom 320 549 5 1
|
||||
window/rack/NodeLeft 326 549 5 1
|
||||
window/rack/NodeRight 332 549 5 1
|
||||
window/rack/NodeTop 338 549 5 1
|
||||
window/rack/SideBack 209 554 1 3
|
||||
window/rack/SideBottom 211 554 1 3
|
||||
window/rack/SideConnector 213 554 1 3
|
||||
window/rack/SideLeft 215 554 1 3
|
||||
window/rack/SideRight 217 554 1 3
|
||||
window/rack/SideTop 219 554 1 3
|
||||
window/rack/NetworkBack 293 326 1 2
|
||||
window/rack/NetworkBottom 295 326 1 2
|
||||
window/rack/NetworkConnector 297 326 1 2
|
||||
window/rack/NetworkLeft 299 326 1 2
|
||||
window/rack/NetworkRight 301 326 1 2
|
||||
window/rack/NetworkTop 303 326 1 2
|
||||
window/rack/NodeBack 287 321 5 1
|
||||
window/rack/NodeBottom 293 321 5 1
|
||||
window/rack/NodeLeft 299 321 5 1
|
||||
window/rack/NodeRight 305 321 5 1
|
||||
window/rack/NodeTop 311 321 5 1
|
||||
window/rack/SideBack 281 326 1 3
|
||||
window/rack/SideBottom 283 326 1 3
|
||||
window/rack/SideConnector 285 326 1 3
|
||||
window/rack/SideLeft 287 326 1 3
|
||||
window/rack/SideRight 289 326 1 3
|
||||
window/rack/SideTop 291 326 1 3
|
||||
window/raid/Slots 134 540 66 26
|
||||
window/tape/Back 332 567 20 15
|
||||
window/tape/BackPressed 353 567 20 15
|
||||
window/tape/Forward 374 567 20 15
|
||||
window/tape/ForwardPressed 395 567 20 15
|
||||
window/tape/Play 416 567 20 15
|
||||
window/tape/PlayPressed 437 567 20 15
|
||||
window/tape/Back 341 567 20 15
|
||||
window/tape/BackPressed 362 567 20 15
|
||||
window/tape/Forward 383 567 20 15
|
||||
window/tape/ForwardPressed 404 567 20 15
|
||||
window/tape/Play 425 567 20 15
|
||||
window/tape/PlayPressed 446 567 20 15
|
||||
window/tape/Screen 134 526 146 13
|
||||
window/tape/Stop 458 567 20 15
|
||||
window/tape/StopPressed 479 567 20 15
|
||||
window/tape/Stop 467 567 20 15
|
||||
window/tape/StopPressed 488 567 20 15
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
#version 140
|
||||
#version 150
|
||||
|
||||
in vec3 inPos;
|
||||
in vec3 inNormal;
|
||||
|
||||
@ -383,6 +383,7 @@ object OcelotDesktop extends LoggingConfiguration with Logging {
|
||||
loadWorld(nbt)
|
||||
|
||||
resetAutosave()
|
||||
logger.info(s"Workspace successfully loaded from: $path")
|
||||
}
|
||||
}
|
||||
} else Failure(new FileNotFoundException("Specified directory does not contain 'workspace.nbt'"))
|
||||
|
||||
@ -5,11 +5,9 @@ import ocelot.desktop.Settings.ExtendedConfig
|
||||
import ocelot.desktop.util.{Logging, SettingsData}
|
||||
import org.apache.commons.lang3.SystemUtils
|
||||
|
||||
import java.io.InputStream
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.nio.file.{Files, Path}
|
||||
import java.util
|
||||
import scala.io.{Codec, Source}
|
||||
|
||||
class Settings(val config: Config) extends SettingsData {
|
||||
// TODO: refactor this mess (having to declare every field 3 times is extremely error-prone)
|
||||
@ -44,9 +42,12 @@ class Settings(val config: Config) extends SettingsData {
|
||||
windowSize.y -= 16
|
||||
}
|
||||
|
||||
keymap.load(config.getConfig("ocelot.keymap"))
|
||||
|
||||
recentWorkspace = config.getOptionalString("ocelot.workspace.recent")
|
||||
pinNewWindows = config.getBooleanOrElse("ocelot.workspace.pinNewWindows", default = true)
|
||||
unfocusedWindowTransparency = config.getDoubleOrElse("ocelot.workspace.unfocusedWindowTransparency", 0.5)
|
||||
unfocusedWindowTransparency = config.getDoubleOrElse("ocelot.workspace.unfocusedWindowTransparency", 0.5).toFloat
|
||||
unfocusedWindowHide = config.getBooleanOrElse("ocelot.workspace.unfocusedWindowHide", default = false)
|
||||
saveOnExit = config.getBooleanOrElse("ocelot.workspace.saveOnExit", default = true)
|
||||
autosave = config.getBooleanOrElse("ocelot.workspace.autosave", default = true)
|
||||
autosavePeriod = config.getIntOrElse("ocelot.workspace.autosavePeriod", default = 300)
|
||||
@ -105,6 +106,9 @@ object Settings extends Logging {
|
||||
|
||||
def withValue(path: String, value: Option[Any]): Config =
|
||||
config.withValue(path, ConfigValueFactory.fromAnyRef(value.orNull))
|
||||
|
||||
def withValue(path: String, value: Int): Config =
|
||||
config.withValue(path, ConfigValueFactory.fromAnyRef(value))
|
||||
}
|
||||
|
||||
class Int2D(var x: Int, var y: Int) {
|
||||
@ -132,39 +136,18 @@ object Settings extends Logging {
|
||||
def get: Settings = settings
|
||||
|
||||
def load(path: Path): Unit = {
|
||||
import java.lang.System.{lineSeparator => EOL}
|
||||
|
||||
if (Files.exists(path)) {
|
||||
var stream: InputStream = null
|
||||
|
||||
try {
|
||||
stream = Files.newInputStream(path)
|
||||
val source = Source.fromInputStream(stream)(Codec.UTF8)
|
||||
val plain = source.getLines().mkString("", EOL, EOL)
|
||||
val config = ConfigFactory.parseString(plain)
|
||||
settings = new Settings(config)
|
||||
source.close()
|
||||
|
||||
settings = new Settings(ConfigFactory.parseFile(path.toFile))
|
||||
logger.info(s"Loaded Ocelot Desktop configuration from: $path")
|
||||
|
||||
return
|
||||
} catch {
|
||||
case _: Throwable =>
|
||||
logger.info(s"Failed to parse $path, using default Ocelot Desktop configuration.")
|
||||
} finally {
|
||||
if (stream != null)
|
||||
stream.close()
|
||||
case t: Throwable => logger.error(s"Failed to parse $path!", t)
|
||||
}
|
||||
}
|
||||
|
||||
val defaults = {
|
||||
val in = getClass.getResourceAsStream("/ocelot/desktop/ocelot.conf")
|
||||
val config = Source.fromInputStream(in)(Codec.UTF8).getLines().mkString("", EOL, EOL)
|
||||
in.close()
|
||||
ConfigFactory.parseString(config)
|
||||
}
|
||||
|
||||
settings = new Settings(defaults)
|
||||
logger.info(s"Using default Ocelot Desktop configuration...")
|
||||
settings = new Settings(ConfigFactory.parseResources("/ocelot/desktop/ocelot.conf"))
|
||||
}
|
||||
|
||||
def save(path: Path): Unit = {
|
||||
@ -186,9 +169,11 @@ object Settings extends Logging {
|
||||
.withValuePreserveOrigin("ocelot.window.fullscreen", settings.windowFullscreen)
|
||||
.withValuePreserveOrigin("ocelot.window.disableVsync", settings.disableVsync)
|
||||
.withValuePreserveOrigin("ocelot.window.debugLwjgl", settings.debugLwjgl)
|
||||
.withValue("ocelot.keymap", settings.keymap.save())
|
||||
.withValue("ocelot.workspace.recent", settings.recentWorkspace)
|
||||
.withValuePreserveOrigin("ocelot.workspace.pinNewWindows", settings.pinNewWindows)
|
||||
.withValuePreserveOrigin("ocelot.workspace.unfocusedWindowTransparency", settings.unfocusedWindowTransparency)
|
||||
.withValuePreserveOrigin("ocelot.workspace.unfocusedWindowHide", settings.unfocusedWindowHide)
|
||||
.withValuePreserveOrigin("ocelot.workspace.saveOnExit", settings.saveOnExit)
|
||||
.withValuePreserveOrigin("ocelot.workspace.autosave", settings.autosave)
|
||||
.withValuePreserveOrigin("ocelot.workspace.autosavePeriod", settings.autosavePeriod)
|
||||
|
||||
@ -21,7 +21,7 @@ object AL10W extends Logging {
|
||||
val exc = OpenAlException(func, errName, err)
|
||||
|
||||
if (Settings.get.logAudioErrorStacktrace) {
|
||||
logger.error(exc)
|
||||
logger.error(exc.getMessage, exc)
|
||||
} else {
|
||||
logger.error(exc.getMessage)
|
||||
}
|
||||
|
||||
@ -246,4 +246,12 @@ object Audio extends Logging {
|
||||
AL10W.alDeleteBuffers(buf.get(i))
|
||||
}
|
||||
}
|
||||
|
||||
def removeAllSources(): Unit = synchronized {
|
||||
for (sourceId <- sources.values) {
|
||||
deleteSource(sourceId)
|
||||
}
|
||||
|
||||
sources.clear()
|
||||
}
|
||||
}
|
||||
|
||||
@ -44,6 +44,8 @@ object SoundBuffers extends Resource {
|
||||
lazy val MinecraftClickRelease: SoundBuffer = load("/ocelot/desktop/sounds/minecraft/click_release.ogg")
|
||||
lazy val MinecraftExplosion: SoundBuffer = load("/ocelot/desktop/sounds/minecraft/explosion.ogg")
|
||||
|
||||
lazy val SelfDestructingCardCountdownBeep: SoundBuffer = load("/ocelot/desktop/sounds/minecraft/countdown_beep.ogg")
|
||||
|
||||
lazy val NoteBlock: Map[String, SoundBuffer] = List(
|
||||
"banjo", "basedrum", "bass", "bell", "bit", "chime", "cow_bell", "didgeridoo", "flute", "guitar",
|
||||
"harp", "hat", "iron_xylophone", "pling", "snare", "xylophone",
|
||||
|
||||
@ -34,15 +34,15 @@ class SoundSource(
|
||||
Audio.getSourceStatus(this)
|
||||
}
|
||||
|
||||
def isPlaying: Boolean = {
|
||||
def playing: Boolean = {
|
||||
status == SoundSource.Status.Playing
|
||||
}
|
||||
|
||||
def isPaused: Boolean = {
|
||||
def paused: Boolean = {
|
||||
status == SoundSource.Status.Paused
|
||||
}
|
||||
|
||||
def isStopped: Boolean = {
|
||||
def stopped: Boolean = {
|
||||
status == SoundSource.Status.Stopped
|
||||
}
|
||||
|
||||
@ -124,9 +124,6 @@ object SoundSource {
|
||||
SoundSource.fromBuffer(SoundBuffers.MinecraftClickRelease, SoundCategory.Interface)
|
||||
}
|
||||
|
||||
lazy val MinecraftExplosion: SoundSource =
|
||||
SoundSource.fromBuffer(SoundBuffers.MinecraftExplosion, SoundCategory.Environment)
|
||||
|
||||
lazy val MachineFloppyInsert: SoundSource =
|
||||
SoundSource.fromBuffer(SoundBuffers.MachineFloppyInsert, SoundCategory.Environment)
|
||||
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
package ocelot.desktop.color
|
||||
|
||||
import ocelot.desktop.geometry.FloatUtils.ExtendedFloat
|
||||
import ocelot.desktop.geometry.Vector3D
|
||||
|
||||
import java.nio.ByteBuffer
|
||||
@ -60,6 +61,15 @@ case class RGBAColorNorm(r: Float, g: Float, b: Float, a: Float = 1f) extends Co
|
||||
HSVAColor(hue, saturation, value, a)
|
||||
}
|
||||
|
||||
def lerp(dst: RGBAColorNorm, t: Float): RGBAColorNorm = {
|
||||
RGBAColorNorm(
|
||||
r.lerp(dst.r, t),
|
||||
g.lerp(dst.g, t),
|
||||
b.lerp(dst.b, t),
|
||||
a.lerp(dst.a, t),
|
||||
)
|
||||
}
|
||||
|
||||
def withAlpha(alpha: Float): RGBAColorNorm = RGBAColorNorm(r, g, b, alpha)
|
||||
|
||||
// ʕ•ᴥ•ʔ
|
||||
|
||||
@ -152,7 +152,7 @@ class OpenFMRadio extends Entity with Environment with DeviceInfo with Logging {
|
||||
}
|
||||
|
||||
def isPlaying: Boolean =
|
||||
playbackSoundSource.isDefined && playbackSoundSource.get.isPlaying || playbackThread.isDefined
|
||||
playbackSoundSource.isDefined && playbackSoundSource.get.playing || playbackThread.isDefined
|
||||
|
||||
@Callback()
|
||||
def start(context: Context, args: Arguments): Array[AnyRef] =
|
||||
|
||||
@ -4,6 +4,6 @@ object FloatUtils {
|
||||
implicit class ExtendedFloat(val v: Float) extends AnyVal {
|
||||
def lerp(that: Float, alpha: Float): Float = v * (1 - alpha) + that * alpha
|
||||
|
||||
def clamp(min: Float = 0f, max: Float = 1f): Float = v.min(max).max(min)
|
||||
def clamped(min: Float = 0f, max: Float = 1f): Float = v.min(max).max(min)
|
||||
}
|
||||
}
|
||||
|
||||
@ -76,7 +76,9 @@ case class Rect2D(x: Float, y: Float, w: Float, h: Float) {
|
||||
this
|
||||
}
|
||||
|
||||
def inflate(addition: Float): Rect2D = Rect2D(x - addition, y - addition, w + addition * 2, h + addition * 2)
|
||||
def inflated(dx: Float, dy: Float): Rect2D = Rect2D(x - dx, y - dy, w + dx * 2, h + dy * 2)
|
||||
|
||||
def inflated(delta: Float): Rect2D = inflated(delta, delta)
|
||||
|
||||
def center: Vector2D = {
|
||||
min + (size * 0.5f).toVector
|
||||
|
||||
@ -24,7 +24,7 @@ object Transform2D {
|
||||
Transform2D.translate(-1f, 1f) >> Transform2D.scale(2f / width, -2f / height)
|
||||
|
||||
def rotate(angle: Float): Transform2D = {
|
||||
val (s, c) = (math.sin(angle).asInstanceOf[Float], math.cos(angle).asInstanceOf[Float])
|
||||
val (s, c) = (math.sin(angle).toFloat, math.cos(angle).toFloat)
|
||||
|
||||
// format: off
|
||||
Transform2D(
|
||||
|
||||
@ -36,9 +36,6 @@ case class Vector2D(x: Float, y: Float) extends Persistable {
|
||||
|
||||
def *(scalar: Float): Vector2D = Vector2D(x * scalar, y * scalar)
|
||||
|
||||
// TODO: remove
|
||||
def *(scalar: Double): Vector2D = Vector2D(x * scalar, y * scalar)
|
||||
|
||||
def /(scalar: Float): Vector2D = Vector2D(x / scalar, y / scalar)
|
||||
|
||||
def snap(v: Float): Vector2D = Vector2D((x / v).floor * v, (y / v).floor * v)
|
||||
|
||||
@ -45,7 +45,7 @@ case class Vector3D(x: Float, y: Float, z: Float) {
|
||||
)
|
||||
|
||||
def angle(that: Vector3D): Float = {
|
||||
math.acos((dot(that) / length / that.length).clamp(-1, 1)).toFloat
|
||||
math.acos((dot(that) / length / that.length).clamped(-1, 1)).toFloat
|
||||
}
|
||||
|
||||
def lerp(that: Vector3D, alpha: Float): Vector3D = {
|
||||
|
||||
@ -2,7 +2,6 @@ package ocelot.desktop.graphics
|
||||
|
||||
import ocelot.desktop.color.{Color, RGBAColorNorm}
|
||||
import ocelot.desktop.geometry.{Rect2D, Size2D, Transform2D, Vector2D}
|
||||
import ocelot.desktop.graphics.IconSource.Animation
|
||||
import ocelot.desktop.graphics.Texture.MinFilteringMode
|
||||
import ocelot.desktop.graphics.mesh.{Mesh2D, MeshInstance2D, MeshVertex2D}
|
||||
import ocelot.desktop.graphics.render.InstanceRenderer
|
||||
@ -280,94 +279,50 @@ class Graphics(private var width: Int, private var height: Int, private var scal
|
||||
|
||||
// I hate scala. Overloaded methods with default arguments are not allowed
|
||||
def sprite(icon: IconSource, bounds: Rect2D): Unit = {
|
||||
sprite(icon.path, bounds.x, bounds.y, bounds.w, bounds.h, Color.White, icon.animation)
|
||||
sprite(icon, bounds.x, bounds.y, bounds.w, bounds.h, Color.White)
|
||||
}
|
||||
|
||||
def sprite(icon: IconSource, bounds: Rect2D, color: Color): Unit = {
|
||||
sprite(icon, bounds.x, bounds.y, bounds.w, bounds.h, color)
|
||||
}
|
||||
|
||||
def sprite(icon: IconSource, pos: Vector2D, size: Size2D): Unit = {
|
||||
sprite(icon.path, pos.x, pos.y, size.width, size.height, Color.White, icon.animation)
|
||||
sprite(icon, pos.x, pos.y, size.width, size.height, Color.White)
|
||||
}
|
||||
|
||||
def sprite(icon: IconSource, pos: Vector2D, color: Color): Unit = {
|
||||
sprite(icon, pos.x, pos.y, color)
|
||||
}
|
||||
|
||||
def sprite(icon: IconSource, pos: Vector2D, size: Size2D, color: Color): Unit = {
|
||||
sprite(icon.path, pos.x, pos.y, size.width, size.height, color, icon.animation)
|
||||
sprite(icon, pos.x, pos.y, size.width, size.height, color)
|
||||
}
|
||||
|
||||
def sprite(icon: IconSource, x: Float, y: Float): Unit = {
|
||||
sprite(icon.path, x, y, icon.animation)
|
||||
sprite(icon, x, y, Color.White)
|
||||
}
|
||||
|
||||
def sprite(icon: IconSource, x: Float, y: Float, width: Float, height: Float): Unit = {
|
||||
sprite(icon.path, x, y, width, height, animation = icon.animation)
|
||||
sprite(icon, x, y, width, height, Color.White)
|
||||
}
|
||||
|
||||
def sprite(icon: IconSource, x: Float, y: Float, width: Float, height: Float, color: Color): Unit = {
|
||||
sprite(icon.path, x, y, width, height, color, icon.animation)
|
||||
def sprite(icon: IconSource, x: Float, y: Float, color: Color): Unit = {
|
||||
val size = Spritesheet.spriteSize(icon.path)
|
||||
sprite(icon, x, y, size.width, size.height, color)
|
||||
}
|
||||
|
||||
def sprite(name: String, bounds: Rect2D): Unit = {
|
||||
sprite(name, bounds.origin, bounds.size, Color.White)
|
||||
}
|
||||
|
||||
def sprite(name: String, x: Float, y: Float, color: Color): Unit = {
|
||||
sprite(name, Vector2D(x, y), Spritesheet.spriteSize(name), color)
|
||||
}
|
||||
|
||||
def sprite(name: String, pos: Vector2D, color: Color): Unit = {
|
||||
sprite(name, pos, Spritesheet.spriteSize(name), color)
|
||||
}
|
||||
|
||||
def sprite(name: String, pos: Vector2D, size: Size2D, color: Color): Unit = {
|
||||
sprite(name, pos.x, pos.y, size.width, size.height, color)
|
||||
}
|
||||
|
||||
def sprite(name: String, pos: Vector2D, size: Size2D): Unit = {
|
||||
sprite(name, pos.x, pos.y, size.width, size.height)
|
||||
}
|
||||
|
||||
def sprite(name: String, x: Float, y: Float): Unit = {
|
||||
sprite(name, x, y, Color.White, None)
|
||||
}
|
||||
|
||||
def sprite(name: String, x: Float, y: Float, animation: Option[Animation]): Unit = {
|
||||
sprite(name, x, y, Color.White, animation)
|
||||
}
|
||||
|
||||
def sprite(name: String, x: Float, y: Float, color: Color, animation: Option[Animation]): Unit = {
|
||||
val size = Spritesheet.spriteSize(name)
|
||||
sprite(name, x, y, size.width, size.height, color, animation)
|
||||
}
|
||||
|
||||
def sprite(name: String, x: Float, y: Float, width: Float, height: Float,
|
||||
color: Color = Color.White,
|
||||
animation: Option[Animation] = None): Unit = {
|
||||
sprite = name
|
||||
def sprite(
|
||||
icon: IconSource,
|
||||
x: Float,
|
||||
y: Float,
|
||||
width: Float,
|
||||
height: Float,
|
||||
color: Color,
|
||||
): Unit = {
|
||||
sprite = icon.path
|
||||
foreground = color
|
||||
_rect(x, y, width, height, fixUV = true, animation)
|
||||
}
|
||||
|
||||
def rect(r: Rect2D, color: Color): Unit = {
|
||||
rect(r.x, r.y, r.w, r.h, color)
|
||||
}
|
||||
|
||||
def rect(x: Float, y: Float, width: Float, height: Float, color: Color = RGBAColorNorm(1f, 1f, 1f)): Unit = {
|
||||
sprite("Empty", x, y, width, height, color)
|
||||
}
|
||||
|
||||
private def checkFont(): Unit = {
|
||||
if (_font != oldFont) {
|
||||
val newFont = _font
|
||||
_font = oldFont
|
||||
flush()
|
||||
_font = newFont
|
||||
oldFont = _font
|
||||
}
|
||||
}
|
||||
|
||||
private def _rect(x: Float, y: Float, width: Float, height: Float,
|
||||
fixUV: Boolean = true,
|
||||
animation: Option[Animation] = None): Unit = {
|
||||
val spriteRect = animation match {
|
||||
case None => this.spriteRect
|
||||
case Some(animation) =>
|
||||
val spriteRect = icon.animation.map { animation =>
|
||||
val duration = animation.frames.map(_._2).sum
|
||||
var timeOffset = 0f
|
||||
var curFrame = 0
|
||||
@ -384,9 +339,52 @@ class Graphics(private var width: Int, private var height: Int, private var scal
|
||||
case Some(size) => Size2D(this.spriteRect.w, this.spriteRect.w * size.height / size.width)
|
||||
case None => Size2D(this.spriteRect.w, this.spriteRect.w)
|
||||
}
|
||||
|
||||
this.spriteRect.copy(y = this.spriteRect.y + curFrame * size.height, h = size.height)
|
||||
}
|
||||
|
||||
_rect(x, y, width, height, fixUV = true, spriteRect)
|
||||
}
|
||||
|
||||
def sprite(
|
||||
name: String,
|
||||
x: Float,
|
||||
y: Float,
|
||||
width: Float,
|
||||
height: Float,
|
||||
color: Color,
|
||||
spriteRect: Option[Rect2D],
|
||||
fixUV: Boolean = true,
|
||||
): Unit = {
|
||||
sprite = name
|
||||
foreground = color
|
||||
|
||||
_rect(x, y, width, height, fixUV, spriteRect)
|
||||
}
|
||||
|
||||
def rect(r: Rect2D, color: Color): Unit = {
|
||||
rect(r.x, r.y, r.w, r.h, color)
|
||||
}
|
||||
|
||||
def rect(x: Float, y: Float, width: Float, height: Float, color: Color = RGBAColorNorm(1f, 1f, 1f)): Unit = {
|
||||
sprite(IconSource.Empty, x, y, width, height, color)
|
||||
}
|
||||
|
||||
private def checkFont(): Unit = {
|
||||
if (_font != oldFont) {
|
||||
val newFont = _font
|
||||
_font = oldFont
|
||||
flush()
|
||||
_font = newFont
|
||||
oldFont = _font
|
||||
}
|
||||
}
|
||||
|
||||
private def _rect(x: Float, y: Float, width: Float, height: Float,
|
||||
fixUV: Boolean = true,
|
||||
spriteRectOptional: Option[Rect2D] = None): Unit = {
|
||||
val spriteRect = spriteRectOptional.getOrElse(this.spriteRect)
|
||||
|
||||
val uvTransform = Transform2D.translate(spriteRect.x, spriteRect.y) >>
|
||||
(if (fixUV)
|
||||
Transform2D.scale(spriteRect.w - 0.25f / 1024, spriteRect.h - 0.25f / 1024)
|
||||
|
||||
@ -3,96 +3,148 @@ package ocelot.desktop.graphics
|
||||
import ocelot.desktop.geometry.Size2D
|
||||
import ocelot.desktop.ui.widget.modal.notification.NotificationType.NotificationType
|
||||
import totoro.ocelot.brain.entity.tape.Tape.{Kind => TapeKind}
|
||||
import totoro.ocelot.brain.util.Direction.{Direction, Down, East, North, South, Up, West}
|
||||
import totoro.ocelot.brain.util.DyeColor
|
||||
import totoro.ocelot.brain.util.ExtendedTier.ExtendedTier
|
||||
import totoro.ocelot.brain.util.Tier.Tier
|
||||
|
||||
case class IconSource(
|
||||
path: String,
|
||||
animation: Option[IconSource.Animation] = None,
|
||||
)
|
||||
case class IconSource(path: String, animation: Option[IconSource.Animation] = None)
|
||||
|
||||
object IconSource {
|
||||
val CardIcon: IconSource = IconSource("icons/Card")
|
||||
val CpuIcon: IconSource = IconSource("icons/CPU")
|
||||
val HddIcon: IconSource = IconSource("icons/HDD")
|
||||
val EepromIcon: IconSource = IconSource("icons/EEPROM")
|
||||
val FloppyIcon: IconSource = IconSource("icons/Floppy")
|
||||
val MemoryIcon: IconSource = IconSource("icons/Memory")
|
||||
val ServerIcon: IconSource = IconSource("icons/Server")
|
||||
val ComponentBusIcon: IconSource = IconSource("icons/ComponentBus")
|
||||
case class Animation(frames: Array[(Int, Float)], frameSize: Option[Size2D])
|
||||
|
||||
val TierIcon: Tier => IconSource = { tier =>
|
||||
IconSource(s"icons/Tier${tier.id}")
|
||||
object Animation {
|
||||
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: Option[Size2D] = None)(frames: Array[(Int, Float)]) = new Animation(frames, size)
|
||||
}
|
||||
|
||||
object Items {
|
||||
class IconScope(directory: String)(implicit parent: Option[IconScope]) {
|
||||
// this makes nested `IconScope` declarations use `this` as their parent scope.
|
||||
implicit def scope: Option[IconScope] = Some(this)
|
||||
|
||||
private def prefix: String = parent match {
|
||||
case Some(parent) => s"${parent.prefix}/$directory"
|
||||
case None => directory
|
||||
}
|
||||
|
||||
protected def get(name: String): IconSource = {
|
||||
get(name, None)
|
||||
}
|
||||
|
||||
protected def get(name: String, animation: Animation): IconSource = {
|
||||
get(name, Some(animation))
|
||||
}
|
||||
|
||||
protected def get(name: String, animation: Option[Animation]): IconSource = {
|
||||
IconSource(s"$prefix/$name", animation)
|
||||
}
|
||||
}
|
||||
|
||||
private implicit val scope: Option[IconScope] = None
|
||||
|
||||
val Empty: IconSource = IconSource("Empty")
|
||||
val EmptySlot: IconSource = IconSource("EmptySlot")
|
||||
val ShadowCorner: IconSource = IconSource("ShadowCorner")
|
||||
val ShadowBorder: IconSource = IconSource("ShadowBorder")
|
||||
val TabArrow: IconSource = IconSource("TabArrow")
|
||||
val BarSegment: IconSource = IconSource("BarSegment")
|
||||
val BackgroundPattern: IconSource = IconSource("BackgroundPattern")
|
||||
val Logo: IconSource = IconSource("Logo")
|
||||
|
||||
val Knob: IconSource = IconSource("Knob")
|
||||
val KnobLimits: IconSource = IconSource("KnobLimits")
|
||||
val KnobCenter: IconSource = IconSource("KnobCenter")
|
||||
|
||||
val Loading: IconSource = IconSource(
|
||||
"Loading",
|
||||
Some(
|
||||
Animation(Size2D(48, 32))(
|
||||
(0, 0.7f),
|
||||
(1, 0.7f),
|
||||
(2, 0.7f),
|
||||
(3, 0.7f),
|
||||
(4, 0.7f),
|
||||
(5, 0.7f),
|
||||
(6, 0.7f),
|
||||
(7, 0.7f),
|
||||
(8, 0.7f),
|
||||
(9, 0.7f),
|
||||
(10, 0.7f),
|
||||
(11, 0.7f),
|
||||
(12, 0.7f),
|
||||
(13, 0.7f),
|
||||
)
|
||||
),
|
||||
)
|
||||
|
||||
object Items extends IconScope("items") {
|
||||
val Cpu: Tier => IconSource = { tier =>
|
||||
IconSource(s"items/CPU${tier.id}")
|
||||
get(s"CPU${tier.id}")
|
||||
}
|
||||
|
||||
val Apu: Tier => IconSource = { tier =>
|
||||
IconSource(s"items/APU${tier.id}", animation = Some(Animations.Apu))
|
||||
get(s"APU${tier.id}", Animations.Apu)
|
||||
}
|
||||
|
||||
val GraphicsCard: Tier => IconSource = { tier =>
|
||||
IconSource(s"items/GraphicsCard${tier.id}")
|
||||
get(s"GraphicsCard${tier.id}")
|
||||
}
|
||||
|
||||
val NetworkCard: IconSource = IconSource("items/NetworkCard")
|
||||
val NetworkCard: IconSource = get("NetworkCard")
|
||||
|
||||
val WirelessNetworkCard: Tier => IconSource = { tier =>
|
||||
IconSource(s"items/WirelessNetworkCard${tier.id}")
|
||||
get(s"WirelessNetworkCard${tier.id}")
|
||||
}
|
||||
|
||||
val LinkedCard: IconSource = IconSource("items/LinkedCard", animation = Some(Animations.LinkedCard))
|
||||
val LinkedCard: IconSource = get("LinkedCard", Animations.LinkedCard)
|
||||
|
||||
val InternetCard: IconSource = IconSource("items/InternetCard", animation = Some(Animations.InternetCard))
|
||||
val InternetCard: IconSource = get("InternetCard", Animations.InternetCard)
|
||||
|
||||
val RedstoneCard: Tier => IconSource = { tier =>
|
||||
IconSource(s"items/RedstoneCard${tier.id}")
|
||||
get(s"RedstoneCard${tier.id}")
|
||||
}
|
||||
|
||||
val DataCard: Tier => IconSource = { tier =>
|
||||
IconSource(s"items/DataCard${tier.id}", animation = Some(Animations.DataCard))
|
||||
get(s"DataCard${tier.id}", Animations.DataCard)
|
||||
}
|
||||
|
||||
val SoundCard: IconSource = IconSource("items/SoundCard", animation = Some(Animations.DataCard))
|
||||
val SoundCard: IconSource = get("SoundCard", Animations.DataCard)
|
||||
|
||||
val SelfDestructingCard: IconSource =
|
||||
IconSource("items/SelfDestructingCard", animation = Some(Animations.SelfDestructingCard))
|
||||
get("SelfDestructingCard", Animations.SelfDestructingCard)
|
||||
|
||||
val OcelotCard: IconSource = IconSource("items/OcelotCard", animation = Some(Animations.OcelotCard))
|
||||
val OcelotCard: IconSource = get("OcelotCard", Animations.OcelotCard)
|
||||
|
||||
val HardDiskDrive: Tier => IconSource = { tier =>
|
||||
IconSource(s"items/HardDiskDrive${tier.id}")
|
||||
get(s"HardDiskDrive${tier.id}")
|
||||
}
|
||||
|
||||
val Eeprom: IconSource = IconSource("items/EEPROM")
|
||||
val Eeprom: IconSource = get("EEPROM")
|
||||
|
||||
val FloppyDisk: DyeColor => IconSource = { color =>
|
||||
IconSource(s"items/FloppyDisk_${color.name}")
|
||||
get(s"FloppyDisk_${color.name}")
|
||||
}
|
||||
|
||||
val Memory: ExtendedTier => IconSource = { tier =>
|
||||
IconSource(s"items/Memory${tier.id}")
|
||||
get(s"Memory${tier.id}")
|
||||
}
|
||||
|
||||
val Server: Tier => IconSource = { tier =>
|
||||
IconSource(s"items/Server${tier.id}")
|
||||
get(s"Server${tier.id}")
|
||||
}
|
||||
|
||||
val ComponentBus: Tier => IconSource = { tier =>
|
||||
IconSource(s"items/ComponentBus${tier.id}")
|
||||
get(s"ComponentBus${tier.id}")
|
||||
}
|
||||
|
||||
val Tape: TapeKind => IconSource = {
|
||||
case TapeKind.Golder => Tape(TapeKind.Gold)
|
||||
case TapeKind.NetherStarrer => Tape(TapeKind.NetherStar)
|
||||
case kind => IconSource(s"items/Tape$kind")
|
||||
case kind => get(s"Tape$kind")
|
||||
}
|
||||
|
||||
val DiskDriveMountable: IconSource = IconSource("items/DiskDriveMountable")
|
||||
val DiskDriveMountable: IconSource = get("DiskDriveMountable")
|
||||
|
||||
// noinspection ScalaWeakerAccess
|
||||
object Animations {
|
||||
@ -114,223 +166,368 @@ object IconSource {
|
||||
}
|
||||
}
|
||||
|
||||
case class Animation(frames: Array[(Int, Float)], frameSize: Option[Size2D])
|
||||
object Icons extends IconScope("icons") {
|
||||
val Card: IconSource = get("Card")
|
||||
val Cpu: IconSource = get("CPU")
|
||||
val Hdd: IconSource = get("HDD")
|
||||
val Eeprom: IconSource = get("EEPROM")
|
||||
val Floppy: IconSource = get("Floppy")
|
||||
val Memory: IconSource = get("Memory")
|
||||
val Server: IconSource = get("Server")
|
||||
val ComponentBus: IconSource = get("ComponentBus")
|
||||
|
||||
object Animation {
|
||||
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: Option[Size2D] = None, frames: Array[(Int, Float)]) = new Animation(frames, size)
|
||||
val Tier: Tier => IconSource = { tier =>
|
||||
get(s"Tier${tier.id}")
|
||||
}
|
||||
|
||||
// ----------------------- Ocelot interface icons -----------------------
|
||||
val SideNone: IconSource = get("SideNone")
|
||||
val SideAny: IconSource = get("SideAny")
|
||||
val SideUndefined: IconSource = get("SideUndefined")
|
||||
val Side: Direction => IconSource = {
|
||||
case Down => get("SideDown")
|
||||
case Up => get("SideUp")
|
||||
case North => get("SideNorth")
|
||||
case South => get("SideSouth")
|
||||
case West => get("SideWest")
|
||||
case East => get("SideEast")
|
||||
case _ => SideUndefined
|
||||
}
|
||||
|
||||
val Notification: NotificationType => IconSource = { notificationType =>
|
||||
IconSource(s"icons/Notification$notificationType")
|
||||
get(s"Notification$notificationType")
|
||||
}
|
||||
|
||||
val Loading: IconSource = IconSource(
|
||||
"Loading",
|
||||
animation = Some(Animation(
|
||||
Size2D(48, 32),
|
||||
(0, 0.7f),
|
||||
(1, 0.7f),
|
||||
(2, 0.7f),
|
||||
(3, 0.7f),
|
||||
(4, 0.7f),
|
||||
(5, 0.7f),
|
||||
(6, 0.7f),
|
||||
(7, 0.7f),
|
||||
(8, 0.7f),
|
||||
(9, 0.7f),
|
||||
(10, 0.7f),
|
||||
(11, 0.7f),
|
||||
(12, 0.7f),
|
||||
(13, 0.7f),
|
||||
)),
|
||||
)
|
||||
val NA: IconSource = get("NA")
|
||||
val SettingsKeymap: IconSource = get("SettingsKeymap")
|
||||
val SettingsSystem: IconSource = get("SettingsSystem")
|
||||
val SettingsSound: IconSource = get("SettingsSound")
|
||||
val SettingsUI: IconSource = get("SettingsUI")
|
||||
val Delete: IconSource = get("Delete")
|
||||
val Label: IconSource = get("Label")
|
||||
val Copy: IconSource = get("Copy")
|
||||
val AspectRatio: IconSource = get("AspectRatio")
|
||||
val Eject: IconSource = get("Eject")
|
||||
val Restart: IconSource = get("Restart")
|
||||
val Edit: IconSource = get("Edit")
|
||||
val Folder: IconSource = get("Folder")
|
||||
val FolderSlash: IconSource = get("FolderSlash")
|
||||
val Code: IconSource = get("Code")
|
||||
val File: IconSource = get("File")
|
||||
val Link: IconSource = get("Link")
|
||||
val LinkSlash: IconSource = get("LinkSlash")
|
||||
val Power: IconSource = get("Power")
|
||||
val Save: IconSource = get("Save")
|
||||
val SaveAs: IconSource = get("SaveAs")
|
||||
val Plus: IconSource = get("Plus")
|
||||
val Cross: IconSource = get("Cross")
|
||||
val Microchip: IconSource = get("Microchip")
|
||||
val Antenna: IconSource = get("Antenna")
|
||||
val Window: IconSource = get("Window")
|
||||
val Tiers: IconSource = get("Tiers")
|
||||
val LinesHorizontal: IconSource = get("LinesHorizontal")
|
||||
val ArrowRight: IconSource = get("ArrowRight")
|
||||
val Book: IconSource = get("Book")
|
||||
val Help: IconSource = get("Help")
|
||||
val Ocelot: IconSource = get("Ocelot")
|
||||
val Guitar: IconSource = get("Guitar")
|
||||
val Keyboard: IconSource = get("Keyboard")
|
||||
val KeyboardOff: IconSource = get("KeyboardOff")
|
||||
val ButtonRandomize: IconSource = get("ButtonRandomize")
|
||||
val ButtonClipboard: IconSource = get("ButtonClipboard")
|
||||
val ButtonCheck: IconSource = get("ButtonCheck")
|
||||
val Home: IconSource = get("Home")
|
||||
val Pin: IconSource = get("Pin")
|
||||
val Unpin: IconSource = get("Unpin")
|
||||
val Close: IconSource = get("Close")
|
||||
val Grid: IconSource = get("Grid")
|
||||
val GridOff: IconSource = get("GridOff")
|
||||
val LMB: IconSource = get("LMB")
|
||||
val RMB: IconSource = get("RMB")
|
||||
val DragLMB: IconSource = get("DragLMB")
|
||||
val DragRMB: IconSource = get("DragRMB")
|
||||
|
||||
val SettingsSystem: IconSource = IconSource("icons/SettingsSystem")
|
||||
val SettingsSound: IconSource = IconSource("icons/SettingsSound")
|
||||
val SettingsUI: IconSource = IconSource("icons/SettingsUI")
|
||||
val Delete: IconSource = IconSource("icons/Delete")
|
||||
val Label: IconSource = IconSource("icons/Label")
|
||||
val Copy: IconSource = IconSource("icons/Copy")
|
||||
val AspectRatio: IconSource = IconSource("icons/AspectRatio")
|
||||
val Eject: IconSource = IconSource("icons/Eject")
|
||||
val Restart: IconSource = IconSource("icons/Restart")
|
||||
val Edit: IconSource = IconSource("icons/Edit")
|
||||
val Folder: IconSource = IconSource("icons/Folder")
|
||||
val FolderSlash: IconSource = IconSource("icons/FolderSlash")
|
||||
val Code: IconSource = IconSource("icons/Code")
|
||||
val File: IconSource = IconSource("icons/File")
|
||||
val Link: IconSource = IconSource("icons/Link")
|
||||
val LinkSlash: IconSource = IconSource("icons/LinkSlash")
|
||||
val Power: IconSource = IconSource("icons/Power")
|
||||
val Save: IconSource = IconSource("icons/Save")
|
||||
val SaveAs: IconSource = IconSource("icons/SaveAs")
|
||||
val Plus: IconSource = IconSource("icons/Plus")
|
||||
val Cross: IconSource = IconSource("icons/Cross")
|
||||
val Microchip: IconSource = IconSource("icons/Microchip")
|
||||
val Antenna: IconSource = IconSource("icons/Antenna")
|
||||
val Window: IconSource = IconSource("icons/Window")
|
||||
val Tiers: IconSource = IconSource("icons/Tiers")
|
||||
val LinesHorizontal: IconSource = IconSource("icons/LinesHorizontal")
|
||||
val ArrowRight: IconSource = IconSource("icons/ArrowRight")
|
||||
val Book: IconSource = IconSource("icons/Book")
|
||||
val Help: IconSource = IconSource("icons/Help")
|
||||
val Ocelot: IconSource = IconSource("icons/Ocelot")
|
||||
val Guitar: IconSource = IconSource("icons/Guitar")
|
||||
val Keyboard: IconSource = IconSource("icons/Keyboard")
|
||||
val KeyboardOff: IconSource = IconSource("icons/KeyboardOff")
|
||||
val WireArrowLeft: IconSource = get("WireArrowLeft")
|
||||
val WireArrowRight: IconSource = get("WireArrowRight")
|
||||
|
||||
// ----------------------- Node icons -----------------------
|
||||
val WaveSine: IconSource = get("WaveSine")
|
||||
val WaveTriangle: IconSource = get("WaveTriangle")
|
||||
val WaveSawtooth: IconSource = get("WaveSawtooth")
|
||||
val WaveSquare: IconSource = get("WaveSquare")
|
||||
val WaveNoise: IconSource = get("WaveNoise")
|
||||
val WaveLFSR: IconSource = get("WaveLFSR")
|
||||
}
|
||||
|
||||
val NA: IconSource = IconSource("icons/NA")
|
||||
object Nodes extends IconScope("nodes") {
|
||||
val NewNode: IconSource = get("NewNode")
|
||||
|
||||
object Nodes {
|
||||
val NewNode: IconSource = IconSource("nodes/NewNode")
|
||||
|
||||
val Cable: IconSource = IconSource("nodes/Cable")
|
||||
val Camera: IconSource = IconSource("nodes/Camera")
|
||||
val Chest: IconSource = IconSource("nodes/Chest")
|
||||
val Cable: IconSource = get("Cable")
|
||||
val Camera: IconSource = get("Camera")
|
||||
val Chest: IconSource = get("Chest")
|
||||
|
||||
val HologramProjector: Tier => IconSource = { tier =>
|
||||
IconSource(s"nodes/HologramProjector${tier.id}")
|
||||
get(s"HologramProjector${tier.id}")
|
||||
}
|
||||
|
||||
val IronNoteBlock: IconSource = IconSource("nodes/IronNoteBlock")
|
||||
val NoteBlock: IconSource = IconSource("nodes/NoteBlock")
|
||||
val OpenFMRadio: IconSource = IconSource("nodes/OpenFMRadio")
|
||||
val Relay: IconSource = IconSource("nodes/Relay")
|
||||
val TapeDrive: IconSource = IconSource("nodes/TapeDrive")
|
||||
val IronNoteBlock: IconSource = get("IronNoteBlock")
|
||||
val NoteBlock: IconSource = get("NoteBlock")
|
||||
val OpenFMRadio: IconSource = get("OpenFMRadio")
|
||||
val Relay: IconSource = get("Relay")
|
||||
val TapeDrive: IconSource = get("TapeDrive")
|
||||
|
||||
object Computer extends PowerIconSource with DiskActivityIconSource {
|
||||
override protected def prefix: String = "nodes/computer"
|
||||
val Lamp: IconSource = get("Lamp")
|
||||
val LampFrame: IconSource = get("LampFrame")
|
||||
val LampGlow: IconSource = get("LampGlow")
|
||||
|
||||
val Default: IconSource = IconSource(s"$prefix/Default")
|
||||
object Computer extends IconScope("computer") with PowerIconSource with DiskActivityIconSource {
|
||||
val Default: IconSource = get("Default")
|
||||
}
|
||||
|
||||
object DiskDrive extends DiskActivityIconSource with FloppyDriveIconSource {
|
||||
override protected def prefix: String = "nodes/disk-drive"
|
||||
|
||||
val Default: IconSource = IconSource(s"$prefix/Default")
|
||||
object DiskDrive extends IconScope("disk-drive") with DiskActivityIconSource with FloppyDriveIconSource {
|
||||
val Default: IconSource = get("Default")
|
||||
}
|
||||
|
||||
object Lamp extends IconSource("nodes/Lamp") {
|
||||
val Frame: IconSource = IconSource("nodes/LampFrame")
|
||||
val Glow: IconSource = IconSource("nodes/LampGlow")
|
||||
object Microcontroller extends IconScope("microcontroller") with PowerIconSource {
|
||||
val Default: IconSource = get("Default")
|
||||
}
|
||||
|
||||
object Microcontroller extends PowerIconSource {
|
||||
override protected def prefix: String = "nodes/microcontroller"
|
||||
|
||||
val Default: IconSource = IconSource(s"$prefix/Default")
|
||||
}
|
||||
|
||||
object OcelotBlock {
|
||||
val Default: IconSource = IconSource(
|
||||
"nodes/ocelot-block/Default",
|
||||
animation = Some(Animation(Size2D(16, 16), (0, 30f), (1, 5f), (2, 2f), (0, 20f), (3, 3f), (4, 2f))),
|
||||
object OcelotBlock extends IconScope("ocelot-block") {
|
||||
val Default: IconSource = get(
|
||||
"Default",
|
||||
Some(Animation(Size2D(16, 16))((0, 30f), (1, 5f), (2, 2f), (0, 20f), (3, 3f), (4, 2f))),
|
||||
)
|
||||
|
||||
val Rx: IconSource = IconSource("nodes/ocelot-block/Rx")
|
||||
val Tx: IconSource = IconSource("nodes/ocelot-block/Tx")
|
||||
val Rx: IconSource = get("Rx")
|
||||
val Tx: IconSource = get("Tx")
|
||||
}
|
||||
|
||||
object Rack {
|
||||
protected val prefix: String = "nodes/rack"
|
||||
|
||||
val Empty: IconSource = IconSource(s"$prefix/Empty")
|
||||
val Default: IconSource = IconSource(s"$prefix/Default")
|
||||
object Rack extends IconScope("rack") {
|
||||
val Empty: IconSource = get("Empty")
|
||||
val Default: IconSource = get("Default")
|
||||
|
||||
val Server: Array[Server] = Array.tabulate(4)(new Server(_))
|
||||
val Drive: Array[Drive] = Array.tabulate(4)(new Drive(_))
|
||||
|
||||
class Server(val slot: Int) extends PowerIconSource with DiskActivityIconSource with NetworkActivityIconSource {
|
||||
override protected def prefix: String = s"${Rack.prefix}/server/$slot"
|
||||
class Server(val slot: Int)
|
||||
extends IconScope(s"server/$slot")
|
||||
with PowerIconSource
|
||||
with DiskActivityIconSource
|
||||
with NetworkActivityIconSource {
|
||||
|
||||
val Default: IconSource = IconSource(s"$prefix/Default")
|
||||
val Default: IconSource = get("Default")
|
||||
}
|
||||
|
||||
class Drive(val slot: Int) extends DiskActivityIconSource with FloppyDriveIconSource {
|
||||
override protected def prefix: String = s"${Rack.prefix}/drive/$slot"
|
||||
class Drive(val slot: Int)
|
||||
extends IconScope(s"drive/$slot")
|
||||
with DiskActivityIconSource
|
||||
with FloppyDriveIconSource {
|
||||
|
||||
val Default: IconSource = IconSource(s"$prefix/Default")
|
||||
val Default: IconSource = get("Default")
|
||||
}
|
||||
}
|
||||
|
||||
object Raid {
|
||||
protected val prefix: String = "nodes/raid"
|
||||
|
||||
val Default: IconSource = IconSource(s"$prefix/Default")
|
||||
object Raid extends IconScope("raid") {
|
||||
val Default: IconSource = get("Default")
|
||||
|
||||
val Drive: Array[Drive] = Array.tabulate(3)(new Drive(_))
|
||||
|
||||
class Drive(val slot: Int) extends DiskActivityIconSource {
|
||||
override protected def prefix: String = s"${Raid.prefix}/$slot"
|
||||
|
||||
val Error: IconSource = IconSource(s"$prefix/Error")
|
||||
class Drive(val slot: Int) extends IconScope(slot.toString) with DiskActivityIconSource {
|
||||
val Error: IconSource = get("Error")
|
||||
}
|
||||
}
|
||||
|
||||
object Screen {
|
||||
protected val prefix: String = "nodes/screen"
|
||||
object Screen extends IconScope("screen") {
|
||||
val Standalone: IconSource = get("Standalone")
|
||||
val PowerOnOverlay: IconSource = get("PowerOnOverlay")
|
||||
|
||||
val Standalone: IconSource = IconSource(s"$prefix/Standalone")
|
||||
val PowerOnOverlay: IconSource = IconSource(s"$prefix/PowerOnOverlay")
|
||||
val ColumnTop: IconSource = get("ColumnTop")
|
||||
val ColumnMiddle: IconSource = get("ColumnMiddle")
|
||||
val ColumnBottom: IconSource = get("ColumnBottom")
|
||||
|
||||
val ColumnTop: IconSource = IconSource(s"$prefix/ColumnTop")
|
||||
val ColumnMiddle: IconSource = IconSource(s"$prefix/ColumnMiddle")
|
||||
val ColumnBottom: IconSource = IconSource(s"$prefix/ColumnBottom")
|
||||
val RowLeft: IconSource = get("RowLeft")
|
||||
val RowMiddle: IconSource = get("RowMiddle")
|
||||
val RowRight: IconSource = get("RowRight")
|
||||
|
||||
val RowLeft: IconSource = IconSource(s"$prefix/RowLeft")
|
||||
val RowMiddle: IconSource = IconSource(s"$prefix/RowMiddle")
|
||||
val RowRight: IconSource = IconSource(s"$prefix/RowRight")
|
||||
val TopLeft: IconSource = get("TopLeft")
|
||||
val TopMiddle: IconSource = get("TopMiddle")
|
||||
val TopRight: IconSource = get("TopRight")
|
||||
|
||||
val TopLeft: IconSource = IconSource(s"$prefix/TopLeft")
|
||||
val TopMiddle: IconSource = IconSource(s"$prefix/TopMiddle")
|
||||
val TopRight: IconSource = IconSource(s"$prefix/TopRight")
|
||||
val MiddleLeft: IconSource = get("MiddleLeft")
|
||||
val Middle: IconSource = get("Middle")
|
||||
val MiddleRight: IconSource = get("MiddleRight")
|
||||
|
||||
val MiddleLeft: IconSource = IconSource(s"$prefix/MiddleLeft")
|
||||
val Middle: IconSource = IconSource(s"$prefix/Middle")
|
||||
val MiddleRight: IconSource = IconSource(s"$prefix/MiddleRight")
|
||||
|
||||
val BottomLeft: IconSource = IconSource(s"$prefix/BottomLeft")
|
||||
val BottomMiddle: IconSource = IconSource(s"$prefix/BottomMiddle")
|
||||
val BottomRight: IconSource = IconSource(s"$prefix/BottomRight")
|
||||
val BottomLeft: IconSource = get("BottomLeft")
|
||||
val BottomMiddle: IconSource = get("BottomMiddle")
|
||||
val BottomRight: IconSource = get("BottomRight")
|
||||
}
|
||||
|
||||
object Holidays {
|
||||
protected val prefix: String = "nodes/holidays"
|
||||
|
||||
val Christmas: IconSource = IconSource(s"$prefix/Christmas")
|
||||
val Valentines: IconSource = IconSource(s"$prefix/Valentines")
|
||||
val Halloween: IconSource = IconSource(s"$prefix/Halloween")
|
||||
object Holidays extends IconScope("holidays") {
|
||||
val Christmas: IconSource = get("Christmas")
|
||||
val Valentines: IconSource = get("Valentines")
|
||||
val Halloween: IconSource = get("Halloween")
|
||||
}
|
||||
}
|
||||
|
||||
trait PowerIconSource {
|
||||
protected def prefix: String
|
||||
|
||||
val On: IconSource = IconSource(s"$prefix/On")
|
||||
val Error: IconSource = IconSource(s"$prefix/Error")
|
||||
trait PowerIconSource extends IconScope {
|
||||
val On: IconSource = get("On")
|
||||
val Error: IconSource = get("Error")
|
||||
}
|
||||
|
||||
trait DiskActivityIconSource {
|
||||
protected def prefix: String
|
||||
|
||||
val DiskActivity: IconSource = IconSource(s"$prefix/DiskActivity")
|
||||
trait DiskActivityIconSource extends IconScope {
|
||||
val DiskActivity: IconSource = get("DiskActivity")
|
||||
}
|
||||
|
||||
trait NetworkActivityIconSource {
|
||||
protected def prefix: String
|
||||
|
||||
val NetworkActivity: IconSource = IconSource(s"$prefix/NetworkActivity")
|
||||
trait NetworkActivityIconSource extends IconScope {
|
||||
val NetworkActivity: IconSource = get("NetworkActivity")
|
||||
}
|
||||
|
||||
trait FloppyDriveIconSource {
|
||||
protected def prefix: String
|
||||
trait FloppyDriveIconSource extends IconScope {
|
||||
val Floppy: IconSource = get("Floppy")
|
||||
}
|
||||
|
||||
val Floppy: IconSource = IconSource(s"$prefix/Floppy")
|
||||
object Screen extends IconScope("screen") {
|
||||
val InnerCornerTL: IconSource = get("InnerCornerTL")
|
||||
val InnerCornerTR: IconSource = get("InnerCornerTR")
|
||||
val InnerCornerBL: IconSource = get("InnerCornerBL")
|
||||
val InnerCornerBR: IconSource = get("InnerCornerBR")
|
||||
|
||||
val OuterCornerTL: IconSource = get("OuterCornerTL")
|
||||
val OuterCornerTR: IconSource = get("OuterCornerTR")
|
||||
val OuterCornerBL: IconSource = get("OuterCornerBL")
|
||||
val OuterCornerBR: IconSource = get("OuterCornerBR")
|
||||
|
||||
val InnerBorderT: IconSource = get("InnerBorderT")
|
||||
val OuterBorderT: IconSource = get("OuterBorderT")
|
||||
|
||||
val InnerBorderB: IconSource = get("InnerBorderB")
|
||||
}
|
||||
|
||||
object Particles extends IconScope("particles") {
|
||||
val Note: IconSource = get("Note")
|
||||
val Smoke: IconSource = get("Smoke")
|
||||
}
|
||||
|
||||
object Buttons extends IconScope("buttons") {
|
||||
val BottomDrawerOpen: IconSource = get("BottomDrawerOpen")
|
||||
val BottomDrawerClose: IconSource = get("BottomDrawerClose")
|
||||
|
||||
val PowerOff: IconSource = get("PowerOff")
|
||||
val PowerOn: IconSource = get("PowerOn")
|
||||
|
||||
val OpenFMRadioVolumeOff: Boolean => IconSource = { isUp =>
|
||||
get(s"OpenFMRadioVolume${if (isUp) "Up" else "Down"}Off")
|
||||
}
|
||||
|
||||
val OpenFMRadioVolumeOn: Boolean => IconSource = { isUp =>
|
||||
get(s"OpenFMRadioVolume${if (isUp) "Up" else "Down"}On")
|
||||
}
|
||||
|
||||
val OpenFMRadioRedstoneOff: IconSource = get("OpenFMRadioRedstoneOff")
|
||||
val OpenFMRadioRedstoneOn: IconSource = get("OpenFMRadioRedstoneOn")
|
||||
|
||||
val OpenFMRadioCloseOff: IconSource = get("OpenFMRadioCloseOff")
|
||||
val OpenFMRadioCloseOn: IconSource = get("OpenFMRadioCloseOn")
|
||||
|
||||
val OpenFMRadioStartOff: IconSource = get("OpenFMRadioStartOff")
|
||||
val OpenFMRadioStopOn: IconSource = get("OpenFMRadioStopOn")
|
||||
|
||||
val RackRelayOff: IconSource = get("RackRelayOff")
|
||||
val RackRelayOn: IconSource = get("RackRelayOn")
|
||||
}
|
||||
|
||||
object Window extends IconScope("window") {
|
||||
val CornerTL: IconSource = get("CornerTL")
|
||||
val CornerTR: IconSource = get("CornerTR")
|
||||
val CornerBL: IconSource = get("CornerBL")
|
||||
val CornerBR: IconSource = get("CornerBR")
|
||||
|
||||
val BorderLight: IconSource = get("BorderLight")
|
||||
val BorderDark: IconSource = get("BorderDark")
|
||||
|
||||
object Tape extends IconScope("tape") {
|
||||
abstract class TapeButtonIconSource private[Tape] (sprite: String) {
|
||||
val Released: IconSource = get(sprite)
|
||||
val Pressed: IconSource = get(s"${sprite}Pressed")
|
||||
}
|
||||
|
||||
object Back extends TapeButtonIconSource("Back")
|
||||
object Play extends TapeButtonIconSource("Play")
|
||||
object Stop extends TapeButtonIconSource("Stop")
|
||||
object Forward extends TapeButtonIconSource("Forward")
|
||||
|
||||
val Screen: IconSource = get("Screen")
|
||||
}
|
||||
|
||||
object Case extends IconScope("case") {
|
||||
val Motherboard: IconSource = get("Motherboard")
|
||||
}
|
||||
|
||||
object Rack extends IconScope("rack") {
|
||||
val Motherboard: IconSource = get("Motherboard")
|
||||
val Lines: IconSource = get("Lines")
|
||||
|
||||
trait DirectionIconSource {
|
||||
protected def iconPrefix: String
|
||||
|
||||
val DirectionIcon: Direction => IconSource = { direction =>
|
||||
get(s"$iconPrefix${direction.side.capitalize}")
|
||||
}
|
||||
}
|
||||
|
||||
trait ConnectorIconSource {
|
||||
protected def iconPrefix: String
|
||||
|
||||
val Connector: IconSource = get(s"${iconPrefix}Connector")
|
||||
}
|
||||
|
||||
object Side extends DirectionIconSource with ConnectorIconSource {
|
||||
override protected def iconPrefix: String = "Side"
|
||||
}
|
||||
|
||||
object Network extends DirectionIconSource with ConnectorIconSource {
|
||||
override protected def iconPrefix: String = "Network"
|
||||
}
|
||||
|
||||
object Node extends DirectionIconSource {
|
||||
override protected def iconPrefix: String = "Node"
|
||||
}
|
||||
}
|
||||
|
||||
object Raid extends IconScope("raid") {
|
||||
val Slots: IconSource = get("Slots")
|
||||
}
|
||||
|
||||
val OpenFMRadio: IconSource = get("OpenFMRadio")
|
||||
}
|
||||
|
||||
object Panel extends IconScope("panel") {
|
||||
val CornerTL: IconSource = get("CornerTL")
|
||||
val CornerTR: IconSource = get("CornerTR")
|
||||
val CornerBL: IconSource = get("CornerBL")
|
||||
val CornerBR: IconSource = get("CornerBR")
|
||||
|
||||
val BorderT: IconSource = get("BorderT")
|
||||
val BorderB: IconSource = get("BorderB")
|
||||
val BorderL: IconSource = get("BorderL")
|
||||
val BorderR: IconSource = get("BorderR")
|
||||
|
||||
val Fill: IconSource = get("Fill")
|
||||
}
|
||||
|
||||
object LightPanel extends IconScope("light-panel") {
|
||||
val CornerTL: IconSource = get("CornerTL")
|
||||
val CornerTR: IconSource = get("CornerTR")
|
||||
val CornerBL: IconSource = get("CornerBL")
|
||||
val CornerBR: IconSource = get("CornerBR")
|
||||
|
||||
val BorderT: IconSource = get("BorderT")
|
||||
val BorderB: IconSource = get("BorderB")
|
||||
val BorderL: IconSource = get("BorderL")
|
||||
val BorderR: IconSource = get("BorderR")
|
||||
|
||||
val Fill: IconSource = get("Fill")
|
||||
val Vent: IconSource = get("Vent")
|
||||
|
||||
val BookmarkLeft: IconSource = get("BookmarkLeft")
|
||||
val BookmarkRight: IconSource = get("BookmarkRight")
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,17 +1,22 @@
|
||||
package ocelot.desktop.inventory
|
||||
|
||||
import ocelot.desktop.inventory.Inventory.SlotObserver
|
||||
import ocelot.desktop.ui.event.{Event, EventAware}
|
||||
import ocelot.desktop.ui.event.{BrainEvent, Dispatchable, EventAware}
|
||||
import ocelot.desktop.util.Disposable
|
||||
import totoro.ocelot.brain.event.NodeEvent
|
||||
|
||||
import scala.collection.mutable
|
||||
|
||||
/** Provides an inventory — a collection of [[Item]]s indexed by slots. */
|
||||
trait Inventory extends EventAware {
|
||||
/**
|
||||
* Provides an inventory — a collection of [[Item]]s indexed by slots.
|
||||
*/
|
||||
trait Inventory extends EventAware with Disposable {
|
||||
// parallels totoro.ocelot.brain.entity.traits.Inventory
|
||||
// this is intentional
|
||||
|
||||
/** The type of items stored in this inventory. */
|
||||
/**
|
||||
* The type of items stored in this inventory.
|
||||
*/
|
||||
type I <: Item
|
||||
|
||||
private type WeakHashSet[A] = mutable.WeakHashMap[A, Unit]
|
||||
@ -20,13 +25,20 @@ trait Inventory extends EventAware {
|
||||
private val itemSlots = mutable.HashMap.empty[I, Int]
|
||||
private val observers = mutable.HashMap.empty[Int, WeakHashSet[SlotObserver]]
|
||||
|
||||
/** Called after a new item is added to the inventory.
|
||||
override def dispose(): Unit = {
|
||||
super.dispose()
|
||||
inventoryIterator.foreach(_.removeAndDispose())
|
||||
}
|
||||
|
||||
/**
|
||||
* Called after a new item is added to the inventory.
|
||||
*
|
||||
* @param slot the slot the item was added to
|
||||
*/
|
||||
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:
|
||||
*
|
||||
@ -41,7 +53,9 @@ trait Inventory extends EventAware {
|
||||
*/
|
||||
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 clearInventory(): Unit = {
|
||||
@ -67,18 +81,20 @@ trait Inventory extends EventAware {
|
||||
super.shouldReceiveEventsFor(address) ||
|
||||
inventoryIterator.flatMap(_.get).exists(_.shouldReceiveEventsFor(address))
|
||||
|
||||
override def handleEvent(event: Event): Unit = {
|
||||
override def handleEvent(event: Dispatchable): Unit = {
|
||||
super.handleEvent(event)
|
||||
|
||||
for (slot <- inventoryIterator; item <- slot.get) {
|
||||
event match {
|
||||
case n: NodeEvent if !item.shouldReceiveEventsFor(n.address) => // ignore
|
||||
case BrainEvent(e: NodeEvent) if !item.shouldReceiveEventsFor(e.address) => // ignore
|
||||
case _ => item.handleEvent(event)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 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) {
|
||||
require(index >= 0)
|
||||
|
||||
@ -86,27 +102,46 @@ trait Inventory extends EventAware {
|
||||
|
||||
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 = {
|
||||
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 = {
|
||||
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 = {
|
||||
setSlot(index, None)
|
||||
}
|
||||
|
||||
/** The [[Item]] contained in this slot. */
|
||||
/**
|
||||
* Removes the item contained in this slot if there is one, calling the item's [[dispose]] method.
|
||||
*/
|
||||
def removeAndDispose(): Unit = {
|
||||
for (item <- get) {
|
||||
remove()
|
||||
item.dispose()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The [[Item]] contained in this slot.
|
||||
*/
|
||||
def get: Option[inventory.I] = slotItems.get(index)
|
||||
|
||||
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`.
|
||||
*/
|
||||
@ -148,8 +183,9 @@ trait Inventory extends EventAware {
|
||||
}
|
||||
|
||||
final object Slot {
|
||||
|
||||
/** Creates a proxy to an inventory slot. */
|
||||
/**
|
||||
* Creates a proxy to an inventory slot.
|
||||
*/
|
||||
def apply(index: Int) = new Slot(index)
|
||||
}
|
||||
|
||||
@ -206,13 +242,15 @@ trait Inventory extends EventAware {
|
||||
object Inventory {
|
||||
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.
|
||||
*/
|
||||
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.
|
||||
*
|
||||
@ -220,7 +258,9 @@ object Inventory {
|
||||
*/
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@ -20,80 +20,7 @@ object Items extends Logging {
|
||||
// this is just to force load the class during initialization
|
||||
def init(): Unit = {}
|
||||
|
||||
/** Registers a recoverer for [[ItemRecoverer.sourceClass]]. */
|
||||
def registerRecoverer(recoverer: ItemRecoverer[_, _]): Unit = {
|
||||
if (!_recoverers.contains(recoverer.sourceClass)) {
|
||||
_recoverers(recoverer.sourceClass) = recoverer
|
||||
logger.info(s"Registered a recoverer for ${recoverer.sourceClass.getName}")
|
||||
}
|
||||
}
|
||||
|
||||
private def registerItemFactoryRecoverers(factory: ItemFactory): Unit = {
|
||||
for (recoverer <- factory.recoverers) {
|
||||
registerRecoverer(recoverer)
|
||||
}
|
||||
}
|
||||
|
||||
def registerSingleton(factory: ItemFactory): Unit = {
|
||||
_groups += SingletonItemGroup(factory.name, factory)
|
||||
registerItemFactoryRecoverers(factory)
|
||||
}
|
||||
|
||||
def registerTiered(name: String, tiers: IterableOnce[Tier])(factory: Tier => ItemFactory): Unit = {
|
||||
val group = TieredItemGroup(name, tiers.iterator.map(tier => (tier, factory(tier))).toSeq)
|
||||
_groups += group
|
||||
|
||||
for ((_, factory) <- group.factories) {
|
||||
registerItemFactoryRecoverers(factory)
|
||||
}
|
||||
}
|
||||
|
||||
def registerExtendedTiered(name: String, tiers: IterableOnce[ExtendedTier])(
|
||||
factory: ExtendedTier => ItemFactory
|
||||
): Unit = {
|
||||
val group = ExtendedTieredItemGroup(name, tiers.iterator.map(tier => (tier, factory(tier))).toSeq)
|
||||
_groups += group
|
||||
|
||||
for ((_, factory) <- group.factories) {
|
||||
registerItemFactoryRecoverers(factory)
|
||||
}
|
||||
}
|
||||
|
||||
def registerArbitrary(name: String, icon: IconSource, factories: IterableOnce[(String, ItemFactory)]): Unit = {
|
||||
val group = ArbitraryItemGroup(name, icon, factories.iterator.toSeq)
|
||||
_groups += group
|
||||
|
||||
for ((_, factory) <- group.factories) {
|
||||
registerItemFactoryRecoverers(factory)
|
||||
}
|
||||
}
|
||||
|
||||
def groups: Iterable[ItemGroup] = _groups
|
||||
|
||||
/** Attempts to recover an [[Item]] from `source`.
|
||||
*
|
||||
* Checks superclasses and traits while looking for a recoverer.
|
||||
*/
|
||||
def recover[A](source: A): Option[Item] = {
|
||||
linearizationOrder(source.getClass.asInstanceOf[Class[_]])
|
||||
.flatMap(_recoverers.get)
|
||||
.map(_.asInstanceOf[ItemRecoverer[_ >: A, _ <: Item]].recover(source))
|
||||
.nextOption()
|
||||
}
|
||||
|
||||
sealed trait ItemGroup {
|
||||
def name: String
|
||||
}
|
||||
|
||||
case class SingletonItemGroup(name: String, factory: ItemFactory) extends ItemGroup
|
||||
|
||||
case class TieredItemGroup(name: String, factories: Seq[(Tier, ItemFactory)]) extends ItemGroup
|
||||
|
||||
case class ExtendedTieredItemGroup(name: String, factories: Seq[(ExtendedTier, ItemFactory)]) extends ItemGroup
|
||||
|
||||
case class ArbitraryItemGroup(name: String, icon: IconSource, factories: Seq[(String, ItemFactory)]) extends ItemGroup
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
logger.debug("Initialize item serialization...")
|
||||
|
||||
registerTiered("CPU", Tier.One to Tier.Three)(new CpuItem.Factory(_))
|
||||
registerTiered("APU", Tier.Two to Tier.Creative)(tier => new ApuItem.Factory(tier.saturatingSub(1)))
|
||||
@ -166,4 +93,81 @@ object Items extends Logging {
|
||||
.map(new TapeItem.Factory(_))
|
||||
.map(factory => (f"${factory.name}%s (${Tape.lengthMinutes(factory.kind)}%.0f min)", factory)),
|
||||
)
|
||||
|
||||
logger.debug("Item serialization initialization finished.")
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/** Registers a recoverer for [[ItemRecoverer.sourceClass]]. */
|
||||
private def registerRecoverer(recoverer: ItemRecoverer[_, _]): Unit = {
|
||||
if (!_recoverers.contains(recoverer.sourceClass)) {
|
||||
_recoverers(recoverer.sourceClass) = recoverer
|
||||
logger.debug(s"Registered a recoverer for ${recoverer.sourceClass.getName}")
|
||||
}
|
||||
}
|
||||
|
||||
private def registerItemFactoryRecoverers(factory: ItemFactory): Unit = {
|
||||
for (recoverer <- factory.recoverers) {
|
||||
registerRecoverer(recoverer)
|
||||
}
|
||||
}
|
||||
|
||||
def registerSingleton(factory: ItemFactory): Unit = {
|
||||
_groups += SingletonItemGroup(factory.name, factory)
|
||||
registerItemFactoryRecoverers(factory)
|
||||
}
|
||||
|
||||
def registerTiered(name: String, tiers: IterableOnce[Tier])(factory: Tier => ItemFactory): Unit = {
|
||||
val group = TieredItemGroup(name, tiers.iterator.map(tier => (tier, factory(tier))).toSeq)
|
||||
_groups += group
|
||||
|
||||
for ((_, factory) <- group.factories) {
|
||||
registerItemFactoryRecoverers(factory)
|
||||
}
|
||||
}
|
||||
|
||||
def registerExtendedTiered(name: String, tiers: IterableOnce[ExtendedTier])(
|
||||
factory: ExtendedTier => ItemFactory
|
||||
): Unit = {
|
||||
val group = ExtendedTieredItemGroup(name, tiers.iterator.map(tier => (tier, factory(tier))).toSeq)
|
||||
_groups += group
|
||||
|
||||
for ((_, factory) <- group.factories) {
|
||||
registerItemFactoryRecoverers(factory)
|
||||
}
|
||||
}
|
||||
|
||||
def registerArbitrary(name: String, icon: IconSource, factories: IterableOnce[(String, ItemFactory)]): Unit = {
|
||||
val group = ArbitraryItemGroup(name, icon, factories.iterator.toSeq)
|
||||
_groups += group
|
||||
|
||||
for ((_, factory) <- group.factories) {
|
||||
registerItemFactoryRecoverers(factory)
|
||||
}
|
||||
}
|
||||
|
||||
def groups: Iterable[ItemGroup] = _groups
|
||||
|
||||
/** Attempts to recover an [[Item]] from `source`.
|
||||
*
|
||||
* Checks superclasses and traits while looking for a recoverer.
|
||||
*/
|
||||
def recover[A](source: A): Option[Item] = {
|
||||
linearizationOrder(source.getClass.asInstanceOf[Class[_]])
|
||||
.flatMap(_recoverers.get)
|
||||
.map(_.asInstanceOf[ItemRecoverer[_ >: A, _ <: Item]].recover(source))
|
||||
.nextOption()
|
||||
}
|
||||
|
||||
sealed trait ItemGroup {
|
||||
def name: String
|
||||
}
|
||||
|
||||
case class SingletonItemGroup(name: String, factory: ItemFactory) extends ItemGroup
|
||||
|
||||
case class TieredItemGroup(name: String, factories: Seq[(Tier, ItemFactory)]) extends ItemGroup
|
||||
|
||||
case class ExtendedTieredItemGroup(name: String, factories: Seq[(ExtendedTier, ItemFactory)]) extends ItemGroup
|
||||
|
||||
case class ArbitraryItemGroup(name: String, icon: IconSource, factories: Seq[(String, ItemFactory)]) extends ItemGroup
|
||||
}
|
||||
|
||||
@ -104,7 +104,7 @@ trait PersistedInventory extends Inventory with Logging with Persistable {
|
||||
}
|
||||
|
||||
protected def onSlotLoadFailed(slotIndex: Int): Unit = {
|
||||
Slot(slotIndex).remove()
|
||||
Slot(slotIndex).removeAndDispose()
|
||||
}
|
||||
|
||||
protected def saveEntityItem(slotNbt: NBTTagCompound, item: EntityItem): Unit = {
|
||||
|
||||
@ -178,7 +178,7 @@ trait SyncedInventory extends PersistedInventory with EventAware with Logging {
|
||||
)
|
||||
|
||||
logger.error("Breaking the loop forcefully by removing the items.")
|
||||
Slot(slotIndex).remove()
|
||||
Slot(slotIndex).removeAndDispose()
|
||||
brainInventory.inventory(slotIndex).remove()
|
||||
} else {
|
||||
direction match {
|
||||
|
||||
@ -45,8 +45,8 @@ class EepromItem(val eeprom: EEPROM) extends Item with ComponentItem with Persis
|
||||
}
|
||||
|
||||
override def fillRmbMenu(menu: ContextMenu): Unit = {
|
||||
menu.addEntry(new ContextMenuSubmenu("External data source", Some(ContextMenuIcon(IconSource.Code))) {
|
||||
addEntry(ContextMenuEntry("Local file", IconSource.File) {
|
||||
menu.addEntry(new ContextMenuSubmenu("External data source", Some(ContextMenuIcon(IconSource.Icons.Code))) {
|
||||
addEntry(ContextMenuEntry("Local file", IconSource.Icons.File) {
|
||||
OcelotDesktop.showFileChooserDialog(JFileChooser.OPEN_DIALOG, JFileChooser.FILES_ONLY) { file =>
|
||||
Try {
|
||||
for (file <- file) {
|
||||
@ -56,7 +56,7 @@ class EepromItem(val eeprom: EEPROM) extends Item with ComponentItem with Persis
|
||||
}
|
||||
})
|
||||
|
||||
addEntry(ContextMenuEntry("File via URL", IconSource.Link) {
|
||||
addEntry(ContextMenuEntry("File via URL", IconSource.Icons.Link) {
|
||||
new InputDialog(
|
||||
title = "File via URL",
|
||||
onConfirmed = { text =>
|
||||
@ -74,7 +74,7 @@ class EepromItem(val eeprom: EEPROM) extends Item with ComponentItem with Persis
|
||||
})
|
||||
|
||||
if (eeprom.codePath.nonEmpty || eeprom.codeURL.nonEmpty) {
|
||||
addEntry(ContextMenuEntry("Detach", IconSource.LinkSlash) {
|
||||
addEntry(ContextMenuEntry("Detach", IconSource.Icons.LinkSlash) {
|
||||
eeprom.codeBytes = Some(Array.empty)
|
||||
})
|
||||
}
|
||||
|
||||
@ -3,37 +3,44 @@ package ocelot.desktop.inventory.item
|
||||
import ocelot.desktop.graphics.IconSource
|
||||
import ocelot.desktop.inventory.traits.{ComponentItem, DiskItem, PersistableItem}
|
||||
import ocelot.desktop.inventory.{Item, ItemFactory, ItemRecoverer}
|
||||
import ocelot.desktop.ui.widget.DiskEditWindow
|
||||
import ocelot.desktop.ui.widget.tooltip.ItemTooltip
|
||||
import totoro.ocelot.brain.entity.fs.ReadWriteLabel
|
||||
import totoro.ocelot.brain.entity.traits.{Disk, Entity, Floppy}
|
||||
import totoro.ocelot.brain.entity.{FloppyManaged, FloppyUnmanaged}
|
||||
import totoro.ocelot.brain.loot.Loot.{FloppyFactory => LootFloppyFactory, LootFloppy}
|
||||
import totoro.ocelot.brain.loot.Loot.{LootFloppy, FloppyFactory => LootFloppyFactory}
|
||||
import totoro.ocelot.brain.util.DyeColor
|
||||
import totoro.ocelot.brain.util.Tier.Tier
|
||||
|
||||
class FloppyItem(var floppy: Floppy) extends Item with ComponentItem with PersistableItem with DiskItem {
|
||||
class FloppyItem(var floppy: Floppy)
|
||||
extends Item
|
||||
with ComponentItem
|
||||
with PersistableItem
|
||||
with DiskItem
|
||||
with DiskEditWindow.HasColor {
|
||||
|
||||
override def entity: Entity with Disk = floppy
|
||||
|
||||
override def diskKind: String = "Floppy"
|
||||
|
||||
override def name: String = floppy.name.getOrElse(super.name)
|
||||
|
||||
override def label: Option[String] = floppy.label.labelOption
|
||||
override def diskLabel: Option[String] = floppy.label.labelOption
|
||||
|
||||
override def isLabelWriteable: Boolean = floppy.label.isInstanceOf[ReadWriteLabel]
|
||||
|
||||
override def setLabel(label: Option[String]): Unit = {
|
||||
override def diskLabel_=(label: Option[String]): Unit = {
|
||||
floppy.label.setLabel(label.orNull)
|
||||
}
|
||||
|
||||
override def color: Some[DyeColor] = Some(floppy.color)
|
||||
override def isLabelWriteable: Boolean = floppy.label.isInstanceOf[ReadWriteLabel]
|
||||
|
||||
override def setColor(color: DyeColor): Unit = {
|
||||
override def color: DyeColor = floppy.color
|
||||
|
||||
override def color_=(color: DyeColor): Unit = {
|
||||
floppy.color = color
|
||||
}
|
||||
|
||||
override def setManaged(managed: Boolean): Unit = {
|
||||
val label = this.label
|
||||
val label = this.diskLabel
|
||||
|
||||
reinserting {
|
||||
floppy =
|
||||
@ -41,13 +48,13 @@ class FloppyItem(var floppy: Floppy) extends Item with ComponentItem with Persis
|
||||
else new FloppyUnmanaged(floppy.name, floppy.color)
|
||||
}
|
||||
|
||||
setLabel(label)
|
||||
diskLabel = label
|
||||
}
|
||||
|
||||
override def fillTooltip(tooltip: ItemTooltip): Unit = {
|
||||
super.fillTooltip(tooltip)
|
||||
|
||||
for (label <- label) {
|
||||
for (label <- diskLabel) {
|
||||
if (label != name) {
|
||||
// this is true for loot floppies
|
||||
addDiskLabelTooltip(tooltip, label)
|
||||
|
||||
@ -6,7 +6,7 @@ import ocelot.desktop.inventory.traits.{ComponentItem, DiskItem, PersistableItem
|
||||
import ocelot.desktop.inventory.{Item, ItemFactory, ItemRecoverer}
|
||||
import ocelot.desktop.ui.widget.tooltip.ItemTooltip
|
||||
import totoro.ocelot.brain.entity.fs.{Label, ReadWriteLabel}
|
||||
import totoro.ocelot.brain.entity.traits.{Disk, Entity}
|
||||
import totoro.ocelot.brain.entity.traits.{Disk, Entity, Environment}
|
||||
import totoro.ocelot.brain.entity.{HDDManaged, HDDUnmanaged}
|
||||
import totoro.ocelot.brain.util.Tier.Tier
|
||||
|
||||
@ -29,14 +29,14 @@ class HddItem(var hdd: Hdd) extends Item with ComponentItem with PersistableItem
|
||||
case Hdd.Unmanaged(hdd) => hdd.label
|
||||
}
|
||||
|
||||
def label: Option[String] = fsLabel.labelOption
|
||||
def diskLabel: Option[String] = fsLabel.labelOption
|
||||
|
||||
override def diskLabel_=(label: Option[String]): Unit = fsLabel.setLabel(label.orNull)
|
||||
|
||||
override def isLabelWriteable: Boolean = fsLabel.isInstanceOf[ReadWriteLabel]
|
||||
|
||||
override def setLabel(label: Option[String]): Unit = fsLabel.setLabel(label.orNull)
|
||||
|
||||
override def setManaged(managed: Boolean): Unit = {
|
||||
val label = this.label
|
||||
val label = this.diskLabel
|
||||
|
||||
reinserting {
|
||||
hdd =
|
||||
@ -44,13 +44,13 @@ class HddItem(var hdd: Hdd) extends Item with ComponentItem with PersistableItem
|
||||
else Hdd(new HDDUnmanaged(tier.get))
|
||||
}
|
||||
|
||||
setLabel(label)
|
||||
diskLabel = label
|
||||
}
|
||||
|
||||
override def fillTooltip(tooltip: ItemTooltip): Unit = {
|
||||
super.fillTooltip(tooltip)
|
||||
|
||||
label.foreach(addDiskLabelTooltip(tooltip, _))
|
||||
diskLabel.foreach(addDiskLabelTooltip(tooltip, _))
|
||||
|
||||
hdd match {
|
||||
case Hdd.Managed(hdd) => addSourcePathTooltip(tooltip, hdd)
|
||||
|
||||
@ -22,7 +22,7 @@ class LinkedCardItem(val linkedCard: LinkedCard) extends Item with ComponentItem
|
||||
|
||||
override def fillRmbMenu(menu: ContextMenu): Unit = {
|
||||
menu.addEntry(
|
||||
ContextMenuEntry("Set channel", IconSource.Antenna) {
|
||||
ContextMenuEntry("Set channel", IconSource.Icons.Antenna) {
|
||||
new TunnelDialog(
|
||||
tunnel => linkedCard.tunnel = tunnel,
|
||||
linkedCard.tunnel,
|
||||
|
||||
@ -23,7 +23,7 @@ class OcelotCardItem(val ocelotCard: OcelotCard)
|
||||
override def tooltipNameColor: Color = ColorScheme("OcelotCardTooltip")
|
||||
|
||||
override def fillRmbMenu(menu: ContextMenu): Unit = {
|
||||
menu.addEntry(ContextMenuEntry("Open console", IconSource.Window) {
|
||||
menu.addEntry(ContextMenuEntry("Open console", IconSource.Icons.Window) {
|
||||
window.open()
|
||||
})
|
||||
|
||||
|
||||
@ -13,7 +13,7 @@ import totoro.ocelot.brain.nbt.NBTTagCompound
|
||||
import totoro.ocelot.brain.util.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 {
|
||||
abstract class Factory extends ItemFactory {
|
||||
@ -43,7 +43,7 @@ object RedstoneCardItem {
|
||||
}
|
||||
|
||||
override def fillRmbMenu(menu: ContextMenu): Unit = {
|
||||
menu.addEntry(ContextMenuEntry("Redstone I/O", IconSource.ArrowRight) {
|
||||
menu.addEntry(ContextMenuEntry("Redstone I/O", IconSource.Icons.ArrowRight) {
|
||||
windowed.window.open()
|
||||
})
|
||||
|
||||
@ -63,6 +63,12 @@ object RedstoneCardItem {
|
||||
|
||||
windowed.save(nbt)
|
||||
}
|
||||
|
||||
override def dispose(): Unit = {
|
||||
windowed.closeAndDisposeWindow()
|
||||
|
||||
super.dispose()
|
||||
}
|
||||
}
|
||||
|
||||
object Tier1 {
|
||||
@ -93,7 +99,7 @@ object RedstoneCardItem {
|
||||
}
|
||||
|
||||
override def fillRmbMenu(menu: ContextMenu): Unit = {
|
||||
menu.addEntry(ContextMenuEntry("Bundled I/O", IconSource.LinesHorizontal) {
|
||||
menu.addEntry(ContextMenuEntry("Bundled I/O", IconSource.Icons.LinesHorizontal) {
|
||||
windowed.window.open()
|
||||
})
|
||||
|
||||
@ -111,6 +117,12 @@ object RedstoneCardItem {
|
||||
|
||||
windowed.save(nbt)
|
||||
}
|
||||
|
||||
override def dispose(): Unit = {
|
||||
windowed.closeAndDisposeWindow()
|
||||
|
||||
super.dispose()
|
||||
}
|
||||
}
|
||||
|
||||
object Tier2 {
|
||||
|
||||
@ -22,7 +22,7 @@ class SelfDestructingCardItem(val card: SelfDestructingCard)
|
||||
tooltip.addLine(
|
||||
if (card.remainingTime < 0) "Fuse has not been set"
|
||||
else if (card.remainingTime == 0) "BOOM!"
|
||||
else card.remainingTime.toString
|
||||
else f"Time to explosion: ${card.remainingTime / 20f}%.2fs"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,25 +1,52 @@
|
||||
package ocelot.desktop.inventory.item
|
||||
|
||||
import ocelot.desktop.audio.{Audio, SoundCategory, SoundSamples, SoundSource, SoundStream}
|
||||
import ocelot.desktop.graphics.IconSource
|
||||
import ocelot.desktop.inventory.traits.{CardItem, ComponentItem, PersistableItem}
|
||||
import ocelot.desktop.inventory.{Item, ItemFactory, ItemRecoverer}
|
||||
import ocelot.desktop.ui.event.BrainEvent
|
||||
import ocelot.desktop.ui.widget.card.SoundCardWindow
|
||||
import ocelot.desktop.ui.widget.contextmenu.{ContextMenu, ContextMenuEntry}
|
||||
import ocelot.desktop.ui.widget.window.Windowed
|
||||
import ocelot.desktop.util.Lazy
|
||||
import totoro.ocelot.brain.Settings
|
||||
import totoro.ocelot.brain.entity.sound_card.SoundCard
|
||||
import totoro.ocelot.brain.entity.traits.{Entity, Environment}
|
||||
import totoro.ocelot.brain.event.SoundCardAudioEvent
|
||||
import totoro.ocelot.brain.util.Tier
|
||||
import totoro.ocelot.brain.util.Tier.Tier
|
||||
|
||||
class SoundCardItem(val soundCard: SoundCard)
|
||||
extends Item with ComponentItem with PersistableItem with CardItem with Windowed[SoundCardWindow] {
|
||||
extends Item
|
||||
with ComponentItem
|
||||
with PersistableItem
|
||||
with CardItem
|
||||
with Windowed[SoundCardWindow] {
|
||||
|
||||
override def createWindow(): SoundCardWindow = new SoundCardWindow(soundCard)
|
||||
|
||||
override def entity: Entity with Environment = soundCard
|
||||
override def entity: SoundCard = soundCard
|
||||
|
||||
private val streamPair = Lazy(Audio.newStream(SoundCategory.Records))
|
||||
private def stream: SoundStream = streamPair.getSync._1
|
||||
private def source: SoundSource = streamPair.getSync._2
|
||||
|
||||
eventHandlers += {
|
||||
case BrainEvent(event: SoundCardAudioEvent) if !Audio.isDisabled =>
|
||||
val samples = SoundSamples(event.data, Settings.get.soundCardSampleRate, SoundSamples.Format.Mono8)
|
||||
stream.enqueue(samples)
|
||||
source.volume = event.volume
|
||||
}
|
||||
|
||||
override def dispose(): Unit = {
|
||||
super.dispose()
|
||||
|
||||
for (stream <- streamPair.getOption) {
|
||||
stream._2.stop()
|
||||
}
|
||||
}
|
||||
|
||||
override def fillRmbMenu(menu: ContextMenu): Unit = {
|
||||
menu.addEntry(ContextMenuEntry("Open card interface", IconSource.Window) {
|
||||
menu.addEntry(ContextMenuEntry("Open card interface", IconSource.Icons.Window) {
|
||||
window.open()
|
||||
})
|
||||
|
||||
|
||||
@ -22,7 +22,7 @@ trait ComponentItem extends EntityItem {
|
||||
}
|
||||
}
|
||||
|
||||
private val copyAddressEntry = ContextMenuEntry("Copy address", IconSource.Copy) {
|
||||
private val copyAddressEntry = ContextMenuEntry("Copy address", IconSource.Icons.Copy) {
|
||||
UiHandler.clipboard = entity.node.address
|
||||
}
|
||||
|
||||
|
||||
@ -13,7 +13,7 @@ trait CpuLikeItem extends ComponentItem {
|
||||
override def entity: Entity with GenericCPU
|
||||
|
||||
override def fillRmbMenu(menu: ContextMenu): Unit = {
|
||||
menu.addEntry(new ContextMenuSubmenu("Set architecture", Some(ContextMenuIcon(IconSource.Microchip))) {
|
||||
menu.addEntry(new ContextMenuSubmenu("Set architecture", Some(ContextMenuIcon(IconSource.Icons.Microchip))) {
|
||||
for (arch <- entity.allArchitectures) {
|
||||
val name = MachineAPI.getArchitectureName(arch) +
|
||||
(if (arch == entity.architecture) " (current)" else "")
|
||||
|
||||
@ -4,40 +4,38 @@ import ocelot.desktop.OcelotDesktop
|
||||
import ocelot.desktop.graphics.IconSource
|
||||
import ocelot.desktop.node.nodes.RaidNode
|
||||
import ocelot.desktop.ui.widget.DiskEditWindow
|
||||
import ocelot.desktop.ui.widget.DiskEditWindow.{CanChangeManaged, EditableDisk, Lockable}
|
||||
import ocelot.desktop.ui.widget.contextmenu.{ContextMenu, ContextMenuEntry}
|
||||
import ocelot.desktop.ui.widget.tooltip.ItemTooltip
|
||||
import ocelot.desktop.ui.widget.window.{Window, Windowed}
|
||||
import totoro.ocelot.brain.entity.traits.{Disk, DiskManaged, DiskRealPathAware, DiskUnmanaged, Entity}
|
||||
import totoro.ocelot.brain.util.DyeColor
|
||||
|
||||
import javax.swing.JFileChooser
|
||||
import scala.util.Try
|
||||
|
||||
/** A utility mixin for HDDs and floppies. */
|
||||
trait DiskItem extends ComponentItem with Windowed[DiskEditWindow] {
|
||||
/**
|
||||
* A utility mixin for HDDs and floppies.
|
||||
*/
|
||||
trait DiskItem
|
||||
extends ComponentItem
|
||||
with EditableDisk
|
||||
with Lockable
|
||||
with CanChangeManaged
|
||||
with Windowed[DiskEditWindow] {
|
||||
|
||||
override def entity: Entity with Disk
|
||||
|
||||
def diskKind: String
|
||||
override def disk: Option[Entity with Disk] = Option(entity)
|
||||
|
||||
def color: Option[DyeColor] = None
|
||||
override def capacity: Long = disk.fold(0L)(_.capacity)
|
||||
|
||||
def setColor(color: DyeColor): Unit = throw new UnsupportedOperationException()
|
||||
|
||||
def label: Option[String]
|
||||
|
||||
def isLabelWriteable: Boolean
|
||||
|
||||
def setLabel(label: Option[String]): Unit
|
||||
|
||||
def setManaged(managed: Boolean): Unit
|
||||
|
||||
def lock(): Unit = reinserting {
|
||||
override def lock(): Unit = reinserting {
|
||||
entity.setLocked(OcelotDesktop.player.nickname)
|
||||
}
|
||||
|
||||
override def createWindow(): DiskEditWindow = new DiskEditWindow(DiskItem.this)
|
||||
override def createWindow(): DiskEditWindow = new DiskEditWindow(this)
|
||||
|
||||
def isEditingAllowed: Boolean = {
|
||||
def editingAllowed: Boolean = {
|
||||
slot.fold(false)(_.inventory match {
|
||||
case _: RaidNode => false
|
||||
case _ => true
|
||||
@ -45,16 +43,19 @@ trait DiskItem extends ComponentItem with Windowed[DiskEditWindow] {
|
||||
}
|
||||
|
||||
override def fillRmbMenu(menu: ContextMenu): Unit = {
|
||||
if (isEditingAllowed) {
|
||||
if (editingAllowed) {
|
||||
// Real path
|
||||
entity match {
|
||||
case diskManaged: DiskManaged =>
|
||||
DiskItem.addRealPathContextMenuEntries(menu, diskManaged,
|
||||
DiskItem.addRealPathContextMenuEntries(
|
||||
menu,
|
||||
diskManaged,
|
||||
realPathSetter => {
|
||||
reinserting {
|
||||
realPathSetter()
|
||||
}
|
||||
})
|
||||
},
|
||||
)
|
||||
case _ =>
|
||||
}
|
||||
|
||||
@ -86,18 +87,22 @@ trait DiskItem extends ComponentItem with Windowed[DiskEditWindow] {
|
||||
|
||||
override def fillTooltip(tooltip: ItemTooltip): Unit = {
|
||||
super.fillTooltip(tooltip)
|
||||
|
||||
if (entity != null) {
|
||||
tooltip.addLine(s"Capacity: ${entity.capacity / 1024} kB")
|
||||
tooltip.addLine(s"Capacity: ${capacity / 1024} kB")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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(
|
||||
if (diskRealPathAware.customRealPath.isDefined) "Change directory" else "Set directory",
|
||||
IconSource.Folder,
|
||||
IconSource.Icons.Folder,
|
||||
) {
|
||||
OcelotDesktop.showFileChooserDialog(JFileChooser.OPEN_DIALOG, JFileChooser.DIRECTORIES_ONLY) { dir =>
|
||||
Try {
|
||||
@ -112,7 +117,7 @@ object DiskItem {
|
||||
})
|
||||
|
||||
if (diskRealPathAware.customRealPath.isDefined) {
|
||||
menu.addEntry(ContextMenuEntry("Reset directory", IconSource.FolderSlash) {
|
||||
menu.addEntry(ContextMenuEntry("Reset directory", IconSource.Icons.FolderSlash) {
|
||||
realPathSetter(() => {
|
||||
// trigger component_removed / component_added signals
|
||||
diskRealPathAware.customRealPath = None
|
||||
@ -122,7 +127,7 @@ object DiskItem {
|
||||
}
|
||||
|
||||
def addEditDiskContextMenuEntries[T <: Window](menu: ContextMenu, windowed: Windowed[T]): Unit = {
|
||||
menu.addEntry(ContextMenuEntry("Edit disk", IconSource.Edit) {
|
||||
menu.addEntry(ContextMenuEntry("Edit disk", IconSource.Icons.Edit) {
|
||||
windowed.window.open()
|
||||
})
|
||||
}
|
||||
|
||||
102
src/main/scala/ocelot/desktop/node/BoomCardFxHandler.scala
Normal file
@ -0,0 +1,102 @@
|
||||
package ocelot.desktop.node
|
||||
|
||||
import ocelot.desktop.audio.{SoundBuffers, SoundCategory, SoundSource}
|
||||
import ocelot.desktop.color.Color
|
||||
import ocelot.desktop.geometry.FloatUtils.ExtendedFloat
|
||||
import ocelot.desktop.graphics.{Graphics, IconSource}
|
||||
import ocelot.desktop.inventory.item.SelfDestructingCardItem
|
||||
import ocelot.desktop.node.BoomCardFxHandler.{ExpandIntensity, ExpandPeriod, FlickerAlpha, FlickerDuty, GlowAlpha, MaxSize, MinSize}
|
||||
import ocelot.desktop.ui.UiHandler
|
||||
import ocelot.desktop.ui.event.BrainEvent
|
||||
import ocelot.desktop.{ColorScheme, OcelotDesktop}
|
||||
import totoro.ocelot.brain.event.SelfDestructingCardBoomEvent
|
||||
|
||||
trait BoomCardFxHandler extends Node with PositionalSoundSourcesNode with SmokeParticleNode {
|
||||
private var boomPhase: Float = -1
|
||||
|
||||
private lazy val explosionSound = {
|
||||
SoundSource.fromBuffer(SoundBuffers.MinecraftExplosion, SoundCategory.Environment)
|
||||
}
|
||||
|
||||
private lazy val countdownBeepSound = {
|
||||
SoundSource.fromBuffer(SoundBuffers.SelfDestructingCardCountdownBeep, SoundCategory.Environment)
|
||||
}
|
||||
|
||||
override def soundSources: Seq[SoundSource] = super.soundSources ++ Seq(
|
||||
explosionSound,
|
||||
countdownBeepSound,
|
||||
)
|
||||
|
||||
eventHandlers += {
|
||||
case BrainEvent(_: SelfDestructingCardBoomEvent) =>
|
||||
OcelotDesktop.updateThreadTasks.add(() => {
|
||||
explosionSound.play()
|
||||
emitSmoke()
|
||||
destroy()
|
||||
})
|
||||
}
|
||||
|
||||
protected def selfDestructingCards: IterableOnce[SelfDestructingCardItem]
|
||||
|
||||
private var phase = 0f
|
||||
private var flickerPhase = 0f
|
||||
|
||||
private def updateBoomCardState(): Unit = {
|
||||
phase = (phase + UiHandler.dt / ExpandPeriod) % 1f
|
||||
flickerPhase = 0f.max(flickerPhase - UiHandler.dt)
|
||||
boomPhase = -1
|
||||
|
||||
for (item <- selfDestructingCards) {
|
||||
if (item.card.time > 0) {
|
||||
// If multiple SDCs are ticking, let the most soon exploding one to define the glow
|
||||
boomPhase = boomPhase.max(1 - item.card.time.toFloat / item.card.initialTime)
|
||||
|
||||
if (item.card.lastBeepTime < 0 || item.card.lastBeepTime - item.card.time >= 20) {
|
||||
countdownBeepSound.play()
|
||||
item.card.lastBeepTime = item.card.time
|
||||
flickerPhase = FlickerDuty
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override def update(): Unit = {
|
||||
super.update()
|
||||
updateBoomCardState()
|
||||
}
|
||||
|
||||
private def expandFactor(phase: Float): Float = {
|
||||
math.sin(2 * math.Pi * phase).toFloat
|
||||
}
|
||||
|
||||
override def drawLight(g: Graphics): Unit = {
|
||||
super.drawLight(g)
|
||||
|
||||
if (boomPhase > 0) {
|
||||
val expand = expandFactor(phase)
|
||||
val glowSize = MinSize.lerp(MaxSize, boomPhase + ExpandIntensity * expand)
|
||||
val alpha = boomPhase * GlowAlpha
|
||||
|
||||
if (flickerPhase >= 0.01) {
|
||||
g.rect(bounds, Color.White.withAlpha(FlickerAlpha * (1 - boomPhase)))
|
||||
}
|
||||
|
||||
g.sprite(
|
||||
IconSource.Nodes.LampGlow,
|
||||
position - size * glowSize,
|
||||
size * (1 + 2 * glowSize),
|
||||
ColorScheme("BoomCardGlowStart").lerp(ColorScheme("BoomCardGlowEnd"), boomPhase).withAlpha(alpha),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object BoomCardFxHandler {
|
||||
private val ExpandPeriod = 1f
|
||||
private val ExpandIntensity = 0.05f
|
||||
private val FlickerDuty = 0.33f
|
||||
private val FlickerAlpha = 0.05f
|
||||
private val MinSize = 0.1f
|
||||
private val MaxSize = 0.5f
|
||||
private val GlowAlpha = 0.4f
|
||||
}
|
||||
@ -1,9 +1,6 @@
|
||||
package ocelot.desktop.node
|
||||
|
||||
import ocelot.desktop.OcelotDesktop
|
||||
import ocelot.desktop.{Settings => DesktopSettings}
|
||||
import ocelot.desktop.audio._
|
||||
import ocelot.desktop.geometry.Vector2D
|
||||
import ocelot.desktop.graphics.{Graphics, IconSource}
|
||||
import ocelot.desktop.inventory.SyncedInventory
|
||||
import ocelot.desktop.node.ComputerAwareNode._
|
||||
@ -11,85 +8,39 @@ import ocelot.desktop.node.Node.Size
|
||||
import ocelot.desktop.ui.UiHandler
|
||||
import ocelot.desktop.ui.event.BrainEvent
|
||||
import ocelot.desktop.ui.event.handlers.DiskActivityHandler
|
||||
import ocelot.desktop.ui.widget.ComputerErrorMessageLabel
|
||||
import ocelot.desktop.ui.particle.Particle
|
||||
import ocelot.desktop.util.Messages
|
||||
import totoro.ocelot.brain.Settings
|
||||
import ocelot.desktop.{ColorScheme, Settings => DesktopSettings}
|
||||
import totoro.ocelot.brain.entity.traits.{Entity, Environment, WorkspaceAware}
|
||||
import totoro.ocelot.brain.event._
|
||||
|
||||
import java.util.Calendar
|
||||
import scala.collection.mutable.ArrayBuffer
|
||||
|
||||
abstract class ComputerAwareNode(entity: Entity with Environment with WorkspaceAware)
|
||||
extends EntityNode(entity)
|
||||
with SyncedInventory
|
||||
with DiskActivityHandler
|
||||
with OcelotLogParticleNode
|
||||
with BoomCardFxHandler
|
||||
with ShiftClickNode {
|
||||
|
||||
// access should be synchronized because messages are added in the update thread
|
||||
private val messages = ArrayBuffer.empty[(Float, ComputerErrorMessageLabel)]
|
||||
|
||||
private def addErrorMessage(message: ComputerErrorMessageLabel): Unit = messages.synchronized {
|
||||
messages += ((0f, message))
|
||||
}
|
||||
|
||||
private lazy val soundCardSounds: (SoundStream, SoundSource) = Audio.newStream(SoundCategory.Records)
|
||||
private def soundCardStream: SoundStream = soundCardSounds._1
|
||||
private def soundCardSource: SoundSource = soundCardSounds._2
|
||||
|
||||
eventHandlers += {
|
||||
case BrainEvent(event: MachineCrashEvent) =>
|
||||
val message = Messages.lift(event.message) match {
|
||||
case Some(message) =>
|
||||
logger.info(s"[EVENT] Machine crash (address = ${event.address})! Message code ${event.message}: $message")
|
||||
message
|
||||
|
||||
case None =>
|
||||
logger.info(s"[EVENT] Machine crash (address = ${event.address})! Message: ${event.message}")
|
||||
event.message
|
||||
}
|
||||
|
||||
addErrorMessage(new ComputerErrorMessageLabel(this, message))
|
||||
UiHandler.root.workspaceView.particleSystem.add(new ErrorMessageParticle(message))
|
||||
|
||||
case BrainEvent(event: BeepEvent) if !Audio.isDisabled =>
|
||||
BeepGenerator.newBeep(".", event.frequency, event.duration).play()
|
||||
|
||||
case BrainEvent(event: BeepPatternEvent) if !Audio.isDisabled =>
|
||||
BeepGenerator.newBeep(event.pattern, 1000, 200).play()
|
||||
|
||||
case BrainEvent(event: SoundCardAudioEvent) if !Audio.isDisabled =>
|
||||
val samples = SoundSamples(event.data, Settings.get.soundCardSampleRate, SoundSamples.Format.Mono8)
|
||||
soundCardStream.enqueue(samples)
|
||||
soundCardSource.volume = event.volume
|
||||
|
||||
case BrainEvent(_: SelfDestructingCardBoomEvent) =>
|
||||
OcelotDesktop.updateThreadTasks.add(() => {
|
||||
SoundSource.MinecraftExplosion.play()
|
||||
destroy()
|
||||
})
|
||||
}
|
||||
|
||||
override def update(): Unit = {
|
||||
messages.synchronized {
|
||||
messages.mapInPlace { case (t, message) => (t + ErrorMessageMoveSpeed * UiHandler.dt, message) }
|
||||
messages.filterInPlace(_._1 <= 1f)
|
||||
}
|
||||
|
||||
super.update()
|
||||
}
|
||||
|
||||
private def drawMessageParticles(g: Graphics): Unit = messages.synchronized {
|
||||
for ((time, message) <- messages.reverseIterator) {
|
||||
message.position = message.initialPosition + Vector2D(0, -MaxErrorMessageDistance * time)
|
||||
message.alpha = 1 - time
|
||||
message.draw(g)
|
||||
}
|
||||
}
|
||||
|
||||
override def drawParticles(g: Graphics): Unit = {
|
||||
super.drawParticles(g)
|
||||
drawMessageParticles(g)
|
||||
}
|
||||
|
||||
protected def drawOverlay(g: Graphics): Unit = HolidayIcon match {
|
||||
@ -110,9 +61,19 @@ abstract class ComputerAwareNode(entity: Entity with Environment with WorkspaceA
|
||||
|
||||
override def draw(g: Graphics): Unit = {
|
||||
super.draw(g)
|
||||
|
||||
drawOverlay(g)
|
||||
}
|
||||
|
||||
private class ErrorMessageParticle(message: String) extends Particle(speed = ErrorMessageMoveSpeed) {
|
||||
private val offsetX = size.width / 2 - message.length * 4
|
||||
private val offsetY = -8
|
||||
override def draw(g: Graphics): Unit = {
|
||||
g.setSmallFont()
|
||||
g.foreground = ColorScheme("ErrorMessage").withAlpha(1 - (2 * time - 1).min(1).max(0))
|
||||
g.text(position.x + offsetX, position.y + offsetY - MaxErrorMessageDistance * time, message)
|
||||
g.setNormalFont()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object ComputerAwareNode {
|
||||
|
||||
@ -35,7 +35,7 @@ abstract class EntityNode(val entity: Entity with Environment) extends Node {
|
||||
override def setupContextMenu(menu: ContextMenu, event: ClickEvent): Unit = {
|
||||
if (exposeAddress && entity.node != null && entity.node.address != null) {
|
||||
menu.addEntry(
|
||||
ContextMenuEntry("Copy address", IconSource.Copy) {
|
||||
ContextMenuEntry("Copy address", IconSource.Icons.Copy) {
|
||||
UiHandler.clipboard = entity.node.address
|
||||
}
|
||||
)
|
||||
|
||||
@ -26,7 +26,7 @@ trait LabeledNode extends Node {
|
||||
}
|
||||
|
||||
override def setupContextMenu(menu: ContextMenu, event: ClickEvent): Unit = {
|
||||
menu.addEntry(ContextMenuEntry("Set label", IconSource.Label) {
|
||||
menu.addEntry(ContextMenuEntry("Set label", IconSource.Icons.Label) {
|
||||
new InputDialog(
|
||||
"Set label",
|
||||
text => {
|
||||
@ -40,6 +40,8 @@ trait LabeledNode extends Node {
|
||||
}
|
||||
|
||||
override def drawLabel(g: Graphics): Unit = {
|
||||
super.drawLabel(g)
|
||||
|
||||
for (label <- label) {
|
||||
g.setSmallFont()
|
||||
g.background = RGBAColor(0, 0, 0, 0)
|
||||
|
||||
@ -14,6 +14,7 @@ import ocelot.desktop.util.Persistable
|
||||
import ocelot.desktop.util.animation.ColorAnimation
|
||||
import totoro.ocelot.brain.nbt.NBTTagCompound
|
||||
import totoro.ocelot.brain.network
|
||||
import totoro.ocelot.brain.util.Direction.Direction
|
||||
|
||||
import scala.collection.mutable.ArrayBuffer
|
||||
|
||||
@ -89,12 +90,12 @@ abstract class Node extends Widget with MouseHandler with HoverHandler with Pers
|
||||
|
||||
def setupContextMenu(menu: ContextMenu, event: ClickEvent): Unit = {
|
||||
if (ports.nonEmpty) {
|
||||
menu.addEntry(ContextMenuEntry("Disconnect", IconSource.LinkSlash, SoundSource.InterfaceClickLow) {
|
||||
menu.addEntry(ContextMenuEntry("Disconnect", IconSource.Icons.LinkSlash, SoundSource.InterfaceClickLow) {
|
||||
disconnectFromAll()
|
||||
})
|
||||
}
|
||||
|
||||
menu.addEntry(ContextMenuEntry("Remove", IconSource.Delete, SoundSource.InterfaceClickLow) {
|
||||
menu.addEntry(ContextMenuEntry("Remove", IconSource.Icons.Delete, SoundSource.InterfaceClickLow) {
|
||||
destroy()
|
||||
})
|
||||
}
|
||||
@ -103,11 +104,11 @@ abstract class Node extends Widget with MouseHandler with HoverHandler with Pers
|
||||
super.update()
|
||||
|
||||
if (isHovered || isMoving) {
|
||||
root.get.statusBar.addMouseEntry("icons/RMB", "Menu")
|
||||
root.get.statusBar.addMouseEntry("icons/DragLMB", "Move node")
|
||||
root.get.statusBar.addMouseEntry(IconSource.Icons.RMB, "Menu")
|
||||
root.get.statusBar.addMouseEntry(IconSource.Icons.DragLMB, "Move node")
|
||||
|
||||
if (ports.nonEmpty) {
|
||||
root.get.statusBar.addMouseEntry("icons/DragRMB", "Connect/Disconnect")
|
||||
root.get.statusBar.addMouseEntry(IconSource.Icons.DragRMB, "Connect/Disconnect")
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -118,7 +119,7 @@ abstract class Node extends Widget with MouseHandler with HoverHandler with Pers
|
||||
super.dispose()
|
||||
}
|
||||
|
||||
def iconSource: IconSource = IconSource.NA
|
||||
def icon: IconSource = IconSource.Icons.NA
|
||||
|
||||
def iconColor: Color = RGBAColor(255, 255, 255)
|
||||
|
||||
@ -126,6 +127,8 @@ abstract class Node extends Widget with MouseHandler with HoverHandler with Pers
|
||||
|
||||
def getNodeByPort(port: NodePort): network.Node = throw new IllegalArgumentException("this node has no ports")
|
||||
|
||||
def rotatable: Boolean
|
||||
|
||||
def connections: Iterator[(NodePort, Node, NodePort)] = _connections.iterator
|
||||
|
||||
def connect(portA: NodePort, node: Node, portB: NodePort): Unit = {
|
||||
@ -260,29 +263,51 @@ abstract class Node extends Widget with MouseHandler with HoverHandler with Pers
|
||||
drawHighlight(g)
|
||||
|
||||
g.sprite(
|
||||
iconSource.path,
|
||||
icon,
|
||||
position.x + HighlightThickness,
|
||||
position.y + HighlightThickness,
|
||||
size.width - HighlightThickness * 2,
|
||||
size.height - HighlightThickness * 2,
|
||||
iconColor,
|
||||
iconSource.animation,
|
||||
)
|
||||
}
|
||||
|
||||
private def drawPortLegend(g: Graphics): Unit = {
|
||||
if (highlight.color.a < 0.001) return
|
||||
|
||||
g.setSmallFont()
|
||||
g.background = PortLegendBg.mapA(_ * highlight.color.a)
|
||||
|
||||
val sidedPorts = ports.iterator.filter(_.direction.isDefined).toSeq.sorted
|
||||
val legendHeight = sidedPorts.size * 8
|
||||
val startY = position.y + (height - legendHeight) / 2
|
||||
|
||||
for ((port, line) <- sidedPorts.iterator.zipWithIndex) {
|
||||
g.foreground = port.getColor.toRGBANorm.mapA(_ * highlight.color.a)
|
||||
g.text(bounds.max.x + HoverInfoMargin, startY + line * 8, directionLabel(port.direction.get), shrink = 1)
|
||||
}
|
||||
|
||||
g.setNormalFont()
|
||||
}
|
||||
|
||||
def drawLight(g: Graphics): Unit = {}
|
||||
|
||||
def drawLabel(g: Graphics): Unit = {}
|
||||
|
||||
def drawParticles(g: Graphics): Unit = {}
|
||||
def drawLabel(g: Graphics): Unit = {
|
||||
drawPortLegend(g)
|
||||
}
|
||||
|
||||
def drawPorts(g: Graphics): Unit = {
|
||||
for ((port, rects) <- portsBounds) {
|
||||
val color = port.getColor
|
||||
for (rect <- rects)
|
||||
for (rect <- rects) {
|
||||
g.rect(rect, color)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def directionLabel(direction: Direction): String = {
|
||||
(if (rotatable) direction.side else direction.cardinal).capitalize
|
||||
}
|
||||
}
|
||||
|
||||
object Node {
|
||||
@ -290,9 +315,12 @@ object Node {
|
||||
protected val HoveredHighlight: RGBAColor = RGBAColor(160, 160, 160)
|
||||
protected val NoHighlight: RGBAColor = RGBAColor(160, 160, 160, 0)
|
||||
|
||||
private val PortLegendBg = Color.Black.withAlpha(0.8f)
|
||||
|
||||
val TexelCount = 16f
|
||||
val Scale = 4f
|
||||
val HighlightThickness = 2f
|
||||
private val HoverInfoMargin = 9f
|
||||
|
||||
val NoHighlightSize: Float = TexelCount * Scale
|
||||
val Size: Float = NoHighlightSize + HighlightThickness * 2f
|
||||
|
||||
@ -2,6 +2,7 @@ package ocelot.desktop.node
|
||||
|
||||
import ocelot.desktop.ColorScheme
|
||||
import ocelot.desktop.color.Color
|
||||
import ocelot.desktop.graphics.IconSource
|
||||
import totoro.ocelot.brain.util.Direction
|
||||
|
||||
case class NodePort(direction: Option[Direction.Value] = None) extends Ordered[NodePort] {
|
||||
@ -15,6 +16,11 @@ case class NodePort(direction: Option[Direction.Value] = None) extends Ordered[N
|
||||
case _ => ColorScheme("PortAny")
|
||||
}
|
||||
|
||||
def getIcon: IconSource = direction match {
|
||||
case Some(direction) => IconSource.Icons.Side(direction)
|
||||
case None => IconSource.Icons.SideAny
|
||||
}
|
||||
|
||||
override def compare(that: NodePort): Int = this.direction.compare(that.direction)
|
||||
|
||||
def toByte: Byte = direction match {
|
||||
|
||||
@ -2,7 +2,7 @@ package ocelot.desktop.node
|
||||
|
||||
import ocelot.desktop.color.Color
|
||||
import ocelot.desktop.geometry.Size2D
|
||||
import ocelot.desktop.graphics.Graphics
|
||||
import ocelot.desktop.graphics.{Graphics, IconSource}
|
||||
import ocelot.desktop.node.Node.Size
|
||||
import ocelot.desktop.ui.event.handlers.{HoverHandler, MouseHandler}
|
||||
import ocelot.desktop.ui.event.{ClickEvent, HoverEvent, MouseEvent}
|
||||
@ -39,20 +39,19 @@ class NodeTypeWidget(val nodeType: NodeType) extends Widget with MouseHandler wi
|
||||
val size = Spritesheet.spriteSize(nodeType.icon) * 4
|
||||
|
||||
g.sprite(
|
||||
nodeType.icon.path,
|
||||
nodeType.icon,
|
||||
position.x + Size / 2 - size.width / 2,
|
||||
position.y + Size / 2 - size.height / 2,
|
||||
size.width,
|
||||
size.height,
|
||||
nodeType.tier.map(TierColor.get).getOrElse(Color.White),
|
||||
nodeType.icon.animation,
|
||||
)
|
||||
}
|
||||
|
||||
override def update(): Unit = {
|
||||
super.update()
|
||||
if (isHovered) {
|
||||
root.get.statusBar.addMouseEntry("icons/LMB", "Add node")
|
||||
root.get.statusBar.addMouseEntry(IconSource.Icons.LMB, "Add node")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,62 +8,53 @@ import ocelot.desktop.graphics.Graphics
|
||||
import ocelot.desktop.node.OcelotLogParticleNode._
|
||||
import ocelot.desktop.ui.UiHandler
|
||||
import ocelot.desktop.ui.event.BrainEvent
|
||||
import ocelot.desktop.ui.particle.Particle
|
||||
|
||||
import scala.collection.mutable
|
||||
import scala.util.Random
|
||||
|
||||
trait OcelotLogParticleNode extends Node {
|
||||
private case class LogParticle(
|
||||
var time: Float = -LogParticleGrow,
|
||||
angle: Float = Random.between(0f, 2 * math.Pi.toFloat * LogParticleMaxAngle),
|
||||
)
|
||||
|
||||
// access should be synchronized because log particles are added in the update thread
|
||||
private val logParticles = mutable.ArrayDeque.empty[LogParticle]
|
||||
|
||||
private def addLogParticle(): Unit = logParticles.synchronized {
|
||||
if (logParticles.length < MaxLogParticles) {
|
||||
logParticles += LogParticle()
|
||||
}
|
||||
}
|
||||
private var queuedParticles: Int = 0
|
||||
|
||||
eventHandlers += {
|
||||
case BrainEvent(OcelotInterface.LogEvent.CardToUser(_, _)) =>
|
||||
addLogParticle()
|
||||
queuedParticles += 1
|
||||
}
|
||||
|
||||
override def update(): Unit = {
|
||||
super.update()
|
||||
|
||||
logParticles.synchronized {
|
||||
logParticles.foreach(particle => particle.time += LogParticleMoveSpeed * UiHandler.dt)
|
||||
logParticles.filterInPlace(_.time <= 1f)
|
||||
}
|
||||
spawnParticles()
|
||||
}
|
||||
|
||||
private def drawLogParticles(g: Graphics): Unit = logParticles.synchronized {
|
||||
for (particle <- logParticles) {
|
||||
val size = (1 + particle.time / LogParticleGrow).clamp() * LogParticleSize
|
||||
val offset = particle.time.clamp() * LogParticleMoveDistance
|
||||
val alpha = 1 - particle.time.clamp()
|
||||
private def spawnParticles(): Unit = {
|
||||
val system = UiHandler.root.workspaceView.particleSystem
|
||||
val toSpawn = queuedParticles min (MaxLogParticles - system.count[LogParticle](Some(this))) max 0
|
||||
|
||||
val r1 = (bounds.w max bounds.h) / math.sqrt(2) + offset + LogParticlePadding
|
||||
for (_ <- 0 until toSpawn) {
|
||||
system.add(new LogParticle)
|
||||
}
|
||||
|
||||
queuedParticles = 0
|
||||
}
|
||||
|
||||
private class LogParticle extends Particle(time = -LogParticleGrow, speed = LogParticleMoveSpeed, origin = Some(this)) {
|
||||
private val angle: Float = Random.between(0f, 2 * math.Pi.toFloat * LogParticleMaxAngle)
|
||||
override def draw(g: Graphics): Unit = {
|
||||
val size = (1 + time / LogParticleGrow).clamped() * LogParticleSize
|
||||
val offset = time.clamped() * LogParticleMoveDistance
|
||||
val alpha = 1 - time.clamped()
|
||||
|
||||
val r1 = (bounds.w max bounds.h) / math.sqrt(2).toFloat + offset + LogParticlePadding
|
||||
val r2 = r1 + size
|
||||
|
||||
for (i <- 0 until LogParticleCount) {
|
||||
val angle = particle.angle + (2 * math.Pi).toFloat * i / LogParticleCount
|
||||
val v = Vector2D.unit(angle)
|
||||
val a = angle + (2 * math.Pi).toFloat * i / LogParticleCount
|
||||
val v = Vector2D.unit(a)
|
||||
val p1 = v * r1 + bounds.center
|
||||
val p2 = v * r2 + bounds.center
|
||||
g.line(p1, p2, 1f, ColorScheme("LogParticle").mapA(_ => alpha))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override def drawParticles(g: Graphics): Unit = {
|
||||
super.drawParticles(g)
|
||||
drawLogParticles(g)
|
||||
}
|
||||
}
|
||||
|
||||
object OcelotLogParticleNode {
|
||||
|
||||
@ -4,18 +4,22 @@ import ocelot.desktop.audio.SoundSource
|
||||
import ocelot.desktop.geometry.Vector3D
|
||||
import ocelot.desktop.{OcelotDesktop, Settings}
|
||||
|
||||
/**
|
||||
* Updates sound sources' position depending on where the camera is.
|
||||
*
|
||||
* @note OpenAL only applies positioning to mono sources!
|
||||
* If your source is stereo, this trait will have no audible effect.
|
||||
*/
|
||||
trait PositionalSoundSourcesNode extends Node {
|
||||
// Every node can have multiple sound sources playing at the same time
|
||||
def soundSources: Seq[SoundSource]
|
||||
def soundSources: Seq[SoundSource] = Seq()
|
||||
|
||||
override def update(): Unit = {
|
||||
super.update()
|
||||
|
||||
var soundPosition: Vector3D = null
|
||||
|
||||
// Calculating position of sound source relative to center of workspace
|
||||
// but only if corresponding setting is enabled
|
||||
if (Settings.get.soundPositional) {
|
||||
val soundPosition = if (Settings.get.soundPositional) {
|
||||
val rootWidthHalf = OcelotDesktop.root.width / 2
|
||||
val rootHeightHalf = OcelotDesktop.root.height / 2
|
||||
|
||||
@ -26,13 +30,13 @@ trait PositionalSoundSourcesNode extends Node {
|
||||
// large monitors the sound may become too "non-audiophile"
|
||||
val limit = 0.05f
|
||||
|
||||
soundPosition = Vector3D(
|
||||
Vector3D(
|
||||
(nodeCenterX - rootWidthHalf) / rootWidthHalf * limit,
|
||||
(nodeCenterY - rootHeightHalf) / rootHeightHalf * limit,
|
||||
0,
|
||||
)
|
||||
} else {
|
||||
soundPosition = Vector3D.Zero
|
||||
Vector3D.Zero
|
||||
}
|
||||
|
||||
for (soundSource <- soundSources)
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
package ocelot.desktop.node
|
||||
|
||||
import ocelot.desktop.graphics.IconSource
|
||||
import ocelot.desktop.ui.event.sources.KeyEvents
|
||||
import ocelot.desktop.ui.event.{ClickEvent, MouseEvent}
|
||||
|
||||
@ -22,6 +23,6 @@ trait ShiftClickNode extends Node {
|
||||
super.update()
|
||||
|
||||
if (isHovered || isMoving)
|
||||
root.get.statusBar.addKeyMouseEntry("icons/LMB", "SHIFT", hoveredShiftStatusBarText)
|
||||
root.get.statusBar.addKeyMouseEntry(IconSource.Icons.LMB, "SHIFT", hoveredShiftStatusBarText)
|
||||
}
|
||||
}
|
||||
|
||||
79
src/main/scala/ocelot/desktop/node/SmokeParticleNode.scala
Normal file
@ -0,0 +1,79 @@
|
||||
package ocelot.desktop.node
|
||||
|
||||
import ocelot.desktop.color.RGBAColorNorm
|
||||
import ocelot.desktop.geometry.{Size2D, Vector2D}
|
||||
import ocelot.desktop.graphics.{Graphics, IconSource}
|
||||
import ocelot.desktop.node.SmokeParticleNode._
|
||||
import ocelot.desktop.ui.UiHandler
|
||||
import ocelot.desktop.ui.particle.Particle
|
||||
import ocelot.desktop.util.Spritesheet
|
||||
|
||||
import scala.util.Random
|
||||
|
||||
trait SmokeParticleNode extends Node {
|
||||
protected def emitSmoke(): Unit = synchronized {
|
||||
for (_ <- 1 to randomCount) {
|
||||
UiHandler.root.workspaceView.particleSystem.add(new SmokeParticle)
|
||||
}
|
||||
}
|
||||
|
||||
private class SmokeParticle extends Particle(ttl = randomDuration) {
|
||||
private val color: RGBAColorNorm = randomColor
|
||||
private var velocity: Vector2D = Vector2D(randomVelocityComponent, randomVelocityComponent)
|
||||
private var offset: Vector2D = Vector2D(0, 0)
|
||||
|
||||
override def update(dt: Float): Unit = {
|
||||
time += dt
|
||||
offset += (velocity + SmokeParticleVolatilizationSpeed) * dt * speed
|
||||
velocity *= math.pow(SmokeParticleVelocityDamping, dt).toFloat
|
||||
}
|
||||
|
||||
override def draw(g: Graphics): Unit = {
|
||||
val spriteRect = Spritesheet.sprites(IconSource.Particles.Smoke.path)
|
||||
|
||||
val animationFrameCount = spriteRect.h / spriteRect.w
|
||||
val animationFrame = (time / ttl * animationFrameCount).toInt
|
||||
|
||||
val particlePosition = bounds.center + offset - SmokeParticleSize.toVector * .5f
|
||||
|
||||
g.sprite(
|
||||
IconSource.Particles.Smoke.path,
|
||||
particlePosition.x,
|
||||
particlePosition.y,
|
||||
SmokeParticleSize.width,
|
||||
SmokeParticleSize.height,
|
||||
color,
|
||||
Some(spriteRect.copy(
|
||||
y = spriteRect.y + animationFrame * spriteRect.w,
|
||||
h = spriteRect.w,
|
||||
)),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private object SmokeParticleNode {
|
||||
private final val SmokeParticleSize: Size2D = Size2D(32, 32)
|
||||
private final val SmokeParticleVelocityRange: Float = 300
|
||||
private final val SmokeParticleVolatilizationSpeed: Vector2D = Vector2D(0, -50)
|
||||
private final val SmokeParticleVelocityDamping: Float = .1f
|
||||
private final val SmokeParticleCount: (Int, Int) = (10, 20)
|
||||
private final val SmokeParticleAnimationDuration: (Float, Float) = (1f, 4f)
|
||||
|
||||
private def randomVelocityComponent: Float = Random.between(
|
||||
-SmokeParticleVelocityRange,
|
||||
SmokeParticleVelocityRange,
|
||||
)
|
||||
private def randomColor: RGBAColorNorm = {
|
||||
val channel = Random.between(.5f, .9f)
|
||||
RGBAColorNorm(channel, channel, channel)
|
||||
}
|
||||
private def randomDuration: Float = Random.between(
|
||||
SmokeParticleAnimationDuration._1,
|
||||
SmokeParticleAnimationDuration._2,
|
||||
)
|
||||
private def randomCount: Int = Random.between(
|
||||
SmokeParticleCount._1,
|
||||
SmokeParticleCount._2,
|
||||
)
|
||||
}
|
||||
@ -1,9 +1,9 @@
|
||||
package ocelot.desktop.node
|
||||
|
||||
import ocelot.desktop.graphics.IconSource
|
||||
import ocelot.desktop.ui.event.sources.KeyEvents
|
||||
import ocelot.desktop.ui.event.{ClickEvent, MouseEvent}
|
||||
import ocelot.desktop.ui.widget.window.{Window, Windowed}
|
||||
import org.lwjgl.input.Keyboard
|
||||
|
||||
trait WindowedNode[T <: Window] extends Node with Windowed[T] {
|
||||
override def dispose(): Unit = {
|
||||
@ -14,7 +14,7 @@ trait WindowedNode[T <: Window] extends Node with Windowed[T] {
|
||||
|
||||
override def update(): Unit = {
|
||||
if (isHovered || isMoving)
|
||||
root.get.statusBar.addMouseEntry("icons/LMB", if (windowCreated && window.isOpen) "Close" else "Open")
|
||||
root.get.statusBar.addMouseEntry(IconSource.Icons.LMB, if (windowCreated && window.isOpen) "Close" else "Open")
|
||||
|
||||
super.update()
|
||||
}
|
||||
|
||||
@ -6,7 +6,9 @@ import ocelot.desktop.node.EntityNode
|
||||
import totoro.ocelot.brain.entity.Cable
|
||||
|
||||
class CableNode(val cable: Cable) extends EntityNode(cable) {
|
||||
override def iconSource: IconSource = IconSource.Nodes.Cable
|
||||
override def icon: IconSource = IconSource.Nodes.Cable
|
||||
|
||||
override def rotatable: Boolean = false
|
||||
|
||||
override def minimumSize: Size2D = Size2D(36, 36)
|
||||
|
||||
|
||||
@ -6,7 +6,9 @@ import ocelot.desktop.node.{EntityNode, LabeledEntityNode, WindowedNode}
|
||||
import ocelot.desktop.windows.CameraWindow
|
||||
|
||||
class CameraNode(val camera: Camera) extends EntityNode(camera) with LabeledEntityNode with WindowedNode[CameraWindow] {
|
||||
override def iconSource: IconSource = IconSource.Nodes.Camera
|
||||
override def icon: IconSource = IconSource.Nodes.Camera
|
||||
|
||||
override def rotatable: Boolean = true
|
||||
|
||||
override def createWindow(): CameraWindow = new CameraWindow(this)
|
||||
}
|
||||
|
||||
@ -10,7 +10,9 @@ import ocelot.desktop.windows.ChestWindow
|
||||
class ChestNode extends LabeledNode with WindowedNode[ChestWindow] with PersistedInventory {
|
||||
override type I = Item with PersistableItem
|
||||
|
||||
override def iconSource: IconSource = IconSource.Nodes.Chest
|
||||
override def icon: IconSource = IconSource.Nodes.Chest
|
||||
|
||||
override def rotatable: Boolean = false
|
||||
|
||||
override def minimumSize: Size2D = Size2D(28, 30) * 2 + 4
|
||||
|
||||
|
||||
@ -10,6 +10,8 @@ class ColorfulLampNode(val lamp: ColorfulLamp) extends EntityNode(lamp) with Lab
|
||||
private var lastColor: RGBAColor = RGBAColor(0, 0, 0)
|
||||
private var mouseHover: Boolean = false
|
||||
|
||||
override def rotatable: Boolean = false
|
||||
|
||||
override def label: Option[String] = super.label.filter(_ => mouseHover)
|
||||
|
||||
override def draw(g: Graphics): Unit = {
|
||||
@ -22,12 +24,12 @@ class ColorfulLampNode(val lamp: ColorfulLamp) extends EntityNode(lamp) with Lab
|
||||
)
|
||||
|
||||
g.rect(position.x + 2, position.y + 2, size.width - 4, size.height - 4, lastColor)
|
||||
g.sprite(IconSource.Nodes.Lamp.Frame, position.x + 2, position.y + 2, size.width - 4, size.height - 4)
|
||||
g.sprite(IconSource.Nodes.LampFrame, position.x + 2, position.y + 2, size.width - 4, size.height - 4)
|
||||
}
|
||||
|
||||
override def drawLight(g: Graphics): Unit = {
|
||||
super.drawLight(g)
|
||||
g.sprite(IconSource.Nodes.Lamp.Glow, position - size / 2, size * 2, lastColor)
|
||||
g.sprite(IconSource.Nodes.LampGlow, position - size / 2, size * 2, lastColor)
|
||||
}
|
||||
|
||||
eventHandlers += {
|
||||
|
||||
@ -2,6 +2,7 @@ package ocelot.desktop.node.nodes
|
||||
|
||||
import ocelot.desktop.color.Color
|
||||
import ocelot.desktop.graphics.{Graphics, IconSource}
|
||||
import ocelot.desktop.inventory.item.SelfDestructingCardItem
|
||||
import ocelot.desktop.node.Node.HighlightThickness
|
||||
import ocelot.desktop.node.{ComputerAwareNode, WindowedNode}
|
||||
import ocelot.desktop.ui.event.ClickEvent
|
||||
@ -15,10 +16,15 @@ import totoro.ocelot.brain.entity.traits.Inventory
|
||||
import totoro.ocelot.brain.util.Tier
|
||||
|
||||
class ComputerNode(val computerCase: Case)
|
||||
extends ComputerAwareNode(computerCase) with AudibleComputerAware with WindowedNode[ComputerWindow] {
|
||||
override val iconSource: IconSource = IconSource.Nodes.Computer.Default
|
||||
extends ComputerAwareNode(computerCase)
|
||||
with AudibleComputerAware
|
||||
with WindowedNode[ComputerWindow] {
|
||||
|
||||
override val icon: IconSource = IconSource.Nodes.Computer.Default
|
||||
override def iconColor: Color = TierColor.get(computerCase.tier)
|
||||
|
||||
override def rotatable: Boolean = true
|
||||
|
||||
override def setupContextMenu(menu: ContextMenu, event: ClickEvent): Unit = {
|
||||
addPowerContextMenuEntries(menu)
|
||||
addTierContextMenuEntries(menu)
|
||||
@ -28,6 +34,12 @@ class ComputerNode(val computerCase: Case)
|
||||
super.setupContextMenu(menu, event)
|
||||
}
|
||||
|
||||
override protected def selfDestructingCards: IterableOnce[SelfDestructingCardItem] = {
|
||||
inventoryIterator.flatMap(_.get).collect {
|
||||
case item: SelfDestructingCardItem => item
|
||||
}
|
||||
}
|
||||
|
||||
override def update(): Unit = {
|
||||
super.update()
|
||||
|
||||
|
||||
@ -21,7 +21,9 @@ class DiskDriveNode(entity: FloppyDiskDrive)
|
||||
with ShiftClickNode
|
||||
with WindowedNode[DiskDriveWindow] {
|
||||
|
||||
override def iconSource: IconSource = IconSource.Nodes.DiskDrive.Default
|
||||
override def icon: IconSource = IconSource.Nodes.DiskDrive.Default
|
||||
|
||||
override def rotatable: Boolean = true
|
||||
|
||||
override def setupContextMenu(menu: ContextMenu, event: ClickEvent): Unit = {
|
||||
if (isFloppyItemPresent) {
|
||||
|
||||
@ -8,7 +8,9 @@ import totoro.ocelot.brain.entity.HologramProjector
|
||||
class HologramProjectorNode(val hologramProjector: HologramProjector)
|
||||
extends EntityNode(hologramProjector) with LabeledEntityNode with WindowedNode[HologramProjectorWindow] {
|
||||
|
||||
override def iconSource: IconSource = IconSource.Nodes.HologramProjector(hologramProjector.tier)
|
||||
override def icon: IconSource = IconSource.Nodes.HologramProjector(hologramProjector.tier)
|
||||
|
||||
override def rotatable: Boolean = false
|
||||
|
||||
override def createWindow(): HologramProjectorWindow = new HologramProjectorWindow(this)
|
||||
}
|
||||
|
||||
@ -9,7 +9,9 @@ import totoro.ocelot.brain.event.{EventBus, NoteBlockTriggerEvent}
|
||||
import scala.util.Random
|
||||
|
||||
class IronNoteBlockNode(val ironNoteBlock: IronNoteBlock) extends NoteBlockNodeBase(ironNoteBlock) {
|
||||
override def iconSource: IconSource = IconSource.Nodes.IronNoteBlock
|
||||
override def icon: IconSource = IconSource.Nodes.IronNoteBlock
|
||||
|
||||
override def rotatable: Boolean = false
|
||||
|
||||
override def onClick(event: ClickEvent): Unit = {
|
||||
event match {
|
||||
|
||||
@ -20,7 +20,8 @@ class MicrocontrollerNode(val microcontroller: Microcontroller)
|
||||
with ComputerAware
|
||||
with DefaultSlotItemsFillable
|
||||
with WindowedNode[ComputerWindow] {
|
||||
override val iconSource: IconSource = IconSource.Nodes.Microcontroller.Default
|
||||
|
||||
override val icon: IconSource = IconSource.Nodes.Microcontroller.Default
|
||||
|
||||
override def setupContextMenu(menu: ContextMenu, event: ClickEvent): Unit = {
|
||||
addPowerContextMenuEntries(menu)
|
||||
@ -54,6 +55,8 @@ class MicrocontrollerNode(val microcontroller: Microcontroller)
|
||||
override def getNodeByPort(port: NodePort): network.Node =
|
||||
microcontroller.sidedNode(port.direction.get)
|
||||
|
||||
override def rotatable: Boolean = true
|
||||
|
||||
override def shouldReceiveEventsFor(address: String): Boolean = {
|
||||
address == microcontroller.machine.node.address ||
|
||||
super.shouldReceiveEventsFor(address)
|
||||
@ -65,6 +68,12 @@ class MicrocontrollerNode(val microcontroller: Microcontroller)
|
||||
override def computerType: ComputerType = ComputerType.Microcontroller
|
||||
override def brainInventory: Inventory = microcontroller.inventory.owner
|
||||
|
||||
override protected def selfDestructingCards: IterableOnce[SelfDestructingCardItem] = {
|
||||
inventoryIterator.flatMap(_.get).collect {
|
||||
case item: SelfDestructingCardItem => item
|
||||
}
|
||||
}
|
||||
|
||||
override def addSlotsBasedOnTier(): Unit = {
|
||||
microcontroller.tier match {
|
||||
case Tier.One =>
|
||||
@ -120,23 +129,26 @@ class MicrocontrollerNode(val microcontroller: Microcontroller)
|
||||
|
||||
override def fillSlotsWithDefaultItems(): Unit = {
|
||||
cardSlots(0).item = {
|
||||
if (microcontroller.tier == Tier.Two)
|
||||
if (microcontroller.tier >= Tier.Two) {
|
||||
WirelessNetworkCardItem.Tier2.Factory.build()
|
||||
else
|
||||
} else {
|
||||
WirelessNetworkCardItem.Tier1.Factory.build()
|
||||
}
|
||||
}
|
||||
|
||||
cardSlots(1).item = {
|
||||
if (microcontroller.tier == Tier.Two)
|
||||
if (microcontroller.tier == Tier.Creative) {
|
||||
RedstoneCardItem.Tier2.Factory.build()
|
||||
else
|
||||
} else {
|
||||
RedstoneCardItem.Tier1.Factory.build()
|
||||
}
|
||||
}
|
||||
|
||||
cpuSlot.item = new CpuItem.Factory(Tier.One).build()
|
||||
|
||||
for (memorySlot <- memorySlots)
|
||||
for (memorySlot <- memorySlots) {
|
||||
memorySlot.item = new MemoryItem.Factory(Tier.One.toExtended(false)).build()
|
||||
}
|
||||
|
||||
eepromSlot.item = EepromItem.Factory.Empty.build()
|
||||
}
|
||||
|
||||
@ -7,10 +7,12 @@ import totoro.ocelot.brain.entity.NoteBlock
|
||||
import totoro.ocelot.brain.event.{EventBus, NoteBlockTriggerEvent}
|
||||
|
||||
class NoteBlockNode(val noteBlock: NoteBlock) extends NoteBlockNodeBase(noteBlock) {
|
||||
override def iconSource: IconSource = IconSource.Nodes.NoteBlock
|
||||
override def icon: IconSource = IconSource.Nodes.NoteBlock
|
||||
|
||||
override def rotatable: Boolean = false
|
||||
|
||||
override def setupContextMenu(menu: ContextMenu, event: ClickEvent): Unit = {
|
||||
menu.addEntry(new ContextMenuSubmenu("Instrument", Some(ContextMenuIcon(IconSource.Guitar))) {
|
||||
menu.addEntry(new ContextMenuSubmenu("Instrument", Some(ContextMenuIcon(IconSource.Icons.Guitar))) {
|
||||
{
|
||||
val maxLen = NoteBlockNode.Instruments.map(_._2.length).max
|
||||
|
||||
|
||||
@ -3,15 +3,14 @@ package ocelot.desktop.node.nodes
|
||||
import ocelot.desktop.ColorScheme
|
||||
import ocelot.desktop.audio.{SoundBuffers, SoundCategory, SoundSource}
|
||||
import ocelot.desktop.geometry.{Size2D, Vector2D}
|
||||
import ocelot.desktop.graphics.Graphics
|
||||
import ocelot.desktop.graphics.{Graphics, IconSource}
|
||||
import ocelot.desktop.node.{EntityNode, LabeledEntityNode}
|
||||
import ocelot.desktop.ui.UiHandler
|
||||
import ocelot.desktop.ui.event.BrainEvent
|
||||
import ocelot.desktop.ui.particle.Particle
|
||||
import totoro.ocelot.brain.entity.traits.{Entity, Environment}
|
||||
import totoro.ocelot.brain.event.NoteBlockTriggerEvent
|
||||
|
||||
import scala.collection.mutable
|
||||
|
||||
abstract class NoteBlockNodeBase(entity: Entity with Environment) extends EntityNode(entity) with LabeledEntityNode {
|
||||
eventHandlers += {
|
||||
case BrainEvent(event: NoteBlockTriggerEvent) =>
|
||||
@ -22,31 +21,25 @@ abstract class NoteBlockNodeBase(entity: Entity with Environment) extends Entity
|
||||
volume = event.volume.toFloat.min(1f).max(0f),
|
||||
).play()
|
||||
|
||||
addParticle(event.pitch)
|
||||
}
|
||||
|
||||
private val particles = mutable.ArrayBuffer[(Float, Int)]()
|
||||
|
||||
private def addParticle(pitch: Int): Unit = {
|
||||
synchronized {
|
||||
particles += ((0f, pitch))
|
||||
}
|
||||
}
|
||||
|
||||
override def drawParticles(g: Graphics): Unit = synchronized {
|
||||
for ((time, pitch) <- particles.reverseIterator) {
|
||||
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)
|
||||
}
|
||||
particles.mapInPlace { case (t, p) => (t + 1.2f * UiHandler.dt, p) }
|
||||
particles.filterInPlace(_._1 <= 1f)
|
||||
UiHandler.root.workspaceView.particleSystem.add(new NoteParticle(event.pitch))
|
||||
}
|
||||
|
||||
override def update(): Unit = {
|
||||
super.update()
|
||||
if (isHovered || isMoving) {
|
||||
root.get.statusBar.addMouseEntry(IconSource.Icons.LMB, "Play sample")
|
||||
}
|
||||
}
|
||||
|
||||
if (isHovered || isMoving)
|
||||
root.get.statusBar.addMouseEntry("icons/LMB", "Play sample")
|
||||
private class NoteParticle(pitch: Int) extends Particle(speed = 1.2f) {
|
||||
override def draw(g: Graphics): Unit = {
|
||||
val col = ColorScheme("Note" + pitch.min(24).max(0)).withAlpha(1 - (2 * time - 1).min(1).max(0))
|
||||
g.sprite(
|
||||
IconSource.Particles.Note,
|
||||
position + Vector2D(pitch / 24f * 40f + 5, height / 2 - 10 - 100 * time),
|
||||
Size2D(14, 20),
|
||||
col,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -22,7 +22,9 @@ class OcelotBlockNode(val ocelot: OcelotBlock)
|
||||
|
||||
override def name: String = "Ocelot Block"
|
||||
|
||||
override def iconSource: IconSource = IconSource.Nodes.OcelotBlock.Default
|
||||
override def icon: IconSource = IconSource.Nodes.OcelotBlock.Default
|
||||
|
||||
override def rotatable: Boolean = false
|
||||
|
||||
override def ocelotInterface: OcelotInterface = ocelot
|
||||
|
||||
@ -62,17 +64,16 @@ class OcelotBlockNode(val ocelot: OcelotBlock)
|
||||
}
|
||||
|
||||
private def drawActivity(g: Graphics, icon: IconSource, lastActivity: Long, currentTime: Long): Unit = {
|
||||
val alpha = (1 - (currentTime - lastActivity) / ActivityFadeOutMs).clamp()
|
||||
val alpha = (1 - (currentTime - lastActivity) / ActivityFadeOutMs).clamped()
|
||||
|
||||
if (alpha > 0) {
|
||||
g.sprite(
|
||||
icon.path,
|
||||
icon,
|
||||
position.x + HighlightThickness,
|
||||
position.y + HighlightThickness,
|
||||
size.width - HighlightThickness * 2,
|
||||
size.height - HighlightThickness * 2,
|
||||
RGBAColorNorm(1f, 1f, 1f, alpha),
|
||||
icon.animation,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -9,7 +9,9 @@ import ocelot.desktop.windows.OpenFMRadioWindow
|
||||
|
||||
class OpenFMRadioNode(val openFMRadio: OpenFMRadio)
|
||||
extends EntityNode(openFMRadio) with LabeledEntityNode with WindowedNode[OpenFMRadioWindow] {
|
||||
override def iconSource: IconSource = IconSource.Nodes.OpenFMRadio
|
||||
override def icon: IconSource = IconSource.Nodes.OpenFMRadio
|
||||
|
||||
override def rotatable: Boolean = true
|
||||
|
||||
private var lastTick = OcelotDesktop.ticker.tick
|
||||
private var animationIndex = 0
|
||||
|
||||
@ -3,7 +3,7 @@ package ocelot.desktop.node.nodes
|
||||
import ocelot.desktop.geometry.{Rect2D, Size2D, Vector2D}
|
||||
import ocelot.desktop.graphics.{Graphics, IconSource}
|
||||
import ocelot.desktop.inventory.Item
|
||||
import ocelot.desktop.inventory.item.{DiskDriveMountableItem, ServerItem}
|
||||
import ocelot.desktop.inventory.item.{DiskDriveMountableItem, SelfDestructingCardItem, ServerItem}
|
||||
import ocelot.desktop.inventory.traits.RackMountableItem
|
||||
import ocelot.desktop.node.Node.{HighlightThickness, NoHighlightSize, Size, TexelCount}
|
||||
import ocelot.desktop.node.{ComputerAwareNode, NodePort, WindowedNode}
|
||||
@ -18,7 +18,7 @@ import totoro.ocelot.brain.network
|
||||
import totoro.ocelot.brain.util.Direction
|
||||
|
||||
class RackNode(val rack: Rack) extends ComputerAwareNode(rack) with WindowedNode[RackWindow] {
|
||||
override val iconSource: IconSource = IconSource.Nodes.Rack.Empty
|
||||
override val icon: IconSource = IconSource.Nodes.Rack.Empty
|
||||
override def exposeAddress = false
|
||||
|
||||
override def ports: Array[NodePort] = Array(
|
||||
@ -31,6 +31,8 @@ class RackNode(val rack: Rack) extends ComputerAwareNode(rack) with WindowedNode
|
||||
|
||||
override def getNodeByPort(port: NodePort): network.Node = rack.sidedNode(port.direction.get)
|
||||
|
||||
override def rotatable: Boolean = true
|
||||
|
||||
override def shouldReceiveEventsFor(address: String): Boolean = {
|
||||
super.shouldReceiveEventsFor(address) ||
|
||||
rack.inventory.entities.exists {
|
||||
@ -43,6 +45,15 @@ class RackNode(val rack: Rack) extends ComputerAwareNode(rack) with WindowedNode
|
||||
}
|
||||
}
|
||||
|
||||
override protected def selfDestructingCards: IterableOnce[SelfDestructingCardItem] = {
|
||||
inventoryIterator.flatMap(_.get).collect({
|
||||
case item: ServerItem =>
|
||||
item.inventoryIterator.flatMap(_.get).collect {
|
||||
case card: SelfDestructingCardItem => card
|
||||
}
|
||||
}).flatten
|
||||
}
|
||||
|
||||
override def setupContextMenu(menu: ContextMenu, event: ClickEvent): Unit = {
|
||||
RackNode.addContextMenuEntriesOfMountable(menu, getMountableByClick(event))
|
||||
|
||||
@ -183,7 +194,7 @@ object RackNode {
|
||||
def addContextMenuEntriesOfMountable(menu: ContextMenu, item: Option[RackMountableItem]): Unit = {
|
||||
item match {
|
||||
case Some(serverItem: ServerItem) =>
|
||||
menu.addEntry(ContextMenuEntry("Set up", IconSource.Window) {
|
||||
menu.addEntry(ContextMenuEntry("Set up", IconSource.Icons.Window) {
|
||||
serverItem.window.open()
|
||||
})
|
||||
|
||||
@ -206,7 +217,7 @@ object RackNode {
|
||||
"Change floppy"
|
||||
else
|
||||
"Set floppy",
|
||||
IconSource.Save,
|
||||
IconSource.Icons.Save,
|
||||
) {
|
||||
diskDriveMountableItem.window.open()
|
||||
})
|
||||
|
||||
@ -8,12 +8,16 @@ import ocelot.desktop.node.Node.{HighlightThickness, NoHighlightSize}
|
||||
import ocelot.desktop.node.{EntityNode, LabeledEntityNode, WindowedNode}
|
||||
import ocelot.desktop.ui.event.ClickEvent
|
||||
import ocelot.desktop.ui.event.handlers.DiskActivityHandler
|
||||
import ocelot.desktop.ui.widget.contextmenu.ContextMenu
|
||||
import ocelot.desktop.ui.widget.DiskEditWindow
|
||||
import ocelot.desktop.ui.widget.DiskEditWindow.EditableDisk
|
||||
import ocelot.desktop.ui.widget.contextmenu.{ContextMenu, ContextMenuEntry}
|
||||
import ocelot.desktop.ui.widget.slot.HddSlotWidget
|
||||
import ocelot.desktop.ui.widget.window.Windowed
|
||||
import ocelot.desktop.util.{DefaultSlotItemsFillable, DrawUtils}
|
||||
import ocelot.desktop.windows.RaidWindow
|
||||
import totoro.ocelot.brain.entity.Raid
|
||||
import totoro.ocelot.brain.entity.traits.Inventory
|
||||
import totoro.ocelot.brain.entity.traits.{Environment, Inventory}
|
||||
import totoro.ocelot.brain.nbt.NBTTagCompound
|
||||
import totoro.ocelot.brain.util.Tier
|
||||
|
||||
import scala.util.Random
|
||||
@ -24,10 +28,14 @@ class RaidNode(val raid: Raid)
|
||||
with LabeledEntityNode
|
||||
with DiskActivityHandler
|
||||
with DefaultSlotItemsFillable
|
||||
with WindowedNode[RaidWindow] {
|
||||
with WindowedNode[RaidWindow]
|
||||
with EditableDisk {
|
||||
|
||||
var diskSlots: Array[HddSlotWidget] = Array.tabulate(3)(index => new HddSlotWidget(Slot(index), Tier.Three))
|
||||
|
||||
override val iconSource: IconSource = IconSource.Nodes.Raid.Default
|
||||
override val icon: IconSource = IconSource.Nodes.Raid.Default
|
||||
|
||||
override def rotatable: Boolean = true
|
||||
|
||||
override def draw(g: Graphics): Unit = {
|
||||
super.draw(g)
|
||||
@ -56,30 +64,51 @@ class RaidNode(val raid: Raid)
|
||||
override def shouldReceiveEventsFor(address: String): Boolean =
|
||||
super.shouldReceiveEventsFor(address) || raid.filesystem.exists(_.node.address == address)
|
||||
|
||||
private val diskEditWindow = new Windowed[DiskEditWindow] {
|
||||
override protected def createWindow(): DiskEditWindow = new DiskEditWindow(RaidNode.this)
|
||||
|
||||
override protected def windowNBTKey: String = "diskEditWindow"
|
||||
}
|
||||
|
||||
override def setupContextMenu(menu: ContextMenu, event: ClickEvent): Unit = {
|
||||
DiskItem.addRealPathContextMenuEntries(menu, raid,
|
||||
DiskItem.addRealPathContextMenuEntries(
|
||||
menu,
|
||||
raid,
|
||||
realPathSetter => {
|
||||
realPathSetter()
|
||||
},
|
||||
)
|
||||
|
||||
menu.addEntry(ContextMenuEntry("Edit disk", IconSource.Icons.Edit) {
|
||||
diskEditWindow.window.open()
|
||||
})
|
||||
|
||||
// TODO: Implement DiskDriveWindow later, because at this moment every
|
||||
// TODO: instance of 'Windowed' trait can have only 1 persistable window
|
||||
|
||||
// TODO: Perhaps we should rework this system with List[Window] and
|
||||
// TODO: registerWindow(...) or something similar
|
||||
|
||||
// menu.addEntry(ContextMenuEntry("Edit disk", IconSource.Edit) {
|
||||
// window.open()
|
||||
// })
|
||||
|
||||
menu.addSeparator()
|
||||
|
||||
super.setupContextMenu(menu, event)
|
||||
}
|
||||
|
||||
override def load(nbt: NBTTagCompound): Unit = {
|
||||
super.load(nbt)
|
||||
|
||||
diskEditWindow.load(nbt)
|
||||
}
|
||||
|
||||
override def save(nbt: NBTTagCompound): Unit = {
|
||||
super.save(nbt)
|
||||
|
||||
diskEditWindow.save(nbt)
|
||||
}
|
||||
|
||||
override def dispose(): Unit = {
|
||||
diskEditWindow.closeAndDisposeWindow()
|
||||
|
||||
super.dispose()
|
||||
}
|
||||
|
||||
// -------------------------------- LabeledEntityNode --------------------------------
|
||||
|
||||
override def fallbackLabelAddress: Option[String] = raid.filesystem.map(_.node.address)
|
||||
override def fallbackLabelAddress: Option[String] = diskAddress
|
||||
|
||||
// -------------------------------- Inventory --------------------------------
|
||||
|
||||
@ -97,4 +126,22 @@ class RaidNode(val raid: Raid)
|
||||
// -------------------------------- Window --------------------------------
|
||||
|
||||
override def createWindow(): RaidWindow = new RaidWindow(this)
|
||||
|
||||
// -------------------------------- EditableDisk --------------------------------
|
||||
|
||||
override def disk: Option[Environment] = raid.filesystem
|
||||
|
||||
override def diskKind: String = "RAID"
|
||||
|
||||
override def diskAddress: Option[String] = raid.filesystem.map(_.node.address)
|
||||
|
||||
override def capacity: Long = raid.filesystem.fold(0L)(_.fileSystem.spaceTotal)
|
||||
|
||||
override def diskLabel: Option[String] = Option(raid.label.getLabel)
|
||||
|
||||
override def diskLabel_=(label: Option[String]): Unit = raid.label.setLabel(label.orNull)
|
||||
|
||||
override def isLabelWriteable: Boolean = true
|
||||
|
||||
override def editingAllowed: Boolean = raid.filesystem.isDefined
|
||||
}
|
||||
|
||||
@ -11,7 +11,7 @@ import totoro.ocelot.brain.util.Direction
|
||||
class RelayNode(val relay: Relay)
|
||||
extends EntityNode(relay) with SyncedInventory with LabeledNode with WindowedNode[RelayWindow] {
|
||||
|
||||
override val iconSource: IconSource = IconSource.Nodes.Relay
|
||||
override val icon: IconSource = IconSource.Nodes.Relay
|
||||
|
||||
override def ports: Array[NodePort] = Array(
|
||||
NodePort(Some(Direction.North)),
|
||||
@ -24,6 +24,8 @@ class RelayNode(val relay: Relay)
|
||||
|
||||
override def getNodeByPort(port: NodePort): network.Node = relay.sidedNode(port.direction.get)
|
||||
|
||||
override def rotatable: Boolean = false
|
||||
|
||||
override def exposeAddress = false
|
||||
|
||||
override def shouldReceiveEventsFor(address: String): Boolean = {
|
||||
|
||||
@ -91,32 +91,34 @@ class ScreenNode(val screen: Screen)
|
||||
}
|
||||
}
|
||||
|
||||
override def iconSource: IconSource = IconSource.Nodes.Screen.Standalone
|
||||
override def icon: IconSource = IconSource.Nodes.Screen.Standalone
|
||||
|
||||
override def iconColor: Color = TierColor.get(screen.tier)
|
||||
|
||||
override def rotatable: Boolean = true
|
||||
|
||||
override def setupContextMenu(menu: ContextMenu, event: ClickEvent): Unit = {
|
||||
// no synchronization here: the methods to turn the screen on/off are indirect.
|
||||
if (screen.getPowerState) {
|
||||
menu.addEntry(ContextMenuEntry("Turn off", IconSource.Power) {
|
||||
menu.addEntry(ContextMenuEntry("Turn off", IconSource.Icons.Power) {
|
||||
screen.setPowerState(false)
|
||||
})
|
||||
} else {
|
||||
menu.addEntry(ContextMenuEntry("Turn on", IconSource.Power) {
|
||||
menu.addEntry(ContextMenuEntry("Turn on", IconSource.Icons.Power) {
|
||||
screen.setPowerState(true)
|
||||
})
|
||||
}
|
||||
|
||||
menu.addEntry(ContextMenuEntry("Set aspect ratio", IconSource.AspectRatio) {
|
||||
menu.addEntry(ContextMenuEntry("Set aspect ratio", IconSource.Icons.AspectRatio) {
|
||||
new ScreenAspectRatioDialog(this).show()
|
||||
})
|
||||
|
||||
if (keyboard.isDefined) {
|
||||
menu.addEntry(ContextMenuEntry("Remove keyboard", IconSource.KeyboardOff) {
|
||||
menu.addEntry(ContextMenuEntry("Remove keyboard", IconSource.Icons.KeyboardOff) {
|
||||
detachKeyboard()
|
||||
})
|
||||
} else {
|
||||
menu.addEntry(ContextMenuEntry("Add keyboard", IconSource.Keyboard) {
|
||||
menu.addEntry(ContextMenuEntry("Add keyboard", IconSource.Icons.Keyboard) {
|
||||
attachKeyboard()
|
||||
})
|
||||
}
|
||||
|
||||