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

View File

@ -34,7 +34,7 @@ object ComponentBusItem {
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))

View File

@ -23,7 +23,7 @@ object CpuItem {
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))

View File

@ -14,7 +14,7 @@ object DataCardItem {
abstract class Factory extends ItemFactory {
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 {

View File

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

View File

@ -84,7 +84,7 @@ object EepromItem {
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(_)))
}

View File

@ -84,7 +84,7 @@ object FloppyItem {
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(_)))
}

View File

@ -21,7 +21,7 @@ object GraphicsCardItem {
override def name: String = s"Graphics Card (${_tier.label})"
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))

View File

@ -76,7 +76,7 @@ object HddItem {
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(
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 icon: IconSource = IconSource.InternetCard
override def icon: IconSource = IconSource.Items.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 icon: IconSource = IconSource.LinkedCard
override def icon: IconSource = IconSource.Items.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 icon: IconSource = IconSource.Memory(ExtendedTier.Creative)
override def icon: IconSource = IconSource.Items.Memory(ExtendedTier.Creative)
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 icon: IconSource = IconSource.Memory(memoryTier)
override def icon: IconSource = IconSource.Items.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 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)

View File

@ -3,53 +3,27 @@ package ocelot.desktop.inventory.item
import ocelot.desktop.ColorScheme
import ocelot.desktop.color.Color
import ocelot.desktop.entity.OcelotCard
import ocelot.desktop.entity.traits.OcelotInterface
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.{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.window.Windowed
import ocelot.desktop.util.Logging
import totoro.ocelot.brain.nbt.ExtendedNBT.extendNBTTagList
import totoro.ocelot.brain.nbt.{NBT, NBTBase, NBTTagCompound, NBTTagString}
import ocelot.desktop.util.{Logging, OcelotInterfaceLogStorage}
import totoro.ocelot.brain.util.Tier
import totoro.ocelot.brain.util.Tier.Tier
import scala.collection.mutable
import scala.jdk.CollectionConverters.BufferHasAsJava
class OcelotCardItem(val ocelotCard: OcelotCard)
extends Item
with ComponentItem
with OcelotInterfaceLogStorage
with PersistableItem
with CardItem
with Windowed[OcelotCardWindow]
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 ocelotInterface: OcelotInterface = ocelotCard
override def tooltipNameColor: Color = ColorScheme("OcelotCardTooltip")
override def fillRmbMenu(menu: ContextMenu): Unit = {
@ -62,124 +36,10 @@ class OcelotCardItem(val ocelotCard: OcelotCard)
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
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 {
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 {
override type I = OcelotCardItem
@ -189,7 +49,7 @@ object OcelotCardItem {
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)

View File

@ -21,7 +21,7 @@ object RedstoneCardItem {
abstract class Factory extends ItemFactory {
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 {

View File

@ -29,7 +29,7 @@ object SelfDestructingCardItem {
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)

View File

@ -1,6 +1,7 @@
package ocelot.desktop.inventory.item
import ocelot.desktop.graphics.IconSource
import ocelot.desktop.inventory.traits.RackMountableItem
import ocelot.desktop.inventory.{ItemFactory, ItemRecoverer}
import ocelot.desktop.ui.widget.slot._
import ocelot.desktop.util.ComputerType.ComputerType
@ -106,7 +107,7 @@ object ServerItem {
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 = {
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 icon: IconSource = IconSource.SoundCard
override def icon: IconSource = IconSource.Items.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 icon: IconSource = IconSource.Tape(kind)
override def icon: IconSource = IconSource.Items.Tape(kind)
override def build(): TapeItem = new TapeItem(new Tape(kind))

View File

@ -14,7 +14,7 @@ object WirelessNetworkCardItem {
abstract class Factory extends ItemFactory {
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) {

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.traits.ComponentItem
import ocelot.desktop.node.nodes.RackNode
import ocelot.desktop.ui.widget.contextmenu.ContextMenu
import totoro.ocelot.brain.entity.result

View File

@ -1,8 +1,7 @@
package ocelot.desktop.node
import ocelot.desktop.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
@ -13,20 +12,18 @@ 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._
import java.util.Calendar
import scala.collection.mutable
import scala.collection.mutable.ArrayBuffer
import scala.util.Random
abstract class ComputerAwareNode(entity: Entity with Environment with WorkspaceAware)
extends EntityNode(entity)
with SyncedInventory
with DiskActivityHandler
with OcelotLogParticleNode
with ShiftClickNode {
// 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))
}
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 def soundCardStream: SoundStream = soundCardSounds._1
private def soundCardSource: SoundSource = soundCardSounds._2
@ -84,17 +67,9 @@ abstract class ComputerAwareNode(entity: Entity with Environment with WorkspaceA
SoundSource.MinecraftExplosion.play()
destroy()
})
case BrainEvent(OcelotCard.LogEvent.CardToUser(_, _)) =>
addLogParticle()
}
override def update(): Unit = {
logParticles.synchronized {
logParticles.foreach(particle => particle.time += LogParticleMoveSpeed * UiHandler.dt)
logParticles.filterInPlace(_.time <= 1f)
}
messages.synchronized {
messages.mapInPlace { case (t, message) => (t + ErrorMessageMoveSpeed * UiHandler.dt, message) }
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 = {
super.drawParticles(g)
drawMessageParticles(g)
drawLogParticles(g)
}
protected def drawOverlay(g: Graphics): Unit = {
@ -162,15 +118,6 @@ object ComputerAwareNode {
private val MaxErrorMessageDistance: Float = 50f
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
{

View File

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

View File

@ -1,6 +1,7 @@
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 totoro.ocelot.brain.entity.{Cable, Case, ColorfulLamp, FloppyDiskDrive, HologramProjector, IronNoteBlock, Microcontroller, NoteBlock, Rack, Raid, Relay, Screen, TapeDrive}
import totoro.ocelot.brain.util.Tier
@ -100,4 +101,12 @@ object NodeRegistry {
addType(NodeType("Tape Drive", "nodes/TapeDrive", None) {
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
import ocelot.desktop.graphics.IconSource
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
override def compare(that: NodeType): Int = this.name.compare(that.name)
}
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)
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)
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
g.sprite(
nodeType.icon,
nodeType.icon.path,
position.x + Size / 2 - size.width / 2,
position.y + Size / 2 - size.height / 2,
size.width,
size.height,
nodeType.tier.map(TierColor.get).getOrElse(Color.White)
nodeType.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.graphics.{Graphics, IconSource}
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.{ComputerAwareNode, NodePort, WindowedNode}
import ocelot.desktop.ui.event.{BrainEvent, ClickEvent}

View File

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

View File

@ -2,7 +2,8 @@ package ocelot.desktop.ui.widget.slot
import ocelot.desktop.graphics.IconSource
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)
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
import ocelot.desktop.geometry.{Rect2D, Size2D}
import ocelot.desktop.graphics.Texture
import ocelot.desktop.graphics.{IconSource, Texture}
import javax.imageio.ImageIO
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(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 = {
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.inventory.item.OcelotCardItem
import ocelot.desktop.ui.layout.{AlignItems, Layout, LinearLayout}
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.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 {
override protected def title: String = s"Ocelot card ${item.component.node.address}"
class OcelotInterfaceWindow(storage: OcelotInterfaceLogStorage) extends PanelWindow {
override protected def title: String = s"${storage.name} ${storage.ocelotInterface.node.address}"
private val logWidget: LogWidget = new LogWidget {
override protected def textWidth: Int = 50
@ -30,7 +29,7 @@ class OcelotCardWindow(item: OcelotCardItem) extends PanelWindow {
}
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 {
@ -46,7 +45,7 @@ class OcelotCardWindow(item: OcelotCardItem) extends PanelWindow {
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)
override def minimumSize: Size2D = super.minimumSize.copy(width = 60)
@ -57,7 +56,7 @@ class OcelotCardWindow(item: OcelotCardItem) extends PanelWindow {
override def onConfirm(): Unit = {
for (messageLimit <- parseInput(text)) {
item.messageLimit = messageLimit
storage.messageLimit = messageLimit
}
super.onConfirm()
@ -82,7 +81,7 @@ class OcelotCardWindow(item: OcelotCardItem) extends PanelWindow {
})
private def clear(): Unit = {
item.clear()
storage.clear()
}
def onMessagesAdded(entries: Iterable[LogEntry]): Unit = {
@ -94,6 +93,6 @@ class OcelotCardWindow(item: OcelotCardItem) extends PanelWindow {
}
def pushLine(line: String): Unit = {
item.component.pushMessage(line)
storage.ocelotInterface.pushMessage(line)
}
}