Merge branch 'develop'
107
CONTRIBUTING.md
Normal file
@ -0,0 +1,107 @@
|
||||
# Contributing to Ocelot
|
||||
The development of Ocelot is a volunteer effort — and that means we're always understaffed.
|
||||
If you'd like to help us out, we'll appreciate it greatly!
|
||||
And to help *you* help us, we've written a guide, which you are reading right now, describing how we do things around here.
|
||||
|
||||
## The Ocelot projects
|
||||
In fact, Ocelot consists of multiple projects, of which Ocelot Desktop is but a part.
|
||||
|
||||
The most important component is [ocelot-brain].
|
||||
It is a Scala library that does all the actual OC emulation.
|
||||
In terms of responsibilities, it roughly corresponds to the server-side OpenComputers code except abstracted away from Minecraft specifics.
|
||||
It's ocelot-brain that defines the behavior of all entities: blocks, cards, etc.; and it's also ocelot-brain that runs all the Lua code.
|
||||
|
||||
Ocelot Desktop builds on top of ocelot-brain and provides a graphical interface to the library.
|
||||
It does rendering, audio, handles user interaction, and translates user input into calls to ocelot-brain.
|
||||
This is also where all items are defined.
|
||||
|
||||
Such architectural separation of concerns admits alternative interfaces to ocelot-brain.
|
||||
[Ocelot.online] is a demonstration of that — it uses the same ocelot-brain library for emulation but provides a web interface instead.
|
||||
At the moment Ocelot.online is not particularly full of features, unfortunately, and we won't focus on it in this document.
|
||||
|
||||
## Issues
|
||||
We use GitLab issues to track feature requests and bugs.
|
||||
As touched on in the previous section, we have three repositories — and, therefore, three issue trackers.
|
||||
Which one should you report your issue to?
|
||||
|
||||
- If you want to request support for a new OC component or report incorrect emulation of an existing component, consider posting an issue to the [ocelot-brain] tracker.
|
||||
- If you'd like to tell us about UI glitches (or ask for some UI improvement), this is squarely in the Ocelot Desktop territory.
|
||||
- Since [Ocelot.online] is not actively developed, it's unlikely that it would be a good choice for an issue — we'd prefer if you told us about issues directly via IRC, Discord, or email.
|
||||
|
||||
If you're not quite sure which one to pick anyway, Ocelot Desktop is a safe bet.
|
||||
Don't worry if you make a mistake — we can always move the issue to the appropriate place.
|
||||
|
||||
### Feature requests
|
||||
Ocelot Desktop wouldn't become so great if nobody was asking for features!
|
||||
However, if you'd like to ask for one, keep in mind that we are a small group of developer volunteering our time and attention to this project.
|
||||
Many issues stayed open for several years before somebody went ahead and finally realized the asked feature.
|
||||
|
||||
So we'll appreciate it if you tell us why you're requesting a feature and how you would use it if it were added.
|
||||
Seeing a clear use-case helps us stay on track if we get to implementing it — and it's also a great source of motivation for developers since this shows how, concretely, their efforts can help you. :)
|
||||
|
||||
### Bugs
|
||||
Bugs may be embarrassing — but this is no reason to hide them.
|
||||
On the contrary, we'd love to hear about them.
|
||||
If you find one, **please tell us** — or, better yet, **report it on the issue tracker**, which makes sure we don't accidentally forget about it and also helps other users who have the same problem!
|
||||
|
||||
Ocelot Desktop should never crash.
|
||||
This is, of course, but a hopeless dream, and it does crash sometimes.
|
||||
**Please do not ignore crashes** (unless you're sure Ocelot is not at fault): we realize this is the most disruptive kind of bug, and we'd like to fix it as soon as possible.
|
||||
|
||||
If you're reporting a bug, please **describe your actions** preceding it: was it pressing a button? loading a workspace? or something else?
|
||||
Ideally (if you can reproduce the issue — that is, trigger it again), you should tell us step-by-step what you did that lead to the problem.
|
||||
But we understand that it's sometimes just impossible — we'd rather have a report alerting us of the bug than be completely oblivious to it.
|
||||
|
||||
Please attach logs if you have them (`%APPDATA%/Ocelot/Logs/Desktop.log` on Windows, `~/.local/share/ocelot/desktop.log` on Linux) — and **especially** if it's a crash.
|
||||
Logs don't always help us, but when they do, they help tremendously: a crash would have a stack trace pointing to the exact line of code that triggered it, for example.
|
||||
|
||||
If the bug only happens with a specific workspace, you could attach the save directory (as a zip archive) as well.
|
||||
|
||||
Finally, sometimes we'd like to ask for additional information after we get a bug report.
|
||||
Though we don't expect an instant reply, it would be sad if you just ghosted us.
|
||||
We don't have a stale issue policy, but we may close issues that were reported such a long time ago that they are likely no longer relevant.
|
||||
|
||||
## Code contribution
|
||||
Admittedly, there's little documentation, and Scala, a nice language though it might be, is not among the most popular choices for software development.
|
||||
Just to warn you of what kind of mess you're getting yourself into if you're going to contribute code. :)
|
||||
This means you'll probably have to figure out how things work by reading the source code.
|
||||
|
||||
It would be a good idea to chat with us if you're not deterred because of the admonition above.
|
||||
Especially if you're implementing a new feature.
|
||||
Doing this can help you avoid some pitfalls and get a better idea of how things should work.
|
||||
|
||||
New code should be submitted as a merge request.
|
||||
We'll review the changes, perhaps suggesting some things to improve.
|
||||
Once we're happy with the code, we'll gladly accept the MR.
|
||||
|
||||
If your work requires changing ocelot-brain and Ocelot Desktop at the same time, you'll need to make two MRs — one for each repository.
|
||||
|
||||
Here are a few things we'd like you to do when writing code.
|
||||
|
||||
- Keep sensible commit names unless you're planning to squash them at the end.
|
||||
|
||||
- Do not commit code that doesn't compile.
|
||||
It makes bisection harder.
|
||||
|
||||
- Try to adhere to the style of existing code.
|
||||
It might be a bit haphazard currently, but please avoid introducing yet another coding convention.
|
||||
|
||||
- Speaking of conventions: we use Scala's naming scheme.
|
||||
For instance, immutable constants are spelled `LikeThis`.
|
||||
|
||||
- Use Scala idioms.
|
||||
The language features an extensive standard library that covers many aspects of programming.
|
||||
Using `Option[T]` instead of `null`, modeling data as case classes extending a sealed trait, and using pattern matching — these things, though perhaps not as widely used in mainstream languages like C++ or Java, make code safer and more consistent.
|
||||
|
||||
## Communication
|
||||
The primary means of communication is via [IRC] or [Discord] (thread "forums ‣ Ocelot Dev").
|
||||
Either English or Russian is fine.
|
||||
(Please give people some time to respond — they might even be asleep at the time you're posting a message.)
|
||||
|
||||
If you despise instant messaging, you can shoot us an email to no-reply at fomalhaut me.
|
||||
(Don't mind the weird address.)
|
||||
|
||||
[ocelot-brain]: https://gitlab.com/cc-ru/ocelot/ocelot-brain "The ocelot-brain repository."
|
||||
[Ocelot.online]: https://gitlab.com/cc-ru/ocelot/ocelot-online "The Ocelot.online repository."
|
||||
[IRC]: ircs://irc.esper.net:6697/cc.ru "#cc.ru at irc.esper.net"
|
||||
[Discord]: https://discord.com/invite/FM9qWGm
|
||||
@ -1,5 +1,5 @@
|
||||
name := "ocelot-desktop"
|
||||
version := "1.10.1"
|
||||
version := "1.10.2"
|
||||
scalaVersion := "2.13.10"
|
||||
|
||||
lazy val root = project.in(file("."))
|
||||
|
||||
@ -1 +1 @@
|
||||
Subproject commit 2ef2cf3bda8f05688c9801d859c1ab16088bd4c4
|
||||
Subproject commit 2a52322a780ac562316f449f8ce54227dd0586fd
|
||||
BIN
sprites/icons/Guitar.png
Normal file
|
After Width: | Height: | Size: 2.9 KiB |
BIN
sprites/window/tape/Back.png
Normal file
|
After Width: | Height: | Size: 3.0 KiB |
BIN
sprites/window/tape/BackPressed.png
Normal file
|
After Width: | Height: | Size: 3.0 KiB |
BIN
sprites/window/tape/Forward.png
Normal file
|
After Width: | Height: | Size: 3.0 KiB |
BIN
sprites/window/tape/ForwardPressed.png
Normal file
|
After Width: | Height: | Size: 3.0 KiB |
BIN
sprites/window/tape/Play.png
Normal file
|
After Width: | Height: | Size: 3.0 KiB |
BIN
sprites/window/tape/PlayPressed.png
Normal file
|
After Width: | Height: | Size: 3.0 KiB |
BIN
sprites/window/tape/Screen.png
Normal file
|
After Width: | Height: | Size: 3.0 KiB |
BIN
sprites/window/tape/Stop.png
Normal file
|
After Width: | Height: | Size: 3.0 KiB |
BIN
sprites/window/tape/StopPressed.png
Normal file
|
After Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 141 KiB After Width: | Height: | Size: 143 KiB |
@ -1,175 +1,176 @@
|
||||
BackgroundPattern 0 0 304 304
|
||||
BarSegment 385 434 16 4
|
||||
Empty 327 572 16 16
|
||||
EmptySlot 305 597 18 18
|
||||
Empty 242 674 16 16
|
||||
EmptySlot 220 699 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 279 305 1 24
|
||||
ShadowCorner 202 572 24 24
|
||||
TabArrow 49 716 8 14
|
||||
blocks/Generic 344 572 16 16
|
||||
ShadowCorner 117 674 24 24
|
||||
TabArrow 134 628 8 14
|
||||
blocks/Generic 259 674 16 16
|
||||
blocks/HologramEffect 287 314 4 4
|
||||
blocks/HologramProjector1Top 361 572 16 16
|
||||
blocks/HologramProjector2Top 378 572 16 16
|
||||
blocks/HologramProjectorSide 395 572 16 16
|
||||
buttons/BottomDrawerClose 324 597 18 18
|
||||
buttons/BottomDrawerOpen 343 597 18 18
|
||||
blocks/HologramProjector1Top 276 674 16 16
|
||||
blocks/HologramProjector2Top 293 674 16 16
|
||||
blocks/HologramProjectorSide 310 674 16 16
|
||||
buttons/BottomDrawerClose 239 699 18 18
|
||||
buttons/BottomDrawerOpen 258 699 18 18
|
||||
buttons/OpenFMRadioCloseOff 404 445 7 8
|
||||
buttons/OpenFMRadioCloseOn 412 445 7 8
|
||||
buttons/OpenFMRadioRedstoneOff 359 445 8 8
|
||||
buttons/OpenFMRadioRedstoneOn 368 445 8 8
|
||||
buttons/OpenFMRadioStartOff 227 572 24 24
|
||||
buttons/OpenFMRadioStartOn 252 572 24 24
|
||||
buttons/OpenFMRadioStopOff 277 572 24 24
|
||||
buttons/OpenFMRadioStopOn 302 572 24 24
|
||||
buttons/OpenFMRadioVolumeDownOff 58 716 10 10
|
||||
buttons/OpenFMRadioVolumeDownOn 69 716 10 10
|
||||
buttons/OpenFMRadioVolumeUpOff 80 716 10 10
|
||||
buttons/OpenFMRadioVolumeUpOn 91 716 10 10
|
||||
buttons/PowerOff 362 597 18 18
|
||||
buttons/PowerOn 381 597 18 18
|
||||
buttons/RackRelayOff 202 553 65 18
|
||||
buttons/RackRelayOn 268 553 65 18
|
||||
icons/Antenna 412 572 16 16
|
||||
icons/ArrowRight 429 572 16 16
|
||||
icons/AspectRatio 446 572 16 16
|
||||
icons/Book 463 572 16 16
|
||||
icons/ButtonCheck 202 630 17 17
|
||||
icons/ButtonClipboard 220 630 17 17
|
||||
icons/ButtonRandomize 238 630 17 17
|
||||
icons/CPU 480 572 16 16
|
||||
icons/Card 497 572 16 16
|
||||
icons/Close 49 683 15 14
|
||||
icons/Code 514 572 16 16
|
||||
icons/ComponentBus 531 572 16 16
|
||||
icons/Copy 548 572 16 16
|
||||
icons/Cross 565 572 16 16
|
||||
icons/Delete 582 572 16 16
|
||||
icons/DragLMB 400 597 21 14
|
||||
icons/DragRMB 422 597 21 14
|
||||
icons/EEPROM 599 572 16 16
|
||||
icons/Edit 616 572 16 16
|
||||
icons/Eject 633 572 16 16
|
||||
icons/File 650 572 16 16
|
||||
icons/Floppy 667 572 16 16
|
||||
icons/Folder 684 572 16 16
|
||||
icons/FolderSlash 701 572 16 16
|
||||
icons/Grid 236 597 22 22
|
||||
icons/GridOff 259 597 22 22
|
||||
icons/HDD 718 572 16 16
|
||||
icons/Help 735 572 16 16
|
||||
icons/Home 282 597 22 22
|
||||
icons/LMB 133 698 11 14
|
||||
icons/Label 752 572 16 16
|
||||
icons/LinesHorizontal 769 572 16 16
|
||||
icons/Link 786 572 16 16
|
||||
icons/LinkSlash 803 572 16 16
|
||||
icons/Memory 820 572 16 16
|
||||
icons/Microchip 837 572 16 16
|
||||
icons/NA 854 572 16 16
|
||||
icons/NotificationError 157 698 11 11
|
||||
icons/NotificationInfo 169 698 11 11
|
||||
icons/NotificationWarning 181 698 11 11
|
||||
icons/Ocelot 871 572 16 16
|
||||
icons/Pin 88 698 14 14
|
||||
icons/Plus 888 572 16 16
|
||||
icons/Power 905 572 16 16
|
||||
icons/RMB 145 698 11 14
|
||||
icons/Restart 922 572 16 16
|
||||
icons/Save 939 572 16 16
|
||||
icons/SaveAs 956 572 16 16
|
||||
icons/Server 973 572 16 16
|
||||
icons/SettingsSound 49 698 12 17
|
||||
icons/SettingsSystem 62 698 12 17
|
||||
icons/SettingsUI 75 698 12 17
|
||||
icons/Tier0 990 572 16 16
|
||||
icons/Tier1 1007 572 16 16
|
||||
icons/Tier2 334 553 16 16
|
||||
icons/Tiers 351 553 16 16
|
||||
icons/Unpin 103 698 14 14
|
||||
icons/WaveLFSR 969 655 24 10
|
||||
icons/WaveNoise 994 655 24 10
|
||||
icons/WaveSawtooth 49 672 24 10
|
||||
icons/WaveSine 74 672 24 10
|
||||
icons/WaveSquare 99 672 24 10
|
||||
icons/WaveTriangle 124 672 24 10
|
||||
icons/Window 368 553 16 16
|
||||
buttons/OpenFMRadioStartOff 142 674 24 24
|
||||
buttons/OpenFMRadioStartOn 167 674 24 24
|
||||
buttons/OpenFMRadioStopOff 192 674 24 24
|
||||
buttons/OpenFMRadioStopOn 217 674 24 24
|
||||
buttons/OpenFMRadioVolumeDownOff 143 628 10 10
|
||||
buttons/OpenFMRadioVolumeDownOn 154 628 10 10
|
||||
buttons/OpenFMRadioVolumeUpOff 165 628 10 10
|
||||
buttons/OpenFMRadioVolumeUpOn 176 628 10 10
|
||||
buttons/PowerOff 277 699 18 18
|
||||
buttons/PowerOn 296 699 18 18
|
||||
buttons/RackRelayOff 117 655 65 18
|
||||
buttons/RackRelayOn 183 655 65 18
|
||||
icons/Antenna 327 674 16 16
|
||||
icons/ArrowRight 344 674 16 16
|
||||
icons/AspectRatio 361 674 16 16
|
||||
icons/Book 378 674 16 16
|
||||
icons/ButtonCheck 117 732 17 17
|
||||
icons/ButtonClipboard 135 732 17 17
|
||||
icons/ButtonRandomize 153 732 17 17
|
||||
icons/CPU 395 674 16 16
|
||||
icons/Card 412 674 16 16
|
||||
icons/Close 134 595 15 14
|
||||
icons/Code 429 674 16 16
|
||||
icons/ComponentBus 446 674 16 16
|
||||
icons/Copy 463 674 16 16
|
||||
icons/Cross 480 674 16 16
|
||||
icons/Delete 497 674 16 16
|
||||
icons/DragLMB 483 699 21 14
|
||||
icons/DragRMB 505 699 21 14
|
||||
icons/EEPROM 514 674 16 16
|
||||
icons/Edit 531 674 16 16
|
||||
icons/Eject 548 674 16 16
|
||||
icons/File 565 674 16 16
|
||||
icons/Floppy 582 674 16 16
|
||||
icons/Folder 599 674 16 16
|
||||
icons/FolderSlash 616 674 16 16
|
||||
icons/Grid 151 699 22 22
|
||||
icons/GridOff 174 699 22 22
|
||||
icons/Guitar 633 674 16 16
|
||||
icons/HDD 650 674 16 16
|
||||
icons/Help 667 674 16 16
|
||||
icons/Home 197 699 22 22
|
||||
icons/LMB 218 610 11 14
|
||||
icons/Label 684 674 16 16
|
||||
icons/LinesHorizontal 701 674 16 16
|
||||
icons/Link 718 674 16 16
|
||||
icons/LinkSlash 735 674 16 16
|
||||
icons/Memory 752 674 16 16
|
||||
icons/Microchip 769 674 16 16
|
||||
icons/NA 786 674 16 16
|
||||
icons/NotificationError 242 610 11 11
|
||||
icons/NotificationInfo 254 610 11 11
|
||||
icons/NotificationWarning 266 610 11 11
|
||||
icons/Ocelot 803 674 16 16
|
||||
icons/Pin 173 610 14 14
|
||||
icons/Plus 820 674 16 16
|
||||
icons/Power 837 674 16 16
|
||||
icons/RMB 230 610 11 14
|
||||
icons/Restart 854 674 16 16
|
||||
icons/Save 871 674 16 16
|
||||
icons/SaveAs 888 674 16 16
|
||||
icons/Server 905 674 16 16
|
||||
icons/SettingsSound 134 610 12 17
|
||||
icons/SettingsSystem 147 610 12 17
|
||||
icons/SettingsUI 160 610 12 17
|
||||
icons/Tier0 922 674 16 16
|
||||
icons/Tier1 939 674 16 16
|
||||
icons/Tier2 956 674 16 16
|
||||
icons/Tiers 973 674 16 16
|
||||
icons/Unpin 188 610 14 14
|
||||
icons/WaveLFSR 901 567 24 10
|
||||
icons/WaveNoise 926 567 24 10
|
||||
icons/WaveSawtooth 951 567 24 10
|
||||
icons/WaveSine 976 567 24 10
|
||||
icons/WaveSquare 134 584 24 10
|
||||
icons/WaveTriangle 159 584 24 10
|
||||
icons/Window 990 674 16 16
|
||||
icons/WireArrowLeft 281 305 4 8
|
||||
icons/WireArrowRight 286 305 4 8
|
||||
items/APU0 134 553 16 96
|
||||
items/APU1 151 553 16 96
|
||||
items/APU2 168 553 16 96
|
||||
items/CPU0 385 553 16 16
|
||||
items/CPU1 402 553 16 16
|
||||
items/CPU2 419 553 16 16
|
||||
items/CardBase 436 553 16 16
|
||||
items/CircuitBoard 453 553 16 16
|
||||
items/ComponentBus0 470 553 16 16
|
||||
items/ComponentBus1 487 553 16 16
|
||||
items/ComponentBus2 504 553 16 16
|
||||
items/ComponentBus3 521 553 16 16
|
||||
items/APU0 49 655 16 96
|
||||
items/APU1 66 655 16 96
|
||||
items/APU2 83 655 16 96
|
||||
items/CPU0 1007 674 16 16
|
||||
items/CPU1 249 655 16 16
|
||||
items/CPU2 266 655 16 16
|
||||
items/CardBase 283 655 16 16
|
||||
items/CircuitBoard 300 655 16 16
|
||||
items/ComponentBus0 317 655 16 16
|
||||
items/ComponentBus1 334 655 16 16
|
||||
items/ComponentBus2 351 655 16 16
|
||||
items/ComponentBus3 368 655 16 16
|
||||
items/DataCard0 49 526 16 128
|
||||
items/DataCard1 66 526 16 128
|
||||
items/DataCard2 83 526 16 128
|
||||
items/DebugCard 538 553 16 16
|
||||
items/DiskDriveMountable 555 553 16 16
|
||||
items/EEPROM 572 553 16 16
|
||||
items/FloppyDisk_dyeBlack 589 553 16 16
|
||||
items/FloppyDisk_dyeBlue 606 553 16 16
|
||||
items/FloppyDisk_dyeBrown 623 553 16 16
|
||||
items/FloppyDisk_dyeCyan 640 553 16 16
|
||||
items/FloppyDisk_dyeGray 657 553 16 16
|
||||
items/FloppyDisk_dyeGreen 674 553 16 16
|
||||
items/FloppyDisk_dyeLightBlue 691 553 16 16
|
||||
items/FloppyDisk_dyeLightGray 708 553 16 16
|
||||
items/FloppyDisk_dyeLime 725 553 16 16
|
||||
items/FloppyDisk_dyeMagenta 742 553 16 16
|
||||
items/FloppyDisk_dyeOrange 759 553 16 16
|
||||
items/FloppyDisk_dyePink 776 553 16 16
|
||||
items/FloppyDisk_dyePurple 793 553 16 16
|
||||
items/FloppyDisk_dyeRed 810 553 16 16
|
||||
items/FloppyDisk_dyeWhite 827 553 16 16
|
||||
items/FloppyDisk_dyeYellow 844 553 16 16
|
||||
items/GraphicsCard0 861 553 16 16
|
||||
items/GraphicsCard1 878 553 16 16
|
||||
items/GraphicsCard2 895 553 16 16
|
||||
items/HardDiskDrive0 912 553 16 16
|
||||
items/HardDiskDrive1 929 553 16 16
|
||||
items/HardDiskDrive2 946 553 16 16
|
||||
items/InternetCard 202 597 16 32
|
||||
items/LinkedCard 185 553 16 96
|
||||
items/Memory0 963 553 16 16
|
||||
items/Memory1 980 553 16 16
|
||||
items/Memory2 997 553 16 16
|
||||
items/Memory3 201 526 16 16
|
||||
items/Memory4 218 526 16 16
|
||||
items/Memory5 235 526 16 16
|
||||
items/Memory6 252 526 16 16
|
||||
items/NetworkCard 269 526 16 16
|
||||
items/DebugCard 385 655 16 16
|
||||
items/DiskDriveMountable 402 655 16 16
|
||||
items/EEPROM 419 655 16 16
|
||||
items/FloppyDisk_dyeBlack 436 655 16 16
|
||||
items/FloppyDisk_dyeBlue 453 655 16 16
|
||||
items/FloppyDisk_dyeBrown 470 655 16 16
|
||||
items/FloppyDisk_dyeCyan 487 655 16 16
|
||||
items/FloppyDisk_dyeGray 504 655 16 16
|
||||
items/FloppyDisk_dyeGreen 521 655 16 16
|
||||
items/FloppyDisk_dyeLightBlue 538 655 16 16
|
||||
items/FloppyDisk_dyeLightGray 555 655 16 16
|
||||
items/FloppyDisk_dyeLime 572 655 16 16
|
||||
items/FloppyDisk_dyeMagenta 589 655 16 16
|
||||
items/FloppyDisk_dyeOrange 606 655 16 16
|
||||
items/FloppyDisk_dyePink 623 655 16 16
|
||||
items/FloppyDisk_dyePurple 640 655 16 16
|
||||
items/FloppyDisk_dyeRed 657 655 16 16
|
||||
items/FloppyDisk_dyeWhite 674 655 16 16
|
||||
items/FloppyDisk_dyeYellow 691 655 16 16
|
||||
items/GraphicsCard0 708 655 16 16
|
||||
items/GraphicsCard1 725 655 16 16
|
||||
items/GraphicsCard2 742 655 16 16
|
||||
items/HardDiskDrive0 759 655 16 16
|
||||
items/HardDiskDrive1 776 655 16 16
|
||||
items/HardDiskDrive2 793 655 16 16
|
||||
items/InternetCard 117 699 16 32
|
||||
items/LinkedCard 100 655 16 96
|
||||
items/Memory0 810 655 16 16
|
||||
items/Memory1 827 655 16 16
|
||||
items/Memory2 844 655 16 16
|
||||
items/Memory3 861 655 16 16
|
||||
items/Memory4 878 655 16 16
|
||||
items/Memory5 895 655 16 16
|
||||
items/Memory6 912 655 16 16
|
||||
items/NetworkCard 929 655 16 16
|
||||
items/OcelotCard 100 526 16 128
|
||||
items/RedstoneCard0 286 526 16 16
|
||||
items/RedstoneCard1 303 526 16 16
|
||||
items/SelfDestructingCard 219 597 16 32
|
||||
items/Server0 320 526 16 16
|
||||
items/Server1 337 526 16 16
|
||||
items/Server2 354 526 16 16
|
||||
items/Server3 371 526 16 16
|
||||
items/RedstoneCard0 946 655 16 16
|
||||
items/RedstoneCard1 963 655 16 16
|
||||
items/SelfDestructingCard 134 699 16 32
|
||||
items/Server0 980 655 16 16
|
||||
items/Server1 997 655 16 16
|
||||
items/Server2 201 540 16 16
|
||||
items/Server3 218 540 16 16
|
||||
items/SoundCard 117 526 16 128
|
||||
items/TapeCopper 388 526 16 16
|
||||
items/TapeDiamond 405 526 16 16
|
||||
items/TapeGold 422 526 16 16
|
||||
items/TapeGreg 439 526 16 16
|
||||
items/TapeIg 456 526 16 16
|
||||
items/TapeIron 473 526 16 16
|
||||
items/TapeNetherStar 490 526 16 16
|
||||
items/TapeSteel 507 526 16 16
|
||||
items/WirelessNetworkCard0 524 526 16 16
|
||||
items/WirelessNetworkCard1 541 526 16 16
|
||||
light-panel/BookmarkLeft 950 655 18 14
|
||||
light-panel/BookmarkRight 256 630 20 14
|
||||
items/TapeCopper 235 540 16 16
|
||||
items/TapeDiamond 252 540 16 16
|
||||
items/TapeGold 269 540 16 16
|
||||
items/TapeGreg 286 540 16 16
|
||||
items/TapeIg 303 540 16 16
|
||||
items/TapeIron 320 540 16 16
|
||||
items/TapeNetherStar 337 540 16 16
|
||||
items/TapeSteel 354 540 16 16
|
||||
items/WirelessNetworkCard0 371 540 16 16
|
||||
items/WirelessNetworkCard1 388 540 16 16
|
||||
light-panel/BookmarkLeft 882 567 18 14
|
||||
light-panel/BookmarkRight 171 732 20 14
|
||||
light-panel/BorderB 292 314 4 4
|
||||
light-panel/BorderL 382 314 4 2
|
||||
light-panel/BorderR 297 314 4 4
|
||||
@ -181,88 +182,88 @@ light-panel/CornerTR 322 314 4 4
|
||||
light-panel/Fill 285 325 2 2
|
||||
light-panel/Vent 356 434 2 38
|
||||
nodes/Cable 377 445 8 8
|
||||
nodes/Camera 558 526 16 16
|
||||
nodes/Chest 118 698 14 14
|
||||
nodes/HologramProjector0 575 526 16 16
|
||||
nodes/HologramProjector1 592 526 16 16
|
||||
nodes/IronNoteBlock 609 526 16 16
|
||||
nodes/Lamp 626 526 16 16
|
||||
nodes/LampFrame 643 526 16 16
|
||||
nodes/Camera 405 540 16 16
|
||||
nodes/Chest 203 610 14 14
|
||||
nodes/HologramProjector0 422 540 16 16
|
||||
nodes/HologramProjector1 439 540 16 16
|
||||
nodes/IronNoteBlock 456 540 16 16
|
||||
nodes/Lamp 473 540 16 16
|
||||
nodes/LampFrame 490 540 16 16
|
||||
nodes/LampGlow 49 305 128 128
|
||||
nodes/NewNode 660 526 16 16
|
||||
nodes/NoteBlock 677 526 16 16
|
||||
nodes/OpenFMRadio 694 526 16 16
|
||||
nodes/Relay 711 526 16 16
|
||||
nodes/TapeDrive 728 526 16 16
|
||||
nodes/computer/Default 745 526 16 16
|
||||
nodes/computer/DiskActivity 762 526 16 16
|
||||
nodes/computer/Error 779 526 16 16
|
||||
nodes/computer/On 796 526 16 16
|
||||
nodes/disk-drive/Default 813 526 16 16
|
||||
nodes/disk-drive/DiskActivity 830 526 16 16
|
||||
nodes/disk-drive/Floppy 847 526 16 16
|
||||
nodes/microcontroller/Default 864 526 16 16
|
||||
nodes/microcontroller/Error 881 526 16 16
|
||||
nodes/microcontroller/On 898 526 16 16
|
||||
nodes/rack/Default 915 526 16 16
|
||||
nodes/rack/Empty 932 526 16 16
|
||||
nodes/rack/drive/0/Default 949 526 16 16
|
||||
nodes/rack/drive/0/DiskActivity 966 526 16 16
|
||||
nodes/rack/drive/0/Floppy 983 526 16 16
|
||||
nodes/rack/drive/1/Default 1000 526 16 16
|
||||
nodes/rack/drive/1/DiskActivity 49 655 16 16
|
||||
nodes/rack/drive/1/Floppy 66 655 16 16
|
||||
nodes/rack/drive/2/Default 83 655 16 16
|
||||
nodes/rack/drive/2/DiskActivity 100 655 16 16
|
||||
nodes/rack/drive/2/Floppy 117 655 16 16
|
||||
nodes/rack/drive/3/Default 134 655 16 16
|
||||
nodes/rack/drive/3/DiskActivity 151 655 16 16
|
||||
nodes/rack/drive/3/Floppy 168 655 16 16
|
||||
nodes/rack/drive/Floppy 185 655 16 16
|
||||
nodes/rack/server/0/Default 202 655 16 16
|
||||
nodes/rack/server/0/DiskActivity 219 655 16 16
|
||||
nodes/rack/server/0/Error 236 655 16 16
|
||||
nodes/rack/server/0/NetworkActivity 253 655 16 16
|
||||
nodes/rack/server/0/On 270 655 16 16
|
||||
nodes/rack/server/1/Default 287 655 16 16
|
||||
nodes/rack/server/1/DiskActivity 304 655 16 16
|
||||
nodes/rack/server/1/Error 321 655 16 16
|
||||
nodes/rack/server/1/NetworkActivity 338 655 16 16
|
||||
nodes/rack/server/1/On 355 655 16 16
|
||||
nodes/rack/server/2/Default 372 655 16 16
|
||||
nodes/rack/server/2/DiskActivity 389 655 16 16
|
||||
nodes/rack/server/2/Error 406 655 16 16
|
||||
nodes/rack/server/2/NetworkActivity 423 655 16 16
|
||||
nodes/rack/server/2/On 440 655 16 16
|
||||
nodes/rack/server/3/Default 457 655 16 16
|
||||
nodes/rack/server/3/DiskActivity 474 655 16 16
|
||||
nodes/rack/server/3/Error 491 655 16 16
|
||||
nodes/rack/server/3/NetworkActivity 508 655 16 16
|
||||
nodes/rack/server/3/On 525 655 16 16
|
||||
nodes/raid/0/DiskActivity 542 655 16 16
|
||||
nodes/raid/0/Error 559 655 16 16
|
||||
nodes/raid/1/DiskActivity 576 655 16 16
|
||||
nodes/raid/1/Error 593 655 16 16
|
||||
nodes/raid/2/DiskActivity 610 655 16 16
|
||||
nodes/raid/2/Error 627 655 16 16
|
||||
nodes/raid/Default 644 655 16 16
|
||||
nodes/screen/BottomLeft 661 655 16 16
|
||||
nodes/screen/BottomMiddle 678 655 16 16
|
||||
nodes/screen/BottomRight 695 655 16 16
|
||||
nodes/screen/ColumnBottom 712 655 16 16
|
||||
nodes/screen/ColumnMiddle 729 655 16 16
|
||||
nodes/screen/ColumnTop 746 655 16 16
|
||||
nodes/screen/Middle 763 655 16 16
|
||||
nodes/screen/MiddleLeft 780 655 16 16
|
||||
nodes/screen/MiddleRight 797 655 16 16
|
||||
nodes/screen/PowerOnOverlay 814 655 16 16
|
||||
nodes/screen/RowLeft 831 655 16 16
|
||||
nodes/screen/RowMiddle 848 655 16 16
|
||||
nodes/screen/RowRight 865 655 16 16
|
||||
nodes/screen/Standalone 882 655 16 16
|
||||
nodes/screen/TopLeft 899 655 16 16
|
||||
nodes/screen/TopMiddle 916 655 16 16
|
||||
nodes/screen/TopRight 933 655 16 16
|
||||
nodes/NewNode 507 540 16 16
|
||||
nodes/NoteBlock 524 540 16 16
|
||||
nodes/OpenFMRadio 541 540 16 16
|
||||
nodes/Relay 558 540 16 16
|
||||
nodes/TapeDrive 575 540 16 16
|
||||
nodes/computer/Default 592 540 16 16
|
||||
nodes/computer/DiskActivity 609 540 16 16
|
||||
nodes/computer/Error 626 540 16 16
|
||||
nodes/computer/On 643 540 16 16
|
||||
nodes/disk-drive/Default 660 540 16 16
|
||||
nodes/disk-drive/DiskActivity 677 540 16 16
|
||||
nodes/disk-drive/Floppy 694 540 16 16
|
||||
nodes/microcontroller/Default 711 540 16 16
|
||||
nodes/microcontroller/Error 728 540 16 16
|
||||
nodes/microcontroller/On 745 540 16 16
|
||||
nodes/rack/Default 762 540 16 16
|
||||
nodes/rack/Empty 779 540 16 16
|
||||
nodes/rack/drive/0/Default 796 540 16 16
|
||||
nodes/rack/drive/0/DiskActivity 813 540 16 16
|
||||
nodes/rack/drive/0/Floppy 830 540 16 16
|
||||
nodes/rack/drive/1/Default 847 540 16 16
|
||||
nodes/rack/drive/1/DiskActivity 864 540 16 16
|
||||
nodes/rack/drive/1/Floppy 881 540 16 16
|
||||
nodes/rack/drive/2/Default 898 540 16 16
|
||||
nodes/rack/drive/2/DiskActivity 915 540 16 16
|
||||
nodes/rack/drive/2/Floppy 932 540 16 16
|
||||
nodes/rack/drive/3/Default 949 540 16 16
|
||||
nodes/rack/drive/3/DiskActivity 966 540 16 16
|
||||
nodes/rack/drive/3/Floppy 983 540 16 16
|
||||
nodes/rack/drive/Floppy 1000 540 16 16
|
||||
nodes/rack/server/0/Default 134 567 16 16
|
||||
nodes/rack/server/0/DiskActivity 151 567 16 16
|
||||
nodes/rack/server/0/Error 168 567 16 16
|
||||
nodes/rack/server/0/NetworkActivity 185 567 16 16
|
||||
nodes/rack/server/0/On 202 567 16 16
|
||||
nodes/rack/server/1/Default 219 567 16 16
|
||||
nodes/rack/server/1/DiskActivity 236 567 16 16
|
||||
nodes/rack/server/1/Error 253 567 16 16
|
||||
nodes/rack/server/1/NetworkActivity 270 567 16 16
|
||||
nodes/rack/server/1/On 287 567 16 16
|
||||
nodes/rack/server/2/Default 304 567 16 16
|
||||
nodes/rack/server/2/DiskActivity 321 567 16 16
|
||||
nodes/rack/server/2/Error 338 567 16 16
|
||||
nodes/rack/server/2/NetworkActivity 355 567 16 16
|
||||
nodes/rack/server/2/On 372 567 16 16
|
||||
nodes/rack/server/3/Default 389 567 16 16
|
||||
nodes/rack/server/3/DiskActivity 406 567 16 16
|
||||
nodes/rack/server/3/Error 423 567 16 16
|
||||
nodes/rack/server/3/NetworkActivity 440 567 16 16
|
||||
nodes/rack/server/3/On 457 567 16 16
|
||||
nodes/raid/0/DiskActivity 474 567 16 16
|
||||
nodes/raid/0/Error 491 567 16 16
|
||||
nodes/raid/1/DiskActivity 508 567 16 16
|
||||
nodes/raid/1/Error 525 567 16 16
|
||||
nodes/raid/2/DiskActivity 542 567 16 16
|
||||
nodes/raid/2/Error 559 567 16 16
|
||||
nodes/raid/Default 576 567 16 16
|
||||
nodes/screen/BottomLeft 593 567 16 16
|
||||
nodes/screen/BottomMiddle 610 567 16 16
|
||||
nodes/screen/BottomRight 627 567 16 16
|
||||
nodes/screen/ColumnBottom 644 567 16 16
|
||||
nodes/screen/ColumnMiddle 661 567 16 16
|
||||
nodes/screen/ColumnTop 678 567 16 16
|
||||
nodes/screen/Middle 695 567 16 16
|
||||
nodes/screen/MiddleLeft 712 567 16 16
|
||||
nodes/screen/MiddleRight 729 567 16 16
|
||||
nodes/screen/PowerOnOverlay 746 567 16 16
|
||||
nodes/screen/RowLeft 763 567 16 16
|
||||
nodes/screen/RowMiddle 780 567 16 16
|
||||
nodes/screen/RowRight 797 567 16 16
|
||||
nodes/screen/Standalone 814 567 16 16
|
||||
nodes/screen/TopLeft 831 567 16 16
|
||||
nodes/screen/TopMiddle 848 567 16 16
|
||||
nodes/screen/TopRight 865 567 16 16
|
||||
panel/BorderB 327 314 4 4
|
||||
panel/BorderL 387 314 4 2
|
||||
panel/BorderR 332 314 4 4
|
||||
@ -306,4 +307,13 @@ window/rack/SideConnector 291 319 1 3
|
||||
window/rack/SideLeft 293 319 1 3
|
||||
window/rack/SideRight 295 319 1 3
|
||||
window/rack/SideTop 297 319 1 3
|
||||
window/raid/Slots 134 526 66 26
|
||||
window/raid/Slots 134 540 66 26
|
||||
window/tape/Back 315 699 20 15
|
||||
window/tape/BackPressed 336 699 20 15
|
||||
window/tape/Forward 357 699 20 15
|
||||
window/tape/ForwardPressed 378 699 20 15
|
||||
window/tape/Play 399 699 20 15
|
||||
window/tape/PlayPressed 420 699 20 15
|
||||
window/tape/Screen 134 526 146 13
|
||||
window/tape/Stop 441 699 20 15
|
||||
window/tape/StopPressed 462 699 20 15
|
||||
|
||||
@ -14,6 +14,10 @@ ocelot {
|
||||
# Set to 0.0 to disable sound completely.
|
||||
volumeMaster: 1.0
|
||||
|
||||
# Volume level for note block/sound card/tape drive sounds. Ranges from 0.0 to 1.0
|
||||
# Set to 0.0 to disable sound completely.
|
||||
volumeRecords: 1.0
|
||||
|
||||
# Volume level for computer case beeps. Ranges from 0.0 to 1.0
|
||||
# Set to 0.0 to disable sound completely.
|
||||
volumeBeep: 0.4
|
||||
@ -21,6 +25,9 @@ ocelot {
|
||||
# Volume level for environmental sounds (like computer fans or HDD activity). Ranges from 0.0 to 1.0
|
||||
# Set to 0.0 to disable sound completely.
|
||||
volumeEnvironment: 1.0
|
||||
|
||||
# Is it necessary to simulate the position of the sound relative to the workspace nodes or not
|
||||
positional: true
|
||||
}
|
||||
|
||||
window {
|
||||
|
||||
BIN
src/main/resources/ocelot/desktop/sounds/machine/tape_button.ogg
Normal file
BIN
src/main/resources/ocelot/desktop/sounds/machine/tape_eject.ogg
Normal file
BIN
src/main/resources/ocelot/desktop/sounds/machine/tape_insert.ogg
Normal file
BIN
src/main/resources/ocelot/desktop/sounds/machine/tape_rewind.ogg
Normal file
@ -16,11 +16,14 @@ class Settings(val config: Config) extends SettingsData {
|
||||
|
||||
brainCustomConfigPath = config.getOptionalString("ocelot.brain.customConfigPath")
|
||||
|
||||
volumeMaster = (config.getDouble("ocelot.sound.volumeMaster") max 0 min 1).toFloat
|
||||
volumeBeep = (config.getDouble("ocelot.sound.volumeBeep") max 0 min 1).toFloat
|
||||
volumeEnvironment = (config.getDouble("ocelot.sound.volumeEnvironment") max 0 min 1).toFloat
|
||||
volumeInterface = (config.getDoubleOrElse("ocelot.sound.volumeInterface", default = 0.5) max 0 min 1).toFloat
|
||||
volumeMaster = (config.getDoubleOrElse("ocelot.sound.volumeMaster", 1) max 0 min 1).toFloat
|
||||
volumeRecords = (config.getDoubleOrElse("ocelot.sound.volumeRecords", 1) max 0 min 1).toFloat
|
||||
volumeBeep = (config.getDoubleOrElse("ocelot.sound.volumeBeep", 1) max 0 min 1).toFloat
|
||||
volumeEnvironment = (config.getDoubleOrElse("ocelot.sound.volumeEnvironment", 1) max 0 min 1).toFloat
|
||||
volumeInterface = (config.getDoubleOrElse("ocelot.sound.volumeInterface", 0.5) max 0 min 1).toFloat
|
||||
|
||||
audioDisable = config.getBooleanOrElse("ocelot.sound.audioDisable", default = false)
|
||||
soundPositional = config.getBooleanOrElse("ocelot.sound.positional", default = true)
|
||||
logAudioErrorStacktrace = config.getBooleanOrElse("ocelot.sound.logAudioErrorStacktrace", default = false)
|
||||
|
||||
scaleFactor = (config.getDoubleOrElse("ocelot.window.scaleFactor", default = 1) max 1 min 3).toFloat
|
||||
@ -155,10 +158,12 @@ object Settings extends Logging {
|
||||
val updatedConfig = settings.config
|
||||
.withValuePreserveOrigin("ocelot.brain.customConfigPath", settings.brainCustomConfigPath)
|
||||
.withValuePreserveOrigin("ocelot.sound.volumeMaster", settings.volumeMaster)
|
||||
.withValuePreserveOrigin("ocelot.sound.volumeRecords", settings.volumeRecords)
|
||||
.withValuePreserveOrigin("ocelot.sound.volumeBeep", settings.volumeBeep)
|
||||
.withValuePreserveOrigin("ocelot.sound.volumeEnvironment", settings.volumeEnvironment)
|
||||
.withValuePreserveOrigin("ocelot.sound.volumeInterface", settings.volumeInterface)
|
||||
.withValuePreserveOrigin("ocelot.sound.audioDisable", settings.audioDisable)
|
||||
.withValuePreserveOrigin("ocelot.sound.positional", settings.soundPositional)
|
||||
.withValuePreserveOrigin("ocelot.sound.logAudioErrorStacktrace", settings.logAudioErrorStacktrace)
|
||||
.withValuePreserveOrigin("ocelot.window.scaleFactor", settings.scaleFactor.doubleValue)
|
||||
.withValue("ocelot.window.position", settings.windowPosition)
|
||||
|
||||
@ -58,12 +58,10 @@ object Audio extends Logging {
|
||||
}
|
||||
|
||||
AL10W.alSourcef(sourceId, AL10.AL_PITCH, source.pitch)
|
||||
AL10W.alSource3f(sourceId, AL10.AL_POSITION, 0f, 0f, 0f)
|
||||
AL10W.alSourcef(
|
||||
sourceId,
|
||||
AL10.AL_GAIN,
|
||||
source.volume * SoundCategory.getSettingsValue(source.soundCategory) * Settings.get.volumeMaster
|
||||
)
|
||||
|
||||
setPosition(sourceId, source)
|
||||
setGain(sourceId, source)
|
||||
|
||||
sources.put(source, sourceId)
|
||||
|
||||
sourceId
|
||||
@ -143,13 +141,9 @@ object Audio extends Logging {
|
||||
}
|
||||
|
||||
AL10W.alSourcef(sourceId, AL10.AL_PITCH, source.pitch)
|
||||
AL10W.alSource3f(sourceId, AL10.AL_POSITION, 0f, 0f, 0f)
|
||||
setPosition(sourceId, source)
|
||||
AL10W.alSourcei(sourceId, AL10.AL_LOOPING, if (source.looping) AL10.AL_TRUE else AL10.AL_FALSE)
|
||||
AL10W.alSourcef(
|
||||
sourceId,
|
||||
AL10.AL_GAIN,
|
||||
source.volume * SoundCategory.getSettingsValue(source.soundCategory) * Settings.get.volumeMaster
|
||||
)
|
||||
setGain(sourceId, source)
|
||||
AL10W.alSourcePlay(sourceId)
|
||||
|
||||
sources.put(source, sourceId)
|
||||
@ -159,9 +153,8 @@ object Audio extends Logging {
|
||||
|
||||
def pauseSource(source: SoundSource): Unit = synchronized {
|
||||
OpenAlException.ignoring {
|
||||
if (Audio.isDisabled) {
|
||||
if (Audio.isDisabled)
|
||||
return
|
||||
}
|
||||
|
||||
if (getSourceStatus(source) == SoundSource.Status.Paused)
|
||||
return
|
||||
@ -187,6 +180,18 @@ object Audio extends Logging {
|
||||
}
|
||||
}
|
||||
|
||||
private def setPosition(sourceId: Int, source: SoundSource): Unit = {
|
||||
AL10W.alSource3f(sourceId, AL10.AL_POSITION, source.position.x, source.position.y, source.position.z)
|
||||
}
|
||||
|
||||
private def setGain(sourceId: Int, source: SoundSource): Unit = {
|
||||
AL10W.alSourcef(
|
||||
sourceId,
|
||||
AL10.AL_GAIN,
|
||||
source.volume * SoundCategory.getSettingsValue(source.soundCategory) * Settings.get.volumeMaster
|
||||
)
|
||||
}
|
||||
|
||||
def update(): Unit = synchronized {
|
||||
if (isDisabled) return
|
||||
|
||||
@ -194,11 +199,8 @@ object Audio extends Logging {
|
||||
OpenAlException.defaulting(false) {
|
||||
cleanupSourceBuffers(sourceId)
|
||||
|
||||
AL10W.alSourcef(
|
||||
sourceId,
|
||||
AL10.AL_GAIN,
|
||||
source.volume * SoundCategory.getSettingsValue(source.soundCategory) * Settings.get.volumeMaster
|
||||
)
|
||||
setPosition(sourceId, source)
|
||||
setGain(sourceId, source)
|
||||
|
||||
AL10W.alGetSourcei(sourceId, AL10.AL_SOURCE_STATE) match {
|
||||
case AL10.AL_STOPPED =>
|
||||
|
||||
@ -24,8 +24,15 @@ object SoundBuffers extends Resource {
|
||||
load("/ocelot/desktop/sounds/machine/hdd_access5.ogg"),
|
||||
load("/ocelot/desktop/sounds/machine/hdd_access6.ogg"),
|
||||
)
|
||||
|
||||
lazy val MachineTapeButton: SoundBuffer = load("/ocelot/desktop/sounds/machine/tape_button.ogg")
|
||||
lazy val MachineTapeEject: SoundBuffer = load("/ocelot/desktop/sounds/machine/tape_eject.ogg")
|
||||
lazy val MachineTapeInsert: SoundBuffer = load("/ocelot/desktop/sounds/machine/tape_insert.ogg")
|
||||
lazy val MachineTapeRewind: SoundBuffer = load("/ocelot/desktop/sounds/machine/tape_rewind.ogg")
|
||||
|
||||
lazy val InterfaceClick: SoundBuffer = load("/ocelot/desktop/sounds/interface/click.ogg")
|
||||
lazy val InterfaceTick: SoundBuffer = load("/ocelot/desktop/sounds/interface/tick.ogg")
|
||||
|
||||
lazy val MinecraftClick: SoundBuffer = load("/ocelot/desktop/sounds/minecraft/click.ogg")
|
||||
lazy val MinecraftExplosion: SoundBuffer = load("/ocelot/desktop/sounds/minecraft/explosion.ogg")
|
||||
|
||||
|
||||
@ -4,10 +4,11 @@ import ocelot.desktop.Settings
|
||||
|
||||
//noinspection ScalaWeakerAccess
|
||||
object SoundCategory extends Enumeration {
|
||||
val Environment, Beep, Interface = Value
|
||||
val Environment, Beep, Interface, Records = Value
|
||||
|
||||
def getSettingsValue(soundCategory: SoundCategory.Value): Float = soundCategory match {
|
||||
case Environment => Settings.get.volumeEnvironment
|
||||
case Records => Settings.get.volumeRecords
|
||||
case Beep => Settings.get.volumeBeep
|
||||
case Interface => Settings.get.volumeInterface
|
||||
}
|
||||
|
||||
@ -6,7 +6,12 @@ import org.lwjgl.openal.AL10
|
||||
import java.nio.ByteBuffer
|
||||
import scala.util.control.Exception.catching
|
||||
|
||||
case class SoundSamples(data: ByteBuffer, rate: Int, format: SoundSamples.Format.Value) extends Logging {
|
||||
case class SoundSamples(private var data: ByteBuffer, rate: Int, format: SoundSamples.Format.Value) extends Logging {
|
||||
if (!data.isDirect) {
|
||||
data = ByteBuffer.allocateDirect(data.remaining()).put(data)
|
||||
data.flip()
|
||||
}
|
||||
|
||||
def genBuffer(): Option[Int] = Audio.synchronized {
|
||||
if (Audio.isDisabled) return None
|
||||
|
||||
@ -29,4 +34,4 @@ object SoundSamples {
|
||||
object Format extends Enumeration {
|
||||
val Stereo16, Mono8, Mono16 = Value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,14 +1,18 @@
|
||||
package ocelot.desktop.audio
|
||||
|
||||
import ocelot.desktop.geometry.Vector3D
|
||||
|
||||
import java.util.concurrent.TimeUnit
|
||||
import scala.concurrent.duration.Duration
|
||||
|
||||
class SoundSource(val kind: SoundSource.Kind,
|
||||
val soundCategory: SoundCategory.Value,
|
||||
val looping: Boolean,
|
||||
val pitch: Float,
|
||||
var volume: Float) {
|
||||
|
||||
class SoundSource(
|
||||
val kind: SoundSource.Kind,
|
||||
val soundCategory: SoundCategory.Value,
|
||||
val looping: Boolean,
|
||||
val pitch: Float,
|
||||
var volume: Float,
|
||||
var position: Vector3D = Vector3D(0, 0, 0)
|
||||
) {
|
||||
def duration: Option[Duration] = kind match {
|
||||
case SoundSource.KindSoundBuffer(buffer) =>
|
||||
Some(Duration(buffer.numSamples.toFloat / buffer.sampleRate, TimeUnit.SECONDS))
|
||||
|
||||
@ -82,7 +82,7 @@ class OpenFMRadio extends Entity with Environment with DeviceInfo with Logging {
|
||||
Format.Mono16
|
||||
|
||||
// Creating Ocelot output sound stream
|
||||
val (outputStream, outputSource) = Audio.newStream(SoundCategory.Environment, volume = volume)
|
||||
val (outputStream, outputSource) = Audio.newStream(SoundCategory.Records, volume = volume)
|
||||
playbackSoundSource = Option(outputSource)
|
||||
|
||||
// Reading chunks from input stream and passing them to output
|
||||
|
||||
@ -165,4 +165,5 @@ object IconSource {
|
||||
val Book: IconSource = IconSource("icons/Book")
|
||||
val Help: IconSource = IconSource("icons/Help")
|
||||
val Ocelot: IconSource = IconSource("icons/Ocelot")
|
||||
val Guitar: IconSource = IconSource("icons/Guitar")
|
||||
}
|
||||
|
||||
@ -1,18 +1,18 @@
|
||||
package ocelot.desktop.node
|
||||
|
||||
import ocelot.desktop.{ColorScheme, OcelotDesktop}
|
||||
import ocelot.desktop.audio._
|
||||
import ocelot.desktop.entity.OcelotCard
|
||||
import ocelot.desktop.geometry.FloatUtils.ExtendedFloat
|
||||
import ocelot.desktop.geometry.Vector2D
|
||||
import ocelot.desktop.graphics.Graphics
|
||||
import ocelot.desktop.inventory.SyncedInventory
|
||||
import ocelot.desktop.node.ComputerAwareNode.{ErrorMessageMoveSpeed, LogParticleCount, LogParticleGrow, LogParticleMaxAngle, LogParticleMoveDistance, LogParticleMoveSpeed, LogParticlePadding, LogParticleSize, MaxErrorMessageDistance, MaxLogParticles}
|
||||
import ocelot.desktop.node.ComputerAwareNode._
|
||||
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.util.Messages
|
||||
import ocelot.desktop.{ColorScheme, OcelotDesktop}
|
||||
import totoro.ocelot.brain.Settings
|
||||
import totoro.ocelot.brain.entity.traits.{Entity, Environment, WorkspaceAware}
|
||||
import totoro.ocelot.brain.event._
|
||||
@ -48,9 +48,9 @@ abstract class ComputerAwareNode(entity: Entity with Environment with WorkspaceA
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Scala has lazy vals. Use them.
|
||||
private var soundCardStream: SoundStream = _
|
||||
private var soundCardSource: SoundSource = _
|
||||
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) =>
|
||||
@ -74,11 +74,6 @@ abstract class ComputerAwareNode(entity: Entity with Environment with WorkspaceA
|
||||
|
||||
case BrainEvent(event: SoundCardAudioEvent) if !Audio.isDisabled =>
|
||||
val samples = SoundSamples(event.data, Settings.get.soundCardSampleRate, SoundSamples.Format.Mono8)
|
||||
if (soundCardStream == null) {
|
||||
val (stream, source) = Audio.newStream(SoundCategory.Beep)
|
||||
soundCardStream = stream
|
||||
soundCardSource = source
|
||||
}
|
||||
soundCardStream.enqueue(samples)
|
||||
soundCardSource.volume = event.volume
|
||||
|
||||
|
||||
@ -1,12 +1,8 @@
|
||||
package ocelot.desktop.node
|
||||
|
||||
trait LabeledEntityNode extends EntityNode with LabeledNode {
|
||||
protected def fallbackLabelAddress: Option[String] = Some(entity.node.address)
|
||||
|
||||
override def label: Option[String] =
|
||||
super.label
|
||||
.orElse(
|
||||
Option(entity.node)
|
||||
.flatMap(node => Option(node.address))
|
||||
.orElse(Some("unknown"))
|
||||
.filter(_ => exposeAddress)
|
||||
)
|
||||
super.label.orElse(if (exposeAddress) fallbackLabelAddress else None)
|
||||
}
|
||||
|
||||
@ -0,0 +1,42 @@
|
||||
package ocelot.desktop.node
|
||||
|
||||
import ocelot.desktop.audio.SoundSource
|
||||
import ocelot.desktop.geometry.Vector3D
|
||||
import ocelot.desktop.{OcelotDesktop, Settings}
|
||||
|
||||
trait PositionalSoundSourcesNode extends Node {
|
||||
// Every node can have multiple sound sources playing at the same time
|
||||
def soundSources: Seq[SoundSource]
|
||||
|
||||
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 rootWidthHalf = OcelotDesktop.root.width / 2
|
||||
val rootHeightHalf = OcelotDesktop.root.height / 2
|
||||
|
||||
val nodeCenterX = position.x + width / 2
|
||||
val nodeCenterY = position.y + height / 2
|
||||
|
||||
// Limiting "significance" of the position as a percentage value because on
|
||||
// large monitors the sound may become too "non-audiophile"
|
||||
val limit = 0.05f
|
||||
|
||||
soundPosition = Vector3D(
|
||||
(nodeCenterX - rootWidthHalf) / rootWidthHalf * limit,
|
||||
(nodeCenterY - rootHeightHalf) / rootHeightHalf * limit,
|
||||
0
|
||||
)
|
||||
}
|
||||
else {
|
||||
soundPosition = Vector3D.Zero
|
||||
}
|
||||
|
||||
for (soundSource <- soundSources)
|
||||
soundSource.position = soundPosition
|
||||
}
|
||||
}
|
||||
@ -48,6 +48,10 @@ class DiskDriveNode(entity: FloppyDiskDrive)
|
||||
)
|
||||
}
|
||||
|
||||
// -------------------------------- LabeledEntityNode --------------------------------
|
||||
|
||||
override def fallbackLabelAddress: Option[String] = entity.filesystemNode.map(_.address)
|
||||
|
||||
// ---------------------------- DiskDriveAware ----------------------------
|
||||
|
||||
override def floppyDiskDrive: FloppyDiskDrive = entity
|
||||
|
||||
@ -1,18 +1,21 @@
|
||||
package ocelot.desktop.node.nodes
|
||||
|
||||
import ocelot.desktop.graphics.IconSource
|
||||
import ocelot.desktop.ui.event.ClickEvent
|
||||
import ocelot.desktop.ui.widget.contextmenu.{ContextMenu, ContextMenuEntry, ContextMenuSubmenu}
|
||||
import ocelot.desktop.ui.widget.contextmenu.{ContextMenu, ContextMenuEntry, ContextMenuIcon, ContextMenuSubmenu}
|
||||
import totoro.ocelot.brain.entity.NoteBlock
|
||||
|
||||
class NoteBlockNode(val noteBlock: NoteBlock) extends NoteBlockNodeBase(noteBlock) {
|
||||
override def icon: String = "nodes/NoteBlock"
|
||||
|
||||
override def setupContextMenu(menu: ContextMenu, event: ClickEvent): Unit = {
|
||||
menu.addEntry(new ContextMenuSubmenu("Instrument") {
|
||||
menu.addEntry(new ContextMenuSubmenu("Instrument", Some(ContextMenuIcon(IconSource.Guitar))) {
|
||||
{
|
||||
val maxLen = NoteBlockNode.Instruments.map(_._2.length).max
|
||||
|
||||
for ((instrument, name) <- NoteBlockNode.Instruments) {
|
||||
val dot = if (noteBlock.instrument == instrument) '•' else ' '
|
||||
|
||||
addEntry(ContextMenuEntry(name.padTo(maxLen, ' ') + dot) {
|
||||
noteBlock.instrument = instrument
|
||||
})
|
||||
|
||||
@ -15,7 +15,7 @@ import scala.collection.mutable
|
||||
abstract class NoteBlockNodeBase(entity: Entity with Environment) extends EntityNode(entity) with LabeledEntityNode {
|
||||
eventHandlers += {
|
||||
case BrainEvent(event: NoteBlockTriggerEvent) =>
|
||||
SoundSource.fromBuffer(SoundBuffers.NoteBlock(event.instrument), SoundCategory.Beep,
|
||||
SoundSource.fromBuffer(SoundBuffers.NoteBlock(event.instrument), SoundCategory.Records,
|
||||
pitch = NoteBlockNode.Pitches(event.pitch), volume = event.volume.toFloat.min(1f).max(0f)).play()
|
||||
addParticle(event.pitch)
|
||||
}
|
||||
|
||||
@ -5,7 +5,7 @@ import ocelot.desktop.inventory.item.HddItem
|
||||
import ocelot.desktop.inventory.traits.DiskItem
|
||||
import ocelot.desktop.inventory.{Item, SyncedInventory}
|
||||
import ocelot.desktop.node.Node.{HighlightThickness, NoHighlightSize}
|
||||
import ocelot.desktop.node.{EntityNode, WindowedNode}
|
||||
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
|
||||
@ -21,6 +21,7 @@ import scala.util.Random
|
||||
class RaidNode(val raid: Raid) extends
|
||||
EntityNode(raid)
|
||||
with SyncedInventory
|
||||
with LabeledEntityNode
|
||||
with DiskActivityHandler
|
||||
with DefaultSlotItemsFillable
|
||||
with WindowedNode[RaidWindow]
|
||||
@ -34,7 +35,6 @@ class RaidNode(val raid: Raid) extends
|
||||
|
||||
val x = position.x + HighlightThickness
|
||||
val y = position.y + HighlightThickness
|
||||
val activityIndex = Random.between(0, raid.getSizeInventory)
|
||||
var prefix: String = null
|
||||
|
||||
for (i <- 0 until raid.getSizeInventory) {
|
||||
@ -56,6 +56,10 @@ class RaidNode(val raid: Raid) extends
|
||||
}
|
||||
}
|
||||
|
||||
// Required for disk activity events processing
|
||||
override def shouldReceiveEventsFor(address: String): Boolean =
|
||||
super.shouldReceiveEventsFor(address) || raid.filesystem.exists(_.node.address == address)
|
||||
|
||||
override def setupContextMenu(menu: ContextMenu, event: ClickEvent): Unit = {
|
||||
DiskItem.addRealPathContextMenuEntries(menu, raid, realPathSetter => {
|
||||
realPathSetter()
|
||||
@ -76,6 +80,10 @@ class RaidNode(val raid: Raid) extends
|
||||
super.setupContextMenu(menu, event)
|
||||
}
|
||||
|
||||
// -------------------------------- LabeledEntityNode --------------------------------
|
||||
|
||||
override def fallbackLabelAddress: Option[String] = raid.filesystem.map(_.node.address)
|
||||
|
||||
// -------------------------------- Inventory --------------------------------
|
||||
|
||||
override type I = Item with HddItem
|
||||
|
||||
@ -1,22 +1,84 @@
|
||||
package ocelot.desktop.node.nodes
|
||||
|
||||
import ocelot.desktop.audio._
|
||||
import ocelot.desktop.inventory.SyncedInventory
|
||||
import ocelot.desktop.inventory.item.TapeItem
|
||||
import ocelot.desktop.node.{EntityNode, LabeledEntityNode, WindowedNode}
|
||||
import ocelot.desktop.node.{EntityNode, LabeledEntityNode, PositionalSoundSourcesNode, WindowedNode}
|
||||
import ocelot.desktop.ui.event.BrainEvent
|
||||
import ocelot.desktop.windows.TapeDriveWindow
|
||||
import totoro.ocelot.brain.entity.TapeDrive
|
||||
import totoro.ocelot.brain.entity.tape.{AudioPacketDfpwm, Dfpwm}
|
||||
import totoro.ocelot.brain.entity.{TapeDrive, TapeDriveState}
|
||||
import totoro.ocelot.brain.event.{TapeDriveAudioEvent, TapeDriveStopEvent}
|
||||
|
||||
class TapeDriveNode(entity: TapeDrive)
|
||||
extends EntityNode(entity)
|
||||
class TapeDriveNode(val tapeDrive: TapeDrive)
|
||||
extends EntityNode(tapeDrive)
|
||||
with SyncedInventory
|
||||
with LabeledEntityNode
|
||||
with WindowedNode[TapeDriveWindow] {
|
||||
with PositionalSoundSourcesNode
|
||||
with WindowedNode[TapeDriveWindow]
|
||||
{
|
||||
|
||||
override def icon: String = "nodes/TapeDrive"
|
||||
|
||||
private lazy val soundTapeRewind: SoundSource = SoundSource.fromBuffer(
|
||||
SoundBuffers.MachineTapeRewind,
|
||||
SoundCategory.Records,
|
||||
looping = true
|
||||
)
|
||||
|
||||
private lazy val streams: (SoundStream, SoundSource) = Audio.newStream(SoundCategory.Records)
|
||||
private def stream: SoundStream = streams._1
|
||||
private def source: SoundSource = streams._2
|
||||
|
||||
private val codec = new Dfpwm
|
||||
|
||||
eventHandlers += {
|
||||
// FIXME: if the tape drive rapidly switches from Playing to another state and back to Playing,
|
||||
// we may get TapeDriveStopEvent and TapeDriveAudioEvent during the same brain update cycle.
|
||||
// however, since `source.stop()` defers removing the source until the next GUI update,
|
||||
// it can potentially remove the newly queued buffers too.
|
||||
case BrainEvent(_: TapeDriveStopEvent) => source.stop()
|
||||
|
||||
case BrainEvent(TapeDriveAudioEvent(_, pkt @ AudioPacketDfpwm(volume, frequency, _))) if !Audio.isDisabled =>
|
||||
// FIXME: OpenAL does not enjoy mixing buffers with different sample rates.
|
||||
// yet if the playback speed changes, it will affect the frequency.
|
||||
// thus we'll be enqueueing a buffer with a different sample rate than the previous buffers.
|
||||
// we'll get errors.
|
||||
val samples = SoundSamples(pkt.decode(codec), frequency, SoundSamples.Format.Mono8)
|
||||
stream.enqueue(samples)
|
||||
source.volume = volume / 127f
|
||||
}
|
||||
|
||||
override def update(): Unit = {
|
||||
super.update()
|
||||
|
||||
val isRewinding = tapeDrive.state.state == TapeDriveState.State.Rewinding || tapeDrive.state.state == TapeDriveState.State.Forwarding
|
||||
|
||||
if (!isRewinding && soundTapeRewind.isPlaying) {
|
||||
soundTapeRewind.stop()
|
||||
} else if (isRewinding && !soundTapeRewind.isPlaying && !Audio.isDisabled) {
|
||||
soundTapeRewind.play()
|
||||
}
|
||||
}
|
||||
|
||||
override def dispose(): Unit = {
|
||||
super.dispose()
|
||||
|
||||
soundTapeRewind.stop()
|
||||
}
|
||||
|
||||
// -------------------------------- PositionalSoundSourcesNode --------------------------------
|
||||
|
||||
override def soundSources: Seq[SoundSource] = Seq(source)
|
||||
|
||||
// -------------------------------- Inventory --------------------------------
|
||||
|
||||
override type I = TapeItem
|
||||
|
||||
override def brainInventory: TapeDrive = entity
|
||||
override def brainInventory: TapeDrive = tapeDrive
|
||||
|
||||
|
||||
// -------------------------------- Windowed --------------------------------
|
||||
|
||||
override protected def createWindow(): TapeDriveWindow = new TapeDriveWindow(this)
|
||||
}
|
||||
|
||||
@ -1,46 +1,65 @@
|
||||
package ocelot.desktop.ui.widget.settings
|
||||
|
||||
import ocelot.desktop.Settings
|
||||
import ocelot.desktop.geometry.{Padding2D, Size2D}
|
||||
import ocelot.desktop.geometry.Size2D
|
||||
import ocelot.desktop.graphics.IconSource
|
||||
import ocelot.desktop.ui.widget.{PaddingBox, Slider}
|
||||
import ocelot.desktop.ui.layout.LinearLayout
|
||||
import ocelot.desktop.ui.widget.{Checkbox, Slider}
|
||||
import ocelot.desktop.util.Orientation
|
||||
|
||||
class SoundSettingsTab extends SettingsTab {
|
||||
override val layout = new LinearLayout(this, orientation = Orientation.Vertical, gap = 8)
|
||||
|
||||
override val icon: IconSource = IconSource.SettingsSound
|
||||
override val label: String = "Sound"
|
||||
|
||||
children :+= new PaddingBox(new Slider(Settings.get.volumeMaster, "Master Volume") {
|
||||
private def addSlider(name: String, value: Float, valueSetter: Float => Unit): Unit = {
|
||||
children :+= new Slider(value, name) {
|
||||
override def minimumSize: Size2D = Size2D(512, 24)
|
||||
|
||||
override def onValueChanged(value: Float): Unit = {
|
||||
valueSetter(value)
|
||||
applySettings()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
addSlider(
|
||||
"Master volume",
|
||||
Settings.get.volumeMaster,
|
||||
Settings.get.volumeMaster = _
|
||||
)
|
||||
|
||||
addSlider(
|
||||
"Music blocks volume",
|
||||
Settings.get.volumeRecords,
|
||||
Settings.get.volumeRecords = _
|
||||
)
|
||||
|
||||
addSlider(
|
||||
"Environment volume",
|
||||
Settings.get.volumeEnvironment,
|
||||
Settings.get.volumeEnvironment = _
|
||||
)
|
||||
|
||||
addSlider(
|
||||
"Beep volume",
|
||||
Settings.get.volumeBeep,
|
||||
Settings.get.volumeBeep = _
|
||||
)
|
||||
|
||||
addSlider(
|
||||
"Interface volume",
|
||||
Settings.get.volumeInterface,
|
||||
Settings.get.volumeInterface = _
|
||||
)
|
||||
|
||||
children :+= new Checkbox("Use positional sound", Settings.get.soundPositional) {
|
||||
override def minimumSize: Size2D = Size2D(512, 24)
|
||||
|
||||
override def onValueChanged(value: Float): Unit = {
|
||||
Settings.get.volumeMaster = value
|
||||
override def onValueChanged(value: Boolean): Unit = {
|
||||
Settings.get.soundPositional = value
|
||||
applySettings()
|
||||
}
|
||||
}, Padding2D(bottom = 8))
|
||||
|
||||
children :+= new PaddingBox(new Slider(Settings.get.volumeBeep, "Beep Volume") {
|
||||
override def minimumSize: Size2D = Size2D(512, 24)
|
||||
|
||||
override def onValueChanged(value: Float): Unit = {
|
||||
Settings.get.volumeBeep = value
|
||||
}
|
||||
}, Padding2D(bottom = 8))
|
||||
|
||||
children :+= new PaddingBox(new Slider(Settings.get.volumeEnvironment, "Environment Volume") {
|
||||
override def minimumSize: Size2D = Size2D(512, 24)
|
||||
|
||||
override def onValueChanged(value: Float): Unit = {
|
||||
Settings.get.volumeEnvironment = value
|
||||
applySettings()
|
||||
}
|
||||
}, Padding2D(bottom = 8))
|
||||
|
||||
children :+= new PaddingBox(new Slider(Settings.get.volumeInterface, "Interface Volume") {
|
||||
override def minimumSize: Size2D = Size2D(512, 24)
|
||||
|
||||
override def onValueChanged(value: Float): Unit = {
|
||||
Settings.get.volumeInterface = value
|
||||
applySettings()
|
||||
}
|
||||
}, Padding2D(bottom = 8))
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,6 +4,8 @@ import org.apache.logging.log4j.Level
|
||||
import org.apache.logging.log4j.core.config.Configurator
|
||||
import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilderFactory
|
||||
|
||||
import java.nio.file.Files
|
||||
|
||||
trait LoggingConfiguration {
|
||||
{
|
||||
val builder = ConfigurationBuilderFactory.newConfigurationBuilder()
|
||||
@ -17,6 +19,8 @@ trait LoggingConfiguration {
|
||||
|
||||
builder.add(consoleAppender)
|
||||
|
||||
Files.createDirectories(OcelotPaths.desktopLog.getParent)
|
||||
|
||||
// File
|
||||
val fileAppender = builder.newAppender("file", "File")
|
||||
fileAppender.addAttribute("fileName", OcelotPaths.desktopLog)
|
||||
|
||||
@ -3,10 +3,11 @@ package ocelot.desktop.util
|
||||
import org.apache.commons.lang3.SystemUtils
|
||||
|
||||
import java.nio.file.{Path, Paths}
|
||||
import java.util.Objects
|
||||
|
||||
object OcelotPaths {
|
||||
def windowsAppDataDirectoryName: String = System.getenv("APPDATA")
|
||||
def linuxHomeDirectoryName: String = System.getProperty(SystemUtils.USER_HOME)
|
||||
def windowsAppDataDirectoryName: String = Objects.requireNonNull(System.getenv("APPDATA"), "%APPDATA% is null")
|
||||
def linuxHomeDirectoryName: String = Objects.requireNonNull(SystemUtils.USER_HOME, "USER_HOME is null")
|
||||
|
||||
def openComputersConfigName: String = {
|
||||
if (SystemUtils.IS_OS_WINDOWS)
|
||||
|
||||
@ -19,9 +19,11 @@ class SettingsData {
|
||||
|
||||
var volumeMaster: Float = 1f
|
||||
var volumeBeep: Float = 1f
|
||||
var volumeRecords: Float = 1f
|
||||
var volumeEnvironment: Float = 1f
|
||||
var volumeInterface: Float = 0.5f
|
||||
var audioDisable: Boolean = false
|
||||
var soundPositional: Boolean = false
|
||||
var logAudioErrorStacktrace: Boolean = false
|
||||
|
||||
var scaleFactor: Float = 1f
|
||||
|
||||
@ -1,19 +1,164 @@
|
||||
package ocelot.desktop.windows
|
||||
|
||||
import ocelot.desktop.geometry.Padding2D
|
||||
import ocelot.desktop.audio.{Audio, SoundBuffers, SoundCategory, SoundSource}
|
||||
import ocelot.desktop.color.{Color, RGBAColorNorm}
|
||||
import ocelot.desktop.geometry.{Padding2D, Size2D}
|
||||
import ocelot.desktop.graphics.Graphics
|
||||
import ocelot.desktop.inventory.item.TapeItem
|
||||
import ocelot.desktop.node.nodes.TapeDriveNode
|
||||
import ocelot.desktop.ui.widget.PaddingBox
|
||||
import ocelot.desktop.ui.layout.{AlignItems, Layout, LinearLayout}
|
||||
import ocelot.desktop.ui.widget.IconButton.{DefaultModel, ReadOnlyModel}
|
||||
import ocelot.desktop.ui.widget.slot.SlotWidget
|
||||
import ocelot.desktop.ui.widget.window.PanelWindow
|
||||
import ocelot.desktop.ui.widget.{IconButton, PaddingBox, Widget}
|
||||
import ocelot.desktop.util.Orientation
|
||||
import totoro.ocelot.brain.entity.TapeDriveState
|
||||
|
||||
class TapeDriveWindow(host: TapeDriveNode) extends PanelWindow {
|
||||
override protected def title: String = s"Tape Drive ${host.entity.node.address}"
|
||||
class TapeDriveWindow(val tapeDriveNode: TapeDriveNode) extends PanelWindow {
|
||||
private val playbackOverlayColor: RGBAColorNorm = RGBAColorNorm(1, 1, 1, 0.01f)
|
||||
|
||||
override def titleMaxLength: Int = 16
|
||||
private lazy val tapeButtonSound: SoundSource = SoundSource.fromBuffer(SoundBuffers.MachineTapeButton, SoundCategory.Environment)
|
||||
private lazy val tapeInsert: SoundSource = SoundSource.fromBuffer(SoundBuffers.MachineTapeInsert, SoundCategory.Environment)
|
||||
private lazy val tapeEject: SoundSource = SoundSource.fromBuffer(SoundBuffers.MachineTapeEject, SoundCategory.Environment)
|
||||
|
||||
override protected def title: String = s"Tape Drive ${tapeDriveNode.tapeDrive.node.address}"
|
||||
override def titleMaxLength: Int = 35
|
||||
|
||||
override def minimumSize: Size2D = Size2D(352, 182)
|
||||
override def maximumSize: Size2D = minimumSize
|
||||
|
||||
setInner(new PaddingBox(
|
||||
new SlotWidget[TapeItem](host.Slot(0)),
|
||||
Padding2D(8, 64, 8, 64)
|
||||
new Widget {
|
||||
override protected val layout: Layout = new LinearLayout(this, orientation = Orientation.Vertical, gap = 16, alignItems = AlignItems.Center)
|
||||
|
||||
// Screen
|
||||
children :+= new Widget {
|
||||
override def minimumSize: Size2D = Size2D(292, 26)
|
||||
override def maximumSize: Size2D = minimumSize
|
||||
|
||||
override def draw(g: Graphics): Unit = {
|
||||
// Screen background
|
||||
g.sprite(
|
||||
"window/tape/Screen",
|
||||
bounds
|
||||
)
|
||||
|
||||
// A barely noticeable overlay showing the playback progress
|
||||
// Btw Computronix doesn't have this feature, so I won't ruin the canon and make it too annoying
|
||||
val playedPart = tapeDriveNode.tapeDrive.position.toFloat / tapeDriveNode.tapeDrive.size.toFloat
|
||||
|
||||
if (playedPart > 0) {
|
||||
val offset = 2
|
||||
|
||||
g.rect(
|
||||
position.x + offset,
|
||||
position.y + offset,
|
||||
(width - offset * 2) * playedPart,
|
||||
height - offset * 2,
|
||||
playbackOverlayColor
|
||||
)
|
||||
}
|
||||
|
||||
// Screen text
|
||||
g.background = Color.Transparent
|
||||
|
||||
var text: String = null
|
||||
|
||||
if (tapeDriveNode.tapeDrive.tape.isDefined) {
|
||||
g.foreground = Color.White
|
||||
text = tapeDriveNode.tapeDrive.storageName
|
||||
|
||||
if (text.isEmpty)
|
||||
text = "Unnamed tape"
|
||||
}
|
||||
else {
|
||||
g.foreground = Color.Red
|
||||
text = "No tape"
|
||||
}
|
||||
|
||||
// Clipping text if it's too long
|
||||
val textMaxLength = 32
|
||||
|
||||
if (text.length > textMaxLength)
|
||||
text = text.take(textMaxLength - 1) + "…"
|
||||
|
||||
// Finally drawing it
|
||||
val textWidth = text.map(g.font.charWidth(_)).sum
|
||||
val textHeight = 16
|
||||
|
||||
g.text(position.x + width / 2 - textWidth / 2, position.y + height / 2 - textHeight / 2, text)
|
||||
}
|
||||
}
|
||||
|
||||
// Slot
|
||||
children :+= new SlotWidget[TapeItem](tapeDriveNode.Slot(0)) {
|
||||
override def onItemAdded(): Unit = {
|
||||
super.onItemAdded()
|
||||
|
||||
if (!Audio.isDisabled)
|
||||
tapeInsert.play()
|
||||
}
|
||||
|
||||
override def onItemRemoved(removedItem: TapeItem, replacedBy: Option[TapeItem]): Unit = {
|
||||
super.onItemRemoved(removedItem, replacedBy)
|
||||
|
||||
if (!Audio.isDisabled)
|
||||
tapeEject.play()
|
||||
}
|
||||
}
|
||||
|
||||
// Buttons
|
||||
children :+= new Widget {
|
||||
override protected val layout: Layout = new LinearLayout(this, gap = 0)
|
||||
|
||||
def addButton(
|
||||
sprite: String,
|
||||
pressedState: TapeDriveState.State,
|
||||
isToggle: Boolean = true
|
||||
): Unit = {
|
||||
children :+= new IconButton(
|
||||
s"window/tape/$sprite",
|
||||
s"window/tape/${sprite}Pressed",
|
||||
sizeMultiplier = 2,
|
||||
mode =
|
||||
if (isToggle)
|
||||
IconButton.Mode.Switch
|
||||
else
|
||||
IconButton.Mode.Regular,
|
||||
model =
|
||||
if (isToggle)
|
||||
ReadOnlyModel(tapeDriveNode.tapeDrive.state.state == pressedState)
|
||||
else
|
||||
DefaultModel(false),
|
||||
) {
|
||||
override def onPressed(): Unit = tapeDriveNode.tapeDrive.state.switchState(pressedState)
|
||||
|
||||
protected override def clickSoundSource: SoundSource = tapeButtonSound
|
||||
}
|
||||
}
|
||||
|
||||
addButton(
|
||||
"Back",
|
||||
TapeDriveState.State.Rewinding
|
||||
)
|
||||
|
||||
addButton(
|
||||
"Play",
|
||||
TapeDriveState.State.Playing
|
||||
)
|
||||
|
||||
addButton(
|
||||
"Stop",
|
||||
TapeDriveState.State.Stopped,
|
||||
false
|
||||
)
|
||||
|
||||
addButton(
|
||||
"Forward",
|
||||
TapeDriveState.State.Forwarding
|
||||
)
|
||||
}
|
||||
},
|
||||
Padding2D(top = 10, left = 18)
|
||||
))
|
||||
}
|
||||
|
||||