Compare commits

..
544 Commits
Author SHA1 Message Date
phidiasandGitHub debf40be81 Merge branch 'explosivegaming:main' into aperx 2026-03-20 15:24:52 +09:00
phidiasandWeblate 19a63fad1e Deleted translation using Weblate (German) 2026-01-15 10:44:08 +00:00
phidiasandWeblate e27a623fdf Deleted translation using Weblate (German) 2026-01-15 10:44:08 +00:00
phidiasandWeblate 0703807f1d Deleted translation using Weblate (German) 2026-01-15 10:44:07 +00:00
phidiasandWeblate fc04da8748 Deleted translation using Weblate (German) 2026-01-15 10:44:07 +00:00
phidiasandWeblate 2bdf18305f Deleted translation using Weblate (Spanish) 2026-01-15 10:44:07 +00:00
phidiasandWeblate 4ac29ce955 Deleted translation using Weblate (German) 2026-01-15 10:44:07 +00:00
phidiasandWeblate 4fcaea613c Deleted translation using Weblate (Spanish) 2026-01-15 10:44:00 +00:00
phidiasandWeblate 613be4986b Deleted translation using Weblate (Spanish) 2026-01-15 10:44:00 +00:00
phidiasandWeblate f1e6869db0 Deleted translation using Weblate (Spanish) 2026-01-15 10:44:00 +00:00
phidiasandWeblate 9b91ca4461 Deleted translation using Weblate (Spanish) 2026-01-15 10:43:59 +00:00
phidiasandWeblate b467a7af64 Deleted translation using Weblate (Portuguese) 2026-01-15 10:43:54 +00:00
phidiasandWeblate 1adbfd58d4 Deleted translation using Weblate (Portuguese) 2026-01-15 10:43:53 +00:00
phidiasandWeblate 7552cd4cdd Deleted translation using Weblate (Portuguese) 2026-01-15 10:43:52 +00:00
phidiasandWeblate 183f6b8807 Deleted translation using Weblate (Portuguese) 2026-01-15 10:43:52 +00:00
phidiasandWeblate d989526e6e Deleted translation using Weblate (Portuguese) 2026-01-15 10:43:52 +00:00
phidiasandWeblate b901ed040b Deleted translation using Weblate (French) 2026-01-15 10:43:46 +00:00
phidiasandWeblate 2167008c0c Deleted translation using Weblate (French) 2026-01-15 10:43:45 +00:00
phidiasandWeblate ee934ed1df Deleted translation using Weblate (French) 2026-01-15 10:43:44 +00:00
phidiasandWeblate a67c9ae738 Deleted translation using Weblate (French) 2026-01-15 10:43:44 +00:00
phidiasandWeblate 212a4ed1bc Deleted translation using Weblate (French) 2026-01-15 10:43:44 +00:00
phidiasandWeblate c03d707087 Added translation using Weblate (Portuguese) 2026-01-15 10:39:12 +00:00
phidiasandWeblate 3340e4c28d Added translation using Weblate (French) 2026-01-15 10:39:12 +00:00
phidiasandWeblate 26dfcf586f Added translation using Weblate (Spanish) 2026-01-15 10:39:12 +00:00
phidiasandWeblate 50a367f0ee Added translation using Weblate (German) 2026-01-15 10:39:12 +00:00
phidiasandWeblate cad9da0ba2 Added translation using Weblate (Portuguese) 2026-01-15 10:39:11 +00:00
phidiasandWeblate 1ae3bbbce3 Added translation using Weblate (French) 2026-01-15 10:39:11 +00:00
phidiasandWeblate 1a63c3ec09 Added translation using Weblate (Spanish) 2026-01-15 10:39:11 +00:00
phidiasandWeblate c673ab6984 Added translation using Weblate (German) 2026-01-15 10:39:11 +00:00
phidiasandWeblate 39d3890215 Added translation using Weblate (Portuguese) 2026-01-15 10:39:07 +00:00
phidiasandWeblate a12260dd87 Added translation using Weblate (French) 2026-01-15 10:39:07 +00:00
phidiasandWeblate 27a9247436 Added translation using Weblate (Spanish) 2026-01-15 10:39:07 +00:00
phidiasandWeblate d0e022a0cf Added translation using Weblate (German) 2026-01-15 10:39:07 +00:00
Weblate 250af62abe Added translation using Weblate (Chinese (Traditional Han script)) 2026-01-15 10:38:25 +00:00
Weblate bcb7a5816b Added translation using Weblate (Chinese (Simplified Han script)) 2026-01-15 10:38:25 +00:00
Weblate fb29f27755 Added translation using Weblate (Portuguese) 2026-01-15 10:38:25 +00:00
Weblate c7cfba2685 Added translation using Weblate (French) 2026-01-15 10:38:25 +00:00
Weblate befe2131f1 Added translation using Weblate (Spanish) 2026-01-15 10:38:25 +00:00
Weblate 49eccb36b1 Added translation using Weblate (German) 2026-01-15 10:38:25 +00:00
phidiasandWeblate d2e7dedf54 Added translation using Weblate (Portuguese) 2026-01-15 09:57:40 +00:00
phidiasandWeblate ded7b33716 Added translation using Weblate (French) 2026-01-15 09:57:40 +00:00
phidiasandWeblate d1a4e25274 Added translation using Weblate (Spanish) 2026-01-15 09:57:40 +00:00
phidiasandWeblate ed51b75671 Added translation using Weblate (German) 2026-01-15 09:57:40 +00:00
phidiasandGitHub 0d689790de Merge branch 'explosivegaming:main' into aperx 2026-01-15 13:57:41 +09:00
phidias b1db4657f4 . 2026-01-15 02:50:20 +09:00
phidias b8f466816f . 2026-01-15 02:37:33 +09:00
phidias 78033ccca9 . 2026-01-15 00:41:27 +09:00
phidias 0ef169b596 . 2026-01-15 00:39:52 +09:00
phidias e99a5befff . 2026-01-15 00:33:47 +09:00
phidias 85631f4fef . 2026-01-15 00:20:25 +09:00
phidias 167b6b083f . 2026-01-15 00:15:55 +09:00
phidias b6cc2bd2e3 . 2026-01-15 00:15:05 +09:00
phidias b75fc72a22 Merge branch 'aperx' of https://github.com/PHIDIAS0303/ExpCluster into aperx 2026-01-15 00:11:45 +09:00
phidias d475793c61 . 2026-01-15 00:11:42 +09:00
phidiasandGitHub fcf0d05a55 Merge pull request #20 from PHIDIAS0303/main
Refactor task list (#414)
2025-12-17 01:28:57 +09:00
phidiasandGitHub 0a4f4f35a1 Update _file_loader.lua 2025-12-17 01:28:07 +09:00
phidiasandGitHub 2daca9024a Merge branch 'aperx' into main 2025-12-17 01:27:33 +09:00
phidiasandGitHub 37afd11c24 Merge pull request #19 from PHIDIAS0303/main
Main
2025-12-04 01:31:37 +09:00
phidias 3a85a15e7a Merge branch 'main' of https://github.com/PHIDIAS0303/ExpCluster 2025-12-04 01:31:25 +09:00
phidias 255cf1e51b . 2025-09-23 19:35:49 +09:00
phidias afc3c94b13 Merge branch 'aperx' of https://github.com/PHIDIAS0303/ExpCluster into aperx 2025-09-23 19:33:46 +09:00
phidias a32095ae2d . 2025-09-23 19:33:39 +09:00
phidiasandGitHub 0dae21dfcb Merge branch 'explosivegaming:main' into aperx 2025-09-22 19:37:50 +09:00
phidiasandGitHub 7ff37d5dd2 Merge branch 'explosivegaming:main' into aperx 2025-09-17 19:06:27 +09:00
phidiasandGitHub d2332dfa47 Merge branch 'explosivegaming:main' into aperx 2025-09-05 22:20:26 +09:00
phidiasandGitHub d75abf7a10 Merge branch 'explosivegaming:main' into aperx 2025-09-05 02:11:24 +09:00
phidiasandGitHub 92a077a895 Merge branch 'explosivegaming:main' into aperx 2025-09-04 20:03:49 +09:00
phidiasandGitHub 54f3aed411 Merge branch 'explosivegaming:main' into aperx 2025-09-04 19:55:32 +09:00
phidiasandGitHub 1ccf766c63 Merge branch 'explosivegaming:main' into aperx 2025-09-04 19:20:06 +09:00
phidias fc8adaceda Merge branch 'aperx' of https://github.com/PHIDIAS0303/ExpCluster into aperx 2025-09-02 22:53:18 +09:00
phidias a6aad24c77 . 2025-09-02 22:53:08 +09:00
phidiasandGitHub b7fa20439b Merge branch 'explosivegaming:main' into aperx 2025-09-02 13:43:43 +09:00
phidias 010d7caa0a Merge branch 'main' of https://github.com/PHIDIAS0303/ExpCluster 2025-09-01 01:21:26 +09:00
phidias c00470e58c . 2025-08-31 02:13:45 +09:00
phidiasandGitHub 0a97ac4ed3 Merge pull request #18 from PHIDIAS0303/main
Refactor some of the Guis from the legacy plugin (#399)
2025-08-31 01:55:04 +09:00
phidias b69653e85f . 2025-08-31 01:54:19 +09:00
phidias 19e2f819ff Merge branch 'main' of https://github.com/PHIDIAS0303/ExpCluster 2025-08-31 01:54:06 +09:00
phidias d917c50e3e . 2025-08-31 01:43:29 +09:00
phidias 0d519700b6 . 2025-08-31 01:41:02 +09:00
phidias 3891e48acd . 2025-08-31 01:36:03 +09:00
phidias b8c437fca1 Merge branch 'aperx' of https://github.com/PHIDIAS0303/ExpCluster into aperx 2025-08-26 13:34:29 +09:00
phidias 301d8647ec . 2025-08-26 13:14:52 +09:00
phidiasandGitHub 52261a06fd Merge pull request #17 from PHIDIAS0303/main
Fix no spawn force without players joining (#400)
2025-08-26 13:10:01 +09:00
phidiasandGitHub 9771ea8ce4 Merge branch 'aperx' into main 2025-08-26 13:09:44 +09:00
phidias 6df40ac157 . 2025-08-25 16:28:41 +09:00
phidias 8cc3805656 Merge branch 'main' into aperx 2025-08-11 02:17:48 +09:00
phidiasandGitHub 703d2981d0 Merge pull request #15 from PHIDIAS0303/main
Fix logic for afk kick (#395)
2025-06-16 00:35:25 +09:00
phidiasandGitHub 65d3c75032 Merge branch 'aperx' into main 2025-06-16 00:35:19 +09:00
phidias 6ba8316cff . 2025-06-16 00:21:16 +09:00
phidias 524654e935 . 2025-05-22 14:17:16 +09:00
phidias 6add30adf3 . 2025-05-19 15:23:20 +09:00
phidias f36403997a Merge branch 'aperx' of https://github.com/PHIDIAS0303/ExpCluster into aperx 2025-05-19 15:22:58 +09:00
phidias a42802f509 .. 2025-05-19 15:22:56 +09:00
phidiasandGitHub b26cec6fae Merge branch 'explosivegaming:main' into aperx 2025-05-18 04:17:25 +09:00
phidias 0fa264ab0a . 2025-05-16 00:45:47 +09:00
phidias 5914aced5e . 2025-05-13 13:24:33 +09:00
phidias cf2b4476d5 . 2025-05-12 22:22:22 +09:00
phidias f79408d37c . 2025-05-12 22:03:43 +09:00
phidias 268feab5f7 . 2025-05-12 21:56:09 +09:00
phidias d61f163170 Merge branch 'aperx' of https://github.com/PHIDIAS0303/ExpCluster into aperx 2025-05-12 20:25:32 +09:00
phidias c36b92c401 . 2025-05-12 20:25:30 +09:00
phidiasandGitHub d851c11e39 Merge pull request #14 from PHIDIAS0303/main
Main
2025-04-27 13:08:51 +09:00
phidiasandGitHub 686efd2b30 Merge branch 'aperx' into main 2025-04-27 13:08:44 +09:00
phidias 2b3a49391f . 2025-04-26 14:16:06 +09:00
phidias 9a6e056008 . 2025-04-26 13:55:24 +09:00
phidias 350f8c3f8d . 2025-04-21 14:51:05 +09:00
phidias e405bd70ac . 2025-04-21 14:41:52 +09:00
phidias 90a3510068 . 2025-04-21 14:39:23 +09:00
phidias 41dd7517d8 . 2025-04-21 14:37:30 +09:00
phidias ee2b2b429d . 2025-04-21 14:27:04 +09:00
phidias 29bb17ed55 . 2025-04-21 14:21:03 +09:00
phidias 61644d1aef . 2025-04-20 14:02:16 +09:00
phidias 4365facdb5 . 2025-04-20 13:54:04 +09:00
phidias f0da6a6507 . 2025-04-19 12:46:12 +09:00
phidias 9e0ccf87e4 . 2025-04-19 12:41:56 +09:00
phidias 0afd445da9 . 2025-04-19 12:40:58 +09:00
phidias e368278d3b . 2025-04-19 12:40:35 +09:00
phidias e808e11dd4 . 2025-04-19 00:40:25 +09:00
phidias 0f54eaee0b . 2025-04-19 00:40:09 +09:00
phidias 7983992371 . 2025-04-19 00:34:37 +09:00
phidias 717955608f . 2025-04-19 00:30:46 +09:00
phidias e78920c4e6 . 2025-04-19 00:26:51 +09:00
phidias 24aafc36da . 2025-04-19 00:26:08 +09:00
phidias 93ea19e290 . 2025-04-19 00:20:48 +09:00
phidias 8ad47a1673 . 2025-04-19 00:16:31 +09:00
phidias 02774919cb . 2025-04-18 23:42:55 +09:00
phidias c6848b7a6e . 2025-04-18 23:27:39 +09:00
phidias 01269ec755 . 2025-04-18 23:19:14 +09:00
phidias 4e51816a69 . 2025-04-18 15:29:22 +09:00
phidias bfc91902c9 . 2025-04-16 18:04:30 +09:00
phidias e2667ada9f . 2025-04-16 18:00:47 +09:00
phidias f64d17772c . 2025-04-16 17:59:43 +09:00
phidias f09f3f2b8a . 2025-04-16 17:57:33 +09:00
phidias 79f67e0442 . 2025-04-16 17:52:20 +09:00
phidias 0efefab723 . 2025-04-16 17:50:44 +09:00
phidias c292004fc1 . 2025-04-16 17:49:16 +09:00
phidias 83f5d71225 . 2025-04-16 17:48:15 +09:00
phidias b9df5beb94 . 2025-04-16 17:47:42 +09:00
phidias 034973bed9 . 2025-04-16 17:46:26 +09:00
phidias d344131a74 . 2025-04-16 17:44:52 +09:00
phidias b05f7ee04f . 2025-04-16 17:40:52 +09:00
phidias 26d4f150d1 . 2025-04-16 17:39:36 +09:00
phidias 0020b79c1f . 2025-04-16 17:37:41 +09:00
phidias cd362e233f . 2025-04-16 17:35:54 +09:00
phidias 7da432dada . 2025-04-16 17:24:56 +09:00
phidias b2e0423655 . 2025-04-16 17:00:03 +09:00
phidias 388b25ca6b . 2025-04-13 00:17:22 +09:00
phidias bd8c2c19c0 . 2025-04-13 00:13:17 +09:00
phidias 7b7effd6ab . 2025-04-13 00:10:01 +09:00
phidias dcf9a58c21 . 2025-04-13 00:06:00 +09:00
phidias b2e5f6cb83 . 2025-04-13 00:04:23 +09:00
phidias 06529f5536 . 2025-04-13 00:01:55 +09:00
phidias 9cf7d7d7a2 Merge branch 'main' into aperx 2025-04-07 17:13:29 +09:00
phidias f88f348efc . 2025-04-07 17:09:21 +09:00
phidias 6c6a14376c . 2025-04-07 17:05:03 +09:00
phidias 02fb1c53e7 . 2025-04-07 17:02:14 +09:00
phidias edbc7de2d5 . 2025-03-23 01:25:40 +09:00
phidias 36ff0c4dcf . 2025-03-23 01:25:00 +09:00
phidias a13e0d0c11 . 2025-03-23 01:24:53 +09:00
phidias 0c143d9d2d . 2025-03-08 13:14:11 +09:00
phidias 981e5b9c2e . 2025-03-08 01:09:18 +09:00
phidias 2bb77fbd04 Merge branch 'aperx' of https://github.com/PHIDIAS0303/ExpCluster into aperx 2025-03-08 01:08:07 +09:00
phidias c95b812f5d . 2025-03-08 01:08:01 +09:00
phidiasandGitHub 15d0f02ea1 Merge branch 'explosivegaming:main' into aperx 2025-03-08 01:06:34 +09:00
phidias 6ce37fb677 Merge branch 'aperx' of https://github.com/PHIDIAS0303/ExpCluster into aperx 2025-03-08 01:05:26 +09:00
phidias 9d03982688 . 2025-03-08 01:05:16 +09:00
phidiasandGitHub 9190733d82 Merge branch 'explosivegaming:main' into aperx 2025-03-07 23:45:09 +09:00
phidias abcfc8f9ee . 2025-03-06 23:37:32 +09:00
phidias 4fa34c31d1 . 2025-03-06 23:29:40 +09:00
phidias 5939ff8e59 . 2025-03-06 23:05:10 +09:00
phidias e07a971e7d Merge branch 'aperx' of https://github.com/PHIDIAS0303/ExpCluster into aperx 2025-03-06 22:20:22 +09:00
phidias e047c7ecf6 . 2025-03-06 22:20:11 +09:00
phidias 23d88c197e . 2025-03-06 22:19:44 +09:00
phidiasandGitHub 76cfb66d68 Merge branch 'explosivegaming:main' into aperx 2025-03-02 13:10:04 +09:00
phidiasandGitHub 33b7b7ffa0 Merge branch 'explosivegaming:main' into aperx 2025-03-01 23:23:46 +09:00
phidias 8ccaf47f6f . 2025-02-27 00:01:12 +09:00
phidias 704dd73df4 . 2025-02-26 23:58:33 +09:00
phidias 860cacf16f . 2025-02-26 23:58:00 +09:00
phidias 2c0040a033 . 2025-02-24 20:05:33 +09:00
phidias aa3976090f . 2025-02-24 19:07:03 +09:00
phidias 03e30caeda . 2025-02-24 19:01:15 +09:00
phidias f7805ae2a7 . 2025-02-24 18:59:47 +09:00
phidias fbf91f4310 . 2025-02-24 18:59:15 +09:00
phidias 3a8b217f8d . 2025-02-24 18:54:27 +09:00
phidias 1cfcdddfa5 . 2025-02-24 18:52:59 +09:00
phidias 8880eb6a57 . 2025-02-24 18:52:54 +09:00
phidias 07fe643d03 . 2025-02-24 18:47:57 +09:00
phidias 17b53a6695 . 2025-02-24 18:46:08 +09:00
phidias 51d266a667 . 2025-02-24 18:42:53 +09:00
phidias 36c65cbea0 . 2025-02-24 18:40:38 +09:00
phidias 32723e388b . 2025-02-24 18:37:55 +09:00
phidias 609796070f . 2025-02-24 18:36:29 +09:00
phidias 67ce09f96e . 2025-02-24 18:33:28 +09:00
phidias 082ee900f4 . 2025-02-24 18:02:03 +09:00
phidias 4e7776bc2d . 2025-02-24 17:44:39 +09:00
phidias 36931d9bb1 . 2025-02-24 17:25:19 +09:00
phidias 9786c95eda . 2025-02-24 17:15:44 +09:00
phidias 261041edd3 . 2025-02-24 17:06:55 +09:00
phidias 5800d4a590 . 2025-02-23 14:52:28 +09:00
phidias 60279c4a49 . 2025-02-23 14:47:07 +09:00
phidias 52c6887887 . 2025-02-23 14:34:02 +09:00
phidias 8b000613a2 Merge branch 'aperx' of https://github.com/PHIDIAS0303/ExpCluster into aperx 2025-02-23 14:33:50 +09:00
phidias 766e96a9fc . 2025-02-23 14:33:39 +09:00
phidias 510a119741 . 2025-02-23 14:04:07 +09:00
phidiasandGitHub 73d1352ac5 Merge branch 'explosivegaming:main' into aperx 2025-02-23 13:15:29 +09:00
phidias b3c70e7700 . 2025-02-23 12:47:01 +09:00
phidias 85f9ebd7d0 . 2025-02-22 23:42:26 +09:00
phidias 5add829a78 . 2025-02-22 23:23:58 +09:00
phidias ce73946885 . 2025-02-16 12:19:46 +09:00
phidias 88da422f9e . 2025-02-13 00:19:40 +09:00
phidias 5a006eaa25 . 2025-02-13 00:15:36 +09:00
phidias 63e21e1332 . 2025-02-13 00:13:02 +09:00
phidias 1ad8b3edd7 . 2025-02-13 00:09:02 +09:00
phidias 005a5501d8 . 2025-02-13 00:06:06 +09:00
phidias 62d818605a . 2025-02-13 00:00:35 +09:00
phidias aacbfa7c3b . 2025-02-12 23:57:00 +09:00
phidias a3fbdd195b . 2025-02-12 23:38:01 +09:00
phidias 63345c753c . 2025-02-12 23:37:34 +09:00
phidias f5d23e9a07 . 2025-02-12 22:42:01 +09:00
phidias e3a1b1f4fe . 2025-02-12 22:41:21 +09:00
phidias 2ec3813e2c . 2025-02-12 22:35:39 +09:00
phidias 4350cfad8d . 2025-02-12 22:29:36 +09:00
phidias e759ca3c48 . 2025-02-12 22:29:27 +09:00
phidias 2ab8a8ae66 . 2025-02-12 22:20:20 +09:00
phidias eb69977365 . 2025-02-12 22:17:50 +09:00
phidias 6bc077ed3e . 2025-02-12 22:13:10 +09:00
phidias b026a84e67 . 2025-02-12 21:59:29 +09:00
phidias 6da76c548e . 2025-02-12 20:53:44 +09:00
phidias 2a0b4f2bfb . 2025-02-12 20:50:38 +09:00
phidias 6ed3a8bd49 . 2025-02-12 20:50:24 +09:00
phidias 35631f45c6 . 2025-02-12 20:46:00 +09:00
phidias 822258eee0 . 2025-02-12 20:39:07 +09:00
phidias 9e0ed189ee . 2025-02-12 20:25:08 +09:00
phidias 29f997bde2 . 2025-02-12 20:22:34 +09:00
phidias 5f24583e90 . 2025-02-12 20:20:42 +09:00
phidias cea58693f9 . 2025-02-12 20:19:34 +09:00
phidias 3f2bb631f8 . 2025-02-12 20:14:21 +09:00
phidias 7b61c8b690 . 2025-02-12 20:12:51 +09:00
phidias 823af41ad4 . 2025-02-12 20:12:22 +09:00
phidias 2720c6938e . 2025-02-12 20:01:00 +09:00
phidias 95bdc0f0f3 . 2025-02-12 19:56:59 +09:00
phidias 20e98d4b13 . 2025-02-12 19:51:53 +09:00
phidias a900440fc4 . 2025-02-12 19:39:34 +09:00
phidias 2f1be9762f . 2025-02-12 19:33:43 +09:00
phidias 02561e36c6 . 2025-02-12 19:26:12 +09:00
phidias dfeec22b64 . 2025-02-12 19:19:02 +09:00
phidias 3059199512 . 2025-02-12 19:14:20 +09:00
phidias f332cd649a . 2025-02-12 19:12:34 +09:00
phidias 6934d44b1d . 2025-02-12 19:12:11 +09:00
phidiasandGitHub dcfb034b69 Merge pull request #12 from PHIDIAS0303/main
Main
2025-02-10 15:40:30 +09:00
phidias 2f2389f1ea . 2025-02-10 15:40:09 +09:00
phidias 127aace0ae . 2025-02-10 15:34:47 +09:00
phidiasandGitHub d219ef85f3 Merge branch 'aperx' into main 2025-02-10 15:27:14 +09:00
phidias adda5c0f9d . 2025-02-10 15:21:35 +09:00
phidias 1fc3047116 . 2025-02-10 15:11:43 +09:00
phidias df3daafc03 . 2025-02-10 15:09:28 +09:00
phidias 3ed1589e7e . 2025-02-10 15:07:18 +09:00
phidias 8ac99a0aa7 . 2025-02-10 04:27:48 +09:00
phidias 544782e65e . 2025-02-08 04:28:47 +09:00
phidias fa16c66f1e . 2025-02-08 04:26:04 +09:00
phidias 13d1452f42 . 2025-02-08 04:21:13 +09:00
phidias 3f6fb51383 . 2025-02-08 04:18:14 +09:00
phidias 71e8f39361 . 2025-02-08 04:16:15 +09:00
phidias 3174efafed .. 2025-02-08 04:15:58 +09:00
phidias af241f2f17 . 2025-02-08 04:11:31 +09:00
phidias 413083e03a . 2025-02-08 04:01:37 +09:00
phidias c241fbef0d . 2025-02-08 04:00:58 +09:00
phidias 4093d30ee5 . 2025-02-08 03:54:13 +09:00
phidias 432d29cc89 . 2025-02-08 03:49:37 +09:00
phidias 725ae0d7b3 . 2025-02-08 01:36:41 +09:00
phidias c7280169a4 . 2025-02-08 01:31:37 +09:00
phidias de5462662f . 2025-02-08 01:27:36 +09:00
phidias 0fea8692af . 2025-02-08 00:59:39 +09:00
phidias a1a9745319 . 2025-02-08 00:56:53 +09:00
phidias f261e1fbf4 . 2025-02-08 00:53:15 +09:00
phidias abfa6b6aba . 2025-02-08 00:43:55 +09:00
phidias 66cbb71c67 . 2025-02-08 00:43:02 +09:00
phidias d3b8c3252c . 2025-02-08 00:36:57 +09:00
phidias 374c727a4a . 2025-02-08 00:33:05 +09:00
phidias c3da0363f3 . 2025-02-08 00:28:26 +09:00
phidias 1df8fd0359 . 2025-02-08 00:23:00 +09:00
phidias d5803bed8d . 2025-02-08 00:18:04 +09:00
phidias c74121bdd4 . 2025-02-08 00:17:48 +09:00
phidias 52a94b7bd3 . 2025-02-08 00:13:27 +09:00
phidias 0a9d4f4c8c . 2025-02-08 00:11:35 +09:00
phidias a763d30e2b . 2025-02-08 00:02:01 +09:00
phidias 6318425f80 . 2025-02-08 00:01:08 +09:00
phidias c6d32f07b5 . 2025-02-07 23:10:52 +09:00
phidias fbd52f9943 . 2025-02-07 23:06:24 +09:00
phidias c8216806ea . 2025-02-07 23:01:41 +09:00
phidias 1e6d1b19de . 2025-02-07 22:53:07 +09:00
phidias 05eee28bb1 . 2025-02-07 22:46:55 +09:00
phidias d2eb463d21 . 2025-02-07 22:44:05 +09:00
phidias 5552e17715 . 2025-02-07 22:41:12 +09:00
phidias 96ac299263 . 2025-02-07 22:30:34 +09:00
phidias 5ebf2eb0bc . 2025-02-07 22:25:52 +09:00
phidias 31ec0afa5b . 2025-02-07 22:20:20 +09:00
phidias f0adfa3e32 . 2025-02-07 19:24:07 +09:00
phidias 04d55cec4a . 2025-02-07 19:23:42 +09:00
phidias 08fbb1fd84 . 2025-02-07 19:17:05 +09:00
phidias 39dbcfeaca . 2025-02-07 19:01:21 +09:00
phidias 2b66965d08 . 2025-02-07 18:59:13 +09:00
phidias d4e76de5fd . 2025-02-07 18:45:18 +09:00
phidias aeb1538095 . 2025-02-07 18:43:51 +09:00
phidias cbd776966e . 2025-02-07 18:40:33 +09:00
phidias 1daef1f216 . 2025-02-07 13:54:01 +09:00
phidias e34b6682ed . 2025-02-07 13:49:44 +09:00
phidias 8da7ab8586 . 2025-02-07 13:22:56 +09:00
phidias fb2a409d4c . 2025-02-07 02:25:40 +09:00
phidiasandGitHub a933eec9e3 Merge branch 'explosivegaming:main' into aperx 2025-02-06 23:24:57 +09:00
phidias 61494e9c48 . 2025-02-06 23:20:13 +09:00
phidias 5241a41ff3 . 2025-02-06 23:14:28 +09:00
phidias 2c4316a53b . 2025-02-06 22:40:01 +09:00
phidias f40fd155ab . 2025-02-06 22:38:21 +09:00
phidias e42d1a8280 . 2025-02-06 22:34:40 +09:00
phidias 68e523bc56 . 2025-02-06 22:30:29 +09:00
phidias dd20b145ab . 2025-02-06 22:25:04 +09:00
phidias 946cc583b1 . 2025-02-06 20:43:29 +09:00
phidias a3f3ec3991 . 2025-02-06 20:40:10 +09:00
phidias b94e5e4dad . 2025-02-06 20:34:23 +09:00
phidias 7da0cf78a8 . 2025-02-06 20:27:46 +09:00
phidias 31154f007e Merge branch 'aperx' of https://github.com/PHIDIAS0303/ExpCluster into aperx 2025-02-05 17:35:40 +09:00
phidias 29395fe161 . 2025-02-05 17:35:07 +09:00
phidiasandGitHub d7c761d138 Merge branch 'explosivegaming:main' into aperx 2025-02-04 14:24:59 +09:00
phidias c8ca527005 . 2025-02-03 23:12:37 +09:00
phidias f63ebf8e7c . 2025-02-03 18:07:06 +09:00
phidias f7549b8017 .. 2025-02-03 18:06:54 +09:00
phidias 52e50fbb06 Merge branch 'aperx' of https://github.com/PHIDIAS0303/ExpCluster into aperx 2025-02-03 15:33:03 +09:00
phidias cd3cdda4bf . 2025-02-03 15:33:00 +09:00
phidiasandGitHub 8c727876bb Merge pull request #11 from PHIDIAS0303/main
Main
2025-02-03 15:13:51 +09:00
phidiasandGitHub 84cdc45362 Merge branch 'aperx' into main 2025-02-03 15:12:46 +09:00
phidiasandGitHub 7c9592bbb1 Merge pull request #10 from PHIDIAS0303/main
Main
2025-01-30 16:50:20 +09:00
phidiasandGitHub 8680e5fbfb Merge branch 'aperx' into main 2025-01-30 16:49:14 +09:00
phidiasandGitHub 6d19f8304d Merge pull request #9 from Cooldude2606/package/gui
Package/gui
2025-01-30 16:45:18 +09:00
phidias 40bf727e49 . 2025-01-29 00:45:39 +09:00
phidias 8407c1357d . 2025-01-29 00:43:43 +09:00
phidias c62cfc413d . 2025-01-29 00:36:26 +09:00
phidias fd5d95bcd8 . 2025-01-29 00:35:57 +09:00
phidias 4c257d1497 . 2025-01-29 00:35:15 +09:00
phidias cc590372de . 2025-01-29 00:34:20 +09:00
phidias fc7c9d41ad . 2025-01-29 00:32:54 +09:00
phidias 9e1f7530e2 . 2025-01-28 22:21:07 +09:00
phidias 8c79c0363c . 2025-01-28 21:53:14 +09:00
phidias d12ad7ed2e . 2025-01-28 21:00:45 +09:00
phidias 8f230bb393 . 2025-01-28 20:47:08 +09:00
phidias 97c6f7f3c3 . 2025-01-28 20:31:44 +09:00
phidias dbe3c873f6 . 2025-01-28 20:26:14 +09:00
phidias be6ccb27f1 . 2025-01-28 20:21:14 +09:00
phidias 1d4af30843 . 2025-01-28 19:49:14 +09:00
phidias a59f5830f2 . 2025-01-28 18:07:17 +09:00
phidias 8ae03a7ee1 . 2025-01-28 18:03:50 +09:00
phidias ddd6dc474f . 2025-01-28 18:03:36 +09:00
phidias 3ae1b7c1f8 . 2025-01-28 18:01:00 +09:00
phidias d475a55460 . 2025-01-28 17:55:04 +09:00
phidias 4bc18e10f5 . 2025-01-28 17:09:27 +09:00
phidias bbe25ec120 . 2025-01-28 17:05:23 +09:00
phidias 1aaae3fa13 . 2025-01-27 00:00:04 +09:00
phidias a7a0e85a95 . 2025-01-26 22:02:17 +09:00
phidias c2089a89ba . 2025-01-26 13:50:58 +09:00
phidias 94b3bd8c20 Merge branch 'aperx' of https://github.com/PHIDIAS0303/ExpCluster into aperx 2025-01-26 13:50:46 +09:00
phidias a86a625d62 . 2025-01-26 13:50:43 +09:00
phidiasandGitHub 42fe79ef91 Merge branch 'explosivegaming:main' into aperx 2025-01-26 03:42:45 +09:00
phidiasandGitHub 6890226f13 Merge pull request #8 from PHIDIAS0303/main
Add option for vlayer on space platforms (#365)
2025-01-25 03:04:34 +09:00
phidiasandGitHub c682b8b81b Merge branch 'aperx' into main 2025-01-25 03:03:58 +09:00
phidias 403bace3ab . 2025-01-23 22:22:53 +09:00
phidias c00f5b54a8 . 2025-01-23 22:17:26 +09:00
phidias cea73d7ab8 . 2025-01-23 22:16:21 +09:00
phidias bf29c1ccd9 . 2025-01-23 22:14:32 +09:00
phidias 6520014c47 . 2025-01-22 20:37:19 +09:00
phidias 064dead08c . 2025-01-22 20:27:50 +09:00
phidias 16574b5908 . 2025-01-22 20:26:23 +09:00
phidias 0eaa2b1542 . 2025-01-22 20:13:32 +09:00
phidias 1f7ac1ace5 . 2025-01-22 20:10:48 +09:00
phidias 357eba0e64 . 2025-01-22 19:51:04 +09:00
phidias 991d26510e . 2025-01-22 19:46:56 +09:00
phidias d4b4f50618 . 2025-01-22 18:42:39 +09:00
phidias 852cf45d0c . 2025-01-22 18:39:18 +09:00
phidias 5e1c27d52f . 2025-01-22 18:37:29 +09:00
phidias b297b0d0b7 . 2025-01-22 18:32:28 +09:00
phidias 733c70823a . 2025-01-22 18:31:07 +09:00
phidias fe901c5f11 . 2025-01-22 18:30:24 +09:00
phidias 112d5b7bda . 2025-01-22 18:27:36 +09:00
phidias 3914e4594d . 2025-01-22 18:17:14 +09:00
phidias b8f78835d0 . 2025-01-22 18:11:19 +09:00
phidias 70625f0888 . 2025-01-22 18:01:47 +09:00
phidiasandGitHub 3382250a34 Merge branch 'explosivegaming:main' into aperx 2025-01-22 17:29:22 +09:00
phidias 4ab3fb0b4d . 2025-01-21 20:42:09 +09:00
phidias d101dc1f11 . 2025-01-21 20:07:41 +09:00
phidias 9b72f9e127 . 2025-01-21 19:58:57 +09:00
phidias 8376f240bb . 2025-01-21 19:52:12 +09:00
phidias e3d42442d4 . 2025-01-21 19:33:31 +09:00
phidias 30f9e32550 . 2025-01-21 19:05:14 +09:00
phidias 3c876d4873 . 2025-01-20 22:54:38 +09:00
phidias ab12a8be52 . 2025-01-20 16:36:41 +09:00
phidias f95f5faad9 . 2025-01-20 16:33:10 +09:00
phidias f21586192d . 2025-01-20 16:29:52 +09:00
phidias 7b7482b70d . 2025-01-20 16:27:56 +09:00
phidias 79f6747f79 . 2025-01-20 16:26:27 +09:00
phidias 111c6e9b29 Merge branch 'aperx' of https://github.com/PHIDIAS0303/ExpCluster into aperx 2025-01-20 16:19:55 +09:00
phidias c56f8b4f27 . 2025-01-20 16:19:52 +09:00
phidiasandGitHub e9635796fa Merge branch 'explosivegaming:main' into aperx 2025-01-20 16:14:15 +09:00
phidias e291759cb7 . 2025-01-17 20:14:03 +09:00
phidias 3f351df82d . 2025-01-17 20:09:48 +09:00
phidias 3fd2876ba7 . 2025-01-17 20:07:36 +09:00
phidias e2173cc57d . 2025-01-17 20:05:42 +09:00
phidias 55fe9b6069 . 2025-01-17 18:05:21 +09:00
phidias be2ae106f5 Merge branch 'aperx' of https://github.com/PHIDIAS0303/ExpCluster into aperx 2025-01-16 14:07:00 +09:00
phidias f9405b930d . 2025-01-16 14:06:51 +09:00
phidiasandGitHub cff86b07f2 Merge branch 'explosivegaming:main' into aperx 2025-01-16 13:57:31 +09:00
phidias 8ad24b3afd . 2025-01-15 17:36:34 +09:00
phidias 9fb1af24d2 . 2025-01-15 16:50:05 +09:00
phidias 736df61152 . 2025-01-15 12:44:37 +09:00
phidias 7784dbf3ad . 2025-01-14 23:38:43 +09:00
phidias 29abf08b70 . 2025-01-14 19:48:51 +09:00
phidias 7c87cfc8bc . 2025-01-14 19:19:55 +09:00
phidias 781760d67e . 2025-01-14 19:13:57 +09:00
phidias fdf0cb66cf . 2025-01-14 18:53:31 +09:00
phidias 967225ebe9 Merge branch 'aperx' of https://github.com/PHIDIAS0303/ExpCluster into aperx 2025-01-14 18:48:28 +09:00
phidias d7dc4040c8 . 2025-01-14 18:48:24 +09:00
phidiasandGitHub ff5987d7f6 Merge pull request #7 from PHIDIAS0303/main
Main
2025-01-14 13:36:05 +09:00
phidiasandGitHub d1cb929c6d Merge branch 'aperx' into main 2025-01-14 13:35:26 +09:00
phidias 843de85206 . 2025-01-14 13:26:49 +09:00
phidias c10de39a89 . 2025-01-13 22:48:10 +09:00
phidias bfe5110254 . 2025-01-13 22:34:31 +09:00
phidias 4fa1ea4fb3 . 2025-01-13 22:03:03 +09:00
phidias 9e44de8850 . 2025-01-13 19:07:40 +09:00
phidias de6474dc76 . 2025-01-12 03:43:48 +09:00
phidias 8b67491d8c . 2025-01-12 03:26:24 +09:00
phidias da323d5152 . 2025-01-11 23:08:26 +09:00
phidias 0d8712dd6b . 2025-01-11 23:07:58 +09:00
phidias cf2f6cfb8c . 2025-01-11 23:07:07 +09:00
phidias 2ed7235f3d . 2025-01-11 23:05:39 +09:00
phidias c9700414c7 . 2025-01-11 22:10:26 +09:00
phidias 6bcd3bc641 Merge branch 'aperx' of https://github.com/PHIDIAS0303/ExpCluster into aperx 2025-01-11 22:09:38 +09:00
phidias a6ddf0c427 . 2025-01-11 22:09:35 +09:00
phidiasandGitHub 68e21db03a Merge branch 'explosivegaming:main' into aperx 2025-01-11 13:13:13 +09:00
phidiasandGitHub 5373b9d9bb Merge pull request #6 from PHIDIAS0303/main
vlayer 2.0 compatible (#353)
2025-01-11 03:42:52 +09:00
phidiasandGitHub eadca324e7 Merge branch 'aperx' into main 2025-01-11 03:42:38 +09:00
phidias 3632738501 . 2025-01-10 17:37:33 +09:00
phidias 645d693583 . 2025-01-10 14:22:33 +09:00
phidias ea04b93c7c . 2025-01-10 14:13:12 +09:00
phidias 518f1fc561 . 2025-01-10 14:04:17 +09:00
phidias 803192e01c . 2025-01-10 02:44:05 +09:00
phidias 8dac727396 . 2025-01-10 00:11:45 +09:00
phidias 7a5577452c . 2025-01-10 00:09:13 +09:00
phidias 8f5d3e4a65 . 2025-01-10 00:01:16 +09:00
phidias 6603a14fd3 . 2025-01-09 23:58:36 +09:00
phidias 46f35eadcc . 2025-01-09 23:58:26 +09:00
phidias 4136b5018b . 2025-01-09 23:54:15 +09:00
phidias 965c1c7712 . 2025-01-09 23:51:34 +09:00
phidias 82e858975b . 2025-01-09 23:49:44 +09:00
phidias f94e9bf17d . 2025-01-09 23:44:35 +09:00
phidias 10bf61247c . 2025-01-09 23:38:39 +09:00
phidias bd488aef76 . 2025-01-09 23:32:21 +09:00
phidias 397661728f . 2025-01-09 23:21:54 +09:00
phidias 5c15bbbe7c . 2025-01-09 22:33:27 +09:00
phidias 62ac9a6406 . 2025-01-09 22:25:49 +09:00
phidias f0172f6270 . 2025-01-09 22:00:17 +09:00
phidias 64ca88a198 . 2025-01-09 21:58:32 +09:00
phidias 6d44936364 . 2025-01-09 20:32:26 +09:00
phidias dee2600eec . 2025-01-09 20:27:47 +09:00
phidias 0a61893f68 . 2025-01-09 19:46:21 +09:00
phidias dd655807c4 Merge branch 'aperx' of https://github.com/PHIDIAS0303/ExpCluster into aperx 2025-01-09 14:42:36 +09:00
phidias f1868e883c . 2025-01-09 14:42:32 +09:00
phidiasandGitHub 91ddb5530a Merge pull request #5 from PHIDIAS0303/main
Main
2025-01-09 14:25:58 +09:00
phidiasandGitHub 65e6647068 Merge branch 'aperx' into main 2025-01-09 14:12:10 +09:00
phidias f0b066cdfe . 2025-01-09 14:07:40 +09:00
phidias b9de395a44 . 2025-01-09 03:49:22 +09:00
phidias 46bb73b927 . 2025-01-09 03:44:56 +09:00
phidias c168095249 . 2025-01-09 03:37:46 +09:00
phidias a02975853d . 2025-01-09 03:26:12 +09:00
phidias 64910c2821 . 2025-01-09 03:11:32 +09:00
phidias b56b53c268 . 2025-01-09 03:10:11 +09:00
phidias 292870bf8c . 2025-01-09 00:20:08 +09:00
phidias 72c1d73a20 . 2025-01-09 00:09:22 +09:00
phidias 055c9bb6ad Merge branch 'aperx' of https://github.com/PHIDIAS0303/ExpCluster into aperx 2025-01-08 23:40:31 +09:00
phidias 7e7bf5708c . 2025-01-08 23:40:30 +09:00
phidiasandGitHub e250610273 Merge pull request #4 from PHIDIAS0303/main
Main
2025-01-08 23:34:30 +09:00
phidiasandGitHub 9265479565 Merge branch 'aperx' into main 2025-01-08 23:34:14 +09:00
phidias b3c19b8010 . 2025-01-08 23:27:14 +09:00
phidias a6da3a74c2 . 2025-01-08 22:22:21 +09:00
phidias ce146be986 . 2025-01-07 23:01:21 +09:00
phidias a03df60302 . 2025-01-07 22:59:50 +09:00
phidias 723f535ab6 . 2025-01-07 22:55:27 +09:00
phidias db70decbf3 . 2025-01-07 21:54:33 +09:00
phidias a38708c203 . 2025-01-07 20:15:03 +09:00
phidias 664c9e67c0 . 2025-01-07 19:35:02 +09:00
phidias 11eb7152bd . 2025-01-07 19:32:25 +09:00
phidias 155721938e . 2025-01-07 18:15:43 +09:00
phidias 251e41bba5 . 2025-01-07 18:07:38 +09:00
phidias fa3168e1ac . 2025-01-07 17:40:53 +09:00
phidias baf9e3f2df . 2025-01-07 17:39:47 +09:00
phidiasandGitHub b60045d70e Merge branch 'explosivegaming:main' into aperx 2025-01-05 04:07:09 +09:00
phidias 3e570b1599 . 2025-01-04 13:22:40 +09:00
phidiasandGitHub 81d9f59e81 Merge branch 'explosivegaming:main' into aperx 2025-01-04 13:10:26 +09:00
phidiasandGitHub 98f1dc6a29 Merge branch 'explosivegaming:main' into aperx 2025-01-04 04:14:08 +09:00
phidiasandGitHub eb97893b3e Merge branch 'explosivegaming:main' into aperx 2025-01-04 04:07:55 +09:00
phidiasandGitHub 599339f805 Merge branch 'explosivegaming:main' into aperx 2025-01-03 14:04:02 +09:00
phidiasandGitHub b920c2984d Merge branch 'explosivegaming:main' into aperx 2025-01-02 14:39:41 +09:00
phidias 4a14972412 . 2025-01-02 14:39:21 +09:00
phidias f5a830d1c6 . 2025-01-01 21:21:54 +09:00
phidias 9a82000792 . 2025-01-01 18:49:16 +09:00
phidias fb01bc1644 . 2025-01-01 17:42:06 +09:00
phidias d049126a54 Merge branch 'aperx' of https://github.com/PHIDIAS0303/ExpCluster into aperx 2025-01-01 17:40:54 +09:00
phidias 890019ca98 . 2025-01-01 17:40:51 +09:00
phidiasandGitHub fe53b474a0 Merge pull request #2 from PHIDIAS0303/main
Main
2025-01-01 17:29:49 +09:00
phidiasandGitHub 8192ebdd3c Merge branch 'aperx' into main 2025-01-01 17:28:23 +09:00
phidias a65a04c810 . 2025-01-01 17:26:07 +09:00
phidias 2749df9f08 . 2024-12-31 23:35:42 +09:00
phidias a991142279 . 2024-12-31 15:12:26 +09:00
phidias cb6ea27391 Merge branch 'aperx' of https://github.com/PHIDIAS0303/ExpCluster into aperx 2024-12-31 13:51:22 +09:00
phidiasandGitHub 6b88725af2 Merge pull request #1 from PHIDIAS0303/main
Main
2024-12-31 13:50:47 +09:00
phidias 118b99f521 . 2024-12-31 13:49:34 +09:00
phidiasandGitHub 25f495b4dd Merge branch 'aperx' into main 2024-12-31 13:48:39 +09:00
phidias 6d6e44f0da . 2024-12-31 13:42:37 +09:00
phidias 7555de476e . 2024-12-31 13:42:19 +09:00
phidias cf900d46cd . 2024-12-31 13:41:38 +09:00
phidias 2944e730b0 . 2024-12-31 13:41:06 +09:00
phidias 1eb7a27c71 . 2024-12-31 13:40:23 +09:00
phidias 3ab5becf16 . 2024-12-31 13:37:50 +09:00
phidias a0c0169a4b . 2024-12-31 13:34:15 +09:00
phidias dfd3f85975 . 2024-12-31 13:33:48 +09:00
phidias 4a1a451ff2 . 2024-12-31 13:14:27 +09:00
phidias d73688dd37 . 2024-12-31 13:13:02 +09:00
phidias 15141e98e0 . 2024-12-31 02:58:15 +09:00
phidias 51961d2aea . 2024-12-31 00:50:23 +09:00
phidias 3266f444cd . 2024-12-31 00:49:15 +09:00
phidias c5395e37b2 . 2024-12-31 00:45:23 +09:00
phidias d2e2e2d6c9 . 2024-12-31 00:28:24 +09:00
phidias bd5b217909 . 2024-12-30 22:38:40 +09:00
phidiasandGitHub 5ae20ac21c Update types.lua 2024-12-30 19:55:13 +09:00
phidiasandGitHub 3830a464f8 Update types.lua 2024-12-30 19:52:55 +09:00
phidias 4362b48106 . 2024-12-30 19:00:02 +09:00
phidias 43f61909e8 . 2024-12-30 18:25:03 +09:00
phidias 596ea47082 . 2024-12-30 16:30:41 +09:00
phidias 80cd1245e5 . 2024-12-30 16:09:23 +09:00
phidias 3e4318fcab . 2024-12-29 23:30:30 +09:00
phidias 47e900f735 . 2024-12-29 23:29:44 +09:00
phidias d88def830f . 2024-12-29 14:16:50 +09:00
phidias b32e854398 . 2024-12-29 14:12:50 +09:00
phidias e9f82db154 . 2024-12-29 14:10:55 +09:00
phidias 0a4b392cf3 . 2024-12-29 14:09:39 +09:00
phidias 87dbf0db57 . 2024-12-29 14:06:16 +09:00
phidias f5d548e709 . 2024-12-29 14:04:13 +09:00
207 changed files with 11207 additions and 9327 deletions
-34
View File
@@ -1,34 +0,0 @@
{
"$schema": "https://raw.githubusercontent.com/EmmyLuaLs/emmylua-analyzer-rust/refs/heads/main/crates/emmylua_code_analysis/resources/schema.json",
"runtime": {
"version": "Lua5.2",
"requirePattern": [ "?", "?.lua" ]
},
"workspace": {
"$comment-moduleMap": "Replaces the fmtk --clusterio-modules plugin, rewrites each file's own module path rather than the require string",
"$comment-library": "clusterio is outside this repo, set CLUSTERIO to your checkout. The factorio typedefs come from .luarc.json, which the factoriomod-debug extension manages",
"library": [ "$CLUSTERIO/packages/host" ],
"ignoreDir": [ ".github", "web", "dist", "factorio" ],
"ignoreGlobs": [ "**/node_modules/**", "**/dist/**", "**/campaigns/**" ],
"moduleMap": [
{ "pattern": "^([^.]+)\\.module\\.module_exports$", "replace": "modules.$1" },
{ "pattern": "^([^.]+)\\.module\\.(.+)$", "replace": "modules.$1.$2" }
]
},
"doc": {
"privateName": [ "__*" ]
},
"$comment-disable": "Carried over from the luals config, which had these as editor only rather than checked in ci",
"$comment-unnecessary-if": "Disabled by the config fmtk generates for itself, the api union types make it unreliable",
"diagnostics": {
"globals": [ "__DebugAdapter", "__Profiler" ],
"disable": [
"assign-type-mismatch",
"param-type-mismatch",
"preferred-local-alias",
"unnecessary-assert",
"unnecessary-if",
"unused"
]
}
}
+34 -52
View File
@@ -1,94 +1,76 @@
name: Lua Lint
name: FMTK Lint
on:
push:
pull_request:
env:
EMMYLUA_CHECK_VERSION: "0.24.0"
CLUSTERIO: ${{ github.workspace }}/.clusterio
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Checkout clusterio modules
uses: actions/checkout@v4
with:
repository: clusterio/clusterio
path: .clusterio
sparse-checkout: packages/host/modules
sparse-checkout-cone-mode: false
- uses: pnpm/action-setup@v4
with:
version: 10
- uses: actions/setup-node@v4
with:
node-version: lts/*
- name: Generate Factorio typedefs
cache: "pnpm"
- name: Install FMTK
run: |
pnpm install factoriomod-debug
pnpm exec fmtk luals-addon "$RUNNER_TEMP"
- name: Install emmylua_check
pnpm exec fmtk luals-addon
jq '.["workspace.library"] += ["${{ github.workspace }}/factorio/library"] | .["runtime.plugin"] = "${{ github.workspace }}/factorio/plugin.lua"' .luarc.json > temp.luarc.json
jq -s '.[0] * .[1].settings' temp.luarc.json ${{ github.workspace }}/factorio/config.json > check.luarc.json
- name: Install LuaLS
run: |
curl -fsSL "https://github.com/EmmyLuaLs/emmylua-analyzer-rust/releases/download/${EMMYLUA_CHECK_VERSION}/emmylua_check-linux-x64.tar.gz" \
| tar -xz -C "$RUNNER_TEMP"
wget https://github.com/LuaLS/lua-language-server/releases/download/3.15.0/lua-language-server-3.15.0-linux-x64.tar.gz -q -O lusls.tar.gz
mkdir luals && tar -xf lusls.tar.gz -C luals && rm lusls.tar.gz
- name: Run Lint Report
shell: bash
run: |
# The typedefs are generated per run, clusterio comes from CLUSTERIO above
jq -n --arg lib "$RUNNER_TEMP/factorio/library" \
'{ workspace: { library: [ $lib ], ignoreGlobs: [ ".clusterio/**" ] } }' \
> "$RUNNER_TEMP/ci.emmyrc.json"
# Exits non zero on error severity, so the gate below is used instead
"$RUNNER_TEMP/emmylua_check" . --config ".emmyrc.json,$RUNNER_TEMP/ci.emmyrc.json" \
--output-format json --output check.json || true
./luals/bin/lua-language-server --check=. --logpath=. --configpath=check.luarc.json --checklevel=Information --check_out_path=check.json
# Credit to https://github.com/Krealle/luals-check-action/blob/main/action.yml
# Although some minor fixes were needed
# Format and print the messages (would be nice to format as a table, but that's too complex for jq)
jq -r --arg root "$PWD/" \
'.[] | (.file | ltrimstr($root)) as $file | .diagnostics[] |
cat check.json | \
jq -r \
'to_entries | map(.key as $file | .value[] |
{
file: $file,
file: $file | sub("file://${{ github.workspace }}/./"; ""),
code: .code,
line: .range.start.line,
message: (.message | split("\n")
| map(if startswith("- ") then .[2:] else . end)
| join("\n")),
severity: (if .severity <= 1 then "ERR" else "WARN" end)
} |
"**[\(.severity)] \(.code):** \(.file)#L\(.line + 1)<br><code>\(.message)</code>"' \
check.json > "$GITHUB_STEP_SUMMARY"
}) |
map("**[\(.severity)] \(.code):** \(.file)#L\(.line)<br><code>\(.message)</code>") | .[]' \
> $GITHUB_STEP_SUMMARY
echo ""
# Github Annotations (limited to 10)
jq -r --arg root "$PWD/" \
'.[] | (.file | ltrimstr($root)) as $file | .diagnostics[] |
{
file: $file,
title: .code,
line: .range.start.line,
endLine: .range.end.line,
col: .range.start.character,
endColumn: .range.end.character,
message: (.message | gsub("\n"; "%0A"; "m")),
level: (if .severity <= 1 then "error" else "warning" end)
} |
"::\(.level) file=\(.file),line=\(.line + 1),endLine=\(.endLine + 1),col=\(.col + 1),endColumn=\(.endColumn + 1),title=\(.title)::\(.message) (\(.title))"' \
check.json
cat check.json | \
jq -r \
'to_entries | map(.key as $file | .value[] |
{
file: $file | sub("file://${{ github.workspace }}/./"; ""),
title: .code,
line: .range.start.line,
endLine: .range.end.line,
col: .range.start.character,
endColumn: .range.end.character,
message: .message | gsub("\n"; "%0A"; "m"),
level: (if .severity <= 1 then "error" else "warning" end)
}) |
map("::\(.level) file=\(.file),line=\(.line+1),endLine=\(.line+1),col=\(.col+1),endColumn=\(.endColumn+1),title=\(.title)::\(.message) (\(.title))") | .[]'
# An entry is emitted for every file checked, including clean ones, so
# the gate counts diagnostics and asserts the workspace was actually read
if [[ $(jq 'length' check.json) -lt 100 ]] ; then
echo "::error::Only $(jq 'length' check.json) files were checked, expected the whole workspace"
exit 1
elif [[ $(jq '[.[].diagnostics[]] | length' check.json) -gt 0 ]] ; then
if [[ $(wc -l < check.json) > 1 ]] ; then
exit 1
else
echo "✅ All checks succeeded!" | tee "$GITHUB_STEP_SUMMARY"
echo "✅ All checks succeeded!" | tee $GITHUB_STEP_SUMMARY
fi
+5 -1
View File
@@ -1,4 +1,8 @@
dist/
node_modules/
.vscode
.luarc.json
exp_util/node-compile-cache/
exp_commands/node-compile-cache/
exp_groups/node-compile-cache/
exp_legacy/node-compile-cache/
exp_scenario/node-compile-cache/
+103
View File
@@ -0,0 +1,103 @@
{
"$schema": "https://raw.githubusercontent.com/LuaLS/vscode-lua/master/setting/schema.json",
"workspace.ignoreDir": [
".vscode", ".github", "luals", "factorio"
],
"completion.requireSeparator": "/",
"runtime.pluginArgs": [
"--clusterio-modules"
],
"doc": {
"regengine": "glob",
"privateName": [ "__*" ],
"packageName": [ "_*" ]
},
"type": {
"checkTableShape": true,
"inferParamType": true
},
"$comment-name-style-check": "Disabled below until config issue fixed: https://github.com/LuaLS/lua-language-server/issues/2643",
"$comment-type-mismatch": "Disabled unless opened due to performance concerns, time reduced to 10s from 400s",
"diagnostics": {
"unusedLocalExclude": [ "_*", "i", "j", "k", "v" ],
"groupFileStatus": {
"ambiguity": "Any",
"await": "None",
"codestyle": "Opened",
"conventions": "Any",
"duplicate": "Any",
"global": "Any",
"luadoc": "Opened",
"redefined": "Opened",
"strict": "Any",
"strong": "Any",
"type-check": "Any",
"unbalanced": "Opened",
"unused": "Opened"
},
"neededFileStatus": {
"no-unknown": "None!",
"spell-check": "None!",
"name-style-check": "None!",
"assign-type-mismatch": "Opened!",
"return-type-mismatch": "Opened!",
"param-type-mismatch": "Opened!"
}
},
"nameStyle.config": {
"local_name_style": [{
"type" : "pattern",
"param": "_?_?(\\S*)",
"$1": "snake_case"
}],
"module_local_name_style": [{
"type" : "pattern",
"param": "_?_?(\\S*)",
"$1": "snake_case"
}, {
"type" : "pattern",
"param": "_?_?(\\S*)",
"$1": "upper_snake_case"
}, {
"type" : "pattern",
"param": "_?_?(\\S*)",
"$1": "pascal_case"
}],
"function_param_name_style": [{
"type" : "pattern",
"param": "_?_?(\\S*)",
"$1": "snake_case"
}],
"function_name_style": [{
"type" : "pattern",
"param": "_?_?(\\S*)",
"$1": "snake_case"
}],
"local_function_name_style": [{
"type" : "pattern",
"param": "_?_?(\\S*)",
"$1": "snake_case"
}],
"table_field_name_style": [{
"type" : "pattern",
"param": "_?_?(\\S*)",
"$1": "snake_case"
}],
"global_variable_name_style": [{
"type" : "pattern",
"param": "_?_?(\\S*)",
"$1": "snake_case"
}, {
"type" : "pattern",
"param": "_?_?(\\S*)",
"$1": "upper_snake_case"
}],
"module_name_style": [ "pascal_case", "snake_case" ],
"require_module_name_style": [ "pascal_case", "snake_case" ],
"class_name_style": "pascal_case",
"const_variable_name_style": "upper_snake_case"
},
"format.defaultConfig": {
"continuation_indent.in_expr": "0"
}
}
-2
View File
@@ -5,8 +5,6 @@ All are welcome to make bug reports, feature requests, and pull requests for our
For developers wanting to add features please follow these guidelines:
- All lua code is documented using ldoc.
- Set the `CLUSTERIO` environment variable to your clusterio development install path, the lint needs it to resolve `modules/clusterio/*`.
- Lua is checked with [emmylua](https://github.com/EmmyLuaLs/emmylua-analyzer-rust), configured by `.emmyrc.json`. Install the EmmyLua extension rather than sumnekolua / luals, and use factoriomod-debug to generate the factorio api types. Note that an inline cast must be written `--[[@as T]]`, the spaced form is not parsed.
- Changes should be made on your own fork and merged into `main` through a pull request.
- Each pull request should be limited to one feature or a few bug fixes and link to the related issue page.
- Pull requests are automatically linted and documentation checked.
+3 -3
View File
@@ -10,7 +10,7 @@ The default permission authorities controlled by the flags: admin_only, system_o
local Storage = require("modules/exp_util/storage")
local Commands = require("modules/exp_commands") --- @class (partial) Commands
local Commands = require("modules/exp_commands") --- @class Commands
local add, allow, deny = Commands.add_permission_authority, Commands.status.success, Commands.status.unauthorised
local authorities = {}
@@ -28,13 +28,13 @@ end)
--- Allow a player access to system commands, use for debug purposes only
--- @param player_name string? The name of the player to give access to, default is the current player
function Commands.unlock_system_commands(player_name)
system_players[player_name or assert(game.player).name] = true
system_players[player_name or game.player.name] = true
end
--- Remove access from system commands for a player, use for debug purposes only
--- @param player_name string? The name of the player to give access to, default is the current player
function Commands.lock_system_commands(player_name)
system_players[player_name or assert(game.player).name] = nil
system_players[player_name or game.player.name] = nil
end
--- Get a list of all players who have system commands unlocked
+6 -6
View File
@@ -25,18 +25,18 @@ end)
--- @param page_size number The number of requests to show per page
--- @return LocalisedString[][], number
local function format_as_pages(commands, page_size)
local current_page = {}
local pages = { current_page }
local pages = { {} }
local page_length = 0
local current_page = 1
local total = 0
for _, command in pairs(commands) do
total = total + 1
page_length = page_length + 1
if page_length > page_size then
current_page = current_page + 1
pages[current_page] = {}
page_length = 1
current_page = {}
pages[#pages + 1] = current_page
end
local description
@@ -48,7 +48,7 @@ local function format_as_pages(commands, page_size)
end
local aliases = #command.aliases > 0 and { "exp-commands_help.aliases", table.concat(command.aliases, ", ") } or ""
current_page[page_length] = { "exp-commands_help.format", command.name, description, aliases }
pages[current_page][page_length] = { "exp-commands_help.format", command.name, description, aliases }
end
return pages, total
@@ -71,7 +71,7 @@ Commands.new("commands", { "exp-commands_help.description" })
page = as_number
end
keyword = tostring(keyword):lower()
keyword = keyword:lower()
local pages, found
if cache and cache.keyword == keyword then
-- Cached value found, no search is needed
+3 -1
View File
@@ -12,7 +12,7 @@ local ExpUtil = require("modules/exp_util")
local Async = require("modules/exp_util/async")
local Storage = require("modules/exp_util/storage")
local Commands = require("modules/exp_commands") --- @class (partial) Commands
local Commands = require("modules/exp_commands") --- @class Commands
local rcon_env = {} --- @type table<string, any>
local rcon_static = {} --- @type table<string, any>
@@ -21,10 +21,12 @@ setmetatable(rcon_static, { __index = _G })
setmetatable(rcon_env, { __index = rcon_static })
--- Some common static values which can be added now
--- @diagnostic disable: name-style-check
rcon_static.Async = Async
rcon_static.ExpUtil = ExpUtil
rcon_static.Commands = Commands
rcon_static.print = Commands.print
--- @diagnostic enable: name-style-check
--- Some common callback values which are useful when a player uses the command
--- @alias ExpCommand.RconDynamic fun(player: LuaPlayer?): any
+1 -1
View File
@@ -19,7 +19,7 @@ Commands.new("_sudo", { "exp-commands_sudo.description" })
--- @cast command ExpCommand
--- @cast parameter string
--- @diagnostic disable-next-line: access-invisible
--- @diagnostic disable-next-line: invisible
return Commands._event_handler{
name = command.name,
tick = game.tick,
+1 -1
View File
@@ -25,7 +25,7 @@ local Commands = require("modules/exp_commands")
local add, parse = Commands.add_data_type, Commands.parse_input
local valid, invalid = Commands.status.success, Commands.status.invalid_input
local types = {} --- @class (partial) Commands.types
local types = {} --- @class Commands.types
--- A boolean value where true is one of: yes, y, true, 1
types.boolean =
+1
View File
@@ -28,6 +28,7 @@ force=Invalid Force Name.
surface=Invalid Surface Name.
planet=Invalid Planet Name.
color=Invalid Color Name.
role=Invalid Role Name.
[exp-commands-authorities]
character-only=This command can not be used in remote view.
+1
View File
@@ -28,6 +28,7 @@ force=勢力名稱錯誤。
surface=地表名稱錯誤。
planet=星球名稱錯誤。
color=顏色名稱錯誤。
role=權限組名稱錯誤。
[exp-commands-authorities]
character-only=該命令不能在遠端視圖中使用。
+1
View File
@@ -28,6 +28,7 @@ force=勢力名稱錯誤。
surface=地表名稱錯誤。
planet=星球名稱錯誤。
color=顏色名稱錯誤。
role=權限組名稱錯誤。
[exp-commands-authorities]
character-only=該命令不能在遠端視圖中使用。
+14 -18
View File
@@ -61,7 +61,7 @@ local Search = require("modules/exp_commands/search")
--- @type LuaPlayer?
local _print_player
--- @class (partial) Commands
--- @class Commands
local Commands = {
color = ExpUtil.color,
format_rich_text_color = ExpUtil.format_rich_text_color,
@@ -84,7 +84,7 @@ local Commands = {
--- Contains the different status values a command can return
Commands.status = {}
--- @class (partial) Commands.types: table<string, Commands.InputParser<any> | Commands.InputParserFactory<any>>
--- @class Commands.types: table<string, Commands.InputParser | Commands.InputParserFactory>
--- Stores all input parsers and validators for different data types
Commands.types = {}
@@ -103,7 +103,7 @@ end
--- @class Commands.Argument
--- @field name string The name of the argument
--- @field description LocalisedString? The description of the argument
--- @field input_parser Commands.InputParser<any> The input parser for the argument
--- @field input_parser Commands.InputParser The input parser for the argument
--- @field optional boolean True when the argument is optional
--- @field default any? The default value of the argument
@@ -159,7 +159,7 @@ Commands.server = setmetatable({
Commands.error("Command does not support rcon usage, requires LuaPlayer." .. key)
error("Command does not support rcon usage, requires LuaPlayer." .. key)
end,
}) --[[@as LuaPlayer]]
}) --[[ @as LuaPlayer ]]
--- Status Returns.
-- Return values used by command callbacks
@@ -170,8 +170,7 @@ Commands.server = setmetatable({
--- @param msg LocalisedString? An optional message to be included when a command completes (only has an effect in command callbacks)
--- @return Commands.Status, LocalisedString # Should be returned directly without modification
function Commands.status.success(msg)
if msg == nil then return Commands.status.success, { "exp-commands.success" } end
return Commands.status.success, msg
return Commands.status.success, msg == nil and { "exp-commands.success" } or msg
end
--- Used to signal an error has occurred in a command, data type parser, or permission authority
@@ -179,8 +178,7 @@ end
--- @param msg LocalisedString? An optional error message to be included in the output, a generic message is used if not provided
--- @return Commands.Status, LocalisedString # Should be returned directly without modification
function Commands.status.error(msg)
if msg == nil then msg = { "exp-commands.error-default" } end
return Commands.status.error, { "exp-commands.error", msg }
return Commands.status.error, { "exp-commands.error", msg == nil and { "exp-commands.error-default" } or msg }
end
--- Used to signal the player is unauthorised to use a command, primarily used by permission authorities but can be used in a command callback
@@ -188,8 +186,7 @@ end
--- @param msg LocalisedString? An optional error message to be included in the output, a generic message is used if not provided
--- @return Commands.Status, LocalisedString # Should be returned directly without modification
function Commands.status.unauthorised(msg)
if msg == nil then msg = { "exp-commands.unauthorized-default" } end
return Commands.status.unauthorised, { "exp-commands.unauthorized", msg }
return Commands.status.unauthorised, { "exp-commands.unauthorized", msg == nil and { "exp-commands.unauthorized-default" } or msg }
end
--- Used to signal the player provided invalid input to an command, primarily used by data type parsers but can be used in a command callback
@@ -197,16 +194,15 @@ end
--- @param msg LocalisedString? An optional error message to be included in the output, a generic message is used if not provided
--- @return Commands.Status, LocalisedString # Should be returned directly without modification
function Commands.status.invalid_input(msg)
if msg == nil then return Commands.status.invalid_input, { "exp-commands.invalid-input" } end
return Commands.status.invalid_input, msg
return Commands.status.invalid_input, msg == nil and { "exp-commands.invalid-input" } or msg
end
--- Used to signal an internal error has occurred, this is reserved for internal use only
--- @param msg LocalisedString? A message detailing the error which has occurred, will be logged and outputted
--- @param msg LocalisedString A message detailing the error which has occurred, will be logged and outputted
--- @return Commands.Status, LocalisedString # Should be returned directly without modification
--- @package
function Commands.status.internal_error(msg)
return Commands.status.internal_error, { "exp-commands.internal-error", msg or "" }
return Commands.status.internal_error, { "exp-commands.internal-error", msg }
end
--- @type table<Commands.Status, string>
@@ -285,7 +281,7 @@ end
--- @alias Commands.InputParserFactory<T> fun(...: any): Commands.InputParser<T>
--- Add a new input parser to the command library, this method validates that it does not already exist
--- @generic T : Commands.InputParser<any> | Commands.InputParserFactory<any>
--- @generic T : Commands.InputParser | Commands.InputParserFactory
--- @param data_type string The name of the data type the input parser reads in and validates, becomes a key of Commands.types
--- @param input_parser T The function used to parse and validate the data type
--- @return T # The function which was provided as the second argument
@@ -299,7 +295,7 @@ function Commands.add_data_type(data_type, input_parser)
end
--- Remove an input parser for a data type, must be the same string that was passed to add_input_parser
--- @param data_type string | Commands.InputParser<any> | Commands.InputParserFactory<any> The data type or input parser you want to remove the input parser for
--- @param data_type string | Commands.InputParser | Commands.InputParserFactory The data type or input parser you want to remove the input parser for
function Commands.remove_data_type(data_type)
Commands.types[data_type] = nil
for k, v in pairs(Commands.types) do
@@ -437,7 +433,7 @@ end
--- Add a new required argument to the command of the given data type
--- @param name string The name of the argument being added
--- @param description LocalisedString? The description of the argument being added
--- @param input_parser Commands.InputParser<any> The input parser to be used for the argument
--- @param input_parser Commands.InputParser The input parser to be used for the argument
--- @return ExpCommand
function Commands._prototype:argument(name, description, input_parser)
assert_command_mutable(self)
@@ -458,7 +454,7 @@ end
--- Add a new optional argument to the command of the given data type
--- @param name string The name of the argument being added
--- @param description LocalisedString? The description of the argument being added
--- @param input_parser Commands.InputParser<any> The input parser to be used for the argument
--- @param input_parser Commands.InputParser The input parser to be used for the argument
--- @return ExpCommand
function Commands._prototype:optional(name, description, input_parser)
assert_command_mutable(self)
+2 -3
View File
@@ -3,7 +3,7 @@ local Search = {}
local Storage = require("modules/exp_util/storage")
--- Setup the storage to contain the pending translations and the completed ones
local pending = {} --- @type table<uint, [string, string]>
local pending = {} --- @type { [1]: string, [2]: string }[]
local translations = {} --- @type table<string, table<string, string>>
Storage.register({
pending,
@@ -50,7 +50,6 @@ function Search.prepare(custom_commands)
name = name,
description = locale_desc,
help_text = locale_desc,
usage = locale_desc,
aliases = {},
}
end
@@ -66,7 +65,7 @@ function Search.on_player_locale_changed(event)
local ids = player.request_translations(required_translations)
assert(ids, "Translation ids was nil")
for i, command_name in ipairs(command_names) do
pending[assert(ids[i])] = { locale, command_name }
pending[ids[i]] = { locale, command_name }
end
end
end
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@expcluster/lib_commands",
"version": "6.5.0",
"version": "0.1.0",
"description": "Clusterio plugin providing a Lua command processing library.",
"author": "Cooldude2606 <https://github.com/Cooldude2606>",
"license": "MIT",
@@ -13,10 +13,10 @@
"node": ">=18"
},
"peerDependencies": {
"@clusterio/lib": "workspace:^"
"@clusterio/lib": "catalog:"
},
"devDependencies": {
"@clusterio/lib": "workspace:^",
"@clusterio/lib": "catalog:",
"@types/node": "catalog:",
"typescript": "catalog:",
"webpack": "catalog:",
-434
View File
@@ -1,434 +0,0 @@
import { BaseControllerPlugin } from "@clusterio/controller";
import * as lib from "@clusterio/lib";
import * as messages from "./messages";
import * as path from "node:path";
export class ControllerPlugin extends BaseControllerPlugin {
groups!: lib.SubscribableDatastore<messages.GroupRecord>;
roleMappings!: lib.SubscribableDatastore<messages.RoleMappingRecord>;
manualAssignments!: lib.SubscribableDatastore<messages.AssignmentRecord>;
resolvedAssignments!: lib.SubscribableDatastore<messages.AssignmentRecord>;
async init() {
const databaseDirectory = this.controller.config.get("controller.database_directory");
this.groups = new lib.SubscribableDatastore(
...await new lib.JsonIdDatastoreProvider(
path.join(databaseDirectory, "exp_groups", "groups.json"),
messages.GroupRecord.fromJSON.bind(messages.GroupRecord),
).bootstrap()
);
this.roleMappings = new lib.SubscribableDatastore(
...await new lib.JsonIdDatastoreProvider(
path.join(databaseDirectory, "exp_groups", "role_mappings.json"),
messages.RoleMappingRecord.fromJSON.bind(messages.RoleMappingRecord),
).bootstrap()
);
this.manualAssignments = new lib.SubscribableDatastore(
...await new lib.JsonIdDatastoreProvider(
path.join(databaseDirectory, "exp_groups", "assignments.json"),
messages.AssignmentRecord.fromJSON.bind(messages.AssignmentRecord),
).bootstrap()
);
this.resolvedAssignments = new lib.SubscribableDatastore();
this.controller.subscriptions.handle(messages.GroupUpdatedEvent, this.handleGroupSubscription.bind(this));
this.controller.subscriptions.handle(messages.RoleMappingUpdatedEvent, this.handleRoleMappingSubscription.bind(this));
this.controller.subscriptions.handle(messages.ManualAssignmentUpdatedEvent, this.handleManualAssignmentSubscription.bind(this));
this.controller.subscriptions.handle(messages.ResolvedAssignmentUpdatedEvent, this.handleResolvedAssignmentSubscription.bind(this));
this.groups.on("update", this.groupsUpdated.bind(this));
this.roleMappings.on("update", this.roleMappingsUpdated.bind(this));
this.manualAssignments.on("update", this.manualAssignmentsUpdated.bind(this));
this.resolvedAssignments.on("update", this.resolvedAssignmentsUpdated.bind(this));
this.controller.handle(messages.GroupCreateRequest, this.handleGroupCreateRequest.bind(this));
this.controller.handle(messages.GroupUpdateRequest, this.handleGroupUpdateRequest.bind(this));
this.controller.handle(messages.GroupDeleteRequest, this.handleGroupDeleteRequest.bind(this));
this.controller.handle(messages.GroupGetRequest, this.handleGroupGetRequest.bind(this));
this.controller.handle(messages.GroupListRequest, this.handleGroupListRequest.bind(this));
this.controller.handle(messages.AssignmentCreateRequest, this.handleAssignmentCreateRequest.bind(this));
this.controller.handle(messages.AssignmentUpdateRequest, this.handleAssignmentUpdateRequest.bind(this));
this.controller.handle(messages.AssignmentDeleteRequest, this.handleAssignmentDeleteRequest.bind(this));
this.controller.handle(messages.AssignmentGetRequest, this.handleAssignmentGetRequest.bind(this));
this.controller.handle(messages.AssignmentListRequest, this.handleAssignmentListRequest.bind(this));
this.controller.handle(messages.RoleMappingCreateRequest, this.handleRoleMappingCreateRequest.bind(this));
this.controller.handle(messages.RoleMappingUpdateRequest, this.handleRoleMappingUpdateRequest.bind(this));
this.controller.handle(messages.RoleMappingDeleteRequest, this.handleRoleMappingDeleteRequest.bind(this));
this.controller.handle(messages.RoleMappingGetRequest, this.handleRoleMappingGetRequest.bind(this));
this.controller.handle(messages.RoleMappingListRequest, this.handleRoleMappingListRequest.bind(this));
}
async onShutdown() {
await Promise.all([
this.groups.save(),
this.manualAssignments.save(),
this.roleMappings.save(),
])
}
/*
Subscriptions
*/
async groupsUpdated(groups: messages.GroupRecord[]) {
this.controller.subscriptions.broadcast(new messages.GroupUpdatedEvent(groups));
// We need to do extra work if the group was deleted
const deletedGroupIds = groups.filter(g => g.isDeleted).map(g => g.id);
if (!deletedGroupIds.length) {
return;
}
// Cascade the delete down to affected role mappings
const mappingsToDelete = [];
for (const mapping of this.roleMappings.values()) {
if (deletedGroupIds.includes(mapping.groupId)) {
mappingsToDelete.push(mapping);
}
}
if (mappingsToDelete.length) {
this.roleMappings.deleteMany(mappingsToDelete);
}
// Cascade the delete down to affected manual assignments
const affectedPlayers = new Set<string>();
const assignmentsToDelete = [];
for (const assignment of this.manualAssignments.values()) {
if (deletedGroupIds.includes(assignment.groupId)) {
assignmentsToDelete.push(assignment);
affectedPlayers.add(assignment.name);
}
}
if (assignmentsToDelete.length) {
this.manualAssignments.deleteMany(assignmentsToDelete);
}
// Find all the affected players who were assigned to this group
for (const resolved of this.resolvedAssignments.values()) {
if (deletedGroupIds.includes(resolved.groupId)) {
affectedPlayers.add(resolved.name);
}
}
if (affectedPlayers.size) {
this.resolvedAssignments.setMany(await this.computeResolvedAssignments([...affectedPlayers]));
}
}
async handleGroupSubscription(request: lib.SubscriptionRequest) {
const groups = [...this.groups.values()]
.filter(group => group.updatedAtMs > request.lastRequestTimeMs);
return groups.length ? new messages.GroupUpdatedEvent(groups) : null;
}
async roleMappingsUpdated(roleMappings: messages.RoleMappingRecord[]) {
this.controller.subscriptions.broadcast(new messages.RoleMappingUpdatedEvent(roleMappings));
// Mappings pointing to a deleted group have already been handled
// But if any are active, then we still must recompute all assignments
let hasActiveGroup = false;
for (const roleMapping of roleMappings) {
const group = this.groups.get(roleMapping.groupId);
if (group && !group.isDeleted) {
hasActiveGroup = true;
break;
}
}
if (!hasActiveGroup) {
return;
}
// Affected players are those without manual assignments
const affectedPlayers = [];
for (const resolved of this.resolvedAssignments.values()) {
if (!this.manualAssignments.has(resolved.name)) {
affectedPlayers.push(resolved.name);
}
}
if (affectedPlayers.length) {
this.resolvedAssignments.setMany(await this.computeResolvedAssignments(affectedPlayers));
}
}
async handleRoleMappingSubscription(request: lib.SubscriptionRequest) {
const mappings = [...this.roleMappings.values()]
.filter(mapping => mapping.updatedAtMs > request.lastRequestTimeMs);
return mappings.length ? new messages.RoleMappingUpdatedEvent(mappings) : null;
}
async manualAssignmentsUpdated(assignments: messages.AssignmentRecord[]) {
this.controller.subscriptions.broadcast(new messages.ManualAssignmentUpdatedEvent(assignments));
// Assignments pointing to a deleted group have already been handled
const affectedPlayers: string[] = [];
for (const assignment of assignments) {
const group = this.groups.get(assignment.groupId);
if (!group || group.isDeleted) continue;
affectedPlayers.push(assignment.name);
}
if (affectedPlayers.length) {
this.resolvedAssignments.setMany(await this.computeResolvedAssignments(affectedPlayers));
}
}
async handleManualAssignmentSubscription(request: lib.SubscriptionRequest) {
const assignments = [...this.resolvedAssignments.values()]
.filter(a => a.updatedAtMs > request.lastRequestTimeMs);
return assignments.length ? new messages.ManualAssignmentUpdatedEvent(assignments) : null;
}
resolvedAssignmentsUpdated(assignments: messages.AssignmentRecord[]) {
this.controller.subscriptions.broadcast(
new messages.ResolvedAssignmentUpdatedEvent(assignments),
assignments.map(assignment => assignment.name),
);
}
async handleResolvedAssignmentSubscription(request: lib.SubscriptionRequest) {
// Check for any missing assignments to be computed on demand
const filters = request.filters.toJSON();
if (filters.length) {
const missing = filters.filter(name => !this.resolvedAssignments.has(name));
if (missing.length) {
this.resolvedAssignments.setMany(await this.computeResolvedAssignments(missing));
}
}
// Filter the assignments
const assignments = (filters.length
? filters.map(name => this.resolvedAssignments.get(name)).filter(Boolean)
: [...this.resolvedAssignments.values()]
).filter(a => a.updatedAtMs > request.lastRequestTimeMs);
return assignments.length ? new messages.ResolvedAssignmentUpdatedEvent(assignments) : null;
}
/*
Groups
*/
async handleGroupListRequest() {
return [...this.groups.values()];
}
async handleGroupCreateRequest(request: messages.GroupCreateRequest) {
if ([...this.groups.values()].some(g => g.name === request.name)) {
throw new lib.RequestError(`Group with name '${request.name}' already exists`);
}
let id = Math.random() * 2**31 | 0;
while (this.groups.has(id)) {
id = Math.random() * 2**31 | 0;
}
const group = new messages.GroupRecord(id, request.name, request.permissions);
this.groups.set(group);
return group;
}
async handleGroupUpdateRequest(request: messages.GroupUpdateRequest) {
const group = request.group;
if (group.id === undefined || !this.groups.has(group.id)) {
throw new lib.RequestError(`Group with ID ${group.id} does not exist`);
}
this.groups.set(group);
}
async handleGroupDeleteRequest(request: messages.GroupDeleteRequest) {
const { groupId } = request;
const group = this.groups.getMutable(groupId);
if (!group) {
throw new lib.RequestError(`Group with ID ${groupId} does not exist`);
}
this.groups.delete(group);
}
async handleGroupGetRequest(request: messages.GroupGetRequest) {
const group = this.groups.get(request.groupId);
if (!group) {
throw new lib.RequestError(`Group with ID ${request.groupId} does not exist`);
}
return group;
}
/*
Groups
*/
async handleAssignmentListRequest() {
return [...this.manualAssignments.values()];
}
async handleAssignmentCreateRequest(request: messages.AssignmentCreateRequest) {
const { name, groupId } = request;
if (this.manualAssignments.has(name)) {
throw new lib.RequestError(`Assignment for '${name}' already exists`);
}
const assignment = new messages.AssignmentRecord(name, groupId);
this.manualAssignments.set(assignment);
return assignment;
}
async handleAssignmentUpdateRequest(request: messages.AssignmentUpdateRequest) {
const assignment = request.assignment;
if (!this.manualAssignments.has(assignment.name)) {
throw new lib.RequestError(`Assignment for '${assignment.name}' does not exist`);
}
this.manualAssignments.set(assignment);
}
async handleAssignmentDeleteRequest(request: messages.AssignmentDeleteRequest) {
const { name } = request;
const assignment = this.manualAssignments.getMutable(name);
if (!assignment) {
throw new lib.RequestError(`Assignment for '${name}' does not exist`);
}
this.manualAssignments.delete(assignment);
}
async handleAssignmentGetRequest(request: messages.AssignmentGetRequest) {
if (request.resolve) {
let assignment = this.resolvedAssignments.get(request.name);
if (!assignment) {
assignment = await this.computeResolvedAssignment(request.name);
this.resolvedAssignments.set(assignment);
}
return assignment;
}
const assignment = this.manualAssignments.get(request.name);
if (!assignment) {
throw new lib.RequestError(`Assignment for '${request.name}' does not exist`);
}
return assignment;
}
/*
Role mappings
*/
async handleRoleMappingListRequest() {
return [...this.roleMappings.values()];
}
async handleRoleMappingCreateRequest(request: messages.RoleMappingCreateRequest) {
let id = Math.random() * 2**31 | 0;
while (this.roleMappings.has(id)) {
id = Math.random() * 2**31 | 0;
}
let priority = request.priority;
const existing = new Set([...this.roleMappings.values()].map(m => m.priority));
while (existing.has(priority)) {
priority++;
}
const roleMapping = new messages.RoleMappingRecord(
id, new Set(request.roleIds), request.groupId, priority, request.enabled,
);
this.roleMappings.set(roleMapping);
return roleMapping;
}
async handleRoleMappingUpdateRequest(request: messages.RoleMappingUpdateRequest) {
const roleMapping = request.roleMapping;
if (roleMapping.id === undefined || !this.roleMappings.has(roleMapping.id)) {
throw new lib.RequestError(`Role mapping with ID ${roleMapping.id} does not exist`);
}
const existing = new Set(
[...this.roleMappings.values()]
.filter(m => m.id !== roleMapping.id)
.map(m => m.priority)
);
let priority = roleMapping.priority;
while (existing.has(priority)) {
priority++;
}
this.roleMappings.set(roleMapping);
}
async handleRoleMappingDeleteRequest(request: messages.RoleMappingDeleteRequest) {
const { id } = request;
const mapping = this.roleMappings.getMutable(id);
if (!mapping) {
throw new lib.RequestError(`Role mapping with ID ${id} does not exist`);
}
this.roleMappings.delete(mapping);
}
async handleRoleMappingGetRequest(request: messages.RoleMappingGetRequest) {
const mapping = this.roleMappings.get(request.id);
if (!mapping) {
throw new lib.RequestError(`Role mapping with ID ${request.id} does not exist`);
}
return mapping;
}
/*
Calculating assignments
*/
async computeResolvedAssignment(playerName: string): Promise<messages.AssignmentRecord> {
// 1) Manual override
const manual = this.manualAssignments.get(playerName);
if (manual) {
return manual;
}
const user = this.controller.users.getByName(playerName);
const userRoles = user?.roleIds ?? new Set<number>();
// 2) Role mappings
let best: messages.RoleMappingRecord | null = null;
for (const mapping of this.roleMappings.values()) {
if (!mapping.enabled) continue;
let matches = true;
for (const roleId of mapping.roleIds) {
if (!userRoles.has(roleId)) {
matches = false;
break;
}
}
if (!matches) continue;
if (!best || mapping.priority > best.priority) {
best = mapping;
}
}
if (best) {
return new messages.AssignmentRecord(playerName, best.groupId);
}
// 3) Default (deleted assignment, assigns to 'Default' in game)
return new messages.AssignmentRecord(playerName, 0, 0, true);
}
async computeResolvedAssignments(playerNames: string[]): Promise<messages.AssignmentRecord[]> {
return Promise.all(playerNames.map(name => this.computeResolvedAssignment(name)));
}
}
-183
View File
@@ -1,183 +0,0 @@
import * as lib from "@clusterio/lib";
import * as messages from "./messages";
declare module "@clusterio/lib" {
export interface InstanceConfigFields {
"exp_groups.sync_mode": "enabled" | "disabled" | "bidirectional"
}
export interface ControllerConfigFields {
}
}
// Group permissions
lib.definePermission({
name: "exp_groups.group.get",
title: "Get Groups",
description: "Retrieve a specific Factorio permission group by ID.",
grantByDefault: true,
});
lib.definePermission({
name: "exp_groups.group.list",
title: "List Groups",
description: "List all Factorio permission groups.",
grantByDefault: true,
});
lib.definePermission({
name: "exp_groups.group.subscribe",
title: "Subscribe to Group Updates",
description: "Receive updates when Factorio permission groups change.",
grantByDefault: true,
});
lib.definePermission({
name: "exp_groups.group.create",
title: "Create Groups",
description: "Create new Factorio permission groups.",
grantByDefault: false,
});
lib.definePermission({
name: "exp_groups.group.update",
title: "Update Groups",
description: "Modify existing Factorio permission groups.",
grantByDefault: false,
});
lib.definePermission({
name: "exp_groups.group.delete",
title: "Delete Groups",
description: "Delete Factorio permission groups.",
grantByDefault: false,
});
// Assignment permissions
lib.definePermission({
name: "exp_groups.assignment.get",
title: "Get Assignments",
description: "Retrieve a specific manual group assignment for a player.",
grantByDefault: true,
});
lib.definePermission({
name: "exp_groups.assignment.list",
title: "List Assignments",
description: "List all manual group assignments.",
grantByDefault: true,
});
lib.definePermission({
name: "exp_groups.assignment.subscribe",
title: "Subscribe to Assignment Updates",
description: "Receive updates when manual group assignments change.",
grantByDefault: true,
});
lib.definePermission({
name: "exp_groups.assignment.create",
title: "Create Assignments",
description: "Manually assign players to groups, overriding role mappings.",
grantByDefault: false,
});
lib.definePermission({
name: "exp_groups.assignment.update",
title: "Update Assignments",
description: "Modify existing manual group assignments.",
grantByDefault: false,
});
lib.definePermission({
name: "exp_groups.assignment.delete",
title: "Delete Assignments",
description: "Remove manual group assignments.",
grantByDefault: false,
});
// Role mapping permissions
lib.definePermission({
name: "exp_groups.role_mapping.get",
title: "Get Role Mappings",
description: "Retrieve a specific role mapping rule.",
grantByDefault: true,
});
lib.definePermission({
name: "exp_groups.role_mapping.list",
title: "List Role Mappings",
description: "List all role mapping rules.",
grantByDefault: true,
});
lib.definePermission({
name: "exp_groups.role_mapping.subscribe",
title: "Subscribe to Role Mapping Updates",
description: "Receive updates when role mapping rules change.",
grantByDefault: true,
});
lib.definePermission({
name: "exp_groups.role_mapping.create",
title: "Create Role Mappings",
description: "Create rules that map user roles to Factorio permission groups.",
grantByDefault: false,
});
lib.definePermission({
name: "exp_groups.role_mapping.update",
title: "Update Role Mappings",
description: "Modify existing role mapping rules.",
grantByDefault: false,
});
lib.definePermission({
name: "exp_groups.role_mapping.delete",
title: "Delete Role Mappings",
description: "Delete role mapping rules.",
grantByDefault: false,
});
export const plugin: lib.PluginDeclaration = {
name: "exp_groups",
title: "ExpGaming - Permission Groups",
description: "Clusterio plugin providing syncing of permission groups",
features: [
"SavePatching",
"ScriptCommands",
],
messages: [
messages.GroupUpdatedEvent,
messages.ManualAssignmentUpdatedEvent,
messages.ResolvedAssignmentUpdatedEvent,
messages.RoleMappingUpdatedEvent,
messages.GroupCreateRequest,
messages.GroupUpdateRequest,
messages.GroupDeleteRequest,
messages.GroupGetRequest,
messages.GroupListRequest,
messages.AssignmentCreateRequest,
messages.AssignmentUpdateRequest,
messages.AssignmentDeleteRequest,
messages.AssignmentGetRequest,
messages.AssignmentListRequest,
messages.RoleMappingCreateRequest,
messages.RoleMappingUpdateRequest,
messages.RoleMappingDeleteRequest,
messages.RoleMappingGetRequest,
messages.RoleMappingListRequest,
],
instanceEntrypoint: "./dist/node/instance",
instanceConfigFields: {
"exp_groups.sync_mode": {
description: "Synchronize permission groups with the controller",
type: "string",
enum: ["disabled", "enabled", "bidirectional"],
initialValue: "bidirectional",
},
},
controllerEntrypoint: "./dist/node/controller",
controllerConfigFields: {
},
webEntrypoint: "./web",
routes: [
"/permission_groups",
"/permission_groups/:id/view",
]
};
-171
View File
@@ -1,171 +0,0 @@
import { BaseInstancePlugin } from "@clusterio/host";
import * as lib from "@clusterio/lib";
import * as messages from "./messages";
export type IpcGroupUpdated = {
group_name: string,
group_id: number | undefined,
permissions: { is_blacklist: boolean, permissions: string[] | undefined },
};
export type IpcGroupDeleted = {
group_name: string,
group_id: number | undefined,
};
export type IpcPlayerAssignments = {
assignments: Record<string, number>,
};
export class InstancePlugin extends BaseInstancePlugin {
// Once only, don't send permissions for these groups
// This is used for groups created on this instance that only need the controller generated id
skipSendingPermissions = new Set<string>();
// This is used for groups updated / deleted on this instance to stop cycles
skipSendingUpdate = new Set<number>();
// Track known online players so that we only apply assignment updates for them
onlinePlayers = new Set<string>();
async init() {
this.instance.handle(messages.GroupUpdatedEvent, this.handleGroupUpdatedEvent.bind(this));
this.instance.handle(messages.ResolvedAssignmentUpdatedEvent, this.handleResolvedAssignmentUpdatedEvent.bind(this));
this.instance.server.handle(`exp_group:group_updated`, this.handleGroupUpdatedIPC.bind(this))
this.instance.server.handle(`exp_group:group_deleted`, this.handleGroupDeletedIPC.bind(this))
this.instance.server.handle(`exp_group:player_assignments`, this.handlePlayerAssignmentsIPC.bind(this))
}
async onInstanceConfigFieldChanged(field: string, curr: unknown, prev: unknown) {
switch(field) {
case "exp_groups.sync_mode":
await this.luaSetEmitEvents(curr == "bidirectional")
break;
}
}
async onStart() {
// We use Date.now() because we need to manually initialise the groups on the lua side
await this.instance.sendTo("controller", new lib.SubscriptionRequest(
`exp_groups:${messages.GroupUpdatedEvent.name}`, "subscribe", Date.now()
));
const groups = await this.instance.sendTo("controller", new messages.GroupListRequest())
await this.luaSendInitialGroups(groups);
await this.luaSetEmitEvents(this.instance.config.get("exp_groups.sync_mode") == "bidirectional")
}
async onPlayerEvent(event: lib.PlayerEvent) {
switch(event.type) {
case "join":
this.onlinePlayers.add(event.name);
await this.subscribePlayerAssignment(event.name);
break;
case "leave":
this.onlinePlayers.delete(event.name);
await this.unsubscribePlayerAssignment(event.name);
break;
}
}
async handleGroupUpdatedEvent(event: messages.GroupUpdatedEvent) {
for (const group of event.updates) {
await this.luaSendGroupUpdate(group);
}
}
async handleResolvedAssignmentUpdatedEvent(event: messages.ManualAssignmentUpdatedEvent) {
for (const assignment of event.updates) {
if (this.onlinePlayers.has(assignment.name)) {
await this.luaSendAssignmentUpdate(assignment);
}
}
}
async handleGroupUpdatedIPC(event: IpcGroupUpdated) {
const permissions = new messages.GroupPermissions(
event.permissions.is_blacklist,
event.permissions.permissions ?? [],
)
if (event.group_id === undefined) {
this.skipSendingPermissions.add(event.group_name);
await this.instance.sendTo("controller",
new messages.GroupCreateRequest(event.group_name, permissions),
);
} else {
this.skipSendingUpdate.add(event.group_id);
await this.instance.sendTo("controller", new messages.GroupUpdateRequest(
new messages.GroupRecord(event.group_id, event.group_name, permissions),
));
}
}
async handleGroupDeletedIPC(event: IpcGroupDeleted) {
if (event.group_id === undefined) {
return;
}
this.skipSendingUpdate.add(event.group_id);
await this.instance.sendTo("controller", new messages.GroupDeleteRequest(event.group_id));
}
async handlePlayerAssignmentsIPC(event: IpcPlayerAssignments) {
await Promise.all(
Object.entries(event.assignments).map(([playerName, groupId]) =>
this.instance.sendTo("controller",
new messages.AssignmentUpdateRequest(new messages.AssignmentRecord(playerName, groupId)),
)
)
);
}
async subscribePlayerAssignment(playerName: string) {
await this.instance.sendTo("controller", new lib.SubscriptionRequest(
`exp_groups:${messages.ResolvedAssignmentUpdatedEvent.name}`, "subscribe", 0, lib.SubscriptionFilters.fromShorthand(playerName)
));
}
async unsubscribePlayerAssignment(playerName: string) {
await this.instance.sendTo("controller", new lib.SubscriptionRequest(
`exp_groups:${messages.ResolvedAssignmentUpdatedEvent.name}`, "unsubscribe", 0, lib.SubscriptionFilters.fromShorthand(playerName)
));
}
async luaSendInitialGroups(groups: messages.GroupRecord[]) {
if (this.instance.config.get("exp_groups.sync_mode") === "disabled") {
return;
}
await this.luaSend("initialise_groups", groups);
}
async luaSendGroupUpdate(group: messages.GroupRecord) {
if (this.instance.config.get("exp_groups.sync_mode") === "disabled") {
return;
}
if (this.skipSendingUpdate.has(group.id)) {
this.skipSendingUpdate.delete(group.id);
return;
}
const json = group.toJSON();
if (this.skipSendingPermissions.has(group.name)) {
this.skipSendingPermissions.delete(group.name);
delete (json as any).permissions;
}
await this.luaSend("receive_group_update", json);
}
async luaSendAssignmentUpdate(assignment: messages.AssignmentRecord) {
if (this.instance.config.get("exp_groups.sync_mode") === "disabled") {
return;
}
await this.luaSend("receive_assignment_update", assignment);
}
async luaSetEmitEvents(emitEvents: boolean) {
await this.luaSend("set_emit_events", emitEvents);
}
async luaSend(receiver: string, json: any) {
await this.instance.sendRcon(`/sc exp_groups.${receiver}(helpers.json_to_table[=[${JSON.stringify(json)}]=])`, true)
}
}
-692
View File
@@ -1,692 +0,0 @@
import * as lib from "@clusterio/lib";
import { Type, Static } from "@sinclair/typebox";
/*
Data records
*/
export class GroupPermissions {
constructor(
public isBlacklist: boolean,
public permissions: string[],
) {}
static jsonSchema = Type.Object({
is_blacklist: Type.Boolean(),
permissions: Type.Array(Type.String()),
});
toJSON(): Static<typeof GroupPermissions.jsonSchema> {
return {
is_blacklist: this.isBlacklist,
permissions: this.permissions,
};
}
static fromJSON(json: Static<typeof this.jsonSchema>) {
return new this(
json.is_blacklist,
json.permissions,
);
}
}
export class GroupRecord {
constructor(
public id: number,
public name: string,
public permissions: GroupPermissions,
public updatedAtMs: number = 0,
public isDeleted: boolean = false,
) {}
static jsonSchema = Type.Object({
id: Type.Integer(),
name: Type.String(),
permissions: GroupPermissions.jsonSchema,
updated_at_ms: Type.Optional(Type.Number()),
is_deleted: Type.Optional(Type.Boolean()),
});
toJSON() {
let json: Static<typeof GroupRecord.jsonSchema> = {
id: this.id,
name: this.name,
permissions: this.permissions.toJSON(),
};
if (this.updatedAtMs) {
json.updated_at_ms = this.updatedAtMs;
}
if (this.isDeleted) {
json.is_deleted = true;
}
return json;
}
static fromJSON(json: Static<typeof this.jsonSchema>) {
return new this(
json.id,
json.name,
GroupPermissions.fromJSON(json.permissions),
json.updated_at_ms ?? 0,
json.is_deleted ?? false,
);
}
}
export class AssignmentRecord {
constructor(
public name: string,
public groupId: number,
public updatedAtMs: number = 0,
public isDeleted: boolean = false,
) {}
get id() {
return this.name;
}
static jsonSchema = Type.Object({
name: Type.String(),
group_id: Type.Integer(),
updated_at_ms: Type.Optional(Type.Number()),
is_deleted: Type.Optional(Type.Boolean()),
});
toJSON(): Static<typeof AssignmentRecord.jsonSchema> {
let json: Static<typeof AssignmentRecord.jsonSchema> = {
name: this.name,
group_id: this.groupId,
};
if (this.updatedAtMs) {
json.updated_at_ms = this.updatedAtMs;
}
if (this.isDeleted) {
json.is_deleted = true;
}
return json;
}
static fromJSON(json: Static<typeof this.jsonSchema>) {
return new this(
json.name,
json.group_id,
json.updated_at_ms ?? 0,
json.is_deleted ?? false,
);
}
}
export class RoleMappingRecord {
constructor(
public id: number,
public roleIds: Set<number>,
public groupId: number,
public priority: number,
public enabled: boolean,
public updatedAtMs: number = 0,
public isDeleted: boolean = false,
) {}
static jsonSchema = Type.Object({
id: Type.Integer(),
role_ids: Type.Array(Type.Integer()),
group_id: Type.Integer(),
priority: Type.Number(),
enabled: Type.Boolean(),
updated_at_ms: Type.Optional(Type.Number()),
is_deleted: Type.Optional(Type.Boolean()),
});
toJSON(): Static<typeof RoleMappingRecord.jsonSchema> {
let json: Static<typeof RoleMappingRecord.jsonSchema> = {
id: this.id,
role_ids: [...this.roleIds],
group_id: this.groupId,
priority: this.priority,
enabled: this.enabled,
};
if (this.updatedAtMs) {
json.updated_at_ms = this.updatedAtMs;
}
if (this.isDeleted) {
json.is_deleted = true;
}
return json;
}
static fromJSON(json: Static<typeof this.jsonSchema>) {
return new this(
json.id,
new Set(json.role_ids),
json.group_id,
json.priority,
json.enabled,
json.updated_at_ms ?? 0,
json.is_deleted ?? false,
);
}
}
/*
Update events
*/
export class GroupUpdatedEvent {
declare ["constructor"]: typeof GroupUpdatedEvent;
static plugin = "exp_groups" as const;
static type = "event" as const;
static src = "controller" as const;
static dst = ["control", "instance"] as const;
static permission = "exp_groups.group.subscribe" as const;
constructor(
public updates: GroupRecord[],
) {}
static jsonSchema = Type.Object({
updates: Type.Array(GroupRecord.jsonSchema),
});
toJSON() {
return { updates: this.updates.map(group => group.toJSON()) };
}
static fromJSON(json: Static<typeof this.jsonSchema>) {
return new this(json.updates.map(group => GroupRecord.fromJSON(group)));
}
}
export class ManualAssignmentUpdatedEvent {
declare ["constructor"]: typeof ManualAssignmentUpdatedEvent;
static plugin = "exp_groups" as const;
static type = "event" as const;
static src = "controller" as const;
static dst = ["control", "instance"] as const;
static permission = "exp_groups.assignment.subscribe" as const;
constructor(
public updates: AssignmentRecord[],
) {}
static jsonSchema = Type.Object({
updates: Type.Array(AssignmentRecord.jsonSchema),
});
toJSON() {
return { updates: this.updates.map(assignment => assignment.toJSON()) };
}
static fromJSON(json: Static<typeof this.jsonSchema>) {
return new this(json.updates.map(assignment => AssignmentRecord.fromJSON(assignment)));
}
}
export class ResolvedAssignmentUpdatedEvent {
declare ["constructor"]: typeof ResolvedAssignmentUpdatedEvent;
static plugin = "exp_groups" as const;
static type = "event" as const;
static src = "controller" as const;
static dst = ["control", "instance"] as const;
static permission = "exp_groups.assignment.subscribe" as const;
constructor(
public updates: AssignmentRecord[],
) {}
static jsonSchema = Type.Object({
updates: Type.Array(AssignmentRecord.jsonSchema),
});
toJSON() {
return { updates: this.updates.map(assignment => assignment.toJSON()) };
}
static fromJSON(json: Static<typeof this.jsonSchema>) {
return new this(json.updates.map(assignment => AssignmentRecord.fromJSON(assignment)));
}
}
export class RoleMappingUpdatedEvent {
declare ["constructor"]: typeof RoleMappingUpdatedEvent;
static plugin = "exp_groups" as const;
static type = "event" as const;
static src = "controller" as const;
static dst = "control" as const;
static permission = "exp_groups.role_mapping.subscribe" as const;
constructor(
public updates: RoleMappingRecord[],
) {}
static jsonSchema = Type.Object({
updates: Type.Array(RoleMappingRecord.jsonSchema),
});
toJSON() {
return { updates: this.updates.map(roleMapping => roleMapping.toJSON()) };
}
static fromJSON(json: Static<typeof this.jsonSchema>) {
return new this(json.updates.map(roleMapping => RoleMappingRecord.fromJSON(roleMapping)));
}
}
/*
Group requests
*/
export class GroupCreateRequest {
declare ["constructor"]: typeof GroupCreateRequest;
static plugin = "exp_groups" as const;
static type = "request" as const;
static src = ["control", "instance"] as const;
static dst = "controller" as const;
static permission = "exp_groups.group.create" as const;
static Response = GroupRecord;
constructor(
public name: string,
public permissions: GroupPermissions,
) {}
static jsonSchema = Type.Object({
name: Type.String(),
permissions: GroupPermissions.jsonSchema,
});
toJSON() {
return {
name: this.name,
permissions: this.permissions.toJSON(),
};
}
static fromJSON(json: Static<typeof this.jsonSchema>) {
return new this(
json.name,
GroupPermissions.fromJSON(json.permissions),
);
}
}
export class GroupUpdateRequest {
declare ["constructor"]: typeof GroupUpdateRequest;
static plugin = "exp_groups" as const;
static type = "request" as const;
static src = ["control", "instance"] as const;
static dst = "controller" as const;
static permission = "exp_groups.group.update" as const;
constructor(
public group: GroupRecord,
) {}
static jsonSchema = Type.Object({
group: GroupRecord.jsonSchema,
});
toJSON() {
return {
group: this.group.toJSON(),
};
}
static fromJSON(json: Static<typeof this.jsonSchema>) {
return new this(
GroupRecord.fromJSON(json.group),
);
}
}
export class GroupDeleteRequest {
declare ["constructor"]: typeof GroupDeleteRequest;
static plugin = "exp_groups" as const;
static type = "request" as const;
static src = ["control", "instance"] as const;
static dst = "controller" as const;
static permission = "exp_groups.group.delete" as const;
constructor(
public groupId: number,
) {}
static jsonSchema = Type.Object({
group_id: Type.Integer(),
});
toJSON() {
return {
group_id: this.groupId,
};
}
static fromJSON(json: Static<typeof this.jsonSchema>) {
return new this(json.group_id);
}
}
export class GroupGetRequest {
declare ["constructor"]: typeof GroupGetRequest;
static plugin = "exp_groups" as const;
static type = "request" as const;
static src = ["control", "instance"] as const;
static dst = "controller" as const;
static permission = "exp_groups.group.get" as const;
static Response = GroupRecord;
constructor(
public groupId: number,
) {}
static jsonSchema = Type.Object({
group_id: Type.Integer(),
});
toJSON() {
return {
group_id: this.groupId,
};
}
static fromJSON(json: Static<typeof this.jsonSchema>) {
return new this(json.group_id);
}
}
export class GroupListRequest {
declare ["constructor"]: typeof GroupListRequest;
static plugin = "exp_groups" as const;
static type = "request" as const;
static src = ["control", "instance"] as const;
static dst = "controller" as const;
static permission = "exp_groups.group.list" as const;
static Response = lib.jsonArray(GroupRecord);
constructor() {}
}
/*
Assignment requests
*/
export class AssignmentCreateRequest {
declare ["constructor"]: typeof AssignmentCreateRequest;
static plugin = "exp_groups" as const;
static type = "request" as const;
static src = ["control", "instance"] as const;
static dst = "controller" as const;
static permission = "exp_groups.assignment.create" as const;
static Response = AssignmentRecord;
constructor(
public name: string,
public groupId: number,
) {}
static jsonSchema = Type.Object({
name: Type.String(),
group_id: Type.Integer(),
});
toJSON() {
return {
name: this.name,
group_id: this.groupId,
};
}
static fromJSON(json: Static<typeof this.jsonSchema>) {
return new this(json.name, json.group_id);
}
}
export class AssignmentUpdateRequest {
declare ["constructor"]: typeof AssignmentUpdateRequest;
static plugin = "exp_groups" as const;
static type = "request" as const;
static src = ["control", "instance"] as const;
static dst = "controller" as const;
static permission = "exp_groups.assignment.update" as const;
constructor(
public assignment: AssignmentRecord,
) {}
static jsonSchema = Type.Object({
assignment: AssignmentRecord.jsonSchema,
});
toJSON() {
return {
assignment: this.assignment.toJSON(),
};
}
static fromJSON(json: Static<typeof this.jsonSchema>) {
return new this(
AssignmentRecord.fromJSON(json.assignment),
);
}
}
export class AssignmentDeleteRequest {
declare ["constructor"]: typeof AssignmentDeleteRequest;
static plugin = "exp_groups" as const;
static type = "request" as const;
static src = ["control", "instance"] as const;
static dst = "controller" as const;
static permission = "exp_groups.assignment.delete" as const;
constructor(
public name: string,
) {}
static jsonSchema = Type.Object({
name: Type.String(),
});
toJSON() {
return {
name: this.name,
};
}
static fromJSON(json: Static<typeof this.jsonSchema>) {
return new this(json.name);
}
}
export class AssignmentGetRequest {
declare ["constructor"]: typeof AssignmentGetRequest;
static plugin = "exp_groups" as const;
static type = "request" as const;
static src = ["control", "instance"] as const;
static dst = "controller" as const;
static permission = "exp_groups.assignment.get" as const;
static Response = AssignmentRecord;
constructor(
public name: string,
public resolve: boolean = false,
) {}
static jsonSchema = Type.Object({
name: Type.String(),
resolve: Type.Boolean(),
});
toJSON() {
return {
name: this.name,
resolve: this.resolve,
};
}
static fromJSON(json: Static<typeof this.jsonSchema>) {
return new this(json.name, json.resolve);
}
}
export class AssignmentListRequest {
declare ["constructor"]: typeof AssignmentListRequest;
static plugin = "exp_groups" as const;
static type = "request" as const;
static src = ["control", "instance"] as const;
static dst = "controller" as const;
static permission = "exp_groups.assignment.list" as const;
static Response = lib.jsonArray(AssignmentRecord);
constructor() {}
}
/*
Role mapping requests
*/
export class RoleMappingCreateRequest {
declare ["constructor"]: typeof RoleMappingCreateRequest;
static plugin = "exp_groups" as const;
static type = "request" as const;
static src = ["control", "instance"] as const;
static dst = "controller" as const;
static permission = "exp_groups.role_mapping.create" as const;
static Response = RoleMappingRecord;
constructor(
public roleIds: number[],
public groupId: number,
public priority: number,
public enabled: boolean,
) {}
static jsonSchema = Type.Object({
role_ids: Type.Array(Type.Integer()),
group_id: Type.Integer(),
priority: Type.Number(),
enabled: Type.Boolean(),
});
toJSON() {
return {
role_ids: this.roleIds,
group_id: this.groupId,
priority: this.priority,
enabled: this.enabled,
};
}
static fromJSON(json: Static<typeof this.jsonSchema>) {
return new this(
json.role_ids,
json.group_id,
json.priority,
json.enabled,
);
}
}
export class RoleMappingUpdateRequest {
declare ["constructor"]: typeof RoleMappingUpdateRequest;
static plugin = "exp_groups" as const;
static type = "request" as const;
static src = ["control", "instance"] as const;
static dst = "controller" as const;
static permission = "exp_groups.role_mapping.update" as const;
constructor(
public roleMapping: RoleMappingRecord,
) {}
static jsonSchema = Type.Object({
role_mapping: RoleMappingRecord.jsonSchema,
});
toJSON() {
return {
role_mapping: this.roleMapping.toJSON(),
};
}
static fromJSON(json: Static<typeof this.jsonSchema>) {
return new this(
RoleMappingRecord.fromJSON(json.role_mapping),
);
}
}
export class RoleMappingDeleteRequest {
declare ["constructor"]: typeof RoleMappingDeleteRequest;
static plugin = "exp_groups" as const;
static type = "request" as const;
static src = ["control", "instance"] as const;
static dst = "controller" as const;
static permission = "exp_groups.role_mapping.delete" as const;
constructor(
public id: number,
) {}
static jsonSchema = Type.Object({
id: Type.Integer(),
});
toJSON() {
return {
id: this.id,
};
}
static fromJSON(json: Static<typeof this.jsonSchema>) {
return new this(json.id);
}
}
export class RoleMappingGetRequest {
declare ["constructor"]: typeof RoleMappingGetRequest;
static plugin = "exp_groups" as const;
static type = "request" as const;
static src = ["control", "instance"] as const;
static dst = "controller" as const;
static permission = "exp_groups.role_mapping.get" as const;
static Response = RoleMappingRecord;
constructor(
public id: number,
) {}
static jsonSchema = Type.Object({
id: Type.Integer(),
});
toJSON() {
return {
id: this.id,
};
}
static fromJSON(json: Static<typeof this.jsonSchema>) {
return new this(json.id);
}
}
export class RoleMappingListRequest {
declare ["constructor"]: typeof RoleMappingListRequest;
static plugin = "exp_groups" as const;
static type = "request" as const;
static src = ["control", "instance"] as const;
static dst = "controller" as const;
static permission = "exp_groups.role_mapping.list" as const;
static Response = lib.jsonArray(RoleMappingRecord);
constructor() {}
}
-438
View File
@@ -1,438 +0,0 @@
--[[-- ExpGroups
Adds permission group syncing to clusterio
]]
local clusterio_api = require("modules/clusterio/api")
local compat = require("modules/clusterio/compat") --[[@as LibCompat]]
--- Top level module table, contains event handlers and public methods
--- @class ExpGroups
local ExpGroups = {}
--- @class ExpPermissionGroups.GroupPermissions
--- @field is_blacklist boolean
--- @field permissions string[]?
--- @class ExpPermissionGroups.GroupRecord
--- @field id number
--- @field name string
--- @field permissions ExpPermissionGroups.GroupPermissions?
--- @field is_deleted boolean
--- @class ExpPermissionGroups.AssignmentRecord
--- @field name string
--- @field group_id number
--- @field is_deleted boolean
--- @class ExpPermissionGroups.ScriptData
--- @field factorio_to_clusterio_id table<number, number?>
--- @field clusterio_id_to_group table<number, LuaPermissionGroup?>
--- @field dirty_groups table<number, LuaPermissionGroup>
--- @field dirty_players table<string, LuaPlayer>
--- @field emit_updates boolean
local script_data = {}
local function setup_script_data()
if compat.script_data["exp_groups"] == nil then
--- @type ExpPermissionGroups.ScriptData
compat.script_data["exp_groups"] = {
factorio_to_clusterio_id = {},
clusterio_id_to_group = {},
dirty_groups = {},
dirty_players = {},
emit_updates = false,
}
end
ExpGroups.on_load()
end
--[[
Helper methods
]]
--- Get the default factorio permission group
--- @return LuaPermissionGroup
local function get_default_group()
return assert(game.permissions.get_group("Default"))
end
--- Move all players from one group to another
--- @param group_src LuaPermissionGroup
--- @param group_dst LuaPermissionGroup
local function move_players(group_src, group_dst)
local add_player = group_dst.add_player
for _, player in pairs(group_src.players) do
add_player(player)
end
end
--- Apply an encoded permission definition to a group
--- @param group LuaPermissionGroup
--- @param permissions ExpPermissionGroups.GroupPermissions
local function decode_group_permissions(group, permissions)
-- Construct a hash map for faster lookup
local action_map = {}
for _, input_action_name in pairs(assert(permissions.permissions)) do
action_map[input_action_name] = true
end
-- Apply the whitelist / backlist to the group
local is_blacklist = permissions.is_blacklist
local action_allowed = not is_blacklist
for input_action_name, input_action in pairs(defines.input_action) do
if action_map[input_action_name] then
group.set_allows_action(input_action, action_allowed)
else
group.set_allows_action(input_action, is_blacklist)
end
end
end
--- Encode the permissions of a group in a shorthand format
--- @param group LuaPermissionGroup
--- @return ExpPermissionGroups.GroupPermissions
local function encode_group_permissions(group)
local whitelist = {} --- @type string[]
local blacklist = {} --- @type string[]
local whitelist_index = 0
local blacklist_index = 0
-- Construct the whitelist and blacklist
local allows_action = group.allows_action
for input_action_name, input_action in pairs(defines.input_action) do
if allows_action(input_action) then
whitelist[whitelist_index] = input_action_name
whitelist_index = whitelist_index + 1
else
blacklist[blacklist_index] = input_action_name
blacklist_index = blacklist_index + 1
end
end
-- Return the whitelist if it is smaller
if blacklist_index > whitelist_index then
return whitelist_index > 0
and { is_blacklist = false, permissions = whitelist }
or { is_blacklist = false }
end
-- Otherwise return the blacklist as it is smaller
return blacklist_index > 0
and { is_blacklist = true, permissions = blacklist }
or { is_blacklist = true }
end
--[[
State handlers
]]
--- Update the factorio permission group
--- @param group_record ExpPermissionGroups.GroupRecord
local function update_group(group_record)
assert(not group_record.is_deleted)
-- Try find the group by id and then then name
local group = script_data.clusterio_id_to_group[group_record.id]
if not group then
group = game.permissions.get_group(group_record.name)
end
-- Create a new group or update the found group
if not group or not group.valid then
group = assert(game.permissions.create_group(group_record.name))
else
group.name = group_record.name
end
-- Update the permissions for the group
if group_record.permissions then
decode_group_permissions(group, group_record.permissions)
end
-- Update the script data
script_data.factorio_to_clusterio_id[group.group_id] = group_record.id
script_data.clusterio_id_to_group[group_record.id] = group
end
--- Delete the factorio permission group
--- @param group_record ExpPermissionGroups.GroupRecord
local function delete_group(group_record)
assert(group_record.is_deleted)
local default_group = get_default_group()
local group = script_data.clusterio_id_to_group[group_record.id]
if group then
move_players(group, default_group)
group.destroy()
end
end
--- Update an assignment by moving the player to their new group
--- @param assignment_record ExpPermissionGroups.AssignmentRecord
local function update_assignment(assignment_record)
assert(not assignment_record.is_deleted)
local group = script_data.clusterio_id_to_group[assignment_record.group_id]
local player = assert(game.get_player(assignment_record.name))
if group then
group.add_player(player)
end
end
--- Clear an assignment by moving the player to the default group
--- @param assignment_record ExpPermissionGroups.AssignmentRecord
local function delete_assignment(assignment_record)
assert(assignment_record.is_deleted)
local default_group = get_default_group()
local player = assert(game.get_player(assignment_record.name))
default_group.add_player(player)
end
--[[
Public methods
]]
--- Restore local references to persistent script data after load
function ExpGroups.on_load()
script_data = compat.script_data["exp_groups"]
end
--- Enable or disable emitting lua changes back to the instance plugin
--- @param enabled boolean?
function ExpGroups.set_emit_events(enabled)
script_data.emit_updates = enabled ~= false
end
--- Replace local state with expected controller state on startup
--- @param group_records ExpPermissionGroups.GroupRecord[]
function ExpGroups.initialise_groups(group_records)
local _emit_events = script_data.emit_updates
script_data.emit_updates = false
-- Update all the received groups
local seen_clusterio_ids = {} --- @type table<number, boolean>
for _, group_record in pairs(group_records) do
update_group(group_record)
seen_clusterio_ids[group_record.id] = true
end
-- Cleanup the script data, removing stale group ids
local factorio_to_clusterio_id = script_data.factorio_to_clusterio_id
local clusterio_id_to_group = script_data.clusterio_id_to_group
for factorio_id, clusterio_id in pairs(factorio_to_clusterio_id) do
if not seen_clusterio_ids[clusterio_id] then
factorio_to_clusterio_id[factorio_id] = nil
clusterio_id_to_group[clusterio_id] = nil
end
end
-- Remove all other groups
local default_group = get_default_group()
local default_group_id = default_group.group_id
for _, group in pairs(game.permissions.groups) do
if not factorio_to_clusterio_id[group.group_id] and group.group_id ~= default_group_id then
move_players(group, default_group)
group.destroy()
end
end
script_data.emit_updates = _emit_events
end
--- Receive an updated version of a group record
--- @param group_record ExpPermissionGroups.GroupRecord
function ExpGroups.receive_group_update(group_record)
local _emit_events = script_data.emit_updates
script_data.emit_updates = false
if group_record.is_deleted then
delete_group(group_record)
else
update_group(group_record)
end
script_data.emit_updates = _emit_events
end
--- Receive an updated version of a assignment record
--- @param assignment_record ExpPermissionGroups.AssignmentRecord
function ExpGroups.receive_assignment_update(assignment_record)
local _emit_events = script_data.emit_updates
script_data.emit_updates = false
if assignment_record.is_deleted then
delete_assignment(assignment_record)
else
update_assignment(assignment_record)
end
script_data.emit_updates = _emit_events
end
--- Get the current script data for debugging purposes
--- @package
function ExpGroups._script_data()
return script_data
end
--[[
IPC events
]]
--- Emit a group update to the instance plugin
--- @param group LuaPermissionGroup
local function emit_group_update(group)
clusterio_api.send_json("exp_group:group_updated", {
group_name = group.name,
group_id = script_data.factorio_to_clusterio_id[group.group_id],
permissions = encode_group_permissions(group),
})
end
--- Emit a group deletion to the instance plugin
--- @param group_id number
--- @param group_name string
local function emit_group_delete(group_id, group_name)
clusterio_api.send_json("exp_group:group_deleted", {
group_name = group_name,
group_id = script_data.factorio_to_clusterio_id[group_id],
})
end
--- Emit a player assignment to the instance plugin
--- @param assignments table<string, number>
local function emit_player_assignments(assignments)
clusterio_api.send_json("exp_group:player_assignments", {
assignments = assignments
})
end
--[[
IPC event queuing
]]
--- Mark a group as changed
--- @param group LuaPermissionGroup
local function mark_group_dirty(group)
script_data.dirty_groups[group.group_id] = group
end
--- Mark a player as changed
--- @param player LuaPlayer
local function mark_player_dirty(player)
script_data.dirty_players[player.name] = player
end
--- Flush queued group updates to the instance plugin
local function flush_group_updates()
-- Check if updates should be updated
if not script_data.emit_updates then
script_data.dirty_groups = {}
return
end
-- Get all the groups and emit their updates
for _, group in pairs(script_data.dirty_groups) do
if group.valid then
emit_group_update(group)
end
end
script_data.dirty_groups = {}
end
--- Flush queued player updates to the instance plugin
local function flush_player_updates()
-- Check if updates should be updated
if not script_data.emit_updates then
script_data.dirty_players = {}
return
end
-- Construct the update payload
local assignments = {} --- @type table<string, number>
for player_name, player in pairs(script_data.dirty_players) do
local group = player.valid and player.permission_group
if group then
assignments[player_name] = script_data.factorio_to_clusterio_id[group.group_id]
end
end
emit_player_assignments(assignments)
script_data.dirty_players = {}
end
--[[
Factorio events
]]
--- Handle clusterio server startup
local function on_server_startup()
setup_script_data()
end
--- Handle creation of permission groups
--- @param event EventData.on_permission_group_added
local function on_permission_group_added(event)
-- Check if updates should be updated
if not script_data.emit_updates then
return
end
mark_group_dirty(event.group)
end
--- Handle deletion of permission groups
--- @param event EventData.on_permission_group_deleted
local function on_permission_group_deleted(event)
-- Check if updates should be updated
if not script_data.emit_updates then
return
end
emit_group_delete(event.id, event.group_name)
end
--- Handle edits make to permission groups
--- @param event EventData.on_permission_group_edited
local function on_permission_group_edited(event)
-- Check if updates should be updated
if not script_data.emit_updates then
return
end
-- Check if this is a player or group event
if event.type == "add-player" or event.type == "remove-player" then
local player = assert(game.get_player(event.other_player_index))
mark_player_dirty(player)
else
mark_group_dirty(event.group)
end
end
--- Periodically flush queued changes
local function on_nth_tick_flush()
flush_group_updates()
flush_player_updates()
end
local e = defines.events
local events = {
[clusterio_api.events.on_server_startup] = on_server_startup,
[e.on_multiplayer_init] = on_server_startup,
[e.on_permission_group_added] = on_permission_group_added,
[e.on_permission_group_deleted] = on_permission_group_deleted,
[e.on_permission_group_edited] = on_permission_group_edited,
}
local on_nth_tick = {
[300] = on_nth_tick_flush,
}
ExpGroups.events = events --- @package
ExpGroups.on_nth_tick = on_nth_tick --- @package
return ExpGroups
-11
View File
@@ -1,11 +0,0 @@
--[[
It is best practice to not expose any globals because all modules share a global environment
However, sometimes you need globals, for example to access functions within rcon commands
Therefore, we advise that this should be the only file in your module to expose globals
Typically this would be your control file as shown in the example below
]]
--- @diagnostic disable: global-in-non-module
-- Access using `/sc exp_groups.foo()`
exp_groups = require("modules/exp_groups/control")
-12
View File
@@ -1,12 +0,0 @@
{
"name": "exp_groups",
"load": [
"control.lua"
],
"require": [
"globals.lua"
],
"dependencies": {
"clusterio": "*"
}
}
-3
View File
@@ -1,3 +0,0 @@
-- Access the exports from other modules using require("modules/exp_groups")
return require("modules/exp_groups/control")
+11 -19
View File
@@ -1,49 +1,41 @@
{
"name": "@expcluster/permission-groups",
"version": "6.5.0",
"description": "Clusterio plugin providing syncing of permission groups",
"author": "Cooldude2606 <https://github.com/Cooldude2606>",
"license": "MIT",
"repository": "explosivegaming/ExpCluster",
"name": "@expcluster/permission_groups",
"private": true,
"version": "0.0.0",
"description": "Example Description. Package. Change me in package.json",
"main": "dist/node/index.js",
"scripts": {
"prepare": "tsc --build && webpack-cli --env production"
"$prepare": "tsc --build && webpack-cli --env production"
},
"engines": {
"node": ">=18"
},
"peerDependencies": {
"@clusterio/controller": "workspace:^",
"@clusterio/host": "workspace:^",
"@clusterio/lib": "workspace:^",
"@clusterio/web_ui": "workspace:^"
"@clusterio/lib": "catalog:"
},
"devDependencies": {
"@ant-design/icons": "^6.2.3",
"@clusterio/controller": "workspace:^",
"@clusterio/host": "workspace:^",
"@clusterio/lib": "workspace:^",
"@clusterio/web_ui": "workspace:^",
"@clusterio/lib": "catalog:",
"@clusterio/web_ui": "catalog:",
"@types/fs-extra": "^11.0.4",
"@types/node": "catalog:",
"@types/react": "catalog:",
"antd": "catalog:",
"react": "catalog:",
"react-dom": "catalog:",
"react-router-dom": "catalog:",
"typescript": "catalog:",
"webpack": "catalog:",
"webpack-cli": "catalog:",
"webpack-merge": "catalog:"
},
"dependencies": {
"@sinclair/typebox": "catalog:"
"@sinclair/typebox": "catalog:",
"fs-extra": "^11.3.3"
},
"publishConfig": {
"access": "public"
},
"keywords": [
"clusterio",
"clusterio-plugin",
"factorio"
]
}
-4
View File
@@ -1,4 +0,0 @@
{
"extends": "../tsconfig.browser.json",
"include": [ "web/**/*.tsx", "web/**/*.ts", "messages.ts", "package.json" ],
}
-7
View File
@@ -1,7 +0,0 @@
{
"files": [],
"references": [
{ "path": "./tsconfig.node.json" },
{ "path": "./tsconfig.browser.json" }
]
}
-5
View File
@@ -1,5 +0,0 @@
{
"extends": "../tsconfig.node.json",
"include": ["./**/*.ts"],
"exclude": ["test/*", "./dist/*"],
}
@@ -1,74 +0,0 @@
import React, { useContext, useEffect } from "react";
import { Modal, Form, Select } from "antd";
import { ControlContext, useUsers } from "@clusterio/web_ui";
import * as messages from "../../messages";
import type { WebPlugin } from "..";
export default function AssignmentForm({ open, setOpen, initial }: {
open: boolean,
setOpen: (open: boolean) => void,
initial?: messages.AssignmentRecord,
}) {
const control = useContext(ControlContext);
const plugin = control.plugins.get("exp_groups") as WebPlugin;
const [groups] = plugin.useGroups();
const [users] = useUsers();
const [form] = Form.useForm();
function submit(values: any) {
if (initial) {
control.send(new messages.AssignmentUpdateRequest(
new messages.AssignmentRecord(
values.name,
values.groupId,
)
));
} else {
control.send(new messages.AssignmentCreateRequest(
values.name,
values.groupId,
));
}
setOpen(false);
}
useEffect(() => {
if (open) {
form.resetFields();
form.setFieldsValue(initial);
}
}, [open, initial]);
return <Modal
title={initial ? "Edit Assignment" : "Create Assignment"}
open={open}
onCancel={() => setOpen(false)}
onOk={() => form.submit()}
>
<Form form={form} onFinish={submit}>
<Form.Item name="name" label="Player" rules={[{ required: true }]}>
<Select disabled={Boolean(initial)} showSearch>
{[...users.values()].map(u => (
<Select.Option key={u.name} value={u.name}>
{u.name}
</Select.Option>
))}
</Select>
</Form.Item>
<Form.Item name="groupId" label="Group" rules={[{ required: true }]}>
<Select>
{[...groups.values()].map(g => (
<Select.Option key={g.id} value={g.id}>
{g.name}
</Select.Option>
))}
</Select>
</Form.Item>
</Form>
</Modal>;
}
@@ -1,111 +0,0 @@
import React, { useContext, useState, useRef } from "react";
import { Table, Button, Space, Input, InputRef } from "antd";
import { EditOutlined, SearchOutlined } from "@ant-design/icons";
import { ControlContext, useAccount } from "@clusterio/web_ui";
import { AssignmentDeleteRequest, AssignmentRecord } from "../../messages";
import type { WebPlugin } from "..";
import AssignmentForm from "./AssignmentForm";
import DeletedConfirm from "./DeleteConfirm";
const strcmp = new Intl.Collator(undefined, { numeric: true, sensitivity: "base" }).compare;
export default function AssignmentsTable() {
const control = useContext(ControlContext);
const plugin = control.plugins.get("exp_groups") as WebPlugin;
const account = useAccount();
const searchInput = useRef<InputRef>(null);
const [assignments, assignmentsSynced] = plugin.useAssignments();
const [groups, groupsSynced] = plugin.useGroups();
const [editing, setEditing] = useState<AssignmentRecord | undefined>();
const [open, setOpen] = useState(false);
const assignmentsArray = [...assignments.values()];
const groupFilters = [...groups.values()]
.filter(group => assignmentsArray.some(assignment => assignment.groupId === group.id))
.map(group => ({ text: group.name, value: group.id }))
return <>
<Table
columns={[
{
title: "Player",
dataIndex: "name",
sorter: (a: AssignmentRecord, b: AssignmentRecord) => strcmp(a.name, b.name),
filterIcon: (filtered: boolean) => (
<SearchOutlined style={{ color: filtered ? "#1677ff" : undefined }} />
),
onFilter: (value, record: AssignmentRecord) => (
record.name.toLowerCase().includes(String(value).toLowerCase())
),
filterDropdownProps: {
onOpenChange: (open: boolean) => open && setTimeout(() => searchInput.current?.select(), 100),
},
filterDropdown: ({ selectedKeys, setSelectedKeys, confirm, clearFilters }) => (
<div style={{ padding: 4 }} onKeyDown={(e) => e.stopPropagation()}>
<Input.Search
allowClear
ref={searchInput}
placeholder={"Search username"}
value={selectedKeys[0]}
onChange={(e) => setSelectedKeys([e.target.value])}
onSearch={() => confirm({ closeDropdown: false })}
onClear={() => {
clearFilters?.({ closeDropdown: false });
confirm({ closeDropdown: true });
}}
/>
</div>
),
},
{
title: "Group",
width: "40%",
filters: groupFilters,
onFilter: (value, record: AssignmentRecord) => (
record.groupId === value
),
render: (_: any, a: any) => (
groups.get(a.groupId)?.name ?? a.groupId
),
},
...(account.hasAnyPermission(
"exp_groups.assignment.update",
"exp_groups.assignment.delete",
) ? [{
title: "Actions",
width: "10%",
render: (_: any, record: AssignmentRecord) => (
<Space>
{account.hasPermission("exp_groups.assignment.update") &&
<Button icon={<EditOutlined />} onClick={() => {
setEditing(record);
setOpen(true);
}} />
}
{account.hasPermission("exp_groups.assignment.delete") && <DeletedConfirm
onConfirm={() => control.send(new AssignmentDeleteRequest(record.name))}
/>}
</Space>
),
}] : []),
]}
dataSource={assignmentsArray}
loading={!assignmentsSynced || !groupsSynced}
rowKey={(a) => a.name}
pagination={{
defaultPageSize: 50,
showSizeChanger: true,
pageSizeOptions: ["10", "20", "50", "100", "200"],
showTotal: (total: number) => `${total} Assignments`,
}}
/>
<AssignmentForm open={open} setOpen={setOpen} initial={editing} />
</>;
}
@@ -1,16 +0,0 @@
import React from "react";
import { Button, Popconfirm } from "antd";
import { DeleteOutlined } from "@ant-design/icons";
import { TooltipPlacement } from "antd/es/tooltip";
export default function DeletedConfirm(props: { onConfirm: () => void, placement?: TooltipPlacement }) {
return <Popconfirm
title="Delete?"
okText="Delete"
okButtonProps={{ danger: true }}
onConfirm={props.onConfirm}
placement={props.placement}
>
<Button danger icon={<DeleteOutlined />} />
</Popconfirm>
}
-45
View File
@@ -1,45 +0,0 @@
import React, { useContext, useEffect } from "react";
import { Modal, Form, Input, Switch } from "antd";
import { useNavigate } from "react-router-dom";
import { ControlContext } from "@clusterio/web_ui";
import * as messages from "../../messages";
export default function GroupForm({ open, setOpen }: {
open: boolean,
setOpen: (open: boolean) => void,
}) {
const control = useContext(ControlContext);
const navigate = useNavigate();
const [form] = Form.useForm();
async function submit(values: any) {
const group = await control.send(new messages.GroupCreateRequest(
values.name, new messages.GroupPermissions(Boolean(values.isBlacklist), [])
));
navigate(`/permission_groups/${group.id}/view`);
setOpen(false);
}
useEffect(() => {
if (open) form.resetFields();
}, [open]);
return <Modal
title="Create Group"
open={open}
onCancel={() => setOpen(false)}
onOk={() => form.submit()}
>
<Form form={form} onFinish={submit}>
<Form.Item name="name" label="Name" rules={[{ required: true }]}>
<Input />
</Form.Item>
<Form.Item name="isBlacklist" label="Grant all by default" valuePropName="checked">
<Switch />
</Form.Item>
</Form>
</Modal>;
}
-287
View File
@@ -1,287 +0,0 @@
import React, { useContext, useEffect, useMemo, useState } from "react";
import { useNavigate, useParams } from "react-router-dom";
import { Button, Checkbox, Input, Space, Spin, Alert } from "antd";
import { ControlContext, useAccount, useDefaultModPack, PageLayout, PageHeader, notifyErrorHandler } from "@clusterio/web_ui";
import DeletedConfirm from "./DeleteConfirm";
import * as messages from "../../messages";
import type { WebPlugin } from "..";
const DOMAIN_MAPPING = {
"Admin": ["admin", "cheat", "permission", "infinity", "editor", "spawn"],
"Building & Crafting": ["mining", "build", "craft", "deconstruct", "rotate", "entity"],
"Inventory": ["inventory", "stack", "cursor", "slot", "quick_bar", "item", "equipment"],
"Blueprints": ["blueprint", "copy", "paste", "undo", "redo", "upgrade"],
"Trains": ["train", "station", "schedule", "interrupt", "rolling_stock"],
"Circuit Network": ["combinator", "circuit", "signal", "wire", "cable", "speaker", "display_panel"],
"GUI": ["gui", "open"],
"Pins & Alerts": ["pin", "alert", "tag"],
"Logistics": ["logistic", "filter", "request"],
"Space": ["space", "surface", "planet", "rocket"],
"Toggles": ["toggle", "switch", "swap", "set"],
};
function getDomain(name: string) {
const n = name.toLowerCase();
for (const [domain, keywords] of Object.entries(DOMAIN_MAPPING)) {
if (keywords.some(k => n.includes(k))) return domain;
}
return "Other";
}
export default function GroupViewPage() {
const control = useContext(ControlContext);
const plugin = control.plugins.get("exp_groups") as WebPlugin;
const navigate = useNavigate();
const account = useAccount();
const { id } = useParams();
const groupId = Number(id);
const [groups, synced] = plugin.useGroups();
const group = groups.get(groupId);
const [name, setName] = useState("");
const defaultModPack = useDefaultModPack();
const [definesJson, setDefinesJson] = useState<Record<string, number> | null>(null);
const [permissionState, setPermissionState] = useState<Record<string, boolean>>({});
const [baselineState, setBaselineState] = useState<Record<string, boolean>>({});
const [search, setSearch] = useState("");
const canUpdate = Boolean(account.hasPermission("exp_groups.group.update"));
const canDelete = Boolean(account.hasPermission("exp_groups.group.delete"));
// fetch defines
useEffect(() => {
if (!defaultModPack?.exportManifest?.assets?.defines) return;
if (definesJson) return;
const assetName = defaultModPack.exportManifest.assets.defines;
(async () => {
const response = await fetch(`${staticRoot}static/${assetName}`);
const json = await response.json();
setDefinesJson(json.input_action ?? {});
})().catch(notifyErrorHandler("Failed to load permissions"));
}, [defaultModPack]);
// initialise permissions
useEffect(() => {
if (!group || !definesJson) return;
if (Object.keys(permissionState).length) return;
const { isBlacklist, permissions } = group.permissions;
const base = Object.fromEntries(
Object.keys(definesJson).map(p => [
p, isBlacklist !== permissions.includes(p)
])
);
setPermissionState(base);
setBaselineState(base);
}, [group, definesJson]);
// sync name
useEffect(() => {
if (group) setName(group.name);
}, [group]);
// derived data
const allPermissions = useMemo(() => (
Object.keys(definesJson ?? {})
), [definesJson]);
const filtered = useMemo(() => allPermissions.filter(p =>
p.toLowerCase().includes(search.toLowerCase())
), [allPermissions, search]);
const grouped = useMemo(() => {
const map = new Map<string, string[]>();
for (const p of filtered) {
const domain = getDomain(p);
if (!map.has(domain)) map.set(domain, []);
map.get(domain)!.push(p);
}
const sorted = [...map.entries()].sort(([a], [b]) => a.localeCompare(b));
for (const [, perms] of sorted) {
perms.sort((a, b) => a.localeCompare(b));
}
return sorted;
}, [filtered]);
const permissionChanged = Object.keys(permissionState).some(
k => permissionState[k] !== baselineState[k]
);
const metaChanged = group ? name !== group.name : false;
const edited = permissionChanged || metaChanged;
function applyChanges() {
if (!group) return;
const whitelist: string[] = [];
const blacklist: string[] = [];
for (const [perm, enabled] of Object.entries(permissionState)) {
if (enabled) whitelist.push(perm);
else blacklist.push(perm);
}
const isBlacklist = blacklist.length < whitelist.length;
const permissions = isBlacklist ? blacklist : whitelist;
control.send(new messages.GroupUpdateRequest(
new messages.GroupRecord(group.id, name, new messages.GroupPermissions(isBlacklist, permissions))
))
.then(() => setBaselineState(permissionState))
.catch(notifyErrorHandler("Error applying changes"));
}
function revertChanges() {
if (!group) return;
setPermissionState(baselineState);
setName(group.name);
}
if (!synced) {
return <PageLayout nav={[{ name: "exp_groups" }, { name: String(groupId) }]}>
<Spin />
</PageLayout>;
}
if (!group) {
return <PageLayout nav={[{ name: "exp_groups" }, { name: String(groupId) }]}>
<PageHeader title="Group not found" />
</PageLayout>;
}
return <PageLayout nav={[{ name: "Permission Groups", path: "/permission_groups" }, { name: group.name }]}>
<PageHeader
title={group.name}
extra={<Space wrap>
{canDelete && <DeletedConfirm placement="bottomRight" onConfirm={async () => {
control.send(new messages.GroupDeleteRequest(group.id));
navigate(`/permission_groups`);
}}/>}
</Space>}
/>
{edited && (
<div style={{
position: "fixed",
bottom: 24,
left: "50%",
transform: "translateX(-50%)",
background: "#2a1912",
borderRadius: 8,
padding: "10px 14px",
display: "flex",
gap: 12,
zIndex: 1000,
}}>
<span>You have unsaved changes</span>
<Space>
<Button onClick={revertChanges}>Revert</Button>
<Button type="primary" onClick={applyChanges}>Apply</Button>
</Space>
</div>
)}
<div style={{ marginBottom: 16, display: "flex", gap: 12 }}>
<label style={{ width: 110 }}>Name</label>
<Input value={name} disabled={!canUpdate} onChange={e => setName(e.target.value)} />
</div>
<h3>Permissions</h3>
{!defaultModPack?.exportManifest?.assets?.defines && (
<Alert
type="warning"
message="Missing export"
description="An export of the default mod pack is required to modify permissions."
style={{ marginBottom: 16 }}
showIcon
/>
)}
{defaultModPack?.exportManifest?.assets?.defines && !definesJson && <Spin />}
{definesJson && <>
<Input.Search
placeholder="Search permissions"
allowClear
onChange={e => setSearch(e.target.value)}
style={{ marginBottom: 16 }}
/>
<Space direction="vertical" style={{ width: "100%" }}>
{grouped.map(([groupName, permissions]) => (
<div key={groupName} style={{ border: "1px solid #424242", padding: 12, borderRadius: 6 }}>
<Space style={{ width: "100%", justifyContent: "space-between" }}>
<strong>{groupName}</strong>
<Space>
<Button
size="small"
disabled={!canUpdate}
onClick={() => {
setPermissionState(prev => {
const next = { ...prev };
for (const p of permissions) next[p] = true;
return next;
});
}}
>
Select all
</Button>
<Button
size="small"
disabled={!canUpdate}
onClick={() => {
setPermissionState(prev => {
const next = { ...prev };
for (const p of permissions) next[p] = false;
return next;
});
}}
>
Clear
</Button>
</Space>
</Space>
<div style={{
marginTop: 12,
display: "grid",
gridTemplateColumns: "repeat(auto-fill, minmax(250px, 1fr))",
gap: 8,
}}>
{permissions.map(p => (
<Checkbox
key={p}
checked={permissionState[p]}
disabled={!canUpdate}
onChange={e =>
setPermissionState(prev => ({ ...prev, [p]: e.target.checked }))
}
>
{p}
</Checkbox>
))}
</div>
</div>
))}
</Space>
</>}
</PageLayout>;
}
-49
View File
@@ -1,49 +0,0 @@
import React, { useContext, useState } from "react";
import { Table } from "antd";
import { useNavigate } from "react-router-dom";
import { ControlContext } from "@clusterio/web_ui";
import { GroupRecord } from "../../messages";
import type { WebPlugin } from "..";
import GroupForm from "./GroupForm";
const strcmp = new Intl.Collator(undefined, { numeric: true, sensitivity: "base" }).compare;
export default function GroupsTable() {
const control = useContext(ControlContext);
const plugin = control.plugins.get("exp_groups") as WebPlugin;
const navigate = useNavigate();
const [groups, synced] = plugin.useGroups();
const [open, setOpen] = useState(false);
return <>
<Table
columns={[
{
title: "Name",
dataIndex: "name",
sorter: (a: any, b: GroupRecord) => strcmp(a.name, b.name),
},
{
title: "Permissions",
width: "50%",
render: (_: any, g: GroupRecord) => (
`${g.permissions.permissions.length} ${g.permissions.isBlacklist ? "Disallowed" : "Allowed"}`
),
},
]}
dataSource={[...groups.values()]}
loading={!synced}
rowKey={(g) => g.id}
pagination={false}
onRow={(g) => ({
onClick: () => navigate(`/permission_groups/${g.id}/view`),
})}
/>
<GroupForm open={open} setOpen={setOpen} />
</>;
}
@@ -1,106 +0,0 @@
import React, { useContext, useEffect } from "react";
import { Modal, Form, Select, InputNumber, Switch, Alert } from "antd";
import { ControlContext, useRoles } from "@clusterio/web_ui";
import { RoleMappingRecord, RoleMappingCreateRequest, RoleMappingUpdateRequest } from "../../messages";
import type { WebPlugin } from "..";
export default function RoleMappingForm({ open, setOpen, initial }: {
open: boolean,
setOpen: (open: boolean) => void,
initial?: RoleMappingRecord,
}) {
const control = useContext(ControlContext);
const plugin = control.plugins.get("exp_groups") as WebPlugin;
const [groups] = plugin.useGroups();
const [roles] = useRoles();
const [form] = Form.useForm();
function submit(values: any) {
if (initial) {
control.send(new RoleMappingUpdateRequest(
new RoleMappingRecord(
initial.id,
new Set(values.roleIds),
values.groupId,
values.priority,
values.enabled,
)
));
} else {
control.send(new RoleMappingCreateRequest(
values.roleIds,
values.groupId,
values.priority,
values.enabled,
));
}
setOpen(false);
}
useEffect(() => {
if (open) {
form.resetFields();
form.setFieldsValue({
...initial,
roleIds: initial ? [...initial.roleIds] : [],
});
}
}, [open, initial]);
return <Modal
title={initial ? "Edit Role Mapping" : "Create Role Mapping"}
open={open}
onCancel={() => setOpen(false)}
onOk={() => form.submit()}
>
<Alert
type="info"
showIcon
message="How role mappings work"
style={{ marginBottom: 16 }}
description={
<ul style={{ paddingLeft: 16, margin: 0 }}>
<li>Manual assignments always take priority.</li>
<li>Mappings are checked in priority order (highest first).</li>
<li>A mapping applies only if the user has <b>all listed roles</b>.</li>
<li>The first matching mapping assigns the group.</li>
<li>If none match, the player is given all permissions.</li>
</ul>
}
/>
<Form form={form} onFinish={submit}>
<Form.Item name="groupId" label="Group" rules={[{ required: true }]}>
<Select>
{[...groups.values()].map(g => (
<Select.Option key={g.id} value={g.id}>
{g.name}
</Select.Option>
))}
</Select>
</Form.Item>
<Form.Item name="roleIds" label="Roles" rules={[{ required: true }]}>
<Select mode="multiple">
{[...roles.values()].map(r => (
<Select.Option key={r.id} value={r.id}>
{r.name}
</Select.Option>
))}
</Select>
</Form.Item>
<Form.Item name="priority" label="Priority" initialValue={100}>
<InputNumber style={{ width: "100%" }} />
</Form.Item>
<Form.Item name="enabled" label="Enabled" valuePropName="checked" initialValue={true}>
<Switch />
</Form.Item>
</Form>
</Modal>;
}
@@ -1,107 +0,0 @@
import React, { useContext, useState } from "react";
import { Table, Button, Space, Tag } from "antd";
import { EditOutlined } from "@ant-design/icons";
import { ControlContext, useAccount, useRoles } from "@clusterio/web_ui";
import { RoleMappingRecord, RoleMappingDeleteRequest } from "../../messages";
import type { WebPlugin } from "..";
import RoleMappingForm from "./RoleMappingForm";
import DeletedConfirm from "./DeleteConfirm";
export default function RoleMappingsTable() {
const control = useContext(ControlContext);
const plugin = control.plugins.get("exp_groups") as WebPlugin;
const account = useAccount();
const [roleMappings, roleMappingsSynced] = plugin.useRoleMappings();
const [groups, groupsSynced] = plugin.useGroups();
const [roles, rolesSynced] = useRoles();
const [editing, setEditing] = useState<RoleMappingRecord | undefined>();
const [open, setOpen] = useState(false);
const roleMappingArray = [...roleMappings.values()];
const roleFilters = [...roles.values()]
.filter(role => roleMappingArray.some(mapping => mapping.roleIds.has(role.id)))
.map(role => ({ text: role.name, value: role.id }));
const groupFilters = [...groups.values()]
.filter(group => roleMappingArray.some(mapping => mapping.groupId === group.id))
.map(group => ({ text: group.name, value: group.id }))
return <>
<Table
columns={[
{
title: "Priority",
dataIndex: "priority",
width: "10%",
},
{
title: "Roles",
filters: roleFilters,
onFilter: (value, record: RoleMappingRecord) => (
record.roleIds.has(value as number)
),
render: (_: any, record: RoleMappingRecord) => (
[...record.roleIds].map(id => <Tag key={id}>{roles.get(id)?.name ?? id}</Tag>)
),
},
{
title: "Group",
width: "30%",
filters: groupFilters,
onFilter: (value, record: RoleMappingRecord) => (
record.groupId === value
),
render: (_: any, record: RoleMappingRecord) => (
groups.get(record.groupId)?.name ?? record.groupId
),
},
{
title: "Enabled",
width: "10%",
filters: [
{ text: "Enabled", value: true },
{ text: "Disabled", value: false },
],
onFilter: (value, record: RoleMappingRecord) => (
record.enabled === value
),
render: (_: any, record: RoleMappingRecord) => (
record.enabled ? "Yes" : "No"
),
},
...(account.hasAnyPermission(
"exp_groups.role_mapping.update",
"exp_groups.role_mapping.delete",
) ? [{
title: "Actions",
width: "10%",
render: (_: any, record: any) => (
<Space>
{account.hasPermission("exp_groups.role_mapping.update") && <Button
icon={<EditOutlined />}
onClick={() => {
setEditing(record);
setOpen(true);
}}
/>}
{account.hasPermission("exp_groups.role_mapping.delete") && <DeletedConfirm
onConfirm={() => control.send(new RoleMappingDeleteRequest(record.id))}
/>}
</Space>
),
}] : []),
]}
dataSource={roleMappingArray.sort((a, b) => b.priority - a.priority)}
loading={!roleMappingsSynced || !groupsSynced || !rolesSynced}
rowKey={(m) => m.id}
pagination={false}
/>
<RoleMappingForm open={open} setOpen={setOpen} initial={editing} />
</>;
}
-108
View File
@@ -1,108 +0,0 @@
import React, { useState, useCallback, useSyncExternalStore } from "react";
import { BaseWebPlugin, PageLayout, PageHeader, useAccount, SectionHeader } from "@clusterio/web_ui";
import { Button } from "antd";
import * as messages from "../messages";
import * as lib from "@clusterio/lib";
import GroupsTable from "./components/GroupsTable";
import AssignmentsTable from "./components/AssignmentsTable";
import RoleMappingsTable from "./components/RoleMappingsTable";
import GroupViewPage from "./components/GroupViewPage";
import GroupForm from "./components/GroupForm";
import RoleMappingForm from "./components/RoleMappingForm";
import AssignmentForm from "./components/AssignmentForm";
function ExpGroupsPage() {
const account = useAccount();
const [groupOpen, setGroupOpen] = useState(false);
const [assignmentOpen, setAssignmentOpen] = useState(false);
const [roleMappingOpen, setRoleMappingOpen] = useState(false);
return <PageLayout nav={[{ name: "Permission Groups" }]}>
<PageHeader title="Permission Groups" />
{account.hasPermission("exp_groups.group.list") && <>
<SectionHeader
title="Groups"
extra={
account.hasPermission("exp_groups.group.create")
? <Button type="primary" onClick={() => setGroupOpen(true)}>Create</Button>
: undefined
}
/>
<GroupsTable />
<GroupForm open={groupOpen} setOpen={setGroupOpen} />
</>}
{account.hasPermission("exp_groups.role_mapping.list") && <>
<SectionHeader
title="Role Mappings"
extra={
account.hasPermission("exp_groups.role_mapping.create")
? <Button type="primary" onClick={() => setRoleMappingOpen(true)}>Create</Button>
: undefined
}
/>
<RoleMappingsTable />
<RoleMappingForm open={roleMappingOpen} setOpen={setRoleMappingOpen} />
</>}
{account.hasPermission("exp_groups.assignment.list") && <>
<SectionHeader
title="Assignments"
extra={
account.hasPermission("exp_groups.assignment.create")
? <Button type="primary" onClick={() => setAssignmentOpen(true)}>Create</Button>
: undefined
}
/>
<AssignmentsTable />
<AssignmentForm open={assignmentOpen} setOpen={setAssignmentOpen} />
</>}
</PageLayout>;
}
export class WebPlugin extends BaseWebPlugin {
groups = new lib.MapSubscriber(messages.GroupUpdatedEvent, this.control);
assignments = new lib.MapSubscriber(messages.ManualAssignmentUpdatedEvent, this.control);
roleMappings = new lib.MapSubscriber(messages.RoleMappingUpdatedEvent, this.control);
async init() {
this.pages = [
{
path: "/permission_groups",
sidebarName: "Permission Groups",
permission: (account => account.hasAnyPermission(
"exp_groups.group.list",
"exp_groups.assignment.list",
"exp_groups.role_mapping.list",
)),
content: <ExpGroupsPage />,
},
{
path: "/permission_groups/:id/view",
sidebarPath: "/permission_groups",
permission: "exp_groups.group.get",
content: <GroupViewPage />,
},
];
}
useGroups() {
const subscribe = useCallback((cb: () => void) => this.groups.subscribe(cb), []);
return useSyncExternalStore(subscribe, () => this.groups.getSnapshot());
}
useAssignments() {
const subscribe = useCallback((cb: () => void) => this.assignments.subscribe(cb), []);
return useSyncExternalStore(subscribe, () => this.assignments.getSnapshot());
}
useRoleMappings() {
const subscribe = useCallback((cb: () => void) => this.roleMappings.subscribe(cb), []);
return useSyncExternalStore(subscribe, () => this.roleMappings.getSnapshot());
}
}
-34
View File
@@ -1,34 +0,0 @@
"use strict";
const path = require("path");
const webpack = require("webpack");
const { merge } = require("webpack-merge");
const common = require("@clusterio/web_ui/webpack.common");
module.exports = (env = {}) => merge(common(env), {
context: __dirname,
entry: "./web/index.tsx",
output: {
path: path.resolve(__dirname, "dist", "web"),
},
plugins: [
new webpack.container.ModuleFederationPlugin({
name: "exp_groups",
library: { type: "window", name: "plugin_exp_groups" },
exposes: {
"./": "./index.ts",
"./package.json": "./package.json",
"./web": "./web/index.tsx",
},
shared: {
"@clusterio/lib": { import: false },
"@clusterio/web_ui": { import: false },
"antd": { import: false },
"react": { import: false },
"react-dom": { import: false },
"react-router": { import: false },
"react-router-dom": { import: false },
},
}),
],
});
+12 -17
View File
@@ -16,7 +16,7 @@ Storage.register(player_elements, function(tbl)
player_elements = tbl
end)
--- @class (partial) Gui
--- @class Gui
local Gui = {
define = ExpElement.new,
from_argument = ExpElement.from_argument,
@@ -46,11 +46,9 @@ end
--- @return LuaPlayer
function Gui.get_player(input)
if type(input) == "table" and not input.player_index then
--- @cast input { element: LuaGuiElement }
return (assert(game.get_player(input.element.player_index)))
return assert(game.get_player(input.element.player_index))
end
--- @cast input LuaGuiElement | { player_index: uint }
return (assert(game.get_player(input.player_index)))
return assert(game.get_player(input.player_index))
end
--- Toggle the enable state of an element
@@ -90,7 +88,7 @@ end
--- @param define ExpElement
--- @param visible Gui.VisibleCallback | boolean | nil
function Gui.add_top_element(define, visible)
assert(Gui.top_elements[define] == nil, "Element is already added to the top flow")
assert(Gui.top_elements[define.name] == nil, "Element is already added to the top flow")
Gui.top_elements[define] = visible or false
end
@@ -98,7 +96,7 @@ end
--- @param define ExpElement
--- @param visible Gui.VisibleCallback | boolean | nil
function Gui.add_left_element(define, visible)
assert(Gui.left_elements[define] == nil, "Element is already added to the left flow")
assert(Gui.left_elements[define.name] == nil, "Element is already added to the left flow")
Gui.left_elements[define] = visible or false
end
@@ -107,7 +105,7 @@ end
--- @param define ExpElement
--- @param visible Gui.VisibleCallback | boolean | nil
function Gui.add_relative_element(define, visible)
assert(Gui.relative_elements[define] == nil, "Element is already added to the relative flow")
assert(Gui.relative_elements[define.name] == nil, "Element is already added to the relative flow")
Gui.relative_elements[define] = visible or false
end
@@ -116,8 +114,7 @@ end
--- @param player LuaPlayer
--- @return LuaGuiElement
function Gui.get_top_element(define, player)
local elements = assert(player_elements[player.index])
return (assert(elements.top[define.name], "Element is not on the top flow"))
return assert(player_elements[player.index].top[define.name], "Element is not on the top flow")
end
--- Register a element define to be drawn to the left flow on join
@@ -125,8 +122,7 @@ end
--- @param player LuaPlayer
--- @return LuaGuiElement
function Gui.get_left_element(define, player)
local elements = assert(player_elements[player.index])
return (assert(elements.left[define.name], "Element is not on the left flow"))
return assert(player_elements[player.index].left[define.name], "Element is not on the left flow")
end
--- Register a element define to be drawn to the relative flow on join
@@ -134,8 +130,7 @@ end
--- @param player LuaPlayer
--- @return LuaGuiElement
function Gui.get_relative_element(define, player)
local elements = assert(player_elements[player.index])
return (assert(elements.relative[define.name], "Element is not on the relative flow"))
return assert(player_elements[player.index].relative[define.name], "Element is not on the relative flow")
end
--- Ensure all the correct elements are visible and exist
@@ -193,9 +188,9 @@ function Gui._ensure_consistency(event)
-- This check isn't needed, but allows the toolbar file to be deleted without modifying any lib code
if Gui.toolbar then
--- @diagnostic disable-next-line: access-invisible
--- @diagnostic disable-next-line invisible
Gui.toolbar._create_elements(player)
--- @diagnostic disable-next-line: access-invisible
--- @diagnostic disable-next-line invisible
Gui.toolbar._ensure_consistency(player)
end
end
@@ -216,7 +211,7 @@ local function on_gui_opened(event)
if visible then
event.element = element
--- @diagnostic disable-next-line: access-invisible
--- @diagnostic disable-next-line invisible
define:raise_event(event)
end
end
+2 -6
View File
@@ -62,7 +62,6 @@ local GuiData = {
--- @field player_data table<uint, any>
--- @field force_data table<uint, any>
--- @field global_data table
--- @field [DataKey] any
-- This class has no prototype methods
-- Same as raw but __index ensures the values exist
@@ -90,16 +89,13 @@ function GuiData._metatable.__index(self, key)
assert(type(key) == "userdata", "Index type '" .. ExpUtil.get_class_name(key) .. "' given to GuiData. Must be of type userdata.")
local object_name = key.object_name --- @diagnostic disable-line assign-type-mismatch
if object_name == "LuaGuiElement" then
--- @cast key LuaGuiElement
local data = self._raw.element_data
local player_elements = data and data[key.player_index]
return player_elements and player_elements[key.index]
elseif object_name == "LuaPlayer" then
--- @cast key LuaPlayer
local data = self._raw.player_data
return data and data[key.index]
elseif object_name == "LuaForce" then
--- @cast key LuaForce
local data = self._raw.force_data
return data and data[key.index]
else
@@ -157,7 +153,7 @@ end
--- @param scope string
--- @return GuiData
function GuiData.get(scope)
return GuiData._scopes[scope] --[[@as GuiData]]
return GuiData._scopes[scope] --[[ @as GuiData ]]
end
--- Used to clean up data from destroyed elements
@@ -172,7 +168,7 @@ local function on_object_destroyed(event)
for _, scope in pairs(registered_scopes) do
local data = scope._raw.element_data
local player_elements = data and data[player_index]
if data and player_elements then
if player_elements then
player_elements[element_index] = nil
if not next(player_elements) then
data[player_index] = nil
+17 -17
View File
@@ -1,4 +1,4 @@
--- @class (partial) Gui
--- @class Gui
local Gui = require("modules/exp_gui")
--- @class Gui.elements
@@ -28,7 +28,7 @@ Elements.aligned_flow = Gui.define("aligned_flow")
vertically_stretchable = vertical_align ~= "center",
horizontally_stretchable = horizontal_align ~= "center",
}
end) --[[@as any]]
end) --[[ @as any ]]
--- A solid horizontal white bar element
--- @class Gui.elements.bar: ExpElement
@@ -48,11 +48,11 @@ Elements.bar = Gui.define("bar")
else
style.horizontally_stretchable = true
end
end) --[[@as any]]
end) --[[ @as any ]]
--- A label which is centered
--- @class Gui.elements.centered_label: ExpElement
--- @overload fun(parent: LuaGuiElement, width: number, caption: LocalisedString?, tooltip: LocalisedString?): LuaGuiElement
--- @overload fun(parent: LuaGuiElement, width: number, caption: LocalisedString, tooltip: LocalisedString?): LuaGuiElement
Elements.centered_label = Gui.define("centered_label")
:draw{
type = "label",
@@ -63,7 +63,7 @@ Elements.centered_label = Gui.define("centered_label")
horizontal_align = "center",
single_line = false,
width = Gui.from_argument(1),
} --[[@as any]]
} --[[ @as any ]]
--- A label which has two white bars on either side of it
--- @class Gui.elements.title_label: ExpElement
@@ -91,7 +91,7 @@ Elements.title_label = Gui.define("title_label")
Elements.bar(flow)
return label
end) --[[@as any]]
end) --[[ @as any ]]
--- A fixed size vertical scroll pane
--- @class Gui.elements.scroll_pane: ExpElement
@@ -109,7 +109,7 @@ Elements.scroll_pane = Gui.define("scroll_pane")
padding = { 1, 3 },
maximal_height = Gui.from_argument(1),
horizontally_stretchable = true,
} --[[@as any]]
} --[[ @as any ]]
--- A fixed size vertical scroll pane containing a table
--- @class Gui.elements.scroll_table: ExpElement
@@ -132,7 +132,7 @@ Elements.scroll_table = Gui.define("scroll_table")
cell_padding = 1,
vertical_align = "center",
horizontally_stretchable = true,
} --[[@as any]]
} --[[ @as any ]]
--- A container frame
--- @class Gui.elements.container: ExpElement
@@ -159,13 +159,13 @@ Elements.container = Gui.define("container")
vertically_stretchable = false,
horizontally_stretchable = false,
minimal_width = Gui.from_argument(1),
} --[[@as any]]
} --[[ @as any ]]
--- Get the root element of a container
--- @param container LuaGuiElement
--- @return LuaGuiElement
function Elements.container.get_root_element(container)
return assert(container.parent)
return container.parent
end
--- A frame within a container
@@ -183,7 +183,7 @@ Elements.subframe_base = Gui.define("container_subframe")
padding = { 3, 6, 0, 6 },
use_header_filler = false,
horizontally_stretchable = true,
} --[[@as any]]
} --[[ @as any ]]
--- @class Gui.elements.header.opts
--- @field name string?
@@ -212,7 +212,7 @@ Elements.header = Gui.define("container_header")
subframe.add{ type = "empty-widget" }.style.horizontally_stretchable = true
return subframe
end) --[[@as any]]
end) --[[ @as any ]]
--- @class Gui.elements.footer.opts
--- @field name string?
@@ -241,7 +241,7 @@ Elements.footer = Gui.define("container_footer")
subframe.add{ type = "empty-widget" }.style.horizontally_stretchable = true
return subframe
end) --[[@as any]]
end) --[[ @as any ]]
--- A button used to destroy its target when clicked, intended for screen frames
--- @class Gui.elements.screen_frame_close: ExpElement
@@ -259,7 +259,7 @@ Elements.screen_frame_close = Gui.define("screen_frame_close")
:on_click(function(def, player, element, event)
--- @cast def Gui.elements.screen_frame_close
Gui.destroy_if_valid(def.data[element])
end) --[[@as any]]
end) --[[ @as any ]]
--- A draggable frame with close button and button flow
--- @class Gui.elements.screen_frame: ExpElement
@@ -313,20 +313,20 @@ Elements.screen_frame = Gui.define("screen_frame")
direction = "vertical",
style = "inside_shallow_frame_packed",
}
end) --[[@as any]]
end) --[[ @as any ]]
--- Get the button flow for a screen frame
--- @param screen_frame LuaGuiElement
--- @return LuaGuiElement
function Elements.screen_frame.get_button_flow(screen_frame)
return (assert(Elements.screen_frame.data[assert(screen_frame.parent)], "Screen frame has no button flow"))
return assert(Elements.screen_frame.data[screen_frame.parent], "Screen frame has no button flow")
end
--- Get the root element of a screen frame
--- @param screen_frame LuaGuiElement
--- @return LuaGuiElement
function Elements.screen_frame.get_root_element(screen_frame)
return assert(screen_frame.parent)
return screen_frame.parent
end
return Elements
+3 -1
View File
@@ -166,6 +166,7 @@ end
function GuiIter.get_tracked_elements(scope, filter)
local class_name = ExpUtil.get_class_name(filter)
if class_name == "nil" then
--- @cast filter nil
return GuiIter.all_elements(scope)
elseif class_name == "LuaPlayer" then
--- @cast filter LuaPlayer
@@ -188,6 +189,7 @@ end
function GuiIter.get_online_elements(scope, filter)
local class_name = ExpUtil.get_class_name(filter)
if class_name == "nil" then
--- @cast filter nil
return GuiIter.filtered_elements(scope, game.connected_players)
elseif class_name == "LuaPlayer" then
--- @cast filter LuaPlayer
@@ -233,7 +235,7 @@ end
function GuiIter.remove_element(scope, player_index, element_index)
local scope_elements = registered_scopes[scope]
if not scope_elements then return end
local player_elements = scope_elements[player_index]
local player_elements = registered_scopes[player_index]
if not player_elements then return end
player_elements[element_index] = nil
end
-1
View File
@@ -1,4 +1,3 @@
[exp-gui]
clear-left-flow=Hide all open windows.
close-toolbar=__CONTROL_LEFT_CLICK__: Toggle Settings\n__CONTROL_RIGHT_CLICK__: Close Toolbar
+3 -6
View File
@@ -43,8 +43,6 @@ ExpElement.events = {}
--- @field _force_data ExpElement.PostDrawCallback?
--- @field _global_data ExpElement.PostDrawCallback?
--- @field _events table<defines.events, ExpElement.EventHandler<EventData>[]>
--- @field _has_handlers boolean
--- @field _track_elements boolean
--- @overload fun(parent: LuaGuiElement, ...: any): LuaGuiElement
ExpElement._prototype = {
_track_elements = false,
@@ -160,7 +158,7 @@ function ExpElement.new(name)
},
}
ExpElement._elements[element_name] = instance --[[@as ExpElement]]
ExpElement._elements[element_name] = instance --[[ @as ExpElement ]]
return setmetatable(instance, ExpElement._metatable)
end
@@ -168,7 +166,7 @@ end
--- @param name string
--- @return ExpElement
function ExpElement.get(name)
return (assert(ExpElement._elements[name], "ExpElement is not defined: " .. tostring(name)))
return assert(ExpElement._elements[name], "ExpElement is not defined: " .. tostring(name))
end
--- Create a new instance of this element definition
@@ -276,7 +274,6 @@ function ExpElement._prototype:draw(definition)
if not next(signals) then
-- If no signals then skip var arg
--- @cast definition LuaGuiElement.add_param
self._draw = function(_, parent)
return parent.add(definition)
end
@@ -462,7 +459,7 @@ function ExpElement._prototype:raise_event(event)
for _, handler in ipairs(handlers) do
-- All gui elements will contain player and element, other events might have these as nil
-- Therefore only the signature of on_event has these values as optional
handler(self, player --[[@as LuaPlayer]], event.element, event --[[@as EventData]])
handler(self, player --[[ @as LuaPlayer ]], event.element, event --[[ @as EventData ]])
end
end
+1 -1
View File
@@ -1,4 +1,4 @@
--- @class (partial) Gui
--- @class Gui
local Gui = require("modules/exp_gui")
--- @class Gui.styles
+16 -18
View File
@@ -1,5 +1,5 @@
--- @class (partial) Gui
--- @class Gui
local Gui = require("modules/exp_gui")
local ExpElement = require("modules/exp_gui/prototype")
local mod_gui = require("mod-gui")
@@ -29,7 +29,7 @@ Toolbar.on_gui_button_toggled = script.generate_event_name()
--- @class _ExpElement._prototype
--- @field on_button_toggled ExpElement.OnEventAdder<EventData.on_gui_button_toggled>
--- @diagnostic disable-next-line: access-invisible, inject-field
--- @diagnostic disable-next-line: invisible, inject-field
function ExpElement._prototype.on_button_toggled(self, handler)
return self:on_event(Toolbar.on_gui_button_toggled, handler)
end
@@ -85,7 +85,7 @@ function Toolbar.set_button_toggled_state(define, player, state, _from_left)
button.style = state and toolbar_button_active_style or toolbar_button_default_style
-- Make the extra required adjustments
local style = button.style --[[@as LuaStyle]]
local style = button.style
style.minimal_width = original_width
style.maximal_height = original_height
if button.type == "sprite-button" then
@@ -335,10 +335,9 @@ Below here is the toolbar settings GUI and its associated functions
--- @param dst LuaGuiElement
local function copy_style(src, dst)
dst.style = src.style.name
local style = dst.style --[[@as LuaStyle]]
style.height = toolbar_button_small
style.width = toolbar_button_small
style.padding = -2
dst.style.height = toolbar_button_small
dst.style.width = toolbar_button_small
dst.style.padding = -2
end
--- Reorder the buttons relative to each other, this will update the datastore
@@ -351,7 +350,7 @@ local function move_toolbar_button(player, item, offset)
-- Swap the position in the list
local list = assert(item.parent)
local other_item = assert(list.children[new_index])
local other_item = list.children[new_index]
list.swap_children(old_index, new_index)
-- Swap the position in the top flow, offset by 1 because of settings button
@@ -366,7 +365,7 @@ local function move_toolbar_button(player, item, offset)
local other_element = Gui.get_left_element(other_left_element, player)
local left_index = element.get_index_in_parent()
local other_index = other_element.get_index_in_parent()
assert(element.parent).swap_children(left_index, other_index)
element.parent.swap_children(left_index, other_index)
end
-- If we are moving in/out of first/last place we need to update the move buttons
@@ -394,7 +393,7 @@ end
--- @param player LuaPlayer
--- @param order Gui.ToolbarOrder
function Toolbar.set_order(player, order)
local list = elements.toolbar_settings.data[player] --[[@as LuaGuiElement]]
local list = elements.toolbar_settings.data[player] --[[ @as LuaGuiElement ]]
local left_flow = Gui.get_left_flow(player)
local top_flow = Gui.get_top_flow(player)
@@ -467,7 +466,7 @@ end
function Toolbar.get_state(player)
-- Get the order of toolbar buttons
local order = {}
local list = elements.toolbar_settings.data[player] --[[@as LuaGuiElement]]
local list = elements.toolbar_settings.data[player] --[[ @as LuaGuiElement ]]
for index, item in pairs(list.children) do
order[index] = { name = item.name, favourite = elements.toolbar_list_item.data[item].set_favourite.state }
end
@@ -488,7 +487,7 @@ end
--- @param player LuaPlayer
function Toolbar._create_elements(player)
-- Add any missing items to the gui
local toolbar_list = elements.toolbar_settings.data[player] --[[@as LuaGuiElement]]
local toolbar_list = elements.toolbar_settings.data[player] --[[ @as LuaGuiElement ]]
local previous_last_index = #toolbar_list.children_names
for define in pairs(Gui.top_elements) do
if define ~= elements.close_toolbar and toolbar_list[define.name] == nil then
@@ -500,8 +499,7 @@ function Toolbar._create_elements(player)
-- Reset the state of the previous last child
local children = toolbar_list.children
if previous_last_index > 0 then
local previous_last = assert(children[previous_last_index])
elements.toolbar_list_item.data[previous_last].move_item_down.enabled = true
elements.toolbar_list_item.data[children[previous_last_index]].move_item_down.enabled = true
end
-- Set the state of the move buttons for the first and last element
@@ -515,7 +513,7 @@ end
--- @param player LuaPlayer
function Toolbar._ensure_consistency(player)
-- Update the toolbar buttons
local list = elements.toolbar_settings.data[player] --[[@as LuaGuiElement]]
local list = elements.toolbar_settings.data[player] --[[ @as LuaGuiElement ]]
for _, button in ipairs(toolbar_buttons) do
-- Update the visible state based on if the player is allowed the button
local element = Gui.get_top_element(button, player)
@@ -621,7 +619,7 @@ elements.move_item_up = Gui.define("move_item_up")
size = toolbar_button_small,
})
:on_click(function(def, player, element)
local item = assert(assert(element.parent).parent)
local item = assert(element.parent.parent)
move_toolbar_button(player, item, -1)
end)
@@ -636,7 +634,7 @@ elements.move_item_down = Gui.define("move_item_down")
size = toolbar_button_small,
})
:on_click(function(def, player, element)
local item = assert(assert(element.parent).parent)
local item = assert(element.parent.parent)
move_toolbar_button(player, item, 1)
end)
@@ -660,7 +658,7 @@ elements.set_favourite = Gui.define("set_favourite")
width = 180,
}
:on_checked_state_changed(function(def, player, element)
local define = ExpElement.get(element.tags.element_name --[[@as string]])
local define = ExpElement.get(element.tags.element_name --[[ @as string ]])
local top_element = Gui.get_top_element(define, player)
local had_visible = Toolbar.has_visible_buttons(player)
top_element.visible = element.state
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@expcluster/lib_gui",
"version": "6.5.0",
"version": "0.1.0",
"description": "Clusterio plugin providing a Lua GUI definition library.",
"author": "Cooldude2606 <https://github.com/Cooldude2606>",
"license": "MIT",
@@ -13,10 +13,10 @@
"node": ">=18"
},
"peerDependencies": {
"@clusterio/lib": "workspace:^"
"@clusterio/lib": "catalog:"
},
"devDependencies": {
"@clusterio/lib": "workspace:^",
"@clusterio/lib": "catalog:",
"@types/node": "catalog:",
"typescript": "catalog:",
"webpack": "catalog:",
+5 -1
View File
@@ -16,15 +16,19 @@ return {
"modules.data.quickbar",
"modules.data.alt-view",
"modules.data.tag",
-- 'modules.data.bonus',
"modules.data.personal-logistic",
"modules.data.language",
--"modules.data.toolbar",
--- GUI
"modules.gui.readme",
"modules.gui.warp-list",
"modules.gui.player-list",
"modules.gui.vlayer",
"modules.gui._role_updates",
"modules.graftorio.require", -- graftorio
-- "modules.graftorio.require", -- graftorio
--- Config Files
"config.expcore.permission_groups", -- loads some predefined permission groups
"config.expcore.roles", -- loads some predefined roles
+13 -7
View File
@@ -4,7 +4,7 @@
--- These are called factories because they return another function
-- use these as a simple methods of adding new items
-- they will do most of the work for you
-- ['item-name'] = factory(params)
-- ["item-name"] = factory(params)
-- luacheck:ignore 212/amount_made 212/items_made 212/player
-- Use these to adjust for ticks ie game.tick < 5*minutes
@@ -12,7 +12,7 @@
local seconds, minutes, hours = 60, 3600, 216000
--- Use to make a split point for the number of items given based on time
-- ['stone-furnace']=cutoff_time(5*minutes, 4,0) -- before 5 minutes give four items after 5 minutes give none
-- ["stone-furnace"]=cutoff_time(5*minutes, 4,0) -- before 5 minutes give four items after 5 minutes give none
local function cutoff_time(time, before, after)
return function(amount_made, items_made, player)
if game.tick < time then
@@ -24,7 +24,8 @@ local function cutoff_time(time, before, after)
end
--- Use to make a split point for the number of items given based on amount made
-- ['firearm-magazine']=cutoff_amount_made(100, 10, 0) -- give 10 items until 100 items have been made
-- ["firearm-magazine"]=cutoff_amount_made(100, 10, 0) -- give 10 items until 100 items have been made
--[[
local function cutoff_amount_made(amount, before, after)
return function(amount_made, items_made, player)
if amount_made < amount then
@@ -34,9 +35,11 @@ local function cutoff_amount_made(amount, before, after)
end
end
end
]]
--- Same as above but will not give any items if x amount has been made of another item, useful for tiers
-- ['light-armor']=cutoff_amount_made_unless(5, 0,1,'heavy-armor',5) -- give light armor once 5 have been made unless 5 heavy armor has been made
-- ["light-armor"]=cutoff_amount_made_unless(5, 0,1,"heavy-armor",5) -- give light armor once 5 have been made unless 5 heavy armor has been made
--[[
local function cutoff_amount_made_unless(amount, before, after, second_item, second_amount)
return function(amount_made, items_made, player)
if items_made(second_item) < second_amount then
@@ -50,9 +53,11 @@ local function cutoff_amount_made_unless(amount, before, after, second_item, sec
end
end
end
]]
-- Use for mass production items where you want the amount to change based on the amount already made
-- ['iron-plate']=scale_amount_made(5*minutes, 10, 10) -- for first 5 minutes give 10 items then after apply a factor of 10
-- ["iron-plate"]=scale_amount_made(5*minutes, 10, 10) -- for first 5 minutes give 10 items then after apply a factor of 10
--[[
local function scale_amount_made(amount, before, scalar)
return function(amount_made, items_made, player)
if amount_made < amount then
@@ -62,12 +67,13 @@ local function scale_amount_made(amount, before, scalar)
end
end
end
]]
--[[
Common values
game.tick is the amount of time the game has been on for
amount_made is the amount of that item which has been made
items_made('item-name') will return the amount of any item made
items_made("item-name") will return the amount of any item made
player is the player who just spawned
hours, minutes, seconds are the number of ticks in each unit of time
]]
@@ -78,7 +84,7 @@ return {
friendly_fire = false, --- @setting friendly_fire weather players will be able to attack each other on the same force
disable_crashsite = true, --- @setting disable_crashsite weather to disable creation of the crashsite
enemy_expansion = false, --- @setting enemy_expansion a catch all for in case the map settings file fails to load
chart_radius = 10 * 32, --- @setting chart_radius the number of tiles that will be charted when the map starts
chart_radius = 16 * 32, --- @setting chart_radius the number of tiles that will be charted when the map starts
items = { --- @setting items items and there condition for being given
["iron-ore"] = cutoff_time(20 * minutes, 80, 0),
["stone"] = cutoff_time(20 * minutes, 20, 0),
+216 -15
View File
@@ -3,17 +3,21 @@
return {
--[[
TODO
force bonus
quick health regeneration
Base point is equal to the amount of standard value in each parameter.
CMMS CRS CCS CISB CHB CRDB PBR
STD 20 60 24 10 12 8 40
= 174
STD 30 90 32 20 16 12 60
= 260
MAX 40 120 48 20 24 16 80
= 348
MAX 60 180 64 40 32 24 120
= 480
]]
points = {
base = 174,
base = 260,
increase_percentage_per_role_level = 0.03,
role_name = "Member",
},
@@ -23,7 +27,8 @@ return {
name = "character_mining_speed_modifier",
scale = 1,
cost = 10,
max_value = 4,
max_value = 6,
initial_value = 3,
value_step = 0.5,
is_percentage = true,
},
@@ -31,7 +36,8 @@ return {
name = "character_running_speed_modifier",
scale = 1,
cost = 60,
max_value = 2,
max_value = 3,
initial_value = 1.5,
value_step = 0.25,
is_percentage = true,
},
@@ -39,29 +45,33 @@ return {
name = "character_crafting_speed_modifier",
scale = 1,
cost = 4,
max_value = 12,
max_value = 16,
initial_value = 8,
value_step = 1,
is_percentage = true,
},
{
name = "character_inventory_slots_bonus",
scale = 10,
cost = 2,
max_value = 100,
scale = 10,
max_value = 200,
initial_value = 100,
value_step = 10,
},
{
name = "character_health_bonus",
scale = 50,
cost = 4,
max_value = 300,
max_value = 400,
initial_value = 200,
value_step = 50,
},
{
name = "character_reach_distance_bonus",
scale = 1,
cost = 1,
max_value = 16,
scale = 1,
max_value = 24,
initial_value = 12,
value_step = 2,
combined_bonus = {
"character_resource_reach_distance_bonus",
@@ -70,15 +80,104 @@ return {
},
{
name = "personal_battery_recharge",
initial_value = 6,
max_value = 12,
value_step = 1,
scale = 4,
cost = 40,
max_value = 8,
value_step = 1,
is_special = true,
},
--[[
['character_item_pickup_distance_bonus'] = {
initial_value = 0,
max_value = 20,
value_step = 1,
scale = 1,
cost = 1,
is_percentage = false
},
['character_loot_pickup_distance_bonus'] = {
initial_value = 0,
max_value = 20,
value_step = 1,
scale = 1,
cost = 1,
is_percentage = false
},
['character_item_drop_distance_bonus'] = {
initial_value = 0,
max_value = 20,
value_step = 1,
scale = 1,
cost = 1,
is_percentage = false
}
]]
},
force_bonus = {
--[[
['character_mining_speed_modifier'] = {
initial_value = 0,
max_value = 6,
value_step = 0.5,
scale = 1,
cost = 10,
is_percentage = true
},
['character_running_speed_modifier'] = {
initial_value = 0,
max_value = 3,
value_step = 0.25,
scale = 1,
cost = 40,
is_percentage = true
},
['character_crafting_speed_modifier'] = {
initial_value = 0,
max_value = 16,
value_step = 1,
scale = 1,
cost = 4,
is_percentage = true
},
['character_inventory_slots_bonus'] = {
initial_value = 0,
max_value = 200,
value_step = 10,
scale = 100,
cost = 2,
is_percentage = false
},
['character_health_bonus'] = {
initial_value = 0,
max_value = 400,
value_step = 50,
cost = 4,
is_percentage = false
},
['character_reach_distance_bonus'] = {
initial_value = 0,
max_value = 24,
value_step = 2,
scale = 1,
cost = 1,
is_percentage = false,
combined_bonus = {
'character_resource_reach_distance_bonus',
'character_build_distance_bonus'
}
},
['worker_robots_speed_modifier'] = {
initial_value = 0,
max_value = 0,
value_step = 0,
scale = 1,
cost = 1,
is_percentage = false
},
]]
["worker_robots_battery_modifier"] = {
initial_value = 1,
max_value = 1,
value_step = 1,
scale = 1,
@@ -86,6 +185,7 @@ return {
is_percentage = false,
},
["worker_robots_storage_bonus"] = {
initial_value = 1,
max_value = 1,
value_step = 1,
scale = 1,
@@ -93,13 +193,114 @@ return {
is_percentage = false,
},
["following_robots_lifetime_modifier"] = {
initial_value = 1,
max_value = 1,
value_step = 1,
scale = 1,
cost = 1,
is_percentage = false,
},
--[[
['character_item_pickup_distance_bonus'] = {
initial_value = 0,
max_value = 20,
value_step = 1,
scale = 1,
cost = 1,
is_percentage = false
},
['character_loot_pickup_distance_bonus'] = {
initial_value = 0,
max_value = 20,
value_step = 1,
scale = 1,
cost = 1,
is_percentage = false
},
['character_item_drop_distance_bonus'] = {
initial_value = 0,
max_value = 20,
value_step = 1,
scale = 1,
cost = 1,
is_percentage = false
},
['character_trash_slot_count'] = {
initial_value = 0,
max_value = 0,
value_step = 0,
scale = 1,
cost = 1,
is_percentage = false
},
['mining_drill_productivity_bonus'] = {
initial_value = 0,
max_value = 0,
value_step = 0,
scale = 1,
cost = 1,
is_percentage = false
},
['train_braking_force_bonus'] = {
initial_value = 0,
max_value = 0,
value_step = 0,
scale = 1,
cost = 1,
is_percentage = false
},
['laboratory_speed_modifier'] = {
initial_value = 0,
max_value = 0,
value_step = 0,
scale = 1,
cost = 1,
is_percentage = false
},
['laboratory_productivity_bonus'] = {
initial_value = 0,
max_value = 0,
value_step = 0,
scale = 1,
cost = 1,
is_percentage = false
},
['inserter_stack_size_bonus'] = {
initial_value = 0,
max_value = 0,
value_step = 0,
scale = 1,
cost = 1,
is_percentage = false
},
['stack_inserter_capacity_bonus'] = {
initial_value = 0,
max_value = 0,
value_step = 0,
scale = 1,
cost = 1,
is_percentage = false
},
['artillery_range_modifier'] = {
initial_value = 0,
max_value = 0,
value_step = 0,
scale = 1,
cost = 1,
is_percentage = false
}
]]
},
surface_bonus = {
--[[
['solar_power_multiplier'] = {
initial_value = 1,
max_value = 1000,
value_step = 1,
scale = 1,
cost = 1,
is_percentage = false
}
]]
},
}
+1 -6
View File
@@ -24,15 +24,11 @@ local afk_time_units = {
}
return {
--- @type table<string, LocalisedString | fun(player: LuaPlayer, is_command: boolean): LocalisedString>
messages = { --- @setting messages will trigger when ever the word is said
["discord"] = { "info.discord" },
["expgaming"] = { "info.website" },
["aperx"] = { "info.website" },
["website"] = { "info.website" },
["status"] = { "info.status" },
["github"] = { "info.github" },
["patreon"] = { "info.patreon" },
["donate"] = { "info.patreon" },
["command"] = { "info.custom-commands" },
["commands"] = { "info.custom-commands" },
["softmod"] = { "info.softmod" },
@@ -67,7 +63,6 @@ return {
command_admin_only = false, --- @setting command_admin_only when true will only allow chat commands for admins
command_permission = "command/chat-commands", --- @setting command_permission the permission used to allow command prefixes
command_prefix = "!", --- @setting command_prefix prefix used for commands below and to print to all players (if enabled above)
--- @type table<string, LocalisedString | fun(player: LuaPlayer, is_command: boolean): LocalisedString>
commands = { --- @setting commands will trigger only when command prefix is given
["dev"] = { "exp_chat-auto-reply.reply-dev" },
["magic"] = { "exp_chat-auto-reply.reply-magic" },
+1 -1
View File
@@ -7,7 +7,7 @@ return {
["Spawn"] = {
spawn_position = { x = 0, y = 0 },
spawn_surface = "nauvis",
entity_name = "behemoth-biter",
entity_name = "small-biter",
messages = {
{ "info.website" },
{ "info.read-readme" },
@@ -1,5 +1,5 @@
--- Use this file to add new permission groups to the game;
-- start with Permission_Groups.new_group('name');
-- start with Permission_Groups.new_group("name");
-- then use either :allow_all() or :disallow_all() to set the default for non specified actions;
-- then use :allow{} and :disallow{} to specify certain actions to allow/disallow
-- @config Permission-Groups
@@ -13,7 +13,7 @@ Groups.new_group("Admin")
Groups.new_group("Mod")
:allow_all()
:disallow{
"add_permission_group",
"add_permission_group", -- admin
"delete_permission_group",
"edit_permission_group",
"import_permissions_string",
@@ -25,7 +25,7 @@ Groups.new_group("Mod")
Groups.new_group("Trusted")
:allow_all()
:disallow{
"add_permission_group", -- mod
"add_permission_group", -- admin
"delete_permission_group",
"edit_permission_group",
"import_permissions_string",
@@ -43,44 +43,52 @@ Groups.new_group("Trusted")
Groups.new_group("Standard")
:allow_all()
:disallow{
"add_permission_group", -- mod
"add_permission_group", -- admin
"delete_permission_group",
"edit_permission_group",
"import_permissions_string",
"map_editor_action",
"toggle_map_editor",
"change_multiplayer_config",
"set_heat_interface_mode", -- trusted
"set_heat_interface_mode",
"set_heat_interface_temperature",
"set_infinity_container_filter_item",
"set_infinity_container_remove_unfiltered_items",
"set_infinity_pipe_filter",
"admin_action",
"admin_action", -- trusted
"delete_space_platform",
"change_programmable_speaker_alert_parameters", -- standard
}
Groups.new_group("Guest")
:allow_all()
:disallow{
"add_permission_group", -- mod
"add_permission_group", -- admin
"delete_permission_group",
"edit_permission_group",
"import_permissions_string",
"map_editor_action",
"toggle_map_editor",
"change_multiplayer_config",
"set_heat_interface_mode", -- trusted
"set_heat_interface_mode",
"set_heat_interface_temperature",
"set_infinity_container_filter_item",
"set_infinity_container_remove_unfiltered_items",
"set_infinity_pipe_filter",
"admin_action",
"admin_action", -- trusted
"delete_space_platform",
"change_programmable_speaker_alert_parameters", -- standard
"change_programmable_speaker_parameters", -- guest
"change_programmable_speaker_circuit_parameters", -- guest
"change_programmable_speaker_parameters",
"drop_item",
"open_new_platform_button_from_rocket_silo",
"set_rocket_silo_send_to_orbit_automated_mode",
"open_new_platform_button_from_rocket_silo",
"instantly_create_space_platform",
"cancel_delete_space_platform",
"rename_space_platform",
-- "launch_rocket",
"change_train_stop_station",
-- "deconstruct",
"remove_cables",
"remove_train_station",
"reset_assembling_machine",
@@ -99,15 +107,15 @@ Groups.new_group("Restricted")
local trusted_time = 60*60*60*10 -- 10 hour
local standard_time = 60*60*60*3 -- 3 hour
local function assign_group(player)
local current_group_name = player.permission_group and player.permission_group.name or 'None'
local current_group_name = player.permission_group and player.permission_group.name or "None"
if player.admin then
Permission_Groups.set_player_group(player,'Admin')
elseif player.online_time > trusted_time or current_group_name == 'Trusted' then
Permission_Groups.set_player_group(player,'Trusted')
elseif player.online_time > standard_time or current_group_name == 'Standard' then
Permission_Groups.set_player_group(player,'Standard')
Permission_Groups.set_player_group(player,"Admin")
elseif player.online_time > trusted_time or current_group_name == "Trusted" then
Permission_Groups.set_player_group(player,"Trusted")
elseif player.online_time > standard_time or current_group_name == "Standard" then
Permission_Groups.set_player_group(player,"Standard")
else
Permission_Groups.set_player_group(player,'Guest')
Permission_Groups.set_player_group(player,"Guest")
end
end
+87 -61
View File
@@ -14,7 +14,7 @@ Roles.define_flag_trigger("is_spectator", function(player, state)
end)
Roles.define_flag_trigger("is_jail", function(player, state)
if player.character then
player.character.disabled_by_script = not state
player.character.active = not state
end
end)
@@ -26,6 +26,7 @@ Roles.new_role("System", "SYS")
:set_flag("is_spectator")
:set_flag("report-immune")
:set_flag("instant-respawn")
:set_flag("deconlog-bypass")
:set_allow_all()
Roles.new_role("Senior Administrator", "SAdmin")
@@ -36,6 +37,7 @@ Roles.new_role("Senior Administrator", "SAdmin")
:set_flag("is_spectator")
:set_flag("report-immune")
:set_flag("instant-respawn")
:set_flag("deconlog-bypass")
:set_parent("Administrator")
:set_allow_all()
@@ -46,6 +48,7 @@ Roles.new_role("Administrator", "Admin")
:set_flag("is_spectator")
:set_flag("report-immune")
:set_flag("instant-respawn")
:set_flag("deconlog-bypass")
:set_parent("Senior Moderator")
:allow{
"command/_rcon",
@@ -58,14 +61,16 @@ Roles.new_role("Senior Moderator", "SMod")
:set_permission_group("Mod")
:set_custom_color{ r = 0, g = 170, b = 0 }
:set_flag("is_admin")
:set_flag("is_system")
:set_flag("is_spectator")
:set_flag("report-immune")
:set_flag("instant-respawn")
:set_flag("deconlog-bypass")
:set_parent("Moderator")
:allow{
"gui/warp-list/bypass-proximity",
"gui/warp-list/bypass-cooldown",
"command/connect-all",
"command/connect-all"
}
Roles.new_role("Moderator", "Mod")
@@ -75,44 +80,56 @@ Roles.new_role("Moderator", "Mod")
:set_flag("is_spectator")
:set_flag("report-immune")
:set_flag("instant-respawn")
:set_flag("deconlog-bypass")
:set_parent("Trainee Moderator")
:allow{
}
Roles.new_role("Trainee Moderator", "TrMod")
Roles.new_role("Trainee Moderator", "Trainee")
:set_permission_group("Mod")
:set_custom_color{ r = 0, g = 170, b = 0 }
:set_flag("is_admin")
:set_flag("is_spectator")
:set_flag("report-immune")
:set_flag("instant-respawn")
:set_flag("deconlog-bypass")
:set_parent("Board Member")
:allow{
"command/assign-role",
"command/unassign-role",
"command/admin-chat",
"command/kill/always",
"command/clear-tag/always",
"command/spawn/always",
"command/clear-reports",
"command/clear-warnings",
"command/clear-script-warnings",
"command/clear-last-warnings",
"command/clear-inventory",
"command/kill-enemies",
"command/remove-enemies",
"command/connect-player",
"command/set-bot-queue",
"command/set-game-speed",
"command/set-friendly-fire",
"command/set-always-day",
"command/set-pollution-enabled",
"command/clear-pollution",
"command/protect-tag",
"command/teleport",
"command/bring",
"command/create-warning",
"command/get-warnings",
"command/get-reports",
"command/protect-entity",
"command/protect-area",
"command/protect-tag",
"command/kick",
"command/ban",
"command/search",
"command/search-online",
"command/search-amount",
"command/search-recent",
"command/clear-pollution",
"command/set-pollution-enabled",
"command/set-bot-queue",
"command/set-game-speed",
"command/kill-enemies",
"command/remove-enemies",
"command/set-friendly-fire",
"command/set-always-day",
"command/clear-reports",
"command/clear-warnings",
"command/clear-script-warnings",
"command/clear-last-warnings",
"command/clear-inventory",
"command/connect-player",
"command/kill/always",
-- "command/clear-tag/always",
"command/spawn/always",
"gui/playerdata"
}
--- Trusted Roles
@@ -122,49 +139,60 @@ Roles.new_role("Board Member", "Board")
:set_flag("is_spectator")
:set_flag("report-immune")
:set_flag("instant-respawn")
:set_flag("deconlog-bypass")
:set_parent("Supporter")
:allow{
"gui/playerdata",
"command/teleport",
"command/bring",
"command/search",
"command/search-online",
"command/search-amount",
"command/search-recent",
"command/spectate",
"command/follow",
"command/clear-blueprints-surface",
}
Roles.new_role("Supporter", "Sup")
:set_permission_group("Trusted")
:set_custom_color{ r = 230, g = 99, b = 34 }
:set_flag("is_spectator")
:set_flag("instant-respawn")
:set_flag("deconlog-bypass")
:set_parent("Partner")
:allow{
}
Roles.new_role("Partner", "Part")
:set_permission_group("Trusted")
:set_custom_color{ r = 140, g = 120, b = 200 }
:set_custom_color{ r = 24, g = 172, b = 188 }
:set_flag("is_spectator")
:set_flag("instant-respawn")
:set_flag("deconlog-bypass")
:set_parent("Senior Member")
:allow{
}
Roles.new_role("Senior Member", "SMem")
:set_permission_group("Trusted")
:set_custom_color{ r = 140, g = 120, b = 200 }
:set_custom_color{ r = 24, g = 172, b = 188 }
:set_flag("is_spectator")
:set_flag("deconlog-bypass")
:set_flag("instant-respawn")
:set_parent("Member")
:allow{
}
--- Standard User Roles
Roles.new_role("Member", "Mem")
:set_permission_group("Trusted")
:set_custom_color{ r = 24, g = 172, b = 188 }
:set_flag("instant-respawn")
:set_flag("deconlog-bypass")
:set_parent("Veteran")
:allow{
"fast-tree-decon",
"gui/bonus",
--"command/tag-color",
"command/set-join-message",
"command/remove-join-message",
"gui/vlayer-edit",
"gui/rocket-info/toggle-active",
"gui/rocket-info/remote_launch",
"gui/tool",
-- "command/tag-color",
"command/clear-blueprints",
--"command/bonus",
"gui/bonus",
"command/personal-logistic",
"command/home",
"command/set-home",
"command/get-home",
@@ -172,33 +200,31 @@ Roles.new_role("Member", "Mem")
"command/goto",
"command/jail",
"command/unjail",
"command/repair",
"command/spectate",
"command/follow",
"command/repair",
"fast-tree-decon"
}
local hours6, hours250 = 6 * 216000, 250 * 60
Roles.new_role("Veteran", "Vet")
:set_permission_group("Trusted")
:set_custom_color{ r = 140, g = 120, b = 200 }
:set_flag("deconlog-bypass")
:set_parent("Regular")
:allow{
"gui/surveillance",
"gui/warp-list/add",
"gui/warp-list/edit",
"gui/tool",
"gui/vlayer-edit",
"gui/rocket-info/toggle-active",
"gui/rocket-info/remote_launch",
"command/chat-bot",
"command/chat-commands",
"command/clear-ground-items",
"command/clear-blueprints",
"command/clear-blueprints-radius",
"command/set-trains-to-automatic",
"command/set-auto-research",
"command/lawnmower",
"command/waterfill",
"command/artillery",
"command/save-quickbar",
"command/personal-logistic",
"command/artillery"
}
:set_auto_assign_condition(function(player)
if player.online_time >= hours6 then
@@ -210,16 +236,12 @@ Roles.new_role("Veteran", "Vet")
end
end)
--- Standard User Roles
local hours1, hours15 = 1 * 216000, 15 * 60
Roles.new_role("Regular", "Reg")
:set_permission_group("Standard")
:set_custom_color{ r = 79, g = 155, b = 163 }
:set_parent("Guest")
:allow{
"standard-decon",
"bypass-entity-protection",
"bypass-nukeprotect",
"gui/task-list/add",
"gui/task-list/edit",
"command/kill",
@@ -227,6 +249,9 @@ Roles.new_role("Regular", "Reg")
"command/spawn",
"command/me",
"command/vlayer-info",
"standard-decon",
"bypass-entity-protection",
"bypass-nukeprotect"
}
:set_auto_assign_condition(function(player)
if player.online_time >= hours1 then
@@ -243,17 +268,6 @@ local default = Roles.new_role("Guest", "")
:set_permission_group("Guest")
:set_custom_color{ r = 185, g = 187, b = 160 }
:allow{
"gui/player-list",
"gui/rocket-info",
"gui/science-info",
"gui/task-list",
"gui/warp-list",
"gui/readme",
"gui/vlayer",
"gui/research",
"gui/autofill",
"gui/module",
"gui/production",
-- "command/tag",
-- "command/tag-clear",
"command/commands",
@@ -263,8 +277,20 @@ local default = Roles.new_role("Guest", "")
"command/ratio",
"command/server-ups",
"command/save-data",
"command/save-quickbar",
"command/data-preference",
"command/connect",
"gui/player-list",
--"gui/rocket-info",
"gui/science-info",
"gui/task-list",
"gui/warp-list",
"gui/readme",
"gui/vlayer",
"gui/research",
"gui/autofill",
"gui/module",
"gui/production",
}
--- Jail role
@@ -294,7 +320,7 @@ Roles.define_role_order{
"Veteran",
"Regular",
"Jail",
"Guest", -- Default must be last if you want to apply restrictions to other roles
"Guest" -- Default must be last if you want to apply restrictions to other roles
}
Roles.override_player_roles{
@@ -14,11 +14,9 @@ local Jail = require("modules.exp_legacy.modules.control.jail") --- @dep modules
local Colors = require("modules/exp_util/include/color")
local format_player_name = ExpUtil.format_player_name_locale
--- Accessors injected by the gui so the actions can read the selected player and set the selected action
local get_selected_player --- @type fun(player: LuaPlayer): LuaPlayer
local set_selected_action --- @type fun(player: LuaPlayer, action: string?)
local function set_accessors(player_getter, action_setter)
get_selected_player, set_selected_action = player_getter, action_setter
local SelectedPlayer, SelectedAction
local function set_datastores(player, action)
SelectedPlayer, SelectedAction = player, action
end
-- auth that will only allow when on player's of lower roles
@@ -31,10 +29,11 @@ local function auth_lower_role(player, selected_player_name)
end
-- gets the action player and a coloured name for the action to be used on
local function get_action_player(player)
local selected_player = get_selected_player(player) --[[@as LuaPlayer]]
local function get_action_player_name(player)
local selected_player_name = SelectedPlayer:get(player)
local selected_player = game.players[selected_player_name]
local selected_player_color = format_player_name(selected_player)
return selected_player, selected_player_color
return selected_player_name, selected_player_color
end
-- teleports one player to another
@@ -64,11 +63,12 @@ end
--- Teleports the user to the action player
-- @element goto_player
local goto_player = new_button("utility/export", { "exp-gui_player-list.goto-player" })
local goto_player = new_button("utility/export", { "player-list.goto-player" })
:on_click(function(def, player, element)
local selected_player = get_action_player(player)
local selected_player_name = get_action_player_name(player)
local selected_player = game.players[selected_player_name]
if not player.character or not selected_player.character then
player.print({ "exp-commands-parse.player-alive" }, Colors.orange_red)
player.print({ "expcore-commands.reject-player-alive" }, Colors.orange_red)
else
teleport(player, selected_player)
end
@@ -76,11 +76,12 @@ local goto_player = new_button("utility/export", { "exp-gui_player-list.goto-pla
--- Teleports the action player to the user
-- @element bring_player
local bring_player = new_button("utility/import", { "exp-gui_player-list.bring-player" })
local bring_player = new_button("utility/import", { "player-list.bring-player" })
:on_click(function(def, player, element)
local selected_player = get_action_player(player)
local selected_player_name = get_action_player_name(player)
local selected_player = game.players[selected_player_name]
if not player.character or not selected_player.character then
player.print({ "exp-commands-parse.player-alive" }, Colors.orange_red)
player.print({ "expcore-commands.reject-player-alive" }, Colors.orange_red)
else
teleport(selected_player, player)
end
@@ -88,83 +89,83 @@ local bring_player = new_button("utility/import", { "exp-gui_player-list.bring-p
--- Reports the action player, requires a reason to be given
-- @element report_player
local report_player = new_button("utility/spawn_flag", { "exp-gui_player-list.report-player" })
local report_player = new_button("utility/spawn_flag", { "player-list.report-player" })
:on_click(function(def, player, element)
local selected_player = get_action_player(player)
if Reports.is_reported(selected_player.name, player.name) then
local selected_player_name = get_action_player_name(player)
if Reports.is_reported(selected_player_name, player.name) then
player.print({ "exp-commands_report.already-reported" }, Colors.orange_red)
else
set_selected_action(player, "command/report")
SelectedAction:set(player, "command/report")
end
end)
local function report_player_callback(player, reason)
local selected_player, selected_player_color = get_action_player(player)
local selected_player_name, selected_player_color = get_action_player_name(player)
local by_player_name_color = format_player_name(player)
game.print{ "exp-commands_reports.response", selected_player_color, reason }
Roles.print_to_roles_higher("Trainee", { "exp-commands_reports.response-admin", selected_player_color, by_player_name_color, reason })
Reports.report_player(selected_player.name, player.name, reason)
Reports.report_player(selected_player_name, player.name, reason)
end
--- Gives the action player a warning, requires a reason
-- @element warn_player
local warn_player = new_button("utility/spawn_flag", { "exp-gui_player-list.warn-player" })
local warn_player = new_button("utility/spawn_flag", { "player-list.warn-player" })
:on_click(function(def, player, element)
set_selected_action(player, "command/give-warning")
SelectedAction:set(player, "command/give-warning")
end)
local function warn_player_callback(player, reason)
local selected_player, selected_player_color = get_action_player(player)
local selected_player_name, selected_player_color = get_action_player_name(player)
local by_player_name_color = format_player_name(player)
game.print{ "exp-commands_warnings.create", selected_player_color, by_player_name_color, reason }
Warnings.add_warning(selected_player.name, player.name, reason)
Warnings.add_warning(selected_player_name, player.name, reason)
end
--- Jails the action player, requires a reason
-- @element jail_player
local jail_player = new_button("utility/multiplayer_waiting_icon", { "exp-gui_player-list.jail-player" })
local jail_player = new_button("utility/multiplayer_waiting_icon", { "player-list.jail-player" })
:on_click(function(def, player, element)
local selected_player, selected_player_color = get_action_player(player)
if Jail.is_jailed(selected_player.name) then
local selected_player_name, selected_player_color = get_action_player_name(player)
if Jail.is_jailed(selected_player_name) then
player.print({ "exp-commands_jail.already-jailed", selected_player_color }, Colors.orange_red)
else
set_selected_action(player, "command/jail")
SelectedAction:set(player, "command/jail")
end
end)
local function jail_player_callback(player, reason)
local selected_player, selected_player_color = get_action_player(player)
local selected_player_name, selected_player_color = get_action_player_name(player)
local by_player_name_color = format_player_name(player)
game.print{ "exp-commands_jail.jailed", selected_player_color, by_player_name_color, reason }
Jail.jail_player(selected_player.name, player.name, reason)
Jail.jail_player(selected_player_name, player.name, reason)
end
--- Kicks the action player, requires a reason
-- @element kick_player
local kick_player = new_button("utility/warning_icon", { "exp-gui_player-list.kick-player" })
local kick_player = new_button("utility/warning_icon", { "player-list.kick-player" })
:on_click(function(def, player, element)
set_selected_action(player, "command/kick")
SelectedAction:set(player, "command/kick")
end)
local function kick_player_callback(player, reason)
local selected_player = get_action_player(player)
local selected_player = get_action_player_name(player)
game.kick_player(selected_player, reason)
end
--- Bans the action player, requires a reason
-- @element ban_player
local ban_player = new_button("utility/danger_icon", { "exp-gui_player-list.ban-player" })
local ban_player = new_button("utility/danger_icon", { "player-list.ban-player" })
:on_click(function(def, player, element)
set_selected_action(player, "command/ban")
SelectedAction:set(player, "command/ban")
end)
local function ban_player_callback(player, reason)
local selected_player = get_action_player(player)
local selected_player = get_action_player_name(player)
game.ban_player(selected_player, reason)
end
return {
set_accessors = set_accessors,
set_datastores = set_datastores,
buttons = {
["command/teleport"] = {
auth = function(player, selected_player)
+1 -1
View File
@@ -4,6 +4,6 @@
local events = defines.events
return {
events.on_player_banned,
-- events.on_player_kicked,
events.on_player_kicked,
-- events.on_player_left_game
}
-1
View File
@@ -4,5 +4,4 @@
return {
fluid = true, --- @setting fluid When true, checks for for fluid pipes when removing miners
chest = true, --- @setting chest When true, checks for for chest when removing miners
beacon = true, --- @setting beacon When true, checks for for beacon when removing miners
}
File diff suppressed because it is too large Load Diff
+4 -2
View File
@@ -5,13 +5,15 @@ return {
repeat_lifetime = 3600 * 20, --- @setting repeat_lifetime The length of time, in ticks, that protected removals will be remembered for
refresh_rate = 3600 * 5, --- @setting refresh_rate How often the age of protected removals are checked against repeat_lifetime
always_protected_names = { --- @setting always_protected_names Names of entities which are always protected
},
always_protected_types = { --- @setting always_protected_types Types of entities which are always protected
"boiler", "generator", "offshore-pump", "reactor", "heat-exchanger", "heat-pipe", "fusion-reactor", "fusion-generator", "power-switch", "rocket-silo",
"boiler", "generator", "offshore-pump", "power-switch", "reactor", "rocket-silo",
},
always_trigger_repeat_names = { --- @setting always_trigger_repeat_names Names of entities which always trigger repeated removal protection
},
always_trigger_repeat_types = { --- @setting always_trigger_repeat_types Types of entities which always trigger repeated removal protection
"reactor", "fusion-reactor", "rocket-silo",
"reactor", "rocket-silo",
},
}
+8
View File
@@ -2,6 +2,14 @@
-- @config Repair
return {
disallow = { --- @setting disallow items in this list will never be repaired
["loader"] = true,
["fast-loader"] = true,
["express-loader"] = true,
["electric-energy-interface"] = true,
["infinity-chest"] = true,
},
max_range = 50, --- @setting max_range the max range that can be used with the repair command
allow_blueprint_repair = false, --- @setting allow_blueprint_repair when true will allow blueprints (things not destroyed by biters) to be build instantly using the repair command
allow_ghost_revive = true, --- @setting allow_ghost_revive when true will allow ghosts (things destroyed by biters) to be build instantly using the repair command
allow_heal_entities = true, --- @setting allow_heal_entities when true will heal entities to full health that are within range
+11 -13
View File
@@ -12,6 +12,16 @@ return {
-- this enable 20 more inventory for each mining productivity level up to 4
bonus_inventory = {
enabled = true,
log = {
["base"] = {
["name"] = "mining-productivity-4",
["level"] = 4
},
["space-age"] = {
["name"] = "mining-productivity-3",
["level"] = 3
},
},
res = {
-- Mining Productivity
["mining-productivity"] = true,
@@ -23,19 +33,7 @@ return {
rate = 5,
limit = 20,
},
file = {
name = "log/research.log",
final_res = {
["base"] = {
["name"] = "mining-productivity-4",
["level"] = 4
},
["space-age"] = {
["name"] = "mining-productivity-3",
["level"] = 3
}
}
},
file_name = "log/research.log",
milestone = {
["base"] = {
["automation"] = 600,
+12 -11
View File
@@ -5,8 +5,8 @@ return {
spawn_area = { --- @setting spawn_area Settings relating to the whole spawn area
-- Enable predefined patches: 128, else: 32
deconstruction_radius = 12, -- @setting deconstruction_radius All entities within this radius will be removed
tile_radius = 14,
deconstruction_tile = "refined-concrete", --- @setting deconstruction_tile Tile to be placed in the deconstruction radius, use nil for map gen
tile_radius = 14,
landfill_radius = 16, --- @setting pattern_radius All water within this radius will be land filled
},
turrets = { --- @setting turrets Settings relating to adding turrets to spawn
@@ -31,18 +31,19 @@ return {
{ 4, -4 },
{ -4, 4 },
{ -4, -4 }
},
}
},
water = { --- @setting water Settings relating to adding water to spawn
enabled = true, --- @setting enabled Whether water tiles will be added to spawn
water_tile = "water-mud", --- @setting water_tile The tile to be used as the water tile
offset = { x = 0, y = 0 }, --- @setting offset The position offset to apply to water tiles
locations = { --- @setting locations The location of the water tiles {x,y}
-- Each is a 3x3 with the closest tile to 0,0 removed
{ 5, 6 }, { 5, 7 }, { 6, 5 }, { 6, 6 }, { 6, 7 }, { 7, 5 }, { 7, 6 }, { 7, 7 },
{ 5, -7 }, { 5, -8 }, { 6, -6 }, { 6, -7 }, { 6, -8 }, { 7, -6 }, { 7, -7 }, { 7, -8 },
{ -6, 6 }, { -6, 7 }, { -7, 5 }, { -7, 6 }, { -7, 7 }, { -8, 5 }, { -8, 6 }, { -8, 7 },
{ -6, -7 }, { -6, -8 }, { -7, -6 }, { -7, -7 }, { -7, -8 }, { -8, -6 }, { -8, -7 }, { -8, -8 },
},
}
},
entities = { --- @setting entities Settings relating to adding entities to spawn
enabled = true, --- @setting enabled Whether entities will be added to spawn
@@ -69,14 +70,14 @@ return {
{ "stone-wall", -8, 1 }, { "stone-wall", -8, 2 }, { "stone-wall", -8, 3 }, { "stone-wall", -8, 4 },
{ "stone-wall", -2, -8 }, { "stone-wall", -3, -8 }, { "stone-wall", -4, -8 }, { "stone-wall", -5, -8 },
{ "stone-wall", -8, -2 }, { "stone-wall", -8, -3 }, { "stone-wall", -8, -4 }, { "stone-wall", -8, -5 },
},
}
},
pattern = {
enabled = false, --- @setting enabled Whether pattern tiles will be added to spawn
pattern_tile = "refined-concrete", --- @setting pattern_tile The tile to be used for the pattern
offset = { x = 0, y = 0 }, --- @setting offset The position offset to apply to pattern tiles
locations = { --- @setting locations The location of the pattern tiles {x,y}
},
}
},
resource_tiles = {
enabled = false,
@@ -87,7 +88,7 @@ return {
amount = 4000,
size = { 26, 27 },
-- offset = {-64,-32}
offset = { x = -64, y = -64 },
offset = { -64, -64 },
},
{
enabled = false,
@@ -95,7 +96,7 @@ return {
amount = 4000,
size = { 26, 27 },
-- offset = {-64, 0}
offset = { x = 64, y = -64 },
offset = { 64, -64 },
},
{
enabled = false,
@@ -103,7 +104,7 @@ return {
amount = 4000,
size = { 22, 20 },
-- offset = {-64, 32}
offset = { x = -64, y = 64 },
offset = { -64, 64 },
},
{
enabled = false,
@@ -111,7 +112,7 @@ return {
amount = 4000,
size = { 22, 20 },
-- offset = {-64, -64}
offset = { x = 64, y = 64 },
offset = { 64, 64 },
},
{
enabled = false,
@@ -119,7 +120,7 @@ return {
amount = 4000,
size = { 22, 20 },
-- offset = {-64, -96}
offset = { x = 0, y = 64 },
offset = { 0, 64 },
},
},
},
@@ -132,7 +133,7 @@ return {
num_patches = 4,
amount = 4000000,
-- offset = {-80, -12},
offset = { x = -12, y = 64 },
offset = { -12, 64 },
-- offset_next = {0, 6}
offset_next = { 6, 0 },
},
+10 -24
View File
@@ -8,8 +8,8 @@ return {
unlimited_capacity = false, --- @setting unlimited_capacity When true the vlayer has an unlimited energy capacity, accumulators are not required
unlimited_surface_area = false, --- @setting unlimited_surface_area When true the vlayer has an unlimited surface area, landfill is not required
modded_auto_downgrade = false, --- @setting modded_auto_downgrade When true modded items will be converted into their base game equivalent, original items can not be recovered
power_on_space = false, --- @setting power_on_space When true allow on spaceship
modded_auto_downgrade = true, --- @setting modded_auto_downgrade When true modded items will be converted into their base game equivalent, original items can not be recovered
power_on_space = true, --- @setting power_on_space When true allow on spaceship
power_on_space_research = { --- @setting power_on_space_research the research level needed to use power_on_space
name = "research-productivity",
level = 10
@@ -29,26 +29,12 @@ return {
},
interface_limit = { --- @setting interface_limit Sets the limit for the number of vlayer interfaces that can be created
energy = 1, -- >1 allows for disconnected power networks to receive power
circuit = 20, -- No caveats
storage_input = 20, -- No caveats
storage_output = 1, -- >0 allows for item teleportation (allowed_items only)
energy = 100, -- >1 allows for disconnected power networks to receive power
circuit = 100, -- No caveats
storage_input = 100, -- No caveats
storage_output = 100, -- >0 allows for item teleportation (allowed_items only)
},
--- @class Vlayer.ItemProperties
--- @field starting_value number
--- @field required_area number
--- @field production number?
--- @field discharge number?
--- @field capacity number?
--- @field surface_area number?
--- @field fuel_value number?
--- @field power boolean?
--- @field modded boolean? Set when the item is registered from a modded equivalent
--- @field base_game_equivalent string?
--- @field multiplier number?
--- @type table<string, Vlayer.ItemProperties>
allowed_items = { --- @setting allowed_items List of all items allowed in vlayer storage and their properties
--[[
Allowed properties:
@@ -87,22 +73,22 @@ return {
required_area = 0,
surface_area = 0,
fuel_value = 4, -- MJ
power = false, -- turn all coal to power to reduce trash
power = true, -- turn all coal to power to reduce trash
},
["solid-fuel"] = {
starting_value = 0,
required_area = 0,
surface_area = 0,
fuel_value = 12, -- MJ
power = false, -- turn all solid fuel to power to reduce trash
power = true, -- turn all solid fuel to power to reduce trash
},
["rocket-fuel"] = {
starting_value = 0,
required_area = 0,
surface_area = 0,
fuel_value = 100, -- MJ
power = false, -- turn all rocket fuel to power to reduce trash
}
power = true, -- turn all rocket fuel to power to reduce trash
},
},
modded_items = { --- @setting modded_items List of all modded items allowed in vlayer storage and their base game equivalent
-1
View File
@@ -2,7 +2,6 @@
-- @config Warnings
return {
--- @type (LocalisedString[] | fun(player: LuaPlayer, by_player_name: string, number_of_warnings: number))[]
actions = { --- @setting actions what actions are taking at number of warnings
-- if a localized string is used then __1__ will by_player_name and __2__ will be the current warning count (auto inserted)
{ "warnings.received", "" },
+2 -16
View File
@@ -149,23 +149,9 @@ PlayerData.Statistics:combine('JoinCount')
local Storage = require("modules/exp_util/storage")
local DatastoreManager = {}
local Datastores = {} --- @type table<string, Datastore>
local Data = {}
--- @class Datastore
--- @field name string
--- @field value_name string
--- @field auto_save boolean
--- @field save_to_disk boolean
--- @field propagate_changes boolean
--- @field serializer function | false
--- @field parent Datastore | false
--- @field children table<string, Datastore>
--- @field metadata table
--- @field events table<string, function[]>
--- @field data table
local Datastores = {}
local Datastore = {}
local Data = {}
local copy = table.deep_copy
local trace = debug.traceback
local table_to_json = helpers.table_to_json
+8 -10
View File
@@ -14,8 +14,7 @@ end
]]
local ext --- @type table<string, any>
local var --- @type table<string, any>
local ext, var
local concat = table.concat
local External = {}
@@ -30,13 +29,12 @@ end
]]
function External.valid()
local stored = storage.ext
if stored == nil then return false end
if ext == stored and var == ext.var then
if storage.ext == nil then return false end
if ext == storage.ext and var == ext.var then
return var ~= nil
else
ext = stored
var = stored.var
ext = storage.ext
var = ext.var
return var ~= nil
end
end
@@ -50,7 +48,7 @@ local servers = External.get_servers()
]]
function External.get_servers()
assert(ext, "No external data was found, use External.valid() to ensure external data exists.")
return (assert(ext.servers, "No server list was found, please ensure that the external service is running"))
return assert(ext.servers, "No server list was found, please ensure that the external service is running")
end
--[[-- Gets a table of all the servers filtered by name, key is the server id, value is the server details
@@ -85,7 +83,7 @@ function External.get_current_server()
assert(ext, "No external data was found, use External.valid() to ensure external data exists.")
local servers = assert(ext.servers, "No server list was found, please ensure that the external service is running")
local server_id = assert(ext.current, "No current id was found, please ensure that the external service is running")
return (assert(servers[server_id], "No details found for server with id: " .. tostring(server_id)))
return assert(servers[server_id], "No details found for server with id: " .. tostring(server_id))
end
--[[-- Gets the details of the given server
@@ -99,7 +97,7 @@ local server = External.get_server_details('eu-01')
function External.get_server_details(server_id)
assert(ext, "No external data was found, use External.valid() to ensure external data exists.")
local servers = assert(ext.servers, "No server list was found, please ensure that the external service is running")
return (assert(servers[server_id], "No details found for server with id: " .. tostring(server_id)))
return assert(servers[server_id], "No details found for server with id: " .. tostring(server_id))
end
--[[-- Gets the status of the given server
@@ -142,7 +142,7 @@ group:set_action('toggle_map_editor', false)
]]
function PermissionsGroups._prototype:set_action(action, state)
local input_action = defines.input_action[action] --[[@as defines.input_action?]]
local input_action = defines.input_action[action] --[[ @as defines.input_action? ]]
if input_action == nil then input_action = action end
assert(type(input_action) == "number", tostring(action) .. " is not a valid input action")
self.actions[input_action] = state
@@ -348,10 +348,8 @@ function PermissionsGroups._prototype:print(message)
end
-- when the game starts it will make the permission groups
--[[
Event.on_init(function()
PermissionsGroups.reload_permissions()
end)
]]
return PermissionsGroups
+1 -1
View File
@@ -55,7 +55,7 @@ PlayerData:set_serializer(Datastore.name_serializer) -- use player name
--- Store and enum for the data saving preference
local DataSavingPreference = PlayerData:combine("DataSavingPreference")
local PreferenceEnum = { "All", "Statistics", "Settings", "Required" } --- @type table<string | number, string | number>
local PreferenceEnum = { "All", "Statistics", "Settings", "Required" }
for k, v in ipairs(PreferenceEnum) do PreferenceEnum[v] = k end
DataSavingPreference:set_default("All")
+12 -31
View File
@@ -117,28 +117,11 @@ local Groups = require("modules.exp_legacy.expcore.permission_groups")
local Colours = ExpUtil.color
local write_json = ExpUtil.write_json
--- @class Roles.Role
--- @field name string
--- @field short_hand string
--- @field index number Position within the role order
--- @field allowed_actions table<string, boolean>
--- @field allow_all_actions boolean
--- @field flags table<string, boolean>
--- @field disallowed_actions table<string, boolean>?
--- @field permission_group ([boolean, string] | string)?
--- @field custom_tag string?
--- @field custom_color Color?
--- @field parent string?
--- @field block_auto_assign boolean?
--- @field auto_assign_condition function?
--- @field set_allow_all fun(self: Roles.Role, state: boolean?): Roles.Role Defined on Roles._prototype
local Roles = {
_prototype = {},
config = {
order = {}, --- @type string[] Contains the order of the roles, lower index is better
roles = {}, --- @type table<string, Roles.Role> Contains the raw info for the roles, indexed by role name
order = {}, -- Contains the order of the roles, lower index is better
roles = {}, -- Contains the raw info for the roles, indexed by role name
flags = {}, -- Contains functions that run when a flag is added/removed from a player
internal = {}, -- Contains all internally accessed roles, such as root, default
players = {}, -- Contains the roles that players have
@@ -217,7 +200,7 @@ function Roles.debug()
local output = ""
for index, role_name in ipairs(Roles.config.order) do
local role = Roles.config.roles[role_name]
local color = (role.custom_color or Colours.white) --[[@as Color.struct]]
local color = role.custom_color or Colours.white
local color_str = string.format("[color=%d, %d, %d]", color.r, color.g, color.b)
output = output .. string.format("\n%s %s) %s[/color]", color_str, index, serpent.line(role))
end
@@ -304,7 +287,6 @@ local role = Roles.get_role_by_name(2)
]]
function Roles.get_role_by_order(index)
local name = Roles.config.order[index]
if not name then return end
return Roles.config.roles[name]
end
@@ -366,17 +348,16 @@ end
local role = Roles.get_player_highest_role(game.player)
]]
--- @return Roles.Role
function Roles.get_player_highest_role(player)
local roles = Roles.get_player_roles(player)
local highest --- @type Roles.Role?
local highest
for _, role in ipairs(roles) do
if not highest or role.index < highest.index then
highest = role
end
end
return (assert(highest, "Player has no roles"))
return highest
end
--- Assignment.
@@ -477,7 +458,7 @@ function Roles.unassign_player(player, roles, by_player_name, skip_checks, silen
-- If the player has a role that needs to defer the role changes, save the roles that need to be unassigned later into a table
local defer_changes = Roles.player_has_flag(player, "defer_role_changes")
if defer_changes and valid_player then
if defer_changes then
local assign_later = Roles.config.deferred_roles[valid_player.name] or {}
for _, role in ipairs(role_objects) do
local role_change = assign_later[role.name]
@@ -738,7 +719,7 @@ local role = Roles.new_role('Moderator', 'Mod')
]]
function Roles.new_role(name, short_hand)
ExpUtil.assert_not_runtime()
if Roles.config.roles[name] then error("Role name is non unique") end
if Roles.config.roles[name] then return error("Role name is non unique") end
local role = setmetatable({
name = name,
short_hand = short_hand or name,
@@ -913,9 +894,10 @@ role:set_permission_group('Admin')
function Roles._prototype:set_permission_group(name, use_factorio_api)
ExpUtil.assert_not_runtime()
if use_factorio_api then
self.permission_group = { true, name } --[[@as [boolean, string] ]]
self.permission_group = { true, name }
else
assert(Groups.get_group_by_name(name), "Permission group not found: " .. name)
local group = Groups.get_group_by_name(name)
if not group then return end
self.permission_group = name
end
return self
@@ -1133,9 +1115,8 @@ local function role_update(event)
-- Updates the players permission group
local highest = Roles.get_player_highest_role(player)
if highest.permission_group then
local permission_group = highest.permission_group --[[@as [boolean, string] ]]
if permission_group[1] then
local group = game.permissions.get_group(permission_group[2])
if highest.permission_group[1] then
local group = game.permissions.get_group(highest.permission_group[2])
if group then
Groups.add_to_permission_group_async(group, player)
end
+2 -7
View File
@@ -1,22 +1,17 @@
[links]
discord=https://discord.aperx.org
website=https://aperx.org
status=https://aperx.org
github=https://github.com/PHIDIAS0303/ExpCluster
patreon=https://aperx.org
discord=
[info]
players-online=There are __1__ players online
total-map-time=This map has been on for __1__
discord=Join us on our discord at: https://discord.aperx.org
website=Please visit our website at: https://aperx.org
status=Want to check if out servers are down? Visit: https://aperx.org
github=Want to help improve our server with some extra features? Help us at: https://github.com/PHIDIAS0303/ExpCluster
patreon=Consider supporting our server at: https://aperx.org
discord=
custom-commands=We use custom commands, such as /tag and /me, use /chelp for more info.
read-readme=Make sure you have read the information gui (It can be found through the info mark on the top left)
softmod=We run a softmod on our servers. A softmod is a custom scenario that runs on this server, an example is the player list.
redmew=We don't talk about redmew here; they beat us to 1000 members ;-;
lhd=All trains must be LHD! This is a long standing rule on our servers, please respect this.
[warnings]
+1 -1
View File
@@ -2,7 +2,7 @@
description-add=Sets / Gets your custom join message.
description-remove=Removes you custom join message.
arg-message=Your custom join message.
greet=[color=0,1,0] Welcome to explosive gaming community server! If you like the server join our discord: __1__ [/color]
greet=[color=0,1,0] Welcome to APERX gaming community server! __1__[/color]
message-set=Your join message has been updated.
message-get=Your join message is: __1__
message-cleared=Your join message has been cleared.
+108
View File
@@ -14,6 +14,46 @@ ban-player=Ban player
afk-time=__1__% of total map time\nLast moved __2__ ago
open-map=__1__ __2__\n__3__\nClick to open map
[rocket-info]
main-tooltip=Rocket Info
launch-tooltip=Launch rocket
launch-tooltip-disabled=Launch rocket (not ready)
toggle-rocket-tooltip=Disable auto launch
toggle-rocket-tooltip-disabled=Enable auto launch
toggle-section-tooltip=Expand Section
toggle-section-collapse-tooltip=Collapse Section
section-caption-stats=Statistics
section-tooltip-stats=Statistics about how rockets are launched
section-caption-milestones=Milestones
section-tooltip-milestones=The times when milestones were met
section-caption-progress=Build Progress
section-tooltip-progress=The progress for your rocket silos
progress-no-silos=You have no rocket silos
data-caption-first-launch=First Launch
data-tooltip-first-launch=The time of the first launch
data-caption-last-launch=Last Launch
data-tooltip-last-launch=The time of the last launch
data-caption-fastest-launch=Fastest Launch
data-tooltip-fastest-launch=The time taken for the fastest launch
data-caption-total-rockets=Total Launched
data-tooltip-total-rockets=Total number of rockets launched by your force
value-tooltip-total-rockets=__1__% of all rockets on this map
data-caption-avg-launch=Average Time
data-tooltip-avg-launch=The average amount of time taken to launch a rocket
data-caption-avg-launch-n=Average Time __1__
data-tooltip-avg-launch-n=The average amount of time taken to launch the last __1__ rockets
data-caption-milestone-n=Milestone __1__
data-tooltip-milestone-n=Time taken to each __1__ rockets
data-caption-milestone-next=N/A
data-tooltip-milestone-next=Not yet achieved
progress-x-pos=X __1__
progress-y-pos=Y __1__
progress-label-tooltip=View on map
progress-launched=Launched
progress-caption=__1__%
progress-tooltip=This silo has launched __1__ rockets
launch-failed=Failed to launch rocket, please wait a few seconds and try again.
[warp-list]
main-caption=Warp List [img=info]
main-tooltip=Warp List
@@ -45,6 +85,74 @@ goto-cooldown=You are on cooldown, wait for your cooldown to recharge
goto-disabled=You are not on a warp point, walk to a warp point
goto-edit=Edit warp icon
[readme]
main-tooltip=Infomation
welcome-tab=Welcome
welcome-tooltip=Welcome to APERX
welcome-general=Welcome to APERX; we host many factorio servers. While you are here, we ask you to follow our rules. You can find these in the tab above. You can also find our custom commands and links to our other servers. This map has been online for __2__.\nPlease note that our servers reset periodically, the next reset is: __1__
welcome-roles=We run a custom role system to help protect the work of others. As a result you may not be able to use your deconstruction planner yet or drop item on the groud. Roles also give you access to some custom features such as adding tasks to our task list or making new warp points.\nYou have been assigned the roles: __1__
welcome-chat=Chatting can be difficult for new players because its different than other games! Its very simple, the button you need to press is the “GRAVE/TILDE” key (which is located under the “ESC key”) - If you would like to change the key, go to your Controls tab in options.\nThe setting you need to change is “Toggle chat (and Lua console)” you currently have it set to "__CONTROL__toggle-console__"
rules-tab=Rules
rules-tooltip=Rules for our server
rules-general=By playing on our servers you accept the rules below. If you are supected of breaking a rule then you will be questioned by one of our moderators. If required you will be banned from our servers, appeals can be made by contacting an Administrator on our discord.
rules-1=Hacking / cheating / abusing bugs will not be tolerated.
rules-2=Any bugs or exploits found should be reported to our staff members.
rules-3=Be respectful to other players in chat, this includes any other forms of communication.
rules-4=Taking all items from a belt or logistics network is forbidden: sharing resources is mandatory.
rules-5=Spamming of chat, bots, unlimited chests, or concrete is not allowed.
rules-6=Before removing major parts of the factory tell other players why you are removing it.
rules-7=Do not cause unnecessary lag by placing/removing tiles with bots or using active provider chests, if think some thing might cause lag please ask staff first.
rules-8=Do not walk in random directions with no reason, this is to reduce map download times.
rules-9=Do not use speakers on global or with alerts without prior permission, no one wants to hear your music at full volume without knowing where the off switch is.
rules-10=Do not rotate belts, deactivate belts with wires, or cause production to stop without good reason, this goes for inserters and spliters as well.
rules-11=Do not make train roundabouts. Other loops such as RoRo stations are allowed.
rules-12=When using trains, use the same size other players have used, many players use 1-2-1, 2-4-2, or 3-8-3, meaning 1 engine 2 cargo 1 engine.
rules-13=Trains are Left Hand Drive (LHD) only, this means trains drive on the left side of the tracks.
rules-14=Do not beg for roles, advertise other servers, or link to harmful sites.
rules-15=Use common sense, report rule breakers, and Administrators have the final word.
commands-tab=Commands
commands-tooltip=Commands which you are able to use
commands-general=We have lots of custom commands which you are able to use. Below you can find a list of all the commands that you are allowed to use and what they do. If you need more information or want to search for a command you can use our /search-help command.
servers-tab=Servers
servers-tooltip=Links to our other servers and sites
servers-general=This is only one of our servers for factorio, we host many of others as well. Below you can find details about all the servers that we host as well as links to our external services such as discord or github.
servers-factorio=Factorio Servers
servers-1=S1
servers-d1=This server resets regularly.
servers-2=S2
servers-d2=This server resets regularly.
servers-3=S3
servers-d3=This server resets regularly.
servers-5=S5
servers-d5=This server is only for members. Check discord for more details.
servers-6=S6
servers-d6=This server is only for members. Check discord for more details.
servers-8=S8
servers-d8=This server is only for event and others. Check discord for more details.
servers-connect-Offline=Server is currently offline
servers-connect-Current=This is your current server
servers-connect-Version=Server is on a different version: __1__
servers-connect-Password=Server requires a password
servers-connect-Modded=Server requires mods to be downloaded
servers-connect-Online=Server is online
servers-external=External Links
servers-open-in-browser=Open in your browser
backers-tab=Backers
backers-tooltip=People who have helped make our server
backers-general=We would like to thank all our staff and backers who have helped our community grow. Our staff have helped to keep our servers safe from trolls and a fun place to play. Our backers have helped us to cover our running costs and provide a great community for us all to enjoy together.
backers-management=Administrators
backers-staff=Staff Members
backers-backers=Board Members, Supporters, and Partners
backers-active=Active Players
data-tab=Data
data-tooltip=All of your stored player data
data-general=Our servers will save your player data so that we can sync it between servers. All of your data can be found below, if you wish to save a copy locally then use /save-data. If you want to change what data is saved then use /set-preference.
data-settings=Settings
data-statistics=Statistics
data-required=Required
data-misc=Miscellaneous
data-format=__1____2__
[bonus]
description=Get / Set the amount of bonus you receive.
arg-amount=Amount to set your bonus to, 0 will disable bonus.
+2 -7
View File
@@ -1,22 +1,17 @@
[links]
discord=https://discord.aperx.org
website=https://aperx.org
status=https://aperx.org
github=https://github.com/PHIDIAS0303/ExpCluster
patreon=https://aperx.org
discord=
[info]
players-online=現在有 __1__ 人在線
total-map-time=地圖時間為 __1__
discord=加入社群: https://discord.aperx.org
website=訪問網站: https://aperx.org
status=伺服器狀態: https://aperx.org
github=增加功能或回報錯誤: https://github.com/PHIDIAS0303/ExpCluster
patreon=支持我們: https://aperx.org
discord=
custom-commands=這裹使用了自制指令,如 /tag 和 /me。可使用 /chelp 查看更多。
read-readme=確保你已閱讀相關資訊。按左上 i 圖標可再次查看。
softmod=這裹用了自設情境,是一種軟裝模組。
redmew=
lhd=列車必須是左則通行。這是本服務器長久以來的規則。
[warnings]
+1 -1
View File
@@ -2,7 +2,7 @@
description-add=設定 / 取得你的加入信息。
description-remove=移除你的加入信息。
arg-message=你的加入信息。
greet=[color=0,1,0] 歡迎來到 EXP 的服務器! 若果你喜歡本服務器可加入 DISCORD: __1__ [/color]
greet=[color=0,1,0] 歡迎來到 APERX 的服務器! __1__[/color]
message-set=你的加入信息已更新。
message-get=你的加入信息為: __1__
message-cleared=你的加入信息已清除。
+108
View File
@@ -14,6 +14,46 @@ ban-player=封禁用戶
afk-time=__1__% 地圖時間\n上次移動: __2__
open-map=__1__ __2__\n__3__\n按下打開地圖
[rocket-info]
main-tooltip=火箭資訊
launch-tooltip=發射火箭
launch-tooltip-disabled=發射火箭 (未準備)
toggle-rocket-tooltip=取消自動發射
toggle-rocket-tooltip-disabled=啟用自動發射
toggle-section-tooltip=擴張欄
toggle-section-collapse-tooltip=收縮欄
section-caption-stats=數據
section-tooltip-stats=火箭發射數據
section-caption-milestones=里程碑
section-tooltip-milestones=里程碑達成次數
section-caption-progress=建造程度
section-tooltip-progress=火箭建造程度
progress-no-silos=你沒有火箭發射井
data-caption-first-launch=首次發射
data-tooltip-first-launch=首次發射時間
data-caption-last-launch=最後發射
data-tooltip-last-launch=最後發射時間
data-caption-fastest-launch=最快發射
data-tooltip-fastest-launch=最快發射時間
data-caption-total-rockets=總發射次數
data-tooltip-total-rockets=我方總發射次數
value-tooltip-total-rockets=__1__% 的火箭發射都在本地圖上。
data-caption-avg-launch=平均時間
data-tooltip-avg-launch=火箭發射平均時間
data-caption-avg-launch-n=平均時間 __1__
data-tooltip-avg-launch-n=__1__ 次火箭發射的平均時間
data-caption-milestone-n=里程碑 __1__
data-tooltip-milestone-n=__1__ 次火箭發射的時間
data-caption-milestone-next=N/A
data-tooltip-milestone-next=未達成
progress-x-pos=X __1__
progress-y-pos=Y __1__
progress-label-tooltip=在地圖上看
progress-launched=已發射
progress-caption=__1__ %
progress-tooltip=該火箭發射井發射了 __1__ 次
launch-failed=火箭發射失敗, 請過一會再試。
[warp-list]
main-caption=傳送陣清單 [img=info]
main-tooltip=傳送陣清單
@@ -45,6 +85,74 @@ goto-cooldown=傳送冷卻中, 請等候冷卻
goto-disabled=你不在傳送陣, 請移至任意傳送陣
goto-edit=修改傳送陣圖案
[readme]
main-tooltip=資訊
welcome-tab=歡迎
welcome-tooltip=歡迎來到 APERX
welcome-general=歡迎來到 APERX; 你在這裏的時候要遵守規則。 你可以在左上方的介面找到更多資訊。 地圖時間 __2__。\n下次地圖重設: __1__
welcome-roles=我們有自訂的身份組來保護其他用戶。 所以你有機會不能使用拆除規劃器或掉東西。 身份組給予你各種權限\n你有以下身份組: __1__
welcome-chat=你可以使用 「重音符/抑音符」 鍵 (ESC 鍵 下方) - 你也可以在選項中修改按鍵。\n你目前設定為 「__CONTROL__toggle-console__」
rules-tab=規則
rules-tooltip=服務器規則
rules-general=你在本服務器遊玩即等同同意以下規則。管理員有機會就相關事項提問。你可能會因為違反規則而被封禁,你可以在 Discord 中提出反對。
rules-1=開掛或濫用漏洞將不會被容忍。
rules-2=當你找到漏洞可以通知職員。
rules-3=請在聊天室中尊重其他人。
rules-4=請不要把所有物品全部取走。
rules-5=請不要濫發信息,大量鋪設地磚,或不為箱子設置上限,浪費資源。
rules-6=當你想大量拆除原有設施前請通知其他用戶。
rules-7=請不要無故使用機器人來大量鋪設或拆除地磚,濫用主動出貨箱。
rules-8=請不要隨意移動來開大量地圖,這有助減少地圖下載時間。
rules-9=請不要無故使用全圖喇叭。
rules-10=請不要無故轉動傳送帶方向,停用傳送帶,或停止生產。
rules-11=請不要用迴旋處。 火車站尾返程則是例外。
rules-12=請跟其他人使用相同的火車組成。常用的有 1-2-1,2-4-2,或 3-8-3,即1個動車組拖2個貨車組再拖不同方向的1個動車組。
rules-13=火車必須是左上右下 (LHD)。
rules-14=禁止要求身份組,發廣告,發惡意連結。
rules-15=使用常識, 舉報違規人員,管理員有最終決定權。
commands-tab=指令
commands-tooltip=你可用的指令
commands-general=這裏有自訂的指令。你可以在下邊找到你可用的指令及其功能介紹。你可以用 /search-help 來取得更多資訊。
servers-tab=伺服器
servers-tooltip=到其他伺服器及網站的連結
servers-general=這是其中一個伺服器。你可以在下方找到其他同樣是由我們運行的伺服器資料。
servers-factorio=異星工廠伺服器
servers-1=S1
servers-d1=這個服務器定期重設。
servers-2=S2
servers-d2=這個服務器定期重設。
servers-3=S3
servers-d3=這個服務器定期重設。
servers-5=S5
servers-d5=這個服務器只為會員運行,可在 Discord 中得到更多資訊。
servers-6=S6
servers-d6=這個服務器只為會員運行,可在 Discord 中得到更多資訊。
servers-8=S8
servers-d8=這個服務器只在活動和其他時運行,可在 Discord 中得到更多資訊。
servers-connect-Offline=該服務器目前沒有在運行
servers-connect-Current=你目前所在的伺服器
servers-connect-Version=該服務器運行不同的遊戲版本: __1__
servers-connect-Password=該服務器需要密碼
servers-connect-Modded=該服務器需要下載模組
servers-connect-Online=該服務器在線
servers-external=外部連結
servers-open-in-browser=可在你的瀏覽器中打開
backers-tab=支持者
backers-tooltip=支持者
backers-general=他們為服務器運行提供了不少幫助。
backers-management=管理員
backers-staff=職員
backers-backers=議員,支持者,和夥伴
backers-active=活躍玩家
data-tab=資料
data-tooltip=所有已存資料
data-general=資料會自動存到各個服務器中。 你也可以用 /save-data 存一份副本。 也可用 /set-preference 來更改喜好。
data-settings=設定
data-statistics=數據
data-required=需要
data-misc=雜項
data-format=__1____2__
[bonus]
description=取得 / 設定 Bonus 量。
arg-amount=Bonus 數量, 0 來停用。
+2 -7
View File
@@ -1,22 +1,17 @@
[links]
discord=https://discord.aperx.org
website=https://aperx.org
status=https://aperx.org
github=https://github.com/PHIDIAS0303/ExpCluster
patreon=https://aperx.org
discord=
[info]
players-online=現在有 __1__ 人在線
total-map-time=地圖時間為 __1__
discord=加入社群: https://discord.aperx.org
website=訪問網站: https://aperx.org
status=伺服器狀態: https://aperx.org
github=增加功能或回報錯誤: https://github.com/PHIDIAS0303/ExpCluster
patreon=支持我們: https://aperx.org
discord=
custom-commands=這裹使用了自制指令,如 /tag 和 /me。可使用 /chelp 查看更多。
read-readme=確保你已閱讀相關資訊。按左上 i 圖標可再次查看。
softmod=這裹用了自設情境,是一種軟裝模組。
redmew=
lhd=列車必須是左則通行。這是本服務器長久以來的規則。
[warnings]
+1 -1
View File
@@ -2,7 +2,7 @@
description-add=設定 / 取得你的加入信息。
description-remove=移除你的加入信息。
arg-message=你的加入信息。
greet=[color=0,1,0] 歡迎來到 EXP 的服務器! 若果你喜歡本服務器可加入 DISCORD: __1__ [/color]
greet=[color=0,1,0] 歡迎來到 APERX 的服務器! __1__[/color]
message-set=你的加入信息已更新。
message-get=你的加入信息為: __1__
message-cleared=你的加入信息已清除。
+108
View File
@@ -14,6 +14,46 @@ ban-player=封禁用戶
afk-time=__1__% 地圖時間\n上次移動: __2__
open-map=__1__ __2__\n__3__\n按下打開地圖
[rocket-info]
main-tooltip=火箭資訊
launch-tooltip=發射火箭
launch-tooltip-disabled=發射火箭 (未準備)
toggle-rocket-tooltip=取消自動發射
toggle-rocket-tooltip-disabled=啟用自動發射
toggle-section-tooltip=擴張欄
toggle-section-collapse-tooltip=收縮欄
section-caption-stats=數據
section-tooltip-stats=火箭發射數據
section-caption-milestones=里程碑
section-tooltip-milestones=里程碑達成次數
section-caption-progress=建造程度
section-tooltip-progress=火箭建造程度
progress-no-silos=你沒有火箭發射井
data-caption-first-launch=首次發射
data-tooltip-first-launch=首次發射時間
data-caption-last-launch=最後發射
data-tooltip-last-launch=最後發射時間
data-caption-fastest-launch=最快發射
data-tooltip-fastest-launch=最快發射時間
data-caption-total-rockets=總發射次數
data-tooltip-total-rockets=我方總發射次數
value-tooltip-total-rockets=__1__% 的火箭發射都在本地圖上。
data-caption-avg-launch=平均時間
data-tooltip-avg-launch=火箭發射平均時間
data-caption-avg-launch-n=平均時間 __1__
data-tooltip-avg-launch-n=__1__ 次火箭發射的平均時間
data-caption-milestone-n=里程碑 __1__
data-tooltip-milestone-n=__1__ 次火箭發射的時間
data-caption-milestone-next=N/A
data-tooltip-milestone-next=未達成
progress-x-pos=X __1__
progress-y-pos=Y __1__
progress-label-tooltip=在地圖上看
progress-launched=已發射
progress-caption=__1__ %
progress-tooltip=該火箭發射井發射了 __1__ 次
launch-failed=火箭發射失敗, 請過一會再試。
[warp-list]
main-caption=傳送陣清單 [img=info]
main-tooltip=傳送陣清單
@@ -45,6 +85,74 @@ goto-cooldown=傳送冷卻中, 請等候冷卻
goto-disabled=你不在傳送陣, 請移至任意傳送陣
goto-edit=修改傳送陣圖案
[readme]
main-tooltip=資訊
welcome-tab=歡迎
welcome-tooltip=歡迎來到 APERX
welcome-general=歡迎來到 APERX; 你在這裏的時候要遵守規則。 你可以在左上方的介面找到更多資訊。 地圖時間 __2__。\n下次地圖重設: __1__
welcome-roles=我們有自訂的身份組來保護其他用戶。 所以你有機會不能使用拆除規劃器或掉東西。 身份組給予你各種權限\n你有以下身份組: __1__
welcome-chat=你可以使用 「重音符/抑音符」 鍵 (ESC 鍵 下方) - 你也可以在選項中修改按鍵。\n你目前設定為 「__CONTROL__toggle-console__」
rules-tab=規則
rules-tooltip=服務器規則
rules-general=你在本服務器遊玩即等同同意以下規則。管理員有機會就相關事項提問。你可能會因為違反規則而被封禁,你可以在 Discord 中提出反對。
rules-1=開掛或濫用漏洞將不會被容忍。
rules-2=當你找到漏洞可以通知職員。
rules-3=請在聊天室中尊重其他人。
rules-4=請不要把所有物品全部取走。
rules-5=請不要濫發信息,大量鋪設地磚,或不為箱子設置上限,浪費資源。
rules-6=當你想大量拆除原有設施前請通知其他用戶。
rules-7=請不要無故使用機器人來大量鋪設或拆除地磚,濫用主動出貨箱。
rules-8=請不要隨意移動來開大量地圖,這有助減少地圖下載時間。
rules-9=請不要無故使用全圖喇叭。
rules-10=請不要無故轉動傳送帶方向,停用傳送帶,或停止生產。
rules-11=請不要用迴旋處。 火車站尾返程則是例外。
rules-12=請跟其他人使用相同的火車組成。常用的有 1-2-1,2-4-2,或 3-8-3,即1個動車組拖2個貨車組再拖不同方向的1個動車組。
rules-13=火車必須是左上右下 (LHD)。
rules-14=禁止要求身份組,發廣告,發惡意連結。
rules-15=使用常識, 舉報違規人員,管理員有最終決定權。
commands-tab=指令
commands-tooltip=你可用的指令
commands-general=這裏有自訂的指令。你可以在下邊找到你可用的指令及其功能介紹。你可以用 /search-help 來取得更多資訊。
servers-tab=伺服器
servers-tooltip=到其他伺服器及網站的連結
servers-general=這是其中一個伺服器。你可以在下方找到其他同樣是由我們運行的伺服器資料。
servers-factorio=異星工廠伺服器
servers-1=S1
servers-d1=這個服務器定期重設。
servers-2=S2
servers-d2=這個服務器定期重設。
servers-3=S3
servers-d3=這個服務器定期重設。
servers-5=S5
servers-d5=這個服務器只為會員運行,可在 Discord 中得到更多資訊。
servers-6=S6
servers-d6=這個服務器只為會員運行,可在 Discord 中得到更多資訊。
servers-8=S8
servers-d8=這個服務器只在活動和其他時運行,可在 Discord 中得到更多資訊。
servers-connect-Offline=該服務器目前沒有在運行
servers-connect-Current=你目前所在的伺服器
servers-connect-Version=該服務器運行不同的遊戲版本: __1__
servers-connect-Password=該服務器需要密碼
servers-connect-Modded=該服務器需要下載模組
servers-connect-Online=該服務器在線
servers-external=外部連結
servers-open-in-browser=可在你的瀏覽器中打開
backers-tab=支持者
backers-tooltip=支持者
backers-general=他們為服務器運行提供了不少幫助。
backers-management=管理員
backers-staff=職員
backers-backers=議員,支持者,和夥伴
backers-active=活躍玩家
data-tab=資料
data-tooltip=所有已存資料
data-general=資料會自動存到各個服務器中。 你也可以用 /save-data 存一份副本。 也可用 /set-preference 來更改喜好。
data-settings=設定
data-statistics=數據
data-required=需要
data-misc=雜項
data-format=__1____2__
[bonus]
description=取得 / 設定 Bonus 量。
arg-amount=Bonus 數量, 0 來停用。
@@ -34,7 +34,7 @@ for _, config_key in ipairs{ "always_protected_names", "always_protected_types",
end
-- Require roles if a permission is assigned in the config
local Roles --- @type table<string, any>
local Roles
if config.ignore_permission then
Roles = require("modules.exp_legacy.expcore.roles") --- @dep expcore.roles
end
@@ -0,0 +1,204 @@
--[[-- Control Module - Rockets
- Stores rocket stats for each force.
@control Rockets
@alias Rockets
@usage
-- import the module from the control modules
local Rockets = require("modules.exp_legacy.modules.control.rockets") --- @dep modules.control.rockets
-- Some basic information is stored for each silo that has been built
-- the data includes: the tick it was built, the rockets launched from it and more
Rockets.get_silo_data(rocket_silo_entity)
-- Some information is also stored for each force
Rockets.get_stats('player')
-- You can get the rocket data for all silos for a force by using get_silos
Rockets.get_silos('player')
-- You can get the launch time for a rocket, meaning what game tick the 50th rocket was launched
Rockets.get_rocket_time('player', 50)
-- The rolling average will work out the time to launch one rocket based on the last X rockets
Rockets.get_rolling_average('player', 10)
]]
local Event = require("modules/exp_legacy/utils/event") --- @dep utils.event
local Storage = require("modules/exp_util/storage")
local config = require("modules.exp_legacy.config.gui.rockets") --- @dep config.rockets
local largest_rolling_avg = 0
for _, avg_over in pairs(config.stats.rolling_avg) do
if avg_over > largest_rolling_avg then
largest_rolling_avg = avg_over
end
end
local Rockets = {
times = {},
stats = {},
silos = {},
}
local rocket_times = Rockets.times
local rocket_stats = Rockets.stats
local rocket_silos = Rockets.silos
Storage.register({
rocket_times = rocket_times,
rocket_stats = rocket_stats,
rocket_silos = rocket_silos,
}, function(tbl)
Rockets.times = tbl.rocket_times
Rockets.stats = tbl.rocket_stats
Rockets.silos = tbl.rocket_silos
rocket_times = Rockets.times
rocket_stats = Rockets.stats
rocket_silos = Rockets.silos
end)
--- Gets the silo data for a given silo entity
--- @param silo LuaEntity Rocket silo entity
--- @return table # Data table for this silo, contains rockets launch, silo status, and its force
function Rockets.get_silo_data(silo)
return rocket_silos[silo.unit_number]
end
--- Gets the silo entity from its silo name, reverse to get_silo_data
--- @param silo_name string Silo name that is stored in its data
--- @return LuaEntity # Rocket silo entity
function Rockets.get_silo_entity(silo_name)
return rocket_silos[tonumber(silo_name)].entity
end
--- Gets the rocket stats for a force
--- @param force_name string Name of the force to get the stats for
--- @return table # Stats for the force
function Rockets.get_stats(force_name)
return rocket_stats[force_name] or {}
end
--- Gets all the rocket silos that belong to a force
--- @param force_name string Name of the force to get the silos for
--- @return table # Array of silo data that all belong to this force
function Rockets.get_silos(force_name)
local rtn = {}
for _, silo_data in pairs(rocket_silos) do
if silo_data.force == force_name then
table.insert(rtn, silo_data)
end
end
return rtn
end
--- Gets the launch time of a given rocket, due to cleaning not all counts are valid
--- @param force_name string Name of the force to get the count for
--- @param rocket_number number Number of the rocket to get the launch time for
--- @return number? # Game tick that the rocket was launched on
function Rockets.get_rocket_time(force_name, rocket_number)
return rocket_times[force_name] and rocket_times[force_name][rocket_number] or nil
end
--- Gets the number of rockets that a force has launched
--- @param force_name string the name of the force to get the count for
--- @return number # Number of rockets that the force has launched
function Rockets.get_rocket_count(force_name)
local force = game.forces[force_name]
return force.rockets_launched
end
--- Gets the total number of rockets launched by all forces
--- @return number # Total number of rockets launched this game
function Rockets.get_game_rocket_count()
local rtn = 0
for _, force in pairs(game.forces) do
rtn = rtn + force.rockets_launched
end
return rtn
end
--- Gets the rolling average time to launch a rocket
--- @param force_name string Name of the force to get the average for
--- @param count number Distance to get the rolling average over
--- @return number # Number of ticks required to launch one rocket
function Rockets.get_rolling_average(force_name, count)
local force = game.forces[force_name]
local rocket_count = force.rockets_launched
if rocket_count == 0 then return 0 end
local last_launch_time = rocket_times[force_name][rocket_count]
local start_rocket_time = 0
if count < rocket_count then
start_rocket_time = rocket_times[force_name][rocket_count - count + 1]
rocket_count = count
end
return math.floor((last_launch_time - start_rocket_time) / rocket_count)
end
--- When a launch is trigger it will wait for the silo to reset
--- @param event EventData.on_rocket_launch_ordered
Event.add(defines.events.on_rocket_launch_ordered, function(event)
local silo_data = Rockets.get_silo_data(event.rocket_silo)
assert(silo_data, "Rocket silo missing data: " .. tostring(event.rocket_silo))
silo_data.launched = silo_data.launched + 1
silo_data.awaiting_reset = true
end)
--- Event used to update the stats and the hui when a rocket is launched
--- @param event EventData.on_cargo_pod_finished_ascending
Event.add(defines.events.on_cargo_pod_finished_ascending, function(event)
local force = event.cargo_pod.force
local force_name = force.name
local rockets_launched = force.rockets_launched
--- Handles updates to the rocket stats
local stats = rocket_stats[force_name]
if not stats then
rocket_stats[force_name] = {}
stats = rocket_stats[force_name]
end
if rockets_launched == 1 then
stats.first_launch = event.tick
stats.fastest_launch = event.tick
elseif event.tick - stats.last_launch < stats.fastest_launch then
stats.fastest_launch = event.tick - stats.last_launch
end
stats.last_launch = event.tick
--- Appends the new rocket into the array
if not rocket_times[force_name] then
rocket_times[force_name] = {}
end
rocket_times[force_name][rockets_launched] = event.tick
local remove_rocket = rockets_launched - largest_rolling_avg
if remove_rocket > 0 and not table.contains(config.milestones, remove_rocket) then
rocket_times[force_name][remove_rocket] = nil
end
end)
--- Adds a silo to the list when it is built
--- @param event EventData.on_built_entity | EventData.on_robot_built_entity
local function on_built(event)
local entity = event.entity
if entity.valid and entity.name == "rocket-silo" then
rocket_silos[entity.unit_number] = {
name = tostring(entity.unit_number),
force = entity.force.name,
entity = entity,
launched = 0,
awaiting_reset = false,
built = game.tick,
}
end
end
Event.add(defines.events.on_built_entity, on_built)
Event.add(defines.events.on_robot_built_entity, on_built)
return Rockets
@@ -4,7 +4,7 @@ local Gui = require("modules/exp_gui")
local Event = require("modules/exp_legacy/utils/event") --- @dep utils.event
----- Locals -----
local follow_label --- @type ExpElement Gui constructor
local follow_label -- Gui constructor
local following = {}
local spectating = {}
local Public = {}
+37 -32
View File
@@ -14,10 +14,10 @@ local mega = 1000000
local vlayer = {}
local vlayer_data = {
entity_interfaces = {
energy = {}, --- @type LuaEntity[]
circuit = {}, --- @type LuaEntity[]
storage_input = {}, --- @type LuaEntity[]
storage_output = {}, --- @type LuaEntity[]
energy = {},
circuit = {},
storage_input = {},
storage_output = {},
},
properties = {
total_surface_area = 0,
@@ -28,10 +28,10 @@ local vlayer_data = {
capacity = 0,
},
storage = {
items = {}, --- @type table<string, number>
power_items = {}, --- @type table<string, { value: number, count: number }>
items = {},
power_items = {},
energy = 0,
unallocated = {}, --- @type table<string, number>
unallocated = {},
},
surface = table.deep_copy(config.surface),
}
@@ -45,7 +45,7 @@ for name, properties in pairs(config.allowed_items) do
if properties.power then
vlayer_data.storage.power_items[name] = {
value = assert(properties.fuel_value) * 1000000,
value = properties.fuel_value * 1000000,
count = 0,
}
end
@@ -341,15 +341,15 @@ function vlayer.create_input_interface(surface, position, circuit, last_user)
end
if circuit then
for k, _ in pairs(circuit) do
for _, v in pairs(circuit[k]) do
interface.connect_neighbour{ wire = defines.wire_type[k], target_entity = v }
for k, _ in pairs({ defines.wire_connector_id.circuit_red, defines.wire_connector_id.circuit_green }) do
for _, v in pairs(circuit.get_wire_connector{ wire_connector_id = k, or_create = false }.real_connections) do
interface.connect_to{ target = v.target }
end
end
end
interface.destructible = false
interface.minable_flag = false
interface.minable = false
interface.operable = true
return interface
@@ -361,7 +361,7 @@ local function handle_input_interfaces()
if not interface.valid then
vlayer_data.entity_interfaces.storage_input[index] = nil
else
local inventory = assert(interface.get_inventory(defines.inventory.chest))
local inventory = interface.get_inventory(defines.inventory.chest)
for _, v in pairs(inventory.get_contents()) do
if config.allowed_items[v.name] then
@@ -384,9 +384,8 @@ local function handle_input_interfaces()
if count_deduct and count_add then
if config.allowed_items[v.name].modded then
local modded_item = assert(config.modded_items[v.name])
if config.modded_auto_downgrade then
vlayer.insert_item(modded_item.base_game_equivalent, count_add * modded_item.multiplier)
vlayer.insert_item(config.modded_items[v.name].base_game_equivalent, count_add * config.modded_items[v.name].multiplier)
else
vlayer.insert_item(v.name, count_add)
end
@@ -420,15 +419,15 @@ function vlayer.create_output_interface(surface, position, circuit, last_user)
end
if circuit then
for k, _ in pairs(circuit) do
for _, v in pairs(circuit[k]) do
interface.connect_neighbour{ wire = defines.wire_type[k], target_entity = v }
for k, _ in pairs({ defines.wire_connector_id.circuit_red, defines.wire_connector_id.circuit_green }) do
for _, v in pairs(circuit.get_wire_connector{ wire_connector_id = k, or_create = false }.real_connections) do
interface.connect_to{ target = v.target }
end
end
end
interface.destructible = false
interface.minable_flag = false
interface.minable = false
interface.operable = true
return interface
@@ -440,14 +439,14 @@ local function handle_output_interfaces()
if not interface.valid then
vlayer_data.entity_interfaces.storage_output[index] = nil
else
local inventory = assert(interface.get_inventory(defines.inventory.chest))
local inventory_request_sections = assert(interface.get_logistic_sections()).sections
local inventory = interface.get_inventory(defines.inventory.chest)
local inventory_request_sections = interface.get_logistic_sections().sections
for i = 1, #inventory_request_sections do
for _, v in pairs(inventory_request_sections[i].filters) do
if v.value and config.allowed_items[v.value.name] then
local current_amount = inventory.get_item_count(v.value.name)
local request_amount = math.min(assert(v.min) - current_amount, vlayer_data.storage.items[v.value.name])
local request_amount = math.min(v.min - current_amount, vlayer_data.storage.items[v.value.name])
if request_amount > 0 and inventory.can_insert{ name = v.value.name, count = request_amount } then
local removed_item_count = vlayer.remove_item(v.value.name, request_amount)
@@ -515,6 +514,9 @@ function vlayer.get_statistics()
energy_sustained = vdp * gsm,
energy_capacity = vlayer_data.properties.capacity * mega,
energy_storage = vlayer_data.storage.energy,
day_time = math.floor(vlayer_data.surface.daytime * vlayer_data.surface.ticks_per_day),
day_length = vlayer_data.surface.ticks_per_day,
tick = game.tick,
}
end
@@ -538,6 +540,9 @@ function vlayer.get_circuits()
energy_sustained = "signal-S",
energy_capacity = "signal-C",
energy_storage = "signal-E",
day_time = "signal-D",
day_length = "signal-L",
tick = "signal-T",
}
end
@@ -561,15 +566,15 @@ function vlayer.create_circuit_interface(surface, position, circuit, last_user)
end
if circuit then
for k, _ in pairs(circuit) do
for _, v in pairs(circuit[k]) do
interface.connect_neighbour{ wire = defines.wire_type[k], target_entity = v }
for k, _ in pairs({ defines.wire_connector_id.circuit_red, defines.wire_connector_id.circuit_green }) do
for _, v in pairs(circuit.get_wire_connector{ wire_connector_id = k, or_create = false }.real_connections) do
interface.connect_to{ target = v.target }
end
end
end
interface.destructible = false
interface.minable_flag = false
interface.minable = false
interface.operable = true
return interface
@@ -583,11 +588,11 @@ local function handle_circuit_interfaces()
if not interface.valid then
vlayer_data.entity_interfaces.circuit[index] = nil
else
local control = interface.get_or_create_control_behavior() --[[@as LuaConstantCombinatorControlBehavior]]
if control.sections_count == 0 then
control.add_section()
local circuit_oc = interface.get_or_create_control_behavior()
if circuit_oc.sections_count == 0 then
circuit_oc.add_section()
end
local circuit_oc = assert(control.sections[1])
circuit_oc = circuit_oc.sections[1]
local signal_index = 1
local circuit = vlayer.get_circuits()
@@ -618,7 +623,7 @@ local function handle_circuit_interfaces()
-- Clear remaining signals to prevent outdated values being present (caused by count > 0 check)
for clear_index = signal_index, #circuit do
if not circuit_oc.get_slot(clear_index).value then
if not circuit_oc.get_slot(clear_index).signal then
break -- There are no more signals to clear
end
@@ -648,7 +653,7 @@ function vlayer.create_energy_interface(surface, position, last_user)
end
interface.destructible = false
interface.minable_flag = false
interface.minable = false
interface.operable = false
interface.electric_buffer_size = 0
interface.power_production = 0
@@ -107,7 +107,7 @@ function Warnings.add_warning(player, by_player_name, reason)
reason = reason or "None given."
local warning_count --- @type number
local warning_count
PlayerWarnings:update(player.name, function(_, warnings)
local warning = {
by_player_name = by_player_name,
+5 -1
View File
@@ -182,13 +182,17 @@ function Warps.make_warp_area(warp_id)
}
entity.destructible = false
entity.health = 0
entity.minable_flag = false
entity.minable = false
entity.rotatable = false
-- Save reference of the last power pole
if entity.type == "electric-pole" then
warp.electric_pole = entity
end
if entity.name == "small-lamp" then
entity.always_on = true
end
end
end
+99
View File
@@ -0,0 +1,99 @@
--[[-- Commands Module - Bonus
- Adds a command that allows players to have increased stats
@data Bonus
]]
local Roles = require("modules.exp_legacy.expcore.roles") --- @dep expcore.roles
local Event = require("modules/exp_legacy/utils/event") --- @dep utils.event
local config = require("modules.exp_legacy.config.bonus") --- @dep config.bonuses
local Commands = require("modules/exp_commands")
-- Stores the bonus for the player
local PlayerData = require("modules.exp_legacy.expcore.player_data") --- @dep expcore.player_data
local PlayerBonus = PlayerData.Settings:combine("Bonus")
PlayerBonus:set_default(0)
PlayerBonus:set_metadata{
permission = "command/bonus",
stringify = function(value)
if not value or value == 0 then
return "None set"
end
return value
end,
}
--- Apply a bonus to a player
local function apply_bonus(player, stage)
if not player.character then
return
end
for k, v in pairs(config.player_bonus) do
player[k] = v.value * stage / 10
if v.combined_bonus then
for i = 1, #v.combined_bonus, 1 do
player[v.combined_bonus[i]] = v.value * stage / 10
end
end
end
end
--- When store is updated apply new bonus to the player
PlayerBonus:on_update(function(player_name, player_bonus)
apply_bonus(game.players[player_name], player_bonus or 0)
end)
--- Changes the amount of bonus you receive
Commands.new("bonus", { "bonus.description" })
:optional("amount", { "bonus.arg-amount" }, Commands.types.integer_range(0, 10))
:register(function(player, amount)
--- @cast amount number?
if amount then
PlayerBonus:set(player, amount)
return Commands.status.success{ "bonus.set", amount }
else
return Commands.status.success{ "bonus.get", PlayerBonus:get(player) }
end
end)
--- When a player respawns re-apply bonus
Event.add(defines.events.on_player_respawned, function(event)
local player = game.players[event.player_index]
apply_bonus(player, PlayerBonus:get(player))
end)
--- Remove bonus if a player no longer has access to the command
local function role_update(event)
local player = game.players[event.player_index]
if not Roles.player_allowed(player, "command/bonus") then
apply_bonus(player, 0)
end
end
--- When a player dies allow them to have instant respawn
Event.add(defines.events.on_player_died, function(event)
local player = game.players[event.player_index]
if Roles.player_has_flag(player, "instant-respawn") then
player.ticks_to_respawn = 120
end
end)
Event.add(defines.events.on_player_created, function(event)
if event.player_index ~= 1 then
return
end
for k, v in pairs(config.force_bonus) do
game.players[event.player_index].force[k] = v.value
end
for k, v in pairs(config.surface_bonus) do
game.players[event.player_index].surface[k] = v.value
end
end)
Event.add(Roles.events.on_role_assigned, role_update)
Event.add(Roles.events.on_role_unassigned, role_update)
@@ -5,10 +5,6 @@ local Event = require("modules/exp_legacy/utils/event") --- @dep utils.event
local PlayerData = require("modules.exp_legacy.expcore.player_data") --- @dep expcore.player_data
local LocalLanguage = PlayerData.Statistics:combine("LocalLanguage")
LocalLanguage:set_default("Unknown")
LocalLanguage:set_metadata{
name = { "exp-statistics.Locale" },
tooltip = { "exp-statistics.Locale-tooltip" },
}
local function set_locale(event)
local player = game.players[event.player_index]
@@ -0,0 +1,89 @@
local Commands = require("modules/exp_commands")
local config = require("modules.exp_legacy.config.personal_logistic") --- @dep config.personal-logistic
---@param target LuaEntity | LuaPlayer
---@param amount number
local function pl(target, amount)
local c
local s
--- @cast target any Remove cast once implemented
error("Needs updating to use 2.0 logistics")
if target.object_name == "LuaPlayer" then
c = target.clear_personal_logistic_slot
s = target.set_personal_logistic_slot
else
c = target.clear_vehicle_logistic_slot
s = target.set_vehicle_logistic_slot
end
for _, v in pairs(config.request) do
c(config.start + v.key)
end
if (amount < 0) then
return
end
local stats = target.force.get_item_production_statistics(target.surface)
for k, v in pairs(config.request) do
local v_min = math.ceil(v.min * amount)
local v_max = math.ceil(v.max * amount)
if v.stack ~= nil and v.stack ~= 1 and v.type ~= "weapon" then
v_min = math.floor(v_min / v.stack) * v.stack
v_max = math.ceil(v_max / v.stack) * v.stack
end
if v.upgrade_of == nil then
if v.type ~= nil then
if stats.get_input_count(k) < config.production_required[v.type] then
if v_min > 0 then
if v_min == v_max then
v_min = math.floor((v_max * 0.5) / v.stack) * v.stack
end
else
v_min = 0
end
end
end
s(config.start + v.key, { name = k, min = v_min, max = v_max })
else
if v.type ~= nil then
if stats.get_input_count(k) >= config.production_required[v.type] then
s(config.start + v.key, { name = k, min = v_min, max = v_max })
local vuo = v.upgrade_of
while (vuo ~= nil) do
s(config.start + config.request[vuo].key, { name = vuo, min = 0, max = 0 })
vuo = config.request[vuo].upgrade_of
end
else
s(config.start + v.key, { name = k, min = 0, max = v_max })
end
end
end
end
end
Commands.new("personal-logistic", "Set Personal Logistic (-1 to cancel all) (Select spidertron to edit spidertron)")
:argument("amount", "", Commands.types.integer_range(-1, 10))
:add_aliases{ "pl" }
:add_flags{ "disabled" } -- Remove once implemented above
:register(function(player, amount)
--- @cast amount number
if player.force.technologies["logistic-robotics"].researched then
if player.selected ~= nil then
if player.selected.name == "spidertron" then
pl(player.selected, amount / 10)
end
else
pl(player, amount / 10)
end
else
return Commands.status.error("Personal Logistic not researched")
end
end)
@@ -43,14 +43,10 @@ local ignored_items = {
--- Saves your quickbar preset to the script-output folder
Commands.new("save-quickbar", "Saves your Quickbar preset items to file")
:add_aliases{ "save-toolbar" }
:add_flags{ "disabled" }
:register(function(player)
local filters = {}
error("2.1 changes to get_quick_bar_slot beak compatibility with 2.0; waiting for upstream")
-- Upstream may add method to compat, or change inventory sync to have a quickbar only mode
for i = 1, 100 do
--[[
local slot = player.get_quick_bar_slot(i)
-- Need to filter out blueprint and blueprint books because the slot is a LuaItemPrototype and does not contain a way to export blueprint data
if slot ~= nil then
@@ -59,7 +55,6 @@ Commands.new("save-quickbar", "Saves your Quickbar preset items to file")
filters[i] = slot.name
end
end
]]
end
if next(filters) then
+1 -1
View File
@@ -18,7 +18,7 @@ ToolbarState:on_load(function(player_name, value)
local decompressed = helpers.json_to_table(assert(helpers.decode_string(value), "Failed String Decode"))
local player = assert(game.get_player(player_name))
Gui.toolbar.set_state(player, decompressed --[[@as Gui.ToolbarState]])
Gui.toolbar.set_state(player, decompressed --[[ @as Gui.ToolbarState ]])
return nil -- We don't save the state, use Gui.toolbar.get_state
end)
@@ -126,19 +126,19 @@ lib.collect_loginet = function()
end
if config.modules.logistorage then
for _, item in pairs(network.get_contents()) do
stats.items[item.name] = (stats.items[item.name] or 0) + item.count
for name, v in pairs(network.get_contents()) do
stats.items[name] = (stats.items[name] or 0) + v
end
-- pickups and deliveries of items
for _, point_list in pairs{ network.provider_points, network.requester_points, network.storage_points } do
for _, point in pairs(point_list) do
for _, item in pairs(point.targeted_items_pickup) do
stats.pickups[item.name] = (stats.pickups[item.name] or 0) + item.count
for name, qty in pairs(point.targeted_items_pickup) do
stats.pickups[name] = (stats.pickups[name] or 0) + qty
end
for _, item in pairs(point.targeted_items_deliver) do
stats.deliveries[item.name] = (stats.deliveries[item.name] or 0) + item.count
for name, qty in pairs(point.targeted_items_deliver) do
stats.deliveries[name] = (stats.deliveries[name] or 0) + qty
end
end
end
@@ -2,6 +2,6 @@ local Gui = require("modules/exp_gui")
local Roles = require("modules.exp_legacy.expcore.roles")
local Event = require("modules/exp_legacy/utils/event")
--- @diagnostic disable: access-invisible
--- @diagnostic disable invisible
Event.add(Roles.events.on_role_assigned, Gui._ensure_consistency)
Event.add(Roles.events.on_role_unassigned, Gui._ensure_consistency)
@@ -1,5 +1,5 @@
local Gui = require("modules/exp_scenario/gui/debug/shim")
local Model = require("modules/exp_scenario/gui/debug/model")
local Gui = require("modules.exp_legacy.utils.gui") --- @dep utils.gui
local Model = require("modules.exp_legacy.modules.gui.debug.model") --- @dep modules.gui.debug.model
local Color = require("modules/exp_util/include/color")
local dump = Model.dump
@@ -1,6 +1,7 @@
local Event = require("modules/exp_legacy/utils/event")
local Storage = require("modules/exp_util/storage")
local Gui = require("modules/exp_scenario/gui/debug/shim")
local Model = require("modules/exp_scenario/gui/debug/model")
local Gui = require("modules.exp_legacy.utils.gui")
local Model = require("modules.exp_legacy.modules.gui.debug.model")
local format = string.format
local insert = table.insert
@@ -13,7 +14,6 @@ local events_to_keep = 10
-- Local vars
local Public = {
name = "Events",
events = {}
}
local name_lookup = {}
@@ -168,7 +168,7 @@ Gui.on_click(
-- Event registers (TODO: turn to removable hooks.. maybe)
for name, id in pairs(events) do
name_lookup[id] = name
Public.events[id] = event_callback
Event.add(id, event_callback)
end
return Public
@@ -1,7 +1,7 @@
local Gui = require("modules/exp_scenario/gui/debug/shim")
local Datastore = require("modules.exp_legacy.expcore.datastore")
local Gui = require("modules.exp_legacy.utils.gui") --- @dep utils.gui
local Datastore = require("modules.exp_legacy.expcore.datastore") --- @dep expcore.datastore
local Color = require("modules/exp_util/include/color")
local Model = require("modules/exp_scenario/gui/debug/model")
local Model = require("modules.exp_legacy.modules.gui.debug.model") --- @dep modules.gui.debug.model
local dump = Model.dump
local concat = table.concat
@@ -3,8 +3,8 @@ local ExpData = require("modules/exp_gui/data")
local ExpIter = require("modules/exp_gui/iter")
local Color = require("modules/exp_util/include/color")
local Gui = require("modules/exp_scenario/gui/debug/shim")
local Model = require("modules/exp_scenario/gui/debug/model")
local Gui = require("modules.exp_legacy.utils.gui") --- @dep utils.gui
local Model = require("modules.exp_legacy.modules.gui.debug.model") --- @dep modules.gui.debug.model
local dump = Model.dump
local dump_text = Model.dump_text
@@ -27,7 +27,7 @@ function Public.show(container)
local left_panel_style = left_panel.style
left_panel_style.width = 300
--- @diagnostic disable-next-line: access-invisible
--- @diagnostic disable-next-line invisible
for element_name in pairs(ExpElement._elements) do
local header = left_panel.add{ type = "flow" }.add{ type = "label", name = header_name, caption = element_name }
Gui.set_data(header, element_name)
@@ -92,18 +92,18 @@ Gui.on_click(
input_text_box.text = concat{ "ExpElement._elements[\"", element_name, "\"]" }
input_text_box.style.font_color = Color.black
--- @diagnostic disable-next-line: access-invisible
--- @diagnostic disable-next-line invisible
local define = ExpElement._elements[element_name]
local content = dump({
--- @diagnostic disable-next-line: access-invisible
--- @diagnostic disable-next-line invisible
debug = define._debug,
--- @diagnostic disable-next-line: access-invisible
--- @diagnostic disable-next-line invisible
has_handlers = define._has_handlers,
--- @diagnostic disable-next-line: access-invisible
--- @diagnostic disable-next-line invisible
track_elements = define._track_elements,
--- @diagnostic disable-next-line: access-invisible
--- @diagnostic disable-next-line invisible
elements = ExpIter._scopes[element_name],
--- @diagnostic disable-next-line: access-invisible
--- @diagnostic disable-next-line invisible
data = ExpData._scopes[element_name]._raw,
}) or "nil"
right_panel.text = content
@@ -1,5 +1,5 @@
local Gui = require("modules/exp_scenario/gui/debug/shim")
local Model = require("modules/exp_scenario/gui/debug/model")
local Gui = require("modules.exp_legacy.utils.gui") --- @dep utils.gui
local Model = require("modules.exp_legacy.modules.gui.debug.model") --- @dep modules.gui.debug.model
local Color = require("modules/exp_util/include/color")
local dump = Model.dump
@@ -1,16 +1,16 @@
local Gui = require("modules/exp_scenario/gui/debug/shim")
local Gui = require("modules.exp_legacy.utils.gui") --- @dep utils.gui
local Color = require("modules/exp_util/include/color")
local Public = {}
local pages = {
require("modules/exp_scenario/gui/debug/redmew_global_view"),
require("modules/exp_scenario/gui/debug/expcore_datastore_view"),
require("modules/exp_scenario/gui/debug/expcore_gui_view"),
require("modules/exp_scenario/gui/debug/global_view"),
require("modules/exp_scenario/gui/debug/package_view"),
require("modules/exp_scenario/gui/debug/_g_view"),
require("modules/exp_scenario/gui/debug/event_view"),
require("modules.exp_legacy.modules.gui.debug.redmew_global_view"),
require("modules.exp_legacy.modules.gui.debug.expcore_datastore_view"),
require("modules.exp_legacy.modules.gui.debug.expcore_gui_view"),
require("modules.exp_legacy.modules.gui.debug.global_view"),
require("modules.exp_legacy.modules.gui.debug.package_view"),
require("modules.exp_legacy.modules.gui.debug._g_view"),
require("modules.exp_legacy.modules.gui.debug.event_view"),
}
local main_frame_name = Gui.uid_name()
@@ -1,10 +1,10 @@
local Gui = require("modules/exp_scenario/gui/debug/shim") --- @dep utils.gui
local Gui = require("modules.exp_legacy.utils.gui") --- @dep utils.gui
local ExpUtil = require("modules/exp_util")
local concat = table.concat
local inspect = table.inspect
local pcall = pcall
local loadstring = load
local loadstring = loadstring --- @diagnostic disable-line
local rawset = rawset
local Public = {}
@@ -1,6 +1,6 @@
local Gui = require("modules/exp_scenario/gui/debug/shim") --- @dep utils.gui
local Gui = require("modules.exp_legacy.utils.gui") --- @dep utils.gui
local Color = require("modules/exp_util/include/color")
local Model = require("modules/exp_scenario/gui/debug/model") --- @dep modules.gui.debug.model
local Model = require("modules.exp_legacy.modules.gui.debug.model") --- @dep modules.gui.debug.model
local dump_function = Model.dump_function
local loaded = _G.package.loaded
@@ -1,7 +1,7 @@
local Gui = require("modules/exp_scenario/gui/debug/shim") --- @dep utils.gui
local Gui = require("modules.exp_legacy.utils.gui") --- @dep utils.gui
local Storage = require("modules/exp_util/storage")
local Color = require("modules/exp_util/include/color")
local Model = require("modules/exp_scenario/gui/debug/model") --- @dep modules.gui.debug.model
local Model = require("modules.exp_legacy.modules.gui.debug.model") --- @dep modules.gui.debug.model
local dump = Model.dump
local dump_text = Model.dump_text
@@ -24,7 +24,7 @@ function Public.show(container)
local left_panel_style = left_panel.style
left_panel_style.width = 300
--- @diagnostic disable-next-line: access-invisible
--- @diagnostic disable-next-line invisible
for token_id in pairs(Storage._registered) do
local header = left_panel.add{ type = "flow" }.add{ type = "label", name = header_name, caption = token_id }
Gui.set_data(header, token_id)

Some files were not shown because too many files have changed in this diff Show More