Merge branch 'develop'

This commit is contained in:
Fingercomp 2025-08-18 19:01:55 +03:00
commit b53311aa27
No known key found for this signature in database
GPG Key ID: BBC71CEE45D86E37
189 changed files with 3312 additions and 1809 deletions

View File

@ -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

View File

@ -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
}

View File

@ -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"

View 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`

Binary file not shown.

@ -1 +1 @@
Subproject commit 4b2d2fcec19f8e238dd1c4bdb09d42b11e9bb6e6
Subproject commit da5dd8877035f4994b77047658561d316319b54b

0
spritepack/spritepack.sh Normal file → Executable file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 196 B

BIN
sprites/icons/SideAny.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 161 B

BIN
sprites/icons/SideDown.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 158 B

BIN
sprites/icons/SideEast.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 161 B

BIN
sprites/icons/SideNone.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 178 B

BIN
sprites/icons/SideNorth.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 154 B

BIN
sprites/icons/SideSouth.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 150 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 B

BIN
sprites/icons/SideUp.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 154 B

BIN
sprites/icons/SideWest.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 B

BIN
sprites/particles/Smoke.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 120 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 123 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 167 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 546 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 157 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 171 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 183 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 183 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 201 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 188 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 188 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 203 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 190 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 219 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 229 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 210 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 228 B

View File

@ -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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 147 KiB

After

Width:  |  Height:  |  Size: 149 KiB

View File

@ -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

View File

@ -1,4 +1,4 @@
#version 140
#version 150
in vec3 inPos;
in vec3 inNormal;

View File

@ -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'"))

View File

@ -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)

View File

@ -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)
}

View File

@ -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()
}
}

View File

@ -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",

View File

@ -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)

View File

@ -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)
// ʕʔ

View File

@ -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] =

View File

@ -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)
}
}

View File

@ -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

View File

@ -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(

View File

@ -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)

View File

@ -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 = {

View File

@ -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)

View File

@ -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")
}
}

View File

@ -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
}
}

View File

@ -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
}

View File

@ -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 = {

View File

@ -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 {

View File

@ -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)
})
}

View File

@ -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)

View File

@ -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)

View File

@ -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,

View File

@ -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()
})

View File

@ -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 {

View File

@ -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"
)
}
}

View File

@ -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()
})

View File

@ -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
}

View File

@ -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 "")

View File

@ -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()
})
}

View 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
}

View File

@ -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 {

View File

@ -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
}
)

View File

@ -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)

View File

@ -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,39 +263,64 @@ 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 {
protected val MovingHighlight: RGBAColor = RGBAColor(240, 250, 240)
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

View File

@ -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 {

View File

@ -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")
}
}
}

View File

@ -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 {

View File

@ -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)

View File

@ -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)
}
}

View 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,
)
}

View File

@ -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()
}

View File

@ -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)

View File

@ -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)
}

View File

@ -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

View File

@ -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 += {

View File

@ -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()

View File

@ -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) {

View File

@ -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)
}

View File

@ -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 {

View File

@ -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()
}

View File

@ -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

View File

@ -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,
)
}
}
}

View File

@ -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,
)
}
}

View File

@ -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

View File

@ -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()
})

View File

@ -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
}

View File

@ -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 = {

View File

@ -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()
})
}

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