Compare commits

...

303 Commits
1.3.1 ... main

Author SHA1 Message Date
Jean-Patrick Guerrero 9437545ddb Bump version 2023-04-02 16:56:20 +02:00
Jean-Patrick Guerrero ff7353392f Fix tooltips 2023-04-02 16:56:18 +02:00
Jean-Patrick Guerrero 45fc03cc26 Bump version 2023-04-02 14:56:14 +02:00
Jean-Patrick Guerrero f26a1c288d New tooltip color + cleaning 2023-04-02 14:49:13 +02:00
Jean-Patrick Guerrero 85fc8219c1 Move notifs to the left 2023-04-02 01:23:11 +02:00
Jean-Patrick Guerrero 83623effbf More HUD improvements 2023-04-02 00:45:39 +02:00
Jean-Patrick Guerrero c64f28a90b Prevent potential crash 2023-04-01 22:51:21 +02:00
Jean-Patrick Guerrero ad08ca56b8 More improvements to notification look 2023-04-01 22:45:18 +02:00
Jean-Patrick Guerrero 1684388851 Ability to stack notifications on top of each other 2023-04-01 22:17:41 +02:00
Jean-Patrick Guerrero 1f43c8fac2 Optimize textures 2023-04-01 18:53:12 +02:00
Jean-Patrick Guerrero 6953a11279 Improve notification look 2023-04-01 18:35:29 +02:00
Jean-Patrick Guerrero 21affdeeba Eventually remove window info 2023-04-01 14:43:02 +02:00
Jean-Patrick Guerrero 7130ea565d Doc 2023-03-08 16:43:35 +01:00
Jean-Patrick Guerrero b7308b4739 Get window info once per second 2023-03-08 16:42:15 +01:00
Jean-Patrick Guerrero d5df9f6f0f Store window info for future work 2023-03-07 14:07:52 +01:00
Jean-Patrick Guerrero 7246249c52 Bump version 2023-03-07 01:24:44 +01:00
Jean-Patrick Guerrero d36d6e67e7 Fix regression 2023-03-07 01:24:27 +01:00
Jean-Patrick Guerrero d5fc865634 Bump version 2023-03-06 22:24:32 +01:00
Jean-Patrick Guerrero fe475747ea Fix crash with some mods 2023-03-06 22:00:49 +01:00
Jean-Patrick Guerrero e2fafbc876 New click sound 2023-03-06 12:38:12 +01:00
Jean-Patrick Guerrero 3b860082b5 Doc 2023-03-05 14:53:36 +01:00
Jean-Patrick Guerrero 36cdcde7ed Bump version 2023-03-05 14:52:55 +01:00
Jean-Patrick Guerrero c9e02dfe09 Fix replacements in Quick Crafting 2023-03-05 14:24:16 +01:00
Jean-Patrick Guerrero 7005794cc3 Merge globalsteps 2023-03-05 13:57:24 +01:00
Jean-Patrick Guerrero 66b88dec3b Bump version 2023-02-05 19:01:03 +01:00
Jean-Patrick Guerrero c207db38a0 Minor tweak 2023-02-02 19:29:35 +01:00
Jean-Patrick Guerrero c89cbf700f New crafting sound 2023-02-02 03:17:48 +01:00
Jean-Patrick Guerrero a1a4535f26 Quick Crafting: indicate the missing materials when you cannot craft 2023-02-02 02:20:42 +01:00
Jean-Patrick Guerrero 80b927de1c Improve set home button look 2023-02-01 16:15:37 +01:00
Jean-Patrick Guerrero 482027d09b Small cleanup 2023-02-01 15:06:36 +01:00
Jean-Patrick Guerrero ea7140a7bb Bugfix 2023-02-01 04:23:28 +01:00
Jean-Patrick Guerrero 84416b130b Minor cleaning 2023-02-01 04:22:03 +01:00
Jean-Patrick Guerrero b31e7a48ab API: Add field to toggle slots in custom tabs 2023-02-01 04:08:52 +01:00
Jean-Patrick Guerrero 7a4f352ce4 Limit HUDCHANGE pkt sending 2023-01-29 21:47:13 +01:00
Jean-Patrick Guerrero 675349bd40 Bump version 2023-01-22 14:20:04 +01:00
Jean-Patrick Guerrero e17f4e4c29 HUD: minor tweak 2023-01-21 19:18:40 +01:00
Jean-Patrick Guerrero 3b1d569118 Attempt to Quick Crafting replacements (again) 2023-01-21 00:43:38 +01:00
Jean-Patrick Guerrero d55994c535 Minor fix 2023-01-20 21:57:27 +01:00
Jean-Patrick Guerrero b6b97aa284 Fix group replacements in Quick Crafting 2023-01-20 21:20:12 +01:00
Jean-Patrick Guerrero b4482f0acf Update MIT License 2023-01-20 16:07:28 +01:00
Jean-Patrick Guerrero e918942439 Minor fix 2023-01-20 16:06:25 +01:00
Jean-Patrick Guerrero 889259ac5e Fix Quick Crafting bugs 2023-01-20 14:39:34 +01:00
Jean-Patrick Guerrero 187b0339bd Minor changes to API 2023-01-19 18:57:16 +01:00
Jean-Patrick Guerrero 74e88acadf Don't allow removing the 'All' tab 2023-01-19 18:34:43 +01:00
Jean-Patrick Guerrero 9e1f608846 Small fix 2023-01-19 18:03:56 +01:00
Jean-Patrick Guerrero 8289d290a3 Fix HUD 2023-01-19 17:58:24 +01:00
Jean-Patrick Guerrero d833199628 API.md: Add summary 2023-01-19 16:50:01 +01:00
Jean-Patrick Guerrero ac7a1fb746 Complete minitabs API 2023-01-19 16:40:31 +01:00
Jean-Patrick Guerrero b95b179a5a Add an API to add minitabs 2023-01-19 01:24:45 +01:00
Jean-Patrick Guerrero 38f1d7c960 Bump version 2023-01-11 00:10:30 +01:00
Jean-Patrick Guerrero 5d4f9b4709 Get rid of Factorio's sounds 2023-01-11 00:07:27 +01:00
Jean-Patrick Guerrero a0a3394e18 Add API to manage waypoints 2023-01-07 12:47:45 +01:00
Jean-Patrick Guerrero d9a16bf39d Bump version 2022-12-05 03:08:19 +01:00
Jean-Patrick Guerrero 2a2837dd0c Minor cleaning 2022-12-01 20:28:44 +01:00
Jean-Patrick Guerrero 080579b2a4 Show player name based on nametag 2022-12-01 20:23:53 +01:00
Jean-Patrick Guerrero b711f8f195 API doc clarification 2022-11-12 19:54:17 +01:00
Jean-Patrick Guerrero 00a258afea Show colored itemstacks the right way 2022-10-02 14:38:49 +02:00
Jean-Patrick Guerrero dd8657ab56 Waypoint viewer: use an animate flag 2022-10-02 13:49:06 +02:00
Jean-Patrick Guerrero 34fa63519e Minor tweak 2022-09-28 15:31:39 +02:00
Jean-Patrick Guerrero 066e0a5d9d Improve wording 2022-09-26 03:17:34 +02:00
Jean-Patrick Guerrero 8fc01b7ece Minor cleaning 2022-09-25 18:57:27 +02:00
Jean-Patrick Guerrero a1af79a870 Minor cleaning 2022-09-25 18:16:17 +02:00
Jean-Patrick Guerrero a5ceae07d7 Oopsie 2022-09-25 17:43:07 +02:00
Jean-Patrick Guerrero 7f437b21f4 Bump version 2022-09-25 17:22:38 +02:00
Jean-Patrick Guerrero e8a811f72c Save more settings accross restarts 2022-09-25 17:22:36 +02:00
Jean-Patrick Guerrero 4c4911eb4f Fix 2022-09-25 16:55:41 +02:00
Jean-Patrick Guerrero 325d6f30be 🚀 Greatly improve Progressive Mode look 2022-09-25 16:23:11 +02:00
Jean-Patrick Guerrero 05995a22df Add sound for skins 2022-09-24 16:24:01 +02:00
Jean-Patrick Guerrero 32594e7552 Small fix 2022-09-24 14:30:45 +02:00
Jean-Patrick Guerrero 27ed1f02e6 More cleaning 2022-09-24 14:23:47 +02:00
Jean-Patrick Guerrero 916e80f2c3 Cleanup vectors 2022-09-24 13:46:50 +02:00
Jean-Patrick Guerrero c42ea6c005 Minor tweak 2022-09-20 01:12:07 +02:00
Jean-Patrick Guerrero da91223c4d Bump version 2022-09-18 14:24:45 +02:00
Jean-Patrick Guerrero 408267754c API.md: rewrite some parts 2022-09-15 12:28:23 +02:00
Jean-Patrick Guerrero b4de48370a HUD: small tweak 2022-09-14 15:37:29 +02:00
Jean-Patrick Guerrero 477efe56d3 Bump version 2022-09-04 19:17:42 +02:00
Jean-Patrick Guerrero 8afb51dae8 Some cleaning 2022-09-04 19:12:57 +02:00
Jean-Patrick Guerrero 734b09b69f Minor cleaning 2022-08-29 11:29:09 +02:00
Jean-Patrick Guerrero 1560d59d4a Bump version 2022-08-28 16:36:30 +02:00
Jean-Patrick Guerrero e4e175a775 Minor tweak 2022-08-28 16:36:08 +02:00
Jean-Patrick Guerrero 7a40f36611 Bump version 2022-08-28 15:36:44 +02:00
Jean-Patrick Guerrero 2467e8bb0b Fix sound play 2022-08-28 15:34:37 +02:00
Jean-Patrick Guerrero 9666834aed Minor tweak 2022-08-28 15:21:20 +02:00
Jean-Patrick Guerrero 7670356c8c Bump version (again) 2022-08-28 14:46:03 +02:00
Jean-Patrick Guerrero 646d16afd8 Hotfix HUD 2022-08-28 14:45:37 +02:00
Jean-Patrick Guerrero 6b54dbc934 Bump version 2022-08-28 14:23:36 +02:00
Jean-Patrick Guerrero 911bed3911 Add option in Style settings to show wielditem descriptions in HUD 2022-08-28 14:18:05 +02:00
Jean-Patrick Guerrero 75fdd57f2a Integrate Legacy Inventory option per-player in Settings popup 2022-08-28 13:31:01 +02:00
Jean-Patrick Guerrero c8d6312772 Minor cleaning 2022-08-21 23:47:15 +02:00
Jean-Patrick Guerrero a4e8fac0e6 Update preview screenshot 2022-08-20 17:45:44 +02:00
Jean-Patrick Guerrero eac4a18df2 HOTFIX 2022-08-20 16:59:26 +02:00
Jean-Patrick Guerrero 2ddaa4ddfb Bump version 2022-08-20 16:20:44 +02:00
Jean-Patrick Guerrero 56b5cb78f2 Minor fix 2022-08-16 16:44:31 +02:00
Jean-Patrick Guerrero 813d27d2cc Minor cleaning 2022-08-16 14:25:02 +02:00
Jean-Patrick Guerrero 52464d4486 Add checkbox to change the panel style (preview: https://i.imgur.com/gNPLf0B.png) 2022-08-16 14:18:26 +02:00
Jean-Patrick Guerrero e300539dd3 Cleaning 2022-08-15 18:19:02 +02:00
Jean-Patrick Guerrero 2cdd03b127 Redesign settings 2022-08-15 17:51:38 +02:00
Jean-Patrick Guerrero 8db3fb4a41 Minor fixes 2022-08-15 15:25:23 +02:00
Jean-Patrick Guerrero d3ad413876 Minor cleaning 2022-08-15 01:00:34 +02:00
Jean-Patrick Guerrero 6219c2f64e Fix bug with Quick Crafting 2022-08-14 21:57:45 +02:00
Jean-Patrick Guerrero 55d364acae Bump version 2022-08-14 20:37:07 +02:00
Jean-Patrick Guerrero 0a8ae9a3d7 Take replacements into consideration in Quick Crafting (fix #73) 2022-08-14 20:29:22 +02:00
Jean-Patrick Guerrero 0e2f233234 Improve Quick Crafting look a bit 2022-08-14 20:12:28 +02:00
Jean-Patrick Guerrero aeeac6ac1b New highlight look 2022-08-14 19:37:44 +02:00
Jean-Patrick Guerrero 0c4b4e6c4f Add ability to goto pagenum from pagenum button 2022-08-14 19:21:01 +02:00
Jean-Patrick Guerrero 7b31c998de Specify custom recipe type in UI 2022-08-14 17:41:03 +02:00
Jean-Patrick Guerrero 7ee19e3dc5 Minor tweak 2022-08-09 15:19:01 +02:00
Jean-Patrick Guerrero 60e21627e1 Some tweaks 2022-08-08 22:09:39 +02:00
Jean-Patrick Guerrero 7e7422def7 🚀 Redesign the armor tab (preview: https://i.imgur.com/Ulkx6KB.png) 2022-08-08 02:50:12 +02:00
Jean-Patrick Guerrero aa10460886 Bump version 2022-08-07 16:34:41 +02:00
Jean-Patrick Guerrero cef11f5301 Update README 2022-08-07 16:34:19 +02:00
Jean-Patrick Guerrero 2297b47dc0 Minor Fix 2022-08-07 01:40:49 +02:00
Jean-Patrick Guerrero a67ef8b08b Some adjusting 2022-08-07 01:23:36 +02:00
Jean-Patrick Guerrero 48ab26b4c6 Add a font size global change slider 2022-08-07 00:42:52 +02:00
Jean-Patrick Guerrero 1389027a22 Another UI tweak 2022-08-01 06:43:20 +02:00
Jean-Patrick Guerrero 629676a975 Bump version 2022-08-01 04:34:06 +02:00
Jean-Patrick Guerrero 20b8110375 Minor tweak 2022-08-01 04:21:17 +02:00
Jean-Patrick Guerrero a3fea93a09 Remove all the legacy code (MT 5.6+ required) 2022-08-01 04:04:14 +02:00
Jean-Patrick Guerrero 326ee3e098 Improve search bar look 2022-08-01 03:54:35 +02:00
Jean-Patrick Guerrero 60bdfa34fb Cleaner bg + slot textures 2022-07-04 05:10:31 +02:00
Jean-Patrick Guerrero 9971b8c3e6 Improve get_desc() 2022-07-04 03:01:30 +02:00
Jean-Patrick Guerrero 5a14116b69 Improve alphabetical sorting 2022-07-03 19:40:23 +02:00
Jean-Patrick Guerrero 222e04d2c4 Minor cleaning 2022-07-03 19:21:13 +02:00
Jean-Patrick Guerrero 8f4c9f28f1 Also for slots 2022-07-03 18:55:13 +02:00
Jean-Patrick Guerrero 37e0c21ca3 Add support for bgimg_middle (better looking tabs) 2022-07-03 17:27:03 +02:00
Jean-Patrick Guerrero 057f0cf03f Fix again 2022-07-03 05:07:49 +02:00
Jean-Patrick Guerrero dca93b7249 Minor cleaning 2022-07-02 02:21:14 +02:00
Jean-Patrick Guerrero 1e9c3ce55a Minor cleaning 2022-07-01 17:28:50 +02:00
Jean-Patrick Guerrero 1627172cce Check string limits correctly 2022-07-01 17:15:10 +02:00
Jean-Patrick Guerrero bd356e4a40 Minor QoL 2022-07-01 15:50:04 +02:00
Jean-Patrick Guerrero f26c6af9c4 Minor fix 2022-07-01 15:15:21 +02:00
Jean-Patrick Guerrero 3f2d983091 Minor tweak 2022-07-01 03:46:25 +02:00
Jean-Patrick Guerrero 6dd95a6a87 Remove the bullshit and fix the sprite once and for all 2022-07-01 03:36:16 +02:00
Jean-Patrick Guerrero 32779ab56f Add comments 2022-07-01 03:10:37 +02:00
Jean-Patrick Guerrero 4ccff6621b Minor tweak 2022-07-01 02:43:05 +02:00
Jean-Patrick Guerrero 7d0f25e8b2 Minor tweak 2022-07-01 00:47:43 +02:00
Jean-Patrick Guerrero 652c486249 Tweak textures 2022-06-30 16:31:43 +02:00
Jean-Patrick Guerrero 9ab47fc0f0 Improve UTF-8 string length counting 2022-06-30 15:18:32 +02:00
Jean-Patrick Guerrero c635343c9b Add a 404 image when no item found 2022-06-30 03:10:24 +02:00
Jean-Patrick Guerrero 662c938afb Minor UI improvement 2022-06-30 02:32:03 +02:00
Jean-Patrick Guerrero b2c8447971 Some UI improvements + fix bug with custom tabs 2022-06-30 01:15:35 +02:00
Jean-Patrick Guerrero c91f787cb2 Correct condition 2022-06-28 01:24:36 +02:00
Jean-Patrick Guerrero dcc4068e46 Fix potential crash during sprite creation 2022-06-28 01:03:41 +02:00
Jean-Patrick Guerrero c421c49916 Fix bug with group stereotype 2022-06-23 18:23:03 +02:00
Jean-Patrick Guerrero 3dfcd95c6f Tweak preprocessor to add -- decrement 2022-06-23 15:04:39 +02:00
Jean-Patrick Guerrero 72d4a5d4b8 Minor cleaning 2022-06-23 14:14:54 +02:00
Jean-Patrick Guerrero f2b4c960ad Fix bug with favs 2022-06-23 02:30:54 +02:00
Jean-Patrick Guerrero 5d6ce3be18 Improve preprocessor a bit 2022-06-23 01:14:45 +02:00
Jean-Patrick Guerrero 5f413a150b Minor improvement 2022-06-19 15:23:50 +02:00
Jean-Patrick Guerrero 563dc719d0 Put a upper limit for sprite creation 2022-06-19 14:47:52 +02:00
Jean-Patrick Guerrero 56cb236025 Fix again 2022-06-19 03:33:21 +02:00
Jean-Patrick Guerrero 8525633d4c Improve groups caching 2022-06-19 02:54:11 +02:00
Jean-Patrick Guerrero cb1dce66f9 Some last fix 2022-06-19 00:36:57 +02:00
Jean-Patrick Guerrero 2db1e885fc Minor cleanup 2022-06-18 23:40:22 +02:00
Jean-Patrick Guerrero 354561d54c Finish animation for group buttons (yay) 2022-06-18 23:20:57 +02:00
Jean-Patrick Guerrero 8e45f303d3 Some fix to previous commit 2022-06-18 22:32:51 +02:00
Jean-Patrick Guerrero a6605263f4 Animated image buttons for groups (needs polish) 2022-06-18 21:52:08 +02:00
Jean-Patrick Guerrero e17e1080d3 Remove some unessecerary [resize 2022-06-18 19:59:34 +02:00
Jean-Patrick Guerrero f0f94017da Cache groups (testing needed) 2022-06-18 19:53:47 +02:00
Jean-Patrick Guerrero cf5f18e1c1 Register creative priv in case MTG is not installed 2022-06-17 03:12:55 +02:00
Jean-Patrick Guerrero 4736b551a8 Fix crash in progressive mode 2022-06-09 12:33:56 +02:00
Jean-Patrick Guerrero e1c0f106cc Fix Luacheck warnings on Ubuntu 22.04 2022-04-26 01:50:25 +02:00
Jean-Patrick Guerrero 46f1136bb7 Empty slots don't make click sound 2022-04-25 19:24:24 +02:00
Jean-Patrick Guerrero e88921fe72 Fix crash 2022-04-25 16:00:49 +02:00
Jean-Patrick Guerrero 7e0feefc89 API doc: minor correction 2022-03-23 03:52:08 +01:00
Jean-Patrick Guerrero 44a6256589 Fix tab access on priv revoking 2022-03-21 03:55:22 +01:00
Jean-Patrick Guerrero 1dd742e887 HUD: minor tweak 2022-03-12 04:30:20 +01:00
Jean-Patrick Guerrero 518ed971ca Hide pagenum when only one page of skins 2022-03-01 12:15:28 +01:00
Jean-Patrick Guerrero e9b8085fde Tweak quick crafting 2022-02-27 19:13:27 +01:00
Jean-Patrick Guerrero 6dc12390db Tweak sorting 2022-02-27 18:53:26 +01:00
Jean-Patrick Guerrero 44610b879e Skin selection UI overhaul 2022-02-26 02:09:53 +01:00
Jean-Patrick Guerrero b0c9bcf3b9 Update screenshot 2022-02-21 01:02:57 +01:00
Jean-Patrick Guerrero de70846d6c Smoother scrolling in skins category 2022-02-20 20:38:51 +01:00
SmallJoker 8b6f50b387 Fix potential crash with skinssb 2022-02-20 18:57:52 +01:00
Jean-Patrick Guerrero 55c6d09389 Wording 2022-01-31 18:09:53 +01:00
Jean-Patrick Guerrero e3941a7b71 Add max_waypoints setting 2022-01-16 18:16:56 +01:00
Jean-Patrick Guerrero 5e8ecf9903 Add util/ 2022-01-15 04:06:31 +01:00
Jean-Patrick Guerrero 2fa971acb4 Fix missing func 2022-01-11 02:49:44 +01:00
Jean-Patrick Guerrero 617ef40a1d Small cleanup 2022-01-09 03:29:10 +01:00
Jean-Patrick Guerrero 6b8e64b532 Split more stuff into different file 2022-01-08 17:33:01 +01:00
Jean-Patrick Guerrero eedc77086f Some cleaning 2022-01-08 16:40:10 +01:00
Jean-Patrick Guerrero ea10743255 mod.conf fix 2022-01-08 15:59:01 +01:00
Jean-Patrick Guerrero 4fe094f3ba Add min_version in mod.conf 2022-01-06 13:35:18 +01:00
Jean-Patrick Guerrero 364534d154 Debug mode: more benchmark precision 2022-01-05 02:12:51 +01:00
Jean-Patrick Guerrero 11e19dd80a Update issue templates 2022-01-04 16:30:35 +01:00
Jean-Patrick Guerrero f0d1caa1b4 Small cleaning 2022-01-04 15:38:59 +01:00
Jean-Patrick Guerrero b48c7862dc Add debug_mode setting + cleanup 2022-01-02 14:51:53 +01:00
Jean-Patrick Guerrero d8e17687e1 A bit of cleaning 2021-12-25 17:24:30 +01:00
Jean-Patrick Guerrero 2fadcdefdd Fix variable name 2021-12-25 17:01:25 +01:00
Jean-Patrick Guerrero c7f6e1db62 API: fix custom recipe registration 2021-12-24 00:43:38 +01:00
Jean-Patrick Guerrero 16a1865e11 More cleaning 2021-12-21 17:16:46 +01:00
Jean-Patrick Guerrero 748cc9a7a1 Small cleaning 2021-12-21 15:39:04 +01:00
Jean-Patrick Guerrero edf0867e6c Don't use bitops for 5.4 clients 2021-12-15 13:45:19 +01:00
Jean-Patrick Guerrero 7275767427 Fix crash with certain mods enabled (address #40) 2021-12-14 21:51:02 +01:00
Jean-Patrick Guerrero c9f7e0a40e Standardize global setting names 2021-12-13 00:34:15 +01:00
Jean-Patrick Guerrero 1f4cec3420 Put back GitHub workflow 2021-12-10 00:35:58 +01:00
Jean-Patrick Guerrero 612bdc86d1 Update README 2021-12-10 00:09:40 +01:00
Jean-Patrick Guerrero 03968b70c5 Add a cool ASCII art 2021-12-09 23:59:29 +01:00
Jean-Patrick Guerrero 10629aeb89 Optimize things out 2021-12-08 16:48:23 +01:00
Jean-Patrick Guerrero 551a9d517d Small improvement 2021-12-08 02:52:43 +01:00
Jean-Patrick Guerrero bcca6f00be Improve rendering of waypoint previews 2021-12-08 02:38:22 +01:00
Jean-Patrick Guerrero 2fcd559261 Some cleanup 2021-12-07 02:55:04 +01:00
Jean-Patrick Guerrero cffdf77e6a Hide items without recipes/usages in survival mode 2021-12-07 00:06:12 +01:00
Jean-Patrick Guerrero cacb9a29fd Add legacy_inventory setting (disabled by default) 2021-12-06 15:16:19 +01:00
Jean-Patrick Guerrero 382ff397a5 HTTP API: Address security hole (thanks @rubenwardy) 2021-12-06 14:44:40 +01:00
Jean-Patrick Guerrero e4fdfa60bf Minor optimization 2021-11-30 20:49:50 +01:00
Jean-Patrick Guerrero b5bb00b90c Update Screenshot 2021-11-30 19:27:43 +01:00
Jean-Patrick Guerrero ab690398b7 Add script for Luacheck 2021-11-30 19:00:47 +01:00
Jean-Patrick Guerrero 0b4250b54b Small cleaning 2021-11-30 16:55:32 +01:00
Jean-Patrick Guerrero b0ed0f8497 Some cleanup 2021-11-30 05:19:17 +01:00
Jean-Patrick Guerrero 80b675f817 Last commit's cleanup 2021-11-30 05:05:07 +01:00
Jean-Patrick Guerrero 95b0434f95 Bags can have custom names 2021-11-30 02:30:49 +01:00
Jean-Patrick Guerrero 94a86fc0c4 Add comment 2021-11-29 20:41:33 +01:00
Jean-Patrick Guerrero d459521b67 Small cleaning 2021-11-29 20:24:26 +01:00
Jean-Patrick Guerrero 939019f4c6 Don't allow putting a filled bag in another bag 2021-11-29 20:03:30 +01:00
Jean-Patrick Guerrero 27226a0d1b Reduce code + Remove github workflow 2021-11-29 19:22:42 +01:00
Jean-Patrick Guerrero 91af3b73d2 More cleaning 2021-11-29 15:51:12 +01:00
Jean-Patrick Guerrero b92857b42a Support for plantlike in isometric map preview 2021-11-29 04:36:37 +01:00
Jean-Patrick Guerrero eaf7c486b8 Add custom operators (Part 2) 2021-11-29 02:48:25 +01:00
Jean-Patrick Guerrero 34548d8509 Add custom operators (Part 1) 2021-11-29 00:35:50 +01:00
Jean-Patrick Guerrero b3f1cf255d Move stuff to HUD 2021-11-26 05:02:08 +01:00
Jean-Patrick Guerrero 54ba95ac80 API: Add i3.hud_notif 2021-11-26 03:34:01 +01:00
Jean-Patrick Guerrero 40a8c548ab Make correct inventory cubes 2021-11-25 19:31:27 +01:00
Jean-Patrick Guerrero eb5a0a68de Add an isometric area preview on Waypoints 2021-11-25 05:04:01 +01:00
Jean-Patrick Guerrero 3d9881fd6c Add HUD_SPEED var 2021-11-24 17:12:20 +01:00
Jean-Patrick Guerrero b0326dab43 Progressive Mode: more cleaning 2021-11-24 02:46:36 +01:00
Jean-Patrick Guerrero 02e66f368a Cleanup Progressive mode 2021-11-24 01:50:43 +01:00
Jean-Patrick Guerrero 0cedd16efe Clean bag code (warning: break backward compat) 2021-11-24 01:08:47 +01:00
Jean-Patrick Guerrero 88b1e56c87 Small cleaning 2021-11-23 01:47:59 +01:00
Jean-Patrick Guerrero 1285a77060 Some cleaning 2021-11-22 22:53:14 +01:00
Jean-Patrick Guerrero 3c1355b13e Re-upload a new screenshot 2021-11-21 22:47:46 +01:00
Jean-Patrick Guerrero dae512015e Update Screenshot 2021-11-21 19:28:01 +01:00
Jean-Patrick Guerrero ec4e929491 GUI: show wear for recipe output 2021-11-21 18:57:40 +01:00
Jean-Patrick Guerrero b4d7dfddef README: Add new badges 2021-11-21 06:03:22 +01:00
Jean-Patrick Guerrero a64266498f Some polish to awards fs 2021-11-20 17:12:04 +01:00
Jean-Patrick Guerrero 734136ac46 Minor thing 2021-11-20 16:38:04 +01:00
Jean-Patrick Guerrero 6c3d2fe5cf Minor API change 2021-11-19 19:35:41 +01:00
Jean-Patrick Guerrero dc8efbd16c De-hardcode 2021-11-19 02:08:29 +01:00
Jean-Patrick Guerrero b62687e84b Small cleanup 2021-11-16 08:50:34 +01:00
Jean-Patrick Guerrero d95caf847a Little tweaking 2021-11-16 05:09:37 +01:00
Jean-Patrick Guerrero 8f49914c9f Minor optimizations 2021-11-16 00:23:06 +01:00
Jean-Patrick Guerrero c5dec1bc77 Disable fields for no-op buttons 2021-11-15 16:50:27 +01:00
Jean-Patrick Guerrero a5751a2771 Small cleanup 2021-11-15 16:30:59 +01:00
Jean-Patrick Guerrero 58e2eb22a7 Optimize textures 2021-11-15 11:06:25 +01:00
Jean-Patrick Guerrero 09b34f0274 Add tooltips to options 2021-11-15 03:29:21 +01:00
Jean-Patrick Guerrero e60f986484 Minor polish 2021-11-15 01:55:48 +01:00
Jean-Patrick Guerrero f2248ae176 Tweak some colors 2021-11-15 00:46:02 +01:00
Jean-Patrick Guerrero 849fadb674 Some polish to previous commit 2021-11-15 00:09:06 +01:00
Jean-Patrick Guerrero 2e7dcd714d Totally rework how bags work + other cool stuff 2021-11-14 22:16:40 +01:00
Jean-Patrick Guerrero 9cc8464111 Little polish 2021-11-11 04:05:13 +01:00
Jean-Patrick Guerrero 09cb35e1fd Allow for group bag=4 2021-11-09 01:42:17 +01:00
Jean-Patrick Guerrero ea8b216fa5 Update Screenshot 2021-11-08 21:56:54 +01:00
Jean-Patrick Guerrero d9f8b738e5 Cleanup waypoints code 2021-11-08 21:03:38 +01:00
Jean-Patrick Guerrero 2f612bb5da Small fix 2021-11-08 20:13:51 +01:00
Jean-Patrick Guerrero 6f5d0a1760 Inventory sorting: Add option for rejecting items 2021-11-08 20:10:52 +01:00
Jean-Patrick Guerrero c51da4a20b New sounds for teleport/trash 2021-11-08 18:48:42 +01:00
Jean-Patrick Guerrero af2c1304ac Small tweak 2021-11-07 23:12:09 +01:00
Jean-Patrick Guerrero 5587341f2e Cleaning + API fix 2021-11-07 22:55:01 +01:00
Jean-Patrick Guerrero 9afbee72ce Additional check in API 2021-11-06 02:20:34 +01:00
Jean-Patrick Guerrero 22bfbf1f56 Simplify recipes comparison 2021-11-04 01:01:33 +01:00
Jean-Patrick Guerrero f779492aed Add new badge to README 2021-11-03 04:34:29 +01:00
Jean-Patrick Guerrero bbe020885c Tweak .luacheck 2021-11-03 04:31:36 +01:00
Jean-Patrick Guerrero fdfacba495 Create luacheck.yml 2021-11-03 04:25:58 +01:00
Jean-Patrick Guerrero d6085d1c63 Rename folder 2021-11-02 02:11:08 +01:00
Jean-Patrick Guerrero 47e68a3e92 More cleaning 2021-11-02 01:47:13 +01:00
Jean-Patrick Guerrero 25e57f6a23 Minor cleaning 2021-11-01 15:43:38 +01:00
Jean-Patrick Guerrero 4fcf4f5790 Rename some variables 2021-11-01 15:34:10 +01:00
Jean-Patrick Guerrero 1728f4beac Add 'ignore hotbar' option for inventory sorting 2021-11-01 15:29:20 +01:00
Jean-Patrick Guerrero 9276598e3e Add new buttons to main inventory, add inventory sorting methods + API 2021-10-31 23:17:15 +01:00
Jean-Patrick Guerrero 8d7ca9df18 Add online recipe to tests 2021-10-31 15:03:47 +01:00
Jean-Patrick Guerrero 3b98f4bec7 Wording 2021-10-25 23:41:56 +02:00
Jean-Patrick Guerrero 980adeb71f Some cleaning 2021-10-25 22:00:19 +02:00
Jean-Patrick Guerrero 2d9e4ee474 Fix CRLF 2021-10-25 19:18:11 +02:00
Jean-Patrick Guerrero 3a83e015ae Small cleaning 2021-10-25 07:38:18 +02:00
Jean-Patrick Guerrero 6d2682b8d9 Cleaning 2021-10-25 06:45:50 +02:00
Jean-Patrick Guerrero 9e50f58f5b Add Compression API 2021-10-25 00:23:44 +02:00
Jean-Patrick Guerrero bd5ea4e6a8 Split init.lua into separate files (Part II) 2021-10-24 23:31:01 +02:00
Jean-Patrick Guerrero caba7f3599 Some cleaning 2021-10-19 06:54:34 +02:00
Jean-Patrick Guerrero 43b20b317e Add Troubleshooting to README 2021-10-19 05:17:27 +02:00
Jean-Patrick Guerrero 8cd04deff4 API Cleanup 2021-10-19 04:16:23 +02:00
Jean-Patrick Guerrero 853ddc9d94 Split init.lua into separate files (Part I) 2021-10-19 03:42:45 +02:00
Jean-Patrick Guerrero f8ac9c45d1 Add i3.get_recipes() 2021-10-18 21:24:29 +02:00
Jean-Patrick Guerrero b6d36e59b0 Add missing doc 2021-10-18 21:11:07 +02:00
Jean-Patrick Guerrero 744c9d5b02 Follow-up commit 2021-10-18 21:03:58 +02:00
Jean-Patrick Guerrero e5f446480e
Merge pull request #34 from wsor4035/main
Allow bags via item groups
2021-10-18 20:54:01 +02:00
unknown a147b9e6a3 spacing 2021-10-13 20:33:06 -04:00
unknown 22961b2758 adress review 2021-10-13 20:31:21 -04:00
unknown ffa50bc3a7 allow bags via item groups 2021-10-12 15:31:08 -04:00
Jean-Patrick Guerrero 815651a32f Fix custom recipes display, add i3.get_current_tab(), remove some superfluous chat messages 2021-09-19 22:04:49 +02:00
Jean-Patrick Guerrero b8db525dac Hide Quick Crafting scrollbars after click 2021-08-24 11:11:38 +02:00
Jean-Patrick Guerrero cbf6256593 Fix potential crash 2021-08-22 02:53:39 +02:00
Jean-Patrick Guerrero 893959b5d4 Fix potential crash in Progressive Mode 2021-08-21 13:27:50 +02:00
Jean-Patrick Guerrero 8a5a14747e Fix crash related to model preview 2021-08-11 17:46:17 +02:00
Jean-Patrick Guerrero b53959f177 Item compression: small fix 2021-08-10 02:10:08 +02:00
Jean-Patrick Guerrero 685b40f318 Update README 2021-08-10 01:57:36 +02:00
Jean-Patrick Guerrero e96d279d29 Progressive Mode: minor fix + HUD bold text support 2021-08-06 04:15:43 +02:00
Jean-Patrick Guerrero c87a179cb5 Minor cleaning 2021-08-06 02:56:36 +02:00
Jean-Patrick Guerrero 7e2256253d Fix skin_id in skins dropdown 2021-07-06 10:58:19 +02:00
Jean-Patrick Guerrero f2cc874ec0 Some tweakings 2021-07-06 01:38:16 +02:00
Jean-Patrick Guerrero 5c96ede065 Add confirmation dialog on inventory clearing 2021-07-05 23:38:43 +02:00
Jean-Patrick Guerrero 1c4ae1df28 Outdated clients are now kicked ruthlessly without fallback 2021-07-05 22:45:32 +02:00
Jean-Patrick Guerrero 970220c561 Add .editorconfig 2021-07-05 03:32:31 +02:00
Jean-Patrick Guerrero 4ca50e846d Fix potential issue with progressive mode 2021-07-05 03:23:19 +02:00
96 changed files with 5853 additions and 3548 deletions

9
.editorconfig Normal file
View File

@ -0,0 +1,9 @@
[*]
end_of_line = lf
[*.{lua,txt,md,json}]
charset = utf8
indent_size = 8
indent_style = tab
insert_final_newline = true
trim_trailing_whitespace = true

24
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@ -0,0 +1,24 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Additional context**
- Mod version? Release or git?
- Engine version?
- LuaJIT enabled?
- Operating system?
- Did you try to disable other mods except i3?

21
.github/workflows/luacheck.yml vendored Normal file
View File

@ -0,0 +1,21 @@
name: Luacheck
on: [push, pull_request]
jobs:
luacheck:
runs-on: ubuntu-20.04
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Setup lua
uses: leafo/gh-actions-lua@v8
with:
luaVersion: 5.1
- name: Setup luarocks
uses: leafo/gh-actions-luarocks@v4
- name: Setup luacheck
run: luarocks install luacheck
- name: Run luacheck linter
run: cd util; lua luacheck.lua

View File

@ -1,6 +1,7 @@
allow_defined_top = true
ignore = {
"631", -- Line is too long.
"get_debug_grid",
}
@ -9,10 +10,13 @@ read_globals = {
"armor",
"skins",
"awards",
"hb",
"vector",
"string",
"table",
"ItemStack",
"VoxelArea",
"VoxelManip",
}
globals = {
@ -21,3 +25,14 @@ globals = {
"sfinv",
"unified_inventory",
}
exclude_files = {
"tests/test_compression.lua",
"tests/test_custom_recipes.lua",
"tests/test_operators.lua",
"tests/test_tabs.lua",
"tests/test_waypoints.lua",
".install",
".luarocks",
}

307
API.md
View File

@ -1,71 +1,104 @@
## API
# API :screwdriver:
### Custom tabs
### Table of Contents
1. [**Tabs**](https://github.com/minetest-mods/i3/blob/main/API.md#tabs)
2. [**Recipes**](https://github.com/minetest-mods/i3/blob/main/API.md#recipes)
3. [**Minitabs**](https://github.com/minetest-mods/i3/blob/main/API.md#minitabs)
4. [**Recipe filters**](https://github.com/minetest-mods/i3/blob/main/API.md#recipe-filters)
5. [**Search filters**](https://github.com/minetest-mods/i3/blob/main/API.md#search-filters)
6. [**Sorting methods**](https://github.com/minetest-mods/i3/blob/main/API.md#sorting-methods)
7. [**Item list compression**](https://github.com/minetest-mods/i3/blob/main/API.md#item-list-compression)
8. [**Waypoints**](https://github.com/minetest-mods/i3/blob/main/API.md#waypoints)
9. [**Miscellaneous**](https://github.com/minetest-mods/i3/blob/main/API.md#miscellaneous)
#### `i3.new_tab(def)`
---
### Tabs
#### `i3.new_tab(name, def)`
- `name` is the tab name.
- `def` is the tab definition.
Custom tabs can be added to the `i3` inventory as follow (example):
```Lua
i3.new_tab {
name = "stuff",
i3.new_tab("stuff", {
description = "Stuff",
image = "image.png", -- Optional, adds an image next to the tab description
image = "image.png", -- Optional, add an image next to the tab description
slots = true -- Optional, whether the inventory slots are shown or not. Disabled by default.
-- Determine if the tab is visible by a player, `false` or `nil` hide the tab
--
-- The functions below are all optional
--
-- Determine if the tab is visible by a player, return false to hide the tab
access = function(player, data)
local name = player:get_player_name()
if name == "singleplayer" then
return false
end
return name == "singleplayer"
end,
-- Build the formspec
formspec = function(player, data, fs)
fs("label[3,1;This is just a test]")
fs("label", 3, 1, "Just a test")
fs"label[3,2;Lorem Ipsum]"
-- No need to return anything
end,
-- Events handling happens here
fields = function(player, data, fields)
if fields.mybutton then
-- Do things
end
-- To prevent a formspec update, return false.
-- Otherwise: no need to return anything, it's automatic.
end,
}
})
```
- `player` is an `ObjectRef` to the user.
- `data` are the user data.
- `fs` is the formspec table which is callable with a metamethod. Each call adds a new entry.
- `fs` is the formspec table which is callable with a metamethod. Every call adds a new entry.
#### `i3.set_fs(player[, extra_formspec])`
#### `i3.set_fs(player)`
Updates the current formspec. `extra_formspec` adds an additional formspec string.
Update the current formspec.
#### `i3.remove_tab(tabname)`
Deletes a tab by name.
Delete a tab by name.
#### `i3.get_current_tab(player)`
Return the current player tab. `player` is an `ObjectRef` to the user.
#### `i3.set_tab(player[, tabname])`
Sets the current tab by name. `player` is an `ObjectRef` to the user.
Set the current tab by name. `player` is an `ObjectRef` to the user.
`tabname` can be omitted to get an empty tab.
#### `i3.override_tab(tabname, def)`
Overrides a tab by name. `def` is the tab definition like seen in `i3.set_tab`.
Override a tab by name. `def` is the tab definition like seen in `i3.set_tab`
#### `i3.get_tabs()`
#### `i3.tabs`
Returns the list of registered tabs.
A list of registered tabs.
---
### Custom recipes
### Recipes
Custom recipes are nonconventional crafts outside the main crafting grid.
They can be registered in-game dynamically and have a size beyond 3x3 items.
**Note:** the registration format differs from the default registration format in everything.
The width is automatically calculated depending where you place the commas. Look at the examples attentively.
The width is automatically calculated depending where you place the commas.
#### Registering a custom crafting type (example)
Examples:
#### Registering a custom crafting type
```Lua
i3.register_craft_type("digging", {
@ -74,31 +107,31 @@ i3.register_craft_type("digging", {
})
```
#### Registering a custom crafting recipe (examples)
#### Registering a custom crafting recipe
```Lua
i3.register_craft({
i3.register_craft {
type = "digging",
result = "default:cobble 2",
items = {"default:stone"},
})
}
```
```Lua
i3.register_craft({
i3.register_craft {
result = "default:cobble 16",
items = {
"default:stone, default:stone, default:stone",
"default:stone, , default:stone",
"default:stone, default:stone, default:stone",
}
})
}
```
Recipes can be registered in a Minecraft-like way:
```Lua
i3.register_craft({
i3.register_craft {
grid = {
"X #",
" ## ",
@ -110,13 +143,13 @@ i3.register_craft({
['X'] = "default:glass",
},
result = "default:mese 3",
})
}
```
Multiples recipes can also be registered:
Multiple recipes can also be registered at once:
```Lua
i3.register_craft({
i3.register_craft {
{
result = "default:mese",
items = {
@ -134,19 +167,66 @@ i3.register_craft({
"default:mese_crystal, default:mese_crystal",
}
},
})
}
```
Recipes can be registered from a given URL containing a JSON file (HTTP support is required¹):
```Lua
i3.register_craft({
i3.register_craft {
url = "https://raw.githubusercontent.com/minetest-mods/i3/main/tests/test_online_recipe.json"
})
}
```
---
### Minitabs
Manage the tabs on the right panel of the inventory.
Allow to make a sensible list sorted by specific groups of items.
#### `i3.new_minitab(name, def)`
Add a new minitab (limited to 6).
- `name` is the tab name.
- `def` is the definition table.
Example:
```Lua
i3.new_minitab("test", {
description = "Test",
-- Whether this tab is visible or not. Optional.
access = function(player, data)
return player:get_player_name() == "singleplayer"
end,
-- Whether a specific item is shown in the list or not.
sorter = function(item, data)
return item:find"wood"
end
})
```
- `player` is an `ObjectRef` to the user.
- `data` are the user data.
- `item` is an item name string.
#### `i3.remove_minitab(name)`
Remove a minitab by name.
- `name` is the name of the tab to remove.
#### `i3.minitabs`
A list of registered minitabs.
---
### Recipe filters
Recipe filters can be used to filter the recipes shown to players. Progressive
@ -154,7 +234,7 @@ mode is implemented as a recipe filter.
#### `i3.add_recipe_filter(name, function(recipes, player))`
Adds a recipe filter with the given `name`. The filter function returns the
Add a recipe filter with the given `name`. The filter function returns the
recipes to be displayed, given the available recipes and an `ObjectRef` to the
user. Each recipe is a table of the form returned by
`minetest.get_craft_recipe`.
@ -176,28 +256,24 @@ end)
#### `i3.set_recipe_filter(name, function(recipe, player))`
Removes all recipe filters and adds a new one.
Remove all recipe filters and add a new one.
#### `i3.remove_recipe_filter(name)`
#### `i3.recipe_filters`
Removes the recipe filter with the given `name`.
#### `i3.get_recipe_filters()`
Returns a map of recipe filters, indexed by name.
A map of recipe filters, indexed by name.
---
### Search filters
Search filters are used to perform specific searches inside the search field.
These filters are cumulative to perform a specific search.
They can be used like so: `<optional_name> +<filter name>=<value1>,<value2>,<...>`
Search filters are used to perform specific searches from the search field.
The filters can be cumulated to perform a specific search.
They are used like so: `<optional_name> +<filter name>=<value1>,<value2>,<...>`
Example usages:
- `+groups=cracky,crumbly`: search for groups `cracky` and `crumbly` in all items.
- `wood +groups=flammable`: search for group `flammable` amongst items which contain
- `+groups=cracky,crumbly` -> search for groups `cracky` and `crumbly` in all items.
- `wood +groups=flammable` -> search for group `flammable` amongst items which contain
`wood` in their names.
Notes:
@ -206,9 +282,12 @@ Notes:
#### `i3.add_search_filter(name, function(item, values))`
Adds a search filter with the given `name`. `values` is a table of all possible values.
Add a search filter.
The search function must return a boolean value (whether the given item should be listed or not).
- `name` is the filter name.
- `values` is a table of all possible values.
Example function sorting items by drawtype:
```lua
@ -225,23 +304,141 @@ i3.add_search_filter("types", function(item, drawtypes)
end)
```
#### `i3.remove_search_filter(name)`
#### `i3.search_filters`
Removes the search filter with the given `name`.
A map of search filters, indexed by name.
#### `i3.get_search_filters()`
---
Returns a map of search filters, indexed by name.
### Sorting methods
Sorting methods are used to filter the player's main inventory.
#### `i3.add_sorting_method(name, def)`
Add a player inventory sorting method.
- `name` is the method name.
- `def` is the method definition.
Example:
```Lua
i3.add_sorting_method("test", {
description = "Cool sorting method",
func = function(list, data)
-- `list`: inventory list
-- `data`: player data
table.sort(list)
-- A list must be returned
return list
end,
})
```
#### `i3.sorting_methods`
A table containing all sorting methods.
---
### Item list compression
`i3` can reduce the item list size by compressing a group of items.
#### `i3.compress(item, def)`
Add a new group of items to compress.
- `item` is the item which represent the group of compressed items.
- `def` is a table specifying the substring replace patterns to be used.
Example:
```Lua
i3.compress("default:diamondblock", {
replace = "diamond",
by = {"bronze", "copper", "gold", "steel", "tin"}
})
```
#### `i3.compress_groups`
A map of all compressed item groups, indexed by stereotypes.
---
### Waypoints
`i3` allows you to manage the waypoints of a specific player.
#### `i3.add_waypoint(player_name, def)`
Add a waypoint to specific player.
- `player_name` is the player name.
- `def` is the waypoint definition table.
Example:
```Lua
i3.add_waypoint("Test", {
player = "singleplayer",
pos = {x = 0, y = 2, z = 0},
color = 0xffff00,
-- image = "heart.png" (optional)
})
```
#### `i3.remove_waypoint(player_name, waypoint_name)`
Remove a waypoint for specific player.
- `player_name` is the player name.
- `waypoint_name` is the waypoint name.
Example:
```Lua
i3.remove_waypoint("singleplayer", "Test")
```
#### `i3.get_waypoints(player_name)`
Return a table of all waypoints of a specific player.
- `player_name` is the player name.
---
### Miscellaneous
#### `i3.hud_notif(name, msg[, img])`
Show a Steam-like HUD notification on the bottom-left corner of the screen.
- `name` is the player name.
- `msg` is the HUD message to show.
- `img` (optional) is the HUD image to show (preferably 16x16 px).
#### `i3.get_recipes(item)`
Return a table of recipes and usages of `item`.
#### `i3.export_url`
If set, the mod will export all the cached recipes and usages in a JSON format
to the given URL (HTTP support is required¹).
#### `groups = {bag = <1-4>}`
The `bag` group in the item definition allows to extend the player inventory size
given a number between 1 and 4.
---
**¹** Add `i3` to the `secure.http_mods` or `secure.trusted_mods` setting in `minetest.conf`.
**[1]** Add `i3` to the `secure.http_mods` or `secure.trusted_mods` setting in `minetest.conf`.

12
LICENSE
View File

@ -3,7 +3,7 @@ License of source code
The MIT License (MIT)
Copyright (c) 2020-2021 Jean-Patrick Guerrero and contributors.
Copyright (c) 2020-2023 Jean-Patrick Guerrero and contributors.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@ -23,6 +23,16 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
Licenses of media (sounds)
--------------------------
Lone_Wolf (CC0):
i3_tab.ogg
i3_click.ogg
i3_cannot.ogg
MadPanCake (CC0):
i3_craft.ogg (https://freesound.org/people/MadPanCake/sounds/567849/)
Licenses of media (textures)
----------------------------

View File

@ -1,29 +1,30 @@
# i3
![logo](https://user-images.githubusercontent.com/7883281/145490041-d91d6bd6-a654-438d-b208-4d5736845ab7.png)
[![ContentDB](https://content.minetest.net/packages/jp/i3/shields/title/)](https://content.minetest.net/packages/jp/i3/) [![ContentDB](https://content.minetest.net/packages/jp/i3/shields/downloads/)](https://content.minetest.net/packages/jp/i3/)
[![GitHub Release](https://img.shields.io/github/release/minetest-mods/i3.svg?style=flat)]() ![workflow](https://github.com/minetest-mods/i3/actions/workflows/luacheck.yml/badge.svg) [![ContentDB](https://content.minetest.net/packages/jp/i3/shields/downloads/)](https://content.minetest.net/packages/jp/i3/) [![PayPal](https://img.shields.io/badge/paypal-donate-yellow.svg)](https://www.paypal.me/jpg84240)
#### **`i3`** is a next-generation inventory for Minetest.
This inventory offers a slick, modern UI made with the latest technologies of the Minetest engine.
This mod features a modern, powerful inventory menu with a good user experience.
**`i3`** provides a rich [**API**](https://github.com/minetest-mods/i3/blob/master/API.md) for mod developers who want to extend it.
This mod requires **Minetest 5.4+**
This mod requires **Minetest 5.6+**
#### List of features:
- Crafting Guide (only in survival mode)
- Crafting Guide (survival mode only)
- Progressive Mode¹
- Quick Crafting
- Backpacks
- 3D Player Model Preview
- Inventory Sorting (alphabetical + item stack compression)
- 3D Player Model Real-Time Preview
- Isometric Map Preview
- Inventory Sorting (+ options: compression, reverse mode, automation, etc.)
- Item List Compression (**`moreblocks`** is supported)
- Item Bookmarks
- Waypoints
- Item List Compression (**`moreblocks`** supported)
- Bags
- Home
**¹** *This mode is a Terraria-like system that shows recipes you can craft from items you ever had in your inventory.
To enable it: `i3_progressive_mode = true` in `minetest.conf`.*
#### This mod officially supports the following mods:
- [**`3d_armor`**](https://content.minetest.net/packages/stu/3d_armor/)
- [**`skinsdb`**](https://content.minetest.net/packages/bell07/skinsdb/)
@ -31,22 +32,26 @@ To enable it: `i3_progressive_mode = true` in `minetest.conf`.*
#### Recommendations
To use this mod in the best conditions, it's recommended to follow these recommendations:
To use this mod in the best conditions:
- Use LuaJIT
- Use a HiDPI widescreen display
- Use the default Freetype font style
#### Troubleshooting
If the inventory's font size is too big on certain setups (namely Windows 10/11 or 144 DPI display), you should lower the
value of the setting `display_density_factor` in your `minetest.conf`. Note that the change is applied after restart.
You can also use the font size slider in the inventory, settings window.
#### Notes
`i3` uses a larger inventory than the usual inventories in Minetest games.
Thus, most chests will be unadapted to this inventory size.
The `i3` inventory is 9 slots wide by default (without backpack), such as in Minecraft.
The `i3` inventory is 9 slots wide by default, such as Minecraft.
Report any bug on the [**Bug Tracker**](https://github.com/minetest-mods/i3/issues).
Report bugs on the [**Bug Tracker**](https://github.com/minetest-mods/i3/issues).
Love this mod? Donations are appreciated: https://www.paypal.me/jpg84240
**Video review on YouTube:** https://www.youtube.com/watch?v=Xd14BCdEZ3o
Demo video (outdated): https://www.youtube.com/watch?v=25nCAaqeacU
![Preview](https://user-images.githubusercontent.com/7883281/123561657-10ba7780-d7aa-11eb-8bbe-dcec348bb28c.png)
![Preview](https://user-images.githubusercontent.com/7883281/185755315-23c2fffa-203d-4115-9dc3-576c92615733.png)

3460
init.lua

File diff suppressed because it is too large Load Diff

View File

@ -1,3 +1,4 @@
name = i3
description = Tiling inventory for Minetest
description = Next-generation inventory
optional_depends = 3d_armor, skinsdb, awards
min_minetest_version = 5.6

View File

@ -1,5 +1,2 @@
# The progressive mode shows recipes you can craft from items you ever had in your inventory.
i3_progressive_mode (Learn crafting recipes progressively) bool false
# Regroup the items of the same type in the item list.
i3_item_compression (Regroup items of the same type) bool true

BIN
sounds/i3_achievement.ogg Normal file

Binary file not shown.

BIN
sounds/i3_cannot.ogg Normal file

Binary file not shown.

BIN
sounds/i3_click.ogg Executable file → Normal file

Binary file not shown.

BIN
sounds/i3_craft.ogg Executable file → Normal file

Binary file not shown.

BIN
sounds/i3_heavy_armor.ogg Normal file

Binary file not shown.

BIN
sounds/i3_heavy_boots.ogg Normal file

Binary file not shown.

BIN
sounds/i3_heavy_helmet.ogg Normal file

Binary file not shown.

Binary file not shown.

BIN
sounds/i3_heavy_shield.ogg Normal file

Binary file not shown.

BIN
sounds/i3_light_armor.ogg Normal file

Binary file not shown.

BIN
sounds/i3_light_boots.ogg Normal file

Binary file not shown.

BIN
sounds/i3_light_helmet.ogg Normal file

Binary file not shown.

Binary file not shown.

BIN
sounds/i3_light_shield.ogg Normal file

Binary file not shown.

BIN
sounds/i3_skin_change.ogg Normal file

Binary file not shown.

BIN
sounds/i3_tab.ogg Executable file → Normal file

Binary file not shown.

BIN
sounds/i3_teleport.ogg Normal file

Binary file not shown.

BIN
sounds/i3_trash.ogg Normal file

Binary file not shown.

499
src/api.lua Normal file
View File

@ -0,0 +1,499 @@
local http = ...
local make_fs, get_inventory_fs = i3.files.gui()
IMPORT("sorter", "sort_inventory", "play_sound")
IMPORT("sort", "concat", "copy", "insert", "remove")
IMPORT("get_player_by_name", "add_hud_waypoint", "init_hud_notif")
IMPORT("gmatch", "split", "S", "err", "fmt", "reg_items", "pos_to_str")
IMPORT("true_str", "true_table", "is_str", "is_func", "is_table", "clean_name")
function i3.register_craft_type(name, def)
if not true_str(name) then
return err "i3.register_craft_type: name missing"
elseif not true_table(def) then
return err "i3.register_craft_type: definition missing"
elseif not is_str(def.description) then
def.description = ""
end
i3.craft_types[name] = def
end
function i3.register_craft(def)
local width, c = 0, 0
if http and true_str(def.url) then
http.fetch({url = def.url}, function(result)
if result.succeeded then
local t = core.parse_json(result.data)
if is_table(t) then
return i3.register_craft(t)
end
end
end)
return
end
if not true_table(def) then
return err "i3.register_craft: craft definition missing"
end
if #def > 1 then
for _, v in pairs(def) do
i3.register_craft(v)
end
return
end
if def.result then
def.output = def.result -- Backward compatibility
def.result = nil
end
if not true_str(def.output) and not def.url then
return err "i3.register_craft: output missing"
end
if not is_table(def.items) then
def.items = {}
end
if def.grid then
if not is_table(def.grid) then
def.grid = {}
end
if not is_table(def.key) then
def.key = {}
end
local cp = copy(def.grid)
sort(cp, function(a, b) return #a > #b end)
width = #cp[1]
for i = 1, #def.grid do
while #def.grid[i] < width do
def.grid[i] = def.grid[i] .. " "
end
end
for symbol in gmatch(concat(def.grid), ".") do
c++
def.items[c] = def.key[symbol]
end
else
local items = copy(def.items)
local lines = {}
def.items = {}
for i = 1, #items do
lines[i] = split(items[i], ",", true)
if #lines[i] > width then
width = #lines[i]
end
end
for i = 1, #items do
while #lines[i] < width do
insert(lines[i], items[i])
end
end
for _, line in ipairs(lines) do
for _, v in ipairs(line) do
c++
def.items[c] = clean_name(v)
end
end
end
local item = ItemStack(def.output):get_name()
i3.recipes_cache[item] = i3.recipes_cache[item] or {}
def.custom = true
def.width = width
insert(i3.recipes_cache[item], def)
end
function i3.add_recipe_filter(name, f)
if not true_str(name) then
return err "i3.add_recipe_filter: name missing"
elseif not is_func(f) then
return err "i3.add_recipe_filter: function missing"
end
i3.recipe_filters[name] = f
end
function i3.set_recipe_filter(name, f)
if not is_str(name) then
return err "i3.set_recipe_filter: name missing"
elseif not is_func(f) then
return err "i3.set_recipe_filter: function missing"
end
i3.recipe_filters = {[name] = f}
end
function i3.add_search_filter(name, f)
if not true_str(name) then
return err "i3.add_search_filter: name missing"
elseif not is_func(f) then
return err "i3.add_search_filter: function missing"
end
i3.search_filters[name] = f
end
function i3.get_recipes(item)
item = core.registered_aliases[item] or item
local recipes = i3.recipes_cache[item]
local usages = i3.usages_cache[item]
return {recipes = recipes, usages = usages}
end
function i3.set_fs(player)
if not player or player.is_fake_player then return end
local name = player:get_player_name()
local data = i3.data[name]
if not data then return end
if data.auto_sorting then
sort_inventory(player, data)
end
for i, tab in ipairs(i3.tabs) do
if data.tab == i and tab.access and not tab.access(player, data) then
data.tab = 1
break
end
end
local fs = make_fs(player, data)
player:set_inventory_formspec(fs)
end
function i3.new_tab(name, def)
if not true_str(name) then
return err "i3.new_tab: tab name missing"
elseif not true_table(def) then
return err "i3.new_tab: tab definition missing"
elseif not true_str(def.description) then
return err "i3.new_tab: description missing"
elseif #i3.tabs == 6 then
return err(fmt("i3.new_tab: cannot add '%s' tab. Limit reached (6).", def.name))
end
def.name = name
insert(i3.tabs, def)
end
i3.new_tab("inventory", {
description = S"Inventory",
formspec = get_inventory_fs,
slots = true,
})
function i3.remove_tab(name)
if not true_str(name) then
return err "i3.remove_tab: tab name missing"
end
for i = #i3.tabs, 2, -1 do
local def = i3.tabs[i]
if def and name == def.name then
remove(i3.tabs, i)
end
end
end
function i3.get_current_tab(player)
local name = player:get_player_name()
local data = i3.data[name]
return data.tab
end
function i3.set_tab(player, tabname)
local name = player:get_player_name()
local data = i3.data[name]
if not tabname or tabname == "" then
data.tab = 0
return
end
for i, tab in ipairs(i3.tabs) do
if tab.name == tabname then
data.tab = i
return
end
end
err(fmt("i3.set_tab: tab name '%s' does not exist", tabname))
end
function i3.override_tab(name, newdef)
if not true_str(name) then
return err "i3.override_tab: tab name missing"
elseif not true_table(newdef) then
return err "i3.override_tab: tab definition missing"
elseif not true_str(newdef.description) then
return err "i3.override_tab: description missing"
end
newdef.name = name
for i, def in ipairs(i3.tabs) do
if def.name == name then
i3.tabs[i] = newdef
break
end
end
end
i3.register_craft_type("digging", {
description = S"Digging",
icon = "i3_steelpick.png",
})
i3.register_craft_type("digging_chance", {
description = S"Digging (by chance)",
icon = "i3_mesepick.png",
})
i3.add_search_filter("groups", function(item, groups)
local def = reg_items[item]
local has_groups = true
for _, group in ipairs(groups) do
if not def.groups[group] then
has_groups = nil
break
end
end
return has_groups
end)
function i3.compress(item, def)
if not true_str(item) then
return err "i3.compress: item name missing"
elseif not true_table(def) then
return err "i3.compress: replace definition missing"
elseif not true_str(def.replace) then
return err "i3.compress: replace string missing"
elseif not is_table(def.by) then
return err "i3.compress: replace substrings missing"
elseif i3.compressed[item] then
return err(fmt("i3.compress: item '%s' is already compressed", item))
end
local t = {}
i3.compress_groups[item] = i3.compress_groups[item] or {}
for _, str in ipairs(def.by) do
local it = item:gsub(def.replace, str)
insert(t, it)
insert(i3.compress_groups[item], it)
i3.compressed[it] = true
end
end
function i3.hud_notif(name, msg, img)
if not true_str(name) then
return err "i3.hud_notif: player name missing"
elseif not true_str(msg) then
return err "i3.hud_notif: message missing"
end
local data = i3.data[name]
if not data then
return err "i3.hud_notif: no player data initialized"
end
local player = get_player_by_name(name)
if not player then return end
local max_y = -125
local def = {
show = true,
max = {x = -330, y = max_y},
hud_msg = msg,
hud_img = img and fmt("%s^[resize:64x64", img) or nil,
hud_timer = 0,
elems = init_hud_notif(player),
}
insert(data.hud.notifs, def)
play_sound(name, "i3_achievement", 1.0)
local nb_notifs = #data.hud.notifs
for i = 1, nb_notifs - 1 do
local notif = data.hud.notifs[i]
if notif then
notif.show = true
notif.max.y = ((nb_notifs - i) + 1) * max_y
notif.hud_timer = 0.5 * (nb_notifs - i)
end
end
end
function i3.add_sorting_method(name, def)
if not true_str(name) then
return err "i3.add_sorting_method: name missing"
elseif not true_table(def) then
return err "i3.add_sorting_method: definition missing"
elseif not is_func(def.func) then
return err "i3.add_sorting_method: function missing"
end
def.name = name
insert(i3.sorting_methods, def)
end
i3.add_sorting_method("alphabetical", {
description = S"Sort items by name (A-Z)",
func = function(list, data)
sorter(list, data, 1)
return list
end
})
i3.add_sorting_method("numerical", {
description = S"Sort items by number of items per stack",
func = function(list, data)
sorter(list, data, 2)
return list
end,
})
function i3.add_waypoint(name, def)
if not true_str(name) then
return err "i3.add_waypoint: name missing"
elseif not true_table(def) then
return err "i3.add_waypoint: definition missing"
elseif not true_str(def.player) then
return err "i3.add_waypoint: player name missing"
end
local data = i3.data[def.player]
if not data then
return err "i3.add_waypoint: no player data initialized"
end
local player = get_player_by_name(def.player)
local id = player and add_hud_waypoint(player, name, def.pos, def.color, def.image) or nil
insert(data.waypoints, {
name = name,
pos = pos_to_str(def.pos, 1),
color = def.color,
image = def.image,
id = id,
})
if data.subcat == 5 then
data.scrbar_inv += 1000
end
i3.set_fs(player)
end
function i3.remove_waypoint(player_name, name)
if not true_str(player_name) then
return err "i3.remove_waypoint: player name missing"
elseif not true_str(name) then
return err "i3.remove_waypoint: waypoint name missing"
end
local data = i3.data[player_name]
if not data then
return err "i3.remove_waypoint: no player data initialized"
end
local player = get_player_by_name(player_name)
for i = #data.waypoints, 1, -1 do
local waypoint = data.waypoints[i]
if waypoint and name == waypoint.name then
if player then
player:hud_remove(waypoint.id)
end
remove(data.waypoints, i)
end
end
i3.set_fs(player)
end
function i3.get_waypoints(player_name)
if not true_str(player_name) then
return err "i3.get_waypoints: player name missing"
end
local data = i3.data[player_name]
if not data then
return err "i3.get_waypoints: no player data initialized"
end
return data.waypoints
end
function i3.new_minitab(name, def)
if #i3.minitabs == 6 then
return err "i3.new_minitab: limit reached (6)"
elseif not true_str(name) then
return err "i3.new_minitab: name missing"
elseif not true_table(def) then
return err "i3.new_minitab: definition missing"
end
def.name = name
insert(i3.minitabs, def)
end
function i3.remove_minitab(name)
if not true_str(name) then
return err "i3.remove_minitab: name missing"
end
for i = #i3.minitabs, 2, -1 do
local v = i3.minitabs[i]
if v and v.name == name then
remove(i3.minitabs, i)
end
end
end
i3.new_minitab("all", {
description = "All",
sorter = function()
return true
end
})
i3.new_minitab("nodes", {
description = "Nodes",
sorter = function(item)
return core.registered_nodes[item]
end
})
i3.new_minitab("items", {
description = "Items",
sorter = function(item)
return core.registered_craftitems[item] or core.registered_tools[item]
end
})

173
src/bags.lua Normal file
View File

@ -0,0 +1,173 @@
local set_fs = i3.set_fs
IMPORT("get_bag_description", "ItemStack")
IMPORT("S", "ES", "fmt", "msg", "slz", "dslz")
IMPORT("get_group", "play_sound", "get_detached_inv", "create_inventory")
local function get_content(content)
local t = {}
for i, v in pairs(content) do
t[i] = ItemStack(v)
end
return t
end
local function init_bags(player)
local name = player:get_player_name()
local data = i3.data[name]
local bag = create_inventory(fmt("i3_bag_%s", name), {
allow_put = function(inv, _, _, stack)
local empty = inv:is_empty"main"
local item_group = get_group(stack:get_name(), "bag")
if empty and item_group > 0 and item_group <= 4 then
return 1
end
if not empty then
msg(name, S"There is already a bag")
else
msg(name, S"This is not a bag")
end
return 0, play_sound(name, "i3_cannot", 0.8)
end,
on_put = function(_, _, _, stack)
data.bag = stack:to_string()
local meta = stack:get_meta()
local content = dslz(meta:get_string"content")
if content then
local inv = get_detached_inv("bag_content", name)
inv:set_list("main", get_content(content))
end
set_fs(player)
end,
on_take = function()
data.bag = nil
data.bag_rename = nil
local content = get_detached_inv("bag_content", name)
content:set_list("main", {})
set_fs(player)
end,
}, name)
bag:set_size("main", 1)
if data.bag then
bag:set_list("main", get_content{data.bag})
end
local function save_content(inv)
local bagstack = bag:get_stack("main", 1)
local meta = bagstack:get_meta()
local desc = get_bag_description(data, bagstack)
if inv:is_empty"main" then
meta:set_string("description", desc)
meta:set_string("content", "")
else
local list = inv:get_list"main"
local t, c = {}, 0
for i = 1, #list do
local stack = list[i]
if not stack:is_empty() then
c++
t[i] = stack:to_string()
end
end
local bag_size = get_group(bagstack:get_name(), "bag")
local percent = fmt("%d", (c * 100) / (bag_size * 4))
meta:set_string("description", ES("@1 (@2% full)", desc, percent))
meta:set_string("content", slz(t))
end
bag:set_stack("main", 1, bagstack)
data.bag = bagstack:to_string()
set_fs(player)
end
local bag_content = create_inventory(fmt("i3_bag_content_%s", name), {
on_move = save_content,
on_put = save_content,
on_take = save_content,
allow_put = function(_, _, _, stack)
local meta = stack:get_meta()
local content = dslz(meta:get_string"content")
if content then
msg(name, "You cannot put a bag in another bag")
return 0, play_sound(name, "i3_cannot", 0.8)
end
return stack:get_count()
end,
}, name)
bag_content:set_size("main", 4*4)
if data.bag then
local meta = bag:get_stack("main", 1):get_meta()
local content = dslz(meta:get_string"content")
if content then
bag_content:set_list("main", get_content(content))
end
end
end
local bag_recipes = {
small = {
rcp = {
{"", "farming:string", ""},
{"group:wool", "group:wool", "group:wool"},
{"group:wool", "group:wool", "group:wool"},
},
size = 2,
},
medium = {
rcp = {
{"farming:string", "i3:bag_small", "farming:string"},
{"farming:string", "i3:bag_small", "farming:string"},
},
size = 3,
},
large = {
rcp = {
{"farming:string", "i3:bag_medium", "farming:string"},
{"farming:string", "i3:bag_medium", "farming:string"},
},
size = 4,
},
}
for size, item in pairs(bag_recipes) do
local bagname = fmt("i3:bag_%s", size)
core.register_craftitem(bagname, {
description = fmt("%s Backpack", size:gsub("^%l", string.upper)),
inventory_image = fmt("i3_bag_%s.png", size),
groups = {bag = item.size},
stack_max = 1,
})
core.register_craft{output = bagname, recipe = item.rcp}
core.register_craft{type = "fuel", recipe = bagname, burntime = 3}
end
return init_bags

339
src/caches.lua Normal file
View File

@ -0,0 +1,339 @@
local replacements = {fuel = {}}
local http = ...
IMPORT("copy", "insert", "sort", "match", "sub")
IMPORT("true_str", "is_table", "valid_item", "table_merge", "table_replace", "table_eq")
IMPORT("fmt", "reg_items", "reg_aliases", "reg_nodes", "is_cube", "get_cube", "ItemStack")
IMPORT("is_group", "extract_groups", "item_has_groups", "groups_to_items", "get_group_stereotype")
local function get_burntime(item)
return core.get_craft_result{method = "fuel", items = {item}}.time
end
local function cache_fuel(item)
local burntime = get_burntime(item)
if burntime > 0 then
i3.fuel_cache[item] = {
type = "fuel",
items = {item},
burntime = burntime,
replacements = replacements.fuel[item],
}
end
end
local function cache_groups(group, groups)
i3.groups[group] = {}
i3.groups[group].groups = groups
i3.groups[group].items = groups_to_items(groups)
if #groups == 1 then
i3.groups[group].stereotype = get_group_stereotype(groups[1])
end
local items = i3.groups[group].items
if #items <= 1 then return end
local px, lim, c = 64, 10, 0
local sprite = "[combine:WxH"
for _, item in ipairs(items) do
local def = reg_items[item]
local tiles = def.tiles or def.tile_images
local texture = true_str(def.inventory_image) and def.inventory_image --or tiles[1]
if def.drawtype and is_cube(def.drawtype) then
texture = get_cube(tiles)
end
if texture then
texture = texture:gsub("%^", "\\^"):gsub(":", "\\:") .. fmt("\\^[resize\\:%ux%u", px, px)
sprite = sprite .. fmt(":0,%u=%s", c * px, texture)
c++
if c == lim then break end
end
end
if c > 1 then
sprite = sprite:gsub("WxH", px .. "x" .. px * c)
i3.groups[group].sprite = sprite
i3.groups[group].count = c
end
end
local function get_item_usages(item, recipe, added)
if is_group(item) then
local group = item:sub(7)
local group_cache = i3.groups[group]
local groups = group_cache and group_cache.groups or extract_groups(item)
if not group_cache then
cache_groups(group, groups)
end
for name, def in pairs(reg_items) do
if not added[name] and valid_item(def) and item_has_groups(def.groups, groups) then
local usage = copy(recipe)
table_replace(usage.items, item, name)
i3.usages_cache[name] = i3.usages_cache[name] or {}
insert(i3.usages_cache[name], 1, usage)
added[name] = true
end
end
elseif valid_item(reg_items[item]) then
i3.usages_cache[item] = i3.usages_cache[item] or {}
insert(i3.usages_cache[item], 1, recipe)
end
end
local function get_usages(recipe)
local added = {}
for _, item in pairs(recipe.items) do
item = reg_aliases[item] or item
if not added[item] then
get_item_usages(item, recipe, added)
added[item] = true
end
end
end
local function cache_usages(item)
local recipes = i3.recipes_cache[item] or {}
for i = 1, #recipes do
get_usages(recipes[i])
end
if i3.fuel_cache[item] then
i3.usages_cache[item] = table_merge(i3.usages_cache[item] or {}, {i3.fuel_cache[item]})
end
end
local function drop_table(name, drop)
local count_sure = 0
local drop_items = drop.items or {}
local max_items = drop.max_items
for i = 1, #drop_items do
local di = drop_items[i]
local valid_rarity = di.rarity and di.rarity > 1
if di.rarity or not max_items or
(max_items and not di.rarity and count_sure < max_items) then
for j = 1, #di.items do
local dstack = ItemStack(di.items[j])
local dname = dstack:get_name()
local dcount = dstack:get_count()
local empty = dstack:is_empty()
if not empty and (dname ~= name or (dname == name and dcount > 1)) then
local rarity = valid_rarity and di.rarity
i3.register_craft {
type = rarity and "digging_chance" or "digging",
items = {name},
output = fmt("%s %u", dname, dcount),
rarity = rarity,
tools = di.tools,
}
end
end
end
if not di.rarity then
count_sure++
end
end
end
local function cache_drops(name, drop)
if true_str(drop) then
local dstack = ItemStack(drop)
local dname = dstack:get_name()
local empty = dstack:is_empty()
if not empty and dname ~= name then
i3.register_craft {
type = "digging",
items = {name},
output = drop,
}
end
elseif is_table(drop) then
drop_table(name, drop)
end
end
local function cache_recipes(item)
local recipes = core.get_all_craft_recipes(item)
if replacements[item] then
local _recipes = {}
for k, v in ipairs(recipes or {}) do
_recipes[#recipes + 1 - k] = v
end
for k, v in pairs(replacements[item]) do
if _recipes[k] then
_recipes[k].replacements = v
end
end
recipes = _recipes
end
if recipes then
i3.recipes_cache[item] = table_merge(recipes, i3.recipes_cache[item] or {})
end
end
--[[ As `core.get_craft_recipe` and `core.get_all_craft_recipes` do not
return the fuel, replacements and toolrepair recipes, we have to
override `core.register_craft` and do some reverse engineering.
See engine's issues #4901, #5745 and #8920. ]]
local old_register_craft = core.register_craft
local rcp_num = {}
core.register_craft = function(def)
old_register_craft(def)
if def.type == "toolrepair" then
i3.toolrepair = def.additional_wear * -100
end
local output = def.output or (true_str(def.recipe) and def.recipe) or nil
if not output then return end
output = {match(output, "%S+")}
local groups
if is_group(output[1]) then
groups = extract_groups(output[1])
output = groups_to_items(groups)
end
for i = 1, #output do
local item = output[i]
rcp_num[item] = (rcp_num[item] or 0) + 1
if def.replacements then
if def.type == "fuel" then
replacements.fuel[item] = def.replacements
else
replacements[item] = replacements[item] or {}
replacements[item][rcp_num[item]] = def.replacements
end
end
end
end
local old_clear_craft = core.clear_craft
core.clear_craft = function(def)
old_clear_craft(def)
-- TODO: hide in crafting guide
end
local function resolve_aliases(hash)
for oldname, newname in pairs(reg_aliases) do
cache_recipes(oldname)
local recipes = i3.recipes_cache[oldname]
if recipes then
if not i3.recipes_cache[newname] then
i3.recipes_cache[newname] = {}
end
local similar
for i = 1, #i3.recipes_cache[oldname] do
local rcp_old = i3.recipes_cache[oldname][i]
for j = 1, #i3.recipes_cache[newname] do
local rcp_new = copy(i3.recipes_cache[newname][j])
rcp_new.output = oldname
if table_eq(rcp_old, rcp_new) then
similar = true
break
end
end
if not similar then
insert(i3.recipes_cache[newname], rcp_old)
end
end
end
if newname ~= "" and i3.recipes_cache[oldname] and reg_items[newname] and not hash[newname] then
insert(i3.init_items, newname)
end
end
end
local function init_recipes()
local _select, _preselect = {}, {}
for name, def in pairs(reg_items) do
if name ~= "" and valid_item(def) then
cache_drops(name, def.drop)
cache_fuel(name)
cache_recipes(name)
_preselect[name] = true
end
end
for name in pairs(_preselect) do
cache_usages(name)
insert(i3.init_items, name)
_select[name] = true
end
resolve_aliases(_select)
sort(i3.init_items)
if http and true_str(i3.export_url) then
local post_data = {
recipes = i3.recipes_cache,
usages = i3.usages_cache,
}
http.fetch_async {
url = i3.export_url,
post_data = core.write_json(post_data),
}
end
end
local function init_cubes()
for name, def in pairs(reg_nodes) do
if def then
local id = core.get_content_id(name)
local tiles = def.tiles or def.tile_images
if is_cube(def.drawtype) then
i3.cubes[id] = get_cube(tiles)
elseif sub(def.drawtype, 1, 9) == "plantlike" or sub(def.drawtype, 1, 8) == "firelike" then
local texture = true_str(def.inventory_image) and def.inventory_image or tiles[1]
if texture then
i3.plants[id] = texture
end
end
end
end
end
return function()
init_recipes()
init_cubes()
end

279
src/callbacks.lua Normal file
View File

@ -0,0 +1,279 @@
local http, storage = ...
local init_bags = i3.files.bags()
local fill_caches = i3.files.caches(http)
local init_hud = i3.files.hud()
local set_fs = i3.set_fs
IMPORT("slz", "min", "insert", "copy", "ItemStack")
IMPORT("spawn_item", "reset_data", "get_detached_inv", "play_sound", "update_inv_size")
core.register_on_player_hpchange(function(player, hpchange)
local name = player:get_player_name()
local data = i3.data[name]
if not data then return end
local hp_max = player:get_properties().hp_max
data.hp = min(hp_max, player:get_hp() + hpchange)
set_fs(player)
end)
core.register_on_dieplayer(function(player)
local name = player:get_player_name()
local data = i3.data[name]
if not data then return end
if i3.settings.drop_bag_on_die then
local bagstack = ItemStack(data.bag)
spawn_item(player, bagstack)
end
data.bag = nil
local bag = get_detached_inv("bag", name)
local content = get_detached_inv("bag_content", name)
bag:set_list("main", {})
content:set_list("main", {})
set_fs(player)
end)
core.register_on_chatcommand(function(name)
local player = core.get_player_by_name(name)
core.after(0, set_fs, player)
end)
core.register_on_priv_grant(function(name, _, priv)
if priv == "creative" or priv == "all" then
local data = i3.data[name]
reset_data(data)
data.favs = {}
local player = core.get_player_by_name(name)
core.after(0, set_fs, player)
end
end)
core.register_on_player_inventory_action(function(player, _, _, info)
local name = player:get_player_name()
if not core.is_creative_enabled(name) and
((info.from_list == "main" and info.to_list == "craft") or
(info.from_list == "craft" and info.to_list == "main") or
(info.from_list == "craftresult" and info.to_list == "main")) then
set_fs(player)
end
end)
if core.global_exists"armor" then
i3.modules.armor = true
local group_indexes = {
{"armor_head", "i3_heavy_helmet"},
{"armor_torso", "i3_heavy_armor"},
{"armor_legs", "i3_heavy_leggings"},
{"armor_feet", "i3_heavy_boots"},
{"armor_shield", "i3_heavy_shield"},
}
local function check_group(def, group)
return def.groups[group] and def.groups[group] > 0
end
armor:register_on_equip(function(player, idx, stack)
local _, armor_inv = armor:get_valid_player(player, "3d_armor")
local def = stack:get_definition()
local name = player:get_player_name()
local data = i3.data[name]
for i, v in ipairs(group_indexes) do
local group, sound = unpack(v)
local stackname = stack:get_name()
if stackname:find"wood" or stackname:find"stone" or stackname:find"cactus" then
sound = sound:gsub("heavy", "light")
end
if i == idx and check_group(def, group) then
data.armor_allow = sound
return armor:register_on_update(set_fs)
end
end
data.armor_disallow = true
armor_inv:remove_item("armor", stack)
end)
armor:register_on_update(function(player)
local _, armor_inv = armor:get_valid_player(player, "3d_armor")
if not armor_inv then return end
for i = 1, 5 do
local stack = armor_inv:get_stack("armor", i)
local def = stack:get_definition()
for j, v in ipairs(group_indexes) do
local group = v[1]
if check_group(def, group) and i ~= j then
armor_inv:set_stack("armor", i, armor_inv:get_stack("armor", j))
armor_inv:set_stack("armor", j, stack)
return play_sound(player:get_player_name(), "i3_cannot", 0.8)
end
end
end
end)
core.register_on_player_inventory_action(function(player, action, _, info)
if action ~= "take" then return end
local name = player:get_player_name()
local data = i3.data[name]
if data.armor_disallow then
local inv = player:get_inventory()
inv:set_stack("main", info.index, info.stack)
data.armor_disallow = nil
play_sound(name, "i3_cannot", 0.8)
elseif data.armor_allow then
play_sound(name, data.armor_allow, 0.8)
data.armor_allow = nil
end
end)
end
if core.global_exists"skins" then
i3.modules.skins = true
end
if core.global_exists"awards" then
i3.modules.awards = true
core.register_on_craft(function(_, player)
set_fs(player)
end)
core.register_on_dignode(function(_, _, player)
set_fs(player)
end)
core.register_on_placenode(function(_, _, player)
set_fs(player)
end)
core.register_on_chat_message(function(name)
local player = core.get_player_by_name(name)
set_fs(player)
end)
end
local function disable_inventories()
if rawget(_G, "sfinv") then
function sfinv.set_player_inventory_formspec() return end
sfinv.enabled = false
end
if rawget(_G, "unified_inventory") then
function unified_inventory.set_inventory_formspec() return end
end
end
core.register_on_mods_loaded(function()
fill_caches()
disable_inventories()
end)
local function get_lang_code(info)
return info and info.lang_code
end
local function get_formspec_version(info)
return info and info.formspec_version or 1
end
local function outdated(name)
core.show_formspec(name, "i3_outdated",
("size[6.5,1.3]image[0,0;1,1;i3_book.png]label[1,0;%s]button_exit[2.6,0.8;1,1;;OK]"):format(
"Your Minetest client is outdated.\nGet the latest version on minetest.net to play the game."))
end
local function init_data(player, info)
local name = player:get_player_name()
i3.data[name] = i3.data[name] or {}
local data = i3.data[name]
for k, v in pairs(i3.default_data) do
local val = data[k]
if val == nil then
val = v
end
data[k] = val
end
data.player_name = name
data.filter = ""
data.pagenum = 1
data.skin_pagenum = 1
data.items = i3.init_items
data.items_raw = i3.init_items
data.favs = {}
data.show_setting = "home"
data.crafting_counts = {}
data.tab = 1
data.itab = 1
data.subcat = 1
data.scrbar_inv = 0
data.lang_code = get_lang_code(info)
data.fs_version = info.formspec_version
update_inv_size(player, data)
core.after(0, set_fs, player)
end
local function save_data(player_name)
local _data = copy(i3.data)
for name, v in pairs(_data) do
for dat in pairs(v) do
if not i3.saves[dat] then
_data[name][dat] = nil
if player_name and i3.data[player_name] then
i3.data[player_name][dat] = nil -- To free up some memory
end
end
end
end
storage:set_string("data", slz(_data))
end
insert(core.registered_on_joinplayers, 1, function(player)
local name = player:get_player_name()
local info = core.get_player_information and core.get_player_information(name)
if not info or get_formspec_version(info) < i3.settings.min_fs_version then
return outdated(name)
end
init_data(player, info)
init_bags(player)
init_hud(player)
end)
core.register_on_leaveplayer(function(player)
local name = player:get_player_name()
save_data(name)
end)
core.register_on_shutdown(save_data)
local function routine()
save_data()
core.after(i3.settings.save_interval, routine)
end
core.after(i3.settings.save_interval, routine)

841
src/common.lua Normal file
View File

@ -0,0 +1,841 @@
local vec = vector.new
local ItemStack = ItemStack
local loadstring = loadstring
local reg_items = core.registered_items
local translate = core.get_translated_string
local sort, concat, insert = table.sort, table.concat, table.insert
local min, floor, ceil = math.min, math.floor, math.ceil
local fmt, find, match, gmatch, sub, split, lower, upper =
string.format, string.find, string.match, string.gmatch,
string.sub, string.split, string.lower, string.upper
if not core.registered_privileges.creative then
core.register_privilege("creative", {
description = "Allow player to use creative inventory",
give_to_singleplayer = false,
give_to_admin = false,
})
end
local old_is_creative_enabled = core.is_creative_enabled
function core.is_creative_enabled(name)
if name == "" then
return old_is_creative_enabled(name)
end
return core.check_player_privs(name, {creative = true}) or old_is_creative_enabled(name)
end
local S = core.get_translator"i3"
local ES = function(...) return core.formspec_escape(S(...)) end
local function is_num(x)
return type(x) == "number"
end
local function is_str(x)
return type(x) == "string"
end
local function is_table(x)
return type(x) == "table"
end
local function is_func(x)
return type(x) == "function"
end
local function true_str(str)
return is_str(str) and str ~= ""
end
local function true_table(x)
return is_table(x) and next(x)
end
local function reset_compression(data)
data.alt_items = nil
data.expand = ""
end
local function msg(name, str)
local prefix = "[i3]"
return core.chat_send_player(name, fmt("%s %s", core.colorize("#ff0", prefix), str))
end
local function err(str)
return core.log("error", str)
end
local function round(num, decimal)
local mul = 10 ^ decimal
return floor(num * mul + 0.5) / mul
end
local function toupper(str)
return str:gsub("%f[%w]%l", upper):gsub("_", " ")
end
local function utf8_len(str)
local c = 0
for _ in str:gmatch"[%z\1-\127\194-\244][\128-\191]*" do -- Arguably working duct-tape code
c++
end
return c
end
local function get_bag_description(data, stack)
local desc = translate(data.lang_code, stack:get_description())
desc = split(desc, "(")[1] or desc
desc = toupper(desc:trim())
return desc
end
local function search(data)
reset_compression(data)
local filter = data.filter
local opt = "^(.-)%+([%w_]+)=([%w_,]+)"
local search_filter = next(i3.search_filters) and match(filter, opt)
local filters = {}
if search_filter then
search_filter = search_filter:trim()
for filter_name, values in gmatch(filter, sub(opt, 6)) do
if i3.search_filters[filter_name] then
values = split(values, ",")
filters[filter_name] = values
end
end
end
local filtered_list, c = {}, 0
for i = 1, #data.items_raw do
local item = data.items_raw[i]
local def = reg_items[item]
local desc = lower(translate(data.lang_code, def.description)) or ""
local search_in = fmt("%s %s", item, desc)
local temp, j, to_add = {}, 1
if search_filter then
for filter_name, values in pairs(filters) do
if values then
local func = i3.search_filters[filter_name]
to_add = (j > 1 and temp[item] or j == 1) and
func(item, values) and (search_filter == "" or
find(search_in, search_filter, 1, true))
if to_add then
temp[item] = true
end
j++
end
end
else
local ok = true
for keyword in gmatch(filter, "%S+") do
if not find(search_in, keyword, 1, true) then
ok = nil
break
end
end
if ok then
to_add = true
end
end
if to_add then
c++
filtered_list[c] = item
end
end
data.items = filtered_list
end
local function table_replace(t, val, new)
for k, v in pairs(t) do
if v == val then
t[k] = new
end
end
end
local function table_merge(t1, t2, hash)
t1 = t1 or {}
t2 = t2 or {}
if hash then
for k, v in pairs(t2) do
t1[k] = v
end
else
local c = #t1
for i = 1, #t2 do
c++
t1[c] = t2[i]
end
end
return t1
end
local function array_diff(t1, t2)
local hash = {}
for i = 1, #t1 do
local v = t1[i]
hash[v] = true
end
for i = 1, #t2 do
local v = t2[i]
hash[v] = nil
end
local diff, c = {}, 0
for i = 1, #t1 do
local v = t1[i]
if hash[v] then
c++
diff[c] = v
end
end
return diff
end
local function table_eq(t1, t2)
local ty1, ty2 = type(t1), type(t2)
if ty1 ~= ty2 then return end
if ty1 ~= "table" and ty2 ~= "table" then
return t1 == t2
end
for k, v in pairs(t1) do
local v2 = t2[k]
if v2 == nil or not table_eq(v, v2) then return end
end
for k, v in pairs(t2) do
local v1 = t1[k]
if v1 == nil or not table_eq(v1, v) then return end
end
return true
end
local function clean_name(item)
if sub(item, 1, 1) == ":" or sub(item, 1, 1) == " " or sub(item, 1, 1) == "_" then
item = sub(item, 2)
end
return item
end
local function is_group(item)
return sub(item, 1, 6) == "group:"
end
local function extract_groups(str)
return split(sub(str, 7), ",")
end
local function item_has_groups(item_groups, groups)
for i = 1, #groups do
local group = groups[i]
if (item_groups[group] or 0) == 0 then return end
end
return true
end
local function valid_item(def)
return def and def.groups.not_in_creative_inventory ~= 1 and
def.description and def.description ~= ""
end
local function get_group_stereotype(group)
local stereotype = i3.group_stereotypes[group]
local def = reg_items[stereotype]
if valid_item(def) then
return stereotype
end
end
local function groups_to_items(groups)
local names = {}
for name, def in pairs(reg_items) do
if valid_item(def) and item_has_groups(def.groups, groups) then
insert(names, name)
end
end
sort(names)
return names
end
local function is_cube(drawtype)
return drawtype == "normal" or drawtype == "liquid" or
sub(drawtype, 1, 9) == "glasslike" or
sub(drawtype, 1, 8) == "allfaces"
end
local function get_cube(tiles)
if not true_table(tiles) then
return "i3_blank.png"
end
local top = tiles[1] or "i3_blank.png"
if is_table(top) then
top = top.name or top.image
end
local left = tiles[3] or top or "i3_blank.png"
if is_table(left) then
left = left.name or left.image
end
local right = tiles[5] or left or "i3_blank.png"
if is_table(right) then
right = right.name or right.image
end
return core.inventorycube(top, left, right)
end
local function apply_recipe_filters(recipes, player)
for _, filter in pairs(i3.recipe_filters) do
recipes = filter(recipes, player)
end
return recipes
end
local function recipe_filter_set()
return next(i3.recipe_filters)
end
local function compression_active(data)
return data.collapse and not recipe_filter_set() and data.filter == ""
end
local function compressible(item, data)
return compression_active(data) and i3.compress_groups[item]
end
local function is_fav(data)
for i = 1, #data.favs do
if data.favs[i] == data.query_item then
return i
end
end
end
local function sort_by_category(data)
reset_compression(data)
local items = data.items_raw
if data.filter ~= "" then
search(data)
items = data.items
end
local new = {}
for i = 1, #items do
local item = items[i]
local tab = i3.minitabs[data.itab]
local to_add = tab.sorter(item, data)
if to_add then
insert(new, item)
end
end
data.items = new
end
local function spawn_item(player, stack)
local dir = player:get_look_dir()
local ppos = player:get_pos()
ppos.y = ppos.y + player:get_properties().eye_height
local look_at = ppos + dir
core.add_item(look_at, stack)
end
local function get_recipes(player, item)
item = core.registered_aliases[item] or item
local recipes = i3.recipes_cache[item]
local usages = i3.usages_cache[item]
if recipes then
recipes = apply_recipe_filters(recipes, player)
end
local no_recipes = not recipes or #recipes == 0
if no_recipes and not usages then return end
usages = apply_recipe_filters(usages, player)
local no_usages = not usages or #usages == 0
return not no_recipes and recipes or nil,
not no_usages and usages or nil
end
local function get_stack(player, stack)
local inv = player:get_inventory()
if inv:room_for_item("main", stack) then
inv:add_item("main", stack)
else
spawn_item(player, stack)
end
end
local function get_group_items(name)
local groups = extract_groups(name)
return i3.groups[name:sub(7)].items or groups_to_items(groups)
end
local function craft_stack(player, data, craft_rcp)
local inv = player:get_inventory()
local rcp_usg = craft_rcp and "recipe" or "usage"
local rcp_def = rcp_usg == "recipe" and data.recipes[data.rnum] or data.usages[data.unum]
local output = craft_rcp and data.recipes[data.rnum].output or data.usages[data.unum].output
output = ItemStack(output)
local stackname, stackcount, stackmax = output:get_name(), output:get_count(), output:get_stack_max()
local scrbar_val = data[fmt("scrbar_%s", craft_rcp and "rcp" or "usg")] or 1
for name, count in pairs(data.crafting_counts[rcp_usg].rcp) do
local items = {[name] = count}
if is_group(name) then
items = {}
local item_groups = get_group_items(name)
local remaining = count
for _, item in ipairs(item_groups) do
for _name, _count in pairs(data.crafting_counts[rcp_usg].inv) do
if item == _name and remaining > 0 then
local c = min(remaining, _count)
items[item] = c
remaining -= c
end
if remaining == 0 then break end
end
end
end
for item, v in pairs(items) do
for _ = 1, v * scrbar_val do
inv:remove_item("main", item)
for _, pair in ipairs(rcp_def.replacements or {}) do
local old_name, new_name = unpack(pair)
if is_group(old_name) then
local item_groups = get_group_items(old_name)
for _, it in ipairs(item_groups) do
if item == it then
get_stack(player, ItemStack(new_name))
end
end
elseif item == old_name then
get_stack(player, ItemStack(new_name))
end
end
end
end
end
local count = stackcount * scrbar_val
local iter = ceil(count / stackmax)
local leftover = count
for _ = 1, iter do
local c = min(stackmax, leftover)
local stack = ItemStack(fmt("%s %s", stackname, c))
get_stack(player, stack)
leftover -= stackmax
end
end
local function play_sound(name, sound, volume)
core.sound_play(sound, {to_player = name, gain = volume}, true)
end
local function safe_teleport(player, pos)
local name = player:get_player_name()
play_sound(name, "i3_teleport", 0.8)
local vel = player:get_velocity()
player:add_velocity(-vel)
local p = vec(pos)
p.y += 0.25
player:set_pos(p)
end
local function sorter(inv, data, mode)
sort(inv, function(a, b)
if mode == 1 then
a = translate(data.lang_code, a:get_short_description())
b = translate(data.lang_code, b:get_short_description())
else
a, b = a:get_count(), b:get_count()
end
if data.reverse_sorting then
return a > b
end
return a < b
end)
end
local function pre_sorting(list, start_i)
local new_inv, special = {}, {}
for i = start_i, #list do
local stack = list[i]
local empty = stack:is_empty()
local meta = stack:get_meta():to_table()
local wear = stack:get_wear() > 0
if not empty then
if next(meta.fields) or wear then
insert(special, stack)
else
insert(new_inv, stack)
end
end
end
new_inv = table_merge(new_inv, special)
return new_inv
end
local function compress_items(list, start_i)
local hash, new_inv, special = {}, {}, {}
for i = start_i, #list do
local stack = list[i]
local name = stack:get_name()
local count = stack:get_count()
local stackmax = stack:get_stack_max()
local empty = stack:is_empty()
local meta = stack:get_meta():to_table()
local wear = stack:get_wear() > 0
if not empty then
if next(meta.fields) or wear or count >= stackmax then
insert(special, stack)
else
hash[name] = hash[name] or 0
hash[name] += count
end
end
end
for name, count in pairs(hash) do
local stackmax = ItemStack(name):get_stack_max()
local iter = ceil(count / stackmax)
local leftover = count
for _ = 1, iter do
insert(new_inv, ItemStack(fmt("%s %u", name, min(stackmax, leftover))))
leftover -= stackmax
end
end
new_inv = table_merge(new_inv, special)
return new_inv
end
local function sort_inventory(player, data)
local inv = player:get_inventory()
local list = inv:get_list"main"
local size = inv:get_size"main"
local start_i = data.ignore_hotbar and (data.hotbar_len + 1) or 1
list = data.inv_compress and compress_items(list, start_i) or pre_sorting(list, start_i)
local new_inv = i3.sorting_methods[data.sort].func(list, data)
if not new_inv then return end
if not data.ignore_hotbar then
inv:set_list("main", new_inv)
return
end
for i = start_i, size do
local index = i - start_i + 1
inv:set_stack("main", i, new_inv[index] or "")
end
end
local function reset_data(data)
data.filter = ""
data.expand = ""
data.pagenum = 1
data.rnum = 1
data.unum = 1
data.scrbar_rcp = 1
data.scrbar_usg = 1
data.query_item = nil
data.enable_search = nil
data.goto_page = nil
data.recipes = nil
data.usages = nil
data.crafting_rcp = nil
data.crafting_usg = nil
data.alt_items = nil
data.confirm_trash = nil
data.show_settings = nil
data.show_setting = "home"
data.items = data.items_raw
if data.itab > 1 then
sort_by_category(data)
end
end
local function add_hud_waypoint(player, name, pos, color, image)
return player:hud_add {
hud_elem_type = image and "image_waypoint" or "waypoint",
name = name,
text = image or "m",
scale = {x = 5, y = 5},
world_pos = pos,
number = color,
image = image,
z_index = -300,
}
end
local function init_hud_notif(player)
return {
bg = player:hud_add {
hud_elem_type = "image",
position = {x = 0, y = 1},
offset = {x = 10, y = 0},
alignment = {x = 1, y = 1},
scale = {x = 0.6, y = 0.6},
text = "i3_bg_notif.png",
z_index = 0xDEAD,
},
img = player:hud_add {
hud_elem_type = "image",
position = {x = 0, y = 1},
offset = {x = 20, y = 20},
alignment = {x = 1, y = 1},
scale = {x = 1, y = 1},
text = "",
z_index = 0xDEAD,
},
text = player:hud_add {
hud_elem_type = "text",
position = {x = 0, y = 1},
offset = {x = 100, y = 40},
alignment = {x = 1, y = 1},
number = 0xffffff,
text = "",
z_index = 0xDEAD,
style = 1,
}
}
end
local function get_detached_inv(name, player_name)
return core.get_inventory {
type = "detached",
name = fmt("i3_%s_%s", name, player_name)
}
end
local function update_inv_size(player, data)
data.hotbar_len = data.legacy_inventory and 8 or 9
data.inv_size = 4 * data.hotbar_len
local inv = player:get_inventory()
inv:set_size("main", data.inv_size)
player:hud_set_hotbar_itemcount(data.hotbar_len)
core.after(0, function()
player:hud_set_hotbar_image(data.legacy_inventory and "gui_hotbar.png" or "i3_hotbar.png")
end)
end
-- Much faster implementation of `unpack`
local function createunpack(n)
local ret = {"local t = ... return "}
for k = 2, n do
ret[2 + (k - 2) * 4] = "t["
ret[3 + (k - 2) * 4] = k - 1
ret[4 + (k - 2) * 4] = "]"
if k ~= n then
ret[5 + (k - 2) * 4] = ","
end
end
return loadstring(concat(ret))
end
local newunpack = createunpack(33)
-------------------------------------------------------------------------------
local _ = {
-- Groups
is_group = is_group,
extract_groups = extract_groups,
item_has_groups = item_has_groups,
groups_to_items = groups_to_items,
get_group_stereotype = get_group_stereotype,
-- Compression
compressible = compressible,
compression_active = compression_active,
-- Sorting
search = search,
sorter = sorter,
get_recipes = get_recipes,
sort_inventory = sort_inventory,
sort_by_category = sort_by_category,
recipe_filter_set = recipe_filter_set,
apply_recipe_filters = apply_recipe_filters,
-- Type checks
is_fav = is_fav,
is_str = is_str,
is_num = is_num,
is_func = is_func,
true_str = true_str,
true_table = true_table,
-- Console
err = err,
msg = msg,
-- Misc. functions
is_cube = is_cube,
get_cube = get_cube,
ItemStack = ItemStack,
valid_item = valid_item,
spawn_item = spawn_item,
clean_name = clean_name,
play_sound = play_sound,
reset_data = reset_data,
safe_teleport = safe_teleport,
init_hud_notif = init_hud_notif,
add_hud_waypoint = add_hud_waypoint,
-- Core functions
clr = core.colorize,
slz = core.serialize,
dslz = core.deserialize,
ESC = core.formspec_escape,
draw_cube = core.inventorycube,
get_group = core.get_item_group,
pos_to_str = core.pos_to_string,
str_to_pos = core.string_to_pos,
check_privs = core.check_player_privs,
get_player_by_name = core.get_player_by_name,
get_connected_players = core.get_connected_players,
-- Inventory
get_stack = get_stack,
craft_stack = craft_stack,
update_inv_size = update_inv_size,
get_detached_inv = get_detached_inv,
get_bag_description = get_bag_description,
create_inventory = core.create_detached_inventory,
-- Registered items
reg_items = core.registered_items,
reg_nodes = core.registered_nodes,
reg_tools = core.registered_tools,
reg_aliases = core.registered_aliases,
reg_entities = core.registered_entities,
reg_craftitems = core.registered_craftitems,
-- i18n
S = S,
ES = ES,
translate = core.get_translated_string,
-- String
sub = string.sub,
find = string.find,
fmt = string.format,
upper = string.upper,
lower = string.lower,
split = string.split,
match = string.match,
gmatch = string.gmatch,
toupper = toupper,
utf8_len = utf8_len,
-- Table
maxn = table.maxn,
sort = table.sort,
copy = table.copy,
concat = table.concat,
insert = table.insert,
remove = table.remove,
indexof = table.indexof,
unpack = newunpack,
is_table = is_table,
table_merge = table_merge,
table_replace = table_replace,
table_eq = table_eq,
array_diff = array_diff,
-- Math
round = round,
abs = math.abs,
min = math.min,
max = math.max,
ceil = math.ceil,
floor = math.floor,
random = math.random,
-- Vectors
vec = vector.new,
vec_round = vector.round,
}
function i3.get(...)
local t = {}
for i, var in ipairs{...} do
t[i] = _[var]
end
return newunpack(t)
end

View File

@ -1,4 +1,4 @@
local fmt, insert = string.format, table.insert
IMPORT("fmt", "copy", "insert")
local wood_types = {
"acacia_wood", "aspen_wood", "junglewood", "pine_wood",
@ -44,7 +44,7 @@ local to_compress = {
["default:mese_post_light"] = {
replace = "mese_post_light",
by = {
"mese_post_light_acacia",
"mese_post_light_acacia_wood",
"mese_post_light_aspen_wood",
"mese_post_light_junglewood",
"mese_post_light_pine_wood",
@ -262,7 +262,7 @@ local moreblocks_nodes = {
"ice",
}
local colors_moreblocks = table.copy(colors)
local colors_moreblocks = copy(colors)
insert(colors_moreblocks, "white")
local moreblocks_mods = {
@ -277,13 +277,7 @@ for _, nodename in ipairs(v) do
t[nodename] = {}
for _, shape in ipairs(circular_saw_names) do
local to_add = true
if shape[1] == "slope" and shape[2] == "" then
to_add = nil
end
if to_add then
if shape[1] ~= "slope" or shape[2] ~= "" then
insert(t[nodename], fmt("%s_%s%s", shape[1], nodename, shape[2]))
end
end
@ -292,7 +286,7 @@ for _, nodename in ipairs(v) do
to_compress[fmt("%s:%s", mod, slope_name)] = {
replace = slope_name,
by = t[nodename]
by = t[nodename],
}
end
end
@ -316,4 +310,4 @@ for _, v2 in ipairs(v) do
end
end
return compressed, _compressed
i3.compress_groups, i3.compressed = compressed, _compressed

24
src/detached_inv.lua Normal file
View File

@ -0,0 +1,24 @@
local set_fs = i3.set_fs
IMPORT("play_sound", "create_inventory")
local trash = create_inventory("i3_trash", {
allow_put = function(_, _, _, stack)
return stack:get_count()
end,
on_put = function(inv, listname, _, _, player)
inv:set_list(listname, {})
local name = player:get_player_name()
local data = i3.data[name]
data.armor_allow = nil
play_sound(name, "i3_trash", 1.0)
if not core.is_creative_enabled(name) then
set_fs(player)
end
end,
})
trash:set_size("main", 1)

476
src/fields.lua Normal file
View File

@ -0,0 +1,476 @@
local set_fs = i3.set_fs
IMPORT("min", "max", "vec_round")
IMPORT("reg_items", "reg_aliases")
IMPORT("sort", "copy", "insert", "remove", "indexof")
IMPORT("S", "random", "translate", "compressible", "ItemStack")
IMPORT("fmt", "find", "match", "sub", "lower", "split", "toupper")
IMPORT("valid_item", "get_stack", "craft_stack", "clean_name", "check_privs", "safe_teleport")
IMPORT("msg", "is_fav", "pos_to_str", "str_to_pos", "add_hud_waypoint", "play_sound", "reset_data")
IMPORT("search", "sort_inventory", "sort_by_category", "get_recipes", "get_detached_inv", "update_inv_size")
local function inv_fields(player, data, fields)
local name = data.player_name
local inv = player:get_inventory()
if fields.dd_sorting_method then
data.sort = tonumber(fields.dd_sorting_method)
elseif fields.sb_font_size then
data.font_size = tonumber(fields.sb_font_size:match"-?%d+$")
end
for field in pairs(fields) do
if sub(field, 1, 4) == "btn_" then
data.subcat = indexof(i3.categories, sub(field, 5))
break
elseif sub(field, 1, 3) == "cb_" then
local str = sub(field, 4)
data[str] = false
if fields[field] == "true" then
data[str] = true
end
if str == "legacy_inventory" then
update_inv_size(player, data)
elseif str == "collapse" then
search(data)
end
elseif sub(field, 1, 8) == "setting_" then
data.show_setting = match(field, "_(%w+)$")
elseif sub(field, 1, 9) == "skin_btn_" then
local id = tonumber(field:match("%d+"))
local _skins = skins.get_skinlist_for_player(name)
play_sound(name, "i3_skin_change", 0.6)
skins.set_player_skin(player, _skins[id])
elseif find(field, "waypoint_%d+") then
local id, action = match(field, "_(%d+)_(%w+)$")
id = tonumber(id)
local waypoint = data.waypoints[id]
if not waypoint then return end
if action == "see" then
if data.waypoint_see and data.waypoint_see == id then
data.waypoint_see = nil
else
data.waypoint_see = id
end
elseif action == "delete" then
player:hud_remove(waypoint.id)
remove(data.waypoints, id)
elseif action == "teleport" then
local pos = str_to_pos(waypoint.pos)
safe_teleport(player, pos)
msg(name, S("Teleported to: @1", waypoint.name))
elseif action == "refresh" then
local color = random(0xffffff)
waypoint.color = color
player:hud_change(waypoint.id, "number", color)
elseif action == "hide" then
if waypoint.hide then
local new_id = add_hud_waypoint(
player, waypoint.name, str_to_pos(waypoint.pos), waypoint.color)
waypoint.id = new_id
waypoint.hide = nil
else
player:hud_remove(waypoint.id)
waypoint.hide = true
end
end
break
end
end
if fields.quit then
data.confirm_trash = nil
data.show_settings = nil
data.waypoint_see = nil
data.bag_rename = nil
data.goto_page = nil
if data.filter == "" then
data.enable_search = nil
end
elseif fields.trash then
data.show_settings = nil
data.confirm_trash = true
elseif fields.settings then
if not data.show_settings then
data.confirm_trash = nil
data.show_settings = true
else
data.show_settings = nil
end
elseif fields.confirm_trash_yes or fields.confirm_trash_no then
if fields.confirm_trash_yes then
inv:set_list("main", {})
inv:set_list("craft", {})
end
data.confirm_trash = nil
elseif fields.close_settings then
data.show_settings = nil
elseif fields.close_preview then
data.waypoint_see = nil
elseif fields.sort then
sort_inventory(player, data)
elseif fields.home then
if not data.home then
return msg(name, "No home set")
elseif not check_privs(name, {home = true}) then
return msg(name, "'home' privilege missing")
end
safe_teleport(player, str_to_pos(data.home))
msg(name, S"Welcome back home!")
elseif fields.set_home then
data.home = pos_to_str(player:get_pos(), 1)
elseif fields.bag_rename then
data.bag_rename = true
elseif fields.confirm_rename then
local bag = get_detached_inv("bag", name)
local bagstack = bag:get_stack("main", 1)
local meta = bagstack:get_meta()
local desc = translate(data.lang_code, bagstack:get_description())
local fill = split(desc, "(")[2]
local newname = fields.bag_newname:gsub("([%(%)])", "")
newname = toupper(newname:trim())
if fill then
newname = fmt("%s (%s", newname, fill)
end
meta:set_string("description", newname)
bag:set_stack("main", 1, bagstack)
data.bag = bagstack:to_string()
data.bag_rename = nil
elseif fields.waypoint_add then
local max_waypoints = i3.settings.max_waypoints
if #data.waypoints >= max_waypoints then
play_sound(name, "i3_cannot", 0.8)
return msg(name, fmt("Waypoints limit reached (%u)", max_waypoints))
end
local pos = player:get_pos()
for _, v in ipairs(data.waypoints) do
if vec_round(pos) == vec_round(str_to_pos(v.pos)) then
play_sound(name, "i3_cannot", 0.8)
return msg(name, S"You already have set a waypoint at this position")
end
end
local waypoint = fields.waypoint_name
if fields.waypoint_name == "" then
waypoint = "Waypoint"
end
local color = random(0xffffff)
local id = add_hud_waypoint(player, waypoint, pos, color)
insert(data.waypoints, {
name = waypoint,
pos = pos_to_str(pos, 1),
color = color,
id = id,
})
data.scrbar_inv += 1000
elseif fields.hide_debug_grid then
data.hide_debug_grid = not data.hide_debug_grid
end
end
local function select_item(player, data, fields)
local item
for field in pairs(fields) do
if find(field, ":") then
item = field
break
end
end
if not item then return end
if compressible(item, data) then
local idx
for i = 1, #data.items do
local it = data.items[i]
if it == item then
idx = i
break
end
end
if data.expand ~= "" then
data.alt_items = nil
if item == data.expand then
data.expand = nil
return
end
end
if idx and item ~= data.expand then
data.alt_items = copy(data.items)
data.expand = item
if i3.compress_groups[item] then
local items = copy(i3.compress_groups[item])
insert(items, fmt("_%s", item))
sort(items, function(a, b)
if a:sub(1, 1) == "_" then
a = a:sub(2)
end
return a < b
end)
local i = 1
for _, v in ipairs(items) do
if valid_item(reg_items[clean_name(v)]) then
insert(data.alt_items, idx + i, v)
i++
end
end
end
end
else
if sub(item, 1, 1) == "_" then
item = sub(item, 2)
elseif sub(item, 1, 6) == "group!" then
item = match(item, "([%w:_]+)$")
end
item = reg_aliases[item] or item
if not reg_items[item] then return end
if core.is_creative_enabled(data.player_name) then
local stack = ItemStack(item)
local stackmax = stack:get_stack_max()
stack = fmt("%s %s", item, stackmax)
return get_stack(player, stack)
end
if item == data.query_item then return end
local recipes, usages = get_recipes(player, item)
data.query_item = item
data.recipes = recipes
data.usages = usages
data.rnum = 1
data.unum = 1
data.scrbar_rcp = 1
data.scrbar_usg = 1
data.crafting_rcp = nil
data.crafting_usg = nil
end
end
local function rcp_fields(player, data, fields)
local sb_rcp, sb_usg = fields.scrbar_rcp, fields.scrbar_usg
if not data.hide_tabs and fields.filter and fields.filter == "" then
data.enable_search = nil
end
if fields.cancel then
reset_data(data)
elseif fields.exit then
data.query_item = nil
elseif fields.enable_search then
if data.hide_tabs then
data.enable_search = not data.enable_search
else
data.enable_search = true
end
elseif fields.filter and (fields.key_enter_field == "filter" or fields.search) then
if fields.filter == "" then
reset_data(data)
return set_fs(player)
end
local str = lower(fields.filter)
if data.filter == str then return end
data.filter = str
data.pagenum = 1
search(data)
if data.itab > 1 then
sort_by_category(data)
end
elseif fields.pagenum then
data.goto_page = not data.goto_page
elseif fields.goto_page then
local pagenum = tonumber(fields.goto_page)
data.pagenum = max(1, min(data.pagemax, pagenum or data.pagenum))
data.goto_page = nil
elseif fields.prev_page or fields.next_page then
if data.pagemax == 1 then return end
data.pagenum -= (fields.prev_page and 1 or -1)
if data.pagenum > data.pagemax then
data.pagenum = 1
elseif data.pagenum == 0 then
data.pagenum = data.pagemax
end
elseif fields.prev_skin or fields.next_skin then
if data.skin_pagemax == 1 then return end
data.skin_pagenum -= (fields.prev_skin and 1 or -1)
if data.skin_pagenum > data.skin_pagemax then
data.skin_pagenum = 1
elseif data.skin_pagenum == 0 then
data.skin_pagenum = data.skin_pagemax
end
elseif fields.prev_recipe or fields.next_recipe then
local num = data.rnum + (fields.prev_recipe and -1 or 1)
data.rnum = data.recipes[num] and num or (fields.prev_recipe and #data.recipes or 1)
data.crafting_rcp = nil
data.scrbar_rcp = 1
elseif fields.prev_usage or fields.next_usage then
local num = data.unum + (fields.prev_usage and -1 or 1)
data.unum = data.usages[num] and num or (fields.prev_usage and #data.usages or 1)
data.crafting_usg = nil
data.scrbar_usg = 1
elseif fields.fav then
local fav = is_fav(data)
if #data.favs < i3.settings.max_favs and not fav then
insert(data.favs, data.query_item)
elseif fav then
remove(data.favs, fav)
end
elseif fields.crafting_rcp or fields.crafting_usg then
if fields.crafting_rcp then
data.crafting_rcp = not data.crafting_rcp
if not data.crafting_rcp then
data.scrbar_rcp = 1
end
else
data.crafting_usg = not data.crafting_usg
if not data.crafting_usg then
data.scrbar_usg = 1
end
end
elseif (sb_rcp and sub(sb_rcp, 1, 3) == "CHG") or (sb_usg and sub(sb_usg, 1, 3) == "CHG") then
data.scrbar_rcp = sb_rcp and tonumber(match(sb_rcp, "%d+"))
data.scrbar_usg = sb_usg and tonumber(match(sb_usg, "%d+"))
elseif fields.craft_rcp or fields.craft_usg then
craft_stack(player, data, fields.craft_rcp)
if fields.craft_rcp then
data.crafting_rcp = nil
data.scrbar_rcp = 1
else
data.crafting_usg = nil
data.scrbar_usg = 1
end
else
select_item(player, data, fields)
end
end
core.register_on_player_receive_fields(function(player, formname, fields)
local name = player:get_player_name()
if formname == "i3_outdated" then
return false, core.kick_player(name, S"Your Minetest client needs updating (www.minetest.net)")
elseif formname ~= "" then
return false
end
-- No-op buttons
if fields.player_name or fields.awards or fields.home_pos or fields.no_item or
fields.no_rcp or fields.select_sorting or fields.sort_method or fields.bg_content or
fields.quick_crafting then
return false
end
-- print(dump(fields))
local data = i3.data[name]
if not data then return end
local sb_inv = fields.scrbar_inv
if sb_inv and sub(sb_inv, 1, 3) == "CHG" then
data.scrbar_inv = tonumber(match(sb_inv, "%d+"))
return
end
for f in pairs(fields) do
if sub(f, 1, 4) == "tab_" then
local tabname = sub(f, 5)
i3.set_tab(player, tabname)
break
elseif sub(f, 1, 5) == "itab_" then
data.pagenum = 1
data.itab = tonumber(f:sub(-1))
sort_by_category(data)
break
end
end
rcp_fields(player, data, fields)
local tab = i3.tabs[data.tab]
if tab then
if tab.slots then
inv_fields(player, data, fields)
end
if tab.fields then
local ret = tab.fields(player, data, fields)
if ret == false then return end
end
end
return true, set_fs(player)
end)

View File

@ -1,6 +1,6 @@
local S = core.get_translator "i3"
IMPORT("S")
local group_stereotypes = {
i3.group_stereotypes = {
dye = "dye:white",
wool = "wool:white",
wood = "default:wood",
@ -11,13 +11,14 @@ local group_stereotypes = {
stone = "default:stone",
leaves = "default:leaves",
coal = "default:coal_lump",
fence = "default:fence_wood",
vessel = "vessels:glass_bottle",
flower = "flowers:dandelion_yellow",
water_bucket = "bucket:bucket_water",
mesecon_conductor_craftable = "mesecons:wire_00000000_off",
}
local group_names = {
i3.group_names = {
dye = S"Any dye",
coal = S"Any coal",
sand = S"Any sand",
@ -26,6 +27,7 @@ local group_names = {
glass = S"Any glass",
stick = S"Any stick",
stone = S"Any stone",
fence = S"Any fence",
carpet = S"Any carpet",
flower = S"Any flower",
leaves = S"Any leaves",
@ -58,5 +60,3 @@ local group_names = {
["color_dark_grey,dye"] = S"Any dark grey dye",
["color_dark_green,dye"] = S"Any dark green dye",
}
return group_stereotypes, group_names

1810
src/gui.lua Normal file

File diff suppressed because it is too large Load Diff

147
src/hud.lua Normal file
View File

@ -0,0 +1,147 @@
IMPORT("max", "ceil", "remove", "str_to_pos")
IMPORT("get_connected_players", "add_hud_waypoint")
local function init_hud(player)
local name = player:get_player_name()
local data = i3.data[name]
local wdesc_y = -90
if core.global_exists"hb" then
wdesc_y -= ceil(hb.hudbars_count / 2) * 5
elseif not i3.settings.damage_enabled then
wdesc_y += 15
end
data.hud = {
notifs = {},
wielditem = player:hud_add {
hud_elem_type = "text",
position = {x = 0.5, y = 1},
offset = {x = 0, y = wdesc_y},
alignment = {x = 0, y = -1},
number = 0xffffff,
text = "",
z_index = 0xDEAD,
style = 1,
}
}
end
local function get_progress(offset, max_val)
local progress = offset * (1 / (max_val - 5))
return 1 - (progress ^ 4)
end
local function show_hud(player, data, notif, idx, dt)
local hud_info_bg = player:hud_get(notif.elems.bg)
local offset = hud_info_bg.offset
if offset.y < notif.max.y then
notif.show = false
notif.hud_timer += dt
end
player:hud_change(notif.elems.text, "text", notif.hud_msg)
if notif.hud_img then
player:hud_change(notif.elems.img, "text", notif.hud_img)
end
if notif.show then
local speed = i3.settings.hud_speed * (100 * get_progress(offset.y, notif.max.y)) * dt
for _, def in pairs(notif.elems) do
local hud_info = player:hud_get(def)
player:hud_change(def, "offset", {
x = hud_info.offset.x,
y = hud_info.offset.y - (speed * max(1, (#data.hud.notifs - idx + 1) / 1.45))
})
end
elseif notif.show == false and notif.hud_timer >= i3.settings.hud_timer_max then
local speed = (i3.settings.hud_speed * 2.6) * (100 * get_progress(offset.x, notif.max.x)) * dt
for _, def in pairs(notif.elems) do
local hud_info = player:hud_get(def)
player:hud_change(def, "offset", {
x = hud_info.offset.x - speed,
y = hud_info.offset.y
})
if hud_info.offset.x < notif.max.x then
player:hud_remove(def)
remove(data.hud.notifs, idx)
end
end
end
end
core.register_globalstep(function(dt)
local players = get_connected_players()
players[0] = #players
for i = 1, players[0] do
local player = players[i]
local name = player:get_player_name()
local data = i3.data[name]
if not data then return end
for idx, notif in ipairs(data.hud.notifs) do
if notif.show ~= nil then
show_hud(player, data, notif, idx, dt)
end
end
local has_text = player:hud_get(data.hud.wielditem).text ~= ""
if not data.wielditem_hud then
if has_text then
player:hud_change(data.hud.wielditem, "text", "")
end
return
end
data.timer = (data.timer or 0) + dt
local wieldidx = player:get_wield_index()
if wieldidx == data.old_wieldidx then
if data.timer >= i3.settings.wielditem_fade_after and has_text then
player:hud_change(data.hud.wielditem, "text", "")
end
return
end
data.timer = 0
data.old_wieldidx = wieldidx
local wielditem = player:get_wielded_item()
local meta = wielditem:get_meta()
local meta_desc = meta:get_string"short_description"
meta_desc = meta_desc:gsub("\27", "")
meta_desc = core.strip_colors(meta_desc)
local desc = meta_desc ~= "" and meta_desc or wielditem:get_short_description()
player:hud_change(data.hud.wielditem, "text", desc:trim())
end
end)
local function init_waypoints(player)
local name = player:get_player_name()
local data = i3.data[name]
data.waypoints = data.waypoints or {}
for _, v in ipairs(data.waypoints) do
if not v.hide then
local id = add_hud_waypoint(player, v.name, str_to_pos(v.pos), v.color, v.image)
v.id = id
end
end
end
return function(player)
init_hud(player)
init_waypoints(player)
end

View File

@ -1,4 +1,4 @@
local model_alias = {
return {
["boats:boat"] = {name = "boats:boat", drawtype = "entity"},
["carts:cart"] = {name = "carts:cart", drawtype = "entity", frames = "0,0"},
["default:chest"] = {name = "default:chest_open"},
@ -9,5 +9,3 @@ local model_alias = {
["doors:door_steel"] = {name = "doors:door_steel_a"},
["xpanes:door_steel_bar"] = {name = "xpanes:door_steel_bar_a"},
}
return model_alias

98
src/preprocessor.lua Normal file
View File

@ -0,0 +1,98 @@
--[[ All source files have to be preprocessed before loading.
This allows implementing custom operators like bitwise ones. ]]
local fmt, split = string.format, string.split
local var = "[%w%.%[%]\"\'_]"
local modpath = core.get_modpath"i3"
local _,_, fs_elements = dofile(modpath .. "/src/styles.lua")
local operators = {
["([%+%-%*%^/&|])="] = function(a, b, c)
return fmt("%s = %s %s %s", a, a, b, c)
end,
["+%+"] = function(a, b)
return fmt("%s = %s + 1\n%s", a, a, b)
end,
["&"] = function(a, b)
return fmt("bit.band(%s, %s)", a, b)
end,
["|"] = function(a, b)
return fmt("bit.bor(%s, %s)", a, b)
end,
["<<"] = function(a, b)
return fmt("bit.lshift(%s, %s)", a, b)
end,
[">>"] = function(a, b)
return fmt("bit.rshift(%s, %s)", a, b)
end,
["<<="] = function(a, b)
return fmt("%s = bit.lshift(%s, %s)", a, a, b)
end,
[">>="] = function(a, b)
return fmt("%s = bit.rshift(%s, %s)", a, a, b)
end,
}
local function compile(data)
data = data:gsub("IMPORT%((.-)%)", function(a)
return "local " .. a:gsub("\"", "") .. " = i3.get(" .. a .. ")"
end)
data = data:gsub("([%w_]+)%(", function(a)
if fs_elements[a] then
return fmt("fs('%s',", a)
end
end)
data = data:gsub("([%w_]+)-%-\n", function(a)
return fmt("%s = %s - 1", a, a)
end)
for op, func in pairs(operators) do
data = data:gsub("(" .. var .. "+)%s?" .. op .. "%s?(" .. var .. "*)", func)
end
return data
end
local function _load(path, line, data, t)
if line then
if not t then
t = split(data, "\n")
end
t[line] = t[line]:gsub("(" .. var .. "+)%s?=%s?(" .. var .. "*)", "%2")
data = table.concat(t, "\n")
else
local file = assert(io.open(path, "r"))
data = file:read"*a"
file:close()
data = compile(data)
end
local l, err = loadstring(data)
if not l then
local err_line = tonumber(err:match(":(%d+):"))
if t then
print("err_line", err_line, t[err_line])
end
if line ~= err_line then
return _load(path, err_line, data, t)
end
end
return l, err
end
return function(path)
return _load(path) or loadfile(path)
end

178
src/progressive.lua Normal file
View File

@ -0,0 +1,178 @@
local set_fs = i3.set_fs
local hud_notif = i3.hud_notif
local POLL_FREQ = 0.25
IMPORT("reg_items", "reg_nodes", "fmt", "table_merge", "array_diff")
IMPORT("is_group", "extract_groups", "item_has_groups", "apply_recipe_filters", "sort_by_category")
i3.remove_minitab"nodes"
i3.remove_minitab"items"
i3.new_minitab("unlocked", {
description = "Unlocked",
sorter = function(item, data)
return data.items_progress[item]
end
})
local function get_filtered_items(player, data)
local items, known = {}, 0
for i = 1, #i3.init_items do
local item = i3.init_items[i]
local recipes = i3.recipes_cache[item]
local usages = i3.usages_cache[item]
recipes = #apply_recipe_filters(recipes or {}, player)
usages = #apply_recipe_filters(usages or {}, player)
if recipes > 0 or usages > 0 then
items[item] = true
known += recipes + usages
end
end
data.known_recipes = known
return items
end
local function item_in_inv(item, inv_items)
local inv_items_size = #inv_items
if is_group(item) then
local groupname = item:sub(7)
local group_cache = i3.groups[groupname]
local groups = group_cache and group_cache.groups or extract_groups(item)
for i = 1, inv_items_size do
local def = core.registered_items[inv_items[i]]
if def then
if item_has_groups(def.groups, groups) then
return true
end
end
end
else
for i = 1, inv_items_size do
if inv_items[i] == item then
return true
end
end
end
end
local function recipe_in_inv(rcp, inv_items)
for _, item in pairs(rcp.items) do
if not item_in_inv(item, inv_items) then return end
end
return true
end
local function progressive_filter(recipes, player)
if not recipes then
return {}
end
local name = player:get_player_name()
local data = i3.data[name]
if #data.inv_items == 0 then
return {}
end
local filtered, c = {}, 0
for i = 1, #recipes do
local recipe = recipes[i]
if recipe_in_inv(recipe, data.inv_items) then
c++
filtered[c] = recipe
end
end
return filtered
end
local item_lists = {"main", "craft", "craftpreview"}
local function get_inv_items(player)
local inv = player:get_inventory()
if not inv then
return {}
end
local stacks = {}
for i = 1, #item_lists do
local list = inv:get_list(item_lists[i])
table_merge(stacks, list)
end
local inv_items, c = {}, 0
for i = 1, #stacks do
local stack = stacks[i]
if not stack:is_empty() then
local name = stack:get_name()
if core.registered_items[name] then
c++
inv_items[c] = name
end
end
end
return inv_items
end
-- Workaround. Need an engine call to detect when the contents of
-- the player inventory changed, instead.
local function poll_new_items(player, data, join)
local inv_items = get_inv_items(player)
local diff = array_diff(inv_items, data.inv_items)
if join or #diff > 0 then
data.inv_items = table_merge(diff, data.inv_items)
local oldknown = data.known_recipes or 0
local items = get_filtered_items(player, data)
data.discovered = data.known_recipes - oldknown
if data.discovered > 0 then
local msg = fmt("%u new recipe%s unlocked!", data.discovered, data.discovered > 1 and "s" or "")
local last_discovered = diff[1]
local img = reg_items[last_discovered].inventory_image
if reg_nodes[last_discovered] then
local id = core.get_content_id(last_discovered)
img = i3.cubes[id] or img
end
hud_notif(data.player_name, msg, img)
end
data.items_progress = items
sort_by_category(data)
set_fs(player)
end
core.after(POLL_FREQ, poll_new_items, player, data)
end
i3.add_recipe_filter("Default progressive filter", progressive_filter)
core.register_on_joinplayer(function(player)
local name = player:get_player_name()
local data = i3.data[name]
if not data then return end
data.inv_items = data.inv_items or {}
data.known_recipes = data.known_recipes or 0
data.discovered = data.discovered or 0
poll_new_items(player, data, true)
end)

View File

@ -1,51 +1,69 @@
local fmt = string.format
local PNG = {
blank = "i3_blank.png",
bg = "i3_bg.png",
bg_full = "i3_bg_full.png",
bg_goto = "i3_bg_goto.png",
bg_content = "i3_bg_content.png",
bar = "i3_bar.png",
hotbar = "i3_hotbar.png",
highlight = "i3_highlight.png",
search = "i3_search.png",
heart = "i3_heart.png",
heart_half = "i3_heart_half.png",
heart_grey = "i3_heart_grey.png",
prev = "i3_next.png^\\[transformFX",
next = "i3_next.png",
arrow = "i3_arrow.png",
arrow_content = "i3_arrow_content.png",
trash = "i3_trash.png",
sort_az = "i3_sort_az.png",
sort_za = "i3_sort_za.png",
sort = "i3_sort.png",
settings = "i3_settings.png",
compress = "i3_compress.png",
fire = "i3_fire.png",
fire_anim = "i3_fire_anim.png",
book = "i3_book.png",
sign = "i3_sign.png",
cancel = "i3_cancel.png",
export = "i3_export.png",
slot = "i3_slot.png",
crafting = "i3_crafting.png",
slot = "i3_slot.png^\\[resize:128x128",
pagenum_hover = "i3_slot.png^\\[resize:128x128^\\[opacity:130",
tab = "i3_tab.png",
tab_small = "i3_tab_small.png",
tab_top = "i3_tab.png^\\[transformFY",
furnace_anim = "i3_furnace_anim.png",
shapeless = "i3_shapeless.png",
bag = "i3_bag.png",
armor = "i3_armor.png",
awards = "i3_award.png",
skins = "i3_skin.png",
waypoints = "i3_waypoint.png",
teleport = "i3_teleport.png",
add = "i3_add.png",
refresh = "i3_refresh.png",
visible = "i3_visible.png^\\[brighten",
nonvisible = "i3_non_visible.png",
exit = "i3_exit.png",
home = "i3_home.png",
flag = "i3_flag_anim.png",
edit = "i3_edit.png",
no_result = "i3_no_result.png",
find_more = "i3_find_more.png",
search_outline = "i3_search_outline.png",
search_outline_trim = "i3_search_outline_trim.png",
all = "i3_all.png",
node = "i3_node.png",
item = "i3_item.png",
cube = "i3_cube.png",
home_px = "i3_home_px.png",
home_px_hover = "i3_home_px_hover.png",
cancel_hover = "i3_cancel.png^\\[brighten",
search_hover = "i3_search.png^\\[brighten",
export_hover = "i3_export.png^\\[brighten",
crafting_hover = "i3_crafting.png^\\[brighten",
trash_hover = "i3_trash.png^\\[brighten^\\[colorize:#f00:100",
compress_hover = "i3_compress.png^\\[brighten",
sort_az_hover = "i3_sort_az.png^\\[brighten",
sort_za_hover = "i3_sort_za.png^\\[brighten",
sort_hover = "i3_sort.png^\\[brighten",
settings_hover = "i3_settings.png^\\[brighten",
prev_hover = "i3_next_hover.png^\\[transformFX",
next_hover = "i3_next_hover.png",
tab_hover = "i3_tab_hover.png",
@ -56,56 +74,67 @@ local PNG = {
awards_hover = "i3_award_hover.png",
skins_hover = "i3_skin_hover.png",
waypoints_hover = "i3_waypoint_hover.png",
teleport_hover = "i3_teleport.png^\\[brighten",
add_hover = "i3_add.png^\\[brighten",
refresh_hover = "i3_refresh.png^\\[brighten",
exit_hover = "i3_exit.png^\\[brighten",
home_hover = "i3_home.png^\\[brighten",
edit_hover = "i3_edit.png^\\[brighten",
all_hover = "i3_all_on.png^\\[brighten",
node_hover = "i3_node_on.png^\\[brighten",
item_hover = "i3_item_on.png^\\[brighten",
}
local styles = fmt([[
local styles = string.format([[
listcolors[#bababa50;#bababa99]
style_type[list;size=1;spacing=0.15]
style_type[field;border=false;bgcolor=transparent]
style_type[label,field;font_size=16]
style_type[button;border=false;content_offset=0]
style_type[image_button,item_image_button;border=false;sound=i3_click]
style_type[item_image_button;bgimg_hovered=%s]
style_type[image_button,item_image_button,checkbox,dropdown;border=false;sound=i3_click]
style_type[item_image_button;bgimg_middle=9;padding=-9]
style_type[item_image_button:hovered;bgimg=%s]
style[;sound=]
style[nofav;sound=i3_cannot]
style[search;content_offset=0]
style[pagenum,no_item,no_rcp;font=bold;font_size=18]
style[enable_search:hovered;bgimg=%s]
style[exit;fgimg=%s;fgimg_hovered=%s;content_offset=0]
style[cancel;fgimg=%s;fgimg_hovered=%s;content_offset=0]
style[search;fgimg=%s;fgimg_hovered=%s;content_offset=0]
style[prev_page;fgimg=%s;fgimg_hovered=%s]
style[next_page;fgimg=%s;fgimg_hovered=%s]
style[prev_recipe;fgimg=%s;fgimg_hovered=%s]
style[next_recipe;fgimg=%s;fgimg_hovered=%s]
style[prev_usage;fgimg=%s;fgimg_hovered=%s]
style[next_usage;fgimg=%s;fgimg_hovered=%s]
style[prev_page,prev_recipe,prev_usage,prev_sort,prev_skin;fgimg=%s;fgimg_hovered=%s]
style[next_page,next_recipe,next_usage,next_sort,next_skin;fgimg=%s;fgimg_hovered=%s]
style[waypoint_add;fgimg=%s;fgimg_hovered=%s;content_offset=0]
style[bag_rename;fgimg=%s;fgimg_hovered=%s;content_offset=0]
style[btn_bag,btn_armor,btn_skins;font=bold;font_size=18;content_offset=0;sound=i3_click]
style[craft_rcp,craft_usg;noclip=true;font_size=16;sound=i3_craft;
bgimg=i3_btn9.png;bgimg_hovered=i3_btn9_hovered.png;
bgimg_pressed=i3_btn9_pressed.png;bgimg_middle=4,6]
style[confirm_trash_yes,confirm_trash_no,set_home;noclip=true;font_size=16;
bgimg=i3_btn9.png;bgimg_hovered=i3_btn9_hovered.png;
bgimg_pressed=i3_btn9_pressed.png;bgimg_middle=4,6]
style[confirm_trash_yes;sound=i3_trash]
]],
PNG.slot,
PNG.exit, PNG.exit_hover,
PNG.cancel, PNG.cancel_hover,
PNG.search, PNG.search_hover,
PNG.prev, PNG.prev_hover,
PNG.next, PNG.next_hover,
PNG.prev, PNG.prev_hover,
PNG.next, PNG.next_hover,
PNG.prev, PNG.prev_hover,
PNG.next, PNG.next_hover,
PNG.add, PNG.add_hover)
PNG.search_outline,
PNG.exit, PNG.exit_hover,
PNG.cancel, PNG.cancel_hover,
PNG.prev, PNG.prev_hover,
PNG.next, PNG.next_hover,
PNG.add, PNG.add_hover,
PNG.edit, PNG.edit_hover)
local fs_elements = {
label = "label[%f,%f;%s]",
box = "box[%f,%f;%f,%f;%s]",
image = "image[%f,%f;%f,%f;%s]",
tooltip = "tooltip[%f,%f;%f,%f;%s]",
tooltip = "tooltip[%f,%f;%f,%f;%s;#32333899;#fff]",
button = "button[%f,%f;%f,%f;%s;%s]",
checkbox = "checkbox[%f,%f;%s;%s;%s]",
slot = "image[%f,%f;%f,%f;" .. fmt("%s;9]", PNG.slot),
item_image = "item_image[%f,%f;%f,%f;%s]",
hypertext = "hypertext[%f,%f;%f,%f;%s;%s]",
bg9 = "background9[%f,%f;%f,%f;%s;false;%u]",
bg9 = "background9[%f,%f;%f,%f;%s;false;12]",
scrollbar = "scrollbar[%f,%f;%f,%f;%s;%s;%u]",
model = "model[%f,%f;%f,%f;%s;%s;%s;%s;%s;%s;%s]",
image_button = "image_button[%f,%f;%f,%f;%s;%s;%s]",
@ -113,4 +142,10 @@ local fs_elements = {
item_image_button = "item_image_button[%f,%f;%f,%f;%s;%s;%s]",
}
return PNG, styles, fs_elements
local colors = {
yellow = "#ffd866",
black = "#2d2a2e",
blue = "#7bf",
}
return PNG, styles, fs_elements, colors

View File

@ -0,0 +1,4 @@
i3.compress("default:diamondblock", {
replace = "diamond",
by = {"bronze", "copper", "gold", "steel", "tin"}
})

View File

@ -6,21 +6,197 @@ local mt2 = ItemStack("dye:red")
mt2:get_meta():set_string("description", "test red")
mt2:get_meta():set_string("color", "#ff0")
minetest.register_craft({
local mt3 = ItemStack("default:pick_diamond")
mt3:get_meta():set_string("description", "Worn Pick")
mt3:get_meta():set_string("color", "yellow")
mt3:set_wear(10000)
minetest.register_craft {
output = mt:to_string(),
type = "shapeless",
recipe = {
"default:wood",
mt2:to_string(),
},
})
}
i3.register_craft({
minetest.register_craft {
output = mt3:to_string(),
type = "shapeless",
recipe = {
"default:pick_mese",
"default:diamond",
},
}
minetest.clear_craft {
recipe = {
{"default:sand", "default:sand"},
{"default:sand", "default:sand"},
},
}
i3.register_craft {
url = "https://raw.githubusercontent.com/minetest-mods/i3/main/tests/test_online_recipe.json"
}
i3.register_craft {
result = "default:ladder_wood 2",
items = {"default:copper_ingot 7, default:tin_ingot, default:steel_ingot 2"},
})
}
i3.register_craft({
i3.register_craft {
result = "default:tree",
items = {
"default:wood",
"",
"default:wood"
},
}
i3.register_craft {
result = "default:cobble 16",
items = {
"default:stone, default:stone",
"default:stone, , default:stone",
", default:stone, default:stone",
}
}
i3.register_craft {
grid = {
"X",
"#",
"X",
"X",
},
key = {
['#'] = "default:wood",
['X'] = "default:glass 2",
},
result = "default:mese 3",
}
i3.register_craft {
grid = {
"X",
"#X",
"X",
"X",
},
key = {
['#'] = "default:wood",
['X'] = "default:glass 2",
},
result = "default:mese 3",
}
i3.register_craft {
grid = {
"X",
},
key = {
['#'] = "default:wood",
['X'] = "default:glass 2",
},
result = "default:mese 3",
}
i3.register_craft {
grid = {
"X#",
},
key = {
['#'] = "default:wood",
['X'] = "default:glass 2",
},
result = "default:mese 3",
}
i3.register_craft {
grid = {
"X#X",
},
key = {
['#'] = "default:wood",
['X'] = "default:glass 2",
},
result = "default:mese 3",
}
i3.register_craft {
grid = {
"X#XX",
},
key = {
['#'] = "default:wood",
['X'] = "default:glass 2",
},
result = "default:mese 3",
}
i3.register_craft {
grid = {
"X#XX",
"X#X",
},
key = {
['#'] = "default:wood",
['X'] = "default:glass 2",
},
result = "default:mese 3",
}
i3.register_craft {
grid = {
"X#XX",
"X#X",
"#",
},
key = {
['#'] = "default:wood",
['X'] = "default:glass 2",
},
result = "default:mese 3",
}
i3.register_craft {
grid = {
"X##XX",
},
key = {
['#'] = "default:wood",
['X'] = "default:glass 2",
},
result = "default:mese 3",
}
i3.register_craft {
grid = {
"X##X#X",
},
key = {
['#'] = "default:wood",
['X'] = "default:glass 2",
},
result = "default:mese 3",
}
i3.register_craft {
grid = {
"X##X#X",
"",
"X",
},
key = {
['#'] = "default:wood",
['X'] = "default:glass 2",
},
result = "default:mese 3",
}
i3.register_craft {
grid = {
"X #",
" ## ",
@ -32,9 +208,9 @@ i3.register_craft({
['X'] = "default:glass 2",
},
result = "default:mese 3",
})
}
i3.register_craft({
i3.register_craft {
grid = {
"X #",
" ## ",
@ -46,9 +222,9 @@ i3.register_craft({
['X'] = "default:glass",
},
result = "default:mese 3",
})
}
i3.register_craft({
i3.register_craft {
grid = {
"X #",
" ## ",
@ -61,9 +237,9 @@ i3.register_craft({
['X'] = "default:glass",
},
result = "default:mese 3",
})
}
i3.register_craft({
i3.register_craft {
grid = {
"X #",
" ## ",
@ -76,10 +252,10 @@ i3.register_craft({
['X'] = "default:glass",
},
result = "default:mese 3",
})
}
i3.register_craft({
i3.register_craft {
grid = {
"X #",
" ## ",
@ -93,9 +269,9 @@ i3.register_craft({
['X'] = "default:glass",
},
result = "default:mese 3",
})
}
i3.register_craft({
i3.register_craft {
grid = {
"X #",
" ## ",
@ -109,9 +285,9 @@ i3.register_craft({
['X'] = "default:glass",
},
result = "default:mese 3",
})
}
i3.register_craft({
i3.register_craft {
grid = {
"X #",
" ## ",
@ -125,9 +301,9 @@ i3.register_craft({
['X'] = "default:glass",
},
result = "default:mese 3",
})
}
i3.register_craft({
i3.register_craft {
grid = {
"X #",
" ## ",
@ -141,9 +317,9 @@ i3.register_craft({
['X'] = "default:glass",
},
result = "default:mese 3",
})
}
i3.register_craft({
i3.register_craft {
grid = {
"X #",
" ## ",
@ -159,23 +335,4 @@ i3.register_craft({
['X'] = "default:glass",
},
result = "default:mese 3",
})
i3.register_craft({
grid = {
"X #",
" ## ",
"X#X#",
"#X#X#",
"X X##X#X",
" ## ",
"#X#X#",
"#X#X#",
"X #",
},
key = {
['#'] = "default:wood",
['X'] = "default:glass",
},
result = "default:mese 3",
})
}

26
tests/test_operators.lua Normal file
View File

@ -0,0 +1,26 @@
local a, b, c = 0, 0, 0
b+=1
c++; local foo = "bar";
local t = {
a = a++,
b = 2,
c = c+=2,
d = a&3,
e = 1,
}
t["b"] <<= 4
t.b >>= 2
assert(t.b == 8)
--print(dump(t))
--c += 1
c*=2
local i = 16
i += i<<4
assert(i == 272)
assert((a+=2) == 2)
assert(c++ == 3)
assert((a-=1) == -1)
assert((c^=4) == 16)
assert((a&b) == 0)
assert((c|=a) == 2)
assert((1<<8) == 256)

View File

@ -1,49 +1,50 @@
i3.new_tab {
name = "test1",
local SWITCH
i3.new_tab("test1", {
description = "Test 1 Test 1",
image = "i3_heart.png",
formspec = function(player, data, fs)
fs("label[3,1;Test 1]")
end,
}
fs("button", 3, 4, 3, 0.8, "test", "Click here")
fs("label", 3, 1, "Just a test")
i3.new_tab {
name = "test2",
if SWITCH then
fs"label[3,2;Button clicked]"
else
fs"label[3,2;Lorem Ipsum]"
end
end,
fields = function(player, data, fields)
if fields.test then
SWITCH = true
end
end
})
i3.new_tab("test2", {
description = "Test 2",
image = "i3_mesepick.png",
slots = true,
formspec = function(player, data, fs)
fs("label[3,1;Test 2]")
end,
}
})
i3.new_tab {
name = "test3",
description = "Test 3",
i3.new_tab("test_creative", {
description = "Test creative",
access = function(player, data)
local name = player:get_player_name()
if name == "singleplayer" then
return true
end
return core.is_creative_enabled(name)
end,
formspec = function(player, data, fs)
fs("label[3,1;Test 3]")
fs("label[3,1;Creative enabled]")
end,
fields = function(player, data, fields)
i3.set_fs(player, "label[3,2;Test extra_fs]")
end,
}
i3.override_tab("test2", {
name = "test2",
description = "Test override",
image = "i3_mesepick.png",
formspec = function(player, data, fs)
fs("label[3,1;Override!]")
end,
fields = i3.set_fs,
})

12
tests/test_waypoints.lua Normal file
View File

@ -0,0 +1,12 @@
core.after(5, function()
i3.add_waypoint("Test", {
player = "singleplayer",
pos = {x = 0, y = 2, z = 0},
color = 0xffff00,
-- image = "heart.png",
})
core.after(5, function()
i3.remove_waypoint("singleplayer", "Test")
end)
end)

BIN
textures/i3_all.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

BIN
textures/i3_all_on.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
textures/i3_armor_1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 186 B

BIN
textures/i3_armor_2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 191 B

BIN
textures/i3_armor_3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 175 B

BIN
textures/i3_armor_4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 167 B

BIN
textures/i3_armor_5.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
textures/i3_bg_content.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

BIN
textures/i3_bg_goto.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

BIN
textures/i3_bg_notif.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
textures/i3_blank.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 262 B

BIN
textures/i3_crafting.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

BIN
textures/i3_cube.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 404 B

BIN
textures/i3_edit.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 749 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.8 KiB

After

Width:  |  Height:  |  Size: 528 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

BIN
textures/i3_find_more.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

BIN
textures/i3_flag_anim.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 227 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 147 B

BIN
textures/i3_highlight.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 B

BIN
textures/i3_home.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 802 B

BIN
textures/i3_home_px.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 171 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 171 B

BIN
textures/i3_item.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

BIN
textures/i3_item_on.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 570 B

After

Width:  |  Height:  |  Size: 570 B

BIN
textures/i3_no_result.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
textures/i3_node.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
textures/i3_node_on.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.5 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 507 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 304 B

BIN
textures/i3_settings.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

BIN
textures/i3_sort.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 845 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

After

Width:  |  Height:  |  Size: 412 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 864 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

141
util/luacheck.lua Executable file
View File

@ -0,0 +1,141 @@
local exec = os.execute
local fmt, find, sub = string.format, string.find, string.sub
local var = "[%w%.%[%]\"\'_]"
local _,_, fs_elements = dofile("../src/styles.lua")
exec "clear"
local function split(str, delim, include_empty, max_splits, sep_is_pattern)
delim = delim or ","
max_splits = max_splits or -2
local items = {}
local pos, len = 1, #str
local plain = not sep_is_pattern
max_splits = max_splits + 1
repeat
local np, npe = find(str, delim, pos, plain)
np, npe = (np or (len+1)), (npe or (len+1))
if (not np) or (max_splits == 1) then
np = len + 1
npe = np
end
local s = sub(str, pos, np - 1)
if include_empty or (s ~= "") then
max_splits = max_splits - 1
items[#items + 1] = s
end
pos = npe + 1
until (max_splits == 0) or (pos > (len + 1))
return items
end
local files = {
"api",
"bags",
"caches",
"callbacks",
"common",
"compression",
"detached_inv",
"fields",
"groups",
"gui",
"hud",
"model_aliases",
"progressive",
"styles",
}
local operators = {
["([%+%-%*%^/&|])="] = function(a, b, c)
return fmt("%s = %s %s %s", a, a, b, c)
end,
["+%+"] = function(a, b)
return fmt("%s = %s + 1\n%s", a, a, b)
end,
["&"] = function(a, b)
return fmt("bit.band(%s, %s)", a, b)
end,
["|"] = function(a, b)
return fmt("bit.bor(%s, %s)", a, b)
end,
["<<"] = function(a, b)
return fmt("bit.lshift(%s, %s)", a, b)
end,
[">>"] = function(a, b)
return fmt("bit.rshift(%s, %s)", a, b)
end,
["<<="] = function(a, b)
return fmt("%s = bit.lshift(%s, %s)", a, a, b)
end,
[">>="] = function(a, b)
return fmt("%s = bit.rshift(%s, %s)", a, a, b)
end,
}
local function compile(data)
data = data:gsub("IMPORT%((.-)%)", function(a)
return "local " .. a:gsub("\"", "") .. " = i3.get(" .. a .. ")"
end)
data = data:gsub("([%w_]+)%(", function(a)
if fs_elements[a] then
return fmt("fs('%s',", a)
end
end)
data = data:gsub("([%w_]+)-%-\n", function(a)
return fmt("%s = %s - 1", a, a)
end)
for op, func in pairs(operators) do
data = data:gsub("(" .. var .. "+)%s?" .. op .. "%s?(" .. var .. "*)", func)
end
return data
end
for _, p in ipairs(files) do
local function _load(path, line, data, t)
if line then
if not t then
t = split(data, "\n")
end
t[line] = t[line]:gsub("(" .. var .. "+)%s?=%s?(" .. var .. "*)", "%2")
data = table.concat(t, "\n")
else
local file = assert(io.open(path, "r"))
data = file:read"*a"
file:close()
data = compile(data)
end
local l, err = loadstring(data)
if not l then
local err_line = tonumber(err:match(":(%d+):"))
if line ~= err_line then
return _load(path, err_line, data, t)
end
end
local _file = io.open(path:match("(.*)%.") .. ".lc", "w")
_file:write(data)
_file:close()
end
_load("../src/" .. p .. ".lua")
end
exec "luacheck ../init.lua"
exec "luacheck ../src/preprocessor.lua"
exec "luacheck ../src/*.lc"
exec "rm ../src/*.lc"

7
util/optipng.sh Normal file
View File

@ -0,0 +1,7 @@
#!/bin/bash
# Colors with 0 alpha need to be preserved, because opaque leaves ignore alpha.
# For that purpose, the use of indexed colors is disabled (-nc).
cd ../textures
find -name '*.png' -print0 | xargs -0 optipng -o7 -zm1-9 -nc -strip all -clobber

23
util/servers.lua Normal file
View File

@ -0,0 +1,23 @@
local JSON = require"JSON" -- luarocks install json-lua
os.execute "clear"
local list = io.popen("curl -s -H 'Accept: text/html' http://servers.minetest.net/list"):read("*a")
list = JSON:decode(list).list
local servers = {}
for _, server in ipairs(list) do
if server.mods then
for _, mod in ipairs(server.mods) do
if mod == "i3" then
table.insert(servers, server.name)
end
end
end
end
if #servers > 0 then
print(("=> %u/%u servers using [i3]:\n\t• %s"):format(#servers, #list, table.concat(servers, "\n\t")))
else
print"No server using [i3]"
end