Add an ocelot block

Closes #131.
This commit is contained in:
Fingercomp 2024-08-28 00:37:53 +07:00
parent a609ae8cb4
commit e994011cd9
No known key found for this signature in database
GPG Key ID: BBC71CEE45D86E37
45 changed files with 898 additions and 663 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 644 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 145 KiB

After

Width:  |  Height:  |  Size: 147 KiB

View File

@ -1,324 +1,327 @@
BackgroundPattern 0 0 304 304 BackgroundPattern 0 0 304 304
BarSegment 385 434 16 4 BarSegment 385 434 16 4
Empty 249 655 16 16 Empty 134 618 16 16
EmptySlot 220 707 18 18 EmptySlot 237 567 18 18
Knob 203 434 50 50 Knob 203 434 50 50
KnobCenter 254 434 50 50 KnobCenter 254 434 50 50
KnobLimits 305 434 50 50 KnobLimits 305 434 50 50
Loading 0 305 48 448 Loading 0 305 48 448
Logo 305 0 168 200 Logo 305 0 168 200
ShadowBorder 279 305 1 24 ShadowBorder 201 540 1 24
ShadowCorner 216 674 24 24 ShadowCorner 233 674 24 24
TabArrow 134 634 8 14 TabArrow 225 600 8 14
blocks/Generic 266 655 16 16 blocks/Generic 151 618 16 16
blocks/HologramEffect 287 314 4 4 blocks/HologramEffect 209 549 4 4
blocks/HologramProjector1Top 283 655 16 16 blocks/HologramProjector1Top 168 618 16 16
blocks/HologramProjector2Top 300 655 16 16 blocks/HologramProjector2Top 185 618 16 16
blocks/HologramProjectorSide 317 655 16 16 blocks/HologramProjectorSide 202 618 16 16
buttons/BottomDrawerClose 239 707 18 18 buttons/BottomDrawerClose 256 567 18 18
buttons/BottomDrawerOpen 258 707 18 18 buttons/BottomDrawerOpen 275 567 18 18
buttons/OpenFMRadioCloseOff 404 445 7 8 buttons/OpenFMRadioCloseOff 404 445 7 8
buttons/OpenFMRadioCloseOn 412 445 7 8 buttons/OpenFMRadioCloseOn 412 445 7 8
buttons/OpenFMRadioRedstoneOff 359 445 8 8 buttons/OpenFMRadioRedstoneOff 359 445 8 8
buttons/OpenFMRadioRedstoneOn 368 445 8 8 buttons/OpenFMRadioRedstoneOn 368 445 8 8
buttons/OpenFMRadioStartOff 241 674 24 24 buttons/OpenFMRadioStartOff 258 674 24 24
buttons/OpenFMRadioStartOn 266 674 24 24 buttons/OpenFMRadioStartOn 283 674 24 24
buttons/OpenFMRadioStopOff 291 674 24 24 buttons/OpenFMRadioStopOff 308 674 24 24
buttons/OpenFMRadioStopOn 316 674 24 24 buttons/OpenFMRadioStopOn 333 674 24 24
buttons/OpenFMRadioVolumeDownOff 143 634 10 10 buttons/OpenFMRadioVolumeDownOff 234 600 10 10
buttons/OpenFMRadioVolumeDownOn 154 634 10 10 buttons/OpenFMRadioVolumeDownOn 245 600 10 10
buttons/OpenFMRadioVolumeUpOff 165 634 10 10 buttons/OpenFMRadioVolumeUpOff 256 600 10 10
buttons/OpenFMRadioVolumeUpOn 176 634 10 10 buttons/OpenFMRadioVolumeUpOn 267 600 10 10
buttons/PowerOff 277 707 18 18 buttons/PowerOff 294 567 18 18
buttons/PowerOn 296 707 18 18 buttons/PowerOn 313 567 18 18
buttons/RackRelayOff 117 655 65 18 buttons/RackRelayOff 134 655 65 18
buttons/RackRelayOn 183 655 65 18 buttons/RackRelayOn 200 655 65 18
icons/Antenna 334 655 16 16 icons/Antenna 219 618 16 16
icons/ArrowRight 351 655 16 16 icons/ArrowRight 236 618 16 16
icons/AspectRatio 368 655 16 16 icons/AspectRatio 253 618 16 16
icons/Book 385 655 16 16 icons/Book 270 618 16 16
icons/ButtonCheck 341 674 17 17 icons/ButtonCheck 134 600 17 17
icons/ButtonClipboard 359 674 17 17 icons/ButtonClipboard 152 600 17 17
icons/ButtonRandomize 377 674 17 17 icons/ButtonRandomize 170 600 17 17
icons/CPU 402 655 16 16 icons/CPU 287 618 16 16
icons/Card 419 655 16 16 icons/Card 304 618 16 16
icons/Close 134 601 15 14 icons/Close 209 600 15 14
icons/Code 436 655 16 16 icons/Code 321 618 16 16
icons/ComponentBus 453 655 16 16 icons/ComponentBus 338 618 16 16
icons/Copy 470 655 16 16 icons/Copy 355 618 16 16
icons/Cross 487 655 16 16 icons/Cross 372 618 16 16
icons/Delete 504 655 16 16 icons/Delete 389 618 16 16
icons/DragLMB 483 707 21 14 icons/DragLMB 500 567 21 14
icons/DragRMB 505 707 21 14 icons/DragRMB 522 567 21 14
icons/EEPROM 521 655 16 16 icons/EEPROM 406 618 16 16
icons/Edit 538 655 16 16 icons/Edit 423 618 16 16
icons/Eject 555 655 16 16 icons/Eject 440 618 16 16
icons/File 572 655 16 16 icons/File 457 618 16 16
icons/Floppy 589 655 16 16 icons/Floppy 474 618 16 16
icons/Folder 606 655 16 16 icons/Folder 491 618 16 16
icons/FolderSlash 623 655 16 16 icons/FolderSlash 508 618 16 16
icons/Grid 151 707 22 22 icons/Grid 168 567 22 22
icons/GridOff 174 707 22 22 icons/GridOff 191 567 22 22
icons/Guitar 640 655 16 16 icons/Guitar 525 618 16 16
icons/HDD 657 655 16 16 icons/HDD 542 618 16 16
icons/Help 674 655 16 16 icons/Help 559 618 16 16
icons/Home 197 707 22 22 icons/Home 214 567 22 22
icons/Keyboard 691 655 16 16 icons/Keyboard 576 618 16 16
icons/KeyboardOff 708 655 16 16 icons/KeyboardOff 593 618 16 16
icons/LMB 218 616 11 14 icons/LMB 350 655 11 14
icons/Label 725 655 16 16 icons/Label 610 618 16 16
icons/LinesHorizontal 742 655 16 16 icons/LinesHorizontal 627 618 16 16
icons/Link 759 655 16 16 icons/Link 644 618 16 16
icons/LinkSlash 776 655 16 16 icons/LinkSlash 661 618 16 16
icons/Memory 793 655 16 16 icons/Memory 678 618 16 16
icons/Microchip 810 655 16 16 icons/Microchip 695 618 16 16
icons/NA 827 655 16 16 icons/NA 712 618 16 16
icons/NotificationError 242 616 11 11 icons/NotificationError 374 655 11 11
icons/NotificationInfo 254 616 11 11 icons/NotificationInfo 386 655 11 11
icons/NotificationWarning 266 616 11 11 icons/NotificationWarning 398 655 11 11
icons/Ocelot 844 655 16 16 icons/Ocelot 729 618 16 16
icons/Pin 173 616 14 14 icons/Pin 305 655 14 14
icons/Plus 861 655 16 16 icons/Plus 746 618 16 16
icons/Power 878 655 16 16 icons/Power 763 618 16 16
icons/RMB 230 616 11 14 icons/RMB 362 655 11 14
icons/Restart 895 655 16 16 icons/Restart 780 618 16 16
icons/Save 912 655 16 16 icons/Save 797 618 16 16
icons/SaveAs 929 655 16 16 icons/SaveAs 814 618 16 16
icons/Server 946 655 16 16 icons/Server 831 618 16 16
icons/SettingsSound 134 616 12 17 icons/SettingsSound 266 655 12 17
icons/SettingsSystem 147 616 12 17 icons/SettingsSystem 279 655 12 17
icons/SettingsUI 160 616 12 17 icons/SettingsUI 292 655 12 17
icons/Tier0 963 655 16 16 icons/Tier0 848 618 16 16
icons/Tier1 980 655 16 16 icons/Tier1 865 618 16 16
icons/Tier2 997 655 16 16 icons/Tier2 882 618 16 16
icons/Tiers 201 540 16 16 icons/Tiers 899 618 16 16
icons/Unpin 188 616 14 14 icons/Unpin 320 655 14 14
icons/WaveLFSR 833 584 24 10 icons/WaveLFSR 901 707 24 10
icons/WaveNoise 858 584 24 10 icons/WaveNoise 926 707 24 10
icons/WaveSawtooth 883 584 24 10 icons/WaveSawtooth 951 707 24 10
icons/WaveSine 908 584 24 10 icons/WaveSine 976 707 24 10
icons/WaveSquare 933 584 24 10 icons/WaveSquare 134 724 24 10
icons/WaveTriangle 958 584 24 10 icons/WaveTriangle 159 724 24 10
icons/Window 218 540 16 16 icons/Window 916 618 16 16
icons/WireArrowLeft 281 305 4 8 icons/WireArrowLeft 203 540 4 8
icons/WireArrowRight 286 305 4 8 icons/WireArrowRight 208 540 4 8
items/APU0 49 655 16 96 items/APU0 49 655 16 96
items/APU1 66 655 16 96 items/APU1 66 655 16 96
items/APU2 83 655 16 96 items/APU2 83 655 16 96
items/CPU0 235 540 16 16 items/CPU0 933 618 16 16
items/CPU1 252 540 16 16 items/CPU1 950 618 16 16
items/CPU2 269 540 16 16 items/CPU2 967 618 16 16
items/CardBase 286 540 16 16 items/CardBase 984 618 16 16
items/CircuitBoard 303 540 16 16 items/CircuitBoard 1001 618 16 16
items/ComponentBus0 320 540 16 16 items/ComponentBus0 134 635 16 16
items/ComponentBus1 337 540 16 16 items/ComponentBus1 151 635 16 16
items/ComponentBus2 354 540 16 16 items/ComponentBus2 168 635 16 16
items/ComponentBus3 371 540 16 16 items/ComponentBus3 185 635 16 16
items/DataCard0 49 526 16 128 items/DataCard0 49 526 16 128
items/DataCard1 66 526 16 128 items/DataCard1 66 526 16 128
items/DataCard2 83 526 16 128 items/DataCard2 83 526 16 128
items/DebugCard 388 540 16 16 items/DebugCard 202 635 16 16
items/DiskDriveMountable 405 540 16 16 items/DiskDriveMountable 219 635 16 16
items/EEPROM 422 540 16 16 items/EEPROM 236 635 16 16
items/FloppyDisk_dyeBlack 439 540 16 16 items/FloppyDisk_dyeBlack 253 635 16 16
items/FloppyDisk_dyeBlue 456 540 16 16 items/FloppyDisk_dyeBlue 270 635 16 16
items/FloppyDisk_dyeBrown 473 540 16 16 items/FloppyDisk_dyeBrown 287 635 16 16
items/FloppyDisk_dyeCyan 490 540 16 16 items/FloppyDisk_dyeCyan 304 635 16 16
items/FloppyDisk_dyeGray 507 540 16 16 items/FloppyDisk_dyeGray 321 635 16 16
items/FloppyDisk_dyeGreen 524 540 16 16 items/FloppyDisk_dyeGreen 338 635 16 16
items/FloppyDisk_dyeLightBlue 541 540 16 16 items/FloppyDisk_dyeLightBlue 355 635 16 16
items/FloppyDisk_dyeLightGray 558 540 16 16 items/FloppyDisk_dyeLightGray 372 635 16 16
items/FloppyDisk_dyeLime 575 540 16 16 items/FloppyDisk_dyeLime 389 635 16 16
items/FloppyDisk_dyeMagenta 592 540 16 16 items/FloppyDisk_dyeMagenta 406 635 16 16
items/FloppyDisk_dyeOrange 609 540 16 16 items/FloppyDisk_dyeOrange 423 635 16 16
items/FloppyDisk_dyePink 626 540 16 16 items/FloppyDisk_dyePink 440 635 16 16
items/FloppyDisk_dyePurple 643 540 16 16 items/FloppyDisk_dyePurple 457 635 16 16
items/FloppyDisk_dyeRed 660 540 16 16 items/FloppyDisk_dyeRed 474 635 16 16
items/FloppyDisk_dyeWhite 677 540 16 16 items/FloppyDisk_dyeWhite 491 635 16 16
items/FloppyDisk_dyeYellow 694 540 16 16 items/FloppyDisk_dyeYellow 508 635 16 16
items/GraphicsCard0 711 540 16 16 items/GraphicsCard0 525 635 16 16
items/GraphicsCard1 728 540 16 16 items/GraphicsCard1 542 635 16 16
items/GraphicsCard2 745 540 16 16 items/GraphicsCard2 559 635 16 16
items/HardDiskDrive0 762 540 16 16 items/HardDiskDrive0 576 635 16 16
items/HardDiskDrive1 779 540 16 16 items/HardDiskDrive1 593 635 16 16
items/HardDiskDrive2 796 540 16 16 items/HardDiskDrive2 610 635 16 16
items/InternetCard 117 707 16 32 items/InternetCard 134 567 16 32
items/LinkedCard 100 655 16 96 items/LinkedCard 100 655 16 96
items/Memory0 813 540 16 16 items/Memory0 627 635 16 16
items/Memory1 830 540 16 16 items/Memory1 644 635 16 16
items/Memory2 847 540 16 16 items/Memory2 661 635 16 16
items/Memory3 864 540 16 16 items/Memory3 678 635 16 16
items/Memory4 881 540 16 16 items/Memory4 695 635 16 16
items/Memory5 898 540 16 16 items/Memory5 712 635 16 16
items/Memory6 915 540 16 16 items/Memory6 729 635 16 16
items/NetworkCard 932 540 16 16 items/NetworkCard 746 635 16 16
items/OcelotCard 100 526 16 128 items/OcelotCard 100 526 16 128
items/RedstoneCard0 949 540 16 16 items/RedstoneCard0 763 635 16 16
items/RedstoneCard1 966 540 16 16 items/RedstoneCard1 780 635 16 16
items/SelfDestructingCard 134 707 16 32 items/SelfDestructingCard 151 567 16 32
items/Server0 983 540 16 16 items/Server0 797 635 16 16
items/Server1 1000 540 16 16 items/Server1 814 635 16 16
items/Server2 134 567 16 16 items/Server2 831 635 16 16
items/Server3 151 567 16 16 items/Server3 848 635 16 16
items/SoundCard 117 526 16 128 items/SoundCard 117 526 16 128
items/TapeCopper 168 567 16 16 items/TapeCopper 865 635 16 16
items/TapeDiamond 185 567 16 16 items/TapeDiamond 882 635 16 16
items/TapeGold 202 567 16 16 items/TapeGold 899 635 16 16
items/TapeGreg 219 567 16 16 items/TapeGreg 916 635 16 16
items/TapeIg 236 567 16 16 items/TapeIg 933 635 16 16
items/TapeIron 253 567 16 16 items/TapeIron 950 635 16 16
items/TapeNetherStar 270 567 16 16 items/TapeNetherStar 967 635 16 16
items/TapeSteel 287 567 16 16 items/TapeSteel 984 635 16 16
items/WirelessNetworkCard0 304 567 16 16 items/WirelessNetworkCard0 1001 635 16 16
items/WirelessNetworkCard1 321 567 16 16 items/WirelessNetworkCard1 358 674 16 16
light-panel/BookmarkLeft 814 584 18 14 light-panel/BookmarkLeft 882 707 18 14
light-panel/BookmarkRight 395 674 20 14 light-panel/BookmarkRight 188 600 20 14
light-panel/BorderB 292 314 4 4 light-panel/BorderB 214 549 4 4
light-panel/BorderL 382 314 4 2 light-panel/BorderL 304 549 4 2
light-panel/BorderR 297 314 4 4 light-panel/BorderR 219 549 4 4
light-panel/BorderT 302 314 4 4 light-panel/BorderT 224 549 4 4
light-panel/CornerBL 307 314 4 4 light-panel/CornerBL 229 549 4 4
light-panel/CornerBR 312 314 4 4 light-panel/CornerBR 234 549 4 4
light-panel/CornerTL 317 314 4 4 light-panel/CornerTL 239 549 4 4
light-panel/CornerTR 322 314 4 4 light-panel/CornerTR 244 549 4 4
light-panel/Fill 285 325 2 2 light-panel/Fill 207 560 2 2
light-panel/Vent 356 434 2 38 light-panel/Vent 356 434 2 38
nodes/Cable 377 445 8 8 nodes/Cable 377 445 8 8
nodes/Camera 338 567 16 16 nodes/Camera 375 674 16 16
nodes/Chest 203 616 14 14 nodes/Chest 335 655 14 14
nodes/HologramProjector0 355 567 16 16 nodes/HologramProjector0 392 674 16 16
nodes/HologramProjector1 372 567 16 16 nodes/HologramProjector1 409 674 16 16
nodes/IronNoteBlock 389 567 16 16 nodes/IronNoteBlock 426 674 16 16
nodes/Lamp 406 567 16 16 nodes/Lamp 443 674 16 16
nodes/LampFrame 423 567 16 16 nodes/LampFrame 460 674 16 16
nodes/LampGlow 49 305 128 128 nodes/LampGlow 49 305 128 128
nodes/NewNode 440 567 16 16 nodes/NewNode 477 674 16 16
nodes/NoteBlock 457 567 16 16 nodes/NoteBlock 494 674 16 16
nodes/OpenFMRadio 474 567 16 16 nodes/OpenFMRadio 511 674 16 16
nodes/Relay 491 567 16 16 nodes/Relay 528 674 16 16
nodes/TapeDrive 508 567 16 16 nodes/TapeDrive 545 674 16 16
nodes/computer/Default 525 567 16 16 nodes/computer/Default 562 674 16 16
nodes/computer/DiskActivity 542 567 16 16 nodes/computer/DiskActivity 579 674 16 16
nodes/computer/Error 559 567 16 16 nodes/computer/Error 596 674 16 16
nodes/computer/On 576 567 16 16 nodes/computer/On 613 674 16 16
nodes/disk-drive/Default 593 567 16 16 nodes/disk-drive/Default 630 674 16 16
nodes/disk-drive/DiskActivity 610 567 16 16 nodes/disk-drive/DiskActivity 647 674 16 16
nodes/disk-drive/Floppy 627 567 16 16 nodes/disk-drive/Floppy 664 674 16 16
nodes/holidays/Christmas 117 674 32 32 nodes/holidays/Christmas 134 674 32 32
nodes/holidays/Halloween 150 674 32 32 nodes/holidays/Halloween 167 674 32 32
nodes/holidays/Valentines 183 674 32 32 nodes/holidays/Valentines 200 674 32 32
nodes/microcontroller/Default 644 567 16 16 nodes/microcontroller/Default 681 674 16 16
nodes/microcontroller/Error 661 567 16 16 nodes/microcontroller/Error 698 674 16 16
nodes/microcontroller/On 678 567 16 16 nodes/microcontroller/On 715 674 16 16
nodes/rack/Default 695 567 16 16 nodes/ocelot-block/Default 117 655 16 80
nodes/rack/Empty 712 567 16 16 nodes/ocelot-block/Rx 732 674 16 16
nodes/rack/drive/0/Default 729 567 16 16 nodes/ocelot-block/Tx 749 674 16 16
nodes/rack/drive/0/DiskActivity 746 567 16 16 nodes/rack/Default 766 674 16 16
nodes/rack/drive/0/Floppy 763 567 16 16 nodes/rack/Empty 783 674 16 16
nodes/rack/drive/1/Default 780 567 16 16 nodes/rack/drive/0/Default 800 674 16 16
nodes/rack/drive/1/DiskActivity 797 567 16 16 nodes/rack/drive/0/DiskActivity 817 674 16 16
nodes/rack/drive/1/Floppy 814 567 16 16 nodes/rack/drive/0/Floppy 834 674 16 16
nodes/rack/drive/2/Default 831 567 16 16 nodes/rack/drive/1/Default 851 674 16 16
nodes/rack/drive/2/DiskActivity 848 567 16 16 nodes/rack/drive/1/DiskActivity 868 674 16 16
nodes/rack/drive/2/Floppy 865 567 16 16 nodes/rack/drive/1/Floppy 885 674 16 16
nodes/rack/drive/3/Default 882 567 16 16 nodes/rack/drive/2/Default 902 674 16 16
nodes/rack/drive/3/DiskActivity 899 567 16 16 nodes/rack/drive/2/DiskActivity 919 674 16 16
nodes/rack/drive/3/Floppy 916 567 16 16 nodes/rack/drive/2/Floppy 936 674 16 16
nodes/rack/drive/Floppy 933 567 16 16 nodes/rack/drive/3/Default 953 674 16 16
nodes/rack/server/0/Default 950 567 16 16 nodes/rack/drive/3/DiskActivity 970 674 16 16
nodes/rack/server/0/DiskActivity 967 567 16 16 nodes/rack/drive/3/Floppy 987 674 16 16
nodes/rack/server/0/Error 984 567 16 16 nodes/rack/drive/Floppy 1004 674 16 16
nodes/rack/server/0/NetworkActivity 1001 567 16 16 nodes/rack/server/0/Default 134 707 16 16
nodes/rack/server/0/On 134 584 16 16 nodes/rack/server/0/DiskActivity 151 707 16 16
nodes/rack/server/1/Default 151 584 16 16 nodes/rack/server/0/Error 168 707 16 16
nodes/rack/server/1/DiskActivity 168 584 16 16 nodes/rack/server/0/NetworkActivity 185 707 16 16
nodes/rack/server/1/Error 185 584 16 16 nodes/rack/server/0/On 202 707 16 16
nodes/rack/server/1/NetworkActivity 202 584 16 16 nodes/rack/server/1/Default 219 707 16 16
nodes/rack/server/1/On 219 584 16 16 nodes/rack/server/1/DiskActivity 236 707 16 16
nodes/rack/server/2/Default 236 584 16 16 nodes/rack/server/1/Error 253 707 16 16
nodes/rack/server/2/DiskActivity 253 584 16 16 nodes/rack/server/1/NetworkActivity 270 707 16 16
nodes/rack/server/2/Error 270 584 16 16 nodes/rack/server/1/On 287 707 16 16
nodes/rack/server/2/NetworkActivity 287 584 16 16 nodes/rack/server/2/Default 304 707 16 16
nodes/rack/server/2/On 304 584 16 16 nodes/rack/server/2/DiskActivity 321 707 16 16
nodes/rack/server/3/Default 321 584 16 16 nodes/rack/server/2/Error 338 707 16 16
nodes/rack/server/3/DiskActivity 338 584 16 16 nodes/rack/server/2/NetworkActivity 355 707 16 16
nodes/rack/server/3/Error 355 584 16 16 nodes/rack/server/2/On 372 707 16 16
nodes/rack/server/3/NetworkActivity 372 584 16 16 nodes/rack/server/3/Default 389 707 16 16
nodes/rack/server/3/On 389 584 16 16 nodes/rack/server/3/DiskActivity 406 707 16 16
nodes/raid/0/DiskActivity 406 584 16 16 nodes/rack/server/3/Error 423 707 16 16
nodes/raid/0/Error 423 584 16 16 nodes/rack/server/3/NetworkActivity 440 707 16 16
nodes/raid/1/DiskActivity 440 584 16 16 nodes/rack/server/3/On 457 707 16 16
nodes/raid/1/Error 457 584 16 16 nodes/raid/0/DiskActivity 474 707 16 16
nodes/raid/2/DiskActivity 474 584 16 16 nodes/raid/0/Error 491 707 16 16
nodes/raid/2/Error 491 584 16 16 nodes/raid/1/DiskActivity 508 707 16 16
nodes/raid/Default 508 584 16 16 nodes/raid/1/Error 525 707 16 16
nodes/screen/BottomLeft 525 584 16 16 nodes/raid/2/DiskActivity 542 707 16 16
nodes/screen/BottomMiddle 542 584 16 16 nodes/raid/2/Error 559 707 16 16
nodes/screen/BottomRight 559 584 16 16 nodes/raid/Default 576 707 16 16
nodes/screen/ColumnBottom 576 584 16 16 nodes/screen/BottomLeft 593 707 16 16
nodes/screen/ColumnMiddle 593 584 16 16 nodes/screen/BottomMiddle 610 707 16 16
nodes/screen/ColumnTop 610 584 16 16 nodes/screen/BottomRight 627 707 16 16
nodes/screen/Middle 627 584 16 16 nodes/screen/ColumnBottom 644 707 16 16
nodes/screen/MiddleLeft 644 584 16 16 nodes/screen/ColumnMiddle 661 707 16 16
nodes/screen/MiddleRight 661 584 16 16 nodes/screen/ColumnTop 678 707 16 16
nodes/screen/PowerOnOverlay 678 584 16 16 nodes/screen/Middle 695 707 16 16
nodes/screen/RowLeft 695 584 16 16 nodes/screen/MiddleLeft 712 707 16 16
nodes/screen/RowMiddle 712 584 16 16 nodes/screen/MiddleRight 729 707 16 16
nodes/screen/RowRight 729 584 16 16 nodes/screen/PowerOnOverlay 746 707 16 16
nodes/screen/Standalone 746 584 16 16 nodes/screen/RowLeft 763 707 16 16
nodes/screen/TopLeft 763 584 16 16 nodes/screen/RowMiddle 780 707 16 16
nodes/screen/TopMiddle 780 584 16 16 nodes/screen/RowRight 797 707 16 16
nodes/screen/TopRight 797 584 16 16 nodes/screen/Standalone 814 707 16 16
panel/BorderB 327 314 4 4 nodes/screen/TopLeft 831 707 16 16
panel/BorderL 387 314 4 2 nodes/screen/TopMiddle 848 707 16 16
panel/BorderR 332 314 4 4 nodes/screen/TopRight 865 707 16 16
panel/BorderT 337 314 4 4 panel/BorderB 249 549 4 4
panel/CornerBL 342 314 4 4 panel/BorderL 309 549 4 2
panel/CornerBR 347 314 4 4 panel/BorderR 254 549 4 4
panel/CornerTL 352 314 4 4 panel/BorderT 259 549 4 4
panel/CornerTR 357 314 4 4 panel/CornerBL 264 549 4 4
panel/Fill 288 325 2 2 panel/CornerBR 269 549 4 4
panel/CornerTL 274 549 4 4
panel/CornerTR 279 549 4 4
panel/Fill 210 560 2 2
particles/Note 377 434 7 10 particles/Note 377 434 7 10
screen/BorderB 284 314 2 8 screen/BorderB 206 549 2 8
screen/BorderT 281 314 2 10 screen/BorderT 203 549 2 10
screen/CornerBL 386 445 8 8 screen/CornerBL 386 445 8 8
screen/CornerBR 395 445 8 8 screen/CornerBR 395 445 8 8
screen/CornerTL 359 434 8 10 screen/CornerTL 359 434 8 10
screen/CornerTR 368 434 8 10 screen/CornerTR 368 434 8 10
window/BorderDark 281 325 1 4 window/BorderDark 203 560 1 4
window/BorderLight 283 325 1 4 window/BorderLight 205 560 1 4
window/CornerBL 362 314 4 4 window/CornerBL 284 549 4 4
window/CornerBR 367 314 4 4 window/CornerBR 289 549 4 4
window/CornerTL 372 314 4 4 window/CornerTL 294 549 4 4
window/CornerTR 377 314 4 4 window/CornerTR 299 549 4 4
window/OpenFMRadio 474 0 232 105 window/OpenFMRadio 474 0 232 105
window/case/Motherboard 123 434 79 70 window/case/Motherboard 123 434 79 70
window/rack/Lines 49 434 73 91 window/rack/Lines 49 434 73 91
window/rack/Motherboard 178 305 100 78 window/rack/Motherboard 178 305 100 78
window/rack/NetworkBack 299 319 1 2 window/rack/NetworkBack 221 554 1 2
window/rack/NetworkBottom 301 319 1 2 window/rack/NetworkBottom 223 554 1 2
window/rack/NetworkConnector 303 319 1 2 window/rack/NetworkConnector 225 554 1 2
window/rack/NetworkLeft 305 319 1 2 window/rack/NetworkLeft 227 554 1 2
window/rack/NetworkRight 307 319 1 2 window/rack/NetworkRight 229 554 1 2
window/rack/NetworkTop 309 319 1 2 window/rack/NetworkTop 231 554 1 2
window/rack/NodeBack 392 314 5 1 window/rack/NodeBack 314 549 5 1
window/rack/NodeBottom 398 314 5 1 window/rack/NodeBottom 320 549 5 1
window/rack/NodeLeft 404 314 5 1 window/rack/NodeLeft 326 549 5 1
window/rack/NodeRight 410 314 5 1 window/rack/NodeRight 332 549 5 1
window/rack/NodeTop 416 314 5 1 window/rack/NodeTop 338 549 5 1
window/rack/SideBack 287 319 1 3 window/rack/SideBack 209 554 1 3
window/rack/SideBottom 289 319 1 3 window/rack/SideBottom 211 554 1 3
window/rack/SideConnector 291 319 1 3 window/rack/SideConnector 213 554 1 3
window/rack/SideLeft 293 319 1 3 window/rack/SideLeft 215 554 1 3
window/rack/SideRight 295 319 1 3 window/rack/SideRight 217 554 1 3
window/rack/SideTop 297 319 1 3 window/rack/SideTop 219 554 1 3
window/raid/Slots 134 540 66 26 window/raid/Slots 134 540 66 26
window/tape/Back 315 707 20 15 window/tape/Back 332 567 20 15
window/tape/BackPressed 336 707 20 15 window/tape/BackPressed 353 567 20 15
window/tape/Forward 357 707 20 15 window/tape/Forward 374 567 20 15
window/tape/ForwardPressed 378 707 20 15 window/tape/ForwardPressed 395 567 20 15
window/tape/Play 399 707 20 15 window/tape/Play 416 567 20 15
window/tape/PlayPressed 420 707 20 15 window/tape/PlayPressed 437 567 20 15
window/tape/Screen 134 526 146 13 window/tape/Screen 134 526 146 13
window/tape/Stop 441 707 20 15 window/tape/Stop 458 567 20 15
window/tape/StopPressed 462 707 20 15 window/tape/StopPressed 479 567 20 15

View File

@ -0,0 +1,22 @@
package ocelot.desktop.entity
import ocelot.desktop.entity.traits.OcelotInterface
import totoro.ocelot.brain.Constants
import totoro.ocelot.brain.entity.traits.DeviceInfo
import totoro.ocelot.brain.entity.traits.DeviceInfo.{DeviceAttribute, DeviceClass}
import totoro.ocelot.brain.network.{Network, Node, Visibility}
class OcelotBlock extends OcelotInterface with DeviceInfo {
override val node: Node = Network.newNode(this, Visibility.Network)
.withComponent("ocelot", Visibility.Network)
.create()
private final lazy val deviceInfo = Map(
DeviceAttribute.Class -> DeviceClass.Generic,
DeviceAttribute.Description -> "Cardboard box simulator",
DeviceAttribute.Vendor -> Constants.DeviceInfo.DefaultVendor,
DeviceAttribute.Product -> "Catbus",
)
override def getDeviceInfo: Map[String, String] = deviceInfo
}

View File

@ -1,18 +1,14 @@
package ocelot.desktop.entity package ocelot.desktop.entity
import ocelot.desktop.entity.OcelotCard.LogEvent import ocelot.desktop.entity.traits.OcelotInterface
import totoro.ocelot.brain.Constants import totoro.ocelot.brain.Constants
import totoro.ocelot.brain.entity.machine.{Arguments, Callback, Context}
import totoro.ocelot.brain.entity.traits.DeviceInfo.{DeviceAttribute, DeviceClass} import totoro.ocelot.brain.entity.traits.DeviceInfo.{DeviceAttribute, DeviceClass}
import totoro.ocelot.brain.entity.traits.{DeviceInfo, Entity, Environment, Tiered, result} import totoro.ocelot.brain.entity.traits.{DeviceInfo, Tiered}
import totoro.ocelot.brain.event.{EventBus, NodeEvent}
import totoro.ocelot.brain.network.{Network, Node, Visibility} import totoro.ocelot.brain.network.{Network, Node, Visibility}
import totoro.ocelot.brain.util.Tier import totoro.ocelot.brain.util.Tier
import totoro.ocelot.brain.util.Tier.Tier import totoro.ocelot.brain.util.Tier.Tier
import java.time.Instant class OcelotCard extends OcelotInterface with DeviceInfo with Tiered {
class OcelotCard extends Entity with Environment with DeviceInfo with Tiered {
override val node: Node = Network.newNode(this, Visibility.Neighbors) override val node: Node = Network.newNode(this, Visibility.Neighbors)
.withComponent("ocelot", Visibility.Neighbors) .withComponent("ocelot", Visibility.Neighbors)
.create() .create()
@ -27,52 +23,4 @@ class OcelotCard extends Entity with Environment with DeviceInfo with Tiered {
) )
override def getDeviceInfo: Map[String, String] = deviceInfo override def getDeviceInfo: Map[String, String] = deviceInfo
@Callback(direct = true, doc = """function(message: string) -- Logs a message to the card's console.""")
def log(context: Context, args: Arguments): Array[AnyRef] = {
val message = args.checkString(0)
EventBus.send(LogEvent.CardToUser(node.address, message))
result()
}
@Callback(direct = true, doc = """function() -- Clears messages logged to the card's console.""")
def clearLog(context: Context, args: Arguments): Array[AnyRef] = {
EventBus.send(LogEvent.Clear(node.address))
result()
}
@Callback(direct = true, doc = """function(): integer -- Returns the current Unix timestamp (UTC, in milliseconds).""")
def getTimestamp(context: Context, args: Arguments): Array[AnyRef] = {
result(Instant.now().toEpochMilli)
}
@Callback(direct = true, doc = """function(): integer -- Returns a high-resolution timer value (in nanoseconds).""")
def getInstant(context: Context, args: Arguments): Array[AnyRef] = {
result(System.nanoTime())
}
@Callback(direct = true, doc = """function(): number -- Returns the remaining call budget.""")
def getRemainingCallBudget(context: Context, args: Arguments): Array[AnyRef] = {
result(context.getRemainingCallBudget)
}
@Callback(direct = true, doc = """function(): number -- Returns the maximum call budget.""")
def getMaxCallBudget(context: Context, args: Arguments): Array[AnyRef] = {
result(context.getMaxCallBudget)
}
def pushMessage(message: String): Unit = {
node.sendToReachable("computer.signal", "ocelot_message", message)
EventBus.send(LogEvent.UserToCard(node.address, message))
}
}
object OcelotCard {
sealed trait LogEvent extends NodeEvent
object LogEvent {
case class CardToUser(address: String, message: String) extends LogEvent
case class UserToCard(address: String, message: String) extends LogEvent
case class Clear(address: String) extends LogEvent
}
} }

View File

@ -0,0 +1,58 @@
package ocelot.desktop.entity.traits
import ocelot.desktop.entity.traits.OcelotInterface.LogEvent
import totoro.ocelot.brain.entity.machine.{Arguments, Callback, Context}
import totoro.ocelot.brain.entity.traits.{Entity, Environment, result}
import totoro.ocelot.brain.event.{EventBus, NodeEvent}
import java.time.Instant
trait OcelotInterface extends Entity with Environment {
@Callback(direct = true, doc = """function(message: string) -- Logs a message to the card's console.""")
def log(context: Context, args: Arguments): Array[AnyRef] = {
val message = args.checkString(0)
EventBus.send(LogEvent.CardToUser(node.address, message))
result()
}
@Callback(direct = true, doc = """function() -- Clears messages logged to the card's console.""")
def clearLog(context: Context, args: Arguments): Array[AnyRef] = {
EventBus.send(LogEvent.Clear(node.address))
result()
}
@Callback(direct = true, doc = """function(): integer -- Returns the current Unix timestamp (UTC, in milliseconds).""")
def getTimestamp(context: Context, args: Arguments): Array[AnyRef] = {
result(Instant.now().toEpochMilli)
}
@Callback(direct = true, doc = """function(): integer -- Returns a high-resolution timer value (in nanoseconds).""")
def getInstant(context: Context, args: Arguments): Array[AnyRef] = {
result(System.nanoTime())
}
@Callback(direct = true, doc = """function(): number -- Returns the remaining call budget.""")
def getRemainingCallBudget(context: Context, args: Arguments): Array[AnyRef] = {
result(context.getRemainingCallBudget)
}
@Callback(direct = true, doc = """function(): number -- Returns the maximum call budget.""")
def getMaxCallBudget(context: Context, args: Arguments): Array[AnyRef] = {
result(context.getMaxCallBudget)
}
def pushMessage(message: String): Unit = {
node.sendToReachable("computer.signal", "ocelot_message", message)
EventBus.send(LogEvent.UserToCard(node.address, message))
}
}
object OcelotInterface {
sealed trait LogEvent extends NodeEvent
object LogEvent {
case class CardToUser(address: String, message: String) extends LogEvent
case class UserToCard(address: String, message: String) extends LogEvent
case class Clear(address: String) extends LogEvent
}
}

View File

@ -8,8 +8,8 @@ import totoro.ocelot.brain.util.ExtendedTier.ExtendedTier
import totoro.ocelot.brain.util.Tier.Tier import totoro.ocelot.brain.util.Tier.Tier
case class IconSource( case class IconSource(
path: String, path: String,
animation: Option[IconSource.Animation] = None animation: Option[IconSource.Animation] = None,
) )
object IconSource { object IconSource {
@ -26,97 +26,92 @@ object IconSource {
IconSource(s"icons/Tier${tier.id}") IconSource(s"icons/Tier${tier.id}")
} }
val Cpu: Tier => IconSource = { tier => object Items {
IconSource(s"items/CPU${tier.id}") val Cpu: Tier => IconSource = { tier =>
} IconSource(s"items/CPU${tier.id}")
}
val Apu: Tier => IconSource = { tier => val Apu: Tier => IconSource = { tier =>
IconSource(s"items/APU${tier.id}", animation = Some(Animations.Apu)) IconSource(s"items/APU${tier.id}", animation = Some(Animations.Apu))
} }
val GraphicsCard: Tier => IconSource = { tier => val GraphicsCard: Tier => IconSource = { tier =>
IconSource(s"items/GraphicsCard${tier.id}") IconSource(s"items/GraphicsCard${tier.id}")
} }
val NetworkCard: IconSource = IconSource("items/NetworkCard") val NetworkCard: IconSource = IconSource("items/NetworkCard")
val WirelessNetworkCard: Tier => IconSource = { tier => val WirelessNetworkCard: Tier => IconSource = { tier =>
IconSource(s"items/WirelessNetworkCard${tier.id}") IconSource(s"items/WirelessNetworkCard${tier.id}")
} }
val LinkedCard: IconSource = IconSource("items/LinkedCard", animation = Some(Animations.LinkedCard)) val LinkedCard: IconSource = IconSource("items/LinkedCard", animation = Some(Animations.LinkedCard))
val InternetCard: IconSource = IconSource("items/InternetCard", animation = Some(Animations.InternetCard)) val InternetCard: IconSource = IconSource("items/InternetCard", animation = Some(Animations.InternetCard))
val RedstoneCard: Tier => IconSource = { tier => val RedstoneCard: Tier => IconSource = { tier =>
IconSource(s"items/RedstoneCard${tier.id}") IconSource(s"items/RedstoneCard${tier.id}")
} }
val DataCard: Tier => IconSource = { tier => val DataCard: Tier => IconSource = { tier =>
IconSource(s"items/DataCard${tier.id}", animation = Some(Animations.DataCard)) IconSource(s"items/DataCard${tier.id}", animation = Some(Animations.DataCard))
} }
val SoundCard: IconSource = IconSource("items/SoundCard", animation = Some(Animations.DataCard)) val SoundCard: IconSource = IconSource("items/SoundCard", animation = Some(Animations.DataCard))
val SelfDestructingCard: IconSource = IconSource("items/SelfDestructingCard", animation = Some(Animations.SelfDestructingCard)) val SelfDestructingCard: IconSource =
IconSource("items/SelfDestructingCard", animation = Some(Animations.SelfDestructingCard))
val OcelotCard: IconSource = IconSource("items/OcelotCard", animation = Some(Animations.OcelotCard)) val OcelotCard: IconSource = IconSource("items/OcelotCard", animation = Some(Animations.OcelotCard))
val HardDiskDrive: Tier => IconSource = { tier => val HardDiskDrive: Tier => IconSource = { tier =>
IconSource(s"items/HardDiskDrive${tier.id}") IconSource(s"items/HardDiskDrive${tier.id}")
} }
val Eeprom: IconSource = IconSource("items/EEPROM") val Eeprom: IconSource = IconSource("items/EEPROM")
val FloppyDisk: DyeColor => IconSource = { color => val FloppyDisk: DyeColor => IconSource = { color =>
IconSource(s"items/FloppyDisk_${color.name}") IconSource(s"items/FloppyDisk_${color.name}")
} }
val Memory: ExtendedTier => IconSource = { tier => val Memory: ExtendedTier => IconSource = { tier =>
IconSource(s"items/Memory${tier.id}") IconSource(s"items/Memory${tier.id}")
} }
val Server: Tier => IconSource = { tier => val Server: Tier => IconSource = { tier =>
IconSource(s"items/Server${tier.id}") IconSource(s"items/Server${tier.id}")
} }
val ComponentBus: Tier => IconSource = { tier => val ComponentBus: Tier => IconSource = { tier =>
IconSource(s"items/ComponentBus${tier.id}") IconSource(s"items/ComponentBus${tier.id}")
} }
val Tape: TapeKind => IconSource = { val Tape: TapeKind => IconSource = {
case TapeKind.Golder => Tape(TapeKind.Gold) case TapeKind.Golder => Tape(TapeKind.Gold)
case TapeKind.NetherStarrer => Tape(TapeKind.NetherStar) case TapeKind.NetherStarrer => Tape(TapeKind.NetherStar)
case kind => IconSource(s"items/Tape$kind") case kind => IconSource(s"items/Tape$kind")
} }
val DiskDriveMountable: IconSource = IconSource("items/DiskDriveMountable") val DiskDriveMountable: IconSource = IconSource("items/DiskDriveMountable")
//noinspection ScalaWeakerAccess //noinspection ScalaWeakerAccess
object Animations { object Animations {
val Apu: Animation = Animation( val Apu: Animation =
(0, 3f), (1, 3f), (2, 3f), (3, 3f), (4, 3f), (5, 3f), Animation((0, 3f), (1, 3f), (2, 3f), (3, 3f), (4, 3f), (5, 3f), (4, 3f), (3, 3f), (2, 3f), (1, 3f), (0, 3f))
(4, 3f), (3, 3f), (2, 3f), (1, 3f), (0, 3f))
val LinkedCard: Animation = val LinkedCard: Animation =
Animation((0, 3f), (1, 3f), (2, 3f), (3, 3f), (4, 3f), (5, 3f)) Animation((0, 3f), (1, 3f), (2, 3f), (3, 3f), (4, 3f), (5, 3f))
val InternetCard: Animation = Animation( val InternetCard: Animation =
(0, 2f), (1, 7f), (0, 5f), (1, 4f), (0, 7f), (1, 2f), (0, 8f), Animation((0, 2f), (1, 7f), (0, 5f), (1, 4f), (0, 7f), (1, 2f), (0, 8f), (1, 9f), (0, 6f), (1, 4f))
(1, 9f), (0, 6f), (1, 4f))
val DataCard: Animation = Animation( val DataCard: Animation = Animation((0, 4f), (1, 4f), (2, 4f), (3, 4f), (4, 4f), (5, 4f), (6, 4f), (7, 4f))
(0, 4f), (1, 4f), (2, 4f), (3, 4f), (4, 4f), (5, 4f), (6, 4f), (7, 4f))
val SelfDestructingCard: Animation = Animation((0, 4f), (1, 4f)) val SelfDestructingCard: Animation = Animation((0, 4f), (1, 4f))
val OcelotCard: Animation = Animation( val OcelotCard: Animation =
(0, 12f), (1, 4f), (2, 4f), (3, 4f), (4, 5f), (5, 6f), (0, 9f), (6, 4f), (7, 3f)) Animation((0, 12f), (1, 4f), (2, 4f), (3, 4f), (4, 5f), (5, 6f), (0, 9f), (6, 4f), (7, 3f))
}
val Loading: Animation = 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)
)
} }
case class Animation(frames: Array[(Int, Float)], frameSize: Option[Size2D]) case class Animation(frames: Array[(Int, Float)], frameSize: Option[Size2D])
@ -128,12 +123,27 @@ object IconSource {
// ----------------------- Ocelot interface icons ----------------------- // ----------------------- Ocelot interface icons -----------------------
val Notification: NotificationType => IconSource = { notificationType => val Notification: NotificationType => IconSource = { notificationType =>
IconSource(s"icons/Notification$notificationType") IconSource(s"icons/Notification$notificationType")
} }
val Loading: IconSource = IconSource("Loading", animation = Some(Animations.Loading)) 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 SettingsSystem: IconSource = IconSource("icons/SettingsSystem") val SettingsSystem: IconSource = IconSource("icons/SettingsSystem")
val SettingsSound: IconSource = IconSource("icons/SettingsSound") val SettingsSound: IconSource = IconSource("icons/SettingsSound")
@ -168,4 +178,20 @@ object IconSource {
val Guitar: IconSource = IconSource("icons/Guitar") val Guitar: IconSource = IconSource("icons/Guitar")
val Keyboard: IconSource = IconSource("icons/Keyboard") val Keyboard: IconSource = IconSource("icons/Keyboard")
val KeyboardOff: IconSource = IconSource("icons/KeyboardOff") val KeyboardOff: IconSource = IconSource("icons/KeyboardOff")
// ----------------------- Node icons -----------------------
val NA: IconSource = IconSource("icons/NA")
object Nodes {
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))),
)
val Rx: IconSource = IconSource("nodes/ocelot-block/Rx")
val Tx: IconSource = IconSource("nodes/ocelot-block/Tx")
}
}
} }

View File

@ -105,7 +105,7 @@ object Items extends Logging {
// Anyway we don't want to see its icon as default one in context menu // Anyway we don't want to see its icon as default one in context menu
registerArbitrary( registerArbitrary(
"Memory", "Memory",
IconSource.Memory(ExtendedTier.ThreeHalf), IconSource.Items.Memory(ExtendedTier.ThreeHalf),
(ExtendedTier.One to ExtendedTier.Creative).iterator (ExtendedTier.One to ExtendedTier.Creative).iterator
.map { .map {
case ExtendedTier.Creative => new MagicalMemoryItem.Factory() case ExtendedTier.Creative => new MagicalMemoryItem.Factory()

View File

@ -26,7 +26,7 @@ object ApuItem {
// we keep the latter tier internally and increment it when dealing with the rest of the world // we keep the latter tier internally and increment it when dealing with the rest of the world
override def tier: Option[Tier] = Some(_tier.saturatingAdd(1)) override def tier: Option[Tier] = Some(_tier.saturatingAdd(1))
override def icon: IconSource = IconSource.Apu(_tier) override def icon: IconSource = IconSource.Items.Apu(_tier)
override def build(): ApuItem = new ApuItem(new APU(_tier)) override def build(): ApuItem = new ApuItem(new APU(_tier))

View File

@ -34,7 +34,7 @@ object ComponentBusItem {
override def tier: Option[Tier] = Some(_tier) override def tier: Option[Tier] = Some(_tier)
override def icon: IconSource = IconSource.ComponentBus(_tier) override def icon: IconSource = IconSource.Items.ComponentBus(_tier)
override def build(): ComponentBusItem = new ComponentBusItem(new ComponentBus(_tier)) override def build(): ComponentBusItem = new ComponentBusItem(new ComponentBus(_tier))

View File

@ -23,7 +23,7 @@ object CpuItem {
override def tier: Option[Tier] = Some(_tier) override def tier: Option[Tier] = Some(_tier)
override def icon: IconSource = IconSource.Cpu(_tier) override def icon: IconSource = IconSource.Items.Cpu(_tier)
override def build(): CpuItem = new CpuItem(new CPU(_tier)) override def build(): CpuItem = new CpuItem(new CPU(_tier))

View File

@ -14,7 +14,7 @@ object DataCardItem {
abstract class Factory extends ItemFactory { abstract class Factory extends ItemFactory {
override def name: String = s"Data Card (${tier.get.label})" override def name: String = s"Data Card (${tier.get.label})"
override def icon: IconSource = IconSource.DataCard(tier.get) override def icon: IconSource = IconSource.Items.DataCard(tier.get)
} }
class Tier1(val dataCard: DataCard.Tier1) extends DataCardItem { class Tier1(val dataCard: DataCard.Tier1) extends DataCardItem {

View File

@ -1,6 +1,7 @@
package ocelot.desktop.inventory.item package ocelot.desktop.inventory.item
import ocelot.desktop.graphics.IconSource import ocelot.desktop.graphics.IconSource
import ocelot.desktop.inventory.traits.RackMountableItem
import ocelot.desktop.inventory.{ItemFactory, ItemRecoverer} import ocelot.desktop.inventory.{ItemFactory, ItemRecoverer}
import ocelot.desktop.util.DiskDriveAware import ocelot.desktop.util.DiskDriveAware
import totoro.ocelot.brain.entity.{DiskDriveMountable, FloppyDiskDrive} import totoro.ocelot.brain.entity.{DiskDriveMountable, FloppyDiskDrive}
@ -26,7 +27,7 @@ object DiskDriveMountableItem {
override def name: String = "Disk Drive" override def name: String = "Disk Drive"
override def icon: IconSource = IconSource.DiskDriveMountable override def icon: IconSource = IconSource.Items.DiskDriveMountable
override def build(): DiskDriveMountableItem = { override def build(): DiskDriveMountableItem = {
val item = new DiskDriveMountableItem(new DiskDriveMountable()) val item = new DiskDriveMountableItem(new DiskDriveMountable())

View File

@ -84,7 +84,7 @@ object EepromItem {
override def tier: Option[Tier] = None override def tier: Option[Tier] = None
override def icon: IconSource = IconSource.Eeprom override def icon: IconSource = IconSource.Items.Eeprom
override def recoverers: Iterable[ItemRecoverer[_, _]] = Some(ItemRecoverer(new EepromItem(_))) override def recoverers: Iterable[ItemRecoverer[_, _]] = Some(ItemRecoverer(new EepromItem(_)))
} }

View File

@ -84,7 +84,7 @@ object FloppyItem {
override def tier: Option[Tier] = None override def tier: Option[Tier] = None
override def icon: IconSource = IconSource.FloppyDisk(color) override def icon: IconSource = IconSource.Items.FloppyDisk(color)
override def recoverers: Iterable[ItemRecoverer[_, _]] = Some(ItemRecoverer(new FloppyItem(_))) override def recoverers: Iterable[ItemRecoverer[_, _]] = Some(ItemRecoverer(new FloppyItem(_)))
} }

View File

@ -21,7 +21,7 @@ object GraphicsCardItem {
override def name: String = s"Graphics Card (${_tier.label})" override def name: String = s"Graphics Card (${_tier.label})"
override def tier: Option[Tier] = Some(_tier) override def tier: Option[Tier] = Some(_tier)
override def icon: IconSource = IconSource.GraphicsCard(_tier) override def icon: IconSource = IconSource.Items.GraphicsCard(_tier)
override def build(): GraphicsCardItem = new GraphicsCardItem(new GraphicsCard(_tier)) override def build(): GraphicsCardItem = new GraphicsCardItem(new GraphicsCard(_tier))

View File

@ -76,7 +76,7 @@ object HddItem {
override def tier: Option[Tier] = Some(_tier) override def tier: Option[Tier] = Some(_tier)
override def icon: IconSource = IconSource.HardDiskDrive(_tier) override def icon: IconSource = IconSource.Items.HardDiskDrive(_tier)
override def build(): HddItem = new HddItem( override def build(): HddItem = new HddItem(
if (managed) Hdd(new HDDManaged(_tier)) else Hdd(new HDDUnmanaged(_tier)) if (managed) Hdd(new HDDManaged(_tier)) else Hdd(new HDDUnmanaged(_tier))

View File

@ -24,7 +24,7 @@ object InternetCardItem {
override def tier: Option[Tier] = Some(Tier.Two) override def tier: Option[Tier] = Some(Tier.Two)
override def icon: IconSource = IconSource.InternetCard override def icon: IconSource = IconSource.Items.InternetCard
override def build(): InternetCardItem = new InternetCardItem(new InternetCard) override def build(): InternetCardItem = new InternetCardItem(new InternetCard)

View File

@ -45,7 +45,7 @@ object LinkedCardItem {
override def tier: Option[Tier] = Some(Tier.Three) override def tier: Option[Tier] = Some(Tier.Three)
override def icon: IconSource = IconSource.LinkedCard override def icon: IconSource = IconSource.Items.LinkedCard
override def build(): LinkedCardItem = new LinkedCardItem(new LinkedCard) override def build(): LinkedCardItem = new LinkedCardItem(new LinkedCard)

View File

@ -24,7 +24,7 @@ object MagicalMemoryItem {
override def tier: Option[Tier] = Some(Tier.One) override def tier: Option[Tier] = Some(Tier.One)
override def icon: IconSource = IconSource.Memory(ExtendedTier.Creative) override def icon: IconSource = IconSource.Items.Memory(ExtendedTier.Creative)
override def build(): MagicalMemoryItem = new MagicalMemoryItem(new MagicalMemory()) override def build(): MagicalMemoryItem = new MagicalMemoryItem(new MagicalMemory())

View File

@ -24,7 +24,7 @@ object MemoryItem {
override def tier: Option[Tier] = Some(memoryTier.toTier) override def tier: Option[Tier] = Some(memoryTier.toTier)
override def icon: IconSource = IconSource.Memory(memoryTier) override def icon: IconSource = IconSource.Items.Memory(memoryTier)
override def build(): MemoryItem = new MemoryItem(new Memory(memoryTier)) override def build(): MemoryItem = new MemoryItem(new Memory(memoryTier))

View File

@ -22,7 +22,7 @@ object NetworkCardItem {
override def name: String = "Network Card" override def name: String = "Network Card"
override def tier: Option[Tier] = Some(Tier.One) override def tier: Option[Tier] = Some(Tier.One)
override def icon: IconSource = IconSource.NetworkCard override def icon: IconSource = IconSource.Items.NetworkCard
override def build(): NetworkCardItem = new NetworkCardItem(new NetworkCard) override def build(): NetworkCardItem = new NetworkCardItem(new NetworkCard)

View File

@ -3,53 +3,27 @@ package ocelot.desktop.inventory.item
import ocelot.desktop.ColorScheme import ocelot.desktop.ColorScheme
import ocelot.desktop.color.Color import ocelot.desktop.color.Color
import ocelot.desktop.entity.OcelotCard import ocelot.desktop.entity.OcelotCard
import ocelot.desktop.entity.traits.OcelotInterface
import ocelot.desktop.graphics.IconSource import ocelot.desktop.graphics.IconSource
import ocelot.desktop.inventory.item.OcelotCardItem.{EntriesTag, EntryKindRx, EntryKindTag, EntryKindTx, EntryMessageTag, MessageLimitTag, MessagesTag}
import ocelot.desktop.inventory.traits.{CardItem, ComponentItem, PersistableItem} import ocelot.desktop.inventory.traits.{CardItem, ComponentItem, PersistableItem}
import ocelot.desktop.inventory.{Item, ItemFactory, ItemRecoverer} import ocelot.desktop.inventory.{Item, ItemFactory, ItemRecoverer}
import ocelot.desktop.ui.event.BrainEvent
import ocelot.desktop.ui.widget.LogWidget.LogEntry
import ocelot.desktop.ui.widget.card.OcelotCardWindow
import ocelot.desktop.ui.widget.contextmenu.{ContextMenu, ContextMenuEntry} import ocelot.desktop.ui.widget.contextmenu.{ContextMenu, ContextMenuEntry}
import ocelot.desktop.ui.widget.window.Windowed import ocelot.desktop.util.{Logging, OcelotInterfaceLogStorage}
import ocelot.desktop.util.Logging
import totoro.ocelot.brain.nbt.ExtendedNBT.extendNBTTagList
import totoro.ocelot.brain.nbt.{NBT, NBTBase, NBTTagCompound, NBTTagString}
import totoro.ocelot.brain.util.Tier import totoro.ocelot.brain.util.Tier
import totoro.ocelot.brain.util.Tier.Tier import totoro.ocelot.brain.util.Tier.Tier
import scala.collection.mutable
import scala.jdk.CollectionConverters.BufferHasAsJava
class OcelotCardItem(val ocelotCard: OcelotCard) class OcelotCardItem(val ocelotCard: OcelotCard)
extends Item extends Item
with ComponentItem with ComponentItem
with OcelotInterfaceLogStorage
with PersistableItem with PersistableItem
with CardItem with CardItem
with Windowed[OcelotCardWindow]
with Logging { with Logging {
private var _messageLimit: Int = 1000
// NOTE: access must be synchronized!
// ocelot.log() is a direct method, so it may push events even if the tick lock is not acquired
private val _entries = mutable.ArrayDeque.empty[LogEntry]
eventHandlers += {
case BrainEvent(OcelotCard.LogEvent.Clear(_)) =>
clear()
case BrainEvent(OcelotCard.LogEvent.CardToUser(_, message)) =>
addEntry(LogEntry.Rx(message))
case BrainEvent(OcelotCard.LogEvent.UserToCard(_, message)) =>
addEntry(LogEntry.Tx(message))
}
override def createWindow(): OcelotCardWindow = new OcelotCardWindow(this)
override def component: OcelotCard = ocelotCard override def component: OcelotCard = ocelotCard
override def ocelotInterface: OcelotInterface = ocelotCard
override def tooltipNameColor: Color = ColorScheme("OcelotCardTooltip") override def tooltipNameColor: Color = ColorScheme("OcelotCardTooltip")
override def fillRmbMenu(menu: ContextMenu): Unit = { override def fillRmbMenu(menu: ContextMenu): Unit = {
@ -62,124 +36,10 @@ class OcelotCardItem(val ocelotCard: OcelotCard)
super.fillRmbMenu(menu) super.fillRmbMenu(menu)
} }
private def loadEntry(nbt: NBTTagCompound): Either[String, LogEntry] = {
nbt.getString(EntryKindTag) match {
case EntryKindRx => Right(LogEntry.Rx(nbt.getString(EntryMessageTag)))
case EntryKindTx => Right(LogEntry.Tx(nbt.getString(EntryMessageTag)))
case "" => Left("entry kind not set")
case k => Left(s"unknown entry kind: $k")
}
}
private def saveEntry(entry: LogEntry): NBTTagCompound = {
val result = new NBTTagCompound()
entry match {
case LogEntry.Rx(message) =>
result.setString(EntryKindTag, EntryKindRx)
result.setString(EntryMessageTag, message)
case LogEntry.Tx(message) =>
result.setString(EntryKindTag, EntryKindTx)
result.setString(EntryMessageTag, message)
}
result
}
override def load(nbt: NBTTagCompound): Unit = {
super.load(nbt)
clear()
if (nbt.hasKey(MessageLimitTag)) {
messageLimit = nbt.getInteger(MessageLimitTag)
}
val entries = if (nbt.hasKey(EntriesTag)) {
nbt.getTagList(EntriesTag, NBT.TAG_COMPOUND).iterator[NBTTagCompound].zipWithIndex.flatMap {
case (entryNbt, idx) =>
loadEntry(entryNbt) match {
case Left(err) =>
logger.warn(s"Could not restore log entry (idx $idx) of ocelot card ${component.node.address}: $err")
None
case Right(entry) => Some(entry)
}
}
} else {
// old save format: plain messages
nbt
.getTagList(MessagesTag, NBT.TAG_STRING)
.iterator[NBTTagString]
.map(entryNbt => LogEntry.Rx(entryNbt.getString))
}
addEntries(entries.toSeq)
}
override def save(nbt: NBTTagCompound): Unit = _entries.synchronized {
super.save(nbt)
nbt.setTagList(EntriesTag, _entries.map(saveEntry(_).asInstanceOf[NBTBase]).asJava)
nbt.setInteger(MessageLimitTag, _messageLimit)
}
override def factory: ItemFactory = OcelotCardItem.Factory override def factory: ItemFactory = OcelotCardItem.Factory
def messageLimit: Int = _messageLimit
def messageLimit_=(limit: Int): Unit = _entries.synchronized {
require(limit > 0)
ensureFreeSpace(_entries.length - limit)
_messageLimit = limit
}
def entryCount: Int = _entries.synchronized {
_entries.length
}
def clear(): Unit = _entries.synchronized {
val count = _entries.length
_entries.clear()
window.onMessagesRemoved(count)
}
private def addEntry(entry: LogEntry): Unit = _entries.synchronized {
ensureFreeSpace(1)
_entries += entry
window.onMessagesAdded(Some(entry))
}
private def addEntries(entries: Seq[LogEntry]): Unit = _entries.synchronized {
ensureFreeSpace(entries.length)
val prevCount = _entries.length
_entries ++= entries.view.takeRight(messageLimit)
window.onMessagesAdded(_entries.view.takeRight(_entries.length - prevCount))
}
private def ensureFreeSpace(n: Int): Unit = _entries.synchronized {
val prevCount = _entries.length
_entries.takeRightInPlace(messageLimit - n)
val removedCount = prevCount - _entries.length
if (removedCount > 0) {
window.onMessagesRemoved(removedCount)
}
}
} }
object OcelotCardItem { object OcelotCardItem {
val MessagesTag = "messages"
val EntriesTag = "entries"
val MessageLimitTag = "limit"
val EntryKindTag = "kind"
val EntryKindRx = "rx"
val EntryKindTx = "tx"
val EntryMessageTag = "msg"
object Factory extends ItemFactory { object Factory extends ItemFactory {
override type I = OcelotCardItem override type I = OcelotCardItem
@ -189,7 +49,7 @@ object OcelotCardItem {
override def tier: Option[Tier] = Some(Tier.One) override def tier: Option[Tier] = Some(Tier.One)
override def icon: IconSource = IconSource.OcelotCard override def icon: IconSource = IconSource.Items.OcelotCard
override def build(): OcelotCardItem = new OcelotCardItem(new OcelotCard) override def build(): OcelotCardItem = new OcelotCardItem(new OcelotCard)

View File

@ -21,7 +21,7 @@ object RedstoneCardItem {
abstract class Factory extends ItemFactory { abstract class Factory extends ItemFactory {
override def name: String = s"Redstone Card (${tier.get.label})" override def name: String = s"Redstone Card (${tier.get.label})"
override def icon: IconSource = IconSource.RedstoneCard(tier.get) override def icon: IconSource = IconSource.Items.RedstoneCard(tier.get)
} }
class Tier1(val redstoneCard: Redstone.Tier1) extends RedstoneCardItem { class Tier1(val redstoneCard: Redstone.Tier1) extends RedstoneCardItem {

View File

@ -29,7 +29,7 @@ object SelfDestructingCardItem {
override def tier: Option[Tier] = Some(Tier.Two) override def tier: Option[Tier] = Some(Tier.Two)
override def icon: IconSource = IconSource.SelfDestructingCard override def icon: IconSource = IconSource.Items.SelfDestructingCard
override def build(): SelfDestructingCardItem = new SelfDestructingCardItem(new SelfDestructingCard) override def build(): SelfDestructingCardItem = new SelfDestructingCardItem(new SelfDestructingCard)

View File

@ -1,6 +1,7 @@
package ocelot.desktop.inventory.item package ocelot.desktop.inventory.item
import ocelot.desktop.graphics.IconSource import ocelot.desktop.graphics.IconSource
import ocelot.desktop.inventory.traits.RackMountableItem
import ocelot.desktop.inventory.{ItemFactory, ItemRecoverer} import ocelot.desktop.inventory.{ItemFactory, ItemRecoverer}
import ocelot.desktop.ui.widget.slot._ import ocelot.desktop.ui.widget.slot._
import ocelot.desktop.util.ComputerType.ComputerType import ocelot.desktop.util.ComputerType.ComputerType
@ -106,7 +107,7 @@ object ServerItem {
override def tier: Option[Tier] = Some(_tier) override def tier: Option[Tier] = Some(_tier)
override def icon: IconSource = IconSource.Server(_tier) override def icon: IconSource = IconSource.Items.Server(_tier)
override def build(): ServerItem = { override def build(): ServerItem = {
val item = new ServerItem(new Server(_tier)) val item = new ServerItem(new Server(_tier))

View File

@ -45,7 +45,7 @@ object SoundCardItem {
override def tier: Option[Tier] = Some(Tier.Two) override def tier: Option[Tier] = Some(Tier.Two)
override def icon: IconSource = IconSource.SoundCard override def icon: IconSource = IconSource.Items.SoundCard
override def build(): SoundCardItem = new SoundCardItem(new SoundCard) override def build(): SoundCardItem = new SoundCardItem(new SoundCard)

View File

@ -55,7 +55,7 @@ object TapeItem {
override def tier: Option[Tier] = None override def tier: Option[Tier] = None
override def icon: IconSource = IconSource.Tape(kind) override def icon: IconSource = IconSource.Items.Tape(kind)
override def build(): TapeItem = new TapeItem(new Tape(kind)) override def build(): TapeItem = new TapeItem(new Tape(kind))

View File

@ -14,7 +14,7 @@ object WirelessNetworkCardItem {
abstract class Factory extends ItemFactory { abstract class Factory extends ItemFactory {
override def name: String = s"Wireless Net. Card (${tier.get.label})" override def name: String = s"Wireless Net. Card (${tier.get.label})"
override def icon: IconSource = IconSource.WirelessNetworkCard(tier.get) override def icon: IconSource = IconSource.Items.WirelessNetworkCard(tier.get)
} }
class Tier1(override val card: WirelessNetworkCard.Tier1) extends WirelessNetworkCardItem(card) { class Tier1(override val card: WirelessNetworkCard.Tier1) extends WirelessNetworkCardItem(card) {

View File

@ -1,7 +1,6 @@
package ocelot.desktop.inventory.item package ocelot.desktop.inventory.traits
import ocelot.desktop.inventory.Item import ocelot.desktop.inventory.Item
import ocelot.desktop.inventory.traits.ComponentItem
import ocelot.desktop.node.nodes.RackNode import ocelot.desktop.node.nodes.RackNode
import ocelot.desktop.ui.widget.contextmenu.ContextMenu import ocelot.desktop.ui.widget.contextmenu.ContextMenu
import totoro.ocelot.brain.entity.result import totoro.ocelot.brain.entity.result

View File

@ -1,8 +1,7 @@
package ocelot.desktop.node package ocelot.desktop.node
import ocelot.desktop.OcelotDesktop
import ocelot.desktop.audio._ import ocelot.desktop.audio._
import ocelot.desktop.entity.OcelotCard
import ocelot.desktop.geometry.FloatUtils.ExtendedFloat
import ocelot.desktop.geometry.Vector2D import ocelot.desktop.geometry.Vector2D
import ocelot.desktop.graphics.Graphics import ocelot.desktop.graphics.Graphics
import ocelot.desktop.inventory.SyncedInventory import ocelot.desktop.inventory.SyncedInventory
@ -13,20 +12,18 @@ import ocelot.desktop.ui.event.BrainEvent
import ocelot.desktop.ui.event.handlers.DiskActivityHandler import ocelot.desktop.ui.event.handlers.DiskActivityHandler
import ocelot.desktop.ui.widget.ComputerErrorMessageLabel import ocelot.desktop.ui.widget.ComputerErrorMessageLabel
import ocelot.desktop.util.Messages import ocelot.desktop.util.Messages
import ocelot.desktop.{ColorScheme, OcelotDesktop}
import totoro.ocelot.brain.Settings import totoro.ocelot.brain.Settings
import totoro.ocelot.brain.entity.traits.{Entity, Environment, WorkspaceAware} import totoro.ocelot.brain.entity.traits.{Entity, Environment, WorkspaceAware}
import totoro.ocelot.brain.event._ import totoro.ocelot.brain.event._
import java.util.Calendar import java.util.Calendar
import scala.collection.mutable
import scala.collection.mutable.ArrayBuffer import scala.collection.mutable.ArrayBuffer
import scala.util.Random
abstract class ComputerAwareNode(entity: Entity with Environment with WorkspaceAware) abstract class ComputerAwareNode(entity: Entity with Environment with WorkspaceAware)
extends EntityNode(entity) extends EntityNode(entity)
with SyncedInventory with SyncedInventory
with DiskActivityHandler with DiskActivityHandler
with OcelotLogParticleNode
with ShiftClickNode { with ShiftClickNode {
// access should be synchronized because messages are added in the update thread // access should be synchronized because messages are added in the update thread
@ -36,20 +33,6 @@ abstract class ComputerAwareNode(entity: Entity with Environment with WorkspaceA
messages += ((0f, message)) messages += ((0f, message))
} }
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 lazy val soundCardSounds: (SoundStream, SoundSource) = Audio.newStream(SoundCategory.Records) private lazy val soundCardSounds: (SoundStream, SoundSource) = Audio.newStream(SoundCategory.Records)
private def soundCardStream: SoundStream = soundCardSounds._1 private def soundCardStream: SoundStream = soundCardSounds._1
private def soundCardSource: SoundSource = soundCardSounds._2 private def soundCardSource: SoundSource = soundCardSounds._2
@ -84,17 +67,9 @@ abstract class ComputerAwareNode(entity: Entity with Environment with WorkspaceA
SoundSource.MinecraftExplosion.play() SoundSource.MinecraftExplosion.play()
destroy() destroy()
}) })
case BrainEvent(OcelotCard.LogEvent.CardToUser(_, _)) =>
addLogParticle()
} }
override def update(): Unit = { override def update(): Unit = {
logParticles.synchronized {
logParticles.foreach(particle => particle.time += LogParticleMoveSpeed * UiHandler.dt)
logParticles.filterInPlace(_.time <= 1f)
}
messages.synchronized { messages.synchronized {
messages.mapInPlace { case (t, message) => (t + ErrorMessageMoveSpeed * UiHandler.dt, message) } messages.mapInPlace { case (t, message) => (t + ErrorMessageMoveSpeed * UiHandler.dt, message) }
messages.filterInPlace(_._1 <= 1f) messages.filterInPlace(_._1 <= 1f)
@ -111,28 +86,9 @@ abstract class ComputerAwareNode(entity: Entity with Environment with WorkspaceA
} }
} }
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()
val r1 = (bounds.w max bounds.h) / math.sqrt(2) + 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 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 = { override def drawParticles(g: Graphics): Unit = {
super.drawParticles(g)
drawMessageParticles(g) drawMessageParticles(g)
drawLogParticles(g)
} }
protected def drawOverlay(g: Graphics): Unit = { protected def drawOverlay(g: Graphics): Unit = {
@ -162,15 +118,6 @@ object ComputerAwareNode {
private val MaxErrorMessageDistance: Float = 50f private val MaxErrorMessageDistance: Float = 50f
private val ErrorMessageMoveSpeed: Float = 0.5f private val ErrorMessageMoveSpeed: Float = 0.5f
private val MaxLogParticles: Int = 15
private val LogParticleMaxAngle: Float = 0.25f
private val LogParticleCount: Int = 12
private val LogParticleGrow: Float = 0.25f
private val LogParticlePadding: Float = 2f
private val LogParticleSize: Float = 10f
private val LogParticleMoveSpeed: Float = 1f
private val LogParticleMoveDistance: Float = 20f
private var HolidaySprite: Option[String] = None private var HolidaySprite: Option[String] = None
{ {

View File

@ -3,6 +3,7 @@ package ocelot.desktop.node
import ocelot.desktop.audio.SoundSource import ocelot.desktop.audio.SoundSource
import ocelot.desktop.color.{Color, RGBAColor} import ocelot.desktop.color.{Color, RGBAColor}
import ocelot.desktop.geometry.{Rect2D, Size2D, Vector2D} import ocelot.desktop.geometry.{Rect2D, Size2D, Vector2D}
import ocelot.desktop.graphics.IconSource.Animation
import ocelot.desktop.graphics.{Graphics, IconSource} import ocelot.desktop.graphics.{Graphics, IconSource}
import ocelot.desktop.node.Node._ import ocelot.desktop.node.Node._
import ocelot.desktop.ui.event.handlers.{ClickHandler, DragHandler, HoverHandler} import ocelot.desktop.ui.event.handlers.{ClickHandler, DragHandler, HoverHandler}
@ -116,8 +117,11 @@ abstract class Node extends Widget with DragHandler with ClickHandler with Hover
super.dispose() super.dispose()
} }
// TODO: remove this (subsumed by iconSource)
def icon: String = "icons/NA" def icon: String = "icons/NA"
def iconSource: IconSource = IconSource(icon)
def iconColor: Color = RGBAColor(255, 255, 255) def iconColor: Color = RGBAColor(255, 255, 255)
def ports: Array[NodePort] = Array() def ports: Array[NodePort] = Array()
@ -257,12 +261,13 @@ abstract class Node extends Widget with DragHandler with ClickHandler with Hover
drawHighlight(g) drawHighlight(g)
g.sprite( g.sprite(
icon, iconSource.path,
position. x + HighlightThickness, position. x + HighlightThickness,
position.y + HighlightThickness, position.y + HighlightThickness,
size.width - HighlightThickness * 2, size.width - HighlightThickness * 2,
size.height - HighlightThickness * 2, size.height - HighlightThickness * 2,
iconColor iconColor,
iconSource.animation,
) )
} }

View File

@ -1,6 +1,7 @@
package ocelot.desktop.node package ocelot.desktop.node
import ocelot.desktop.entity.{Camera, OpenFMRadio} import ocelot.desktop.entity.{Camera, OcelotBlock, OpenFMRadio}
import ocelot.desktop.graphics.IconSource
import ocelot.desktop.node.nodes._ import ocelot.desktop.node.nodes._
import totoro.ocelot.brain.entity.{Cable, Case, ColorfulLamp, FloppyDiskDrive, HologramProjector, IronNoteBlock, Microcontroller, NoteBlock, Rack, Raid, Relay, Screen, TapeDrive} import totoro.ocelot.brain.entity.{Cable, Case, ColorfulLamp, FloppyDiskDrive, HologramProjector, IronNoteBlock, Microcontroller, NoteBlock, Rack, Raid, Relay, Screen, TapeDrive}
import totoro.ocelot.brain.util.Tier import totoro.ocelot.brain.util.Tier
@ -100,4 +101,12 @@ object NodeRegistry {
addType(NodeType("Tape Drive", "nodes/TapeDrive", None) { addType(NodeType("Tape Drive", "nodes/TapeDrive", None) {
new TapeDriveNode(new TapeDrive) new TapeDriveNode(new TapeDrive)
}) })
// ------------------------------ Custom nodes ------------------------------
nextGroup("Custom")
addType(NodeType("Ocelot Block", IconSource.Nodes.OcelotBlock.Default, None) {
new OcelotBlockNode(new OcelotBlock)
})
} }

View File

@ -1,17 +1,27 @@
package ocelot.desktop.node package ocelot.desktop.node
import ocelot.desktop.graphics.IconSource
import totoro.ocelot.brain.util.Tier.Tier import totoro.ocelot.brain.util.Tier.Tier
class NodeType(val name: String, val icon: String, val tier: Option[Tier], factory: => Node) extends Ordered[NodeType] { class NodeType(val name: String, val icon: IconSource, val tier: Option[Tier], factory: => Node)
extends Ordered[NodeType] {
def make(): Node = factory def make(): Node = factory
override def compare(that: NodeType): Int = this.name.compare(that.name) override def compare(that: NodeType): Int = this.name.compare(that.name)
} }
object NodeType { object NodeType {
def apply(name: String, icon: String, tier: Tier)(factory: => Node): NodeType = // TODO: remove this
def apply(name: String, icon: IconSource, tier: Tier)(factory: => Node): NodeType =
new NodeType(name, icon, Some(tier), factory) new NodeType(name, icon, Some(tier), factory)
def apply(name: String, icon: String, tier: Option[Tier])(factory: => Node): NodeType = def apply(name: String, icon: String, tier: Tier)(factory: => Node): NodeType =
new NodeType(name, IconSource(icon), Some(tier), factory)
// TODO: remove this
def apply(name: String, icon: IconSource, tier: Option[Tier])(factory: => Node): NodeType =
new NodeType(name, icon, tier, factory) new NodeType(name, icon, tier, factory)
def apply(name: String, icon: String, tier: Option[Tier])(factory: => Node): NodeType =
new NodeType(name, IconSource(icon), tier, factory)
} }

View File

@ -39,12 +39,13 @@ class NodeTypeWidget(val nodeType: NodeType) extends Widget with ClickHandler wi
val size = Spritesheet.spriteSize(nodeType.icon) * 4 val size = Spritesheet.spriteSize(nodeType.icon) * 4
g.sprite( g.sprite(
nodeType.icon, nodeType.icon.path,
position.x + Size / 2 - size.width / 2, position.x + Size / 2 - size.width / 2,
position.y + Size / 2 - size.height / 2, position.y + Size / 2 - size.height / 2,
size.width, size.width,
size.height, size.height,
nodeType.tier.map(TierColor.get).getOrElse(Color.White) nodeType.tier.map(TierColor.get).getOrElse(Color.White),
nodeType.icon.animation,
) )
} }

View File

@ -0,0 +1,80 @@
package ocelot.desktop.node
import ocelot.desktop.ColorScheme
import ocelot.desktop.entity.traits.OcelotInterface
import ocelot.desktop.geometry.FloatUtils.ExtendedFloat
import ocelot.desktop.geometry.Vector2D
import ocelot.desktop.graphics.Graphics
import ocelot.desktop.node.OcelotLogParticleNode._
import ocelot.desktop.ui.UiHandler
import ocelot.desktop.ui.event.BrainEvent
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()
}
}
eventHandlers += {
case BrainEvent(OcelotInterface.LogEvent.CardToUser(_, _)) =>
addLogParticle()
}
override def update(): Unit = {
super.update()
logParticles.synchronized {
logParticles.foreach(particle => particle.time += LogParticleMoveSpeed * UiHandler.dt)
logParticles.filterInPlace(_.time <= 1f)
}
}
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()
val r1 = (bounds.w max bounds.h) / math.sqrt(2) + 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 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 {
private val MaxLogParticles: Int = 15
private val LogParticleMaxAngle: Float = 0.25f
private val LogParticleCount: Int = 12
private val LogParticleGrow: Float = 0.25f
private val LogParticlePadding: Float = 2f
private val LogParticleSize: Float = 10f
private val LogParticleMoveSpeed: Float = 1f
private val LogParticleMoveDistance: Float = 20f
}

View File

@ -0,0 +1,83 @@
package ocelot.desktop.node.nodes
import ocelot.desktop.color.RGBAColorNorm
import ocelot.desktop.entity.OcelotBlock
import ocelot.desktop.entity.traits.OcelotInterface
import ocelot.desktop.geometry.FloatUtils.ExtendedFloat
import ocelot.desktop.graphics.{Graphics, IconSource}
import ocelot.desktop.node.Node.HighlightThickness
import ocelot.desktop.node.nodes.OcelotBlockNode.ActivityFadeOutMs
import ocelot.desktop.node.{EntityNode, LabeledEntityNode, OcelotLogParticleNode, WindowedNode}
import ocelot.desktop.ui.widget.LogWidget
import ocelot.desktop.ui.widget.LogWidget.LogEntry
import ocelot.desktop.util.OcelotInterfaceLogStorage
import ocelot.desktop.windows.OcelotInterfaceWindow
class OcelotBlockNode(val ocelot: OcelotBlock)
extends EntityNode(ocelot)
with LabeledEntityNode
with OcelotLogParticleNode
with OcelotInterfaceLogStorage
with WindowedNode[OcelotInterfaceWindow] {
override def name: String = "Ocelot Block"
override def iconSource: IconSource = IconSource.Nodes.OcelotBlock.Default
override def ocelotInterface: OcelotInterface = ocelot
private var lastRx: Long = -1L
private var lastTx: Long = -1L
override protected def onMessagesAdded(entries: => Iterable[LogWidget.LogEntry]): Unit = {
super.onMessagesAdded(entries)
var hasRx = false
var hasTx = false
for (entry <- entries.iterator.takeWhile(_ => !hasRx || !hasTx)) {
entry match {
case _: LogEntry.Rx => hasRx = true
case _: LogEntry.Tx => hasTx = true
}
}
val t = System.currentTimeMillis()
if (hasRx) {
lastRx = t
}
if (hasTx) {
lastTx = t
}
}
override def draw(g: Graphics): Unit = {
super.draw(g)
val t = System.currentTimeMillis()
drawActivity(g, IconSource.Nodes.OcelotBlock.Rx, lastRx, t)
drawActivity(g, IconSource.Nodes.OcelotBlock.Tx, lastTx, t)
}
private def drawActivity(g: Graphics, icon: IconSource, lastActivity: Long, currentTime: Long): Unit = {
val alpha = (1 - (currentTime - lastActivity) / ActivityFadeOutMs).clamp()
if (alpha > 0) {
g.sprite(
icon.path,
position.x + HighlightThickness,
position.y + HighlightThickness,
size.width - HighlightThickness * 2,
size.height - HighlightThickness * 2,
RGBAColorNorm(1f, 1f, 1f, alpha),
icon.animation,
)
}
}
}
object OcelotBlockNode {
private val ActivityFadeOutMs: Float = 300f
}

View File

@ -3,7 +3,8 @@ package ocelot.desktop.node.nodes
import ocelot.desktop.geometry.{Rect2D, Size2D, Vector2D} import ocelot.desktop.geometry.{Rect2D, Size2D, Vector2D}
import ocelot.desktop.graphics.{Graphics, IconSource} import ocelot.desktop.graphics.{Graphics, IconSource}
import ocelot.desktop.inventory.Item import ocelot.desktop.inventory.Item
import ocelot.desktop.inventory.item.{DiskDriveMountableItem, RackMountableItem, ServerItem} import ocelot.desktop.inventory.item.{DiskDriveMountableItem, ServerItem}
import ocelot.desktop.inventory.traits.RackMountableItem
import ocelot.desktop.node.Node.{HighlightThickness, NoHighlightSize, Size, TexelCount} import ocelot.desktop.node.Node.{HighlightThickness, NoHighlightSize, Size, TexelCount}
import ocelot.desktop.node.{ComputerAwareNode, NodePort, WindowedNode} import ocelot.desktop.node.{ComputerAwareNode, NodePort, WindowedNode}
import ocelot.desktop.ui.event.{BrainEvent, ClickEvent} import ocelot.desktop.ui.event.{BrainEvent, ClickEvent}

View File

@ -79,7 +79,7 @@ class DiskEditWindow(item: DiskItem) extends PanelWindow {
for (dyeColor <- row) { for (dyeColor <- row) {
def isColorSelected: Boolean = dyeColor == item.color.get def isColorSelected: Boolean = dyeColor == item.color.get
val floppyIcon = IconSource.FloppyDisk(dyeColor).path val floppyIcon = IconSource.Items.FloppyDisk(dyeColor).path
children :+= new IconButton( children :+= new IconButton(
floppyIcon, floppyIcon,
floppyIcon, floppyIcon,

View File

@ -2,7 +2,8 @@ package ocelot.desktop.ui.widget.slot
import ocelot.desktop.graphics.IconSource import ocelot.desktop.graphics.IconSource
import ocelot.desktop.inventory.Inventory import ocelot.desktop.inventory.Inventory
import ocelot.desktop.inventory.item.{RackMountableItem, ServerItem} import ocelot.desktop.inventory.item.ServerItem
import ocelot.desktop.inventory.traits.RackMountableItem
class RackMountableSlotWidget(slot: Inventory#Slot) class RackMountableSlotWidget(slot: Inventory#Slot)
extends SlotWidget[RackMountableItem](slot) extends SlotWidget[RackMountableItem](slot)

View File

@ -0,0 +1,167 @@
package ocelot.desktop.util
import ocelot.desktop.entity.traits.OcelotInterface
import ocelot.desktop.ui.event.{BrainEvent, EventAware}
import ocelot.desktop.ui.widget.LogWidget.LogEntry
import ocelot.desktop.ui.widget.window.Windowed
import ocelot.desktop.util.OcelotInterfaceLogStorage._
import ocelot.desktop.windows.OcelotInterfaceWindow
import totoro.ocelot.brain.nbt.ExtendedNBT.extendNBTTagList
import totoro.ocelot.brain.nbt.{NBT, NBTBase, NBTTagCompound, NBTTagString}
import scala.collection.mutable
import scala.jdk.CollectionConverters.BufferHasAsJava
trait OcelotInterfaceLogStorage
extends EventAware
with Persistable
with Windowed[OcelotInterfaceWindow]
with Logging {
def ocelotInterface: OcelotInterface
def name: String
override def createWindow(): OcelotInterfaceWindow = new OcelotInterfaceWindow(this)
private var _messageLimit: Int = 1000
// NOTE: access must be synchronized!
// ocelot.log() is a direct method, so it may push events even if the tick lock is not acquired
private val _entries = mutable.ArrayDeque.empty[LogEntry]
eventHandlers += {
case BrainEvent(OcelotInterface.LogEvent.Clear(_)) =>
clear()
case BrainEvent(OcelotInterface.LogEvent.CardToUser(_, message)) =>
addEntry(LogEntry.Rx(message))
case BrainEvent(OcelotInterface.LogEvent.UserToCard(_, message)) =>
addEntry(LogEntry.Tx(message))
}
private def loadEntry(nbt: NBTTagCompound): Either[String, LogEntry] = {
nbt.getString(EntryKindTag) match {
case EntryKindRx => Right(LogEntry.Rx(nbt.getString(EntryMessageTag)))
case EntryKindTx => Right(LogEntry.Tx(nbt.getString(EntryMessageTag)))
case "" => Left("entry kind not set")
case k => Left(s"unknown entry kind: $k")
}
}
private def saveEntry(entry: LogEntry): NBTTagCompound = {
val result = new NBTTagCompound()
entry match {
case LogEntry.Rx(message) =>
result.setString(EntryKindTag, EntryKindRx)
result.setString(EntryMessageTag, message)
case LogEntry.Tx(message) =>
result.setString(EntryKindTag, EntryKindTx)
result.setString(EntryMessageTag, message)
}
result
}
override def load(nbt: NBTTagCompound): Unit = {
super.load(nbt)
clear()
if (nbt.hasKey(MessageLimitTag)) {
messageLimit = nbt.getInteger(MessageLimitTag)
}
val entries = if (nbt.hasKey(EntriesTag)) {
nbt.getTagList(EntriesTag, NBT.TAG_COMPOUND).iterator[NBTTagCompound].zipWithIndex.flatMap {
case (entryNbt, idx) =>
loadEntry(entryNbt) match {
case Left(err) =>
logger.warn(
s"Could not restore log entry (idx $idx) of ocelot interface ${ocelotInterface.node.address}:" +
s" $err"
)
None
case Right(entry) => Some(entry)
}
}
} else {
// old save format: plain messages
nbt
.getTagList(MessagesTag, NBT.TAG_STRING)
.iterator[NBTTagString]
.map(entryNbt => LogEntry.Rx(entryNbt.getString))
}
addEntries(entries.toSeq)
}
override def save(nbt: NBTTagCompound): Unit = _entries.synchronized {
super.save(nbt)
nbt.setTagList(EntriesTag, _entries.map(saveEntry(_).asInstanceOf[NBTBase]).asJava)
nbt.setInteger(MessageLimitTag, _messageLimit)
}
def messageLimit: Int = _messageLimit
def messageLimit_=(limit: Int): Unit = _entries.synchronized {
require(limit > 0)
ensureFreeSpace(_entries.length - limit)
_messageLimit = limit
}
def entryCount: Int = _entries.synchronized {
_entries.length
}
def clear(): Unit = _entries.synchronized {
val count = _entries.length
_entries.clear()
window.onMessagesRemoved(count)
}
private def addEntry(entry: LogEntry): Unit = _entries.synchronized {
ensureFreeSpace(1)
_entries += entry
onMessagesAdded(Some(entry))
}
private def addEntries(entries: Seq[LogEntry]): Unit = _entries.synchronized {
ensureFreeSpace(entries.length)
val prevCount = _entries.length
_entries ++= entries.view.takeRight(messageLimit)
onMessagesAdded(_entries.view.takeRight(_entries.length - prevCount))
}
private def ensureFreeSpace(n: Int): Unit = _entries.synchronized {
val prevCount = _entries.length
_entries.takeRightInPlace(messageLimit - n)
val removedCount = prevCount - _entries.length
if (removedCount > 0) {
window.onMessagesRemoved(removedCount)
}
}
protected def onMessagesAdded(entries: => Iterable[LogEntry]): Unit = {
window.onMessagesAdded(entries)
}
}
object OcelotInterfaceLogStorage {
private val MessagesTag = "messages"
private val EntriesTag = "entries"
private val MessageLimitTag = "limit"
private val EntryKindTag = "kind"
private val EntryKindRx = "rx"
private val EntryKindTx = "tx"
private val EntryMessageTag = "msg"
}

View File

@ -1,7 +1,7 @@
package ocelot.desktop.util package ocelot.desktop.util
import ocelot.desktop.geometry.{Rect2D, Size2D} import ocelot.desktop.geometry.{Rect2D, Size2D}
import ocelot.desktop.graphics.Texture import ocelot.desktop.graphics.{IconSource, Texture}
import javax.imageio.ImageIO import javax.imageio.ImageIO
import scala.collection.mutable import scala.collection.mutable
@ -15,6 +15,20 @@ object Spritesheet extends Resource with Logging {
def spriteSize(sprite: String): Size2D = sprites(sprite).size * resolution def spriteSize(sprite: String): Size2D = sprites(sprite).size * resolution
def spriteSize(iconSource: IconSource): Size2D = iconSource.animation match {
case Some(animation) =>
animation.frameSize match {
case Some(size) => size
case None =>
val size = spriteSize(iconSource.path)
Size2D(size.width, size.width)
}
case None => spriteSize(iconSource.path)
}
def load(): Unit = { def load(): Unit = {
logger.info("Loading sprites") logger.info("Loading sprites")

View File

@ -1,15 +1,14 @@
package ocelot.desktop.ui.widget.card package ocelot.desktop.windows
import ocelot.desktop.geometry.{Padding2D, Size2D} import ocelot.desktop.geometry.{Padding2D, Size2D}
import ocelot.desktop.inventory.item.OcelotCardItem
import ocelot.desktop.ui.layout.{AlignItems, Layout, LinearLayout} import ocelot.desktop.ui.layout.{AlignItems, Layout, LinearLayout}
import ocelot.desktop.ui.widget.LogWidget.LogEntry import ocelot.desktop.ui.widget.LogWidget.LogEntry
import ocelot.desktop.ui.widget.{Button, Checkbox, Filler, Label, LogWidget, PaddingBox, TextInput, Widget}
import ocelot.desktop.ui.widget.window.PanelWindow import ocelot.desktop.ui.widget.window.PanelWindow
import ocelot.desktop.util.Orientation import ocelot.desktop.ui.widget.{Button, Checkbox, Filler, Label, LogWidget, PaddingBox, TextInput, Widget}
import ocelot.desktop.util.{OcelotInterfaceLogStorage, Orientation}
class OcelotCardWindow(item: OcelotCardItem) extends PanelWindow { class OcelotInterfaceWindow(storage: OcelotInterfaceLogStorage) extends PanelWindow {
override protected def title: String = s"Ocelot card ${item.component.node.address}" override protected def title: String = s"${storage.name} ${storage.ocelotInterface.node.address}"
private val logWidget: LogWidget = new LogWidget { private val logWidget: LogWidget = new LogWidget {
override protected def textWidth: Int = 50 override protected def textWidth: Int = 50
@ -30,7 +29,7 @@ class OcelotCardWindow(item: OcelotCardItem) extends PanelWindow {
} }
children :+= new Label { children :+= new Label {
override def text: String = s"Messages: ${item.entryCount} / ${item.messageLimit}" override def text: String = s"Messages: ${storage.entryCount} / ${storage.messageLimit}"
} }
children :+= new Widget { children :+= new Widget {
@ -46,7 +45,7 @@ class OcelotCardWindow(item: OcelotCardItem) extends PanelWindow {
Padding2D(right = 12) Padding2D(right = 12)
) )
children :+= new TextInput(item.messageLimit.toString) { children :+= new TextInput(storage.messageLimit.toString) {
private def parseInput(text: String): Option[Int] = text.toIntOption.filter(_ > 0) private def parseInput(text: String): Option[Int] = text.toIntOption.filter(_ > 0)
override def minimumSize: Size2D = super.minimumSize.copy(width = 60) override def minimumSize: Size2D = super.minimumSize.copy(width = 60)
@ -57,7 +56,7 @@ class OcelotCardWindow(item: OcelotCardItem) extends PanelWindow {
override def onConfirm(): Unit = { override def onConfirm(): Unit = {
for (messageLimit <- parseInput(text)) { for (messageLimit <- parseInput(text)) {
item.messageLimit = messageLimit storage.messageLimit = messageLimit
} }
super.onConfirm() super.onConfirm()
@ -82,7 +81,7 @@ class OcelotCardWindow(item: OcelotCardItem) extends PanelWindow {
}) })
private def clear(): Unit = { private def clear(): Unit = {
item.clear() storage.clear()
} }
def onMessagesAdded(entries: Iterable[LogEntry]): Unit = { def onMessagesAdded(entries: Iterable[LogEntry]): Unit = {
@ -94,6 +93,6 @@ class OcelotCardWindow(item: OcelotCardItem) extends PanelWindow {
} }
def pushLine(line: String): Unit = { def pushLine(line: String): Unit = {
item.component.pushMessage(line) storage.ocelotInterface.pushMessage(line)
} }
} }