From bdc80516c0f0adbb3b7f236a16f912445a08bc39 Mon Sep 17 00:00:00 2001 From: igor Date: Tue, 30 Jan 2018 05:41:56 +0300 Subject: [PATCH] =?UTF-8?q?=D0=91=D0=B5=D0=BA=D0=B0=D0=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 640cd89f-8e29-4b66-a0eb-7680c33760b4/.prop | 1 + 640cd89f-8e29-4b66-a0eb-7680c33760b4/1.pic | Bin 0 -> 144 bytes .../MineOS/Applications/.icons | 1 + .../MineOS/Applications/3DPrint.app/Main.lua | 764 ++++ .../3DPrint.app/Resources/About/Russian.txt | 1 + .../3DPrint.app/Resources/Icon.pic | Bin 0 -> 142 bytes .../MineOS/Applications/3DTest.app/Main.lua | 539 +++ .../3DTest.app/Resources/About/Russian.txt | 1 + .../3DTest.app/Resources/Icon.pic | Bin 0 -> 134 bytes .../Applications/AppMarket.app/Main.lua | 246 ++ .../AppMarket.app/Resources/About/Russian.txt | 1 + .../AppMarket.app/Resources/Icon.pic | Bin 0 -> 117 bytes .../Resources/Localization/English.lang | 17 + .../Resources/Localization/Russian.lang | 17 + .../AppMarket.app/Resources/TempIcon.pic | Bin 0 -> 254 bytes .../AppMarket.app/Resources/Update.pic | Bin 0 -> 2689 bytes .../Applications/Battleship.app/Main.lua | 339 ++ .../Resources/About/Russian.txt | 1 + .../Battleship.app/Resources/Icon.pic | Bin 0 -> 106 bytes .../Applications/BufferDemo.app/Main.lua | 160 + .../Resources/About/Russian.txt | 1 + .../BufferDemo.app/Resources/Icon.pic | Bin 0 -> 108 bytes .../BufferDemo.app/Resources/Wallpaper.pic | Bin 0 -> 8639 bytes .../MineOS/Applications/Calendar.app/Main.lua | 379 ++ .../Calendar.app/Resources/About/Russian.txt | 1 + .../Calendar.app/Resources/Icon.pic | Bin 0 -> 165 bytes .../MineOS/Applications/Camera.app/Main.lua | 229 + .../Camera.app/Resources/About/Russian.txt | 1 + .../Camera.app/Resources/Icon.pic | Bin 0 -> 187 bytes .../Applications/ChristmasTree.app/Main.lua | 148 + .../Resources/About/Russian.txt | 1 + .../ChristmasTree.app/Resources/Icon.pic | Bin 0 -> 152 bytes .../MineOS/Applications/CodeDoor.app/Main.lua | 276 ++ .../CodeDoor.app/Resources/About/Russian.txt | 1 + .../CodeDoor.app/Resources/Icon.pic | Bin 0 -> 115 bytes .../MineOS/Applications/Control.app/Main.lua | 60 + .../Control.app/Resources/Icon.pic | Bin 0 -> 168 bytes .../Resources/Localization/English.lang | 24 + .../Resources/Localization/Russian.lang | 24 + .../Control.app/Resources/Modules/1.lua | 189 + .../Control.app/Resources/Modules/2.lua | 107 + .../Control.app/Resources/Modules/3.lua | 146 + .../Control.app/Resources/Modules/4.lua | 50 + .../MineOS/Applications/Finder.app/Main.lua | 363 ++ .../Finder.app/Resources/Favourites.cfg | 1 + .../Finder.app/Resources/Icon.pic | Bin 0 -> 155 bytes .../Applications/FlappyBird.app/Main.lua | 294 ++ .../Resources/About/Russian.txt | 1 + .../FlappyBird.app/Resources/Flappy.pic | Bin 0 -> 274 bytes .../FlappyBird.app/Resources/Icon.pic | Bin 0 -> 146 bytes .../Applications/ForceAdmin.app/Main.lua | 93 + .../Resources/About/Russian.txt | 1 + .../ForceAdmin.app/Resources/Icon.pic | Bin 0 -> 222 bytes .../Applications/FuckTheRain.app/Main.lua | 77 + .../Resources/About/Russian.txt | 1 + .../FuckTheRain.app/Resources/Icon.pic | Bin 0 -> 83 bytes .../MineOS/Applications/GeoScan2.app/Main.lua | 220 + .../GeoScan2.app/Resources/Earth.pic | Bin 0 -> 1448 bytes .../GeoScan2.app/Resources/Icon.pic | Bin 0 -> 206 bytes .../MineOS/Applications/GuessWord.app/.icons | 1 + .../Applications/GuessWord.app/Main.lua | 588 +++ .../GuessWord.app/Resources/About/Russian.txt | 1 + .../GuessWord.app/Resources/Icon.pic | Bin 0 -> 121 bytes .../MineOS/Applications/HEX.app/Main.lua | 380 ++ .../HEX.app/Resources/About/Russian.txt | 1 + .../Applications/HEX.app/Resources/Icon.pic | Bin 0 -> 167 bytes .../Applications/HoloClock.app/Main.lua | 248 ++ .../HoloClock.app/Resources/Icon.pic | Bin 0 -> 134 bytes .../MineOS/Applications/HoloEdit.app/Main.lua | 785 ++++ .../HoloEdit.app/Resources/Icon.pic | Bin 0 -> 86 bytes .../Resources/Localization/English.lang | 29 + .../Resources/Localization/Russian.lang | 29 + .../Applications/InfoPanel.app/Main.lua | 186 + .../InfoPanel.app/Resources/About/Russian.txt | 1 + .../InfoPanel.app/Resources/Icon.pic | Bin 0 -> 171 bytes .../InfoPanel.app/Resources/Pages/Claims.txt | 15 + .../InfoPanel.app/Resources/Pages/Main.txt | 21 + .../InfoPanel.app/Resources/Pages/Rules.txt | 49 + .../InfoPanel.app/Resources/Pages/SSPI.txt | 35 + .../Applications/MineCode IDE.app/Main.lua | 1855 ++++++++ .../Resources/About/Russian.txt | 1 + .../MineCode IDE.app/Resources/Icon.pic | Bin 0 -> 179 bytes .../Resources/Localization/English.lang | 61 + .../Resources/Localization/Russian.lang | 61 + .../MineOS/Applications/Palette.app/Main.lua | 15 + .../Palette.app/Resources/Icon.pic | Bin 0 -> 201 bytes .../Applications/Photoshop.app/Main.lua | 1381 ++++++ .../Photoshop.app/Resources/About/Russian.txt | 1 + .../Photoshop.app/Resources/Icon.pic | Bin 0 -> 113 bytes .../Resources/Localization/English.lang | 92 + .../Resources/Localization/Russian.lang | 92 + .../Applications/PrintImage.app/Main.lua | 330 ++ .../PrintImage.app/Resources/Icon.pic | Bin 0 -> 104 bytes .../Applications/QuantumCube.app/Main.lua | 442 ++ .../Resources/About/Russian.txt | 1 + .../QuantumCube.app/Resources/Icon.pic | Bin 0 -> 97 bytes .../MineOS/Applications/Radio.app/Main.lua | 314 ++ .../Radio.app/Resources/About/Russian.txt | 1 + .../Applications/Radio.app/Resources/Icon.pic | Bin 0 -> 167 bytes .../MineOS/Applications/RayWalk.app/Main.lua | 200 + .../RayWalk.app/Resources/About/Russian.txt | 1 + .../RayWalk.app/Resources/Icon.pic | Bin 0 -> 101 bytes .../Resources/Localization/English.lang | 26 + .../Resources/Localization/Russian.lang | 25 + .../RayWalk.app/Resources/RayEngine.cfg | 28 + .../Weapons/CrosshairTextures/Angled.pic | Bin 0 -> 71 bytes .../Weapons/CrosshairTextures/Default.pic | Bin 0 -> 60 bytes .../Weapons/CrosshairTextures/Dotted.pic | Bin 0 -> 35 bytes .../Weapons/CrosshairTextures/Half.pic | Bin 0 -> 51 bytes .../Resources/Weapons/FireTextures/Plasma.pic | Bin 0 -> 1582 bytes .../Weapons/FireTextures/PowderFire.pic | Bin 0 -> 1187 bytes .../Weapons/WeaponTextures/Pistol.pic | Bin 0 -> 2882 bytes .../Weapons/WeaponTextures/Plasmer.pic | Bin 0 -> 5162 bytes .../Weapons/WeaponTextures/Rifle.pic | Bin 0 -> 3798 bytes .../Weapons/WeaponTextures/Sniper.pic | Bin 0 -> 5202 bytes .../RayWalk.app/Resources/Weapons/Weapons.cfg | 46 + .../Resources/Worlds/ExampleWorld/Blocks.cfg | 26 + .../Resources/Worlds/ExampleWorld/Map.cfg | 35 + .../Resources/Worlds/ExampleWorld/Player.cfg | 17 + .../Resources/Worlds/ExampleWorld/World.cfg | 28 + .../Resources/Worlds/SundownBeams/Blocks.cfg | 26 + .../Resources/Worlds/SundownBeams/Map.cfg | 28 + .../Resources/Worlds/SundownBeams/Player.cfg | 17 + .../Resources/Worlds/SundownBeams/World.cfg | 28 + .../Applications/RunningString.app/Main.lua | 72 + .../Resources/About/Russian.txt | 1 + .../RunningString.app/Resources/Icon.pic | Bin 0 -> 183 bytes .../MineOS/Applications/Shooting.app/Main.lua | 286 ++ .../Shooting.app/Resources/Icon.pic | Bin 0 -> 138 bytes .../MineOS/Applications/Stargate.app/Main.lua | 330 ++ .../Stargate.app/Resources/Ch1.pic | Bin 0 -> 266 bytes .../Stargate.app/Resources/Ch2.pic | Bin 0 -> 298 bytes .../Stargate.app/Resources/Icon.pic | Bin 0 -> 292 bytes .../Stargate.app/Resources/OffOff.pic | Bin 0 -> 12475 bytes .../Stargate.app/Resources/OffOn.pic | Bin 0 -> 17083 bytes .../Stargate.app/Resources/OnOff.pic | Bin 0 -> 18443 bytes .../Stargate.app/Resources/OnOn.pic | Bin 0 -> 17855 bytes .../Applications/TurretControl.app/Main.lua | 291 ++ .../Resources/About/Russian.txt | 1 + .../TurretControl.app/Resources/Icon.pic | Bin 0 -> 152 bytes .../TurretControl.app/Resources/Turret.pic | Bin 0 -> 324 bytes .../MineOS/Applications/VK.app/Main.lua | 1367 ++++++ .../VK.app/Resources/About/Russian.txt | 1 + .../Applications/VK.app/Resources/Icon.pic | Bin 0 -> 144 bytes .../Applications/VK.app/Resources/VKLogo.pic | Bin 0 -> 769 bytes .../MineOS/Applications/Weather.app/Main.lua | 156 + .../Weather.app/Resources/About/Russian.txt | 1 + .../Weather.app/Resources/Cloudy.pic | Bin 0 -> 92 bytes .../Weather.app/Resources/Foggy.pic | Bin 0 -> 71 bytes .../Weather.app/Resources/Icon.pic | Bin 0 -> 242 bytes .../Weather.app/Resources/Rainy.pic | Bin 0 -> 83 bytes .../Weather.app/Resources/Snowy.pic | Bin 0 -> 71 bytes .../Weather.app/Resources/Stormy.pic | Bin 0 -> 110 bytes .../Weather.app/Resources/Sunny.pic | Bin 0 -> 170 bytes .../MineOS/Desktop/.icons | 1 + .../MineOS/Desktop/3DPrint.lnk | 1 + .../MineOS/Desktop/3DTest.lnk | 1 + .../MineOS/Desktop/Battleship.lnk | 1 + .../MineOS/Desktop/Braille.app/Main.lua | 268 ++ .../Desktop/Braille.app/Resources/Icon.pic | Bin 0 -> 171 bytes .../MineOS/Desktop/BufferDemo.lnk | 1 + .../MineOS/Desktop/Calendar.lnk | 1 + .../MineOS/Desktop/Camera.app/Main.lua | 191 + .../Desktop/Camera.app/Resources/Icon.pic | Bin 0 -> 214 bytes .../MineOS/Desktop/Camera.lnk | 1 + .../MineOS/Desktop/ChristmasTree.lnk | 1 + .../MineOS/Desktop/CodeDoor.lnk | 1 + .../MineOS/Desktop/EBAMARKET2.app/Main.lua | 1270 ++++++ .../Desktop/EBAMARKET2.app/Resources/Icon.pic | Bin 0 -> 117 bytes .../MineOS/Desktop/FlappyBird.lnk | 1 + .../MineOS/Desktop/ForceAdmin.lnk | 1 + .../MineOS/Desktop/FuckTheRain.lnk | 1 + .../MineOS/Desktop/GeoScan2.lnk | 1 + .../MineOS/Desktop/Graph2.app/Main.lua | 146 + .../Desktop/Graph2.app/Resources/Icon.pic | Bin 0 -> 161 bytes .../MineOS/Desktop/GuessWord.lnk | 1 + .../MineOS/Desktop/HEX.lnk | 1 + .../MineOS/Desktop/HoloClock.lnk | 1 + .../MineOS/Desktop/HoloEdit.lnk | 1 + .../MineOS/Desktop/InfoPanel.lnk | 1 + .../MineOS/Desktop/PS4.app/.icons | 1 + .../MineOS/Desktop/PS4.app/Main.lua | 299 ++ .../MineOS/Desktop/PS4.app/Resources/Icon.pic | Bin 0 -> 113 bytes .../PS4.app/Resources/Tools/01_Move.lua | 34 + .../PS4.app/Resources/Tools/02_Brush.lua | 31 + .../PS4.app/Resources/Tools/03_Eraser.lua | 31 + .../MineOS/Desktop/Palette.lnk | 1 + .../MineOS/Desktop/PrintImage.lnk | 1 + .../MineOS/Desktop/QuantumCube.lnk | 1 + .../MineOS/Desktop/Radio.lnk | 1 + .../MineOS/Desktop/RayWalk.lnk | 1 + .../MineOS/Desktop/RunningString.lnk | 1 + .../MineOS/Desktop/Shooting.lnk | 1 + .../MineOS/Desktop/Spinner.app/Main.lua | 93 + .../Desktop/Spinner.app/Resources/1.pic | Bin 0 -> 3217 bytes .../Desktop/Spinner.app/Resources/2.pic | Bin 0 -> 3339 bytes .../Desktop/Spinner.app/Resources/3.pic | Bin 0 -> 3479 bytes .../Desktop/Spinner.app/Resources/4.pic | Bin 0 -> 3397 bytes .../Desktop/Spinner.app/Resources/5.pic | Bin 0 -> 3319 bytes .../Desktop/Spinner.app/Resources/6.pic | Bin 0 -> 3451 bytes .../Desktop/Spinner.app/Resources/7.pic | Bin 0 -> 3499 bytes .../Desktop/Spinner.app/Resources/8.pic | Bin 0 -> 3417 bytes .../Desktop/Spinner.app/Resources/Icon.pic | Bin 0 -> 273 bytes .../MineOS/Desktop/Stargate.lnk | 1 + .../MineOS/Desktop/Translate.app/Main.lua | 192 + .../Translate.app/Resources/Config.cfg | 1 + .../Desktop/Translate.app/Resources/Icon.pic | Bin 0 -> 254 bytes .../Desktop/Translate.app/Resources/Logo.pic | Bin 0 -> 1869 bytes .../MineOS/Desktop/TurretControl.lnk | 1 + .../MineOS/Desktop/VK.lnk | 1 + .../MineOS/Desktop/Weather.lnk | 1 + .../MineOS/Pictures/AhsokaTano.pic | Bin 0 -> 35871 bytes .../MineOS/Pictures/Block.pic | Bin 0 -> 9557 bytes .../MineOS/Pictures/Catniss.pic | Bin 0 -> 39107 bytes .../MineOS/Pictures/Ciri.pic | Bin 0 -> 35705 bytes .../MineOS/Pictures/Girl.pic | Bin 0 -> 39309 bytes .../MineOS/Pictures/HilbertCurve.pic | Bin 0 -> 8735 bytes .../MineOS/Pictures/MoonTouch.pic | Bin 0 -> 14131 bytes .../MineOS/Pictures/Mystery.pic | Bin 0 -> 15535 bytes .../MineOS/Pictures/Nettle.pic | Bin 0 -> 14153 bytes .../MineOS/Pictures/Raspberry.pic | Bin 0 -> 14703 bytes .../MineOS/Pictures/Space.pic | Bin 0 -> 33041 bytes .../MineOS/Pictures/Tatu.pic | Bin 0 -> 34079 bytes .../MineOS/Pictures/TyanSunset.pic | Bin 0 -> 12407 bytes .../Application data/AppMarket/Cache/168.pic | Bin 0 -> 117 bytes .../Application data/AppMarket/Cache/170.pic | Bin 0 -> 179 bytes .../Application data/AppMarket/Cache/174.pic | Bin 0 -> 171 bytes .../Application data/AppMarket/Cache/176.pic | Bin 0 -> 155 bytes .../Application data/AppMarket/Cache/239.pic | Bin 0 -> 161 bytes .../Application data/AppMarket/Cache/244.pic | Bin 0 -> 242 bytes .../Application data/AppMarket/Cache/247.pic | Bin 0 -> 254 bytes .../Application data/AppMarket/Cache/249.pic | Bin 0 -> 167 bytes .../Application data/AppMarket/Cache/251.pic | Bin 0 -> 201 bytes .../Application data/AppMarket/Config.cfg | 1 + .../Application data/FlappyBird/Scores.cfg | 1 + .../Application data/HoloClock/Settings.cfg | 1 + .../Application data/MineCode IDE/Config.cfg | 1 + .../Application data/Palette/Favourites.cfg | 1 + .../Application data/Stargate/Contacts.cfg | 1 + .../Application data/Weather/Forecast.cfg | 1 + .../MineOS/System/Applications.cfg | 1129 +++++ .../MineOS/System/AutorunManager/Filelist.txt | 1 + .../MineOS/System/Extensions/Arc/Launcher.lua | 9 + .../System/Extensions/Lua/ContextMenu.lua | 29 + .../MineOS/System/Extensions/Lua/Launcher.lua | 8 + .../System/Extensions/Pic/ContextMenu.lua | 13 + .../MineOS/System/Icons/3DModel.pic | Bin 0 -> 156 bytes .../MineOS/System/Icons/Application.pic | Bin 0 -> 114 bytes .../MineOS/System/Icons/Archive.pic | Bin 0 -> 156 bytes .../MineOS/System/Icons/Computer.pic | Bin 0 -> 120 bytes .../MineOS/System/Icons/Config.pic | Bin 0 -> 112 bytes .../MineOS/System/Icons/FileNotExists.pic | Bin 0 -> 135 bytes .../MineOS/System/Icons/Floppy.pic | Bin 0 -> 222 bytes .../MineOS/System/Icons/Folder.pic | Bin 0 -> 90 bytes .../MineOS/System/Icons/HDD.pic | Bin 0 -> 248 bytes .../MineOS/System/Icons/Image.pic | Bin 0 -> 208 bytes .../MineOS/System/Icons/Lua.pic | Bin 0 -> 200 bytes .../MineOS/System/Icons/Pastebin.pic | Bin 0 -> 185 bytes .../MineOS/System/Icons/Robot.pic | Bin 0 -> 150 bytes .../MineOS/System/Icons/SampleIcon.pic | Bin 0 -> 161 bytes .../MineOS/System/Icons/Script.pic | Bin 0 -> 73 bytes .../MineOS/System/Icons/Steve.pic | Bin 0 -> 195 bytes .../MineOS/System/Icons/Tablet.pic | Bin 0 -> 158 bytes .../MineOS/System/Icons/Text.pic | Bin 0 -> 104 bytes .../MineOS/System/Icons/Trash.pic | Bin 0 -> 132 bytes .../MineOS/System/Installer.lua | 324 ++ .../MineOS/System/Localization/English.lang | 167 + .../MineOS/System/Localization/Russian.lang | 167 + .../MineOS/System/Properties.cfg | 86 + .../MineOS/System/Radio/Stations.cfg | 1 + .../MineOS/System/Screensavers/Clock.lua | 70 + .../MineOS/System/Screensavers/Lines.lua | 62 + .../MineOS/System/Screensavers/Mandala.lua | 147 + .../MineOS/System/Screensavers/Matrix.lua | 96 + .../MineOS/System/Screensavers/NyanCat.lua | 834 ++++ .../MineOS/System/Screensavers/XCOM.lua | 29 + .../MineOS/Trash/Config.cfg | 1 + .../MineOS/Trash/HEX-copy.app/Main.lua | 380 ++ .../Trash/HEX-copy.app/Resources/Icon.pic | Bin 0 -> 167 bytes .../MineOS/Trash/HEX.app/Main.lua | 380 ++ .../MineOS/Trash/HEX.app/Resources/Icon.pic | Bin 0 -> 167 bytes .../MineOS/Trash/Icon.pic | Bin 0 -> 117 bytes .../MineOS/Trash/LehaTanks.app/Main.lua | 413 ++ .../Trash/LehaTanks.app/Resources/Icon.pic | Bin 0 -> 76 bytes .../LehaTanks.app/Resources/Tanks/Bullet.pic | Bin 0 -> 22 bytes .../Resources/Tanks/Enemy1Down.pic | Bin 0 -> 175 bytes .../Resources/Tanks/Enemy1Left.pic | Bin 0 -> 159 bytes .../Resources/Tanks/Enemy1Right.pic | Bin 0 -> 176 bytes .../Resources/Tanks/Enemy1Up.pic | Bin 0 -> 175 bytes .../Resources/Tanks/Play1DOWN.pic | Bin 0 -> 175 bytes .../Resources/Tanks/Play1LEFT.pic | Bin 0 -> 159 bytes .../Resources/Tanks/Play1RIGHT.pic | Bin 0 -> 176 bytes .../LehaTanks.app/Resources/Tanks/Play1UP.pic | Bin 0 -> 175 bytes .../Textures/EnemyTank/EnemyTankDOWN.pic | Bin 0 -> 240 bytes .../Textures/EnemyTank/EnemyTankLEFT.pic | Bin 0 -> 220 bytes .../Textures/EnemyTank/EnemyTankRIGHT.pic | Bin 0 -> 220 bytes .../Textures/EnemyTank/EnemyTankUP.pic | Bin 0 -> 240 bytes .../Resources/Textures/Map/Base.pic | Bin 0 -> 94 bytes .../Resources/Textures/Map/Brick.pic | Bin 0 -> 87 bytes .../Player1Tank/Player1NewTankDOWN.pic | Bin 0 -> 240 bytes .../Player1Tank/Player1NewTankLEFT.pic | Bin 0 -> 220 bytes .../Player1Tank/Player1NewTankRIGHT.pic | Bin 0 -> 220 bytes .../Textures/Player1Tank/Player1NewTankUP.pic | Bin 0 -> 240 bytes .../MineOS/Trash/Sudoku.app/.icons | 1 + .../MineOS/Trash/Sudoku.app/Main.lua | 147 + .../MineOS/Trash/Sudoku.app/Resources/.icons | 1 + .../Trash/Sudoku.app/Resources/Icon.pic | Bin 0 -> 161 bytes .../MineOS/Trash/newApplicationInfoWidget | 1143 +++++ .../MultiScreen.cfg | 1 + 640cd89f-8e29-4b66-a0eb-7680c33760b4/OS.lua | 1016 +++++ .../OSEnergyTurrets.lua | 75 + .../Tanks/1.pic | Bin 0 -> 200 bytes .../Tanks/2.pic | Bin 0 -> 194 bytes .../Tanks/3.pic | Bin 0 -> 200 bytes .../Tanks/4.pic | Bin 0 -> 174 bytes .../autorunGenerator.lua | 268 ++ .../bin/address.lua | 2 + .../bin/alias.lua | 62 + .../bin/cat.lua | 37 + .../bin/cd.lua | 51 + .../bin/clear.lua | 8 + .../bin/components.lua | 52 + .../bin/cp.lua | 32 + .../bin/date.lua | 1 + .../bin/df.lua | 76 + .../bin/dmesg.lua | 38 + .../bin/du.lua | 128 + .../bin/echo.lua | 11 + .../bin/edit.lua | 682 +++ .../bin/find.lua | 132 + .../bin/flash.lua | 88 + .../bin/grep.lua | 315 ++ .../bin/head.lua | 137 + .../bin/hostname.lua | 22 + .../bin/install.lua | 51 + .../bin/label.lua | 49 + .../bin/less.lua | 304 ++ .../bin/list.lua | 33 + .../bin/ln.lua | 35 + .../bin/ls.lua | 13 + .../bin/lshw.lua | 42 + .../bin/lua.lua | 17 + .../bin/man.lua | 20 + .../bin/mkdir.lua | 27 + .../bin/mktmp.lua | 69 + .../bin/more.lua | 68 + .../bin/mount.lua | 66 + .../bin/mv.lua | 31 + .../bin/pastebin.lua | 153 + .../bin/primary.lua | 28 + .../bin/pwd.lua | 14 + .../bin/rc.lua | 142 + .../bin/reboot.lua | 4 + .../bin/redstone.lua | 103 + .../bin/resolution.lua | 32 + .../bin/rm.lua | 158 + .../bin/rmdir.lua | 104 + .../bin/scale.lua | 17 + .../bin/set.lua | 25 + .../bin/sh.lua | 92 + .../bin/shutdown.lua | 5 + .../bin/sleep.lua | 61 + .../bin/source.lua | 31 + .../bin/time.lua | 18 + .../bin/touch.lua | 54 + .../bin/umount.lua | 37 + .../bin/unalias.lua | 19 + .../bin/unset.lua | 9 + .../bin/uptime.lua | 13 + .../bin/useradd.lua | 14 + .../bin/userdel.lua | 13 + .../bin/wget.lua | 115 + .../bin/which.lua | 25 + .../bin/yes.lua | 32 + .../boot/00_base.lua | 45 + .../boot/01_process.lua | 64 + .../boot/02_os.lua | 80 + .../boot/03_io.lua | 99 + .../boot/04_component.lua | 204 + .../boot/10_devfs.lua | 23 + .../boot/90_filesystem.lua | 65 + .../boot/91_gpu.lua | 15 + .../boot/92_keyboard.lua | 40 + .../boot/93_term.lua | 59 + .../boot/94_shell.lua | 10 + .../boot/99_rc.lua | 6 + .../braillePixels.lua | 1 + .../brailleRenderer.lua | 51 + .../brickGame.lua | 38 + .../draconic.lua | 214 + 640cd89f-8e29-4b66-a0eb-7680c33760b4/draw.lua | 18 + .../etc/edit.cfg | 98 + .../etc/filesystem.cfg | 1 + 640cd89f-8e29-4b66-a0eb-7680c33760b4/etc/motd | 37 + .../etc/profile | 29 + .../etc/rc.cfg | 3 + .../etc/rc.d/example.lua | 14 + .../executionTime.lua | 20 + 640cd89f-8e29-4b66-a0eb-7680c33760b4/g.lua | 15 + .../gitRepoContent.lua | 15 + .../home/.shrc | 5 + 640cd89f-8e29-4b66-a0eb-7680c33760b4/init.lua | 200 + .../lib/.palette.cfg | 1 + .../lib/ECSAPI.lua | 2241 ++++++++++ .../lib/FormatModules/OCAF.lua | 211 + .../lib/FormatModules/OCIF.lua | 201 + .../lib/FormatModules/RAW.lua | 70 + .../lib/GUI.lua | 3888 +++++++++++++++++ .../lib/GUI2/Main.lua | 437 ++ .../lib/MeowEngine/Main.lua | 519 +++ .../lib/MineOSCore.lua | 376 ++ .../lib/MineOSInterface.lua | 1619 +++++++ .../lib/MineOSNetwork.lua | 400 ++ .../lib/MineOSPaths.lua | 31 + .../lib/OpenComputersGL/Main.lua | 386 ++ .../lib/OpenComputersGL/Materials.lua | 51 + .../lib/OpenComputersGL/Renderer.lua | 349 ++ .../lib/SHA2.lua | 243 ++ .../lib/advancedLua.lua | 581 +++ .../lib/archive.lua | 97 + .../lib/bigLetters.lua | 614 +++ .../lib/bit32.lua | 102 + .../lib/buffer.lua | 186 + .../lib/color.lua | 173 + .../lib/colors.lua | 30 + .../lib/compressor.lua | 215 + .../lib/context.lua | 137 + .../lib/devfs.lua | 345 ++ .../lib/doubleBuffering.lua | 700 +++ .../lib/event.lua | 227 + .../lib/filesystem.lua | 515 +++ .../lib/image.lua | 444 ++ .../lib/internet.lua | 130 + .../lib/io.lua | 111 + .../lib/json.lua | 1053 +++++ .../lib/keyboard.lua | 94 + .../lib/note.lua | 126 + .../lib/package.lua | 146 + .../lib/palette.lua | 3 + .../lib/pipes.lua | 375 ++ .../lib/process.lua | 161 + .../lib/rayEngine.lua | 566 +++ .../lib/rc.lua | 7 + .../lib/scale.lua | 87 + .../lib/serialization.lua | 20 + .../lib/sh.lua | 890 ++++ .../lib/shell.lua | 223 + .../lib/sides.lua | 62 + .../lib/syntax.lua | 164 + .../lib/term.lua | 732 ++++ .../lib/text.lua | 396 ++ .../lib/tools/boot.lua | 147 + .../lib/tools/buffered_read.lua | 162 + .../lib/tools/buffered_write.lua | 50 + .../lib/tools/delayLookup.lua | 33 + .../lib/tools/delayParse.lua | 67 + .../lib/tools/devfs/01_hw.lua | 145 + .../lib/tools/devfs/02_utils.lua | 50 + .../lib/tools/devfs/adapters/computer.lua | 9 + .../lib/tools/devfs/adapters/eeprom.lua | 22 + .../lib/tools/devfs/adapters/filesystem.lua | 25 + .../lib/tools/devfs/adapters/gpu.lua | 15 + .../lib/tools/devfs/adapters/internet.lua | 7 + .../lib/tools/devfs/adapters/modem.lua | 12 + .../lib/tools/devfs/adapters/screen.lua | 18 + .../lib/tools/device_labeling.lua | 101 + .../lib/tools/fsmod.lua | 68 + .../lib/tools/full_ls.lua | 391 ++ .../lib/tools/install_basics.lua | 209 + .../lib/tools/install_utils.lua | 90 + .../lib/tools/keyboard_full.lua | 141 + .../lib/tools/lua_shell.lua | 134 + .../lib/tools/programLocations.lua | 24 + .../lib/tools/ro_wrapper.lua | 30 + .../lib/tools/transfer.lua | 251 ++ .../lib/transforms.lua | 191 + .../lib/uuid.lua | 22 + .../lib/vector.lua | 71 + .../lib/web.lua | 156 + .../lib/xmlParser.lua | 59 + 640cd89f-8e29-4b66-a0eb-7680c33760b4/map.lua | 7 + .../market.lua | 292 ++ .../memTest.lua | 18 + .../miniPaletteGenerator.lua | 52 + .../multiScreen.lua | 313 ++ .../ramConsumeTest.lua | 25 + .../sudoku.lua | 82 + .../symbols.lua | 294 ++ 640cd89f-8e29-4b66-a0eb-7680c33760b4/t.lua | 34 + .../tanks.lua | 160 + 640cd89f-8e29-4b66-a0eb-7680c33760b4/test.txt | 63 + .../timeTest.lua | 16 + .../tunnelBIOS.lua | 52 + .../tunnelPCSoftware.lua | 72 + .../usr/man/address | 12 + .../usr/man/alias | 24 + .../usr/man/cat | 15 + .../usr/man/cd | 42 + .../usr/man/clear | 12 + .../usr/man/cp | 16 + .../usr/man/date | 12 + .../usr/man/df | 15 + .../usr/man/dmesg | 13 + .../usr/man/echo | 19 + .../usr/man/edit | 19 + .../usr/man/grep | 34 + .../usr/man/head | 31 + .../usr/man/hostname | 12 + .../usr/man/install | 109 + .../usr/man/label | 16 + .../usr/man/ln | 19 + .../usr/man/ls | 80 + .../usr/man/lua | 12 + .../usr/man/man | 15 + .../usr/man/mkdir | 15 + .../usr/man/more | 12 + .../usr/man/mount | 26 + .../usr/man/mv | 22 + .../usr/man/pastebin | 23 + .../usr/man/primary | 16 + .../usr/man/pwd | 12 + .../usr/man/rc | 35 + .../usr/man/reboot | 12 + .../usr/man/redstone | 22 + .../usr/man/resolution | 16 + .../usr/man/rm | 52 + .../usr/man/sh | 40 + .../usr/man/shutdown | 12 + .../usr/man/umount | 17 + .../usr/man/unalias | 12 + .../usr/man/uptime | 12 + .../usr/man/useradd | 16 + .../usr/man/userdel | 12 + .../usr/man/wget | 23 + .../usr/man/which | 15 + .../usr/man/yes | 24 + .../usr/misc/greetings.txt | 28 + Applications.cfg | 8 +- Applications/Braille/Main.lua | 4 +- Applications/HEX/Main.lua | 4 +- lib/GUI.lua | 31 +- lib/MineOSInterface.lua | 2 +- 542 files changed, 55855 insertions(+), 16 deletions(-) create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/.prop create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/1.pic create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/.icons create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/3DPrint.app/Main.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/3DPrint.app/Resources/About/Russian.txt create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/3DPrint.app/Resources/Icon.pic create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/3DTest.app/Main.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/3DTest.app/Resources/About/Russian.txt create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/3DTest.app/Resources/Icon.pic create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/AppMarket.app/Main.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/AppMarket.app/Resources/About/Russian.txt create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/AppMarket.app/Resources/Icon.pic create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/AppMarket.app/Resources/Localization/English.lang create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/AppMarket.app/Resources/Localization/Russian.lang create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/AppMarket.app/Resources/TempIcon.pic create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/AppMarket.app/Resources/Update.pic create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Battleship.app/Main.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Battleship.app/Resources/About/Russian.txt create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Battleship.app/Resources/Icon.pic create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/BufferDemo.app/Main.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/BufferDemo.app/Resources/About/Russian.txt create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/BufferDemo.app/Resources/Icon.pic create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/BufferDemo.app/Resources/Wallpaper.pic create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Calendar.app/Main.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Calendar.app/Resources/About/Russian.txt create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Calendar.app/Resources/Icon.pic create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Camera.app/Main.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Camera.app/Resources/About/Russian.txt create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Camera.app/Resources/Icon.pic create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/ChristmasTree.app/Main.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/ChristmasTree.app/Resources/About/Russian.txt create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/ChristmasTree.app/Resources/Icon.pic create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/CodeDoor.app/Main.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/CodeDoor.app/Resources/About/Russian.txt create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/CodeDoor.app/Resources/Icon.pic create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Control.app/Main.lua create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Control.app/Resources/Icon.pic create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Control.app/Resources/Localization/English.lang create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Control.app/Resources/Localization/Russian.lang create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Control.app/Resources/Modules/1.lua create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Control.app/Resources/Modules/2.lua create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Control.app/Resources/Modules/3.lua create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Control.app/Resources/Modules/4.lua create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Finder.app/Main.lua create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Finder.app/Resources/Favourites.cfg create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Finder.app/Resources/Icon.pic create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/FlappyBird.app/Main.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/FlappyBird.app/Resources/About/Russian.txt create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/FlappyBird.app/Resources/Flappy.pic create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/FlappyBird.app/Resources/Icon.pic create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/ForceAdmin.app/Main.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/ForceAdmin.app/Resources/About/Russian.txt create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/ForceAdmin.app/Resources/Icon.pic create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/FuckTheRain.app/Main.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/FuckTheRain.app/Resources/About/Russian.txt create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/FuckTheRain.app/Resources/Icon.pic create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/GeoScan2.app/Main.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/GeoScan2.app/Resources/Earth.pic create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/GeoScan2.app/Resources/Icon.pic create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/GuessWord.app/.icons create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/GuessWord.app/Main.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/GuessWord.app/Resources/About/Russian.txt create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/GuessWord.app/Resources/Icon.pic create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/HEX.app/Main.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/HEX.app/Resources/About/Russian.txt create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/HEX.app/Resources/Icon.pic create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/HoloClock.app/Main.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/HoloClock.app/Resources/Icon.pic create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/HoloEdit.app/Main.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/HoloEdit.app/Resources/Icon.pic create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/HoloEdit.app/Resources/Localization/English.lang create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/HoloEdit.app/Resources/Localization/Russian.lang create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/InfoPanel.app/Main.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/InfoPanel.app/Resources/About/Russian.txt create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/InfoPanel.app/Resources/Icon.pic create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/InfoPanel.app/Resources/Pages/Claims.txt create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/InfoPanel.app/Resources/Pages/Main.txt create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/InfoPanel.app/Resources/Pages/Rules.txt create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/InfoPanel.app/Resources/Pages/SSPI.txt create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/MineCode IDE.app/Main.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/MineCode IDE.app/Resources/About/Russian.txt create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/MineCode IDE.app/Resources/Icon.pic create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/MineCode IDE.app/Resources/Localization/English.lang create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/MineCode IDE.app/Resources/Localization/Russian.lang create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Palette.app/Main.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Palette.app/Resources/Icon.pic create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Photoshop.app/Main.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Photoshop.app/Resources/About/Russian.txt create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Photoshop.app/Resources/Icon.pic create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Photoshop.app/Resources/Localization/English.lang create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Photoshop.app/Resources/Localization/Russian.lang create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/PrintImage.app/Main.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/PrintImage.app/Resources/Icon.pic create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/QuantumCube.app/Main.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/QuantumCube.app/Resources/About/Russian.txt create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/QuantumCube.app/Resources/Icon.pic create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Radio.app/Main.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Radio.app/Resources/About/Russian.txt create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Radio.app/Resources/Icon.pic create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/RayWalk.app/Main.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/RayWalk.app/Resources/About/Russian.txt create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/RayWalk.app/Resources/Icon.pic create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/RayWalk.app/Resources/Localization/English.lang create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/RayWalk.app/Resources/Localization/Russian.lang create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/RayWalk.app/Resources/RayEngine.cfg create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/RayWalk.app/Resources/Weapons/CrosshairTextures/Angled.pic create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/RayWalk.app/Resources/Weapons/CrosshairTextures/Default.pic create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/RayWalk.app/Resources/Weapons/CrosshairTextures/Dotted.pic create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/RayWalk.app/Resources/Weapons/CrosshairTextures/Half.pic create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/RayWalk.app/Resources/Weapons/FireTextures/Plasma.pic create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/RayWalk.app/Resources/Weapons/FireTextures/PowderFire.pic create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/RayWalk.app/Resources/Weapons/WeaponTextures/Pistol.pic create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/RayWalk.app/Resources/Weapons/WeaponTextures/Plasmer.pic create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/RayWalk.app/Resources/Weapons/WeaponTextures/Rifle.pic create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/RayWalk.app/Resources/Weapons/WeaponTextures/Sniper.pic create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/RayWalk.app/Resources/Weapons/Weapons.cfg create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/RayWalk.app/Resources/Worlds/ExampleWorld/Blocks.cfg create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/RayWalk.app/Resources/Worlds/ExampleWorld/Map.cfg create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/RayWalk.app/Resources/Worlds/ExampleWorld/Player.cfg create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/RayWalk.app/Resources/Worlds/ExampleWorld/World.cfg create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/RayWalk.app/Resources/Worlds/SundownBeams/Blocks.cfg create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/RayWalk.app/Resources/Worlds/SundownBeams/Map.cfg create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/RayWalk.app/Resources/Worlds/SundownBeams/Player.cfg create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/RayWalk.app/Resources/Worlds/SundownBeams/World.cfg create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/RunningString.app/Main.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/RunningString.app/Resources/About/Russian.txt create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/RunningString.app/Resources/Icon.pic create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Shooting.app/Main.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Shooting.app/Resources/Icon.pic create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Stargate.app/Main.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Stargate.app/Resources/Ch1.pic create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Stargate.app/Resources/Ch2.pic create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Stargate.app/Resources/Icon.pic create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Stargate.app/Resources/OffOff.pic create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Stargate.app/Resources/OffOn.pic create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Stargate.app/Resources/OnOff.pic create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Stargate.app/Resources/OnOn.pic create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/TurretControl.app/Main.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/TurretControl.app/Resources/About/Russian.txt create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/TurretControl.app/Resources/Icon.pic create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/TurretControl.app/Resources/Turret.pic create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/VK.app/Main.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/VK.app/Resources/About/Russian.txt create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/VK.app/Resources/Icon.pic create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/VK.app/Resources/VKLogo.pic create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Weather.app/Main.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Weather.app/Resources/About/Russian.txt create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Weather.app/Resources/Cloudy.pic create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Weather.app/Resources/Foggy.pic create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Weather.app/Resources/Icon.pic create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Weather.app/Resources/Rainy.pic create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Weather.app/Resources/Snowy.pic create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Weather.app/Resources/Stormy.pic create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Weather.app/Resources/Sunny.pic create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/.icons create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/3DPrint.lnk create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/3DTest.lnk create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/Battleship.lnk create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/Braille.app/Main.lua create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/Braille.app/Resources/Icon.pic create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/BufferDemo.lnk create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/Calendar.lnk create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/Camera.app/Main.lua create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/Camera.app/Resources/Icon.pic create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/Camera.lnk create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/ChristmasTree.lnk create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/CodeDoor.lnk create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/EBAMARKET2.app/Main.lua create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/EBAMARKET2.app/Resources/Icon.pic create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/FlappyBird.lnk create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/ForceAdmin.lnk create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/FuckTheRain.lnk create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/GeoScan2.lnk create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/Graph2.app/Main.lua create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/Graph2.app/Resources/Icon.pic create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/GuessWord.lnk create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/HEX.lnk create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/HoloClock.lnk create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/HoloEdit.lnk create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/InfoPanel.lnk create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/PS4.app/.icons create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/PS4.app/Main.lua create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/PS4.app/Resources/Icon.pic create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/PS4.app/Resources/Tools/01_Move.lua create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/PS4.app/Resources/Tools/02_Brush.lua create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/PS4.app/Resources/Tools/03_Eraser.lua create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/Palette.lnk create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/PrintImage.lnk create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/QuantumCube.lnk create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/Radio.lnk create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/RayWalk.lnk create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/RunningString.lnk create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/Shooting.lnk create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/Spinner.app/Main.lua create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/Spinner.app/Resources/1.pic create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/Spinner.app/Resources/2.pic create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/Spinner.app/Resources/3.pic create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/Spinner.app/Resources/4.pic create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/Spinner.app/Resources/5.pic create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/Spinner.app/Resources/6.pic create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/Spinner.app/Resources/7.pic create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/Spinner.app/Resources/8.pic create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/Spinner.app/Resources/Icon.pic create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/Stargate.lnk create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/Translate.app/Main.lua create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/Translate.app/Resources/Config.cfg create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/Translate.app/Resources/Icon.pic create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/Translate.app/Resources/Logo.pic create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/TurretControl.lnk create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/VK.lnk create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/Weather.lnk create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Pictures/AhsokaTano.pic create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Pictures/Block.pic create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Pictures/Catniss.pic create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Pictures/Ciri.pic create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Pictures/Girl.pic create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Pictures/HilbertCurve.pic create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Pictures/MoonTouch.pic create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Pictures/Mystery.pic create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Pictures/Nettle.pic create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Pictures/Raspberry.pic create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Pictures/Space.pic create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Pictures/Tatu.pic create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Pictures/TyanSunset.pic create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Application data/AppMarket/Cache/168.pic create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Application data/AppMarket/Cache/170.pic create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Application data/AppMarket/Cache/174.pic create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Application data/AppMarket/Cache/176.pic create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Application data/AppMarket/Cache/239.pic create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Application data/AppMarket/Cache/244.pic create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Application data/AppMarket/Cache/247.pic create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Application data/AppMarket/Cache/249.pic create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Application data/AppMarket/Cache/251.pic create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Application data/AppMarket/Config.cfg create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Application data/FlappyBird/Scores.cfg create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Application data/HoloClock/Settings.cfg create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Application data/MineCode IDE/Config.cfg create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Application data/Palette/Favourites.cfg create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Application data/Stargate/Contacts.cfg create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Application data/Weather/Forecast.cfg create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Applications.cfg create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/AutorunManager/Filelist.txt create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Extensions/Arc/Launcher.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Extensions/Lua/ContextMenu.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Extensions/Lua/Launcher.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Extensions/Pic/ContextMenu.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Icons/3DModel.pic create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Icons/Application.pic create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Icons/Archive.pic create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Icons/Computer.pic create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Icons/Config.pic create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Icons/FileNotExists.pic create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Icons/Floppy.pic create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Icons/Folder.pic create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Icons/HDD.pic create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Icons/Image.pic create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Icons/Lua.pic create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Icons/Pastebin.pic create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Icons/Robot.pic create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Icons/SampleIcon.pic create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Icons/Script.pic create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Icons/Steve.pic create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Icons/Tablet.pic create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Icons/Text.pic create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Icons/Trash.pic create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Installer.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Localization/English.lang create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Localization/Russian.lang create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Properties.cfg create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Radio/Stations.cfg create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Screensavers/Clock.lua create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Screensavers/Lines.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Screensavers/Mandala.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Screensavers/Matrix.lua create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Screensavers/NyanCat.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Screensavers/XCOM.lua create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Trash/Config.cfg create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Trash/HEX-copy.app/Main.lua create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Trash/HEX-copy.app/Resources/Icon.pic create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Trash/HEX.app/Main.lua create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Trash/HEX.app/Resources/Icon.pic create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Trash/Icon.pic create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Trash/LehaTanks.app/Main.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Trash/LehaTanks.app/Resources/Icon.pic create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Trash/LehaTanks.app/Resources/Tanks/Bullet.pic create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Trash/LehaTanks.app/Resources/Tanks/Enemy1Down.pic create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Trash/LehaTanks.app/Resources/Tanks/Enemy1Left.pic create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Trash/LehaTanks.app/Resources/Tanks/Enemy1Right.pic create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Trash/LehaTanks.app/Resources/Tanks/Enemy1Up.pic create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Trash/LehaTanks.app/Resources/Tanks/Play1DOWN.pic create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Trash/LehaTanks.app/Resources/Tanks/Play1LEFT.pic create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Trash/LehaTanks.app/Resources/Tanks/Play1RIGHT.pic create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Trash/LehaTanks.app/Resources/Tanks/Play1UP.pic create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Trash/LehaTanks.app/Resources/Textures/EnemyTank/EnemyTankDOWN.pic create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Trash/LehaTanks.app/Resources/Textures/EnemyTank/EnemyTankLEFT.pic create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Trash/LehaTanks.app/Resources/Textures/EnemyTank/EnemyTankRIGHT.pic create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Trash/LehaTanks.app/Resources/Textures/EnemyTank/EnemyTankUP.pic create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Trash/LehaTanks.app/Resources/Textures/Map/Base.pic create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Trash/LehaTanks.app/Resources/Textures/Map/Brick.pic create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Trash/LehaTanks.app/Resources/Textures/Player1Tank/Player1NewTankDOWN.pic create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Trash/LehaTanks.app/Resources/Textures/Player1Tank/Player1NewTankLEFT.pic create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Trash/LehaTanks.app/Resources/Textures/Player1Tank/Player1NewTankRIGHT.pic create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Trash/LehaTanks.app/Resources/Textures/Player1Tank/Player1NewTankUP.pic create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Trash/Sudoku.app/.icons create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Trash/Sudoku.app/Main.lua create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Trash/Sudoku.app/Resources/.icons create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Trash/Sudoku.app/Resources/Icon.pic create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Trash/newApplicationInfoWidget create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/MultiScreen.cfg create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/OS.lua create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/OSEnergyTurrets.lua create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/Tanks/1.pic create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/Tanks/2.pic create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/Tanks/3.pic create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/Tanks/4.pic create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/autorunGenerator.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/address.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/alias.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/cat.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/cd.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/clear.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/components.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/cp.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/date.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/df.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/dmesg.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/du.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/echo.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/edit.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/find.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/flash.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/grep.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/head.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/hostname.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/install.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/label.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/less.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/list.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/ln.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/ls.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/lshw.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/lua.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/man.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/mkdir.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/mktmp.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/more.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/mount.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/mv.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/pastebin.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/primary.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/pwd.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/rc.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/reboot.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/redstone.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/resolution.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/rm.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/rmdir.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/scale.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/set.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/sh.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/shutdown.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/sleep.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/source.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/time.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/touch.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/umount.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/unalias.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/unset.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/uptime.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/useradd.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/userdel.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/wget.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/which.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/yes.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/boot/00_base.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/boot/01_process.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/boot/02_os.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/boot/03_io.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/boot/04_component.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/boot/10_devfs.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/boot/90_filesystem.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/boot/91_gpu.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/boot/92_keyboard.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/boot/93_term.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/boot/94_shell.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/boot/99_rc.lua create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/braillePixels.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/brailleRenderer.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/brickGame.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/draconic.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/draw.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/etc/edit.cfg create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/etc/filesystem.cfg create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/etc/motd create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/etc/profile create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/etc/rc.cfg create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/etc/rc.d/example.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/executionTime.lua create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/g.lua create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/gitRepoContent.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/home/.shrc create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/init.lua create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/.palette.cfg create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/ECSAPI.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/FormatModules/OCAF.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/FormatModules/OCIF.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/FormatModules/RAW.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/GUI.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/GUI2/Main.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/MeowEngine/Main.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/MineOSCore.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/MineOSInterface.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/MineOSNetwork.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/MineOSPaths.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/OpenComputersGL/Main.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/OpenComputersGL/Materials.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/OpenComputersGL/Renderer.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/SHA2.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/advancedLua.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/archive.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/bigLetters.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/bit32.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/buffer.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/color.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/colors.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/compressor.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/context.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/devfs.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/doubleBuffering.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/event.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/filesystem.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/image.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/internet.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/io.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/json.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/keyboard.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/note.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/package.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/palette.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/pipes.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/process.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/rayEngine.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/rc.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/scale.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/serialization.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/sh.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/shell.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/sides.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/syntax.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/term.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/text.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/tools/boot.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/tools/buffered_read.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/tools/buffered_write.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/tools/delayLookup.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/tools/delayParse.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/tools/devfs/01_hw.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/tools/devfs/02_utils.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/tools/devfs/adapters/computer.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/tools/devfs/adapters/eeprom.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/tools/devfs/adapters/filesystem.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/tools/devfs/adapters/gpu.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/tools/devfs/adapters/internet.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/tools/devfs/adapters/modem.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/tools/devfs/adapters/screen.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/tools/device_labeling.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/tools/fsmod.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/tools/full_ls.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/tools/install_basics.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/tools/install_utils.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/tools/keyboard_full.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/tools/lua_shell.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/tools/programLocations.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/tools/ro_wrapper.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/tools/transfer.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/transforms.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/uuid.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/vector.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/web.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/xmlParser.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/map.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/market.lua create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/memTest.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/miniPaletteGenerator.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/multiScreen.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/ramConsumeTest.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/sudoku.lua create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/symbols.lua create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/t.lua create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/tanks.lua create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/test.txt create mode 100644 640cd89f-8e29-4b66-a0eb-7680c33760b4/timeTest.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/tunnelBIOS.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/tunnelPCSoftware.lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/address create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/alias create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/cat create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/cd create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/clear create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/cp create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/date create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/df create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/dmesg create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/echo create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/edit create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/grep create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/head create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/hostname create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/install create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/label create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/ln create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/ls create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/lua create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/man create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/mkdir create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/more create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/mount create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/mv create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/pastebin create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/primary create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/pwd create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/rc create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/reboot create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/redstone create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/resolution create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/rm create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/sh create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/shutdown create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/umount create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/unalias create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/uptime create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/useradd create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/userdel create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/wget create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/which create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/yes create mode 100755 640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/misc/greetings.txt diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/.prop b/640cd89f-8e29-4b66-a0eb-7680c33760b4/.prop new file mode 100755 index 00000000..5cf0ed07 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/.prop @@ -0,0 +1 @@ +{label = "OpenOS", reboot=true, setlabel=true, setboot=true} diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/1.pic b/640cd89f-8e29-4b66-a0eb-7680c33760b4/1.pic new file mode 100644 index 0000000000000000000000000000000000000000..303d57d199887d908f594b119429b3aaec7fc115 GIT binary patch literal 144 zcmXwv%MHRn3 24 then maxShapeCount = 24 end +local currentMode = 1 +local modes = { + "неактивная", + "активная" +} +local currentTexture = "planks_oak" +local currentTint = ecs.colors.orange +local useTint = false +local showLayerOnHologram = true + +local pixelWidth = 6 +local pixelHeight = 3 +local drawingZoneWidth = pixelWidth * 16 +local drawingZoneHeight = pixelHeight * 16 +local xDrawingZone = math.floor(widthOfDrawingCYKA / 2 - drawingZoneWidth / 2) +local yDrawingZone = 3 + +local shapeColors = {} +local HUE = 0 +local HUEAdder = math.floor(360 / maxShapeCount) +for i = 1, maxShapeCount do + shapeColors[i] = color.HSBToInteger(HUE, 1, 1) + HUE = HUE + HUEAdder +end +HUE, HUEAdder = nil, nil + +local model = {} + +------------------------------------------------------------------------------------------------------------------------ + +local function swap(a, b) + return b, a +end + +local function correctShapeCoords(shapeNumber) + if model.shapes[shapeNumber] then + if model.shapes[shapeNumber][1] >= model.shapes[currentShape][4] then + model.shapes[shapeNumber][1], model.shapes[currentShape][4] = swap(model.shapes[currentShape][1], model.shapes[currentShape][4]) + model.shapes[shapeNumber][1] = model.shapes[shapeNumber][1] - 1 + model.shapes[shapeNumber][4] = model.shapes[shapeNumber][4] + 1 + -- ecs.error("СУКА") + end + if model.shapes[shapeNumber][2] >= model.shapes[currentShape][5] then + model.shapes[shapeNumber][2], model.shapes[currentShape][5] = swap(model.shapes[currentShape][2], model.shapes[currentShape][5]) + model.shapes[shapeNumber][2] = model.shapes[shapeNumber][2] - 1 + model.shapes[shapeNumber][5] = model.shapes[shapeNumber][5] + 1 + -- ecs.error("СУКА2") + end + if model.shapes[shapeNumber][3] >= model.shapes[currentShape][6] then + model.shapes[shapeNumber][3], model.shapes[currentShape][6] = swap(model.shapes[currentShape][3], model.shapes[currentShape][6]) + model.shapes[shapeNumber][3] = model.shapes[shapeNumber][3] - 1 + model.shapes[shapeNumber][6] = model.shapes[shapeNumber][6] + 1 + -- ecs.error("СУКА3") + end + end +end + +local function loadShapeParameters() + if model.shapes[currentShape] then + currentTexture = model.shapes[currentShape].texture + if model.shapes[currentShape].tint then + currentTint = model.shapes[currentShape].tint + useTint = true + else + useTint = false + end + end +end + +local function fixModelArray() + model.label = model.label or "Sample label" + model.tooltip = model.tooltip or "Sample tooltip" + model.lightLevel = model.lightLevel or 0 + model.emitRedstone = model.emitRedstone or false + model.buttonMode = model.buttonMode or false + model.collidable = model.collidable or {true, true} + model.shapes = model.shapes or {} + + currentLayer = 1 + currentShape = 1 + currentMode = 1 + loadShapeParameters() +end + +--Объекты для тача +local obj = {} +local function newObj(class, name, ...) + obj[class] = obj[class] or {} + obj[class][name] = {...} +end + +local function drawShapeNumbers(x, y) + local counter = 1 + local xStart = x + + for j = 1, 4 do + for i = 1, 6 do + if currentShape == counter then + newObj("ShapeNumbers", counter, buffer.button(x, y, 4, 1, shapeColors[counter], 0xFFFFFF - shapeColors[counter], tostring(counter))) + -- newObj("ShapeNumbers", counter, buffer.button(x, y, 4, 1, colors.shapeNumbersActiveBackground, colors.shapeNumbersActiveText, tostring(counter))) + else + newObj("ShapeNumbers", counter, buffer.button(x, y, 4, 1, colors.shapeNumbersBackground, colors.shapeNumbersText, tostring(counter))) + end + + x = x + 5 + counter = counter + 1 + if counter > maxShapeCount then return end + end + x = xStart + y = y + 2 + end +end + +local function toolBarInfoLine(y, text) + buffer.square(xToolbar, y, widthOfToolbar, 1, colors.toolbarInfoBackground, 0xFFFFFF, " ") + buffer.text(xToolbar + 1, y, colors.toolbarInfoText, text) +end + +local function centerText(y, color, text) + local x = math.floor(xToolbar + widthOfToolbar / 2 - unicode.len(text) / 2) + buffer.text(x, y, color, text) +end + +local function addButton(y, back, fore, text) + newObj("ToolbarButtons", text, buffer.button(xToolbar + 2, y, widthOfToolbar - 4, 3, back, fore, text)) +end + +local function printKeyValue(x, y, keyColor, valueColor, key, value, limit) + local totalLength = unicode.len(key .. ": " .. value) + if totalLength > limit then + value = unicode.sub(value, 1, limit - unicode.len(key .. ": ") - 1) .. "…" + end + buffer.text(x, y, keyColor, key .. ":") + buffer.text(x + unicode.len(key) + 2, y, valueColor, value) +end + +local function getShapeCoords() + local coords = "элемент не создан" + if model.shapes[currentShape] then + coords = "(" .. model.shapes[currentShape][1] .. "," .. model.shapes[currentShape][2] .. "," .. model.shapes[currentShape][3] .. ");(" .. model.shapes[currentShape][4] .. "," .. model.shapes[currentShape][5] .. "," .. model.shapes[currentShape][6] .. ")" + end + return coords +end + +local function fixNumber(number) + if number < 10 then number = "0" .. number end + return tostring(number) +end + +local function drawToolbar() + buffer.square(xToolbar, 1, widthOfToolbar, ySize, colors.toolbarBackground, 0xFFFFFF, " ") + + local x = xToolbar + 8 + local y = 3 + + --Текущий слой + bigLetters.drawText(x, y, colors.toolbarBigLetters, fixNumber(currentLayer)) + y = y + 6 + centerText(y, colors.toolbarText, "Текущий слой") + + --Управление элементом + y = y + 2 + x = xToolbar + 2 + toolBarInfoLine(y, "Управление моделью"); y = y + 2 + printKeyValue(x, y, colors.toolbarKeyText, colors.toolbarValueText, "Имя", model.label, widthOfToolbar - 4); y = y + 1 + printKeyValue(x, y, colors.toolbarKeyText, colors.toolbarValueText, "Описание", model.tooltip, widthOfToolbar - 4); y = y + 1 + printKeyValue(x, y, colors.toolbarKeyText, colors.toolbarValueText, "Как кнопка", tostring(model.buttonMode), widthOfToolbar - 4); y = y + 1 + printKeyValue(x, y, colors.toolbarKeyText, colors.toolbarValueText, "Редстоун-сигнал", tostring(model.emitRedstone), widthOfToolbar - 4); y = y + 1 + printKeyValue(x, y, colors.toolbarKeyText, colors.toolbarValueText, "Коллизия", tostring(model.collidable[currentMode]), widthOfToolbar - 4); y = y + 1 + printKeyValue(x, y, colors.toolbarKeyText, colors.toolbarValueText, "Уровень света", tostring(model.lightLevel), widthOfToolbar - 4); y = y + 1 + y = y + 1 + printKeyValue(x, y, ecs.colors.blue, colors.toolbarValueText, "Состояние", modes[currentMode], widthOfToolbar - 4); y = y + 1 + y = y + 1 + addButton(y, colors.toolbarButtonBackground, colors.toolbarButtonText, "Изменить параметры"); y = y + 4 + addButton(y, colors.toolbarButtonBackground, colors.toolbarButtonText, "Напечатать"); y = y + 4 + toolBarInfoLine(y, "Управление элементом " .. currentShape); y = y + 2 + printKeyValue(x, y, colors.toolbarKeyText, colors.toolbarValueText, "Текстура", tostring(currentTexture), widthOfToolbar - 4); y = y + 1 + printKeyValue(x, y, colors.toolbarKeyText, colors.toolbarValueText, "Оттенок", ecs.HEXtoString(currentTint, 6, true), widthOfToolbar - 4); y = y + 1 + printKeyValue(x, y, colors.toolbarKeyText, colors.toolbarValueText, "Использовать оттенок", tostring(useTint), widthOfToolbar - 4); y = y + 1 + printKeyValue(x, y, colors.toolbarKeyText, colors.toolbarValueText, "Позиция", getShapeCoords(), widthOfToolbar - 4); y = y + 2 + addButton(y, colors.toolbarButtonBackground, colors.toolbarButtonText, "Изменить параметры "); y = y + 4 + + --Элементы + toolBarInfoLine(y, "Выбор элемента"); y = y + 2 + drawShapeNumbers(x, y) + y = y + 8 +end + +local function drawTopMenu(selected) + obj["TopMenu"] = ecs.drawTopMenu(1, 1, xSize - widthOfToolbar, colors.toolbarBackground, selected, {"Файл", 0x262626}, {"Проектор", 0x262626}, {"О программе", 0x262626}) +end + +local function renderCurrentLayerOnHologram(xStart, yStart, zStart) + if showLayerOnHologram then + for i = yStart, yStart + 16 do + component.hologram.set(xStart - 1, i, zStart + (16 - currentLayer), 3) + component.hologram.set(xStart + 16, i, zStart + (16 - currentLayer), 3) + end + + for i = (xStart-1), (xStart + 16) do + component.hologram.set(i, yStart - 1, zStart + (16 - currentLayer), 3) + component.hologram.set(i, yStart + 16, zStart + (16 - currentLayer), 3) + end + end +end + +local function drawModelOnHologram() + if hologramAvailable then + local xStart, yStart, zStart = 16,4,16 + component.hologram.clear() + + for shape in pairs(model.shapes) do + if (currentMode == 2 and model.shapes[shape].state) or (currentMode == 1 and not model.shapes[shape].state) then + if model.shapes[shape] then + for x = model.shapes[shape][1], model.shapes[shape][4] - 1 do + for y = model.shapes[shape][2], model.shapes[shape][5] - 1 do + for z = model.shapes[shape][3], model.shapes[shape][6] - 1 do + --Эта хуйня для того, чтобы в разных режимах не ебало мозг + if (model.shapes[shape].state and currentMode == 2) or (not model.shapes[shape].state and currentMode == 1) then + if shape == currentShape then + component.hologram.set(xStart + x, yStart + y, zStart + 15 - z, 2) + else + component.hologram.set(xStart + x, yStart + y, zStart + 15 - z, 1) + end + end + end + end + end + end + end + end + + renderCurrentLayerOnHologram(xStart, yStart, zStart) + end +end + +local function printModel(count) + printer.reset() + printer.setLabel(model.label) + printer.setTooltip(model.tooltip) + printer.setCollidable(model.collidable[1], model.collidable[2]) + printer.setLightLevel(model.lightLevel) + printer.setRedstoneEmitter(model.emitRedstone) + printer.setButtonMode(model.buttonMode) + + for i in pairs(model.shapes) do + printer.addShape( + model.shapes[i][1], + (model.shapes[i][2]), + (model.shapes[i][3]), + + model.shapes[i][4], + (model.shapes[i][5]), + (model.shapes[i][6]), + + model.shapes[i].texture, + model.shapes[i].state, + model.shapes[i].tint + ) + end + + local success, reason = printer.commit(count) + if not success then + ecs.error("Ошибка печати: " .. reason) + end +end + +local function drawPixel(x, y, width, height, color, trasparency) + buffer.square(xDrawingZone + x * pixelWidth - pixelWidth, yDrawingZone + y * pixelHeight - pixelHeight, width * pixelWidth, height * pixelHeight, color, 0xFFFFFF, " ", trasparency) +end + +local function setka() + drawPixel(1, 1, 16, 16, colors.drawingZoneBackground) + local shade = colors.drawingZoneBackground - 0x111111 + + for j = 1, 16 do + for i = 1, 16 do + if j % 2 == 0 then + if i % 2 == 0 then + drawPixel(i, j, 1, 1, shade) + end + else + if i % 2 ~= 0 then + drawPixel(i, j, 1, 1, shade) + end + end + end + end +end + +local function drawDrawingZone() + + setka() + + local selectionStartPoint = {} + local selectionEndPoint = {} + local trasparency = 70 + + for shape in pairs(model.shapes) do + + --Если по состояниям все заебок + if ((model.shapes[shape].state and currentMode == 2) or (not model.shapes[shape].state and currentMode == 1)) then + + selectionStartPoint.x = model.shapes[shape][1] + 1 + selectionStartPoint.y = model.shapes[shape][2] + 1 + selectionStartPoint.z = model.shapes[shape][3] + 1 + selectionEndPoint.x = model.shapes[shape][4] + selectionEndPoint.y = model.shapes[shape][5] + selectionEndPoint.z = model.shapes[shape][6] + local yDifference = selectionEndPoint.y - selectionStartPoint.y + 1 + + if currentLayer >= selectionStartPoint.z and currentLayer <= selectionEndPoint.z then + if shape ~= currentShape then + local h, s, b = color.IntegerToHSB(shapeColors[shape]) + s = 0.3 + -- ecs.error("РИСУЮ") + drawPixel(selectionStartPoint.x, 18 - selectionStartPoint.y - yDifference, selectionEndPoint.x - selectionStartPoint.x + 1, yDifference, color.HSBToInteger(h, s, b)) + -- drawPixel(selectionStartPoint.x, selectionStartPoint.z, selectionEndPoint.x - selectionStartPoint.x + 1, selectionEndPoint.z - selectionStartPoint.z + 1, shapeColors[shape], trasparency) + else + drawPixel(selectionStartPoint.x, 18 - selectionStartPoint.y - yDifference, selectionEndPoint.x - selectionStartPoint.x + 1, yDifference, shapeColors[shape]) + + --Точки + if selectionStartPoint.z == currentLayer then + drawPixel(selectionStartPoint.x, 17 - selectionStartPoint.y, 1, 1, colors.drawingZoneStartPoint) + end + + if selectionEndPoint.z == currentLayer then + drawPixel(selectionEndPoint.x, 17 - selectionEndPoint.y, 1, 1, colors.drawingZoneEndPoint) + end + end + end + end + end +end + +local function drawAll() + buffer.square(1, 2, xSize, ySize, colors.drawingZoneCYKA, 0xFFFFFF, " ") + drawDrawingZone() + drawToolbar() + buffer.draw() + drawTopMenu(0) +end + +local function save(path) + fs.makeDirectory(fs.path(path) or "") + local file = io.open(path, "w") + file:write(serialization.serialize(model)) + file:close() +end + +local function open(path) + if fs.exists(path) then + if ecs.getFileFormat(path) == ".3dm" then + local file = io.open(path, "r") + model = serialization.unserialize(file:read("*a")) + fixModelArray() + file:close() + drawAll() + drawModelOnHologram() + else + ecs.error("Файл имеет неизвестный формат. Поддерживаются только модели в формате .3dm.") + end + else + ecs.error("Файл \"" .. path .. "\" не существует") + end +end + +------------------------------------------------------------------------------------------------------------------------ + +model = {} +fixModelArray() + +local args = {...} +if args[1] and fs.exists(args[1]) then + open(args[1]) +end + +drawAll() +drawModelOnHologram() + +------------------------------------------------------------------------------------------------------------------------ + +local startPointSelected = false +local xShapeStart, yShapeStart, zShapeStart, xShapeEnd, yShapeEnd, zShapeEnd + +while true do + local e = { event.pull() } + if e[1] == "touch" then + --Если кликнули в зону рисования + if ecs.clickedAtArea(e[3], e[4], xDrawingZone, yDrawingZone, xDrawingZone + drawingZoneWidth - 1, yDrawingZone + drawingZoneHeight - 1) then + if not startPointSelected then + xShapeStart = math.ceil((e[3] - xDrawingZone + 1) / pixelWidth) + yShapeStart = math.ceil((e[4] - yDrawingZone + 1) / pixelHeight) + zShapeStart = currentLayer + + startPointSelected = true + model.shapes[currentShape] = nil + -- buffer.square(xDrawingZone, yDrawingZone, drawingZoneWidth, drawingZoneHeight, colors.drawingZoneBackground, 0xFFFFFF, " ") + + drawPixel(xShapeStart, yShapeStart, 1, 1, colors.drawingZoneStartPoint) + + buffer.draw() + else + xShapeEnd = math.ceil((e[3] - xDrawingZone + 1) / pixelWidth) + yShapeEnd = math.ceil((e[4] - yDrawingZone + 1) / pixelHeight) + zShapeEnd = currentLayer + + drawPixel(xShapeEnd, yShapeEnd, 1, 1, colors.drawingZoneEndPoint) + startPointSelected = false + + model.shapes[currentShape] = { + xShapeStart - 1, + 17 - yShapeStart - 1, + zShapeStart - 1, + + xShapeEnd, + 17 - yShapeEnd, + zShapeEnd, + + texture = currentTexture, + } + + model.shapes[currentShape].state = nil + model.shapes[currentShape].tint = nil + if currentMode == 2 then model.shapes[currentShape].state = true end + if useTint then model.shapes[currentShape].tint = currentTint end + + correctShapeCoords(currentShape) + + drawAll() + drawModelOnHologram() + end + else + for key in pairs(obj.ShapeNumbers) do + if ecs.clickedAtArea(e[3], e[4], obj.ShapeNumbers[key][1], obj.ShapeNumbers[key][2], obj.ShapeNumbers[key][3], obj.ShapeNumbers[key][4]) then + currentShape = key + loadShapeParameters() + drawAll() + drawModelOnHologram() + break + end + end + + for key in pairs(obj.ToolbarButtons) do + if ecs.clickedAtArea(e[3], e[4], obj.ToolbarButtons[key][1], obj.ToolbarButtons[key][2], obj.ToolbarButtons[key][3], obj.ToolbarButtons[key][4]) then + buffer.button(obj.ToolbarButtons[key][1], obj.ToolbarButtons[key][2], widthOfToolbar - 4, 3, ecs.colors.blue, 0xFFFFFF, key) + buffer.draw() + os.sleep(0.2) + + if key == "Напечатать" then + local data = ecs.universalWindow("auto", "auto", 36, 0x262626, true, + {"EmptyLine"}, + {"CenterText", ecs.colors.orange, "Напечатать"}, + {"EmptyLine"}, + {"Slider", 0xFFFFFF, ecs.colors.orange, 1, 64, 1, "", " штук"}, + {"EmptyLine"}, + {"Button", {ecs.colors.orange, 0xffffff, "OK"}, {0x999999, 0xffffff, "Отмена"}} + ) + + if data[2] == "OK" then + printModel(data[1]) + end + + elseif key == "Изменить параметры" then + local data = ecs.universalWindow("auto", "auto", 36, 0x262626, true, + {"EmptyLine"}, + {"CenterText", ecs.colors.orange, "Параметры модели"}, + {"EmptyLine"}, + {"Input", 0xFFFFFF, ecs.colors.orange, model.label}, + {"Input", 0xFFFFFF, ecs.colors.orange, model.tooltip}, + {"Selector", 0xFFFFFF, ecs.colors.orange, "Неактивная", "Активная"}, + {"EmptyLine"}, + {"Switch", ecs.colors.orange, 0xffffff, 0xFFFFFF, "Как кнопка", model.buttonMode}, + {"EmptyLine"}, + {"Switch", ecs.colors.orange, 0xffffff, 0xFFFFFF, "Редстоун-сигнал", model.emitRedstone}, + {"EmptyLine"}, + {"Switch", ecs.colors.orange, 0xffffff, 0xFFFFFF, "Коллизия", model.collidable[currentMode]}, + {"EmptyLine"}, + {"Slider", 0xFFFFFF, ecs.colors.orange, 0, 15, model.lightLevel, "Уровень света: ", ""}, + {"EmptyLine"}, + {"Button", {ecs.colors.orange, 0xffffff, "OK"}, {0x999999, 0xffffff, "Отмена"}} + ) + + if data[8] == "OK" then + model.label = data[1] or "Sample label" + model.tooltip = data[2] or "Sample tooltip" + if data[3] == "Активная" then + currentMode = 2 + else + currentMode = 1 + end + model.buttonMode = data[4] + model.emitRedstone = data[5] + model.collidable[currentMode] = data[6] + model.lightLevel = data[7] + end + + elseif key == "Изменить параметры " then + local data = ecs.universalWindow("auto", "auto", 36, 0x262626, true, + {"EmptyLine"}, + {"CenterText", ecs.colors.orange, "Параметры элемента"}, + {"EmptyLine"}, + {"Input", 0xFFFFFF, ecs.colors.orange, currentTexture}, + {"Color", "Оттенок", currentTint}, + {"EmptyLine"}, + {"Switch", ecs.colors.orange, 0xffffff, 0xFFFFFF, "Использовать оттенок", useTint}, + {"EmptyLine"}, + {"Button", {ecs.colors.orange, 0xffffff, "OK"}, {0x999999, 0xffffff, "Отмена"}} + ) + + if data[4] == "OK" then + currentTexture = data[1] + currentTint = data[2] + useTint = data[3] + + if model.shapes[currentShape] then + model.shapes[currentShape].texture = currentTexture + if useTint then + model.shapes[currentShape].tint = currentTint + else + model.shapes[currentShape].tint = nil + end + end + end + end + + drawAll() + drawModelOnHologram() + break + end + end + + for key in pairs(obj.TopMenu) do + if ecs.clickedAtArea(e[3], e[4], obj.TopMenu[key][1], obj.TopMenu[key][2], obj.TopMenu[key][3], obj.TopMenu[key][4]) then + drawTopMenu(obj.TopMenu[key][5]) + -- buffer.button(obj.TopMenu[key][1] - 1, obj.TopMenu[key][2], unicode.len(key) + 2, 1, ecs.colors.blue, 0xFFFFFF, key) + -- buffer.draw() + + local action + if key == "Файл" then + action = context.menu(obj.TopMenu[key][1] - 1, obj.TopMenu[key][2] + 1, {"Новый"}, "-", {"Открыть"}, {"Сохранить"}, "-", {"Выход"}) + elseif key == "Проектор" then + action = context.menu(obj.TopMenu[key][1] - 1, obj.TopMenu[key][2] + 1, {"Масштаб", not hologramAvailable}, {"Отступ проекции", not hologramAvailable}, {"Изменить палитру", not hologramAvailable}, "-", {"Включить показ слоя", not hologramAvailable}, {"Отключить показ слоя", not hologramAvailable}, "-", {"Включить вращение", not hologramAvailable}, {"Отключить вращение", not hologramAvailable}) + elseif key == "О программе" then + ecs.universalWindow("auto", "auto", 36, 0x262626, true, + {"EmptyLine"}, + {"CenterText", ecs.colors.orange, "3DPrint v3.0"}, + {"EmptyLine"}, + {"CenterText", 0xFFFFFF, "Автор:"}, + {"CenterText", 0xBBBBBB, "Тимофеев Игорь"}, + {"CenterText", 0xBBBBBB, "vk.com/id7799889"}, + {"EmptyLine"}, + {"CenterText", 0xFFFFFF, "Тестеры:"}, + {"CenterText", 0xBBBBBB, "Семёнов Сeмён"}, + {"CenterText", 0xBBBBBB, "vk.com/day_z_utes"}, + {"CenterText", 0xBBBBBB, "Бесфамильный Яков"}, + {"CenterText", 0xBBBBBB, "vk.com/mathem"}, + {"CenterText", 0xBBBBBB, "Егор Палиев"}, + {"CenterText", 0xBBBBBB, "vk.com/mrherobrine"}, + {"EmptyLine"}, + {"Button", {ecs.colors.orange, 0xffffff, "OK"}} + ) + end + + if action == "Сохранить" then + local data = ecs.universalWindow("auto", "auto", 30, 0x262626, true, + {"EmptyLine"}, + {"CenterText", ecs.colors.orange, "Сохранить как"}, + {"EmptyLine"}, + {"Input", 0xFFFFFF, ecs.colors.orange, "Путь"}, + {"Selector", 0xFFFFFF, ecs.colors.orange, ".3dm"}, + {"EmptyLine"}, + {"Button", {ecs.colors.orange, 0xffffff, "OK"}, {0x999999, 0xffffff, "Отмена"}} + ) + if data[3] == "OK" then + data[1] = data[1] or "Untitled" + local filename = data[1] .. data[2] + save(filename) + end + elseif action == "Открыть" then + local data = ecs.universalWindow("auto", "auto", 30, 0x262626, true, + {"EmptyLine"}, + {"CenterText", ecs.colors.orange, "Открыть"}, + {"EmptyLine"}, + {"Input", 0xFFFFFF, ecs.colors.orange, "Путь"}, + {"EmptyLine"}, + {"Button", {ecs.colors.orange, 0xffffff, "OK"}, {0x999999, 0xffffff, "Отмена"}} + ) + if data[2] == "OK" then + open(data[1]) + end + elseif action == "Новый" then + model = {} + fixModelArray() + drawAll() + drawModelOnHologram() + elseif action == "Выход" then + gpu.setResolution(xOld, yOld) + buffer.flush() + buffer.draw(true) + if hologramAvailable then component.hologram.clear() end + return + elseif action == "Масштаб" then + local data = ecs.universalWindow("auto", "auto", 36, 0x262626, true, + {"EmptyLine"}, + {"CenterText", ecs.colors.orange, "Изменить масштаб"}, + {"EmptyLine"}, + {"Slider", ecs.colors.white, ecs.colors.orange, 1, 100, math.ceil(component.hologram.getScale() * 100 / 4), "", "%"}, + {"EmptyLine"}, + {"Button", {ecs.colors.orange, 0xffffff, "OK"}, {0x999999, 0xffffff, "Отмена"}} + ) + + if data[2] == "OK" then + component.hologram.setScale(data[1] * 4 / 100) + end + elseif action == "Отступ проекции" then + local translation = { component.hologram.getTranslation() } + local data = ecs.universalWindow("auto", "auto", 36, 0x262626, true, + {"EmptyLine"}, + {"CenterText", ecs.colors.orange, "Отступ проекции"}, + {"EmptyLine"}, + {"CenterText", 0xFFFFFF, "Эти параметры позволяют проецировать"}, + {"CenterText", 0xFFFFFF, "голограмму на некотором расстоянии от"}, + {"CenterText", 0xFFFFFF, "проектора. Удобно, если вы хотите спрятать"}, + {"CenterText", 0xFFFFFF, "проектор от чужих глаз."}, + {"EmptyLine"}, + {"Slider", 0xFFFFFF, ecs.colors.orange, 1, 100, translation[1] * 100, "Ось X: ", "%"}, + {"Slider", 0xFFFFFF, ecs.colors.orange, 1, 100, translation[2] * 100, "Ось Y: ", "%"}, + {"Slider", 0xFFFFFF, ecs.colors.orange, 1, 100, translation[3] * 100, "Ось Z: ", "%"}, + {"EmptyLine"}, + {"Button", {ecs.colors.orange, 0xffffff, "OK"}, {0x999999, 0xffffff, "Отмена"}} + ) + + if data[4] == "OK" then + component.hologram.setTranslation(data[1] / 100, data[2] / 100, data[3] / 100) + end + elseif action == "Изменить палитру" then + local data = ecs.universalWindow("auto", "auto", 36, 0x262626, true, + {"EmptyLine"}, + {"CenterText", ecs.colors.orange, "Палитра проектора"}, + {"EmptyLine"}, + {"Color", "Цвет активного элемента", component.hologram.getPaletteColor(2)}, + {"Color", "Цвет других элементов", component.hologram.getPaletteColor(1)}, + {"Color", "Цвет рамки высоты", component.hologram.getPaletteColor(3)}, + {"EmptyLine"}, + {"Button", {ecs.colors.orange, 0xffffff, "OK"}, {0x999999, 0xffffff, "Отмена"}} + ) + + if data[4] == "OK" then + component.hologram.setPaletteColor(2, data[1]) + component.hologram.setPaletteColor(1, data[2]) + component.hologram.setPaletteColor(3, data[3]) + end + elseif action == "Включить показ слоя" then + showLayerOnHologram = true + drawModelOnHologram() + elseif action == "Отключить показ слоя" then + showLayerOnHologram = false + drawModelOnHologram() + elseif action == "Включить вращение" then + component.hologram.setRotationSpeed(15, 0, 23, 0) + elseif action == "Отключить вращение" then + component.hologram.setRotationSpeed(0, 0, 0, 0) + end + + drawTopMenu(0) + end + end + end + elseif e[1] == "scroll" then + if e[5] == 1 then + if currentLayer < 16 then + currentLayer = currentLayer + 1 + drawAll() + drawModelOnHologram() + end + else + if currentLayer > 1 then + currentLayer = currentLayer - 1 + drawAll() + drawModelOnHologram() + end + end + end +end + + + + + + + + + + + + + + + + + diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/3DPrint.app/Resources/About/Russian.txt b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/3DPrint.app/Resources/About/Russian.txt new file mode 100755 index 00000000..26c56000 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/3DPrint.app/Resources/About/Russian.txt @@ -0,0 +1 @@ +Это профессиональный инструмент для создания и распечатки моделей на 3D-принтере. Поддерживает все возможные функции принтера без исключения. Для работы требуется сам принтер и, по желанию, голографический проектор 2 уровня. \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/3DPrint.app/Resources/Icon.pic b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/3DPrint.app/Resources/Icon.pic new file mode 100755 index 0000000000000000000000000000000000000000..a95c78138253c115711dc23001c3b97f15fe2e6c GIT binary patch literal 142 zcmXZVu?@md31sLSQrzf3szMrq7cdCf( z+Y1v07}Y%X?TJZmpsJlRJr3Clq3D`t1Y74nKN1fJ-q~>}v|~Xp1zQU-7yS1`au%+} G=J5q+NE18& literal 0 HcmV?d00001 diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/3DTest.app/Main.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/3DTest.app/Main.lua new file mode 100755 index 00000000..66d43c37 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/3DTest.app/Main.lua @@ -0,0 +1,539 @@ + +-------------------------------------------------------- Libraries -------------------------------------------------------- + +-- package.loaded["GUI"] = nil +-- package.loaded["doubleBuffering"] = nil +-- package.loaded["vector"] = nil +-- package.loaded["OpenComputersGL/Main"] = nil +-- package.loaded["OpenComputersGL/Materials"] = nil +-- package.loaded["OpenComputersGL/Renderer"] = nil +-- package.loaded["MeowEngine/Main"] = nil + +local color = require("color") +local computer = require("computer") +local buffer = require("doubleBuffering") +local event = require("event") +local GUI = require("GUI") +local vector = require("vector") +local materials = require("OpenComputersGL/Materials") +local renderer = require("OpenComputersGL/Renderer") +local OCGL = require("OpenComputersGL/Main") +local meowEngine = require("MeowEngine/Main") + +---------------------------------------------- Anus preparing ---------------------------------------------- + +-- /MineOS/Desktop/3DTest.app/3DTest.lua + +buffer.flush() +meowEngine.intro(vector.newVector3(0, 0, 0), 20) + +local mainContainer = GUI.fullScreenContainer() +local scene = meowEngine.newScene(0x1D1D1D) + +scene.renderMode = OCGL.renderModes.flatShading +scene.auxiliaryMode = OCGL.auxiliaryModes.disabled + +scene.camera:translate(-2.5, 8.11, -19.57) +scene.camera:rotate(math.rad(30), 0, 0) +scene:addLight(meowEngine.newLight(vector.newVector3(0, 20, 0), 1.0, 200)) + +---------------------------------------------- Constants ---------------------------------------------- + +local blockSize = 5 +local rotationAngle = math.rad(5) +local translationOffset = 1 + +---------------------------------------------- Voxel-world system ---------------------------------------------- + +local world = {{{}}} + +local worldMesh = scene:addObject( + meowEngine.newMesh( + vector.newVector3(0, 0, 0), { }, { }, + materials.newSolidMaterial(0xFF00FF) + ) +) + +local function checkBlock(x, y, z) + if world[z] and world[z][y] and world[z][y][x] then + return true + end + return false +end + +local function setBlock(x, y, z, value) + world[z] = world[z] or {} + world[z][y] = world[z][y] or {} + world[z][y][x] = value +end + +local blockSides = { + front = 1, + left = 2, + back = 3, + right = 4, + up = 5, + down = 6 +} + +local function renderWorld() + worldMesh.vertices = {} + worldMesh.triangles = {} + + for z in pairs(world) do + for y in pairs(world[z]) do + for x in pairs(world[z][y]) do + local firstVertexIndex = #worldMesh.vertices + 1 + local xBlock, yBlock, zBlock = (x - 1) * blockSize, (y - 1) * blockSize, (z - 1) * blockSize + local material = materials.newSolidMaterial(world[z][y][x]) + + table.insert(worldMesh.vertices, vector.newVector3(xBlock, yBlock, zBlock)) + table.insert(worldMesh.vertices, vector.newVector3(xBlock, yBlock + blockSize, zBlock)) + table.insert(worldMesh.vertices, vector.newVector3(xBlock + blockSize, yBlock + blockSize, zBlock)) + table.insert(worldMesh.vertices, vector.newVector3(xBlock + blockSize, yBlock, zBlock)) + table.insert(worldMesh.vertices, vector.newVector3(xBlock, yBlock, zBlock + blockSize)) + table.insert(worldMesh.vertices, vector.newVector3(xBlock, yBlock + blockSize, zBlock + blockSize)) + table.insert(worldMesh.vertices, vector.newVector3(xBlock + blockSize, yBlock + blockSize, zBlock + blockSize)) + table.insert(worldMesh.vertices, vector.newVector3(xBlock + blockSize, yBlock, zBlock + blockSize)) + + -- Front (1, 2) + if not checkBlock(x, y, z - 1) then + table.insert(worldMesh.triangles, OCGL.newIndexedTriangle(firstVertexIndex, firstVertexIndex + 1, firstVertexIndex + 2, material)) + table.insert(worldMesh.triangles, OCGL.newIndexedTriangle(firstVertexIndex + 3, firstVertexIndex, firstVertexIndex + 2, material)) + end + + -- Left (3, 4) + if not checkBlock(x - 1, y, z) then + table.insert(worldMesh.triangles, OCGL.newIndexedTriangle(firstVertexIndex + 5, firstVertexIndex + 1, firstVertexIndex + 4, material)) + table.insert(worldMesh.triangles, OCGL.newIndexedTriangle(firstVertexIndex + 1, firstVertexIndex, firstVertexIndex + 4, material)) + end + + -- Back (5, 6) + if not checkBlock(x, y, z + 1) then + table.insert(worldMesh.triangles, OCGL.newIndexedTriangle(firstVertexIndex + 6, firstVertexIndex + 5, firstVertexIndex + 7, material)) + table.insert(worldMesh.triangles, OCGL.newIndexedTriangle(firstVertexIndex + 5, firstVertexIndex + 4, firstVertexIndex + 7, material)) + end + + -- Right (7, 8) + if not checkBlock(x + 1, y, z) then + table.insert(worldMesh.triangles, OCGL.newIndexedTriangle(firstVertexIndex + 3, firstVertexIndex + 2, firstVertexIndex + 6, material)) + table.insert(worldMesh.triangles, OCGL.newIndexedTriangle(firstVertexIndex + 7, firstVertexIndex + 3, firstVertexIndex + 6, material)) + end + + -- Up (9, 10) + if not checkBlock(x, y + 1, z) then + table.insert(worldMesh.triangles, OCGL.newIndexedTriangle(firstVertexIndex + 1, firstVertexIndex + 5, firstVertexIndex + 6, material)) + table.insert(worldMesh.triangles, OCGL.newIndexedTriangle(firstVertexIndex + 2, firstVertexIndex + 1, firstVertexIndex + 6, material)) + end + + -- Down (11, 12) + if not checkBlock(x, y - 1, z) then + table.insert(worldMesh.triangles, OCGL.newIndexedTriangle(firstVertexIndex + 4, firstVertexIndex, firstVertexIndex + 7, material)) + table.insert(worldMesh.triangles, OCGL.newIndexedTriangle(firstVertexIndex, firstVertexIndex + 3, firstVertexIndex + 7, material)) + end + end + end + end +end + +-- Mode 1 +local hue, hueStep = 0, 360 / 9 +for z = -1, 1 do + for x = -1, 1 do + if not (x == 0 and z == 0) then + setBlock(x, 0, z, color.HSBToInteger(hue, 1, 1)) + hue = hue + hueStep + end + end +end + +-- -- Mode 2 +-- for z = 1, 7 do +-- for x = -3, 3 do +-- setBlock(x, 0, z, 0xFFFFFF) +-- end +-- end + +---------------------------------------------- Cat ---------------------------------------------- + +-- scene:addObject(meowEngine.newPolyCatMesh(vector.newVector3(0, 5, 0), 5)) +-- scene:addObject(meowEngine.newFloatingText(vector.newVector3(0, -2, 0), 0xEEEEEE, "Тест плавающего текста")) + +---------------------------------------------- Texture ---------------------------------------------- + +-- scene.camera:translate(0, 20, 0) +-- scene.camera:rotate(math.rad(90), 0, 0) +-- local texturedPlane = scene:addObject(meowEngine.newTexturedPlane(vector.newVector3(0, 0, 0), 20, 20, materials.newDebugTexture(16, 16, 40))) + +---------------------------------------------- Wave ---------------------------------------------- + +-- local xCells, yCells = 4, 1 +-- local plane = meowEngine.newPlane(vector.newVector3(0, 0, 0), 40, 15, xCells, yCells, materials.newSolidMaterial(0xFFFFFF)) +-- plane.nextWave = function(mesh) +-- for xCell = 1, xCells do +-- for yCell = 1, yCells do + +-- end +-- end +-- end + +---------------------------------------------- Fractal field ---------------------------------------------- + +-- local function createField(vector3Position, xCellCount, yCellCount, cellSize) +-- local totalWidth, totalHeight = xCellCount * cellSize, yCellCount * cellSize +-- local halfWidth, halfHeight = totalWidth / 2, totalHeight / 2 +-- xCellCount, yCellCount = xCellCount + 1, yCellCount + 1 +-- local vertices, triangles = {}, {} + +-- local vertexIndex = 1 +-- for yCell = 1, yCellCount do +-- for xCell = 1, xCellCount do +-- table.insert(vertices, vector.newVector3(xCell * cellSize - cellSize - halfWidth, yCell * cellSize - cellSize - halfHeight, 0)) + +-- if xCell < xCellCount and yCell < yCellCount then +-- table.insert(triangles, +-- OCGL.newIndexedTriangle( +-- vertexIndex, +-- vertexIndex + 1, +-- vertexIndex + xCellCount +-- ) +-- ) +-- table.insert(triangles, +-- OCGL.newIndexedTriangle( +-- vertexIndex + 1, +-- vertexIndex + xCellCount + 1, +-- vertexIndex + xCellCount +-- ) +-- ) +-- end + +-- vertexIndex = vertexIndex + 1 +-- end +-- end + +-- local mesh = meowEngine.newMesh(vector3Position, vertices, triangles,materials.newSolidMaterial(0xFF8888)) + +-- local function getRandomSignedInt(from, to) +-- return (math.random(0, 1) == 1 and 1 or -1) * (math.random(from, to)) +-- end + +-- local function getRandomDirection() +-- return getRandomSignedInt(5, 100) / 100 +-- end + +-- mesh.randomizeTrianglesColor = function(mesh, hueChangeSpeed, brightnessChangeSpeed, minimumBrightness) +-- mesh.hue = mesh.hue and mesh.hue + hueChangeSpeed or math.random(0, 360) +-- if mesh.hue > 359 then mesh.hue = 0 end + +-- for triangleIndex = 1, #mesh.triangles do +-- mesh.triangles[triangleIndex].brightness = mesh.triangles[triangleIndex].brightness and mesh.triangles[triangleIndex].brightness + getRandomSignedInt(1, brightnessChangeSpeed) or math.random(minimumBrightness, 100) +-- if mesh.triangles[triangleIndex].brightness > 100 then +-- mesh.triangles[triangleIndex].brightness = 100 +-- elseif mesh.triangles[triangleIndex].brightness < minimumBrightness then +-- mesh.triangles[triangleIndex].brightness = minimumBrightness +-- end +-- mesh.triangles[triangleIndex][4] = materials.newSolidMaterial(color.HSBToInteger(mesh.hue, 1, mesh.triangles[triangleIndex].brightness)) +-- end +-- end + +-- mesh.randomizeVerticesPosition = function(mesh, speed) +-- local vertexIndex = 1 +-- for yCell = 1, yCellCount do +-- for xCell = 1, xCellCount do +-- if xCell > 1 and xCell < xCellCount and yCell > 1 and yCell < yCellCount then +-- mesh.vertices[vertexIndex].offset = mesh.vertices[vertexIndex].offset or {0, 0} +-- mesh.vertices[vertexIndex].direction = mesh.vertices[vertexIndex].direction or {getRandomDirection(), getRandomDirection()} + +-- local newOffset = { +-- mesh.vertices[vertexIndex].direction[1] * (speed * cellSize), +-- mesh.vertices[vertexIndex].direction[1] * (speed * cellSize) +-- } + +-- for i = 1, 2 do +-- if math.abs(mesh.vertices[vertexIndex].offset[i] + newOffset[i]) < cellSize / 2 then +-- mesh.vertices[vertexIndex].offset[i] = mesh.vertices[vertexIndex].offset[i] + newOffset[i] +-- mesh.vertices[vertexIndex][i] = mesh.vertices[vertexIndex][i] + newOffset[i] +-- else +-- mesh.vertices[vertexIndex].direction[i] = getRandomDirection() +-- end +-- end +-- end +-- vertexIndex = vertexIndex + 1 +-- end +-- end +-- end + +-- return mesh +-- end + +-- local plane = createField(vector.newVector3(0, 0, 0), 8, 4, 4) +-- scene:addObject(plane) +-- plane:randomizeTrianglesColor(10, 10, 50) + +-------------------------------------------------------- Controls -------------------------------------------------------- + +local function move(x, y, z) + local moveVector = vector.newVector3(x, y, z) + OCGL.rotateVectorRelativeToXAxis(moveVector, scene.camera.rotation[1]) + OCGL.rotateVectorRelativeToYAxis(moveVector, scene.camera.rotation[2]) + scene.camera:translate(moveVector[1], moveVector[2], moveVector[3]) +end + +local function moveLight(x, y, z) + scene.lights[mainContainer.toolbar.lightSelectComboBox.selectedItem].position[1] = scene.lights[mainContainer.toolbar.lightSelectComboBox.selectedItem].position[1] + x + scene.lights[mainContainer.toolbar.lightSelectComboBox.selectedItem].position[2] = scene.lights[mainContainer.toolbar.lightSelectComboBox.selectedItem].position[2] + y + scene.lights[mainContainer.toolbar.lightSelectComboBox.selectedItem].position[3] = scene.lights[mainContainer.toolbar.lightSelectComboBox.selectedItem].position[3] + z +end + +local controls = { + -- F1 + [59 ] = function() mainContainer.toolbar.hidden = not mainContainer.toolbar.hidden; mainContainer.infoTextBox.hidden = not mainContainer.infoTextBox.hidden end, + -- Arrows + [200] = function() scene.camera:rotate(-rotationAngle, 0, 0) end, + [208] = function() scene.camera:rotate(rotationAngle, 0, 0) end, + [203] = function() scene.camera:rotate(0, -rotationAngle, 0) end, + [205] = function() scene.camera:rotate(0, rotationAngle, 0) end, + [16 ] = function() scene.camera:rotate(0, 0, rotationAngle) end, + [18 ] = function() scene.camera:rotate(0, 0, -rotationAngle) end, + -- WASD + [17 ] = function() move(0, 0, translationOffset) end, + [31 ] = function() move(0, 0, -translationOffset) end, + [30 ] = function() move(-translationOffset, 0, 0) end, + [32 ] = function() move(translationOffset, 0, 0) end, + -- RSHIFT, SPACE + [42 ] = function() move(0, -translationOffset, 0) end, + [57 ] = function() move(0, translationOffset, 0) end, + -- NUM 4 6 8 5 1 3 + [75 ] = function() moveLight(-translationOffset, 0, 0) end, + [77 ] = function() moveLight(translationOffset, 0, 0) end, + [72 ] = function() moveLight(0, 0, translationOffset) end, + [80 ] = function() moveLight(0, 0, -translationOffset) end, + [79 ] = function() moveLight(0, -translationOffset, 0) end, + [81 ] = function() moveLight(0, translationOffset, 0) end, +} + +-------------------------------------------------------- GUI -------------------------------------------------------- + +local OCGLView = GUI.object(1, 1, mainContainer.width, mainContainer.height) + +local function drawInvertedText(x, y, text) + local index = buffer.getIndex(x, y) + local background, foreground = buffer.rawGet(index) + buffer.rawSet(index, background, 0xFFFFFF - foreground, text) +end + +local function drawCross(x, y) + drawInvertedText(x - 2, y, "━") + drawInvertedText(x - 1, y, "━") + drawInvertedText(x + 2, y, "━") + drawInvertedText(x + 1, y, "━") + drawInvertedText(x, y - 1, "┃") + drawInvertedText(x, y + 1, "┃") +end + +OCGLView.draw = function(object) + mainContainer.oldClock = os.clock() + if world then renderWorld() end + scene:render() + if mainContainer.toolbar.zBufferSwitch.state then + renderer.visualizeDepthBuffer() + end + drawCross(renderer.viewport.xCenter, math.floor(renderer.viewport.yCenter / 2)) +end + +OCGLView.eventHandler = function(mainContainer, object, eventData) + if eventData[1] == "touch" then + local targetVector = vector.newVector3(scene.camera.position[1], scene.camera.position[2], scene.camera.position[3] + 1000) + OCGL.rotateVectorRelativeToXAxis(targetVector, scene.camera.rotation[1]) + OCGL.rotateVectorRelativeToYAxis(targetVector, scene.camera.rotation[2]) + local objectIndex, triangleIndex, distance = meowEngine.sceneRaycast(scene, scene.camera.position, targetVector) + + if objectIndex then + local triangle = scene.objects[objectIndex].triangles[triangleIndex] + local xWorld = math.floor(((scene.objects[objectIndex].vertices[scene.objects[objectIndex].triangles[triangleIndex][1]][1] + scene.objects[objectIndex].vertices[scene.objects[objectIndex].triangles[triangleIndex][2]][1] + scene.objects[objectIndex].vertices[scene.objects[objectIndex].triangles[triangleIndex][3]][1]) / 3) / blockSize) + 1 + local yWorld = math.floor(((scene.objects[objectIndex].vertices[triangle[1]][2] + scene.objects[objectIndex].vertices[triangle[2]][2] + scene.objects[objectIndex].vertices[triangle[3]][2]) / 3) / blockSize) + 1 + local zWorld = math.floor(((scene.objects[objectIndex].vertices[triangle[1]][3] + scene.objects[objectIndex].vertices[triangle[2]][3] + scene.objects[objectIndex].vertices[triangle[3]][3]) / 3) / blockSize) + 1 + + local normalVector = vector.getSurfaceNormal( + scene.objects[objectIndex].vertices[triangle[1]], + scene.objects[objectIndex].vertices[triangle[2]], + scene.objects[objectIndex].vertices[triangle[3]] + ) + + if normalVector[1] > 0 and eventData[5] ~= 1 or normalVector[1] < 0 and eventData[5] == 1 then + xWorld = xWorld - 1 + elseif normalVector[2] > 0 and eventData[5] ~= 1 or normalVector[2] < 0 and eventData[5] == 1 then + yWorld = yWorld - 1 + elseif normalVector[3] > 0 and eventData[5] ~= 1 or normalVector[3] < 0 and eventData[5] == 1 then + zWorld = zWorld - 1 + end + + setBlock(xWorld, yWorld, zWorld, eventData[5] == 1 and mainContainer.toolbar.blockColorSelector.color or nil) + end + end +end + +mainContainer:addChild(OCGLView) + +mainContainer.infoTextBox = mainContainer:addChild(GUI.textBox(2, 4, 45, mainContainer.height, nil, 0xEEEEEE, {}, 1, 0, 0)) +local lines = { + "Copyright © 2016-2017 - Developed by ECS Inc.", + "Timofeef Igor (vk.com/id7799889), Trifonov Gleb (vk.com/id88323331), Verevkin Yakov (vk.com/id60991376), Bogushevich Victoria (vk.com/id171497518)", + "All rights reserved", +} +mainContainer:addChild(GUI.textBox(1, mainContainer.height - #lines + 1, mainContainer.width, #lines, nil, 0x3C3C3C, lines, 1)):setAlignment(GUI.alignment.horizontal.center, GUI.alignment.vertical.top) + +local elementY = 2 +mainContainer.toolbar = mainContainer:addChild(GUI.container(mainContainer.width - 31, 1, 32, mainContainer.height)) +local elementWidth = mainContainer.toolbar.width - 2 +mainContainer.toolbar:addChild(GUI.panel(1, 1, mainContainer.toolbar.width, mainContainer.toolbar.height, 0x0, 0.5)) + +mainContainer.toolbar:addChild(GUI.label(2, elementY, elementWidth, 1, 0xEEEEEE, "Render mode")):setAlignment(GUI.alignment.horizontal.center, GUI.alignment.vertical.top); elementY = elementY + 2 +mainContainer.toolbar.renderModeComboBox = mainContainer.toolbar:addChild(GUI.comboBox(2, elementY, elementWidth, 1, 0x2D2D2D, 0xAAAAAA, 0x555555, 0x888888)); elementY = elementY + mainContainer.toolbar.renderModeComboBox.height + 1 +mainContainer.toolbar.renderModeComboBox:addItem("disabled") +mainContainer.toolbar.renderModeComboBox:addItem("constantShading") +mainContainer.toolbar.renderModeComboBox:addItem("flatShading") +mainContainer.toolbar.renderModeComboBox.selectedItem = scene.renderMode +mainContainer.toolbar.renderModeComboBox.onItemSelected = function() + scene.renderMode = mainContainer.toolbar.renderModeComboBox.selectedItem +end + +mainContainer.toolbar.auxiliaryModeComboBox = mainContainer.toolbar:addChild(GUI.comboBox(2, elementY, elementWidth, 1, 0x2D2D2D, 0xAAAAAA, 0x555555, 0x888888)); elementY = elementY + mainContainer.toolbar.auxiliaryModeComboBox.height + 1 +mainContainer.toolbar.auxiliaryModeComboBox:addItem("disabled") +mainContainer.toolbar.auxiliaryModeComboBox:addItem("wireframe") +mainContainer.toolbar.auxiliaryModeComboBox:addItem("vertices") +mainContainer.toolbar.auxiliaryModeComboBox.selectedItem = scene.auxiliaryMode +mainContainer.toolbar.auxiliaryModeComboBox.onItemSelected = function() + scene.auxiliaryMode = mainContainer.toolbar.auxiliaryModeComboBox.selectedItem +end + +mainContainer.toolbar:addChild(GUI.label(2, elementY, elementWidth, 1, 0xAAAAAA, "Perspective proj:")) +mainContainer.toolbar.perspectiveSwitch = mainContainer.toolbar:addChild(GUI.switch(mainContainer.toolbar.width - 8, elementY, 8, 0x66DB80, 0x2D2D2D, 0xEEEEEE, scene.camera.projectionEnabled)); elementY = elementY + 2 +mainContainer.toolbar.perspectiveSwitch.onStateChanged = function() + scene.camera.projectionEnabled = mainContainer.toolbar.perspectiveSwitch.state +end + +mainContainer.toolbar:addChild(GUI.label(2, elementY, elementWidth, 1, 0xAAAAAA, "Z-buffer visualize:")) +mainContainer.toolbar.zBufferSwitch = mainContainer.toolbar:addChild(GUI.switch(mainContainer.toolbar.width - 8, elementY, 8, 0x66DB80, 0x2D2D2D, 0xEEEEEE, false)); elementY = elementY + 2 + + +local function calculateLightComboBox() + mainContainer.toolbar.lightSelectComboBox.dropDownMenu.itemsContainer.children = {} + for i = 1, #scene.lights do + mainContainer.toolbar.lightSelectComboBox:addItem(tostring(i)) + end + mainContainer.toolbar.lightSelectComboBox.selectedItem = #mainContainer.toolbar.lightSelectComboBox.dropDownMenu.itemsContainer.children + mainContainer.toolbar.lightIntensitySlider.value = scene.lights[mainContainer.toolbar.lightSelectComboBox.selectedItem].intensity * 100 + mainContainer.toolbar.lightEmissionSlider.value = scene.lights[mainContainer.toolbar.lightSelectComboBox.selectedItem].emissionDistance +end + +mainContainer.toolbar:addChild(GUI.label(2, elementY, elementWidth, 1, 0xEEEEEE, "Light control")):setAlignment(GUI.alignment.horizontal.center, GUI.alignment.vertical.top); elementY = elementY + 2 +mainContainer.toolbar.lightSelectComboBox = mainContainer.toolbar:addChild(GUI.comboBox(2, elementY, elementWidth, 1, 0x2D2D2D, 0xAAAAAA, 0x555555, 0x888888)); elementY = elementY + mainContainer.toolbar.lightSelectComboBox.height + 1 + +mainContainer.toolbar.addLightButton = mainContainer.toolbar:addChild(GUI.button(2, elementY, elementWidth, 1, 0x2D2D2D, 0xAAAAAA, 0x555555, 0xAAAAAA, "Add light")); elementY = elementY + 2 +mainContainer.toolbar.addLightButton.onTouch = function() + scene:addLight(meowEngine.newLight(vector.newVector3(0, 10, 0), mainContainer.toolbar.lightIntensitySlider.value / 100, mainContainer.toolbar.lightEmissionSlider.value)) + calculateLightComboBox() +end + +mainContainer.toolbar.removeLightButton = mainContainer.toolbar:addChild(GUI.button(2, elementY, elementWidth, 1, 0x2D2D2D, 0xAAAAAA, 0x555555, 0xAAAAAA, "Remove light")); elementY = elementY + 2 +mainContainer.toolbar.removeLightButton.onTouch = function() + if #scene.lights > 1 then + table.remove(scene.lights, mainContainer.toolbar.lightSelectComboBox.selectedItem) + calculateLightComboBox() + end +end + +mainContainer.toolbar.lightIntensitySlider = mainContainer.toolbar:addChild(GUI.slider(2, elementY, elementWidth, 0xCCCCCC, 0x2D2D2D, 0xEEEEEE, 0xAAAAAA, 0, 500, 100, false, "Intensity: ", "")); elementY = elementY + 3 +mainContainer.toolbar.lightIntensitySlider.onValueChanged = function() + scene.lights[mainContainer.toolbar.lightSelectComboBox.selectedItem].intensity = mainContainer.toolbar.lightIntensitySlider.value / 100 +end +mainContainer.toolbar.lightEmissionSlider = mainContainer.toolbar:addChild(GUI.slider(2, elementY, elementWidth, 0xCCCCCC, 0x2D2D2D, 0xEEEEEE, 0xAAAAAA, 0, scene.lights[mainContainer.toolbar.lightSelectComboBox.selectedItem].emissionDistance, scene.lights[mainContainer.toolbar.lightSelectComboBox.selectedItem].emissionDistance, false, "Distance: ", "")); elementY = elementY + 3 +mainContainer.toolbar.lightEmissionSlider.onValueChanged = function() + scene.lights[mainContainer.toolbar.lightSelectComboBox.selectedItem].emissionDistance = mainContainer.toolbar.lightEmissionSlider.value +end +calculateLightComboBox() + +mainContainer.toolbar.blockColorSelector = mainContainer.toolbar:addChild(GUI.colorSelector(2, elementY, elementWidth, 1, 0xEEEEEE, "Block color")); elementY = elementY + mainContainer.toolbar.blockColorSelector.height + 1 +mainContainer.toolbar.backgroundColorSelector = mainContainer.toolbar:addChild(GUI.colorSelector(2, elementY, elementWidth, 1, scene.backgroundColor, "Background color")); elementY = elementY + mainContainer.toolbar.blockColorSelector.height + 1 +mainContainer.toolbar.backgroundColorSelector.onTouch = function() + scene.backgroundColor = mainContainer.toolbar.backgroundColorSelector.color +end + +mainContainer.toolbar:addChild(GUI.label(2, elementY, elementWidth, 1, 0xEEEEEE, "RAM monitoring")):setAlignment(GUI.alignment.horizontal.center, GUI.alignment.vertical.top); elementY = elementY + 2 +mainContainer.toolbar.RAMChart = mainContainer.toolbar:addChild(GUI.chart(2, elementY, elementWidth, mainContainer.toolbar.height - elementY - 3, 0xEEEEEE, 0xAAAAAA, 0x555555, 0x66DB80, 0.35, 0.25, "s", "%", true, {})); elementY = elementY + mainContainer.toolbar.RAMChart.height + 1 +mainContainer.toolbar.RAMChart.roundValues = true +-- mainContainer.toolbar.RAMChart.showXAxisValues = false +mainContainer.toolbar.RAMChart.counter = 1 + +mainContainer.toolbar:addChild(GUI.button(1, mainContainer.toolbar.height - 2, mainContainer.toolbar.width, 3, 0x2D2D2D, 0xEEEEEE, 0x444444, 0xEEEEEE, "Exit")).onTouch = function() + mainContainer:stopEventHandling() +end + +local FPSCounter = GUI.object(2, 2, 8, 3) +FPSCounter.draw = function(FPSCounter) + renderer.renderFPSCounter(FPSCounter.x, FPSCounter.y, tostring(math.ceil(1 / (os.clock() - mainContainer.oldClock) / 10)), 0xFFFF00) +end +mainContainer:addChild(FPSCounter) + +mainContainer.eventHandler = function(mainContainer, object, eventData) + if not mainContainer.toolbar.hidden then + local totalMemory = computer.totalMemory() + table.insert(mainContainer.toolbar.RAMChart.values, {mainContainer.toolbar.RAMChart.counter, math.ceil((totalMemory - computer.freeMemory()) / totalMemory * 100)}) + mainContainer.toolbar.RAMChart.counter = mainContainer.toolbar.RAMChart.counter + 1 + if #mainContainer.toolbar.RAMChart.values > 20 then table.remove(mainContainer.toolbar.RAMChart.values, 1) end + + mainContainer.infoTextBox.lines = { + " ", + "SceneObjects: " .. #scene.objects, + " ", + "OCGLVertices: " .. #OCGL.vertices, + "OCGLTriangles: " .. #OCGL.triangles, + "OCGLLines: " .. #OCGL.lines, + "OCGLFloatingTexts: " .. #OCGL.floatingTexts, + "OCGLLights: " .. #OCGL.lights, + " ", + "CameraFOV: " .. string.format("%.2f", math.deg(scene.camera.FOV)), + "СameraPosition: " .. string.format("%.2f", scene.camera.position[1]) .. " x " .. string.format("%.2f", scene.camera.position[2]) .. " x " .. string.format("%.2f", scene.camera.position[3]), + "СameraRotation: " .. string.format("%.2f", math.deg(scene.camera.rotation[1])) .. " x " .. string.format("%.2f", math.deg(scene.camera.rotation[2])) .. " x " .. string.format("%.2f", math.deg(scene.camera.rotation[3])), + "CameraNearClippingSurface: " .. string.format("%.2f", scene.camera.nearClippingSurface), + "CameraFarClippingSurface: " .. string.format("%.2f", scene.camera.farClippingSurface), + "CameraProjectionSurface: " .. string.format("%.2f", scene.camera.projectionSurface), + "CameraPerspectiveProjection: " .. tostring(scene.camera.projectionEnabled), + " ", + "Controls:", + " ", + "Arrows - camera rotation", + "WASD/Shift/Space - camera movement", + "LMB/RMB - destroy/place block", + "NUM 8/2/4/6/1/3 - selected light movement", + "F1 - toggle GUI overlay", + } + + mainContainer.infoTextBox.height = #mainContainer.infoTextBox.lines + end + + if eventData[1] == "key_down" then + if controls[eventData[4]] then controls[eventData[4]]() end + elseif eventData[1] == "scroll" then + if eventData[5] == 1 then + if scene.camera.FOV < math.rad(170) then + scene.camera:setFOV(scene.camera.FOV + math.rad(5)) + end + else + if scene.camera.FOV > math.rad(5) then + scene.camera:setFOV(scene.camera.FOV - math.rad(5)) + end + end + end + + mainContainer:draw() + buffer.draw() +end + +-------------------------------------------------------- Ebat-kopat -------------------------------------------------------- + +mainContainer:startEventHandling(0) + + + diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/3DTest.app/Resources/About/Russian.txt b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/3DTest.app/Resources/About/Russian.txt new file mode 100755 index 00000000..86e147be --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/3DTest.app/Resources/About/Russian.txt @@ -0,0 +1 @@ +Программа-демонстратор возможностей 3D-движка, созданного специально для низкопроизводительных компьютеров. В ней воплощены практически все наработки нашей команды: от отрисовки сложных трехмерных объектов и динамического освещения до текстурирования и пользовательского интерфейса на мощной GUI-библиотеке. \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/3DTest.app/Resources/Icon.pic b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/3DTest.app/Resources/Icon.pic new file mode 100755 index 0000000000000000000000000000000000000000..358e8b8d3dc4d968b1aa342a80d33bece6c61e9c GIT binary patch literal 134 zcmXYq(FuS+3`3i?-gOLj5d^=SNfbxuk0abX;(r&>3fFw(K~l0#%Un$PrC|i66c)ZE y+{g>xJ97BhQNpjp#sX7RA|B-d2^X;FP^7zdZ 2 then + app.height = #lines + 2 + end + end + + return app +end + +local function addUpdateImage() + window.contentContainer:deleteChildren() + local cyka = window.contentContainer:addChild(GUI.image(math.floor(window.contentContainer.width / 2 - image.getWidth(updateImage) / 2), math.floor(window.contentContainer.height / 2 - image.getHeight(updateImage) / 2) - 1, updateImage)) + return cyka.localY + cyka.height + 2 +end + +local function updateApplicationList() + local y = addUpdateImage() + window.contentContainer:addChild(GUI.label(1, y, window.contentContainer.width, 1, 0x888888, localization.checkingForUpdates)):setAlignment(GUI.alignment.horizontal.center, GUI.alignment.vertical.top) + mainContainer:draw() + buffer.draw() + + applicationList = table.fromString(web.request(applicationListURL)) +end + + +local function displayApps(fromPage, typeFilter, nameFilter, updateCheck) + window.contentContainer:deleteChildren() + + local y = 2 + local finalApplicationList = {} + + if updateCheck then + local oldApplicationList = table.fromFile(MineOSPaths.applicationList) + + for j = 1, #applicationList do + local pathFound = false + + for i = 1, #oldApplicationList do + if oldApplicationList[i].path == applicationList[j].path then + if oldApplicationList[i].version < applicationList[j].version then + table.insert(finalApplicationList, applicationList[j]) + end + + pathFound = true + break + end + end + + if not pathFound then + table.insert(finalApplicationList, applicationList[j]) + end + end + + if #finalApplicationList == 0 then + window.contentContainer:addChild(GUI.label(1, 1, window.contentContainer.width, window.contentContainer.height, 0x888888, localization.youHaveNewestApps)):setAlignment(GUI.alignment.horizontal.center, GUI.alignment.vertical.center) + mainContainer:draw() + buffer.draw() + return + else + window.contentContainer:addChild(GUI.roundedButton(math.floor(window.contentContainer.width / 2 - 9), y, 18, 1, 0xBBBBBB, 0xFFFFFF, 0x999999, 0xFFFFFF, localization.updateAll)).onTouch = function() + y = addUpdateImage() + + local progressBarWidth = math.floor(window.contentContainer.width * 0.65) + local progressBar = window.contentContainer:addChild(GUI.progressBar(math.floor(window.contentContainer.width / 2 - progressBarWidth / 2), y, progressBarWidth, 0x33B6FF, 0xDDDDDD, 0x0, 0, true, false)) + local label = window.contentContainer:addChild(GUI.label(1, y + 1, window.contentContainer.width, 1, 0x888888, "")):setAlignment(GUI.alignment.horizontal.center, GUI.alignment.vertical.top) + + for i = 1, #finalApplicationList do + progressBar.value = math.floor(i / #finalApplicationList * 100) + label.text = localization.updating .. fs.name(finalApplicationList[i].path) + + mainContainer:draw() + buffer.draw() + + MineOSCore.downloadApplication(finalApplicationList[i], MineOSCore.properties.language) + end + + mainContainer:draw() + buffer.draw() + + table.toFile(MineOSPaths.applicationList, applicationList) + + computer.shutdown(true) + end + end + else + window.contentContainer.searchInputTextBox = window.contentContainer:addChild(GUI.input(math.floor(window.contentContainer.width / 2 - 10), y, 20, 1, 0xFFFFFF, 0x444444, 0xAAAAAA, 0xFFFFFF, 0x2D2D2D, "", localization.search, true)) + window.contentContainer.searchInputTextBox.onInputFinished = function() + if window.contentContainer.searchInputTextBox.text then + displayApps(1, typeFilter, window.contentContainer.searchInputTextBox.text) + end + end + + for i = 1, #applicationList do + if (not typeFilter or typeFilter == applicationList[i].type) and (not nameFilter or string.unicodeFind(unicode.lower(fs.name(applicationList[i].path)), unicode.lower(nameFilter))) then + table.insert(finalApplicationList, applicationList[i]) + end + end + end + + y = y + 2 + + mainContainer:draw() + buffer.draw() + + local appOnPageCounter, fromAppCounter, fromApp = 1, 1, (fromPage - 1) * appsPerPage + 1 + for i = 1, #finalApplicationList do + if fromAppCounter >= fromApp then + y, appOnPageCounter = y + window.contentContainer:addChild(newApp(1, y, window.contentContainer.width, finalApplicationList[i])).height + 1, appOnPageCounter + 1 + + mainContainer:draw() + buffer.draw() + + if appOnPageCounter > appsPerPage then + break + end + end + + fromAppCounter = fromAppCounter + 1 + end + + -- Pages buttons CYKA + local buttonWidth, text = 5, localization.page .. fromPage + local textLength = unicode.len(text) + local x = math.floor(window.contentContainer.width / 2 - (buttonWidth * 2 + textLength + 4) / 2) + window.contentContainer:addChild(GUI.roundedButton(x, y, buttonWidth, 1, 0xBBBBBB, 0xFFFFFF, 0x999999, 0xFFFFFF, "<")).onTouch = function() + if fromPage > 1 then + displayApps(fromPage - 1, typeFilter, nameFilter) + end + end + x = x + buttonWidth + 2 + + window.contentContainer:addChild(GUI.label(x, y, textLength, 1, 0x3C3C3C, text)) + x = x + textLength + 2 + + window.contentContainer:addChild(GUI.roundedButton(x, y, buttonWidth, 1, 0xBBBBBB, 0xFFFFFF, 0x999999, 0xFFFFFF, ">")).onTouch = function() + displayApps(fromPage + 1, typeFilter, nameFilter) + end + + mainContainer:draw() + buffer.draw() +end + +window.contentContainer = window:addChild(GUI.container(3, 4, window.width - 4, window.height - 3)) +window.contentContainer.eventHandler = function(mainContainer, object, eventData) + if eventData[1] == "scroll" and (eventData[5] == -1 or window.contentContainer.children[1].localY <= 1) then + for i = 1, #window.contentContainer.children do + window.contentContainer.children[i].localY = window.contentContainer.children[i].localY + eventData[5] + end + mainContainer:draw() + buffer.draw() + end +end + +local tabs = { + window.tabBar:addItem(localization.applications), + window.tabBar:addItem(localization.libraries), + window.tabBar:addItem(localization.wallpapers), + window.tabBar:addItem(localization.other), + window.tabBar:addItem(localization.updates) +} + +window.onResize = function(width, height) + window.contentContainer.width, window.contentContainer.height = width - 4, height - 3 + window.tabBar.width = width + window.backgroundPanel.width = width + window.backgroundPanel.height = height - window.tabBar.height + tabs[window.tabBar.selectedItem].onTouch() +end + +tabs[1].onTouch = function() displayApps(1, "Application") end +tabs[2].onTouch = function() displayApps(1, "Library") end +tabs[3].onTouch = function() displayApps(1, "Wallpaper") end +tabs[4].onTouch = function() displayApps(1, "Script") end +tabs[5].onTouch = function() displayApps(1, nil, nil, true) end + +---------------------------------------------------------------------------------------------------------------- + +if select(1, ...) == "updates" then + window.tabBar.selectedItem = 5 +end + +updateApplicationList() +tabs[window.tabBar.selectedItem].onTouch() + + + + + + + diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/AppMarket.app/Resources/About/Russian.txt b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/AppMarket.app/Resources/About/Russian.txt new file mode 100755 index 00000000..3f93c646 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/AppMarket.app/Resources/About/Russian.txt @@ -0,0 +1 @@ +Одно из главных системных приложений, позволяющее проверять наличие необходимых обновлений, а также загружать красивейшие программы, созданные специально для MineOS. \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/AppMarket.app/Resources/Icon.pic b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/AppMarket.app/Resources/Icon.pic new file mode 100755 index 0000000000000000000000000000000000000000..1e0deecfa632d0f9ab14ada7d678dd78b68790d6 GIT binary patch literal 117 zcmXYp%L#xm5JWS(o6VMCFM{C7(mY5I1UpDO>OGC2laD&Iz?*lT_^qgu|M)1dk0dG)86Y<0XgMkLL5Pm{_9ftWx@j8wot2y-0| zR#>dEQ`t3g<)x8eg zb^mUTjjz9xvFP#A2{M^%h#Q=7%hh!_nQnaZH8H+HaM$Nr7o#`7_Js$E(U-(hpPDOP zroR2RmLntVA{gKO7QAG}2syhXg4`g#>MN<z3^FUHH2H#wr5 z$|wrSCcgQWpj`3Qsc)VgnPPnN3Lnc3A@|K=3<0 zRx>`~LzU>t*B|rHY#A~oxB;8;l&Ry+6R3ycp}>7A5<|+lfZ9nDlCpbne^V zWRO&!&oWH?9;rnk3}87#dbmkW*cQIizH3=&HS9X^-OuKqJ(F#TSV?Y>SVUwucZ9yZ z%@!y|BZiTTnjhGlB68H6Q|z4NYu`PgV3mzi70l?BV4N{gF=H}{C$=U|+1dAx+;?m+ zF^_Y~up?`YB4|S+vyIw^U2PfXbbvD)=#u)D$}$zjZYPpTb($en=`-vnGB(#GL72fz zcsCgmZFH=%a}v@aKQvo;NzA9(gXM;W3Zl#Yk-Ja%{|s?^&2@h zyu^vGKc~9YYeuo}?)C%;wSmv=eH;vEPzVhyfnXWa3@2|;tb+9Kyi{k*nu3Ym?=2#e zAouN;^jGD5s0YvHD%lWA-~Nv>QD1Unyx>vj#5XaP2?JzWk@f8ZrdRt)eEqID@283# zX3W%CZCw14T{<~KAxW+*82jeCK6A=F?N`U;$hS&*jTHO(9oBfD#3057;V2p;zuWUT z`h@b|?P(oic()d^+XuABqC6Q-_F*_IMnV5O8gB9NX>z=$R{zZed)M#JkI{-hWGMfX z-`A~Ly`)xx`6ffC99Zr$tQ^l#sC_@;Li?8b=5r+kp;{3yF$*Qlw&wy5-reGRI^*bj zACOT>l+A>fUBwHLe@HNukJ}JouNlk-YocT6$k1*D>MZo0j~Gk0ammTtr>E^}e>Lpf z*N-{UbtGp)&ce4(C|fn)(*QfQgiw3SLa3=kENwK=?%eDLTZT;ae5^h2?epkGUwcWq zkt*|-(lxn$748iOM6AIQwzdBx75=YK0+Ts-fn~piZ6VC7YA4g}J zX<S+SVPy zTXApNTd3G@fQ=%$(;N8+M;UH?`}8=WgG+AU9D6pH7^d0~tLE{bZManrYWrTXiCF{BPsT5I>ns;OIS0{!mLhG&Br zt<)zik^qr!=>=?BhP9rIZGR-^M%A0>=-fuwZ^@!Kgk2b=%Ua1f*I? s[j][1]-2 and y > s[j][2]-2 and y < s[j][2]+2) then + x = math.random(var, var+19) + y = math.random(3, 12) + break + elseif i ~= j and (j == 10 or (i == 10 and j == 9)) then + s[i][1] = makePixelX(x, var) + s[i][2] = y + yes = false + end + end + end + end + end + if var == 25 then + ships = s + else + shipsE = s + end +end + +--Рисуем поле +function drawField() + g.setBackground(0xffffff) + g.fill(25,3,20,10," ") + g.setBackground(0xDDDDDD) + if args[1] ~= "fast" then + for i=1,10 do + local delta = math.fmod(i,2) + for j=1,5 do + g.fill(23+4*j-2*delta, i+2, 2, 1, " ") + end + end + end +end + +--Рисуем поле врага +function drawFieldE() + g.setBackground(0xffffff) + g.fill(3,3,20,10," ") + g.setBackground(0xDDDDDD) + if args[1] ~= "fast" then + for i=1,10 do + local delta = math.fmod(i,2) + for j=1,5 do + g.fill(1+4*j-2*delta, i+2, 2, 1, " ") + end + end + end +end + +--Кнопка готово после рандома +function drawButton2() + g.setBackground(colors.pink) + term.setCursor(13, 11) + term.write(" Готово ") +end + +--Кнопка рандома своих кораблей +function drawButton() + g.setBackground(colors.lime) + term.setCursor(3, 11) + term.write(" Авто ") +end + +--Очищаем пустое место +function clearShipsField() + g.setBackground(colors.lightGray) + g.fill(3,3,22,10," ") +end + +--Гуишечка +drawField() +drawShips() +drawButton() +g.setBackground(colors.lightGray) +g.setForeground(colors.black) +term.setCursor(3,13) +term.write("Установите корабли") + +--Цикл для установки своих корабликов вручную +local ship = 0 +local prevX = 0 +local shipCoords = {0,0} +local setting = true +local playing = true +local button2 = false +while setting do + local event, _, x, y = event.pull() + if event == "touch" then + if x > 2 and x < 13 and y == 11 then + setShipsAuto(25) + drawField() + clearShipsField() + drawShips() + drawButton() + drawButton2() + button2 = true + elseif button2 and x > 12 and x < 24 and y == 11 then + setting = false + break + elseif x > w-4 and x < w and y == 1 then + setting = false + playing = false + break + end + elseif event == "drag" then + if ship == 0 then + for i=1,10 do + if x > ships[i][1] and x < ships[i][1]+ships[i][3] and y == ships[i][2] then + ship = i + shipCoords[1] = ships[i][1] + shipCoords[2] = ships[i][2] + break + end + end + else + ships[ship][1] = ships[ship][1] + x - prevX + ships[ship][2] = y + if ships[ship][1] > 2 and ships[ship][1]+ships[ship][3]-1 < 45 and y > 2 and y < 13 then + drawField() + clearShipsField() + drawShips() + drawButton() + end + end + prevX = x + elseif event == "drop" then + if ship > 0 then + if ships[ship][1] < 25 or ships[ship][1]+ships[ship][3]-1 > 45 or y < 3 or y > 13then + ships[ship][1] = shipCoords[1] + ships[ship][2] = shipCoords[2] + end + for i=1,10 do + if i ~= ship and (ships[ship][1] < ships[i][1]+ships[i][3]+1 and ships[ship][1]+ships[ship][3]-1 > ships[i][1]-2 and ships[ship][2] > ships[i][2]-2 and ships[ship][2] < ships[i][2]+2) then + ships[ship][1] = shipCoords[1] + ships[ship][2] = shipCoords[2] + break + end + end + ships[ship][1] = makePixelX(ships[ship][1], 25) + end + ship = 0 + drawField() + clearShipsField() + drawShips() + drawButton() + for i=1,10 do + if ships[i][1] < 25 then + break + elseif i == 10 then + setting = false + break + end + end + end +end + +--Следующий цикл для игры +setShipsAuto(3) +drawFieldE() +g.setBackground(colors.lightGray) +g.setForeground(colors.black) +term.setCursor(3,13) +term.write("Противник ") +term.setCursor(25,13) +term.write("Вы") +g.setBackground(colors.magenta) +g.fill(23, 3, 2, 10, " ") +while playing do + local event, _, x, y = event.pull() + if event == "touch" then + if shots < 20 and shotsE2 < 20 and x > 2 and x < 23 and y > 2 and y < 13 then + x = makePixelX(x, 3) + for i=1,10 do + if x > shipsE[i][1]-1 and x < shipsE[i][1]+shipsE[i][3] and y == shipsE[i][2] then + shots = shots + 1 + g.setBackground(colors.red) + break + end + g.setBackground(colors.blue) + end + g.fill(x, y, 2, 1, " ") + local yes = true + local xE, yE = 0, 0 + while yes do + xE = makePixelX(math.random(25,44), 3) + yE = math.random(3,12) + for i=1,#shotsE do + if xE == shotsE[i][1] and yE == shotsE[i][2] then + break + elseif i == #shotsE then + yes = false + break + end + end + end + table.insert(shotsE, {makePixelX(xE, 3), yE}) + if args[2] ~= "notime" then + g.setBackground(colors.purple) + g.fill(23, 3, 2, 10, " ") + os.sleep(math.floor(math.random(2))-0.5) + g.setBackground(colors.magenta) + g.fill(23, 3, 2, 10, " ") + end + for i=1,10 do + if xE > ships[i][1]-1 and xE < ships[i][1]+ships[i][3] and yE == ships[i][2] then + shotsE2 = shotsE2 + 1 + g.setBackground(colors.red) + break + end + g.setBackground(colors.blue) + end + g.fill(xE, yE, 2, 1, " ") + if shots == 20 or shotsE2 == 20 then + g.setBackground(colors.lightGray) + g.fill(2, 3, 43, 12, " ") + g.setBackground(colors.white) + g.fill(15, 5, 16, 3, " ") + + if shots == 20 then + term.setCursor(20, 6) + term.write("Победа") + elseif shotsE2 == 20 then + term.setCursor(18, 6) + term.write("Поражение") + end + end + elseif x > w-4 and x < w and y == 1 then + playing = false + break + end + end +end + +--При выходе +g.setForeground(colors.white) +g.setBackground(colors.black) +term.clear() +g.setResolution(g.maxResolution()) \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Battleship.app/Resources/About/Russian.txt b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Battleship.app/Resources/About/Russian.txt new file mode 100755 index 00000000..76fbd88d --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Battleship.app/Resources/About/Russian.txt @@ -0,0 +1 @@ +Популярная игра "Морской бой", написанная товарищем Nezn с форума ComputerCraft.ru. Рассчитана на одного игрока, вам предстоит захватывающее сражение с флотом противника, контролируемым ИИ на Lua. \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Battleship.app/Resources/Icon.pic b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Battleship.app/Resources/Icon.pic new file mode 100755 index 0000000000000000000000000000000000000000..c7304d2780f42f3c1b44f8d7a6d259f1bf73d64b GIT binary patch literal 106 zcmXZUu?>JQ3`9|%V>=#$U67Eds2B(-Go(SR75Xd!Mxndn-QVVQF&BPFdMzN{!6|9J mkrXWEyDv|ze>Xav0+~O6A-Zg literal 0 HcmV?d00001 diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/BufferDemo.app/Main.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/BufferDemo.app/Main.lua new file mode 100755 index 00000000..e0b0f2a9 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/BufferDemo.app/Main.lua @@ -0,0 +1,160 @@ + +local buffer = require("doubleBuffering") +local event = require("event") +local image = require("image") + +local currentBackground = 0x990000 +local risovatKartinku = true +local showPanel = true +local transparency = 0.25 +local xWindow, yWindow = 5, 5 + +local fon = image.load("MineOS/Applications/BufferDemo.app/Resources/Wallpaper.pic") + +buffer.flush() +local bufferWidth, bufferHeight = buffer.getResolution() + +local function drawBackground() + --Заполним весь наш экран цветом фона 0x262626, цветом текста 0xFFFFFF и символом " " + if not risovatKartinku then + buffer.square(1, 1, bufferWidth, bufferHeight, currentBackground, 0xFFFFFF, " ") + else + buffer.image(1, 1, fon) + end +end + +--Создаем переменные с координатами начала и размерами нашего окна +local width, height = 82, 25 + +-- local cykaPicture = image.load("System/OS/Icons/Steve.pic") +-- local cykaPicture2 = image.load("System/OS/Icons/Love.pic") + +local function drawWindow(x, y) + + --Тени + local shadowTransparency = 0.6 + buffer.square(x + width, y + 1, 2, height, 0x000000, 0xFFFFFF, " ", shadowTransparency) + buffer.square(x + 2, y + height, width - 2, 1, 0x000000, 0xFFFFFF, " ", shadowTransparency) + + --Создаем прозрачную часть окна, где отображается "Избранное" + buffer.square(x, y, 20, height, 0xFFFFFF, 0xFFFFFF, " ", transparency) + + --Создаем непрозрачную часть окна для отображения всяких файлов и т.п. + buffer.square(x + 20, y, width - 20, height, 0xFFFFFF, 0xFFFFFF, " ") + + --Создаем три более темных полоски вверху окна, имитируя объем + buffer.square(x, y, width, 1, 0xDDDDDD, 0xFFFFFF, " ") + buffer.square(x, y + 1, width, 1, 0xCCCCCC, 0xFFFFFF, " ") + buffer.square(x, y + 2, width, 1, 0xBBBBBB, 0xFFFFFF, " ") + + --Рисуем заголовок нашего окошка + buffer.text(x + 30, y, 0x000000, "Тестовое окно") + + --Создаем три кнопки в левом верхнем углу для закрытия, разворачивания и сворачивания окна + buffer.set(x + 1, y, 0xDDDDDD, 0xFF4940, "⬤") + buffer.set(x + 3, y, 0xDDDDDD, 0xFFB640, "⬤") + buffer.set(x + 5, y, 0xDDDDDD, 0x00B640, "⬤") + + --Рисуем элементы "Избранного" чисто для демонстрации + buffer.text(x + 1, y + 3, 0x000000, "Избранное") + for i = 1, 6 do buffer.text(x + 2, y + 3 + i, 0x555555, "Вариант " .. i) end + + --Рисуем "Файлы" в виде желтых квадратиков. Нам без разницы, а выглядит красиво + for j = 1, 3 do + for i = 1, 5 do + local xPos, yPos = x + 22 + i*12 - 12, y + 4 + j*7 - 7 + buffer.square(xPos, yPos, 8, 4, 0xFFFF80, 0xFFFFFF, " ") + buffer.text(xPos, yPos + 5, 0x262626, "Файл " .. i .. "x" .. j) + end + end + + --Ну, и наконец рисуем голубой скроллбар справа + buffer.square(x + width - 1, y + 3, 1, height - 3, 0xBBBBBB, 0xFFFFFF, " ") + buffer.square(x + width - 1, y + 3, 1, 4, 0x3366CC, 0xFFFFFF, " ") + + --Изображения! + -- buffer.image(x + 23, y + 6, cykaPicture) + -- buffer.image(x + 33, y + 12, cykaPicture2) + + if showPanel then + xPos, yPos = x, y + height + 2 + buffer.square(xPos, yPos, width, 10, 0xFFFFFF, 0xFFFFFF, " ", transparency) + + --Тень + buffer.square(xPos + width, yPos + 1, 2, 10, 0x000000, 0xFFFFFF, " ", shadowTransparency) + buffer.square(xPos + 2, yPos + 10, width - 2, 1, 0x000000, 0xFFFFFF, " ", shadowTransparency) + + yPos = yPos + 1 + xPos = xPos + 2 + buffer.text(xPos + 2, yPos, 0x262626, "Клик левой кнопкой мыши: изменить позицию окошка"); yPos = yPos + 1 + buffer.text(xPos + 2, yPos, 0x262626, "Клик правой кнопкой: нарисовать еще одно такое же окошко"); yPos = yPos + 1 + buffer.text(xPos + 2, yPos, 0x262626, "Колесо мыши: изменить прозрачность окна"); yPos = yPos + 2 + buffer.text(xPos + 2, yPos, 0x262626, "Space: переключить фон между картинкой и статичным цветом"); yPos = yPos + 1 + buffer.text(xPos + 2, yPos, 0x262626, "Shift: изменить цвет фона на рандомный"); yPos = yPos + 1 + buffer.text(xPos + 2, yPos, 0x262626, "Tab: включить или отключить данную информационную панель"); yPos = yPos + 1 + buffer.text(xPos + 2, yPos, 0x262626, "Enter: выйти отсудова на хер"); yPos = yPos + 1 + end +end + +drawBackground() +drawWindow(xWindow, yWindow) +buffer.draw() + +while true do + local e = {event.pull()} + if e[1] == "touch" or e[1] == "drag" then + if e[5] == 0 then + drawBackground() + xWindow, yWindow = e[3], e[4] + drawWindow(xWindow, yWindow) + buffer.draw() + else + xWindow, yWindow = e[3], e[4] + drawWindow(xWindow, yWindow) + buffer.draw() + end + elseif e[1] == "key_down" then + if e[4] == 42 then + currentBackground = math.random(0x000000, 0xFFFFFF) + drawBackground() + drawWindow(xWindow, yWindow) + buffer.draw() + elseif e[4] == 28 then + buffer.square(1, 1, bufferWidth, bufferHeight, 0x262626, 0xFFFFFF, " ") + buffer.draw() + return + elseif e[4] == 57 then + risovatKartinku = not risovatKartinku + drawBackground() + drawWindow(xWindow, yWindow) + buffer.draw() + elseif e[4] == 15 then + showPanel = not showPanel + drawBackground() + drawWindow(xWindow, yWindow) + buffer.draw() + end + elseif e[1] == "scroll" then + if e[5] == 1 then + if transparency > 0.05 then + transparency = transparency - 0.05 + drawBackground() + drawWindow(xWindow, yWindow) + buffer.draw() + end + else + if transparency < 1 then + transparency = transparency + 0.05 + drawBackground() + drawWindow(xWindow, yWindow) + buffer.draw() + end + end + end +end + + + + + + diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/BufferDemo.app/Resources/About/Russian.txt b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/BufferDemo.app/Resources/About/Russian.txt new file mode 100755 index 00000000..1d721885 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/BufferDemo.app/Resources/About/Russian.txt @@ -0,0 +1 @@ +Программа-демонстратор возможностей нашей библиотеки двойной буферизации изображения. Вся ОС работает именно на этой библиотеке. \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/BufferDemo.app/Resources/Icon.pic b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/BufferDemo.app/Resources/Icon.pic new file mode 100755 index 0000000000000000000000000000000000000000..aa3c658a700e3d3d3879d6e027bb175c2f2fe3cc GIT binary patch literal 108 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 countOfDays then break end + if i >= firstDay then startDrawing = true end + if startDrawing then gpu.set(xPos, yPos, tostring(counter)); counter = counter + 1 end + xPos = xPos + constants.xSpaceBetweenNumbers + 2 + end + yPos = yPos + constants.ySpaceBetweenNumbers + 1 + end +end + +--Получить номер следующего дня +local function getNextDay(day) + if day < 7 then + return (day + 1) + else + return 1 + end +end + +--Просчитать данные о годе +local function calculateYear(year, dayOf1Jan) + local massivGoda = {} + local visokosniy = visokosniy(year) + + local firstDayPosition = dayOf1Jan + + --Получаем количество дней в каждом месяце + for month = 1, 12 do + --Создаем подмассив месяца в массиве года + massivGoda[month] = {} + --Если это февраль + if month == 2 then + --Если год високосный + if visokosniy then + massivGoda[month].countOfDays = 29 + massivGoda[month].firstDayPosition = firstDayPosition + firstDayPosition = getNextDay(firstDayPosition) + --Если не високосный + else + massivGoda[month].countOfDays = 28 + massivGoda[month].firstDayPosition = firstDayPosition + end + --Если не февраль + else + massivGoda[month].countOfDays = countOfDays[month] + massivGoda[month].firstDayPosition = firstDayPosition + for i = 1, monthDateMove[month] do + firstDayPosition = getNextDay(firstDayPosition) + end + end + end + + return massivGoda +end + +--Получить день недели первого января указанного года +local function polu4itDenNedeliPervogoJanvarja(year, debug) + local den = 0 + + local difference = math.abs(year - 1010) + local koli4estvoVisokosnih + + if difference % 4 == 0 then + koli4estvoVisokosnih = difference / 4 + elseif difference % 4 == 1 then + koli4estvoVisokosnih = math.floor(difference / 4) + elseif difference % 4 == 2 then + koli4estvoVisokosnih = math.floor(difference / 4) + elseif difference % 4 == 3 then + koli4estvoVisokosnih = math.floor(difference / 4) + 1 + end + + local sdvig = difference + koli4estvoVisokosnih + + if sdvig % 7 == 0 then + den = 1 + else + den = sdvig % 7 + 1 + end + + if debug then + print("Год: "..year) + print("Разница в годах: "..difference) + print("Кол-во високосных: "..koli4estvoVisokosnih) + print("Сдвиг по дням: "..sdvig) + print("День недели: "..den) + print(" ") + end + + return den +end + +--Нарисовать календарь +local function drawCalendar(xPos, yPos, year) + ecs.square(xPos, yPos, 120, 48, constants.backgroundColor) + --Получаем позицию первого января указанного года + local janFirst = polu4itDenNedeliPervogoJanvarja(year) + --Получаем массив года + local massivGoda = calculateYear(year, janFirst) + + --Перебираем массив года + for i = 1, #massivGoda do + --Рисуем месяц + drawMonth(xPos, yPos, massivGoda[i].firstDayPosition, massivGoda[i].countOfDays, year, i) + --Корректируем коорды + xPos = xPos + constants.xSpaceBetweenMonths + 27 + if i % 4 == 0 then xPos = 3; yPos = yPos + constants.ySpaceBetweenMonths + 15 end + end +end + +local function drawSymbol(x, y, symbol) + local xPos, yPos = x, y + for j = 1, #numbers[symbol] do + xPos = x + for i = 1, #numbers[symbol][j] do + if numbers[symbol][j][i] ~= 0 then + gpu.set(xPos, yPos, " ") + end + xPos = xPos + 2 + end + yPos = yPos + 1 + end +end + +local function drawYear(x, y, year) + year = tostring(year) + for i = 1, #year do + drawSymbol(x, y, string.sub(year, i, i)) + x = x + 8 + end +end + +local next, prev + +local function drawInfo() + local xPos, yPos = 127, 4 + ecs.square(xPos, yPos, 30, 5, constants.backgroundColor) + gpu.setBackground(constants.bigNumberColor) + drawYear(xPos, yPos, constants.programYear) + yPos = yPos + 6 + + local name = "Следующий год"; newObj("Buttons", name, ecs.drawButton(xPos, yPos, 30, 3, name, 0xDDDDDD, 0x262626)); yPos = yPos + 4 + name = "Предыдущий год"; newObj("Buttons", name, ecs.drawButton(xPos, yPos, 30, 3, name, 0xDDDDDD, 0x262626)); yPos = yPos + 4 + name = "Выйти"; newObj("Buttons", name, ecs.drawButton(xPos, yPos, 30, 3, name, 0xDDDDDD, 0x262626)); yPos = yPos + 4 + +end + +local function drawAll() + --Очищаем экран + ecs.square(1, 1, xMax, yMax, constants.backgroundColor) + --Рисуем календарик + drawCalendar(3, 2, constants.programYear) + --Рисуем парашу + drawInfo() +end + +-------------------------------------------------------------------------------------------------------------------- + +if xMax < 150 then error("This program requires Tier 3 GPU and Tier 3 Screen.") end +--Запоминаем старое разрешение экрана +local xOld, yOld = gpu.getResolution() +--Ставим максимальное +gpu.setResolution(xMax, yMax) +--Получаем данные о текущей дате (os.date выдает неверную дату и месяц, забавно) +constants.currentDay, constants.currentMonth, constants.currentYear = ecs.getHostTime(2) +constants.programDay, constants.programMonth, constants.programYear = constants.currentDay, constants.currentMonth, constants.currentYear +--Рисуем все +drawAll() + +while true do + local e = {event.pull()} + if e[1] == "touch" then + for key in pairs(obj["Buttons"]) do + if ecs.clickedAtArea(e[3], e[4], obj["Buttons"][key][1], obj["Buttons"][key][2], obj["Buttons"][key][3], obj["Buttons"][key][4]) then + ecs.drawButton(obj["Buttons"][key][1], obj["Buttons"][key][2], 30, 3, key, constants.weekendColor, constants.currentDayColor) + os.sleep(0.2) + + if key == "Следующий год" then + constants.programYear = constants.programYear + 1 + elseif key == "Предыдущий год" then + constants.programYear = constants.programYear - 1 + elseif key == "Выйти" then + gpu.setResolution(xOld, yOld) + ecs.prepareToExit() + return + end + + drawInfo() + drawCalendar(3, 2, constants.programYear) + + break + end + end + end +end + + + + + + + + diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Calendar.app/Resources/About/Russian.txt b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Calendar.app/Resources/About/Russian.txt new file mode 100755 index 00000000..f29f8362 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Calendar.app/Resources/About/Russian.txt @@ -0,0 +1 @@ +Calendar - простая программа, не нуждающаяся в особом представлении. Красивый интерфейс позволяет легко узнать день недели конкретной даты любого года. \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Calendar.app/Resources/Icon.pic b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Calendar.app/Resources/Icon.pic new file mode 100755 index 0000000000000000000000000000000000000000..fe7934661de21ea863ed5e2a2342e71e2fd95c6e GIT binary patch literal 165 zcmXYpxeb6o3!O#Bz++aOvPcu(THD45b$gPhqW-6!;caA E07= 0 then + if distance > distanceLimit then distance = distanceLimit end + + color = currentPalette[(#currentPalette + 1) - math.ceil(distance / (distanceLimit / #currentPalette))] + + buffer.set(xPos, yPos, color, 0x000000, " ") + buffer.set(xPos + 1, yPos, color, 0x000000, " ") + else + buffer.set(xPos, yPos, 0x000000, 0x000000, " ") + buffer.set(xPos + 1, yPos, 0x000000, 0x000000, " ") + end + + percent = (x * y) / (widthOfImage * heightOfImage) + xPos = xPos + 2 + end + xPos = x + yPos = yPos + 1 + end + + ecs.drawOldPixels(oldPixels) +end + +local function drawDistanceMeter() + local width = 4 + local xPos, yPos = widthOfImage - 3 - width, 3 + buffer.square(xPos, yPos, width + 2, #currentPalette * 2 + 2, 0x000000, 0x000000, " ") + yPos = yPos + 1 + xPos = xPos + 1 + for i = #currentPalette, 1, -1 do + buffer.square(xPos, yPos, width, 2, currentPalette[i], 0x000000, " ") + yPos = yPos + 2 + end +end + +local xOld, yOld = gpu.getResolution() +gpu.setResolution(100, 50) +buffer.flush() + +gui() +ecs.square(1, 2, widthOfImage, heightOfImage - 1, 0x000000) +buffer.square(1, 2, widthOfImage, heightOfImage - 1, 0x000000, 0x000000, " ") +capture(1, 1) +drawDistanceMeter() +buffer.draw() +gui() + +while true do + local e = {event.pull()} + if e[1] == "touch" then + for key in pairs(topObjects) do + if ecs.clickedAtArea(e[3], e[4], topObjects[key][1], topObjects[key][2], topObjects[key][3], topObjects[key][4]) then + currentTopObject = topObjects[key][5] + gui() + + if key == "Камера" then + + local action = context.menu(topObjects[key][1] - 1, 2, {"О программе"}, {"Выход"}) + + if action == "О программе" then + + local text = "Эта программа является тестом библиотеки двойной буферизации изображения, написана для проверки адекватности нескольких функций. Сама идея сперта у какого-то крутого парня с форума CC, однако немного доработана в GUI-плане. Такие дела." + + ecs.universalWindow("auto", "auto", 36, 0xeeeeee, true, {"EmptyLine"}, {"CenterText", 0x000000, "О программе \"Камера\""}, {"EmptyLine"}, {"TextField", 9, 0xFFFFFF, 0x000000, 0xaaaaaa, ecs.colors.blue, text}, {"EmptyLine"}, {"Button", {0x999999, 0xffffff, "OK"}}) + + elseif action == "Выход" then + gpu.setResolution(xOld, yOld) + ecs.prepareToExit() + return + end + + elseif key == "Сделать снимок" then + capture(1, 1) + drawDistanceMeter() + buffer.draw() + gui() + elseif key == "Параметры рендера" then + + local action = context.menu(topObjects[key][1] - 1, 2, {"Масштаб"}, {"Дальность рейкастинга"}) + + if action == "Масштаб" then + local data = ecs.universalWindow("auto", "auto", 36, 0xeeeeee, true, {"EmptyLine"}, {"CenterText", 0x000000, "Изменить масштаб:"}, {"EmptyLine"}, {"Slider", 0x262626, 0x880000, 1, 100, 100, "", "%"}, {"EmptyLine"}, {"Button", {0x999999, 0xffffff, "OK"}}) + + local part = (0.04 - 0.01) / 100 + local percent = part * data[1] + + step = percent + from = step * 25 + + capture(1, 1) + drawDistanceMeter() + buffer.draw() + gui() + elseif action == "Дальность рейкастинга" then + local data = ecs.universalWindow("auto", "auto", 36, 0xeeeeee, true, {"EmptyLine"}, {"CenterText", 0x000000, "Изменить дальность:"}, {"EmptyLine"}, {"Slider", 0x262626, 0x880000, 10, 36, distanceLimit, "", " блоков"}, {"EmptyLine"}, {"Button", {0x999999, 0xffffff, "OK"}}) + + distanceLimit = data[1] + + capture(1, 1) + drawDistanceMeter() + buffer.draw() + gui() + end + + elseif key == "Палитра" then + + local action = context.menu(topObjects[key][1] - 1, 2, {"Черно-белая"}, {"Термальная"}) + + if action == "Черно-белая" then + currentPalette = grayScale + capture(1, 1) + drawDistanceMeter() + buffer.draw() + gui() + elseif action == "Термальная" then + currentPalette = rainbow + capture(1, 1) + drawDistanceMeter() + buffer.draw() + gui() + end + end + + currentTopObject = 0 + gui() + break + end + end + end +end + + + + + + + + + + + + diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Camera.app/Resources/About/Russian.txt b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Camera.app/Resources/About/Russian.txt new file mode 100755 index 00000000..23540c7b --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Camera.app/Resources/About/Russian.txt @@ -0,0 +1 @@ +Программа-рейкастер, делающая снимки местности с помощью камеры из мода Computronix. Разумеется, данный мод должен быть установлен для работы. \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Camera.app/Resources/Icon.pic b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Camera.app/Resources/Icon.pic new file mode 100755 index 0000000000000000000000000000000000000000..09876074273877dd72b601c6343f72e3ed78ee77 GIT binary patch literal 187 zcmXYryA1+C3`BSA{RI?371sa~646EUvjH_25WoDc9j*aa!m!%N&rkMf=ELcjYeWb; zw1 then + local x,y,z=tSnow[i].x+math.random(-1, 1), tSnow[i].y-1, tSnow[i].z+math.random(-1, 1) + if x<1 then x=1 end + if x>46 then x=46 end + if z<1 then z=1 end + if z>46 then z=46 end + c=hologram.get(x, y, z) + if c==0 or c==1 then + hologram.set(tSnow[i].x, tSnow[i].y, tSnow[i].z, 0) + tSnow[i].x, tSnow[i].y, tSnow[i].z=x,y,z + hologram.set(x, y, z, 1) + i=i+1 + else + table.remove(tSnow,i) + end + else + table.remove(tSnow,i) + end + os.sleep(snowMode) + end +end + +ecs.info("auto", "auto", "", "Счастливого нового года!") + +hologram.clear() +hologram.setScale(scale) +hologram.setRotationSpeed(rotationMode, 0, 23, 0) + +spruce() +while 1 do + gen_snow() + falling_snow() +end \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/ChristmasTree.app/Resources/About/Russian.txt b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/ChristmasTree.app/Resources/About/Russian.txt new file mode 100755 index 00000000..b0f5477c --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/ChristmasTree.app/Resources/About/Russian.txt @@ -0,0 +1 @@ +Красивая новогодняя программа, написанная разработчиком Doob, сотворяющая атмосферу праздника в любом месте, где бы вы ни находились. \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/ChristmasTree.app/Resources/Icon.pic b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/ChristmasTree.app/Resources/Icon.pic new file mode 100755 index 0000000000000000000000000000000000000000..ac91b1777a6ecb3089b553e9b68394087e4b1ff4 GIT binary patch literal 152 zcmXZUu?+%23(5sulFa}MteLplAwlH;KhzT9B( RW??z*ln 0 then + if ecs.clickedAtArea(e2[3], e2[4], buttons["*"][1], buttons["*"][2], buttons["*"][3], buttons["*"][4]) then + pressButton(buttons["*"][1], buttons["*"][2], "*") + return true + end + end + return false +end + +local function redstone(go) + if go then + rs.setOutput(sides.top, 15) + local goexit = waitForExit() + rs.setOutput(sides.top, 0) + rs.setOutput(sides.bottom, 0) + return goexit + else + rs.setOutput(sides.bottom, 15) + os.sleep(2) + rs.setOutput(sides.top, 0) + rs.setOutput(sides.bottom, 0) + end + return false +end + +------------------------------------------------------------------------------------------------------------ + +ecs.prepareToExit(colors.background) +loadConfig() + +local oldWidth, oldHeight = gpu.getResolution() +gpu.setResolution(34, 17) +xSize, ySize = 34, 17 + +drawAll() +infoPanel("Введите пароль", colors.borders, colors.background) + +while true do + local e = {event.pull()} + if e[1] == "touch" then + for key in pairs(buttons) do + if ecs.clickedAtArea(e[3], e[4], buttons[key][1], buttons[key][2], buttons[key][3], buttons[key][4]) then + + if showKeyPresses then + pressButton(buttons[key][1], buttons[key][2], key) + end + + if key == "*" then + input = nil + infoPanel("Поле ввода очищено", colors.borders, colors.background) + elseif key == "#" then + drawAll() + if input == password then + infoPanel("Доступ разрешён!", ecs.colors.green, 0xFFFFFF) + local goexit = redstone(true) + for i = 1, #nicknames do + if nicknames[i] == e[6] then nicknames[i] = nil end + end + table.insert(nicknames, e[6]) + saveConfig() + + if goexit then + ecs.prepareToExit() + gpu.setResolution(oldWidth, oldHeight) + ecs.prepareToExit() + return + end + else + infoPanel("Доступ запрещён!", ecs.colors.red, 0xFFFFFF) + redstone(false) + end + infoPanel("Введите пароль", colors.borders, colors.background) + input = nil + else + input = (input or "") .. key + + infoPanel(input, colors.borders, colors.background, not showPassword) + end + drawAll() + + break + end + end + + if ecs.clickedAtArea(e[3], e[4], biometry[1], biometry[2], biometry[3], biometry[4]) then + visualScan(biometry[1], biometry[2] + 4, 0.08) + if checkNickname(e[6]) then + infoPanel("Привет, " .. e[6], ecs.colors.green, 0xFFFFFF) + redstone(true) + else + infoPanel("В доступе отказано!", ecs.colors.red, 0xFFFFFF) + redstone(false) + end + infoPanel("Введите пароль", colors.borders, colors.background) + drawAll() + end + end +end + + + + + + + + diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/CodeDoor.app/Resources/About/Russian.txt b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/CodeDoor.app/Resources/About/Russian.txt new file mode 100755 index 00000000..a2f0d277 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/CodeDoor.app/Resources/About/Russian.txt @@ -0,0 +1 @@ +Программа для защиты вашего жилища от нежелательных гостей. При старте вы указываете желаемый пароль, а как только вы или ваш друг его корректно ввели, система автоматически вносит вас в список доверенных пользователей, так что вы сможете пользоваться биометрической защитой, не тратя время на ввод пароля. Крайне красивая и полезная программа. \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/CodeDoor.app/Resources/Icon.pic b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/CodeDoor.app/Resources/Icon.pic new file mode 100755 index 0000000000000000000000000000000000000000..e17de0ae2579dbe8cbea4731b415996359d0507e GIT binary patch literal 115 zcmeZw_H<+8U}0on;80-HWMp7r(@tiHhdNKz9;i03;ZK(y$0qus{}p9C7#A{(YPd`)tBr z(&i5kK9B?{&2MAil$&2cTp5YAr>p{jwdeg`U@KleyuA|Hf{Gdaq4~R`B3aKwbFp2V SY#m_E=UC|}P%Z(P0{8(gG8y9l literal 0 HcmV?d00001 diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Control.app/Resources/Localization/English.lang b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Control.app/Resources/Localization/English.lang new file mode 100755 index 00000000..41fd3ad3 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Control.app/Resources/Localization/English.lang @@ -0,0 +1,24 @@ +{ + moduleDisk = "Disks", + moduleRAM = "RAM", + moduleEvent = "Events", + moduleLua = "Lua interpreter", + + diskLabel = "Disk label", + bootable = "Bootable", + of = "of", + free = "Free", + options = "Options", + arguments = "Arguments", + format = "Format", + execute = "Execute", + waitingEvents = "Waiting for events", + processingEnabled = "Event processing enabled", + luaInfo = { + {color = 0x880000, text = _G._VERSION .. " Copyright (C) 1994-2017 Lua.org, PUC-Rio"}, + "Type a statement and hit Enter to evaluate it", + "Prefix an expression with \"=\" to show its value", + " " + }, + luaType = "Type statement here" +} \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Control.app/Resources/Localization/Russian.lang b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Control.app/Resources/Localization/Russian.lang new file mode 100755 index 00000000..7ce06038 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Control.app/Resources/Localization/Russian.lang @@ -0,0 +1,24 @@ +{ + moduleDisk = "Диски", + moduleRAM = "Память", + moduleEvent = "События", + moduleLua = "Интерпретатор Lua", + + diskLabel = "Имя диска", + bootable = "Загрузочный", + of = "из", + free = "Свободно", + options = "Опции", + arguments = "Аргументы", + format = "Форматировать", + execute = "Выполнить", + waitingEvents = "Ожидание событий", + processingEnabled = "Обработка событий", + luaInfo = { + {color = 0x880000, text = _G._VERSION .. " Copyright (C) 1994-2017 Lua.org, PUC-Rio"}, + "Введите выражение и нажмите Enter, чтобы выполнить его", + "Используйте префикс \"=\", чтобы вывести значение переменной", + " ", + }, + luaType = "Введите выражение здесь" +} \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Control.app/Resources/Modules/1.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Control.app/Resources/Modules/1.lua new file mode 100644 index 00000000..396f6548 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Control.app/Resources/Modules/1.lua @@ -0,0 +1,189 @@ + +local args = {...} +local mainContainer, window, localization = args[1], args[2], args[3] + +require("advancedLua") +local component = require("component") +local computer = require("computer") +local GUI = require("GUI") +local buffer = require("doubleBuffering") +local image = require("image") +local MineOSPaths = require("MineOSPaths") +local MineOSInterface = require("MineOSInterface") +local unicode = require("unicode") +local syntax = require("syntax") + +---------------------------------------------------------------------------------------------------------------- + +local module = {} +module.name = localization.moduleLua + +---------------------------------------------------------------------------------------------------------------- + +module.onTouch = function() + window.contentContainer:deleteChildren() + + _G.component = require("component") + _G.computer = require("computer") + + local textBox = window.contentContainer:addChild(GUI.textBox(1, 1, window.contentContainer.width, window.contentContainer.height - 3, nil, 0x444444, localization.luaInfo, 1, 2, 1)) + textBox.scrollBarEnabled = true + + local placeholder = localization.luaType + local input = window.contentContainer:addChild(GUI.input(1, window.contentContainer.height - 2, window.contentContainer.width, 3, 0x2D2D2D, 0xE1E1E1, 0x666666, 0x2D2D2D, 0xE1E1E1, "", placeholder, true)) + + input.textDrawMethod = function(x, y, color, text) + if text == placeholder then + buffer.text(x, y, color, text) + else + syntax.highlightString(x, y, text, 2) + end + end + + local function add(data, color) + for line in data:gmatch("[^\n]+") do + local wrappedLine = string.wrap(line, textBox.textWidth) + for i = 1, #wrappedLine do + table.insert(textBox.lines, color and {color = color, text = wrappedLine[i]} or wrappedLine[i]) + end + end + + textBox:scrollToEnd() + -- local abc = " "; for i = 1, 30 do abc = abc .. "p " end; print(abc) + end + + input.historyEnabled = true + + input.autoComplete.colors.default.background = 0x4B4B4B + input.autoComplete.colors.default.text = 0xC3C3C3 + input.autoComplete.colors.default.textMatch = 0xFFFFFF + input.autoComplete.colors.selected.background = 0x777777 + input.autoComplete.colors.selected.text = 0xD2D2D2 + input.autoComplete.colors.selected.textMatch = 0xFFFFFF + input.autoComplete.scrollBar.colors.background = 0x666666 + input.autoComplete.scrollBar.colors.foreground = 0xAAAAAA + + input.autoCompleteVerticalAlignment = GUI.alignment.vertical.top + input.autoCompleteEnabled = true + input.autoCompleteMatchMethod = function() + local inputTextLength = unicode.len(input.text) + local left, right = 1, inputTextLength + for i = input.cursorPosition - 1, 1, -1 do + if not unicode.sub(input.text, i, i):match("[%w%_%.]+") then + left = i + 1 + break + end + end + for i = input.cursorPosition, inputTextLength do + if not unicode.sub(input.text, i, i):match("[%w%_%.]+") then + right = i - 1 + break + end + end + local cykaText = unicode.sub(input.text, left, right) + + local array = {} + local t = _G + if cykaText:match("^[%w%_%.]+$") then + local words = {} + for word in cykaText:gmatch("[^%.]+") do + table.insert(words, word) + end + local dotInEnd = unicode.sub(cykaText, -1, -1) == "." + local wordCount = #words - (dotInEnd and 0 or 1) + + for i = 1, wordCount do + if t[words[i]] and type(t[words[i]]) == "table" then + t = t[words[i]] + else + input.autoComplete:clear() + return + end + end + + input.autoComplete.resultLeft = unicode.sub(input.text, 1, left - 1) + if wordCount > 0 then + input.autoComplete.resultLeft = input.autoComplete.resultLeft .. table.concat(words, ".", 1, wordCount) .. "." + end + input.autoComplete.resultRight = unicode.sub(input.text, right + 1, -1) + + if dotInEnd then + for key, value in pairs(t) do + table.insert(array, tostring(key)) + end + input.autoComplete:match(array) + else + for key, value in pairs(t) do + table.insert(array, tostring(key)) + end + input.autoComplete:match(array, words[#words]) + end + elseif input.text == "" then + input.autoComplete.resultLeft = "" + input.autoComplete.resultRight = "" + for key, value in pairs(t) do + table.insert(array, tostring(key)) + end + input.autoComplete:match(array) + else + input.autoComplete:clear() + end + end + + input.autoComplete.onItemSelected = function() + input.text = input.autoComplete.resultLeft .. input.autoComplete.items[input.autoComplete.selectedItem] + input:setCursorPosition(unicode.len(input.text) + 1) + input.text = input.text .. input.autoComplete.resultRight + + if input.autoCompleteEnabled then + input.autoCompleteMatchMethod() + end + + mainContainer:draw() + buffer.draw() + end + + input.onInputFinished = function() + if input.text:len() > 0 then + local oldPrint = print + + print = function(...) + local args = {...} + for i = 1, #args do + if type(args[i]) == "table" then + args[i] = table.toString(args[i], true, 2, false, 2) + else + args[i] = tostring(args[i]) + end + end + add(table.concat(args, " ")) + end + + add("> " .. input.text, 0xAAAAAA) + + if input.text:match("^%=") then + input.text = "return " .. unicode.sub(input.text, 2, -1) + end + + local result, reason = load(input.text) + if result then + local data = {xpcall(result, debug.traceback())} + if data[1] then + print(table.unpack(data, 2)) + else + add(tostring(data[2]), 0x880000) + end + else + add(tostring(reason), 0x880000) + end + + print = oldPrint + + input.text = "" + end + end +end + +---------------------------------------------------------------------------------------------------------------- + +return module \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Control.app/Resources/Modules/2.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Control.app/Resources/Modules/2.lua new file mode 100644 index 00000000..e767408e --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Control.app/Resources/Modules/2.lua @@ -0,0 +1,107 @@ + +local args = {...} +local mainContainer, window, localization = args[1], args[2], args[3] + +require("advancedLua") +local component = require("component") +local computer = require("computer") +local GUI = require("GUI") +local buffer = require("doubleBuffering") +local image = require("image") +local MineOSPaths = require("MineOSPaths") +local MineOSInterface = require("MineOSInterface") + +---------------------------------------------------------------------------------------------------------------- + +local module = {} +module.name = localization.moduleDisk + +local HDDImage = image.load(MineOSPaths.icons .. "HDD.pic") +local floppyImage = image.load(MineOSPaths.icons .. "Floppy.pic") + +---------------------------------------------------------------------------------------------------------------- + +module.onTouch = function() + window.contentContainer:deleteChildren() + local container = window.contentContainer:addChild(GUI.container(1, 1, window.contentContainer.width, window.contentContainer.height)) + + local y = 2 + for address in component.list("filesystem") do + local proxy = component.proxy(address) + local isBoot = computer.getBootAddress() == proxy.address + local isReadOnly = proxy.isReadOnly() + + local diskContainer = container:addChild(GUI.container(1, y, container.width, 4)) + + local button = diskContainer:addChild(GUI.adaptiveRoundedButton(1, 3, 2, 0, 0x2D2D2D, 0xE1E1E1, 0x0, 0xE1E1E1, localization.options)) + button.onTouch = function() + local container = MineOSInterface.addUniversalContainer(mainContainer, localization.options) + local inputField = container.layout:addChild(GUI.input(1, 1, 36, 3, 0xE1E1E1, 0x666666, 0x666666, 0xE1E1E1, 0x2D2D2D, proxy.getLabel() or "", localization.diskLabel)) + inputField.onInputFinished = function() + if inputField.text and inputField.text:len() > 0 then + proxy.setLabel(inputField.text) + + container:delete() + module.onTouch() + end + end + + local formatButton = container.layout:addChild(GUI.button(1, 1, 36, 3, 0xC3C3C3, 0x2D2D2D, 0x666666, 0xE1E1E1, localization.format)) + formatButton.onTouch = function() + local list = proxy.list("/") + for i = 1, #list do + proxy.remove(list[i]) + end + + container:delete() + module.onTouch() + end + formatButton.disabled = isReadOnly + + local switch = container.layout:addChild(GUI.switchAndLabel(1, 1, 36, 8, 0x66DB80, 0x1E1E1E, 0xE1E1E1, 0xBBBBBB, localization.bootable .. ":", isBoot)).switch + switch.onStateChanged = function() + if switch.state then + computer.setBootAddress(proxy.address) + + container:delete() + module.onTouch() + end + end + + mainContainer:draw() + buffer.draw() + end + button.localX = diskContainer.width - button.width - 1 + + local width = diskContainer.width - button.width - 17 + local x = 13 + local spaceTotal = proxy.spaceTotal() + local spaceUsed = proxy.spaceUsed() + + diskContainer:addChild(GUI.image(3, 1, isReadOnly and floppyImage or HDDImage)) + diskContainer:addChild(GUI.label(x, 1, width, 1, 0x2D2D2D, (proxy.getLabel() or "Unknown") .. " (" .. (isBoot and (localization.bootable .. ", ") or "") .. proxy.address .. ")")) + diskContainer:addChild(GUI.progressBar(x, 3, width, 0x66DB80, 0xD2D2D2, 0xD2D2D2, spaceUsed / spaceTotal * 100, true)) + diskContainer:addChild(GUI.label(x, 4, width, 1, 0xBBBBBB, localization.free .. " " .. math.roundToDecimalPlaces((spaceTotal - spaceUsed) / 1024 / 1024, 2) .. " MB " .. localization.of .. " " .. math.roundToDecimalPlaces(spaceTotal / 1024 / 1024, 2) .. " MB")):setAlignment(GUI.alignment.horizontal.center, GUI.alignment.vertical.top) + + y = y + diskContainer.height + 1 + end + + container.eventHandler = function(mainContainer, object, eventData) + if eventData[1] == "scroll" then + if eventData[5] < 0 or container.children[1].localY < 2 then + for i = 1, #container.children do + container.children[i].localY = container.children[i].localY + eventData[5] + end + + mainContainer:draw() + buffer.draw() + end + elseif eventData[1] == "component_added" or eventData[1] == "component_removed" and eventData[3] == "filesystem" then + module.onTouch() + end + end +end + +---------------------------------------------------------------------------------------------------------------- + +return module \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Control.app/Resources/Modules/3.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Control.app/Resources/Modules/3.lua new file mode 100644 index 00000000..89e8a290 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Control.app/Resources/Modules/3.lua @@ -0,0 +1,146 @@ + +local args = {...} +local mainContainer, window, localization = args[1], args[2], args[3] + +require("advancedLua") +local component = require("component") +local computer = require("computer") +local GUI = require("GUI") +local buffer = require("doubleBuffering") +local image = require("image") +local MineOSPaths = require("MineOSPaths") +local MineOSInterface = require("MineOSInterface") +local unicode = require("unicode") + +---------------------------------------------------------------------------------------------------------------- + +local module = {} +module.name = localization.moduleRAM + +---------------------------------------------------------------------------------------------------------------- + +module.onTouch = function() + window.contentContainer:deleteChildren() + + local cykaPanel = window.contentContainer:addChild(GUI.panel(1, 1, 1, 1, 0xE1E1E1)) + + local mainLayout = window.contentContainer:addChild(GUI.layout(1, 1, window.contentContainer.width, window.contentContainer.height, 2, 1)) + mainLayout:setColumnWidth(1, GUI.sizePolicies.percentage, 0.3) + mainLayout:setColumnWidth(2, GUI.sizePolicies.percentage, 0.7) + mainLayout:setCellFitting(1, 1, true, true) + mainLayout:setCellFitting(2, 1, true, true) + + local tree = mainLayout:setCellPosition(1, 1, mainLayout:addChild(GUI.tree(1, 1, 1, 1, 0xE1E1E1, 0x3C3C3C, 0x3C3C3C, 0xAAAAAA, 0x3C3C3C, 0xFFFFFF, 0xBBBBBB, 0xAAAAAA, 0xC3C3C3, 0x444444, GUI.filesystemModes.both, GUI.filesystemModes.file))) + + local itemsLayout = mainLayout:setCellPosition(2, 1, mainLayout:addChild(GUI.layout(1, 1, 1, 1, 1, 2))) + itemsLayout:setRowHeight(1, GUI.sizePolicies.percentage, 0.6) + itemsLayout:setRowHeight(2, GUI.sizePolicies.percentage, 0.4) + itemsLayout:setCellFitting(1, 1, true, false, 4, 0) + itemsLayout:setCellFitting(1, 2, true, true) + + local infoLabel = itemsLayout:setCellPosition(1, 1, itemsLayout:addChild(GUI.label(1, 1, 1, 1, 0x3C3C3C, "nil")):setAlignment(GUI.alignment.horizontal.center, GUI.alignment.vertical.top)) + local argumentsInputField = itemsLayout:setCellPosition(1, 1, itemsLayout:addChild(GUI.input(1, 1, 1, 3, 0xFFFFFF, 0x666666, 0x888888, 0xFFFFFF, 0x262626, nil, localization.arguments))) + local executeButton = itemsLayout:setCellPosition(1, 1, itemsLayout:addChild(GUI.button(1, 1, 1, 3, 0x3C3C3C, 0xFFFFFF, 0x0, 0xFFFFFF, localization.execute))) + local outputTextBox = itemsLayout:setCellPosition(1, 2, itemsLayout:addChild(GUI.textBox(1, 1, 1, 1, 0xFFFFFF, 0x888888, {"nil"}, 1, 1, 0))) + + local function updateList(tree, t, definitionName, offset) + local list = {} + for key in pairs(t) do + table.insert(list, key) + end + + local i, expandables = 1, {} + while i <= #list do + if type(t[list[i]]) == "table" then + table.insert(expandables, list[i]) + table.remove(list, i) + else + i = i + 1 + end + end + + table.sort(expandables, function(a, b) return unicode.lower(tostring(a)) < unicode.lower(tostring(b)) end) + table.sort(list, function(a, b) return unicode.lower(tostring(a)) < unicode.lower(tostring(b)) end) + + for i = 1, #expandables do + local definition = definitionName .. expandables[i] + + tree:addItem(tostring(expandables[i]), definition, offset, true) + if tree.expandedItems[definition] then + updateList(tree, t[expandables[i]], definition, offset + 2) + end + end + + for i = 1, #list do + tree:addItem(tostring(list[i]), {key = list[i], value = t[list[i]]}, offset, false) + end + end + + local function out(text) + local wrappedLines = string.wrap(text, outputTextBox.width - 2) + for i = 1, #wrappedLines do + table.insert(outputTextBox.lines, wrappedLines[i]) + end + end + + tree.onItemExpanded = function() + tree.items = {} + updateList(tree, _G, "_G", 1) + end + + tree.onItemSelected = function() + local valueType = type(tree.selectedItem.value) + local valueIsFunction = valueType == "function" + + executeButton.disabled = not valueIsFunction + argumentsInputField.disabled = executeButton.disabled + + infoLabel.text = tostring(tree.selectedItem.key) .. " (" .. valueType .. ")" + outputTextBox.lines = {} + + if valueIsFunction then + out("nil") + else + out(tostring(tree.selectedItem.value)) + end + end + + executeButton.onTouch = function() + outputTextBox.lines = {} + + local data = "local method = ({...})[1]; return method(" .. (argumentsInputField.text or "") .. ")" + local success, reason = load(data) + if success then + local success, reason = pcall(success, tree.selectedItem.value) + if success then + if type(reason) == "table" then + local serialized = table.toString(reason, true, 2, false, 3) + for line in serialized:gmatch("[^\n]+") do + out(line) + end + else + out(tostring(reason)) + end + else + out("Failed to pcall loaded string \"" .. data .. "\": " .. reason) + end + else + out("Failed to load string \"" .. data .. "\": " .. reason) + end + + mainContainer:draw() + buffer.draw() + end + + + executeButton.disabled = true + argumentsInputField.disabled = true + executeButton.colors.disabled.background = 0x777777 + executeButton.colors.disabled.text = 0xD2D2D2 + + tree.onItemExpanded() +end + +---------------------------------------------------------------------------------------------------------------- + +return module \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Control.app/Resources/Modules/4.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Control.app/Resources/Modules/4.lua new file mode 100644 index 00000000..c0bd7ae9 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Control.app/Resources/Modules/4.lua @@ -0,0 +1,50 @@ + +local args = {...} +local mainContainer, window, localization = args[1], args[2], args[3] + +require("advancedLua") +local component = require("component") +local computer = require("computer") +local GUI = require("GUI") +local buffer = require("doubleBuffering") +local image = require("image") +local MineOSPaths = require("MineOSPaths") +local MineOSInterface = require("MineOSInterface") + +---------------------------------------------------------------------------------------------------------------- + +local module = {} +module.name = localization.moduleEvent + +---------------------------------------------------------------------------------------------------------------- + +module.onTouch = function() + window.contentContainer:deleteChildren() + + local container = window.contentContainer:addChild(GUI.container(1, 1, window.contentContainer.width, window.contentContainer.height)) + + local layout = container:addChild(GUI.layout(1, 1, container.width, window.contentContainer.height, 1, 1)) + layout:setCellAlignment(1, 1, GUI.alignment.horizontal.center, GUI.alignment.vertical.top) + layout:setCellMargin(1, 1, 0, 1) + + local textBox = layout:addChild(GUI.textBox(1, 1, container.width - 4, container.height - 4, nil, 0x888888, {localization.waitingEvents .. "..."}, 1, 0, 0)) + local switch = layout:addChild(GUI.switchAndLabel(1, 1, 27, 6, 0x66DB80, 0x1E1E1E, 0xFFFFFF, 0x2D2D2D, localization.processingEnabled .. ": ", true)).switch + + container.eventHandler = function(mainContainer, object, eventData) + if switch.state and eventData[1] then + local lines = table.concat(eventData, " ") + lines = string.wrap(lines, textBox.width) + for i = 1, #lines do + table.insert(textBox.lines, lines[i]) + end + textBox:scrollToEnd() + + mainContainer:draw() + buffer.draw() + end + end +end + +---------------------------------------------------------------------------------------------------------------- + +return module \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Finder.app/Main.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Finder.app/Main.lua new file mode 100644 index 00000000..2439c24b --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Finder.app/Main.lua @@ -0,0 +1,363 @@ + +local GUI = require("GUI") +local buffer = require("doubleBuffering") +local computer = require("computer") +local fs = require("filesystem") +local event = require("event") +local MineOSPaths = require("MineOSPaths") +local MineOSCore = require("MineOSCore") +local MineOSNetwork = require("MineOSNetwork") +local MineOSInterface = require("MineOSInterface") + +local args, options = require("shell").parse(...) + +------------------------------------------------------------------------------------------------------ + +local mainContainer, window = MineOSInterface.addWindow(MineOSInterface.filledWindow(1, 1, 88, 26, 0xF0F0F0)) + +local iconFieldYOffset = 2 +local scrollTimerID + +local favourites = { + {text = "Root", path = "/"}, + {text = "Desktop", path = MineOSPaths.desktop}, + {text = "Applications", path = MineOSPaths.applications}, + {text = "Pictures", path = MineOSPaths.pictures}, + {text = "System", path = MineOSPaths.system}, + {text = "Trash", path = MineOSPaths.trash}, +} +local resourcesPath = MineOSCore.getCurrentApplicationResourcesDirectory() +local favouritesPath = resourcesPath .. "Favourites.cfg" + +if fs.exists(favouritesPath) then + favourites = table.fromFile(favouritesPath) +else + table.toFile(favouritesPath, favourites) +end + +------------------------------------------------------------------------------------------------------ + +local workpathHistory = {} +local workpathHistoryCurrent = 0 + +local function updateFileListAndDraw() + window.iconField:updateFileList() + mainContainer:draw() + buffer.draw() +end + +local function workpathHistoryButtonsUpdate() + window.prevButton.disabled = workpathHistoryCurrent <= 1 + window.nextButton.disabled = workpathHistoryCurrent >= #workpathHistory +end + +local function addWorkpath(path) + workpathHistoryCurrent = workpathHistoryCurrent + 1 + table.insert(workpathHistory, workpathHistoryCurrent, path) + for i = workpathHistoryCurrent + 1, #workpathHistory do + workpathHistory[i] = nil + end + + workpathHistoryButtonsUpdate() + window.searchInput.text = "" + window.iconField.yOffset = iconFieldYOffset + window.iconField:setWorkpath(path) +end + +local function prevOrNextWorkpath(next) + if next then + if workpathHistoryCurrent < #workpathHistory then + workpathHistoryCurrent = workpathHistoryCurrent + 1 + end + else + if workpathHistoryCurrent > 1 then + workpathHistoryCurrent = workpathHistoryCurrent - 1 + end + end + + workpathHistoryButtonsUpdate() + window.iconField.yOffset = iconFieldYOffset + window.iconField:setWorkpath(workpathHistory[workpathHistoryCurrent]) + + updateFileListAndDraw() +end + +------------------------------------------------------------------------------------------------------ + +local function newSidebarItem(text, textColor, path) + local y = 1 + if #window.sidebarContainer.itemsContainer.children > 0 then + y = window.sidebarContainer.itemsContainer.children[#window.sidebarContainer.itemsContainer.children].localY + 1 + end + local object = window.sidebarContainer.itemsContainer:addChild(GUI.object(1, y, 1, 1)) + + object.text = text + object.textColor = textColor + object.path = path + + object.draw = function(object) + object.width = window.sidebarContainer.itemsContainer.width + + local textColor = object.textColor + if object.path == window.iconField.workpath then + textColor = 0xFFFFFF + buffer.square(object.x, object.y, object.width, 1, 0x3366CC, textColor, " ") + end + buffer.text(object.x + 1, object.y, textColor, string.limit(object.text, object.width - 1, "center")) + end + + object.eventHandler = function(mainContainer, object, eventData) + if eventData[1] == "touch" then + if object.onTouch then + object.onTouch(object, eventData) + end + end + end + + return object +end + +local function sidebarItemOnTouch(object, eventData) + if eventData[5] == 0 then + if fs.isDirectory(object.path) then + addWorkpath(object.path) + mainContainer:draw() + buffer.draw() + + updateFileListAndDraw() + end + else + + end +end + +local function updateSidebar() + window.sidebarContainer.itemsContainer:deleteChildren() + + window.sidebarContainer.itemsContainer:addChild(newSidebarItem("Favourites", 0x3C3C3C)) + for i = 1, #favourites do + window.sidebarContainer.itemsContainer:addChild(newSidebarItem(" " .. fs.name(favourites[i].text), 0x555555, favourites[i].path)).onTouch = sidebarItemOnTouch + end + + if MineOSCore.properties.network.enabled and MineOSNetwork.getProxyCount() > 0 then + window.sidebarContainer.itemsContainer:addChild(newSidebarItem(" ", 0x3C3C3C)) + window.sidebarContainer.itemsContainer:addChild(newSidebarItem("Network", 0x3C3C3C)) + + for proxy, path in fs.mounts() do + if proxy.network then + window.sidebarContainer.itemsContainer:addChild(newSidebarItem(" " .. MineOSNetwork.getProxyName(proxy), 0x555555, path .. "/")).onTouch = sidebarItemOnTouch + end + end + end + + window.sidebarContainer.itemsContainer:addChild(newSidebarItem(" ", 0x3C3C3C)) + + window.sidebarContainer.itemsContainer:addChild(newSidebarItem("Mounts", 0x3C3C3C)) + for proxy, path in fs.mounts() do + if path ~= "/" and not proxy.network then + window.sidebarContainer.itemsContainer:addChild(newSidebarItem(" " .. (proxy.getLabel() or fs.name(path)), 0x555555, path .. "/")).onTouch = sidebarItemOnTouch + end + end +end + +window.titlePanel = window:addChild(GUI.panel(1, 1, 1, 3, 0xDDDDDD)) + +window.prevButton = window:addChild(GUI.adaptiveRoundedButton(9, 2, 1, 0, 0xFFFFFF, 0x3C3C3C, 0x3C3C3C, 0xFFFFFF, "<")) +window.prevButton.onTouch = function() + prevOrNextWorkpath(false) +end +window.prevButton.colors.disabled.background = window.prevButton.colors.default.background +window.prevButton.colors.disabled.text = 0xCCCCCC + +window.nextButton = window:addChild(GUI.adaptiveRoundedButton(14, 2, 1, 0, 0xFFFFFF, 0x3C3C3C, 0x3C3C3C, 0xFFFFFF, ">")) +window.nextButton.onTouch = function() + prevOrNextWorkpath(true) +end +window.nextButton.colors.disabled = window.prevButton.colors.disabled + +window.sidebarContainer = window:addChild(GUI.container(1, 4, 20, 1)) +window.sidebarContainer.panel = window.sidebarContainer:addChild(GUI.panel(1, 1, window.sidebarContainer.width, 1, 0xFFFFFF, MineOSCore.properties.transparencyEnabled and 0.24)) +window.sidebarContainer.itemsContainer = window.sidebarContainer:addChild(GUI.container(1, 1, window.sidebarContainer.width, 1)) + +window.iconField = window:addChild( + MineOSInterface.iconField( + 1, 4, 1, 1, 2, 2, 0x3C3C3C, 0x3C3C3C, + MineOSPaths.desktop + ) +) + +window.iconField.launchers.directory = function(icon) + addWorkpath(icon.path) + updateFileListAndDraw() +end + +window.iconField.launchers.showPackageContent = function(icon) + addWorkpath(icon.path) + updateFileListAndDraw() +end + +window.iconField.launchers.showContainingFolder = function(icon) + addWorkpath(fs.path(MineOSCore.readShortcut(icon.path))) + updateFileListAndDraw() +end + +window.scrollBar = window:addChild(GUI.scrollBar(1, 4, 1, 1, 0xC3C3C3, 0x444444, iconFieldYOffset, 1, 1, 1, 1, true)) + +window.searchInput = window:addChild(GUI.input(1, 2, 36, 1, 0xFFFFFF, 0x666666, 0xAAAAAA, 0xFFFFFF, 0x2D2D2D, nil, "Search", true)) +window.searchInput.onInputFinished = function() + window.iconField.filenameMatcher = window.searchInput.text + window.iconField.fromFile = 1 + window.iconField.yOffset = iconFieldYOffset + + updateFileListAndDraw() +end + +local function updateScrollBar() + local shownFilesCount = #window.iconField.fileList - window.iconField.fromFile + 1 + + local horizontalLines = math.ceil(shownFilesCount / window.iconField.iconCount.horizontal) + local minimumOffset = 3 - (horizontalLines - 1) * (MineOSCore.properties.iconHeight + MineOSCore.properties.iconVerticalSpaceBetween) - MineOSCore.properties.iconVerticalSpaceBetween + + if window.iconField.yOffset > iconFieldYOffset then + window.iconField.yOffset = iconFieldYOffset + elseif window.iconField.yOffset < minimumOffset then + window.iconField.yOffset = minimumOffset + end + + if shownFilesCount > window.iconField.iconCount.total then + window.scrollBar.hidden = false + window.scrollBar.maximumValue = math.abs(minimumOffset) + window.scrollBar.value = math.abs(window.iconField.yOffset - iconFieldYOffset) + else + window.scrollBar.hidden = true + end +end + +local overrideUpdateFileList = window.iconField.updateFileList +window.iconField.updateFileList = function(...) + overrideUpdateFileList(...) + updateScrollBar() +end + +window.iconField.eventHandler = function(mainContainer, object, eventData) + if eventData[1] == "scroll" then + eventData[5] = eventData[5] * 2 + window.iconField.yOffset = window.iconField.yOffset + eventData[5] + + updateScrollBar() + + local delta = window.iconField.yOffset - window.iconField.iconsContainer.children[1].localY + for i = 1, #window.iconField.iconsContainer.children do + window.iconField.iconsContainer.children[i].localY = window.iconField.iconsContainer.children[i].localY + delta + end + + mainContainer:draw() + buffer.draw() + + if scrollTimerID then + event.cancel(scrollTimerID) + scrollTimerID = nil + end + + scrollTimerID = event.timer(0.3, function() + computer.pushSignal("Finder", "updateFileList") + end, 1) + elseif (eventData[1] == "MineOSCore" or eventData[1] == "Finder") and eventData[2] == "updateFileList" then + if eventData[1] == "MineOSCore" then + window.iconField.yOffset = iconFieldYOffset + end + updateFileListAndDraw() + end +end + +window.statusBar = window:addChild(GUI.object(1, 1, 1, 1)) +window.statusBar.draw = function(object) + buffer.square(object.x, object.y, object.width, object.height, 0xFFFFFF, 0x3C3C3C, " ") + buffer.text(object.x + 1, object.y, 0x3C3C3C, string.limit(("root/" .. window.iconField.workpath):gsub("/+$", ""):gsub("%/+", " ► "), object.width - 1, "start")) +end +window.statusBar.eventHandler = function(mainContainer, object, eventData) + if (eventData[1] == "component_added" or eventData[1] == "component_removed") and eventData[3] == "filesystem" then + updateSidebar() + + mainContainer:draw() + buffer.draw() + elseif eventData[1] == "MineOSNetwork" then + if eventData[2] == "updateProxyList" or eventData[2] == "timeout" then + updateSidebar() + + mainContainer:draw() + buffer.draw() + end + end +end +window.sidebarResizer = window:addChild(GUI.resizer(1, 4, 3, 5, 0xFFFFFF, 0x0)) + +local function calculateSizes(width, height) + window.sidebarContainer.height = height - 3 + + window.sidebarContainer.panel.width = window.sidebarContainer.width + window.sidebarContainer.panel.height = window.sidebarContainer.height + + window.sidebarContainer.itemsContainer.width = window.sidebarContainer.width + window.sidebarContainer.itemsContainer.height = window.sidebarContainer.height + + window.sidebarResizer.localX = window.sidebarContainer.width - 1 + window.sidebarResizer.localY = math.floor(window.sidebarContainer.localY + window.sidebarContainer.height / 2 - window.sidebarResizer.height / 2 - 1) + + window.backgroundPanel.width = width - window.sidebarContainer.width + window.backgroundPanel.height = height - 4 + window.backgroundPanel.localX = window.sidebarContainer.width + 1 + window.backgroundPanel.localY = 4 + + window.statusBar.localX = window.sidebarContainer.width + 1 + window.statusBar.localY = height + window.statusBar.width = window.backgroundPanel.width + + window.titlePanel.width = width + window.searchInput.width = math.floor(width * 0.25) + window.searchInput.localX = width - window.searchInput.width - 1 + + window.iconField.width = window.backgroundPanel.width + window.iconField.height = height + 4 + window.iconField.localX = window.backgroundPanel.localX + window.iconField.localY = window.backgroundPanel.localY + + window.scrollBar.localX = window.width + window.scrollBar.height = window.backgroundPanel.height + window.scrollBar.shownValueCount = window.scrollBar.height - 1 + + window.actionButtons:moveToFront() +end + +window.onResize = function(width, height) + calculateSizes(width, height) + window.iconField:updateFileList() +end + +window.sidebarResizer.onResize = function(mainContainer, object, eventData, dragWidth, dragHeight) + window.sidebarContainer.width = window.sidebarContainer.width + dragWidth + window.sidebarContainer.width = window.sidebarContainer.width >= 5 and window.sidebarContainer.width or 5 + calculateSizes(window.width, window.height) +end + +window.sidebarResizer.onResizeFinished = function() + window.iconField:updateFileList() +end + +local overrideMaximize = window.actionButtons.maximize.onTouch +window.actionButtons.maximize.onTouch = function() + window.iconField.yOffset = iconFieldYOffset + overrideMaximize() +end + +------------------------------------------------------------------------------------------------------ + +if options.o and args[1] and fs.isDirectory(args[1]) then + addWorkpath(args[1]) +else + addWorkpath("/") +end + +updateSidebar() +window:resize(window.width, window.height) + diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Finder.app/Resources/Favourites.cfg b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Finder.app/Resources/Favourites.cfg new file mode 100644 index 00000000..a1220e39 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Finder.app/Resources/Favourites.cfg @@ -0,0 +1 @@ +{[1]={["path"]="/",["text"]="Root"},[2]={["path"]="/MineOS/Desktop/",["text"]="Desktop"},[3]={["path"]="/MineOS/Applications/",["text"]="Applications"},[4]={["path"]="/MineOS/Pictures/",["text"]="Pictures"},[5]={["path"]="/MineOS/System/",["text"]="System"},[6]={["path"]="/MineOS/Trash/",["text"]="Trash"}} \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Finder.app/Resources/Icon.pic b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Finder.app/Resources/Icon.pic new file mode 100644 index 0000000000000000000000000000000000000000..3a4281b90d233405df321548c1d7890dbc60ceea GIT binary patch literal 155 zcmXAiy%B>@41+D%?`H%`cF7%=RIEdpA(Bha(LsW@_c(txdzN~d+#`5 z2$c3&ebGXsyM`k+4M5C+p)(wl-~dY{bM;8MsHa|6EQ0881r12jp(=yBWP3f?IhT9A NRP&cT1pXbO`~Wso7jXap literal 0 HcmV?d00001 diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/FlappyBird.app/Main.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/FlappyBird.app/Main.lua new file mode 100755 index 00000000..50affd38 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/FlappyBird.app/Main.lua @@ -0,0 +1,294 @@ +local image = require("image") +local buffer = require("doubleBuffering") +local keyboard = require("keyboard") +local bigLetters = require("bigLetters") +local fs = require("filesystem") +local serialization = require("serialization") +local ecs = require("ECSAPI") +local event = require("event") +local MineOSCore = require("MineOSCore") +local MineOSPaths = require("MineOSPaths") +--afaefa + +buffer.flush() +local bufferWidth, bufferHeight = buffer.getResolution() + +local config = { + FPS = 0.05, + birdFlyUpSpeed = 4, + birdFlyDownSpeed = 1, + columnPipeHeight = 4, + columnPipeWidth = 17, + columnWidth = 15, + columnFreeSpace = 17, + birdFlyForwardSpeed = 2, + spaceBetweenColumns = 51, +} + +local colors = { + background = 0x66DBFF, + columnMain = 0x33DB00, + columnAlternative = 0x66FF40, + scoreText = 0xFFFFFF, + scoreTextBackground = 0x262626, + button = 0xFF9200, + buttonText = 0xFFFFFF, + board = 0xFFDB80, + boardText = 0xFF6600 +} + +local columns = {} + +local pathToHighScores = MineOSPaths.applicationData .. "/FlappyBird/Scores.cfg" +local pathToFlappyImage = MineOSCore.getCurrentApplicationResourcesDirectory() .. "Flappy.pic" +local bird = image.load(pathToFlappyImage) +local xBird, yBird = 8, math.floor(bufferHeight / 2 - 3) +local birdIsAlive = true + +local scores = {} +local currentScore, currentUser = 0, 0 +local xScore, yScore = math.floor(bufferWidth / 2 - 6), math.floor(bufferHeight * 0.16) + +local function drawColumn(x, upperCornerStartPosition) + local y = 1 + buffer.square(x + 1, y, config.columnWidth, upperCornerStartPosition - config.columnPipeHeight, colors.columnMain, 0x0, " ") + buffer.square(x, upperCornerStartPosition - config.columnPipeHeight, config.columnPipeWidth, config.columnPipeHeight, colors.columnAlternative, 0x0, " ") + + y = upperCornerStartPosition + config.columnFreeSpace + buffer.square(x, y, config.columnPipeWidth, config.columnPipeHeight, colors.columnAlternative, 0x0, " ") + y = y + config.columnPipeHeight + buffer.square(x + 1, y, config.columnWidth, bufferHeight - y + 1, colors.columnMain, 0x0, " ") +end + +local function dieBirdDie() + if birdIsAlive then + bird = image.blend(bird, 0x880000, 0.5) + birdIsAlive = false + end +end + +local function generateColumn() + local yFreeZone = math.random(config.columnPipeHeight + 2, bufferHeight - config.columnPipeHeight - config.columnFreeSpace) + table.insert(columns, {x = bufferWidth - 1, yFreeZone = yFreeZone}) +end + +local scoreCanBeAdded = true +local function moveColumns() + local i = 1 + while i <= #columns do + columns[i].x = columns[i].x - 1 + + if (columns[i].x >= xBird and columns[i].x <= xBird + 13) then + if ((yBird >= columns[i].yFreeZone) and (yBird + 6 <= columns[i].yFreeZone + config.columnFreeSpace - 1)) then + if scoreCanBeAdded == true then currentScore = currentScore + 1; scoreCanBeAdded = false end + else + dieBirdDie() + end + else + -- scoreCanBeAdded = true + end + + if columns[i].x < -(config.columnPipeWidth) then + scoreCanBeAdded = true + table.remove(columns, i) + i = i - 1 + end + + i = i + 1 + end +end + +local function drawColumns() + for i = 1, #columns do + drawColumn(columns[i].x, columns[i].yFreeZone) + end +end + +local function drawBackground() + buffer.clear(colors.background) +end + +local function drawBird() + buffer.image(xBird, yBird, bird) +end + +local function drawBigCenterText(y, textColor, usePseudoShadow, text) + local width = bigLetters.getTextSize(text) + local x = math.floor(bufferWidth / 2 - width / 2) + + if usePseudoShadow then buffer.square(x - 2, y - 1, width + 4, 7, colors.scoreTextBackground, 0x0, " ") end + bigLetters.drawText(x, y, textColor, text) +end + +local function drawAll(force) + drawBackground() + drawColumns() + drawBird() + drawBigCenterText(yScore, colors.scoreText, true,tostring(currentScore)) + + buffer.draw(force) +end + +local function saveHighScores() + fs.makeDirectory(fs.path(pathToHighScores)) + local file = io.open(pathToHighScores, "w") + file:write(serialization.serialize(scores)) + file:close() +end + +local function loadHighScores() + if fs.exists(pathToHighScores) then + local file = io.open(pathToHighScores, "r") + scores = serialization.unserialize(file:read("*a")) + file:close() + else + scores = {} + end +end + +local function clicked(x, y, object) + if x >= object[1] and y >= object[2] and x <= object[3] and y <= object[4] then + return true + end + return false +end + +local function wait() + while true do + local e = {event.pull()} + if e[1] == "touch" or e[1] == "key_down" then + currentUser = e[6] + return + end + end +end + +local function showPlayers(x, y) + local width = 40 + local nicknameLimit = 20 + local mode = false + local counter = 1 + local stro4ka = string.rep(" ", nicknameLimit).."│"..string.rep(" ", width - nicknameLimit) + ecs.colorTextWithBack(x, y, 0xffffff, ecs.colors.blue, stro4ka) + gpu.set(x + 1, y, "Имя игрока") + gpu.set(x + nicknameLimit + 2, y, "Очки") + + for key, val in pairs(players) do + local color = 0xffffff + + if mode then + color = color - 0x222222 + end + + gpu.setForeground(0x262626) + gpu.setBackground(color) + gpu.set(x, y + counter, stro4ka) + gpu.set(x + 3, y + counter, ecs.stringLimit("end", key, nicknameLimit - 4)) + gpu.set(x + nicknameLimit + 2, y + counter, tostring(players[key][1])) + ecs.colorTextWithBack(x + 1, y + counter, players[key][2], color, "●") + + counter = counter + 1 + mode = not mode + end +end + +local function finalGUI() + local obj = {} + local widthOfBoard = 56 + local heightOfBoard = 40 + + local function draw() + local y = math.floor(bufferHeight / 2 - 19) + local x = math.floor(bufferWidth / 2 - widthOfBoard / 2) + + drawAll() + + buffer.square(x, y, widthOfBoard, heightOfBoard, colors.board, 0xFFFFFF, " ", 0.3) + + y = y + 2 + drawBigCenterText(y, colors.boardText, false, "score") + y = y + 8 + drawBigCenterText(y, 0xFFFFFF, true, tostring(currentScore)) + y = y + 8 + drawBigCenterText(y, colors.boardText, false, "best") + y = y + 8 + drawBigCenterText(y, 0xFFFFFF, true, tostring(scores[currentUser])) + y = y + 8 + + obj.retry = { buffer.button(x, y, widthOfBoard, 3, 0xFF6600, colors.buttonText, "Заново") }; y = y + 3 + -- obj.records = { buffer.button(x, y, widthOfBoard, 3, 0xFF9900, colors.buttonText, "Таблица рекордов") }; y = y + 3 + obj.exit = { buffer.button(x, y, widthOfBoard, 3, 0x262626, colors.buttonText, "Выход") }; y = y + 3 + + buffer.draw() + end + + draw() + + while true do + local e = {event.pull("touch")} + if clicked(e[3], e[4], obj.retry) then + buffer.button(obj.retry[1], obj.retry[2], widthOfBoard, 3, 0xFFFFFF, 0x000000, "Заново") + buffer.draw() + os.sleep(0.2) + currentScore = 0 + birdIsAlive = true + scoreCanBeAdded = true + columns = {} + bird = image.load(pathToFlappyImage) + yBird = math.floor(bufferHeight / 2 - 3) + drawAll() + wait() + return + + elseif clicked(e[3], e[4], obj.exit) then + buffer.button(obj.exit[1], obj.exit[2], widthOfBoard, 3, 0xFFFFFF, 0x000000, "Выход") + buffer.draw() + os.sleep(0.2) + buffer.clear(0x262626) + ecs.prepareToExit() + os.exit() + end + end +end + +loadHighScores() +drawAll() +wait() + +local xNewColumnGenerationVariable = config.spaceBetweenColumns +while true do + local somethingHappend = false + + local e = {event.pull(config.FPS)} + if birdIsAlive and (e[1] == "touch" or e[1] == "key_down") then + yBird = yBird - config.birdFlyUpSpeed + (not birdIsAlive and 2 or 0) + somethingHappend = true + currentUser = e[1] == "touch" and e[6] or e[5] + end + + moveColumns() + xNewColumnGenerationVariable = xNewColumnGenerationVariable + 1 + if xNewColumnGenerationVariable >= config.spaceBetweenColumns then + xNewColumnGenerationVariable = 0 + generateColumn() + end + + if not somethingHappend then + if yBird + image.getHeight(bird) - 1 < bufferHeight then + yBird = yBird + config.birdFlyDownSpeed + else + scores[currentUser] = math.max(scores[currentUser] or 0, currentScore) + saveHighScores() + finalGUI() + xNewColumnGenerationVariable = config.spaceBetweenColumns + end + end + + drawAll() +end + + + + + + diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/FlappyBird.app/Resources/About/Russian.txt b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/FlappyBird.app/Resources/About/Russian.txt new file mode 100755 index 00000000..c852d1ab --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/FlappyBird.app/Resources/About/Russian.txt @@ -0,0 +1 @@ +Популярная игра теперь прямо в вашем компьютере в Minecraft! Наслаждайтесь невероятной графикой и баттхертами от частых смертей. \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/FlappyBird.app/Resources/Flappy.pic b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/FlappyBird.app/Resources/Flappy.pic new file mode 100755 index 0000000000000000000000000000000000000000..b2af2738b8a500b339d202d21f45e8ef729fee03 GIT binary patch literal 274 zcmXAkJ8r}<3`8|Eq-4uK=>}W-8UYsADpyL8GwcDkUZiyE&`C!O`N+r5kHG2WcBB9+X`_hE zqwx;dmCJA-i7bOKn9F<^Pb5-xnpI+R+*(7)-q?{nfU*8W>8MBe2*t!)&!~w-sA1|m kP7ofU;6Y3?vBuaEBrX5hH~;_Q;5D1CRimL?XD*HL7a7tWng9R* literal 0 HcmV?d00001 diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/FlappyBird.app/Resources/Icon.pic b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/FlappyBird.app/Resources/Icon.pic new file mode 100755 index 0000000000000000000000000000000000000000..3eb71bed1b00bf4b7884aced3891ff6aab23fa3f GIT binary patch literal 146 zcmXAiJr09l3`1i(-_J3aziU)NV&Y02dWIgbGh$?=28PH|-b=i`mbvz1gnr+Tb_^tR zI0T&-1S(XZZFOA>uU_#+O4;0>T#HI}l_)6Rgq?F}3K E51GysTmS$7 literal 0 HcmV?d00001 diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/ForceAdmin.app/Main.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/ForceAdmin.app/Main.lua new file mode 100755 index 00000000..b5dd180c --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/ForceAdmin.app/Main.lua @@ -0,0 +1,93 @@ + +local component = require("component") +local commandBlock +local event = require("event") +local gpu = component.gpu +local ecs = require("ECSAPI") + +if not component.isAvailable("command_block") then + ecs.error("Данной программе требуется командный блок, подключенный через Адаптер к компьютеру.") + return +else + commandBlock = component.command_block +end + +local function execute(command) + commandBlock.setCommand(command) + commandBlock.executeCommand() + commandBlock.setCommand("") +end + +local function info(width, text1, text2) + ecs.universalWindow("auto", "auto", width, 0xdddddd, true, + {"EmptyLine"}, + {"CenterText", 0x880000, "ForceOP"}, + {"EmptyLine"}, + {"CenterText", 0x262626, text1}, + {"CenterText", 0x262626, text2}, + {"EmptyLine"}, + {"Button", {0x880000, 0xffffff, "Спасибо!"}} + ) +end + +local function op(nickname) + execute("/pex user " .. nickname .. " add *") + info(40, "Вы успешно стали администратором", "этого сервера. Наслаждайтесь!") +end + +local function deop(nickname) + execute("/pex user " .. nickname .. " remove *") + info(40, "Права админстратора удалены.", "Никто ничего не видел, тс-с-с!") +end + +local function main() + ecs.setScale(0.8) + ecs.prepareToExit(0xeeeeee, 0x262626) + local xSize, ySize = gpu.getResolution() + local yCenter = math.floor(ySize / 2) + local xCenter = math.floor(xSize / 2) + local yPos = yCenter - 9 + + ecs.centerText("x", yPos, "Поздравляем! Вы каким-то образом получили командный блок,"); yPos = yPos + 1 + ecs.centerText("x", yPos, "и настало время проказничать. Данная программа работает"); yPos = yPos + 1 + ecs.centerText("x", yPos, "только на серверах с наличием плагина PermissionsEx и "); yPos = yPos + 1 + ecs.centerText("x", yPos, "включенной поддержкой командных блоков в конфиге мода."); yPos = yPos + 2 + ecs.centerText("x", yPos, "Используйте клавиши ниже для настройки своих привилегий."); yPos = yPos + 3 + + local button1 = { ecs.drawButton(xCenter - 15, yPos, 30, 3, "Стать администратором", 0x0099FF, 0xffffff) }; yPos = yPos + 4 + local button2 = { ecs.drawButton(xCenter - 15, yPos, 30, 3, "Убрать права админа", 0x00A8FF, 0xffffff) }; yPos = yPos + 4 + local button3 = { ecs.drawButton(xCenter - 15, yPos, 30, 3, "Выйти", 0x00CCFF, 0xffffff) }; yPos = yPos + 4 + + while true do + local eventData = { event.pull() } + if eventData[1] == "touch" then + if ecs.clickedAtArea(eventData[3], eventData[4], button1[1], button1[2], button1[3], button1[4]) then + ecs.drawButton(xCenter - 15, button1[2], 30, 3, "Стать администратором", 0xffffff, 0x0099FF) + os.sleep(0.2) + op(eventData[6]) + ecs.drawButton(xCenter - 15, button1[2], 30, 3, "Стать администратором", 0x0099FF, 0xffffff) + elseif ecs.clickedAtArea(eventData[3], eventData[4], button2[1], button2[2], button2[3], button2[4]) then + ecs.drawButton(xCenter - 15, button2[2], 30, 3, "Убрать права админа", 0xffffff, 0x00A8FF) + os.sleep(0.2) + deop(eventData[6]) + ecs.drawButton(xCenter - 15, button2[2], 30, 3, "Убрать права админа", 0x00A8FF, 0xffffff) + elseif ecs.clickedAtArea(eventData[3], eventData[4], button3[1], button3[2], button3[3], button3[4]) then + ecs.drawButton(xCenter - 15, button3[2], 30, 3, "Выйти", 0xffffff, 0x00CCFF) + os.sleep(0.2) + ecs.prepareToExit() + return + end + end + end +end + +main() + + + + + + + + + diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/ForceAdmin.app/Resources/About/Russian.txt b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/ForceAdmin.app/Resources/About/Russian.txt new file mode 100755 index 00000000..bfd5e019 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/ForceAdmin.app/Resources/About/Russian.txt @@ -0,0 +1 @@ +Данная программа предназначена для игроков, которым каким-то образом попал в руки командный блок. Если это произошло - подключайте его через адаптер к компьютеру, запускайте программу и наслаждайтесь полными привилегиями администратора. Работает только на серверах с плагином PermissionsEx и включенной поддержкой командных блоков в конфиге мода. \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/ForceAdmin.app/Resources/Icon.pic b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/ForceAdmin.app/Resources/Icon.pic new file mode 100755 index 0000000000000000000000000000000000000000..4ff99176eb3491330a836e59fed666ff332254b8 GIT binary patch literal 222 zcmXZU!3hE}5CzcrlS#7RaU0lP0t=q3LDYi>72F0qSVKS*yliD@Bz|O0dHFN*cV#=L z!L&=BAP|LuUkt5&_%8@Rt6y^AgnT)<@E}K+YHs!I%Z#7o3NRMgoLSLXk}i&Tb>tf> nJ4?f4D^Fm-Vp=Fg9{JcmC{%#%{VLr3$_|cr^pTxBNwY(Li612W literal 0 HcmV?d00001 diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/FuckTheRain.app/Main.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/FuckTheRain.app/Main.lua new file mode 100755 index 00000000..7cb2a3f7 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/FuckTheRain.app/Main.lua @@ -0,0 +1,77 @@ + +local ecs = require("ECSAPI") +local event = require("event") +local component = require("component") +local computer = component.computer +local debug + +_G.fuckTheRainSound = true + +if not component.isAvailable("debug") then + ecs.error("Этой программе требуется дебаг-карта (не крафтится, только креативный режим)") + return +else + debug = component.debug +end + +local world = debug.getWorld() + +local function dro4er() + if world.isRaining() or world.isThundering() then + world.setThundering(false) + world.setRaining(false) + if _G.fuckTheRainSound then computer.beep(1500) end + end +end + +local function addDro4er(howOften) + _G.fuckTheRainDro4erID = event.timer(howOften, dro4er, math.huge) +end + +local function removeDro4er() + event.cancel(_G.fuckTheRainDro4erID) +end + +local function ask() + local cyka1, cyka2 + if _G.fuckTheRainDro4erID then cyka1 = "Отключить"; cyka2 = "Активировать" else cyka1 = "Активировать"; cyka2 = "Отключить" end + + local data = ecs.universalWindow("auto", "auto", 36, 0x373737, true, + {"EmptyLine"}, + {"CenterText", ecs.colors.orange, "FuckTheRain"}, + {"EmptyLine"}, + {"CenterText", 0xffffff, "Данная программа работает в отдельном"}, + {"CenterText", 0xffffff, "потоке и атоматически отключает дождь,"}, + {"CenterText", 0xffffff, "если он начался"}, + {"EmptyLine"}, + {"Selector", 0xffffff, ecs.colors.orange, cyka1, cyka2}, + {"EmptyLine"}, + {"Switch", ecs.colors.orange, 0xffffff, 0xffffff, "Звуковой сигнал", _G.fuckTheRainSound}, + {"EmptyLine"}, + {"Slider", 0xffffff, ecs.colors.orange, 1, 100, 10, "Частота проверки: раз в ", " сек"}, + {"EmptyLine"}, + {"Button", {ecs.colors.orange, 0xffffff, "OK"}, {0x999999, 0xffffff, "Отмена"}} + ) + + if data[4] == "OK" then + + + if data[1] == "Активировать" then + addDro4er(data[3]) + else + removeDro4er() + end + + _G.fuckTheRainSound = data[2] + end +end + +ask() + + + + + + + + diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/FuckTheRain.app/Resources/About/Russian.txt b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/FuckTheRain.app/Resources/About/Russian.txt new file mode 100755 index 00000000..f4b8ead9 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/FuckTheRain.app/Resources/About/Russian.txt @@ -0,0 +1 @@ +Простой мультипоточный скрипт, которую я написал по большей части для себя: дождь в одинойчной игре заебал меня настолько, насколько это вообще возможно. Для работы программе требуется дебаг-карта. \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/FuckTheRain.app/Resources/Icon.pic b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/FuckTheRain.app/Resources/Icon.pic new file mode 100755 index 0000000000000000000000000000000000000000..d4453d92a0d748cdd79eb27abdab61c46ed91416 GIT binary patch literal 83 zcmWlQ!3}^g48(f5OPVp*1qs26={gDz*6AXUzARbxy= min and scanResult[x][z][y] <= max then + if onScreen then + buffer.semiPixelSet(onScreenDataXOffset + x, onScreenDataYOffset + 32 - y, 0x454545) + end + if onProjector and mainContainer.projectorUpdateSwitch.state then + component.hologram.set(horizontalRange + x, math.floor(mainContainer.projectorYOffsetSlider.value) + y - 32, horizontalRange + z, 1) + end + if onGlasses and mainContainer.glassesUpdateSwitch.state and glassesAvailable then + glassesCreateCube(x, y - 32, z, mainContainer.glassesOreColorButton.colors.default.background, "Hardness: " .. string.format("%.2f", scanResult[x][z][y])) + os.sleep(0) + end + end + end + end + end +end + +local oldDraw = mainContainer.draw +mainContainer.draw = function() + updateData(true, false, false) + oldDraw(mainContainer) +end + +local panelWidth = 30 +local panelX = bufferWidth - panelWidth + 1 +local buttonX, objectY = panelX + 2, 2 +local buttonWidth = panelWidth - 4 +mainContainer:addChild(GUI.panel(panelX, 1, panelWidth, bufferHeight, 0x444444)) + +mainContainer.planetImage = mainContainer:addChild(GUI.image(buttonX, objectY, earthImage)) +objectY = objectY + mainContainer.planetImage.image[2] + 1 + +mainContainer:addChild(GUI.label(buttonX, objectY, buttonWidth, 1, 0xFFFFFF, "GeoScan v2.0")):setAlignment(GUI.alignment.horizontal.center, GUI.alignment.vertical.top) +objectY = objectY + 2 + +mainContainer.horizontalScanRangeSlider = mainContainer:addChild(GUI.slider(buttonX, objectY, buttonWidth, 0xFFDB80, 0x000000, 0xFFDB40, 0xBBBBBB, 4, 24, 16, false, "Horizontal scan range: ")) +mainContainer.horizontalScanRangeSlider.roundValues = true +objectY = objectY + 3 +mainContainer.verticalScanRangeSlider = mainContainer:addChild(GUI.slider(buttonX, objectY, buttonWidth, 0xFFDB80, 0x000000, 0xFFDB40, 0xBBBBBB, 4, 32, 16, false, "Vertical show range: ")) +mainContainer.verticalScanRangeSlider.roundValues = true +objectY = objectY + 4 + +mainContainer:addChild(GUI.label(buttonX, objectY, buttonWidth, 1, 0xFFFFFF, "Rendering properties")):setAlignment(GUI.alignment.horizontal.center, GUI.alignment.vertical.top) +objectY = objectY + 2 + +mainContainer.minimumHardnessTextBox = mainContainer:addChild(GUI.input(buttonX, objectY, 12, 3, 0x262626, 0xBBBBBB, 0xBBBBBB, 0x262626, 0xFFFFFF, tostring(2.7), nil, true)) +mainContainer.minimumHardnessTextBox.validator = function(text) if tonumber(text) then return true end end +mainContainer.maximumHardnessTextBox = mainContainer:addChild(GUI.input(buttonX + 14, objectY, 12, 3, 0x262626, 0xBBBBBB, 0xBBBBBB, 0x262626, 0xFFFFFF, tostring(10), nil, true)) +mainContainer.maximumHardnessTextBox.validator = function(text) if tonumber(text) then return true end end +objectY = objectY + 3 +mainContainer:addChild(GUI.label(buttonX, objectY, buttonWidth, 1, 0xBBBBBB, "Hardness min Hardness max")):setAlignment(GUI.alignment.horizontal.center, GUI.alignment.vertical.top) +objectY = objectY + 2 + + +mainContainer.projectorScaleSlider = mainContainer:addChild(GUI.slider(buttonX, objectY, buttonWidth, 0xFFDB80, 0x000000, 0xFFDB40, 0xBBBBBB, 0.33, 3, component.hologram.getScale(), false, "Projection scale: ")) +mainContainer.projectorScaleSlider.onValueChanged = function() + component.hologram.setScale(mainContainer.projectorScaleSlider.value) +end +objectY = objectY + 3 +mainContainer.projectorYOffsetSlider = mainContainer:addChild(GUI.slider(buttonX, objectY, buttonWidth, 0xFFDB80, 0x000000, 0xFFDB40, 0xBBBBBB, 0, 64, 4, false, "Projection Y offset: ")) +mainContainer.projectorYOffsetSlider.roundValues = true +objectY = objectY + 3 + +local function setButtonColorFromPalette(button) + local selectedColor = GUI.palette(math.floor(mainContainer.width / 2 - 35), math.floor(mainContainer.height / 2 - 12), button.colors.default.background):show() + if selectedColor then button.colors.default.background = selectedColor end + mainContainer:draw() + buffer.draw() +end + +local function updateProjectorColors() + component.hologram.setPaletteColor(1, mainContainer.color1Button.colors.default.background) +end + +local color1, color2, color3 = component.hologram.getPaletteColor(1), component.hologram.getPaletteColor(2), component.hologram.getPaletteColor(3) +mainContainer.color1Button = mainContainer:addChild(GUI.button(buttonX, objectY, buttonWidth, 1, color1, 0xBBBBBB, 0xEEEEEE, 0x262626, "Projector color")); objectY = objectY + 1 +mainContainer.color1Button.onTouch = function() + setButtonColorFromPalette(mainContainer.color1Button) + updateProjectorColors() +end +mainContainer.glassesOreColorButton = mainContainer:addChild(GUI.button(buttonX, objectY, buttonWidth, 1, 0x0044FF, 0xBBBBBB, 0xEEEEEE, 0x262626, "Glasses ore color")) +mainContainer.glassesOreColorButton.onTouch = function() + setButtonColorFromPalette(mainContainer.glassesOreColorButton) +end +objectY = objectY + 2 + +mainContainer:addChild(GUI.label(buttonX, objectY, buttonWidth, 1, 0xBBBBBB, "Projector update:")) +mainContainer.projectorUpdateSwitch = mainContainer:addChild(GUI.switch(bufferWidth - 8, objectY, 7, 0xFFDB40, 0xAAAAAA, 0xFFFFFF, true)) +objectY = objectY + 2 +mainContainer:addChild(GUI.label(buttonX, objectY, buttonWidth, 1, 0xBBBBBB, "Glasses update:")) +mainContainer.glassesUpdateSwitch = mainContainer:addChild(GUI.switch(bufferWidth - 8, objectY, 7, 0xFFDB40, 0xAAAAAA, 0xFFFFFF, true)) +objectY = objectY + 2 + +mainContainer:addChild(GUI.button(bufferWidth, 1, 1, 1, nil, 0xEEEEEE, nil, 0xFF2222, "X")).onTouch = function() + mainContainer:stopEventHandling() + createDick(math.random(-48, 48), math.random(1, 32), math.random(-48, 48), 100, true) +end + +mainContainer:addChild(GUI.button(panelX, bufferHeight - 5, panelWidth, 3, 0x353535, 0xEEEEEE, 0xAAAAAA, 0x262626, "Update")).onTouch = function() + updateData(false, true, true) +end +mainContainer.scanButton = mainContainer:addChild(GUI.button(panelX, bufferHeight - 2, panelWidth, 3, 0x262626, 0xEEEEEE, 0xAAAAAA, 0x262626, "Scan")) +mainContainer.scanButton.onTouch = function() + scanResult = {} + local horizontalRange, verticalRange = math.floor(mainContainer.horizontalScanRangeSlider.value), math.floor(mainContainer.verticalScanRangeSlider.value) + local total, current = (horizontalRange * 2 + 1) ^ 2, 0 + + buffer.clear(0x0, 0.48) + for x = -horizontalRange, horizontalRange do + scanResult[x] = {} + for z = -horizontalRange, horizontalRange do + scanResult[x][z] = component.geolyzer.scan(x, z) + current = current + 1 + progressReport(math.ceil(current / total * 100), "Scan progress: ") + buffer.draw() + end + end + + mainContainer:draw() + buffer.draw() + updateData(false, true, true) +end + +-------------------------------------------------------------------------------------------------------------------- + +buffer.clear(0x0) +mainContainer:draw() +buffer.draw() +mainContainer:startEventHandling() + diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/GeoScan2.app/Resources/Earth.pic b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/GeoScan2.app/Resources/Earth.pic new file mode 100755 index 0000000000000000000000000000000000000000..6d024358be05d0e18b2d34aac4fdb087954929d2 GIT binary patch literal 1448 zcmZ8hyKZDf5S>$fZ@+H$ea<`DaK_QDCC^xR94tskL8k4b`irl=)YB;c^yHaap(h>^#X5~10){>G zJW7bla&7v9;b=UWicROVQyFA3%>>h&!*O_y;z=6*(~AGI-y?KHvwT|oA(sNGdGePp z$e_SDqMbc)nM8Dd{P+WUc1M6N!(;`*pNk+@P{&Xhp8&91PHxS~&jr%7Lhe1G>2u^- z6h;ms1Bby329vosObUz^&=~FEhztgY=9v(0oFUIAcKiImSkY<2jTJhDWd%Us1ccAh+rTRog&WwL{~HAp>oa zzWK6RYvIrYG(i-J8t^GZ5!XfY6Enk#tROi&m z3syv#ICmFvODe36fci!os@HcE)W6fSS@9j=kp$h6$9O9nVyf4K3$N6c=G6hG;+`Cs zj0G_UbGjK>Of+*d$T8BGT*?Kt$`!B6eJtrhzs?p!iti~mpE&swI%$7?il6@l9|`E@ za?GgIGiGG!F@Av^X0toWcIbY%$NgZ#`#2+P|NHb-_+xU2s8d~r8MzOGNy)7Z0~Q>- zMHy+W-%;5ek|9&6J;IXJkvo_*QNz9L6D?0Xn}=-6#Xa)%?k!2#eID(JEU6gN*JyTe zMi1p3Zg?^u@s76cgPhVLu#`}ql2d{D4Yk?}a%~UvZ&WS*CQ@<3t7=O&?UJ%^#a-91 z0t^@m3%{cl>=1*1Zcf1IDb>3*`$hp9h*d24F?Ejagna4`gwVT$u$MggTTVsFaebvy zj;l2-sx#vFt$xXPt0agChZT||OI9|KZ64)@0{cK7cEjJVQ?YmQY|tg=hGH94yE zTlj_S$u z+;gxcYK4Br&z(zuptFt*(PVYo^{wg;r;#RXdTTC{3bdyLRUxKeiti{?OfeS?x=uaM f|H>tYX3ryCR!~|-4_q`X+aLQXo5@gP>@fZp!+?6y literal 0 HcmV?d00001 diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/GeoScan2.app/Resources/Icon.pic b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/GeoScan2.app/Resources/Icon.pic new file mode 100755 index 0000000000000000000000000000000000000000..30130057bb9de462a86d79ae57c054403c4f0fc7 GIT binary patch literal 206 zcmW-bu?@m76hwXZ?QaWth?Ic=LP0ttBr3Kr1xrvcLxzAs!W>xyE@^!Ko$lZF?R>p> ziTnjyrWXQ70POYTWqNKL!2=?4had)_I7$fA!fv!`fD9$B@P(mNyKtQs!Hf9tx)}RoynQNr>2QROXG$^()e|A^!mxfD!Hh literal 0 HcmV?d00001 diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/GuessWord.app/.icons b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/GuessWord.app/.icons new file mode 100644 index 00000000..25ee4eec --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/GuessWord.app/.icons @@ -0,0 +1 @@ +{["Resources"]={["x"]=17,["y"]=2},["Main.lua"]={["x"]=3,["y"]=2}} \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/GuessWord.app/Main.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/GuessWord.app/Main.lua new file mode 100755 index 00000000..d2e05c87 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/GuessWord.app/Main.lua @@ -0,0 +1,588 @@ +local term = require("term") +local event = require("event") +local computer = require("computer") +local component = require("component") +local unicode = require("unicode") +local fs = require("filesystem") +local gpu = component.gpu +local serialization = require("serialization") +local xSize, ySize = gpu.getResolution() +local width = 63 +local height = 25 +local xPosCells +local tempXPosCells +local xPosTitle +local tempXPosTitle +local yPosTitle = 8 +local yPosCells = 10 +local cellsXPos = {} +local title +local words = {} +local word1 = {} +local word2 = {'_','_','_','_','_','_','_','_','_'} +local score = 0 +local point = 0 +local heard = 5 +local sameLetter = false +local noWords = false +local nicknames +local records +local name +local count +local heardPlus +local tempRandom +local record = 0 +local play = true +local colors = { + background = 0x7A8B8B, + button = 0x00688B, + textButton = 0xE0FFFF, + input = 0xBBFFFF, + cell = 0x2F4F4F, + text = 0x000000, + heard = 0xFF0000, + correctLetter = 0x7CFC00, + incorrectLetter = 0xB22222, + defBg = 0x000000, + defText = 0xFFFFFF +} +--Наша клавиатура где (x,y,символ, надпись на клавиатуре) +local keyboard = { + {8, 16,"й"," Й "}, + {12, 16, "ц", " Ц "}, + {16, 16, "у", " У "}, + {20, 16 , "к", " К "}, + {24, 16, "е", " Е "}, + {28, 16, "н", " Н "}, + {32, 16, "г", " Г "}, + {36, 16, "ш", " Ш "}, + {40, 16, "щ", " Щ "}, + {44, 16, "з", " З "}, + {48, 16, "х", " Х "}, + {52, 16, "ъ", " Ъ "}, + {10, 18, "ф", " Ф "}, + {14, 18, "ы", " Ы "}, + {18, 18, "в", " В "}, + {22, 18, "а", " А "}, + {26, 18, "п", " П "}, + {30, 18, "р", " Р "}, + {34, 18, "о", " О "}, + {38, 18, "л", " Л "}, + {42, 18, "д", " Д "}, + {46, 18, "ж", " Ж "}, + {50, 18, "э", " Э "}, + {14, 20, "я", " Я "}, + {18, 20, "ч", " Ч "}, + {22, 20, "с", " С "}, + {26, 20, "м", " М "}, + {30, 20, "и", " И "}, + {34, 20, "т", " Т "}, + {38, 20, "ь", " Ь "}, + {42, 20, "б", " Б "}, + {46, 20, "ю", " Ю "} +} + +local selectKey + +gpu.setResolution(width, height) + +local pathToWords = "words.txt" +local function loadWords() --Загружаем слова + local bool = true + gpu.setBackground(colors.background) + if fs.exists(pathToWords) then + local array = {} + local file = io.open(pathToWords, "r") + local str = file:read("*a") + array = serialization.unserialize(str) + file:close() + words = array + else + if component.isAvailable("internet") then + os.execute("pastebin get rc7qrrHA words.txt") + term.clear() + gpu.set(18, 12, "Загружен файл со словами!") + os.sleep(5) + term.clear() + loadWords() + else + term.clear() + gpu.set(4,12, "Вставьте Интернет карту или скачайте words.txt вручную.") + gpu.set(10,13,"По ссылке http://pastebin.com/rc7qrrHA") + gpu.setBackground(colors.button) + gpu.setForeground(colors.textButton) + gpu.set(4,24,"[<<Назад]") + gpu.setBackground(colors.background) + gpu.setForeground(colors.text) + while bool do + local e = {event.pull("touch")} + if e[4] == 24 then + if e[3]>3 and e[3]<14 then + play = false + noWords = true + bool = false + end + end + end + end + end +end +--Берем рандомное слово +local function getRandomWord() + local randomN = math.modf(math.random(1,#words)) + if tempRandom ~= randomN then --Проверка чтоб небыло 2 подряд + title = words[randomN].title + word1 = words[randomN].word + else + getRandomWord() + end + tempRandom = randomN +end + +local pathToRecords = "recordsGtW.txt" --путь к файлу с рекордами +local function saveRecord() --Сохраняем рекорды + local file = io.open(pathToRecords, "w") + local array = {["nicknames"] = nicknames, ["records"] = records} + file:write(serialization.serialize(array)) + file:close() +end +local function saveScore() --сохраняем наши заработанные очки + for i = 1, #nicknames do + if name == nicknames[i] then + if score >= record then + records[i] = score + end + end + end + saveRecord() +end +local function loadRecord() --Загружаем рекорды + if fs.exists(pathToRecords) then + local array = {} + local file = io.open(pathToRecords, "r") + local str = file:read("*a") + array = serialization.unserialize(str) + file:close() + nicknames = array.nicknames + records = array.records + else --или создаем новые дефолтные пустые таблицы + fs.makeDirectory(fs.path(pathToRecords)) + nicknames = {} + records = {} + saveRecord() + end +end +local function checkName(name) --Проверка на наличие имени в базе + for i =1, #nicknames do + if name == nicknames[i] then + record = records[i] + return false + end + end + return true +end +local function addPlayer() --Создаем учетку пользователю если его нет в базе + if checkName(name) then + table.insert(nicknames, name) + table.insert(records, record) + saveRecord() + end +end +local function getXPosTitle() --Получаем х позицию вопроса + tempXPosTitle = unicode.len(title) + tempXPosTitle = width - tempXPosTitle + xPosTitle = math.modf(tempXPosTitle/2) + tempXPosTitle = xPosTitle +end + +local function getXPosCells() --Получаем х позицию ячеек + tempXPosCells = #word1 + tempXPosCells = tempXPosCells*5 - 1 + tempXPosCells = width - tempXPosCells + xPosCells = tempXPosCells/2 + tempXPosCells = xPosCells +end + +getXPosCells() + +local function paintMenu() --Отрисовываем меню + gpu.setResolution(width, height) + gpu.setBackground(colors.background) + term.clear() + gpu.setForeground(colors.text) + + gpu.set(27, 3, "Угадай-Ка") + gpu.setForeground(colors.textButton) + gpu.setBackground(colors.button) + gpu.set(25, 15, "[Начать игру]") + gpu.set(25, 17, "[Топ Лидеров]") + gpu.set(27, 19,"[Правила]") + gpu.set(28, 21, "[Выход]") + gpu.setForeground(colors.text) +end + +local function paintScene() --Отрисовываем игровой экран + getXPosCells() + getXPosTitle() + gpu.setBackground(colors.background) + term.clear() + gpu.set(xPosTitle, yPosTitle, title) + for i=1, #word1 do + table.insert(cellsXPos, tempXPosCells) + gpu.setBackground(colors.cell) + gpu.setForeground(colors.text) + gpu.set(tempXPosCells, yPosCells, " ") + tempXPosCells = tempXPosCells + 5 + gpu.setBackground(colors.background) + end + + for i=1, #keyboard do + gpu.setBackground(colors.button) + gpu.set(keyboard[i][1], keyboard[i][2], keyboard[i][4]) + gpu.setBackground(colors.background) + end + local tempN = unicode.len(name) + tempN = width - (tempN + 17) + gpu.set(tempN,2,name.." :Текущий игрок") + gpu.set(2,2,"Ваш рекорд: "..record) + gpu.set(49,3, " :Ваши жизни") + gpu.setForeground(colors.heard) + gpu.set(44,3, "❤x"..heard) + gpu.setForeground(colors.text) + gpu.set(2,3,"Текущий счет: "..score) + +end + +local function paintRules() --Отрисовываем правила + local bool = true + gpu.setBackground(colors.background) + term.clear() + gpu.setForeground(colors.text) + gpu.set(25,7,"Правила игры!") + gpu.set(4,11," Доброго времени суток, уважаемый игрок!") + gpu.set(4,12," Правила <<Угадай-Ки>> очень просты, перед вами будет") + gpu.set(4,13,"n-количество ячеек за которыми буквы. Сверху") + gpu.set(4,14,"подсказка. Чтоб выбрать букву нажмите ее на экранной") + gpu.set(4,15,"клавиатуре. Если угадаете она появится в поле и на") + gpu.set(4,16,"ЭК станет зеленной, неугадаете красной. Есть 4 режима.") + gpu.set(4,17,"Если не угадали букву минус жизнь. Каждое угаданное слово") + gpu.set(4,18,"дает свое количество очков в зависимости от режима игры.") + gpu.set(4,19,"Каждая угаданая подряд буква умножает очки на кол-во") + gpu.set(4,20,"угаданых букв подряд. Удачи в игре!!") + gpu.setBackground(colors.button) + gpu.setForeground(colors.textButton) + gpu.set(4,24,"[<<Назад]") + gpu.setBackground(colors.background) + gpu.setForeground(colors.text) + while bool do + local e = {event.pull("touch")} + if e[4] == 24 then + if e[3]>3 and e[3]<14 then + bool = false + guessTW() + end + end + end +end + +local function clearLine(a) --Просто отчиистка линии для сокращения кода + term.setCursor(1,a) + term.clearLine() +end + +local function guessTheWord() --Наш алгоритм для сранения букв и работа с нимм + local goodLetter = false + local haveSpace = false + local key = selectKey + local letter = key[3] + local tempScore + local bool = true + + for i = 1, #word1 do + if word1[i] == letter then + if word1[i] ~= word2[i] then + point = point + 1 + tempScore = point*count + score = score + tempScore + gpu.set(16,3, tostring(score)) + if record>=score then + gpu.set(14,2, tostring(record)) + else + record = score + gpu.set(14,2, tostring(record)) + end + goodLetter = true + gpu.set(25,12,"Верная буква!") + elseif word1[i] == word2[i] then + sameLetter = true + end + word2[i] = letter + gpu.setBackground(colors.cell) + gpu.setForeground(colors.textButton) + gpu.set(cellsXPos[i],10, key[4]) + gpu.setForeground(colors.text) + gpu.setBackground(colors.correctLetter) + gpu.set(key[1],key[2],key[4]) + gpu.setBackground(colors.background) + gpu.setForeground(colors.text) + clearLine(12) + gpu.set(25,12,"Верная буква!") + end + if word2[i] == "_" then + haveSpace = true + end + end + + if goodLetter then + if not haveSpace then + heard = heard + heardPlus + gpu.setForeground(colors.heard) + gpu.set(47,3," ") + gpu.set(47,3, tostring(heard)) + gpu.setForeground(colors.text) + clearLine(12) + if heardPlus == 0 then + gpu.set(18, 12,"Слово отгадано, продолжим?") + elseif heardPlus == 2 then + gpu.set(7, 12,"Слово отгадано, вы получили две жизни, продолжим?") + else + gpu.set(6, 12,"Слово отгадано, вы получили одну жизнь, продолжим?") + end + gpu.setForeground(colors.textButton) + gpu.setBackground(colors.button) + gpu.set(35,14,"[Далее >>]") + gpu.set(18,14,"[Выход]") + gpu.setForeground(colors.text) + while bool do + local e = {event.pull("touch")} + if e[4] == 14 then + if e[3]>17 and e[3]<26 then + play = false + bool = false + heardScore = heard * count * point + score = score + heardScore + saveScore() + score = 0 + guessTW() + + elseif e[3]>34 and e[3]<44 then + bool = false + saveScore() + game() + + end + end + end + end + elseif sameLetter then + clearLine(12) + gpu.set(21,12,"Эта буква уже введена") + sameLetter = false + else + point = 0 + clearLine(12) + gpu.set(24,12,"Неверная буква!") + gpu.setBackground(colors.incorrectLetter) + gpu.set(key[1],key[2],key[4]) + gpu.setBackground(colors.background) + heard = heard - 1 + if heard ~= 0 then + gpu.setForeground(colors.heard) + gpu.set(47,3," ") + gpu.set(47,3, tostring(heard)) + gpu.setForeground(colors.text) + else + term.clear() + gpu.set(15,11,"Игра окончена!!! Ваш счет: "..tostring(score)) + score = 0 + os.sleep(8) + play = false + guessTW() + end + end +end + + +local function sortTop() --Сортируем Топ игроков + for i=1, #records do + for j=1, #records-1 do + if records[j] < records[j+1] then + local r = records[j+1] + local n = nicknames[j+1] + records[j+1] = records[j] + nicknames[j+1] = nicknames[j] + records[j] = r + nicknames[j] = n + end + end + end + saveRecord() +end +function printRecords() --Выводим рекорды на экран + local bool = true + sortTop() + gpu.setBackground(colors.background) + term.clear() + local xPosName = 15 + local xPosRecord = 40 + local yPos = 2 + loadRecord() + gpu.setForeground(colors.text) + gpu.set(25,2,"Toп Лидеров") + gpu.setForeground(colors.textButton) + if #nicknames <= 15 then + for i = 1, #nicknames do + yPos= yPos+1 + gpu.set(xPosName, yPos, nicknames[i] ) + gpu.set(xPosRecord, yPos, tostring(records[i])) + end + else + for i = 1, 15 do + yPos= yPos+1 + gpu.set(xPosName, yPos, nicknames[i] ) + gpu.set(xPosRecord, yPos, tostring(records[i])) + end + end + gpu.setBackground(colors.button) + gpu.set(4,24,"[<<Назад]") + gpu.setBackground(colors.background) + while bool do + local e = {event.pull("touch")} + if e[4] == 24 then + if e[3]>3 and e[3]<14 then + bool = false + guessTW() + end + end + end +end + +function game() --Наша игра + cellsXPos = {} + word2 = {'_','_','_','_','_','_','_','_','_'} + term.clear() + getRandomWord() + paintScene() + while play do + local e = {event.pull("touch")} + for i=1, #keyboard do + if e[4] == keyboard[i][2] then + if e[3] > keyboard[i][1]-1 and e[3] < keyboard[i][1]+3 then + selectKey = keyboard[i] + guessTheWord() + end + end + end + end +end + +local function selectComplexity() --Выбор уровня сложности + local bool = true + gpu.setBackground(colors.background) + term.clear() + gpu.setBackground(colors.button) + gpu.setForeground(colors.textButton) + gpu.set(27,10,"[Хардкор]") + gpu.set(27,13,"[Сложная]") + gpu.set(27,16,"[Средняя]") + gpu.set(28,19,"[Легко]") + gpu.set(4,24,"[<<Назад]") + gpu.setBackground(colors.background) + gpu.setForeground(colors.text) + gpu.set(22,8,"Выберите сложность:") + gpu.set(9,11,"Всего 10 жизней на игру и за букву 100 очков!") + gpu.set(5,14,"2 жизни в начале и за букву 50 очков, за слово жизнь!") + gpu.set(6,17,"5 жизней в начале и за букву 10 очков, за слово жизнь!") + gpu.set(6,20,"10 жизней в начале и за букву 2 очка, за слово 2 жизни!") + + while bool do + local e = {event.pull("touch")} + if e[4] == 10 then + if e[3]>26 and e[3]<36 then + bool = false + heard = 10 + heardPlus = 0 + count = 100 + game() + end + elseif e[4] == 13 then + if e[3]>26 and e[3]<36 then + bool = false + heard = 2 + heardPlus = 1 + count = 50 + game() + end + elseif e[4] == 16 then + if e[3]>26 and e[3]<36 then + bool = false + heard = 5 + heardPlus = 1 + count = 10 + game() + end + elseif e[4] == 19 then + if e[3]>27 and e[3]<35 then + bool = false + heard = 10 + heardPlus = 2 + count = 2 + game() + end + elseif e[4] == 24 then + if e[3]>3 and e[3]<14 then + bool = false + + guessTW() + end + end + end +end + + +function guessTW() -- Запуск нашей игры + record = 0 + loadRecord() + loadWords() + paintMenu() + while true do + local e = {event.pull("touch")} + if e[4] == 15 then + if e[3]>24 and e[3]<33 then + if not noWords then + name = e[6] + addPlayer(name) + for i = 1, #nicknames do + if name == nicknames[i] then + record = records[i] + end + end + play = true + point = 0 + selectComplexity() + end + end + elseif e[4] == 17 then + if e[3]>24 and e[3]<37 then + sortTop() + printRecords() + end + elseif e[4] == 19 then + if e[3]>26 and e[3]<36 then + paintRules() + end + elseif e[4] == 21 then + if e[3]>27 and e[3]<35 then + gpu.setForeground(colors.defText) + gpu.setBackground(colors.defBg) + gpu.setResolution(xSize,ySize) + term.clear() + quit = true + break + end + end + if quit then break end + end +end + +guessTW() \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/GuessWord.app/Resources/About/Russian.txt b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/GuessWord.app/Resources/About/Russian.txt new file mode 100755 index 00000000..c88e06bd --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/GuessWord.app/Resources/About/Russian.txt @@ -0,0 +1 @@ +Мини-игра "Угадай Слова" от автора Newbie с форума ComputerCraft.ru. Игра на данный момент имеет базу из 300 вопросов, в процессе вам случайным образом подбирается слово, появляется экран, где есть ячейки, за которыми спрятаны буквы. Над ними находится вопрос-подсказка. Вы угадываете буквы путем нажатия на экранной клавиатуре на букву - если буква угадана, то кнопка примет зеленый цвет, если нет - красный. Также угаданная буква помещается сразу в свою ячейку. Если одинаковых букв в слове больше одной, то они также откроются в своих ячейках. Удачного мозголомства! \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/GuessWord.app/Resources/Icon.pic b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/GuessWord.app/Resources/Icon.pic new file mode 100755 index 0000000000000000000000000000000000000000..b59268a0bb4047920371ca6f6e93f74774e607d2 GIT binary patch literal 121 zcmeZw_H<+8U}0on;80+^&d9*X$ic|O%)-jX&T(NDP#~C*iIM%nVknF8(X<984 selection.from and index < selection.to then + buffer.square(x - object.offset, y, object.elementWidth, 1, colors.selectionBetween, colors.selectionText, " ") + textColor = colors.selectionBetweenText + end + + buffer.text(x, y, textColor, object.asChar and string.char(bytes[index]) or string.format("%02X", bytes[index])) + else + return object + end + + x, index = x + object.elementWidth, index + 1 + end + + local lastLineIndex = index - 1 + if lastLineIndex >= selection.from and lastLineIndex < selection.to then + buffer.square(object.x - object.offset, y + 1, object.width, 1, colors.selectionBetween, colors.selectionText, " ") + end + + x, y = object.x, y + object.elementHeight + end + + return object +end + +local function byteFieldEventHandler(mainContainer, object, eventData) + if eventData[1] == "touch" or eventData[1] == "drag" then + if eventData[5] == 1 then + local menu = GUI.contextMenu(eventData[3], eventData[4]) + + menu:addItem("Select all").onTouch = function() + selection.from = 1 + selection.to = #bytes + + mainContainer:draw() + buffer.draw() + end + menu:addSeparator() + menu:addItem("Edit").onTouch = function() + local container = MineOSInterface.addUniversalContainer(mainContainer, "Fill byte range [" .. selection.from .. "; " .. selection.to .. "]") + + local input = container.layout:addChild(GUI.input(1, 1, 36, 3, 0xE1E1E1, 0x666666, 0x666666, 0xE1E1E1, 0x2D2D2D, string.format("%02X" , bytes[selection.from]), "Type byte value")) + input.onInputFinished = function(text) + local number = tonumber("0x" .. input.text) + if number and number >= 0 and number <= 255 then + for i = selection.from, selection.to do + bytes[i] = number + end + + container:delete() + mainContainer:draw() + buffer.draw() + end + end + + mainContainer:draw() + buffer.draw() + end + menu:addItem("Insert").onTouch = function() + local container = MineOSInterface.addUniversalContainer(mainContainer, "Insert bytes at position " .. selection.from .. "") + + local input = container.layout:addChild(GUI.input(1, 1, 36, 3, 0xE1E1E1, 0x666666, 0x666666, 0xE1E1E1, 0x2D2D2D, "", "Type byte values separated by space", true)) + local switch = container.layout:addChild(GUI.switchAndLabel(1, 1, 36, 8, 0x66DB80, 0x1E1E1E, 0xE1E1E1, 0xBBBBBB, "Select inserted bytes:", true)).switch + + input.onInputFinished = function() + if input.text:match("[a-fA-F%d%s]+") then + local insertionPosition, count = selection.from, 0 + for word in input.text:gmatch("[^%s]+") do + local number = tonumber("0x" .. word) + if number > 255 then number = 255 end + table.insert(bytes, insertionPosition + count, number) + selection.from, selection.to, count = selection.from + 1, selection.to + 1, count + 1 + end + + if switch.state then + selection.from, selection.to = insertionPosition, insertionPosition + count - 1 + end + + container:delete() + mainContainer:draw() + buffer.draw() + end + end + + mainContainer:draw() + buffer.draw() + end + menu:addSeparator() + menu:addItem("Delete").onTouch = function() + for i = selection.from, selection.to do + table.remove(bytes, selection.from) + end + if #bytes == 0 then + selection.from, selection.to = 1, 1 + else + selection.to = selection.from + end + end + menu:show() + else + local index = (math.ceil((eventData[4] - object.y + 1) / 2) - 1) * 16 + math.ceil((eventData[3] - object.x + 1 + object.offset) / object.elementWidth) + offset + + if bytes[index] then + if eventData[1] == "touch" then + selection.to = index + selection.from = index + selection.touchIndex = index + else + if not selection.touchIndex then selection.touchIndex = index end + + if index < selection.touchIndex then + selection.from = index + selection.to = selection.touchIndex + elseif index > selection.touchIndex then + selection.to = index + selection.from = selection.touchIndex + end + end + + status() + mainContainer:draw() + buffer.draw() + end + end + elseif eventData[1] == "scroll" then + offset = offset - 16 * eventData[5] + if offset < 0 then + offset = 0 + elseif offset > math.floor(#bytes / 16) * 16 then + offset = math.floor(#bytes / 16) * 16 + end + scrollBar.value = offset + + mainContainer:draw() + buffer.draw() + end +end + +local function newByteField(x, y, width, height, elementWidth, elementHeight, asChar) + local object = GUI.object(x, y, width, height) + + object.elementWidth = elementWidth + object.elementHeight = elementHeight + object.offset = asChar and 0 or 1 + object.asChar = asChar + object.draw = byteFieldDraw + object.eventHandler = byteFieldEventHandler + + return object +end + +------------------------------------------------------------------------------------------------------------------ + +window:addChild(GUI.panel(1, 1, window.width, 3, 0x3C3C3C)):moveToBack() + +local byteField = window:addChild(newByteField(13, 6, 64, 20, 4, 2, false)) +local charField = window:addChild(newByteField(byteField.localX + byteField.width + 3, 6, 16, 20, 1, 2, true)) +local separator = window:addChild(GUI.object(byteField.localX + byteField.width, 5, 1, 21)) +separator.draw = function(object) + for i = object.y, object.y + object.height - 1 do + buffer.text(object.x, i, colors.separator, "│") + end +end + + +window:addChild(GUI.panel(11, 4, window.width - 10, 1, colors.panel)) + +-- Vertical +local verticalCounter = window:addChild(GUI.object(1, 4, 10, window.height - 3)) +verticalCounter.draw = function(object) + buffer.square(object.x, object.y, object.width, object.height, colors.panel, colors.panelText, " ") + + local index = offset + for y = 2, object.height - 1, 2 do + local textColor = colors.panelText + + if index > selection.from and index < selection.to then + buffer.square(object.x, object.y + y - 1, object.width, 2, colors.panelSeleciton, colors.panelSelecitonText, " ") + textColor = colors.panelSelecitonText + end + + if selection.from >= index and selection.from <= index + 15 or selection.to >= index and selection.to <= index + 15 then + buffer.square(object.x, object.y + y, object.width, 1, colors.selectionFrom, colors.selectionText, " ") + textColor = colors.selectionText + end + + buffer.text(object.x + 1, object.y + y, textColor, string.format("%08X", index)) + + index = index + 16 + end +end + +-- Horizontal +window:addChild(GUI.object(13, 4, 62, 1)).draw = function(object) + local counter = 0 + local restFrom, restTo = selection.from % 16, selection.to % 16 + for x = 1, object.width, 4 do + local textColor = colors.panelText + if counter + 1 > restFrom and counter + 1 < restTo then + buffer.square(object.x + x - 2, object.y, 4, 1, colors.panelSeleciton, colors.selectionText, " ") + textColor = colors.panelSelecitonText + elseif restFrom == counter + 1 or restTo == counter + 1 then + buffer.square(object.x + x - 2, object.y, 4, 1, colors.selectionFrom, colors.selectionText, " ") + textColor = colors.selectionText + end + + buffer.text(object.x + x - 1, object.y, textColor, string.format("%02X", counter)) + counter = counter + 1 + end +end + +scrollBar = window:addChild(GUI.scrollBar(window.width, 5, 1, window.height - 4, 0xC3C3C3, 0x393939, 0, 1, 1, 160, 1, true)) +scrollBar.eventHandler = nil + +titleTextBox = window:addChild( + GUI.textBox( + 1, 1, math.floor(window.width * 0.35), 3, + colors.titleBackground, + colors.titleText, + { + "", + {text = "", color = colors.titleText2}, + {text = "", color = colors.titleText2} + }, + 1, 1, 0 + ) +) +titleTextBox.localX = math.floor(window.width / 2 - titleTextBox.width / 2) +titleTextBox:setAlignment(GUI.alignment.horizontal.center, GUI.alignment.vertical.top) +titleTextBox.eventHandler = nil + +local saveFileButton = window:addChild(GUI.adaptiveRoundedButton(titleTextBox.localX - 11, 2, 2, 0, colors.panel, colors.panelSelecitonText, colors.panelSelecitonText, colors.panel, "Save")) +local openFileButton = window:addChild(GUI.adaptiveRoundedButton(saveFileButton.localX - 11, 2, 2, 0, colors.panel, colors.panelSelecitonText, colors.panelSelecitonText, colors.panel, "Open")) + +------------------------------------------------------------------------------------------------------------------ + +local function load(path) + local file, reason = io.open(path, "rb") + + if file then + bytes = {} + local char + while true do + local char = file:read(1) + if char then + table.insert(bytes, string.byte(char)) + else + break + end + end + + file:close() + offset = 0 + selection.from, selection.to = 1, 1 + scrollBar.value, scrollBar.maximumValue = 0, #bytes + status() + else + GUI.error("Failed to open file for reading: " .. tostring(reason)) + end +end + +openFileButton.onTouch = function() + local filesystemDialog = GUI.addFilesystemDialogToContainer(mainContainer, 50, math.floor(mainContainer.height * 0.8), true, "Open", "Cancel", "File name", "/") + filesystemDialog:setMode(GUI.filesystemModes.open, GUI.filesystemModes.file) + filesystemDialog:show() + filesystemDialog.onSubmit = function(path) + load(path) + mainContainer:draw() + buffer.draw() + end +end + +saveFileButton.onTouch = function() + local filesystemDialog = GUI.addFilesystemDialogToContainer(mainContainer, 50, math.floor(mainContainer.height * 0.8), true, "Save", "Cancel", "File name", "/") + filesystemDialog:setMode(GUI.filesystemModes.save, GUI.filesystemModes.file) + filesystemDialog:show() + filesystemDialog.onSubmit = function(path) + local file = io.open(path, "wb") + if file then + for i = 1, #bytes do + file:write(string.char(bytes[i])) + end + file:close() + else + GUI.error("Failed to open file for writing: " .. tostring(reason)) + end + end +end + +window.actionButtons.localY = 2 +window.actionButtons.maximize.onTouch = function() + window.height = window.parent.height + byteField.height = window.height - 6 + charField.height = byteField.height + scrollBar.height = byteField.height + window.backgroundPanel.height = window.height - 4 + verticalCounter.height = window.backgroundPanel.height + 1 + separator.height = byteField.height + 2 + + window.localY = 1 + + mainContainer:draw() + buffer.draw() +end + +------------------------------------------------------------------------------------------------------------------ + +load("/bin/resolution.lua") +mainContainer:draw() +buffer.draw() + + + + + + + + + diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/HEX.app/Resources/About/Russian.txt b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/HEX.app/Resources/About/Russian.txt new file mode 100755 index 00000000..06f490e2 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/HEX.app/Resources/About/Russian.txt @@ -0,0 +1 @@ +HEX - мощный редактор файлов в шестнадцатеричном режиме. Он позволяет индивидуально редактировать байты, удалять их, инвертировать, вставлять новые. Незаменимая вещь для тру прогеров! \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/HEX.app/Resources/Icon.pic b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/HEX.app/Resources/Icon.pic new file mode 100755 index 0000000000000000000000000000000000000000..46c7ddbef98a8243608dcb845032a0469145fd8d GIT binary patch literal 167 zcmXYpF%Cj83`1)>O*{NUFtdR`%<#Xy#KgjZ5QpFt+~@C{gtTe~=f$!7nD47sLrT*L z8@>?i+`&+84M-HB%)`{$fs|c334RzqKMt@R_edh54u){!5OY-%YjW*T!hfGvgrA7n Q7%Nj=>KmZtrH2Il0l_>O4gdfE literal 0 HcmV?d00001 diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/HoloClock.app/Main.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/HoloClock.app/Main.lua new file mode 100755 index 00000000..61ca2d5e --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/HoloClock.app/Main.lua @@ -0,0 +1,248 @@ + +require("advancedLua") +local fs = require("filesystem") +local component = require("component") +local unicode = require("unicode") +local event = require("event") +local buffer = require("doubleBuffering") +local MineOSPaths = require("MineOSPaths") +local GUI = require("GUI") + +-------------------------------------------------------------------------------------------- + +if not component.isAvailable("hologram") then + GUI.error("This program needs a Tier 2 holo-projector to work") + return +end + +-------------------------------------------------------------------------------------------- + +local date +local path = MineOSPaths.applicationData .. "/HoloClock/Settings.cfg" +local config = { + dateColor = 0xFFFFFF, + holoScale = 1 +} + +-------------------------------------------------------------------------------------------- + +local symbols = { + ["0"] = { + { 0, 1, 1, 1, 0 }, + { 1, 0, 0, 0, 1 }, + { 1, 0, 0, 0, 1 }, + { 0, 0, 0, 0, 0 }, + { 1, 0, 0, 0, 1 }, + { 1, 0, 0, 0, 1 }, + { 0, 1, 1, 1, 0 }, + }, + ["1"] = { + { 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 1 }, + { 0, 0, 0, 0, 1 }, + { 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 1 }, + { 0, 0, 0, 0, 1 }, + { 0, 0, 0, 0, 0 }, + }, + ["2"] = { + { 0, 1, 1, 1, 0 }, + { 0, 0, 0, 0, 1 }, + { 0, 0, 0, 0, 1 }, + { 0, 1, 1, 1, 0 }, + { 1, 0, 0, 0, 0 }, + { 1, 0, 0, 0, 0 }, + { 0, 1, 1, 1, 0 }, + }, + ["3"] = { + { 0, 1, 1, 1, 0 }, + { 0, 0, 0, 0, 1 }, + { 0, 0, 0, 0, 1 }, + { 0, 1, 1, 1, 0 }, + { 0, 0, 0, 0, 1 }, + { 0, 0, 0, 0, 1 }, + { 0, 1, 1, 1, 0 }, + }, + ["4"] = { + { 0, 0, 0, 0, 0 }, + { 1, 0, 0, 0, 1 }, + { 1, 0, 0, 0, 1 }, + { 0, 1, 1, 1, 0 }, + { 0, 0, 0, 0, 1 }, + { 0, 0, 0, 0, 1 }, + { 0, 0, 0, 0, 0 }, + }, + ["5"] = { + { 0, 1, 1, 1, 0 }, + { 1, 0, 0, 0, 0 }, + { 1, 0, 0, 0, 0 }, + { 0, 1, 1, 1, 0 }, + { 0, 0, 0, 0, 1 }, + { 0, 0, 0, 0, 1 }, + { 0, 1, 1, 1, 0 }, + }, + ["6"] = { + { 0, 1, 1, 1, 0 }, + { 1, 0, 0, 0, 0 }, + { 1, 0, 0, 0, 0 }, + { 0, 1, 1, 1, 0 }, + { 1, 0, 0, 0, 1 }, + { 1, 0, 0, 0, 1 }, + { 0, 1, 1, 1, 0 }, + }, + ["7"] = { + { 0, 1, 1, 1, 0 }, + { 0, 0, 0, 0, 1 }, + { 0, 0, 0, 0, 1 }, + { 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 1 }, + { 0, 0, 0, 0, 1 }, + { 0, 0, 0, 0, 0 }, + }, + ["8"] = { + { 0, 1, 1, 1, 0 }, + { 1, 0, 0, 0, 1 }, + { 1, 0, 0, 0, 1 }, + { 0, 1, 1, 1, 0 }, + { 1, 0, 0, 0, 1 }, + { 1, 0, 0, 0, 1 }, + { 0, 1, 1, 1, 0 }, + }, + ["9"] = { + { 0, 1, 1, 1, 0 }, + { 1, 0, 0, 0, 1 }, + { 1, 0, 0, 0, 1 }, + { 0, 1, 1, 1, 0 }, + { 0, 0, 0, 0, 1 }, + { 0, 0, 0, 0, 1 }, + { 0, 1, 1, 1, 0 }, + }, + [":"] = { + { 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0 }, + { 0, 0, 1, 0, 0 }, + { 0, 0, 0, 0, 0 }, + { 0, 0, 1, 0, 0 }, + { 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0 }, + }, +} + +-------------------------------------------------------------------------------------------- + +local function save() + table.toFile(path, config) +end + +local function load() + if fs.exists(path) then + config = table.fromFile(path) + else + save() + end +end + +-------------------------------------------------------------------------------------------- + +local function drawSymbolOnScreen(x, y, symbol, color) + local xPos = x + for j = 1, #symbols[symbol] do + for i = 1, #symbols[symbol][j] do + if symbols[symbol][j][i] == 1 then + buffer.square(xPos, y, 2, 1, color, 0x000000, " ") + end + xPos = xPos + 2 + end + xPos = x + y = y + 1 + end +end + + +local function drawSymbolOnProjector(x, y, z, symbol) + local xPos = x + for j = 1, #symbols[symbol] do + for i = 1, #symbols[symbol][j] do + if symbols[symbol][j][i] == 1 then + component.hologram.set(xPos, y, z, 1) + else + component.hologram.set(xPos, y, z, 0) + end + xPos = xPos + 1 + end + xPos = x + y = y - 1 + end +end + +local function drawText(x, y, text, color) + for i = 1, unicode.len(text) do + local symbol = unicode.sub(text, i, i) + drawSymbolOnScreen(x, y, symbol, color) + drawSymbolOnProjector(i * 6 + 4, 16, 24, symbol) + x = x + 12 + end +end + +local function changeHoloColor() + component.hologram.setPaletteColor(1, config.dateColor) +end + +local function getDate() + date = string.sub(os.date("%T"), 1, -4) +end + +local function flashback() + buffer.clear(0x0, 0.3) +end + +local function drawOnScreen() + local width, height = 58, 7 + local x, y = math.floor(buffer.getWidth() / 2 - width / 2), math.floor(buffer.getHeight() / 2 - height / 2) + + drawText(x, y, "88:88", 0x000000) + drawText(x, y, date, config.dateColor) + + y = y + 9 + GUI.label(1, y, buffer.getWidth(), 1, config.dateColor, "Press R to randomize clock color, scroll to change projection scale,"):setAlignment(GUI.alignment.horizontal.center, GUI.alignment.vertical.top):draw(); y = y + 1 + GUI.label(1, y, buffer.getWidth(), 1, config.dateColor, "or press Enter to save and quit"):setAlignment(GUI.alignment.horizontal.center, GUI.alignment.vertical.top):draw() + -- GUI.label(1, y, buffer.getWidth(), 1, 0xFFFFFF, ""):draw() + + buffer.draw() +end + +-------------------------------------------------------------------------------------------- + +load() +component.hologram.clear() +changeHoloColor() +component.hologram.setScale(config.holoScale) +flashback() + +while true do + getDate() + drawOnScreen() + + local e = {event.pull(1)} + if e[1] == "scroll" then + if e[5] == 1 then + if config.holoScale < 4 then config.holoScale = config.holoScale + 0.1; component.hologram.setScale(config.holoScale); save() end + else + if config.holoScale > 0.33 then config.holoScale = config.holoScale - 0.1; component.hologram.setScale(config.holoScale); save() end + end + elseif e[1] == "key_down" then + if e[4] == 19 then + config.dateColor = math.random(0x666666, 0xFFFFFF) + changeHoloColor() + save() + elseif e[4] == 28 then + save() + component.hologram.clear() + return + end + end +end + + + + diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/HoloClock.app/Resources/Icon.pic b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/HoloClock.app/Resources/Icon.pic new file mode 100755 index 0000000000000000000000000000000000000000..427da9b14afa26391c6f1131837fc6e678a14e25 GIT binary patch literal 134 zcmXYmy$ypv42AukeYVG-rioG@q_mL`R4l+a%#aB(Sr)n6N!NWp=lj?^ja6cTKokk8 y6^|qt>gbZs_I42EHNdV0UlLGo8POjjrXto6RfH4fW=7=ib8~NIM)jzjXyp&ISPK6D literal 0 HcmV?d00001 diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/HoloEdit.app/Main.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/HoloEdit.app/Main.lua new file mode 100755 index 00000000..0d68db5f --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/HoloEdit.app/Main.lua @@ -0,0 +1,785 @@ +-- Hologram Editor +-- by NEO, Totoro +-- 10/14/2014, all right reserved =) +-- райтс, хуяйтс, резервед ЙОПТА + +local MineOSCore = require("MineOSCore") +local unicode = require('unicode') +local event = require('event') +local term = require('term') +local fs = require('filesystem') +local com = require('component') +local gpu = com.gpu + +local lang = MineOSCore.getCurrentApplicationLocalization() + +-- Константы -- +HOLOH = 32 +HOLOW = 48 + +-- Цвета -- +backcolor = 0x000000 +forecolor = 0xFFFFFF +infocolor = 0x0066FF +errorcolor = 0xFF0000 +helpcolor = 0x006600 +graycolor = 0x080808 +goldcolor = 0xFFDF00 +-- *** -- + + +-- загружаем доп. оборудование +function trytofind(name) + if com.isAvailable(name) then + return com.getPrimary(name) + else + return nil + end +end + +local h = trytofind('hologram') + +-- ========================================= H O L O G R A P H I C S ========================================= -- +holo = {} +function set(x, y, z, value) + if holo[x] == nil then holo[x] = {} end + if holo[x][y] == nil then holo[x][y] = {} end + holo[x][y][z] = value +end +function get(x, y, z) + if holo[x] ~= nil and holo[x][y] ~= nil and holo[x][y][z] ~= nil then + return holo[x][y][z] + else + return 0 + end +end + +function save(filename) + -- сохраняем палитру + file = io.open(filename, 'wb') + for i=1, 3 do + for c=1, 3 do + file:write(string.char(colortable[i][c])) + end + end + -- сохраняем массив + for x=1, HOLOW do + for y=1, HOLOH do + for z=1, HOLOW, 4 do + a = get(x,y,z) + b = get(x,y,z+1) + c = get(x,y,z+2) + d = get(x,y,z+3) + byte = d*64 + c*16 + b*4 + a + file:write(string.char(byte)) + end + end + end + file:close() +end + +local function load(filename) + if fs.exists(filename) then + file = io.open(filename, 'rb') + -- загружаем палитру + for i=1, 3 do + for c=1, 3 do + colortable[i][c] = string.byte(file:read(1)) + end + setHexColor(i,colortable[i][1], + colortable[i][2], + colortable[i][3]) + end + -- загружаем массив + holo = {} + for x=1, HOLOW do + for y=1, HOLOH do + for z=1, HOLOW, 4 do + byte = string.byte(file:read(1)) + for i=0, 3 do + a = byte % 4 + byte = math.floor(byte / 4) + if a ~= 0 then set(x,y,z+i, a) end + end + end + end + end + file:close() + return true + else + --print("[ОШИБКА] Файл "..filename.." не найден.") + return false + end +end + + +-- ============================================= G R A P H I C S ============================================= -- +-- проверка разрешения экрана, для комфортной работы необходимо разрешение > HOLOW по высоте и ширине +OLDWIDTH, OLDHEIGHT = gpu.getResolution() +WIDTH, HEIGHT = gpu.maxResolution() +if HEIGHT < HOLOW+2 then + error(lang.badGPU) +else + WIDTH = HOLOW*2+40 + HEIGHT = HOLOW+2 + gpu.setResolution(WIDTH, HEIGHT) +end +gpu.setForeground(forecolor) +gpu.setBackground(backcolor) + +-- рисуем линию +local strLine = "+" +for i=1, WIDTH do + strLine = strLine..'-' +end +function line(x1, x2, y) + gpu.set(x1,y,string.sub(strLine, 1, x2-x1)) + gpu.set(x2,y,'+') +end + +-- рисуем фрейм +function frame(x1, y1, x2, y2, caption) + line(x1, x2, y1) + line(x1, x2, y2) + + if caption ~= nil then + gpu.set(x1+(x2-x1)/2-unicode.len(caption)/2, y1, caption) + end +end + +-- рисуем сетку +local strGrid = "" +for i=1, HOLOW/2 do + strGrid = strGrid.."██ " +end +function drawGrid(x, y) + gpu.fill(0, y, MENUX, HOLOW, ' ') + gpu.setForeground(graycolor) + for i=0, HOLOW-1 do + if view>0 and i==HOLOH then + gpu.setForeground(forecolor) + line(1, MENUX-1, y+HOLOH) + break + end + gpu.set(x+(i%2)*2, y+i, strGrid) + end + if view == 0 then gpu.setForeground(forecolor) end +end + +-- рисуем цветной прямоугольник +function drawRect(x, y, color) + gpu.set(x, y, "╓──────╖") + gpu.set(x, y+1, "║ ║") + gpu.set(x, y+2, "╙──────╜") + gpu.setForeground(color) + gpu.set(x+2, y+1, "████") + gpu.setForeground(forecolor) +end + +MENUX = HOLOW*2+5 +BUTTONW = 12 + +-- рисуем меню выбора "кисти" +function drawColorSelector() + frame(MENUX, 3, WIDTH-2, 16, lang.palette) + for i=0, 3 do + drawRect(MENUX+1+i*8, 5, hexcolortable[i]) + end + gpu.set(MENUX+1, 10, "R:") + gpu.set(MENUX+1, 11, "G:") + gpu.set(MENUX+1, 12, "B:") +end +function drawColorCursor(force) + if brush.color*8 ~= brush.x then brush.x = brush.color*8 end + if force or brush.gx ~= brush.x then + gpu.set(MENUX+1+brush.gx, 8, " ") + if brush.gx < brush.x then brush.gx = brush.gx + 1 end + if brush.gx > brush.x then brush.gx = brush.gx - 1 end + gpu.set(MENUX+1+brush.gx, 8, " -^--^- ") + end +end +function drawLayerSelector() + frame(MENUX, 16, WIDTH-2, 28, lang.layer) + gpu.set(MENUX+13, 18, lang.level) + gpu.set(MENUX+1, 23, lang.mainLevel) +end +function drawButtonsPanel() + frame(MENUX, 28, WIDTH-2, 36, lang.control) +end + +function mainScreen() + term.clear() + frame(1,1, WIDTH, HEIGHT, "{ Hologram Editor }") + -- "холст" + drawLayer() + drawColorSelector() + drawColorCursor(true) + drawLayerSelector() + drawButtonsPanel() + buttonsDraw() + textboxesDraw() + -- "about" - коротко о создателях + gpu.setForeground(infocolor) + gpu.setBackground(graycolor) + gpu.set(MENUX+3, HEIGHT-11, " Hologram Editor v0.60 Beta ") + gpu.setForeground(forecolor) + gpu.set(MENUX+3, HEIGHT-10, " * * * ") + gpu.set(MENUX+3, HEIGHT-9, lang.developers) + gpu.set(MENUX+3, HEIGHT-8, " NEO, Totoro ") + gpu.set(MENUX+3, HEIGHT-7, " * * * ") + gpu.set(MENUX+3, HEIGHT-6, lang.contact) + gpu.set(MENUX+3, HEIGHT-5, " computercraft.ru/forum ") + gpu.setBackground(backcolor) + -- выход + gpu.set(MENUX, HEIGHT-2, lang.quit) +end + + +-- =============================================== L A Y E R S =============================================== -- +GRIDX = 3 +GRIDY = 2 +function drawLayer() + drawGrid(GRIDX, GRIDY) + -- вид сверху (y) + if view == 0 then + for x=1, HOLOW do + for z=1, HOLOW do + gn = get(x, ghost_layer, z) + n = get(x, layer, z) + if n == 0 and gn ~= 0 then + gpu.setForeground(darkhexcolors[gn]) + gpu.set((GRIDX-2) + x*2, (GRIDY-1) + z, "░░") + end + if n ~= 0 then + gpu.setForeground(hexcolortable[n]) + gpu.set((GRIDX-2) + x*2, (GRIDY-1) + z, "██") + end + end + end + -- вид спереди (z) + elseif view == 1 then + for x=1, HOLOW do + for y=1, HOLOH do + n = get(x, y, layer) + gn = get(x, y, ghost_layer) + if n == 0 and gn ~= 0 then + gpu.setForeground(darkhexcolors[gn]) + gpu.set((GRIDX-2) + x*2, (GRIDY+HOLOH) - y, "░░") + end + if n ~= 0 then + gpu.setForeground(hexcolortable[n]) + gpu.set((GRIDX-2) + x*2, (GRIDY+HOLOH) - y, "██") + end + end + end + -- вид сбоку (x) + else + for z=1, HOLOW do + for y=1, HOLOH do + gn = get(ghost_layer, y, z) + n = get(layer, y, z) + if n == 0 and gn ~= 0 then + gpu.setForeground(darkhexcolors[gn]) + gpu.set((GRIDX+HOLOW*2) - z*2, (GRIDY+HOLOH) - y, "░░") + end + if n ~= 0 then + gpu.setForeground(hexcolortable[n]) + gpu.set((GRIDX+HOLOW*2) - z*2, (GRIDY+HOLOH) - y, "██") + end + end + end + end + gpu.setForeground(forecolor) + -- for messages + repaint = false +end +function fillLayer() + for x=1, HOLOW do + for z=1, HOLOW do + set(x, layer, z, brush.color) + end + end + drawLayer() +end +function clearLayer() + for x=1, HOLOW do + if holo[x] ~= nil then holo[x][layer] = nil end + end + drawLayer() +end + + +-- ============================================== B U T T O N S ============================================== -- +Button = {} +Button.__index = Button +function Button.new(func, x, y, text, color, width) + self = setmetatable({}, Button) + + self.form = '[ ' + if width == nil then width = 0 + else width = (width - unicode.len(text))-4 end + for i=1, math.floor(width/2) do + self.form = self.form.. ' ' + end + self.form = self.form..text + for i=1, math.ceil(width/2) do + self.form = self.form.. ' ' + end + self.form = self.form..' ]' + + self.func = func + + self.x = x; self.y = y + self.color = color + self.visible = true + + return self +end +function Button:draw(color) + if self.visible then + local color = color or self.color + gpu.setBackground(color) + if color > 0x888888 then gpu.setForeground(backcolor) end + gpu.set(self.x, self.y, self.form) + gpu.setBackground(backcolor) + if color > 0x888888 then gpu.setForeground(forecolor) end + end +end +function Button:click(x, y) + if self.visible then + if y == self.y then + if x >= self.x and x < self.x+unicode.len(self.form) then + self.func() + self:draw(self.color/2) + os.sleep(0.1) + self:draw() + return true + end + end + end + return false +end +buttons = {} +function buttonsNew(func, x, y, text, color, width) + table.insert(buttons, Button.new(func, x, y, text, color, width)) +end +function buttonsDraw() + for i=1, #buttons do + buttons[i]:draw() + end +end +function buttonsClick(x, y) + for i=1, #buttons do + buttons[i]:click(x, y) + end +end + +-- ================================ B U T T O N S F U N C T I O N A L I T Y ================================ -- +function exit() running = false end +function nextLayer() + -- ограничения разные для разных видов/проекций + local limit = HOLOH + if view > 0 then limit = HOLOW end + + if layer < limit then + layer = layer + 1 + tb_layer:setValue(layer) + tb_layer:draw(true) + moveGhost() + drawLayer() + end +end +function prevLayer() + if layer > 1 then + layer = layer - 1 + tb_layer:setValue(layer) + tb_layer:draw(true) + moveGhost() + drawLayer() + end +end +function setLayer(value) + local n = tonumber(value) + local limit = HOLOH + if view > 0 then limit = HOLOW end + if n == nil or n < 1 or n > limit then return false end + layer = n + moveGhost() + drawLayer() + return true +end +function nextGhost() + local limit = HOLOH + if view > 0 then limit = HOLOW end + + if ghost_layer_below then + ghost_layer_below = false + if ghost_layer < limit then + ghost_layer = layer + 1 + else ghost_layer = limit end + drawLayer() + else + if ghost_layer < limit then + ghost_layer = ghost_layer + 1 + drawLayer() + end + end +end +function prevGhost() + if not ghost_layer_below then + ghost_layer_below = true + if layer > 1 then + ghost_layer = layer - 1 + else ghost_layer = 1 end + drawLayer() + else + if ghost_layer > 1 then + ghost_layer = ghost_layer - 1 + drawLayer() + end + end +end +function setGhostLayer(value) + local n = tonumber(value) + local limit = HOLOH + if view > 0 then limit = HOLOW end + if n == nil or n < 1 or n > limit then return false end + ghost_layer = n + drawLayer() + return true +end +function moveGhost() + if ghost_layer_below then + if layer > 1 then ghost_layer = layer - 1 + else ghost_layer = 1 end + else + local limit = HOLOH + if view > 0 then limit = HOLOW end + if layer < limit then ghost_layer = layer + 1 + else ghost_layer = limit end + end +end + +function setFilename(str) + if str ~= nil and str ~= '' and unicode.len(str)<30 then + return true + else + return false + end +end + +function setHexColor(n, r, g, b) + local hexcolor = rgb2hex(r,g,b) + hexcolortable[n] = hexcolor + darkhexcolors[n] = bit32.rshift(bit32.band(hexcolor, 0xfefefe), 1) +end +function rgb2hex(r,g,b) + return r*65536+g*256+b +end +function changeRed(value) return changeColor(1, value) end +function changeGreen(value) return changeColor(2, value) end +function changeBlue(value) return changeColor(3, value) end +function changeColor(rgb, value) + if value == nil then return false end + n = tonumber(value) + if n == nil or n < 0 or n > 255 then return false end + -- сохраняем данные в таблицу + colortable[brush.color][rgb] = n + setHexColor(brush.color, colortable[brush.color][1], + colortable[brush.color][2], + colortable[brush.color][3]) + -- обновляем цвета на панельке + for i=0, 3 do + drawRect(MENUX+1+i*8, 5, hexcolortable[i]) + end + return true +end + +function moveSelector(num) + brush.color = num + tb_red:setValue(colortable[num][1]); tb_red:draw(true) + tb_green:setValue(colortable[num][2]); tb_green:draw(true) + tb_blue:setValue(colortable[num][3]); tb_blue:draw(true) +end + +function setTopView() + view = 0 + -- в виде сверху меньше слоев + if layer > HOLOH then layer = HOLOH end + drawLayer() +end +function setFrontView() view = 1; drawLayer() end +function setSideView() view = 2; drawLayer() end + +function drawHologram() + -- проверка на наличие проектора + h = trytofind('hologram') + if h ~= nil then + local depth = h.maxDepth() + -- очищаем + h.clear() + -- отправляем палитру + if depth == 2 then + for i=1, 3 do + h.setPaletteColor(i, hexcolortable[i]) + end + else + h.setPaletteColor(1, hexcolortable[1]) + end + -- отправляем массив + for x=1, HOLOW do + for y=1, HOLOH do + for z=1, HOLOW do + n = get(x,y,z) + if n ~= 0 then + if depth == 2 then + h.set(x,y,z,n) + else + h.set(x,y,z,1) + end + end + end + end + end + end +end + +function newHologram() + holo = {} + drawLayer() +end + +function saveHologram() + local filename = tb_file:getValue() + if filename ~= FILE_REQUEST then + -- выводим предупреждение + showMessage(lang.savingFile, lang.attention, goldcolor) + -- добавляем фирменное расширение =) + if string.sub(filename, -3) ~= '.3d' then + filename = filename..'.3d' + end + -- сохраняем + save(filename) + -- выводим предупреждение + showMessage(lang.complete, lang.attention, goldcolor) + repaint = true + end +end + +function loadHologram() + local filename = tb_file:getValue() + if filename ~= FILE_REQUEST then + -- выводим предупреждение + showMessage(lang.loadingFile, lang.attention, goldcolor) + -- добавляем фирменное расширение =) + if string.sub(filename, -3) ~= '.3d' then + filename = filename..'.3d' + end + -- загружаем + load(filename) + -- обновляем значения в текстбоксах + tb_red:setValue(colortable[brush.color][1]); tb_red:draw(true) + tb_green:setValue(colortable[brush.color][2]); tb_green:draw(true) + tb_blue:setValue(colortable[brush.color][3]); tb_blue:draw(true) + -- обновляем цвета на панельке + for i=0, 3 do + drawRect(MENUX+1+i*8, 5, hexcolortable[i]) + end + -- обновляем слой + drawLayer() + end +end + +-- ============================================ T E X T B O X E S ============================================ -- +Textbox = {} +Textbox.__index = Textbox +function Textbox.new(func, x, y, value, width) + self = setmetatable({}, Textbox) + + self.form = '>' + if width == nil then width = 10 end + for i=1, width-1 do + self.form = self.form..' ' + end + + self.func = func + self.value = tostring(value) + + self.x = x; self.y = y + self.visible = true + + return self +end +function Textbox:draw(content) + if self.visible then + if content then gpu.setBackground(graycolor) end + gpu.set(self.x, self.y, self.form) + if content then gpu.set(self.x+2, self.y, self.value) end + gpu.setBackground(backcolor) + end +end +function Textbox:click(x, y) + if self.visible then + if y == self.y then + if x >= self.x and x < self.x+unicode.len(self.form) then + self:draw(false) + term.setCursor(self.x+2, self.y) + value = string.sub(term.read({self.value}), 1, -2) + if self.func(value) then + self.value = value + end + self:draw(true) + return true + end + end + end + return false +end +function Textbox:setValue(value) + self.value = tostring(value) +end +function Textbox:getValue() + return self.value +end +textboxes = {} +function textboxesNew(func, x, y, value, width) + textbox = Textbox.new(func, x, y, value, width) + table.insert(textboxes, textbox) + return textbox +end +function textboxesDraw() + for i=1, #textboxes do + textboxes[i]:draw(true) + end +end +function textboxesClick(x, y) + for i=1, #textboxes do + textboxes[i]:click(x, y) + end +end + + +-- ============================================= M E S S A G E S ============================================= -- +repaint = false +function showMessage(text, caption, color) + local x = WIDTH/2 - unicode.len(text)/2 - 4 + local y = HEIGHT/2 - 2 + gpu.fill(x, y, unicode.len(text)+8, 5, ' ') + frame(x, y, x+unicode.len(text)+7, y+4, caption) + gpu.setForeground(color) + gpu.set(x+4,y+2, text) + gpu.setForeground(forecolor) +end + + +-- =========================================== M A I N C Y C L E =========================================== -- +-- инициализация +colortable = {{255, 0, 0}, {0, 255, 0}, {0, 102, 255}} +colortable[0] = {0, 0, 0} +hexcolortable = {} +darkhexcolors = {} +for i=0,3 do setHexColor(i, colortable[i][1], colortable[i][2], colortable[i][3]) end +brush = {color = 1, x = 8, gx = 8} +ghost_layer = 1 +ghost_layer_below = true +layer = 1 +view = 0 +running = true + +buttonsNew(exit, WIDTH-BUTTONW-2, HEIGHT-2, lang.exit, errorcolor, BUTTONW) +buttonsNew(drawLayer, MENUX+10, 14, lang.refresh, goldcolor, BUTTONW) +buttonsNew(prevLayer, MENUX+1, 19, '-', infocolor, 5) +buttonsNew(nextLayer, MENUX+7, 19, '+', infocolor, 5) +buttonsNew(setTopView, MENUX+1, 21, lang.fromUp, infocolor, 10) +buttonsNew(setFrontView, MENUX+12, 21, lang.fromFront, infocolor, 10) +buttonsNew(setSideView, MENUX+24, 21, lang.fromSide, infocolor, 9) + +buttonsNew(prevGhost, MENUX+1, 24, lang.lower, infocolor, 6) +buttonsNew(nextGhost, MENUX+10, 24, lang.upper, infocolor, 6) + +buttonsNew(clearLayer, MENUX+1, 26, lang.clear, infocolor, BUTTONW) +buttonsNew(fillLayer, MENUX+2+BUTTONW, 26, lang.fill, infocolor, BUTTONW) + +buttonsNew(drawHologram, MENUX+8, 30, lang.toProjector, goldcolor, 16) +buttonsNew(saveHologram, MENUX+1, 33, lang.save, helpcolor, BUTTONW) +buttonsNew(loadHologram, MENUX+8+BUTTONW, 33, lang.load, infocolor, BUTTONW) +buttonsNew(newHologram, MENUX+1, 35, lang.new, infocolor, BUTTONW) + +tb_red = textboxesNew(changeRed, MENUX+5, 10, '255', WIDTH-MENUX-7) +tb_green = textboxesNew(changeGreen, MENUX+5, 11, '0', WIDTH-MENUX-7) +tb_blue = textboxesNew(changeBlue, MENUX+5, 12, '0', WIDTH-MENUX-7) +tb_layer = textboxesNew(setLayer, MENUX+13, 19, '1', WIDTH-MENUX-15) +tb_ghostlayer = textboxesNew(setGhostLayer, MENUX+19, 24, ' ', WIDTH-MENUX-21) +FILE_REQUEST = lang.enterFileName +tb_file = textboxesNew(setFilename, MENUX+1, 32, FILE_REQUEST, WIDTH-MENUX-3) +mainScreen() + +while running do + if brush.x ~= brush.gx then name, add, x, y, b = event.pull(0.02) + else name, add, x, y, b = event.pull(1.0) end + + if name == 'key_down' then + -- если нажата 'Q' - выходим + if y == 16 then break + elseif y == 41 then + moveSelector(0) + elseif y>=2 and y<=4 then + moveSelector(y-1) + elseif y == 211 then + clearLayer() + end + elseif name == 'touch' then + -- проверка GUI + buttonsClick(x, y) + textboxesClick(x, y) + -- выбор цвета + if x>MENUX+1 and x4 and y<8 then + moveSelector(math.floor((x-MENUX-1)/8)) + end + end + end + if name == 'touch' or name == 'drag' then + -- "рисование" + local limit = HOLOW + if view > 0 then limit = HOLOH end + if x >= GRIDX and x < GRIDX+HOLOW*2 then + if y >= GRIDY and y < GRIDY+limit then + -- перерисуем, если на экране был мессейдж + if repaint then drawLayer() end + -- рассчет клика + if view == 0 then + dx = math.floor((x-GRIDX)/2)+1; gx = dx + dy = layer; gy = ghost_layer + dz = y-GRIDY+1; gz = dz + elseif view == 1 then + dx = math.floor((x-GRIDX)/2)+1; gx = dx + dy = HOLOH - (y-GRIDY); gy = dy + dz = layer; gz = ghost_layer + else + dx = layer; gx = ghost_layer + dy = HOLOH - (y-GRIDY); gy = dy + dz = HOLOW - math.floor((x-GRIDX)/2); gz = dz + end + if b == 0 and brush.color ~= 0 then + set(dx, dy, dz, brush.color) + gpu.setForeground(hexcolortable[brush.color]) + gpu.set(x-(x-GRIDX)%2, y, "██") + else + set(dx, dy, dz, 0) + gpu.setForeground(darkhexcolors[get(gx,gy,gz)]) + gpu.set(x-(x-GRIDX)%2, y, "░░") + end + gpu.setForeground(forecolor) + end + end + end + + drawColorCursor() +end + +-- завершение +term.clear() +gpu.setResolution(OLDWIDTH, OLDHEIGHT) +gpu.setForeground(0xFFFFFF) +gpu.setBackground(0x000000) diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/HoloEdit.app/Resources/Icon.pic b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/HoloEdit.app/Resources/Icon.pic new file mode 100755 index 0000000000000000000000000000000000000000..1e74fc67118c596b853a792f88e0c76e0901c256 GIT binary patch literal 86 zcmXZR!3lss3`EiVNzA4Udl3Xr)?`2SxQ4~k@c!J#b!LU8v13>uByc*8vd;cuQp#h? R;f$wAE|sX+^km)f?*}I|1%3bk literal 0 HcmV?d00001 diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/HoloEdit.app/Resources/Localization/English.lang b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/HoloEdit.app/Resources/Localization/English.lang new file mode 100755 index 00000000..fe0905bc --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/HoloEdit.app/Resources/Localization/English.lang @@ -0,0 +1,29 @@ +{ + badGPU = "[ERROR] Your monitor/GPU doesn't support required resolution.", + palette = "[ Palette ]", + control = "[ Control ]", + layer = "[ Layer ]", + level = "Hologram level:", + mainLevel = "Main level:", + quit = "Exit: 'Q' or ", + developers = "Developers: ", + contact = "Contact", + savingFile = "Saving file...", + loadingFile = "Loading file...", + attention = "[ Attention ]", + complete = "[ Done! ]", + exit = "Quit", + refresh = "Refresh", + fromUp = "Top", + fromFront = "Front", + fromSide = "Side", + upper = "Upper", + lower = "Lower", + clear = "Clear", + fill = "Fill", + toProjector = "To projector", + save = "Save", + load = "Load", + new = "New file", + enterFileName = "Enter file name here:", +} diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/HoloEdit.app/Resources/Localization/Russian.lang b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/HoloEdit.app/Resources/Localization/Russian.lang new file mode 100755 index 00000000..5780c77f --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/HoloEdit.app/Resources/Localization/Russian.lang @@ -0,0 +1,29 @@ +{ + badGPU = "[ОШИБКА] Ваш монитор/видеокарта не поддерживает требуемое разрешение.", + palette = "[ Палитра ]", + control = "[ Управление ]", + layer = "[ Слой ]", + level = "Уровень голограммы:", + mainLevel = "Направляющий уровень:", + quit = "Выход: 'Q' или ", + developers = "Разработчики: ", + contact = "Контакты:", + savingFile = "Сохраняю файл...", + loadingFile = "Загружаю файл...", + attention = "[ Внимание ]", + complete = "[ Файл сохранен! ]", + exit = "Выход", + refresh = "Обновить", + fromUp = "Сверху", + fromFront = "Спереди", + fromSide = "Сбоку", + upper = "Выше", + lower = "Ниже", + clear = "Очистить", + fill = "Залить", + toProjector = "На проектор", + save = "Сохранить", + load = "Загрузить", + new = "Новый файл", + enterFileName = "Введите сюда имя файла", +} diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/InfoPanel.app/Main.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/InfoPanel.app/Main.lua new file mode 100755 index 00000000..cbb299fd --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/InfoPanel.app/Main.lua @@ -0,0 +1,186 @@ +local ecs = require("ECSAPI") +local MineOSCore = require("MineOSCore") +local xml = require("xmlParser") +local image = require("image") +local event = require("event") +local unicode = require("unicode") +local fs = require("filesystem") +local gpu = require("component").gpu + +------------------------------------------------------------------------------------------------------------------ + +local config = { + scale = 0.63, + leftBarWidth = 20, + scrollSpeed = 6, + pathToInfoPanelFolder = MineOSCore.getCurrentApplicationResourcesDirectory() .. "Pages/", + colors = { + leftBar = 0xEEEEEE, + leftBarText = 0x262626, + leftBarSelection = 0x00C6FF, + leftBarSelectionText = 0xFFFFFF, + scrollbarBack = 0xEEEEEE, + scrollbarPipe = 0x3366CC, + background = 0x262626, + text = 0xFFFFFF, + }, +} + +local xOld, yOld = gpu.getResolution() +ecs.setScale(config.scale) +local xSize, ySize = gpu.getResolution() + +fs.makeDirectory(config.pathToInfoPanelFolder) +local currentFile = 1 +local fileList +local stroki = {} +local currentString = 1 +local stringsHeightLimit = ySize - 2 +local stringsWidthLimit = xSize - config.leftBarWidth - 4 + +------------------------------------------------------------------------------------------------------------------ + +local obj = {} +local function newObj(class, name, ...) + obj[class] = obj[class] or {} + obj[class][name] = {...} +end + +local function drawLeftBar() + --ecs.square(1, 1, config.leftBarWidth, ySize, config.colors.leftBar) + fileList = ecs.getFileList(config.pathToInfoPanelFolder) + obj["Files"] = {} + local yPos = 1, 1 + for i = 1, #fileList do + if i == currentFile then + newObj("Files", i, ecs.drawButton(1, yPos, config.leftBarWidth, 3, ecs.hideFileFormat(fileList[i]), config.colors.leftBarSelection, config.colors.leftBarSelectionText)) + else + if i % 2 == 0 then + newObj("Files", i, ecs.drawButton(1, yPos, config.leftBarWidth, 3, ecs.stringLimit("end", ecs.hideFileFormat(fileList[i]), config.leftBarWidth - 2), config.colors.leftBar, config.colors.leftBarText)) + else + newObj("Files", i, ecs.drawButton(1, yPos, config.leftBarWidth, 3, ecs.stringLimit("end", ecs.hideFileFormat(fileList[i]), config.leftBarWidth - 2), config.colors.leftBar - 0x111111, config.colors.leftBarText)) + end + end + yPos = yPos + 3 + end + ecs.square(1, yPos, config.leftBarWidth, ySize - yPos + 1, config.colors.leftBar) +end + +local function loadFile() + currentString = 1 + stroki = {} + local file = io.open(config.pathToInfoPanelFolder .. fileList[currentFile], "r") + for line in file:lines() do table.insert(stroki, xml.collect(line)) end + file:close() +end + +local function drawMain() + local x, y = config.leftBarWidth + 3, 2 + local xPos, yPos = x, y + + ecs.square(xPos, yPos, xSize - config.leftBarWidth - 5, ySize, config.colors.background) + gpu.setForeground(config.colors.text) + + for line = currentString, (stringsHeightLimit + currentString - 1) do + if stroki[line] then + for i = 1, #stroki[line] do + if type(stroki[line][i]) == "table" then + if stroki[line][i].label == "color" then + gpu.setForeground(tonumber(stroki[line][i][1])) + elseif stroki[line][i].label == "image" then + local bg, fg = gpu.getBackground(), gpu.getForeground() + local picture = image.load(stroki[line][i][1]) + image.draw(xPos, yPos, picture) + yPos = yPos + picture.height - 1 + gpu.setForeground(fg) + gpu.setBackground(bg) + end + else + gpu.set(xPos, yPos, stroki[line][i]) + xPos = xPos + unicode.len(stroki[line][i]) + end + end + yPos = yPos + 1 + xPos = x + else + break + end + end + +end + +local function drawScrollBar() + local name + name = "⬆"; newObj("Scroll", name, ecs.drawButton(xSize - 2, 1, 3, 3, name, config.colors.leftBarSelection, config.colors.leftBarSelectionText)) + name = "⬇"; newObj("Scroll", name, ecs.drawButton(xSize - 2, ySize - 2, 3, 3, name, config.colors.leftBarSelection, config.colors.leftBarSelectionText)) + + ecs.srollBar(xSize - 2, 4, 3, ySize - 6, #stroki, currentString, config.colors.scrollbarBack, config.colors.scrollbarPipe) +end + +------------------------------------------------------------------------------------------------------------------ + +ecs.prepareToExit() +drawLeftBar() +loadFile() +drawMain() +drawScrollBar() + +while true do + local e = {event.pull()} + if e[1] == "touch" then + for key in pairs(obj["Files"]) do + if ecs.clickedAtArea(e[3], e[4], obj["Files"][key][1], obj["Files"][key][2], obj["Files"][key][3], obj["Files"][key][4]) then + currentFile = key + loadFile() + drawLeftBar() + drawMain() + drawScrollBar() + break + end + end + + for key in pairs(obj["Scroll"]) do + if ecs.clickedAtArea(e[3], e[4], obj["Scroll"][key][1], obj["Scroll"][key][2], obj["Scroll"][key][3], obj["Scroll"][key][4]) then + ecs.drawButton(obj["Scroll"][key][1], obj["Scroll"][key][2], 3, 3, key, config.colors.leftBarSelectionText, config.colors.leftBarSelection) + os.sleep(0.2) + ecs.drawButton(obj["Scroll"][key][1], obj["Scroll"][key][2], 3, 3, key, config.colors.leftBarSelection, config.colors.leftBarSelectionText) + + if key == "⬆" then + if currentString > config.scrollSpeed then + currentString = currentString - config.scrollSpeed + drawMain() + drawScrollBar() + end + else + if currentString < (#stroki - config.scrollSpeed + 1) then + currentString = currentString + config.scrollSpeed + drawMain() + drawScrollBar() + end + end + + break + end + end + + elseif e[1] == "key_down" then + if e[4] == 28 then + gpu.setResolution(xOld, yOld) + ecs.prepareToExit() + return + end + end +end + + + + + + + + + + + + + diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/InfoPanel.app/Resources/About/Russian.txt b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/InfoPanel.app/Resources/About/Russian.txt new file mode 100755 index 00000000..b82f7960 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/InfoPanel.app/Resources/About/Russian.txt @@ -0,0 +1 @@ +Эта программа предназначена для визуального отображения информации для пользователей компьютера, она идеально впишется в ваш спавн, дом или милитаризированный бункер. Файлы с информацией хранятся в папке MineOS/System/InfoPanel, вы можете изменить их в любое время. \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/InfoPanel.app/Resources/Icon.pic b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/InfoPanel.app/Resources/Icon.pic new file mode 100755 index 0000000000000000000000000000000000000000..8f074d1222f4742b9f1a67b7d5eba556df50cffb GIT binary patch literal 171 zcmXYqO$q`r42An8X{Q_S;JVik#94?iH<*QjAi5H~{g`R92`}&WcAC$n8YvwvY%~O( z{2YS5q;)V2L9Ze3?p8%Uo@gj`bZas&$(GAhwO0xFFAA00Buttex0xFFFFFF! С помощью этой панели +вы всегда можете узнать последние новости сервера и ознакомиться с +основными устоями, сложившимися в нашем обществе. Новичкам рекомендуется +прочитать все имеющиеся вкладки. + + История сервера + +Buttex существует с незапамятного 2010 года, когда кубач еще только +набирал свою популярность. Основным направлением сервера всегда были +красивые постройки, редстоун-схемы, а с недавних пор еще и кодинг +на компьютерах, предоставляемых модами. На сервере всегда царила +дружеская и домашняя атмосфера, которую мы поддерживаем из года в год. +Иногда, когда нашей теплой компании надоедало играть в кубики, мы +делали перерыв, но в скором времени сервер возрождался раз за разом. +Сейчас на дворе ноябрь, 2015 год, и мы вновь открываем Buttex для всех +желающих в новом формате - с собственным лаунчером, интересными модами +и грамотно настроенными плагинами. + + Приятной игры, дорогие мои! \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/InfoPanel.app/Resources/Pages/Rules.txt b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/InfoPanel.app/Resources/Pages/Rules.txt new file mode 100755 index 00000000..c204a560 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/InfoPanel.app/Resources/Pages/Rules.txt @@ -0,0 +1,49 @@ + Правила + +На сервере существует лишь одно правило: 0xFF5555нет никаких правил0xFFFFFF. + +Вам дозволяется абсолютно все: гриферство, читерство, мат в чате, +оскорбление админа и других игроков, ибо все это - неотъемлемая +часть нашего бытия. Вы можете быть культурным и святым человеком, +а можете крушить все направо-налево, строя хуи и свастики из грязи. +Помните, что не существует плохих или хороших деяний, есть лишь +поступки и их закономерные последствия. Таким образом, если вы +загадите личную хату админа, то он выебет вас по самые гланды, а если +будете неадекватным малым, то крайне велика вероятность, что вам +всадят нож в спину. + + Багоюз + +Любые баги - это вина администрации, безответственно отнесшейся к +исправлению ошибок плагинов и модов. Таким образом, если вы нашли +способ дюпа, способ использования недочетов сервера себе на благо, +крайне рекомендуется сообщить об этом админам - вы получите приятный +бонус за бдительность, а другие игроки получат честную игру. + + Флуд + +Если в чате творится полный бардак, и нежелательные личности флудят +или просто раздражают вас - используйте Систему Серверной Поддержки +Игроков, чтобы навести порядок: команда "0xFFAA00сервер замуть Cyka0xFFFFFF" +откроет голосование за мут игрока под ником Cyka. + + Убийства + +Вас убил сильно развитый игрок? Ну что ж, печально. Чтобы избежать +подобных эксцессов, защищайте ваше жилище тщательнее, закрывайте +дыры в обороне, копайте больше ресурсов для создания орудия мести. + + Гриферство + +Чей-то робот своровал ваши солнечные панели и механизмы IC2? Какая +неприятность! Чтобы защититься от этого, грамотно приватьте зону +вашего дома, закрывайте все щели, ставьте двери, которые нельзя +открыть редстоуном. Если вы узнали имя вора, обратитесь к обладателям +мощной брони и оружия, чтобы восстановить справедливость. + + Заключение + +Причиной всему этому "беззаконию" послужило наличие слишком больших +ограничений на большинстве серверов, где складывается впечатление, +что админы - нежные телки с завышенным ЧСВ. Но долой такую чушь! +Добро пожаловать на 0xFFAA00Buttex0xFFFFFF, где каждый сам себе хозяин! \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/InfoPanel.app/Resources/Pages/SSPI.txt b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/InfoPanel.app/Resources/Pages/SSPI.txt new file mode 100755 index 00000000..a90842aa --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/InfoPanel.app/Resources/Pages/SSPI.txt @@ -0,0 +1,35 @@ + ССПИ + +У нас имеется автоматическая 0xFFAA00Серверная Система Поддержки Игроков0xFFFFFF, +сокращенно 0xFFAA00ССПИ0xFFFFFF. Она предназначена для минимизации контактов вида +игрок-админ, заменяя по сути целый штаб модераторов. Данная система +работает через чат в виде виртуального собеседника - вводите указанные +ниже команды и получайте результат. + +● 0xFFAA00Сервер дай ресов0xFFFFFF - вы получите произвольное количество произвольных + произвольно выбранных ресурсов. +● 0xFFAA00Сервер замуть [Username]0xFFFFFF - откроется публичное голосование за мут + игрока с ником Username. +● 0xFFAA00Сервер как дела 0xFFFFFF- если ССПИ будет в хорошем расположении духа, она + может рассказать, как у нее дела. За последствия плохого настроения + системы мы ответственности не несем. +● 0xFFAA00Сервер очисти чат0xFFFFFF - очищает чат лично для вас, отправляя вам в ЛС + большое количество пробелов. +● 0xFFAA00Сервер скажи админам [сообщение]0xFFFFFF - отправляет всем администраторам + сообщение, даже если они отсутствуют на сервере. Впоследствии они + смогут его прочесть. +● 0xFFAA00Сервер вероятность [событие]0xFFFFFF - вычисляет вероятность какого-либо + события. Полезно, чтобы доказать кому-то, что он пидор. +● 0xFFAA00Сервер хеш [фраза]0xFFFFFF - возвращает хеш-сумму указанной фразы, просчитанную + по алгоритму SHA2-256. + +Следующие команды для ССПИ предназначены только для администраторов, +у простых смертных к ним нет доступа: + +● 0xFFAA00Сервер отпизди [Username]0xFFFFFF - убивает игрока с ником Username. +● 0xFFAA00Сервер сделай день/ночь0xFFFFFF - устанавливает указанное время суток. +● 0xFFAA00Сервер включи/выключи свет0xFFFFFF - управление освещение спавна. + +Кроме того, ключевое слово "Сервер" имеет несколько синонимов +для удобства: серв, сервак, ССПИ, а также все эти вариации, +написанные с заглавной буквы. \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/MineCode IDE.app/Main.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/MineCode IDE.app/Main.lua new file mode 100755 index 00000000..535326b8 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/MineCode IDE.app/Main.lua @@ -0,0 +1,1855 @@ + +---------------------------------------------------- Libraries ---------------------------------------------------- + +-- "/MineOS/Applications/MineCode IDE.app/MineCode IDE.lua" -o /OS.lua + +-- package.loaded.syntax = nil +-- package.loaded.ECSAPI = nil +-- package.loaded.GUI = nil +-- package.loaded.MineOSCore = nil + +local args = {...} +require("advancedLua") +local computer = require("computer") +local component = require("component") +local fs = require("filesystem") +local buffer = require("doubleBuffering") +local event = require("event") +local syntax = require("syntax") +local unicode = require("unicode") +local web = require("web") +local image = require("image") +local keyboard = require("keyboard") +local GUI = require("GUI") +local MineOSPaths = require("MineOSPaths") +local MineOSCore = require("MineOSCore") +local MineOSInterface = require("MineOSInterface") + +---------------------------------------------------- Constants ---------------------------------------------------- + +local about = { + "MineCode IDE", + "Copyright © 2014-2017 ECS Inc.", + " ", + "Developers:", + " ", + "Timofeev Igor, vk.com/id7799889", + "Trifonov Gleb, vk.com/id88323331", + " ", + "Testers:", + " ", + "Semyonov Semyon, vk.com/id92656626", + "Prosin Mihail, vk.com/id75667079", + "Shestakov Timofey, vk.com/id113499693", + "Bogushevich Victoria, vk.com/id171497518", + "Vitvitskaya Yana, vk.com/id183425349", + "Golovanova Polina, vk.com/id226251826", +} + +local config = { + leftTreeViewWidth = 26, + syntaxColorScheme = syntax.colorScheme, + scrollSpeed = 8, + cursorColor = 0x00A8FF, + cursorSymbol = "┃", + cursorBlinkDelay = 0.5, + doubleClickDelay = 0.4, + screenResolution = {}, + enableAutoBrackets = true, + highlightLuaSyntax = true, + enableAutocompletion = true, +} +config.screenResolution.width, config.screenResolution.height = component.gpu.getResolution() + +local colors = { + topToolBar = 0xDDDDDD, + bottomToolBar = { + background = 0x3C3C3C, + buttons = 0x2D2D2D, + buttonsText = 0xFFFFFF, + }, + topMenu = { + backgroundColor = 0xEEEEEE, + textColor = 0x444444, + backgroundPressedColor = 0x3366CC, + textPressedColor = 0xFFFFFF, + }, + title = { + default = { + sides = 0x555555, + background = 0x3C3C3C, + text = 0xEEEEEE, + }, + onError = { + sides = 0xCC4940, + background = 0x880000, + text = 0xEEEEEE, + }, + }, + highlights = { + onError = 0xFF4940, + onBreakpoint = 0x990000, + } +} + +local possibleBrackets = { + openers = { + ["{"] = "}", + ["["] = "]", + ["("] = ")", + ["\""] = "\"", + ["\'"] = "\'" + }, + closers = { + ["}"] = "{", + ["]"] = "[", + [")"] = "(", + ["\""] = "\"", + ["\'"] = "\'" + } +} + +local cursor = { + position = { + symbol = 1, + line = 1 + }, + blinkState = false +} + +local scriptCoroutine +local resourcesPath = MineOSCore.getCurrentApplicationResourcesDirectory() +local configPath = MineOSPaths.applicationData .. "MineCode IDE/Config.cfg" +local localization = MineOSCore.getLocalization(resourcesPath .. "Localization/") +local findStartFrom +local clipboard +local breakpointLines +local lastErrorLine +local autocompleteDatabase + +------------------------------------------------------------------------------------------------------------------ + +local function convertTextPositionToScreenCoordinates(symbol, line) + return + mainContainer.codeView.codeAreaPosition + symbol - mainContainer.codeView.fromSymbol + 1, + mainContainer.codeView.y + line - mainContainer.codeView.fromLine +end + +local function convertScreenCoordinatesToTextPosition(x, y) + return x - mainContainer.codeView.codeAreaPosition + mainContainer.codeView.fromSymbol - 1, y - mainContainer.codeView.y + mainContainer.codeView.fromLine +end + +------------------------------------------------------------------------------------------------------------------ + +local function saveConfig() + table.toFile(configPath, config) +end + +local function loadConfig() + if fs.exists(configPath) then + config = table.fromFile(configPath) + syntax.colorScheme = config.syntaxColorScheme + else + saveConfig() + end +end + +------------------------------------------------------------------------------------------------------------------ + +local function updateAutocompleteDatabaseFromString(str, value) + for word in str:gmatch("[%a%d%_]+") do + if not word:match("^%d+$") then + autocompleteDatabase[word] = value + end + end +end + +local function updateAutocompleteDatabaseFromFile() + if config.enableAutocompletion then + autocompleteDatabase = {} + for line = 1, #mainContainer.codeView.lines do + updateAutocompleteDatabaseFromString(mainContainer.codeView.lines[line], true) + end + end +end + +local function getCurrentWordStartingAndEnding(fromSymbol) + local shittySymbolsRegexp, from, to = "[%s%c%p]" + + for i = fromSymbol, 1, -1 do + if unicode.sub(mainContainer.codeView.lines[cursor.position.line], i, i):match(shittySymbolsRegexp) then break end + from = i + end + + for i = fromSymbol, unicode.len(mainContainer.codeView.lines[cursor.position.line]) do + if unicode.sub(mainContainer.codeView.lines[cursor.position.line], i, i):match(shittySymbolsRegexp) then break end + to = i + end + + return from, to +end + +local function aplhabeticalSort(t) + table.sort(t, function(a, b) return a[1] < b[1] end) +end + +local function getAutocompleteDatabaseMatches(stringToSearch) + local matches = {} + + for word in pairs(autocompleteDatabase) do + if word ~= stringToSearch then + local match = word:match("^" .. stringToSearch) + if match then + table.insert(matches, { word, match }) + end + end + end + + aplhabeticalSort(matches) + return matches +end + +local function hideAutocompleteWindow() + mainContainer.autocompleteWindow.hidden = true +end + +local function showAutocompleteWindow() + if config.enableAutocompletion then + mainContainer.autocompleteWindow.currentWordStarting, mainContainer.autocompleteWindow.currentWordEnding = getCurrentWordStartingAndEnding(cursor.position.symbol - 1) + + if mainContainer.autocompleteWindow.currentWordStarting then + mainContainer.autocompleteWindow.matches = getAutocompleteDatabaseMatches( + unicode.sub( + mainContainer.codeView.lines[cursor.position.line], + mainContainer.autocompleteWindow.currentWordStarting, + mainContainer.autocompleteWindow.currentWordEnding + ) + ) + + if #mainContainer.autocompleteWindow.matches > 0 then + mainContainer.autocompleteWindow.fromMatch, mainContainer.autocompleteWindow.currentMatch = 1, 1 + mainContainer.autocompleteWindow.hidden = false + else + hideAutocompleteWindow() + end + else + hideAutocompleteWindow() + end + end +end + +local function toggleEnableAutocompleteDatabase() + config.enableAutocompletion = not config.enableAutocompletion + autocompleteDatabase = {} + saveConfig() +end + +------------------------------------------------------------------------------------------------------------------ + +local function calculateSizes() + mainContainer.width, mainContainer.height = buffer.getResolution() + + if mainContainer.leftTreeView.hidden then + mainContainer.codeView.localX, mainContainer.codeView.width = 1, mainContainer.width + mainContainer.bottomToolBar.localX, mainContainer.bottomToolBar.width = mainContainer.codeView.localX, mainContainer.codeView.width + else + mainContainer.codeView.localX, mainContainer.codeView.width = mainContainer.leftTreeView.width + 1, mainContainer.width - mainContainer.leftTreeView.width + mainContainer.bottomToolBar.localX, mainContainer.bottomToolBar.width = mainContainer.codeView.localX, mainContainer.codeView.width + end + + if mainContainer.topToolBar.hidden then + mainContainer.leftTreeView.localY, mainContainer.leftTreeView.height = 2, mainContainer.height - 1 + mainContainer.codeView.localY, mainContainer.codeView.height = 2, mainContainer.height - 1 + mainContainer.errorContainer.localY = 2 + else + mainContainer.leftTreeView.localY, mainContainer.leftTreeView.height = 5, mainContainer.height - 4 + mainContainer.codeView.localY, mainContainer.codeView.height = 5, mainContainer.height - 4 + mainContainer.errorContainer.localY = 5 + end + + if mainContainer.bottomToolBar.hidden then + + else + mainContainer.codeView.height = mainContainer.codeView.height - 3 + end + + mainContainer.leftTreeViewResizer.localX = mainContainer.leftTreeView.width - 2 + mainContainer.leftTreeViewResizer.localY = math.floor(mainContainer.leftTreeView.localY + mainContainer.leftTreeView.height / 2 - mainContainer.leftTreeViewResizer.height / 2) + + mainContainer.settingsContainer.width, mainContainer.settingsContainer.height = mainContainer.width, mainContainer.height + mainContainer.settingsContainer.backgroundPanel.width, mainContainer.settingsContainer.backgroundPanel.height = mainContainer.settingsContainer.width, mainContainer.settingsContainer.height + + mainContainer.bottomToolBar.localY = mainContainer.height - 2 + mainContainer.bottomToolBar.findButton.localX = mainContainer.bottomToolBar.width - mainContainer.bottomToolBar.findButton.width + 1 + mainContainer.bottomToolBar.inputField.width = mainContainer.bottomToolBar.width - mainContainer.bottomToolBar.inputField.localX - mainContainer.bottomToolBar.findButton.width + 1 + + mainContainer.topToolBar.width, mainContainer.topToolBar.backgroundPanel.width = mainContainer.width, mainContainer.width + mainContainer.titleTextBox.width = math.floor(mainContainer.topToolBar.width * 0.32) + mainContainer.titleTextBox.localX = math.floor(mainContainer.topToolBar.width / 2 - mainContainer.titleTextBox.width / 2) + mainContainer.runButton.localX = mainContainer.titleTextBox.localX - mainContainer.runButton.width - 2 + mainContainer.toggleSyntaxHighlightingButton.localX = mainContainer.runButton.localX - mainContainer.toggleSyntaxHighlightingButton.width - 2 + mainContainer.addBreakpointButton.localX = mainContainer.toggleSyntaxHighlightingButton.localX - mainContainer.addBreakpointButton.width - 2 + mainContainer.toggleLeftToolBarButton.localX = mainContainer.titleTextBox.localX + mainContainer.titleTextBox.width + 2 + mainContainer.toggleBottomToolBarButton.localX = mainContainer.toggleLeftToolBarButton.localX + mainContainer.toggleLeftToolBarButton.width + 2 + mainContainer.toggleTopToolBarButton.localX = mainContainer.toggleBottomToolBarButton.localX + mainContainer.toggleBottomToolBarButton.width + 2 + + mainContainer.RAMUsageProgressBar.localX = mainContainer.toggleTopToolBarButton.localX + mainContainer.toggleTopToolBarButton.width + 3 + mainContainer.RAMUsageProgressBar.width = mainContainer.topToolBar.width - mainContainer.RAMUsageProgressBar.localX - 3 + + mainContainer.errorContainer.localX, mainContainer.errorContainer.width = mainContainer.titleTextBox.localX, mainContainer.titleTextBox.width + mainContainer.errorContainer.backgroundPanel.width, mainContainer.errorContainer.errorTextBox.width = mainContainer.errorContainer.width, mainContainer.errorContainer.width - 4 + + mainContainer.topMenu.width = mainContainer.width +end + +local function updateTitle() + if not mainContainer.topToolBar.hidden then + if mainContainer.errorContainer.hidden then + mainContainer.titleTextBox.lines[1] = string.limit(localization.file .. ": " .. (mainContainer.leftTreeView.selectedItem or localization.none), mainContainer.titleTextBox.width - 4) + mainContainer.titleTextBox.lines[2] = string.limit(localization.cursor .. cursor.position.line .. localization.line .. cursor.position.symbol .. localization.symbol, mainContainer.titleTextBox.width - 4) + if mainContainer.codeView.selections[1] then + local countOfSelectedLines = mainContainer.codeView.selections[1].to.line - mainContainer.codeView.selections[1].from.line + 1 + local countOfSelectedSymbols + if mainContainer.codeView.selections[1].from.line == mainContainer.codeView.selections[1].to.line then + countOfSelectedSymbols = unicode.len(unicode.sub(mainContainer.codeView.lines[mainContainer.codeView.selections[1].from.line], mainContainer.codeView.selections[1].from.symbol, mainContainer.codeView.selections[1].to.symbol)) + else + countOfSelectedSymbols = unicode.len(unicode.sub(mainContainer.codeView.lines[mainContainer.codeView.selections[1].from.line], mainContainer.codeView.selections[1].from.symbol, -1)) + for line = mainContainer.codeView.selections[1].from.line + 1, mainContainer.codeView.selections[1].to.line - 1 do + countOfSelectedSymbols = countOfSelectedSymbols + unicode.len(mainContainer.codeView.lines[line]) + end + countOfSelectedSymbols = countOfSelectedSymbols + unicode.len(unicode.sub(mainContainer.codeView.lines[mainContainer.codeView.selections[1].to.line], 1, mainContainer.codeView.selections[1].to.symbol)) + end + mainContainer.titleTextBox.lines[3] = string.limit(localization.selection .. countOfSelectedLines .. localization.lines .. countOfSelectedSymbols .. localization.symbols, mainContainer.titleTextBox.width - 4) + else + mainContainer.titleTextBox.lines[3] = string.limit(localization.selection .. localization.none, mainContainer.titleTextBox.width - 4) + end + else + mainContainer.titleTextBox.lines[1], mainContainer.titleTextBox.lines[3] = " ", " " + if lastErrorLine then + mainContainer.titleTextBox.lines[2] = localization.runtimeError + else + mainContainer.titleTextBox.lines[2] = localization.debugging .. (_G.MineCodeIDEDebugInfo and _G.MineCodeIDEDebugInfo.line or "N/A") + end + end + end +end + +local function gotoLine(line) + mainContainer.codeView.fromLine = math.ceil(line - mainContainer.codeView.height / 2) + if mainContainer.codeView.fromLine < 1 then + mainContainer.codeView.fromLine = 1 + elseif mainContainer.codeView.fromLine > #mainContainer.codeView.lines then + mainContainer.codeView.fromLine = #mainContainer.codeView.lines + end +end + +local function updateHighlights() + mainContainer.codeView.highlights = {} + + if breakpointLines then + for i = 1, #breakpointLines do + mainContainer.codeView.highlights[breakpointLines[i]] = colors.highlights.onBreakpoint + end + end + + if lastErrorLine then + mainContainer.codeView.highlights[lastErrorLine] = colors.highlights.onError + end +end + +local function calculateErrorContainerSizeAndBeep(hideBreakpointButtons, frequency, times) + mainContainer.errorContainer.errorTextBox.height = #mainContainer.errorContainer.errorTextBox.lines + mainContainer.errorContainer.height = 2 + mainContainer.errorContainer.errorTextBox.height + mainContainer.errorContainer.backgroundPanel.height = mainContainer.errorContainer.height + + mainContainer.errorContainer.breakpointExitButton.hidden, mainContainer.errorContainer.breakpointContinueButton.hidden = hideBreakpointButtons, hideBreakpointButtons + if not hideBreakpointButtons then + mainContainer.errorContainer.height = mainContainer.errorContainer.height + 1 + mainContainer.errorContainer.breakpointExitButton.localY, mainContainer.errorContainer.breakpointContinueButton.localY = mainContainer.errorContainer.height, mainContainer.errorContainer.height + mainContainer.errorContainer.breakpointExitButton.width = math.floor(mainContainer.errorContainer.width / 2) + mainContainer.errorContainer.breakpointContinueButton.localX, mainContainer.errorContainer.breakpointContinueButton.width = mainContainer.errorContainer.breakpointExitButton.width + 1, mainContainer.errorContainer.width - mainContainer.errorContainer.breakpointExitButton.width + end + + updateTitle() + mainContainer:draw() + buffer.draw() + + for i = 1, times do component.computer.beep(frequency, 0.08) end +end + +local function showBreakpointMessage(variables) + mainContainer.titleTextBox.colors.background, mainContainer.titleTextBox.colors.text = colors.title.onError.background, colors.title.onError.text + mainContainer.errorContainer.hidden = false + + mainContainer.errorContainer.errorTextBox:setAlignment(GUI.alignment.horizontal.center, GUI.alignment.vertical.top) + mainContainer.errorContainer.errorTextBox.lines = {} + + for variable, value in pairs(variables) do + table.insert(mainContainer.errorContainer.errorTextBox.lines, variable .. " = " .. value) + end + + if #mainContainer.errorContainer.errorTextBox.lines > 0 then + table.insert(mainContainer.errorContainer.errorTextBox.lines, 1, " ") + table.insert(mainContainer.errorContainer.errorTextBox.lines, 1, {text = localization.variables, color = 0x0}) + else + table.insert(mainContainer.errorContainer.errorTextBox.lines, 1, {text = localization.variablesNotAvailable, color = 0x0}) + end + + calculateErrorContainerSizeAndBeep(false, 1800, 1) +end + +local function showErrorContainer(errorCode) + mainContainer.titleTextBox.colors.background, mainContainer.titleTextBox.colors.text = colors.title.onError.background, colors.title.onError.text + mainContainer.errorContainer.hidden = false + + mainContainer.errorContainer.errorTextBox:setAlignment(GUI.alignment.horizontal.left, GUI.alignment.vertical.top) + mainContainer.errorContainer.errorTextBox.lines = string.wrap({errorCode}, mainContainer.errorContainer.errorTextBox.width) + + -- Извлекаем ошибочную строку текущего скрипта + lastErrorLine = tonumber(errorCode:match("%:(%d+)%: in main chunk")) + if lastErrorLine then + -- Делаем поправку на количество брейкпоинтов в виде вставленных дебаг-строк + if breakpointLines then + local countOfBreakpointsBeforeLastErrorLine = 0 + for i = 1, #breakpointLines do + if breakpointLines[i] < lastErrorLine then + countOfBreakpointsBeforeLastErrorLine = countOfBreakpointsBeforeLastErrorLine + 1 + else + break + end + end + lastErrorLine = lastErrorLine - countOfBreakpointsBeforeLastErrorLine + end + gotoLine(lastErrorLine) + end + updateHighlights() + calculateErrorContainerSizeAndBeep(true, 1500, 3) +end + +local function hideErrorContainer() + mainContainer.titleTextBox.colors.background, mainContainer.titleTextBox.colors.text = colors.title.default.background, colors.title.default.text + mainContainer.errorContainer.hidden = true + lastErrorLine, scriptCoroutine = nil, nil + updateHighlights() +end + +local function hideSettingsContainer() + for childIndex = 2, #mainContainer.settingsContainer.children do mainContainer.settingsContainer.children[childIndex] = nil end + mainContainer.settingsContainer.hidden = true + mainContainer:draw() + buffer.draw() +end + +local function clearSelection() + mainContainer.codeView.selections[1] = nil +end + +local function clearBreakpoints() + breakpointLines = nil + updateHighlights() +end + +local function addBreakpoint() + hideErrorContainer() + breakpointLines = breakpointLines or {} + + local lineExists + for i = 1, #breakpointLines do + if breakpointLines[i] == cursor.position.line then + lineExists = i + break + end + end + + if lineExists then + table.remove(breakpointLines, lineExists) + else + table.insert(breakpointLines, cursor.position.line) + end + + if #breakpointLines > 0 then + table.sort(breakpointLines, function(a, b) return a < b end) + else + breakpointLines = nil + end + + updateHighlights() +end + +local function fixFromLineByCursorPosition() + if mainContainer.codeView.fromLine > cursor.position.line then + mainContainer.codeView.fromLine = cursor.position.line + elseif mainContainer.codeView.fromLine + mainContainer.codeView.height - 2 < cursor.position.line then + mainContainer.codeView.fromLine = cursor.position.line - mainContainer.codeView.height + 2 + end +end + +local function fixFromSymbolByCursorPosition() + if mainContainer.codeView.fromSymbol > cursor.position.symbol then + mainContainer.codeView.fromSymbol = cursor.position.symbol + elseif mainContainer.codeView.fromSymbol + mainContainer.codeView.codeAreaWidth - 3 < cursor.position.symbol then + mainContainer.codeView.fromSymbol = cursor.position.symbol - mainContainer.codeView.codeAreaWidth + 3 + end +end + +local function fixCursorPosition(symbol, line) + if line < 1 then + line = 1 + elseif line > #mainContainer.codeView.lines then + line = #mainContainer.codeView.lines + end + + local lineLength = unicode.len(mainContainer.codeView.lines[line]) + if symbol < 1 or lineLength == 0 then + symbol = 1 + elseif symbol > lineLength then + symbol = lineLength + 1 + end + + return symbol, line +end + +local function setCursorPosition(symbol, line) + cursor.position.symbol, cursor.position.line = fixCursorPosition(symbol, line) + fixFromLineByCursorPosition() + fixFromSymbolByCursorPosition() + hideAutocompleteWindow() + hideErrorContainer() +end + +local function setCursorPositionAndClearSelection(symbol, line) + setCursorPosition(symbol, line) + clearSelection() +end + +local function moveCursor(symbolOffset, lineOffset) + if mainContainer.autocompleteWindow.hidden or lineOffset == 0 then + if mainContainer.codeView.selections[1] then + if symbolOffset < 0 or lineOffset < 0 then + setCursorPositionAndClearSelection(mainContainer.codeView.selections[1].from.symbol, mainContainer.codeView.selections[1].from.line) + else + setCursorPositionAndClearSelection(mainContainer.codeView.selections[1].to.symbol, mainContainer.codeView.selections[1].to.line) + end + else + local newSymbol, newLine = cursor.position.symbol + symbolOffset, cursor.position.line + lineOffset + + if symbolOffset < 0 and newSymbol < 1 then + newLine, newSymbol = newLine - 1, math.huge + elseif symbolOffset > 0 and newSymbol > unicode.len(mainContainer.codeView.lines[newLine] or "") + 1 then + newLine, newSymbol = newLine + 1, 1 + end + + setCursorPositionAndClearSelection(newSymbol, newLine) + end + elseif not mainContainer.autocompleteWindow.hidden then + mainContainer.autocompleteWindow.currentMatch = mainContainer.autocompleteWindow.currentMatch + lineOffset + + if mainContainer.autocompleteWindow.currentMatch < 1 then + mainContainer.autocompleteWindow.currentMatch = 1 + elseif mainContainer.autocompleteWindow.currentMatch > #mainContainer.autocompleteWindow.matches then + mainContainer.autocompleteWindow.currentMatch = #mainContainer.autocompleteWindow.matches + elseif mainContainer.autocompleteWindow.currentMatch < mainContainer.autocompleteWindow.fromMatch then + mainContainer.autocompleteWindow.fromMatch = mainContainer.autocompleteWindow.currentMatch + elseif mainContainer.autocompleteWindow.currentMatch > mainContainer.autocompleteWindow.fromMatch + mainContainer.autocompleteWindow.height - 1 then + mainContainer.autocompleteWindow.fromMatch = mainContainer.autocompleteWindow.currentMatch - mainContainer.autocompleteWindow.height + 1 + end + end +end + +local function setCursorPositionToHome() + setCursorPositionAndClearSelection(1, 1) +end + +local function setCursorPositionToEnd() + setCursorPositionAndClearSelection(unicode.len(mainContainer.codeView.lines[#mainContainer.codeView.lines]) + 1, #mainContainer.codeView.lines) +end + +local function scroll(direction, speed) + if direction == 1 then + if mainContainer.codeView.fromLine > speed then + mainContainer.codeView.fromLine = mainContainer.codeView.fromLine - speed + else + mainContainer.codeView.fromLine = 1 + end + else + if mainContainer.codeView.fromLine < #mainContainer.codeView.lines - speed then + mainContainer.codeView.fromLine = mainContainer.codeView.fromLine + speed + else + mainContainer.codeView.fromLine = #mainContainer.codeView.lines + end + end +end + +local function pageUp() + scroll(1, mainContainer.codeView.height - 2) +end + +local function pageDown() + scroll(-1, mainContainer.codeView.height - 2) +end + +local function selectWord() + local from, to = getCurrentWordStartingAndEnding(cursor.position.symbol) + if from and to then + mainContainer.codeView.selections[1] = { + from = {symbol = from, line = cursor.position.line}, + to = {symbol = to, line = cursor.position.line}, + } + cursor.position.symbol = to + end +end + +local function removeTabs(text) + local result = text:gsub("\t", string.rep(" ", mainContainer.codeView.indentationWidth)) + return result +end + +local function removeWindowsLineEndings(text) + local result = text:gsub("\r\n", "\n") + return result +end + +local function changeResolution(width, height) + buffer.setResolution(width, height) + calculateSizes() + mainContainer:draw() + buffer.draw() + config.screenResolution.width = width + config.screenResolution.height = height +end + +local function changeResolutionWindow() + mainContainer.settingsContainer.hidden = false + local textBoxesWidth = math.floor(mainContainer.width * 0.3) + local textBoxWidth, x, y = math.floor(textBoxesWidth / 2), math.floor(mainContainer.width / 2 - textBoxesWidth / 2), math.floor(mainContainer.height / 2) - 3 + + mainContainer.settingsContainer:addChild(GUI.label(1, y, mainContainer.width, 1, 0xFFFFFF, localization.changeResolution)):setAlignment(GUI.alignment.horizontal.center, GUI.alignment.vertical.top); y = y + 3 + local inputFieldWidth = mainContainer.settingsContainer:addChild(GUI.input(x, y, textBoxWidth, 3, 0xCCCCCC, 0x777777, 0x777777, 0xCCCCCC, 0x2D2D2D, tostring(config.screenResolution.width))); x = x + textBoxWidth + 2 + local inputFieldHeight = mainContainer.settingsContainer:addChild(GUI.input(x, y, textBoxWidth, 3, 0xCCCCCC, 0x777777, 0x777777, 0xCCCCCC, 0x2D2D2D, tostring(config.screenResolution.height))) + + local maxResolutionWidth, maxResolutionHeight = component.gpu.maxResolution() + inputFieldWidth.validator = function(text) + local number = tonumber(text) + if number and number >= 1 and number <= maxResolutionWidth then return true end + end + inputFieldHeight.validator = function(text) + local number = tonumber(text) + if number and number >= 1 and number <= maxResolutionHeight then return true end + end + + mainContainer.settingsContainer.backgroundPanel.eventHandler = function(mainContainer, object, eventData) + if eventData[1] == "touch" then + config.screenResolution.width, config.screenResolution.height = tonumber(inputFieldWidth.text), tonumber(inputFieldHeight.text) + saveConfig() + hideSettingsContainer() + changeResolution(config.screenResolution.width, config.screenResolution.height) + end + end +end + +local function createInputTextBoxForSettingsWindow(title, placeholder, onInputFinishedMethod, validatorMethod) + mainContainer.settingsContainer.hidden = false + local textBoxWidth = math.floor(mainContainer.width * 0.3) + local x, y = math.floor(mainContainer.width / 2 - textBoxWidth / 2), math.floor(mainContainer.height / 2) - 3 + + mainContainer.settingsContainer:addChild(GUI.label(1, y, mainContainer.width, 1, 0xFFFFFF, title)):setAlignment(GUI.alignment.horizontal.center, GUI.alignment.vertical.top); y = y + 3 + mainContainer.settingsContainer.inputField = mainContainer.settingsContainer:addChild(GUI.input(x, y, textBoxWidth, 3, 0xCCCCCC, 0x777777, 0x777777, 0xCCCCCC, 0x2D2D2D, "", placeholder)) + + mainContainer.settingsContainer.inputField.validator = validatorMethod + mainContainer.settingsContainer.inputField.onInputFinished = function(...) + onInputFinishedMethod(...) + hideSettingsContainer() + end +end + +local function newFile() + autocompleteDatabase = {} + mainContainer.codeView.lines = {""} + mainContainer.codeView.maximumLineLength = 1 + setCursorPositionAndClearSelection(1, 1) + mainContainer.leftTreeView.selectedItem = nil + clearBreakpoints() +end + +local function loadFile(path) + newFile() + + local file = io.open(path, "r") + for line in file:lines() do + line = removeWindowsLineEndings(removeTabs(line)) + table.insert(mainContainer.codeView.lines, line) + mainContainer.codeView.maximumLineLength = math.max(mainContainer.codeView.maximumLineLength, unicode.len(line)) + end + file:close() + + if #mainContainer.codeView.lines > 1 then + table.remove(mainContainer.codeView.lines, 1) + end + + mainContainer.leftTreeView.selectedItem = path + updateAutocompleteDatabaseFromFile() +end + +local function saveFile(path) + fs.makeDirectory(fs.path(path)) + local file, reason = io.open(path, "w") + if file then + for line = 1, #mainContainer.codeView.lines do + file:write(mainContainer.codeView.lines[line], "\n") + end + file:close() + else + GUI.error("Failed to open file for writing: " .. tostring(reason)) + end +end + +local function gotoLineWindow() + createInputTextBoxForSettingsWindow(localization.gotoLine, localization.lineNumber, + function() + gotoLine(tonumber(mainContainer.settingsContainer.inputField.text)) + end, + function() + if mainContainer.settingsContainer.inputField.text:match("%d+") then return true end + end + ) +end + +local function openFileWindow() + createInputTextBoxForSettingsWindow(localization.openFile, localization.pathToFile, + function() + loadFile(mainContainer.settingsContainer.inputField.text) + end, + function() + if fs.exists(mainContainer.settingsContainer.inputField.text) then return true end + end + ) +end + +local function saveFileAsWindow() + createInputTextBoxForSettingsWindow(localization.saveAs, localization.pathToFile, + function() + if unicode.len(mainContainer.settingsContainer.inputField.text or "") > 0 then + saveFile(mainContainer.settingsContainer.inputField.text) + mainContainer.leftTreeView:updateFileList() + mainContainer.leftTreeView.selectedItem = mainContainer.leftTreeView.workPath .. mainContainer.settingsContainer.inputField.text + + updateTitle() + mainContainer:draw() + buffer.draw() + end + end + ) +end + +local function saveFileWindow() + saveFile(mainContainer.leftTreeView.selectedItem) + mainContainer.leftTreeView:updateFileList() +end + +local function splitStringIntoLines(s) + s = removeWindowsLineEndings(removeTabs(s)) + + local lines, searchLineEndingFrom, maximumLineLength, lineEndingFoundAt, line = {}, 1, 0 + repeat + lineEndingFoundAt = string.unicodeFind(s, "\n", searchLineEndingFrom) + if lineEndingFoundAt then + line = unicode.sub(s, searchLineEndingFrom, lineEndingFoundAt - 1) + searchLineEndingFrom = lineEndingFoundAt + 1 + else + line = unicode.sub(s, searchLineEndingFrom, -1) + end + + table.insert(lines, line) + maximumLineLength = math.max(maximumLineLength, unicode.len(line)) + until not lineEndingFoundAt + + return lines, maximumLineLength +end + +local function downloadFileFromWeb() + createInputTextBoxForSettingsWindow(localization.getFromWeb, localization.url, + function() + local result, reason = web.request(mainContainer.settingsContainer.inputField.text) + if result then + newFile() + mainContainer.codeView.lines, mainContainer.codeView.maximumLineLength = splitStringIntoLines(result) + else + GUI.error("Failed to connect to URL: " .. tostring(reason)) + end + hideSettingsContainer() + end + ) +end + +------------------------------------------------------------------------------------------------------------------ + +local function getVariables(codePart) + local variables = {} + -- Сначала мы проверяем участок кода на наличие комментариев + if + not codePart:match("^%-%-") and + not codePart:match("^[\t%s]+%-%-") + then + -- Затем заменяем все строковые куски в участке кода на "ничего", чтобы наш "прекрасный" парсер не искал переменных в строках + codePart = codePart:gsub("\"[^\"]+\"", "") + -- Потом разбиваем код на отдельные буквенно-цифровые слова, не забыв точечку с двоеточием + for word in codePart:gmatch("[%a%d%.%:%_]+") do + -- Далее проверяем, не совпадает ли это слово с одним из луа-шаблонов, то бишь, не является ли оно частью синтаксиса + if + word ~= "local" and + word ~= "return" and + word ~= "while" and + word ~= "repeat" and + word ~= "until" and + word ~= "for" and + word ~= "in" and + word ~= "do" and + word ~= "if" and + word ~= "then" and + word ~= "else" and + word ~= "elseif" and + word ~= "end" and + word ~= "function" and + word ~= "true" and + word ~= "false" and + word ~= "nil" and + word ~= "not" and + word ~= "and" and + word ~= "or" and + -- Также проверяем, не число ли это в чистом виде + not word:match("^[%d%.]+$") and + not word:match("^0x%x+$") and + -- Или символ конкатенации, например + not word:match("^%.+$") + then + variables[word] = true + end + end + end + + return variables +end + +local function continue() + -- Готовим экран к запуску + local oldResolutionX, oldResolutionY = component.gpu.getResolution() + MineOSInterface.clearTerminal() + + -- Запускаем + _G.MineCodeIDEDebugInfo = nil + local coroutineResumeSuccess, coroutineResumeReason = coroutine.resume(scriptCoroutine) + + -- Анализируем результат запуска + if coroutineResumeSuccess then + if coroutine.status(scriptCoroutine) == "dead" then + MineOSInterface.waitForPressingAnyKey() + hideErrorContainer() + buffer.setResolution(oldResolutionX, oldResolutionY); mainContainer:draw(); buffer.draw(true) + else + -- Тест на пидора, мало ли у чувака в проге тоже есть yield + if _G.MineCodeIDEDebugInfo then + buffer.setResolution(oldResolutionX, oldResolutionY); mainContainer:draw(); buffer.draw(true) + gotoLine(_G.MineCodeIDEDebugInfo.line) + showBreakpointMessage(_G.MineCodeIDEDebugInfo.variables) + end + end + else + buffer.setResolution(oldResolutionX, oldResolutionY); mainContainer:draw(); buffer.draw(true) + showErrorContainer(debug.traceback(scriptCoroutine, coroutineResumeReason)) + end +end + +local function run() + hideErrorContainer() + + -- Инсертим брейкпоинты + if breakpointLines then + local offset = 0 + for i = 1, #breakpointLines do + local variables = getVariables(mainContainer.codeView.lines[breakpointLines[i] + offset]) + + local breakpointMessage = "_G.MineCodeIDEDebugInfo = {variables = {" + for variable in pairs(variables) do + breakpointMessage = breakpointMessage .. "[\"" .. variable .. "\"] = type(" .. variable .. ") == 'string' and '\"' .. " .. variable .. " .. '\"' or tostring(" .. variable .. "), " + end + breakpointMessage = breakpointMessage .. "}, line = " .. breakpointLines[i] .. "}; coroutine.yield()" + + table.insert(mainContainer.codeView.lines, breakpointLines[i] + offset, breakpointMessage) + offset = offset + 1 + end + end + + -- Лоадим кодыч + local loadSuccess, loadReason = load(table.concat(mainContainer.codeView.lines, "\n")) + + -- Чистим дерьмо вилочкой, чистим + if breakpointLines then + for i = 1, #breakpointLines do + table.remove(mainContainer.codeView.lines, breakpointLines[i]) + end + end + + -- Запускаем кодыч + if loadSuccess then + scriptCoroutine = coroutine.create(loadSuccess) + continue() + else + showErrorContainer(loadReason) + end +end + +local function deleteLine(line) + if #mainContainer.codeView.lines > 1 then + table.remove(mainContainer.codeView.lines, line) + setCursorPositionAndClearSelection(1, cursor.position.line) + + updateAutocompleteDatabaseFromFile() + end +end + +local function deleteSpecifiedData(fromSymbol, fromLine, toSymbol, toLine) + local upperLine = unicode.sub(mainContainer.codeView.lines[fromLine], 1, fromSymbol - 1) + local lowerLine = unicode.sub(mainContainer.codeView.lines[toLine], toSymbol + 1, -1) + for line = fromLine + 1, toLine do + table.remove(mainContainer.codeView.lines, fromLine + 1) + end + mainContainer.codeView.lines[fromLine] = upperLine .. lowerLine + setCursorPositionAndClearSelection(fromSymbol, fromLine) + + updateAutocompleteDatabaseFromFile() +end + +local function deleteSelectedData() + if mainContainer.codeView.selections[1] then + deleteSpecifiedData( + mainContainer.codeView.selections[1].from.symbol, + mainContainer.codeView.selections[1].from.line, + mainContainer.codeView.selections[1].to.symbol, + mainContainer.codeView.selections[1].to.line + ) + + clearSelection() + end +end + +local function copy() + if mainContainer.codeView.selections[1] then + if mainContainer.codeView.selections[1].to.line == mainContainer.codeView.selections[1].from.line then + clipboard = { unicode.sub(mainContainer.codeView.lines[mainContainer.codeView.selections[1].from.line], mainContainer.codeView.selections[1].from.symbol, mainContainer.codeView.selections[1].to.symbol) } + else + clipboard = { unicode.sub(mainContainer.codeView.lines[mainContainer.codeView.selections[1].from.line], mainContainer.codeView.selections[1].from.symbol, -1) } + for line = mainContainer.codeView.selections[1].from.line + 1, mainContainer.codeView.selections[1].to.line - 1 do + table.insert(clipboard, mainContainer.codeView.lines[line]) + end + table.insert(clipboard, unicode.sub(mainContainer.codeView.lines[mainContainer.codeView.selections[1].to.line], 1, mainContainer.codeView.selections[1].to.symbol)) + end + end +end + +local function cut() + if mainContainer.codeView.selections[1] then + copy() + deleteSelectedData() + end +end + +local function pasteSelectedAutocompletion() + local firstPart = unicode.sub(mainContainer.codeView.lines[cursor.position.line], 1, mainContainer.autocompleteWindow.currentWordStarting - 1) + local secondPart = unicode.sub(mainContainer.codeView.lines[cursor.position.line], mainContainer.autocompleteWindow.currentWordEnding + 1, -1) + mainContainer.codeView.lines[cursor.position.line] = firstPart .. mainContainer.autocompleteWindow.matches[mainContainer.autocompleteWindow.currentMatch][1] .. secondPart + setCursorPositionAndClearSelection(unicode.len(firstPart .. mainContainer.autocompleteWindow.matches[mainContainer.autocompleteWindow.currentMatch][1]) + 1, cursor.position.line) + hideAutocompleteWindow() +end + +local function paste(pasteLines) + if pasteLines then + if mainContainer.codeView.selections[1] then + deleteSelectedData() + end + + local firstPart = unicode.sub(mainContainer.codeView.lines[cursor.position.line], 1, cursor.position.symbol - 1) + local secondPart = unicode.sub(mainContainer.codeView.lines[cursor.position.line], cursor.position.symbol, -1) + + if #pasteLines == 1 then + mainContainer.codeView.lines[cursor.position.line] = firstPart .. pasteLines[1] .. secondPart + setCursorPositionAndClearSelection(cursor.position.symbol + unicode.len(pasteLines[1]), cursor.position.line) + else + mainContainer.codeView.lines[cursor.position.line] = firstPart .. pasteLines[1] + for pasteLine = #pasteLines - 1, 2, -1 do + table.insert(mainContainer.codeView.lines, cursor.position.line + 1, pasteLines[pasteLine]) + end + table.insert(mainContainer.codeView.lines, cursor.position.line + #pasteLines - 1, pasteLines[#pasteLines] .. secondPart) + setCursorPositionAndClearSelection(unicode.len(pasteLines[#pasteLines]) + 1, cursor.position.line + #pasteLines - 1) + end + + updateAutocompleteDatabaseFromFile() + end +end + +local function selectAndPasteColor() + local startColor = 0xFF0000 + if mainContainer.codeView.selections[1] and mainContainer.codeView.selections[1].from.line == mainContainer.codeView.selections[1].to.line then + startColor = tonumber(unicode.sub(mainContainer.codeView.lines[mainContainer.codeView.selections[1].from.line], mainContainer.codeView.selections[1].from.symbol, mainContainer.codeView.selections[1].to.symbol)) or startColor + end + + local selectedColor = GUI.palette(math.floor(mainContainer.width / 2 - 35), math.floor(mainContainer.height / 2 - 12), startColor):show() + if selectedColor then + paste({string.format("0x%06X", selectedColor)}) + end +end + +local function pasteRegularChar(unicodeByte, char) + if not keyboard.isControl(unicodeByte) then + paste({char}) + if char == " " then + updateAutocompleteDatabaseFromFile() + end + showAutocompleteWindow() + end +end + +local function pasteAutoBrackets(unicodeByte) + local char = unicode.char(unicodeByte) + local currentSymbol = unicode.sub(mainContainer.codeView.lines[cursor.position.line], cursor.position.symbol, cursor.position.symbol) + + -- Если у нас вообще врублен режим автоскобок, то чекаем их + if config.enableAutoBrackets then + -- Ситуация, когда курсор находится на закрывающей скобке, и нехуй ее еще раз вставлять + if possibleBrackets.closers[char] and currentSymbol == char then + deleteSelectedData() + setCursorPosition(cursor.position.symbol + 1, cursor.position.line) + -- Если нажата открывающая скобка + elseif possibleBrackets.openers[char] then + -- А вот тут мы берем в скобочки уже выделенный текст + if mainContainer.codeView.selections[1] then + local firstPart = unicode.sub(mainContainer.codeView.lines[mainContainer.codeView.selections[1].from.line], 1, mainContainer.codeView.selections[1].from.symbol - 1) + local secondPart = unicode.sub(mainContainer.codeView.lines[mainContainer.codeView.selections[1].from.line], mainContainer.codeView.selections[1].from.symbol, -1) + mainContainer.codeView.lines[mainContainer.codeView.selections[1].from.line] = firstPart .. char .. secondPart + mainContainer.codeView.selections[1].from.symbol = mainContainer.codeView.selections[1].from.symbol + 1 + + if mainContainer.codeView.selections[1].to.line == mainContainer.codeView.selections[1].from.line then + mainContainer.codeView.selections[1].to.symbol = mainContainer.codeView.selections[1].to.symbol + 1 + end + + firstPart = unicode.sub(mainContainer.codeView.lines[mainContainer.codeView.selections[1].to.line], 1, mainContainer.codeView.selections[1].to.symbol) + secondPart = unicode.sub(mainContainer.codeView.lines[mainContainer.codeView.selections[1].to.line], mainContainer.codeView.selections[1].to.symbol + 1, -1) + mainContainer.codeView.lines[mainContainer.codeView.selections[1].to.line] = firstPart .. possibleBrackets.openers[char] .. secondPart + cursor.position.symbol = cursor.position.symbol + 2 + -- А тут мы делаем двойную автоскобку, если можем + elseif possibleBrackets.openers[char] and not currentSymbol:match("[%a%d%_]") then + paste({char .. possibleBrackets.openers[char]}) + setCursorPosition(cursor.position.symbol - 1, cursor.position.line) + cursor.blinkState = false + -- Ну, и если нет ни выделений, ни можем ебануть автоскобочку по регулярке + else + pasteRegularChar(unicodeByte, char) + end + -- Если мы вообще на скобку не нажимали + else + pasteRegularChar(unicodeByte, char) + end + -- Если оффнуты афтоскобки + else + pasteRegularChar(unicodeByte, char) + end +end + +local function backspaceAutoBrackets() + local previousSymbol = unicode.sub(mainContainer.codeView.lines[cursor.position.line], cursor.position.symbol - 1, cursor.position.symbol - 1) + local currentSymbol = unicode.sub(mainContainer.codeView.lines[cursor.position.line], cursor.position.symbol, cursor.position.symbol) + if config.enableAutoBrackets and possibleBrackets.openers[previousSymbol] and possibleBrackets.openers[previousSymbol] == currentSymbol then + deleteSpecifiedData(cursor.position.symbol, cursor.position.line, cursor.position.symbol, cursor.position.line) + end +end + +local function delete() + if mainContainer.codeView.selections[1] then + deleteSelectedData() + else + if cursor.position.symbol < unicode.len(mainContainer.codeView.lines[cursor.position.line]) + 1 then + deleteSpecifiedData(cursor.position.symbol, cursor.position.line, cursor.position.symbol, cursor.position.line) + else + if cursor.position.line > 1 then + deleteSpecifiedData(unicode.len(mainContainer.codeView.lines[cursor.position.line]) + 1, cursor.position.line, 0, cursor.position.line + 1) + end + end + + -- updateAutocompleteDatabaseFromFile() + showAutocompleteWindow() + end +end + +local function backspace() + if mainContainer.codeView.selections[1] then + deleteSelectedData() + else + if cursor.position.symbol > 1 then + backspaceAutoBrackets() + deleteSpecifiedData(cursor.position.symbol - 1, cursor.position.line, cursor.position.symbol - 1, cursor.position.line) + else + if cursor.position.line > 1 then + deleteSpecifiedData(unicode.len(mainContainer.codeView.lines[cursor.position.line - 1]) + 1, cursor.position.line - 1, 0, cursor.position.line) + end + end + + -- updateAutocompleteDatabaseFromFile() + showAutocompleteWindow() + end +end + +local function enter() + local firstPart = unicode.sub(mainContainer.codeView.lines[cursor.position.line], 1, cursor.position.symbol - 1) + local secondPart = unicode.sub(mainContainer.codeView.lines[cursor.position.line], cursor.position.symbol, -1) + mainContainer.codeView.lines[cursor.position.line] = firstPart + table.insert(mainContainer.codeView.lines, cursor.position.line + 1, secondPart) + setCursorPositionAndClearSelection(1, cursor.position.line + 1) +end + +local function selectAll() + mainContainer.codeView.selections[1] = { + from = { + symbol = 1, line = 1 + }, + to = { + symbol = unicode.len(mainContainer.codeView.lines[#mainContainer.codeView.lines]), line = #mainContainer.codeView.lines + } + } +end + +local function isLineCommented(line) + if mainContainer.codeView.lines[line] == "" or mainContainer.codeView.lines[line]:match("%-%-%s?") then return true end +end + +local function commentLine(line) + mainContainer.codeView.lines[line] = "-- " .. mainContainer.codeView.lines[line] +end + +local function uncommentLine(line) + local countOfReplaces + mainContainer.codeView.lines[line], countOfReplaces = mainContainer.codeView.lines[line]:gsub("%-%-%s?", "", 1) + return countOfReplaces +end + +local function toggleComment() + if mainContainer.codeView.selections[1] then + local allLinesAreCommented = true + + for line = mainContainer.codeView.selections[1].from.line, mainContainer.codeView.selections[1].to.line do + if not isLineCommented(line) then + allLinesAreCommented = false + break + end + end + + for line = mainContainer.codeView.selections[1].from.line, mainContainer.codeView.selections[1].to.line do + if allLinesAreCommented then + uncommentLine(line) + else + commentLine(line) + end + end + + local modifyer = 3 + if allLinesAreCommented then modifyer = -modifyer end + setCursorPosition(cursor.position.symbol + modifyer, cursor.position.line) + mainContainer.codeView.selections[1].from.symbol, mainContainer.codeView.selections[1].to.symbol = mainContainer.codeView.selections[1].from.symbol + modifyer, mainContainer.codeView.selections[1].to.symbol + modifyer + else + if isLineCommented(cursor.position.line) then + if uncommentLine(cursor.position.line) > 0 then + setCursorPositionAndClearSelection(cursor.position.symbol - 3, cursor.position.line) + end + else + commentLine(cursor.position.line) + setCursorPositionAndClearSelection(cursor.position.symbol + 3, cursor.position.line) + end + end +end + +local function indentLine(line) + mainContainer.codeView.lines[line] = string.rep(" ", mainContainer.codeView.indentationWidth) .. mainContainer.codeView.lines[line] +end + +local function unindentLine(line) + mainContainer.codeView.lines[line], countOfReplaces = string.gsub(mainContainer.codeView.lines[line], "^" .. string.rep("%s", mainContainer.codeView.indentationWidth), "") + return countOfReplaces +end + +local function indentOrUnindent(isIndent) + if mainContainer.codeView.selections[1] then + local countOfReplacesInFirstLine, countOfReplacesInLastLine + + for line = mainContainer.codeView.selections[1].from.line, mainContainer.codeView.selections[1].to.line do + if isIndent then + indentLine(line) + else + local countOfReplaces = unindentLine(line) + if line == mainContainer.codeView.selections[1].from.line then + countOfReplacesInFirstLine = countOfReplaces + elseif line == mainContainer.codeView.selections[1].to.line then + countOfReplacesInLastLine = countOfReplaces + end + end + end + + if isIndent then + setCursorPosition(cursor.position.symbol + mainContainer.codeView.indentationWidth, cursor.position.line) + mainContainer.codeView.selections[1].from.symbol, mainContainer.codeView.selections[1].to.symbol = mainContainer.codeView.selections[1].from.symbol + mainContainer.codeView.indentationWidth, mainContainer.codeView.selections[1].to.symbol + mainContainer.codeView.indentationWidth + else + if countOfReplacesInFirstLine > 0 then + mainContainer.codeView.selections[1].from.symbol = mainContainer.codeView.selections[1].from.symbol - mainContainer.codeView.indentationWidth + if cursor.position.line == mainContainer.codeView.selections[1].from.line then + setCursorPosition(cursor.position.symbol - mainContainer.codeView.indentationWidth, cursor.position.line) + end + end + + if countOfReplacesInLastLine > 0 then + mainContainer.codeView.selections[1].to.symbol = mainContainer.codeView.selections[1].to.symbol - mainContainer.codeView.indentationWidth + if cursor.position.line == mainContainer.codeView.selections[1].to.line then + setCursorPosition(cursor.position.symbol - mainContainer.codeView.indentationWidth, cursor.position.line) + end + end + end + else + if isIndent then + indentLine(cursor.position.line) + setCursorPositionAndClearSelection(cursor.position.symbol + mainContainer.codeView.indentationWidth, cursor.position.line) + else + if unindentLine(cursor.position.line) > 0 then + setCursorPositionAndClearSelection(cursor.position.symbol - mainContainer.codeView.indentationWidth, cursor.position.line) + end + end + end +end + +local function updateRAMProgressBar() + if not mainContainer.topToolBar.hidden then + local totalMemory = computer.totalMemory() + mainContainer.RAMUsageProgressBar.value = math.ceil((totalMemory - computer.freeMemory()) / totalMemory * 100) + end +end + +local function find() + if not mainContainer.bottomToolBar.hidden and mainContainer.bottomToolBar.inputField.text ~= "" then + findStartFrom = findStartFrom + 1 + + for line = findStartFrom, #mainContainer.codeView.lines do + local whereToFind, whatToFind = mainContainer.codeView.lines[line], mainContainer.bottomToolBar.inputField.text + if not mainContainer.bottomToolBar.caseSensitiveButton.pressed then + whereToFind, whatToFind = unicode.lower(whereToFind), unicode.lower(whatToFind) + end + + local success, starting, ending = pcall(string.unicodeFind, whereToFind, whatToFind) + if success then + if starting then + mainContainer.codeView.selections[1] = { + from = {symbol = starting, line = line}, + to = {symbol = ending, line = line}, + color = 0xCC9200 + } + findStartFrom = line + gotoLine(line) + return + end + else + GUI.error("Wrong searching regex") + end + end + + findStartFrom = 0 + end +end + +local function findFromFirstDisplayedLine() + findStartFrom = mainContainer.codeView.fromLine + find() +end + +local function toggleBottomToolBar() + mainContainer.bottomToolBar.hidden = not mainContainer.bottomToolBar.hidden + mainContainer.toggleBottomToolBarButton.pressed = not mainContainer.bottomToolBar.hidden + calculateSizes() + + if not mainContainer.bottomToolBar.hidden then + mainContainer:draw() + findFromFirstDisplayedLine() + end +end + +local function toggleTopToolBar() + mainContainer.topToolBar.hidden = not mainContainer.topToolBar.hidden + mainContainer.toggleTopToolBarButton.pressed = not mainContainer.topToolBar.hidden + calculateSizes() +end + +local function createEditOrRightClickMenu(x, y) + local editOrRightClickMenu = GUI.contextMenu(x, y) + editOrRightClickMenu:addItem(localization.cut, not mainContainer.codeView.selections[1], "^X").onTouch = function() + cut() + end + editOrRightClickMenu:addItem(localization.copy, not mainContainer.codeView.selections[1], "^C").onTouch = function() + copy() + end + editOrRightClickMenu:addItem(localization.paste, not clipboard, "^V").onTouch = function() + paste(clipboard) + end + editOrRightClickMenu:addSeparator() + editOrRightClickMenu:addItem(localization.comment, false, "^/").onTouch = function() + toggleComment() + end + editOrRightClickMenu:addItem(localization.indent, false, "Tab").onTouch = function() + indentOrUnindent(true) + end + editOrRightClickMenu:addItem(localization.unindent, false, "⇧Tab").onTouch = function() + indentOrUnindent(false) + end + editOrRightClickMenu:addItem(localization.deleteLine, false, "^Del").onTouch = function() + deleteLine(cursor.position.line) + end + editOrRightClickMenu:addSeparator() + editOrRightClickMenu:addItem(localization.addBreakpoint, false, "F9").onTouch = function() + addBreakpoint() + mainContainer:draw() + buffer.draw() + end + editOrRightClickMenu:addItem(localization.clearBreakpoints, not breakpointLines, "^F9").onTouch = function() + clearBreakpoints() + end + editOrRightClickMenu:addSeparator() + editOrRightClickMenu:addItem(localization.selectAndPasteColor, false, "^⇧C").onTouch = function() + selectAndPasteColor() + end + editOrRightClickMenu:addItem(localization.selectWord).onTouch = function() + selectWord() + end + editOrRightClickMenu:addItem(localization.selectAll, false, "^A").onTouch = function() + selectAll() + end + editOrRightClickMenu:show() +end + +local function tick() + updateTitle() + updateRAMProgressBar() + mainContainer:draw() + + if cursor.blinkState and mainContainer.settingsContainer.hidden then + local x, y = convertTextPositionToScreenCoordinates(cursor.position.symbol, cursor.position.line) + if + x >= mainContainer.codeView.codeAreaPosition + 1 and + y >= mainContainer.codeView.y and + x <= mainContainer.codeView.codeAreaPosition + mainContainer.codeView.codeAreaWidth - 2 and + y <= mainContainer.codeView.y + mainContainer.codeView.height - 2 + then + buffer.text(x, y, config.cursorColor, config.cursorSymbol) + end + end + + buffer.draw() +end + +local function createMainContainer() + mainContainer = GUI.fullScreenContainer() + + mainContainer.codeView = mainContainer:addChild(GUI.codeView(1, 1, 1, 1, {""}, 1, 1, 1, {}, {}, config.highlightLuaSyntax, 2)) + mainContainer.codeView.scrollBars.vertical.onTouch = function() + mainContainer.codeView.fromLine = mainContainer.codeView.scrollBars.vertical.value + end + mainContainer.codeView.scrollBars.horizontal.onTouch = function() + mainContainer.codeView.fromSymbol = mainContainer.codeView.scrollBars.horizontal.value + end + + mainContainer.topMenu = mainContainer:addChild(GUI.menu(1, 1, 1, colors.topMenu.backgroundColor, colors.topMenu.textColor, colors.topMenu.backgroundPressedColor, colors.topMenu.textPressedColor)) + + local item1 = mainContainer.topMenu:addItem("MineCode", 0x0) + item1.onTouch = function() + local menu = GUI.contextMenu(item1.x, item1.y + 1) + menu:addItem(localization.about).onTouch = function() + mainContainer.settingsContainer.hidden = false + local y = math.floor(mainContainer.settingsContainer.height / 2 - #about / 2) + mainContainer.settingsContainer:addChild(GUI.textBox(1, y, mainContainer.settingsContainer.width, #about, nil, 0xEEEEEE, about, 1)):setAlignment(GUI.alignment.horizontal.center, GUI.alignment.vertical.top) + end + menu:addItem(localization.quit, false, "^W").onTouch = function() + mainContainer:stopEventHandling() + end + menu:show() + end + + local item2 = mainContainer.topMenu:addItem(localization.file) + item2.onTouch = function() + local menu = GUI.contextMenu(item2.x, item2.y + 1) + menu:addItem(localization.new, false, "^N").onTouch = function() + newFile() + mainContainer:draw() + buffer.draw() + end + menu:addItem(localization.open, false, "^O").onTouch = function() + openFileWindow() + end + if component.isAvailable("internet") then + menu:addItem(localization.getFromWeb, false, "^U").onTouch = function() + downloadFileFromWeb() + end + end + menu:addSeparator() + menu:addItem(localization.save, not mainContainer.leftTreeView.selectedItem, "^S").onTouch = function() + saveFileWindow() + end + menu:addItem(localization.saveAs, false, "^⇧S").onTouch = function() + saveFileAsWindow() + end + menu:show() + end + + local item3 = mainContainer.topMenu:addItem(localization.edit) + item3.onTouch = function() + createEditOrRightClickMenu(item3.x, item3.y + 1) + end + + local item4 = mainContainer.topMenu:addItem(localization.properties) + item4.onTouch = function() + local menu = GUI.contextMenu(item4.x, item4.y + 1) + menu:addItem(localization.colorScheme).onTouch = function() + mainContainer.settingsContainer.hidden = false + + local colorSelectorsCount, colorSelectorCountX = 0, 4; for key in pairs(config.syntaxColorScheme) do colorSelectorsCount = colorSelectorsCount + 1 end + local colorSelectorCountY = math.ceil(colorSelectorsCount / colorSelectorCountX) + local colorSelectorWidth, colorSelectorHeight, colorSelectorSpaceX, colorSelectorSpaceY = math.floor(mainContainer.settingsContainer.width / colorSelectorCountX * 0.8), 3, 2, 1 + + local startX, y = math.floor(mainContainer.settingsContainer.width / 2 - (colorSelectorCountX * (colorSelectorWidth + colorSelectorSpaceX) - colorSelectorSpaceX) / 2), math.floor(mainContainer.settingsContainer.height / 2 - (colorSelectorCountY * (colorSelectorHeight + colorSelectorSpaceY) - colorSelectorSpaceY + 3) / 2) + mainContainer.settingsContainer:addChild(GUI.label(1, y, mainContainer.settingsContainer.width, 1, 0xFFFFFF, localization.colorScheme)):setAlignment(GUI.alignment.horizontal.center, GUI.alignment.vertical.top); y = y + 3 + local x, counter = startX, 1 + + local colors = {} + for key in pairs(config.syntaxColorScheme) do + table.insert(colors, {key}) + end + + aplhabeticalSort(colors) + + for i = 1, #colors do + local colorSelector = mainContainer.settingsContainer:addChild(GUI.colorSelector(x, y, colorSelectorWidth, colorSelectorHeight, config.syntaxColorScheme[colors[i][1]], colors[i][1])) + colorSelector.onTouch = function() + config.syntaxColorScheme[colors[i][1]] = colorSelector.color + syntax.colorScheme = config.syntaxColorScheme + saveConfig() + end + + x, counter = x + colorSelectorWidth + colorSelectorSpaceX, counter + 1 + if counter > colorSelectorCountX then + x, y, counter = startX, y + colorSelectorHeight + colorSelectorSpaceY, 1 + end + end + end + menu:addItem(localization.cursorProperties).onTouch = function() + mainContainer.settingsContainer.hidden = false + + local elementWidth = math.floor(mainContainer.width * 0.3) + local x, y = math.floor(mainContainer.width / 2 - elementWidth / 2), math.floor(mainContainer.height / 2) - 7 + mainContainer.settingsContainer:addChild(GUI.label(1, y, mainContainer.settingsContainer.width, 1, 0xFFFFFF, localization.cursorProperties)):setAlignment(GUI.alignment.horizontal.center, GUI.alignment.vertical.top); y = y + 3 + local inputField = mainContainer.settingsContainer:addChild(GUI.input(x, y, elementWidth, 3, 0xCCCCCC, 0x777777, 0x777777, 0xCCCCCC, 0x2D2D2D, config.cursorSymbol, localization.cursorSymbol)); y = y + 5 + inputField.validator = function(text) + if unicode.len(text) == 1 then return true end + end + inputField.onInputFinished = function() + config.cursorSymbol = inputField.text; saveConfig() + end + local colorSelector = mainContainer.settingsContainer:addChild(GUI.colorSelector(x, y, elementWidth, 3, config.cursorColor, localization.cursorColor)); y = y + 5 + colorSelector.onTouch = function() + config.cursorColor = colorSelector.color; saveConfig() + end + local horizontalSlider = mainContainer.settingsContainer:addChild(GUI.slider(x, y, elementWidth, 0xFFDB80, 0x000000, 0xFFDB40, 0xDDDDDD, 1, 1000, config.cursorBlinkDelay * 1000, false, localization.cursorBlinkDelay .. ": ", " ms")) + horizontalSlider.onValueChanged = function() + config.cursorBlinkDelay = horizontalSlider.value / 1000; saveConfig() + end + end + + if mainContainer.topToolBar.hidden then + menu:addItem(localization.toggleTopToolBar).onTouch = function() + toggleTopToolBar() + end + end + menu:addSeparator() + menu:addItem(config.enableAutoBrackets and localization.disableAutoBrackets or localization.enableAutoBrackets, false, "^]").onTouch = function() + config.enableAutoBrackets = not config.enableAutoBrackets + saveConfig() + end + menu:addItem(config.enableAutocompletion and localization.disableAutocompletion or localization.enableAutocompletion, false, "^I").onTouch = function() + toggleEnableAutocompleteDatabase() + end + menu:addSeparator() + menu:addItem(localization.changeResolution, false, "^R").onTouch = function() + changeResolutionWindow() + end + menu:show() + end + + local item5 = mainContainer.topMenu:addItem(localization.gotoCyka) + item5.onTouch = function() + local menu = GUI.contextMenu(item5.x, item5.y + 1) + menu:addItem(localization.pageUp, false, "PgUp").onTouch = function() + pageUp() + end + menu:addItem(localization.pageDown, false, "PgDn").onTouch = function() + pageDown() + end + menu:addItem(localization.gotoStart, false, "Home").onTouch = function() + setCursorPositionToHome() + end + menu:addItem(localization.gotoEnd, false, "End").onTouch = function() + setCursorPositionToEnd() + end + menu:addSeparator() + menu:addItem(localization.gotoLine, false, "^L").onTouch = function() + gotoLineWindow() + end + menu:show() + end + + mainContainer.topToolBar = mainContainer:addChild(GUI.container(1, 2, 1, 3)) + mainContainer.topToolBar.backgroundPanel = mainContainer.topToolBar:addChild(GUI.panel(1, 1, 1, 3, colors.topToolBar)) + mainContainer.titleTextBox = mainContainer.topToolBar:addChild(GUI.textBox(1, 1, 1, 3, 0x0, 0x0, {}, 1):setAlignment(GUI.alignment.horizontal.center, GUI.alignment.vertical.top)) + local titleTextBoxOldDraw = mainContainer.titleTextBox.draw + mainContainer.titleTextBox.draw = function(titleTextBox) + titleTextBoxOldDraw(titleTextBox) + local sidesColor = mainContainer.errorContainer.hidden and colors.title.default.sides or colors.title.onError.sides + buffer.square(titleTextBox.x, titleTextBox.y, 1, titleTextBox.height, sidesColor, titleTextBox.colors.text, " ") + buffer.square(titleTextBox.x + titleTextBox.width - 1, titleTextBox.y, 1, titleTextBox.height, sidesColor, titleTextBox.colors.text, " ") + end + + mainContainer.RAMUsageProgressBar = mainContainer.topToolBar:addChild(GUI.progressBar(1, 2, 1, 0x777777, 0xBBBBBB, 0xAAAAAA, 50, true, true, "RAM: ", "%")) + + --☯◌☺ + mainContainer.addBreakpointButton = mainContainer.topToolBar:addChild(GUI.adaptiveButton(1, 1, 3, 1, 0x878787, 0xEEEEEE, 0xCCCCCC, 0x444444, "x")) + mainContainer.addBreakpointButton.onTouch = function() + addBreakpoint() + mainContainer:draw() + buffer.draw() + end + + mainContainer.toggleSyntaxHighlightingButton = mainContainer.topToolBar:addChild(GUI.adaptiveButton(1, 1, 3, 1, 0xCCCCCC, 0x444444, 0x696969, 0xEEEEEE, "◌")) + mainContainer.toggleSyntaxHighlightingButton.switchMode, mainContainer.toggleSyntaxHighlightingButton.pressed = true, true + mainContainer.toggleSyntaxHighlightingButton.onTouch = function() + mainContainer.codeView.highlightLuaSyntax = not mainContainer.codeView.highlightLuaSyntax + config.highlightLuaSyntax = mainContainer.codeView.highlightLuaSyntax + saveConfig() + mainContainer:draw() + buffer.draw() + end + + mainContainer.runButton = mainContainer.topToolBar:addChild(GUI.adaptiveButton(1, 1, 3, 1, 0x4B4B4B, 0xEEEEEE, 0xCCCCCC, 0x444444, "▷")) + mainContainer.runButton.onTouch = function() + run() + end + + mainContainer.toggleLeftToolBarButton = mainContainer.topToolBar:addChild(GUI.adaptiveButton(1, 1, 3, 1, 0xCCCCCC, 0x444444, 0x4B4B4B, 0xEEEEEE, "⇦")) + mainContainer.toggleLeftToolBarButton.switchMode, mainContainer.toggleLeftToolBarButton.pressed = true, true + mainContainer.toggleLeftToolBarButton.onTouch = function() + mainContainer.leftTreeView.hidden = not mainContainer.toggleLeftToolBarButton.pressed + mainContainer.leftTreeViewResizer.hidden = mainContainer.leftTreeView.hidden + calculateSizes() + mainContainer:draw() + buffer.draw() + end + + mainContainer.toggleBottomToolBarButton = mainContainer.topToolBar:addChild(GUI.adaptiveButton(1, 1, 3, 1, 0xCCCCCC, 0x444444, 0x696969, 0xEEEEEE, "⇩")) + mainContainer.toggleBottomToolBarButton.switchMode, mainContainer.toggleBottomToolBarButton.pressed = true, false + mainContainer.toggleBottomToolBarButton.onTouch = function() + mainContainer.bottomToolBar.hidden = not mainContainer.toggleBottomToolBarButton.pressed + calculateSizes() + mainContainer:draw() + buffer.draw() + end + + mainContainer.toggleTopToolBarButton = mainContainer.topToolBar:addChild(GUI.adaptiveButton(1, 1, 3, 1, 0xCCCCCC, 0x444444, 0x878787, 0xEEEEEE, "⇧")) + mainContainer.toggleTopToolBarButton.switchMode, mainContainer.toggleTopToolBarButton.pressed = true, true + mainContainer.toggleTopToolBarButton.onTouch = function() + mainContainer.topToolBar.hidden = not mainContainer.toggleTopToolBarButton.pressed + calculateSizes() + mainContainer:draw() + buffer.draw() + end + + mainContainer.bottomToolBar = mainContainer:addChild(GUI.container(1, 1, 1, 3)) + mainContainer.bottomToolBar.caseSensitiveButton = mainContainer.bottomToolBar:addChild(GUI.adaptiveButton(1, 1, 2, 1, 0x3C3C3C, 0xEEEEEE, 0xBBBBBB, 0x2D2D2D, "Aa")) + mainContainer.bottomToolBar.caseSensitiveButton.switchMode = true + mainContainer.bottomToolBar.onTouch = function() + find() + end + mainContainer.bottomToolBar.inputField = mainContainer.bottomToolBar:addChild(GUI.input(7, 1, 10, 3, 0xCCCCCC, 0x999999, 0x999999, 0xCCCCCC, 0x2D2D2D, "", localization.findSomeShit)) + mainContainer.bottomToolBar.inputField.onInputFinished = function() + findFromFirstDisplayedLine() + end + mainContainer.bottomToolBar.findButton = mainContainer.bottomToolBar:addChild(GUI.adaptiveButton(1, 1, 3, 1, 0x3C3C3C, 0xEEEEEE, 0xBBBBBB, 0x2D2D2D, localization.find)) + mainContainer.bottomToolBar.findButton.onTouch = function() + find() + end + mainContainer.bottomToolBar.hidden = true + + mainContainer.leftTreeView = mainContainer:addChild(GUI.filesystemTree(1, 1, config.leftTreeViewWidth, 1, 0xCCCCCC, 0x3C3C3C, 0x3C3C3C, 0x999999, 0x3C3C3C, 0xE1E1E1, 0xBBBBBB, 0xAAAAAA, 0xBBBBBB, 0x444444, GUI.filesystemModes.both, GUI.filesystemModes.file)) + mainContainer.leftTreeView.onItemSelected = function(path) + loadFile(path) + + updateTitle() + mainContainer:draw() + buffer.draw() + end + mainContainer.leftTreeView:updateFileList() + mainContainer.leftTreeViewResizer = mainContainer:addChild(GUI.resizer(1, 1, 3, 5, 0x888888, 0x0)) + mainContainer.leftTreeViewResizer.onResize = function(mainContainer, object, eventData, dragWidth, dragHeight) + mainContainer.leftTreeView.width = mainContainer.leftTreeView.width + dragWidth + calculateSizes() + end + + mainContainer.leftTreeViewResizer.onResizeFinished = function() + config.leftTreeViewWidth = mainContainer.leftTreeView.width + saveConfig() + end + + mainContainer.errorContainer = mainContainer:addChild(GUI.container(1, 1, 1, 1)) + mainContainer.errorContainer.backgroundPanel = mainContainer.errorContainer:addChild(GUI.panel(1, 1, 1, 1, 0xFFFFFF, 0.3)) + mainContainer.errorContainer.errorTextBox = mainContainer.errorContainer:addChild(GUI.textBox(3, 2, 1, 1, nil, 0x4B4B4B, {}, 1)) + mainContainer.errorContainer.breakpointExitButton = mainContainer.errorContainer:addChild(GUI.button(1, 1, 1, 1, 0x3C3C3C, 0xCCCCCC, 0x2D2D2D, 0x888888, localization.finishDebug)) + mainContainer.errorContainer.breakpointContinueButton = mainContainer.errorContainer:addChild(GUI.button(1, 1, 1, 1, 0x444444, 0xCCCCCC, 0x2D2D2D, 0x888888, localization.continueDebug)) + mainContainer.errorContainer.breakpointExitButton.onTouch = hideErrorContainer + mainContainer.errorContainer.breakpointContinueButton.onTouch = continue + hideErrorContainer() + + mainContainer.settingsContainer = mainContainer:addChild(GUI.container(1, 1, 1, 1)) + mainContainer.settingsContainer.backgroundPanel = mainContainer.settingsContainer:addChild(GUI.panel(1, 1, mainContainer.settingsContainer.width, mainContainer.settingsContainer.height, 0x0, 0.3)) + mainContainer.settingsContainer.backgroundPanel.eventHandler = function(mainContainer, object, eventData) + if eventData[1] == "touch" then + hideSettingsContainer() + end + end + mainContainer.settingsContainer.hidden = true + + mainContainer.autocompleteWindow = mainContainer:addChild(GUI.object(1, 1, 40, 1)) + mainContainer.autocompleteWindow.maximumHeight = 8 + mainContainer.autocompleteWindow.matches = {} + mainContainer.autocompleteWindow.fromMatch = 1 + mainContainer.autocompleteWindow.currentMatch = 1 + mainContainer.autocompleteWindow.hidden = true + mainContainer.autocompleteWindow.draw = function(object) + mainContainer.autocompleteWindow.x, mainContainer.autocompleteWindow.y = convertTextPositionToScreenCoordinates(mainContainer.autocompleteWindow.currentWordStarting, cursor.position.line) + mainContainer.autocompleteWindow.x, mainContainer.autocompleteWindow.y = mainContainer.autocompleteWindow.x, mainContainer.autocompleteWindow.y + 1 + + object.height = object.maximumHeight + if object.height > #object.matches then object.height = #object.matches end + + buffer.square(object.x, object.y, object.width, object.height, 0xFFFFFF, 0x0, " ") + + local y = object.y + for i = object.fromMatch, #object.matches do + local firstColor, secondColor = 0x3C3C3C, 0x999999 + + if i == object.currentMatch then + buffer.square(object.x, y, object.width, 1, 0x2D2D2D, 0xEEEEEE, " ") + firstColor, secondColor = 0xEEEEEE, 0x999999 + end + + buffer.text(object.x + 1, y, secondColor, unicode.sub(object.matches[i][1], 1, object.width - 2)) + buffer.text(object.x + 1, y, firstColor, unicode.sub(object.matches[i][2], 1, object.width - 2)) + + y = y + 1 + if y > object.y + object.height - 1 then break end + end + + if object.height < #object.matches then + GUI.scrollBar(object.x + object.width - 1, object.y, 1, object.height, 0x444444, 0x00DBFF, 1, #object.matches, object.currentMatch, object.height, 1, true):draw() + end + end + + mainContainer.codeView.eventHandler = function(mainContainer, object, eventData) + if eventData[1] == "touch" then + if eventData[5] == 1 then + createEditOrRightClickMenu(eventData[3], eventData[4]) + else + setCursorPositionAndClearSelection(convertScreenCoordinatesToTextPosition(eventData[3], eventData[4])) + end + + cursor.blinkState = true + tick() + elseif eventData[1] == "double_touch" then + cursor.blinkState = true + selectWord() + + mainContainer:draw() + buffer.draw() + elseif eventData[1] == "drag" then + if eventData[5] ~= 1 then + mainContainer.codeView.selections[1] = mainContainer.codeView.selections[1] or {from = {}, to = {}} + mainContainer.codeView.selections[1].from.symbol, mainContainer.codeView.selections[1].from.line = cursor.position.symbol, cursor.position.line + mainContainer.codeView.selections[1].to.symbol, mainContainer.codeView.selections[1].to.line = fixCursorPosition(convertScreenCoordinatesToTextPosition(eventData[3], eventData[4])) + + if mainContainer.codeView.selections[1].from.line > mainContainer.codeView.selections[1].to.line then + mainContainer.codeView.selections[1].from.line, mainContainer.codeView.selections[1].to.line = mainContainer.codeView.selections[1].to.line, mainContainer.codeView.selections[1].from.line + mainContainer.codeView.selections[1].from.symbol, mainContainer.codeView.selections[1].to.symbol = mainContainer.codeView.selections[1].to.symbol, mainContainer.codeView.selections[1].from.symbol + elseif mainContainer.codeView.selections[1].from.line == mainContainer.codeView.selections[1].to.line then + if mainContainer.codeView.selections[1].from.symbol > mainContainer.codeView.selections[1].to.symbol then + mainContainer.codeView.selections[1].from.symbol, mainContainer.codeView.selections[1].to.symbol = mainContainer.codeView.selections[1].to.symbol, mainContainer.codeView.selections[1].from.symbol + end + end + end + + cursor.blinkState = true + tick() + elseif eventData[1] == "key_down" then + -- Ctrl or CMD + if keyboard.isKeyDown(29) or keyboard.isKeyDown(219) then + -- Slash + if eventData[4] == 53 then + toggleComment() + -- ] + elseif eventData[4] == 27 then + config.enableAutoBrackets = not config.enableAutoBrackets + saveConfig() + -- I + elseif eventData[4] == 23 then + toggleEnableAutocompleteDatabase() + -- A + elseif eventData[4] == 30 then + selectAll() + -- C + elseif eventData[4] == 46 then + -- Shift + if keyboard.isKeyDown(42) then + selectAndPasteColor() + else + copy() + end + -- V + elseif eventData[4] == 47 then + paste(clipboard) + -- X + elseif eventData[4] == 45 then + cut() + -- W + elseif eventData[4] == 17 then + mainContainer:stopEventHandling() + -- N + elseif eventData[4] == 49 then + newFile() + -- O + elseif eventData[4] == 24 then + openFileWindow() + -- U + elseif eventData[4] == 22 and component.isAvailable("internet") then + downloadFileFromWeb() + -- S + elseif eventData[4] == 31 then + -- Shift + if mainContainer.leftTreeView.selectedItem and not keyboard.isKeyDown(42) then + saveFileWindow() + else + saveFileAsWindow() + end + -- F + elseif eventData[4] == 33 then + toggleBottomToolBar() + -- G + elseif eventData[4] == 34 then + find() + -- L + elseif eventData[4] == 38 then + gotoLineWindow() + -- Backspace + elseif eventData[4] == 14 then + deleteLine(cursor.position.line) + -- Delete + elseif eventData[4] == 211 then + deleteLine(cursor.position.line) + -- R + elseif eventData[4] == 19 then + changeResolutionWindow() + end + -- Arrows up, down, left, right + elseif eventData[4] == 200 then + moveCursor(0, -1) + elseif eventData[4] == 208 then + moveCursor(0, 1) + elseif eventData[4] == 203 then + moveCursor(-1, 0) + elseif eventData[4] == 205 then + moveCursor(1, 0) + -- Backspace + elseif eventData[4] == 14 then + backspace() + -- Tab + elseif eventData[4] == 15 then + if keyboard.isKeyDown(42) then + indentOrUnindent(false) + else + indentOrUnindent(true) + end + -- Enter + elseif eventData[4] == 28 then + if mainContainer.autocompleteWindow.hidden then + enter() + else + pasteSelectedAutocompletion() + end + -- F5 + elseif eventData[4] == 63 then + run() + -- F9 + elseif eventData[4] == 67 then + -- Shift + if keyboard.isKeyDown(42) then + clearBreakpoints() + else + addBreakpoint() + end + -- Home + elseif eventData[4] == 199 then + setCursorPositionToHome() + -- End + elseif eventData[4] == 207 then + setCursorPositionToEnd() + -- Page Up + elseif eventData[4] == 201 then + pageUp() + -- Page Down + elseif eventData[4] == 209 then + pageDown() + -- Delete + elseif eventData[4] == 211 then + delete() + else + pasteAutoBrackets(eventData[3]) + end + + cursor.blinkState = true + tick() + elseif eventData[1] == "scroll" then + scroll(eventData[5], config.scrollSpeed) + tick() + elseif eventData[1] == "clipboard" then + paste(splitStringIntoLines(eventData[3])) + tick() + elseif not eventData[1] then + cursor.blinkState = not cursor.blinkState + tick() + end + end +end + +---------------------------------------------------- RUSH B! ---------------------------------------------------- + +loadConfig() +createMainContainer() +changeResolution(config.screenResolution.width, config.screenResolution.height) +updateTitle() +updateRAMProgressBar() +mainContainer:draw() + +if args[1] and fs.exists(args[1]) then + loadFile(args[1]) +else + newFile() +end + +mainContainer:draw() +buffer.draw() +mainContainer:startEventHandling(config.cursorBlinkDelay) + + diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/MineCode IDE.app/Resources/About/Russian.txt b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/MineCode IDE.app/Resources/About/Russian.txt new file mode 100755 index 00000000..8d91824a --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/MineCode IDE.app/Resources/About/Russian.txt @@ -0,0 +1 @@ +MineCode IDE - это мощный инстурмент для разработки приложений с богатым функционалом: от подсветки синтаксиса Lua, выделения текста и работы с буфером обмена до поддержки пользовательских цветовых схем. Удобный файловый менеджер также прилагается. \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/MineCode IDE.app/Resources/Icon.pic b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/MineCode IDE.app/Resources/Icon.pic new file mode 100755 index 0000000000000000000000000000000000000000..f913804b49c344b8a8f676b1cd7a05823cd15c6f GIT binary patch literal 179 zcmXYqyA8rX5JdfE_s*Bm0;vP4KtggvL;?IHkobh8R6%JJKoyRF#a#CEW@mOe?-#i^ zO*1*rF(7oTVCR4prre&Y@>x}~%cvY(|BCc_-0Ad$j4Ze11M;+v_#LzDrb5})g6{$b b9NR~4kVww&A!Qm#Vg@mTN$G%*-A literal 0 HcmV?d00001 diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/MineCode IDE.app/Resources/Localization/English.lang b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/MineCode IDE.app/Resources/Localization/English.lang new file mode 100755 index 00000000..cbe064d8 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/MineCode IDE.app/Resources/Localization/English.lang @@ -0,0 +1,61 @@ +{ + addBreakpoint = "Add breakpoint", + clearBreakpoints = "Clear breakpoints", + finishDebug = "Stop", + continueDebug = "Continue", + gotoLine = "Goto line", + lineNumber = "Line", + enableAutocompletion = "Enable autocompletion", + disableAutocompletion = "Disable autocompletion", + selectAndPasteColor = "Select and paste color", + enableAutoBrackets = "Enable autobrackets", + disableAutoBrackets = "Disable autobrackets", + url = "http://example.com/something.lua", + getFromWeb = "Download from URL", + debugging = "Debugger on line ", + runtimeError = "Runtime error", + variablesNotAvailable = "No variables found", + variables = "Values of the variables:", + changeResolution = "Change screen resolution", + pathToFile = "Path to file", + selectWord = "Select current word", + gotoCyka = "Goto", + gotoEnd = "Scroll to end", + gotoStart = "Scroll to start", + pageDown = "Page down", + pageUp = "Page up", + deleteLine = "Delete current line", + cursorProperties = "Cursor properties", + cursorSymbol = "Symbol", + cursorColor = "Color", + cursorBlinkDelay = "Blink delay", + selection = "Selection: ", + none = "none", + properties = "Properties", + about = "About", + quit = "Quit from MineCode", + line = " line, ", + symbol = " symbol", + lines = " lines, ", + symbols = " symbols", + file = "File", + cursor = "Cursor: ", + new = "New", + open = "Open", + openFile = "Open file", + save = "Save", + saveAs = "Save as", + colorScheme = "Color scheme", + color = "Color", + toggleTopToolBar = "Show top toolbar", + find = "Find", + findSomeShit = "Let's find some shit…", + cut = "Cut", + copy = "Copy", + paste = "Paste", + selectAll = "Select all", + edit = "Edit", + comment = "Toggle comment", + indent = "Indent", + unindent = "Unindent", +} \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/MineCode IDE.app/Resources/Localization/Russian.lang b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/MineCode IDE.app/Resources/Localization/Russian.lang new file mode 100755 index 00000000..e5a44fb2 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/MineCode IDE.app/Resources/Localization/Russian.lang @@ -0,0 +1,61 @@ +{ + addBreakpoint = "Точка останова", + clearBreakpoints = "Удалить точки останова", + finishDebug = "Стоп", + continueDebug = "Продолжить", + gotoLine = "Перейти к строке", + lineNumber = "Номер строки", + enableAutocompletion = "Включить автодополнение", + disableAutocompletion = "Отключить автодополнение", + selectAndPasteColor = "Выбрать и вставить цвет", + enableAutoBrackets = "Включить авто-скобки", + disableAutoBrackets = "Отключить авто-скобки", + url = "http://example.com/something.lua", + getFromWeb = "Загрузить по URL", + debugging = "Отладчик на строке ", + runtimeError = "Ошибка при выполнении программы", + variablesNotAvailable = "Не найдено возможных переменных", + variables = "Значения переменных:", + changeResolution = "Изменить разрешение экрана", + pathToFile = "Путь к файлу", + selectWord = "Выделить текущее слово", + gotoCyka = "Переход", + gotoEnd = "В конец", + gotoStart = "В начало", + pageDown = "На страницу ниже", + pageUp = "На страницу выше", + deleteLine = "Удалить текущую строку", + cursorProperties = "Параметры курсора", + cursorSymbol = "Символ", + cursorColor = "Цвет", + cursorBlinkDelay = "Время мигания", + selection = "Выделение: ", + none = "нет", + properties = "Настройки", + about = "О программе", + quit = "Выйти из MineCode", + line = " строка, ", + symbol = " символ", + lines = " строк, ", + symbols = " символов", + file = "Файл", + cursor = "Курсор: ", + new = "Новый", + open = "Открыть", + openFile = "Открыть файл", + save = "Сохранить", + saveAs = "Сохранить как", + colorScheme = "Цветовая схема", + color = "Цвет", + toggleTopToolBar = "Показать панель инстурментов", + find = "Найти", + findSomeShit = "Давай найдем какую-нибудь хуйню…", + cut = "Вырезать", + copy = "Копировать", + paste = "Вставить", + selectAll = "Выделить все", + edit = "Редактировать", + comment = "Комментировать", + indent = "Табулировать", + unindent = "Детабулировать", +} \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Palette.app/Main.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Palette.app/Main.lua new file mode 100755 index 00000000..22ef415b --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Palette.app/Main.lua @@ -0,0 +1,15 @@ + +local MineOSInterface = require("MineOSInterface") + +local mainContainer, window = MineOSInterface.addWindow( + MineOSInterface.windowFromContainer( + require("GUI").palette(1, 1, 0x9900FF) + ) +) + +window.OKButton.onTouch = function() + window:close() + MineOSInterface.OSDraw() +end + +window.cancelButton.onTouch = window.OKButton.onTouch \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Palette.app/Resources/Icon.pic b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Palette.app/Resources/Icon.pic new file mode 100755 index 0000000000000000000000000000000000000000..bb47d9e36b4894c7ca316cb020a3d940000a7825 GIT binary patch literal 201 zcmW-au?_)I5JmUQoA>r5)M`=T15~Ra5h?|0{xq4oy~W;J(m z@0qzX><)S%DoG-*!@jbj1Ne*PV?+aWh$~aV5dw8od|0zI zs?7qUHD(6ml&87a`S= 0 then + return math.floor(num + 0.5) + else + return math.ceil(num - 0.5) + end +end + +--Отрисовка "прозрачной зоны", этакая сеточка чередующаяся +local function drawTransparentZone(x, y) + if showTransparencyGrid then + y = y - 1 + + local stro4ka1 = "" + local stro4ka2 = "" + if image.getWidth(masterPixels) % 2 == 0 then + stro4ka1 = string.rep("▒ ", math.floor(image.getWidth(masterPixels) / 2)) + stro4ka2 = stro4ka1 + else + stro4ka1 = string.rep("▒ ", math.floor(image.getWidth(masterPixels) / 2)) + stro4ka2 = stro4ka1 .. "▒" + end + + for i = 1, image.getHeight(masterPixels) do + if i % 2 == 0 then + buffer.square(x, y + i, image.getWidth(masterPixels), 1, colors.transparencyWhite, colors.transparencyGray, " ") + buffer.text(x + 1, y + i, colors.transparencyGray, stro4ka1) + else + buffer.square(x, y + i, image.getWidth(masterPixels), 1, colors.transparencyWhite, colors.transparencyGray, " ") + buffer.text(x, y + i, colors.transparencyGray, stro4ka2) + end + end + else + buffer.square(x, y, image.getWidth(masterPixels), image.getHeight(masterPixels), colors.transparencyWhite, 0x000000, " ") + end +end + +--Банальная заливка фона +local function drawBackground() + buffer.square(sizes.xStartOfDrawingArea, sizes.yStartOfDrawingArea, sizes.widthOfDrawingArea, sizes.heightOfDrawingArea + 1, colors.drawingArea, 0xFFFFFF, " ") +end + +--Отрисовка цветов +local function drawColors() + local xPos, yPos = 2, buffer.getHeight() - 4 + buffer.square(xPos, yPos, 3, 2, currentBackground, 0xFFFFFF, " ") + buffer.square(xPos + 3, yPos + 1, 1, 2, currentForeground, 0xFFFFFF, " ") + buffer.square(xPos + 1, yPos + 2, 2, 1, currentForeground, 0xFFFFFF, " ") + buffer.text(xPos + 1, yPos + 3, 0xaaaaaa, "←→") + + newObj("Colors", 1, xPos, yPos, xPos + 2, yPos + 1) + newObj("Colors", 2, xPos + 3, yPos + 1, xPos + 3, yPos + 2) + newObj("Colors", 3, xPos + 1, yPos + 2, xPos + 3, yPos + 2) + newObj("Colors", 4, xPos + 1, yPos + 3, xPos + 2, yPos + 3) +end + +--Отрисовка панели инструментов слева +local function drawLeftBar() + --Рисуем подложечку + buffer.square(1, 5, sizes.widthOfLeftBar, sizes.heightOfLeftBar, colors.leftToolbar, 0xFFFFFF, " ") + --Рисуем инструменты + local yPos = sizes.yStartOfInstruments + for i = 1, #instruments do + if currentInstrument == i then + buffer.square(1, yPos, sizes.widthOfLeftBar, sizes.heightOfInstrument, colors.leftToolbarButton, 0xFFFFFF, " ") + else + buffer.square(1, yPos, sizes.widthOfLeftBar, sizes.heightOfInstrument, colors.leftToolbar, 0xFFFFFF, " ") + end + buffer.text(3, yPos + 1, colors.leftToolbarButtonText, instruments[i]) + + newObj("Instruments", i, 1, yPos, sizes.widthOfLeftBar, yPos + sizes.heightOfInstrument - 1) + + yPos = yPos + sizes.heightOfInstrument + end + --И цвета + drawColors() +end + +--Отрисовка верхнего меню +local function drawTopMenu() + obj.menu = GUI.menu(1, 1, buffer.getWidth(), colors.topMenu, colors.topMenuText, 0x3366CC, 0xFFFFFF, 0) + obj.menu:addItem("PS", ecs.colors.blue) + obj.menu:addItem(localization.file) + obj.menu:addItem(localization.image) + obj.menu:addItem(localization.view) + obj.menu:addItem(localization.hotkeys) + obj.menu:addItem(localization.about) + obj.menu:draw() +end + +--Функция, создающая пустой массив изображения на основе указанных ранее длины и ширины +local function createEmptyMasterPixels() + --Создаем пустой мастерпиксельс + for j = 1, image.getHeight(masterPixels) * image.getWidth(masterPixels) do + table.insert(masterPixels, 0x010101) + table.insert(masterPixels, 0x010101) + table.insert(masterPixels, 1.0) + table.insert(masterPixels, " ") + end +end + +--Мини-консолька для отладки, сообщающая снизу, че происходит ваще +local function console(text) + buffer.square(sizes.xStartOfDrawingArea, buffer.getHeight(), sizes.widthOfDrawingArea, 1, colors.console, colors.consoleText, " ") + + local _, total, used = ecs.getInfoAboutRAM() + local RAMText = used .. "/" .. total .. " KB RAM" + buffer.text(sizes.xEndOfDrawingArea - unicode.len(RAMText), buffer.getHeight(), colors.consoleText, RAMText) + + buffer.text(sizes.xStartOfDrawingArea + 1, buffer.getHeight(), colors.consoleText, text) +end + +--Функция, берущая указанный пиксель из массива изображения и рисующая его в буфере корректно, +--т.е. с учетом прозрачности и т.п. +local function drawPixel(x, y, xPixel, yPixel, iterator) + --Получаем тукущие данные о пикселе + local background, foreground, alpha, symbol = masterPixels[iterator], masterPixels[iterator + 1], masterPixels[iterator + 2], masterPixels[iterator + 3] + --Если пиксель не прозрачный + if alpha == 0 then + buffer.set(x, y, background, foreground, symbol) + --Если пиксель прозрачнее непрозрачного + elseif alpha > 0 then + local blendColor + if xPixel % 2 == 0 then + if yPixel % 2 == 0 then + blendColor = colors.transparencyGray + else + blendColor = colors.transparencyWhite + end + else + if yPixel % 2 == 0 then + blendColor = colors.transparencyWhite + else + blendColor = colors.transparencyGray + end + end + + buffer.set(x, y, color.blend(blendColor, background, alpha), foreground, symbol) + end + background, foreground, alpha, symbol = nil, nil, nil, nil +end + +--Пиздюлинка, показывающая размер текста и тыпы +local function drawTooltip(x, y, ...) + local texts = {...} + --Рассчитываем ширину пиздюлинки + local maxWidth = 0; for i = 1, #texts do maxWidth = math.max(maxWidth, unicode.len(texts[i])) end + --Рисуем пиздюлинку + buffer.square(x, y, maxWidth + 2, #texts, 0x000000, 0xFFFFFF, " ", 0.69); x = x + 1 + for i = 1, #texts do buffer.text(x, y, 0xFFFFFF, texts[i]); y = y + 1 end +end + +--Функция для отрисовки выделения соотв. инструментом +local function drawSelection() + local color = 0x000000 + local xStart, yStart = sizes.xStartOfImage + selection.x - 1, sizes.yStartOfImage + selection.y - 1 + local xEnd, yEnd = xStart + selection.width - 1, yStart + selection.height - 1 + local currentBackground + + local function nextColor() + if color == 0x000000 then color = 0xFFFFFF else color = 0x000000 end + end + + --"━" + --"┃" + + local function drawSelectionSquare(x1, y1, x2, y2, xStep, yStep, symbol) + for y = y1, y2, yStep do + for x = x1, x2, xStep do + local background = buffer.get(x, y) + buffer.set(x, y, background, color, symbol) + nextColor() + end + end + end + + local function drawCornerPoint(x, y, symbol) + local background = buffer.get(x, y) + buffer.set(x, y, background, color, symbol) + nextColor() + end + + if selection.width > 1 and selection.height > 1 then + drawCornerPoint(xStart, yStart, "┏") + drawSelectionSquare(xStart + 1, yStart, xEnd - 1, yStart, 1, 1, "━") + drawCornerPoint(xEnd, yStart, "┓") + drawSelectionSquare(xEnd, yStart + 1, xEnd, yEnd - 1, 1, 1, "┃") + drawCornerPoint(xEnd, yEnd, "┛") + drawSelectionSquare(xEnd - 1, yEnd, xStart + 1, yEnd, -1, 1, "━") + drawCornerPoint(xStart, yEnd, "┗") + drawSelectionSquare(xStart, yEnd - 1, xStart, yStart + 1, 1, -1, "┃") + else + if selection.width == 1 then + drawSelectionSquare(xStart, yStart, xStart, yEnd, 1, 1, "┃") + elseif selection.height == 1 then + drawSelectionSquare(xStart, yStart, xEnd, yStart, 1, 1, "━") + end + end + + drawTooltip(xEnd + 2, yEnd - 1, localization.w .. ": " .. selection.width .. " px", localization.h .. ": " .. selection.height .. " px") + -- drawTooltip(xEnd + 2, yEnd - 1, "Ш: " .. selection.width .. " px", "В: " .. selection.height .. " px", "S: " .. xStart .. "x" .. yStart, "E: " .. xEnd .. "x" .. yEnd) +end + +local function line(x0, y0, x1, y1, background, applyToMasterPixels) + local steep = false; + + if math.abs(x0 - x1) < math.abs(y0 - y1 ) then + x0, y0 = swap(x0, y0) + x1, y1 = swap(x1, y1) + steep = true; + end + + if (x0 > x1) then + x0, x1 = swap(x0, x1) + y0, y1 = swap(y0, y1) + end + + local dx = x1 - x0; + local dy = y1 - y0; + local derror2 = math.abs(dy) * 2 + local error2 = 0; + local y = y0; + + for x = x0, x1, 1 do + if steep then + if applyToMasterPixels then + image.set(masterPixels, y, x, background, 0x000000, 0x00, " ") + else + buffer.set(y, x, background, 0x000000, " ") + end + else + if applyToMasterPixels then + image.set(masterPixels, x, y, background, 0x000000, 0x00, " ") + else + buffer.set(x, y, background, 0x000000, " ") + end + end + + error2 = error2 + derror2; + + if error2 > dx then + y = y + (y1 > y0 and 1 or -1); + error2 = error2 - dx * 2; + end + end +end + +local function drawShapeCornerPoints(xStart, yStart, xEnd, yEnd) + buffer.set(xStart, yStart, 0x99FF80, 0x000000, " ") + buffer.set(xEnd, yEnd, 0x99FF80, 0x000000, " ") +end + +local function drawLineShape() + local xStart, yStart = sizes.xStartOfImage + selection.xStart - 1, sizes.yStartOfImage + selection.yStart - 1 + local xEnd, yEnd = sizes.xStartOfImage + selection.xEnd - 1, sizes.yStartOfImage + selection.yEnd - 1 + + line(xStart, yStart, xEnd, yEnd, currentBackground) + drawShapeCornerPoints(xStart, yStart, xEnd, yEnd) + drawTooltip(xEnd + 2, yEnd - 3, localization.w .. ": " .. selection.width .. " px", localization.h .. ": " .. selection.height .. " px", " ", localization.accept) +end + +--Функция для обводки выделенной зоны +local function stroke(x, y, width, height, color, applyToMasterPixels) + if applyToMasterPixels then + local iterator + for i = x, x + width - 1 do + iterator = image.getImageIndexByCoordinates(i, y, image.getWidth(masterPixels)) + masterPixels[iterator] = color; masterPixels[iterator + 1] = 0x0; masterPixels[iterator + 2] = 0x0; masterPixels[iterator + 3] = " " + + iterator = image.getImageIndexByCoordinates(i, y + height - 1, image.getWidth(masterPixels)) + masterPixels[iterator] = color; masterPixels[iterator + 1] = 0x0; masterPixels[iterator + 2] = 0x0; masterPixels[iterator + 3] = " " + end + + for i = y, y + height - 1 do + iterator = image.getImageIndexByCoordinates(x, i, image.getWidth(masterPixels)) + masterPixels[iterator] = color; masterPixels[iterator + 1] = 0x0; masterPixels[iterator + 2] = 0x0; masterPixels[iterator + 3] = " " + + iterator = image.getImageIndexByCoordinates(x + width - 1, i, image.getWidth(masterPixels)) + masterPixels[iterator] = color; masterPixels[iterator + 1] = 0x0; masterPixels[iterator + 2] = 0x0; masterPixels[iterator + 3] = " " + end + else + buffer.square(x, y, width, 1, color, 0x000000, " ") + buffer.square(x, y + height - 1, width, 1, color, 0x000000, " ") + + buffer.square(x, y, 1, height, color, 0x000000, " ") + buffer.square(x + width - 1, y, 1, height, color, 0x000000, " ") + end +end + +local function ellipse(xStart, yStart, width, height, color, applyToMasterPixels) + --helper function, draws pixel and mirrors it + local function setpixel4(centerX, centerY, deltaX, deltaY, color) + if applyToMasterPixels then + image.set(masterPixels, centerX + deltaX, centerY + deltaY, color, 0x000000, 0x00, " ") + image.set(masterPixels, centerX - deltaX, centerY + deltaY, color, 0x000000, 0x00, " ") + image.set(masterPixels, centerX + deltaX, centerY - deltaY, color, 0x000000, 0x00, " ") + image.set(masterPixels, centerX - deltaX, centerY - deltaY, color, 0x000000, 0x00, " ") + else + buffer.set(centerX + deltaX, centerY + deltaY, color, 0x000000, " ") + buffer.set(centerX - deltaX, centerY + deltaY, color, 0x000000, " ") + buffer.set(centerX + deltaX, centerY - deltaY, color, 0x000000, " ") + buffer.set(centerX - deltaX, centerY - deltaY, color, 0x000000, " ") + end + end + + --red ellipse, 2*10px border + local centerX = math.floor(xStart + width / 2) + local centerY = math.floor(yStart + height / 2) + local radiusX = math.floor(width / 2) + local radiusY = math.floor(height / 2) + local radiusX2 = radiusX ^ 2 + local radiusY2 = radiusY ^ 2 + + --upper and lower halves + local quarter = math.floor(radiusX2 / math.sqrt(radiusX2 + radiusY2)) + for x = 0, quarter do + local y = radiusY * math.sqrt(1 - x^2 / radiusX2) + setpixel4(centerX, centerY, x, math.floor(y), color); + end + + --right and left halves + quarter = math.floor(radiusY2 / math.sqrt(radiusX2 + radiusY2)); + for y = 0, quarter do + x = radiusX * math.sqrt(1 - y^2 / radiusY2); + setpixel4(centerX, centerY, math.floor(x), y, color); + end +end + +local function polygon(xCenter, yCenter, xStart, yStart, countOfEdges, color) + local degreeStep = 360 / countOfEdges + + local deltaX, deltaY = xStart - xCenter, yStart - yCenter + local radius = math.sqrt(deltaX ^ 2 + deltaY ^ 2) + local halfRadius = radius / 2 + local startDegree = math.deg(math.asin(deltaX / radius)) + + local function calculatePosition(degree) + local radDegree = math.rad(degree) + local deltaX2 = math.sin(radDegree) * radius + local deltaY2 = math.cos(radDegree) * radius + return round(xCenter + deltaX2), round(yCenter + (deltaY >= 0 and deltaY2 or -deltaY2)) + end + + local xOld, yOld, xNew, yNew = calculatePosition(startDegree) + + for degree = (startDegree + degreeStep - 1), (startDegree + 360), degreeStep do + xNew, yNew = calculatePosition(degree) + buffer.line(xOld, yOld, xNew, yNew, color, 0x000000, " ") + xOld, yOld = xNew, yNew + end +end + +local function drawPolygonShape() + local xStart, yStart = sizes.xStartOfImage + selection.xStart - 1, sizes.yStartOfImage + selection.yStart - 1 + local xEnd, yEnd = sizes.xStartOfImage + selection.xEnd - 1, sizes.yStartOfImage + selection.yEnd - 1 + + local radius = math.floor(math.sqrt(selection.width ^ 2 + (selection.height*2) ^ 2)) + polygon(xStart, yStart, xEnd, yEnd, currentPolygonCountOfEdges, currentBackground) + + drawShapeCornerPoints(xStart, yStart, xEnd, yEnd) + drawTooltip(xEnd + 2, yEnd - 3, localization.radius .. ": " .. radius .. " px", localization.edges .. ": " .. currentPolygonCountOfEdges, " ", localization.accept) +end + +local function drawSquareShape(type) + local xStart, yStart = sizes.xStartOfImage + selection.x - 1, sizes.yStartOfImage + selection.y - 1 + local xEnd, yEnd = xStart + selection.width - 1, yStart + selection.height - 1 + + if type == "filledSquare" then + buffer.square(xStart, yStart, selection.width, selection.height, currentBackground, 0x000000, " ") + elseif type == "frame" then + stroke(xStart, yStart, selection.width, selection.height, currentBackground, false) + elseif type == "ellipse" then + ellipse(xStart, yStart, selection.width, selection.height, currentBackground, false) + end + + drawShapeCornerPoints(xStart, yStart, xEnd, yEnd) + drawTooltip(xEnd + 2, yEnd - 3, localization.w .. ": " .. selection.width .. " px", localization.h .. ": " .. selection.height .. " px", " ", localization.accept) +end + +local function drawMultiPointInstrument() + if selection and selection.finished == true then + if instruments[currentInstrument] == "M" then + drawSelection() + elseif instruments[currentInstrument] == "S" then + if currentShape == localization.line then + drawLineShape() + elseif currentShape == localization.ellipse then + drawSquareShape("ellipse") + elseif currentShape == localization.rectangle then + drawSquareShape("filledSquare") + elseif currentShape == localization.border then + drawSquareShape("frame") + elseif currentShape == localization.polygon then + drawPolygonShape() + end + end + end +end + +--Отрисовка изображения +local function drawImage() + --Стартовые нужности + local xPixel, yPixel = 1, 1 + local xPos, yPos = sizes.xStartOfImage, sizes.yStartOfImage + + --Устанавливаем ограничение прорисовки, чтобы картинка не съебывала за дозволенную зону + buffer.setDrawLimit(sizes.xStartOfDrawingArea, sizes.yStartOfDrawingArea, sizes.widthOfDrawingArea, sizes.heightOfDrawingArea) + + --Рисуем прозрачную зону + drawTransparentZone(xPos, yPos) + + --Перебираем массив мастерпиксельса + for i = 3, #masterPixels, 4 do + --Рисуем пиксель, если у него прозрачность не абсолютная, ЛИБО имеется какой-то символ + --Т.е. даже если прозрачность и охуела, но символ есть, то рисуем его + if masterPixels[i + 2] ~= 1 or masterPixels[i + 3] ~= " " then + drawPixel(xPos, yPos, xPixel, yPixel, i) + end + --Всякие расчеты координат + xPixel = xPixel + 1 + xPos = xPos + 1 + if xPixel > image.getWidth(masterPixels) then xPixel = 1; xPos = sizes.xStartOfImage; yPixel = yPixel + 1; yPos = yPos + 1 end + end + + if image.getWidth(masterPixels) > 0 and image.getHeight(masterPixels) > 0 then + local text = localization.size .. ": " .. image.getWidth(masterPixels) .. "x" .. image.getHeight(masterPixels) .. " px" + xPos = math.floor(sizes.xStartOfImage + image.getWidth(masterPixels) / 2 - unicode.len(text) / 2) + buffer.text(xPos, sizes.yEndOfImage + 1, 0xFFFFFF, text) + end + + --Рисуем мультиинструмент + drawMultiPointInstrument() + --Убираем ограничение отрисовки + buffer.resetDrawLimit() +end + +--Просто для удобства +local function drawBackgroundAndImage() + drawBackground() + drawImage() +end + +--Функция, рисующая ВСЕ, абсолютли, епта +local function drawAll() + drawBackground() + drawLeftBar() + drawTopMenu() + drawBackgroundAndImage() + + buffer.draw() +end + +------------------------------------------------ Вспомогательные функции для работы с изображением и прочим -------------------------------------------------------------- + +--Смена инструмента на указанный номер +local function changeInstrumentTo(ID) + currentInstrument = ID + selection = nil + drawAll() +end + +--Перемещалка картинки в указанном направлении, поддерживающая все инструменты +local function move(direction) + if instruments[currentInstrument] == "M" and selection and selection.finished == true then + if direction == "up" then + selection.y = selection.y - 1 + if selection.y < 1 then selection.y = 1 end + elseif direction == "down" then + selection.y = selection.y + 1 + if selection.y + selection.height - 1 > image.getHeight(masterPixels) then selection.y = selection.y - 1 end + elseif direction == "left" then + selection.x = selection.x - 1 + if selection.x < 1 then selection.x = 1 end + elseif direction == "right" then + selection.x = selection.x + 1 + if selection.x + selection.width - 1 > image.getWidth(masterPixels) then selection.x = selection.x - 1 end + end + else + local howMuchUpDown = 2 + local howMuchLeftRight = 4 + if direction == "up" then + reCalculateImageSizes(sizes.xStartOfImage, sizes.yStartOfImage - howMuchUpDown) + elseif direction == "down" then + reCalculateImageSizes(sizes.xStartOfImage, sizes.yStartOfImage + howMuchUpDown) + elseif direction == "left" then + reCalculateImageSizes(sizes.xStartOfImage - howMuchLeftRight, sizes.yStartOfImage) + elseif direction == "right" then + reCalculateImageSizes(sizes.xStartOfImage + howMuchLeftRight, sizes.yStartOfImage) + end + end + drawBackgroundAndImage() + buffer.draw() +end + +--Просто более удобная установка пикселя, а то все эти плюсы, минусы, бррр +local function setPixel(iterator, background, foreground, alpha, symbol) + masterPixels[iterator] = background + masterPixels[iterator + 1] = foreground + masterPixels[iterator + 2] = alpha + masterPixels[iterator + 3] = symbol +end + +--Функция, меняющая цвета местами +local function swapColors() + currentBackground, currentForeground = swap(currentBackground, currentForeground) + drawColors() + console("Цвета поменяны местами") +end + +--Ух, сука! Функция для работы инструмента текста +--Лютая дичь, спиздил со старого фш, но, вроде, пашет нормас +--Правда, чет есть предчувствие, что костыльная и багованная она, ну да похуй +local function inputText(x, y, limit) + local oldPixels = ecs.rememberOldPixels(x,y-1,x+limit-1,y+1) + local text = "" + local inputPos = 1 + + local function drawThisShit() + for i = 1, inputPos do + ecs.invertedText(x + i - 1, y + 1, "─") + ecs.adaptiveText(x + i - 1, y - 1, " ", currentBackground) + end + ecs.invertedText(x + inputPos - 1, y + 1, "▲")--"▲","▼" + ecs.invertedText(x + inputPos - 1, y - 1, "▼") + ecs.adaptiveText(x, y, ecs.stringLimit("start", text, limit, false), currentBackground) + end + + drawThisShit() + + while true do + local e = {event.pull()} + if e[1] == "key_down" then + if e[4] == 14 then + if unicode.len(text) >= 1 then + text = unicode.sub(text, 1, -2) + if unicode.len(text) < (limit - 1) then + inputPos = inputPos - 1 + end + ecs.drawOldPixels(oldPixels) + drawThisShit() + end + elseif e[4] == 28 then + break + elseif e[4] == 200 then + text = text .. "▀" + if unicode.len(text) < limit then + inputPos = inputPos + 1 + end + drawThisShit() + elseif e[4] == 208 then + text = text .. "▄" + if unicode.len(text) < limit then + inputPos = inputPos + 1 + end + drawThisShit() + else + local symbol = ecs.convertCodeToSymbol(e[3]) + if symbol ~= nil then + text = text .. symbol + if unicode.len(text) < limit then + inputPos = inputPos + 1 + end + drawThisShit() + end + end + elseif e[1] == "clipboard" then + if e[3] then + text = text .. e[3] + if unicode.len(text) < limit then + inputPos = inputPos + unicode.len(e[3]) + end + drawThisShit() + end + end + end + + ecs.drawOldPixels(oldPixels) + if text == "" then text = " " end + return text +end + +--Функция-применятор текста к массиву изображения +local function saveTextToPixels(x, y, text) + local sText = unicode.len(text) + local iterator + x = x - 1 + for i = 1, sText do + if x + i > image.getWidth(masterPixels) then break end + iterator = image.getImageIndexByCoordinates(x + i, y, image.getWidth(masterPixels)) + setPixel(iterator, masterPixels[iterator], currentBackground, masterPixels[iterator + 2], unicode.sub(text, i, i)) + end +end + +--Функция-центратор картинки по центру моника +local function tryToFitImageOnCenterOfScreen() + reCalculateImageSizes() + + local x, y = sizes.xStartOfImage, sizes.yStartOfImage + if image.getWidth(masterPixels) < sizes.widthOfDrawingArea then + x = math.floor(sizes.xStartOfDrawingArea + sizes.widthOfDrawingArea / 2 - image.getWidth(masterPixels) / 2) - 1 + end + + if image.getHeight(masterPixels) < sizes.heightOfDrawingArea then + y = math.floor(sizes.yStartOfDrawingArea + sizes.heightOfDrawingArea / 2 - image.getHeight(masterPixels) / 2) + end + + reCalculateImageSizes(x, y) +end + +--Функция, спрашивающая юзверя, какого размера пикчу он хочет создать - ну, и создает ее +local function new() + selection = nil + local data = ecs.universalWindow("auto", "auto", 30, ecs.windowColors.background, true, {"EmptyLine"}, {"CenterText", 0x262626, localization.newDocument}, {"EmptyLine"}, {"Input", 0x262626, 0x880000, localization.width}, {"Input", 0x262626, 0x880000, localization.height}, {"EmptyLine"}, {"Button", {0x999999, 0xffffff, "OK"}}) + + data[1] = tonumber(data[1]) or 51 + data[2] = tonumber(data[2]) or 19 + + masterPixels = {} + masterPixels[1], masterPixels[2] = data[1], data[2] + createEmptyMasterPixels() + tryToFitImageOnCenterOfScreen() + drawAll() +end + +--Обычная рекурсивная заливка, алгоритм спизжен с вики +--Есть инфа, что выжирает стек, но Луа, вроде, не особо ругается, так что заебок все +local function fill(x, y, startColor, fillColor) + local function doFill(xStart, yStart) + local iterator = image.getImageIndexByCoordinates(xStart, yStart, image.getWidth(masterPixels)) + + --Завершаем функцию, если цвет в массиве не такой, какой мы заливаем + if masterPixels[iterator] ~= startColor or masterPixels[iterator] == fillColor then return end + + --Заливаем в память + masterPixels[iterator] = fillColor + masterPixels[iterator + 2] = currentAlpha + + doFill(xStart + 1, yStart) + doFill(xStart - 1, yStart) + doFill(xStart, yStart + 1) + doFill(xStart, yStart - 1) + + iterator = nil + end + doFill(x, y) +end + +--Кисть, КИИИИСТЬ +local function brush(x, y, background, foreground, alpha, symbol) + --Смещение влево и вправо относительно указанного центра кисти + --КОРОЧ, НЕ ТУПИ + --Чтобы кисточка была по центру мыши, ну + local position = math.floor(currentBrushSize / 2) + x, y = x - position, y - position + --Хуевинка для рисования + local newIterator + --Перебираем кисть по ширине и высоте + for cyka = 1, currentBrushSize do + for pidor = 1, currentBrushSize do + --Если этот кусочек входит в границы рисовабельной зоны, то + if x >= 1 and x <= image.getWidth(masterPixels) and y >= 1 and y <= image.getHeight(masterPixels) then + + --Считаем итератор для кусочка кисти + newIterator = image.getImageIndexByCoordinates(x, y, image.getWidth(masterPixels)) + + --Если прозрачности кисти ВАЩЕ НЕТ, то просто рисуем как обычненько все + if alpha == 0 then + setPixel(newIterator, background, foreground, alpha, symbol) + --Если прозрачности кисти есть какая-то, но она не абсолютная + elseif alpha < 1 and alpha > 0 then + --Если пиксель в массиве ни хуя не прозрачный, то оставляем его таким же, разве что цвет меняем на сблендированный + if masterPixels[newIterator + 2] == 0 then + local gettedBackground = color.blend(masterPixels[newIterator], background, alpha) + setPixel(newIterator, gettedBackground, foreground, 0, symbol) + --А если прозрачный, то смешиваем прозрачности + --Пиздануться вообще, сук + else + --Если его прозрачность максимальная + if masterPixels[newIterator + 2] == 1 then + setPixel(newIterator, background, foreground, alpha, symbol) + --Если не максимальная + else + local newAlpha = masterPixels[newIterator + 2] - (1 - alpha) + if newAlpha < 0 then newAlpha = 0 end + setPixel(newIterator, background, foreground, newAlpha, symbol) + end + end + --Если указанная прозрачность максимальна, т.е. равна 0xFF + else + setPixel(newIterator, 0x000000, 0x000000, 1, " ") + end + + --Рисуем пиксель из мастерпиксельса + drawPixel(x + sizes.xStartOfImage - 1, y + sizes.yStartOfImage - 1, x, y, newIterator) + end + + x = x + 1 + end + x = x - currentBrushSize + y = y + 1 + end +end + +--Функция-обрезчик картинки +local function crop() + if selection then + masterPixels = image.crop(masterPixels, selection.x, selection.y, selection.width, selection.height) + selection = nil + tryToFitImageOnCenterOfScreen() + drawAll() + end +end + +--Функция-расширитель картинки +local function expand() + local data = ecs.universalWindow("auto", "auto", 30, ecs.windowColors.background, true, + {"EmptyLine"}, + {"CenterText", 0x262626, localization.expand}, + {"EmptyLine"}, + {"Input", 0x262626, 0x880000, localization.countOfPixels}, + {"Selector", 0x262626, 0x880000, localization.fromBottom, localization.fromTop, localization.fromLeft, localization.fromRight}, + {"EmptyLine"}, + {"Button", {0xaaaaaa, 0xffffff, "OK"}, {0x888888, 0xffffff, localization.cancel}} + ) + + if data[3] == "OK" then + local countOfPixels = tonumber(data[1]) + if countOfPixels then + masterPixels = image.expand(masterPixels, + data[2] == localization.fromTop and countOfPixels or 0, + data[2] == localization.fromBottom and countOfPixels or 0, + data[2] == localization.fromLeft and countOfPixels or 0, + data[2] == localization.fromRight and countOfPixels or 0, + 0x010101, 0x010101, 1, " " + ) + reCalculateImageSizes(sizes.xStartOfImage, sizes.yStartOfImage) + drawAll() + end + end +end + +--Функция-загрузчик картинки из файла +local function loadImageFromFile(path) + if fs.exists(path) then + selection = nil + masterPixels = image.load(path) + savePath = path + tryToFitImageOnCenterOfScreen() + else + ecs.error("Файл \"" .. path .. "\" не существует") + end +end + +--Функция-заполнитель выделенной зоны какими-либо данными +local function fillSelection(background, foreground, alpha, symbol) + for j = selection.y, selection.y + selection.height - 1 do + for i = selection.x, selection.x + selection.width - 1 do + local iterator = image.getImageIndexByCoordinates(i, j, image.getWidth(masterPixels)) + masterPixels[iterator] = background + masterPixels[iterator + 1] = foreground + masterPixels[iterator + 2] = alpha + masterPixels[iterator + 3] = symbol + end + end + + drawAll() +end + +local function applyShapeToMasterPixels() + if currentShape == localization.line then + line(selection.xStart, selection.yStart, selection.xEnd, selection.yEnd, currentBackground, true) + elseif currentShape == localization.rectangle then + fillSelection(currentBackground, 0x00000, 0x00, " ") + elseif currentShape == localization.border then + stroke(selection.x, selection.y, selection.width, selection.height, currentBackground, true) + elseif currentShape == localization.ellipse then + ellipse(selection.x, selection.y, selection.width, selection.height, currentBackground, true) + end + + selection = nil + drawBackgroundAndImage() + buffer.draw() +end + +------------------------------------------------ Старт программы -------------------------------------------------------------- + +--Рисуем весь интерфейс чисто для красоты +drawAll() + +--Открываем файлы по аргументам программы +if args[1] and fs.exists(args[1]) then + loadImageFromFile(args[1]) +else + new() +end + +--Отрисовываем интерфейс снова, поскольку у нас либо создался новый документ, либо открылся имеющийся файл +drawAll() + +--Анализируем ивенты +while true do + local e = {event.pull()} + if e[1] == "touch" or e[1] == "drag" or e[1] == "drop" then + --Левый клик + if e[5] == 0 then + --Если кликнули на рисовабельную зонку + if ecs.clickedAtArea(e[3], e[4], sizes.xStartOfImage, sizes.yStartOfImage, sizes.xEndOfImage, sizes.yEndOfImage) then + + --Получаем координаты в изображении и итератор + local x, y = e[3] - sizes.xStartOfImage + 1, e[4] - sizes.yStartOfImage + 1 + local iterator = image.getImageIndexByCoordinates(x, y, image.getWidth(masterPixels)) + + --Все для инструментов мультиточечного рисования + if instruments[currentInstrument] == "M" or instruments[currentInstrument] == "S" then + if e[1] == "touch" then + selection = {} + selection.xStart, selection.yStart = x, y + + elseif e[1] == "drag" and selection then + local x1, y1 = selection.xStart, selection.yStart + local x2, y2 = x, y + if x1 > x2 then + x1, x2 = swap(x1, x2) + end + if y1 > y2 then + y1, y2 = swap(y1, y2) + end + + selection.x, selection.y = x1, y1 + selection.x2, selection.y2 = x2, y2 + selection.xEnd, selection.yEnd = x, y + selection.width = selection.x2 - selection.x + 1 + selection.height = selection.y2 - selection.y + 1 + + selection.finished = true + end + + --Если выделение полностью завершено, то отдаем контроль отрисовочным функциям + -- if instruments[currentInstrument] == "M" then + drawBackgroundAndImage() + buffer.draw() + -- end + --Кисть + elseif instruments[currentInstrument] == "B" then + + --Если нажата клавиша альт + if keyboard.isKeyDown(56) then + local _, gettedForeground, gettedBackground = component.gpu.get(e[3], e[4]) + currentBackground = gettedBackground + currentForeground = gettedForeground + drawColors() + buffer.draw() + + --Если обычная кисть, просто кисть, вообще всем кистям кисть + else + brush(x, y, currentBackground, currentForeground, currentAlpha, currentSymbol) + --Пишем что-то в консоли + console("Кисть: клик на точку "..e[3].."x"..e[4]..", координаты в изображении: "..x.."x"..y..", индекс массива изображения: "..iterator) + buffer.draw() + end + --Ластик + elseif instruments[currentInstrument] == "E" then + brush(x, y, currentBackground, currentForeground, 1, currentSymbol) + console("Ластик: клик на точку "..e[3].."x"..e[4]..", координаты в изображении: "..x.."x"..y..", индекс массива изображения: "..iterator) + buffer.draw() + --Текст + elseif instruments[currentInstrument] == "T" then + local limit = image.getWidth(masterPixels) - x + 1 + local text = inputText(e[3], e[4], limit) + saveTextToPixels(x, y, text) + drawImage() + buffer.draw() + + --Заливка + elseif instruments[currentInstrument] == "F" then + + fill(x, y, masterPixels[iterator], currentBackground) + drawImage() + buffer.draw() + + end + + iterator, x, y = nil, nil, nil + + end + + --Цвета + for key in pairs(obj["Colors"]) do + if ecs.clickedAtArea(e[3], e[4], obj["Colors"][key][1], obj["Colors"][key][2], obj["Colors"][key][3], obj["Colors"][key][4]) then + if key == 1 then + currentBackground = GUI.palette(math.floor(buffer.getWidth() / 2 - 35), math.floor(buffer.getHeight() / 2 - 12), currentBackground):show() or currentBackground + drawAll() + elseif key == 2 or key == 3 then + currentForeground = GUI.palette(math.floor(buffer.getWidth() / 2 - 35), math.floor(buffer.getHeight() / 2 - 12), currentForeground):show() or currentForeground + drawAll() + elseif key == 4 then + buffer.text(obj["Colors"][key][1], obj["Colors"][key][2], 0xFF0000, "←→") + os.sleep(0.2) + swapColors() + buffer.draw() + end + break + end + end + + --Инструменты + for key in pairs(obj["Instruments"]) do + if ecs.clickedAtArea(e[3], e[4], obj["Instruments"][key][1], obj["Instruments"][key][2], obj["Instruments"][key][3], obj["Instruments"][key][4]) then + selection = nil + currentInstrument = key + drawLeftBar(); buffer.draw() + if instruments[currentInstrument] == "S" then + local menu = GUI.contextMenu(obj["Instruments"][key][3] + 1, obj["Instruments"][key][2]) + menu:addItem(localization.line) + menu:addItem(localization.ellipse) + menu:addItem(localization.rectangle) + menu:addItem(localization.polygon) + menu:addItem(localization.border) + + local action = menu:show() + currentShape = action or localization.line + if currentShape == localization.polygon then + local data = ecs.universalWindow("auto", "auto", 30, ecs.windowColors.background, true, + {"EmptyLine"}, + {"CenterText", 0x262626, localization.edges}, + {"EmptyLine"}, + {"Selector", 0x262626, 0x880000, "3", "4", "5", "6", "7", "8", "9", "10"}, + {"EmptyLine"}, + {"Button", {0xaaaaaa, 0xffffff, "OK"}} + ) + currentPolygonCountOfEdges = tonumber(data[1]) + end + drawAll() + end + break + end + end + + --Верхний меню-бар + local object + for i = 1, #obj.menu.children do + if obj.menu.children[i]:isClicked(e[3], e[4]) then + object = obj.menu.children[i] + break + end + end + if object then + object:press({draw = function() end}, object, {}) + buffer.draw() + local action + if object.text == localization.file then + local menu = GUI.contextMenu(object.x, object.y + 1) + + menu:addItem(localization.new, false, "^N") + menu:addItem(localization.open, false, "^O") + menu:addItem(localization.createFromString) + menu:addSeparator() + menu:addItem(localization.save, savePath == nil, "^S") + menu:addItem(localization.saveAs) + menu:addSeparator() + menu:addItem(localization.exit) + + action = menu:show() + elseif object.text == localization.image then + local menu = GUI.contextMenu(object.x, object.y + 1) + + menu:addItem(localization.expand) + menu:addSeparator() + menu:addItem(localization.flipHorizontal) + menu:addItem(localization.flipVertical) + + action = menu:show() + elseif object.text == localization.view then + local menu = GUI.contextMenu(object.x, object.y + 1) + + menu:addItem(localization.transparencyPad) + + action = menu:show() + elseif object.text == localization.about then + ecs.universalWindow("auto", "auto", 36, 0xeeeeee, true, {"EmptyLine"}, {"CenterText", 0x880000, photoshopVersion}, {"EmptyLine"}, {"CenterText", 0x262626, localization.developers}, {"CenterText", 0x555555, "Тимофеев Игорь"}, {"CenterText", 0x656565, "vk.com/id7799889"}, {"CenterText", 0x656565, "Трифонов Глеб"}, {"CenterText", 0x656565, "vk.com/id88323331"}, {"EmptyLine"}, {"CenterText", 0x262626, localization.testers}, {"CenterText", 0x656565, "Шестаков Тимофей"}, {"CenterText", 0x656565, "vk.com/id113499693"}, {"CenterText", 0x656565, "Вечтомов Роман"}, {"CenterText", 0x656565, "vk.com/id83715030"}, {"CenterText", 0x656565, "Омелаенко Максим"}, {"CenterText", 0x656565, "vk.com/paladincvm"}, {"EmptyLine"},{"Button", {0xbbbbbb, 0xffffff, "OK"}}) + elseif object.text == localization.hotkeys then + ecs.universalWindow( "auto", "auto", 42, 0xeeeeee, true, + table.unpack(localization.hotkeysLines) + ) + end + + if action == localization.exit then + ecs.prepareToExit() + return + elseif action == localization.hueSaturation then + local data = ecs.universalWindow("auto", "auto", 30, ecs.windowColors.background, true, + {"EmptyLine"}, + {"CenterText", 0x262626, localization.hueSaturation}, + {"EmptyLine"}, + {"Slider", 0x262626, 0x880000, 0, 100, 50, localization.hue .. ": ", ""}, + {"Slider", 0x262626, ecs.colors.red, 0, 100, 50, localization.saturation .. ": ", ""}, + {"Slider", 0x262626, 0x000000, 0, 100, 50, localization.brightness .. ": ", ""}, + {"EmptyLine"}, + {"Button", {0xaaaaaa, 0xffffff, "OK"}, {0x888888, 0xffffff, localization.cancel}} + ) + if data[4] == "OK" then + masterPixels = image.hueSaturationBrightness(masterPixels, data[1] - 50, data[2] - 50, data[3] - 50) + drawAll() + end + elseif action == localization.gaussianBlur then + local data = ecs.universalWindow("auto", "auto", 30, ecs.windowColors.background, true, + {"EmptyLine"}, + {"CenterText", 0x262626, localization.gaussianBlur}, + {"EmptyLine"}, + {"Slider", 0x262626, 0x880000, 1, 5, 2, localization.radius .. ": ", ""}, + {"Slider", 0x262626, 0x880000, 1, 255, 0x88, localization.force .. ": ", ""}, + {"EmptyLine"}, + {"Button", {0xaaaaaa, 0xffffff, "OK"}, {0x888888, 0xffffff, localization.cancel}} + ) + if data[3] == "OK" then + masterPixels = image.gaussianBlur(masterPixels, tonumber(data[1]), tonumber(data[2])) + drawAll() + end + elseif action == localization.colorBalance then + local data = ecs.universalWindow("auto", "auto", 30, ecs.windowColors.background, true, + {"EmptyLine"}, + {"CenterText", 0x262626, localization.colorBalance}, + {"EmptyLine"}, + {"Slider", 0x262626, 0x880000, 0, 100, 50, "R: ", ""}, + {"Slider", 0x262626, ecs.colors.green, 0, 100, 50, "G: ", ""}, + {"Slider", 0x262626, ecs.colors.blue, 0, 100, 50, "B: ", ""}, + {"EmptyLine"}, + {"Button", {0xaaaaaa, 0xffffff, "OK"}, {0x888888, 0xffffff, localization.cancel}} + ) + if data[4] == "OK" then + masterPixels = image.colorBalance(masterPixels, data[1] - 50, data[2] - 50, data[3] - 50) + drawAll() + end + elseif action == localization.photoFilter then + local data = ecs.universalWindow("auto", "auto", 30, ecs.windowColors.background, true, + {"EmptyLine"}, + {"CenterText", 0x262626, localization.photoFilter}, + {"EmptyLine"}, + {"Color", localization.filterColor, 0x333333}, + {"Slider", 0x262626, 0x880000, 0, 255, 100, localization.transparency .. ": ", ""}, + {"EmptyLine"}, + {"Button", {0xaaaaaa, 0xffffff, "OK"}, {0x888888, 0xffffff, localization.cancel}} + ) + if data[3] == "OK" then + masterPixels = image.photoFilter(masterPixels, data[1], data[2]) + drawAll() + end + elseif action == localization.crop then + crop() + elseif action == localization.expand then + expand() + elseif action == localization.flipVertical then + masterPixels = image.flipVertically(masterPixels) + drawAll() + elseif action == localization.flipHorizontal then + masterPixels = image.flipHorizontally(masterPixels) + drawAll() + elseif action == localization.invertColors then + masterPixels = image.invert(masterPixels) + drawAll() + elseif action == localization.blackWhite then + masterPixels = image.blackAndWhite(masterPixels) + drawAll() + elseif action == localization.rotateBy90 then + masterPixels = image.rotate(masterPixels, 90) + drawAll() + elseif action == localization.rotateBy180 then + masterPixels = image.rotate(masterPixels, 180) + drawAll() + elseif action == localization.new then + new() + drawAll() + elseif action == localization.saveAs then + local data = ecs.universalWindow("auto", "auto", 30, ecs.windowColors.background, true, + {"EmptyLine"}, + {"CenterText", 0x262626, localization.saveAs}, + {"EmptyLine"}, + {"Input", 0x262626, 0x880000, localization.path}, + {"Selector", 0x262626, 0x880000, "OCIF6", "OCIF1", "StringImage"}, + {"EmptyLine"}, + {"Button", {0xaaaaaa, 0xffffff, "OK"}, {0x888888, 0xffffff, localization.cancel}} + ) + + if data[3] == "OK" then + data[1] = data[1] or "Untitled.pic" + + local path = string.gsub(data[1], "%.pic$", "") .. ".pic" + if data[2] == "StringImage" then + local file = io.open(path, "w") + file:write(image.toString(masterPixels)) + file:close() + else + savePath = path + image.save(path, masterPixels, data[2] == "OCIF6" and 6 or 1) + end + end + elseif action == localization.save then + image.save(savePath, masterPixels) + + elseif action == localization.open then + local data = ecs.universalWindow("auto", "auto", 30, ecs.windowColors.background, true, {"EmptyLine"}, {"CenterText", 0x262626, localization.open}, {"EmptyLine"}, {"Input", 0x262626, 0x880000, localization.path}, {"EmptyLine"}, {"Button", {0xaaaaaa, 0xffffff, "OK"}, {0x888888, 0xffffff, localization.cancel}}) + if data[2] == "OK" then + local fileFormat = ecs.getFileFormat(data[1]) + + if not data[1] then + ecs.error("Некорректное имя файла!") + elseif not fs.exists(data[1]) then + ecs.error("Файл\""..data[1].."\" не существует!") + elseif fileFormat ~= ".pic" and fileFormat ~= ".rawpic" and fileFormat ~= ".png" then + ecs.error("Формат файла \""..fileFormat.."\" не поддерживается!") + else + loadImageFromFile(data[1]) + drawAll() + end + end + elseif action == localization.createFromString then + local data = ecs.universalWindow("auto", "auto", 30, ecs.windowColors.background, true, + {"EmptyLine"}, + {"CenterText", 0x262626, localization.createFromString}, + {"EmptyLine"}, + {"Input", 0x262626, 0x880000, localization.string}, + {"EmptyLine"}, + {"Button", {0xaaaaaa, 0xffffff, "OK"}, {0x888888, 0xffffff, localization.cancel}} + ) + if data[2] == "OK" then + local success, picture = pcall(image.fromString, data[1]) + if success then + masterPixels, selection, savePath = picture, nil, nil + tryToFitImageOnCenterOfScreen() + drawAll() + else + error("Failed to create image from string") + end + end + elseif action == localization.transparencyPad then + local data = ecs.universalWindow("auto", "auto", 30, ecs.windowColors.background, true, + {"EmptyLine"}, + {"CenterText", 0x262626, localization.transparencyPad}, + {"EmptyLine"}, + {"Color", localization.transparencyColor .. " 1", colors.transparencyWhite}, + {"Color", localization.transparencyColor .. " 2", colors.transparencyGray}, + {"EmptyLine"}, + {"Switch", 0xF2B233, 0xffffff, 0x262626, localization.transparencyGrid, showTransparencyGrid}, + {"EmptyLine"}, + {"Button", {0xaaaaaa, 0xffffff, "OK"}, {0x888888, 0xffffff, localization.cancel}} + ) + + if data[4] == "OK" then + colors.transparencyWhite, colors.transparencyGray, showTransparencyGrid = data[1], data[2], data[3] + drawAll() + end + end + + drawTopMenu() + buffer.draw() + end + else + --Если кликнули на рисовабельную зонку + if ecs.clickedAtArea(e[3], e[4], sizes.xStartOfImage, sizes.yStartOfImage, sizes.xEndOfImage, sizes.yEndOfImage) then + + if instruments[currentInstrument] == "M" and selection then + local menu = GUI.contextMenu(e[3], e[4]) + + menu:addItem(localization.deselect) + menu:addItem(localization.crop) + menu:addSeparator() + menu:addItem(localization.fill) + menu:addItem(localization.border) + menu:addSeparator() + menu:addItem(localization.clear) + + action = menu:show() + if action == localization.deselect then + selection = nil + drawAll() + elseif action == localization.crop then + crop() + elseif action == localization.clear then + fillSelection(0x0, 0x0, 1, " ") + elseif action == localization.fill then + fillSelection(currentBackground, 0x0, 0x0, " ") + elseif action == localization.border then + stroke(selection.x, selection.y, selection.width, selection.height, currentBackground, true) + drawAll() + end + else + local x, y, width, height = e[3], e[4], 30, 12 + --А это чтоб за края экрана не лезло + if y + height >= buffer.getHeight() then y = buffer.getHeight() - height end + if x + width + 1 >= buffer.getWidth() then x = buffer.getWidth() - width - 1 end + + currentBrushSize, currentAlpha = table.unpack(ecs.universalWindow(x, y, width, 0xeeeeee, true, {"EmptyLine"}, {"CenterText", 0x880000, localization.brushParameters}, {"Slider", 0x262626, 0x880000, 1, 10, currentBrushSize, localization.size .. ": ", " px"}, {"Slider", 0x262626, 0x880000, 0, 255, currentAlpha, localization.transparency .. ": ", ""}, {"EmptyLine"}, {"Button", {0xbbbbbb, 0xffffff, "OK"}})) + buffer.draw() + end + end + end + + elseif e[1] == "key_down" then + --Стрелки + if e[4] == 200 then + move("up") + elseif e[4] == 208 then + move("down") + elseif e[4] == 203 then + move("left") + elseif e[4] == 205 then + move("right") + -- --Пробел + -- elseif e[4] == 57 then + -- drawAll() + --ENTER + elseif e[4] == 28 then + if instruments[currentInstrument] == "S" and selection and selection.finished then + applyShapeToMasterPixels() + end + --BACKSPACE + elseif e[4] == 14 then + if selection and selection.finished then + fillSelection(0x000000, 0x000000, 0x00, " ") + drawAll() + end + --X + elseif e[4] == 45 then + swapColors() + buffer.draw() + --B + elseif e[4] == 48 then + changeInstrumentTo(2) + --E + elseif e[4] == 18 then + changeInstrumentTo(3) + --G + elseif e[4] == 34 then + changeInstrumentTo(4) + --T + elseif e[4] == 20 then + changeInstrumentTo(5) + --M + elseif e[4] == 50 then + changeInstrumentTo(1) + --D + elseif e[4] == 32 then + if keyboard.isControlDown() then + selection = nil + drawAll() + else + currentBackground = 0x000000 + currentForeground = 0xFFFFFF + currentAlpha = 0x00 + drawColors() + buffer.draw() + end + end + elseif e[1] == "scroll" then + if e[5] == 1 then + move("up") + else + move("down") + end + end +end + +------------------------------------------------ Выход из программы -------------------------------------------------------------- diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Photoshop.app/Resources/About/Russian.txt b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Photoshop.app/Resources/About/Russian.txt new file mode 100755 index 00000000..a4aa238f --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Photoshop.app/Resources/About/Russian.txt @@ -0,0 +1 @@ +Photoshop - это мощный графический редактор, написанный специально для работы с нашей ОС. Он поддерживает работу с кистями, прозрачностью, имеет функции заливки, выбора цвета из красочной палитры, позволяет создавать настоящие мини-шедевры прямо на вашем ПК. Вся графика в нашей ОС нарисована именно в этой программе. \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Photoshop.app/Resources/Icon.pic b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Photoshop.app/Resources/Icon.pic new file mode 100755 index 0000000000000000000000000000000000000000..e7d2f0beb1f4d8e9a159f2b44c89aa1568c9e053 GIT binary patch literal 113 zcmXYo%Mn085JdOQtg&IZ5(Y(aqRIv&3ZZQP5@Aa1sXwoKpSHzPBn0a;69m)PSnCx% kL4~AvNUt+7`Jq-F87K&xWL7|CHwA^~zWo0Z>-4A#J|3wHZ2$lO literal 0 HcmV?d00001 diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Photoshop.app/Resources/Localization/English.lang b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Photoshop.app/Resources/Localization/English.lang new file mode 100755 index 00000000..c576084a --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Photoshop.app/Resources/Localization/English.lang @@ -0,0 +1,92 @@ +{ + newDocument = "New document", + width = "Width", + height = "Height", + cancel = "Cancel", + w = "W", + h = "H", + + view = "View", + transparencyPad = "Transparency pad", + transparencyColor = "Transparency color", + transparencyGrid = "Transparency grid", + + path = "Path", + string = "String", + file = "File", + image = "Image", + edit = "Edit", + hotkeys = "Hot keys", + about = "About", + brushSize = "Brush size", + transparency = "Transparency", + brushParameters = "Brush parameters", + size = "Size", + + new = "New", + open = "Open", + createFromString = "Create from StringImage", + save = "Save", + saveAs = "Save as", + exit = "Exit", + + crop = "Crop", + expand = "Expand", + rotateBy90 = "Rotate by 90 degrees", + rotateBy180 = "Rotate by 180 degrees", + flipVertical = "Flip vertical", + flipHorizontal = "Flip horizontal", + + hueSaturation = "Hue/Saturation", + colorBalance = "Color balance", + photoFilter = "Photo filter", + invertColors = "Invert colors", + blackWhite = "Black and white", + gaussianBlur = "Gaussian blur", + + countOfPixels = "Count of pixels", + fromBottom = "From bottom", + fromTop = "From top", + fromLeft = "From left", + fromRight = "From right", + hue = "Hue", + saturation = "Saturation", + brightness = "Brightness", + filterColor = "Filter color", + radius = "Radius", + force = "Force", + edges = "Edges", + accept = "Enter - accept", + + line = "Line", + ellipse = "Ellipse", + rectangle = "Rectangle", + polygon = "Polygon", + border = "Border", + + fill = "Fill", + clear = "Clear", + deselect = "Deselect", + + hotkeysLines = { + {"EmptyLine"}, + {"CenterText", 0x880000, "Hotkeys"}, + {"EmptyLine"}, + {"CenterText", 0x000000, "B - Brush"}, + {"CenterText", 0x000000, "E - Eraser"}, + {"CenterText", 0x000000, "T - Text"}, + {"CenterText", 0x000000, "G - Fill"}, + {"CenterText", 0x000000, "M - Selection"}, + {"CenterText", 0x000000, " "}, + {"CenterText", 0x000000, "Arrows - move image"}, + {"CenterText", 0x000000, "X - swap colors"}, + {"CenterText", 0x000000, "D - make colors black&white"}, + {"CenterText", 0x000000, "Ctrl+D - deselect"}, + {"CenterText", 0x000000, "Alt+Click - pick color (brush only)"}, + {"EmptyLine"}, + {"Button", {0xbbbbbb, 0xffffff, "OK"}}, + }, + + developers = "Developers:", + testers = "Testers:", +} \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Photoshop.app/Resources/Localization/Russian.lang b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Photoshop.app/Resources/Localization/Russian.lang new file mode 100755 index 00000000..a811b943 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Photoshop.app/Resources/Localization/Russian.lang @@ -0,0 +1,92 @@ +{ + newDocument = "Новый документ", + width = "Ширина", + height = "Высота", + cancel = "Отмена", + w = "Ш", + h = "В", + + view = "Вид", + transparencyPad = "Подложка прозрачности", + transparencyColor = "Цвет прозрачности", + transparencyGrid = "Сетка прозрачности", + + path = "Путь", + string = "Строка", + file = "Файл", + image = "Изображение", + edit = "Редактировать", + hotkeys = "Горячие клавиши", + about = "О программе", + brushSize = "Размер кисти", + transparency = "Прозрачность", + brushParameters = "Параметры кисти", + size = "Размер", + + new = "Новый", + open = "Открыть", + createFromString = "Создать из StringImage", + save = "Сохранить", + saveAs = "Сохранить как", + exit = "Выход", + + crop = "Обрезать", + expand = "Расширить", + rotateBy90 = "Повернуть на 90 градусов", + rotateBy180 = "Повернуть на 180 градусов", + flipVertical = "Отразить по вертикали", + flipHorizontal = "Отразить по горизонтали", + + hueSaturation = "Тон/Насыщенность", + colorBalance = "Цветовой баланс", + photoFilter = "Фотофильтр", + invertColors = "Инвертировать цвета", + blackWhite = "Черно-белый фильтр", + gaussianBlur = "Размытие по Гауссу", + + countOfPixels = "Количество пикселей", + fromBottom = "Снизу", + fromTop = "Сверху", + fromLeft = "Слева", + fromRight = "Справа", + hue = "Тон", + saturation = "Насыщенность", + brightness = "Яркость", + filterColor = "Цвет фильтра", + radius = "Радиус", + force = "Сила", + edges = "Грани", + accept = "Enter - применить", + + line = "Линия", + ellipse = "Эллипс", + rectangle = "Прямоугольник", + polygon = "Многоугольник", + border = "Рамка", + + fill = "Заливка", + clear = "Очистить", + deselect = "Убрать выделение", + + hotkeysLines = { + {"EmptyLine"}, + {"CenterText", 0x880000, "Горячие клавиши"}, + {"EmptyLine"}, + {"CenterText", 0x000000, "B - кисть"}, + {"CenterText", 0x000000, "E - ластик"}, + {"CenterText", 0x000000, "T - текст"}, + {"CenterText", 0x000000, "G - заливка"}, + {"CenterText", 0x000000, "M - выделение"}, + {"CenterText", 0x000000, " "}, + {"CenterText", 0x000000, "Arrows - перемещение"}, + {"CenterText", 0x000000, "X - поменять цвета местами"}, + {"CenterText", 0x000000, "D - сменить цвета на черный и белый"}, + {"CenterText", 0x000000, "Ctrl+D - убрать выделение"}, + {"CenterText", 0x000000, "Alt+Click - выбрать цвет (только для кисти)"}, + {"EmptyLine"}, + {"Button", {0xbbbbbb, 0xffffff, "OK"}}, + }, + + developers = "Разработчики:", + testers = "Тестеры:", +} \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/PrintImage.app/Main.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/PrintImage.app/Main.lua new file mode 100755 index 00000000..e43ac59d --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/PrintImage.app/Main.lua @@ -0,0 +1,330 @@ + +----------------------------------------- Libraries ----------------------------------------- + +local component = require("component") +local computer = require("computer") +local unicode = require("unicode") +local fs = require("filesystem") +local advancedLua = require("advancedLua") +local color = require("color") +local image = require("image") +local buffer = require("doubleBuffering") +local GUI = require("GUI") +local MineOSPaths = require("MineOSPaths") +local MineOSCore = require("MineOSCore") + +----------------------------------------- cyka ----------------------------------------- + +if not component.isAvailable("printer3d") then GUI.error("This program requires at least one 3D-printer"); return end +local args, options = require("shell").parse(...) +local startImagePath = args[1] == "open" and args[2] or "/MineOS/System/Icons/Steve.pic" +local configPath = MineOSPaths.applicationData .. "PrintImage/Config.cfg" +local panelWidth = 34 +local mainContainer +local mainImage +local printers +local currentPrinter = 1 +local shapeResolutionLimit = 4 +local timeDelay = 0.05 + +----------------------------------------- Config ----------------------------------------- + +local function save() + table.toFile(configPath, config) +end + +local function load() + if fs.exists(configPath) then + config = table.fromFile(configPath) + else + config = { + mainMaterial = "quartz_block_side", + printName = "My picture", + showGrid = true, + floorMode = false, + frame = {enabled = true, width = 3, material = "planks_spruce"}, + lightEmission = {enabled = false, level = 8} + } + save() + end +end + +----------------------------------------- Printer-related cyka ----------------------------------------- + +local function getPrinters() + printers = {} + for address in pairs(component.list("printer3d")) do table.insert(printers, component.proxy(address)) end +end + +local function addShapePixel(x, y, color, xPrinterPixel, yPrinterPixel) + local pixelSize = math.floor(16 / shapeResolutionLimit) + local xPrinter = x * pixelSize - pixelSize + local yPrinter = y * pixelSize - pixelSize + + if config.floorMode then + printers[currentPrinter].addShape(xPrinter, 0, yPrinter, xPrinter + pixelSize, 16, yPrinter + pixelSize, config.mainMaterial, false, color) + else + if config.frame.enabled then + local xModifyer1, xModifyer2, yModifyer1, yModifyer2 = 0, 0, 0, 0 + if xPrinterPixel == 1 then xModifyer1 = config.frame.width end + if xPrinterPixel == image.getWidth(mainImage) then xModifyer2 = -config.frame.width end + if yPrinterPixel == 1 then yModifyer2 = -config.frame.width end + if yPrinterPixel == image.getHeight(mainImage) * 2 then yModifyer1 = config.frame.width end + printers[currentPrinter].addShape(xPrinter + xModifyer1, yPrinter + yModifyer1, 15, xPrinter + pixelSize + xModifyer2, yPrinter + pixelSize + yModifyer2, 16, config.mainMaterial, false, color) + else + printers[currentPrinter].addShape(xPrinter, 15, yPrinter, xPrinter + pixelSize, 16, yPrinter + pixelSize, config.mainMaterial, false, color) + end + end +end + +local function beginPrint() + buffer.clear(0x0000000, 50) + + local xShape, yShape = 1, 1 + local xShapeCount, yShapeCount = math.ceil(image.getWidth(mainImage) / shapeResolutionLimit), math.ceil(image.getHeight(mainImage) * 2 / shapeResolutionLimit) + local counter = 0 + while true do + if printers[currentPrinter].status() == "idle" then + printers[currentPrinter].reset() + printers[currentPrinter].setLabel(config.printName) + printers[currentPrinter].setTooltip("Part " .. xShape .. "x" .. yShape .. " of " .. xShapeCount .. "x" .. yShapeCount) + if config.lightEmission.enabled then printers[currentPrinter].setLightLevel(config.lightEmission.level) end + + local jReplacer = shapeResolutionLimit + for j = 1, shapeResolutionLimit / 2 do + for i = 1, shapeResolutionLimit do + local xImage = xShape * shapeResolutionLimit - shapeResolutionLimit + i + local yImage = yShape * (shapeResolutionLimit / 2) - (shapeResolutionLimit / 2) + j + + if xImage <= image.getWidth(mainImage) and yImage <= image.getHeight(mainImage) then + local background, foreground, alpha, symbol = image.get(mainImage, xImage, yImage) + if alpha < 0xFF then + if symbol == " " then foreground = background end + addShapePixel(i, jReplacer, background, xImage, yImage * 2 - 1) + addShapePixel(i, jReplacer - 1, foreground, xImage, yImage * 2) + end + + GUI.progressBar(math.floor(buffer.getWidth() / 2 - 25), math.floor(buffer.getHeight() / 2), 50, 0x3366CC, 0xFFFFFF, 0xFFFFFF, math.ceil(counter * 100 / (xShapeCount * yShapeCount)), true, true, "Progress: ", "%"):draw() + buffer.draw() + -- else + -- error("Printing out of mainImage range") + end + end + + jReplacer = jReplacer - 2 + end + + if config.frame.enabled and not config.floorMode then + local xFrame, yFrame = shapeResolutionLimit * (image.getWidth(mainImage) % shapeResolutionLimit), shapeResolutionLimit * ((image.getHeight(mainImage) * 2) % shapeResolutionLimit) + xFrame = xShape == xShapeCount and (xFrame == 0 and 16 or xFrame) or 16 + yFrame = yShape == yShapeCount and (yFrame == 0 and 0 or yFrame) or 0 + + if xShape == 1 then printers[currentPrinter].addShape(0, yFrame, 14, config.frame.width, 16, 16, config.frame.material) end + if xShape == xShapeCount then printers[currentPrinter].addShape(xFrame - config.frame.width, yFrame, 14, xFrame, 16, 16, config.frame.material) end + + if yShape == 1 then printers[currentPrinter].addShape(0, 16 - config.frame.width, 14, xFrame, 16, 16, config.frame.material) end + if yShape == yShapeCount then printers[currentPrinter].addShape(0, yFrame, 14, xFrame, yFrame + config.frame.width, 16, config.frame.material) end + end + + printers[currentPrinter].commit() + + counter = counter + 1 + xShape = xShape + 1 + if xShape > xShapeCount then xShape = 1; yShape = yShape + 1 end + if yShape > yShapeCount then break end + end + + currentPrinter = currentPrinter + 1 + if currentPrinter > #printers then currentPrinter = 1 end + os.sleep(timeDelay) + end + + buffer.clear() + mainContainer:draw() + buffer.draw(true) +end + +----------------------------------------- Window-zaluped parasha ----------------------------------------- + +local function getStatus() + local xBlocks, yBlocks = math.ceil(image.getWidth(mainImage) / shapeResolutionLimit), math.ceil(image.getHeight(mainImage) * 2 / shapeResolutionLimit) + mainContainer.shadeContainer.statusTextBox.lines = { + "Image size: " .. image.getWidth(mainImage) .. "x" .. image.getHeight(mainImage) .. " px", + "Count of printers: " .. #printers, + "Print result: " .. xBlocks .. "x" .. yBlocks .. " blocks", + "Total count: " .. xBlocks * yBlocks .. " blocks" + } +end + +local function verticalLine(x, y, height, transparency) + for i = y, y + height - 1 do + local background = buffer.get(x, i) + buffer.set(x, i, background, color.blend(background, 0xFFFFFF, transparency), "│") + end +end + +local function horizontalLine(x, y, width, transparency) + for i = x, x + width - 1 do + local background, foreground, symbol = buffer.get(i, y) + buffer.set(i, y, background, color.blend(background, 0xFFFFFF, transparency), symbol == "│" and "┼" or "─") + end +end + +local function drawMainImageObject(object) + if mainImage then + local xImage = image.getWidth(mainImage) < buffer.getWidth() and math.floor(buffer.getWidth() / 2 - image.getWidth(mainImage) / 2) or 1 + local yImage = image.getHeight(mainImage) < buffer.getHeight() and math.floor(buffer.getHeight() / 2 - image.getHeight(mainImage) / 2) or 1 + buffer.image(xImage, yImage, mainImage) + GUI.windowShadow(xImage, yImage, image.getWidth(mainImage), image.getHeight(mainImage), 50, true) + if config.showGrid then + for x = xImage, xImage + image.getWidth(mainImage) - 1, shapeResolutionLimit do verticalLine(x, yImage, image.getHeight(mainImage), 0.627) end + for y = yImage, yImage + image.getHeight(mainImage) - 1, shapeResolutionLimit / 2 do horizontalLine(xImage, y, image.getWidth(mainImage), 0.627) end + buffer.text(1, 1, 0xBBBBBB, "хуй") + end + end +end + +local function createWindow() + mainContainer = GUI.fullScreenContainer() + mainContainer:addChild(GUI.panel(1, 1, mainContainer.width, mainContainer.height, 0xEEEEEE)) + mainContainer:addChild(GUI.object(1, 1, mainContainer.width, mainContainer.height)).draw = drawMainImageObject + local textBoxesWidth = math.floor(panelWidth * 0.55) + + mainContainer.shadeContainer = mainContainer:addChild(GUI.container(mainContainer.width - panelWidth + 1, 1, panelWidth, mainContainer.height)) + mainContainer.shadeContainer:addChild(GUI.panel(1, 1, mainContainer.shadeContainer.width, mainContainer.shadeContainer.height, 0x0000000, 0.4)) + + local y = 2 + mainContainer.shadeContainer:addChild(GUI.label(1, y, mainContainer.shadeContainer.width, 1, 0xFFFFFF, "Main properties")):setAlignment(GUI.alignment.horizontal.center, GUI.alignment.vertical.top) + + y = y + 2 + mainContainer.shadeContainer:addChild(GUI.label(3, y, mainContainer.shadeContainer.width, 1, 0xCCCCCC, "Image path:")) + local filesystemChooser = mainContainer.shadeContainer:addChild(GUI.filesystemChooser(mainContainer.shadeContainer.width - textBoxesWidth - 1, y, textBoxesWidth, 1, 0xEEEEEE, 0x262626, 0x444444, 0x999999, startImagePath, MineOSCore.localization.open, MineOSCore.localization.cancel, "Image path", "/")) + filesystemChooser:addExtensionFilter(".pic") + filesystemChooser.onSubmit = function(path) + mainImage = image.load(path) + getStatus() + mainContainer:draw() + buffer.draw() + end + + y = y + 2 + mainContainer.shadeContainer:addChild(GUI.label(3, y, mainContainer.shadeContainer.width, 1, 0xCCCCCC, "Material:")) + local mainMaterialTextBox = mainContainer.shadeContainer:addChild(GUI.input(mainContainer.shadeContainer.width - textBoxesWidth - 1, y, textBoxesWidth, 1, 0xEEEEEE, 0x555555, 0x555555, 0xEEEEEE, 0x262626, config.mainMaterial, nil, false)) + mainMaterialTextBox.onInputFinished = function() + config.mainMaterial = mainMaterialTextBox.text + save() + end + + y = y + 2 + mainContainer.shadeContainer:addChild(GUI.label(3, y, mainContainer.shadeContainer.width, 1, 0xCCCCCC, "Print name:")) + local printNameTextBox = mainContainer.shadeContainer:addChild(GUI.input(mainContainer.shadeContainer.width - textBoxesWidth - 1, y, textBoxesWidth, 1, 0xEEEEEE, 0x555555, 0x555555, 0xEEEEEE, 0x262626, config.printName, nil, false)) + printNameTextBox.onInputFinished = function() + config.printName = printNameTextBox.text + save() + end + + y = y + 2 + mainContainer.shadeContainer:addChild(GUI.label(3, y, mainContainer.shadeContainer.width, 1, 0xCCCCCC, "Floor mode:")) + local floorSwitch = mainContainer.shadeContainer:addChild(GUI.switch(mainContainer.shadeContainer.width - 9, y, 8, 0xFFDB40, 0xAAAAAA, 0xFFFFFF, config.floorMode)) + floorSwitch.onStateChanged = function() + config.floorMode = floorSwitch.state + save() + end + + y = y + 2 + mainContainer.shadeContainer:addChild(GUI.label(3, y, mainContainer.shadeContainer.width, 1, 0xCCCCCC, "Show grid:")) + local gridSwitch = mainContainer.shadeContainer:addChild(GUI.switch(mainContainer.shadeContainer.width - 9, y, 8, 0xFFDB40, 0xAAAAAA, 0xFFFFFF, config.showGrid)) + gridSwitch.onStateChanged = function() + config.showGrid = gridSwitch.state + save() + mainContainer:draw() + end + + y = y + 4 + mainContainer.shadeContainer:addChild(GUI.label(1, y, mainContainer.shadeContainer.width, 1, 0xFFFFFF, "Frame properties")):setAlignment(GUI.alignment.horizontal.center, GUI.alignment.vertical.top) + y = y + 2 + mainContainer.shadeContainer:addChild(GUI.label(3, y, mainContainer.shadeContainer.width, 1, 0xCCCCCC, "Enabled:")) + local frameSwitch = mainContainer.shadeContainer:addChild(GUI.switch(mainContainer.shadeContainer.width - 9, y, 8, 0xFFDB40, 0xAAAAAA, 0xFFFFFF, config.frame.enabled)) + frameSwitch.onStateChanged = function() + config.frame.enabled = frameSwitch.state + save() + end + y = y + 2 + mainContainer.shadeContainer:addChild(GUI.label(3, y, mainContainer.shadeContainer.width, 1, 0xCCCCCC, "Material:")) + local frameMaterialTextBox = mainContainer.shadeContainer:addChild(GUI.input(mainContainer.shadeContainer.width - textBoxesWidth - 1, y, textBoxesWidth, 1, 0xEEEEEE, 0x555555, 0x555555, 0xEEEEEE, 0x262626, config.frame.material, nil, false)) + frameMaterialTextBox.onInputFinished = function() + config.frame.material = frameMaterialTextBox.text + save() + end + + y = y + 2 + local frameWidthSlider = mainContainer.shadeContainer:addChild(GUI.slider(3, y, mainContainer.shadeContainer.width - 4, 0xFFDB80, 0x000000, 0xFFDB40, 0xCCCCCC, 1, shapeResolutionLimit - 1, config.frame.width, false, "Width: " , " voxel(s)")) + frameWidthSlider.onValueChanged = function() + config.frame.width = frameWidthSlider.value + save() + end + frameWidthSlider.roundValues = true + + y = y + 5 + mainContainer.shadeContainer:addChild(GUI.label(1, y, mainContainer.shadeContainer.width, 1, 0xFFFFFF, "Light emission")):setAlignment(GUI.alignment.horizontal.center, GUI.alignment.vertical.top) + y = y + 2 + mainContainer.shadeContainer:addChild(GUI.label(3, y, mainContainer.shadeContainer.width, 1, 0xCCCCCC, "Enabled:")) + local lightSwitch = mainContainer.shadeContainer:addChild(GUI.switch(mainContainer.shadeContainer.width - 9, y, 8, 0xFFDB40, 0xAAAAAA, 0xFFFFFF, config.lightEmission.enabled)) + lightSwitch.onStateChanged = function() + config.lightEmission.enabled = true + save() + end + + y = y + 2 + local lightSlider = mainContainer.shadeContainer:addChild(GUI.slider(3, y, mainContainer.shadeContainer.width - 4, 0xFFDB80, 0x000000, 0xFFDB40, 0xCCCCCC, 1, 8, 8, false, "Radius: " , " block(s)")) + lightSlider.roundValues = true + lightSlider.onValueChanged = function() + config.lightEmission.value = lightSlider.value + save() + end + + y = y + 5 + mainContainer.shadeContainer:addChild(GUI.label(1, y, mainContainer.shadeContainer.width, 1, 0xFFFFFF, "Summary information:")):setAlignment(GUI.alignment.horizontal.center, GUI.alignment.vertical.top) + y = y + 2 + mainContainer.shadeContainer.statusTextBox = mainContainer.shadeContainer:addChild(GUI.textBox(3, y, mainContainer.shadeContainer.width - 4, 5, nil, 0xCCCCCC, {}, 1)):setAlignment(GUI.alignment.horizontal.left, GUI.alignment.vertical.top) + + mainContainer.shadeContainer:addChild(GUI.button(1, mainContainer.shadeContainer.height - 5, mainContainer.shadeContainer.width, 3, 0x363636, 0xFFFFFF, 0xFFFFFF, 0x262626, "Exit")).onTouch = function() + mainContainer:stopEventHandling() + end + + mainContainer.shadeContainer:addChild(GUI.button(1, mainContainer.shadeContainer.height - 2, mainContainer.shadeContainer.width, 3, 0x262626, 0xFFFFFF, 0xFFFFFF, 0x262626, "Start print")).onTouch = function() + beginPrint() + end + + mainContainer.eventHandler = function(mainContainer, object, eventData) + if (eventData[1] == "component_added" or eventData[1] == "component_removed") and eventData[3] == "printer3d" then + getPrinters() + getStatus() + mainContainer:draw() + buffer.draw() + end + end +end + +----------------------------------------- Shitty meatball rolls ----------------------------------------- + +buffer.flush() +load() +getPrinters() +createWindow() +mainImage = image.load(startImagePath) +getStatus() +mainContainer:draw() +buffer.draw() + +mainContainer:startEventHandling() + + + + + + + + + + diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/PrintImage.app/Resources/Icon.pic b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/PrintImage.app/Resources/Icon.pic new file mode 100755 index 0000000000000000000000000000000000000000..f28c8154b8dac54bbbbb1303b6e40b7be9695875 GIT binary patch literal 104 zcmXZTu?>JQ3`9|%Z9>Li7bGMqMoN&FfFesUPZsbZ;fhpGzxdaF9x1zuShR|P4kR^S g(i-hy7x?VWZwO403w8QYrJaHfL9~}KROe*VAI}L80ssI2 literal 0 HcmV?d00001 diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/QuantumCube.app/Main.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/QuantumCube.app/Main.lua new file mode 100755 index 00000000..726b160e --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/QuantumCube.app/Main.lua @@ -0,0 +1,442 @@ +local os = require("os") +local term = require("term") +local event = require("event") +local component = require("component") +local gpu = component.gpu +math.randomseed(os.time()) +local version = "Cube v1.0" +local level = {} +local doors = {} +local indicator={} +local nick_player +local markColor = {0xff0000, 0xffff00, 0x00ff00, 0x00ffff, 0x0000ff, 0xff00ff, 0xffffff} +local playerColor = 3 +local sx,sy = gpu.getResolution() +sx, sy = math.modf(sx/4), math.modf(sy/2) -- точка отступа/координата игрока на экране (статичные)/центр событий +local px,py = 3,3 -- относительные координаты игрока в комнате +local min_map, max_map = 1, 1000 -- ограничители номеров комнат, чтобы сильно не запутаться при прохождении +level[1] = {number=1, color = 0xffffff, mark=false} -- цель игры - попасть в эту комнату +------------------------------------------------------------------------------------- +----------------------- Генерация случайных формул для дверей ----------------------- +local rand_1, rand_2 ,rand_3, rand_4 +while true do + rand_1, rand_2 ,rand_3, rand_4 = math.random(1,2), math.random(10,30), math.random(1,2), math.random(10,30) + if rand_1~=rand_3 and rand_2~=rand_4 then break end +end +formula={} +formula[1]=function(n) return n*rand_1 + rand_2 end +formula[2]=function(n) return n*rand_3 + rand_4 end +formula[-1]=function(n) return (n - rand_2)/rand_1 end +formula[-2]=function(n) return (n - rand_4)/rand_3 end +------------------------------------------------------------------------------------- +---------------------- Возвращает или генерирует новую комнату ---------------------- +function gen_level(i) + i = tonumber(i) + if not level[i] then + level[i]={number=i, color = math.random(0x000000, 0xffffff), mark=false} + end + return level[i] +end +------------------------------------------------------------------------------------- +-------------------------- Проверка, существует ли комната -------------------------- +function proverka(x,y) -- где x номер формулы, y номер текущей комнаты + local number = formula[x](y) + return number >= min_map and number <= max_map and number == math.modf(number) +end +------------------------------------------------------------------------------------- +---------------------------- Генерация статистики комнат ---------------------------- +function sorting(sorting_table, not_sorting_table) + local trash_table={} + while true do + if #not_sorting_table==0 then + break + else + local new_level = not_sorting_table[1] + local power = true + for i=1, #sorting_table do + if sorting_table[i][1]== new_level[1] then + power = false + if new_level[2] < sorting_table[i][2] then + sorting_table[i][2]=new_level[2] + if proverka(1,new_level[1]) then + trash_table[#trash_table+1] = {formula[1](new_level[1]), new_level[2]+1} + end + if proverka(2,new_level[1]) then + trash_table[#trash_table+1] = {formula[2](new_level[1]), new_level[2]+1} + end + if proverka(-1,new_level[1]) then + trash_table[#trash_table+1] = {formula[-1](new_level[1]), new_level[2]+1} + end + if proverka(-2,new_level[1]) then + trash_table[#trash_table+1] = {formula[-2](new_level[1]), new_level[2]+1} + end + end + table.remove(not_sorting_table,1) + end + end + if power then + sorting_table[#sorting_table+1] = new_level + table.remove(not_sorting_table,1) + if proverka(1,new_level[1]) then + not_sorting_table[#not_sorting_table+1] = {formula[1](new_level[1]), new_level[2]+1} + end + if proverka(2,new_level[1]) then + not_sorting_table[#not_sorting_table+1] = {formula[2](new_level[1]), new_level[2]+1} + end + if proverka(-1,new_level[1]) then + not_sorting_table[#not_sorting_table+1] = {formula[-1](new_level[1]), new_level[2]+1} + end + if proverka(-2,new_level[1]) then + not_sorting_table[#not_sorting_table+1] = {formula[-2](new_level[1]), new_level[2]+1} + end + end + end + end + return sorting_table, trash_table +end + +-- первая сортировка +local not_sorting_tb, trash_tb={} +not_sorting_tb[1]={1,0} +local sorting_tb, trash_tb = sorting({}, not_sorting_tb) + +-- последующие сортировки +while true do + not_sorting_tb = trash_tb + sorting_tb, trash_tb = sorting(sorting_tb, not_sorting_tb) + if #trash_tb == 0 then break end +end + +-- очищаем память +not_sorting_tb, trash_tb = nil, nil + +-- перестраиваем таблицу +local stat_table={} +for i=1, #sorting_tb do + stat_table[sorting_tb[i][1]]=sorting_tb[i][2] +end +------------------------------------------------------------------------------------- +------------------ Находим номер самой удалённой комнаты от выхода ------------------ +local j=1 +for i=1, #sorting_tb do + if sorting_tb[i][2]>sorting_tb[j][2] then + j=i + end +end +------------------------------------------------------------------------------------- +----------------------- Устанавливаем номер стартовой комнаты ----------------------- +local chamber = gen_level(sorting_tb[j][1]) + +-- запишем количество комнат в игре +local max_table, max_level = #sorting_tb, sorting_tb[j][2] + +-- удалим из памяти ненужную таблицу +sorting_tb = nil +------------------------------------------------------------------------------------- +--------------------------- Переставляет двери в комнате ---------------------------- +function reload_doors() + local rezerv={} + for i=1,4 do -- занесём двери в базу данных, чтобы знать использованные формулы + if doors[i] then + rezerv[#rezerv+1]=doors[i] + end + end + for i=1,4 do -- перебираем все 4 двери + if not doors[i] then + while true do + local rand = math.random(-2,2) + local rezerv_2 = 0 + if rand ~= 0 then + if #rezerv > 0 then -- проверка, есть ли комната с такой же формулой + for j=1, #rezerv do + if rezerv[j] == rand then break else rezerv_2 = rezerv_2 + 1 end + end + end + if rezerv_2 == #rezerv then -- если нет повторяющихся формул, то присваивается данная формула + doors[i] = rand + rezerv[#rezerv+1]=rand + break + end + end + end + end + end +end +-- //данная функция достаточно сложна чтобы запутаться +------------------------------------------------------------------------------------- +---------------------------------- Рисования меток ---------------------------------- +function mark_print(nx, ny, number) + if level[number].mark then + for i=1, #level[number].mark do + gpu.setBackground(level[number].mark[i][3]) + gpu.set((level[number].mark[i][1]+nx)*2, level[number].mark[i][2]+ny, " ") + end + end +end +------------------------------------------------------------------------------------- +------------------------- Рисования комнаты по координатам -------------------------- +function level_print(nx, ny, color, number) + number = tostring(number) + gpu.setBackground(color) + gpu.set(nx*2, ny, " ") + gpu.set((nx+4)*2, ny, " ") + gpu.set(nx*2, ny+6, " ") + gpu.set((nx+4)*2, ny+6, " ") + + gpu.set(nx*2, ny+1, " ") + gpu.set(nx*2, ny+2, " ") + gpu.set(nx*2, ny+4, " ") + gpu.set(nx*2, ny+5, " ") + gpu.set((nx+6)*2, ny+1, " ") + gpu.set((nx+6)*2, ny+2, " ") + gpu.set((nx+6)*2, ny+4, " ") + gpu.set((nx+6)*2, ny+5, " ") + + gpu.setBackground(0x000000) + gpu.set(nx*2+6-math.modf((string.len(number)-1)/2), ny+3, number) +end +------------------------------------------------------------------------------------- +----------------------------- Переходы между комнатами ------------------------------ +pxx, pyy = {}, {} +pxx[-1]=function(nx) + local rezerv_3 = doors[1] + if proverka(rezerv_3, chamber.number) then + doors={} + doors[2] = -rezerv_3 + reload_doors() + chamber = gen_level(formula[rezerv_3](chamber.number)) + ppx = 6 + else + ppx = px + end +end +pxx[7]=function(nx) + local rezerv_3 = doors[2] + if proverka(rezerv_3, chamber.number) then + doors={} + doors[1] = -rezerv_3 + reload_doors() + chamber = gen_level(formula[rezerv_3](chamber.number)) + ppx = 0 + else + ppx = px + end +end +pyy[-1]=function(ny) + local rezerv_3 = doors[3] + if proverka(rezerv_3, chamber.number) then + doors={} + doors[4] = -rezerv_3 + reload_doors() + chamber = gen_level(formula[rezerv_3](chamber.number)) + ppy = 6 + else + ppy = py + end +end +pyy[7]=function(ny) + local rezerv_3 = doors[4] + if proverka(rezerv_3, chamber.number) then + doors={} + doors[3] = -rezerv_3 + reload_doors() + chamber = gen_level(formula[rezerv_3](chamber.number)) + ppy = 0 + else + ppy = py + end +end +-- //работает как надо, но лучше подредактировать +------------------------------------------------------------------------------------- +-------------------------------- Передвижение игрока -------------------------------- +function player_update(nx,ny) + ppx, ppy = px+nx, py+ny + if not ((ppx==0 or ppy==0 or ppx==6 or ppy==6) and ppx~=3 and ppy~=3) then + if pxx[ppx] then pxx[ppx](ppx) + elseif pyy[ppy] then pyy[ppy](ppy) + end + px,py = ppx,ppy + end +end +-- //работает как надо, но лучше подредактировать +------------------------------------------------------------------------------------- +--------------------------------- Блок отображения ---------------------------------- +function update(nick) + nick_player = nick + term.clear() + + -- текущая комната + gen_level(chamber.number) + mark_print(sx-px,sy-py, chamber.number) + level_print(sx-px,sy-py,chamber.color, chamber.number) + + -- комната слева + if proverka(doors[1], chamber.number) and px==0 then + local number = formula[doors[1]](chamber.number) + gen_level(number) + mark_print(sx-7-px,sy-py, number) + level_print(sx-7-px,sy-py,gen_level(formula[doors[1]](chamber.number)).color, gen_level(formula[doors[1]](chamber.number)).number) + end + + -- комната справа + if proverka(doors[2], chamber.number) and px==6 then + local number = formula[doors[2]](chamber.number) + gen_level(number) + mark_print(sx+7-px,sy-py, number) + level_print(sx+7-px,sy-py,gen_level(formula[doors[2]](chamber.number)).color, gen_level(formula[doors[2]](chamber.number)).number) + end + + -- комната сверху + if proverka(doors[3], chamber.number) and py==0 then + local number = formula[doors[3]](chamber.number) + gen_level(number) + mark_print(sx-px,sy-7-py, number) + level_print(sx-px,sy-7-py,gen_level(formula[doors[3]](chamber.number)).color, gen_level(formula[doors[3]](chamber.number)).number) + end + + -- комната снизу + if proverka(doors[4], chamber.number) and py==6 then + local number = formula[doors[4]](chamber.number) + gen_level(number) + mark_print(sx-px,sy+7-py, number) + level_print(sx-px,sy+7-py,gen_level(formula[doors[4]](chamber.number)).color, number) + end + + -- отображение игрока + gpu.setBackground(0xff0000) + gpu.set(sx*2, sy, " ") + + -- текстовые индикаторы + for i=1, #indicator do + indicator[i]() + end + + -- индикатор выбранного цвета + gpu.setBackground(markColor[playerColor]) + gpu.set(2, sy*2, " ") +end +------------------------------------------------------------------------------------- +---------------------------------- Блок управления ---------------------------------- +local command={} +command[200]=function() player_update(0,-1) end -- вверх +command[208]=function() player_update(0,1) end -- вниз +command[203]=function() player_update(-1,0) end -- влево +command[205]=function() player_update(1,0) end -- вправо +command[17]=function() -- ставить метку + if not level[chamber.number].mark then level[chamber.number].mark={} end + level[chamber.number].mark[#level[chamber.number].mark+1]={px,py,markColor[playerColor]} + end +command[30]=function() if playerColor-1<1 then playerColor=#markColor else playerColor=playerColor-1 end end -- цвет слева +command[32]=function() if playerColor+1>#markColor then playerColor=1 else playerColor=playerColor+1 end end -- цвет справа +command[31]=function() -- удалить метку + if level[chamber.number].mark then + for i=#level[chamber.number].mark, 1, -1 do + if px==level[chamber.number].mark[i][1] and py==level[chamber.number].mark[i][2] then + table.remove(level[chamber.number].mark,i) + end + end + end + end +command[23]=function() -- включает режим разработчика + if #indicator==0 then + indicator[1]=function() + gpu.setBackground(0x000000) + gpu.setForeground(0xffff00) + gpu.set(2, 2, "max level: "..max_level) + gpu.set(2, 3, "all levels: "..max_table) + gpu.set(2, 4, "this level: "..stat_table[chamber.number]) + gpu.setForeground(0xff0000) + gpu.set(2, 5, "formula 1: ".."n".."*"..rand_1.." + "..rand_2) + gpu.set(2, 6, "formula 2: ".."n".."*"..rand_3.." + "..rand_4) + gpu.set(2, 7, "formula -1: ".."(n - "..rand_2..")/"..rand_1) + gpu.set(2, 8, "formula -2: ".."(n - "..rand_4..")/"..rand_3) + gpu.setForeground(0xff00ff) + gpu.set(2, 9, "progress: " .. math.modf(100-stat_table[chamber.number]*100/max_level).."%") + gpu.setForeground(0xff00ff) + gpu.set(2, 10, "color: "..playerColor) + gpu.setForeground(0xffffff) + end + else + table.remove(indicator,1) + end +end +command[35]=function() -- отобразить управление + term.clear() + gpu.setForeground(0xff0000) + print(version) + print("") + gpu.setForeground(0x00ff00) + print("Target: searching chamber 1") + print("Game 100% passable") + gpu.setForeground(0xffff00) + print("Control:") + print("Q - exit game") + print("H - help") + print("I - info") + print("W - set mark") + print("S - remove mark") + print("A - back color mark") + print("D - next color mark") + print("") + gpu.setForeground(0xffffff) + print("press enter to continue...") + while true do + _,_,_, key = event.pull("key_down") + if key == 28 then + break + end + end +end +------------------------------------------------------------------------------------- +---------------------- Показываем управление до начала игры ------------------------- +command[35]() +------------------------------------------------------------------------------------- +------------------------- Отображение комнат до начала игры ------------------------- +reload_doors() +update(nick) +gpu.setBackground(0x000000) +------------------------------------------------------------------------------------- +------------------------------------- Тело игры ------------------------------------- +while true do + _,_,_, key, nick = event.pull("key_down") + if key==16 then + term.clear() + gpu.setForeground(0xff0000) + print("Exit to game?") + gpu.setForeground(0xffffff) + print("") + print("y/n") + while true do + _,_,_, key = event.pull("key_down") + if key == 21 then + term.clear() + return + elseif key == 49 then + break + end + end + elseif command[key] then + command[key]() + end + update(nick) + gpu.setBackground(0x000000) + if chamber.number == 1 then break end -- цель игры, комната с этим номером + os.sleep(1/15) -- задержка, для более удобного управления +end +------------------------------------------------------------------------------------- +------------------------------------- Прощание ------------------------------------- +term.clear() +gpu.setForeground(0x00ff00) +print("Congratulations "..nick_player.."!") +print("You win!") +print("") +gpu.setForeground(0xffffff) +print("press enter to exit...") +while true do + _,_,_, key = event.pull("key_down") + if key == 28 then + break + end +end +term.clear() +------------------------------------------------------------------------------------- \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/QuantumCube.app/Resources/About/Russian.txt b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/QuantumCube.app/Resources/About/Russian.txt new file mode 100755 index 00000000..5afb8118 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/QuantumCube.app/Resources/About/Russian.txt @@ -0,0 +1 @@ +Это мини-игра, разработанная товарищем qwertyMan с форума ComputerCraft.ru. А я ее нагло спиздил. Цель игры: вы должны понять, как устроен "квантовый куб", решить задачу (найти цепочку выходов) к комнате номер 1 и выбраться из квантового лабиринта. А на деле рандомно бегать в поисках комнаты номер 1, не понимать как устроена система нумераций, ловить баттхёрты и проклинать всех, кого только можно. Потому что если даже соседняя комната и окажется под номером 1, то вы можете запросто пробежать и даже не заглянуть в неё. Так как мы видим лишь те комнаты, на границе с которыми стоим. \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/QuantumCube.app/Resources/Icon.pic b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/QuantumCube.app/Resources/Icon.pic new file mode 100755 index 0000000000000000000000000000000000000000..a6643e546702915c6dac42b5117a67e7907d3e9d GIT binary patch literal 97 zcmW-X(G7qw5JC^`tR2JLDkdg=xl%uzp#!#yi2Z=TgXg@jQUhi_dV?{EpqHIQop8oB b`YS#`<)n+pdK^q~{9Q6wKo73XmxuZXKQ0Jd literal 0 HcmV?d00001 diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Radio.app/Main.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Radio.app/Main.lua new file mode 100755 index 00000000..bae826e0 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Radio.app/Main.lua @@ -0,0 +1,314 @@ + +-- package.loaded.bigLetters = nil +local event = require("event") +local buffer = require("doubleBuffering") +local bigLetters = require("bigLetters") +local unicode = require("unicode") +local component = require("component") +local fs = require("filesystem") +local context = require("context") +local serialization = require("serialization") +local ecs = require("ECSAPI") +local radio + +if not component.isAvailable("openfm_radio") then + ecs.error("Этой программе требуется радио из мода OpenFM. Причем не на всех версиях еще работает, автор мода - пидор! Проверял на ноябрьской, полет нормальный.") + return +else + radio = component.openfm_radio +end + +local pathToSaveStations = "MineOS/System/Radio/Stations.cfg" +local stationNameLimit = 8 +local spaceBetweenStations = 8 +local countOfStationsLimit = 9 +local lineHeight + +local config = { + colors = { + background = 0x1b1b1b, + line = 0xFFFFFF, + lineShadow = 0x000000, + activeStation = 0xFFA800, + otherStation = 0xBBBBBB, + bottomToolBarDefaultColor = 0xaaaaaa, + bottomToolBarCurrentColor = 0xFFA800, + }, +} + +local radioStations = { + currentStation = 3, + { + name = "Galnet Soft", + url = "http://galnet.ru:8000/soft" + }, + { + name = "Европа Плюс", + url = "http://ep256.streamr.ru" + }, + { + name = "L-Radio", + url = "http://server2.lradio.ru:8000/lradio64.aac.m3u" + }, + { + name = "Radio Record", + url = "http://online.radiorecord.ru:8101/rr_128.m3u" + }, + { + name = "Moscow FM", + url = "http://livestream.rfn.ru:8080/moscowfmen128.m3u" + }, +} + +--Объекты для тача +local obj = {} + +local function drawStation(x, y, name, color) + bigLetters.drawText(x, y, color, name) +end + +local function drawLine() + local x = math.floor(buffer.getWidth() / 2) + for i = 1, lineHeight do + buffer.text(x + 1, i, config.colors.lineShadow, "▎") + buffer.text(x, i, config.colors.line, "▍") + end +end + +local function drawLeftArrow(x, y, color) + local bg, fg = config.colors.background, color + local arrow = { + { {bg, fg, " "}, {bg, fg, " "}, {bg, fg, "*"} }, + { {bg, fg, " "}, {bg, fg, "*"}, {bg, fg, " "} }, + { {bg, fg, "*"}, {bg, fg, " "}, {bg, fg, " "} }, + { {bg, fg, " "}, {bg, fg, "*"}, {bg, fg, " "} }, + { {bg, fg, " "}, {bg, fg, " "}, {bg, fg, "*"} }, + } + buffer.customImage(x, y, arrow) +end + +local function drawRightArrow(x, y, color) + local bg, fg = config.colors.background, color + local arrow = { + { {bg, fg, "*"}, {bg, fg, " "}, {bg, fg, " "} }, + { {bg, fg, " "}, {bg, fg, "*"}, {bg, fg, " "} }, + { {bg, fg, " "}, {bg, fg, " "}, {bg, fg, "*"} }, + { {bg, fg, " "}, {bg, fg, "*"}, {bg, fg, " "} }, + { {bg, fg, "*"}, {bg, fg, " "}, {bg, fg, " "} }, + } + buffer.customImage(x, y, arrow) +end + +local function drawMenu() + local width = 36 + (3 * 2 + 2) * #radioStations + local x, y = math.floor(buffer.getWidth() / 2 - width / 2), lineHeight + math.floor((buffer.getHeight() - lineHeight) / 2 - 1) + + obj.gromkostPlus = {x, y, x + 4, y + 3} + x = bigLetters.drawText(x, y, config.colors.bottomToolBarDefaultColor, "+", "*") + 1 + x = x + 1 + + obj.strelkaVlevo = {x, y, x + 4, y + 3} + drawLeftArrow(x, y, config.colors.bottomToolBarDefaultColor); x = x + 5 + x = x + 3 + + local color + for i = 1, #radioStations do + if i == radioStations.currentStation then color = config.colors.bottomToolBarCurrentColor else color = config.colors.bottomToolBarDefaultColor end + x = bigLetters.drawText(x, y, color, tostring(i), "*") + 1 + end + + x = x + 2 + obj.strelkaVpravo = {x, y, x + 4, y + 3} + drawRightArrow(x, y, config.colors.bottomToolBarDefaultColor) + + x = x + 8 + obj.gromkostMinus = {x, y, x + 4, y + 3} + x = bigLetters.drawText(x, y, config.colors.bottomToolBarDefaultColor, "-", "*") + 1 +end + +local function drawStations() + local prevWidth, currentWidth, nextWidth, name + + -- Текущая станция + name = ecs.stringLimit("end", unicode.lower(radioStations[radioStations.currentStation].name), stationNameLimit, true) + currentWidth = bigLetters.getTextSize(name) + local x, y = math.floor(buffer.getWidth() / 2 - currentWidth / 2), math.floor(buffer.getHeight() / 2 - 3) + drawStation(x, y, name, config.colors.activeStation) + + -- Предедущая + if radioStations[radioStations.currentStation - 1] then + name = ecs.stringLimit("start", unicode.lower(radioStations[radioStations.currentStation - 1].name), stationNameLimit) + prevWidth = bigLetters.getTextSize(name) + drawStation(x - prevWidth - spaceBetweenStations, y, name, config.colors.otherStation) + end + + -- Следующая + if radioStations[radioStations.currentStation + 1] then + name = ecs.stringLimit("end", unicode.lower(radioStations[radioStations.currentStation + 1].name), stationNameLimit) + nextWidth = bigLetters.getTextSize(name) + drawStation(x + currentWidth + spaceBetweenStations + 1, y, name, config.colors.otherStation) + end + -- ecs.error(x, x - prevWidth - spaceBetweenStations, prevWidth, currentWidth, nextWidth) +end + +local function drawAll() + -- Коррекция от кривых ручонок юзверей + if radioStations.currentStation < 1 then + radioStations.currentStation = 1 + elseif radioStations.currentStation > #radioStations then + radioStations.currentStation = #radioStations + end + + buffer.square(1, 1, buffer.getWidth(), buffer.getHeight(), config.colors.background, 0xFFFFFF, " ") + + drawStations() + drawLine() + drawMenu() + + buffer.draw() +end + +local function saveStations() + fs.makeDirectory(fs.path(pathToSaveStations)) + local file = io.open(pathToSaveStations, "w") + file:write(serialization.serialize(radioStations)) + file:close() +end + +local function loadStations() + if fs.exists(pathToSaveStations) then + local file = io.open(pathToSaveStations, "r") + radioStations = serialization.unserialize(file:read("*a")) + file:close() + else + saveStations() + end +end + +local function switchStation(i) + if i == 1 then + if radioStations.currentStation < #radioStations then + radioStations.currentStation = radioStations.currentStation + 1 + saveStations() + radio.stop() + radio.setURL(radioStations[radioStations.currentStation].url) + radio.start() + end + else + if radioStations.currentStation > 1 then + radioStations.currentStation = radioStations.currentStation - 1 + saveStations() + radio.stop() + radio.setURL(radioStations[radioStations.currentStation].url) + radio.start() + end + end +end + +local function volume(i) + if i == 1 then + radio.volUp() + else + radio.volDown() + end +end + + +buffer.flush() +lineHeight = math.floor(buffer.getHeight() * 0.7) +loadStations() +radio.stop() +radio.setURL(radioStations[radioStations.currentStation].url) +radio.start() +drawAll() + +while true do + local e = {event.pull()} + if e[1] == "touch" then + if e[5] == 0 then + if ecs.clickedAtArea(e[3], e[4], obj.strelkaVlevo[1], obj.strelkaVlevo[2], obj.strelkaVlevo[3], obj.strelkaVlevo[4]) then + drawLeftArrow(obj.strelkaVlevo[1], obj.strelkaVlevo[2], config.colors.bottomToolBarCurrentColor) + buffer.draw() + os.sleep(0.2) + switchStation(-1) + drawAll() + elseif ecs.clickedAtArea(e[3], e[4], obj.strelkaVpravo[1], obj.strelkaVpravo[2], obj.strelkaVpravo[3], obj.strelkaVpravo[4]) then + drawRightArrow(obj.strelkaVpravo[1], obj.strelkaVpravo[2], config.colors.bottomToolBarCurrentColor) + buffer.draw() + os.sleep(0.2) + switchStation(1) + drawAll() + elseif ecs.clickedAtArea(e[3], e[4], obj.gromkostPlus[1], obj.gromkostPlus[2], obj.gromkostPlus[3], obj.gromkostPlus[4]) then + bigLetters.drawText(obj.gromkostPlus[1], obj.gromkostPlus[2], config.colors.bottomToolBarCurrentColor, "+", "*" ) + buffer.draw() + volume(1) + os.sleep(0.2) + drawAll() + elseif ecs.clickedAtArea(e[3], e[4], obj.gromkostMinus[1], obj.gromkostMinus[2], obj.gromkostMinus[3], obj.gromkostMinus[4]) then + bigLetters.drawText(obj.gromkostMinus[1], obj.gromkostMinus[2], config.colors.bottomToolBarCurrentColor, "-", "*" ) + buffer.draw() + volume(-1) + os.sleep(0.2) + drawAll() + end + else + local action = context.menu(e[3], e[4], {"Добавить станцию", #radioStations >= countOfStationsLimit}, {"Удалить станцию", #radioStations < 2}, "-", {"О программе"}, "-", {"Выход"}) + if action == "Добавить станцию" then + local data = ecs.universalWindow("auto", "auto", 36, 0x262626, true, + {"EmptyLine"}, + {"CenterText", ecs.colors.orange, "Добавить станцию"}, + {"EmptyLine"}, + {"Input", 0xFFFFFF, ecs.colors.orange, "Название станции"}, + {"Input", 0xFFFFFF, ecs.colors.orange, "URL-ссылка на стрим"}, + {"EmptyLine"}, + {"Button", {ecs.colors.orange, 0x262626, "OK"}, {0x999999, 0xffffff, "Отмена"}} + ) + if data[3] == "OK" then + table.insert(radioStations, {name = data[1], url = data[2]}) + saveStations() + drawAll() + end + elseif action == "Удалить станцию" then + table.remove(radioStations, radioStations.currentStation) + saveStations() + drawAll() + + elseif action == "О программе" then + ecs.universalWindow("auto", "auto", 36, 0x262626, true, + {"EmptyLine"}, + {"CenterText", ecs.colors.orange, "Radio v1.0"}, + {"EmptyLine"}, + {"CenterText", 0xFFFFFF, "Автор:"}, + {"CenterText", 0xBBBBBB, "Тимофеев Игорь"}, + {"CenterText", 0xBBBBBB, "vk.com/id7799889"}, + {"EmptyLine"}, + {"CenterText", 0xFFFFFF, "Тестер:"}, + {"CenterText", 0xBBBBBB, "Олег Гречкин"}, + {"CenterText", 0xBBBBBB, "http://vk.com/id250552893"}, + {"EmptyLine"}, + {"CenterText", 0xFFFFFF, "Автор идеи:"}, + {"CenterText", 0xBBBBBB, "MrHerobrine с Dreamfinity"}, + {"EmptyLine"}, + {"Button", {ecs.colors.orange, 0xffffff, "OK"}} + ) + elseif action == "Выход" then + buffer.square(1, 1, buffer.getWidth(), buffer.getHeight(), config.colors.background, 0xFFFFFF, " ") + buffer.draw() + ecs.prepareToExit() + radio.stop() + return + end + end + + elseif e[1] == "scroll" then + switchStation(e[5]) + drawAll() + end +end + + + + + + + diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Radio.app/Resources/About/Russian.txt b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Radio.app/Resources/About/Russian.txt new file mode 100755 index 00000000..e96c4609 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Radio.app/Resources/About/Russian.txt @@ -0,0 +1 @@ +Программа для управления радио из мода OpenFM, стилизованная под известный плеер iRiver SPINN. \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Radio.app/Resources/Icon.pic b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Radio.app/Resources/Icon.pic new file mode 100755 index 0000000000000000000000000000000000000000..c5f17e38f4de143b492944eee9c33c298e9fd88f GIT binary patch literal 167 zcmXxcxebI+3`NoZZ2u*q22w(n5Rr^D(nLtm0>ns^Kn+j|4N(FOfCEo= 40 and num <= 160 then return true end end + resolutionTextBoxHeight.validator = function(text) local num = tonumber(text); if num and num >= 12 and num <= 50 then return true end end + local function onAnyResolutionTextBoxInputFinished() + window:stopEventHandling() + rayEngine.changeResolution(tonumber(resolutionTextBoxWidth.text), tonumber(resolutionTextBoxHeight.text)) + settings() + end + resolutionTextBoxWidth.onInputFinished = onAnyResolutionTextBoxInputFinished + resolutionTextBoxHeight.onInputFinished = onAnyResolutionTextBoxInputFinished + + local drawDistanceSlider = window:addChild(GUI.slider(x, y, sliderWidth, 0xFFDB80, 0x000000, 0xFFDB40, 0xDDDDDD, 100, 5000, rayEngine.properties.drawDistance, true, localization.drawDistance)) + drawDistanceSlider.onValueChanged = function() + rayEngine.properties.drawDistance = drawDistanceSlider.value + window:draw() + buffer.draw() + end; y = y + 4 + + local shadingDistanceSlider = window:addChild(GUI.slider(x, y, sliderWidth, 0xFFDB80, 0x000000, 0xFFDB40, 0xDDDDDD, 100, 3000, rayEngine.properties.shadingDistance, true, localization.shadingDistance)) + shadingDistanceSlider.onValueChanged = function() + rayEngine.properties.shadingDistance = shadingDistanceSlider.value + window:draw() + buffer.draw() + end; y = y + 4 + + local shadingCascadesSlider = window:addChild(GUI.slider(x, y, sliderWidth, 0xFFDB80, 0x000000, 0xFFDB40, 0xDDDDDD, 2, 48, rayEngine.properties.shadingCascades, true, localization.shadingCascades)) + shadingCascadesSlider.onValueChanged = function() + rayEngine.properties.shadingCascades = shadingCascadesSlider.value + window:draw() + buffer.draw() + end; y = y + 4 + + local raycastQualitySlider = window:addChild(GUI.slider(x, y, sliderWidth, 0xFFDB80, 0x000000, 0xFFDB40, 0xDDDDDD, 0.5, 32, rayEngine.properties.raycastQuality, true, localization.raycastQuality)) + raycastQualitySlider.onValueChanged = function() + rayEngine.properties.raycastQuality = raycastQualitySlider.value + window:draw() + buffer.draw() + end; y = y + 4 + + local currentTimeSlider = window:addChild(GUI.slider(x, y, sliderWidth, rayEngine.world.colors.sky.current, 0x000000, rayEngine.world.colors.sky.current, 0xDDDDDD, 0, rayEngine.world.dayNightCycle.length, rayEngine.world.dayNightCycle.currentTime, true, localization.dayNightCycle, localization.seconds)) + currentTimeSlider.onValueChanged = function() + rayEngine.world.dayNightCycle.currentTime = currentTimeSlider.value + rayEngine.refreshTimeDependentColors() + currentTimeSlider.colors.active = rayEngine.world.colors.sky.current + currentTimeSlider.colors.pipe = rayEngine.world.colors.sky.current + window:draw() + buffer.draw() + end; y = y + 4 + + window:addChild(GUI.label(x, y, sliderWidth, 1, 0xDDDDDD, localization.enableSemipixelRenderer)) + + local graphonSwitch = window:addChild(GUI.switch(x + sliderWidth - 8, y, 8, 0xFFDB40, 0xAAAAAA, 0xFFFFFF, not rayEngine.properties.useSimpleRenderer)) + graphonSwitch.onStateChanged = function() + rayEngine.properties.useSimpleRenderer = not graphonSwitch.state + window:draw() + buffer.draw() + end; y = y + 3 + + window:addChild(GUI.label(x, y, sliderWidth, 1, 0xDDDDDD, localization.enableDayNightCycle)) + + local lockTimeSwitch = window:addChild(GUI.switch(x + sliderWidth - 8, y, 8, 0xFFDB40, 0xAAAAAA, 0xFFFFFF, rayEngine.world.dayNightCycle.enabled)) + lockTimeSwitch.onStateChanged = function() + rayEngine.world.dayNightCycle.enabled = lockTimeSwitch.state + window:draw() + buffer.draw() + end; y = y + 3 + + window:addChild(GUI.button(x, y, sliderWidth, 3, 0xEEEEEE, 0x262626, 0xBBBBBB, 0x262626, localization.continue)).onTouch = function() window:stopEventHandling(); table.toFile(applicationResourcesDirectory .. "RayEngine.cfg", rayEngine.properties, true) end + + window:draw(); buffer.draw(); window:startEventHandling() +end + +local function menu() + local window = GUI.fullScreenContainer() + local oldDraw = window.draw + window.draw = function() + menuBackground() + oldDraw(window) + end + + local buttonWidth, buttonHeight = 50, 3 + local worlds = {} + for file in fs.list(worldsPath) do table.insert(worlds, unicode.sub(file, 1, -2)) end + local x, y = math.floor(window.width / 2 - buttonWidth / 2), math.floor(window.height / 2 - #worlds * (buttonHeight + 1) / 2 - 11) + + window:addChild(GUI.label(1, y, window.width, 1, 0xFFFFFF, rayWalkVersion)):setAlignment(GUI.alignment.horizontal.center, GUI.alignment.vertical.top); y = y + 3 + window:addChild(GUI.button(x, y, buttonWidth, buttonHeight, 0xEEEEEE, 0x262626, 0xBBBBBB, 0x262626, localization.continue)).onTouch = function() window:stopEventHandling() end; y = y + buttonHeight + 1 + window:addChild(GUI.button(x, y, buttonWidth, buttonHeight, 0xEEEEEE, 0x262626, 0xBBBBBB, 0x262626, localization.settings)).onTouch = function() window:stopEventHandling(); settings() end; y = y + buttonHeight + 1 + window:addChild(GUI.button(x, y, buttonWidth, buttonHeight, 0xEEEEEE, 0x262626, 0x999999, 0x262626, localization.exit)).onTouch = function() buffer.clear(0x000000); buffer.draw(); os.exit() end; y = y + buttonHeight + 1 + window:addChild(GUI.label(1, y, window.width, 1, 0xFFFFFF, localization.loadWorld)):setAlignment(GUI.alignment.horizontal.center, GUI.alignment.vertical.top); y = y + 2 + + for i = 1, #worlds do + window:addChild(GUI.button(x, y, buttonWidth, buttonHeight, 0xEEEEEE, 0x262626, 0xBBBBBB, 0x262626, worlds[i])).onTouch = function() rayEngine.loadWorld(worldsPath .. worlds[i]); window:stopEventHandling() end + y = y + buttonHeight + 1 + end + + local lines = {}; for i = 1, #localization.controlsHelp do table.insert(lines, localization.controlsHelp[i]) end + table.insert(lines, 1, " ") + table.insert(lines, 1, {text = localization.controls, color = 0xFFFFFF}) + window:addChild(GUI.textBox(1, y, window.width, #lines, nil, 0xCCCCCC, lines, 1):setAlignment(GUI.alignment.horizontal.center, GUI.alignment.vertical.top)); y = y + #lines + 1 + + window:draw(); buffer.draw(); window:startEventHandling() +end + + +---------------------------------------------------------------------------------------------------------------------------------- + +local controls = { + ["key_down"] = { + [16] = rayEngine.turnLeft, --q + [18] = rayEngine.turnRight, --e + [30] = rayEngine.moveLeft, --a + [32] = rayEngine.moveRight, --d + [17] = rayEngine.moveForward, --w + [31] = rayEngine.moveBackward, --s + [50] = rayEngine.toggleMinimap, --m + [37] = rayEngine.toggleCompass, --k + [25] = rayEngine.toggleWatch, --p + [14] = menu, --backspace + [28] = rayEngine.commandLine, --enter + [57] = rayEngine.jump, --space + [29] = rayEngine.crouch, --ctrl + [59] = rayEngine.toggleDebugInformation, -- F1 + }, + ["key_up"] = { + [29] = rayEngine.crouch, --ctrl + }, +} + +-------------------------------------------------------------------------------------------------------------- + +rayEngine.loadEngineProperties(applicationResourcesDirectory .. "RayEngine.cfg") +rayEngine.loadWeapons(applicationResourcesDirectory .. "Weapons/") +rayEngine.loadWorld(worldsPath .. "ExampleWorld") +rayEngine.changeResolution(rayEngine.properties.screenResolution.width, rayEngine.properties.screenResolution.height) +-- rayEngine.intro() +menu() +rayEngine.update() + +while true do + local e = { event.pull(1) } + if e[1] == "touch" then + if e[5] == 1 then + if not rayEngine.currentWeapon then rayEngine.place(3, 0x3) end + else + if rayEngine.currentWeapon then rayEngine.fire() else rayEngine.destroy(3) end + end + elseif e[1] == "key_down" then + if e[4] > 1 and e[4] < 10 then + rayEngine.changeWeapon(e[4] - 2) + else + if controls[e[1]] and controls[e[1]][e[4]] then controls[e[1]][e[4]]() end + end + elseif e[1] == "key_up" then + if controls[e[1]] and controls[e[1]][e[4]] then controls[e[1]][e[4]]() end + end + rayEngine.update() +end \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/RayWalk.app/Resources/About/Russian.txt b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/RayWalk.app/Resources/About/Russian.txt new file mode 100755 index 00000000..afa9796e --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/RayWalk.app/Resources/About/Russian.txt @@ -0,0 +1 @@ +Это приложение - вершина графических возможностей OpenComputers! Оно демонстрирует всю мощь нашей библиотеки тройной буферизации, игрового движка RayEngine, а также объектно-ориентированной оконной библиотеки windows, которые в совокупности позволяют рендерить графику на уровне Wolfenstein и DOOM. \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/RayWalk.app/Resources/Icon.pic b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/RayWalk.app/Resources/Icon.pic new file mode 100755 index 0000000000000000000000000000000000000000..b8e59198110608e957eb9e058ab6b45a326303e2 GIT binary patch literal 101 zcmXxbu?>JA6h%?)|6gDXcQG-svSA?u>;NcqL||p$o_z3-*=XxD*&`Zfi)!j+I&Kf`wODYwQg>wK#elOA%JtW|$hw zCoGH+9BOf!3WqG3c@I}CNt zLFwVH0aCRhVNi$~g%PuM*me7?1kp8n!ao3*p84o6gxa(FDa^RTTV77Z+$|QuMyDsaNCE-a zolrrY)LDQS8GA9nAg~&{5=g}B84C8Zyl|}fm2I>&0TsA2AKO>+rV2^DMGok`68-Ll z11Y1ba%<5ADTjCd+deqqWt7xi{eyMLiB2O~cn9xg+5{=4h@zC$lw=qyiQPwNv8xm3 zIMpa`UR11ltzFr!4L2+&FS>9%n7ETGX_guFS!FUnRc&8lf9EXPD9bUikSLqcTzKKl zvkfyxm7t4I8e5Ou3-305lq|q?Vme5~yfGN=@kh9AHPekzere-**SMK!&wM8A#e1S**V&72cI4oD68Q5P% zqe@7x$|E`~Et-c3h!#Uc>1Mg4<_?GPySyQw{=!H57v`mT6nDmk5^Vve(HIOjR&*6g zJM)Rfj5M7k+J#u}5h8|w@ppmT?#Y~)nA0J{+DO-&E1`mL{ydqHeSYkgh^)HJgUD58b=wBznC|^r?2d^pU0iQ@h!b_D}9n1D{QW8FrU>7TO$}Rw7R7u zgi@?eRR47JbX}EZBPk3}u5!>FkjBQ98aP%%4-3fM=b=c|{AQ`Vsx#;jx6 z_4HO_Q56WD`a>_MUzyocmyW-Pnnp{i(IhNgR!0(R7Nt>0^i7^6_J}lWY#r%KhYhIS NdB#cIdY$XG`yZDWa_j&A literal 0 HcmV?d00001 diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/RayWalk.app/Resources/Weapons/FireTextures/PowderFire.pic b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/RayWalk.app/Resources/Weapons/FireTextures/PowderFire.pic new file mode 100755 index 0000000000000000000000000000000000000000..89b283c304923fc02e169ee5fbb559602cc46d8e GIT binary patch literal 1187 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 literal 0 HcmV?d00001 diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/RayWalk.app/Resources/Weapons/WeaponTextures/Rifle.pic b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/RayWalk.app/Resources/Weapons/WeaponTextures/Rifle.pic new file mode 100755 index 0000000000000000000000000000000000000000..fd034377d6e24d14501729617e45e5a9187a7a92 GIT binary patch literal 3798 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 F`X3OSEK2|Y literal 0 HcmV?d00001 diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/RayWalk.app/Resources/Weapons/Weapons.cfg b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/RayWalk.app/Resources/Weapons/Weapons.cfg new file mode 100755 index 00000000..f7aa4274 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/RayWalk.app/Resources/Weapons/Weapons.cfg @@ -0,0 +1,46 @@ +{ + { + name = "USP-5", + damage = 25, + weaponTexture = "WeaponTextures/Pistol.pic", + fireTexture = "FireTextures/PowderFire.pic", + firePosition = { + x = -1, + y = -4, + }, + crosshairTexture = "CrosshairTextures/Default.pic", + }, + { + name = "MAG20-USP", + damage = 48, + weaponTexture = "WeaponTextures/Rifle.pic", + fireTexture = "FireTextures/PowderFire.pic", + firePosition = { + x = 1, + y = -1, + }, + crosshairTexture = "CrosshairTextures/Half.pic", + }, + { + name = "UberSniper228", + damage = 100, + weaponTexture = "WeaponTextures/Sniper.pic", + fireTexture = "FireTextures/PowderFire.pic", + firePosition = { + x = -5, + y = 1, + }, + crosshairTexture = "CrosshairTextures/Angled.pic", + }, + { + name = "Plasmer", + damage = 80, + weaponTexture = "WeaponTextures/Plasmer.pic", + fireTexture = "FireTextures/Plasma.pic", + firePosition = { + x = 1, + y = -2, + }, + crosshairTexture = "CrosshairTextures/Dotted.pic", + }, +} \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/RayWalk.app/Resources/Worlds/ExampleWorld/Blocks.cfg b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/RayWalk.app/Resources/Worlds/ExampleWorld/Blocks.cfg new file mode 100755 index 00000000..edc7dd40 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/RayWalk.app/Resources/Worlds/ExampleWorld/Blocks.cfg @@ -0,0 +1,26 @@ +{ + { + color = 0xFFFFFF, + canBeDestroyed = false, + }, + { + color = 0xFFFFFF, + canBeDestroyed = true, + }, + { + color = 0xFF8888, + canBeDestroyed = true, + }, + { + color = 0x88FF88, + canBeDestroyed = true, + }, + { + color = 0xFF88FF, + canBeDestroyed = true, + }, + { + color = 0xFFEE60, + canBeDestroyed = true, + }, +} \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/RayWalk.app/Resources/Worlds/ExampleWorld/Map.cfg b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/RayWalk.app/Resources/Worlds/ExampleWorld/Map.cfg new file mode 100755 index 00000000..6c03aaea --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/RayWalk.app/Resources/Worlds/ExampleWorld/Map.cfg @@ -0,0 +1,35 @@ +{ + { 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1 }, + { 0x1, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0x1 }, + { 0x1, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0x3, 0x2, nil, 0x1 }, + { 0x1, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0x2, nil, 0x1 }, + { 0x1, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0x1 }, + { 0x1, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0x1 }, + { 0x1, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0x1 }, + { 0x1, nil, nil, nil, nil, nil, 0x2, 0x2, 0x2, 0x2, 0x2, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0x1 }, + { 0x1, nil, nil, nil, nil, nil, 0x5, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0x1 }, + { 0x1, nil, nil, nil, nil, nil, 0x4, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0x1 }, + { 0x1, nil, nil, nil, nil, nil, 0x3, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0x1 }, + { 0x1, nil, nil, nil, nil, nil, 0x2, nil, nil, nil, nil, nil, nil, nil, nil, 0x2, nil, nil, nil, nil, nil, nil, nil, 0x1 }, + { 0x1, nil, nil, nil, nil, nil, 0x2, nil, nil, nil, nil, nil, nil, nil, nil, 0x2, nil, nil, nil, nil, nil, nil, nil, 0x1 }, + { 0x1, nil, nil, nil, nil, nil, 0x2, nil, nil, nil, nil, nil, nil, nil, nil, 0x2, nil, nil, nil, nil, nil, nil, nil, 0x1 }, + { 0x1, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0x2, 0x2, 0x2, 0x2, 0x2, nil, nil, nil, nil, nil, 0x1 }, + { 0x1, nil, nil, nil, nil, nil, 0x2, nil, nil, nil, nil, nil, nil, nil, nil, 0x2, nil, nil, nil, nil, nil, nil, nil, 0x1 }, + { 0x1, nil, nil, nil, nil, nil, 0x2, nil, nil, nil, nil, nil, nil, nil, nil, 0x2, nil, nil, nil, nil, nil, nil, nil, 0x1 }, + { 0x1, nil, nil, nil, nil, nil, 0x2, nil, nil, nil, nil, nil, nil, nil, nil, 0x2, nil, nil, nil, nil, nil, nil, nil, 0x1 }, + { 0x1, nil, nil, nil, nil, nil, 0x2, nil, nil, nil, nil, nil, nil, nil, nil, 0x2, nil, nil, nil, nil, nil, nil, nil, 0x1 }, + { 0x1, nil, nil, nil, nil, nil, 0x2, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0x1 }, + { 0x1, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0x1 }, + { 0x1, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0x1 }, + { 0x1, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0x1 }, + { 0x1, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0x1 }, + { 0x1, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0x2, nil, nil, 0x1 }, + { 0x1, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0x3, nil, nil, 0x1 }, + { 0x1, nil, nil, nil, nil, nil, nil, nil, nil, 0x2, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0x3, nil, nil, 0x1 }, + { 0x1, nil, nil, nil, nil, nil, nil, nil, nil, 0x2, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0x2, nil, nil, 0x1 }, + { 0x1, nil, nil, nil, nil, nil, nil, nil, nil, 0x4, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0x2, nil, nil, 0x1 }, + { 0x1, nil, nil, nil, nil, nil, nil, nil, nil, 0x2, 0x4, 0x4, 0x2, nil, 0x2, 0x2, nil, nil, nil, nil, nil, nil, nil, 0x1 }, + { 0x1, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0x1 }, + { 0x1, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0x1 }, + { 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1 }, +} \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/RayWalk.app/Resources/Worlds/ExampleWorld/Player.cfg b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/RayWalk.app/Resources/Worlds/ExampleWorld/Player.cfg new file mode 100755 index 00000000..777a7e19 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/RayWalk.app/Resources/Worlds/ExampleWorld/Player.cfg @@ -0,0 +1,17 @@ +{ + position = { + x = 5, + y = 5, + }, + health = { + maximum = 100, + current = 100, + }, + isCrouched = false, + jumpHeight = 10, + moveSpeed = 16, + rotationSpeed = 5, + crouchHeight = 10, + fieldOfView = 60, + rotation = 0, +} \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/RayWalk.app/Resources/Worlds/ExampleWorld/World.cfg b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/RayWalk.app/Resources/Worlds/ExampleWorld/World.cfg new file mode 100755 index 00000000..ae6f7c3f --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/RayWalk.app/Resources/Worlds/ExampleWorld/World.cfg @@ -0,0 +1,28 @@ +{ + dayNightCycle = { + enabled = true, + currentTime = 100, + speed = 1, + length = 300, + }, + colors = { + ground = 0xEEEEEE, + clouds = 0xFFFFFF, + sky = { + 0x002440, + 0x002480, + 0x3349BF, + 0x6692FF, + 0x99DBFF, + 0x99DBFF, + 0x99DBFF, + 0x66B6FF, + 0x66B6FF, + 0xFFDB80, + 0xFFB640, + 0xCCBD00, + 0x000080, + 0x002440, + }, + }, +} \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/RayWalk.app/Resources/Worlds/SundownBeams/Blocks.cfg b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/RayWalk.app/Resources/Worlds/SundownBeams/Blocks.cfg new file mode 100755 index 00000000..edc7dd40 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/RayWalk.app/Resources/Worlds/SundownBeams/Blocks.cfg @@ -0,0 +1,26 @@ +{ + { + color = 0xFFFFFF, + canBeDestroyed = false, + }, + { + color = 0xFFFFFF, + canBeDestroyed = true, + }, + { + color = 0xFF8888, + canBeDestroyed = true, + }, + { + color = 0x88FF88, + canBeDestroyed = true, + }, + { + color = 0xFF88FF, + canBeDestroyed = true, + }, + { + color = 0xFFEE60, + canBeDestroyed = true, + }, +} \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/RayWalk.app/Resources/Worlds/SundownBeams/Map.cfg b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/RayWalk.app/Resources/Worlds/SundownBeams/Map.cfg new file mode 100755 index 00000000..c4e7c04c --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/RayWalk.app/Resources/Worlds/SundownBeams/Map.cfg @@ -0,0 +1,28 @@ +{ + { 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1 }, + { 0x1, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0x1 }, + { 0x1, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0x1 }, + { 0x1, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0x1 }, + { 0x1, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0x1 }, + { 0x1, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0x1 }, + { 0x1, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0x1 }, + { 0x1, nil, nil, nil, nil, nil, 0x1, 0x1, 0x1, 0x1, 0x1, nil, nil, nil, nil, nil, nil, nil, nil, 0x1 }, + { 0x1, nil, nil, nil, nil, nil, 0x1, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0x1 }, + { 0x1, nil, nil, nil, nil, nil, 0x1, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0x1 }, + { 0x1, nil, nil, nil, nil, nil, 0x1, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0x1 }, + { 0x1, nil, nil, nil, nil, nil, 0x1, nil, nil, nil, nil, nil, nil, nil, nil, 0x1, nil, nil, nil, 0x1 }, + { 0x1, nil, nil, nil, nil, nil, 0x1, nil, nil, nil, nil, nil, nil, nil, nil, 0x1, nil, nil, nil, 0x1 }, + { 0x1, nil, nil, nil, nil, nil, 0x1, nil, nil, nil, nil, nil, nil, nil, nil, 0x1, nil, nil, nil, 0x1 }, + { 0x1, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0x1, 0x1, 0x1, 0x1, 0x1, nil, 0x1 }, + { 0x1, nil, nil, nil, nil, nil, 0x1, nil, nil, nil, nil, nil, nil, nil, nil, 0x1, nil, nil, nil, 0x1 }, + { 0x1, nil, nil, nil, nil, nil, 0x1, nil, nil, nil, nil, nil, nil, nil, nil, 0x1, nil, nil, nil, 0x1 }, + { 0x1, nil, nil, nil, nil, nil, 0x1, nil, nil, nil, nil, nil, nil, nil, nil, 0x1, nil, nil, nil, 0x1 }, + { 0x1, nil, nil, nil, nil, nil, 0x1, nil, nil, nil, nil, nil, nil, nil, nil, 0x1, nil, nil, nil, 0x1 }, + { 0x1, nil, nil, nil, nil, nil, 0x1, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0x1 }, + { 0x1, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0x1 }, + { 0x1, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0x1 }, + { 0x1, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0x1 }, + { 0x1, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0x1 }, + { 0x1, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 0x1 }, + { 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1 }, +} \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/RayWalk.app/Resources/Worlds/SundownBeams/Player.cfg b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/RayWalk.app/Resources/Worlds/SundownBeams/Player.cfg new file mode 100755 index 00000000..777a7e19 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/RayWalk.app/Resources/Worlds/SundownBeams/Player.cfg @@ -0,0 +1,17 @@ +{ + position = { + x = 5, + y = 5, + }, + health = { + maximum = 100, + current = 100, + }, + isCrouched = false, + jumpHeight = 10, + moveSpeed = 16, + rotationSpeed = 5, + crouchHeight = 10, + fieldOfView = 60, + rotation = 0, +} \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/RayWalk.app/Resources/Worlds/SundownBeams/World.cfg b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/RayWalk.app/Resources/Worlds/SundownBeams/World.cfg new file mode 100755 index 00000000..ae6f7c3f --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/RayWalk.app/Resources/Worlds/SundownBeams/World.cfg @@ -0,0 +1,28 @@ +{ + dayNightCycle = { + enabled = true, + currentTime = 100, + speed = 1, + length = 300, + }, + colors = { + ground = 0xEEEEEE, + clouds = 0xFFFFFF, + sky = { + 0x002440, + 0x002480, + 0x3349BF, + 0x6692FF, + 0x99DBFF, + 0x99DBFF, + 0x99DBFF, + 0x66B6FF, + 0x66B6FF, + 0xFFDB80, + 0xFFB640, + 0xCCBD00, + 0x000080, + 0x002440, + }, + }, +} \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/RunningString.app/Main.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/RunningString.app/Main.lua new file mode 100755 index 00000000..d2773f52 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/RunningString.app/Main.lua @@ -0,0 +1,72 @@ +local ecs = require("ECSAPI") +local event = require("event") +local gpu = require("component").gpu +local unicode = require("unicode") +local keyboard = require("keyboard") + +local str,freq,speed,scale,bg,fg + +-- gpu.setResolution(gpu.maxResolution()) +-- ecs.prepareToExit() + +local data = ecs.universalWindow("auto", "auto", 30, ecs.windowColors.background, true, + {"EmptyLine"}, + {"CenterText", 0x880000, "Бегущая строка"}, + {"EmptyLine"}, + {"CenterText", 0x000000, "Для выхода из программы"}, + {"CenterText", 0x000000, "удерживайте Enter"}, + {"EmptyLine"}, + {"Input", 0x262626, 0x880000, "Программист за работой, не мешай, сука!"}, + {"Color", "Цвет фона", 0x000000}, + {"Color", "Цвет текста", 0xFFFFFF}, + {"Slider", 0x262626, 0x880000, 1, 100, 1, "Масштаб: ", "%"}, + {"Slider", 0x262626, 0x880000, 1, 100, 40, "Скорость: ", "/100 FPS"}, + {"EmptyLine"}, + {"Button", {0xbbbbbb, 0xffffff, "OK"}, {0x999999, 0xffffff, "Отмена"}} +) + +-- ecs.error(table.unpack(data)) +if data[6] == "OK" then + str = data[1] or "Где текст, сука?" + bg = tonumber(data[2]) or 0x000000 + fg = tonumber(data[3]) or 0xFFFFFF + scale = tonumber(data[4])/100 or 0.1 + speed = tonumber(data[5])/100 or 0.4 + freq = 5 +else + return +end + +local xOld, yOld = gpu.getResolution() +ecs.setScale(scale) +local xSize, ySize = gpu.getResolution() +gpu.setBackground(bg) +gpu.setForeground(fg) +gpu.fill(1, 1, xSize, ySize, " ") + +str = " " .. str .. string.rep(" ", freq) + +while true do + str = unicode.sub(str, 2, -1) .. unicode.sub(str, 1, 1) + gpu.set(math.ceil(xSize / 2 - unicode.len(str) / 2), math.ceil(ySize / 2), str) + + if keyboard.isKeyDown(28) then + gpu.setResolution(xOld, yOld) + ecs.prepareToExit() + return + end + + local e = event.pull(speed) + if e == "key_down" or e == "touch" then return end +end + + + + + + + + + + + diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/RunningString.app/Resources/About/Russian.txt b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/RunningString.app/Resources/About/Russian.txt new file mode 100755 index 00000000..96d61478 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/RunningString.app/Resources/About/Russian.txt @@ -0,0 +1 @@ +Бегущая строка, красиво и наглядно отображающая любое количество информации. Сделана специально по заказу Никиты Ярычева, ссылка на вк: http://vk.com/mcmodder. В общем, если ты читаешь это, то передай ему, что он пидор, ибо мне пришлось добавлять кучу новых фич в библиотеку ecsapi, а это нервы, нервы! \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/RunningString.app/Resources/Icon.pic b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/RunningString.app/Resources/Icon.pic new file mode 100755 index 0000000000000000000000000000000000000000..6eacebcc4e2eb9a017a4e7109d1e561ef89a0a26 GIT binary patch literal 183 zcmYMsy$!-Z3&?$NK~xEUy}-@$s9=10 then + drawSymb(x, y, tostring(math.floor(text/10)), color) + drawSymb(x + 6, y, tostring(math.floor(text%10)), color) + else + drawSymb(x, y, tostring(text), color) + end +end + +local function isIn(x1, y1, x2, y2, xClick, yClick) + if xClick >= x1 and xClick <= x2 and yClick >=y1 and yClick <= y2 then + return true + else + return false + end +end + +local function drawLastScore(x, y, score, color) + ecs.square((x + 6) * 2, y, 35, 7, 0x262626) + drawKrug(x + 3, y + 3, 3, color) + drawText(x + 9, y, score, 0xffffff) + + local yPos = 34 + newObj("Buttons", "Заново", ecs.drawAdaptiveButton(xScore, yPos, 18, 1, "Заново", ecs.colors.blue, 0xffffff)) + yPos = yPos + 4 + newObj("Buttons", "Выйти ", ecs.drawAdaptiveButton(xScore, yPos, 18, 1, "Выйти ", ecs.colors.blue, 0xffffff)) + +end + +local function Tir() + ecs.prepareToExit() + + showPlayers(xScore, yScore) + drawLastScore(xScore / 2, 22, 0, 0xffffff) + + drawMishen() + while true do + local e = {event.pull()} + if e[1] == "touch" then + for key, val in pairs(obj["Buttons"]) do + if ecs.clickedAtArea(e[3], e[4], obj["Buttons"][key][1], obj["Buttons"][key][2], obj["Buttons"][key][3], obj["Buttons"][key][4]) then + ecs.drawAdaptiveButton(obj["Buttons"][key][1], obj["Buttons"][key][2], 18, 1, key, 0xffffff, 0x000000) + os.sleep(0.5) + + if key == "Выйти " then + return "exit" + else + players = {} + return 0 + end + end + end + e[3] = e[3]/2 + AddPlayer(e[6]) + AddScore(e[6], GetScore(e[3], e[4])) + SetPixel(e[3], e[4], players[e[6]][2]) + showPlayers(xScore, yScore) + drawLastScore(xScore / 2, 22, GetScore(e[3], e[4]),players[e[6]][2]) + end + end +end + +-------------------------- + +gpu.setBackground(0x262626) +gpu.fill(1, 1, xSize, ySize, " ") + +while true do + local exit = Tir() + if exit == "exit" then break end +end + +gpu.setResolution(xOld, yOld) +ecs.prepareToExit() + diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Shooting.app/Resources/Icon.pic b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Shooting.app/Resources/Icon.pic new file mode 100755 index 0000000000000000000000000000000000000000..7a2f264ad7ba784dc5eca7726c2c5e3091702a3d GIT binary patch literal 138 zcmXYou?>JQ3( 0 then + table.remove(contacts, mainContainer.contactsComboBox.selectedItem) + updateContacts() + saveContacts() + updateButtons() + + mainContainer:draw() + buffer.draw() + end +end + +mainContainer:addChild(GUI.label(x, y, width, 1, 0xEEEEEE, "Energy to dial")):setAlignment(GUI.alignment.horizontal.center, GUI.alignment.vertical.top); y = y + 2 +mainContainer.fuelProgressBar = mainContainer:addChild(GUI.progressBar(x, y, width, 0xBBBBBB, 0x0, 0xEEEEEE, 100, true, true, "", "%")); y = y + 3 +mainContainer.exitButton = mainContainer:addChild(GUI.framedButton(x, y, width, 3, 0xEEEEEE, 0xEEEEEE, 0xBBBBBB, 0xBBBBBB, "Exit")); y = y + 4 +mainContainer.exitButton.onTouch = function() + mainContainer:stopEventHandling() +end + +mainContainer.eventHandler = function(mainContainer, object, eventData) + if eventData[1] == "sgIrisStateChange" then + update() + mainContainer:draw() + buffer.draw() + elseif eventData[1] == "sgStargateStateChange" then + if eventData[3] == "Idle" or eventData[3] == "Connected" then + update() + updateChevrons(eventData[3] == "Connected") + mainContainer:draw() + buffer.draw() + end + elseif eventData[1] == "sgChevronEngaged" then + if mainContainer.chevrons[eventData[3]] then + mainContainer.chevrons[eventData[3]].isActivated = true + mainContainer.chevrons[eventData[3]].text = eventData[4] + mainContainer:draw() + buffer.draw() + end + elseif eventData[1] == "sgMessageReceived" then + GUI.error(eventData[3]) + end +end + +loadContacts() +updateContacts() +update() +updateChevrons(stargate.stargateState() == "Connected") + +mainContainer:draw() +buffer.draw(true) +mainContainer:startEventHandling() + + + + + + + + diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Stargate.app/Resources/Ch1.pic b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Stargate.app/Resources/Ch1.pic new file mode 100755 index 0000000000000000000000000000000000000000..8ab765eca18018a48537a1eea5c08a31285aa8f4 GIT binary patch literal 266 zcmXYry$J$Q5QN|Cd-v`NumD5P0&GAKj7+ou8!-_?5JVKzK;+$B1O9+ups^j8Zwp2y zp5xC(!|b=S(`mL_K5=49Me_qv48YOigg#z?xJbhUHEN4Uvl)pVfmp*4sg!hW4-QU? z5e)7SSPfIk1y@ePjJG!)(B1I1OO`P!)R`!sm>u1Z6wR(!e`Tck9t}L{dd-%$5cS*Gz{6aPd2%56_*B8V#W$v(R3bSeVn`jx!pl| literal 0 HcmV?d00001 diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Stargate.app/Resources/Ch2.pic b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Stargate.app/Resources/Ch2.pic new file mode 100755 index 0000000000000000000000000000000000000000..b5c53a9735b8199a1bed78910e65caf75377d557 GIT binary patch literal 298 zcmXYsJqp4=6omJ^U6b6v*3#q}f?#D4J%EkJun|NM1RJ#w*`GqeUNJ%r^Dd&3b*p@Q zJDHuGjwa)*FsxKx!jmgn5|Jrsg-=hsqYjxeu{%;g4QtQ983J1DhlbH=aeLSb0VClB zTM{!BVlR;m{&OeDIofgw_hd712VjLZ*ft@Ff24pB*YZEWHP$hT!Yv(Y@o(I^fJN?z wnr=77ZV{-lg~c}MQs5fcEzHGU(`z#EO)Mj94V!40^jFaUtXSg>#t`Sl5Ae7^0ssI2 literal 0 HcmV?d00001 diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Stargate.app/Resources/Icon.pic b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Stargate.app/Resources/Icon.pic new file mode 100755 index 0000000000000000000000000000000000000000..d99d3812d5f826c2b505cf969db3473705531222 GIT binary patch literal 292 zcmXYrF>V4e5JmmRgV_V*0F((ApdnSHNK_Qz0_kX|xImCrEZdN7l@w9DAf*lxMI?$G zAx$LuxybU3T>0tE_n(;Um$kSWl`T$H=Ny1WP93X?3C+-80!fe{h#c76AQ&W@D{M6X0!TVZ9*-R48CxEQ z>ELmooTD6M|9_3sRk)ZqAWc4D}gD-cQ!H=4JHk+uVYM5DQL#|iisn>)`uYs0othhh5s zfnX>ciN@lIWGbD>_7CI+hlWQ+$Hpfnr>66TVyRrQZ0GdZbLZzST)Z@Y`O3o8#cNB; zE30ek8=G6(*XtB}WB2B*+jpLM_G2HvYb<{#p&v$+dGUipeSj{$lkYF!{ZHUO_I|70 z=g0y3sSBQFo#4}6JvMXVDTWNy-Yb=kICS{P(Ox{9s?DB0bM`$B2+zG|-GL9^z0~vQ zyZslLfA{hyqTs0#7!&Udf#||JMZ7bT-cQDrg?CDcU44g?cL(JAi@Cb^WIV& zr4L&so<#TQvrkI+5S8kQ-SfF8))vn`_FWi8cJST%R*v+(iT+fA z`pgejfFH&`e%Cg!de?0|2=*COOk+6!A8IqWgCe%;Xu!mr75F%^xK%|0Lk<3u4#38O z?JM9)5xXTL8gt;g%>^0TUq=L!jyZ*9!ML`RGa5IZ!xXmepp4W$hOl!8w^1_E+xKuE z7S6e?PXzn;!A81{S$?=!<9}t8_l*0f;Ouk!r^cVnX(TI1+VIcHB4e{7Ngj5j64RzShCC+3Tsr26mo;OZW~36lrU-g@1uYV zRq8+A#4vVVM-ZcqF^u{s(vH!@Aa?Fy9CfK)M#F7=DlC;9q^g)T3OHWEX)142P;hXe zLH&!E3E&^lj~$-ISHf%s7srvCz?$W|iwQJq7(qQ?mN8$!qGgO-vH7`aA?RSurtVW1 z3mBF$HE(0wF$U0YOrO{V*bh1Gwh4UI_O2 zt31F!!0_XOgO!>Q#9|a1j^V?MgKOVJ22GlUi|2abbJz;b&=Qd(rcn|vB7~fSJ^pujAJ0+RDYx}m zpOgwqAB|q*)aA^GEc!WLpV^@C>=?4tAIJSD$k>w> z<2sJbnlH$sgis1&I*#GjXoZYz-iIHff9B_g!{8rtux~IaLzr?~|K#Hq^v;}(sKUSk z`M(^?EUi=U5x4as{cXW#oIJ&RLa9M}mQc0`041E?qhmH~+~Y4)@AF)58`H*iy^L+Q z^#zmKa>xEAgJ8YJSC=e&nV-`RYFu4(TVD)tf77Ctxb9$GS#5h+ZDg=TD_bV$Q{qe+ z)Z{N$^yXQ&_0RGIQ+CTyXznpMdD?$5?69|$~g$NV1@d8|ga5A7j|q}%$Mzt1OFW)%8yX%d5b z4CU%uJjY4^inKG@CW^(H8N}%b77|!}lPc5&nArjrOIWd)92OP?kk=gK5EUY4|H`eZ zvP&3Oq|#BvDzWolX_p35#f#7&3U2G`)S+xLU%;{5WCE6jbPu_H&I(qk z)`kC|@MN8zMr#y!M#o>W`8#7YPp~DMcE&PhD2X{(LCS4?6LO8E zL=U>HZ_y_WzQ24I|4G?t+o-Vy(GUr}`)@uf$8xjcLuDKrO-60L#xo5%+;hO9@j@uR z!Ly!FWtxyq-e4hy5r?~{Fd#QFa#}2amQmKxZtnA~Nr6}aGm2kEop6pK>b*5@DHe75V4g)^`kLf0zbfiKHw+_*@8ixAk3y!<$r!*&{(*E>o_# zt?$u}O5+Pn`m2Ij9)Xz~u3*Jtd>N`xQ5#eHEVl`_^?fgkd^l=T^8gMzIM2!-cUun+ z(rZ`gG=$yTc#+XicU!N}<2JWqO#DF3=N=ii^+P^WR9+mRXac~`{US;>F@i~NCXkuI|E1Eg z8g(k+m?F-P#XXj6n)}xT+#0vPe&1sSM|x$~u2Ey!!Q#6iEWJrJSf6W50y2nK1AWHp zT+ISuN$NiBl^6aGsr-)+yFEV8s4;9d_ZhY&oDwEGvh#0*5Jm*+F>G0UFOKbf`h;%$ zEkj9$^D@Qbx9OEz&#D4BCxe_%+}SXt5&l+DfJs$EmWR)%8AW;`d9M}8%649jlo zj{%iiKjs6NXfmLPG1C~W<1M;^kg80Zv-~Q^=Yz2HwXFO&8)csJ^}J)IaAO*qBZPA5 z5X3ntYch}9_rrZ=fEq47Ps>IeZgM_=c_xlo#PJ+56_k6fIi$ocqaZx6m`s5g+AQ+` zW28L2`|lxqQ9~)K{m%s zX@8PNwt3cZWtL5HL_4Kep4y)Z^--M?uG9@B)$W_5Iww^YES%QqDmZ2NL>01%s^l#H z8QA9$(>4KR6uFua#?}OeU#FB|$IQbjpcLmJSW*HQ-DkL0NLbpRJ^+p_#bpsE93~yh za*b5JnULb22`UGoSXT~MILx|Ylu)Sj)UXMtQC?dKzXJpB+77JnO)KnJI)%#kAKxjk}*^A^qv32k{;na|J@cUr13oVOh&O+yE) zpeSu;oAhj>W)@H^W3+-f6`mn%?@^aSO0FVmZum)IQ$D#WpH%etbNnElM~D5BgpmOZmysp*=y{w_(SFghqm5LZk_U)e zG+G{4-1g@Weh3Q-OUO)!IyrphhTHxE!J@$$zTXWLQj%hB<&t7~><@dVwcx>>K}wL2{f0OQ-s1_o&XO(4g(GhJOVWl} zvtGdsTclAYjVjT?I_OLEr6Mz~-o(IJhpC-%+mFZ_>0E;jU6f&pSaI85@k&Kfxvt`| z>9)U0ekx|nNUk#uoAO8&+jpoG1-;DetBeKK##!2p9{Hc#X2xxQjXyYCw%Am`g`>(G z#h}~%I$b3M84}tSVF@Zq5*ksMB?wgQZ+Kf4F)Yej#@s$dlw|^s?NBV!>02Hz&+{FF zL~T^a^?(sY2vFRz(#hadpQOk0HJza?sHeIB2A(>ZEw|m`UNx>brxNJHq~Mn|Xk3XU z8hFudw`q7S8&>(8A~Lo+4B-aXGcJ|5hZPCaNaPhaUFxhj&${hz`}^<`fyis}#@W#F zwSJJzTvcx7aYC3h-Yc?ExBc&2sZe83euv&ta-I|`iLnN@ze`y%)sV8NgmXgp0m&+p zGg4X!6*-Jlr@^qwPLLn8zvo3v1w)RSPSMw#irTo_{=SzMJ{+-$-iHG!E-HNmxBbu~ zlr|SAo?np!?il7zYhK$zx0l1M0Tb4a-Es*y@?@q)pvI$f{YQ)qX0^5;=AlaH>zMRj-E;_27o&B&xK0?`MY29l%Lx zepQcC*l^oF=N@Fl#^y~Mo7!g(SuXWWqnSsnh-gU=F0d)O?O(9!HR_T`^pl4)-HyD64;jc|0kj3Me zB{WB{J%N55WgI)w*+-((e$^v#mh3CWpu)jkQCat_+x{)NhUihn=7*dregaLm{X1Eg zK)G7NP6dN}orUgPQ8EAf1BALEq7XwyNuShaNg3!h{%RAWvQ<9Gs@#YaR^0X_3Y8`^$(`%-s5hWrS0<5llKpJRvfdD^V;>>EM!%R+6ML zjw)tNSxDa4CTF1dS@GqmSqfjZLn%b3jQG_oHcALp4FCKn)?#RmF;V@~7|Y{g7T5cY z)b&xqCDn=;88)L~^t+w=EN2Z?=6nznRW_PM98oJ!sgt{yaALha@WL!jx5dtcz9@|n zL#e|w)@pS6W*|_+wGyVvn5-C+OO`P*VXOTbQd2&12L&^|UDvoSh<;^by5>u(-#wW_ zEQ*XOV$NZwekh7$42fQ&$X=-cdyndr+|C1!+(ndraTWubCu&h;hm%28p6f;Qj}g6T zx1@ATsIgK|PrIE@$jeN1`j8~*WrkGK?R?UMn0^i!I-l}=P4kySxrL1fe? z>Mcq0Zs*fpHrhsRTj^Mk@Xw&nc$E%Fi*C}C&(m(I12^5y3#`daMzzgB4xg~=)R2%> zw5Yz~HJT#lc0TL9G}Oyug1u#`#+*_GP6!?aMs(6uxATum0<(gwt-)EC=0#3}(~{gN z1$p8y`qyn*&h0N!6dbQcoNOHyT(wK_gaXT-1irSL=`JRD{o3|V-5|C`ToiFx6 z*~sjw87<>9&&*oAkkj^}LfIhOyUv%q#Q{t^;PM)!7TnHD6rvhVF<oh z1jAz-nI3^=ORTvj*`T1Gs2=UnrUVFLu7^ zoyY1;$O_nXJKyr2lyO7I#|q?jSYb?e)9;2)=o$qmTapse5M+1R3c(|Q9tuTh9#lAa>h}(InkY*1+QWWc@ z4u4an_?Yw(r11VRea7fDBDcy2 zR`9F?+9Zu+meqn_Wa5;{$BNte$pIRikri0XG=nP2Aw?d@S0}V?HdvI|9hcZ|a?5F3 z-uB+%yESZpSzY%Z4(8fHU8a>^|w6JXBw!=}kQ81?1dW?uwOmY~)e};Qz@mNIx$JARzNU8n6nLKujnvZkj zQg?#WxKhA|52FK!j3E(6e1dYBr+%@!aSYT58<{Z`A901*GDcp(UkM;pw|hU(=jY9V zacEWyp00@)gKG{SSm*Y+{;+oo=0pBg`xEC58IFR0*}w2%+>X-2ylZ(toVZXo4V3f7p<^bUL%rq;s7T^5!A-5k zJ&wdM@X!E`u8O`f=DmC67~o<@b}0PgAGIVb^JMJ~cer{Ne^-w@>x!un) z5!5)6gy(sI)cqWVsF__AoaC|Sc12k!J`F3 z3=%#WR6dKB9d^54I%o%OxZRhCyt=79+`B+Hp)wx8v3m?sTP>X*lVb0u-R_rZ0o^ei zce}c;(QQ>lBqBpv)n&i$)#qJH5RiY=U0l}es$W;mMb?e(SJ+Ufb*0hr*wU5B1-JWE zKifO%by>hr!XYL)Q}|{={TZ)ydtHp5gOTpb9thYh7+sMTLLHWS@OjMbe*FM{2YXL= z>#8$2HW99Ywz)svw0cYE)HTj8P5u*4^&E`DG&;Lwa>e<0uOkbkYi2I8(CO zEMu{vd)FAR+>G#c*66Q~3Ccz7=G|_~3(bITvkTrGH$@hCb4qK{uk*dCfNiPCOWf5YwrlieUM0s?zHkbusl}$-Z6D+;8MKsJbGzRT z^u>fwM^02J)>i%<{xd~2w1S5B!_nyb%pTvFIj*}F&#$a*A&g0rsxp}=p`u>Kp_-wQ zBtxsrfkr^Lrbi|7)EmYG8k0y$LS5^Yc><$)oOfkR;L{E!>iqw>iAxQ3fO8XA(N#4| zWtrWyaqOhq{dZ=7V&{Y+CaJ#=(45eAyWin$^nF^tzs_#xVhD?GQ2)C0=d2}yRniB# zj@Lq*>*;!=B=}$wHE68CNsK5Ls&CS=vA6h%oH@x--~BEptqO>1Rc~&@ySV50otL%Z zBC}#}5zWvGSngLVA_B>ASA)f$_pp~VySI6t+Wj7@y2!zV>R5r(Sw7B_xESJPb%hqs zu6QZA-S2xDJ1$3g0~8)M=XQJ3lQISrw^`xG56F|n?qZsztlam?B{NEZK}cyBNeoPK za?|}GBVHqdtcqI+mcy1M6Vx=?&{a{D`W#Vcn4I`_f8i;zJR4V`!M(B8DET+BkZbfF2-pFsh3<+Y9a^mS4v^Asa+(Gl*&kl`t>AeJP~# z80g21A^lxXklcs(Wg5kcYAmL8qczu4WN=6O3*;D1l9m{ET(p>g9x|i5}Y>!RB)m$tyXH zRZBQ~j{#l5aXp%KyRS0xbRo1YzBVYN3+dXn%4tY!7%|mMHjxdkU~TvfbyE2u#)$Ya zqkrpH;FccU1$cjsR1l5-*r-}>6`JdEI{n~wf5-06B34|tjKnRCud)SWa$W3kn1A5m zCGRO6yQsg3VBj(XIVswm#!eoEf)Tw{1pgkMGtj$}YM%8s1UZMZkFlg2|9kReP3&0k zrm&_IETAf>74X7OcOECpVzUm*2nRl+5lvXsAJa*)2ua{;B(|Cy(trI0OjLAVEb_Ce zLcuE!E~X#qX{0jP2%;83Im~-6YU1bIjMS|b!1y^viH$RIG3C46Kd^mMVDC~kCmpN# z9uPR@p~FX|We(c`=33Fn^ESAI%lhjso?MZ7NcB0yo-0s5e-OvRD8#WmjvGndCiqjj vO^B4S8o^MK5M$I$AEx{mkLivt-1A!q3+FvNJo6k+>EPVH=YHoN#h?9eb!_EG literal 0 HcmV?d00001 diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Stargate.app/Resources/OffOn.pic b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Stargate.app/Resources/OffOn.pic new file mode 100755 index 0000000000000000000000000000000000000000..0d28f6f6ae3fd20557b078e57edcd9a3eab9455f GIT binary patch literal 17083 zcma)jNp~FSbtP{^W&%0S^E_t+<|zS^07w8NKyW5AlVlaEB-M(W>c0SzvTVtkRdQRB zC0Q6`NiGf6Y*|vt=eRdo^{%`3vFeR`;aYU*^f`9l8&RVr9sATdMKUwu``-74`|f-1 z%R76A`+-+%oICgboa1M~XLvT9FfUy>O!{{A53Z$gJ!2eP%bHiN?niMD!{xZSb?NeR zJU1-=!qW1}sy#Kou&BR`FhUvCF~+Gzpx$Wq`c~6ub>n8KTuq>oL@j0J3&o64u4H|= ze6f@>s;#_PZg&d)T)oxq6a$4C|Lm6hjb^7iDEr!-UcchwXWgo=+3E~xzDl(_s0XS| zJ~fzNl4$rBSJyT!HL)2qdi{{u>kq>IdSfs%8!?)_DEcw8H<*hX)_lU?!njbzMg?nC ztkkevH!1c2gGdwkmOqiYHyXF`AcI)Pj20etjr^nDR4AOwA3l1aKOGFklG($_iv$03 zyf}VbMAGsHVv`3i*?#}c$%B_ISQZsvm_C1Cicg0l(O5i@Or1aB*{M`_ko|-94)I;M(E!8#iy=zH|59{n2>x;NhbeUVQx0%OCpiM_w@w|5J

^I|ZZc~^wOd!L3L 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 Rlz!{SQO!;am*Q-F{eM2DMJfOQ literal 0 HcmV?d00001 diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Stargate.app/Resources/OnOff.pic b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Stargate.app/Resources/OnOff.pic new file mode 100755 index 0000000000000000000000000000000000000000..75cde429c02ddcfd8a568c25c08bf28e27249797 GIT binary patch literal 18443 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 literal 0 HcmV?d00001 diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/TurretControl.app/Main.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/TurretControl.app/Main.lua new file mode 100755 index 00000000..3e74fb08 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/TurretControl.app/Main.lua @@ -0,0 +1,291 @@ + +local component = require("component") +local buffer = require("doubleBuffering") +local image = require("image") +local event = require("event") +local ecs = require("ECSAPI") +local serialization = require("serialization") +local unicode = require("unicode") + +------------------------------------------------------------------------------------------------------------------------------------- + +-- /MineOS/Applications/TurretControl.app/TurretControl.lua + +buffer.flush() +local pathToTurretPicture = "MineOS/Applications/TurretControl.app/Resources/Turret.pic" +local turretImage = image.load(pathToTurretPicture) +local turrets = {} +local proxies = {} + +local turretConfig = { + turretsOn = false, + attacksNeutrals = false, + attacksPlayers = false, + attacksMobs = false, +} + +local yTurrets = 2 +local spaceBetweenTurretsHorizontal = 2 +local spaceBetweenTurretsVertical = 1 +local turretHeight = turretImage[2] + 12 +local turretWidth = turretImage[1] + 8 +local countOfTurretsCanBeShowByWidth = math.floor(buffer.getWidth() / (turretWidth + spaceBetweenTurretsHorizontal)) +local xTurrets = math.floor(buffer.getWidth() / 2 - (countOfTurretsCanBeShowByWidth * (turretWidth + spaceBetweenTurretsHorizontal)) / 2 ) + math.floor(spaceBetweenTurretsHorizontal / 2) + +local yellowColor = 0xFFDB40 + +------------------------------------------------------------------------------------------------------------------------------------- + +--Объекты +local obj = {} +local function newObj(class, name, ...) + obj[class] = obj[class] or {} + obj[class][name] = {...} +end + +local function getProxiesOfAllComponents(name) + for address in component.list(name) do + table.insert(proxies, component.proxy(address)) + end +end + +local function getTurrets() + + turretConfig.turretsOn = false + turretConfig.attacksNeutrals = false + turretConfig.attacksPlayers = false + turretConfig.attacksMobs = false + + getProxiesOfAllComponents("tierOneTurretBase") + getProxiesOfAllComponents("tierTwoTurretBase") + getProxiesOfAllComponents("tierThreeTurretBase") + getProxiesOfAllComponents("tierFourTurretBase") + getProxiesOfAllComponents("tierFiveTurretBase") + + for i = 1, #proxies do + -- print(proxies[i].type) + -- ecs.error(proxies[i].type) + if type(proxies[i].getCurrentEnergyStorage()) ~= "string" then + local turret = {} + turret.type = proxies[i].type + -- turret.isActive = (proxies[i].isAttacksPlayers() and proxies[i].isAttacksMobs()) and true or false + turret.energyPercent = math.ceil(proxies[i].getCurrentEnergyStorage() / proxies[i].getMaxEnergyStorage() * 100) + turret.proxy = proxies[i] + table.insert(turrets, turret) + + turret.isActive = false + turret.proxy.setAttacksNeutrals(false) + turret.proxy.setAttacksPlayers(false) + turret.proxy.setAttacksMobs(false) + end + end +end + +local function progressBar(x, y, width, height, background, foreground, percent) + buffer.square(x, y, width, height, background, 0x0, " ") + buffer.frame(x, y, width, height, foreground) + width = width - 2 + local cykaWidth = math.ceil(width * percent / 100) + buffer.text(x + 1, y + 1, foreground, string.rep("▒", cykaWidth)) +end + +local function drawTurrets(y) + local counter = 0 + local x = xTurrets + + if #turrets <= 0 then + local text = "Подключите турели из мода OpenModularTurrets" + local x = math.floor(buffer.getWidth() / 2 - unicode.len(text) / 2) + buffer.text(x, math.floor(buffer.getHeight() / 2 - 2), yellowColor, text) + return + end + + for turret = 1, #turrets do + local yPos = y + buffer.frame(x, yPos, turretWidth, turretHeight, yellowColor) + yPos = yPos + 1 + buffer.text(x + 2, yPos, yellowColor, ecs.stringLimit("end", "Турель " .. turrets[turret].proxy.address, turretWidth - 4)) + yPos = yPos + 2 + buffer.image(x + 4, yPos, turretImage) + yPos = yPos + turretImage[2] + 1 + buffer.text(x + 2, yPos, yellowColor, "Энергия:") + yPos = yPos + 1 + progressBar(x + 1, yPos, turretWidth - 2, 3, 0x000000, yellowColor, turrets[turret].energyPercent) + yPos = yPos + 4 + local widthOfButton = 13 + -- local isActive = turrets[turret].getActive() + local isActive = turrets[turret].isActive + newObj("TurretOn", turret, buffer.button(x + 2, yPos, widthOfButton, 1, isActive and yellowColor or 0x000000, isActive and 0x000000 or yellowColor, "ВКЛ")) + obj.TurretOn[turret].proxy = turrets[turret].proxy + newObj("TurretOff", turret, buffer.button(x + 2 + widthOfButton + 2, yPos, widthOfButton, 1, not isActive and yellowColor or 0x000000, not isActive and 0x000000 or yellowColor, "ВЫКЛ")) + yPos = yPos + 1 + + x = x + turretWidth + spaceBetweenTurretsHorizontal + counter = counter + 1 + if counter % countOfTurretsCanBeShowByWidth == 0 then + x = xTurrets + y = y + turretHeight + spaceBetweenTurretsVertical + end + end +end + +local function drawSeparator(y) + buffer.text(1, y, yellowColor, string.rep("─", buffer.getWidth())) +end + +local function drawButtonWithState(x, y, width, height, text, state) + if state then + buffer.button(x, y, width, height, yellowColor, 0x000000, text) + else + buffer.framedButton(x, y, width, height, 0x000000, yellowColor, text) + end + + return (x + width + 1) +end + +local function drawBottomBar() + local height = 6 + local y = buffer.getHeight() - height + 1 + buffer.square(1, y, buffer.getWidth(), height, 0x000000, yellowColor, " ") + drawSeparator(y) + local text = " ECS® Security Systems™ " + local x = math.floor(buffer.getWidth() / 2 - unicode.len(text) / 2) + buffer.text(x, y, yellowColor, text) + + y = y + 2 + + local widthOfButton = 19 + local totalWidth = (widthOfButton + 2) * 6 + local x = math.floor(buffer.getWidth() / 2 - totalWidth / 2) + 1 + + newObj("BottomButtons", "On", x, y, x + widthOfButton - 1, y + 2) + x = drawButtonWithState(x, y, widthOfButton, 3, turretConfig.turretsOn and "Турели ВКЛ" or "Турели ВЫКЛ", turretConfig.turretsOn) + newObj("BottomButtons", "AddPlayer", x, y, x + widthOfButton - 1, y + 2) + x = drawButtonWithState(x, y, widthOfButton, 3, "Добавить игрока", false) + newObj("BottomButtons", "AttacksMobs", x, y, x + widthOfButton - 1, y + 2) + x = drawButtonWithState(x, y, widthOfButton, 3, "Атака мобов", turretConfig.attacksMobs) + newObj("BottomButtons", "AttacksNeutrals", x, y, x + widthOfButton - 1, y + 2) + x = drawButtonWithState(x, y, widthOfButton, 3, "Атака нейтралов", turretConfig.attacksNeutrals) + newObj("BottomButtons", "AttacksPlayers", x, y, x + widthOfButton - 1, y + 2) + x = drawButtonWithState(x, y, widthOfButton, 3, "Атака игроков", turretConfig.attacksPlayers) + newObj("BottomButtons", "Exit", x, y, x + widthOfButton - 1, y + 2) + x = drawButtonWithState(x, y, widthOfButton, 3, "Выход", false) +end + +local function drawAll() + buffer.clear(0x000000) + drawTurrets(yTurrets) + drawBottomBar() + buffer.draw() +end + +local function refresh() + turrets = {} + proxies = {} + getTurrets() + drawAll() +end + +local function changeTurretState(i, state) + turrets[i].isActive = state + turrets[i].energyPercent = math.ceil(turrets[i].proxy.getCurrentEnergyStorage() / turrets[i].proxy.getMaxEnergyStorage() * 100) + if state == true then + turrets[i].proxy.setAttacksNeutrals(turretConfig.attacksNeutrals) + turrets[i].proxy.setAttacksPlayers(turretConfig.attacksPlayers) + turrets[i].proxy.setAttacksMobs(turretConfig.attacksMobs) + else + turrets[i].proxy.setAttacksNeutrals(false) + turrets[i].proxy.setAttacksPlayers(false) + turrets[i].proxy.setAttacksMobs(false) + end +end + +------------------------------------------------------------------------------------------------------------------------------------- + +refresh() + +while true do + local e = {event.pull()} + if e[1] == "touch" then + if obj.TurretOn then + for key in pairs(obj.TurretOn) do + if ecs.clickedAtArea(e[3], e[4], obj.TurretOn[key][1], obj.TurretOn[key][2], obj.TurretOn[key][3], obj.TurretOn[key][4]) then + changeTurretState(key, true) + drawAll() + break + end + end + end + + if obj.TurretOff then + for key in pairs(obj.TurretOff) do + if ecs.clickedAtArea(e[3], e[4], obj.TurretOff[key][1], obj.TurretOff[key][2], obj.TurretOff[key][3], obj.TurretOff[key][4]) then + changeTurretState(key, false) + drawAll() + break + end + end + end + + for key in pairs(obj.BottomButtons) do + if ecs.clickedAtArea(e[3], e[4], obj.BottomButtons[key][1], obj.BottomButtons[key][2], obj.BottomButtons[key][3], obj.BottomButtons[key][4]) then + if key == "On" then + turretConfig.turretsOn = not turretConfig.turretsOn + for i = 1, #turrets do changeTurretState(i, turretConfig.turretsOn) end + drawAll() + elseif key == "AttacksNeutrals" then + turretConfig.attacksNeutrals = not turretConfig.attacksNeutrals + for i = 1, #turrets do changeTurretState(i, turrets[i].isActive) end + drawAll() + elseif key == "AttacksMobs" then + turretConfig.attacksMobs = not turretConfig.attacksMobs + for i = 1, #turrets do changeTurretState(i, turrets[i].isActive) end + drawAll() + elseif key == "AttacksPlayers" then + turretConfig.attacksPlayers = not turretConfig.attacksPlayers + for i = 1, #turrets do changeTurretState(i, turrets[i].isActive) end + drawAll() + elseif key == "AddPlayer" then + buffer.button(obj.BottomButtons[key][1], obj.BottomButtons[key][2], 19, 3, yellowColor, 0x000000, "Добавить игрока") + buffer.draw() + os.sleep(0.2) + drawAll() + local data = ecs.universalWindow("auto", "auto", 30, 0x1e1e1e, true, {"EmptyLine"}, {"CenterText", ecs.colors.orange, "Добавить игрока"}, {"EmptyLine"}, {"Input", 0xFFFFFF, ecs.colors.orange, "Никнейм"}, {"EmptyLine"}, {"Button", {ecs.colors.orange, 0xffffff, "OK"}, {0x999999, 0xffffff, "Отмена"}} ) + if data[2] == "OK" then for i = 1, #turrets do turrets[i].proxy.addTrustedPlayer(data[1]) end end + elseif key == "Exit" then + buffer.button(obj.BottomButtons[key][1], obj.BottomButtons[key][2], 19, 3, yellowColor, 0x000000, "Выход") + buffer.draw() + os.sleep(0.2) + buffer.clear(0x262626) + ecs.prepareToExit() + return + end + + break + end + end + + elseif e[1] == "scroll" then + if e[5] == 1 then + yTurrets = yTurrets + 2 + else + yTurrets = yTurrets - 2 + end + drawAll() + elseif e[1] == "component_added" or e[1] == "component_removed" then + if e[3] == "tierOneTurretBase" or e[3] == "tierTwoTurretBase" or e[3] == "tierThreeTurretBase" or e[3] == "tierFourTurretBase" or e[3] == "tierFiveTurretBase" then + refresh() + end + end +end + + + + + + + + + + + diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/TurretControl.app/Resources/About/Russian.txt b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/TurretControl.app/Resources/About/Russian.txt new file mode 100755 index 00000000..9afc9140 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/TurretControl.app/Resources/About/Russian.txt @@ -0,0 +1 @@ +Приложение, предназначенное для управления турелями из мода OpenModularTurrets. \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/TurretControl.app/Resources/Icon.pic b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/TurretControl.app/Resources/Icon.pic new file mode 100755 index 0000000000000000000000000000000000000000..9effbea446d1127e7f9d3c8833ba0d72cbfa90f7 GIT binary patch literal 152 zcmW-ZxeWp_6h#00!~SJ#yrK&vB%%#CL^((-IgwkSFE;{XNAu>;Y{&Ddr0Y+7fj|@q zHIGu$os_+OnBSqQ*4s{A;p(-w7x~5sJVe+%ZFD?XSs3bfIu7?3XvjA%6kO*;l*h2V OJxujjn2Rckq5J_otrF${ literal 0 HcmV?d00001 diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/TurretControl.app/Resources/Turret.pic b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/TurretControl.app/Resources/Turret.pic new file mode 100755 index 0000000000000000000000000000000000000000..46b99f7f80f8ed346eae0496111224667507093d GIT binary patch literal 324 zcmY*VL2iRU4D{IET_^#T5MYnFg7`(HN;&n?2US(xkO$@?sTWcyJ&dKXXFcQne7}77 z6!<}Mzg= zone[1] and y >= zone[2] and x <= zone[3] and y <= zone[4] then + return true + end + return false +end + +--Интерфейс логина в аккаунт ВК, постараюсь сделать пографонистей +--Хотя хах! Кого я обманываю, ага +local function loginGUI(startUsername, startPassword) + local background = 0x002440 + local buttonColor = 0x666DFF + local textColor = 0x262626 + local username, password = startUsername, startPassword + + local textFieldWidth = 50 + local textFieldHeight = 3 + local x, y = math.floor(buffer.getWidth() / 2 - textFieldWidth / 2), math.floor(buffer.getHeight() / 2) + + local obj = {} + obj.username = {x, y, x + textFieldWidth - 1, y + 2}; y = y + textFieldHeight + 1 + obj.password = {x, y, x + textFieldWidth - 1, y + 2}; y = y + textFieldHeight + 1 + obj.button = GUI.button(x, y, textFieldWidth, textFieldHeight, buttonColor, 0xEEEEEE, 0xEEEEEE, buttonColor, "Войти") + + local VKLogoImage = image.load(VKLogoImagePath) + local loginTextBox = GUI.input(x, obj.username[2], textFieldWidth, 3, 0xEEEEEE, 0x777777, 0x777777, 0xEEEEEE, 0x262626, username, "E-Mail", false) + loginTextBox.parent = { draw = function() loginTextBox:draw() end } + loginTextBox.getFirstParent = function() return loginTextBox.parent end + + local passwordTextBox = GUI.input(x, obj.password[2], textFieldWidth, 3, 0xEEEEEE, 0x777777, 0x777777, 0xEEEEEE, 0x262626, password, "Password", false, "*") + passwordTextBox.parent = { draw = function() passwordTextBox:draw() end } + passwordTextBox.getFirstParent = function() return passwordTextBox.parent end + + + local function draw() + buffer.clear(colors.loginGUIBackground) + + buffer.image(x + 5, obj.username[2] - 15, VKLogoImage) + loginTextBox:draw() + passwordTextBox:draw() + obj.button:draw() + + buffer.draw() + end + + while true do + draw() + local e = {event.pull()} + if e[1] == "touch" then + if clickedAtZone(e[3], e[4], obj.username) then + loginTextBox.eventHandler({draw = function() loginTextBox:draw() end}, loginTextBox, {[1] = "touch", [3] = loginTextBox.x, [4] = loginTextBox.y}) + username = loginTextBox.text + + elseif clickedAtZone(e[3], e[4], obj.password) then + passwordTextBox.eventHandler({draw = function() passwordTextBox:draw() end}, passwordTextBox, {[1] = "touch", [3] = passwordTextBox.x, [4] = passwordTextBox.y}) + password = passwordTextBox.text + + elseif obj.button:isClicked(e[3], e[4]) then + obj.button:press(0.2) + draw() + local success, loginData = getLoginDataRequest(username or "", password or "") + if success then + if settings.saveAuthData then settings.username = username; settings.password = password; saveSettings() end + loginData.username = username + loginData.password = password + return loginData + else + GUI.error("Ошибка авторизации: " .. tostring(loginData)) + end + end + end + end +end + +---------------------------------------------------- GUI для взаимодействия с VK API ---------------------------------------------- + +local function drawPersonalAvatar(x, y, width, height) + drawAvatar(x, y, width, height, personalInfo.id, unicode.sub(personalInfo.first_name, 1, 1) .. unicode.sub(personalInfo.last_name, 1, 1)) +end + +local function status(text) + buffer.square(mainZoneX, buffer.getHeight(), mainZoneWidth, 1, colors.statusBar, 0x0, " ") + buffer.text(mainZoneX + 1, buffer.getHeight(), colors.statusBarText, text) + buffer.draw() +end + +local function drawTopBar(text) + buffer.square(mainZoneX, 1, mainZoneWidth, 3, colors.topBar, 0x0, " ") + local x = math.floor(mainZoneX + mainZoneWidth / 2 - unicode.len(text) / 2 - 1) + buffer.text(x, 2, colors.topBarText, text) +end + +--Рисуем главную зону +local function clearGUIZone() + buffer.square(mainZoneX, mainZoneY, mainZoneWidth, mainZoneHeight, colors.mainZone, 0x0, " ") +end + +local function drawEmptyCloud(x, y, cloudWidth, cloudHeight, cloudColor, fromYou) + local upperPixel = "▀" + local lowerPixel = "▄" + + --Рисуем финтифлюшечки + if not fromYou then + buffer.set(x, y - cloudHeight + 2, colors.mainZone, cloudColor, upperPixel) + buffer.set(x + 1, y - cloudHeight + 2, cloudColor, 0xFFFFFF, " ") + x = x + 2 + else + buffer.set(x + cloudWidth + 3, y - cloudHeight + 2, colors.mainZone, cloudColor, upperPixel) + buffer.set(x + cloudWidth + 2, y - cloudHeight + 2, cloudColor, 0xFFFFFF, " ") + end + + --Заполняшечки + buffer.square(x + 1, y - cloudHeight + 1, cloudWidth, cloudHeight, cloudColor, 0xFFFFFF, " ") + buffer.square(x, y - cloudHeight + 2, cloudWidth + 2, cloudHeight - 2, cloudColor, 0xFFFFFF, " ") + + --Сгругленные краешки + buffer.set(x, y - cloudHeight + 1, colors.mainZone, cloudColor, lowerPixel) + buffer.set(x + cloudWidth + 1, y - cloudHeight + 1, colors.mainZone, cloudColor, lowerPixel) + buffer.set(x, y, colors.mainZone, cloudColor, upperPixel) + buffer.set(x + cloudWidth + 1, y, colors.mainZone, cloudColor, upperPixel) + + return y - cloudHeight + 1 +end + +local function drawTextCloud(x, y, cloudColor, textColor, fromYou, text) + local y = drawEmptyCloud(x, y, cloudWidth, #text + 2, cloudColor, fromYou) + x = fromYou and x + 2 or x + 4 + + for i = 1, #text do + buffer.text(x, y + i, textColor, text[i]) + end + + return y +end + +local function getAttachments(messageArray) + local text = "Вложения: " + for j = 1, #messageArray.attachments do + if messageArray.attachments[j].type == "sticker" then + text = text .. "стикер, " + elseif messageArray.attachments[j].type == "photo" then + text = text .. "фото, " + elseif messageArray.attachments[j].type == "video" then + text = text .. "видео, " + elseif messageArray.attachments[j].type == "audio" then + text = text .. "аудио, " + elseif messageArray.attachments[j].type == "wall" then + text = text .. "запись на стене, " + end + end + text = unicode.sub(text, 1, -3) + + return text +end + +local function drawMessageInputBar(currentText) + local x, y = mainZoneX, buffer.getHeight() - 5 + buffer.square(x, y, mainZoneWidth, 5, colors.messageInputBarColor, 0x0, " ") + obj.messageInputBar = GUI.input(x + 2, y + 1, mainZoneWidth - 4, 3, 0xFFFFFF, 0x444444, 0x444444, 0xFFFFFF, 0x262626, nil, "Введите сообщение", true) + obj.messageInputBar.parent = { draw = function() obj.messageInputBar:draw() end } + obj.messageInputBar.getFirstParent = function() return obj.messageInputBar.parent end + obj.messageInputBar:draw() +end + +local function getUserNamesFromTheirIDs(IDsArray) + local success, usersData = usersInformationRequest(table.unpack(IDsArray)) + local userNames = {} + if success and usersData.response then + for i = 1, #usersData.response do + userNames[usersData.response[i].id] = { + first_name = usersData.response[i].first_name, + last_name = usersData.response[i].last_name, + } + end + end + return success, userNames +end + +local function messagesGUI() + + status("Загружаю историю переписки") + local success, messages = getMessagesRequest(currentMessagesPeerID, messageToShowFrom - 1, countOfMessagesToLoadFromServer) + if success and messages.response then + + whatIsOnScreen = "messages" + + if currentMessagesPeerID > 2000000000 then + status("Загружаю имена пользователей из переписки (актуально для конференций)") + + local IDsArray = {}; + for i = 1, #messages.response.items do table.insert(IDsArray, messages.response.items[i].user_id) end + local userNamesSuccess, userNames = getUserNamesFromTheirIDs(IDsArray) + for i = 1, #messages.response.items do + messages.response.items[i].first_name = userNames[messages.response.items[i].user_id].first_name or "N/A" + messages.response.items[i].last_name = userNames[messages.response.items[i].user_id].last_name or "N/A" + end + IDsArray = nil + end + + clearGUIZone() + drawTopBar("Сообщения") + + -- saveToFile("lastMessagesRequest.json", serialization.serialize(messages)) + + buffer.setDrawLimit(mainZoneX, mainZoneY, mainZoneX + mainZoneWidth - 1, mainZoneY + mainZoneHeight - 1) + + local y = buffer.getHeight() - 7 + local xSender = mainZoneX + 2 + local xYou = buffer.getWidth() - 7 + + for i = 1, #messages.response.items do + + local messageTextArray = {} + + --Если строка пиздатая + if messages.response.items[i].body ~= "" then table.insert(messageTextArray, optimizeStringForWrongSymbols(messages.response.items[i].body)) end + if messages.response.items[i].fwd_messages then table.insert(messageTextArray, "Пересланные сообщения") end + if messages.response.items[i].attachments then table.insert(messageTextArray, getAttachments(messages.response.items[i])) end + if messages.response.items[i].action == "chat_invite_user" then table.insert(messageTextArray, "Пользователь под ID " .. messages.response.items[i].from_id .. " пригласил в беседу пользователя под ID " .. messages.response.items[i].action_mid) end + + messageTextArray = string.wrap(messageTextArray, cloudWidth - 4) + local peerID = getPeerIDFromMessageArray(messages.response.items[i]) + + --Делаем дату пиздатой + -- messages.response.items[i].date = os.date("%d.%m.%y в %X", messages.response.items[i].date) + messages.response.items[i].date = os.date("%H:%M", messages.response.items[i].date) + + if messages.response.items[i].out == 1 then + y = drawTextCloud(xYou - cloudWidth - 6, y, colors.yourCloudColor, colors.yourCloudTextColor, true, messageTextArray) + drawPersonalAvatar(xYou, y, 6, 3) + buffer.text(xYou - cloudWidth - unicode.len(messages.response.items[i].date) - 8, y + 1, colors.dateTime, messages.response.items[i].date) + else + y = drawTextCloud(xSender + 8, y, colors.senderCloudColor, colors.senderCloudTextColor, false, messageTextArray) + drawAvatar(xSender, y, 6, 3, peerID, messages.response.items[i].first_name and (unicode.sub(messages.response.items[i].first_name, 1, 1) .. unicode.sub(messages.response.items[i].last_name, 1, 1)) or currentMessagesAvatarText) + buffer.text(xSender + cloudWidth + 14, y + 1, colors.dateTime, messages.response.items[i].date) + end + + y = y - 2 + end + + local currentText + + -- Создаем объект тырканья + drawMessageInputBar() + status("История переписки загружена, ожидаю ввода сообщения") + buffer.resetDrawLimit() + end +end + +local function drawDialog(y, dialogBackground, avatarID, avatarText, text1, text2, text3) + --Рисуем подложку под диалог нужного цвета + buffer.square(mainZoneX, y, mainZoneWidth, 5, dialogBackground, 0x0, " ") + --Рисуем аватарку, чо уж + drawAvatar(mainZoneX + 2, y + 1, 6, 3, avatarID, avatarText) + --Пишем все, что нужно + y = y + 1 + if text1 then buffer.text(mainZoneX + 10, y, 0x000000, text1); y = y + 1 end + if text2 then buffer.text(mainZoneX + 10, y, 0x555555, text2); y = y + 1 end + if text3 then buffer.text(mainZoneX + 10, y, 0x666666, text3); y = y + 1 end +end + +local function dialogsGUI() + + local success, dialogs = getDialogsRequest(dialogToShowFrom - 1, countOfDialogsToLoadFromServer) + if success and dialogs.response then + + whatIsOnScreen = "dialogs" + + obj.dialogList = {} + + clearGUIZone() + drawTopBar("Сообщения") + + --Ебашим КНОПАЧКИ спама + obj.crazyTypingButton = GUI.adaptiveButton(mainZoneX + 2, 2, 1, 0, 0xFFFFFF, colors.topBar, 0xAAAAAA, 0x000000, "CrazyTyping") + -- obj.spamButton = {buffer.adaptiveButton(obj.crazyTypingButton[3] + 2, 2, 1, 0, 0xFFFFFF, colors.topBar, "Спам")} + + --НУ ТЫ ПОНЯЛ, АГА + status("Получаю имена пользователей по ID") + local IDsArray = {} + for i = 1, #dialogs.response.items do + if not dialogs.response.items[i].message.chat_id and dialogs.response.items[i].message.user_id and dialogs.response.items[i].message.user_id > 0 then + table.insert(IDsArray, dialogs.response.items[i].message.user_id) + end + end + local userNamesSuccess, userNames = getUserNamesFromTheirIDs(IDsArray) + for i = 1, #dialogs.response.items do + if not dialogs.response.items[i].message.chat_id and dialogs.response.items[i].message.user_id and dialogs.response.items[i].message.user_id > 0 then + dialogs.response.items[i].message.title = userNames[dialogs.response.items[i].message.user_id].first_name or "N/A" .. " " .. userNames[dialogs.response.items[i].message.user_id].last_name or "" + end + end + + local y = mainZoneY + local avatarText = "" + local peerID + local color + + for i = 1, #dialogs.response.items do + --Ебемся с цветами + if dialogs.response.items[i].unread then + if i % 2 == 0 then + color = 0xCCDBFF + else + color = 0xCCDBFF + end + else + if i % 2 == 0 then + color = 0xEEEEEE + else + color = 0xFFFFFF + end + end + + avatarText = unicode.sub(dialogs.response.items[i].message.title, 1, 2) + peerID = getPeerIDFromMessageArray(dialogs.response.items[i].message) + + --Ебля с текстом диалога + local text1 = dialogs.response.items[i].message.title + local text2 + local text3 + + --Если это банальное сообщение + if dialogs.response.items[i].message.body and dialogs.response.items[i].message.body ~= "" then + text2 = optimizeStringForWrongSymbols(dialogs.response.items[i].message.body) + end + + --Если есть какие-либо пересланные сообщения, то + if dialogs.response.items[i].message.fwd_messages then + text3 = "Пересланные сообщения" + --Если есть какие-либо вложения, то + elseif dialogs.response.items[i].message.attachments then + text3 = getAttachments(dialogs.response.items[i].message) + end + + --Рисуем диалог + drawDialog(y, color, peerID, avatarText, text1, text2, text3) + + --Рисуем пиздюлинку, показывающую кол-во непрочитанных сообщений + if dialogs.response.items[i].unread and dialogs.response.items[i].unread ~= 0 then + local cyka = tostring(dialogs.response.items[i].unread) + local cykaWidth = unicode.len(cyka) + 2 + local cykaX = buffer.getWidth() - cykaWidth - 2 + buffer.square(cykaX, y + 2, cykaWidth, 1, ecs.colors.blue, 0x0, " ") + buffer.text(cykaX + 1, y + 2, 0xFFFFFF, cyka) + end + + obj.dialogList[i] = GUI.object(mainZoneX, y, mainZoneWidth, 5) + obj.dialogList[i][5], obj.dialogList[i][6], obj.dialogList[i][7], obj.dialogList[i][8], obj.dialogList[i][9] = peerID, avatarText, text1, text2, text3 + + y = y + 5 + end + end + + status("Список диалогов получен") +end + +--Гуишка аудиозаписей +--А-А-А-А!!!!! МОЙ КРАСИВЫЙ ТРЕУГОЛЬНИЧЕК PLAY, БЛЯДЬ!!!! ШТО ТЫ ДЕЛАЕШЬ, SANGAR, ПРЕКРАТИ!!!! +local function audioGUI(ID) + status("Загружаю список аудиозаписей") + local success, audios = getAudioRequest(ID, audioToShowFrom - 1, countOfAudioToLoadFromServer) + if success and audios.response then + whatIsOnScreen = "audio" + obj.audio = {} + clearGUIZone() + drawTopBar("Аудиозаписи " .. audios.response.items[1].name_gen) + + local y = mainZoneY + local color + for i = 2, #audios.response.items do + color = 0xFFFFFF + if i % 2 == 0 then color = 0xEEEEEE end + + buffer.square(mainZoneX, y, mainZoneWidth, 5, color, 0x0, " ") + obj.audio[i] = GUI.button(mainZoneX + 2, y + 1, 5, 3, colors.audioPlayButton, colors.audioPlayButtonText, 0x66FF80, colors.audioPlayButton, ">") + obj.audio[i][5] = audios.response.items[i] + + local x = mainZoneX + 9 + buffer.text(x, y + 1, colors.audioPlayButton, audios.response.items[i].artist) + x = x + unicode.len(audios.response.items[i].artist) + buffer.text(x, y + 1, 0x000000, " - " .. audios.response.items[i].title) + + x = mainZoneX + 9 + local hours = string.format("%02.f", math.floor(audios.response.items[i].duration / 3600)) + local minutes = string.format("%02.f", math.floor(audios.response.items[i].duration / 60 - (hours * 60))) + local seconds = string.format("%02.f", math.floor(audios.response.items[i].duration - hours * 3600 - minutes * 60)) + buffer.text(x, y + 2, 0x888888, "Длительность: " .. hours .. ":" .. minutes .. ":" .. seconds) + + y = y + 5 + end + else + GUI.error("Ошибка при получении списка аудиозаписей") + end +end + +local function checkField(field) + if field and field ~= "" and field ~= " " then return true end + return false +end + +local function userProfileRequest() + --Ебашим основную инфу + status("Загружаю информацию о пользователе под ID " .. currentProfile.ID) + local profileSuccess, userProfile = usersInformationRequest(currentProfile.ID) + + --Ебашим стену + status("Загружаю содержимое стены пользователя " .. currentProfile.ID) + local wallSuccess, wall = userWallRequest(currentProfile.ID, 20, currentProfile.wallOffset) + --Получаем инфу о юзверях со стены + local userNamesSuccess, userNames + if wallSuccess and wall.response then + local IDsArray = {} + for i = 1, #wall.response.items do table.insert(IDsArray, wall.response.items[i].from_id) end + status("Загружаю имена людей, оставивших сообщения на стене пользователя " .. currentProfile.ID) + userNamesSuccess, userNames = getUserNamesFromTheirIDs(IDsArray) + IDsArray = nil + end + + --Ебашим френдсов + status("Загружаю информацию о друзьях пользователя под ID " .. currentProfile.ID) + local friendsSuccess, friends = userFriendsRequest(currentProfile.ID, countOfFriendsToDisplayInProfile, 0, "random", "nom") + + --Анализируем на пиздатость + if (profileSuccess and userProfile.response) and (wallSuccess and wall.response) and (userNamesSuccess) and (friendsSuccess and friends.response) then + -- saveToFile("lastUserProfileRequest.json", serialization.serialize(userProfile)) + currentProfile.userProfile = userProfile + currentProfile.wall = wall + currentProfile.userNames = userNames + currentProfile.friends = friends + return true + else + GUI.error("Ошибка при загрузке информации о профиле") + return false + end +end + +local function userProfileGUI() + clearGUIZone() + whatIsOnScreen = "userProfile" + drawTopBar("Страница пользователя " .. currentProfile.ID) + + buffer.setDrawLimit(mainZoneX, mainZoneY, mainZoneX + mainZoneWidth - 1, mainZoneY + mainZoneHeight - 1) + + local xAvatar, yAvatar = mainZoneX + 4, currentProfileY + local x, y = xAvatar, yAvatar + local avatarWidth = 18 + local avatarHeight = math.floor(avatarWidth / 2) + + --Рисуем авку + currentProfile.avatarText = unicode.sub(currentProfile.userProfile.response[1].first_name, 1, 1) .. unicode.sub(currentProfile.userProfile.response[1].last_name, 1, 1) + drawAvatar(x, y, avatarWidth, avatarHeight, currentProfile.ID, currentProfile.avatarText) + --Рисуем имячко и статус + x = x + avatarWidth + 4 + buffer.text(x, y, 0x000000, currentProfile.userProfile.response[1].first_name .. " " .. currentProfile.userProfile.response[1].last_name); y = y + 1 + buffer.text(x, y, 0xAAAAAA, currentProfile.userProfile.response[1].status); y = y + 2 + + --Инфааааа + local informationOffset = 20 + local informationKeyColor = 0x888888 + local informationTitleColor = 0x000000 + local informationValueColor = 0x002440 + local informationSeparatorColor = 0xCCCCCC + + local function drawInfo(x, y2, key, value) + if checkField(value) then + value = {value} + value = string.wrap(value, buffer.getWidth() - x - 4 - informationOffset) + buffer.text(x, y2, informationKeyColor, key) + for i = 1, #value do + buffer.text(x + informationOffset, y2, informationValueColor, value[i]) + y2 = y2 + 1 + end + y = y2 + end + end + + local function drawSeparator(x, y2, text) + buffer.text(x, y2, informationTitleColor, text) + buffer.text(x + unicode.len(text) + 1, y2, informationSeparatorColor, string.rep("─", buffer.getWidth() - x - unicode.len(text))) + y = y + 1 + end + + drawSeparator(x, y, "Основная информация"); y = y + 1 + + drawInfo(x, y, "Дата рождения:", currentProfile.userProfile.response[1].bdate) + if currentProfile.userProfile.response[1].city then drawInfo(x, y, "Город:", currentProfile.userProfile.response[1].city.title) end + drawInfo(x, y, "Образование:", currentProfile.userProfile.response[1].university_name) + drawInfo(x, y, "Веб-сайт", currentProfile.userProfile.response[1].site); y = y + 1 + + drawSeparator(x, y, "Контактная информация"); y = y + 1 + + drawInfo(x, y, "Мобильный телефон:", currentProfile.userProfile.response[1].mobile_phone) + drawInfo(x, y, "Домашний телефон:", currentProfile.userProfile.response[1].home_phone) + drawInfo(x, y, "Skype:", currentProfile.userProfile.response[1].skype); y = y + 1 + + drawSeparator(x, y, "Личная информация"); y = y + 1 + + drawInfo(x, y, "Интересы:", currentProfile.userProfile.response[1].interests) + drawInfo(x, y, "Деятельность:", currentProfile.userProfile.response[1].activities) + drawInfo(x, y, "Любимая музыка:", currentProfile.userProfile.response[1].music) + drawInfo(x, y, "Любимая фильмы:", currentProfile.userProfile.response[1].movies) + drawInfo(x, y, "Любимая телешоу:", currentProfile.userProfile.response[1].tv) + drawInfo(x, y, "Любимая книги:", currentProfile.userProfile.response[1].books) + drawInfo(x, y, "Любимая игры:", currentProfile.userProfile.response[1].games) + drawInfo(x, y, "О себе:", currentProfile.userProfile.response[1].about) + + -- А ВОТ И СТЕНОЧКА ПОДЪЕХАЛА НА ПРАЗДНИК ДУШИ + y = y + 1 + buffer.square(x, y, buffer.getWidth() - x - 2, 1, 0xCCCCCC, 0x0, " "); buffer.text(x + 1, y, 0x262626, "Стена"); y = y + 2 + --Перебираем всю стенку + for i = 1, #currentProfile.wall.response.items do + --Если это не репост или еще не хуйня какая-то + if currentProfile.wall.response.items[i].text ~= "" then + -- GUI.error(userNames) + drawAvatar(x, y, 6, 3, currentProfile.wall.response.items[i].from_id, unicode.sub(currentProfile.userNames[currentProfile.wall.response.items[i].from_id].first_name, 1, 1) .. unicode.sub(currentProfile.userNames[currentProfile.wall.response.items[i].from_id].last_name, 1, 1)) + buffer.text(x + 8, y, informationValueColor, currentProfile.userNames[currentProfile.wall.response.items[i].from_id].first_name .. " " .. currentProfile.userNames[currentProfile.wall.response.items[i].from_id].last_name) + local date = os.date("%d.%m.%y в %H:%M", currentProfile.wall.response.items[i].date) + buffer.text(buffer.getWidth() - unicode.len(date) - 2, y, 0xCCCCCC, date) + y = y + 1 + local text = {currentProfile.wall.response.items[i].text} + text = string.wrap(text, buffer.getWidth() - x - 10) + for i = 1, #text do + buffer.text(x + 8, y, 0x000000, text[i]) + y = y + 1 + end + y = y + 1 + if #text == 1 then y = y + 1 end + end + end + + --ПодАвочная параша + informationOffset = 13 + x, y = xAvatar, yAvatar + y = y + avatarHeight + 1 + + currentProfile.avatarWidth = avatarWidth + currentProfile.sendMessageButton = GUI.button(x, y, avatarWidth, 1, 0xCCCCCC, 0x000000, 0x888888, 0x000000,"Сообщение") + y = y + 2 + currentProfile.audiosButton = GUI.button(x, y, avatarWidth, 1, 0xCCCCCC, 0x000000, 0x888888, 0x000000, "Аудиозаписи") + y = y + 2 + + drawInfo(x, y, "Подписчики: ", tostring(currentProfile.userProfile.response[1].counters.followers)) + drawInfo(x, y, "Фотографии: ", currentProfile.userProfile.response[1].counters.photos) + drawInfo(x, y, "Видеозаписи: ", currentProfile.userProfile.response[1].counters.videos) + drawInfo(x, y, "Аудиозаписи: ", currentProfile.userProfile.response[1].counters.audios) + + --Друзяшки, ЕПТАААААА, АХАХАХАХАХАХАХАХАХА + y = y + 1 + buffer.square(x, y, avatarWidth, 1, 0xCCCCCC, 0x0, " "); buffer.text(x + 1, y, 0x262626, "Друзья (" .. currentProfile.userProfile.response[1].counters.friends .. ")"); y = y + 2 + local xPos, yPos = x + 1, y + local count = 1 + for i = 1, #currentProfile.friends.response.items do + drawAvatar(xPos, yPos, 6, 3, currentProfile.friends.response.items[i].id, unicode.sub(currentProfile.friends.response.items[i].first_name, 1, 1) .. unicode.sub(currentProfile.friends.response.items[i].last_name, 1, 1)) + buffer.text(xPos - 1, yPos + 3, 0x000000, ecs.stringLimit("end", currentProfile.friends.response.items[i].first_name .. " " .. currentProfile.friends.response.items[i].last_name, 8)) + xPos = xPos + 10 + if i % 2 == 0 then xPos = x + 1; yPos = yPos + 5 end + count = count + 1 + if count > countOfFriendsToDisplayInProfile then break end + end + + buffer.resetDrawLimit() +end + +local function loadAndShowProfile(ID) + currentProfileY = mainZoneY + 2 + currentProfile = {ID = ID, wallOffset = 0} + if userProfileRequest() then userProfileGUI(currentProfile.ID) end +end + +local function friendsGUI() + status("Загружаю список друзей") + local success, friends = userFriendsRequest(personalInfo.id, countOfFriendsToGetOnFriendsTab, currentFriendsOffset, "hints", "nom") + status("Загружаю список категорий друзей") + local successLists, friendsLists = userFriendsListsRequest(personalInfo.id) + if (success and friends.response) and (successLists and friendsLists.response) then + -- saveToFile("lastFriendsResponse.json", serialization.serialize(friends)) + clearGUIZone() + currentFriends = {sendMessageButtons = {}, openProfileButtons = {}} + whatIsOnScreen = "friends" + drawTopBar("Друзья") + buffer.setDrawLimit(mainZoneX, mainZoneY, mainZoneX + mainZoneWidth - 1, mainZoneY + mainZoneHeight - 1) + + local function getListName(listID) + local name = "N/A" + for i = 1, #friendsLists.response.items do + if friendsLists.response.items[i].id == listID then + name = friendsLists.response.items[i].name + break + end + end + return name + end + + local x, y = mainZoneX + 2, mainZoneY + for i = 1, #friends.response.items do + --Падложка + if i % 2 == 0 then buffer.square(mainZoneX, y, mainZoneWidth, 5 + (friends.response.items[i].lists and 1 or 0), 0xEEEEEE, 0x0, " ") end + --Юзер + y = y + 1 + local subbedName = unicode.sub(friends.response.items[i].first_name, 1, 1) .. unicode.sub(friends.response.items[i].last_name, 1, 1) + drawAvatar(x, y, 6, 3, friends.response.items[i].id, subbedName) + local text = friends.response.items[i].first_name .. " " .. friends.response.items[i].last_name + buffer.text(x + 8, y, colors.topBar, text) + local text2 = friends.response.items[i].last_seen and (", " .. (friends.response.items[i].online == 1 and "онлайн" or "был(а) в сети " .. os.date("%d.%m.%y в %H:%M", friends.response.items[i].last_seen.time))) or " " + buffer.text(x + 8 + unicode.len(text), y, 0xAAAAAA, text2) + + if friends.response.items[i].lists then + y = y + 1 + local cykaX = x + 8 + for listID = 1, #friends.response.items[i].lists do + local listName = getListName(friends.response.items[i].lists[listID]) + local listWidth = unicode.len(listName) + 2 + local listBackColor = math.floor(0xFFFFFF / friends.response.items[i].lists[listID]) + local listTextColor = (listBackColor > 0x7FFFFF and 0x000000 or 0xFFFFFF) + buffer.square(cykaX, y, listWidth, 1, listBackColor, listTextColor, " ") + buffer.text(cykaX + 1, y, listTextColor, listName) + cykaX = cykaX + listWidth + 2 + end + end + + y = y + 1 + buffer.text(x + 8, y, 0x999999, "Написать сообщение") + currentFriends.sendMessageButtons[friends.response.items[i].id] = {x + 8, y, x + 18, y, subbedName} + y = y + 1 + buffer.text(x + 8, y, 0x999999, "Открыть профиль") + currentFriends.openProfileButtons[friends.response.items[i].id] = {x + 8, y, x + 18, y, subbedName} + + y = y + 2 + end + + buffer.resetDrawLimit() + else + GUI.error("Ошибка при получении списка друзей пользователя") + end +end + +local function newsGUI() + clearGUIZone() + drawTopBar("Новости") + whatIsOnScreen = "news" + buffer.setDrawLimit(mainZoneX, mainZoneY, mainZoneX + mainZoneWidth - 1, mainZoneY + mainZoneHeight - 1) + + local function getAvatarTextAndNameForNews(source_id) + local avatarText, name = "N/A", "N/A" + if source_id < 0 then + for i = 1, #news.response.groups do + if news.response.groups[i].id == math.abs(source_id) then + avatarText = unicode.sub(news.response.groups[i].name, 1, 2) + name = news.response.groups[i].name + break + end + end + else + for i = 1, #news.response.profiles do + if news.response.profiles[i].id == source_id then + avatarText = unicode.sub(news.response.profiles[i].first_name, 1, 1) .. unicode.sub(news.response.profiles[i].last_name, 1, 1) + name = news.response.profiles[i].first_name .. " " .. news.response.profiles[i].last_name + break + end + end + end + return avatarText, name + end + + local x, y = mainZoneX + 2, mainZoneY + for item = currentNews, currentNews + countOfNewsToShow do + if news.response.items[item] then + --Делаем текст пиздатым + news.response.items[item].text = optimizeStringForWrongSymbols(news.response.items[item].text) + --Убираем говно из новостей + if news.response.items[item].text == "" then + if news.response.items[item].copy_history then + news.response.items[item].text = "Репост" + elseif news.response.items[item].attachments then + news.response.items[item].text = getAttachments(news.response.items[item]) + end + end + --Делаем его еще пизже + local text = {news.response.items[item].text}; text = string.wrap(text, buffer.getWidth() - x - 10) + --Получаем инфу нужную + local avatarText, name = getAvatarTextAndNameForNews(news.response.items[item].source_id) + --Сместиться потом на стока вот + local yShift = 5 + if #text > 2 then yShift = yShift + #text - 2 end + + --Рисуем авку и название хуйни + if item % 2 == 0 then buffer.square(mainZoneX, y, mainZoneWidth, yShift, 0xEEEEEE, 0x0, " ") end + drawAvatar(x, y + 1, 6, 3, math.abs(news.response.items[item].source_id), avatarText) + buffer.text(x + 7, y + 1, colors.topBar, name) + --Рисуем текст + for line = 1, #text do + buffer.text(x + 7, y + line + 1, 0x000000, text[line]) + end + + y = y + yShift + end + end + + buffer.resetDrawLimit() +end + +local function getAndShowNews() + status("Загружаю список новостей") + local success, news1 = newsRequest(countOfNewsToGet) + if success and news1.response then + news = news1 + currentNews = 1 + newsGUI() + else + GUI.error("Ошибка при получении списка новостей") + end +end + +local function drawLeftBar() + --Подложка под элементы + buffer.square(1, 1, leftBarWidth, buffer.getHeight(), colors.leftBar, 0xFFFFFF, " ") + + if personalInfo then + drawPersonalAvatar(3, 2, 6, 3) + buffer.text(11, 3, 0xFFFFFF, ecs.stringLimit("end", personalInfo.first_name .. " " .. personalInfo.last_name, leftBarWidth - 11)) + end + + --Элементы + obj.leftBar = {} + local y, color = 6 + for i = 1, #leftBarElements do + color = colors.leftBarAlternative + if i % 2 == 0 then color = colors.leftBar end + if i == currentLeftBarElement then color = colors.leftBarSelection end + + newObj("leftBar", i, 1, y, leftBarWidth, y + 2) + + buffer.square(1, y, leftBarWidth, 3, color, 0xFFFFFF, " ") + y = y + 1 + buffer.text(3, y, colors.leftBarText, ecs.stringLimit("end", leftBarElements[i], leftBarWidth - 4)) + y = y + 2 + end +end + +--Главное ГУИ с левтбаром и прочим +local function mainGUI() + drawLeftBar() + --Отображаем гую нужную выбранную + if leftBarElements[currentLeftBarElement] == "Сообщения" then + status("Получаю список диалогов") + messageToShowFrom = 1 + dialogToShowFrom = 1 + dialogsGUI() + elseif leftBarElements[currentLeftBarElement] == "Аудиозаписи" then + status("Получаю список аудозаписей") + audioToShowFrom = 1 + audioGUI(personalInfo.id) + elseif leftBarElements[currentLeftBarElement] == "Моя страница" then + loadAndShowProfile(personalInfo.id) + -- loadAndShowProfile(186860159) + elseif leftBarElements[currentLeftBarElement] == "Друзья" then + friendsGUI() + elseif leftBarElements[currentLeftBarElement] == "Новости" then + getAndShowNews() + end + + buffer.draw() +end + +local function spam(id) + while true do + local randomMessages = { + "Ты мое золотце", + "Ты никогда не сделаешь сайт", + "Ты ничтожество", + "Твоя жизнь ничего не значит", + "Ты ничего не добьешься", + "Ты завалишь экзамены", + "Ты никому не нужна", + "Ты не напишешь курсовую", + "Твое животное помрет завтра", + "Не добавляй в ЧС!", + "Передаем привет от Яши и Меня (а кто я?)", + "Хуй!", + "Пизда!", + "Залупа!", + "Пенис!", + "Хер!", + "Давалка!" + } + local text = randomMessages[math.random(1, #randomMessages)] .. " (с любовью, отправлено с OpenComputers)" + sendMessageRequest(tostring(id), text) + print("Отправляю сообщение: " .. text) + os.sleep(2) + end +end + + +---------------------------------------------------- Старт скрипта ---------------------------------------------------------------- + +--Инициализируем библиотеку двойного буффера +--Эх, что бы я делал, если б не накодил ее? 0.2 фпс на GPU мертвеца! +buffer.flush() +--Хуярим настррррроечки +loadSettings() +--Активируем форму логина +local loginData = loginGUI(settings.username, settings.password) +access_token = loginData.access_token +--Получаем персональные данные +_, personalInfo = usersInformationRequest(loginData.user_id) +personalInfo = personalInfo.response[1] + +-- --Ебемся в попчанский +-- spam(21321257) + +--Активируем главное GUI +clearGUIZone() +mainGUI() + +while true do + local e = {event.pull()} + if e[1] == "touch" then + + if whatIsOnScreen == "audio" then + for key in pairs(obj.audio) do + if obj.audio[key]:isClicked(e[3], e[4]) then + obj.audio[key]:press(0.2) + + if component.isAvailable("openfm_radio") then + component.openfm_radio.stop() + component.openfm_radio.setURL(obj.audio[key][5].url) + component.openfm_radio.start() + status("Вывожу в статус играемую музыку") + setCurrentAudioPlaying(currentProfile and currentProfile.ID or personalInfo.id, obj.audio[key][5].id) + else + GUI.error("Эта функция доступна только при наличии установленного мода OpenFM, добавляющего полноценное интернет-радио") + end + + break + end + end + end + + if whatIsOnScreen == "dialogs" then + for key in pairs(obj.dialogList) do + if obj.dialogList[key]:isClicked(e[3], e[4]) then + drawDialog(obj.dialogList[key].y, 0xFF8888, obj.dialogList[key][5], obj.dialogList[key][6], obj.dialogList[key][7], obj.dialogList[key][8], obj.dialogList[key][9]) + buffer.draw() + os.sleep(0.2) + status("Загружаю переписку с пользователем " .. obj.dialogList[key][7]) + currentMessagesPeerID = obj.dialogList[key][5] + currentMessagesAvatarText = obj.dialogList[key][6] + messagesGUI() + break + end + end + + if obj.crazyTypingButton:isClicked(e[3], e[4]) then + obj.crazyTypingButton:press(0.2) + local data = ecs.universalWindow("auto", "auto", 36, 0x262626, true, + {"EmptyLine"}, + {"CenterText", ecs.colors.orange, "CrazyTyping"}, + {"EmptyLine"}, + {"Slider", 0xFFFFFF, ecs.colors.orange, 1, 15, 5, "Количество диалогов: ", ""}, + {"Slider", 0xFFFFFF, ecs.colors.orange, 1, 100, 5, "Количество запросов: ", ""}, + {"Slider", 0xFFFFFF, ecs.colors.orange, 1, 5000, 500, "Задержка между запросами: ", " мс"}, + {"EmptyLine"}, + {"Button", {ecs.colors.orange, 0xffffff, "OK"}, {0x999999, 0xffffff, "Отмена"}} + ) + if data[4] == "OK" then + for i = 1, data[2] do + local count = 1 + for key in pairs(obj.dialogList) do + -- GUI.error("Ебашу спам диалогу под пиром: " .. obj.dialogList[key][5]) + ecs.info("auto", "auto", "CrazyTyping", "Запрос: " .. i .. " из " .. data[2] .. ", диалог: " .. count .. " из ".. data[1] .. ", peerID: " .. obj.dialogList[key][5]) + setCrazyTypingRequest(obj.dialogList[key][5]) + count = count + 1 + if count > data[1] then break end + os.sleep(data[3] / 1000) + end + end + buffer.draw(true) + end + end + end + + if whatIsOnScreen == "messages" then + if obj.messageInputBar:isClicked(e[3], e[4]) then + obj.messageInputBar.eventHandler({draw = function() obj.messageInputBar:draw() end}, obj.messageInputBar, {[1] = "touch", [3] = obj.messageInputBar.x, [4] = obj.messageInputBar.y}) + local newText = obj.messageInputBar.text + if newText and newText ~= " " and newText ~= "" then + computer.beep(1700) + status("Отправляю сообщение пользователю") + sendMessageRequest(currentMessagesPeerID, newText .. (settings.addSendingInfo and messageEndAdderText or "")) + status("Обновляю историю переписки") + messageToShowFrom = 1 + messagesGUI() + end + drawMessageInputBar() + end + end + + if whatIsOnScreen == "userProfile" then + if currentProfile.audiosButton:isClicked(e[3], e[4]) then + currentProfile.audiosButton:press(0.2) + audioToShowFrom = 1 + audioGUI(currentProfile.ID) + buffer.draw() + elseif currentProfile.sendMessageButton:isClicked(e[3], e[4]) then + currentProfile.sendMessageButton:press(0.2) + currentMessagesPeerID = currentProfile.ID + messageToShowFrom = 1 + currentMessagesAvatarText = currentProfile.avatarText + messagesGUI() + end + end + + if whatIsOnScreen == "friends" then + for ID in pairs(currentFriends.sendMessageButtons) do + if clickedAtZone(e[3], e[4], currentFriends.sendMessageButtons[ID]) then + buffer.text(currentFriends.sendMessageButtons[ID][1], currentFriends.sendMessageButtons[ID][2], 0x000000, "Написать сообщение") + buffer.draw() + currentMessagesPeerID = ID + messageToShowFrom = 1 + currentMessagesAvatarText = currentFriends.sendMessageButtons[ID][5] + messagesGUI() + break + end + end + + for ID in pairs(currentFriends.openProfileButtons) do + if clickedAtZone(e[3], e[4], currentFriends.openProfileButtons[ID]) then + buffer.text(currentFriends.openProfileButtons[ID][1], currentFriends.openProfileButtons[ID][2], 0x000000, "Открыть профиль") + buffer.draw() + loadAndShowProfile(ID) + buffer.draw() + break + end + end + end + + for key in pairs(obj.leftBar) do + if clickedAtZone(e[3], e[4], obj.leftBar[key]) then + -- GUI.error("Кликнули на лефт бар ээлемент") + local oldLeftBarElement = currentLeftBarElement + currentLeftBarElement = key + + drawLeftBar() + buffer.draw() + + if leftBarElements[currentLeftBarElement] == "Выход" then + os.sleep(0.3) + buffer.clear(0x262626) + ecs.prepareToExit() + return + elseif leftBarElements[currentLeftBarElement] == "Аудиозаписи" then + currentProfile = currentProfile or {} + currentProfile.ID = personalInfo.id + elseif leftBarElements[currentLeftBarElement] == "Настройки" then + local data = ecs.universalWindow("auto", "auto", 36, 0x262626, true, + {"EmptyLine"}, + {"CenterText", ecs.colors.orange, "Настройки"}, + {"EmptyLine"}, + {"Switch", ecs.colors.orange, 0xffffff, 0xFFFFFF, "Сохранять данные авторизации", settings.saveAuthData}, + {"EmptyLine"}, + {"Switch", ecs.colors.orange, 0xffffff, 0xFFFFFF, "Добавлять приписку \"Отправлено с ...\"", settings.addSendingInfo}, + {"EmptyLine"}, + {"CenterText", ecs.colors.orange, "OpenComputers VK Client v4.0"}, + {"EmptyLine"}, + {"CenterText", ecs.colors.white, "Автор: Игорь Тимофеев, vk.com/id7799889"}, + {"CenterText", ecs.colors.white, "Все права защищены, епта! Попробуй только спиздить!"}, + {"EmptyLine"}, + {"Button", {ecs.colors.orange, 0xffffff, "OK"}, {0x999999, 0xffffff, "Отмена"}} + ) + if data[3] == "OK" then + settings.saveAuthData = data[1] + settings.addSendingInfo = data[2] + + if settings.saveAuthData then + settings.username = loginData.username + settings.password = loginData.password + else + settings.username = nil + settings.password = nil + end + saveSettings() + + currentLeftBarElement = oldLeftBarElement + end + end + + mainGUI() + break + end + end + elseif e[1] == "scroll" then + if e[5] == 1 then + if whatIsOnScreen == "dialogs" then + dialogToShowFrom = dialogToShowFrom - dialogScrollSpeed + if dialogToShowFrom < 1 then dialogToShowFrom = 1 end + status("Прокручиваю диалоги, отправляю запрос на сервер") + dialogsGUI() + buffer.draw() + elseif whatIsOnScreen == "messages" then + messageToShowFrom = messageToShowFrom + messagesScrollSpeed + status("Прокручиваю сообщения, отправляю запрос на сервер") + messagesGUI() + buffer.draw() + elseif whatIsOnScreen == "audio" then + audioToShowFrom = audioToShowFrom - audioScrollSpeed + if audioToShowFrom < 1 then audioToShowFrom = 1 end + status("Прокручиваю аудозаписи, отправляю запрос на сервер") + audioGUI(currentProfile and currentProfile.ID or personalInfo.id) + buffer.draw() + elseif whatIsOnScreen == "userProfile" then + currentProfileY = currentProfileY + profileScrollSpeed + if currentProfileY > mainZoneY + 2 then currentProfileY = mainZoneY + 2 end + userProfileGUI() + buffer.draw() + elseif whatIsOnScreen == "friends" then + currentFriendsOffset = currentFriendsOffset - friendsScrollSpeed + if currentFriendsOffset < 0 then currentFriendsOffset = 0 end + friendsGUI() + buffer.draw() + elseif whatIsOnScreen == "news" then + currentNews = currentNews - 1 + if currentNews < 1 then currentNews = 1 end + newsGUI() + buffer.draw() + end + else + if whatIsOnScreen == "dialogs" then + dialogToShowFrom = dialogToShowFrom + dialogScrollSpeed + status("Прокручиваю диалоги, отправляю запрос на сервер") + dialogsGUI() + buffer.draw() + elseif whatIsOnScreen == "messages" then + messageToShowFrom = messageToShowFrom - messagesScrollSpeed + if messageToShowFrom < 1 then messageToShowFrom = 1 end + status("Прокручиваю сообщения, отправляю запрос на сервер") + messagesGUI() + buffer.draw() + elseif whatIsOnScreen == "audio" then + audioToShowFrom = audioToShowFrom + audioScrollSpeed + status("Прокручиваю аудозаписи, отправляю запрос на сервер") + audioGUI(currentProfile and currentProfile.ID or personalInfo.id) + buffer.draw() + elseif whatIsOnScreen == "userProfile" then + currentProfileY = currentProfileY - profileScrollSpeed + userProfileGUI() + buffer.draw() + elseif whatIsOnScreen == "friends" then + currentFriendsOffset = currentFriendsOffset + friendsScrollSpeed + friendsGUI() + buffer.draw() + elseif whatIsOnScreen == "news" then + currentNews = currentNews + 1 + newsGUI() + buffer.draw() + end + end + end +end + +-- local success, dialogs = getDialogsRequest(0, 5) +-- saveToFile(serialization.serialize(dialogs)) + + +-- sendMessageRequest(dialogs.response.items[2], "тестовое сообщение, отправлено через OpenComputers VK Client by Игорь, епта") + + + + + diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/VK.app/Resources/About/Russian.txt b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/VK.app/Resources/About/Russian.txt new file mode 100755 index 00000000..dc55052e --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/VK.app/Resources/About/Russian.txt @@ -0,0 +1 @@ +Полноценный клиент для сайта VK.com, позволяющий общаться с вашими друзьями, просматривать основную информацию их профилей, читать новостную ленту, а также прослушивать аудиозаписи. \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/VK.app/Resources/Icon.pic b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/VK.app/Resources/Icon.pic new file mode 100755 index 0000000000000000000000000000000000000000..c4363302d74a944266db7e2b71940063bdaf4b1a GIT binary patch literal 144 zcmeZw_H<+8U}5^t!1QQZ3nK#~8zUnVBgdm@4PZ74kj=ornF$oOWn^Y#@dh%^0~N65 zKt-7IpbRzzAcKL0k&TI&g_Vt+iH(tonT?%;8E60tRLmKwl&J*DU{8fISi^t}khv@X D%)kws literal 0 HcmV?d00001 diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/VK.app/Resources/VKLogo.pic b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/VK.app/Resources/VKLogo.pic new file mode 100755 index 0000000000000000000000000000000000000000..a6a2e9324c9ba13dfc9b65bb373f17354dd60895 GIT binary patch literal 769 zcmZ8fORn264D?VRTbAv7o$rg|*oW}Oy8;Ee>M^qEA!49sDDXBpK$7dk+4mwTW##o1 z-3Zi(8j{2L{^i?OU0ZqvC=npA*n)&qI*d$glYFsUtvB1ZU9m3@RZR|bP%RAjIpw&P z1~i0KtvD3v!lh~#%HdZS50pb34Y1OjVP0Vv)p7P&)KS{}^UlVN`W>LrwBs|Io%tM3S^>-za)auqfs`E4S|JHhL?};OW+SnB=nfI0UP?V0Po}fE za!M8ghZxy~TT5?bu!^xca;5Z45+w_H5iKl=9wBxOta#h)ucC*>;>l@r=a1D7xS zo>yYsz+lObc372P&xx`8i3j(RiB##K0l&JldS?ML$F_C6dCR4=BQYm(K-#c2R-@qk zck0f0RnPKT*Vm*Wfy%1!bXFcNjAlJ6BtVXC?gURBtY%?QUYTmw@l--VmfUKf;?yhg z@{pOheR}9+ETM!9qfwMQ!+pH{HJ(ho8NCU61R`8`Z~mXEGIr-b%zElg>>=;tCwrXK KfOH&6WPbob!%pk~ literal 0 HcmV?d00001 diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Weather.app/Main.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Weather.app/Main.lua new file mode 100755 index 00000000..3cc39a86 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Weather.app/Main.lua @@ -0,0 +1,156 @@ + +require("advancedLua") +local web = require("web") +local json = require("json") +local fs = require("filesystem") +local bigLetters = require("bigLetters") +local buffer = require("doubleBuffering") +local image = require("image") +local unicode = require("unicode") +local component = require("component") +local GUI = require("GUI") +local MineOSCore = require("MineOSCore") +local MineOSInterface = require("MineOSInterface") +local MineOSPaths = require("MineOSPaths") + +-------------------------------------------------------------------------------------------------------- + +local mainContainer, window = MineOSInterface.addWindow(MineOSInterface.filledWindow(1, 1, 130, 30, 0)) +window.backgroundPanel.colors.transparency = 0.2 + +local weatherContainer = window:addChild(GUI.container(1, 1, 1, 23)) + +local resources = MineOSCore.getCurrentApplicationResourcesDirectory() +local weatherIcons = { + sunny = image.load(resources .. "Sunny.pic"), + sunnyAndCloudy = image.load(resources .. "Icon.pic"), + snowy = image.load(resources .. "Snowy.pic"), + rainy = image.load(resources .. "Rainy.pic"), + cloudy = image.load(resources .. "Cloudy.pic"), + thundery = image.load(resources .. "Stormy.pic"), + foggy = image.load(resources .. "Foggy.pic"), +} + +-------------------------------------------------------------------------------------------------------- + +local function newWeather(x, y, day) + local object = GUI.object(x, y, 14, 11) + + local type + if day.weather[1].id == 800 then + type = "sunny" + elseif day.weather[1].id == 801 then + type = "sunnyAndCloudy" + elseif day.weather[1].id >= 800 then + type = "cloudy" + elseif day.weather[1].id >= 700 then + type = "foggy" + elseif day.weather[1].id >= 600 then + type = "snowy" + elseif day.weather[1].id >= 300 then + type = "rainy" + elseif day.weather[1].id >= 200 then + type = "thundery" + else + type = "sunnyAndCloudy" + end + + local temp = math.round(day.temp.min) .. " / " .. math.round(day.temp.max) .. " °C" + local pressure = math.round(day.pressure / 1.33322387415) .. " mm Hg" + local humidity = math.round(day.humidity) .. "%" + local winds = { + [0] = "N", + [1] = "NE", + [2] = "E", + [3] = "SE", + [4] = "S", + [5] = "SW", + [6] = "W", + [7] = "NW", + [8] = "N", + } + local wind = day.speed .. " m/s, " .. (winds[math.round(day.deg / 45)] or "N/A") + + local function centerText(y, color, text) + buffer.text(math.floor(object.x + object.width / 2 - unicode.len(text) / 2), y, color, text) + end + + object.draw = function() + centerText(object.y, 0xFFFFFF, os.date("%a", day.dt)) + buffer.image(object.x + 3, object.y + 2, weatherIcons[type]) + centerText(object.y + 7, 0xFFFFFF, temp) + centerText(object.y + 8, 0xDDDDDD, wind) + centerText(object.y + 9, 0xBBBBBB, pressure) + centerText(object.y + 10, 0x999999, humidity) + end + + return object +end + +local function updateForecast(city) + local result, reason = web.request("http://api.openweathermap.org/data/2.5/forecast/daily?&appid=98ba4333281c6d0711ca78d2d0481c3d&units=metric&cnt=17&q=" .. web.encode(city)) + if result then + result = json:decode(result) + + if result.list then + weatherContainer:deleteChildren() + + local x, y = 1, 1 + local currentDay = result.list[1] + local object = weatherContainer:addChild(GUI.object(x + 2, y, 40, 8)) + object.draw = function() + bigLetters.drawText(object.x, object.y, 0xFFFFFF, math.round((currentDay.temp.max + currentDay.temp.min) / 2) .. "°") + buffer.text(object.x, object.y + 6, 0xFFFFFF, result.city.name .. ", " .. result.city.country) + buffer.text(object.x, object.y + 7, 0xFFFFFF, "Population: " .. math.shorten(result.city.population, 2)) + end + + y = y + object.height + 1 + + local input = weatherContainer:addChild(GUI.input(x + 2, y, 25, 1, 0xE1E1E1, 0x666666, 0x666666, 0xE1E1E1, 0x2D2D2D, "", "Type city name here")) + input.onInputFinished = function() + updateForecast(input.text) + input.text = "" + + mainContainer:draw() + buffer.draw() + end + + y = y + input.height + 2 + + for i = 1, #result.list do + local object = weatherContainer:addChild(newWeather(x, y, result.list[i])) + x = x + object.width + 2 + end + else + GUI.error(result.message) + end + else + GUI.error("Wrong result. Check city name and try again.") + end +end + + +-------------------------------------------------------------------------------------------------------- + +window.onResize = function(width, height) + window.backgroundPanel.width = width + window.backgroundPanel.height = height + weatherContainer.width = width + weatherContainer.localY = height - weatherContainer.height - 1 + weatherContainer.localX = 3 +end + +window:resize(window.width, window.height) + +updateForecast("Санкт-Петербург") + + + + + + + + + + + diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Weather.app/Resources/About/Russian.txt b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Weather.app/Resources/About/Russian.txt new file mode 100755 index 00000000..31b9dabb --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Weather.app/Resources/About/Russian.txt @@ -0,0 +1 @@ +Просматривайте информацию о погоде прямо из мира Minecraft! Просто введите название города и наслаждайтесь приятным интерфейсом, украшающим любое жилище. \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Weather.app/Resources/Cloudy.pic b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Weather.app/Resources/Cloudy.pic new file mode 100755 index 0000000000000000000000000000000000000000..08a860a4cff87c8d5ed099997661947c208c0a00 GIT binary patch literal 92 zcmW-W!3}^g48(GmG|3p<1qq24EAe24uExKMK+4Cm<#RiBPva{(#|^Au+6l=3 literal 0 HcmV?d00001 diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Weather.app/Resources/Foggy.pic b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Weather.app/Resources/Foggy.pic new file mode 100755 index 0000000000000000000000000000000000000000..85a69089f4b5012f7a0881f59f0b27e00a98262e GIT binary patch literal 71 zcmXAfxeb6o5X5$7zbS)WkdTO|jf4Ve`#ND50go&eU$hIqDDsnK NKIX`oTO<^Y)&p7l1}Xpm literal 0 HcmV?d00001 diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Weather.app/Resources/Icon.pic b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Weather.app/Resources/Icon.pic new file mode 100644 index 0000000000000000000000000000000000000000..f1b0f8f1e57aaef4dbc2e583436eb31d18eca562 GIT binary patch literal 242 zcmXYrJr2S!42AO?=SN%%35kg-agPo}4H6eXWdMm=l-)Pz)TI?T#Zx}TpP%1pe>}v@ z^mRL_S2(NyQIh_tJi`N0-ii|#Ug}_mk5W4OCh40osu88$6c-S()R&<~TujJP&WcRU zV8FR7ebGasp3?lgAxnK2YS3U9blb1^XhjZ-z7)xGh-p^#pjLI-iUg?}lBbv$skQx2 F_y=A(ML7Tf literal 0 HcmV?d00001 diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Weather.app/Resources/Rainy.pic b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Applications/Weather.app/Resources/Rainy.pic new file mode 100755 index 0000000000000000000000000000000000000000..d4453d92a0d748cdd79eb27abdab61c46ed91416 GIT binary patch literal 83 zcmWlQ!3}^g48(f5OPVp*1qs26={gDz*6AXUzARbxyMZV=taq*ao91x$g6SK6>Cp picture[1] then + x, y = 1, y + 1 + end + end + + image.save(path, picture) + else + local pizda = { + width = drawingArea.width / 4, + height = drawingArea.height / 4, + } + + for i = 1, #drawingArea.children do + table.insert(pizda, { + background = drawingArea.children[i].background, + foreground = drawingArea.children[i].foreground, + pixels = drawingArea.children[i].pixels, + }) + end + + table.toFile(path, pizda, true) + end + end + + filesystemDialog:show() +end + +openButton.onTouch = function() + local filesystemDialog = GUI.addFilesystemDialogToContainer(mainContainer, 50, math.floor(mainContainer.height * 0.8), true, "OK", "Cancel", "Path", "/") + + filesystemDialog:setMode(GUI.filesystemModes.open, GUI.filesystemModes.file) + filesystemDialog:addExtensionFilter(".braiile") + + filesystemDialog.onSubmit = function(path) + local pizda = table.fromFile(path) + drawingArea:deleteChildren() + + newNoGUI(pizda.width, pizda.height) + + for i = 1, #drawingArea.children do + drawingArea.children[i].background = pizda[i].background + drawingArea.children[i].foreground = pizda[i].foreground + drawingArea.children[i].pixels = pizda[i].pixels + end + + mainContainer:draw() + buffer.draw() + end + + filesystemDialog:show() +end + +window.actionButtons.minimize:delete() +window.actionButtons.maximize:delete() + + +--------------------------------------------------------------------------------------------------------- + +newNoGUI(8, 4) + +mainContainer:draw() +buffer.draw(true) + + + diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/Braille.app/Resources/Icon.pic b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/Braille.app/Resources/Icon.pic new file mode 100644 index 0000000000000000000000000000000000000000..a959182b8df53e7fd234e59c3bb313424ffb9397 GIT binary patch literal 171 zcmXwy!41MN3`P5GC$YpBjDm#3iIqSc+A)%50&dLn7Xh~oCx1Ub>)$W8Yj(qTI= autoupdateSlider.value / 1000 then + takePicture() + lastUptime = uptime + end + end +end + +window.actionButtons:moveToFront() + +semiPixelSwitch.onStateChanged = takePicture +FOVSlider.onValueChanged = takePicture + +paletteSwitch.onStateChanged = function() + palette = paletteSwitch.state and thermal or grayscale + mainContainer:draw() + buffer.draw() +end + +autoupdateSwitch.onStateChanged = function() + autoupdateSlider.hidden = not autoupdateSwitch.state + mainContainer:draw() + buffer.draw() +end + +for address in component.list("camera") do + comboBox:addItem(address).onTouch = function() + cameraProxy = component.proxy(address) + takePicture() + end +end + +window.onResize = function(width, height) + layout.height = window.height + window.backgroundPanel.height = window.height + cameraView.height = window.height + cameraView.width = window.width - window.backgroundPanel.width + + shootButton.localX = math.floor(1 + window.backgroundPanel.width / 2 - shootButton.width / 2) + shootButton.localY = window.height - shootButton.height + + takePicture() +end + +window:resize(window.width, window.height) +takePicture() diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/Camera.app/Resources/Icon.pic b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/Camera.app/Resources/Icon.pic new file mode 100644 index 0000000000000000000000000000000000000000..77b0437459a835bfecabc0cca496b8e69dfe981a GIT binary patch literal 214 zcmXZW%?ScQ3c<$g;d%59`gAYePH_s8<5gSU6ugzoorM5b#}QA^=~BbR-- jj`{OK^|>Vl8WSTRVyl>#;qY`?B5WLk29||KDWB1Qx#u`| literal 0 HcmV?d00001 diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/Camera.lnk b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/Camera.lnk new file mode 100644 index 00000000..32f58662 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/Camera.lnk @@ -0,0 +1 @@ +/MineOS/Applications/Camera.app/ \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/ChristmasTree.lnk b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/ChristmasTree.lnk new file mode 100644 index 00000000..265aa24f --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/ChristmasTree.lnk @@ -0,0 +1 @@ +/MineOS/Applications/ChristmasTree.app/ \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/CodeDoor.lnk b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/CodeDoor.lnk new file mode 100644 index 00000000..2c72c72d --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/CodeDoor.lnk @@ -0,0 +1 @@ +/MineOS/Applications/CodeDoor.app/ \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/EBAMARKET2.app/Main.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/EBAMARKET2.app/Main.lua new file mode 100644 index 00000000..692cb0d5 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/EBAMARKET2.app/Main.lua @@ -0,0 +1,1270 @@ + +require("advancedLua") +local component = require("component") +local computer = require("computer") +local web = require("web") +local GUI = require("GUI") +local buffer = require("doubleBuffering") +local MineOSCore = require("MineOSCore") +local MineOSPaths = require("MineOSPaths") +local MineOSInterface = require("MineOSInterface") +local image = require("image") +local fs = require("filesystem") +local color = require("color") +local unicode = require("unicode") + +-------------------------------------------------------------------------------- + +local host = "http://buttex.ru/mineos/appmarket/" +local luaIcon = image.load(MineOSPaths.icons .. "Lua.pic") +local scriptIcon = image.load(MineOSPaths.icons .. "Script.pic") +local appMarketPath = MineOSPaths.applicationData .. "AppMarket/" +local configPath = appMarketPath .. "Config.cfg" +local iconCachePath = appMarketPath .. "Cache/" + +local config = { + descriptionLanguage = "en", + orderBy = 1, + orderDirection = 1, + user = {} +} + +local categories = { + "Приложения", + "Библиотеки", + "Скрипты", +} + +local orderDirections = { + "desc", + "asc", +} + +local downloadPaths = { + "/MineOS/Applications/", + "/lib/", + "/bin/", +} + +local licenses = { + "MIT", + "GNU GPLv3", + "GNU AGPLv3", + "GNU LGPLv3", + "Apache Licence 2.0", + "Mozilla Public License 2.0", + "The Unlicense", +} + +local orderBys = { + "average_rating", + "file_id", + "publication_name", +} + +local search = "" +local appWidth, appHeight, appHSpacing, appVSpacing, currentPage, appsPerPage, appsPerWidth, appsPerHeight = 34, 6, 2, 1 + +local updateFileList, editPublication + +-------------------------------------------------------------------------------- + +local function saveConfig() + table.toFile(configPath, config) +end + +local function loadConfig() + if fs.exists(configPath) then + config = table.fromFile(configPath) + else + saveConfig() + end +end + +-------------------------------------------------------------------------------- + +local function RawAPIRequest(script, postData, notUnserialize) + local url = host .. script .. ".php?" .. web.serialize(postData) + -- table.toFile("/test.txt", {url}) + local requestResult, requestReason = web.request(url) + if requestResult then + if not notUnserialize then + local unserializeResult, unserializeReason = table.fromString(requestResult) + if unserializeResult then + if unserializeResult.success then + return unserializeResult + else + return false, "API request not succeded: " .. tostring(unserializeResult.reason) + end + else + return false, "Failed to unserialize response data: " .. tostring(unserializeReason) .. ", the data was: " .. tostring(requestResult) + end + else + return result + end + else + return false, "Web request failed: " .. tostring(requestReason) + end +end + +local function fieldAPIRequest(fieldToReturn, ...) + local success, reason = RawAPIRequest(...) + if success then + if success[fieldToReturn] then + return success[fieldToReturn] + else + return false, "Request was successful, but field " .. tostring(fieldToReturn) .. " doesn't exists" + end + else + return false, reason + end +end + +local function checkImage(url) + local handle = component.internet.request(url) + if handle then + local _, _, responseData + repeat + _, _, responseData = handle:response() + until responseData + + local contentLength = tonumber(responseData["Content-Length"][1]) + if contentLength <= 10240 then + local data, chunk, reason = "" + while true do + chunk, reason = handle.read(math.huge) + if chunk then + data = data .. chunk + if #data > 8 then + if data:sub(1, 4) == "OCIF" then + if string.byte(data:sub(6, 6)) > 8 or string.byte(data:sub(7, 7)) > 4 then + handle:close() + return false, "Image size is larger than 8x4" + end + else + handle:close() + return false, "Wrong image file signature" + end + end + else + handle:close() + if reason then + return false, reason + else + return data + end + end + end + else + handle:close() + return false, "Specified image size is too big" + end + else + return false, "Invalid URL" + end +end + +-------------------------------------------------------------------------------- + +local mainContainer, window = MineOSInterface.addWindow(MineOSInterface.tabbedWindow(1, 1, 110, 30)) +local overrideWindowDraw = window.draw +window.draw = function(...) + overrideWindowDraw(...) + buffer.text(window.x, window.y + window.height, 0xFF0000, "Free RAM: " .. math.floor(computer.freeMemory() / 1024)) +end + +local contentContainer = window:addChild(GUI.container(1, 4, 1, 1)) +local statusWidget = window:addChild(GUI.object(1, 1, 1, 1)) +statusWidget.draw = function() + buffer.square(statusWidget.x, statusWidget.y, statusWidget.width, 1, 0x2D2D2D, 0xF0F0F0, " ") + buffer.text(statusWidget.x + 1, statusWidget.y, 0xF0F0F0, statusWidget.text) +end + +-------------------------------------------------------------------------------- + +local function status(text) + statusWidget.text = text + MineOSInterface.OSDraw() +end + +local function getAllDependencies(application) + if application.dependencies then + local allDependencies = {} + + local function getAllDependenciesRecursively(file_ids) + local list, reason = fieldAPIRequest("list", "list", { + file_ids = file_ids, + fields = { + "dependencies", + "publication_name", + "path", + "source_url", + "category_id", + } + }) + + if list then + local newDependenciesList = {} + + for i = 1, #list do + if not allDependencies[list[i].file_id] and list[i].file_id ~= application.file_id then + allDependencies[list[i].file_id] = list[i] + end + + if list[i].dependencies then + for j = 1, #list[i].dependencies do + if not allDependencies[list[i].dependencies[j]] and list[i].dependencies[j] ~= application.file_id then + table.insert(newDependenciesList, list[i].dependencies[j]) + end + end + end + end + + if #newDependenciesList > 0 then + getAllDependenciesRecursively(newDependenciesList) + end + else + GUI.error(reason) + end + end + + getAllDependenciesRecursively(application.dependencies) + + application.expandedDependencies = {} + for key, value in pairs(allDependencies) do + table.insert(application.expandedDependencies, value) + allDependencies[key] = nil + end + + if #application.expandedDependencies == 0 then + application.expandedDependencies = nil + end + end +end + +local function ratingWidgetDraw(object) + local x = 0 + for i = 1, 5 do + buffer.text(object.x + x, object.y, math.round(object.rating) >= i and object.colors.first or object.colors.second, "*") + x = x + object.spacing + end + + return object +end + +local function newRatingWidget(x, y, rating, firstColor, secondColor) + local object = GUI.object(x, y, 9, 1) + + object.colors = { + first = firstColor or 0xFFB600, + second = secondColor or 0xC3C3C3 + } + object.spacing = 2 + object.draw = ratingWidgetDraw + object.rating = rating + + return object +end + +-------------------------------------------------------------------------------- + +local function getApplicationIcon(category_id, dependencies) + if dependencies then + for i = 1, #dependencies do + if dependencies[i].path == "Resources/Icon.pic" then + local path = iconCachePath .. dependencies[i].file_id .. ".pic" + + if fs.exists(path) then + return image.load(path) + else + local data, reason = checkImage(dependencies[i].source_url) + if data then + local file = io.open(path, "w") + file:write(data) + file:close() + + return image.load(path) + else + GUI.error("Failed to download publication icon: " .. reason) + break + end + end + end + end + end + + if category_id == 2 then + return luaIcon + else + return scriptIcon + end +end + +local function addPanel(container, color) + container.panel = container:addChild(GUI.panel(1, 1, container.width, container.height, color or 0xFFFFFF)) +end + +local function addApplicationInfo(container, application) + addPanel(container) + container.image = container:addChild(GUI.image(3, 2, getApplicationIcon(application.category_id, application.expandedDependencies))) + container.nameLabel = container:addChild(GUI.text(13, 2, 0x0, application.publication_name)) + container.versionLabel = container:addChild(GUI.text(13, 3, 0x888888, "©" .. application.user_name)) + container.rating = container:addChild(newRatingWidget(13, 4, application.average_rating or 0)) + container.downloadButton = container:addChild(GUI.adaptiveRoundedButton(13, 5, 1, 0, 0xBBBBBB, 0xFFFFFF, 0x888888, 0xFFFFFF, "Загрузить")) + container.downloadButton.onTouch = function() + local filesystemDialog = GUI.addFilesystemDialogToContainer(MineOSInterface.mainContainer, 50, math.floor(MineOSInterface.mainContainer.height * 0.8), true, "Загрузить", "Отмена", "Имя приложения", "/") + + filesystemDialog.input.text = application.category_id == 1 and application.publication_name or fs.hideExtension(application.path) + filesystemDialog:addExtensionFilter(application.category_id == 1 and ".app" or ".lua") + + filesystemDialog.filesystemTree.selectedItem = downloadPaths[application.category_id] + filesystemDialog:expandPath(filesystemDialog.filesystemTree.selectedItem) + + filesystemDialog.onSubmit = function(downloadPath) + local container = MineOSInterface.addUniversalContainer(MineOSInterface.mainContainer, "") + container.layout:setCellFitting(2, 1, false, false) + + local progressBar = container.layout:addChild(GUI.progressBar(1, 1, 36, 0x66DB80, 0x0, 0xEEEEEE, 0, true, true, "", "%")) + + local function updateLabel(pizda) + container.label.text = "Загрузка " .. (pizda.publication_name or fs.name(pizda.path)) + end + updateLabel(application) + + MineOSInterface.OSDraw() + + -- SAVED + getAllDependencies(application) + local whatToDownload = {application} + if application.expandedDependencies then + for i = 1, #application.expandedDependencies do + table.insert(whatToDownload, application.expandedDependencies[i]) + end + end + + for i = 1, #whatToDownload do + progressBar.value = math.round(i / #whatToDownload * 100) + updateLabel(whatToDownload[i]) + + -- Если это публикация + local finalPath + if whatToDownload[i].category_id then + finalPath = downloadPaths[whatToDownload[i].category_id] + + if whatToDownload[i].category_id == 1 then + if i == 1 then + finalPath = downloadPath .. "/Main.lua" + else + finalPath = finalPath .. whatToDownload[i].publication_name .. ".app/Main.lua" + end + else + finalPath = finalPath .. whatToDownload[i].path + end + -- Если это ресурс + else + finalPath = downloadPath .. "/" .. whatToDownload[i].path + end + + -- GUI.error(whatToDownload[i].source_url, finalPath) + -- local success, reason = web.download(whatToDownload[i].source_url, finalPath) + -- if not success then + -- GUI.error(reason) + -- end + + MineOSInterface.OSDraw() + end + + container:delete() + MineOSInterface.OSDraw() + end + + filesystemDialog:setMode(GUI.filesystemModes.save, GUI.filesystemModes.file) + filesystemDialog:show() + end +end + +local function keyValueWidgetUpdate(object) + object.width = unicode.len(object.key .. object.value) +end + +local function keyValueWidgetDraw(object) + keyValueWidgetUpdate(object) + buffer.text(object.x, object.y, object.colors.key, object.key) + buffer.text(object.x + unicode.len(object.key), object.y, object.colors.value, object.value) +end + +local function newKeyValueWidget(x, y, keyColor, valueColor, key, value) + local object = GUI.object(x, y, 1, 1) + + object.colors = { + key = keyColor, + value = valueColor + } + object.key = key + object.value = value + + object.draw = keyValueWidgetDraw + keyValueWidgetUpdate(object) + + return object +end + +local function containerScrollEventHandler(mainContainer, object, eventData) + if eventData[1] == "scroll" then + local first, last = object.children[1], object.children[#object.children] + + if eventData[5] == 1 then + if first.localY < 2 then + for i = 1, #object.children do + object.children[i].localY = object.children[i].localY + 1 + end + MineOSInterface.OSDraw() + end + else + if last.localY + last.height - 1 >= object.height then + for i = 1, #object.children do + object.children[i].localY = object.children[i].localY - 1 + end + MineOSInterface.OSDraw() + end + end + end +end + +local function newApplicationInfo(file_id) + status("Получение информации о приложении...") + + local info, reason = fieldAPIRequest("list", "list", { + file_ids = {file_id}, + fields = { + "publication_id", + "publication_name", + "average_rating", + "version", + "reviews", + "description", + "category_id", + "dependencies", + "user_name", + "license", + "timestamp", + "path", + }, + description_language = config.descriptionLanguage, + }) + + if info then + local application = info[1] + + status("Построение древа зависимостей...") + getAllDependencies(application) + + contentContainer:deleteChildren() + + local infoContainer = contentContainer:addChild(GUI.container(1, 1, contentContainer.width, contentContainer.height)) + infoContainer.eventHandler = containerScrollEventHandler + + -- Жирный йоба-лейаут для отображения ВАЩЕ всего - и инфы, и отзыввов + local layout = infoContainer:addChild(GUI.layout(3, 2, infoContainer.width - 4, infoContainer.height, 1, 1)) + layout:setCellAlignment(1, 1, GUI.alignment.horizontal.center, GUI.alignment.vertical.top) + + -- А вот эт уже контейнер чисто инфы крч + local detailsContainer = layout:addChild(GUI.container(3, 2, layout.width, 6)) + + -- Тут будут находиться ваще пизда подробности о публикации + local ratingsContainer = detailsContainer:addChild(GUI.container(1, 1, 26, 6)) + ratingsContainer.localX = detailsContainer.width - ratingsContainer.width + 1 + addPanel(ratingsContainer, 0xE1E1E1) + + -- Всякая текстовая пизда + local y = 2 + ratingsContainer:addChild(newKeyValueWidget(2, y, 0x2D2D2D, 0x888888, "Разработчик", ": " .. application.user_name)); y = y + 1 + ratingsContainer:addChild(newKeyValueWidget(2, y, 0x2D2D2D, 0x888888, "Лицензия", ": " .. application.license)); y = y + 1 + ratingsContainer:addChild(newKeyValueWidget(2, y, 0x2D2D2D, 0x888888, "Категория", ": " .. categories[application.category_id])); y = y + 1 + ratingsContainer:addChild(newKeyValueWidget(2, y, 0x2D2D2D, 0x888888, "Версия", ": " .. string.format("%.2f", application.version))); y = y + 1 + ratingsContainer:addChild(newKeyValueWidget(2, y, 0x2D2D2D, 0x888888, "Обновлено", ": " .. os.date("%d.%m.%Y", application.timestamp))); y = y + 1 + y = y + 1 + + -- Добавляем инфу с общими рейтингами + if application.reviews then + status("Формирование структуры отзывов...") + + local ratings = {0, 0, 0, 0, 0} + for i = 1, #application.reviews do + ratings[application.reviews[i].rating] = ratings[application.reviews[i].rating] + 1 + end + + ratingsContainer:addChild(newKeyValueWidget(2, y, 0x2D2D2D, 0x888888, "Средний рейтинг", ": " .. string.format("%.1f", application.average_rating or 0))); y = y + 1 + + for i = #ratings, 1, -1 do + local text = tostring(ratings[i]) + local textLength = #text + ratingsContainer:addChild(newRatingWidget(2, y, i, nil, 0xC3C3C3)) + ratingsContainer:addChild(GUI.progressBar(12, y, ratingsContainer.width - textLength - 13, 0x2D2D2D, 0xC3C3C3, 0xC3C3C3, ratings[i] / #application.reviews * 100, true)) + ratingsContainer:addChild(GUI.text(ratingsContainer.width - textLength, y, 0x2D2D2D, text)) + y = y + 1 + end + end + + -- Добавляем описание и прочую пизду + local textDetailsContainer = detailsContainer:addChild(GUI.container(1, 1, detailsContainer.width - ratingsContainer.width, detailsContainer.height)) + addApplicationInfo(textDetailsContainer, application) + + local lines = string.wrap(info[1].description, textDetailsContainer.width - 4) + local textBox = textDetailsContainer:addChild(GUI.textBox(3, 7, textDetailsContainer.width - 4, #lines, nil, 0x888888, lines, 1, 0, 0)) + textBox.eventHandler = nil + + if application.expandedDependencies then + local publicationDependencyExists, resourceDependencyExists = false, false + for i = 1, #application.expandedDependencies do + if application.expandedDependencies[i].publication_name then + publicationDependencyExists = true + else + resourceDependencyExists = true + end + end + + local x, y = 3, textBox.localY + textBox.height + 1 + + if resourceDependencyExists then + textDetailsContainer:addChild(GUI.label(1, y, textDetailsContainer.width, 1, 0x666666, "Структура приложения")):setAlignment(GUI.alignment.horizontal.center, GUI.alignment.vertical.top) + y = y + 2 + + local tree = textDetailsContainer:addChild(GUI.tree(2, y, textDetailsContainer.width - 2, 1, 0xF0F0F0, 0x3C3C3C, 0xAAAAAA, 0xAAAAAA, 0x3C3C3C, 0xE1E1E1, 0xBBBBBB, 0xAAAAAA, 0xC3C3C3, 0x444444)) + + local dependencyTree = {} + for i = 1, #application.expandedDependencies do + if not application.expandedDependencies[i].publication_name then + local idiNahooy = dependencyTree + for blyad in (application.publication_name .. ".app/" .. fs.path(application.expandedDependencies[i].path)):gmatch("[^/]+") do + if not idiNahooy[blyad] then + idiNahooy[blyad] = {} + end + idiNahooy = idiNahooy[blyad] + end + table.insert(idiNahooy, fs.name(application.expandedDependencies[i].path)) + end + end + table.insert(dependencyTree[application.publication_name .. ".app"], "Main.lua") + -- GUI.error(dependencyTree) + + local function pizda(t, offset) + for key, value in pairs(t) do + if type(value) == "table" then + tree:addItem(key, key, offset, true) + tree.expandedItems[key] = true + + pizda(value, offset + 2) + else + tree:addItem(value, value, offset, false) + end + end + end + + pizda(dependencyTree, 1) + + tree.height = #tree.items + tree.eventHandler = nil + y = y + tree.height + 1 + end + + if publicationDependencyExists then + textDetailsContainer:addChild(GUI.label(1, y, textDetailsContainer.width, 1, 0x666666, "Зависимости")):setAlignment(GUI.alignment.horizontal.center, GUI.alignment.vertical.top) + y = y + 2 + + for i = 1, #application.expandedDependencies do + local text = application.expandedDependencies[i].publication_name or fs.name(application.expandedDependencies[i].path) + if application.expandedDependencies[i].publication_name then + local textLength = unicode.len(text) + if x + textLength + 4 > textDetailsContainer.width - 4 then + x, y = 3, y + 2 + end + local button = textDetailsContainer:addChild(GUI.roundedButton(x, y, textLength + 2, 1, 0xBBBBBB, 0xFFFFFF, 0x2D2D2D, 0xFFFFFF, text)) + button.onTouch = function() + newApplicationInfo(application.expandedDependencies[i].file_id) + end + x = x + button.width + 2 + end + end + + y = y + 2 + end + end + + textDetailsContainer.height = math.max( + textDetailsContainer.children[#textDetailsContainer.children].localY + textDetailsContainer.children[#textDetailsContainer.children].height, + ratingsContainer.children[#ratingsContainer.children].localY + ratingsContainer.children[#ratingsContainer.children].height + ) + textDetailsContainer.panel.height = textDetailsContainer.height + + ratingsContainer.height = textDetailsContainer.height + ratingsContainer.panel.height = textDetailsContainer.height + + detailsContainer.height = textDetailsContainer.height + + if config.user.token and config.user.name ~= application.user_name then + layout:addChild(GUI.adaptiveRoundedButton(1, 1, 2, 0, 0x666666, 0xFFFFFF, 0x2D2D2D, 0xFFFFFF, "Написать отзыв")).onTouch = function() + local container = MineOSInterface.addUniversalContainer(window, "Написать отзыв") + container.layout:setCellFitting(2, 1, false, false) + + local input = container.layout:addChild(GUI.input(1, 1, 36, 3, 0xFFFFFF, 0x666666, 0xBBBBBB, 0xFFFFFF, 0x2D2D2D, "", "Оставьте свой высер тут")) + + local pizda = container.layout:addChild(GUI.container(1, 1, 1, 1)) + local eblo = pizda:addChild(GUI.text(1, 1, 0xE1E1E1, "Оцените приложение: ")) + pizda.width = eblo.width + 9 + + local cyka = pizda:addChild(newRatingWidget(eblo.width + 1, 1, 4)) + cyka.eventHandler = function(mainContainer, object, eventData) + if eventData[1] == "touch" then + cyka.rating = (eventData[3] - object.x + 1) / object.width * 5 + MineOSInterface.OSDraw() + end + end + + local govno = container.layout:addChild(GUI.adaptiveRoundedButton(1, 1, 1, 0, 0xFFFFFF, 0x2D2D2D, 0x0, 0xFFFFFF, "Отправить высер")) + govno.disabled = true + govno.colors.disabled.background = 0xAAAAAA + govno.colors.disabled.text = 0xC3C3C3 + govno.onTouch = function() + local success, reason = RawAPIRequest("review", { + token = config.user.token, + publication_id = application.publication_id, + rating = cyka.rating, + comment = input.text, + }) + + container:delete() + + if success then + newApplicationInfo(application.file_id) + else + MineOSInterface.OSDraw() + GUI.error(reason) + end + end + + input.onInputFinished = function() + local textLength, from, to = unicode.len(input.text), 2, 1000 + if textLength >= from and textLength <= to then + govno.disabled = false + else + govno.disabled = true + GUI.error("Слишком охуевший высер. Его длина величиной " .. textLength .. " выходит за границы допустимого диапазона [" .. from .. "; " .. to .. "]") + end + + MineOSInterface.OSDraw() + end + + MineOSInterface.OSDraw() + end + end + + if application.reviews then + -- Отображаем все оценки + layout:addChild(GUI.text(1, 1, 0x666666, "Отзывы пользователей")) + + -- Перечисляем все отзывы + local counter, limit = 0, 10 + + for i = 1, #application.reviews do + if application.reviews[i].comment then + local reviewContainer = layout:addChild(GUI.container(1, 1, layout.width, 4)) + addPanel(reviewContainer) + + local y = 2 + local nameLabel = reviewContainer:addChild(GUI.text(3, y, 0x2D2D2D, application.reviews[i].user_name)) + reviewContainer:addChild(GUI.text(nameLabel.localX + nameLabel.width + 1, y, 0xC3C3C3, "(" .. os.date("%d.%m.%Y в %H:%M", application.reviews[i].timestamp) .. ")")) + y = y + 1 + + reviewContainer:addChild(newRatingWidget(3, y, application.reviews[i].rating)) + y = y + 1 + + local lines = string.wrap(application.reviews[i].comment, reviewContainer.width - 4) + local textBox = reviewContainer:addChild(GUI.textBox(3, y, reviewContainer.width - 4, #lines, nil, 0x888888, lines, 1, 0, 0)) + textBox.eventHandler = nil + y = y + #lines + 1 + + if application.reviews[i].votes then + reviewContainer:addChild(GUI.text(3, y, 0xC3C3C3, application.reviews[i].positive_votes .. " из " .. application.reviews[i].votes .. " пользователей считают этот отзыв полезным")) + y = y + 1 + end + + if config.user.token then + local wasHelpText = reviewContainer:addChild(GUI.text(3, y, 0xC3C3C3, "Был ли этот отзыв полезен?")) + local yesButton = reviewContainer:addChild(GUI.adaptiveButton(wasHelpText.localX + wasHelpText.width + 1, y, 0, 0, nil, 0x666666, nil, 0x2D2D2D, "Да")) + local stripLabel = reviewContainer:addChild(GUI.text(yesButton.localX + yesButton.width + 1, y, 0xC3C3C3, "|")) + local noButton = reviewContainer:addChild(GUI.adaptiveButton(stripLabel.localX + stripLabel.width + 1, y, 0, 0, nil, 0x666666, nil, 0x2D2D2D, "Нет")) + + local function go(rating) + RawAPIRequest("review_vote", { + token = config.user.token, + review_id = application.reviews[i].id, + rating = rating + }, true) + + computer.beep(1500, 0.1) + + wasHelpText.text = "Спасибо за ответ." + wasHelpText.color = 0x666666 + yesButton:delete() + stripLabel:delete() + noButton:delete() + + MineOSInterface.OSDraw() + end + + yesButton.onTouch = function() + go(1) + end + + noButton.onTouch = function() + go(0) + end + + y = y + 1 + end + + reviewContainer.height = y + reviewContainer.panel.height = reviewContainer.height + + counter = counter + 1 + if counter > limit then + break + end + end + end + end + + layout:update() + layout.height = layout.children[#layout.children].localY + layout.children[#layout.children].height - 1 + + status("Ожидание") + else + GUI.error(reason) + end +end + +-------------------------------------------------------------------------------- + +local function applicationWidgetEventHandler(mainContainer, object, eventData) + if eventData[1] == "touch" then + object.parent.panel.colors.background = 0xE1E1E1 + MineOSInterface.OSDraw() + newApplicationInfo(object.parent.application.file_id) + end +end + +local function newApplicationPreview(x, y, application) + local container = GUI.container(x, y, appWidth, appHeight) + + container.application = application + addApplicationInfo(container, application) + + container.panel.eventHandler, + container.image.eventHandler, + container.nameLabel.eventHandler, + container.versionLabel.eventHandler, + container.rating.eventHandler = + applicationWidgetEventHandler, + applicationWidgetEventHandler, + applicationWidgetEventHandler, + applicationWidgetEventHandler, + applicationWidgetEventHandler + + return container +end + +-------------------------------------------------------------------------------- + +editPublication = function() + contentContainer:deleteChildren() + + local layout = contentContainer:addChild(GUI.layout(1, 1, contentContainer.width, contentContainer.height, 3, 1)) + layout:setCellAlignment(1, 1, GUI.alignment.horizontal.right, GUI.alignment.vertical.center) + layout:setCellAlignment(2, 1, GUI.alignment.horizontal.left, GUI.alignment.vertical.center) + layout:setCellFitting(2, 1, true, false) + layout:setCellMargin(1, 1, 1, 0) + + layout:addChild(GUI.text(1, 1, 0x2D2D2D, "Категория:")) + layout:addChild(GUI.text(1, 1, 0x2D2D2D, "Лицензия:")) + layout:addChild(GUI.text(1, 1, 0x2D2D2D, "Имя публикации:")) + layout:addChild(GUI.text(1, 1, 0x2D2D2D, "URL главного файла:")) + local iconHint = layout:addChild(GUI.text(1, 1, 0x2D2D2D, "URL иконки:")) + local pathHint = layout:addChild(GUI.text(1, 1, 0x2D2D2D, "Путь главного файла:")) + layout:addChild(GUI.text(1, 1, 0x2D2D2D, "Описание:")) + layout:addChild(GUI.object(1, 1, 1, 1)) + layout:addChild(GUI.object(1, 1, 1, 1)) + + layout.defaultColumn = 2 + + layout:addChild(GUI.label(1, 1, 36, 1, 0x0, "Опубликовать ПО")):setAlignment(GUI.alignment.horizontal.center, GUI.alignment.vertical.top) + + local categoryComboBox = layout:addChild(GUI.comboBox(1, 1, 36, 1, 0xFFFFFF, 0x666666, 0x999999, 0xE1E1E1)) + for i = 1, #categories do + categoryComboBox:addItem(categories[i]) + end + + local licenseComboBox = layout:addChild(GUI.comboBox(1, 1, 36, 1, 0xFFFFFF, 0x666666, 0x999999, 0xE1E1E1)) + for i = 1, #licenses do + licenseComboBox:addItem(licenses[i]) + end + + local nameInput = layout:addChild(GUI.input(1, 1, 36, 1, 0xFFFFFF, 0x666666, 0xBBBBBB, 0xFFFFFF, 0x2D2D2D, "", "My Script")) + local mainUrlInput = layout:addChild(GUI.input(1, 1, 36, 1, 0xFFFFFF, 0x666666, 0xBBBBBB, 0xFFFFFF, 0x2D2D2D, "", "http://example.com/Main.lua")) + local iconUrlInput = layout:addChild(GUI.input(1, 1, 36, 1, 0xFFFFFF, 0x666666, 0xBBBBBB, 0xFFFFFF, 0x2D2D2D, "", "http://example.com/Icon.pic")) + local mainPathInput = layout:addChild(GUI.input(1, 1, 36, 1, 0xFFFFFF, 0x666666, 0xBBBBBB, 0xFFFFFF, 0x2D2D2D, "", "MyScript.lua")) + local descriptionInput = layout:addChild(GUI.input(1, 1, 36, 1, 0xFFFFFF, 0x666666, 0xBBBBBB, 0xFFFFFF, 0x2D2D2D, "", "This is my cool script")) + + layout:addChild(GUI.label(1, 1, 36, 1, 0x0, "Зависимости и ресурсы")):setAlignment(GUI.alignment.horizontal.center, GUI.alignment.vertical.top) + + local dependenciesLayout = layout:addChild(GUI.layout(1, 1, 36, 1, 2, 1)) + dependenciesLayout:setColumnWidth(1, GUI.sizePolicies.percentage, 1.0) + dependenciesLayout:setColumnWidth(2, GUI.sizePolicies.absolute, 8) + dependenciesLayout:setCellFitting(1, 1, true, false) + dependenciesLayout:setCellMargin(2, 1, 1, 0) + dependenciesLayout:setCellAlignment(1, 1, GUI.alignment.horizontal.left, GUI.alignment.vertical.top) + dependenciesLayout:setCellAlignment(2, 1, GUI.alignment.horizontal.left, GUI.alignment.vertical.top) + dependenciesLayout:setCellDirection(1, 1, GUI.directions.horizontal) + dependenciesLayout:setCellDirection(2, 1, GUI.directions.horizontal) + local dependenciesComboBox = dependenciesLayout:addChild(GUI.comboBox(1, 1, 29, 1, 0xFFFFFF, 0x666666, 0x999999, 0xE1E1E1)) + dependenciesLayout.defaultColumn = 2 + + local addButton = dependenciesLayout:addChild(GUI.button(1, 1, 3, 1, 0x666666, 0xFFFFFF, 0x2D2D2D, 0xFFFFFF, "+")) + local removeButton = dependenciesLayout:addChild(GUI.button(1, 1, 3, 1, 0x666666, 0xFFFFFF, 0x2D2D2D, 0xFFFFFF, "-")) + + local function checkRemoveButton() + local count = dependenciesComboBox:count() + removeButton.disabled = count == 0 + dependenciesComboBox.selectedItem = count + end + checkRemoveButton() + + addButton.onTouch = function() + local container = MineOSInterface.addUniversalContainer(window, "Добавить зависимость") + + container.layout:setCellFitting(2, 1, false, false) + + local dependencyTypeComboBox = container.layout:addChild(GUI.comboBox(1, 1, 36, 3, 0xFFFFFF, 0x666666, 0x999999, 0xE1E1E1)) + dependencyTypeComboBox:addItem("Существующая публикация") + dependencyTypeComboBox:addItem("Файл ресурсов", categoryComboBox.selectedItem > 1) + + local publicationNameInput = container.layout:addChild(GUI.input(1, 1, 36, 3, 0xFFFFFF, 0x666666, 0xBBBBBB, 0xFFFFFF, 0x2D2D2D, "", "Double Buffering")) + local urlInput = container.layout:addChild(GUI.input(1, 1, 36, 3, 0xFFFFFF, 0x666666, 0xBBBBBB, 0xFFFFFF, 0x2D2D2D, "", "http://example.com/English.lang")) + local pathInput = container.layout:addChild(GUI.input(1, 1, 36, 3, 0xFFFFFF, 0x666666, 0xBBBBBB, 0xFFFFFF, 0x2D2D2D, "", "Localization/English.lang")) + + local button = container.layout:addChild(GUI.button(1, 1, 36, 3, 0x666666, 0xFFFFFF, 0x0, 0xFFFFFF, "Добавить")) + button.onTouch = function() + if dependencyTypeComboBox.selectedItem == 1 then + dependenciesComboBox:addItem(publicationNameInput.text).publication_name = publicationNameInput.text + else + local item = dependenciesComboBox:addItem(pathInput.text) + item.path = pathInput.text + item.source_url = urlInput.text + end + + checkRemoveButton() + + container:delete() + MineOSInterface.OSDraw() + end + + publicationNameInput.onInputFinished = function() + if dependencyTypeComboBox.selectedItem == 1 then + button.disabled = #publicationNameInput.text == 0 + else + button.disabled = #pathInput.text == 0 or #urlInput.text == 0 + end + end + pathInput.onInputFinished, urlInput.onInputFinished = publicationNameInput.onInputFinished, publicationNameInput.onInputFinished + + dependencyTypeComboBox.onItemSelected = function() + pathInput.hidden = dependencyTypeComboBox.selectedItem == 1 + urlInput.hidden = pathInput.hidden + publicationNameInput.hidden = not pathInput.hidden + + MineOSInterface.OSDraw() + end + + publicationNameInput.onInputFinished() + dependencyTypeComboBox.onItemSelected() + end + + removeButton.onTouch = function() + dependenciesComboBox:getItem(dependenciesComboBox.selectedItem):delete() + checkRemoveButton() + MineOSInterface.OSDraw() + end + + local publishButton = layout:addChild(GUI.adaptiveRoundedButton(1, 1, 2, 0, 0x666666, 0xFFFFFF, 0x2D2D2D, 0xFFFFFF, "Опубликовать")) + + nameInput.onInputFinished = function() + publishButton.disabled = not (#nameInput.text > 0 and #mainUrlInput.text > 0 and (iconUrlInput.hidden and true or #iconUrlInput.text > 0) and (mainPathInput.hidden and true or #mainPathInput.text > 0) and #descriptionInput.text > 0) + end + mainUrlInput.onInputFinished, mainPathInput.onInputFinished, iconUrlInput.onInputFinished, descriptionInput.onInputFinished = nameInput.onInputFinished, nameInput.onInputFinished, nameInput.onInputFinished, nameInput.onInputFinished + + categoryComboBox.onItemSelected = function() + iconHint.hidden = categoryComboBox.selectedItem > 1 + iconUrlInput.hidden = iconHint.hidden + + pathHint.hidden = not iconHint.hidden + mainPathInput.hidden = pathHint.hidden + + nameInput.onInputFinished() + MineOSInterface.OSDraw() + end + + publishButton.onTouch = function() + local dependencies = {} + for i = 1, dependenciesComboBox:count() do + local item = dependenciesComboBox:getItem(i) + if item.publication_name then + table.insert(dependencies, { + publication_name = item.publication_name + }) + else + table.insert(dependencies, { + source_url = item.source_url, + path = "Resources/" .. item.path + }) + end + end + + if categoryComboBox.selectedItem == 1 then + table.insert(dependencies, { + source_url = iconUrlInput.text, + path = "Resources/Icon.pic" + }) + end + + local success, reason = RawAPIRequest("upload", { + token = config.user.token, + name = web.encode(nameInput.text), + source_url = mainUrlInput.text, + path = web.encode(categoryComboBox.selectedItem == 1 and "Main.lua" or mainPathInput.text), + description = web.encode(descriptionInput.text), + license_id = licenseComboBox.selectedItem, + dependencies = dependencies, + category_id = categoryComboBox.selectedItem, + }) + + if success then + window.tabBar.selectedItem = categoryComboBox.selectedItem + config.orderBy = 2 + updateFileList(window.tabBar.selectedItem) + else + GUI.error(reason) + end + end + + categoryComboBox.onItemSelected() +end + +-------------------------------------------------------------------------------- + +updateFileList = function(category_id) + status("Обновление списка приложений...") + + -- Получаем общий список приложений + local list, reason = fieldAPIRequest("list", "list", { + publications_only = true, + category_id = category_id, + fields = { + "average_rating", + "dependencies", + "publication_name", + "user_name", + "path", + "source_url", + }, + order_by = orderBys[config.orderBy], + order_direction = orderDirections[config.orderDirection], + offset = currentPage * appsPerPage, + count = appsPerPage + 1, + search = search + }) + + if list then + contentContainer:deleteChildren() + + local y = 2 + + local layout = contentContainer:addChild(GUI.layout(1, y, contentContainer.width, 1, 1, 1)) + layout:setCellDirection(1, 1, GUI.directions.horizontal) + layout:setCellSpacing(1, 1, 2) + + local input = layout:addChild(GUI.input(1, 1, 20, layout.height, 0xFFFFFF, 0x2D2D2D, 0x666666, 0xFFFFFF, 0x2D2D2D, search or "", "Поиск", true)) + input.onInputFinished = function() + if #input.text == 0 then + search = nil + else + search = input.text + end + + updateFileList(category_id) + end + + local orderByComboBox = layout:addChild(GUI.comboBox(1, 1, 18, layout.height, 0xFFFFFF, 0x666666, 0x999999, 0xE1E1E1)) + orderByComboBox:addItem("По рейтингу") + orderByComboBox:addItem("По дате") + orderByComboBox:addItem("По имени") + orderByComboBox.selectedItem = config.orderBy + + local orderDirectionComboBox = layout:addChild(GUI.comboBox(1, 1, 18, layout.height, 0xFFFFFF, 0x666666, 0x999999, 0xE1E1E1)) + orderDirectionComboBox:addItem("По убыванию") + orderDirectionComboBox:addItem("По возрастанию") + orderDirectionComboBox.selectedItem = config.orderDirection + + orderByComboBox.onItemSelected = function() + config.orderBy = orderByComboBox.selectedItem + config.orderDirection = orderDirectionComboBox.selectedItem + updateFileList(category_id) + saveConfig() + end + orderDirectionComboBox.onItemSelected = orderByComboBox.onItemSelected + + if config.user.token then + local uploadButton = layout:addChild(GUI.adaptiveRoundedButton(1, 1, 1, 0, 0x666666, 0xFFFFFF, 0x2D2D2D, 0xFFFFFF, "Опубликовать ПО")) + uploadButton.onTouch = function() + editPublication() + end + end + + y = y + layout.height + 1 + + local navigationLayout = contentContainer:addChild(GUI.layout(1, contentContainer.height - 1, contentContainer.width, 1, 1, 1)) + navigationLayout:setCellDirection(1, 1, GUI.directions.horizontal) + navigationLayout:setCellSpacing(1, 1, 2) + + local function switchPage(forward) + currentPage = currentPage + (forward and 1 or -1) + updateFileList(category_id) + end + + local backButton = navigationLayout:addChild(GUI.adaptiveRoundedButton(1, 1, 1, 0, 0xFFFFFF, 0x666666, 0x2D2D2D, 0xFFFFFF, "<")) + backButton.disabled = currentPage == 0 + backButton.onTouch = function() + switchPage(false) + end + + navigationLayout:addChild(GUI.text(1, 1, 0x666666, "Страница " .. (currentPage + 1))) + local nextButton = navigationLayout:addChild(GUI.adaptiveRoundedButton(1, 1, 1, 0, 0xFFFFFF, 0x666666, 0x2D2D2D, 0xFFFFFF, ">")) + nextButton.disabled = #list <= appsPerPage + nextButton.onTouch = function() + switchPage(true) + end + + local xStart = math.floor(1 + contentContainer.width / 2 - (appsPerWidth * (appWidth + appHSpacing) - appHSpacing) / 2) + local x, counter = xStart, 1 + for i = 1, #list do + -- Сам ты пидор! + list[i].category_id = category_id + -- Если мы чекаем приложухи, и в этой публикации есть какие-то зависимости + if category_id == 1 and list[i].dependencies then + -- Получаем лист этих зависимостей по идшникам, выдавая только путь и урлку + local dependencies, reason = fieldAPIRequest("list", "list", { + file_ids = list[i].dependencies, + fields = { + "path", + "source_url" + } + }) + + if dependencies then + list[i].expandedDependencies = dependencies + else + GUI.error(reason) + end + end + + contentContainer:addChild(newApplicationPreview(x, y, list[i])) + + x = x + appWidth + appHSpacing + if counter >= appsPerPage then + break + elseif counter % appsPerWidth == 0 then + x, y = xStart, y + appHeight + appVSpacing + end + counter = counter + 1 + + -- Если мы тока шо создали приложеньку, от отрисовываем содержимое сразу же + if category_id == 1 then + MineOSInterface.OSDraw() + end + end + else + GUI.error(reason) + end + + status("Ожидание") +end + +window.onResize = function(width, height) + window.backgroundPanel.width = width + window.backgroundPanel.height = height - 4 + contentContainer.width = width + contentContainer.height = window.height - 4 + window.tabBar.width = width + statusWidget.width = window.width + statusWidget.localY = window.height + + appsPerWidth = math.floor((contentContainer.width + appHSpacing) / (appWidth + appHSpacing)) + appsPerHeight = math.floor((contentContainer.height - 6 + appVSpacing) / (appHeight + appVSpacing)) + appsPerPage = appsPerWidth * appsPerHeight +end + +local function account() + contentContainer:deleteChildren() + + local layout = contentContainer:addChild(GUI.layout(1, 1, contentContainer.width, contentContainer.height, 1, 1)) + + if config.user.token then + local list, reason = fieldAPIRequest("list", "list", { + file_ids = {file_id}, + fields = { + "publication_id", + "publication_name", + "file_id", + }, + publications_only = true, + user_id = config.user.id, + order_by = "publication_name", + }) + + if list then + layout:addChild(GUI.text(1, 1, 0x2D2D2D, "Профиль")) + layout:addChild(GUI.label(1, 1, 36, 1, 0x888888, "Имя: " .. config.user.name)) + layout:addChild(GUI.label(1, 1, 36, 1, 0x888888, "E-Mail: " .. config.user.email)) + layout:addChild(GUI.label(1, 1, 36, 1, 0x888888, "Дата регистрации: " .. os.date("%d.%m.%Y", config.user.timestamp))) + + layout:addChild(GUI.adaptiveRoundedButton(1, 1, 2, 0, 0xAAAAAA, 0xFFFFFF, 0x2D2D2D, 0xFFFFFF, "Выход")).onTouch = function() + config.user = {} + saveConfig() + account() + end + + if #list > 0 then + layout:addChild(GUI.text(1, 1, 0x2D2D2D, "Публикации")) + + local comboBox = layout:addChild(GUI.comboBox(1, 1, 36, 1, 0xFFFFFF, 0x666666, 0x999999, 0xE1E1E1)) + for i = 1, #list do + comboBox:addItem(list[i].publication_name) + end + + local buttonsLayout = layout:addChild(GUI.layout(1, 1, layout.width, 1, 1, 1)) + buttonsLayout:setCellDirection(1, 1, GUI.directions.horizontal) + buttonsLayout:setCellSpacing(1, 1, 3) + buttonsLayout:addChild(GUI.adaptiveRoundedButton(1, 1, 1, 0, 0xAAAAAA, 0xFFFFFF, 0x2D2D2D, 0xFFFFFF, "Открыть")).onTouch = function() + newApplicationInfo(list[comboBox.selectedItem].file_id) + end + buttonsLayout:addChild(GUI.adaptiveRoundedButton(1, 1, 1, 0, 0xAAAAAA, 0xFFFFFF, 0x2D2D2D, 0xFFFFFF, "Изменить")) + buttonsLayout:addChild(GUI.adaptiveRoundedButton(1, 1, 1, 0, 0xAAAAAA, 0xFFFFFF, 0x2D2D2D, 0xFFFFFF, "Удалить")) + end + + + else + GUI.error(reason) + end + else + local function addShit(register) + layout:deleteChildren() + + local text = register and "Register" or "Login" + layout:addChild(GUI.label(1, 1, 36, 1, 0x0, text)):setAlignment(GUI.alignment.horizontal.center, GUI.alignment.vertical.top) + + if register then + layout.nameInput = layout:addChild(GUI.input(1, 1, 36, 3, 0xFFFFFF, 0x666666, 0xBBBBBB, 0xFFFFFF, 0x2D2D2D, "", "Username")) + end + + layout.emailInput = layout:addChild(GUI.input(1, 1, 36, 3, 0xFFFFFF, 0x666666, 0xBBBBBB, 0xFFFFFF, 0x2D2D2D, config.user.email or "", register and "E-mail" or "E-Mail или никнейм")) + layout.passwordInput = layout:addChild(GUI.input(1, 1, 36, 3, 0xFFFFFF, 0x666666, 0xBBBBBB, 0xFFFFFF, 0x2D2D2D, config.user.password or "", "Password", false, "*")) + + if register then + layout.submit = layout:addChild(GUI.button(1, 1, 36, 3, 0xAAAAAA, 0xFFFFFF, 0x666666, 0xFFFFFF, text)) + end + end + + addShit(false) + + layout:addChild(GUI.button(1, 1, 36, 3, 0xAAAAAA, 0xFFFFFF, 0x666666, 0xFFFFFF, "Login")).onTouch = function() + local user, reason = fieldAPIRequest("user", "login", { + [(string.find(layout.emailInput.text, "@") and "email" or "name")] = layout.emailInput.text, + password = layout.passwordInput.text + }) + + if user then + config.user = { + token = user.token, + name = user.name, + id = user.id, + email = user.email, + timestamp = user.timestamp, + password = layout.passwordInput.text, + } + saveConfig() + account() + else + GUI.error(reason) + end + end + + local registerLayout = layout:addChild(GUI.layout(1, 1, layout.width, 1, 1, 1)) + registerLayout:setCellDirection(1, 1, GUI.directions.horizontal) + + local registerText = registerLayout:addChild(GUI.text(1, 1, 0xAAAAAA, "Еще не зарегистрированы?")) + registerLayout:addChild(GUI.adaptiveButton(1, 1, 0, 0, nil, 0x666666, nil, 0x0, "Создать аккаунт")).onTouch = function() + addShit(true) + + layout.submit.onTouch = function() + local information, reason = fieldAPIRequest("information", "register", { + name = layout.nameInput.text, + email = layout.emailInput.text, + password = layout.passwordInput.text, + }) + + if information then + GUI.error("Все заебись! Чекни свое мыло (" .. layout.emailInput.text .. ") и папку спама, чтобы подтвердить свой акк") + else + GUI.error(reason) + end + end + end + end +end + +local function loadCategory(id) + currentPage = 0 + updateFileList(id) +end + +window.tabBar:addItem(categories[1]).onTouch = function() + loadCategory(1) +end + +window.tabBar:addItem(categories[2]).onTouch = function() + loadCategory(2) +end + +window.tabBar:addItem(categories[3]).onTouch = function() + loadCategory(3) +end + +window.tabBar:addItem("Обновления").onTouch = function() + +end + +window.tabBar:addItem("Аккаунт").onTouch = function() + account() +end + +-------------------------------------------------------------------------------- + +loadConfig() +window:resize(window.width, window.height) +-- window.tabBar:getItem(2).onTouch() +newApplicationInfo(169) + + + + + + diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/EBAMARKET2.app/Resources/Icon.pic b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/EBAMARKET2.app/Resources/Icon.pic new file mode 100644 index 0000000000000000000000000000000000000000..1e0deecfa632d0f9ab14ada7d678dd78b68790d6 GIT binary patch literal 117 zcmXYp%L#xm5JWS(o6VMCFM{C7(mY5I1UpDO>OGC scaleSlider.maximumValue then + scaleSlider.value = scaleSlider.maximumValue + end + + update() + + mainContainer:draw() + buffer.draw() + end +end + +--------------------------------------------------------------------------------------------------------- + +update() +mainContainer:draw() +buffer.draw() + + + diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/Graph2.app/Resources/Icon.pic b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/Graph2.app/Resources/Icon.pic new file mode 100644 index 0000000000000000000000000000000000000000..dbdfb99756f5ace01b0d670ba7de3430aa722770 GIT binary patch literal 161 zcmXYp!3_d23$|EbYmg?;r8c=*2crpM&KZe_L6p#|TF57#4 NirXWQ`mWh*`40l!B`g2{ literal 0 HcmV?d00001 diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/GuessWord.lnk b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/GuessWord.lnk new file mode 100644 index 00000000..25f4d112 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/GuessWord.lnk @@ -0,0 +1 @@ +/MineOS/Applications/GuessWord.app/ \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/HEX.lnk b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/HEX.lnk new file mode 100644 index 00000000..1b7a24e1 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/HEX.lnk @@ -0,0 +1 @@ +/MineOS/Applications/HEX.app/ \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/HoloClock.lnk b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/HoloClock.lnk new file mode 100644 index 00000000..b254f30d --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/HoloClock.lnk @@ -0,0 +1 @@ +/MineOS/Applications/HoloClock.app/ \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/HoloEdit.lnk b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/HoloEdit.lnk new file mode 100644 index 00000000..7ec71be4 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/HoloEdit.lnk @@ -0,0 +1 @@ +/MineOS/Applications/HoloEdit.app/ \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/InfoPanel.lnk b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/InfoPanel.lnk new file mode 100644 index 00000000..c79e511c --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/InfoPanel.lnk @@ -0,0 +1 @@ +/MineOS/Applications/InfoPanel.app/ \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/PS4.app/.icons b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/PS4.app/.icons new file mode 100644 index 00000000..3588aaf4 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/PS4.app/.icons @@ -0,0 +1 @@ +{["Main.lua"]={["y"]=2,["x"]=17},["Resources"]={["y"]=2,["x"]=3}} \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/PS4.app/Main.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/PS4.app/Main.lua new file mode 100644 index 00000000..4a587361 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/PS4.app/Main.lua @@ -0,0 +1,299 @@ + +-- package.loaded.color = nil +-- package.loaded.GUI = nil + +require("advancedLua") +local computer = require("computer") +local component = require("component") +local fs = require("filesystem") +local buffer = require("doubleBuffering") +local GUI = require("GUI") +local unicode = require("unicode") +local image = require("image") +local keyboard = require("keyboard") +local color = require("color") + +local resourcesPath = fs.path(getCurrentScript()) .. "/Resources/" +local toolsPath = resourcesPath .. "/Tools/" + +--------------------------------------------------------------------------------------------------------- + +-- /MineOS/Desktop/PS4.app/Main.lua +local mainContainer = GUI.fullScreenContainer() +mainContainer.backgroundPanel = mainContainer:addChild(GUI.panel(1, 2, mainContainer.width, mainContainer.height - 1, 0x262626)) + +--------------------------------------------------------------------------------------------------------- + +mainContainer.drawingZone = mainContainer:addChild(GUI.object(8, 3, 1, 1)) +mainContainer.drawingZone.layers = {current = 1} + +local function mergeLayersAtPixel(x, y) + local background, foreground, alpha, symbol = image.get(mainContainer.drawingZone.layers[1], x, y) + + for layer = 2, #mainContainer.drawingZone.layers do + local backgroundNext, foregroundNext, alphaNext, symbolNext = image.get(mainContainer.drawingZone.layers[layer], x, y) + if backgroundNext then + if background then + background, alpha = color.blendRGBA(background, backgroundNext, alpha / 255, alphaNext / 255) + alpha = alpha * 255 + else + background, alpha = backgroundNext, alphaNext / 255 + end + + foreground = foregroundNext + symbol = symbolNext + end + end + + return background or 0x0, foreground or 0x0, alpha or 255, symbol or " " +end + +mainContainer.drawingZone.draw = function(object) + local step = false + for y = 1, object.height do + for x = 1, object.width do + local background, foreground, alpha, symbol = mergeLayersAtPixel(x, y) + buffer.set( + object.x + x - 1, + object.y + y - 1, + color.blend(step and 0xFFFFFF or 0xDDDDDD, background, alpha / 255), + foreground, + symbol + ) + + step = not step + end + end +end + +mainContainer.drawingZone.eventHandler = function(mainContainer, object, eventData) + mainContainer.leftToolbar.toolsContainer.children[mainContainer.leftToolbar.toolsContainer.current].module.onEvent(mainContainer, eventData) +end + +--------------------------------------------------------------------------------------------------------- + +mainContainer.menu = mainContainer:addChild(GUI.menu(1, 1, mainContainer.width, 0xDDDDDD, 0x666666, 0x3366CC, 0xFFFFFF)) +mainContainer.menu:addItem("PS", 0x0) +mainContainer.menu:addItem("File", 0x444444).onTouch = function() + +end +mainContainer.menu:addItem("Edit", 0x444444).onTouch = function() + +end + +--------------------------------------------------------------------------------------------------------- + +local layersToolbarWidth = math.floor(mainContainer.width * 0.2) +mainContainer.layersToolbar = mainContainer:addChild(GUI.container(mainContainer.width - layersToolbarWidth + 1, 2, layersToolbarWidth, mainContainer.height - 1)) +mainContainer.layersToolbar.layersObject = mainContainer.layersToolbar:addChild(GUI.object(1, 1, mainContainer.layersToolbar.width, mainContainer.layersToolbar.height)) +mainContainer.layersToolbar.layersObject.offset = 0 + +mainContainer.layersToolbar.layersObject.draw = function(object) + buffer.square(object.x, object.y, object.width, object.height, 0x444444, 0x555555, " ") + + local y = object.y + object.offset + buffer.text(object.x, y, 0x262626, string.rep("─", object.width)) + buffer.text(object.x + 5, y, 0x262626, "┬") + y = y + 1 + for i = #mainContainer.drawingZone.layers, 1, -1 do + if i == mainContainer.drawingZone.layers.current then + buffer.square(object.x, y, object.width, 3, 0x555555, 0xAAAAAA, " ") + end + + -- Миниатюра + buffer.square(object.x + 7, y, 6, 3, 0xFFFFFF) + + -- Текст + buffer.text(object.x + 14, y + 1, 0xAAAAAA, mainContainer.drawingZone.layers[i].text) + + -- Кнопочка оффанья + if mainContainer.drawingZone.layers[i].hidden then + buffer.set(object.x + 2, y + 1, 0x3C3C3C, 0xAAAAAA, " ") + else + buffer.set(object.x + 2, y + 1, 0x3C3C3C, 0xAAAAAA, "*") + end + + -- Рамки + for i = 1, 3 do + buffer.text(object.x + 5, y + i - 1, 0x262626, "│") + end + buffer.text(object.x, y + 3, 0x262626, string.rep("─", object.width)) + buffer.text(object.x + 5, y + 3, 0x262626, i > 1 and "┼" or "┴") + + + y = y + 4 + end +end + +mainContainer.layersToolbar.layersObject.eventHandler = function(mainContainer, object, eventData) + if eventData[1] == "touch" then + local index = math.ceil((eventData[4] - object.y - object.offset + 1) / 4) + + mainContainer.drawingZone.layers.current = #mainContainer.drawingZone.layers - index + 1 + + mainContainer:draw() + buffer.draw() + elseif eventData[1] == "scroll" then + object.offset = object.offset + eventData[5] + if object.offset > 0 then + object.offset = 0 + end + + mainContainer:draw() + buffer.draw() + end +end + +local function newLayer(text) + local atIndex = #mainContainer.drawingZone.layers > 0 and mainContainer.drawingZone.layers.current + 1 or 1 + + table.insert( + mainContainer.drawingZone.layers, + atIndex, + { + text = text, + hidden = false, + [1] = mainContainer.drawingZone.width, + [2] = mainContainer.drawingZone.height + } + ) +end + +--------------------------------------------------------------------------------------------------------- + +mainContainer.leftToolbar = mainContainer:addChild(GUI.container(1, 2, 5, mainContainer.height - 1)) +mainContainer.leftToolbar:addChild(GUI.panel(1, 1, mainContainer.leftToolbar.width, mainContainer.leftToolbar.height, 0x444444)) +mainContainer.leftToolbar.toolsContainer = mainContainer.leftToolbar:addChild(GUI.container(1, 1, mainContainer.leftToolbar.width, mainContainer.leftToolbar.height)) + +local function toolDraw(object) + local background, foreground = object.state and 0x3C3C3C or 0x555555, object.state and 0xAAAAAA or 0xAAAAAA + buffer.square(object.x, object.y, object.width, object.height, background, foreground, " ") + buffer.set(math.floor(object.x + object.width / 2), math.floor(object.y + object.height / 2), background, foreground, object.module.shortcut) + + return object +end + +local function selectTool(index) + mainContainer.leftToolbar.toolsContainer.current = index + for i = 1, #mainContainer.leftToolbar.toolsContainer.children do + mainContainer.leftToolbar.toolsContainer.children[i].state = i == index + end +end + +local function toolEventHandler(mainContainer, object, eventData) + if eventData[1] == "touch" then + selectTool(object:indexOf()) + + mainContainer:draw() + buffer.draw() + end +end + +local function newTool(y, path) + local object = GUI.object(1, y, mainContainer.leftToolbar.width, 3) + + local success, reason = dofile(path) + if success then + object.module = success + else + error("Failed to load module: " .. tostring(reason)) + end + object.draw = toolDraw + object.eventHandler = toolEventHandler + + return object +end + +local y = 1 +local toolsList = fs.sortedList(toolsPath, "name", false) +for i = 1, #toolsList do + y = y + mainContainer.leftToolbar.toolsContainer:addChild(newTool(y, toolsPath .. toolsList[i])).height +end + +--------------------------------------------------------------------------------------------------------- + +local function colorSelectorDraw(object) + buffer.square(object.x, object.y, object.width, object.height, object.color, 0x0, " ") +end + +local function colorSelectorEventHandler(mainContainer, object, eventData) + if eventData[1] == "touch" then + object.color = require("palette").show(math.floor(mainContainer.width / 2 - 35), math.floor(mainContainer.height / 2 - 12), object.color) or object.color + mainContainer:draw() + buffer.draw() + end +end + +local function newColorSelector(x, y, width, height, color) + local object = GUI.object(x, y, width, height) + + object.color = color + object.draw = colorSelectorDraw + object.eventHandler = colorSelectorEventHandler + + return object +end + +local colorSelectorsY = mainContainer.leftToolbar.height - 4 +mainContainer.leftToolbar.secondColorSelector = mainContainer.leftToolbar:addChild(newColorSelector(2, colorSelectorsY + 1, 4, 2, 0xFF0000)) +mainContainer.leftToolbar.firstColorSelector = mainContainer.leftToolbar:addChild(newColorSelector(1, colorSelectorsY, 4, 2, 0x0000FF)) + +--------------------------------------------------------------------------------------------------------- + +local function new(width, height, background, foreground, alpha, symbol) + mainContainer.drawingZone.width, mainContainer.drawingZone.height = width, height + newLayer("Layer 1") +end + +--------------------------------------------------------------------------------------------------------- + +mainContainer.eventHandler = function(mainContainer, object, eventData) + if eventData[1] == "key_down" then + for i = 1, #mainContainer.leftToolbar.toolsContainer.children do + if mainContainer.leftToolbar.toolsContainer.children[i].module.keyCode == eventData[4] then + selectTool(i) + mainContainer:draw() + buffer.draw() + + break + end + end + + -- N + if eventData[4] == 49 then + newLayer("Layer " .. #mainContainer.drawingZone.layers + 1) + mainContainer:draw() + buffer.draw() + elseif eventData[4] == 45 then + mainContainer.leftToolbar.firstColorSelector.color, mainContainer.leftToolbar.secondColorSelector.color = mainContainer.leftToolbar.secondColorSelector.color, mainContainer.leftToolbar.firstColorSelector.color + mainContainer:draw() + buffer.draw() + end + end +end + +--------------------------------------------------------------------------------------------------------- + +buffer.flush() +buffer.draw(true) + +selectTool(2) +new(51, 19, 0x0, 0xFFFFFF, 0x0, "A") +mainContainer:draw() +buffer.draw(true) +mainContainer:startEventHandling() + + + + + + + + + + + + + + + diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/PS4.app/Resources/Icon.pic b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/PS4.app/Resources/Icon.pic new file mode 100644 index 0000000000000000000000000000000000000000..e7d2f0beb1f4d8e9a159f2b44c89aa1568c9e053 GIT binary patch literal 113 zcmXYo%Mn085JdOQtg&IZ5(Y(aqRIv&3ZZQP5@Aa1sXwoKpSHzPBn0a;69m)PSnCx% kL4~AvNUt+7`Jq-F87K&xWL7|CHwA^~zWo0Z>-4A#J|3wHZ2$lO literal 0 HcmV?d00001 diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/PS4.app/Resources/Tools/01_Move.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/PS4.app/Resources/Tools/01_Move.lua new file mode 100644 index 00000000..e842eba5 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/PS4.app/Resources/Tools/01_Move.lua @@ -0,0 +1,34 @@ + +local image = require("image") +local buffer = require("doubleBuffering") +local tool = {} + +--------------------------------------------------------------------------------------------------------- + +tool.shortcut = "M" +tool.keyCode = 47 +tool.lastTouch = {x = 0, y = 0} +tool.offset = {x = 0, y = 0} + +tool.onSelected = function(mainContainer) + +end + +tool.onDeselected = function(mainContainer) + +end + +tool.onEvent = function(mainContainer, eventData) + if eventData[1] == "touch" then + tool.lastTouch.x, tool.lastTouch.y = eventData[3], eventData[4] + elseif eventData[1] == "drag" then + local offset = eventData[3] - tool.lastTouch.x, eventData[3] - tool.lastTouch.y + + mainContainer:draw() + buffer.draw() + end +end + +--------------------------------------------------------------------------------------------------------- + +return tool \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/PS4.app/Resources/Tools/02_Brush.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/PS4.app/Resources/Tools/02_Brush.lua new file mode 100644 index 00000000..f28e91ed --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/PS4.app/Resources/Tools/02_Brush.lua @@ -0,0 +1,31 @@ + +local image = require("image") +local buffer = require("doubleBuffering") +local tool = {} + +--------------------------------------------------------------------------------------------------------- + +tool.shortcut = "B" +tool.keyCode = 48 + +tool.onSelected = function(mainContainer) + +end + +tool.onDeselected = function(mainContainer) + +end + +tool.onEvent = function(mainContainer, eventData) + if eventData[1] == "touch" or eventData[1] == "drag" then + local x, y = eventData[3] - mainContainer.drawingZone.x + 1, eventData[4] - mainContainer.drawingZone.y + 1 + image.set(mainContainer.drawingZone.layers[mainContainer.drawingZone.layers.current], x, y, mainContainer.leftToolbar.firstColorSelector.color, 0x0, 0x0, " ") + + mainContainer:draw() + buffer.draw() + end +end + +--------------------------------------------------------------------------------------------------------- + +return tool \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/PS4.app/Resources/Tools/03_Eraser.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/PS4.app/Resources/Tools/03_Eraser.lua new file mode 100644 index 00000000..2a5cc178 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/PS4.app/Resources/Tools/03_Eraser.lua @@ -0,0 +1,31 @@ + +local image = require("image") +local buffer = require("doubleBuffering") +local tool = {} + +--------------------------------------------------------------------------------------------------------- + +tool.shortcut = "E" +tool.keyCode = 18 + +tool.onSelected = function(mainContainer) + +end + +tool.onDeselected = function(mainContainer) + +end + +tool.onEvent = function(mainContainer, eventData) + if eventData[1] == "touch" or eventData[1] == "drag" then + local x, y = eventData[3] - mainContainer.drawingZone.x + 1, eventData[4] - mainContainer.drawingZone.y + 1 + image.set(mainContainer.drawingZone.layers[mainContainer.drawingZone.layers.current], x, y, 0x0, 0x0, 0xFF, " ") + + mainContainer:draw() + buffer.draw() + end +end + +--------------------------------------------------------------------------------------------------------- + +return tool \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/Palette.lnk b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/Palette.lnk new file mode 100644 index 00000000..13ba3a77 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/Palette.lnk @@ -0,0 +1 @@ +/MineOS/Applications/Palette.app/ \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/PrintImage.lnk b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/PrintImage.lnk new file mode 100644 index 00000000..9c847d93 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/PrintImage.lnk @@ -0,0 +1 @@ +/MineOS/Applications/PrintImage.app/ \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/QuantumCube.lnk b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/QuantumCube.lnk new file mode 100644 index 00000000..693aa456 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/QuantumCube.lnk @@ -0,0 +1 @@ +/MineOS/Applications/QuantumCube.app/ \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/Radio.lnk b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/Radio.lnk new file mode 100644 index 00000000..12aee897 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/Radio.lnk @@ -0,0 +1 @@ +/MineOS/Applications/Radio.app/ \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/RayWalk.lnk b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/RayWalk.lnk new file mode 100644 index 00000000..c576d268 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/RayWalk.lnk @@ -0,0 +1 @@ +/MineOS/Applications/RayWalk.app/ \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/RunningString.lnk b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/RunningString.lnk new file mode 100644 index 00000000..2cee86d8 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/RunningString.lnk @@ -0,0 +1 @@ +/MineOS/Applications/RunningString.app/ \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/Shooting.lnk b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/Shooting.lnk new file mode 100644 index 00000000..8ca2bf42 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/Shooting.lnk @@ -0,0 +1 @@ +/MineOS/Applications/Shooting.app \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/Spinner.app/Main.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/Spinner.app/Main.lua new file mode 100644 index 00000000..ce205e53 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/Spinner.app/Main.lua @@ -0,0 +1,93 @@ + +require("advancedLua") +local fs = require("filesystem") +local buffer = require("doubleBuffering") +local color = require("color") +local image = require("image") +local GUI = require("GUI") + +------------------------------------------------------------------------------------------ + +local spinnersPath = fs.path(getCurrentScript()) .. "/Resources/" +local spinners = {} +local currentSpinner = 1 +local spinnerLimit = 8 +local spinnerHue = math.random(0, 360) +local spinnerHueStep = 20 + +local mainContainer = GUI.fullScreenContainer() +mainContainer:addChild(GUI.panel(1, 1, mainContainer.width, mainContainer.height, 0x0)) +local spinnerImage = mainContainer:addChild(GUI.image(1, 1, {1, 1})) + +------------------------------------------------------------------------------------------ + +local function changeColor(hue, saturation) + for i = 1, #spinners do + for y = 1, image.getHeight(spinners[i]) do + for x = 1, image.getWidth(spinners[i]) do + local background, foreground, alpha, symbol = image.get(spinners[i], x, y) + local hBackground, sBackground, bBackground = color.IntegerToHSB(background) + local hForeground, sForeground, bForeground = color.IntegerToHSB(foreground) + image.set( + spinners[i], + x, + y, + color.HSBToInteger(hue, saturation, bBackground), + color.HSBToInteger(hue, saturation, bForeground), + alpha, + symbol + ) + end + end + end + spinnerImage.image = spinners[currentSpinner] +end + +mainContainer.eventHandler = function(mainContainer, object, eventData) + if eventData[1] == "key_down" then + mainContainer:stopEventHandling() + elseif eventData[1] == "touch" then + spinnerHue = spinnerHue + spinnerHueStep * (eventData[5] == 1 and -1 or 1) + if spinnerHue > 360 then + spinnerHue = 0 + elseif spinnerHue < 0 then + spinnerHue = 360 + end + changeColor(spinnerHue, 1) + end + + currentSpinner = currentSpinner + 1 + if currentSpinner > #spinners then + currentSpinner = 1 + end + spinnerImage.image = spinners[currentSpinner] + + mainContainer:draw() + buffer.draw() +end + +------------------------------------------------------------------------------------------ + +for i = 1, spinnerLimit do + spinners[i] = image.load(spinnersPath .. i .. ".pic") +end +spinnerImage.width = image.getWidth(spinners[currentSpinner]) +spinnerImage.height = image.getHeight(spinners[currentSpinner]) +spinnerImage.localX = math.floor(mainContainer.width / 2 - spinnerImage.width / 2) +spinnerImage.localY = math.floor(mainContainer.height / 2 - spinnerImage.height/ 2) + +changeColor(spinnerHue, 1) +buffer.flush() +mainContainer:draw() +buffer.draw(true) + +mainContainer:startEventHandling(0) + + + + + + + + + diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/Spinner.app/Resources/1.pic b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/Spinner.app/Resources/1.pic new file mode 100644 index 0000000000000000000000000000000000000000..21a56038f803fabe1ee23c8e14522c58510f4cf5 GIT binary patch literal 3217 zcmbVO*^V365#_nv?1fA3&3)%=oK>1%2oNAo0iw+BXl87|PLKdj;)h5`U^hwI0cJXZ z0|%A^1J+jz`F?+dIn^8}FeM;?U%Ky7x9+J^r>gsxKl{bct;;q_rC;gnMwL)%;{?hu z%a-l9m1?c-`9T=PN!l0;N8`zKZ~x%%Xm)&ZdUk&C<@NuS$n%i>3gi~_qgiycU(D>T~}de7nA#rqcD1htIGdlRQ=yf z)%rVtew>ne{c(GnAtTiP<7Cq837Jr z1Qv#OaL~))!`#3^Coex322xMCd%m(KD|k3s^HI-L9Su)4nj4r%batysP6=hCxAf`Q z=w5NX@t{(~4V=v2tJ-0%8VGDmpVMUOjW-GYpuT`SLQwPHwb)-7` z03=I=7}e#P4^>Is@R*$w=OkAvL@GXCGoq1Rd?==b-qMQ~JhOsXPnEHYIbz? zF-qKx@x($;XTMP;>xRIGSBOw~K&q_q5&gA}XJhYq=2d|KG1*w@*gi(!O@&R&w^6#|@IlM&+1neX<7q>uIDWnt!{ zt+Q9EWIRwsgS9fy^>MsWAqi`!>cRHfsm}g_Qdt5!lU%gclv1xjT72x~hzxe+4H`V7 zv(Io(P%+MQ_PJRy)--SCoTnR#BlY1*m=!c6%m95!W{N$%$W+M`J?yU-Tq`G%$VB*@ zE0vt+%7`TrYt6@4XPKawx^NQrQuvoDLC5Q7K!19Xq;ybgX z)|B^Igh&W9q6lZtc&uLJ)Tkh?W9(s^8!JZjL~N}Hmguq*!qkg@V=Dnd`<}`WXslfa z(GeN7M8~2GLi}D5rV@-&Ojs5wILSFHr?9u=4s`ZI5nCOBUc7N{I0?|&7RxYI&h(b3 zUBp*(93Yx8U{g9qt~{xM^qvP2B1P^5Xe+cMn6X@NVj;8Sta{+O)cFqDx6EwsfvHHZNK=clV`#Yn^bJhvIDJl!yntLq=zBO^QGz4S zsQJfvIY1m@n82!uD$n%%mWZWOHTCjEQ4k)^{v62>aDmI~>zd*^jr2r=P ze6WdCG11G93b}=tZ4)`t^N;A0>1J!w^N-2Y9kovdlrgn1buoTM8Z9Jpb;y!kN=p-B zpXlZ93prFV)bmf2*p;_-b#{dLwmC$y`4YmpPiZc*i-^pod_ylP<_sU$njh+GseAU@08Bo&y_+_L$CqZ z;Fj}8B)t9$Qn+Sj+nvO$`1{9XO}WP#=B67l%XOUmnjR~v-PQ9ih(uZ<+sE?D0&7LQ zeO26)V9!BAE*jP_mFDH?<==SXfh}U}<3ca*$&U1t=2m+Hz5M&5m!4k!qreofNiM%G z?yMSN7n>|2|KOclCvX%KR zb$bphdik0kQ~54VJn06~8EtXlOYw;(f|f? z^#>^`H4N+6_f*6tE&vC5{+a{nn#X#%?L}2~9IB{$PpMgw{2yGl4=aFaAg+P`a-Ij< zM_Du!N+2;J(R+x#fTljKysH(WZi_HL|7_kTbGg^B7 z#-Tr^qTypIT`58{#VNn$cp?}>xh6p@?0Oi!Q{i$!hu^@gqH~ literal 0 HcmV?d00001 diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/Spinner.app/Resources/2.pic b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/Spinner.app/Resources/2.pic new file mode 100644 index 0000000000000000000000000000000000000000..013dc0d54e6e13fbce3339189accc122ec24b0a4 GIT binary patch literal 3339 zcmbVO+m0K@5#{Nb;c&>glbj)!_v@0>uAJx>0tCoY9#;At*&7RTZh(+CD|yh6beupt zN&Jw+HWWx-LGrzSggG@_0n&O61OY)#v%9+L)TvWN{_P~f&OTEml-l?N z$}p|6U2&>4*YkrgitCMLlD67so$hcno=j)+^Tl%Y;NtS(CqMb=BQ^h#3wP{{@x68* z`7&qs*>rpT!SKV7*5p33v(9Mz0j~1BzS?KK{zqo^|Kh7My)U5P`Vf{gyvORJ{HmiL zTHK+j)+|i!vh2p(zGLb|P4|mDx}!! zzt5=k=!EEg$%=S>sM_}baVJM!R~Q(oH8E8>vs5xG^KJ88QAz5UL_et-j8?0h+NC#g z?L_*f*Y|48{sPn^fR6Wjv*`QtI#ChtH_G$%q>>Ei@LhFY~Vs~*qfDWL*%B7oD+V6ZnHeKAktA3;81Gets1EN)i>A(g1+s$x zZJm9tO2!@y%n$HUeu@xYjssmz_2wCFaA5e6hd^h~ic3BcojpehPf*9X&b}~8c#1NH zS3Gu6m&*-2(Alq5$u2Rh6JvQrx0M~gENpZa1bF_Mct^QaSLRs*lLWmK10TUto>g;G zOwBqfd*VnBW)p)1iwar}&T=Fe>J7bP$*>bi#20pc!+6*5P}?!Sy;n8{9^3;x5x(=9 zC)J!MzJ#dd>Y(bT%*=!^l)})J6n{%+2UcjJv)_@I9aD^QmEgj`?5JWiQ>^4_6Dx=J z=rhvU3zCs@>#~Bm-n@ViQA0y#f8h3k6=G>)bQ7#xa=+m&UxCp0r7EdCD>6A!5ZxSt zysxvDDDj@hYB6MDqo=b!a!aZ-JCYHa{0SwJ6r+)1$O{U@_1Kk5lF+CDheMNX7dQ~FXyedGs2=yi-vpZI&nM1IpI?I?~iClN|=Bol) zEI2IU*nCan1Cw!I^I-~af4xnwr8obOB|%zEG5v8+D~}?MTS^wpiAg{pLHXq^@YN%2}J{6;{9>L+7Cs zAcUDS-%Gvuo`H@mztWj3r8X;_y&;!-7#JMP5VHtN6Jrm4&IU5-uoCoBjE;2U=*=6d zJy&&f8tAglswTo2-4JS7YWo(~^ja4l-dWOD2Ft^nb5TIMjxfbS{Z=We@oJCxT3k+deOP)8+iw%Gk=O*!hBrXkZ}hM`*Zg=rzx*YgTa}%6cda zSOKgXmbfK#KHn8Gn{vkTNClwA>UePEmnh)PS1I)MOPs`z==Cd8hCqzLk!qPdrm>G9MZH8J*b zRYl!}dE^ALrGfcxLa#I4&XtSQr%1Q!B1k|M)@~sSW{rTV*a* zhzANI15+Q<0E^FxQcx!cIOvG00Ck}#LMmmdpe+f8Xq;LzX0oP2uYsOxl>I@V=I5W% zpvKv_KFzv-`q;|o+OgJ3;`6onWIMZU0oATkaO@!u70Dws)M350+3?Mf1O_TT3GV literal 0 HcmV?d00001 diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/Spinner.app/Resources/3.pic b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/Spinner.app/Resources/3.pic new file mode 100644 index 0000000000000000000000000000000000000000..d16757fae268358406d42c24a4293129cc0dc3e5 GIT binary patch literal 3479 zcma)8*|Hl~71cW3l6r2<-J0hygu%Z+6;(X(aP3z<#g4%wKvJ0LAr4izr3O;m+dzO2 z91@bRko10jM6BKNwS7~*R32>gJ-yF5d#}CrI{kz1e*b%=J8cvSKh()vs(?Zpcc2Wj zSSnk#<9dD&R;smnquGk$cBk9x4~C=hWICHKmaFyVjc>g9&2N4CJ9pF@|98*LU}JWB z@JmQ@d|T9uQ;Un+Le4o}XIHxI!2W=BTt}6^R+Ke){h5Q|Y<`VoHzelF+xSJcjtBL! zJlDso1bVfy<@q;x@*8Y|AAE)C;I*q^y=<{rHvuX)x=pcdtYJnt@hdYyg!7UId( zbx33WdJ>}kVEhWB&FQUSFR@x*vx(6y#8Z|pD2{K-i_(2Xt?f5&$?LhozruKitY)ae z%*3*&I;)cEtjqjo8ST#6M(o;V6nA=kPK9S*56Q%LR4YEu%OtQ>tDTR|By_5^=sZW0 zO4+PbWteJM)v<11-NZV=Qtvp3ZUJ=+AEJceh4Nqr2=`nWtzcuaMiYxmm}p<2g?XkF z1`dq-%EmNAaAHg`GxtUnfr(xb-J_~v;BrwJ7&$1P2(Zz~LscjjR2}^y1{Y|ccMmlT z4=AF)f``%?7N(g|L*QcJVJ4Urgb@}NRtLg+4rU=LM`p3il#eJtQzt(HDT9ly-kl!< zL^}DgQ84!m+#GoL^iq}33*em)VST}H5A_UtzFO(zQ4UZ;M<+kYAAK}*@(v#lC`-Uk zRl&Mon)3tN7_Jb)UaK0WKEjM?G2>Dl(S3pj4^$IF3!@WabXkCEp&Md)Ko!=J)f6N2 zZS*qbq8-SIKT`$!g4sKJWntVl*r9v;(>|Cr%-Wc`uzWa2WvcPSg}3J6PR7H13zG*7 zbWdb*k|}a-&_NvxPjE2R$mOIb5rKOX1QD9IF0~4c6#aKElZTA?$hARa>Mgd2* zr{|%llV9dc#DerIdF0M+8Eu{XT3zDEBPPPSC&<6ygRG47?zj1_3hFv}Ulr7zaEm75 zOjV#l*NC_bD$i@diO!TT0^Xp->HIeP^K_% zA4&V%#Lz{VNYr+%UMuF8h$}T`>@Dg2JujIfA19J@1%ci@%`0{!@uy7j$fNriyS3PG zPX&FwOAJ}IusHF*XwQvh_EH@f$xQ2#f0F0a5(fXoOiu`4>)nU>YMtlqKFXneSea^I z=&^KT&vK|nA4bNOk;#u3eq?h@o%}<%=lhA?{WE{`;p*MLa=ZvVoqUq7HieI8MRG^f zxQzJ3J@JLQA}aOlW4y|)om_&dE zvCS%4Ep%*j9ry&N;ukIzlhPlpdo0R15N#||Y}GNbF}oz9eMN1%HknjP4QIAd3YsS} zJAS~Ij&$AN4h z?1}&NL_1!V$F4*GOOO5Hx$OpZynUMu+td0sI(<7Ir4U8wt;cCk-^o`j^fvhfn0hNK z;>^>4PR>p@I3ek~j2AUjb^6|OyBX^3d&Cuyc6IuTJfO~**giHzAPtNJ(uc`L@uwyL z9s#zw7g3zC-oB3wF4?=fPM`2GqY(Rg`$UPMRnU@^kiNEkD$=C6ZtS!52Xd{6vro?< zoB`pc)*?4)LfXVAyo^oho>Uo=0FK_uz>5}#I^E_qghId{RH3+Mqsj-R#JMAkj)W>l z#&AqeHn6mn=bw0CP9scZG#sh-k2pWFW8xm7UDEU+=UNoD5&3YkB>nn2XJdM%VI-Lr zv360@>7VmL9Yj(^g)^A``}sfZB%#@7-s(OG3yeCEmLHF0dU{tsQ6+ zvrO5F*OIE#PfTL|kSsbF6M~b_cF+-&Dmqd`)rh-2Dbc{(L8#Mz^Rr}6DXtAH9_De& z3+}Igb0b&VK#ajgU)ZUjvEg4W5wuLSNTyU3#jj#DnmCwy7>4|6$#J3w{BcRz_8A`} zp5lB)M#a^Zl1xCWP5+a>Y*LsholD6ew8gPQz5N_lJT4%K>3{PmmoA!qkz0`^H=cpM z58|2VNYOSHXJHQSG2BB#lCGjJ**df?WaTP+Q%t&Tt7z%aNE$9$p*V@$MoYb=(b3Q& ztUUQniiM@6i8-gk))S^kR|*0%s%_E_S);rPk;22AKQ0x)M`J|#f_?=mg6pz zVOqB1dVUZ_agx>>%~m_>bn~-be=r=4C)3${aei^RTwPt?`QG<`@WZ?6|2Sh@|JOJ# zL@*bxwf;5{Yx+8~Q?mB?Yiv%XRYf^@W%ok)b*NWhP8imB75g+hmGkO;ee&HhU-8!% zf7hh>QWDKuVXtWLV*gw7TO76WGXd=1ENNfdZZ#N^QZE{2|C77UtyaR&7kzbaXT(cY z=KqNiui#apY?(N>jL~vs8-wAMqXyTmY4-=$p206)8CQX2bo2fo^o^_<7ZNJ+xpkVI zY3!wS`NJ#|Cq3<^YCNf%GFT1Ooi#CUVbNCEBEzDCx!!Oty?byly{~*+M2L5WfvJh1 zi+m3kizQmD8e`PX=;GE6%dzX1=+(%czMbS#WrID6GV?!hWm7qbA%qv~K0;#?Px zRL#EOxsiwSd-P=QXu?OU5;`k3wMesamUOM!4&8Y!GEvK-wb8fcZ~9udp~vztib){$1{dh-r;*rP46-&Hl| z5nYA%_!T_hm$yTz;^bgt=zDMj_)G5k^li9NT1PX&qBM3$;O&tpKRBR^GhMu=YQ`QJ zh6lsO*c0%>QstP2Y$6USkk+Sq^InBTfR^6;vMRibTo=EpmZ2y>peA1T}N7 zEUHt6!NF*d`a=sdPdW2ar6#Y9dXD^v6rtWcsnYe?#o`ZDjtyCUB;g2tUYcFZ16+l; zj4)r(BE>*&3O?VlFTEqNQ_^u?Qi|kkQMn0)QJ+BEv@krVTzOu3=#dPrmR6H;bfDUZ=W+ea&5!P~b8`wk{n2j)YzG_Y_{FV!9vE)SgjiC&A% zBcg0c$IeSWxh_cw2UiDb3578x1=o++(jDcI4WwQPW9oYQ*kGTjrdo&1zFxm=*0AETd!L+WV-Q_h zCzwi!kvx3AvTIYP)+bSDNSeQuT@|7oQ8?DWt85}F=K6z*QVCisGCdYP3yJ9G!{>uL z(CgDO6(pZjClg6X##zM0)Z5c!2NLLy)d;x~{v$G1T5Sb#F=cYGi0=xK7-PLHDj{j1 z?_egE#>5vDNJ=(lBol4x^}31|O3ywPcZAX}m7|>4Q|(On=*xW}fCjQ=ULUKc%&UPj zX@?76Z$IG`v*(?X`xr~Zol==tI*5*{PTfN53GMc;d@T3URBuI?j%3i_cF|+G)D^z(TvAX-!3Ohk>c~t? zTgYr&yGTNe5=0NHDm$VkfuKPV+$@b2+HJHm!RH@_NNI){RfGXJ zQKfRx^Lfp$zs4hOT5|4d5>ws-c;cV7TnnCb+=b=AxX%bN6^m^cJ*ddG3}jYA;j>&| zQ&Gb@28oDtX*aPjap9of#WYZg?+>nCQ~fHhoL?W;64w@zN>26(g)4reG)yh^e}-E99tfdYN%gRF=COf5A*($+0p^f55g#JCG9lp?Cf^;di}w0G~Pcrd~|d?IXOK$zj*xQsd|qy+IyU- zzsH$IZlaJ!0G>G%_L3En3m@Y4&(jf$&;s+17(Vnqv%PMdtYmbk3kc)kpY_BT-a+i<^y{~pxAaC_j}@;sZDl_o!_MKo zu!QX2L+}DctYd5-GFAWF((CMl^9x&w*lb8&oIBd?Ue9&?Allcj2y(VXsh*>YjQ?%h`>(#2p;O#0V~0 zW&R5e3eN15`3I_|i5p{u7Fuhh7|EI?0ex6)3pt$0aD@ona`utTst|F6xXeGU7A%~V z`7c$?xTdL-8`VOujdX7x>kp$lIkd*na)g6|^;6&2(B;6OPR$nlXGqHWR;-e`m$AwyPUr-=m z!m}5+neL4G-RW4wo zJ>e_BUYY-?I%q5RsLWrhT4MoUSZcsr@iCSvkRS(Zx=rM)kA6AJ8OROIoG!?R;FhDX z%yY?uIj3blt%feLGM{aS1oN})hOC@zh7KW~{e{=nnidXk$Q^^&%swX^L{~Ki1-TKG zvwv_4k!Fl{e7A*B|Eg^H0@xSTFeBEpFLBEaEeY|BCMYCG3oS&HATXgx{H}_JS9F+g zQ~pNP)Pe?CwkEWKxZCtv&c514S}*gjh~|R560KQ7et)eo7Lv!e+1wj2dy;oc0_7rU zVB)l3i-cF4b}&3*NG1WA{im|p7OdW?TYgyBxx<1l*7}_xyy_rXB2t|lR#>(7Jmpav zG7K(hHezqHX5Ax(86n$LFukLl03D%FfE^wF^J;<^ztd~gWaPq-K${pmV<-kruK0@4 zlcPQo9@TYBQbZPZZ4n34Q>}41r-)Up+N6fDD;7+Kgm>z;dvJ8i52sKK1deJxS~I~e z^$VT4NaadJXzQl1PVN}#kFcDICO%`+@1|%kc;N97n#vp>bLdX^pv{=NLYpT#VlkPD zbj9c!Dm-=2SZWdWbSj=nDU*Em)>?+xgYVP+e4|$uc`*H;>aK+Rn}3K}eaY(VtSEd* zQ-p&aCMMczLczV<(0Ilg9AEL_-H{Egf-4tmf;=pzA5|UWU{H_-2?jz{t(<;bC%u%V z^v5<@6<_ ztR4pC{F7=!BpZHT4MUlIRqeIexzkrwWWlDFeM#jt=~pm;k#O}~%IhE!W+WJy?3(%O z%8)HXwlI6W0aL)qR^;nm!JB5VCOTssjdCjeyize*20<(5)5>$e+ikk(X-DAwOe1Zz z2K)>i8x-6YIx8(x*^W(KIF;}caLw93HnI1NCT(Prxg|mTxr(RF)|pcV$RC~YQti)7 zByuNun+Dr0=YM6}Nt2Ck`0^0Rf-ffihKuy>jDW`w0?E7N>a~fEXV4*Df zEGMhe5(EFFmM!VamK%{BW~h33UueOt7;f#zHOYFv2e_ zMo6{}b(|V1KEI}e3~f=hCaEDR;J`etUgO)UGg~Guh=mzppsV1-K*L1KLe@mXQF`Ac z)qgGykUl4|QXytj@P9*=+z|yvPX%LI(FPEYgq?p~<=hgo{JYB8f=%aSf6u~!KyOmV z<{L4J1)8@M3f{2{(nRl)U}WgZUzAYXC0BIaq@qz?`k~y-1$#I&1i_*nN<$hW3#p5J z39^m8u#RMWTgBl^54|(5`O!oyn0BZX%w@Rd#D!Ed!0shcF)`d!vho-q6$Q`<6fax3 TeyLGrhQ7tyA7?H?AHDwq0(D;4 literal 0 HcmV?d00001 diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/Spinner.app/Resources/6.pic b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/Spinner.app/Resources/6.pic new file mode 100644 index 0000000000000000000000000000000000000000..5eabac92dfd401460459fa671894029239f7bf24 GIT binary patch literal 3451 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 Z$*;7o^x+U|VtSviI4xvI<8f?k{s)t3j6eVY literal 0 HcmV?d00001 diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/Spinner.app/Resources/8.pic b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/Spinner.app/Resources/8.pic new file mode 100644 index 0000000000000000000000000000000000000000..393a0e839362f7688cb1cacbf6bfa6e878fa790f GIT binary patch literal 3417 zcmbVO*={4p73HaJlD+TU+(c@ZNNU$F1PG9)JZSVgGZJig95{dtKLmCR#9RzJ7&QEl+$=0mGT9hut?Op>(K zek@x3p;4T)JC7MP{uiU>L!byFO!)on%QCv0gJgJNwH%_FzXXj}l z2J+l_({V{AyX&94*nWE=TzEr-{LZBHv-5{sDef^Ze#xlymV0TI|DtL8{7vciG>w!0 z_D<*jc&9{}80eV@>gYGnZz8Y}JIGvPZFA%`@?L*y`^dLg7RO}$ByiuReHuE+Pfw?j zv2k)5n|ZN$k{CtnC^d@xVTL?cS*~ZCJ-db!=`{j47yQ|~1rQHa48MWy$c%AdBFm68 z(HSGc(G5&^C0C8oyfsp^JnV$<7Ak_RkS>*h$V6BgiE1Bv82H%IlWU2i>WF;oT_J{Z zO$XyAbQKQ~Vtb?#*crkxnka7IW4C1FvjnFpjtk^v{Zi zm8<$=ANh)&8+vvPqDLQ*p1enGS+c6_Qr)6~wI%)c^z6OL!bMk4-j}8Fuoi%LR8Ppz z)RX7(s!95V!oETdL+y!0k*aBM)+d@>DHWHVhs&>a$MRx@k+j{alRkN;$aA+XE zU@E~AI?k6ygvdZzM}EUY-#}5CiRx__?3fykTFPPE2wsN$XG}CGjRgvLOI4UM^R_{@ zm$1>-vyU()5-u`5`3N;alAxPPsUKGmnoN!7HrDjylY7Vt9|JwBo{+=RlTZ2Lf^mj= z@|mg`wKYdOl89!onh5pebJUnkgneO5%g13!u(u2Pnf*}$5K}hr3$bDe zqXdDJ8yhNV``9VbGDRBn>?^z=r92$y+1IRh$@(%q`-aOE z>-F{I&(&+b>`IUl7D1bR%Y`tJ!|>N?*A*hla!C-l`?tzNBGCN3G6=xxzBdKOP5Zg<6an=SYfCqnB<_3A)8q%vc#=vwHqAqO5d<)FdpXFqaLLLt$! z*OZphOtG7zSqQrWnaofaEHx)1{`oi>^Upx%Mb*Vl6aEcXHhO^!+%2;lly-~_g?57N zRPBvTtQBx9gpNwqL&Q(%Ai%Mnytbq)PwkJGrP?`^eg{{K8KEbkZFDZHr#6G}sXJD3 zwzgsgV@Bn=dxue~r<6vg#+o9L#_*q6TeNS@F%#8_r3#8$~SLY(O7 zhdh-e273N`8F1psGCt-)2-wu~JK4#|#rYp(moW5PvTphr#_EQplG`B*n}4q4bq9UX zw}=`s{S#}EvG7+6z|y}oCGGm1Gz8d?7!4xK&4fBIr$QJ@>h)oO{nAtjeejFN3t_Ez zS0)0Xhh06T9px|O-NzlDvOS;PYtz6H%)XMiMq=>$Bv)>1QOQccOtYe;r#z&91k}#? z*9PatQ0O_>@NjaMcP?3(6s-9-3?r6jLTO)cFDrx`%FO&P+$&j2py%IG*hFsACa0Mh zR(%S^{yUxszX$+i8U>!Vo_=>PXN8{sqh2!?tbQyI>=Avnvn!lFq_K*5H#w@)yVHcO z1s0)+sxY!Tj==nco`nl0{ap2?hijc>=hxyaGuHfI5rVb=}h*TJp!{J==HsD>H zh$>i!@1`=NZ!^NCg}#k}r`C`4{CjmlHz`Gem25sGBn>L+{3ZQ~<5x5`6v8IfT%*_;df+!O6O_`|^z=?3L%4eSx^YDTa*?i>l*19B#i?A_ o{P5{Wb)I1FAeSLA5EC61%;04+R?U8jQO0{sC8>?Jha|+{Uwb-Oq5uE@ literal 0 HcmV?d00001 diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/Spinner.app/Resources/Icon.pic b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/Spinner.app/Resources/Icon.pic new file mode 100644 index 0000000000000000000000000000000000000000..34e3511264d727209eba4609c7a69a3a41a8cbef GIT binary patch literal 273 zcmXwzNeV(S5JZ1<`VTj7>s~k3XaoxB2eFsFd@5R|z2zK~X;)CB zXL#0erN0>JsQtEZE)(7*ozMExK=Y2UHj8Y-qXs~KtYeP;RuPQ<4UQ?*Y|HpEl-w;G H41x9s?P5k= literal 0 HcmV?d00001 diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/Stargate.lnk b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/Stargate.lnk new file mode 100644 index 00000000..fe581214 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/Stargate.lnk @@ -0,0 +1 @@ +/MineOS/Applications/Stargate.app/ \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/Translate.app/Main.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/Translate.app/Main.lua new file mode 100644 index 00000000..954d0cbc --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/Translate.app/Main.lua @@ -0,0 +1,192 @@ + +require("advancedLua") +local fs = require("filesystem") +local json = require("json") +local web = require("web") +local GUI = require("GUI") +local buffer = require("doubleBuffering") +local image = require("image") +local unicode = require("unicode") + +------------------------------------------------------------------------------------------------------------------ + +local resourcesPath = fs.path(getCurrentScript()) .. "/Resources/" +local configPath = resourcesPath .. "Config.cfg" +local config = { + APIKey = "trnsl.1.1.20170831T153247Z.6ecf9d7198504994.8ce5a3aa9f9a2ecbe7b2377af37ffe5ad379f4ca", + fromLanguage = "Русский", + toLanguage = "Английский", + languages = {}, +} + +local function saveConfig() + table.toFile(configPath, config) +end + +if fs.exists(configPath) then + config = table.fromFile(configPath) +end + +------------------------------------------------------------------------------------------------------------------ + +local mainContainer = GUI.fullScreenContainer() +mainContainer:addChild(GUI.panel(1, 1, mainContainer.width, mainContainer.height, 0x1E1E1E)) +local actionButtons = mainContainer:addChild(GUI.actionButtons(2, 1)) +local layout = mainContainer:addChild(GUI.layout(1, 1, mainContainer.width, mainContainer.height, 1, 1)) + +local logo = layout:addChild(GUI.image(1, 1, image.load(resourcesPath .. "Logo.pic"))) +local elementWidth = image.getWidth(logo.image) +layout:addChild(GUI.object(1, 1, 1, 1)) + +local fromLanguageContainer = layout:addChild(GUI.container(1, 1, elementWidth, 1)) +local fromLanguageAutoDetectButton = fromLanguageContainer:addChild(GUI.adaptiveButton(1, 1, 2, 0, 0x2D2D2D, 0xBBBBBB, 0x666666, 0xBBBBBB, "Определить язык")) +fromLanguageAutoDetectButton.localX = fromLanguageContainer.width - fromLanguageAutoDetectButton.width + 1 +local fromComboBox = fromLanguageContainer:addChild(GUI.comboBox(1, 1, fromLanguageAutoDetectButton.localX - 3, 1, 0x2D2D2D, 0xAAAAAA, 0x444444, 0x888888)) +local fromInputField = layout:addChild(GUI.input(1, 1, elementWidth, 5, 0x2D2D2D, 0x666666, 0x444444, 0x3C3C3C, 0xBBBBBB, nil, "Введите текст", true)) + +local switchButton = layout:addChild(GUI.adaptiveRoundedButton(1, 1, 3, 1, 0x2D2D2D, 0xBBBBBB, 0x666666, 0xBBBBBB, "←→")) + +local toComboBox = layout:addChild(GUI.comboBox(1, 1, elementWidth, 1, 0x2D2D2D, 0xAAAAAA, 0x444444, 0x888888)) +local toInputField = layout:addChild(GUI.input(1, 1, elementWidth, 5, 0x2D2D2D, 0x666666, 0x444444, 0x3C3C3C, 0xBBBBBB, nil, nil)) + +layout:addChild(GUI.label(1, 1, elementWidth, 1, 0xAAAAAA, "API Key:"):setAlignment(GUI.alignment.horizontal.center, GUI.alignment.vertical.top)) +local APIKeyInputField = layout:addChild(GUI.input(1, 1, elementWidth, 1, 0x1E1E1E, 0x666666, 0x444444, 0x1E1E1E, 0xBBBBBB, config.APIKey, "Введите API Key", true)) + +local infoLabel = layout:addChild(GUI.label(1, 1, elementWidth, 1, 0xFF6D40, " "):setAlignment(GUI.alignment.horizontal.center, GUI.alignment.vertical.top)) + +------------------------------------------------------------------------------------------------------------------ + +local function status(text) + infoLabel.text = text + mainContainer:draw() + buffer.draw() +end + +local function getLanguageIndex(text, short) + local type = short and 1 or 2 + for i = 1, #config.languages do + if config.languages[i][type] == text then + return i + end + end +end + +local function fillLanguages() + for i = 1, #config.languages do + fromComboBox:addItem(config.languages[i][2]) + toComboBox:addItem(config.languages[i][2]) + end +end + +local function checkLanguages() + if #config.languages == 0 then + local result, reason = web.request("https://translate.yandex.net/api/v1.5/tr.json/getLangs?key=" .. config.APIKey .. "&ui=ru") + if result then + fromComboBox:clear() + toComboBox:clear() + + local yandexArray = json:decode(result) + for key, value in pairs(yandexArray.langs) do + table.insert(config.languages, {key, value}) + end + table.sort(config.languages, function(a, b) return a[2] < b[2] end) + fillLanguages() + saveConfig() + else + error("Ошибка получения списка языков") + end + else + fillLanguages() + end +end + +local function translate() + if unicode.len(fromInputField.text or "") > 0 then + status("Отправка запроса на перевод...") + + local result, reason = web.request( + "https://translate.yandex.net/api/v1.5/tr.json/translate?key=" .. config.APIKey .. + "&text=" .. string.optimizeForURLRequests(fromInputField.text) .. + "&lang=" .. config.languages[getLanguageIndex(fromComboBox:getItem(fromComboBox.selectedItem).text, false)][1] .. "-" .. + config.languages[getLanguageIndex(toComboBox:getItem(toComboBox.selectedItem).text, false)][1] + ) + + if result then + toInputField.text = json:decode(result).text[1] + status(" ") + + config.fromLanguage = fromComboBox:getItem(fromComboBox.selectedItem).text + config.toLanguage = toComboBox:getItem(toComboBox.selectedItem).text + saveConfig() + else + status("Ошибка во время запроса на перевод") + end + end +end + +fromLanguageAutoDetectButton.onTouch = function() + if unicode.len(fromInputField.text or "") > 0 then + status("Отправка запроса на определение языка...") + + local result, reason = web.request( + "https://translate.yandex.net/api/v1.5/tr.json/detect?key=" .. config.APIKey .. + "&text=" .. string.optimizeForURLRequests(fromInputField.text) + ) + + if result then + result = json:decode(result) + if result.lang then + fromComboBox.selectedItem = getLanguageIndex(result.lang, true) + translate() + else + status("Невозможно определить язык") + end + else + status("Ошибка во время запроса на определение языка") + end + end +end + +actionButtons.close.onTouch = function() + mainContainer:stopEventHandling() +end + +switchButton.onTouch = function() + fromComboBox.selectedItem, toComboBox.selectedItem = toComboBox.selectedItem, fromComboBox.selectedItem + fromInputField.text, toInputField.text = toInputField.text, fromInputField.text + translate() +end + +fromInputField.onInputFinished = function() + translate() +end + +fromComboBox.onItemSelected = function() + translate() +end + +toComboBox.onItemSelected = function() + translate() +end + +APIKeyInputField.onInputFinished = function() + if APIKeyInputField.text then + config.APIKey = APIKeyInputField.text + translate() + saveConfig() + end +end + +toInputField.eventHandler = nil + +------------------------------------------------------------------------------------------------------------------ + +checkLanguages() +fromComboBox.selectedItem = getLanguageIndex(config.fromLanguage, false) +toComboBox.selectedItem = getLanguageIndex(config.toLanguage, false) + +mainContainer:draw() +buffer.draw(true) +mainContainer:startEventHandling() + + diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/Translate.app/Resources/Config.cfg b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/Translate.app/Resources/Config.cfg new file mode 100644 index 00000000..06e6cdfb --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/Translate.app/Resources/Config.cfg @@ -0,0 +1 @@ +{["toLanguage"]="Казахский",["languages"]={[1]={[1]="az",[2]="Азербайджанский"},[2]={[1]="sq",[2]="Албанский"},[3]={[1]="am",[2]="Амхарский"},[4]={[1]="en",[2]="Английский"},[5]={[1]="ar",[2]="Арабский"},[6]={[1]="hy",[2]="Армянский"},[7]={[1]="af",[2]="Африкаанс"},[8]={[1]="eu",[2]="Баскский"},[9]={[1]="ba",[2]="Башкирский"},[10]={[1]="be",[2]="Белорусский"},[11]={[1]="bn",[2]="Бенгальский"},[12]={[1]="my",[2]="Бирманский"},[13]={[1]="bg",[2]="Болгарский"},[14]={[1]="bs",[2]="Боснийский"},[15]={[1]="cy",[2]="Валлийский"},[16]={[1]="hu",[2]="Венгерский"},[17]={[1]="vi",[2]="Вьетнамский"},[18]={[1]="ht",[2]="Гаитянский"},[19]={[1]="gl",[2]="Галисийский"},[20]={[1]="nl",[2]="Голландский"},[21]={[1]="mrj",[2]="Горномарийский"},[22]={[1]="el",[2]="Греческий"},[23]={[1]="ka",[2]="Грузинский"},[24]={[1]="gu",[2]="Гуджарати"},[25]={[1]="da",[2]="Датский"},[26]={[1]="he",[2]="Иврит"},[27]={[1]="yi",[2]="Идиш"},[28]={[1]="id",[2]="Индонезийский"},[29]={[1]="ga",[2]="Ирландский"},[30]={[1]="is",[2]="Исландский"},[31]={[1]="es",[2]="Испанский"},[32]={[1]="it",[2]="Итальянский"},[33]={[1]="kk",[2]="Казахский"},[34]={[1]="kn",[2]="Каннада"},[35]={[1]="ca",[2]="Каталанский"},[36]={[1]="ky",[2]="Киргизский"},[37]={[1]="zh",[2]="Китайский"},[38]={[1]="ko",[2]="Корейский"},[39]={[1]="xh",[2]="Коса"},[40]={[1]="km",[2]="Кхмерский"},[41]={[1]="lo",[2]="Лаосский"},[42]={[1]="la",[2]="Латынь"},[43]={[1]="lv",[2]="Латышский"},[44]={[1]="lt",[2]="Литовский"},[45]={[1]="lb",[2]="Люксембургский"},[46]={[1]="mk",[2]="Македонский"},[47]={[1]="mg",[2]="Малагасийский"},[48]={[1]="ms",[2]="Малайский"},[49]={[1]="ml",[2]="Малаялам"},[50]={[1]="mt",[2]="Мальтийский"},[51]={[1]="mi",[2]="Маори"},[52]={[1]="mr",[2]="Маратхи"},[53]={[1]="mhr",[2]="Марийский"},[54]={[1]="mn",[2]="Монгольский"},[55]={[1]="de",[2]="Немецкий"},[56]={[1]="ne",[2]="Непальский"},[57]={[1]="no",[2]="Норвежский"},[58]={[1]="pa",[2]="Панджаби"},[59]={[1]="pap",[2]="Папьяменто"},[60]={[1]="fa",[2]="Персидский"},[61]={[1]="pl",[2]="Польский"},[62]={[1]="pt",[2]="Португальский"},[63]={[1]="ro",[2]="Румынский"},[64]={[1]="ru",[2]="Русский"},[65]={[1]="ceb",[2]="Себуанский"},[66]={[1]="sr",[2]="Сербский"},[67]={[1]="si",[2]="Сингальский"},[68]={[1]="sk",[2]="Словацкий"},[69]={[1]="sl",[2]="Словенский"},[70]={[1]="sw",[2]="Суахили"},[71]={[1]="su",[2]="Сунданский"},[72]={[1]="tl",[2]="Тагальский"},[73]={[1]="tg",[2]="Таджикский"},[74]={[1]="th",[2]="Тайский"},[75]={[1]="ta",[2]="Тамильский"},[76]={[1]="tt",[2]="Татарский"},[77]={[1]="te",[2]="Телугу"},[78]={[1]="tr",[2]="Турецкий"},[79]={[1]="udm",[2]="Удмуртский"},[80]={[1]="uz",[2]="Узбекский"},[81]={[1]="uk",[2]="Украинский"},[82]={[1]="ur",[2]="Урду"},[83]={[1]="fi",[2]="Финский"},[84]={[1]="fr",[2]="Французский"},[85]={[1]="hi",[2]="Хинди"},[86]={[1]="hr",[2]="Хорватский"},[87]={[1]="cs",[2]="Чешский"},[88]={[1]="sv",[2]="Шведский"},[89]={[1]="gd",[2]="Шотландский (гэльский)"},[90]={[1]="emj",[2]="Эмодзи"},[91]={[1]="eo",[2]="Эсперанто"},[92]={[1]="et",[2]="Эстонский"},[93]={[1]="jv",[2]="Яванский"},[94]={[1]="ja",[2]="Японский"}},["APIKey"]="trnsl.1.1.20170831T153247Z.6ecf9d7198504994.8ce5a3aa9f9a2ecbe7b2377af37ffe5ad379f4ca",["fromLanguage"]="Русский"} \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/Translate.app/Resources/Icon.pic b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/Translate.app/Resources/Icon.pic new file mode 100644 index 0000000000000000000000000000000000000000..026f5a0c9bbe92f3ae435d4a45b9a474826db5dd GIT binary patch literal 254 zcmXZXK?(vf32laD&Iz?*lT_^qgu|M)1dk0dG)86Y<0XgMkLL5Pm{_9ftWx@j8wot2y-0| zR#>dEQ`t3g<)x8egNmihoms zT~KV8s2UjDaLNh_i*nnFs>XrA;WfAXV^2#jd>b5QR93$ui>zz1NoUC+l>Kk>x69f8E+4@0R2IbEc1Z~&LO&K9 zrKrH7IQ!q@iDXc~{u9#^O@-pIsF*{aJWHY*5?7Pxmwka8CqTzjNkQ4x@a&y6hadps zz@c>anMu_GX!w&cwV1!;vZj6ZU%-0>;6e^xz z%K<(=O6H;d3iA-p>c3FFBYDR@Ae`&jpTIsygX0pp)(_pP=@l8CVvZi5HSdA%3aBNr zv(@+<2M{>&)>Ls0jBSznE(c$r=2P72kUE+W*s3U34r_apW21BkMT1jz)Q8L|_l*(E;dI-Ef5z6KOqeU{L9!F3-UfNX4 zLC$nkO&3SUdHxGIfVQDcn-UJi(IcAV;3WT9i068_$I%JhH+jr(^f=#-agIc_k}N3r z0Z+tS&4ugZ;ECX(oN5V={GehC8Idba4U(+UnVo%(p5mpr9M6{HBG}5ZaUL5QnVerp zZSFzrv@R&<-#_{uTjJNi|{8UyPhNhD=LbMdnKn0 z9G&L_w{s-wm2sKi=q)rNz0#gaWAGiD>IF0*&m5}b=ns7;e~5&P@&MHz45Y=uCE*O+ GasCE}#<<%6 literal 0 HcmV?d00001 diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/TurretControl.lnk b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/TurretControl.lnk new file mode 100644 index 00000000..3b50c1e3 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/TurretControl.lnk @@ -0,0 +1 @@ +/MineOS/Applications/TurretControl.app/ \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/VK.lnk b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/VK.lnk new file mode 100644 index 00000000..d8da35d4 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/VK.lnk @@ -0,0 +1 @@ +/MineOS/Applications/VK.app/ \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/Weather.lnk b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/Weather.lnk new file mode 100644 index 00000000..086fbc09 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Desktop/Weather.lnk @@ -0,0 +1 @@ +/MineOS/Applications/Weather.app/ \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Pictures/AhsokaTano.pic b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Pictures/AhsokaTano.pic new file mode 100755 index 0000000000000000000000000000000000000000..ed942c4839b9dc4f67de9518e5f3f43814012560 GIT binary patch literal 35871 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 Ap#T5? literal 0 HcmV?d00001 diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Pictures/Block.pic b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Pictures/Block.pic new file mode 100755 index 0000000000000000000000000000000000000000..d2b162ba1696a89697100a1471f2c826836dd6b1 GIT binary patch literal 9557 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 bPF!>2JclU(uNCVMi$){rtPnX^c^Ll>ty;E{ literal 0 HcmV?d00001 diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Pictures/Catniss.pic b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Pictures/Catniss.pic new file mode 100755 index 0000000000000000000000000000000000000000..bb0c4024aa11da49ffe802929bd726110fa4552e GIT binary patch literal 39107 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 literal 0 HcmV?d00001 diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Pictures/Ciri.pic b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Pictures/Ciri.pic new file mode 100755 index 0000000000000000000000000000000000000000..2d5fe3ceafe28f385da878c49017b9b3124563c2 GIT binary patch literal 35705 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 literal 0 HcmV?d00001 diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Pictures/Girl.pic b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Pictures/Girl.pic new file mode 100755 index 0000000000000000000000000000000000000000..a8bcf38fe2904559cd56526dd016bd465c23776a GIT binary patch literal 39309 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 QB8IO~u0k5TlR)|Z0o%J(_W%F@ literal 0 HcmV?d00001 diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Pictures/HilbertCurve.pic b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Pictures/HilbertCurve.pic new file mode 100644 index 0000000000000000000000000000000000000000..f7bfbb909725ab6e9402c81491305c19153d5e4d GIT binary patch literal 8735 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 VW=vSo?B{m3sW^$gc{xZm{|~@ZDu@68 literal 0 HcmV?d00001 diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Pictures/Nettle.pic b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Pictures/Nettle.pic new file mode 100755 index 0000000000000000000000000000000000000000..2e63b65dd5bba447a1b32b656a8d286ebd1e36cb GIT binary patch literal 14153 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 Tmv<*j7&h=mi6N^p4%z<~b2vkv literal 0 HcmV?d00001 diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Pictures/Raspberry.pic b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Pictures/Raspberry.pic new file mode 100755 index 0000000000000000000000000000000000000000..1da69c1a86d2589facf2bf1e20db4ba91aa6dbef GIT binary patch literal 14703 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 RkHY{(c=PJ|)ng`}|1a~??6Lp= literal 0 HcmV?d00001 diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Pictures/Space.pic b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Pictures/Space.pic new file mode 100755 index 0000000000000000000000000000000000000000..c2a53e53c391c4cb4db469fa7caa2c817da44740 GIT binary patch literal 33041 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 literal 0 HcmV?d00001 diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Pictures/Tatu.pic b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Pictures/Tatu.pic new file mode 100644 index 0000000000000000000000000000000000000000..c016ae8dfb9af7780ee8b8873910832882cb7a03 GIT binary patch literal 34079 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 MVeJ10Kl8x<1F-ng+W-In literal 0 HcmV?d00001 diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Pictures/TyanSunset.pic b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Pictures/TyanSunset.pic new file mode 100644 index 0000000000000000000000000000000000000000..90ed02efdb38feb905184aa6ea691a43249d7dc1 GIT binary patch literal 12407 zcmX|n<$5bk59Fur0W*ZmcFc_76J|bPW@ct)W@fmyy^@vByU+eeo@6|>ds^yJRY^64 zl=Fqh87=RDW}OOtQr|(h$3zmUcdn- zApRc(#+lwaK?IbQs8Wls?kS^GmDYfOu;Hw_g%=81hK*0ktZrgCTGH9Q_{fQfB3`R^ z2UBvZcmX4`IRdHaO#+S+NfcVWkm$T&fdJ}-oHR4HWnfsuvSMd&RxB3BYQovI4NWc6 zvl5P}Zt59bl?sI_-=F%%uH`iu#LESVc7LdK?D4Y?gmA`E!uZSEx^Q9X*#ln;5$QN7 zC4UMSzOtpQ>+$)W)>CY9q7u{>U-aeDo28`}p`hQ=gU%b3_mO0FELT19PbnCmP z&aZC1e{C?#Dn*i5gW2s3=T=nJ%`acRf7;2w2*(LjzVezn2Mq8;XK>Qq{vj9ic}VZD z7e<0)WGW=;=^LDyTUc4!*bhspYU=xjMkZ#K*N@I3l9H<4(W$xB&7G5rJ1yxSG(o>x z$#5_*w!C%BD(zXdEp2@RLzB~UTW9yr-@jZugQ1Nv+9PQh`GpNFJ$(bC_fMaEj6kG} zHM=~2vU2jGDOp|HDNw|#%vQV87fwpa$Sf!=uMvtUFOrS~LQ*G);C7$c2M;WsP#27Q$oRAm9y`EKwvRf&590+xx1^W+H`jolv>k%A(LP^yg88Qdvp8BzhuE8_H?$}<@!lq;S2f8~V4ghdJo z6=_ocWaqZbEUmmK1rlRcab<1uPz;EK1e78HctsO%nv+@{;5totE+}%4k zGBJDo^opV6h2R#6l2gKcg{kBL1PPRh*A-II#72sAc_tGsVuMOM!qwFil9s8I>Ydi0i2p zOF^zQ+1>tNT5);ZiiG7v#g#Qa7?w(iIFepi+S1WIHG3!%Y7^bwvYvs-d3g+@h)Y0} zOv)=Rt*EJMyuE){NLiU8Ho@SGlvLC4ZYhk& zNO7N>>N_rd5(Su)gh`1ZtSlB70aILAT`MGPfr!IHuIru@b2L69v$R7(CU>QRKqxD} zd2C`&#&9Z2y_|})S)Cxo9F+GG3u$b1fVfX+CZ*+SXz@Uk4vxK0q9+*}KBzQ6u?Y)X4BN4)3t?fvuHV`|1*CJ?aZ7x3lPwe`*IJ-p-o@#()Cyov4I17nB{3;5#3=Jw9+ z-u~gy$?5s!^$mW`^UIGTNXuKSN)58pYk^YHxf>lMw+BeovgKfV9@1RWDo8{3ChUw$|=!MO)4 zFmfqMWHzsFKg}$jgYA8bfQF`L@hzKHmM~qD%Qm4*i5N3E_x)=ZG7_WB>Gt~5++=m# zLwhz3yrgs1M}{V^B#8o<+O&X({B4#g5rpuBD4^7j7u{pSvB@NiEp^yTBr z1}}ElXa*l$KDcZ_usVD5le32o&H|kd5W1nqOBXLbd}MCLPY%vIr4}f&Q<);sK{Bqy?H@01VdAaeatJ}9vA6I{I{rvf3ge?=qn zoi<9iIJ;0FO3>)TDY*qMxbu?QK|QP)$kLsWR?ps=1R|Z)o;AO?dL>Q7DwLXCRd@Ak zrn0z*MaTw}qZoU;tY{0mg^gnGKBG~dmfezO7&}k6Vj7-dJJiUJ!#Tu(Wqo{Rq z`tth6#7pATuJoDD5EhAv22jI!Gyb*Lg1_!ML=G@H31QUG?k4KL9+LLsg%ls(+|@HM zdva%h7e6~VG`4wc1al|vgph|7h~qLUb#SI9smnfET-IiQh@aGaUrKt36?q0U^$8ph zRKuDJ@^o;jCk@R$$TU!Y>#dO*g6VgR1s<%-CY~L;w5G11scT|xVQB@Aq!ZS|-~!J* zZ{dzlZ|0=CkuQj4j6UP?%72bliING=w@2mH+MWt>(=hg3$eku9Uf1mi5w9p z%x&$M8EHb*w}oNF3maCVP}_JuUZqR46xlhT;yA6r1)Uz+zJKl|EoXYBZ1?)s0M|xZ zyS!&&!b>Y(9jqCOtuSVX7-tj%|8$Bhq`KQdXut_N7j>4_X~{9BPN|n9LCl;YV8f%E;tgIbc6>(a<*I!&(-?Ff{vbw(UWzz{nu~uZJH4V+J?VVHe z2zRS@PtORDUq5zfyv`m%cAS%6R9@ZU5GAN}20PO0lme$LIW;3QyR^KeJ-XSeE=77< zM-(a%3pX~mws)`H%=Z4trH45_ySOru>9Z?&A~K{HgVXI#$;izwtFEbUnVdO3eKW_& zb#|{moRps1IWjgexBBq(^8RU&$dw7|M61J}lJ;lUs+GuMH2;-cEWs(!%AA{D*tE2^ z^qE ztZQy_2{Utw%UgH$&u-jYO4{z(#f^tH9Gz<5&;a{JVP5n2`sU63_peE*a=LvrjV;|{ zlXDAe2iK3USBhTF3|(s2@j{uNVr?+c#_3%nP0pB-@xk)1j^d3ZI+73SIX&BNmh zEIUsxZy%?UL?XYj(8A&dD+Q{vL5v-&9#USbqZmgosqE5|<7WetQn7ky6sWwZxoZb! zUnVN_{B;vCyh_OBm4^|*x&*GJ@Gge~A9BgHOKD=9P@+h*ctXi(Sp}7i?cHM&)4PXe zH5(hRFtJo@tAeFcOX4=!(8MHN(opqfoW6Mq64-hn7(I<~N~efRib zGRU<0e<#HsjHLX@%qzeRs;sT==$t@I+Y2Eey23;V^AfnZV=c_Z_3f(-uI=#UfDR`V z-E$W93`_mHmE1kp;KI%{w|DM1NMWUu#f>Z}i-O#~9qv1ni9pSKX64_8#^yE)G}~ap zPNZ@NWo02J*j>b5A%q<_lz5@h2aRg7vZp2Imj*g{{$&J(iAXh_GBe4ka}jU8ns_we!Q;7eQEyYK`!VaElhFVsd3PVMC8*#UPaDDkIG z!3a;6yp`NP+F;ENZ4NF8aiqS{Ntf3)TyW`zqbt@9Zw@9c`}4<1Jq?{MrWp^G8xHP; zcdV7!-rYa6F&L~Q7TiLcy0;XNgv zNW`AMZLs3ti%MGBI>&#V#98l(e%HAh&aXHtT-l-9L96Q;oRH#zDM(1EYWj z!gL>2>m&`SYZU>;C;ubNDBwl~j2IW>2WTJ(aH9c_3*Rv01_eDjzw`(~kP{DP6@&4- z7Qm+(>{7V*gG~;{9n=9MYADr#rBftIAr1+mU6MhI&fs1cE) z6B+C@i$Wv-`$2dQ(-3)m_rpm5#+o4rw=EEWD;I>}+D!sLA8X_CEsjokU@&=qjU>Tzf(NO+#|b}fq$kkjg)bjnKmYdA)BCqmxdmEmfZq#= zPNLGdpwdmssy!q&-%HAee6)N1$xl{ybWp9Q$yqrDI57fe0^C{~jJt9B%Oc>`f+|4T z*FB;Tr14~9TL3FA;I$C11G}DZazC^NsHt%xNbEUY4Bpo1cMx7Ki3Q57(BY#YxDJrD zy&(L(k|n~Z8H5&6*I}hKb!|d86v45FR@Kx=;YSM_I@r}S;f13U0~{HN=g@?5K_0?| z+*qN<5Ay+735(*vsDgL_Jh+J5CWI+5Jm`V=sZ?bNP-WaukVYzR8aM%LYT#N+3}*DP zECG^L9fZ7Q;)kyQbAI>mj69fbUA=VRa%$?GQ0<~Ih7mWzDRmC5>YYwaJs}msK6Z~#Fu07 zUt^*z7{TIKStCNZCnH!jmGM{`6Fn(8Lt;4c3Q;;LVy(X5sE=orDqYg(uU{tir59Az zHFb0ij80B}2H3p1+n4ts6Ds)r4MDbw>J#m1I(>Aap~2b*EnV5!(^0YcTF)rsOw$IA zm20t-8(E-?L}sFB4Ryx!%7t+2h9VCMC5z$0OJr$2;>_`r+J*oru22!9RSm})c53eO z?oG?MmR`Sgu&gJ#6a%F8DFm<}q|3YaZYU7JnFp%{VPkwuVt#3Zlze^rNq)78)nU>f zs!3L{MrLvO)AP}!Y-*laIJ?lQJpNE}I$CSRWfhfGbsb$jlhd!JyE zBs~fB9#$mHEw1*`>{gU0wTrJ>vUaG02LoIhVZ#Lcsn`x?2Z%IK?}iEy?0X2O(m|b< zls4$uz@N8|A0PSCWZ)!5Um$bO#aLm~fX-%s%+3el+$|H(fw5O1nV1z(^q<7ACna}p zeqaKuO4Gl(Er<0GDXIuVYCP5I$JJCQ?$eOrF(Zt4bUcGL!mdCtF}b|DwfpuiWM($E zK1J{%W)859OUc8F4A$gyZEs%z*Gh755id3*TC7P0DA(7|5`?+`=u^|&j!I5r(<-Y` zLv>=|TDyBvBa1P(0~3!($bU2x>A#k$wc|PhRu`Du3^FvYhd8}n;`RFwHDd@Tisr6b zYc!!9>kp>(2P7cTnWO#`(zW!UL}m5`l91eF=9gE6SZ^Sllo1AV^n?o!j)F}0;PsP_ z4MAd;b1MqvQA@Zd>Qrndx#-hOwP`|@JB|rw$;$Afi^AK-Pdsyle6=yPkPQ*P*vO5YmhN0w>@bYxeaYo zXE)zQ0WVe>6C0O~Ls(dHOTr|S;R4K%bvFTixPJRi5QUP^l`rn>o7hsZ zAa81EZxAPlV0G`{Fv@rCR5F#>nKHR?eENWF=Icj5*3Z>)r!Q1q`L}*>cxq<;^7>9A zHezcbl$JtARK@@IF}ela-qQ+;k+M zZyba6zk(BpB>x&H7`sGka6~{ic1~Qe$9wCiM z&=}4Bf-+){y9jJwow4#cpu8XT%4MnR&%j!%s^LIMd;Pg~p>O~+FD`M1`Ns|U1afesL zBpQKq8hE<2e3{yI%c|Oj&hCMc zr~+I%Jbie2`_ia1*v+uJy^+++oWf!ZPRqpV)$PlNR$;U_0_gBp*K9pLqjC77Q^&?9 z+FZT>3LDfV(LP2=Sxw8>$CqBjie!4D)e)NeGO$8fg4%5)LcJYg9iVg)v&|&}hC|z2 zZwyAcfftG-(%6K5y2+Q4 zmX%vr(bC!PR|v2N5lV?Zb5SXRWlun6w5DbweW?6f+tBg#6J)Y;i%Ub0*~|B_S}4>p zXzAtYp~FC>!M{fEolrBWU9v!)0~Du>88{tm8p!;n3B(=9VlACYGsc*QJps}%;!E;C z#7k8f`4YJG5lx04k^)c|gfa~>eJyD|(2=fpJ$Ma}V+8FbQu3=SHW4CvC^FE>@n<7z zaTK@Bm`G|ypQM-S7=K#rr-8(Uji3%Ay})gmlftVUHu`uCEvh(mLzb5NlZDc1)dT4| zVK^nHptv0KQBO*%4Jdl=!2tIsLNi6zz(IM*hX$5(m?vbLe)n|h|Vk4|p9G`FZuFDj|{+cJt?!9J47DgzsvU$?kyq@kn^ z6EwRSG2MB=to$&*upACOynv1$Txeh%54;b=2I!H&dJqabq-LnLkcuh+oOy{T-p9n1 z{B8F`u?n`;srQ_dh?`XC)S={fAlmy(qo|ZtdCAi6(bJ^PVfuKB3cePB6#-D z)IWJ*^wBxqm>7tp`Y?5($yZQ>x#KHd0$*sfkw9wWFQPSd0IDSRh*stuAnA$jXU`Y(MH~Y3r32StDti zH*lJwlBSkUqs$dR{hXa2HFa9L=1iE9)vZHtimd#}CBeQdVM0VCiZz8$>CVi5Q9MT0 zq|B&0*^7$h58?lvhbUvxML`RXDK>zx?UsjqH>`DN`nGrW_79F?`*9w5_YQU+Z&Cez z{vg;h8l4^Qt!ZfO?CYPLUS8ckJUPF-Lr)Y9YvgGLSJZb(%gQM#tEj~f!Di?D(kg1Z z+lRN$Z^r!Jm!sumvN=6ydtvrrv#()vd~#*|^y2pZmDRISY+C<6XUiAL&MU93X>4xq zo!NPL`{8(2tTs8)3wSQki$N>YK(7`?bTFWY0R!|KVaNo%iO_F_ejDjUkFeJT!)_S% zKwprK&m4ugXvnrs!fZud=i+`unp03zR@2xzG;wtM@DU~q>~hfC2YTph!1FrFp`;%) zLqCBB3-nS}{dZ&ggkkcguYYXNj~!}HW&H;SLLc;on1Sh~gRrm>ZNvWI$?fBaD6ayY zg1-61txGKo>0rnReI_z6+J5ETv*0i_K(l6F?K(UnF=HtLn7KZfn;1jVM#Q(q7TwJxBbs0`(on=b`#`E z44u{gA6+RdZRM!klBIz@E!#IRJUXVMeS^b#Yz-T*t!-p`q6uJP@Y+Zs(~Hkwj{Y9B zP`qsYciF~7pUlqm;S~oHz1>OrhFzq$-wl0U+S@nmBYh)&{0ZPsP}mpUgXNWt?T{YR z6n7?CmPW?LC()2zTwX^pkq}2Zp12JR2o?8)#h|V2O-2-QV1ZKf@GI|M&C_4vj3XqL*^Xii0`D_^zpi zrH3a@9;eaST`{tdkDRw4A)Y zVF|;F3(BNaBAb>mR9EyJq)S`7A=12~1XUPyDG|SNG8TY?S|0QQ36^iPm5cr*h)}?A z$>{ji4vJ!sT#5d%CDdE4K+n|NqFBm^;u3tJKUqbsopVbPhBi)}NJWB#V0K+Y&*=V< zOu;6o4G2J3{4+A^+J-;nVyRkZ^99p0>!+R-5(EpST9;@;s$8Zd@zw}Xmn6j0A0D3r zK$w=^+((26T-n9XNXHr4KeNHYD#uf)(vbM4h{qm-RHcSq4Geg2yg}C7+KWj#F}u3H zgM`wpVCekP9;@j8cQKkf2dC#~3v&sOkXKgSgjxylYi3~$9i7|9mk&M`s9cK-A&6`p zSz9z1?E)pr%qYKzf~nh!(HId5)W~u?p-71g1OZr-gE+{x_YWMOhG=C?t&$@ev+Lmn zTgRwJm6(QikIxa7@N(r>90)Z49w`HdEF5v{)Y8f68&Af5gjg@3Uj{h}I0=C{Oya&{ zfscnTQ?vybf;$bNNJw}eHpfpmzGGx=asBR9z=THceuNxmMNvhaNRX74nOk1n*e`|( z35OIel9eN6F#v6wGLXoD4MLj&7$p&T!c-`=L_m0jR`y*ufrW97>0e%X;DzOQ;)X}2 z&VB`?p-m`G(E1u$|3TEKNRVAv9L0H5G_U`_Xb)0#TN}1@K zLsHq__0y}HN10S4i*Z#qHjghXo!)%OV=1qURau=8tinxQ{ln)sZ$Cjo)QY%xqa&P> zo?lq?7Yppv+|J|c=Z{i@Fe+2(F@e(lL2Xks|=kon0!n4v?q=d#;IT6a^)hGaMSiI6Rf8xYw9mJ`k4Av8l&alX05{M{G z1ByE=31baZ3_lX`{VRo~7T{r~Q_e967<)WFae|0(doLiW1UF*1k-r%o^7`*XtgIT5ny+;Xb7_Nj&Tx!+`>V+i<49lg~V( z7J^K@YKp^38jxKviO?fNktBixFF5#Cx9=YjT#8}C3;hx}l){sY%{#t+d=3CFXXVx7ODjPZ zT_IyoA(HyC2CEkODpT} zL{7{sZET&qCx|67tHT{`>z!HoR5975&0U(PrzmQm31st;hjlfWo2eG$0n#-dB$r<; z=mT0gI1b>m1{qPj*47~jL2?+J931fQsv-jHW(>Fh0Y~EjmmhQi@C3oospnvyXXh6$ z@16y8X8uS>j-NzuEv7hfAz@J&etb#cQx02keD?@?4u>!C%0D?pe_OEdPtLBa?Hyh{ z{3PgDC8m!tkeq?dBTs{v)L(mHzzkrQS zOKPxs;oJ}2hp$CNXG$24boy;u+ zNym@}eQ{0;Xtd*~juv4Vg&ht61mUC;jrD;boK5^2qCE1+daLd< zUSLsx7$LNaNZ*8*Ckj`3Nqdik6&BUjFG)$!fsE0Vl=sU?e6kCfe)UC|QfN* z-7*$?6dW^tcJVHRfQUh=J}4o^kd&CvN$r#q!%&bkwuETi)vXewVODM|dU=Z>@u3LA z#F-)<1xM#sw|Lq&el0+I;8zGZ-qat&(Fz4{A(rCNR)ulkEg=RX5wORSnBh34q~i5A zp7P?{1Xikuu|%{~Ad-1W2%a=VDE5IGpFc!-zy`8Z3tX625V|NI*!uU`S!3<7_J zDRCT@15Sy>DS?jwC!|fiV8ypsdfxEos6cJ3hH_a&apY0dHz( zX&;5hsH9fC$aA9TVTq@+0-js^L-SbPCLg5G}7gW}a&YWCEo8=#JiN+L2{nOGjIy1le z5`<2LTCBAvr)B0K#Y3Wv4iS1uTL-7_-yw)mYFMGz5KK& z^p3W!zVVrbtv&24Mr|>)mft?&5^5kO6i6gw-g5IH-}^7i4@OyeBy$@_P_UK)!; zBbx5XVG{%i9J= z=G9aXXVQVmi&*`k23`Yv5Pp0Lj!aGqNb{2z{z_@@{IQIrzXhOIK?M2`MR~3y3R{?= zyyGVZ3=wd}@|+^ScJb*m4rK9!&Co*J6$r`6O|Rf<){N$MR(GO+bcU zL}d9&Oi^)3xYoXb>A6!Womt(M3A?BEaU*W;e*zFAmwNL{Dr+0sJENNIA_xwJ8PHgb zs-&c7<%;egZVUqd{ObDd;qe*no-zs0r%SZpNDSKGSmFvx%A-UZ1sdiTRtYo;@Y5Fm ziPuxy@YnaRpD=MH$E1MTin|(2PWw09fNh|E;x4M~qM123iervA@3OjqlQ{dQ7Z3mD zdA=h6v1uTUi*h}u+vCHSq5RFs#dnugRyW`=>+V4(WMXP|4rd6_1dhOwf1&IDb%BRX;G2z=;#> ze;XGXs1j}yU|mL54_as(Y{bJ;fH2D)d;%~$ET{;Z>;}IMBocVh1MO7ubadrK1S?`J zJ6>=}$n2w^P?-!HMu!1N41!02QwkyCPFK>SOGCx}~%cvY(|BCc_-0Ad$j4Ze11M;+v_#LzDrb5})g6{$b b9NR~4kVww&A!Qm#Vg@mTN$G%*-A literal 0 HcmV?d00001 diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Application data/AppMarket/Cache/174.pic b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Application data/AppMarket/Cache/174.pic new file mode 100644 index 0000000000000000000000000000000000000000..a959182b8df53e7fd234e59c3bb313424ffb9397 GIT binary patch literal 171 zcmXwy!41MN3`P5GC$YpBjDm#3iIqSc+A)%50&dLn7Xh~oCx1Ub>)$W8Yj(qTI@41+D%?`H%`cF7%=RIEdpA(Bha(LsW@_c(txdzN~d+#`5 z2$c3&ebGXsyM`k+4M5C+p)(wl-~dY{bM;8MsHa|6EQ0881r12jp(=yBWP3f?IhT9A NRP&cT1pXbO`~Wso7jXap literal 0 HcmV?d00001 diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Application data/AppMarket/Cache/239.pic b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Application data/AppMarket/Cache/239.pic new file mode 100644 index 0000000000000000000000000000000000000000..dbdfb99756f5ace01b0d670ba7de3430aa722770 GIT binary patch literal 161 zcmXYp!3_d23$|EbYmg?;r8c=*2crpM&KZe_L6p#|TF57#4 NirXWQ`mWh*`40l!B`g2{ literal 0 HcmV?d00001 diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Application data/AppMarket/Cache/244.pic b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Application data/AppMarket/Cache/244.pic new file mode 100644 index 0000000000000000000000000000000000000000..f1b0f8f1e57aaef4dbc2e583436eb31d18eca562 GIT binary patch literal 242 zcmXYrJr2S!42AO?=SN%%35kg-agPo}4H6eXWdMm=l-)Pz)TI?T#Zx}TpP%1pe>}v@ z^mRL_S2(NyQIh_tJi`N0-ii|#Ug}_mk5W4OCh40osu88$6c-S()R&<~TujJP&WcRU zV8FR7ebGasp3?lgAxnK2YS3U9blb1^XhjZ-z7)xGh-p^#pjLI-iUg?}lBbv$skQx2 F_y=A(ML7Tf literal 0 HcmV?d00001 diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Application data/AppMarket/Cache/247.pic b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Application data/AppMarket/Cache/247.pic new file mode 100644 index 0000000000000000000000000000000000000000..026f5a0c9bbe92f3ae435d4a45b9a474826db5dd GIT binary patch literal 254 zcmXZXK?(vf32laD&Iz?*lT_^qgu|M)1dk0dG)86Y<0XgMkLL5Pm{_9ftWx@j8wot2y-0| zR#>dEQ`t3g<)x8egO*{NUFtdR`%<#Xy#KgjZ5QpFt+~@C{gtTe~=f$!7nD47sLrT*L z8@>?i+`&+84M-HB%)`{$fs|c334RzqKMt@R_edh54u){!5OY-%YjW*T!hfGvgrA7n Q7%Nj=>KmZtrH2Il0l_>O4gdfE literal 0 HcmV?d00001 diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Application data/AppMarket/Cache/251.pic b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Application data/AppMarket/Cache/251.pic new file mode 100644 index 0000000000000000000000000000000000000000..bb47d9e36b4894c7ca316cb020a3d940000a7825 GIT binary patch literal 201 zcmW-au?_)I5JmUQoA>r5)M`=T15~Ra5h?|0{xq4oy~W;J(m z@0qzX><)S%DoG-*!@jbj1Ne*PV?+aWh$~aV5dw8od|0zI zs?7qUHD(6ml&87a`S 4096).onTouch = function() + computer.beep(1500, 0.2) + local file = io.open(icon.path, "r") + component.eeprom.set(file:read("*a")) + file:close() + for i = 1, 2 do + computer.beep(2000, 0.2) + end +end \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Extensions/Lua/Launcher.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Extensions/Lua/Launcher.lua new file mode 100755 index 00000000..dafb76ca --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Extensions/Lua/Launcher.lua @@ -0,0 +1,8 @@ + +local args = {...} +local MineOSInterface = require("MineOSInterface") + +MineOSInterface.clearTerminal() +if MineOSInterface.safeLaunch(args[1]) then + MineOSInterface.waitForPressingAnyKey() +end diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Extensions/Pic/ContextMenu.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Extensions/Pic/ContextMenu.lua new file mode 100755 index 00000000..d2abe4a8 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Extensions/Pic/ContextMenu.lua @@ -0,0 +1,13 @@ + +local args = {...} +local computer = require("computer") +local MineOSCore = require("MineOSCore") +local MineOSInterface = require("MineOSInterface") + +local icon, menu = args[1], args[2] +menu:addItem(MineOSCore.localization.setAsWallpaper).onTouch = function() + MineOSCore.properties.wallpaperEnabled = true + MineOSCore.properties.wallpaper = icon.path + MineOSCore.saveProperties() + computer.pushSignal("MineOSCore", "updateWallpaper") +end diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Icons/3DModel.pic b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Icons/3DModel.pic new file mode 100755 index 0000000000000000000000000000000000000000..21abbe728b27c63d5a404c859bc9a241dfca7d08 GIT binary patch literal 156 zcmXYoyA1*{5JY!o$M(_1RX{=sNJvBk*8(Y>5D++^GA?%`uvi;B>1jT<^Yu~}@oQLN zFo9SYc%Z=SF!~c4b^si`PxZgPGHoY(;w$K$QbvF0uC2aDNvg~d**2I%u2Ly_Z9e^x I-+D~(AGf&{*Z=?k literal 0 HcmV?d00001 diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Icons/Application.pic b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Icons/Application.pic new file mode 100755 index 0000000000000000000000000000000000000000..9d7cd339dbb2e7295b622570e07f9ead52d215ce GIT binary patch literal 114 zcmeZw_H<+8U}5^tz^K5;z{tSD$i&FO1jNih%mTy=46F)_*MU+jOib*FK$aFG3nR;; rX)Vl;89y;HvN18Uu(Gk+F?KUDL)m{As~DM}Y(1z3<^l+ViIoii7XA)T literal 0 HcmV?d00001 diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Icons/Archive.pic b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Icons/Archive.pic new file mode 100755 index 0000000000000000000000000000000000000000..969d6cd095ea0eb610a5540a09b94f90d5241192 GIT binary patch literal 156 zcmXYnu?>Vk3RoGM+4?C%)qi^J9clzSI{941JM|0V1bBV cm@ygb&L~fY_dm$G;KWoo)rwc9HNECRKYfV`OaK4? literal 0 HcmV?d00001 diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Icons/FileNotExists.pic b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Icons/FileNotExists.pic new file mode 100755 index 0000000000000000000000000000000000000000..beb2616dbd1f1ff881003793b83a5a795e18ab37 GIT binary patch literal 135 zcmX}kF$#b%5Jb_;>}Iog3hyEWtUQHo$d2+ci6Wn z@atgL42KRta&BYOEW8DgZ0spW0v=j*YRQIKs}2DuDzyBnvp9udXngRYX>EE+QDoZM z(2KE<<06y8r=G$}eEjufEP6m2Pxb5(Z8+;`0juM9sljAv`Ca2slzdyElH6D5f80ne A$N&HU literal 0 HcmV?d00001 diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Icons/Folder.pic b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Icons/Folder.pic new file mode 100755 index 0000000000000000000000000000000000000000..b85aab8c7f70891aa57b6160f6ec00fa020f8251 GIT binary patch literal 90 zcmeZw_H<+8U}5^tz@)&)0ECPztZeKYkESmMvi~sxxeN@93M~IXGR%xj%zuFlCJrD2 Rtn4$8#{%d50P>jOJODF?3Pu0` literal 0 HcmV?d00001 diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Icons/HDD.pic b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Icons/HDD.pic new file mode 100644 index 0000000000000000000000000000000000000000..2d9a6c210e6c793f60a95922c3bec564b3c5ef12 GIT binary patch literal 248 zcmXw!F%H5o3`O&8r^(7O;2KCsOe{T6K}fK%BC(_mBNGFHskh(&z6iJ>NEyEE_x+vC za=mJz(8obj2gfZDz`z>4Jq}j7IYb zx8e}Z1mp Qw^Pmqwy$W~m8%5u1>$=;MgRZ+ literal 0 HcmV?d00001 diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Icons/Image.pic b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Icons/Image.pic new file mode 100755 index 0000000000000000000000000000000000000000..269a6401c3815e1a41c2faab4e5bb3899f056331 GIT binary patch literal 208 zcmXZWy$J$A5C!0UGqbyw+rVJ~HX#TmnwuJ#*^D)at(Yoe3xbgt2;!ZO#|DpYeuA0x ze7PuH*qG2GAq5PbG0p^&Qr_amRiNxzk^9NO2@oe@C>!FaR}ITuoqa`2MR^K;6Tm*g hkHnB4!gR%~{?x`n)R*uTapW@r>P6&Pd&u{#h(9Zi7V!W8 literal 0 HcmV?d00001 diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Icons/Lua.pic b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Icons/Lua.pic new file mode 100755 index 0000000000000000000000000000000000000000..bd03e50111c88aca4e23ae22be57805b663b8dfa GIT binary patch literal 200 zcmXYrK?(vv3D@=u fx*I$9QQfp_DA$hs*pT6)PKZHL)GX!FK2t4!&}kag literal 0 HcmV?d00001 diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Icons/Pastebin.pic b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Icons/Pastebin.pic new file mode 100755 index 0000000000000000000000000000000000000000..038d95bae062e1ea96acdb16a7d1e7a64840e50b GIT binary patch literal 185 zcmeZw_H<+8U}0on5GZ2QVm!sj!pM;cWc+7jWMnUeim()Io=RFV5BdRgfP&ff+kt=6CH}OCh9>6g_VwQxa87*34RlLC2Wo>gmGtT Kr7rPAV3}`_)(~j` literal 0 HcmV?d00001 diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Icons/SampleIcon.pic b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Icons/SampleIcon.pic new file mode 100755 index 0000000000000000000000000000000000000000..06a0bcaa7b1d2103e69b3ea3f1f696f51d8b8bad GIT binary patch literal 161 zcmXYqyA6Oa3`BFbW<6vf7&@AChOB`M?bH%^#MX8NmPm literal 0 HcmV?d00001 diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Icons/Text.pic b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Icons/Text.pic new file mode 100755 index 0000000000000000000000000000000000000000..3ade1c48a5912f94ac79135b1f2d17f5cc5204dc GIT binary patch literal 104 zcmXYo%L#x$5JM-K&u$spfHolro@~a0m0F9pb-+11UP#Ef9eZ@+6buN&Ky)UWSR&#V bo}7#p_n+XSDz*hLrXr~3(OA~dM^S8a`*kd=`{{9o1PMT1ks>m zGc@H|7WW5`cNA(_k>EaRur8)Q!EWJYOKSPF2lvAXn=^e!s^#0Hia6@cj~ROa7snzO Ah5!Hn literal 0 HcmV?d00001 diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Installer.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Installer.lua new file mode 100755 index 00000000..5c80a36b --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Installer.lua @@ -0,0 +1,324 @@ + +-- package.loaded.web = nil +-- package.loaded.GUI = nil + +local fs = require("filesystem") +local component = require("component") +local computer = require("computer") +local unicode = require("unicode") +local shell = require("shell") +local serialization = require("serialization") +local gpu = component.gpu +local screen = component.screen + +------------------------------------------------------------------------------------------------------------------------------------ + +local reasons = {} + +if not _G._OSVERSION or tonumber(_G._OSVERSION:sub(8, 10)) < 1.5 then + table.insert(reasons, "Old version of OpenComputers mod detected: MineOS requires OpenComputers 1.5 or newer to work properly.") +end + +-- if computer.getArchitecture and computer.getArchitecture() ~= "Lua 5.2" then +-- table.insert(reasons, "Unsupported CPU architecture detected: please take CPU in your hands, switch it to Lua 5.2 arhitecture and try again.") +-- end + +if component.isAvailable("tablet") then + table.insert(reasons, "Tablet PC detected: MineOS can't be installed on tablets.") +end + +if screen.setPrecise and screen.setPrecise(false) == nil then + table.insert(reasons, "Low-tier screen detected: MineOS requires Tier3 screen to work properly.") +else + if gpu.maxResolution() < 160 then + table.insert(reasons, "Low-tier GPU detected: MineOS requires Tier3 GPU to work properly.") + end +end + +if computer.totalMemory() < 2097152 then + table.insert(reasons, "Not enough RAM: MineOS requires at least 2MB (2x Tier3 RAM modules) to work properly.") +end + +if #reasons > 0 then + print(" ") + for i = 1, #reasons do + print(reasons[i]) + print(" ") + end + + return +end + +------------------------------------------------------------------------------------------------------------------------------------ + +local paths = { + applicationList = "/MineOS/System/OS/Applications.cfg", + OSSettings = "/MineOS/System/OS/OSSettings.cfg", +} + +local urls = { + applicationList = "https://raw.githubusercontent.com/IgorTimofeev/OpenComputers/master/Applications.cfg", + installer = "https://raw.githubusercontent.com/IgorTimofeev/OpenComputers/master/Installer/", + EFI = "https://raw.githubusercontent.com/IgorTimofeev/OpenComputers/master/MineOS/EFI.lua", +} + +------------------------------------------------------------------------------------------------------------------------------------ + +local function unserializeFile(path) + local file = io.open(path, "r") + local data = serialization.unserialize(file:read("*a")) + file:close() + return data +end + +local function wget(url, path) + fs.makeDirectory(fs.path(path)) + shell.execute("wget " ..url .. " " .. path .. " -fq") +end + +print("Downloading MineOS file list...") +wget(urls.applicationList, paths.applicationList) +applicationList = unserializeFile(paths.applicationList) + +print(" ") +for i = 1, #applicationList do + if applicationList[i].preloadFile then + print("Downloading framework \"" .. fs.name(applicationList[i].path) .. "\"") + wget(applicationList[i].url, applicationList[i].path) + end +end + +------------------------------------------------------------------------------------------------------------------------------------ + +print(" ") +print("Loading installer images...") +local image = require("image") + +local images = { + languages = [[3C100000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0068FF▄003CFF▄009EFF▄6D9900▄6D9E00▄6D9900▄6C6D00▄673700▄3C0000 0067FF▄0067FF▄0055FF▄0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0055FFВ0055FFи0055FFб0055FFе0055FFр0055FFі0055FFт0055FFь0000FF 0055FFм0055FFо0055FFв0055FFу0000FF 0000FF 0000FF 0000FF 003DFF▄003DFF▄3D6D00▄689E00▄9EAC00▄9E6800▄993700▄379E00▄6D9E00▄6D3C00▄3C0000 6D3700▄67AA00▄808100▄810000 678000▄547E00▄360000 0054FF▄002AFF▄0000FF 0000FF 0000FF 007EFFC007EFFh007EFFo007EFFi007EFFs007EFFi007EFFs007EFFs007EFFe007EFFz0000FF 007EFFv007EFFo007EFFt007EFFr007EFFe0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 003CFF▄3D9E00▄6D0000 6D9E00▄0C6D00▄6D3D00▄D53C00▄D59E00▄D59E00▄D6D700▄6DD600▄D56D00▄817E00▄376600▄6C0000 6CAA00▄800000 AA0000 7E6600▄7E9700▄7E6600▄547E00▄2A5300▄002AFF▄0000FF 0000FF 0000FF 0000FF 0000FF 007EFFl007EFFa007EFFn007EFFg007EFFu007EFFe0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0029FFC0029FFa0029FFm0029FFb0029FFi0029FFa0029FFr0000FF 0029FFl0029FFa0000FF 0000FF 0000FF 0000FF 0000FF 3C0C00▄6C3700▄0C9E00▄6D6800▄680C00▄379E00▄3D3700▄0CCE00▄CEFE00▄3D3700▄D50C00▄9C3C00▄3DCD00▄6B6C00▄3B9D00▄6CCE00▄9D0000 CD9C00▄C89D00▄979800▄970000 669200▄660000 7E0000 535400▄295300▄0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0029FFl0029FFe0029FFn0029FFg0029FFu0029FFa0000FF 0000FF 0000FF 0000FF 0000FF 0D0C00▄0C3700▄3D0C00▄0C3C00▄3D0C00▄376D00▄379900▄3D0C00▄0C3700▄3CCD00▄F89D00▄9D6C00▄9D6B00▄9CC800▄CD9C00▄ABCD00▄CD6C00▄C70C00▄CECD00▄979C00▄370C00▄98C800▄980000 929700▄668000▄7F0000 545500▄2A5300▄0000FF 0000FF 0000FF 0000FF 0053FFW0053FFy0053FFb0053FFi0053FFe0053FFr0053FFz0000FF 0053FFs0053FFw0053FFó0053FFj0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 380C00▄0C3700▄0C3700▄370000 3C0C00▄6D0C00▄6DD700▄0C3700▄6BCD00▄3DCD00▄CD3D00▄9D3800▄6B3700▄0CCD00▄6B0C00▄CD9C00▄CD0C00▄0CC700▄07C800▄07C800▄9D0000 C79800▄079700▄979200▄7E9200▄7F7E00▄666100▄7E6600▄365500▄292A00▄0000FF 0000FF 0053FFj0053FFę0053FFz0053FFy0053FFk0000FF 0053FF(0053FFk0053FFu0053FFr0053FFw0053FFa0053FF)0055FFВ0055FFы0055FFб0055FFе0055FFр0055FFи0055FFт0055FFе0000FF 0055FFя0055FFз0055FFы0055FFк0000FF 003DFF▄6D3700▄3D3700▄0C0000 0C0000 370700▄0C0700▄370000 376D00▄F8D700▄3DD600▄3DCD00▄3DFE00▄3DF800▄CD3700▄0C0000 9C0C00▄370700▄970700▄C80700▄C79700▄C8C300▄C80000 986700▄7E9700▄936600▄939800▄7E6100▄7E5500▄540000 2A0000 0028FF▄0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 373D00▄0C0000 0C6800▄0C3700▄0C0000 370C00▄6D0C00▄0CC800▄C8D700▄F3F800▄F90000 F9F300▄F90000 F80000 F9C800▄0CF900▄F9CE00▄F9CE00▄CE0000 C80000 C80700▄C39200▄C30000 C8C300▄06C800▄069800▄069800▄060000 360600▄545300▄2A0000 280000 0000FF 0000FF 0055FFا0055FFخ0055FFت0055FFر0000FF 0055FFل0055FFغ0055FFت0055FFك0000FF 0000FF 0000FF 0000FF 0080FFS0080FFc0080FFe0080FFg0080FFl0080FFi0000FF 0080FFl0080FFa0000FF 0000FF 0000FF 0000FF 0C0000 0C0000 3D0C00▄370000 370C00▄0C0700▄FEF900▄F90000 FEF900▄F8F300▄F90000 C8F900▄C8F900▄C8F900▄F9F300▄C8CE00▄D6C800▄C3C800▄CECD00▄CDC800▄C3C800▄079200▄C30700▄C89800▄C89800▄980000 980600▄060000 060000 362A00▄2A2800▄282900▄0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0080FFl0080FFi0080FFn0080FFg0080FFu0080FFa0000FF 0000FF 0000FF 0000FF 003CFF▀370000 373C00▄370700▄0C0000 070C00▄F8CD00▄F9CD00▄D7CD00▄FECD00▄F8C800▄F9F800▄C80000 D60000 F9CD00▄CE0000 C8CD00▄C3C800▄C80000 C80000 C8C700▄929D00▄079D00▄979300▄983700▄069200▄370600▄060000 060000 2A2900▄2A2800▄0028FF▀0000FF 0000FF 002AFFא002AFFת0000FF 002AFFה002AFFש002AFFפ002AFFה0000FF 002AFFש002AFFל002AFFך0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0C3C00▄370C00▄370C00▄370000 0C9E00▄9D3C00▄9DCE00▄D6CE00▄D7CD00▄D70000 F9CE00▄CDCE00▄C8CD00▄37CD00▄D6CD00▄C8CD00▄D5C700▄CE9D00▄C89C00▄AC9D00▄9DAA00▄AA9D00▄AA9700▄AA8100▄540600▄060000 063600▄062900▄2A2800▄280000 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 002AFFב002AFFח002AFFר0000FF 0000FF 0000FF 0000FF 0000FF 0054FFV0054FFa0054FFl0054FFi0054FFt0054FFs0054FFe0000FF 0054FFk0054FFi0054FFe0054FFl0054FFi0000FF 0000FF 0000FF 376700▄0C3700▄070C00▄073700▄070000 9E0C00▄CE0700▄AB6800▄D66D00▄D56D00▄CE0C00▄D56C00▄C83B00▄9D6B00▄9DD500▄6C8100▄AAAC00▄97AB00▄9D9700▄970000 AA9700▄986B00▄360600▄060000 060000 060000 292800▄280000 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0C0000 070000 070C00▄070000 070000 070000 6D6700▄070600▄370000 073700▄9D0600▄6C9D00▄816B00▄6C6600▄666B00▄816B00▄AA6600▄6C6B00▄976600▄986600▄063100▄060000 060000 062800▄282900▄280000 0000FF 0000FF 0000FF 007EFFW007EFFä007EFFh007EFFl007EFFe007EFFn0000FF 007EFFs007EFFi007EFFe0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0007FF▀370600▄370600▄370000 370600▄670600▄060000 060000 070600▄063700▄070C00▄6B0000 6B6600▄6B0000 6B6600▄363B00▄660000 660000 663600▄060000 062A00▄292800▄280000 0028FF▀0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 007EFFi007EFFh007EFFr007EFFe0000FF 007EFFs007EFFp007EFFr007EFFa007EFFc007EFFh007EFFe0000FF 0000FF 0000FFC0000FFh0000FFo0000FFo0000FFs0000FFe0000FF 0000FFl0000FFa0000FFn0000FFg0000FFu0000FFa0000FFg0000FFe0000FF 0000FF 0000FF 0006FF▀0037FF▀370600▄063700▄060000 060000 060000 060000 543700▄927E00▄660000 666100▄666100▄665500▄543600▄062900▄062900▄2A2800▄002AFF▀0029FF▀0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0036FF▀0006FF▀0031FF▀062900▄312900▄312800▄532800▄532800▄532800▄002AFF▀0029FF▀0028FF▀0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF ]], + OS = [[5A14AA0000 AA2900MAA2900iAA2900nAA2900eAA2900OAA2900SAA0000 AA0000 AA0000 AA0000 AA0000 AA0000 AA0000 AA0000 AA0000 AA0000 AA0000 AA0000 AA0000 AA0000 AAAB00▄AB0000 AB0000 AB0000 ABAA00▄AA0000 ABAA00▄AB0000 ABAA00▄AB0000 AB0000 AB0000 ACAB00▄AC0000 ABAC00▄AB0000 AB0000 AB0000 AB0000 AB0000 AB0000 AB0000 AB0000 AB0000 AB0000 AB0000 ABAA00▄AA0000 AA0000 AA0000 AA0000 AA0000 AA0000 AA0000 AA0000 AA0000 AA0000 AA0000 AA0000 AA0000 AA0000 AA0000 AA0000 AA0000 ABAA00▄AAAB00▄AA0000 AA0000 AA0000 AA0000 AA0000 AA0000 AA0000 AA0000 AA0000 AA0000 AA0000 AA0000 AA0000 AA0000 AA0000 AA0000 AA0000 AA0000 AA0000 AA0000 AA0000 AA0000 AA0000 280000 290000 290000 280000 280000 290000 280000 290000 290000 282900▄290000 290000 290000 292A00▄290000 290000 290000 290000 2A5300▄2A5300▄2A0000 545500▄7E0000 7E5400▄2A0000 536100▄2A5300▄290000 532900▄2A5300▄555400▄530000 555300▄7E7F00▄7F7E00▄819800▄7F8100▄7E8000▄535500▄530000 535400▄2A5300▄545300▄540000 540000 545300▄545300▄2A5300▄2A0000 2A0000 292A00▄290000 292A00▄290000 290000 290000 292800▄290000 282900▄280000 280000 290000 292800▄280000 280000 2A2900▄530000 2A0000 2A0000 290000 290000 290000 290000 290000 290000 280000 280000 280000 280000 280000 280000 280000 280000 280000 280000 280000 280000 280000 280000 280000 280000 290000 280000 534300.FF0000 FF1800▄FF1800▄FF0000 290000 290000 290000 290000 290000 29A700▀290000 290000 292A00▄535400▄532A00▄002200▀000000 00E300▄000000 002200▀669200▄669200▄920000 292900F29E400#29E400E290000 532A00▄545300▄552A00▄7E7F00▄7E5600▐7E5500▃7F5500▃A72A00▀7F8000▄547E00▄535400▄540000 ABAA00▄AB5500▄ABAA00▄AB5500▄530000 530000 292A00▄2A2900▄AB5500▗36D400▞553600▙AB5500▖290000 282900▄290000 280000 292800▄290000 282900▄280000 280000 292800▄280000 530000 2AFB00D2AFB00P29FB00n530000 290000 290000 290000 AC0000 A45400▄A45400▄A45400▄800000 290000 290000 290000 280000 281A00▟1AF200▗280000 280000 292800▄290000 280000 280000 280000 282900▄53FF00.FF0000 FF1800▄FF1800▄FF0000<290000 290000 2A0000 295300▀295300▀295300▀295300▀FF0000<540000 532A00▄540000 220000 002200▄000000 002200▄FF0000<920000 920000 929300▄922900▀290000 290000 7E2900▀530000 7E5400▄530000 535400▄555300▄802A00▄542A00▄7F2A00▄555400▄987F00▄7E8000▄7E5500▄7F2A00▄2A0000 7F2A00▄7F2A00▄2A0000 532A00▄530000 2A5300▄800000 805500▀800000 800000 290000 290000 290000 29D600▝D68100▄D68100▄D68100▄FF0000<290000 280000 292800▄530000 534E00▀530000 533B00▀FF0000<2A0000 290000 290000 D7AC00▀D70000 D70000 D70000 FF0000<290000 290000 290000 290000 282900▄300000 290000 FF0000<290000 290000 290000 280000 280000 290000 28FF00C28FF00a29FF00t29FF00l29FF00k290000 2A2900▄2A0000 2AFF00r29FF00p53FF00.53FF00l55FF00k2A2900▄530000 7FFF00u7FFF00a54FF00t61FF00…92FF00.92FF00l92FF00k980000 98C300▄98FF00E98FF00XC3FF00lC3FF00k980000 980000 617F00▄53FF00u55FF00r54FF00t55FF00…55FF00.AAFF00n98AA00▄7E9800▄54FF00m55FF00r7EFF00H54FF00…55FF00.54FF00n2A0000 290000 2AFF00C2AFF00m29FF00r2AFF00.2AFF00l29FF00k290000 29FF00B29FF00t28FF00t29FF00e29FF00p29FF00l29FF00n290000 28FF00328FF00P29FF00i29FF00n29FF00.29FF00n290000 290000 29FF00R29FF00y29FF00a29FF00k29FF00.29FF00n290000 290000 29FF00C2AFF00r29FF00s29FF00…29FF00.29FF00l29FF00k290000 290000 280000 290000 290000 FF0000 FF0000 290000 FF0000 FF0000 290000 2A0000 2A5300▄2AD500▁2AD500▆D50000 D50000 AB0000 290000 550000 7E0000 78FF00M78FF00i2AFF00R2AFF00l2AFF00e989300▄980000 C30000 C30000 C30000 C30000 C30000 C3C800▄C3C800▄C30000 980000 ADFF00uADFF00eAD0000 AD0000 7E0000 7F0000 800000 AA0000 2A0000 FF0000 FF0000 FF2A00▀535400▄545300▄535500▄2A0000 290000 810000 810000 810000 292A00▄290000 290000 290000 293C00▄3C0C00▄290600▄290000 290000 290000 290000 730000 B10000 FB0000 000000 290000 290000 290000 290000 FFD600▄A49E00▄734200▄484200▄D8EC00▄290000 290000 290000 00D600▀00D600▀00D600▀00D600▀00D600▀290000 290000 290000 280000 290000 290000 FF2900▀290000 290000 290000 FF2900▀290000 530000 530000 D70000 D70000 D70000 D70000 29AB00▛290000 7E0000 615400▄FF2A00IFF2A00f2A0000 2A0000 2A0000 989200▄C30000 C30000 C3A400\C8C300▄C80000 C8A400/AB0000 AB0000 C8AB00▄C80000 FF0000 FF0000 FF2A006FFAD0077F7E00▄7F8000▄547F00▄7F9800▄F60000 EE2A00▀EE2A00▀EE2A00▀545300▄532A00▄2A0000 545300▄FF0000 FF0000 FF0000 AC0000 2A0000 2A0000 290000 0C3C00▄0C0000 370700▄066600▄280000 290000 290000 290000 730000 B10000 FB0000 000000 FF0000 290000 290000 290000 7F5500▄675400▄3C3600▄0C0B00▄226900▄290000 290000 290000 000000 00F200с00F200р00F200к00F200а290000 292A00▄2A2900▄280000 290000 290000 FF0000 FF0000 290000 FF0000 FF0000<290000 530000 530000 D70000 D70000 D70000 D70000 FF0000<290000 7E5500▄540000 FF0000 FF0000 2A0000 2A0000 FF0000<920000 980000 C30000 482A00▄482A00▄FF2A00▄482A00▄AB0000 AB0000 AB0000 C80000 FF0000 FF0000 FF0000 FF0000 552A00▄807E00▄807F00▄550000 EC2A00▀EE2A00▀EE2A00▀EE2A00▀2A0000 530000 532A00▄292A00▄FF0000 FF0000 FF0000 540000 535400▄2A0000 2A0000 2A0000 2A3C00▀3C6700▄362A00▄FF0000<2A2900▄290000 290000 290000 FF0000 FF0000 FF0000 FF0000<290000 290000 290000 532900▄532900▄062800▄062800▄FF0000<290000 290000 290000 00D600▄00D600▄00D600▄00D600▄FF0000<290000 2A0000 292A00▄280000 290000 29FF00M29FF00n29FF00S2AFF00w2AFF00r2AFF00l29FF00k540000 530000 29FF00D54FF00e55FF00t53FF00.55FF00n2A0000 530000 28FF00n29FF00f29FF00P2AFF00…53FF00.53FF00l61FF00k7F5400▄98FF00o98FF00o98FF00CC3FF00…AAFF00.AAFF00nAAFF00k980000 80FF00a54FF00e53FF00d2AFF00a29FF00.61FF00n530000 2A0000 7EFF00l53FF00p54FF00y81FF00…80FF00.7FFF00n2A5300▄2AFF00H2AFF00o2AFF00o53FF00d2AFF00t2AFF00.53FF00n550000 2AFF00G2AFF00o53FF00S53FF00a53FF0022AFF00l29FF00n290000 29FF00B29FF00f29FF00e29FF00r29FF00o29FF00l29FF00k290000 29FF00P2AFF00l29FF00t2AFF00e29FF00.29FF00n290000 290000 2AFF00R2AFF00n29FF00i29FF00…2AFF00.2AFF00l2AFF00k2A0000 530000 280000 2A2900▄2A0000 2A0000 2A0000 2A0000 2A0000 2A0000 290000 540000 540000 2A0000 FD0000 FD0000 FD0000 7E0000 2A2900▄290000 280000 00FF00▄00FF00▄00FF00▄00FF00▄00FF00▀290000 2A0000 2A0000 060000 060000 060000 060000 980000 980000 7E6100▄2A5300▄2A0000 2A4800╵294800╹2A4800╹615300▄2A0000 292A00▄292A00▄555400▄2A5400▀2A5400▀7E5400▄81AA00▄7E5500▄2A5500▄292A00▄FFD800┌FF0000─FF0000─FFD800┐2A0000 542A00▄7E5500▄D50000 FFD500▀FFD500▀FFD500▀D50000 530000 540000 2A0000 292A00▄A40000 A40000 A40000 290000 290000 290000 290000 290000 290000 290000 290000 290000 290000 290000 290000 290000 290000 290000 290000 290000 2A0000 290000 2A2900▄290000 2A0000 2A0000 2A0000 2A0000 2A0000 2A0000 2A0000 290000 540000 540000 F70000 F7D500▄D5D600▀D5D600▀D5D600▀290000 290000 2A0000 00FF00▄00FF00▄00FF00▄00FF00▄FF0000▄2A7F00▄532800▄2A6600▄063D00C063D00l063D00e063D00tAA0000 98AA00▄617E00▄619200▄615300▄295300▄535500▄290A00▄280000 362A00▄280000 292A00▄2A0000 A44300▄A44300▄2A0000 542A00▄98AA00▄555300▄7E0000 FF0000│D80000 D80000 FF0000│545500▄2A5300▄2A0000 810000 7EFF00▒7EFF00▒55FF00▒810000 2A5400▄2A5300▄532A00▄534300▜43FF00a43FF00d43FF00i294300▛290000 2A2900▄290000 290000 290000 290000 290000 290000 290000 290000 2A0000 290000 290000 290000 290000 290000 290000 2A2900▄290000 290000 2A5300▄2A2900▄290000 2A0000 2A0000 2A0000 290000 2A0000 530000 543600▄2A0000 532A00▄7F0000 540000 7E0000 282900▄280000 280000 292800▄292800▄2A0000 295300▄2A6600▄939200▄C30000 C30000 989300▄920000 980000 C80000 C80000 C80000 939800▄979300▄C39200▄920000 920000 610000 2A5C00▄292A00▄290000 282900▄290000 282900▄292800▄545500▄532A00▄555300▄808100▄2A5400▄7F5500▄2A5500▄2A2900▄2A0000 290000 550000 540000 2A0000 2A0000 545300▄540000 540000 555400▄545500▄545300▄292A00▄290000 2A2900▄2A0000 2A0000 290000 290000 290000 290000 290000 292A00▄290000 290000 290000 290000 290000 290000 290000 290000 2A2900▄2A2900▄2A2900▄290000 290000 290000 2A0000 2A2900▄290000 290000 2A0000 2A0000 290000 2A0000 2A2900▄540000 540000 290000 7F0000 550000 7E0000 535400▄295300▄2A6100▄535500▄557F00▄7F9800▄7F9800▄7F9800▄980000 980000 980000 980000 980000 980000 AB0000 AB0000 C3AA00▄930000 930000 920000 920000 92C200▄920000 5B8D00▄305C00▄5B0000 8D0000 5B0000 2A0000 292A00▄290000 535400▄530000 555300▄987F00▄2A7E00▄555300▄535500▄292A00▄2A0000 292A00▄532900▄540000 7E5400▄530000 530000 530000 2A5300▄530000 545300▄530000 530000 2A5300▄290000 290000 2A0000 2A0000 292A00▄290000 290000 290000 290000 290000 290000 290000 290000 290000 290000 290000 290000 290000 290000 290000 290000 282900▄2A0000 290000 290000 292A00▄2A0000 2A2900▄290000 2A5300▄290000 540000 545500▄2A0000 7F7E00▄557E00▄550000 556100▄530000 669200▄920000 7F9200▄989300▄930000 989300▄930000 980000 980000 939200▄939200▄989300▄AB0000 AC0000 AB0000 920000 920000 920000 920000 920000 92BD00▄920000 928D00▄928D00▄5B6100▄560000 5B0000 5B0000 290000 280000 2A2900▄545300▄2A5300▄987F00▄549800▄7E5300▄7E7F00▄2A0000 290000 2A0000 2A2900▄532A00▄540000 540000 530000 530000 2A0000 2A0000 530000 530000 530000 2A0000 292A00▄290000 290000 2A2900▄292A00▄290000 290000 290000 290000 290000 2A0000 290000 290000 290000 292800▄290000 290000 290000 290000 290000 280000 290000 290000 290000 2A2900▄532A00▄2A2900▄290000 2A0000 290000 545300▄550000 2A0000 550000 7E7F00▄550000 550000 2A0000 666100▄660000 920000 920000 920000 939200▄920000 930000 930000 920000 920000 939200▄AB0000 AC0000 ABAC00▄920000 928D00▄920000 920000 920000 920000 928D00▄8D6100▄615C00▄8D5B00▄565B00▄5B5C00▄5B0000 293000▄282900▄292800▄2A0000 530000 545300▄980000 2A0000 7F5500▄535400▄2A0000 2A5300▄290000 290000 540000 545500▄535400▄532A00▄2A5300▄2A0000 2A0000 530000 530000 2A5300▄2A0000 290000 290000 290000 2A2900▄292A00▄290000 290000 290000 2A2900▄2A2900▄2A2900▄2A2900▄292800▄290000 290000 290000 292800▄292800▄282900▄280000 290000 290000 282900▄290000 2A5300▄290000 290000 532A00▄290000 530000 550000 530000 530000 7F7E00▄7E0000 615500▄556100▄530000 610000 610000 610000 928D00▄928D00▄920000 920000 926600▄920000 618D00▄615C00▄986100▄989200▄989200▄615B00▄615C00▄610000 8D0000 8D0000 8D0000 8D0000 610000 5C0000 5C5B00▄5B0000 5B5600▄5C5B00▄300000 290000 282900▄292800▄290000 557E00▄540000 988000▄557E00▄7F7E00▄545300▄2A5300▄532A00▄530000 292A00▄532900▄550000 545500▄2A5300▄545300▄2A5300▄2A0000 2A0000 532A00▄530000 292A00▄290000 290000 292800▄290000 290000 290000 280000 280000 292800▄292800▄290000 290000 290000 290000 290000 290000 282900▄280000 280000 290000 292800▄280000 290000 290000 290000 290000 530000 292A00▄530000 550000 530000 530000 540000 540000 7E5500▄530000 610000 530000 615300▄610000 920000 920000 920000 920000 920000 615300▄2A5300▄292A00▄2A2900▄2A5C00▄532900▄2A2900▄2A2900▄610000 8D0000 8D0000 8D0000 610000 5C6100▄5B5C00▄2A0000 300000 2B0000 302900▄290000 290000 290000 290000 290000 532A00▄547F00▄7F0000 7E0000 535400▄7E5500▄530000 530000 2A5300▄532A00▄292A00▄290000 532900▄540000 530000 535400▄530000 530000 2A0000 2A0000 530000 530000 292A00▄290000 290000 280000 290000 290000 292800▄280000 292800▄290000 290000 290000 290000 290000 290000 290000 292800▄280000 290000 280000 280000 292800▄290000 290000 290000 532A00▄2A0000 530000 530000 2A0000 530000 2A0000 550000 7F0000 530000 2A0000 532A00▄2A5300▄615C00▄610000 610000 8D6100▄920000 929300▄930000 7F9800▄617E00▄536100▄2A0000 2A5300▄610000 8D0000 8D0000 8D0000 8D0000 610000 29D600▀29FF00▀29FF00▀29D600▀300000 300000 290000 290000 544800S540000 540000 540000 282900▄555400▄545500▄555400▄54D700▒2AD700▒7FD700▒55D700▐557E00▄292A00▄532A00▄2A5300▄292A00▄290000 2A0000 530000 555400▄545500▄530000 530000 2A0000 292A00▄2A0000 2A0000 290000 290000 292800▄290000 290000 290000 282900▄290000 280000 282900▄280000 290000 290000 290000 290000 280000 290000 290000 280000 280000 290000 290000 290000 2A0000 2A0000 2A0000 545300▄290000 2A5300▄540000 540000 550000 557E00▄2A5300▄2A2900▄530000 532A00▄5C0000 5C0000 615C00▄610000 926600▄932A00▄8D3000▄5B3000▄8D0000 55D500╚55D500╬55D500╬55D500╝935400▄987F00▄7F0000 7F5500▄295500-295500c295500o295500m545500▄550000 540000 540000 FF0000 FF0000 FF0000 FF0000 540000 540000 7F0000 7F0000 7FAC00▜7EAC00▒55AC00▒7F7E00▄818000▄7E7F00▄295300▄2A2900▄530000 2A5300▄290000 290000 532A00▄530000 555300▄555400▄530000 530000 2A0000 290000 290000 290000 290000 282900▄292800▄290000 280000 290000 290000 282900▄290000 290000 290000 290000 290000 280000 290000 290000 282900▄280000 290000 290000 290000 2A0000 290000 2A0000 540000 2A2900▄530000 540000 535400▄2A0000 7E0000 535400▄530000 290000 530000 530000 530000 535C00▄296100▄285C00▄295C00▄2A5B00▄930000 939800▄93C900▄93AA00▄93C400▄930000 7F0000 7F9800▄7F9800▄980000 980000 980000 800000 7F0000 7F0000 7F0000 7E0000 7F0000 7F0000 7F0000 7F0000 7F0000 7F0000 7F0000 7F0000 818000▄808100▄810000 7F8000▄7F0000 7F0000 818000▄7F0000 2A5400▄292A00▄532A00▄530000 290000 290000 290000 530000 545300▄540000 540000 2A5300▄2A0000 292A00▄290000 290000 290000 290000 280000 292800▄280000 292800▄290000 290000 290000 290000 290000 290000 ]], + downloading = [[3F100000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0B0000 0B0000 410000 410000 0B0000 0B0000 410000 410000 0B0000 0B0000 500000 500000 500000 500000 500000 500000 0B0000 0B0000 410000 410000 0B0000 0B0000 3B0000 3B0000 3B0000 3B0000 0B0000 0B0000 410000 410000 0B0000 0B0000 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0B0000 0B0000 410000 410000 0B0000 0B0000 410000 410000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 410000 410000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 410000 410000 0B0000 0B0000 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 180000 0018FF▄0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0B0000 0B0000 410000 410000 0B0000 0B0000 410000 410000 410000 410000 410000 410000 410000 410000 410000 410000 410000 410000 410000 410000 410000 410000 410000 410000 410000 410000 410000 410000 410000 410000 0B0000 0B0000 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 180000 180000 180000 0018FF▄0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0B0000 0B0000 410000 410000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 180000 180000 180000 180000 180000 0018FF▄0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0B0000 0B0000 410000 410000 0B0000 0B0000 410000 410000 410000 410000 0B0000 0B0000 290000 290000 290000 290000 0B0000 0B0000 410000 410000 410000 410000 410000 410000 410000 410000 410000 410000 410000 410000 0B0000 0B0000 180000 180000 180000 180000 180000 180000 180000 180000 180000 180000 180000 180000 180000 180000 180000 180000 180000 180000 180000 180000 180000 0018FF▄0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0B0000 0B0000 410000 410000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 290000 290000 290000 290000 0B0000 0B0000 410000 410000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 180000 180000 180000 180000 180000 180000 180000 180000 180000 180000 180000 180000 180000 180000 180000 180000 180000 180000 180000 180000 180000 180000 180000 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0B0000 0B0000 410000 410000 0B0000 0B0000 410000 410000 410000 410000 0B0000 0B0000 290000 290000 290000 290000 0B0000 0B0000 410000 410000 410000 410000 410000 410000 410000 410000 410000 410000 410000 410000 0B0000 0B0000 180000 180000 180000 180000 180000 180000 180000 180000 180000 180000 180000 180000 180000 180000 180000 180000 180000 180000 180000 180000 180000 0018FF▀0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0B0000 0B0000 410000 410000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 290000 290000 290000 290000 0B0000 0B0000 410000 410000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 180000 180000 180000 180000 180000 0018FF▀0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0B0000 0B0000 410000 410000 0B0000 0B0000 410000 410000 410000 410000 0B0000 0B0000 290000 290000 290000 290000 0B0000 0B0000 410000 410000 410000 410000 410000 410000 410000 410000 410000 410000 410000 410000 0B0000 0B0000 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 180000 180000 180000 0018FF▀0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0B0000 0B0000 410000 410000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 290000 290000 290000 290000 0B0000 0B0000 410000 410000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 180000 0018FF▀0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0B0000 0B0000 410000 410000 0B0000 0B0000 410000 410000 410000 410000 0B0000 0B0000 290000 290000 290000 290000 0B0000 0B0000 410000 410000 410000 410000 410000 410000 410000 410000 410000 410000 410000 410000 0B0000 0B0000 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0B0000 0B0000 410000 410000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 290000 290000 290000 290000 0B0000 0B0000 410000 410000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0B0000 0B0000 410000 410000 0B0000 0B0000 410000 410000 410000 410000 0B0000 0B0000 290000 290000 290000 290000 0B0000 0B0000 410000 410000 410000 410000 410000 410000 410000 410000 410000 410000 410000 410000 0B0000 0B0000 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0B0000 0B0000 410000 410000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 290000 290000 290000 290000 0B0000 0B0000 410000 410000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 0B0000 ]], + OK = [[24120000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF DDE700▄DDE200▄DDE200▄DDE200▄D8E200▄DDDD00▄D8DD00▄D8DD00▄0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 00FBFF▄ECF600▄ECF100▄ECF100▄ECEC00▄E7F100▄E7EC00▄E7EC00▄E7E7FF▀0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 00FBFF▄FBFB00▄F6FB00▄FBFB00▄F6FB00▄F6FB00▄F6FB00▄F6FB00▄F6F6FF▀0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF A5A500▄A5A500▄D0A500▄D0A500▄D0A500▄D0D000▄D0D000▄FBD100▄0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 794E00▄794E00▄794E00▄794E00▄A54E00▄A54E00▄797900▄A57900▄0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 232300▄232300▄232300▄4E2300▄232300▄4E2300▄4E2300▄4E2300▄0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0025FF▄242500▄242400▄242400▄242400▄242400▄232400▄242400▄7B7BFF▀0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0026FF▄252600▄252600▄262600▄252600▄252600▄252500▄255100▄0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 1818FF▀1D1300▄1D1300▄1D1300▄1D1800▄1D1800▄221800▄221800▄001DFF▄0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF A92700▄272200▄272700▄272700▄272700▄262700▄272700▄2626FF▀0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0E0EFF▀0E0900▄0E0900▄0E0900▄0E0900▄130E00▄130900▄130E00▄130E00▄0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 4D1800▄1D1800▄221800▄221800▄221D00▄221D00▄221D00▄2222FF▀0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 043400▄042F00▄090400▄040400▄090400▄090400▄090400▄090400▄0039FF▄0000FF 0000FF 0000FF 0000FF 0E0900▄0E0900▄130900▄130E00▄130E00▄130E00▄180E00▄0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 2F2FFF▀2F5A00▄2F5A00▄2F2F00▄2F2F00▄042F00▄2F2F00▄042F00▄002FFF▄0000FF 0004FF▄040400▄042F00▄040400▄040400▄090400▄040400▄090400▄0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 5A8600▄868600▄5AB100▄5A8600▄5A8600▄5A8600▄5A5A00▄5A8600▄2F5A00▄5A5A00▄2F5A00▄2F5A00▄2F5A00▄2F2F00▄2F2FFF▀0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF B1B100▄B1B100▄B1DC00▄B1B100▄86DC00▄B1B100▄86B100▄86B100▄86B100▄86B100▄86B100▄868600▄8686FF▀0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF DCDCFF▀DCDB00▄DCDB00▄DCDC00▄DCDB00▄DCDC00▄DCDC00▄B1DC00▄B1DC00▄B1DC00▄0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF DBDBFF▀DBDA00▄DBDA00▄DBDA00▄DBDB00▄DBDA00▄DBDB00▄DBDBFF▀0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF D9D9FF▀D9D900▄D9D900▄DAD800▄DADAFF▀0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF D8D8FF▀D8D800▄D8D8FF▀0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF ]], + EEPROM = [[1E120000FF 0000FF 0000FF 0000FF 0000FF 0000FF 6C4100▄6C6B00▄416C00▄3B5500▄3B0000 553B00▄3B3C00▄3B5500▄3B0000 553B00▄3B3C00▄3B5500▄3B0000 553B00▄3B3C00▄363B00▄360000 363B00▄3B3600▄360000 363B00▄363B00▄360000 3B3600▄0000FF 0000FF 0000FF 0041FF▄006CFF▄006CFF▄6C3B00▄6C3B00▄413B00▄3C6C00▄3B6C00▄554600▄3B6C00▄3B6C00▄3C6C00▄554600▄3B6C00▄3B6C00▄3C7E00▄3B6C00▄3B6C00▄364100▄366C00▄364100▄3B7E00▄363C00▄364000▄360B00▄3B2A00▄362A00▄0000FF 0000FF 0000FF 6C4100▄6B6C00▄416C00▄3B0000 554100▄3B0000 716C00▄6C4100▄6C7100▄6C0000 466B00▄6C4100▄6C7100▄6C0000 466B00▄416C00▄404100▄7E6C00▄404100▄6C6B00▄406C00▄403C00▄7E4000▄417E00▄2A0000 2A0000 0B2A00▄3B0000 3B3C00▄553B00▄3B0000 3B7E00▄3B0000 416C00▄7E4000▄6C4100▄407E00▄7E4000▄404100▄3B7E00▄410000 6B5500▄CD0000 CD0000 CD0000 3B7E00▄3B4000▄3B5500▄C8CD00▄CDC800▄C8CD00▄404100▄7E0000 404100▄0B2A00▄0A2A00▄2A0000 553600▄3B0000 553600▄3C7100▄3B7100▄3C7100▄6B6C00▄417E00▄7E4100▄40CD00▄67D200▄55CD00▄406C00▄7E4100▄407E00▄CD0000 F8C800▄CD0000 3B5500▄3C3B00▄3B7E00▄CD9D00▄CDC800▄CD9D00▄667E00▄404100▄554000▄2A0000 0B2A00▄2A0B00▄360000 363B00▄3B3600▄9D7200▄729D00▄817100▄410000 407E00▄6C4100▄CDF800▄F9CD00▄CD0000 556C00▄404100▄3B6700▄CD0000 CDC800▄CD0000 3B7E00▄415500▄3B0000 C7C800▄9D0000 C7C800▄3C4000▄407E00▄7E4100▄0B2A00▄2A0A00▄2A0000 2A0000 0B2A00▄2A0000 3B0000 3B3C00▄553B00▄6C4100▄407E00▄6C4100▄CD0000 CDC800▄CEF800▄3B4100▄7E4000▄406700▄7E4100▄407E00▄7E4100▄3B0000 405500▄3C3B00▄3B7E00▄554000▄3B3C00▄7E4000▄415500▄664100▄052900▄292800▄290000 002AFF▀002AFF▀000BFF▀550500▄3B2900▄3B2900▄6C3B00▄403B00▄7E5500▄CE3B00▄CD3B00▄CD5500▄554000▄673B00▄403B00▄3B3C00▄405500▄6C3B00▄3B0000 3C3B00▄405400▄3C3B00▄3B0000 665400▄414000▄405500▄7E4100▄052900▄290500▄282900▄0000FF 0000FF 0000FF 292800▄290500▄290000 3C4000▄7E3B00▄405500▄3C3B00▄553B00▄3B3C00▄3C3B00▄553B00▄3B3C00▄554000▄3B0000 3C3B00▄543B00▄3B5500▄3B0000 543B00▄3B5400▄3C3B00▄7E4000▄417E00▄405500▄290500▄290000 052900▄360000 363B00▄3B3600▄3B0000 3C5500▄3B0000 7E3B00▄3B3C00▄3C4000▄405500▄553B00▄3B3C00▄3B6600▄553C00▄3B0000 553B00▄3C3B00▄553B00▄3B0000 3B3700▄363B00▄3C3B00▄3B3600▄543B00▄3C6B00▄404100▄417E00▄292800▄290500▄282900▄363B00▄360000 3B3600▄3C7100▄408100▄3C7100▄7E4000▄3B3C00▄554000▄3BAB00▄3CAB00▄3BAB00▄3BAB00▄3BAB00▄55AB00▄3CCE00▄55AC00▄3BAC00▄55AC00▄3BAC00▄3BAC00▄549E00▄3BAA00▄3CAA00▄3C4000▄663C00▄407E00▄290500▄290000 282900▄360000 3B3600▄360000 9D7200▄729D00▄710000 550000 3B4100▄553B00▄AB0000 AB0000 AB0000 AB0000 AB0000 AB0000 ABAC00▄AC0000 AC0000 ABAC00▄AC0000 AC0000 ABAA00▄AB0000 ABAA00▄554100▄6C4000▄403C00▄062900▄052900▄282900▄2A0000 0B2A00▄2A0B00▄407E00▄414000▄6C3C00▄3B7E00▄3B4000▄7E3B00▄D50000 D50000 D50000 FF0000 FF0000 FF0000 FF0000 FF0000 FF0000 D60000 D6D700▄D60000 D50000 D50000 D50000 363B00▄360000 3B3600▄282900▄280500▄290000 0B2A00▄2A3500▄2A0000 413B00▄7E3B00▄6C3B00▄3B0000 553B00▄3C3B00▄D5AA00▄ACAA00▄D5AA00▄FFAC00▄FFD500▄FFAC00▄FFAB00▄FFAB00▄FFAB00▄D6AC00▄D6AB00▄D7AB00▄D5AB00▄D5AB00▄D5AB00▄3B2A00▄360000 3B3600▄290000 290500▄282900▄2A0000 0B2A00▄2A0B00▄3B0000 3B5500▄3B0000 3C5500▄3B0000 553B00▄AA0000 AA8100▄AA0000 AC0000 ACD500▄AC0000 ACAB00▄ABAC00▄AB0000 ABAC00▄AB0000 AB0000 AB0000 AAAB00▄ABAA00▄360000 362A00▄0B3600▄280500▄290000 052900▄052900▄282900▄292800▄3B5500▄3C3B00▄3B0000 360000 360000 360000 0B3600▄362A00▄2A3600▄360B00▄2A3600▄360B00▄362A00▄2A0B00▄362A00▄2A0B00▄362A00▄2A0B00▄2A0000 2A0B00▄2A0000 360B00▄0B3500▄360000 282900▄290500▄282900▄282900▄052900▄290000 3B2900▄3B2900▄3C2800▄360500▄362900▄3B0500▄2A2900▄0B0500▄2A2900▄2A0500▄352900▄360500▄2A0500▄360500▄360500▄2A0500▄2A0500▄2A0500▄362800▄350600▄2A0500▄360500▄362900▄0B2900▄052900▄290500▄290000 052900▄292800▄052900▄290000 052800▄290000 290500▄290000 280500▄290000 290500▄052900▄290000 292800▄290000 290500▄290000 292800▄290000 290500▄290000 052900▄290000 290500▄282900▄292800▄052900▄282900▄290500▄282900▄]], +} + +for key in pairs(images) do + images[key] = image.fromString(images[key]) + os.sleep(0.05) +end + +------------------------------------------------------------------------------------------------------------------------------------ + +local web = require("web") +local buffer = require("doubleBuffering") +local GUI = require("GUI") + +buffer.setResolution(gpu.maxResolution()) +local mainContainer = GUI.fullScreenContainer() +mainContainer:addChild(GUI.panel(1, 1, mainContainer.width, mainContainer.height, 0x2D2D2D)) + +local stageContainer = mainContainer:addChild(GUI.container(math.floor(mainContainer.width / 2 - 90 / 2), math.floor(mainContainer.height / 2 - 28 / 2), 90, 28)) +stageContainer:addChild(GUI.panel(1, 1, stageContainer.width, stageContainer.height, 0xDDDDDD)) + +------------------------------------------------------------------------------------------------------------------------------------ + +local OSSettings = {} +local stages = {current = 1} +local localization + +local function addButtonsToStage() + local buttonWidth = 7 + local spaceBetween = 5 + + local totalWidth = (stages.current > 1 and buttonWidth or 0) + (stages.current > 1 and stages.current < #stages and spaceBetween or 0) + (stages.current < #stages and buttonWidth or 0) + local x = math.floor(stageContainer.width / 2 - totalWidth / 2) + 1 + local y = stageContainer.height - 3 + + if stages.current > 1 then + stageContainer.previousStageButton = stageContainer:addChild(GUI.roundedButton(x, y, buttonWidth, 3, 0xAAAAAA, 0xDDDDDD, 0x777777, 0xDDDDDD, "⇦")) + stageContainer.previousStageButton.colors.disabled.background = 0xCCCCCC + stageContainer.previousStageButton.colors.disabled.text = 0xDDDDDD + stageContainer.previousStageButton.onTouch = function() + stages.load(stages.current - 1) + end + x = x + stageContainer.previousStageButton.width + spaceBetween + end + + if stages.current < #stages then + stageContainer.nextStageButton = stageContainer:addChild(GUI.roundedButton(x, y, buttonWidth, 3, 0xAAAAAA, 0xDDDDDD, 0x777777, 0xDDDDDD, "⇨")) + stageContainer.nextStageButton.colors.disabled.background = 0xCCCCCC + stageContainer.nextStageButton.colors.disabled.text = 0xDDDDDD + stageContainer.nextStageButton.onTouch = function() + stages.load(stages.current + 1) + end + end +end + +function stages.load(stage) + stages.current = stage + stageContainer:deleteChildren(2) + + stages[stage]() + + mainContainer:draw() + buffer.draw() +end + +local function addImageToStage(y, picture) + stageContainer:addChild(GUI.image(math.floor(stageContainer.width / 2 - image.getWidth(picture) / 2), y, picture)) + return y + image.getHeight(picture) - 1 +end + +------------------------------------------------------------------------------------------------------------------------------------ + +local function loadLocalization(language) + OSSettings.language = language + localization = serialization.unserialize(web.request(urls.installer .. OSSettings.language .. ".lang")) +end + +stages[1] = function() + addButtonsToStage() + local y = addImageToStage(3, images.languages) + y = y + 3 + local comboBox = stageContainer:addChild(GUI.comboBox(math.floor(stageContainer.width / 2 - 15), y, 30, 3, 0xFFFFFF, 0x555555, 0xAAAAAA, 0xDDDDDD)) + loadLocalization("Russian") + comboBox:addItem("Russian").onTouch = function() + loadLocalization("Russian") + end + comboBox:addItem("English").onTouch = function() + loadLocalization("English") + end +end + +------------------------------------------------------------------------------------------------------------------------------------ + +local function addSwitchToStage(x, y, color, text, state) + stageContainer:addChild(GUI.label(math.floor(x + 4 - unicode.len(text) / 2), y + 1, stageContainer.width, 1, 0x555555, text)) + return stageContainer:addChild(GUI.switch(x, y, 8, color, 0x444444, 0xFFFFFF, state)) +end + +stages[2] = function() + addButtonsToStage() + stageContainer:addChild(GUI.image(1, 1, images.OS)) + local y = 22 + local spaceBetween = 22 + local x = math.floor(stageContainer.width / 2 - 25) + + stageContainer.fullInstallationSwitch = addSwitchToStage(x, y, 0xFF4940, localization.fullInstallation, true) + x = x + spaceBetween + + stageContainer.downloadWallpapersSwitch = addSwitchToStage(x, y, 0x3392FF, localization.installWallpapers, true) + x = x + spaceBetween + + stageContainer.showApplicationsHelpSwitch = addSwitchToStage(x, y, 0x66DB80, localization.showApplicationsHelp, true) +end + +------------------------------------------------------------------------------------------------------------------------------------ + +stages[3] = function() + addButtonsToStage() + local data = web.request("https://raw.githubusercontent.com/IgorTimofeev/OpenComputers/master/MineOS/License/" .. OSSettings.language .. ".lang") + local lines = {} + for line in data:gmatch("[^\n]+") do + table.insert(lines, line) + end + stageContainer:addChild(GUI.textBox(1, 1, 90, 20, 0xFFFFFF, 0x444444, string.wrap(lines, 88), 1, 1, 1)) + + stageContainer.nextStageButton.disabled = true + local switch = addSwitchToStage(41, 22, 0x666666, localization.terms, false) + switch.onStateChanged = function(state) + stageContainer.nextStageButton.disabled = not state + mainContainer:draw() + buffer.draw() + end +end + +------------------------------------------------------------------------------------------------------------------------------------ + +stages[4] = function() + local y = addImageToStage(5, images.downloading) + y = y + 3 + stageContainer.nextStageButton.disabled, stageContainer.previousStageButton.disabled = true, true + + local width = 62 + local x = math.floor(stageContainer.width / 2 - width / 2) + local progressBar = stageContainer:addChild(GUI.progressBar(x, y, width, 0x3392FF, 0xBBBBBB, 0x555555, 0, true, false)) + local fileLabel = stageContainer:addChild(GUI.label(x, y + 1, width, 1, 0x666666, "")):setAlignment(GUI.alignment.horizontal.center, GUI.alignment.vertical.top) + + local thingsToDownload = {} + for i = 1, #applicationList do + if + not applicationList[i].preLoadFile and + ( + (applicationList[i].type == "Library" or applicationList[i].type == "Icon") + or + (applicationList[i].forceDownload) + or + (applicationList[i].type == "Wallpaper" and stageContainer.downloadWallpapersSwitch.state == true) + or + (applicationList[i].type == "Application" and stageContainer.fullInstallationSwitch.state == true) + ) + then + table.insert(thingsToDownload, applicationList[i]) + end + + applicationList[i] = nil + end + + applicationList = nil + + for i = 1, #thingsToDownload do + fileLabel.text = localization.downloading .. " " .. fs.name(thingsToDownload[i].path) + progressBar.value = math.ceil(i / #thingsToDownload * 100) + + mainContainer:draw() + buffer.draw() + + web.downloadMineOSApplication(thingsToDownload[i], OSSettings.language) + end + + stageContainer:deleteChildren(2) + y = addImageToStage(4, images.EEPROM) + stageContainer:addChild(GUI.label(1, y + 3, stageContainer.width, 1, 0x666666, localization.flashingEEPROM)):setAlignment(GUI.alignment.horizontal.center, GUI.alignment.vertical.top) + mainContainer:draw() + buffer.draw() + + component.eeprom.set(web.request(urls.EFI)) + + stages.load(5) +end + +------------------------------------------------------------------------------------------------------------------------------------ + +stages[5] = function() + addImageToStage(3, images.OK) + stageContainer.children[#stageContainer.children].localPosition.x = stageContainer.children[#stageContainer.children].localPosition.x + 3 + + stageContainer:addChild(GUI.label(1, 22, stageContainer.width, 1, 0x666666, localization.needToReboot)):setAlignment(GUI.alignment.horizontal.center, GUI.alignment.vertical.top) + stageContainer:addChild(GUI.adaptiveRoundedButton(math.floor(stageContainer.width / 2 - (unicode.len(localization.reboot) + 4) / 2), stageContainer.height - 4, 2, 1, 0xAAAAAA, 0xDDDDDD, 0x777777, 0xDDDDDD, localization.reboot)).onTouch = function() + OSSettings.wallpaper = stageContainer.downloadWallpapersSwitch.state and "/MineOS/Pictures/Space.pic" or nil + OSSettings.screensaver = "Matrix" + OSSettings.screensaverDelay = 20 + OSSettings.showHelpOnApplicationStart = stageContainer.showApplicationsHelpSwitch.state + OSSettings.dockShortcuts = { + "/MineOS/Applications/AppMarket.app/", + "/MineOS/Applications/MineCode IDE.app/", + "/MineOS/Applications/Photoshop.app/", + } + + table.toFile(paths.OSSettings, OSSettings) + + local file = io.open("/autorun.lua", "w") + file:write("dofile(\"/OS.lua\")") + file:close() + + require("computer").shutdown(true) + end +end + +------------------------------------------------------------------------------------------------------------------------------------ + +stages.load(1) +mainContainer:draw() +buffer.draw(true) +mainContainer:startEventHandling() diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Localization/English.lang b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Localization/English.lang new file mode 100755 index 00000000..fd10f447 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Localization/English.lang @@ -0,0 +1,167 @@ +{ + network = "Network", + networkTimeout = "Remote computer is not responding", + networkAccessDenied = "Access to the filesystem of the remote computer is denied. Contact it's owner to get appropriate privileges", + networkAllowReadAndWrite = "Filesystem access", + networkModemNotAvailable = "Attach modem to use network functions", + networkState = "Enable network mode", + networkComputers = "Network PCs options", + networkComputersNotFound = "Computers not fount", + networkName = "Name of this PC", + networkSearchRadius = "Searching radius", + openWith = "Open with", + select = "Choose application…", + keepInDock = "Keep in dock", + closeAllWindows = "Close all windows", + newWindow = "New window", + launchWithArguments = "Launch with arguments", + dontShowAnymore = "Don't show again", + applicationHelp = "About application", + newName = "New name", + folderName = "Folder name", + fileName = "File name", + applicationName = "Application name", + file = "File", + notExists = "not exists", + alreadyExists = "already exists", + inDirectory = "in directory", + needReplace = "Replace it?", + yes = "Yes", + no = "No", + cancel = "Cancel", + open = "Open", + applyToAll = "Apply to all", + toDirectory = "to directory", + copying = "Copying", + faylaBlyad = "file", + + resolution = "Screen resolution", + pressAnyKeyToContinue = "Press any key to continue", + screensaver = "Screensaver", + screensaverDelay = "Delay", + screensaverEnabled = "Screensaver enabled", + + areYouSure = "Are you sure?", + emptyTrash = "Empty trash", + type = "Type", + size = "Size", + date = "Date", + path = "Path", + folder = "Folder", + unknown = "Unknown", + calculatingSize = "calculating…", + + properties = "Properties", + newFolderFromChosen = "New folder from chosen", + create = "New", + newFolder = "Folder", + newFile = "File", + newFileFromURL = "File from URL", + newApplication = "MineOS application", + paste = "Paste", + copy = "Copy", + cut = "Cut", + edit = "Edit", + editInPhotoshop = "Edit in Photoshop", + rename = "Rename", + editShortcut = "Edit shortcut", + createShortcut = "Create shortcut", + addToDock = "Add to Dock", + removeFromDock = "Remove from Dock", + moveRight = "Move right", + moveLeft = "Move left", + archive = "Add to archive", + delete = "Delete", + addToFavourites = "Add to favourites", + setAsWallpaper = "Set as wallpaper", + showPackageContent = "Show package content", + showContainingFolder = "Show containing folder", + flashEEPROM = "Write on EEPROM", + + sortBy = "Sort", + sortByType = "By type", + sortByName = "By name", + sortByDate = "By date", + showExtension = "File extensions", + showHiddenFiles = "Hidden files", + showApplicationIcons = "Application icons", + + aboutSystem = "About this PC", + updates = "Updates", + update = "Refresh", + shutdown = "Shutdown", + logout = "Logout", + reboot = "Reboot", + returnToShell = "Return to Shell", + + protectYourComputer = "Computer protection", + inputPassword = "Input password", + confirmInputPassword = "Confirm password", + oldPassword = "Old password", + newPassword = "New password", + setProtectionMethod = "Change computer protection method", + wrongOldPassword = "Wrong old password!", + passwordSucessfullyChanged = "Password has been successfully changed!", + withoutProtection = "Without protection", + passwordProtection = "Password protection", + biometricProtection = "Biometric protection", + putFingerToVerify = "Put your finger to authorize", + putFingerToRegister = "Put your finger to create a biometric signature", + fingerprintCreated = "Biometric signature has been created", + accessDenied = "Access denied", + welcomeBack = "Welcome, ", + passwordsAreDifferent = "Passwords are different", + incorrectPassword = "Incorrect password", + mineOSCreatorUsedMasterPassword = "The creator of this operating system has used Master-Password", + loginToSystem = "Login", + + colorScheme = "Color scheme", + iconProperties = "Icons properties", + spaceBetweenIcons = "Space bewteen icons", + sizeOfIcons = "Size of icons", + byHorizontal = "Horizontal", + byVertical = "Vertical", + wallpaper = "Wallpaper", + wallpaperPath = "Path to wallpaper", + iconPath = "Path to icon", + wallpaperModeStretch = "Stretch", + wallpaperModeCenter = "Center", + wallpaperEnabled = "Wallpaper enabled", + wallpaperBrightness = "Wallpaper brightness", + wallpaperSwitchInfo = "Disabling this option will decrease RAM usage and increase system perfomance", + backgroundColor = "Background color", + menuColor = "Menu color", + dockColor = "Dock color", + transparencyEnabled = "Transparency enabled", + transparencySwitchInfo = "Disabling this option will increase system perfomance", + screenResolution = "Screen resolution", + changePassword = "Change password", + shortcutIsCorrupted = "Shortcut is linked to non-existent file", + sortAutomatically = "Align to grid", + onDesktop = "On desktop", + inCurrentDirectory = "In current directory", + + settings = "Preferences", + months = { + Jan = "Jan", + Feb = "Feb", + Mar = "Mar", + Apr = "Apr", + May = "May", + Jun = "Jun", + Jul = "Jul", + Aug = "Aug", + Sep = "Sep", + Oct = "Oct", + Nov = "Nov", + Dec = "Dec", + }, + timezone = "Timezone", + + errorWhileRunningProgram = "Error while running ", + sendedFeedback = "Feedback was sent", + sendFeedback = "Send feedback", + yourContacts = "Your contacts", + additionalInfo = "Additional information", + stackTraceback = "Stack traceback", +} \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Localization/Russian.lang b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Localization/Russian.lang new file mode 100755 index 00000000..d0f6950a --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Localization/Russian.lang @@ -0,0 +1,167 @@ +{ + network = "Сеть", + networkTimeout = "Удаленный компьютер не ответил на запрос", + networkAccessDenied = "Доступ к файловой системе удаленного компьютера запрещен. Свяжитесь с его владельцем для предоставления соответствующих привилегий", + networkAllowReadAndWrite = "Доступ к файлам", + networkModemNotAvailable = "Подключите модем для использования сетевых функций", + networkState = "Включить сетевой режим", + networkComputers = "Параметры удаленных ПК", + networkComputersNotFound = "Компьютеры в сети не обнаружены", + networkName = "Имя данного ПК", + networkSearchRadius = "Радиус обнаружения", + openWith = "Открыть с помощью", + select = "Выбрать программу…", + keepInDock = "Оставить в Dock", + closeAllWindows = "Закрыть все окна", + newWindow = "Новое окно", + launchWithArguments = "Запустить с аргументами", + dontShowAnymore = "Больше не показывать", + applicationHelp = "О приложении", + newName = "Новое имя", + folderName = "Имя папки", + fileName = "Имя файла", + applicationName = "Имя приложения", + file = "Файл", + notExists = "не существует", + alreadyExists = "уже существует", + inDirectory = "в директории", + needReplace = "Заменить?", + yes = "Да", + no = "Нет", + cancel = "Отмена", + open = "Открыть", + applyToAll = "Применить ко всем", + toDirectory = "в директории", + copying = "Копирование", + faylaBlyad = "файла", + + resolution = "Разрешение экрана", + pressAnyKeyToContinue = "Нажмите любую клавишу, чтобы продолжить", + screensaver = "Заставка", + screensaverDelay = "Задержка", + screensaverEnabled = "Использовать заставку", + + areYouSure = "Вы уверены?", + emptyTrash = "Очистить корзину", + type = "Тип", + size = "Размер", + date = "Дата", + path = "Путь", + folder = "Папка", + unknown = "Неизвестно", + calculatingSize = "идет подсчет…", + + properties = "Свойства", + newFolderFromChosen = "Новая папка из выбранного", + create = "Создать", + newFolder = "Папку", + newFile = "Файл", + newFileFromURL = "Файл по URL", + newApplication = "Приложение MineOS", + paste = "Вставить", + copy = "Копировать", + cut = "Вырезать", + edit = "Редактировать", + editInPhotoshop = "Редактировать в Photoshop", + rename = "Переименовать", + editShortcut = "Редактировать ярлык", + createShortcut = "Создать ярлык", + addToDock = "Добавить в Dock", + removeFromDock = "Удалить из Dock", + moveRight = "Передвинуть правее", + moveLeft = "Передвинуть левее", + archive = "Добавить в архив", + delete = "Удалить", + addToFavourites = "Добавить в избранное", + setAsWallpaper = "Установить как обои", + showPackageContent = "Показать содержимое пакета", + showContainingFolder = "Открыть содержащую папку", + flashEEPROM = "Записать на EEPROM", + + sortBy = "Упорядочить", + sortByType = "По типу", + sortByName = "По имени", + sortByDate = "По дате", + showExtension = "Расширения файлов", + showHiddenFiles = "Скрытые файлы", + showApplicationIcons = "Иконки приложений", + + aboutSystem = "Об этом ПК", + updates = "Обновления", + update = "Обновить", + shutdown = "Выключить", + logout = "Выйти", + reboot = "Перезагрузить", + returnToShell = "Вернуться в Shell", + + protectYourComputer = "Защита компьютера", + inputPassword = "Введите пароль", + confirmInputPassword = "Подтвердите пароль", + oldPassword = "Старый пароль", + newPassword = "Новый пароль", + setProtectionMethod = "Изменить метод защиты компьютера", + wrongOldPassword = "Неверный старый пароль", + passwordSucessfullyChanged = "Пароль успешно изменен!", + withoutProtection = "Без защиты", + passwordProtection = "Защита паролем", + biometricProtection = "Биометрическая", + putFingerToVerify = "Приложите палец для авторизации", + putFingerToRegister = "Приложите палец для создания биометрического снимка", + fingerprintCreated = "Биометрический снимок создан", + accessDenied = "Доступ запрещен", + welcomeBack = "Добро пожаловать, ", + passwordsAreDifferent = "Пароли не совпадают", + incorrectPassword = "Неверный пароль", + mineOSCreatorUsedMasterPassword = "Создатель операционной системы использовал мастер-ключ", + loginToSystem = "Вход в систему", + + colorScheme = "Цветовая схема", + iconProperties = "Параметры иконок", + spaceBetweenIcons = "Расстояние между иконками", + sizeOfIcons = "Размер иконок", + byHorizontal = "По горизонтали", + byVertical = "По вертикали", + wallpaper = "Обои", + wallpaperPath = "Путь к обоям", + iconPath = "Путь к иконке", + wallpaperModeStretch = "Растянуть", + wallpaperModeCenter = "По центру", + wallpaperEnabled = "Использовать обои", + wallpaperBrightness = "Яркость обоев", + wallpaperSwitchInfo = "Отключение этой опции существенно снизит расход оперативной памяти и увеличит производительность", + backgroundColor = "Цвет фона", + menuColor = "Цвет меню", + dockColor = "Цвет Dock", + transparencyEnabled = "Прозрачность интерфейса", + transparencySwitchInfo = "Отключение этой опции существенно снизит количество прямых обращений к CPU и увеличит производительность", + screenResolution = "Разрешение экрана", + changePassword = "Изменить пароль", + shortcutIsCorrupted = "Ярлык ссылается на несуществующий файл", + sortAutomatically = "Привязать к сетке", + onDesktop = "На рабочем столе", + inCurrentDirectory = "В текущей директории", + + settings = "Настройки", + months = { + Jan = "Января", + Feb = "Февраля", + Mar = "Марта", + Apr = "Апреля", + May = "Мая", + Jun = "Июня", + Jul = "Июля", + Aug = "Августа", + Sep = "Сентября", + Oct = "Октября", + Nov = "Ноября", + Dec = "Декабря", + }, + timezone = "Временная зона", + + errorWhileRunningProgram = "Ошибка при выполнении ", + sendedFeedback = "Отчет отправлен", + sendFeedback = "Отправить отчет", + yourContacts = "Ваши контакты", + additionalInfo = "Дополнительная информация", + stackTraceback = "Стек ошибки", +} \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Properties.cfg b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Properties.cfg new file mode 100644 index 00000000..2531649d --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Properties.cfg @@ -0,0 +1,86 @@ +{ + wallpaper = "/MineOS/Pictures/Girl.pic", + dockColor = 16777215, + screensaverEnabled = false, + iconWidth = 12, + timezone = 3, + showApplicationIcons = true, + network = { + enabled = true, + name = "Йа аццкий сотона2", + signalStrength = 512, + users = { + ["055ad405-a2ff-47fa-a554-12582bb22a67"] = { + allowMessages = true, + allowReadAndWrite = true + }, + ["7dd58ca3-450c-4d9f-b278-ce3722a31ae7"] = { + allowReadAndWrite = true + }, + ["3de501c7-f31a-4a11-a1f6-09c32ea94428"] = { + allowMessages = true, + allowReadAndWrite = true + } + } + }, + wallpaperEnabled = false, + iconHorizontalSpaceBetween = 1, + screensaver = "XCOM.lua", + iconVerticalSpaceBetween = 1, + showExtension = false, + protectionMethod = "withoutProtection", + transparencyEnabled = true, + biometryHash = "d9d045d9c7711d72efaf75407c8eaa9acc5dc70f9be0087099131b7ed22cbb9d", + extensionAssociations = { + [".txt"] = { + icon = "/MineOS/System/Icons//Text.pic", + launcher = "/MineOS/Applications//MineCode IDE.app/Main.lua" + }, + [".lua"] = { + icon = "/MineOS/System/Icons//Lua.pic", + contextMenu = "/MineOS/System/Extensions/Lua/ContextMenu.lua", + launcher = "/MineOS/System/Extensions/Lua/Launcher.lua" + }, + script = { + icon = "/MineOS/System/Icons//Script.pic", + contextMenu = "/MineOS/System/Extensions/Lua/ContextMenu.lua", + launcher = "/MineOS/System/Extensions/Lua/Launcher.lua" + }, + [".pic"] = { + icon = "/MineOS/System/Icons//Image.pic", + contextMenu = "/MineOS/System/Extensions/Pic/ContextMenu.lua", + launcher = "/MineOS/Applications//Photoshop.app/Main.lua" + }, + [".arc"] = { + icon = "/MineOS/System/Icons//Archive.pic", + launcher = "/MineOS/System/Extensions/Arc/Launcher.lua" + }, + [".3dm"] = { + icon = "/MineOS/System/Icons//3DModel.pic", + launcher = "/MineOS/Applications//3DPrint.app/Main.lua" + }, + [".cfg"] = { + icon = "/MineOS/System/Icons//Config.pic", + launcher = "/MineOS/Applications//MineCode IDE.app/Main.lua" + } + }, + language = "Russian", + backgroundColor = 1973790, + dockShortcuts = { + [5] = "/MineOS/Applications/Control.app/", + [1] = "/MineOS/Applications/AppMarket.app/", + [2] = "/MineOS/Applications/MineCode IDE.app/", + [3] = "/MineOS/Applications/Finder.app/", + [4] = "/MineOS/Applications/Photoshop.app/" + }, + iconHeight = 6, + wallpaperMode = 2, + screensaverDelay = 26.675, + resolution = { + [1] = 160, + [2] = 44 + }, + menuColor = 14803425, + wallpaperBrightness = 1, + showHelpOnApplicationStart = false +} \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Radio/Stations.cfg b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Radio/Stations.cfg new file mode 100644 index 00000000..02f30a07 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Radio/Stations.cfg @@ -0,0 +1 @@ +{[1]={["url"]="http://galnet.ru:8000/soft",["name"]="Galnet Soft"},[2]={["url"]="http://ep256.streamr.ru",["name"]="Европа Плюс"},[3]={["url"]="http://server2.lradio.ru:8000/lradio64.aac.m3u",["name"]="L-Radio"},[4]={["url"]="http://online.radiorecord.ru:8101/rr_128.m3u",["name"]="Radio Record"},[5]={["url"]="http://livestream.rfn.ru:8080/moscowfmen128.m3u",["name"]="Moscow FM"},["currentStation"]=3} \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Screensavers/Clock.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Screensavers/Clock.lua new file mode 100755 index 00000000..14fa2a8b --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Screensavers/Clock.lua @@ -0,0 +1,70 @@ +local gpu = require("component").gpu +local event = require("event") +local w, h, t, q = gpu.getResolution() +local numb, ha, wh, p, s, u, e, gsB, gS, ti, r, slp, tn = {29850,29351,30887,18925,14735,27343,9383,31407,31147,[0]=31599}, h/2-2, {0, 8, nil, 18, 26}, "▀", " ", h%2, w/2, gpu.setBackground, gpu.set, table.insert, math.random, os.sleep, tonumber + +local function drawN(x, y, n) + local c = 0 + for i = 0, 14 do + if bit32.extract(numb[n], i) == 1 then + gsB(60928) + gS(x, y, s) + else + gsB(0) + gS(x, y, s) + end + c, x = c + 1, x + 2 + if c % 3 == 0 then + y, x = y + 1, x - 6 + end + end +end + +gsB(0) +gpu.fill(1, 1, w, h, " ") +local tbl = {x = {}, y = {}} +for x = 1, w, 2 do + for y = 1, ha-1-u do + ti(tbl.x, x) + ti(tbl.y, y) + end +end +for n = 1, #tbl.x do + k = r(n) + tbl.x[n], tbl.x[k], tbl.y[n], tbl.y[k] = + tbl.x[k], tbl.x[n], tbl.y[k], tbl.y[n] +end +while true do + q = 1 + for i = 1, #tbl.x do + gpu.setForeground(r(tbl.x[i]*tbl.y[i])*512) + gS(tbl.x[i], tbl.y[i], p) + gS(-tbl.x[i]+w, -tbl.y[i]+h+1, p) + q = q + 1 + if q == 55 then + t = os.date("%T") + for o = 1, 5 do + if o ~= 3 then + drawN(e+wh[o]-15, ha+u, tn(t:sub(o,o))) + end + end + if tn(t:sub(5, 5))%2 == 0 then + gsB(60928) + else + gsB(0) + end + gS(e, ha+1+u, s) + gS(e, ha+3+u, s) + gsB(0) + q = 1 + slp(0.05) + end + local cykaNahooy = {event.pull(0)} + if cykaNahooy[1] == "key_down" or cykaNahooy[1] == "touch" then + gpu.setBackground(0x0) + gpu.fill(1, 1, w, h, " ") + return + end + end + slp(0.05) +end \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Screensavers/Lines.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Screensavers/Lines.lua new file mode 100644 index 00000000..41092ad1 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Screensavers/Lines.lua @@ -0,0 +1,62 @@ + +local buffer = require("doubleBuffering") +local event = require("event") + +------------------------------------------------------------------------------------- + +local lineCount = 10 +local backgroundColor = 0x0 +local lineColor = 0xFFFFFF +local bufferWidth, bufferHeight = buffer.getResolution() + +------------------------------------------------------------------------------------- + +local t = {} + +function rnd() + if math.random(0,1) == 0 then + return -1 + else + return 1 + end +end + +for i = 1, lineCount do + t[i] = { + x = math.random(1, bufferWidth), + y = math.random(1, bufferHeight * 2), + dx = rnd(), + dy = rnd() + } +end + +------------------------------------------------------------------------------------- + +buffer.clear(backgroundColor) +buffer.draw(true) + +while true do + local eventType = event.pull(0.0001) + if eventType == "touch" or eventType == "key_down" then + break + end + + for i = 1, lineCount do + t[i].x = t[i].x + t[i].dx + t[i].y = t[i].y + t[i].dy + + if t[i].x > bufferWidth then t[i].dx = -1 end + if t[i].y > bufferHeight * 2 then t[i].dy = -1 end + if t[i].x < 1 then t[i].dx = 1 end + if t[i].y < 1 then t[i].dy = 1 end + end + + buffer.clear(backgroundColor) + + for i = 1, lineCount - 1 do + buffer.semiPixelLine(t[i].x, t[i].y, t[i + 1].x, t[i + 1].y, lineColor) + end + + buffer.semiPixelLine(t[1].x, t[1].y, t[lineCount].x, t[lineCount].y, lineColor) + buffer.draw() +end \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Screensavers/Mandala.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Screensavers/Mandala.lua new file mode 100755 index 00000000..2affae0e --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Screensavers/Mandala.lua @@ -0,0 +1,147 @@ +local gpu, r, xr, ti = require("component").gpu, math.random, bit32.bxor, table.insert +local event = require("event") + +local tbl, tbl1, S, gsF, gsB, w, h, n, c, Fc, Bc, C, D, i, j, m, k, q, p, a, b = {}, {x = {}, y = {}}, "▄", gpu.setForeground, gpu.setBackground, gpu.getResolution() + +local t = (w-h*2)/2 + +local function pix(x, y, color) + + n = y%2 + + y = (y+n)/2 + + c, Fc, Bc = gpu.get(x+t, y) + + if c ~= S then + + Fc = Bc + + end + + if n == 0 then + + Fc = color + + else + + Bc = color + + end + + gsF(Fc) + + gsB(Bc) + + gpu.set(x+t, y, S) + +end + + + +gsB(0) + +gpu.fill(1, 1, w, h, " ") + +for i = 1, h do + + tbl[i] = {} + + for j = 1, h do + + ti(tbl1.x, i) + + ti(tbl1.y, j) + + end + +end + +for n = 1, #tbl1.x do + + k = r(n) + + tbl1.x[n], tbl1.x[k], tbl1.y[n], tbl1.y[k] = + + tbl1.x[k], tbl1.x[n], tbl1.y[k], tbl1.y[n] + +end + +while true do + + for i = 1, h do + + for j = 1, h do + + tbl[i][j] = 0 + + end + + end + + for i = 1, h do + + m = r(0, 1) + + tbl[i][1], tbl[1][i] = m, m + + end + + C, D, i, j = r(0, 255), t + + for y = 2, #tbl do + + for x = y, #tbl[y] do + + q = xr(tbl[x-1][y], tbl[x][y-1]) + + tbl[x][y], tbl[y][x] = q, q + + end + + end + + for o = 1, #tbl1.x do + + i, j = tbl1.x[o], tbl1.y[o] + + p, a, b = i*j*C, -j+h*2, -i+h*2 + + if tbl[i][j] == 1 then + + pix(j, i, p) + + pix(a, b, p) + + pix(a, i, p) + + pix(j, b, p) + + else + + pix(j, i, 0) + + pix(a, b, 0) + + pix(a, i, 0) + + pix(j, b, 0) + + end + + pix(r(-D+1, 0), r(1, h*2), C) + + pix(r(h*2, w-D), r(1, h*2), C) + + end + + gsF(65280) + gsB(0) + + local e = {event.pull(1)} + if e[1] == "key_down" or e[1] == "touch" then + gpu.setBackground(0x0) + gpu.fill(1, 1, w, h, " ") + break + end +end \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Screensavers/Matrix.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Screensavers/Matrix.lua new file mode 100755 index 00000000..ae6b176a --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Screensavers/Matrix.lua @@ -0,0 +1,96 @@ + +local event = require("event") +local gpu = require("component").gpu + +-------------------------------------------------------------------------------------------------------------------- + +local maximumLines = 60 +local minimumLineLength = 5 +local maximumLineLength = 55 +local backgroundColor = 0x000000 +local speed = 0.00 + +local chars = { "ァ", "ア", "ィ", "イ", "ゥ", "ウ", "ェ", "エ", "ォ", "オ", "カ", "ガ", "キ", "ギ", "ク", "グ", "ケ", "ゲ", "コ", "ゴ", "サ", "ザ", "シ", "ジ", "ス", "ズ", "セ", "ゼ", "ソ", "ゾ", "タ", "ダ", "チ", "ヂ", "ッ", "ツ", "ヅ", "テ", "デ", "ト", "ド", "ナ", "ニ", "ヌ", "ネ", "ノ", "ハ", "バ", "パ", "ヒ", "ビ", "ピ", "フ", "ブ", "プ", "ヘ", "ベ", "ペ", "ホ", "ボ", "ポ", "マ", "ミ", "ム", "メ", "モ", "ャ", "ヤ", "ュ", "ユ", "ョ", "ヨ", "ラ", "リ", "ル", "レ", "ロ", "ヮ", "ワ", "ヰ", "ヱ", "ヲ", "ン", "ヴ", "ヵ", "ヶ", "・", "ー", "ヽ", "ヾ" } +local lineColorsForeground = { 0xFFFFFF, 0xBBFFBB, 0x88FF88, 0x33FF33, 0x00FF00, 0x00EE00, 0x00DD00, 0x00CC00, 0x00BB00, 0x00AA00, 0x009900, 0x008800, 0x007700, 0x006600, 0x005500, 0x004400, 0x003300, 0x002200, 0x001100 } +local lineColorsBackground = { 0x004400, 0x004400, 0x003300, 0x003300, 0x002200, 0x001100 } + +-------------------------------------------------------------------------------------------------------------------- + +local lines = {} +local lineColorsForegroundCount = #lineColorsForeground +local charsCount = #chars +local screenWidth, screenHeight = gpu.getResolution() +local currentBackground, currentForeground + +local function setBackground(color) + if currentBackground ~= color then + gpu.setBackground(color) + currentBackground = color + end +end + +local function setForeground(color) + if currentForeground ~= color then + gpu.setForeground(color) + currentForeground = color + end +end + +-------------------------------------------------------------------------------------------------------------------- + +setBackground(backgroundColor) +gpu.fill(1, 1, screenWidth, screenHeight, " ") + +local i, colors, background, part, eventType +while true do + while #lines < maximumLines do + table.insert(lines, { + x = math.random(1, screenWidth), + y = 1, + length = math.random(minimumLineLength, maximumLineLength) + }) + end + + gpu.copy(1, 1, screenWidth, screenHeight, 0, 1) + setBackground(backgroundColor) + gpu.fill(1, 1, screenWidth, 1, " ") + + i, colors = 1, {} + while i <= #lines do + lines[i].y = lines[i].y + 1 + if lines[i].y - lines[i].length > 0 then + table.remove(lines, i) + else + part = math.ceil(lineColorsForegroundCount * lines[i].y / lines[i].length) + + background = lineColorsBackground[part] or 0x000000 + colors[background] = colors[background] or {} + colors[background][lineColorsForeground[part]] = colors[background][lineColorsForeground[part]] or {} + table.insert(colors[background][lineColorsForeground[part]], i) + + i = i + 1 + end + end + + for background in pairs(colors) do + setBackground(background) + for foreground in pairs(colors[background]) do + setForeground(foreground) + for i = 1, #colors[background][foreground] do + gpu.set(lines[colors[background][foreground][i]].x, 1, chars[math.random(1, charsCount)]) + end + end + end + + eventType = event.pull(speed) + if eventType == "key_down" or eventType == "touch" then + setBackground(0x000000) + setForeground(0xFFFFFF) + gpu.fill(1, 1, screenWidth, screenHeight, " ") + break + end +end + + + + diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Screensavers/NyanCat.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Screensavers/NyanCat.lua new file mode 100644 index 00000000..b358486c --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Screensavers/NyanCat.lua @@ -0,0 +1,834 @@ +local buffer = require("doubleBuffering") +local event = require("event") + +local nyans = { + { + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,.,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ".,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,''''''''''''''',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,>>>>>>>,,,,,,,,>>>>>>'@@@@@@@@@@@@@@@',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ">>>>>>>>>>>>>>>>>>>>>>>'@@@$$$$$$$$$$$@@@',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ">>>>>>>>>>>>>>>>>>>>>>>'@@$$$$$-$$-$$$$@@',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "&&&&&&&&&&&&&&&&&&&&&&&'@$$-$$$$$$''$-$$@','',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "&&&&&&&&&&&&&&&&&&&&&&&'@$$$$$$$$'**'$$$@''**',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "&&&+++++++&&&&&&&&'''++'@$$$$$-$$'***$$$@'***',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "++++++++++++++++++**''+'@$$$$$$$$'***''''****',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "++++++++++++++++++'**'''@$$$$$$$$'***********',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "+++#######++++++++''**''@$$$$$$-'*************',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "###################''**'@$-$$$$$'***.'****.'**',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "####################''''@$$$$$$$'***''**'*''**',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "###=======########====''@@$$$-$$'*%%********%%',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "======================='@@@$$$$$$'***''''''**',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "===;;;;;;;.=======;;;;'''@@@@@@@@@'*********',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ";;;;;;;;;;;;;;;;;;;;;'***''''''''''''''''''',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ";;;;;;;;;;;;;;;;;;;;;'**'','*',,,,,'*','**',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ";;;,,,,.,,;;;.;;;;,,,'''',,'',,,,,,,'',,'',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,..,..,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + }, + { + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,..,..,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,.,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,.,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,.,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,''''''''''''''',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,>>>>>>>,,,,,,,,>>>>>>'@@@@@@@@@@@@@@@',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ">>>>>>>>>>>>>>>>>>>>>>>'@@@$$$$$$$$$$$@@@',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ">>>>>>>>>>>>>>>>>>>>>>>'@@$$$$$-$$-$$$$@@',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "&&&&&&&&&&&&&&&&&&&&&&&'@$$-$$$$$$$''-$$@',,'',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "&&&&&&&&&&&&&&&&&&&&&&&'@$$$$$$$$$'**'$$@','**',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "&&&+++++++&&&&&&&&+++++'@$$$$$-$$$'***$$@''***',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "+++++++++++++++++++'+++'@$$$$$$$$$'***''''****',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "++++++++++++++++++'*'++'@$$$$$$$$$'***********',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "+++#######++++++++'*''''@$$$$$$-$'*************',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "###################****'@$-$$$$$$'***.'****.'**',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "###################''**'@$$$$$$$$'***''**'*''**',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "###=======########==='''@@$$$-$$$'*%%********%%',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "======================='@@@$$$$$$$'***''''''**',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "===;;;;;;;========;;;;;''@@@@@@@@@@'*********',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ";;;;;;;;;;;;;;;;;;;;;;'**'''''''''''''''''''',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ";;;;;;;;;;;;;;;;;;;;;;'**','*',,,,,,**','**',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ";;;,,.,,,,;;;;;;;;,,,,''',,,'',,,,,,''',,''',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,..,,..,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + }, + { + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,..,.,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,..,.,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,", + ">>,,,,,,,>>>>>>>>,,,,,,,,''''''''''''''',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ">>>>>>>>>>>>>>>>>>>>>>>>'@@@@@@@@@@@@@@@',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ">>>>>>>>>>>>>>>>>>>>>>>'@@@$$$$$$$$$$$@@@',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "&&&&&&&&&&&&&&&&&&&&&&&'@@$$$$$-$$-$$$$@@',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "&&&&&&&&&&&&&&&&&&&&&&&'@$$-$$$$$$$''-$$@',,'',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "++&&&&&&&++++++++&&&&&&'@$$$$$$$$$'**'$$@','**',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "+++++++++++++++++++++++'@$$$$$-$$$'***$$@''***',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "+++++++++++++++++++++++'@$$$$$$$$$'***''''****',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "##+++++++########++++++'@$$$$$$$$$'***********',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "######################''@$$$$$$-$'*************',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "###################'''''@$-$$$$$$'***.'****.'**',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "==#######========#'****'@$$$$$$$$'***''**'*''**',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "==================='''='@@$$$-$$$'*%%********%%',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ";;=======;;;;;;;;======'@@@$$$$$$$'***''''''**',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ";;;;;;;;;;;;;;;;;;;;;;;''@@@@@@@@@@'*********',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ";.;;;;;;;;;;;;;;;;;;;;;'*'''''''''''''''''''',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ".,.;;;;;;,,,,,,,,;;;;;;'**',**',,,,,,**','**',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",.,,,,,,,,,,,,,,,,,,,,,''',,''',,,,,,''',,''',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,.,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,.,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + }, + { + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,.,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,.,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,.,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,.,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,", + ">>,,,,,,,>>>>>>>>,,,,,,,,''''''''''''''',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ">>>>>>>>>>>>>>>>>>>>>>>>'@@@@@@@@@@@@@@@',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ">>>>>>>>>>>>>>>>>>>>>>>'@@@$$$$$$$$$$$@@@',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "&&&&&&&&&&&&&&&&&&&&&&&'@@$$$$$-$$-$$$$@@',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "&&&&&&&&&&&&&&&&&&&&&&&'@$$-$$$$$$$''-$$@',,'',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "++&&&&&&&++++++++&&&&&&'@$$$$$$$$$'**'$$@','**',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "+++++++++++++++++++++++'@$$$$$-$$$'***$$@''***',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "+++++++++++++++++++++++'@$$$$$$$$$'***''''****',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "##+++++++########++++++'@$$$$$$$$$'***********',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "#####################'''@$$$$$$-$'*************',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "###################''**'@$-$$$$$$'***.'****.'**',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "==#######========##****'@$$$$$$$$'***''**'*''**',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "=================='*'=='@@$$$-$$$'*%%********%%',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ";;=======;;;;;;;;=='==='@@@$$$$$$$'***''''''**',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ";;;;;;;;;;;;;;;;;;;;;;;''@@@@@@@@@@'*********',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ";;;;;;;;;;;;;;;;;;;;;;'**'''''''''''''''''''',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,;;;;;;;,,,,,,,,;;;;;'**','*',,,,,,'*','**',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,''',,,'',,,,,,,'',,''',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,.,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + }, + { + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,.,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,>>>>>>>,,,,,,,,>>>>>>>''''''''''''''',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ">>>>>>>>>>>>>>>>>>>>>>>>'@@@@@@@@@@@@@@@',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ">>>>>>>>>>>>>>>>>>>>>>>'@@@$$$$$$$$$$$@@@',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "&&&&&&&&&&&&&&&&&&&&&&&'@@$$$$$-$$-$$$$@@',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "&&&&&&&&&&&&&&&&&&&&&&&'@$$-$$$$$$''$-$$@','',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "&&&+++++++&&&&&&&&+++++'@$$$$$$$$'**'$$$@''**',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "+++++++++++++++++++++++'@$$$$$-$$'***$$$@'***',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "++++++++++++++++++'''++'@$$$$$$$$'***''''****',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "+++#######+++++++'**''''@$$$$$$$$'***********',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "#################'****''@$$$$$$-'*************',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "##################''''*'@$-$$$$$'***.'****.'**',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "###=======########==='''@$$$$$$$'***''**'*''**',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "======================='@@$$$-$$'*%%********%%',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "===;;;;;;;========;;;;''@@@$$$$$$'***''''''**',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ";;;;;;;;;;;;;;;;;;;;;''''@@@@@@@@@'*********',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ";;;;;;;;;;;;;;;;;;;;'***'''''''''''''''''''',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ";;;,,,,,,,;;;;;;;;,,'**','**,,,,,,'**,'**',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,''',,,'',,,,,,,'',,''',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,.,..,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + }, + { + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,..,..,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,>>>>>>>,,,,,,,,>>>>>>>''''''''''''''',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ">>>>>>>>>>>>>>>>>>>>>>>>'@@@@@@@@@@@@@@@',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ">>>>>>>>>>>>>>>>>>>>>>>'@@@$$$$$$$$$$$@@@',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "&&&&&&&&&&&&&&&&&&&&&&&'@@$$$$$-$$''$$$@@','',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "&&&&&&&&&&&&&&&&&&&&&&&'@$$-$$$$$'**'-$$@''**',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "&&&+++++++&&&&&&&&+++++'@$$$$$$$$'***$$$@'***',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "+++++++++++++++++++'+++'@$$$$$-$$'***''''****',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "++++++++++++++++++'*'++'@$$$$$$$$'***********',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "+++#######++++++++'*''''@$$$$$$$'*************',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "###################****'@$$$$$$-'***.'****.'**',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "###################''**'@$-$$$$$'***''**'*''**',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "###=======########==='''@$$$$$$$'*%%********%%',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "======================='@@$$$-$$$'***''''''**',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "===;;;;;;;========;;;;''@@@$$$$$$$'*********',,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,", + ";;;;;;;;;;;;;;;;;;;;;'*''@@@@@@@@@@''''''''',,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,", + ";;;;;;;;;;;;;;;;;;;;'***''''''''''''''''*',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ";;;,,,,,,,;;;;;;;;,,'**','**,,,,,,'**,'**',,,,,,,,,,,,,,,,,,..,.,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,''',,''',,,,,,''',,''',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,..,..,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",.,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + }, + { + ".,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ".,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,..,.,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,''''''''''''''',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ">>,,,,,,,>>>>>>>>,,,,,,,'@@@@@@@@@@@@@@@',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ">>>>>>>>>>>>>>>>>>>>>>>'@@@$$$$$$$$$$$@@@',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ">>>>>>>>>>>>>>>>>>>>>>>'@@$$$$$-$$-$$$$@@',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "&&&&&&&&&&&&&&&&&&&&&&&'@$$-$$$$$$''$-$$@','',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "&&&&&&&&&&&&&&&&&&&&&&&'@$$$$$$$$'**'$$$@''**',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "++&&&&&&&++++++++&'''&&'@$$$$$-$$'***$$$@'***',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "++++++++++++++++++'*''+'@$$$$$$$$'***''''****',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "++++++++++++++++++'**'''@$$$$$$$$'***********',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "##+++++++########++'**''@$$$$$$-'*************',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "###################''**'@$-$$$$$'***.'****.'**',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "####################''''@$$$$$$$'***''**'*''**',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "==#######========#####''@@$$$-$$'*%%********%%',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "======================='@@@$$$$$$'***''''''**',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ";;=======;;;;;;;;====='''@@@@@@@@@'*********',,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,", + ";;;;;;;;;;;;;;;;;;;;;'***''''''''''''''''''',,,,,,,,,,.,,,.,,,,,,,,,,,,,,,,,,,,,", + ";;;;;;;;;;;;;;;;;;;;;'**'','*',,,,,'**,'**',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,;;;;;;;,,,,,,,,;;;;'''',,'',,,,,,,'',,'',,,,,,,,,,,.,,,,,.,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,.,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,.,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + }, + { + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,.,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,..,..,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,''''''''''''''',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ">>,,,,,,,>>>>>>>>,,,,,,,'@@@@@@@@@@@@@@@',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ">>>>>>>>>>>>>>>>>>>>>>>'@@@$$$$$$$$$$$@@@',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ">>>>>>>>>>>>>>>>>>>>>>>'@@$$$$$-$$-$$$$@@',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "&&&&&&&&&&&&&&&&&&&&&&&'@$$-$$$$$$$''-$$@',,'',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "&&&&&&&&&&&&&&&&&&&&&&&'@$$$$$$$$$'**'$$@','**',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "++&&&&&&&++++++++&&&&&&'@$$$$$-$$$'***$$@''***',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "+++++++++++++++++++'+++'@$$$$$$$$$'***''''****',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "++++++++++++++++++'*'++'@$$$$$$$$$'***********',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "##+++++++########+'*''''@$$$$$$-$'*************',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "###################****'@$-$$$$$$'***.'****.'**',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "###################''**'@$$$$$$$$'***''**'*''**',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "==#######========####'''@@$$$-$$$'*%%********%%',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "======================='@@@$$$$$$$'***''''''**',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ";;=======;;;;;;;;======''@@@@@@@@@@'*********',,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ";;;;;;;;;;;;;;;;;;;;;;'**'''''''''''''''''''',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ";;;;;;;;;;;;;;;;;;;;;;'**','*',,,,,,**','**',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,;;;;;;;,,,,,,,,;;;;;''',,,'',,,,,,''',,''',,.,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,..,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + }, + { + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,.,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,.,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,..,...,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,>>>>>>>,,,,,,,,>>>>>>>''''''''''''''',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ">>>>>>>>>>>>>>>>>>>>>>>>'@@@@@@@@@@@@@@@',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ">>>>>>>>>>>>>>>>>>>>>>>'@@@$$$$$$$$$$$@@@',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "&&&&&&&&&&&&&&&&&&&&&&&'@@$$$$$-$$-$$$$@@',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "&&&&&&&&&&&&&&&&&&&&&&&'@$$-$$$$$$$''-$$@',,'',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "&&&+++++++&&&&&&&&+++++'@$$$$$$$$$'**'$$@','**',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "+++++++++++++++++++++++'@$$$$$-$$$'***$$@''***',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "+++++++++++++++++++++++'@$$$$$$$$$'***''''****',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "+++#######++++++++#####'@$$$$$$$$$'***********',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "######################''@$$$$$$-$'*************',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "###################'''''@$-$$$$$$'***.'****.'**',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "###=======########'****'@$$$$$$$$'***''**'*''**',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "==================='''='@@$$$-$$$'*%%********%%',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "===;;;;;;;========;;;;;'@@@$$$$$$$'***''''''**',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ";;;;;;;;;;;;;;;;;;;;;;;''@@@@@@@@@@'*********',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ";;;;;;;;;;;;;;;;;;;;;;;'*'''''''''''''''''''',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ";;;,,,,,,,;;;;;;;;,,,,,'**',**',,,,,,**'.'**',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,''',,''',,,,,,''',,''',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,.,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,.,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,", + }, + { + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,.,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,.,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,.,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,>>>>>>>,,,,,,,,>>>>>>>''''''''''''''',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ">>>>>>>>>>>>>>>>>>>>>>>>'@@@@@@@@@@@@@@@',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ">>>>>>>>>>>>>>>>>>>>>>>'@@@$$$$$$$$$$$@@@',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "&&&&&&&&&&&&&&&&&&&&&&&'@@$$$$$-$$-$$$$@@',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "&&&&&&&&&&&&&&&&&&&&&&&'@$$-$$$$$$$''-$$@',,'',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "&&&+++++++&&&&&&&&+++++'@$$$$$$$$$'**'$$@','**',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "+++++++++++++++++++++++'@$$$$$-$$$'***$$@''***',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "+++++++++++++++++++++++'@$$$$$$$$$'***''''****',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "+++#######++++++++#####'@$$$$$$$$$'***********',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "#####################'''@$$$$$$-$'*************',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "###################''**'@$-$$$$$$'***.'****.'**',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "###=======########=****'@$$$$$$$$'***''**'*''**',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "=================='*'=='@@$$$-$$$'*%%********%%',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "===;;;;;;;========;';;;'@@@$$$$$$$'***''''''**',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ";;;;;;;;;;;;;;;;;;;;;;;''@@@@@@@@@@'*********',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ";;;;;;;;;;;;;;;;;;;;;;'**'''''''''''''''''''',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ";;;,,,,,,,;;;;;;;;,,,,'**','*',,..,.**','**',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,''',,,'',,,,.,''',,''',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,.,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,.,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,.,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + }, + { + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ".,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ".,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,.,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ">>,,,,,,,>>>>>>>>,,,,,,,,''''''''''''''',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ">>>>>>>>>>>>>>>>>>>>>>>>'@@@@@@@@@@@@@@@',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ">>>>>>>>>>>>>>>>>>>>>>>'@@@$$$$$$$$$$$@@@',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "&&&&&&&&&&&&&&&&&&&&&&&'@@$$$$$-$$-$$$$@@',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "&&&&&&&&&&&&&&&&&&&&&&&'@$$-$$$$$$''$-$$@','',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "++&&&&&&&++++++++&&&&&&'@$$$$$$$$'**'$$$@''**',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "+++++++++++++++++++++++'@$$$$$-$$'***$$$@'***',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "++++++++++++++++++'''++'@$$$$$$$$'***''''****',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "##+++++++########'**''''@$$$$$$$$'***********',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "#################'****''@$$$$$$-'*************',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "##################''''*'@$-$$$$$'***.'****.'**',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "==#######========####'''@$$$$$$$'***''**'*''**',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "======================='@@$$$-$$'*%%********%%',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ";;=======;;;;;;;;=====''@@@$$$$$$'***''''''**',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ";;;;;;;;;;;;;;;;;;;;;''''@@@@@@@@@'*********',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ";;;;;;;;;;;;;;;;;;;;'***'''''''''''''''''''',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,;;;;;;;,,,,,,,,;;;'**'.'**..,,,,'**''**',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,''',,,'',,,,,,,''',''',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ".,.,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + }, + { + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ">>,,,,,,,>>>>>>>>,,,,,,,,''''''''''''''',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ">>>>>>>>>>>>>>>>>>>>>>>>'@@@@@@@@@@@@@@@',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ">>>>>>>>>>>>>>>>>>>>>>>'@@@$$$$$$$$$$$@@@',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "&&&&&&&&&&&&&&&&&&&&&&&'@@$$$$$-$$''$$$@@','',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "&&&&&&&&&&&&&&&&&&&&&&&'@$$-$$$$$'**'-$$@''**',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "++&&&&&&&++++++++&&&&&&'@$$$$$$$$'***$$$@'***',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "+++++++++++++++++++'+++'@$$$$$-$$'***''''****',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "++++++++++++++++++'*'++'@$$$$$$$$'***********',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "##+++++++########+'*''''@$$$$$$$'*************',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "###################****'@$$$$$$-'***.'****.'**',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "###################''**'@$-$$$$$'***''**'*''**',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "==#######========####'''@$$$$$$$'*%%********%%',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + "======================='@@$$$-$$$'***''''''**',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ";;=======;;;;;;;;=.===''@@@$$$$$$$'*********',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ";;;;;;;;;;;;;;;;;;;;.'*''@@@@@@@@@@''''''''',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ";;;;;;;;;;;;;;;;;;;;'***''''''''''''''''*',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,;;;;;;;,,,,,,,.;;;'**','**,,,,,,'**''**',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,''',,''',,,,,,''',,''',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", + } +} + +local colors = { + [","] = 0x00005f, -- Blue background + ["."] = 0xffffff, -- White stars + ["'"] = 0x000000, -- Black border + ["@"] = 0xffffd7, -- Tan poptart + ["$"] = 0xd787af, -- Pink poptart + ["-"] = 0xd70087, -- Red poptart + [">"] = 0xff0000, -- Red rainbow + ["&"] = 0xffaf00, -- Orange rainbow + ["+"] = 0xffff00, -- Yellow Rainbow + ["#"] = 0x87ff00, -- Green rainbow + ["="] = 0x0087ff, -- Light blue rainbow + [";"] = 0x0000af, -- Dark blue rainbow + ["*"] = 0x585858, -- Gray cat face + ["%"] = 0xd787af -- Pink cheeks +} + +buffer.clear() +buffer.draw(true) + +local sizeX,sizeY = 80, 50 +while true do + for frame=1, #nyans do + for y=1, sizeY do + for x=1, sizeX do + local pos = (y <= sizeX and x <= sizeX) and string.sub(nyans[frame][y], x, x) or "," + buffer.square(x * 2 - 1, y, 2, 1, colors[pos], 0x0, " ") + end + end + buffer.draw() + local eventType = event.pull(0) + if eventType == "touch" or eventType == "key_down" then + return + end + end +end \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Screensavers/XCOM.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Screensavers/XCOM.lua new file mode 100755 index 00000000..843ed011 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/System/Screensavers/XCOM.lua @@ -0,0 +1,29 @@ + +local image = require("image") +local buffer = require("doubleBuffering") +local event = require("event") + +local source = image.fromString([[402A000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀002800⣄000B00⢤001600⣤281700⣤001700⣤281C00⣤004C00⣤284D00⣤004C00⣤004700⣤001C00⣤004700⣤284C00⣤057800⣤287700⣤287700⣤004700⣤287800⣤004C00⣤065200⡤287700⣤287700⣤284D00⣤287700⣤285200⣤057700⣤284C00⣤284700⣤004700⣤284C00⣤284700⣤004700⣤281C00⣤001700⣤004700⣤001700⣤001700⣤001700⣤001600⣤000600⠄000000⠀002800⡀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀002800⢀002800⢂000600⡐061C00⢂0B2100⣒002200⠒282200⠒281C00⠗281C00⠖281C00⠗282100⠲287D00⠚287D00⠒287D00⠚28A900⠒284D00⠒282100⠛062200⠒285200⠒285200⠒065200⠒285200⠒06A900⠒28A900⠓067D00⠚285200⠒287D00⠒067D00⠒287D00⠒285200⠒055200⠒282200⠒285200⠒282200⠒282200⠒282100⠚285200⠒285200⠒005200⠒0B5200⢒061C00⣒001000⣊000600⠂002800⢐000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀002800⠠002800⠠000100⠠280B00⢼065200⠤0B5200⠭000600⠡280600⢤000600⠥280600⠱002100⣤000600⠘004D00⢠000B00⠧280600⢕004700⢠001C00⡄280600⠥000600⡧001700⢠285200⡤007D00⢤001100⡄280B00⢥280600⠍284D00⠤280600⠧000600⠭004C00⢠004700⡄280600⠵280600⠥000600⠇002200⣠005200⠤001700⡄280600⠱000600⠬000600⠬000600⡔001700⠨0C7D00⠤292100⡅000600⠄000600⠄002800⠆000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀002800⣘000100⠐000600⣨281700⢀0B2200⣘004C00⡃000600⣘000600⣐285200⠒000600⣃004D00⠘067D00⣀005200⠃000600⣊280600⣃004D00⢘001C00⡃280600⣋000600⡒117D00⢂004700⡀004D00⢀284C00⡄000600⣊000600⡃0B5200⣐280600⡃000600⣃285200⢘004D00⡂000B00⣃000600⣓001C00⢸005200⣃000B00⣂285200⣘001600⠇005200⠐004700⠂000600⣓000600⡙0BA900⢚0B7D00⣃280B00⣣000600⠐000600⡀002800⡂000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀002800⠄002800⠦000100⠄000600⢦067D00⠤0B7D00⠤000B00⠣000600⡲280600⠴000600⠱000600⠢000600⠢004700⠉000600⠦000600⠢000600⡢001600⠈281600⠁000600⠦000600⠕001100⠈004700⠉001700⠉000B00⠵000600⠔000600⠔001700⠈280600⠔000600⠦001700⠈284C00⠉000C00⠁000600⠢000600⠢004700⠈051C00⠉004100⠁000600⠪000600⠢000600⠧280600⠣000600⠦007700⠸16A900⠤004D00⠄000600⠤000600⠠002800⠠002800⠁000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀002800⢀000600⢀000600⢐000C00⢀065200⣀1CA900⣈007800⡃000600⣜280600⣪005200⢠284D00⡞005200⠙284C00⠛000600⣏004D00⢀067D00⡞007D00⠛067D00⢣285200⡀050B00⣝117D00⣑007D00⣄0B7D00⣘280B00⣍000600⣇067D00⢸06A900⣋004700⡁000600⣍285200⢘284C00⡇280600⣝003C00⢸297D00⡟004C00⠛007D00⣄280600⢑004D00⢀285200⡜005200⠙282100⢳004C00⡄280600⣏001100⠈11A900⢉11A900⣁281100⡄000600⡄000100⠂000100⡀002800⠂000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀002800⢢000100⢀000600⢂281100⠰064D00⠴0B4D00⡶000B00⠳280600⠶280600⡶001100⠘007D00⠶004C00⠤005200⠶280B00⢾001100⠘007D00⠶004D00⠤007D00⠶281100⠣280B00⣶064D00⠶051700⠘067D00⠶280B00⣷280B00⣶064D00⠰004100⠇050B00⣷280B00⣶064C00⠰051C00⠇280600⡶001100⠸067800⠦004D00⠴004D00⠇000600⣲001100⠘005200⠶001C00⠤005200⠶280C00⠃000600⢖000600⠧281C00⠸112100⠷281700⡦000600⡂000600⠂002800⠆002800⠂000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀002800⡨002800⡭000600⢅000600⢦064D00⢨17A900⣭28A900⡅280B00⣼280B00⣽007D00⢠00A900⣠007D00⣀007D00⣄007D00⣤287D00⣤287D00⣤287D00⣤287D00⣤287D00⣤287D00⣤287D00⣤067D00⣀287D00⣤287D00⣤287D00⣤287D00⣤28A900⣀287D00⣤287D00⣤287D00⣠287D00⣤287D00⣤284D00⣤287D00⣤284D00⣄004D00⣄284D00⣄004D00⣤005200⣄007D00⣄004D00⣤007D00⣄005200⣠280B00⡭050600⡯001100⠈0C5200⢩0B5200⣭281100⡅000600⢈000600⠈002800⡌000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀002800⢐000600⠐000100⡈000600⢆280B00⣓06A900⣲16D400⡒001100⠃280B00⣷050B00⣷000C00⡘06A900⠳11A900⡒117D00⡒117D00⢒117D00⢒16A900⠒16A900⡒16A900⡒16A900⠒16A900⢒11A900⢒117D00⡒16A900⢒16A900⠒16A900⢒16A900⢒16A900⠒16A900⢒16A900⠒16A900⡒117D00⠒11A900⠒117D00⠒117D00⠒115200⡒114D00⡒105200⣒114D00⡒0B5200⣒114C00⢒107D00⠒0C5200⡒004700⠃280600⣺280B00⡲000600⣃00A900⢐117D00⡒281C00⣆000600⣒000600⢐000100⠐002800⠠000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀002800⠬000600⠔000600⠥000600⣭284D00⢨1CD400⢭29D400⡍000B00⣽280C00⢭060B00⠭060B00⡭000C00⣭06A900⠹16A900⡭16A900⠭16A900⡭16A900⢭1CA900⡭16A900⠭16A900⠭16A900⠭16A900⢭16A900⠭16A900⢭16A900⢭16A900⠭16A900⠭16A900⠭16A900⠭16A900⠭11A900⠭11A900⠭11A900⠭11A900⠭11A900⠭117D00⠭0B7D00⣭117D00⠭117D00⠭117D00⠭0B4D00⣭067D00⡭007800⠁280B00⣭280B00⣭280B00⣭280B00⢽004100⠈17A900⢭0BA900⣥000B00⡭000600⠤000600⠠000100⠁002800⢀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀002800⡘000600⡀000600⡣280B00⣒281C00⣐0CD400⣐47D400⡒004100⣓061100⣒281100⣚060C00⢐281100⣒281100⣒051100⣓067700⠘16A900⢒1CA900⣒1CA900⣒17A900⣛1CA900⣒17A900⣒16A900⣒16A900⣒17A900⣒16A900⣒16A900⣓16A900⣒16A900⣒11A900⢒16A900⣒16A900⣒11A900⣒11A900⣒117D00⣒117D00⣒117D00⣒117D00⣒0C7D00⣒11A900⣒117D00⣒11A900⣒28A900⠛280B00⣳280B00⣳280B00⣓280B00⣗280B00⣳280B00⣻280C00⣀287D00⢘17A900⣒287800⡂280B00⣇000600⣃000600⢂000500⡀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀002800⠠000600⠠000600⠨280600⠮281100⠤0B7D00⢤4CFF00⠤06D400⡇11FF00⠠06D400⣤061600⡬061600⠤061100⠥061100⠬061100⠬061100⠬067200⠈06A900⠽16A900⠭16A900⠭16A900⠭16A900⠭17A900⠭17A900⠭16A900⠭17A900⠭41A900⠭3CA900⠭107D00⠯0B2100⠭0CA900⠭16A900⠬16A900⠭16A900⠭16A900⠭16A900⠬16A900⠭167D00⠭11A900⠥117D00⠭117D00⠬287700⠏050B00⣽280B00⢯280B00⡯280B00⡯050B00⣯280B00⡿004700⢠06A900⠼067200⡌16D400⠭11D400⠤281600⠤280600⠤000600⠅000100⠄002800⠄000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀002800⣘000600⣈000600⣚280B00⣻054700⢚17D400⣘21D700⣛0BA900⣁4CD400⣓21D400⣛6DD400⣓0BD400⣀061700⣛061700⣓061600⣓061100⣓291100⣛061100⣚28A400⠛16A900⣛16A900⣛16A900⣛16A900⣛16A900⣛16A900⣛16A900⣛17A900⣂16A900⣀0BA900⣀0B4D00⣛11A900⣀16A900⣀16A900⣚16A900⣛16A900⣛16A900⣛11A900⣛11A900⣛11A900⣛0B7D00⡛284700⠃060B00⣓280B00⣻280B00⣻280B00⣻060C00⢀284D00⢀06D400⣐11A900⣛11A900⣛11D400⣃29A900⢸1CD400⣛067D00⣂280B00⣏000600⣃000600⢑002800⢑000000⠀000000⠀000000⠀000000⠀000000⠀002800⠠002800⠲000600⠦280600⡺281100⠴287D00⢴4CD400⠤0BD400⠇11D400⢼4CA900⠶4CD400⠤4CA900⠴4CA900⠾0BD400⣶0B4C00⡄061600⠶061100⠦061100⠦291100⠴061100⠤284700⠹11A900⠶16A900⠤16A900⠤16A900⠦16A900⠤17A900⠦16A900⠴16A900⠤117D00⠦0C7800⠴117D00⠶117D00⠴16A900⠴16A900⠤167D00⠴16A900⠤167D00⠦117D00⠦287800⠏281100⠵060B00⠴280B00⢿280B00⢿060B00⠴000C00⠖285200⢰0C7D00⠶167D00⠶16A900⠴167D00⠦16A900⠶064700⡮117D00⠿0B7D00⠧280C00⠦280600⠦000600⠢000600⠠002800⠂000000⠀000000⠀000000⠀000000⠀002800⢬000600⢡050600⣹050C00⣈061C00⣉1CD400⣸4CD400⣉16A900⣉7CD400⣉51D400⣉4CD400⣉77D400⣉77D400⣉51D400⣉51D400⣉11D400⣄0B7D00⡀0B1700⣍0B1100⣩0B1100⣉0B1100⣍067D00⠈11A900⢩47A900⣉47A900⣉47A900⣉47A900⣉47A900⣉47A900⣉17A900⣉1CA900⣉1CA900⣉17A900⣉47A900⣉1CA900⣉42A900⣉47A900⣉1CA900⣉28A900⠋061100⡁061100⡁060C00⢀060C00⢁060C00⠁06A900⣀11A900⣜47A900⣉1CA900⣉47A900⣉47A900⣉47A900⣉47D400⣉11D400⣁11A900⢉47A900⣉067D00⣁050B00⣯280600⣋000100⢌000500⠂002800⢀000000⠀000000⠀002800⠐000100⠐000600⡒280600⡶281100⢲067800⢲4DA900⠖11D400⠗0BA900⣾4CA900⠶4CA900⠶7CA900⠖4CA900⠶16A900⠺4CA900⠶21D400⠶1CA900⠶16D400⠶067D00⣆061600⠶061100⠲061100⠒061100⠒061100⠆06A900⠲17A900⠖16A900⠖17A900⠖177D00⠖16A900⠖16A900⠶167D00⠶167D00⠖16A900⠖16A900⠖16A900⠖16A900⠖0B7D00⠶061700⠃281100⠶280C00⠶060B00⠶280C00⠶064200⢀287D00⡶17A900⠲16A900⠲16A900⠖16A900⠶0BA900⠲1CA900⠶17A900⢶1CA900⠶1CD400⠶064C00⡖17D400⠶0BA900⡶290C00⠄280600⡖000600⠆000600⠂002800⠂000000⠀000000⠀002800⡡000600⡡000600⢭060B00⡨064700⢨17D400⣨78D400⡭16A900⢭4DD400⣭4CD400⢭4CD400⠭4CD400⠍17D400⡍16A900⣭16D400⢍47D400⠭4CD400⠍1CD400⣭1CD400⣭16A900⣧06A900⣄061700⢭061100⡭061600⡭061100⣭06D400⠉16A900⢭16A900⣭16A900⣭16D400⣭16A900⣭1BA900⣭17A900⣭16A900⣭16A900⣭16A900⣭0BA900⡍294700⠁061100⡉291100⡡061100⣁061100⢉287D00⣤16A900⣬16A900⣭16A900⣭17D400⠭16D400⠉11D400⠉105200⣭17D400⠉47D400⠩4CD400⢩4DD400⣩0BD400⣏11D700⢹1CD400⣍067800⣅061100⡀280600⡭000600⠌000100⠄002800⠄002800⠐000600⠐000600⡘280B00⣲061600⠐0B7D00⢲47D400⣖16D400⡓10D400⣲4CD400⠒4CD400⠒4CD400⠒17D400⡒16D400⣒16A900⡒16D400⣒17D400⡒1CD400⣒1CD400⠒1CD400⣒1CD400⢒1CD400⢒06D400⣖067200⡒061600⣒061600⣒281600⢒064700⠘0CA900⠒16A900⢒16A900⢒16A900⢒16A900⠒16A900⢒16A900⠒16A900⡒06A900⠒061100⠒061100⠒061100⠒061100⠒287700⣀0BD400⢒16A900⢒16A900⡒16A900⠒16A900⢒16D400⠒11D400⣒11A900⡖117D00⠒11D400⣖16D400⣒4CD400⠒4CD400⠒47D400⣒0B5200⡚21D700⠒0BD400⣖281700⣂280B00⣗000600⢓000100⠐002800⡐002800⠬000600⠌000600⡬280B00⣯061700⠬11D700⢬51D400⠭117D00⠭4CD400⠭4CD400⠭4CD400⠭4CD400⠭4CD400⠭4CD400⠭1CD400⠭51D400⠭4CD400⠭4CD400⠭4CD400⠭21D400⠭1CD400⠭1CD400⡭1CD400⠭6DD400⠥06D400⠤061600⠭061600⠭051100⣭284700⠬06A900⠹0B7D00⠯0B7D00⠭0BA900⠩0B7D00⠽067D00⠽287D00⠉061100⠬051100⠭291100⠭281C00⠩0BA900⠤16A900⠭16A900⠭16A900⢭1BD400⠭1CA900⠭1CD400⠭1CA900⠭1CD400⠭47D400⠭17D400⠬4CD400⠭47D400⠭4CD400⠭4CD400⠭4CD400⠭0BD400⡥11D400⠽1CD400⠭287800⡥051100⡅280600⠥000600⠅000100⠄002800⣚000600⣊280600⣺281100⣒28A900⢐77D400⣒16D400⡗11D400⣲4CD400⣒4CD400⣒4CD400⣒4CD400⣒4CD400⣒4DD400⣒4CD400⣒21D400⣓17D400⣓41D400⣛1CA900⣛1CD400⣚1CD400⣒1CD400⣐11A900⣖11D400⣒11A900⣲0CA900⣒06D400⣀281100⣛061100⣒061100⡂28A900⠙16A900⢒17A900⣒06A900⡛281600⠃051100⣚061100⡒287700⢀06A900⣰11D400⣒0CD400⣐0BA900⣒0C7D00⣢16D400⣀11D400⣀117800⣛11A900⣛11D400⣛11D400⡚17D400⠒4CD400⢒1CD400⣒1CD400⣒4CD400⣒4CD400⣒1CD400⣛47D400⣒0B7800⣚4CD400⣒0BD400⣂061600⡀280600⣓000600⣃000100⠁000600⠠000600⡢280B00⡯061700⠤11D700⢴4CD400⠤117D00⠥4CD400⠤47D400⠬16D400⠭16D400⠿1CD400⠭17D400⠤1CD400⠤11D400⠿1CD400⠭42D400⠤1CD400⠬1CD400⠬16D400⠭11A900⠯41D400⠤1CD400⠭17D400⠤1CD400⠤1CD400⠬17A900⠬06D400⠦281700⡄280C00⠥050C00⠥287700⠈004C00⠏003600⡽060B00⠬280C00⠭287D00⢠11A900⠬16A900⠬16A900⠤16A900⠤16A900⠬11A900⠤11A900⠬0BA900⠯11D400⠭11D400⠤11D400⠤11A900⠭0C7D00⠵0CD400⠤16D400⠤11A900⠭167D00⠽0CA900⠿11D400⠭17D400⠬0BD400⡥0BD400⠽47D400⠤064D00⠄280B00⡯000600⠦000600⠂000600⣈050600⣺281100⣙0BA900⣀4DD700⣛17D700⡋0BA900⣛16D400⣉47D400⣁72D400⣀4DD400⣐1CA900⣓17D400⣁72D400⣒4CD400⣐47D400⣒47D400⣒47D400⣒72D400⣐11A900⣛1CD400⣒47D400⣒1CA900⣀1CD400⣀16A900⣀11A900⣀11A900⣈10A900⣉367D00⣉064C00⣄280B00⣻280B00⣻280B00⣛050B00⣻281700⢠0B7800⣌37A900⣉0BA900⣉11A900⣀11A900⣀16A900⣀16A900⣀16A900⣀17A900⣀11A900⣂0B7D00⣐42A900⣐42D400⣀17A900⣒42D400⣐47D400⣒42D400⣀11A900⣐1CA900⣒47D400⣀1CD400⣀16D400⣈0BD400⣋067800⣙21D400⣛11D400⣃064C00⡀280600⣙000600⡐000600⠰280B00⡺281700⢴0BA900⢾1CA900⠷0B7800⠧1CD400⠾4CA900⠶4CD400⠤11A900⠿16A900⢤17A900⢶4CA900⠶4CA900⠶4CA900⠶4CA900⠶4CA900⠶47A900⠶17A900⠦0BA900⠶1CA900⠶1CA900⠶11A900⠶06A900⠷0B7700⠿114700⢋0B4D00⡥0B7D00⠤287D00⠶281700⠂280B00⢾280B00⠾000B00⠾280B00⠷280B00⡾007800⠲067D00⠦0B7800⡤0B4700⣍0B4C00⠚067800⠾06A900⠶117D00⠶167D00⠦0CA900⠦064700⠴16A900⠶16A900⠶16A900⠶1BA900⠴1CA900⠶1CA900⠦21A900⠶0BD400⠦0B7D00⠿16A900⠶17A900⠶4CD400⠤06A900⡆0BD400⢸16A900⠶064C00⠆280600⡶000600⠢280600⣩061000⢨0B7800⢩4CD400⣉47D400⡉11A900⣹4CD400⣉7CD400⣉17D400⣍16A900⣩4DD400⣉4CD400⣉4CD400⣉4CD400⣉77D400⣉77D400⣉7CD400⣉4CD400⣉1CD400⡍11A900⣉11A900⡉11A900⣥1CA900⣄47A900⣉1CA900⣉42A900⣉1CA900⡉06A900⠋061100⡁060B00⣩280B00⣟00A900⣠0BA900⣤284700⡀060B00⣉060B00⣅285200⠈0B7D00⢫16A900⣉17A900⣉47A900⣉41A900⣀0CA900⣤0B7D00⡍0B7800⣁117D00⡈1CA900⠉1CA900⣍1CA900⣉1CA900⣩47D400⣉4CD400⣉4CD400⣉47D400⣉17D400⣁11A900⡉47D400⢉4CD400⣉1CD400⣍0BA900⢈47D400⣉11A900⣁281100⡇280600⡍280600⠲281100⠶06A900⢸21D400⠲0B7D00⠗11D400⣾21A900⢶47D400⠖107D00⠲17A900⢲4CD400⠒4CA900⢶21D400⡶17D400⠒1CD400⠒47A900⠒16A900⣶11A900⣶1CD400⠖1CA900⠲0CA900⡶067800⣦0B1C00⡛06A900⠓067D00⠶06A900⠖281100⡳060B00⠶280C00⢲281100⢂007800⣴117D00⠒117D00⠒0B7D00⣶004700⣄280B00⢷280C00⢖050B00⣾064D00⠐067D00⠶287D00⠖064D00⠊064700⣀28A400⣴11A900⠖11A900⠶0BA900⣶0B7D00⣶114800⣶115200⡒11A900⠒11A900⠶1CA900⠲1CA900⠒1CA900⠲067D00⡖0B7D00⠺21D400⠖21D400⠲287700⡆0BA900⢲11A900⣶051600⡆280600⠖280B00⢩061100⠨42D400⢭4CD400⠭06A900⣌11FF00⠩21D400⣭1CD400⣭0B7D00⣭16D400⠭17D400⠉11D400⣩16A900⣭4CD400⡭4CD400⢭21D400⣭21D400⢭4CD400⠭21D400⣭17A900⣭16A900⣭1CD400⠭17A900⣭0BA900⡥051100⣭061100⡁061100⡁060C00⡡281100⣭067D00⠈0BA900⠉0BA900⠉0BA900⠉0BA900⠉0BA900⠉281100⣯061100⠁280C00⣭060C00⢁280C00⣭064700⠸16A900⣬11A900⣭16A900⣭16A900⣭1CA900⣭1CA900⣭1CD400⡭1CD400⡭16D400⣭1CA900⣭11A900⣤0B7D00⣭0BA900⡍11D400⠉067D00⠍0BA900⢨1CD400⣭0CD400⡭06D400⢉06A900⣬1CD400⡭067D00⡅000B00⡭000600⠂280B00⡳281100⠺06D400⠛1CD400⢒0BD400⣷0CA900⣌067D00⢛0CA900⣔0B5200⣒41D700⢒4CD400⡒4CD400⡒4CD400⡒1CD400⣒47D400⢒1CD400⣒1CD400⢒1CD400⡒1CD400⡒17A900⣒11D400⡒017D00⠃061100⡒061100⠒061100⠐281100⣒057800⣐11A900⣒11A900⣒117D00⣒117D00⡒16A900⣒11A900⣒16A900⣒16A900⣒06A900⣒287200⡐060C00⡀061100⠂281100⣒281100⣚067D00⠳16A900⣒16A900⡒16A900⡒16A900⣒16A900⣒16A900⣒16A900⣒1CD400⢒1CD400⡒1CD400⣒17A900⡒06A900⠒0BA400⢀0B7D00⡀284D00⢛06A900⣴1CD400⣒11D400⠒065200⠃280600⡒000600⡂000100⠈000600⠌000B00⠭281100⠩067200⠈0BD400⠩42D400⠭17D400⠥06D400⡍0BD400⠧067D00⣭11D400⠩11D400⠽21D400⠭21D400⠭1CD400⠭1CD400⠭17A900⠭1BA900⠭1BA900⠭06A900⠏287200⠩051100⢭060C00⠅050C00⡭281100⢭06A900⣤117D00⠭11A900⠭11A900⠭117D00⠭16A900⠭16A900⠭16A900⠭16A900⠭16A900⠭16A900⠭16A900⠥284C00⡄061100⠨290C00⠈060B00⠭060C00⠄287D00⠉0BA900⠭117D00⠭11A900⠭16A900⠭16A900⠭16A900⠭17A900⠭41A900⠭06A900⠏06A400⠁28D400⡤28A900⠏28A900⡤1CD400⠬11A900⠭28A300⠍004700⠉280600⠍000600⠅000100⠄002800⠈002800⢊000600⡑000600⡑000B00⣚280C00⠙061700⠛06A900⠛16A900⣛0BA900⣓01D400⣘11D400⢲0BD400⣀067D00⣛0BA900⠛17D400⠒1CA900⣚16A900⣚0BA900⡛284C00⠃281100⣚280C00⣛280C00⣚060C00⠐287D00⢀0BA900⣚11A900⣓11A900⣓11A900⣒11A900⣒11A900⣒11A900⣒11A900⣚11A900⣒16A900⣓11A900⣒11A900⣓11A900⣒16A900⣓06A900⣂001100⣓060B00⣓280B00⣻280B00⣟280C00⡂007D00⠛11A900⢒117D00⣒114D00⡛0BA900⠚287700⠛01A900⣀0B7D00⣰28A900⢛067D00⣐117D00⣚0B7D00⡚064C00⠛280B00⣛000B00⢛000600⠘000100⢈000100⠈002800⠐000000⠀000000⠀002800⠠002800⠱000100⠐000600⠌000600⠭280B00⠻287200⠩287D00⠿16D400⠬06A900⣦067D00⡝28D400⠿0CA900⠦06A900⣤286D00⡬287700⠉060B00⠵060B00⠭280B00⡿060B00⠥280B00⠿284C00⣠117D00⠤117D00⠤167D00⠤117D00⠤16A900⠤16A900⠤11A900⠤117D00⠤167D00⠤11A900⠤117D00⠤16A900⠤16A900⠤117D00⠤16A900⠤117D00⠬287D00⢦281100⡄280B00⡯280B00⡯280B00⢯280B00⠯284700⠈284700⠉281600⣠067D00⠤0B7D00⠶007700⠟005200⠡0CA900⠤06A900⠯284700⠏001100⠍000600⠄000600⠅000100⠐002800⠡000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀002800⠁000600⠁000600⠑000600⡉000600⡫280B00⣛067D00⠈06A900⠛16A900⣙067D00⣆28A900⡙00A900⠋061100⠁060C00⠈280B00⣻280B00⣻060B00⣊061100⢀067D00⣀284C00⣉287D00⠉067D00⠙06A900⠛0BA900⠛0C7D00⠛0BA400⠛0C7D00⠛0C7D00⠛117D00⠛0C7D00⠛0B7800⣛0C7D00⠛0B7D00⠛067D00⡛067D00⠛287D00⠛284D00⣉064C00⣁052200⡀050B00⣟280B00⣏050600⣻280B00⣹280B00⣛001C00⠈004D00⠛287D00⣁0C5200⣚104D00⣛295200⠋281700⠃000600⢑000600⠈000100⠐002800⠑000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀002800⠠002800⠂000600⠂000600⠊000600⠲281100⠑281600⠞064C00⠳115200⠶284D00⢦001100⡀280B00⠾280B00⡿280B00⡷281100⢠287800⠶117800⠶117800⠶117800⠶117800⠶107800⠶0B4D00⠶0B7800⠶0B4D00⠶0B4D00⠶067800⠶0B7800⠶0B7800⡶0B7800⠶0B7800⠶0B7800⠶0B4D00⠶0B4D00⠶117800⠶117800⠶114700⠶0C4C00⠶001C00⡆280B00⠷050600⢷280600⠲000600⠗001700⣠004D00⠶064C00⠷281700⠟000B00⠯000600⠒002800⠜000100⠄002800⠂002800⠂000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀002800⠁000100⠈000500⠁000600⢉000B00⢈000C00⠙285200⠉065200⢩165200⣁005200⣤281100⡉285200⢠167D00⣌167D00⣉167D00⣉16A900⣉16A900⣉167D00⣉167D00⣉167D00⣉167D00⣉16A900⣉16A900⣉16A900⣉16A900⣉16A900⣉17A900⣉167D00⣉167D00⣉167D00⣉167D00⣉167D00⣉167D00⣉115200⣍115200⣩112700⣉285200⣄280B00⡝005200⢠067D00⣼167D00⡉065200⠋281C00⠋000600⠈000600⡀000100⠁002800⠁002800⠂000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀002800⠠002800⠐002800⠪000600⠂000600⠳281C00⠘067D00⠲0B7D00⠒004700⣆007D00⠲117D00⠒117D00⠒114D00⠒117D00⠒117D00⠒117D00⠒117D00⠒11A900⠒11A900⠒11A900⠒11A900⠒11A900⠒11A900⠒117D00⠒117D00⠒117D00⠒117D00⠒117D00⠒11A900⠒117D00⠒0C7D00⠒117D00⠒007D00⠖001700⣃002200⣶0C2100⠒281C00⠗001100⠓002800⡚002800⠖000100⠂000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀002800⠈002800⠈000600⠈000600⠈000600⠡001100⠉067D00⠉365200⢭0C5200⣥284D00⣌067D00⠩0B5200⣭0C7D00⢭115200⣭115200⢭105200⣭117D00⠭117D00⠭11A900⢭11A900⠭117D00⠭117D00⠭0C7D00⡭117D00⠭117D00⠭0B7D00⣭117D00⠭117D00⠭117D00⠭067D00⡭007D00⠉284D00⣤0B4D00⣭0B2100⠍001C00⠉000600⠩002800⠩000600⠈002800⠈000000⠀002800⠁000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀002800⠂000100⠂000100⠂000600⠊000B00⡺064C00⠐0B2200⢒065200⣒004D00⣀065200⠒117D00⢒117D00⣒117D00⣒0B5200⣒0C5200⣒0C4D00⣒107D00⣒117D00⣒11A900⣒117D00⣒117D00⠒117D00⣒117D00⣒117D00⣒067D00⡲005200⢃284C00⣰117D00⡒285200⠒001600⠓000600⢂000600⠂000100⠂002800⠐000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀002800⠂000600⠡000600⠠000B00⠈001100⠈064C00⠩0C5200⠭065200⠤004700⡌284200⠩284700⠉284700⠉064700⠉064700⠉064C00⠉067800⠉067700⠉054800⠉282100⠉284700⠉284700⠉284100⠉285200⠤0B5200⠬065200⠍004700⠉000B00⠅000600⠅000100⠄002800⠈002800⠈000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀002800⠐002800⠈002800⠚000100⠂000600⠊284100⠘064700⢛0B1C00⣛052100⣃002200⡐012100⢲0B5200⣒064D00⣒0B5200⣒065200⣒064D00⣒0B4D00⣒0B5200⡒002100⢒285200⣀0B2100⣚0B1C00⡓281700⠛000600⠉000100⠐000500⠂002800⠊000000⠀002800⠂000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀002800⠈002800⠠000100⠈000500⠈000B00⠌001100⠹0B1700⠽061C00⠿281C00⠦004200⡙002100⠿062200⠦0B5200⠶115200⠤004C00⠟001C00⠡281C00⠾061C00⠿004100⠯000B00⠍002900⡵000100⠄000100⠂000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀002800⠂000100⠂000600⠁001100⠈281100⠻0B1C00⠙101C00⣛001C00⣆281C00⡙284D00⠛005200⣡064D00⣜0B2200⣁291700⡛004700⠁000600⠁000100⢀002800⠐000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀002800⠠002800⠐002800⠔000B00⠺284100⠰291C00⠶061C00⠶001700⠶0B1C00⠶281600⠟001100⠋000600⠁002800⠰000000⠀002800⠄000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀002800⠈002800⠈002800⡉000600⠁001600⠉001700⠉291C00⠝001600⠋000600⠉000600⠁002800⢉002800⠈000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀000000⠀]]) + +local width, height, scale, scaleMod = image.getWidth(source), image.getHeight(source), 1, 0.1 +local targetMinimalScale, scaledWidth = 2 / width, width +local bufferWidth, bufferHeight = buffer.getResolution() + +while true do + local transformed = image.transform(source, math.ceil(width * scale), height) + buffer.clear(0x0) + buffer.image(math.floor(bufferWidth / 2 - image.getWidth(transformed) / 2), math.floor(bufferHeight / 2 - image.getHeight(transformed) / 2), transformed) + buffer.draw() + + scale = scale - scaleMod + if scale < targetMinimalScale then + scale, scaleMod = targetMinimalScale, -scaleMod + elseif scale > 1.0 then + scale, scaleMod = 1.0, -scaleMod + end + + local eventType = event.pull(0) + if eventType == "touch" or eventType == "key_down" then + break + end +end diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Trash/Config.cfg b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Trash/Config.cfg new file mode 100644 index 00000000..a1f22da7 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Trash/Config.cfg @@ -0,0 +1 @@ +{["toLanguage"]="Украинский",["fromLanguage"]="Грузинский",["APIKey"]="",["languages"]={[30]={[1]="is",[2]="Исландский"},[1]={[1]="az",[2]="Азербайджанский"},[7]={[1]="af",[2]="Африкаанс"},[31]={[1]="es",[2]="Испанский"},[32]={[1]="it",[2]="Итальянский"},[33]={[1]="kk",[2]="Казахский"},[34]={[1]="kn",[2]="Каннада"},[8]={[1]="eu",[2]="Баскский"},[35]={[1]="ca",[2]="Каталанский"},[36]={[1]="ky",[2]="Киргизский"},[37]={[1]="zh",[2]="Китайский"},[38]={[1]="ko",[2]="Корейский"},[9]={[1]="ba",[2]="Башкирский"},[39]={[1]="xh",[2]="Коса"},[40]={[1]="km",[2]="Кхмерский"},[41]={[1]="lo",[2]="Лаосский"},[42]={[1]="la",[2]="Латынь"},[10]={[1]="be",[2]="Белорусский"},[43]={[1]="lv",[2]="Латышский"},[44]={[1]="lt",[2]="Литовский"},[45]={[1]="lb",[2]="Люксембургский"},[2]={[1]="sq",[2]="Албанский"},[11]={[1]="bn",[2]="Бенгальский"},[47]={[1]="mg",[2]="Малагасийский"},[48]={[1]="ms",[2]="Малайский"},[49]={[1]="ml",[2]="Малаялам"},[50]={[1]="mt",[2]="Мальтийский"},[12]={[1]="my",[2]="Бирманский"},[51]={[1]="mi",[2]="Маори"},[52]={[1]="mr",[2]="Маратхи"},[53]={[1]="mhr",[2]="Марийский"},[54]={[1]="mn",[2]="Монгольский"},[13]={[1]="bg",[2]="Болгарский"},[55]={[1]="de",[2]="Немецкий"},[56]={[1]="ne",[2]="Непальский"},[57]={[1]="no",[2]="Норвежский"},[58]={[1]="pa",[2]="Панджаби"},[14]={[1]="bs",[2]="Боснийский"},[59]={[1]="pap",[2]="Папьяменто"},[60]={[1]="fa",[2]="Персидский"},[61]={[1]="pl",[2]="Польский"},[3]={[1]="am",[2]="Амхарский"},[15]={[1]="cy",[2]="Валлийский"},[63]={[1]="ro",[2]="Румынский"},[64]={[1]="ru",[2]="Русский"},[65]={[1]="ceb",[2]="Себуанский"},[16]={[1]="hu",[2]="Венгерский"},[67]={[1]="si",[2]="Сингальский"},[68]={[1]="sk",[2]="Словацкий"},[69]={[1]="sl",[2]="Словенский"},[17]={[1]="vi",[2]="Вьетнамский"},[71]={[1]="su",[2]="Сунданский"},[72]={[1]="tl",[2]="Тагальский"},[73]={[1]="tg",[2]="Таджикский"},[18]={[1]="ht",[2]="Гаитянский"},[75]={[1]="ta",[2]="Тамильский"},[76]={[1]="tt",[2]="Татарский"},[4]={[1]="en",[2]="Английский"},[19]={[1]="gl",[2]="Галисийский"},[79]={[1]="udm",[2]="Удмуртский"},[80]={[1]="uz",[2]="Узбекский"},[81]={[1]="uk",[2]="Украинский"},[20]={[1]="nl",[2]="Голландский"},[83]={[1]="fi",[2]="Финский"},[84]={[1]="fr",[2]="Французский"},[85]={[1]="hi",[2]="Хинди"},[21]={[1]="mrj",[2]="Горномарийский"},[87]={[1]="cs",[2]="Чешский"},[88]={[1]="sv",[2]="Шведский"},[89]={[1]="gd",[2]="Шотландский (гэльский)"},[22]={[1]="el",[2]="Греческий"},[91]={[1]="et",[2]="Эстонский"},[92]={[1]="jv",[2]="Яванский"},[5]={[1]="ar",[2]="Арабский"},[23]={[1]="ka",[2]="Грузинский"},[24]={[1]="gu",[2]="Гуджарати"},[25]={[1]="da",[2]="Датский"},[46]={[1]="mk",[2]="Македонский"},[26]={[1]="he",[2]="Иврит"},[62]={[1]="pt",[2]="Португальский"},[86]={[1]="hr",[2]="Хорватский"},[6]={[1]="hy",[2]="Армянский"},[27]={[1]="yi",[2]="Идиш"},[66]={[1]="sr",[2]="Сербский"},[70]={[1]="sw",[2]="Суахили"},[74]={[1]="th",[2]="Тайский"},[28]={[1]="id",[2]="Индонезийский"},[77]={[1]="te",[2]="Телугу"},[78]={[1]="tr",[2]="Турецкий"},[82]={[1]="ur",[2]="Урду"},[29]={[1]="ga",[2]="Ирландский"},[90]={[1]="eo",[2]="Эсперанто"},[93]={[1]="ja",[2]="Японский"}}} \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Trash/HEX-copy.app/Main.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Trash/HEX-copy.app/Main.lua new file mode 100644 index 00000000..f9020c55 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Trash/HEX-copy.app/Main.lua @@ -0,0 +1,380 @@ + +require("advancedLua") +local bit32 = require("bit32") +local fs = require("filesystem") +local GUI = require("GUI") +local buffer = require("doubleBuffering") +local unicode = require("unicode") +local MineOSInterface = require("MineOSInterface") + +------------------------------------------------------------------------------------------------------------------ + +local colors = { + background = 0xF0F0F0, + backgroundText = 0x555555, + panel = 0x2D2D2D, + panelText = 0x999999, + panelSeleciton = 0x444444, + panelSelecitonText = 0xE1E1E1, + selectionFrom = 0x990000, + selectionTo = 0x990000, + selectionText = 0xFFFFFF, + selectionBetween = 0xD2D2D2, + selectionBetweenText = 0x000000, + separator = 0xCCCCCC, + titleBackground = 0x990000, + titleText = 0xFFFFFF, + titleText2 = 0xE1E1E1, +} + +local bytes = {} +local offset = 0 +local selection = { + from = 1, + to = 1, +} + +local scrollBar, titleTextBox + +------------------------------------------------------------------------------------------------------------------ + +local mainContainer, window = MineOSInterface.addWindow(MineOSInterface.filledWindow(1, 1, 98, 25, colors.background)) + +window.backgroundPanel.localX, window.backgroundPanel.localY = 11, 5 +window.backgroundPanel.width, window.backgroundPanel.height = window.width - 10, window.height - 4 + +local function status() + titleTextBox.lines[1] = "Selected byte" .. (selection.from == selection.to and "" or "s") .. ": " .. selection.from .. "-" .. selection.to + titleTextBox.lines[2].text = "UTF-8: \"" .. string.char(table.unpack(bytes, selection.from, selection.to)) .. "\"" + titleTextBox.lines[3].text = "INT: " .. bit32.byteArrayToNumber({table.unpack(bytes, selection.from, selection.to)}) +end + +local function byteFieldDraw(object) + local x, y, index = object.x, object.y, 1 + offset + local xCount, yCount = math.ceil(object.width / object.elementWidth), math.ceil(object.height / object.elementHeight) + + for j = 1, yCount do + for i = 1, xCount do + if bytes[index] then + local textColor = colors.backgroundText + if index == selection.from or index == selection.to then + buffer.square(x - object.offset, y, object.elementWidth, 1, index == selection.from and colors.selectionFrom or colors.selectionTo, colors.selectionText, " ") + textColor = colors.selectionText + elseif index > selection.from and index < selection.to then + buffer.square(x - object.offset, y, object.elementWidth, 1, colors.selectionBetween, colors.selectionText, " ") + textColor = colors.selectionBetweenText + end + + buffer.text(x, y, textColor, object.asChar and string.char(bytes[index]) or string.format("%02X", bytes[index])) + else + return object + end + + x, index = x + object.elementWidth, index + 1 + end + + local lastLineIndex = index - 1 + if lastLineIndex >= selection.from and lastLineIndex < selection.to then + buffer.square(object.x - object.offset, y + 1, object.width, 1, colors.selectionBetween, colors.selectionText, " ") + end + + x, y = object.x, y + object.elementHeight + end + + return object +end + +local function byteFieldEventHandler(mainContainer, object, eventData) + if eventData[1] == "touch" or eventData[1] == "drag" then + if eventData[5] == 1 then + local menu = GUI.contextMenu(eventData[3], eventData[4]) + + menu:addItem("Select all").onTouch = function() + selection.from = 1 + selection.to = #bytes + + mainContainer:draw() + buffer.draw() + end + menu:addSeparator() + menu:addItem("Edit").onTouch = function() + local container = MineOSInterface.addUniversalContainer(mainContainer, "Fill byte range [" .. selection.from .. "; " .. selection.to .. "]") + + local input = container.layout:addChild(GUI.input(1, 1, 36, 3, 0xE1E1E1, 0x666666, 0x666666, 0xE1E1E1, 0x2D2D2D, string.format("%02X" , bytes[selection.from]), "Type byte value")) + input.onInputFinished = function(text) + local number = tonumber("0x" .. input.text) + if number and number >= 0 and number <= 255 then + for i = selection.from, selection.to do + bytes[i] = number + end + + container:delete() + mainContainer:draw() + buffer.draw() + end + end + + mainContainer:draw() + buffer.draw() + end + menu:addItem("Insert").onTouch = function() + local container = MineOSInterface.addUniversalContainer(mainContainer, "Insert bytes at position " .. selection.from .. "") + + local input = container.layout:addChild(GUI.input(1, 1, 36, 3, 0xE1E1E1, 0x666666, 0x666666, 0xE1E1E1, 0x2D2D2D, "", "Type byte values separated by space", true)) + local switch = container.layout:addChild(GUI.switchAndLabel(1, 1, 36, 8, 0x66DB80, 0x1E1E1E, 0xE1E1E1, 0xBBBBBB, "Select inserted bytes:", true)).switch + + input.onInputFinished = function() + if input.text:match("[a-fA-F%d%s]+") then + local insertionPosition, count = selection.from, 0 + for word in input.text:gmatch("[^%s]+") do + local number = tonumber("0x" .. word) + if number > 255 then number = 255 end + table.insert(bytes, insertionPosition + count, number) + selection.from, selection.to, count = selection.from + 1, selection.to + 1, count + 1 + end + + if switch.state then + selection.from, selection.to = insertionPosition, insertionPosition + count - 1 + end + + container:delete() + mainContainer:draw() + buffer.draw() + end + end + + mainContainer:draw() + buffer.draw() + end + menu:addSeparator() + menu:addItem("Delete").onTouch = function() + for i = selection.from, selection.to do + table.remove(bytes, selection.from) + end + if #bytes == 0 then + selection.from, selection.to = 1, 1 + else + selection.to = selection.from + end + end + menu:show() + else + local index = (math.ceil((eventData[4] - object.y + 1) / 2) - 1) * 16 + math.ceil((eventData[3] - object.x + 1 + object.offset) / object.elementWidth) + offset + + if bytes[index] then + if eventData[1] == "touch" then + selection.to = index + selection.from = index + selection.touchIndex = index + else + if not selection.touchIndex then selection.touchIndex = index end + + if index < selection.touchIndex then + selection.from = index + selection.to = selection.touchIndex + elseif index > selection.touchIndex then + selection.to = index + selection.from = selection.touchIndex + end + end + + status() + mainContainer:draw() + buffer.draw() + end + end + elseif eventData[1] == "scroll" then + offset = offset - 16 * eventData[5] + if offset < 0 then + offset = 0 + elseif offset > math.floor(#bytes / 16) * 16 then + offset = math.floor(#bytes / 16) * 16 + end + scrollBar.value = offset + + mainContainer:draw() + buffer.draw() + end +end + +local function newByteField(x, y, width, height, elementWidth, elementHeight, asChar) + local object = GUI.object(x, y, width, height) + + object.elementWidth = elementWidth + object.elementHeight = elementHeight + object.offset = asChar and 0 or 1 + object.asChar = asChar + object.draw = byteFieldDraw + object.eventHandler = byteFieldEventHandler + + return object +end + +------------------------------------------------------------------------------------------------------------------ + +window:addChild(GUI.panel(1, 1, window.width, 3, 0x3C3C3C)):moveToBack() + +local byteField = window:addChild(newByteField(13, 6, 64, 20, 4, 2, false)) +local charField = window:addChild(newByteField(byteField.localX + byteField.width + 3, 6, 16, 20, 1, 2, true)) +local separator = window:addChild(GUI.object(byteField.localX + byteField.width, 5, 1, 21)) +separator.draw = function(object) + for i = object.y, object.y + object.height - 1 do + buffer.text(object.x, i, colors.separator, "│") + end +end + + +window:addChild(GUI.panel(11, 4, window.width - 10, 1, colors.panel)) + +-- Vertical +local verticalCounter = window:addChild(GUI.object(1, 4, 10, window.height - 3)) +verticalCounter.draw = function(object) + buffer.square(object.x, object.y, object.width, object.height, colors.panel, colors.panelText, " ") + + local index = offset + for y = 2, object.height - 1, 2 do + local textColor = colors.panelText + + if index > selection.from and index < selection.to then + buffer.square(object.x, object.y + y - 1, object.width, 2, colors.panelSeleciton, colors.panelSelecitonText, " ") + textColor = colors.panelSelecitonText + end + + if selection.from >= index and selection.from <= index + 15 or selection.to >= index and selection.to <= index + 15 then + buffer.square(object.x, object.y + y, object.width, 1, colors.selectionFrom, colors.selectionText, " ") + textColor = colors.selectionText + end + + buffer.text(object.x + 1, object.y + y, textColor, string.format("%08X", index)) + + index = index + 16 + end +end + +-- Horizontal +window:addChild(GUI.object(13, 4, 62, 1)).draw = function(object) + local counter = 0 + local restFrom, restTo = selection.from % 16, selection.to % 16 + for x = 1, object.width, 4 do + local textColor = colors.panelText + if counter + 1 > restFrom and counter + 1 < restTo then + buffer.square(object.x + x - 2, object.y, 4, 1, colors.panelSeleciton, colors.selectionText, " ") + textColor = colors.panelSelecitonText + elseif restFrom == counter + 1 or restTo == counter + 1 then + buffer.square(object.x + x - 2, object.y, 4, 1, colors.selectionFrom, colors.selectionText, " ") + textColor = colors.selectionText + end + + buffer.text(object.x + x - 1, object.y, textColor, string.format("%02X", counter)) + counter = counter + 1 + end +end + +scrollBar = window:addChild(GUI.scrollBar(window.width, 5, 1, window.height - 4, 0xC3C3C3, 0x393939, 0, 1, 1, 160, 1, true)) +scrollBar.eventHandler = nil + +titleTextBox = window:addChild( + GUI.textBox( + 1, 1, math.floor(window.width * 0.35), 3, + colors.titleBackground, + colors.titleText, + { + "", + {text = "", color = colors.titleText2}, + {text = "", color = colors.titleText2} + }, + 1, 1, 0 + ) +) +titleTextBox.localX = math.floor(window.width / 2 - titleTextBox.width / 2) +titleTextBox:setAlignment(GUI.alignment.horizontal.center, GUI.alignment.vertical.top) +titleTextBox.eventHandler = nil + +local saveFileButton = window:addChild(GUI.adaptiveRoundedButton(titleTextBox.localX - 11, 2, 2, 0, colors.panel, colors.panelSelecitonText, colors.panelSelecitonText, colors.panel, "Save")) +local openFileButton = window:addChild(GUI.adaptiveRoundedButton(saveFileButton.localX - 11, 2, 2, 0, colors.panel, colors.panelSelecitonText, colors.panelSelecitonText, colors.panel, "Open")) + +------------------------------------------------------------------------------------------------------------------ + +local function load(path) + local file, reason = io.open(path, "rb") + + if file then + bytes = {} + local char + while true do + local char = file:read(1) + if char then + table.insert(bytes, string.byte(char)) + else + break + end + end + + file:close() + offset = 0 + selection.from, selection.to = 1, 1 + scrollBar.value, scrollBar.maximumValue = 0, #bytes + status() + else + GUI.error("Failed to open file for reading: " .. tostring(reason)) + end +end + +openFileButton.onTouch = function() + local filesystemDialog = GUI.addFilesystemDialogToContainer(mainContainer, "Open", "Cancel", "File name", "/") + filesystemDialog:setMode(GUI.filesystemModes.open, GUI.filesystemModes.file) + filesystemDialog:show() + filesystemDialog.onSubmit = function(path) + load(path) + mainContainer:draw() + buffer.draw() + end +end + +saveFileButton.onTouch = function() + local filesystemDialog = GUI.addFilesystemDialogToContainer(mainContainer, "Save", "Cancel", "File name", "/") + filesystemDialog:setMode(GUI.filesystemModes.save, GUI.filesystemModes.file) + filesystemDialog:show() + filesystemDialog.onSubmit = function(path) + local file = io.open(path, "wb") + if file then + for i = 1, #bytes do + file:write(string.char(bytes[i])) + end + file:close() + else + GUI.error("Failed to open file for writing: " .. tostring(reason)) + end + end +end + +window.actionButtons.localY = 2 +window.actionButtons.maximize.onTouch = function() + window.height = window.parent.height + byteField.height = window.height - 6 + charField.height = byteField.height + scrollBar.height = byteField.height + window.backgroundPanel.height = window.height - 4 + verticalCounter.height = window.backgroundPanel.height + 1 + separator.height = byteField.height + 2 + + window.localY = 1 + + mainContainer:draw() + buffer.draw() +end + +------------------------------------------------------------------------------------------------------------------ + +load("/bin/resolution.lua") +mainContainer:draw() +buffer.draw() + + + + + + + + + diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Trash/HEX-copy.app/Resources/Icon.pic b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Trash/HEX-copy.app/Resources/Icon.pic new file mode 100644 index 0000000000000000000000000000000000000000..46c7ddbef98a8243608dcb845032a0469145fd8d GIT binary patch literal 167 zcmXYpF%Cj83`1)>O*{NUFtdR`%<#Xy#KgjZ5QpFt+~@C{gtTe~=f$!7nD47sLrT*L z8@>?i+`&+84M-HB%)`{$fs|c334RzqKMt@R_edh54u){!5OY-%YjW*T!hfGvgrA7n Q7%Nj=>KmZtrH2Il0l_>O4gdfE literal 0 HcmV?d00001 diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Trash/HEX.app/Main.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Trash/HEX.app/Main.lua new file mode 100644 index 00000000..f9020c55 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Trash/HEX.app/Main.lua @@ -0,0 +1,380 @@ + +require("advancedLua") +local bit32 = require("bit32") +local fs = require("filesystem") +local GUI = require("GUI") +local buffer = require("doubleBuffering") +local unicode = require("unicode") +local MineOSInterface = require("MineOSInterface") + +------------------------------------------------------------------------------------------------------------------ + +local colors = { + background = 0xF0F0F0, + backgroundText = 0x555555, + panel = 0x2D2D2D, + panelText = 0x999999, + panelSeleciton = 0x444444, + panelSelecitonText = 0xE1E1E1, + selectionFrom = 0x990000, + selectionTo = 0x990000, + selectionText = 0xFFFFFF, + selectionBetween = 0xD2D2D2, + selectionBetweenText = 0x000000, + separator = 0xCCCCCC, + titleBackground = 0x990000, + titleText = 0xFFFFFF, + titleText2 = 0xE1E1E1, +} + +local bytes = {} +local offset = 0 +local selection = { + from = 1, + to = 1, +} + +local scrollBar, titleTextBox + +------------------------------------------------------------------------------------------------------------------ + +local mainContainer, window = MineOSInterface.addWindow(MineOSInterface.filledWindow(1, 1, 98, 25, colors.background)) + +window.backgroundPanel.localX, window.backgroundPanel.localY = 11, 5 +window.backgroundPanel.width, window.backgroundPanel.height = window.width - 10, window.height - 4 + +local function status() + titleTextBox.lines[1] = "Selected byte" .. (selection.from == selection.to and "" or "s") .. ": " .. selection.from .. "-" .. selection.to + titleTextBox.lines[2].text = "UTF-8: \"" .. string.char(table.unpack(bytes, selection.from, selection.to)) .. "\"" + titleTextBox.lines[3].text = "INT: " .. bit32.byteArrayToNumber({table.unpack(bytes, selection.from, selection.to)}) +end + +local function byteFieldDraw(object) + local x, y, index = object.x, object.y, 1 + offset + local xCount, yCount = math.ceil(object.width / object.elementWidth), math.ceil(object.height / object.elementHeight) + + for j = 1, yCount do + for i = 1, xCount do + if bytes[index] then + local textColor = colors.backgroundText + if index == selection.from or index == selection.to then + buffer.square(x - object.offset, y, object.elementWidth, 1, index == selection.from and colors.selectionFrom or colors.selectionTo, colors.selectionText, " ") + textColor = colors.selectionText + elseif index > selection.from and index < selection.to then + buffer.square(x - object.offset, y, object.elementWidth, 1, colors.selectionBetween, colors.selectionText, " ") + textColor = colors.selectionBetweenText + end + + buffer.text(x, y, textColor, object.asChar and string.char(bytes[index]) or string.format("%02X", bytes[index])) + else + return object + end + + x, index = x + object.elementWidth, index + 1 + end + + local lastLineIndex = index - 1 + if lastLineIndex >= selection.from and lastLineIndex < selection.to then + buffer.square(object.x - object.offset, y + 1, object.width, 1, colors.selectionBetween, colors.selectionText, " ") + end + + x, y = object.x, y + object.elementHeight + end + + return object +end + +local function byteFieldEventHandler(mainContainer, object, eventData) + if eventData[1] == "touch" or eventData[1] == "drag" then + if eventData[5] == 1 then + local menu = GUI.contextMenu(eventData[3], eventData[4]) + + menu:addItem("Select all").onTouch = function() + selection.from = 1 + selection.to = #bytes + + mainContainer:draw() + buffer.draw() + end + menu:addSeparator() + menu:addItem("Edit").onTouch = function() + local container = MineOSInterface.addUniversalContainer(mainContainer, "Fill byte range [" .. selection.from .. "; " .. selection.to .. "]") + + local input = container.layout:addChild(GUI.input(1, 1, 36, 3, 0xE1E1E1, 0x666666, 0x666666, 0xE1E1E1, 0x2D2D2D, string.format("%02X" , bytes[selection.from]), "Type byte value")) + input.onInputFinished = function(text) + local number = tonumber("0x" .. input.text) + if number and number >= 0 and number <= 255 then + for i = selection.from, selection.to do + bytes[i] = number + end + + container:delete() + mainContainer:draw() + buffer.draw() + end + end + + mainContainer:draw() + buffer.draw() + end + menu:addItem("Insert").onTouch = function() + local container = MineOSInterface.addUniversalContainer(mainContainer, "Insert bytes at position " .. selection.from .. "") + + local input = container.layout:addChild(GUI.input(1, 1, 36, 3, 0xE1E1E1, 0x666666, 0x666666, 0xE1E1E1, 0x2D2D2D, "", "Type byte values separated by space", true)) + local switch = container.layout:addChild(GUI.switchAndLabel(1, 1, 36, 8, 0x66DB80, 0x1E1E1E, 0xE1E1E1, 0xBBBBBB, "Select inserted bytes:", true)).switch + + input.onInputFinished = function() + if input.text:match("[a-fA-F%d%s]+") then + local insertionPosition, count = selection.from, 0 + for word in input.text:gmatch("[^%s]+") do + local number = tonumber("0x" .. word) + if number > 255 then number = 255 end + table.insert(bytes, insertionPosition + count, number) + selection.from, selection.to, count = selection.from + 1, selection.to + 1, count + 1 + end + + if switch.state then + selection.from, selection.to = insertionPosition, insertionPosition + count - 1 + end + + container:delete() + mainContainer:draw() + buffer.draw() + end + end + + mainContainer:draw() + buffer.draw() + end + menu:addSeparator() + menu:addItem("Delete").onTouch = function() + for i = selection.from, selection.to do + table.remove(bytes, selection.from) + end + if #bytes == 0 then + selection.from, selection.to = 1, 1 + else + selection.to = selection.from + end + end + menu:show() + else + local index = (math.ceil((eventData[4] - object.y + 1) / 2) - 1) * 16 + math.ceil((eventData[3] - object.x + 1 + object.offset) / object.elementWidth) + offset + + if bytes[index] then + if eventData[1] == "touch" then + selection.to = index + selection.from = index + selection.touchIndex = index + else + if not selection.touchIndex then selection.touchIndex = index end + + if index < selection.touchIndex then + selection.from = index + selection.to = selection.touchIndex + elseif index > selection.touchIndex then + selection.to = index + selection.from = selection.touchIndex + end + end + + status() + mainContainer:draw() + buffer.draw() + end + end + elseif eventData[1] == "scroll" then + offset = offset - 16 * eventData[5] + if offset < 0 then + offset = 0 + elseif offset > math.floor(#bytes / 16) * 16 then + offset = math.floor(#bytes / 16) * 16 + end + scrollBar.value = offset + + mainContainer:draw() + buffer.draw() + end +end + +local function newByteField(x, y, width, height, elementWidth, elementHeight, asChar) + local object = GUI.object(x, y, width, height) + + object.elementWidth = elementWidth + object.elementHeight = elementHeight + object.offset = asChar and 0 or 1 + object.asChar = asChar + object.draw = byteFieldDraw + object.eventHandler = byteFieldEventHandler + + return object +end + +------------------------------------------------------------------------------------------------------------------ + +window:addChild(GUI.panel(1, 1, window.width, 3, 0x3C3C3C)):moveToBack() + +local byteField = window:addChild(newByteField(13, 6, 64, 20, 4, 2, false)) +local charField = window:addChild(newByteField(byteField.localX + byteField.width + 3, 6, 16, 20, 1, 2, true)) +local separator = window:addChild(GUI.object(byteField.localX + byteField.width, 5, 1, 21)) +separator.draw = function(object) + for i = object.y, object.y + object.height - 1 do + buffer.text(object.x, i, colors.separator, "│") + end +end + + +window:addChild(GUI.panel(11, 4, window.width - 10, 1, colors.panel)) + +-- Vertical +local verticalCounter = window:addChild(GUI.object(1, 4, 10, window.height - 3)) +verticalCounter.draw = function(object) + buffer.square(object.x, object.y, object.width, object.height, colors.panel, colors.panelText, " ") + + local index = offset + for y = 2, object.height - 1, 2 do + local textColor = colors.panelText + + if index > selection.from and index < selection.to then + buffer.square(object.x, object.y + y - 1, object.width, 2, colors.panelSeleciton, colors.panelSelecitonText, " ") + textColor = colors.panelSelecitonText + end + + if selection.from >= index and selection.from <= index + 15 or selection.to >= index and selection.to <= index + 15 then + buffer.square(object.x, object.y + y, object.width, 1, colors.selectionFrom, colors.selectionText, " ") + textColor = colors.selectionText + end + + buffer.text(object.x + 1, object.y + y, textColor, string.format("%08X", index)) + + index = index + 16 + end +end + +-- Horizontal +window:addChild(GUI.object(13, 4, 62, 1)).draw = function(object) + local counter = 0 + local restFrom, restTo = selection.from % 16, selection.to % 16 + for x = 1, object.width, 4 do + local textColor = colors.panelText + if counter + 1 > restFrom and counter + 1 < restTo then + buffer.square(object.x + x - 2, object.y, 4, 1, colors.panelSeleciton, colors.selectionText, " ") + textColor = colors.panelSelecitonText + elseif restFrom == counter + 1 or restTo == counter + 1 then + buffer.square(object.x + x - 2, object.y, 4, 1, colors.selectionFrom, colors.selectionText, " ") + textColor = colors.selectionText + end + + buffer.text(object.x + x - 1, object.y, textColor, string.format("%02X", counter)) + counter = counter + 1 + end +end + +scrollBar = window:addChild(GUI.scrollBar(window.width, 5, 1, window.height - 4, 0xC3C3C3, 0x393939, 0, 1, 1, 160, 1, true)) +scrollBar.eventHandler = nil + +titleTextBox = window:addChild( + GUI.textBox( + 1, 1, math.floor(window.width * 0.35), 3, + colors.titleBackground, + colors.titleText, + { + "", + {text = "", color = colors.titleText2}, + {text = "", color = colors.titleText2} + }, + 1, 1, 0 + ) +) +titleTextBox.localX = math.floor(window.width / 2 - titleTextBox.width / 2) +titleTextBox:setAlignment(GUI.alignment.horizontal.center, GUI.alignment.vertical.top) +titleTextBox.eventHandler = nil + +local saveFileButton = window:addChild(GUI.adaptiveRoundedButton(titleTextBox.localX - 11, 2, 2, 0, colors.panel, colors.panelSelecitonText, colors.panelSelecitonText, colors.panel, "Save")) +local openFileButton = window:addChild(GUI.adaptiveRoundedButton(saveFileButton.localX - 11, 2, 2, 0, colors.panel, colors.panelSelecitonText, colors.panelSelecitonText, colors.panel, "Open")) + +------------------------------------------------------------------------------------------------------------------ + +local function load(path) + local file, reason = io.open(path, "rb") + + if file then + bytes = {} + local char + while true do + local char = file:read(1) + if char then + table.insert(bytes, string.byte(char)) + else + break + end + end + + file:close() + offset = 0 + selection.from, selection.to = 1, 1 + scrollBar.value, scrollBar.maximumValue = 0, #bytes + status() + else + GUI.error("Failed to open file for reading: " .. tostring(reason)) + end +end + +openFileButton.onTouch = function() + local filesystemDialog = GUI.addFilesystemDialogToContainer(mainContainer, "Open", "Cancel", "File name", "/") + filesystemDialog:setMode(GUI.filesystemModes.open, GUI.filesystemModes.file) + filesystemDialog:show() + filesystemDialog.onSubmit = function(path) + load(path) + mainContainer:draw() + buffer.draw() + end +end + +saveFileButton.onTouch = function() + local filesystemDialog = GUI.addFilesystemDialogToContainer(mainContainer, "Save", "Cancel", "File name", "/") + filesystemDialog:setMode(GUI.filesystemModes.save, GUI.filesystemModes.file) + filesystemDialog:show() + filesystemDialog.onSubmit = function(path) + local file = io.open(path, "wb") + if file then + for i = 1, #bytes do + file:write(string.char(bytes[i])) + end + file:close() + else + GUI.error("Failed to open file for writing: " .. tostring(reason)) + end + end +end + +window.actionButtons.localY = 2 +window.actionButtons.maximize.onTouch = function() + window.height = window.parent.height + byteField.height = window.height - 6 + charField.height = byteField.height + scrollBar.height = byteField.height + window.backgroundPanel.height = window.height - 4 + verticalCounter.height = window.backgroundPanel.height + 1 + separator.height = byteField.height + 2 + + window.localY = 1 + + mainContainer:draw() + buffer.draw() +end + +------------------------------------------------------------------------------------------------------------------ + +load("/bin/resolution.lua") +mainContainer:draw() +buffer.draw() + + + + + + + + + diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Trash/HEX.app/Resources/Icon.pic b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Trash/HEX.app/Resources/Icon.pic new file mode 100644 index 0000000000000000000000000000000000000000..46c7ddbef98a8243608dcb845032a0469145fd8d GIT binary patch literal 167 zcmXYpF%Cj83`1)>O*{NUFtdR`%<#Xy#KgjZ5QpFt+~@C{gtTe~=f$!7nD47sLrT*L z8@>?i+`&+84M-HB%)`{$fs|c334RzqKMt@R_edh54u){!5OY-%YjW*T!hfGvgrA7n Q7%Nj=>KmZtrH2Il0l_>O4gdfE literal 0 HcmV?d00001 diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Trash/Icon.pic b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Trash/Icon.pic new file mode 100755 index 0000000000000000000000000000000000000000..1e0deecfa632d0f9ab14ada7d678dd78b68790d6 GIT binary patch literal 117 zcmXYp%L#xm5JWS(o6VMCFM{C7(mY5I1UpDO>OGC= R1X1 and R2Y2 >= R1Y1 +end + +local config={ + FPS=0, + FPSc=0, + ostime=os.time(), + work=true, + systemtick=false, + PathToRes=fs.path(getCurrentScript()) +} + +local function getTankIntersection(tank) + for i = 1, #tanksContainer.children do + local child = tanksContainer.children[i] + if child ~= tank then + if getRectangleIntersection( + tank.x-1, + tank.y-1, + tank.x + tank.width+1, + tank.y + tank.height+1, + child.x, + child.y, + child.x + child.width, + child.y + child.height + ) + then + return false + end + end + end + return true +end + +local function getBulletIntersection(bullet) -------ПЕРЕПОСССАТЬ + for i = 1, #tanksContainer.children do + local child = tanksContainer.children[i] + + if getRectangleIntersection( + bullet.x, + bullet.y, + bullet.x + bullet.width-1, + bullet.y + bullet.height-1, + child.x, + child.y, + child.x + child.width-1, + child.y + child.height-1 + ) and bullet.type ~= child.type then + tanksContainer.children[i]:delete() + if child.type=="friend" then + Player1Lifes=Player1Lifes-1 + Player1Dead=true + else + EnemyCount=EnemyCount-1 + end + return true + end + + end + for i = 1, #BulletContainer.children do + local child = BulletContainer.children[i] + + if getRectangleIntersection( + bullet.x, + bullet.y, + bullet.x + bullet.width-1, + bullet.y + bullet.height-1, + child.x, + child.y, + child.x + child.width-1, + child.y + child.height-1 + ) and bullet.type ~= child.type + then + BulletContainer.children[i]:delete() + --EnemyCount=EnemyCount-1 + return true + end + + end + return false +end + +local function newBullet(x, y, type, MoveSide) + local Pulya = GUI.object(x, y, 1, 1) + + Pulya.MoveSide = MoveSide + Pulya.type = type + Pulya.speed = 3 + -- Pulya.FriendModel = image.load(config.PathToRes.."Resources/Tanks/Bullet.pic") + + Pulya.draw = function() + --if Debug then buffer.text(Pulya.x,Pulya.y-1,0xFFFFFF,"Х="..Pulya.x.." У="..Pulya.y) end + -- buffer.image(Pulya.x, Pulya.y, Pulya.FriendModel) + buffer.text(Pulya.x, Pulya.y, 0xFF0000, "▄") + end + + Pulya.eventHandler = function(mainContainer,object,eventData) + + if Pulya.MoveSide == "UP" then + Pulya.localY = Pulya.localY - Pulya.speed + elseif Pulya.MoveSide == "DOWN" then + Pulya.localY = Pulya.localY + Pulya.speed + elseif Pulya.MoveSide == "RIGHT" then + Pulya.localX = Pulya.localX + Pulya.speed + elseif Pulya.MoveSide == "LEFT" then + Pulya.localX = Pulya.localX - Pulya.speed + end + + if getBulletIntersection(Pulya) then + Pulya:delete() + end + + if not (Pulya.x >= 1 and Pulya.x <= DisplayWidth and Pulya.y >= 1 and Pulya.y <= DisplayHeight) then + Pulya:delete() + end + end + + return Pulya +end + + +local function Player1(x,y) + + local MyFuckingTank = GUI.object(x,y,8,4) + + MyFuckingTank.Speed = 1 + MyFuckingTank.MoveSide = "UP" + MyFuckingTank.type = "friend" + + + MyFuckingTank.ModelMoveUp = image.load(config.PathToRes.."Resources/Textures/Player1Tank/Player1NewTankUP.pic") + MyFuckingTank.ModelMoveDown = image.load(config.PathToRes.."Resources/Textures/Player1Tank/Player1NewTankDOWN.pic") + MyFuckingTank.ModelMoveLeft = image.load(config.PathToRes.."Resources/Textures/Player1Tank/Player1NewTankLEFT.pic") + MyFuckingTank.ModelMoveRight = image.load(config.PathToRes.."Resources/Textures/Player1Tank/Player1NewTankRIGHT.pic") + + MyFuckingTank.Model = MyFuckingTank.ModelMoveUp + + + MyFuckingTank.draw = function(MyFuckingTank) + if MyFuckingTank.MoveSide == "UP" then + MyFuckingTank.Model = MyFuckingTank.ModelMoveUp + elseif MyFuckingTank.MoveSide == "DOWN" then + MyFuckingTank.Model = MyFuckingTank.ModelMoveDown + elseif MyFuckingTank.MoveSide == "RIGHT" then + MyFuckingTank.Model = MyFuckingTank.ModelMoveRight + elseif MyFuckingTank.MoveSide == "LEFT" then + MyFuckingTank.Model = MyFuckingTank.ModelMoveLeft + end + + buffer.frame(MyFuckingTank.x-1, MyFuckingTank.y-1 , MyFuckingTank.width + 2, MyFuckingTank.height + 2, 0xFFFFFF) + buffer.image(MyFuckingTank.x, MyFuckingTank.y, MyFuckingTank.Model) + + if Debug then buffer.text(1,2,0xFFFFFF,"Танк Х="..MyFuckingTank.x.." Танк У="..MyFuckingTank.y) end + buffer.text(1,3,0xFFFFFF,"EnemyCount="..EnemyCount.." MaxEnemyOnMap="..MaxEnemyOnMap) + end + + return MyFuckingTank +end + + +local function Enemy() + local x,y = 0,0 + local SpawnPoint = math.random(3) + local Huy={} + --Процедура умного спавна + if SpawnPoint==1 then + x=1 + y=1 + elseif SpawnPoint==2 then + x=DisplayWidth/2 + y=1 + elseif SpawnPoint==3 then + x=DisplayWidth - 7 + y=1 + end + + + local FuckingEnemy = GUI.object(x,y,8,4) + + + if getTankIntersection(FuckingEnemy) then + + FuckingEnemy.Speed = 1 + FuckingEnemy.MoveSide = "DOWN" + FuckingEnemy.MaxBullet = 2 + + FuckingEnemy.type = "enemy" + + FuckingEnemy.ModelMoveUp = image.load(config.PathToRes.."Resources/Textures/EnemyTank/EnemyTankUP.pic") + FuckingEnemy.ModelMoveDown = image.load(config.PathToRes.."Resources/Textures/EnemyTank/EnemyTankDOWN.pic") + FuckingEnemy.ModelMoveLeft = image.load(config.PathToRes.."Resources/Textures/EnemyTank/EnemyTankLEFT.pic") + FuckingEnemy.ModelMoveRight = image.load(config.PathToRes.."Resources/Textures/EnemyTank/EnemyTankRIGHT.pic") + + FuckingEnemy.Model = FuckingEnemy.ModelMoveUp + + local function ChangeMoveSide() + local ChangeSide = math.random(3) + local Sides={ + "UP", + "DOWN", + "LEFT", + "RIGHT" + } + + for i=1,#Sides do + if Sides[i] == FuckingEnemy.MoveSide then table.remove(Sides,i) break end + end + + FuckingEnemy.MoveSide = Sides[ChangeSide] + + end + + + FuckingEnemy.eventHandler = function(mainContainer,object,eventData) ---Мозги + if math.random(100) <= 5 then + BulletContainer:addChild(newBullet(FuckingEnemy.x + 3, FuckingEnemy.y + 1, "enemy",FuckingEnemy.MoveSide)) + end + + if math.random(100) <= 4 then + ChangeMoveSide() + end + end + + FuckingEnemy.draw = function() + + if FuckingEnemy.MoveSide == "UP" then + if FuckingEnemy.localY > 1 then + if getTankIntersection(FuckingEnemy) then + FuckingEnemy.localY = FuckingEnemy.localY - FuckingEnemy.Speed + else + FuckingEnemy.localY = FuckingEnemy.localY + FuckingEnemy.Speed + ChangeMoveSide() + end + else + ChangeMoveSide() + end + + FuckingEnemy.Model = FuckingEnemy.ModelMoveUp + + elseif FuckingEnemy.MoveSide == "DOWN" then + if FuckingEnemy.localY < DisplayHeight-3 then + if getTankIntersection(FuckingEnemy) then + FuckingEnemy.localY = FuckingEnemy.localY + FuckingEnemy.Speed + else + FuckingEnemy.localY = FuckingEnemy.localY - FuckingEnemy.Speed + ChangeMoveSide() + end + else + ChangeMoveSide() + end + + FuckingEnemy.Model = FuckingEnemy.ModelMoveDown + + elseif FuckingEnemy.MoveSide == "RIGHT" then + if FuckingEnemy.localX < DisplayWidth-8 then + if getTankIntersection(FuckingEnemy) then + FuckingEnemy.localX = FuckingEnemy.localX + FuckingEnemy.Speed + else + FuckingEnemy.localX = FuckingEnemy.localX - FuckingEnemy.Speed + ChangeMoveSide() + end + else + ChangeMoveSide() + end + + FuckingEnemy.Model = FuckingEnemy.ModelMoveRight + + elseif FuckingEnemy.MoveSide == "LEFT" then + + if FuckingEnemy.localX > 1 then + if getTankIntersection(FuckingEnemy) then + FuckingEnemy.localX = FuckingEnemy.localX - FuckingEnemy.Speed + else + FuckingEnemy.localX = FuckingEnemy.localX + FuckingEnemy.Speed + ChangeMoveSide() + end + else + ChangeMoveSide() + end + + FuckingEnemy.Model = FuckingEnemy.ModelMoveLeft + end + + buffer.image(FuckingEnemy.x, FuckingEnemy.y, FuckingEnemy.Model) + + end + + return FuckingEnemy + else + EnemyCount=EnemyCount-1 + return false + end + +end + + local function KeyPress(keycode,lable) --для дэбага, наверное + lable.text="Код клавиши="..keycode + if keycode == 113 or keycode == 1081 then --если нажали Q(81) или q(113) или й 1081 - выйти нахуй + config.work = false + end +end + +--Типа код---------------------------------------------------------------------------------------- + +local MyFTN = tanksContainer:addChild(Player1(50,20)) +mainContainer.eventHandler = function(mainContainer, object, eventData) + +if Player1Dead and Player1Lifes >= 1 then + Player1Dead=false + Player1Lifes=Player1Lifes-1 + MyFTN = tanksContainer:addChild(Player1(50,20)) +elseif Player1Lifes<=0 then + --GUI.error("Ты проебал!") + --mainContainer:stopEventHandling(0) +end + + +if EnemyCount < MaxEnemyOnMap then +EnemyCount=EnemyCount+1 + local Obj = Enemy() + if Obj==false then + + else + tanksContainer:addChild(Obj) + + end +end + local EvD=eventData + if EvD[1] == "key_down" then + if EvD[4] == 200 or EvD[4] == 17 then + PlayerMoveSide = "UP" + elseif EvD[4] == 208 or EvD[4] == 31 then + PlayerMoveSide = "DOWN" + elseif EvD[4] == 203 or EvD[4] == 30 then + PlayerMoveSide = "LEFT" + elseif EvD[4] == 205 or EvD[4] == 32 then + PlayerMoveSide = "RIGHT" + elseif EvD[4] == 57 then + if Player1Dead~=true then BulletContainer:addChild(newBullet(MyFTN.x + math.random(2)+2, MyFTN.y + 1, "friend",MyFTN.MoveSide)) end + elseif EvD[4] == 19 then + local Obj = Enemy() + if Obj==false then + else + tanksContainer:addChild(Obj) + EnemyCount=EnemyCount+1 + end + end + elseif EvD[1] == "key_up" then + PlayerMoveSide="none" + end + + if PlayerMoveSide == "UP" then + MyFTN.MoveSide = "UP" + if MyFTN.y > 1 then MyFTN.localY = MyFTN.y - MyFTN.Speed end + + elseif PlayerMoveSide == "DOWN" then + MyFTN.MoveSide = "DOWN" + if MyFTN.y < DisplayHeight-3 then MyFTN.localY = MyFTN.y + MyFTN.Speed end + + elseif PlayerMoveSide == "LEFT" then + MyFTN.MoveSide = "LEFT" + if MyFTN.x > 1 then MyFTN.localX = MyFTN.x - MyFTN.Speed*2 end + + elseif PlayerMoveSide == "RIGHT" then + MyFTN.MoveSide = "RIGHT" + if MyFTN.x < DisplayWidth-8 then MyFTN.localX = MyFTN.x + MyFTN.Speed*2 end + end + --KeyPress(EvD[4],cyka) + + buffer.clear(0x0) + mainContainer:draw() + buffer.draw() +end + +mainContainer:draw() +buffer.draw(true) +mainContainer:startEventHandling(0) + + + + diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Trash/LehaTanks.app/Resources/Icon.pic b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Trash/LehaTanks.app/Resources/Icon.pic new file mode 100755 index 0000000000000000000000000000000000000000..b9bdf68c275f36d3b077538986211cb859e2da0c GIT binary patch literal 76 zcmXxau?>JQ5JS$A$rm6F05bOkQvd(} literal 0 HcmV?d00001 diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Trash/LehaTanks.app/Resources/Tanks/Enemy1Down.pic b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Trash/LehaTanks.app/Resources/Tanks/Enemy1Down.pic new file mode 100755 index 0000000000000000000000000000000000000000..a4e7a4abb06cfb66627111a0a18005cdd298fa29 GIT binary patch literal 175 zcmXYpI|>3p5JacC=L24AK``PxWQMj&2L(m(>K&)UzBUyk zWj`ay5CEPGEfB4Z%M}S W#j3Phu)f~Im$^QIE$o+2i$H!6Q7~cv literal 0 HcmV?d00001 diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Trash/LehaTanks.app/Resources/Tanks/Enemy1Left.pic b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Trash/LehaTanks.app/Resources/Tanks/Enemy1Left.pic new file mode 100755 index 0000000000000000000000000000000000000000..401ba60176775290bbae12adf07d030de8ba8f12 GIT binary patch literal 159 zcmXZUu?>Sj6a&z+KX6jkiWG^8DY8dOq-2QWQYMIw4zY_jpi7w#T=k#yZFkr9)R?#f`GKVka~;iBB*jtxMiJRLOWdp$_!@bQTaM6EOyHp#v_`{Wh|^Do21; VdIKzvZk1(j_%GYmhVZ**M;xM1Eb9OO literal 0 HcmV?d00001 diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Trash/LehaTanks.app/Resources/Tanks/Enemy1Up.pic b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Trash/LehaTanks.app/Resources/Tanks/Enemy1Up.pic new file mode 100755 index 0000000000000000000000000000000000000000..5d989fa4c8e9e589b8d283a2bdd5cfaf55a27528 GIT binary patch literal 175 zcmXZUOA5kJ429vGtaW6A92@Fkby`BbVY z#JIj7_yRDtF*0$vK{NpwnoHeOQ4Lk(z&^O%U{JOhS5Jle9;>mx|EpiP0he(%hRk*k W?)lic`jd4EUJQl(89cFl!q^W)6EE@r literal 0 HcmV?d00001 diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Trash/LehaTanks.app/Resources/Tanks/Play1DOWN.pic b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Trash/LehaTanks.app/Resources/Tanks/Play1DOWN.pic new file mode 100755 index 0000000000000000000000000000000000000000..a4e7a4abb06cfb66627111a0a18005cdd298fa29 GIT binary patch literal 175 zcmXYpI|>3p5JacC=L24AK``PxWQMj&2L(m(>K&)UzBUyk zWj`ay5CEPGEfB4Z%M}S W#j3Phu)f~Im$^QIE$o+2i$H!6Q7~cv literal 0 HcmV?d00001 diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Trash/LehaTanks.app/Resources/Tanks/Play1LEFT.pic b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Trash/LehaTanks.app/Resources/Tanks/Play1LEFT.pic new file mode 100755 index 0000000000000000000000000000000000000000..401ba60176775290bbae12adf07d030de8ba8f12 GIT binary patch literal 159 zcmXZUu?>Sj6a&z+KX6jkiWG^8DY8dOq-2QWQYMIw4zY_jpi7w#T=k#yZFkr9)R?#f`GKVka~;iBB*jtxMiJRLOWdp$_!@bQTaM6EOyHp#v_`{Wh|^Do21; VdIKzvZk1(j_%GYmhVZ**M;xM1Eb9OO literal 0 HcmV?d00001 diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Trash/LehaTanks.app/Resources/Tanks/Play1UP.pic b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Trash/LehaTanks.app/Resources/Tanks/Play1UP.pic new file mode 100755 index 0000000000000000000000000000000000000000..5d989fa4c8e9e589b8d283a2bdd5cfaf55a27528 GIT binary patch literal 175 zcmXZUOA5kJ429vGtaW6A92@Fkby`BbVY z#JIj7_yRDtF*0$vK{NpwnoHeOQ4Lk(z&^O%U{JOhS5Jle9;>mx|EpiP0he(%hRk*k W?)lic`jd4EUJQl(89cFl!q^W)6EE@r literal 0 HcmV?d00001 diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Trash/LehaTanks.app/Resources/Textures/EnemyTank/EnemyTankDOWN.pic b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Trash/LehaTanks.app/Resources/Textures/EnemyTank/EnemyTankDOWN.pic new file mode 100755 index 0000000000000000000000000000000000000000..f50fdefbec9dab15275360b412b2628cfb3d594e GIT binary patch literal 240 zcmXYrF%H5o3`O(pG|j@PkdT--Meo7JB|vHtF#%#jEQo7#_9?o-Pud}o{r&&558M4N zL@z(*EuG*nz@xMA$kM|Hh=x8|dBMpHc7EdD12WfQv!yB z)b!-2_Pv$TTuN8It*YAiSyi!eZmNRnw_&tmv>vx$l$g8(gnP#aU6a-uKpSShD2R$^ J>E2*K{|EfHKCb`( literal 0 HcmV?d00001 diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Trash/LehaTanks.app/Resources/Textures/EnemyTank/EnemyTankLEFT.pic b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Trash/LehaTanks.app/Resources/Textures/EnemyTank/EnemyTankLEFT.pic new file mode 100755 index 0000000000000000000000000000000000000000..410d3b9f937d9a08b1b3d9899043dbf2bd01dbe0 GIT binary patch literal 220 zcmXYrF%AMT3+ve)-cVNHs85_(~h*BS2ksfT@!4WjA^v9w-7XP_6IsgCw literal 0 HcmV?d00001 diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Trash/LehaTanks.app/Resources/Textures/EnemyTank/EnemyTankRIGHT.pic b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Trash/LehaTanks.app/Resources/Textures/EnemyTank/EnemyTankRIGHT.pic new file mode 100755 index 0000000000000000000000000000000000000000..0fc54cbb3299bce7634bf33cba627c29b6a7440d GIT binary patch literal 220 zcmXYrF%H5o3`O&86FP7!BqSzI(R(m~I`sk|CC$i=*tkY#pQ0PsNw)W$|DSR>o=vq@Gix)4xlakJ+ua zo0v>~!My(>$P5#;iEk6V-y^_)2+`Ok9}fI?02&|P3t9*(&mWFwD8$D{MS*&Lp`%bh zpRkiJTun^1p7Vmy$5Tb)bhy^8QIl^Kl^WhvG+H!TH0N57qIuSourS=0Y4m(71r;z} L&Yy@$kqqe%J}o{> literal 0 HcmV?d00001 diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Trash/LehaTanks.app/Resources/Textures/Map/Base.pic b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Trash/LehaTanks.app/Resources/Textures/Map/Base.pic new file mode 100755 index 0000000000000000000000000000000000000000..3d1cca7596c9070ee01ae9ac27d3f5f7fbd84be0 GIT binary patch literal 94 zcmXAgK@NaG2m>k5UE~%1#l*yu7ythiUGUO$GVOg_=SZpuc03Sbf@8*(s-Pv*{AX7t VM{~Zl7Kp=^*I3aXkMdR7`2qR31Umo# literal 0 HcmV?d00001 diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Trash/LehaTanks.app/Resources/Textures/Map/Brick.pic b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Trash/LehaTanks.app/Resources/Textures/Map/Brick.pic new file mode 100755 index 0000000000000000000000000000000000000000..ab6a244cb57087e70caaff1b41ed7888e4cf0bd6 GIT binary patch literal 87 zcmYky!3}^w2t-i^7#66)UQA3pZAtcIg}Rdyo7 literal 0 HcmV?d00001 diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Trash/LehaTanks.app/Resources/Textures/Player1Tank/Player1NewTankDOWN.pic b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Trash/LehaTanks.app/Resources/Textures/Player1Tank/Player1NewTankDOWN.pic new file mode 100755 index 0000000000000000000000000000000000000000..c8c161fe26531e0ab807f203b9991b4c28382d64 GIT binary patch literal 240 zcmXYrF%H5o3`O(pG|j@PkdT--Meo7JB|vHtF#%#jEQq`K2Hm>BPud}o{r&&558M4N zL@z(*EuG*nz@xMA$kM|Hh=x8|dBMpHc7EdD12WfQv!yB z)b!-2_Pv$TTuN8It*YAiSyi!eZmNRnw_&tmv>vx$l$g8(gnP#aU6a-uKpSShD2R$^ J>E2*K{|8<1Kac+ve)-cVNHs85_(~h*BS2ksfT@!4WjA^v9w-7XSCQI>-P3 literal 0 HcmV?d00001 diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Trash/LehaTanks.app/Resources/Textures/Player1Tank/Player1NewTankRIGHT.pic b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Trash/LehaTanks.app/Resources/Textures/Player1Tank/Player1NewTankRIGHT.pic new file mode 100755 index 0000000000000000000000000000000000000000..8eea1f0d853feacb9ab00f8df4a059e34e48a7ec GIT binary patch literal 220 zcmXYrF%H5o3`O&86FP7!BqSzI(R(m~I`sk|CC$i=*tm;t(5)NTNw)W$|DSR>o=8@lTHWw4*)uy-wh>%mFAC)7cfZYhhYJMHNP;J zkYGNtPCnL%J-RmRHRsliI^8OYYtD5+(EDAkVb`#0E;S;H^0OAm3e&w|)O@nS!US9Q L^NV3(BtiNEhgv@! literal 0 HcmV?d00001 diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Trash/Sudoku.app/.icons b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Trash/Sudoku.app/.icons new file mode 100644 index 00000000..037b37a6 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Trash/Sudoku.app/.icons @@ -0,0 +1 @@ +{[".icons"]={["x"]=8,["y"]=11},["Main.lua"]={["x"]=17,["y"]=2},["Resources"]={["x"]=3,["y"]=2}} \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Trash/Sudoku.app/Main.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Trash/Sudoku.app/Main.lua new file mode 100644 index 00000000..621bb86e --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Trash/Sudoku.app/Main.lua @@ -0,0 +1,147 @@ + +local component = require("component") +local buffer = require("doubleBuffering") +local event = require("event") +local color = require("color") +local unicode = require("unicode") +local GUI = require("GUI") +local MineOSCore = require("MineOSCore") + +-------------------------------------------------------------------------------------------- + +local cells = {} +for y = 1, 9 do + cells[y] = {} + for x = 1, 9 do + cells[y][x] = {value = nil, variants = {}} + end +end + +-------------------------------------------------------------------------------------------- + +local function getCellVariants(xCell, yCell) + for i = 1, 9 do + cells[yCell][xCell].variants[i] = true + end + if cells[yCell][xCell].value then + cells[yCell][xCell].variants[cells[yCell][xCell].value] = false + end + + for y = 1, 9 do + if y ~= yCell and cells[y][xCell].value then + cells[yCell][xCell].variants[cells[y][xCell].value] = false + end + end + + for x = 1, 9 do + if x ~= xCell and cells[yCell][x].value then + cells[yCell][xCell].variants[cells[yCell][x].value] = false + end + end + + local xCellGroup, yCellGroup = math.ceil(xCell / 3) * 3, math.ceil(yCell / 3) * 3 + for y = yCellGroup - 2, yCellGroup do + for x = xCellGroup - 2, xCellGroup do + if x ~= xCell and y ~= yCell and cells[y][x].value then + cells[yCell][xCell].variants[cells[y][x].value] = false + end + end + end +end + +local function getAllVariants() + for y = 1, 9 do + for x = 1, 9 do + getCellVariants(x, y) + end + end +end + +local function generate(count) + for i = 1, count do + local indexedVariants, xCell, yCell = {} + repeat + xCell, yCell = math.random(1, 9), math.random(1, 9) + for key, value in pairs(cells[yCell][xCell].variants) do + if value == true then + table.insert(indexedVariants, key) + end + end + until cells[yCell][xCell].value == nil and #indexedVariants > 1 + + + cells[yCell][xCell].value = indexedVariants[math.random(1, #indexedVariants)] + getAllVariants() + end +end + +-------------------------------------------------------------------------------------------- + +local mainContainer, window = MineOSCore.addWindow(GUI.filledWindow(1, 1, 71, 36, 0xEEEEEE)) + +local sudoku = window:addChild(GUI.object(1, 2, 71, 36)) +sudoku.colors = { + lines = { + thin = 0xAAAAAA, + fat = 0x000000 + } +} +sudoku.draw = function(sudoku) + local x, y = sudoku.x + 7, sudoku.y + 3 + + for i = 1, 8 do + buffer.text(sudoku.x, y, i % 3 == 0 and sudoku.colors.lines.fat or sudoku.colors.lines.thin, string.rep("─", sudoku.width)) + y = y + 4 + end + + for i = 1, 8 do + for j = sudoku.y, sudoku.y + sudoku.height - 1 do + local background, foreground, symbol = buffer.get(x, j) + if symbol == "─" then + symbol = "┼" + else + symbol = "│" + end + + buffer.set(x, j, background, i % 3 == 0 and sudoku.colors.lines.fat or sudoku.colors.lines.thin, symbol) + end + + x = x + 8 + end + + x, y = sudoku.x, sudoku.y + for yCell = 1, 9 do + for xCell = 1, 9 do + local xCyka, yCyka = x, y + for key, value in pairs(cells[yCell][xCell].variants) do + if value then + buffer.text(xCyka, yCyka, 0xBBBBBB, tostring(key)) + end + + xCyka = xCyka + 2 + if xCyka - x > 5 then + xCyka, yCyka = x, yCyka + 1 + end + end + + if cells[yCell][xCell].value then + buffer.text(x + 3, y + 1, 0x880000, tostring(cells[yCell][xCell].value)) + end + + x = x + 8 + end + + x, y = sudoku.x, y + 4 + end +end + +-- sudoku.eventHandler = function(mainContainer, object, eventData) +-- if eventData[1] == "touch" then +-- GUI.error(eventData) +-- end +-- end + +-------------------------------------------------------------------------------------------- + +getAllVariants() +generate(50) \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Trash/Sudoku.app/Resources/.icons b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Trash/Sudoku.app/Resources/.icons new file mode 100644 index 00000000..118a3645 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Trash/Sudoku.app/Resources/.icons @@ -0,0 +1 @@ +{["Icon.pic"]={["x"]=3,["y"]=2}} \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Trash/Sudoku.app/Resources/Icon.pic b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MineOS/Trash/Sudoku.app/Resources/Icon.pic new file mode 100644 index 0000000000000000000000000000000000000000..06a0bcaa7b1d2103e69b3ea3f1f696f51d8b8bad GIT binary patch literal 161 zcmXYqyA6Oa3`BFb 8 then + if data:sub(1, 4) == "OCIF" then + if string.byte(data:sub(6, 6)) > 8 or string.byte(data:sub(7, 7)) > 4 then + handle:close() + return false, "Image size is larger than 8x4" + end + else + handle:close() + return false, "Wrong image file signature" + end + end + else + handle:close() + if reason then + return false, reason + else + return data + end + end + end + else + handle:close() + return false, "Specified image size is too big" + end + else + return false, "Invalid URL" + end +end + +-------------------------------------------------------------------------------- + +local mainContainer, window = MineOSInterface.addWindow(MineOSInterface.tabbedWindow(1, 1, 110, 30)) +local overrideWindowDraw = window.draw +window.draw = function(...) + overrideWindowDraw(...) + buffer.text(window.x, window.y + window.height, 0xFF0000, "Free RAM: " .. math.floor(computer.freeMemory() / 1024)) +end + +local contentContainer = window:addChild(GUI.container(1, 4, 1, 1)) +local statusWidget = window:addChild(GUI.object(1, 1, 1, 1)) +statusWidget.draw = function() + buffer.square(statusWidget.x, statusWidget.y, statusWidget.width, 1, 0x2D2D2D, 0xF0F0F0, " ") + buffer.text(statusWidget.x + 1, statusWidget.y, 0xF0F0F0, statusWidget.text) +end + +-------------------------------------------------------------------------------- + +local function status(text) + statusWidget.text = text + MineOSInterface.OSDraw() +end + +local function getAllDependencies(mainFileID, mainFileDependencies) + local allDependencies = {} + + local function getAllDependenciesRecursively(file_ids) + local list, reason = fieldAPIRequest("list", "list", { + file_ids = file_ids, + fields = { + "dependencies", + "publication_name", + "path", + "source_url" + } + }) + + if list then + local newDependenciesList = {} + + for i = 1, #list do + if not allDependencies[list[i].file_id] and list[i].file_id ~= mainFileID then + allDependencies[list[i].file_id] = list[i] + end + + if list[i].dependencies then + for j = 1, #list[i].dependencies do + if not allDependencies[list[i].dependencies[j]] and list[i].dependencies[j] ~= mainFileID then + table.insert(newDependenciesList, list[i].dependencies[j]) + end + end + end + end + + if #newDependenciesList > 0 then + getAllDependenciesRecursively(newDependenciesList) + end + else + GUI.error(reason) + end + end + + getAllDependenciesRecursively(mainFileDependencies) + + local result = {} + for key, value in pairs(allDependencies) do + table.insert(result, value) + allDependencies[key] = nil + end + + if #result > 0 then + return result + end +end + +local function ratingWidgetDraw(object) + local x = 0 + for i = 1, 5 do + buffer.text(object.x + x, object.y, object.rating >= i and object.colors.first or object.colors.second, "*") + x = x + object.spacing + end + + return object +end + +local function newRatingWidget(x, y, rating, firstColor, secondColor) + local object = GUI.object(x, y, 9, 1) + + object.colors = { + first = firstColor or 0xFFB600, + second = secondColor or 0xC3C3C3 + } + object.spacing = 2 + object.draw = ratingWidgetDraw + object.rating = rating + + return object +end + +-------------------------------------------------------------------------------- + +local function getApplicationIcon(category_id, dependencies) + if dependencies then + for i = 1, #dependencies do + if dependencies[i].path == "Resources/Icon.pic" then + local path = iconCachePath .. dependencies[i].file_id .. ".pic" + + if fs.exists(path) then + return image.load(path) + else + local data, reason = checkImage(dependencies[i].source_url) + if data then + local file = io.open(path, "w") + file:write(data) + file:close() + + return image.load(path) + else + GUI.error("Failed to download publication icon: " .. reason) + break + end + end + end + end + end + + if category_id == 2 then + return luaIcon + else + return scriptIcon + end +end + +local function addPanel(container, color) + container.panel = container:addChild(GUI.panel(1, 1, container.width, container.height, color or 0xFFFFFF)) +end + +local function addShit(container, application) + addPanel(container) + container.image = container:addChild(GUI.image(3, 2, application.icon)) + container.nameLabel = container:addChild(GUI.text(13, 2, 0x0, application.publication_name)) + container.versionLabel = container:addChild(GUI.text(13, 3, 0x666666, "©" .. application.user_name)) + container.rating = container:addChild(newRatingWidget(13, 4, application.average_rating or 0)) + container.downloadButton = container:addChild(GUI.adaptiveRoundedButton(13, 5, 1, 0, 0xBBBBBB, 0xFFFFFF, 0x888888, 0xFFFFFF, "Загрузить")) +end + +local function keyValueWidgetUpdate(object) + object.width = unicode.len(object.key .. object.value) +end + +local function keyValueWidgetDraw(object) + keyValueWidgetUpdate(object) + buffer.text(object.x, object.y, object.colors.key, object.key) + buffer.text(object.x + unicode.len(object.key), object.y, object.colors.value, object.value) +end + +local function newKeyValueWidget(x, y, keyColor, valueColor, key, value) + local object = GUI.object(x, y, 1, 1) + + object.colors = { + key = keyColor, + value = valueColor + } + object.key = key + object.value = value + + object.draw = keyValueWidgetDraw + keyValueWidgetUpdate(object) + + return object +end + +local function containerScrollEventHandler(mainContainer, object, eventData) + if eventData[1] == "scroll" then + local first, last = object.children[1], object.children[#object.children] + + if eventData[5] == 1 then + if first.localY < 2 then + for i = 1, #object.children do + object.children[i].localY = object.children[i].localY + 1 + end + MineOSInterface.OSDraw() + end + else + if last.localY + last.height - 1 >= object.height then + for i = 1, #object.children do + object.children[i].localY = object.children[i].localY - 1 + end + MineOSInterface.OSDraw() + end + end + end +end + +local function newApplicationInfoWidget(file_id) + status("Получение информации о приложении...") + + local info, reason = fieldAPIRequest("list", "list", { + file_ids = {file_id}, + fields = { + "publication_id", + "publication_name", + "average_rating", + "version", + "reviews", + "description", + "category_id", + "dependencies", + "user_name", + "license", + "timestamp", + }, + description_language = config.descriptionLanguage, + }) + + if info then + local application = info[1] + + if application.dependencies then + status("Построение древа зависимостей...") + application.dependencies = getAllDependencies(file_id, application.dependencies) + end + + contentContainer:deleteChildren() + + local infoContainer = contentContainer:addChild(GUI.container(1, 1, contentContainer.width, contentContainer.height)) + infoContainer.eventHandler = containerScrollEventHandler + + -- Жирный йоба-лейаут для отображения ВАЩЕ всего - и инфы, и отзыввов + local layout = infoContainer:addChild(GUI.layout(3, 2, infoContainer.width - 4, infoContainer.height, 1, 1)) + layout:setCellAlignment(1, 1, GUI.alignment.horizontal.center, GUI.alignment.vertical.top) + + -- А вот эт уже контейнер чисто инфы крч + local detailsContainer = layout:addChild(GUI.container(3, 2, layout.width, 6)) + + -- Тут будут находиться ваще пизда подробности о публикации + local ratingsContainer = detailsContainer:addChild(GUI.container(1, 1, 26, 6)) + ratingsContainer.localX = detailsContainer.width - ratingsContainer.width + 1 + addPanel(ratingsContainer, 0xE1E1E1) + + -- Всякая текстовая пизда + local y = 2 + ratingsContainer:addChild(newKeyValueWidget(2, y, 0x2D2D2D, 0x888888, "Разработчик", ": " .. application.user_name)); y = y + 1 + ratingsContainer:addChild(newKeyValueWidget(2, y, 0x2D2D2D, 0x888888, "Лицензия", ": " .. application.license)); y = y + 1 + ratingsContainer:addChild(newKeyValueWidget(2, y, 0x2D2D2D, 0x888888, "Категория", ": " .. categories[application.category_id])); y = y + 1 + ratingsContainer:addChild(newKeyValueWidget(2, y, 0x2D2D2D, 0x888888, "Версия", ": " .. string.format("%.2f", application.version))); y = y + 1 + ratingsContainer:addChild(newKeyValueWidget(2, y, 0x2D2D2D, 0x888888, "Обновлено", ": " .. os.date("%d.%m.%Y", application.timestamp))); y = y + 1 + y = y + 1 + + -- Добавляем инфу с общими рейтингами + if application.reviews then + status("Формирование структуры отзывов...") + + local ratings = {0, 0, 0, 0, 0} + for i = 1, #application.reviews do + ratings[application.reviews[i].rating] = ratings[application.reviews[i].rating] + 1 + end + + ratingsContainer:addChild(newKeyValueWidget(2, y, 0x2D2D2D, 0x888888, "Средний рейтинг", ": " .. string.format("%.1f", application.average_rating or 0))); y = y + 1 + + for i = #ratings, 1, -1 do + local text = tostring(ratings[i]) + local textLength = #text + ratingsContainer:addChild(newRatingWidget(2, y, i, nil, 0xC3C3C3)) + ratingsContainer:addChild(GUI.progressBar(12, y, ratingsContainer.width - textLength - 13, 0x2D2D2D, 0xC3C3C3, 0xC3C3C3, ratings[i] / #application.reviews * 100, true)) + ratingsContainer:addChild(GUI.text(ratingsContainer.width - textLength, y, 0x2D2D2D, text)) + y = y + 1 + end + end + + -- Добавляем описание и прочую пизду + local textDetailsContainer = detailsContainer:addChild(GUI.container(1, 1, detailsContainer.width - ratingsContainer.width, detailsContainer.height)) + application.icon = getApplicationIcon(application.category_id, application.dependencies) + addShit(textDetailsContainer, application) + + local lines = string.wrap(info[1].description, textDetailsContainer.width - 4) + local textBox = textDetailsContainer:addChild(GUI.textBox(3, 7, textDetailsContainer.width - 4, #lines, nil, 0x888888, lines, 1, 0, 0)) + textBox.eventHandler = nil + + if application.dependencies then + local dependencyWithPublicationNameExists = false + for i = 1, #application.dependencies do + if application.dependencies[i].publication_name then + dependencyWithPublicationNameExists = true + break + end + end + + if dependencyWithPublicationNameExists then + local x, y = 3, textBox.localY + textBox.height + 1 + + textDetailsContainer:addChild(GUI.label(1, y, textDetailsContainer.width, 1, 0x666666, "Зависимости")):setAlignment(GUI.alignment.horizontal.center, GUI.alignment.vertical.top) + y = y + 2 + + for i = 1, #application.dependencies do + local text = application.dependencies[i].publication_name or fs.name(application.dependencies[i].path) + if application.dependencies[i].publication_name then + local textLength = unicode.len(text) + if x + textLength + 4 > textDetailsContainer.width - 4 then + x, y = 3, y + 2 + end + local button = textDetailsContainer:addChild(GUI.roundedButton(x, y, textLength + 2, 1, 0xBBBBBB, 0xFFFFFF, 0x2D2D2D, 0xFFFFFF, text)) + button.onTouch = function() + newApplicationInfoWidget(application.dependencies[i].file_id) + end + x = x + button.width + 2 + end + end + end + end + + + textDetailsContainer.height = math.max( + textDetailsContainer.children[#textDetailsContainer.children].localY + textDetailsContainer.children[#textDetailsContainer.children].height, + ratingsContainer.children[#ratingsContainer.children].localY + ratingsContainer.children[#ratingsContainer.children].height + ) + textDetailsContainer.panel.height = textDetailsContainer.height + + ratingsContainer.height = textDetailsContainer.height + ratingsContainer.panel.height = textDetailsContainer.height + + detailsContainer.height = textDetailsContainer.height + + if config.token then + layout:addChild(GUI.adaptiveRoundedButton(1, 1, 2, 0, 0x666666, 0xFFFFFF, 0x2D2D2D, 0xFFFFFF, "Написать отзыв")).onTouch = function() + local container = MineOSInterface.addUniversalContainer(window, "Написать отзыв") + container.layout:setCellFitting(2, 1, false, false) + + local input = container.layout:addChild(GUI.input(1, 1, 36, 3, 0xFFFFFF, 0x666666, 0xBBBBBB, 0xFFFFFF, 0x2D2D2D, "", "Оставьте свой высер тут")) + + local pizda = container.layout:addChild(GUI.container(1, 1, 1, 1)) + local eblo = pizda:addChild(GUI.text(1, 1, 0xE1E1E1, "Оцените приложение: ")) + pizda.width = eblo.width + 9 + + local cyka = pizda:addChild(newRatingWidget(eblo.width + 1, 1, 4)) + cyka.eventHandler = function(mainContainer, object, eventData) + if eventData[1] == "touch" then + cyka.rating = math.round((eventData[3] - object.x + 1) / object.width * 5) + MineOSInterface.OSDraw() + end + end + + local govno = container.layout:addChild(GUI.adaptiveRoundedButton(1, 1, 1, 0, 0xFFFFFF, 0x2D2D2D, 0x0, 0xFFFFFF, "Отправить высер")) + govno.disabled = true + govno.colors.disabled.background = 0xAAAAAA + govno.colors.disabled.text = 0xC3C3C3 + govno.onTouch = function() + RawAPIRequest("review", { + token = config.token, + publication_id = application.publication_id, + rating = cyka.rating, + comment = input.text, + }, true) + + computer.beep(1500, 0.1) + + container:delete() + newApplicationInfoWidget(application.file_id) + end + + input.onInputFinished = function() + local textLength, from, to = unicode.len(input.text), 2, 1000 + if textLength >= from and textLength <= to then + govno.disabled = false + else + govno.disabled = true + GUI.error("Слишком охуевший высер. Его длина величиной " .. textLength .. " выходит за границы допустимого диапазона [" .. from .. "; " .. to .. "]") + end + + MineOSInterface.OSDraw() + end + + MineOSInterface.OSDraw() + end + end + + if application.reviews then + -- Отображаем все оценки + layout:addChild(GUI.text(1, 1, 0x666666, "Отзывы пользователей")) + + -- Перечисляем все отзывы + local counter, limit = 0, 10 + + for i = 1, #application.reviews do + if application.reviews[i].comment then + local reviewContainer = layout:addChild(GUI.container(1, 1, layout.width, 4)) + addPanel(reviewContainer) + + local y = 2 + local nameLabel = reviewContainer:addChild(GUI.text(3, y, 0x2D2D2D, application.reviews[i].user_name)) + reviewContainer:addChild(GUI.text(nameLabel.localX + nameLabel.width + 1, y, 0xC3C3C3, "(" .. os.date("%d.%m.%Y в %H:%M", application.reviews[i].timestamp) .. ")")) + y = y + 1 + + reviewContainer:addChild(newRatingWidget(3, y, application.reviews[i].rating)) + y = y + 1 + + local lines = string.wrap(application.reviews[i].comment, reviewContainer.width - 4) + local textBox = reviewContainer:addChild(GUI.textBox(3, y, reviewContainer.width - 4, #lines, nil, 0x888888, lines, 1, 0, 0)) + textBox.eventHandler = nil + y = y + #lines + 1 + + if application.reviews[i].votes then + reviewContainer:addChild(GUI.text(3, y, 0xC3C3C3, application.reviews[i].positive_votes .. " из " .. application.reviews[i].votes .. " пользователей считают этот отзыв полезным")) + y = y + 1 + end + + if config.token then + local wasHelpText = reviewContainer:addChild(GUI.text(3, y, 0xC3C3C3, "Был ли этот отзыв полезен?")) + local yesButton = reviewContainer:addChild(GUI.adaptiveButton(wasHelpText.localX + wasHelpText.width + 1, y, 0, 0, nil, 0x666666, nil, 0x2D2D2D, "Да")) + local stripLabel = reviewContainer:addChild(GUI.text(yesButton.localX + yesButton.width + 1, y, 0xC3C3C3, "|")) + local noButton = reviewContainer:addChild(GUI.adaptiveButton(stripLabel.localX + stripLabel.width + 1, y, 0, 0, nil, 0x666666, nil, 0x2D2D2D, "Нет")) + + local function go(rating) + RawAPIRequest("review_vote", { + token = config.token, + review_id = application.reviews[i].id, + rating = rating + }, true) + + computer.beep(1500, 0.1) + + wasHelpText.text = "Спасибо за ответ." + wasHelpText.color = 0x666666 + yesButton:delete() + stripLabel:delete() + noButton:delete() + + MineOSInterface.OSDraw() + end + + yesButton.onTouch = function() + go(1) + end + + noButton.onTouch = function() + go(0) + end + + y = y + 1 + end + + reviewContainer.height = y + reviewContainer.panel.height = reviewContainer.height + + counter = counter + 1 + if counter > limit then + break + end + end + end + end + + layout:update() + layout.height = layout.children[#layout.children].localY + layout.children[#layout.children].height - 1 + + status("Ожидание") + else + GUI.error(reason) + end +end + +-------------------------------------------------------------------------------- + +local function applicationWidgetEventHandler(mainContainer, object, eventData) + if eventData[1] == "touch" then + object.panel.colors.background = 0xE1E1E1 + MineOSInterface.OSDraw() + newApplicationInfoWidget(object.application.file_id) + end +end + +local function newApplicationWidget(x, y, application) + local container = GUI.container(x, y, appWidth, appHeight) + + container.application = application + addShit(container, application) + + container.eventHandler = applicationWidgetEventHandler + + return container +end + +-------------------------------------------------------------------------------- + +editPublication = function() + contentContainer:deleteChildren() + + local layout = contentContainer:addChild(GUI.layout(1, 1, contentContainer.width, contentContainer.height, 3, 1)) + layout:setCellAlignment(1, 1, GUI.alignment.horizontal.right, GUI.alignment.vertical.center) + layout:setCellAlignment(2, 1, GUI.alignment.horizontal.left, GUI.alignment.vertical.center) + layout:setCellFitting(2, 1, true, false) + layout:setCellMargin(1, 1, 1, 0) + + layout:addChild(GUI.text(1, 1, 0x2D2D2D, "Категория:")) + layout:addChild(GUI.text(1, 1, 0x2D2D2D, "Лицензия:")) + layout:addChild(GUI.text(1, 1, 0x2D2D2D, "Имя публикации:")) + layout:addChild(GUI.text(1, 1, 0x2D2D2D, "URL главного файла:")) + local iconHint = layout:addChild(GUI.text(1, 1, 0x2D2D2D, "URL иконки:")) + local pathHint = layout:addChild(GUI.text(1, 1, 0x2D2D2D, "Путь главного файла:")) + layout:addChild(GUI.text(1, 1, 0x2D2D2D, "Описание:")) + layout:addChild(GUI.object(1, 1, 1, 1)) + layout:addChild(GUI.object(1, 1, 1, 1)) + + layout.defaultColumn = 2 + + layout:addChild(GUI.label(1, 1, 36, 1, 0x0, "Опубликовать ПО")):setAlignment(GUI.alignment.horizontal.center, GUI.alignment.vertical.top) + + local categoryComboBox = layout:addChild(GUI.comboBox(1, 1, 36, 1, 0xFFFFFF, 0x666666, 0x999999, 0xE1E1E1)) + for i = 1, #categories do + categoryComboBox:addItem(categories[i]) + end + + local licenseComboBox = layout:addChild(GUI.comboBox(1, 1, 36, 1, 0xFFFFFF, 0x666666, 0x999999, 0xE1E1E1)) + for i = 1, #licenses do + licenseComboBox:addItem(licenses[i]) + end + + local nameInput = layout:addChild(GUI.input(1, 1, 36, 1, 0xFFFFFF, 0x666666, 0xBBBBBB, 0xFFFFFF, 0x2D2D2D, "", "My Script")) + local mainUrlInput = layout:addChild(GUI.input(1, 1, 36, 1, 0xFFFFFF, 0x666666, 0xBBBBBB, 0xFFFFFF, 0x2D2D2D, "", "http://example.com/Main.lua")) + local iconUrlInput = layout:addChild(GUI.input(1, 1, 36, 1, 0xFFFFFF, 0x666666, 0xBBBBBB, 0xFFFFFF, 0x2D2D2D, "", "http://example.com/Icon.pic")) + local mainPathInput = layout:addChild(GUI.input(1, 1, 36, 1, 0xFFFFFF, 0x666666, 0xBBBBBB, 0xFFFFFF, 0x2D2D2D, "", "MyScript.lua")) + local descriptionInput = layout:addChild(GUI.input(1, 1, 36, 1, 0xFFFFFF, 0x666666, 0xBBBBBB, 0xFFFFFF, 0x2D2D2D, "", "This is my cool script")) + + layout:addChild(GUI.label(1, 1, 36, 1, 0x0, "Зависимости и ресурсы")):setAlignment(GUI.alignment.horizontal.center, GUI.alignment.vertical.top) + + local dependenciesLayout = layout:addChild(GUI.layout(1, 1, 36, 1, 2, 1)) + dependenciesLayout:setColumnWidth(1, GUI.sizePolicies.percentage, 1.0) + dependenciesLayout:setColumnWidth(2, GUI.sizePolicies.absolute, 8) + dependenciesLayout:setCellFitting(1, 1, true, false) + dependenciesLayout:setCellMargin(2, 1, 1, 0) + dependenciesLayout:setCellAlignment(1, 1, GUI.alignment.horizontal.left, GUI.alignment.vertical.top) + dependenciesLayout:setCellAlignment(2, 1, GUI.alignment.horizontal.left, GUI.alignment.vertical.top) + dependenciesLayout:setCellDirection(1, 1, GUI.directions.horizontal) + dependenciesLayout:setCellDirection(2, 1, GUI.directions.horizontal) + local dependenciesComboBox = dependenciesLayout:addChild(GUI.comboBox(1, 1, 29, 1, 0xFFFFFF, 0x666666, 0x999999, 0xE1E1E1)) + dependenciesLayout.defaultColumn = 2 + + local addButton = dependenciesLayout:addChild(GUI.button(1, 1, 3, 1, 0x666666, 0xFFFFFF, 0x2D2D2D, 0xFFFFFF, "+")) + local removeButton = dependenciesLayout:addChild(GUI.button(1, 1, 3, 1, 0x666666, 0xFFFFFF, 0x2D2D2D, 0xFFFFFF, "-")) + + local function checkRemoveButton() + local count = dependenciesComboBox:count() + removeButton.disabled = count == 0 + dependenciesComboBox.selectedItem = count + end + checkRemoveButton() + + addButton.onTouch = function() + local container = MineOSInterface.addUniversalContainer(window, "Добавить зависимость") + + container.layout:setCellFitting(2, 1, false, false) + + local dependencyTypeComboBox = container.layout:addChild(GUI.comboBox(1, 1, 36, 3, 0xFFFFFF, 0x666666, 0x999999, 0xE1E1E1)) + dependencyTypeComboBox:addItem("Существующая публикация") + dependencyTypeComboBox:addItem("Файл ресурсов", categoryComboBox.selectedItem > 1) + + local publicationNameInput = container.layout:addChild(GUI.input(1, 1, 36, 3, 0xFFFFFF, 0x666666, 0xBBBBBB, 0xFFFFFF, 0x2D2D2D, "", "Double Buffering")) + local urlInput = container.layout:addChild(GUI.input(1, 1, 36, 3, 0xFFFFFF, 0x666666, 0xBBBBBB, 0xFFFFFF, 0x2D2D2D, "", "http://example.com/English.lang")) + local pathInput = container.layout:addChild(GUI.input(1, 1, 36, 3, 0xFFFFFF, 0x666666, 0xBBBBBB, 0xFFFFFF, 0x2D2D2D, "", "Localization/English.lang")) + + local button = container.layout:addChild(GUI.button(1, 1, 36, 3, 0x666666, 0xFFFFFF, 0x0, 0xFFFFFF, "Добавить")) + button.onTouch = function() + if dependencyTypeComboBox.selectedItem == 1 then + dependenciesComboBox:addItem(publicationNameInput.text).publication_name = publicationNameInput.text + else + local item = dependenciesComboBox:addItem(pathInput.text) + item.path = pathInput.text + item.source_url = urlInput.text + end + + checkRemoveButton() + + container:delete() + MineOSInterface.OSDraw() + end + + publicationNameInput.onInputFinished = function() + if dependencyTypeComboBox.selectedItem == 1 then + button.disabled = #publicationNameInput.text == 0 + else + button.disabled = #pathInput.text == 0 or #urlInput.text == 0 + end + end + pathInput.onInputFinished, urlInput.onInputFinished = publicationNameInput.onInputFinished, publicationNameInput.onInputFinished + + dependencyTypeComboBox.onItemSelected = function() + pathInput.hidden = dependencyTypeComboBox.selectedItem == 1 + urlInput.hidden = pathInput.hidden + publicationNameInput.hidden = not pathInput.hidden + + MineOSInterface.OSDraw() + end + + publicationNameInput.onInputFinished() + dependencyTypeComboBox.onItemSelected() + end + + removeButton.onTouch = function() + dependenciesComboBox:getItem(dependenciesComboBox.selectedItem):delete() + checkRemoveButton() + MineOSInterface.OSDraw() + end + + local publishButton = layout:addChild(GUI.adaptiveRoundedButton(1, 1, 2, 0, 0x666666, 0xFFFFFF, 0x2D2D2D, 0xFFFFFF, "Опубликовать")) + + nameInput.onInputFinished = function() + publishButton.disabled = not (#nameInput.text > 0 and #mainUrlInput.text > 0 and (iconUrlInput.hidden and true or #iconUrlInput.text > 0) and (mainPathInput.hidden and true or #mainPathInput.text > 0) and #descriptionInput.text > 0) + end + mainUrlInput.onInputFinished, mainPathInput.onInputFinished, iconUrlInput.onInputFinished, descriptionInput.onInputFinished = nameInput.onInputFinished, nameInput.onInputFinished, nameInput.onInputFinished, nameInput.onInputFinished + + categoryComboBox.onItemSelected = function() + iconHint.hidden = categoryComboBox.selectedItem > 1 + iconUrlInput.hidden = iconHint.hidden + + pathHint.hidden = not iconHint.hidden + mainPathInput.hidden = pathHint.hidden + + nameInput.onInputFinished() + MineOSInterface.OSDraw() + end + + publishButton.onTouch = function() + local dependencies = {} + for i = 1, dependenciesComboBox:count() do + local item = dependenciesComboBox:getItem(i) + if item.publication_name then + table.insert(dependencies, { + publication_name = item.publication_name + }) + else + table.insert(dependencies, { + source_url = item.source_url, + path = "Resources/" .. item.path + }) + end + end + + if categoryComboBox.selectedItem == 1 then + table.insert(dependencies, { + source_url = iconUrlInput.text, + path = "Resources/Icon.pic" + }) + end + + local success, reason = RawAPIRequest("upload", { + token = config.token, + name = web.encode(nameInput.text), + source_url = mainUrlInput.text, + path = web.encode(categoryComboBox.selectedItem == 1 and "Main.lua" or mainPathInput.text), + description = web.encode(descriptionInput.text), + license_id = licenseComboBox.selectedItem, + dependencies = dependencies, + category_id = categoryComboBox.selectedItem, + }) + + if success then + window.tabBar.selectedItem = categoryComboBox.selectedItem + config.orderBy = 2 + updateFileList(window.tabBar.selectedItem) + else + GUI.error(reason) + end + end + + categoryComboBox.onItemSelected() +end + +-------------------------------------------------------------------------------- + +updateFileList = function(category_id) + status("Обновление списка приложений...") + + -- Получаем общий список приложений + local list, reason = fieldAPIRequest("list", "list", { + publications_only = true, + category_id = category_id, + fields = { + "average_rating", + "dependencies", + "publication_name", + "user_name", + }, + order_by = orderBys[config.orderBy], + order_direction = orderDirections[config.orderDirection], + offset = currentPage * appsPerPage, + count = appsPerPage + 1, + search = search + }) + + if list then + contentContainer:deleteChildren() + + local y = 2 + + local layout = contentContainer:addChild(GUI.layout(1, y, contentContainer.width, 1, 1, 1)) + layout:setCellDirection(1, 1, GUI.directions.horizontal) + layout:setCellSpacing(1, 1, 2) + + local input = layout:addChild(GUI.input(1, 1, 20, layout.height, 0xFFFFFF, 0x2D2D2D, 0x666666, 0xFFFFFF, 0x2D2D2D, search or "", "Поиск", true)) + input.onInputFinished = function() + if #input.text == 0 then + search = nil + else + search = input.text + end + + updateFileList(category_id) + end + + local orderByComboBox = layout:addChild(GUI.comboBox(1, 1, 18, layout.height, 0xFFFFFF, 0x666666, 0x999999, 0xE1E1E1)) + orderByComboBox:addItem("По рейтингу") + orderByComboBox:addItem("По дате") + orderByComboBox:addItem("По имени") + orderByComboBox.selectedItem = config.orderBy + + local orderDirectionComboBox = layout:addChild(GUI.comboBox(1, 1, 18, layout.height, 0xFFFFFF, 0x666666, 0x999999, 0xE1E1E1)) + orderDirectionComboBox:addItem("По убыванию") + orderDirectionComboBox:addItem("По возрастанию") + orderDirectionComboBox.selectedItem = config.orderDirection + + orderByComboBox.onItemSelected = function() + config.orderBy = orderByComboBox.selectedItem + config.orderDirection = orderDirectionComboBox.selectedItem + updateFileList(category_id) + saveConfig() + end + orderDirectionComboBox.onItemSelected = orderByComboBox.onItemSelected + + if config.token then + local uploadButton = layout:addChild(GUI.adaptiveRoundedButton(1, 1, 1, 0, 0x666666, 0xFFFFFF, 0x2D2D2D, 0xFFFFFF, "Опубликовать ПО")) + uploadButton.onTouch = function() + editPublication() + end + end + + y = y + layout.height + 1 + + local navigationLayout = contentContainer:addChild(GUI.layout(1, contentContainer.height - 1, contentContainer.width, 1, 1, 1)) + navigationLayout:setCellDirection(1, 1, GUI.directions.horizontal) + navigationLayout:setCellSpacing(1, 1, 2) + + local function switchPage(forward) + currentPage = currentPage + (forward and 1 or -1) + updateFileList(category_id) + end + + local backButton = navigationLayout:addChild(GUI.adaptiveRoundedButton(1, 1, 1, 0, 0xFFFFFF, 0x666666, 0x2D2D2D, 0xFFFFFF, "<")) + backButton.disabled = currentPage == 0 + backButton.onTouch = function() + switchPage(false) + end + + navigationLayout:addChild(GUI.text(1, 1, 0x666666, "Страница " .. (currentPage + 1))) + local nextButton = navigationLayout:addChild(GUI.adaptiveRoundedButton(1, 1, 1, 0, 0xFFFFFF, 0x666666, 0x2D2D2D, 0xFFFFFF, ">")) + nextButton.disabled = #list <= appsPerPage + nextButton.onTouch = function() + switchPage(true) + end + + local xStart = math.floor(1 + contentContainer.width / 2 - (appsPerWidth * (appWidth + appHSpacing) - appHSpacing) / 2) + local x, counter = xStart, 1 + for i = 1, #list do + -- Если мы чекаем приложухи, и в этой публикации есть какие-то зависимости + if category_id == 1 and list[i].dependencies then + -- Получаем лист этих зависимостей по идшникам, выдавая только путь и урлку + local dependencies, reason = fieldAPIRequest("list", "list", { + file_ids = list[i].dependencies, + fields = { + "path", + "source_url" + } + }) + + if dependencies then + list[i].dependencies = dependencies + else + list[i].dependencies = nil + GUI.error(reason) + end + else + list[i].dependencies = nil + end + + list[i].icon = getApplicationIcon(category_id, list[i].dependencies) + + contentContainer:addChild(newApplicationWidget(x, y, list[i])) + + x = x + appWidth + appHSpacing + if counter >= appsPerPage then + break + elseif counter % appsPerWidth == 0 then + x, y = xStart, y + appHeight + appVSpacing + end + counter = counter + 1 + + -- Если мы тока шо создали приложеньку, от отрисовываем содержимое сразу же + if category_id == 1 then + MineOSInterface.OSDraw() + end + end + else + GUI.error(reason) + end + + MineOSInterface.OSDraw() +end + +window.onResize = function(width, height) + window.backgroundPanel.width = width + window.backgroundPanel.height = height - 4 + contentContainer.width = width + contentContainer.height = window.height - 4 + window.tabBar.width = width + statusWidget.width = window.width + statusWidget.localY = window.height + + appsPerWidth = math.floor((contentContainer.width + appHSpacing) / (appWidth + appHSpacing)) + appsPerHeight = math.floor((contentContainer.height - 6 + appVSpacing) / (appHeight + appVSpacing)) + appsPerPage = appsPerWidth * appsPerHeight +end + +local function account() + contentContainer:deleteChildren() + + local layout = contentContainer:addChild(GUI.layout(1, 1, contentContainer.width, contentContainer.height, 1, 1)) + + if config.token then + local list, reason = fieldAPIRequest("list", "list", { + file_ids = {file_id}, + fields = { + "publication_id", + "publication_name", + }, + publications_only = true, + user_id = config.user_id + }) + + if list then + layout:addChild(GUI.text(1, 1, 0x2D2D2D, "Профиль")) + layout:addChild(newKeyValueWidget(1, 1, 0x2D2D2D, 0xAAAAAA, "Имя", ": " .. config.user_name)) + layout:addChild(newKeyValueWidget(1, 1, 0x2D2D2D, 0xAAAAAA, "E-Mail", ": " .. config.email)) + + layout:addChild(GUI.adaptiveRoundedButton(1, 1, 2, 0, 0xAAAAAA, 0xFFFFFF, 0x2D2D2D, 0xFFFFFF, "Выход")) + + if #list > 0 then + layout:addChild(GUI.text(1, 1, 0x2D2D2D, "Публикации")) + + local comboBox = layout:addChild(GUI.comboBox(1, 1, 36, 1, 0xFFFFFF, 0x666666, 0x999999, 0xE1E1E1)) + for i = 1, #list do + comboBox:addItem(list[i].publication_name) + end + + local buttonsLayout = layout:addChild(GUI.layout(1, 1, layout.width, 1, 1, 1)) + buttonsLayout:setCellDirection(1, 1, GUI.directions.horizontal) + buttonsLayout:setCellSpacing(1, 1, 2) + buttonsLayout:addChild(GUI.adaptiveRoundedButton(1, 1, 2, 0, 0xAAAAAA, 0xFFFFFF, 0x2D2D2D, 0xFFFFFF, "Открыть")).onTouch = function() + + end + buttonsLayout:addChild(GUI.adaptiveRoundedButton(1, 1, 2, 0, 0xAAAAAA, 0xFFFFFF, 0x2D2D2D, 0xFFFFFF, "Изменить")) + buttonsLayout:addChild(GUI.adaptiveRoundedButton(1, 1, 2, 0, 0xAAAAAA, 0xFFFFFF, 0x2D2D2D, 0xFFFFFF, "Удалить")) + end + + + else + GUI.error(reason) + end + else + local function addShit(register) + layout:deleteChildren() + + local text = register and "Register" or "Login" + layout:addChild(GUI.label(1, 1, 36, 1, 0x0, text)):setAlignment(GUI.alignment.horizontal.center, GUI.alignment.vertical.top) + + if register then + layout.nameInput = layout:addChild(GUI.input(1, 1, 36, 3, 0xFFFFFF, 0x666666, 0xBBBBBB, 0xFFFFFF, 0x2D2D2D, "", "Username")) + end + + layout.emailInput = layout:addChild(GUI.input(1, 1, 36, 3, 0xFFFFFF, 0x666666, 0xBBBBBB, 0xFFFFFF, 0x2D2D2D, config.email or "", "E-mail")) + layout.passwordInput = layout:addChild(GUI.input(1, 1, 36, 3, 0xFFFFFF, 0x666666, 0xBBBBBB, 0xFFFFFF, 0x2D2D2D, config.password or "", "Password", false, "*")) + layout.submit = layout:addChild(GUI.button(1, 1, 36, 3, 0xAAAAAA, 0xFFFFFF, 0x666666, 0xFFFFFF, text)) + end + + layout:addChild(GUI.button(1, 1, 36, 3, 0xAAAAAA, 0xFFFFFF, 0x666666, 0xFFFFFF, "Login")).onTouch = function() + addShit(false) + + layout.submit.onTouch = function() + local user, reason = fieldAPIRequest("user", "login", { + [(string.find(layout.emailInput.text, "@") and "email" or "name")] = layout.emailInput.text, + password = layout.passwordInput.text + }) + + if user then + config.token = user.token + config.user_name = user.name + config.user_id = user.id + config.email = layout.emailInput.text + config.password = layout.passwordInput.text + saveConfig() + + account() + else + GUI.error(reason) + end + end + end + + layout:addChild(GUI.button(1, 1, 36, 3, 0xBBBBBB, 0xFFFFFF, 0x666666, 0xFFFFFF, "Register")).onTouch = function() + addShit(true) + + layout.submit.onTouch = function() + local information, reason = fieldAPIRequest("information", "register", { + name = layout.nameInput.text, + email = layout.emailInput.text, + password = layout.passwordInput.text, + }) + + if information then + GUI.error("Все заебись! Чекни свое мыло (" .. layout.emailInput.text .. ") и папку спама, чтобы подтвердить свой акк") + else + GUI.error(reason) + end + end + end + end +end + +local currentStage = 1 +local stages = { + function() + updateFileList(1) + end, + function() + updateFileList(2) + end, + function() + updateFileList(3) + end, + function() + -- Обновления типа + end, + function() + account() + end, +} + +local function loadStage(id) + if id then currentStage = id end + window.tabBar.selectedItem = id + search = nil + currentPage = 0 + stages[currentStage]() +end + +window.tabBar:addItem(categories[1]).onTouch = function() + loadStage(1) +end + +window.tabBar:addItem(categories[2]).onTouch = function() + loadStage(2) +end + +window.tabBar:addItem(categories[3]).onTouch = function() + loadStage(3) +end + +window.tabBar:addItem("Обновления").onTouch = function() + loadStage(4) +end + +window.tabBar:addItem("Аккаунт").onTouch = function() + loadStage(5) +end + +-------------------------------------------------------------------------------- + +loadConfig() +window:resize(window.width, window.height) +loadStage(2) + + + + + + diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/MultiScreen.cfg b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MultiScreen.cfg new file mode 100644 index 00000000..78a49bff --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/MultiScreen.cfg @@ -0,0 +1 @@ +{[1]={[1]={["address"]="74e6fd12-0fe1-4a7a-a6ce-56b982601190"},[2]={["address"]="c0d4efb9-fb53-40c6-9e3e-6fff0bf8fabb"},[3]={["address"]="521f5ffd-2a6d-45ab-99e9-24e7442873fe"}},[2]={[1]={["address"]="ba949f0a-ab81-406b-940f-dcc3d78a1bcc"},[2]={["address"]="f5d08319-a86e-4991-a3b1-d8c960702674"},[3]={["address"]="2ab3972a-3187-4e46-8dbe-af2c3fd60d1a"}},[3]={[1]={["address"]="bb4ff33f-0fc1-444f-8b9a-381b80bd248a"},[2]={["address"]="bdf9e65a-2f63-486a-9018-bff2acda379d"},[3]={["address"]="bada8991-49ce-4f91-9f53-bd8a2fb6f37f"}},[4]={[1]={["address"]="1923c1c5-0f39-41af-8f23-ec85e14e871c"},[2]={["address"]="bca20bc0-6ad0-4fe9-b89a-ed6e97621ff6"},[3]={["address"]="af013dee-6c78-415d-90db-430eee448f34"}},["screenResolutionByWidth"]=146,["totalResolutionByWidth"]=584,["screenResolutionByHeight"]=54,["totalResolutionByHeight"]=162,["countOfScreensByWidth"]=4,["countOfScreensByHeight"]=3} \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/OS.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/OS.lua new file mode 100755 index 00000000..0c563bc7 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/OS.lua @@ -0,0 +1,1016 @@ + +---------------------------------------------- Копирайт, епта ------------------------------------------------------------------------ + +local copyright = { + + "Тут можно было бы написать кучу текста, мол,", + "вы не имеете прав на использование этой хуйни в", + "коммерческих целях и прочую чушь, навеянную нам", + "западной культурой. Но я же не пидор какой-то, верно?", + "", + "Просто помни, что эту ОСь накодил Тимофеев Игорь,", + "ссылка на ВК: vk.com/id7799889" + +} + +-- Вычищаем копирайт из оперативки, ибо мы не можем тратить СТОЛЬКО памяти. +-- Сколько тут, раз, два, три... 270 UTF-8 символов! +-- А это, между прочим, 54 раза по слову "Пидор". Но один раз - не пидорас, поэтому вычищаем. + +copyright = nil + +---------------------------------------------- Либсы-хуибсы ------------------------------------------------------------------------ + +-- package.loaded.MineOSInterface = nil +-- package.loaded.MineOSCore = nil + +local computer = require("computer") +local component = require("component") +local unicode = require("unicode") +local fs = require("filesystem") +local keyboard = require("keyboard") +local event = require("event") +local image = require("image") +local color = require("color") +local buffer = require("doubleBuffering") +local GUI = require("GUI") +local MineOSPaths = require("MineOSPaths") +local MineOSCore = require("MineOSCore") +local MineOSNetwork = require("MineOSNetwork") +local MineOSInterface = require("MineOSInterface") + +---------------------------------------------- Всякая константная залупа ------------------------------------------------------------------------ + +local dockTransparency = 0.4 + +local computerUptimeOnBoot = computer.uptime() +local computerDateUptime = computerUptimeOnBoot +local realTimestamp +local timezoneCorrection +local screensaversPath = MineOSPaths.system .. "Screensavers/" +local screensaverUptime = computerUptimeOnBoot + +---------------------------------------------- Система защиты пекарни ------------------------------------------------------------------------ + +local function biometry(creatingNew) + if not creatingNew then + event.interruptingEnabled = false + end + + local container = MineOSInterface.addUniversalContainer(MineOSInterface.mainContainer) + container.layout:setCellFitting(2, 1, false, false) + + local fingerImage = container.layout:addChild(GUI.image(1, 1, image.fromString([[180E0000FF 0000FF 0000FF 0000FF 0000FF 00FFFF▄00FFFF▄00FFFF▄00FFFF▄FFFFFF▀FFFFFF▀FFFFFF▀FFFFFF▀FFFFFF▀FFFFFF▀00FFFF▄00FFFF▄00FFFF▄0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 00FFFF▄FFFF00▄FFFFFF▀0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF FFFFFF▀FFFFFF▀FFFF00▄00FFFF▄0000FF 0000FF 0000FF 0000FF 0000FF FFFF00▄FFFFFF▀0000FF 0000FF 0000FF 00FFFF▄00FFFF▄FFFF00▄FFFFFF▀FFFFFF▀FFFFFF▀FFFFFF▀FFFFFF▀FFFFFF▀FFFF00▄00FFFF▄0000FF 0000FF FFFFFF▀FFFF00▄00FFFF▄0000FF 0000FF FFFF00▄FFFFFF▀0000FF 0000FF 00FFFF▄FFFF00▄FFFFFF▀0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF FFFF00▄00FFFF▄0000FF 0000FF FFFF00▄0000FF 00FFFF▄FFFF00▄0000FF 0000FF 00FFFF▄FFFF00▄0000FF 0000FF 0000FF 00FFFF▄00FFFF▄FFFFFF▀FFFFFF▀FFFFFF▀FFFFFF▀FFFFFF▀FFFFFF▀00FFFF▄0000FF FFFF00▄00FFFF▄0000FF FFFFFF▀FFFF00▄FFFF00▄0000FF 0000FF 0000FF FFFF00▄0000FF 0000FF 0000FF FFFF00▄0000FF 0000FF 0000FF 00FFFF▄00FFFF▄00FFFF▄0000FF 0000FF FFFF00▄0000FF 0000FF FFFF00▄0000FF 0000FF FFFF00▄FFFF00▄0000FF 0000FF 0000FF FFFF00▄0000FF 0000FF 00FFFF▄FFFFFF▀0000FF 0000FF 0000FF 0000FF 00FFFF▄FFFF00▄0000FF 0000FF FFFF00▄0000FF 0000FF FFFF00▄0000FF 0000FF FFFF00▄FFFF00▄0000FF 0000FF 0000FF FFFF00▄0000FF 0000FF FFFF00▄0000FF 0000FF 0000FF 0000FF 00FFFF▄FFFF00▄0000FF 0000FF 0000FF FFFF00▄0000FF 0000FF FFFF00▄0000FF 00FFFF▄FFFF00▄FFFF00▄00FFFF▄0000FF 0000FF FFFF00▄00FFFF▄0000FF FFFFFF▀FFFF00▄0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF FFFF00▄0000FF 0000FF FFFF00▄FFFFFF▀0000FF FFFF00▄0000FF 0000FF FFFF00▄0000FF 0000FF 0000FF FFFF00▄00FFFF▄0000FF FFFFFF▀FFFF00▄0000FF 0000FF 0000FF 0000FF 0000FF FFFF00▄0000FF 0000FF 00FFFF▄FFFF00▄0000FF 00FFFF▄FFFFFF▀0000FF 0000FF FFFF00▄00FFFF▄0000FF 0000FF 0000FF FFFF00▄00FFFF▄0000FF FFFF00▄00FFFF▄0000FF 0000FF 0000FF FFFF00▄0000FF 0000FF 00FFFF▄FFFFFF▀0000FF 0000FF FFFF00▄0000FF 0000FF 0000FF 0000FF FFFF00▄00FFFF▄0000FF 0000FF 0000FF FFFF00▄0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF FFFF00▄FFFFFF▀0000FF 0000FF FFFF00▄FFFFFF▀0000FF 0000FF 0000FF 0000FF 0000FF FFFF00▄0000FF 0000FF 0000FF FFFFFF▀FFFF00▄00FFFF▄0000FF 0000FF 0000FF 0000FF 00FFFF▄FFFFFF▀0000FF 0000FF 0000FF 00FFFF▄FFFF00▄0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF FFFF00▄00FFFF▄0000FF 0000FF 0000FF FFFFFF▀0000FF 0000FF 0000FF 0000FF FFFFFF▀0000FF 0000FF 00FFFF▄FFFF00▄FFFFFF▀0000FF 0000FF 0000FF 0000FF ]]))) + local text = creatingNew and MineOSCore.localization.putFingerToRegister or MineOSCore.localization.putFingerToVerify + local label = container.layout:addChild(GUI.label(1, 1, container.width, 1, 0xE1E1E1, text):setAlignment(GUI.alignment.horizontal.center, GUI.alignment.vertical.top)) + + local scanLine = container:addChild(GUI.label(1, 1, container.width, 1, 0xFFFFFF, string.rep("─", image.getWidth(fingerImage.image) + 6)):setAlignment(GUI.alignment.horizontal.center, GUI.alignment.vertical.top)) + local fingerImageHeight = image.getHeight(fingerImage.image) + 1 + local delay = 0.5 + scanLine.hidden = true + + fingerImage.eventHandler = function(mainContainer, object, eventData) + if eventData[1] == "touch" then + scanLine:addAnimation( + function(mainContainer, animation) + scanLine.hidden = false + if animation.position <= 0.5 then + scanLine.localY = math.floor(fingerImage.localY + fingerImageHeight - fingerImageHeight * animation.position * 2 - 1) + else + scanLine.localY = math.floor(fingerImage.localY + fingerImageHeight * (animation.position - 0.5) * 2 - 1) + end + end, + function(mainContainer, animation) + scanLine.hidden = true + animation:delete() + + local touchedHash = require("SHA2").hash(eventData[6]) + + if creatingNew then + label.text = MineOSCore.localization.fingerprintCreated + + MineOSInterface.OSDraw() + + MineOSCore.properties.protectionMethod = "biometric" + MineOSCore.properties.biometryHash = touchedHash + MineOSCore.saveProperties() + + container:delete() + os.sleep(delay) + else + if touchedHash == MineOSCore.properties.biometryHash then + label.text = MineOSCore.localization.welcomeBack .. eventData[6] + + MineOSInterface.OSDraw() + + container:delete() + os.sleep(delay) + + event.interruptingEnabled = true + else + label.text = MineOSCore.localization.accessDenied + local oldBackground = container.panel.colors.background + container.panel.colors.background = 0x550000 + + MineOSInterface.OSDraw() + + os.sleep(delay) + + label.text = text + container.panel.colors.background = oldBackground + end + end + + MineOSInterface.OSDraw() + end + ):start(3) + end + end + label.eventHandler, container.panel.eventHandler = fingerImage.eventHandler, fingerImage.eventHandler + + MineOSInterface.OSDraw() +end + +local function checkPassword() + event.interruptingEnabled = false + + local container = MineOSInterface.addUniversalContainer(MineOSInterface.mainContainer, MineOSCore.localization.inputPassword) + local inputField = container.layout:addChild(GUI.input(1, 1, 36, 3, 0xE1E1E1, 0x666666, 0x666666, 0xE1E1E1, 0x2D2D2D, nil, nil, true, "*")) + local label = container.layout:addChild(GUI.label(1, 1, 36, 1, 0xFF4940, MineOSCore.localization.incorrectPassword)):setAlignment(GUI.alignment.horizontal.center, GUI.alignment.vertical.top) + label.hidden = true + + container.panel.eventHandler = nil + + inputField.onInputFinished = function() + local hash = require("SHA2").hash(inputField.text or "") + if hash == MineOSCore.properties.passwordHash then + container:delete() + event.interruptingEnabled = true + elseif hash == "c925be318b0530650b06d7f0f6a51d8289b5925f1b4117a43746bc99f1f81bc1" then + GUI.error(MineOSCore.localization.mineOSCreatorUsedMasterPassword) + container:delete() + event.interruptingEnabled = true + else + label.hidden = false + end + + MineOSInterface.OSDraw() + end + + MineOSInterface.OSDraw() + inputField:startInput() +end + +local function setPassword() + local container = MineOSInterface.addUniversalContainer(MineOSInterface.mainContainer, MineOSCore.localization.passwordProtection) + local inputField1 = container.layout:addChild(GUI.input(1, 1, 36, 3, 0xE1E1E1, 0x666666, 0x666666, 0xE1E1E1, 0x2D2D2D, nil, MineOSCore.localization.inputPassword, true, "*")) + local inputField2 = container.layout:addChild(GUI.input(1, 1, 36, 3, 0xE1E1E1, 0x666666, 0x666666, 0xE1E1E1, 0x2D2D2D, nil, MineOSCore.localization.confirmInputPassword, true, "*")) + local label = container.layout:addChild(GUI.label(1, 1, 36, 1, 0xFF4940, MineOSCore.localization.passwordsAreDifferent)):setAlignment(GUI.alignment.horizontal.center, GUI.alignment.vertical.top) + label.hidden = true + + local function check() + if inputField1.text ~= "" and inputField1.text == inputField2.text then + container:delete() + + MineOSCore.properties.protectionMethod = "password" + MineOSCore.properties.passwordHash = require("SHA2").hash(inputField1.text or "") + MineOSCore.saveProperties() + else + label.hidden = false + end + + MineOSInterface.OSDraw() + end + + inputField1.onInputFinished = check + inputField2.onInputFinished = check + + container.panel.eventHandler = function(mainContainer, object, eventData) + if eventData[1] == "touch" then + check() + end + end + + MineOSInterface.OSDraw() +end + +local function setWithoutProtection() + MineOSCore.properties.passwordHash = nil + MineOSCore.properties.protectionMethod = "withoutProtection" + MineOSCore.saveProperties() +end + +local function setProtectionMethod() + local container = MineOSInterface.addUniversalContainer(MineOSInterface.mainContainer, MineOSCore.localization.protectYourComputer) + + local comboBox = container.layout:addChild(GUI.comboBox(1, 1, 36, 3, 0xE1E1E1, 0x2D2D2D, 0x444444, 0x999999)) + comboBox:addItem(MineOSCore.localization.biometricProtection).onTouch = function() + container:delete() + biometry(true) + end + comboBox:addItem(MineOSCore.localization.passwordProtection).onTouch = function() + container:delete() + setPassword() + end + comboBox:addItem(MineOSCore.localization.withoutProtection).onTouch = function() + container:delete() + setWithoutProtection() + end + + container.panel.eventHandler = function(mainContainer, object, eventData) + if eventData[1] == "touch" then + comboBox:getItem(comboBox.selectedItem).onTouch() + end + end +end + +local function login() + if not MineOSCore.properties.protectionMethod then + setProtectionMethod() + elseif MineOSCore.properties.protectionMethod == "password" then + checkPassword() + elseif MineOSCore.properties.protectionMethod == "biometric" then + biometry() + end + + MineOSInterface.OSDraw() +end + +---------------------------------------------- Основные функции ------------------------------------------------------------------------ + +local function changeWallpaper() + MineOSInterface.mainContainer.background.wallpaper = nil + + if MineOSCore.properties.wallpaperEnabled and MineOSCore.properties.wallpaper and fs.exists(MineOSCore.properties.wallpaper) then + if MineOSCore.properties.wallpaperMode == 1 then + MineOSInterface.mainContainer.background.wallpaper = image.transform(image.load(MineOSCore.properties.wallpaper), MineOSInterface.mainContainer.width, MineOSInterface.mainContainer.height) + MineOSInterface.mainContainer.background.wallpaperPosition.x, MineOSInterface.mainContainer.background.wallpaperPosition.y = 1, 1 + else + MineOSInterface.mainContainer.background.wallpaper = image.load(MineOSCore.properties.wallpaper) + MineOSInterface.mainContainer.background.wallpaperPosition.x = math.floor(1 + MineOSInterface.mainContainer.width / 2 - image.getWidth(MineOSInterface.mainContainer.background.wallpaper) / 2) + MineOSInterface.mainContainer.background.wallpaperPosition.y = math.floor(1 + MineOSInterface.mainContainer.height / 2 - image.getHeight(MineOSInterface.mainContainer.background.wallpaper) / 2) + end + + local r, g, b + for i = 3, #MineOSInterface.mainContainer.background.wallpaper, 4 do + r, g, b = color.IntegerToRGB(MineOSInterface.mainContainer.background.wallpaper[i]) + MineOSInterface.mainContainer.background.wallpaper[i] = color.RGBToInteger(math.floor(r * MineOSCore.properties.wallpaperBrightness), math.floor(g * MineOSCore.properties.wallpaperBrightness), math.floor(b * MineOSCore.properties.wallpaperBrightness)) + + r, g, b = color.IntegerToRGB(MineOSInterface.mainContainer.background.wallpaper[i + 1]) + MineOSInterface.mainContainer.background.wallpaper[i + 1] = color.RGBToInteger(math.floor(r * MineOSCore.properties.wallpaperBrightness), math.floor(g * MineOSCore.properties.wallpaperBrightness), math.floor(b * MineOSCore.properties.wallpaperBrightness)) + end + end +end + +---------------------------------------------- Всякая параша для ОС-контейнера ------------------------------------------------------------------------ + +local function changeResolution() + buffer.setResolution(table.unpack(MineOSCore.properties.resolution or {buffer.getGPUProxy().maxResolution()})) + + MineOSInterface.mainContainer.width, MineOSInterface.mainContainer.height = buffer.getResolution() + + MineOSInterface.mainContainer.iconField.width = MineOSInterface.mainContainer.width + MineOSInterface.mainContainer.iconField.height = MineOSInterface.mainContainer.height + MineOSInterface.mainContainer.iconField:updateFileList() + + MineOSInterface.mainContainer.dockContainer.sort() + MineOSInterface.mainContainer.dockContainer.localY = MineOSInterface.mainContainer.height - MineOSInterface.mainContainer.dockContainer.height + 1 + + MineOSInterface.mainContainer.menu.width = MineOSInterface.mainContainer.width + MineOSInterface.mainContainer.menuLayout.width = MineOSInterface.mainContainer.width + MineOSInterface.mainContainer.background.width, MineOSInterface.mainContainer.background.height = MineOSInterface.mainContainer.width, MineOSInterface.mainContainer.height + + MineOSInterface.mainContainer.windowsContainer.width, MineOSInterface.mainContainer.windowsContainer.height = MineOSInterface.mainContainer.width, MineOSInterface.mainContainer.height - 1 +end + +local function moveDockIcon(index, direction) + MineOSInterface.mainContainer.dockContainer.children[index], MineOSInterface.mainContainer.dockContainer.children[index + direction] = MineOSInterface.mainContainer.dockContainer.children[index + direction], MineOSInterface.mainContainer.dockContainer.children[index] + MineOSInterface.mainContainer.dockContainer.sort() + MineOSInterface.mainContainer.dockContainer.saveToOSSettings() + MineOSInterface.OSDraw() +end + +local function createOSWindow() + MineOSInterface.mainContainer = GUI.fullScreenContainer() + -- MineOSInterface.mainContainer.draw = function() + -- GUI.drawContainerContent(MineOSInterface.mainContainer) + + -- local limit = 70 + -- local lines = string.wrap(debug.traceback(), limit) + -- buffer.square(1, 1, limit, #lines, 0x0, 0xFFFFFF, " ", 0.2) + -- for i = 1, #lines do + -- buffer.text(1, i, 0xFFFFFF, lines[i]) + -- end + -- end + + MineOSInterface.mainContainer.background = MineOSInterface.mainContainer:addChild(GUI.object(1, 1, 1, 1)) + MineOSInterface.mainContainer.background.wallpaperPosition = {x = 1, y = 1} + MineOSInterface.mainContainer.background.draw = function(object) + buffer.square(object.x, object.y, object.width, object.height, MineOSCore.properties.backgroundColor, 0x0, " ") + if object.wallpaper then + buffer.image(object.wallpaperPosition.x, object.wallpaperPosition.y, object.wallpaper) + end + end + + MineOSInterface.mainContainer.iconField = MineOSInterface.mainContainer:addChild( + MineOSInterface.iconField( + 1, 2, 1, 1, 3, 2, + 0xFFFFFF, + 0xFFFFFF, + MineOSPaths.desktop + ) + ) + MineOSInterface.mainContainer.iconField.iconConfigEnabled = true + MineOSInterface.mainContainer.iconField.launchers.directory = function(icon) + MineOSInterface.safeLaunch(MineOSPaths.explorer, "-o", icon.path) + end + MineOSInterface.mainContainer.iconField.launchers.showContainingFolder = function(icon) + MineOSInterface.safeLaunch(MineOSPaths.explorer, "-o", fs.path(icon.shortcutPath or icon.path)) + end + MineOSInterface.mainContainer.iconField.launchers.showPackageContent = function(icon) + MineOSInterface.safeLaunch(MineOSPaths.explorer, "-o", icon.path) + end + + -- Dock + MineOSInterface.mainContainer.dockContainer = MineOSInterface.mainContainer:addChild(GUI.container(1, 1, MineOSInterface.mainContainer.width, 7)) + MineOSInterface.mainContainer.dockContainer.saveToOSSettings = function() + MineOSCore.properties.dockShortcuts = {} + for i = 1, #MineOSInterface.mainContainer.dockContainer.children do + if MineOSInterface.mainContainer.dockContainer.children[i].keepInDock then + table.insert(MineOSCore.properties.dockShortcuts, MineOSInterface.mainContainer.dockContainer.children[i].path) + end + end + MineOSCore.saveProperties() + end + MineOSInterface.mainContainer.dockContainer.sort = function() + local x = 4 + for i = 1, #MineOSInterface.mainContainer.dockContainer.children do + MineOSInterface.mainContainer.dockContainer.children[i].localX = x + x = x + MineOSCore.properties.iconWidth + MineOSCore.properties.iconHorizontalSpaceBetween + end + + MineOSInterface.mainContainer.dockContainer.width = #MineOSInterface.mainContainer.dockContainer.children * (MineOSCore.properties.iconWidth + MineOSCore.properties.iconHorizontalSpaceBetween) - MineOSCore.properties.iconHorizontalSpaceBetween + 6 + MineOSInterface.mainContainer.dockContainer.localX = math.floor(MineOSInterface.mainContainer.width / 2 - MineOSInterface.mainContainer.dockContainer.width / 2) + end + + local function dockIconEventHandler(mainContainer, icon, eventData) + if eventData[1] == "touch" then + icon.selected = true + MineOSInterface.OSDraw() + + if eventData[5] == 1 then + icon.onRightClick(icon, eventData) + else + icon.onLeftClick(icon, eventData) + end + + icon.selected = false + MineOSInterface.OSDraw() + end + end + + MineOSInterface.mainContainer.dockContainer.addIcon = function(path, window) + local icon = MineOSInterface.mainContainer.dockContainer:addChild(MineOSInterface.icon(1, 2, path, 0x2D2D2D, 0xFFFFFF)) + icon:analyseExtension() + icon:moveBackward() + + icon.eventHandler = dockIconEventHandler + + icon.onLeftClick = function(icon, eventData) + if icon.windows then + for window in pairs(icon.windows) do + window.hidden = false + window:moveToFront() + end + MineOSInterface.OSDraw() + else + MineOSInterface.iconDoubleClick(icon, eventData) + end + end + + icon.onRightClick = function(icon, eventData) + local indexOf = icon:indexOf() + + local menu = MineOSInterface.contextMenu(eventData[3], eventData[4]) + if icon.windows then + menu:addItem(MineOSCore.localization.newWindow).onTouch = function() + MineOSInterface.iconDoubleClick(icon, eventData) + end + menu:addItem(MineOSCore.localization.closeAllWindows).onTouch = function() + for window in pairs(icon.windows) do + window:close() + end + MineOSInterface.OSDraw() + end + end + menu:addItem(MineOSCore.localization.showContainingFolder).onTouch = function() + MineOSInterface.safeLaunch(MineOSPaths.explorer, "-o", fs.path(icon.shortcutPath or icon.path)) + end + menu:addSeparator() + menu:addItem(MineOSCore.localization.moveRight, indexOf >= #MineOSInterface.mainContainer.dockContainer.children - 1).onTouch = function() + moveDockIcon(indexOf, 1) + end + menu:addItem(MineOSCore.localization.moveLeft, indexOf <= 1).onTouch = function() + moveDockIcon(indexOf, -1) + end + menu:addSeparator() + if icon.keepInDock then + if #MineOSInterface.mainContainer.dockContainer.children > 1 then + menu:addItem(MineOSCore.localization.removeFromDock).onTouch = function() + if icon.windows then + icon.keepInDock = nil + else + icon:delete() + MineOSInterface.mainContainer.dockContainer.sort() + end + MineOSInterface.mainContainer.dockContainer.saveToOSSettings() + MineOSInterface.OSDraw() + end + end + else + if icon.windows then + menu:addItem(MineOSCore.localization.keepInDock).onTouch = function() + icon.keepInDock = true + MineOSInterface.mainContainer.dockContainer.saveToOSSettings() + end + end + end + + menu:show() + end + + MineOSInterface.mainContainer.dockContainer.sort() + + return icon + end + + -- Trash + local icon = MineOSInterface.mainContainer.dockContainer.addIcon(MineOSPaths.trash) + icon.launchers.directory = function(icon) + MineOSInterface.safeLaunch(MineOSPaths.explorer, "-o", icon.path) + end + icon:analyseExtension() + icon.image = MineOSInterface.iconsCache.trash + + icon.eventHandler = dockIconEventHandler + + icon.onLeftClick = function(icon, eventData) + MineOSInterface.iconDoubleClick(icon, eventData) + end + + icon.onRightClick = function(icon, eventData) + local menu = MineOSInterface.contextMenu(eventData[3], eventData[4]) + menu:addItem(MineOSCore.localization.emptyTrash).onTouch = function() + local container = MineOSInterface.addUniversalContainer(MineOSInterface.mainContainer, MineOSCore.localization.areYouSure) + + container.layout:addChild(GUI.button(1, 1, 30, 1, 0xE1E1E1, 0x2D2D2D, 0xA, 0x2D2D2D, "OK")).onTouch = function() + for file in fs.list(MineOSPaths.trash) do + fs.remove(MineOSPaths.trash .. file) + end + container:delete() + computer.pushSignal("MineOSCore", "updateFileList") + end + + container.panel.onTouch = function() + container:delete() + MineOSInterface.OSDraw() + end + + MineOSInterface.OSDraw() + end + + menu:show() + end + + for i = 1, #MineOSCore.properties.dockShortcuts do + MineOSInterface.mainContainer.dockContainer.addIcon(MineOSCore.properties.dockShortcuts[i]).keepInDock = true + end + + -- Draw dock drawDock dockDraw cyka заебался искать, блядь + MineOSInterface.mainContainer.dockContainer.draw = function(dockContainer) + local color, currentDockTransparency, currentDockWidth, xPos = MineOSCore.properties.dockColor, dockTransparency, dockContainer.width - 2, dockContainer.x + + for y = dockContainer.y + dockContainer.height - 1, dockContainer.y + dockContainer.height - 4, -1 do + buffer.text(xPos, y, color, "◢", MineOSCore.properties.transparencyEnabled and currentDockTransparency) + buffer.square(xPos + 1, y, currentDockWidth, 1, color, 0xFFFFFF, " ", MineOSCore.properties.transparencyEnabled and currentDockTransparency) + buffer.text(xPos + currentDockWidth + 1, y, color, "◣", MineOSCore.properties.transparencyEnabled and currentDockTransparency) + + currentDockTransparency, currentDockWidth, xPos = currentDockTransparency + 0.08, currentDockWidth - 2, xPos + 1 + if currentDockTransparency > 1 then + currentDockTransparency = 1 + end + end + + GUI.drawContainerContent(dockContainer) + end + + -- Custom windows support + MineOSInterface.mainContainer.windowsContainer = MineOSInterface.mainContainer:addChild(GUI.container(1, 2, 1, 1)) + + -- Main menu + MineOSInterface.mainContainer.menu = MineOSInterface.mainContainer:addChild(GUI.menu(1, 1, MineOSInterface.mainContainer.width, MineOSCore.properties.menuColor, 0x555555, 0x3366CC, 0xFFFFFF)) + local item1 = MineOSInterface.mainContainer.menu:addItem("MineOS", 0x000000) + item1.onTouch = function() + local menu = MineOSInterface.contextMenu(item1.x, item1.y + 1) + + menu:addItem(MineOSCore.localization.updates).onTouch = function() + MineOSInterface.safeLaunch("/MineOS/Applications/AppMarket.app/Main.lua", "updates") + end + + menu:addSeparator() + + menu:addItem(MineOSCore.localization.logout, MineOSCore.properties.protectionMethod == "withoutProtection").onTouch = function() + login() + end + + menu:addItem(MineOSCore.localization.reboot).onTouch = function() + MineOSNetwork.broadcastComputerState(false) + require("computer").shutdown(true) + end + + menu:addItem(MineOSCore.localization.shutdown).onTouch = function() + MineOSNetwork.broadcastComputerState(false) + require("computer").shutdown() + end + + menu:addSeparator() + + menu:addItem(MineOSCore.localization.returnToShell).onTouch = function() + MineOSNetwork.broadcastComputerState(false) + MineOSInterface.mainContainer:stopEventHandling() + MineOSInterface.clearTerminal() + os.exit() + end + + menu:show() + end + + local item2 = MineOSInterface.mainContainer.menu:addItem(MineOSCore.localization.network) + item2.onTouch = function() + local container = MineOSInterface.addUniversalContainer(MineOSInterface.mainContainer, MineOSCore.localization.network) + local insertModemTextBox = container.layout:addChild(GUI.textBox(1, 1, 36, 1, nil, 0x555555, {MineOSCore.localization.networkModemNotAvailable}, 1, 0, 0, true, true)) + + local stateSwitchAndLabel = container.layout:addChild(GUI.switchAndLabel(1, 1, 36, 8, 0x66DB80, 0x2D2D2D, 0xE1E1E1, 0x888888, MineOSCore.localization.networkState .. ":", MineOSCore.properties.network.enabled)) + local signalStrengthSlider = container.layout:addChild(GUI.slider(1, 1, 36, 0x66DB80, 0x2D2D2D, 0xE1E1E1, 0x888888, 0, 512, MineOSCore.properties.network.signalStrength, false, MineOSCore.localization.networkSearchRadius ..": ", "")) + signalStrengthSlider.roundValues = true + signalStrengthSlider.height = 2 + + container.layout:addChild(GUI.object(1, 1, 1, 1)) + + container.layout:addChild(GUI.label(1, 1, container.width, 1, 0xE1E1E1, MineOSCore.localization.networkName):setAlignment(GUI.alignment.horizontal.center, GUI.alignment.vertical.top)) + local networkNameInput = container.layout:addChild(GUI.input(1, 1, 36, 3, 0xE1E1E1, 0x666666, 0x666666, 0xE1E1E1, 0x2D2D2D, MineOSCore.properties.network.name or "")) + + container.layout:addChild(GUI.label(1, 1, container.width, 1, 0xE1E1E1, MineOSCore.localization.networkComputers):setAlignment(GUI.alignment.horizontal.center, GUI.alignment.vertical.top)) + local comboBox = container.layout:addChild(GUI.comboBox(1, 1, 36, 3, 0xE1E1E1, 0x2D2D2D, 0x444444, 0x999999)) + local allowReadAndWriteSwitchAndLabel = container.layout:addChild(GUI.switchAndLabel(1, 1, 36, 8, 0x66DB80, 0x2D2D2D, 0xE1E1E1, 0x888888, MineOSCore.localization.networkAllowReadAndWrite .. ":", false)) + local noPCDetectedLabel = container.layout:addChild(GUI.label(1, 1, container.width, 1, 0x888888, MineOSCore.localization.networkComputersNotFound):setAlignment(GUI.alignment.horizontal.center, GUI.alignment.vertical.top)) + + local function check() + local modemAvailable = component.isAvailable("modem") + for i = 3, #container.layout.children do + container.layout.children[i].hidden = not modemAvailable + end + insertModemTextBox.hidden = modemAvailable + + if modemAvailable then + local stateSwitchAndLabelIndexOf = stateSwitchAndLabel:indexOf() + for i = 3, #container.layout.children do + if i ~= stateSwitchAndLabelIndexOf then + container.layout.children[i].hidden = not stateSwitchAndLabel.switch.state + end + end + + if stateSwitchAndLabel.switch.state then + signalStrengthSlider.hidden = not MineOSNetwork.modemProxy.isWireless() + + comboBox:clear() + for proxy, path in fs.mounts() do + if proxy.network then + local item = comboBox:addItem(MineOSNetwork.getProxyName(proxy)) + item.proxyAddress = proxy.address + item.onTouch = function() + allowReadAndWriteSwitchAndLabel.switch:setState(MineOSCore.properties.network.users[item.proxyAddress].allowReadAndWrite) + end + end + end + + noPCDetectedLabel.hidden = comboBox:count() > 0 + allowReadAndWriteSwitchAndLabel.hidden = not noPCDetectedLabel.hidden + comboBox.hidden = not noPCDetectedLabel.hidden + + if noPCDetectedLabel.hidden then + comboBox:getItem(comboBox.selectedItem).onTouch() + end + end + end + + MineOSInterface.OSDraw() + end + + networkNameInput.onInputFinished = function() + if #networkNameInput.text > 0 then + MineOSCore.properties.network.name = networkNameInput.text + MineOSCore.saveProperties() + + MineOSNetwork.broadcastComputerState(MineOSCore.properties.network.enabled) + end + end + + signalStrengthSlider.onValueChanged = function() + MineOSCore.properties.network.signalStrength = math.floor(signalStrengthSlider.value) + MineOSCore.saveProperties() + end + + stateSwitchAndLabel.switch.onStateChanged = function() + if stateSwitchAndLabel.switch.state then + MineOSNetwork.enable() + else + MineOSNetwork.disable() + end + + check() + end + + allowReadAndWriteSwitchAndLabel.switch.onStateChanged = function() + MineOSCore.properties.network.users[comboBox:getItem(comboBox.selectedItem).proxyAddress].allowReadAndWrite = allowReadAndWriteSwitchAndLabel.switch.state + MineOSCore.saveProperties() + end + + container.panel.eventHandler = function(mainContainer, object, eventData) + if eventData[1] == "touch" then + container:delete() + MineOSInterface.OSDraw() + elseif (eventData[1] == "component_added" or eventData[1] == "component_removed") and eventData[3] == "modem" then + check() + elseif eventData[1] == "MineOSNetwork" and eventData[2] == "updateProxyList" then + check() + end + end + + check() + end + + local item3 = MineOSInterface.mainContainer.menu:addItem(MineOSCore.localization.settings) + item3.onTouch = function() + local menu = MineOSInterface.contextMenu(item3.x, item3.y + 1) + + menu:addItem(MineOSCore.localization.screenResolution).onTouch = function() + local container = MineOSInterface.addUniversalContainer(MineOSInterface.mainContainer, MineOSCore.localization.screenResolution) + + local widthTextBox = container.layout:addChild(GUI.input(1, 1, 36, 3, 0xE1E1E1, 0x666666, 0x666666, 0xE1E1E1, 0x2D2D2D, tostring(MineOSCore.properties.resolution and MineOSCore.properties.resolution[1] or 160), "Width", true)) + widthTextBox.validator = function(text) + local number = tonumber(text) + if number then return number >= 1 and number <= 160 end + end + + local heightTextBox = container.layout:addChild(GUI.input(1, 1, 36, 3, 0xE1E1E1, 0x666666, 0x666666, 0xE1E1E1, 0x2D2D2D, tostring(MineOSCore.properties.resolution and MineOSCore.properties.resolution[2] or 50), "Height", true)) + heightTextBox.validator = function(text) + local number = tonumber(text) + if number then return number >= 1 and number <= 50 end + end + + container.panel.eventHandler = function(mainContainer, object, eventData) + if eventData[1] == "touch" then + container:delete() + MineOSCore.properties.resolution = {tonumber(widthTextBox.text), tonumber(heightTextBox.text)} + MineOSCore.saveProperties() + changeResolution() + changeWallpaper() + MineOSInterface.mainContainer.updateFileListAndDraw() + end + end + end + + menu:addSeparator() + + menu:addItem(MineOSCore.localization.wallpaper).onTouch = function() + local container = MineOSInterface.addUniversalContainer(MineOSInterface.mainContainer, MineOSCore.localization.wallpaper) + + local filesystemChooser = container.layout:addChild(GUI.filesystemChooser(1, 1, 36, 3, 0xE1E1E1, 0x2D2D2D, 0x444444, 0x999999, MineOSCore.properties.wallpaper, MineOSCore.localization.open, MineOSCore.localization.cancel, MineOSCore.localization.wallpaperPath, "/")) + filesystemChooser:addExtensionFilter(".pic") + filesystemChooser.onSubmit = function(path) + MineOSCore.properties.wallpaper = path + MineOSCore.saveProperties() + changeWallpaper() + + MineOSInterface.OSDraw() + end + + local comboBox = container.layout:addChild(GUI.comboBox(1, 1, 36, 3, 0xE1E1E1, 0x2D2D2D, 0x444444, 0x999999)) + comboBox.selectedItem = MineOSCore.properties.wallpaperMode or 1 + comboBox:addItem(MineOSCore.localization.wallpaperModeStretch) + comboBox:addItem(MineOSCore.localization.wallpaperModeCenter) + + local switch = container.layout:addChild(GUI.switchAndLabel(1, 1, 36, 8, 0x66DB80, 0x2D2D2D, 0xE1E1E1, 0xE1E1E1, MineOSCore.localization.wallpaperEnabled .. ":", MineOSCore.properties.wallpaperEnabled)).switch + switch.onStateChanged = function() + MineOSCore.properties.wallpaperEnabled = switch.state + MineOSCore.saveProperties() + changeWallpaper() + + MineOSInterface.OSDraw() + end + + container.layout:addChild(GUI.textBox(1, 1, 36, 1, nil, 0x555555, {MineOSCore.localization.wallpaperSwitchInfo}, 1, 0, 0, true, true)) + + local slider = container.layout:addChild(GUI.slider(1, 1, 36, 0x66DB80, 0x2D2D2D, 0xE1E1E1, 0x888888, 0, 100, MineOSCore.properties.wallpaperBrightness * 100, false, MineOSCore.localization.wallpaperBrightness .. ": ", "%")) + slider.roundValues = true + slider.onValueChanged = function() + MineOSCore.properties.wallpaperBrightness = slider.value / 100 + MineOSCore.saveProperties() + changeWallpaper() + + MineOSInterface.OSDraw() + end + container.layout:addChild(GUI.object(1, 1, 1, 1)) + + comboBox.onItemSelected = function() + MineOSCore.properties.wallpaperMode = comboBox.selectedItem + MineOSCore.saveProperties() + changeWallpaper() + + MineOSInterface.OSDraw() + end + end + menu:addItem(MineOSCore.localization.screensaver).onTouch = function() + local container = MineOSInterface.addUniversalContainer(MineOSInterface.mainContainer, MineOSCore.localization.screensaver) + + local comboBox = container.layout:addChild(GUI.comboBox(1, 1, 36, 3, 0xE1E1E1, 0x2D2D2D, 0x444444, 0x999999)) + local fileList = fs.sortedList(screensaversPath, "name", false) + for i = 1, #fileList do + comboBox:addItem(fs.hideExtension(fileList[i])) + if MineOSCore.properties.screensaver == fileList[i] then + comboBox.selectedItem = i + end + end + local switch = container.layout:addChild(GUI.switchAndLabel(1, 1, 36, 8, 0x66DB80, 0x2D2D2D, 0xE1E1E1, 0xE1E1E1, MineOSCore.localization.screensaverEnabled .. ":", MineOSCore.properties.screensaverEnabled)).switch + local slider = container.layout:addChild(GUI.slider(1, 1, 36, 0x66DB80, 0x2D2D2D, 0xE1E1E1, 0x888888, 1, 80, MineOSCore.properties.screensaverDelay, false, MineOSCore.localization.screensaverDelay .. ": ", "")) + + container.panel.eventHandler = function(mainContainer, object, eventData) + if eventData[1] == "touch" then + container:delete() + MineOSInterface.OSDraw() + + MineOSCore.properties.screensaverEnabled = switch.state + MineOSCore.properties.screensaver = fileList[comboBox.selectedItem] + MineOSCore.properties.screensaverDelay = slider.value + + MineOSCore.saveProperties() + end + end + + MineOSInterface.OSDraw() + end + + menu:addItem(MineOSCore.localization.colorScheme).onTouch = function() + local container = MineOSInterface.addUniversalContainer(MineOSInterface.mainContainer, MineOSCore.localization.colorScheme) + + local backgroundColorSelector = container.layout:addChild(GUI.colorSelector(1, 1, 36, 3, MineOSCore.properties.backgroundColor, MineOSCore.localization.backgroundColor)) + local menuColorSelector = container.layout:addChild(GUI.colorSelector(1, 1, 36, 3, MineOSCore.properties.menuColor, MineOSCore.localization.menuColor)) + local dockColorSelector = container.layout:addChild(GUI.colorSelector(1, 1, 36, 3, MineOSCore.properties.dockColor, MineOSCore.localization.dockColor)) + + local switch = container.layout:addChild(GUI.switchAndLabel(1, 1, 36, 8, 0x66DB80, 0x2D2D2D, 0xE1E1E1, 0xE1E1E1, MineOSCore.localization.transparencyEnabled .. ":", MineOSCore.properties.transparencyEnabled)).switch + switch.onStateChanged = function() + MineOSCore.properties.transparencyEnabled = switch.state + MineOSCore.saveProperties() + MineOSInterface.mainContainer.menu.colors.transparency = MineOSCore.properties.transparencyEnabled and menuTransparency + container.panel.colors.background = switch.state and 0x0 or (MineOSCore.properties.backgroundColor) + container.panel.colors.transparency = switch.state and 0.2 + + MineOSInterface.OSDraw() + end + container.layout:addChild(GUI.textBox(1, 1, 36, 1, nil, 0x555555, {MineOSCore.localization.transparencySwitchInfo}, 1, 0, 0, true, true)) + + -- Шоб рисовалось в реальном времени + backgroundColorSelector.onTouch = function() + MineOSCore.properties.backgroundColor = backgroundColorSelector.color + MineOSCore.properties.menuColor = menuColorSelector.color + MineOSCore.properties.dockColor = dockColorSelector.color + MineOSInterface.mainContainer.menu.colors.default.background = MineOSCore.properties.menuColor + + MineOSInterface.OSDraw() + end + menuColorSelector.onTouch = backgroundColorSelector.onTouch + dockColorSelector.onTouch = backgroundColorSelector.onTouch + + container.panel.eventHandler = function(mainContainer, object, eventData) + if eventData[1] == "touch" then + container:delete() + MineOSInterface.OSDraw() + + MineOSCore.saveProperties() + end + end + end + + menu:addItem(MineOSCore.localization.iconProperties).onTouch = function() + local container = MineOSInterface.addUniversalContainer(MineOSInterface.mainContainer, MineOSCore.localization.iconProperties) + + local showExtensionSwitch = container.layout:addChild(GUI.switchAndLabel(1, 1, 36, 8, 0x66DB80, 0x2D2D2D, 0xE1E1E1, 0x888888, MineOSCore.localization.showExtension .. ":", MineOSCore.properties.showExtension)).switch + local showHiddenFilesSwitch = container.layout:addChild(GUI.switchAndLabel(1, 1, 36, 8, 0x66DB80, 0x2D2D2D, 0xE1E1E1, 0x888888, MineOSCore.localization.showHiddenFiles .. ":", MineOSCore.properties.showHiddenFiles)).switch + local showApplicationIconsSwitch = container.layout:addChild(GUI.switchAndLabel(1, 1, 36, 8, 0x66DB80, 0x2D2D2D, 0xE1E1E1, 0x888888, MineOSCore.localization.showApplicationIcons .. ":", MineOSCore.properties.showApplicationIcons)).switch + + container.layout:addChild(GUI.label(1, 1, container.width, 1, 0xE1E1E1, MineOSCore.localization.sizeOfIcons):setAlignment(GUI.alignment.horizontal.center, GUI.alignment.vertical.top)) + + local iconWidthSlider = container.layout:addChild(GUI.slider(1, 1, 36, 0x66DB80, 0x2D2D2D, 0xE1E1E1, 0x888888, 8, 16, MineOSCore.properties.iconWidth, false, MineOSCore.localization.byHorizontal .. ": ", "")) + local iconHeightSlider = container.layout:addChild(GUI.slider(1, 1, 36, 0x66DB80, 0x2D2D2D, 0xE1E1E1, 0x888888, 6, 16, MineOSCore.properties.iconHeight, false, MineOSCore.localization.byVertical .. ": ", "")) + + container.layout:addChild(GUI.object(1, 1, 1, 0)) + container.layout:addChild(GUI.label(1, 1, container.width, 1, 0xE1E1E1, MineOSCore.localization.spaceBetweenIcons):setAlignment(GUI.alignment.horizontal.center, GUI.alignment.vertical.top)) + + local iconHorizontalSpaceBetweenSlider = container.layout:addChild(GUI.slider(1, 1, 36, 0x66DB80, 0x2D2D2D, 0xE1E1E1, 0x888888, 0, 5, MineOSCore.properties.iconHorizontalSpaceBetween, false, MineOSCore.localization.byHorizontal .. ": ", "")) + local iconVerticalSpaceBetweenSlider = container.layout:addChild(GUI.slider(1, 1, 36, 0x66DB80, 0x2D2D2D, 0xE1E1E1, 0x888888, 0, 5, MineOSCore.properties.iconVerticalSpaceBetween, false, MineOSCore.localization.byVertical .. ": ", "")) + + iconHorizontalSpaceBetweenSlider.roundValues, iconVerticalSpaceBetweenSlider.roundValues = true, true + iconWidthSlider.roundValues, iconHeightSlider.roundValues = true, true + + iconWidthSlider.onValueChanged = function() + MineOSInterface.setIconProperties(math.floor(iconWidthSlider.value), math.floor(iconHeightSlider.value), MineOSCore.properties.iconHorizontalSpaceBetween, MineOSCore.properties.iconVerticalSpaceBetween) + end + iconHeightSlider.onValueChanged = iconWidthSlider.onValueChanged + + iconHorizontalSpaceBetweenSlider.onValueChanged = function() + MineOSInterface.setIconProperties(MineOSCore.properties.iconWidth, MineOSCore.properties.iconHeight, math.floor(iconHorizontalSpaceBetweenSlider.value), math.floor(iconVerticalSpaceBetweenSlider.value)) + end + iconVerticalSpaceBetweenSlider.onValueChanged = iconHorizontalSpaceBetweenSlider.onValueChanged + + showExtensionSwitch.onStateChanged = function() + MineOSCore.properties.showExtension = showExtensionSwitch.state + MineOSCore.properties.showHiddenFiles = showHiddenFilesSwitch.state + MineOSCore.properties.showApplicationIcons = showApplicationIconsSwitch.state + MineOSCore.saveProperties() + + computer.pushSignal("MineOSCore", "updateFileList") + end + showHiddenFilesSwitch.onStateChanged, showApplicationIconsSwitch.onStateChanged = showExtensionSwitch.onStateChanged, showExtensionSwitch.onStateChanged + end + + menu:addSeparator() + + menu:addItem(MineOSCore.localization.setProtectionMethod).onTouch = function() + setProtectionMethod() + end + + menu:show() + end + + MineOSInterface.mainContainer.menuLayout = MineOSInterface.mainContainer:addChild(GUI.layout(1, 1, 1, 1, 1, 1)) + MineOSInterface.mainContainer.menuLayout:setCellSpacing(1, 1, 0) + MineOSInterface.mainContainer.menuLayout:setCellDirection(1, 1, GUI.directions.horizontal) + MineOSInterface.mainContainer.menuLayout:setCellAlignment(1, 1, GUI.alignment.horizontal.right, GUI.alignment.vertical.top) + + local dateButton = MineOSInterface.addMenuWidget(GUI.button(1, 1, 1, 1, nil, 0x0, 0x3366CC, 0xFFFFFF, " ")) + dateButton.switchMode = true + + dateButton.onTouch = function() + local menu = MineOSInterface.contextMenu(dateButton.x, dateButton.y + 1) + for i = -12, 12 do + menu:addItem("GMT" .. (i >= 0 and "+" or "") .. i).onTouch = function() + MineOSCore.properties.timezone = i + MineOSCore.saveProperties() + + MineOSCore.OSUpdateTimezone() + MineOSCore.OSUpdateDate() + MineOSInterface.OSDraw() + end + end + menu:show() + dateButton.pressed = false + MineOSInterface.OSDraw() + end + + MineOSCore.OSUpdateTimezone = function() + local timezone = MineOSCore.properties.timezone or 0 + timezoneCorrection = timezone * 3600 + end + + MineOSCore.OSUpdateDate = function() + if not realTimestamp then + local name = MineOSPaths.system .. "/Timestamp.tmp" + local file = io.open(name, "w") + file:close() + realTimestamp = math.floor(fs.lastModified(name) / 1000) + fs.remove(name) + end + + local firstPart, month, secondPart = os.date( + "%d %b %Y %T", + realTimestamp + computerDateUptime - computerUptimeOnBoot + timezoneCorrection + ):match("(%d+%s)(%a+)(.+)") + + dateButton.text = firstPart .. (MineOSCore.localization.months[month] or "monthNotAvailable:" .. month) .. secondPart + dateButton.width = unicode.len(dateButton.text) + 2 + end + + MineOSInterface.OSDraw = function(force) + MineOSInterface.mainContainer:draw() + buffer.draw(force) + end + + MineOSInterface.mainContainer.updateFileListAndDraw = function(forceRedraw) + MineOSInterface.mainContainer.iconField:updateFileList() + MineOSInterface.OSDraw(forceRedraw) + end + + MineOSInterface.mainContainer.eventHandler = function(mainContainer, object, eventData) + if eventData[1] == "key_down" then + local windowsCount = #MineOSInterface.mainContainer.windowsContainer.children + -- Ctrl or CMD + if windowsCount > 0 and not eventData.lastWindowHandled and (keyboard.isKeyDown(29) or keyboard.isKeyDown(219)) then + -- W + if eventData[4] == 17 then + eventData.lastWindowHandled = true + MineOSInterface.mainContainer.windowsContainer.children[windowsCount]:close() + + mainContainer:draw() + buffer.draw() + -- H + elseif eventData[4] == 35 then + eventData.lastWindowHandled = true + + local lastUnhiddenWindowIndex = 1 + for i = 1, #MineOSInterface.mainContainer.windowsContainer.children do + if not MineOSInterface.mainContainer.windowsContainer.children[i].hidden then + lastUnhiddenWindowIndex = i + end + end + MineOSInterface.mainContainer.windowsContainer.children[lastUnhiddenWindowIndex]:minimize() + + mainContainer:draw() + buffer.draw() + end + end + elseif eventData[1] == "MineOSCore" then + if eventData[2] == "updateFileList" then + MineOSInterface.mainContainer.updateFileListAndDraw() + elseif eventData[2] == "updateFileListAndBufferTrueRedraw" then + MineOSInterface.mainContainer.updateFileListAndDraw(true) + elseif eventData[2] == "updateWallpaper" then + changeWallpaper() + MineOSInterface.OSDraw() + end + elseif eventData[1] == "MineOSNetwork" then + if eventData[2] == "accessDenied" then + GUI.error(MineOSCore.localization.networkAccessDenied) + elseif eventData[2] == "timeout" then + GUI.error(MineOSCore.localization.networkTimeout) + end + end + + local computerUptime = computer.uptime() + + if computerUptime - computerDateUptime >= 1 then + MineOSCore.OSUpdateDate() + MineOSInterface.OSDraw() + computerDateUptime = computerUptime + end + + if MineOSCore.properties.screensaverEnabled then + if eventData[1] then + screensaverUptime = computer.uptime() + end + + if computerUptime - screensaverUptime >= MineOSCore.properties.screensaverDelay then + if fs.exists(screensaversPath .. MineOSCore.properties.screensaver) then + MineOSInterface.safeLaunch(screensaversPath .. MineOSCore.properties.screensaver) + MineOSInterface.OSDraw(true) + end + + screensaverUptime = computer.uptime() + end + end + end +end + +---------------------------------------------- Сама ОС ------------------------------------------------------------------------ + +MineOSCore.localization = table.fromFile(MineOSPaths.localizationFiles .. MineOSCore.properties.language .. ".lang") + +createOSWindow() +changeResolution() +changeWallpaper() +MineOSCore.OSUpdateTimezone() +MineOSCore.OSUpdateDate() +login() + +MineOSNetwork.update() + +while true do + local success, path, line, traceback = MineOSCore.call( + MineOSInterface.mainContainer.startEventHandling, + MineOSInterface.mainContainer, + 0 + ) + if success then + break + else + createOSWindow() + changeResolution() + changeWallpaper() + MineOSCore.OSUpdateDate() + MineOSInterface.mainContainer.updateFileListAndDraw() + + MineOSInterface.showErrorWindow(path, line, traceback) + + MineOSInterface.OSDraw() + end +end diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/OSEnergyTurrets.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/OSEnergyTurrets.lua new file mode 100644 index 00000000..71c1bd2c --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/OSEnergyTurrets.lua @@ -0,0 +1,75 @@ +local component = require("component") +local event = require("event") + +local rad180 = math.rad(180) +local playerHitpointYOffset = 1.2 +local shaftSingleExtensionValue = 0.5 +local defaultShaftLength = 2 + +local turrets = { + ["4b1229f4-640c-4288-8055-3fc1d9761b4c"] = { + x = -221.5, + y = 75.5, + z = 324.5 + }, + ["c7646649-292c-49e5-8c4a-4549511cce87"] = { + x = -220.5, + y = 75.5, + z = 324.5 + }, + ["86a3b628-8f8b-42e5-b890-2ec6c2065d36"] = { + x = -218.5, + y = 75.5, + z = 324.5 + }, + ["bdb164ab-af71-4eb0-8fe0-cdd0fc40364e"] = { + x = -216.5, + y = 75.5, + z = 324.5 + }, + ["a03228e7-0041-4909-acb0-8f78d7ddfc24"] = { + x = -215.5, + y = 75.5, + z = 324.5 + }, +} +for address in pairs(turrets) do + turrets[address].proxy = component.proxy(address) + turrets[address].proxy.extendShaft(defaultShaftLength) +end + +local scanners = {} +for address in component.list("os_entdetector") do + table.insert(scanners, component.proxy(address)) +end + +while true do + local entities = {} + for i = 1, #scanners do + local localEntities = scanners[i].scanPlayers(512) + for j = 1, #localEntities do + entities[localEntities[j].name] = localEntities[j] + end + end + + for name, data in pairs(entities) do + for _, turret in pairs(turrets) do + local dx = data.x - turret.x + local dy = (data.y + playerHitpointYOffset) - (turret.y + turret.proxy.getShaftLength() * shaftSingleExtensionValue) + local dz = data.z - turret.z + + turret.proxy.moveToRadians( + dz > 0 and rad180 - math.atan(dx / dz) or -math.atan(dx / dz), + dz > 0 and math.atan(dy / dz) or -math.atan(dy / dz) + ) + + if turret.proxy.isReady() and turret.proxy.isOnTarget() then + turret.proxy.fire() + end + end + + break + end + + os.sleep(0.05) +end \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/Tanks/1.pic b/640cd89f-8e29-4b66-a0eb-7680c33760b4/Tanks/1.pic new file mode 100644 index 0000000000000000000000000000000000000000..b86544313b0529d05cdc9c392263f776445b1fea GIT binary patch literal 200 zcmXwz!4bkR3+Nn8ZHpckbOu;S#m-Z|~~ez{$XDSxT@{6K;N z1(Z_Fmv4Vh-I%w}js$#pcH97DZ?D6eukXXEujd(3vWRXyBGeqCol&APP+}j*uqTSo XV?e04-3rMn3RcrX(OmkEN07*W?A|j{ literal 0 HcmV?d00001 diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/Tanks/2.pic b/640cd89f-8e29-4b66-a0eb-7680c33760b4/Tanks/2.pic new file mode 100644 index 0000000000000000000000000000000000000000..08d326bc9ca0209f6a8a245e592b781238a02c9f GIT binary patch literal 194 zcmXwz!41MN3`O&8H|buLkT|gtSJWi}S)eBn7rKiX1b(da5b68*+3{sLpYm+-OU3II z2_}$2P-egHtXCMax8C|fI&Po90Q_+n@b(%sd%cCMloQvsjgu=Bleg!Hu`#Hqd3^^K Z8g(C$MnvO9YLoCR<+G^z7kygT`U650FPs1X literal 0 HcmV?d00001 diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/Tanks/3.pic b/640cd89f-8e29-4b66-a0eb-7680c33760b4/Tanks/3.pic new file mode 100644 index 0000000000000000000000000000000000000000..5f1ea635f5d66ced09e6dc4ed471daeadefa5270 GIT binary patch literal 200 zcmXwz!4bkh2u1e;f$b|Z&UmsVPkOKklQfW4yrv zh-lb*ONvt;&Yb{)>Gw#mEoXvlMne3b5Vy@|HrQQCje*@~8MT?NhgocHX~3;%A(7fT O& limit then break end + if i % 2 == 0 then color = 0xFFFFFF; color2 = 0x262626 else color = 0xEEEEEE; color2 = 0x262626 end + if i == selectedObject then color = ecs.colors.green; color2 = 0xFFFFFF end + + if autorunObjects[i] then + + ecs.square(x, yPos, dataWidth - 1, 1, color) + + if autorunObjects[i].enabled then + ecs.colorTextWithBack(x + 3, yPos, ecs.colors.blue, color, "✔") + else + ecs.colorTextWithBack(x + 3, yPos, ecs.colors.red, color, "❌") + end + + gpu.setBackground(color) + gpu.setForeground(color2) + gpu.set(x + 9, yPos, ecs.stringLimit("start", autorunObjects[i].path, 37)) + + gpu.set(x + 48, yPos, autorunObjects[i].size .. " КБ") + + newObj("List", i, x, yPos, x + dataWidth - 1, yPos) + + yPos = yPos + 1 + end + end +end + +local function drawWindow() + local xPos, yPos + + ecs.square(x, y, width, height, 0xDDDDDD) + + xPos = x + 3 + yPos = y + 4 + + ecs.colorText(xPos, yPos, 0x262626, "Эти объекты будут запускаться автоматически при загрузке:") + yPos = yPos + 2 + + ecs.square(x, y, width, 3, 0xCCCCCC) + + ecs.centerText("x", y + 1, "Менеджер автозагрузки") + + drawFiles(x + 3, y + 6) + + yPos = y + height - 7 + ecs.colorTextWithBack(xPos, yPos, 0x262626, 0xDDDDDD, "Чтобы отключить загрузку файла, снимите галочку рядом с именем") + yPos = yPos + 1 + gpu.set(xPos, yPos, "программы. Приоритет загузки снижается сверху вниз.") + yPos = yPos + 2 + + local name + name = "+"; newObj("Buttons", name, ecs.drawAdaptiveButton(xPos, yPos, 2, 1, name, 0xFFFFFF, 0x262626)); xPos = obj["Buttons"][name][3] + 2 + name = "-"; newObj("Buttons", name, ecs.drawAdaptiveButton(xPos, yPos, 2, 1, name, 0xFFFFFF, 0x262626)); xPos = obj["Buttons"][name][3] + 2 + + name = "Выше"; newObj("Buttons", name, ecs.drawAdaptiveButton(xPos, yPos, 2, 1, name, 0xFFFFFF, 0x262626)); xPos = obj["Buttons"][name][3] + 2 + name = "Ниже"; newObj("Buttons", name, ecs.drawAdaptiveButton(xPos, yPos, 2, 1, name, 0xFFFFFF, 0x262626)); xPos = obj["Buttons"][name][3] + 2 + -- if fs.isAutorunEnabled() then + -- name = "Выключить автозапуск" + -- else + -- name = "Включить автозапуск " + -- end + -- newObj("Buttons", name, ecs.drawAdaptiveButton(xPos, yPos, 2, 1, name, 0xAAAAAA, 0xFFFFFF)); xPos = obj["Buttons"][name][3] + 2 + + xPos = x + width - 12 + name = "Выход"; newObj("Buttons", name, ecs.drawAdaptiveButton(xPos, yPos, 2, 1, name, 0x888888, 0xFFFFFF)) +end + +------------------------------------------ Программа --------------------------------------------------------------- + +loadList() +drawWindow() + +while true do + local e = {event.pull()} + if e[1] == "touch" then + --if obj["List"] and #obj["List"] > 0 then + for key in pairs(obj["List"]) do + if ecs.clickedAtArea(e[3], e[4], obj["List"][key][1], obj["List"][key][2], obj["List"][key][3], obj["List"][key][4]) then + if selectedObject ~= key then + selectedObject = key + drawFiles(x + 3, y + 6) + else + if e[3] >= obj["List"][key][1] + 2 and e[3] <= obj["List"][key][1] + 4 then + autorunObjects[key].enabled = not autorunObjects[key].enabled + drawFiles(x + 3, y + 6) + saveList() + saveAutorun() + end + end + break + end + end + --end + + for key in pairs(obj["Buttons"]) do + if ecs.clickedAtArea(e[3], e[4], obj["Buttons"][key][1], obj["Buttons"][key][2], obj["Buttons"][key][3], obj["Buttons"][key][4]) then + ecs.drawAdaptiveButton(obj["Buttons"][key][1], obj["Buttons"][key][2], 2, 1, key, ecs.colors.blue, 0xFFFFFF) + os.sleep(0.2) + ecs.drawAdaptiveButton(obj["Buttons"][key][1], obj["Buttons"][key][2], 2, 1, key, 0xFFFFFF, 0x262626) + + if key == "-" then + table.remove(autorunObjects, selectedObject) + if drawListFrom == selectedObject then drawListFrom = 1 end + drawFiles(x + 3, y + 6) + saveList() + saveAutorun() + elseif key == "+" then + local data = ecs.universalWindow("auto", "auto", 36, 0xeeeeee, true, {"EmptyLine"}, {"CenterText", 0x880000, "Добавить новый файл"}, {"EmptyLine"}, {"Input", 0x262626, 0x880000, "Путь к файлу"}, {"EmptyLine"}, {"Button", {0x888888, 0xffffff, "Добавить"}, {0xAAAAAA, 0xffffff, "Отмена"}}) + if data[2] == "Добавить" then + if fs.exists(data[1]) then + local cyka = false + for i = 1, #autorunObjects do + if autorunObjects[i].path == data[1] then cyka = true end + end + if not cyka then + table.insert(autorunObjects, { path = data[1], enabled = true, size = math.ceil(fs.size(data[1]) / 1024) }) + drawFiles(x + 3, y + 6) + saveList() + saveAutorun() + else + ecs.error("Файл \"" .. data[1] .. "\" уже есть в этом списке!") + end + else + ecs.error("Файл \"" .. data[1] .. "\" не существует!") + end + end + elseif key == "Выше" then + if selectedObject > 1 then + local cyka = autorunObjects[selectedObject] + table.remove(autorunObjects, selectedObject) + table.insert(autorunObjects, selectedObject - 1, cyka) + selectedObject = selectedObject - 1 + drawFiles(x + 3, y + 6) + saveList() + saveAutorun() + end + elseif key == "Ниже" then + if selectedObject < #autorunObjects then + local cyka = autorunObjects[selectedObject] + table.remove(autorunObjects, selectedObject) + table.insert(autorunObjects, selectedObject + 1, cyka) + selectedObject = selectedObject + 1 + drawFiles(x + 3, y + 6) + saveList() + saveAutorun() + end + elseif key == "Выход" then + ecs.drawOldPixels(oldPixels) + return + -- elseif key == "Включить автозапуск " then + -- fs.setAutorunEnabled(true) + -- drawWindow() + -- elseif key == "Выключить автозапуск" then + -- fs.setAutorunEnabled(false) + -- drawWindow() + end + + break + end + end + + elseif e[1] == "scroll" then + if e[5] == 1 then + if drawListFrom > 1 then drawListFrom = drawListFrom - 1; drawFiles(x + 3, y + 6) end + else + if drawListFrom < #autorunObjects then drawListFrom = drawListFrom + 1; drawFiles(x + 3, y + 6) end + end + end +end + + + + + + + + + + + + + diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/address.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/address.lua new file mode 100755 index 00000000..c05461d4 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/address.lua @@ -0,0 +1,2 @@ +local computer = require("computer") +io.write(computer.address(),"\n") diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/alias.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/alias.lua new file mode 100755 index 00000000..a9594a44 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/alias.lua @@ -0,0 +1,62 @@ +local shell = require("shell") +local args, options = shell.parse(...) + +local ec, error_prefix = 0, "alias:" + +if options.help then + print(string.format("Usage: alias: [name[=value] ... ]", cmd_name)) + return +end + +local function validAliasName(k) + return k:match("[/%$`=|&;%(%)<> \t]") == nil +end + +local function setAlias(k, v) + if not validAliasName(k) then + io.stderr:write(string.format("%s `%s': invalid alias name\n", error_prefix, k)) + else + shell.setAlias(k, v) + end +end + +local function printAlias(k) + local v = shell.getAlias(k) + if not v then + io.stderr:write(string.format("%s %s: not found\n", error_prefix, k)) + ec = 1 + else + io.write(string.format("alias %s='%s'\n", k, v)) + end +end + +local function splitPair(arg) + local matchBegin, matchEnd = arg:find("=") + if matchBegin == nil or matchBegin == 1 then + return arg + else + return arg:sub(1, matchBegin - 1), arg:sub(matchEnd + 1) + end +end + +local function handlePair(k, v) + if v then + return setAlias(k, v) + else + return printAlias(k) + end +end + +if not next(args) then -- no args + -- print all aliases + for k,v in shell.aliases() do + print(string.format("alias %s='%s'", k, v)) + end +else + for k,v in pairs(args) do + checkArg(1,v,"string") + handlePair(splitPair(v)) + end +end + +return ec diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/cat.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/cat.lua new file mode 100755 index 00000000..6a943ab7 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/cat.lua @@ -0,0 +1,37 @@ +local shell = require("shell") +local fs = require("filesystem") + +local args = shell.parse(...) +local ec = 0 +if #args == 0 then + args = {"-"} +end + +for i = 1, #args do + local arg = args[i] + if fs.isDirectory(arg) then + io.stderr:write(string.format('cat %s: Is a directory\n', arg)) + ec = 1 + else + local file, reason + if args[i] == "-" then + file, reason = io.stdin, "missing stdin" + else + file, reason = io.open(shell.resolve(args[i])) + end + if not file then + io.stderr:write(string.format("cat: %s: %s\n", args[i], tostring(reason))) + ec = 1 + else + repeat + local line = file:read("*L") + if line then + io.write(line) + end + until not line + file:close() + end + end +end + +return ec diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/cd.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/cd.lua new file mode 100755 index 00000000..44236919 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/cd.lua @@ -0,0 +1,51 @@ +local shell = require("shell") +local fs = require("filesystem") + +local args, ops = shell.parse(...) +local path = nil +local verbose = false + +if ops.help then + print( +[[Usage cd [dir] +For more options, run: man cd]]) + return +end + +if #args == 0 then + local home = os.getenv("HOME") + if not home then + io.stderr:write("cd: HOME not set\n") + return 1 + end + path = home +elseif args[1] == '-' then + verbose = true + local oldpwd = os.getenv("OLDPWD"); + if not oldpwd then + io.stderr:write("cd: OLDPWD not set\n") + return 1 + end + path = oldpwd +else + path = args[1] +end + +local resolved = shell.resolve(path) +if not fs.exists(resolved) then + io.stderr:write("cd: ",path,": No such file or directory\n") + return 1 +end + +path = resolved +local oldpwd = shell.getWorkingDirectory() +local result, reason = shell.setWorkingDirectory(path) +if not result then + io.stderr:write("cd: ", path, ": ", reason) + return 1 +else + os.setenv("OLDPWD", oldpwd) +end +if verbose then + os.execute("pwd") +end diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/clear.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/clear.lua new file mode 100755 index 00000000..19f6961f --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/clear.lua @@ -0,0 +1,8 @@ + +local gpu = require("component").gpu +gpu.setBackground(0x1B1B1B) +gpu.setForeground(0xEEEEEE) + +local width, height = gpu.getResolution() +gpu.fill(1, 1, width, height, " ") +require("term").setCursor(1, 1) \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/components.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/components.lua new file mode 100755 index 00000000..7039f406 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/components.lua @@ -0,0 +1,52 @@ +local component = require("component") +local shell = require("shell") +local text = require("text") + +local args, options = shell.parse(...) +local count = tonumber(options.limit) or math.huge + +local components = {} +local padTo = 1 + +if #args == 0 then -- get all components if no filters given. + args[1] = "" +end +for _, filter in ipairs(args) do + for address, name in component.list(filter) do + if name:len() > padTo then + padTo = name:len() + 2 + end + components[address] = name + end +end + +padTo = padTo + 8 - padTo % 8 +for address, name in pairs(components) do + io.write(text.padRight(name, padTo) .. address .. '\n') + + if options.l then + local proxy = component.proxy(address) + local padTo = 1 + local methods = {} + for name, member in pairs(proxy) do + if type(member) == "table" or type(member) == "function" then + if name:len() > padTo then + padTo = name:len() + 2 + end + table.insert(methods, name) + end + end + table.sort(methods) + padTo = padTo + 8 - padTo % 8 + + for _, name in ipairs(methods) do + local doc = tostring(proxy[name]) + io.write(" " .. text.padRight(name, padTo) .. doc .. '\n') + end + end + + count = count - 1 + if count <= 0 then + break + end +end diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/cp.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/cp.lua new file mode 100755 index 00000000..a69e4c1a --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/cp.lua @@ -0,0 +1,32 @@ +local shell = require("shell") +local transfer = require("tools/transfer") + +local args, options = shell.parse(...) +options.h = options.h or options.help +if #args < 2 or options.h then + io.write("Usage: cp [-inrv] \n") + io.write(" -i: prompt before overwrite (overrides -n option).\n") + io.write(" -n: do not overwrite an existing file.\n") + io.write(" -r: copy directories recursively.\n") + io.write(" -u: copy only when the SOURCE file differs from the destination\n") + io.write(" file or when the destination file is missing.\n") + io.write(" -P: preserve attributes, e.g. symbolic links.\n") + io.write(" -v: verbose output.\n") + io.write(" -x: stay on original source file system.\n") + return not not options.h +end + +-- clean options for copy (as opposed to move) +options = +{ + cmd = "cp", + i = options.i, + n = options.n, + r = options.r, + u = options.u, + P = options.P, + v = options.v, + x = options.x, +} + +return transfer.batch(args, options) diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/date.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/date.lua new file mode 100755 index 00000000..da077bb8 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/date.lua @@ -0,0 +1 @@ +io.write(os.date("%F %T").."\n") diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/df.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/df.lua new file mode 100755 index 00000000..d7b6184c --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/df.lua @@ -0,0 +1,76 @@ +local fs = require("filesystem") +local shell = require("shell") +local text = require("text") + +local args, options = shell.parse(...) + +local function formatSize(size) + if not options.h then + return tostring(size) + elseif type(size) == "string" then + return size + end + local sizes = {"", "K", "M", "G"} + local unit = 1 + local power = options.si and 1000 or 1024 + while size > power and unit < #sizes do + unit = unit + 1 + size = size / power + end + return math.floor(size * 10) / 10 .. sizes[unit] +end + +local mounts = {} +if #args == 0 then + for proxy, path in fs.mounts() do + if not mounts[proxy] or mounts[proxy]:len() > path:len() then + mounts[proxy] = path + end + end +else + for i = 1, #args do + local proxy, path = fs.get(shell.resolve(args[i])) + if not proxy then + io.stderr:write(args[i], ": no such file or directory\n") + else + mounts[proxy] = path + end + end +end + +local result = {{"Filesystem", "Used", "Available", "Use%", "Mounted on"}} +for proxy, path in pairs(mounts) do + local label = proxy.getLabel() or proxy.address + local used, total = proxy.spaceUsed(), proxy.spaceTotal() + local available, percent + if total == math.huge then + used = used or "N/A" + available = "unlimited" + percent = "0%" + else + available = total - used + percent = used / total + if percent ~= percent then -- NaN + available = "N/A" + percent = "N/A" + else + percent = math.ceil(percent * 100) .. "%" + end + end + table.insert(result, {label, formatSize(used), formatSize(available), tostring(percent), path}) +end + +local m = {} +for _, row in ipairs(result) do + for col, value in ipairs(row) do + m[col] = math.max(m[col] or 1, value:len()) + end +end + +for _, row in ipairs(result) do + for col, value in ipairs(row) do + local padding = col == #row and 0 or 2 + io.write(text.padRight(value, m[col] + padding)) + end + print() +end diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/dmesg.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/dmesg.lua new file mode 100755 index 00000000..038b3d7a --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/dmesg.lua @@ -0,0 +1,38 @@ +local event = require "event" +local term = require "term" + +local args = {...} +local gpu = term.gpu() +local interactive = io.output().tty +local color, isPal, evt +if interactive then + color, isPal = gpu.getForeground() +end +io.write("Press 'Ctrl-C' to exit\n") +pcall(function() + repeat + if #args > 0 then + evt = table.pack(event.pullMultiple("interrupted", table.unpack(args))) + else + evt = table.pack(event.pull()) + end + if interactive then gpu.setForeground(0xCC2200) end + io.write("[" .. os.date("%T") .. "] ") + if interactive then gpu.setForeground(0x44CC00) end + io.write(tostring(evt[1]) .. string.rep(" ", math.max(10 - #tostring(evt[1]), 0) + 1)) + if interactive then gpu.setForeground(0xB0B00F) end + io.write(tostring(evt[2]) .. string.rep(" ", 37 - #tostring(evt[2]))) + if interactive then gpu.setForeground(0xFFFFFF) end + if evt.n > 2 then + for i = 3, evt.n do + io.write(" " .. tostring(evt[i])) + end + end + + io.write("\n") + until evt[1] == "interrupted" +end) +if interactive then + gpu.setForeground(color, isPal) +end + diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/du.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/du.lua new file mode 100755 index 00000000..864e6518 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/du.lua @@ -0,0 +1,128 @@ +local shell = require("shell") +local fs = require("filesystem") + +local args, options, reason = shell.parse(...) +if #args == 0 then + args[1] = '.' +end + +local TRY=[[ +Try 'du --help' for more information.]] + +local VERSION=[[ +du (OpenOS bin) 1.0 +Written by payonel, patterned after GNU coreutils du]] + +local HELP=[[ +Usage: du [OPTION]... [FILE]... +Summarize disk usage of each FILE, recursively for directories. + + -h, --human-readable print sizes in human readable format (e.g., 1K 234M 2G) + -s, --summarize display only a total for each argument + --help display this help and exit + --version output version information and exit]] + +if options.help then + print(HELP) + return true +end + +if options.version then + print(VERSION) + return true +end + +local function addTrailingSlash(path) + if path:sub(-1) ~= '/' then + return path .. '/' + else + return path + end +end + +local function opCheck(shortName, longName) + local enabled = options[shortName] or options[longName] + options[shortName] = nil + options[longName] = nil + return enabled +end + +local bHuman = opCheck('h', 'human-readable') +local bSummary = opCheck('s', 'summarize') + +if next(options) then + for op,v in pairs(options) do + io.stderr:write(string.format("du: invalid option -- '%s'\n", op)) + end + io.stderr:write(TRY..'\n') + return 1 +end + +local function formatSize(size) + if not bHuman then + return tostring(size) + end + local sizes = {"", "K", "M", "G"} + local unit = 1 + local power = options.si and 1000 or 1024 + while size > power and unit < #sizes do + unit = unit + 1 + size = size / power + end + + return math.floor(size * 10) / 10 .. sizes[unit] +end + +local function printSize(size, rpath) + local displaySize = formatSize(size) + io.write(string.format("%s%s\n", string.format("%-12s", displaySize), rpath)) +end + +local function visitor(rpath) + local subtotal = 0 + local dirs = 0 + local spath = shell.resolve(rpath) + + if fs.isDirectory(spath) then + local list_result = fs.list(spath) + for list_item in list_result do + local vtotal, vdirs = visitor(addTrailingSlash(rpath) .. list_item) + subtotal = subtotal + vtotal + dirs = dirs + vdirs + end + + if dirs == 0 then -- no child dirs + if not bSummary then + printSize(subtotal, rpath) + end + end + + elseif not fs.isLink(spath) then + subtotal = fs.size(spath) + end + + return subtotal, dirs +end + +for i,arg in ipairs(args) do + local path = shell.resolve(arg) + + if not fs.exists(path) then + io.stderr:write(string.format("du: cannot access '%s': no such file or directory\n", arg)) + return 1 + else + if fs.isDirectory(path) then + local total = visitor(arg) + + if bSummary then + printSize(total, arg) + end + elseif fs.isLink(path) then + printSize(0, arg) + else + printSize(fs.size(path), arg) + end + end +end + +return true diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/echo.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/echo.lua new file mode 100755 index 00000000..db036c3e --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/echo.lua @@ -0,0 +1,11 @@ +local args, options = require("shell").parse(...) +if options.help then + print([[`echo` writes the provided string(s) to the standard output. + -n do not output the trialing newline + --help display this help and exit]]) + return +end +io.write(table.concat(args," ")) +if not options.n then + print() +end diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/edit.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/edit.lua new file mode 100755 index 00000000..a5f9cbe3 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/edit.lua @@ -0,0 +1,682 @@ +local event = require("event") +local fs = require("filesystem") +local keyboard = require("keyboard") +local shell = require("shell") +local term = require("term") +local text = require("text") +local unicode = require("unicode") + +if not term.isAvailable() then + return +end +local gpu = term.gpu() +local args, options = shell.parse(...) +if #args == 0 then + io.write("Usage: edit ") + return +end + +local filename = shell.resolve(args[1]) + +local readonly = options.r or fs.get(filename) == nil or fs.get(filename).isReadOnly() + +if fs.isDirectory(filename) then + io.stderr:write("file is a directory\n") + return 1 +elseif not fs.exists(filename) and readonly then + io.stderr:write("file system is read only\n") + return 1 +end + +local function loadConfig() + -- Try to load user settings. + local env = {} + local config = loadfile("/etc/edit.cfg", nil, env) + if config then + pcall(config) + end + -- Fill in defaults. + env.keybinds = env.keybinds or { + left = {{"left"}}, + right = {{"right"}}, + up = {{"up"}}, + down = {{"down"}}, + home = {{"home"}}, + eol = {{"end"}}, + pageUp = {{"pageUp"}}, + pageDown = {{"pageDown"}}, + + backspace = {{"back"}}, + delete = {{"delete"}}, + deleteLine = {{"control", "delete"}, {"shift", "delete"}}, + newline = {{"enter"}}, + + save = {{"control", "s"}}, + close = {{"control", "w"}}, + find = {{"control", "f"}}, + findnext = {{"control", "g"}, {"control", "n"}, {"f3"}} + } + -- Generate config file if it didn't exist. + if not config then + local root = fs.get("/") + if root and not root.isReadOnly() then + fs.makeDirectory("/etc") + local f = io.open("/etc/edit.cfg", "w") + if f then + local serialization = require("serialization") + for k, v in pairs(env) do + f:write(k.."="..tostring(serialization.serialize(v, math.huge)).."\n") + end + f:close() + end + end + end + return env +end + +term.clear() +term.setCursorBlink(true) + +local running = true +local buffer = {} +local scrollX, scrollY = 0, 0 +local config = loadConfig() + +local getKeyBindHandler -- forward declaration for refind() + +local function helpStatusText() + local function prettifyKeybind(label, command) + local keybind = type(config.keybinds) == "table" and config.keybinds[command] + if type(keybind) ~= "table" or type(keybind[1]) ~= "table" then return "" end + local alt, control, shift, key + for _, value in ipairs(keybind[1]) do + if value == "alt" then alt = true + elseif value == "control" then control = true + elseif value == "shift" then shift = true + else key = value end + end + if not key then return "" end + return label .. ": [" .. + (control and "Ctrl+" or "") .. + (alt and "Alt+" or "") .. + (shift and "Shift+" or "") .. + unicode.upper(key) .. + "] " + end + return prettifyKeybind("Save", "save") .. + prettifyKeybind("Close", "close") .. + prettifyKeybind("Find", "find") +end + +------------------------------------------------------------------------------- + +local function setStatus(value) + local x, y, w, h = term.getGlobalArea() + value = unicode.wlen(value) > w - 10 and unicode.wtrunc(value, w - 9) or value + value = text.padRight(value, w - 10) + gpu.set(x, y + h - 1, value) +end + +local function getArea() + local x, y, w, h = term.getGlobalArea() + return x, y, w, h - 1 +end + +local function removePrefix(line, length) + if length >= unicode.wlen(line) then + return "" + else + local prefix = unicode.wtrunc(line, length + 1) + local suffix = unicode.sub(line, unicode.len(prefix) + 1) + length = length - unicode.wlen(prefix) + if length > 0 then + suffix = (" "):rep(unicode.charWidth(suffix) - length) .. unicode.sub(suffix, 2) + end + return suffix + end +end + +local function lengthToChars(line, length) + if length > unicode.wlen(line) then + return unicode.len(line) + 1 + else + local prefix = unicode.wtrunc(line, length) + return unicode.len(prefix) + 1 + end +end + + +local function isWideAtPosition(line, x) + local index = lengthToChars(line, x) + if index > unicode.len(line) then + return false, false + end + local prefix = unicode.sub(line, 1, index) + local char = unicode.sub(line, index, index) + --isWide, isRight + return unicode.isWide(char), unicode.wlen(prefix) == x +end + +local function drawLine(x, y, w, h, lineNr) + local yLocal = lineNr - scrollY + if yLocal > 0 and yLocal <= h then + local str = removePrefix(buffer[lineNr] or "", scrollX) + str = unicode.wlen(str) > w and unicode.wtrunc(str, w + 1) or str + str = text.padRight(str, w) + gpu.set(x, y - 1 + lineNr - scrollY, str) + end +end + +local function getCursor() + local cx, cy = term.getCursor() + return cx + scrollX, cy + scrollY +end + +local function line() + local cbx, cby = getCursor() + return buffer[cby] +end + +local function getNormalizedCursor() + local cbx, cby = getCursor() + local wide, right = isWideAtPosition(buffer[cby], cbx) + if wide and right then + cbx = cbx - 1 + end + return cbx, cby +end + +local function setCursor(nbx, nby) + local x, y, w, h = getArea() + nby = math.max(1, math.min(#buffer, nby)) + + local ncy = nby - scrollY + if ncy > h then + term.setCursorBlink(false) + local sy = nby - h + local dy = math.abs(scrollY - sy) + scrollY = sy + if h > dy then + gpu.copy(x, y + dy, w, h - dy, 0, -dy) + end + for lineNr = nby - (math.min(dy, h) - 1), nby do + drawLine(x, y, w, h, lineNr) + end + elseif ncy < 1 then + term.setCursorBlink(false) + local sy = nby - 1 + local dy = math.abs(scrollY - sy) + scrollY = sy + if h > dy then + gpu.copy(x, y, w, h - dy, 0, dy) + end + for lineNr = nby, nby + (math.min(dy, h) - 1) do + drawLine(x, y, w, h, lineNr) + end + end + term.setCursor(term.getCursor(), nby - scrollY) + + nbx = math.max(1, math.min(unicode.wlen(line()) + 1, nbx)) + local wide, right = isWideAtPosition(line(), nbx) + local ncx = nbx - scrollX + if ncx > w or (ncx + 1 > w and wide and not right) then + term.setCursorBlink(false) + scrollX = nbx - w + ((wide and not right) and 1 or 0) + for lineNr = 1 + scrollY, math.min(h + scrollY, #buffer) do + drawLine(x, y, w, h, lineNr) + end + elseif ncx < 1 or (ncx - 1 < 1 and wide and right) then + term.setCursorBlink(false) + scrollX = nbx - 1 - ((wide and right) and 1 or 0) + for lineNr = 1 + scrollY, math.min(h + scrollY, #buffer) do + drawLine(x, y, w, h, lineNr) + end + end + term.setCursor(nbx - scrollX, nby - scrollY) + --update with term lib + nbx, nby = getCursor() + gpu.set(x + w - 10, y + h, text.padLeft(string.format("%d,%d", nby, nbx), 10)) +end + +local function highlight(bx, by, length, enabled) + local x, y, w, h = getArea() + local cx, cy = bx - scrollX, by - scrollY + cx = math.max(1, math.min(w, cx)) + cy = math.max(1, math.min(h, cy)) + length = math.max(1, math.min(w - cx, length)) + + local fg, fgp = gpu.getForeground() + local bg, bgp = gpu.getBackground() + if enabled then + gpu.setForeground(bg, bgp) + gpu.setBackground(fg, fgp) + end + local indexFrom = lengthToChars(buffer[by], bx) + local value = unicode.sub(buffer[by], indexFrom) + if unicode.wlen(value) > length then + value = unicode.wtrunc(value, length + 1) + end + gpu.set(x - 1 + cx, y - 1 + cy, value) + if enabled then + gpu.setForeground(fg, fgp) + gpu.setBackground(bg, bgp) + end +end + +local function home() + local cbx, cby = getCursor() + setCursor(1, cby) +end + +local function ende() + local cbx, cby = getCursor() + setCursor(unicode.wlen(line()) + 1, cby) +end + +local function left() + local cbx, cby = getNormalizedCursor() + if cbx > 1 then + local wideTarget, rightTarget = isWideAtPosition(line(), cbx - 1) + if wideTarget and rightTarget then + setCursor(cbx - 2, cby) + else + setCursor(cbx - 1, cby) + end + return true -- for backspace + elseif cby > 1 then + setCursor(cbx, cby - 1) + ende() + return true -- again, for backspace + end +end + +local function right(n) + n = n or 1 + local cbx, cby = getNormalizedCursor() + local be = unicode.wlen(line()) + 1 + local wide, right = isWideAtPosition(line(), cbx + n) + if wide and right then + n = n + 1 + end + if cbx + n <= be then + setCursor(cbx + n, cby) + elseif cby < #buffer then + setCursor(1, cby + 1) + end +end + +local function up(n) + n = n or 1 + local cbx, cby = getCursor() + if cby > 1 then + setCursor(cbx, cby - n) + end +end + +local function down(n) + n = n or 1 + local cbx, cby = getCursor() + if cby < #buffer then + setCursor(cbx, cby + n) + end +end + +local function delete(fullRow) + local cx, cy = term.getCursor() + local cbx, cby = getCursor() + local x, y, w, h = getArea() + local function deleteRow(row) + local content = table.remove(buffer, row) + local rcy = cy + (row - cby) + if rcy <= h then + gpu.copy(x, y + rcy, w, h - rcy, 0, -1) + drawLine(x, y, w, h, row + (h - rcy)) + end + return content + end + if fullRow then + term.setCursorBlink(false) + if #buffer > 1 then + deleteRow(cby) + else + buffer[cby] = "" + gpu.fill(x, y - 1 + cy, w, 1, " ") + end + setCursor(1, cby) + elseif cbx <= unicode.wlen(line()) then + term.setCursorBlink(false) + local index = lengthToChars(line(), cbx) + buffer[cby] = unicode.sub(line(), 1, index - 1) .. + unicode.sub(line(), index + 1) + drawLine(x, y, w, h, cby) + elseif cby < #buffer then + term.setCursorBlink(false) + local append = deleteRow(cby + 1) + buffer[cby] = buffer[cby] .. append + drawLine(x, y, w, h, cby) + else + return + end + setStatus(helpStatusText()) +end + +local function insert(value) + if not value or unicode.len(value) < 1 then + return + end + term.setCursorBlink(false) + local cx, cy = term.getCursor() + local cbx, cby = getCursor() + local x, y, w, h = getArea() + local index = lengthToChars(line(), cbx) + buffer[cby] = unicode.sub(line(), 1, index - 1) .. + value .. + unicode.sub(line(), index) + drawLine(x, y, w, h, cby) + right(unicode.wlen(value)) + setStatus(helpStatusText()) +end + +local function enter() + term.setCursorBlink(false) + local cx, cy = term.getCursor() + local cbx, cby = getCursor() + local x, y, w, h = getArea() + local index = lengthToChars(line(), cbx) + table.insert(buffer, cby + 1, unicode.sub(buffer[cby], index)) + buffer[cby] = unicode.sub(buffer[cby], 1, index - 1) + drawLine(x, y, w, h, cby) + if cy < h then + if cy < h - 1 then + gpu.copy(x, y + cy, w, h - (cy + 1), 0, 1) + end + drawLine(x, y, w, h, cby + 1) + end + setCursor(1, cby + 1) + setStatus(helpStatusText()) +end + +local findText = "" + +local function find() + local x, y, w, h = getArea() + local cx, cy = term.getCursor() + local cbx, cby = getCursor() + local ibx, iby = cbx, cby + while running do + if unicode.len(findText) > 0 then + local sx, sy + for syo = 1, #buffer do -- iterate lines with wraparound + sy = (iby + syo - 1 + #buffer - 1) % #buffer + 1 + sx = string.find(buffer[sy], findText, syo == 1 and ibx or 1, true) + if sx and (sx >= ibx or syo > 1) then + break + end + end + if not sx then -- special case for single matches + sy = iby + sx = string.find(buffer[sy], findText, nil, true) + end + if sx then + sx = unicode.wlen(string.sub(buffer[sy], 1, sx - 1)) + 1 + cbx, cby = sx, sy + setCursor(cbx, cby) + highlight(cbx, cby, unicode.wlen(findText), true) + end + end + term.setCursor(7 + unicode.wlen(findText), h + 1) + setStatus("Find: " .. findText) + + local _, address, char, code = term.pull("key_down") + if address == term.keyboard() then + local handler, name = getKeyBindHandler(code) + highlight(cbx, cby, unicode.wlen(findText), false) + if name == "newline" then + break + elseif name == "close" then + handler() + elseif name == "backspace" then + findText = unicode.sub(findText, 1, -2) + elseif name == "find" or name == "findnext" then + ibx = cbx + 1 + iby = cby + elseif not keyboard.isControl(char) then + findText = findText .. unicode.char(char) + end + end + end + setCursor(cbx, cby) + setStatus(helpStatusText()) +end + +------------------------------------------------------------------------------- + +local keyBindHandlers = { + left = left, + right = right, + up = up, + down = down, + home = home, + eol = ende, + pageUp = function() + local x, y, w, h = getArea() + up(h - 1) + end, + pageDown = function() + local x, y, w, h = getArea() + down(h - 1) + end, + + backspace = function() + if not readonly and left() then + delete() + end + end, + delete = function() + if not readonly then + delete() + end + end, + deleteLine = function() + if not readonly then + delete(true) + end + end, + newline = function() + if not readonly then + enter() + end + end, + + save = function() + if readonly then return end + local new = not fs.exists(filename) + local backup + if not new then + backup = filename .. "~" + for i = 1, math.huge do + if not fs.exists(backup) then + break + end + backup = filename .. "~" .. i + end + fs.copy(filename, backup) + end + local f, reason = io.open(filename, "w") + if f then + local chars, firstLine = 0, true + for _, line in ipairs(buffer) do + if not firstLine then + line = "\n" .. line + end + firstLine = false + f:write(line) + chars = chars + unicode.len(line) + end + f:close() + local format + if new then + format = [["%s" [New] %dL,%dC written]] + else + format = [["%s" %dL,%dC written]] + end + setStatus(string.format(format, fs.name(filename), #buffer, chars)) + else + setStatus(reason) + end + if not new then + fs.remove(backup) + end + end, + close = function() + -- TODO ask to save if changed + running = false + end, + find = function() + findText = "" + find() + end, + findnext = find +} + +getKeyBindHandler = function(code) + if type(config.keybinds) ~= "table" then return end + -- Look for matches, prefer more 'precise' keybinds, e.g. prefer + -- ctrl+del over del. + local result, resultName, resultWeight = nil, nil, 0 + for command, keybinds in pairs(config.keybinds) do + if type(keybinds) == "table" and keyBindHandlers[command] then + for _, keybind in ipairs(keybinds) do + if type(keybind) == "table" then + local alt, control, shift, key + for _, value in ipairs(keybind) do + if value == "alt" then alt = true + elseif value == "control" then control = true + elseif value == "shift" then shift = true + else key = value end + end + local keyboardAddress = term.keyboard() + if (not alt or keyboard.isAltDown(keyboardAddress)) and + (not control or keyboard.isControlDown(keyboardAddress)) and + (not shift or keyboard.isShiftDown(keyboardAddress)) and + code == keyboard.keys[key] and + #keybind > resultWeight + then + resultWeight = #keybind + resultName = command + result = keyBindHandlers[command] + end + end + end + end + end + return result, resultName +end + +------------------------------------------------------------------------------- + +local function onKeyDown(char, code) + local handler = getKeyBindHandler(code) + if handler then + handler() + elseif readonly and code == keyboard.keys.q then + running = false + elseif not readonly then + if not keyboard.isControl(char) then + insert(unicode.char(char)) + elseif unicode.char(char) == "\t" then + insert(" ") + end + end +end + +local function onClipboard(value) + value = value:gsub("\r\n", "\n") + local cbx, cby = getCursor() + local start = 1 + local l = value:find("\n", 1, true) + if l then + repeat + local line = string.sub(value, start, l - 1) + line = text.detab(line, 2) + insert(line) + enter() + start = l + 1 + l = value:find("\n", start, true) + until not l + end + insert(string.sub(value, start)) +end + +local function onClick(x, y) + setCursor(x + scrollX, y + scrollY) +end + +local function onScroll(direction) + local cbx, cby = getCursor() + setCursor(cbx, cby - direction * 12) +end + +------------------------------------------------------------------------------- + +do + local f = io.open(filename) + if f then + local x, y, w, h = getArea() + local chars = 0 + for line in f:lines() do + if line:sub(-1) == "\r" then + line = line:sub(1, -2) + end + table.insert(buffer, line) + chars = chars + unicode.len(line) + if #buffer <= h then + drawLine(x, y, w, h, #buffer) + end + end + f:close() + if #buffer == 0 then + table.insert(buffer, "") + end + local format + if readonly then + format = [["%s" [readonly] %dL,%dC]] + else + format = [["%s" %dL,%dC]] + end + setStatus(string.format(format, fs.name(filename), #buffer, chars)) + else + table.insert(buffer, "") + setStatus(string.format([["%s" [New File] ]], fs.name(filename))) + end + setCursor(1, 1) +end + +while running do + local event, address, arg1, arg2, arg3 = term.pull() + if address == term.keyboard() or address == term.screen() then + local blink = true + if event == "key_down" then + onKeyDown(arg1, arg2) + elseif event == "clipboard" and not readonly then + onClipboard(arg1) + elseif event == "touch" or event == "drag" then + local x, y, w, h = getArea() + arg1 = arg1 - x + 1 + arg2 = arg2 - y + 1 + if arg1 >= 1 and arg2 >= 1 and arg1 <= w and arg2 <= h then + onClick(arg1, arg2) + end + elseif event == "scroll" then + onScroll(arg3) + else + blink = false + end + if blink then + term.setCursorBlink(true) + end + end +end + +term.clear() +term.setCursorBlink(false) diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/find.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/find.lua new file mode 100755 index 00000000..a15bb14a --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/find.lua @@ -0,0 +1,132 @@ +local shell = require("shell") +local fs = require("filesystem") +local text = require("text") + +local USAGE = +[===[Usage: find [path] [--type=[dfs]] [--[i]name=EXPR] + --path if not specified, path is assumed to be current working directory + --type returns results of a given type, d:directory, f:file, and s:symlinks + --name specify the file name pattern. Use quote to include *. iname is + case insensitive + --help display this help and exit]===] + +local args, options = shell.parse(...) + +if (not args or not options) or options.help then + print(USAGE) + if not options.help then + return 1 + else + return -- nil return, meaning no error + end +end + +if #args > 1 then + io.stderr:write(USAGE..'\n') + return 1 +end + +local path = #args == 1 and args[1] or "." + +local bDirs = true +local bFiles = true +local bSyms = true + +local fileNamePattern = "" +local bCaseSensitive = true + +if options.iname and options.name then + io.stderr:write("find cannot define both iname and name\n") + return 1 +end + +if options.type then + bDirs = false + bFiles = false + bSyms = false + + if options.type == "f" then + bFiles = true + elseif options.type == "d" then + bDirs = true + elseif options.type == "s" then + bSyms = true + else + io.stderr:write(string.format("find: Unknown argument to type: %s\n", options.type)) + io.stderr:write(USAGE..'\n') + return 1 + end +end + +if options.iname or options.name then + bCaseSensitive = options.iname ~= nil + fileNamePattern = options.iname or options.name + + if type(fileNamePattern) ~= "string" then + io.stderr:write('find: missing argument to `name\'\n') + return 1 + end + + if not bCaseSensitive then + fileNamePattern = fileNamePattern:lower() + end + + -- prefix any * with . for gnu find glob matching + fileNamePattern = text.escapeMagic(fileNamePattern) + fileNamePattern = fileNamePattern:gsub("%%%*", ".*") +end + +local function isValidType(spath) + if not fs.exists(spath) then + return false + end + + if fileNamePattern:len() > 0 then + local fileName = spath:gsub('.*/','') + + if fileName:len() == 0 then + return false + end + + local caseFileName = fileName + + if not bCaseSensitive then + caseFileName = caseFileName:lower() + end + + local s, e = caseFileName:find(fileNamePattern) + if not s or not e then + return false + end + + if s ~= 1 or e ~= caseFileName:len() then + return false + end + end + + if fs.isDirectory(spath) then + return bDirs + elseif fs.isLink(spath) then + return bSyms + else + return bFiles + end +end + +local function visit(rpath) + local spath = shell.resolve(rpath) + + if isValidType(spath) then + local result = rpath:gsub('/+$','') + print(result) + end + + if fs.isDirectory(spath) then + local list_result = fs.list(spath) + for list_item in list_result do + visit(rpath:gsub('/+$', '') .. '/' .. list_item) + end + end +end + +visit(path) diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/flash.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/flash.lua new file mode 100755 index 00000000..5fdcfe88 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/flash.lua @@ -0,0 +1,88 @@ +local component = require("component") +local shell = require("shell") +local fs = require("filesystem") + +local args, options = shell.parse(...) + +if #args < 1 and not options.l then + io.write("Usage: flash [-qlr] [] [label]\n") + io.write(" q: quiet mode, don't ask questions.\n") + io.write(" l: print current contents of installed EEPROM.\n") + io.write(" r: save the current contents of installed EEPROM to file.\n") + return +end + +local function printRom() + local eeprom = component.eeprom + io.write(eeprom.get()) +end + +local function readRom() + local eeprom = component.eeprom + fileName = shell.resolve(args[1]) + if not options.q then + if fs.exists(fileName) then + io.write("Are you sure you want to overwrite " .. fileName .. "?\n") + io.write("Type `y` to confirm.\n") + repeat + local response = io.read() + until response and response:lower():sub(1, 1) == "y" + end + io.write("Reading EEPROM " .. eeprom.address .. ".\n" ) + end + local bios = eeprom.get() + local file = assert(io.open(fileName, "wb")) + file:write(bios) + file:close() + if not options.q then + io.write("All done!\nThe label is '" .. eeprom.getLabel() .. "'.\n") + end +end + +local function writeRom() + local file = assert(io.open(args[1], "rb")) + local bios = file:read("*a") + file:close() + + if not options.q then + io.write("Insert the EEPROM you would like to flash.\n") + io.write("When ready to write, type `y` to confirm.\n") + repeat + local response = io.read() + until response and response:lower():sub(1, 1) == "y" + io.write("Beginning to flash EEPROM.\n") + end + + local eeprom = component.eeprom + + if not options.q then + io.write("Flashing EEPROM " .. eeprom.address .. ".\n") + io.write("Please do NOT power down or restart your computer during this operation!\n") + end + + eeprom.set(bios) + + local label = args[2] + if not options.q and not label then + io.write("Enter new label for this EEPROM. Leave input blank to leave the label unchanged.\n") + label = io.read() + end + if label and #label > 0 then + eeprom.setLabel(label) + if not options.q then + io.write("Set label to '" .. eeprom.getLabel() .. "'.\n") + end + end + + if not options.q then + io.write("All done! You can remove the EEPROM and re-insert the previous one now.\n") + end +end + +if options.l then + printRom() +elseif options.r then + readRom() +else + writeRom() +end diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/grep.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/grep.lua new file mode 100755 index 00000000..3c66033c --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/grep.lua @@ -0,0 +1,315 @@ +--[[ +An adaptation of Wobbo's grep +https://raw.githubusercontent.com/OpenPrograms/Wobbo-Programs/master/grep/grep.lua +]]-- + +-- POSIX grep for OpenComputers +-- one difference is that this version uses Lua regex, not POSIX regex. + +local fs = require("filesystem") +local shell = require("shell") +local term = require("term") + +-- Process the command line arguments + +local args, options = shell.parse(...) + +local gpu = term.gpu() + +local function printUsage(ostream, msg) + local s = ostream or io.stdout + if msg then + s:write(msg,'\n') + end + s:write([[Usage: grep [OPTION]... PATTERN [FILE]... +Example: grep -i "hello world" menu.lua main.lua +for more information, run: man grep +]]) +end + +local PATTERNS = {args[1]} +local FILES = {select(2, table.unpack(args))} + +local LABEL_COLOR = 0xb000b0 +local LINE_NUM_COLOR = 0x00FF00 +local MATCH_COLOR = 0xFF0000 +local COLON_COLOR = 0x00FFFF + +local function pop(...) + local result + for _,key in ipairs({...}) do + result = options[key] or result + options[key] = nil + end + return result +end + +-- Specify the variables for the options +local plain = pop('F','fixed-strings') + plain = not pop('e','--lua-regexp') and plain +local pattern_file = pop('file') +local match_whole_word = pop('w','word-regexp') +local match_whole_line = pop('x','line-regexp') +local ignore_case = pop('i','ignore-case') +local stdin_label = pop('label') or '(standard input)' +local stderr = pop('s','no-messages') and {write=function()end} or io.stderr +local invert_match = not not pop('v','invert-match') + +-- no version output, just help +if pop('V','version','help') then + printUsage() + return 0 +end + +local max_matches = tonumber(pop('max-count')) or math.huge +local print_line_num = pop('n','line-number') +local search_recursively = pop('r','recursive') + +-- Table with patterns to check for +if pattern_file then + local pattern_file_path = shell.resolve(pattern_file) + if not fs.exists(pattern_file_path) then + stderr:write('grep: ',pattern_file,': file not found') + return 2 + end + table.insert(FILES, 1, PATTERNS[1]) + PATTERNS = {} + for line in io.lines(pattern_file_path) do + PATTERNS[#PATTERNS+1] = line + end +end + +if #PATTERNS == 0 then + printUsage(stderr) + return 2 +end + +if #FILES == 0 then + FILES = search_recursively and {'.'} or {'-'} +end + +if not options.h and search_recursively then + options.H = true +end + +if #FILES < 2 then + options.h = true +end + +local f_only = pop('l','files-with-matches') +local no_only = pop('L','files-without-match') and not f_only + +local include_filename = pop('H','with-filename') + include_filename = not pop('h','no-filename') or include_filename + +local m_only = pop('o','only-matching') +local quiet = pop('q','quiet','silent') + +local print_count = pop('c','count') +local colorize = pop('color','colour') and io.output().tty and term.isAvailable() + +local noop = function(...)return ...;end +local setc = colorize and gpu.setForeground or noop +local getc = colorize and gpu.getForeground or noop + +local trim = pop('t','trim') +local trim_front = trim and function(s)return s:gsub('^%s+','')end or noop +local trim_back = trim and function(s)return s:gsub('%s+$','')end or noop + +if next(options) then + if not quiet then + printUsage(stderr, 'unexpected option: '..next(options)) + return 2 + end + return 0 +end +-- Resolve the location of a file, without searching the path +local function resolve(file) + if file:sub(1,1) == '/' then + return fs.canonical(file) + else + if file:sub(1,2) == './' then + file = file:sub(3, -1) + end + return fs.canonical(fs.concat(shell.getWorkingDirectory(), file)) + end +end + +--- Builds a case insensitive patterns, code from stackoverflow +--- (questions/11401890/case-insensitive-lua-pattern-matching) +if ignore_case then + for i=1,#PATTERNS do + -- find an optional '%' (group 1) followed by any character (group 2) + PATTERNS[i] = PATTERNS[i]:gsub("(%%?)(.)", function(percent, letter) + if percent ~= "" or not letter:match("%a") then + -- if the '%' matched, or `letter` is not a letter, return "as is" + return percent .. letter + else -- case-insensitive + return string.format("[%s%s]", letter:lower(), letter:upper()) + end + end) + end +end + +local function getAllFiles(dir, file_list) + for node in fs.list(shell.resolve(dir)) do + local rel_path = dir:gsub("/+$","") .. '/' .. node + local resolved_path = shell.resolve(rel_path) + if fs.isDirectory(resolved_path) then + getAllFiles(rel_path, file_list) + else + file_list[#file_list+1] = rel_path + end + end +end + +if search_recursively then + local files = {} + for i,arg in ipairs(FILES) do + if fs.isDirectory(arg) then + getAllFiles(arg, files) + else + files[#files+1]=arg + end + end + FILES=files +end + +-- Prepare an iterator for reading files +local function readLines() + local curHand = nil + local curFile = nil + local meta = nil + return function() + if not curFile then + local file = table.remove(FILES, 1) + if not file then + return + end + meta = {line_num=0,hits=0} + if file == "-" then + curFile = file + meta.label = stdin_label + curHand = io.input() + else + meta.label = file + local file, reason = resolve(file) + if fs.exists(file) then + curHand, reason = io.open(file, 'r') + if not curHand then + local msg = string.format("failed to read from %s: %s", meta.label, reason) + stderr:write("grep: ",msg,"\n") + return false, 2 + else + curFile = meta.label + end + else + stderr:write("grep: ",file,": file not found\n") + return false, 2 + end + end + end + meta.line = nil + if not meta.close and curHand then + meta.line_num = meta.line_num + 1 + meta.line = curHand:read("*l") + end + if not meta.line then + curFile = nil + if curHand then + curHand:close() + end + return false, meta + else + return meta, curFile + end + end +end + +local function write(part, color) + local prev_color = color and getc() + if color then setc(color) end + io.write(part) + if color then setc(prev_color) end +end +local flush=(f_only or no_only or print_count) and function(m) + if no_only and m.hits == 0 or f_only and m.hits ~= 0 then + write(m.label, LABEL_COLOR) + write('\n') + elseif print_count then + if include_filename then + write(m.label, LABEL_COLOR) + write(':', COLON_COLOR) + end + write(m.hits) + write('\n') + end +end +local ec = nil +local any_hit_ec = 1 +local function test(m,p) + local empty_line = true + local last_index, slen = 1, #m.line + local needs_filename, needs_line_num = include_filename, print_line_num + local hit_value = 1 + while last_index <= slen and not m.close do + local i, j = m.line:find(p, last_index, plain) + local word_fail, line_fail = + match_whole_word and not (i and not (m.line:sub(i-1,i-1)..m.line:sub(j+1,j+1)):find("[%a_]")), + match_whole_line and not (i==1 and j==slen) + local matched = not ((m_only or last_index==1) and not i) + if (hit_value == 1 and word_fail) or line_fail then + matched,i,j = false + end + if invert_match == matched then break end + if max_matches == 0 then os.exit(1) end + any_hit_ec = 0 + m.hits, hit_value = m.hits + hit_value, 0 + if f_only or no_only then + m.close = true + end + if flush or quiet then return end + if needs_filename then + write(m.label, LABEL_COLOR) + write(':', COLON_COLOR) + needs_filename = nil + end + if needs_line_num then + write(m.line_num, LINE_NUM_COLOR) + write(':', COLON_COLOR) + needs_line_num = nil + end + local s=m_only and '' or m.line:sub(last_index,(i or 0)-1) + local g=i and m.line:sub(i,j) or '' + if i==1 then g=trim_front(g) elseif last_index==1 then s=trim_front(s) end + if j==slen then g=trim_back(g) elseif not i then s=trim_back(s) end + write(s) + write(g, MATCH_COLOR) + empty_line = false + last_index = (j or slen)+1 + if m_only or last_index>slen then + write("\n") + empty_line = true + needs_filename, needs_line_num = include_filename, print_line_num + elseif p:find("^^") and not plain then p="^$" end + end + if not empty_line then write("\n") end + if max_matches ~= math.huge and max_matches >= m.hits then + m.close = true + end +end +for meta,status in readLines() do + if not meta then + if type(status) == 'table' then if flush then + flush(status) end -- this was the last object, closing out + elseif status then + ec = status or ec + end + else + for _,p in ipairs(PATTERNS) do + test(meta,p) + end + end +end + +return ec or any_hit_ec diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/head.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/head.lua new file mode 100755 index 00000000..99dfa216 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/head.lua @@ -0,0 +1,137 @@ +local shell = require("shell") +local fs = require("filesystem") + +local args, options = shell.parse(...) +local error_code = 0 + +local function pop(key, convert) + local result = options[key] + options[key] = nil + if result and convert then + local c = tonumber(result) + if not c then + io.stderr:write(string.format("use --%s=n where n is a number\n", key)) + options.help = true + error_code = 1 + end + result = c + end + return result +end + +local bytes = pop('bytes', true) +local lines = pop('lines', true) +local quiet = {pop('q'), pop('quiet'), pop('silent')} +quiet = quiet[1] or quiet[2] or quiet[3] +local verbose = {pop('v'), pop('verbose')} +verbose = verbose[1] or verbose[2] +local help = pop('help') +local invalid_key = next(options) + +if bytes and lines then + invalid_key = 'bytes and lines both specified' +end + +if help or next(options) then + local invalid_key = next(options) + if invalid_key then + invalid_key = string.format('invalid option: %s\n', invalid_key) + error_code = 1 + else + invalid_key = '' + end + print(invalid_key .. [[Usage: head [--lines=n] file +Print the first 10 lines of each FILE to stdout. +For more info run: man head]]) + os.exit(error_code) +end + +if #args == 0 then + args = {'-'} +end + +if quiet and verbose then + quiet = false +end + +local function new_stream() + return + { + open=true, + capacity=math.abs(lines or bytes or 10), + bytes=bytes, + buffer=(lines and lines < 0 and {}) or (bytes and bytes < 0 and '') + } +end + +local function close(stream) + if stream.buffer then + if type(stream.buffer) == 'table' then + stream.buffer = table.concat(stream.buffer) + end + io.stdout:write(stream.buffer) + stream.buffer = nil + end + stream.open = false +end + +local function push(stream, line) + if not line then + return close(stream) + end + + local cost = stream.bytes and line:len() or 1 + stream.capacity = stream.capacity - cost + + if not stream.buffer then + if stream.bytes and stream.capacity < 0 then + line = line:sub(1,stream.capacity-1) + end + io.write(line) + if stream.capacity <= 0 then + return close(stream) + end + else + if type(stream.buffer) == 'table' then -- line storage + stream.buffer[#stream.buffer+1] = line + if stream.capacity < 0 then + table.remove(stream.buffer, 1) + stream.capacity = 0 -- zero out + end + else -- byte storage + stream.buffer = stream.buffer .. line + if stream.capacity < 0 then + stream.buffer = stream.buffer:sub(-stream.capacity+1) + stream.capacity = 0 -- zero out + end + end + end + +end + +for i=1,#args do + local arg = args[i] + local file + if arg == '-' then + arg = 'standard input' + file = io.stdin + else + file, reason = io.open(arg, 'r') + if not file then + io.stderr:write(string.format([[head: cannot open '%s' for reading: %s]], arg, reason)) + end + end + if file then + if verbose or #args > 1 then + io.write(string.format('==> %s <==\n', arg)) + end + + local stream = new_stream() + + while stream.open do + push(stream, file:read('*L')) + end + + file:close() + end +end diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/hostname.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/hostname.lua new file mode 100755 index 00000000..4e88379a --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/hostname.lua @@ -0,0 +1,22 @@ +local args = {...} +if args[1] then + local file, reason = io.open("/etc/hostname", "w") + if not file then + io.stderr:write(reason .. "\n") + return 1 + else + file:write(args[1]) + file:close() + os.setenv("HOSTNAME", args[1]) + os.setenv("PS1", "$HOSTNAME:$PWD# ") + end +else + local file = io.open("/etc/hostname") + if file then + io.write(file:read("*l"), "\n") + file:close() + else + io.stderr:write("Hostname not set\n") + return 1 + end +end diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/install.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/install.lua new file mode 100755 index 00000000..1b9176d5 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/install.lua @@ -0,0 +1,51 @@ +local computer = require("computer") +local shell = require("shell") + +local options + +do + local basic, reason = loadfile(package.searchpath("tools/install_basics", package.path), "bt", _G) + if not basic then + io.stderr:write("failed to load install: " .. tostring(reason) .. "\n") + return 1 + end + options = basic(...) +end + +if not options then return end +local write = io.write + +if computer.freeMemory() < 50000 then + write("Low memory, collecting garbage\n") + for i=1,20 do os.sleep(0) end +end + +local cp, reason = loadfile(shell.resolve("cp", "lua"), "bt", _G) + +local ec = cp(table.unpack(options.cp_args)) +if ec ~= nil and ec ~= 0 then + return ec +end + +write("Installation complete!\n") + +if options.setlabel then + pcall(options.target.dev.setLabel, options.label) +end + +if options.setboot then + local address = options.target.dev.address + if computer.setBootAddress(address) then + write("Boot address set to " .. address) + end +end + +if options.reboot then + write("Reboot now? [Y/n] ") + if ((io.read() or "n").."y"):match("^%s*[Yy]") then + write("\nRebooting now!\n") + computer.shutdown(true) + end +end + +write("Returning to shell.\n") diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/label.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/label.lua new file mode 100755 index 00000000..25cf958f --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/label.lua @@ -0,0 +1,49 @@ +local shell = require("shell") +local devfs = require("devfs") +local comp = require("component") + +local args, options = shell.parse(...) +if #args < 1 then + io.write("Usage: label [-a] [

]\n") + io.write("Note that the address may be abbreviated.\n") + return 1 +end + +local componentType = args[1] + +if #args > 1 then + local address = args[2] + if not component.get(address) then + io.stderr:write("no component with this address\n") + return 1 + else + component.setPrimary(componentType, address) + os.sleep(0.1) -- allow signals to be processed + end +end +if component.isAvailable(componentType) then + io.write(component.getPrimary(componentType).address, "\n") +else + io.stderr:write("no primary component for this type\n") + return 1 +end diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/pwd.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/pwd.lua new file mode 100755 index 00000000..5ea691f8 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/pwd.lua @@ -0,0 +1,14 @@ +local shell = require("shell") +local fs = require("filesystem") +local _,op = shell.parse(...) + +local path, why = shell.getWorkingDirectory(), "" +if op.P then + path, why = fs.realPath(path) +end +if not path then + io.stderr:write(string.format("error retrieving current directory: %s", why)) + os.exit(1) +end + +io.write(path, "\n") diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/rc.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/rc.lua new file mode 100755 index 00000000..48855362 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/rc.lua @@ -0,0 +1,142 @@ +local rc = require("rc") +local fs = require("filesystem") +local shell = require("shell") + +local function loadConfig() + local env = {} + local result, reason = loadfile('/etc/rc.cfg', 't', env) + if result then + result, reason = xpcall(result, debug.traceback) + if result then + return env + end + end + return nil, reason +end + +local function saveConfig(conf) + local file, reason = io.open('/etc/rc.cfg', 'w') + if not file then + return nil, reason + end + for key, value in pairs(conf) do + file:write(tostring(key) .. " = " .. require("serialization").serialize(value) .. "\n") + end + + file:close() + return true +end + +local function load(name, args) + if rc.loaded[name] then + return rc.loaded[name] + end + local fileName = fs.concat('/etc/rc.d/', name .. '.lua') + local env = setmetatable({args = args}, {__index = _G}) + local result, reason = loadfile(fileName, 't', env) + if result then + result, reason = xpcall(result, debug.traceback) + if result then + rc.loaded[name] = env + return env + end + end + return nil, reason +end + +function rc.unload(name) + rc.loaded[name] = nil +end + +local function rawRunCommand(conf, name, cmd, args, ...) + local result, what = load(name, args) + if result then + if not cmd then + io.output():write("Commands for service " .. name .. "\n") + for command, val in pairs(result) do + if type(val) == "function" then + io.output():write(tostring(command) .. " ") + end + end + return true + elseif type(result[cmd]) == "function" then + result, what = xpcall(result[cmd], debug.traceback, ...) + if result then + return true + end + elseif cmd == "restart" and type(result["stop"]) == "function" and type(result["start"]) == "function" then + local daemon = result + result, what = xpcall(daemon["stop"], debug.traceback, ...) + if result then + result, what = xpcall(daemon["start"], debug.traceback, ...) + if result then + return true + end + end + elseif cmd == "enable" then + conf.enabled = conf.enabled or {} + for _, _name in ipairs(conf.enabled) do + if name == _name then + return nil, "Service already enabled" + end + end + conf.enabled[#conf.enabled + 1] = name + return saveConfig(conf) + elseif cmd == "disable" then + conf.enabled = conf.enabled or {} + for n, _name in ipairs(conf.enabled) do + if name == _name then + table.remove(conf.enabled, n) + end + end + return saveConfig(conf) + else + what = "Command '" .. cmd .. "' not found in daemon '" .. name .. "'" + end + end + return nil, what +end + +local function runCommand(name, cmd, ...) + local conf, reason = loadConfig() + if not conf then + return nil, reason + end + return rawRunCommand(conf, name, cmd, conf[name], ...) +end + +local function allRunCommand(cmd, ...) + local conf, reason = loadConfig() + if not conf then + return nil, reason + end + local results = {} + for _, name in ipairs(conf.enabled or {}) do + results[name] = table.pack(rawRunCommand(conf, name, cmd, conf[name], ...)) + end + return results +end + +local args = table.pack(...) + +if #args == 0 then + local results,reason = allRunCommand("start") + if not results then + local msg = "rc failed to start:"..tostring(reason) + io.stderr:write(msg) + require("event").onError(msg) + return + end + for name, result in pairs(results) do + local ok, reason = table.unpack(result) + if not ok then + io.stderr:write(reason .. "\n") + end + end +else + local result, reason = runCommand(table.unpack(args)) + if not result then + io.stderr:write(reason .. "\n") + return 1 + end +end diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/reboot.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/reboot.lua new file mode 100755 index 00000000..1d050c3a --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/reboot.lua @@ -0,0 +1,4 @@ +local computer = require("computer") + +io.write("Rebooting...") +computer.shutdown(true) \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/redstone.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/redstone.lua new file mode 100755 index 00000000..6d9c112f --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/redstone.lua @@ -0,0 +1,103 @@ +local colors = require("colors") +local component = require("component") +local shell = require("shell") +local sides = require("sides") + +if not component.isAvailable("redstone") then + io.stderr:write("This program requires a redstone card or redstone I/O block.\n") + return 1 +end +local rs = component.redstone + +local args, options = shell.parse(...) +if #args == 0 and not options.w and not options.f then + io.write("Usage:\n") + io.write(" redstone []\n") + if rs.setBundledOutput then + io.write(" redstone -b []\n") + end + if rs.setWirelessOutput then + io.write(" redstone -w []\n") + io.write(" redstone -f []\n") + end + return +end + +if options.w then + if not rs.setWirelessOutput then + io.stderr:write("wireless redstone not available\n") + return 1 + end + if #args > 0 then + local value = args[1] + if tonumber(value) then + value = tonumber(value) > 0 + else + value = ({["true"]=true,["on"]=true,["yes"]=true})[value] ~= nil + end + rs.setWirelessOutput(value) + end + io.write("in: " .. tostring(rs.getWirelessInput()) .. "\n") + io.write("out: " .. tostring(rs.getWirelessOutput()) .. "\n") +elseif options.f then + if not rs.setWirelessOutput then + io.stderr:write("wireless redstone not available\n") + return 1 + end + if #args > 0 then + local value = args[1] + if not tonumber(value) then + io.stderr:write("invalid frequency\n") + return 1 + end + rs.setWirelessFrequency(tonumber(value)) + end + io.write("freq: " .. tostring(rs.getWirelessFrequency()) .. "\n") +else + local side = sides[args[1]] + if not side then + io.stderr:write("invalid side\n") + return 1 + end + if type(side) == "string" then + side = sides[side] + end + + if options.b then + if not rs.setBundledOutput then + io.stderr:write("bundled redstone not available\n") + return 1 + end + local color = colors[args[2]] + if not color then + io.stderr:write("invalid color\n") + return 1 + end + if type(color) == "string" then + color = colors[color] + end + if #args > 2 then + local value = args[3] + if tonumber(value) then + value = tonumber(value) + else + value = ({["true"]=true,["on"]=true,["yes"]=true})[value] and 255 or 0 + end + rs.setBundledOutput(side, color, value) + end + io.write("in: " .. rs.getBundledInput(side, color) .. "\n") + io.write("out: " .. rs.getBundledOutput(side, color) .. "\n") + else + if #args > 1 then + local value = args[2] + if tonumber(value) then + value = tonumber(value) + else + value = ({["true"]=true,["on"]=true,["yes"]=true})[value] and 15 or 0 + end + rs.setOutput(side, value) + end + io.write("in: " .. rs.getInput(side) .. "\n") + io.write("out: " .. rs.getOutput(side) .. "\n") + end +end diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/resolution.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/resolution.lua new file mode 100755 index 00000000..7823d1d9 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/resolution.lua @@ -0,0 +1,32 @@ +local shell = require("shell") +local term = require("term") + +local args = shell.parse(...) +local gpu = term.gpu() + +if #args == 0 then + local w, h = gpu.getViewport() + io.write(w," ",h,"\n") + return +end + +if #args ~= 2 then + print("Usage: resolution [ ]") + return +end + +local w = tonumber(args[1]) +local h = tonumber(args[2]) +if not w or not h then + io.stderr:write("invalid width or height\n") + return 1 +end + +local result, reason = gpu.setResolution(w, h) +if not result then + if reason then -- otherwise we didn't change anything + io.stderr:write(reason..'\n') + end + return 1 +end +term.clear() diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/rm.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/rm.lua new file mode 100755 index 00000000..d77d6a32 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/rm.lua @@ -0,0 +1,158 @@ +local fs = require("filesystem") +local shell = require("shell") + +local function usage() + print("Usage: rm [options] [ [...]]"..[[ + + -f ignore nonexistent files and arguments, never prompt + -r remove directories and their contents recursively + -v explain what is being done + --help display this help and exit + +For complete documentation and more options, run: man rm]]) +end + +local args, options = shell.parse(...) +if #args == 0 or options.help then + usage() + return 1 +end + +local bRec = options.r or options.R or options.recursive +local bForce = options.f or options.force +local bVerbose = options.v or options.verbose +local bEmptyDirs = options.d or options.dir +local promptLevel = (options.I and 3) or (options.i and 1) or 0 + +bVerbose = bVerbose and not bForce +promptLevel = bForce and 0 or promptLevel + +local function perr(...) + if not bForce then + io.stderr:write(...) + end +end + +local function pout(...) + if not bForce then + io.stdout:write(...) + end +end + +local metas = {} + +-- promptLevel 3 done before fs.exists +-- promptLevel 1 asks for each, displaying fs.exists on hit as it visits + +local function _path(m) return shell.resolve(m.rel) end +local function _link(m) return fs.isLink(_path(m)) end +local function _exists(m) return _link(m) or fs.exists(_path(m)) end +local function _dir(m) return not _link(m) and fs.isDirectory(_path(m)) end +local function _readonly(m) return not _exists(m) or fs.get(_path(m)).isReadOnly() end +local function _empty(m) return _exists(m) and _dir(m) and (fs.list(_path(m))==nil) end + +local function createMeta(origin, rel) + local m = {origin=origin,rel=rel:gsub("/+$", "")} + if _dir(m) then + m.rel = m.rel .. '/' + end + return m +end + +local function unlink(path) + os.remove(path) + return true +end + +local function confirm() + if bForce then + return true + end + local r = io.read("*l") + return r == 'y' or r == 'yes' +end + +local function remove_all(parent) + if parent == nil or not _dir(parent) or _empty(parent) then + return true + end + + local all_ok = true + if bRec and promptLevel == 1 then + pout(string.format("rm: descend into directory `%s'? ", parent.rel)) + if not confirm() then + return false + end + + for file in fs.list(_path(parent)) do + local child = createMeta(parent.origin, parent.rel .. file) + all_ok = remove(child) and all_ok + end + end + + return all_ok +end + +local function remove(meta) + if not remove_all(meta) then + return false + end + + if not _exists(meta) then + perr(string.format("rm: cannot remove `%s': No such file or directory\n", meta.rel)) + return false + elseif _dir(meta) and not bRec and not (_empty(meta) and bEmptyDirs) then + if not bEmptyDirs then + perr(string.format("rm: cannot remove `%s': Is a directory\n", meta.rel)) + else + perr(string.format("rm: cannot remove `%s': Directory not empty\n", meta.rel)) + end + return false + end + + local ok = true + if promptLevel == 1 then + if _dir(meta) then + pout(string.format("rm: remove directory `%s'? ", meta.rel)) + elseif meta.link then + pout(string.format("rm: remove symbolic link `%s'? ", meta.rel)) + else -- file + pout(string.format("rm: remove regular file `%s'? ", meta.rel)) + end + + ok = confirm() + end + + if ok then + if _readonly(meta) then + perr(string.format("rm: cannot remove `%s': Is read only\n", meta.rel)) + return false + elseif not unlink(_path(meta)) then + perr(meta.rel .. ": failed to be removed\n") + ok = false + elseif bVerbose then + pout("removed '" .. meta.rel .. "'\n"); + end + end + + return ok +end + +for _,arg in ipairs(args) do + metas[#metas+1] = createMeta(arg, arg) +end + +if promptLevel == 3 and #metas > 3 then + pout(string.format("rm: remove %i arguments? ", #metas)) + if not confirm() then + return + end +end + +local ok = true +for _,meta in ipairs(metas) do + local result = remove(meta) + ok = ok and result +end + +return bForce or ok diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/rmdir.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/rmdir.lua new file mode 100755 index 00000000..98ef870f --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/rmdir.lua @@ -0,0 +1,104 @@ +local shell = require("shell") +local fs = require("filesystem") +local text = require("text") + +local args, options = shell.parse(...) + +local function usage() + print( +[[Usage: rmdir [OPTION]... DIRECTORY... +Removes the DIRECTORY(ies), if they are empty. + + -q, --ignore-fail-on-non-empty + ignore failures due solely to non-empty directories + -p, --parents remove DIRECTORY and its empty ancestors + e.g. 'rmdir -p a/b/c' is similar to 'rmdir a/b/c a/b a' + -v, --verbose output a diagnostic for every directory processed + --help display this help and exit]]) +end + +if options.help then + usage() + return 0 +end + +if #args == 0 then + io.stderr:write("rmdir: missing operand\n") + return 1 +end + +options.p = options.p or options.parents +options.v = options.v or options.verbose +options.q = options.q or options['ignore-fail-on-non-empty'] + +local ec = 0 +local function ec_bump() + ec = 1 + return 1 +end + +local function remove(path, ...) + -- check to end recursion + if path == nil then + return true + end + + if options.v then + print(string.format('rmdir: removing directory, %s', path)) + end + + local rpath = shell.resolve(path) + if path == '.' then + io.stderr:write('rmdir: failed to remove directory \'.\': Invalid argument\n') + return ec_bump() + elseif not fs.exists(rpath) then + io.stderr:write("rmdir: cannot remove " .. path .. ": path does not exist\n") + return ec_bump() + elseif fs.isLink(rpath) or not fs.isDirectory(rpath) then + io.stderr:write("rmdir: cannot remove " .. path .. ": not a directory\n") + return ec_bump() + else + local list, reason = fs.list(rpath) + + if not list then + io.stderr:write(tostring(reason)..'\n') + return ec_bump() + else + if list() then + if not options.q then + io.stderr:write("rmdir: failed to remove " .. path .. ": Directory not empty\n") + end + return ec_bump() + else + -- path exists and is empty? + local ok, reason = fs.remove(rpath) + if not ok then + io.stderr:write(tostring(reason)..'\n') + return ec_bump(), reason + end + return remove(...) -- the final return of all else + end + end + end +end + +for _,path in ipairs(args) do + -- clean up the input + path = path:gsub('/+', '/') + + local segments = {} + if options.p and path:len() > 1 and path:find('/') then + chain = text.split(path, {'/'}, true) + local prefix = '' + for _,e in ipairs(chain) do + table.insert(segments, 1, prefix .. e) + prefix = prefix .. e .. '/' + end + else + segments = {path} + end + + remove(table.unpack(segments)) +end + +return ec diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/scale.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/scale.lua new file mode 100755 index 00000000..fa6fabdc --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/scale.lua @@ -0,0 +1,17 @@ +local ecs = require("ECSAPI") +local gpu = require("component").gpu + +local arg = {...} +if arg[1] == "get" or arg[1] == "show" or arg[1] == "print" or arg[1] == "write" or arg[1] == "info" or arg[1] == "help" then + local max1, max2 = gpu.maxResolution() + local cur1, cur2 = gpu.getResolution() + local scale = cur1 / max1 * 100 + print(" ") + print("Maximum resolution: " .. max1 .. "x".. max2) + print("Current resolution: " .. cur1 .. "x" .. cur2) + print(" ") + print("Scale: " .. scale .. "%") + print(" ") +else + ecs.setScale(tonumber(arg[1]) or 1) +end diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/set.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/set.lua new file mode 100755 index 00000000..fa50073a --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/set.lua @@ -0,0 +1,25 @@ +local text = require("text") + +local args = {...} + +if #args < 1 then + for k,v in pairs(os.getenv()) do + io.write(k .. "='" .. string.gsub(v, "'", [['"'"']]) .. "'\n") + end +else + local count = 0 + for _, expr in ipairs(args) do + local e = expr:find('=') + if e then + os.setenv(expr:sub(1,e-1), expr:sub(e+1)) + else + if count == 0 then + for i = 1, os.getenv('#') do + os.setenv(i, nil) + end + end + count = count + 1 + os.setenv(count, expr) + end + end +end diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/sh.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/sh.lua new file mode 100755 index 00000000..9a7d7b2d --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/sh.lua @@ -0,0 +1,92 @@ +local event = require("event") +local shell = require("shell") +local term = require("term") +local text = require("text") +local sh = require("sh") + +local input = table.pack(...) +local args, options = shell.parse(select(3,table.unpack(input))) +if input[2] then + table.insert(args, 1, input[2]) +end + +local history = {} +shell.prime() + +if #args == 0 and (io.stdin.tty or options.i) and not options.c then + -- interactive shell. + -- source profile + if not term.isAvailable() then event.pull("term_available") end + loadfile(shell.resolve("source","lua"))("/etc/profile") + while true do + if not term.isAvailable() then -- don't clear unless we lost the term + while not term.isAvailable() do + event.pull("term_available") + end + term.clear() + end + local gpu = term.gpu() + while term.isAvailable() do + local foreground = gpu.setForeground(0xFF0000) + term.write(sh.expand(os.getenv("PS1") or "$ ")) + gpu.setForeground(foreground) + term.setCursorBlink(true) + local ok, command = pcall(term.read, history, nil, sh.hintHandler) + if not ok then + if command == "interrupted" then -- hard interrupt + io.write("^C\n") + break + elseif not term.isAvailable() then + break + else -- crash? + io.stderr:write("\nshell crashed: " .. tostring(command) .. "\n") + break + end + end + if not command then + if command == false then + break -- soft interrupt + end + io.write("exit\n") -- pipe closed + return -- eof + end + command = text.trim(command) + if command == "exit" then + return + elseif command ~= "" then + local result, reason = sh.execute(_ENV, command) + if term.getCursor() > 1 then + print() + end + if not result then + io.stderr:write((reason and tostring(reason) or "unknown error") .. "\n") + end + end + end + end +elseif #args == 0 and not io.stdin.tty then + while true do + io.write(sh.expand(os.getenv("PS1") or "$ ")) + local command = io.read("*l") + if not command then + command = "exit" + io.write(command,"\n") + end + command = text.trim(command) + if command == "exit" then + return + elseif command ~= "" then + local result, reason = os.execute(command) + if not result then + io.stderr:write((reason and tostring(reason) or "unknown error") .. "\n") + end + end + end +else + -- execute command. + local result = table.pack(sh.execute(...)) + if not result[1] then + error(result[2], 0) + end + return table.unpack(result, 2) +end diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/shutdown.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/shutdown.lua new file mode 100755 index 00000000..ab9ccccb --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/shutdown.lua @@ -0,0 +1,5 @@ +local computer = require("computer") +local term = require("term") + +term.clear() +computer.shutdown() \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/sleep.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/sleep.lua new file mode 100755 index 00000000..4612715d --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/sleep.lua @@ -0,0 +1,61 @@ +local shell = require('shell') +local args, options = shell.parse(...) + +if options.help then + print([[Usage: sleep NUMBER[SUFFIX]... +Pause for NUMBER seconds. SUFFIX may be 's' for seconds (the default), +'m' for minutes, 'h' for hours or 'd' for days. Unlike most implementations +that require NUMBER be an integer, here NUMBER may be an arbitrary floating +point number. Given two or more arguments, pause for the amount of time +specified by the sum of their values.]]) +end + +local function help(bad_arg) + print("sleep: invalid option -- '"..tostring(bad_arg).."'") + print("Try 'sleep --help' for more information.") +end + +local function time_type_multiplier(time_type) + if not time_type or #time_type == 0 or time_type == 's' then + return 1 + elseif time_type == 'm' then + return 60 + elseif time_type == 'h' then + return 60 * 60 + elseif time_type == 'd' then + return 60 * 60 * 24 + end + + -- weird error, my bad + assert(false,'bug parsing parameter:'..tostring(time_type)) +end + +options.help = nil +if next(options) then + help(next(options)) + return 1 +end + +local total_time = 0 + +for _,v in ipairs(args) do + local interval = v:match('^%d+%.?%d*[smhd]?$') + if not interval then + help(v) + return 1 + end + + local time_type = interval:match('[smhd]') or '' + interval = interval:sub(1, -#time_type-1) + interval = tonumber(interval) + + if interval < 0 then + help(v) + return 1 + end + + interval = time_type_multiplier(time_type) * interval + total_time = total_time + interval +end + +os.sleep(total_time) diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/source.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/source.lua new file mode 100755 index 00000000..a1b04071 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/source.lua @@ -0,0 +1,31 @@ +local shell = require("shell") +local fs = require("filesystem") +local sh = require("sh") + +local args, options = shell.parse(...) + +if #args ~= 1 then + io.stderr:write("specify a single file to source\n"); + return 1 +end + +local file, reason = io.open(args[1], "r") + +if not file then + if not options.q then + io.stderr:write(string.format("could not source %s because: %s\n", args[1], reason)); + end + return 1 +else + local status, reason = xpcall(function() + repeat + local line = file:read() + if line then + sh.execute(nil, line) + end + until not line + end, function(msg) return {msg, debug.traceback()} end) + + file:close() + if not status and reason then assert(false, tostring(reason[1]) .."\n".. tostring(reason[2])) end +end diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/time.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/time.lua new file mode 100755 index 00000000..470597b5 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/time.lua @@ -0,0 +1,18 @@ +local computer = require('computer') +local sh = require('sh') + +local real_before, cpu_before = computer.uptime(), os.clock() +local cmd_result = 0 +if ... then + sh.execute(nil, ...) + cmd_result = sh.getLastExitCode() +end +local real_after, cpu_after = computer.uptime(), os.clock() + +local real_diff = real_after - real_before +local cpu_diff = cpu_after - cpu_before + +print(string.format('real%5dm%.3fs', math.floor(real_diff/60), real_diff%60)) +print(string.format('cpu %5dm%.3fs', math.floor(cpu_diff/60), cpu_diff%60)) + +return cmd_result diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/touch.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/touch.lua new file mode 100755 index 00000000..74e541d5 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/touch.lua @@ -0,0 +1,54 @@ +--[[Lua implementation of the UN*X touch command--]] +local shell = require("shell") +local fs = require("filesystem") + +local args, options = shell.parse(...) + +local function usage() + print( +[[Usage: touch [OPTION]... FILE... +Update the modification times of each FILE to the current time. +A FILE argument that does not exist is created empty, unless -c is supplied. + + -c, --no-create do not create any files + --help display this help and exit]]) +end + +if options.help then + usage() + return 0 +elseif #args == 0 then + io.stderr:write("touch: missing operand\n") + return 1 +end + +options.c = options.c or options["no-create"] +local errors = 0 + +for _,arg in ipairs(args) do + local path = shell.resolve(arg) + + if fs.isDirectory(path) then + io.stderr:write(string.format("`%s' ignored: directories not supported\n", arg)) + else + local real, reason = fs.realPath(path) + if real then + local file + if fs.exists(real) or not options.c then + file = io.open(real, "a") + end + if not file then + real = options.c + reason = "permission denied" + else + file:close() + end + end + if not real then + io.stderr:write(string.format("touch: cannot touch `%s': %s\n", arg, reason)) + errors = 1 + end + end +end + +return errors diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/umount.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/umount.lua new file mode 100755 index 00000000..07940856 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/umount.lua @@ -0,0 +1,37 @@ +local fs = require("filesystem") +local shell = require("shell") + +local args, options = shell.parse(...) + +if #args < 1 then + io.write("Usage: umount [-a] \n") + io.write(" -a Remove any mounts by file system label or address instead of by path. Note that the address may be abbreviated.\n") + return 1 +end + +local proxy, reason +if options.a then + proxy, reason = fs.proxy(args[1]) + if proxy then + proxy = proxy.address + end +else + local path = shell.resolve(args[1]) + proxy, reason = fs.get(path) + if proxy then + proxy = reason -- = path + if proxy ~= path then + io.stderr:write("not a mount point\n") + return 1 + end + end +end +if not proxy then + io.stderr:write(tostring(reason)..'\n') + return 1 +end + +if not fs.umount(proxy) then + io.stderr:write("nothing to unmount here\n") + return 1 +end diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/unalias.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/unalias.lua new file mode 100755 index 00000000..ceea94d4 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/unalias.lua @@ -0,0 +1,19 @@ +local shell = require("shell") + +local args = shell.parse(...) +if #args < 1 then + io.write("Usage: unalias ...\n") + return 2 +end +local e = 0 + +for _,arg in ipairs(args) do + local result = shell.getAlias(arg) + if not result then + io.stderr:write(string.format("unalias: %s: not found\n", arg)) + e = 1 + else + shell.setAlias(arg, nil) + end +end +return e diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/unset.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/unset.lua new file mode 100755 index 00000000..1fb68e47 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/unset.lua @@ -0,0 +1,9 @@ +local args = {...} + +if #args < 1 then + io.write("Usage: unset [ [...]]\n") +else + for _, k in ipairs(args) do + os.setenv(k, nil) + end +end diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/uptime.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/uptime.lua new file mode 100755 index 00000000..34d502c7 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/uptime.lua @@ -0,0 +1,13 @@ +local computer = require("computer") + +local seconds = math.floor(computer.uptime()) +local minutes, hours = 0, 0 +if seconds >= 60 then + minutes = math.floor(seconds / 60) + seconds = seconds % 60 +end +if minutes >= 60 then + hours = math.floor(minutes / 60) + minutes = minutes % 60 +end +io.write(string.format("%02d:%02d:%02d\n", hours, minutes, seconds)) diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/useradd.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/useradd.lua new file mode 100755 index 00000000..56711c22 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/useradd.lua @@ -0,0 +1,14 @@ +local computer = require("computer") +local shell = require("shell") + +local args = shell.parse(...) +if #args ~= 1 then + io.write("Usage: useradd \n") + return 1 +end + +local result, reason = computer.addUser(args[1]) +if not result then + io.stderr:write(reason..'\n') + return 1 +end diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/userdel.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/userdel.lua new file mode 100755 index 00000000..ec858d61 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/userdel.lua @@ -0,0 +1,13 @@ +local computer = require("computer") +local shell = require("shell") + +local args = shell.parse(...) +if #args ~= 1 then + io.write("Usage: userdel \n") + return 1 +end + +if not computer.removeUser(args[1]) then + io.stderr:write("no such user\n") + return 1 +end diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/wget.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/wget.lua new file mode 100755 index 00000000..135ce671 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/wget.lua @@ -0,0 +1,115 @@ +local component = require("component") +local fs = require("filesystem") +local internet = require("internet") +local shell = require("shell") +local text = require("text") + +if not component.isAvailable("internet") then + io.stderr:write("This program requires an internet card to run.") + return +end + +local args, options = shell.parse(...) +options.q = options.q or options.Q + +if #args < 1 then + io.write("Usage: wget [-fq] []\n") + io.write(" -f: Force overwriting existing files.\n") + io.write(" -q: Quiet mode - no status messages.\n") + io.write(" -Q: Superquiet mode - no error messages.") + return +end + +local url = text.trim(args[1]) +local filename = args[2] +if not filename then + filename = url + local index = string.find(filename, "/[^/]*$") + if index then + filename = string.sub(filename, index + 1) + end + index = string.find(filename, "?", 1, true) + if index then + filename = string.sub(filename, 1, index - 1) + end +end +filename = text.trim(filename) +if filename == "" then + if not options.Q then + io.stderr:write("could not infer filename, please specify one") + end + return nil, "missing target filename" -- for programs using wget as a function +end +filename = shell.resolve(filename) + +local preexisted +if fs.exists(filename) then + preexisted = true + if not options.f then + if not options.Q then + io.stderr:write("file already exists") + end + return nil, "file already exists" -- for programs using wget as a function + end +end + +local f, reason = io.open(filename, "a") +if not f then + if not options.Q then + io.stderr:write("failed opening file for writing: " .. reason) + end + return nil, "failed opening file for writing: " .. reason -- for programs using wget as a function +end +f:close() +f = nil + +if not options.q then + io.write("Downloading... ") +end +local result, response = pcall(internet.request, url) +if result then + local result, reason = pcall(function() + for chunk in response do + if not f then + f, reason = io.open(filename, "wb") + assert(f, "failed opening file for writing: " .. tostring(reason)) + end + f:write(chunk) + end + end) + if not result then + if not options.q then + io.stderr:write("failed.\n") + end + if f then + f:close() + if not preexisted then + fs.remove(filename) + end + end + if not options.Q then + io.stderr:write("HTTP request failed: " .. reason .. "\n") + end + return nil, reason -- for programs using wget as a function + end + if not options.q then + io.write("success.\n") + end + + if f then + f:close() + end + + if not options.q then + io.write("Saved data to " .. filename .. "\n") + end +else + if not options.q then + io.write("failed.\n") + end + if not options.Q then + io.stderr:write("HTTP request failed: " .. response .. "\n") + end + return nil, response -- for programs using wget as a function +end +return true -- for programs using wget as a function diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/which.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/which.lua new file mode 100755 index 00000000..503d4429 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/which.lua @@ -0,0 +1,25 @@ +local shell = require("shell") + +local args = shell.parse(...) +if #args == 0 then + io.write("Usage: which \n") + return 255 +end + +for i = 1, #args do + local result, reason = shell.resolve(args[i], "lua") + + if not result then + result = shell.getAlias(args[i]) + if result then + result = args[i] .. ": aliased to " .. result + end + end + + if result then + print(result) + else + io.stderr:write(args[i] .. ": " .. reason .. "\n") + return 1 + end +end diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/yes.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/yes.lua new file mode 100755 index 00000000..715ca290 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/bin/yes.lua @@ -0,0 +1,32 @@ +--[[Lua implementation of the UN*X yes command--]] +local shell = require("shell") + +local args, options = shell.parse(...) + +if options.V or options.version then + io.write("yes v:1.0-3\n") + io.write("Inspired by functionality of yes from GNU coreutils\n") + return 0 +end + +if options.h or options.help then + io.write("Usage: yes [string]...\n") + io.write("OR: yes [-V/h]\n") + io.write("\n") + io.write("yes prints the command line arguments, or 'y', until is killed.\n") + io.write("\n") + io.write("Options:\n") + io.write(" -V, --version Version\n") + io.write(" -h, --help This help\n") + return 0 +end + +local msg = #args == 0 and 'y' or table.concat(args, ' ') +msg = msg .. '\n' + +while io.write(msg) do + if io.stdout.tty then + os.sleep(0) + end +end +return 0 diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/boot/00_base.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/boot/00_base.lua new file mode 100755 index 00000000..58158434 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/boot/00_base.lua @@ -0,0 +1,45 @@ +function loadfile(filename, mode, env) + local handle, reason = require("filesystem").open(filename) + if not handle then + return nil, reason + end + local buffer = {} + while true do + local data, reason = handle:read(1024) + if not data then + handle:close() + if reason then + return nil, reason + end + break + end + table.insert(buffer, data) + end + buffer[1] = (buffer[1] or ""):gsub("^#![^\n]+", "") -- remove shebang if any + buffer = table.concat(buffer) + return load(buffer, "=" .. filename, mode, env) +end + +function dofile(filename) + local program, reason = loadfile(filename) + if not program then + return error(reason .. ':' .. filename, 0) + end + return program() +end + +function print(...) + local args = table.pack(...) + local stdout = io.stdout + stdout:setvbuf("line") + for i = 1, args.n do + local arg = tostring(args[i]) + if i > 1 then + arg = "\t" .. arg + end + stdout:write(arg) + end + stdout:write("\n") + stdout:setvbuf("no") + stdout:flush() +end diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/boot/01_process.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/boot/01_process.lua new file mode 100755 index 00000000..df7a1798 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/boot/01_process.lua @@ -0,0 +1,64 @@ +local process = require("process") + +--Initialize coroutine library-- +local _coroutine = coroutine -- real coroutine backend + +_G.coroutine = {} +package.loaded.coroutine = _G.coroutine + +for key,value in pairs(_coroutine) do + if type(value) == "function" and value ~= "running" and value ~= "create" then + _G.coroutine[key] = function(...) + local thread = _coroutine.running() + local info = process.info(thread) + -- note the gc thread does not have a process info + assert(info,"process not found for " .. tostring(thread)) + local data = info.data + local co = data.coroutine_handler + local handler = co[key] + return handler(...) + end + else + _G.coroutine[key] = value + end +end + +local init_thread = _coroutine.running() +local init_load = _G.load + +_G.load = function(ld, source, mode, env) + env = env or select(2, process.running()) + return init_load(ld, source, mode, env) +end + +local kernel_create = _coroutine.create +_coroutine.create = function(f,standAlone) + local co = kernel_create(f) + if not standAlone then + table.insert(process.findProcess().instances, co) + end + return co +end + +_coroutine.wrap = function(f) + local thread = coroutine.create(f) + return function(...) + local result_pack = table.pack(coroutine.resume(thread, ...)) + local result, reason = result_pack[1], result_pack[2] + assert(result, reason) + return select(2, table.unpack(result_pack)) + end +end + +process.list[init_thread] = { + path = "/init.lua", + command = "init", + env = _ENV, + data = + { + vars={}, + io={}, --init will populate this + coroutine_handler=setmetatable({}, {__index=_coroutine}) + }, + instances = setmetatable({}, {__mode="v"}) +} diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/boot/02_os.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/boot/02_os.lua new file mode 100755 index 00000000..ee6a3964 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/boot/02_os.lua @@ -0,0 +1,80 @@ +local computer = require("computer") +local event = require("event") +local fs = require("filesystem") +local shell = require("shell") +local unicode = require("unicode") +local process = require("process") + +local function env() + return process.info().data.vars +end + +os.execute = function(command) + if not command then + return type(shell) == "table" + end + return shell.execute(command) +end + +function os.exit(code) + error({reason="terminated", code=code}, 0) +end + +function os.getenv(varname) + local env = env() + if not varname then + return env + end + return env[varname] +end + +function os.setenv(varname, value) + checkArg(1, varname, "string", "number") + if value == nil then + env()[varname] = nil + else + local success, val = pcall(tostring, value) + if success then + env()[varname] = val + return val + else + return nil, val + end + end +end + +function os.remove(...) + return fs.remove(...) +end + +function os.rename(...) + return fs.rename(...) +end + +function os.sleep(timeout) + checkArg(1, timeout, "number", "nil") + local deadline = computer.uptime() + (timeout or 0) + repeat + event.pull(deadline - computer.uptime()) + until computer.uptime() >= deadline +end + +function os.tmpname() + local path = os.getenv("TMPDIR") or "/tmp" + if fs.exists(path) then + for i = 1, 10 do + local name = fs.concat(path, tostring(math.random(1, 0x7FFFFFFF))) + if not fs.exists(name) then + return name + end + end + end +end + +os.setenv("PATH", "/bin:/usr/bin:/home/bin:.") +os.setenv("TMP", "/tmp") -- Deprecated +os.setenv("TMPDIR", "/tmp") + +if computer.tmpAddress() then + fs.mount(computer.tmpAddress(), os.getenv("TMPDIR") or "/tmp") +end diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/boot/03_io.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/boot/03_io.lua new file mode 100755 index 00000000..34a58b17 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/boot/03_io.lua @@ -0,0 +1,99 @@ +local buffer = require("buffer") +local term = require("term") + +local io_open = io.open +function io.open(path, mode) + return io_open(require("shell").resolve(path), mode) +end + +local stdinStream = {handle="stdin"} +local stdoutStream = {handle="stdout"} +local stderrStream = {handle="stderr"} +local stdinHistory = {} + +local function badFileDescriptor() + return nil, "bad file descriptor" +end + +function stdinStream:close() + return nil, "cannot close standard file" +end +stdoutStream.close = stdinStream.close +stderrStream.close = stdinStream.close + +function stdinStream:read(n, dobreak) + stdinHistory.dobreak = dobreak + local result = term.readKeyboard(stdinHistory) + return result +end + +function stdoutStream:write(str) + term.drawText(str, self.wrap ~= false) + return self +end + +function stderrStream:write(str) + local gpu = term.gpu() + local set_depth = gpu and gpu.getDepth() and gpu.getDepth() > 1 + + if set_depth then + set_depth = gpu.setForeground(0xFF0000) + end + + term.drawText(str, true) + + if set_depth then + gpu.setForeground(set_depth) + end + + return self +end + +stdinStream.seek = badFileDescriptor +stdinStream.write = badFileDescriptor +stdoutStream.read = badFileDescriptor +stdoutStream.seek = badFileDescriptor +stderrStream.read = badFileDescriptor +stderrStream.seek = badFileDescriptor + +local core_stdin = buffer.new("r", stdinStream) +local core_stdout = buffer.new("w", stdoutStream) +local core_stderr = buffer.new("w", stderrStream) + +core_stdout:setvbuf("no") +core_stderr:setvbuf("no") +core_stdin.tty = true +core_stdout.tty = true +core_stderr.tty = true + +core_stdin.close = stdinStream.close +core_stdout.close = stdinStream.close +core_stderr.close = stdinStream.close + +local fd_map = +{ + -- key name => method name + stdin = 'input', + stdout = 'output', + stderr = 'error' +} + +local io_mt = getmetatable(io) or {} +io_mt.__index = function(t, k) + if fd_map[k] then + return io[fd_map[k]]() + end +end +io_mt.__newindex = function(t, k, v) + if fd_map[k] then + io[fd_map[k]](v) + else + rawset(io, k, v) + end +end + +setmetatable(io, io_mt) + +io.stdin = core_stdin +io.stdout = core_stdout +io.stderr = core_stderr diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/boot/04_component.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/boot/04_component.lua new file mode 100755 index 00000000..8d620e79 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/boot/04_component.lua @@ -0,0 +1,204 @@ +local component = require("component") +local computer = require("computer") +local event = require("event") + +local adding = {} +local removing = {} +local primaries = {} + +------------------------------------------------------------------------------- + +-- This allows writing component.modem.open(123) instead of writing +-- component.getPrimary("modem").open(123), which may be nicer to read. +setmetatable(component, { + __index = function(_, key) + return component.getPrimary(key) + end, + __pairs = function(self) + local parent = false + return function(_, key) + if parent then + return next(primaries, key) + else + local k, v = next(self, key) + if not k then + parent = true + return next(primaries) + else + return k, v + end + end + end + end +}) + +function component.get(address, componentType) + checkArg(1, address, "string") + checkArg(2, componentType, "string", "nil") + for c in component.list(componentType, true) do + if c:sub(1, address:len()) == address then + return c + end + end + return nil, "no such component" +end + +function component.isAvailable(componentType) + checkArg(1, componentType, "string") + if not primaries[componentType] and not adding[componentType] then + -- This is mostly to avoid out of memory errors preventing proxy + -- creation cause confusion by trying to create the proxy again, + -- causing the oom error to be thrown again. + component.setPrimary(componentType, component.list(componentType, true)()) + end + return primaries[componentType] ~= nil +end + +function component.isPrimary(address) + local componentType = component.type(address) + if componentType then + if component.isAvailable(componentType) then + return primaries[componentType].address == address + end + end + return false +end + +function component.getPrimary(componentType) + checkArg(1, componentType, "string") + assert(component.isAvailable(componentType), + "no primary '" .. componentType .. "' available") + return primaries[componentType] +end + +function component.setPrimary(componentType, address) + checkArg(1, componentType, "string") + checkArg(2, address, "string", "nil") + if address ~= nil then + address = component.get(address, componentType) + assert(address, "no such component") + end + + local wasAvailable = primaries[componentType] + if wasAvailable and address == wasAvailable.address then + return + end + local wasAdding = adding[componentType] + if wasAdding and address == wasAdding.address then + return + end + if wasAdding then + event.cancel(wasAdding.timer) + end + primaries[componentType] = nil + adding[componentType] = nil + + local primary = address and component.proxy(address) or nil + if wasAvailable then + computer.pushSignal("component_unavailable", componentType) + end + if primary then + if wasAvailable or wasAdding then + adding[componentType] = { + address=address, + proxy = primary, + timer=event.timer(0.1, function() + adding[componentType] = nil + primaries[componentType] = primary + computer.pushSignal("component_available", componentType) + end) + } + else + primaries[componentType] = primary + computer.pushSignal("component_available", componentType) + end + end +end + +------------------------------------------------------------------------------- + +local function onComponentAdded(_, address, componentType) + local prev = primaries[componentType] or (adding[componentType] and adding[componentType].proxy) + + if prev then + -- special handlers -- some components are just better at being primary + if componentType == "screen" then + --the primary has no keyboards but we do + if #prev.getKeyboards() == 0 then + local first_kb = component.invoke(address, 'getKeyboards')[1] + if first_kb then + -- just in case our kb failed to achieve primary + -- possible if existing primary keyboard became primary first without a screen + -- then prev (a screen) was added without a keyboard + -- and then we attached this screen+kb pair, and our kb fired first - failing to achieve primary + -- also, our kb may fire right after this, which is fine + component.setPrimary("keyboard", first_kb) + prev = nil -- nil meaning we should take this new one over the previous + end + end + elseif componentType == "keyboard" then + -- to reduce signal noise, if this kb is also the prev, we do not need to reset primary + if address ~= prev.address then + --keyboards never replace primary keyboards unless the are the only keyboard on the primary screen + local current_screen = primaries.screen or (adding.screen and adding.screen.proxy) + --if there is not yet a screen, do not use this keyboard, it's not any better + if current_screen then + -- the next phase is complicated + -- there is already a screen and there is already a keyboard + -- this keyboard is only better if this is a keyboard of the primary screen AND the current keyboard is not + -- i don't think we can trust kb order (1st vs 2nd), 2nd could fire first + -- but if there are two kbs on a screen, we can give preferred treatment to the first + -- thus, assume 2nd is not attached for the purposes of primary kb + -- and THUS, whichever (if either) is the 1st kb of the current screen + -- this is only possible if + -- 1. the only kb on the system (current) has no screen + -- 2. a screen is added without a kb + -- 3. this kb is added later manually + + -- prev is true when addr is not equal to the primary keyboard of the current screen -- meaning + -- when addr is different, and thus it is not the primary keyboard, then we ignore this + -- keyboard, and keep the previous + -- prev is false means we should take this new keyboard + prev = address ~= current_screen.getKeyboards()[1] + end + end + end + end + + if not prev then + component.setPrimary(componentType, address) + end +end + +local function onComponentRemoved(_, address, componentType) + if primaries[componentType] and primaries[componentType].address == address or + adding[componentType] and adding[componentType].address == address + then + local next = component.list(componentType, true)() + component.setPrimary(componentType, next) + + if componentType == "screen" and next then + -- setPrimary already set the proxy (if successful) + local proxy = (primaries.screen or (adding.screen and adding.screen.proxy)) + if proxy then + -- if a screen is removed, and the primary keyboard is actually attached to another, non-primary, screen + -- then the `next` screen, if it has a keyboard, should TAKE priority + local next_kb = proxy.getKeyboards()[1] -- costly, don't call this method often + local old_kb = primaries.keyboard or adding.keyboard + -- if the next screen doesn't have a kb, this operation is without purpose, leave things as they are + -- if there was no previous kb, use the new one + if next_kb and (not old_kb or old_kb.address ~= next_kb) then + component.setPrimary("keyboard", next_kb) + end + end + end + end +end + +event.listen("component_added", onComponentAdded) +event.listen("component_removed", onComponentRemoved) + +if _G.boot_screen then + component.setPrimary("screen", _G.boot_screen) +end +_G.boot_screen = nil diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/boot/10_devfs.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/boot/10_devfs.lua new file mode 100755 index 00000000..ee075090 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/boot/10_devfs.lua @@ -0,0 +1,23 @@ +require("filesystem").mount( +setmetatable({ + address = "f5501a9b-9c23-1e7a-4afe-4b65eed9b88a" +}, +{ + __index=function(tbl,key) + local result = + ({ + getLabel = "devfs", + spaceTotal = 0, + spaceUsed = 0, + isReadOnly = false, + })[key] + + if result ~= nil then + return function() return result end + end + local lib = require("devfs") + lib.register(tbl) + return lib.proxy[key] + end +}), "/dev") + diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/boot/90_filesystem.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/boot/90_filesystem.lua new file mode 100755 index 00000000..5b0da6ca --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/boot/90_filesystem.lua @@ -0,0 +1,65 @@ +local component = require("component") +local event = require("event") +local fs = require("filesystem") +local shell = require("shell") + +local isInitialized, pendingAutoruns = false, {} + +local function onInit() + isInitialized = true + for _, run in ipairs(pendingAutoruns) do + local result, reason = pcall(run) + if not result then + local path = fs.concat(os.getenv("TMPDIR") or "/tmp", "event.log") + local log = io.open(path, "a") + if log then + log:write(tostring(result) .. ":" .. tostring(reason) .. "\n") + log:close() + end + end + end + pendingAutoruns = nil +end + +local function onComponentAdded(_, address, componentType) + if componentType == "filesystem" and require("computer").tmpAddress() ~= address then + local proxy = component.proxy(address) + if proxy then + local name = address:sub(1, 3) + while fs.exists(fs.concat("/mnt", name)) and + name:len() < address:len() -- just to be on the safe side + do + name = address:sub(1, name:len() + 1) + end + name = fs.concat("/mnt", name) + fs.mount(proxy, name) + if fs.isAutorunEnabled() then + local file = shell.resolve(fs.concat(name, "autorun"), "lua") or + shell.resolve(fs.concat(name, ".autorun"), "lua") + if file then + local run = function() + assert(shell.execute(file, _ENV, proxy)) + end + if isInitialized then + run() + else + table.insert(pendingAutoruns, run) + end + end + end + end + end +end + +local function onComponentRemoved(_, address, componentType) + if componentType == "filesystem" then + if fs.get(shell.getWorkingDirectory()).address == address then + shell.setWorkingDirectory("/") + end + fs.umount(address) + end +end + +event.listen("init", onInit) +event.listen("component_added", onComponentAdded) +event.listen("component_removed", onComponentRemoved) diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/boot/91_gpu.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/boot/91_gpu.lua new file mode 100755 index 00000000..7475b11c --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/boot/91_gpu.lua @@ -0,0 +1,15 @@ +local component = require("component") +local event = require("event") + +local function onComponentAvailable(_, componentType) + if (componentType == "screen" and component.isAvailable("gpu")) or + (componentType == "gpu" and component.isAvailable("screen")) + then + component.gpu.bind(component.screen.address) + local depth = 2^(component.gpu.getDepth()) + os.setenv("TERM", "term-"..depth.."color") + require("computer").pushSignal("gpu_bound", component.gpu.address, component.screen.address) + end +end + +event.listen("component_available", onComponentAvailable) diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/boot/92_keyboard.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/boot/92_keyboard.lua new file mode 100755 index 00000000..3865c45d --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/boot/92_keyboard.lua @@ -0,0 +1,40 @@ +local component = require("component") +local event = require("event") +local keyboard = require("keyboard") + +local function onKeyDown(_, address, char, code) + if keyboard.pressedChars[address] then + keyboard.pressedChars[address][char] = true + keyboard.pressedCodes[address][code] = true + end +end + +local function onKeyUp(_, address, char, code) + if keyboard.pressedChars[address] then + keyboard.pressedChars[address][char] = nil + keyboard.pressedCodes[address][code] = nil + end +end + +local function onComponentAdded(_, address, componentType) + if componentType == "keyboard" then + keyboard.pressedChars[address] = {} + keyboard.pressedCodes[address] = {} + end +end + +local function onComponentRemoved(_, address, componentType) + if componentType == "keyboard" then + keyboard.pressedChars[address] = nil + keyboard.pressedCodes[address] = nil + end +end + +for address in component.list("keyboard", true) do + onComponentAdded("component_added", address, "keyboard") +end + +event.listen("key_down", onKeyDown) +event.listen("key_up", onKeyUp) +event.listen("component_added", onComponentAdded) +event.listen("component_removed", onComponentRemoved) diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/boot/93_term.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/boot/93_term.lua new file mode 100755 index 00000000..eca706a1 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/boot/93_term.lua @@ -0,0 +1,59 @@ +local component = require("component") +local computer = require("computer") +local event = require("event") +local term = require("term") +local process = require("process") + +-- this should be the init level process +process.info().data.window = term.internal.open() + +event.listen("gpu_bound", function(ename, gpu) + gpu=component.proxy(gpu) + term.bind(gpu) + computer.pushSignal("term_available") +end) + +local function components_changed(ename, address, type) + local window = term.internal.window() + if not window then + return + end + + if ename == "component_available" or ename == "component_unavailable" then + type = address + end + + if ename == "component_removed" or ename == "component_unavailable" then + -- address can be type, when ename is *_unavailable, but *_removed works here and that's all we need + if type == "gpu" and window.gpu.address == address then + window.gpu = nil + window.keyboard = nil + elseif type == "keyboard" then + -- we could check if this was our keyboard + -- i.e. if address == window.keyboard + -- but it is also simple for the terminal to + -- recheck what kb to use + window.keyboard = nil + end + elseif (ename == "component_added" or ename == "component_available") and type == "keyboard" then + -- we need to clear the current terminals cached keyboard (if any) when + -- a new keyboard becomes available. This is in case the new keyboard was + -- attached to the terminal's window. The terminal library has the code to + -- determine what the best keyboard to use is, but here we'll just set the + -- cache to nil to force term library to reload it. An alternative to this + -- method would be to make sure the terminal library doesn't cache the + -- wrong keybaord to begin with but, users may actually expect that any + -- primary keyboard is a valid keyboard (weird, in my opinion) + window.keyboard = nil + end + + if (type == "screen" or type == "gpu") and not term.isAvailable() then + computer.pushSignal("term_unavailable") + end +end + +event.listen("component_removed", components_changed) +event.listen("component_added", components_changed) +event.listen("component_available", components_changed) +event.listen("component_unavailable", components_changed) + diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/boot/94_shell.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/boot/94_shell.lua new file mode 100755 index 00000000..3074ce6e --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/boot/94_shell.lua @@ -0,0 +1,10 @@ +local shell = require("shell") + +require("event").listen("init", function() + local file = io.open("/etc/hostname") + if file then + os.setenv("HOSTNAME", file:read("*l")) + os.setenv("PS1", "$HOSTNAME:$PWD# ") + file:close() + end +end) diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/boot/99_rc.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/boot/99_rc.lua new file mode 100755 index 00000000..7a62d02c --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/boot/99_rc.lua @@ -0,0 +1,6 @@ +-- Run all enabled rc scripts. +local shell = require("shell") +local rc = shell.resolve("rc", "lua") +if rc then + dofile(rc) +end diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/braillePixels.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/braillePixels.lua new file mode 100644 index 00000000..8441d5d3 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/braillePixels.lua @@ -0,0 +1 @@ +{[1]="⣀",[2]="⠉",[3]="⡇",[4]="⢸"} \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/brailleRenderer.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/brailleRenderer.lua new file mode 100755 index 00000000..f17dee82 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/brailleRenderer.lua @@ -0,0 +1,51 @@ + +local unicode = require("unicode") +----------------------------------------------------------------------------------------------------------------------------- + +local function getBrailleChar(a, b, c, d, e, f, g, h) + return unicode.char(10240 + 128*h + 64*g + 32*f + 16*d + 8*b + 4*e + 2*c + a) +end + +table.toFile("braillePixels.lua", + { + getBrailleChar( + 0, 0, + 0, 0, + 0, 0, + 1, 1 + ), + getBrailleChar( + 1, 1, + 0, 0, + 0, 0, + 0, 0 + ), + getBrailleChar( + 1, 0, + 1, 0, + 1, 0, + 1, 0 + ), + getBrailleChar( + 0, 1, + 0, 1, + 0, 1, + 0, 1 + ) + } +) + +----------------------------------------------------------------------------------------------------------------------------- + +return brailleRenderer + + + + + + + + + + + diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/brickGame.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/brickGame.lua new file mode 100755 index 00000000..b9763ff7 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/brickGame.lua @@ -0,0 +1,38 @@ +local buffer = require("doubleBuffering") +local color = require("color") +local GUI = dofile("/lib/GUI.lua") + +local brickGame = {} + +------------------------------------------------------------------------------------------ + +local function brickGameDraw(object) + buffer.square(object.x + 1, object.y, object.width - 2, 1, object.colors.shade) + buffer.square(object.x, object.y + 1, object.width, , object.colors.shade) + +end + +function brickGame.new(x, y, width, height, caseColor, screenColor, screenPixelColor) + local object = GUI.object(x, y, width, height) + object.colors = { + case = caseColor, + screen = screenColor, + pixel = screenPixelColor, + } + + object.screen = {} + for j = 1, #object.screen do + object.screen[j] = {} + for i = 1, #object.screen[j] do + object.screen[j][i] = false + end + end + object.draw = brickGameDraw + + return object +end + + +------------------------------------------------------------------------------------------ + +return brickGame diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/draconic.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/draconic.lua new file mode 100755 index 00000000..7db21f34 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/draconic.lua @@ -0,0 +1,214 @@ + +---------------------------------------------------- Libraries ---------------------------------------------------- + +package.loaded.windows = nil +package.loaded.GUI = nil + +require("advancedLua") +local computer = require("computer") +local component = require("component") +local fs = require("filesystem") +local buffer = require("doubleBuffering") +local GUI = require("GUI") +local MineOSCore = require("MineOSCore") +local event = require("event") +local unicode = require("unicode") +local image = require("image") +local bigLetters = require("bigLetters") + +---------------------------------------------------- Constants ---------------------------------------------------- + +local mainWindow +local reactorInfo +local reactor, fieldFluxGate, outputFluxGate, rfstorage +local fieldFluxGateAddress = "fe13faa4-88b2-4291-922f-ef00b9be1fee" +local outputFluxGateAddress = "34f8f61f-6dc8-4563-95ee-5ad991e5a41a" +local rfstorageAddress = "b7ac96a5-a6bb-4b58-b066-e0261ac8cfbf" + +local GUIUpdateDelay = 1 +local GUIUpdateTimer = 0 + +local function drawBigText(object) + bigLetters.drawText(object.x, object.y, object.color, object.text) +end + +local function newBigText(x, y, color, text) + local object = GUI.object(x, y, 1, 5) + object.color = color + object.draw = drawBigText + object.text = text + return object +end + +local function addNewChartValue(chart, value) + chart.historyIndex = (chart.historyIndex or 1) + 1 + table.insert(chart.values, {chart.historyIndex, value}) + if #chart.values > tonumber(mainWindow.chartHistorySizeTextBox.text) then table.remove(chart.values, 1) end +end + +local function newChart(x, y, width, height, color, yPrefix, chartName) + mainWindow:addChild(GUI.label(x, y, width, 1, 0xEEEEEE, chartName)); y = y + 2 + return mainWindow:addChart(x, y, width, height, 0xFFFFFF, 0xBBBBBB, 0x777777, color, 0.35, 0.3, " s", yPrefix, true, {}) +end + +local function log(text) + table.insert(mainWindow.logConsole.lines, text) + if #mainWindow.logConsole.lines > mainWindow.logConsole.height then table.remove(mainWindow.logConsole.lines, 1) end +end + +local function createWindow() + mainWindow = GUI.fullScreenContainer() + mainWindow.backgroundPanel = mainWindow:addPanel(1, 1, mainWindow.width, mainWindow.height, 0x1B1B1B) + + local chartWidth, chartHeight = 40, math.floor((mainWindow.height - 10) / 3) + local yChart = 2 + mainWindow.temperatureChart = newChart(mainWindow.width - chartWidth - 1, yChart, chartWidth, chartHeight, 0xFF5555, " °C", "Core temperature") + mainWindow.enrgyChart = newChart(3, yChart, chartWidth, chartHeight, 0xFFDB40, " RF/t", "Generation rate"); yChart = yChart + chartHeight + 3 + mainWindow.fieldChart = newChart(mainWindow.width - chartWidth - 1, yChart, chartWidth, chartHeight, 0x33B6FF, "%", "Containment field") + mainWindow.eergySaturationChart = newChart(3, yChart, chartWidth, chartHeight, 0x6624FF, "%", "Energy saturation"); yChart = yChart + chartHeight + 3 + mainWindow.storageChart = newChart(mainWindow.width - chartWidth - 1, yChart, chartWidth, chartHeight, 0x66FF80, "%", "Draconic storage") + mainWindow.felConversionChart = newChart(3, yChart, chartWidth, chartHeight, 0xBBBBBB, "%", "Fuel conversion"); yChart = yChart + chartHeight + 3 + + local x, y = chartWidth + 9, 3 + local elementWidth = mainWindow.width - chartWidth * 2 - 16 + mainWindow:addLabel(x, y, elementWidth, 1, 0xEEEEEE, "Core status"); y = y + 2 + mainWindow.statusBigText = mainWindow:addChild(newBigText(x, y, 0xEEEEEE, reactorInfo.status)); y = y + 6 + + mainWindow:addLabel(x, y, elementWidth, 1, 0xEEEEEE, "Preferred core params"); y = y + 2 + mainWindow.temperatureSlider = mainWindow:addHorizontalSlider(x, y, elementWidth, 0xFF5555, 0x444444, 0xFF8888, 0xEEEEEE, 0, 8000, 7000, false, "Temperature: ", "°C"); y = y + 3 + mainWindow.fieldSlider = mainWindow:addHorizontalSlider(x, y, elementWidth, 0x33B6FF, 0x444444, 0x66DBFF, 0xEEEEEE, 0, 100, 15, false, "Field power: ", "%"); y = y + 3 + mainWindow.storageSlider = mainWindow:addHorizontalSlider(x, y, elementWidth, 0x66FF80, 0x444444, 0xCCFFBF, 0xEEEEEE, 0, 100, 90, false, "Storage fillness yield: ", "%"); y = y + 3 + + mainWindow:addLabel(x, y, elementWidth, 1, 0xEEEEEE, "History log"); y = y + 2 + mainWindow.logConsole = mainWindow:addTextBox(x, y, elementWidth, 11, 0x262626, 0xCCCCCC, {}, 1, 1, 0); y = y + mainWindow.logConsole.height + 1 + + local fieldWidth = 24 + mainWindow:addLabel(x, y, fieldWidth, 1, 0xEEEEEE, "Chart history size") + mainWindow.chartHistorySizeTextBox = mainWindow:addInputTextBox(x, y + 2, fieldWidth, 3, 0x262626, 0xBBBBBB, 0x262626, 0xEEEEEE, "50", nil, true) + + mainWindow:addLabel(mainWindow.chartHistorySizeTextBox.localPosition.x + mainWindow.chartHistorySizeTextBox.width + 2, y, fieldWidth, 1, 0xEEEEEE, "History log size") + mainWindow.logHistorySizeTextBox = mainWindow:addInputTextBox(mainWindow.chartHistorySizeTextBox.localPosition.x + mainWindow.chartHistorySizeTextBox.width + 2, y + 2, fieldWidth, 3, 0x262626, 0xBBBBBB, 0x262626, 0xEEEEEE, "50", nil, true) + + mainWindow:addLabel(mainWindow.logHistorySizeTextBox.localPosition.x + mainWindow.logHistorySizeTextBox.width + 2, y, 10, 1, 0xEEEEEE, "Chart mode") + mainWindow.chartModeSwitch = mainWindow:addSwitch(mainWindow.logHistorySizeTextBox.localPosition.x + mainWindow.logHistorySizeTextBox.width + 2, y + 3, 10, 0xFFDB40, 0xBBBBBB, 0xFFFFFF, true) + mainWindow.chartModeSwitch.onStateChanged = function() + mainWindow.temperatureChart.fillChartArea = mainWindow.chartModeSwitch.state + mainWindow.energyChart.fillChartArea = mainWindow.chartModeSwitch.state + mainWindow.fieldChart.fillChartArea = mainWindow.chartModeSwitch.state + mainWindow.energySaturationChart.fillChartArea = mainWindow.chartModeSwitch.state + mainWindow.storageChart.fillChartArea = mainWindow.chartModeSwitch.state + mainWindow.fuelConversionChart.fillChartArea = mainWindow.chartModeSwitch.state + end + y = y + 7 + + local firstImage = image.load("/powerButton1.pic") + local secondImage = image.load("/powerButton2.pic") + mainWindow.powerImage = mainWindow:addImage(math.floor(x + elementWidth / 2 - firstImage.width / 2), y, firstImage) + mainWindow.powerImage.onTouch = function() + mainWindow.powerImage.image = mainWindow.powerImage.cyka and firstImage or secondImage + mainWindow.powerImage.cyka = not mainWindow.powerImage.cyka + end + + mainWindow.onDrawFinished = function() + for i = 1, mainWindow.height do + buffer.text(chartWidth + 5, i, 0xEEEEEE, "│") + buffer.text(mainWindow.width - chartWidth - 4, i, 0xEEEEEE, "│") + end + end + + mainWindow.onAnyEvent = function(eventData) + local uptime = computer.uptime() + if uptime > GUIUpdateTimer then + GUIUpdateTimer = uptime + + reactorInfo = reactor.getReactorInfo() + addNewChartValue(mainWindow.temperatureChart, reactorInfo.temperature) + addNewChartValue(mainWindow.fieldChart, reactorInfo.fieldStrength / reactorInfo.maxFieldStrength * 100) + addNewChartValue(mainWindow.energyChart, reactorInfo.generationRate) + addNewChartValue(mainWindow.storageChart, rfstorage.getEnergyStored() / rfstorage.getMaxEnergyStored() * 100) + addNewChartValue(mainWindow.energySaturationChart, reactorInfo.energySaturation / reactorInfo.maxEnergySaturation * 100) + addNewChartValue(mainWindow.fuelConversionChart, reactorInfo.fuelConversion / reactorInfo.maxFuelConversion * 100) + + if reactorInfo.status == "online" then + mainWindow.statusBigText.text = "online" + elseif reactorInfo.status == "stopping" then + mainWindow.statusBigText.text = "stpng" + elseif reactorInfo.status == "offline" then + mainWindow.statusBigText.text = "yield" + elseif reactorInfo.status == "charging" then + mainWindow.statusBigText.text = "chrgin" + elseif reactorInfo.status == "charged" then + mainWindow.statusBigText.text = "chrged" + else + mainWindow.statusBigText.text = "uknw" + end + log("textInfo: " .. math.random(0, 100)) + + mainWindow:draw() + buffer.draw() + end + end +end + +local function getProxies() + reactor = component.proxy(component.list("draconic_reactor")()) + fieldFluxGate = component.proxy(fieldFluxGateAddress) + outputFluxGate = component.proxy(outputFluxGateAddress) + rfstorage = component.proxy(rfstorageAddress) +end + +local function checkReactorShit() + local reactorInfo = reactor.getReactorInfo() + local fieldLowFlow = fieldFluxGate.getSignalLowFlow() + local outputLowFlow = outputFluxGate.getSignalLowFlow() + + local fieldStrength = reactorInfo.fieldStrength / reactorInfo.maxFieldStrength + + print("Drain rate: " .. reactorInfo.fieldDrainRate) + print("Target output: " .. preferredOutput) + print("fieldStrength: " .. fieldStrength) + print("targetdFieldStrength: " .. reactorInfo.fieldDrainRate / reactorInfo.maxFieldStrength * 100 .. "%") + + if reactorInfo.temperature > maximumTemperature or fieldStrength < minimumFieldStrength then + fieldFluxGate.setSignalLowFlow(1000000000) + outputFluxGate.setSignalLowFlow(0) + computer.beep(1500, 0.2) + else + outputFluxGate.setSignalLowFlow(preferredOutput) + + if fieldStrength > preferredFieldStrength then + local newFlow = fieldLowFlow / 2 + if newFlow < 2 then newFlow = 2 end + fieldFluxGate.setSignalLowFlow(newFlow) + else + local newFlow = fieldLowFlow * 2 + if newFlow < 2 then newFlow = 2 end + fieldFluxGate.setSignalLowFlow(newFlow) + end + end +end + +local function chargeReactor() + reactor.stopReactor() + reactor.chargeReactor() + local reactorInfo + repeat + reactorInfo = reactor.getReactorInfo() + print("Зарядочка") + fieldFluxGate.setSignalLowFlow(100000000) + os.sleep(0.05) + until reactorInfo.status == "charged" or reactorInfo.status == "online" + reactor.activateReactor() +end + +---------------------------------------------------- Meow-meow ---------------------------------------------------- + +buffer.changeResolution(component.gpu.maxResolution()) + +getProxies() +reactorInfo = reactor.getReactorInfo() +createWindow() + +mainWindow:draw() +buffer.draw() +mainWindow:handleEvents(1) diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/draw.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/draw.lua new file mode 100755 index 00000000..33d1c68a --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/draw.lua @@ -0,0 +1,18 @@ + +-- package.loaded.doubleBuffering = nil +-- package.loaded.image = nil + +local args = {...} +local image = require("image") +local buffer = require("doubleBuffering") +local gpu = require("component").gpu + +gpu.setBackground(0x0) +gpu.fill(1, 1, 160, 50, " ") + +-- image.draw(1, 1, image.load(args[1])) + + +buffer.clear(0xFF8888) +buffer.image(1, 1, image.load(args[1])) +buffer.draw(true) \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/etc/edit.cfg b/640cd89f-8e29-4b66-a0eb-7680c33760b4/etc/edit.cfg new file mode 100755 index 00000000..775faf04 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/etc/edit.cfg @@ -0,0 +1,98 @@ +keybinds={ + ["findnext"] = { + [1] = { + [1] = "control", + [2] = "g" + }, + [2] = { + [1] = "control", + [2] = "n" + }, + [3] = { + [1] = "f3" + } + }, + ["home"] = { + [1] = { + [1] = "home" + } + }, + ["find"] = { + [1] = { + [1] = "control", + [2] = "f" + } + }, + ["newline"] = { + [1] = { + [1] = "enter" + } + }, + ["backspace"] = { + [1] = { + [1] = "back" + } + }, + ["pageUp"] = { + [1] = { + [1] = "pageUp" + } + }, + ["save"] = { + [1] = { + [1] = "control", + [2] = "s" + } + }, + ["down"] = { + [1] = { + [1] = "down" + } + }, + ["up"] = { + [1] = { + [1] = "up" + } + }, + ["right"] = { + [1] = { + [1] = "right" + } + }, + ["close"] = { + [1] = { + [1] = "control", + [2] = "w" + } + }, + ["deleteLine"] = { + [1] = { + [1] = "control", + [2] = "delete" + }, + [2] = { + [1] = "shift", + [2] = "delete" + } + }, + ["left"] = { + [1] = { + [1] = "left" + } + }, + ["delete"] = { + [1] = { + [1] = "delete" + } + }, + ["pageDown"] = { + [1] = { + [1] = "pageDown" + } + }, + ["eol"] = { + [1] = { + [1] = "end" + } + } +} diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/etc/filesystem.cfg b/640cd89f-8e29-4b66-a0eb-7680c33760b4/etc/filesystem.cfg new file mode 100755 index 00000000..3f3b66b2 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/etc/filesystem.cfg @@ -0,0 +1 @@ +autorun=true \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/etc/motd b/640cd89f-8e29-4b66-a0eb-7680c33760b4/etc/motd new file mode 100755 index 00000000..5d011ae7 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/etc/motd @@ -0,0 +1,37 @@ +#!/bin/lua + +local component = require("component") +local computer = require("computer") +local text = require("text") +local unicode = require("unicode") +local term = require("term") + +if not component.isAvailable("gpu") then + return +end +local lines = {_OSVERSION .. " (" .. math.floor(computer.totalMemory() / 1024) .. "k RAM)"} +local maxWidth = unicode.len(lines[1]) +local f = io.open("/usr/misc/greetings.txt") +if f then + local greetings = {} + pcall(function() + for line in f:lines() do table.insert(greetings, line) end + end) + f:close() + local greeting = greetings[math.random(1, #greetings)] + if greeting then + local width = math.max(10, term.getViewport()) + for line in text.wrappedLines(greeting, width - 4, width - 4) do + table.insert(lines, line) + maxWidth = math.max(maxWidth, unicode.len(line)) + end + end +end +local borders = {{unicode.char(0x2552), unicode.char(0x2550), unicode.char(0x2555)}, + {unicode.char(0x2502), nil, unicode.char(0x2502)}, + {unicode.char(0x2514), unicode.char(0x2500), unicode.char(0x2518)}} +io.write(borders[1][1] .. string.rep(borders[1][2], maxWidth + 2) .. borders[1][3] .. "\n") +for _, line in ipairs(lines) do + io.write(borders[2][1] .. " " .. text.padRight(line, maxWidth) .. " " .. borders[2][3] .. "\n") +end +io.write(borders[3][1] .. string.rep(borders[3][2], maxWidth + 2) .. borders[3][3] .. "\n") diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/etc/profile b/640cd89f-8e29-4b66-a0eb-7680c33760b4/etc/profile new file mode 100755 index 00000000..d77e0c89 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/etc/profile @@ -0,0 +1,29 @@ +alias dir=ls +alias list=ls +alias move=mv +alias rename=mv +alias copy=cp +alias del=rm +alias md=mkdir +alias cls=clear +alias less=more +alias rs=redstone +alias view=edit\ -r +alias help=man +alias cp=cp\ -i + +set EDITOR=/bin/edit +set HISTSIZE=10 +set HOME=/ +set IFS=\ +set MANPATH=/usr/man:. +set PAGER=/bin/more +set PS1='$PWD# ' +set PWD=/ +set SHELL=/bin/sh +set LS_COLORS="{FILE=0xFFFFFF,DIR=0x66CCFF,LINK=0xFFAA00,['*.lua']=0x00FF00}" + +cd $HOME +clear +/etc/motd +source $HOME/.shrc -q diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/etc/rc.cfg b/640cd89f-8e29-4b66-a0eb-7680c33760b4/etc/rc.cfg new file mode 100755 index 00000000..864cbb5d --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/etc/rc.cfg @@ -0,0 +1,3 @@ +enabled = {} + +example = "Hello World!" diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/etc/rc.d/example.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/etc/rc.d/example.lua new file mode 100755 index 00000000..3ac56815 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/etc/rc.d/example.lua @@ -0,0 +1,14 @@ +local count = 0 + +function start(msg) + print("This script displays a welcome message and counts the number " .. + "of times it has been called. The welcome message can be set in the " .. + "config file /etc/rc.cfg") + print(args) + if msg then + print(msg) + end + print(count) + print("runlevel: " .. require("computer").runlevel()) + count = count + 1 +end diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/executionTime.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/executionTime.lua new file mode 100755 index 00000000..b2c4d663 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/executionTime.lua @@ -0,0 +1,20 @@ + + +local MineOSCore = require("MineOSCore") + +local t = {cyka = {}} + +t.size = 160 * 50 +for i = 1, t.size do + t.cyka[i] = 0xFFFFFF +end + +local abc = 0 +local method = function() + for i = 1, 30000 do + abc = #t.cyka + 5 + end +end + +print("Время выполнения среднее: ", MineOSCore.getAverageMethodExecutionTime(method, 15)) + diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/g.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/g.lua new file mode 100644 index 00000000..12cd2f1a --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/g.lua @@ -0,0 +1,15 @@ +local buffer = require("doubleBuffering") +local color = require("color") +local image = require("image") +local web = require('web') +local MineOSCore = require("MineOSCore") + +local picture, reason = MineOSCore.loadImageFromString(web.request("https://github.com/IgorTimofeev/OpenComputers/raw/master/Icons/Security.pic")) +if picture then + buffer.clear(0x0) + buffer.image(2, 2, picture) + buffer.draw(true) +else + print(reason) +end + diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/gitRepoContent.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/gitRepoContent.lua new file mode 100644 index 00000000..139ed66b --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/gitRepoContent.lua @@ -0,0 +1,15 @@ + +local web = require("web") + +local function fileList(user, repo, path) + local result, reason = web.request("https://github.com/" .. user .. "/" .. repo .. "/" .. path) + if result then + for path in result:gmatch("octicon%-file%-.+ 0 then + screen = address + end + end + + -- Report boot progress if possible. + local gpu = component.list("gpu", true)() + local w, h + if gpu and screen then + component.invoke(gpu, "bind", screen) + w, h = component.invoke(gpu, "maxResolution") + component.invoke(gpu, "setResolution", w, h) + component.invoke(gpu, "setBackground", background) + component.invoke(gpu, "setForeground", foreground) + component.invoke(gpu, "fill", 1, 1, w, h, " ") + end + + local function centerText(y, text, color) + if gpu and screen then + local msgWidth = unicode.len(text) + local x = math.floor(w / 2 - msgWidth / 2) + component.invoke(gpu, "fill", 1, y, w, 1, " ") + component.invoke(gpu, "setForeground", color) + component.invoke(gpu, "set", x, y, text) + end + end + + local y = math.floor(h / 2 - 1) + + local function status(text) + centerText(y, "MineOS", logoColor) + centerText(y + 1, text, foreground) + end + + status("Booting " .. _OSVERSION .. "...") + + -- Custom low-level loadfile/dofile implementation reading from our ROM. + local function loadfile(file) + status("Loading " .. file) + local handle, reason = rom.open(file) + if not handle then + error(reason) + end + local buffer = "" + repeat + local data, reason = rom.read(handle) + if not data and reason then + error(reason) + end + buffer = buffer .. (data or "") + until not data + rom.close(handle) + return load(buffer, "=" .. file) + end + + local function dofile(file) + local program, reason = loadfile(file) + if program then + local result = table.pack(pcall(program)) + if result[1] then + return table.unpack(result, 2, result.n) + else + error(result[2]) + end + else + error(reason) + end + end + + status("Initializing package management") + + -- Load file system related libraries we need to load other stuff moree + -- comfortably. This is basically wrapper stuff for the file streams + -- provided by the filesystem components. + local package = dofile("/lib/package.lua") + + do + -- Unclutter global namespace now that we have the package module. + _G.component = nil + _G.computer = nil + _G.process = nil + _G.unicode = nil + + -- Initialize the package module with some of our own APIs. + package.loaded.component = component + package.loaded.computer = computer + package.loaded.unicode = unicode + package.preload["buffer"] = loadfile("/lib/buffer.lua") + package.preload["filesystem"] = loadfile("/lib/filesystem.lua") + + -- Inject the package and io modules into the global namespace, as in Lua. + _G.package = package + _G.io = loadfile("/lib/io.lua")() + + --mark modules for delay loaded api + -- package.delayed["text"] = true + -- package.delayed["sh"] = true + -- package.delayed["transforms"] = true + -- package.delayed["term"] = true + end + + status("Initializing file system") + + -- Mount the ROM and temporary file systems to allow working on the file + -- system module from this point on. + require("filesystem").mount(computer.getBootAddress(), "/") + package.preload={} + + status("Running boot scripts") + + -- Run library startup scripts. These mostly initialize event handlers. + local scripts = {} + for _, file in rom.inits() do + local path = "boot/" .. file + if not rom.isDirectory(path) then + table.insert(scripts, path) + end + end + table.sort(scripts) + for i = 1, #scripts do + dofile(scripts[i]) + end + + status("Initializing components") + + local primaries = {} + for c, t in component.list() do + local s = component.slot(c) + if not primaries[t] or (s >= 0 and s < primaries[t].slot) then + primaries[t] = {address=c, slot=s} + end + computer.pushSignal("component_added", c, t) + end + for t, c in pairs(primaries) do + component.setPrimary(t, c.address) + end +end + +-- MineOS Init data +do + -- -- Загружаем необходимые библиотеки, дабы избежать потерь памяти + -- local shell = require("shell"); shell.setWorkingDirectory("") + -- local ecs = require("ECSAPI") + -- local component = require("component") + -- -- Загружаем параметры ОС (через инит-метод идет загрузка OSSettings) + -- require("MineOSCore") + -- -- Выставляем адекватный масштаб монитора + -- ecs.fadeOut(background, 0x1b1b1b, 0.05) + -- ecs.setScale(1) + + -- Завершаем работу с инициализацией + os.sleep(0.1) -- Allow signal processing by libraries. + require("computer").pushSignal("init") + os.sleep(0.1) -- Allow init processing by libraries. + runlevel = 1 +end + +while true do + local result, reason = pcall(loadfile("bin/sh.lua")) + if not result then + io.stderr:write((reason ~= nil and tostring(reason) or "unknown error") .. "\n") + io.write("Press any key to continue.\n") + os.sleep(0.5) + require("event").pull("key") + end +end \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/.palette.cfg b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/.palette.cfg new file mode 100644 index 00000000..d35805b8 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/.palette.cfg @@ -0,0 +1 @@ +{[5]=13304357,[1]=6684736,[2]=16711935,[3]=8101612,[4]=4827889,[6]=4515354} \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/ECSAPI.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/ECSAPI.lua new file mode 100755 index 00000000..42d00b0a --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/ECSAPI.lua @@ -0,0 +1,2241 @@ + + +local advancedLua = require("advancedLua") +local component = require("component") +local term = require("term") +local unicode = require("unicode") +local event = require("event") +local fs = require("filesystem") +local shell = require("shell") +local keyboard = require("keyboard") +local computer = require("computer") +local serialization = require("serialization") + + +local gpu = component.gpu +local ecs = {} + +---------------------------------------------------------------------------------------------------- + +ecs.windowColors = { + background = 0xeeeeee, + usualText = 0x444444, + subText = 0x888888, + tab = 0xaaaaaa, + title = 0xffffff, + shadow = 0x444444, +} + +ecs.colors = { + white = 0xffffff, + orange = 0xF2B233, + magenta = 0xE57FD8, + lightBlue = 0x99B2F2, + yellow = 0xDEDE6C, + lime = 0x7FCC19, + pink = 0xF2B2CC, + gray = 0x4C4C4C, + lightGray = 0x999999, + cyan = 0x4C99B2, + purple = 0xB266E5, + blue = 0x3366CC, + brown = 0x7F664C, + green = 0x57A64E, + red = 0xCC4C4C, + black = 0x000000, + ["0"] = 0xffffff, + ["1"] = 0xF2B233, + ["2"] = 0xE57FD8, + ["3"] = 0x99B2F2, + ["4"] = 0xDEDE6C, + ["5"] = 0x7FCC19, + ["6"] = 0xF2B2CC, + ["7"] = 0x4C4C4C, + ["8"] = 0x999999, + ["9"] = 0x4C99B2, + ["a"] = 0xB266E5, + ["b"] = 0x3366CC, + ["c"] = 0x7F664C, + ["d"] = 0x57A64E, + ["e"] = 0xCC4C4C, + ["f"] = 0x000000 +} + +---------------------------------------------------------------------------------------------------- + +--Адекватный запрос к веб-серверу вместо стандартного Internet API, бросающего stderr, когда ему вздумается +function ecs.internetRequest(url) + local success, response = pcall(component.internet.request, url) + if success then + local responseData = "" + while true do + local data, responseChunk = response.read() + if data then + responseData = responseData .. data + else + if responseChunk then + return false, responseChunk + else + return true, responseData + end + end + end + else + return false, reason + end +end + +--Загрузка файла с инета +function ecs.getFileFromUrl(url, path) + local success, response = ecs.internetRequest(url) + if success then + fs.makeDirectory(fs.path(path) or "") + local file = io.open(path, "w") + file:write(response) + file:close() + else + ecs.error("Could not connect to to URL address \"" .. url .. "\"") + return + end +end + +--Отключение принудительного завершения программ +function ecs.disableInterrupting() + _G.eventInterruptBackup = package.loaded.event.shouldInterrupt + _G.eventSoftInterruptBackup = package.loaded.event.shouldSoftInterrupt + + package.loaded.event.shouldInterrupt = function () return false end + package.loaded.event.shouldSoftInterrupt = function () return false end +end + +--Включение принудительного завершения программ +function ecs.enableInterrupting() + if _G.eventInterruptBackup then + package.loaded.event.shouldInterrupt = _G.eventInterruptBackup + package.loaded.event.shouldSoftInterrupt = _G.eventSoftInterruptBackup + else + error("Cant't enable interrupting beacause of it's already enabled.") + end +end + +function ecs.getScaledResolution(scale, debug) + --Базовая коррекция масштаба, чтобы всякие умники не писали своими погаными ручонками, чего не следует + if scale > 1 then + scale = 1 + elseif scale < 0.1 then + scale = 0.1 + end + + --Просчет монитора в псевдопикселях - забей, даже объяснять не буду, работает как часы + local function calculateAspect(screens) + local abc = 12 + + if screens == 2 then + abc = 28 + elseif screens > 2 then + abc = 28 + (screens - 2) * 16 + end + + return abc + end + + --Рассчитываем пропорцию монитора в псевдопикселях + local xScreens, yScreens = component.proxy(component.gpu.getScreen()).getAspectRatio() + local xPixels, yPixels = calculateAspect(xScreens), calculateAspect(yScreens) + local proportion = xPixels / yPixels + + --Получаем максимально возможное разрешение данной видеокарты + local xMax, yMax = component.gpu.maxResolution() + + --Получаем теоретическое максимальное разрешение монитора с учетом его пропорции, но без учета лимита видеокарты + local newWidth, newHeight + if proportion >= 1 then + newWidth = xMax + newHeight = math.floor(newWidth / proportion / 2) + else + newHeight = yMax + newWidth = math.floor(newHeight * proportion * 2) + end + + --Получаем оптимальное разрешение для данного монитора с поддержкой видеокарты + local optimalNewWidth, optimalNewHeight = newWidth, newHeight + + if optimalNewWidth > xMax then + local difference = newWidth / xMax + optimalNewWidth = xMax + optimalNewHeight = math.ceil(newHeight / difference) + end + + if optimalNewHeight > yMax then + local difference = newHeight / yMax + optimalNewHeight = yMax + optimalNewWidth = math.ceil(newWidth / difference) + end + + --Корректируем идеальное разрешение по заданному масштабу + local finalNewWidth, finalNewHeight = math.floor(optimalNewWidth * scale), math.floor(optimalNewHeight * scale) + + --Выводим инфу, если нужно + if debug then + print(" ") + print("Максимальное разрешение: "..xMax.."x"..yMax) + print("Пропорция монитора: "..xPixels.."x"..yPixels) + print("Коэффициент пропорции: "..proportion) + print(" ") + print("Теоретическое разрешение: "..newWidth.."x"..newHeight) + print("Оптимизированное разрешение: "..optimalNewWidth.."x"..optimalNewHeight) + print(" ") + print("Новое разрешение: "..finalNewWidth.."x"..finalNewHeight) + print(" ") + end + + return finalNewWidth, finalNewHeight +end + +--Установка масштаба монитора +function ecs.setScale(scale, debug) + --Устанавливаем выбранное разрешение + component.gpu.setResolution(ecs.getScaledResolution(scale, debug)) +end + +function ecs.rebindGPU(address) + component.gpu.bind(address) +end + +--Получаем всю инфу об оперативку в килобайтах +function ecs.getInfoAboutRAM() + local free = math.floor(computer.freeMemory() / 1024) + local total = math.floor(computer.totalMemory() / 1024) + local used = total - free + + return free, total, used +end + +--Получить информацию о жестких дисках +function ecs.getHDDs() + local candidates = {} + for address in component.list("filesystem") do + local proxy = component.proxy(address) + if proxy.address ~= computer.tmpAddress() and proxy.getLabel() ~= "internet" then + local isFloppy, spaceTotal = false, math.floor(proxy.spaceTotal() / 1024) + if spaceTotal < 600 then isFloppy = true end + table.insert(candidates, { + ["spaceTotal"] = spaceTotal, + ["spaceUsed"] = math.floor(proxy.spaceUsed() / 1024), + ["label"] = proxy.getLabel(), + ["address"] = proxy.address, + ["isReadOnly"] = proxy.isReadOnly(), + ["isFloppy"] = isFloppy, + }) + end + end + return candidates +end + +--Форматировать диск +function ecs.formatHDD(address) + local proxy = component.proxy(address) + local list = proxy.list("") + ecs.info("auto", "auto", "", "Formatting disk...") + for _, file in pairs(list) do + if type(file) == "string" then + if not proxy.isReadOnly(file) then proxy.remove(file) end + end + end + list = nil +end + +--Установить имя жесткого диска +function ecs.setHDDLabel(address, label) + local proxy = component.proxy(address) + proxy.setLabel(label or "Untitled") +end + +--Найти монтированный путь конкретного адреса диска +function ecs.findMount(address) + for fs1, path in fs.mounts() do + if fs1.address == component.get(address) then + return path + end + end +end + +function ecs.getArraySize(array) + local size = 0 + for key in pairs(array) do + size = size + 1 + end + return size +end + +--Скопировать файлы с одного диска на другой с заменой +function ecs.duplicateFileSystem(fromAddress, toAddress) + local source, destination = ecs.findMount(fromAddress), ecs.findMount(toAddress) + ecs.info("auto", "auto", "", "Copying file system...") + shell.execute("bin/cp -rx "..source.."* "..destination) +end + +--Загрузка файла с пастебина +function ecs.getFromPastebin(paste, path) + local url = "http://pastebin.com/raw.php?i=" .. paste + ecs.getFileFromUrl(url, path) +end + +--Загрузка файла с гитхаба +function ecs.getFromGitHub(url, path) + url = "https://raw.githubusercontent.com/" .. url + ecs.getFileFromUrl(url, path) +end + +--Загрузить ОС-приложение +function ecs.getOSApplication(application) + --Если это приложение + if application.type == "Application" then + --Удаляем приложение, если оно уже существовало и создаем все нужные папочки + application.name = "/" .. application.name + fs.remove(application.name .. ".app") + fs.makeDirectory(application.name .. ".app/Resources") + + --Загружаем основной исполняемый файл и иконку + ecs.getFromGitHub(application.url, application.name .. ".app/Main.lua") + ecs.getFromGitHub(application.icon, application.name .. ".app/Resources/Icon.pic") + + --Если есть ресурсы, то загружаем ресурсы + if application.resources then + for i = 1, #application.resources do + ecs.getFromGitHub(application.resources[i].url, application.name .. ".app/Resources/" .. application.resources[i].name) + end + end + + --Если есть файл "о программе", то грузим и его + if application.about then + ecs.getFromGitHub(application.about .. _G.OSSettings.language .. ".txt", application.name .. ".app/Resources/About/" .. _G.OSSettings.language .. ".txt") + end + + --Если имеется режим создания ярлыка, то создаем его + if application.createShortcut then + local desktopPath = "MineOS/Desktop/" + + if application.createShortcut == "desktop" then + ecs.createShortCut(desktopPath .. fs.name(application.name) .. ".lnk", application.name .. ".app") + end + end + + --Если тип = другой, чужой, а мб и свой пастебин + elseif application.type == "Pastebin" then + ecs.getFromPastebin(application.url, application.name) + + --Если просто какой-то скрипт + elseif application.type == "Script" or application.type == "Library" or application.type == "Icon" or application.type == "Wallpaper" then + ecs.getFromGitHub(application.url, application.name) + + --А если ваще какая-то абстрактная хуйня, либо ссылка на веб, то загружаем по УРЛ-ке + else + ecs.getFileFromUrl(application.url, application.name) + end +end + +--Получить список приложений, которые требуется обновить +function ecs.getAppsToUpdate(debug) + --Задаем стартовые пути + local pathToApplicationsFile = "MineOS/System/OS/Applications.txt" + local pathToSecondApplicationsFile = "MineOS/System/OS/Applications2.txt" + --Путь к файл-листу на пастебине + local paste = "3j2x4dDn" + --Выводим инфу + local oldPixels + if debug then oldPixels = ecs.info("auto", "auto", " ", "Checking for updates...") end + --Получаем свеженький файл + ecs.getFromPastebin(paste, pathToSecondApplicationsFile) + --Читаем оба файла + local file = io.open(pathToApplicationsFile, "r") + local applications = serialization.unserialize(file:read("*a")) + file:close() + --И второй + file = io.open(pathToSecondApplicationsFile, "r") + local applications2 = serialization.unserialize(file:read("*a")) + file:close() + + local countOfUpdates = 0 + + --Просматриваем свеженький файлик и анализируем, че в нем нового, все старое удаляем + local i = 1 + while true do + --Разрыв цикла + if i > #applications2 then break end + --Новая версия файла + local newVersion, oldVersion = applications2[i].version, 0 + --Получаем старую версию этого файла + for j = 1, #applications do + if applications2[i].name == applications[j].name then + oldVersion = applications[j].version or 0 + break + end + end + --Если новая версия новее, чем старая, то добавить в массив то, что нужно обновить + if newVersion > oldVersion then + applications2[i].needToUpdate = true + countOfUpdates = countOfUpdates + 1 + end + + i = i + 1 + end + --Если чет рисовалось, то стереть на хер + if oldPixels then ecs.drawOldPixels(oldPixels) end + --Возвращаем массив с тем, че нужно обновить и просто старый аппликашнс на всякий случай + return applications2, countOfUpdates +end + +--Сделать строку пригодной для отображения в ОпенКомпах +--Заменяет табсы на пробелы и виндовый возврат каретки на человеческий UNIX-овский +function ecs.stringOptimize(sto4ka, indentatonWidth) + sto4ka = string.gsub(sto4ka, "\r\n", "\n") + sto4ka = string.gsub(sto4ka, " ", string.rep(" ", indentatonWidth or 2)) + return stro4ka +end + +--ИЗ ДЕСЯТИЧНОЙ В ШЕСТНАДЦАТИРИЧНУЮ +function ecs.decToBase(IN,BASE) + local hexCode = "0123456789ABCDEFGHIJKLMNOPQRSTUVW" + OUT = "" + local ostatok = 0 + while IN>0 do + ostatok = math.fmod(IN,BASE) + 1 + IN = math.floor(IN/BASE) + OUT = string.sub(hexCode,ostatok,ostatok)..OUT + end + if #OUT == 1 then OUT = "0"..OUT end + if OUT == "" then OUT = "00" end + return OUT +end + +--Правильное конвертирование HEX-переменной в строковую +function ecs.HEXtoString(color, bitCount, withNull) + local stro4ka = string.format("%X",color) + local sStro4ka = unicode.len(stro4ka) + if sStro4ka < bitCount then + stro4ka = string.rep("0", bitCount - sStro4ka) .. stro4ka + end + sStro4ka = nil + if withNull then return "0x"..stro4ka else return stro4ka end +end + +--КЛИКНУЛИ ЛИ В ЗОНУ +function ecs.clickedAtArea(x,y,sx,sy,ex,ey) + if (x >= sx) and (x <= ex) and (y >= sy) and (y <= ey) then return true end + return false +end + +--Заливка всего экрана указанным цветом +function ecs.clearScreen(color) + if color then component.gpu.setBackground(color) end + term.clear() +end + +--Установка пикселя нужного цвета +function ecs.setPixel(x,y,color) + component.gpu.setBackground(color) + component.gpu.set(x,y," ") +end + +--Простая установка цветов в одну строку, ибо я ленивый +function ecs.setColor(background, foreground) + component.gpu.setBackground(background) + component.gpu.setForeground(foreground) +end + +--Цветной текст +function ecs.colorText(x,y,textColor,text) + component.gpu.setForeground(textColor) + component.gpu.set(x,y,text) +end + +--Цветной текст с жопкой! +function ecs.colorTextWithBack(x,y,textColor,backColor,text) + component.gpu.setForeground(textColor) + component.gpu.setBackground(backColor) + component.gpu.set(x,y,text) +end + +--Инверсия цвета +function ecs.invertColor(color) + return 0xffffff - color +end + +--Адаптивный текст, подстраивающийся под фон +function ecs.adaptiveText(x,y,text,textColor) + component.gpu.setForeground(textColor) + x = x - 1 + for i=1,unicode.len(text) do + local info = {component.gpu.get(x+i,y)} + component.gpu.setBackground(info[3]) + component.gpu.set(x+i,y,unicode.sub(text,i,i)) + end +end + +--Костыльная замена обычному string.find() +--Работает медленнее, но хотя бы поддерживает юникод +function unicode.find(str, pattern, init, plain) + if init then + if init < 0 then + init = -#unicode.sub(str,init) + elseif init > 0 then + init = #unicode.sub(str,1,init-1)+1 + end + end + + a, b = string.find(str, pattern, init, plain) + + if a then + local ap,bp = str:sub(1,a-1), str:sub(a,b) + a = unicode.len(ap)+1 + b = a + unicode.len(bp)-1 + return a,b + else + return a + end +end + +--Умный текст по аналогии с майнчатовским. Ставишь символ параграфа, указываешь хуйню - и хуякс! Работает! +function ecs.smartText(x, y, text) + local sText = unicode.len(text) + local specialSymbol = "§" + --Разбираем по кусочкам строку и получаем цвета + local massiv = {} + local iterator = 1 + local currentColor = component.gpu.getForeground() + while iterator <= sText do + local symbol = unicode.sub(text, iterator, iterator) + if symbol == specialSymbol then + currentColor = ecs.colors[unicode.sub(text, iterator + 1, iterator + 1) or "f"] + iterator = iterator + 1 + else + table.insert(massiv, {symbol, currentColor}) + end + symbol = nil + iterator = iterator + 1 + end + x = x - 1 + for i = 1, #massiv do + if currentColor ~= massiv[i][2] then currentColor = massiv[i][2]; component.gpu.setForeground(massiv[i][2]) end + component.gpu.set(x + i, y, massiv[i][1]) + end +end + +--Аналог умного текста, но использующий HEX-цвета для кодировки +function ecs.formattedText(x, y, text, limit) + --Ограничение длины строки + limit = limit or math.huge + --Стартовая позиция курсора для отрисовки + local xPos = x + --Создаем массив символов данной строки + local symbols = {} + for i = 1, unicode.len(text) do table.insert(symbols, unicode.sub(text, i, i)) end + --Перебираем все символы строки, пока не переберем все или не достигнем указанного лимита + local i = 1 + while i <= #symbols and i <= limit do + --Если находим символ параграфа, то + if symbols[i] == "§" then + --Меняем цвет текста на указанный + component.gpu.setForeground(tonumber("0x" .. symbols[i+1] .. symbols[i+2] .. symbols[i+3] .. symbols[i+4] .. symbols[i+5] .. symbols[i+6])) + --Увеличиваем лимит на 7, т.к. + limit = limit + 7 + --Сдвигаем итератор цикла на 7 + i = i + 7 + end + --Рисуем символ на нужной позиции + component.gpu.set(xPos, y, symbols[i]) + --Увеличиваем позицию курсора и итератор на 1 + xPos = xPos + 1 + i = i + 1 + end +end + +--Инвертированный текст на основе цвета фона +function ecs.invertedText(x,y,symbol) + local info = {component.gpu.get(x,y)} + ecs.adaptiveText(x,y,symbol,ecs.invertColor(info[3])) +end + +--Адаптивное округление числа +function ecs.adaptiveRound(chislo) + local celaya,drobnaya = math.modf(chislo) + if drobnaya >= 0.5 then + return (celaya + 1) + else + return celaya + end +end + +--Округление до опред. кол-ва знаков после запятой +function ecs.round(num, idp) + local mult = 10^(idp or 0) + return math.floor(num * mult + 0.5) / mult +end + +--Обычный квадрат указанного цвета +function ecs.square(x,y,width,height,color) + component.gpu.setBackground(color) + component.gpu.fill(x,y,width,height," ") +end + +--Юникодовская рамка +function ecs.border(x, y, width, height, back, fore) + local stringUp = "┌"..string.rep("─", width - 2).."┐" + local stringDown = "└"..string.rep("─", width - 2).."┘" + component.gpu.setForeground(fore) + component.gpu.setBackground(back) + component.gpu.set(x, y, stringUp) + component.gpu.set(x, y + height - 1, stringDown) + + local yPos = 1 + for i = 1, (height - 2) do + component.gpu.set(x, y + yPos, "│") + component.gpu.set(x + width - 1, y + yPos, "│") + yPos = yPos + 1 + end +end + +--Кнопка в виде текста в рамке +function ecs.drawFramedButton(x, y, width, height, text, color) + ecs.border(x, y, width, height, component.gpu.getBackground(), color) + component.gpu.fill(x + 1, y + 1, width - 2, height - 2, " ") + x = x + math.floor(width / 2 - unicode.len(text) / 2) + y = y + math.floor(width / 2 - 1) + component.gpu.set(x, y, text) +end + +--Юникодовский разделитель +function ecs.separator(x, y, width, back, fore) + ecs.colorTextWithBack(x, y, fore, back, string.rep("─", width)) +end + +--Автоматическое центрирование текста по указанной координате (x, y, xy) +function ecs.centerText(mode,coord,text) + local dlina = unicode.len(text) + local xSize,ySize = component.gpu.getResolution() + + if mode == "x" then + component.gpu.set(math.floor(xSize/2-dlina/2),coord,text) + elseif mode == "y" then + component.gpu.set(coord,math.floor(ySize/2),text) + else + component.gpu.set(math.floor(xSize/2-dlina/2),math.floor(ySize/2),text) + end +end + +--Отрисовка "изображения" по указанному массиву +function ecs.drawCustomImage(x,y,pixels) + x = x - 1 + y = y - 1 + local pixelsWidth = #pixels[1] + local pixelsHeight = #pixels + local xEnd = x + pixelsWidth + local yEnd = y + pixelsHeight + + for i=1,pixelsHeight do + for j=1,pixelsWidth do + if pixels[i][j][3] ~= "#" then + if component.gpu.getBackground() ~= pixels[i][j][1] then component.gpu.setBackground(pixels[i][j][1]) end + if component.gpu.getForeground() ~= pixels[i][j][2] then component.gpu.setForeground(pixels[i][j][2]) end + component.gpu.set(x+j,y+i,pixels[i][j][3]) + end + end + end + + return (x+1),(y+1),xEnd,yEnd +end + +--Корректировка стартовых координат. Core-функция для всех моих программ +function ecs.correctStartCoords(xStart,yStart,xWindowSize,yWindowSize) + local xSize,ySize = component.gpu.getResolution() + if xStart == "auto" then + xStart = math.floor(xSize/2 - xWindowSize/2) + end + if yStart == "auto" then + yStart = math.ceil(ySize/2 - yWindowSize/2) + end + return xStart,yStart +end + +--Запомнить область пикселей и возвратить ее в виде массива +function ecs.rememberOldPixels(x, y, x2, y2) + local newPNGMassiv = { ["backgrounds"] = {} } + local xSize, ySize = component.gpu.getResolution() + newPNGMassiv.x, newPNGMassiv.y = x, y + + --Перебираем весь массив стандартного PNG-вида по высоте + local xCounter, yCounter = 1, 1 + for j = y, y2 do + xCounter = 1 + for i = x, x2 do + + if (i > xSize or i < 0) or (j > ySize or j < 0) then + error("Can't remember pixel, because it's located behind the screen: x("..i.."), y("..j..") out of xSize("..xSize.."), ySize("..ySize..")\n") + end + + local symbol, fore, back = component.gpu.get(i, j) + + newPNGMassiv["backgrounds"][back] = newPNGMassiv["backgrounds"][back] or {} + newPNGMassiv["backgrounds"][back][fore] = newPNGMassiv["backgrounds"][back][fore] or {} + + table.insert(newPNGMassiv["backgrounds"][back][fore], {xCounter, yCounter, symbol} ) + + xCounter = xCounter + 1 + back, fore, symbol = nil, nil, nil + end + + yCounter = yCounter + 1 + end + + xSize, ySize = nil, nil + return newPNGMassiv +end + +--Нарисовать запомненные ранее пиксели из массива +function ecs.drawOldPixels(massivSudaPihay) + --Перебираем массив с фонами + for back, backValue in pairs(massivSudaPihay["backgrounds"]) do + component.gpu.setBackground(back) + for fore, foreValue in pairs(massivSudaPihay["backgrounds"][back]) do + component.gpu.setForeground(fore) + for pixel = 1, #massivSudaPihay["backgrounds"][back][fore] do + if massivSudaPihay["backgrounds"][back][fore][pixel][3] ~= transparentSymbol then + component.gpu.set(massivSudaPihay.x + massivSudaPihay["backgrounds"][back][fore][pixel][1] - 1, massivSudaPihay.y + massivSudaPihay["backgrounds"][back][fore][pixel][2] - 1, massivSudaPihay["backgrounds"][back][fore][pixel][3]) + end + end + end + end +end + +--Ограничение длины строки. Маст-хев функция. +function ecs.stringLimit(mode, text, size, noDots) + if unicode.len(text) <= size then return text end + local length = unicode.len(text) + if mode == "start" then + if noDots then + return unicode.sub(text, length - size + 1, -1) + else + return "…" .. unicode.sub(text, length - size + 2, -1) + end + else + if noDots then + return unicode.sub(text, 1, size) + else + return unicode.sub(text, 1, size - 1) .. "…" + end + end +end + +--Получить текущее реальное время компьютера, хостящего сервер майна +function ecs.getHostTime(timezone) + timezone = timezone or 2 + --Создаем файл с записанной в него парашей + local file = io.open("HostTime.tmp", "w") + file:write("") + file:close() + --Коррекция времени на основе часового пояса + local timeCorrection = timezone * 3600 + --Получаем дату изменения файла в юникс-виде + local lastModified = tonumber(string.sub(fs.lastModified("HostTime.tmp"), 1, -4)) + timeCorrection + --Удаляем файл, ибо на хуй он нам не нужен + fs.remove("HostTime.tmp") + --Конвертируем юникс-время в норм время + local year, month, day, hour, minute, second = os.date("%Y", lastModified), os.date("%m", lastModified), os.date("%d", lastModified), os.date("%H", lastModified), os.date("%M", lastModified), os.date("%S", lastModified) + --Возвращаем все + return tonumber(day), tonumber(month), tonumber(year), tonumber(hour), tonumber(minute), tonumber(second) +end + +--Получить спискок файлов из конкретной директории, костыль +function ecs.getFileList(path) + local list = fs.list(path) + local massiv = {} + for file in list do + --if string.find(file, "%/$") then file = unicode.sub(file, 1, -2) end + table.insert(massiv, file) + end + list = nil + return massiv +end + +--Получить файловое древо. Сильно нагружает систему, только для дебага! +function ecs.getFileTree(path) + local massiv = {} + local list = ecs.getFileList(path) + for key, file in pairs(list) do + if fs.isDirectory(path.."/"..file) then + table.insert(massiv, getFileTree(path.."/"..file)) + else + table.insert(massiv, file) + end + end + list = nil + + return massiv +end + +--Поиск по файловой системе +function ecs.find(path, cheBudemIskat) + --Массив, в котором будут находиться все найденные соответствия + local massivNaydennogoGovna = {} + --Костыль, но удобный + local function dofind(path, cheBudemIskat) + --Получаем список файлов в директории + local list = ecs.getFileList(path) + --Перебираем все элементы файл листа + for key, file in pairs(list) do + --Путь к файлу + local pathToFile = path..file + --Если нашло совпадение в имени файла, то выдает путь к этому файлу + if string.find(unicode.lower(file), unicode.lower(cheBudemIskat)) then + table.insert(massivNaydennogoGovna, pathToFile) + end + --Анализ, что делать дальше + if fs.isDirectory(pathToFile) then + dofind(pathToFile, cheBudemIskat) + end + --Очищаем оперативку + pathToFile = nil + end + --Очищаем оперативку + list = nil + end + --Выполняем функцию + dofind(path, cheBudemIskat) + --Возвращаем, че нашло + return massivNaydennogoGovna +end + +--Получение формата файла +function ecs.getFileFormat(path) + local name = fs.name(path) + local starting, ending = string.find(name, "(.)%.[%d%w]*$") + if starting == nil then + return nil + else + return unicode.sub(name,starting + 1, -1) + end + name, starting, ending = nil, nil, nil +end + +--Проверить, скрытый ли файл (.пидор, .хуй = true; пидор, хуй = false) +function ecs.isFileHidden(path) + local name = fs.name(path) + local starting, ending = string.find(name, "^%.(.*)$") + if starting == nil then + return false + else + return true + end + name, starting, ending = nil, nil, nil +end + +--Скрыть формат файла +function ecs.hideFileFormat(path) + local name = fs.name(path) + local fileFormat = ecs.getFileFormat(name) + if fileFormat == nil then + return name + else + return unicode.sub(name, 1, unicode.len(name) - unicode.len(fileFormat)) + end +end + +--Ожидание клика либо нажатия какой-либо клавиши +function ecs.waitForTouchOrClick() + while true do + local e = { event.pull() } + if e[1] == "key_down" or e[1] == "touch" then break end + end +end + +--То же самое, но в сокращенном варианте +function ecs.wait() + ecs.waitForTouchOrClick() +end + +--Нарисовать кнопочки закрытия окна +function ecs.drawCloses(x, y, active) + local symbol = "⮾" + ecs.colorText(x, y , (active == 1 and ecs.colors.blue) or 0xCC4C4C, symbol) + ecs.colorText(x + 2, y , (active == 2 and ecs.colors.blue) or 0xDEDE6C, symbol) + ecs.colorText(x + 4, y , (active == 3 and ecs.colors.blue) or 0x57A64E, symbol) +end + +--Нарисовать верхнюю оконную панель с выбором объектов +function ecs.drawTopBar(x, y, width, selectedObject, background, foreground, ...) + local objects = { ... } + ecs.square(x, y, width, 3, background) + local widthOfObjects = 0 + local spaceBetween = 2 + for i = 1, #objects do + widthOfObjects = widthOfObjects + unicode.len(objects[i][1]) + spaceBetween + end + local xPos = x + math.floor(width / 2 - widthOfObjects / 2) + for i = 1, #objects do + if i == selectedObject then + ecs.square(xPos, y, unicode.len(objects[i][1]) + spaceBetween, 3, ecs.colors.blue) + component.gpu.setForeground(0xffffff) + else + component.gpu.setBackground(background) + component.gpu.setForeground(foreground) + end + component.gpu.set(xPos + spaceBetween / 2, y + 2, objects[i][1]) + component.gpu.set(xPos + math.ceil(unicode.len(objects[i][1]) / 2), y + 1, objects[i][2]) + + xPos = xPos + unicode.len(objects[i][1]) + spaceBetween + end +end + +--Нарисовать топ-меню, горизонтальная полоска такая с текстами +function ecs.drawTopMenu(x, y, width, color, selectedObject, ...) + local objects = { ... } + local objectsToReturn = {} + local xPos = x + 2 + local spaceBetween = 2 + ecs.square(x, y, width, 1, color) + for i = 1, #objects do + if i == selectedObject then + ecs.square(xPos - 1, y, unicode.len(objects[i][1]) + spaceBetween, 1, ecs.colors.blue) + component.gpu.setForeground(0xffffff) + component.gpu.set(xPos, y, objects[i][1]) + component.gpu.setForeground(objects[i][2]) + component.gpu.setBackground(color) + else + if component.gpu.getForeground() ~= objects[i][2] then component.gpu.setForeground(objects[i][2]) end + component.gpu.set(xPos, y, objects[i][1]) + end + objectsToReturn[objects[i][1]] = { xPos, y, xPos + unicode.len(objects[i][1]) - 1, y, i } + xPos = xPos + unicode.len(objects[i][1]) + spaceBetween + end + return objectsToReturn +end + +--Функция отрисовки кнопки указанной ширины +function ecs.drawButton(x,y,width,height,text,backColor,textColor) + x,y = ecs.correctStartCoords(x,y,width,height) + + local textPosX = math.floor(x + width / 2 - unicode.len(text) / 2) + local textPosY = math.floor(y + height / 2) + ecs.square(x,y,width,height,backColor) + ecs.colorText(textPosX,textPosY,textColor,text) + + return x, y, (x + width - 1), (y + height - 1) +end + +--Отрисовка кнопки с указанными отступами от текста +function ecs.drawAdaptiveButton(x,y,offsetX,offsetY,text,backColor,textColor) + local length = unicode.len(text) + local width = offsetX*2 + length + local height = offsetY*2 + 1 + + x,y = ecs.correctStartCoords(x,y,width,height) + + ecs.square(x,y,width,height,backColor) + ecs.colorText(x+offsetX,y+offsetY,textColor,text) + + return x,y,(x+width-1),(y+height-1) +end + +--Отрисовка оконной "тени" +function ecs.windowShadow(x,y,width,height) + component.gpu.setBackground(ecs.windowColors.shadow) + component.gpu.fill(x+width,y+1,2,height," ") + component.gpu.fill(x+1,y+height,width,1," ") +end + +--Просто белое окошко с тенью +function ecs.blankWindow(x,y,width,height) + local oldPixels = ecs.rememberOldPixels(x,y,x+width+1,y+height) + + ecs.square(x,y,width,height,ecs.windowColors.background) + + ecs.windowShadow(x,y,width,height) + + return oldPixels +end + +--Белое окошко, но уже с титлом вверху! +function ecs.emptyWindow(x,y,width,height,title) + + local oldPixels = ecs.rememberOldPixels(x,y,x+width+1,y+height) + + --ОКНО + component.gpu.setBackground(ecs.windowColors.background) + component.gpu.fill(x,y+1,width,height-1," ") + + --ТАБ СВЕРХУ + component.gpu.setBackground(ecs.windowColors.tab) + component.gpu.fill(x,y,width,1," ") + + --ТИТЛ + component.gpu.setForeground(ecs.windowColors.title) + local textPosX = x + math.floor(width/2-unicode.len(title)/2) -1 + component.gpu.set(textPosX,y,title) + + --ТЕНЬ + ecs.windowShadow(x,y,width,height) + + return oldPixels + +end + +function ecs.getWordsArrayFromString(s) + local words = {} + for word in string.gmatch(s, "[^%s]+") do table.insert(words, word) end + return words +end + +--Моя любимая функция ошибки C: +function ecs.error(...) + local args = {...} + local text = "" + if #args > 1 then + for i = 1, #args do + --text = text .. "[" .. i .. "] = " .. tostring(args[i]) + if type(args[i]) == "string" then args[i] = "\"" .. args[i] .. "\"" end + text = text .. tostring(args[i]) + if i ~= #args then text = text .. ", " end + end + else + text = tostring(args[1]) + end + ecs.universalWindow("auto", "auto", math.ceil(component.gpu.getResolution() * 0.45), ecs.windowColors.background, true, {"EmptyLine"}, {"CenterText", 0x880000, "Ошибка!"}, {"EmptyLine"}, {"WrappedText", 0x262626, text}, {"EmptyLine"}, {"Button", {0x880000, 0xffffff, "OK!"}}) +end + +--Очистить экран, установить комфортные цвета и поставить курсок на 1, 1 +function ecs.prepareToExit(color1, color2) + term.setCursor(1, 1) + ecs.clearScreen(color1 or 0x333333) + component.gpu.setForeground(color2 or 0xffffff) + component.gpu.set(1, 1, "") +end + +--Конвертация из юникода в символ. Вроде норм, а вроде и не норм. Но полезно. +function ecs.convertCodeToSymbol(code) + local symbol + if code ~= 0 and code ~= 13 and code ~= 8 and code ~= 9 and code ~= 200 and code ~= 208 and code ~= 203 and code ~= 205 and not keyboard.isControlDown() then + symbol = unicode.char(code) + if keyboard.isShiftPressed then symbol = unicode.upper(symbol) end + end + return symbol +end + +--Шкала прогресса - маст-хев! +function ecs.progressBar(x, y, width, height, background, foreground, percent) + local activeWidth = math.ceil(width * percent / 100) + ecs.square(x, y, width, height, background) + ecs.square(x, y, activeWidth, height, foreground) +end + +--Окошко с прогрессбаром! Давно хотел +function ecs.progressWindow(x, y, width, percent, text, returnOldPixels) + local height = 6 + local barWidth = width - 6 + + x, y = ecs.correctStartCoords(x, y, width, height) + + local oldPixels + if returnOldPixels then + oldPixels = ecs.rememberOldPixels(x, y, x + width + 1, y + height) + end + + ecs.emptyWindow(x, y, width, height, " ") + ecs.colorTextWithBack(x + math.floor(width / 2 - unicode.len(text) / 2), y + 4, 0x000000, ecs.windowColors.background, text) + ecs.progressBar(x + 3, y + 2, barWidth, 1, 0xCCCCCC, ecs.colors.blue, percent) + + return oldPixels +end + +--Функция для ввода текста в мини-поле. +function ecs.inputText(x, y, limit, cheBiloVvedeno, background, foreground, justDrawNotEvent, maskTextWith) + limit = limit or 10 + cheBiloVvedeno = cheBiloVvedeno or "" + background = background or 0xffffff + foreground = foreground or 0x000000 + + component.gpu.setBackground(background) + component.gpu.setForeground(foreground) + component.gpu.fill(x, y, limit, 1, " ") + + local text = cheBiloVvedeno + + local function draw() + term.setCursorBlink(false) + + local dlina = unicode.len(text) + local xCursor = x + dlina + if xCursor > (x + limit - 1) then xCursor = (x + limit - 1) end + + if maskTextWith then + component.gpu.set(x, y, ecs.stringLimit("start", string.rep("●", dlina), limit)) + else + component.gpu.set(x, y, ecs.stringLimit("start", text, limit)) + end + + term.setCursor(xCursor, y) + + term.setCursorBlink(true) + end + + draw() + + if justDrawNotEvent then term.setCursorBlink(false); return cheBiloVvedeno end + + while true do + local e = {event.pull()} + if e[1] == "key_down" then + if e[4] == 14 then + term.setCursorBlink(false) + text = unicode.sub(text, 1, -2) + if unicode.len(text) < limit then component.gpu.set(x + unicode.len(text), y, " ") end + draw() + elseif e[4] == 28 then + term.setCursorBlink(false) + return text + else + local symbol = ecs.convertCodeToSymbol(e[3]) + if symbol then + text = text..symbol + draw() + end + end + elseif e[1] == "touch" then + term.setCursorBlink(false) + return text + elseif e[1] == "clipboard" then + if e[3] then + text = text..e[3] + draw() + end + end + end +end + +--Спросить, заменять ли файл (если таковой уже имеется) +function ecs.askForReplaceFile(path, includeForAllButton) + local cyka = { + {"EmptyLine"}, + {"CenterText", 0x262626, "Файл \"".. fs.name(path) .. "\" уже имеется в этом месте."}, + {"CenterText", 0x262626, "Заменить его перемещаемым объектом?"}, + {"EmptyLine"} + } + if includeForAllButton then table.insert(cyka, {"Switch", 0xF2B233, 0xffffff, 0x262626, "Для всех", true}) end + table.insert(cyka, {"Button", {0x444444, 0xFFFFFF, "Заменить"}, {0x666666, 0xFFFFFF, "Отмена"}}) + + local action = ecs.universalWindow("auto", "auto", 46, ecs.windowColors.background, true, table.unpack(cyka)) + if action[includeForAllButton and 2 or 1] == "Заменить" then + return 1, action[includeForAllButton and 1] + else + return 2, action[includeForAllButton and 1] + end +end + +--Проверить имя файла на соответствие критериям +function ecs.checkName(name, path) + --Если ввели хуйню какую-то, то + if name == "" or name == " " or name == nil then + ecs.error("Неверное имя файла.") + return false + else + --Если файл с новым путем уже существует, то + if fs.exists(path .. name) then + ecs.error("Файл \"".. name .. "\" уже имеется в этом месте.") + return false + --А если все заебок, то + else + return true + end + end +end + +--Переименование файлов (для операционки) +function ecs.rename(mainPath) + --Задаем стартовую щнягу + local name = fs.name(mainPath) + path = fs.path(mainPath) + --Рисуем окошко ввода нового имени файла + local inputs = ecs.universalWindow("auto", "auto", 30, ecs.windowColors.background, true, {"EmptyLine"}, {"CenterText", 0x262626, "Переименовать"}, {"EmptyLine"}, {"Input", 0x262626, 0x880000, name}, {"EmptyLine"}, {"Button", {0xbbbbbb, 0xffffff, "OK"}}) + --Переименовываем + if ecs.checkName(inputs[1], path) then + fs.rename(mainPath, path .. inputs[1]) + end +end + +--Создать новую папку (для операционки) +function ecs.newFolder(path) + --Рисуем окошко ввода нового имени файла + local inputs = ecs.universalWindow("auto", "auto", 30, ecs.windowColors.background, true, {"EmptyLine"}, {"CenterText", 0x262626, "Новая папка"}, {"EmptyLine"}, {"Input", 0x262626, 0x880000, ""}, {"EmptyLine"}, {"Button", {0xbbbbbb, 0xffffff, "OK"}}) + + if ecs.checkName(inputs[1], path) then + fs.makeDirectory(path .. inputs[1]) + end +end + +--Создать новый файл (для операционки) +function ecs.newFile(path) + --Рисуем окошко ввода нового имени файла + local inputs = ecs.universalWindow("auto", "auto", 30, ecs.windowColors.background, true, {"EmptyLine"}, {"CenterText", 0x262626, "Новый файл"}, {"EmptyLine"}, {"Input", 0x262626, 0x880000, ""}, {"EmptyLine"}, {"Button", {0xbbbbbb, 0xffffff, "OK"}}) + + if ecs.checkName(inputs[1], path) then + local MineOSCore = require("MineOSCore") + MineOSCore.safeLaunch(MineOSCore.paths.applications .. "/MineCode IDE.app/Main.lua", "open", path .. inputs[1]) + end +end + +--Создать новое приложение (для операционки) +function ecs.newApplication(path, startName) + --Рисуем окошко ввода нового имени файла + local inputs + if not startName then + inputs = ecs.universalWindow("auto", "auto", 30, ecs.windowColors.background, true, {"EmptyLine"}, {"CenterText", 0x262626, "Новое приложение"}, {"EmptyLine"}, {"Input", 0x262626, 0x880000, "Введите имя"}, {"EmptyLine"}, {"Button", {0xbbbbbb, 0xffffff, "OK"}}) + end + + if ecs.checkName(inputs[1] .. ".app", path) then + local name = path .. inputs[1] .. ".app/Resources/" + fs.makeDirectory(name) + fs.copy("MineOS/System/OS/Icons/SampleIcon.pic", name .. "Icon.pic") + local file = io.open(path .. inputs[1] .. ".app/Main.lua", "w") + file:write("require('GUI').error('Hello world')") + file:close() + end +end + +--Создать приложение на основе существующего ЛУА-файла +function ecs.newApplicationFromLuaFile(pathToLuaFile, pathWhereToCreateApplication) + local data = ecs.universalWindow("auto", "auto", 30, ecs.windowColors.background, true, {"EmptyLine"}, {"CenterText", 0x000000, "Новое приложение"}, {"EmptyLine"}, {"Input", 0x262626, 0x880000, "Имя приложения"}, {"Input", 0x262626, 0x880000, "Путь к иконке приложения"}, {"EmptyLine"}, {"Button", {0xbbbbbb, 0xffffff, "OK"}}) + data[1] = data[1] or "MyApplication" + data[2] = data[2] or "MineOS/System/OS/Icons/SampleIcon.pic" + if fs.exists(data[2]) then + fs.makeDirectory(pathWhereToCreateApplication .. "/" .. data[1] .. ".app/Resources") + fs.copy(pathToLuaFile, pathWhereToCreateApplication .. "/" .. data[1] .. ".app/" .. data[1] .. ".lua") + fs.copy(data[2], pathWhereToCreateApplication .. "/" .. data[1] .. ".app/Resources/Icon.pic") + + --ecs.universalWindow("auto", "auto", 30, ecs.windowColors.background, true, {"EmptyLine"}, {"CenterText", 0x000000, "Приложение создано!"}, {"EmptyLine"}, {"Button", {ecs.colors.green, 0xffffff, "OK"}}) + else + ecs.error("Указанный файл иконки не существует.") + return + end +end + +--Простое информационное окошечко. Возвращает старые пиксели - мало ли понадобится. +function ecs.info(x, y, title, text) + x = x or "auto" + y = y or "auto" + title = title or " " + text = text or "Sample text" + + local width = unicode.len(text) + 4 + local height = 4 + x, y = ecs.correctStartCoords(x, y, width, height) + + local oldPixels = ecs.rememberOldPixels(x, y, x + width + 1, y + height) + + ecs.emptyWindow(x, y, width, height, title) + ecs.colorTextWithBack(x + 2, y + 2, ecs.windowColors.usualText, ecs.windowColors.background, text) + + return oldPixels +end + +--Вертикальный скроллбар. Маст-хев! +function ecs.srollBar(x, y, width, height, countOfAllElements, currentElement, backColor, frontColor) + local sizeOfScrollBar = math.ceil(1 / countOfAllElements * height) + local displayBarFrom = math.floor(y + height * ((currentElement - 1) / countOfAllElements)) + + ecs.square(x, y, width, height, backColor) + ecs.square(x, displayBarFrom, width, sizeOfScrollBar, frontColor) + + sizeOfScrollBar, displayBarFrom = nil, nil +end + +--Отрисовка поля с текстом. Сюда пихать массив вида {"строка1", "строка2", "строка3", ...} +function ecs.textField(x, y, width, height, lines, displayFrom, background, foreground, scrollbarBackground, scrollbarForeground) + x, y = ecs.correctStartCoords(x, y, width, height) + + background = background or 0xffffff + foreground = foreground or ecs.windowColors.usualText + + local sLines = #lines + local lineLimit = width - 3 + + --Парсим строки + local line = 1 + while lines[line] do + local sLine = unicode.len(lines[line]) + if sLine > lineLimit then + local part1, part2 = unicode.sub(lines[line], 1, lineLimit), unicode.sub(lines[line], lineLimit + 1, -1) + lines[line] = part1 + table.insert(lines, line + 1, part2) + part1, part2 = nil, nil + end + line = line + 1 + sLine = nil + end + line = nil + + ecs.square(x, y, width - 1, height, background) + ecs.srollBar(x + width - 1, y, 1, height, sLines, displayFrom, scrollbarBackground, scrollbarForeground) + + component.gpu.setBackground(background) + component.gpu.setForeground(foreground) + local yPos = y + for i = displayFrom, (displayFrom + height - 1) do + if lines[i] then + component.gpu.set(x + 1, yPos, lines[i]) + yPos = yPos + 1 + else + break + end + end + + return sLines +end + +--Получение верного имени языка. Просто для безопасности. (для операционки) +function ecs.getCorrectLangName(pathToLangs) + local language = _G.OSSettings.language .. ".lang" + if not fs.exists(pathToLangs .. "/" .. language) then + language = "English.lang" + end + return language +end + +--Чтение языкового файла (для операционки) +function ecs.readCorrectLangFile(pathToLangs) + local lang + + local language = ecs.getCorrectLangName(pathToLangs) + + lang = config.readAll(pathToLangs .. "/" .. language) + + return lang +end + +-------------------------ВСЕ ДЛЯ ОСКИ------------------------------------------------------------------------------- + +function ecs.searchInArray(array, textToSearch) + local newArray = {} + for i = 1, #array do + if string.find(unicode.lower(array[i]), unicode.lower(textToSearch)) then table.insert(newArray, array[i]) end + end + return newArray +end + +function ecs.sortFiles(path, fileList, sortingMethod, showHiddenFiles) + local sortedFileList = {} + if sortingMethod == "type" or sortingMethod == 0 then + local typeList = {} + for i = 1, #fileList do + local fileFormat = ecs.getFileFormat(fileList[i]) or "Script" + if fs.isDirectory(path .. fileList[i]) and fileFormat ~= ".app" then fileFormat = "Folder" end + typeList[fileFormat] = typeList[fileFormat] or {} + table.insert(typeList[fileFormat], fileList[i]) + end + + if typeList["Folder"] then + for i = 1, #typeList["Folder"] do + table.insert(sortedFileList, typeList["Folder"][i]) + end + typeList["Folder"] = nil + end + + for fileFormat in pairs(typeList) do + for i = 1, #typeList[fileFormat] do + table.insert(sortedFileList, typeList[fileFormat][i]) + end + end + elseif sortingMethod == "name" or sortingMethod == 1 then + sortedFileList = fileList + elseif sortingMethod == "date" or sortingMethod == 2 then + for i = 1, #fileList do + fileList[i] = {fileList[i], fs.lastModified(path .. fileList[i])} + end + table.sort(fileList, function(a,b) return a[2] > b[2] end) + for i = 1, #fileList do + table.insert(sortedFileList, fileList[i][1]) + end + else + error("Unknown sorting method: " .. tostring(sortingMethod)) + end + + local i = 1 + while i <= #sortedFileList do + if not showHiddenFiles and ecs.isFileHidden(sortedFileList[i]) then + table.remove(sortedFileList, i) + else + i = i + 1 + end + end + + return sortedFileList +end + +--Сохранить файл конфигурации ОС +function ecs.saveOSSettings() + local pathToOSSettings = "MineOS/System/OS/OSSettings.cfg" + if not _G.OSSettings then error("Массив настроек ОС отсутствует в памяти!") end + fs.makeDirectory(fs.path(pathToOSSettings)) + local file = io.open(pathToOSSettings, "w") + file:write(serialization.serialize(_G.OSSettings)) + file:close() +end + +--Загрузить файл конфигурации ОС, а если его не существует, то создать +function ecs.loadOSSettings() + local pathToOSSettings = "MineOS/System/OS/OSSettings.cfg" + if fs.exists(pathToOSSettings) then + local file = io.open(pathToOSSettings, "r") + _G.OSSettings = serialization.unserialize(file:read("*a")) + file:close() + else + _G.OSSettings = { showHelpOnApplicationStart = true, language = "Russian" } + ecs.saveOSSettings() + end +end + +--Отобразить окно с содержимым файла информации о приложении +function ecs.applicationHelp(pathToApplication) + local pathToAboutFile = pathToApplication .. "/resources/About/" .. _G.OSSettings.language .. ".txt" + if _G.OSSettings and _G.OSSettings.showHelpOnApplicationStart and fs.exists(pathToAboutFile) then + local applicationName = fs.name(pathToApplication) + local file = io.open(pathToAboutFile, "r") + local text = "" + for line in file:lines() do text = text .. line .. " " end + file:close() + + local data = ecs.universalWindow("auto", "auto", 30, 0xeeeeee, true, + {"EmptyLine"}, + {"CenterText", 0x000000, "О приложении " .. applicationName}, + {"EmptyLine"}, + {"TextField", 16, 0xFFFFFF, 0x262626, 0xcccccc, 0x353535, text}, + {"EmptyLine"}, + {"Button", {ecs.colors.orange, 0x262626, "OK"}, {0x999999, 0xffffff, "Больше не показывать"}} + ) + if data[1] ~= "OK" then + _G.OSSettings.showHelpOnApplicationStart = false + ecs.saveOSSettings() + end + end +end + +function ecs.correctFileNameIfFileExists(path, requestedName) + local number = 1 + local fileFormat = ecs.getFileFormat(requestedName) or "" + requestedName = ecs.hideFileFormat(requestedName) + while true do + local finalFileName = requestedName .. string.rep("-copy", number) .. fileFormat + if fs.exists(path .. "/" .. finalFileName) then + number = number + 1 + else + return finalFileName + end + end +end + +--Создать ярлык для конкретной проги (для операционки) +function ecs.createShortCut(pathToShortcut, pathToFile) + local pathToPathToShortcut = fs.path(pathToShortcut) or "/" + fs.makeDirectory(pathToPathToShortcut) + if fs.exists(pathToShortcut) then + pathToShortcut = ecs.correctFileNameIfFileExists(pathToPathToShortcut, pathToShortcut) + end + + local file = io.open(pathToShortcut, "w") + file:write("return ", "\"", pathToFile, "\"") + file:close() +end + +--Получить данные о файле из ярлыка (для операционки) +function ecs.readShortcut(path) + local success, filename = pcall(loadfile(path)) + if success then + return filename + else + error("Ошибка чтения файла ярлыка. Вероятно, он создан криво, либо не существует в папке \"" .. fs.path(path) or "" .. "\"") + end +end + +--Редактирование файла (для операционки) +function ecs.editFile(path) + ecs.prepareToExit() + shell.execute("edit " .. path) +end + +--Копирование файлов и папок +function ecs.copy(from, toFolder) + fs.makeDirectory(toFolder) + + if fs.isDirectory(from) then + local currentAction, yesToAllAction + local function recursiveFolderCopy(from, to) + for file in fs.list(from) do + local finalFromName = from .. "/" .. file + local finalToName = to .. "/" .. file + + if fs.exists(finalToName) then + if not yesToAllAction then + currentAction, yesToAll = ecs.askForReplaceFile(finalToName, true) + if yesToAll == true then yesToAllAction = true end + end + else + currentAction = 1 + end + + if currentAction == 1 then + if fs.isDirectory(finalFromName) then + fs.makeDirectory(finalToName) + recursiveFolderCopy(finalFromName, finalToName) + else + fs.copy(finalFromName, finalToName) + end + end + end + end + + recursiveFolderCopy(from, toFolder .. fs.name(from)) + else + local to = toFolder .. "/" .. fs.name(from) + local action = 1 + if fs.exists(to) then action = ecs.askForReplaceFile(to) end + if action == 1 then fs.copy(from, to) end + end +end + +-- Анимация затухания экрана +function ecs.fadeOut(startColor, targetColor, speed) + local xSize, ySize = component.gpu.getResolution() + while startColor >= targetColor do + component.gpu.setBackground(startColor) + component.gpu.fill(1, 1, xSize, ySize, " ") + startColor = startColor - 0x111111 + os.sleep(speed or 0) + end +end + +-- Анимация загорания экрана +function ecs.fadeIn(startColor, targetColor, speed) + local xSize, ySize = component.gpu.getResolution() + while startColor <= targetColor do + component.gpu.setBackground(startColor) + component.gpu.fill(1, 1, xSize, ySize, " ") + startColor = startColor + 0x111111 + os.sleep(speed or 0) + end +end + +-- Анимация выхода в олдскул-телевизионном стиле +function ecs.TV(speed, targetColor) + local xSize, ySize = component.gpu.getResolution() + local xCenter, yCenter = math.floor(xSize / 2), math.floor(ySize / 2) + component.gpu.setBackground(targetColor or 0x000000) + + for y = 1, yCenter do + component.gpu.fill(1, y - 1, xSize, 1, " ") + component.gpu.fill(1, ySize - y + 1, xSize, 1, " ") + os.sleep(speed or 0) + end + + for x = 1, xCenter - 1 do + component.gpu.fill(x, yCenter, 1, 1, " ") + component.gpu.fill(xSize - x + 1, yCenter, 1, 1, " ") + os.sleep(speed or 0) + end + os.sleep(0.3) + component.gpu.fill(1, yCenter, xSize, 1, " ") +end + + + +---------------------------------------------ОКОШЕЧКИ------------------------------------------------------------ + + +--Описание ниже, ебана. Ниже - это значит в самой жопе кода! +function ecs.universalWindow(x, y, width, background, closeWindowAfter, ...) + local objects = {...} + local countOfObjects = #objects + + local pressedButton + local pressedMultiButton + + --Задаем высотные константы для объектов + local objectsHeights = { + ["button"] = 3, + ["centertext"] = 1, + ["emptyline"] = 1, + ["input"] = 3, + ["slider"] = 3, + ["select"] = 3, + ["selector"] = 3, + ["separator"] = 1, + ["switch"] = 1, + ["color"] = 3, + } + + --Скорректировать ширину, если нужно + local function correctWidth(newWidthForAnalyse) + width = math.max(width, newWidthForAnalyse) + end + + --Корректируем ширину + for i = 1, countOfObjects do + local objectType = string.lower(objects[i][1]) + + if objectType == "centertext" then + correctWidth(unicode.len(objects[i][3]) + 2) + elseif objectType == "slider" then --!!!!!!!!!!!!!!!!!! ВОТ ТУТ НЕ ЗАБУДЬ ФИКСАНУТЬ + correctWidth(unicode.len(objects[i][7]..tostring(objects[i][5].." ")) + 2) + elseif objectType == "select" then + for j = 4, #objects[i] do + correctWidth(unicode.len(objects[i][j]) + 2) + end + --elseif objectType == "selector" then + + --elseif objectType == "separator" then + + elseif objectType == "textfield" then + correctWidth(5) + elseif objectType == "wrappedtext" then + correctWidth(6) + elseif objectType == "button" then + --Корректируем ширину + local widthOfButtons = 0 + local maxButton = 0 + for j = 2, #objects[i] do + maxButton = math.max(maxButton, unicode.len(objects[i][j][3]) + 2) + end + widthOfButtons = maxButton * #objects[i] + correctWidth(widthOfButtons) + elseif objectType == "switch" then + local dlina = unicode.len(objects[i][5]) + 2 + 10 + 4 + correctWidth(dlina) + elseif objectType == "color" then + correctWidth(unicode.len(objects[i][2]) + 6) + end + end + + --Считаем высоту этой хуйни + local height = 0 + for i = 1, countOfObjects do + local objectType = string.lower(objects[i][1]) + if objectType == "select" then + height = height + (objectsHeights[objectType] * (#objects[i] - 3)) + elseif objectType == "textfield" then + height = height + objects[i][2] + elseif objectType == "wrappedtext" then + --Заранее парсим текст перенесенный + objects[i].wrapped = string.wrap({objects[i][3]}, width - 4) + objects[i].height = #objects[i].wrapped + height = height + objects[i].height + else + height = height + objectsHeights[objectType] + end + end + + --Коорректируем стартовые координаты + x, y = ecs.correctStartCoords(x, y, width, height) + --Запоминаем инфу о том, что было нарисовано, если это необходимо + local oldPixels, oldBackground, oldForeground + if closeWindowAfter then + oldBackground = component.gpu.getBackground() + oldForeground = component.gpu.getForeground() + oldPixels = ecs.rememberOldPixels(x, y, x + width - 1, y + height - 1) + end + --Считаем все координаты объектов + objects[1].y = y + if countOfObjects > 1 then + for i = 2, countOfObjects do + local objectType = string.lower(objects[i - 1][1]) + if objectType == "select" then + objects[i].y = objects[i - 1].y + (objectsHeights[objectType] * (#objects[i - 1] - 3)) + elseif objectType == "textfield" then + objects[i].y = objects[i - 1].y + objects[i - 1][2] + elseif objectType == "wrappedtext" then + objects[i].y = objects[i - 1].y + objects[i - 1].height + else + objects[i].y = objects[i - 1].y + objectsHeights[objectType] + end + end + end + + --Объекты для тача + local obj = {} + local function newObj(class, name, ...) + obj[class] = obj[class] or {} + obj[class][name] = {...} + end + + --Отображение объекта по номеру + local function displayObject(number, active) + local objectType = string.lower(objects[number][1]) + + if objectType == "centertext" then + local xPos = x + math.floor(width / 2 - unicode.len(objects[number][3]) / 2) + component.gpu.setForeground(objects[number][2]) + component.gpu.setBackground(background) + component.gpu.set(xPos, objects[number].y, objects[number][3]) + + elseif objectType == "input" then + + if active then + --Рамочка + ecs.border(x + 1, objects[number].y, width - 2, objectsHeights.input, background, objects[number][3]) + --Тестик + objects[number][4] = ecs.inputText(x + 3, objects[number].y + 1, width - 6, "", background, objects[number][3], false, objects[number][5]) + else + --Рамочка + ecs.border(x + 1, objects[number].y, width - 2, objectsHeights.input, background, objects[number][2]) + --Текстик + component.gpu.set(x + 3, objects[number].y + 1, ecs.stringLimit("start", objects[number][4], width - 6)) + ecs.inputText(x + 3, objects[number].y + 1, width - 6, objects[number][4], background, objects[number][2], true, objects[number][5]) + end + + newObj("Inputs", number, x + 1, objects[number].y, x + width - 2, objects[number].y + 2) + + elseif objectType == "slider" then + local widthOfSlider = width - 2 + local xOfSlider = x + 1 + local yOfSlider = objects[number].y + 1 + local countOfSliderThings = objects[number][5] - objects[number][4] + local showSliderValue= objects[number][7] + + local dolya = widthOfSlider / countOfSliderThings + local position = math.floor(dolya * objects[number][6]) + --Костыль + if (xOfSlider + position) > (xOfSlider + widthOfSlider - 1) then position = widthOfSlider - 2 end + + --Две линии + ecs.separator(xOfSlider, yOfSlider, position, background, objects[number][3]) + ecs.separator(xOfSlider + position, yOfSlider, widthOfSlider - position, background, objects[number][2]) + --Слудир + ecs.square(xOfSlider + position, yOfSlider, 2, 1, objects[number][3]) + + --Текстик под слудиром + if showSliderValue then + local text = showSliderValue .. tostring(objects[number][6]) .. (objects[number][8] or "") + local textPos = (xOfSlider + widthOfSlider / 2 - unicode.len(text) / 2) + ecs.square(x, yOfSlider + 1, width, 1, background) + ecs.colorText(textPos, yOfSlider + 1, objects[number][2], text) + end + + newObj("Sliders", number, xOfSlider, yOfSlider, x + widthOfSlider, yOfSlider, dolya) + + elseif objectType == "select" then + local usualColor = objects[number][2] + local selectionColor = objects[number][3] + + objects[number].selectedData = objects[number].selectedData or 1 + + local symbol = "✔" + local yPos = objects[number].y + for i = 4, #objects[number] do + --Коробка для галочки + ecs.border(x + 1, yPos, 5, 3, background, usualColor) + --Текст + component.gpu.set(x + 7, yPos + 1, objects[number][i]) + --Галочка + if objects[number].selectedData == (i - 3) then + ecs.colorText(x + 3, yPos + 1, selectionColor, symbol) + else + component.gpu.set(x + 3, yPos + 1, " ") + end + + obj["Selects"] = obj["Selects"] or {} + obj["Selects"][number] = obj["Selects"][number] or {} + obj["Selects"][number][i - 3] = { x + 1, yPos, x + width - 2, yPos + 2 } + + yPos = yPos + objectsHeights.select + end + + elseif objectType == "selector" then + local borderColor = objects[number][2] + local arrowColor = objects[number][3] + local selectorWidth = width - 2 + objects[number].selectedElement = objects[number].selectedElement or objects[number][4] + + local topLine = "┌" .. string.rep("─", selectorWidth - 6) .. "┬───┐" + local midLine = "│" .. string.rep(" ", selectorWidth - 6) .. "│ │" + local botLine = "└" .. string.rep("─", selectorWidth - 6) .. "┴───┘" + + local yPos = objects[number].y + + local function bordak(borderColor) + component.gpu.setBackground(background) + component.gpu.setForeground(borderColor) + component.gpu.set(x + 1, objects[number].y, topLine) + component.gpu.set(x + 1, objects[number].y + 1, midLine) + component.gpu.set(x + 1, objects[number].y + 2, botLine) + component.gpu.set(x + 3, objects[number].y + 1, ecs.stringLimit("start", objects[number].selectedElement, width - 6)) + ecs.colorText(x + width - 4, objects[number].y + 1, arrowColor, "▼") + end + + bordak(borderColor) + + --Выпадающий список, самый гемор, блядь + if active then + local xPos, yPos = x + 1, objects[number].y + 3 + local spisokWidth = width - 2 + local countOfElements = #objects[number] - 3 + local spisokHeight = countOfElements + 1 + local oldPixels = ecs.rememberOldPixels( xPos, yPos, xPos + spisokWidth - 1, yPos + spisokHeight - 1) + + local coords = {} + + bordak(arrowColor) + + --Рамку рисуем поверх фоника + local topLine = "├"..string.rep("─", spisokWidth - 6).."┴───┤" + local midLine = "│"..string.rep(" ", spisokWidth - 2).."│" + local botLine = "└"..string.rep("─", selectorWidth - 2) .. "┘" + ecs.colorTextWithBack(xPos, yPos - 1, arrowColor, background, topLine) + for i = 1, spisokHeight - 1 do + component.gpu.set(xPos, yPos + i - 1, midLine) + end + component.gpu.set(xPos, yPos + spisokHeight - 1, botLine) + + --Элементы рисуем + xPos = xPos + 2 + for i = 1, countOfElements do + ecs.colorText(xPos, yPos, borderColor, ecs.stringLimit("start", objects[number][i + 3], spisokWidth - 4)) + coords[i] = {xPos - 1, yPos, xPos + spisokWidth - 4, yPos} + yPos = yPos + 1 + end + + --Обработка + local exit + while true do + if exit then break end + local e = {event.pull()} + if e[1] == "touch" then + for i = 1, #coords do + if ecs.clickedAtArea(e[3], e[4], coords[i][1], coords[i][2], coords[i][3], coords[i][4]) then + ecs.square(coords[i][1], coords[i][2], spisokWidth - 2, 1, ecs.colors.blue) + ecs.colorText(coords[i][1] + 1, coords[i][2], 0xffffff, objects[number][i + 3]) + os.sleep(0.3) + objects[number].selectedElement = objects[number][i + 3] + exit = true + break + end + end + end + end + + ecs.drawOldPixels(oldPixels) + end + + newObj("Selectors", number, x + 1, objects[number].y, x + width - 2, objects[number].y + 2) + + elseif objectType == "separator" then + ecs.separator(x, objects[number].y, width, background, objects[number][2]) + + elseif objectType == "textfield" then + newObj("TextFields", number, x + 1, objects[number].y, x + width - 2, objects[number].y + objects[number][2] - 1) + if not objects[number].strings then objects[number].strings = string.wrap({objects[number][7]}, width - 3) end + objects[number].displayFrom = objects[number].displayFrom or 1 + ecs.textField(x, objects[number].y, width, objects[number][2], objects[number].strings, objects[number].displayFrom, objects[number][3], objects[number][4], objects[number][5], objects[number][6]) + + elseif objectType == "wrappedtext" then + component.gpu.setBackground(background) + component.gpu.setForeground(objects[number][2]) + for i = 1, #objects[number].wrapped do + component.gpu.set(x + 2, objects[number].y + i - 1, objects[number].wrapped[i]) + end + + elseif objectType == "button" then + + obj["MultiButtons"] = obj["MultiButtons"] or {} + obj["MultiButtons"][number] = {} + + local widthOfButton = math.floor(width / (#objects[number] - 1)) + + local xPos, yPos = x, objects[number].y + for i = 1, #objects[number] do + if type(objects[number][i]) == "table" then + local x1, y1, x2, y2 = ecs.drawButton(xPos, yPos, widthOfButton, 3, objects[number][i][3], objects[number][i][1], objects[number][i][2]) + table.insert(obj["MultiButtons"][number], {x1, y1, x2, y2, widthOfButton}) + xPos = x2 + 1 + + if i == #objects[number] then + ecs.square(xPos, yPos, x + width - xPos, 3, objects[number][i][1]) + obj["MultiButtons"][number][i - 1][5] = obj["MultiButtons"][number][i - 1][5] + x + width - xPos + end + + x1, y1, x2, y2 = nil, nil, nil, nil + end + end + + elseif objectType == "switch" then + + local xPos, yPos = x + 2, objects[number].y + local activeColor, passiveColor, textColor, text, state = objects[number][2], objects[number][3], objects[number][4], objects[number][5], objects[number][6] + local switchWidth = 8 + ecs.colorTextWithBack(xPos, yPos, textColor, background, text) + + xPos = x + width - switchWidth - 2 + if state then + ecs.square(xPos, yPos, switchWidth, 1, activeColor) + ecs.square(xPos + switchWidth - 2, yPos, 2, 1, passiveColor) + --ecs.colorTextWithBack(xPos + 4, yPos, passiveColor, activeColor, "ON") + else + ecs.square(xPos, yPos, switchWidth, 1, passiveColor - 0x444444) + ecs.square(xPos, yPos, 2, 1, passiveColor) + --ecs.colorTextWithBack(xPos + 4, yPos, passiveColor, passiveColor - 0x444444, "OFF") + end + newObj("Switches", number, xPos, yPos, xPos + switchWidth - 1, yPos) + + elseif objectType == "color" then + local xPos, yPos = x + 1, objects[number].y + local blendedColor = require("color").blend(objects[number][3], 0xFFFFFF, 0.705882) + local w = width - 2 + + ecs.colorTextWithBack(xPos, yPos + 2, blendedColor, background, string.rep("▀", w)) + ecs.colorText(xPos, yPos, objects[number][3], string.rep("▄", w)) + ecs.square(xPos, yPos + 1, w, 1, objects[number][3]) + + ecs.colorText(xPos + 1, yPos + 1, 0xffffff - objects[number][3], objects[number][2]) + newObj("Colors", number, xPos, yPos, x + width - 2, yPos + 2) + end + end + + --Отображение всех объектов + local function displayAllObjects() + for i = 1, countOfObjects do + displayObject(i) + end + end + + --Подготовить массив возвращаемый + local function getReturn() + local massiv = {} + + for i = 1, countOfObjects do + local type = string.lower(objects[i][1]) + + if type == "button" then + table.insert(massiv, pressedButton) + elseif type == "input" then + table.insert(massiv, objects[i][4]) + elseif type == "select" then + table.insert(massiv, objects[i][objects[i].selectedData + 3]) + elseif type == "selector" then + table.insert(massiv, objects[i].selectedElement) + elseif type == "slider" then + table.insert(massiv, objects[i][6]) + elseif type == "switch" then + table.insert(massiv, objects[i][6]) + elseif type == "color" then + table.insert(massiv, objects[i][3]) + else + table.insert(massiv, nil) + end + end + + return massiv + end + + local function redrawBeforeClose() + if closeWindowAfter then + ecs.drawOldPixels(oldPixels) + component.gpu.setBackground(oldBackground) + component.gpu.setForeground(oldForeground) + end + end + + --Рисуем окно + ecs.square(x, y, width, height, background) + displayAllObjects() + + while true do + local e = {event.pull()} + if e[1] == "touch" or e[1] == "drag" then + + --Анализируем клик на кнопки + if obj["MultiButtons"] then + for key in pairs(obj["MultiButtons"]) do + for i = 1, #obj["MultiButtons"][key] do + if ecs.clickedAtArea(e[3], e[4], obj["MultiButtons"][key][i][1], obj["MultiButtons"][key][i][2], obj["MultiButtons"][key][i][3], obj["MultiButtons"][key][i][4]) then + ecs.drawButton(obj["MultiButtons"][key][i][1], obj["MultiButtons"][key][i][2], obj["MultiButtons"][key][i][5], 3, objects[key][i + 1][3], objects[key][i + 1][2], objects[key][i + 1][1]) + os.sleep(0.3) + pressedButton = objects[key][i + 1][3] + redrawBeforeClose() + return getReturn() + end + end + end + end + + --А теперь клик на инпуты! + if obj["Inputs"] then + for key in pairs(obj["Inputs"]) do + if ecs.clickedAtArea(e[3], e[4], obj["Inputs"][key][1], obj["Inputs"][key][2], obj["Inputs"][key][3], obj["Inputs"][key][4]) then + displayObject(key, true) + displayObject(key) + break + end + end + end + + --А теперь галочковыбор! + if obj["Selects"] then + for key in pairs(obj["Selects"]) do + for i in pairs(obj["Selects"][key]) do + if ecs.clickedAtArea(e[3], e[4], obj["Selects"][key][i][1], obj["Selects"][key][i][2], obj["Selects"][key][i][3], obj["Selects"][key][i][4]) then + objects[key].selectedData = i + displayObject(key) + break + end + end + end + end + + --Хм, а вот и селектор подъехал! + if obj["Selectors"] then + for key in pairs(obj["Selectors"]) do + if ecs.clickedAtArea(e[3], e[4], obj["Selectors"][key][1], obj["Selectors"][key][2], obj["Selectors"][key][3], obj["Selectors"][key][4]) then + displayObject(key, true) + displayObject(key) + break + end + end + end + + --Слайдеры, епта! "Потный матан", все делы + if obj["Sliders"] then + for key in pairs(obj["Sliders"]) do + if ecs.clickedAtArea(e[3], e[4], obj["Sliders"][key][1], obj["Sliders"][key][2], obj["Sliders"][key][3], obj["Sliders"][key][4]) then + local xOfSlider, dolya = obj["Sliders"][key][1], obj["Sliders"][key][5] + local currentPixels = e[3] - xOfSlider + local currentValue = math.floor(currentPixels / dolya) + --Костыль + if e[3] == obj["Sliders"][key][3] then currentValue = objects[key][5] end + objects[key][6] = currentValue or objects[key][6] + displayObject(key) + break + end + end + end + + if obj["Switches"] then + for key in pairs(obj["Switches"]) do + if ecs.clickedAtArea(e[3], e[4], obj["Switches"][key][1], obj["Switches"][key][2], obj["Switches"][key][3], obj["Switches"][key][4]) then + objects[key][6] = not objects[key][6] + displayObject(key) + break + end + end + end + + if obj["Colors"] then + for key in pairs(obj["Colors"]) do + if ecs.clickedAtArea(e[3], e[4], obj["Colors"][key][1], obj["Colors"][key][2], obj["Colors"][key][3], obj["Colors"][key][4]) then + local oldColor = objects[key][3] + objects[key][3] = 0xffffff - objects[key][3] + displayObject(key) + os.sleep(0.3) + objects[key][3] = oldColor + displayObject(key) + + local paletteWidth, paletteHeight = 75, 27 + local screenWidth, screenHeight = component.gpu.getResolution() + local paletteX, paletteY = math.floor(screenWidth / 2 - paletteWidth / 2), math.floor(screenHeight / 2 - paletteHeight / 2) + local oldPixels = ecs.rememberOldPixels(paletteX, paletteY, paletteX + paletteWidth - 1, paletteY + paletteHeight - 1) + local color = require("GUI").palette(paletteX, paletteY, objects[key][3]):show() + ecs.drawOldPixels(oldPixels) + objects[key][3] = color or oldColor + + displayObject(key) + break + end + end + end + + elseif e[1] == "scroll" then + if obj["TextFields"] then + for key in pairs(obj["TextFields"]) do + if ecs.clickedAtArea(e[3], e[4], obj["TextFields"][key][1], obj["TextFields"][key][2], obj["TextFields"][key][3], obj["TextFields"][key][4]) then + if e[5] == 1 then + if objects[key].displayFrom > 1 then objects[key].displayFrom = objects[key].displayFrom - 1; displayObject(key) end + else + if objects[key].displayFrom < #objects[key].strings then objects[key].displayFrom = objects[key].displayFrom + 1; displayObject(key) end + end + end + end + end + elseif e[1] == "key_down" then + if e[4] == 28 then + redrawBeforeClose() + return getReturn() + end + end + end +end + +--Демонстрационное окно, показывающее всю мощь universalWindow +function ecs.demoWindow() + --Очищаем экран перед юзанием окна и ставим курсор на 1, 1 + ecs.prepareToExit() + --Рисуем окно и получаем данные после взаимодействия с ним + local data = ecs.universalWindow("auto", "auto", 36, 0xeeeeee, true, + {"EmptyLine"}, + {"CenterText", 0x880000, "Здорово, ебана!"}, + {"EmptyLine"}, + {"Input", 0x262626, 0x880000, "Сюда вводить можно"}, + {"Selector", 0x262626, 0x880000, "Выбор формата", "PNG", "JPG", "GIF", "PSD"}, + {"EmptyLine"}, + {"WrappedText", 0x262626, "Тест автоматического переноса букв в зависимости от ширины данного окна. Пока что тупо режет на куски, не особо красиво."}, + {"EmptyLine"}, + {"Select", 0x262626, 0x880000, "Я пидор", "Я не пидор"}, + {"Slider", 0x262626, 0x880000, 1, 100, 50, "Убито ", " младенцев"}, + {"EmptyLine"}, + {"Separator", 0xaaaaaa}, + {"Switch", 0xF2B233, 0xffffff, 0x262626, "✈ Авиарежим", false}, + {"EmptyLine"}, + {"Switch", 0x3366CC, 0xffffff, 0x262626, "☾ Не беспокоить", true}, + {"Separator", 0xaaaaaa}, + {"EmptyLine"}, + {"TextField", 5, 0xffffff, 0x262626, 0xcccccc, 0x3366CC, "Тест текстового информационного поля. По сути это тот же самый WrappedText, разве что эта хрень ограничена по высоте, и ее можно скроллить. Ну же, поскролль меня! Скролль меня полностью! Моя жадная пизда жаждет твой хуй!"}, + {"Color", "Цвет фона", 0xFF0000}, + {"EmptyLine"}, + {"Button", {0x57A64E, 0xffffff, "Да"}, {0xF2B233, 0xffffff, "Нет"}, {0xCC4C4C, 0xffffff, "Отмена"}} + ) + --Еще разок + ecs.prepareToExit() + --Выводим данные + print(" ") + print("Вывод данных из окна:") + for i = 1, #data do print("["..i.."] = "..tostring(data[i])) end + print(" ") +end + +-- ecs.demoWindow() + +--[[ +Функция universalWindow(x, y, width, background, closeWindowAfter, ...) + + Это универсальная модульная функция для максимально удобного и быстрого отображения + необходимой вам информации. С ее помощью вводить данные с клавиатуры, осуществлять выбор + из предложенных вариантов, рисовать красивые кнопки, отрисовывать обычный текст, + отрисовывать текстовые поля с возможностью прокрутки, рисовать разделители и прочее. + Любой объект выделяется с помощью клика мыши, после чего функция приступает к работе + с этим объектом. + +Аргументы функции: + + x и y: это числа, обозначающие стартовые координаты левого верхнего угла данного окна. + Вместо цифр вы также можете написать "auto" - и программа автоматически разместит окно + по центру экрана по выбранной координате. Или по обеим координатам, если вам угодно. + + width: это ширина окна, которую вы можете задать по собственному желанию. Если некторые + объекты требуют расширения окна, то окно будет автоматически расширено до нужной ширины. + Да, вот такая вот тавтология ;) + + background: базовый цвет окна (цвет фона, кому как понятнее). + + closeWindowAfter: eсли true, то окно по завершению функции будет выгружено, а на его месте + отрисуются пиксели, которые имелись на экране до выполнения функции. Удобно, если не хочешь + париться с перерисовкой интерфейса. + + ... : многоточием тут является перечень объектов, указанных через запятую. Каждый объект + является массивом и имеет собственный формат. Ниже перечислены все возможные типы объектов. + + {"Button", {Цвет кнопки1, Цвет текста на кнопке1, Сам текст1}, {Цвет кнопки2, Цвет текста на кнопке2, Сам текст2}, ...} + + Это объект для рисования кнопок. Каждая кнопка - это массив, состоящий из трех элементов: + цвета кнопки, цвета текста на кнопке и самого текста. Кнопок может быть неограниченное количество, + однако чем их больше, тем большее требуется разрешение экрана по ширине. + + Интерактивный объект. + + {"Input", Цвет рамки и текста, Цвет при выделении, Стартовый текст [, Маскировать символом]} + + Объект для рисования полей ввода текстовой информации. Удобно для открытия или сохранения файлов, + Опциональный аргумент "Маскировать символом" полезен, если вы делаете поле для ввода пароля. + Никто не увидит ваш текст. В качестве данного аргумента передается символ, например "*". + + Интерактивный объект. + + {"Selector", Цвет рамки, Цвет при выделении, Выбор 1, Выбор 2, Выбор 3 ...} + + Внешне схож с объектом "Input", однако в этом случае вы будете выбирать один из предложенных + вариантов из выпадающего списка. По умолчанию выбран первый вариант. + + Интерактивный объект. + + {"Select", Цвет рамки, Цвет галочки, Выбор 1, Выбор 2, Выбор 3 ...} + + Объект выбора. Отличается от "Selector" тем, что здесь вы выбираете один из вариантов, отмечая + его галочкой. По умолчанию выбран первый вариант. + + Интерактивный объект. + + {"Slider", Цвет линии слайдера, Цвет пимпочки слайдера, Значения слайдера ОТ, Значения слайдера ДО, Текущее значение [, Текст-подсказка ДО] [, Текст-подсказка ПОСЛЕ]} + + Ползунок, позволяющий задавать определенное количество чего-либо в указанном интервале. Имеются два + опциональных аргумента, позволяющих четко понимать, с чем именно мы имеем дело. + + К примеру, если аргумент "Текст-подсказка ДО" будет равен "Съедено ", а аргумент "Текст-подсказка ПОСЛЕ" + будет равен " яблок", а значение слайдера будет равно 50, то на экране будет написано "Съедено 50 яблок". + + Интерактивный объект. + + {"Switch", Активный цвет, Пассивный цвет, Цвет текста, Текст, Состояние} + + Переключатель, принимающий два состояния: true или false. Текст - это всего лишь информация, некое + название данного переключателя. + + Интерактивный объект. + + {"CenterText", Цвет текста, Сам текст} + + Отображение текста указанного цвета по центру окна. Чисто для информативных целей. + + {"WrappedText", Цвет текста, Текст} + + Отображение большого количества текста с автоматическим переносом. Прото режет слова на кусочки, + перенос символический. Чисто для информативных целей. + + {"TextField", Высота, Цвет фона, Цвет текста, Цвет скроллбара, Цвет пимпочки скроллбара, Сам текст} + + Текстовое поле с возможностью прокрутки. Отличается от "WrappedText" + фиксированной высотой. Чисто для информативных целей. + + {"Separator", Цвет разделителя} + + Линия-разделитель, помогающая лучше отделять объекты друг от друга. Декоративный объект. + + {"EmptyLine"} + + Пустое пространство, помогающая лучше отделять объекты друг от друга. Декоративный объект. + + Каждый из объектов рисуется по порядку сверху вниз. Каждый объект автоматически + увеличивает высоту окна до необходимого значения. Если объектов будет указано слишком много - + т.е. если окно вылезет за пределы экрана, то программа завершится с ошибкой. + + Что возвращает функция: + + Возвратом является массив, пронумерованный от 1 до <количества объектов>. + К примеру, 1 индекс данного массива соответствует 1 указанному объекту. + Каждый индекс данного массива несет в себе какие-то данные, которые вы + внесли в объект во время работы функции. + Например, если в 1-ый объект типа "Input" вы ввели фразу "Hello world", + то первый индекс в возвращенном массиве будет равен "Hello world". + Конкретнее это будет вот так: massiv[1] = "Hello world". + + Если взаимодействие с объектом невозможно - например, как в случае + с EmptyLine, CenterText, TextField или Separator, то в возвращенном + массиве этот объект указываться не будет. + + Готовые примеры использования функции указаны ниже и закомментированы. + Выбирайте нужный и раскомментируйте. +]] + +--Функция-демонстратор, показывающая все возможные объекты в одном окне. Код окна находится выше. +--ecs.demoWindow() + +--Функция, предлагающая сохранить файл в нужном месте в нужном формате. +--ecs.universalWindow("auto", "auto", 30, ecs.windowColors.background, true, {"EmptyLine"}, {"CenterText", 0x262626, "Сохранить как"}, {"EmptyLine"}, {"Input", 0x262626, 0x880000, "Путь"}, {"Selector", 0x262626, 0x880000, "PNG", "JPG", "PSD"}, {"EmptyLine"}, {"Button", {0xbbbbbb, 0xffffff, "OK!"}}) + +---------------------------------------------------------------------------------------------------- + +return ecs + + diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/FormatModules/OCAF.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/FormatModules/OCAF.lua new file mode 100755 index 00000000..faad2cb9 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/FormatModules/OCAF.lua @@ -0,0 +1,211 @@ + +require("advancedLua") +local bit32 = require("bit32") +local unicode = require("unicode") +local fs = require("filesystem") + +----------------------------------------------------------------------------------------------- + +local module = {} + +local encodingMethods = {} +local OCAFSignature = "OCAF" +local readBufferSize = 1024 +local ignoredFiles = { + [".DS_Store"] = true +} + +----------------------------------------------------------------------------------------------- + +local function getFileList(path) + local fileList = {} + for file in fs.list(path) do + table.insert(fileList, path .. "/" .. file) + end + + return fileList +end + +local function readPath(archiveFileHandle) + local sizeOfSizeArray = {} + for i = 1, string.byte(archiveFileHandle:read(1)) do + table.insert(sizeOfSizeArray, string.byte(archiveFileHandle:read(1))) + end + + return archiveFileHandle:read(bit32.byteArrayToNumber(sizeOfSizeArray)) +end + +-- Записываем путь в виде <кол-во байт для размера пути> <размер пути> <путь> +local function writePath(archiveFileHandle, path) + -- Получаем юникод-байтики названия файла или папки + local pathBytes = {} + for i = 1, unicode.len(path) do + local charBytes = { string.byte(unicode.sub(path, i, i), 1, 6) } + for j = 1, #charBytes do + table.insert(pathBytes, charBytes[j]) + end + end + + -- Записываем количество всякой хуйни + local bytesForCountPathBytes = bit32.numberToByteArray(#pathBytes) + archiveFileHandle:write(string.char(#bytesForCountPathBytes)) + for i = 1, #bytesForCountPathBytes do + archiveFileHandle:write(string.char(bytesForCountPathBytes[i])) + end + + -- Записываем путь + for i = 1, #pathBytes do + archiveFileHandle:write(string.char(pathBytes[i])) + end +end + +----------------------------------------------------------------------------------------------- + +encodingMethods[0] = {} + +encodingMethods[0].pack = function(archiveFileHandle, fileList, localPath) + for i = 1, #fileList do + local filename = fs.name(fileList[i]) or "" + local currentLocalPath = (localPath or "") .. "/" .. filename + -- print("Writing path:", currentLocalPath) + + if not ignoredFiles[filename] then + if fs.isDirectory(fileList[i]) then + archiveFileHandle:write(string.char(1)) + writePath(archiveFileHandle, currentLocalPath) + + local success, reason = encodingMethods[0].pack(archiveFileHandle, getFileList(fileList[i]), currentLocalPath) + if not success then + return success, reason + end + else + archiveFileHandle:write(string.char(0)) + writePath(archiveFileHandle, currentLocalPath) + + local fileHandle, reason = io.open(fileList[i], "rb") + if fileHandle then + -- Пишем размер файла + local fileSize = fs.size(fileList[i]) + local fileSizeBytes = bit32.numberToByteArray(fileSize) + archiveFileHandle:write(string.char(#fileSizeBytes)) + for i = 1, #fileSizeBytes do + archiveFileHandle:write(string.char(fileSizeBytes[i])) + end + + -- Пишем содержимое + local data + while true do + data = fileHandle:read(readBufferSize) + if data then + archiveFileHandle:write(data) + else + break + end + end + + fileHandle:close() + else + return false, "Failed to open file for reading: " .. tostring(reason) + end + end + end + end + + return true +end + +encodingMethods[0].unpack = function(archiveFileHandle, unpackPath) + while true do + local typeData = archiveFileHandle:read(1) + if typeData then + local type = string.byte(typeData) + local localPath = unpackPath .. readPath(archiveFileHandle) + -- print("Readed path:", localPath) + + if type == 0 then + -- Читаем размер файлика + local sizeOfSizeArray = {} + for i = 1, string.byte(archiveFileHandle:read(1)) do + table.insert(sizeOfSizeArray, string.byte(archiveFileHandle:read(1))) + end + local fileSize = bit32.byteArrayToNumber(sizeOfSizeArray) + -- print("Readed file size:", fileSize) + + -- Читаем и записываем содержимое файлика + local fileHandle, reason = io.open(localPath, "wb") + if fileHandle then + local readedCount, needToRead, data = 0 + while readedCount < fileSize do + needToRead = math.min(readBufferSize, fileSize - readedCount) + fileHandle:write(archiveFileHandle:read(needToRead)) + readedCount = readedCount + needToRead + end + + fileHandle:close() + else + return false, "Failed to open file for writing: " .. tostring(reason) + end + else + fs.makeDirectory(localPath) + end + else + return true + end + end +end + +----------------------------------------------------------------------------------------------- + +module.pack = function(archivePath, fileList, encodingMethod) + local archiveFileHandle, reason = io.open(archivePath, "wb") + if archiveFileHandle then + archiveFileHandle:write(OCAFSignature) + archiveFileHandle:write(string.char(encodingMethod)) + + if encodingMethods[encodingMethod] then + local success, reason = encodingMethods[encodingMethod].pack(archiveFileHandle, fileList) + archiveFileHandle:close() + + return success, reason + else + archiveFileHandle:close() + + return false, "Encoding method " .. tostring(encodingMethod) .. " doesn't supported" + end + else + return false, "Failed to open archive file for writing: " .. tostring(reason) + end +end + + +module.unpack = function(archivePath, unpackPath) + local archiveFileHandle, reason = io.open(archivePath, "rb") + if archiveFileHandle then + local readedSignature = archiveFileHandle:read(#OCAFSignature) + if readedSignature == OCAFSignature then + local readedEncodingMethod = string.byte(archiveFileHandle:read(1)) + if encodingMethods[readedEncodingMethod] then + local success, reason = encodingMethods[readedEncodingMethod].unpack(archiveFileHandle, unpackPath) + archiveFileHandle:close() + + return success, reason + else + archiveFileHandle:close() + + return false, "Encoding method " .. tostring(encodingMethod) .. " doesn't supported" + end + else + archiveFileHandle:close() + + return false, "Archive signature doesn't match OCAF" + end + else + return false, "Failed to open archive file for reading: " .. tostring(reason) + end +end + +----------------------------------------------------------------------------------------------- + +return module + + diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/FormatModules/OCIF.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/FormatModules/OCIF.lua new file mode 100755 index 00000000..bf45df02 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/FormatModules/OCIF.lua @@ -0,0 +1,201 @@ + +local args = {...} +local image = args[1] + +---------------------------------------- Libraries ---------------------------------------- + +local bit32 = require("bit32") +require("advancedLua") +local unicode = require("unicode") +local fs = require("filesystem") +local color = require("color") + +------------------------------------------------------------------------------------------------------------ + +local module = {} +local OCIFSignature = "OCIF" +local encodingMethods = { + load = {}, + save = {} +} + +------------------------------------------------------------------------------------------------------------ + +local function writeByteArrayToFile(file, byteArray) + for i = 1, #byteArray do + file:write(string.char(byteArray[i])) + end +end + +local function readNumberFromFile(file, countOfBytes) + local byteArray = {} + for i = 1, countOfBytes do + table.insert(byteArray, string.byte(file:read(1))) + end + + return bit32.byteArrayToNumber(byteArray) +end + +---------------------------------------- Uncompressed OCIF1 encoding ---------------------------------------- + +encodingMethods.save[5] = function(file, picture) + for i = 3, #picture, 4 do + file:write(string.char(color.to8Bit(picture[i]))) + file:write(string.char(color.to8Bit(picture[i + 1]))) + file:write(string.char(math.floor(picture[i + 2] * 255))) + writeByteArrayToFile(file, {string.byte(picture[i + 3], 1, 6)}) + end +end + +encodingMethods.load[5] = function(file, picture) + table.insert(picture, readNumberFromFile(file, 2)) + table.insert(picture, readNumberFromFile(file, 2)) + + for i = 1, image.getWidth(picture) * image.getHeight(picture) do + table.insert(picture, color.to24Bit(string.byte(file:read(1)))) + table.insert(picture, color.to24Bit(string.byte(file:read(1)))) + table.insert(picture, string.byte(file:read(1)) / 255) + table.insert(picture, fs.readUnicodeChar(file)) + end +end + +---------------------------------------- Grouped and compressed OCIF6 encoding ---------------------------------------- + +encodingMethods.save[6] = function(file, picture) + -- Grouping picture by it's alphas, symbols and colors + local groupedPicture = image.group(picture, true) + -- Writing 1 byte for alphas array size + file:write(string.char(table.size(groupedPicture))) + + for alpha in pairs(groupedPicture) do + -- Writing 1 byte for current alpha value + file:write(string.char(math.floor(alpha * 255))) + -- Writing 2 bytes for symbols array size + writeByteArrayToFile(file, bit32.numberToFixedSizeByteArray(table.size(groupedPicture[alpha]), 2)) + + for symbol in pairs(groupedPicture[alpha]) do + -- Writing N bytes for current unicode symbol value + writeByteArrayToFile(file, { string.byte(symbol, 1, 6) }) + -- Writing 1 byte for backgrounds array size + file:write(string.char(table.size(groupedPicture[alpha][symbol]))) + + for background in pairs(groupedPicture[alpha][symbol]) do + -- Writing 1 byte for background color value (compressed by color) + file:write(string.char(background)) + -- Writing 1 byte for foregrounds array size + file:write(string.char(table.size(groupedPicture[alpha][symbol][background]))) + + for foreground in pairs(groupedPicture[alpha][symbol][background]) do + -- Writing 1 byte for foreground color value (compressed by color) + file:write(string.char(foreground)) + -- Writing 1 byte for y array size + file:write(string.char(table.size(groupedPicture[alpha][symbol][background][foreground]))) + + for y in pairs(groupedPicture[alpha][symbol][background][foreground]) do + -- Writing 1 byte for current y value + file:write(string.char(y)) + -- Writing 1 byte for x array size + file:write(string.char(#groupedPicture[alpha][symbol][background][foreground][y])) + + for x = 1, #groupedPicture[alpha][symbol][background][foreground][y] do + file:write(string.char(groupedPicture[alpha][symbol][background][foreground][y][x])) + end + end + end + end + end + end +end + +encodingMethods.load[6] = function(file, picture) + table.insert(picture, string.byte(file:read(1))) + table.insert(picture, string.byte(file:read(1))) + + local currentAlpha, currentSymbol, currentBackground, currentForeground, currentY, currentX + local alphaSize, symbolSize, backgroundSize, foregroundSize, ySize, xSize + + alphaSize = string.byte(file:read(1)) + + for alpha = 1, alphaSize do + currentAlpha = string.byte(file:read(1)) / 255 + symbolSize = readNumberFromFile(file, 2) + + for symbol = 1, symbolSize do + currentSymbol = fs.readUnicodeChar(file) + backgroundSize = string.byte(file:read(1)) + + for background = 1, backgroundSize do + currentBackground = color.to24Bit(string.byte(file:read(1))) + foregroundSize = string.byte(file:read(1)) + + for foreground = 1, foregroundSize do + currentForeground = color.to24Bit(string.byte(file:read(1))) + ySize = string.byte(file:read(1)) + + for y = 1, ySize do + currentY = string.byte(file:read(1)) + xSize = string.byte(file:read(1)) + + for x = 1, xSize do + currentX = string.byte(file:read(1)) + image.set(picture, currentX, currentY, currentBackground, currentForeground, currentAlpha, currentSymbol) + end + end + end + end + end + end +end + +---------------------------------------- Public load&save methods of module ---------------------------------------- + +function module.load(path) + local file, reason = io.open(path, "rb") + if file then + local readedSignature = file:read(#OCIFSignature) + if readedSignature == OCIFSignature then + local encodingMethod = string.byte(file:read(1)) + if encodingMethods.load[encodingMethod] then + local picture = {} + encodingMethods.load[encodingMethod](file, picture) + file:close() + + return picture + else + file:close() + error("Failed to load OCIF image from path (\"" .. tostring(path) .. "\"): encoding method \"" .. tostring(encodingMethod) .. "\" is not supported") + end + else + file:close() + error("Failed to load OCIF image from path (\"" .. tostring(path) .. "\"): wrong signature (\"" .. tostring(readedSignature) .. "\")") + end + else + error("Failed to open file \"" .. tostring(path) .. "\" for reading: " .. tostring(reason)) + end +end + +function module.save(path, picture, encodingMethod) + encodingMethod = encodingMethod or 6 + + local file, reason = io.open(path, "wb") + if file then + if encodingMethods.save[encodingMethod] then + -- Writing signature, encoding method, image width and height + file:write(OCIFSignature, string.char(encodingMethod), string.char(picture[1]), string.char(picture[2])) + -- Executing selected encoding method + encodingMethods.save[encodingMethod](file, picture) + file:close() + else + file:close() + error("Failed to save file as OCIF image: encoding method \"" .. tostring(encodingMethod) .. "\" is not supported") + end + else + error("Failed to open file for writing: " .. tostring(reason)) + end +end + +------------------------------------------------------------------------------------------------------------ + +return module + + diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/FormatModules/RAW.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/FormatModules/RAW.lua new file mode 100755 index 00000000..a0b8668a --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/FormatModules/RAW.lua @@ -0,0 +1,70 @@ + +local args = {...} +local image = args[1] + +local unicode = require("unicode") +local module = {} + +-------------------------------------------------------------------------------------------------------------- + +function module.load(path) + local file, reason = io.open(path, "r") + if file then + local picture, pictureWidth, lineCounter = {0, 0}, nil, 0 + for line in file:lines() do + local lineLength = unicode.len(line) + if not pictureWidth then + pictureWidth = (lineLength + 1) / 19 + picture[1] = pictureWidth + end + + for x = 1, lineLength, 19 do + table.insert(picture, tonumber("0x" .. unicode.sub(line, x, x + 5))) + table.insert(picture, tonumber("0x" .. unicode.sub(line, x + 7, x + 12))) + table.insert(picture, tonumber("0x" .. unicode.sub(line, x + 14, x + 15))) + table.insert(picture, unicode.sub(line, x + 17, x + 17)) + end + + lineCounter = lineCounter + 1 + end + + picture[2] = lineCounter + file:close() + return picture + else + error("Failed to open file \"" .. tostring(path) .. "\" for reading: " .. tostring(reason)) + end +end + +function module.save(path, picture, encodingMethod) + local file, reason = io.open(path, "w") + if file then + local x = 1 + for i = 3, #picture, 4 do + file:write( + string.format("%06X", picture[i]), " ", + string.format("%06X", picture[i + 1]), " ", + string.format("%02X", picture[i + 2]), " ", + picture[i + 3] + ) + + x = x + 1 + if x > picture[1] then + x = 1 + file:write("\n") + else + file:write(" ") + end + end + + file:close() + else + error("Failed to open file for writing: " .. tostring(reason)) + end +end + +-------------------------------------------------------------------------------------------------------------- + +return module + + diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/GUI.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/GUI.lua new file mode 100755 index 00000000..21b4b6b1 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/GUI.lua @@ -0,0 +1,3888 @@ + +require("advancedLua") +local component = require("component") +local computer = require("computer") +local keyboard = require("keyboard") +local fs = require("filesystem") +local unicode = require("unicode") +local event = require("event") +local color = require("color") +local image = require("image") +local buffer = require("doubleBuffering") + +----------------------------------------------------------------------------------------------------- + +local GUI = {} + +GUI.alignment = { + horizontal = enum( + "left", + "center", + "right" + ), + vertical = enum( + "top", + "center", + "bottom" + ) +} + +GUI.directions = enum( + "horizontal", + "vertical" +) + +GUI.sizePolicies = enum( + "percentage", + "absolute" +) + +GUI.dropDownMenuElementTypes = enum( + "default", + "separator" +) + +GUI.filesystemModes = enum( + "file", + "directory", + "both", + "open", + "save" +) + +GUI.colors = { + disabled = { + background = 0x888888, + text = 0xAAAAAA + }, + contextMenu = { + separator = 0x888888, + default = { + background = 0xFFFFFF, + text = 0x2D2D2D + }, + disabled = 0x888888, + pressed = { + background = 0x3366CC, + text = 0xFFFFFF + }, + transparency = { + background = 0.24, + shadow = 0.4 + } + }, +} + +GUI.paletteConfigPath = "/lib/.palette.cfg" + +----------------------------------------- Interface objects ----------------------------------------- + +local function callMethod(method, ...) + if method then method(...) end +end + +local function objectIsClicked(object, x, y) + return + x >= object.x and + y >= object.y and + x <= object.x + object.width - 1 and + y <= object.y + object.height - 1 and + not object.disabled and + not object.hidden +end + +local function objectDraw(object) + return object +end + +function GUI.object(x, y, width, height) + return { + x = x, + y = y, + width = width, + height = height, + isClicked = objectIsClicked, + draw = objectDraw + } +end + +----------------------------------------- Object alignment ----------------------------------------- + +function GUI.setAlignment(object, horizontalAlignment, verticalAlignment) + object.alignment = { + horizontal = horizontalAlignment, + vertical = verticalAlignment + } + + return object +end + +function GUI.getAlignmentCoordinates(object, subObject) + local x, y + if object.alignment.horizontal == GUI.alignment.horizontal.left then + x = object.x + elseif object.alignment.horizontal == GUI.alignment.horizontal.center then + x = object.x + object.width / 2 - subObject.width / 2 + elseif object.alignment.horizontal == GUI.alignment.horizontal.right then + x = object.x + object.width - subObject.width + else + error("Unknown horizontal alignment: " .. tostring(object.alignment.horizontal)) + end + + if object.alignment.vertical == GUI.alignment.vertical.top then + y = object.y + elseif object.alignment.vertical == GUI.alignment.vertical.center then + y = object.y + object.height / 2 - subObject.height / 2 + elseif object.alignment.vertical == GUI.alignment.vertical.bottom then + y = object.y + object.height - subObject.height + else + error("Unknown vertical alignment: " .. tostring(object.alignment.vertical)) + end + + return x, y +end + +function GUI.getMarginCoordinates(object) + local x, y = object.x, object.y + + if object.alignment.horizontal == GUI.alignment.horizontal.left then + x = x + object.margin.horizontal + elseif object.alignment.horizontal == GUI.alignment.horizontal.right then + x = x - object.margin.horizontal + end + + if object.alignment.vertical == GUI.alignment.vertical.top then + y = y + object.margin.vertical + elseif object.alignment.vertical == GUI.alignment.vertical.bottom then + y = y - object.margin.vertical + end + + return x, y +end + +----------------------------------------- Containers ----------------------------------------- + +local function containerObjectIndexOf(object) + if not object.parent then error("Object doesn't have a parent container") end + + for objectIndex = 1, #object.parent.children do + if object.parent.children[objectIndex] == object then + return objectIndex + end + end +end + +local function containerObjectMoveForward(object) + local objectIndex = containerObjectIndexOf(object) + if objectIndex < #object.parent.children then + object.parent.children[index], object.parent.children[index + 1] = object.parent.children[index + 1], object.parent.children[index] + end + + return object +end + +local function containerObjectMoveBackward(object) + local objectIndex = containerObjectIndexOf(object) + if objectIndex > 1 then + object.parent.children[objectIndex], object.parent.children[objectIndex - 1] = object.parent.children[objectIndex - 1], object.parent.children[objectIndex] + end + + return object +end + +local function containerObjectMoveToFront(object) + table.remove(object.parent.children, containerObjectIndexOf(object)) + table.insert(object.parent.children, object) + + return object +end + +local function containerObjectMoveToBack(object) + table.remove(object.parent.children, containerObjectIndexOf(object)) + table.insert(object.parent.children, 1, object) + + return object +end + +local function containerObjectGetFirstParent(object) + local currentParent = object.parent + while currentParent.parent do + currentParent = currentParent.parent + end + + return currentParent +end + +local function containerObjectSelfDelete(object) + table.remove(object.parent.children, containerObjectIndexOf(object)) +end + +----------------------------------------------------------------------------------------------------- + +local function containerObjectAnimationStart(animation, duration) + animation.position = 0 + animation.duration = duration + animation.started = true + animation.startUptime = computer.uptime() + + computer.pushSignal("GUI", "animationStarted") +end + +local function containerObjectAnimationStop(animation) + animation.position = 0 + animation.started = false +end + +local function containerObjectAnimationDelete(animation) + animation.deleteLater = true +end + +local function containerObjectAddAnimation(object, frameHandler, onFinish) + local animation = { + object = object, + position = 0, + start = containerObjectAnimationStart, + stop = containerObjectAnimationStop, + delete = containerObjectAnimationDelete, + frameHandler = frameHandler, + onFinish = onFinish, + } + + local firstParent = object:getFirstParent() + firstParent.animations = firstParent.animations or {} + table.insert(firstParent.animations, animation) + + return animation +end + +function GUI.addChildToContainer(container, object, atIndex) + object.localX = object.x + object.localY = object.y + object.indexOf = containerObjectIndexOf + object.moveToFront = containerObjectMoveToFront + object.moveToBack = containerObjectMoveToBack + object.moveForward = containerObjectMoveForward + object.moveBackward = containerObjectMoveBackward + object.getFirstParent = containerObjectGetFirstParent + object.delete = containerObjectSelfDelete + object.parent = container + object.addAnimation = containerObjectAddAnimation + + if atIndex then + table.insert(container.children, atIndex, object) + else + table.insert(container.children, object) + end + + return object +end + +local function deleteContainersContent(container, from, to) + from = from or 1 + for objectIndex = from, to or #container.children do + table.remove(container.children, from) + end +end + +local function getRectangleIntersection(R1X1, R1Y1, R1X2, R1Y2, R2X1, R2Y1, R2X2, R2Y2) + if R2X1 <= R1X2 and R2Y1 <= R1Y2 and R2X2 >= R1X1 and R2Y2 >= R1Y1 then + return + math.max(R2X1, R1X1), + math.max(R2Y1, R1Y1), + math.min(R2X2, R1X2), + math.min(R2Y2, R1Y2) + else + return + end +end + +function GUI.drawContainerContent(container) + local R1X1, R1Y1, R1X2, R1Y2, child = buffer.getDrawLimit() + local intersectionX1, intersectionY1, intersectionX2, intersectionY2 = getRectangleIntersection( + R1X1, + R1Y1, + R1X2, + R1Y2, + container.x, + container.y, + container.x + container.width - 1, + container.y + container.height - 1 + ) + + if intersectionX1 then + buffer.setDrawLimit(intersectionX1, intersectionY1, intersectionX2, intersectionY2) + + for i = 1, #container.children do + child = container.children[i] + + if not child.hidden then + child.x, child.y = container.x + child.localX - 1, container.y + child.localY - 1 + child:draw() + end + end + + buffer.setDrawLimit(R1X1, R1Y1, R1X2, R1Y2) + end + + return container +end + +local function containerHandler(isScreenEvent, mainContainer, currentContainer, eventData, intersectionX1, intersectionY1, intersectionX2, intersectionY2) + local breakRecursion, child = false + + if not isScreenEvent or intersectionX1 and eventData[3] >= intersectionX1 and eventData[4] >= intersectionY1 and eventData[3] <= intersectionX2 and eventData[4] <= intersectionY2 then + for i = #currentContainer.children, 1, -1 do + child = currentContainer.children[i] + + if not child.hidden then + if child.children then + local newIntersectionX1, newIntersectionY1, newIntersectionX2, newIntersectionY2 = getRectangleIntersection( + intersectionX1, + intersectionY1, + intersectionX2, + intersectionY2, + child.x, + child.y, + child.x + child.width - 1, + child.y + child.height - 1 + ) + + if newIntersectionX1 then + if containerHandler(isScreenEvent, mainContainer, child, eventData, newIntersectionX1, newIntersectionY1, newIntersectionX2, newIntersectionY2) then + breakRecursion = true + break + end + end + else + if isScreenEvent then + if child:isClicked(eventData[3], eventData[4]) then + callMethod(child.eventHandler, mainContainer, child, eventData) + breakRecursion = true + break + end + else + callMethod(child.eventHandler, mainContainer, child, eventData) + end + end + end + end + + callMethod(currentContainer.eventHandler, mainContainer, currentContainer, eventData) + end + + if breakRecursion then + return true + end +end + +local function containerStartEventHandling(container, eventHandlingDelay) + container.eventHandlingDelay = eventHandlingDelay + + local eventData, animationIndex, animation, animationOnFinishMethods + repeat + eventData = { event.pull(container.animations and 0 or container.eventHandlingDelay) } + + containerHandler( + ( + eventData[1] == "touch" or + eventData[1] == "drag" or + eventData[1] == "drop" or + eventData[1] == "scroll" or + eventData[1] == "double_touch" + ), + container, + container, + eventData, + container.x, + container.y, + container.x + container.width - 1, + container.y + container.height - 1 + ) + + if container.animations then + animationIndex, animationOnFinishMethods = 1, {} + + -- Продрачиваем анимации и вызываем обработчики кадров + while animationIndex <= #container.animations do + animation = container.animations[animationIndex] + + if animation.deleteLater then + table.remove(container.animations, animationIndex) + if #container.animations == 0 then + container.animations = nil + break + end + else + if animation.started then + animationNeedDraw = true + animation.position = (computer.uptime() - animation.startUptime) / animation.duration + + if animation.position < 1 then + animation.frameHandler(container, animation) + else + animation.position = 1 + animation.started = false + animation.frameHandler(container, animation) + + if animation.onFinish then + table.insert(animationOnFinishMethods, animation) + end + end + end + + animationIndex = animationIndex + 1 + end + end + + -- По завершению продрочки отрисовываем изменения на экране + container:draw() + buffer.draw() + + -- Вызываем поочередно все методы .onFinish + for i = 1, #animationOnFinishMethods do + animationOnFinishMethods[i].onFinish(container, animationOnFinishMethods[i]) + end + end + until container.dataToReturn + + local dataToReturn = container.dataToReturn + container.dataToReturn = nil + return table.unpack(dataToReturn) +end + +local function containerReturnData(container, ...) + container.dataToReturn = {...} +end + +local function containerStopEventHandling(container) + containerReturnData(container, nil) +end + +function GUI.container(x, y, width, height) + local container = GUI.object(x, y, width, height) + + container.children = {} + container.draw = GUI.drawContainerContent + container.deleteChildren = deleteContainersContent + container.addChild = GUI.addChildToContainer + container.returnData = containerReturnData + container.startEventHandling = containerStartEventHandling + container.stopEventHandling = containerStopEventHandling + + return container +end + +function GUI.fullScreenContainer() + return GUI.container(1, 1, buffer.getResolution()) +end + +----------------------------------------- Buttons ----------------------------------------- + +local function buttonPlayAnimation(button, onFinish) + button.animationStarted = true + button:addAnimation( + function(mainContainer, animation) + if button.pressed then + if button.colors.default.background and button.colors.pressed.background then + button.animationCurrentBackground = color.transition(button.colors.pressed.background, button.colors.default.background, animation.position) + end + button.animationCurrentText = color.transition(button.colors.pressed.text, button.colors.default.text, animation.position) + else + if button.colors.default.background and button.colors.pressed.background then + button.animationCurrentBackground = color.transition(button.colors.default.background, button.colors.pressed.background, animation.position) + end + button.animationCurrentText = color.transition(button.colors.default.text, button.colors.pressed.text, animation.position) + end + end, + function(mainContainer, animation) + button.animationStarted = false + button.pressed = not button.pressed + onFinish(mainContainer, animation) + end + ):start(button.animationDuration) +end + +local function buttonPress(button, mainContainer, object, eventData) + if button.animated then + buttonPlayAnimation(button, function(mainContainer, animation) + if button.onTouch then + button.onTouch(mainContainer, button, eventData) + end + + animation:delete() + + if not button.switchMode then + buttonPlayAnimation(button, function(mainContainer, animation) + animation:delete() + end) + end + end) + else + button.pressed = not button.pressed + + mainContainer:draw() + buffer.draw() + + if not button.switchMode then + button.pressed = not button.pressed + + os.sleep(0.2) + + mainContainer:draw() + buffer.draw() + end + + if button.onTouch then + button.onTouch(mainContainer, button, eventData) + end + end +end + +local function buttonEventHandler(mainContainer, button, eventData) + if eventData[1] == "touch" then + button:press(mainContainer, button, eventData) + end +end + +local function buttonGetColors(button) + if button.disabled then + return button.colors.disabled.background, button.colors.disabled.text + else + if button.animated and button.animationStarted then + return button.animationCurrentBackground, button.animationCurrentText + else + if button.pressed then + return button.colors.pressed.background, button.colors.pressed.text + else + return button.colors.default.background, button.colors.default.text + end + end + end +end + +local function buttonDrawText(button, textColor) + buffer.text(math.floor(button.x + button.width / 2 - unicode.len(button.text) / 2), math.floor(button.y + button.height / 2), textColor, button.text) +end + +local function buttonDraw(button) + local backgroundColor, textColor = buttonGetColors(button) + if backgroundColor then + buffer.square(button.x, button.y, button.width, button.height, backgroundColor, textColor, " ", button.colors.transparency) + end + + buttonDrawText(button, textColor) +end + +local function framedButtonDraw(button) + local backgroundColor, textColor = buttonGetColors(button) + if backgroundColor then + buffer.frame(button.x, button.y, button.width, button.height, backgroundColor) + end + + buttonDrawText(button, textColor) +end + +local function roundedButtonDraw(button) + local backgroundColor, textColor = buttonGetColors(button) + + if backgroundColor then + local x2, y2 = button.x + button.width - 1, button.y + button.height - 1 + if button.height > 1 then + buffer.text(button.x + 1, button.y, backgroundColor, string.rep("▄", button.width - 2)) + buffer.text(button.x, button.y, backgroundColor, "⣠") + buffer.text(x2, button.y, backgroundColor, "⣄") + + buffer.square(button.x, button.y + 1, button.width, button.height - 2, backgroundColor, textColor, " ") + + buffer.text(button.x + 1, y2, backgroundColor, string.rep("▀", button.width - 2)) + buffer.text(button.x, y2, backgroundColor, "⠙") + buffer.text(x2, y2, backgroundColor, "⠋") + else + buffer.square(button.x, button.y, button.width, button.height, backgroundColor, textColor, " ") + GUI.roundedCorners(button.x, button.y, button.width, button.height, backgroundColor) + end + end + + buttonDrawText(button, textColor) +end + +local function buttonCreate(x, y, width, height, backgroundColor, textColor, backgroundPressedColor, textPressedColor, text) + local button = GUI.object(x, y, width, height) + + button.colors = { + default = { + background = backgroundColor, + text = textColor + }, + pressed = { + background = backgroundPressedColor, + text = textPressedColor + }, + disabled = { + background = GUI.colors.disabled.background, + text = GUI.colors.disabled.text + } + } + button.animationCurrentBackground = backgroundColor + button.animationCurrentText = textColor + + button.text = text + button.animationDuration = 0.2 + button.animated = true + button.pressed = false + + button.press = buttonPress + button.eventHandler = buttonEventHandler + + return button +end + +local function adaptiveButtonCreate(x, y, xOffset, yOffset, backgroundColor, textColor, backgroundPressedColor, textPressedColor, text) + return buttonCreate(x, y, unicode.len(text) + xOffset * 2, yOffset * 2 + 1, backgroundColor, textColor, backgroundPressedColor, textPressedColor, text) +end + +function GUI.button(...) + local button = buttonCreate(...) + button.draw = buttonDraw + return button +end + +function GUI.adaptiveButton(...) + local button = adaptiveButtonCreate(...) + button.draw = buttonDraw + return button +end + +function GUI.framedButton(...) + local button = buttonCreate(...) + button.draw = framedButtonDraw + return button +end + +function GUI.adaptiveFramedButton(...) + local button = adaptiveButtonCreate(...) + button.draw = framedButtonDraw + return button +end + +function GUI.roundedButton(...) + local button = buttonCreate(...) + button.draw = roundedButtonDraw + return button +end + +function GUI.adaptiveRoundedButton(...) + local button = adaptiveButtonCreate(...) + button.draw = roundedButtonDraw + return button +end + +----------------------------------------- Panel ----------------------------------------- + +local function drawPanel(object) + buffer.square(object.x, object.y, object.width, object.height, object.colors.background, 0x000000, " ", object.colors.transparency) + return object +end + +function GUI.panel(x, y, width, height, color, transparency) + local object = GUI.object(x, y, width, height) + + object.colors = { + background = color, + transparency = transparency + } + object.draw = drawPanel + + return object +end + +----------------------------------------- Label ----------------------------------------- + +local function drawLabel(object) + local xText, yText = GUI.getAlignmentCoordinates(object, {width = unicode.len(object.text), height = 1}) + buffer.text(math.floor(xText), math.floor(yText), object.colors.text, object.text) + return object +end + +function GUI.label(x, y, width, height, textColor, text) + local object = GUI.object(x, y, width, height) + object.setAlignment = GUI.setAlignment + object:setAlignment(GUI.alignment.horizontal.left, GUI.alignment.vertical.top) + object.colors = {text = textColor} + object.text = text + object.draw = drawLabel + return object +end + +----------------------------------------- Image ----------------------------------------- + +local function drawImage(object) + buffer.image(object.x, object.y, object.image) + return object +end + +function GUI.image(x, y, image) + local object = GUI.object(x, y, image[1], image[2]) + object.image = image + object.draw = drawImage + return object +end + +----------------------------------------- Action buttons ----------------------------------------- + +function GUI.actionButtons(x, y, fatSymbol) + local symbol = fatSymbol and "⬤" or "●" + + local container = GUI.container(x, y, 5, 1) + container.close = container:addChild(GUI.button(1, 1, 1, 1, nil, 0xFF4940, nil, 0x992400, symbol)) + container.minimize = container:addChild(GUI.button(3, 1, 1, 1, nil, 0xFFB640, nil, 0x996D00, symbol)) + container.maximize = container:addChild(GUI.button(5, 1, 1, 1, nil, 0x00B640, nil, 0x006D40, symbol)) + + return container +end + +----------------------------------------- Menu ----------------------------------------- + +local function menuDraw(menu) + buffer.square(menu.x, menu.y, menu.width, 1, menu.colors.default.background, menu.colors.default.text, " ", menu.colors.transparency) + menu:reimplementedDraw() +end + +local function menuItemEventHandler(mainContainer, object, eventData) + if eventData[1] == "touch" then + if object.onTouch then + object.pressed = true + mainContainer:draw() + buffer.draw() + + object.onTouch(eventData) + + object.pressed = false + mainContainer:draw() + buffer.draw() + end + end +end + +local function menuAddItem(menu, text, textColor) + local x = 2; for i = 1, #menu.children do x = x + unicode.len(menu.children[i].text) + 2; end + local item = menu:addChild(GUI.adaptiveButton(x, 1, 1, 0, nil, textColor or menu.colors.default.text, menu.colors.pressed.background, menu.colors.pressed.text, text)) + item.animated = false + item.eventHandler = menuItemEventHandler + + return item +end + +function GUI.menu(x, y, width, backgroundColor, textColor, backgroundPressedColor, textPressedColor, backgroundTransparency) + local menu = GUI.container(x, y, width, 1) + + menu.colors = { + default = { + background = backgroundColor, + text = textColor, + }, + pressed = { + background = backgroundPressedColor, + text = textPressedColor, + }, + transparency = backgroundTransparency + } + menu.addItem = menuAddItem + menu.reimplementedDraw = menu.draw + menu.draw = menuDraw + + return menu +end + +----------------------------------------- ProgressBar Object ----------------------------------------- + +local function drawProgressBar(object) + local activeWidth = math.floor(math.min(object.value, 100) / 100 * object.width) + if object.thin then + buffer.text(object.x, object.y, object.colors.passive, string.rep("━", object.width)) + buffer.text(object.x, object.y, object.colors.active, string.rep("━", activeWidth)) + else + buffer.square(object.x, object.y, object.width, object.height, object.colors.passive, 0x0, " ") + buffer.square(object.x, object.y, activeWidth, object.height, object.colors.active, 0x0, " ") + end + + if object.showValue then + local stringValue = (object.valuePrefix or "") .. object.value .. (object.valuePostfix or "") + buffer.text(math.floor(object.x + object.width / 2 - unicode.len(stringValue) / 2), object.y + 1, object.colors.value, stringValue) + end + + return object +end + +function GUI.progressBar(x, y, width, activeColor, passiveColor, valueColor, value, thin, showValue, valuePrefix, valuePostfix) + local object = GUI.object(x, y, width, 1) + + object.value = value + object.colors = {active = activeColor, passive = passiveColor, value = valueColor} + object.thin = thin + object.draw = drawProgressBar + object.showValue = showValue + object.valuePrefix = valuePrefix + object.valuePostfix = valuePostfix + + return object +end + +----------------------------------------- Other GUI elements ----------------------------------------- + +function GUI.windowShadow(x, y, width, height, transparency, thin) + transparency = transparency + if thin then + buffer.square(x + width, y + 1, 1, height - 1, 0x000000, 0x000000, " ", transparency) + buffer.text(x + 1, y + height, 0x000000, string.rep("▀", width), transparency) + buffer.text(x + width, y, 0x000000, "▄", transparency) + else + buffer.square(x + width, y + 1, 2, height, 0x000000, 0x000000, " ", transparency) + buffer.square(x + 2, y + height, width - 2, 1, 0x000000, 0x000000, " ", transparency) + end +end + +function GUI.roundedCorners(x, y, width, height, color, transparency) + buffer.text(x - 1, y, color, "⠰", transparency) + buffer.text(x + width, y, color, "⠆", transparency) +end + +------------------------------------------------- Error window ------------------------------------------------------------------- + +function GUI.error(...) + local args = {...} + for i = 1, #args do + if type(args[i]) == "table" then + args[i] = table.toString(args[i], true) + else + args[i] = tostring(args[i]) + end + end + if #args == 0 then args[1] = "nil" end + + local sign = image.fromString([[06030000FF 0000FF 00F7FF▟00F7FF▙0000FF 0000FF 0000FF 00F7FF▟F7FF00 F7FF00 00F7FF▙0000FF 00F7FF▟F7FF00CF7FF00yF7FF00kF7FF00a00F7FF▙]]) + local offset = 2 + local lines = #args > 1 and "\"" .. table.concat(args, "\", \"") .. "\"" or args[1] + local bufferWidth, bufferHeight = buffer.getResolution() + local width = math.floor(bufferWidth * 0.5) + local textWidth = width - image.getWidth(sign) - 2 + + lines = string.wrap(lines, textWidth) + local height = image.getHeight(sign) + if #lines + 2 > height then + height = #lines + 2 + end + + local mainContainer = GUI.container(1, math.floor(bufferHeight / 2 - height / 2), bufferWidth, height + offset * 2) + local oldPixels = buffer.copy(mainContainer.x, mainContainer.y, mainContainer.width, mainContainer.height) + + local x, y = math.floor(bufferWidth / 2 - width / 2), offset + 1 + mainContainer:addChild(GUI.panel(1, 1, mainContainer.width, mainContainer.height, 0x1D1D1D)) + mainContainer:addChild(GUI.image(x, y, sign)) + mainContainer:addChild(GUI.textBox(x + image.getWidth(sign) + 2, y, textWidth, #lines, 0x1D1D1D, 0xE1E1E1, lines, 1, 0, 0)).eventHandler = nil + local buttonWidth = 10 + local button = mainContainer:addChild(GUI.roundedButton(x + image.getWidth(sign) + textWidth - buttonWidth + 2, mainContainer.height - offset, buttonWidth, 1, 0x3366CC, 0xE1E1E1, 0xE1E1E1, 0x3366CC, "OK")) + + button.onTouch = function() + mainContainer:stopEventHandling() + buffer.paste(mainContainer.x, mainContainer.y, oldPixels) + buffer.draw() + end + + mainContainer.eventHandler = function(mainContainer, object, eventData) + if eventData[1] == "key_down" and eventData[4] == 28 then + button.animated = false + button:press(mainContainer, object, eventData) + end + end + + mainContainer:draw() + buffer.draw(true) + mainContainer:startEventHandling() +end + +----------------------------------------- CodeView object ----------------------------------------- + +local function codeViewDraw(codeView) + local syntax = require("syntax") + local toLine = codeView.fromLine + codeView.height - 1 + + -- Line numbers bar and code area + codeView.lineNumbersWidth = unicode.len(tostring(toLine)) + 2 + codeView.codeAreaPosition = codeView.x + codeView.lineNumbersWidth + codeView.codeAreaWidth = codeView.width - codeView.lineNumbersWidth + buffer.square(codeView.x, codeView.y, codeView.lineNumbersWidth, codeView.height, syntax.colorScheme.lineNumbersBackground, syntax.colorScheme.lineNumbersText, " ") + buffer.square(codeView.codeAreaPosition, codeView.y, codeView.codeAreaWidth, codeView.height, syntax.colorScheme.background, syntax.colorScheme.text, " ") + + -- Line numbers texts + local y = codeView.y + for line = codeView.fromLine, toLine do + if codeView.lines[line] then + local text = tostring(line) + if codeView.highlights[line] then + buffer.square(codeView.x, y, codeView.lineNumbersWidth, 1, codeView.highlights[line], syntax.colorScheme.text, " ", 0.3) + buffer.square(codeView.codeAreaPosition, y, codeView.codeAreaWidth, 1, codeView.highlights[line], syntax.colorScheme.text, " ") + end + buffer.text(codeView.codeAreaPosition - unicode.len(text) - 1, y, syntax.colorScheme.lineNumbersText, text) + y = y + 1 + else + break + end + end + + local oldDrawLimitX1, oldDrawLimitY1, oldDrawLimitX2, oldDrawLimitY2 = buffer.getDrawLimit() + buffer.setDrawLimit(codeView.codeAreaPosition, codeView.y, codeView.codeAreaPosition + codeView.codeAreaWidth - 1, codeView.y + codeView.height - 1) + + local function drawUpperSelection(y, selectionIndex) + buffer.square( + codeView.codeAreaPosition + codeView.selections[selectionIndex].from.symbol - codeView.fromSymbol + 1, + y + codeView.selections[selectionIndex].from.line - codeView.fromLine, + codeView.codeAreaWidth - codeView.selections[selectionIndex].from.symbol + codeView.fromSymbol - 1, + 1, + codeView.selections[selectionIndex].color or syntax.colorScheme.selection, syntax.colorScheme.text, " " + ) + end + + local function drawLowerSelection(y, selectionIndex) + buffer.square( + codeView.codeAreaPosition, + y + codeView.selections[selectionIndex].from.line - codeView.fromLine, + codeView.selections[selectionIndex].to.symbol - codeView.fromSymbol + 2, + 1, + codeView.selections[selectionIndex].color or syntax.colorScheme.selection, syntax.colorScheme.text, " " + ) + end + + if #codeView.selections > 0 then + for selectionIndex = 1, #codeView.selections do + y = codeView.y + local dy = codeView.selections[selectionIndex].to.line - codeView.selections[selectionIndex].from.line + if dy == 0 then + buffer.square( + codeView.codeAreaPosition + codeView.selections[selectionIndex].from.symbol - codeView.fromSymbol + 1, + y + codeView.selections[selectionIndex].from.line - codeView.fromLine, + codeView.selections[selectionIndex].to.symbol - codeView.selections[selectionIndex].from.symbol + 1, + 1, + codeView.selections[selectionIndex].color or syntax.colorScheme.selection, syntax.colorScheme.text, " " + ) + elseif dy == 1 then + drawUpperSelection(y, selectionIndex); y = y + 1 + drawLowerSelection(y, selectionIndex) + else + drawUpperSelection(y, selectionIndex); y = y + 1 + for i = 1, dy - 1 do + buffer.square(codeView.codeAreaPosition, y + codeView.selections[selectionIndex].from.line - codeView.fromLine, codeView.codeAreaWidth, 1, codeView.selections[selectionIndex].color or syntax.colorScheme.selection, syntax.colorScheme.text, " "); y = y + 1 + end + + drawLowerSelection(y, selectionIndex) + end + end + end + + -- Code strings + y = codeView.y + buffer.setDrawLimit(codeView.codeAreaPosition + 1, y, codeView.codeAreaPosition + codeView.codeAreaWidth - 2, y + codeView.height - 1) + for i = codeView.fromLine, toLine do + if codeView.lines[i] then + if codeView.highlightLuaSyntax then + syntax.highlightString(codeView.codeAreaPosition - codeView.fromSymbol + 2, y, codeView.lines[i], codeView.indentationWidth) + else + buffer.text(codeView.codeAreaPosition - codeView.fromSymbol + 2, y, syntax.colorScheme.text, codeView.lines[i]) + end + y = y + 1 + else + break + end + end + buffer.setDrawLimit(oldDrawLimitX1, oldDrawLimitY1, oldDrawLimitX2, oldDrawLimitY2) + + if #codeView.lines > codeView.height then + codeView.scrollBars.vertical.hidden = false + codeView.scrollBars.vertical.colors.background, codeView.scrollBars.vertical.colors.foreground = syntax.colorScheme.scrollBarBackground, syntax.colorScheme.scrollBarForeground + codeView.scrollBars.vertical.minimumValue, codeView.scrollBars.vertical.maximumValue, codeView.scrollBars.vertical.value, codeView.scrollBars.vertical.shownValueCount = 1, #codeView.lines, codeView.fromLine, codeView.height + codeView.scrollBars.vertical.localX = codeView.width + codeView.scrollBars.vertical.localY = 1 + codeView.scrollBars.vertical.height = codeView.height - 1 + else + codeView.scrollBars.vertical.hidden = true + end + + if codeView.maximumLineLength > codeView.codeAreaWidth - 2 then + codeView.scrollBars.horizontal.hidden = false + codeView.scrollBars.horizontal.colors.background, codeView.scrollBars.horizontal.colors.foreground = syntax.colorScheme.scrollBarBackground, syntax.colorScheme.scrollBarForeground + codeView.scrollBars.horizontal.minimumValue, codeView.scrollBars.horizontal.maximumValue, codeView.scrollBars.horizontal.value, codeView.scrollBars.horizontal.shownValueCount = 1, codeView.maximumLineLength, codeView.fromSymbol, codeView.codeAreaWidth - 2 + codeView.scrollBars.horizontal.localX = codeView.lineNumbersWidth + 1 + codeView.scrollBars.horizontal.localY = codeView.height + codeView.scrollBars.horizontal.width = codeView.codeAreaWidth - 1 + else + codeView.scrollBars.horizontal.hidden = true + end + + codeView:reimplementedDraw() +end + +function GUI.codeView(x, y, width, height, lines, fromSymbol, fromLine, maximumLineLength, selections, highlights, highlightLuaSyntax, indentationWidth) + local codeView = GUI.container(x, y, width, height) + + codeView.lines = lines + codeView.fromSymbol = fromSymbol + codeView.fromLine = fromLine + codeView.maximumLineLength = maximumLineLength + codeView.selections = selections or {} + codeView.highlights = highlights or {} + codeView.highlightLuaSyntax = highlightLuaSyntax + codeView.indentationWidth = indentationWidth + + codeView.scrollBars = { + vertical = codeView:addChild(GUI.scrollBar(1, 1, 1, 1, 0x0, 0x0, 1, 1, 1, 1, 1, true)), + horizontal = codeView:addChild(GUI.scrollBar(1, 1, 1, 1, 0x0, 0x0, 1, 1, 1, 1, 1, true)) + } + + codeView.reimplementedDraw = codeView.draw + codeView.draw = codeViewDraw + + return codeView +end + +----------------------------------------- Color Selector object ----------------------------------------- + +local function colorSelectorDraw(colorSelector) + local overlayColor = colorSelector.color < 0x7FFFFF and 0xFFFFFF or 0x000000 + buffer.square(colorSelector.x, colorSelector.y, colorSelector.width, colorSelector.height, colorSelector.color, overlayColor, " ") + if colorSelector.pressed then + buffer.square(colorSelector.x, colorSelector.y, colorSelector.width, colorSelector.height, overlayColor, overlayColor, " ", 0.8) + end + if colorSelector.height > 1 then + buffer.text(colorSelector.x, colorSelector.y + colorSelector.height - 1, overlayColor, string.rep("▄", colorSelector.width), 0.8) + end + buffer.text(colorSelector.x + 1, colorSelector.y + math.floor(colorSelector.height / 2), overlayColor, string.limit(colorSelector.text, colorSelector.width - 2)) + return colorSelector +end + +local function colorSelectorEventHandler(mainContainer, object, eventData) + if eventData[1] == "touch" then + object.pressed = true + mainContainer:draw() + buffer.draw() + + object.color = GUI.palette(math.floor(mainContainer.width / 2 - 35), math.floor(mainContainer.height / 2 - 12), object.color or object.color):show() + + object.pressed = false + mainContainer:draw() + buffer.draw() + callMethod(object.onTouch, eventData) + end +end + +function GUI.colorSelector(x, y, width, height, color, text) + local colorSelector = GUI.object(x, y, width, height) + + colorSelector.eventHandler = colorSelectorEventHandler + colorSelector.color = color + colorSelector.text = text + colorSelector.draw = colorSelectorDraw + + return colorSelector +end + +----------------------------------------- Chart object ----------------------------------------- + +local function getAxisValue(number, postfix, roundValues) + if roundValues then + return math.floor(number) .. postfix + else + local integer, fractional = math.modf(number) + local firstPart, secondPart = "", "" + if math.abs(integer) >= 1000 then + return math.shorten(integer, 2) .. postfix + else + if math.abs(fractional) > 0 then + return string.format("%.2f", number) .. postfix + else + return number .. postfix + end + end + end +end + +local function drawChart(object) + -- Sorting by x value + local valuesCopy = {} + for i = 1, #object.values do valuesCopy[i] = object.values[i] end + table.sort(valuesCopy, function(a, b) return a[1] < b[1] end) + + if #valuesCopy == 0 then valuesCopy = {{0, 0}} end + + -- Max, min, deltas + local xMin, xMax, yMin, yMax = valuesCopy[1][1], valuesCopy[#valuesCopy][1], valuesCopy[1][2], valuesCopy[1][2] + for i = 1, #valuesCopy do yMin, yMax = math.min(yMin, valuesCopy[i][2]), math.max(yMax, valuesCopy[i][2]) end + local dx, dy = xMax - xMin, yMax - yMin + + -- y axis values and helpers + local value, chartHeight, yAxisValueMaxWidth, yAxisValues = yMin, object.height - 1 - (object.showXAxisValues and 1 or 0), 0, {} + for y = object.y + object.height - 3, object.y + 1, -chartHeight * object.yAxisValueInterval do + local stringValue = getAxisValue(value, object.yAxisPostfix, object.roundValues) + yAxisValueMaxWidth = math.max(yAxisValueMaxWidth, unicode.len(stringValue)) + table.insert(yAxisValues, {y = math.ceil(y), value = stringValue}) + value = value + dy * object.yAxisValueInterval + end + local stringValue = getAxisValue(yMax, object.yAxisPostfix, object.roundValues) + table.insert(yAxisValues, {y = object.y, value = stringValue}) + yAxisValueMaxWidth = math.max(yAxisValueMaxWidth, unicode.len(stringValue)) + + local chartWidth = object.width - (object.showYAxisValues and yAxisValueMaxWidth + 2 or 0) + local chartX = object.x + object.width - chartWidth + for i = 1, #yAxisValues do + if object.showYAxisValues then + buffer.text(chartX - unicode.len(yAxisValues[i].value) - 2, yAxisValues[i].y, object.colors.axisValue, yAxisValues[i].value) + end + buffer.text(chartX, yAxisValues[i].y, object.colors.helpers, string.rep("─", chartWidth)) + end + + -- x axis values + if object.showXAxisValues then + value = xMin + for x = chartX, chartX + chartWidth - 2, chartWidth * object.xAxisValueInterval do + local stringValue = getAxisValue(value, object.xAxisPostfix, object.roundValues) + buffer.text(math.floor(x - unicode.len(stringValue) / 2), object.y + object.height - 1, object.colors.axisValue, stringValue) + value = value + dx * object.xAxisValueInterval + end + local value = getAxisValue(xMax, object.xAxisPostfix, object.roundValues) + buffer.text(object.x + object.width - unicode.len(value), object.y + object.height - 1, object.colors.axisValue, value) + end + + -- Axis lines + for y = object.y, object.y + chartHeight - 1 do + buffer.text(chartX - 1, y, object.colors.axis, "┨") + end + buffer.text(chartX - 1, object.y + chartHeight, object.colors.axis, "┗" .. string.rep("┯━", math.floor(chartWidth / 2))) + + local function fillVerticalPart(x1, y1, x2, y2) + local dx, dy = x2 - x1, y2 - y1 + local absdx, absdy = math.abs(dx), math.abs(dy) + if absdx >= absdy then + local step, y = dy / absdx, y1 + for x = x1, x2, (x1 < x2 and 1 or -1) do + local yFloor = math.floor(y) + buffer.semiPixelSquare(math.floor(x), yFloor, 1, math.floor(object.y + chartHeight) * 2 - yFloor - 1, object.colors.chart) + y = y + step + end + else + local step, x = dx / absdy, x1 + for y = y1, y2, (y1 < y2 and 1 or -1) do + local yFloor = math.floor(y) + buffer.semiPixelSquare(math.floor(x), yFloor, 1, math.floor(object.y + chartHeight) * 2 - yFloor - 1, object.colors.chart) + x = x + step + end + end + end + + -- chart + for i = 1, #valuesCopy - 1 do + local x = math.floor(chartX + (valuesCopy[i][1] - xMin) / dx * (chartWidth - 1)) + local y = math.floor(object.y + chartHeight - 1 - (valuesCopy[i][2] - yMin) / dy * (chartHeight - 1)) * 2 + local xNext = math.floor(chartX + (valuesCopy[i + 1][1] - xMin) / dx * (chartWidth - 1)) + local yNext = math.floor(object.y + chartHeight - 1 - (valuesCopy[i + 1][2] - yMin) / dy * (chartHeight - 1)) * 2 + if object.fillChartArea then + fillVerticalPart(x, y, xNext, yNext) + else + buffer.semiPixelLine(x, y, xNext, yNext, object.colors.chart) + end + end + + return object +end + +function GUI.chart(x, y, width, height, axisColor, axisValueColor, axisHelpersColor, chartColor, xAxisValueInterval, yAxisValueInterval, xAxisPostfix, yAxisPostfix, fillChartArea, values) + local object = GUI.object(x, y, width, height) + + object.colors = {axis = axisColor, chart = chartColor, axisValue = axisValueColor, helpers = axisHelpersColor} + object.draw = drawChart + object.values = values or {} + object.xAxisPostfix = xAxisPostfix + object.yAxisPostfix = yAxisPostfix + object.xAxisValueInterval = xAxisValueInterval + object.yAxisValueInterval = yAxisValueInterval + object.fillChartArea = fillChartArea + object.showYAxisValues = true + object.showXAxisValues = true + + return object +end + +----------------------------------------- Dropdown Menu ----------------------------------------- + +local function dropDownMenuItemDraw(item) + local yText = item.y + math.floor(item.height / 2) + + if item.type == GUI.dropDownMenuElementTypes.default then + local textColor = item.color or item.parent.parent.colors.default.text + + if item.pressed then + textColor = item.parent.parent.colors.pressed.text + buffer.square(item.x, item.y, item.width, item.height, item.parent.parent.colors.pressed.background, textColor, " ") + elseif item.disabled then + textColor = item.parent.parent.colors.disabled.text + end + + buffer.text(item.x + 1, yText, textColor, item.text) + if item.shortcut then + buffer.text(item.x + item.width - unicode.len(item.shortcut) - 1, yText, textColor, item.shortcut) + end + else + buffer.text(item.x, yText, item.parent.parent.colors.separator, string.rep("─", item.width)) + end + + return item +end + +local function dropDownMenuItemEventHandler(mainContainer, object, eventData) + if eventData[1] == "touch" then + if object.type == GUI.dropDownMenuElementTypes.default then + object.pressed = true + mainContainer:draw() + buffer.draw() + + if object.subMenu then + object.subMenu.y = object.parent.y + object.localY - 1 + object.subMenu.x = object.parent.x + object.parent.width + if buffer.getWidth() - object.parent.x - object.parent.width + 1 < object.subMenu.width then + object.subMenu.x = object.parent.x - object.subMenu.width + end + + object.subMenu:show() + else + os.sleep(0.2) + end + + object.pressed = false + mainContainer:draw() + buffer.draw() + mainContainer.selectedItem = object:indexOf() + + callMethod(object.onTouch) + end + + mainContainer:stopEventHandling() + end +end + +local function dropDownMenuCalculateSizes(menu) + local y, totalHeight = menu.itemsContainer.children[1].localY, 0 + for i = 1, #menu.itemsContainer.children do + menu.itemsContainer.children[i].width = menu.width + menu.itemsContainer.children[i].localY = y + + y = y + menu.itemsContainer.children[i].height + totalHeight = totalHeight + (menu.itemsContainer.children[i].type == GUI.dropDownMenuElementTypes.separator and 1 or menu.itemHeight) + end + menu.height = math.min(totalHeight, menu.maximumHeight, buffer.getHeight() - menu.y) + menu.itemsContainer.width, menu.itemsContainer.height = menu.width, menu.height + + menu.nextButton.localY = menu.height + menu.prevButton.width, menu.nextButton.width = menu.width, menu.width + menu.prevButton.hidden = menu.itemsContainer.children[1].localY >= 1 + menu.nextButton.hidden = menu.itemsContainer.children[#menu.itemsContainer.children].localY + menu.itemsContainer.children[#menu.itemsContainer.children].height - 1 <= menu.height +end + +local function dropDownMenuRemoveItem(menu, index) + table.remove(menu.itemsContainer.children, index) + dropDownMenuCalculateSizes(menu) + return menu +end + +local function dropDownMenuAddItem(menu, text, disabled, shortcut, color) + local item = menu.itemsContainer:addChild(GUI.object(1, 1, 1, menu.itemHeight)) + + item.type = GUI.dropDownMenuElementTypes.default + item.text = text + item.disabled = disabled + item.shortcut = shortcut + item.color = color + item.draw = dropDownMenuItemDraw + item.eventHandler = dropDownMenuItemEventHandler + + dropDownMenuCalculateSizes(menu) + + return item +end + +local function dropDownMenuAddSeparator(menu) + local item = dropDownMenuAddItem(menu) + item.type = GUI.dropDownMenuElementTypes.separator + item.height = 1 + + return item +end + +local function dropDownMenuScrollDown(menu) + if menu.itemsContainer.children[1].localY < 1 then + for i = 1, #menu.itemsContainer.children do + menu.itemsContainer.children[i].localY = menu.itemsContainer.children[i].localY + 1 + end + end + menu:draw() + buffer.draw() +end + +local function dropDownMenuScrollUp(menu) + if menu.itemsContainer.children[#menu.itemsContainer.children].localY + menu.itemsContainer.children[#menu.itemsContainer.children].height - 1 > menu.height then + for i = 1, #menu.itemsContainer.children do + menu.itemsContainer.children[i].localY = menu.itemsContainer.children[i].localY - 1 + end + end + menu:draw() + buffer.draw() +end + +local function dropDownMenuEventHandler(mainContainer, object, eventData) + if eventData[1] == "scroll" then + if eventData[5] == 1 then + dropDownMenuScrollDown(object) + else + dropDownMenuScrollUp(object) + end + end +end + +local function dropDownMenuDraw(menu) + dropDownMenuCalculateSizes(menu) + + if menu.oldPixels then + buffer.paste(menu.x, menu.y, menu.oldPixels) + else + menu.oldPixels = buffer.copy(menu.x, menu.y, menu.width + 1, menu.height + 1) + end + + buffer.square(menu.x, menu.y, menu.width, menu.height, menu.colors.default.background, menu.colors.default.text, " ", menu.colors.transparency.background) + GUI.drawContainerContent(menu) + GUI.windowShadow(menu.x, menu.y, menu.width, menu.height, menu.colors.transparency.shadow, true) + + return menu +end + +local function dropDownMenuShow(menu) + local mainContainer = GUI.fullScreenContainer() + -- Удаляем олдпиксельсы, чтоб старое дерьмое не рисовалось во всяких комбобоксах + menu.oldPixels = nil + mainContainer:addChild(GUI.object(1, 1, mainContainer.width, mainContainer.height)).eventHandler = function(mainContainer, object, eventData) + if eventData[1] == "touch" then + buffer.paste(menu.x, menu.y, menu.oldPixels) + buffer.draw() + mainContainer:stopEventHandling() + end + end + mainContainer:addChild(menu) + + menu:draw() + buffer.draw() + mainContainer:startEventHandling() + buffer.paste(menu.x, menu.y, menu.oldPixels) + buffer.draw() + -- А вот тут удаляем чисто шоб память не грузить + menu.oldPixels = nil + + if mainContainer.selectedItem then + return menu.itemsContainer.children[mainContainer.selectedItem].text, mainContainer.selectedItem + end +end + +function GUI.dropDownMenu(x, y, width, maximumHeight, itemHeight, backgroundColor, textColor, backgroundPressedColor, textPressedColor, disabledColor, separatorColor, backgroundTransparency, shadowTransparency) + local menu = GUI.container(x, y, width, 1) + + menu.colors = { + default = { + background = backgroundColor, + text = textColor + }, + pressed = { + background = backgroundPressedColor, + text = textPressedColor + }, + disabled = { + text = disabledColor + }, + separator = separatorColor, + transparency = { + background = backgroundTransparency, + shadow = shadowTransparency + } + } + + menu.itemsContainer = menu:addChild(GUI.container(1, 1, menu.width, menu.height)) + menu.prevButton = menu:addChild(GUI.button(1, 1, menu.width, 1, backgroundColor, textColor, backgroundPressedColor, textPressedColor, "▲")) + menu.nextButton = menu:addChild(GUI.button(1, 1, menu.width, 1, backgroundColor, textColor, backgroundPressedColor, textPressedColor, "▼")) + menu.prevButton.colors.transparency, menu.nextButton.colors.transparency = backgroundTransparency, backgroundTransparency + menu.prevButton.onTouch = function() + dropDownMenuScrollDown(menu) + end + menu.nextButton.onTouch = function() + dropDownMenuScrollUp(menu) + end + + menu.itemHeight = itemHeight + menu.addSeparator = dropDownMenuAddSeparator + menu.addItem = dropDownMenuAddItem + menu.removeItem = dropDownMenuRemoveItem + menu.draw = dropDownMenuDraw + menu.show = dropDownMenuShow + menu.maximumHeight = maximumHeight + menu.eventHandler = dropDownMenuEventHandler + + return menu +end + +----------------------------------------- Context Menu ----------------------------------------- + +local function contextMenuCalculate(menu) + local widestItem, widestShortcut = 0, 0 + for i = 1, #menu.itemsContainer.children do + if menu.itemsContainer.children[i].type == GUI.dropDownMenuElementTypes.default then + widestItem = math.max(widestItem, unicode.len(menu.itemsContainer.children[i].text)) + if menu.itemsContainer.children[i].shortcut then + widestShortcut = math.max(widestShortcut, unicode.len(menu.itemsContainer.children[i].shortcut)) + end + end + end + menu.width = 2 + widestItem + (widestShortcut > 0 and 3 + widestShortcut or 0) + menu.height = #menu.itemsContainer.children +end + +local function contextMenuShow(menu) + contextMenuCalculate(menu) + + local bufferWidth, bufferHeight = buffer.getResolution() + if menu.y + menu.height >= bufferHeight then menu.y = bufferHeight - menu.height end + if menu.x + menu.width + 1 >= bufferWidth then menu.x = bufferWidth - menu.width - 1 end + + return dropDownMenuShow(menu) +end + +local function contextMenuAddItem(menu, ...) + contextMenuCalculate(menu) + return dropDownMenuAddItem(menu, ...) +end + +local function contextMenuAddSeparator(menu, ...) + contextMenuCalculate(menu) + return dropDownMenuAddSeparator(menu, ...) +end + +local function contextMenuAddSubMenu(menu, text) + local item = menu:addItem(text, false, "►") + item.subMenu = GUI.contextMenu(1, 1) + item.subMenu.colors = menu.colors + + return item.subMenu +end + +function GUI.contextMenu(x, y, backgroundColor, textColor, backgroundPressedColor, textPressedColor, disabledColor, separatorColor, backgroundTransparency, shadowTransparency) + local menu = GUI.dropDownMenu(x, y, 1, math.ceil(buffer.getHeight() * 0.5), 1, + backgroundColor or GUI.colors.contextMenu.default.background, + textColor or GUI.colors.contextMenu.default.text, + backgroundPressedColor or GUI.colors.contextMenu.pressed.background, + textPressedColor or GUI.colors.contextMenu.pressed.text, + disabledColor or GUI.colors.contextMenu.disabled, + separatorColor or GUI.colors.contextMenu.separator, + backgroundTransparency or GUI.colors.contextMenu.transparency.background, + shadowTransparency or GUI.colors.contextMenu.transparency.shadow + ) + + menu.colors.transparency.background = menu.colors.transparency.background or GUI.colors.contextMenu.transparency.background + menu.colors.transparency.shadow = menu.colors.transparency.shadow or GUI.colors.contextMenu.transparency.shadow + + menu.show = contextMenuShow + menu.addSubMenu = contextMenuAddSubMenu + menu.addItem = contextMenuAddItem + menu.addSeparator = contextMenuAddSeparator + + return menu +end + +----------------------------------------- Combo Box Object ----------------------------------------- + +local function drawComboBox(object) + buffer.square(object.x, object.y, object.width, object.height, object.colors.default.background, object.colors.default.text, " ") + if object.dropDownMenu.itemsContainer.children[object.selectedItem] then + buffer.text(object.x + 1, math.floor(object.y + object.height / 2), object.colors.default.text, string.limit(object.dropDownMenu.itemsContainer.children[object.selectedItem].text, object.width - object.height - 2, "right")) + end + GUI.button(object.x + object.width - object.height * 2 + 1, object.y, object.height * 2 - 1, object.height, object.colors.arrow.background, object.colors.arrow.text, 0x0, 0x0, object.pressed and "▲" or "▼"):draw() + + return object +end + +local function comboBoxGetItem(object, index) + return object.dropDownMenu.itemsContainer.children[index] +end + +local function comboBoxRemoveItem(object, index) + object.dropDownMenu:removeItem(index) + if object.selectedItem > #object.dropDownMenu.itemsContainer.children then + object.selectedItem = #object.dropDownMenu.itemsContainer.children + end +end + +local function comboBoxCount(object) + return #object.dropDownMenu.itemsContainer.children +end + +local function comboBoxClear(object) + object.dropDownMenu.itemsContainer:deleteChildren() + object.selectedItem = 1 + + return object +end + +local function comboBoxIndexOfItem(object, text) + for i = 1, #object.dropDownMenu.itemsContainer.children do + if object.dropDownMenu.itemsContainer.children[i].text == text then + return i + end + end +end + +local function selectComboBoxItem(object) + object.pressed = true + object:draw() + + object.dropDownMenu.x, object.dropDownMenu.y = object.x, object.y + object.height + object.dropDownMenu.width = object.width + local _, selectedItem = object.dropDownMenu:show() + + object.selectedItem = selectedItem or object.selectedItem + object.pressed = false + object:draw() + buffer.draw() + + return object +end + +local function comboBoxEventHandler(mainContainer, object, eventData) + if eventData[1] == "touch" and #object.dropDownMenu.itemsContainer.children > 0 then + object:selectItem() + callMethod(object.onItemSelected, object.dropDownMenu.itemsContainer.children[object.selectedItem], eventData) + end +end + +local function comboBoxAddItem(object, ...) + return object.dropDownMenu:addItem(...) +end + +local function comboBoxAddSeparator(object) + return object.dropDownMenu:addSeparator() +end + +function GUI.comboBox(x, y, width, elementHeight, backgroundColor, textColor, arrowBackgroundColor, arrowTextColor) + local object = GUI.object(x, y, width, elementHeight) + + object.eventHandler = comboBoxEventHandler + object.colors = { + default = { + background = backgroundColor, + text = textColor + }, + pressed = { + background = GUI.colors.contextMenu.pressed.background, + text = GUI.colors.contextMenu.pressed.text + }, + arrow = { + background = arrowBackgroundColor, + text = arrowTextColor + } + } + + object.dropDownMenu = GUI.dropDownMenu(1, 1, 1, math.ceil(buffer.getHeight() * 0.5), elementHeight, + object.colors.default.background, + object.colors.default.text, + object.colors.pressed.background, + object.colors.pressed.text, + GUI.colors.contextMenu.disabled, + GUI.colors.contextMenu.separator, + GUI.colors.contextMenu.transparency.background, + GUI.colors.contextMenu.transparency.shadow + ) + object.selectedItem = 1 + object.addItem = comboBoxAddItem + object.removeItem = comboBoxRemoveItem + object.addSeparator = comboBoxAddSeparator + object.draw = drawComboBox + object.selectItem = selectComboBoxItem + object.clear = comboBoxClear + object.indexOfItem = comboBoxIndexOfItem + object.getItem = comboBoxGetItem + object.count = comboBoxCount + + return object +end + +----------------------------------------- Switch and label object ----------------------------------------- + +local function switchAndLabelDraw(switchAndLabel) + switchAndLabel.label.width = switchAndLabel.width + switchAndLabel.switch.localX = switchAndLabel.width - switchAndLabel.switch.width + + switchAndLabel.label.x, switchAndLabel.label.y = switchAndLabel.x + switchAndLabel.label.localX - 1, switchAndLabel.y + switchAndLabel.label.localY - 1 + switchAndLabel.switch.x, switchAndLabel.switch.y = switchAndLabel.x + switchAndLabel.switch.localX - 1, switchAndLabel.y + switchAndLabel.switch.localY - 1 + + switchAndLabel.label:draw() + switchAndLabel.switch:draw() + + return switchAndLabel +end + +function GUI.switchAndLabel(x, y, width, switchWidth, activeColor, passiveColor, pipeColor, textColor, text, switchState) + local switchAndLabel = GUI.container(x, y, width, 1) + + switchAndLabel.label = switchAndLabel:addChild(GUI.label(1, 1, width, 1, textColor, text)) + switchAndLabel.switch = switchAndLabel:addChild(GUI.switch(1, 1, switchWidth, activeColor, passiveColor, pipeColor, switchState)) + switchAndLabel.draw = switchAndLabelDraw + + return switchAndLabel +end + +----------------------------------------- Horizontal Slider Object ----------------------------------------- + +local function sliderDraw(object) + -- На всякий случай делаем значение не меньше минимального и не больше максимального + object.value = math.min(math.max(object.value, object.minimumValue), object.maximumValue) + + if object.showMaximumAndMinimumValues then + local stringMaximumValue, stringMinimumValue = tostring(object.roundValues and math.floor(object.maximumValue) or math.roundToDecimalPlaces(object.maximumValue, 2)), tostring(object.roundValues and math.floor(object.minimumValue) or math.roundToDecimalPlaces(object.minimumValue, 2)) + buffer.text(object.x - unicode.len(stringMinimumValue) - 1, object.y, object.colors.value, stringMinimumValue) + buffer.text(object.x + object.width + 1, object.y, object.colors.value, stringMaximumValue) + end + + if object.currentValuePrefix or object.currentValuePostfix then + local stringCurrentValue = (object.currentValuePrefix or "") .. (object.roundValues and math.floor(object.value) or math.roundToDecimalPlaces(object.value, 2)) .. (object.currentValuePostfix or "") + buffer.text(math.floor(object.x + object.width / 2 - unicode.len(stringCurrentValue) / 2), object.y + 1, object.colors.value, stringCurrentValue) + end + + local activeWidth = math.floor(object.width - ((object.maximumValue - object.value) * object.width / (object.maximumValue - object.minimumValue))) + buffer.text(object.x, object.y, object.colors.passive, string.rep("━", object.width)) + buffer.text(object.x, object.y, object.colors.active, string.rep("━", activeWidth)) + buffer.text(object.x + activeWidth - 1, object.y, object.colors.pipe, "⬤") + + return object +end + +local function sliderEventHandler(mainContainer, object, eventData) + if eventData[1] == "touch" or eventData[1] == "drag" then + local clickPosition = eventData[3] - object.x + 1 + object.value = object.minimumValue + (clickPosition * (object.maximumValue - object.minimumValue) / object.width) + mainContainer:draw() + buffer.draw() + callMethod(object.onValueChanged, object.value, eventData) + end +end + +function GUI.slider(x, y, width, activeColor, passiveColor, pipeColor, valueColor, minimumValue, maximumValue, value, showMaximumAndMinimumValues, currentValuePrefix, currentValuePostfix) + local object = GUI.object(x, y, width, 1) + + object.eventHandler = sliderEventHandler + object.colors = {active = activeColor, passive = passiveColor, pipe = pipeColor, value = valueColor} + object.draw = sliderDraw + object.minimumValue = minimumValue + object.maximumValue = maximumValue + object.value = value + object.showMaximumAndMinimumValues = showMaximumAndMinimumValues + object.currentValuePrefix = currentValuePrefix + object.currentValuePostfix = currentValuePostfix + object.roundValues = false + + return object +end + +----------------------------------------- Switch object ----------------------------------------- + +local function switchDraw(switch) + buffer.text(switch.x - 1, switch.y, switch.colors.passive, "⠰") + buffer.square(switch.x, switch.y, switch.width, 1, switch.colors.passive, 0x000000, " ") + buffer.text(switch.x + switch.width, switch.y, switch.colors.passive, "⠆") + + buffer.text(switch.x - 1, switch.y, switch.colors.active, "⠰") + buffer.square(switch.x, switch.y, switch.pipePosition - 1, 1, switch.colors.active, 0x000000, " ") + + buffer.text(switch.x + switch.pipePosition - 2, switch.y, switch.colors.pipe, "⠰") + buffer.square(switch.x + switch.pipePosition - 1, switch.y, 2, 1, switch.colors.pipe, 0x000000, " ") + buffer.text(switch.x + switch.pipePosition + 1, switch.y, switch.colors.pipe, "⠆") + + return switch +end + +local function switchSetState(switch, state) + switch.state = state + switch.pipePosition = switch.state and switch.width - 1 or 1 + + return switch +end + +local function switchEventHandler(mainContainer, switch, eventData) + if eventData[1] == "touch" then + switch.state = not switch.state + switch:addAnimation( + function(mainContainer, animation) + if switch.state then + switch.pipePosition = math.round(1 + animation.position * (switch.width - 2)) + else + switch.pipePosition = math.round(1 + (1 - animation.position) * (switch.width - 2)) + end + end, + function(mainContainer, animation) + animation:delete() + callMethod(switch.onStateChanged, mainContainer, switch, eventData, switch.state) + end + ):start(switch.animationDuration) + end +end + +function GUI.switch(x, y, width, activeColor, passiveColor, pipeColor, state) + local switch = GUI.object(x, y, width, 1) + + switch.pipePosition = 1 + switch.eventHandler = switchEventHandler + switch.colors = { + active = activeColor, + passive = passiveColor, + pipe = pipeColor, + } + switch.draw = switchDraw + switch.state = state or false + switch.update = switchUpdate + switch.animated = true + switch.animationDuration = 0.3 + switch.setState = switchSetState + + switch:setState(state) + + return switch +end + +----------------------------------------- Layout object ----------------------------------------- + +local function layoutCheckCell(layout, column, row) + if column < 1 or column > #layout.columnSizes or row < 1 or row > #layout.rowSizes then + error("Specified grid position (" .. tostring(column) .. "x" .. tostring(row) .. ") is out of layout grid range") + end +end + +local function layoutGetAbsoluteTotalSize(array) + local absoluteTotalSize = 0 + for i = 1, #array do + if array[i].sizePolicy == GUI.sizePolicies.absolute then + absoluteTotalSize = absoluteTotalSize + array[i].size + end + end + return absoluteTotalSize +end + +local function layoutGetCalculatedSize(array, index, dependency) + if array[index].sizePolicy == GUI.sizePolicies.percentage then + array[index].calculatedSize = array[index].size * dependency + else + array[index].calculatedSize = array[index].size + end +end + +local function layoutUpdate(layout) + local columnPercentageTotalSize, rowPercentageTotalSize = layout.width - layoutGetAbsoluteTotalSize(layout.columnSizes), layout.height - layoutGetAbsoluteTotalSize(layout.rowSizes) + for row = 1, #layout.rowSizes do + layoutGetCalculatedSize(layout.rowSizes, row, rowPercentageTotalSize) + for column = 1, #layout.columnSizes do + layoutGetCalculatedSize(layout.columnSizes, column, columnPercentageTotalSize) + layout.cells[row][column].width, layout.cells[row][column].height = 0, 0 + end + end + + -- Подготавливаем объекты к расположению и подсчитываем тотальные размеры + local child, layoutRow, layoutColumn, cell + for i = 1, #layout.children do + child = layout.children[i] + + if not child.hidden then + layoutRow, layoutColumn = child.layoutRow, child.layoutColumn + + -- Проверка на позицию в сетке + if layoutRow >= 1 and layoutRow <= #layout.rowSizes and layoutColumn >= 1 and layoutColumn <= #layout.columnSizes then + cell = layout.cells[layoutRow][layoutColumn] + -- Авто-фиттинг объектов + if cell.fitting.horizontal then + child.width = math.round(layout.columnSizes[layoutColumn].calculatedSize - cell.fitting.horizontalRemove) + end + if cell.fitting.vertical then + child.height = math.round(layout.rowSizes[layoutRow].calculatedSize - cell.fitting.verticalRemove) + end + + -- Направление и расчет размеров + if cell.direction == GUI.directions.horizontal then + cell.width = cell.width + child.width + cell.spacing + cell.height = math.max(cell.height, child.height) + else + cell.width = math.max(cell.width, child.width) + cell.height = cell.height + child.height + cell.spacing + end + else + error("Layout child with index " .. i .. " has been assigned to cell (" .. layoutColumn .. "x" .. layoutRow .. ") out of layout grid range") + end + end + end + + -- Высчитываем стартовую позицию объектов ячейки + local x, y = 1, 1 + for row = 1, #layout.rowSizes do + for column = 1, #layout.columnSizes do + cell = layout.cells[row][column] + cell.x, cell.y = GUI.getAlignmentCoordinates( + { + x = x, + y = y, + width = layout.columnSizes[column].calculatedSize, + height = layout.rowSizes[row].calculatedSize, + alignment = cell.alignment, + }, + { + width = cell.width - (cell.direction == GUI.directions.horizontal and cell.spacing or 0), + height = cell.height - (cell.direction == GUI.directions.vertical and cell.spacing or 0), + } + ) + + -- Учитываем отступы от краев ячейки + if cell.margin then + cell.x, cell.y = GUI.getMarginCoordinates(cell) + end + + x = x + layout.columnSizes[column].calculatedSize + end + + x, y = 1, y + layout.rowSizes[row].calculatedSize + end + + -- Размещаем все объекты + for i = 1, #layout.children do + child = layout.children[i] + + if not child.hidden then + cell = layout.cells[child.layoutRow][child.layoutColumn] + + child.localX, cell.localY = GUI.getAlignmentCoordinates(cell, child) + + if cell.direction == GUI.directions.horizontal then + child.localX, child.localY = math.floor(cell.x), math.floor(cell.localY) + cell.x = cell.x + child.width + cell.spacing + else + child.localX, child.localY = math.floor(child.localX), math.floor(cell.y) + cell.y = cell.y + child.height + cell.spacing + end + end + end +end + +local function layoutSetCellPosition(layout, column, row, object) + layoutCheckCell(layout, column, row) + object.layoutRow = row + object.layoutColumn = column + + return object +end + +local function layoutSetCellDirection(layout, column, row, direction) + layoutCheckCell(layout, column, row) + layout.cells[row][column].direction = direction + + return layout +end + +local function layoutSetCellSpacing(layout, column, row, spacing) + layoutCheckCell(layout, column, row) + layout.cells[row][column].spacing = spacing + + return layout +end + +local function layoutSetCellAlignment(layout, column, row, horizontalAlignment, verticalAlignment) + layoutCheckCell(layout, column, row) + layout.cells[row][column].alignment.horizontal, layout.cells[row][column].alignment.vertical = horizontalAlignment, verticalAlignment + + return layout +end + +local function layoutSetCellMargin(layout, column, row, horizontalMargin, verticalMargin) + layoutCheckCell(layout, column, row) + layout.cells[row][column].margin = { + horizontal = horizontalMargin, + vertical = verticalMargin + } + + return layout +end + +local function layoutNewCell() + return { + alignment = { + horizontal = GUI.alignment.horizontal.center, + vertical = GUI.alignment.vertical.center + }, + direction = GUI.directions.vertical, + fitting = { + horizontal = false, vertical = false}, + spacing = 1, + } +end + +local function layoutCalculatePercentageSize(changingExistent, array, index) + if array[index].sizePolicy == GUI.sizePolicies.percentage then + local allPercents, beforeFromIndexPercents = 0, 0 + for i = 1, #array do + if array[i].sizePolicy == GUI.sizePolicies.percentage then + allPercents = allPercents + array[i].size + + if i <= index then + beforeFromIndexPercents = beforeFromIndexPercents + array[i].size + end + end + end + + local modifyer + if changingExistent then + if beforeFromIndexPercents > 1 then + error("Layout summary percentage > 100% at index " .. index) + end + modifyer = (1 - beforeFromIndexPercents) / (allPercents - beforeFromIndexPercents) + else + modifyer = (1 - array[index].size) / (allPercents - array[index].size) + end + + for i = changingExistent and index + 1 or 1, #array do + if array[i].sizePolicy == GUI.sizePolicies.percentage and i ~= index then + array[i].size = modifyer * array[i].size + end + end + end +end + +local function layoutSetColumnWidth(layout, column, sizePolicy, size) + layout.columnSizes[column].sizePolicy, layout.columnSizes[column].size = sizePolicy, size + layoutCalculatePercentageSize(true, layout.columnSizes, column) + + return layout +end + +local function layoutSetRowHeight(layout, row, sizePolicy, size) + layout.rowSizes[row].sizePolicy, layout.rowSizes[row].size = sizePolicy, size + layoutCalculatePercentageSize(true, layout.rowSizes, row) + + return layout +end + +local function layoutAddColumn(layout, sizePolicy, size) + for i = 1, #layout.rowSizes do + table.insert(layout.cells[i], layoutNewCell()) + end + + table.insert(layout.columnSizes, { + sizePolicy = sizePolicy, + size = size + }) + layoutCalculatePercentageSize(false, layout.columnSizes, #layout.columnSizes) + -- GUI.error(layout.columnSizes) + + return layout +end + +local function layoutAddRow(layout, sizePolicy, size) + local row = {} + for i = 1, #layout.columnSizes do + table.insert(row, layoutNewCell()) + end + + table.insert(layout.cells, row) + table.insert(layout.rowSizes, { + sizePolicy = sizePolicy, + size = size + }) + + layoutCalculatePercentageSize(false, layout.rowSizes, #layout.rowSizes) + -- GUI.error(layout.rowSizes) + + return layout +end + +local function layoutRemoveRow(layout, row) + table.remove(layout.cells, row) + + layout.rowSizes[row].size = 0 + layoutCalculatePercentageSize(false, layout.rowSizes, row) + + table.remove(layout.rowSizes, row) + + return layout +end + +local function layoutRemoveColumn(layout, column) + for i = 1, #layout.rowSizes do + table.remove(layout.cells[i], column) + end + + layout.columnSizes[column].size = 0 + layoutCalculatePercentageSize(false, layout.columnSizes, column) + + table.remove(layout.columnSizes, column) + + return layout +end + +local function layoutSetGridSize(layout, columnCount, rowCount) + layout.cells = {} + layout.rowSizes = {} + layout.columnSizes = {} + + local rowSize, columnSize = 1 / rowCount, 1 / columnCount + for i = 1, rowCount do + layoutAddRow(layout, GUI.sizePolicies.percentage, 1 / i) + end + + for i = 1, columnCount do + layoutAddColumn(layout, GUI.sizePolicies.percentage, 1 / i) + end + + return layout +end + +local function layoutDraw(layout) + layoutUpdate(layout) + GUI.drawContainerContent(layout) + + if layout.showGrid then + local x, y = layout.x, layout.y + for j = 1, #layout.columnSizes do + for i = 1, #layout.rowSizes do + buffer.frame( + math.round(x), + math.round(y), + math.round(layout.columnSizes[j].calculatedSize), + math.round(layout.rowSizes[i].calculatedSize), + 0xFF0000 + ) + y = y + layout.rowSizes[i].calculatedSize + end + x, y = x + layout.columnSizes[j].calculatedSize, layout.y + end + end +end + +local function layoutFitToChildrenSize(layout, column, row) + layout.width, layout.height = 0, 0 + + for i = 1, #layout.children do + if not layout.children[i].hidden then + if layout.cells[row][column].direction == GUI.directions.horizontal then + layout.width = layout.width + layout.children[i].width + layout.cells[row][column].spacing + layout.height = math.max(layout.height, layout.children[i].height) + else + layout.width = math.max(layout.width, layout.children[i].width) + layout.height = layout.height + layout.children[i].height + layout.cells[row][column].spacing + end + end + end + + if layout.cells[row][column].direction == GUI.directions.horizontal then + layout.width = layout.width - layout.cells[row][column].spacing + else + layout.height = layout.height - layout.cells[row][column].spacing + end + + return layout +end + +local function layoutSetCellFitting(layout, column, row, horizontal, vertical, horizontalRemove, verticalRemove ) + layoutCheckCell(layout, column, row) + layout.cells[row][column].fitting = { + horizontal = horizontal, + vertical = vertical, + horizontalRemove = horizontalRemove or 0, + verticalRemove = verticalRemove or 0, + } + + return layout +end + +local function layoutAddChild(layout, object, ...) + object.layoutRow = layout.defaultRow + object.layoutColumn = layout.defaultColumn + GUI.addChildToContainer(layout, object, ...) + + return object +end + +function GUI.layout(x, y, width, height, columnCount, rowCount) + local layout = GUI.container(x, y, width, height) + + layout.defaultRow = 1 + layout.defaultColumn = 1 + + layout.addRow = layoutAddRow + layout.addColumn = layoutAddColumn + layout.removeRow = layoutRemoveRow + layout.removeColumn = layoutRemoveColumn + + layout.setRowHeight = layoutSetRowHeight + layout.setColumnWidth = layoutSetColumnWidth + + layout.setCellPosition = layoutSetCellPosition + layout.setCellDirection = layoutSetCellDirection + layout.setGridSize = layoutSetGridSize + layout.setCellSpacing = layoutSetCellSpacing + layout.setCellAlignment = layoutSetCellAlignment + layout.setCellMargin = layoutSetCellMargin + + layout.fitToChildrenSize = layoutFitToChildrenSize + layout.setCellFitting = layoutSetCellFitting + + layout.update = layoutUpdate + layout.addChild = layoutAddChild + layout.draw = layoutDraw + + layoutSetGridSize(layout, columnCount, rowCount) + + return layout +end + +----------------------------------------------------------------------------------------------------- + +local function filesystemDialogDraw(filesystemDialog) + if filesystemDialog.extensionComboBox.hidden then + filesystemDialog.input.width = filesystemDialog.cancelButton.localX - 4 + else + filesystemDialog.input.width = filesystemDialog.extensionComboBox.localX - 3 + end + + if filesystemDialog.IOMode == GUI.filesystemModes.save then + filesystemDialog.submitButton.disabled = not filesystemDialog.input.text + else + filesystemDialog.input.text = filesystemDialog.filesystemTree.selectedItem or "" + filesystemDialog.submitButton.disabled = not filesystemDialog.filesystemTree.selectedItem + end + + GUI.drawContainerContent(filesystemDialog) + GUI.windowShadow(filesystemDialog.x, filesystemDialog.y, filesystemDialog.width, filesystemDialog.height, GUI.colors.contextMenu.transparency.shadow, true) + + return filesystemDialog +end + +local function filesystemDialogSetMode(filesystemDialog, IOMode, filesystemMode) + filesystemDialog.IOMode = IOMode + filesystemDialog.filesystemMode = filesystemMode + + if filesystemDialog.IOMode == GUI.filesystemModes.save then + filesystemDialog.filesystemTree.showMode = GUI.filesystemModes.directory + filesystemDialog.filesystemTree.selectionMode = GUI.filesystemModes.directory + filesystemDialog.input.disabled = false + filesystemDialog.extensionComboBox.hidden = filesystemDialog.filesystemMode ~= GUI.filesystemModes.file or not filesystemDialog.filesystemTree.extensionFilters + else + if filesystemDialog.filesystemMode == GUI.filesystemModes.file then + filesystemDialog.filesystemTree.showMode = GUI.filesystemModes.both + filesystemDialog.filesystemTree.selectionMode = GUI.filesystemModes.file + else + filesystemDialog.filesystemTree.showMode = GUI.filesystemModes.directory + filesystemDialog.filesystemTree.selectionMode = GUI.filesystemModes.directory + end + + filesystemDialog.input.disabled = true + filesystemDialog.extensionComboBox.hidden = true + end + + filesystemDialog.filesystemTree:updateFileList() +end + +local function filesystemDialogAddExtensionFilter(filesystemDialog, extension) + filesystemDialog.extensionComboBox:addItem(extension) + filesystemDialog.extensionComboBox.width = math.max(filesystemDialog.extensionComboBox.width, unicode.len(extension) + 3) + filesystemDialog.extensionComboBox.localX = filesystemDialog.cancelButton.localX - filesystemDialog.extensionComboBox.width - 2 + filesystemDialog.filesystemTree:addExtensionFilter(extension) + + filesystemDialog:setMode(filesystemDialog.IOMode, filesystemDialog.filesystemMode) +end + +local function filesystemDialogExpandPath(filesystemDialog, ...) + filesystemDialog.filesystemTree:expandPath(...) +end + +function GUI.filesystemDialog(x, y, width, height, submitButtonText, cancelButtonText, placeholderText, path) + local filesystemDialog = GUI.container(x, y, width, height) + + filesystemDialog:addChild(GUI.panel(1, height - 2, width, 3, 0xD2D2D2)) + + filesystemDialog.cancelButton = filesystemDialog:addChild(GUI.adaptiveRoundedButton(1, height - 1, 1, 0, 0xE1E1E1, 0x3C3C3C, 0x3C3C3C, 0xE1E1E1, cancelButtonText)) + filesystemDialog.submitButton = filesystemDialog:addChild(GUI.adaptiveRoundedButton(1, height - 1, 1, 0, 0x3C3C3C, 0xE1E1E1, 0xE1E1E1, 0x3C3C3C, submitButtonText)) + filesystemDialog.submitButton.localX = filesystemDialog.width - filesystemDialog.submitButton.width - 1 + filesystemDialog.cancelButton.localX = filesystemDialog.submitButton.localX - filesystemDialog.cancelButton.width - 2 + + filesystemDialog.extensionComboBox = filesystemDialog:addChild(GUI.comboBox(1, height - 1, 1, 1, 0xE1E1E1, 0x666666, 0xC3C3C3, 0x888888)) + filesystemDialog.extensionComboBox.hidden = true + + filesystemDialog.input = filesystemDialog:addChild(GUI.input(2, height - 1, 1, 1, 0xE1E1E1, 0x666666, 0x999999, 0xE1E1E1, 0x3C3C3C, "", placeholderText)) + + filesystemDialog.filesystemTree = filesystemDialog:addChild(GUI.filesystemTree(1, 1, width, height - 3, 0xE1E1E1, 0x3C3C3C, 0x3C3C3C, 0xAAAAAA, 0x3C3C3C, 0xE1E1E1, 0xBBBBBB, 0xAAAAAA, 0xC3C3C3, 0x444444)) + filesystemDialog.filesystemTree.workPath = path + + filesystemDialog.draw = filesystemDialogDraw + filesystemDialog.setMode = filesystemDialogSetMode + filesystemDialog.addExtensionFilter = filesystemDialogAddExtensionFilter + + filesystemDialog.expandPath = filesystemDialogExpandPath + filesystemDialog:setMode(GUI.filesystemModes.open, GUI.filesystemModes.file) + + return filesystemDialog +end + +local function filesystemDialogShow(filesystemDialog) + filesystemDialog:addAnimation( + function(mainContainer, animation) + filesystemDialog.localY = math.floor(1 + (1.0 - animation.position) * (-filesystemDialog.height)) + end, + function(mainContainer, animation) + animation:delete() + end + ):start(0.5) + + return filesystemDialog +end + +----------------------------------------------------------------------------------------------------- + +function GUI.addFilesystemDialogToContainer(parentContainer, width, height, addPanel, ...) + local container = parentContainer:addChild(GUI.container(1, 1, parentContainer.width, parentContainer.height)) + + if addPanel then + container:addChild(GUI.panel(1, 1, container.width, container.height, 0x0, 0.3)) + end + + local filesystemDialog = container:addChild(GUI.filesystemDialog(1, 1, width, height, ...)) + filesystemDialog.localX = math.floor(container.width / 2 - filesystemDialog.width / 2) + filesystemDialog.localY = -filesystemDialog.height + + local function onAnyTouch() + local firstParent = filesystemDialog:getFirstParent() + container:delete() + firstParent:draw() + buffer.draw() + end + + filesystemDialog.cancelButton.onTouch = function() + onAnyTouch() + callMethod(filesystemDialog.onCancel) + end + + filesystemDialog.submitButton.onTouch = function() + onAnyTouch() + + local path = filesystemDialog.filesystemTree.selectedItem or filesystemDialog.filesystemTree.workPath or "/" + if filesystemDialog.IOMode == GUI.filesystemModes.save then + path = path .. filesystemDialog.input.text + + if filesystemDialog.filesystemMode == GUI.filesystemModes.file then + local selectedItem = filesystemDialog.extensionComboBox:getItem(filesystemDialog.extensionComboBox.selectedItem) + path = path .. (selectedItem and selectedItem.text or "") + else + path = path .. "/" + end + end + + callMethod(filesystemDialog.onSubmit, path) + end + + filesystemDialog.show = filesystemDialogShow + + return filesystemDialog +end + +----------------------------------------------------------------------------------------------------- + +local function filesystemChooserDraw(object) + local tipWidth = object.height * 2 - 1 + local y = math.floor(object.y + object.height / 2) + + buffer.square(object.x, object.y, object.width - tipWidth, object.height, object.colors.background, object.colors.text, " ") + buffer.square(object.x + object.width - tipWidth, object.y, tipWidth, object.height, object.pressed and object.colors.tipText or object.colors.tipBackground, object.pressed and object.colors.tipBackground or object.colors.tipText, " ") + buffer.text(object.x + object.width - math.floor(tipWidth / 2) - 1, y, object.pressed and object.colors.tipBackground or object.colors.tipText, "…") + buffer.text(object.x + 1, y, object.colors.text, string.limit(object.path or object.placeholderText, object.width - tipWidth - 2, "left")) + + return filesystemChooser +end + +local function filesystemChooserAddExtensionFilter(object, extension) + object.extensionFilters[unicode.lower(extension)] = true +end + +local function filesystemChooserSetMode(object, filesystemMode) + object.filesystemMode = filesystemMode +end + +local function filesystemChooserEventHandler(mainContainer, object, eventData) + if eventData[1] == "touch" then + object.pressed = true + mainContainer:draw() + buffer.draw() + + local filesystemDialog = GUI.addFilesystemDialogToContainer(mainContainer, 50, math.floor(mainContainer.height * 0.8), false, object.submitButtonText, object.cancelButtonText, object.placeholderText, object.filesystemDialogPath) + + for key in pairs(object.extensionFilters) do + filesystemDialog:addExtensionFilter(key) + end + filesystemDialog:setMode(GUI.filesystemModes.open, object.filesystemMode) + + filesystemDialog.onCancel = function() + object.pressed = false + + mainContainer:draw() + buffer.draw() + end + + filesystemDialog.onSubmit = function(path) + object.path = path + object.pressed = false + + mainContainer:draw() + buffer.draw() + callMethod(object.onSubmit, object.path) + end + + filesystemDialog:show() + end +end + +function GUI.filesystemChooser(x, y, width, height, backgroundColor, textColor, tipBackgroundColor, tipTextColor, path, submitButtonText, cancelButtonText, placeholderText, filesystemDialogPath) + local object = GUI.object(x, y, width, height) + + object.eventHandler = comboBoxEventHandler + object.colors = { + tipBackground = tipBackgroundColor, + tipText = tipTextColor, + text = textColor, + background = backgroundColor + } + + object.submitButtonText = submitButtonText + object.cancelButtonText = cancelButtonText + object.placeholderText = placeholderText + object.pressed = false + object.path = path + object.filesystemDialogPath = filesystemDialogPath + object.filesystemMode = GUI.filesystemModes.file + object.extensionFilters = {} + + object.draw = filesystemChooserDraw + object.eventHandler = filesystemChooserEventHandler + object.addExtensionFilter = filesystemChooserAddExtensionFilter + object.setMode = filesystemChooserSetMode + + return object +end + +----------------------------------------------------------------------------------------------------- + +local function resizerDraw(object) + local horizontalMode = object.width >= object.height + local x, y, symbol + if horizontalMode then + buffer.text(object.x, math.floor(object.y + object.height / 2), object.colors.helper, string.rep("━", object.width)) + else + local x = math.floor(object.x + object.width / 2) + local bufferWidth, bufferHeight, index = buffer.getResolution() + + for i = object.y, object.y + object.height - 1 do + if x >= 1 and x <= bufferWidth and i >= 1 and i <= bufferHeight then + index = buffer.getIndex(x, i) + buffer.rawSet(index, buffer.rawGet(index), object.colors.helper, "┃") + end + end + end + + if object.touchPosition then + buffer.text(object.touchPosition.x - 1, object.touchPosition.y, object.colors.arrow, "←→") + end +end + +local function resizerEventHandler(mainContainer, object, eventData) + if eventData[1] == "touch" then + object.touchPosition = {x = eventData[3], y = eventData[4]} + + mainContainer:draw() + buffer.draw() + elseif eventData[1] == "drag" and object.touchPosition then + local x, y = object.touchPosition.x, object.touchPosition.y + object.touchPosition.x, object.touchPosition.y = eventData[3], eventData[4] + + if object.onResize then + object.onResize(mainContainer, object, eventData, eventData[3] - x, eventData[4] - y) + end + + mainContainer:draw() + buffer.draw() + elseif eventData[1] == "drop" then + object.touchPosition = nil + + if object.onResizeFinished then + object.onResizeFinished(mainContainer, object, eventData) + end + + mainContainer:draw() + buffer.draw() + end +end + +function GUI.resizer(x, y, width, height, helperColor, arrowColor) + local object = GUI.object(x, y, width, height) + + object.colors = { + helper = helperColor, + arrow = arrowColor + } + + object.draw = resizerDraw + object.eventHandler = resizerEventHandler + + return object +end + +----------------------------------------- Scrollbar object ----------------------------------------- + +local function scrollBarDraw(scrollBar) + local isVertical = scrollBar.height > scrollBar.width + local valuesDelta = scrollBar.maximumValue - scrollBar.minimumValue + 1 + local part = scrollBar.value / valuesDelta + + if isVertical then + local barSize = math.ceil(scrollBar.shownValueCount / valuesDelta * scrollBar.height) + local halfBarSize = math.floor(barSize / 2) + + scrollBar.ghostPosition.y = scrollBar.y + halfBarSize + scrollBar.ghostPosition.height = scrollBar.height - barSize + + if scrollBar.thin then + local y1 = math.floor(scrollBar.ghostPosition.y + part * scrollBar.ghostPosition.height - halfBarSize) + local y2 = y1 + barSize - 1 + local background + + for y = scrollBar.y, scrollBar.y + scrollBar.height - 1 do + background = buffer.get(scrollBar.x, y) + buffer.set(scrollBar.x, y, background, y >= y1 and y <= y2 and scrollBar.colors.foreground or scrollBar.colors.background, "┃") + end + else + buffer.square(scrollBar.x, scrollBar.y, scrollBar.width, scrollBar.height, scrollBar.colors.background, scrollBar.colors.foreground, " ") + buffer.square( + scrollBar.x, + math.floor(scrollBar.ghostPosition.y + part * scrollBar.ghostPosition.height - halfBarSize), + scrollBar.width, + barSize, + scrollBar.colors.foreground, 0x0, " " + ) + end + else + local barSize = math.ceil(scrollBar.shownValueCount / valuesDelta * scrollBar.width) + local halfBarSize = math.floor(barSize / 2) + + scrollBar.ghostPosition.x = scrollBar.x + halfBarSize + scrollBar.ghostPosition.width = scrollBar.width - barSize + + if scrollBar.thin then + local x1 = math.floor(scrollBar.ghostPosition.x + part * scrollBar.ghostPosition.width - halfBarSize) + local x2 = x1 + barSize - 1 + local background + + for x = scrollBar.x, scrollBar.x + scrollBar.width - 1 do + background = buffer.get(x, scrollBar.y) + buffer.set(x, scrollBar.y, background, x >= x1 and x <= x2 and scrollBar.colors.foreground or scrollBar.colors.background, "⠤") + end + else + buffer.square(scrollBar.x, scrollBar.y, scrollBar.width, scrollBar.height, scrollBar.colors.background, scrollBar.colors.foreground, " ") + buffer.square( + math.floor(scrollBar.ghostPosition.x + part * scrollBar.ghostPosition.width - halfBarSize), + scrollBar.y, + barSize, + scrollBar.height, + scrollBar.colors.foreground, 0x0, " " + ) + end + end + + return scrollBar +end + +local function scrollBarEventHandler(mainContainer, object, eventData) + local newValue = object.value + + if eventData[1] == "touch" or eventData[1] == "drag" then + local delta = object.maximumValue - object.minimumValue + 1 + if object.height > object.width then + newValue = math.floor((eventData[4] - object.y + 1) / object.height * delta) + else + newValue = math.floor((eventData[3] - object.x + 1) / object.width * delta) + end + elseif eventData[1] == "scroll" then + if eventData[5] == 1 then + if object.value >= object.minimumValue + object.onScrollValueIncrement then + newValue = object.value - object.onScrollValueIncrement + else + newValue = object.minimumValue + end + else + if object.value <= object.maximumValue - object.onScrollValueIncrement then + newValue = object.value + object.onScrollValueIncrement + else + newValue = object.maximumValue + end + end + end + + if eventData[1] == "touch" or eventData[1] == "drag" or eventData[1] == "scroll" then + object.value = newValue + callMethod(object.onTouch, eventData) + mainContainer:draw() + buffer.draw() + end +end + +function GUI.scrollBar(x, y, width, height, backgroundColor, foregroundColor, minimumValue, maximumValue, value, shownValueCount, onScrollValueIncrement, thin) + local scrollBar = GUI.object(x, y, width, height) + + scrollBar.eventHandler = scrollBarEventHandler + scrollBar.maximumValue = maximumValue + scrollBar.minimumValue = minimumValue + scrollBar.value = value + scrollBar.onScrollValueIncrement = onScrollValueIncrement + scrollBar.shownValueCount = shownValueCount + scrollBar.thin = thin + scrollBar.colors = { + background = backgroundColor, + foreground = foregroundColor, + } + scrollBar.ghostPosition = {} + scrollBar.draw = scrollBarDraw + + return scrollBar +end + +----------------------------------------- Tree object ----------------------------------------- + +local function treeDraw(tree) + local y, yEnd, showScrollBar = tree.y, tree.y + tree.height - 1, #tree.items > tree.height + local textLimit = tree.width - (showScrollBar and 1 or 0) + + if tree.colors.default.background then + buffer.square(tree.x, tree.y, tree.width, tree.height, tree.colors.default.background, tree.colors.default.expandable, " ") + end + + for i = tree.fromItem, #tree.items do + local textColor, arrowColor, text = tree.colors.default.notExpandable, tree.colors.default.arrow, tree.items[i].expandable and "■ " or "□ " + + if tree.selectedItem == tree.items[i].definition then + textColor, arrowColor = tree.colors.selected.any, tree.colors.selected.arrow + buffer.square(tree.x, y, tree.width, 1, tree.colors.selected.background, textColor, " ") + else + if tree.items[i].expandable then + textColor = tree.colors.default.expandable + elseif tree.items[i].disabled then + textColor = tree.colors.disabled + end + end + + if tree.items[i].expandable then + buffer.text(tree.x + tree.items[i].offset, y, arrowColor, tree.expandedItems[tree.items[i].definition] and "▽" or "▷") + end + + buffer.text(tree.x + tree.items[i].offset + 2, y, textColor, unicode.sub(text .. tree.items[i].name, 1, textLimit - tree.items[i].offset - 2)) + + y = y + 1 + if y > yEnd then break end + end + + if showScrollBar then + local scrollBar = tree.scrollBar + scrollBar.x = tree.x + tree.width - 1 + scrollBar.y = tree.y + scrollBar.width = 1 + scrollBar.height = tree.height + scrollBar.colors.background = tree.colors.scrollBar.background + scrollBar.colors.foreground = tree.colors.scrollBar.foreground + scrollBar.minimumValue = 1 + scrollBar.maximumValue = #tree.items + scrollBar.value = tree.fromItem + scrollBar.shownValueCount = tree.height + scrollBar.onScrollValueIncrement = 1 + scrollBar.thin = true + + scrollBar:draw() + end + + return tree +end + +local function treeEventHandler(mainContainer, tree, eventData) + if eventData[1] == "touch" then + local i = eventData[4] - tree.y + tree.fromItem + if tree.items[i] then + if + tree.items[i].expandable and + ( + tree.selectionMode == GUI.filesystemModes.file or + eventData[3] >= tree.x + tree.items[i].offset - 1 and eventData[3] <= tree.x + tree.items[i].offset + 1 + ) + then + if tree.expandedItems[tree.items[i].definition] then + tree.expandedItems[tree.items[i].definition] = nil + else + tree.expandedItems[tree.items[i].definition] = true + end + + callMethod(tree.onItemExpanded, tree.selectedItem, eventData) + else + if + ( + ( + tree.selectionMode == GUI.filesystemModes.both or + tree.selectionMode == GUI.filesystemModes.directory and tree.items[i].expandable or + tree.selectionMode == GUI.filesystemModes.file + ) and not tree.items[i].disabled + ) + then + tree.selectedItem = tree.items[i].definition + callMethod(tree.onItemSelected, tree.selectedItem, eventData) + end + end + + mainContainer:draw() + buffer.draw() + end + elseif eventData[1] == "scroll" then + if eventData[5] == 1 then + if tree.fromItem > 1 then + tree.fromItem = tree.fromItem - 1 + mainContainer:draw() + buffer.draw() + end + else + if tree.fromItem < #tree.items then + tree.fromItem = tree.fromItem + 1 + mainContainer:draw() + buffer.draw() + end + end + end +end + +local function treeAddItem(tree, name, definition, offset, expandable, disabled) + table.insert(tree.items, {name = name, expandable = expandable, offset = offset or 0, definition = definition, disabled = disabled}) + return tree +end + +function GUI.tree(x, y, width, height, backgroundColor, expandableColor, notExpandableColor, arrowColor, backgroundSelectedColor, anySelectionColor, arrowSelectionColor, disabledColor, scrollBarBackground, scrollBarForeground, showMode, selectionMode) + local tree = GUI.container(x, y, width, height) + + tree.eventHandler = treeEventHandler + tree.colors = { + default = { + background = backgroundColor, + expandable = expandableColor, + notExpandable = notExpandableColor, + arrow = arrowColor, + }, + selected = { + background = backgroundSelectedColor, + any = anySelectionColor, + arrow = arrowSelectionColor, + }, + scrollBar = { + background = scrollBarBackground, + foreground = scrollBarForeground + }, + disabled = disabledColor + } + tree.items = {} + tree.fromItem = 1 + tree.selectedItem = nil + tree.expandedItems = {} + + tree.scrollBar = GUI.scrollBar(1, 1, 1, 1, 0x0, 0x0, 1, 1, 1, 1, 1) + + tree.showMode = showMode + tree.selectionMode = selectionMode + tree.eventHandler = treeEventHandler + tree.addItem = treeAddItem + tree.draw = treeDraw + + return tree +end + +----------------------------------------- FilesystemTree object ----------------------------------------- + +local function filesystemTreeUpdateFileListRecursively(tree, path, offset) + local list = {} + for file in fs.list(path) do + table.insert(list, file) + end + + local i, expandables = 1, {} + while i <= #list do + if fs.isDirectory(path .. list[i]) then + table.insert(expandables, list[i]) + table.remove(list, i) + else + i = i + 1 + end + end + + table.sort(expandables, function(a, b) return unicode.lower(a) < unicode.lower(b) end) + table.sort(list, function(a, b) return unicode.lower(a) < unicode.lower(b) end) + + if tree.showMode == GUI.filesystemModes.both or tree.showMode == GUI.filesystemModes.directory then + for i = 1, #expandables do + tree:addItem(fs.name(expandables[i]), path .. expandables[i], offset, true) + + if tree.expandedItems[path .. expandables[i]] then + filesystemTreeUpdateFileListRecursively(tree, path .. expandables[i], offset + 2) + end + end + end + + if tree.showMode == GUI.filesystemModes.both or tree.showMode == GUI.filesystemModes.file then + for i = 1, #list do + tree:addItem(list[i], path .. list[i], offset, false, tree.extensionFilters and not tree.extensionFilters[fs.extension(path .. list[i], true)] or false) + end + end +end + +local function filesystemTreeUpdateFileList(tree) + tree.items = {} + filesystemTreeUpdateFileListRecursively(tree, tree.workPath, 1) +end + +local function filesystemTreeAddExtensionFilter(tree, extensionFilter) + tree.extensionFilters = tree.extensionFilters or {} + tree.extensionFilters[unicode.lower(extensionFilter)] = true +end + +local function filesystemTreeExpandPath(tree, path) + local blyadina = tree.workPath + for pizda in path:gmatch("[^/]+") do + blyadina = blyadina .. pizda .. "/" + tree.expandedItems[blyadina] = true + end +end + +function GUI.filesystemTree(...) + local tree = GUI.tree(...) + + tree.workPath = "/" + tree.updateFileList = filesystemTreeUpdateFileList + tree.addExtensionFilter = filesystemTreeAddExtensionFilter + tree.expandPath = filesystemTreeExpandPath + tree.onItemExpanded = function() + tree:updateFileList() + end + + return tree +end + +----------------------------------------- Text Box object ----------------------------------------- + +local function textBoxCalculate(object) + local doubleVerticalOffset = object.offset.vertical * 2 + object.textWidth = object.width - object.offset.horizontal * 2 - (object.scrollBarEnabled and 1 or 0) + + object.linesCopy = {} + + if object.autoWrap then + for i = 1, #object.lines do + local isTable = type(object.lines[i]) == "table" + for subLine in (isTable and object.lines[i].text or object.lines[i]):gmatch("[^\n]+") do + local wrappedLine = string.wrap(subLine, object.textWidth) + for j = 1, #wrappedLine do + table.insert(object.linesCopy, isTable and {text = wrappedLine[j], color = object.lines[i].color} or wrappedLine[j]) + end + end + end + else + for i = 1, #object.lines do + table.insert(object.linesCopy, object.lines[i]) + end + end + + if object.autoHeight then + object.height = #object.linesCopy + doubleVerticalOffset + end + + object.textHeight = object.height - doubleVerticalOffset +end + +local function textBoxDraw(object) + textBoxCalculate(object) + + if object.colors.background then + buffer.square(object.x, object.y, object.width, object.height, object.colors.background, object.colors.text, " ", object.colors.transparency) + end + + local x, y = nil, object.y + object.offset.vertical + local lineType, text, textColor + for i = object.currentLine, object.currentLine + object.textHeight - 1 do + if object.linesCopy[i] then + lineType = type(object.linesCopy[i]) + if lineType == "string" then + text, textColor = string.limit(object.linesCopy[i], object.textWidth), object.colors.text + elseif lineType == "table" then + text, textColor = string.limit(object.linesCopy[i].text, object.textWidth), object.linesCopy[i].color + else + error("Unknown TextBox line type: " .. tostring(lineType)) + end + + x = GUI.getAlignmentCoordinates( + { + x = object.x + object.offset.horizontal, + y = 1, + width = object.textWidth, + height = 1, + alignment = object.alignment + }, + { + width = unicode.len(text), + height = 1 + } + ) + buffer.text(math.floor(x), y, textColor, text) + y = y + 1 + else + break + end + end + + if object.scrollBarEnabled and object.textHeight < #object.lines then + object.scrollBar.x = object.x + object.width - 1 + object.scrollBar.y = object.y + object.scrollBar.height = object.height + object.scrollBar.maximumValue = #object.lines - object.textHeight + 1 + object.scrollBar.value = object.currentLine + object.scrollBar.shownValueCount = object.textHeight + + object.scrollBar:draw() + end + + return object +end + +local function scrollDownTextBox(object, count) + count = math.min(count or 1, #object.lines - object.height - object.currentLine + object.offset.vertical * 2 + 1) + if #object.lines >= object.height and object.currentLine < #object.lines - count then + object.currentLine = object.currentLine + count + end + + return object +end + +local function scrollUpTextBox(object, count) + count = count or 1 + if object.currentLine > count and object.currentLine >= 1 then object.currentLine = object.currentLine - count end + return object +end + +local function scrollToStartTextBox(object) + object.currentLine = 1 + return object +end + +local function scrollToEndTextBox(object) + if #object.lines > object.textHeight then + object.currentLine = #object.lines - object.textHeight + 1 + end + + return object +end + +local function textBoxScrollEventHandler(mainContainer, object, eventData) + if eventData[1] == "scroll" then + if eventData[5] == 1 then + object:scrollUp() + mainContainer:draw() + buffer.draw() + else + object:scrollDown() + mainContainer:draw() + buffer.draw() + end + end +end + +function GUI.textBox(x, y, width, height, backgroundColor, textColor, lines, currentLine, horizontalOffset, verticalOffset, autoWrap, autoHeight) + local object = GUI.object(x, y, width, height) + + object.eventHandler = textBoxScrollEventHandler + object.colors = { + text = textColor, + background = backgroundColor + } + object.setAlignment = GUI.setAlignment + object:setAlignment(GUI.alignment.horizontal.left, GUI.alignment.vertical.top) + object.lines = lines + object.currentLine = currentLine or 1 + object.draw = textBoxDraw + object.scrollUp = scrollUpTextBox + object.scrollDown = scrollDownTextBox + object.scrollToStart = scrollToStartTextBox + object.scrollToEnd = scrollToEndTextBox + object.offset = {horizontal = horizontalOffset or 0, vertical = verticalOffset or 0} + object.autoWrap = autoWrap + object.autoHeight = autoHeight + object.scrollBar = GUI.scrollBar(1, 1, 1, 1, 0xC3C3C3, 0x444444, 1, 1, 1, 1, 1, true) + object.scrollBarEnabled = false + + textBoxCalculate(object) + + return object +end + +----------------------------------------- Input object ----------------------------------------- + +local function inputSetCursorPosition(input, newPosition) + if newPosition < 1 then + newPosition = 1 + elseif newPosition > unicode.len(input.text) + 1 then + newPosition = unicode.len(input.text) + 1 + end + + if newPosition > input.textCutFrom + input.width - 1 - input.textOffset * 2 then + input.textCutFrom = input.textCutFrom + newPosition - (input.textCutFrom + input.width - 1 - input.textOffset * 2) + elseif newPosition < input.textCutFrom then + input.textCutFrom = newPosition + end + + input.cursorPosition = newPosition + + return input +end + +local function inputTextDrawMethod(x, y, color, text) + buffer.text(x, y, color, text) +end + +local function inputDraw(input) + local background, foreground, transparency, text + if input.focused then + background, transparency = input.colors.focused.background, input.colors.focused.transparency + if input.text == "" then + input.textCutFrom = 1 + foreground, text = input.colors.placeholderText, input.text + else + foreground = input.colors.focused.text + if input.textMask then + text = string.rep(input.textMask, unicode.len(input.text)) + else + text = input.text + end + end + else + background, transparency = input.colors.default.background, input.colors.default.transparency + if input.text == "" then + input.textCutFrom = 1 + foreground, text = input.colors.placeholderText, input.placeholderText + else + foreground = input.colors.default.text + if input.textMask then + text = string.rep(input.textMask, unicode.len(input.text)) + else + text = input.text + end + end + end + + if background then + buffer.square(input.x, input.y, input.width, input.height, background, foreground, " ", transparency) + end + + local y = input.y + math.floor(input.height / 2) + + input.textDrawMethod( + input.x + input.textOffset, + y, + foreground, + unicode.sub( + text or "", + input.textCutFrom, + input.textCutFrom + input.width - 1 - input.textOffset * 2 + ) + ) + + if input.cursorBlinkState then + local index = buffer.getIndex(input.x + input.cursorPosition - input.textCutFrom + input.textOffset, y) + local background = buffer.rawGet(index) + buffer.rawSet(index, background, input.colors.cursor, input.cursorSymbol) + end + + if input.autoCompleteEnabled then + input.autoComplete.x = input.x + if input.autoCompleteVerticalAlignment == GUI.alignment.vertical.top then + input.autoComplete.y = input.y - input.autoComplete.height + else + input.autoComplete.y = input.y + input.height + end + input.autoComplete.width = input.width + input.autoComplete:draw() + end +end + +local function inputStartInput(input) + local mainContainer = input:getFirstParent() + + local textOnStart = input.text + input.focused = true + + if input.historyEnabled then + input.historyIndex = input.historyIndex + 1 + end + + if input.eraseTextOnFocus then + input.text = "" + end + + input.cursorBlinkState = true + input:setCursorPosition(unicode.len(input.text) + 1) + + if input.autoCompleteEnabled then + input.autoCompleteMatchMethod() + end + + mainContainer:draw() + buffer.draw() + + while true do + local eventData = { event.pull(input.cursorBlinkDelay) } + + if eventData[1] == "touch" or eventData[1] == "drag" then + if input:isClicked(eventData[3], eventData[4]) then + input:setCursorPosition(input.textCutFrom + eventData[3] - input.x - input.textOffset) + + input.cursorBlinkState = true + mainContainer:draw() + buffer.draw() + elseif input.autoComplete:isClicked(eventData[3], eventData[4]) then + input.autoComplete.eventHandler(mainContainer, input.autoComplete, eventData) + else + input.cursorBlinkState = false + break + end + elseif eventData[1] == "scroll" then + input.autoComplete.eventHandler(mainContainer, input.autoComplete, eventData) + elseif eventData[1] == "key_down" then + -- Return + if eventData[4] == 28 then + if input.autoCompleteEnabled and input.autoComplete.itemCount > 0 then + input.autoComplete.eventHandler(mainContainer, input.autoComplete, eventData) + else + if input.historyEnabled then + -- Очистка истории + for i = 1, (#input.history - input.historyLimit) do + table.remove(input.history, 1) + end + + -- Добавление введенных данных в историю + if input.history[#input.history] ~= input.text and unicode.len(input.text) > 0 then + table.insert(input.history, input.text) + end + input.historyIndex = #input.history + end + + input.cursorBlinkState = false + break + end + -- Arrows up/down/left/right + elseif eventData[4] == 200 then + if input.autoCompleteEnabled and input.autoComplete.selectedItem > 1 then + input.autoComplete.eventHandler(mainContainer, input.autoComplete, eventData) + else + if input.historyEnabled and #input.history > 0 then + -- Добавление уже введенного текста в историю при стрелке вверх + if input.historyIndex == #input.history + 1 and unicode.len(input.text) > 0 then + input.history[input.historyIndex] = input.text + end + + input.historyIndex = input.historyIndex - 1 + if input.historyIndex > #input.history then + input.historyIndex = #input.history + elseif input.historyIndex < 1 then + input.historyIndex = 1 + end + + input.text = input.history[input.historyIndex] + input:setCursorPosition(unicode.len(input.text) + 1) + + if input.autoCompleteEnabled then + input.autoCompleteMatchMethod() + end + end + end + elseif eventData[4] == 208 then + if input.autoCompleteEnabled and input.historyIndex == #input.history + 1 then + input.autoComplete.eventHandler(mainContainer, input.autoComplete, eventData) + else + if input.historyEnabled and #input.history > 0 then + input.historyIndex = input.historyIndex + 1 + if input.historyIndex > #input.history then + input.historyIndex = #input.history + elseif input.historyIndex < 1 then + input.historyIndex = 1 + end + + input.text = input.history[input.historyIndex] + input:setCursorPosition(unicode.len(input.text) + 1) + + if input.autoCompleteEnabled then + input.autoCompleteMatchMethod() + end + end + end + elseif eventData[4] == 203 then + input:setCursorPosition(input.cursorPosition - 1) + elseif eventData[4] == 205 then + input:setCursorPosition(input.cursorPosition + 1) + -- Backspace + elseif eventData[4] == 14 then + input.text = unicode.sub(unicode.sub(input.text, 1, input.cursorPosition - 1), 1, -2) .. unicode.sub(input.text, input.cursorPosition, -1) + input:setCursorPosition(input.cursorPosition - 1) + + if input.autoCompleteEnabled then + input.autoCompleteMatchMethod() + end + -- Delete + elseif eventData[4] == 211 then + input.text = unicode.sub(input.text, 1, input.cursorPosition - 1) .. unicode.sub(input.text, input.cursorPosition + 1, -1) + + if input.autoCompleteEnabled then + input.autoCompleteMatchMethod() + end + else + local char = unicode.char(eventData[3]) + if not keyboard.isControl(eventData[3]) then + input.text = unicode.sub(input.text, 1, input.cursorPosition - 1) .. char .. unicode.sub(input.text, input.cursorPosition, -1) + input:setCursorPosition(input.cursorPosition + 1) + + if input.autoCompleteEnabled then + input.autoCompleteMatchMethod() + end + end + end + + input.cursorBlinkState = true + mainContainer:draw() + buffer.draw() + elseif eventData[1] == "clipboard" then + input.text = unicode.sub(input.text, 1, input.cursorPosition - 1) .. eventData[3] .. unicode.sub(input.text, input.cursorPosition, -1) + input:setCursorPosition(input.cursorPosition + unicode.len(eventData[3])) + + input.cursorBlinkState = true + mainContainer:draw() + buffer.draw() + elseif not eventData[1] then + input.cursorBlinkState = not input.cursorBlinkState + mainContainer:draw() + buffer.draw() + end + end + + input.focused = false + if input.autoCompleteEnabled then + input.autoComplete:clear() + end + + if input.validator then + if not input.validator(input.text) then + input.text = textOnStart + input:setCursorPosition(unicode.len(input.text) + 1) + end + end + + callMethod(input.onInputFinished, mainContainer, input, mainEventData, input.text) + + mainContainer:draw() + buffer.draw() +end + +local function inputEventHandler(mainContainer, input, mainEventData) + if mainEventData[1] == "touch" then + input:startInput() + end +end + +function GUI.input(x, y, width, height, backgroundColor, textColor, placeholderTextColor, backgroundFocusedColor, textFocusedColor, text, placeholderText, eraseTextOnFocus, textMask) + local input = GUI.object(x, y, width, height) + + input.colors = { + default = { + background = backgroundColor, + text = textColor + }, + focused = { + background = backgroundFocusedColor, + text = textFocusedColor + }, + placeholderText = placeholderTextColor, + cursor = 0x00A8FF + } + + input.text = text or "" + input.placeholderText = placeholderText + input.eraseTextOnFocus = eraseTextOnFocus + input.textMask = textMask + + input.textOffset = 1 + input.textCutFrom = 1 + input.cursorPosition = 1 + input.cursorSymbol = "┃" + input.cursorBlinkDelay = 0.4 + input.cursorBlinkState = false + input.textMask = textMask + input.setCursorPosition = inputSetCursorPosition + + input.history = {} + input.historyLimit = 20 + input.historyIndex = 0 + input.historyEnabled = false + + input.textDrawMethod = inputTextDrawMethod + input.draw = inputDraw + input.eventHandler = inputEventHandler + input.startInput = inputStartInput + + input.autoComplete = GUI.autoComplete(1, 1, 30, 7, 0xE1E1E1, 0x999999, 0x3C3C3C, 0x3C3C3C, 0x999999, 0xE1E1E1, 0xC3C3C3, 0x444444) + input.autoCompleteEnabled = false + input.autoCompleteVerticalAlignment = GUI.alignment.vertical.bottom + + return input +end + +----------------------------------------------------------------------------------------------------- + +local function autoCompleteDraw(object) + local y, yEnd = object.y, object.y + object.height - 1 + + buffer.square(object.x, object.y, object.width, object.height, object.colors.default.background, object.colors.default.text, " ") + + for i = object.fromItem, object.itemCount do + local textColor, textMatchColor = object.colors.default.text, object.colors.default.textMatch + if i == object.selectedItem then + buffer.square(object.x, y, object.width, 1, object.colors.selected.background, object.colors.selected.text, " ") + textColor, textMatchColor = object.colors.selected.text, object.colors.selected.textMatch + end + + buffer.text(object.x + 1, y, textMatchColor, object.matchText) + buffer.text(object.x + 1 + object.matchTextLength, y, textColor, unicode.sub(object.items[i], object.matchTextLength + 1, object.width - 2 - object.matchTextLength)) + + y = y + 1 + if y > yEnd then + break + end + end + + if object.itemCount > object.height then + object.scrollBar.x = object.x + object.width - 1 + object.scrollBar.y = object.y + object.scrollBar.height = object.height + object.scrollBar.maximumValue = object.itemCount - object.height + 1 + object.scrollBar.value = object.fromItem + object.scrollBar.shownValueCount = object.height + + object.scrollBar:draw() + end +end + +local function autoCompleteScroll(mainContainer, object, direction) + if object.itemCount >= object.height then + object.fromItem = object.fromItem + direction + if object.fromItem < 1 then + object.fromItem = 1 + elseif object.fromItem > object.itemCount - object.height + 1 then + object.fromItem = object.itemCount - object.height + 1 + end + end +end + +local function autoCompleteEventHandler(mainContainer, object, eventData) + if eventData[1] == "touch" then + object.selectedItem = eventData[4] - object.y + object.fromItem + mainContainer:draw() + buffer.draw() + + callMethod(object.onItemSelected, mainContainer, object, eventData, object.selectedItem) + elseif eventData[1] == "scroll" then + autoCompleteScroll(mainContainer, object, -eventData[5]) + mainContainer:draw() + buffer.draw() + elseif eventData[1] == "key_down" then + if eventData[4] == 28 then + callMethod(object.onItemSelected, mainContainer, object, eventData, object.selectedItem) + elseif eventData[4] == 200 then + object.selectedItem = object.selectedItem - 1 + if object.selectedItem < 1 then + object.selectedItem = 1 + end + + if object.selectedItem == object.fromItem - 1 then + autoCompleteScroll(mainContainer, object, -1) + end + + mainContainer:draw() + buffer.draw() + elseif eventData[4] == 208 then + object.selectedItem = object.selectedItem + 1 + if object.selectedItem > object.itemCount then + object.selectedItem = object.itemCount + end + + if object.selectedItem == object.fromItem + object.height then + autoCompleteScroll(mainContainer, object, 1) + end + + mainContainer:draw() + buffer.draw() + end + end +end + +local function autoCompleteClear(object) + object.items = {} + object.itemCount = 0 + object.fromItem = 1 + object.selectedItem = 1 + object.height = 0 +end + +local function autoCompleteMatch(object, variants, text) + object:clear() + + if text then + for i = 1, #variants do + if variants[i] ~= text and variants[i]:match("^" .. text) then + table.insert(object.items, variants[i]) + end + end + else + for i = 1, #variants do + table.insert(object.items, variants[i]) + end + end + + object.matchText = text or "" + object.matchTextLength = unicode.len(object.matchText) + + table.sort(object.items, function(a, b) return unicode.lower(a) < unicode.lower(b) end) + + object.itemCount = #object.items + object.height = math.min(object.itemCount, object.maximumHeight) + + return object +end + +function GUI.autoComplete(x, y, width, maximumHeight, backgroundColor, textColor, textMatchColor, backgroundSelectedColor, textSelectedColor, textMatchSelectedColor, scrollBarBackground, scrollBarForeground) + local object = GUI.object(x, y, width, maximumHeight) + + object.colors = { + default = { + background = backgroundColor, + text = textColor, + textMatch = textMatchColor + }, + selected = { + background = backgroundSelectedColor, + text = textSelectedColor, + textMatch = textMatchSelectedColor + } + } + + object.maximumHeight = maximumHeight + object.fromItem = 1 + object.selectedItem = 1 + object.items = {} + object.matchText = " " + object.matchTextLength = 1 + object.itemCount = 0 + + object.scrollBar = GUI.scrollBar(1, 1, 1, 1, scrollBarBackground, scrollBarForeground, 1, 1, 1, 1, 1, true) + + object.match = autoCompleteMatch + object.draw = autoCompleteDraw + object.eventHandler = autoCompleteEventHandler + object.clear = autoCompleteClear + + object:clear() + + return object +end + +----------------------------------------------------------------------------------------------------- + +local function brailleCanvasDraw(brailleCanvas) + local index, background, foreground, symbol + for y = 1, brailleCanvas.height do + for x = 1, brailleCanvas.width do + index = buffer.getIndex(brailleCanvas.x + x - 1, brailleCanvas.y + y - 1) + background, foreground, symbol = buffer.rawGet(index) + buffer.rawSet(index, background, brailleCanvas.pixels[y][x][9], brailleCanvas.pixels[y][x][10]) + end + end + + return brailleCanvas +end + +local function brailleCanvasSet(brailleCanvas, x, y, state, color) + local xReal, yReal = math.ceil(x / 2), math.ceil(y / 4) + + brailleCanvas.pixels[yReal][xReal][(y - (yReal - 1) * 4 - 1) * 2 + x - (xReal - 1) * 2] = state and 1 or 0 + brailleCanvas.pixels[yReal][xReal][9] = color or brailleCanvas.pixels[yReal][xReal][9] + brailleCanvas.pixels[yReal][xReal][10] = unicode.char( + 10240 + + 128 * brailleCanvas.pixels[yReal][xReal][8] + + 64 * brailleCanvas.pixels[yReal][xReal][7] + + 32 * brailleCanvas.pixels[yReal][xReal][6] + + 16 * brailleCanvas.pixels[yReal][xReal][4] + + 8 * brailleCanvas.pixels[yReal][xReal][2] + + 4 * brailleCanvas.pixels[yReal][xReal][5] + + 2 * brailleCanvas.pixels[yReal][xReal][3] + + brailleCanvas.pixels[yReal][xReal][1] + ) + + return brailleCanvas +end + +local function brailleCanvasGet(brailleCanvas, x, y) + local xReal, yReal = math.ceil(x / 2), math.ceil(y / 4) + return brailleCanvas.pixels[yReal][xReal][(y - (yReal - 1) * 4 - 1) * 2 + x - (xReal - 1) * 2], brailleCanvas.pixels[yReal][xReal][9], brailleCanvas.pixels[yReal][xReal][10] +end + +local function brailleCanvasFill(brailleCanvas, x, y, width, height, state, color) + for j = y, y + height - 1 do + for i = x, x + width - 1 do + brailleCanvas:set(i, j, state, color) + end + end +end + +local function brailleCanvasClear(brailleCanvas) + for j = 1, brailleCanvas.height * 4 do + brailleCanvas.pixels[j] = {} + for i = 1, brailleCanvas.width * 2 do + brailleCanvas.pixels[j][i] = { 0, 0, 0, 0, 0, 0, 0, 0, 0x0, " " } + end + end +end + +function GUI.brailleCanvas(x, y, width, height) + local brailleCanvas = GUI.object(x, y, width, height) + + brailleCanvas.pixels = {} + + brailleCanvas.get = brailleCanvasGet + brailleCanvas.set = brailleCanvasSet + brailleCanvas.fill = brailleCanvasFill + brailleCanvas.clear = brailleCanvasClear + + brailleCanvas.draw = brailleCanvasDraw + + brailleCanvas:clear() + + return brailleCanvas +end + +----------------------------------------- TabBar ----------------------------------------- + +local function tabBarDraw(tabBar) + local totalWidth = 0 + for i = 2, #tabBar.children do + totalWidth = totalWidth + tabBar.children[i].width + tabBar.spaceBetweenTabs + end + + local x = math.floor(tabBar.width / 2 - (totalWidth - tabBar.spaceBetweenTabs) / 2) + for i = 2, #tabBar.children do + tabBar.children[i].colors.default.background = tabBar.colors.default.background + tabBar.children[i].colors.default.text = tabBar.colors.default.text + tabBar.children[i].colors.pressed.background = tabBar.colors.selected.background + tabBar.children[i].colors.pressed.text = tabBar.colors.selected.text + tabBar.children[i].localX = x + tabBar.children[i].pressed = tabBar.selectedItem == i - 1 + + x = x + tabBar.children[i].width + tabBar.spaceBetweenTabs + end + + tabBar.backgroundPanel.colors.background = tabBar.colors.default.background + tabBar.backgroundPanel.width, tabBar.backgroundPanel.height = tabBar.width, tabBar.height + + GUI.drawContainerContent(tabBar) + + return tabBar +end + + + +local function tabBarTabEventHandler(mainContainer, tabBarTab, eventData) + if eventData[1] == "touch" then + tabBarTab.parent.selectedItem = tabBarTab:indexOf() - 1 + + callMethod(tabBarTab.onTouch, mainContainer, tabBarTab, eventData) + + mainContainer:draw() + buffer.draw() + end +end + +local function tabBarAddItem(tabBar, text) + local item = tabBar:addChild(GUI.button(1, 1, unicode.len(text) + tabBar.horizontalTabOffset * 2, tabBar.height, tabBar.colors.default.background, tabBar.colors.default.text, tabBar.colors.selected.background, tabBar.colors.selected.text, text)) + item.animated = false + item.switchMode = true + item.eventHandler = tabBarTabEventHandler + + if #tabBar.children - 1 == tabBar.selectedItem then + item.pressed = true + end + + return item +end + +local function tabBarGetItem(tabBar, index) + return tabBar.children[index + 1] +end + +function GUI.tabBar(x, y, width, height, horizontalTabOffset, spaceBetweenTabs, backgroundColor, textColor, backgroundSelectedColor, textSelectedColor, ...) + local tabBar = GUI.container(x, y, width, height) + + tabBar.colors = { + default = { + background = backgroundColor, + text = textColor + }, + selected = { + background = backgroundSelectedColor, + text = textSelectedColor + } + } + + tabBar.horizontalTabOffset = horizontalTabOffset + tabBar.spaceBetweenTabs = spaceBetweenTabs + tabBar.selectedItem = 1 + tabBar.backgroundPanel = tabBar:addChild(GUI.panel(1, 1, 1, 1, backgroundColor)) + + tabBar.addItem = tabBarAddItem + tabBar.getItem = tabBarGetItem + tabBar.draw = tabBarDraw + + return tabBar +end + +-------------------------------------------------------------------------------------------------------------- + +local function paletteShow(palette) + local mainContainer = GUI.fullScreenContainer() + mainContainer:addChild(palette) + + palette.OKButton.onTouch = function(mainContainer, object, eventData) + mainContainer:stopEventHandling() + end + + palette.cancelButton.onTouch = function(mainContainer, object, eventData) + mainContainer:stopEventHandling() + end + + mainContainer:draw() + buffer.draw() + mainContainer:startEventHandling() + + return palette.color.hex +end + +function GUI.palette(x, y, startColor) + local palette = GUI.container(x, y, 71, 25) + + palette.color = {hsb = {}, rgb = {}} + palette:addChild(GUI.panel(1, 1, palette.width, palette.height, 0xEEEEEE)) + + local bigImage = palette:addChild(GUI.image(1, 1, image.create(50, 25))) + local bigCrest = palette:addChild(GUI.object(1, 1, 5, 3)) + + local function paletteDrawBigCrestPixel(x, y, symbol) + local background, foreground = buffer.get(x, y) + local r, g, b = color.IntegerToRGB(background) + buffer.set(x, y, background, (r + g + b) / 3 >= 127 and 0x0 or 0xFFFFFF, symbol) + end + + bigCrest.draw = function(object) + paletteDrawBigCrestPixel(object.x, object.y + 1, "─") + paletteDrawBigCrestPixel(object.x + 1, object.y + 1, "─") + paletteDrawBigCrestPixel(object.x + 3, object.y + 1, "─") + paletteDrawBigCrestPixel(object.x + 4, object.y + 1, "─") + paletteDrawBigCrestPixel(object.x + 2, object.y, "│") + paletteDrawBigCrestPixel(object.x + 2, object.y + 2, "│") + end + + local miniImage = palette:addChild(GUI.image(53, 1, image.create(3, 25))) + + local miniCrest = palette:addChild(GUI.object(52, 1, 5, 1)) + miniCrest.draw = function(object) + buffer.text(object.x, object.y, 0x0, ">") + buffer.text(object.x + 4, object.y, 0x0, "<") + end + + local colorPanel = palette:addChild(GUI.panel(58, 2, 12, 3, 0x0)) + palette.OKButton = palette:addChild(GUI.roundedButton(58, 6, 12, 1, 0x444444, 0xFFFFFF, 0x2D2D2D, 0xFFFFFF, "OK")) + palette.cancelButton = palette:addChild(GUI.roundedButton(58, 8, 12, 1, 0xFFFFFF, 0x666666, 0x2D2D2D, 0xFFFFFF, "Cancel")) + + local function paletteRefreshBigImage() + local saturationStep, brightnessStep, saturation, brightness = 1 / bigImage.width, 1 / bigImage.height, 0, 1 + for j = 1, bigImage.height do + for i = 1, bigImage.width do + image.set(bigImage.image, i, j, color.optimize(color.HSBToInteger(palette.color.hsb.hue, saturation, brightness)), 0x0, 0x0, " ") + saturation = saturation + saturationStep + end + saturation, brightness = 0, brightness - brightnessStep + end + end + + local function paletteRefreshMiniImage() + local hueStep, hue = 360 / miniImage.height, 0 + for j = 1, miniImage.height do + for i = 1, miniImage.width do + image.set(miniImage.image, i, j, color.optimize(color.HSBToInteger(hue, 1, 1)), 0x0, 0, " ") + end + hue = hue + hueStep + end + end + + local function paletteUpdateCrestsCoordinates() + bigCrest.localX = math.floor((bigImage.width - 1) * palette.color.hsb.saturation) - 1 + bigCrest.localY = math.floor((bigImage.height - 1) - (bigImage.height - 1) * palette.color.hsb.brightness) + miniCrest.localY = math.floor(palette.color.hsb.hue / 360 * miniImage.height) + end + + local inputs + + local function paletteUpdateInputs() + inputs[1].text = tostring(palette.color.rgb.red) + inputs[2].text = tostring(palette.color.rgb.green) + inputs[3].text = tostring(palette.color.rgb.blue) + inputs[4].text = tostring(math.floor(palette.color.hsb.hue)) + inputs[5].text = tostring(math.floor(palette.color.hsb.saturation * 100)) + inputs[6].text = tostring(math.floor(palette.color.hsb.brightness * 100)) + inputs[7].text = string.format("%06X", palette.color.hex) + colorPanel.colors.background = palette.color.hex + end + + local function paletteSwitchColorFromHex(hex) + palette.color.hex = hex + palette.color.rgb.red, palette.color.rgb.green, palette.color.rgb.blue = color.IntegerToRGB(hex) + palette.color.hsb.hue, palette.color.hsb.saturation, palette.color.hsb.brightness = color.RGBToHSB(palette.color.rgb.red, palette.color.rgb.green, palette.color.rgb.blue) + paletteUpdateInputs() + end + + local function paletteSwitchColorFromHsb(hue, saturation, brightness) + palette.color.hsb.hue, palette.color.hsb.saturation, palette.color.hsb.brightness = hue, saturation, brightness + palette.color.rgb.red, palette.color.rgb.green, palette.color.rgb.blue = color.HSBToRGB(hue, saturation, brightness) + palette.color.hex = color.RGBToInteger(palette.color.rgb.red, palette.color.rgb.green, palette.color.rgb.blue) + paletteUpdateInputs() + end + + local function paletteSwitchColorFromRgb(red, green, blue) + palette.color.rgb.red, palette.color.rgb.green, palette.color.rgb.blue = red, green, blue + palette.color.hsb.hue, palette.color.hsb.saturation, palette.color.hsb.brightness = color.RGBToHSB(red, green, blue) + palette.color.hex = color.RGBToInteger(red, green, blue) + paletteUpdateInputs() + end + + local function onAnyInputFinished() + paletteRefreshBigImage() + paletteUpdateCrestsCoordinates() + palette:getFirstParent():draw() + buffer.draw() + end + + local function onHexInputFinished() + paletteSwitchColorFromHex(tonumber("0x" .. inputs[7].text)) + onAnyInputFinished() + end + + local function onRgbInputFinished() + paletteSwitchColorFromRgb(tonumber(inputs[1].text), tonumber(inputs[2].text), tonumber(inputs[3].text)) + onAnyInputFinished() + end + + local function onHsbInputFinished() + paletteSwitchColorFromHsb(tonumber(inputs[4].text), tonumber(inputs[5].text) / 100, tonumber(inputs[6].text) / 100) + onAnyInputFinished() + end + + local function rgbValidaror(text) + local number = tonumber(text) if number and number >= 0 and number <= 255 then return true end + end + + local function hValidator(text) + local number = tonumber(text) if number and number >= 0 and number <= 359 then return true end + end + + local function sbValidator(text) + local number = tonumber(text) if number and number >= 0 and number <= 100 then return true end + end + + local function hexValidator(text) + if string.match(text, "^[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]$") then + return true + end + end + + inputs = { + { shortcut = "R:", validator = rgbValidaror, onInputFinished = onRgbInputFinished }, + { shortcut = "G:", validator = rgbValidaror, onInputFinished = onRgbInputFinished }, + { shortcut = "B:", validator = rgbValidaror, onInputFinished = onRgbInputFinished }, + { shortcut = "H:", validator = hValidator, onInputFinished = onHsbInputFinished }, + { shortcut = "S:", validator = sbValidator, onInputFinished = onHsbInputFinished }, + { shortcut = "L:", validator = sbValidator, onInputFinished = onHsbInputFinished }, + { shortcut = "0x", validator = hexValidator, onInputFinished = onHexInputFinished } + } + + local y = 10 + for i = 1, #inputs do + palette:addChild(GUI.label(58, y, 2, 1, 0x000000, inputs[i].shortcut)) + + local validator, onInputFinished = inputs[i].validator, inputs[i].onInputFinished + inputs[i] = palette:addChild(GUI.input(61, y, 9, 1, 0xFFFFFF, 0x666666, 0x666666, 0xFFFFFF, 0x000000, "", "", true)) + inputs[i].validator = validator + inputs[i].onInputFinished = onInputFinished + + y = y + 2 + end + + local favourites + if fs.exists(GUI.paletteConfigPath) then + favourites = table.fromFile(GUI.paletteConfigPath) + else + favourites = {} + for i = 1, 6 do favourites[i] = color.HSBToInteger(math.random(0, 360), 1, 1) end + table.toFile(GUI.paletteConfigPath, favourites) + end + + local favouritesContainer = palette:addChild(GUI.container(58, 24, 12, 1)) + for i = 1, #favourites do + favouritesContainer:addChild(GUI.button(i * 2 - 1, 1, 2, 1, favourites[i], 0x0, 0x0, 0x0, " ")).onTouch = function(mainContainer, object, eventData) + paletteSwitchColorFromHex(button.colors.default.background) + paletteRefreshBigImage() + paletteUpdateCrestsCoordinates() + mainContainer:draw() + buffer.draw() + end + end + + palette:addChild(GUI.button(58, 25, 12, 1, 0xFFFFFF, 0x444444, 0x2D2D2D, 0xFFFFFF, "+")).onTouch = function(mainContainer, object, eventData) + local favouriteExists = false + for i = 1, #favourites do + if favourites[i] == palette.color.hex then + favouriteExists = true + break + end + end + + if not favouriteExists then + table.insert(favourites, 1, palette.color.hex) + table.remove(favourites, #favourites) + for i = 1, #favourites do + favouritesContainer.children[i].colors.default.background = favourites[i] + favouritesContainer.children[i].colors.pressed.background = 0x0 + end + + table.toFile(GUI.paletteConfigPath, favourites) + + mainContainer:draw() + buffer.draw() + end + end + + bigImage.eventHandler = function(mainContainer, object, eventData) + if eventData[1] == "touch" or eventData[1] == "drag" and bigImage:isClicked(eventData[3], eventData[4]) then + bigCrest.localX, bigCrest.localY = eventData[3] - palette.x - 1, eventData[4] - palette.y + paletteSwitchColorFromHex(select(3, component.gpu.get(eventData[3], eventData[4]))) + mainContainer:draw() + buffer.draw() + end + end + bigCrest.eventHandler = bigImage.eventHandler + + miniImage.eventHandler = function(mainContainer, object, eventData) + if eventData[1] == "touch" or eventData[1] == "drag" then + miniCrest.localY = eventData[4] - palette.y + 1 + paletteSwitchColorFromHsb((eventData[4] - miniImage.y) * 360 / miniImage.height, palette.color.hsb.saturation, palette.color.hsb.brightness) + paletteRefreshBigImage() + mainContainer:draw() + buffer.draw() + end + end + + palette.show = paletteShow + + paletteSwitchColorFromHex(startColor) + paletteUpdateCrestsCoordinates() + paletteRefreshBigImage() + paletteRefreshMiniImage() + + return palette +end + +----------------------------------------------------------------------------------------------------- + +local function textUpdate(object) + object.width = unicode.len(object.text) + return object +end + +local function textDraw(object) + object:update() + buffer.text(object.x, object.y, object.color, object.text) + return object +end + +function GUI.text(x, y, color, text) + local object = GUI.object(x, y, 1, 1) + + object.text = text + object.color = color + object.update = textUpdate + object.draw = textDraw + object:update() + + return object +end + +----------------------------------------------------------------------------------------------------- + +-- buffer.clear() +-- buffer.draw(true) + +-- local mainContainer = GUI.fullScreenContainer() +-- mainContainer:addChild(GUI.panel(1, 1, mainContainer.width, mainContainer.height, 0x2D2D2D)) + +-- local w, h = 40, 20 +-- mainContainer:addChild(GUI.panel(3, 2, w, h, 0x3C3C3C)) + +-- local layout = mainContainer:addChild(GUI.layout(3, 2, w, h, 1, 1)) +-- layout:setCellAlignment(1, 1, GUI.alignment.horizontal.center, GUI.alignment.vertical.center) +-- layout.defaultRow, layout.defaultColumn = 1, 1 + +-- layout:addChild(GUI.text(1, 1, 0xFFFFFF, "Мяу-мяу")) +-- layout:addChild(GUI.text(1, 1, 0xFFFFFF, "Это всего лишь обычный текст")) +-- layout:addChild(GUI.text(1, 1, 0xFFFFFF, "Текстик, как делы?")) +-- layout:addChild(GUI.text(1, 1, 0xFFFFFF, "Вот уж мяу так мяу, мда, лол, кек, чебурек")) + + +-- mainContainer:draw() +-- buffer.draw(true) +-- mainContainer:startEventHandling() + + +----------------------------------------------------------------------------------------------------- + +return GUI + + + + + + diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/GUI2/Main.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/GUI2/Main.lua new file mode 100755 index 00000000..2d048022 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/GUI2/Main.lua @@ -0,0 +1,437 @@ + +require("advancedLua") +local component = require("component") +local computer = require("computer") +local keyboard = require("keyboard") +local fs = require("filesystem") +local unicode = require("unicode") +local event = require("event") +local color = require("color") +local image = require("image") +local buffer = require("doubleBuffering") + +-------------------------------------------------------------------------------------------- + +local GUIAlignment = { + horizontal = enum( + "left", + "center", + "right" + ), + vertical = enum( + "top", + "center", + "bottom" + ) +} + +local GUIDirections = enum( + "horizontal", + "vertical" +) + +-------------------------------------------------------------------------------------------- + +local function objectIsClicked(object, x, y) + return + x >= object.x and + y >= object.y and + x <= object.x + object.width - 1 and + y <= object.y + object.height - 1 and + not object.disabled and + not object.hidden +end + +local function objectDraw(object) + return object +end + +local function GUIObject(x, y, width, height) + return { + x = x, + y = y, + width = width, + height = height, + isClicked = objectIsClicked, + draw = objectDraw + } +end + +-------------------------------------------------------------------------------------------- + +local function GUISetAlignment(object, horizontalAlignment, verticalAlignment) + object.alignment = { + horizontal = horizontalAlignment, + vertical = verticalAlignment + } + + return object +end + +local function GUIGetAlignmentCoordinates(object, subObject) + local x, y + if object.alignment.horizontal == GUIAlignment.horizontal.left then + x = object.x + elseif object.alignment.horizontal == GUIAlignment.horizontal.center then + x = math.floor(object.x + object.width / 2 - subObject.width / 2) + elseif object.alignment.horizontal == GUIAlignment.horizontal.right then + x = object.x + object.width - subObject.width + else + error("Unknown horizontal alignment: " .. tostring(object.alignment.horizontal)) + end + + if object.alignment.vertical == GUIAlignment.vertical.top then + y = object.y + elseif object.alignment.vertical == GUIAlignment.vertical.center then + y = math.floor(object.y + object.height / 2 - subObject.height / 2) + elseif object.alignment.vertical == GUIAlignment.vertical.bottom then + y = object.y + object.height - subObject.height + else + error("Unknown vertical alignment: " .. tostring(object.alignment.vertical)) + end + + return x, y +end + +local function GUIGetMarginCoordinates(object) + local x, y = object.x, object.y + + if object.alignment.horizontal == GUIAlignment.horizontal.left then + x = x + object.margin.horizontal + elseif object.alignment.horizontal == GUIAlignment.horizontal.right then + x = x - object.margin.horizontal + end + + if object.alignment.vertical == GUIAlignment.vertical.top then + y = y + object.margin.vertical + elseif object.alignment.vertical == GUIAlignment.vertical.bottom then + y = y - object.margin.vertical + end + + return x, y +end + +-------------------------------------------------------------------------------------------- + +local function containerObjectIndexOf(object) + if not object.parent then error("Object doesn't have a parent container") end + + for objectIndex = 1, #object.parent.children do + if object.parent.children[objectIndex] == object then + return objectIndex + end + end +end + +local function containerObjectMoveForward(object) + local objectIndex = containerObjectIndexOf(object) + if objectIndex < #object.parent.children then + object.parent.children[index], object.parent.children[index + 1] = object.parent.children[index + 1], object.parent.children[index] + end + + return object +end + +local function containerObjectMoveBackward(object) + local objectIndex = containerObjectIndexOf(object) + if objectIndex > 1 then + object.parent.children[objectIndex], object.parent.children[objectIndex - 1] = object.parent.children[objectIndex - 1], object.parent.children[objectIndex] + end + + return object +end + +local function containerObjectMoveToFront(object) + table.remove(object.parent.children, containerObjectIndexOf(object)) + table.insert(object.parent.children, object) + + return object +end + +local function containerObjectMoveToBack(object) + table.remove(object.parent.children, containerObjectIndexOf(object)) + table.insert(object.parent.children, 1, object) + + return object +end + +local function containerObjectGetFirstParent(object) + local currentParent = object.parent + while currentParent.parent do + currentParent = currentParent.parent + end + + return currentParent +end + +local function containerObjectSelfDelete(object) + table.remove(object.parent.children, containerObjectIndexOf(object)) +end + +-------------------------------------------------------------------------------------------- + +local function containerObjectAnimationStart(animation, duration) + animation.position = 0 + animation.duration = duration + animation.started = true + animation.startUptime = computer.uptime() + + computer.pushSignal("GUI", "animationStarted") +end + +local function containerObjectAnimationStop(animation) + animation.position = 0 + animation.started = false +end + +local function containerObjectAnimationDelete(animation) + animation.deleteLater = true +end + +local function containerObjectAddAnimation(object, frameHandler, onFinish) + local animation = { + object = object, + position = 0, + start = containerObjectAnimationStart, + stop = containerObjectAnimationStop, + delete = containerObjectAnimationDelete, + frameHandler = frameHandler, + onFinish = onFinish, + } + + local firstParent = object:getFirstParent() + firstParent.animations = firstParent.animations or {} + table.insert(firstParent.animations, animation) + + return animation +end + +local function GUIAddChildToContainer(container, object, atIndex) + object.localX = object.x + object.localY = object.y + object.indexOf = containerObjectIndexOf + object.moveToFront = containerObjectMoveToFront + object.moveToBack = containerObjectMoveToBack + object.moveForward = containerObjectMoveForward + object.moveBackward = containerObjectMoveBackward + object.getFirstParent = containerObjectGetFirstParent + object.delete = containerObjectSelfDelete + object.parent = container + object.addAnimation = containerObjectAddAnimation + + if atIndex then + table.insert(container.children, atIndex, object) + else + table.insert(container.children, object) + end + + return object +end + +local function deleteContainersContent(container, from, to) + from = from or 1 + for objectIndex = from, to or #container.children do + table.remove(container.children, from) + end +end + +local function getRectangleIntersection(R1X1, R1Y1, R1X2, R1Y2, R2X1, R2Y1, R2X2, R2Y2) + if R2X1 <= R1X2 and R2Y1 <= R1Y2 and R2X2 >= R1X1 and R2Y2 >= R1Y1 then + return + math.max(R2X1, R1X1), + math.max(R2Y1, R1Y1), + math.min(R2X2, R1X2), + math.min(R2Y2, R1Y2) + else + return + end +end + +local function GUIDrawContainerContent(container) + local R1X1, R1Y1, R1X2, R1Y2, child = buffer.getDrawLimit() + local intersectionX1, intersectionY1, intersectionX2, intersectionY2 = getRectangleIntersection( + R1X1, + R1Y1, + R1X2, + R1Y2, + container.x, + container.y, + container.x + container.width - 1, + container.y + container.height - 1 + ) + + if intersectionX1 then + buffer.setDrawLimit(intersectionX1, intersectionY1, intersectionX2, intersectionY2) + + for i = 1, #container.children do + child = container.children[i] + + if not child.hidden then + child.x, child.y = container.x + child.localX - 1, container.y + child.localY - 1 + child:draw() + end + end + + buffer.setDrawLimit(R1X1, R1Y1, R1X2, R1Y2) + end + + return container +end + +local function containerHandler(isScreenEvent, mainContainer, currentContainer, eventData, intersectionX1, intersectionY1, intersectionX2, intersectionY2) + local breakRecursion, child = false + + if not isScreenEvent or intersectionX1 and eventData[3] >= intersectionX1 and eventData[4] >= intersectionY1 and eventData[3] <= intersectionX2 and eventData[4] <= intersectionY2 then + for i = #currentContainer.children, 1, -1 do + child = currentContainer.children[i] + + if not child.hidden then + if child.children then + local newIntersectionX1, newIntersectionY1, newIntersectionX2, newIntersectionY2 = getRectangleIntersection( + intersectionX1, + intersectionY1, + intersectionX2, + intersectionY2, + child.x, + child.y, + child.x + child.width - 1, + child.y + child.height - 1 + ) + + if newIntersectionX1 then + if containerHandler(isScreenEvent, mainContainer, child, eventData, newIntersectionX1, newIntersectionY1, newIntersectionX2, newIntersectionY2) then + breakRecursion = true + break + end + end + else + if isScreenEvent then + if child:isClicked(eventData[3], eventData[4]) then + if child.eventHandler then child.eventHandler(mainContainer, child, eventData) end + breakRecursion = true + break + end + else + if child.eventHandler then child.eventHandler(mainContainer, child, eventData) end + end + end + end + end + + if currentContainer.eventHandler then currentContainer.eventHandler(mainContainer, currentContainer, eventData) end + end + + if breakRecursion then + return true + end +end + +local function containerStartEventHandling(container, eventHandlingDelay) + container.eventHandlingDelay = eventHandlingDelay + + local eventData, animationIndex, animation, animationOnFinishMethods + repeat + eventData = { event.pull(container.animations and 0 or container.eventHandlingDelay) } + + containerHandler( + ( + eventData[1] == "touch" or + eventData[1] == "drag" or + eventData[1] == "drop" or + eventData[1] == "scroll" or + eventData[1] == "double_touch" + ), + container, + container, + eventData, + container.x, + container.y, + container.x + container.width - 1, + container.y + container.height - 1 + ) + + if container.animations then + animationIndex, animationOnFinishMethods = 1, {} + + -- Продрачиваем анимации и вызываем обработчики кадров + while animationIndex <= #container.animations do + animation = container.animations[animationIndex] + + if animation.deleteLater then + table.remove(container.animations, animationIndex) + if #container.animations == 0 then + container.animations = nil + break + end + else + if animation.started then + animationNeedDraw = true + animation.position = (computer.uptime() - animation.startUptime) / animation.duration + + if animation.position < 1 then + animation.frameHandler(container, animation) + else + animation.position = 1 + animation.started = false + animation.frameHandler(container, animation) + + if animation.onFinish then + table.insert(animationOnFinishMethods, animation) + end + end + end + + animationIndex = animationIndex + 1 + end + end + + -- По завершению продрочки отрисовываем изменения на экране + container:draw() + buffer.draw() + + -- Вызываем поочередно все методы .onFinish + for i = 1, #animationOnFinishMethods do + animationOnFinishMethods[i].onFinish(container, animationOnFinishMethods[i]) + end + end + until container.dataToReturn + + local dataToReturn = container.dataToReturn + container.dataToReturn = nil + return table.unpack(dataToReturn) +end + +local function containerReturnData(container, ...) + container.dataToReturn = {...} +end + +local function containerStopEventHandling(container) + containerReturnData(container, nil) +end + +local function GUIContainer(x, y, width, height) + local container = GUIObject(x, y, width, height) + + container.children = {} + container.draw = GUIDrawContainerContent + container.deleteChildren = deleteContainersContent + container.addChild = GUIAddChildToContainer + container.returnData = containerReturnData + container.startEventHandling = containerStartEventHandling + container.stopEventHandling = containerStopEventHandling + + return container +end + +local function GUIFullScreenContainer() + return GUIContainer(1, 1, buffer.getResolution()) +end + +-------------------------------------------------------------------------------------------- + +return + + + + + diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/MeowEngine/Main.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/MeowEngine/Main.lua new file mode 100755 index 00000000..c45dac92 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/MeowEngine/Main.lua @@ -0,0 +1,519 @@ + +-------------------------------------------------------- Libraries -------------------------------------------------------- + +local buffer = require("doubleBuffering") +local vector = require("vector") +local OCGL = require("OpenComputersGL/Main") +local renderer = require("OpenComputersGL/Renderer") +local materials = require("OpenComputersGL/Materials") +local meowEngine = {} + +-------------------------------------------------------- Universal object methods -------------------------------------------------------- + +function meowEngine.newPivotPoint(vector3Position) + return { + position = vector3Position, + axis = { + vector.newVector3(1, 0, 0), + vector.newVector3(0, 1, 0), + vector.newVector3(0, 0, 1), + } + } +end + +-------------------------------------------------------- Light object -------------------------------------------------------- + +function meowEngine.newLight(vector3Position, intensity, emissionDistance) + return { + position = vector3Position, + emissionDistance = emissionDistance, + intensity = intensity + } +end + +-------------------------------------------------------- Mesh object -------------------------------------------------------- + +local function pushMeshToRenderQueue(mesh) + local vector3Vertex1, vector3Vertex2, vector3Vertex3 + for triangleIndex = 1, #mesh.triangles do + vector3Vertex1, vector3Vertex2, vector3Vertex3 = mesh.vertices[mesh.triangles[triangleIndex][1]], mesh.vertices[mesh.triangles[triangleIndex][2]], mesh.vertices[mesh.triangles[triangleIndex][3]] + OCGL.pushTriangleToRenderQueue( + vector.newVector5(vector3Vertex1[1], vector3Vertex1[2], vector3Vertex1[3], vector3Vertex1[4], vector3Vertex1[5]), + vector.newVector5(vector3Vertex2[1], vector3Vertex2[2], vector3Vertex2[3], vector3Vertex2[4], vector3Vertex2[5]), + vector.newVector5(vector3Vertex3[1], vector3Vertex3[2], vector3Vertex3[3], vector3Vertex3[4], vector3Vertex3[5]), + mesh.triangles[triangleIndex][4] or mesh.material + ) + end +end + + +function meowEngine.newMesh(vector3Position, vertices, triangles, material) + local mesh = {} + + mesh.vertices = vertices + mesh.position = vector3Position + for vertexIndex = 1, #mesh.vertices do + mesh.vertices[vertexIndex][1], mesh.vertices[vertexIndex][2], mesh.vertices[vertexIndex][3] = mesh.vertices[vertexIndex][1] + vector3Position[1], mesh.vertices[vertexIndex][2] + vector3Position[2], mesh.vertices[vertexIndex][3] + vector3Position[3] + end + mesh.triangles = triangles + mesh.material = material + mesh.pushToRenderQueue = pushMeshToRenderQueue + + return mesh +end + +-------------------------------------------------------- Line object -------------------------------------------------------- + +local function pushLineToRenderQueue(line) + OCGL.pushLineToRenderQueue( + vector.newVector3(line.vertices[1][1], line.vertices[1][2], line.vertices[1][3]), + vector.newVector3(line.vertices[2][1], line.vertices[2][2], line.vertices[2][3]), + line.color + ) +end + +function meowEngine.newLine(vector3Position, vector3Vertex1, vector3Vertex2, color) + return { + vertices = { vector3Vertex1, vector3Vertex2 }, + color = color, + pushToRenderQueue = pushLineToRenderQueue + } +end + +-------------------------------------------------------- Floating text object -------------------------------------------------------- + +local function pushFloatingTextToRenderQueue(floatingText) + OCGL.pushFloatingTextToRenderQueue( + vector.newVector3(floatingText.position[1], floatingText.position[2], floatingText.position[3]), + floatingText.text, + floatingText.color + ) +end + +function meowEngine.newFloatingText(vector3Position, color, text) + return { + position = vector3Position, + color = color, + text = text, + pushToRenderQueue = pushFloatingTextToRenderQueue + } +end + +-------------------------------------------------------- Plane object -------------------------------------------------------- + +function meowEngine.newPlane(vector3Position, width, height, segmentsWidth, segmentsHeight, material) + local vertices, triangles, widthCellSize, heightCellSize, vertexIndex = {}, {}, width / segmentsWidth, height / segmentsHeight, 1 + segmentsWidth, segmentsHeight = segmentsWidth + 1, segmentsHeight + 1 + + for zSegment = 1, segmentsHeight do + for xSegment = 1, segmentsWidth do + table.insert(vertices, vector.newVector3(xSegment * widthCellSize - widthCellSize, 0, zSegment * heightCellSize - heightCellSize)) + + if xSegment < segmentsWidth and zSegment < segmentsHeight then + table.insert(triangles, + OCGL.newIndexedTriangle( + vertexIndex, + vertexIndex + 1, + vertexIndex + segmentsWidth + ) + ) + table.insert(triangles, + OCGL.newIndexedTriangle( + vertexIndex + 1, + vertexIndex + segmentsWidth + 1, + vertexIndex + segmentsWidth + ) + ) + end + + vertexIndex = vertexIndex + 1 + end + end + + return meowEngine.newMesh(vector3Position, vertices, triangles, material) +end + +-------------------------------------------------------- Textured plane object -------------------------------------------------------- + +function meowEngine.newTexturedPlane(vector3Position, width, height, texture) + width, height = width / 2, height / 2 + return meowEngine.newMesh( + vector3Position, + { + vector.newVector5(-width, 0, -height, 1, texture.height), + vector.newVector5(-width, 0, height, 1, 1), + vector.newVector5(width, 0, height, texture.width, 1), + vector.newVector5(width, 0, -height, texture.width, texture.height), + }, + { + OCGL.newIndexedTriangle(1, 2, 3), + OCGL.newIndexedTriangle(1, 4, 3) + }, + materials.newTexturedMaterial(texture) + ) +end + +-------------------------------------------------------- Cube object -------------------------------------------------------- + +--[[ + | / + | / + y z + x ----- + + FRONT LEFT BACK RIGHT TOP BOTTOM + 2######3 3######6 6######7 7######2 7######6 8######5 + ######## ######## ######## ######## ######## ######## + 1######4 4######5 5######8 8######1 2######3 1######4 +]] + +function meowEngine.newCube(vector3Position, size, material) + local halfSize = size / 2 + return meowEngine.newMesh( + vector3Position, + { + -- (1-2-3-4) + vector.newVector3(-halfSize, -halfSize, -halfSize), + vector.newVector3(-halfSize, halfSize, -halfSize), + vector.newVector3(halfSize, halfSize, -halfSize), + vector.newVector3(halfSize, -halfSize, -halfSize), + -- (5-6-7-8) + vector.newVector3(halfSize, -halfSize, halfSize), + vector.newVector3(halfSize, halfSize, halfSize), + vector.newVector3(-halfSize, halfSize, halfSize), + vector.newVector3(-halfSize, -halfSize, halfSize), + }, + { + -- Front + OCGL.newIndexedTriangle(1, 2, 3), + OCGL.newIndexedTriangle(1, 4, 3), + -- Left + OCGL.newIndexedTriangle(4, 3, 6), + OCGL.newIndexedTriangle(4, 5, 6), + -- Back + OCGL.newIndexedTriangle(5, 6, 7), + OCGL.newIndexedTriangle(5, 8, 7), + -- Right + OCGL.newIndexedTriangle(8, 7, 2), + OCGL.newIndexedTriangle(8, 1, 2), + -- Top + OCGL.newIndexedTriangle(2, 7, 6), + OCGL.newIndexedTriangle(2, 3, 6), + -- Bottom + OCGL.newIndexedTriangle(1, 8, 5), + OCGL.newIndexedTriangle(1, 4, 5), + }, + material + ) +end + +-------------------------------------------------------- Camera object -------------------------------------------------------- + + +local function cameraSetRotation(camera, axisXRotation, axisYRotation, axisZRotation) + camera.rotation[1], camera.rotation[2], camera.rotation[3] = axisXRotation, axisYRotation, axisZRotation + return camera +end + +local function cameraRotate(camera, axisXAdditionalRotation, axisYAdditionalRotation, axisZAdditionalRotation) + cameraSetRotation(camera, camera.rotation[1] + axisXAdditionalRotation, camera.rotation[2] + axisYAdditionalRotation, camera.rotation[3] + axisZAdditionalRotation) + return camera +end + +local function cameraLookAt(camera, xLook, yLook, zLook) + local dx, dy, dz = xLook - camera.position[1], yLook - camera.position[2], zLook - camera.position[3] + local rad180 = math.rad(180) + + local roty = math.atan(dx / dz) + if dz < 0 then roty = roty + rad180 end + + local rotx = math.atan(math.sqrt(dx ^ 2 + dz ^ 2) / dy) - math.rad(90) + if dy < 0 then rotx = rotx + rad180 end + + cameraSetRotation(camera, rotx, roty, 0) +end + +local function cameraSetPosition(camera, x, y, z) + camera.position[1], camera.position[2], camera.position[3] = x, y, z + return camera +end + +local function cameraTranslate(camera, xTranslation, yTranslation, zTranslation, xLookingAtTranslation, yLookingAtTranslation, zLookingAtTranslation) + cameraSetPosition(camera, camera.position[1] + xTranslation, camera.position[2] + yTranslation, camera.position[3] + zTranslation) + return camera +end + +local function cameraSetFOV(camera, FOV) + if FOV > 0 and FOV < math.pi then + camera.FOV = FOV + camera.projectionSurface = camera.farClippingSurface - camera.FOV / math.rad(180) * (camera.farClippingSurface - camera.nearClippingSurface) + else + error("FOV can't be < 0 or > 180 degrees") + end + + return camera +end + +function meowEngine.newCamera(vector3Position, FOV, nearClippingSurface, farClippingSurface) + local camera = {} + + camera.projectionEnabled = true + camera.position = vector3Position + camera.rotation = {} + camera.nearClippingSurface = nearClippingSurface + camera.farClippingSurface = farClippingSurface + camera.FOV = FOV + + camera.setPosition = cameraSetPosition + camera.translate = cameraTranslate + camera.rotate = cameraRotate + camera.setRotation = cameraSetRotation + camera.setFOV = cameraSetFOV + camera.lookAt = cameraLookAt + + -- Создаем точку "лука" (и матрицу поворота камеры), а также ее плоскость проекции через ФОВ + cameraSetRotation(camera, 0, 0, 0) + cameraSetFOV(camera, camera.FOV) + + return camera +end + +-------------------------------------------------------- Scene object -------------------------------------------------------- + +local function sceneAddObject(scene, object) + table.insert(scene.objects, object) + return object +end + +local function sceneAddLight(scene, light) + table.insert(scene.lights, light) + return light +end + +local function sceneAddObjects(scene, objects) + for objectIndex = 1, #objects do table.insert(scene.objects, objects[objectIndex]) end + return objects +end + +local function sceneRender(scene) + renderer.setViewport( 1, 1, buffer.getWidth(), buffer.getHeight() * 2, scene.camera.nearClippingSurface, scene.camera.farClippingSurface, scene.camera.projectionSurface) + OCGL.clearBuffer(scene.backgroundColor) + OCGL.renderMode = scene.renderMode + OCGL.auxiliaryMode = scene.auxiliaryMode + + for objectIndex = 1, #scene.objects do + scene.objects[objectIndex]:pushToRenderQueue() + end + + for lightIndex = 1, #scene.lights do + OCGL.pushLightToRenderQueue( + vector.newVector3(scene.lights[lightIndex].position[1], scene.lights[lightIndex].position[2], scene.lights[lightIndex].position[3]), + scene.lights[lightIndex].intensity, + scene.lights[lightIndex].emissionDistance + ) + end + + OCGL.translate(-scene.camera.position[1], -scene.camera.position[2], -scene.camera.position[3]) + OCGL.rotate(OCGL.rotateVectorRelativeToYAxis, -scene.camera.rotation[2]) + OCGL.rotate(OCGL.rotateVectorRelativeToXAxis, -scene.camera.rotation[1]) + -- OCGL.rotate(OCGL.rotateVectorRelativeToZAxis, -scene.camera.rotation[3]) + + if scene.renderMode == OCGL.renderModes.flatShading then + OCGL.calculateLights() + end + + if scene.camera.projectionEnabled then + OCGL.createPerspectiveProjection() + end + + OCGL.render() + + return scene +end + +function meowEngine.newScene(backgroundColor) + local scene = {} + + scene.renderMode = OCGL.renderModes.constantShading + scene.auxiliaryMode = OCGL.auxiliaryModes.disabled + + scene.backgroundColor = backgroundColor + + scene.objects = {} + scene.lights = {} + scene.addObject = sceneAddObject + scene.addLight = sceneAddLight + scene.addObjects = sceneAddObjects + scene.render = sceneRender + + scene.camera = meowEngine.newCamera(vector.newVector3(0, 0, 0), math.rad(90), 1, 100) + + return scene +end + +-------------------------------------------------------- Raycasting methods -------------------------------------------------------- + +local function vectorMultiply(a, b) + return vector.newVector3( + a[2] * b[3] - a[3] * b[2], + a[3] * b[1] - a[1] * b[3], + a[1] * b[2] - a[2] * b[1] + ) +end + +local function getVectorDistance(a) + return math.sqrt(a[1] ^ 2 + a[2] ^ 2 + a[3] ^ 2) +end + +-- В случае попадания лучика этот метод вернет сам треугольник, а также дистанцию до его плоскости +function meowEngine.meshRaycast(mesh, vector3RayStart, vector3RayEnd) + local minimalDistance, closestTriangleIndex + for triangleIndex = 1, #mesh.triangles do + -- Это вершины треугольника + local A, B, C = mesh.vertices[mesh.triangles[triangleIndex][1]], mesh.vertices[mesh.triangles[triangleIndex][2]], mesh.vertices[mesh.triangles[triangleIndex][3]] + -- Это вектор, образованный произведением двух векторов-сторон треугольника, он образует параллелограмм + local ABC = vectorMultiply( + vector.newVector3(C[1] - A[1], C[2] - A[2], C[3] - A[3]), + vector.newVector3(B[1] - A[1], B[2] - A[2], B[3] - A[3]) + ) + -- Рассчитываем удаленность виртуальной плоскости треугольника от старта нашего луча + local D = -ABC[1] * A[1] - ABC[2] * A[2] - ABC[3] * A[3] + local firstPart = D + ABC[1] * vector3RayStart[1] + ABC[2] * vector3RayStart[2] + ABC[3] * vector3RayStart[3] + local secondPart = ABC[1] * vector3RayStart[1] - ABC[1] * vector3RayEnd[1] + ABC[2] * vector3RayStart[2] - ABC[2] * vector3RayEnd[2] + ABC[3] * vector3RayStart[3] - ABC[3] * vector3RayEnd[3] + + -- Если наш лучик не параллелен той ебучей плоскости треугольника + if secondPart ~= 0 then + local distance = firstPart / secondPart + -- И если этот объект находится ближе к старту луча, нежели предыдущий + if (distance >= 0 and distance <= 1) and (not minimalDistance or distance < minimalDistance) then + + -- То считаем точку попадания луча в данную плоскость (но ни хуя не факт, что он попадет в треугольник!) + local S = vector.newVector3( + vector3RayStart[1] + (vector3RayEnd[1] - vector3RayStart[1]) * distance, + vector3RayStart[2] + (vector3RayEnd[2] - vector3RayStart[2]) * distance, + vector3RayStart[3] + (vector3RayEnd[3] - vector3RayStart[3]) * distance + ) + + -- Далее считаем сумму площадей параллелограммов, образованных тремя треугольниками, образовавшихся при попадании точки в треугольник + -- Нууу тип кароч смари: точка ебанула в центр, и треугольник распидорасило на три мелких. Ну, и три мелких могут образовать параллелограммы свои + -- И, кароч, если сумма трех площадей этих мелких уебков будет сильно отличаться от площади жирного треугольника, то луч не попал + -- Ну, а площадь считается через sqrt(x^2+y^2+z^2) для каждого йоба-вектора + + ---- *A *B + + + -- * Shotxyz + -- *ABC + + --- *C + + local SA = vector.newVector3(A[1] - S[1], A[2] - S[2], A[3] - S[3]) + local SB = vector.newVector3(B[1] - S[1], B[2] - S[2], B[3] - S[3]) + local SC = vector.newVector3(C[1] - S[1], C[2] - S[2], C[3] - S[3]) + + local vectorDistanceSum = getVectorDistance(vectorMultiply(SA, SB)) + getVectorDistance(vectorMultiply(SB, SC)) + getVectorDistance(vectorMultiply(SC, SA)) + local ABCDistance = getVectorDistance(ABC) + + -- Вот тут мы чекаем погрешность расчетов. Если все заебок, то кидаем этот треугольник в "проверенные"" + if math.abs(vectorDistanceSum - ABCDistance) < 1 then + closestTriangleIndex = triangleIndex + minimalDistance = distance + end + end + end + end + + return closestTriangleIndex, minimalDistance +end + +function meowEngine.sceneRaycast(scene, vector3RayStart, vector3RayEnd) + local closestObjectIndex, closestTriangleIndex, minimalDistance + + for objectIndex = 1, #scene.objects do + if scene.objects[objectIndex].triangles then + local triangleIndex, distance = meowEngine.meshRaycast(scene.objects[objectIndex], vector3RayStart, vector3RayEnd) + if triangleIndex and (not minimalDistance or distance < minimalDistance ) then + closestObjectIndex, closestTriangleIndex, minimalDistance = objectIndex, triangleIndex, distance + end + end + end + + return closestObjectIndex, closestTriangleIndex, minimalDistance +end + +-------------------------------------------------------- Intro -------------------------------------------------------- + +function meowEngine.newPolyCatMesh(vector3Position, size) + return meowEngine.newMesh( + vector3Position, + { + vector.newVector3(-1.0 * size, 0.8 * size, 0.3 * size), + vector.newVector3(-0.5 * size, 0.5 * size, 0.3 * size), + vector.newVector3(0.0 * size, 0.5 * size, 0.3 * size), + vector.newVector3(0.5 * size, 0.5 * size, 0.3 * size), + vector.newVector3(1.0 * size, 0.8 * size, 0.3 * size), + vector.newVector3(0.8 * size, 0.2 * size, 0.3 * size), + vector.newVector3(0.7 * size, -0.3 * size, 0.3 * size), + vector.newVector3(0.0 * size, -0.8 * size, 0.3 * size), + vector.newVector3(-0.7 * size, -0.3 * size, 0.3 * size), + vector.newVector3(-0.8 * size, 0.2 * size, 0.3 * size), + vector.newVector3(-0.2 * size, -0.1 * size, 0.0 * size), + vector.newVector3(0.2 * size, -0.1 * size, 0.0 * size), + vector.newVector3(0.0 * size, -0.3 * size, 0.0 * size) + }, + { + OCGL.newIndexedTriangle(1, 2, 10, materials.newSolidMaterial(0x555555)), + OCGL.newIndexedTriangle(2, 11, 10, materials.newSolidMaterial(0x6fe7fc)), + OCGL.newIndexedTriangle(2, 3, 11, materials.newSolidMaterial(0xDDDDDD)), + OCGL.newIndexedTriangle(3, 12, 11, materials.newSolidMaterial(0xDDDDDD)), + OCGL.newIndexedTriangle(3, 4, 12, materials.newSolidMaterial(0xDDDDDD)), + OCGL.newIndexedTriangle(4, 6, 12, materials.newSolidMaterial(0xa8f1fd)), + OCGL.newIndexedTriangle(4, 5, 6, materials.newSolidMaterial(0x808080)), + + OCGL.newIndexedTriangle(6, 7, 8, materials.newSolidMaterial(0xCCCCCC)), + OCGL.newIndexedTriangle(12, 6, 8, materials.newSolidMaterial(0xCCCCCC)), + OCGL.newIndexedTriangle(13, 12, 8, materials.newSolidMaterial(0xCCCCCC)), + + OCGL.newIndexedTriangle(11, 12, 13, materials.newSolidMaterial(0x555555)), + OCGL.newIndexedTriangle(11, 13, 8, materials.newSolidMaterial(0xBBBBBB)), + OCGL.newIndexedTriangle(10, 11, 8, materials.newSolidMaterial(0xBBBBBB)), + OCGL.newIndexedTriangle(10, 8, 9, materials.newSolidMaterial(0xBBBBBB)) + }, + materials.newSolidMaterial(0xFF0000) + ) +end + +function meowEngine.intro(vector3Position, size) + local GUI = require("GUI") + local scene = meowEngine.newScene(0xEEEEEE) + scene:addObject(meowEngine.newPolyCatMesh(vector3Position, size)) + scene:addObject(meowEngine.newFloatingText(vector.newVector3(vector3Position[1] + 2, vector3Position[2] - size, vector3Position[3] + size * 0.1), 0xBBBBBB, "Powered by MeowEngine™")) + + local from, to, speed = -30, 20, 4 + local transparency, transparencyStep = 0, 1 / math.abs(to - from) * speed + + scene.camera:setPosition(from, 0, -32) + while scene.camera.position[1] < to do + scene.camera:translate(speed, 0, 0) + scene.camera:lookAt(0, 0, 0) + scene:render() + if scene.camera.position[1] < to then buffer.clear(0x0, transparency) end + buffer.draw() + + transparency = transparency + transparencyStep + -- ecs.error("POS: " .. scene.camera.position[1] .. ", " .. scene.camera.position[2] .. ", " .. scene.camera.position[3] .. ", ROT: " .. math.deg(scene.camera.rotation[1]) .. ", " .. math.deg(scene.camera.rotation[2]) .. ", " .. math.deg(scene.camera.rotation[3])) + os.sleep(0.01) + end + + os.sleep(2) + + for i = 1, 0, -0.2 do + scene:render() + buffer.clear(0x0, i) + buffer.draw() + end +end + +-------------------------------------------------------- Zalupa -------------------------------------------------------- + +return meowEngine diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/MineOSCore.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/MineOSCore.lua new file mode 100755 index 00000000..0a0114fb --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/MineOSCore.lua @@ -0,0 +1,376 @@ + +require("advancedLua") +local web = require("web") +local component = require("component") +local buffer = require("doubleBuffering") +local filesystem = require("filesystem") +local unicode = require("unicode") +local image = require("image") +local color = require("color") +local MineOSPaths = require("MineOSPaths") + +---------------------------------------------------------------------------------------------------------------- + +local MineOSCore = {} +MineOSCore.localization = {} + +---------------------------------------------------------------------------------------------------------------- + +function MineOSCore.getCurrentScriptDirectory() + return filesystem.path(getCurrentScript()) +end + +function MineOSCore.getCurrentApplicationResourcesDirectory() + return MineOSCore.getCurrentScriptDirectory() .. "/Resources/" +end + +function MineOSCore.getLocalization(pathToLocalizationFolder) + local localizationFileName = pathToLocalizationFolder .. MineOSCore.properties.language .. ".lang" + if filesystem.exists(localizationFileName) then + return table.fromFile(localizationFileName) + else + error("Localization file \"" .. localizationFileName .. "\" doesn't exists") + end +end + +function MineOSCore.getCurrentApplicationLocalization() + return MineOSCore.getLocalization(MineOSCore.getCurrentApplicationResourcesDirectory() .. "Localization/") +end + +----------------------------------------------------------------------------------------------------------------------------------- + +function MineOSCore.createShortcut(where, forWhat) + filesystem.makeDirectory(filesystem.path(where)) + local file = io.open(where, "w") + file:write(forWhat) + file:close() +end + +function MineOSCore.readShortcut(path) + local file = io.open(path, "r") + local data = file:read("*a") + file:close() + + return data +end + +----------------------------------------------------------------------------------------------------------------------------------- + +function MineOSCore.saveProperties() + table.toFile(MineOSPaths.properties, MineOSCore.properties, true) +end + +function MineOSCore.loadPropeties() + local saveLater = false + + if filesystem.exists(MineOSPaths.properties) then + MineOSCore.properties = table.fromFile(MineOSPaths.properties) + else + MineOSCore.properties = {} + saveLater = true + end + + local defaultValues = { + language = "Russian", + transparencyEnabled = true, + showApplicationIcons = true, + iconHorizontalSpaceBetween = 1, + iconVerticalSpaceBetween = 1, + iconWidth = 12, + iconHeight = 6, + showExtension = false, + wallpaper = MineOSPaths.pictures .. "Raspberry.pic", + wallpaperMode = 2, + wallpaperBrightness = 1.0, + screensaver = "Matrix", + screensaverDelay = 20, + timezone = 3, + dockColor = 0xFFFFFF, + menuColor = 0xE1E1E1, + backgroundColor = 0x1E1E1E, + dockShortcuts = { + MineOSPaths.applications .. "AppMarket.app/", + MineOSPaths.applications .. "MineCode IDE.app/", + MineOSPaths.applications .. "Finder.app/", + MineOSPaths.applications .. "Photoshop.app/", + MineOSPaths.applications .. "Control.app/", + }, + network = { + users = {}, + enabled = true, + signalStrength = 512, + }, + } + + MineOSCore.associateExtension(".pic", MineOSPaths.applications .. "/Photoshop.app/Main.lua", MineOSPaths.icons .. "/Image.pic", MineOSPaths.extensionAssociations .. "Pic/ContextMenu.lua") + MineOSCore.associateExtension(".txt", MineOSPaths.editor, MineOSPaths.icons .. "/Text.pic") + MineOSCore.associateExtension(".cfg", MineOSPaths.editor, MineOSPaths.icons .. "/Config.pic") + MineOSCore.associateExtension(".3dm", MineOSPaths.applications .. "/3DPrint.app/Main.lua", MineOSPaths.icons .. "/3DModel.pic") + + MineOSCore.associateExtension("script", MineOSPaths.extensionAssociations .. "Lua/Launcher.lua", MineOSPaths.icons .. "/Script.pic", MineOSPaths.extensionAssociations .. "Lua/ContextMenu.lua") + MineOSCore.associateExtension(".lua", MineOSPaths.extensionAssociations .. "Lua/Launcher.lua", MineOSPaths.icons .. "/Lua.pic", MineOSPaths.extensionAssociations .. "Lua/ContextMenu.lua") + MineOSCore.associateExtension(".arc", MineOSPaths.extensionAssociations .. "Arc/Launcher.lua", MineOSPaths.icons .. "/Archive.pic") + + for key, value in pairs(defaultValues) do + if MineOSCore.properties[key] == nil then + MineOSCore.properties[key] = value + saveLater = true + end + end + + if saveLater then + MineOSCore.saveProperties() + end +end + +----------------------------------------------------------------------------------------------------------------------------------- + +function MineOSCore.associateExtensionLauncher(extension, pathToLauncher) + MineOSCore.properties.extensionAssociations = MineOSCore.properties.extensionAssociations or {} + MineOSCore.properties.extensionAssociations[extension] = MineOSCore.properties.extensionAssociations[extension] or {} + MineOSCore.properties.extensionAssociations[extension].launcher = pathToLauncher +end + +function MineOSCore.associateExtensionIcon(extension, pathToIcon) + MineOSCore.properties.extensionAssociations[extension] = MineOSCore.properties.extensionAssociations[extension] or {} + MineOSCore.properties.extensionAssociations[extension].icon = pathToIcon +end + +function MineOSCore.associateExtensionContextMenu(extension, pathToContextMenu) + MineOSCore.properties.extensionAssociations[extension] = MineOSCore.properties.extensionAssociations[extension] or {} + MineOSCore.properties.extensionAssociations[extension].contextMenu = pathToContextMenu +end + +function MineOSCore.associateExtension(extension, pathToLauncher, pathToIcon, pathToContextMenu) + MineOSCore.associateExtensionLauncher(extension, pathToLauncher) + MineOSCore.associateExtensionIcon(extension, pathToIcon) + MineOSCore.associateExtensionContextMenu(extension, pathToContextMenu) +end + +function MineOSCore.associationsExtensionAutomatically() + local path, extension = MineOSPaths.extensionAssociations + for file in filesystem.list(path) do + if filesystem.isDirectory(path .. file) then + extension = "." .. unicode.sub(file, 1, -2) + + if filesystem.exists(path .. file .. "ContextMenu.lua") then + MineOSCore.associateExtensionContextMenu(extension, path .. file .. "Context menu.lua") + end + + if filesystem.exists(path .. file .. "Launcher.lua") then + MineOSCore.associateExtensionLauncher(extension, path .. file .. "Launcher.lua") + end + end + end +end + +----------------------------------------------------------------------------------------------------------------------------------- + +--Функция парсинга Lua-сообщения об ошибке. Конвертирует из строки в массив. +function MineOSCore.parseErrorMessage(error, indentationWidth) + local parsedError = {} + + --Замена /r/n и табсов + error = string.gsub(error, "\r\n", "\n") + error = string.gsub(error, " ", string.rep(" ", indentationWidth or 4)) + + --Удаление энтеров + local searchFrom, starting, ending = 1 + for i = 1, unicode.len(error) do + starting, ending = string.find(error, "\n", searchFrom) + if starting then + table.insert(parsedError, unicode.sub(error, searchFrom, starting - 1)) + searchFrom = ending + 1 + else + break + end + end + + --На всякий случай, если сообщение об ошибке без энтеров вообще, т.е. однострочное + if #parsedError == 0 then table.insert(parsedError, error) end + + return parsedError +end + +function MineOSCore.call(method, ...) + local args = {...} + local function launchMethod() + method(table.unpack(args)) + end + + local function tracebackMethod(xpcallTraceback) + local traceback, info, firstMatch = tostring(xpcallTraceback) .. "\n" .. debug.traceback() + for runLevel = 0, math.huge do + info = debug.getinfo(runLevel) + if info then + if (info.what == "main" or info.what == "Lua") and info.source ~= "=machine" then + if firstMatch then + return { + path = info.source:sub(2, -1), + line = info.currentline, + traceback = traceback + } + else + firstMatch = true + end + end + else + error("Failed to get debug info for runlevel " .. runLevel) + end + end + end + + local xpcallSuccess, xpcallReason = xpcall(launchMethod, tracebackMethod) + if type(xpcallReason) == "string" or type(xpcallReason) == "nil" then xpcallReason = {path = "/lib/MineOSCore.lua", line = 1, traceback = "MineOSCore fatal error: " .. tostring(xpcallReason)} end + if not xpcallSuccess and not xpcallReason.traceback:match("^table") and not xpcallReason.traceback:match("interrupted") then + return false, xpcallReason.path, xpcallReason.line, xpcallReason.traceback + end + + return true +end + +function MineOSCore.safeLaunch(path, ...) + path = path:gsub("/+", "/") + MineOSCore.lastLaunchPath = path + + local oldResolutionWidth, oldResolutionHeight = buffer.getResolution() + local finalSuccess, finalPath, finalLine, finalTraceback = true + + if filesystem.exists(path) then + local loadSuccess, loadReason = loadfile("/" .. path) + if loadSuccess then + local success, path, line, traceback = MineOSCore.call(loadSuccess, ...) + if not success then + finalSuccess, finalPath, finalLine, finalTraceback = false, path, line, traceback + end + else + local match = string.match(loadReason, ":(%d+)%:") + finalSuccess, finalPath, finalLine, finalTraceback = false, path, tonumber(match) or 1, loadReason + end + else + require("GUI").error("Failed to safely launch file that doesn't exists: \"" .. path .. "\"") + end + + component.screen.setPrecise(false) + buffer.setResolution(oldResolutionWidth, oldResolutionHeight) + + return finalSuccess, finalPath, finalLine, finalTraceback +end + +----------------------------------------------------------------------------------------------------------------------------------- + +function MineOSCore.downloadApplication(application, language, createShortcut) + if application.type == "Application" then + filesystem.remove(application.path .. ".app") + + web.download(application.url, application.path .. ".app/Main.lua") + web.download(application.icon, application.path .. ".app/Resources/Icon.pic") + + if application.resources then + for i = 1, #application.resources do + web.download(application.resources[i].url, application.path .. ".app/Resources/" .. application.resources[i].path) + end + end + + if application.about then + web.download(application.about .. language .. ".txt", application.path .. ".app/Resources/About/" .. language .. ".txt") + end + + if application.createShortcut or createShortcut then + MineOSCore.createShortcut(MineOSPaths.desktop .. filesystem.name(application.path) .. ".lnk", application.path .. ".app/") + end + else + web.download(application.url, application.path) + end +end + +----------------------------------------------------------------------------------------------------------------------------------- + +function MineOSCore.loadImageFromString(bytes) + bytes = {string.byte(bytes, 1, #bytes)} + + local signature = string.char(bytes[1], bytes[2], bytes[3], bytes[4]) + if signature == "OCIF" then + local encodingMethod = bytes[5] + if encodingMethod == 6 then + local width, height = bytes[6], bytes[7] + local picture = {width, height} + + local i = 8 + while i <= #bytes do + local alphaSize = bytes[i] + i = i + 1 + + for a = 1, alphaSize do + local alpha = bytes[i] / 255 + local symbolSize = bit32.bor(bit32.lshift(bytes[i + 1], 8), bytes[i + 2]) + i = i + 3 + + for s = 1, symbolSize do + local utf8CharSize = 1 + for j = 7, 1, -1 do + if bit32.band(bit32.rshift(bytes[i], j), 1) == 0 then + utf8CharSize = 8 - j + break + end + end + + local symbol + if utf8CharSize == 1 then + symbol = string.char(bytes[i]) + i = i + 1 + else + symbol = string.char(table.unpack(bytes, i, i + utf8CharSize - 2)) + i = i + utf8CharSize - 1 + end + + local backgroundSize = bytes[i] + i = i + 1 + + for b = 1, backgroundSize do + local background = color.to24Bit(bytes[i]) + local foregroundSize = bytes[i + 1] + i = i + 2 + + for f = 1, foregroundSize do + local foreground = color.to24Bit(bytes[i]) + local ySize = bytes[i + 1] + i = i + 2 + + for ys = 1, ySize do + local y = bytes[i] + local xSize = bytes[i + 1] + i = i + 2 + + for xs = 1, xSize do + image.set(picture, bytes[i], y, background, foreground, alpha, symbol) + i = i + 1 + end + end + end + end + end + end + end + + return picture + else + return false, "Unsupported encoding method: " .. encodingMethod + end + else + return false, "Unsupported signature: " .. signature + end +end + +----------------------------------------------------------------------------------------------------------------------------------- + +MineOSCore.loadPropeties() + +----------------------------------------------------------------------------------------------------------------------------------- + +return MineOSCore + + + + + diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/MineOSInterface.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/MineOSInterface.lua new file mode 100755 index 00000000..d315d008 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/MineOSInterface.lua @@ -0,0 +1,1619 @@ + +local component = require("component") +local computer = require("computer") +local keyboard = require("keyboard") +local event = require("event") +local term = require("term") +local MineOSCore = require("MineOSCore") +local MineOSPaths = require("MineOSPaths") +local image = require("image") +local GUI = require("GUI") +local fs = require("filesystem") +local unicode = require("unicode") +local buffer = require("doubleBuffering") +local MineOSInterface = {} + +----------------------------------------------------------------------------------------------------------------------------------- + +MineOSInterface.iconsCache = {} +MineOSInterface.iconClickDelay = 0.2 +MineOSInterface.iconConfigFileName = ".icons" +MineOSInterface.iconImageWidth = 8 +MineOSInterface.iconImageHeight = 4 + +MineOSInterface.colors = { + windows = { + title = { + background = 0xE1E1E1, + text = 0x2D2D2D + }, + shadowTransparency = 0.5, + backgroundPanel = 0xF0F0F0, + tabBar = { + default = { + background = 0x2D2D2D, + text = 0xF0F0F0 + }, + selected = { + background = 0xF0F0F0, + text = 0x2D2D2D + } + } + } +} + +----------------------------------------------------------------------------------------------------------------------------------- + +local function calculateIconSizes() + MineOSInterface.iconHalfWidth = math.floor(MineOSCore.properties.iconWidth / 2) + MineOSInterface.iconTextHeight = MineOSCore.properties.iconHeight - MineOSInterface.iconImageHeight - 1 + MineOSInterface.iconImageHorizontalOffset = math.floor(MineOSInterface.iconHalfWidth - MineOSInterface.iconImageWidth / 2) +end + +function MineOSInterface.setIconProperties(width, height, horizontalSpaceBetween, verticalSpaceBetween) + MineOSCore.properties.iconWidth, MineOSCore.properties.iconHeight, MineOSCore.properties.iconHorizontalSpaceBetween, MineOSCore.properties.iconVerticalSpaceBetween = width, height, horizontalSpaceBetween, verticalSpaceBetween + MineOSCore.saveProperties() + calculateIconSizes() + + MineOSInterface.mainContainer.iconField:deleteIconConfig() + MineOSInterface.mainContainer.dockContainer.sort() + + computer.pushSignal("MineOSCore", "updateFileList") +end + +calculateIconSizes() + +----------------------------------------------------------------------------------------------------------------------------------- + +function MineOSInterface.clearTerminal() + local gpu = component.gpu + gpu.setBackground(0x1D1D1D) + gpu.setForeground(0xFFFFFF) + local width, height = gpu.getResolution() + gpu.fill(1, 1, width, height, " ") + term.setCursor(1, 1) +end + +function MineOSInterface.waitForPressingAnyKey() + print(" ") + print(MineOSCore.localization.pressAnyKeyToContinue) + while true do + local eventType = event.pull() + if eventType == "key_down" or eventType == "touch" then + break + end + end +end + +function MineOSInterface.launchScript(path) + MineOSInterface.clearTerminal() + if MineOSInterface.safeLaunch(path) then + MineOSInterface.waitForPressingAnyKey() + end +end + +----------------------------------------------------------------------------------------------------------------------------------- + +function MineOSInterface.cacheIconSource(name, path) + if not MineOSInterface.iconsCache[name] then + MineOSInterface.iconsCache[name] = image.load(path) + end + + return MineOSInterface.iconsCache[name] +end + +local function iconDrawNameLine(x, y, line, icon) + local lineLength = unicode.len(line) + local x = math.floor(x - lineLength / 2) + if icon.selected then + buffer.square(x, y, lineLength, 1, icon.colors.selection, 0x0, " ", icon.colors.selectionTransparency) + end + buffer.text(x, y, icon.colors.text, line) +end + +local function iconDraw(icon) + local text = MineOSCore.properties.showExtension and icon.name or icon.nameWithoutExtension + local xCenter, yText = icon.x + MineOSInterface.iconHalfWidth, icon.y + MineOSInterface.iconImageHeight + 1 + + local charIndex = 1 + for lineIndex = 1, MineOSInterface.iconTextHeight do + if lineIndex < MineOSInterface.iconTextHeight then + iconDrawNameLine(xCenter, yText, unicode.sub(text, charIndex, charIndex + icon.width - 1), icon) + charIndex, yText = charIndex + icon.width, yText + 1 + else + iconDrawNameLine(xCenter, yText, string.limit(unicode.sub(text, charIndex, -1), icon.width, "center"), icon) + end + end + + local xImage = icon.x + MineOSInterface.iconImageHorizontalOffset + if icon.selected then + local xSelection = xImage - 1 + buffer.text(xSelection, icon.y - 1, icon.colors.selection, string.rep("▄", MineOSInterface.iconImageWidth + 2), icon.colors.selectionTransparency) + buffer.text(xSelection, icon.y + MineOSInterface.iconImageHeight, icon.colors.selection, string.rep("▀", MineOSInterface.iconImageWidth + 2), icon.colors.selectionTransparency) + buffer.square(xSelection, icon.y, MineOSInterface.iconImageWidth + 2, MineOSInterface.iconImageHeight, icon.colors.selection, 0x0, " ", icon.colors.selectionTransparency) + end + + if icon.image then + if icon.cut then + if not icon.semiTransparentImage then + icon.semiTransparentImage = image.copy(icon.image) + for i = 3, #icon.semiTransparentImage, 4 do + icon.semiTransparentImage[i + 2] = icon.semiTransparentImage[i + 2] + 0.6 + if icon.semiTransparentImage[i + 2] > 1 then + icon.semiTransparentImage[i + 2] = 1 + end + end + end + + buffer.image(xImage, icon.y, icon.semiTransparentImage, true) + else + buffer.image(xImage, icon.y, icon.image) + end + elseif icon.liveImage then + icon.liveImage(xImage, icon.y) + end + + local xShortcut = xImage + MineOSInterface.iconImageWidth + if icon.isShortcut then + buffer.set(xShortcut - 1, icon.y + MineOSInterface.iconImageHeight - 1, 0xFFFFFF, 0x0, "<") + end + + if icon.windows then + buffer.text(xCenter - 1, icon.y + MineOSInterface.iconImageHeight, 0x66DBFF, "╺╸") + + local windowCount = table.size(icon.windows) + if windowCount > 1 then + + windowCount = tostring(windowCount) + local windowCountLength = #windowCount + local xTip, yTip = xShortcut - windowCountLength, icon.y + + buffer.square(xTip, yTip, windowCountLength, 1, 0xFF4940, 0xFFFFFF, " ") + buffer.text(xTip, yTip, 0xFFFFFF, windowCount) + buffer.text(xTip - 1, yTip, 0xFF4940, "⢸") + buffer.text(xTip + windowCountLength, yTip, 0xFF4940, "⡇") + buffer.text(xTip, yTip - 1, 0xFF4940, string.rep("⣀", windowCountLength)) + buffer.text(xTip, yTip + 1, 0xFF4940, string.rep("⠉", windowCountLength)) + end + end +end + +local function iconEventHandler(mainContainer, object, eventData) + if eventData[1] == "touch" then + object.lastTouchPosition = object.lastTouchPosition or {} + object.lastTouchPosition.x, object.lastTouchPosition.y = eventData[3], eventData[4] + object:moveToFront() + + if eventData[5] == 0 then + object.parent.parent.onLeftClick(object, eventData) + else + object.parent.parent.onRightClick(object, eventData) + end + elseif eventData[1] == "double_touch" and object:isClicked(eventData[3], eventData[4]) and eventData[5] == 0 then + object.parent.parent.onDoubleClick(object, eventData) + elseif eventData[1] == "drag" and object.parent.parent.iconConfigEnabled and object.lastTouchPosition then + -- Ебучие авторы мода, ну на кой хуй было делать drop-ивент без наличия drag? ПИДОРЫ + object.dragStarted = true + object.localX = object.localX + eventData[3] - object.lastTouchPosition.x + object.localY = object.localY + eventData[4] - object.lastTouchPosition.y + object.lastTouchPosition.x, object.lastTouchPosition.y = eventData[3], eventData[4] + + mainContainer:draw() + buffer.draw() + elseif eventData[1] == "drop" and object.parent.parent.iconConfigEnabled and object.dragStarted then + object.dragStarted = nil + object.parent.parent.iconConfig[object.name .. (object.isDirectory and "/" or "")] = { + x = object.localX, + y = object.localY + } + object.parent.parent:saveIconConfig() + object.lastTouchPosition = nil + end +end + +local function iconAnalyseExtension(icon) + if icon.isDirectory then + if icon.extension == ".app" then + if MineOSCore.properties.showApplicationIcons then + if fs.exists(icon.path .. "/Resources/Icon.pic") then + icon.image = image.load(icon.path .. "/Resources/Icon.pic") + elseif fs.exists(icon.path .. "/Resources/Icon.lua") then + local data, reason = loadfile(icon.path .. "/Resources/Icon.lua") + if data then + data, reason = data() + if data then + icon.liveImage = data + else + error("Failed to load live icon image: " .. tostring(reason)) + end + else + error("Failed to load live icon image: " .. tostring(reason)) + end + else + icon.image = MineOSInterface.iconsCache.fileNotExists + end + else + icon.image = MineOSInterface.iconsCache.application + end + + icon.launch = icon.launchers.application + else + icon.image = MineOSInterface.iconsCache.folder + icon.launch = icon.launchers.directory + end + else + if icon.extension == ".lnk" then + icon.shortcutPath = MineOSCore.readShortcut(icon.path) + icon.shortcutExtension = fs.extension(icon.shortcutPath) + icon.shortcutIsDirectory = fs.isDirectory(icon.shortcutPath) + icon.isShortcut = true + + local shortcutIcon = iconAnalyseExtension({ + path = icon.shortcutPath, + extension = icon.shortcutExtension, + name = icon.name, + nameWithoutExtension = icon.nameWithoutExtension, + isDirectory = icon.shortcutIsDirectory, + iconImage = icon.iconImage, + launchers = icon.launchers + }) + + icon.image = shortcutIcon.image + icon.shortcutLaunch = shortcutIcon.launch + icon.launch = icon.launchers.shortcut + + shortcutIcon = nil + elseif not fs.exists(icon.path) then + icon.image = MineOSInterface.iconsCache.fileNotExists + icon.launch = icon.launchers.corrupted + else + if MineOSCore.properties.extensionAssociations[icon.extension] then + icon.launch = icon.launchers.extension + icon.image = MineOSInterface.cacheIconSource(icon.extension, MineOSCore.properties.extensionAssociations[icon.extension].icon) + else + icon.launch = icon.launchers.script + icon.image = MineOSInterface.iconsCache.script + end + end + end + + return icon +end + +local function iconIsClicked(icon, x, y) + return + x >= icon.x + MineOSInterface.iconImageHorizontalOffset and + y >= icon.y and + x <= icon.x + MineOSInterface.iconImageHorizontalOffset + MineOSInterface.iconImageWidth - 1 and + y <= icon.y + MineOSInterface.iconImageHeight - 1 + or + x >= icon.x and + y >= icon.y + MineOSInterface.iconImageHeight + 1 and + x <= icon.x + MineOSCore.properties.iconWidth - 1 and + y <= icon.y + MineOSCore.properties.iconHeight - 1 +end + +function MineOSInterface.icon(x, y, path, textColor, selectionColor) + local icon = GUI.object(x, y, MineOSCore.properties.iconWidth, MineOSCore.properties.iconHeight) + + icon.colors = { + text = textColor, + selection = selectionColor, + selectionTransparency = 0.6 + } + + icon.path = path + icon.extension = fs.extension(icon.path) or "script" + icon.name = fs.name(path) + icon.nameWithoutExtension = fs.hideExtension(icon.name) + icon.isDirectory = fs.isDirectory(icon.path) + icon.isShortcut = false + icon.selected = false + + icon.isClicked = iconIsClicked + icon.draw = iconDraw + icon.launchers = table.copy(MineOSInterface.iconLaunchers) + icon.analyseExtension = iconAnalyseExtension + + return icon +end + +local function iconFieldUpdate(iconField) + iconField.backgroundObject.width, iconField.backgroundObject.height = iconField.width, iconField.height + iconField.iconsContainer.width, iconField.iconsContainer.height = iconField.width, iconField.height + + iconField.iconCount.horizontal = math.floor((iconField.width - iconField.xOffset) / (MineOSCore.properties.iconWidth + MineOSCore.properties.iconHorizontalSpaceBetween)) + iconField.iconCount.vertical = math.floor((iconField.height - iconField.yOffset) / (MineOSCore.properties.iconHeight + MineOSCore.properties.iconVerticalSpaceBetween)) + iconField.iconCount.total = iconField.iconCount.horizontal * iconField.iconCount.vertical + + return iconField +end + +local function iconFieldLoadIconConfig(iconField) + if fs.exists(iconField.workpath .. MineOSInterface.iconConfigFileName) then + iconField.iconConfig = table.fromFile(iconField.workpath .. MineOSInterface.iconConfigFileName) + else + iconField.iconConfig = {} + end +end + +local function iconFieldSaveIconConfig(iconField) + table.toFile(iconField.workpath .. MineOSInterface.iconConfigFileName, iconField.iconConfig) +end + +local function iconFieldDeleteIconConfig(iconField) + iconField.iconConfig = {} + fs.remove(iconField.workpath .. MineOSInterface.iconConfigFileName, iconField.iconConfig) +end + +----------------------------------------------------------------------------------------------------------------------------------- + +MineOSInterface.iconLaunchers = {} + +function MineOSInterface.iconLaunchers.application(icon) + local pathToAboutFile = icon.path .. "/resources/About/" .. MineOSCore.properties.language .. ".txt" + if MineOSCore.properties.showHelpOnApplicationStart and fs.exists(pathToAboutFile) then + local container = MineOSInterface.addUniversalContainer(MineOSInterface.mainContainer, MineOSCore.localization.applicationHelp .. " \"" .. fs.name(icon.path) .. "\"") + + local lines = {} + for line in io.lines(pathToAboutFile) do + table.insert(lines, line) + end + + container.layout:addChild(GUI.textBox(1, 1, 50, 1, nil, 0xcccccc, lines, 1, 0, 0, true, true)) + local button = container.layout:addChild(GUI.button(1, 1, 30, 1, 0xE1E1E1, 0x2D2D2D, 0xAAAAAA, 0x2D2D2D, MineOSCore.localization.dontShowAnymore)) + + local function onExit() + container:delete() + MineOSInterface.safeLaunch((icon.shortcutPath or icon.path) .. "/Main.lua") + end + + container.panel.eventHandler = function(mainContainer, object, eventData) + if eventData[1] == "touch" then + onExit() + end + end + + button.onTouch = function() + MineOSCore.properties.showHelpOnApplicationStart = false + MineOSCore.saveProperties() + onExit() + end + else + MineOSInterface.safeLaunch(icon.path .. "/Main.lua") + end +end + +function MineOSInterface.iconLaunchers.directory(icon) + icon.parent.parent:setWorkpath(icon.path) +end + +function MineOSInterface.iconLaunchers.shortcut(icon) + local oldPath = icon.path + icon.path = icon.shortcutPath + icon:shortcutLaunch() + icon.path = oldPath +end + +function MineOSInterface.iconLaunchers.corrupted(icon) + GUI.error("Application is corrupted") +end + +function MineOSInterface.iconLaunchers.extension(icon) + MineOSInterface.safeLaunch(MineOSCore.properties.extensionAssociations[icon.extension].launcher, icon.path, "-o") +end + +function MineOSInterface.iconLaunchers.script(icon) + MineOSInterface.launchScript(icon.path) +end + +function MineOSInterface.iconLaunchers.showPackageContent(icon) + icon.parent.parent:setWorkpath(icon.path) + icon.parent.parent:updateFileList() + icon:getFirstParent():draw() + buffer.draw() +end + +function MineOSInterface.iconLaunchers.showContainingFolder(icon) + icon.parent.parent:setWorkpath(fs.path(icon.shortcutPath)) + icon.parent.parent:updateFileList() + icon:getFirstParent():draw() + buffer.draw() +end + +----------------------------------------------------------------------------------------------------------------------------------- + +local function getCykaIconPosition(iconField) + local y = iconField.yOffset + for i = 1, #iconField.iconsContainer.children do + y = math.max(y, iconField.iconsContainer.children[i].localY) + end + + local x = iconField.xOffset + for i = 1, #iconField.iconsContainer.children do + if iconField.iconsContainer.children[i].localY == y then + x = math.max(x, iconField.iconsContainer.children[i].localX) + end + end + + x = x + MineOSCore.properties.iconWidth + MineOSCore.properties.iconHorizontalSpaceBetween + if x + MineOSCore.properties.iconWidth + MineOSCore.properties.iconHorizontalSpaceBetween > iconField.iconsContainer.width then + x, y = iconField.xOffset, y + MineOSCore.properties.iconHeight + MineOSCore.properties.iconVerticalSpaceBetween + end + + return x, y +end + +local function iconFieldUpdateFileList(iconField) + -- GUI.error(debug.traceback()) + iconField.fileList = fs.sortedList(iconField.workpath, MineOSCore.properties.sortingMethod or "type", MineOSCore.properties.showHiddenFiles, iconField.filenameMatcher, false) + iconField:update() + + -- Грузим инфу об иконочках + if iconField.iconConfigEnabled then + iconField:loadIconConfig() + end + + local configList, notConfigList = {}, {} + for i = iconField.fromFile, iconField.fromFile + iconField.iconCount.total - 1 do + if iconField.fileList[i] then + if iconField.iconConfigEnabled and iconField.iconConfig[iconField.fileList[i]] then + table.insert(configList, iconField.fileList[i]) + else + table.insert(notConfigList, iconField.fileList[i]) + end + else + break + end + end + + local function checkClipboard(icon) + if MineOSCore.clipboard and MineOSCore.clipboard.cut then + for i = 1, #MineOSCore.clipboard do + if MineOSCore.clipboard[i] == icon.path then + icon.cut = true + end + end + end + end + + -- Заполнение дочернего контейнера + iconField.iconsContainer:deleteChildren() + for i = 1, #configList do + local icon = iconField.iconsContainer:addChild(MineOSInterface.icon( + iconField.iconConfig[configList[i]].x, + iconField.iconConfig[configList[i]].y, + iconField.workpath .. configList[i], + iconField.colors.text, + iconField.colors.selection + )) + + checkClipboard(icon) + icon.eventHandler = iconEventHandler + icon.launchers = iconField.launchers + icon:analyseExtension() + end + + local x, y + if #configList > 0 then + x, y = getCykaIconPosition(iconField, configList) + else + x, y = iconField.xOffset, iconField.yOffset + end + for i = 1, #notConfigList do + local icon = iconField.iconsContainer:addChild(MineOSInterface.icon(x, y, iconField.workpath .. notConfigList[i], iconField.colors.text, iconField.colors.selection)) + iconField.iconConfig[notConfigList[i]] = {x = x, y = y} + + checkClipboard(icon) + icon.eventHandler = iconEventHandler + icon.launchers = iconField.launchers + icon:analyseExtension() + + x = x + MineOSCore.properties.iconWidth + MineOSCore.properties.iconHorizontalSpaceBetween + if x + MineOSCore.properties.iconWidth + MineOSCore.properties.iconHorizontalSpaceBetween - 1 > iconField.iconsContainer.width then + x, y = iconField.xOffset, y + MineOSCore.properties.iconHeight + MineOSCore.properties.iconVerticalSpaceBetween + end + end + + if iconField.iconConfigEnabled then + iconField:saveIconConfig() + end + + return iconField +end + +local function iconFieldBackgroundObjectEventHandler(mainContainer, object, eventData) + if eventData[1] == "touch" then + if eventData[5] == 0 then + object.parent:deselectAll() + object.parent.selection = { + x1 = eventData[3], + y1 = eventData[4] + } + + mainContainer:draw() + buffer.draw() + else + local menu = MineOSInterface.contextMenu(eventData[3], eventData[4]) + + local subMenu = menu:addSubMenu(MineOSCore.localization.create) + + subMenu:addItem(MineOSCore.localization.newFile).onTouch = function() + MineOSInterface.newFile(MineOSInterface.mainContainer, object.parent, eventData[3], eventData[4], object.parent.workpath) + end + + subMenu:addItem(MineOSCore.localization.newFolder).onTouch = function() + MineOSInterface.newFolder(MineOSInterface.mainContainer, object.parent, eventData[3], eventData[4], object.parent.workpath) + end + + subMenu:addItem(MineOSCore.localization.newFileFromURL, not component.isAvailable("internet")).onTouch = function() + MineOSInterface.newFileFromURL(MineOSInterface.mainContainer, object.parent, eventData[3], eventData[4], object.parent.workpath) + end + + subMenu:addSeparator() + + subMenu:addItem(MineOSCore.localization.newApplication).onTouch = function() + MineOSInterface.newApplication(MineOSInterface.mainContainer, object.parent, eventData[3], eventData[4], object.parent.workpath) + end + + menu:addSeparator() + + local subMenu = menu:addSubMenu(MineOSCore.localization.sortBy) + + subMenu:addItem(MineOSCore.localization.sortByName).onTouch = function() + object.parent:deleteIconConfig() + + MineOSCore.properties.sortingMethod = "name" + MineOSCore.saveProperties() + computer.pushSignal("MineOSCore", "updateFileList") + end + + + subMenu:addItem(MineOSCore.localization.sortByDate).onTouch = function() + object.parent:deleteIconConfig() + + MineOSCore.properties.sortingMethod = "date" + MineOSCore.saveProperties() + computer.pushSignal("MineOSCore", "updateFileList") + end + + subMenu:addItem(MineOSCore.localization.sortByType).onTouch = function() + object.parent:deleteIconConfig() + + MineOSCore.properties.sortingMethod = "type" + MineOSCore.saveProperties() + computer.pushSignal("MineOSCore", "updateFileList") + end + + menu:addItem(MineOSCore.localization.sortAutomatically).onTouch = function() + object.parent:deleteIconConfig() + computer.pushSignal("MineOSCore", "updateFileList") + end + + menu:addItem(MineOSCore.localization.update).onTouch = function() + computer.pushSignal("MineOSCore", "updateFileList") + end + + menu:addSeparator() + + menu:addItem(MineOSCore.localization.paste, not MineOSCore.clipboard).onTouch = function() + local i = 1 + while i <= #MineOSCore.clipboard do + if fs.exists(MineOSCore.clipboard[i]) then + i = i + 1 + else + table.remove(MineOSCore.clipboard, i) + end + end + + MineOSInterface.copy(MineOSCore.clipboard, object.parent.workpath) + + if MineOSCore.clipboard.cut then + for i = 1, #MineOSCore.clipboard do + fs.remove(MineOSCore.clipboard[i]) + end + MineOSCore.clipboard = nil + end + + computer.pushSignal("MineOSCore", "updateFileList") + end + + menu:show() + end + elseif eventData[1] == "drag" then + if object.parent.selection then + object.parent.selection.x2 = eventData[3] + object.parent.selection.y2 = eventData[4] + object:moveToFront() + + mainContainer:draw() + buffer.draw() + end + elseif eventData[1] == "drop" then + object.parent.selection = nil + object:moveToBack() + + mainContainer:draw() + buffer.draw() + end +end + +local function iconFieldBackgroundObjectDraw(object) + if object.parent.selection and object.parent.selection.x2 then + local x1, y1, x2, y2 = object.parent.selection.x1, object.parent.selection.y1, object.parent.selection.x2, object.parent.selection.y2 + + if x2 < x1 then + x1, x2 = x2, x1 + end + + if y2 < y1 then + y1, y2 = y2, y1 + end + + buffer.square(x1, y1, x2 - x1 + 1, y2 - y1 + 1, object.parent.colors.selection, 0x0, " ", 0.6) + + for i = 1, #object.parent.iconsContainer.children do + local xCenter, yCenter = object.parent.iconsContainer.children[i].x + MineOSCore.properties.iconWidth / 2, object.parent.iconsContainer.children[i].y + MineOSCore.properties.iconHeight / 2 + object.parent.iconsContainer.children[i].selected = + xCenter >= x1 and + xCenter <= x2 and + yCenter >= y1 and + yCenter <= y2 + end + end +end + +local function iconFieldDeselectAll(iconField) + for i = 1, #iconField.iconsContainer.children do + iconField.iconsContainer.children[i].selected = false + end +end + +local function iconFieldGetSelectedIcons(iconField) + local selectedIcons = {} + + for i = 1, #iconField.iconsContainer.children do + if iconField.iconsContainer.children[i].selected then + table.insert(selectedIcons, iconField.iconsContainer.children[i]) + end + end + + return selectedIcons +end + +local function iconFieldSetWorkpath(iconField, path) + iconField.workpath = path + iconField.filenameMatcher = nil + iconField.fromFile = 1 + + return iconField +end + +function MineOSInterface.iconField(x, y, width, height, xOffset, yOffset, textColor, selectionColor, workpath) + local iconField = GUI.container(x, y, width, height) + + iconField.colors = { + text = textColor, + selection = selectionColor + } + + iconField.iconConfig = {} + iconField.iconCount = {} + iconField.fileList = {} + iconField.fromFile = 1 + iconField.iconConfigEnabled = false + iconField.xOffset = xOffset + iconField.yOffset = yOffset + iconField.workpath = workpath + iconField.filenameMatcher = nil + + iconField.backgroundObject = iconField:addChild(GUI.object(1, 1, width, height)) + iconField.backgroundObject.eventHandler = iconFieldBackgroundObjectEventHandler + iconField.backgroundObject.draw = iconFieldBackgroundObjectDraw + + iconField.iconsContainer = iconField:addChild(GUI.container(1, 1, width, height)) + + iconField.updateFileList = iconFieldUpdateFileList + iconField.update = iconFieldUpdate + iconField.eventHandler = iconFieldEventHandler + iconField.deselectAll = iconFieldDeselectAll + iconField.loadIconConfig = iconFieldLoadIconConfig + iconField.saveIconConfig = iconFieldSaveIconConfig + iconField.deleteIconConfig = iconFieldDeleteIconConfig + iconField.getSelectedIcons = iconFieldGetSelectedIcons + iconField.setWorkpath = iconFieldSetWorkpath + + iconField.onLeftClick = MineOSInterface.iconLeftClick + iconField.onRightClick = MineOSInterface.iconRightClick + iconField.onDoubleClick = MineOSInterface.iconDoubleClick + + iconField.launchers = table.copy(MineOSInterface.iconLaunchers) + + return iconField +end + +---------------------------------------------------------------------------------------------------------------- + +function MineOSInterface.contextMenu(...) + local menu = GUI.contextMenu(...) + + menu.colors.transparency.background = MineOSCore.properties.transparencyEnabled and GUI.colors.contextMenu.transparency.background + menu.colors.transparency.shadow = MineOSCore.properties.transparencyEnabled and GUI.colors.contextMenu.transparency.shadow + + return menu +end + +function MineOSInterface.iconLeftClick(icon, eventData) + if not keyboard.isKeyDown(29) and not keyboard.isKeyDown(219) then + icon.parent.parent:deselectAll() + end + icon.selected = true + + MineOSInterface.OSDraw() +end + +function MineOSInterface.iconDoubleClick(icon, eventData) + icon.selected = false + icon:launch() + MineOSInterface.OSDraw() + -- computer.pushSignal("MineOSCore", "updateFileList") +end + +function MineOSInterface.iconRightClick(icon, eventData) + icon.selected = true + MineOSInterface.OSDraw() + + local selectedIcons = icon.parent.parent:getSelectedIcons() + + local menu = MineOSInterface.contextMenu(eventData[3], eventData[4]) + if #selectedIcons == 1 then + if icon.isDirectory then + if icon.extension == ".app" then + menu:addItem(MineOSCore.localization.showPackageContent).onTouch = function() + icon.parent.parent.launchers.showPackageContent(icon) + end + menu:addItem(MineOSCore.localization.launchWithArguments).onTouch = function() + MineOSInterface.launchWithArguments(MineOSInterface.mainContainer, icon.path) + end + + menu:addSeparator() + end + + -- if icon.extension ~= ".app" then + -- menu:addItem(MineOSCore.localization.addToFavourites).onTouch = function() + + -- end + -- end + + + else + if icon.isShortcut then + menu:addItem(MineOSCore.localization.editShortcut).onTouch = function() + MineOSInterface.editShortcut(MineOSInterface.mainContainer, icon.path) + computer.pushSignal("MineOSCore", "updateFileList") + end + + menu:addItem(MineOSCore.localization.showContainingFolder).onTouch = function() + icon.parent.parent.launchers.showContainingFolder(icon) + end + + menu:addSeparator() + else + if MineOSCore.properties.extensionAssociations[icon.extension] and MineOSCore.properties.extensionAssociations[icon.extension].contextMenu then + pcall(loadfile(MineOSCore.properties.extensionAssociations[icon.extension].contextMenu), icon, menu) + menu:addSeparator() + end + + -- local subMenu = menu:addSubMenu(MineOSCore.localization.openWith) + -- local fileList = fs.sortedList(MineOSPaths.applications, "name") + -- subMenu:addItem(MineOSCore.localization.select) + -- subMenu:addSeparator() + -- for i = 1, #fileList do + -- subMenu:addItem(fileList[i].nameWithoutExtension) + -- end + end + end + end + + if #selectedIcons > 1 then + menu:addItem(MineOSCore.localization.newFolderFromChosen .. " (" .. #selectedIcons .. ")").onTouch = function() + MineOSInterface.newFolderFromChosen(MineOSInterface.mainContainer, icon.parent.parent, eventData[3], eventData[4], selectedIcons) + end + menu:addSeparator() + end + + menu:addItem(MineOSCore.localization.archive .. (#selectedIcons > 1 and " (" .. #selectedIcons .. ")" or "")).onTouch = function() + local itemsToArchive = {} + for i = 1, #selectedIcons do + table.insert(itemsToArchive, selectedIcons[i].path) + end + + local success, reason = require("archive").pack(fs.path(icon.path) .. "/Archive.arc", itemsToArchive) + if not success then + GUI.error(reason) + end + + computer.pushSignal("MineOSCore", "updateFileList") + end + + local function cutOrCopy(cut) + for i = 1, #icon.parent.children do + icon.parent.children[i].cut = nil + end + + MineOSCore.clipboard = {cut = cut} + for i = 1, #selectedIcons do + selectedIcons[i].cut = cut + table.insert(MineOSCore.clipboard, selectedIcons[i].path) + end + end + + menu:addItem(MineOSCore.localization.cut).onTouch = function() + cutOrCopy(true) + end + + menu:addItem(MineOSCore.localization.copy).onTouch = function() + cutOrCopy() + end + + if not icon.isShortcut or #selectedIcons > 1 then + local subMenu = menu:addSubMenu(MineOSCore.localization.createShortcut) + + subMenu:addItem(MineOSCore.localization.inCurrentDirectory).onTouch = function() + for i = 1, #selectedIcons do + if not selectedIcons[i].isShortcut then + MineOSCore.createShortcut( + fs.path(selectedIcons[i].path) .. "/" .. selectedIcons[i].nameWithoutExtension .. ".lnk", + selectedIcons[i].path + ) + end + end + + computer.pushSignal("MineOSCore", "updateFileList") + end + + subMenu:addItem(MineOSCore.localization.onDesktop).onTouch = function() + for i = 1, #selectedIcons do + if not selectedIcons[i].isShortcut then + MineOSCore.createShortcut( + MineOSPaths.desktop .. "/" .. selectedIcons[i].nameWithoutExtension .. ".lnk", + selectedIcons[i].path + ) + end + end + + computer.pushSignal("MineOSCore", "updateFileList") + end + end + + if #selectedIcons == 1 then + menu:addItem(MineOSCore.localization.rename).onTouch = function() + MineOSInterface.rename(MineOSInterface.mainContainer, icon.path) + end + end + + menu:addItem(MineOSCore.localization.delete).onTouch = function() + for i = 1, #selectedIcons do + if fs.path(selectedIcons[i].path) == MineOSPaths.trash then + fs.remove(selectedIcons[i].path) + else + local newName = MineOSPaths.trash .. selectedIcons[i].name + local clearName = selectedIcons[i].nameWithoutExtension + local repeats = 1 + while fs.exists(newName) do + newName, repeats = MineOSPaths.trash .. clearName .. string.rep("-copy", repeats) .. selectedIcons[i].extension, repeats + 1 + end + fs.rename(selectedIcons[i].path, newName) + end + end + + computer.pushSignal("MineOSCore", "updateFileList") + end + + menu:addSeparator() + + if #selectedIcons == 1 then + menu:addItem(MineOSCore.localization.addToDock).onTouch = function() + MineOSInterface.mainContainer.dockContainer.addIcon(icon.path).keepInDock = true + MineOSInterface.mainContainer.dockContainer.saveToOSSettings() + end + end + + menu:addItem(MineOSCore.localization.properties).onTouch = function() + for i = 1, #selectedIcons do + MineOSInterface.propertiesWindow(eventData[3], eventData[4], 40, selectedIcons[i]) + end + end + + menu:show() + + icon.parent.parent:deselectAll() + MineOSInterface.OSDraw() +end + +----------------------------------------------------------------------------------------------------------------------------------- + +function MineOSInterface.addUniversalContainer(parentContainer, title) + local container = parentContainer:addChild(GUI.container(1, 1, parentContainer.width, parentContainer.height)) + + container.panel = container:addChild(GUI.panel(1, 1, container.width, container.height, MineOSCore.properties.transparencyEnabled and 0x0 or MineOSCore.properties.backgroundColor, MineOSCore.properties.transparencyEnabled and 0.2)) + container.layout = container:addChild(GUI.layout(1, 1, container.width, container.height, 3, 1)) + container.layout.defaultColumn = 2 + container.layout:setColumnWidth(1, GUI.sizePolicies.percentage, 0.375) + container.layout:setColumnWidth(2, GUI.sizePolicies.percentage, 0.25) + container.layout:setColumnWidth(3, GUI.sizePolicies.percentage, 0.375) + container.layout:setCellFitting(2, 1, true, false) + + if title then + container.label = container.layout:addChild(GUI.label(1, 1, 1, 1, 0xE1E1E1, title)):setAlignment(GUI.alignment.horizontal.center, GUI.alignment.vertical.top) + end + + container.panel.eventHandler = function(mainContainer, object, eventData) + if eventData[1] == "touch" then + container:delete() + mainContainer:draw() + buffer.draw() + end + end + + return container +end + +local function addUniversalContainerWithInputTextBox(parentWindow, text, title, placeholder) + local container = MineOSInterface.addUniversalContainer(parentWindow, title) + + container.inputField = container.layout:addChild(GUI.input(1, 1, 36, 3, 0xE1E1E1, 0x666666, 0x666666, 0xE1E1E1, 0x2D2D2D, text, placeholder, false)) + container.label = container.layout:addChild(GUI.label(1, 1, 36, 1, 0xFF4940, MineOSCore.localization.file .. " " .. MineOSCore.localization.alreadyExists)):setAlignment(GUI.alignment.horizontal.center, GUI.alignment.vertical.top) + container.label.hidden = true + + return container +end + +local function checkFileToExists(container, path) + if fs.exists(path) then + container.label.hidden = false + container.parent:draw() + buffer.draw() + else + container:delete() + fs.makeDirectory(fs.path(path)) + return true + end +end + +local function checkIconConfigCanSavePosition(iconField, x, y, filename) + if iconField.iconConfigEnabled then + iconField.iconConfig[filename] = { x = x, y = y } + iconField:saveIconConfig() + end +end + +function MineOSInterface.newFile(parentWindow, iconField, x, y, path) + local container = addUniversalContainerWithInputTextBox(parentWindow, nil, MineOSCore.localization.newFile, MineOSCore.localization.fileName) + + container.inputField.onInputFinished = function() + if checkFileToExists(container, path .. container.inputField.text) then + local file = io.open(path .. container.inputField.text, "w") + file:close() + checkIconConfigCanSavePosition(iconField, x, y, container.inputField.text) + MineOSInterface.safeLaunch(MineOSPaths.editor, path .. container.inputField.text) + computer.pushSignal("MineOSCore", "updateFileList") + end + end + + parentWindow:draw() + buffer.draw() +end + +function MineOSInterface.newFolder(parentWindow, iconField, x, y, path) + local container = addUniversalContainerWithInputTextBox(parentWindow, nil, MineOSCore.localization.newFolder, MineOSCore.localization.folderName) + + container.inputField.onInputFinished = function() + if checkFileToExists(container, path .. container.inputField.text) then + fs.makeDirectory(path .. container.inputField.text) + checkIconConfigCanSavePosition(iconField, x, y, container.inputField.text .. "/") + computer.pushSignal("MineOSCore", "updateFileList") + end + end + + parentWindow:draw() + buffer.draw() + + return container +end + +function MineOSInterface.newFileFromURL(parentWindow, iconField, x, y, path) + local container = addUniversalContainerWithInputTextBox(parentWindow, nil, "Загрузить файл по URL", MineOSCore.localization.fileName) + + container.inputFieldURL = container.layout:addChild(GUI.input(1, 1, 36, 3, 0xE1E1E1, 0x666666, 0x666666, 0xE1E1E1, 0x2D2D2D, nil, "URL", false)) + container.inputField.onInputFinished = function() + if container.inputField.text then + if fs.exists(path .. container.inputField.text) then + container.label.hidden = false + parentWindow:draw() + buffer.draw() + else + if container.inputFieldURL.text then + local success, reason = require("web").download(container.inputFieldURL.text, path .. container.inputField.text) + if not success then + GUI.error(reason) + end + + container:delete() + checkIconConfigCanSavePosition(iconField, x, y, container.inputField.text) + computer.pushSignal("MineOSCore", "updateFileList") + end + end + end + end + container.inputFieldURL.onInputFinished = container.inputField.onInputFinished + + parentWindow:draw() + buffer.draw() +end + +function MineOSInterface.newApplication(parentWindow, iconField, x, y, path) + local container = addUniversalContainerWithInputTextBox(parentWindow, nil, MineOSCore.localization.newApplication, MineOSCore.localization.applicationName) + + local filesystemChooser = container.layout:addChild(GUI.filesystemChooser(1, 1, 36, 3, 0xE1E1E1, 0x666666, 0x444444, 0x999999, nil, MineOSCore.localization.open, MineOSCore.localization.cancel, MineOSCore.localization.iconPath, "/")) + filesystemChooser:addExtensionFilter(".pic") + filesystemChooser:moveBackward() + + container.panel.eventHandler = function(mainContainer, object, eventData) + if eventData[1] == "touch" then + container:delete() + + if container.inputField.text then + local finalPath = path .. container.inputField.text .. ".app/" + if checkFileToExists(container, finalPath) then + fs.makeDirectory(finalPath .. "/Resources/") + fs.copy(filesystemChooser.path or MineOSPaths.icons .. "SampleIcon.pic", finalPath .. "/Resources/Icon.pic") + + local file = io.open(finalPath .. "Main.lua", "w") + file:write("require(\"GUI\").error(\"Hello world\")") + file:close() + + checkIconConfigCanSavePosition(iconField, x, y, container.inputField.text .. ".app/") + computer.pushSignal("MineOSCore", "updateFileList") + end + else + parentWindow:draw() + buffer.draw() + end + end + end + + parentWindow:draw() + buffer.draw() +end + +function MineOSInterface.newFolderFromChosen(parentWindow, iconField, x, y, selectedIcons) + local container = addUniversalContainerWithInputTextBox(parentWindow, nil, MineOSCore.localization.newFolderFromChosen .. " (" .. #selectedIcons .. ")", MineOSCore.localization.folderName) + + container.inputField.onInputFinished = function() + local path = fs.path(selectedIcons[1].path) .. container.inputField.text + if checkFileToExists(container, path) then + fs.makeDirectory(path) + for i = 1, #selectedIcons do + fs.rename(selectedIcons[i].path, path .. "/" .. selectedIcons[i].name) + end + + checkIconConfigCanSavePosition(iconField, x, y, container.inputField.text) + computer.pushSignal("MineOSCore", "updateFileList") + end + end + + parentWindow:draw() + buffer.draw() + + return container +end + +function MineOSInterface.rename(parentWindow, path) + local container = addUniversalContainerWithInputTextBox(parentWindow, fs.name(path), MineOSCore.localization.rename, MineOSCore.localization.newName) + + container.inputField.onInputFinished = function() + if checkFileToExists(container, fs.path(path) .. container.inputField.text) then + fs.rename(path, fs.path(path) .. container.inputField.text) + computer.pushSignal("MineOSCore", "updateFileList") + end + end + + parentWindow:draw() + buffer.draw() +end + +function MineOSInterface.editShortcut(parentWindow, path) + local text = MineOSCore.readShortcut(path) + local container = addUniversalContainerWithInputTextBox(parentWindow, text, MineOSCore.localization.editShortcut, MineOSCore.localization.rename) + + container.panel.eventHandler = nil + container.inputField.onInputFinished = function() + if fs.exists(container.inputField.text) then + MineOSCore.createShortcut(path, container.inputField.text) + container:delete() + computer.pushSignal("MineOSCore", "updateFileList") + else + container.label.text = MineOSCore.localization.shortcutIsCorrupted + container.label.hidden = false + MineOSInterface.OSDraw() + end + end + + parentWindow:draw() + buffer.draw() +end + +function MineOSInterface.launchWithArguments(parentWindow, path) + local container = addUniversalContainerWithInputTextBox(parentWindow, nil, MineOSCore.localization.launchWithArguments) + + container.inputField.onInputFinished = function() + local args = {} + if container.inputField.text then + for arg in container.inputField.text:gmatch("[^%s]+") do + table.insert(args, arg) + end + end + container:delete() + + MineOSInterface.clearTerminal() + if MineOSInterface.safeLaunch(path, table.unpack(args)) then + MineOSInterface.waitForPressingAnyKey() + end + + parentWindow:draw() + buffer.draw(true) + end +end + +----------------------------------------- Windows patterns ----------------------------------------- + +local function windowResize(window, width, height) + window.width, window.height = width, height + if window.onResize then window.onResize(width, height) end + + return window +end + +function MineOSInterface.addWindow(window) + window.x = math.floor(MineOSInterface.mainContainer.windowsContainer.width / 2 - window.width / 2) + window.y = math.floor(MineOSInterface.mainContainer.windowsContainer.height / 2 - window.height / 2) + + MineOSInterface.mainContainer.windowsContainer:addChild(window) + + -- Получаем путь исполняемого файла + local dockPath = MineOSCore.lastLaunchPath or "/OS.lua" + local dockPathPath = fs.path(dockPath) + if fs.extension(dockPathPath) == ".app" then + dockPath = dockPathPath + end + -- Хуячим иконку в докыч, если такой еще не существует + local dockIcon + for i = 1, #MineOSInterface.mainContainer.dockContainer.children do + if MineOSInterface.mainContainer.dockContainer.children[i].path == dockPath then + dockIcon = MineOSInterface.mainContainer.dockContainer.children[i] + break + end + end + dockIcon = dockIcon or MineOSInterface.mainContainer.dockContainer.addIcon(dockPath, window) + -- Ебурим ссылку на окно в иконку + dockIcon.windows = dockIcon.windows or {} + dockIcon.windows[window] = true + + -- Смещаем окно правее и ниже, если уже есть открыте окна этой софтины + local lastIndex + for i = #MineOSInterface.mainContainer.windowsContainer.children, 1, -1 do + if MineOSInterface.mainContainer.windowsContainer.children[i] ~= window and dockIcon.windows[MineOSInterface.mainContainer.windowsContainer.children[i]] then + lastIndex = i + break + end + end + if lastIndex then + window.localX, window.localY = MineOSInterface.mainContainer.windowsContainer.children[lastIndex].localX + 4, MineOSInterface.mainContainer.windowsContainer.children[lastIndex].localY + 2 + end + + window.resize = windowResize + window.close = function(window) + local sameIconExists = false + for i = 1, #MineOSInterface.mainContainer.dockContainer.children do + if + MineOSInterface.mainContainer.dockContainer.children[i].path == dockPath and + MineOSInterface.mainContainer.dockContainer.children[i].windows and + table.size(MineOSInterface.mainContainer.dockContainer.children[i].windows) > 1 + then + MineOSInterface.mainContainer.dockContainer.children[i].windows[window] = nil + sameIconExists = true + break + end + end + + if not sameIconExists then + dockIcon.windows = nil + if not dockIcon.keepInDock then + dockIcon:delete() + MineOSInterface.mainContainer.dockContainer.sort() + end + end + + window:delete() + end + + window.maximize = function(window) + if window.maximized then + window.localX = window.oldGeometry.x + window.localY = window.oldGeometry.y + window:resize(window.oldGeometry.width, window.oldGeometry.height) + else + window.oldGeometry = { + x = window.localX, + y = window.localY, + width = window.width, + height = window.height + } + window.localX, window.localY = 1, 1 + window:resize(window.parent.width, window.parent.height) + end + + window.maximized = not window.maximized + MineOSInterface.OSDraw() + end + + window.minimize = function(window) + window.hidden = true + MineOSInterface.OSDraw() + end + + if window.actionButtons then + window.actionButtons.close.onTouch = function() + window.close(window) + end + window.actionButtons.maximize.onTouch = function() + window.maximize(window) + end + window.actionButtons.minimize.onTouch = function() + window.minimize(window) + end + end + + MineOSCore.lastLaunchPath = nil + + return MineOSInterface.mainContainer, window +end + +----------------------------------------------------------------------------------------------------------------------------------- + +local function addKeyAndValue(window, x, y, key, value) + x = x + window:addChild(GUI.label(x, y, unicode.len(key) + 1, 1, 0x333333, key .. ":")).width + 1 + return window:addChild(GUI.label(x, y, unicode.len(value), 1, 0x555555, value)) +end + +function MineOSInterface.propertiesWindow(x, y, width, icon) + local mainContainer, window = MineOSInterface.addWindow(MineOSInterface.titledWindow(x, y, width, 1, package.loaded.MineOSCore.localization.properties)) + + window.backgroundPanel.colors.transparency = 0.2 + window:addChild(GUI.image(2, 3, icon.image)) + + local x, y = 11, 3 + addKeyAndValue(window, x, y, package.loaded.MineOSCore.localization.type, icon.extension and icon.extension or (icon.isDirectory and package.loaded.MineOSCore.localization.folder or package.loaded.MineOSCore.localization.unknown)); y = y + 1 + local fileSizeLabel = addKeyAndValue(window, x, y, package.loaded.MineOSCore.localization.size, icon.isDirectory and package.loaded.MineOSCore.localization.calculatingSize or string.format("%.2f", fs.size(icon.path) / 1024) .. " KB"); y = y + 1 + addKeyAndValue(window, x, y, package.loaded.MineOSCore.localization.date, os.date("%d.%m.%y, %H:%M", math.floor(fs.lastModified(icon.path) / 1000))); y = y + 1 + addKeyAndValue(window, x, y, package.loaded.MineOSCore.localization.path, " ") + + local textBox = window:addChild(GUI.textBox(17, y, window.width - 18, 1, nil, 0x555555, {icon.path}, 1, 0, 0, true, true)) + textBox.eventHandler = nil + + window.actionButtons.minimize:delete() + window.actionButtons.maximize:delete() + + window.height = textBox.y + textBox.height + window.backgroundPanel.width = window.width + window.backgroundPanel.height = textBox.y + textBox.height + + mainContainer:draw() + buffer.draw() + + if icon.isDirectory then + fileSizeLabel.text = string.format("%.2f", fs.directorySize(icon.path) / 1024) .. " KB" + mainContainer:draw() + buffer.draw() + end +end + +----------------------------------------------------------------------------------------------------------------------------------- + +local function GUICopy(parentContainer, fileList, toPath) + local applyYes, breakRecursion + + local container = MineOSInterface.addUniversalContainer(parentContainer, MineOSCore.localization.copying) + local textBox = container.layout:addChild(GUI.textBox(1, 1, container.width, 1, nil, 0x777777, {}, 1, 0, 0, true, true):setAlignment(GUI.alignment.horizontal.center, GUI.alignment.vertical.top)) + local switchAndLabel = container.layout:addChild(GUI.switchAndLabel(1, 1, 37, 8, 0x66DB80, 0x1E1E1E, 0xE1E1E1, 0x777777, MineOSCore.localization.applyToAll .. ":", false)) + container.panel.eventHandler = nil + + local buttonsLayout = container.layout:addChild(GUI.layout(1, 1, 1, 1, 1, 1)) + buttonsLayout:addChild(GUI.button(1, 1, 11, 1, 0xE1E1E1, 0x2D2D2D, 0xAAAAAA, 0x2D2D2D, MineOSCore.localization.yes)).onTouch = function() + applyYes = true + parentContainer:stopEventHandling() + end + buttonsLayout:addChild(GUI.button(1, 1, 11, 1, 0xE1E1E1, 0x2D2D2D, 0xAAAAAA, 0x2D2D2D, MineOSCore.localization.no)).onTouch = function() + parentContainer:stopEventHandling() + end + buttonsLayout:addChild(GUI.button(1, 1, 11, 1, 0xE1E1E1, 0x2D2D2D, 0xAAAAAA, 0x2D2D2D, MineOSCore.localization.cancel)).onTouch = function() + breakRecursion = true + parentContainer:stopEventHandling() + end + buttonsLayout:setCellDirection(1, 1, GUI.directions.horizontal) + buttonsLayout:setCellSpacing(1, 1, 2) + buttonsLayout:fitToChildrenSize(1, 1) + + local function copyOrMove(path, finalPath) + switchAndLabel.hidden = true + buttonsLayout.hidden = true + + textBox.lines = { + MineOSCore.localization.copying .. " " .. MineOSCore.localization.faylaBlyad .. " " .. fs.name(path) .. " " .. MineOSCore.localization.toDirectory .. " " .. string.canonicalPath(toPath), + } + textBox.height = #textBox.lines + + parentContainer:draw() + buffer.draw() + + fs.remove(finalPath) + fs.copy(path, finalPath) + end + + local function recursiveCopy(path, toPath) + local finalPath = toPath .. "/" .. fs.name(path) + + if fs.isDirectory(path) then + fs.makeDirectory(finalPath) + + for file in fs.list(path) do + if breakRecursion then + return + end + recursiveCopy(path .. "/" .. file, finalPath) + end + else + if fs.exists(finalPath) then + if not switchAndLabel.switch.state then + switchAndLabel.hidden = false + buttonsLayout.hidden = false + applyYes = false + + textBox.lines = { + MineOSCore.localization.file .. " " .. fs.name(path) .. " " .. MineOSCore.localization.alreadyExists .. " " .. MineOSCore.localization.inDirectory .. " " .. string.canonicalPath(toPath), + MineOSCore.localization.needReplace, + } + textBox.height = #textBox.lines + + parentContainer:draw() + buffer.draw() + + parentContainer:startEventHandling() + + parentContainer:draw() + buffer.draw() + end + + if applyYes then + copyOrMove(path, finalPath) + end + else + copyOrMove(path, finalPath) + end + end + end + + for i = 1, #fileList do + recursiveCopy(fileList[i], toPath) + end + + container:delete() + parentContainer:draw() + buffer.draw() +end + +function MineOSInterface.copy(what, toPath) + if type(what) == "string" then + what = {what} + end + + GUICopy(MineOSInterface.mainContainer, what, toPath) +end + +function MineOSInterface.addMenuWidget(object) + MineOSInterface.mainContainer.menuLayout:addChild(object) + object:moveToBack() + + return object +end + + +----------------------------------------------------------------------------------------------------------------------------------- + +function MineOSInterface.showErrorWindow(path, line, traceback) + buffer.clear(0x0, 0.5) + + local mainContainer = GUI.container(1, 1, buffer.getWidth(), math.floor(buffer.getHeight() * 0.5)) + mainContainer.y = math.floor(buffer.getHeight() / 2 - mainContainer.height / 2) + + mainContainer:addChild(GUI.panel(1, 1, mainContainer.width, 3, 0x383838)) + mainContainer:addChild(GUI.label(1, 2, mainContainer.width, 1, 0xFFFFFF, MineOSCore.localization.errorWhileRunningProgram .. "\"" .. fs.name(path) .. "\"")):setAlignment(GUI.alignment.horizontal.center, GUI.alignment.vertical.top) + local actionButtons = mainContainer:addChild(GUI.actionButtons(2, 2, false)) + local sendToDeveloperButton = mainContainer:addChild(GUI.adaptiveButton(9, 1, 2, 1, 0x444444, 0xFFFFFF, 0x343434, 0xFFFFFF, MineOSCore.localization.sendFeedback)) + + local codeView = mainContainer:addChild(GUI.codeView(1, 4, math.floor(mainContainer.width * 0.62), mainContainer.height - 3, {}, 1, 1, 100, {}, {[line] = 0xFF4444}, true, 2)) + codeView.scrollBars.horizontal.hidden = true + + codeView.fromLine = line - math.floor((mainContainer.height - 3) / 2) + 1 + if codeView.fromLine <= 0 then + codeView.fromLine = 1 + end + local toLine, lineCounter = codeView.fromLine + codeView.height - 1, 1 + + for line in io.lines(path) do + if lineCounter >= codeView.fromLine and lineCounter <= toLine then + codeView.lines[lineCounter] = string.gsub(line, " ", " ") + elseif lineCounter < codeView.fromLine then + codeView.lines[lineCounter] = " " + elseif lineCounter > toLine then + break + end + lineCounter = lineCounter + 1 + if lineCounter % 200 == 0 then + os.sleep(0.1) + end + end + + mainContainer:addChild(GUI.textBox(codeView.width + 1, 4, mainContainer.width - codeView.width, codeView.height, 0xFFFFFF, 0x0, string.wrap(MineOSCore.parseErrorMessage(traceback, 4), mainContainer.width - codeView.width - 2), 1, 1, 0)) + + actionButtons.close.onTouch = function() + mainContainer:stopEventHandling() + end + + mainContainer.eventHandler = function(mainContainer, object, eventData) + if eventData[1] == "key_down" and eventData[4] == 28 then + actionButtons.close.onTouch() + end + end + + sendToDeveloperButton.onTouch = function() + if component.isAvailable("internet") then + local url = "https://api.mcmodder.ru/ECS/report.php?path=" .. path .. "&errorMessage=" .. string.optimizeForURLRequests(traceback) + local success, reason = component.internet.request(url) + if success then + success:close() + end + + sendToDeveloperButton.text = MineOSCore.localization.sendedFeedback + mainContainer:draw() + buffer.draw() + os.sleep(1) + end + + actionButtons.close.onTouch() + end + + mainContainer:draw() + buffer.draw() + + for i = 1, 3 do + component.computer.beep(1500, 0.08) + end + + mainContainer:startEventHandling() +end + +function MineOSInterface.safeLaunch(...) + local success, path, line, traceback = MineOSCore.safeLaunch(...) + if not success then + MineOSInterface.showErrorWindow(path, line, traceback) + end + + return success, path, line, traceback +end + +----------------------------------------- Window object ----------------------------------------- + +local function windowDraw(window) + GUI.windowShadow(window.x, window.y, window.width, window.height, MineOSInterface.colors.windows.shadowTransparency, true) + GUI.drawContainerContent(window) + return window +end + +local function windowCheck(window, x, y) + for i = #window.children, 1, -1 do + if window.children[i].children then + if windowCheck(window.children[i], x, y) then + return true + end + elseif window.children[i].eventHandler and window.children[i]:isClicked(x, y) then + return true + end + end +end + +local function windowEventHandler(mainContainer, window, eventData) + if eventData[1] == "touch" and not windowCheck(window, eventData[3], eventData[4]) then + window.lastTouchPosition = { + x = eventData[3], + y = eventData[4] + } + + if window ~= window.parent.children[#window.parent.children] then + window:moveToFront() + + mainContainer:draw() + buffer.draw() + end + elseif eventData[1] == "drag" and window.lastTouchPosition and not windowCheck(window, eventData[3], eventData[4]) then + local xOffset, yOffset = eventData[3] - window.lastTouchPosition.x, eventData[4] - window.lastTouchPosition.y + window.lastTouchPosition.x, window.lastTouchPosition.y = eventData[3], eventData[4] + + if xOffset ~= 0 or yOffset ~= 0 then + window.localX, window.localY = window.localX + xOffset, window.localY + yOffset + + mainContainer:draw() + buffer.draw() + end + elseif eventData[1] == "drop" then + window.lastTouchPosition = nil + end +end + +function MineOSInterface.windowFromContainer(container) + container.eventHandler = windowEventHandler + container.draw = windowDraw + + return container +end + +function MineOSInterface.window(x, y, width, height) + return MineOSInterface.windowFromContainer(GUI.container(x, y, width, height)) +end + +function MineOSInterface.filledWindow(x, y, width, height, backgroundColor) + local window = MineOSInterface.window(x, y, width, height) + + window.backgroundPanel = window:addChild(GUI.panel(1, 1, width, height, backgroundColor)) + window.actionButtons = window:addChild(GUI.actionButtons(2, 2, false)) + + return window +end + +function MineOSInterface.titledWindow(x, y, width, height, title, addTitlePanel) + local window = MineOSInterface.filledWindow(x, y, width, height, MineOSInterface.colors.windows.backgroundPanel) + + if addTitlePanel then + window.titlePanel = window:addChild(GUI.panel(1, 1, width, 1, MineOSInterface.colors.windows.title.background)) + window.backgroundPanel.localY, window.backgroundPanel.height = 2, window.height - 1 + end + window.titleLabel = window:addChild(GUI.label(1, 1, width, height, MineOSInterface.colors.windows.title.text, title)):setAlignment(GUI.alignment.horizontal.center, GUI.alignment.vertical.top) + window.actionButtons.localY = 1 + window.actionButtons:moveToFront() + + return window +end + +function MineOSInterface.tabbedWindow(x, y, width, height, ...) + local window = MineOSInterface.filledWindow(x, y, width, height, MineOSInterface.colors.windows.backgroundPanel) + + window.tabBar = window:addChild(GUI.tabBar(1, 1, window.width, 3, 2, 0, MineOSInterface.colors.windows.tabBar.default.background, MineOSInterface.colors.windows.tabBar.default.text, MineOSInterface.colors.windows.tabBar.selected.background, MineOSInterface.colors.windows.tabBar.selected.text, ...)) + window.backgroundPanel.localY, window.backgroundPanel.height = 4, window.height - 3 + window.actionButtons:moveToFront() + window.actionButtons.localY = 2 + + return window +end + +----------------------------------------------------------------------------------------------------------------------------------- + +MineOSInterface.cacheIconSource("folder", MineOSPaths.icons .. "Folder.pic") +MineOSInterface.cacheIconSource("fileNotExists", MineOSPaths.icons .. "FileNotExists.pic") +MineOSInterface.cacheIconSource("application", MineOSPaths.icons .. "Application.pic") +MineOSInterface.cacheIconSource("trash", MineOSPaths.icons .. "Trash.pic") +MineOSInterface.cacheIconSource("script", MineOSPaths.icons .. "Script.pic") + +----------------------------------------------------------------------------------------------------------------------------------- + +return MineOSInterface \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/MineOSNetwork.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/MineOSNetwork.lua new file mode 100755 index 00000000..07fda8a7 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/MineOSNetwork.lua @@ -0,0 +1,400 @@ + +local component = require("component") +local MineOSCore = require("MineOSCore") +local computer = require("computer") +local event = require("event") +local filesystemComponent = require("component").proxy(computer.getBootAddress()) +local filesystemLibrary = require("filesystem") + +-- Ебучие херолизы, с каких залупнинских хуев я должен учитывать их говнокод и синтаксические ошибки? +-- GGWP +if not filesystemLibrary.unmount and filesystemLibrary.umount then + filesystemLibrary.unmount = filesystemLibrary.umount +end + +---------------------------------------------------------------------------------------------------------------- + +local MineOSNetwork = {} + +MineOSNetwork.modemPort = 1488 +MineOSNetwork.modemProxy = nil +MineOSNetwork.modemPacketReserve = 128 +MineOSNetwork.timeout = 2 +MineOSNetwork.filesystemHandles = {} +MineOSNetwork.mountPath = "/ftp/" + +---------------------------------------------------------------------------------------------------------------- + +function MineOSNetwork.getProxyName(proxy) + return proxy.name and proxy.name .. " (" .. proxy.address .. ")" or proxy.address +end + +function MineOSNetwork.getProxy(address) + for proxy, path in filesystemLibrary.mounts() do + if proxy.network and proxy.address == address then + return proxy + end + end +end + +function MineOSNetwork.getProxyCount() + local count = 0 + for proxy, path in filesystemLibrary.mounts() do + if proxy.network then + count = count + 1 + end + end + + return count +end + +function MineOSNetwork.unmountAll() + for proxy in filesystemLibrary.mounts() do + if proxy.network then + filesystemLibrary.unmount(proxy) + end + end +end + +---------------------------------------------------------------------------------------------------------------- + +function MineOSNetwork.sendMessage(address, ...) + if MineOSNetwork.modemProxy then + return MineOSNetwork.modemProxy.send(address, MineOSNetwork.modemPort, ...) + else + MineOSNetwork.modemProxy = nil + return false, "Modem component is not available" + end +end + +function MineOSNetwork.broadcastMessage(...) + if MineOSNetwork.modemProxy then + return MineOSNetwork.modemProxy.broadcast(MineOSNetwork.modemPort, ...) + else + MineOSNetwork.modemProxy = nil + return false, "Modem component is not available" + end +end + +function MineOSNetwork.setSignalStrength(strength) + if MineOSNetwork.modemProxy then + if MineOSNetwork.modemProxy.isWireless() then + return MineOSNetwork.modemProxy.setStrength(strength) + else + return false, "Modem component is not wireless" + end + else + MineOSNetwork.modemProxy = nil + return false, "Modem component is not available" + end +end + +function MineOSNetwork.updateModemState() + if component.isAvailable("modem") then + MineOSNetwork.modemProxy = component.proxy(component.list("modem")()) + MineOSNetwork.modemProxy.open(MineOSNetwork.modemPort) + + return true + else + MineOSNetwork.modemProxy = nil + MineOSNetwork.unmountAll() + + return false, "Modem component is not available" + end +end + +function MineOSNetwork.broadcastComputerState(state) + return MineOSNetwork.broadcastMessage("MineOSNetwork", state and "computerAvailable" or "computerNotAvailable", MineOSCore.properties.network.name) +end + +---------------------------------------------------------------------------------------------------------------- + +local function newFilesystemProxy(address) + local function request(method, returnOnFailure, ...) + MineOSNetwork.sendMessage(address, "MineOSNetwork", "request", method, ...) + + while true do + local eventData = { event.pull(MineOSNetwork.timeout, "modem_message") } + + if eventData[3] == address and eventData[6] == "MineOSNetwork" then + if eventData[7] == "response" and eventData[8] == method then + return table.unpack(eventData, 9) + elseif eventData[7] == "accessDenied" then + computer.pushSignal("MineOSNetwork", "accessDenied", address) + return returnOnFailure, "Access denied" + end + elseif not eventData[1] then + local proxy = MineOSNetwork.getProxy(address) + if proxy then + filesystemLibrary.unmount(proxy) + end + + computer.pushSignal("MineOSNetwork", "timeout") + + return returnOnFailure, "Network filesystem timeout" + end + end + end + + return { + type = "filesystem", + address = address, + slot = 0, + network = true, + + getLabel = function() + return request("getLabel", "N/A") + end, + + isReadOnly = function() + return request("isReadOnly", "N/A") + end, + + spaceUsed = function() + return request("spaceUsed", "N/A") + end, + + spaceTotal = function() + return request("spaceTotal", "N/A") + end, + + exists = function(path) + return request("exists", false, path) + end, + + isDirectory = function(path) + return request("isDirectory", false, path) + end, + + makeDirectory = function(path) + return request("makeDirectory", false, path) + end, + + setLabel = function(name) + return request("setLabel", false, name) + end, + + remove = function(path) + return request("remove", false, path) + end, + + lastModified = function(path) + return request("lastModified", 0, path) + end, + + size = function(path) + return request("size", 0, path) + end, + + list = function(path) + return table.fromString(request("list", "{}", path)) + end, + + seek = function(handle, whence, offset) + return request("seek", 0, handle, whence, offset) + end, + + open = function(path, mode) + return request("open", false, path, mode) + end, + + close = function(handle) + return request("close", false, handle) + end, + + read = function(handle, count) + return request("read", "", handle, count) + end, + + write = function(handle, data) + local maxPacketSize = MineOSNetwork.modemProxy.maxPacketSize() - MineOSNetwork.modemPacketReserve + repeat + if not request("write", false, handle, data:sub(1, maxPacketSize)) then + return false + end + data = data:sub(maxPacketSize + 1) + until #data == 0 + + return true + end, + + rename = function(from, to) + local proxyFrom = filesystemLibrary.get(from) + local proxyTo = filesystemLibrary.get(to) + + if proxyFrom.network or proxyTo.network then + local success, handleFrom, handleTo, data, reason = true + + handleFrom, reason = proxyFrom.open(from, "rb") + if handleFrom then + handleTo, reason = proxyTo.open(to, "wb") + if handleTo then + while true do + data, readReason = proxyFrom.read(handleFrom, 1024) + if data then + success, reason = proxyTo.write(handleTo, data) + if not success then + break + end + else + success = false + break + end + end + + proxyFrom.close(handleTo) + else + success = false + end + + proxyFrom.close(handleFrom) + else + success = false + end + + if success then + success, reason = proxyFrom.remove(from) + end + + return success, reason + else + return request("rename", false, from, to) + end + end, + } +end + +local exceptionMethods = { + getLabel = function() + return MineOSCore.properties.network.name or MineOSNetwork.modemProxy.address + end, + + list = function(path) + return table.toString(filesystemComponent.list(path)) + end, + + open = function(path, mode) + local ID + while not ID do + ID = math.random(1, 0x7FFFFFFF) + for handleID in pairs(MineOSNetwork.filesystemHandles) do + if handleID == ID then + ID = nil + end + end + end + + MineOSNetwork.filesystemHandles[ID] = filesystemComponent.open(path, mode) + + return ID + end, + + close = function(ID) + local data, reason = filesystemComponent.close(MineOSNetwork.filesystemHandles[ID]) + MineOSNetwork.filesystemHandles[ID] = nil + return data, reason + end, + + read = function(ID, ...) + return filesystemComponent.read(MineOSNetwork.filesystemHandles[ID], ...) + end, + + write = function(ID, ...) + return filesystemComponent.write(MineOSNetwork.filesystemHandles[ID], ...) + end, + + seek = function(ID, ...) + return filesystemComponent.seek(MineOSNetwork.filesystemHandles[ID], ...) + end, +} + +local function handleRequest(eventData) + -- print("REQ", table.unpack(eventData, 6)) + + if MineOSCore.properties.network.users[eventData[3]].allowReadAndWrite then + local result = { pcall(exceptionMethods[eventData[8]] or filesystemComponent[eventData[8]], table.unpack(eventData, 9)) } + if result[1] then + MineOSNetwork.sendMessage(eventData[3], "MineOSNetwork", "response", eventData[8], table.unpack(result, 2)) + else + MineOSNetwork.sendMessage(eventData[3], "MineOSNetwork", "response", eventData[8], result[1], result[2]) + end + else + MineOSNetwork.sendMessage(eventData[3], "MineOSNetwork", "accessDenied") + end +end + +---------------------------------------------------------------------------------------------------------------- + +function MineOSNetwork.update() + MineOSNetwork.unmountAll() + MineOSNetwork.updateModemState() + MineOSNetwork.setSignalStrength(MineOSCore.properties.network.signalStrength) + MineOSNetwork.broadcastComputerState(MineOSCore.properties.network.enabled) + + if MineOSNetwork.eventHandlerID then + event.removeHandler(MineOSNetwork.eventHandlerID) + end + + if MineOSCore.properties.network.enabled then + MineOSNetwork.eventHandlerID = event.addHandler(function(...) + local eventData = {...} + + if (eventData[1] == "component_added" or eventData[1] == "component_removed") and eventData[3] == "modem" then + MineOSNetwork.updateModemState() + elseif eventData[1] == "modem_message" and MineOSCore.properties.network.enabled and eventData[6] == "MineOSNetwork" then + if eventData[7] == "request" then + handleRequest(eventData) + elseif eventData[7] == "computerAvailable" or eventData[7] == "computerAvailableRedirect" then + for proxy in filesystemLibrary.mounts() do + if proxy.network and proxy.address == eventData[3] then + filesystemLibrary.unmount(proxy) + end + end + + proxy = newFilesystemProxy(eventData[3]) + proxy.name = eventData[8] + filesystemLibrary.mount(proxy, MineOSNetwork.mountPath .. eventData[3]:sub(1, 3) .. "/") + + if eventData[7] == "computerAvailable" then + MineOSNetwork.sendMessage(eventData[3], "MineOSNetwork", "computerAvailableRedirect", MineOSCore.properties.network.name) + end + + if not MineOSCore.properties.network.users[eventData[3]] then + MineOSCore.properties.network.users[eventData[3]] = {} + MineOSCore.saveProperties() + end + + computer.pushSignal("MineOSNetwork", "updateProxyList") + elseif eventData[7] == "computerNotAvailable" then + local proxy = MineOSNetwork.getProxy(eventData[3]) + if proxy then + filesystemLibrary.unmount(proxy) + end + + computer.pushSignal("MineOSNetwork", "updateProxyList") + end + end + end) + end +end + +function MineOSNetwork.disable() + MineOSCore.properties.network.enabled = false + MineOSCore.saveProperties() + MineOSNetwork.update() +end + +function MineOSNetwork.enable() + MineOSCore.properties.network.enabled = true + MineOSCore.saveProperties() + MineOSNetwork.update() +end + +---------------------------------------------------------------------------------------------------------------- + +return MineOSNetwork + + + + + diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/MineOSPaths.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/MineOSPaths.lua new file mode 100755 index 00000000..8b255c0c --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/MineOSPaths.lua @@ -0,0 +1,31 @@ + +local filesystem = require("filesystem") +local MineOSPaths = {} + +---------------------------------------------------------------------------------------------------------------- + +MineOSPaths.OS = "/MineOS/" +MineOSPaths.downloads = MineOSPaths.OS .. "Downloads/" +MineOSPaths.system = MineOSPaths.OS .. "System/" +MineOSPaths.applicationData = MineOSPaths.system .. "Application data/" +MineOSPaths.extensionAssociations = MineOSPaths.system .. "Extensions/" +MineOSPaths.localizationFiles = MineOSPaths.system .. "Localization/" +MineOSPaths.icons = MineOSPaths.system .. "Icons/" +MineOSPaths.applications = MineOSPaths.OS .. "Applications/" +MineOSPaths.pictures = MineOSPaths.OS .. "Pictures/" +MineOSPaths.desktop = MineOSPaths.OS .. "Desktop/" +MineOSPaths.applicationList = MineOSPaths.system .. "Applications.cfg" +MineOSPaths.trash = MineOSPaths.OS .. "Trash/" +MineOSPaths.properties = MineOSPaths.system .. "Properties.cfg" +MineOSPaths.editor = MineOSPaths.applications .. "/MineCode IDE.app/Main.lua" +MineOSPaths.explorer = MineOSPaths.applications .. "/Finder.app/Main.lua" + +---------------------------------------------------------------------------------------------------------------- + +filesystem.makeDirectory(MineOSPaths.pictures) +filesystem.makeDirectory(MineOSPaths.applicationData) +filesystem.makeDirectory(MineOSPaths.trash) + +---------------------------------------------------------------------------------------------------------------- + +return MineOSPaths \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/OpenComputersGL/Main.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/OpenComputersGL/Main.lua new file mode 100755 index 00000000..e981ab6f --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/OpenComputersGL/Main.lua @@ -0,0 +1,386 @@ + +-------------------------------------------------------- Libraries -------------------------------------------------------- + +local color = require("color") +local vector = require("vector") +local buffer = require("doubleBuffering") +local materials = require("OpenComputersGL/Materials") +local renderer = require("OpenComputersGL/Renderer") +local OCGL = {} + +-------------------------------------------------------- Constants -------------------------------------------------------- + +OCGL.axis = { + x = 1, + y = 2, + z = 3, +} + +OCGL.colors = { + axis = { + x = 0xFF0000, + y = 0x00FF00, + z = 0x0000FF, + }, + pivotPoint = 0xFFFFFF, + wireframe = 0x000000, + vertices = 0xFFDB40, + lights = 0x44FF44 +} + +OCGL.renderModes = { + disabled = 1, + constantShading = 2, + flatShading = 3, +} + +OCGL.auxiliaryModes = { + disabled = 1, + wireframe = 2, + vertices = 3, +} + +OCGL.renderMode = 3 +OCGL.auxiliaryMode = 1 + +OCGL.vertices = {} +OCGL.triangles = {} +OCGL.lines = {} +OCGL.floatingTexts = {} +OCGL.lights = {} + +local sinTable, cosTable = {}, {} + +-------------------------------------------------------- Sin / Cos optimization -------------------------------------------------------- + +function OCGL.sin(angle) + sinTable[angle] = sinTable[angle] or math.sin(angle) + return sinTable[angle] +end + +function OCGL.cos(angle) + cosTable[angle] = cosTable[angle] or math.cos(angle) + return cosTable[angle] +end + +-------------------------------------------------------- Vertex field methods -------------------------------------------------------- + +function OCGL.rotateVectorRelativeToXAxis(vector, angle) + local sin, cos = OCGL.sin(angle), OCGL.cos(angle) + vector[2], vector[3] = cos * vector[2] - sin * vector[3], sin * vector[2] + cos * vector[3] +end + +function OCGL.rotateVectorRelativeToYAxis(vector, angle) + local sin, cos = OCGL.sin(angle), OCGL.cos(angle) + vector[1], vector[3] = cos * vector[1] + sin * vector[3], cos * vector[3] - sin * vector[1] +end + +function OCGL.rotateVectorRelativeToZAxis(vector, angle) + local sin, cos = OCGL.sin(angle), OCGL.cos(angle) + vector[1], vector[2] = cos * vector[1] - sin * vector[2], sin * vector[1] + cos * vector[2] +end + +function OCGL.translate(xTranslation, yTranslation, zTranslation) + for vertexIndex = 1, #OCGL.vertices do + OCGL.vertices[vertexIndex][1], OCGL.vertices[vertexIndex][2], OCGL.vertices[vertexIndex][3] = OCGL.vertices[vertexIndex][1] + xTranslation, OCGL.vertices[vertexIndex][2] + yTranslation, OCGL.vertices[vertexIndex][3] + zTranslation + end +end + +function OCGL.rotate(vectorRotationMethod, angle) + for vertexIndex = 1, #OCGL.vertices do + vectorRotationMethod(OCGL.vertices[vertexIndex], angle) + end +end + +-------------------------------------------------------- Render queue methods -------------------------------------------------------- + +function OCGL.newIndexedLight(indexOfVertex1, intensity, emissionDistance) + return { indexOfVertex1, intensity, emissionDistance } +end + +function OCGL.newIndexedTriangle(indexOfVertex1, indexOfVertex2, indexOfVertex3, material) + return { indexOfVertex1, indexOfVertex2, indexOfVertex3, material } +end + +function OCGL.newIndexedLine(indexOfVertex1, indexOfVertex2, color) + return { indexOfVertex1, indexOfVertex2, color } +end + +function OCGL.newIndexedFloatingText(indexOfVertex, color, text) + return {indexOfVertex, text, color} +end + +function OCGL.pushLightToRenderQueue(vector3Vertex, intensity, emissionDistance) + table.insert(OCGL.vertices, vector3Vertex) + table.insert(OCGL.lights, OCGL.newIndexedLight(OCGL.nextVertexIndex, intensity, emissionDistance)) + OCGL.nextVertexIndex = OCGL.nextVertexIndex + 1 +end + +function OCGL.pushTriangleToRenderQueue(vector3Vertex1, vector3Vertex2, vector3Vertex3, material) + table.insert(OCGL.vertices, vector3Vertex1) + table.insert(OCGL.vertices, vector3Vertex2) + table.insert(OCGL.vertices, vector3Vertex3) + table.insert(OCGL.triangles, OCGL.newIndexedTriangle(OCGL.nextVertexIndex, OCGL.nextVertexIndex + 1, OCGL.nextVertexIndex + 2, material)) + OCGL.nextVertexIndex = OCGL.nextVertexIndex + 3 +end + +function OCGL.pushLineToRenderQueue(vector3Vertex1, vector3Vertex2, color) + table.insert(OCGL.vertices, vector3Vertex1) + table.insert(OCGL.vertices, vector3Vertex2) + table.insert(OCGL.lines, OCGL.newIndexedLine(OCGL.nextVertexIndex, OCGL.nextVertexIndex + 1, color)) + OCGL.nextVertexIndex = OCGL.nextVertexIndex + 2 +end + +function OCGL.pushFloatingTextToRenderQueue(vector3Vertex, color, text) + table.insert(OCGL.vertices, vector3Vertex) + table.insert(OCGL.floatingTexts, OCGL.newIndexedFloatingText(OCGL.nextVertexIndex, color, text)) + OCGL.nextVertexIndex = OCGL.nextVertexIndex + 1 +end + +-------------------------------------------------------- Rendering methods -------------------------------------------------------- + +function OCGL.clearBuffer(backgroundColor) + OCGL.nextVertexIndex, OCGL.vertices, OCGL.triangles, OCGL.lines, OCGL.floatingTexts, OCGL.lights = 1, {}, {}, {}, {}, {} + renderer.clearDepthBuffer() + buffer.clear(backgroundColor) +end + +function OCGL.createPerspectiveProjection() + local zProjectionDivZ + for vertexIndex = 1, #OCGL.vertices do + zProjectionDivZ = math.abs(renderer.viewport.projectionSurface / OCGL.vertices[vertexIndex][3]) + OCGL.vertices[vertexIndex][1] = zProjectionDivZ * OCGL.vertices[vertexIndex][1] + OCGL.vertices[vertexIndex][2] = zProjectionDivZ * OCGL.vertices[vertexIndex][2] + end +end + +function OCGL.getTriangleLightIntensity(vertex1, vertex2, vertex3, indexedLight) + local lightVector = { + OCGL.vertices[indexedLight[1]][1] - (vertex1[1] + vertex2[1] + vertex3[1]) / 3, + OCGL.vertices[indexedLight[1]][2] - (vertex1[2] + vertex2[2] + vertex3[2]) / 3, + OCGL.vertices[indexedLight[1]][3] - (vertex1[3] + vertex2[3] + vertex3[3]) / 3 + } + local lightDistance = vector.length(lightVector) + + if lightDistance <= indexedLight[3] then + local normalVector = vector.getSurfaceNormal(vertex1, vertex2, vertex3) + -- buffer.text(2, buffer.height - 2, 0x0, "normalVector: " .. normalVector[1] .. " x " .. normalVector[2] .. " x " .. normalVector[3]) + + local cameraScalar = vector.scalarMultiply({0, 0, 100}, normalVector) + local lightScalar = vector.scalarMultiply(lightVector, normalVector ) + + -- buffer.text(2, buffer.height - 1, 0xFFFFFF, "Scalars: " .. cameraScalar .. " x " .. lightScalar) + if cameraScalar < 0 and lightScalar >= 0 or cameraScalar >= 0 and lightScalar < 0 then + local absAngle = math.abs(math.acos(lightScalar / (lightDistance * vector.length(normalVector)))) + if absAngle > 1.5707963267949 then + absAngle = 3.1415926535898 - absAngle + end + -- buffer.text(2, buffer.height, 0xFFFFFF, "Angle: " .. math.deg(angle) .. ", newAngle: " .. math.deg(absAngle) .. ", intensity: " .. absAngle / 1.5707963267949) + return indexedLight[2] * (1 - lightDistance / indexedLight[3]) * (1 - absAngle / 1.5707963267949) + else + return 0 + end + else + -- buffer.text(2, buffer.height, 0x0, "Out of light range: " .. lightDistance .. " vs " .. indexedLight[2]) + return 0 + end +end + +function OCGL.calculateLights() + for triangleIndex = 1, #OCGL.triangles do + for lightIndex = 1, #OCGL.lights do + local intensity = OCGL.getTriangleLightIntensity( + OCGL.vertices[OCGL.triangles[triangleIndex][1]], + OCGL.vertices[OCGL.triangles[triangleIndex][2]], + OCGL.vertices[OCGL.triangles[triangleIndex][3]], + OCGL.lights[lightIndex] + ) + if OCGL.triangles[triangleIndex][5] then + OCGL.triangles[triangleIndex][5] = OCGL.triangles[triangleIndex][5] + intensity + else + OCGL.triangles[triangleIndex][5] = intensity + end + end + end +end + +function OCGL.render() + local vertex1, vertex2, vertex3, material, auxiliaryColor = {}, {}, {} + + for triangleIndex = 1, #OCGL.triangles do + vertex1[1], vertex1[2], vertex1[3] = renderer.viewport.xCenter + OCGL.vertices[OCGL.triangles[triangleIndex][1]][1], renderer.viewport.yCenter - OCGL.vertices[OCGL.triangles[triangleIndex][1]][2], OCGL.vertices[OCGL.triangles[triangleIndex][1]][3] + vertex2[1], vertex2[2], vertex2[3] = renderer.viewport.xCenter + OCGL.vertices[OCGL.triangles[triangleIndex][2]][1], renderer.viewport.yCenter - OCGL.vertices[OCGL.triangles[triangleIndex][2]][2], OCGL.vertices[OCGL.triangles[triangleIndex][2]][3] + vertex3[1], vertex3[2], vertex3[3] = renderer.viewport.xCenter + OCGL.vertices[OCGL.triangles[triangleIndex][3]][1], renderer.viewport.yCenter - OCGL.vertices[OCGL.triangles[triangleIndex][3]][2], OCGL.vertices[OCGL.triangles[triangleIndex][3]][3] + material = OCGL.triangles[triangleIndex][4] + + if + renderer.isVertexInViewRange(vertex1[1], vertex1[2], vertex1[3]) or + renderer.isVertexInViewRange(vertex2[1], vertex2[2], vertex2[3]) or + renderer.isVertexInViewRange(vertex3[1], vertex3[2], vertex3[3]) + then + if material.type == materials.types.solid then + if OCGL.renderMode == OCGL.renderModes.constantShading then + renderer.renderFilledTriangle({ vertex1, vertex2, vertex3 }, material.color) + elseif OCGL.renderMode == OCGL.renderModes.flatShading then + -- local finalColor = 0x0 + -- finalColor = color.blend(material.color, 0x0, OCGL.triangles[triangleIndex][5]) + -- OCGL.triangles[triangleIndex][5] = nil + -- renderer.renderFilledTriangle({ vertex1, vertex2, vertex3 }, finalColor) + + local r, g, b = color.IntegerToRGB(material.color) + r, g, b = r * OCGL.triangles[triangleIndex][5], g * OCGL.triangles[triangleIndex][5], b * OCGL.triangles[triangleIndex][5] + if r > 255 then r = 255 end + if g > 255 then g = 255 end + if b > 255 then b = 255 end + OCGL.triangles[triangleIndex][5] = nil + + renderer.renderFilledTriangle({ vertex1, vertex2, vertex3 }, color.RGBToInteger(r, g, b)) + end + elseif material.type == materials.types.textured then + vertex1[4], vertex1[5] = OCGL.vertices[OCGL.triangles[triangleIndex][1]][4], OCGL.vertices[OCGL.triangles[triangleIndex][1]][5] + vertex2[4], vertex2[5] = OCGL.vertices[OCGL.triangles[triangleIndex][2]][4], OCGL.vertices[OCGL.triangles[triangleIndex][2]][5] + vertex3[4], vertex3[5] = OCGL.vertices[OCGL.triangles[triangleIndex][3]][4], OCGL.vertices[OCGL.triangles[triangleIndex][3]][5] + + renderer.renderTexturedTriangle({ vertex1, vertex2, vertex3 }, material.texture) + else + error("Material type " .. tostring(material.type) .. " doesn't supported for rendering triangles") + end + + if OCGL.auxiliaryMode ~= OCGL.auxiliaryModes.disabled then + vertex1[1], vertex1[2], vertex1[3] = math.floor(renderer.viewport.xCenter + OCGL.vertices[OCGL.triangles[triangleIndex][1]][1]), math.floor(renderer.viewport.yCenter - OCGL.vertices[OCGL.triangles[triangleIndex][1]][2]), math.floor(OCGL.vertices[OCGL.triangles[triangleIndex][1]][3]) + vertex2[1], vertex2[2], vertex2[3] = math.floor(renderer.viewport.xCenter + OCGL.vertices[OCGL.triangles[triangleIndex][2]][1]), math.floor(renderer.viewport.yCenter - OCGL.vertices[OCGL.triangles[triangleIndex][2]][2]), math.floor(OCGL.vertices[OCGL.triangles[triangleIndex][2]][3]) + vertex3[1], vertex3[2], vertex3[3] = math.floor(renderer.viewport.xCenter + OCGL.vertices[OCGL.triangles[triangleIndex][3]][1]), math.floor(renderer.viewport.yCenter - OCGL.vertices[OCGL.triangles[triangleIndex][3]][2]), math.floor(OCGL.vertices[OCGL.triangles[triangleIndex][3]][3]) + + if OCGL.auxiliaryMode == OCGL.auxiliaryModes.wireframe then + renderer.renderLine(vertex1[1], vertex1[2], vertex1[3], vertex2[1], vertex2[2], vertex2[3], OCGL.colors.wireframe) + renderer.renderLine(vertex2[1], vertex2[2], vertex2[3], vertex3[1], vertex3[2], vertex3[3], OCGL.colors.wireframe) + renderer.renderLine(vertex1[1], vertex1[2], vertex1[3], vertex3[1], vertex3[2], vertex3[3], OCGL.colors.wireframe) + elseif OCGL.auxiliaryMode == OCGL.auxiliaryModes.vertices then + renderer.renderDot(vertex1[1], vertex1[2], vertex1[3], OCGL.colors.vertices) + renderer.renderDot(vertex2[1], vertex2[2], vertex2[3], OCGL.colors.vertices) + renderer.renderDot(vertex3[1], vertex3[2], vertex3[3], OCGL.colors.vertices) + end + end + end + end + + if OCGL.auxiliaryMode ~= OCGL.auxiliaryModes.disabled then + for lightIndex = 1, #OCGL.lights do + renderer.renderDot( + math.floor(renderer.viewport.xCenter + OCGL.vertices[OCGL.lights[lightIndex][1]][1]), + math.floor(renderer.viewport.yCenter - OCGL.vertices[OCGL.lights[lightIndex][1]][2]), + math.floor(OCGL.vertices[OCGL.lights[lightIndex][1]][3]), + OCGL.colors.lights + ) + end + end + + for floatingTextIndex = 1, #OCGL.floatingTexts do + vertex1 = OCGL.vertices[OCGL.floatingTexts[floatingTextIndex][1]] + renderer.renderFloatingText( + renderer.viewport.xCenter + vertex1[1], + renderer.viewport.yCenter - vertex1[2], + vertex1[3], + OCGL.floatingTexts[floatingTextIndex][2], + OCGL.floatingTexts[floatingTextIndex][3] + ) + end + + -- for lineIndex = 1, #OCGL.lines do + -- vertex1, vertex2, material = OCGL.vertices[OCGL.lines[lineIndex][1]], OCGL.vertices[OCGL.lines[lineIndex][2]], OCGL.lines[lineIndex][3] + + -- if OCGL.renderMode == renderer.renderModes.vertices then + -- renderer.renderDot(vertex1, material) + -- renderer.renderDot(vertex2, material) + -- else + -- renderer.renderLine( + -- math.floor(vertex1[1]), + -- math.floor(vertex1[2]), + -- vertex1[3], + -- math.floor(vertex2[1]), + -- math.floor(vertex2[2]), + -- vertex2[3], + -- material + -- ) + -- end + -- end +end + +-------------------------------------------------------- Raycasting methods -------------------------------------------------------- + +local function vectorMultiply(a, b) + return vector.newVector3(a[2] * b[3] - a[3] * b[2], a[3] * b[1] - a[1] * b[3], a[1] * b[2] - a[2] * b[1]) +end + +local function getVectorDistance(a) + return math.sqrt(a[1] ^ 2 + a[2] ^ 2 + a[3] ^ 2) +end + +-- В случае попадания лучика этот метод вернет сам треугольник, а также дистанцию до его плоскости +function OCGL.triangleRaycast(vector3RayStart, vector3RayEnd) + local minimalDistance, closestTriangleIndex + for triangleIndex = 1, #OCGL.triangles do + -- Это вершины треугольника + local A, B, C = OCGL.vertices[OCGL.triangles[triangleIndex][1]], OCGL.vertices[OCGL.triangles[triangleIndex][3]], OCGL.vertices[OCGL.triangles[triangleIndex][3]] + -- ecs.error(A[1], A[2], A[3], vector3RayStart[1], vector3RayStart[2], vector3RayStart[3]) + -- Это хз че + local ABC = vectorMultiply(vector.newVector3(C[1] - A[1], C[2] - A[2], C[3] - A[3]), vector.newVector3(B[1] - A[1], B[2] - A[2], B[3] - A[3])) + -- Рассчитываем удаленность виртуальной плоскости треугольника от старта нашего луча + local D = -ABC[1] * A[1] - ABC[2] * A[2] - ABC[3] * A[3] + local firstPart = D + ABC[1] * vector3RayStart[1] + ABC[2] * vector3RayStart[2] + ABC[3] * vector3RayStart[3] + local secondPart = ABC[1] * vector3RayStart[1] - ABC[1] * vector3RayEnd[1] + ABC[2] * vector3RayStart[2] - ABC[2] * vector3RayEnd[2] + ABC[3] * vector3RayStart[3] - ABC[3] * vector3RayEnd[3] + + -- ecs.error(firstPart, secondPart) + + -- if firstPart ~= 0 or secondPart ~= 0 then ecs.error(firstPart, secondPart) end + -- Если наш лучик не параллелен той ебучей плоскости треугольника + if secondPart ~= 0 then + local distance = firstPart / secondPart + -- И если этот объект находится ближе к старту луча, нежели предыдущий + if (distance >= 0 and distance <= 1) and (not minimalDistance or distance < minimalDistance) then + + -- То считаем точку попадания луча в данную плоскость (но ни хуя не факт, что он попадет в треугольник!) + local S = vector.newVector3( + vector3RayStart[1] + (vector3RayEnd[1] - vector3RayStart[1]) * distance, + vector3RayStart[2] + (vector3RayEnd[2] - vector3RayStart[2]) * distance, + vector3RayStart[3] + (vector3RayEnd[3] - vector3RayStart[3]) * distance + ) + + -- Далее считаем сумму площадей параллелограммов, образованных тремя треугольниками, образовавшихся при попадании точки в треугольник + -- Нууу тип кароч смари: точка ебанула в центр, и треугольник распидорасило на три мелких. Ну, и три мелких могут образовать параллелограммы свои + -- И, кароч, если сумма трех площадей этих мелких уебков будет сильно отличаться от площади жирного треугольника, то луч не попал + -- Ну, а площадь считается через sqrt(x^2+y^2+z^2) для каждого йоба-вектора + + ---- *A *B + + + -- * Shotxyz + + + --- *C + + local SA = vector.newVector3(A[1] - S[1], A[2] - S[2], A[3] - S[3]) + local SB = vector.newVector3(B[1] - S[1], B[2] - S[2], B[3] - S[3]) + local SC = vector.newVector3(C[1] - S[1], C[2] - S[2], C[3] - S[3]) + + local vectorDistanceSum = getVectorDistance(vectorMultiply(SA, SB)) + getVectorDistance(vectorMultiply(SB, SC)) + getVectorDistance(vectorMultiply(SC, SA)) + local ABCDistance = getVectorDistance(ABC) + + -- Вот тут мы чекаем погрешность расчетов. Если все заебок, то кидаем этот треугольник в "проверенные"" + if math.abs(vectorDistanceSum - ABCDistance) < 1 then + closestTriangleIndex = triangleIndex + minimalDistance = distance + end + end + end + end + + -- ecs.error(closestTriangleIndex) + if OCGL.triangles[closestTriangleIndex] then + return OCGL.triangles[closestTriangleIndex][5], OCGL.triangles[closestTriangleIndex][6], minimalDistance + end +end + +-------------------------------------------------------- Constants -------------------------------------------------------- + +return OCGL diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/OpenComputersGL/Materials.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/OpenComputersGL/Materials.lua new file mode 100755 index 00000000..fc09ba0c --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/OpenComputersGL/Materials.lua @@ -0,0 +1,51 @@ + +local color = require("color") +local materials = {} + +------------------------------------------------------------------------------------------------------------------------ + +materials.types = { + textured = 1, + solid = 2, +} + +function materials.newDebugTexture(width, height, h) + local texture = {width = width, height = height} + + local bStep = 1 / height + local sStep = 1 / width + + local s, b = 0, 0 + local blackSquare = false + for y = 1, height do + texture[y] = {} + for x = 1, width do + texture[y][x] = blackSquare == true and 0x0 or color.HSBToInteger(h, s, b) + blackSquare = not blackSquare + b = b + bStep + end + b = 0 + s = s + sStep + blackSquare = not blackSquare + end + return texture +end + +function materials.newSolidMaterial(color) + return { + type = materials.types.solid, + color = color + } +end + +function materials.newTexturedMaterial(texture) + return { + type = materials.types.textured, + texture = texture + } +end + +------------------------------------------------------------------------------------------------------------------------ + +return materials + diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/OpenComputersGL/Renderer.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/OpenComputersGL/Renderer.lua new file mode 100755 index 00000000..ff7000a5 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/OpenComputersGL/Renderer.lua @@ -0,0 +1,349 @@ + +-------------------------------------------------------- Libraries -------------------------------------------------------- + +local computer = require("computer") +local vector = require("vector") +local unicode = require("unicode") +local materials = require("OpenComputersGL/Materials") +local buffer = require("doubleBuffering") + +local renderer = { + depthBuffer = {}, + viewport = {}, +} + +-------------------------------------------------------- Renderer -------------------------------------------------------- + +function renderer.clearDepthBuffer() + for y = 1, renderer.viewport.height do + renderer.depthBuffer[y] = {} + for x = 1, renderer.viewport.width do + renderer.depthBuffer[y][x] = math.huge + end + end +end + +function renderer.setViewport(x1, y1, x2, y2, nearClippingSurface, farClippingSurface, projectionSurface) + renderer.viewport.x1 = x1 + renderer.viewport.y1 = y1 + renderer.viewport.x2 = x2 + renderer.viewport.y2 = y2 + renderer.viewport.nearClippingSurface = nearClippingSurface + renderer.viewport.farClippingSurface = farClippingSurface + renderer.viewport.projectionSurface = projectionSurface + renderer.viewport.width = x2 - x1 + 1 + renderer.viewport.height = y2 - y1 + 1 + renderer.viewport.xCenter = math.floor(x1 + renderer.viewport.width / 2) + renderer.viewport.yCenter = math.floor(y1 + renderer.viewport.height / 2) +end + +function renderer.setPixelUsingDepthBuffer(x, y, pixelDepthValue, pixelColor) + if + renderer.isVertexInViewRange(x, y, pixelDepthValue) + then + if pixelDepthValue < renderer.depthBuffer[y][x] then + renderer.depthBuffer[y][x] = pixelDepthValue + buffer.semiPixelRawSet(buffer.getIndex(x, math.ceil(y / 2)), pixelColor, y % 2 == 0) + -- buffer.set(x, y, pixelColor, 0x0, " ") + end + end +end + +function renderer.isVertexInViewRange(x, y, z) + return + x >= renderer.viewport.x1 and + x <= renderer.viewport.x2 and + y >= renderer.viewport.y1 and + y <= renderer.viewport.y2 and + -- z >= renderer.viewport.projectionSurface - (renderer.viewport.projectionSurface - renderer.viewport.nearClippingSurface) * 0.6 and + z >= renderer.viewport.nearClippingSurface and + z <= renderer.viewport.farClippingSurface +end + +function renderer.visualizeDepthBuffer() + local minDepth, maxDepth = math.huge, -math.huge + for y = 1, #renderer.depthBuffer do + for x = 1, #renderer.depthBuffer[y] do + if renderer.depthBuffer[y][x] ~= math.huge then + minDepth, maxDepth = math.min(minDepth, renderer.depthBuffer[y][x]), math.max(maxDepth, renderer.depthBuffer[y][x]) + end + end + end + + local delta = math.abs(maxDepth - minDepth) + local grayscalePalette = { [0] = 0xFFFFFF, [1] = 0xEEEEEE, [2] = 0xDDDDDD, [3] = 0xCCCCCC, [4] = 0xBBBBBB, [5] = 0xAAAAAA, [6] = 0x999999, [7] = 0x888888, [8] = 0x777777, [9] = 0x666666, [10] = 0x555555, [11] = 0x444444, [12] = 0x333333, [13] = 0x222222, [14] = 0x111111, [15] = 0x000000 } + + for y = 1, #renderer.depthBuffer do + for x = 1, #renderer.depthBuffer[y] do + local value = (renderer.depthBuffer[y][x] - math.abs(minDepth)) / delta + local color = grayscalePalette[math.floor(#grayscalePalette * value)] + buffer.semiPixelSet(x, y, color or 0x0) + end + end +end + +-------------------------------------------------------- Line rendering -------------------------------------------------------- + +function renderer.renderLine(x1, y1, z1, x2, y2, z2, color) + local incycleValueFrom, incycleValueTo, outcycleValueFrom, outcycleValueTo, isReversed, incycleValueDelta, outcycleValueDelta = x1, x2, y1, y2, false, math.abs(x2 - x1), math.abs(y2 - y1) + if incycleValueDelta < outcycleValueDelta then + incycleValueFrom, incycleValueTo, outcycleValueFrom, outcycleValueTo, isReversed, incycleValueDelta, outcycleValueDelta = y1, y2, x1, x2, true, outcycleValueDelta, incycleValueDelta + end + + if outcycleValueFrom > outcycleValueTo then + outcycleValueFrom, outcycleValueTo = outcycleValueTo, outcycleValueFrom + incycleValueFrom, incycleValueTo = incycleValueTo, incycleValueFrom + z1, z2 = z2, z1 + end + + local outcycleValue, outcycleValueCounter, outcycleValueTriggerIncrement = outcycleValueFrom, 1, incycleValueDelta / outcycleValueDelta + local outcycleValueTrigger = outcycleValueTriggerIncrement + local z, zStep = z1, (z2 - z1) / incycleValueDelta + + for incycleValue = incycleValueFrom, incycleValueTo, incycleValueFrom < incycleValueTo and 1 or -1 do + if isReversed then + renderer.setPixelUsingDepthBuffer(outcycleValue, incycleValue, z, color) + else + renderer.setPixelUsingDepthBuffer(incycleValue, outcycleValue, z, color) + end + + outcycleValueCounter, z = outcycleValueCounter + 1, z + zStep + if outcycleValueCounter > outcycleValueTrigger then + outcycleValue, outcycleValueTrigger = outcycleValue + 1, outcycleValueTrigger + outcycleValueTriggerIncrement + end + end +end + +function renderer.renderDot(x, y, z, color) + renderer.setPixelUsingDepthBuffer(x, y, z, color) +end + +-------------------------------------------------------- Triangles render -------------------------------------------------------- + +local function getTriangleDrawingShit(points) + local topID, centerID, bottomID = 1, 1, 1 + + for i = 1, 3 do + points[i][2] = math.floor(points[i][2]) + if points[i][2] < points[topID][2] then topID = i end + if points[i][2] > points[bottomID][2] then bottomID = i end + end + for i = 1, 3 do if i ~= topID and i ~= bottomID then centerID = i end end + + local yCenterMinusYTop = points[centerID][2] - points[topID][2] + local yBottomMinusYTop = points[bottomID][2] - points[topID][2] + + local x1Screen, x2Screen = points[topID][1], points[topID][1] + local x1ScreenStep = (points[centerID][1] - points[topID][1]) / yCenterMinusYTop + local x2ScreenStep = (points[bottomID][1] - points[topID][1]) / yBottomMinusYTop + + local z1Screen, z2Screen = points[topID][3], points[topID][3] + local z1ScreenStep = (points[centerID][3] - points[topID][3]) / yCenterMinusYTop + local z2ScreenStep = (points[bottomID][3] - points[topID][3]) / yBottomMinusYTop + + return topID, centerID, bottomID, x1Screen, x2Screen, x1ScreenStep, x2ScreenStep, z1Screen, z2Screen, z1ScreenStep, z2ScreenStep +end + + +local function getTriangleSecondPartScreenCoordinates(points, centerID, bottomID) + -- return x1Screen, x1ScreenStep, z1Screen, z1ScreenStep + local yBottomMinusYCenter = points[bottomID][2] - points[centerID][2] + return + points[centerID][1], + (points[bottomID][1] - points[centerID][1]) / yBottomMinusYCenter, + points[centerID][3], + (points[bottomID][3] - points[centerID][3]) / yBottomMinusYCenter +end + +local function fillPart(x1Screen, x2Screen, z1Screen, z2Screen, y, color) + if x2Screen < x1Screen then + x1Screen, x2Screen, z1Screen, z2Screen = x2Screen, x1Screen, z2Screen, z1Screen + end + + local z, zStep = z1Screen, (z2Screen - z1Screen) / (x2Screen - x1Screen) + for x = math.floor(x1Screen), math.floor(x2Screen) do + renderer.setPixelUsingDepthBuffer(x, y, z, color) + z = z + zStep + end +end + +function renderer.renderFilledTriangle(points, color) + local topID, centerID, bottomID, x1Screen, x2Screen, x1ScreenStep, x2ScreenStep, z1Screen, z2Screen, z1ScreenStep, z2ScreenStep = getTriangleDrawingShit(points) + -- Рисуем первый кусок треугольника от верхней точки до центральной + for y = points[topID][2], points[centerID][2] - 1 do + fillPart(x1Screen, x2Screen, z1Screen, z2Screen, y, color) + x1Screen, x2Screen, z1Screen, z2Screen = x1Screen + x1ScreenStep, x2Screen + x2ScreenStep, z1Screen + z1ScreenStep, z2Screen + z2ScreenStep + end + + -- Далее считаем, как будет изменяться X от центрельной точки до нижней + x1Screen, x1ScreenStep, z1Screen, z1ScreenStep = getTriangleSecondPartScreenCoordinates(points, centerID, bottomID) + -- И рисуем нижний кусок треугольника от центральной точки до нижней + for y = points[centerID][2], points[bottomID][2] do + fillPart(x1Screen, x2Screen, z1Screen, z2Screen, y, color) + x1Screen, x2Screen, z1Screen, z2Screen = x1Screen + x1ScreenStep, x2Screen + x2ScreenStep, z1Screen + z1ScreenStep, z2Screen + z2ScreenStep + end +end + +local function fillTexturedPart(firstZ, secondZ, x1Screen, x2Screen, z1Screen, z2Screen, u1Texture, u2Texture, v1Texture, v2Texture, y, texture) + if x2Screen < x1Screen then + x1Screen, x2Screen, z1Screen, z2Screen = x2Screen, x1Screen, z2Screen, z1Screen + u1Texture, u2Texture = u2Texture, u1Texture + v1Texture, v2Texture = v2Texture, v1Texture + end + + local z, zStep = z1Screen, (z2Screen - z1Screen) / (x2Screen - x1Screen) + + -- secondZ - (v2Texture - v1Texture) + -- z - x + + u2Texture = u1Texture + (u2Texture - u1Texture) * (secondZ / z) + v2Texture = v1Texture + (v2Texture - v1Texture) * (secondZ / z) + + local u, uStep = u1Texture, (u2Texture - u1Texture) / (x2Screen - x1Screen) + local v, vStep = v1Texture, (v2Texture - v1Texture) / (x2Screen - x1Screen) + + -- buffer.text(1, 1, 0xFF00FF, "GOVNO: " .. math.abs(renderer.viewport.projectionSurface / z)) + + local color, uVal, vVal + for x = math.floor(x1Screen), math.floor(x2Screen) do + uVal, vVal = math.floor(u + 0.5), math.floor(v + 0.5) + if texture[vVal] and texture[vVal][uVal] then + color = texture[vVal][uVal] + else + color = 0x00FF00 + end + renderer.setPixelUsingDepthBuffer(x, y, z, color) + -- buffer.semiPixelSet(x, y, color) + z, u, v = z + zStep, u + uStep, v + vStep + end +end + +function renderer.renderTexturedTriangle(points, texture) + local topID, centerID, bottomID, x1Screen, x2Screen, x1ScreenStep, x2ScreenStep, z1Screen, z2Screen, z1ScreenStep, z2ScreenStep = getTriangleDrawingShit(points) + + local u1Texture, u2Texture = points[topID][4], points[topID][4] + local u1TextureStep = (points[centerID][4] - points[topID][4]) / (points[centerID][2] - points[topID][2]) + local u2TextureStep = (points[bottomID][4] - points[topID][4]) / (points[bottomID][2] - points[topID][2]) + + local v1Texture, v2Texture = points[topID][5], points[topID][5] + local v1TextureStep = (points[centerID][5] - points[topID][5]) / (points[centerID][2] - points[topID][2]) + local v2TextureStep = (points[bottomID][5] - points[topID][5]) / (points[bottomID][2] - points[topID][2]) + + for y = points[topID][2], points[centerID][2] - 1 do + fillTexturedPart(points[topID][3], points[bottomID][3], x1Screen, x2Screen, z1Screen, z2Screen, u1Texture, u2Texture, v1Texture, v2Texture, y, texture) + x1Screen, x2Screen, z1Screen, z2Screen = x1Screen + x1ScreenStep, x2Screen + x2ScreenStep, z1Screen + z1ScreenStep, z2Screen + z2ScreenStep + u1Texture, u2Texture, v1Texture, v2Texture = u1Texture + u1TextureStep, u2Texture + u2TextureStep, v1Texture + v1TextureStep, v2Texture + v2TextureStep + end + + x1Screen, x1ScreenStep, z1Screen, z1ScreenStep = getTriangleSecondPartScreenCoordinates(points, centerID, bottomID) + u1Texture, u1TextureStep = points[centerID][4], (points[bottomID][4] - points[centerID][4]) / (points[bottomID][2] - points[centerID][2]) + v1Texture, v1TextureStep = points[centerID][5], (points[bottomID][5] - points[centerID][5]) / (points[bottomID][2] - points[centerID][2]) + + for y = points[centerID][2], points[bottomID][2] do + fillTexturedPart(points[topID][3], points[bottomID][3], x1Screen, x2Screen, z1Screen, z2Screen, u1Texture, u2Texture, v1Texture, v2Texture, y, texture) + x1Screen, x2Screen, z1Screen, z2Screen = x1Screen + x1ScreenStep, x2Screen + x2ScreenStep, z1Screen + z1ScreenStep, z2Screen + z2ScreenStep + u1Texture, u2Texture, v1Texture, v2Texture = u1Texture + u1TextureStep, u2Texture + u2TextureStep, v1Texture + v1TextureStep, v2Texture + v2TextureStep + end + + -- for i = 1, 3 do + -- buffer.text(math.floor(points[i][1]), math.floor(points[i][2]), 0xFFFFFF, "ID " .. i .. ": u = " .. points[i][4] .. ", v = " .. points[topID][5]) + -- end +end + +-------------------------------------------------------- Floating text rendering -------------------------------------------------------- + +function renderer.renderFloatingText(x, y, z, color, text) + local textLength = unicode.len(text) + x, y = math.floor(x - textLength / 2), math.floor(y) + local yInteger, yFractional = math.modf(y / 2) + local index, background + + for i = 1, textLength do + if renderer.isVertexInViewRange(x, y, z) then + if z < renderer.depthBuffer[y][x] then + if yFractional == 0 then + renderer.depthBuffer[y - 1][x] = z + renderer.depthBuffer[y][x] = z + else + renderer.depthBuffer[y][x] = z + if renderer.depthBuffer[y + 1] then + renderer.depthBuffer[y + 1][x] = z + end + end + + index = buffer.getIndex(x, yInteger) + background = buffer.rawGet(index) + buffer.rawSet(index, background, color, unicode.sub(text, i, i)) + end + end + x = x + 1 + end +end + +-------------------------------------------------------- FPS counter overlay render -------------------------------------------------------- + +local function drawSegments(x, y, segments, color) + for i = 1, #segments do + if segments[i] == 1 then + buffer.semiPixelSquare(x, y, 3, 1, color) + elseif segments[i] == 2 then + buffer.semiPixelSquare(x + 2, y, 1, 3, color) + elseif segments[i] == 3 then + buffer.semiPixelSquare(x + 2, y + 2, 1, 3, color) + elseif segments[i] == 4 then + buffer.semiPixelSquare(x, y + 4, 3, 1, color) + elseif segments[i] == 5 then + buffer.semiPixelSquare(x, y + 2, 1, 3, color) + elseif segments[i] == 6 then + buffer.semiPixelSquare(x, y, 1, 3, color) + elseif segments[i] == 7 then + buffer.semiPixelSquare(x, y + 2, 3, 1, color) + else + error("Че за говно ты сюда напихал? Переделывай!") + end + end +end + +function renderer.renderFPSCounter(x, y, fps, color) + local numbers = { + ["0"] = { 1, 2, 3, 4, 5, 6 }, + ["1"] = { 2, 3 }, + ["2"] = { 1, 2, 4, 5, 7 }, + ["3"] = { 1, 2, 3, 4, 7 }, + ["4"] = { 2, 3, 6, 7 }, + ["5"] = { 1, 3, 4, 6, 7 }, + ["6"] = { 1, 3, 4, 5, 6, 7 }, + ["7"] = { 1, 2, 3 }, + ["8"] = { 1, 2, 3, 4, 5, 6, 7 }, + ["9"] = { 1, 2, 3, 4, 6, 7 }, + } + + for i = 1, #fps do + drawSegments(x, y, numbers[fps:sub(i, i)], color) + x = x + 4 + end +end + +------------------------------------------------------------------------------------------------------------------------ + +-- buffer.start() +-- buffer.clear(0xFFFFFF) + +-- local texture = materials.newDebugTexture(16, 16, 0xFF00FF, 0x000000) +-- renderer.renderTexturedTriangle({ +-- {2, 2, 1, 1, 1}, +-- {2, 52, 1, 1, 16}, +-- {52, 52, 1, 16, 16}, +-- }, texture) +-- renderer.renderTexturedTriangle({ +-- {2, 2, 1, 1, 1}, +-- {52, 2, 1, 16, 1}, +-- {52, 52, 1, 16, 16}, +-- }, texture) + +-- buffer.draw(true) + +------------------------------------------------------------------------------------------------------------------------ + +return renderer + diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/SHA2.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/SHA2.lua new file mode 100755 index 00000000..a2420c7a --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/SHA2.lua @@ -0,0 +1,243 @@ +-- SHA-256 code in Lua 5.2; based on the pseudo-code from +-- Wikipedia (http://en.wikipedia.org/wiki/SHA-2) + +local bit32 = require("bit32") + +local band, rrotate, bxor, rshift, bnot = + bit32.band, bit32.rrotate, bit32.bxor, bit32.rshift, bit32.bnot + +local string, setmetatable, assert = string, setmetatable, assert + +_ENV = nil + +-- Initialize table of round constants +-- (first 32 bits of the fractional parts of the cube roots of the first +-- 64 primes 2..311): +local k = { + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, + 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, + 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, + 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, + 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, + 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, + 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, + 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, + 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, + 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, + 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, + 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, + 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, + 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2, +} + + +-- transform a string of bytes in a string of hexadecimal digits +local function str2hexa (s) + local h = string.gsub(s, ".", function(c) + return string.format("%02x", string.byte(c)) + end) + return h +end + + +-- transform number 'l' in a big-endian sequence of 'n' bytes +-- (coded as a string) +local function num2s (l, n) + local s = "" + for i = 1, n do + local rem = l % 256 + s = string.char(rem) .. s + l = (l - rem) / 256 + end + return s +end + +-- transform the big-endian sequence of four bytes starting at +-- index 'i' in 's' into a number +local function s232num (s, i) + local n = 0 + for i = i, i + 3 do + n = n*256 + string.byte(s, i) + end + return n +end + + +-- append the bit '1' to the message +-- append k bits '0', where k is the minimum number >= 0 such that the +-- resulting message length (in bits) is congruent to 448 (mod 512) +-- append length of message (before pre-processing), in bits, as 64-bit +-- big-endian integer +local function preproc (msg, len) + local extra = -(len + 1 + 8) % 64 + len = num2s(8 * len, 8) -- original len in bits, coded + msg = msg .. "\128" .. string.rep("\0", extra) .. len + assert(#msg % 64 == 0) + return msg +end + + +local function initH224 (H) + -- (second 32 bits of the fractional parts of the square roots of the + -- 9th through 16th primes 23..53) + H[1] = 0xc1059ed8 + H[2] = 0x367cd507 + H[3] = 0x3070dd17 + H[4] = 0xf70e5939 + H[5] = 0xffc00b31 + H[6] = 0x68581511 + H[7] = 0x64f98fa7 + H[8] = 0xbefa4fa4 + return H +end + + +local function initH256 (H) + -- (first 32 bits of the fractional parts of the square roots of the + -- first 8 primes 2..19): + H[1] = 0x6a09e667 + H[2] = 0xbb67ae85 + H[3] = 0x3c6ef372 + H[4] = 0xa54ff53a + H[5] = 0x510e527f + H[6] = 0x9b05688c + H[7] = 0x1f83d9ab + H[8] = 0x5be0cd19 + return H +end + + +local function digestblock (msg, i, H) + + -- break chunk into sixteen 32-bit big-endian words w[1..16] + local w = {} + for j = 1, 16 do + w[j] = s232num(msg, i + (j - 1)*4) + end + + -- Extend the sixteen 32-bit words into sixty-four 32-bit words: + for j = 17, 64 do + local v = w[j - 15] + local s0 = bxor(rrotate(v, 7), rrotate(v, 18), rshift(v, 3)) + v = w[j - 2] + local s1 = bxor(rrotate(v, 17), rrotate(v, 19), rshift(v, 10)) + w[j] = w[j - 16] + s0 + w[j - 7] + s1 + end + + -- Initialize hash value for this chunk: + local a, b, c, d, e, f, g, h = + H[1], H[2], H[3], H[4], H[5], H[6], H[7], H[8] + + -- Main loop: + for i = 1, 64 do + local s0 = bxor(rrotate(a, 2), rrotate(a, 13), rrotate(a, 22)) + local maj = bxor(band(a, b), band(a, c), band(b, c)) + local t2 = s0 + maj + local s1 = bxor(rrotate(e, 6), rrotate(e, 11), rrotate(e, 25)) + local ch = bxor (band(e, f), band(bnot(e), g)) + local t1 = h + s1 + ch + k[i] + w[i] + + h = g + g = f + f = e + e = d + t1 + d = c + c = b + b = a + a = t1 + t2 + end + + -- Add (mod 2^32) this chunk's hash to result so far: + H[1] = band(H[1] + a) + H[2] = band(H[2] + b) + H[3] = band(H[3] + c) + H[4] = band(H[4] + d) + H[5] = band(H[5] + e) + H[6] = band(H[6] + f) + H[7] = band(H[7] + g) + H[8] = band(H[8] + h) + +end + + +local function finalresult224 (H) + -- Produce the final hash value (big-endian): + return + str2hexa(num2s(H[1], 4)..num2s(H[2], 4)..num2s(H[3], 4)..num2s(H[4], 4).. + num2s(H[5], 4)..num2s(H[6], 4)..num2s(H[7], 4)) +end + + +local function finalresult256 (H) + -- Produce the final hash value (big-endian): + return + str2hexa(num2s(H[1], 4)..num2s(H[2], 4)..num2s(H[3], 4)..num2s(H[4], 4).. + num2s(H[5], 4)..num2s(H[6], 4)..num2s(H[7], 4)..num2s(H[8], 4)) +end + + +---------------------------------------------------------------------- +local HH = {} -- to reuse + +local function hash224 (msg) + msg = preproc(msg, #msg) + local H = initH224(HH) + + -- Process the message in successive 512-bit (64 bytes) chunks: + for i = 1, #msg, 64 do + digestblock(msg, i, H) + end + + return finalresult224(H) +end + + +local function hash256 (msg) + msg = preproc(msg, #msg) + local H = initH256(HH) + + -- Process the message in successive 512-bit (64 bytes) chunks: + for i = 1, #msg, 64 do + digestblock(msg, i, H) + end + + return finalresult256(H) +end +---------------------------------------------------------------------- +-- local mt = {} + +-- local function new256 () +-- local o = {H = initH256({}), msg = "", len = 0} +-- setmetatable(o, mt) +-- return o +-- end + +-- mt.__index = mt + +-- function mt:add (m) +-- self.msg = self.msg .. m +-- self.len = self.len + #m +-- local t = 0 +-- while #self.msg - t >= 64 do +-- digestblock(self.msg, t + 1, self.H) +-- t = t + 64 +-- end +-- self.msg = self.msg:sub(t + 1, -1) +-- end + + +-- function mt:close () +-- self.msg = preproc(self.msg, self.len) +-- self:add("") +-- return finalresult256(self.H) +-- end +---------------------------------------------------------------------- + +return { + hash = hash256 + -- hash256 = hash256, + -- hash224 = hash224, + -- new256 = new256, +} \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/advancedLua.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/advancedLua.lua new file mode 100755 index 00000000..8e25b072 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/advancedLua.lua @@ -0,0 +1,581 @@ + +local filesystem = require("filesystem") +local unicode = require("unicode") +local bit32 = require("bit32") + +---------------------------------------------------------------------------------------------------- + +function _G.getCurrentScript() + local info + for runLevel = 0, math.huge do + info = debug.getinfo(runLevel) + if info then + if info.what == "main" then + return info.source:sub(2, -1) + end + else + error("Failed to get debug info for runlevel " .. runLevel) + end + end +end + +function _G.enum(...) + local enums = {...} + for i = 1, #enums do + enums[enums[i]] = i + enums[i] = nil + end + + return enums +end + +---------------------------------------------------------------------------------------------------- + +function bit32.merge(number2, number1) + local cutter = math.ceil(math.log(number1 + 1, 256)) * 8 + while number2 > 0 do + number1, number2, cutter = bit32.bor(bit32.lshift(bit32.band(number2, 0xFF), cutter), number1), bit32.rshift(number2, 8), cutter + 8 + end + + return number1 +end + +function bit32.numberToByteArray(number) + local byteArray = {} + + repeat + table.insert(byteArray, 1, bit32.band(number, 0xFF)) + number = bit32.rshift(number, 8) + until number <= 0 + + return byteArray +end + +function bit32.numberToFixedSizeByteArray(number, size) + local byteArray, counter = {}, 0 + + repeat + table.insert(byteArray, 1, bit32.band(number, 0xFF)) + number = bit32.rshift(number, 8) + counter = counter + 1 + until number <= 0 + + for i = 1, size - counter do + table.insert(byteArray, 1, 0x0) + end + + return byteArray +end + +function bit32.byteArrayToNumber(byteArray) + local result = byteArray[1] + for i = 2, #byteArray do + result = bit32.bor(bit32.lshift(result, 8), byteArray[i]) + end + + return result +end + +function bit32.bitArrayToByte(bitArray) + local result = 0 + for i = 1, #bitArray do + result = bit32.bor(bitArray[i], bit32.lshift(result, 1)) + end + + return result +end + +---------------------------------------------------------------------------------------------------- + +function math.round(num) + if num >= 0 then + return math.floor(num + 0.5) + else + return math.ceil(num - 0.5) + end +end + +function math.roundToDecimalPlaces(num, decimalPlaces) + local mult = 10 ^ (decimalPlaces or 0) + return math.round(num * mult) / mult +end + +function math.getDigitCount(num) + return num == 0 and 1 or math.ceil(math.log(num + 1, 10)) +end + +function math.doubleToString(num, digitCount) + return string.format("%." .. (digitCount or 1) .. "f", num) +end + +function math.shorten(number, digitCount) + local shortcuts = { + "K", + "M", + "B", + "T" + } + + local index = math.floor(math.log(number, 1000)) + if number < 1000 then + return number + elseif index > #shortcuts then + index = #shortcuts + end + + return math.roundToDecimalPlaces(number / 1000 ^ index, digitCount) .. shortcuts[index] +end + +---------------------------------------------------------------------------------------------------- + +-- function filesystem.path(path) +-- return path:match("^(.+%/).") or "" +-- end + +-- function filesystem.name(path) +-- return path:match("%/?([^%/]+)%/?$") +-- end + +function filesystem.extension(path, lower) + local extension = path:match("[^%/]+(%.[^%/]+)%/?$") + return (lower and extension) and (unicode.lower(extension)) or extension +end + +function filesystem.hideExtension(path) + return path:match("(.+)%..+") or path +end + +function filesystem.isFileHidden(path) + if path:match("^%..+$") then + return true + end + + return false +end + +function filesystem.sortedList(path, sortingMethod, showHiddenFiles, filenameMatcher, filenameMatcherCaseSensitive) + if not filesystem.exists(path) then + error("Failed to get file list: directory \"" .. tostring(path) .. "\" doesn't exists") + end + + if not filesystem.isDirectory(path) then + error("Failed to get file list: path \"" .. tostring(path) .. "\" is not a directory") + end + + local fileList, sortedFileList = {}, {} + for file in filesystem.list(path) do + if not filenameMatcher or string.unicodeFind(filenameMatcherCaseSensitive and file or unicode.lower(file), filenameMatcherCaseSensitive and filenameMatcher or unicode.lower(filenameMatcher)) then + table.insert(fileList, file) + end + end + + if #fileList > 0 then + if sortingMethod == "type" then + local extension + for i = 1, #fileList do + extension = filesystem.extension(fileList[i]) or "Script" + if filesystem.isDirectory(path .. fileList[i]) and extension ~= ".app" then + extension = ".01_Folder" + end + fileList[i] = {fileList[i], extension} + end + + table.sort(fileList, function(a, b) return unicode.lower(a[2]) < unicode.lower(b[2]) end) + + local currentExtensionList, currentExtension = {}, fileList[1][2] + for i = 1, #fileList do + if currentExtension == fileList[i][2] then + table.insert(currentExtensionList, fileList[i][1]) + else + table.sort(currentExtensionList, function(a, b) return unicode.lower(a) < unicode.lower(b) end) + for j = 1, #currentExtensionList do + table.insert(sortedFileList, currentExtensionList[j]) + end + currentExtensionList, currentExtension = {fileList[i][1]}, fileList[i][2] + end + end + + table.sort(currentExtensionList, function(a, b) return unicode.lower(a) < unicode.lower(b) end) + + for j = 1, #currentExtensionList do + table.insert(sortedFileList, currentExtensionList[j]) + end + elseif sortingMethod == "name" then + sortedFileList = fileList + table.sort(sortedFileList, function(a, b) return unicode.lower(a) < unicode.lower(b) end) + elseif sortingMethod == "date" then + for i = 1, #fileList do + fileList[i] = {fileList[i], filesystem.lastModified(path .. fileList[i])} + end + + table.sort(fileList, function(a, b) return unicode.lower(a[2]) > unicode.lower(b[2]) end) + + for i = 1, #fileList do + table.insert(sortedFileList, fileList[i][1]) + end + else + error("Unknown sorting method: " .. tostring(sortingMethod)) + end + + local i = 1 + while i <= #sortedFileList do + if not showHiddenFiles and filesystem.isFileHidden(sortedFileList[i]) then + table.remove(sortedFileList, i) + else + i = i + 1 + end + end + end + + return sortedFileList +end + +function filesystem.directorySize(path) + local size = 0 + for file in filesystem.list(path) do + if filesystem.isDirectory(path .. file) then + size = size + filesystem.directorySize(path .. file) + else + size = size + filesystem.size(path .. file) + end + end + + return size +end + +function filesystem.readUnicodeChar(file) + local byteArray = {string.byte(file:read(1))} + + local nullBitPosition = 0 + for i = 1, 7 do + if bit32.band(bit32.rshift(byteArray[1], 8 - i), 0x1) == 0x0 then + nullBitPosition = i + break + end + end + + for i = 1, nullBitPosition - 2 do + table.insert(byteArray, string.byte(file:read(1))) + end + + return string.char(table.unpack(byteArray)) +end + +---------------------------------------------------------------------------------------------------- + +function table.serialize(array, prettyLook, indentationWidth, indentUsingTabs, recursionStackLimit) + checkArg(1, array, "table") + + recursionStackLimit = recursionStackLimit or math.huge + local indentationSymbolAdder = string.rep(indentUsingTabs and " " or " ", indentationWidth or 2) + local equalsSymbol = prettyLook and " = " or "=" + + local function serializeRecursively(array, currentIndentationSymbol, currentRecusrionStack) + local result, nextIndentationSymbol, keyType, valueType, stringValue = {"{"}, currentIndentationSymbol .. indentationSymbolAdder + + if prettyLook then + table.insert(result, "\n") + end + + for key, value in pairs(array) do + keyType, valueType, stringValue = type(key), type(value), tostring(value) + + if prettyLook then + table.insert(result, nextIndentationSymbol) + end + + if keyType == "number" then + table.insert(result, "[") + table.insert(result, key) + table.insert(result, "]") + table.insert(result, equalsSymbol) + elseif keyType == "string" then + -- Короч, если типа начинается с буковки, а также если это алфавитно-нумерическая поеботня + if prettyLook and key:match("^%a") and key:match("^[%w%_]+$") then + table.insert(result, key) + else + table.insert(result, "[\"") + table.insert(result, key) + table.insert(result, "\"]") + end + + table.insert(result, equalsSymbol) + end + + if valueType == "number" or valueType == "boolean" or valueType == "nil" then + table.insert(result, stringValue) + elseif valueType == "string" or valueType == "function" then + table.insert(result, "\"") + table.insert(result, stringValue) + table.insert(result, "\"") + elseif valueType == "table" then + if currentRecusrionStack < recursionStackLimit then + table.insert( + result, + table.concat( + serializeRecursively( + value, + nextIndentationSymbol, + currentRecusrionStack + 1 + ) + ) + ) + else + table.insert(result, "\"…\"") + end + end + + table.insert(result, ",") + + if prettyLook then + table.insert(result, "\n") + end + end + + -- Удаляем запятую + if prettyLook then + if #result > 2 then + table.remove(result, #result - 1) + end + + table.insert(result, currentIndentationSymbol) + else + if #result > 1 then + table.remove(result, #result) + end + end + + table.insert(result, "}") + + return result + end + + return table.concat(serializeRecursively(array, "", 1)) +end + +function table.unserialize(serializedString) + checkArg(1, serializedString, "string") + + local result, reason = load("return " .. serializedString) + if result then + result, reason = pcall(result) + if result then + return reason + else + return nil, reason + end + else + return nil, reason + end +end + +table.toString = table.serialize +table.fromString = table.unserialize + +function table.toFile(path, array, prettyLook, indentationWidth, indentUsingTabs, recursionStackLimit, appendToFile) + checkArg(1, path, "string") + checkArg(2, array, "table") + + filesystem.makeDirectory(filesystem.path(path) or "") + + local file, reason = io.open(path, appendToFile and "a" or "w") + if file then + file:write(table.serialize(array, prettyLook, indentationWidth, indentUsingTabs, recursionStackLimit)) + file:close() + else + error("Failed to open file for writing: " .. tostring(reason)) + end +end + +function table.fromFile(path) + checkArg(1, path, "string") + + if filesystem.exists(path) then + if filesystem.isDirectory(path) then + error("\"" .. path .. "\" is a directory") + else + local file = io.open(path, "r") + local data = table.unserialize(file:read("*a")) + file:close() + return data + end + else + error("\"" .. path .. "\" doesn't exists") + end +end + +function table.copy(tableToCopy) + local function copyTableRecursively(source, destination) + for key, value in pairs(source) do + if type(value) == "table" then + destination[key] = {} + doTableCopy(source[key], destination[key]) + else + destination[key] = value + end + end + end + + local result = {} + copyTableRecursively(tableToCopy, result) + + return result +end + +function table.size(t) + local size = 0 + for key in pairs(t) do size = size + 1 end + return size +end + +function table.contains(t, object) + for _, value in pairs(t) do + if value == object then + return true + end + end + return false +end + +function table.indexOf(t, object) + for i = 1, #t do + if t[i] == object then + return i + end + end +end + +function table.sortAlphabetically(t) + table.sort(t, function(a, b) return a < b end) +end + +---------------------------------------------------------------------------------------------------- + +function string.brailleChar(a, b, c, d, e, f, g, h) + return unicode.char(10240 + 128*h + 64*g + 32*f + 16*d + 8*b + 4*e + 2*c + a) +end + +function string.canonicalPath(str) + return string.gsub("/" .. str, "%/+", "/") +end + +function string.optimize(str, indentationWidth) + str = string.gsub(str, "\r\n", "\n") + str = string.gsub(str, " ", string.rep(" ", indentationWidth or 2)) + return str +end + +function string.optimizeForURLRequests(code) + if code then + code = string.gsub(code, "([^%w ])", function (c) + return string.format("%%%02X", string.byte(c)) + end) + code = string.gsub(code, " ", "+") + end + return code +end + +function string.unicodeFind(str, pattern, init, plain) + if init then + if init < 0 then + init = -#unicode.sub(str, init) + elseif init > 0 then + init = #unicode.sub(str, 1, init - 1) + 1 + end + end + + a, b = string.find(str, pattern, init, plain) + + if a then + local ap, bp = str:sub(1, a - 1), str:sub(a,b) + a = unicode.len(ap) + 1 + b = a + unicode.len(bp) - 1 + return a, b + else + return a + end +end + +function string.limit(s, limit, mode, noDots) + local length = unicode.len(s) + if length <= limit then return s end + + if mode == "left" then + if noDots then + return unicode.sub(s, length - limit + 1, -1) + else + return "…" .. unicode.sub(s, length - limit + 2, -1) + end + elseif mode == "center" then + local integer, fractional = math.modf(limit / 2) + if fractional == 0 then + return unicode.sub(s, 1, integer) .. "…" .. unicode.sub(s, -integer + 1, -1) + else + return unicode.sub(s, 1, integer) .. "…" .. unicode.sub(s, -integer, -1) + end + else + if noDots then + return unicode.sub(s, 1, limit) + else + return unicode.sub(s, 1, limit - 1) .. "…" + end + end +end + +function string.wrap(data, limit) + if type(data) == "string" then data = {data} end + + local wrappedLines, result, preResult, position = {} + + -- Дублируем таблицу строк, шоб не перекосоебить ченить переносами + for i = 1, #data do + wrappedLines[i] = data[i] + end + + -- Отсечение возврата каретки-ебуретки + local i = 1 + while i <= #wrappedLines do + local position = string.unicodeFind(wrappedLines[i], "\n") + if position then + table.insert(wrappedLines, i + 1, unicode.sub(wrappedLines[i], position + 1, -1)) + wrappedLines[i] = unicode.sub(wrappedLines[i], 1, position - 1) + end + + i = i + 1 + end + + -- Сам перенос + local i = 1 + while i <= #wrappedLines do + result = "" + + for word in wrappedLines[i]:gmatch("[^%s]+") do + preResult = result .. word + + if unicode.len(preResult) > limit then + if unicode.len(word) > limit then + table.insert(wrappedLines, i + 1, unicode.sub(wrappedLines[i], limit + 1, -1)) + result = unicode.sub(wrappedLines[i], 1, limit) + else + table.insert(wrappedLines, i + 1, unicode.sub(wrappedLines[i], unicode.len(result) + 1, -1)) + end + + break + else + result = preResult .. " " + end + end + + wrappedLines[i] = result:gsub("%s+$", ""):gsub("^%s+", "") + + i = i + 1 + end + + return wrappedLines +end + +---------------------------------------------------------------------------------------------------- + +return {loaded = true} + + diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/archive.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/archive.lua new file mode 100755 index 00000000..3ac0a294 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/archive.lua @@ -0,0 +1,97 @@ + +local fs = require("filesystem") +local computer = require("computer") +local component = require("component") + +----------------------------------------------------------------------------------------------- + +local archive = { + formatModules = {}, +} + +----------------------------------------------------------------------------------------------- + +function archive.loadFormatModule(path) + local loadedModule, result = loadfile(path) + if loadedModule then + local success, result = pcall(loadedModule, image) + if success then + table.insert(archive.formatModules, result) + return archive.formatModules[#archive.formatModules] + else + error("Failed to call format module: " .. tostring(result)) + end + else + error("Failed to load format module: " .. tostring(result)) + end +end + +----------------------------------------------------------------------------------------------- + +function archive.pack(archivePath, fileList, formatModuleID, encodingMethod) + if type(fileList) ~= "table" then + fileList = {fileList} + end + formatModuleID = formatModuleID or 1 + + if archive.formatModules[formatModuleID] then + if archive.formatModules[formatModuleID].pack then + return archive.formatModules[formatModuleID].pack(archivePath, fileList, encodingMethod or 0) + else + return false, "Format module doesn't have .pack() method" + end + else + return false, "Format module with " .. tostring(formatModuleID) .. " doesn't exists" + end +end + +function archive.unpack(archivePath, unpackPath, formatModuleID) + if fs.exists(archivePath) then + formatModuleID = formatModuleID or 1 + + if archive.formatModules[formatModuleID] then + if archive.formatModules[formatModuleID].pack then + return archive.formatModules[formatModuleID].unpack(archivePath, unpackPath) + else + return false, "Format module doesn't have .unpack() method" + end + else + return false, "Format module with " .. tostring(formatModuleID) .. " doesn't exists" + end + else + return false, "Archive file \"" .. tostring(archivePath) .. "\" doesn't exists" + end +end + +----------------------------------------------------------------------------------------------- + +archive.loadFormatModule("/lib/FormatModules/OCAF.lua") + +----------------------------------------------------------------------------------------------- + +-- print("Packing...") +-- print( +-- archive.pack("/1.arc", { +-- "/MineOS/Applications/Finder.app/", +-- "/OS.lua", +-- "/usr/", +-- "/lib/", +-- }) +-- ) + +-- print("Unpacking...") +-- fs.remove("/unpacked/") +-- fs.makeDirectory("/unpacked/") +-- print( +-- archive.unpack("/1.arc", "/unpacked/") +-- ) + +----------------------------------------------------------------------------------------------- + +return archive + + + + + + diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/bigLetters.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/bigLetters.lua new file mode 100755 index 00000000..384041a0 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/bigLetters.lua @@ -0,0 +1,614 @@ + +local unicode = require("unicode") +local buffer = require("doubleBuffering") +local bigLetters = {} + +local pixelHeight = 5 +local lettersInterval = 2 +local unknownSymbol = "*" +local spaceWidth = 2 + +local letters = { + ["0"] = { + { 1, 1, 1 }, + { 1, 0, 1 }, + { 1, 0, 1 }, + { 1, 0, 1 }, + { 1, 1, 1 }, + }, + ["1"] = { + { 0, 1, 0 }, + { 1, 1, 0 }, + { 0, 1, 0 }, + { 0, 1, 0 }, + { 1, 1, 1 }, + }, + ["2"] = { + { 1, 1, 1 }, + { 0, 0, 1 }, + { 1, 1, 1 }, + { 1, 0, 0 }, + { 1, 1, 1 }, + }, + ["3"] = { + { 1, 1, 1 }, + { 0, 0, 1 }, + { 1, 1, 1 }, + { 0, 0, 1 }, + { 1, 1, 1 }, + }, + ["4"] = { + { 1, 0, 1 }, + { 1, 0, 1 }, + { 1, 1, 1 }, + { 0, 0, 1 }, + { 0, 0, 1 }, + }, + ["5"] = { + { 1, 1, 1 }, + { 1, 0, 0 }, + { 1, 1, 1 }, + { 0, 0, 1 }, + { 1, 1, 1 }, + }, + ["6"] = { + { 1, 1, 1 }, + { 1, 0, 0 }, + { 1, 1, 1 }, + { 1, 0, 1 }, + { 1, 1, 1 }, + }, + ["7"] = { + { 1, 1, 1 }, + { 0, 0, 1 }, + { 0, 0, 1 }, + { 0, 0, 1 }, + { 0, 0, 1 }, + }, + ["8"] = { + { 1, 1, 1 }, + { 1, 0, 1 }, + { 1, 1, 1 }, + { 1, 0, 1 }, + { 1, 1, 1 }, + }, + ["9"] = { + { 1, 1, 1 }, + { 1, 0, 1 }, + { 1, 1, 1 }, + { 0, 0, 1 }, + { 1, 1, 1 }, + }, + + + + + ["a"] = { + { 0, 1, 1, 0 }, + { 1, 0, 0, 1 }, + { 1, 1, 1, 1 }, + { 1, 0, 0, 1 }, + { 1, 0, 0, 1 }, + }, + ["b"] = { + { 1, 1, 1, 0}, + { 1, 0, 0, 1}, + { 1, 1, 1, 0}, + { 1, 0, 0, 1}, + { 1, 1, 1, 1}, + }, + ["c"] = { + { 0, 1, 1, 1 }, + { 1, 0, 0, 0 }, + { 1, 0, 0, 0 }, + { 1, 0, 0, 0 }, + { 0, 1, 1, 1 }, + }, + ["d"] = { + { 1, 1, 1, 1, 0 }, + { 0, 1, 0, 0, 1 }, + { 0, 1, 0, 0, 1 }, + { 0, 1, 0, 0, 1 }, + { 1, 1, 1, 1, 0 }, + }, + ["e"] = { + { 1, 1, 1, 1 }, + { 1, 0, 0, 0 }, + { 1, 1, 1, 0 }, + { 1, 0, 0, 0 }, + { 1, 1, 1, 1 }, + }, + ["f"] = { + { 1, 1, 1, 1 }, + { 1, 0, 0, 0 }, + { 1, 1, 1, 0 }, + { 1, 0, 0, 0 }, + { 1, 0, 0, 0 }, + }, + ["g"] = { + { 0, 1, 1, 1}, + { 1, 0, 0, 0}, + { 1, 0, 1, 1}, + { 1, 0, 0, 1}, + { 0, 1, 1, 1}, + }, + ["h"] = { + { 1, 0, 0, 1}, + { 1, 0, 0, 1}, + { 1, 1, 1, 1}, + { 1, 0, 0, 1}, + { 1, 0, 0, 1}, + }, + ["i"] = { + { 1, 1, 1}, + { 0, 1, 0}, + { 0, 1, 0}, + { 0, 1, 0}, + { 1, 1, 1}, + }, + ["j"] = { + { 0, 0, 1}, + { 0, 0, 1}, + { 0, 0, 1}, + { 1, 0, 1}, + { 0, 1, 0}, + }, + ["k"] = { + { 1, 0, 0, 1}, + { 1, 0, 1, 0}, + { 1, 1, 0, 0}, + { 1, 0, 1, 0}, + { 1, 0, 0, 1}, + }, + ["l"] = { + { 1, 0, 0}, + { 1, 0, 0}, + { 1, 0, 0}, + { 1, 0, 0}, + { 1, 1, 1}, + }, + ["m"] = { + { 1, 0, 0, 0, 1 }, + { 1, 1, 0, 1, 1 }, + { 1, 0, 1, 0, 1 }, + { 1, 0, 0, 0, 1 }, + { 1, 0, 0, 0, 1 }, + }, + ["n"] = { + { 1, 0, 0, 0, 1 }, + { 1, 1, 0, 0, 1 }, + { 1, 0, 1, 0, 1 }, + { 1, 0, 0, 1, 1 }, + { 1, 0, 0, 0, 1 }, + }, + ["o"] = { + { 0, 1, 1, 0}, + { 1, 0, 0, 1}, + { 1, 0, 0, 1}, + { 1, 0, 0, 1}, + { 0, 1, 1, 0}, + }, + ["p"] = { + { 1, 1, 1, 0 }, + { 1, 0, 0, 1 }, + { 1, 1, 1, 0 }, + { 1, 0, 0, 0 }, + { 1, 0, 0, 0 }, + }, + ["q"] = { + { 0, 1, 1, 0}, + { 1, 0, 0, 1}, + { 1, 0, 0, 1}, + { 1, 0, 1, 1}, + { 0, 1, 1, 0}, + }, + ["r"] = { + { 1, 1, 1, 0}, + { 1, 0, 0, 1}, + { 1, 1, 1, 0}, + { 1, 0, 0, 1}, + { 1, 0, 0, 1}, + }, + ["s"] = { + { 0, 1, 1, 1}, + { 1, 0, 0, 0}, + { 0, 1, 1, 0}, + { 0, 0, 0, 1}, + { 1, 1, 1, 0}, + }, + ["t"] = { + { 1, 1, 1, 1, 1 }, + { 0, 0, 1, 0, 0 }, + { 0, 0, 1, 0, 0 }, + { 0, 0, 1, 0, 0 }, + { 0, 0, 1, 0, 0 }, + }, + ["u"] = { + { 1, 0, 0, 1}, + { 1, 0, 0, 1}, + { 1, 0, 0, 1}, + { 1, 0, 0, 1}, + { 0, 1, 1, 0}, + }, + ["v"] = { + { 1, 0, 0, 0, 1 }, + { 1, 0, 0, 0, 1 }, + { 1, 0, 0, 0, 1 }, + { 0, 1, 0, 1, 0 }, + { 0, 0, 1, 0, 0 }, + }, + ["w"] = { + { 1, 0, 0, 0, 1 }, + { 1, 0, 0, 0, 1 }, + { 1, 0, 1, 0, 1 }, + { 1, 0, 1, 0, 1 }, + { 0, 1, 0, 1, 0 }, + }, + ["x"] = { + { 1, 0, 0, 0, 1 }, + { 0, 1, 0, 1, 0 }, + { 0, 0, 1, 0, 0 }, + { 0, 1, 0, 1, 0 }, + { 1, 0, 0, 0, 1 }, + }, + ["y"] = { + { 1, 0, 0, 1 }, + { 1, 0, 0, 1 }, + { 0, 1, 1, 1 }, + { 0, 0, 0, 1 }, + { 1, 1, 1, 0 }, + }, + ["z"] = { + { 1, 1, 1, 1, 1 }, + { 0, 0, 0, 1, 0 }, + { 0, 0, 1, 0, 0 }, + { 0, 1, 0, 0, 0 }, + { 1, 1, 1, 1, 1 }, + }, + ["а"] = { + { 0, 1, 1, 0 }, + { 1, 0, 0, 1 }, + { 1, 1, 1, 1 }, + { 1, 0, 0, 1 }, + { 1, 0, 0, 1 }, + }, + ["б"] = { + { 1, 1, 1, 1 }, + { 1, 0, 0, 0 }, + { 1, 1, 1, 0 }, + { 1, 0, 0, 1 }, + { 1, 1, 1, 0 }, + }, + ["в"] = { + { 1, 1, 1, 0 }, + { 1, 0, 0, 1 }, + { 1, 1, 1, 0 }, + { 1, 0, 0, 1 }, + { 1, 1, 1, 0 }, + }, + ["г"] = { + { 1, 1, 1 }, + { 1, 0, 0 }, + { 1, 0, 0 }, + { 1, 0, 0 }, + { 1, 0, 0 }, + }, + ["д"] = { + { 0, 0, 1, 1, 0 }, + { 0, 1, 0, 1, 0 }, + { 0, 1, 0, 1, 0 }, + { 0, 1, 0, 1, 0 }, + { 1, 1, 1, 1, 1 }, + }, + ["е"] = { + { 1, 1, 1, 1 }, + { 1, 0, 0, 0 }, + { 1, 1, 1, 0 }, + { 1, 0, 0, 0 }, + { 1, 1, 1, 1 }, + }, + ["ё"] = { + { 1, 0, 1, 0 }, + { 0, 0, 0, 0 }, + { 1, 1, 1, 1 }, + { 1, 0, 0, 0 }, + { 1, 1, 1, 0 }, + { 1, 0, 0, 0 }, + { 1, 1, 1, 1 }, + }, + ["ж"] = { + { 1, 0, 1, 0, 1 }, + { 0, 1, 1, 1, 0 }, + { 0, 0, 1, 0, 0 }, + { 0, 1, 1, 1, 0 }, + { 1, 0, 1, 0, 1 }, + }, + ["з"] = { + { 0, 1, 1, 1, 0 }, + { 0, 0, 0, 0, 1 }, + { 0, 0, 1, 1, 0 }, + { 0, 0, 0, 0, 1 }, + { 0, 1, 1, 1, 0 }, + }, + ["и"] = { + { 1, 0, 0, 0, 1 }, + { 1, 0, 0, 1, 1 }, + { 1, 0, 1, 0, 1 }, + { 1, 1, 0, 0, 1 }, + { 1, 0, 0, 0, 1 }, + }, + ["й"] = { + { 0, 1, 1, 1, 0 }, + { 0, 0, 0, 0, 0 }, + { 1, 0, 0, 0, 1 }, + { 1, 0, 0, 1, 1 }, + { 1, 0, 1, 0, 1 }, + { 1, 1, 0, 0, 1 }, + { 1, 0, 0, 0, 1 }, + }, + ["к"] = { + { 1, 0, 0, 1, 0 }, + { 1, 0, 1, 0, 0 }, + { 1, 1, 0, 0, 0 }, + { 1, 0, 1, 0, 0 }, + { 1, 0, 0, 1, 0 }, + }, + ["л"] = { + { 0, 0, 1, 1 }, + { 0, 1, 0, 1 }, + { 0, 1, 0, 1 }, + { 1, 0, 0, 1 }, + { 1, 0, 0, 1 }, + }, + ["м"] = { + { 1, 0, 0, 0, 1 }, + { 1, 1, 0, 1, 1 }, + { 1, 0, 1, 0, 1 }, + { 1, 0, 0, 0, 1 }, + { 1, 0, 0, 0, 1 }, + }, + ["н"] = { + { 1, 0, 0, 1 }, + { 1, 0, 0, 1 }, + { 1, 1, 1, 1 }, + { 1, 0, 0, 1 }, + { 1, 0, 0, 1 }, + }, + ["о"] = { + { 0, 1, 1, 0 }, + { 1, 0, 0, 1 }, + { 1, 0, 0, 1 }, + { 1, 0, 0, 1 }, + { 0, 1, 1, 0 }, + }, + ["п"] = { + { 1, 1, 1, 1 }, + { 1, 0, 0, 1 }, + { 1, 0, 0, 1 }, + { 1, 0, 0, 1 }, + { 1, 0, 0, 1 }, + }, + ["р"] = { + { 1, 1, 1, 0}, + { 1, 0, 0, 1}, + { 1, 1, 1, 0}, + { 1, 0, 0, 0}, + { 1, 0, 0, 0}, + }, + ["с"] = { + { 0, 1, 1, 1 }, + { 1, 0, 0, 0 }, + { 1, 0, 0, 0 }, + { 1, 0, 0, 0 }, + { 0, 1, 1, 1 }, + }, + ["т"] = { + { 1, 1, 1, 1, 1 }, + { 0, 0, 1, 0, 0 }, + { 0, 0, 1, 0, 0 }, + { 0, 0, 1, 0, 0 }, + { 0, 0, 1, 0, 0 }, + }, + ["у"] = { + { 1, 0, 0, 1 }, + { 1, 0, 0, 1 }, + { 0, 1, 1, 1 }, + { 0, 0, 0, 1 }, + { 1, 1, 1, 0 }, + }, + ["ф"] = { + { 0, 1, 1, 1, 0 }, + { 1, 0, 1, 0, 1 }, + { 0, 1, 1, 1, 0 }, + { 0, 0, 1, 0, 0 }, + { 0, 0, 1, 0, 0 }, + }, + ["х"] = { + { 1, 0, 0, 0, 1 }, + { 0, 1, 0, 1, 0 }, + { 0, 0, 1, 0, 0 }, + { 0, 1, 0, 1, 0 }, + { 1, 0, 0, 0, 1 }, + }, + ["ц"] = { + { 1, 0, 0, 1, 0 }, + { 1, 0, 0, 1, 0 }, + { 1, 0, 0, 1, 0 }, + { 1, 0, 0, 1, 0 }, + { 0, 1, 1, 1, 1 }, + }, + ["ч"] = { + { 1, 0, 0, 1 }, + { 1, 0, 0, 1 }, + { 0, 1, 1, 1 }, + { 0, 0, 0, 1 }, + { 0, 0, 0, 1 }, + }, + ["ш"] = { + { 1, 0, 0, 0, 1 }, + { 1, 0, 0, 0, 1 }, + { 1, 0, 1, 0, 1 }, + { 1, 0, 1, 0, 1 }, + { 1, 1, 1, 1, 1 }, + }, + ["щ"] = { + { 1, 0, 0, 0, 1, 0 }, + { 1, 0, 0, 0, 1, 0 }, + { 1, 0, 1, 0, 1, 0 }, + { 1, 0, 1, 0, 1, 0 }, + { 1, 1, 1, 1, 1, 1 }, + }, + ["ъ"] = { + { 1, 1, 0, 0, 0 }, + { 0, 1, 0, 0, 0 }, + { 0, 1, 1, 1, 0 }, + { 0, 1, 0, 0, 1 }, + { 0, 1, 1, 1, 0 }, + }, + ["ы"] = { + { 1, 0, 0, 0, 0, 1 }, + { 1, 0, 0, 0, 0, 1 }, + { 1, 1, 1, 0, 0, 1 }, + { 1, 0, 0, 1, 0, 1 }, + { 1, 1, 1, 0, 0, 1 }, + }, + ["ь"] = { + { 1, 0, 0, 0 }, + { 1, 0, 0, 0 }, + { 1, 1, 1, 0 }, + { 1, 0, 0, 1 }, + { 1, 1, 1, 0 }, + }, + ["э"] = { + { 1, 1, 1, 0 }, + { 0, 0, 0, 1 }, + { 0, 1, 1, 1 }, + { 0, 0, 0, 1 }, + { 1, 1, 1, 0 }, + }, + ["ю"] = { + { 1, 0, 0, 1, 1, 0 }, + { 1, 0, 1, 0, 0, 1 }, + { 1, 1, 1, 0, 0, 1 }, + { 1, 0, 1, 0, 0, 1 }, + { 1, 0, 0, 1, 1, 0 }, + }, + ["я"] = { + { 0, 1, 1, 1 }, + { 1, 0, 0, 1 }, + { 0, 1, 1, 1 }, + { 1, 0, 0, 1 }, + { 1, 0, 0, 1 }, + }, + + + ["-"] = { + { 0, 0, 0 }, + { 0, 0, 0 }, + { 1, 1, 1 }, + { 0, 0, 0 }, + { 0, 0, 0 }, + }, + ["_"] = { + { 0, 0, 0 }, + { 0, 0, 0 }, + { 0, 0, 0 }, + { 0, 0, 0 }, + { 1, 1, 1 }, + }, + ["+"] = { + { 0, 0, 0 }, + { 0, 1, 0 }, + { 1, 1, 1 }, + { 0, 1, 0 }, + { 0, 0, 0 }, + }, + + ["*"] = { + { 0, 0, 0 }, + { 1, 0, 1 }, + { 0, 1, 0 }, + { 1, 0, 1 }, + { 0, 0, 0 }, + }, + ["°"] = { + { 1 }, + { 0 }, + { 0 }, + { 0 }, + { 0 }, + }, + ["…"] = { + { 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0 }, + { 1, 0, 1, 0, 1 }, + }, +} + +function bigLetters.draw(x, y, color, symbol, drawWithSymbol) + if symbol == " " then + return spaceWidth + elseif not letters[symbol] then + symbol = unknownSymbol + end + + for j = 1, #letters[symbol] do + for i = 1, #letters[symbol][j] do + if letters[symbol][j][i] == 1 then + if not drawWithSymbol then + buffer.square(x + i * 2 - 2, y + (pixelHeight - #letters[symbol]) + j - 1, 2, 1, color, 0xFFFFFF, " ") + else + buffer.text(x + i * 2 - 2, y + (pixelHeight - #letters[symbol]) + j - 1, color, "*") + end + end + end + end + + return #letters[symbol][1] +end + +function bigLetters.drawText(x, y, color, stroka, drawWithSymbol) + checkArg(4, stroka, "string") + for i = 1, unicode.len(stroka) do + x = x + bigLetters.draw(x, y, color, unicode.sub(stroka, i, i), drawWithSymbol) * 2 + lettersInterval + end + return x +end + +function bigLetters.getTextSize(text) + local width, height = 0, 0 + local symbol, symbolWidth, symbolHeight + for i = 1, unicode.len(text) do + symbol = unicode.sub(text, i, i) + if symbol == " " then + symbolWidth = spaceWidth + symbolHeight = 5 + elseif not letters[symbol] then + symbolHeight = #letters[unknownSymbol] + symbolWidth = #letters[unknownSymbol][1] + else + symbolHeight = #letters[symbol] + symbolWidth = #letters[symbol][1] + end + + width = width + symbolWidth * 2 + lettersInterval + height = math.max(height, symbolHeight) + end + + return (width - lettersInterval), height +end + +return bigLetters + + + + + + + + + + + diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/bit32.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/bit32.lua new file mode 100755 index 00000000..67bbcffe --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/bit32.lua @@ -0,0 +1,102 @@ +--[[ Backwards compat for Lua 5.3; only loaded in 5.3 because package.loaded is + prepopulated with the existing global bit32 in 5.2. ]] + +local bit32 = {} + +------------------------------------------------------------------------------- + +local function fold(init, op, ...) + local result = init + local args = table.pack(...) + for i = 1, args.n do + result = op(result, args[i]) + end + return result +end + +local function trim(n) + return n & 0xFFFFFFFF +end + +local function mask(w) + return ~(0xFFFFFFFF << w) +end + +function bit32.arshift(x, disp) + return x // (2 ^ disp) +end + +function bit32.band(...) + return fold(0xFFFFFFFF, function(a, b) return a & b end, ...) +end + +function bit32.bnot(x) + return ~x +end + +function bit32.bor(...) + return fold(0, function(a, b) return a | b end, ...) +end + +function bit32.btest(...) + return bit32.band(...) ~= 0 +end + +function bit32.bxor(...) + return fold(0, function(a, b) return a ~ b end, ...) +end + +local function fieldargs(f, w) + w = w or 1 + assert(f >= 0, "field cannot be negative") + assert(w > 0, "width must be positive") + assert(f + w <= 32, "trying to access non-existent bits") + return f, w +end + +function bit32.extract(n, field, width) + local f, w = fieldargs(field, width) + return (n >> f) & mask(w) +end + +function bit32.replace(n, v, field, width) + local f, w = fieldargs(field, width) + local m = mask(w) + return (n & ~(m << f)) | ((v & m) << f) +end + +function bit32.lrotate(x, disp) + if disp == 0 then + return x + elseif disp < 0 then + return bit32.rrotate(x, -disp) + else + disp = disp & 31 + x = trim(x) + return trim((x << disp) | (x >> (32 - disp))) + end +end + +function bit32.lshift(x, disp) + return trim(x << disp) +end + +function bit32.rrotate(x, disp) + if disp == 0 then + return x + elseif disp < 0 then + return bit32.lrotate(x, -disp) + else + disp = disp & 31 + x = trim(x) + return trim((x >> disp) | (x << (32 - disp))) + end +end + +function bit32.rshift(x, disp) + return trim(x >> disp) +end + +------------------------------------------------------------------------------- + +return bit32 diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/buffer.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/buffer.lua new file mode 100755 index 00000000..df72bb5d --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/buffer.lua @@ -0,0 +1,186 @@ +local computer = require("computer") +local unicode = require("unicode") + +local buffer = {} +local metatable = { + __index = buffer, + __metatable = "file" +} + +function buffer.new(mode, stream) + local result = { + mode = {}, + stream = stream, + bufferRead = "", + bufferWrite = "", + bufferSize = math.max(512, math.min(8 * 1024, computer.freeMemory() / 8)), + bufferMode = "full", + readTimeout = math.huge + } + mode = mode or "r" + for i = 1, unicode.len(mode) do + result.mode[unicode.sub(mode, i, i)] = true + end + return setmetatable(result, metatable) +end + +function buffer:close() + if self.mode.w or self.mode.a then + self:flush() + end + self.closed = true + return self.stream:close() +end + +function buffer:flush() + if #self.bufferWrite > 0 then + local tmp = self.bufferWrite + self.bufferWrite = "" + local result, reason = self.stream:write(tmp) + if result then + self.bufferWrite = "" + else + if reason then + return nil, reason + else + return nil, "bad file descriptor" + end + end + end + + return self +end + +function buffer:lines(...) + local args = table.pack(...) + return function() + local result = table.pack(self:read(table.unpack(args, 1, args.n))) + if not result[1] and result[2] then + error(result[2]) + end + return table.unpack(result, 1, result.n) + end +end + +local function readChunk(self) + if computer.uptime() > self.timeout then + error("timeout") + end + local result, reason = self.stream:read(math.max(1,self.bufferSize)) + if result then + self.bufferRead = self.bufferRead .. result + return self + else -- error or eof + return nil, reason + end +end + +function buffer:readLine(chop, timeout) + self.timeout = timeout or (computer.uptime() + self.readTimeout) + local start = 1 + while true do + local buf = self.bufferRead + local i = buf:find("[\r\n]", start) + local c = i and buf:sub(i,i) + local is_cr = c == "\r" + if i and (not is_cr or i < #buf) then + local n = buf:sub(i+1,i+1) + if is_cr and n == "\n" then + c = c .. n + end + local result = buf:sub(1, i - 1) .. (chop and "" or c) + self.bufferRead = buf:sub(i + #c) + return result + else + start = #self.bufferRead - (is_cr and 1 or 0) + local result, reason = readChunk(self) + if not result then + if reason then + return nil, reason + else -- eof + local result = #self.bufferRead > 0 and self.bufferRead or nil + self.bufferRead = "" + return result + end + end + end + end +end + +function buffer:read(...) + if not self.mode.r then + return nil, "read mode was not enabled for this stream" + end + + if self.mode.w or self.mode.a then + self:flush() + end + + local formats = table.pack(...) + if formats.n == 0 then + return self:readLine(true) + end + return require("tools/buffered_read").read(self, readChunk, formats) +end + +function buffer:seek(whence, offset) + return require("tools/buffered_read").seek(self, whence, offset) +end + +function buffer:setvbuf(mode, size) + mode = mode or self.bufferMode + size = size or self.bufferSize + + assert(mode == "no" or mode == "full" or mode == "line", + "bad argument #1 (no, full or line expected, got " .. tostring(mode) .. ")") + assert(mode == "no" or type(size) == "number", + "bad argument #2 (number expected, got " .. type(size) .. ")") + + self.bufferMode = mode + self.bufferSize = size + + return self.bufferMode, self.bufferSize +end + +function buffer:getTimeout() + return self.readTimeout +end + +function buffer:setTimeout(value) + self.readTimeout = tonumber(value) +end + +function buffer:write(...) + if self.closed then + return nil, "bad file descriptor" + end + if not self.mode.w and not self.mode.a then + return nil, "write mode was not enabled for this stream" + end + local args = table.pack(...) + for i = 1, args.n do + if type(args[i]) == "number" then + args[i] = tostring(args[i]) + end + checkArg(i, args[i], "string") + end + + for i = 1, args.n do + local arg = args[i] + local result, reason + + if self.bufferMode == "no" then + result, reason = self.stream:write(arg) + else + result, reason = require("tools/buffered_write").write(self, arg) + end + + if not result then + return nil, reason + end + end + + return self +end + +return buffer diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/color.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/color.lua new file mode 100755 index 00000000..08d77deb --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/color.lua @@ -0,0 +1,173 @@ + +local bit32 = require("bit32") +local computer = require("computer") + +local color = {} +local bit32Lshift, bit32Rshift, bit32Band, bit32Bor, mathFloor, mathMax, mathMin, mathHuge, mathModf = bit32.lshift, bit32.rshift, bit32.band, bit32.bor, math.floor, math.max, math.min, math.huge, math.modf + +----------------------------------------------------------------------------------------------------------------------- + +-- Optimized Lua 5.3 bitwise support +local RGBToInteger, IntegerToRGB +if computer.getArchitecture and computer.getArchitecture() == "Lua 5.3" then + RGBToInteger = load([[ + return function(r, g, b) + return (r // 1 << 16) | (g // 1 << 8) | b // 1 + end + ]])() + + IntegerToRGB = load([[ + return function(IntegerColor) + return IntegerColor >> 16, IntegerColor >> 8 & 0xFF, IntegerColor & 0xFF + end + ]])() +else + RGBToInteger = function(r, g, b) + return bit32Bor(bit32Bor(bit32Lshift(r, 16), bit32Lshift(g, 8)), b) + end + + IntegerToRGB = function(IntegerColor) + return bit32Rshift(IntegerColor, 16), bit32Band(bit32Rshift(IntegerColor, 8), 0xFF), bit32Band(IntegerColor, 0xFF) + end +end + +----------------------------------------------------------------------------------------------------------------------- + +local function RGBToHSB(r, g, b) + local max, min = mathMax(r, g, b), mathMin(r, g, b) + + if max == min then + return 0, max == 0 and 0 or (1 - min / max), max / 255 + elseif max == r and g >= b then + return 60 * (g - b) / (max - min), max == 0 and 0 or (1 - min / max), max / 255 + elseif max == r and g < b then + return 60 * (g - b) / (max - min) + 360, max == 0 and 0 or (1 - min / max), max / 255 + elseif max == g then + return 60 * (b - r) / (max - min) + 120, max == 0 and 0 or (1 - min / max), max / 255 + elseif max == b then + return 60 * (r - g) / (max - min) + 240, max == 0 and 0 or (1 - min / max), max / 255 + else + return 0, max == 0 and 0 or (1 - min / max), max / 255 + end +end + +local function HSBToRGB(h, s, b) + local integer, fractional = mathModf(h / 60) + local p, q, t = b * (1 - s), b * (1 - s * fractional), b * (1 - (1 - fractional) * s) + + if integer == 0 then + return mathFloor(b * 255), mathFloor(t * 255), mathFloor(p * 255) + elseif integer == 1 then + return mathFloor(q * 255), mathFloor(b * 255), mathFloor(p * 255) + elseif integer == 2 then + return mathFloor(p * 255), mathFloor(b * 255), mathFloor(t * 255) + elseif integer == 3 then + return mathFloor(p * 255), mathFloor(q * 255), mathFloor(b * 255) + elseif integer == 4 then + return mathFloor(t * 255), mathFloor(p * 255), mathFloor(b * 255) + else + return mathFloor(b * 255), mathFloor(p * 255), mathFloor(q * 255) + end +end + +local function IntegerToHSB(IntegerColor) + return RGBToHSB(IntegerToRGB(IntegerColor)) +end + +local function HSBToInteger(h, s, b) + return RGBToInteger(HSBToRGB(h, s, b)) +end + +----------------------------------------------------------------------------------------------------------------------- + +local function blend(firstColor, secondColor, secondColorTransparency) + local invertedTransparency, r1, g1, b1 = 1 - secondColorTransparency, IntegerToRGB(firstColor) + local r2, g2, b2 = IntegerToRGB(secondColor) + + return RGBToInteger( + r2 * invertedTransparency + r1 * secondColorTransparency, + g2 * invertedTransparency + g1 * secondColorTransparency, + b2 * invertedTransparency + b1 * secondColorTransparency + ) +end + +----------------------------------------------------------------------------------------------------------------------- + +local function transition(color1, color2, position) + local r1, g1, b1 = IntegerToRGB(color1) + local r2, g2, b2 = IntegerToRGB(color2) + + return RGBToInteger( + r1 + (r2 - r1) * position, + g1 + (g2 - g1) * position, + b1 + (b2 - b1) * position + ) +end + +local function average(colors) + local sColors, averageRed, averageGreen, averageBlue, r, g, b = #colors, 0, 0, 0 + + for i = 1, sColors do + r, g, b = IntegerToRGB(colors[i]) + averageRed, averageGreen, averageBlue = averageRed + r, averageGreen + g, averageBlue + b + end + + return RGBToInteger(mathFloor(averageRed / sColors), mathFloor(averageGreen / sColors), mathFloor(averageBlue / sColors)) +end + +----------------------------------------------------------------------------------------------------------------------- + +local openComputersPalette = { 0x000000, 0x000040, 0x000080, 0x0000BF, 0x0000FF, 0x002400, 0x002440, 0x002480, 0x0024BF, 0x0024FF, 0x004900, 0x004940, 0x004980, 0x0049BF, 0x0049FF, 0x006D00, 0x006D40, 0x006D80, 0x006DBF, 0x006DFF, 0x009200, 0x009240, 0x009280, 0x0092BF, 0x0092FF, 0x00B600, 0x00B640, 0x00B680, 0x00B6BF, 0x00B6FF, 0x00DB00, 0x00DB40, 0x00DB80, 0x00DBBF, 0x00DBFF, 0x00FF00, 0x00FF40, 0x00FF80, 0x00FFBF, 0x00FFFF, 0x0F0F0F, 0x1E1E1E, 0x2D2D2D, 0x330000, 0x330040, 0x330080, 0x3300BF, 0x3300FF, 0x332400, 0x332440, 0x332480, 0x3324BF, 0x3324FF, 0x334900, 0x334940, 0x334980, 0x3349BF, 0x3349FF, 0x336D00, 0x336D40, 0x336D80, 0x336DBF, 0x336DFF, 0x339200, 0x339240, 0x339280, 0x3392BF, 0x3392FF, 0x33B600, 0x33B640, 0x33B680, 0x33B6BF, 0x33B6FF, 0x33DB00, 0x33DB40, 0x33DB80, 0x33DBBF, 0x33DBFF, 0x33FF00, 0x33FF40, 0x33FF80, 0x33FFBF, 0x33FFFF, 0x3C3C3C, 0x4B4B4B, 0x5A5A5A, 0x660000, 0x660040, 0x660080, 0x6600BF, 0x6600FF, 0x662400, 0x662440, 0x662480, 0x6624BF, 0x6624FF, 0x664900, 0x664940, 0x664980, 0x6649BF, 0x6649FF, 0x666D00, 0x666D40, 0x666D80, 0x666DBF, 0x666DFF, 0x669200, 0x669240, 0x669280, 0x6692BF, 0x6692FF, 0x66B600, 0x66B640, 0x66B680, 0x66B6BF, 0x66B6FF, 0x66DB00, 0x66DB40, 0x66DB80, 0x66DBBF, 0x66DBFF, 0x66FF00, 0x66FF40, 0x66FF80, 0x66FFBF, 0x66FFFF, 0x696969, 0x787878, 0x878787, 0x969696, 0x990000, 0x990040, 0x990080, 0x9900BF, 0x9900FF, 0x992400, 0x992440, 0x992480, 0x9924BF, 0x9924FF, 0x994900, 0x994940, 0x994980, 0x9949BF, 0x9949FF, 0x996D00, 0x996D40, 0x996D80, 0x996DBF, 0x996DFF, 0x999200, 0x999240, 0x999280, 0x9992BF, 0x9992FF, 0x99B600, 0x99B640, 0x99B680, 0x99B6BF, 0x99B6FF, 0x99DB00, 0x99DB40, 0x99DB80, 0x99DBBF, 0x99DBFF, 0x99FF00, 0x99FF40, 0x99FF80, 0x99FFBF, 0x99FFFF, 0xA5A5A5, 0xB4B4B4, 0xC3C3C3, 0xCC0000, 0xCC0040, 0xCC0080, 0xCC00BF, 0xCC00FF, 0xCC2400, 0xCC2440, 0xCC2480, 0xCC24BF, 0xCC24FF, 0xCC4900, 0xCC4940, 0xCC4980, 0xCC49BF, 0xCC49FF, 0xCC6D00, 0xCC6D40, 0xCC6D80, 0xCC6DBF, 0xCC6DFF, 0xCC9200, 0xCC9240, 0xCC9280, 0xCC92BF, 0xCC92FF, 0xCCB600, 0xCCB640, 0xCCB680, 0xCCB6BF, 0xCCB6FF, 0xCCDB00, 0xCCDB40, 0xCCDB80, 0xCCDBBF, 0xCCDBFF, 0xCCFF00, 0xCCFF40, 0xCCFF80, 0xCCFFBF, 0xCCFFFF, 0xD2D2D2, 0xE1E1E1, 0xF0F0F0, 0xFF0000, 0xFF0040, 0xFF0080, 0xFF00BF, 0xFF00FF, 0xFF2400, 0xFF2440, 0xFF2480, 0xFF24BF, 0xFF24FF, 0xFF4900, 0xFF4940, 0xFF4980, 0xFF49BF, 0xFF49FF, 0xFF6D00, 0xFF6D40, 0xFF6D80, 0xFF6DBF, 0xFF6DFF, 0xFF9200, 0xFF9240, 0xFF9280, 0xFF92BF, 0xFF92FF, 0xFFB600, 0xFFB640, 0xFFB680, 0xFFB6BF, 0xFFB6FF, 0xFFDB00, 0xFFDB40, 0xFFDB80, 0xFFDBBF, 0xFFDBFF, 0xFFFF00, 0xFFFF40, 0xFFFF80, 0xFFFFBF, 0xFFFFFF } + +local function to8Bit(color24Bit) + local closestDelta, r, g, b, closestIndex, delta, openComputersPaletteR, openComputersPaletteG, openComputersPaletteB = mathHuge, IntegerToRGB(color24Bit) + + for index = 1, #openComputersPalette do + if color24Bit == openComputersPalette[index] then + return index - 1 + else + openComputersPaletteR, openComputersPaletteG, openComputersPaletteB = IntegerToRGB(openComputersPalette[index]) + delta = (openComputersPaletteR - r) ^ 2 + (openComputersPaletteG - g) ^ 2 + (openComputersPaletteB - b) ^ 2 + + if delta < closestDelta then + closestDelta, closestIndex = delta, index + end + end + end + + return closestIndex - 1 +end + +local function to24Bit(color8Bit) + return openComputersPalette[color8Bit + 1] +end + +local function optimize(color24Bit) + return to24Bit(to8Bit(color24Bit)) +end + +----------------------------------------------------------------------------------------------------------------------- + +-- local c, a = blendRGBA(0x0000FF, 0xFF0000, 0.5, 0.5) +-- print(string.format("0x%06X", c), a) + +----------------------------------------------------------------------------------------------------------------------- + +return { + RGBToInteger = RGBToInteger, + IntegerToRGB = IntegerToRGB, + RGBToHSB = RGBToHSB, + HSBToRGB = HSBToRGB, + IntegerToHSB = IntegerToHSB, + HSBToInteger = HSBToInteger, + blend = blend, + + transition = transition, + + to8Bit = to8Bit, + to24Bit = to24Bit, + optimize = optimize, +} + + + diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/colors.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/colors.lua new file mode 100755 index 00000000..f9be923c --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/colors.lua @@ -0,0 +1,30 @@ +local colors = { + [0] = "white", + [1] = "orange", + [2] = "magenta", + [3] = "lightblue", + [4] = "yellow", + [5] = "lime", + [6] = "pink", + [7] = "gray", + [8] = "silver", + [9] = "cyan", + [10] = "purple", + [11] = "blue", + [12] = "brown", + [13] = "green", + [14] = "red", + [15] = "black" +} + +do + local keys = {} + for k in pairs(colors) do + table.insert(keys, k) + end + for _, k in pairs(keys) do + colors[colors[k]] = k + end +end + +return colors \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/compressor.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/compressor.lua new file mode 100755 index 00000000..f37e3a15 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/compressor.lua @@ -0,0 +1,215 @@ + +local unicode = require("unicode") +local fs = require("filesystem") +local compressor = {} + +------------------------------------------------------------------------------------------------------------------ + +local function numberToByteArray(number) + local byteArray = {} + while number > 0 do + table.insert(byteArray, 1, bit32.band(number, 0xFF)) + number = bit32.rshift(number, 8) + end + return byteArray +end + +local function byteArrayToNumber(byteArray) + local number = byteArray[1] + for i = 2, #byteArray do + number = bit32.bor(byteArray[i], bit32.lshift(number, 8)) + end + return number +end + +local function info(showInfo, text) + if showInfo then + print(text) + end +end + +------------------------------------------------------------------------------------------------------------------ + +local function writePath(compressedFile, path) + -- Получаем юникод-байтики названия файла или папки + local pathBytes = {} + for i = 1, unicode.len(path) do + local charBytes = { string.byte(unicode.sub(path, i, i), 1, 6) } + for j = 1, #charBytes do + table.insert(pathBytes, charBytes[j]) + end + end + -- Записываем количество байт, необходимое для записи РАЗМЕРА байт пути + local bytesForCountOfBytesForPath = numberToByteArray(#pathBytes) + compressedFile:write(string.char(#bytesForCountOfBytesForPath)) + -- Записываем количество байт, необходимое для записи самого пути + for i = 1, #bytesForCountOfBytesForPath do + compressedFile:write(string.char(bytesForCountOfBytesForPath[i])) + end + -- Записываем байтики пути + for i = 1, #pathBytes do + compressedFile:write(string.char(pathBytes[i])) + end +end + +local function writeFileSize(compressedFile, path) + local size = fs.size(path) + local bytesForSize = numberToByteArray(size) + -- Записываем количество байт, необходимое для записи РАЗМЕРА байт размера файла + compressedFile:write(string.char(#bytesForSize)) + -- Записываем сами байты размера файла + for i = 1, #bytesForSize do + compressedFile:write(string.char(bytesForSize[i])) + end +end + +local function getFileList(path) + local fileList = {} + for file in fs.list(path) do + table.insert(fileList, path .. file) + end + return fileList +end + +local function doCompressionRecursively(compressedFile, fileList, currentPackPath, pathToCompressedFile, showInfo) + for file = 1, #fileList do + local filename = (fs.name(fileList[file]) or "") + local filePackPath = currentPackPath .. filename + + -- info(showInfo, "Local path: " .. filePackPath) + if fileList[file] == pathToCompressedFile or filename == "mnt" or filename == "dev" or filename == ".DS_Store" then + info(showInfo, "Skipping restricted path \"" .. fileList[file] .. "\"") + else + if fs.isDirectory(fileList[file]) then + info(showInfo, "Packing directory " .. fileList[file]) + + compressedFile:write("D") + writePath(compressedFile, filePackPath .. "/") + + doCompressionRecursively(compressedFile, getFileList(fileList[file]), filePackPath .. "/", pathToCompressedFile, showInfo) + else + info(showInfo, "Packing file " .. fileList[file]) + + compressedFile:write("F") + writePath(compressedFile, filePackPath) + writeFileSize(compressedFile, fileList[file]) + + local fileToCompress = io.open(fileList[file], "rb") + compressedFile:write(fileToCompress:read("*a")) + fileToCompress:close() + end + end + -- require("ECSAPI").wait() + end +end + +function compressor.pack(pathToCompressedFile, ...) + local data, showInfo = {...}, false + if type(data[#data]) == "boolean" then showInfo = data[#data]; data[#data] = nil end + + info(showInfo, "Packing data to file \"" .. pathToCompressedFile .. "\"...") + info(showInfo, " ") + fs.makeDirectory(fs.path(pathToCompressedFile)) + + -- Открываем файл со сжатым контентом + local compressedFile, reason = io.open(pathToCompressedFile, "wb") + if not compressedFile then + error("Failed to open package file for writing: " .. tostring(reason)) + end + -- Записываем сигнатурку + compressedFile:write("ARCH") + -- Пакуем данные + doCompressionRecursively(compressedFile, data, "", pathToCompressedFile, showInfo) + -- Закрываем файл со сжатым контентом + compressedFile:close() + info(showInfo, " ") + info(showInfo, "Data packing finished") +end + +------------------------------------------------------------------------------------------------------------------ + +local function readPath(compressedFile) + local countOfBytesForPathBytes = string.byte(compressedFile:read(1)) + local pathBytes = {} + for i = 1, countOfBytesForPathBytes do + table.insert(pathBytes, string.byte(compressedFile:read(1))) + end + local pathSize = byteArrayToNumber(pathBytes) + local path = compressedFile:read(pathSize) + -- info(showInfo, "Колво байт под байты пути: ", countOfBytesForPathBytes) + -- info(showInfo, "Колво байт под путь: ", pathSize) + -- info(showInfo, "Путь: ", path) + return path +end + +local function readFileSize(compressedFile) + local countOfBytesForFileSize = string.byte(compressedFile:read(1)) + local fileSizeBytes = {} + for i = 1, countOfBytesForFileSize do + table.insert(fileSizeBytes, string.byte(compressedFile:read(1))) + end + local fileSize = byteArrayToNumber(fileSizeBytes) + -- info(showInfo, "Размер файла: ", fileSize) + return fileSize +end + +function compressor.unpack(pathToCompressedFile, pathWhereToUnpack, showInfo) + info(showInfo, "Unpacking data from file \"" .. pathToCompressedFile .. "\"...") + info(showInfo, " ") + fs.makeDirectory(pathWhereToUnpack) + + local compressedFile, reason = io.open(pathToCompressedFile, "rb") + if not compressedFile then + error("Failed to open package file for reading: " .. tostring(reason)) + end + + local signature = compressedFile:read(4) + if signature == "ARCH" then + while true do + local type = compressedFile:read(1) + if type == "D" then + local path = readPath(compressedFile) + fs.makeDirectory(pathWhereToUnpack .. path) + info(showInfo, "Unpacking directory \"" .. path .. "\"") + elseif type == "F" then + local path = readPath(compressedFile) + local size = readFileSize(compressedFile) + + info(showInfo, "Unpacking file \"" .. path .. "\"") + local file, reason = io.open(pathWhereToUnpack .. path, "wb") + if file then + file:write(compressedFile:read(size)) + file:close() + else + compressedFile:read(size) + info(showInfo, "Failed to open file for writing while unpacking: " .. tostring(reason)) + end + elseif not type then + break + else + compressedFile:close() + error("Packed file is corrupted, unknown path type: " .. tostring(type)) + end + end + else + compressedFile:close() + error("Packed file is corrupted, wrong signature: " .. tostring(signature)) + end + + compressedFile:close() + info(showInfo, " ") + info(showInfo, "Unpacking data finished") +end + +------------------------------------------------------------------------------------------------------------------ + +-- compressor.pack("/test1.pkg", "/MineOS/System/OS/", "/etc/", true) +-- info(showInfo, " ") +-- compressor.unpack("/test1.pkg", "/papkaUnpacked/", true) + +------------------------------------------------------------------------------------------------------------------ + +return compressor + + + diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/context.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/context.lua new file mode 100755 index 00000000..6c1abef9 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/context.lua @@ -0,0 +1,137 @@ +local component = require("component") +local event = require("event") +local unicode = require("unicode") +local ecs = require("ECSAPI") +local gpu = component.gpu + +local context = {} + +local separatorColor = 0xcccccc +local shortcutAutism = 3 + +---------------------------------------------------------------------------------------------------------------- + +--ОБЪЕКТЫ +local obj = {} +local function newObj(class, name, ...) + obj[class] = obj[class] or {} + obj[class][name] = {...} +end + +function context.menu(x, y, ...) + + local data = {...} + local sData = #data + + obj = {} + + --Получаем размер экрана + local xSize, ySize = gpu.getResolution() + + --Получаем самую жирную полоску текста + local biggestElement = 0 + for i = 1, sData do + if data[i] ~= "-" then + local length = unicode.len(data[i][1]) + if data[i][3] then length = length + shortcutAutism + unicode.len(data[i][3]) end + biggestElement = math.max(biggestElement, length) + length = nil + end + end + + --Задание ширины и высоты + local width = 4 + biggestElement + local height = sData + + --А это чтоб за края экрана не лезло + if y + height >= ySize then y = ySize - height end + if x + width + 1 >= xSize then x = xSize - width - 1 end + + --Рисуем окошечко и запоминаем, че было до него (сначала был Бог...!) + local oldPixels = ecs.rememberOldPixels(x, y, x + width + 1, y + height) + ecs.square(x, y, width, height, 0xffffff) + ecs.windowShadow(x, y, width, height) + gpu.setBackground(0xffffff) + + --Нарисовать конкретный элемент + local function drawElement(i, background, foreground, yPos) + + if background then ecs.square(x, yPos, width, 1, background) end + + --Получаем текстик + local text + if data[i] == "-" then + ecs.colorText(x, yPos, separatorColor, string.rep("─", width)) + else + --Нужный цвет + local color = foreground or 0x000000 + if data[i][2] then color = separatorColor end + + --Рисуем текстик + ecs.colorText(x + 2, yPos, color, data[i][1]) + + --Рисуем сокращение + if data[i][3] then gpu.set(x + width - 2 - unicode.len(data[i][3]), yPos, data[i][3]) end + + if not data[i][2] then newObj("Elements", i, x, yPos, x + width - 1, yPos) end + end + end + + --Рисуем все элементы + local counter = 0 + local yPos + for i = 1, sData do + yPos = y + counter + + drawElement(i, nil, nil, yPos) + + counter = counter + 1 + end + + --Проверка нажатия + local action + local e = {event.pull("touch")} + if obj["Elements"] then + for key, val in pairs(obj["Elements"]) do + if ecs.clickedAtArea(e[3], e[4], obj["Elements"][key][1], obj["Elements"][key][2], obj["Elements"][key][3], obj["Elements"][key][4]) then + + --ecs.error("Кол-во объектов: "..#obj["Elements"]..", кликнули на объект номер = "..tostring(key)..", #Data = "..#data..", sData ="..sData) + + drawElement(key, ecs.colors.blue, 0xffffff, e[4]) + os.sleep(0.3) + action = data[key][1] + break + end + end + end + + --Красим то, че было + ecs.drawOldPixels(oldPixels) + + --Возвращаем выбранное + return action + +end + +---------------------------------------------------------------------------------------------------------------- + +-- while true do +-- local e = {event.pull("touch")} +-- ecs.prepareToExit() +-- local action = context.menu(e[3], e[4], {"Показать содержимое"}, "-", {"Вырезать", false, "^X"}, {"Копировать", false, "^C"}, {"Вставить", true, "^V"}, "-", {"Переименовать"}, {"Создать ярлык"}, {"Добавить в архив"}, "-", {"Удалить", false, "⌫"}) +-- ecs.prepareToExit() +-- print("Ты выбрал = "..tostring(action)) +-- end + +--local action = context.menu(6, 2, {(function() if showHiddenFiles then return "Скрывать скрытые файлы" else return "Показывать скрытые файлы" end end)()}, {(function() if showSystemFiles then return "Скрывать системные файлы" else return "Показывать системные файлы" end end)()}, "-", {(function() if showFileFormat then return "Скрывать формат файлов" else return "Показывать формат файлов" end end)()}) + + +return context + + + + + + + + diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/devfs.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/devfs.lua new file mode 100755 index 00000000..e8faa2f7 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/devfs.lua @@ -0,0 +1,345 @@ +local fs = require("filesystem") +local text = require("text") + +local api = {} + +local function new_node(proxy) + local node = {proxy=proxy} + if not proxy or not proxy.list then + node.children = {} + end + return node +end + +local function array_read(array, separator) + separator = separator or " " + local builder = {} + for _,value in ipairs(array) do + table.insert(builder, tostring(value)) + end + return table.concat(builder, separator) +end + +local function child_iterator(node) + -- a node can either list or have children, but not both (see add_child) + -- a node can be a file, which has a proxy, but no children + local listed = {} + if node then + if node.proxy and node.proxy.list then + -- list should return a table, not another iterator + -- the elements in the list are not nodes, but proxies + -- we have to wrap each entry with a virtual node (a node that is not in a child-parent tree) + -- list can be a function that returns a table, or the table already + local list = node.proxy.list + listed = type(list) == "table" and list or list() + elseif node.children then + listed = node.children + end + end + local availables = {} + for name, item in pairs(listed) do + if name:len() > 0 then + if not item.proxy then item = new_node(item) end + if not item.proxy.isAvailable or item.proxy.isAvailable() then + availables[name] = item + end + end + end + return pairs(availables) +end + +local function get_child(node, name) + for child_name, child in child_iterator(node) do + if child_name == name then + return child + end + end +end + +local function add_child(node, name, proxy) + if not node or node.proxy and node.proxy.list then + return nil, "cannot add child to listing proxy" + end + + local child = new_node(proxy) + node.children[name] = child + return child +end + +local function findNode(path, bCreate) + local segments = fs.segments(path) + local node = api.root + while #segments > 0 do + local name = table.remove(segments, 1) + local next = get_child(node, name) + if not next then + if bCreate then + if not add_child(node, name) then + return nil, "cannot create child node" + end + else + return nil, "no such file or directory" + end + end + node = next or get_child(node, name) + end + return node +end + +-- devfs api + +api.root = new_node() + +function api.create(path, proxy) + checkArg(1, path, "string") + checkArg(2, proxy, "table", "nil") + local pwd = fs.path(path) + local name = fs.name(path) + if not name then return nil, "invalid devfs path" end + local pnode, why = findNode(pwd, true) + if not pnode then + return nil, why + end + + if get_child(pnode, name) then + return nil, "file or directory exists" + end + + return add_child(pnode, name, proxy) +end + +-- the filesystem object as seen from the system mount interface +api.proxy = {} + +-- forward declare injector +local inject_dynamic_pairs +local function dynamic_list(path, fsnode) + local nodes, links, dirs = {}, {}, {} + local node = findNode(path) + if node then + for name,node in child_iterator(node) do + if node.proxy and node.proxy.link then + links[name] = node.proxy.link + elseif node.proxy and node.proxy.list then + local child = {name=name,parent=fsnode} + local child_path = path .. "/" .. name + inject_dynamic_pairs(child, child_path, true) + dirs[name] = child + else + nodes[name] = node + end + end + end + return nodes, links, dirs +end + +inject_dynamic_pairs = function(fsnode, path, bStoreUse) + if getmetatable(fsnode) then return end + fsnode.children = nil + fsnode.links = nil + setmetatable(fsnode, + { + __index = function(tbl, key) + local bLinks = key == "links" + local bChildren = key == "children" + if not bLinks and not bChildren then return end + local nodes, links, dirs = dynamic_list(path, tbl) + if bStoreUse then + tbl.children = dirs + tbl.links = links + end + return bLinks and links or dirs + end + }) +end + +local label_lib = dofile("/lib/tools/device_labeling.lua") +label_lib.loadRules() +api.getDeviceLabel = label_lib.getDeviceLabel +api.setDeviceLabel = label_lib.setDeviceLabel + +local registered = false +function api.register(public_proxy) + if registered then return end + registered = true + + local start_path = "/lib/tools/devfs/" + for starter in fs.list(start_path) do + local full_path = start_path .. starter + local _,matched = starter:gsub("%.lua$","") + if matched > 0 then + local data = dofile(full_path) + for name, entry in pairs(data) do + api.create(name, entry) + end + end + end + + if rawget(public_proxy, "fsnode") then + inject_dynamic_pairs(public_proxy.fsnode, "") + end +end + +function api.proxy.list(path) + local result = {} + for name,node in pairs(dynamic_list(path, false, false)) do + table.insert(result, name) + end + return result +end + +function api.proxy.isDirectory(path) + local node = findNode(path) + return node and node.proxy and node.proxy.list +end + +function api.proxy.size(path) + checkArg(1, path, "string") + local node, why = findNode(path) + if not node or not node.proxy then + return 0 + end + + local proxy = node.proxy + if proxy.list then return 0 end + if proxy.size then return proxy.size() end + if proxy.open then return 0 end + if proxy.read then return proxy.read():len() end + if proxy[1] ~= nil then return array_read(proxy):len() end + return 0 +end + +function api.proxy.lastModified() + return 0 +end + +function api.proxy.exists(path) + checkArg(1, path, "string") + return not not findNode(path) +end + +function api.getDevice(path) + checkArg(1, path, "string") + local device + local reason = "no such device" + local real, why = fs.realPath(require("shell").resolve(path)) + if not real then return nil, why end + if fs.exists(real) then + -- we don't have a good way of knowing where dev is mounted still + -- similar hack in api.proxy.open + real = fs.path(real) .. (fs.name(real) or "") + local part, subbed = real:gsub("^/dev/", "") + if subbed > 0 and part:len() > 0 then + local node = findNode(part) + if node and node.proxy then + -- must be a special device node + device = node.proxy.device + end + if not device then + reason = "not a device" + end + else + device, reason = fs.get(real) + end + end + return device, reason +end + +function api.proxy.open(path, mode) + checkArg(1, path, "string") + checkArg(2, mode, "string", "nil") + + mode = mode or "r" + local bRead = mode:match("[ra]") + local bWrite = mode:match("[wa]") + + if not bRead and not bWrite then + return nil, "invalid mode" + end + + local node, why = findNode(path) + if not node then + return nil, why + elseif not node.proxy or node.proxy.list then + return nil, "is a directory" + end + + local proxy = node.proxy + + -- in case someone tries to open a link directly, refer them back to fs + -- this is an unfortunate pathing hack due to optimizations for memory + if proxy.link then + return fs.open("/dev/"..path, mode) + end + + -- special (but common) simple readonly cases + if proxy[1] ~= nil then -- contains special readonly value + local array = proxy + proxy.read = function()return array_read(array) end + end + + if proxy.open then + return proxy.open(mode) + end + + if bRead and not proxy.read then + return nil, "cannot open for read" + elseif bWrite and not proxy.write then + return nil, "cannot open for write" + end + + local txtRead = bRead and proxy.read() + + if bWrite then + return text.internal.writer(proxy.write, mode, txtRead) + end + + return text.internal.reader(txtRead, mode) +end + +-- as long as the fsnode hack is used, fs.isLink is not needed here +-- function api.proxy.isLink(path) end + +local function checked_invoke(handle, method, ...) + checkArg(1, handle, "table") + checkArg(2, method, "string") + checkArg(3, handle[method], "function", "table", "nil") + local m = handle[method] + if not m then + return nil, "bad file handle" + elseif type(m) == "table" then + local mm = getmetatable(m) + assert(mm and mm.__call, string.format("FILE handle [%s] method defined, but is not callable", tostring(method))) + end + return m(handle, ...) +end + +function api.proxy.read(h, ...) + return checked_invoke(h, "read", ...) +end + +function api.proxy.close(h, ...) + return checked_invoke(h, "close", ...) +end + +function api.proxy.write(h, ...) + return checked_invoke(h, "write", ...) +end + +function api.proxy.seek(h, ...) + return checked_invoke(h, "seek", ...) +end + +function api.proxy.remove() + return nil, "cannot remove file or directory" +end + +function api.proxy.makeDirectory() + return nil, "use create in the devfs api" +end + +function api.proxy.setLabel() + return nil, "cannot set label on devfs" +end + +return api diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/doubleBuffering.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/doubleBuffering.lua new file mode 100755 index 00000000..a05e0951 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/doubleBuffering.lua @@ -0,0 +1,700 @@ + +local component = require("component") +local unicode = require("unicode") +local color = require("color") +local image = require("image") + +-------------------------------------------------------------------------------------------------------------- + +local bufferWidth, bufferHeight, bufferTripleWidth +local currentFrame, newFrame +local drawLimitX1, drawLimitX2, drawLimitY1, drawLimitY2 + +local GPUProxy, GPUProxyGetResolution, GPUProxySetResolution, GPUProxyBind, GPUProxyGetBackground, GPUProxyGetForeground, GPUProxySetBackground, GPUProxySetForeground, GPUProxyGet, GPUProxySet, GPUProxyFill +local mathCeil, mathFloor, mathModf, mathAbs = math.ceil, math.floor, math.modf, math.abs +local tableInsert, tableConcat = table.insert, table.concat +local colorBlend = color.blend +local unicodeLen, unicodeSub = unicode.len, unicode.sub + +-------------------------------------------------------------------------------------------------------------- + +local function getCoordinates(index) + local integer, fractional = mathModf(index / bufferTripleWidth) + return mathCeil(fractional * bufferWidth), integer + 1 +end + +local function getIndex(x, y) + return bufferTripleWidth * (y - 1) + x * 3 - 2 +end + +-------------------------------------------------------------------------------------------------------------- + +local function setDrawLimit(x1, y1, x2, y2) + drawLimitX1, drawLimitY1, drawLimitX2, drawLimitY2 = x1, y1, x2, y2 +end + +local function resetDrawLimit() + drawLimitX1, drawLimitY1, drawLimitX2, drawLimitY2 = 1, 1, bufferWidth, bufferHeight +end + +local function getDrawLimit() + return drawLimitX1, drawLimitY1, drawLimitX2, drawLimitY2 +end + +-------------------------------------------------------------------------------------------------------------- + +local function flush(width, height) + if not width or not height then + width, height = GPUProxyGetResolution() + end + + currentFrame, newFrame = {}, {} + bufferWidth = width + bufferHeight = height + bufferTripleWidth = width * 3 + resetDrawLimit() + + for y = 1, bufferHeight do + for x = 1, bufferWidth do + tableInsert(currentFrame, 0x010101) + tableInsert(currentFrame, 0xFEFEFE) + tableInsert(currentFrame, " ") + + tableInsert(newFrame, 0x010101) + tableInsert(newFrame, 0xFEFEFE) + tableInsert(newFrame, " ") + end + end +end + +local function setResolution(width, height) + GPUProxySetResolution(width, height) + flush(width, height) +end + +local function getResolution() + return bufferWidth, bufferHeight +end + +local function getWidth() + return bufferWidth +end + +local function getHeight() + return bufferHeight +end + +local function bindScreen(...) + GPUProxyBind(...) + flush(GPUProxyGetResolution()) +end + +local function getGPUProxy() + return GPUProxy +end + +local function updateGPUProxyMethods() + GPUProxyGet = GPUProxy.get + GPUProxyGetResolution = GPUProxy.getResolution + GPUProxyGetBackground = GPUProxy.getBackground + GPUProxyGetForeground = GPUProxy.getForeground + + GPUProxySet = GPUProxy.set + GPUProxySetResolution = GPUProxy.setResolution + GPUProxySetBackground = GPUProxy.setBackground + GPUProxySetForeground = GPUProxy.setForeground + + GPUProxyBind = GPUProxy.bind + GPUProxyFill = GPUProxy.fill +end + +local function bindGPU(address) + GPUProxy = component.proxy(address) + updateGPUProxyMethods() + flush(GPUProxyGetResolution()) +end + +-------------------------------------------------------------------------------------------------------------- + +local function rawSet(index, background, foreground, symbol) + newFrame[index], newFrame[index + 1], newFrame[index + 2] = background, foreground, symbol +end + +local function rawGet(index) + return newFrame[index], newFrame[index + 1], newFrame[index + 2] +end + +local function get(x, y) + local index = getIndex(x, y) + if x >= 1 and y >= 1 and x <= bufferWidth and y <= bufferHeight then + return newFrame[index], newFrame[index + 1], newFrame[index + 2] + else + return 0x000000, 0x000000, " " + end +end + +local function set(x, y, background, foreground, symbol) + local index = getIndex(x, y) + if x >= drawLimitX1 and y >= drawLimitY1 and x <= drawLimitX2 and y <= drawLimitY2 then + newFrame[index] = background + newFrame[index + 1] = foreground + newFrame[index + 2] = symbol + end +end + +local function square(x, y, width, height, background, foreground, symbol, transparency) + local index, indexStepOnEveryLine, indexPlus1 = getIndex(x, y), (bufferWidth - width) * 3 + + for j = y, y + height - 1 do + if j >= drawLimitY1 and j <= drawLimitY2 then + for i = x, x + width - 1 do + if i >= drawLimitX1 and i <= drawLimitX2 then + indexPlus1 = index + 1 + + if transparency then + newFrame[index], newFrame[indexPlus1] = + colorBlend(newFrame[index], background, transparency), + colorBlend(newFrame[indexPlus1], background, transparency) + else + newFrame[index], newFrame[indexPlus1], newFrame[index + 2] = background, foreground, symbol + end + end + + index = index + 3 + end + + index = index + indexStepOnEveryLine + else + index = index + bufferTripleWidth + end + end +end + +local function clear(color, transparency) + square(1, 1, bufferWidth, bufferHeight, color or 0x0, 0x000000, " ", transparency) +end + +local function copy(x, y, width, height) + local copyArray = { width = width, height = height } + + local index + for j = y, y + height - 1 do + for i = x, x + width - 1 do + if i >= 1 and j >= 1 and i <= bufferWidth and j <= bufferHeight then + index = getIndex(i, j) + tableInsert(copyArray, newFrame[index]) + tableInsert(copyArray, newFrame[index + 1]) + tableInsert(copyArray, newFrame[index + 2]) + else + tableInsert(copyArray, 0x0) + tableInsert(copyArray, 0x0) + tableInsert(copyArray, " ") + end + end + end + + return copyArray +end + +local function paste(x, y, copyArray) + local index, arrayIndex + if not copyArray or #copyArray == 0 then error("Массив области экрана пуст.") end + + for j = y, y + copyArray.height - 1 do + for i = x, x + copyArray.width - 1 do + if i >= drawLimitX1 and j >= drawLimitY1 and i <= drawLimitX2 and j <= drawLimitY2 then + --Рассчитываем индекс массива основного изображения + index = getIndex(i, j) + --Копипаст формулы, аккуратнее! + --Рассчитываем индекс массива вставочного изображения + arrayIndex = (copyArray.width * (j - y) + (i - x + 1)) * 3 - 2 + --Вставляем данные + newFrame[index] = copyArray[arrayIndex] + newFrame[index + 1] = copyArray[arrayIndex + 1] + newFrame[index + 2] = copyArray[arrayIndex + 2] + end + end + end +end + +local function rasterizeLine(x1, y1, x2, y2, method) + local inLoopValueFrom, inLoopValueTo, outLoopValueFrom, outLoopValueTo, isReversed, inLoopValueDelta, outLoopValueDelta = x1, x2, y1, y2, false, mathAbs(x2 - x1), mathAbs(y2 - y1) + if inLoopValueDelta < outLoopValueDelta then + inLoopValueFrom, inLoopValueTo, outLoopValueFrom, outLoopValueTo, isReversed, inLoopValueDelta, outLoopValueDelta = y1, y2, x1, x2, true, outLoopValueDelta, inLoopValueDelta + end + + if outLoopValueFrom > outLoopValueTo then + outLoopValueFrom, outLoopValueTo = outLoopValueTo, outLoopValueFrom + inLoopValueFrom, inLoopValueTo = inLoopValueTo, inLoopValueFrom + end + + local outLoopValue, outLoopValueCounter, outLoopValueTriggerIncrement = outLoopValueFrom, 1, inLoopValueDelta / outLoopValueDelta + local outLoopValueTrigger = outLoopValueTriggerIncrement + for inLoopValue = inLoopValueFrom, inLoopValueTo, inLoopValueFrom < inLoopValueTo and 1 or -1 do + if isReversed then + method(outLoopValue, inLoopValue) + else + method(inLoopValue, outLoopValue) + end + + outLoopValueCounter = outLoopValueCounter + 1 + if outLoopValueCounter > outLoopValueTrigger then + outLoopValue, outLoopValueTrigger = outLoopValue + 1, outLoopValueTrigger + outLoopValueTriggerIncrement + end + end +end + +local function line(x1, y1, x2, y2, background, foreground, alpha, symbol) + rasterizeLine(x1, y1, x2, y2, function(x, y) + set(x, y, background, foreground, alpha, symbol) + end) +end + +local function text(x, y, textColor, data, transparency) + if y >= drawLimitY1 and y <= drawLimitY2 then + local charIndex, bufferIndex = 1, getIndex(x, y) + 1 + + for charIndex = 1, unicodeLen(data) do + if x >= drawLimitX1 and x <= drawLimitX2 then + if transparency then + newFrame[bufferIndex] = colorBlend(newFrame[bufferIndex - 1], textColor, transparency) + else + newFrame[bufferIndex] = textColor + end + + newFrame[bufferIndex + 1] = unicodeSub(data, charIndex, charIndex) + end + + x, bufferIndex = x + 1, bufferIndex + 3 + end + end +end + +local function formattedText(x, y, data) + if y >= drawLimitY1 and y <= drawLimitY2 then + local charIndex, bufferIndex, textColor, char, number = 1, getIndex(x, y) + 1, 0xFFFFFF + + while charIndex <= unicodeLen(text) do + if x >= drawLimitX1 and x <= drawLimitX2 then + char = unicodeSub(data, charIndex, charIndex) + if char == "#" then + number = tonumber("0x" .. unicodeSub(data, charIndex + 1, charIndex + 6)) + if number then + textColor, charIndex = number, charIndex + 7 + else + newFrame[bufferIndex], newFrame[bufferIndex + 1], x, charIndex, bufferIndex = textColor, char, x + 1, charIndex + 1, bufferIndex + 3 + end + else + newFrame[bufferIndex], newFrame[bufferIndex + 1], x, charIndex, bufferIndex = textColor, char, x + 1, charIndex + 1, bufferIndex + 3 + end + else + x, charIndex, bufferIndex = x + 1, charIndex + 1, bufferIndex + 3 + end + end + end +end + +local function image(x, y, picture, blendForeground) + local xPos, xEnd, bufferIndexStepOnReachOfImageWidth = x, x + picture[1] - 1, (bufferWidth - picture[1]) * 3 + local bufferIndex, bufferIndexPlus1, imageIndexPlus1, imageIndexPlus2, imageIndexPlus3 = getIndex(x, y) + + for imageIndex = 3, #picture, 4 do + if xPos >= drawLimitX1 and y >= drawLimitY1 and xPos <= drawLimitX2 and y <= drawLimitY2 then + bufferIndexPlus1, imageIndexPlus1, imageIndexPlus2, imageIndexPlus3 = bufferIndex + 1, imageIndex + 1, imageIndex + 2, imageIndex + 3 + + if picture[imageIndexPlus2] == 0 then + newFrame[bufferIndex], newFrame[bufferIndexPlus1] = picture[imageIndex], picture[imageIndexPlus1] + elseif picture[imageIndexPlus2] > 0 and picture[imageIndexPlus2] < 1 then + newFrame[bufferIndex] = colorBlend(newFrame[bufferIndex], picture[imageIndex], picture[imageIndexPlus2]) + + if blendForeground then + newFrame[bufferIndexPlus1] = colorBlend(newFrame[bufferIndexPlus1], picture[imageIndexPlus1], picture[imageIndexPlus2]) + else + newFrame[bufferIndexPlus1] = picture[imageIndexPlus1] + end + elseif picture[imageIndexPlus2] == 1 and picture[imageIndexPlus3] ~= " " then + newFrame[bufferIndexPlus1] = picture[imageIndexPlus1] + end + + newFrame[bufferIndex + 2] = picture[imageIndexPlus3] + end + + xPos, bufferIndex = xPos + 1, bufferIndex + 3 + if xPos > xEnd then + xPos, y, bufferIndex = x, y + 1, bufferIndex + bufferIndexStepOnReachOfImageWidth + end + end +end + +local function frame(x, y, width, height, color) + local stringUp, stringDown, x2 = "┌" .. string.rep("─", width - 2) .. "┐", "└" .. string.rep("─", width - 2) .. "┘", x + width - 1 + text(x, y, color, stringUp); y = y + 1 + for i = 1, height - 2 do + text(x, y, color, "│") + text(x2, y, color, "│") + y = y + 1 + end + + text(x, y, color, stringDown) +end + +-------------------------------------------------------------------------------------------------------------- + +local function semiPixelRawSet(index, color, yPercentTwoEqualsZero) + local upperPixel, lowerPixel, bothPixel, indexPlus1, indexPlus2 = "▀", "▄", " ", index + 1, index + 2 + local background, foreground, symbol = newFrame[index], newFrame[indexPlus1], newFrame[indexPlus2] + + if yPercentTwoEqualsZero then + if symbol == upperPixel then + if color == foreground then + newFrame[index], newFrame[indexPlus1], newFrame[indexPlus2] = color, foreground, bothPixel + else + newFrame[index], newFrame[indexPlus1], newFrame[indexPlus2] = color, foreground, symbol + end + elseif symbol == bothPixel then + if color ~= background then + newFrame[index], newFrame[indexPlus1], newFrame[indexPlus2] = background, color, lowerPixel + end + else + newFrame[index], newFrame[indexPlus1], newFrame[indexPlus2] = background, color, lowerPixel + end + else + if symbol == lowerPixel then + if color == foreground then + newFrame[index], newFrame[indexPlus1], newFrame[indexPlus2] = color, foreground, bothPixel + else + newFrame[index], newFrame[indexPlus1], newFrame[indexPlus2] = color, foreground, symbol + end + elseif symbol == bothPixel then + if color ~= background then + newFrame[index], newFrame[indexPlus1], newFrame[indexPlus2] = background, color, upperPixel + end + else + newFrame[index], newFrame[indexPlus1], newFrame[indexPlus2] = background, color, upperPixel + end + end +end + +local function semiPixelSet(x, y, color) + local yFixed = mathCeil(y / 2) + if x >= drawLimitX1 and yFixed >= drawLimitY1 and x <= drawLimitX2 and yFixed <= drawLimitY2 then + semiPixelRawSet(getIndex(x, yFixed), color, y % 2 == 0) + end +end + +local function semiPixelSquare(x, y, width, height, color) + -- for j = y, y + height - 1 do for i = x, x + width - 1 do semiPixelSet(i, j, color) end end + local index, indexStepForward, indexStepBackward, jPercentTwoEqualsZero, jFixed = getIndex(x, mathCeil(y / 2)), (bufferWidth - width) * 3, width * 3 + for j = y, y + height - 1 do + jPercentTwoEqualsZero = j % 2 == 0 + + for i = x, x + width - 1 do + jFixed = mathCeil(j / 2) + -- if x >= drawLimitX1 and jFixed >= drawLimitY1 and x <= drawLimitX2 and jFixed <= drawLimitY2 then + semiPixelRawSet(index, color, jPercentTwoEqualsZero) + -- end + index = index + 3 + end + + if jPercentTwoEqualsZero then + index = index + indexStepForward + else + index = index - indexStepBackward + end + end +end + +local function semiPixelLine(x1, y1, x2, y2, color) + rasterizeLine(x1, y1, x2, y2, function(x, y) + semiPixelSet(x, y, color) + end) +end + +local function semiPixelCircle(xCenter, yCenter, radius, color) + local function insertPoints(x, y) + semiPixelSet(xCenter + x, yCenter + y, color) + semiPixelSet(xCenter + x, yCenter - y, color) + semiPixelSet(xCenter - x, yCenter + y, color) + semiPixelSet(xCenter - x, yCenter - y, color) + end + + local x, y = 0, radius + local delta = 3 - 2 * radius; + while (x < y) do + insertPoints(x, y); + insertPoints(y, x); + if (delta < 0) then + delta = delta + (4 * x + 6) + else + delta = delta + (4 * (x - y) + 10) + y = y - 1 + end + x = x + 1 + end + + if x == y then insertPoints(x, y) end +end + +-------------------------------------------------------------------------------------------------------------- + +local function getPointTimedPosition(firstPoint, secondPoint, time) + return { + x = firstPoint.x + (secondPoint.x - firstPoint.x) * time, + y = firstPoint.y + (secondPoint.y - firstPoint.y) * time + } +end + +local function getConnectionPoints(points, time) + local connectionPoints = {} + for point = 1, #points - 1 do + tableInsert(connectionPoints, getPointTimedPosition(points[point], points[point + 1], time)) + end + return connectionPoints +end + +local function getMainPointPosition(points, time) + if #points > 1 then + return getMainPointPosition(getConnectionPoints(points, time), time) + else + return points[1] + end +end + +local function semiPixelBezierCurve(points, color, precision) + local linePoints = {} + for time = 0, 1, precision or 0.01 do + tableInsert(linePoints, getMainPointPosition(points, time)) + end + + for point = 1, #linePoints - 1 do + semiPixelLine(mathFloor(linePoints[point].x), mathFloor(linePoints[point].y), mathFloor(linePoints[point + 1].x), mathFloor(linePoints[point + 1].y), color) + end +end + +-- DELETE THIS CYKA BLYAD NAHOOOY ZAEBAL GOVNOKOD PLODIT ---------------------------------------------- + +local function button(x, y, width, height, background, foreground, data) + local textLength = unicodeLen(data) + if textLength > width - 2 then data = unicodeSub(data, 1, width - 2) end + + local textPosX = mathFloor(x + width / 2 - textLength / 2) + local textPosY = mathFloor(y + height / 2) + square(x, y, width, height, background, foreground, " ") + text(textPosX, textPosY, foreground, data) + + return x, y, (x + width - 1), (y + height - 1) +end + +local function adaptiveButton(x, y, xOffset, yOffset, background, foreground, data) + local width = xOffset * 2 + unicodeLen(data) + local height = yOffset * 2 + 1 + + square(x, y, width, height, background, 0xFFFFFF, " ") + text(x + xOffset, y + yOffset, foreground, data) + + return x, y, (x + width - 1), (y + height - 1) +end + +local function framedButton(x, y, width, height, backColor, buttonColor, data) + square(x, y, width, height, backColor, buttonColor, " ") + frame(x, y, width, height, buttonColor) + + x = mathFloor(x + width / 2 - unicodeLen(data) / 2) + y = mathFloor(y + height / 2) + + text(x, y, buttonColor, data) +end + +local function scrollBar(x, y, width, height, countOfAllElements, currentElement, backColor, frontColor) + local sizeOfScrollBar = mathCeil(height / countOfAllElements) + local displayBarFrom = mathFloor(y + height * ((currentElement - 1) / countOfAllElements)) + + square(x, y, width, height, backColor, 0xFFFFFF, " ") + square(x, displayBarFrom, width, sizeOfScrollBar, frontColor, 0xFFFFFF, " ") + + sizeOfScrollBar, displayBarFrom = nil, nil +end + +local function horizontalScrollBar(x, y, width, countOfAllElements, currentElement, background, foreground) + local pipeSize = mathCeil(width / countOfAllElements) + local displayBarFrom = mathFloor(x + width * ((currentElement - 1) / countOfAllElements)) + + text(x, y, background, string.rep("▄", width)) + text(displayBarFrom, y, foreground, string.rep("▄", pipeSize)) +end + +local function customImage(x, y, pixels) + x = x - 1 + y = y - 1 + + for i=1, #pixels do + for j=1, #pixels[1] do + if pixels[i][j][3] ~= "#" then + set(x + j, y + i, pixels[i][j][1], pixels[i][j][2], pixels[i][j][3]) + end + end + end + + return (x + 1), (y + 1), (x + #pixels[1]), (y + #pixels) +end + +-------------------------------------------------------------------------------------------------------------- + +local function debug(...) + local args = {...} + local text = {} + for i = 1, #args do + tableInsert(text, tostring(args[i])) + end + + local b = GPUProxyGetBackground() + local f = GPUProxyGetForeground() + GPUProxySetBackground(0x0) + GPUProxySetForeground(0xFFFFFF) + GPUProxyFill(1, bufferHeight, bufferWidth, 1, " ") + GPUProxySet(2, bufferHeight, tableConcat(text, ", ")) + GPUProxySetBackground(b) + GPUProxySetForeground(f) +end + +local function draw(force) + -- local oldClock = os.clock() + + local changes, index, indexStepOnEveryLine = {}, getIndex(drawLimitX1, drawLimitY1), (bufferWidth - drawLimitX2 + drawLimitX1 - 1) * 3 + local x, indexPlus1, indexPlus2, equalChars, charX, charIndex, charIndexPlus1, charIndexPlus2, currentForeground + local currentFrameIndex, currentFrameIndexPlus1, currentFrameIndexPlus2, changesCurrentFrameIndex, changesCurrentFrameIndexCurrentFrameIndexPlus1 + + for y = drawLimitY1, drawLimitY2 do + x = drawLimitX1 + while x <= drawLimitX2 do + indexPlus1, indexPlus2 = index + 1, index + 2 + + -- Determine if some pixel data was changed (or if argument was passed) + if + currentFrame[index] ~= newFrame[index] or + currentFrame[indexPlus1] ~= newFrame[indexPlus1] or + currentFrame[indexPlus2] ~= newFrame[indexPlus2] or + force + then + -- Make pixel at both frames equal + currentFrameIndex, currentFrameIndexPlus1, currentFrameIndexPlus2 = newFrame[index], newFrame[indexPlus1], newFrame[indexPlus2] + currentFrame[index] = currentFrameIndex + currentFrame[indexPlus1] = currentFrameIndexPlus1 + currentFrame[indexPlus2] = currentFrameIndexPlus2 + + -- Look for pixels with equal chars from right of current pixel + equalChars = {currentFrameIndexPlus2} + charX, charIndex = x + 1, index + 3 + while charX <= drawLimitX2 do + charIndexPlus1, charIndexPlus2 = charIndex + 1, charIndex + 2 + -- Pixels becomes equal only if they have same background and (whitespace char or same foreground) + if + currentFrameIndex == newFrame[charIndex] and + ( + newFrame[charIndexPlus2] == " " or + currentFrameIndexPlus1 == newFrame[charIndexPlus1] + ) + then + -- Make pixel at both frames equal + currentFrame[charIndex] = newFrame[charIndex] + currentFrame[charIndexPlus1] = newFrame[charIndexPlus1] + currentFrame[charIndexPlus2] = newFrame[charIndexPlus2] + + tableInsert(equalChars, currentFrame[charIndexPlus2]) + else + break + end + + charIndex, charX = charIndex + 3, charX + 1 + end + + -- Group pixels that need to be drawn by background and foreground + changes[currentFrameIndex] = changes[currentFrameIndex] or {} + changesCurrentFrameIndex = changes[currentFrameIndex] + changesCurrentFrameIndex[currentFrameIndexPlus1] = changesCurrentFrameIndex[currentFrameIndexPlus1] or {} + changesCurrentFrameIndexCurrentFrameIndexPlus1 = changesCurrentFrameIndex[currentFrameIndexPlus1] + + tableInsert(changesCurrentFrameIndexCurrentFrameIndexPlus1, x) + tableInsert(changesCurrentFrameIndexCurrentFrameIndexPlus1, y) + tableInsert(changesCurrentFrameIndexCurrentFrameIndexPlus1, tableConcat(equalChars)) + + x, index = x + #equalChars - 1, index + (#equalChars - 1) * 3 + end + + x, index = x + 1, index + 3 + end + + index = index + indexStepOnEveryLine + end + + -- Draw grouped pixels on screen + for background, foregrounds in pairs(changes) do + GPUProxySetBackground(background) + + for foreground, pixels in pairs(foregrounds) do + if currentForeground ~= foreground then + GPUProxySetForeground(foreground) + currentForeground = foreground + end + + for i = 1, #pixels, 3 do + GPUProxySet(pixels[i], pixels[i + 1], pixels[i + 2]) + end + end + end + + changes = nil + + -- debug("os.clock() delta: " .. (os.clock() - oldClock)) +end + +------------------------------------------------------------------------------------------------------ + +bindGPU(component.getPrimary("gpu").address) + +------------------------------------------------------------------------------------------------------ + +return { + getCoordinates = getCoordinates, + getIndex = getIndex, + setDrawLimit = setDrawLimit, + resetDrawLimit = resetDrawLimit, + getDrawLimit = getDrawLimit, + flush = flush, + setResolution = setResolution, + bindScreen = bindScreen, + bindGPU = bindGPU, + getGPUProxy = getGPUProxy, + getResolution = getResolution, + getWidth = getWidth, + getHeight = getHeight, + rawSet = rawSet, + rawGet = rawGet, + get = get, + set = set, + square = square, + clear = clear, + copy = copy, + paste = paste, + rasterizeLine = rasterizeLine, + line = line, + text = text, + formattedText = formattedText, + image = image, + frame = frame, + semiPixelRawSet = semiPixelRawSet, + semiPixelSet = semiPixelSet, + semiPixelSquare = semiPixelSquare, + semiPixelLine = semiPixelLine, + semiPixelCircle = semiPixelCircle, + semiPixelBezierCurve = semiPixelBezierCurve, + draw = draw, + debug = debug, + + button = button, + adaptiveButton = adaptiveButton, + framedButton = framedButton, + scrollBar = scrollBar, + horizontalScrollBar = horizontalScrollBar, + customImage = customImage, +} \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/event.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/event.lua new file mode 100755 index 00000000..c7beee91 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/event.lua @@ -0,0 +1,227 @@ + +-- This is a fast OpenComputers event processing library written as an alternative +-- for its' OpenOS analogue which has become too slow and inefficient in the latest updates + +-------------------------------------------------------------------------------------------------------- + +local computer = require("computer") + +local event, handlers, interruptingKeysDown, lastInterrupt = { + push = computer.pushSignal, + interruptingEnabled = true, + interruptingDelay = 1, + interruptingKeyCodes = { + [29] = true, + [46] = true, + [56] = true + }, + onError = function(errorMessage) + -- require("GUI").error("Event handler error: \"" .. tostring(errorMessage) .. "\"") + end +}, {}, {}, 0 + +-------------------------------------------------------------------------------------------------------- + +function event.addHandler(callback, signalType, times, interval) + checkArg(1, callback, "function") + checkArg(2, signalType, "string", "nil") + checkArg(3, times, "number", "nil") + checkArg(4, nextTriggerTime, "number", "nil") + + local ID = math.random(1, 0x7FFFFFFF) + while handlers[ID] do + ID = math.random(1, 0x7FFFFFFF) + end + + handlers[ID] = { + signalType = signalType, + callback = callback, + times = times or math.huge, + interval = interval, + nextTriggerTime = interval and (computer.uptime() + interval) or nil + } + + return ID +end + +function event.removeHandler(ID) + checkArg(1, ID, "number") + + if handlers[ID] then + handlers[ID] = nil + return true + else + return false, "No registered handlers found for ID \"" .. ID .. "\"" + end +end + +function event.getHandler(ID) + checkArg(1, ID, "number") + + if handlers[ID] then + return handlers[ID] + else + return false, "No registered handlers found for ID \"" .. ID .. "\"" + end +end + +-------------------------------------------------------------------------------------------------------- + +function event.listen(signalType, callback) + checkArg(1, signalType, "string") + checkArg(2, callback, "function") + + for ID, handler in pairs(handlers) do + if handler.callback == callback then + return false, "Callback method " .. tostring(callback) .. " is already registered" + end + end + + event.addHandler(callback, signalType) + return true +end + +function event.ignore(signalType, callback) + checkArg(1, signalType, "string") + checkArg(2, callback, "function") + + for ID, handler in pairs(handlers) do + if handler.signalType == signalType and handler.callback == callback then + handlers[ID] = nil + return true + end + end + + return false, "No registered listeners found for signal \"" .. signalType .. "\" and callback method \"" .. tostring(callback) +end + +-------------------------------------------------------------------------------------------------------- + +function event.timer(interval, callback, times) + checkArg(1, interval, "number") + checkArg(2, callback, "function") + checkArg(3, times, "number", "nil") + + return event.addHandler(callback, nil, times or 1, interval) +end + +event.cancel = event.removeHandler + +-------------------------------------------------------------------------------------------------------- + +local function executeHandlerCallback(callback, ...) + local success, result = pcall(callback, ...) + if success then + return result + else + if type(event.onError) == "function" then + pcall(event.onError, result) + end + end +end + +function event.skip(signalType) + event.skipSignalType = signalType +end + +function event.pull(...) + local args = {...} + + local args1Type, preferredTimeout, signalType = type(args[1]) + if args1Type == "string" then + preferredTimeout, signalType = math.huge, args[1] + elseif args1Type == "number" then + preferredTimeout, signalType = args[1], type(args[2]) == "string" and args[2] or nil + end + + local uptime, signalData, timeout = computer.uptime() + local deadline = uptime + (preferredTimeout or math.huge) + + while uptime <= deadline do + -- Determining pullSignal timeout + timeout = deadline + for ID, handler in pairs(handlers) do + if handler.nextTriggerTime then + timeout = math.min(timeout, handler.nextTriggerTime) + end + end + + -- Pulling signal data + signalData = { computer.pullSignal(timeout - computer.uptime()) } + + -- Handlers processing + for ID, handler in pairs(handlers) do + if handler.times > 0 then + uptime = computer.uptime() + + if + (not handler.signalType or handler.signalType == signalData[1]) and + (not handler.nextTriggerTime or handler.nextTriggerTime <= uptime) + then + handler.times = handler.times - 1 + if handler.nextTriggerTime then + handler.nextTriggerTime = uptime + handler.interval + end + + executeHandlerCallback(handler.callback, table.unpack(signalData)) + end + else + handlers[ID] = nil + end + end + + -- Interruption support + if event.interruptingEnabled then + -- Analysing for which interrupting key is pressed - we don't need keyboard API for this + if signalData[1] == "key_down" then + if event.interruptingKeyCodes[signalData[4]] then + interruptingKeysDown[signalData[4]] = true + end + elseif signalData[1] == "key_up" then + if event.interruptingKeyCodes[signalData[4]] then + interruptingKeysDown[signalData[4]] = nil + end + end + + local shouldInterrupt = true + for keyCode in pairs(event.interruptingKeyCodes) do + if not interruptingKeysDown[keyCode] then + shouldInterrupt = false + end + end + + if shouldInterrupt and uptime - lastInterrupt > event.interruptingDelay then + lastInterrupt = uptime + error("interrupted", 0) + end + end + + -- Loop-breaking conditions + if signalData[1] and (not signalType or signalType == signalData[1]) then + if signalData[1] == event.skipSignalType then + event.skipSignalType = nil + else + return table.unpack(signalData) + end + end + end +end + +-------------------------------------------------------------------------------------------------------- + +local doubleTouchInterval, lastTouchX, lastTouchY, lastTouchButton, lastTouchUptime, lastTouchScreenAddress = 0.3, 0, 0, 0, 0 + +event.listen("touch", function(signalType, screenAddress, x, y, button, user) + local uptime = computer.uptime() + + if lastTouchX == x and lastTouchY == y and lastTouchButton == button and lastTouchScreenAddress == screenAddress and uptime - lastTouchUptime <= doubleTouchInterval then + event.skip("touch") + computer.pushSignal("double_touch", screenAddress, x, y, button, user) + end + + lastTouchX, lastTouchY, lastTouchButton, lastTouchUptime, lastTouchScreenAddress = x, y, button, uptime, screenAddress +end) + +-------------------------------------------------------------------------------------------------------- + +return event diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/filesystem.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/filesystem.lua new file mode 100755 index 00000000..8f7c553d --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/filesystem.lua @@ -0,0 +1,515 @@ +local component = require("component") +local unicode = require("unicode") + +local filesystem, fileStream = {}, {} +local isAutorunEnabled = nil +local mtab = {name="", children={}, links={}} +local fstab = {} + +local function segments(path) + local parts = {} + for part in path:gmatch("[^\\/]+") do + local current, up = part:find("^%.?%.$") + if current then + if up == 2 then + table.remove(parts) + end + else + table.insert(parts, part) + end + end + return parts +end + +local function saveConfig() + local root = filesystem.get("/") + if root and not root.isReadOnly() then + filesystem.makeDirectory("/etc") + local f = filesystem.open("/etc/filesystem.cfg", "w") + if f then + f:write("autorun="..tostring(isAutorunEnabled)) + f:close() + end + end +end + +local function findNode(path, create, resolve_links) + checkArg(1, path, "string") + local visited = {} + local parts = segments(path) + local ancestry = {} + local node = mtab + local index = 1 + while index <= #parts do + local part = parts[index] + ancestry[index] = node + if not node.children[part] then + local link_path = node.links[part] + if link_path then + if not resolve_links and #parts == index then break end + + if visited[path] then + return nil, string.format("link cycle detected '%s'", path) + end + -- the previous parts need to be conserved in case of future ../.. link cuts + visited[path] = index + local pst_path = "/" .. table.concat(parts, "/", index + 1) + local pre_path + + if link_path:match("^[^/]") then + pre_path = table.concat(parts, "/", 1, index - 1) .. "/" + local link_parts = segments(link_path) + local join_parts = segments(pre_path .. link_path) + local back = (index - 1 + #link_parts) - #join_parts + index = index - back + node = ancestry[index] + else + pre_path = "" + index = 1 + node = mtab + end + + path = pre_path .. link_path .. pst_path + parts = segments(path) + part = nil -- skip node movement + elseif create then + node.children[part] = {name=part, parent=node, children={}, links={}} + else + break + end + end + if part then + node = node.children[part] + index = index + 1 + end + end + + local vnode, vrest = node, #parts >= index and table.concat(parts, "/", index) + local rest = vrest + while node and not node.fs do + rest = rest and filesystem.concat(node.name, rest) or node.name + node = node.parent + end + return node, rest, vnode, vrest +end + +------------------------------------------------------------------------------- + +function filesystem.isAutorunEnabled() + if isAutorunEnabled == nil then + local env = {} + local config = loadfile("/etc/filesystem.cfg", nil, env) + if config then + pcall(config) + isAutorunEnabled = not not env.autorun + else + isAutorunEnabled = true + end + saveConfig() + end + return isAutorunEnabled +end + +function filesystem.setAutorunEnabled(value) + checkArg(1, value, "boolean") + isAutorunEnabled = value + saveConfig() +end + +filesystem.segments = segments + +function filesystem.canonical(path) + local result = table.concat(segments(path), "/") + if unicode.sub(path, 1, 1) == "/" then + return "/" .. result + else + return result + end +end + +function filesystem.concat(...) + local set = table.pack(...) + for index, value in ipairs(set) do + checkArg(index, value, "string") + end + return filesystem.canonical(table.concat(set, "/")) +end + +function filesystem.get(path) + local node, rest = findNode(path) + if node.fs then + local proxy = node.fs + path = "" + while node and node.parent do + path = filesystem.concat(node.name, path) + node = node.parent + end + path = filesystem.canonical(path) + if path ~= "/" then + path = "/" .. path + end + return proxy, path + end + return nil, "no such file system" +end + +function filesystem.realPath(path) + checkArg(1, path, "string") + local node, rest, vnode, vrest = findNode(path, false, true) + if not node then return nil, rest end + local parts = {rest or nil} + repeat + table.insert(parts, 1, node.name) + node = node.parent + until not node + return table.concat(parts, "/") +end + +function filesystem.isLink(path) + local name = filesystem.name(path) + local node, rest, vnode, vrest = findNode(filesystem.path(path), false, true) + if not node then return nil, rest end + local target = vnode.links[name] + -- having vrest here indicates we are not at the + -- owning vnode due to a mount point above this point + -- but we can have a target when there is a link at + -- the mount point root, with the same name + if not vrest and target ~= nil then + return true, target + end + return false +end + +function filesystem.link(target, linkpath) + checkArg(1, target, "string") + checkArg(2, linkpath, "string") + + if filesystem.exists(linkpath) then + return nil, "file already exists" + end + local linkpath_parent = filesystem.path(linkpath) + if not filesystem.exists(linkpath_parent) then + return nil, "no such directory" + end + local linkpath_real, reason = filesystem.realPath(linkpath_parent) + if not linkpath_real then + return nil, reason + end + if not filesystem.isDirectory(linkpath_real) then + return nil, "not a directory" + end + + local node, rest, vnode, vrest = findNode(linkpath_real, true) + vnode.links[filesystem.name(linkpath)] = target + return true +end + +function filesystem.mount(fs, path) + checkArg(1, fs, "string", "table") + if type(fs) == "string" then + fs = filesystem.proxy(fs) + end + assert(type(fs) == "table", "bad argument #1 (file system proxy or address expected)") + checkArg(2, path, "string") + + local real + if not mtab.fs then + if path == "/" then + real = path + else + return nil, "rootfs must be mounted first" + end + else + local why + real, why = filesystem.realPath(path) + if not real then + return nil, why + end + + if filesystem.exists(real) then + return nil, "file already exists" + end + end + + local fsnode + if fstab[real] then + return nil, "another filesystem is already mounted here" + end + for path,node in pairs(fstab) do + if node.fs.address == fs.address then + fsnode = node + break + end + end + + if not fsnode then + fsnode = select(3, findNode(real, true)) + -- allow filesystems to intercept their own nodes + fs.fsnode = fsnode + else + local pwd = filesystem.path(real) + local parent = select(3, findNode(pwd, true)) + local name = filesystem.name(real) + fsnode = setmetatable({name=name,parent=parent},{__index=fsnode}) + parent.children[name] = fsnode + end + + fsnode.fs = fs + fstab[real] = fsnode + + return true +end + +function filesystem.mounts() + local tmp = {} + for path,node in pairs(fstab) do + table.insert(tmp, {node.fs,path}) + end + return function() + local next = table.remove(tmp) + if next then return table.unpack(next) end + end +end + +function filesystem.path(path) + local parts = segments(path) + local result = table.concat(parts, "/", 1, #parts - 1) .. "/" + if unicode.sub(path, 1, 1) == "/" and unicode.sub(result, 1, 1) ~= "/" then + return "/" .. result + else + return result + end +end + +function filesystem.name(path) + local parts = segments(path) + return parts[#parts] +end + +function filesystem.proxy(filter) + checkArg(1, filter, "string") + local address + for c in component.list("filesystem", true) do + if component.invoke(c, "getLabel") == filter then + address = c + break + end + if c:sub(1, filter:len()) == filter then + address = c + break + end + end + if not address then + return nil, "no such file system" + end + return component.proxy(address) +end + +function filesystem.umount(fsOrPath) + checkArg(1, fsOrPath, "string", "table") + local real + local fs + local addr + if type(fsOrPath) == "string" then + real = filesystem.realPath(fsOrPath) + addr = fsOrPath + else -- table + fs = fsOrPath + end + + local paths = {} + for path,node in pairs(fstab) do + if real == path or addr == node.fs.address or fs == node.fs then + table.insert(paths, path) + end + end + for _,path in ipairs(paths) do + local node = fstab[path] + fstab[path] = nil + node.fs = nil + node.parent.children[node.name] = nil + end + return #paths > 0 +end + +function filesystem.exists(path) + if not filesystem.realPath(filesystem.path(path)) then + return false + end + local node, rest, vnode, vrest = findNode(path) + if not vrest or vnode.links[vrest] then -- virtual directory or symbolic link + return true + elseif node and node.fs then + return node.fs.exists(rest) + end + return false +end + +function filesystem.size(path) + local node, rest, vnode, vrest = findNode(path, false, true) + if not node or not vnode.fs and (not vrest or vnode.links[vrest]) then + return 0 -- virtual directory or symlink + end + if node.fs and rest then + return node.fs.size(rest) + end + return 0 -- no such file or directory +end + +function filesystem.isDirectory(path) + local real, reason = filesystem.realPath(path) + if not real then return nil, reason end + local node, rest, vnode, vrest = findNode(real) + if not vnode.fs and not vrest then + return true -- virtual directory (mount point) + end + if node.fs then + return not rest or node.fs.isDirectory(rest) + end + return false +end + +function filesystem.lastModified(path) + local node, rest, vnode, vrest = findNode(path, false, true) + if not node or not vnode.fs and not vrest then + return 0 -- virtual directory + end + if node.fs and rest then + return node.fs.lastModified(rest) + end + return 0 -- no such file or directory +end + +function filesystem.list(path) + local node, rest, vnode, vrest = findNode(path, false, true) + local result = {} + if node then + result = node.fs and node.fs.list(rest or "") or {} + -- `if not vrest` indicates that vnode reached the end of path + -- in other words, vnode[children, links] represent path + if not vrest then + for k,n in pairs(vnode.children) do + if not n.fs or fstab[filesystem.concat(path, k)] then + table.insert(result, k .. "/") + end + end + for k in pairs(vnode.links) do + table.insert(result, k) + end + end + end + local set = {} + for _,name in ipairs(result) do + set[filesystem.canonical(name)] = name + end + return function() + local key, value = next(set) + set[key or false] = nil + return value + end +end + +function filesystem.makeDirectory(path) + if filesystem.exists(path) then + return nil, "file or directory with that name already exists" + end + local node, rest = findNode(path) + if node.fs and rest then + local success, reason = node.fs.makeDirectory(rest) + if not success and not reason and node.fs.isReadOnly() then + reason = "filesystem is readonly" + end + return success, reason + end + if node.fs then + return nil, "virtual directory with that name already exists" + end + return nil, "cannot create a directory in a virtual directory" +end + +function filesystem.remove(path) + return require("tools/fsmod").remove(path, findNode) +end + +function filesystem.rename(oldPath, newPath) + return require("tools/fsmod").rename(oldPath, newPath, findNode) +end + +function filesystem.copy(fromPath, toPath) + local data + local input, reason = filesystem.open(fromPath, "rb") + if input then + local output, reason = filesystem.open(toPath, "wb") + if output then + repeat + data, reason = input:read(1024) + if not data then break end + data, reason = output:write(data) + if not data then data, reason = false, "failed to write" end + until not data + output:close() + end + input:close() + end + return data == nil, reason +end + +function fileStream:close() + if self.handle then + self.fs.close(self.handle) + self.handle = nil + end +end + +function fileStream:read(n) + if not self.handle then + return nil, "file is closed" + end + return self.fs.read(self.handle, n) +end + +function fileStream:seek(whence, offset) + if not self.handle then + return nil, "file is closed" + end + return self.fs.seek(self.handle, whence, offset) +end + +function fileStream:write(str) + if not self.handle then + return nil, "file is closed" + end + return self.fs.write(self.handle, str) +end + +function filesystem.open(path, mode) + checkArg(1, path, "string") + mode = tostring(mode or "r") + checkArg(2, mode, "string") + + assert(({r=true, rb=true, w=true, wb=true, a=true, ab=true})[mode], + "bad argument #2 (r[b], w[b] or a[b] expected, got " .. mode .. ")") + + local node, rest = findNode(path, false, true) + if not node then + return nil, rest + end + if not node.fs or not rest or (({r=true,rb=true})[mode] and not node.fs.exists(rest)) then + return nil, "file not found" + end + + local handle, reason = node.fs.open(rest, mode) + if not handle then + return nil, reason + end + + local stream = {fs = node.fs, handle = handle} + + local metatable = {__index = fileStream, + __metatable = "filestream"} + return setmetatable(stream, metatable) +end + +------------------------------------------------------------------------------- + +return filesystem diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/image.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/image.lua new file mode 100755 index 00000000..24a0e10d --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/image.lua @@ -0,0 +1,444 @@ + +-------------------------------------------------- Libraries -------------------------------------------------- + +local color = require("color") +local unicode = require("unicode") +local fs = require("filesystem") +local gpu = require("component").gpu + +-------------------------------------------------- Constants -------------------------------------------------- + +local image = {} +image.formatModules = {} + +-------------------------------------------------- Low-level methods -------------------------------------------------- + +function image.iterationYield(iteration) + if iteration % 603 == 0 then os.sleep(0) end +end + +function image.getImageCoordinatesByIndex(index, width) + local integer, fractional = math.modf((index - 2) / (width * 4)) + return math.ceil(fractional * width), integer + 1 +end + +function image.getImageIndexByCoordinates(x, y, width) + return (width * 4) * (y - 1) + x * 4 - 1 +end + +function image.group(picture, compressColors) + local groupedPicture, x, y, iPlus2, iPlus3, background, foreground = {}, 1, 1 + + for i = 3, #picture, 4 do + iPlus2, iPlus3 = i + 2, i + 3 + + if compressColors then + background, foreground = color.to8Bit(picture[i]), color.to8Bit(picture[i + 1]) + image.iterationYield(i) + else + background, foreground = picture[i], picture[i + 1] + end + + groupedPicture[picture[iPlus2]] = groupedPicture[picture[iPlus2]] or {} + groupedPicture[picture[iPlus2]][picture[iPlus3]] = groupedPicture[picture[iPlus2]][picture[iPlus3]] or {} + groupedPicture[picture[iPlus2]][picture[iPlus3]][background] = groupedPicture[picture[iPlus2]][picture[iPlus3]][background] or {} + groupedPicture[picture[iPlus2]][picture[iPlus3]][background][foreground] = groupedPicture[picture[iPlus2]][picture[iPlus3]][background][foreground] or {} + groupedPicture[picture[iPlus2]][picture[iPlus3]][background][foreground][y] = groupedPicture[picture[iPlus2]][picture[iPlus3]][background][foreground][y] or {} + + table.insert(groupedPicture[picture[iPlus2]][picture[iPlus3]][background][foreground][y], x) + + x = x + 1 + if x > picture[1] then + x, y = 1, y + 1 + end + end + + return groupedPicture +end + +function image.draw(x, y, picture) + local groupedPicture = image.group(picture) + local _, _, currentBackground, currentForeground, gpuGetBackground, imageX, imageY + + for alpha in pairs(groupedPicture) do + for symbol in pairs(groupedPicture[alpha]) do + + if not (symbol == " " and alpha == 1) then + for background in pairs(groupedPicture[alpha][symbol]) do + + if background ~= currentBackground then + currentBackground = background + gpu.setBackground(background) + end + + for foreground in pairs(groupedPicture[alpha][symbol][background]) do + + if foreground ~= currentForeground and symbol ~= " " then + currentForeground = foreground + gpu.setForeground(foreground) + end + + for yPos in pairs(groupedPicture[alpha][symbol][background][foreground]) do + for xPos = 1, #groupedPicture[alpha][symbol][background][foreground][yPos] do + imageX, imageY = x + groupedPicture[alpha][symbol][background][foreground][yPos][xPos] - 1, y + yPos - 1 + + if alpha > 0 then + _, _, gpuGetBackground = gpu.get(imageX, imageY) + + if alpha == 1 then + currentBackground = gpuGetBackground + gpu.setBackground(currentBackground) + else + currentBackground = color.blend(gpuGetBackground, background, alpha) + gpu.setBackground(currentBackground) + end + end + + gpu.set(imageX, imageY, symbol) + end + end + end + end + end + end + end +end + +function image.create(width, height, background, foreground, alpha, symbol, random) + local picture = {width, height} + + for i = 1, width * height do + table.insert(picture, random and math.random(0x0, 0xFFFFFF) or (background or 0x0)) + table.insert(picture, random and math.random(0x0, 0xFFFFFF) or (foreground or 0x0)) + table.insert(picture, alpha or 0x0) + table.insert(picture, random and string.char(math.random(65, 90)) or (symbol or " ")) + end + + return picture +end + +function image.copy(picture) + local newPicture = {} + for i = 1, #picture do + table.insert(newPicture, picture[i]) + end + + return newPicture +end + +function image.optimize(picture) + local iPlus1, iPlus2, iPlus3 + + for i = 3, #picture, 4 do + iPlus1, iPlus2, iPlus3 = i + 1, i + 2, i + 3 + + if picture[i] == picture[iPlus1] and (picture[iPlus3] == "▄" or picture[iPlus3] == "▀") then + picture[iPlus3] = " " + end + + if picture[iPlus3] == " " then + picture[iPlus1] = 0x000000 + end + end + + return picture +end + +-------------------------------------------------- Filesystem related methods -------------------------------------------------- + +function image.loadFormatModule(path, fileExtension) + local loadSuccess, loadReason = loadfile(path) + if loadSuccess then + local xpcallSuccess, xpcallReason = pcall(loadSuccess, image) + if xpcallSuccess then + image.formatModules[fileExtension] = xpcallReason + else + error("Failed to execute image format module: " .. tostring(xpcallReason)) + end + else + error("Failed to load image format module: " .. tostring(loadReason)) + end +end + +local function getFileExtension(path) + return string.match(path, "^.+(%.[^%/]+)%/?$") +end + +local function loadOrSave(methodName, path, ...) + local fileExtension = getFileExtension(path) + if image.formatModules[fileExtension] then + return image.formatModules[fileExtension][methodName](path, ...) + else + error("Failed to open file \"" .. tostring(path) .. "\" as image: format module for extension \"" .. tostring(fileExtension) .. "\" is not loaded") + end +end + +function image.save(path, picture, encodingMethod) + return loadOrSave("save", path, image.optimize(picture), encodingMethod) +end + +function image.load(path) + return loadOrSave("load", path) +end + +-------------------------------------------------- Image serialization -------------------------------------------------- + +function image.toString(picture) + local charArray = { + string.format("%02X", picture[1]), + string.format("%02X", picture[2]) + } + + for i = 3, #picture, 4 do + table.insert(charArray, string.format("%02X", color.to8Bit(picture[i]))) + table.insert(charArray, string.format("%02X", color.to8Bit(picture[i + 1]))) + table.insert(charArray, string.format("%02X", math.floor(picture[i + 2] * 255))) + table.insert(charArray, picture[i + 3]) + + image.iterationYield(i) + end + + return table.concat(charArray) +end + +function image.fromString(pictureString) + local picture = { + tonumber("0x" .. unicode.sub(pictureString, 1, 2)), + tonumber("0x" .. unicode.sub(pictureString, 3, 4)) + } + + for i = 5, unicode.len(pictureString), 7 do + table.insert(picture, color.to24Bit(tonumber("0x" .. unicode.sub(pictureString, i, i + 1)))) + table.insert(picture, color.to24Bit(tonumber("0x" .. unicode.sub(pictureString, i + 2, i + 3)))) + table.insert(picture, tonumber("0x" .. unicode.sub(pictureString, i + 4, i + 5)) / 255) + table.insert(picture, unicode.sub(pictureString, i + 6, i + 6)) + end + + return picture +end + +-------------------------------------------------- Image processing -------------------------------------------------- + +function image.set(picture, x, y, background, foreground, alpha, symbol) + local index = image.getImageIndexByCoordinates(x, y, picture[1]) + picture[index], picture[index + 1], picture[index + 2], picture[index + 3] = background, foreground, alpha, symbol + + return picture +end + +function image.get(picture, x, y) + local index = image.getImageIndexByCoordinates(x, y, picture[1]) + return picture[index], picture[index + 1], picture[index + 2], picture[index + 3] +end + +function image.getSize(picture) + return picture[1], picture[2] +end + +function image.getWidth(picture) + return picture[1] +end + +function image.getHeight(picture) + return picture[2] +end + +function image.transform(picture, newWidth, newHeight) + local newPicture, stepWidth, stepHeight, background, foreground, alpha, symbol = {newWidth, newHeight}, picture[1] / newWidth, picture[2] / newHeight + + local x, y = 1, 1 + for j = 1, newHeight do + for i = 1, newWidth do + background, foreground, alpha, symbol = image.get(picture, math.floor(x), math.floor(y)) + table.insert(newPicture, background) + table.insert(newPicture, foreground) + table.insert(newPicture, alpha) + table.insert(newPicture, symbol) + + x = x + stepWidth + end + x, y = 1, y + stepHeight + end + + return newPicture +end + +function image.crop(picture, fromX, fromY, width, height) + if fromX >= 1 and fromY >= 1 and fromX + width - 1 <= picture[1] and fromY + height - 1 <= picture[2] then + local newPicture, background, foreground, alpha, symbol = {width, height} + + for y = fromY, fromY + height - 1 do + for x = fromX, fromX + width - 1 do + background, foreground, alpha, symbol = image.get(picture, x, y) + table.insert(newPicture, background) + table.insert(newPicture, foreground) + table.insert(newPicture, alpha) + table.insert(newPicture, symbol) + end + end + + return newPicture + else + error("Failed to crop image: target coordinates are out of source range") + end +end + +function image.flipHorizontally(picture) + local newPicture, background, foreground, alpha, symbol = {picture[1], picture[2]} + + for y = 1, picture[2] do + for x = picture[1], 1, -1 do + background, foreground, alpha, symbol = image.get(picture, x, y) + table.insert(newPicture, background) + table.insert(newPicture, foreground) + table.insert(newPicture, alpha) + table.insert(newPicture, symbol) + end + end + + return newPicture +end + +function image.flipVertically(picture) + local newPicture, background, foreground, alpha, symbol = {picture[1], picture[2]} + + for y = picture[2], 1, -1 do + for x = 1, picture[1] do + background, foreground, alpha, symbol = image.get(picture, x, y) + table.insert(newPicture, background) + table.insert(newPicture, foreground) + table.insert(newPicture, alpha) + table.insert(newPicture, symbol) + end + end + + return newPicture +end + +function image.expand(picture, fromTop, fromBottom, fromLeft, fromRight, background, foreground, alpha, symbol) + local newPicture = image.create(picture[1] + fromRight + fromLeft, picture[2] + fromTop + fromBottom, background, foreground, alpha, symbol) + + for y = 1, picture[2] do + for x = 1, picture[1] do + image.set(newPicture, x + fromLeft, y + fromTop, image.get(picture, x, y)) + end + end + + return newPicture +end + +function image.blend(picture, blendColor, transparency) + local newPicture = {picture[1], picture[2]} + + for i = 3, #picture, 4 do + table.insert(newPicture, color.blend(picture[i], blendColor, transparency)) + table.insert(newPicture, color.blend(picture[i + 1], blendColor, transparency)) + table.insert(newPicture, picture[i + 2]) + table.insert(newPicture, picture[i + 3]) + end + + return newPicture +end + +function image.blur(picture, radius, strength) + local blurMatrix = {} + + local xValue, yValue, step = 1, 1, 1 / radius + for y = 0, radius do + for x = 0, radius do + blurMatrix[y], blurMatrix[-y] = blurMatrix[y] or {}, blurMatrix[-y] or {} + + blurMatrix[y][x] = (xValue + yValue) / 2 * strength + blurMatrix[y][-x], blurMatrix[-y][x], blurMatrix[-y][-x] = blurMatrix[y][x], blurMatrix[y][x], blurMatrix[y][x] + + xValue = xValue - step + end + + xValue, yValue = 1, yValue - step + end + + local newPicture, xImage, yImage = image.copy(picture) + for y = 1, image.getHeight(picture) do + for x = 1, image.getWidth(picture) do + local backgroundOld, foregroundOld, alpha, symbol = image.get(picture, x, y) + + for yMatrix = -radius, radius do + for xMatrix = -radius, radius do + xImage, yImage = x + xMatrix, y + yMatrix + + if xImage >= 1 and xImage <= image.getWidth(picture) and yImage >= 1 and yImage <= image.getHeight(picture) then + local backgroundNew, foregroundNew = image.get(newPicture, xImage, yImage) + + image.set(newPicture, xImage, yImage, + color.blend(backgroundOld, backgroundNew, blurMatrix[yMatrix][xMatrix]), + color.blend(foregroundOld, foregroundNew, blurMatrix[yMatrix][xMatrix]), + alpha, + symbol + ) + end + end + end + end + + if y % 2 == 0 then + os.sleep(0.05) + end + end + + return newPicture +end + +function image.rotate(picture, angle) + local radAngle = math.rad(angle) + local sin, cos = math.sin(radAngle), math.cos(radAngle) + local pixMap = {} + + local xCenter, yCenter = picture[1] / 2, picture[2] / 2 + local xMin, xMax, yMin, yMax = math.huge, -math.huge, math.huge, -math.huge + for y = 1, picture[2] do + for x = 1, picture[1] do + local xNew = math.round(xCenter + (x - xCenter) * cos - (y - yCenter) * sin) + local yNew = math.round(yCenter + (y - yCenter) * cos + (x - xCenter) * sin) + + xMin, xMax, yMin, yMax = math.min(xMin, xNew), math.max(xMax, xNew), math.min(yMin, yNew), math.max(yMax, yNew) + + pixMap[yNew] = pixMap[yNew] or {} + pixMap[yNew][xNew] = {image.get(picture, x, y)} + end + end + + local newPicture = image.create(xMax - xMin + 1, yMax - yMin + 1, 0xFF0000, 0x0, 0x0, "#") + for y in pairs(pixMap) do + for x in pairs(pixMap[y]) do + image.set(newPicture, x - xMin + 1, y - yMin + 1, pixMap[y][x][1], pixMap[y][x][2], pixMap[y][x][3], pixMap[y][x][4]) + end + end + + return newPicture +end + +------------------------------------------------------------------------------------------------------------------------ + +image.loadFormatModule("/lib/FormatModules/OCIF.lua", ".pic") + +------------------------------------------------------------------------------------------------------------------------ + +-- local picture = image.load("/MineOS/Pictures/Block.pic") +-- gpu.setBackground(0x2D2D2D) +-- gpu.fill(1, 1, 160, 50, " ") +-- image.draw(2, 2, image.rotate(picture, 180)) + +------------------------------------------------------------------------------------------------------------------------ + +return image + + + + + + + + + + + diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/internet.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/internet.lua new file mode 100755 index 00000000..c924a0da --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/internet.lua @@ -0,0 +1,130 @@ +local buffer = require("buffer") +local component = require("component") +local event = require("event") + +local internet = {} + +------------------------------------------------------------------------------- + +function internet.request(url, data, headers) + checkArg(1, url, "string") + checkArg(2, data, "string", "table", "nil") + checkArg(3, headers, "table", "nil") + + if not component.isAvailable("internet") then + error("no primary internet card found", 2) + end + local inet = component.internet + + local post + if type(data) == "string" then + post = data + elseif type(data) == "table" then + for k, v in pairs(data) do + post = post and (post .. "&") or "" + post = post .. tostring(k) .. "=" .. tostring(v) + end + end + + local request, reason = inet.request(url, post, headers) + if not request then + error(reason, 2) + end + + return setmetatable( + { + ["()"] = "function():string -- Tries to read data from the socket stream and return the read byte array.", + close = setmetatable({}, + { + __call = request.close, + __tostring = function() return "function() -- closes the connection" end + }) + }, + { + __call = function() + while true do + local data, reason = request.read() + if not data then + request.close() + if reason then + error(reason, 2) + else + return nil -- eof + end + elseif #data > 0 then + return data + end + -- else: no data, block + os.sleep(0) + end + end, + __index = request, + }) +end + +------------------------------------------------------------------------------- + +local socketStream = {} + +function socketStream:close() + if self.socket then + self.socket.close() + self.socket = nil + end +end + +function socketStream:seek() + return nil, "bad file descriptor" +end + +function socketStream:read(n) + if not self.socket then + return nil, "connection is closed" + end + return self.socket.read(n) +end + +function socketStream:write(value) + if not self.socket then + return nil, "connection is closed" + end + while #value > 0 do + local written, reason = self.socket.write(value) + if not written then + return nil, reason + end + value = string.sub(value, written + 1) + end + return true +end + +function internet.socket(address, port) + checkArg(1, address, "string") + checkArg(2, port, "number", "nil") + if port then + address = address .. ":" .. port + end + + local inet = component.internet + local socket, reason = inet.connect(address) + if not socket then + return nil, reason + end + + local stream = {inet = inet, socket = socket} + local metatable = {__index = socketStream, + __metatable = "socketstream"} + return setmetatable(stream, metatable) +end + +function internet.open(address, port) + local stream, reason = internet.socket(address, port) + if not stream then + return nil, reason + end + return buffer.new("rwb", stream) +end + +------------------------------------------------------------------------------- + +return internet \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/io.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/io.lua new file mode 100755 index 00000000..9cc26862 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/io.lua @@ -0,0 +1,111 @@ +local io = {} + +------------------------------------------------------------------------------- + +function io.close(file) + return (file or io.output()):close() +end + +function io.flush() + return io.output():flush() +end + +function io.lines(filename, ...) + if filename then + local file, reason = io.open(filename) + if not file then + error(reason, 2) + end + local args = table.pack(...) + return function() + local result = table.pack(file:read(table.unpack(args, 1, args.n))) + if not result[1] then + if result[2] then + error(result[2], 2) + else -- eof + file:close() + return nil + end + end + return table.unpack(result, 1, result.n) + end + else + return io.input():lines() + end +end + +function io.open(path, mode) + -- These requires are not on top because this is a bootstrapped file. + local stream, result = require("filesystem").open(path, mode) + if stream then + return require("buffer").new(mode, stream) + else + return nil, result + end +end + +function io.stream(fd,file,mode) + checkArg(1,fd,'number') + assert(fd>=0,'fd must be >= 0. 0 is input, 1 is stdout, 2 is stderr') + if file then + if type(file) == "string" then + local result, reason = io.open(file, mode) + if not result then + error(reason, 2) + end + file = result + elseif not io.type(file) then + error("bad argument #1 (string or file expected, got " .. type(file) .. ")", 2) + end + require("process").info().data.io[fd] = file + end + return require("process").info().data.io[fd] +end + +function io.input(file) + return io.stream(0, file, 'r') +end + +function io.output(file) + return io.stream(1, file,'w') +end + +function io.error(file) + return io.stream(2, file,'w') +end + +function io.popen(prog, mode, env) + return require('pipes').popen(prog, mode, env) +end + +function io.read(...) + return io.input():read(...) +end + +function io.tmpfile() + local name = os.tmpname() + if name then + return io.open(name, "a") + end +end + +function io.type(object) + if type(object) == "table" then + if getmetatable(object) == "file" then + if object.stream.handle then + return "file" + else + return "closed file" + end + end + end + return nil +end + +function io.write(...) + return io.output():write(...) +end + +------------------------------------------------------------------------------- + +return io diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/json.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/json.lua new file mode 100755 index 00000000..5f114257 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/json.lua @@ -0,0 +1,1053 @@ +-- -*- coding: utf-8 -*- +-- +-- Simple JSON encoding and decoding in pure Lua. +-- +-- Copyright 2010-2014 Jeffrey Friedl +-- http://regex.info/blog/ +-- +-- Latest version: http://regex.info/blog/lua/json +-- +-- This code is released under a Creative Commons CC-BY "Attribution" License: +-- http://creativecommons.org/licenses/by/3.0/deed.en_US +-- +-- It can be used for any purpose so long as the copyright notice above, +-- the web-page links above, and the 'AUTHOR_NOTE' string below are +-- maintained. Enjoy. +-- +local VERSION = 20141223.14 -- version history at end of file +local AUTHOR_NOTE = "-[ JSON.lua package by Jeffrey Friedl (http://regex.info/blog/lua/json) version 20141223.14 ]-" + +-- +-- The 'AUTHOR_NOTE' variable exists so that information about the source +-- of the package is maintained even in compiled versions. It's also +-- included in OBJDEF below mostly to quiet warnings about unused variables. +-- +local OBJDEF = { + VERSION = VERSION, + AUTHOR_NOTE = AUTHOR_NOTE, +} + + +-- +-- Simple JSON encoding and decoding in pure Lua. +-- http://www.json.org/ +-- +-- +-- JSON = assert(loadfile "JSON.lua")() -- one-time load of the routines +-- +-- local lua_value = JSON:decode(raw_json_text) +-- +-- local raw_json_text = JSON:encode(lua_table_or_value) +-- local pretty_json_text = JSON:encode_pretty(lua_table_or_value) -- "pretty printed" version for human readability +-- +-- +-- +-- DECODING (from a JSON string to a Lua table) +-- +-- +-- JSON = assert(loadfile "JSON.lua")() -- one-time load of the routines +-- +-- local lua_value = JSON:decode(raw_json_text) +-- +-- If the JSON text is for an object or an array, e.g. +-- { "what": "books", "count": 3 } +-- or +-- [ "Larry", "Curly", "Moe" ] +-- +-- the result is a Lua table, e.g. +-- { what = "books", count = 3 } +-- or +-- { "Larry", "Curly", "Moe" } +-- +-- +-- The encode and decode routines accept an optional second argument, +-- "etc", which is not used during encoding or decoding, but upon error +-- is passed along to error handlers. It can be of any type (including nil). +-- +-- +-- +-- ERROR HANDLING +-- +-- With most errors during decoding, this code calls +-- +-- JSON:onDecodeError(message, text, location, etc) +-- +-- with a message about the error, and if known, the JSON text being +-- parsed and the byte count where the problem was discovered. You can +-- replace the default JSON:onDecodeError() with your own function. +-- +-- The default onDecodeError() merely augments the message with data +-- about the text and the location if known (and if a second 'etc' +-- argument had been provided to decode(), its value is tacked onto the +-- message as well), and then calls JSON.assert(), which itself defaults +-- to Lua's built-in assert(), and can also be overridden. +-- +-- For example, in an Adobe Lightroom plugin, you might use something like +-- +-- function JSON:onDecodeError(message, text, location, etc) +-- LrErrors.throwUserError("Internal Error: invalid JSON data") +-- end +-- +-- or even just +-- +-- function JSON.assert(message) +-- LrErrors.throwUserError("Internal Error: " .. message) +-- end +-- +-- If JSON:decode() is passed a nil, this is called instead: +-- +-- JSON:onDecodeOfNilError(message, nil, nil, etc) +-- +-- and if JSON:decode() is passed HTML instead of JSON, this is called: +-- +-- JSON:onDecodeOfHTMLError(message, text, nil, etc) +-- +-- The use of the fourth 'etc' argument allows stronger coordination +-- between decoding and error reporting, especially when you provide your +-- own error-handling routines. Continuing with the the Adobe Lightroom +-- plugin example: +-- +-- function JSON:onDecodeError(message, text, location, etc) +-- local note = "Internal Error: invalid JSON data" +-- if type(etc) = 'table' and etc.photo then +-- note = note .. " while processing for " .. etc.photo:getFormattedMetadata('fileName') +-- end +-- LrErrors.throwUserError(note) +-- end +-- +-- : +-- : +-- +-- for i, photo in ipairs(photosToProcess) do +-- : +-- : +-- local data = JSON:decode(someJsonText, { photo = photo }) +-- : +-- : +-- end +-- +-- +-- +-- +-- +-- DECODING AND STRICT TYPES +-- +-- Because both JSON objects and JSON arrays are converted to Lua tables, +-- it's not normally possible to tell which original JSON type a +-- particular Lua table was derived from, or guarantee decode-encode +-- round-trip equivalency. +-- +-- However, if you enable strictTypes, e.g. +-- +-- JSON = assert(loadfile "JSON.lua")() --load the routines +-- JSON.strictTypes = true +-- +-- then the Lua table resulting from the decoding of a JSON object or +-- JSON array is marked via Lua metatable, so that when re-encoded with +-- JSON:encode() it ends up as the appropriate JSON type. +-- +-- (This is not the default because other routines may not work well with +-- tables that have a metatable set, for example, Lightroom API calls.) +-- +-- +-- ENCODING (from a lua table to a JSON string) +-- +-- JSON = assert(loadfile "JSON.lua")() -- one-time load of the routines +-- +-- local raw_json_text = JSON:encode(lua_table_or_value) +-- local pretty_json_text = JSON:encode_pretty(lua_table_or_value) -- "pretty printed" version for human readability +-- local custom_pretty = JSON:encode(lua_table_or_value, etc, { pretty = true, indent = "| ", align_keys = false }) +-- +-- On error during encoding, this code calls: +-- +-- JSON:onEncodeError(message, etc) +-- +-- which you can override in your local JSON object. +-- +-- The 'etc' in the error call is the second argument to encode() +-- and encode_pretty(), or nil if it wasn't provided. +-- +-- +-- PRETTY-PRINTING +-- +-- An optional third argument, a table of options, allows a bit of +-- configuration about how the encoding takes place: +-- +-- pretty = JSON:encode(val, etc, { +-- pretty = true, -- if false, no other options matter +-- indent = " ", -- this provides for a three-space indent per nesting level +-- align_keys = false, -- see below +-- }) +-- +-- encode() and encode_pretty() are identical except that encode_pretty() +-- provides a default options table if none given in the call: +-- +-- { pretty = true, align_keys = false, indent = " " } +-- +-- For example, if +-- +-- JSON:encode(data) +-- +-- produces: +-- +-- {"city":"Kyoto","climate":{"avg_temp":16,"humidity":"high","snowfall":"minimal"},"country":"Japan","wards":11} +-- +-- then +-- +-- JSON:encode_pretty(data) +-- +-- produces: +-- +-- { +-- "city": "Kyoto", +-- "climate": { +-- "avg_temp": 16, +-- "humidity": "high", +-- "snowfall": "minimal" +-- }, +-- "country": "Japan", +-- "wards": 11 +-- } +-- +-- The following three lines return identical results: +-- JSON:encode_pretty(data) +-- JSON:encode_pretty(data, nil, { pretty = true, align_keys = false, indent = " " }) +-- JSON:encode (data, nil, { pretty = true, align_keys = false, indent = " " }) +-- +-- An example of setting your own indent string: +-- +-- JSON:encode_pretty(data, nil, { pretty = true, indent = "| " }) +-- +-- produces: +-- +-- { +-- | "city": "Kyoto", +-- | "climate": { +-- | | "avg_temp": 16, +-- | | "humidity": "high", +-- | | "snowfall": "minimal" +-- | }, +-- | "country": "Japan", +-- | "wards": 11 +-- } +-- +-- An example of setting align_keys to true: +-- +-- JSON:encode_pretty(data, nil, { pretty = true, indent = " ", align_keys = true }) +-- +-- produces: +-- +-- { +-- "city": "Kyoto", +-- "climate": { +-- "avg_temp": 16, +-- "humidity": "high", +-- "snowfall": "minimal" +-- }, +-- "country": "Japan", +-- "wards": 11 +-- } +-- +-- which I must admit is kinda ugly, sorry. This was the default for +-- encode_pretty() prior to version 20141223.14. +-- +-- +-- AMBIGUOUS SITUATIONS DURING THE ENCODING +-- +-- During the encode, if a Lua table being encoded contains both string +-- and numeric keys, it fits neither JSON's idea of an object, nor its +-- idea of an array. To get around this, when any string key exists (or +-- when non-positive numeric keys exist), numeric keys are converted to +-- strings. +-- +-- For example, +-- JSON:encode({ "one", "two", "three", SOMESTRING = "some string" })) +-- produces the JSON object +-- {"1":"one","2":"two","3":"three","SOMESTRING":"some string"} +-- +-- To prohibit this conversion and instead make it an error condition, set +-- JSON.noKeyConversion = true +-- + + + + +-- +-- SUMMARY OF METHODS YOU CAN OVERRIDE IN YOUR LOCAL LUA JSON OBJECT +-- +-- assert +-- onDecodeError +-- onDecodeOfNilError +-- onDecodeOfHTMLError +-- onEncodeError +-- +-- If you want to create a separate Lua JSON object with its own error handlers, +-- you can reload JSON.lua or use the :new() method. +-- +--------------------------------------------------------------------------- + +local default_pretty_indent = " " +local default_pretty_options = { pretty = true, align_keys = false, indent = default_pretty_indent } + +local isArray = { __tostring = function() return "JSON array" end } isArray.__index = isArray +local isObject = { __tostring = function() return "JSON object" end } isObject.__index = isObject + + +function OBJDEF:newArray(tbl) + return setmetatable(tbl or {}, isArray) +end + +function OBJDEF:newObject(tbl) + return setmetatable(tbl or {}, isObject) +end + +local function unicode_codepoint_as_utf8(codepoint) + -- + -- codepoint is a number + -- + if codepoint <= 127 then + return string.char(codepoint) + + elseif codepoint <= 2047 then + -- + -- 110yyyxx 10xxxxxx <-- useful notation from http://en.wikipedia.org/wiki/Utf8 + -- + local highpart = math.floor(codepoint / 0x40) + local lowpart = codepoint - (0x40 * highpart) + return string.char(0xC0 + highpart, + 0x80 + lowpart) + + elseif codepoint <= 65535 then + -- + -- 1110yyyy 10yyyyxx 10xxxxxx + -- + local highpart = math.floor(codepoint / 0x1000) + local remainder = codepoint - 0x1000 * highpart + local midpart = math.floor(remainder / 0x40) + local lowpart = remainder - 0x40 * midpart + + highpart = 0xE0 + highpart + midpart = 0x80 + midpart + lowpart = 0x80 + lowpart + + -- + -- Check for an invalid character (thanks Andy R. at Adobe). + -- See table 3.7, page 93, in http://www.unicode.org/versions/Unicode5.2.0/ch03.pdf#G28070 + -- + if ( highpart == 0xE0 and midpart < 0xA0 ) or + ( highpart == 0xED and midpart > 0x9F ) or + ( highpart == 0xF0 and midpart < 0x90 ) or + ( highpart == 0xF4 and midpart > 0x8F ) + then + return "?" + else + return string.char(highpart, + midpart, + lowpart) + end + + else + -- + -- 11110zzz 10zzyyyy 10yyyyxx 10xxxxxx + -- + local highpart = math.floor(codepoint / 0x40000) + local remainder = codepoint - 0x40000 * highpart + local midA = math.floor(remainder / 0x1000) + remainder = remainder - 0x1000 * midA + local midB = math.floor(remainder / 0x40) + local lowpart = remainder - 0x40 * midB + + return string.char(0xF0 + highpart, + 0x80 + midA, + 0x80 + midB, + 0x80 + lowpart) + end +end + +function OBJDEF:onDecodeError(message, text, location, etc) + if text then + if location then + message = string.format("%s at char %d of: %s", message, location, text) + else + message = string.format("%s: %s", message, text) + end + end + + if etc ~= nil then + message = message .. " (" .. OBJDEF:encode(etc) .. ")" + end + + if self.assert then + self.assert(false, message) + else + assert(false, message) + end +end + +OBJDEF.onDecodeOfNilError = OBJDEF.onDecodeError +OBJDEF.onDecodeOfHTMLError = OBJDEF.onDecodeError + +function OBJDEF:onEncodeError(message, etc) + if etc ~= nil then + message = message .. " (" .. OBJDEF:encode(etc) .. ")" + end + + if self.assert then + self.assert(false, message) + else + assert(false, message) + end +end + +local function grok_number(self, text, start, etc) + -- + -- Grab the integer part + -- + local integer_part = text:match('^-?[1-9]%d*', start) + or text:match("^-?0", start) + + if not integer_part then + self:onDecodeError("expected number", text, start, etc) + end + + local i = start + integer_part:len() + + -- + -- Grab an optional decimal part + -- + local decimal_part = text:match('^%.%d+', i) or "" + + i = i + decimal_part:len() + + -- + -- Grab an optional exponential part + -- + local exponent_part = text:match('^[eE][-+]?%d+', i) or "" + + i = i + exponent_part:len() + + local full_number_text = integer_part .. decimal_part .. exponent_part + local as_number = tonumber(full_number_text) + + if not as_number then + self:onDecodeError("bad number", text, start, etc) + end + + return as_number, i +end + + +local function grok_string(self, text, start, etc) + + if text:sub(start,start) ~= '"' then + self:onDecodeError("expected string's opening quote", text, start, etc) + end + + local i = start + 1 -- +1 to bypass the initial quote + local text_len = text:len() + local VALUE = "" + while i <= text_len do + local c = text:sub(i,i) + if c == '"' then + return VALUE, i + 1 + end + if c ~= '\\' then + VALUE = VALUE .. c + i = i + 1 + elseif text:match('^\\b', i) then + VALUE = VALUE .. "\b" + i = i + 2 + elseif text:match('^\\f', i) then + VALUE = VALUE .. "\f" + i = i + 2 + elseif text:match('^\\n', i) then + VALUE = VALUE .. "\n" + i = i + 2 + elseif text:match('^\\r', i) then + VALUE = VALUE .. "\r" + i = i + 2 + elseif text:match('^\\t', i) then + VALUE = VALUE .. "\t" + i = i + 2 + else + local hex = text:match('^\\u([0123456789aAbBcCdDeEfF][0123456789aAbBcCdDeEfF][0123456789aAbBcCdDeEfF][0123456789aAbBcCdDeEfF])', i) + if hex then + i = i + 6 -- bypass what we just read + + -- We have a Unicode codepoint. It could be standalone, or if in the proper range and + -- followed by another in a specific range, it'll be a two-code surrogate pair. + local codepoint = tonumber(hex, 16) + if codepoint >= 0xD800 and codepoint <= 0xDBFF then + -- it's a hi surrogate... see whether we have a following low + local lo_surrogate = text:match('^\\u([dD][cdefCDEF][0123456789aAbBcCdDeEfF][0123456789aAbBcCdDeEfF])', i) + if lo_surrogate then + i = i + 6 -- bypass the low surrogate we just read + codepoint = 0x2400 + (codepoint - 0xD800) * 0x400 + tonumber(lo_surrogate, 16) + else + -- not a proper low, so we'll just leave the first codepoint as is and spit it out. + end + end + VALUE = VALUE .. unicode_codepoint_as_utf8(codepoint) + + else + + -- just pass through what's escaped + VALUE = VALUE .. text:match('^\\(.)', i) + i = i + 2 + end + end + end + + self:onDecodeError("unclosed string", text, start, etc) +end + +local function skip_whitespace(text, start) + + local _, match_end = text:find("^[ \n\r\t]+", start) -- [http://www.ietf.org/rfc/rfc4627.txt] Section 2 + if match_end then + return match_end + 1 + else + return start + end +end + +local grok_one -- assigned later + +local function grok_object(self, text, start, etc) + if text:sub(start,start) ~= '{' then + self:onDecodeError("expected '{'", text, start, etc) + end + + local i = skip_whitespace(text, start + 1) -- +1 to skip the '{' + + local VALUE = self.strictTypes and self:newObject { } or { } + + if text:sub(i,i) == '}' then + return VALUE, i + 1 + end + local text_len = text:len() + while i <= text_len do + local key, new_i = grok_string(self, text, i, etc) + + i = skip_whitespace(text, new_i) + + if text:sub(i, i) ~= ':' then + self:onDecodeError("expected colon", text, i, etc) + end + + i = skip_whitespace(text, i + 1) + + local new_val, new_i = grok_one(self, text, i) + + VALUE[key] = new_val + + -- + -- Expect now either '}' to end things, or a ',' to allow us to continue. + -- + i = skip_whitespace(text, new_i) + + local c = text:sub(i,i) + + if c == '}' then + return VALUE, i + 1 + end + + if text:sub(i, i) ~= ',' then + self:onDecodeError("expected comma or '}'", text, i, etc) + end + + i = skip_whitespace(text, i + 1) + end + + self:onDecodeError("unclosed '{'", text, start, etc) +end + +local function grok_array(self, text, start, etc) + if text:sub(start,start) ~= '[' then + self:onDecodeError("expected '['", text, start, etc) + end + + local i = skip_whitespace(text, start + 1) -- +1 to skip the '[' + local VALUE = self.strictTypes and self:newArray { } or { } + if text:sub(i,i) == ']' then + return VALUE, i + 1 + end + + local VALUE_INDEX = 1 + + local text_len = text:len() + while i <= text_len do + local val, new_i = grok_one(self, text, i) + + -- can't table.insert(VALUE, val) here because it's a no-op if val is nil + VALUE[VALUE_INDEX] = val + VALUE_INDEX = VALUE_INDEX + 1 + + i = skip_whitespace(text, new_i) + + -- + -- Expect now either ']' to end things, or a ',' to allow us to continue. + -- + local c = text:sub(i,i) + if c == ']' then + return VALUE, i + 1 + end + if text:sub(i, i) ~= ',' then + self:onDecodeError("expected comma or '['", text, i, etc) + end + i = skip_whitespace(text, i + 1) + end + self:onDecodeError("unclosed '['", text, start, etc) +end + + +grok_one = function(self, text, start, etc) + -- Skip any whitespace + start = skip_whitespace(text, start) + + if start > text:len() then + self:onDecodeError("unexpected end of string", text, nil, etc) + end + + if text:find('^"', start) then + return grok_string(self, text, start, etc) + + elseif text:find('^[-0123456789 ]', start) then + return grok_number(self, text, start, etc) + + elseif text:find('^%{', start) then + return grok_object(self, text, start, etc) + + elseif text:find('^%[', start) then + return grok_array(self, text, start, etc) + + elseif text:find('^true', start) then + return true, start + 4 + + elseif text:find('^false', start) then + return false, start + 5 + + elseif text:find('^null', start) then + return nil, start + 4 + + else + self:onDecodeError("can't parse JSON", text, start, etc) + end +end + +function OBJDEF:decode(text, etc) + if type(self) ~= 'table' or self.__index ~= OBJDEF then + OBJDEF:onDecodeError("JSON:decode must be called in method format", nil, nil, etc) + end + + if text == nil then + self:onDecodeOfNilError(string.format("nil passed to JSON:decode()"), nil, nil, etc) + elseif type(text) ~= 'string' then + self:onDecodeError(string.format("expected string argument to JSON:decode(), got %s", type(text)), nil, nil, etc) + end + + if text:match('^%s*$') then + return nil + end + + if text:match('^%s*<') then + -- Can't be JSON... we'll assume it's HTML + self:onDecodeOfHTMLError(string.format("html passed to JSON:decode()"), text, nil, etc) + end + + -- + -- Ensure that it's not UTF-32 or UTF-16. + -- Those are perfectly valid encodings for JSON (as per RFC 4627 section 3), + -- but this package can't handle them. + -- + if text:sub(1,1):byte() == 0 or (text:len() >= 2 and text:sub(2,2):byte() == 0) then + self:onDecodeError("JSON package groks only UTF-8, sorry", text, nil, etc) + end + + local success, value = pcall(grok_one, self, text, 1, etc) + + if success then + return value + else + -- if JSON:onDecodeError() didn't abort out of the pcall, we'll have received the error message here as "value", so pass it along as an assert. + if self.assert then + self.assert(false, value) + else + assert(false, value) + end + -- and if we're still here, return a nil and throw the error message on as a second arg + return nil, value + end +end + +local function backslash_replacement_function(c) + if c == "\n" then + return "\\n" + elseif c == "\r" then + return "\\r" + elseif c == "\t" then + return "\\t" + elseif c == "\b" then + return "\\b" + elseif c == "\f" then + return "\\f" + elseif c == '"' then + return '\\"' + elseif c == '\\' then + return '\\\\' + else + return string.format("\\u%04x", c:byte()) + end +end + +local chars_to_be_escaped_in_JSON_string + = '[' + .. '"' -- class sub-pattern to match a double quote + .. '%\\' -- class sub-pattern to match a backslash + .. '%z' -- class sub-pattern to match a null + .. '\001' .. '-' .. '\031' -- class sub-pattern to match control characters + .. ']' + +local function json_string_literal(value) + local newval = value:gsub(chars_to_be_escaped_in_JSON_string, backslash_replacement_function) + return '"' .. newval .. '"' +end + +local function object_or_array(self, T, etc) + -- + -- We need to inspect all the keys... if there are any strings, we'll convert to a JSON + -- object. If there are only numbers, it's a JSON array. + -- + -- If we'll be converting to a JSON object, we'll want to sort the keys so that the + -- end result is deterministic. + -- + local string_keys = { } + local number_keys = { } + local number_keys_must_be_strings = false + local maximum_number_key + + for key in pairs(T) do + if type(key) == 'string' then + table.insert(string_keys, key) + elseif type(key) == 'number' then + table.insert(number_keys, key) + if key <= 0 or key >= math.huge then + number_keys_must_be_strings = true + elseif not maximum_number_key or key > maximum_number_key then + maximum_number_key = key + end + else + self:onEncodeError("can't encode table with a key of type " .. type(key), etc) + end + end + + if #string_keys == 0 and not number_keys_must_be_strings then + -- + -- An empty table, or a numeric-only array + -- + if #number_keys > 0 then + return nil, maximum_number_key -- an array + elseif tostring(T) == "JSON array" then + return nil + elseif tostring(T) == "JSON object" then + return { } + else + -- have to guess, so we'll pick array, since empty arrays are likely more common than empty objects + return nil + end + end + + table.sort(string_keys) + + local map + if #number_keys > 0 then + -- + -- If we're here then we have either mixed string/number keys, or numbers inappropriate for a JSON array + -- It's not ideal, but we'll turn the numbers into strings so that we can at least create a JSON object. + -- + + if self.noKeyConversion then + self:onEncodeError("a table with both numeric and string keys could be an object or array; aborting", etc) + end + + -- + -- Have to make a shallow copy of the source table so we can remap the numeric keys to be strings + -- + map = { } + for key, val in pairs(T) do + map[key] = val + end + + table.sort(number_keys) + + -- + -- Throw numeric keys in there as strings + -- + for _, number_key in ipairs(number_keys) do + local string_key = tostring(number_key) + if map[string_key] == nil then + table.insert(string_keys , string_key) + map[string_key] = T[number_key] + else + self:onEncodeError("conflict converting table with mixed-type keys into a JSON object: key " .. number_key .. " exists both as a string and a number.", etc) + end + end + end + + return string_keys, nil, map +end + +-- +-- Encode +-- +-- 'options' is nil, or a table with possible keys: +-- pretty -- if true, return a pretty-printed version +-- indent -- a string (usually of spaces) used to indent each nested level +-- align_keys -- if true, align all the keys when formatting a table +-- +local encode_value -- must predeclare because it calls itself +function encode_value(self, value, parents, etc, options, indent) + + if value == nil then + return 'null' + + elseif type(value) == 'string' then + return json_string_literal(value) + + elseif type(value) == 'number' then + if value ~= value then + -- + -- NaN (Not a Number). + -- JSON has no NaN, so we have to fudge the best we can. This should really be a package option. + -- + return "null" + elseif value >= math.huge then + -- + -- Positive infinity. JSON has no INF, so we have to fudge the best we can. This should + -- really be a package option. Note: at least with some implementations, positive infinity + -- is both ">= math.huge" and "<= -math.huge", which makes no sense but that's how it is. + -- Negative infinity is properly "<= -math.huge". So, we must be sure to check the ">=" + -- case first. + -- + return "1e+9999" + elseif value <= -math.huge then + -- + -- Negative infinity. + -- JSON has no INF, so we have to fudge the best we can. This should really be a package option. + -- + return "-1e+9999" + else + return tostring(value) + end + + elseif type(value) == 'boolean' then + return tostring(value) + + elseif type(value) ~= 'table' then + self:onEncodeError("can't convert " .. type(value) .. " to JSON", etc) + + else + -- + -- A table to be converted to either a JSON object or array. + -- + local T = value + + if type(options) ~= 'table' then + options = {} + end + if type(indent) ~= 'string' then + indent = "" + end + + if parents[T] then + self:onEncodeError("table " .. tostring(T) .. " is a child of itself", etc) + else + parents[T] = true + end + + local result_value + + local object_keys, maximum_number_key, map = object_or_array(self, T, etc) + if maximum_number_key then + -- + -- An array... + -- + local ITEMS = { } + for i = 1, maximum_number_key do + table.insert(ITEMS, encode_value(self, T[i], parents, etc, options, indent)) + end + + if options.pretty then + result_value = "[ " .. table.concat(ITEMS, ", ") .. " ]" + else + result_value = "[" .. table.concat(ITEMS, ",") .. "]" + end + + elseif object_keys then + -- + -- An object + -- + local TT = map or T + + if options.pretty then + + local KEYS = { } + local max_key_length = 0 + for _, key in ipairs(object_keys) do + local encoded = encode_value(self, tostring(key), parents, etc, options, indent) + if options.align_keys then + max_key_length = math.max(max_key_length, #encoded) + end + table.insert(KEYS, encoded) + end + local key_indent = indent .. tostring(options.indent or "") + local subtable_indent = key_indent .. string.rep(" ", max_key_length) .. (options.align_keys and " " or "") + local FORMAT = "%s%" .. string.format("%d", max_key_length) .. "s: %s" + + local COMBINED_PARTS = { } + for i, key in ipairs(object_keys) do + local encoded_val = encode_value(self, TT[key], parents, etc, options, subtable_indent) + table.insert(COMBINED_PARTS, string.format(FORMAT, key_indent, KEYS[i], encoded_val)) + end + result_value = "{\n" .. table.concat(COMBINED_PARTS, ",\n") .. "\n" .. indent .. "}" + + else + + local PARTS = { } + for _, key in ipairs(object_keys) do + local encoded_val = encode_value(self, TT[key], parents, etc, options, indent) + local encoded_key = encode_value(self, tostring(key), parents, etc, options, indent) + table.insert(PARTS, string.format("%s:%s", encoded_key, encoded_val)) + end + result_value = "{" .. table.concat(PARTS, ",") .. "}" + + end + else + -- + -- An empty array/object... we'll treat it as an array, though it should really be an option + -- + result_value = "[]" + end + + parents[T] = false + return result_value + end +end + + +function OBJDEF:encode(value, etc, options) + if type(self) ~= 'table' or self.__index ~= OBJDEF then + OBJDEF:onEncodeError("JSON:encode must be called in method format", etc) + end + return encode_value(self, value, {}, etc, options or nil) +end + +function OBJDEF:encode_pretty(value, etc, options) + if type(self) ~= 'table' or self.__index ~= OBJDEF then + OBJDEF:onEncodeError("JSON:encode_pretty must be called in method format", etc) + end + return encode_value(self, value, {}, etc, options or default_pretty_options) +end + +function OBJDEF.__tostring() + return "JSON encode/decode package" +end + +OBJDEF.__index = OBJDEF + +function OBJDEF:new(args) + local new = { } + + if args then + for key, val in pairs(args) do + new[key] = val + end + end + + return setmetatable(new, OBJDEF) +end + +return OBJDEF:new() + +-- +-- Version history: +-- +-- 20141223.14 The encode_pretty() routine produced fine results for small datasets, but isn't really +-- appropriate for anything large, so with help from Alex Aulbach I've made the encode routines +-- more flexible, and changed the default encode_pretty() to be more generally useful. +-- +-- Added a third 'options' argument to the encode() and encode_pretty() routines, to control +-- how the encoding takes place. +-- +-- Updated docs to add assert() call to the loadfile() line, just as good practice so that +-- if there is a problem loading JSON.lua, the appropriate error message will percolate up. +-- +-- 20140920.13 Put back (in a way that doesn't cause warnings about unused variables) the author string, +-- so that the source of the package, and its version number, are visible in compiled copies. +-- +-- 20140911.12 Minor lua cleanup. +-- Fixed internal reference to 'JSON.noKeyConversion' to reference 'self' instead of 'JSON'. +-- (Thanks to SmugMug's David Parry for these.) +-- +-- 20140418.11 JSON nulls embedded within an array were being ignored, such that +-- ["1",null,null,null,null,null,"seven"], +-- would return +-- {1,"seven"} +-- It's now fixed to properly return +-- {1, nil, nil, nil, nil, nil, "seven"} +-- Thanks to "haddock" for catching the error. +-- +-- 20140116.10 The user's JSON.assert() wasn't always being used. Thanks to "blue" for the heads up. +-- +-- 20131118.9 Update for Lua 5.3... it seems that tostring(2/1) produces "2.0" instead of "2", +-- and this caused some problems. +-- +-- 20131031.8 Unified the code for encode() and encode_pretty(); they had been stupidly separate, +-- and had of course diverged (encode_pretty didn't get the fixes that encode got, so +-- sometimes produced incorrect results; thanks to Mattie for the heads up). +-- +-- Handle encoding tables with non-positive numeric keys (unlikely, but possible). +-- +-- If a table has both numeric and string keys, or its numeric keys are inappropriate +-- (such as being non-positive or infinite), the numeric keys are turned into +-- string keys appropriate for a JSON object. So, as before, +-- JSON:encode({ "one", "two", "three" }) +-- produces the array +-- ["one","two","three"] +-- but now something with mixed key types like +-- JSON:encode({ "one", "two", "three", SOMESTRING = "some string" })) +-- instead of throwing an error produces an object: +-- {"1":"one","2":"two","3":"three","SOMESTRING":"some string"} +-- +-- To maintain the prior throw-an-error semantics, set +-- JSON.noKeyConversion = true +-- +-- 20131004.7 Release under a Creative Commons CC-BY license, which I should have done from day one, sorry. +-- +-- 20130120.6 Comment update: added a link to the specific page on my blog where this code can +-- be found, so that folks who come across the code outside of my blog can find updates +-- more easily. +-- +-- 20111207.5 Added support for the 'etc' arguments, for better error reporting. +-- +-- 20110731.4 More feedback from David Kolf on how to make the tests for Nan/Infinity system independent. +-- +-- 20110730.3 Incorporated feedback from David Kolf at http://lua-users.org/wiki/JsonModules: +-- +-- * When encoding lua for JSON, Sparse numeric arrays are now handled by +-- spitting out full arrays, such that +-- JSON:encode({"one", "two", [10] = "ten"}) +-- returns +-- ["one","two",null,null,null,null,null,null,null,"ten"] +-- +-- In 20100810.2 and earlier, only up to the first non-null value would have been retained. +-- +-- * When encoding lua for JSON, numeric value NaN gets spit out as null, and infinity as "1+e9999". +-- Version 20100810.2 and earlier created invalid JSON in both cases. +-- +-- * Unicode surrogate pairs are now detected when decoding JSON. +-- +-- 20100810.2 added some checking to ensure that an invalid Unicode character couldn't leak in to the UTF-8 encoding +-- +-- 20100731.1 initial public release +-- diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/keyboard.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/keyboard.lua new file mode 100755 index 00000000..378eb02f --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/keyboard.lua @@ -0,0 +1,94 @@ +local component = require("component") + +local keyboard = {pressedChars = {}, pressedCodes = {}} + +-- these key definitions are only a subset of all the defined keys +-- __index loads all key data from /lib/tools/keyboard_full.lua (only once) +-- new key metadata should be added here if required for boot +keyboard.keys = { + c = 0x2E, + d = 0x20, + q = 0x10, + back = 0x0E, -- backspace + delete = 0xD3, + down = 0xD0, + enter = 0x1C, + home = 0xC7, + lcontrol = 0x1D, + left = 0xCB, + lmenu = 0x38, -- left Alt + lshift = 0x2A, + pageDown = 0xD1, + rcontrol = 0x9D, + right = 0xCD, + rmenu = 0xB8, -- right Alt + rshift = 0x36, + space = 0x39, + tab = 0x0F, + up = 0xC8, + ["end"] = 0xCF, +} + +-- Create inverse mapping for name lookup. +setmetatable(keyboard.keys, +{ + __index = function(tbl, k) + getmetatable(keyboard.keys).__index = nil -- to be safe + loadfile(package.searchpath("tools/keyboard_full", package.path), "t", setmetatable({keyboard=keyboard},{__index=_G}))() + return tbl[k] + end +}) + +------------------------------------------------------------------------------- + +local function getKeyboardAddress(address) + return address or require("term").keyboard() +end + +local function getPressedCodes(address) + address = getKeyboardAddress(address) + return address and keyboard.pressedCodes[address] or false +end + +local function getPressedChars(address) + address = getKeyboardAddress(address) + return address and keyboard.pressedChars[address] or false +end + +function keyboard.isAltDown(address) + checkArg(1, address, "string", "nil") + local pressedCodes = getPressedCodes(address) + return pressedCodes and (pressedCodes[keyboard.keys.lmenu] or pressedCodes[keyboard.keys.rmenu]) ~= nil +end + +function keyboard.isControl(char) + return type(char) == "number" and (char < 0x20 or (char >= 0x7F and char <= 0x9F)) +end + +function keyboard.isControlDown(address) + checkArg(1, address, "string", "nil") + local pressedCodes = getPressedCodes(address) + return pressedCodes and (pressedCodes[keyboard.keys.lcontrol] or pressedCodes[keyboard.keys.rcontrol]) ~= nil +end + +function keyboard.isKeyDown(charOrCode, address) + checkArg(1, charOrCode, "string", "number") + checkArg(2, address, "string", "nil") + if type(charOrCode) == "string" then + local pressedChars = getPressedChars(address) + return pressedChars and pressedChars[utf8 and utf8.codepoint(charOrCode) or charOrCode:byte()] + elseif type(charOrCode) == "number" then + local pressedCodes = getPressedCodes(address) + return pressedCodes and pressedCodes[charOrCode] + end +end + +function keyboard.isShiftDown(address) + checkArg(1, address, "string", "nil") + local pressedCodes = getPressedCodes(address) + return pressedCodes and (pressedCodes[keyboard.keys.lshift] or pressedCodes[keyboard.keys.rshift]) ~= nil +end + +------------------------------------------------------------------------------- + +return keyboard diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/note.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/note.lua new file mode 100755 index 00000000..67b7c870 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/note.lua @@ -0,0 +1,126 @@ +--Provides all music notes in range of computer.beep in MIDI and frequency form +--Author: Vexatos +local computer = require("computer") + +local note = {} +--The table that maps note names to their respective MIDI codes +local notes = {} +--The reversed table "notes" +local reverseNotes = {} + +do + --All the base notes + local tempNotes = { + "c", + "c#", + "d", + "d#", + "e", + "f", + "f#", + "g", + "g#", + "a", + "a#", + "b" + } + --The table containing all the standard notes and # semitones in correct order, temporarily + local sNotes = {} + --The table containing all the b semitones + local bNotes = {} + + --Registers all possible notes in order + do + table.insert(sNotes,"a0") + table.insert(sNotes,"a#0") + table.insert(bNotes,"bb0") + table.insert(sNotes,"b0") + for i = 1,6 do + for _,v in ipairs(tempNotes) do + table.insert(sNotes,v..tostring(i)) + if #v == 1 and v ~= "c" and v ~= "f" then + table.insert(bNotes,v.."b"..tostring(i)) + end + end + end + end + for i=21,95 do + notes[sNotes[i-20]]=tostring(i) + end + + --Reversing the whole table in reverseNotes, used for note.get + do + for k,v in pairs(notes) do + reverseNotes[tonumber(v)]=k + end + end + + --This is registered after reverseNotes to avoid conflicts + for k,v in ipairs(bNotes) do + notes[v]=tostring(notes[string.gsub(v,"(.)b(.)","%1%2")]-1) + end +end + +--Converts string or frequency into MIDI code +function note.midi(n) + if type(n) == "string" then + n = string.lower(n) + if tonumber(notes[n])~=nil then + return tonumber(notes[n]) + else + error("Wrong input "..tostring(n).." given to note.midi, needs to be [semitone sign], e.g. A#0 or Gb4") + end + elseif type(n) == "number" then + return math.floor((12*math.log(n/440,2))+69) + else + error("Wrong input "..tostring(n).." given to note.midi, needs to be a number or a string") + end +end + +--Converts String or MIDI code into frequency +function note.freq(n) + if type(n) == "string" then + n = string.lower(n) + if tonumber(notes[n])~=nil then + return math.pow(2,(tonumber(notes[n])-69)/12)*440 + else + error("Wrong input "..tostring(n).." given to note.freq, needs to be [semitone sign], e.g. A#0 or Gb4",2) + end + elseif type(n) == "number" then + return math.pow(2,(n-69)/12)*440 + else + error("Wrong input "..tostring(n).." given to note.freq, needs to be a number or a string",2) + end +end + +--Converts a MIDI value back into a string +function note.name(n) + n = tonumber(n) + if reverseNotes[n] then + return string.upper(string.match(reverseNotes[n],"^(.)"))..string.gsub(reverseNotes[n],"^.(.*)","%1") + else + error("Attempt to get a note for a non-exsisting MIDI code",2) + end +end + +--Converts Note block ticks (0-24) to MIDI code (34-58) and vice-versa +function note.ticks(n) + if type(n) == "number" then + if n>=0 and n<=24 then + return n+34 + elseif n>=34 and n<=58 then + return n-34 + else + error("Wrong input "..tostring(n).." given to note.ticks, needs to be a number [0-24 or 34-58]",2) + end + else + error("Wrong input "..tostring(n).." given to note.ticks, needs to be a number",2) + end +end + +--Plays a tone, input is either the note as a string or the MIDI code as well as the duration of the tone +function note.play(tone,duration) + computer.beep(note.freq(tone),duration) +end + +return note diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/package.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/package.lua new file mode 100755 index 00000000..d753917e --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/package.lua @@ -0,0 +1,146 @@ +local package = {} + +package.path = "/lib/?.lua;/usr/lib/?.lua;/home/lib/?.lua;./?.lua;/lib/?/init.lua;/usr/lib/?/init.lua;/home/lib/?/init.lua;./?/init.lua" + +local loading = {} + +local loaded = { + ["_G"] = _G, + ["bit32"] = bit32, + ["coroutine"] = coroutine, + ["math"] = math, + ["os"] = os, + ["package"] = package, + ["string"] = string, + ["table"] = table +} +package.loaded = loaded + +local preload = {} +package.preload = preload + +local delayed = {} +package.delayed = delayed + +package.searchers = {} + +function package.searchpath(name, path, sep, rep) + checkArg(1, name, "string") + checkArg(2, path, "string") + sep = sep or '.' + rep = rep or '/' + sep, rep = '%' .. sep, rep + name = string.gsub(name, sep, rep) + local fs = require("filesystem") + local errorFiles = {} + for subPath in string.gmatch(path, "([^;]+)") do + subPath = string.gsub(subPath, "?", name) + if subPath:sub(1, 1) ~= "/" and os.getenv then + subPath = fs.concat(os.getenv("PWD") or "/", subPath) + end + if fs.exists(subPath) then + local file = fs.open(subPath, "r") + if file then + file:close() + return subPath + end + end + table.insert(errorFiles, "\tno file '" .. subPath .. "'") + end + return nil, table.concat(errorFiles, "\n") +end + +local function preloadSearcher(module) + if preload[module] ~= nil then + return preload[module] + else + return "\tno field package.preload['" .. module .. "']" + end +end + +local delay_data = {} +local delay_tools = setmetatable({},{__mode="v"}) + +function delay_data.__index(tbl,key) + local lookup = delay_data.lookup or loadfile(package.searchpath("tools/delayLookup", package.path), "bt", _G) + delay_data.lookup = lookup + return lookup(delay_data, tbl, key) +end +delay_data.__pairs = delay_data.__index -- nil key acts like pairs + +local function delaySearcher(module) + if not delayed[module] then + return "\tno field package.delayed['" .. module .. "']" + end + local filepath, reason = package.searchpath(module, package.path) + if not filepath then + return reason + end + local parser = delay_tools.parser or loadfile(package.searchpath("tools/delayParse", package.path), "bt", _G) + delay_tools.parser = parser + local loader, reason = parser(filepath,delay_data) + return loader, reason +end + +local function pathSearcher(module) + local filepath, reason = package.searchpath(module, package.path) + if filepath then + local loader, reason = loadfile(filepath, "bt", _G) + if loader then + return loader, filepath + else + return reason + end + else + return reason + end +end + +table.insert(package.searchers, preloadSearcher) +table.insert(package.searchers, delaySearcher) +table.insert(package.searchers, pathSearcher) + +function require(module) + checkArg(1, module, "string") + if loaded[module] ~= nil then + return loaded[module] + elseif not loading[module] then + loading[module] = true + local loader, value, errorMsg = nil, nil, {"module '" .. module .. "' not found:"} + for i = 1, #package.searchers do + -- the pcall is mostly for out of memory errors + local ok, f, extra = pcall(package.searchers[i], module) + if not ok then + table.insert(errorMsg, "\t" .. (f or "nil")) + elseif f and type(f) ~= "string" then + loader = f + value = extra + break + elseif f then + table.insert(errorMsg, f) + end + end + if loader then + local success, result = pcall(loader, module, value) + loading[module] = false + if not success then + error(result, 2) + end + if result then + loaded[module] = result + elseif not loaded[module] then + loaded[module] = true + end + return loaded[module] + else + loading[module] = false + error(table.concat(errorMsg, "\n"), 2) + end + else + error("already loading: " .. module .. "\n" .. debug.traceback(), 2) + end +end + +------------------------------------------------------------------------------- + +return package diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/palette.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/palette.lua new file mode 100755 index 00000000..f5e35a5e --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/palette.lua @@ -0,0 +1,3 @@ + + +error("/lib/palette.lua is no longer supported. User GUI.palette instead") \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/pipes.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/pipes.lua new file mode 100755 index 00000000..3a54bf67 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/pipes.lua @@ -0,0 +1,375 @@ +local tx = require("transforms") +local shell = require("shell") +local sh = require("sh") +local process = require("process") + +local plib = {} + +plib.internal = {} + +local pipeStream = {} +local function bfd() return nil, "bad file descriptor" end +function pipeStream.new(pm) + local stream = {pm=pm} + local metatable = {__index = pipeStream} + return setmetatable(stream, metatable) +end +function pipeStream:resume() + local yield_args = table.pack(self.pm.pco.resume_all()) + if not yield_args[1] then + self.pm.dead = true + + if not yield_args[1] and yield_args[2] then + io.stderr:write(tostring(yield_args[2]) .. "\n") + end + end + return table.unpack(yield_args) +end +function pipeStream:close() + if self.pm.closed then -- already closed + return + end + + self.pm.closed = true + + -- if our pco stack is empty, we've already run fully + if self.pm.pco.top() == nil then + return + end + + -- if a thread aborted we have set dead true + if self.pm.dead then + return + end + + -- run prog until dead + local co = self.pm.pco.previous_handler + local pco_root = self.pm.threads[1] + if co.status(pco_root) == "dead" then + -- I would have liked the pco stack to unwind itself for dead coroutines + -- maybe I haven't handled aborts corrects + return + end + + return self:resume(true) +end +function pipeStream:read(n) + local pm = self.pm + + if pm.closed then + return bfd() + end + + if pm:buffer() == '' and not pm.dead then + local result = table.pack(self:resume()) + if not result[1] then + -- resume can fail if p1 crashes + self:close() + return nil, "pipe closed unexpectedly" + elseif result.n > 1 and not result[2] then + return result[2], result[3] + end + end + + local result = pm:buffer(n) + if result == '' and pm.dead and n > 0 then + return nil -- eof + end + + return result +end +function pipeStream:seek(whence, offset) + return bfd() +end +function pipeStream:write(v) + local pm = self.pm + if pm.closed or pm.dead then + -- if prog is dead, ignore all writes + if pm.pco.previous_handler.status(pm.threads[pm.self_id]) ~= "dead" then + error("attempt to use a closed stream") + end + return bfd() + end + + pm:buffer(pm:buffer() .. v) + + -- allow handler to push write event + local result = table.pack(self:resume()) + if not result[1] then + -- resume can fail if p1 crashes + pm.dead = true + self:close() + return nil, "pipe closed unexpectedly" + end + + return self +end + +function plib.internal.redirectRead(pm) + local reader = {pm=pm} + function reader:read(n) + local pm = self.pm + local pco = pm.pco + -- if we have any buffer, return it first + + if pm:buffer() == '' and not pm.closed and not pm.dead then + pco.yield_all() + end + + if pm.closed or pm.dead then + return nil + end + + return pm:buffer(n) + end + + return reader +end + +function plib.internal.create(fp, init, name) + local _co = process.info().data.coroutine_handler + + local pco = setmetatable( + { + stack = {}, + next = false, + create = _co.create, + wrap = _co.wrap, + previous_handler = _co + }, {__index=_co}) + + function pco.top() + return pco.stack[#pco.stack] + end + function pco.yield(...) + -- pop last + pco.set_unwind(pco.running()) + return _co.yield(...) + end + function pco.index_of(thread) + for i,t in ipairs(pco.stack) do + if t == thread then + return i + end + end + end + function pco.yield_all(...) + local current = pco.running() + local existing_index = pco.index_of(current) + assert(existing_index, "cannot yield inactive stack") + pco.next = current + return _co.yield(...) + end + function pco.set_unwind(from) + pco.next = false + if from then + local index = pco.index_of(from) + if index then + pco.stack = tx.sub(pco.stack, 1, index-1) + pco.next = pco.stack[index-1] or false + end + end + end + function pco.resume_all(...) + local base = pco.stack[1] + local top = pco.top() + if type(base) ~= "thread" or _co.status(base) ~= "suspended" or + type(top) ~= "thread" or _co.status(top) ~= "suspended" then + return false + end + + local status, result = pcall(function(...) + local _result = table.pack(pco.resume(top, ...)) + return _result + end,...) + + if not status then + return nil, result + end + + return table.unpack(result) + end + function pco.resume(thread, ...) + checkArg(1, thread, "thread") + local status = pco.status(thread) + if status ~= "suspended" then + local msg = string.format("cannot resume %s coroutine", + status == "dead" and "dead" or "non-suspended") + return false, msg + end + + local current_index = pco.index_of(pco.running()) + local existing_index = pco.index_of(thread) + + if not existing_index then + assert(current_index, "pco coroutines cannot resume threads outside the stack") + pco.stack = tx.concat(tx.sub(pco.stack, 1, current_index), {thread}) + end + + if current_index then + -- current should be waiting for yield + pco.next = thread + return true, _co.yield(...) -- pass args to resume next + else + -- the stack is not running + pco.next = false + local yield_args = table.pack(_co.resume(thread, ...)) + if #pco.stack > 0 then + -- thread may have crashed (crash unwinds as well) + -- or we don't have next lined up (unwind) + if not pco.next or not yield_args[1] then + -- unwind from current index, not top + pco.set_unwind(thread) + end + + -- if next is current thread, yield_all is active + -- in such a case, yield out first, then resume where we left off + if pco.next and pco.next ~= thread then + local next = pco.next + pco.next = false + return pco.resume(next, table.unpack(yield_args,2,yield_args.n)) + end + end + + return table.unpack(yield_args) + end + end + function pco.status(thread) + checkArg(1, thread, "thread") + + local current_index = pco.index_of(pco.running()) + local existing_index = pco.index_of(thread) + + if current_index and existing_index and existing_index < current_index then + local current = pco.stack[current_index] + if current and _co.status(current) == "running" then + return "normal" + end + end + + return _co.status(thread) + end + + if fp then + pco.stack = {process.load(fp,nil,init,name or "pco root")} + process.info(pco.stack[1]).data.coroutine_handler = pco + end + + return pco +end + +local pipeManager = {} +function pipeManager.reader(pm,...) + while pm.pco.status(pm.threads[pm.prog_id]) ~= "dead" do + pm.pco.yield_all() + + -- kick back to main thread, true to kick back one further + if pm.closed then break end + + -- if we are a reader pipe, we leave the buffer alone and yield to previous + if pm.pco.status(pm.threads[pm.prog_id]) ~= "dead" then + pm.pco.yield() + end + end + pm.dead = true +end + +function pipeManager:buffer(value) + -- if value but no stream, buffer for buffer + + local s = self and self.pipe and self.pipe.stream + if not s then + if type(value) == "string" or self.prewrite then + self.prewrite = self.prewrite or {} + s = self.prewrite -- s.buffer will be self.prewrite.buffer + else + return '' + end + elseif self.prewrite then -- we stored, previously, a prewrite buffer + s.buffer = self.prewrite.buffer .. s.buffer + self.prewrite = nil + end + + if type(value) == "string" then + s.buffer = value + return value + elseif type(value) ~= "number" then + return s.buffer -- don't truncate + end + + local result = string.sub(s.buffer, 1, value) + s.buffer = string.sub(s.buffer, value + 1) + return result +end + +function pipeManager.new(prog, mode, env) + mode = mode or "r" + if mode ~= "r" and mode ~= "w" then + return nil, "bad argument #2: invalid mode " .. tostring(mode) .. " must be r or w" + end + + local shellPath = os.getenv("SHELL") or "/bin/sh" + local shellPath, reason = shell.resolve(shellPath, "lua") + if not shellPath then + return nil, reason + end + + local pm = setmetatable( + {dead=false,closed=false,prog=prog,mode=mode,env=env}, + {__index=pipeManager} + ) + pm.prog_id = pm.mode == "r" and 1 or 2 + pm.self_id = pm.mode == "r" and 2 or 1 + pm.handler = pm.mode == "r" and + function()return pipeManager.reader(pm)end or + function()pm.dead=true end + + pm.commands = {} + pm.commands[pm.prog_id] = {shellPath, {}} + pm.commands[pm.self_id] = {pm.handler, {}} + + pm.root = function() + local reason + pm.threads, reason = sh.internal.createThreads(pm.commands, pm.env, {[pm.prog_id]=table.pack(pm.env,pm.prog)}) + + if not pm.threads then + pm.dead = true + return false, reason + end + + pm.pipe = process.info(pm.threads[1]).data.io[1] + + -- if we are the writer, we need args to resume prog + if pm.mode == "w" then + pm.pipe.stream.redirect[0] = plib.internal.redirectRead(pm) + end + + return sh.internal.runThreads(pm.threads) + end + + return pm +end + +function plib.popen(prog, mode, env) + checkArg(1, prog, "string") + checkArg(2, mode, "string", "nil") + checkArg(3, env, "table", "nil") + + local pm, reason = pipeManager.new(prog, mode, env) + + if not pm then + return false, reason + end + + pm.pco=plib.internal.create(pm.root) + + local pfd = require("buffer").new(mode, pipeStream.new(pm)) + pfd:setvbuf("no", 0) -- 2nd are to read chunk size + + -- popen processes start on create (which is LAME :P) + pfd.stream:resume() + + return pfd +end + +return plib diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/process.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/process.lua new file mode 100755 index 00000000..b2f1bbba --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/process.lua @@ -0,0 +1,161 @@ +local process = {} + +------------------------------------------------------------------------------- + +--Initialize coroutine library-- +process.list = setmetatable({}, {__mode="k"}) + +function process.findProcess(co) + co = co or coroutine.running() + for main, p in pairs(process.list) do + if main == co then + return p + end + for _, instance in pairs(p.instances) do + if instance == co then + return p + end + end + end +end + +------------------------------------------------------------------------------- +local function parent_data(pre, tbl, k, ...) + if tbl and k then + return parent_data(pre, tbl[k], ...) + end + return setmetatable(pre, {__index=tbl}) +end + +function process.load(path, env, init, name) + checkArg(1, path, "string", "function") + checkArg(2, env, "table", "nil") + checkArg(3, init, "function", "nil") + checkArg(4, name, "string", "nil") + + assert(type(path) == "string" or env == nil, "process cannot load function environemnts") + name = name or "" + + local p = process.findProcess() + if p then + env = env or p.env + end + env = setmetatable({}, {__index=env or _G}) + + local code = nil + if type(path) == 'string' then + code = function(...) + local fs, shell = require("filesystem"), require("shell") + local program, reason = shell.resolve(path, 'lua') + if not program then + if fs.isDirectory(shell.resolve(path)) then + io.stderr:write(path .. ": is a directory\n") + return 126 + end + local handler = require("tools/programLocations") + handler.reportNotFound(path, reason) + return 127 + end + os.setenv("_", program) + local f, reason = fs.open(program) + local shebang = f and f:read(1024):match("^#!([^\n]+)") + f:close() + if shabang then + local result = table.pack(shell.execute(shebang:gsub("%s",""), env, program, ...)) + assert(result[1], result[2]) + return table.unpack(result) + end + local command, reason = loadfile(program, "bt", env) + if not command then + io.stderr:write(program..(reason or ""):gsub("^[^:]*", "").."\n") + return 128 + end + return command(...) + end + else -- path is code + code = path + end + + local thread = nil + thread = coroutine.create(function(...) + -- pcall code so that we can remove it from the process list on exit + local result = + { + xpcall(function(...) + os.setenv("_", name) + init = init or function(...) return ... end + return code(init(...)) + end, + function(msg) + -- msg can be a custom error object + if type(msg) == 'table' then + if msg.reason ~= "terminated" then + io.stderr:write(msg.reason.."\n") + end + return msg.code or 0 + end + local stack = debug.traceback():gsub('^([^\n]*\n)[^\n]*\n[^\n]*\n','%1') + io.stderr:write(string.format('%s:\n%s', msg or '', stack)) + return 128 -- syserr + end, ...) + } + process.internal.close(thread) + --result[1] is false if the exception handler also crashed + if not result[1] and type(result[2]) ~= "number" then + require("event").onError(string.format("process library exception handler crashed: %s", tostring(result[2]))) + end + return select(2, table.unpack(result)) + end, true) + process.list[thread] = { + path = path, + command = name, + env = env, + data = parent_data( + { + handles = {}, + io = parent_data({}, p, "data", "io"), + coroutine_handler = parent_data({}, p, "data", "coroutine_handler"), + }, p, "data"), + parent = p, + instances = setmetatable({}, {__mode="v"}), + } + return thread +end + +function process.running(level) -- kept for backwards compat, prefer process.info + local info = process.info(level) + if info then + return info.path, info.env, info.command + end +end + +function process.info(levelOrThread) + local p + if type(levelOrThread) == "thread" then + p = process.findProcess(levelOrThread) + else + local level = levelOrThread or 1 + p = process.findProcess() + while level > 1 and p do + p = p.parent + level = level - 1 + end + end + if p then + return {path=p.path, env=p.env, command=p.command, data=p.data} + end +end + +--table of undocumented api subject to change and intended for internal use +process.internal = {} +--this is a future stub for a more complete method to kill a process +function process.internal.close(thread) + checkArg(1,thread,"thread") + local pdata = process.info(thread).data + for k,v in pairs(pdata.handles) do + v:close() + end + process.list[thread] = nil +end + +return process diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/rayEngine.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/rayEngine.lua new file mode 100755 index 00000000..520569ac --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/rayEngine.lua @@ -0,0 +1,566 @@ + +local component = require("component") +local computer = require("computer") +local advancedLua = require("advancedLua") +local color = require("color") +local image = require("image") +local unicode = require("unicode") +local buffer = require("doubleBuffering") +local GUI = require("GUI") +local event = require("event") + +---------------------------------------------------- Константы ------------------------------------------------------------------ + +local rayEngine = {} + +rayEngine.debugInformationEnabled = true +rayEngine.minimapEnabled = true +rayEngine.compassEnabled = false +rayEngine.watchEnabled = false +rayEngine.drawFieldOfViewOnMinimap = false +rayEngine.chatShowTime = 4 +rayEngine.chatHistory = {} + +---------------------------------------------- Расчетные функции ------------------------------------------------------------------ + +-- Позиция горизонта, относительно которой рисуется мир +function rayEngine.calculateHorizonPosition() + rayEngine.horizonPosition = math.floor(buffer.getHeight() / 2) +end + +-- Размер панели чата и лимита его истории +function rayEngine.calculateChatSize() + rayEngine.chatPanelWidth, rayEngine.chatPanelHeight = math.floor(buffer.getWidth() * 0.4), math.floor(buffer.getHeight() * 0.4) + rayEngine.chatHistoryLimit = rayEngine.chatPanelHeight +end + +-- Шаг, с которым будет изменяться угол рейкаста +function rayEngine.calculateRaycastStep() + rayEngine.raycastStep = rayEngine.player.fieldOfView / buffer.getWidth() +end + +-- Позиция оружия на экране и всех его вспомогательных текстур +function rayEngine.calculateWeaponPosition() + rayEngine.currentWeapon.xWeapon = buffer.getWidth() - rayEngine.currentWeapon.weaponTexture[1] + 1 + rayEngine.currentWeapon.yWeapon = buffer.getHeight() - rayEngine.currentWeapon.weaponTexture[2] + 1 + rayEngine.currentWeapon.xFire = rayEngine.currentWeapon.xWeapon + rayEngine.weapons[rayEngine.currentWeapon.ID].firePosition.x + rayEngine.currentWeapon.yFire = rayEngine.currentWeapon.yWeapon + rayEngine.weapons[rayEngine.currentWeapon.ID].firePosition.y + rayEngine.currentWeapon.xCrosshair = math.floor(buffer.getWidth() / 2 - rayEngine.currentWeapon.crosshairTexture[1] / 2) + rayEngine.currentWeapon.yCrosshair = math.floor(buffer.getHeight() / 2 - rayEngine.currentWeapon.crosshairTexture[2] / 2) +end + +-- Грубо говоря, это расстояние от камеры до виртуального экрана, на котором рисуется весь наш мир, влияет на размер блоков +function rayEngine.calculateDistanceToProjectionPlane() + rayEngine.distanceToProjectionPlane = (buffer.getWidth() / 2) / math.tan(math.rad((rayEngine.player.fieldOfView / 2))) +end + +-- Быстрый перерасчет всего, что нужно +function rayEngine.calculateAllParameters() + rayEngine.calculateHorizonPosition() + rayEngine.calculateChatSize() + rayEngine.calculateRaycastStep() + rayEngine.calculateDistanceToProjectionPlane() + if rayEngine.currentWeapon then rayEngine.calculateWeaponPosition() end +end + +---------------------------------------------- Вспомогательные функции ------------------------------------------------------------------ + +local function constrainAngle(value) + if ( value < 0 ) then + value = value + 360 + elseif ( value > 360 ) then + value = value - 360 + end + return value +end + +local function getSkyColorByTime() + return rayEngine.world.colors.sky[rayEngine.world.dayNightCycle.currentTime > 0 and math.ceil(rayEngine.world.dayNightCycle.currentTime / rayEngine.world.dayNightCycle.length * #rayEngine.world.colors.sky) or 1] +end + +local function getBrightnessByTime() + return rayEngine.properties.shadingTransparencyMap[rayEngine.world.dayNightCycle.currentTime > 0 and math.ceil(rayEngine.world.dayNightCycle.currentTime / rayEngine.world.dayNightCycle.length * #rayEngine.properties.shadingTransparencyMap) or 1] +end + +local function getTileColor(basecolor, distance) + local limitedDistance = math.floor(distance * rayEngine.properties.shadingCascades / rayEngine.properties.shadingDistance) + local transparency = rayEngine.currentShadingTransparencyMapValue - limitedDistance / rayEngine.properties.shadingCascades + transparency = (transparency >= rayEngine.properties.shadingTransparencyMap[1] and transparency <= 1) and transparency or rayEngine.properties.shadingTransparencyMap[1] + return color.blend(basecolor, 0x000000, transparency) +end + +function rayEngine.refreshTimeDependentColors() + rayEngine.world.colors.sky.current = getSkyColorByTime() + rayEngine.currentShadingTransparencyMapValue = getBrightnessByTime() + rayEngine.world.colors.groundByTime = color.blend(rayEngine.world.colors.ground, 0x000000, rayEngine.currentShadingTransparencyMapValue) +end + +local function doDayNightCycle() + if rayEngine.world.dayNightCycle.enabled then + local computerUptime = computer.uptime() + if (computerUptime - rayEngine.world.dayNightCycle.lastComputerUptime) >= rayEngine.world.dayNightCycle.speed then + rayEngine.world.dayNightCycle.currentTime = rayEngine.world.dayNightCycle.currentTime + rayEngine.world.dayNightCycle.speed + if rayEngine.world.dayNightCycle.currentTime > rayEngine.world.dayNightCycle.length then rayEngine.world.dayNightCycle.currentTime = 0 end + rayEngine.world.dayNightCycle.lastComputerUptime = computerUptime + + rayEngine.refreshTimeDependentColors() + end + end +end + +local function convertWorldCoordsToMapCoords(x, y) + return math.round(x / rayEngine.properties.tileWidth), math.round(y / rayEngine.properties.tileWidth) +end + +local function getBlockCoordsByLook(distance) + local radRotation = math.rad(rayEngine.player.rotation) + return convertWorldCoordsToMapCoords(rayEngine.player.position.x + distance * math.sin(radRotation) * rayEngine.properties.tileWidth, rayEngine.player.position.y + distance * math.cos(radRotation) * rayEngine.properties.tileWidth) +end + +---------------------------------------------------- Работа с файлами ------------------------------------------------------------------ + +-- Загрузка параметров движка +function rayEngine.loadEngineProperties(pathToRayEnginePropertiesFile) + rayEngine.properties = table.fromFile(pathToRayEnginePropertiesFile) +end + +-- Загрузка конифгурации оружия +function rayEngine.loadWeapons(pathToWeaponsFolder) + rayEngine.weaponsFolder = pathToWeaponsFolder + rayEngine.weapons = table.fromFile(rayEngine.weaponsFolder .. "Weapons.cfg") + rayEngine.changeWeapon(1) +end + +-- Загрузка конкретного мира +function rayEngine.loadWorld(pathToWorldFolder) + rayEngine.world = table.fromFile(pathToWorldFolder .. "/World.cfg") + rayEngine.map = table.fromFile(pathToWorldFolder .. "/Map.cfg") + rayEngine.player = table.fromFile(pathToWorldFolder .. "/Player.cfg") + rayEngine.blocks = table.fromFile(pathToWorldFolder .. "/Blocks.cfg") + -- Дополняем карту ее размерами + rayEngine.map.width = #rayEngine.map[1] + rayEngine.map.height = #rayEngine.map + -- Ебашим правильную позицию игрока, основанную на этой ХУЙНЕ, которую ГЛЕБ так ЛЮБИТ + rayEngine.player.position.x = rayEngine.properties.tileWidth * rayEngine.player.position.x - rayEngine.properties.tileWidth / 2 + rayEngine.player.position.y = rayEngine.properties.tileWidth * rayEngine.player.position.y - rayEngine.properties.tileWidth / 2 + -- Рассчитываем цвета, зависимые от времени - небо, землю, стены + rayEngine.refreshTimeDependentColors() + -- Обнуляем текущее время, если превышен лимит, а то мало ли какой пидорас начнет править конфиги мира + rayEngine.world.dayNightCycle.currentTime = rayEngine.world.dayNightCycle.currentTime > rayEngine.world.dayNightCycle.length and 0 or rayEngine.world.dayNightCycle.currentTime + -- Осуществляем базовое получение аптайма пекарни + rayEngine.world.dayNightCycle.lastComputerUptime = computer.uptime() + -- Рассчитываем необходимые параметры движка + rayEngine.calculateAllParameters() + + -- rayEngine.wallsTexture = image.load("/heart.pic") + -- rayEngine.wallsTexture = image.transform(rayEngine.wallsTexture, rayEngine.properties.tileWidth, rayEngine.properties.tileWidth / 2) +end + +---------------------------------------------------- Функции, связанные с игроком ------------------------------------------------------------------ + +function rayEngine.changeWeapon(weaponID) + if rayEngine.weapons[weaponID] then + rayEngine.currentWeapon = { + ID = weaponID, + damage = rayEngine.weapons[weaponID].damage, + weaponTexture = image.load(rayEngine.weaponsFolder .. rayEngine.weapons[weaponID].weaponTexture), + fireTexture = image.load(rayEngine.weaponsFolder .. rayEngine.weapons[weaponID].fireTexture), + crosshairTexture = image.load(rayEngine.weaponsFolder .. rayEngine.weapons[weaponID].crosshairTexture) + } + rayEngine.calculateWeaponPosition() + else + rayEngine.currentWeapon = nil + end +end + +function rayEngine.move(distanceForward, distanceRight) + local forwardRotation = math.rad(rayEngine.player.rotation) + local rightRotation = math.rad(rayEngine.player.rotation + 90) + local xNew = rayEngine.player.position.x + distanceForward * math.sin(forwardRotation) + distanceRight * math.sin(rightRotation) + local yNew = rayEngine.player.position.y + distanceForward * math.cos(forwardRotation) + distanceRight * math.cos(rightRotation) + + local xWorld, yWorld = convertWorldCoordsToMapCoords(xNew, yNew) + if rayEngine.map[yWorld][xWorld] == nil then + rayEngine.player.position.x, rayEngine.player.position.y = xNew, yNew + end +end + +function rayEngine.rotate(angle) + rayEngine.player.rotation = constrainAngle(rayEngine.player.rotation + angle) +end + +function rayEngine.turnRight() + rayEngine.rotate(rayEngine.player.rotationSpeed) +end + +function rayEngine.turnLeft() + rayEngine.rotate(-rayEngine.player.rotationSpeed) +end + +function rayEngine.moveForward() + rayEngine.move(rayEngine.player.moveSpeed, 0) +end + +function rayEngine.moveBackward() + rayEngine.move(-rayEngine.player.moveSpeed, 0) +end + +function rayEngine.moveLeft() + rayEngine.move(0, -rayEngine.player.moveSpeed) +end + +function rayEngine.moveRight() + rayEngine.move(0, rayEngine.player.moveSpeed) +end + +function rayEngine.jump() + if not rayEngine.player.jumpTimer then + local function onJumpFinished() + rayEngine.horizonPosition = rayEngine.horizonPosition - rayEngine.player.jumpHeight; + rayEngine.horizonPosition = rayEngine.horizonPosition - rayEngine.player.jumpHeight; + rayEngine.player.jumpTimer = nil + end + + rayEngine.player.jumpTimer = event.timer(1, onJumpFinished) + rayEngine.horizonPosition = rayEngine.horizonPosition + rayEngine.player.jumpHeight + rayEngine.horizonPosition = rayEngine.horizonPosition + rayEngine.player.jumpHeight + end +end + +function rayEngine.crouch() + rayEngine.player.isCrouched = not rayEngine.player.isCrouched + local heightAdder = rayEngine.player.isCrouched and -rayEngine.player.crouchHeight or rayEngine.player.crouchHeight + rayEngine.horizonPosition = rayEngine.horizonPosition + heightAdder + rayEngine.horizonPosition = rayEngine.horizonPosition + heightAdder +end + +function rayEngine.destroy(distance) + local xBlock, yBlock = getBlockCoordsByLook(distance) + if rayEngine.map[yBlock] and rayEngine.map[yBlock][xBlock] and rayEngine.blocks[rayEngine.map[yBlock][xBlock]] and rayEngine.blocks[rayEngine.map[yBlock][xBlock]].canBeDestroyed then rayEngine.map[yBlock][xBlock] = nil end +end + +function rayEngine.place(distance, blockColor) + local xBlock, yBlock = getBlockCoordsByLook(distance) + if rayEngine.map[yBlock] and rayEngine.map[yBlock][xBlock] == nil then rayEngine.map[yBlock][xBlock] = blockColor end +end + +---------------------------------------------------- Функции интерфейса ------------------------------------------------------------------ + +function rayEngine.drawDebugInformation(x, y, width, transparency, ...) + local lines = {...} + buffer.square(x, y, width, #lines, 0x000000, 0x000000, " ", transparency); x = x + 1 + for line = 1, #lines do buffer.text(x, y, 0xEEEEEE, lines[line]); y = y + 1 end +end + +local function drawFieldOfViewAngle(x, y, distance, color) + local fieldOfViewHalf = rayEngine.player.fieldOfView / 2 + local firstAngle, secondAngle = math.rad(-(rayEngine.player.rotation - fieldOfViewHalf)), math.rad(-(rayEngine.player.rotation + fieldOfViewHalf)) + local xFirst, yFirst = math.floor(x + math.sin(firstAngle) * distance), math.floor(y + math.cos(firstAngle) * distance) + local xSecond, ySecond = math.floor(x + math.sin(secondAngle) * distance), math.floor(y + math.cos(secondAngle) * distance) + buffer.semiPixelLine(x, y, xFirst, yFirst, color) + buffer.semiPixelLine(x, y, xSecond, ySecond, color) +end + +function rayEngine.drawMap(x, y, width, height, transparency) + local xHalf, yHalf = math.floor(width / 2), math.floor(height / 2) + local xMap, yMap = convertWorldCoordsToMapCoords(rayEngine.player.position.x, rayEngine.player.position.y) + + buffer.square(x, y, width, yHalf, 0x000000, 0x000000, " ", transparency) + + local xPos, yPos = x, y * 2 - 1 + for i = yMap - yHalf + 1, yMap + yHalf do + for j = xMap + xHalf + 1, xMap - xHalf + 2, -1 do + if rayEngine.map[i] and rayEngine.map[i][j] then + buffer.semiPixelSet(xPos, yPos, rayEngine.blocks[rayEngine.map[i][j]].color) + end + xPos = xPos + 1 + end + xPos = x; yPos = yPos + 1 + end + + local xPlayer, yPlayer = x + xHalf, y + yHalf + --Поле зрения + if rayEngine.drawFieldOfViewOnMinimap then drawFieldOfViewAngle(xPlayer, yPlayer, 5, 0xCCFFBF) end + --Игрок + buffer.semiPixelSet(xPlayer, yPlayer, 0x66FF40) +end + +function rayEngine.intro() + local logo = image.fromString("17060000FF 0000FF 0000FF 0000FF 007EFF▄007EFF▄007EFF▄007EFF▀007EFF▀007EFF▀007EFF▀007EFF▀007EFF▀007EFF▀007EFF▄007EFF▄007EFF▄0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 007EFF▄007EFF▀007EFF▀0000FF 0000FF 0000FF 0000FF 0053FF▄0053FF▀0053FF▀0053FF▀0053FF▄0000FF 0000FF 0000FF 0000FF 007EFF▀007EFF▀007EFF▄0000FF 0000FF 0000FF 007EFF▀007EFF▄0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 530000 0000FF 0078FF▀0000FF 537800▀0078FF▀0078FF▀0078FF▀0078FF▀0078FF▀0078FF▀7E7800▀0078FF▀0000FF 0078FF▀0000FF 0000FF 007EFF▀007EFF▀007EFF▄007EFF▄007EFF▄0000FF 0000FF 0053FF▀0053FF▀0053FF▀0000FF 0000FF 007EFF▄007EFF▄007EFF▄007EFF▀007EFF▀0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 007EFF▀007EFF▀007EFF▀007EFF▀007EFF▀007EFF▀007EFF▀0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 007EFFP007EFFo007EFFw007EFFe007EFFr007EFFe007EFFd0000FF 007EFFb007EFFy0000FF 007EFFR007EFFa007EFFy007EFFE007EFFn007EFFg007EFFi007EFFn007EFFe007EFF™0000FF 0000FF ") + local x, y = math.floor(buffer.getWidth() / 2 - logo[1] / 2), math.floor(buffer.getHeight() / 2 - logo[2] / 2) + local function draw(transparency) + buffer.clear(0xF0F0F0); + buffer.image(x, y, logo) + buffer.square(1, 1, buffer.getWidth(), buffer.getHeight(), 0x000000, 0x000000, " ", transparency) + buffer.draw() + os.sleep(0) + end + for i = 0, 100, 20 do draw(i) end + os.sleep(1.5) + for i = 100, 0, -20 do draw(i) end +end + +function rayEngine.compass(x, y) + if not rayEngine.compassImage then rayEngine.compassImage = image.fromString("1C190000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 553600▄373100▄543600▄373600▄543600▄373100▄540000 375400▄673700▄0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0055FF▄675400▄553700▄375500▄550000 375500▄540000 375400▄540000 373600▄310000 675500▄677E00▄375300▄365400▄373600▄540000 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 555400▄553700▄540000 543700▄540000 375300▄533100▄310B00▄310000 310000 360000 543100▄375300▄553100▄533600▄543200▄313600▄372A00▄373100▄0054FF▄0054FF▄0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 540000 543200▄540000 545300▄540600▄063100▄310000 315400▄365400▄373100▄313600▄530000 0000FF 0000FF 535400▄535400▄540000 365300▄533100▄065300▄310000 530600▄0054FF▄0000FF 0000FF 0000FF 0000FF 0000FF 530000 365300▄543600▄310600▄312A00▄2A5300▄365300▄543600▄540000 365300▄315300▄530000 0000FF 0000FF 535400▄540000 540000 313600▄363100▄540000 313600▄315300▄062A00▄543100▄0000FF 0000FF 0000FF 0000FF 315300▄533600▄312A00▄2A3100▄315300▄533600▄315400▄540000 535400▄540000 530000 533100▄0000FF 0000FF 540000 540000 315400▄540000 543100▄530000 543100▄313600▄2A0000 2A2900▄540000 0000FF 0000FF 0000FF 533100▄315400▄530000 062A00▄533100▄315300▄535400▄533100▄540000 315400▄543100▄530000 0000FF 0000FF 540000 535400▄315300▄535400▄363100▄535400▄530000 530000 312A00▄2A5300▄0054FF▀0000FF 0000FF 0000FF 312A00▄530000 553600▄2A5500▄2A0000 312A00▄533600▄545300▄315400▄535400▄540000 540000 0053FF▄0053FF▄540000 530000 535400▄535400▄545300▄530000 363100▄312A00▄313600▄545300▄0000FF 0000FF 0000FF 0000FF 530000 535400▄540000 545300▄555400▄315500▄315400▄533100▄543100▄530000 533100▄530000 535400▄535500▄533100▄535400▄315300▄533100▄533600▄315300▄530000 542A00▄312800▄0029FF▀0000FF 0000FF 0000FF 0000FF 530000 545500▄540000 555300▄535400▄530000 542A00▄545300▄365400▄530000 543600▄2A5400▄547E00▄550000 545300▄533600▄540000 530000 530000 542A00▄2A0000▄0029FF▀0000FF 0000FF 0000FF 0000FF 0000FF 530000 535400▄540000 540000 540000 545300▄530000 540000 2A5500▄545300▄292A00▄290000 292A00▄290000 295400▄292A00▄292A00▄290000 542A00▄543600▄285400▄0054FF▄0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 530000 533100▄540000 530000 535400▄0053FF▀0053FF▀542800▄542800▄532800▄2A2900▄542900▄532900▄542900▄542900▄532900▄542800▄2A2900▄2A2800▄532900▄552800▄542800▄0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 2A5300▄530000 535400▄540000 530000 532A00▀2A5300▄2A5500▄0029FF▀0028FF▀0028FF▀0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0028FF▀0028FF▀0028FF▀295300▄535500▄0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 535400▄545300▄530000 545300▄530000 2A0000 2A5300▄545300▄532A00▄542900▄532900▄2A2900▄2A2900▄2A2800▄2A2800▄2A2800▄2A2900▄2A2900▄532900▄532900▄2A2900▄542A00▄545300▄0054FF▄0054FF▄0000FF 0000FF 0000FF 545300▄530000 530000 545300▄2A5300▄532A00▄542900▄290000 295400▄297F00▄548100▄548100▄558100▄558100▄558100▄558100▄538100▄2A8100▄007E00▄295400▄2A2900▄295400▄2A2900▄290000 542A00▄557E00▄0000FF 0000FF 530000 2A0000 545300▄530000 532900▄285400▄2A8000▄7F8100▄810000 810000 810000 810000 810000 810000 812A00N810000 810000 810000 810000 810000 808100▄548100▄545500▄295300▄282900▄542900▄0000FF 0000FF 2A0000 2A0000 545300▄2A2900▄298000▄810000 810000 815500381550018155005810000 810000 810000 810000 810000 810000 810000 810000 81550048155005810000 810000 810000 558100▄2A5300▄282900▄0029FF▄0000FF 532A00▄2A0000 545300▄547E00▄810000 810000 810000 810000 810000 810000 810000 810000 810000 810000 810000 810000 810000 810000 810000 810000 810000 810000 810000 810000 7F8000▄290000 292800▄0000FF 2A0000 2A0000 2A5300▄7E5300▄810000 812A00W810000 810000 810000 810000 810000 810000 810000 810000 810000 810000 810000 810000 810000 810000 810000 810000 812A00E810000 807E00▄2A2900▄292800▄0000FF 2A0000 2A2900▄2A0000 2A0000 552A00▄810000 810000 810000 810000 810000 810000 810000 810000 810000 810000 810000 810000 810000 810000 810000 810000 810000 810000 815500▄542900▄280000 0028FF▀530000 2A0000 2A0000 290000 2A2900▄545300▄552A00▄815500▄810000 815500281550028155005810000 810000 810000 810000 810000 810000 810000 815500181550038155005817F00▄552900▄292800▄280000 282A00▄0000FF 530000 2A0000 540000 2A5300▄282A00▄290000 532900▄542A00▄542800▄552900▄7F5300▄815500▄817F00▄817F00▄810000 812A00S810000 817F00▄815500▄815500▄555400▄532900▄292800▄280000 282A00▄282A00▄530000 0000FF 532A00▄2A0000 530000 530000 2A5300▄290000 282900▄002900▄280000 280000 280000 290000 292A00▄2A2900▄542900▄532900▄2A0000 295300▄282900▄280000 280000 280000 282900▄295300▄295300▄2A5300▄552A00▄0000FF 530000 295300▄535400▄540000 535400▄540000 535400▄2A5500▄282900▄280000▄280000 280000▄290000▄2A2800▄2A2900▄552A00▄7E0000▄2A0000▄280000▄280000 280000▄280000▄002AFF▀002AFF▀002AFF▀002AFF▀0000FF 0000FF 532900▄532800▄0029FF▀0029FF▀0029FF▀0029FF▀0029FF▀0029FF▀0000FF 2A9800▄285500▄547E00▄7E5400▄7F5300▄7E2900▄7E2900▄552A00▄542A00▄2A5300▄282A00▄2A7E00▄0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF ") end + buffer.image(x, y, rayEngine.compassImage) + + x, y = x + 15, y + 17 + local distance = 3.4 + local northAngle = -rayEngine.player.rotation + local xScaleFactor = 2.2 + local southPoint, northPoint = {}, {} + local northAngleRad = math.rad(northAngle) + northPoint.x, northPoint.y = math.round(x + math.sin(northAngleRad) * distance * xScaleFactor), math.round(y - math.cos(northAngleRad) * distance) + northAngleRad = math.rad(northAngle + 180) + southPoint.x, southPoint.y = math.round(x + math.sin(northAngleRad) * distance * xScaleFactor), math.round(y - math.cos(northAngleRad) * distance) + + y = y * 2 + buffer.semiPixelLine(x, y, northPoint.x, northPoint.y * 2, 0xFF5555) + buffer.semiPixelLine(x, y, southPoint.x, southPoint.y * 2, 0xFFFFFF) + buffer.semiPixelSet(x, y, 0x000000) +end + +function rayEngine.watch(x, y) + if not rayEngine.watchImage then rayEngine.watchImage = image.fromString("20190000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0053FF▄552900▄673100▄7E2A00▄7E3100▄7E2A00▄7E3100▄672A00▄7E2A00▄672900▄7F3100▄7E2A00▄0053FF▄0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 313600▄290000 290600▄062900▄290000 062900▄290000 062900▄290000 290600▄062900▄2A2900▄2A0600▄0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 313600▄012900▄2A2900▄290000 012900▄290000 290000 012A00▄290000 290000 312900▄293100▄310600▄0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 310000 292800▄062A00▄290000 290100▄062900▄290000 290000 2C2900▄290600▄293100▄2A2900▄292800▄0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0054FF▄557F00▄012800▄290000 290000 292800▄290000 012900▄292800▄290600▄290000 292800▄290000 012800▄807E00▄0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF AB8100▄7F8000▄283100▄280000 015400▄318100▄005300▄065500▄315500▄282A00▄296700▄015500▄290000 002900▄677E00▄81AA00▄0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 807F00▄7F8000▄677E00▄812900▄672800▄542800▄2A0000▄2A0000▄815400▄ACAA00▄555400▄283100▄2A0000 312900▄672800▄7F5300▄7F0000 7F0000 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 007EFF▄817E00▄7E2900▄2A0000▄805300▄54AA00▄003100▄2A0000 310600▄532900▄312800▄532900▄360100▄552900▄672800▄7E2A00▄315500▄678000▄555300▄540000▄805400▄0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0081FF▄802900▄280100▄280000 285400▄310600▄532900▄290000 290000 290000 290000 29D700129D7002290000 290000 290000 282900▄062900▄2A2900▄550600▄556700▄285500▄542800▄0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0080FF▄557E00▄28AA00▄005500▄552900▄290000 290000 290000 29D700129D7001290000 290000 290000 290000 290000 290000 290000 29D7001290000 290000 290000 290000 672900▄318000▄297F00▄0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 543100▄002800▄553100▄7E2800▄290000 29D700129D7000290000 290000 290000 290000 290000 290000 290000 290000 290000 290000 290000 290000 290000 29D7002290000 290000 7E2900▄7E6700▄282900▄808100▄0000FF 0000FF 0000FF 0000FF 805500▄280000▄290000 310600▄290000 290000 290000 290000 290000 290000 290000 290000 290000 290000 290000 290000 290000 290000 290000 290000 290000 290000 290000 290000 290000 555400▄535400▄533100▄D58000▄0080FF▄0000FF 552900▀550000 54AB00▄558100▄290000 290000 29D7009290000 290000 290000 290000 290000 290000 290000 290000 290000 290000 290000 290000 290000 290000 290000 290000 290000 29D7003290000 315400▄548100▄067E00▄557F00▄806700▄ACAB00▄0055FF▀545500▄AB3100▄815400▄290100▄290000 290000 290000 290000 290000 290000 290000 290000 290000 290000 290000 290000 290000 290000 290000 290000 290000 290000 290000 290000 290000 545300▄AB5500▄815300▄558000▄558000▄0000FF 0000FF 538100▄002800▄290600▄290000 290000 290000 29D7008290000 290000 290000 290000 290000 290000 290000 290000 290000 290000 290000 290000 290000 290000 290000 29D7004290000 290000 530000 000000 286700▄0080FF▀0000FF 0000FF 0000FF 0000FF 292A00▄000100▄530000 285400▄290000 290000 290000 290000 290000 290000 290000 290000 290000 290000 290000 290000 290000 290000 290000 290000 290000 290000 290000 296700▄063100▄280000 818000▄0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 550000 810000▄532800▄015300▄280000 292800▄290000 29D7007290000 290000 290000 290000 290000 290000 290000 290000 290000 29D7005290000 290100▄292A00▄065300▄7F0000▄802900▄81C900▄0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 549900▄002900▄292800▄312800▄290600▄283100▄290600▄292800▄290000 290000 290000 29D7006290000 290000 292800▄292800▄290000 285300▄2A0600▄362800▄280000 285500▄0081FF▀0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 7EAA00▄317E00▄005300▄547E00▄7F2900▄290000▄292A00▄063100▄283100▄295500▄286200▄285500▄012A00▄292A00▄310600▄540000▄807E00▄005500▄015500▄530000 0081FF▀0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 7E8100▄7E8000▄557F00▄317E00▄2A3600▄283100▄005400▄2A5300▄817E00▄AA7E00▄545300▄005300▄005400▄283100▄537E00▄548100▄7F0000 7E8000▄0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 819800▄807F00▄280000 002900▄312900▄7E2800▄292800▄532800▄552900▄280000 550100▄542800▄282900▄000100▄7E0000 AA9800▄0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 555300▄012800▄290000 062900▄290000 062900▄290000 062900▄290000 290000 290000 290600▄280000 007EFF▀0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 313600▄062900▄312900▄290000 012900▄293100▄290000 062900▄312900▄290600▄312900▄2A0000 2A2900▄0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 315400▄290600▄310000 062900▄290000 293100▄062900▄290000 293100▄290000 293100▄062900▄312A00▄0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0054FF▀285500▄285500▄015500▄285500▄285500▄015500▄295500▄015500▄285500▄015500▄285500▄065500▄0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF 0000FF ") end + + buffer.image(x, y, rayEngine.watchImage) + x, y = x + 15, y + 12 + + local realTimeInSeconds = rayEngine.world.dayNightCycle.currentTime * 86400 / rayEngine.world.dayNightCycle.length + local hours = realTimeInSeconds / 3600 + local _, minutes = math.modf(hours) + local hourAngle = math.rad(hours * 360 / 12) + local minuteAngle = math.rad(minutes * 360) + local hourArrowLength, minuteArrowLength = 2.8, 4.5 + local xMinute, yMinute = math.round(x + math.sin(minuteAngle) * minuteArrowLength * 2), math.round(y - math.cos(minuteAngle) * minuteArrowLength) + local xHour, yHour = math.round(x + math.sin(hourAngle) * hourArrowLength * 2), math.round(y - math.cos(hourAngle) * hourArrowLength) + + y = y * 2 + buffer.semiPixelLine(x, y, xMinute, yMinute * 2, 0xEEEEEE) + buffer.semiPixelLine(x, y, xHour, yHour * 2, 0xEEEEEE) +end + +local function addItemToChatHistory(text, color) + text = string.wrap({text}, rayEngine.chatPanelWidth - 2) + table.insert(rayEngine.chatHistory, {color = color, text = text}) + if #rayEngine.chatHistory > rayEngine.chatHistoryLimit then table.remove(rayEngine.chatHistory, 1) end +end + +function rayEngine.chat(transparency) + local x, y = 1, buffer.getHeight() - rayEngine.chatPanelHeight - 3 + buffer.square(x, y, rayEngine.chatPanelWidth, rayEngine.chatPanelHeight, 0x000000, 0xFFFFFF, " ", transparency or 0.5) + buffer.setDrawLimit(x, y, x + rayEngine.chatPanelWidth - 1, y + rayEngine.chatPanelHeight - 1) + local yMessage = y + rayEngine.chatPanelHeight - 1 + x = x + 1 + + for message = #rayEngine.chatHistory, 1, -1 do + for line = #rayEngine.chatHistory[message].text, 1, -1 do + buffer.text(x, yMessage, rayEngine.chatHistory[message].color or 0xFFFFFF, rayEngine.chatHistory[message].text[line]) + yMessage = yMessage - 1 + if yMessage < y then buffer.resetDrawLimit(); return end + end + end + + buffer.resetDrawLimit() +end + +function rayEngine.commandLine(transparency) + transparency = transparency or 50 + local inputPanelHeight = 3 + local x, y = 1, buffer.getHeight() - inputPanelHeight + 1 + --Врубаем чат и рисуем все, включая его + rayEngine.chatEnabled = true + rayEngine.update() + + --Ввод данных + local input = GUI.input(x, y, buffer.getWidth(), 3, 0xFFFFFF, 0x3C3C3C, 0x666666, 0xFFFFFF, 0x3C3C3C, "") + input.eventHandler({draw = function() input:draw() end}, input, {[1] = "touch", [3] = input.x, [4] = input.y}) + + local words = {}; for word in string.gmatch(input.text, "[^%s]+") do table.insert(words, unicode.lower(word)) end + if #words > 0 then + if unicode.sub(words[1], 1, 1) == "/" then + words[1] = unicode.sub(words[1], 2, -1) + if words[1] == "time" then + if words[2] == "set" and words[3] and tonumber(words[3]) then + local newTime = tonumber(words[3]) + if newTime < 0 or newTime > rayEngine.world.dayNightCycle.length then + addItemToChatHistory("Время не может быть отрицательным и превышать длину суток (" .. rayEngine.world.dayNightCycle.length .. " секю)", 0xFF8888) + else + rayEngine.world.dayNightCycle.currentTime = math.floor(newTime) + addItemToChatHistory("Время успешно изменено на: " .. newTime, 0xFFDB40) + end + elseif words[2] == "get" then + addItemToChatHistory("Текущее время: " .. rayEngine.world.dayNightCycle.currentTime, 0xFFDB40) + addItemToChatHistory("Длина суток: " .. rayEngine.world.dayNightCycle.length, 0xFFDB40) + elseif words[2] == "lock" then + rayEngine.world.dayNightCycle.enabled = not rayEngine.world.dayNightCycle.enabled + addItemToChatHistory("Состояние цикла дня и ночи: " .. tostring(rayEngine.world.dayNightCycle.enabled), 0xFFDB40) + end + elseif words[1] == "setrenderquality" and tonumber(words[2]) then + rayEngine.properties.raycastQuality = tonumber(words[2]) + addItemToChatHistory("Качество рендера изменено на: " .. tonumber(words[2]), 0xFFDB40) + elseif words[1] == "setdrawdistance" and tonumber(words[2]) then + rayEngine.properties.drawDistance = tonumber(words[2]) + addItemToChatHistory("Дистанция прорисовки изменена на: " .. tonumber(words[2]), 0xFFDB40) + elseif words[1] == "setshadingcascades" and tonumber(words[2]) then + rayEngine.properties.shadingCascades = tonumber(words[2]) + addItemToChatHistory("Количество цветов для отрисовки блока изменено на: " .. tonumber(words[2]), 0xFFDB40) + elseif words[1] == "setshadingdistance" and tonumber(words[2]) then + rayEngine.properties.shadingDistance = tonumber(words[2]) + addItemToChatHistory("Дистация затенения блоков изменена на: " .. tonumber(words[2]), 0xFFDB40) + elseif words[1] == "help" then + addItemToChatHistory("Доступные команды:", 0xFFDB40) + addItemToChatHistory("/time get", 0xFFFFBF) + addItemToChatHistory("/time set ", 0xFFFFBF) + addItemToChatHistory("/time lock", 0xFFFFBF) + addItemToChatHistory(" ", 0xFFFFFF) + addItemToChatHistory("/setRenderQuality ", 0xFFFFBF) + addItemToChatHistory("/setDrawDistance ", 0xFFFFBF) + addItemToChatHistory("/setShadingCascades ", 0xFFFFBF) + addItemToChatHistory("/setShadingDistance ", 0xFFFFBF) + else + addItemToChatHistory("Неизвестная команда. Введите /help для получения списка команд", 0xFF8888) + end + else + addItemToChatHistory("> " .. input.text, 0xFFFFFF) + end + end + + --Активируем таймер + if rayEngine.chatTimer then event.cancel(rayEngine.chatTimer) end + rayEngine.chatEnabled = true + rayEngine.chatTimer = event.timer(rayEngine.chatShowTime, function() rayEngine.chatEnabled = false; rayEngine.chatTimer = nil; update() end) +end + +function rayEngine.toggleMinimap() + rayEngine.minimapEnabled = not rayEngine.minimapEnabled +end + +function rayEngine.toggleDebugInformation() + rayEngine.debugInformationEnabled = not rayEngine.debugInformationEnabled +end + +function rayEngine.toggleCompass() + rayEngine.compassEnabled = not rayEngine.compassEnabled + if not rayEngine.compassEnabled then rayEngine.compassImage = nil end +end + +function rayEngine.toggleWatch() + rayEngine.watchEnabled = not rayEngine.watchEnabled + if not rayEngine.watchEnabled then rayEngine.watchImage = nil end +end + +function rayEngine.drawWeapon() + if rayEngine.currentWeapon.needToFire then buffer.image(rayEngine.currentWeapon.xFire, rayEngine.currentWeapon.yFire, rayEngine.currentWeapon.fireTexture); rayEngine.currentWeapon.needToFire = false end + buffer.image(rayEngine.currentWeapon.xWeapon, rayEngine.currentWeapon.yWeapon, rayEngine.currentWeapon.weaponTexture) + buffer.image(rayEngine.currentWeapon.xCrosshair, rayEngine.currentWeapon.yCrosshair, rayEngine.currentWeapon.crosshairTexture) +end + +function rayEngine.drawStats() + local width = math.floor(buffer.getWidth() * 0.3) + local height = 5 + local x, y = buffer.getWidth() - width - 1, 2 + buffer.square(x, y, width, height, 0x000000, 0xFFFFFF, " ", 0.5) + + GUI.progressBar(x + 1, y + 4, width - 2, 1, 0x000000, 0xFF5555, rayEngine.player.health.current, rayEngine.player.health.maximum, true) +end + +---------------------------------------------------- Функции отрисовки мира ------------------------------------------------------------------ + +local function raycast(angle) + angle = math.rad(angle) + local angleSinDistance, angleCosDistance, currentDistance, xWorld, yWorld, xMap, yMap, tile = math.sin(angle) * rayEngine.properties.raycastQuality, math.cos(angle) * rayEngine.properties.raycastQuality, 0, rayEngine.player.position.x, rayEngine.player.position.y + + while true do + if currentDistance <= rayEngine.properties.drawDistance then + xMap, yMap = math.floor(xWorld / rayEngine.properties.tileWidth), math.floor(yWorld / rayEngine.properties.tileWidth) + if rayEngine.map[yMap] and rayEngine.map[yMap][xMap] then + return currentDistance, rayEngine.map[yMap][xMap] + end + + xWorld, yWorld = xWorld + angleSinDistance, yWorld + angleCosDistance + currentDistance = currentDistance + rayEngine.properties.raycastQuality + else + return nil + end + end +end + +function rayEngine.drawWorld() + --Земля + buffer.clear(rayEngine.world.colors.groundByTime) + --Небо + buffer.square(1, 1, buffer.getWidth(), rayEngine.horizonPosition, rayEngine.world.colors.sky.current, 0x0, " ") + --Сцена + local startAngle, endAngle, startX, distanceToTile, tileID, height, startY, tileColor = rayEngine.player.rotation - rayEngine.player.fieldOfView / 2, rayEngine.player.rotation + rayEngine.player.fieldOfView / 2, 1 + for angle = startAngle, endAngle, rayEngine.raycastStep do + distanceToTile, tileID = raycast(angle) + if distanceToTile then + -- Получаем цвет стенки + tileColor = getTileColor(rayEngine.blocks[tileID].color, distanceToTile) + + -- Поддержка "высококачественной" doubleHeight-графики + if rayEngine.properties.useSimpleRenderer then + height = rayEngine.properties.tileWidth / distanceToTile * rayEngine.distanceToProjectionPlane + startY = rayEngine.horizonPosition - height / 2 + 1 + buffer.square(math.floor(startX), math.floor(startY), 1, math.floor(height), tileColor, 0x000000, " ") + else + height = rayEngine.properties.tileWidth / distanceToTile * rayEngine.distanceToProjectionPlane * 2 + startY = rayEngine.horizonPosition * 2 - height / 2 + 1 + buffer.semiPixelSquare(math.floor(startX), math.floor(startY), 1, height, tileColor) + end + + --ТИКСТУРКА)))00 + -- local xTexture = startX % rayEngine.properties.tileWidth + 1 + -- if xTexture >= 1 and xTexture <= buffer.getWidth() then + -- local column = image.getColumn(rayEngine.wallsTexture, xTexture) + -- column = image.transform(column, 1, height) + -- buffer.image(math.floor(startX), math.floor(startY), column) + -- end + end + startX = startX + 1 + end +end + +function rayEngine.update() + local frameRenderClock = os.clock() + + rayEngine.drawWorld() + if rayEngine.currentWeapon then rayEngine.drawWeapon() end + if rayEngine.minimapEnabled then rayEngine.drawMap(3, 2, 24, 24, 0.5) end + -- rayEngine.drawStats() + local xTools, yTools = 3, buffer.getHeight() - 25 + if rayEngine.compassEnabled then rayEngine.compass(xTools, yTools); xTools = xTools + 30 end + if rayEngine.watchEnabled then rayEngine.watch(xTools, yTools) end + if rayEngine.chatEnabled then rayEngine.chat() end + doDayNightCycle() + + if rayEngine.debugInformationEnabled then + rayEngine.drawDebugInformation(3, 2 + (rayEngine.minimapEnabled and 12 or 0), 24, 0.6, + "renderTime: " .. math.doubleToString((os.clock() - frameRenderClock) * 1000, 2) .. " ms", + "freeRAM: " .. math.doubleToString(computer.freeMemory() / 1024, 2) .. " KB", + "pos: " .. math.doubleToString(rayEngine.player.position.x) .. " x " .. math.doubleToString(rayEngine.player.position.y) + ) + end + + buffer.draw() +end + +---------------------------------------------------------------------------------------------------------------------------------- + +function rayEngine.changeResolution(width, height) + component.gpu.setResolution(width, height) + buffer.flush() + rayEngine.calculateAllParameters() +end + +function rayEngine.fire() + rayEngine.currentWeapon.needToFire = true + rayEngine.update() + os.sleep(0.1) + rayEngine.update() +end + +---------------------------------------------------------------------------------------------------------------------------------- + +return rayEngine diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/rc.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/rc.lua new file mode 100755 index 00000000..07173323 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/rc.lua @@ -0,0 +1,7 @@ +-- Keeps track of loaded scripts to retain local values between invocation +-- of their command callbacks. +local rc = {} +rc.loaded = {} + +return rc + diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/scale.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/scale.lua new file mode 100755 index 00000000..fdca6b64 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/scale.lua @@ -0,0 +1,87 @@ + +local component = require("component") +local screenScale = {} + +------------------------------------------------------------------------------------------------------ + +local function calculateAspect(screens) + if screens == 2 then + return 28 + elseif screens > 2 then + return 28 + (screens - 2) * 16 + else + return 12 + end +end + +function screenScale.getResolution(scale, debug) + if scale > 1 then + scale = 1 + elseif scale < 0.1 then + scale = 0.1 + end + + local xScreens, yScreens = component.proxy(component.gpu.getScreen()).getAspectRatio() + local xPixels, yPixels = calculateAspect(xScreens), calculateAspect(yScreens) + local proportion = xPixels / yPixels + + local xMax, yMax = component.gpu.maxResolution() + + local newWidth, newHeight + if proportion >= 1 then + newWidth = xMax + newHeight = math.floor(newWidth / proportion / 2) + else + newHeight = yMax + newWidth = math.floor(newHeight * proportion * 2) + end + + local optimalNewWidth, optimalNewHeight = newWidth, newHeight + if optimalNewWidth > xMax then + local difference = newWidth / xMax + optimalNewWidth = xMax + optimalNewHeight = math.ceil(newHeight / difference) + end + + if optimalNewHeight > yMax then + local difference = newHeight / yMax + optimalNewHeight = yMax + optimalNewWidth = math.ceil(newWidth / difference) + end + + local finalNewWidth, finalNewHeight = math.floor(optimalNewWidth * scale), math.floor(optimalNewHeight * scale) + + if debug then + print(" ") + print("Максимальное разрешение: "..xMax.."x"..yMax) + print("Пропорция монитора: "..xPixels.."x"..yPixels) + print("Коэффициент пропорции: "..proportion) + print(" ") + print("Теоретическое разрешение: "..newWidth.."x"..newHeight) + print("Оптимизированное разрешение: "..optimalNewWidth.."x"..optimalNewHeight) + print(" ") + print("Новое разрешение: "..finalNewWidth.."x"..finalNewHeight) + print(" ") + end + + return finalNewWidth, finalNewHeight +end + +--Установка масштаба монитора +function screenScale.set(scale, debug) + --Устанавливаем выбранное разрешение + component.gpu.setResolution(screenScale.getResolution(scale, debug)) +end + +------------------------------------------------------------------------------------------------------ + +screenScale.set(0.8) + +------------------------------------------------------------------------------------------------------ + +return screenScale + + + + + diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/serialization.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/serialization.lua new file mode 100755 index 00000000..5dc54de4 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/serialization.lua @@ -0,0 +1,20 @@ + +require("advancedLua") +local serialization = {} + +------------------------------------------------- Public methods ----------------------------------------------------------------- + +function serialization.serialize(variable, ...) + local variableType = type(variable) + if variableType == "table" then + return table.serialize(variable, ...) + else + return tostring(variable) + end +end + +serialization.unserialize = table.unserialize + +---------------------------------------------------------------------------------------------------------------------- + +return serialization diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/sh.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/sh.lua new file mode 100755 index 00000000..e7a441ca --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/sh.lua @@ -0,0 +1,890 @@ +local event = require("event") +local fs = require("filesystem") +local process = require("process") +local shell = require("shell") +local term = require("term") +local text = require("text") +local tx = require("transforms") +local unicode = require("unicode") + +local sh = {} +sh.internal = {} + +-- --[[@@]] are not just comments, but custom annotations for delayload methods. +-- See package.lua and the api wiki for more information +local function isWordOf(w, vs) return w and #w == 1 and not w[1].qr and tx.first(vs,{{w[1].txt}}) ~= nil end +local function isWord(w,v) return isWordOf(w,{v}) end +local local_env = {event=event,fs=fs,process=process,shell=shell,term=term,text=text,tx=tx,unicode=unicode,isWordOf=isWordOf,isWord=isWord} + +------------------------------------------------------------------------------- + +--SH API + +sh.internal.globbers = {{"*",".*"},{"?","."}} +sh.internal.ec = {} +sh.internal.ec.parseCommand = 127 +sh.internal.ec.last = 0 + +function sh.getLastExitCode() + return sh.internal.ec.last +end + +function sh.internal.command_passed(ec) + local code = sh.internal.command_result_as_code(ec) + return code == 0 +end + +function sh.internal.command_result_as_code(ec) + -- convert lua result to bash ec + if ec == false then + return 1 + elseif ec == nil or ec == true then + return 0 + elseif type(ec) ~= "number" then + return 2 -- illegal number + else + return ec + end +end + +function sh.internal.resolveActions(input, resolver, resolved) + checkArg(1, input, "string") + checkArg(2, resolver, "function", "nil") + checkArg(3, resolved, "table", "nil") + resolver = resolver or shell.getAlias + resolved = resolved or {} + + local processed = {} + + local prev_was_delim, simple = true, true + local words, reason = text.internal.tokenize(input) + + if not words then + return nil, reason + end + + while #words > 0 do + local next = table.remove(words,1) + if isWordOf(next, {";","&&","||","|"}) then + prev_was_delim,simple = true,false + resolved = {} + elseif prev_was_delim then + prev_was_delim = false + -- if current is actionable, resolve, else pop until delim + if next and #next == 1 and not next[1].qr then + local key = next[1].txt + if key == "!" then + prev_was_delim,simple = true,false -- special redo + elseif not resolved[key] then + resolved[key] = resolver(key) + local value = resolved[key] + if value and key ~= value then + local replacement_tokens, reason = sh.internal.resolveActions(value, resolver, resolved) + if not replacement_tokens then + return replacement_tokens, reason + end + simple = simple and reason + words = tx.concat(replacement_tokens, words) + next = table.remove(words,1) + end + end + end + end + + table.insert(processed, next) + end + + return processed, simple +end + +function sh.internal.statements(input) + checkArg(1, input, "string") + + local words, reason = sh.internal.resolveActions(input) + if type(words) ~= "table" then + return words, reason + elseif #words == 0 then + return true + elseif reason and not input:find("[<>]") then + return {words}, reason + end + + -- we shall validate pipes before any statement execution + local statements = sh.internal.splitStatements(words) + for i=1,#statements do + local ok, why = sh.internal.hasValidPiping(statements[i]) + if not ok then return nil,why end + end + return statements +end + +-- returns true if key is a string that represents a valid command line identifier +function sh.internal.isIdentifier(key) + if type(key) ~= "string" then + return false + end + + return key:match("^[%a_][%w_]*$") == key +end + +function sh.expand(value) + local expanded = value + :gsub("%$([_%w%?]+)", function(key) + if key == "?" then + return tostring(sh.getLastExitCode()) + end + return os.getenv(key) or '' + end) + :gsub("%${(.*)}", function(key) + if sh.internal.isIdentifier(key) then + return sh.internal.expandKey(key) + end + io.stderr:write("${" .. key .. "}: bad substitution\n") + os.exit(1) + end) + if expanded:find('`') then + expanded = sh.internal.parse_sub(expanded) + end + return expanded +end + +function sh.internal.expand(word) + if #word == 0 then return {} end + local result = '' + for i=1,#word do + local part = word[i] + -- sh.expand runs command substitution on backticks + -- if the entire quoted area is backtick quoted, then + -- we can save some checks by adding them back in + local q = part.qr and part.qr[1] == '`' and '`' or '' + result = result .. (not (part.qr and part.qr[3]) and sh.expand(q..part.txt..q) or part.txt) + end + return {result} +end + +-- expand to files in path, or try key substitution +-- word is a list of metadata-filled word parts +-- note: text.internal.words(string) returns an array of these words +function sh.internal.evaluate(word) + checkArg(1, word, "table") + if #word == 0 then + return {} + elseif #word == 1 and word[1].qr then + return sh.internal.expand(word) + end + local function make_pattern(seg) + local result = seg + for _,glob_rule in ipairs(sh.internal.globbers) do + result = result:gsub("%%%"..glob_rule[1], glob_rule[2]) + local reduced = result + repeat + result = reduced + reduced = result:gsub(text.escapeMagic(glob_rule[2]):rep(2), glob_rule[2]) + until reduced == result + end + return result + end + local glob_pattern = '' + local has_globits = false + for i=1,#word do local part = word[i] + local next = part.txt + if not part.qr then + local escaped = text.escapeMagic(next) + next = make_pattern(escaped) + if next ~= escaped then + has_globits = true + end + end + glob_pattern = glob_pattern .. next + end + if not has_globits then + return sh.internal.expand(word) + end + local globs = sh.internal.glob(glob_pattern) + return #globs == 0 and sh.internal.expand(word) or globs +end + +function sh.hintHandler(full_line, cursor) + return sh.internal.hintHandlerImpl(full_line, cursor) +end + +function sh.internal.parseCommand(words) + checkArg(1, words, "table") + if #words == 0 then + return nil + end + -- evaluated words + local ewords = {} + -- the arguments have < or > which require parsing for redirection + local has_tokens + for i=1,#words do + for _, arg in ipairs(sh.internal.evaluate(words[i])) do + table.insert(ewords, arg) + has_tokens = has_tokens or arg:find("[<>]") + end + end + return table.remove(ewords, 1), ewords, has_tokens +end + +function sh.internal.createThreads(commands, env, start_args) + -- Piping data between programs works like so: + -- program1 gets its output replaced with our custom stream. + -- program2 gets its input replaced with our custom stream. + -- repeat for all programs + -- custom stream triggers execution of "next" program after write. + -- custom stream triggers yield before read if buffer is empty. + -- custom stream may have "redirect" entries for fallback/duplication. + local threads = {} + for i = 1, #commands do + local program, c_args, c_has_tokens = table.unpack(commands[i]) + local name, thread = tostring(program) + local thread_env = type(program) == "string" and env or nil + local thread, reason = process.load(program, thread_env, function(...) + local cdata = process.info().data.command + local args, has_tokens, start_args = cdata.args, cdata.has_tokens, cdata.start_args + if has_tokens then + args = sh.internal.buildCommandRedirects(args) + end + + sh.internal.concatn(args, start_args) + sh.internal.concatn(args, {...}, select('#', ...)) + + -- popen expects each process to first write an empty string + -- this is required for proper thread order + io.write("") + return table.unpack(args, 1, args.n or #args) + end, name) + + threads[i] = thread + + if not thread then + for i,t in ipairs(threads) do + process.internal.close(t) + end + return nil, reason + end + + local pdata = process.info(thread).data + pdata.command = + { + args = c_args, + has_tokens = c_has_tokens, + start_args = start_args and start_args[i] or {} + } + + end + + if #threads > 1 then + sh.internal.buildPipeChain(threads) + end + + return threads +end + +function sh.internal.runThreads(threads) + local result = {} + for i = 1, #threads do + -- Emulate CC behavior by making yields a filtered event.pull() + local thread, args = threads[i], {} + while coroutine.status(thread) ~= "dead" do + result = table.pack(coroutine.resume(thread, table.unpack(args))) + if coroutine.status(thread) ~= "dead" then + args = sh.internal.handleThreadYield(result) + if table.remove(args, 1) then + -- in case this was the end of the line, args is returned + return args[2] + end + elseif not result[1] then + io.stderr:write(result[2]) + end + end + end + return result[2] +end + +function sh.internal.executePipes(pipe_parts, eargs, env) + local commands = {} + for i=1,#pipe_parts do + commands[i] = table.pack(sh.internal.parseCommand(pipe_parts[i])) + if commands[i][1] == nil then + local err = commands[i][2] + if type(err) == "string" then + io.stderr:write(err,"\n") + end + return sh.internal.ec.parseCommand + end + end + local threads, reason = sh.internal.createThreads(commands, env, {[#commands]=eargs}) + if not threads then + io.stderr:write(reason,"\n") + return false + end + return sh.internal.runThreads(threads) +end + +function sh.execute(env, command, ...) + checkArg(2, command, "string") + if command:find("^%s*#") then return true, 0 end + local statements, reason = sh.internal.statements(command) + if not statements or statements == true then + return statements, reason + elseif #statements == 0 then + return true, 0 + end + + -- MUST be table.pack for non contiguous ... + local eargs = table.pack(...) + + -- simple + if reason then + sh.internal.ec.last = sh.internal.command_result_as_code(sh.internal.executePipes(statements, eargs, env)) + return true + end + + return sh.internal.execute_complex(statements, eargs, env) +end + +function sh.internal.concatn(apack, bpack, bn) + local an = (apack.n or #apack) + bn = bn or bpack.n or #bpack + for i=1,bn do + apack[an + i] = bpack[i] + end + apack.n = an + bn +end + +function --[[@delayloaded-start@]] sh.internal.handleThreadYield(result) + local action = result[2] + if action == nil or type(action) == "number" then + return table.pack(pcall(event.pull, table.unpack(result, 2, result.n))) + else + return table.pack(coroutine.yield(table.unpack(result, 2, result.n))) + end +end --[[@delayloaded-end@]] + +function --[[@delayloaded-start@]] sh.internal.buildCommandRedirects(args, thread) + local data = process.info(thread).data + local tokens, ios, handles = args, data.io, data.handles + args = {} + local from_io, to_io, mode + for i = 1, #tokens do + local token = tokens[i] + if token == "<" then + from_io = 0 + mode = "r" + else + local first_index, last_index, from_io_txt, mode_txt, to_io_txt = token:find("(%d*)(>>?)(.*)") + if mode_txt then + mode = mode_txt == ">>" and "a" or "w" + from_io = from_io_txt and tonumber(from_io_txt) or 1 + if to_io_txt ~= "" then + to_io = tonumber(to_io_txt:sub(2)) + ios[from_io] = ios[to_io] + mode = nil + end + else -- just an arg + if not mode then + table.insert(args, token) + else + local file, reason = io.open(shell.resolve(token), mode) + if not file then + io.stderr:write("could not open '" .. token .. "': " .. reason .. "\n") + os.exit(1) + end + table.insert(handles, file) + ios[from_io] = file + end + mode = nil + end + end + end + + return args +end --[[@delayloaded-end@]] + +function --[[@delayloaded-start@]] sh.internal.buildPipeChain(threads) + local prev_pipe + for i=1,#threads do + local thread = threads[i] + local data = process.info(thread).data + local pio = data.io + + local pipe + if i < #threads then + pipe = require("buffer").new("rw", sh.internal.newMemoryStream()) + pipe:setvbuf("no", 0) + -- buffer close flushes the buffer, but we have no buffer + -- also, when the buffer is closed, read and writes don't pass through + -- simply put, we don't want buffer:close + pipe.close = function(self) self.stream:close() end + pipe.stream.redirect[1] = rawget(pio, 1) + pio[1] = pipe + table.insert(data.handles, pipe) + end + + if prev_pipe then + prev_pipe.stream.redirect[0] = rawget(pio, 0) + prev_pipe.stream.next = thread + pio[0] = prev_pipe + end + + prev_pipe = pipe + end +end --[[@delayloaded-end@]] + +function --[[@delayloaded-start@]] sh.internal.glob(glob_pattern) + local segments = text.split(glob_pattern, {"/"}, true) + local hiddens = tx.foreach(segments,function(e)return e:match("^%%%.")==nil end) + local function is_visible(s,i) + return not hiddens[i] or s:match("^%.") == nil + end + + local function magical(s) + for _,glob_rule in ipairs(sh.internal.globbers) do + if (" "..s):match("[^%%]"..text.escapeMagic(glob_rule[2])) then + return true + end + end + end + + local is_abs = glob_pattern:sub(1, 1) == "/" + local root = is_abs and '' or shell.getWorkingDirectory():gsub("([^/])$","%1/") + local paths = {is_abs and "/" or ''} + local relative_separator = '' + for i,segment in ipairs(segments) do + local enclosed_pattern = string.format("^(%s)/?$", segment) + local next_paths = {} + for _,path in ipairs(paths) do + if fs.isDirectory(root..path) then + if magical(segment) then + for file in fs.list(root..path) do + if file:match(enclosed_pattern) and is_visible(file, i) then + table.insert(next_paths, path..relative_separator..file:gsub("/+$",'')) + end + end + else -- not a globbing segment, just use it raw + local plain = text.removeEscapes(segment) + local fpath = root..path..relative_separator..plain + local hit = path..relative_separator..plain:gsub("/+$",'') + if fs.exists(fpath) then + table.insert(next_paths, hit) + end + end + end + end + paths = next_paths + if not next(paths) then break end + relative_separator = "/" + end + -- if no next_paths were hit here, the ENTIRE glob value is not a path + return paths +end --[[@delayloaded-end@]] + +function --[[@delayloaded-start@]] sh.getMatchingPrograms(baseName) + local result = {} + local result_keys = {} -- cache for fast value lookup + -- TODO only matching files with .lua extension for now, might want to + -- extend this to other extensions at some point? env var? file attrs? + if not baseName or #baseName == 0 then + baseName = "^(.*)%.lua$" + else + baseName = "^(" .. text.escapeMagic(baseName) .. ".*)%.lua$" + end + for basePath in string.gmatch(os.getenv("PATH"), "[^:]+") do + for file in fs.list(shell.resolve(basePath)) do + local match = file:match(baseName) + if match and not result_keys[match] then + table.insert(result, match) + result_keys[match] = true + end + end + end + return result +end --[[@delayloaded-end@]] + +function --[[@delayloaded-start@]] sh.getMatchingFiles(partial_path) + -- name: text of the partial file name being expanded + local name = partial_path:gsub("^.*/", "") + -- here we remove the name text from the partialPrefix + local basePath = unicode.sub(partial_path, 1, -unicode.len(name) - 1) + + local resolvedPath = shell.resolve(basePath) + local result, baseName = {} + + -- note: we strip the trailing / to make it easier to navigate through + -- directories using tab completion (since entering the / will then serve + -- as the intention to go into the currently hinted one). + -- if we have a directory but no trailing slash there may be alternatives + -- on the same level, so don't look inside that directory... (cont.) + if fs.isDirectory(resolvedPath) and name == "" then + baseName = "^(.-)/?$" + else + baseName = "^(" .. text.escapeMagic(name) .. ".-)/?$" + end + + for file in fs.list(resolvedPath) do + local match = file:match(baseName) + if match then + table.insert(result, basePath .. match:gsub("(%s)", "\\%1")) + end + end + -- (cont.) but if there's only one match and it's a directory, *then* we + -- do want to add the trailing slash here. + if #result == 1 and fs.isDirectory(shell.resolve(result[1])) then + result[1] = result[1] .. "/" + end + return result +end --[[@delayloaded-end@]] + +function --[[@delayloaded-start@]] sh.internal.hintHandlerSplit(line) + -- I do not plan on having text tokenizer parse error on + -- trailiing \ in case of future support for multiple line + -- input. But, there are also no hints for it + if line:match("\\$") then return nil end + + local splits, simple = text.internal.tokenize(line,{show_escapes=true}) + if not splits then -- parse error, e.g. unclosed quotes + return nil -- no split, no hints + end + + local num_splits = #splits + + -- search for last statement delimiters + local last_close = 0 + for index = num_splits, 1, -1 do + local word = splits[index] + if isWordOf(word, {";","&&","||","|"}) then + last_close = index + break + end + end + + -- if the very last word of the line is a delimiter + -- consider this a fresh new, empty line + -- this captures edge cases with empty input as well (i.e. no splits) + if last_close == num_splits then + return nil -- no hints on empty command + end + + local last_word = splits[num_splits] + local normal = text.internal.normalize({last_word})[1] + + -- if there is white space following the words + -- and we have at least one word following the last delimiter + -- then in all cases we are looking for ANY arg + if unicode.sub(line, -unicode.len(normal)) ~= normal then + return line, nil, "" + end + + local prefix = unicode.sub(line, 1, -unicode.len(normal) - 1) + + -- renormlizing the string will create 'printed' quality text + normal = text.internal.normalize(text.internal.tokenize(normal), true)[1] + + -- one word: cmd + -- many: arg + if last_close == num_splits - 1 then + return prefix, normal, nil + else + return prefix, nil, normal + end +end --[[@delayloaded-end@]] + +function --[[@delayloaded-start@]] sh.internal.hintHandlerImpl(full_line, cursor) + -- line: text preceding the cursor: we want to hint this part (expand it) + local line = unicode.sub(full_line, 1, cursor - 1) + -- suffix: text following the cursor (if any, else empty string) to append to the hints + local suffix = unicode.sub(full_line, cursor) + + -- hintHandlerSplit helps make the hints work even after delimiters such as ; + -- it also catches parse errors such as unclosed quotes + -- prev: not needed for this hint + -- cmd: the command needing hint + -- arg: the argument needing hint + local prev, cmd, arg = sh.internal.hintHandlerSplit(line) + + -- also, if there is no text to hint, there are no hints + if not prev then -- no hints e.g. unclosed quote, e.g. no text + return {} + end + local result + + local searchInPath = cmd and not cmd:find("/") + if searchInPath then + result = sh.getMatchingPrograms(cmd) + else + -- special arg issue, after equal sign + if arg then + local equal_index = arg:find("=[^=]*$") + if equal_index then + prev = prev .. unicode.sub(arg, 1, equal_index) + arg = unicode.sub(arg, equal_index + 1) + end + end + result = sh.getMatchingFiles(cmd or arg) + end + + -- in very special cases, the suffix should include a blank space to indicate to the user that the hint is discrete + local resultSuffix = suffix + if #result > 0 and unicode.sub(result[1], -1) ~= "/" and + not suffix:sub(1,1):find('%s') and + #result == 1 or searchInPath then + resultSuffix = " " .. resultSuffix + end + + table.sort(result) + for i = 1, #result do + -- the hints define the whole line of text + result[i] = prev .. result[i] .. resultSuffix + end + return result +end --[[@delayloaded-end@]] + +-- verifies that no pipes are doubled up nor at the start nor end of words +function --[[@delayloaded-start@]] sh.internal.hasValidPiping(words, pipes) + checkArg(1, words, "table") + checkArg(2, pipes, "table", "nil") + + if #words == 0 then + return true + end + + local semi_split = tx.find(text.syntax, {";"}) -- all symbols before ; in syntax CAN be repeated + pipes = pipes or tx.sub(text.syntax, semi_split + 1) + + local state = "" -- cannot start on a pipe + + for w=1,#words do + local word = words[w] + for p=1,#word do + local part = word[p] + if part.qr then + state = nil + elseif part.txt == "" then + state = nil -- not sure how this is possible (empty part without quotes?) + elseif #text.split(part.txt, pipes, true) == 0 then + local prev = state + state = part.txt + if prev then -- cannot have two pipes in a row + word = nil + break + end + else + state = nil + end + end + if not word then -- bad pipe + break + end + end + + if state then + return false, "parse error near " .. state + else + return true + end +end --[[@delayloaded-end@]] + +function --[[@delayloaded-start@]] sh.internal.boolean_executor(chains, predicator) + local function not_gate(result) + return sh.internal.command_passed(result) and 1 or 0 + end + + local last = true + local boolean_stage = 1 + local negation_stage = 2 + local command_stage = 0 + local stage = negation_stage + local skip = false + + for ci=1,#chains do + local next = chains[ci] + local single = #next == 1 and #next[1] == 1 and not next[1][1].qr and next[1][1].txt + + if single == "||" then + if stage ~= command_stage or #chains == 0 then + return nil, "syntax error near unexpected token '"..single.."'" + end + if sh.internal.command_passed(last) then + skip = true + end + stage = boolean_stage + elseif single == "&&" then + if stage ~= command_stage or #chains == 0 then + return nil, "syntax error near unexpected token '"..single.."'" + end + if not sh.internal.command_passed(last) then + skip = true + end + stage = boolean_stage + elseif not skip then + local chomped = #next + local negate = sh.internal.remove_negation(next) + chomped = chomped ~= #next + if negate then + local prev = predicator + predicator = function(n,i) + local result = not_gate(prev(n,i)) + predicator = prev + return result + end + end + if chomped then + stage = negation_stage + end + if #next > 0 then + last = predicator(next,ci) + stage = command_stage + end + else + skip = false + stage = command_stage + end + end + + if stage == negation_stage then + last = not_gate(last) + end + + return last +end --[[@delayloaded-end@]] + +function --[[@delayloaded-start@]] sh.internal.splitStatements(words, semicolon) + checkArg(1, words, "table") + checkArg(2, semicolon, "string", "nil") + semicolon = semicolon or ";" + + return tx.partition(words, function(g, i, t) + if isWord(g,semicolon) then + return i, i + end + end, true) +end --[[@delayloaded-end@]] + +function --[[@delayloaded-start@]] sh.internal.splitChains(s,pc) + checkArg(1, s, "table") + checkArg(2, pc, "string", "nil") + pc = pc or "|" + return tx.partition(s, function(w) + -- each word has multiple parts due to quotes + if isWord(w,pc) then + return true + end + end, true) -- drop |s +end --[[@delayloaded-end@]] + +function --[[@delayloaded-start@]] sh.internal.groupChains(s) + checkArg(1,s,"table") + return tx.partition(s,function(w)return isWordOf(w,{"&&","||"})end) +end --[[@delayloaded-end@]] + +function --[[@delayloaded-start@]] sh.internal.remove_negation(chain) + if isWord(chain[1],"!") then + table.remove(chain, 1) + return true and not sh.internal.remove_negation(chain) + end + return false +end --[[@delayloaded-end@]] + +function --[[@delayloaded-start@]] sh.internal.newMemoryStream() + local memoryStream = {} + + function memoryStream:close() + self.closed = true + self.redirect = {} + end + + function memoryStream:seek() + return nil, "bad file descriptor" + end + + function memoryStream:read(n) + if self.closed then + return nil -- eof + end + if self.redirect[0] then + -- popen could be using this code path + -- if that is the case, it is important to leave stream.buffer alone + return self.redirect[0]:read(n) + elseif self.buffer == "" then + coroutine.yield() + end + local result = string.sub(self.buffer, 1, n) + self.buffer = string.sub(self.buffer, n + 1) + return result + end + + function memoryStream:write(value) + if not self.redirect[1] and self.closed then + -- if next is dead, ignore all writes + if coroutine.status(self.next) ~= "dead" then + io.stderr:write("attempt to use a closed stream\n") + os.exit(1) + end + elseif self.redirect[1] then + return self.redirect[1]:write(value) + elseif not self.closed then + self.buffer = self.buffer .. value + local result = table.pack(coroutine.resume(self.next)) + if coroutine.status(self.next) == "dead" then + self:close() + end + if not result[1] then + io.stderr:write(tostring(result[2]) .. "\n") + os.exit(1) + end + return self + end + os.exit(0) -- abort the current process: SIGPIPE + end + + local stream = {closed = false, buffer = "", + redirect = {}, result = {}} + local metatable = {__index = memoryStream, + __metatable = "memorystream"} + return setmetatable(stream, metatable) +end --[[@delayloaded-end@]] + +function --[[@delayloaded-start@]] sh.internal.execute_complex(statements, eargs, env) + for si=1,#statements do local s = statements[si] + local chains = sh.internal.groupChains(s) + local last_code = sh.internal.boolean_executor(chains, function(chain, chain_index) + local pipe_parts = sh.internal.splitChains(chain) + local next_args = chain_index == #chains and si == #statements and eargs or {} + return sh.internal.executePipes(pipe_parts, next_args, env) + end) + sh.internal.ec.last = sh.internal.command_result_as_code(last_code) + end + return true +end --[[@delayloaded-end@]] + + +function --[[@delayloaded-start@]] sh.internal.parse_sub(input) + -- cannot use gsub here becuase it is a [C] call, and io.popen needs to yield at times + local packed = {} + -- not using for i... because i can skip ahead + local i, len = 1, #input + + while i < len do + + local fi, si, capture = input:find("`([^`]*)`", i) + + if not fi then + table.insert(packed, input:sub(i)) + break + end + + local sub = io.popen(capture) + local result = input:sub(i, fi - 1) .. sub:read("*a") + sub:close() + -- all whitespace is replaced by single spaces + -- we requote the result because tokenize will respect this as text + table.insert(packed, (text.trim(result):gsub("%s+"," "))) + + i = si+1 + end + + return table.concat(packed) +end --[[@delayloaded-end@]] + +return sh, local_env diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/shell.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/shell.lua new file mode 100755 index 00000000..0b13e5f0 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/shell.lua @@ -0,0 +1,223 @@ +local fs = require("filesystem") +local text = require("text") +local unicode = require("unicode") +local process = require("process") + +local shell = {} + +-- Cache loaded shells for command execution. This puts the requirement on +-- shells that they do not keep a global state, since they may be called +-- multiple times, but reduces memory usage a lot. +local shells = setmetatable({}, {__mode="v"}) + +function shell.getShell() + local shellPath = os.getenv("SHELL") or "/bin/sh" + local shellName, reason = shell.resolve(shellPath, "lua") + if not shellName then + return nil, "cannot resolve shell `" .. shellPath .. "': " .. reason + end + if shells[shellName] then + return shells[shellName] + end + local sh, reason = loadfile(shellName, "t") + if sh then + shells[shellName] = sh + end + return sh, reason +end + +local function findFile(name, ext) + checkArg(1, name, "string") + local function findIn(dir) + if dir:sub(1, 1) ~= "/" then + dir = shell.resolve(dir) + end + dir = fs.concat(fs.concat(dir, name), "..") + local name = fs.name(name) + local list = fs.list(dir) + if list and name then + local files = {} + for file in list do + files[file] = true + end + if ext and unicode.sub(name, -(1 + unicode.len(ext))) == "." .. ext then + -- Name already contains extension, prioritize. + if files[name] then + return true, fs.concat(dir, name) + end + elseif files[name] then + -- Check exact name. + return true, fs.concat(dir, name) + elseif ext then + -- Check name with automatially added extension. + local name = name .. "." .. ext + if files[name] then + return true, fs.concat(dir, name) + end + end + end + return false + end + if unicode.sub(name, 1, 1) == "/" then + local found, where = findIn("/") + if found then return where end + elseif unicode.sub(name, 1, 2) == "./" then + local found, where = findIn(shell.getWorkingDirectory()) + if found then return where end + else + for path in string.gmatch(shell.getPath(), "[^:]+") do + local found, where = findIn(path) + if found then return where end + end + end + return false +end + +------------------------------------------------------------------------------- + +function shell.prime() + local data = process.info().data + for _,key in ipairs({'aliases','vars'}) do + -- first time get need to populate + local raw = rawget(data, key) + if not raw then + -- current process does not have the key + local current = data[key] + data[key] = {} + if current then + for k,v in pairs(current) do + data[key][k] = v + end + end + end + end +end + +function shell.getAlias(alias) + return process.info().data.aliases[alias] +end + +function shell.setAlias(alias, value) + checkArg(1, alias, "string") + checkArg(2, value, "string", "nil") + process.info().data.aliases[alias] = value +end + +function shell.aliases() + return pairs(process.info().data.aliases) +end + +function shell.resolveAlias(command, args) + checkArg(1, command, "string") + checkArg(2, args, "table", "nil") + args = args or {} + local program, lastProgram = command, nil + while true do + local tokens = text.tokenize(shell.getAlias(program) or program) + program = tokens[1] + if program == lastProgram then + break + end + lastProgram = program + for i = #tokens, 2, -1 do + table.insert(args, 1, tokens[i]) + end + end + return program, args +end + +function shell.getWorkingDirectory() + -- if no env PWD default to / + return os.getenv("PWD") or "/" +end + +function shell.setWorkingDirectory(dir) + checkArg(1, dir, "string") + -- ensure at least / + -- and remove trailing / + dir = fs.canonical(dir):gsub("^$", "/"):gsub("(.)/$", "%1") + if fs.isDirectory(dir) then + os.setenv("PWD", dir) + return true + else + return nil, "not a directory" + end +end + +function shell.getPath() + return os.getenv("PATH") +end + +function shell.setPath(value) + os.setenv("PATH", value) +end + +function shell.resolve(path, ext) + if ext then + checkArg(2, ext, "string") + local where = findFile(path, ext) + if where then + return where + else + return nil, "file not found" + end + else + if unicode.sub(path, 1, 1) == "/" then + return fs.canonical(path) + else + return fs.concat(shell.getWorkingDirectory(), path) + end + end +end + +function shell.execute(command, env, ...) + local sh, reason = shell.getShell() + if not sh then + return false, reason + end + local result = table.pack(coroutine.resume(process.load(function(...) + return sh(...) + end), env, command, ...)) + if not result[1] and type(result[2]) == "table" and result[2].reason == "terminated" then + if result[2].code then + return true + else + return false, "terminated" + end + end + return table.unpack(result, 1, result.n) +end + +function shell.parse(...) + local params = table.pack(...) + local args = {} + local options = {} + local doneWithOptions = false + for i = 1, params.n do + local param = params[i] + if not doneWithOptions and type(param) == "string" then + if param == "--" then + doneWithOptions = true -- stop processing options at `--` + elseif unicode.sub(param, 1, 2) == "--" then + if param:match("%-%-(.-)=") ~= nil then + options[param:match("%-%-(.-)=")] = param:match("=(.*)") + else + options[unicode.sub(param, 3)] = true + end + elseif unicode.sub(param, 1, 1) == "-" and param ~= "-" then + for j = 2, unicode.len(param) do + options[unicode.sub(param, j, j)] = true + end + else + table.insert(args, param) + end + else + table.insert(args, param) + end + end + return args, options +end + +------------------------------------------------------------------------------- + +return shell diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/sides.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/sides.lua new file mode 100755 index 00000000..a17d3f66 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/sides.lua @@ -0,0 +1,62 @@ +local sides = { + [0] = "bottom", + [1] = "top", + [2] = "back", + [3] = "front", + [4] = "right", + [5] = "left", + [6] = "unknown", + + bottom = 0, + top = 1, + back = 2, + front = 3, + right = 4, + left = 5, + unknown = 6, + + down = 0, + up = 1, + north = 2, + south = 3, + west = 4, + east = 5, + + negy = 0, + posy = 1, + negz = 2, + posz = 3, + negx = 4, + posx = 5, + + forward = 3 +} + +local metatable = getmetatable(sides) or {} + +-- sides[0..5] are mapped to itertable[1..6]. +local itertable = { + sides[0], + sides[1], + sides[2], + sides[3], + sides[4], + sides[5] +} + +-- Future-proofing against the possible introduction of additional +-- logical sides (e.g. [7] = "all", [8] = "none", etc.). +function metatable.__len(sides) + return #itertable +end + +-- Allow `sides` to be iterated over like a normal (1-based) array. +function metatable.__ipairs(sides) + return ipairs(itertable) +end + +setmetatable(sides, metatable) + +------------------------------------------------------------------------------- + +return sides diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/syntax.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/syntax.lua new file mode 100755 index 00000000..26595578 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/syntax.lua @@ -0,0 +1,164 @@ + +require("advancedLua") +local buffer = require("doubleBuffering") +local unicode = require("unicode") + +local syntax = {} + +---------------------------------------------------------------------------------------------------------------------------------------- + +syntax.indentationSeparator = "│" + +syntax.colorScheme = { + background = 0x1E1E1E, + text = 0xEEEEEE, + strings = 0x99FF80, + loops = 0xffff98, + comments = 0x888888, + boolean = 0xFFDB40, + logic = 0xffcc66, + numbers = 0x66DBFF, + functions = 0xffcc66, + compares = 0xffff98, + lineNumbersBackground = 0x2D2D2D, + lineNumbersText = 0xCCCCCC, + scrollBarBackground = 0x2D2D2D, + scrollBarForeground = 0x5A5A5A, + selection = 0x555555, + indentation = 0x2D2D2D, +} + +syntax.patterns = { + -- Комментарии + { "%-%-.+", "comments", 0, 0 }, + + -- Строки + { "\"[^\"]+\"", "strings", 0, 0 }, + + -- Циклы, условия и прочая поебень + { "while ", "loops", 0, 1 }, + { "do$", "loops", 0, 0 }, + { "do ", "loops", 0, 1 }, + { "end$", "loops", 0, 0 }, + { "end[%s%;]", "loops", 0, 1 }, + { "for ", "loops", 0, 1 }, + { " in ", "loops", 0, 1 }, + { "repeat$", "loops", 0, 0 }, + { "if ", "loops", 0, 1 }, + { "then", "loops", 0, 0 }, + { "until ", "loops", 0, 1 }, + { "return", "loops", 0, 0 }, + { "local ", "loops", 0, 1 }, + { "function ", "loops", 0, 1 }, + { "else$", "loops", 0, 0 }, + { "else[%s%;]", "loops", 0, 1 }, + { "elseif ", "loops", 0, 1 }, + { " break$", "loops", 0, 0 }, + { " break ", "loops", 0, 0 }, + + -- Истина, ложь, нулл + { "true", "boolean", 0, 0 }, + { "false", "boolean", 0, 0 }, + { "nil", "boolean", 0, 0 }, + + --Функции + { "[%s%=%{%(][^%s%(%)%{%}%[%]]+%(", "functions", 1, 1 }, + { "^[^%s%(%)%{%}%[%]]+%(", "functions", 0, 1 }, + + -- Логические выражения + { " and ", "logic", 0, 1 }, + { " or ", "logic", 0, 1 }, + { " not ", "logic", 0, 1 }, + + -- Конкатенация строк + { "[^%d]%.+[^%d]", "logic", 1, 1 }, + + -- Сравнения и мат. операции + { "[%>%<%=%~%+%-%*%/%^%#%%]", "compares", 0, 0 }, + + -- Числа + { "0x%w+", "numbers", 0, 0 }, + { "[^%a%d][%.%d]+$", "numbers", 1, 0 }, + { "[^%a%d][%.%d]+[^%a%d]", "numbers", 1, 1 }, +} + +---------------------------------------------------------------------------------------------------------------------------------------- + +-- Отрисовка строки с подсвеченным синтаксисом +function syntax.highlightString(x, y, str, indentationWidth) + local x1, y1, x2, y2 = buffer.getDrawLimit() + + if y >= y1 and y <= y2 then + local stringLength, symbols, colors, searchFrom, starting, ending, bufferIndex, background = unicode.len(str), {}, {} + + for symbol = 1, stringLength do + symbols[symbol] = unicode.sub(str, symbol, symbol) + end + + for patternIndex = #syntax.patterns, 1, -1 do + searchFrom = 1 + while true do + starting, ending = string.unicodeFind(str, syntax.patterns[patternIndex][1], searchFrom) + if starting then + for symbol = starting + syntax.patterns[patternIndex][3], ending - syntax.patterns[patternIndex][4] do + colors[symbol] = syntax.colorScheme[syntax.patterns[patternIndex][2]] + end + else + break + end + searchFrom = ending + 1 - syntax.patterns[patternIndex][4] + end + end + + local notSpaceNotFound, indentationSymbolCounter = true, 1 + + for symbol = 1, stringLength do + if notSpaceNotFound then + if symbols[symbol] == " " then + colors[symbol] = syntax.colorScheme.indentation + if indentationSymbolCounter == 1 then + symbols[symbol] = syntax.indentationSeparator + indentationSymbolCounter = indentationWidth + 1 + end + else + notSpaceNotFound = false + end + indentationSymbolCounter = indentationSymbolCounter - 1 + end + + if x > x2 then + break + elseif x >= x1 then + bufferIndex = bufferIndex or buffer.getIndex(x, y) + background = buffer.rawGet(bufferIndex) + buffer.rawSet(bufferIndex, background, colors[symbol] or syntax.colorScheme.text, symbols[symbol]) + + bufferIndex = bufferIndex + 3 + end + + x = x + 1 + end + end +end + +---------------------------------------------------------------------------------------------------------------- + +-- buffer.start() +-- buffer.clear(0x1b1b1b) + +-- buffer.square(5, 5, 30, 3, syntax.colorScheme.background, 0x0, " ") +-- -- syntax.highlightString(5, 6, "if not fs.exists(path) then error(\"File \\\"\"..path..\"\\\" doesnt't exsists.\\n\") end") +-- syntax.highlightString(5, 6, "for i = 1, 10 do", 2) +-- syntax.highlightString(5, 7, " local abc = print(123)", 2) +-- syntax.highlightString(5, 8, " local abc = print(123)", 2) +-- syntax.highlightString(5, 9, "end", 2) + +-- buffer.draw(true) + +---------------------------------------------------------------------------------------------------------------- + +return syntax + + + + diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/term.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/term.lua new file mode 100755 index 00000000..b936ac26 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/term.lua @@ -0,0 +1,732 @@ +local unicode = require("unicode") +local event = require("event") +local process = require("process") +local kb = require("keyboard") +local component = require("component") +local computer = require("computer") +local keys = kb.keys + +local term = {} +term.internal = {} + +function term.internal.window() + return process.info().data.window +end + +local W = term.internal.window +local local_env = {unicode=unicode,event=event,process=process,W=W,kb=kb} + +local gpu_intercept = {} +local function update_viewport(window, width, height) + window = window or W() + local gpu = window.gpu + if not gpu then return end + if not gpu_intercept[gpu] then + gpu_intercept[gpu] = {} -- only override a gpu once + -- the gpu can change resolution before we get a chance to call events and handle screen_resized + -- unfortunately, we have to handle viewport changes by intercept + local setr, setv = gpu.setResolution, gpu.setViewport + gpu.setResolution = function(...) + gpu_intercept[gpu] = {} + return setr(...) + end + gpu.setViewport = function(...) + gpu_intercept[gpu] = {} + return setv(...) + end + end + if not width and not gpu_intercept[gpu][window] then + width, height = gpu.getViewport() + end + if width then + window:resize(width, height) + gpu_intercept[gpu][window] = true + end +end + +local function resize(window, width, height) + window.w, window.h = width, height +end + +function term.internal.open(...) + local dx, dy, w, h = ... + local window = {x=1,y=1,fullscreen=select("#",...)==0,dx=dx or 0,dy=dy or 0,w=w,h=h,blink=true,resize=resize} + event.listen("screen_resized", function(_,addr,w,h) + if term.isAvailable(window) and term.screen(window) == addr and window.fullscreen then + update_viewport(window, w, h) + end + end) + return window +end + +function term.getViewport(window) + window = window or W() + update_viewport(window) + return window.w, window.h, window.dx, window.dy, window.x, window.y +end + +function term.setViewport(w,h,dx,dy,x,y,window) + window = window or W() + + dx,dy,x,y = dx or 0,dy or 0,x or 1,y or 1 + if not w or not h then + local gw,gh = window.gpu.getViewport() + w,h = w or gw, h or gh + end + + window.dx,window.dy,window.x,window.y,window.gw,window.gh = dx, dy, x, y, gw, gh + update_viewport(window, w, h) +end + +function term.gpu(window) + window = window or W() + return window.gpu +end + +function term.clear() + local w = W() + local gpu = w.gpu + if not gpu then return end + gpu.fill(1+w.dx,1+w.dy,w.w,w.h," ") + w.x,w.y=1,1 +end + +function term.isAvailable(w) + w = w or W() + return w and not not (w.gpu and w.gpu.getScreen()) +end + +function term.internal.pull(input, timeout, ...) + timeout = timeout or math.huge + + local w = W() + local d, h, dx, dy, x, y = term.getViewport(w) + local out = (x<1 or x>d or y<1 or y>h) + + if input and out then + input:move(0) + y = w.y + input:scroll() + end + + x, y = w.x + dx, w.y + dy + local gpu = (input or not out) and w.gpu + + local bgColor, bgIsPalette + local fgColor, fgIsPalette + local char_at_cursor + local blinking + if gpu then + bgColor, bgIsPalette = gpu.getBackground() + -- it can happen during a type of race condition when a screen is removed + if not bgColor then + return nil, "interrupted" + end + + fgColor, fgIsPalette = gpu.getForeground() + char_at_cursor = gpu.get(x, y) + + blinking = w.blink + if input then + blinking = input.blink + end + end + + -- get the next event + local blinked = false + local done = false + local signal + while true do + if gpu then + if not blinked and not done then + gpu.setForeground(bgColor, bgIsPalette) + gpu.setBackground(fgColor, fgIsPalette) + gpu.set(x, y, char_at_cursor) + gpu.setForeground(fgColor, fgIsPalette) + gpu.setBackground(bgColor, bgIsPalette) + blinked = true + elseif blinked then + gpu.set(x, y, char_at_cursor) + blinked = false + end + end + + if done then + return table.unpack(signal, 1, signal.n) + end + + signal = table.pack(event.pull(math.min(.5, timeout), ...)) + timeout = timeout - .5 + done = signal.n > 1 or timeout < .5 + end +end + +function term.pull(...) + local args = table.pack(...) + local timeout = nil + if type(args[1]) == "number" then + timeout = table.remove(args, 1) + args.n = args.n - 1 + end + return term.internal.pull(nil, timeout, table.unpack(args, 1, args.n)) +end + +function term.read(history,dobreak,hintHandler,pwchar,filter) + if not io.stdin.tty then return io.read() end + local ops = history or {} + ops.dobreak = ops.dobreak + if ops.dobreak==nil then ops.dobreak = dobreak end + ops.hintHandler = ops.hintHandler or hintHandler + ops.pwchar = ops.pwchar or pwchar + ops.filter = ops.filter or filter + return term.readKeyboard(ops) +end + +function term.internal.split(input) + local data,index=input.data,input.index + local dlen = unicode.len(data) + index=math.max(0,math.min(index,dlen)) + local tail=dlen-index + return unicode.sub(data,1,index),tail==0 and""or unicode.sub(data,-tail) +end + +function term.internal.build_vertical_reader(input) + input.sy = 0 + input.scroll = function(_) + _.sy = _.sy + term.internal.scroll(_.w) + _.w.y = math.min(_.w.y,_.w.h) + end + input.move = function(_,n) + local w=_.w + _.index = math.min(math.max(0,_.index+n),unicode.len(_.data)) + local s1,s2 = term.internal.split(_) + s2 = unicode.sub(s2.." ",1,1) + local data_remaining = ("_"):rep(_.promptx-1)..s1..s2 + w.y = _.prompty - _.sy + while true do + local wlen_remaining = unicode.wlen(data_remaining) + if wlen_remaining > w.w then + local line_cut = unicode.wtrunc(data_remaining, w.w+1) + data_remaining = unicode.sub(data_remaining,unicode.len(line_cut)+1) + w.y=w.y+1 + else + w.x = wlen_remaining-unicode.wlen(s2)+1 + break + end + end + end + input.clear_tail = function(_) + local win=_.w + local oi,w,h,dx,dy,ox,oy = _.index,term.getViewport(win) + _:move(math.huge) + _:move(-1) + local ex,ey=win.x,win.y + win.x,win.y,_.index=ox,oy,oi + x=oy==ey and ox or 1 + win.gpu.fill(x+dx,ey+dy,w-x+1,1," ") + end + input.update = function(_,arg) + local w,cursor,suffix=_.w + local s1,s2=term.internal.split(_) + if type(arg) == "number" then + local ndata + if arg < 0 then if _.index<=0 then return end + _:move(-1) + ndata=unicode.sub(s1,1,-2)..s2 + else if _.index>=unicode.len(_.data) then return end + s2=unicode.sub(s2,2) + ndata=s1..s2 + end + suffix=s2 + input:clear_tail() + _.data = ndata + else + _.data=s1..arg..s2 + _.index=_.index+unicode.len(arg) + cursor,suffix=arg,s2 + end + if cursor then _:draw(_.mask(cursor)) end + if suffix and suffix~="" then + local px,py,ps=w.x,w.y,_.sy + _:draw(_.mask(suffix)) + w.x,w.y=px,py-(_.sy-ps) + end + end + input.clear = function(_) + _:move(-math.huge) + _:draw((" "):rep(unicode.wlen(_.data))) + _:move(-math.huge) + _.index=0 + _.data="" + end + input.draw = function(_,text) + _.sy = _.sy + term.drawText(text,true) + end +end + +function term.internal.read_history(history,input,change) + if not change then + if unicode.wlen(input.data) > 0 then + table.insert(history.list,1,input.data) + history.list[(tonumber(os.getenv("HISTSIZE")) or 10)+1]=nil + history.list[0]=nil + end + else + local ni = history.index + change + if ni >= 0 and ni <= #history.list then + history.list[history.index]=input.data + history.index = ni + input:clear() + input:update(history.list[ni]) + end + end +end + +function term.readKeyboard(ops) + checkArg(1,ops,"table") + local filter = ops.filter and function(i) return term.internal.filter(ops.filter,i) end or term.internal.nop + local pwchar = ops.pwchar and function(i) return term.internal.mask(ops.pwchar,i) end or term.internal.nop + local history,db,hints={list=ops,index=0},ops.dobreak,{handler=ops.hintHandler} + local w=W() + local draw=io.stdin.tty and term.drawText or term.internal.nop + local input={w=w,promptx=w.x,prompty=w.y,index=0,data="",mask=pwchar} + input.blink = ops.blink + if input.blink == nil then + input.blink = w.blink + end + + -- two wrap types currently supported, vertical and hortizontal + if ops.nowrap then term.internal.build_horizontal_reader(input) + else term.internal.build_vertical_reader(input) + end + + while true do + local name, address, char, code = term.internal.pull(input) + if not term.isAvailable() then + return + end + + -- we have to keep checking what kb is active in case it is switching during use + -- we could have multiple screens, each with keyboards active + local main_kb = term.keyboard(w) + local main_sc = term.screen(w) + local c + local backup_cache = hints.cache + if name == "interrupted" then + draw("^C\n",true) + return false + elseif address == main_kb or address == main_sc then + if name == "touch" or name == "drag" then + term.internal.onTouch(input,char,code) + elseif name == "clipboard" then + c = term.internal.clipboard(char) + hints.cache = nil + elseif name == "key_down" then + hints.cache = nil + local ctrl = kb.isControlDown(address) + if ctrl and code == keys.d then return + elseif char == 9 then + hints.cache = backup_cache + term.internal.tab(input,hints) + elseif char == 13 and filter(input) then + input:move(math.huge) + if db ~= false then + draw("\n") + end + term.internal.read_history(history,input) + return input.data .. "\n" + elseif code == keys.up then term.internal.read_history(history, input, 1) + elseif code == keys.down then term.internal.read_history(history, input, -1) + elseif code == keys.left then input:move(ctrl and term.internal.ctrl_movement(input, -1) or -1) + elseif code == keys.right then input:move(ctrl and term.internal.ctrl_movement(input, 1) or 1) + elseif code == keys.home then input:move(-math.huge) + elseif code == keys["end"] then input:move( math.huge) + elseif code == keys.back then c = -1 + elseif code == keys.delete then c = 0 + elseif char >= 32 then c = unicode.char(char) + elseif ctrl and char == "w"then -- TODO: cut word + else hints.cache = backup_cache -- ignored chars shouldn't clear hint cache + end + end + -- if we obtained something (c) to handle + if c then + input:update(c) + end + end + end +end + +-- cannot use term.write = io.write because io.write invokes metatable +function term.write(value,wrap) + local stdout = io.output() + local stream = stdout and stdout.stream + local previous_wrap = stream.wrap + stream.wrap = wrap == nil and true or wrap + stdout:write(value) + stdout:flush() + stream.wrap = previous_wrap +end + +function term.getCursor() + local w = W() + return w.x,w.y +end + +function term.setCursor(x,y) + local w = W() + w.x,w.y=x,y +end + +function term.drawText(value, wrap, window) + window = window or W() + if not window then return end + local gpu = window.gpu + if not gpu then return end + local w,h,dx,dy,x,y = term.getViewport(window) + local sy = 0 + local vlen = #value + local index = 1 + local cr_last,beeped = false,false + local function scroll(_sy,_y) + return _sy + term.internal.scroll(window,_y-h), math.min(_y,h) + end + local uptime = computer.uptime + local last_sleep = uptime() + while index <= vlen do + if uptime() - last_sleep > 4 then + os.sleep(0) + last_sleep = uptime() + end + local si,ei = value:find("[\t\r\n\a]", index) + si = si or vlen+1 + if index==si then + local delim = value:sub(index, index) + if delim=="\t" then + x=((x-1)-((x-1)%8))+9 + elseif delim=="\r" or (delim=="\n" and not cr_last) then + x,y=1,y+1 + sy,y = scroll(sy,y) + elseif delim=="\a" and not beeped then + computer.beep() + beeped = true + end + cr_last = delim == "\r" + else + sy,y = scroll(sy,y) + si = si - 1 + local next = value:sub(index, si) + local wlen_needed = unicode.wlen(next) + local slen = #next + local wlen_remaining = w - x + 1 + local clean_end = "" + if wlen_remaining < wlen_needed then + next = unicode.wtrunc(next, wlen_remaining + 1) + wlen_needed = unicode.wlen(next) + clean_end = (" "):rep(wlen_remaining-wlen_needed) + if not wrap then + si = math.huge + end + end + gpu.set(x+dx,y+dy,next..clean_end) + x = x + wlen_needed + if wrap and slen ~= #next then + si = si - (slen - #next) + x = 1 + y = y + 1 + end + end + index = si + 1 + end + + window.x,window.y = x,y + return sy +end + +function term.internal.scroll(w,n) + w = w or W() + local gpu,d,h,dx,dy,x,y = w.gpu,term.getViewport(w) + n = n or (y-h) + if n <= 0 then return 0 end + gpu.copy(dx+1,dy+n+1,d,h-n,0,-n) + gpu.fill(dx+1,dy+h-n+1,d,n," ") + return n +end + +function term.internal.nop(...) + return ... +end + +function term.setCursorBlink(enabled) + W().blink=enabled +end + +function term.getCursorBlink() + return W().blink +end + +function term.bind(gpu, window) + window = window or W() + window.gpu = gpu or window.gpu + window.keyboard = nil -- without a keyboard bound, always use the screen's main keyboard (1st) + if window.fullscreen then + term.setViewport(nil,nil,nil,nil,window.x,window.y,window) + end +end + +function term.keyboard(window) + window = window or W() or {} -- this method needs to be safe even if there is no terminal window (e.g. no gpu) + + if window.keyboard then + return window.keyboard + end + + local system_keyboard = component.isAvailable("keyboard") and component.keyboard + system_keyboard = system_keyboard and system_keyboard.address or "no_system_keyboard" + + local screen = term.screen(window) + + if not screen then + -- no screen, no known keyboard, use system primary keyboard if any + return system_keyboard + end + + -- if we are using a gpu bound to the primary scren, then use the primary keyboard + if component.isAvailable("screen") and component.screen.address == screen then + window.keyboard = system_keyboard + else + -- calling getKeyboards() on the screen is costly (time) + -- custom terminals should avoid designs that require + -- this on every key hit + + -- this is expensive (slow!) + window.keyboard = component.invoke(screen, "getKeyboards")[1] or system_keyboard + end + + return window.keyboard +end + +function term.screen(window) + window = window or W() + local gpu = window.gpu + if not gpu then + return nil + end + return gpu.getScreen() +end + +function --[[@delayloaded-start@]] term.scroll(number, window) + -- if zero scroll length is requested, do nothing + if number == 0 then return end + -- window is optional, default to current active terminal + window = window or W() + -- gpu works with global coordinates + local gpu,width,height,dx,dy,x,y = window.gpu,term.getViewport(w) + + -- scroll request can be too large + local abs_number = math.abs(number) + if (abs_number >= height) then + term.clear() + return + end + + -- box positions to shift + local box_height = height - abs_number + local top = 0 + if number > 0 then + top = number -- (e.g. 1 scroll moves box at 2) + end + + gpu.copy(dx + 1, dy + top + 1, width, box_height, 0, -number) + + local fill_top = 0 + if number > 0 then + fill_top = box_height + end + + gpu.fill(dx + 1, dy + fill_top + 1, width, abs_number, " ") +end --[[@delayloaded-end@]] + +function --[[@delayloaded-start@]] term.internal.ctrl_movement(input, dir) + local index, data = input.index, input.data + + local function isEdge(char) + return char == "" or not not char:find("%s") + end + + local last=dir<0 and 0 or unicode.len(data) + local start=index+dir+1 + for i=start,last,dir do + local a,b = unicode.sub(data, i-1, i-1), unicode.sub(data, i, i) + if isEdge(a) and not isEdge(b) then return i-(index+1) end + end + return last - index +end --[[@delayloaded-end@]] + +function --[[@delayloaded-start@]] term.internal.onTouch(input,gx,gy) + if input.data == "" then return end + input:move(-math.huge) + local w = W() + gx,gy=gx-w.dx,gy-w.dy + local x2,y2,d = input.w.x,input.w.y,input.w.w + local char_width_to_move = ((gy*d+gx)-(y2*d+x2)) + if char_width_to_move <= 0 then return end + local total_wlen = unicode.wlen(input.data) + if char_width_to_move >= total_wlen then + input:move(math.huge) + else + local chars_to_move = unicode.wtrunc(input.data, char_width_to_move + 1) + input:move(unicode.len(chars_to_move)) + end + -- fake white space can make the index off, redo adjustment for alignment + x2,y2,d = input.w.x,input.w.y,input.w.w + char_width_to_move = ((gy*d+gx)-(y2*d+x2)) + if (char_width_to_move < 0) then + -- using char_width_to_move as a type of index is wrong, but large enough and helps to speed this up + local up_to_cursor = unicode.sub(input.data, input.index+char_width_to_move, input.index) + local full_wlen = unicode.wlen(up_to_cursor) + local without_tail = unicode.wtrunc(up_to_cursor, full_wlen + char_width_to_move + 1) + local chars_cut = unicode.len(up_to_cursor) - unicode.len(without_tail) + input:move(-chars_cut) + end +end --[[@delayloaded-end@]] + +function --[[@delayloaded-start@]] term.internal.build_horizontal_reader(input) + term.internal.build_vertical_reader(input) + input.clear_tail = function(_) + local w,h,dx,dy,x,y = term.getViewport(_.w) + local s1,s2=term.internal.split(_) + local wlen = math.min(unicode.wlen(s2),w-x+1) + _.w.gpu.fill(x+dx,y+dy,wlen,1," ") + end + input.move = function(_,n) + local win = _.w + local a = _.index + local b = math.max(0,math.min(unicode.len(_.data),_.index+n)) + _.index = b + a,b = a w then + local blank + if i == unicode.len(data) then + available,blank=available-1," " + else + i,blank=i+1,"" + end + data = unicode.sub(data,1,i) + local rev = unicode.reverse(data) + local ending = unicode.wtrunc(rev, available+1) + data = unicode.reverse(ending) + gpu.set(sx,sy,data..blank) + win.x=math.min(w,_.promptx+unicode.wlen(data)) + elseif x < _.promptx then + data = unicode.sub(data,_.index+1) + if unicode.wlen(data) > available then + data = unicode.wtrunc(data,available+1) + end + gpu.set(sx,sy,data) + end + end + input.clear = function(_) + local win = _.w + local gpu,data,px=win.gpu,_.data,_.promptx + local w,h,dx,dy,x,y = term.getViewport(win) + _.index,_.data,win.x=0,"",px + gpu.fill(px+dx,y+dy,w-px+1-dx,1," ") + end +end --[[@delayloaded-end@]] + +function --[[@delayloaded-start@]] term.clearLine(window) + window = window or W() + local w,h,dx,dy,x,y = term.getViewport(window) + window.gpu.fill(dx+1,dy+math.max(1,math.min(y,h)),w,1," ") + window.x=1 +end --[[@delayloaded-end@]] + +function --[[@delayloaded-start@]] term.internal.mask(mask,input) + if not mask then return input end + if type(mask) == "function" then return mask(input) end + return mask:rep(unicode.wlen(input)) +end --[[@delayloaded-end@]] + +function --[[@delayloaded-start@]] term.internal.filter(filter,input) + if not filter then return true + elseif type(filter) == "string" then return input.data:match(filter) + elseif filter(input.data) then return true + else require("computer").beep(2000, 0.1) end +end --[[@delayloaded-end@]] + +function --[[@delayloaded-start@]] term.internal.tab(input,hints) + if not hints.handler then return end + local main_kb = term.keyboard() + -- term may not have a keyboard + -- in which case, we shouldn't be handling tab events + if not main_kb then + return + end + if not hints.cache then + local data = hints.handler + hints.handler = function(...) + if type(data) == "table" then + return data + else + return data(...) or {} + end + end + hints.cache = hints.handler(input.data, input.index + 1) + hints.cache.i = -1 + end + + local cache = hints.cache + local cache_size = #cache + + if cache_size == 1 and cache.i == 0 then + -- there was only one solution, and the user is asking for the next + hints.cache = hints.handler(cache[1], input.index + 1) + hints.cache.i = -1 + cache = hints.cache + cache_size = #cache + end + + local change = kb.isShiftDown(main_kb) and -1 or 1 + cache.i = (cache.i + change) % math.max(#cache, 1) + local next = cache[cache.i + 1] + if next then + local tail = unicode.len(input.data) - input.index + input:clear() + input:update(next) + input:move(-tail) + end +end --[[@delayloaded-end@]] + +function --[[@delayloaded-start@]] term.getGlobalArea(window) + local w,h,dx,dy = term.getViewport(window) + return dx+1,dy+1,w,h +end --[[@delayloaded-end@]] + +function --[[@delayloaded-start@]] term.internal.clipboard(char) + local first_line, end_index = char:find("\13?\10") + if first_line then + local after = char:sub(end_index + 1) + if after ~= "" then + require("computer").pushSignal("key_down", term.keyboard(), 13, 28) + require("computer").pushSignal("clipboard", term.keyboard(), after) + end + char = char:sub(1, first_line - 1) + end + return char +end --[[@delayloaded-end@]] + +return term, local_env diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/text.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/text.lua new file mode 100755 index 00000000..35278fa9 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/text.lua @@ -0,0 +1,396 @@ +local unicode = require("unicode") +local tx = require("transforms") + +-- --[[@@]] are not just comments, but custom annotations for delayload methods. +-- See package.lua and the api wiki for more information + +local text = {} +local local_env = {tx=tx,unicode=unicode} + +text.internal = {} + +text.syntax = {"^%d?>>?&%d+$",";","&&","||?","^%d?>>?",">>?","<"} + +function --[[@delayloaded-start@]] text.detab(value, tabWidth) + checkArg(1, value, "string") + checkArg(2, tabWidth, "number", "nil") + tabWidth = tabWidth or 8 + local function rep(match) + local spaces = tabWidth - match:len() % tabWidth + return match .. string.rep(" ", spaces) + end + local result = value:gsub("([^\n]-)\t", rep) -- truncate results + return result +end --[[@delayloaded-end@]] + +-- used in motd +function text.padRight(value, length) + checkArg(1, value, "string", "nil") + checkArg(2, length, "number") + if not value or unicode.wlen(value) == 0 then + return string.rep(" ", length) + else + return value .. string.rep(" ", length - unicode.wlen(value)) + end +end + +function --[[@delayloaded-start@]] text.padLeft(value, length) + checkArg(1, value, "string", "nil") + checkArg(2, length, "number") + if not value or unicode.wlen(value) == 0 then + return string.rep(" ", length) + else + return string.rep(" ", length - unicode.wlen(value)) .. value + end +end --[[@delayloaded-end@]] + +function text.trim(value) -- from http://lua-users.org/wiki/StringTrim + local from = string.match(value, "^%s*()") + return from > #value and "" or string.match(value, ".*%S", from) +end + +function text.wrap(value, width, maxWidth) + checkArg(1, value, "string") + checkArg(2, width, "number") + checkArg(3, maxWidth, "number") + local line, nl = value:match("([^\r\n]*)(\r?\n?)") -- read until newline + if unicode.wlen(line) > width then -- do we even need to wrap? + local partial = unicode.wtrunc(line, width) + local wrapped = partial:match("(.*[^a-zA-Z0-9._()'`=])") + if wrapped or unicode.wlen(line) > maxWidth then + partial = wrapped or partial + return partial, unicode.sub(value, unicode.len(partial) + 1), true + else + return "", value, true -- write in new line. + end + end + local start = unicode.len(line) + unicode.len(nl) + 1 + return line, start <= unicode.len(value) and unicode.sub(value, start) or nil, unicode.len(nl) > 0 +end + +function text.wrappedLines(value, width, maxWidth) + local line, nl + return function() + if value then + line, value, nl = text.wrap(value, width, maxWidth) + return line + end + end +end + +------------------------------------------------------------------------------- + +-- separate string value into an array of words delimited by whitespace +-- groups by quotes +-- options is a table used for internal undocumented purposes +function text.tokenize(value, options) + checkArg(1, value, "string") + checkArg(2, options, "table", "nil") + options = options or {} + + local tokens, reason = text.internal.tokenize(value, options) + + if type(tokens) ~= "table" then + return nil, reason + end + + if options.doNotNormalize then + return tokens + end + + return text.internal.normalize(tokens) +end + +function text.escapeMagic(txt) + return txt:gsub('[%(%)%.%%%+%-%*%?%[%^%$]', '%%%1') +end + +function text.removeEscapes(txt) + return txt:gsub("%%([%(%)%.%%%+%-%*%?%[%^%$])","%1") +end + +------------------------------------------------------------------------------- +-- like tokenize, but does not drop any text such as whitespace +-- splits input into an array for sub strings delimited by delimiters +-- delimiters are included in the result if not dropDelims +function --[[@delayloaded-start@]] text.split(input, delimiters, dropDelims, di) + checkArg(1, input, "string") + checkArg(2, delimiters, "table") + checkArg(3, dropDelims, "boolean", "nil") + checkArg(4, di, "number", "nil") + + if #input == 0 then return {} end + di = di or 1 + local result = {input} + if di > #delimiters then return result end + + local function add(part, index, r, s, e) + local sub = part:sub(s,e) + if #sub == 0 then return index end + local subs = r and text.split(sub,delimiters,dropDelims,r) or {sub} + for i=1,#subs do + table.insert(result, index+i-1, subs[i]) + end + return index+#subs + end + + local i,d=1,delimiters[di] + while true do + local next = table.remove(result,i) + if not next then break end + local si,ei = next:find(d) + if si and ei and ei~=0 then -- delim found + i=add(next, i, di+1, 1, si-1) + i=dropDelims and i or add(next, i, false, si, ei) + i=add(next, i, di, ei+1) + else + i=add(next, i, di+1, 1, #next) + end + end + + return result +end --[[@delayloaded-end@]] + +----------------------------------------------------------------------------- + +function text.internal.tokenize(value, options) + checkArg(1, value, "string") + checkArg(2, options, "table", "nil") + options = options or {} + local delimiters = options.delimiters + local custom = not not options.delimiters + delimiters = delimiters or text.syntax + + local words, reason = text.internal.words(value, options) + + local splitter = text.escapeMagic(custom and table.concat(delimiters) or "<>|;&") + if type(words) ~= "table" or + #splitter == 0 or + not value:find("["..splitter.."]") then + return words, reason + end + + return text.internal.splitWords(words, delimiters) +end + +-- tokenize input by quotes and whitespace +function text.internal.words(input, options) + checkArg(1, input, "string") + checkArg(2, options, "table", "nil") + options = options or {} + local quotes = options.quotes + local show_escapes = options.show_escapes + local qr = nil + quotes = quotes or {{"'","'",true},{'"','"'},{'`','`'}} + local function append(dst, txt, qr) + local size = #dst + if size == 0 or dst[size].qr ~= qr then + dst[size+1] = {txt=txt, qr=qr} + else + dst[size].txt = dst[size].txt..txt + end + end + -- token meta is {string,quote rule} + local tokens, token = {}, {} + local escaped, start = false, -1 + for i = 1, unicode.len(input) do + local char = unicode.sub(input, i, i) + if escaped then -- escaped character + escaped = false + -- include escape char if show_escapes + -- or the followwing are all true + -- 1. qr active + -- 2. the char escaped is NOT the qr closure + -- 3. qr is not literal + if show_escapes or (qr and not qr[3] and qr[2] ~= char) then + append(token, '\\', qr) + end + append(token, char, qr) + elseif char == "\\" and (not qr or not qr[3]) then + escaped = true + elseif qr and qr[2] == char then -- end of quoted string + -- if string is empty, we can still capture a quoted empty arg + if #token == 0 or #token[#token] == 0 then + append(token, '', qr) + end + qr = nil + elseif not qr and tx.first(quotes,function(Q) + qr=Q[1]==char and Q or nil return qr end) then + start = i + elseif not qr and string.find(char, "%s") then + if #token > 0 then + table.insert(tokens, token) + end + token = {} + else -- normal char + append(token, char, qr) + end + end + if qr then + return nil, "unclosed quote at index " .. start + end + + if #token > 0 then + table.insert(tokens, token) + end + + return tokens +end + +-- splits each word into words at delimiters +-- delimiters are kept as their own words +-- quoted word parts are not split +function --[[@delayloaded-start@]] text.internal.splitWords(words, delimiters) + checkArg(1,words,"table") + checkArg(2,delimiters,"table") + + local split_words = {} + local next_word + local function add_part(part) + if next_word then + split_words[#split_words+1] = {} + end + table.insert(split_words[#split_words], part) + next_word = false + end + for wi=1,#words do local word = words[wi] + next_word = true + for pi=1,#word do local part = word[pi] + local qr = part.qr + if qr then + add_part(part) + else + local part_text_splits = text.split(part.txt, delimiters) + tx.foreach(part_text_splits, function(sub_txt, spi) + local delim = #text.split(sub_txt, delimiters, true) == 0 + next_word = next_word or delim + add_part({txt=sub_txt,qr=qr}) + next_word = delim + end) + end + end + end + + return split_words +end --[[@delayloaded-end@]] + +function --[[@delayloaded-start@]] text.internal.normalize(words, omitQuotes) + checkArg(1, words, "table") + checkArg(2, omitQuotes, "boolean", "nil") + local norms = {} + for _,word in ipairs(words) do + local norm = {} + for _,part in ipairs(word) do + norm = tx.concat(norm, not omitQuotes and part.qr and {part.qr[1], part.txt, part.qr[2]} or {part.txt}) + end + norms[#norms+1]=table.concat(norm) + end + return norms +end --[[@delayloaded-end@]] + +function --[[@delayloaded-start@]] text.internal.stream_base(binary) + return + { + binary = binary, + plen = binary and string.len or unicode.len, + psub = binary and string.sub or unicode.sub, + seek = function (handle, whence, to) + if not handle.txt then + return nil, "bad file descriptor" + end + to = to or 0 + local offset = handle:indexbytes() + if whence == "cur" then + offset = offset + to + elseif whence == "set" then + offset = to + elseif whence == "end" then + offset = handle.len + to + end + offset = math.max(0, math.min(offset, handle.len)) + handle:byteindex(offset) + return offset + end, + indexbytes = function (handle) + return handle.psub(handle.txt, 1, handle.index):len() + end, + byteindex = function (handle, offset) + local sub = string.sub(handle.txt, 1, offset) + handle.index = handle.plen(sub) + end, + } +end --[[@delayloaded-end@]] + +function --[[@delayloaded-start@]] text.internal.reader(txt, mode) + checkArg(1, txt, "string") + local reader = setmetatable( + { + txt = txt, + len = string.len(txt), + index = 0, + read = function(_, n) + checkArg(1, n, "number") + if not _.txt then + return nil, "bad file descriptor" + end + if _.index >= _.plen(_.txt) then + return nil + end + local next = _.psub(_.txt, _.index + 1, _.index + n) + _.index = _.index + _.plen(next) + return next + end, + close = function(_) + if not _.txt then + return nil, "bad file descriptor" + end + _.txt = nil + return true + end, + }, {__index=text.internal.stream_base(mode:match("b"))}) + + return require("buffer").new("r", reader) +end --[[@delayloaded-end@]] + +function --[[@delayloaded-start@]] text.internal.writer(ostream, mode, append_txt) + if type(ostream) == "table" then + local mt = getmetatable(ostream) or {} + checkArg(1, mt.__call, "function") + end + checkArg(1, ostream, "function", "table") + checkArg(2, append_txt, "string", "nil") + local writer = setmetatable( + { + txt = "", + index = 0, -- last location of write + len = 0, + write = function(_, ...) + if not _.txt then + return nil, "bad file descriptor" + end + local pre = _.psub(_.txt, 1, _.index) + local vs = {} + local pos = _.psub(_.txt, _.index + 1) + for i,v in ipairs({...}) do + table.insert(vs, v) + end + vs = table.concat(vs) + _.index = _.index + _.plen(vs) + _.txt = pre .. vs .. pos + _.len = string.len(_.txt) + return true + end, + close = function(_) + if not _.txt then + return nil, "bad file descriptor" + end + ostream((append_txt or "") .. _.txt) + _.txt = nil + return true + end, + }, {__index=text.internal.stream_base(mode:match("b"))}) + + return require("buffer").new("w", writer) +end --[[@delayloaded-end@]] + +return text, local_env diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/tools/boot.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/tools/boot.lua new file mode 100755 index 00000000..27c10959 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/tools/boot.lua @@ -0,0 +1,147 @@ +-- called from /init.lua +local raw_loadfile = ... + +_G._OSVERSION = "OpenOS 1.6.1" + +local component = component +local computer = computer +local unicode = unicode + +-- Runlevel information. +local runlevel, shutdown = "S", computer.shutdown +computer.runlevel = function() return runlevel end +computer.shutdown = function(reboot) + runlevel = reboot and 6 or 0 + if os.sleep then + computer.pushSignal("shutdown") + os.sleep(0.1) -- Allow shutdown processing. + end + shutdown(reboot) +end + +local screen = component.list('screen', true)() +for address in component.list('screen', true) do + if #component.invoke(address, 'getKeyboards') > 0 then + screen = address + break + end +end + +_G.boot_screen = screen + +-- Report boot progress if possible. +local gpu = component.list("gpu", true)() +local w, h +if gpu and screen then + component.invoke(gpu, "bind", screen) + w, h = component.invoke(gpu, "maxResolution") + component.invoke(gpu, "setResolution", w, h) + component.invoke(gpu, "setBackground", 0x000000) + component.invoke(gpu, "setForeground", 0xFFFFFF) + component.invoke(gpu, "fill", 1, 1, w, h, " ") +end +local y = 1 +local function status(msg) + if gpu and screen then + component.invoke(gpu, "set", 1, y, msg) + if y == h then + component.invoke(gpu, "copy", 1, 2, w, h - 1, 0, -1) + component.invoke(gpu, "fill", 1, h, w, 1, " ") + else + y = y + 1 + end + end +end + +status("Booting " .. _OSVERSION .. "...") + +-- Custom low-level dofile implementation reading from our ROM. +local loadfile = function(file) + status("> " .. file) + return raw_loadfile(file) +end + +local function dofile(file) + local program, reason = loadfile(file) + if program then + local result = table.pack(pcall(program)) + if result[1] then + return table.unpack(result, 2, result.n) + else + error(result[2]) + end + else + error(reason) + end +end + +status("Initializing package management...") + +-- Load file system related libraries we need to load other stuff moree +-- comfortably. This is basically wrapper stuff for the file streams +-- provided by the filesystem components. +local package = dofile("/lib/package.lua") + +do + -- Unclutter global namespace now that we have the package module and a filesystem + _G.component = nil + _G.computer = nil + _G.process = nil + _G.unicode = nil + -- Inject the package modules into the global namespace, as in Lua. + _G.package = package + + -- Initialize the package module with some of our own APIs. + package.loaded.component = component + package.loaded.computer = computer + package.loaded.unicode = unicode + package.preload["buffer"] = loadfile("/lib/buffer.lua") + package.preload["filesystem"] = loadfile("/lib/filesystem.lua") + + -- Inject the io modules + _G.io = loadfile("/lib/io.lua")() + + --mark modules for delay loaded api + package.delayed["text"] = true + package.delayed["sh"] = true + package.delayed["transforms"] = true + package.delayed["term"] = true +end + +status("Initializing file system...") + +-- Mount the ROM and temporary file systems to allow working on the file +-- system module from this point on. +require("filesystem").mount(computer.getBootAddress(), "/") +package.preload={} + +status("Running boot scripts...") + +-- Run library startup scripts. These mostly initialize event handlers. +local function rom_invoke(method, ...) + return component.invoke(computer.getBootAddress(), method, ...) +end + +local scripts = {} +for _, file in ipairs(rom_invoke("list", "boot")) do + local path = "boot/" .. file + if not rom_invoke("isDirectory", path) then + table.insert(scripts, path) + end +end +table.sort(scripts) +for i = 1, #scripts do + dofile(scripts[i]) +end + +status("Initializing components...") + +for c, t in component.list() do + computer.pushSignal("component_added", c, t) +end + +status("Initializing system...") + +computer.pushSignal("init") -- so libs know components are initialized. +require("event").pull(1, "init") -- Allow init processing. +_G.runlevel = 1 diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/tools/buffered_read.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/tools/buffered_read.lua new file mode 100755 index 00000000..c419d997 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/tools/buffered_read.lua @@ -0,0 +1,162 @@ +local unicode = require("unicode") +local adv_buf = {} + +function adv_buf.readNumber(self, readChunk) + local len, sub + if self.mode.b then + len = rawlen + sub = string.sub + else + len = unicode.len + sub = unicode.sub + end + + local buffer = "" + local white_done + + local function peek() + if len(self.bufferRead) == 0 then + local result, reason = readChunk(self) + if not result then + return result, reason + end + end + return sub(self.bufferRead, 1, 1) + end + + local function pop() + local n = sub(self.bufferRead, 1, 1) + self.bufferRead = sub(self.bufferRead, 2) + return n + end + + local function take() + buffer = buffer .. pop() + end + + while true do + local peeked = peek() + if not peeked then + break + end + + if peeked:match("[%s]") then + if white_done then + break + end + pop() + else + white_done = true + if not tonumber(buffer .. peeked .. "0") then + break + end + take() -- add pop to buffer + end + end + + return tonumber(buffer) +end + +function adv_buf.readBytesOrChars(self, readChunk, n) + n = math.max(n, 0) + local len, sub + if self.mode.b then + len = rawlen + sub = string.sub + else + len = unicode.len + sub = unicode.sub + end + local buffer = "" + repeat + if len(self.bufferRead) == 0 then + local result, reason = readChunk(self) + if not result then + if reason then + return nil, reason + else -- eof + return #buffer > 0 and buffer or nil + end + end + end + local left = n - len(buffer) + buffer = buffer .. sub(self.bufferRead, 1, left) + self.bufferRead = sub(self.bufferRead, left + 1) + until len(buffer) == n + return buffer +end + +function adv_buf.readAll(self, readChunk) + repeat + local result, reason = readChunk(self) + if not result and reason then + return nil, reason + end + until not result -- eof + local result = self.bufferRead + self.bufferRead = "" + return result +end + +function adv_buf.read(self, readChunk, formats) + self.timeout = require("computer").uptime() + self.readTimeout + local function read(n, format) + if type(format) == "number" then + return adv_buf.readBytesOrChars(self, readChunk, format) + else + local first_char_index = 1 + if type(format) ~= "string" then + error("bad argument #" .. n .. " (invalid option)") + elseif unicode.sub(format, 1, 1) == "*" then + first_char_index = 2 + end + format = unicode.sub(format, first_char_index, first_char_index) + if format == "n" then + return adv_buf.readNumber(self, readChunk) + elseif format == "l" then + return self:readLine(true, self.timeout) + elseif format == "L" then + return self:readLine(false, self.timeout) + elseif format == "a" then + return adv_buf.readAll(self, readChunk) + else + error("bad argument #" .. n .. " (invalid format)") + end + end + end + + local results = {} + for i = 1, formats.n do + local result, reason = read(i, formats[i]) + if result then + results[i] = result + elseif reason then + return nil, reason + end + end + return table.unpack(results, 1, formats.n) +end + +function adv_buf.seek(self, whence, offset) + whence = tostring(whence or "cur") + assert(whence == "set" or whence == "cur" or whence == "end", + "bad argument #1 (set, cur or end expected, got " .. whence .. ")") + offset = offset or 0 + checkArg(2, offset, "number") + assert(math.floor(offset) == offset, "bad argument #2 (not an integer)") + + if self.mode.w or self.mode.a then + self:flush() + elseif whence == "cur" then + offset = offset - #self.bufferRead + end + local result, reason = self.stream:seek(whence, offset) + if result then + self.bufferRead = "" + return result + else + return nil, reason + end +end + +return adv_buf diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/tools/buffered_write.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/tools/buffered_write.lua new file mode 100755 index 00000000..fab12e99 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/tools/buffered_write.lua @@ -0,0 +1,50 @@ +local unicode = require("unicode") +local adv_buf = {} + +function adv_buf.write(self, arg) + local result, reason + if self.bufferMode == "full" then + if self.bufferSize - #self.bufferWrite < #arg then + result, reason = self:flush() + if not result then + return nil, reason + end + end + if #arg > self.bufferSize then + result, reason = self.stream:write(arg) + else + self.bufferWrite = self.bufferWrite .. arg + result = self + end + else--if self.bufferMode == "line" then + local l + repeat + local idx = arg:find("\n", (l or 0) + 1, true) + if idx then + l = idx + end + until not idx + if l or #arg > self.bufferSize then + result, reason = self:flush() + if not result then + return nil, reason + end + end + if l then + result, reason = self.stream:write(arg:sub(1, l)) + if not result then + return nil, reason + end + arg = arg:sub(l + 1) + end + if #arg > self.bufferSize then + result, reason = self.stream:write(arg) + else + self.bufferWrite = self.bufferWrite .. arg + result = self + end + end + return result, reason +end + +return adv_buf diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/tools/delayLookup.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/tools/delayLookup.lua new file mode 100755 index 00000000..c0e049b0 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/tools/delayLookup.lua @@ -0,0 +1,33 @@ +local data,tbl,key = ... +local z = data[tbl] + +if key then -- index + local method = z.methods[key] + local cache = z.cache[key] + if method and not cache then + local file = io.open(z.path,"r") + if file then + file:seek("set", method[1]) + local loaded = load("return function"..file:read(method[2]), "=delayed-"..key,"t",z.env) + file:close() + assert(loaded,"failed to load "..key) + cache = loaded() + --lazy_protect(key, cache) + z.cache[key] = cache + end + end + return cache +else -- pairs + local set,k,v = {} + while true do + k,v = next(tbl,k) + if not k then break end + set[k] = v + end + for k in pairs(z.methods) do + if not set[k] then + set[k] = function(...)return tbl[k](...)end + end + end + return pairs(set) +end diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/tools/delayParse.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/tools/delayParse.lua new file mode 100755 index 00000000..9f7412f7 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/tools/delayParse.lua @@ -0,0 +1,67 @@ +local filepath,delay_data = ... +local file, reason = io.open(filepath, "r") +if not file then + return reason +end + +local methods = {} +local delay_start_pattern = "^%s*function%s*%-%-%[%[@delayloaded%-start@%]%]%s*(.*)$" +local delay_end_pattern = "^%s*end%s*%-%-%[%[@delayloaded%-end@%]%]%s*$" +local n,buffer,lib_name,current_method,open = 0,{} + +while true do + local line = file:readLine(false) + if current_method then + local closed = not line or line:match(delay_end_pattern) + if closed then + local path,method_name,args = open:match("^(.-)([^%.]+)(%(.*)$") + current_method = current_method-#args + methods[path] = methods[path] or {} + methods[path][method_name] = {current_method,n+#line-current_method} + current_method=nil + end + elseif line then + open = line:match(delay_start_pattern) + if open then + lib_name,open = open:match("^([^%.]+)%.(.*)$") + current_method = n+#line + else + buffer[#buffer+1] = line + end + else + file:close() + break + end + n = n + #line +end + +if not next(methods) or current_method or not lib_name then + return "no methods found or unclosed marker for delayed load" +end + +buffer = table.concat(buffer) +local loader, reason = load(buffer, "="..filepath, "t", _G) +local library, local_env = loader() +if library then + local_env = local_env or {} + local_env[lib_name] = library + + local env = setmetatable(local_env, {__index=_G}) + + for path,pack in pairs(methods) do + local target = library + for name in path:gmatch("[^%.]+") do target = target[name] end + delay_data[target] = + { + methods = pack, + cache = {}, + env = env, + path = filepath + } + setmetatable(target, delay_data) + end + + return function()return library end, filepath +end + +return reason diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/tools/devfs/01_hw.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/tools/devfs/01_hw.lua new file mode 100755 index 00000000..dd50a6f5 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/tools/devfs/01_hw.lua @@ -0,0 +1,145 @@ +local comp = require("component") +local fs = require("filesystem") +local text = require("text") + +local dcache = {} +local pcache = {} +local adapter_pwd = "/lib/tools/devfs/adapters/" + +local adapter_api = {} + +function adapter_api.toArgsPack(input, pack) + local split = text.split(input, {"%s"}, true) + local req = pack[1] + local num = #split + if num < req then return nil, "insufficient args" end + local result = {n=num} + for index=1,num do + local typename = pack[index+1] + local token = split[index] + if typename == "boolean" then + if token ~= "true" and token ~= "false" then return nil, "bad boolean value" end + token = token == "true" + elseif typename == "number" then + token = tonumber(token) + if not token then return nil, "bad number value" end + end + result[index] = token + end + return result +end + +function adapter_api.createWriter(callback, ...) + local types = table.pack(...) + return function(input) + local args, why = adapter_api.toArgsPack(input, types) + if not args then return why end + return callback(table.unpack(args, 1, args.n)) + end +end + +function adapter_api.create_toggle(read, write, switch) + return + { + read = function() return tostring(read()) end, + write = function(value) + value = text.trim(tostring(value)) + local on = value == "1" or value == "true" + local off = value == "0" or value == "false" + if not on and not off then + return nil, "bad value" + end + if switch then + (off and switch or write)() + else + write(on) + end + end + } +end + +function adapter_api.make_link(list, addr, prefix, bOmitZero) + prefix = prefix or "" + local zero = bOmitZero and "" or "0" + local id = 0 + local name + repeat + name = string.format("%s%s", prefix, id == 0 and zero or tostring(id)) + id = id + 1 + until not list[name] + list[name] = {link=addr} +end + +return +{ + components = + { + list = function() + local dirs = {} + local types = {} + local labels = {} + local ads = {} + + dirs["by-type"] = {list=function()return types end} + dirs["by-label"] = {list=function()return labels end} + dirs["by-address"] = {list=function()return ads end} + + -- first sort the addr, primaries first, then sorted by address lexigraphically + local hw_addresses = {} + for addr,type in comp.list() do + table.insert(hw_addresses, {addr,type}) + end + + table.sort(hw_addresses, function(a, b) + local aaddr, atype = table.unpack(a) + local baddr, btype = table.unpack(b) + + if atype == btype then + local aprim = comp.isPrimary(aaddr) + local bprim = comp.isPrimary(baddr) + if aprim then return true end + if bprim then return false end + end + + return aaddr < baddr + end) + + for _,pair in ipairs(hw_addresses) do + local addr, type = table.unpack(pair) + if not dcache[type] then + local adapter_file = adapter_pwd .. type .. ".lua" + local loader = loadfile(adapter_file, "bt", _G) + dcache[type] = loader and loader(adapter_api) + end + local adapter = dcache[type] + if adapter then + local proxy = pcache[addr] or comp.proxy(addr) + pcache[addr] = proxy + ads[addr] = + { + list = function() + local devfs_proxy = adapter(proxy) + devfs_proxy.address = {proxy.address} + devfs_proxy.slot = {proxy.slot} + devfs_proxy.type = {proxy.type} + devfs_proxy.device = {device=proxy} + return devfs_proxy + end + } + + -- by type building + local type_dir = types[type] or {list={}} + adapter_api.make_link(type_dir.list, "../../by-address/"..addr) + types[type] = type_dir + + -- by label building (labels are only supported in filesystems + local label = require("devfs").getDeviceLabel(proxy) + if label then + adapter_api.make_link(labels, "../by-address/"..addr, label, true) + end + end + end + return dirs + end + }, +} diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/tools/devfs/02_utils.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/tools/devfs/02_utils.lua new file mode 100755 index 00000000..a34c49ab --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/tools/devfs/02_utils.lua @@ -0,0 +1,50 @@ +return +{ + eeprom = + { + link = "components/by-type/eeprom/0/contents", + isAvailable = function() + local comp = require("component") + return comp.list("eeprom")() + end + }, + ["eeprom-data"] = + { + link = "components/by-type/eeprom/0/data", + isAvailable = function() + local comp = require("component") + return comp.list("eeprom")() + end + }, + null = + { + open = function(mode) + return + { + read = function() end, + write = function() end + } + end + }, + random = + { + open = function(mode) + if mode and not mode:match("r") then + return nil, "read only" + end + return + { + read = function(self, n) + local chars = {} + for i=1,n do + table.insert(chars,string.char(math.random(0,255))) + end + return table.concat(chars) + end + } + end, + size = function() + return math.huge + end + }, +} diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/tools/devfs/adapters/computer.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/tools/devfs/adapters/computer.lua new file mode 100755 index 00000000..166dc7d0 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/tools/devfs/adapters/computer.lua @@ -0,0 +1,9 @@ +local adapter_api = ... + +return function(proxy) + return + { + beep = {write=adapter_api.createWriter(proxy.beep, 0, "number", "number")}, + running = adapter_api.create_toggle(proxy.isRunning, proxy.start, proxy.stop), + } +end diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/tools/devfs/adapters/eeprom.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/tools/devfs/adapters/eeprom.lua new file mode 100755 index 00000000..a34b439d --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/tools/devfs/adapters/eeprom.lua @@ -0,0 +1,22 @@ +local cache = {} +local function cload(callback) + local c = cache[callback] + if not c then + c = callback() + cache[callback] = c + end + return c +end + +return function(proxy) + return + { + contents = {read=proxy.get, write=proxy.set}, + data = {read=proxy.getData, write=proxy.setData}, + checksum = {read=proxy.getChecksum,size=function() return 8 end}, + size = {cload(proxy.getSize)}, + dataSize = {cload(proxy.getDataSize)}, + label = {write=proxy.setLabel,proxy.getLabel()}, + makeReadonly = {write=proxy.makeReadonly} + } +end diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/tools/devfs/adapters/filesystem.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/tools/devfs/adapters/filesystem.lua new file mode 100755 index 00000000..163d44eb --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/tools/devfs/adapters/filesystem.lua @@ -0,0 +1,25 @@ +local fs = require("filesystem") +local text = require("text") + +return function(proxy) + return + { + ["label"] = + { + read = function() return proxy.getLabel() or "" end, + write= function(v) proxy.setLabel(text.trim(v)) end + }, + ["isReadOnly"] = {proxy.isReadOnly()}, + ["spaceUsed"] = {proxy.spaceUsed()}, + ["spaceTotal"] = {proxy.spaceTotal()}, + ["mounts"] = {read = function() + local mounts = {} + for mproxy,mpath in fs.mounts() do + if mproxy.address == proxy.address then + table.insert(mounts, mpath) + end + end + return table.concat(mounts, "\n") + end} + } +end diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/tools/devfs/adapters/gpu.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/tools/devfs/adapters/gpu.lua new file mode 100755 index 00000000..9e8e6cf5 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/tools/devfs/adapters/gpu.lua @@ -0,0 +1,15 @@ +local adapter_api = ... + +return function(proxy) + return + { + viewport = {write = adapter_api.createWriter(proxy.setViewport, 2, "number", "number"), proxy.getViewport()}, + resolution = {write = adapter_api.createWriter(proxy.setResolution, 2, "number", "number"), proxy.getResolution()}, + maxResolution = {proxy.maxResolution()}, + screen = {link="../"..proxy.getScreen(),isAvailable=proxy.getScreen}, + depth = {write = adapter_api.createWriter(proxy.setDepth, 1, "number"), proxy.getDepth()}, + maxDepth = {proxy.maxDepth()}, + background = {write = adapter_api.createWriter(proxy.setBackground, 1, "number", "boolean"), proxy.getBackground()}, + foreground = {write = adapter_api.createWriter(proxy.setForeground, 1, "number", "boolean"), proxy.getForeground()}, + } +end diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/tools/devfs/adapters/internet.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/tools/devfs/adapters/internet.lua new file mode 100755 index 00000000..95c3f74e --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/tools/devfs/adapters/internet.lua @@ -0,0 +1,7 @@ +return function(proxy) + return + { + httpEnabled = {proxy.isHttpEnabled()}, + tcpEnabled = {proxy.isTcpEnabled()}, + } +end diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/tools/devfs/adapters/modem.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/tools/devfs/adapters/modem.lua new file mode 100755 index 00000000..a9327bd3 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/tools/devfs/adapters/modem.lua @@ -0,0 +1,12 @@ +return function(proxy) + return + { + wakeMessage = + { + read = function() return proxy.getWakeMessage() or "" end, + write= function(msg) return proxy.setWakeMessage(msg) end, + }, + wireless = {proxy.isWireless()}, + maxPacketSize = {proxy.maxPacketSize()}, + } +end diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/tools/devfs/adapters/screen.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/tools/devfs/adapters/screen.lua new file mode 100755 index 00000000..2c9ad671 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/tools/devfs/adapters/screen.lua @@ -0,0 +1,18 @@ +local adapter_api = ... + +return function(proxy) + return + { + ["aspectRatio"] = {proxy.getAspectRatio()}, + ["keyboards"] = {read=function() + local ks = {} + for _,ka in ipairs(proxy.getKeyboards()) do + table.insert(ks, ka) + end + return table.concat(ks, "\n") + end}, + ["on"] = adapter_api.create_toggle(proxy.isOn, proxy.turnOn, proxy.turnOff), -- turnOn and turnOff + ["precise"] = adapter_api.create_toggle(proxy.isPrecise, proxy.setPrecise), + ["touchModeInverted"] = adapter_api.create_toggle(proxy.isTouchModeInverted, proxy.setTouchModeInverted), + } +end diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/tools/device_labeling.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/tools/device_labeling.lua new file mode 100755 index 00000000..207778c5 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/tools/device_labeling.lua @@ -0,0 +1,101 @@ +local fs = require("filesystem") + +local lib = {} + +local rules_path = "/etc/udev/rules.d/" +local auto_rules = "autogenerated.lua" + +local function fs_key(dir, filename) + local long_name = dir .. '/' .. filename + local segments = fs.segments(long_name) + local result = '/' .. table.concat(segments, '/') + return result +end + +function lib.loadRules(root_dir) + checkArg(1, root_dir, "string", "nil") + root_dir = (root_dir or rules_path) + lib.rules = {} + lib.rules[fs_key(root_dir, auto_rules)] = {} + + for file in fs.list(root_dir) do + if file:match("%.lua$") then + local path = fs_key(root_dir, file) + local file = io.open(path) + if file then + local load_rule = load("return {" .. file:read("*a") .. "}") + file:close() + if load_rule then + local ok, rule = pcall(load_rule) + if ok and type(rule) == "table" then + local irule = {} + lib.rules[path] = irule + for _,v in ipairs(rule) do + if type(v) == "table" then + table.insert(irule, v) + end + -- else invalid rule + end + end + end + end + end + end +end + +function lib.saveRule(rule_set, path) + checkArg(1, rule_set, "table") + checkArg(2, path, "string") + local file = io.open(path, "w") + if not file then return end -- fs may be read only, totally fine, this just won't persist + for index, irule in ipairs(rule_set) do + file:write(require("serialization").serialize(irule), ",\n") + end + file:close() +end + +function lib.saveRules() + for path, rule_set in pairs(rules) do + lib.saveRule(rule_set, path) + end +end + +local function getIRule(proxy) + checkArg(1, proxy, "table") + for path,rule_set in pairs(lib.rules) do + for index, irule in ipairs(rule_set) do + if irule.address == proxy.address then + return irule, index, rule_set, path + end + end + end +end + +function lib.getDeviceLabel(proxy) + local irule = getIRule(proxy) + if irule and irule.label then + return irule.label + elseif proxy.getLabel then + return proxy.getLabel() + end +end + +function lib.setDeviceLabel(proxy, label) + local irule, index, rule_set, path = getIRule(proxy) + if not irule then + -- if the device supports labels, use it instead + if proxy.setLabel then + return proxy.setLabel(label) + end + path = fs_key(rules_path, auto_rules) + rule_set = lib.rules[path] + index = #rule_set + 1 + irule = {address=proxy.address} + table.insert(rule_set, irule) + end + irule.label = label + lib.saveRule(rule_set, path) +end + +return lib + diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/tools/fsmod.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/tools/fsmod.lua new file mode 100755 index 00000000..b59a314f --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/tools/fsmod.lua @@ -0,0 +1,68 @@ +local filesystem = require("filesystem") + +local lib = {} +function lib.remove(path, findNode) + local function removeVirtual() + local node, rest, vnode, vrest = findNode(filesystem.path(path), false, true) + -- vrest represents the remaining path beyond vnode + -- vrest is nil if vnode reaches the full path + -- thus, if vrest is NOT NIL, then we SHOULD NOT remove children nor links + if not vrest then + local name = filesystem.name(path) + if vnode.children[name] or vnode.links[name] then + vnode.children[name] = nil + vnode.links[name] = nil + while vnode and vnode.parent and not vnode.fs and not next(vnode.children) and not next(vnode.links) do + vnode.parent.children[vnode.name] = nil + vnode = vnode.parent + end + return true + end + end + -- return false even if vrest is nil because this means it was a expected + -- to be a real file + return false + end + local function removePhysical() + node, rest = findNode(path) + if node.fs and rest then + return node.fs.remove(rest) + end + return false + end + local success = removeVirtual() + success = removePhysical() or success -- Always run. + if success then return true + else return nil, "no such file or directory" + end +end + +function lib.rename(oldPath, newPath, findNode) + if filesystem.isLink(oldPath) then + local node, rest, vnode, vrest = findNode(filesystem.path(oldPath)) + local target = vnode.links[filesystem.name(oldPath)] + local result, reason = filesystem.link(target, newPath) + if result then + filesystem.remove(oldPath) + end + return result, reason + else + local oldNode, oldRest = findNode(oldPath) + local newNode, newRest = findNode(newPath) + if oldNode.fs and oldRest and newNode.fs and newRest then + if oldNode.fs.address == newNode.fs.address then + return oldNode.fs.rename(oldRest, newRest) + else + local result, reason = filesystem.copy(oldPath, newPath) + if result then + return filesystem.remove(oldPath) + else + return nil, reason + end + end + end + return nil, "trying to read from or write to virtual directory" + end +end + +return lib diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/tools/full_ls.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/tools/full_ls.lua new file mode 100755 index 00000000..576e6bbc --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/tools/full_ls.lua @@ -0,0 +1,391 @@ +local fs = require("filesystem") +local shell = require("shell") +local term = require("term") +local unicode = require("unicode") + +local dirsArg, ops = shell.parse(...) + +if ops.help then + print([[Usage: ls [OPTION]... [FILE]... + -a, --all do not ignore entries starting with . + --full-time with -l, print time in full iso format + -h, --human-readable with -l and/or -s, print human readable sizes + --si likewise, but use powers of 1000 not 1024 + -l use a long listing format + -r, --reverse reverse order while sorting + -R, --recursive list subdirectories recursively + -S sort by file size + -t sort by modification time, newest first + -X sort alphabetically by entry extension + -1 list one file per line + -p append / indicator to directories + -M display Microsoft-style file and directory + count after listing + --no-color Do not colorize the output (default colorized) + --help display this help and exit +For more info run: man ls]]) + return 0 +end + +if #dirsArg == 0 then + table.insert(dirsArg, ".") +end + +local ec = 0 +local gpu = term.gpu() +local fOut = term.isAvailable() and io.output().tty +local function perr(msg) io.stderr:write(msg,"\n") ec = 2 end +local function stat(names, index) + local name = names[index] + if type(name) == "table" then + return name + end + local info = {} + info.key = name + info.path = name:sub(1, 1) == "/" and "" or names.path + info.name = ops.p and name or name:gsub("/+$", "") + info.sort_name = info.name:gsub("^%.","") + info.full_path = fs.concat(info.path, info.name) + info.isLink, info.link = fs.isLink(info.full_path) + info.isDir = fs.isDirectory(info.full_path) + info.size = info.isLink and 0 or fs.size(info.full_path) + info.time = fs.lastModified(info.full_path) + info.fs = fs.get(info.full_path) + info.ext = info.name:match("(%.[^.]+)$") or "" + names[index] = info + return info +end +local function toArray(i) local r={} for n in i do r[#r+1]=n end return r end +local restore_color = function() end +local set_color = function() end +local prev_color +local function colorize() return prev_color end +if fOut and not ops["no-color"] then + local LSC = os.getenv("LS_COLORS") + if type(LSC) == "string" then + LSC = require("serialization").unserialize(LSC) + end + if not LSC then + perr("ls: unparsable value for LS_COLORS environment variable") + else + prev_color = gpu.getForeground() + restore_color = function() gpu.setForeground(prev_color) end + colorize = function(info) + return + info.isLink and LSC.LINK or + info.isDir and LSC.DIR or + LSC['*'..info.ext] or + LSC.FILE or + prev_color + end + set_color=function(c) + if gpu.getForeground() ~= c then + io.stdout:flush() + gpu.setForeground(c) + end + end + end +end +local msft={reports=0,proxies={}} +function msft.report(files, dirs, used, proxy) + local free = proxy.spaceTotal() - proxy.spaceUsed() + restore_color() + local pattern = "%5i File(s) %11i bytes\n%5i Dir(s) %11s bytes free\n" + io.write(string.format(pattern, files, used, dirs, tostring(free))) +end +function msft.tail(names) + local fsproxy = fs.get(names.path) + if not fsproxy then + return + end + local totalSize, totalFiles, totalDirs = 0, 0, 0 + for i=1,#names do + local info = stat(names, i) + if info.isDir then + totalDirs = totalDirs + 1 + else + totalFiles = totalFiles + 1 + end + totalSize = totalSize + info.size + end + msft.report(totalFiles, totalDirs, totalSize, fsproxy) + local ps = msft.proxies + ps[fsproxy] = ps[fsproxy] or {files=0,dirs=0,used=0} + local p = ps[fsproxy] + p.files = p.files + totalFiles + p.dirs = p.dirs + totalDirs + p.used = p.used + totalSize + msft.reports = msft.reports + 1 +end +function msft.final() + if msft.reports < 2 then return end + local groups = {} + for proxy,report in pairs(msft.proxies) do + table.insert(groups, {proxy=proxy,report=report}) + end + restore_color() + print("Total Files Listed:") + for _,pair in ipairs(groups) do + local proxy, report = pair.proxy, pair.report + if #groups>1 then + print("As pertaining to: "..proxy.address) + end + msft.report(report.files, report.dirs, report.used, proxy) + end +end + +if not ops.M then + msft.tail=function()end + msft.final=function()end +end + +local function nod(n) + return n and (tostring(n):gsub("(%.[0-9]+)0+$","%1")) or "0" +end + +local function formatSize(size) + if not ops.h and not ops['human-readable'] and not ops.si then + return tostring(size) + end + local sizes = {"", "K", "M", "G"} + local unit = 1 + local power = ops.si and 1000 or 1024 + while size > power and unit < #sizes do + unit = unit + 1 + size = size / power + end + return nod(math.floor(size*10)/10)..sizes[unit] +end + +local function pad(txt) + txt = tostring(txt) + return #txt >= 2 and txt or "0"..txt +end + +local function formatDate(epochms) + local day_names={"Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"} + local month_names={"January","February","March","April","May","June","July","August","September","October","November","December"} + if epochms == 0 then return "" end + local d = os.date("*t", epochms) + local day, hour, min, sec = nod(d.day), pad(nod(d.hour)), pad(nod(d.min)), pad(nod(d.sec)) + if ops["full-time"] then + return string.format("%s-%s-%s %s:%s:%s ", d.year, pad(nod(d.month)), pad(day), hour, min, sec) + else + return string.format("%s %+2s %+2s:%+2s ", month_names[d.month]:sub(1,3), day, hour, pad(min)) + end +end + +local function filter(names) + if ops.a then + return names + end + local set = {} + for key, value in pairs(names) do + if type(key) == "number" then + local info = stat(names, key) + if fs.name(info.name):sub(1, 1) ~= "." then + table.insert(set, names[key]) + end + else + set[key] = value + end + end + return set +end + +local function sort(names) + local once = false + local function ni(v) + local vname = type(v) == "string" and v or v.key + for i=1,#names do + local info = stat(names, i) + if info.key == vname then + return i + end + end + end + local function sorter(key) + once = true + table.sort(names, function(a, b) + local ast = stat(names, ni(a)) + local bst = stat(names, ni(b)) + return ast[key] > bst[key] + end) + end + if ops.t then sorter("time") end + if ops.X then sorter("ext") end + if ops.S then sorter("size") end + local rev = ops.r or ops.reverse + if not once then sorter("sort_name") rev=not rev end + if rev then + for i=1,#names/2 do + names[i], names[#names - i + 1] = names[#names - i + 1], names[i] + end + end + return names +end + +local function dig(names, dirs, dir) + if ops.R then + local di = 1 + for i=1,#names do + local info = stat(names, i) + if info.isDir then + local path = dir..(dir:sub(-1) == "/" and "" or "/") + table.insert(dirs, di, path..info.name) + di = di + 1 + end + end + end + return names +end + +local first_display = true +local function display(names) + local mt={} + local lines = setmetatable({}, mt) + if ops.l then + lines.n = #names + local max_size_width = 1 + local max_date_width = 0 + for i=1,lines.n do + local info = stat(names, i) + max_size_width = math.max(max_size_width, formatSize(info.size):len()) + max_date_width = math.max(max_date_width, formatDate(info.time):len()) + end + mt.__index = function(tbl, index) + local info = stat(names, index) + local file_type = info.isLink and 'l' or info.isDir and 'd' or 'f' + local link_target = info.isLink and string.format(" -> %s", info.link:gsub("/+$", "") .. (info.isDir and "/" or "")) or "" + local write_mode = info.fs.isReadOnly() and '-' or 'w' + local size = formatSize(info.size) + local modDate = formatDate(info.time) + local format = "%s-r%s %+"..tostring(max_size_width).."s %"..tostring(max_date_width).."s" + local meta = string.format(format, file_type, write_mode, size, modDate) + local item = info.name..link_target + return {{color = prev_color, name = meta}, {color = colorize(info), name = item}} + end + elseif ops["1"] or not fOut then + lines.n = #names + mt.__index = function(tbl, index) + local info = stat(names, index) + return {{color = colorize(info), name = info.name}} + end + else -- columns + local num_columns, items_per_column, width = 0, 0, term.getViewport() - 1 + local function real(x, y) + local index = y + ((x-1) * items_per_column) + return index <= #names and index or nil + end + local function max_name(column_index) + local max = 0 -- return the width of the max element in column_index + for r=1,items_per_column do + local ri = real(column_index, r) + if not ri then break end + local info = stat(names, ri) + max = math.max(max, unicode.wlen(info.name)) + end + return max + end + local function measure() + local total = 0 + for column_index=1,num_columns do + total = total + max_name(column_index) + (column_index > 1 and 2 or 0) + end + return total + end + while items_per_column<#names do + items_per_column = items_per_column + 1 + num_columns = math.ceil(#names/items_per_column) + if measure() < width then + break + end + end + lines.n = items_per_column + mt.__index=function(tbl, line_index) + return setmetatable({},{ + __len=function()return num_columns end, + __index=function(tbl, column_index) + local ri = real(column_index, line_index) + if not ri then return end + local info = stat(names, ri) + local name = info.name + return {color = colorize(info), name = name .. string.rep(' ', max_name(column_index) - unicode.wlen(name) + (column_index < num_columns and 2 or 0))} + end, + }) + end + end + for line_index=1,lines.n do + local line = lines[line_index] + for element_index=1,#line do + local e = line[element_index] + if not e then break end + first_display = false + set_color(e.color) + io.write(e.name) + end + print() + end + msft.tail(names) +end +local header = function() end +if #dirsArg > 1 or ops.R then + header = function(path) + if not first_display then print() end + restore_color() + io.write(path,":\n") + end +end +local function splitDirsFromFileArgs(dirs) + local trimmed = {} + local files = {} + for _,dir in ipairs(dirs) do + local path = shell.resolve(dir) + if not fs.exists(path) then + perr("cannot access " .. tostring(path) .. ": No such file or directory") + elseif fs.isDirectory(path) then + table.insert(trimmed, dir) + else -- file or link + table.insert(files, dir) + end + end + return files, trimmed +end +local function displayDirList(dirs) + while #dirs > 0 do + local dir = table.remove(dirs, 1) + header(dir) + local path = shell.resolve(dir) + local list, reason = fs.list(path) + if not list then + perr(reason) + else + local names = toArray(list) + names.path = path + display(dig(sort(filter(names)), dirs, dir)) + end + end +end +local dir_set, file_set = {}, {path=shell.getWorkingDirectory()} +for _,dir in ipairs(dirsArg) do + local path = shell.resolve(dir) + local real, why = fs.realPath(path) + local access_msg = "cannot access " .. tostring(path) .. ": " + if not real then + perr(access_msg .. why) + elseif not fs.exists(path) then + perr(access_msg .. "No such file or directory") + elseif fs.isDirectory(path) then + table.insert(dir_set, dir) + else -- file or link + table.insert(file_set, dir) + end +end +io.output():setvbuf("line") +if #file_set > 0 then display(sort(file_set)) end +displayDirList(dir_set) +msft.final() +io.output():flush() +io.output():setvbuf("no") +restore_color() +return ec diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/tools/install_basics.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/tools/install_basics.lua new file mode 100755 index 00000000..9f17b6ca --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/tools/install_basics.lua @@ -0,0 +1,209 @@ +local computer = require("computer") +local shell = require("shell") +local component = require("component") +local event = require("event") +local fs = require("filesystem") +local unicode = require("unicode") +local text = require("text") + +local write = io.write + +local args, options = shell.parse(...) + +options.sources = {} +options.targets = {} +options.source_label = args[1] + +local root_exception + +if options.help then + write([[Usage: install [OPTION]... + --from=ADDR install filesystem at ADDR + default: builds list of + candidates and prompts user + --to=ADDR same as --from but for target + --fromDir=PATH install PATH from source + --root=PATH same as --fromDir but target + --toDir=PATH same as --root + -u, --update update files interactively + --label override label from .prop + --nosetlabel do not label target + --nosetboot do not use target for boot + --noreboot do not reboot after install +]]) + return nil -- exit success +end + +local rootfs = fs.get("/") +if not rootfs then + io.stderr:write("no root filesystem, aborting\n"); + os.exit(1) +end + +local function up_deprecate(old_key, new_key) + if options[new_key] == nil then + options[new_key] = options[old_key] + end + options[old_key] = nil +end + +local function cleanPath(path) + if path then + local rpath = shell.resolve(path) + if fs.isDirectory(rpath) then + return fs.canonical(rpath) .. '/' + end + end +end + +local rootAddress = rootfs.address +-- if the rootfs is read only, it is probably the loot disk! +root_exception = rootAddress +if rootfs.isReadOnly() then + root_exception = nil +end + +-- this may be OpenOS specific, default to "" in case no /dev mount point +local devfsAddress = (fs.get("/dev/") or {}).address or "" + +-- tmp is only valid if specified as an option +local tmpAddress = computer.tmpAddress() + +----- For now, I am allowing source==target -- cp can handle it if the user prepares conditions correctly +----- in other words, install doesn't need to filter this scenario: +--if #options.targets == 1 and #options.sources == 1 and options.targets[1] == options.sources[1] then +-- io.stderr:write("It is not the intent of install to use the same source and target filesystem.\n") +-- return 1 +--end + +------ load options +up_deprecate('noboot', 'nosetboot') +up_deprecate('nolabelset', 'nosetlabel') +up_deprecate('name', 'label') + +options.source_root = cleanPath(options.from) +options.target_root = cleanPath(options.to) + +options.target_dir = fs.canonical(options.root or options.toDir or "") + +options.update = options.u or options.update + +local function path_to_dev(path) + return path and fs.isDirectory(path) and not fs.isLink(path) and fs.get(path) +end + +local source_dev = path_to_dev(options.source_root) +local target_dev = path_to_dev(options.target_root) + +-- use a single for loop of all filesystems to build the list of candidates of sources and targets +local function validDevice(candidate, exceptions, specified, existing) + local address = candidate.dev.address + + for _,e in ipairs(existing) do + if e.dev.address == address then + return + end + end + + if specified then + if address:find(specified, 1, true) == 1 then + return true + end + else + for _,e in ipairs(exceptions) do + if e == address then + return + end + end + return true + end +end + +for dev, path in fs.mounts() do + local candidate = {dev=dev, path=path:gsub("/+$","")..'/'} + + if validDevice(candidate, {devfsAddress, tmpAddress, root_exception}, source_dev and source_dev.address or options.from, options.sources) then + -- root path is either the command line path given for this dev or its natural mount point + local root_path = source_dev == dev and options.source_root or path + if (options.from or fs.list(root_path)()) then -- ignore empty sources unless specified + local prop = fs.open(root_path .. '/.prop') + if prop then + local prop_data = prop:read(math.huge) + prop:close() + prop = prop_data + end + candidate.prop = prop and load('return ' .. prop)() or {} + if not candidate.prop.ignore then + if not options.source_label or options.source_label:lower() == (candidate.prop.label or (dev.getLabel() or "")):lower() then + table.insert(options.sources, candidate) + end + end + end + end + + -- in case candidate is valid for BOTH, we want a new table + candidate = {dev=candidate.dev, path=candidate.path} -- but not the prop + + if validDevice(candidate, {devfsAddress, tmpAddress}, target_dev and target_dev.address or options.to, options.targets) then + if not dev.isReadOnly() then + table.insert(options.targets, candidate) + elseif options.to then + io.stderr:write("Cannot install to " .. options.to .. ", it is read only\n") + os.exit(1) + end + end +end + +local source = options.sources[1] +local target = options.targets[1] +local utils_path = package.searchpath("tools/install_utils", package.path) + +if #options.sources ~= 1 or #options.targets ~= 1 then + source, target = loadfile(utils_path, "bt", _G)('select', options) +end + +if not source then return end +options.source_root = options.source_root or source.path + +if not target then return end +options.target_root = options.target_root or target.path + +-- now that source is selected, we can update options +options.label = options.label or source.prop.label +options.setlabel = source.prop.setlabel and not options.nosetlabel +options.setboot = source.prop.setboot and not options.nosetboot +options.reboot = source.prop.reboot and not options.noreboot +options.source_dir = fs.canonical(source.prop.fromDir or options.fromDir or "") .. '/.' + +local cp_args = +{ + "-vrx" .. (options.update and "ui" or ""), + options.source_root .. options.source_dir, + options.target_root:gsub("//","/") .. options.target_dir +} + +local source_display = (source.prop or {}).label or source.dev.getLabel() or source.path +local special_target = "" +if #options.targets > 1 or options.to then + special_target = " to " .. cp_args[3] +end +io.write("Install " .. source_display .. special_target .. "? [Y/n] ") +if not ((io.read() or "n").."y"):match("^%s*[Yy]") then + write("Installation cancelled\n") + os.exit() +end + +local installer_path = options.source_root .. "/.install" +if fs.exists(installer_path) then + os.exit(loadfile(utils_path, "bt", _G)('install', options)) +end + +return +{ + setlabel = options.setlabel, + label = options.label, + setboot = options.setboot, + reboot = options.reboot, + target = target, + cp_args = cp_args, +} diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/tools/install_utils.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/tools/install_utils.lua new file mode 100755 index 00000000..d70a7b23 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/tools/install_utils.lua @@ -0,0 +1,90 @@ +local cmd, options = ... + +local function select_prompt(devs, prompt) + table.sort(devs, function(a, b) return a.path 1 then + io.write(prompt,'\n') + + for i = 1, #devs do + local src = devs[i] + local label = src.dev.getLabel() + if label then + label = label .. " (" .. src.dev.address:sub(1, 8) .. "...)" + else + label = src.dev.address + end + io.write(i .. ") " .. label .. " at " .. src.path .. '\n') + end + + io.write("Please enter a number between 1 and " .. #devs .. '\n') + io.write("Enter 'q' to cancel the installation: ") + choice = nil + while not choice do + result = io.read() or "q" + if result == "q" then + os.exit() + end + local number = tonumber(result) + if number and number > 0 and number <= #devs then + choice = devs[number] + else + io.write("Invalid input, please try again: ") + end + end + end + + return choice +end + +if cmd == 'select' then + if #options.sources == 0 then + if options.source_label then + io.stderr:write("Nothing to install labeled: " .. options.source_label .. '\n') + elseif options.from then + io.stderr:write("Nothing to install from: " .. options.from .. '\n') + else + io.stderr:write("Nothing to install\n") + end + os.exit(1) + end + + if #options.targets == 0 then + if options.to then + io.stderr:write("No such target to install to: " .. options.to .. '\n') + else + io.stderr:write("No writable disks found, aborting\n") + end + os.exit(1) + end + + local source = select_prompt(options.sources, "What do you want to install?") + if #options.sources > 1 and #options.targets > 1 then + io.write('\n') + end + local target = select_prompt(options.targets, "Where do you want to install to?") + + return source, target + +elseif cmd == 'install' then + local installer_path = options.source_root .. "/.install" + local installer, reason = loadfile(installer_path, "bt", setmetatable({install= + { + from=options.source_root, + to=options.target_root, + fromDir=options.source_dir, + root=options.target_dir, + update=options.update, + label=options.label, + setlabel=options.setlabel, + setboot=options.setboot, + reboot=options.reboot, + }}, {__index=_G})) + if not installer then + io.stderr:write("installer failed to load: " .. tostring(reason) .. '\n') + os.exit(1) + else + return installer() + end +end \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/tools/keyboard_full.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/tools/keyboard_full.lua new file mode 100755 index 00000000..2270420b --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/tools/keyboard_full.lua @@ -0,0 +1,141 @@ +keyboard.keys["1"] = 0x02 +keyboard.keys["2"] = 0x03 +keyboard.keys["3"] = 0x04 +keyboard.keys["4"] = 0x05 +keyboard.keys["5"] = 0x06 +keyboard.keys["6"] = 0x07 +keyboard.keys["7"] = 0x08 +keyboard.keys["8"] = 0x09 +keyboard.keys["9"] = 0x0A +keyboard.keys["0"] = 0x0B +keyboard.keys.a = 0x1E +keyboard.keys.b = 0x30 +keyboard.keys.c = 0x2E +keyboard.keys.d = 0x20 +keyboard.keys.e = 0x12 +keyboard.keys.f = 0x21 +keyboard.keys.g = 0x22 +keyboard.keys.h = 0x23 +keyboard.keys.i = 0x17 +keyboard.keys.j = 0x24 +keyboard.keys.k = 0x25 +keyboard.keys.l = 0x26 +keyboard.keys.m = 0x32 +keyboard.keys.n = 0x31 +keyboard.keys.o = 0x18 +keyboard.keys.p = 0x19 +keyboard.keys.q = 0x10 +keyboard.keys.r = 0x13 +keyboard.keys.s = 0x1F +keyboard.keys.t = 0x14 +keyboard.keys.u = 0x16 +keyboard.keys.v = 0x2F +keyboard.keys.w = 0x11 +keyboard.keys.x = 0x2D +keyboard.keys.y = 0x15 +keyboard.keys.z = 0x2C + +keyboard.keys.apostrophe = 0x28 +keyboard.keys.at = 0x91 +keyboard.keys.back = 0x0E -- backspace +keyboard.keys.backslash = 0x2B +keyboard.keys.capital = 0x3A -- capslock +keyboard.keys.colon = 0x92 +keyboard.keys.comma = 0x33 +keyboard.keys.enter = 0x1C +keyboard.keys.equals = 0x0D +keyboard.keys.grave = 0x29 -- accent grave +keyboard.keys.lbracket = 0x1A +keyboard.keys.lcontrol = 0x1D +keyboard.keys.lmenu = 0x38 -- left Alt +keyboard.keys.lshift = 0x2A +keyboard.keys.minus = 0x0C +keyboard.keys.numlock = 0x45 +keyboard.keys.pause = 0xC5 +keyboard.keys.period = 0x34 +keyboard.keys.rbracket = 0x1B +keyboard.keys.rcontrol = 0x9D +keyboard.keys.rmenu = 0xB8 -- right Alt +keyboard.keys.rshift = 0x36 +keyboard.keys.scroll = 0x46 -- Scroll Lock +keyboard.keys.semicolon = 0x27 +keyboard.keys.slash = 0x35 -- / on main keyboard +keyboard.keys.space = 0x39 +keyboard.keys.stop = 0x95 +keyboard.keys.tab = 0x0F +keyboard.keys.underline = 0x93 + +-- Keypad (and numpad with numlock off) +keyboard.keys.up = 0xC8 +keyboard.keys.down = 0xD0 +keyboard.keys.left = 0xCB +keyboard.keys.right = 0xCD +keyboard.keys.home = 0xC7 +keyboard.keys["end"] = 0xCF +keyboard.keys.pageUp = 0xC9 +keyboard.keys.pageDown = 0xD1 +keyboard.keys.insert = 0xD2 +keyboard.keys.delete = 0xD3 + +-- Function keys +keyboard.keys.f1 = 0x3B +keyboard.keys.f2 = 0x3C +keyboard.keys.f3 = 0x3D +keyboard.keys.f4 = 0x3E +keyboard.keys.f5 = 0x3F +keyboard.keys.f6 = 0x40 +keyboard.keys.f7 = 0x41 +keyboard.keys.f8 = 0x42 +keyboard.keys.f9 = 0x43 +keyboard.keys.f10 = 0x44 +keyboard.keys.f11 = 0x57 +keyboard.keys.f12 = 0x58 +keyboard.keys.f13 = 0x64 +keyboard.keys.f14 = 0x65 +keyboard.keys.f15 = 0x66 +keyboard.keys.f16 = 0x67 +keyboard.keys.f17 = 0x68 +keyboard.keys.f18 = 0x69 +keyboard.keys.f19 = 0x71 + +-- Japanese keyboards +keyboard.keys.kana = 0x70 +keyboard.keys.kanji = 0x94 +keyboard.keys.convert = 0x79 +keyboard.keys.noconvert = 0x7B +keyboard.keys.yen = 0x7D +keyboard.keys.circumflex = 0x90 +keyboard.keys.ax = 0x96 + +-- Numpad +keyboard.keys.numpad0 = 0x52 +keyboard.keys.numpad1 = 0x4F +keyboard.keys.numpad2 = 0x50 +keyboard.keys.numpad3 = 0x51 +keyboard.keys.numpad4 = 0x4B +keyboard.keys.numpad5 = 0x4C +keyboard.keys.numpad6 = 0x4D +keyboard.keys.numpad7 = 0x47 +keyboard.keys.numpad8 = 0x48 +keyboard.keys.numpad9 = 0x49 +keyboard.keys.numpadmul = 0x37 +keyboard.keys.numpaddiv = 0xB5 +keyboard.keys.numpadsub = 0x4A +keyboard.keys.numpadadd = 0x4E +keyboard.keys.numpaddecimal = 0x53 +keyboard.keys.numpadcomma = 0xB3 +keyboard.keys.numpadenter = 0x9C +keyboard.keys.numpadequals = 0x8D + +-- Create inverse mapping for name lookup. +setmetatable(keyboard.keys, +{ + __index = function(tbl, k) + if type(k) ~= "number" then return end + for name,value in pairs(keyboard.keys) do + if value == k then + return name + end + end + end +}) diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/tools/lua_shell.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/tools/lua_shell.lua new file mode 100755 index 00000000..8163ed68 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/tools/lua_shell.lua @@ -0,0 +1,134 @@ +local package = require("package") +local term = require("term") + +local gpu = term.gpu() + +local function optrequire(...) + local success, module = pcall(require, ...) + if success then + return module + end +end + +local env -- forward declare for binding in metamethod +env = setmetatable({}, { + __index = function(t, k) + _ENV[k] = _ENV[k] or optrequire(k) + return _ENV[k] + end, + __pairs = function(self) + local t = self + return function(_, key) + local k, v = next(t, key) + if not k and t == env then + t = _ENV + k, v = next(t) + end + if not k and t == _ENV then + t = package.loaded + k, v = next(t) + end + return k, v + end + end +}) +env._PROMPT = tostring(env._PROMPT or "lua> ") + +local history = {} + +local function findTable(t, path) + if type(t) ~= "table" then return nil end + if not path or #path == 0 then return t end + local name = string.match(path, "[^.]+") + for k, v in pairs(t) do + if k == name then + return findTable(v, string.sub(path, #name + 2)) + end + end + local mt = getmetatable(t) + if t == env then mt = {__index=_ENV} end + if mt then + return findTable(mt.__index, path) + end + return nil +end + +local function findKeys(t, r, prefix, name) + if type(t) ~= "table" then return end + for k, v in pairs(t) do + if type(k) == "string" and string.match(k, "^"..name) then + local postfix = "" + if type(v) == "function" then postfix = "()" + elseif type(v) == "table" and getmetatable(v) and getmetatable(v).__call then postfix = "()" + elseif type(v) == "table" then postfix = "." + end + r[prefix..k..postfix] = true + end + end + local mt = getmetatable(t) + if t == env then mt = {__index=_ENV} end + if mt then + return findKeys(mt.__index, r, prefix, name) + end +end + +local function hint(line, index) + line = (line or "") + local tail = line:sub(index) + line = line:sub(1, index - 1) + local path = string.match(line, "[a-zA-Z_][a-zA-Z0-9_.]*$") + if not path then return nil end + local suffix = string.match(path, "[^.]+$") or "" + local prefix = string.sub(path, 1, #path - #suffix) + local tbl = findTable(env, prefix) + if not tbl then return nil end + local keys = {} + local hints = {} + findKeys(tbl, keys, string.sub(line, 1, #line - #suffix), suffix) + for key in pairs(keys) do + table.insert(hints, key .. tail) + end + return hints +end + +gpu.setForeground(0xFFFFFF) +term.write(_VERSION .. " Copyright (C) 1994-2015 Lua.org, PUC-Rio\n") +gpu.setForeground(0xFFFF00) +term.write("Enter a statement and hit enter to evaluate it.\n") +term.write("Prefix an expression with '=' to show its value.\n") +term.write("Press Ctrl+D to exit the interpreter.\n") +gpu.setForeground(0xFFFFFF) + +while term.isAvailable() do + local foreground = gpu.setForeground(0x00FF00) + term.write(env._PROMPT) + gpu.setForeground(foreground) + local command = term.read(history, nil, hint) + if not command then -- eof + return + end + local code, reason + if string.sub(command, 1, 1) == "=" then + code, reason = load("return " .. string.sub(command, 2), "=stdin", "t", env) + else + code, reason = load(command, "=stdin", "t", env) + end + if code then + local result = table.pack(xpcall(code, debug.traceback)) + if not result[1] then + if type(result[2]) == "table" and result[2].reason == "terminated" then + os.exit(result[2].code) + end + io.stderr:write(tostring(result[2]) .. "\n") + else + for i = 2, result.n do + term.write(require("serialization").serialize(result[i], true, nil, nil, 1) .. "\t", true) + end + if term.getCursor() > 1 then + term.write("\n") + end + end + else + io.stderr:write(tostring(reason) .. "\n") + end +end diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/tools/programLocations.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/tools/programLocations.lua new file mode 100755 index 00000000..9772e00e --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/tools/programLocations.lua @@ -0,0 +1,24 @@ +local computer = require("computer") +local lib = {} + +function lib.locate(path) + for _,lookup in ipairs(computer.getProgramLocations()) do + if lookup[1] == path then + return lookup[2] + end + end +end + +function lib.reportNotFound(path, reason) + checkArg(1, path, "string") + local loot = lib.locate(path) + if loot then + io.stderr:write("The program '" .. path .. "' is currently not installed. To install it:\n" .. + "1. Craft the '" .. loot .. "' floppy disk and insert it into this computer.\n" .. + "2. Run `install " .. loot .. "`") + elseif type(reason) == "string" then + io.stderr:write(path .. ": " .. reason .. "\n") + end +end + +return lib diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/tools/ro_wrapper.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/tools/ro_wrapper.lua new file mode 100755 index 00000000..c9f2f3d1 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/tools/ro_wrapper.lua @@ -0,0 +1,30 @@ +local lib = {} + +function lib.wrap(proxy) + checkArg(1, proxy, "table") + if proxy.isReadOnly() then + return proxy + end + + local function roerr() return nil, "filesystem is readonly" end + return setmetatable({ + rename = roerr, + open = function(path, mode) + checkArg(1, path, "string") + checkArg(2, mode, "string") + if mode:match("[wa]") then + return roerr() + end + return proxy.open(path, mode) + end, + isReadOnly = function() + return true + end, + write = roerr, + setLabel = roerr, + makeDirectory = roerr, + remove = roerr, + }, {__index=proxy}) +end + +return lib diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/tools/transfer.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/tools/transfer.lua new file mode 100755 index 00000000..f70751e0 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/tools/transfer.lua @@ -0,0 +1,251 @@ +local fs = require("filesystem") +local shell = require("shell") +local lib = {} + +local function perr(ops, format, ...) + if format then + io.stderr:write(ops.cmd .. string.format(": " .. format, ...) .. "\n") + ops.exit_code = 1 + return 1 + end +end + +local function contents_check(arg, options, bMustExist) + if arg == "" then + return perr(options, "cannot create regular file '' No such file or directory") + end + local path = shell.resolve(arg) + local content_pattern = "^(%.*)(.?)" + local contents_of, of_dir = arg:reverse():match(content_pattern) + of_dir = of_dir:match("^/?$") + local dots = contents_of and contents_of:len() or 0 + contents_of = of_dir and ({true,true})[dots] + + if (not bMustExist or fs.exists(path)) and of_dir and not fs.isDirectory(path) then + perr(options, "'%s' is not a directory", arg) + os.exit(1) + end + + return contents_of, path +end + +local function areEqual(path1, path2) + local f1, f2 = fs.open(path1, "rb") + if f1 then + f2 = fs.open(path2, "rb") + if f2 then + local result = true + local chunkSize = 4 * 1024 + repeat + local s1, s2 = f1:read(chunkSize), f2:read(chunkSize) + if s1 ~= s2 then + result = false + break + end + until not s1 or not s2 + f2:close() + end + f1:close() + end + assert(f1 and f2, "could not open files for reading: " .. path1 .. ", " .. path2) + return result +end + +local function status(verbose, from, to) + if verbose then + to = to and (" -> " .. to) or "" + io.write(from .. to .. "\n") + end + os.sleep(0) -- allow interrupting +end + +local function prompt(message) + io.write(message .. " [Y/n] ") + local result = io.read() + if not result then -- closed pipe + os.exit(1) + end + return result and (result == "" or result:sub(1, 1):lower() == "y") +end + +local function stat(path, ops, P) + local real, reason = fs.realPath(path) + if not real and not P then + perr(ops, "cannot read '%s': '%s'", path, reason) + return false + end + local isLink, linkTarget = fs.isLink(path) + return true, + real, + reason, + isLink, + linkTarget, + fs.exists(path), + fs.get(path), + real and fs.isDirectory(real) +end + +function lib.recurse(fromPath, toPath, options, origin, top) + local mv = options.cmd == "mv" + local function release(result, reason) + if result and mv and top then + local rm_result = not fs.get(fromPath).isReadOnly() and fs.remove(fromPath) + if not rm_result then + perr(options, "cannot remove '%s': filesystem is readonly", fromPath) + result = false + end + end + return result, reason + end + + local + ok, + fromReal, + fromError, + fromIsLink, + fromLinkTarget, + fromExists, + fromFs, + fromIsDir = stat(fromPath, options, options.P) + if not ok then return nil end + local + ok, + toReal, + toError, + toIsLink, + toLinkTarget, + toExists, + toFs, + toIsDir = stat(toPath, options) + if not ok then os.exit(1) end + if toFs.isReadOnly() then + perr(options, "cannot create target '%s': filesystem is readonly", toPath) + return + end + + local same_path = fromReal == toReal + local same_link = fromIsLink and toIsLink and same_path + + local verbose = options.v + local same_fs = fromFs == toFs + local is_mount = origin[fromReal] + + if mv and is_mount then + return false, string.format("cannot move '%s', it is a mount point", fromPath) + end + + if fromIsLink and options.P and not (toExists and same_path and not toIsLink) then + if toExists and options.n then + return true + end + fs.remove(toPath) + if toExists then + status(verbose, string.format("removed '%s'\n", toPath)) + end + status(verbose, fromPath, toPath) + return release(fs.link(fromLinkTarget, toPath)) + elseif fromIsDir then + if not options.r then + status(true, string.format("omitting directory '%s'", fromPath)) + options.exit_code = 1 + return true + end + if toExists and not toIsDir then + -- my real cp always does this, even with -f, -n or -i. + return nil, "cannot overwrite non-directory '" .. toPath .. "' with directory '" .. fromPath .. "'" + end + if options.x and not top and is_mount then + return true + end + if same_fs then + if (toReal.."/"):find(fromReal.."/",1,true) then + return nil, "cannot write a directory, '" .. fromPath .. "', into itself, '" .. toPath .. "'" + elseif mv then + return os.rename(fromPath, toPath) + end + end + if not toExists then + status(verbose, fromPath, toPath) + fs.makeDirectory(toPath) + end + for file in fs.list(fromPath) do + local result, reason = lib.recurse(fs.concat(fromPath, file), fs.concat(toPath, file), options, origin, false) -- false, no longer top + if not result then + return false, reason + end + end + return release(true) + elseif fromExists then + if toExists then + if same_path then + return nil, "'" .. fromPath .. "' and '" .. toPath .. "' are the same file" + end + if options.n then + return true + end + if options.u and not toIsDir and areEqual(fromReal, toReal) then + return true + end + if options.i then + if not prompt("overwrite '" .. toPath .. "'?") then + return true + end + end + if toIsDir then + return nil, "cannot overwrite directory '" .. toPath .. "' with non-directory" + end + fs.remove(toReal) + end + status(verbose, fromPath, toPath) + return release(fs.copy(fromPath, toPath)) + else + return nil, "'" .. fromPath .. "': No such file or directory" + end +end + +function lib.batch(args, options) + options.exit_code = 0 + + -- standardized options + options.i = options.i and not options.f + options.P = options.P or options.r + + local origin = {} + for dev,path in fs.mounts() do + origin[path] = dev + end + + local toArg = table.remove(args) + local _, toPath = contents_check(toArg, options) + if not toPath then + return 1 + end + local originalToIsDir = fs.isDirectory(toPath) + + for _,arg in ipairs(args) do + -- a "contents of" copy is where src path ends in . or .. + -- a source path ending with . is not sufficient - could be the source filename + local contents_of, fromPath = contents_check(arg, options, true) + if fromPath then + -- we do not append fromPath name to toPath in case of contents_of copy + local nextPath = toPath + if contents_of and options.cmd == "mv" then + perr(options, "invalid move path '%s'", arg) + else + if not contents_of and originalToIsDir then + nextPath = fs.concat(nextPath, fs.name(fromPath)) + end + + local result, reason = lib.recurse(fromPath, nextPath, options, origin, true) + + if not result then + perr(options, reason) + end + end + end + end + + return options.exit_code +end + +return lib \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/transforms.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/transforms.lua new file mode 100755 index 00000000..630d8332 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/transforms.lua @@ -0,0 +1,191 @@ +local lib={} +lib.internal={} +function lib.internal.range_adjust(f,l,s) + checkArg(1,f,'number','nil') + checkArg(2,l,'number','nil') + checkArg(3,s,'number') + if f==nil then f=1 elseif f<0 then f=s+f+1 end + if l==nil then l=s elseif l<0 then l=s+l+1 end + return f,l +end +function lib.internal.table_view(tbl,f,l) + return setmetatable({}, + { + __index=function(b,k)return(type(k)~='number'or(k>=f and k<=l))and tbl[k]or nil end, + __len=function(b)return#tbl end, + }) +end +local adjust=lib.internal.range_adjust +local view=lib.internal.table_view + +-- first(p1,p2) searches for the first range in p1 that satisfies p2 +function lib.first(tbl,pred,f,l) + checkArg(1,tbl,'table') + checkArg(2,pred,'function','table') + if type(pred)=='table'then + local set;set,pred=pred,function(e,fi,tbl) + for vi=1,#set do + local v=set[vi] + if lib.begins(tbl,v,fi) then return true,#v end + end + end + end + local s=#tbl + f,l=adjust(f,l,s) + tbl=view(tbl,f,l) + for i=f,l do + local si,ei=pred(tbl[i],i,tbl) + if si then + return i,i+(ei or 1)-1 + end + end +end + +-- returns true if p1 at first p3 equals element for element p2 +function lib.begins(tbl,v,f,l) + checkArg(1,tbl,'table') + checkArg(2,v,'table') + local vs=#v + f,l=adjust(f,l,#tbl) + if vs>(l-f+1)then return end + for i=1,vs do + if tbl[f+i-1]~=v[i] then return end + end + return true +end + +-- works like string.sub but on elements of an indexed table +function --[[@delayloaded-start@]] lib.sub(tbl,f,l) + checkArg(1,tbl,'table') + local r,s={},#tbl + f,l=adjust(f,l,s) + l=math.min(l,s) + for i=math.max(f,1),l do + r[#r+1]=tbl[i] + end + return r +end --[[@delayloaded-end@]] + +-- if value was made by lib.sub then find can find from whence +function --[[@delayloaded-start@]] lib.find(tbl, sub, first, last) + checkArg(1, tbl, 'table') + checkArg(2, sub, 'table') + local sub_len = #sub + return lib.first(tbl, function(element, index, projected_table) + for n=0,sub_len-1 do + if projected_table[n + index] ~= sub[n + 1] then return nil end + end + return 1, sub_len + end, first, last) +end --[[@delayloaded-end@]] + +-- Returns a list of subsets of tbl where partitioner acts as a delimiter. +function --[[@delayloaded-start@]] lib.partition(tbl,partitioner,dropEnds,f,l) + checkArg(1,tbl,'table') + checkArg(2,partitioner,'function','table') + checkArg(3,dropEnds,'boolean','nil') + if type(partitioner)=='table'then + return lib.partition(tbl,function(e,i,tbl) + return lib.first(tbl,partitioner,i) + end,dropEnds,f,l) + end + local s=#tbl + f,l=adjust(f,l,s) + local cut=view(tbl,f,l) + local result={} + local need=true + local exp=function()if need then result[#result+1]={}need=false end end + local i=f + while i<=l do + local e=cut[i] + local ds,de=partitioner(e,i,cut) + -- true==partition here + if ds==true then ds,de=i,i + elseif ds==false then ds,de=nil,nil end + if ds~=nil then + ds,de=adjust(ds,de,l) + ds=ds>=i and ds--no more + end + if not ds then -- false or nil + exp() + table.insert(result[#result],e) + else + local sub=lib.sub(cut,i,not dropEnds and de or (ds-1)) + if #sub>0 then + exp() + result[#result+math.min(#result[#result],1)]=sub + end + -- ensure i moves forward + local ensured=math.max(math.max(de or ds,ds),i) + if de and ds and de 0 then + result = result .. "-" + end + for i = 1,set do + result = result .. string.format("%02x", math.random(0, 255)) + end + end + + return result +end + +return uuid diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/vector.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/vector.lua new file mode 100755 index 00000000..ab4d8712 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/vector.lua @@ -0,0 +1,71 @@ + +local vectorLibrary = {} + +------------------------------------------------------------------------------------------------------------------------ + +function vectorLibrary.newVector2(x, y) + return { x, y } +end + +function vectorLibrary.newVector3(x, y, z) + return { x, y, z } +end + +function vectorLibrary.newVector4(x, y, z, w) + return { x, y, z, w } +end + +function vectorLibrary.newVector5(x, y, z, u, v) + return { x, y, z, u, v } +end + +------------------------------------------------------------------------------------------------------------------------ + +function vectorLibrary.scalarMultiply(vectorA, vectorB) + local result = 0 + for dismension = 1, #vectorA do + result = result + vectorA[dismension] * vectorB[dismension] + end + + return result +end + +function vectorLibrary.length(vector) + local result = 0 + for dismension = 1, #vector do + result = result + vector[dismension] ^ 2 + end + + return math.sqrt(result) +end + +function vectorLibrary.normalize(vector) + local invertedLength = 1 / vectorLibrary.length(vector) + vector[1], vector[2], vector[3] = vector[1] * invertedLength, vector[2] * invertedLength, vector[3] * invertedLength + + return vector +end + +function vectorLibrary.getSurfaceNormal(vector1, vector2, vector3) + return { + vector1[2] * (vector2[3] - vector3[3]) + vector2[2] * (vector3[3] - vector1[3]) + vector3[2] * (vector1[3] - vector2[3]), + vector1[3] * (vector2[1] - vector3[1]) + vector2[3] * (vector3[1] - vector1[1]) + vector3[3] * (vector1[1] - vector2[1]), + vector1[1] * (vector2[2] - vector3[2]) + vector2[1] * (vector3[2] - vector1[2]) + vector3[1] * (vector1[2] - vector2[2]) + } +end + +function vectorLibrary.toString(vector) + local result = "(" + for dismension = 1, #vector do + result = result .. string.format("%.2f", vector[dismension]) + if dismension < #vector then + result = result .. "; " + end + end + return result .. ")" +end + +------------------------------------------------------------------------------------------------------------------------ + +return vectorLibrary + diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/web.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/web.lua new file mode 100755 index 00000000..a20ea846 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/web.lua @@ -0,0 +1,156 @@ + +local component = require("component") +local fs = require("filesystem") +local web = {} + +---------------------------------------------------------------------------------------------------- + +local function serializeTable(table, currentData) + local result = "" + + for key, value in pairs(table) do + local keyType, valueType = type(key), type(value) + + if keyType == "number" then + key = key - 1 + end + + if valueType == "table" then + result = result .. serializeTable(value, currentData .. "[" .. key .. "]") + else + result = result .. currentData .. "[" .. key .. "]=" .. tostring(value) .. "&" + end + end + + return result +end + +function web.serialize(data) + if type(data) == "table" then + local serializedData = "" + + for key, value in pairs(data) do + if type(value) == "table" then + serializedData = serializedData .. serializeTable(value, key) + else + serializedData = serializedData .. key .. "=" .. tostring(value) .. "&" + end + end + + return serializedData + else + return tostring(data) + end +end + +function web.encode(data) + if data then + data = string.gsub(data, "([^%w ])", function(char) + return string.format("%%%02X", string.byte(char)) + end) + data = string.gsub(data, " ", "+") + end + + return data +end + +---------------------------------------------------------------------------------------------------- + +function web.rawRequest(url, postData, headers, chunkHandler, chunkSize) + if postData then + postData = web.serialize(postData) + end + + local pcallSuccess, requestHandle, requestReason = pcall(component.internet.request, url, postData, headers) + if pcallSuccess then + if requestHandle then + while true do + local chunk, reason = requestHandle.read(chunkSize or math.huge) + if chunk then + chunkHandler(chunk) + else + requestHandle:close() + if reason then + return false, reason + else + return true + end + end + end + else + return false, "Invalid URL-address" + end + else + return false, "Invalid arguments to component.internet.request" + end +end + +function web.request(url, postData, headers) + local data = "" + local success, reason = web.rawRequest(url, postData, headers, function(chunk) + data = data .. chunk + end) + + if success then + return data + else + return false, reason + end +end + +function web.download(url, path) + fs.makeDirectory(fs.path(path) or "") + + local handle, reason = io.open(path, "w") + if handle then + local success, reason = web.rawRequest(url, nil, nil, function(chunk) + handle:write(chunk) + end) + + handle:close() + if success then + return true + else + fs.remove(path) + return false, reason + end + else + return false, "Failed to open file for writing: " .. tostring(reason) + end +end + +function web.run(url, ...) + local result, reason = web.request(url) + if result then + result, reason = load(result) + if result then + result = { pcall(result, ...) } + if result[1] then + return table.unpack(result, 2) + else + return false, "Failed to run script: " .. tostring(result[2]) + end + else + return false, "Failed to run script: " .. tostring(loadReason) + end + else + return false, reason + end +end + +---------------------------------------------------------------------------------------------------- + +-- print( +-- web.run( +-- "https://raw.githubusercontent.com/IgorTimofeev/OpenComputers/master/Screensavers/Matrix.lua" +-- ) +-- ) + +---------------------------------------------------------------------------------------------------- + +return web + + + + + diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/xmlParser.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/xmlParser.lua new file mode 100755 index 00000000..a7527eaa --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/lib/xmlParser.lua @@ -0,0 +1,59 @@ + +local xml = {} + +------------------------------------------------------------------------------------------------------------ + +function xml.parseargs(s) + local arg = {} + string.gsub(s, "([%-%w]+)=([\"'])(.-)%2", function (w, _, a) + arg[w] = a + end) + return arg +end + +function xml.collect(s) + local stack = {} + local top = {} + table.insert(stack, top) + local ni,c,label,xarg, empty + local i, j = 1, 1 + while true do + ni,j,c,label,xarg, empty = string.find(s, "<(%/?)([%w:]+)(.-)(%/?)>", i) + if not ni then break end + local text = string.sub(s, i, ni-1) + if not string.find(text, "^%s*$") then + table.insert(top, text) + end + if empty == "/" then -- empty element tag + table.insert(top, {label=label, xarg=xml.parseargs(xarg), empty=1}) + elseif c == "" then -- start tag + top = {label=label, xarg=xml.parseargs(xarg)} + table.insert(stack, top) -- new level + else -- end tag + local toclose = table.remove(stack) -- remove top + top = stack[#stack] + if #stack < 1 then + error("nothing to close with "..label) + end + if toclose.label ~= label then + error("trying to close "..toclose.label.." with "..label) + end + table.insert(top, toclose) + end + i = j+1 + end + local text = string.sub(s, i) + if not string.find(text, "^%s*$") then + table.insert(stack[#stack], text) + end + if #stack > 1 then + error("unclosed "..stack[#stack].label) + end + return stack[1] +end + +------------------------------------------------------------------------------------------------------------ + +return xml + + diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/map.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/map.lua new file mode 100755 index 00000000..c11a1e46 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/map.lua @@ -0,0 +1,7 @@ + +local image = + + + + + diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/market.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/market.lua new file mode 100755 index 00000000..5e34a1fb --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/market.lua @@ -0,0 +1,292 @@ + +---------------------------------------------------- Libraries ---------------------------------------------------- + +package.loaded.windows = nil +package.loaded.GUI = nil + +require("advancedLua") +local sides = require("sides") +local computer = require("computer") +local component = require("component") +local fs = require("filesystem") +local buffer = require("doubleBuffering") +local GUI = require("GUI") +local windows = require("windows") +local MineOSCore = require("MineOSCore") +local event = require("event") +local unicode = require("unicode") +local ecs = require("ECSAPI") +local image = require("image") +local bigLetters = require("bigLetters") +local internet = component.internet +local transposer = component.transposer + +---------------------------------------------------- Constants ---------------------------------------------------- + +local scriptAddress = "http://94.242.34.251:8888/MinecraftMarket/market.php?" +local colors = { + background = 0x1B1B1B, + tradeWidget = { + background = 0xDDDDDD, + text = 0x262626 + } +} +local currentUser +local mainWindow + +local function databaseRequest(url) + url = scriptAddress .. url + url = url:gsub(" ", "%%20") + local success, response = ecs.internetRequest(url) + if success then + -- ecs.error("return " .. response) + response = load("return " .. response)() + else + GUI.error(response, {title = {color = 0xFFDB40, text = "URL request failed"}}) + end + return success, response +end + +------------------------------------------------ Trades tab ------------------------------------------------ + +local function getTrades(offset, count, search) + return databaseRequest("getTrades&offset=" .. offset .. "&count=" .. count .. (search and "&search=" .. search or "")) +end + +local function getMyTrades() + return databaseRequest("getTrades&user=" .. currentUser.name) +end + +local function newTradeWidget(y, trades, i, isMyTrade) + local object = GUI.container(1, y,mainWindow.contentContainer.width, 3) + + object:addPanel(1, 1, object.width, object.height, colors.tradeWidget.background) + object.itemIDLabel = object:addLabel(2, 2, 30, 1, colors.tradeWidget.text, trades[i].itemLabel) + if isMyTrade then + object:addButton(object.width - 13, 1, 14, object.height, 0xFF5555, 0xFFFFFF, 0xAA2222, 0xFFFFFF, "Убрать").onTouch = function() + + end + else + object:addButton(object.width - 13, 1, 14, object.height, 0x66B680, 0xFFFFFF, 0x004900, 0xFFFFFF, "Купить").onTouch = function() + + end + end + + return object +end + +local function updateTrades(offset, count, search) + mainWindow.contentContainer:deleteChildren(2) + local myTradesSuccess, myTradesResponse = getMyTrades() + local allTradesSuccesss, allTradesResponse = getTrades(offset, count, search) + + if myTradesSuccess and allTradesSuccesss then + local y = 1 + -- if #myTradesResponse.trades > 0 then + -- for i = 1, #myTradesResponse.trades do + -- mainWindow.contentContainer:addChild(newTradeWidget(y, myTradesResponse.trades, i, true)) + -- y = y + 4 + -- end + -- end + + -- mainWindow.contentContainer.searchTextBox.localPosition.y = y + y = y + 2 + for i = 1, #allTradesResponse.trades do + mainWindow.contentContainer:addChild(newTradeWidget(y, allTradesResponse.trades, i)) + y = y + 4 + end + end +end + +local function showTrades(offset, count) + mainWindow.contentContainer.searchTextBox = mainWindow.contentContainer:addInputTextBox(math.floor(mainWindow.width / 2 - 20), 1, 40, 1, 0x262626, 0x777777, 0x262626, 0xDDDDDD, nil, "Давай найдем че-нить", true) + mainWindow.contentContainer.searchTextBox.onInputFinished = function() + updateTrades(offset, count, mainWindow.contentContainer.searchTextBox.text) + end + updateTrades(offset, count) +end + +------------------------------------------------ Inventory tab ------------------------------------------------ + +local function getUser(nickname) + return databaseRequest("getUser=" .. nickname) +end + +local function updateCurrentUser(nickname) + local success, response = getUser(nickname) + if success then + currentUser = response + end +end + +local function newInventoryItemWidget(x, y, width, height, inventory, inventoryID, background, foreground1, foreground2) + local object = GUI.container(x, y, width, height) + object.inventoryID = inventoryID + object:addPanel(1, 1, object.width, object.height, background) + object:addLabel(2, 3, object.width - 2, 1, foreground1, inventory[inventoryID].label):setAlignment(GUI.alignment.horizontal.center, GUI.alignment.vertical.top) + if unicode.len(inventory[inventoryID].label) > object.width - 2 then + object:addLabel(2, 4, object.width - 2, 1, foreground1, unicode.sub(inventory[inventoryID].label, object.width - 1, -1)):setAlignment(GUI.alignment.horizontal.center, GUI.alignment.vertical.top) + end + object:addLabel(2, 6, object.width - 2, 1, foreground2, math.shortenNumber(tonumber(inventory[inventoryID].count), 2)):setAlignment(GUI.alignment.horizontal.center, GUI.alignment.vertical.top) + return object +end + +local function digitize(side, nickname) + local items = "" + for slot = 1, transposer.getInventorySize(side) do + local stack = transposer.getStackInSlot(side, slot) + if stack then + items = items .. "&items[" .. (slot - 1) .. "][id]=" .. stack.name + items = items .. "&items[" .. (slot - 1) .. "][data]=" .. stack.damage + items = items .. "&items[" .. (slot - 1) .. "][label]=" .. stack.label + items = items .. "&items[" .. (slot - 1) .. "][count]=" .. stack.size + end + end + local file = io.open("PIDORAS.lua", "w") + file:write(scriptAddress .. "addItemsToInventory=" .. nickname .. items) + file:close() + local success, response = databaseRequest("addItemsToInventory=" .. nickname .. items) + if success and response.success then + ecs.error("Оцифровочка завершена") + end +end + +local function updateInventory() + mainWindow.contentContainer:deleteChildren(3) + + local width, height = 16, 8 + local xCountOfItems = math.floor((mainWindow.contentContainer.width - mainWindow.contentContainer.itemDetailContainer.width - 2) / width) + local yCountOfItems = math.floor((mainWindow.contentContainer.height - 4) / height) + local totalCountOfItems = xCountOfItems * yCountOfItems + local fromItem = (mainWindow.contentContainer.currentPage - 1) * totalCountOfItems + 1 + mainWindow.contentContainer.countOfPages = math.ceil(#currentUser.inventory / totalCountOfItems) + + local x, y, chessStep, background, foreground1, foreground2 = 1, 1, true + for j = 1, yCountOfItems do + for i = 1, xCountOfItems do + if currentUser.inventory[fromItem] then + if fromItem == mainWindow.contentContainer.currentItem then + background, foreground1, foreground2 = 0x66B680, 0xFFFFFF, 0x0 + else + foreground1, foreground2 = 0x262626, 0x777777 + if chessStep then background = 0xEEEEEE else background = 0xDDDDDD end + end + + local object = mainWindow.contentContainer:addChild(newInventoryItemWidget(x, y, width, height, currentUser.inventory, fromItem, background, foreground1, foreground2)) + for i = 1, #object.children do + object.children[i].onTouch = function() + mainWindow.contentContainer.currentItem = object.inventoryID + updateInventory() + end + end + else + break + end + + x, fromItem, chessStep = x + width, fromItem + 1, not chessStep + end + x, y = 1, y + height + end + + if currentUser.inventory[mainWindow.contentContainer.currentItem] then + mainWindow.contentContainer.itemTextBox.lines = { + "ID: " .. currentUser.inventory[mainWindow.contentContainer.currentItem].id, + "Data: " .. currentUser.inventory[mainWindow.contentContainer.currentItem].data, + "Label: " .. currentUser.inventory[mainWindow.contentContainer.currentItem].label, + "Count: " .. currentUser.inventory[mainWindow.contentContainer.currentItem].count, + } + end + + mainWindow.contentContainer.pagesContainer.pageLabel.text = mainWindow.contentContainer.currentPage .. " из " .. mainWindow.contentContainer.countOfPages +end + +local function showInventory() + mainWindow.contentContainer.currentPage = 1 + mainWindow.contentContainer.currentItem = 1 + mainWindow.contentContainer.countOfPages = 1 + + local width = 32 + mainWindow.contentContainer.itemDetailContainer = mainWindow.contentContainer:addContainer(mainWindow.contentContainer.width - width + 1, 1, width, mainWindow.contentContainer.height) + mainWindow.contentContainer.itemDetailContainer:addPanel(1, 1, mainWindow.contentContainer.itemDetailContainer.width, mainWindow.contentContainer.itemDetailContainer.height, 0xEEEEEE) + + mainWindow.contentContainer.itemTextBox = mainWindow.contentContainer.itemDetailContainer:addTextBox(2, 2, mainWindow.contentContainer.itemDetailContainer.width - 2, 5, nil, 0x262626, {}, 1, 0, 0) + + mainWindow.contentContainer.itemDetailContainer:addButton(1, mainWindow.contentContainer.itemDetailContainer.height - 8, mainWindow.contentContainer.itemDetailContainer.width, 3, 0x669280, 0xFFFFFF, 0x339240, 0xFFFFFF, "Оцифровать").onTouch = function(eventData) + digitize(sides.up, eventData[6]) + updateCurrentUser(eventData[6]) + updateInventory() + end + mainWindow.contentContainer.itemDetailContainer:addButton(1, mainWindow.contentContainer.itemDetailContainer.height - 5, mainWindow.contentContainer.itemDetailContainer.width, 3, 0x66B680, 0xFFFFFF, 0x339240, 0xFFFFFF, "Материализовать") + mainWindow.contentContainer.itemDetailContainer:addButton(1, mainWindow.contentContainer.itemDetailContainer.height - 2, mainWindow.contentContainer.itemDetailContainer.width, 3, 0x66DB80, 0xFFFFFF, 0x339240, 0xFFFFFF, "Продать") + + width = 25 + mainWindow.contentContainer.pagesContainer = mainWindow.contentContainer:addContainer(mainWindow.contentContainer.width - mainWindow.contentContainer.itemDetailContainer.width - width - 1, mainWindow.contentContainer.height - 2, width, 3) + mainWindow.contentContainer.pagesContainer:addPanel(1, 1, mainWindow.contentContainer.pagesContainer.width, mainWindow.contentContainer.pagesContainer.height, 0xEEEEEE) + mainWindow.contentContainer.pagesContainer.pageLabel = mainWindow.contentContainer.pagesContainer:addLabel(6, 2, mainWindow.contentContainer.pagesContainer.width - 10, 1, 0x262626, ""):setAlignment(GUI.alignment.horizontal.center, GUI.alignment.vertical.top) + mainWindow.contentContainer.pagesContainer:addButton(1, 1, 5, mainWindow.contentContainer.pagesContainer.height, 0x66B680, 0xFFFFFF, 0x004900, 0xFFFFFF, "<").onTouch = function() + if mainWindow.contentContainer.currentPage > 1 then + mainWindow.contentContainer.currentPage = mainWindow.contentContainer.currentPage - 1 + updateInventory() + end + end + mainWindow.contentContainer.pagesContainer:addButton(mainWindow.contentContainer.pagesContainer.width - 4, 1, 5, mainWindow.contentContainer.pagesContainer.height, 0x66B680, 0xFFFFFF, 0x004900, 0xFFFFFF, ">").onTouch = function() + if mainWindow.contentContainer.currentPage < mainWindow.contentContainer.countOfPages then + mainWindow.contentContainer.currentPage = mainWindow.contentContainer.currentPage + 1 + updateInventory() + end + end + + updateInventory() +end + +------------------------------------------------ Mainwindow ------------------------------------------------ + +local function createWindow() + mainWindow = windows.fullScreen() + mainWindow.backgroundPanel = mainWindow:addPanel(1, 1, mainWindow.width, mainWindow.height, colors.background) + mainWindow.tabBar = mainWindow:addTabBar(1, 1, mainWindow.width, 3, 1, 0xDDDDDD, 0x262626, 0xC4C4C4, 0x262626, "Купить", "Инвентарь", "Лотерея") + mainWindow.tabBar.onTabSwitched = function(newTab, eventData) + mainWindow.contentContainer:deleteChildren() + if newTab == 1 then + updateCurrentUser(eventData[6]) + showTrades(0, 50) + elseif newTab == 2 then + updateCurrentUser(eventData[6]) + showInventory(1, 1) + end + end + mainWindow.contentContainer = mainWindow:addContainer(3, 5, mainWindow.width - 4, mainWindow.height - 5) + + mainWindow.onAnyEvent = function(eventData) + if eventData[1] == "scroll" then + if mainWindow.tabBar.selectedTab == 1 then + for i = 1, #mainWindow.contentContainer.children do + mainWindow.contentContainer.children[i].localPosition.y = mainWindow.contentContainer.children[i].localPosition.y + (eventData[5] == 1 and 2 or -2) + end + end + end + mainWindow:draw() + buffer.draw() + end +end + +---------------------------------------------------- Meow-meow ---------------------------------------------------- + +buffer.start() +createWindow() + +mainWindow:draw() +buffer.draw() +mainWindow:handleEvents(1) + + + + + + + + + + + + diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/memTest.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/memTest.lua new file mode 100644 index 00000000..745f8c91 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/memTest.lua @@ -0,0 +1,18 @@ + + +local computer = require("computer") + + +local f = function(abc) for i = 1, 10 do print("aefaef") end end +local t = {a = {b = {c = {}}}} +for i = 1, 1000 do + table.insert(t.a.b.c, 123) +end +local zalupa = {} +local m = computer.freeMemory() +for i = 1, 2 do + zalupa[i] = f +end +local cyka = m - computer.freeMemory() +print("RASHOD", cyka) +print(l) \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/miniPaletteGenerator.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/miniPaletteGenerator.lua new file mode 100755 index 00000000..d0ba5461 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/miniPaletteGenerator.lua @@ -0,0 +1,52 @@ + +local libraries = { + advancedLua = "advancedLua", + colorlib = "colorlib", + image = "image", + buffer = "doubleBuffering", +} + +for library in pairs(libraries) do if not _G[library] then _G[library] = require(libraries[library]) end end; libraries = nil + + +local function createPalette(width, height, hue) + local picture = image.create(width, height) + local saturation, brightness = 0, 100 + local saturationStep, brightnessStep = 100 / width, 100 / (height * 2) + for j = 1, height do + for i = 1, width do + local background = colorlib.HSBtoHEX(hue, saturation, brightness) + local foreground = colorlib.HSBtoHEX(hue, saturation, brightness - brightnessStep) + image.set(picture, i, j, background, foreground, 0x0, "▄") + saturation = saturation + saturationStep + end + saturation = 0 + brightness = brightness - brightnessStep - brightnessStep + end + return picture +end + +buffer.clear(0xFFFFFF) +buffer.draw(true) + +local hues = {0, } +local x, y = 2, 2 +local paletteWidth, paletteHeight = 8, 4 +for hue = 0, 360, 3.5 do + local picture = createPalette(paletteWidth, paletteHeight, hue) + buffer.image(x, y, picture) + x = x + picture.width + 2 + if x >= buffer.screen.width - paletteWidth then + x = 2 + y = y + paletteHeight + 1 + end +end + +buffer.draw() + +-- local saveID = 5 +-- image.save("palette" .. saveID .. ".pic", createPalette(paletteWidth, paletteHeight, hues[saveID]), 1) + + + + diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/multiScreen.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/multiScreen.lua new file mode 100755 index 00000000..f50c75ce --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/multiScreen.lua @@ -0,0 +1,313 @@ + +require("advancedLua") +local ecs = require("ECSAPI") +local components = require("component") +local serialization = require("serialization") +local fs = require("filesystem") +local event = require("event") +local unicode = require("unicode") +local bit32 = require("bit32") +local color = require("color") +local gpu = components.gpu + +-------------------------------------------------------------------------------------------------------------------------------------------- + +local mainScreenAddress = gpu.getScreen() +local pathToConfigFile = "/MultiScreen.cfg" + +local colors = { + background = 0x262626, + foreground = 0xDDDDDD, + currentScreen = ecs.colors.green, + screen = 0xDDDDDD, +} + +local baseResolution = { + width = 146, + height = 54, +} + +local monitors = {} + +-------------------------------------------------------------------------------------------------------------------------------------------- + +local currentBackground, currentForeground, currentAddress = 0x000000, 0xffffff, "" + +local function multiScreenSet(x, y, background, foreground, text) + local xMonitor = math.ceil(x / monitors.screenResolutionByWidth) + local yMonitor = math.ceil(y / monitors.screenResolutionByHeight) + + if monitors[xMonitor] and monitors[xMonitor][yMonitor] then + if currentAddress ~= monitors[xMonitor][yMonitor].address then + gpu.bind(monitors[xMonitor][yMonitor].address, false) + gpu.setBackground(background) + gpu.setForeground(foreground) + + currentBackground, currentForeground = background, foreground + + currentAddress = monitors[xMonitor][yMonitor].address + end + + if currentBackground ~= background then + gpu.setBackground(background) + currentBackground = background + end + + if currentForeground ~= foreground then + gpu.setForeground(foreground) + currentForeground = foreground + end + + gpu.set(x - (xMonitor - 1) * monitors.screenResolutionByWidth, y - (yMonitor - 1) * monitors.screenResolutionByHeight, text) + end +end + +local function multiScreenClear(color) + for address in components.list("screen") do + if address ~= mainScreenAddress then + gpu.bind(address, false) + gpu.setResolution(baseResolution.width, baseResolution.height) + gpu.setDepth(8) + gpu.setBackground(0x0) + gpu.setForeground(0xffffff) + gpu.fill(1, 1, baseResolution.width, baseResolution.height, " ") + end + end + + gpu.bind(mainScreenAddress, false) +end + +-------------------------------------------------------------------------------------------------------------------------------------------- + +local function getAllConnectedScreens() + local massiv = {} + for address in pairs(components.list("screen")) do + table.insert(massiv, address) + end + return massiv +end + +local function configurator() + fs.makeDirectory(fs.path(pathToConfigFile)) + + local data = ecs.universalWindow("auto", "auto", 40, 0xeeeeee, true, {"EmptyLine"}, {"CenterText", 0x880000, "Здорово, ебана!"}, {"EmptyLine"}, {"WrappedText", 0x262626, "Добро пожаловать в программу конфигурации мультимонитора. Вам необходимо указать количество мониторов по ширине и высоте, которые вы желаете объединить, а также выбрать желаемый масштаб."}, {"EmptyLine"}, {"Input", 0x262626, 0x880000, "Ширина"}, {"Input", 0x262626, 0x880000, "Высота"}, {"Slider", 0x262626, 0x880000, 1, 100, 100, "Масштаб: ", "%"}, {"EmptyLine"}, {"Button", {ecs.colors.orange, 0xffffff, "Подтвердить"}, {0x777777, 0xffffff, "Отмена"}}) + local width, height, scale = tonumber(data[1]), tonumber(data[2]), tonumber(data[3]) / 100 + if data[4] == "Отмена" then + ecs.prepareToExit() + print("Калибровка отменена!") + os.exit() + end + + baseResolution.width, baseResolution.height = math.floor(baseResolution.width * scale), math.floor(baseResolution.height * scale) + + -- ecs.error(baseResolution.width .. "x" ..baseResolution.height .. " ccale = " ..scale) + + local countOfConnectedScreens = #getAllConnectedScreens() + + while ((countOfConnectedScreens - 1) < width * height) do + data = ecs.universalWindow("auto", "auto", 44, 0xeeeeee, true, {"EmptyLine"}, {"WrappedText", 0x262626, "Теперь вам необходимо подключить внешние мониторы. Вы указали, что собираетесь сделать мультимонитор из " .. width*height .. " мониторов, но на данный момент вы подключили " .. countOfConnectedScreens - 1 .. " мониторов. Так что подключайте все так, как указали, и жмите \"Далее\"."}, {"EmptyLine"}, {"Button", {ecs.colors.orange, 0xffffff, "Далее"}, {0x777777, 0xffffff, "Отмена"}}) + if data[1] == "Отмена" then + ecs.prepareToExit() + print("Калибровка отменена!") + os.exit() + end + countOfConnectedScreens = #getAllConnectedScreens() + end + + ---- + + local w, h = 8, 3 + local xC, yC = 1, 1 + local xSize, ySize = gpu.getResolution() + + local function drawMonitors() + ecs.clearScreen(colors.background) + local x, y = 3, 2 + local xPos, yPos = x, y + for j = 1, height do + for i = 1, width do + if j == yC and i == xC then + ecs.square(xPos, yPos, w, h, colors.currentScreen) + else + ecs.square(xPos, yPos, w, h, colors.screen) + end + xPos = xPos + w + 2 + end + yPos = yPos + h + 1 + xPos = x + end + + gpu.setBackground(colors.background) + gpu.setForeground(colors.foreground) + ecs.centerText("x", ySize - 5, "Начинаем процесс калибровки. Коснитесь монитора, подсвеченного зеленым цветом.") + ecs.centerText("x", ySize - 4, "Не нарушайте порядок прокосновений!") + end + + ecs.prepareToExit() + print("Идет подготовка мониторов...") + multiScreenClear(0x0) + + monitors = {} + local monitorCount = width * height + local counter = 1 + while counter <= monitorCount do + drawMonitors() + local e = {event.pull("touch")} + if e[2] ~= mainScreenAddress then + local exists = false + for x = 1, #monitors do + for y = 1, #monitors[x] do + if monitors[x][y].address == e[2] then + ecs.error("Ты уже кликал на этот монитор. Совсем уебок штоле?") + exists = true + end + end + end + + if not exists then + gpu.bind(e[2], false) + gpu.setResolution(baseResolution.width, baseResolution.height) + gpu.setDepth(8) + + local color = color.HSBToInteger(counter / monitorCount * 360, 1, 1) + gpu.setBackground(color) + gpu.setForeground(0xffffff - color) + gpu.fill(1, 1, baseResolution.width, baseResolution.height, " ") + + ecs.centerText("xy", 0, "Монитор " .. xC .. "x" .. yC .. " откалиброван!") + + gpu.bind(mainScreenAddress, false) + + monitors[xC] = monitors[xC] or {} + monitors[xC][yC] = {address = e[2]} + + xC = xC + 1 + if xC > width and yC < height then + xC, yC = 1, yC + 1 + end + end + else + ecs.error("Ну что ты за мудак криворукий! Сказано же, каких мониторов касаться. Не трогай этот монитор.") + end + + counter = counter + 1 + end + + monitors.countOfScreensByWidth = width + monitors.countOfScreensByHeight = height + monitors.screenResolutionByWidth = baseResolution.width + monitors.screenResolutionByHeight = baseResolution.height + monitors.totalResolutionByWidth = baseResolution.width * width + monitors.totalResolutionByHeight = baseResolution.height * height + + ecs.prepareToExit() + ecs.universalWindow("auto", "auto", 40, 0xeeeeee, true, {"EmptyLine"}, {"CenterText", 0x262626, "Калибровка успешно завершена!"}, {"EmptyLine"}, {"Button", {ecs.colors.orange, 0xffffff, "Отлично"}}) + ecs.prepareToExit() +end + +local function saveConfig() + local file = io.open(pathToConfigFile, "w") + file:write(serialization.serialize(monitors)) + file:close() +end + +local function loadConfig() + if fs.exists(pathToConfigFile) then + local file = io.open(pathToConfigFile, "r") + monitors = serialization.unserialize(file:read("*a")) + file:close() + print(" ") + print("Файл конфигурации мультимонитора загружен") + print(" ") + print("Количество экранов: " .. monitors.countOfScreensByWidth .. "x" .. monitors.countOfScreensByHeight .. " шт") + print("Разрешение каждого экрана: " .. monitors.screenResolutionByWidth .. "x" .. monitors.screenResolutionByHeight .. " px") + print("Суммарное разрешение кластера: " .. monitors.totalResolutionByWidth .. "x" .. monitors.totalResolutionByHeight .. " px") + -- print("Суммарное разрешение кластера через шрифт Брайля: ".. monitors.totalResolutionByWidth * 2 .. "x" .. monitors.totalResolutionByHeight * 4 .. " px") + print(" ") + else + configurator() + saveConfig() + loadConfig() + end +end + +-------------------------------------------------------------------------------------------------------------------------------------------- + +--Прочитать n байтов из файла, возвращает прочитанные байты как число, если не удалось прочитать, то возвращает 0 +local function readNumber(file, count) + return bit32.byteArrayToNumber({string.byte(file:read(count), 1, count)}) +end + +local function drawBigImageFromOCIFRawFile(x, y, path) + local file = io.open(path, "rb") + print("Открываем файл " .. path) + local signature = file:read(4) + print("Читаем сигнатуру файла: " .. signature) + local encodingMethod = string.byte(file:read(1)) + print("Читаем метод кодирования: " .. tostring(encodingMethod)) + + if encodingMethod ~= 5 then + print("Неподдерживаемый метод кодирования. Откройте конвертер, измените формат на OCIF5 (Multiscreen) и повторите попытку") + file:close() + end + + local width = readNumber(file, 2) + local height = readNumber(file, 2) + + print("Ширина пикчи: " .. tostring(width)) + print("Высота пикчи: " .. tostring(height)) + + print("Начинаю отросовку пикчи...") + + for j = 1, height do + for i = 1, width do + local background = color.to24Bit(string.byte(file:read(1))) + local foreground = color.to24Bit(string.byte(file:read(1))) + file:read(1) + local symbol = fs.readUnicodeChar(file) + + multiScreenSet(x + i - 1, y + j - 1, background, foreground, symbol) + end + end + + file:close() + + gpu.bind(mainScreenAddress, false) + print("Отрисовка пикчи завершена") +end + +-------------------------------------------------------------------------------------------------------------------------------------------- + +local args = {...} + +if args[1] == "draw" and args[2] then + loadConfig() + print("Идет очистка мониторов...") + multiScreenClear(0x000000) + if fs.exists(args[2]) then + drawBigImageFromOCIFRawFile(1, 1, args[2]) + else + print("Файл " .. tostring(args[2]) .. " не найден. Используйте абсолютный путь к файлу, добавив / в начало") + end +elseif args[1] == "calibrate" then + fs.remove(pathToConfigFile) + loadConfig() +elseif args[1] == "clear" then + loadConfig() + multiScreenClear(tonumber(args[2] or 0x000000)) +else + loadConfig() + print("Использование программы:") + print(" MultiScreen calibrate - перекалибровать мониторы") + print(" MultiScreen draw <путь к изображению> - отобразить изображение из файла на мониторах") + print(" MultiScreen clear <цвет> - очистить мониторы с указанным цветом (черным по умолчанию)") +end + +-------------------------------------------------------------------------------------------------------------------------------------------- + +return multiScreen + + + + + diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/ramConsumeTest.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/ramConsumeTest.lua new file mode 100755 index 00000000..5acf87c3 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/ramConsumeTest.lua @@ -0,0 +1,25 @@ + + +local computer = require("computer") + +local old = computer.freeMemory() + +-- local t = {width = 160, height = 50} +-- for i = 1, t.width * t.height do +-- table.insert(t, 0xFFFFFF) +-- table.insert(t, 0xFFFFFF) +-- table.insert(t, 0xFF) +-- table.insert(t, "Й") +-- end + +local t = {160, 50} +for i = 1, t[1] * t[2] do + table.insert(t, 0xFFFFFF) + table.insert(t, 0xFFFFFF) + table.insert(t, 0xFF) + table.insert(t, "Й") +end + +print("Сожрало памяти: " .. (old - computer.freeMemory()) / 1024 .. " KB") + + diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/sudoku.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/sudoku.lua new file mode 100755 index 00000000..dc72231f --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/sudoku.lua @@ -0,0 +1,82 @@ + +local component = require("component") +local buffer = require("doubleBuffering") +local event = require("event") +local color = require("color") +local unicode = require("unicode") +local GUI = require("GUI") +local MineOSCore = require("MineOSCore") + +-------------------------------------------------------------------------------------------- + +local cells = {} +for y = 1, 9 do + cells[y] = {} + for x = 1, 9 do + cells[y][x] = {value = nil, variants = {}} + end +end + +-------------------------------------------------------------------------------------------- + +local function getCellVariants(xCell, yCell) + for i = 1, 9 do + cells[yCell][xCell].variants[i] = true + end + + for y = 1, 9 do + if y ~= yCell and cells[y][xCell].value then + cells[yCell][xCell].variants[cells[y][xCell].value] = false + end + end + + for x = 1, 9 do + if x ~= xCell and cells[yCell][x].value then + cells[yCell][xCell].variants[cells[yCell][x].value] = false + end + end + + local xCellGroup, yCellGroup = math.ceil(xCell / 3) * 3, math.ceil(yCell / 3) * 3 + for y = yCellGroup - 2, yCellGroup do + for x = xCellGroup - 2, xCellGroup do + if x ~= xCell and y ~= yCell and cells[y][x].value then + cells[yCell][xCell].variants[cells[y][x].value] = false + end + end + end +end + +local function getAllVariants() + for y = 1, 9 do + for x = 1, 9 do + getCellVariants(x, y) + end + end +end + +local function drawVerticalLine(x, y, height, color) + for i = y, y + height - 1 do + buffer.text(x, i, color, "│") + end +end + +local function drawHorizontalLine(x, y, width, color) + buffer.text(x, y, color, string.rep("─", width)) +end + +-------------------------------------------------------------------------------------------- + +local mainContainer, window = MineOSCore.addWindow(GUI.filledWindow(1, 1, 72, 36, 0xEEEEEE)) + +local sudoku = window:addChild(GUI.object(1, 2, 72, 36)) +sudoku.draw = function(sudoku) + local x, y = sudoku.x + 2, sudoku.y + 1 + for i = 1, 8 do + drawVerticalLine(x, sudoku.y, sudoku.height, 0x0) + x = x + 3 + end + for i = 1, 8 do + drawHorizontalLine(sudoku.x, y, sudoku.width, 0x0) + y = y + 2 + end +end \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/symbols.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/symbols.lua new file mode 100644 index 00000000..937d7baf --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/symbols.lua @@ -0,0 +1,294 @@ +local com = require("component") +local unicode = require("unicode") +local event = require("event") +local gpu = com.gpu +-- ─│┌┐└┘├┤┬┴┼ + +local w, h = gpu.maxResolution() +local hMax = math.floor((w-7)/3) -- максимум символов влезет по горизонтали +local vMax = math.floor((h-2)/3) -- максимум символов влезет по вертикали +w, h = hMax*3+7, vMax*3+1 +gpu.setResolution(w, h) -- установка оптимального размера + +gpu.setBackground(0) +gpu.setForeground(0xffffff) +gpu.fill(1, 1, w, h, " ") + +local position = 1 -- позиция списка избранного +local startSymbol = 0 -- символ, с которого начинается отображаемый список +local liked = {} +local curSymbol = -1 +local mode = false -- текущий список - избранное +local file = "" -- текущий сохраняемый файл +local fileNumber = 0 + +local function fillSymbols(start, count) + local str = "" + if start == -1 then -- заполнить средними разделителями + str = "├" + for i=0, (count-1) do + str = str.."──┼" + end + str = str:sub(1, str:len()-("┼"):len()).."┤" + elseif start == -2 then -- заполнить двумя пробелами и разделителем + str = "│" + for i=0, (count-1) do + str = str.." │" + end + else + str = "│" + for i=start, (start+count-1) do -- заполнить символом, пробелом и разделителем + if i > 0xffff then + str = str.." │" + else + char = unicode.char(i) + if unicode.charWidth(char) > 1 then + str = str..char.."│" + else + str = str..char.." │" + end + end + end + end + return str +end +local function drawAllTable() + gpu.fill(1, 1, w-6, h, " ") + str = "┌" -- верхняя часть + for i=0, (hMax-1) do + str = str.."──┬" + end + gpu.set(1, 1, str) + for i=0, (vMax-1) do + gpu.set(1, 2+i*3, fillSymbols(startSymbol+i*hMax, hMax)) + gpu.set(1, 3+i*3, fillSymbols(-2, hMax)) + gpu.set(1, 4+i*3, fillSymbols(-1, hMax)) + end + str = "└" -- нижняя часть + for i=0, (hMax-1) do + str = str.."──┴" + end + gpu.set(1, h ,str) +end + +local function fillTableSymbols(start, count, tab) + local str = "" + if start == -1 then -- заполнить средними разделителями + str = "├" + for i=0, (count-1) do + str = str.."──┼" + end + str = str:sub(1, str:len()-("┼"):len()).."┤" + elseif start == -2 then -- заполнить двумя пробелами и разделителем + str = "│" + for i=0, (count-1) do + str = str.." │" + end + else + str = "│" + for i=start, (start+count-1) do -- заполнить символом, пробелом и разделителем + if i > #tab then + str = str.." │" + else + char = unicode.char(tab[i]) + if unicode.charWidth(char) > 1 then + str = str..char.."│" + else + str = str..char.." │" + end + end + end + end + return str +end +local function drawLikedTable() + gpu.fill(2, 2, w-8, h-2, " ") + for i=0, (vMax-1) do + gpu.set(1, 2+i*3, fillTableSymbols(position+i*hMax, hMax, liked)) + gpu.set(1, 3+i*3, fillTableSymbols(-2, hMax)) + gpu.set(1, 4+i*3, fillTableSymbols(-1, hMax)) + end + str = "└" + for i=0, (hMax-1) do + str = str.."──┴" + end + gpu.set(1, h ,str) +end + +local function drawButton(x,y,text) + gpu.set(x, y, "▁▁▁▁▁") + gpu.set(x, y+1, text) + gpu.set(x, y+2, "▔▔▔▔▔") +end + +local function drawSaved(text) + if text ~= "" then + gpu.set(w-5, h-6, "Saved") + gpu.set(w-5, h-5, "as") + gpu.set(w-5, h-4, text) + else + gpu.fill(w-5, h-6, 5, 3, " ") + end +end + +local function drawCharInfo(char, liked) + gpu.fill(w-5, 8, 5, 5, " ") + local ch = unicode.char(char) + gpu.set(w-5, 8, ch..(unicode.charWidth(ch) > 1 and "" or " ").." "..(liked and "◢◣" or "/\\")) + gpu.set(w-5, 9, " "..(liked and "◥◤" or "\\/")) + gpu.set(w-5, 10, "Code:") + gpu.set(w-5, 11, tostring(char)) + gpu.set(w-5, 12, string.format("x%X", char)) +end + +local function drawMenu() + gpu.set(w-5, 1, "────><") + gpu.fill(w, 2, 1, h-2, "│") + + gpu.set(w-5, 2, " ◢ ◣ ") + gpu.set(w-5, 3, " ◥ ◤ ") + gpu.set(w-5, 4, string.format("%X/", startSymbol)) + gpu.set(w-5, 5, "xFFFF") + + drawButton(w-5, h-9, "Liked") + drawButton(w-5, h-3, "Save") + + gpu.set(w-5, h, "─────┘") +end + +local function getSymbolIndex(x, y) -- считается порядковый номер по координатам клика + if x < 2 or y < 2 or (x-1)%3==0 or + (y-1)%3==0 then return -1 end + + return math.floor((y-1)/3)*hMax + math.floor((x-1)/3) +end + +drawAllTable() +drawMenu() +while true do + local evnt = {event.pull("touch")} + local x, y = evnt[3], evnt[4] + if evnt[5] == 0 then + if x > (w-2) and y == 1 then -- выход + break + else + if x < w-5 then -- выбор символа + local i = getSymbolIndex(x, y) + if i > -1 then + if mode then + if position+i <= #liked then + drawCharInfo(liked[position+i], true) + curSymbol = liked[position+i] + end + elseif startSymbol+i <= 0xffff then + local finded = false -- есть ли символ в избранных + for j=1, #liked do + if liked[j] == startSymbol+i then + finded = true + break + end + end + drawCharInfo(startSymbol+i, finded) + curSymbol = startSymbol+i + end + end + else + if x < w then + local str = "" + if y > 1 and y < 4 then -- перемотка страниц + if x > w-3 then + if mode then + if (position+hMax*vMax) <= #liked then + position = position+hMax*vMax + str = string.format("%X/", position) + gpu.set(w-5, 4, str..(" "):rep(5-str:len())) + drawLikedTable() + end + else + if (startSymbol+hMax*vMax) <= 0xffff then + startSymbol = startSymbol + hMax*vMax + str = string.format("%X/", startSymbol) + gpu.set(w-5, 4, str..(" "):rep(5-str:len())) + drawAllTable() + end + end + elseif x < w-3 then + if mode then + if position > 1 then + position = math.max(position - hMax*vMax, 0) + str = string.format("%X/", position) + gpu.set(w-5, 4, str..(" "):rep(5-str:len())) + drawLikedTable() + end + else + if startSymbol > 0 then + startSymbol = math.max(startSymbol - hMax*vMax, 0) + str = string.format("%X/", startSymbol) + gpu.set(w-5, 4, str..(" "):rep(5-str:len())) + drawAllTable() + end + end + end + elseif y > 7 and y < 10 then + if x > w-3 then -- добавить/убрать символ в списке "Избранное" + local finded = false + for i=1, #liked do + if liked[i] == curSymbol then + finded = true + table.remove(liked, i) + break + end + end + if not finded then + table.insert(liked, curSymbol) + end + if mode then + drawLikedTable() + end + drawSaved("") + drawCharInfo(curSymbol, not finded) + end + elseif y > h-10 and y < h-6 then -- избранное/все + if mode then + mode = false + drawButton(w-5, h-9, "Liked") + str = string.format("%X/", startSymbol) + gpu.set(w-5, 4, str..(" "):rep(5-str:len())) + gpu.set(w-5, 5, "xFFFF") + drawAllTable() + else + mode = true + drawButton(w-5, h-9, " All ") + str = string.format("%X/", position) + gpu.set(w-5, 4, str..(" "):rep(5-str:len())) + str = tostring(#liked) + gpu.set(w-5, 5, str..(" "):rep(5-str:len())) + drawLikedTable() + end + elseif y > h-4 and y < h then -- сохранить + if #liked > 0 then + if file == "" then + fileNumber = math.random(99999) + file = "/symbols-"..tostring(fileNumber)..".txt" + end + + local fs = io.open(file, "w") + str = "" + for i=1, #liked do + str = str..unicode.char(liked[i]) + end + fs:write(str) + fs:close() + + drawSaved(tostring(fileNumber)) + end + end + end + end + end + end +end + +w, h = gpu.maxResolution() +gpu.setResolution(w, h) +gpu.fill(1, 1, w, h, " ") \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/t.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/t.lua new file mode 100644 index 00000000..bfd50f31 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/t.lua @@ -0,0 +1,34 @@ +local bit32 = require("bit32") + +local function hashFile(path) + local file, reason = io.open(path, "rb") + if file then + local bufferSize, hash, data, bytes = 4096, 0 + while true do + data = file:read(bufferSize) + if data then + bytes = {string.byte(data, 1, bufferSize)} + for i = 1, #bytes do + hash = hash + bytes[i] * 0x990C9AB5 + hash = bit32.bxor(hash, bit32.rshift(hash, 16)) + end + else + break + end + end + + file:close() + + return string.format("%X", hash) + else + error("Failed to open file for reading: " .. tostring(reason)) + end +end + +-- print(hashFile("/OS.lua")) + +------------------------------------------------------------------------------------------------------------ + + + + diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/tanks.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/tanks.lua new file mode 100644 index 00000000..295b653a --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/tanks.lua @@ -0,0 +1,160 @@ + +require("advancedLua") +local computer = require("computer") +local component = require("component") +local fs = require("filesystem") +local buffer = require("doubleBuffering") +local GUI = require("GUI") +local unicode = require("unicode") +local image = require("image") +local color = require("color") +local MineOSCore = require("MineOSCore") + +--------------------------------------------------------------------------------------------------------- + +local tankImages = {} +for i = 1, 4 do + table.insert(tankImages, image.load("/Tanks/" .. i .. ".pic")) +end + +local function newTank(x, y, rotation, speed) + local object = GUI.object(x, y, 8, 4) + + object.type = "tank" + object.controllable = false + object.rotation = rotation + object.speed = speed + + object.draw = function(object) + buffer.image(object.x, object.y, tankImages[object.rotation]) + + if object.controllable then + buffer.frame(object.x - 1, object.y - 1, object.width + 2, object.height + 2, 0xFFFFFF) + end + end + + return object +end + +--------------------------------------------------------------------------------------------------------- + +local function newBullet(x, y, rotation, speed) + local object = GUI.object(x, y, 1, 1) + + object.type = "bullet" + object.rotation = rotation + object.speed = speed + + object.draw = function(object) + buffer.text(object.x, object.y, 0xFF0000, "*") + end + + return object +end + +--------------------------------------------------------------------------------------------------------- + +local mainContainer = GUI.fullScreenContainer() +mainContainer:addChild(GUI.panel(1, 1, mainContainer.width, mainContainer.height, 0x1E1E1E)) + +local tanksContainer = mainContainer:addChild(GUI.container(1, 1, mainContainer.width, mainContainer.height)) +for i = 1, 10 do + tanksContainer:addChild( + newTank( + math.random(mainContainer.width - 8), + math.random(mainContainer.height - 4), + math.random(4), + 1 + ) + ) +end + +local myTank = tanksContainer.children[1] +myTank.controllable = true + +local moveSpeed = 0 +local lastComputerUptime = computer.uptime() + +mainContainer.eventHandler = function(mainContainer, object, eventData) + if eventData[1] == "touch" then + elseif eventData[1] == "key_down" then + if eventData[4] == 17 then + myTank.rotation = 1 + elseif eventData[4] == 31 then + myTank.rotation = 3 + elseif eventData[4] == 30 then + myTank.rotation = 4 + elseif eventData[4] == 32 then + myTank.rotation = 2 + elseif eventData[4] == 57 then + tanksContainer:addChild(newBullet(myTank.x + 2, myTank.y + 2, myTank.rotation, 4)) + elseif eventData[4] == 42 then + myTank.speed = myTank.speed == 0 and 1 or 0 + end + end + + local computerUptime = computer.uptime() + if computerUptime - lastComputerUptime > moveSpeed then + local i = 1 + while i <= #tanksContainer.children do + local child = tanksContainer.children[i] + + local function deleteBullet() + if child.type == "bullet" then + child:delete() + i = i - 1 + end + end + + if child.rotation == 1 then + child.localY = child.localY - child.speed + if child.localY <= 1 then + deleteBullet() + + child.localY = 1 + child.rotation = math.random(4) + end + elseif child.rotation == 2 then + child.localX = child.localX + child.speed * 2 + if child.localX + child.width - 1 >= tanksContainer.width then + deleteBullet() + + child.localX = tanksContainer.width - child.width + child.rotation = math.random(4) + end + elseif child.rotation == 3 then + child.localY = child.localY + child.speed + if child.localY + child.height - 1 >= tanksContainer.height then + deleteBullet() + + child.localY = tanksContainer.height - child.height + child.rotation = math.random(4) + end + else + child.localX = child.localX - child.speed * 2 + if child.localX <= 1 then + deleteBullet() + + child.localX = 1 + child.rotation = math.random(4) + end + end + + i = i + 1 + end + + lastComputerUptime = computerUptime + mainContainer:draw() + buffer.draw() + end +end + + +--------------------------------------------------------------------------------------------------------- + +mainContainer:draw() +buffer.draw(true) +mainContainer:startEventHandling(0) + + + diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/test.txt b/640cd89f-8e29-4b66-a0eb-7680c33760b4/test.txt new file mode 100644 index 00000000..acdc4d51 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/test.txt @@ -0,0 +1,63 @@ +{ + [1] = { + dependencies = { + [1] = 98, + [2] = 97, + [3] = 73, + [4] = 93 + }, + category_id = 2, + user_name = "ECS", + publication_name = "GUI", + file_id = 100, + path = "GUI.lua" + }, + [2] = { + dependencies = { + [1] = 97 + }, + file_id = 98, + publication_name = "Syntax", + source_url = "https://raw.githubusercontent.com/IgorTimofeev/OpenComputers/master/lib/syntax.lua", + path = "syntax.lua" + }, + [3] = { + source_url = "https://raw.githubusercontent.com/IgorTimofeev/OpenComputers/master/lib/advancedLua.lua", + publication_name = "Advanced Lua", + file_id = 93, + path = "advancedLua.lua" + }, + [4] = { + dependencies = { + [1] = 92, + [2] = 73 + }, + file_id = 97, + publication_name = "Double Buffering", + source_url = "https://raw.githubusercontent.com/IgorTimofeev/OpenComputers/master/lib/doubleBuffering.lua", + path = "doubleBuffering.lua" + }, + [5] = { + source_url = "https://raw.githubusercontent.com/IgorTimofeev/OpenComputers/master/lib/color.lua", + publication_name = "Color", + file_id = 92, + path = "color.lua" + }, + [6] = { + dependencies = { + [1] = 92, + [2] = 93, + [3] = 177 + }, + file_id = 73, + publication_name = "Image", + source_url = "https://raw.githubusercontent.com/IgorTimofeev/OpenComputers/master/lib/image.lua", + path = "image.lua" + }, + [7] = { + source_url = "https://raw.githubusercontent.com/IgorTimofeev/OpenComputers/master/lib/FormatModules/OCIF.lua", + publication_name = "OCIF", + file_id = 177, + path = "OCIF.lua" + } +} \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/timeTest.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/timeTest.lua new file mode 100644 index 00000000..da64d6a5 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/timeTest.lua @@ -0,0 +1,16 @@ + + +local bit32 = require("bit32") + +local function toRGB1(IntegerColor) + return bit32.rshift(IntegerColor, 16), bit32.band(bit32.rshift(IntegerColor, 8), 0xFF), bit32.band(IntegerColor, 0xFF) +end + +local function toRGB2(IntegerColor) + return bit32.rshift(IntegerColor, 16), bit32.band(bit32.rshift(IntegerColor, 8), 0xFF), bit32.band(IntegerColor, 0xFF) +end + + +local oldClock = os.clock() + +print("TIME: ", os.clock() - oldClock) \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/tunnelBIOS.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/tunnelBIOS.lua new file mode 100755 index 00000000..54b04913 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/tunnelBIOS.lua @@ -0,0 +1,52 @@ + +computer.beep(2000, 0.1) + +local eeprom = component.proxy(component.list("eeprom")()) +local modem = component.proxy(component.list("modem")()) +local port = 512 +modem.open(port) + +------------------------------------------------------------------------ + +local masterControllerAddress +local thisComputerAddress = computer.address() + +------------------------------------------------------------------------ + +local function turnOnNearbyComputers() + for address in component.list("computer") do + if address ~= thisComputerAddress then + component.proxy(address).start() + end + end +end + +------------------------------------------------------------------------ + +turnOnNearbyComputers() +modem.broadcast(port, "tunnelUpdate", thisComputerAddress) + +while true do + local eventData = {computer.pullSignal()} + if eventData[1] == "modem_message" then + if eventData[6] == "flash" then + if load(eventData[7]) then + for i = 1, 3 do + computer.beep(1000, 0.5) + end + eeprom.set(eventData[7]) + computer.shutdown(true) + else + computer.beep(400, 0.2) + end + elseif eventData[6] == "tunnelState" then + if eventData[7] == "stop" and thisComputerAddress ~= masterControllerAddress then + computer.shutdown() + elseif eventData[7] == "start" then + turnOnNearbyComputers() + end + elseif eventData[6] == "tunnelMasterControllerUpdate" then + masterControllerAddress = eventData[7] + end + end +end diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/tunnelPCSoftware.lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/tunnelPCSoftware.lua new file mode 100755 index 00000000..9c6fecff --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/tunnelPCSoftware.lua @@ -0,0 +1,72 @@ + +local component = require("component") +local buffer = require("doubleBuffering") +local GUI = require("GUI") +local modem = component.modem +local port = 512 +modem.open(port) + +-------------------------------------------------------------------------------------------------- + +local masterControllerAddress + +-------------------------------------------------------------------------------------------------- + +local mainContainer = GUI.fullScreenContainer() + +mainContainer:addChild(GUI.panel(1, 1, mainContainer.width, mainContainer.height, 0x262626)) +local textBox = mainContainer:addChild(GUI.textBox(1, 1, mainContainer.width, mainContainer.height, nil, 0x555555, {}, 1, 0, 0, false, false)) +local function info(text) + table.insert(textBox.lines, text) + if #textBox.lines > textBox.height then + table.remove(textBox.lines, 1) + end + mainContainer:draw() + buffer.draw() +end +local layout = mainContainer:addChild(GUI.layout(1, 1, mainContainer.width, mainContainer.height, 1, 1)) +layout:setCellSpacing(1, 1, 0) + +local function sendFile(command, path) + local file = io.open(path, "r") + local data = file:read("*a") + file:close() + modem.broadcast(port, command, data) +end + +layout:addChild(GUI.roundedButton(1, 1, 30, 3, 0xEEEEEE, 0x262626, 0x888888, 0x262626, "Прошить биос")).onTouch = function() + sendFile("flash", "/tunnelBIOS.lua") + masterControllerAddress = nil +end + +layout:addChild(GUI.roundedButton(1, 1, 30, 3, 0xEEEEEE, 0x262626, 0x888888, 0x262626, "Оффнуть все")).onTouch = function() + modem.broadcast(port, "tunnelState", "stop") +end + +layout:addChild(GUI.roundedButton(1, 1, 30, 3, 0xEEEEEE, 0x262626, 0x888888, 0x262626, "Врубить все")).onTouch = function() + modem.broadcast(port, "tunnelState", "start") +end + +mainContainer.eventHandler = function(mainContainer, object, eventData) + if eventData[1] == "modem_message" then + info("Message: " .. table.concat({table.unpack(eventData, 6)}, " ")) + if eventData[6] == "tunnelHandle" then + modem.broadcast(port, "tunnelResend", table.unpack(eventData, 7)) + elseif eventData[6] == "tunnelUpdate" then + if not masterControllerAddress then + info("Назначаю контроллером йобу: " .. eventData[7]) + masterControllerAddress = eventData[7] + end + + modem.send(eventData[3], port, "tunnelMasterControllerUpdate", masterControllerAddress) + end + end +end + +-------------------------------------------------------------------------------------------------- + +mainContainer:draw() +buffer.draw(true) +mainContainer:startEventHandling() + + diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/address b/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/address new file mode 100755 index 00000000..e3368c92 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/address @@ -0,0 +1,12 @@ +NAME + address - display the computer's address + +SYNOPSIS + address + +DESCRIPTION + `address` allows printing the computer's component address. This can be useful to quickly look up a computer's address without an Analyzer. Knowing a computer's address can be useful if you wish to directly send network messages between two computers. + +EXAMPLES + address + Displays the address of the computer the program is run on. \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/alias b/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/alias new file mode 100755 index 00000000..ca80e2c8 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/alias @@ -0,0 +1,24 @@ +NAME + alias - displays and manipulates aliases for programs + +SYNOPSIS + alias + alias name + alias name=value + +DESCRIPTION + `alias` allows listing and editing aliases for programs. An alias is an alternative name that can be used to start a program. A program can have multiple aliases. Aliases can also contain parameters and options to pass to the actual program. An alias can also, in turn, have aliases. + +EXAMPLES + alias + Displays the list of all current aliases. + + alias name + Displays the value the specified alias, i.e. what the specified alias stands for. + + alias name=value + alias name='value with arguments' + Sets the value of the alias with the specified name. Note that white space separates arguments to a command, thus to have an alias whose value has whitespace, quote the value + + alias name1 name2 name3=value + You can also return the value, or set the value, of an arbitrary number of aliases. \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/cat b/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/cat new file mode 100755 index 00000000..b3484835 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/cat @@ -0,0 +1,15 @@ +NAME + cat - concatenate files and print on the standard output + +SYNOPSIS + cat [FILE]... + +DESCRIPTION + `cat` allows concatenating files or standard input to standard output. + +EXAMPLES + cat + Copy standard input to standard output. + + cat test.txt + Output contents of file test.txt. \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/cd b/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/cd new file mode 100755 index 00000000..027bb77d --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/cd @@ -0,0 +1,42 @@ +NAME + cd - change the current working directory + +SYNOPSIS + cd [directory] + cd - + +DESCRIPTION + `cd` allows changing the current working directory, i.e the directory based on which relative paths are resolved. + + If no operand is given then HOME environment variable is used. + + If the operand is - (just a single dash) then OLDPWD environment variable is used. + + Relative path components ./ and ../ may be used to denote the working directory and the parent directory, respectively. An operand starting with ./ is equivalent to PWD environment variable. See examples for illustrations. + + Lastly, cd will attempt to change the current directory to the path defined by the operand, reporting errors if any. + + If cd is successful, OLDPWD environment variable will also be set to the previous value of PWD (that is the current working directory immediately prior to the call to cd). + +ENVIRONMENT VARIABLES + PWD + Parent Working Directory, the current working directory + + OLDPWD + Old PWD, set each time the PWD is changed by calling the cd utility + + HOME + Represents the path of the user's home directory + +EXAMPLES + cd a + Changes to directory `a` in the current working directory + + cd /bin + Changes to directory `/bin`, using the specified absolute path + + cd ../ + Changes to the parent directory of the current working directory + + cd - + Changes to the previos directory, defined by OLDPWD, and set each time cd changes PWD diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/clear b/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/clear new file mode 100755 index 00000000..65fbec37 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/clear @@ -0,0 +1,12 @@ +NAME + clear - clears the terminal + +SYNOPSIS + clear + +DESCRIPTION + Clears any text from the screen, using the current background and foreground (text) colors and resets the cursor position to (1, 1). + +EXAMPLES + clear + Clears the screen/ \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/cp b/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/cp new file mode 100755 index 00000000..1c95d7b9 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/cp @@ -0,0 +1,16 @@ +NAME + cp - copy files + +SYNOPSIS + cp SOURCE DEST + cp SOURCE DIRECTORY + +DESCRIPTION + `cp` allows copying single files on a filesystem and across filesystems. + +EXAMPLES + cp a b + Copy file `a` to new file `b` in the same directory. + + cp /home/a.txt /home/d/ + Copy file `/home/a.txt` to new file `/home/d/a.txt`. \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/date b/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/date new file mode 100755 index 00000000..2d28c759 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/date @@ -0,0 +1,12 @@ +NAME + date - get the current time and date + +SYNOPSIS + date + +DESCRIPTION + Writes the current time and date to the standard output. Note that the time is measured in in-game time, with the date starting on the 1st of January 1970 as the time the world was created. + +EXAMPLES + date + Displays the current date and time. diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/df b/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/df new file mode 100755 index 00000000..5fc6e477 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/df @@ -0,0 +1,15 @@ +NAME + df - report file system disk space usage + +SYNOPSIS + df [FILE]... + +DESCRIPTION + `df` outputs disk space information for the file systems containing the specified files. If no file names are given it returns the information for all currently mounted file systems. + +EXAMPLES + df + Show global file system disk usage. + + df /home /var + Show disk usage of file systems mounted at `/home` and `/var`. \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/dmesg b/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/dmesg new file mode 100755 index 00000000..ee8f0d0b --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/dmesg @@ -0,0 +1,13 @@ +NAME + dmesg - display messages(events) + +SYNOPIS + dmesg [EVENT]... + +EXAMPLES + dmesg + Shows all events. + + dmesg touch + Shows only "touch" events. + diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/echo b/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/echo new file mode 100755 index 00000000..6cc9fcf5 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/echo @@ -0,0 +1,19 @@ +NAME + echo - display a line of text + +SYNOPSIS + echo [STRING]... + +DESCRIPTION + `echo` writes the provided string(s) to the standard output. + + -n do not output the trialing newline + + --help display this help and exit + +EXAMPLES + echo test + Print `test` to the standard output (which is usually the terminal). + + echo "a b" + Writes the string `a b`. \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/edit b/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/edit new file mode 100755 index 00000000..f54ef27e --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/edit @@ -0,0 +1,19 @@ +NAME + edit - primitive file editor + +SYNOPSIS + edit FILE + +DESCRIPTION + A very simple text file editor. To create new files with `edit`, open a file in a writable file system that doesn't exist and save. + +OPTIONS + -r + opens file as read only + +EXAMPLES + edit /tmp/test.txt + Opens the file `/tmp/test.txt` for editing. If it doesn't exists, it will be created upon saving. + + edit /bin/ls.lua + Opens the file `/bin/ls.lua`, which will be opened in read-only mode, assuming `/bin` is the default as provided by the read-only-memory. \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/grep b/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/grep new file mode 100755 index 00000000..31cfdb78 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/grep @@ -0,0 +1,34 @@ +NAME + grep - Search for PATTERN in each FILE or standard input. PATTERN is, by default, a Lua regular expression + Example: grep -i 'hello world' menu.lua main.lua + +OPTIONS +Regexp selection and interpretation: + -e, --lua-regexp PATTERN is a Lua regexp (default) + -F, --fixed-strings PATTERN is a plain string + --file=FILE use newline separated PATTERNs from FILE + -w, --word-regexp force PATTERN to match only whole words + -x, --line-regexp force PATTERN to match only whole lines + -i, --ignore-case ignore case distinctions + +Miscellaneous: + --label=LABEL use LABEL instead of (standard input) for stdin prefix + -s, --no-messages suppress error messages + -v, --invert-match select non-matching lines + --help display help message and exit + +Output control: + --max-count=NUM stop after NUM matches + -n, --line-number print line number with output lines + -H, --with-filename print the file name for each match + -h, --no-filename suppress the file name prefix on output + -o, --only-matching show only the part of a line matching PATTERN + -q, --quiet, --silent suppress all normal output + -r, --recursive search directories in FILEs recursively + -L, --files-without-match print only names of FILEs containing no match + -l, --files-with-matches print only names of FILEs containing matches + -c, --count print only a count of matching lines per FILE + --color, --colour user markers to highlight the matching strings + -t, --trim trim whitespace off results + +When FILE is -, read stdin. With no FILE, read - or ./ if a command line -r. If fewer than two FILEs are given, assume -h. Exit status is 0 if any line is selected, 1 otherwise. If any error occurs and -q is not given, the exit status is 2. diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/head b/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/head new file mode 100755 index 00000000..98bed61d --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/head @@ -0,0 +1,31 @@ +NAME + head - Print the first 10 lines of each FILE to stdout. + +SYNOPSIS + head [OPTION]... [FILE]... + +DESCRIPTION + Print the first 10 lines of each FILE to stdout. + With no FILE, or when FILE is -, read stdin. + + --bytes=[-]n print the first n bytes of each file' + with the leading '-', print all but the last + n bytes of each file + --lines=[-]n print the first n lines instead of the first 10; + with the leading '-', print all but the last + n lines of each file + -q, --quiet, --silent never print headers giving file names + -v, --verbose always print headers giving file names + --help print help message + +EXAMPLES + head + head - + Read next 10 lines from standard in and print to standard out, then close. + + head file.txt + Print first 10 lines of file.txt and print to stdout + + head -n 32 file.txt + Print first 32 lines of file.txt and print to stdout + diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/hostname b/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/hostname new file mode 100755 index 00000000..ae853e58 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/hostname @@ -0,0 +1,12 @@ +NAME + hostname - Display and modify hostname + +SYNOPIS + hostname [NEW NAME] + +EXAMPLES + hostname + Prints currently set hostname + + hostname test + Sets hostname of this computer to test diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/install b/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/install new file mode 100755 index 00000000..e1b60ed7 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/install @@ -0,0 +1,109 @@ +NAME + install - installs files from a source filesystem to a target filesystem + +SYNOPSIS + install [name] [OPTIONS]... + +DESCRIPTION + install builds a list of candidate source and target mounted filesystems. If there are multiple candidates, the user is prompted for selections. By default, install copies all files from the source filesystem's root to the target filesystem's root path. The source filesystem can define label, boot, and reboot behavior via .prop and a fully custom install experience via .install which supercedes install running cp from source to target filesystems. Developers creating their own .install files for devices should respect environment variables set by install as per options it is given, such as the root path. This manual page details those environment variables. + +OPTIONS + --from=ADDR + Specifies the source filesystem or its root path. ADDR can be the device uuid or a directory path. If this is a directory path, it represents a root path to install from. This option can also be used to specify source paths that would otherwise be ignored, those being devfs, tmpfs, and the rootfs. e.g. --from=/tmp . Note that if both --from and --label are used, install expects the source path to have a .prop defining the same label. See .prop for more details. + + --to=ADDR + Same as --from but specifies the target filesystem by uuid or its root path. This option can also be used to specify filesystems that are otherwise ignored including tmpfs. i.e. --to=ADDR where ADDR matches the tmpfs device address or its mount point path. e.g. --to=/tmp + + --fromDir=PATH + Install PATH from source. PATH is relative to the root of the source filesystem or path given by --from. The default is . + + --root=PATH + Same as --fromDir but for the target filesystem. + + --toDir=PATH + Same as --root. Either can be used. It is meaningless to specify both and is not documented which takes precedence in such a case. + + -u, --update + Indicates that install should prompt the user before modifying files. This invokes -i and -u for /bin/cp. + +The following can override settings defined in .prop in the source filesystem. + + --label=LABEL + use LABEL for label instead of any value specified by .prop + + --nosetlabel + do not set target label. --nolabelset is deprecated + + --nosetboot + do not set target as default boot device when rebooting. --noboot is deprecated + + --noreboot + do not reboot after install + +.prop + .prop should have valid lua syntax for a table of keys and their values: e.g. "{label='OpenOS'}" + All fields are optional, as is the .prop file + + label:string + Declares an identifying name of the installation. This is displayed by install during source selection and also can be used on the commandline: e.g. (where {label="tape"} is given) `install tape`. If setlabel is true, this value is used for the target filesystem label. --label overrides this value. Note that install uses a case insensitive search: e.g. install TAPE works the same as install tape. + + setlabel:boolean + Determines whether the install should set the target filesystem's label. If .prop does not define a label and the user does not define a command line --label=LABEL, setlabel has no action. --nosetlabel overrides this value + + setboot:boolean + Determines if the target filesystem should be set as the machine's default boot device. Default is false, overriden by --nosetboot + + reboot:boolean + Determines if the machine should reboot after the install completes. Overriden by --noreboot + + ignore:boolean + If true, the installer will skip the source filesystem and not include it for selection + + EXAMPLE: + {label='OpenOS', setlabel=true, setboot=true, reboot=true} + +.install ENVIRONMENT + A loot disc can optionally provide a custom installation script at the root of the source filesytem selected for installation. The script must be named ".install" + When provided, the default install action is replaced by executation of this script. The default action is to copy all source files to the destination + An _ENV.install table is set in the environment of '.install' when loaded + These are the keys and their descriptions of that table + + _ENV.install.from: + This is the path of the selected source filesystem to install from. It should be the path to the executing .install + example: /mnt/ABC/.install is executing, thus _ENV.install.from is "/mnt/ABC/" + + _ENV.install.to: + This is the path of the selected target filesystem to install to. + example: "/" + + _ENV.install.fromdir + This is the relative path to use in the source filesystem as passed by command line to install. If unspecified to install it defaults to "." + example: Perhaps the user executed `install --fromDir="bin"` with the intention that only files under /mnt/ABC/bin would be copied to their rootfs + + _ENV.install.root + This is the relative path to use in the target filesystem as passed by command line to install. If unspecified to install it defaults to "." + example: The user prefers to install to usr/ and uses `install --root=usr` and here _ENV.install.root would be "usr" + + _ENV.install.update + Assigned value of --update, see OPTIONS + + _ENV.install.label + Assigned value of --label or .prop's label, see OPTIONS + + _ENV.install.setlabel + Assigned value of .prop's setlabel unless --nosetlabel, see OPTIONS + + _ENV.install.setboot + Assigned value of .prop's boot unless --nosetboot, see OPTIONS + + _ENV.install.reboot + Assigned value of .prop's reboot unless --noreboot, see OPTIONS + +EXAMPLES + install + Searches all non rootfs filesystems to install from, and all non tmpfs filesystems to install to. Prompts the user for a selection, and copies. If .prop is defined in source, sets label and will prompt for reboot when completed. + + install openos + Searches candidates source filesystems that have .prop's that define label="OpenOS" and prompts the user to confirm install to candidate target filesystems. + + diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/label b/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/label new file mode 100755 index 00000000..1765d84b --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/label @@ -0,0 +1,16 @@ +NAME + label - show or change the label of file systems + +SYNOPSIS + label FILE [STRING] + label -a ADDRESS [STRING] + +DESCRIPTION + `label` allows reading and writing the label of file systems. The file system can either be specified by a path to or into a mount, or by its address. + +EXAMPLES + label /home + Write the label of the file system mounted at `/home` to the standard output. + + label -a 93f test + Change the label of the file system of which the address starts with `93f` to `test`. \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/ln b/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/ln new file mode 100755 index 00000000..8bf404a7 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/ln @@ -0,0 +1,19 @@ +NAME + ln - creates symbolic links + +SYNOPSIS + ln FILE [TARGET] + +DESCRIPTION + `ln` allows creating virtual symbolic links. A symbolic link is a reference in the file system that can be used to point to other nodes in the file system. For example, a symbolic link to a file will behave like that file: it can be opened and changed, where in reality the file the link references is changed. A symbolic link to a directory will behave as such. + + Note that symbolic links can lead to cycles (recursion) in the file system structure. + + Symbolic links in OpenOS are 'virtual'. They are not stored on any file system, and as such will not persist across a reboot of the computer. This also means that the can be created in virtual folders, and even on read-only file systems. + +EXAMPLES + ln /bin/ls.lua + Creates a symbolic link `ls.lua` to the file `/bin/ls.lua` in the current working directory. + + ln /home/magic.lua /bin/magic.lua + Creates a symbolic link to file `/home/magic.lua` in the `/bin` directory. \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/ls b/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/ls new file mode 100755 index 00000000..cbfc0eed --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/ls @@ -0,0 +1,80 @@ +NAME + ls - list directory contents + +SYNOPSIS + ls [OPTION]... [FILE]... + +DESCRIPTION + List information about the specified files, or the current working directory by default. + +OPTIONS + -a, --all + do not ignore entries starting with . + + --full-time + with -l, print time in full iso format + + -h, --human-readable + with -l and/or -s, print human readable sizes + + --si + likewise, but use powers of 1000 not 1024 + + -l + use a long listing format + + -r, --reverse + reverse order while sorting + + -R, --recursive + list subdirectories recursively + + -S + sort by file size + + -t + sort by modification time, newest first + + -X + sort alphabetically by entry extension + + -1 + list one file per line + + --no-color + Do not colorize the output (default colorized) + + --help + display this help and exit]]) + + -p + append / indicator to directories + + -M + display Microsoft-style file and directory count after listing + +ENVIRONMENT VARIABLES + + LS_COLORS + A serialized table listing colors to use for listing filesystem elements. + + FILE + Coloring to use when listing a regular file + + DIR + Coloring to use when listing a directory + + LINK + Coloring to use when listing a symbolic link + + *. + Coloring to use for regular files with an extension + + The default LS_COLORS string is "{FILE=0xFFFFFF,DIR=0x66CCFF,LINK=0xFFAA00,["*.lua"]=0x00FF00}" + +EXAMPLES + ls + Displays the contents of the current directory. + + ls /bin /mnt + Displays the contents of the `/bin`/ and `/mnt` directories, one after the other. \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/lua b/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/lua new file mode 100755 index 00000000..b24786f6 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/lua @@ -0,0 +1,12 @@ +NAME + lua - a simple Lua interpreter + +SYNOPSIS + lua + +DESCRIPTION + Launches a command line that can be used to evaluate Lua statements and expressions. This can be very useful for testing out commands. Note that the interpreter will automatically try to resolve undefined globals using `require`, i.e. it will try to load a package with the specified name. + +EXAMPLES + lua + Launches the Lua interpreter. \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/man b/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/man new file mode 100755 index 00000000..0e25672b --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/man @@ -0,0 +1,15 @@ +NAME + man - help program, providing help on various programs and topics + +SYNOPSIS + man topic + +DESCRIPTION + `man` is the system's help viewer. Each help topic is normally the name of a program or library. Topics are stored as individual text files in the `/usr/man` folder. Additional help topics can be provided by creating a symbolic link to a file. + +EXAMPLES + man man + Display the help for the `man` program. + + man ls + Display the help for the `ls` program. \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/mkdir b/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/mkdir new file mode 100755 index 00000000..e5b12462 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/mkdir @@ -0,0 +1,15 @@ +NAME + mkdir - make directories + +SYNOPSIS + mkdir DIRECTORY... + +DESCRIPTION + Create the specified directories, if they don't already exist. + +EXAMPLES + mkdir a + Create directory `a` in the current directory. + + mkdir /a/b c + Create directory `/a` if it doesn't already exists, then create directory `/a/b` and create directory `c` in the current directory. \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/more b/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/more new file mode 100755 index 00000000..1d8f371a --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/more @@ -0,0 +1,12 @@ +NAME + more - primitive file viewer + +SYNOPSIS + more FILE + +DESCRIPTION + `more` allows viewing the contents of a file one screen full at a time. + +EXAMPLES + more /home/a.txt + Displays the contents of file `/home/a.txt` diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/mount b/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/mount new file mode 100755 index 00000000..491f1577 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/mount @@ -0,0 +1,26 @@ +NAME + mount - mount a file system + +SYNOPSIS + mount + mount LABEL PATH + mount ADDRESS PATH + +OPTIONS + -r mount filesystem readonly + +DESCRIPTION + All files accessible in OpenOS are arranged in one big tree, starting with the root node, '/'. The files are the leaves of the tree, directories are inner nodes of the tree. Files can be distributed across several devices (file system components, such as hard drives and floppies). The `mount` command is used to attach a file system to this tree. The `umount` command can be used to remove a mounted file system from the tree (note that `rm` works for this, too). + +EXAMPLES + mount + Display a list of all currently mounted file systems. + + mount test /home + Mounts the file system labeled `test` at `/home`. + + mount 56f /var + Mounts the file system of which the address starts with `56f` at `/var`. + + mount -r tmpfs /tmp_ro + Mounts a readonly access path of tmpfs to /tmp_ro \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/mv b/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/mv new file mode 100755 index 00000000..081740ba --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/mv @@ -0,0 +1,22 @@ +NAME + mv - move (rename) files + +SYNOPSIS + mv SOURCE DEST + +DESCRIPTION + Renames files - and folders, as long they remain on the same file system. Files that are 'renamed' to another file system will actually be copied, then deleted. + +OPTIONS + -f overwrite without prompt + -i prompt before overwriting + unless -f + -v verbose + -h, --help show this help + +EXAMPLES + mv a b + Renames file `a` to `b`. + + mv /home/a /var/b + Moves file from `/home/a` to `/var/b`. \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/pastebin b/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/pastebin new file mode 100755 index 00000000..bfbe8535 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/pastebin @@ -0,0 +1,23 @@ +NAME + pastebin - download and upload programs from and to pastebin + +SYNOPSIS + pastebin get PASTID FILE + pastebin put FILE + pastebin run PASTEID [ARGUMENT]... + +DESCRIPTION + The pastebin program allows downloading programs from pastebin, identified by their paste ID. I can also be used to upload programs to pastebin. + + The pastebin program requires an internet card and internet access to be enabled in the mod's configuration. + +OPTIONS + -f + do not prompt before overwriting + +EXAMPLES + pastebin get AbCdEfGh test + Downloads the paste with ID `AbCdEfGh` and writes it to file `test`. + + pastebin put prog.lua + Uploads the program `prog.lua` to pastebin. \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/primary b/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/primary new file mode 100755 index 00000000..b868f738 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/primary @@ -0,0 +1,16 @@ +NAME + primary - get or set primary components + +SYNOPSIS + primary TYPE + primary TYPE ADDRESS + +DESCRIPTION + This program allows reading the address of the current primary component of the specified type. It also allows changing the current primary component for a specified type by providing the (abbreviated) address of the new primary component. + +EXAMPLES + primary gpu + Writes the address of the current primary GPU to the standard output. + + primary gpu 24a + Makes the GPU of which the address starts with `24a` the new primary GPU. \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/pwd b/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/pwd new file mode 100755 index 00000000..cab464b6 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/pwd @@ -0,0 +1,12 @@ +NAME + pwd - print name of current/working directory + +SYNOPSIS + pwd + +DESCRIPTION + `pwd` writes the name of the current working directory to the standard output. + +EXAMPLES + pwd + Write the current directory to the terminal. \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/rc b/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/rc new file mode 100755 index 00000000..5a8dcffb --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/rc @@ -0,0 +1,35 @@ +NAME + rc - Manage services + +SYNOPSIS + rc SERVICE COMMAND [ARGS...] + +DESCRIPTION + Controls services in /etc/rc.d/ + Common commands are start/stop/restart, there are also special commands enable/disable. A command is global function in executable file that is stored in /etc/rc.d/ directory. Services can define their own commands. + +COMMANDS + start + This command starts specified service, executed automatically for all enabled services when system boots. + + stop + This command stops specified service. + + restart + This command restarts specified service. This command doesn't have to be implemented by services when start and stop commands are present. + + enable + This command enables specified service. Executing this command won't start the service. It's implemented by the rc library, but can be overridden by service. + + disable + This command disables specified service. Executing this command won't stop the service. It's implemented by the rc library, but can be overridden by service. + +EXAMPLES + rc example + Lists commands of example service. + + rc example start + Starts example service. + + rc example enable + Makes example start on system boot. diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/reboot b/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/reboot new file mode 100755 index 00000000..d0361127 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/reboot @@ -0,0 +1,12 @@ +NAME + reboot - restarts the computer + +SYNOPSIS + reboot + +DESCRIPTION + `reboot` will immediately issue a reboot of the computer, shutting it down then starting it back up. + +EXAMPLES + reboot + Reboots the computer. \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/redstone b/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/redstone new file mode 100755 index 00000000..930ba34b --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/redstone @@ -0,0 +1,22 @@ +NAME + redstone - access to redstone input and output + +SYNOPSIS + redstone SIDE + redstone SIDE VALUE + redstone SIDE -b COLOR + redstone SIDE -b COLOR VALUE + +DESCRIPTION + This program allows manipulating redstone output of the computer via the shell, if it has a built-in redstone card or is connected to a redstone I/O block. It can also be used to just display the current in- and output. If another mod is installed that provides bundled redstone logic, such as RedLogic, MineFactory Reloaded (Rednet Cables) or Project: Red, it can also be used to interact with bundled signals by passing the `-b` flag. + +OPTIONS + -b + interact with bundled signals + +EXAMPLES + redstone front + Displays the simple in- and output on the front face of a computer with a redstone card. + + redstone north -b lime 200 + Sets the bundled `lime` output of a redstone I/O block's northern side to 200. \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/resolution b/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/resolution new file mode 100755 index 00000000..50db233e --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/resolution @@ -0,0 +1,16 @@ +NAME + resolution - get or set screen resolution + +SYNOPSIS + resolution + resolution WIDTH HEIGHT + +DESCRIPTION + The `resolution` program is used to write the resolution of the current primary screen to the standard output, or to change the resolution to a new value. + +EXAMPLES + resolution + Displays the current screen resolution. + + resolution 30 10 + Sets the screen's resolution to 30 by 10 characters. diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/rm b/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/rm new file mode 100755 index 00000000..acb2ed25 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/rm @@ -0,0 +1,52 @@ +NAME + rm - remove files or directories + +SYNOPSIS + rm [OPTION]... FILE... + +DESCRIPTION + Removes all of the specified files, one by one. By default, it does not remove directories. To remove a mount, please use umount. + + If the -I option is given, and there are more than three files or the -r, -R, or --recursive are given, the rm prompts the user for whether to proceed with the entire operation. If the response is not affirmative, the entire command is aborted. + + Otherwise, if a file is unwritablle, standard input is a terminal, and the -f or --force option is not given, or the -i option is given, rm prompts the user for whether to remove the file. If the response is not affirmative, the file is skipped. + +OPTIONS + Remove (unlink) the FILE(s). + + -f, --force + ignore nonexistent files and arguments, never prompt + + -i prompt before every removal + + -I prompt once before removing more than three files, or when removing recursively; less intrusive than -i, while still giving protection against most mistakes + + --one-file-system + when removing a hierarchy recursively, skip any directory that is on a file system different from that of the corresponding command line argument + + --no-preserve-root + do not treat '/' specially + + --preserve-root + do not remove '/' (default) + + -r, -R, --recursive + remove directories and their contents recursively + + -d, --dir + remove empty directories + + -v, --verbose + explain what is being done + + --help + display this help and exit. + +By default, rm does not remove directories. Use the --recursive (-r or -R) option to remove each listed directory, too, along with all of its contents. + +To remove a file whose name starts with a '-', for example '-foo', use this command: + rm ./-foo + +EXAMPLES + rm a + Deletes the file `a`. diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/sh b/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/sh new file mode 100755 index 00000000..9341521e --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/sh @@ -0,0 +1,40 @@ +NAME + sh - command interpreter (shell) + +SYNOPSIS + sh + +DESCRIPTION + This is the basic, built-in standard shell of OpenOS. It provides basic functionality and does the job for getting started. To run a command, enter it and press enter. The first token in a command will usually be a program. Any additional parameters will be passed along to the program. + + Arguments to programs can be quoted, to provide strings with multiple spaces in them, for example: + echo "a b" + will print the string `a b` to the screen. It is also possible to use single quotes (echo 'a b'). + + Single quotes also suppress variable expansion. Per default, expressions like `$NAME` and `${NAME}` are expanded using environment variables (also accessible via the `os.getenv` method). + + Basic globbing is supported, i.e. '*' and '?' are expanded approriately. For example: + ls b?n/ + will list all files in `/bin/` (and, if it exists `/ban` and so on). + cp /bin/* /usr/bin/ + will copy all files from `/bin` to `/usr/bin`. + + The shell provides basic redirects and piping: + cat f > f2 + copies the contents of file `f` to `f2`, for example. + echo 'this is a "test"' >> f2 + will append the string 'this is a "test"' to the file `f2`. + + Redirects can be combined: + cat < f >> f2 + will feed the contents of file `f` to cat, which will then output it (in append mode) to file `f2`. + + Finally, pipes can be used to pass data between programs: + ls | cat > f + will enumerate the files and directories in the working directory, write them to its output stream, which is cat's input stream, which will in turn write the data to file `f`. + + The shell also supports aliases, which can be created using `alias` and removed using `unalias` (or using the `shell` API). For example, `dir` is a standard alias for `ls`. + +EXAMPLES + sh + Starts a new shell. \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/shutdown b/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/shutdown new file mode 100755 index 00000000..e8edd13a --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/shutdown @@ -0,0 +1,12 @@ +NAME + shutdown - shut down the computer + +SYNOPSIS + shutdown + +DESCRIPTION + Immediately shuts down the computer. + +EXAMPLES + shutdown + Stops the computer. \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/umount b/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/umount new file mode 100755 index 00000000..0d7417b4 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/umount @@ -0,0 +1,17 @@ +NAME + umount - remove a file system mount + +SYNOPSIS + umount PATH + umount -a LABEL + umount -a ADDRESS + +DESCRIPTION + Removes either a single mount point if given the path into a mount, or all mount points for a specified file system if given the label or address of the file system. + +EXAMPLES + umount /mnt/82f + Unmounts the automatically generated mountpoint at `/mnt/82f`. + + umount -a 82f + Removes all mounts of the file system for which the address starts with `82f`. diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/unalias b/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/unalias new file mode 100755 index 00000000..ed0f16d1 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/unalias @@ -0,0 +1,12 @@ +NAME + unalias - removes aliases for programs + +SYNOPSIS + unalias name + +DESCRIPTION + Allows removal of aliases created with the `alias` command. + +EXAMPLES + unalias dir + Removes the `dir` alias (usually an alias for `ls`). \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/uptime b/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/uptime new file mode 100755 index 00000000..3f39f593 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/uptime @@ -0,0 +1,12 @@ +NAME + uptime - how long has the computer been running + +SYNOPSIS + uptime + +DESCRIPTION + Writes the time in real time hours, minutes and seconds the computer has been running to the standard output. + +EXAMPLES + uptime + Displays the time the computer has been running. \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/useradd b/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/useradd new file mode 100755 index 00000000..9eab2507 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/useradd @@ -0,0 +1,16 @@ +NAME + useradd - adds a player to the list of authorized users + +SYNOPSIS + useradd NAME + +DESCRIPTION + Adds a player to the list of users that can use the computer. To add a player, he has to be logged in when the command is run. Note that the names are case sensitive. Users can be removed again using `userdel`. + + As long as the list of players registered on a computer is empty, the computer can be used by all players. Once there is at least one entry in the list of users, only the players in the list can use the computer. This includes modifying its inventory, performing keyboard and mouse input as well as breaking the computer block. + + Computer ownership can be disabled in the configuration. + +EXAMPLES + useradd Steve + Adds the player named `Steve` to the list of users. \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/userdel b/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/userdel new file mode 100755 index 00000000..ae9d57f5 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/userdel @@ -0,0 +1,12 @@ +NAME + userdel - removes a player from the list of authorized users + +SYNOPSIS + userdel NAME + +DESCRIPTION + Removes a player from the list of users authorized to use this computer. See `useradd`. + +EXAMPLES + userdel Steve + Removes the player named `Steve` from the userlist. \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/wget b/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/wget new file mode 100755 index 00000000..01ee95bd --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/wget @@ -0,0 +1,23 @@ +NAME + wget - download files via http + +SYNOPSIS + wget URL [FILE] + +DESCRIPTION + The wget program allows downloading programs from the interwebs, given the URL to download from. + + The wget program requires an internet card and internet access to be enabled in the mod's configuration. + +OPTIONS + -f + do not prompt before overwriting + -q + only print errors, no status messages + +EXAMPLES + wget http://example.com/data.zip + Downloads the file `data.zip` and saves it as `data.zip`. + + wget http://example.com/data.zip blah.zip + Downloads the file `data.zip` and saves it as `blah.zip`. \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/which b/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/which new file mode 100755 index 00000000..f514689a --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/which @@ -0,0 +1,15 @@ +NAME + which - locate a command + +SYNOPSIS + which COMMAND + +DESCRIPTION + This program writes the full path to each of the specified programs to the standard output. If a program is an alias, this is indicated. If a program cannot be found and error message will be written. + +EXAMPLES + which ls + Displays `/bin/ls.lua`. + + which doesntexist dir + Displays `doesntexist: file not found` and `dir: aliased to ls`. \ No newline at end of file diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/yes b/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/yes new file mode 100755 index 00000000..b302e057 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/man/yes @@ -0,0 +1,24 @@ +NAME + yes - Automatically answers yes to every question. + +SYNOPSIS + yes [string]... + yes [-V/h] + +DESCRIPTION + Prints strings in command line arguments, if there is none, prints 'y'. + Followed by newline, until it is killed. + + This might be used for programs which don't have force (-f) option + and require user interaction. + +EXAMPLES + yes + Says y on every line. + + yes no + Says no on every line. + + yes "yes: I'm great servant, but an evil master." + Repeats sentence. + diff --git a/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/misc/greetings.txt b/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/misc/greetings.txt new file mode 100755 index 00000000..17467d51 --- /dev/null +++ b/640cd89f-8e29-4b66-a0eb-7680c33760b4/usr/misc/greetings.txt @@ -0,0 +1,28 @@ +Tier 2 and 3 screens can act as touch screens - don't attach a keyboard or sneak-activate them. +You can change the text size on screens by changing their resolution - run `resolution 40 16` in the shell. +Firing arrows on touch capable screens can trigger touch events. +Item colors indicate their tier - white is tier one, yellow is tier two and cyan is tier three. +Use an Analyzer to get more information on blocks - for example, to find out why a computer crashed. +Keyboards have to be attached to or placed next to a screen to work. +You can install OpenOS on a writable medium by running the `install` program. +Internet Cards can be used to make HTTP requests and open raw TCP connections. +If you crafted something by mistake or don't need it any longer, throw it into a disassembler. +Have a look at the code of the built-in programs for examples on how to use the APIs. +Most programs can be interrupted by pressing Ctrl+Alt+C. +Paste the contents of the clipboard using the middle mouse button or a configurable key (default: insert). +Computers will consume less power while idling - i.e. when os.sleep(n > 0.05) is called. +Screens will consume more power the more lit characters they display. +Most blocks act as 'cables' - use relays and power distributors to create separate networks. +Welcome to the dark side - here, have some cookies. +Screens can display Unicode - paste the special chars or use unicode.char. +Run `help` or `man programname` for ingame help on programs shipped with OpenOS - start with `man man`. +For more help, there's a wiki at http://ocdoc.cil.li/ - or find the IRC loot disk and join #oc. +Computers have a very basic, built-in speaker - control it using computer.beep(). +Many component methods have a short documentation - use `=component.componentName.methodName` in the Lua interpreter to see it. +You can get a list of all attached components using the `components` program. +If you encounter out of memory errors, throw more RAM at your computer. +Have you tried turning it off and on again? +To disable this greeting, install OpenOS to a writeable medium and delete `/etc/motd`. +Did you know OpenComputers has a forum? No? Well, it's at https://oc.cil.li/. +Please report bugs on the Github issue tracker, thank you! +Beware of cycles when building networks, or you may get duplicate messages! diff --git a/Applications.cfg b/Applications.cfg index abed2391..fe80fa1b 100644 --- a/Applications.cfg +++ b/Applications.cfg @@ -239,7 +239,7 @@ path="/lib/MineOSInterface.lua", url="https://raw.githubusercontent.com/IgorTimofeev/OpenComputers/master/lib/MineOSInterface.lua", type="Library", - version=1.30, + version=1.31, }, { path="/lib/MineOSPaths.lua", @@ -319,7 +319,7 @@ url="https://raw.githubusercontent.com/IgorTimofeev/OpenComputers/master/lib/GUI.lua", type="Library", preloadFile=true, - version=2.13, + version=2.14, }, { path="/lib/rayEngine.lua", @@ -506,7 +506,7 @@ type="Application", icon="https://raw.githubusercontent.com/IgorTimofeev/OpenComputers/master/Applications/HEX/Icon.pic", createShortcut=true, - version=1.09, + version=1.11, }, { path="/MineOS/Applications/Spinner", @@ -573,7 +573,7 @@ type="Application", icon="https://raw.githubusercontent.com/IgorTimofeev/OpenComputers/master/Applications/Braille/Icon.pic", createShortcut=true, - version=1.08, + version=1.09, }, { path="/MineOS/Applications/GeoScan2", diff --git a/Applications/Braille/Main.lua b/Applications/Braille/Main.lua index 4897a5aa..dc4512fa 100644 --- a/Applications/Braille/Main.lua +++ b/Applications/Braille/Main.lua @@ -166,7 +166,7 @@ newButton.onTouch = function() end saveButton.onTouch = function() - local filesystemDialog = GUI.addFilesystemDialogToContainer(mainContainer, "OK", "Cancel", "Path", "/") + local filesystemDialog = GUI.addFilesystemDialogToContainer(mainContainer, 50, math.floor(mainContainer.height * 0.8), true, "OK", "Cancel", "Path", "/") filesystemDialog:setMode(GUI.filesystemModes.save, GUI.filesystemModes.file) filesystemDialog:addExtensionFilter(".pic") @@ -229,7 +229,7 @@ saveButton.onTouch = function() end openButton.onTouch = function() - local filesystemDialog = GUI.addFilesystemDialogToContainer(mainContainer, "OK", "Cancel", "Path", "/") + local filesystemDialog = GUI.addFilesystemDialogToContainer(mainContainer, 50, math.floor(mainContainer.height * 0.8), true, "OK", "Cancel", "Path", "/") filesystemDialog:setMode(GUI.filesystemModes.open, GUI.filesystemModes.file) filesystemDialog:addExtensionFilter(".braiile") diff --git a/Applications/HEX/Main.lua b/Applications/HEX/Main.lua index f9020c55..bbcce368 100755 --- a/Applications/HEX/Main.lua +++ b/Applications/HEX/Main.lua @@ -321,7 +321,7 @@ local function load(path) end openFileButton.onTouch = function() - local filesystemDialog = GUI.addFilesystemDialogToContainer(mainContainer, "Open", "Cancel", "File name", "/") + local filesystemDialog = GUI.addFilesystemDialogToContainer(mainContainer, 50, math.floor(mainContainer.height * 0.8), true, "Open", "Cancel", "File name", "/") filesystemDialog:setMode(GUI.filesystemModes.open, GUI.filesystemModes.file) filesystemDialog:show() filesystemDialog.onSubmit = function(path) @@ -332,7 +332,7 @@ openFileButton.onTouch = function() end saveFileButton.onTouch = function() - local filesystemDialog = GUI.addFilesystemDialogToContainer(mainContainer, "Save", "Cancel", "File name", "/") + local filesystemDialog = GUI.addFilesystemDialogToContainer(mainContainer, 50, math.floor(mainContainer.height * 0.8), true, "Save", "Cancel", "File name", "/") filesystemDialog:setMode(GUI.filesystemModes.save, GUI.filesystemModes.file) filesystemDialog:show() filesystemDialog.onSubmit = function(path) diff --git a/lib/GUI.lua b/lib/GUI.lua index 18ea1e2c..21b4b6b1 100755 --- a/lib/GUI.lua +++ b/lib/GUI.lua @@ -2202,13 +2202,17 @@ local function filesystemDialogAddExtensionFilter(filesystemDialog, extension) filesystemDialog:setMode(filesystemDialog.IOMode, filesystemDialog.filesystemMode) end +local function filesystemDialogExpandPath(filesystemDialog, ...) + filesystemDialog.filesystemTree:expandPath(...) +end + function GUI.filesystemDialog(x, y, width, height, submitButtonText, cancelButtonText, placeholderText, path) local filesystemDialog = GUI.container(x, y, width, height) filesystemDialog:addChild(GUI.panel(1, height - 2, width, 3, 0xD2D2D2)) - filesystemDialog.cancelButton = filesystemDialog:addChild(GUI.adaptiveRoundedButton(1, height - 1, 2, 0, 0xE1E1E1, 0x3C3C3C, 0x3C3C3C, 0xE1E1E1, cancelButtonText)) - filesystemDialog.submitButton = filesystemDialog:addChild(GUI.adaptiveRoundedButton(1, height - 1, 2, 0, 0x3C3C3C, 0xE1E1E1, 0xE1E1E1, 0x3C3C3C, submitButtonText)) + filesystemDialog.cancelButton = filesystemDialog:addChild(GUI.adaptiveRoundedButton(1, height - 1, 1, 0, 0xE1E1E1, 0x3C3C3C, 0x3C3C3C, 0xE1E1E1, cancelButtonText)) + filesystemDialog.submitButton = filesystemDialog:addChild(GUI.adaptiveRoundedButton(1, height - 1, 1, 0, 0x3C3C3C, 0xE1E1E1, 0xE1E1E1, 0x3C3C3C, submitButtonText)) filesystemDialog.submitButton.localX = filesystemDialog.width - filesystemDialog.submitButton.width - 1 filesystemDialog.cancelButton.localX = filesystemDialog.submitButton.localX - filesystemDialog.cancelButton.width - 2 @@ -2224,6 +2228,7 @@ function GUI.filesystemDialog(x, y, width, height, submitButtonText, cancelButto filesystemDialog.setMode = filesystemDialogSetMode filesystemDialog.addExtensionFilter = filesystemDialogAddExtensionFilter + filesystemDialog.expandPath = filesystemDialogExpandPath filesystemDialog:setMode(GUI.filesystemModes.open, GUI.filesystemModes.file) return filesystemDialog @@ -2244,11 +2249,14 @@ end ----------------------------------------------------------------------------------------------------- -function GUI.addFilesystemDialogToContainer(parentContainer, ...) +function GUI.addFilesystemDialogToContainer(parentContainer, width, height, addPanel, ...) local container = parentContainer:addChild(GUI.container(1, 1, parentContainer.width, parentContainer.height)) - container:addChild(GUI.object(1, 1, container.width, container.height)) - local filesystemDialog = container:addChild(GUI.filesystemDialog(1, 1, math.floor(container.width * 0.35), math.floor(container.height * 0.8), ...)) + if addPanel then + container:addChild(GUI.panel(1, 1, container.width, container.height, 0x0, 0.3)) + end + + local filesystemDialog = container:addChild(GUI.filesystemDialog(1, 1, width, height, ...)) filesystemDialog.localX = math.floor(container.width / 2 - filesystemDialog.width / 2) filesystemDialog.localY = -filesystemDialog.height @@ -2315,7 +2323,7 @@ local function filesystemChooserEventHandler(mainContainer, object, eventData) mainContainer:draw() buffer.draw() - local filesystemDialog = GUI.addFilesystemDialogToContainer(mainContainer, object.submitButtonText, object.cancelButtonText, object.placeholderText, object.filesystemDialogPath) + local filesystemDialog = GUI.addFilesystemDialogToContainer(mainContainer, 50, math.floor(mainContainer.height * 0.8), false, object.submitButtonText, object.cancelButtonText, object.placeholderText, object.filesystemDialogPath) for key in pairs(object.extensionFilters) do filesystemDialog:addExtensionFilter(key) @@ -2738,7 +2746,7 @@ local function filesystemTreeUpdateFileListRecursively(tree, path, offset) if tree.showMode == GUI.filesystemModes.both or tree.showMode == GUI.filesystemModes.file then for i = 1, #list do - tree:addItem(list[i], path .. list[i], offset, false,tree.extensionFilters and not tree.extensionFilters[fs.extension(path .. list[i], true)] or false) + tree:addItem(list[i], path .. list[i], offset, false, tree.extensionFilters and not tree.extensionFilters[fs.extension(path .. list[i], true)] or false) end end end @@ -2753,12 +2761,21 @@ local function filesystemTreeAddExtensionFilter(tree, extensionFilter) tree.extensionFilters[unicode.lower(extensionFilter)] = true end +local function filesystemTreeExpandPath(tree, path) + local blyadina = tree.workPath + for pizda in path:gmatch("[^/]+") do + blyadina = blyadina .. pizda .. "/" + tree.expandedItems[blyadina] = true + end +end + function GUI.filesystemTree(...) local tree = GUI.tree(...) tree.workPath = "/" tree.updateFileList = filesystemTreeUpdateFileList tree.addExtensionFilter = filesystemTreeAddExtensionFilter + tree.expandPath = filesystemTreeExpandPath tree.onItemExpanded = function() tree:updateFileList() end diff --git a/lib/MineOSInterface.lua b/lib/MineOSInterface.lua index 1df03d5e..d315d008 100755 --- a/lib/MineOSInterface.lua +++ b/lib/MineOSInterface.lua @@ -945,7 +945,7 @@ function MineOSInterface.addUniversalContainer(parentContainer, title) container.layout:setCellFitting(2, 1, true, false) if title then - container.layout:addChild(GUI.label(1, 1, 1, 1, 0xE1E1E1, title)):setAlignment(GUI.alignment.horizontal.center, GUI.alignment.vertical.top) + container.label = container.layout:addChild(GUI.label(1, 1, 1, 1, 0xE1E1E1, title)):setAlignment(GUI.alignment.horizontal.center, GUI.alignment.vertical.top) end container.panel.eventHandler = function(mainContainer, object, eventData)